Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

Cómo leer y escribir archivos CSV con Java

Imagen ornamental

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...).

José M. Alarcón Aguín Fundador de campusMVP, es ingeniero industrial y especialista en consultoría de empresa. Ha escrito diversos libros, habiendo publicado hasta la fecha cientos de artículos sobre informática e ingeniería en publicaciones especializadas. Microsoft lo ha reconocido como MVP (Most Valuable Professional) en desarrollo web desde el año 2004 hasta la actualidad. Puedes seguirlo en Twitter en @jm_alarcon o leer sus blog técnico o personal. Ver todos los posts de José M. Alarcón Aguín
Archivado en: Lenguajes y plataformas

Boletín campusMVP.es

Solo cosas útiles. Una vez al mes.

🚀 Únete a miles de desarrolladores

DATE DE ALTA

x No me interesa | x Ya soy suscriptor

La mejor formación online para desarrolladores como tú

Comentarios (18) -

Porqué este código no me funciona? He adjuntado mi libreria en el proyecto netbeans  pero se produce una excepción y me la da sensación que es porque la API no está bien.

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/lang3/ObjectUtils
  at com.opencsv.CSVParser.<init>(CSVParser.java:209)
  at com.opencsv.CSVReader.<init>(CSVReader.java:196)
  at com.opencsv.CSVReader.<init>(CSVReader.java:178)
  at com.opencsv.CSVReader.<init>(CSVReader.java:130)
  at com.opencsv.CSVReader.<init>(CSVReader.java:70)
  at csvfile.CsvFile.main(CsvFile.java:21)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.lang3.ObjectUtils
  at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
  at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
  at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
  at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
  ... 6 more

import java.io.*;
import com.opencsv.CSVReader;

public class CsvFile {

  public static void main(String[] args) throws FileNotFoundException, IOException {
    String csvFilename = "D:\\Libros\\Java\\MyProjects\\CsvFile\\ISO-Codes.csv";
                CSVReader csvReader = new CSVReader(new FileReader(csvFilename));
                String[] row = null;
            while((row = csvReader.readNext()) != null) {
                    System.out.println(row[0]
                        + " # " + row[1]
                        + " #  " + row[2]);
                }
            //...
        csvReader.close();
    
  }

}

Gracias de antemano por responder.

Responder

Hola Blanca:

Ese error te lo da porque te falta el archivo:

commons-lang3-3.0.jar

en tu classpath, y es necesario. Añádelo y te compilará.

Saludos.

Responder

Gracias, funciona perfecto!

Responder

Nilton alejandro
Nilton alejandro

Como podria formatear todo el campo a fecha yyyy-mm-dd hh:mm:ss ya que cuando exporto a csv con opencsv me sale en otro formato algunas fechas ejm: enero 23 del 2019, considerando que mi consulta a traves de query todo me sale con formato yyyy-mm-dd hh:mm:ss. Gracias por su nueva respuesta.

Responder

Hola Nilton:

Puedes decorar la propiedad de tipo fecha de tu código con @CsvDate (opencsv.sourceforge.net/.../CsvDate.html) y especificar el formato que quieras. Por ejemplo:

@CsvDate(value = "yyyy-mm-dd hh:mm:ss")
private Date fechaNacimiento;

Saludos.

Responder

He probado a utilizar opencsv, pero CSVReader solo acepta un parámetro. No le puedo pasar el carácter separador.
Es un problema de la versión o tengo que añadir algo más que opencsv?

Responder

José Manuel Alarcón
José Manuel Alarcón

Hola José Luis:

En principio eso lo tienes excplicado en el artículo en la sección "Datos con el separador dentro y escapeados", aunque ahora mismo no tengo a mano un compilador para probarlo.

De todos modos, existe otra manera de hacerlo directamente con OpenCSV. OpenCSV usa una interfaz fluida. Eso significa que, para hacer esto en concreto que deseas, puedes crear una clase de "parsing" aparte (opencsv.sourceforge.net/.../CSVParserBuilder.html) indicando qué delimitador quieres, por ejemplo así:

CSVParser conPipe = new CSVParserBuilder().withSeparator('|').build();

que usará "pipes" como delimitador de los datos, y luego usarla con el reader mediante el método withCSVParser (opencsv.sourceforge.net/.../CSVReaderBuilder.html , busca withCSVparser en esta página) de esta manera:

CSVReader csvReader = new CSVReader(new FileReader(archCSV)).withCSVParser(conPipe)
                        .build();

que te devolverá un CSVReader que usa "pipes" para delimitar los datos, que es justo lo que quieres.

Espero que te sirva.

Saludos!

Responder

Gracias por la información!
Sigo sin saber por qué en este ejemplo al constructor de CSVReader se le pueden pasar dos parámetros si en la documentación de opencsv indica que solo recibe uno, pero imagino que quizás existió en versiones anteriores y no se ha actualizado.

Un saludo

Responder

José Manuel Alarcón
José Manuel Alarcón

Mmmm, tienes razón. He estado viendo en el Internet Arcive la documentación de OpenCSV para ver cómo la han ido variando en el tiempo, y hasta 2018 al menos este constructor funcionaba, y el artículo es de 2017. Lo voy a actualizar con la versión más moderna para que no haya confusión. Gracias por hacérnoslo notar.

Saludos!

Responder

Buenos días!

Estoy probando lo que me comentaste y no consigo leer un simple csv con 6 columnas y 8 registros.

Lo he probado con el CSVParser, tanto usando un separador ";" como "\t" y utilizando el método withSkpLines o no y el resultado es el mismo: fila[0] = a la ruta del archivo.

final CSVParser parser =
          new CSVParserBuilder()
          //.withSeparator(';')
          .withSeparator('\t')
          .withIgnoreQuotations(true)
          .build();
      csvReader =  new CSVReaderBuilder(new StringReader(archCSV))
          .withSkipLines(1)
          .withCSVParser(parser)
          .build();
      String[] fila = null;
      while((fila = csvReader.readNext()) != null) {
          resultat += fila[0]+"\n";

Si en cambio utilizo el constructor de CSVReader, solo me pilla loas tres primeras filas y las otras 5 no aparecen:
      CSVReader csvReader =  new CSVReader(new FileReader(archCSV));
      String[] fila = null;
      while((fila = csvReader.readNext()) != null) {
          resultat += fila[0]+"\n";

Qué hago mal? No debo recorrerlo con un while con readNext()?

Gracias!

Responder

José Manuel Alarcón
José Manuel Alarcón

Hola José Luis:

Para poder probarlo en real y ver que funciona (el comentario lo escribí sin probar nada, de oído), he creado un ejemplo concreto en repl.it que lee dos archivos CSV: uno con comas y el otro con putos y coma y los muestra por pantalla. Así podrás ver ambos en funcionamiento, y en especial el segundo de ellos (con ";") que es el que te interesa.

He añadido al post dicho ejemplo ejecutable directamente en el navegador y también un pequeño vídeo que explica rápidamente el código. A ver si así queda más completo y te sale bien.

Saludos!

Responder

Hola, replique tu código y sin embargo la consola no me muestra ningún resultado, no sé si estoy mal en algún apartado?

este es mi código:

package TestOfTest;
import com.opencsv.*;
import java.io.*;
import java.io.IOException;
public class CSV_Download {

    public static void main(String[] args){
        FileReader archCSV = null;
        CSVReader csvReader = null;
        try {
            archCSV = new FileReader("H:\\TestFolder\\Test.csv");
            csvReader = new CSVReader(archCSV);
            String[] fila = null;
            while ((fila = csvReader.readNext()) != null) {
                System.out.println(fila[0]
                        + " | " + fila[1]
                        + " | " + fila[2]);
            }
        } catch (IOException e) {
            System.out.println(e);
        } catch (Exception e) {
            System.out.println(e);
        } finally {
            try {
                archCSV.close();
                csvReader.close();
            } catch (IOException e) {
                System.out.println(e);
            }
        }
    }
}

Resultado:
===============================================
Default Suite
Total tests run: 0, Passes: 0, Failures: 0, Skips: 0
===============================================

Process finished with exit code 0

Responder

Estoy haciendo un proyecto con JSP en Eclipse en el que tengo que procesar un archivo .csv, vi este tutorial y me puse a probarlo, pero me está dando una advertencia de "Dead Code" en el fragmento que acontinuación pongo entre asteriscos:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
    
    //Objects and variables
    PrintWriter out = response.getWriter();
    RequestDispatcher requestDispatcher;
    
    String country = request.getParameter("country").trim();
    String csvFileReceived = request.getParameter("csvFile").trim();
    CSVReader csvFile = new CSVReader(new FileReader(csvFileReceived));
      
      if(country == "" && csvFile == null) ***{
        
        out.print("<h2 style=\"color:#FF0000\">Debe introducir los nombres manualmente o mediante archivo .csv</h2> <br/><br/>");
        requestDispatcher = request.getRequestDispatcher("/index.jsp");
        requestDispatcher.include(request, response);
        
      ***} else if(country != "" && csvFile != null) {
        
        out.print("<h2 style=\"color:#FF0000\">Debe elegir un método unicamente, no es posible ambos</h2> <br/><br/>");
        requestDispatcher = request.getRequestDispatcher("/index.jsp");
        requestDispatcher.include(request, response);
        
      }



Como posibles soluciones me da:
"Remove including condition" --- Elimina el " if " afectado y deja el " else if " como un " if "

"Add @SupressWarnings 'unused' to 'doPost()' --- Genera "@SuppressWarnings("unused")" encima del método

"Configure problem severity" --- Para configurar el problema

"Extract to method" --- Me genera el siguiente método (solo para el " if ", el " else if " no se ve afectado):

private void extracted(HttpServletRequest request, HttpServletResponse response, PrintWriter out)
      throws ServletException, IOException {
    RequestDispatcher requestDispatcher;
    {
        
        out.print("<h2 style=\"color:#FF0000\">Debe introducir los nombres manualmente o mediante archivo .csv</h2> <br/><br/>");
        requestDispatcher = request.getRequestDispatcher("/index.jsp");
        requestDispatcher.include(request, response);
        
      }
}

Responder

José Manuel Alarcón
José Manuel Alarcón

Hola Álvaro:

Para que una condición AND (&&) se cumpla, ambas partes de la misma deben ser true. En tu "if" tienes esto:

if(country == "" && csvFile == null)

pero la segunda condición no se puede dar nunca porque csvFile lo instancias justo en la línea anterior y como mucho puede darte una excepción (que deberías gestionar) pero no va a ser nunca nulo. Eso lo detecta el compilador y por eso te dice que esa rama del condicional está "muerta", es decir, nunca vas a pasar por ella. Por lo tanto deberías eliminar esa rama o usar otras condiciones para la misma.

Saludos.

Responder

Buenas, no sé si podría ayudarme... llevo como una semana intentado lo siguiente:

Tengo dos headers en un mismo csv y no consigo leerlo y agregar cada linea a su respectivo POJO. Es decir, primero habría una serie de filas que corresponde a la primer Header y luego comenzaria el siguiente header y las correspondientes filas.

Cuando lo hago con solo un Header sí lo consigo, pero con dos en el mismo... imposible.
El primer header tiene 15 columnas  y el segundo 7.

Agradecido de antemano.

Responder

José Manuel Alarcón
José Manuel Alarcón

Hola:

Eso son dos archivos CSV, no uno. Tendrás que cargarlos independientemente. Que yo sepa, CSV no permite mezclar datos en un mismo archivo.

Otra opción es que lo cargues en una cadena de texto, lo separes (si tienes claro dónde cambia de uno a otro) y luego los cargues independientemente.

Saludos.

Responder

Sí... bueno, en un mismo fichero hay dos headers con diferentes columnas. Tal que así:

-Línea en blanco-
[HEADER 1] (15 col)
Fila 1
Fila 2
Fila 3
Fila 4
-Línea en blanco-
[HEADER 2] (7 col)
Fila 1
Fila 2

Quizás podría dividir este fichero en dos ficheros csv y ya tratarlos de manera independiente cada uno. ¿Habría alguna forma "directa" de dividirlo por la segunda línea en blanco?

Gracias.

Responder

José Manuel Alarcón
José Manuel Alarcón

Claro, es lo segundo que te decía. Lo cargas en memoria en una cadena, buscas la separación (en este caso parece fácil ni siquiera necesitas una expresión regular) para separarlo en dos cadenas, y luego procesas cada cadena de CSV por separado.

Puedes procesar directamente desde cadenas con opencsv, no necesitas que estén físicamente en archivos:

String valores = "el contenido de tus datos CSV";

CSVReader reader = new CSVReader(new StringReader(valores))) {
    // Lo que sea...
}

Saludos

Responder

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.