Java
Tutorial Java: Leer y escribir archivos
Aprende a leer y escribir archivos en Java 11+ usando Files y BufferedReader/Writer con gestión adecuada de codificación UTF-8.
Aprende Java y certifícateFiles.readAllLines() y Files.readString() (Java 11+)
La lectura de archivos es una operación fundamental en cualquier aplicación que necesite procesar datos externos. Java 11 introdujo métodos simplificados para leer archivos de texto, eliminando gran parte del código repetitivo que era necesario en versiones anteriores.
Los métodos Files.readAllLines()
y Files.readString()
forman parte del paquete java.nio.file
, que proporciona una API moderna para operaciones de entrada/salida. Estos métodos están diseñados para leer archivos de texto completos de manera eficiente y con una sintaxis concisa.
Files.readAllLines()
El método readAllLines()
lee todas las líneas de un archivo y las devuelve como una lista de cadenas. Cada elemento de la lista representa una línea individual del archivo.
La sintaxis básica es:
List<String> lines = Files.readAllLines(Path path, Charset charset);
Donde:
path
es un objetoPath
que representa la ubicación del archivocharset
es el conjunto de caracteres utilizado para decodificar el archivo (opcional)
Veamos un ejemplo práctico:
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.List;
public class LeerLineas {
public static void main(String[] args) {
// Definir la ruta del archivo
Path ruta = Paths.get("datos.txt");
try {
// Leer todas las líneas del archivo
List<String> lineas = Files.readAllLines(ruta, StandardCharsets.UTF_8);
// Procesar cada línea
for (int i = 0; i < lineas.size(); i++) {
System.out.println("Línea " + (i + 1) + ": " + lineas.get(i));
}
// También podemos usar expresiones lambda
lineas.forEach(linea -> System.out.println("Contenido: " + linea));
} catch (IOException e) {
System.err.println("Error al leer el archivo: " + e.getMessage());
}
}
}
Este método es ideal para archivos de tamaño moderado donde necesitamos procesar el contenido línea por línea. Sin embargo, hay que tener en cuenta que carga todo el archivo en memoria, por lo que no es recomendable para archivos muy grandes.
Files.readString()
Introducido en Java 11, readString()
es aún más conciso que readAllLines()
. Este método lee todo el contenido de un archivo y lo devuelve como una única cadena de texto.
La sintaxis básica es:
String contenido = Files.readString(Path path, Charset charset);
Veamos un ejemplo:
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
public class LeerContenidoCompleto {
public static void main(String[] args) {
Path ruta = Paths.get("mensaje.txt");
try {
// Leer todo el contenido como una sola cadena
String contenido = Files.readString(ruta, StandardCharsets.UTF_8);
// Mostrar el contenido
System.out.println("Contenido del archivo:");
System.out.println(contenido);
// Podemos procesar el contenido como cualquier String
if (contenido.contains("importante")) {
System.out.println("El archivo contiene información importante");
}
// Contar palabras
String[] palabras = contenido.split("\\s+");
System.out.println("El archivo contiene " + palabras.length + " palabras");
} catch (IOException e) {
System.err.println("Error al leer el archivo: " + e.getMessage());
}
}
}
Este método es perfecto para situaciones donde necesitamos el contenido completo del archivo como una sola cadena, como al leer archivos de configuración, plantillas o cualquier texto que deba procesarse en su totalidad.
Manejo de excepciones
Ambos métodos lanzan una excepción IOException
si ocurre algún problema durante la lectura, por lo que es necesario manejarla adecuadamente. Algunas situaciones comunes que pueden causar excepciones son:
- El archivo no existe
- No se tienen permisos de lectura
- El archivo está siendo utilizado por otro proceso
- Problemas con la codificación de caracteres
Un patrón común para manejar estas situaciones es utilizar un bloque try-catch:
try {
String contenido = Files.readString(ruta);
// Procesar el contenido
} catch (NoSuchFileException e) {
System.err.println("El archivo no existe: " + e.getMessage());
} catch (AccessDeniedException e) {
System.err.println("No tienes permisos para leer el archivo: " + e.getMessage());
} catch (IOException e) {
System.err.println("Error al leer el archivo: " + e.getMessage());
}
Uso con try-with-resources
Aunque readAllLines()
y readString()
gestionan automáticamente el cierre de recursos, en operaciones más complejas podemos combinarlos con el patrón try-with-resources:
import java.nio.file.*;
import java.io.*;
public class ProcesarArchivo {
public static void main(String[] args) {
Path rutaEntrada = Paths.get("entrada.txt");
Path rutaSalida = Paths.get("salida.txt");
try {
// Leer el contenido
String contenido = Files.readString(rutaEntrada);
// Procesar el contenido (por ejemplo, convertir a mayúsculas)
String resultado = contenido.toUpperCase();
// Escribir el resultado en otro archivo
try (BufferedWriter writer = Files.newBufferedWriter(rutaSalida)) {
writer.write(resultado);
}
System.out.println("Procesamiento completado con éxito");
} catch (IOException e) {
System.err.println("Error durante el procesamiento: " + e.getMessage());
}
}
}
Rendimiento y consideraciones de memoria
Es importante considerar el tamaño del archivo al elegir entre estos métodos:
readAllLines()
carga cada línea como un objetoString
separado en una lista, lo que puede ser más eficiente en memoria para archivos grandes si solo necesitas procesar algunas líneas.readString()
carga todo el contenido en una única cadena, lo que es más eficiente para archivos pequeños o medianos que necesitas procesar en su totalidad.
Para archivos extremadamente grandes (cientos de MB o GB), ninguno de estos métodos es adecuado, y deberías considerar alternativas como BufferedReader
que permiten procesar el archivo por partes.
Ejemplo práctico: Análisis de un archivo de registro
Veamos un ejemplo más completo donde analizamos un archivo de registro para extraer información relevante:
import java.nio.file.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.io.IOException;
public class AnalizadorLogs {
public static void main(String[] args) {
Path rutaLog = Paths.get("servidor.log");
try {
// Leer todas las líneas del archivo de log
List<String> lineasLog = Files.readAllLines(rutaLog);
// Contar errores por fecha
Map<String, Integer> erroresPorFecha = new HashMap<>();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String fechaHoy = LocalDate.now().format(formatter);
for (String linea : lineasLog) {
if (linea.contains("ERROR")) {
// Extraer la fecha (asumimos formato: 2023-10-15 14:23:45 ERROR ...)
String fecha = linea.substring(0, 10);
// Incrementar contador para esta fecha
erroresPorFecha.put(fecha,
erroresPorFecha.getOrDefault(fecha, 0) + 1);
}
}
// Mostrar resultados
System.out.println("Análisis de errores en el log:");
erroresPorFecha.forEach((fecha, cantidad) -> {
System.out.printf("Fecha: %s - Errores: %d %s%n",
fecha, cantidad,
fecha.equals(fechaHoy) ? "(HOY)" : "");
});
// Calcular total de errores
int totalErrores = erroresPorFecha.values().stream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("Total de errores encontrados: " + totalErrores);
} catch (IOException e) {
System.err.println("Error al analizar el archivo de log: " + e.getMessage());
}
}
}
Este ejemplo muestra cómo readAllLines()
puede ser útil para analizar archivos de registro, donde necesitamos procesar cada línea individualmente para extraer información específica.
Files.writeString() y Files.write() (Java 11+)
Así como Java 11 introdujo métodos simplificados para la lectura de archivos, también incorporó métodos concisos para la escritura de datos en archivos. Los métodos Files.writeString()
y Files.write()
permiten escribir contenido en archivos de texto de manera eficiente y con una sintaxis mucho más limpia que las alternativas tradicionales.
Estos métodos forman parte del paquete java.nio.file
y están diseñados para complementar las operaciones de lectura que vimos anteriormente, proporcionando una API coherente para el manejo completo de archivos de texto.
Files.writeString()
El método writeString()
permite escribir una cadena de texto completa en un archivo en una sola operación. Este método es ideal cuando tenemos todo el contenido preparado como un único String
.
La sintaxis básica es:
Path path = Files.writeString(Path path, CharSequence content, Charset charset, OpenOption... options);
Donde:
path
es la ruta del archivo donde escribircontent
es el contenido a escribir (puede ser un String u otro CharSequence)charset
es la codificación de caracteres (opcional)options
son opciones adicionales para controlar cómo se realiza la escritura
Veamos un ejemplo práctico:
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
public class EscribirArchivo {
public static void main(String[] args) {
// Definir la ruta del archivo
Path ruta = Paths.get("salida.txt");
// Contenido a escribir
String contenido = "Este es un ejemplo de texto\n" +
"que será escrito en un archivo\n" +
"usando Files.writeString().\n";
try {
// Escribir el contenido en el archivo
Files.writeString(ruta, contenido, StandardCharsets.UTF_8);
System.out.println("Archivo escrito correctamente.");
// Añadir más contenido al final del archivo
String masContenido = "\nEsta línea se añade al final del archivo existente.";
Files.writeString(ruta, masContenido, StandardCharsets.UTF_8,
StandardOpenOption.APPEND);
System.out.println("Contenido adicional añadido al archivo.");
} catch (IOException e) {
System.err.println("Error al escribir en el archivo: " + e.getMessage());
}
}
}
En este ejemplo, primero escribimos un contenido inicial en el archivo, creándolo si no existe o sobrescribiéndolo si ya existe. Luego, añadimos más contenido al final del archivo existente utilizando la opción StandardOpenOption.APPEND
.
Files.write()
El método write()
es más versátil que writeString()
ya que puede escribir diferentes tipos de datos:
- Colecciones de líneas (como List)
- Arrays de bytes
- Iterables de cualquier tipo que pueda convertirse a String
La sintaxis básica para escribir líneas de texto es:
Path path = Files.write(Path path, Iterable<? extends CharSequence> lines, Charset charset, OpenOption... options);
Veamos algunos ejemplos:
Escribir una lista de líneas
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.List;
import java.util.Arrays;
public class EscribirLineas {
public static void main(String[] args) {
Path ruta = Paths.get("listado.txt");
// Lista de líneas a escribir
List<String> lineas = Arrays.asList(
"Primera línea del archivo",
"Segunda línea con datos importantes",
"Tercera línea con más información",
"Última línea del archivo"
);
try {
// Escribir todas las líneas de una vez
Files.write(ruta, lineas, StandardCharsets.UTF_8);
System.out.println("Archivo con líneas escrito correctamente.");
} catch (IOException e) {
System.err.println("Error al escribir las líneas: " + e.getMessage());
}
}
}
Este método es especialmente útil cuando tenemos los datos ya organizados en una colección de líneas, como podría ser el resultado de un procesamiento previo o datos extraídos de otra fuente.
Escribir datos binarios
También podemos usar Files.write()
para escribir datos binarios (bytes):
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
public class EscribirBytes {
public static void main(String[] args) {
Path ruta = Paths.get("datos.bin");
// Array de bytes a escribir
byte[] datos = {65, 66, 67, 68, 69, 70}; // Equivale a "ABCDEF" en ASCII
try {
// Escribir los bytes en el archivo
Files.write(ruta, datos);
System.out.println("Archivo binario escrito correctamente.");
} catch (IOException e) {
System.err.println("Error al escribir los bytes: " + e.getMessage());
}
}
}
Opciones de escritura
Tanto writeString()
como write()
aceptan opciones adicionales que controlan el comportamiento de la operación de escritura. Estas opciones se especifican mediante constantes de la enumeración StandardOpenOption
:
StandardOpenOption.CREATE
: Crea un nuevo archivo si no existe (comportamiento predeterminado)StandardOpenOption.CREATE_NEW
: Crea un nuevo archivo, fallando si ya existeStandardOpenOption.APPEND
: Añade contenido al final del archivo en lugar de sobrescribirloStandardOpenOption.TRUNCATE_EXISTING
: Elimina el contenido existente antes de escribir (comportamiento predeterminado)StandardOpenOption.WRITE
: Abre para escritura (implícito)StandardOpenOption.SYNC
: Sincroniza el contenido con el almacenamiento subyacente
Veamos un ejemplo que utiliza varias opciones:
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
public class OpcionesEscritura {
public static void main(String[] args) {
Path logFile = Paths.get("aplicacion.log");
try {
// Crear el archivo solo si no existe
if (Files.notExists(logFile)) {
Files.createFile(logFile);
System.out.println("Archivo de log creado.");
}
// Preparar entrada de log
String timestamp = LocalDateTime.now().toString();
String entradaLog = String.format("[%s] Aplicación iniciada\n", timestamp);
// Añadir al final del archivo con sincronización
Files.writeString(logFile, entradaLog, StandardCharsets.UTF_8,
StandardOpenOption.APPEND,
StandardOpenOption.SYNC);
System.out.println("Entrada añadida al log.");
// Simular algunas operaciones
System.out.println("Realizando operaciones...");
Thread.sleep(2000);
// Añadir otra entrada
timestamp = LocalDateTime.now().toString();
entradaLog = String.format("[%s] Operaciones completadas\n", timestamp);
Files.writeString(logFile, entradaLog, StandardCharsets.UTF_8,
StandardOpenOption.APPEND,
StandardOpenOption.SYNC);
System.out.println("Segunda entrada añadida al log.");
} catch (IOException e) {
System.err.println("Error de E/S: " + e.getMessage());
} catch (InterruptedException e) {
System.err.println("Operación interrumpida: " + e.getMessage());
}
}
}
Este ejemplo muestra cómo crear un archivo de registro (log) y añadir entradas secuencialmente, asegurando que cada escritura se sincronice con el almacenamiento físico.
Manejo de excepciones y buenas prácticas
Al escribir en archivos, es importante manejar adecuadamente las posibles excepciones que pueden ocurrir:
try {
Files.writeString(ruta, datos);
} catch (NoSuchFileException e) {
System.err.println("No se puede encontrar la ruta especificada: " + e.getMessage());
} catch (DirectoryNotEmptyException e) {
System.err.println("La ruta especifica un directorio no vacío: " + e.getMessage());
} catch (AccessDeniedException e) {
System.err.println("No tienes permisos para escribir en esta ubicación: " + e.getMessage());
} catch (IOException e) {
System.err.println("Error de E/S al escribir el archivo: " + e.getMessage());
}
Algunas buenas prácticas al escribir archivos:
- Verifica que tienes permisos de escritura antes de intentar escribir
- Considera hacer una copia de seguridad de archivos importantes antes de sobrescribirlos
- Para archivos críticos, escribe primero en un archivo temporal y luego renómbralo
- Utiliza
StandardOpenOption.SYNC
para datos importantes que no deben perderse
Ejemplo práctico: Generador de informes
Veamos un ejemplo más completo que genera un informe en formato CSV a partir de datos procesados:
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class GeneradorInformes {
static class Venta {
String producto;
double importe;
LocalDate fecha;
Venta(String producto, double importe, LocalDate fecha) {
this.producto = producto;
this.importe = importe;
this.fecha = fecha;
}
}
public static void main(String[] args) {
// Simular algunos datos de ventas
List<Venta> ventas = Arrays.asList(
new Venta("Laptop", 899.99, LocalDate.of(2023, 10, 15)),
new Venta("Monitor", 249.50, LocalDate.of(2023, 10, 15)),
new Venta("Teclado", 45.99, LocalDate.of(2023, 10, 16)),
new Venta("Mouse", 22.50, LocalDate.of(2023, 10, 16)),
new Venta("Laptop", 1299.99, LocalDate.of(2023, 10, 17)),
new Venta("Impresora", 189.99, LocalDate.of(2023, 10, 17))
);
// Generar informe CSV
Path rutaInforme = Paths.get("informe_ventas.csv");
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy");
try {
// Preparar encabezado y líneas del informe
List<String> lineasInforme = new ArrayList<>();
lineasInforme.add("Producto,Importe,Fecha");
for (Venta venta : ventas) {
String linea = String.format("%s,%.2f,%s",
venta.producto,
venta.importe,
venta.fecha.format(fmt));
lineasInforme.add(linea);
}
// Escribir el informe
Files.write(rutaInforme, lineasInforme, StandardCharsets.UTF_8);
System.out.println("Informe CSV generado en: " + rutaInforme.toAbsolutePath());
// Generar también un resumen
Path rutaResumen = Paths.get("resumen_ventas.txt");
// Calcular estadísticas
double totalVentas = ventas.stream()
.mapToDouble(v -> v.importe)
.sum();
double promedioVenta = totalVentas / ventas.size();
String resumen = String.format(
"RESUMEN DE VENTAS\n" +
"=================\n" +
"Período: %s - %s\n" +
"Total de ventas: %.2f €\n" +
"Número de transacciones: %d\n" +
"Importe promedio: %.2f €\n",
ventas.get(0).fecha.format(fmt),
ventas.get(ventas.size() - 1).fecha.format(fmt),
totalVentas,
ventas.size(),
promedioVenta
);
Files.writeString(rutaResumen, resumen, StandardCharsets.UTF_8);
System.out.println("Resumen de ventas generado en: " + rutaResumen.toAbsolutePath());
} catch (IOException e) {
System.err.println("Error al generar los informes: " + e.getMessage());
}
}
}
Este ejemplo muestra cómo podemos utilizar Files.write()
para generar un archivo CSV con múltiples líneas y Files.writeString()
para crear un informe de texto formateado, todo a partir de los mismos datos procesados.
Rendimiento y consideraciones
Los métodos writeString()
y write()
están optimizados para la mayoría de los casos de uso comunes, pero hay algunas consideraciones importantes:
- Para archivos pequeños o medianos, estos métodos son ideales por su simplicidad y rendimiento
- Para escrituras frecuentes en el mismo archivo, considera usar un
BufferedWriter
- Para archivos muy grandes o cuando necesitas control preciso sobre el buffer, las clases tradicionales como
FileOutputStream
pueden ser más apropiadas - Si necesitas añadir contenido frecuentemente a un archivo de log, considera abrir un
BufferedWriter
y mantenerlo abierto durante la ejecución del programa
Trabajar con BufferedReader/BufferedWriter cuando sea necesario
Aunque Java 11+ ofrece métodos simplificados como Files.readString()
y Files.writeString()
, existen situaciones donde necesitamos un control más granular sobre la lectura y escritura de archivos. Las clases BufferedReader
y BufferedWriter
son herramientas fundamentales para estos escenarios, proporcionando eficiencia y flexibilidad adicionales.
Estas clases utilizan un buffer interno para minimizar las operaciones de entrada/salida, lo que mejora significativamente el rendimiento cuando trabajamos con archivos grandes o cuando necesitamos procesar datos línea por línea.
¿Cuándo usar BufferedReader?
BufferedReader
es especialmente útil en los siguientes escenarios:
- Cuando necesitas procesar archivos muy grandes que no caben en memoria
- Cuando requieres leer el archivo línea por línea de forma eficiente
- Para implementar procesamiento en streaming sin cargar todo el contenido
- Cuando necesitas control preciso sobre el proceso de lectura
La sintaxis básica para crear un BufferedReader
es:
try (BufferedReader reader = new BufferedReader(new FileReader("archivo.txt"))) {
// Operaciones de lectura
}
Sin embargo, es recomendable utilizar la versión que permite especificar la codificación de caracteres:
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("archivo.txt"), StandardCharsets.UTF_8))) {
// Operaciones de lectura con codificación específica
}
O mejor aún, utilizando los métodos de la clase Files
:
try (BufferedReader reader = Files.newBufferedReader(Paths.get("archivo.txt"), StandardCharsets.UTF_8)) {
// Operaciones de lectura
}
Lectura eficiente línea por línea
Una de las principales ventajas de BufferedReader
es su método readLine()
, que permite leer el archivo línea por línea:
import java.io.BufferedReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
public class ProcesadorLineas {
public static void main(String[] args) {
try (BufferedReader reader = Files.newBufferedReader(
Paths.get("datos_grandes.csv"), StandardCharsets.UTF_8)) {
String linea;
long contador = 0;
// Leer y procesar cada línea individualmente
while ((linea = reader.readLine()) != null) {
// Procesar solo si la línea contiene datos relevantes
if (!linea.isEmpty() && !linea.startsWith("#")) {
procesarLinea(linea);
contador++;
// Mostrar progreso cada 10,000 líneas
if (contador % 10_000 == 0) {
System.out.printf("Procesadas %,d líneas%n", contador);
}
}
}
System.out.printf("Procesamiento completado. Total: %,d líneas%n", contador);
} catch (IOException e) {
System.err.println("Error durante la lectura: " + e.getMessage());
}
}
private static void procesarLinea(String linea) {
// Ejemplo: dividir la línea por comas y procesar los campos
String[] campos = linea.split(",");
// Aquí iría la lógica específica de procesamiento
}
}
Este enfoque es mucho más eficiente para archivos grandes que cargar todo el contenido en memoria con Files.readAllLines()
, ya que solo mantiene una línea en memoria a la vez.
Procesamiento de archivos con millones de líneas
Para archivos extremadamente grandes, BufferedReader
es la opción ideal:
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
public class AnalizadorLogGigante {
public static void main(String[] args) {
Path rutaLog = Paths.get("servidor_gigante.log");
Map<String, Integer> erroresPorTipo = new HashMap<>();
try (BufferedReader reader = Files.newBufferedReader(rutaLog, StandardCharsets.UTF_8)) {
String linea;
long lineasProcesadas = 0;
long inicio = System.currentTimeMillis();
while ((linea = reader.readLine()) != null) {
lineasProcesadas++;
// Buscar líneas de error y clasificarlas
if (linea.contains("ERROR")) {
// Extraer el tipo de error (suponemos formato: "ERROR: [TipoError] Mensaje")
int inicio1 = linea.indexOf("[");
int fin = linea.indexOf("]", inicio1);
if (inicio1 != -1 && fin != -1) {
String tipoError = linea.substring(inicio1 + 1, fin);
erroresPorTipo.put(tipoError, erroresPorTipo.getOrDefault(tipoError, 0) + 1);
}
}
// Mostrar progreso periódicamente
if (lineasProcesadas % 100_000 == 0) {
long tiempoActual = System.currentTimeMillis();
double segundos = (tiempoActual - inicio) / 1000.0;
System.out.printf("Procesadas %,d líneas (%.2f líneas/seg)%n",
lineasProcesadas, lineasProcesadas / segundos);
}
}
// Mostrar resultados
System.out.println("\nAnálisis completado:");
System.out.printf("Total líneas procesadas: %,d%n", lineasProcesadas);
System.out.println("Distribución de errores:");
erroresPorTipo.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.forEach(entry -> System.out.printf(" %-20s: %,d%n", entry.getKey(), entry.getValue()));
} catch (IOException e) {
System.err.println("Error al procesar el archivo: " + e.getMessage());
}
}
}
Este ejemplo muestra cómo procesar un archivo de registro extremadamente grande, manteniendo estadísticas mientras se lee secuencialmente, sin necesidad de cargar todo el archivo en memoria.
¿Cuándo usar BufferedWriter?
BufferedWriter
es la contraparte para escritura y resulta especialmente útil en estos casos:
- Para escribir grandes volúmenes de datos de manera eficiente
- Cuando necesitas control preciso sobre el momento de escritura en disco
- Para escrituras frecuentes donde el buffering mejora el rendimiento
- Cuando requieres funcionalidades específicas como
newLine()
La forma recomendada de crear un BufferedWriter
es:
try (BufferedWriter writer = Files.newBufferedWriter(
Paths.get("salida.txt"), StandardCharsets.UTF_8)) {
// Operaciones de escritura
}
Escritura eficiente línea por línea
BufferedWriter
permite escribir contenido de forma eficiente, especialmente cuando se trata de múltiples líneas:
import java.io.BufferedWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.Random;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class GeneradorDatos {
public static void main(String[] args) {
// Generar un millón de registros de datos simulados
int totalRegistros = 1_000_000;
Random random = new Random();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
try (BufferedWriter writer = Files.newBufferedWriter(
Paths.get("datos_generados.csv"), StandardCharsets.UTF_8)) {
// Escribir encabezado
writer.write("id,timestamp,temperatura,humedad,presion");
writer.newLine();
// Generar y escribir datos
for (int i = 1; i <= totalRegistros; i++) {
// Simular datos de sensores
LocalDateTime timestamp = LocalDateTime.now().minusSeconds(random.nextInt(86400));
double temperatura = 15.0 + random.nextDouble() * 20.0;
int humedad = 30 + random.nextInt(70);
double presion = 1000.0 + random.nextDouble() * 50.0;
// Formatear y escribir la línea
String linea = String.format("%d,%s,%.2f,%d,%.2f",
i, timestamp.format(formatter), temperatura, humedad, presion);
writer.write(linea);
writer.newLine(); // Método específico para añadir salto de línea
// Mostrar progreso
if (i % 100_000 == 0) {
System.out.printf("Generados %,d registros (%.1f%%)%n",
i, (i * 100.0 / totalRegistros));
}
}
System.out.println("Generación de datos completada.");
} catch (IOException e) {
System.err.println("Error al generar los datos: " + e.getMessage());
}
}
}
Este ejemplo muestra cómo generar un archivo CSV con un millón de registros de forma eficiente. El método newLine()
es especialmente útil porque inserta el separador de línea específico de la plataforma.
Combinando BufferedReader y BufferedWriter
Un caso de uso común es procesar un archivo y generar otro, como en este ejemplo de filtrado y transformación:
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class TransformadorArchivos {
public static void main(String[] args) {
Path archivoEntrada = Paths.get("datos_crudos.txt");
Path archivoSalida = Paths.get("datos_procesados.txt");
try (BufferedReader reader = Files.newBufferedReader(archivoEntrada, StandardCharsets.UTF_8);
BufferedWriter writer = Files.newBufferedWriter(archivoSalida, StandardCharsets.UTF_8)) {
String linea;
int lineasLeidas = 0;
int lineasEscritas = 0;
// Procesar el archivo línea por línea
while ((linea = reader.readLine()) != null) {
lineasLeidas++;
// Aplicar transformaciones y filtrado
if (linea.trim().isEmpty() || linea.startsWith("#")) {
continue; // Saltar líneas vacías y comentarios
}
// Transformar la línea (ejemplo: convertir a mayúsculas y añadir timestamp)
String lineaProcesada = String.format("[%d] %s",
System.currentTimeMillis(), linea.toUpperCase());
// Escribir la línea procesada
writer.write(lineaProcesada);
writer.newLine();
lineasEscritas++;
}
System.out.printf("Procesamiento completado: %d líneas leídas, %d líneas escritas%n",
lineasLeidas, lineasEscritas);
} catch (IOException e) {
System.err.println("Error durante el procesamiento: " + e.getMessage());
}
}
}
Control del buffer y flush
Una característica importante de BufferedWriter
es el control sobre cuándo se vacía el buffer y se escribe físicamente en el disco:
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.time.LocalDateTime;
public class RegistroActividad {
public static void main(String[] args) {
Path archivoLog = Paths.get("actividad.log");
try (BufferedWriter logWriter = Files.newBufferedWriter(
archivoLog, StandardCharsets.UTF_8, StandardOpenOption.CREATE,
StandardOpenOption.APPEND)) {
// Simular actividades que generan entradas de log
for (int i = 1; i <= 10; i++) {
String mensaje = String.format("[%s] Actividad %d ejecutada",
LocalDateTime.now(), i);
logWriter.write(mensaje);
logWriter.newLine();
// Forzar la escritura física después de cada entrada
// Esto garantiza que los datos se escriban inmediatamente
logWriter.flush();
System.out.println("Registrada: " + mensaje);
// Simular tiempo entre actividades
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (IOException e) {
System.err.println("Error al escribir en el log: " + e.getMessage());
}
}
}
El método flush()
fuerza la escritura inmediata del contenido del buffer al archivo físico, lo que es crucial para aplicaciones como registros de actividad donde queremos asegurarnos de que los datos se escriban de inmediato, incluso en caso de fallo del programa.
Rendimiento: BufferedReader/Writer vs. métodos de Files
Para entender mejor cuándo usar cada enfoque, consideremos las diferencias de rendimiento:
Files.readAllLines()
yFiles.readString()
: Excelentes para archivos pequeños o medianos (hasta decenas de MB), pero consumen más memoria.BufferedReader
: Superior para archivos grandes, procesamiento línea por línea y cuando la memoria es limitada.Files.writeString()
yFiles.write()
: Ideales para escrituras simples y únicas.BufferedWriter
: Mejor para escrituras frecuentes, archivos grandes o cuando necesitas control preciso.
Ejemplo práctico: Procesamiento de datos CSV
Veamos un ejemplo completo que muestra cómo procesar un archivo CSV grande, realizar cálculos y generar un informe:
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
public class ProcesadorVentas {
static class Venta {
String region;
String producto;
double importe;
Venta(String region, String producto, double importe) {
this.region = region;
this.producto = producto;
this.importe = importe;
}
}
public static void main(String[] args) {
Path archivoVentas = Paths.get("ventas_anuales.csv");
Path archivoInforme = Paths.get("informe_regional.txt");
Map<String, Double> ventasPorRegion = new HashMap<>();
Set<String> productos = new TreeSet<>();
// Procesar el archivo de ventas
try (BufferedReader reader = Files.newBufferedReader(archivoVentas, StandardCharsets.UTF_8)) {
String linea;
boolean primeraLinea = true;
while ((linea = reader.readLine()) != null) {
// Saltar la línea de encabezado
if (primeraLinea) {
primeraLinea = false;
continue;
}
// Parsear la línea CSV
String[] campos = linea.split(",");
if (campos.length >= 3) {
String region = campos[0].trim();
String producto = campos[1].trim();
double importe = Double.parseDouble(campos[2].trim());
// Actualizar estadísticas
ventasPorRegion.put(region,
ventasPorRegion.getOrDefault(region, 0.0) + importe);
productos.add(producto);
}
}
// Generar informe
try (BufferedWriter writer = Files.newBufferedWriter(archivoInforme, StandardCharsets.UTF_8)) {
writer.write("INFORME DE VENTAS POR REGIÓN");
writer.newLine();
writer.write("==========================");
writer.newLine();
writer.newLine();
writer.write(String.format("Total de productos diferentes: %d", productos.size()));
writer.newLine();
writer.write(String.format("Total de regiones: %d", ventasPorRegion.size()));
writer.newLine();
writer.newLine();
writer.write("VENTAS POR REGIÓN:");
writer.newLine();
writer.write("-----------------");
writer.newLine();
// Ordenar regiones por volumen de ventas (de mayor a menor)
ventasPorRegion.entrySet().stream()
.sorted(Map.Entry.<String, Double>comparingByValue().reversed())
.forEach(entry -> {
try {
writer.write(String.format("%-15s: %,.2f €",
entry.getKey(), entry.getValue()));
writer.newLine();
} catch (IOException e) {
System.err.println("Error al escribir: " + e.getMessage());
}
});
System.out.println("Informe generado correctamente en: " + archivoInforme);
}
} catch (IOException e) {
System.err.println("Error durante el procesamiento: " + e.getMessage());
}
}
}
Este ejemplo demuestra cómo BufferedReader
y BufferedWriter
pueden trabajar juntos para procesar datos de forma eficiente, incluso con archivos grandes.
Gestión de codificación de caracteres
Un aspecto crucial al trabajar con archivos de texto es la codificación de caracteres. Tanto BufferedReader
como BufferedWriter
permiten especificar la codificación, lo que es esencial para manejar correctamente caracteres internacionales:
// Lectura con codificación específica
try (BufferedReader reader = Files.newBufferedReader(
Paths.get("datos_utf8.txt"), StandardCharsets.UTF_8)) {
// Procesar archivo UTF-8
}
// Escritura con codificación específica
try (BufferedWriter writer = Files.newBufferedReader(
Paths.get("salida_latin1.txt"), StandardCharsets.ISO_8859_1)) {
// Escribir con codificación Latin-1
}
Java proporciona varias constantes en StandardCharsets
para las codificaciones más comunes:
StandardCharsets.UTF_8
: La codificación recomendada para la mayoría de los casosStandardCharsets.UTF_16
: Para textos que requieren caracteres Unicode de 16 bitsStandardCharsets.ISO_8859_1
: También conocida como Latin-1, para idiomas de Europa occidentalStandardCharsets.US_ASCII
: Para texto ASCII básico (7 bits)
En general, UTF-8 es la mejor opción para la mayoría de las aplicaciones modernas, ya que soporta todos los caracteres Unicode de forma eficiente.
Gestión de codificación de caracteres (UTF-8)
La correcta gestión de la codificación de caracteres es fundamental cuando trabajamos con archivos de texto en Java. Una codificación inadecuada puede provocar problemas como la aparición de caracteres extraños (mojibake), pérdida de información o incluso errores en tiempo de ejecución.
En el mundo moderno del desarrollo de software, UTF-8 se ha convertido en el estándar de facto para la codificación de texto. Esta codificación permite representar prácticamente cualquier carácter de cualquier idioma, lo que la hace ideal para aplicaciones internacionales.
¿Por qué es importante la codificación de caracteres?
Cuando trabajamos con archivos de texto, lo que realmente almacenamos son bytes, no caracteres. La codificación determina cómo se mapean estos bytes a caracteres legibles. Diferentes codificaciones utilizan diferentes mapeos, lo que significa que el mismo conjunto de bytes puede interpretarse como caracteres completamente distintos según la codificación utilizada.
Por ejemplo, consideremos el carácter español "ñ":
- En UTF-8, se representa con los bytes
0xC3 0xB1
- En ISO-8859-1 (Latin-1), se representa con el byte
0xF1
- En Windows-1252, también se representa con
0xF1
Si leemos un archivo que contiene "ñ" guardado en ISO-8859-1, pero lo interpretamos como UTF-8, veremos "ñ" en lugar de "ñ".
Especificar la codificación en Java
Java proporciona la clase StandardCharsets
(del paquete java.nio.charset
) que contiene constantes para las codificaciones más comunes:
import java.nio.charset.StandardCharsets;
// Codificaciones disponibles como constantes
Charset utf8 = StandardCharsets.UTF_8;
Charset utf16 = StandardCharsets.UTF_16;
Charset latin1 = StandardCharsets.ISO_8859_1;
Charset ascii = StandardCharsets.US_ASCII;
También podemos obtener una codificación por su nombre:
import java.nio.charset.Charset;
Charset windows1252 = Charset.forName("windows-1252");
Charset gbk = Charset.forName("GBK"); // Codificación china
Detectar la codificación de un archivo
Java no proporciona una forma nativa de detectar automáticamente la codificación de un archivo. Existen algunas estrategias para intentar determinarla:
- Marcas BOM (Byte Order Mark): Algunos archivos UTF-8 o UTF-16 comienzan con bytes especiales que indican su codificación.
- Heurísticas: Analizar patrones de bytes para inferir la codificación probable.
- Bibliotecas externas: Como juniversalchardet, que implementa el algoritmo de detección de Mozilla.
Un ejemplo simple para detectar BOM en archivos:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class DetectorBOM {
public static void main(String[] args) throws IOException {
Path archivo = Paths.get("ejemplo.txt");
byte[] bytes = Files.readAllBytes(archivo);
String codificacion = "desconocida";
// Verificar marcas BOM
if (bytes.length >= 3 &&
bytes[0] == (byte)0xEF &&
bytes[1] == (byte)0xBB &&
bytes[2] == (byte)0xBF) {
codificacion = "UTF-8 con BOM";
} else if (bytes.length >= 2 &&
bytes[0] == (byte)0xFE &&
bytes[1] == (byte)0xFF) {
codificacion = "UTF-16BE";
} else if (bytes.length >= 2 &&
bytes[0] == (byte)0xFF &&
bytes[1] == (byte)0xFE) {
codificacion = "UTF-16LE";
}
System.out.println("Codificación detectada: " + codificacion);
}
}
Conversión entre codificaciones
En ocasiones necesitamos convertir texto entre diferentes codificaciones:
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class ConversionCodificacion {
public static void main(String[] args) {
// Texto original en UTF-8
String textoOriginal = "Hola, こんにちは, привет, 你好";
// Convertir a bytes usando Latin-1 (perderemos caracteres no latinos)
byte[] bytesLatin1 = textoOriginal.getBytes(StandardCharsets.ISO_8859_1);
// Convertir de vuelta a String usando Latin-1
String textoLatin1 = new String(bytesLatin1, StandardCharsets.ISO_8859_1);
// Convertir a bytes usando UTF-8 (preserva todos los caracteres)
byte[] bytesUtf8 = textoOriginal.getBytes(StandardCharsets.UTF_8);
// Convertir de vuelta a String usando UTF-8
String textoUtf8 = new String(bytesUtf8, StandardCharsets.UTF_8);
System.out.println("Original: " + textoOriginal);
System.out.println("Después de Latin-1: " + textoLatin1);
System.out.println("Después de UTF-8: " + textoUtf8);
// Mostrar diferencia en número de bytes
System.out.println("Longitud en Latin-1: " + bytesLatin1.length + " bytes");
System.out.println("Longitud en UTF-8: " + bytesUtf8.length + " bytes");
}
}
Configuración de la codificación por defecto
Java utiliza la codificación del sistema operativo como predeterminada cuando no se especifica una. Esto puede causar problemas de portabilidad, ya que diferentes sistemas pueden tener diferentes codificaciones predeterminadas.
Podemos verificar y establecer la codificación predeterminada:
import java.nio.charset.Charset;
public class CodificacionPredeterminada {
public static void main(String[] args) {
// Obtener la codificación predeterminada del sistema
Charset predeterminada = Charset.defaultCharset();
System.out.println("Codificación predeterminada: " + predeterminada);
// También podemos establecerla mediante propiedades del sistema
// (debe hacerse al inicio de la aplicación)
System.setProperty("file.encoding", "UTF-8");
// Nota: En Java 9+ esto no siempre funciona debido a cambios en la JVM
// La mejor práctica es especificar siempre la codificación explícitamente
}
}
Buenas prácticas para trabajar con UTF-8
Para garantizar una gestión adecuada de la codificación, especialmente con UTF-8, sigue estas recomendaciones:
- 1. Especifica siempre la codificación explícitamente en todas las operaciones de lectura/escritura:
// Al leer archivos
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
// Leer con UTF-8 explícito
}
// Al escribir archivos
try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
// Escribir con UTF-8 explícito
}
- 2. Utiliza UTF-8 para todos los archivos nuevos que crees:
// Crear un archivo de configuración en UTF-8
Path config = Paths.get("config.json");
String contenido = "{ \"nombre\": \"José Pérez\", \"país\": \"España\" }";
Files.writeString(config, contenido, StandardCharsets.UTF_8);
- 3. Añade BOM cuando sea necesario para compatibilidad con algunas aplicaciones (especialmente de Microsoft):
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class EscribirConBOM {
public static void main(String[] args) throws Exception {
Path archivo = Paths.get("con_bom.txt");
String texto = "Este archivo tiene BOM UTF-8";
// Escribir BOM UTF-8 seguido del contenido
try (OutputStream out = Files.newOutputStream(archivo,
StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
// Escribir BOM UTF-8 (EF BB BF)
out.write(new byte[] { (byte)0xEF, (byte)0xBB, (byte)0xBF });
// Escribir el contenido en UTF-8
out.write(texto.getBytes(StandardCharsets.UTF_8));
}
System.out.println("Archivo creado con BOM UTF-8");
}
}
- 4. Maneja correctamente los caracteres especiales en URLs y nombres de archivo:
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
public class CodificacionURL {
public static void main(String[] args) throws Exception {
String parametro = "búsqueda=café&país=España";
// Codificar para URL
String codificado = URLEncoder.encode(parametro, StandardCharsets.UTF_8.toString());
System.out.println("URL codificada: " + codificado);
// Decodificar desde URL
String decodificado = URLDecoder.decode(codificado, StandardCharsets.UTF_8.toString());
System.out.println("URL decodificada: " + decodificado);
}
}
Manejo de problemas comunes de codificación
Problema 1: Caracteres extraños al leer archivos
Si al leer un archivo aparecen caracteres extraños, probablemente estás usando una codificación incorrecta:
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class SolucionarProblemasDecodificacion {
public static void main(String[] args) throws Exception {
Path archivo = Paths.get("texto_con_acentos.txt");
// Intentar con diferentes codificaciones
Charset[] codificaciones = {
StandardCharsets.UTF_8,
StandardCharsets.ISO_8859_1,
Charset.forName("windows-1252")
};
for (Charset charset : codificaciones) {
try {
List<String> lineas = Files.readAllLines(archivo, charset);
System.out.println("Usando " + charset + ":");
lineas.forEach(System.out::println);
System.out.println("-------------------");
} catch (Exception e) {
System.out.println("Error con " + charset + ": " + e.getMessage());
}
}
}
}
Problema 2: Pérdida de caracteres al escribir
Si al escribir un archivo se pierden caracteres, asegúrate de usar una codificación que soporte todos los caracteres necesarios:
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class ComparacionEscritura {
public static void main(String[] args) throws Exception {
String texto = "Caracteres especiales: áéíóúñÑ€¥£©®™";
// Escribir con diferentes codificaciones
Path archivoUtf8 = Paths.get("especiales_utf8.txt");
Path archivoLatin1 = Paths.get("especiales_latin1.txt");
Files.writeString(archivoUtf8, texto, StandardCharsets.UTF_8);
Files.writeString(archivoLatin1, texto, StandardCharsets.ISO_8859_1);
// Leer y comparar resultados
String leido1 = Files.readString(archivoUtf8, StandardCharsets.UTF_8);
String leido2 = Files.readString(archivoLatin1, StandardCharsets.ISO_8859_1);
System.out.println("Original: " + texto);
System.out.println("Leído de UTF-8: " + leido1);
System.out.println("Leído de Latin-1: " + leido2);
System.out.println("¿UTF-8 preservó todo? " + texto.equals(leido1));
System.out.println("¿Latin-1 preservó todo? " + texto.equals(leido2));
}
}
Trabajando con archivos de diferentes codificaciones
En entornos reales, a menudo necesitamos trabajar con archivos que utilizan diferentes codificaciones. Aquí hay un ejemplo de cómo convertir un archivo de una codificación a otra:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class ConvertidorCodificacion {
public static void main(String[] args) {
Path archivoOrigen = Paths.get("origen_windows1252.txt");
Path archivoDestino = Paths.get("destino_utf8.txt");
// Codificaciones de origen y destino
Charset codificacionOrigen = Charset.forName("windows-1252");
Charset codificacionDestino = StandardCharsets.UTF_8;
try (BufferedReader reader = Files.newBufferedReader(archivoOrigen, codificacionOrigen);
BufferedWriter writer = Files.newBufferedWriter(archivoDestino, codificacionDestino)) {
String linea;
while ((linea = reader.readLine()) != null) {
// Aquí podríamos hacer transformaciones adicionales si fuera necesario
writer.write(linea);
writer.newLine();
}
System.out.println("Archivo convertido exitosamente de " +
codificacionOrigen + " a " + codificacionDestino);
} catch (Exception e) {
System.err.println("Error durante la conversión: " + e.getMessage());
e.printStackTrace();
}
}
}
Consideraciones para aplicaciones internacionales
Si estás desarrollando aplicaciones que se utilizarán en diferentes países o que manejarán texto en múltiples idiomas, considera estas recomendaciones adicionales:
- Utiliza siempre UTF-8 como codificación predeterminada para todos los archivos de texto.
- Normaliza las cadenas Unicode cuando sea necesario comparar textos con caracteres especiales.
- Considera las diferencias culturales en la ordenación y comparación de cadenas.
import java.text.Collator;
import java.text.Normalizer;
import java.util.Arrays;
import java.util.Locale;
public class TextoInternacional {
public static void main(String[] args) {
// Normalización de texto Unicode
String s1 = "café"; // 'é' como un solo carácter
String s2 = "cafe\u0301"; // 'e' seguido del acento combinante
System.out.println("Sin normalizar:");
System.out.println("s1: " + s1 + ", longitud: " + s1.length());
System.out.println("s2: " + s2 + ", longitud: " + s2.length());
System.out.println("¿Son iguales? " + s1.equals(s2));
// Normalizar a la forma NFC (composición canónica)
String n1 = Normalizer.normalize(s1, Normalizer.Form.NFC);
String n2 = Normalizer.normalize(s2, Normalizer.Form.NFC);
System.out.println("\nNormalizados (NFC):");
System.out.println("n1: " + n1 + ", longitud: " + n1.length());
System.out.println("n2: " + n2 + ", longitud: " + n2.length());
System.out.println("¿Son iguales? " + n1.equals(n2));
// Ordenación sensible al idioma
String[] palabras = {"apple", "época", "zebra", "ñandú", "árbol"};
// Ordenación estándar (basada en valores Unicode)
Arrays.sort(palabras);
System.out.println("\nOrdenación estándar:");
Arrays.stream(palabras).forEach(System.out::println);
// Ordenación específica para español
Collator collator = Collator.getInstance(new Locale("es", "ES"));
Arrays.sort(palabras, collator);
System.out.println("\nOrdenación para español:");
Arrays.stream(palabras).forEach(System.out::println);
}
}
Resumen de codificaciones comunes
- UTF-8: Codificación variable (1-4 bytes por carácter). Compatible con ASCII. Recomendada para casi todos los casos.
- UTF-16: Codificación variable (2 o 4 bytes por carácter). Usada internamente por Java para String.
- ISO-8859-1 (Latin-1): Codificación de 1 byte para idiomas de Europa occidental.
- Windows-1252: Similar a Latin-1 pero con caracteres adicionales en posiciones que Latin-1 no usa.
- US-ASCII: Codificación de 7 bits para caracteres básicos ingleses.
La elección de UTF-8 como estándar para todos tus archivos de texto simplificará enormemente el manejo de caracteres internacionales y evitará muchos problemas de compatibilidad, especialmente en aplicaciones que se ejecutan en diferentes plataformas o que intercambian datos con otros sistemas.
Ejercicios de esta lección Leer y escribir archivos
Evalúa tus conocimientos de esta lección Leer y escribir archivos con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Streams: match
Gestión de errores y excepciones
CRUD en Java de modelo Customer sobre un ArrayList
Clases abstractas
Listas
Métodos de la clase String
Streams: reduce()
API java.nio 2
Polimorfismo
Pattern Matching
Streams: flatMap()
Llamada y sobrecarga de funciones
Métodos referenciados
Métodos de la clase String
Representación de Fecha
Operadores lógicos
Inferencia de tipos con var
Tipos de datos
Estructuras de iteración
Streams: forEach()
Objetos
Funciones lambda
Uso de Scanner
Tipos de variables
Streams: collect()
Operadores aritméticos
Arrays y matrices
Clases y objetos
Interfaz funcional Consumer
CRUD en Java de modelo Customer sobre un HashMap
Interfaces
Enumeraciones Enums
API Optional
Interfaz funcional Function
Encapsulación
Interfaces
Uso de API Optional
Representación de Hora
Herencia básica
Clases y objetos
Interfaz funcional Supplier
HashMap
Sobrecarga de métodos
Polimorfismo de tiempo de ejecución
OOP en Java
Sobrecarga de métodos
CRUD de productos en Java
Clases sealed
Creación de Streams
Records
Encapsulación
Streams: min max
Herencia
Métodos avanzados de la clase String
Funciones
Polimorfismo de tiempo de compilación
Reto sintaxis Java
Conjuntos
Estructuras de control
Recursión
Excepciones
Herencia avanzada
Estructuras de selección
Uso de interfaces
Operadores
Variables
HashSet
Objeto Scanner
Streams: filter()
Operaciones de Streams
Interfaz funcional Predicate
Streams: sorted()
Configuración de entorno
Uso de variables
Clases
Streams: distinct()
Streams: count()
ArrayList
Mapas
Datos de referencia
Interfaces funcionales
Métodos básicos de la clase String
Tipos de datos
Clases abstractas
Instalación
Funciones
Excepciones
Estructuras de control
Herencia de clases
La clase Scanner
Generics
Streams: map()
Funciones y encapsulamiento
Todas las lecciones de Java
Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Instalación De Java
Introducción Y Entorno
Configuración De Entorno Java
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Recursión
Sintaxis
Arrays Y Matrices
Sintaxis
Excepciones
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Clases Abstractas
Programación Orientada A Objetos
Interfaces
Programación Orientada A Objetos
Sobrecarga De Métodos
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
La Clase Scanner
Programación Orientada A Objetos
Métodos De La Clase String
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Records
Programación Orientada A Objetos
Pattern Matching
Programación Orientada A Objetos
Inferencia De Tipos Con Var
Programación Orientada A Objetos
Enumeraciones Enums
Programación Orientada A Objetos
Generics
Programación Orientada A Objetos
Clases Sealed
Programación Orientada A Objetos
Listas
Framework Collections
Conjuntos
Framework Collections
Mapas
Framework Collections
Funciones Lambda
Programación Funcional
Interfaz Funcional Consumer
Programación Funcional
Interfaz Funcional Predicate
Programación Funcional
Interfaz Funcional Supplier
Programación Funcional
Interfaz Funcional Function
Programación Funcional
Métodos Referenciados
Programación Funcional
Creación De Streams
Programación Funcional
Operaciones Intermedias Con Streams: Map()
Programación Funcional
Operaciones Intermedias Con Streams: Filter()
Programación Funcional
Operaciones Intermedias Con Streams: Distinct()
Programación Funcional
Operaciones Finales Con Streams: Collect()
Programación Funcional
Operaciones Finales Con Streams: Min Max
Programación Funcional
Operaciones Intermedias Con Streams: Flatmap()
Programación Funcional
Operaciones Intermedias Con Streams: Sorted()
Programación Funcional
Operaciones Finales Con Streams: Reduce()
Programación Funcional
Operaciones Finales Con Streams: Foreach()
Programación Funcional
Operaciones Finales Con Streams: Count()
Programación Funcional
Operaciones Finales Con Streams: Match
Programación Funcional
Api Optional
Programación Funcional
Transformación
Programación Funcional
Reducción Y Acumulación
Programación Funcional
Mapeo
Programación Funcional
Streams Paralelos
Programación Funcional
Agrupación Y Partición
Programación Funcional
Filtrado Y Búsqueda
Programación Funcional
Api Java.nio 2
Entrada Y Salida Io
Fundamentos De Io
Entrada Y Salida Io
Leer Y Escribir Archivos
Entrada Y Salida Io
Httpclient Moderno
Entrada Y Salida Io
Clases De Nio2
Entrada Y Salida Io
Api Java.time
Api Java.time
Localtime
Api Java.time
Localdatetime
Api Java.time
Localdate
Api Java.time
Executorservice
Concurrencia
Virtual Threads (Project Loom)
Concurrencia
Future Y Completablefuture
Concurrencia
Spring Framework
Frameworks Para Java
Micronaut
Frameworks Para Java
Maven
Frameworks Para Java
Gradle
Frameworks Para Java
Lombok Para Java
Frameworks Para Java
Quarkus
Frameworks Para Java
Ecosistema Jakarta Ee De Java
Frameworks Para Java
Introducción A Junit 5
Testing
Certificados de superación de Java
Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender y utilizar los métodos Files.readAllLines() y Files.readString() para la lectura de archivos.
- Aprender a escribir archivos con Files.writeString() y Files.write(), incluyendo opciones de apertura y codificación.
- Conocer el uso eficiente de BufferedReader y BufferedWriter para manejar archivos grandes o con control granular.
- Entender la importancia de la codificación de caracteres, especialmente UTF-8, y cómo gestionarla en Java.
- Aplicar buenas prácticas y manejo de excepciones en operaciones de entrada/salida de archivos.