HOWTO · Java

Cómo eliminar los saltos de línea de un archivo en Java

Este tutorial muestra cómo eliminar los saltos de línea de un archivo en Java con múltiples enfoques como replace, separador de línea, replaceAll.

En esta página

Este tutorial muestra cómo eliminar los saltos de línea de un archivo en Java. Para un archivo pequeño o mediano, lea todo el archivo en un String con Files.readString(), elimine cada salto de línea con replaceAll("\\R", "") y escriba el resultado con Files.writeString(). También cubre archivos de Windows con \r\n, archivos grandes procesados línea por línea, una variante con Scanner y la eliminación de solo el salto final. Los ejemplos necesitan Java 11 o posterior; ambos métodos de Files se agregaron en Java 11.

Cada ejemplo trabaja sobre este archivo de ejemplo, notes.txt:

Remove line breaks
from this file
with Java

Siempre que un ejemplo afirma que los saltos desaparecieron, la afirmación se comprueba con cat -A, que imprime un $ al final de cada línea y muestra un retorno de carro como ^M:

$ cat -A notes.txt
Remove line breaks$
from this file$
with Java$

Eliminar cada salto de línea con Files.readString() y replaceAll()

Files.readString() lee el archivo completo en un String, decodificándolo como UTF-8. El patrón de expresión regular \R coincide con cualquier salto de línea Unicode: un avance de línea, un retorno de carro o un par \r\n tratado como un único salto. Eliminar todas las coincidencias borra por tanto los tres tipos de finales de línea en una sola pasada. El texto limpio se guarda luego en un archivo nuevo, cleaned.txt:

Códigos de ejemplo:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class RemoveAllBreaks {
    public static void main(String[] args) throws IOException {
        Path input = Path.of("notes.txt");
        String content = Files.readString(input);
        String cleaned = content.replaceAll("\\R", "");
        Files.writeString(Path.of("cleaned.txt"), cleaned);
        System.out.println(cleaned);
    }
}

Salida:

Remove line breaksfrom this filewith Java

Observe que las palabras breaks y from quedan pegadas: al eliminar un salto no se inserta nada en su lugar. La comprobación con cat -A confirma que no queda ningún $, así que cleaned.txt es una sola línea sin salto de línea final:

$ cat -A cleaned.txt
Remove line breaksfrom this filewith Java

Manejar los finales de línea CRLF de Windows

Los archivos de texto creados en Windows terminan cada línea con \r\n; los archivos de Linux y macOS usan solo \n. Esto rompe la solución ingenua, replace("\n", ""): elimina la mitad \n de cada final de línea de Windows pero deja la mitad \r unida a cada línea. El texto se ve correcto al imprimirlo, pero los retornos de carro ocultos corrompen el procesamiento posterior, porque cada valor releído conserva un \r al final. El siguiente ejemplo trabaja sobre un archivo de estilo Windows, notes-crlf.txt, y escribe en disco tanto el resultado ingenuo como el corregido:

Códigos de ejemplo:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class RemoveCrlfBreaks {
    public static void main(String[] args) throws IOException {
        String content = Files.readString(Path.of("notes-crlf.txt"));

        String naive = content.replace("\n", "");
        Files.writeString(Path.of("trap.txt"), naive);
        System.out.println("replace only: " + naive.length() + " characters");

        String fixed = content.replaceAll("\\R", "");
        Files.writeString(Path.of("fixed.txt"), fixed);
        System.out.println("replaceAll \\R: " + fixed.length() + " characters");
    }
}

Salida:

replace only: 39 characters
replaceAll \R: 37 characters

Los dos caracteres extra son los retornos de carro sobrevivientes, visibles como ^M con cat -A. Solo el resultado de replaceAll() está completamente libre de ellos:

$ cat -A trap.txt
First Windows line^MSecond Windows line^M
$ cat -A fixed.txt
First Windows lineSecond Windows line

Procesar un archivo grande línea por línea con BufferedReader

Leer un archivo entero en memoria es el enfoque incorrecto para archivos que pueden crecer hasta cientos de megabytes. Files.newBufferedReader() combinado con el flujo lines() procesa en su lugar una línea a la vez: el flujo entrega cada línea sin su terminador, y Collectors.joining() concatena las líneas en un String. Solo el resultado unido se construye en memoria:

Códigos de ejemplo:

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Collectors;

public class JoinLines {
    public static void main(String[] args) throws IOException {
        try (BufferedReader reader = Files.newBufferedReader(Path.of("notes.txt"))) {
            String joined = reader.lines().collect(Collectors.joining(" "));
            System.out.println(joined);
        }
    }
}

Salida:

Remove line breaks from this file with Java

El delimitador pasado a joining() merece atención. Este ejemplo usa " " para que las palabras en los límites de líneas vecinas permanezcan separadas; compare las palabras pegadas del primer ejemplo. Use joining("") cuando los límites no tengan significado, por ejemplo en registros de datos de ancho fijo, y el resultado deba ser una cadena ininterrumpida.

Leer el archivo con un Scanner en su lugar

Un Scanner puede hacer el mismo trabajo cuando el archivo ya se está analizando token por token. Establecer su delimitador en \R convierte cada línea en un token sin su salto de línea, de modo que al agregar todos los tokens se reconstruye el contenido sin ningún salto. Pase el juego de caracteres explícitamente, como se muestra a continuación, o un Scanner usará silenciosamente el juego de caracteres predeterminado de la plataforma donde Files.readString() usaría UTF-8:

Códigos de ejemplo:

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;

public class ScannerRead {
    public static void main(String[] args) throws IOException {
        StringBuilder builder = new StringBuilder();
        try (Scanner scanner = new Scanner(Path.of("notes.txt"), StandardCharsets.UTF_8)) {
            scanner.useDelimiter("\\R");
            while (scanner.hasNext()) {
                builder.append(scanner.next());
            }
        }
        System.out.println(builder);
    }
}

Salida:

Remove line breaksfrom this filewith Java

Eliminar solo el salto de línea final

A veces los saltos de línea internos son los datos y solo el final es un problema: las comparaciones contra un valor esperado fallan por un único \n invisible al final. Anclar el patrón al final de la cadena lo resuelve. replaceAll("\\R+$", "") elimina uno o más saltos de línea al final del contenido y no toca nada más:

Códigos de ejemplo:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class TrimTrailingBreak {
    public static void main(String[] args) throws IOException {
        Path input = Path.of("notes.txt");
        String content = Files.readString(input);
        String trimmed = content.replaceAll("\\R+$", "");
        Files.writeString(Path.of("trimmed.txt"), trimmed);
        System.out.println("Before: " + content.length() + " characters");
        System.out.println("After: " + trimmed.length() + " characters");
    }
}

Salida:

Before: 44 characters
After: 43 characters

Desapareció exactamente un carácter: el salto de línea final. Los dos saltos internos sobrevivieron: sus líneas siguen terminando con $, la última línea no tiene ninguno:

$ cat -A trimmed.txt
Remove line breaks$
from this file$
with Java

Escribir el resultado de vuelta en el archivo original de forma segura

Si el texto limpio debe reemplazar el original, no lo escriba directamente sobre el archivo que acaba de leer: Files.writeString() trunca primero el destino, por lo que un fallo durante la escritura destruye el contenido original. El patrón seguro es escribir el contenido nuevo en un archivo temporal en el mismo directorio y luego moverlo sobre el original con Files.move(). Un movimiento dentro de un directorio es un cambio de nombre, por lo que los lectores ven el contenido antiguo o el nuevo, nunca una mezcla escrita a medias. Aplicado a una copia del archivo de ejemplo, notes-copy.txt:

Códigos de ejemplo:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

public class SafeWriteBack {
    public static void main(String[] args) throws IOException {
        Path target = Path.of("notes-copy.txt");
        String cleaned = Files.readString(target).replaceAll("\\R", "");
        Path temp = target.resolveSibling(target.getFileName() + ".tmp");
        Files.writeString(temp, cleaned);
        Files.move(temp, target, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("notes-copy.txt now holds: " + Files.readString(target));
    }
}

Salida:

notes-copy.txt now holds: Remove line breaksfrom this filewith Java