Java 101: los entresijos de la entrada / salida estándar

En artículos anteriores de Java 101 , me referí a los conceptos de redirección, dispositivo de entrada estándar y dispositivo de salida estándar. Para demostrar la entrada de datos, se llamaron varios ejemplos System.in.read(). Resulta que System.in.read()ingresa datos del dispositivo de entrada estándar. Para demostrar la salida de datos, ejemplos llamados System.out.print()y System.out.println(). A diferencia de System.in.read(), esos métodos , secuencias con nombre de código ejecutable (que se explorarán en el artículo del próximo mes), envían su salida al dispositivo de salida estándar. ¿Quiere saber más sobre los conceptos de E / S estándar? ¡Sigue leyendo!

La E / S estándar es un mecanismo de entrada / salida estandarizado que se origina en el sistema operativo Unix. Aunque este mecanismo se usa principalmente con sistemas operativos más antiguos que no son GUI, la E / S estándar todavía juega un papel en los sistemas operativos GUI (interfaz gráfica de usuario) modernos, donde la gente lo usa para depurar programas que funcionan mal y para enseñar entrada / salida en entrada- cursos de programación de nivel.

Como probablemente habrá adivinado, la E / S estándar usa dispositivos para ingresar y enviar datos. Estos dispositivos incluyen entrada estándar, salida estándar y error estándar.

Entrada estándar

El dispositivo de entrada estándar es la parte del sistema operativo que controla desde dónde recibe un programa su entrada. De forma predeterminada, el dispositivo de entrada estándar lee esa entrada desde un controlador de dispositivo conectado al teclado. Sin embargo, puede redirigir, o cambiar, la fuente de entrada a un controlador de dispositivo adjunto a un archivo de modo que la entrada parezca venir "mágicamente" de un archivo, en lugar del teclado.

Un programa ingresa sus datos desde el dispositivo de entrada estándar llamando al System.in.read()método de Java . Busque en la documentación del SDK y descubrirá una clase llamada System. Esa clase contiene una variable llamada in: un objeto creado a partir de una subclase de InputStream. El carácter de punto después de los Systemestados que inpertenece a Systemy el carácter de punto después de los inestados a los que read()pertenece in. En otras palabras, read()es un método que pertenece a un objeto llamado in, que a su vez pertenece a una clase llamada System. (Discutiré más sobre clases, objetos y "pertenecer a" el próximo mes).

System.in.read()no toma argumentos y devuelve un entero, lo que ha llevado a algunos a creer que System.in.read()devuelve números enteros introducidos por el usuario. Para aclarar, System.in.read()devuelve un código ASCII de 7 bits de una tecla (si el dispositivo de entrada estándar está configurado en el teclado) o un byte de 8 bits de un archivo (si el dispositivo de entrada estándar se ha redirigido desde el teclado a un archivo). En cualquier caso, System.in.read()convierte el código en un entero de 32 bits y devuelve el resultado.

Suponga que el dispositivo de entrada estándar está configurado en el teclado. La siguiente es una descripción de lo que sucede en Windows: Cuando escribe una tecla en un teclado controlado por Windows, el sistema operativo almacena el código ASCII de 7 bits de esa tecla en un búfer de teclas interno. Ese búfer clave contiene hasta aproximadamente 16 códigos ASCII y está organizado como una estructura de datos de cola circular de primero en entrar / primero en salir. System.in.read()recupera el código ASCII del encabezado del búfer de claves y luego elimina ese código del búfer de claves. Ese código ASCII de 7 bits luego se convierte en un int( System.in.read()anteponiendo 25 bits cero al código) y regresa al llamador del método. Una segunda System.in.read()llamada al método recupera el siguiente código ASCII, que ahora está en la cabecera del búfer de claves, y así sucesivamente.

Suponga que no hay códigos ASCII en el búfer de claves. ¿Lo que pasa? System.in.read()espera a que el usuario escriba las teclas y presione el terminador. En Windows, ese terminador es la Enterclave. Al presionar Enter, Windows almacena un código de retorno de carro (ASCII 13) seguido de un código de nueva línea (ASCII 10) en el búfer de teclas. Por lo tanto, el búfer de claves puede contener varios códigos ASCII seguidos de un retorno de carro y un carácter de nueva línea. El primero de esos códigos vuelve de System.in.read(). Verifique esa actividad ingresando, compilando y ejecutando la Echoaplicación; su código fuente aparece en el Listado 1.

Listado 1. Echo.java

// Echo.java class Echo {public static void main (String [] args) throws java.io.IOException {int ch; System.out.print ("Ingrese texto:"); while ((ch = System.in.read ())! = '\ n') System.out.print ((char) ch); }}

Echo completa los siguientes pasos:

  1. Llama al System.out.print()método, que toma un Stringargumento, para generar un mensaje
  2. Llamadas System.in.read()para ingresar códigos ASCII desde el dispositivo de entrada estándar como enteros de 32 bits
  3. Convierte esos enteros de 32 bits en caracteres Unicode de 16 bits por medio de la (char)conversión
  4. Llama al System.out.print()método, que toma un charargumento, para hacer eco de esos caracteres Unicode en el dispositivo de salida estándar

Los últimos tres pasos de los cuatro pasos anteriores tienen lugar en un ciclo while y continúan hasta que se lee un carácter de nueva línea. Para ejecutar Echode manera que se introduce desde el teclado y salidas a la pantalla, emita el siguiente comando: java Echo.

Aunque System.in.read()nunca arroja una excepción (consulte el tema de conteo de palabras en este artículo para obtener una definición de ese término), cuando el dispositivo de entrada estándar está configurado en el teclado, puede generar una excepción cuando redirige el dispositivo de entrada estándar desde el teclado a un archivo. Por ejemplo, suponga que redirige el dispositivo de entrada estándar a un archivo y System.in.read()lee el contenido del archivo. Ahora suponga que el archivo está ubicado en un disquete y el usuario expulsa ese disco durante la operación de lectura. Cuando ocurre la expulsión, System.in.read()lanza una excepción, informando al programa que no puede leer el archivo. Eso proporciona la razón para agregar la throws java.io.IOExceptioncláusula al main()encabezado del método. (Explorará excepciones, lanzar excepciones y conceptos relacionados en un artículo futuro).

¿Cómo se redirige el dispositivo de entrada estándar para que la entrada se origine en un archivo? La respuesta es introducir un signo menor que <, en la línea de comando y seguir ese símbolo con un nombre de archivo. Para ver cómo funciona, emita la siguiente línea de comando:java Echo . The command line redirects the standard input device to a file called Echo.java. When Echo runs, because each line ends in a new-line character, only the first line of text in Echo.java appears on the screen.

Suppose you need a utility program that reads an entire file and either displays the file's contents on the screen, copies those contents to another file, or copies those contents to a printer. Unfortunately, the Echo program only performs that task until it encounters the first new-line character. What do you do? The answer to the problem lies in the Type application. Listing 2 provides the source code:

Listing 2. Type.java

// Type.java class Type { public static void main (String [] args) throws java.io.IOException { int ch; while ((ch = System.in.read ()) != -1) System.out.print ((char) ch); } } 

Type resembles Echo, however, there is no prompt, and the while loop tests against -1 (which indicates end of file) instead of \n (which indicates end of line). To run Type, issue the following command line: java Type . The contents of Type.java -- or whatever file is specified -- will display. As an experiment, try specifying java Type. What do you think will happen? (Hint: this program resembles Echo but doesn't end until you press Ctrl+C.)

Earlier, I mentioned that some programmers mistakenly think that System.in.read() returns a user-entered number. As you've just seen, that isn't the case. But what must you do if you want to use System.in.read() to retrieve a number? Take a look at the Convert application, whose source code is presented in Listing 3.

Listing 3. Convert.java

// Convert.java class Convert { public static void main (String [] args) throws java.io.IOException { System.out.print ("Please enter a number: "); int num = 0; int ch; while ((ch = System.in.read ()) != '\n') if (ch >= '0' && ch <= '9') { num *= 10; num += ch - '0'; } else break; System.out.println ("num = " + num); System.out.println ("num squared = " + num * num); } } 

Listing 3's Convert program prompts the user to enter a number (via System.out.print ("Please enter a number: ");). It reads these digits -- one at a time -- and converts each digit's numeric code to a binary number that is added to a variable called num. Finally, calls to System.out.println() output the value inside num and the square of that value to the standard output device.

Convert demonstrates the time-honored technique of using a while loop to test for a digit, premultiplying a variable by 10 (to make room for the incoming digit), converting a digit to its binary equivalent, and adding that binary equivalent to the variable. However, that technique is not a sound technique to use if you're writing a program for deployment in different countries as some countries use digits other than 0 through 9 -- such as Tamil digits. To make the program operate with other digits, you need to expand the if statement to test for those digits and modify the ch - '0' expression. Fortunately, Java simplifies that task by providing a Character class, which you'll explore in a future article.

Standard output

The standard output device is that part of the operating system that controls where a program sends its output. By default, the standard output device sends the output to a device driver attached to the screen. However, the output destination can be redirected to a device driver attached to a file or printer, which results in the same program displaying its findings on the screen, saving them in a file, or providing a hardcopy listing of the results.

You achieve standard output by calling Java's System.out.print() and System.out.println() methods. Except for the fact that print() methods don't output a new-line character after the data, the two method groups are equivalent. Methods exist to output Boolean, character, character array, double-precision floating-point, floating-point, integer, long integer, string, and object values. To demonstrate these methods, Listing 4 presents source code to the Print application.

Listing 4. Print.java

// Print.java class Print { public static void main (String [] args) { boolean b = true; System.out.println (b); char c = 'A'; System.out.println (c); char [] carray = { 'A', 'B', 'C' }; System.out.println (carray); double d = 3.5; System.out.println (d); float f = -9.3f; System.out.println (f); int i = 'X'; System.out.println (i); long l = 9000000; System.out.println (l); String s = "abc"; System.out.println (s); System.out.println (new Print ()); } } 

Listing 4 has probably triggered some questions for you. First, what is all that System.out. business doing in front of println()? Again, refer to the System class in the SDK documentation. The class contains a variable called out -- an object created from a class called PrintStream. The period character after System indicates that out belongs to System. The period character after out states that println() belongs to out. In other words, println() is a method that belongs to an object called out, which in turn belongs to a class called System.

The second question you might be asking yourself involves println() argument data types: how is it possible for the same println() method to be called with different types of argument data? The answer: because there are several println() methods in the PrintStream class. At runtime, the JVM knows which println() method to call by examining the number of method-call arguments and their data types. (Declaring several methods with the same name but different numbers of arguments and argument data types is known as method overloading. I will discuss that concept next month.)

Finally, you might be wondering about System.out.println (new Print ());. That method call illustrates the println() method, which takes an Object argument. First, the creation operator new creates an object from the Print class and returns a reference to -- also known as the address of -- that object. Finally, that address passes as an argument to the println() method, which takes an Object argument. The method converts the object's contents to a string and outputs that string. By default, the string consists of the name of the object's class, followed by an @ (at) character, followed by a hexadecimal-formatted integer that represents the object's hashcode. (I will present hashcodes and the conversion of objects to strings in an upcoming article.)

Compile Print.java and run the program by issuing the following command line: java Print. You should see nine lines of output. Redirect that output to the out.dat file by issuing the following command line: java Print >out.dat. You can now view the contents of the file.

The greater-than sign, >, indicates standard output redirection. Whenever you want to redirect the standard output device from the screen to a file or printer, specify that symbol followed by the file or printer name on the command line. For example, redirect Print's output to a Windows printer by issuing the following command line: java Print >prn.