HOWTO · Java
Java에서 파일에서 줄 바꿈을 제거하는 방법
이 튜토리얼은 replace, line separator, replaceAll과 같은 여러 가지 접근 방식으로 Java 파일에서 줄 바꿈을 제거하는 방법을 보여줍니다.
이 페이지의 내용
이 튜토리얼에서는 Java에서 파일의 줄 바꿈을 제거하는 방법을 보여줍니다. 작거나 중간 크기의 파일이라면 가장 짧은 방법은 Files.readString()로 파일 전체를 String으로 읽고, replaceAll("\\R", "")로 모든 줄 바꿈을 삭제한 뒤, Files.writeString()로 결과를 다시 쓰는 것입니다. 또한 \r\n으로 끝나는 Windows 파일, 줄 단위로 처리하는 큰 파일, Scanner를 사용하는 방법, 마지막 줄 바꿈만 제거하는 방법도 다룹니다. 예제를 실행하려면 Java 11 이상이 필요합니다. 두 Files 메서드 모두 Java 11에서 추가되었습니다.
모든 예제는 이 샘플 파일 notes.txt를 사용합니다:
Remove line breaks
from this file
with Java
예제가 줄 바꿈이 사라졌다고 주장할 때마다 그 주장은 cat -A로 확인합니다. 이 명령은 각 줄 끝에 $를 표시하고 캐리지 리턴을 ^M으로 보여줍니다:
$ cat -A notes.txt
Remove line breaks$
from this file$
with Java$
Files.readString()와 replaceAll()로 모든 줄 바꿈 제거하기
Files.readString()는 파일 전체를 UTF-8로 디코딩하여 하나의 String으로 읽습니다. 정규식 패턴 \R은 모든 유니코드 줄 바꿈과 일치합니다. 줄 바꿈 문자, 캐리지 리턴, 또는 하나의 줄 바꿈으로 취급되는 \r\n 쌍이 그렇습니다. 모든 일치를 제거하면 세 가지 줄 끝 형식을 한 번에 삭제할 수 있습니다. 정리된 텍스트는 새 파일 cleaned.txt에 저장됩니다:
예제 코드:
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);
}
}
출력:
Remove line breaksfrom this filewith Java
breaks와 from이라는 단어가 서로 붙어 있는 것을 확인하세요. 줄 바꿈을 제거해도 그 자리에 아무것도 삽입되지 않습니다. cat -A로 확인하면 $가 하나도 남아 있지 않으므로, cleaned.txt는 마지막 줄 바꿈도 없는 한 줄짜리 파일입니다:
$ cat -A cleaned.txt
Remove line breaksfrom this filewith Java
Windows CRLF 줄 끝 처리하기
Windows에서 만든 텍스트 파일은 각 줄이 \r\n으로 끝나고, Linux와 macOS의 파일은 \n만 사용합니다. 이 차이가 순진한 수정 방법인 replace("\n", "")를 무너뜨립니다. 이 방법은 Windows 줄 끝의 \n 절반만 제거하고 \r 절반을 각 줄에 남겨둡니다. 출력하면 텍스트가 올바르게 보이지만, 숨겨진 캐리지 리턴이 이후 처리를 망가뜨립니다. 다시 읽은 값의 끝에 \r가 남아 있기 때문입니다. 다음 예제는 Windows 스타일 파일 notes-crlf.txt를 사용하고, 순진한 결과와 수정된 결과를 모두 디스크에 씁니다:
예제 코드:
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");
}
}
출력:
replace only: 39 characters
replaceAll \R: 37 characters
추가된 두 문자는 살아남은 캐리지 리턴으로, cat -A에서는 ^M으로 보입니다. 그것들이 완전히 없는 것은 replaceAll()의 결과뿐입니다:
$ cat -A trap.txt
First Windows line^MSecond Windows line^M
$ cat -A fixed.txt
First Windows lineSecond Windows line
BufferedReader로 큰 파일을 줄 단위로 처리하기
수백 메가바이트까지 커질 수 있는 파일에서는 파일 전체를 메모리로 읽는 것이 잘못된 접근입니다. 대신 Files.newBufferedReader()와 lines() 스트림을 결합하면 한 번에 한 줄씩 처리할 수 있습니다. 스트림은 종결자를 제외한 각 줄을 전달하고, Collectors.joining()이 줄들을 하나의 String으로 연결합니다. 메모리에 구축되는 것은 연결된 결과뿐입니다:
예제 코드:
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);
}
}
}
출력:
Remove line breaks from this file with Java
joining()에 전달되는 구분자는 주의할 가치가 있습니다. 이 예제는 " "를 사용하므로 이웃한 줄 경계의 단어들이 분리된 상태를 유지합니다. 첫 번째 예제의 붙어버린 단어와 비교해 보세요. 경계가 아무 의미도 없을 때, 예를 들어 고정 너비 데이터 레코드에서는 결과가 끊기지 않는 문자열이어야 하므로 joining("")을 사용합니다.
대신 Scanner로 파일 읽기
Scanner는 파일이 이미 토큰 단위로 분석되고 있을 때 같은 작업을 할 수 있습니다. 구분자를 \R로 설정하면 각 줄이 줄 바꿈이 없는 하나의 토큰이 되므로, 모든 토큰을 추가하면 줄 바꿈이 전혀 없는 내용을 재구성할 수 있습니다. 아래와 같이 문자 집합을 명시적으로 전달하세요. 그렇지 않으면 Scanner는 Files.readString()이 UTF-8을 사용하는 상황에서 플랫폼 기본 문자 집합을 조용히 사용합니다:
예제 코드:
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);
}
}
출력:
Remove line breaksfrom this filewith Java
마지막 줄 바꿈만 제거하기
때로는 내부 줄 바꿈이 곧 데이터이고 마지막 줄 바꿈만 문제입니다. 끝에 있는 보이지 않는 \n 하나 때문에 기대값과의 비교가 실패합니다. 패턴을 문자열 끝에 고정하면 이 문제가 해결됩니다. replaceAll("\\R+$", "")는 내용의 맨 끝에 있는 하나 이상의 줄 바꿈을 제거하고 그 외의 것은 건드리지 않습니다:
예제 코드:
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");
}
}
출력:
Before: 44 characters
After: 43 characters
사라진 문자는 정확히 하나, 마지막 줄 바꿈입니다. 내부의 두 줄 바꿈은 살아남았습니다. 그 줄들은 여전히 $로 끝나고, 마지막 줄에는 없습니다:
$ cat -A trimmed.txt
Remove line breaks$
from this file$
with Java
결과를 원본 파일에 안전하게 다시 쓰기
정리된 텍스트가 원본을 대체해야 한다면, 방금 읽은 파일 위에 직접 쓰지 마세요. Files.writeString()는 먼저 대상을 잘라내므로, 쓰기 도중 충돌이 발생하면 원본 내용이 파괴됩니다. 안전한 방법은 새 내용을 같은 디렉터리의 임시 파일에 쓰고 Files.move()로 원본 위로 이동하는 것입니다. 한 디렉터리 안에서의 이동은 이름 변경이므로, 읽는 쪽에서는 이전 내용이나 새 내용 중 하나만 볼 수 있고 절반만 쓰인 혼합 상태는 결코 볼 수 없습니다. 샘플 파일의 복사본 notes-copy.txt에 적용한 예제입니다:
예제 코드:
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));
}
}
출력:
notes-copy.txt now holds: Remove line breaksfrom this filewith Java