We will find a way, we always have.

-interstellar

Programming Language/자바

[자바] 공백 처리 탐험기 (isEmpty(), isBlank(), strip(), trim())

Redddy 2024. 9. 8. 19:36

서론

Tomcat 구현하기 미션 중 요청을 처리하는 과정에서 공백 제거를 하지않아 예외가 발생했다. 

 

 

자바에서는 양 사이드 공백 제거를 위해 trim()과 strip() 두가지 메서드를 제공한다. 이 둘은 어떤 차이를 가지고 있고 어떻게 구현되어있는지 알아보자. 

 

공백 확인

공백 제거로 들어가기 전 공백 확인 메서드를 살펴보자. 

문자가 빈문자열인지 확인할 때 isEmpty() 혹은 isBlank() 메서드를 사용하곤 했다. 

 

isEmpty()와 isBlank() 테스트

 

 

isEmpty()를 사용하면 문자열이 딱 "" 요녀석인지만 확인해주고 isBlank()를 사용하면 "" 이외의 공백, 탭, 줄바꿈과 같은 녀석들도 판별할 수 있다. 

 

 

isBlank() 집중적으로 파고들기

 

 

어떻게 확인하는가?

 

위 메서드들은 어떻게 문자가 비었는지 알까

소스코드를 까보자. 

 

isEmpty()

 

String 클래스가 제공하는 isEmpty() 함수의 구현은 아래와 같다. 

 

String 클래스의 isEmpty()

 

단순히 문자의 길이가 0인지 확인하고 있다. @Override가 붙어 있는데 이는 String 클래스가 상속하고 있는  CharSequence 인터페이스의 메서드를 재정의한 것이다. 

 

isBlank()

isBlank() 메서드는 isEmpty() 메서드 보다는 조금 복잡해보인다. 

 

 

 

이 부분만 봐서는 대애충 문자중 whitespace가 아닌 인덱스랑 문자 길이랑 같은지 확인하는 듯하다. 코드를 좀 더 들어가보기 전에 white space가 무엇인지 주석으로 설명해준 것 스윽 한번 읽고 가자.  

우리가 알고 있던 공백 말고도 꽤 많은 white space(유니코드에서 정의해놓은 공백 문자)가 있었다. 

 

 

 

 

첫번째로 만나는 indexOfNonWhitespace

 

StringLatin1의 indexOfNonWhitespace 메서드

 

StringUTF16의 indexOfNonWhitespace 메서드

 

 

indexOfNonWhitespace를 타고타고 가보면 첫번째 문자부터 끝까지 확인하면서 whitespace인지 아닌지 확인하는 모습을 볼 수 있었다. 

 

 

length() 메서드는 아래처럼 구현되어 있다. 

 

 

 

공백 제거

공백 확인하는 방법은 이쯤에서 멈추고 이제 공백을 제거해보자. 

 

공백을 제거하는 메서드도 strip() trim()이 있다.

 

 

둘 다 공백을 아주 잘 제거해준다. 

 

 

이런 식으로 줄바꿈 혹은 탭도 잘 제거해준다. 

 

 

하지만 이건 어떨까

 

 

언뜻 보기에 공백이지만 trim() 은 실패한다.

 

 

코드를 까보자. 

trim() 메서드

 

위의 주석을 보니 유니코드 20(16진수)번 이하인 녀석들만 제거해준단다. 

 

출처: https://simple.m.wikipedia.org/wiki/ASCII

 

 

trim() 메서드

 

코드를 보니 그렇게 구현되어 있다. 

 

strip() 메서드

 

반면 strip()은 앞서 살펴봤던 white space를 제거해준다. 다시말해 strip() 메서드가 trim() 메서드 보다 더 많은 범위의 공백을 제거해주는 것을 확인할 수 있다. 

 

StringLatin1 과 StringUTF16

 

계속 보이는 StringLatin1과 StringUTF16은 또 어떤 친구들인지 알아보자. 

영어 알파벳은 1바이트로 표현이 가능한반면 우리의 자랑스러운 한국어는 1바이트로 표현이 되지 않고 2바이트가 필요하다. 

 

자바 버전 8에서는 String 클래스 내부에 char[]로 구성되어 있었지만 자바 버전 9에서는 byte[]로 구성하고 coder 라는 새로운 변수가 생겼다. coder가 0(LATIN1)이라면 영문같이 1바이트로 표현가능한 것이고 1(UTF16)이라면 2바이트가 필요하다는 의미이다. 

 

 

 

 

char는 2바이트를 차지하는 반면 byte는 1바이트만 차지한다. 영문을 char를 사용해 표현하게 된다면 의미없는 바이트 0이 계속해서 채워지게 될 것임으로 char를 사용하는 것보다 byte를 사용하는게 훨씬 더 효율적이다. 

그래서 자바 6에서는 String을 char[]를 사용하되, byte[]를 사용할 수 있는 옵션도 제공하였다. 

 

-XX:+UseCompressedStrings

 

위와 같은 VM옵션을 사용하면 문자열이 char[] 대신 byte [] 로 저장되므로 많은 메모리를 절약할 수 있다. 하지만 String 연산자가 char[]에 의존한다는 점과 생성자가 char[]로 되어 있다는 문제점이 있어 JDK 7에서 제거되었다. 

 

그러나 JDK 9에서 CompactStrings라는 이름으로 다시 부활하였다. 

 

이제부턴 문자열을 생성할 때마다 문자열의 모든 문자를 바이트로 표현할 수 있는 경우 내부적으로 바이트 배열을 사용하여 한 문자에 대해 한 바이트가 제공되고 아니라면 두 바이트로 제공한다. 

 

 

 

 

 

위 문자를 JDK 8과 JDK 11에서 어떻게 저장하고 있는지 디버그 해보자.

 

 

JDK 8

 

JDK 8에선 char를 사용하고 있고, 한문자를 char 하나를 사용해서 표현하고 있다. 

 

 

JDK 11

바이트를 사용하고 있고, english는 JDK 8에서 char를 사용했을 때와 동일하지만 korean에서는 한글을 표현하는데 2바이트씩 사용하고 있고 coder의 값은 1인 것을 확인할 수 있다. 

 

재미지다.

 

 

 

 

문자열 다루기로 돌아와 다시 정리해보자면,,,

 

 

  • 확인하는 공백 범위
    • isEmpty(): "" 인지만 확인합니다.
    • isBlank(): 유니코드에서 정의된 모든 공백 문자로만 이루어졌는지 확인합니다. 

 

 

 

  • 처리하는 공백의 범위:
    • trim(): ASCII 코드 32 이하의 공백 문자만 제거합니다.
    • strip(): 유니코드에서 정의된 모든 공백 문자를 제거합니다.

 

  • 도입 시기:
    • trim(): Java 1.0부터 존재.
    • strip(): Java 11에서 도입.
    • isEmpty(): Java 6.0에서 도입.
    • isBlank(): Java 11에 도입

 

  • String 표현
    • Java 8 이전에는 char 사용
    • Java 9 부터 byte를 사용하고 coder를 사용해 1바이트로 표현 가능한지 확인

 

 

결론 strip()이 trim() 상위 호환이니 사용하지 않을 이유가 없다. 

 

 

 

참고자료

 

자바 스펙 254

자바 코드

java-9-compact-string