toggle menu

[JavaScript] 문자열의 바이트(Byte) 길이를 구하는 방법

2013.07.17 14:14 JavaScript
인터넷 상에 문자열의 Byte 길이를 체크하는 루틴이 다양하게 공개되어 있는데,
대부분은 for문을 돌려서 2바이트 문자들을 구분하고 값을 더하는 형태로 구현되어 있다.

더 좋은 방법은 없을까하던 차에 적절하고 깔끔한데다가 빠르기까지한 루틴을 발견해서 정리해본다.
기존 루틴과의 비교를 위해서 각 방법의 속도를 측정해 보았다.



알려진 루틴 속도 비교

사용된 소스는 아래와 같으며, 크롬브라우저의 콘솔창에서 테스트했다.

// 변수선언
var string = undefined; //테스트할 문자열
  
// 문자열 초기화
for(var j=0; j<10000; j++) {
    string += "This is 아무의미없는 문자열";
}
var stringLength = string.length;
var stringByteLength = 0;
  
  
// 일반적인 FOR문으로 문자열 BYTE 계산
console.time("일반적인FOR방식"); 
for(var i=0; i<stringLength; i++) {
    if(escape(string.charAt(i)).length >= 4)
        stringByteLength += 3;
    else if(escape(string.charAt(i)) == "%A7")
        stringByteLength += 3;
    else
        if(escape(string.charAt(i)) != "%0D")
            stringByteLength++;
}
console.log(stringByteLength + " Bytes")
console.timeEnd("일반적인FOR방식");
  
  
// 개선된 FOR문으로 문자열 BYTE 계산
console.time("개선된FOR방식");
stringByteLength = (function(s,b,i,c){
    for(b=i=0;c=s.charCodeAt(i++);b+=c>>11?3:c>>7?2:1);
    return b
})(string);
console.log(stringByteLength + " Bytes");
console.timeEnd("개선된FOR방식");
  
  
// encodeURI로 문자열 BYTE 계산
console.time("encodeURI방식");
stringByteLength = ~-encodeURI(string).split(/%..|./).length;
console.log(stringByteLength + " Bytes");
console.timeEnd("encodeURI방식");
  
  
// 정규식을 활용한 계산
console.time("정규식방식");
stringByteLength = string.replace(/[\0-\x7f]|([0-\u07ff]|(.))/g,"$&$1$2").length;
console.log(stringByteLength + " Bytes");
console.timeEnd("정규식방식");


UTF-8 인코딩 방식에서 한글은 3바이트로 계산되므로 실행 결과 모두 정확하게 360009 바이트를 구해주었지만 속도에서는 꽤 차이를 보인다.



여러차례 동일한 테스트를 반복해서 수행했을 때 결과는 일반적으로 인터넷 상에 돌아다니는 FOR문을 이용한 방식은 무려 평균 800ms 이상이 걸리는 반면, 개선된 FOR문을 활용한 방식은 불과 2ms 이내에 Byte 길이를 계산해주었다.




개선된 FOR문 로직 분석
function getByteLength(s,b,i,c){
    for(b=i=0;c=s.charCodeAt(i++);b+=c>>11?3:c>>7?2:1);
    return b;
}

변수명이 단순화되어 있어서 코드를 해석하기가 다소 까다롭지만, 비트연산에 대한 어느정도의 이해가 있다면, 생각보다 단순한 로직임을 알 수 있다.

먼저, FOR문의 초기화식에선 BYTE값을 넣어줄 b와 스트링을 탐색할 때 사용할 임시변수 i를 0으로 초기화시킨다.

이후, c에 현재 위치의 유니코드값을 charCodeAt() 함수를 사용해서 가져온다. 만약 스트링 전체를 탐색했더면 이 부분에서 실패하고 FOR문이 종료될 것이다. c에 현재 위치의 유니코드값을 가져왔다면, 이어서 바이트 계산을 비트연산으로 진행한다.

2048(2^11)로 나누었을때 몫이 있으면 2048보다 큰 유니코드이므로 3바이트, 그보다 작은데 128(2^7)로 나누었을 때 몫이 있으면 128보다 큰 유니코드이므로 2바이트, 나머지 경우엔 1바이트를 할당하는 방식으로 비트 연산을 활용하고 있다.

마지막으로, 계산이 완료되면 바이트값인 b를 리턴해주는 것으로 마무리된다.
기존 FOR문과 동일한 기능을 수행하지만 비트연산을 활용함으로써 상당히 빠르게 계산을 수행해준다.



결론

기존 FOR문이 무려 800ms 이상 걸리는 반면, encodeURI를 사용한 방식은 150ms 내외에서 처리되었고, 정규식을 활용한 방식은 30ms 이내에서 처리되었다. 개선된 FOR문은 2ms 내외로 매우 빠르게 처리되었다. 브라우저의 자바스크립트 엔진마다 속도의 차이는 있겠지만, 같은 FOR문이라도 어떻게 짜느냐에 따라 속도차이가 상당히 다르게 나타나는 것을 확인할 수 있다.

여러차례 테스트를 진행한 결과, 개선된 FOR문을 사용하는 경우에는, 문자열의 길이가 길어져도 계산 속도에는 별 영향을 미치지 않았다. 따라서 위의 개선된 FOR문 방식을 사용할 것을 권장한다.



참조
https://gist.github.com/mathiasbynens/1010324


JavaScript 관련 포스팅 더보기