Leer fichero CSV con Java

De ChuWiki

Leer un fichero CSV con java es en principio sencillo. Un fichero CSV no es más que un fichero de texto con valores separados por comas. Basta con leer línea a línea el fichero e ir partiendo cada línea por el separador coma correspondiente. Por ejemplo

Juan, Pedro, Antonio

Sin embargo, un fichero CSV más complejo puede no ser tan fácil de leer. Imagina que el separador (la coma), forma parte de alguno de los valores. Para distinguir entre una coma que separa valores y una coma que forma parte de los valores, no queda más remedio que entrecomillar los valores. En el siguiente ejemplo la coma separa también nombres de apellidos

"Alvarez Diaz, Juan", "Lopez Moreno, Pedro", "Gomez Sanchez, Antonio"

Esto ya no es tan fácil de separar desde java. Y encima lo empeoramos si las comillas formaran parte de nuestro valor, en ese caso, para distinguirlo de unas comillas que delimitan valores, hay que ponerlas dobles, como en este ejemplo

"un * asterisco", "una , coma", "unas "" comillas"

En el tercer valor, hay que poner "" para saber que esa comilla forma parte del valor.

Veamos dos ejemplos de código. Uno MainWithoutLibrary.java en el que haremos sólo con java un lector de CSV simple. Y otro segundo ejemplo MainWithOpenCsv.java en el que usaremos la librería OpenCSV que nos facilitará los CSV complejos.

Lectura de CSV sencillo[editar]

Imagina un CSV sencillo, sin que los separadores formen parte del valor y sin que las comillas formen parte del valor, por ejemplo, este

"uno";"dos";"tres"
"cuatro";"cinco";"seis"

Usamos el punto y coma como separador y las comillas para delimitar los valores. El código java que puede leer esto sería el siguiente

   public static final String SEPARATOR=";";
   public static final String QUOTE="\"";

   public static void main(String[] args) {

      BufferedReader br = null;
      
      try {
         
         br =new BufferedReader(new FileReader("files/Libro1.csv"));
         String line = br.readLine();
         while (null!=line) {
            String [] fields = line.split(SEPARATOR);
            System.out.println(Arrays.toString(fields));
            
            fields = removeTrailingQuotes(fields);
            System.out.println(Arrays.toString(fields));
            
            line = br.readLine();
         }
         
      } catch (Exception e) {
         ...
      } finally {
         if (null!=br) {
            br.close();
         }
      }

Vamos a explicarlo brevemente.

Lo primero es abrir el fichero para leerlo. Puesto que es un fichero de texto con líneas, una clase cómoda para leerlo es BufferedReader, que admite en su constructor un FileReader. Esto nos permitirá usar el método readLine() para ir leyendo líneas de una en una.

Un bucle mientras haya líneas, es decir, mientras readLine() no lea null.

Puesto que el separador que usamos es el punto y coma, una forma fácil de partir la cadena es usar el método split() de la clase String. La línea

String [] fields = line.split(SEPARATOR);

parte la línea usando el separador y nos devuelve un array de String con tantos elementos como campos. Por ejemplo, para la primera línea del CSV que contiene "uno";"dos";"tres", nos devolvería un array de tres String, con los valores "uno", "dos" y "tres".

Un detalle a tener en cuenta, es que nos dejará las comillas en los valores, así que tendremos "uno" con sus dos comillas, una a cada lado, y así con todos los demás valores. Si queremos quitar estas comillas, hemos creado un método removeTrailingQuotes() al que pasamos nuestro array de String y nos devuelve con los mismos elementos, pero sin comillas. El código de este método es el siguiente

   private static String[] removeTrailingQuotes(String[] fields) {

      String result[] = new String[fields.length];

      for (int i=0;i<result.length;i++){
         result[i] = fields[i].replaceAll("^"+QUOTE, "").replaceAll(QUOTE+"$", "");
      }
      return result;
   }

Creamos un array de String para guardar los resultados con la misma longitud que el array que nos pasan. Hacemos un bucle para ir recorriendo cada uno de los elementos.

Para quitar las comillas, usamos el método replaceAll() de la clase String. Este método admite como primer parámetro una expresión regular y como segundo parámetro el nuevo valor que queremos poner. En nuestro ejemplo:

  • En expresiones regulares, un ^ indica principio de la cadena. Así que ^" quiere decir unas comillas al principio de la cadena. Así que reemplazamos ^" por nada (cadena vacía "").
  • En expresiones regulares, un $ indica final de la cadena. Así que "$ quiere decir unas comillas al final de la cadena. Así que reemplazamos "$ por nada (cadena vacía "")

Con esto quitamos las comillas del principio y del final en cada valor. A partir de ahí, sólo queda hacer lo que tengamos que hacer con cada valor, que en nuestro ejemplo sólo es sacarlo por pantalla.

Leer fichero CSV complejo[editar]

Para un fichero CSV más complejo, en el que el separador o las comillas formen parte de los valores, la separación de campos no es tarea sencilla, así que podemos usar alguna librería que nos dé el trabajo hecho. Una de ellas es OpenCSV. Es un jar que te puedes descargar en la misma página siguiendo el enlace de descarga, o bien, si usas Maven, puedes poner la siguiente dependencia en tu fichero pom.xml

<dependency>
	<groupId>com.opencsv</groupId>
	<artifactId>opencsv</artifactId>
	<version>3.7</version>
	<scope>compile</scope>
</dependency>

Una vez añadida la dependencia en tu proyecto, el siguiente código de ejemplo indica cómo leer un CSV con esta librería

   public static final char SEPARATOR=';';
   public static final char QUOTE='"';

   public static void main(String[] args) {

      CSVReader reader = null;
      try {
         reader = new CSVReader(new FileReader("files/Libro2.csv"),SEPARATOR,QUOTE);
         String[] nextLine=null;
         
         while ((nextLine = reader.readNext()) != null) {
            System.out.println(Arrays.toString(nextLine));
         }
         
      } catch (Exception e) {
         ...
      } finally {
         if (null != reader) {
            reader.close();
         } 
      }
   }

El proceso es sencillo. Basta abrir el fichero usando la clase CSVReader. En el constructor, aparte del fichero, podemos pasar dos parámetros más. Uno es el separador del fichero CSV (las ; en nuestro ejemplo) y el otro el carácter que se use para delimitar los valores (Las " en nuestro ejemplo).

Igual que antes, leer con el método readNext() de CSVReader hasta que lleguemos al final de fichero (el método devolverá null). La ventaja respecto a no usar librería, es que este método readNext() nos devuelve directamente el array de String con los valores de una línea, un elemento en el array por cada valor. Y ya nos devuelve las comillas quitadas de cada elemento. Es decir, nos hemos ahorrado hacer el split() a mano y el removeTrailingQuotes(). Y además lo hace mejor, porqque ante una línea como esta

"uno";"d;o""s";"tres"

en el que el segundo elemento lleva un ; y unas " como parte del valor, nos devuelve los valores de forma correcta

uno
d;o"s
tres