본문 바로가기
Dev. Cookbook/Java, JSP

[Java] printf() 한글 문자열 포맷 오류 해결방법

by breezyday 2022. 4. 25.

1. printf 오류 확인

Java 프로그래밍을 시작하고 나면 처음 시작하는 것이 'Hello Java'입니다. Java의 문법과 각종 API와 Collection 등을 공부하면서 프로그램을 공부하다 보면 결과를 출력하기 위해서는 System.out.println() 함수와 System.out.printf() 문을 사용하게 됩니다. 간단한 문자열 출력은 System.out.println() 함수를 주로 사용하지만, 사용의 편의를 위해서 printf() 문도 사용하곤 합니다. 

 

일반적인 사용에서는 전혀 문제가 없습니다. 한글도 잘 출력되며 영문과 함께 섞어 사용해도 전혀 문제가 없습니다. 그러나 한글을 포함한 문자열의 길이 조절까지 포함하게 되면 문제가됩니다.  

 

public static void main(String[] args) {
    String[] books = {
        "Java Programming 정복",
        "이것이 오라클이다",
        "Pro JAVA 8 Programming"
    };

    String[] authors = {
        "신용권",
        "우재남",
        "Bret Spell"
    };

    for (int i=0; i<3; i++) {
        System.out.printf("|%-24s|%14s|\n"
                   ,books[i], authors[i]);
    }
}

 

%s구문에서 문자열을 일정한 길이에 맞게 출력하려면 %길이s로 사용합니다. 그리고 왼쪽 정렬은 -를 넣어주면 됩니다.

즉 위의 코드에서 책 제목은 왼쪽 정렬로 24만큼, 저자는 오른쪽 정렬로 14만큼 맞춰서 출력하는 format 문자열은  |%-24s|%14s|이 됩니다.   | 는 식별용으로 추가했습니다. 

 

위 코드의 실행결과는 아래와 같습니다. 

 

|Java Programming 정복     |           신용권|
|이것이 오라클이다               |           우재남|
|Pro JAVA 8 Programming  |    Bret Spell|

2. 원인

그 이유는 printf에서 글자 수를 판단할 때 한글의 경우 2칸을 사용하지만 글자 수는 1글자로 취급하기 때문입니다.

 

출력시 칸은 2칸을 차지하지만 문자열 개수로는 1개로 파악하기 때문에 한글을 섞어서 formatting을 시도하면 출력 시 길이 계산에 오류가 발생합니다. 아래의 코드로 문자열의 길이를 파악해보면 확인이 가능합니다.

 

for (int i=0; i<3; i++) {
    System.out.printf("\"%s\"의 길이는:%d\n"
                       , books[i], books[i].length());
}

 

출력 결과입니다.

 

"Java Programming 정복"의 길이는:19
"이것이 오라클이다"의 길이는:9
"Pro JAVA 8 Programming"의 길이는:22

3. 오류 해결 방법

오류 해결방법은 크게 아름답지는 않습니다.

 

조금 찾아봤지만 printf문을 직접 만들지 않는 한 해결의 실마리는 좀처럼 보이지 않습니다. printf() 함수를 직접 만들어서 한글 출력 문제를 해결할 수도 있겠지만, 그렇게 자주 사용할 일이 많다고 생각하지는 않습니다. 

 

문제를 직시하면 한글이 포함된 문자열의 경우 길이는 한글을 1자로 해석하기 때문에 출력 format에서 길이한글이 차지하는 길이의 절반만큼 감소하여 출력하도록 하는 방법을 사용하면 비교적 적은 노력으로 해결할 수 있습니다.  

"원래 문자열을 출력하고자 하는 길이" 
- ( "Byte로 계산한 문자열의 길이" - "한글로 계산한 문자열의 길이" ) 

와 같이 계산하면 한글을 포함한 문자열의 길이가 printf() 함수에서 지정한 기본 길이에 맞게 출력이 됩니다. 

말로 설명하는 것 보다는 코드를 보는 것이 빠르겠네요.

 

3.1 문자열의 Byte 길이 확인

문자열의 Byte 길이를 확인하려면 String 메소드인 getBytes() 함수를 사용하면 됩니다. 

getBytes()함수에서 인코딩을 지정하고 그 길이를 확인하면 문자열의 Byte길이를 확인할 수 있습니다. 

 

try {
    length = str.getBytes("euc-kr").length;
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}

 

간단하게 접근하면 굳이 함수화하지 않아도 되지만 Exception 핸들링 문제로 함수화 하는 것이 조금 더 보기 편리합니다. 문자열 byte 길이 계산 시 인코딩은 "euc-kr" 해주어야 합니다. "utf-8"로 지정하면 3바이트로 계산되이 됩니다.

3.2 문자열 길이 계산 

// format 문자열 길이와 문자열을 받아
// 실제 출력에 맞게 문자열의 길이를 다시 계산하여 반환

public static int getPrintfStrLength(int formatSize, String str) {
    return formatSize - (getByteLength(str) - str.length());
}

// 문자열의 Byte 길이를 계산하여 반환
public static int getByteLength(String str) {
    int length=0;

    if (str!=null) {
        try {
            length = str.getBytes("euc-kr").length;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    return length;
}

 

출력에 사용할 실제 문자열 길이 계산은 조금 더 코드가 추가됩니다. 

 

앞서 설명했듯이 원하는 format의 문자열 길이에서 실제 문자열에서 한글의 Byte길이의 절반만큼 빼주면 됩니다.

출력하려는 문자열에서 Byte길이를 구한 다음 출력하려는 문자열의 길이를 빼면, 한글의 Byte길이의 절반이 됩니다. 즉, 출력하려는 길이에서 한글이 사용되는 만큼 짧게 줄여서 계산하여 format 하면 됩니다. 

 

문제를 해결한 전체 코드는 아래와 같습니다. 

 

import java.io.UnsupportedEncodingException;

public class StringTestEx {
	public static int getPrintfStrLength(int formatSize, String str) {
		return formatSize - (getByteLength(str) - str.length());
	}
	
	public static int getByteLength(String str) {
		int length=0;
		
		if (str!=null) {
			try {
				length = str.getBytes("euc-kr").length;
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
		
		return length;
	}

	public static void main(String[] args) {
		String[] books = {
	        "Java Programming 정복",
	        "이것이 오라클이다",
	        "Pro JAVA 8 Programming"
		};
	    
		String[] authors = {
	    	"신용권",
	        "우재남",
	        "Bret Spell"
		};
		
		for (int i=0; i<3; i++) {
			int len1 = getPrintfStrLength(24, books[i]);
			int len2 = getPrintfStrLength(14, authors[i]);
			
			String formatStr = "|%-"+len1+"s|%"+len2+"s|\n";			
			System.out.printf(formatStr, books[i], authors[i]);
		}
		System.out.println();
	}
}

 

위 코드의 출력결과는 아래와 같습니다. 

 

|Java Programming 정복   |        신용권|
|이것이 오라클이다       |        우재남|
|Pro JAVA 8 Programming  |    Bret Spell|

4. 설명을 마치며...

혼자서 찾아본 해결방법입니다만, 개인적으로는 썩 마음에 들지는 않습니다. 더 좋은 해결방법이 있다면 댓글 부탁드립니다. 

댓글