본문 바로가기
빌드노트 검색

빌드노트

Java split 뒤 마지막 빈 열이 사라지는 이유

Java split이 끝의 빈 필드를 버리는 이유와 split(",", -1)로 보존한 뒤 열 수를 검사하는 방법을 설명한다. 빈 배열·빈 문자열 원소의 차이, 양수 limit의 함정과 정규식 구분자·CSV 문법의 경계를 실제 결과로 확인한다.

Java에서 A,B,를 쉼표로 나눴는데 열이 두 개만 나온다면 split 호출을 확인한다. 기본 호출은 끝의 빈 문자열을 버린다. 끝의 빈 필드도 한 열로 세는 입력이면 row.split(",", -1)로 나눈 뒤 열 수를 검사한다.

JDK 17의 bin 경로가 PATH에 잡혀 있으면 터미널에서 jshell -q를 실행하고 다음 코드를 붙여 넣을 수 있다. row를 자신의 입력으로 바꾸고, 예상 열 수인 3도 입력 형식에 맞춘다.

String row = "A,B,";
String[] fields = row.split(",", -1);
System.out.println("length=" + fields.length);
if (fields.length != 3) throw new IllegalArgumentException("expected 3 columns");
System.out.println("last-empty=" + fields[fields.length - 1].isEmpty());

Windows 11, OpenJDK 17.0.2에서 이 합성 입력을 실행한 출력은 다음과 같았다. JShell의 프롬프트는 생략했다.

length=3
last-empty=true

세 번째 열은 사라지지 않고 빈 문자열로 남았다. 열 수가 맞는지와 필수 값이 비었는지는 별도 검사다. 마지막 값이 필수라면 열 수를 확인한 다음 그 값도 검사해야 한다.

빈 배열과 빈 문자열 한 개는 다르다

Java SE 17의 String.split 문서 (새 탭에서 열림)에서 split(regex)는 limit가 0인 호출과 같다. 0은 끝에 연속된 빈 문자열을 버리고, 음수는 보존한다. 같은 환경에서 기본·0·-1을 비교한 핵심 결과는 다음과 같다. ""는 빈 문자열 값이다.

  • 입력 "A,B,": 기본·0은 길이 2, [0]="A" [1]="B". -1은 길이 3, [0]="A" [1]="B" [2]=""였다.
  • 입력 ",,": 기본·0은 길이 0이라 인덱스가 없다. -1은 길이 3, [0]="" [1]="" [2]=""였다.
  • 빈 입력 "": 세 방식 모두 길이 1, [0]=""였다. 쉼표가 한 번도 일치하지 않아 원래 문자열 하나가 남는다.

빈 입력의 결과에는 빈 문자열 원소가 하나 있다. ,,를 기본 호출로 나눈 빈 배열에는 [0] 자체가 없다. 빈 행을 건너뛸지, 빈 필드 하나로 받을지는 입력 형식에서 따로 정한다.

중간·앞의 빈 필드는 이 쉼표 실험에서 세 방식 모두 남았다. A,,C는 길이 3, [0]="A" [1]="" [2]="C"였고, ,B,C는 길이 3, [0]="" [1]="B" [2]="C"였다. 반면 A,,는 기본·0에서 길이 1인 [0]="A"만 남고, -1에서 길이 3인 [0]="A" [1]="" [2]=""가 됐다. 버리는 대상은 끝에 이어진 빈 문자열이다.

예상 열 수를 limit에 넣으면 초과 열을 놓친다

"A,B,C,D".split(",", 3)도 길이가 3이었다. 하지만 값은 [0]="A" [1]="B" [2]="C,D"였다. 양수 limit는 분할 횟수를 제한하고 마지막 원소에 남은 입력을 모은다. 따라서 세 열인지 검사하려고 3을 넣으면 네 열짜리 입력이 통과할 수 있다. 같은 입력을 -1로 나누면 길이 4, [0]="A" [1]="B" [2]="C" [3]="D"가 됐다.

반대로 A처럼 구분자가 없는 입력은 -1이어도 길이 1이다. 음수 limit는 없는 열을 새로 채우지 않는다. 위 코드에서 길이가 예상과 다르면 분할 직전의 실제 문자열과 구분자, 앞서 적용한 전처리를 확인한다. 이 실험에서는 공백을 제거하지 않았다. A,B, 처럼 끝에 공백이 있으면 마지막 값은 빈 문자열이 아니다.

점 구분자와 CSV 따옴표는 별도로 처리한다

split의 첫 인자는 정규식이다. "A.B.".split(".")는 길이 0, split(".", -1)은 길이 5의 빈 문자열 배열이었다. 정규식 .이 이 입력의 A와 B까지 구분자로 소비했기 때문이다. 문자 그대로의 점을 구분자로 쓰려면 다음처럼 전달한다. 같은 JShell에서 이어 실행할 수 있다.

import java.util.regex.Pattern;
String[] dotFields = "A.B.".split(Pattern.quote("."), -1);
System.out.println("length=" + dotFields.length);

출력은 length=3이었고 값은 [0]="A" [1]="B" [2]=""였다. Pattern.quote 문서 (새 탭에서 열림)의 이 메서드는 구분자의 정규식 특수 의미를 없앤다. 끝의 빈 필드까지 남기는 역할은 여전히 -1이 맡는다. 확인을 마치면 /exit로 종료한다.

다만 쉼표를 문자 그대로 나눈다고 CSV 문법까지 처리되지는 않는다. 합성 입력 "A,B",C,를 쉼표와 -1로 나누자 길이 4가 됐다. [0]은 "A, [1]은 B", [2]는 C, [3]은 빈 문자열이었다. 따옴표 안의 쉼표도 잘린 것이다. RFC 4180 제2절 (새 탭에서 열림)이 설명하는 인용 필드의 쉼표·줄바꿈·따옴표 이스케이프를 허용한다면, 해당 문법을 처리하는 CSV 파서가 필요하다.

이번 관찰은 OpenJDK 17.0.2에서 합성 문자열을 분할한 결과다. 실제 CSV 파일과 CSV 파서는 실행하지 않았으며 다른 JDK 버전도 확인하지 않았다.

← 전체 글 보기