Una situación muy habitual en cualquier aplicación consiste en la necesidad de leer y escribir archivos con valores separados por comas (CSV, de su nombre en inglés: Comma Separated Values).
Si te interesa aprender bien los fundamentos de esta plataforma, no pierdas de vista nuestro curso Java
Esto se puede hacer nativamente en Java usando las API de lectura y escritura de archivos y procesando cada línea con código específico para separar los valores o para crear las líneas correspondientes. Pero hacerlo así, "a pelo" es un trabajo muy ingrato, propenso a errores y que tiene poco sentido si podemos hacerlo de una manera mejor y más directa. Y esto precisamente es lo que nos proporciona la conocida biblioteca Open Source llamada opencsv.
Además su licencia es de tipo Apache 2.0, lo que nos permite usarla para cualquier propósito, incluso para crear aplicaciones comerciales.
Aunque opencsv tiene infinidad de posibilidades, vamos a ver los métodos más sencillos para poder usarla en nuestras aplicaciones, sean estas de escritorio, Web, etc...
Cómo es un archivo CSV
Como su propio nombre indica, un archivo CSV consiste en una serie de valores separados por comas, que es el caso más habitual, aunque en realidad las comas muchas veces pueden ser otros caracteres, como puntos y coma, tabuladores, etc...
Por regla general estos archivos comienzan con una línea que lleva los nombres de cada uno de los campos de datos que se están almacenando.
Por ejemplo, el siguiente es un fragmento de un archivo CSV que representaría información sobre los códigos estándar para representar a los diferentes países del mundo. Incluye el nombre del país, su código de 2 caracteres, de 3 caracteres, su código numérico y si es considerado un estado soberano o no (sacada de aquí):
English-Short-Name,Alpha-2-code,Alpha-3-code,Numeric-code,Independent
Afghanistan,AF,AFG,4,Yes
Åland Islands,AX,ALA,248,No
Albania,AL,ALB,8,Yes
....
Spain,ES,ESP,724,Yes
La primera fila contiene el nombre de los diferentes campos, y cada una de las siguientes lleva los valores de esos campos de cada registro.
Puedes descargar el archivo entero desde aquí (ZIP, 7.11KB) con "," como con el ";" para luego.
Procesar este tipo de archivos es muy sencillo pues es leer las líneas una a una y separarlas por las comas para obtener los campos correspondientes, así que se puede hacer con las capacidades básicas de cualquier lenguaje de programación o plataforma. Ahora bien, se pueden dar muchos casos especiales que hay que tener en cuenta, como campos vacíos por el medio, campos vacíos al final, valores que lleven comas como parte de su contenido... Y entonces la cosa empieza a complicarse.
Cómo leer archivos CSV con Java y opencsv
Lo primero es hacerse con el JAR de opencsv, que puedes descargar desde su página de SourceForge. Una vez descargado y añadido a tu proyecto el código necesario es muy directo:
String archCSV = "D:\\ISO-Codes.csv";
CSVReader csvReader = new CSVReader(new FileReader(archCSV));
String[] fila = null;
while((fila = csvReader.readNext()) != null) {
System.out.println(fila[0]
+ " | " + fila[1]
+ " | " + fila[2]);
}
csvReader.close();
Este código tan sencillo lo que hace es crear una instancia de la clase CSVReader
expuesta por opencsv a la que le pasa en su constructor el FileReader
de Java necesario para leer el archivo que nos interesa. Luego en un bucle indefinido de tipo while
se recorren todas las filas una a una para leerlas y mostrar los valores de cada campo por la consola.
Fíjate en que, una vez leída una fila con readNext()
, se obtiene un array que en cada una de sus posiciones tiene el valor correspondiente, y ya se tiene en cuenta cosas como las mencionadas (que falte un campo, etc...).
Usar un separador distinto a la coma
Actualizado en marzo de 2020
Si el separador utilizado fuera otro diferente (por ejemplo un ;
en vez de una coma, cosa que hace Excel en Español cuando exportas una hoja de cálculo a CSV), lograrlo es un poco más complicado, aunque no demasiado.
Lo que tenemos que hacer en ese caso es definir un parser nuevo utilizando la clase CSVParserBuilder
, de la siguiente manera:
CSVParser conPuntoYComa = new CSVParserBuilder().withSeparator(';').build();
Fíjate que en este caso se usan comillas simples y no dobles ya que se trata de un literal de tipo char
, no de una cadena. Si necesitamos usar caracteres especiales podemos "escapearlos" de la manera habitual. Por ejemplo, para usar un tabulador podríamos escribir: '\t'
.
Esto construye un nuevo parseador para contenido delimitado. Ahora, para poder utilizarlo con un CSVReaderBuilder, sacamos partido a la interfaz fluida de esta clase, encadenando la llamada de la siguiente manera:
CSVReader csvReader = new CSVReader(new FileReader(archCSV)).withCSVParser(conPuntoYComa).build();
De este modo se genera un nuevo reader que utilizará el nuevo delimitador.
He dejado un ejemplo completo ejecutable en la nube en Repl.it, que puedes ver y ejecutar aquí embebido (Pulsa en los archivos y luego en Main.java
en caso de que no se vea este archivo por defecto. Tiene el caso simple comentado):
Y además un breve vídeo explicativo del código anterior:
Leer todas las filas de golpe
Si quisieras leer y obtener todas las líneas de golpe en lugar de ir una a una podrías usar el método readAll()
:
CSVReader csvReader = new CSVReader(new FileReader(archCSV));
List<String[]> datos = csvReader.readAll();
que ya nos entregaría una lista de matrices de cadenas que podemos procesar del mismo modo pero en un bucle determinado de tipo for
.
Cómo generar archivos CSV con Java y opencsv
Ahora que ya sabemos cómo escribir los archivos, vamos a realizar el proceso inverso: partiendo de unos datos que tenemos en memoria (quizá en una matriz o una lista de objetos), vamos a crear en disco un archivo .csv con esa información delimitada por comas.
Suponiendo que queremos ir escribiendo línea por línea, el proceso se parece mucho al de lectura, usando un CSVWriter
en lugar de la clase que usábamos para leer:
String [] pais = {"Spain", "ES", "ESP", "724", "Yes"};
String archCSV = "D:\\ISO-Codes.csv";
CSVWriter writer = new CSVWriter(new FileWriter(archCSV));
writer.writeNext(pais);
writer.close();
En este caso, por sencillez, estamos simulando que tenemos un dato metido en un array, pero en la práctica obtendríamos esta información de una base de datos, de la entrada de un usuario, etc... Eso ya es cosa tuya. Pero el proceso sería el mismo.
Si tenemos más de un dato al mismo tiempo (por ejemplo un conjunto de registros sacados de una base de datos) y queremos escribirlos todos de golpe, podemos usar writeAll()
para ello:
//Creo mi lista de países (los sacaría de algún lado, aquí los creamos a mano para el ejemplo)
List<String[]> paises = new ArrayList<String[]>;
paises.add({"Afghanistan", "AF", "AFG", "4", "Yes"});
paises.add({"Spain", "ES", "ESP", "724", "Yes"});
//Etc...
String archCSV = "D:\\ISO-Codes.csv";
CSVWriter writer = new CSVWriter(new FileWriter(archCSV));
writer.writeAll(paises);
writer.close();
Con esto se escribirían todos de golpe.
Del mismo modo que antes podíamos especificar el separador y el delimitador como segundo y tercer parámetro del constructor respectivamente, en el caso del CSVWriter
pasa exactamente lo mismo, por lo que podríamos indicar otros distintos a la coma y las comillas dobles por defecto, para que se usasen en la generación del archivo si así fuese necesario.
Técnicas más avanzadas
Lo que acabamos de ver es la manera más simple y directa de leer y escribir CSV desde Java. Si queremos tener más potencia podemos usar otras características avanzadas de opencsv para lograrlo. Pero esto ya se sale del ámbito y del espacio disponible para un artículo como este.
Por ejemplo, en archivos cuya primera línea contiene los nombres de los campos de los registros (como en el ejemplo de los países que vimos al principio), es posible obtener directamente objetos Java Bean mapeados cuyas propiedades son dichos campos, de modo que no tengamos que manejar elementos de un array por orden usando su índice. Y lo mismo para escribirlos a disco desde objetos. Para ello te remitimos a la documentación de opencsv (lectura y escritura), así como para aprender otras características más específicas de esta biblioteca (anotaciones, filtrado de datos, saltar registros...).