Breaking

Post Top Ad

Your Ad Spot

viernes, 14 de junio de 2019

Java: leer un archivo en un ArrayList

Introducción

Hay muchas maneras de leer y escribir archivos en Java .
Por lo general, tenemos algunos datos en la memoria, en los que realizamos operaciones y luego persistimos en un archivo. Sin embargo, si queremos cambiar esa información, debemos volver a colocar el contenido del archivo en la memoria y realizar operaciones.
Si, por ejemplo, nuestro archivo contiene una larga lista que queremos ordenar, tendremos que leerlo en una estructura de datos adecuada, realizar operaciones y luego persistir una vez más, en este caso un archivo ArrayList.
Esto se puede lograr con varios enfoques diferentes:
  • Files.readAllLines()
  • FileReader
  • Scanner
  • BufferedReader
  • ObjectInputStream
  • API de Java Streams

Archivos.readAllLines ()

Desde Java 7, es posible cargar todas las líneas de un archivo de una ArrayListmanera muy simple:
try {  
    ArrayList<String> lines = new ArrayList<>(Files.readAllLines(Paths.get(fileName)));
}
catch (IOException e) {  
    // Handle a potential exception
}
También podemos especificar un charsetpara manejar diferentes formatos de texto, si es necesario:
try {  
    Charset charset = StandardCharsets.UTF_8;
    ArrayList<String> lines = new ArrayList<>(Files.readAllLines(Paths.get(fileName), charset));
}
catch (IOException e) {  
    // Handle a potential exception
}
Files.readAllLines() Abre y cierra automáticamente los recursos necesarios.

Escáner

Tan agradable y simple como era el método anterior, solo es útil para leer el archivo línea por línea. ¿Qué pasaría si todos los datos se almacenaran en una sola línea?
Scanneres una herramienta fácil de usar para analizar tipos primitivos y cadenas. El uso Scannerpuede ser tan simple o tan difícil como el desarrollador quiera hacerlo.
Un ejemplo simple de cuándo preferiríamos usarlo Scannersería si nuestro archivo tuviera solo una línea, y los datos deban analizarse en algo utilizable.
Un delimitador es una secuencia de caracteres que se Scannerusa para separar valores. De forma predeterminada, utiliza una serie de espacios / tabulaciones como delimitador (espacio en blanco entre los valores), pero podemos declarar nuestro propio delimitador y usarlo para analizar los datos.
Echemos un vistazo a un archivo de ejemplo:
some-2123-different-values- in - this -text-with a common-delimiter  
En tal caso, es fácil notar que todos los valores tienen un delimitador común. Simplemente podemos declarar que "-" rodeado por cualquier número de espacios en blanco es nuestro delimitador.
// We'll use "-" as our delimiter
ArrayList<String> arrayList = new ArrayList<>();  
try (Scanner s = new Scanner(new File(fileName)).useDelimiter("\\s*-\\s*")) {  
    // \\s* in regular expressions means "any number or whitespaces".
    // We could've said simply useDelimiter("-") and Scanner would have
    // included the whitespaces as part of the data it extracted.
    while (s.hasNext()) {
        arrayList.add(s.next());
    }
}
catch (FileNotFoundException e) {  
    // Handle the potential exception
}
Ejecutar este fragmento de código nos daría un resultado ArrayListcon estos elementos:
[some, 2, different, values, in, this, text, with a common, delimiter]
Por otro lado, si solo hubiéramos usado el delimitador predeterminado (espacios en blanco), ArrayListse vería así:
[some-2-different-values-, in, -, this, -text-with, a, common-delimiter]
Scannertiene algunas funciones útiles para los datos de análisis sintáctico, tales como nextInt()nextDouble(), etc.
Importante : Calling .nextInt()será NO devolver el siguiente intvalor que se puede encontrar en el archivo! Devolverá un intvalor solo si los siguientes elementos de los Scanner"escaneos" son válidos int, de lo contrario se lanzará una excepción. Una forma fácil de asegurarse de que no se produzca una excepción es realizar una comprobación correspondiente de "tiene" .hasNextInt()antes de usarla .nextInt().
Aunque no vemos que cuando llamamos funciones como scanner.nextInt()scanner.hasNextDouble(), se Scannerusan expresiones regulares en el fondo.
Muy importante: Una gran error común con el uso Scannerse produce cuando se trabaja con archivos que tienen múltiples líneas y utilizar .nextLine()en conjunción con .nextInt()nextDouble(), etc.
Echemos un vistazo a otro archivo:
12  
some data we want to read as a string in one line  
10  
A menudo, los desarrolladores más nuevos que utilizan Scannerescribirían código como:
try (Scanner scanner = new Scanner(new File("example.txt"))) {  
    int a = scanner.nextInt();
    String s = scanner.nextLine();
    int b = scanner.nextInt();

    System.out.println(a + ", " + s + ", " + b);
}
catch (FileNotFoundException e) {  
    // Handle a potential exception
}
//catch (InputMismatchException e) {
//    // This will occur in the code above
//}
Este código parece ser lógicamente correcto: leemos un entero del archivo, luego la siguiente línea y luego el segundo entero. Si intenta ejecutar este código, se InputMismatchExceptionlanzará sin una razón obvia.
Si comienza a depurar e imprimir lo que ha escaneado, verá que está int abien cargado, pero String sestá vacío.
¿Porqué es eso? La primera cosa importante a tener en cuenta es que una vez que Scannerlee algo del archivo, continúa escaneando el archivo desde el primer carácter después de los datos que escaneaba anteriormente.
Por ejemplo, si tuviéramos "12 13 14" en un archivo y llamáramos .nextInt()una vez, el escáner luego fingiría que solo había "13 14" en el archivo. Observe que el espacio entre "12" y "13" todavía está presente.
La segunda cosa importante a tener en cuenta es que la primera línea de nuestro example.txtarchivo no solo contiene el número 12, sino también lo que se llama un "carácter de nueva línea", y en realidad es en 12\nlugar de solo 12.
Nuestro archivo, en realidad, se ve así:
12\n  
some data we want to read as a string in one line\n  
10  
Cuando llamamos por primera vez .nextInt()Scannerlee solo el número 12, y deja el primero \nsin leer.
.nextLine()luego lee todos los caracteres que el escáner no ha leído hasta que alcanza el primer \ncarácter, el cual salta y luego devuelve los caracteres que leyó. Este es exactamente el problema en nuestro caso: tenemos un \npersonaje sobrante después de leer el archivo 12.
Así que cuando llamamos .nextLine()obtenemos una cadena vacía como resultado ya Scannerque no agrega el \ncarácter a la cadena que devuelve.
Ahora Scannerestá al comienzo de la segunda línea en nuestro archivo, y cuando intentamos llamar .nextInt()Scannerencuentra algo que no se puede analizar inty lanza lo mencionado anteriormente InputMismatchException.

Soluciones

  • Ya que sabemos qué es exactamente lo que está mal en este código, podemos codificar una solución alternativa. Simplemente "consumiremos" el carácter de nueva línea entre .nextInt().nextLine():
...
int a = scanner.nextInt();  
scanner.nextLine(); // Simply consumes the bothersome \n  
String s = scanner.nextLine();  
...
  • Dado que sabemos cómo example.txtestá formateado, podemos leer la línea completa del archivo y analizar las líneas necesarias utilizando Integer.parseInt():
...
int a = Integer.parseInt(scanner.nextLine());  
String s = scanner.nextLine();  
int b = Integer.parseInt(scanner.nextLine());  
...

BufferedReader

BufferedReaderlee el texto de un flujo de entrada de caracteres, pero lo hace almacenando en búfer los caracteres para proporcionar .read()operaciones eficientes Dado que acceder a un HDD es una operación que consume mucho tiempo, BufferedReaderrecopila más datos de los que solicitamos y los almacena en un búfer.
La idea es que cuando llamamos .read()(o una operación similar) es probable que volvamos a leer pronto desde el mismo bloque de datos del que acabamos de leer, y así los datos "circundantes" se almacenan en un búfer. En caso de que quisiéramos leerlo, lo leíamos directamente desde el búfer en lugar de hacerlo desde el disco, que es mucho más eficiente.
Esto nos lleva a lo que BufferedReaderes bueno para leer archivos grandes. BufferedReadertiene una memoria búfer significativamente mayor que Scanner(8192 caracteres por defecto frente a 1024 caracteres por defecto, respectivamente).
BufferedReaderse utiliza como envoltorio para otros lectores , y por eso los constructores BufferedReadertoman un objeto Reader como parámetro, como a FileReader.
Estamos usando try-with-resources para no tener que cerrar el lector manualmente:
ArrayList<String> arrayList = new ArrayList<>();

try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {  
    while (reader.ready()) {
        arrayList.add(reader.readLine());
    }
}
catch (IOException e) {  
    // Handle a potential exception
}
Se recomienda envolver una FileReadercon una BufferedReader, exactamente debido a los beneficios de rendimiento.

ObjectInputStream

ObjectInputStreamSolo debe usarse al lado ObjectOutputStreamLo que estas dos clases nos ayudan a lograr es almacenar un objeto (o una matriz de objetos) en un archivo y luego leerlo fácilmente.
Esto solo se puede hacer con clases que implementan la Serializableinterfaz. La Serializableinterfaz no tiene métodos ni campos y solo sirve para identificar la semántica de ser serializable:
public static class MyClass implements Serializable {  
    int someInt;
    String someString;

    public MyClass(int someInt, String someString) {
        this.someInt = someInt;
        this.someString = someString;
    }
}

public static void main(String[] args) throws IOException, ClassNotFoundException {  
    // The file extension doesn't matter in this case, since they're only there to tell
    // the OS with what program to associate a particular file
    ObjectOutputStream objectOutputStream =
        new ObjectOutputStream(new FileOutputStream("data.olivera"));

    MyClass first = new MyClass(1, "abc");
    MyClass second = new MyClass(2, "abc");

    objectOutputStream.writeObject(first);
    objectOutputStream.writeObject(second);
    objectOutputStream.close();

    ObjectInputStream objectInputStream =
                new ObjectInputStream(new FileInputStream("data.olivera"));

    ArrayList<MyClass> arrayList = new ArrayList<>();

    try (objectInputStream) {
        while (true) {
            Object read = objectInputStream.readObject();
            if (read == null)
                break;

            // We should always cast explicitly
            MyClass myClassRead = (MyClass) read;
            arrayList.add(myClassRead);
        }
    }
    catch (EOFException e) {
        // This exception is expected
    }

    for (MyClass m : arrayList) {
        System.out.println(m.someInt + " " + m.someString);
    }
}

API de Java Streams

Desde Java 8, otra forma rápida y fácil de cargar el contenido de un archivo en una ArrayListsería usando la API de Java Streams :
// Using try-with-resources so the stream closes automatically
try (Stream<String> stream = Files.lines(Paths.get(fileName))) {  
    ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
}
catch (IOException e) {  
    // Handle a potential exception
}
Sin embargo, tenga en cuenta que este enfoque, al igual Files.readAllLines()que solo funcionaría si los datos se almacenan en líneas.
El código anterior no hace nada especial, y rara vez usaríamos las transmisiones de esta manera. Sin embargo, ya que estamos cargando estos datos en un lugar ArrayListpara que podamos procesarlos en primer lugar, los flujos ofrecen una excelente manera de hacerlo.
Podemos ordenar / filtrar / mapear fácilmente los datos antes de almacenarlos en un ArrayList:
try (Stream<String> stream = Files.lines(Paths.get(fileName))) {  
    ArrayList<String> arrayList = stream.map(String::toLowerCase)
                                        .filter(line -> !line.startsWith("a"))
                                        .sorted(Comparator.comparing(String::length))
                                        .collect(Collectors.toCollection(ArrayList::new));
}
catch (IOException e) {  
    // Handle a potential exception
}

Conclusión

Hay varias formas diferentes en las que puede leer datos de un archivo en un archivo ArrayListCuando solo necesitas leer las líneas como usan los elementos Files.readAllLinescuando tiene datos que se pueden analizar fácilmente Scannercuando se trabaja con archivos de gran tamaño FileReaderenvuelto con BufferedReadercuando se trata de una matriz de objetos ObjectInputStream(pero asegúrese de que los datos se escribieron usando ObjectOutputStream).

No hay comentarios.:

Publicar un comentario

Dejanos tu comentario para seguir mejorando!

Post Top Ad

Your Ad Spot

Páginas