miércoles, 6 de enero de 2016

I/O y NIO - Navegación de archivos y I/O (1/7)

I/O sabemos que se trata de Input / Output, y consiste - generalmente - en el manejo de archivos, aunque también puede manejar la entrada y salida de bytes desde cualquier entrada/salida como fuera un puerto serial, cadena de caracteres, impresora, etc.

NIO es el "Nuevo I/O" que contiene paquetes nuevos desde Java 1.4. Actualmente hay un NIO.2, o sea, paquetes más nuevos aún, que aparece en Java 7. Para generalizar, cuando se mencione NIO, se estará tratando de NIO2.

En este post veremos algunos ejemplos sobre estos paquetes que son muy útiles, y también necesarios para el examen de certificación.


Creación de un archivo

package com.apuntesdejava.ionio;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Ejemplo01 {

    private static final Logger LOG = Logger.getLogger(Ejemplo01.class.getName());

    public static void main(String[] args) {
        try {
            //Se crea una referenia a un archivo, pero no existe aún
            File file = new File("archivo01.txt");
            //A ver, ¿existe?
            System.out.println("Existe:" + file.exists());//la primera vez, no
            //tratamos de crearlo...
            boolean newFile = file.createNewFile(); //devuelve TRUE si logró crearlo porque no existía
            //a ver..
            System.out.println("Se creo el archivo:"+newFile);
            //y existe???
            System.out.println("Existe 2:" + file.exists());//la primera vez, no
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
    }

}

Bastante simple... hasta ahora.

Escribir y Leer contenido de un archivo

Ahora, hagamos un programa que pueda escribir un contenido en un archivo creado.
package com.apuntesdejava.ionio;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ReaderWritter {

    private static final Logger LOG = Logger.getLogger(ReaderWritter.class.getName());

    public static void main(String[] args) {
        //Nuevo archivo de pruebas
        File file = new File("archivo02.txt");
        //Creamos un objeto para escribir caracteres en el archivo de prueba
        try (FileWriter fw = new FileWriter(file)) {
            fw.write("Hola todos\nsaludos desde el espacio\n"); //Este es el contenido
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }

        //Ahora leeremos el contenido
        try (FileReader fr = new FileReader(file)) {
            //tenemos nuestro espacio para leer el contenido
            char[] buffer = new char[100];
            //... y al leerlo, guardamos el tamanio leiodo
            int tamanio = fr.read(buffer);
            System.out.println("Tamaño del contenido:" + tamanio);
            //Escribimos el contenido
            System.out.println("Contenido:");
            for (char c : buffer) {
                System.out.print(c); //caracter por caracter
            }
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
    }

}

En la siguiente tabla veremos un pequeño api de las clases y métodos más importantes para java.io

ClaseHeredado deArgumentos de los
constructores importantes
Métodos importantes
File Object File, String
String
String, String
createNewFile()
delete()
isDirectory()
isFile()
list()
mkdir()
renameTo()
FileWriter Writer File
String
close()
flush()
write()
BufferedWriter Writer Writer close()
flush()
newLine()
write()
PrintWriter Writer File
String
OutputStream
Writer
close()
flush()
format(), printf()
print(), println()
write()
FileReader Reader File
String
read()
BufferedReader Reader Reader read()
readLine()


Si revisamos un poco la tabla, podemos notar que si queremos leer de archivo con texto plano (usando el método readLine) solo está en la clase BufferedReader.... pero este no tiene un constructor con el nombre del archivo como argumento. Entonces ¿cómo leo el archivo?. Aquí es donde debemos juntar con otras clases.
    private static final Logger LOG = Logger.getLogger(BufferedReaderTest.class.getName());

    public static void main(String[] args) {
        String nombreArchivo = "archivo03.txt";

        //vamos a crear un archivo para escribir en él
        try (PrintWriter pw = new PrintWriter(nombreArchivo)) {
            pw.println("Este es un texto de ejemplo");
        } catch (FileNotFoundException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }

        //ahora, vamos a leerlo
        try (FileReader fr = new FileReader(nombreArchivo)) {
            BufferedReader br = new BufferedReader(fr);

            String linea;
            //leer hasta que sea null, que es el fin de archivo
            while ((linea = br.readLine()) != null) {
                LOG.info(linea);//imprimir el contenido en pantalla
            }

        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
    }


En la línea 35 podemos ver que se creará un objeto de FileReader utilizando el nombre del archivo dado en un String, y en la línea 36 se utiliza este objeto para la creación de un objeto de tipo BufferedReader. A partir de aquí ya se puede leer el contenido de manera natural.

Manejando archivos y carpetas (directorios)

La clase java.io.File permite manejar tanto archivos como carpetas. Adicionalmente, sus métodos pueden ser usados para borrar archivos, renombrarlos, saber si existe el archivo, crear archivos temporales, cambiar atributos del archivo y determinar si es un archivo o una carpeta. La confusión sucede cuando un objeto de tipo java.io.File es usado para representar o un archivo o una carpeta. Por ejemplo, si creamos un objeto así:

File file = new File("algo");

... pueden suceder dos cosas:
  1. Si "algo" no existe, ningún archivo es creado
  2. Si "algo" existe, el objeto referenciado file apunta a un archivo existente.
Recordemos que la instrucción file = new File("algo"); NUNCA crea un archivo.

Existen dos maneras de crear un archivo.

1. Invocando al método createNewFile() del objeto java.io.File.

File file = new File("algo"); //Archivo aún no creado
file.createNewFile();         //Crea un archivo llamado "algo" apuntado por 'file'

2. Creando una instancia de Writer o Stream. Específicamente, instanciar un FileWriter, PrintWriter o FileOutputStream. De cualquier manera, se creará el archivo ni bien es instanciado. Por ejemplo:

File file = new File("algo");           //Archivo aún no creado
PrintWriter pw = new PrintWriter(file); //Se instancia 'pw' de tipo 'PrintWriter' 
                                        //y apunta al archivo 'algo' asignado por 'file'.

Crear una carpeta (o directorio) es similar al de un archivo.
File miDir = new File("mi_directorio"); // crea un objeto
miDir.mkdir();                          // crea la carpeta con el nombre 
                                        // descrito en el objeto 'miDir'
//una vez creada la carpeta, se puede crear un archivo
File miArchivo = new File(miDir,"archivo04.txt");  //el primer argumento es el 
                                                   //directorio donde estará el archivo
miArchivo.createNewFile();


Además de crear archivos, la clase java.io.File hace otras cosas como renombrar y borrar archivos. El siguiente código muestra un poco de estos métodos. Notar las líneas resaltadas.
public class DeleteRenameFilesTest {

    private static final Logger LOG = Logger.getLogger(DeleteRenameFilesTest.class.getName());

    public static void main(String[] args) {
        try {
            File dir0 = new File("dir0"); //haciendo un directorio
            File dir1 = new File(dir0, "dir1");//haciendo un subdirectorio
            dir1.mkdirs(); //con esto se crean todas las carpetas necesarias

            File file1 = new File(dir1, "file1.txt"); //creando un archivo
            file1.createNewFile();

            File file2 = new File(dir0, "file2.txt");//creando otro archivo 
            file2.createNewFile();

            //tratando de borrar el directorio 'dir0'...
            LOG.log(Level.INFO, "Borrando el directorio {0}:{1}",
                    new Object[]{dir0, dir0.delete()});
            //... pero no va a poder porque tiene contenido. Debe estar vacío

            File file3 = new File(dir0, "file3.txt");//otro objeto
            file1.renameTo(file3); //.. lo renombramos (o movemos)

            //Tratando de borrar el directorio 'dir1'...
            LOG.log(Level.INFO, "Borrando el directorio {0}:{1}",
                    new Object[]{dir1, dir1.delete()});
            //... y lo logra, porque el archivo ya fue movido

            File dir2 = new File("nuevo_dir");//renombramos el directorio...            
            LOG.log(Level.INFO, "Renombrando el directorio {0} a {1}: {2}",
                    new Object[]{dir0, dir2, dir0.renameTo(dir2)}); //... y mantendrá el contenido
            //La primera vez lo hará, pero la segunda no, porque ya existe

        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
    }

}

La clase java.io.Console

Esta clase apareción en Java 6. En el contexto común, la consola es un dispositivo físico con un teclado y una pantalla (como una PC o una MAC). Si estamos ejecutando Java SE 6 desde la línea de comandos, ya estamos accediendo al objeto de la consola, el cual está referenciando invocando al método System.console(). Recordemos que es posible ejecutar nuestros programas Java en un entorno que no tenga acceso al objeto de la consola, y cuando se invoca al método System.console(), este retornará null. ¿Como en qué situación? Por ejemplo, en la ventana "Output" del IDE: esa ventana no tiene entrada de teclado.

La clase Console hace que sea más fácil la entrada de datos desde la línea de comando, tanto para entradas con eco o sin eco (como los password), y también hace que sea más fácil escribir textos formateados a la línea de comandos. Esto es bastante útil cuando se quiere trabajar con pruebas que no necesite interfaz gráfica (GUI). Veamos un ejemplo bastante explicado.

public class ClaseConsole {

    public static void main(String[] args) {
        String nombre = "";
        Console c = System.console(); //obtiendo un Console
        char[] pass;
        pass = c.readPassword("%s", "Escriba contraseña:"); //devuelve un char[]
        for (char chr : pass) {
            c.format("%c", chr); //salida con formato
        }
        c.format("\n");

        //probamos la entrada de texto
        while (true) {
            nombre = c.readLine("%s", "Escribe algo:"); //devuelve un objeto
            c.format("\tsalida: %s\n", nombre);
        }
    }

}

Para salir, presionar Ctrl+C
La salida sería esta: