Una mirada en profundidad al tipo de carácter de Java

La versión 1.1 de Java presenta una serie de clases para tratar con personajes. Estas nuevas clases crean una abstracción para convertir de una noción de valores de caracteres específica de la plataforma en valores Unicode . Esta columna analiza lo que se ha agregado y las motivaciones para agregar estas clases de personajes.

Escriba char

Quizás el tipo base más abusado en el lenguaje C es el tipo char . Se abusa del tipo char en parte porque se define en 8 bits, y durante los últimos 25 años, 8 bits también ha definido la porción de memoria indivisible más pequeña en las computadoras. Cuando combina este último hecho con el hecho de que el conjunto de caracteres ASCII se definió para que quepa en 7 bits, el tipo char hace un tipo "universal" muy conveniente. Además, en C, un puntero a una variable de tipo char se convirtió en el tipo de puntero universal porque cualquier cosa que pudiera ser referenciada como char también podría ser referenciada como cualquier otro tipo mediante el uso de casting.

El uso y abuso del tipo char en el lenguaje C dio lugar a muchas incompatibilidades entre las implementaciones del compilador, por lo que en el estándar ANSI para C, se realizaron dos cambios específicos: el puntero universal se redefinió para tener un tipo de vacío, por lo que se requiere un explícito declaración del programador; y se consideró que el valor numérico de los caracteres estaba firmado, definiendo así cómo serían tratados cuando se usaran en cálculos numéricos. Luego, a mediados de la década de 1980, ingenieros y usuarios se dieron cuenta de que 8 bits eran insuficientes para representar a todos los personajes del mundo. Desafortunadamente, en ese momento, C estaba tan arraigado que la gente no estaba dispuesta, tal vez ni siquiera, a cambiar la definición de char.tipo. Ahora avancemos a los años 90, a los inicios de Java. Uno de los muchos principios establecidos en el diseño del lenguaje Java fue que los caracteres serían de 16 bits. Esta opción admite el uso de Unicode , una forma estándar de representar muchos tipos diferentes de caracteres en muchos idiomas diferentes. Desafortunadamente, también preparó el escenario para una variedad de problemas que solo ahora se están rectificando.

¿Qué es un personaje de todos modos?

Sabía que estaba en problemas cuando me encontré haciendo la pregunta: "Entonces, ¿qué es un personaje?" Bueno, un personaje es una letra, ¿verdad? Un montón de letras forman una palabra, las palabras forman frases, etc. Sin embargo, la realidad es que la relación entre la representación de un carácter en una pantalla de computadora, llamada su glifo , y el valor numérico que especifica ese glifo, llamado a code point, no es realmente sencilla en absoluto.

Me considero afortunado de ser un hablante nativo del idioma inglés. Primero, porque era el lenguaje común de un número significativo de quienes contribuyeron al diseño y desarrollo de la computadora digital moderna; segundo, porque tiene un número relativamente pequeño de glifos. Hay 96 caracteres imprimibles en la definición ASCII que se pueden usar para escribir en inglés. Compare esto con el chino, donde hay más de 20.000 glifos definidos y esa definición es incompleta. Desde sus inicios en el código Morse y Baudot, la simplicidad general (pocos glifos, frecuencia estadística de aparición) del idioma inglés lo ha convertido en la lingua-franca de la era digital. Pero a medida que ha aumentado el número de personas que ingresan a la era digital, también lo ha hecho el número de hablantes no nativos de inglés. A medida que crecían los números,cada vez más personas estaban cada vez menos dispuestas a aceptar que las computadoras usaran ASCII y solo hablaran inglés. Esto aumentó en gran medida el número de "caracteres" que las computadoras necesitaban comprender. Como resultado, la cantidad de glifos codificados por computadoras tuvo que duplicarse.

El número de caracteres disponibles se duplicó cuando el venerable código ASCII de 7 bits se incorporó a una codificación de caracteres de 8 bits llamada ISO Latin-1 (o ISO 8859_1, siendo "ISO" la Organización Internacional de Normalización). Como puede haber recopilado por el nombre de codificación, este estándar permitió la representación de muchos de los idiomas derivados del latín utilizados en el continente europeo. Sin embargo, el hecho de que se creara el estándar no significaba que fuera utilizable. En ese momento, muchas computadoras ya habían comenzado a usar los otros 128 "caracteres" que podrían estar representados por un carácter de 8 bits con alguna ventaja. Los dos ejemplos supervivientes del uso de estos caracteres adicionales son la computadora personal de IBM (PC) y la terminal de computadora más popular de la historia, la Digital Equipment Corporation VT-100.Este último sigue vivo en forma de software emulador de terminal.

El momento real de la muerte del carácter de 8 bits sin duda se debatirá durante décadas, pero lo relaciono con la introducción de la computadora Macintosh en 1984. La Macintosh trajo dos conceptos muy revolucionarios a la informática convencional: fuentes de caracteres que se almacenaban en RAM; y WorldScript, que podría usarse para representar caracteres en cualquier idioma. Por supuesto, esto era simplemente una copia de lo que Xerox había estado enviando en sus máquinas de la clase Dandelion en forma del sistema de procesamiento de texto Star, pero Macintosh trajo estos nuevos conjuntos de caracteres y fuentes a una audiencia que todavía usaba terminales "tontos". . Una vez iniciado, el uso de diferentes fuentes no podía detenerse, era demasiado atractivo para demasiada gente. A finales de los 80,la presión para estandarizar el uso de todos estos caracteres llegó a un punto crítico con la formación del Consorcio Unicode, que publicó su primera especificación en 1990. Desafortunadamente, durante los años 80 e incluso en los 90, el número de juegos de caracteres se multiplicó. Muy pocos de los ingenieros que estaban creando nuevos códigos de caracteres en ese momento consideraron viable el incipiente estándar Unicode, por lo que crearon sus propias asignaciones de códigos a glifos. Entonces, aunque Unicode no fue bien aceptado, la noción de que solo había 128 o como máximo 256 caracteres disponibles definitivamente desapareció. Después de Macintosh, la compatibilidad con diferentes fuentes se convirtió en una característica imprescindible para el procesamiento de texto. Los caracteres de ocho bits se estaban extinguiendo.80 e incluso en los 90, el número de juegos de caracteres se multiplicó. Muy pocos de los ingenieros que estaban creando nuevos códigos de caracteres en ese momento consideraron viable el incipiente estándar Unicode, por lo que crearon sus propias asignaciones de códigos a glifos. Entonces, aunque Unicode no fue bien aceptado, la noción de que solo había 128 o como máximo 256 caracteres disponibles definitivamente desapareció. Después de Macintosh, la compatibilidad con diferentes fuentes se convirtió en una característica imprescindible para el procesamiento de texto. Los caracteres de ocho bits se estaban extinguiendo.80 e incluso en los 90, el número de juegos de caracteres se multiplicó. Muy pocos de los ingenieros que estaban creando nuevos códigos de caracteres en ese momento consideraron viable el incipiente estándar Unicode, por lo que crearon sus propias asignaciones de códigos a glifos. Entonces, si bien Unicode no fue bien aceptado, la idea de que solo había 128 o como máximo 256 caracteres disponibles definitivamente desapareció. Después de Macintosh, la compatibilidad con diferentes fuentes se convirtió en una característica imprescindible para el procesamiento de texto. Los caracteres de ocho bits se estaban extinguiendo.la idea de que solo había 128 o como máximo 256 caracteres disponibles había desaparecido definitivamente. Después de Macintosh, la compatibilidad con diferentes fuentes se convirtió en una característica imprescindible para el procesamiento de texto. Los caracteres de ocho bits se estaban extinguiendo.la idea de que solo había 128 o como máximo 256 caracteres disponibles había desaparecido definitivamente. Después de Macintosh, la compatibilidad con diferentes fuentes se convirtió en una característica imprescindible para el procesamiento de texto. Los caracteres de ocho bits se estaban extinguiendo.

Java y Unicode

Entré en la historia en 1992 cuando me uní al grupo Oak (el lenguaje Java se llamaba Oak cuando se desarrolló por primera vez) en Sun. El tipo de base charse definió en 16 bits sin firmar, el único tipo sin firmar en Java. El fundamento del carácter de 16 bits era que admitiría cualquier representación de carácter Unicode, lo que haría que Java fuera adecuado para representar cadenas en cualquier lenguaje compatible con Unicode. Pero poder representar la cadena y poder imprimirla siempre han sido problemas separados. Dado que la mayor parte de la experiencia en el grupo Oak provino de sistemas Unix y sistemas derivados de Unix, el conjunto de caracteres más cómodo fue, nuevamente, ISO Latin-1. Además, con la herencia Unix del grupo, el sistema de E / S de Java se modeló en gran parte en la abstracción de flujo de Unix mediante el cual cada dispositivo de E / S podría representarse mediante un flujo de bytes de 8 bits. Esta combinación dejó una especie de error en el lenguaje entre un dispositivo de entrada de 8 bits y los caracteres de 16 bits de Java. Así,en cualquier lugar donde las cadenas de Java tuvieran que leerse o escribirse en un flujo de 8 bits, había un pequeño fragmento de código, un truco, para mapear mágicamente caracteres de 8 bits en unicode de 16 bits.

En las versiones 1.0 del Java Developer Kit (JDK), el truco de entrada estaba en la DataInputStreamclase y el de salida era toda la PrintStreamclase. (En realidad, había una clase de entrada nombrada TextInputStreamen la versión alfa 2 de Java, pero fue reemplazada por el DataInputStreamtruco en la versión real). Esto continúa causando problemas para los programadores principiantes de Java, ya que buscan desesperadamente el equivalente en Java de C función getc(). Considere el siguiente programa Java 1.0:

importar java.io. *; public class falso {public static void main (String args []) {FileInputStream fis; DataInputStream dis; char c; intente {fis = new FileInputStream ("data.txt"); dis = nuevo DataInputStream (fis); while (verdadero) {c = dis.readChar (); System.out.print (c); System.out.flush (); si (c == '\ n') romper; } fis.close (); } catch (Exception e) {} System.exit (0); }}

A primera vista, este programa parece abrir un archivo, leerlo un carácter a la vez y salir cuando se lee la primera línea nueva. Sin embargo, en la práctica, lo que se obtiene es salida basura. Y la razón por la que recibe basura es que readChar lee caracteres Unicode de 16 bits e System.out.printimprime lo que supone que son caracteres ISO Latin-1 de 8 bits. Sin embargo, si cambia el programa anterior para usar la función readLineDataInputStream , parecerá que funciona porque el código en readLinelee un formato que se define con un guiño a la especificación Unicode como "UTF-8 modificado". (UTF-8 es el formato que Unicode especifica para representar caracteres Unicode en un flujo de entrada de 8 bits). Entonces, la situación en Java 1.0 es que las cadenas de Java están compuestas por caracteres Unicode de 16 bits, pero solo hay un mapeo que mapea Caracteres ISO Latin-1 en Unicode. Afortunadamente, Unicode define la página de códigos "0", es decir, los 256 caracteres cuyos 8 bits superiores son todos cero, para que se correspondan exactamente con el conjunto ISO Latin-1. Por lo tanto, el mapeo es bastante trivial, y mientras solo esté usando archivos de caracteres ISO Latin-1, no tendrá ningún problema cuando los datos abandonen un archivo, sean manipulados por una clase Java y luego reescritos en un archivo. .

There were two problems with burying the input conversion code into these classes: Not all platforms stored their multilingual files in modified UTF-8 format; and certainly, the applications on these platforms didn't necessarily expect non-Latin characters in this form. Therefore, the implementation support was incomplete, and there was no easy way to add the needed support in a later release.

Java 1.1 and Unicode

The Java 1.1 release introduced an entirely new set of interfaces for handling characters, called Readers and Writers. I modified the class named bogus from above into a class named cool. The cool class uses an InputStreamReader class to process the file rather than the DataInputStream class. Note that InputStreamReader is a subclass of the new Reader class and the System.out is now a PrintWriter object, which is a subclass of the Writer class. The code for this example is shown below:

import java.io.*; public class cool { public static void main(String args[]) { FileInputStream fis; InputStreamReader irs; char c; try { fis = new FileInputStream("data.txt"); irs = new InputStreamReader(fis); System.out.println("Using encoding : "+irs.getEncoding()); while (true) { c = (char) irs.read(); System.out.print(c); System.out.flush(); if (c == '\n') break; } fis.close(); } catch (Exception e) { } System.exit(0); } } 

The primary difference between this example and the previous code listing is the use of the InputStreamReader class rather than the DataInputStream class. Another way in which this example is different from the previous one is that there is an additional line that prints out the encoding used by the InputStreamReader class.

The important point is that the existing code, once undocumented (and ostensibly unknowable) and embedded inside the implementation of the getChar method of the DataInputStream class, has been removed (actually its use is deprecated; it will be removed in a future release). In the 1.1 version of Java, the mechanism that performs the conversion is now encapsulated in the Reader class. This encapsulation provides a way for the Java class libraries to support many different external representations of non-Latin characters while always using Unicode internally.

Of course, like the original I/O subsystem design, there are symmetric counterparts to the reading classes that perform writing. The class OutputStreamWriter can be used to write strings to an output stream, the class BufferedWriter adds a layer of buffering, and so on.

Trading warts or real progress?

The somewhat lofty goal of the design of the Reader and Writerclasses was to tame what is currently a hodge-podge of representation standards for the same information by providing a standard way of converting back and forth between the legacy representation -- be it Macintosh Greek or Windows Cyrillic -- and Unicode. So, a Java class that deals with strings need not change when it moves from platform to platform. This might be the end of the story, except that now that the conversion code is encapsulated, the question arises as to what that code assumes.

Mientras investigaba esta columna, me acordé de una cita famosa de un ejecutivo de Xerox (antes de que fuera Xerox, cuando era Haloid Company) acerca de que la fotocopiadora era superflua porque era bastante fácil para una secretaria poner un trozo de papel carbón en su máquina de escribir y hacer una copia de un documento mientras estaba creando el original. Por supuesto, lo que es obvio en retrospectiva es que la fotocopiadora beneficia mucho más a la persona que recibe un documento que a una persona que genera un documento. JavaSoft ha mostrado una falta similar de conocimiento sobre el uso de las clases de codificación y decodificación de caracteres en el diseño de esta parte del sistema.