리소스 번들 로딩

Java SE 2005/01/19 10:49 Posted by Sun

리소스 번들을 이용해 텍스트 스트링을 각국의 언어별(특히, 영어에 있어서는 각 지역별 영어)로 임베드할 수 있다. 이전의 Tech Tip에서는 리소스 번들의 사용에 대해서 논의했었다. 이를 잠시 되새겨보자. 당신의 프로그램에 "Hello, World"와 같은 스트링이 필요하다면 이를 프로그램에 코딩하는 것이 하나의 방법이 되겠다. 그러나 리소스 번들을 이용하면 스트링을 하드 코딩할 필요가 없다. 대신에 룩업 테이블에 스트링을 넣고 런타임시 프로그램이 스트링을 찾아보도록 할 수 있다. 프로그램이 다른 지역값(locale)으로 구동한다면 찾아보기(lookup)기능은, 번역되어있을 경우에는 다른 스트링을, 번역되어있지 않을 경우에는 원래의 스트링을 찾을 것이다. 지역값에 상관없이 동일한 코드로 구동되므로 프로그램의 코드에는 영향을 미치지 않는다. 사용자가 해야할 것은 값들의 룩업 테이블을 생성하고 번역하는 것 뿐이다.

앞에서 언급했듯이 리소스 번들은 지역값을 대상으로 한다. 영어가 지역값일 때 "영어로 'greet' 스트링이 필요함."라고 할 수 있으며, 미국 영어의 'color'와 영국 영어의 'colour'의 차이를 둘 수도 있다. 지역값은 국가별 언어뿐 아니라 각 언어 안에서도 지역구분을 지원하기 때문이다. 즉 미국식 영어 중에서도 Southern California나 New York City등의 각 지역에 따라 다른 구문을 지정할 수 있다.

ListResourceBundle를 확장하는 .class 파일에 리소스 번들을 정의하거나 .properties 파일로 백업되는 PopertyResourceBundle를 사용할 수도 있다. 리소스 번들과 지역값을 결합시킬 때는 두가지 검색이 수반된다. 첫번째 검색으로는 가장 가까운 요청 리소스 번들을 찾고, 두번째 검색으로는 요청된 key에 대한 스트링을 찾는다. 왜 이러한 차이를 둘까? 리서치 번들을 검색할 때 시스템은 리소스 번들을 찾는 즉시 검색을 중단하고 요청된 리소스 번들을 로딩한다. 시스템이 요청된 번들에서 key를 찾지 못하면 다른 리소스 번들에서 찾게 된다. 결국 찾지 못하면 시스템은 MissingResourceException를 던진다.

이에 대한 시연으로 Greeting 이라는 이름의 번들 안에서 New York 지역값에 대한 스트링을 찾는다고 가정하고 지역값이 다음과 같이 생성되었다고 하자.

   Locale newYork = new Locale("en", "US", "NewYork")

그리고 다음과 같이 리소스 번들을 요청하면,

   ResourceBundle bundle = 
     ResourceBundle.getBundle("Greeting", newYork);

시스템은 우선 번들에 대한 .class 파일을 찾는다. 그 파일은 New York 과 같이 변하는 지역값은 레벨로서 그 파일은 Greeting_en_US_NewYork.class이 될 것이다. 시스템이 클래스 경로에서 .class파일을 찾지 못하면 Greeting_en_US_NewYork.properties파일을 검색하게된다. 이어서 Greeting_en_US.class, Greeting_en_US.properties, Greeting_en.class, Greeting_en.properties, Greeting.class, Greeting.properties를 검색하게 될 것이다. 리소스 번들을 찾게 되면 시스템은 검색을 멈춘다. 다행히 캐쉬가 포함되어 있어 시스템이 언제나 모든 곳을 검색하는 것은 아니다. 그렇지만 검색해야하는 곳이 잠재적으로 매우 많은 것이 사실이다.

그 후 시스템은 두번째 찾아보기를 실행한다. 이번에는 요청된 키를 찾는 단계이다. 요청한 키가 시스템이 찾은 번들 안에 없다면 시스템은 언어, 지역, 등의 현재 번들의 레벨을 넘어서서 또다른 리소스 번들을 찾게 된다. 이에 따라 .class 파일이든 .properties 파일이든지 상관없이 좀 더 많은 번들을 로딩하게 된다.

사용자들이 아마도 궁금할 것은 .class 파일을 사용하는 것과 .properties 파일을 사용하는 것 중 어느 것이 더 나은것인가에 대한 것이다. .class 파일이 먼저 검색되고 그 다음이 .properties 파일이다. 또한 .class은 클래스 로더에서 직접 로딩되는 반면 .properties 파일은 번들이 로딩될 필요하 있을 때마다 매번 파싱되어야한다. 파싱은 두번의 과정을 거친다. \uXXX과 같은 유니코드를 다루려면 시스템은 반드시 각각의 key=value 라인을 두번씩 스캔한 후, 값으로부터 키를 분리해야한다.

자 이제 이 두개의 로딩 시간을 비교해보자. 다음의 테스트 프로그램으로 먼저 시작해보면,

   import java.util.*;

   public class Test1 {
     public static void main(String args[]) {
       Locale locale = Locale.ENGLISH;
       long start = System.nanoTime();
       ResourceBundle myResources =
         ResourceBundle.getBundle("MyResources", locale);
       long end1 = System.nanoTime();
       String string = myResources.getString("HelpKey");
       long end2 = System.nanoTime();
       System.out.println("Load: " + (end1 - start));
       System.out.println("Fetch: " + (end2 - end1));
       System.out.println("HelpKey: " + string);
     }
   }

사용자가 1.4 Java 플랫폼을 구동한다면 테스트 프로그램을 변경하여 nanoTime 대신 currentTimeMillis을 호출하도록 한다. nanoTime 메소드는 나노세컨드 단위(10억분의 1초)로 실행되며, currentTimeMillis는 밀리세컨드 단위(천분의 1초)로 실행된다. 또한 이번 팁의 마지막 부분에 나오는 microbenchmarks를 참고하자.

다음으로 테스트 프로그램과 같은 디렉토리 안에 ListResourceBundle 클래스를 생성하자.

   import java.util.*;
   
   public class MyResources extends ListResourceBundle {
     public Object[][] getContents() {
       return contents;
     }
     private static final Object[][] contents = {
       {"OkKey", "OK"},
       {"CancelKey", "Cancel"},
       {"HelpKey", "Help"},
       {"YesKey", "Yes"},
       {"NoKey", "No"},
     };
    }

테스트 프로그램과 MyResources 클래스를 컴파일하고 테스트 프로그램을 구동시키자.

결과는 작업 환경, 즉 RAM의 크기와 프로세서의 속도에 따라 달라질 것이다. 다음은 768 MB RAM으로 Windows XP를 구동하는 800 MHz 장치에서의 결과이다.

   Load: 25937415
   Fetch: 62994

이제 properties 파일인 MyResources.properties를 다음의 요소와 함께 생성하자.

   OkKey=OK
   CancelKey=Cancel
   HelpKey=Help
   YesKey=Yes
   NoKey=No

테스트 프로그램을 다시 구동하고, 이번에는 먼저 MyResources 클래스를 제거한다. .properties 파일을 이용하여 프로그램을 구동할 것이다. 다음은 좀전과 같은 사양의 장치에서 나온 결과이다.

   Load: 101469357
   Fetch: 35450  

로딩시간에 있어서는 ListResourceBundlePropertyResourceBundle보다 빠르다는 것을 알 수 있다. 그러나 놀랍게도 패칭(fetch) 시간으로는 PropertyResourceBundleListResourceBundle보다 두 배 가까이 빠르다. 대략, 로딩에 있어서는 다섯배의 차이가 나고 패칭에 있어서는 두배의 차이가 나므로 이를 따라잡기 위해서는 많은 패칭 작업이 필요하다. 나노세컨드는 10억분의 1의 시간이라는 것과 밀리세컨드는 천분의 1초라는 사실을 잊지 말기 바란다.

테스트 프로그램을 다시 구동하고, 이번에는 .class.properties파일에 있는 100개의 요소들을 사용하자. 이전 파일의 다섯 요소를 20번 복사하고 복사할 때마다 처음부분을 약간 변경함으로써 간단히 파일을 생성할 수 있다. 예를 들어 OkKeyOkKey1으로, CancelKeyCancelKey1로 변경하는 식이다. 이에 대한 결과는 이전의 결과와 같이 로딩에 있어서는 ListResourceBundle이 빠르고 패칭에 있어서는 PropertyResourceBundle이 빠를 것이다. 사실 PropertyResourceBundle의 100개 요소에 대한 로딩시간은 5개를 패칭하는 시간과 비슷하다는 걸 알 수 있을 것이다.

ListResourceBundle
   Load: 12782686
   Fetch: 262788

PropertyResourceBundle
   Load: 12600795
   Fetch: 35175   

지역값을 언어(Locale.ENGLISH)에서 언어/지역(Locale.US)으로 바꾸면 좀 더 재미있는 결과가 나타난다.

Loading 5 / Locale.US:

ListResourceBundle
   Load: 13152117
   Fetch: 32921
   
PropertyResourceBundle
   Load: 14592024
   Fetch: 261060   
   
Loading 100 / Locale.US:

ListResourceBundle:
   Load: 12837863
   Fetch: 264264
   
PropertyResourceBundle   
   Load: 14468366
   Fetch: 33166

모든 클래스에서 ListResourceBundle이 초기 번들을 로딩하는 시간은 언제나 빠른 반면에, 패칭 시간은 가끔 느리기도 한다. 그렇다면 어떤 것이 좋을까? 작은 리소스 번들의 경우 ListResourceBundle이 둘 중에 빠르고, 큰 경우 ListResourceBundle를 사용하지 않는 것이 좋아 보인다. ListResourceBundle은 2차원으로 배열된 것을 룩업맵으로 변경시켜야하므로 이것이 더 느린 이유이다.

이 결과들을 보면 ListResourceBundle를 사용하지 않겠다고 마음먹을 수도 있다. 예를 들어 서버 기반 프로그램에서 .class 파일보다 .properties 파일을 유지하는 것이 더 쉽고, 로딩 시간은 무시해도 괜찮다고 느낄 수 있다. 그러나 ListResourceBundle은 단지 스트링의 2차원적 배열만이 아니다. getContents 메소드는 Object 배열을 리턴한다.

   public Object[][] getContents()

이것은 무엇을 의미하나? 간단한 스트링을 넘어선 컨텐츠를 로컬화하고 싶다면 반드시 ListResourceBundle 오브젝트를 사용해야한다. 이는 이미지, 색깔, 차원 등을 로컬화해준다. PropertyResourceBundle에서는 스트링 이외의 오브젝트를 가질 수 없다.

샘플 프로그램에서의 시간 테스트는 microbenchmark 으로 여겨질 수 있음을 주의하기 바란다. 이는 당연히 발전될 것이지만 리소스 번들 로딩의 캐싱으로는 한번의 구동에서 여러 번의 루핑을 실행할 때 정확한 로딩 시간을 측정하기 힘들다. Microbenchmark를 작성하는 기술에 대해서는 JavaOne 2002 발표자료인 How NOT To Write A Microbenchmark를 참조하기 바란다. 덧붙여, 이 분야의 많은 성능 작업이 JDK 5.0에서 완료되었으며 Java 2 SDK, Standard Edition, v 1.4.x 사용할 때와는 숫자들이 대체로 다를 것이다.

리소스 번들로 작업하는 것에 대한 좀 더 많은 정보는 Java Tutorial의 javadoc for the ResourceBundle class, internationalization trailCore Java Internationalization페이지를 참고하기 바란다.

"Java SE" 카테고리의 다른 글

2005/01/19 10:49 2005/01/19 10:49

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/206

댓글을 달아 주세요

  1. 박정숙  수정/삭제  댓글쓰기

    좋은 정보 감사해요~

    2007/09/19 05:35
[로그인][오픈아이디란?]

◀ Prev 1  ... 557 558 559 560 561 562 563 564 565  ... 641  Next ▶