다시 오랜만에 쓰는 프로그래밍 관련 팁.
별로 쓰일 일이 없고, 생각안하고 써도 크게 문제가 되지 않을 수 있는 부분이다.

최근의 IT 계열 뉴스에서 자주 올라오던 기사인데,
1초가 더 생겼으니 1초를 소중히 생각하자면서 나오던 뭐 그런뉴스와 관련되는것이다.
( http://www.dt.co.kr/contents.html?article_no=2008122202011757731006 )

응? 근데 1초가 더 생긴다고? 어떻게 시간이 더 생길 수 있지?
그것이 바로 윤초의 문제이다.

2005년에 후지쯔 코리아 사이트에 올라온 공지를 보면 잘 설명되어 있다.
( http://www.fujitsu.com/kr/news/pr/notice20051230.html )

귀찮으신 분들을 위해서 간단하게 정리하자면,

정의.
태양의 위치로 재는 시간과, 세슘원자로 재는 시간과의 차이를 보정하기 위해서 1초를 더하거나 빼는 것( 태양의 위치는 지구의 자전에 따라서 결정되며, 세슘원자의 진동수는 고정적이다. - 물론 아닐 수도 있다. )

적용법 ( 1초를 더하는 "양의 윤초"인 경우 )
1. 그리니치천문대(영국)를 기준으로 23시 59분 59초 다음이, 24시 59분 60초, 그 다음이 0시 0분 0초가 된다.
2. 그러므로 한국에서는 1월 1일 8시 59분 59초 다음에 60초로 1초가 추가 된다. ( GMT +9 이니깐 )

요것만 알아도 상식을 늘리는것으로 도움이 되겠지만,  프로그래밍에 관련된 정보를 알아 보고 가도록 하자. 후지쯔코리아에서 제공한 링크의 내용중에서  신경쓰일만한 부분만 긁어서 보도록 하자.


윤초 대응 유무에 따른 시스템 시각 변화 패턴

  • 윤초 보정한 시스템
    - Solaris, Linux : NTP STEP 모드 사용 시
                 표준시각 : 8:59:59 → 8:59:60 → 9:00:00
                 시스템시각 : 8:59:59 →8:59:59 → 9:00:00 ( 같은 시각이 2회 반복)
    - UXP/DS(NTP STEP 모드 사용 시), Windows(Windows Time Service)
                 표준시각 : 8:59:59 → 8:59:60 → 9:00:00 → 9:00:01 → 9:00:02
                 시스템시각 : 8:59:59 → 9:00:00 → 9:00:01 → 9:00:02 → 9:00:02
    ※ 보정타이밍은 시각동기시(위의 예는 9:00:02 에 동기한 예)
    - Linux : RHEL3 UR6/RHEL4 UR2제공의 zoneinfo를 사용 시
                 표준시각 : 8:59:59 → 8:59:60 → 9:00:00
                 시스템시각 : 8:59:59 → 8:59:60 → 9:00:00 (8:59:60초에 주의)
    ※상기 zoneinfo(/usr/share/zoneinfo/right/Asia/Seoul) 를 사용하지 말것을 추천
  • 윤초보정을 하지 않는 시스템
    - Solaris, Windows, Linux, UXP/DS
                  표준시각 : 8:59:59 → 8:59:60 → 9:00:00
                  시스템시각 : 8:59:59 → 9:00:00 → 9:00:01 ( 표준시각보다 1초 빨리감)


OS에서 지원하느냐 마느냐, 혹은 설정에 따라서 어떻게 되느냐의 문제도 있겠지만, 프로그램 언어에서 지원해주지 않으면 말짱 황인거 아니겠는가?


그럼 우선 Java API문서에서 시간에 관련된 클래스의 API문서 중 일부를 보도록 하자.
Date클래스에 보면 시간을 얻어오는 getSeconds()라는 메소드가 있다.  비록 Deprecated 되었지만 말이다. ( http://java.sun.com/javase/6/docs/api/java/util/Date.html#getSeconds() )

java.util
Class Date

getSeconds

@Deprecated
public int getSeconds()
Deprecated. As of JDK version 1.1, replaced by Calendar.get(Calendar.SECOND).
Returns the number of seconds past the minute represented by this date. The value returned is between 0 and 61. The values 60 and 61 can only occur on those Java Virtual Machines that take leap seconds into account.
Returns:
the number of seconds past the minute represented by this date.
See Also:
Calendar

일반적으로 생각하지 못했던 부분이 바로 API문서에 적혀 있다. 우리는 초의 반환값으로 0부터 59까지의 숫자중에 하나가 반환된것이라 생각하지만, 실제로는 60이라는 숫자나, 혹은 61이라는 숫자가 등장할 수도 있다. API에서 그렇게 적어 두었기 때문에, 언제든지 발생할 가능성은 존재한다. ( 하지만 과연 프로그래밍할때 이런것을 생각하는 사람이 몇이나 될까? )

재미삼아 다른언어에서는 윤초를 어떻게 다루고 있는지도 확인해 보자.

php ( http://kr2.php.net/manual/en/function.date.php )

s Seconds, with leading zeros 00 through 59


perl ( http://search.cpan.org/~drolsky/DateTime-0.4501/lib/DateTime.pm )
Dave Rolsky > DateTime-0.4501 > DateTime

  • $dt->second()
  • $dt->sec()

    Returns the second, from 0..61. The values 60 and 61 are used for leap seconds.


  • python ( http://docs.python.org/library/time.html )

    5

    tm_sec

    range [0,61]; see (1) in strftime() description


    C ( http://www.cplusplus.com/reference/clibrary/ctime/strftime.html )
    size_t strftime ( .... );
    %S Second (00-61) 02

    .Net ( http://msdn.microsoft.com/en-us/library/system.globalization.calendar.getsecond.aspx )
    .NET Framework Class Library
    Calendar..::.GetSecond Method

    Return Value

    Type: System..::.Int32
    An integer from 0 to 59 that represents the seconds in time.



    php와 .Net만 지원을 하지 않고 있는것인가?
    ( 사실은 내가 잘 검색안해 봐서 그럴 수도 있다. - 엉뚱한 클래스를 뒤졌을 수도 있고. )

    뭐 결론은, 크게 없다. 그냥 알아는 두자!!

    카페에 적었던글을 다시 옮겨 둡니다.
    ----
    안녕하세요.
     찬 입니다.

    오늘도 기초시리즈.
    String의 intern()에 대해서 이야기 해 보도록 하죠.

    intern() 에 대해서 알기 위해서는, 우선 String 자체에 대해서 좀 알아 봐야 합니다.

    String str1 = "Hello";
    String str2 = "Hello";
    String str3 = "Hello";


    이렇게 해 두면 str1과 str2와 str3는 모두 하나의 객체를 가리키고 있습니다.
    왜 그런지 알아 봅시다.

    .java파일을 컴파일 하게 되면, .class파일이 만들어 지게 됩니다.
    .class 파일 안에는 현재 클래스의 정보가 들어있게 되겠지요.

    complie할때에 이미 저 문자를 사용해야 한다는것을 알수 있기 때문에 .class파일안에다가 바로 문자열을 저장해 두는것입니다.
    이때 "Hello"라는 문자열은 .class파일의 "String pool"에 들어가게 됩니다.
    ( 이 class파일이 실제로 로딩이 되면, "Hello"가 메모리의 "String pool"에 로딩됩니다. )

    그런데, 총 3개의 "Hello"라는 문자열이 있는데, .class파일에 3개씩이나 넣어야 할까요?
    모두 같은 문자열이니 이때는 "String pool"에 1개의 "Hello"만 들어가게 됩니다. 이게 가장 효율적이니깐요..
    ( 왜 그렇냐고 물으신다면, 원래 그렇게 만들어서 그렇다고 말할 수 밖에 없습니다. ^^ )

    위의 소스를 컴파일을 해 만들어진 .class파일을 editor를 이용해서 열어 보면 Hello가 하나만 있는것을 알 수 있죠.

    사용자 삽입 이미지


    그렇다면 아래의 코드를 한번 보도록 합시다.

      String str1 = new String("Hello");
      String str2 = new String("Hello");
      String str3 = new String("Hello");

      System.err.println(System.identityHashCode(str1));
      System.err.println(System.identityHashCode(str2));
      System.err.println(System.identityHashCode(str3));

    결과
    3526198
    7699183
    14285251

    위의 소스는 str1, str2, str3는 모두 new String(String)을 이용해서 만들어 보았습니다.
    그렇게 했더니 str1, str2, str3의 hashcode가 서로 다르게 나왔습니다. 이는 서로 다른 객체임을 뜻합니다.

    "Hello"를 가지고 String을 new 했으나,
    실제로 만들어진 객체는 "Hello"라고 인자로 준객체가 아니라, Hello를 가지는 새로운 객체를 만들어 준것입니다.


    위의 소스를 컴파일해서 실행하면, 총 4개의 "Hello"가 메모리에 로딩되게 됩니다.

    1. 위의 java화일에서 "Hello"라고 되어 있는 놈 ( String pool영역에 있음 )
    2. "Hello"를 이용해서 새로운 String을 만들어준 str1이 가리키고 있는 놈 ( Heap에 있음 )
    3. "Hello"를 이용해서 새로운 String을 만들어준 str2이 가리키고 있는 놈 ( Heap에 있음 )
    4. "Hello"를 이용해서 새로운 String을 만들어준 str3이 가리키고 있는 놈 ( Heap에 있음 )


    헉!! 이런 무려 4개나 만들어진단 말야?
    왜 똑같은 String인데 4개나 만들어야 하는거야? 그냥 무조건 1개만 쓰도록 하면 되지 않을까?

    그래서 String에는 intern()이라는 놈이 있습니다.
    intern() 메소드는, 새롭게 만들어진 String객체를 상수화 시켜 줍니다.
    만들어진 String 객체가 이미 상수로 만들어진 문자열이라면, 지금 만들어진 놈을 버리고, 상수를 가리키게 합니다.
    즉, Heap에 새롭게 만들어진 객체를 버리고, 상수를 재활용하도록 하게 하는것이죠.

    뭔말인지 모르겠다면, 예제를 보면 알 수 있습니다.
    intern()을 사용한 아래의 예제를 보겠습니다.

      String str1 = "Hello";
      String str2 = new String("Hello").intern();
      String str3 = new String("Hello").intern();
      System.err.println(System.identityHashCode(str1));
      System.err.println(System.identityHashCode(str2));
      System.err.println(System.identityHashCode(str3));

    결과
    3526198
    3526198
    3526198

    우와! 세상에! 결과로 모두 같은 값이 나왔습니다.
    str1에 대한 "Hello"는 상수에 있는 놈을 가리키고 있습니다.
    str2는 새로운 String 객체를 만들었지만, intern()을 호출하여 만들어진 객체를 버리고 "Hello" 상수를 가리키게 했습니다.
    str3도 str2와 같은 작업을 하게 됩니다.

    예전코드는 무려 4개나 Hello 객체가 있었지만, 지금은 딸랑 1개만 있습니다.
    우와! 세상에 이렇게 좋은게 있다니!!
    오예~ 그럼 이제 무조건 intern()을 써야 겠다!!
    라고 생각하면 큰일입니다.

    intern에는 엄청난 함정이 있습니다.
    intern에 대한 API문서를 확인해 보도록 하죠.


    intern

    public String intern()
    Returns a canonical representation for the string object.

    A pool of strings, initially empty, is maintained privately by the class String.

    When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

    It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

    All literal strings and string-valued constant expressions are interned. String literals are defined in §3.10.5 of the Java Language Specification

    Returns:
    a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.

    너무 기니깐, 굵게 표시해둔 글자만 대충(!) 읽어 보도록 하면,
    intern 메소드가 호출이 되면,

    1. String pool에 있는 각종 문자열에 equals해서 같은게 있다면 그 놈을 반환하고,
    2. 같은게 없다면 String pool에 String object를 추가하고, 추가한 놈을 반환한다.

    intern은 Heap에 만들어진 객체를 놓아주고, String pool에 있는 객체를 가리키게 합니다.
    그렇게 됨으로써 Heap의 메모리를 아낄 수 있습니다.

    하지만 intern() 메소드를 사용함으로 인해서 손해를 보는것도 생각해 보아야 합니다.
    1. 우선 String 객체를 하나 만들어야 합니다.
    2. String의 equals 메소드를 이용해서 String pool에 있는 놈을 찾아서 비교해야 합니다. ( 시간이 걸림 )
    3. String pool에 들어 갔으므로, 더 이상 GC(가비지컬렉션)의 대상이 될 수 없습니다. ( 메모리 관리 불가 )

    시간이 얼마나 걸리는지 간단한 예제를 가지고 테스트 해보도록 하면 아래의 결과를 얻을 수 있습니다.

      long startTime = System.currentTimeMillis();
      for ( int i = 0 ; i < 5000000; i++ ) {
           String str = "Hello";
      }
      long endTime = System.currentTimeMillis();
      System.err.println("String pool = "  + ( endTime - startTime));
     
      long startTime1 = System.currentTimeMillis();
      for ( int i = 0 ; i < 5000000; i++ ) {
           new String("Hello");
      }
      long endTime1 = System.currentTimeMillis();
      System.err.println("new String = "  + ( endTime1 - startTime1));
     
      long startTime2 = System.currentTimeMillis();
      for ( int i = 0 ; i < 5000000; i++ ) {
           new String("Hello").intern();
      }
      long endTime2 = System.currentTimeMillis();
      System.err.println("new String intern = "  + ( endTime2 - startTime2));


    결과
    String pool = 31
    new String = 188
    new String intern = 1796


    intern을 하게 되면, new String하는 시간과 String pool을 뒤지면서 equals하는 시간까지 걸리므로
    당연히 그 속도가 느릴 수 밖에 없습니다.

    그리고
    보통 intern()의 특징인 "메모리를 아낄 수 있다" 만 생각을 하고 프로그래밍을 하지,
    intern()하는데 시간이 오래 걸린다와, GC의 대상이 될 수 없다. 라는것은 생각하지 않고 프로그래밍을 하지요.

    특히, "메모리를 아낄 수 있다" 라는 생각에 10번도 안쓰는 String 객체를 intern()을 이용해서 상수화 시키게 되면,
    영원히 GC의 대상이 되지 않기 때문에 오히려 "메모리를 버리는 꼴"이 되는것을 조심해야 합니다.

    똑같은 문자열을 가지는 String을 100개를 생성한다고 해도, 나중에 GC가 되어서
    메모리에서 사라질 가능성이 있다면, 오히려 그것이 메모리를 아낄 수 있는 길입니다.


    결론.
    intern을 제대로 이해하지 못하고 사용한다면,
    메모리를 아끼는것이 아니라, 메모리를 마음껏 버리는 짓을 하게 될 것입니다.


    - 내용추가(2013.10.23)
    생각해 보니 String.intern()된 애들도 GC의 대상에 포함될 수 있을 듯 합니다.
    CustomClassLoader를 만든뒤에 Class를 loading했을때, CustomClassLoader가 GC가 되면class자체에 대한 reference도 없어 질테니, class에 설정된 String들도 GC의 대상이 될 가능성이 있겠네요.

    - 내용추가(2018.06.22)
    ssossohow 님께서 댓글로 업데이트 되어야 하는 부분 알려 주셨습니다. 댓글 중 일부를 글에 남겨 둡니다. ssossohow님 감사합니다. ^_^

    string constant pool은 java7 이전에 Perm영역에 있었는데 java7부터 Heap영역에 위치한다. 변경된 이유는 Perm 영역은 고정된 사이즈이기 때문에 문자열이 지속적으로 늘어나면 OOM이 발생할 확률이 높은 영역이다. pool을 Heap으로 옮김으로써 GC의 대상이 될 수 있어 OOM 발생확률이 극도로 낮아지기 때문에 위치를 옮긴 것이다.



    -----
    잘못된 내용이나, 오해할 소지가 있는 내용은 언제든지 코멘트 남겨 주세요~ ^^
    ( 상수와 String pool은.. 참.. 애매하군요. 설명하기에.. )

    1. 옷장수 2008.12.15 15:38 신고

      오~ 썬메일에 찬찬찬의 블로그가 떴어요~

      • Chan 2008.12.16 10:24 신고

        오우. 월요일 휴가라서 오늘에서야 보네요~ ㅎ
        방문 감사~ ^^

    2. 버리 2008.12.15 18:57 신고

      ㅋㅋ 저도 썬메일보고 반가운 마음에 들렀어요.
      내가 아는 사람 블로그다! 하구요.ㅎㅎ

      • Chan 2008.12.16 10:25 신고

        신기하군요 -_- 제 글이 뜨다니.. 풉;

    3. UncleJoe 2008.12.16 14:10 신고

      그냥 지나치고 간과할수 있는 부분에 대해서 다시 생각할수 있는 좋은 글이네요.

    4. cetauri 2009.12.16 15:32 신고

      구글링중 찬대리님 블로그가 제일 위에 나오네요 ㅋㅋ

      • Chan 2009.12.19 16:55 신고

        이런데서 ;; 신분을 밝혀 버리다니~~

    5. 2010.08.30 15:58

      비밀댓글입니다

      • 2010.08.30 19:18 신고

        저도 정확하게는 잘 모르지만

        1. permanent영역도 GC가 되나요? 제가 알기론 클래스와 메소드가 들어 있는것으로 아는데, 그게 GC가 된다 치고, 그렇다면 있던 class가 GC가 되고나면, 똑같은 class인데 class loading이 두번 일어 날까요? ^^ 전 그렇지는 않을꺼라 생각합니다. ^^

        2. 어쩔때 사용하면 좋은지는 저도 잘 모르겠습니다. ^^
        String.intern()에 관련된 자료를 몇개 찾아보면, "String.equals()를 쓰지 않고, == 로 비교할 수 있기 때문에 비교연산이 많은 경우에 유리하다" 라고 되어 있지만, 그렇게 사용하려면 관련된 모든 String들을 intern() 시켜야 하겠지요. 코드가 매우 잘 만들어져 있지 않다면, intern()시켜야 할 지점을 잡아 내는게 쉽지 않으리라 생각합니다.

        그리고 String.intern()을 호출하게 되면 어차피 이미 메모리에 올라가 있는 String들을 대상으로 String.equals과 비슷한 일을 해야 할 것입니다. 그렇게 치면 "반드시 ==를 이용하기 때문에 비교 연산이 빠르다" 라고 할 수는 없을 듯 합니다. ^^ ( 하지만 다른분의 글에서는 확실히 빠르다고는 합니다. ㅎㅎ )

        예상할 수 있는 String만 온다면 문제가 없겠지만, 사용자의 입력에 의해서 좌우되는 String이라면 절대 사용해서는 안되겠죠?? ㅎㅎ.

        저는 일을 하면서 아직도, intern()을 사용해야 할 만한곳을 아직 찾지는 못했습니다. 하지만 intern()의 특성을 잘 이해 할 수 있다면 언젠가는 한 번 써 볼 수 있는 날이 오지 않을까? 생각해 봅니다. ^^

    6. ... 2010.08.31 10:42 신고

      답변달아주셔서..
      덧글달려다 차단됐다고 나오네요.
      글을 남기지는 못하고 갑니다ㅠ

      • 2010.08.31 12:52 신고

        헉.. 왜 차단 되었을까요 T_T
        제가 그렇게 만든건 아닙니다. ㅎㅎ ^^;

    7. 2010.09.02 10:49

      비밀댓글입니다

      • 2010.09.23 02:05 신고

        다시 방문 감사드립니다. ^^
        (뒤늦게 인사드리네요. ㅎ )

    8. Java 2012.06.29 18:06 신고

      - intern은 String pool 에 들어가지만 String pool에 등록되었다고 gc의 대상에서 벗어나는것은
      아닙니다. 예전부터 자주보던 글이라 다른사람들도 오해할것같아서 적게되었습니다.

    9. Java 2012.06.29 18:06 신고

      - intern은 String pool 에 들어가지만 String pool에 등록되었다고 gc의 대상에서 벗어나는것은
      아닙니다. 예전부터 자주보던 글이라 다른사람들도 오해할것같아서 적게되었습니다.

      • 2012.07.08 01:21 신고

        아.. 잘못된 정보었네요. ㅜㅡㅜ
        근데 어떤 경우에 GC의 대상이 되는지 좀 알려주세요. ㅜㅡㅜ

    10. 이정섭 2013.12.20 15:39 신고

      메소드 자체를 synchronized할 필요가 없고
      String으로 key에 따라 synchronized를 해야할 경우 사용하면 좋을거같습니다.
      (key값은 jsp에서)
      synchronized(key){
      .....
      }

      • 2013.12.22 04:06 신고

        아마도 [Java/Tip] Hashtable을 제대로 활용하지 못하는 경우... ( http://blog.ggaman.com/917 ) 이 글의 코멘트에 있는 내용에 답 글을 다신것 같습니다.

        안타깝게도 제 생각은 다릅니다.
        key만으로 synchronized 걸어 주면 안됩니다.
        key는 String 객체이고, String 객체는 매번 새롭게 생성 될 수 있습니다.
        그러므로 똑같은 "A"라도 서로 다른 객체를 여러개 만들 수 있고,
        그러므로 서로 다른 객체를 synchronized 잡는것은 의미가 없을 수 있습니다.
        만약 할 것이라면
        syncfhorized(key.intern()) 으로 처리한다면 할 수 있겠지요.

    11. ssossohow 2018.06.05 20:41 신고

      최근에 공부한 내용 기반으로 수정되어야 하는 부분을 남깁니다.
      글 수정에 참고가 되셨으면 좋겠습니다.

      string constant pool은 java7 이전에 Perm영역에 있었는데 java7부터 Heap영역에 위치한다. 변경된 이유는 Perm 영역은 고정된 사이즈이기 때문에 문자열이 지속적으로 늘어나면 OOM이 발생할 확률이 높은 영역이다. pool을 Heap으로 옮김으로써 GC의 대상이 될 수 있어 OOM 발생확률이 극도로 낮아지기 때문에 위치를 옮긴 것이다.

      • 2018.06.22 16:23 신고

        좋은 내용 감사합니다. ^_^
        글 내용에 업데이트 해 두도록 하겠습니다. ^_^

    기초시리즈입니다.
    오늘은 Hashtable에 대해서 잠시 이야기해 보도록 하죠.

    Hashtable은 key를 이용해서 value를 꺼낼 수 있도록 해 주는 자료구조죠.

                       Hashtable table = new Hashtable();                

                       table.put("영화, "유쥬얼서스펙트);

                       table.put("오락, "황금어장라디오스타);

                       table.put("음악, "윤종신노래짱 ㅎㅎ);

    뭐 이런식으로, 우선 table에 key와 value를 넣어 주고

                       String key = "영화";
                       String value = (String) table.get(key);

    "영화"를 key로 가지고 있는 value, 즉 "유쥬얼서스펙트"를 꺼낼 수 있도록 되어 있습니다.
    보다시피 아주 간단한 방법으로 사용할 수 있습니다.

    그래서 간단하게 코드를 한번 짜 보았습니다.

                       Hashtable table = new Hashtable();

                       table.put("영화, "유쥬얼서스펙트);
                       table.put("오락, "황금어장라디오스타);
                       table.put("음악, "윤종신노래짱 ㅎㅎ);                 

                       String key = "오락;

                       String value = null;

                       

                       // "오락"이라고 되어 있는 key에 값이 있는지 확인하다.

                       boolean isContains = table.containsKey(key);


                       if ( isContains ) {
                                 // 값이 있으니깐 가져다 쓰도록 하자.

                                 value = (String) table.get(key);

                       } else {

                                 // 값이 없으니깐 안 정해 놨다고 하자.

                                 value = "뭘좋아하는지안정해놨네";

                       }


                       System.err.println(key + " : " + value);

    하는 일은,
    1. Hashtable을 하나 만들고,
    2. 각종 값을 넣고,
    3. 가지고 오고자 하는 key가 있다면, value를 가지고 오고
    4. 없으면 다른 값을 설정해 준다.

    아주 자연스러운 과정으로 보인다.

    하지만 위의 코드는 수정되어야 할 부분을 가지고 있다.
    위의, 코드를 짤때 Hashtable의 특성을 생각하지 않고, 쉽게 짰기 때문에 수정할 부분이 발생된다.

    재미삼아 한번 맞춰 보세요~ 일부러 공란을 조금 두겠습니다.
    ( 블로그에 오시는 분들은 ;; 고수분들이실꺼라 ;; 걍 공란 없습니다. ㅋㅋ )

    그 부분은 바로 아래의 코드이다.

    // "오락"이라고 되어 있는 key에 값이 있는지 확인하다.

    boolean isContains = table.containsKey(key);


    if ( isContains ) { ....

     


    해당 key에 설정된 값이 있는지 확인한 뒤에 값을 가지고 오는게 뭐가 틀렸냐고 하겠지만,
    위의 코드로 인해서 약간의 손해를 보게 된다.

    왜 그런지 Hashtable의 API를 확인해 보자.
    get(Object) 메소드를 살펴 보도록 하자.

    ------

    http://java.sun.com/javase/6/docs/api/java/util/Hashtable.html#get(java.lang.Object)

    get

    public V get(Object key)
    Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.

    .....

    Returns:
    the value to which the specified key is mapped, or null if this map contains no mapping for the key

    ......

    ------

    get의 return부분은 아래와 같이 설명되어 있다.

    인자로 들어온 key에 맵핑되어 있는 value를 반환하거나,
    map에서 인자로 들어온 key로 맵핑된것이 없으면 null을 반환한다.

    즉, get했을때 인자로 key를 주어서 null이 나오면, map(table)안에 해당 정보가 없다는 말이다.

    그렇다면 put할때, key에 해당하는 value로 null을 줄 수 있지 않을까?
    key에 해당하는 value를 null을 줄 수 있다면, get(key) 했을때 null이 반환될 가능성이 있기 때문이다.

    하지만 그런 걱정은 안해도 된다.
    Hashtable에 대해 설명해둔 API문서를 다시 확인해 보자.

    ------
    http://java.sun.com/javase/6/docs/api/java/util/Hashtable.html

    public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, Serializable

    This class implements a hashtable, which maps keys to values. Any non-null object can be used as a key or as a value.

    .....

    ------

    Hashtable은 key혹은 value로 non-null인 어떠한 Object도 사용가능하다고 한다.
    즉, key와 value는 절대로 null이 될 수 없다.


    자,
    Hashtable에 대한 대충의 정리가 끝났으니, 생각없이 짜 놓았던 소스를 다시 보도록 하자.

                       Hashtable table = new Hashtable();

                       table.put("영화, "유쥬얼서스펙트);
                       table.put("오락, "황금어장라디오스타);
                       table.put("음악, "윤종신노래짱 ㅎㅎ);          

                       

                       String key = "오락;

                       String value = null;

                       

                       // "오락"이라고 되어 있는 key에 값이 있는지 확인하다.

                       boolean isContains = table.containsKey(key);


                       if ( isContains ) {
                                 // 값이 있으니깐 가져다 쓰도록 하자.

                                 value = (String) table.get(key);

                       } else {

                                 // 값이 없으니깐 안 정해 놨다고 하자.

                                 value = "뭘좋아하는지안정해놨네";

                       }


                       System.err.println(key + " : " + value);



    위의 소스에서 손해 보고 있는 코드는 아래와 같다.



    boolean isContains = table.containsKey(key);

    if ( isContains ) { ....




    위의 소스대로라면

    1. containsKey로 실제 key가 존재하는지 확인하고,

    2. key가 존재하면 get을 이용해서 값을 가지고 온다.



    하지만, 다음과 같이 고쳐 쓸 수도 있다.


                       String key = "오락;

                       String value = table.get(key);


                       if ( value == null ) {

                                 // 값이 없으니깐 안 정해 놨다고 하자.

                                 value = "뭘좋아하는지안정해놨네";

                       }




    get(key)메소드만을 사용해서 null체크를 이용해 똑같은 일을 수행할 수 있다.

    굳이 containsKey를 이용해서 확인할 필요가 없다는 말이다.


    그렇다면 재미삼에 containsKey메소드와 get 메소드의 내용을 잠시 살펴 보자.



    ------

    JDK 1.6.0_05 ( c:\program files\java\jdk1.6.0_05\src.zip 파일안에 있음 )


     

       public synchronized boolean containsKey(Object key) {

                 Entry tab[] = table;

                 int hash = key.hashCode();

                 int index = (hash & 0x7FFFFFFF) % tab.length;

                 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {

                     if ((e.hash == hash) && e.key.equals(key)) {

                               return true;

                     }

                 }

                 return false;

       }

     

       public synchronized V get(Object key) {

                 Entry tab[] = table;

                 int hash = key.hashCode();

                 int index = (hash & 0x7FFFFFFF) % tab.length;

                 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {

                     if ((e.hash == hash) && e.key.equals(key)) {

                               return e.value;

                     }

                 }

                 return null;

       }

    ------



    보다시피, containsKey와 get의 메소드는 거의 동일하며,


    마지막에 true, false를 반환하느냐 ( containsKey )

    혹은 Object, null을 반환하느냐만 ( get )

    다를 뿐이다.


    그러므로 constainsKey대신에 get을 사용해도 똑같은 일을 할 수 있다.

    ( 사실은 get을쓰게 되면 Object의 레퍼런스가 하나 더 생성되겠지만.. 자세한 이야기는 집어 치우자. )




    get을 하려고 containsKey를 사용해서 값이 있는지 확인하는것은

    전혀 필요 없는짓이며 이로 인해서 많은 손해를 보게 된다.



    사실 containsKey를 get하기 전에 사용한다고 해서,그렇게 많이 손해를 보겠느냐~ 라고 생각할 수도 있다.

    기껏해야 0.00001초 정도 아닐까. 라고 생각할 수 있다.

    그렇다고 치면 10000번씩 호출해도 0.001초 차이 날까 말까 하지 않을까?


    그래 그 말도 맞을 수도 있다.


    하지만, 위의 코드중에서 containsKey 메소드를 다시 한번 보도록 하자.


       public synchronized boolean containsKey(Object key) {

                 Entry tab[] = table;

                 int hash = key.hashCode();

                 int index = (hash & 0x7FFFFFFF) % tab.length;

                 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {

                     if ((e.hash == hash) && e.key.equals(key)) {

                               return true;

                     }

                 }

                 return false;

       }


    위의 코드에서 눈여겨 볼만한 부분은 synchronized 키워드이다.


    Hashtable은 put, remove, get등 모든 데이터 입출력에 대해서 "동기화" 된다.

    ( 왜 그러냐고 물으면 안된다. Java에서 그렇게 정의해 두었다. 정의라는것에 이유는 없다. )

    ( 동기화에 관련된 자세한 이야기는 다른 강좌등을 참고하도록 하자. )

    ------

    As of the Java 2 platform v1.2, this class was retrofitted to implement the Map interface, making it a member of the Java Collections Framework. Unlike the new collection implementations, Hashtable is synchronized.

    ------


    Hashtable 객체의 put, remove, get, containsKey 등의 메소드가 호출 될때마다

    1. 다른 Thread에서 자신의 객체에 접근할 수 없게 무조건 Lock를 잡고

    2. 해당 메소드를 수행하고 결과를 반환한 뒤에

    3. 잡았던 Lock를 놓게 된다.


    이때 위의 동작만 보더라도 결과만 반환하는것이 아니라, Lock를 잡고, 푸는 과정만해도 2번의 동작이 추가 된다.

    그리고 일반적으로 Lock를 잡고, 푸는것은 많은 시간이 소모되는 작업으로 알려져 있다.




    그러므로

    Hashtable의 특성인

    1. cotainsKey대신 get을 이용할 수도 있음.

    2. 동기화

    에 대해서 잘 알고, Hashtable을 사용하도록 하자.



    --------

    문제가 될만한 사항이 있으면 언제든지 알려 주세요.

    1. 무적조로™ 2008.11.20 09:35 신고

      난 containsKey() 메서드를 안쓸 뿐이고....
      넣었으면 빼야된다는 생각만 할 뿐이고....
      내가 안넣었으니 null 올꺼라고 생각할 뿐이고..

      • Chan 2008.11.20 10:00 신고

        난....

        하고, 시간을 두는 센스~ 를 보여야징~ ㅎㅎ

    2. 버리 2008.11.20 10:34 신고

      아주 사소한 문제긴 한데...ㅋㅋ
      string을 감싸는 "가 왼쪽에만 있는건 새로 추가된..건 아니죠?ㅋㅋㅋ

      • Chan 2008.11.20 13:16 신고

        너무 예리하세요~ ㅎㅎ
        복사해서 붙여 넣다 보니~ ㅎㅎ

    3. 쇼니 2008.11.24 11:45 신고

      있는지만 확인할땐 containsKey() 값을 결국 써야할땐 get()...
      값을 사용하는 경우가 거의 다라고 보면 되겠지만... ^^
      단일쓰레드환경에서는 HashMap을 사용하면 되는거궁..
      HashMap은 value로 null을 넣을 수도 있고..
      ㅋ 뭐든지 상황에 맞게!! ㅎ

      • Chan 2008.11.24 13:45 신고

        빙고~ 옳소~ ㅎㅎ

        HashTable을 HashMap으로 바꾸려고 하면,
        1. 단일 Thread에서 동작하는가?
        2. null을 넣을 가능성이 있냐?
        를 살펴 보아야 겠고~ ㅎㅎ

        추가하자면 Multi Thread환경에서도
        1. static하게 put하고 이후에 get만 하는 경우에는
        HashTable을 HashMap으로 바꾸어도 상관없다는거..

        뭐든지 상황에 맞게~ ㅋㅋ
        ( 점점 ;; java 공부 블로그가 되어 가고 있나~ ㅎ. )

        모르는게 너무 많아.. ㅠ_ㅠ

    4. Richpapa 2009.08.27 12:51 신고

      왜 더 낫다는 거죠? 둘다 sync 이고, 내부 소스가 거의 동일하고... 성능에 지장이 없을 것 같은데... 또한 위의 예가 단편적일 수도 있지만 키든 vaule든 객체가 null인지 체크해야만 하는 null object 패턴까지 써야 되는 것처럼 보입니다. 그렇다면 아싸리 불린값이 더 깔끔해보이는데... 한 수 알려주세요.

      • Chan 2009.08.27 13:45 신고

        위 글에서 세번째에 있는 코드들을 확인해 보면
        위의 예제가 하는 일은,
        1. 해당 값이 있는지 확인하고,
        2. 값이 있으면 가지고 오는 코드입니다.

        즉,
        1. contains()로 확인을 한 뒤에
        2. get()을 수행한다.
        는 것입니다.

        하지만,
        contains와 get()은 같은일을 하고,
        결과의 형태만 다르게 반환하므로,

        contains()와 get()을 순차적으로 수행할때에는
        그냥 get()을 이용하는것이 더 효율적이다.
        라는 말을 하고 싶었습니다.

        당연히, 있는지 없는지 체크할때에는
        contains만 사용하는것이 의미상 더 바람직하겠지요.


        이하의 이야기는 Richpapa님에게 드리는 말은 아닙니다.
        오래된 글인데 여러분들이 글을 남겨주셔서 ^^; 혹시나 오해하지 않도록 설명을 달아 둡니다.

        참고로,
        위에 예제로 제시한 코드는 Multi Thread에서 안전한 코드가 아닙니다. Hashtable를 썼으니 안전한거 아니냐? 고 생각되시는 분들은, Thread 공부를 조금 더 하셔야 할 듯 합니다. ^^

    5. 초보 2013.10.11 20:41 신고

      궁금한게 있어서 질문을 드립니다.

      "위에 예제로 제시한 코드는 Multi Thread에서 안전한 코드가 아닙니다. Hashtable를 썼으니 안전한거 아니냐? 고 생각되시는 분들은 ..."라고 하셨는데 설명을 부탁드립니다.

      그리고 "추가하자면 Multi Thread환경에서도 1. static하게 put하고 이후에 get만 하는 경우에는 HashTable을 HashMap으로 바꾸어도 상관없다는거.." 이 부분에 대해서도 설명을 부탁드립니다.

      조금 고민을 해봤는데 잘 모르겠네요. ㅎㅎ

      • 2013.10.23 20:47 신고

        답변이 많이 늦었습니다.

        "Hashtable을 사용했다고 MultiThread에 안전하다고 생각해서는 안된다" 에 대한 답변 드립니다.

        Hashtable은 put, remove의 각각의 메소드에 대해서는 MultiThread에 안전하게 동작합니다. 하지만 이러한것이 섞여 있을때는 불안전해 질 수 있습니다.

        예를 들어...
        Hashtable table = new Hashtable();

        1: boolean contains = table.contains("AA")
        2:
        3: if ( !contains ) table.put("AA","aa");

        위와 같은 코드가 있다고 한다면

        Thead-1에서는 1번 Line 문장을 수행하여 contains값이 true라고 받았다고 가정합니다.

        이때 Thread-1이 3번 라인을 수정하기 전에
        Thread-2가 table.remove("AA") 를 수행했다고 합시다.

        실제 table에는 "AA"가 없는 상태인데도,
        Thread-1에서는 이미 있다고 판단하였기 때문에
        table에 "AA"를 추가해 주지 않습니다.

        이렇듯 단순 Hashtable만을 사용했다고 해서 Thread에 안전한 코드를 작성했다고 볼 수는 없다는 말입니다.

      • 2013.10.23 20:52 신고

        "추가하자면 Multi Thread환경에서도 1. static하게 put하고 이후에 get만 하는 경우에는 HashTable을 HashMap으로 바꾸어도 상관없다는거.." 에 대한 설명 드립니다.

        위에 설명한 Hashtable의 예에서도 보았다시피, Hashtable을 썼다고 해서 안전한 코드를 작성했다고 보기 어렵습니다.

        반대로 HashMap을 사용한다고해도 잘만 작성한다면, MultiThread에 안전한 코드를 작성할 수 있습니다.

        public synchronized void put(String key, String value ) {
        hashmap.put(key, value);
        }

        public synchronized void remove(String key) {
        hashmap.remove(key);
        }

        위와 같이 작성한다면 hashmap은, 동기화 되는 method내부에서만 사용됩니다. 즉 hashmap에 접근할 수 있는것은 한번에 1개의 thread만 접근할 수 있게 된 것입니다.

        그러므로 hashmap을 사용해도 코드를 작성하는 방법에 따라서 MultiThread에 안전하게 작성할 수 있게 되는것입니다.

    6. MJS 2016.08.17 13:50 신고

      와우.. 좋은정보 감사드립니다.

    네이버 카페 코드인에 심심해서 적어 놨던 글을 다시 옮겨옴.
    ( 워낙 포스팅이 없어서 ;; 요런걸로 때움. ㅋㅋㅋ )
    -------
    안녕하세요.
     찬 입니다.

    우리가 일반적으로 Map이나 HashTable을 쓸때 다음과 같이 사용하지요.

    Map map = new HashMap(100);

    map.put("찬", new Person( Person.MEN, 29 ) );
    map.put("철수", new Person( Person.MEN, 15) );
    map.put("영희", new Person( Person.WOMAN , 13 ) );

    이때 map에
    key로 "찬", "철수", "영희" 와 같이 String을 주고,
    value로는 Person 객체를 만들어서 넣어 줍니다.

    이때,
    map에서는 key 값이 중복되면 기존에 있던 value에다가 새로운 value를 덮어 써 버리게 되는 것처럼 보입니다.

    map.put("찬", new Person( Person.MEN, 29 ) );
    map.put("철수", new Person( Person.MEN, 15) );
    map.put("영희", new Person( Person.WOMAN , 13 ) );
    map.put("철수", new Person( Person.WOMAN, 15 ) ) ;  // 으악! 철수를 여자로 만들어 버렸어!!

    put할때에는 key의 hashcode를 보고 쓰게 되어 있죠.
    즉, "철수".hashCode() 값이 같기 때문에, 예전에 있던 값에다가 덮어써 버리게 됩니다.
    ( 정확하게 말하면, 원래의 Hash 개념으로는 hashCode가 같은 애들은 bucket에 저장됩니다. 덮어 써 버리지는 않습니다. 하지만 자바의 기본 구현에서는 Key의 hashCode에 맞춰서 하나만 들어 가요. )


    그렇다면 String의 hashCode()에 대해서 좀 알아 봅시다.
    아래는 String의 hashCode()에 대한 API문서 내용입니다.

    hashCode

    public int hashCode()
    Returns a hashcode for this string. The hashcode for a String object is computed as
     s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
    using int arithmetic, where s[i] is the ith character of the string, n is the length of the string, and ^ indicates exponentiation. (The hash value of the empty string is zero.)
    Overrides:
    hashCode in class Object
    Returns:
    a hash code value for this object.
     

    보다시피 String의 hashCode()는 "s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]" 요런 공식에 의해서
    값이 반환되게 되어 있는데요, 공식이 무진장 어렵죠. 무슨 말인지도 잘 모르겠고..

    공식보다는 hashCode()의 반환값에 대해서 이야기 해 보죠.
    hashCode()의 반환값은 int입니다. String 객체의 hashCode()를 가지고 오면 int형태를 반환하게 되죠.


    자, 그럼 다시 생각해 봅시다.
    int는 총 4byte를 차지할 수 있는 정수형 기본 타입입니다.
    4byte로 표현할 수 있는 갯수는 0부터 0xFFFFFFFF(4,294,967,295) 입니다.
    즉, hashCode()의 결과는 그 많은 int값 중에서  하나를 반환됩니다.


    그렇다면 또 다시 String의 hashCode를 생각해 봅시다.

    하지만 String은 무한개를 생성해 낼 수 있습니다. ( "A", "A1",..."AZ", "AA1", "ZZ....ZZZ", "가1A" .... 등등 )
    그런데, 이렇게 무한개로 생성해 낼 수 있는 String 객체의 hash code는 int가 반환할 수 있는 숫자중에 하나 입니다.

    확률상 서로 다른 String 인데도, 같은 hashcode를 가지는 String이 있을 수 있습니다.


    그렇기 때문에,
    HashMap이나 Hashtable를 사용할때 아무런 생각없이 String을 key으로 주고 쓰고 있습니다.
    재수가 없으면 데이터가 날아 가는 문제가 발생할 수 있습니다.
    ( 또 다시 이야기 하자면, 데이터가 날아 가지는 않습니다. - 자세한 사항은 HashMap의 코드를 분석해 보세요~ )


    그러므로 정말로 반드시 유일한 값이 필요하다고 한다면, hashCode()를 사용해서는 안될것입니다.
    char[]을 일일이 가지고 와서 적당히 조작해서 int값으로 만드는 방법을 쓰던지 해야 할것입니다.
    ( 하지만, 위에서도 말했다시피 - int는 제한적이고 string은 무제한이므로, int를 가지고 어찌할 방법은 없습니다. )

    2009년 8월 27일 추가 - 최근에 오래된 글에 대한 코멘트가 많이 달리네요. ㅎ.

    지민아빠님의 코멘트처럼, 사실은 hashCode가 같다고 해서 꼭 문제가 되는것은 아닙니다. 
    이미 들어 있는 hashCode를 가진 key를 이용해서 put하면, return으로 이전에 들어 있던 Object가 나오거든요.
    이 글에서 이야기 하고 싶은것은,
    String.hashCode()는 유일한 값을 반환하지 않는다는 것입니다.
    그러므로 이글 오해할 가능성이 있는 내용을 포함하고 있습니다.

    잘 이해가 되지 않으시는 분들은 이 게시물에 cobus 님께서 달아 두신 comment를 참고하시면 이해가 되실 겁니다. ^_^
    2011년 6월 23일 추가 -
    오늘 또 어느 익명의 정의에 사도께서는 "이 딴 글은 내려버려라~" 고 적어 주셔서, 내용을 일부 수정합니다.
     
    어흑. 진짜 글을 내려 버려야 하나.. 쩝ㅋㅋㅋ


    1. 지민아빠 2008.11.19 00:40 신고

      hashcode 는 같은 값이 나오지만, 그래서 equals 도 같이 사용하는 거잖아요. 내부적으로는 그래서 예비 버킷이 있는.... 흠. 암튼. 유용한 글.. 좋아요. ㅎㅎ

    2. 2009.08.27 12:41 신고

      해쉬가 같으면 같은 빠겟스에 담는다는거고...
      빠겟스 수는 적절하게 조절되어질테고~

      결론이 좀....ㅋㅋㅋ

      여튼 잘봤음

      • Chan 2009.08.27 13:50 신고

        우왕~
        요즘에 오래된 글에 많은 사람들이 코멘트를 남겨주시네~ ㅎㅎ
        위의 글은 제대로 오해할 소지가 많지~ ㅎㅎ
        하지만, 고치긴 귀찮아. ㅋㅋ

    3. 분홍 2010.10.11 17:04 신고

      잘봤습니다. hashcode 의 문제점을 아주 잘 파악한 좋은글이네요...

    4. 흠.. 2011.06.20 18:34 신고

      오해할 소지가 있는 글이 아니고
      아예 개념을 잘 모르고 쓴 글이네요...

      아직도 안지우고 냅두고 있다는 것이 신기합니다..
      나는 바로 내릴 것 같은데..

      • Chan 2011.06.23 18:24 신고

        허허.
        이렇게 오래 된 글을 찾아 주셔서 감사합니다.
        아무래도 내용을 수정해야 겠군요. ^^

    5. 지나가다 2013.08.29 01:55 신고

      해쉬코드는 문자열에 유일합니다.
      char val[] = value;

      for (int i = 0; i < value.length; i++) {
      h = 31 * h + val[i];
      }
      hash = h;
      위가 hashCode() 소스입니다.
      value는 해싱할 문자열이고
      각 글자자릿수의 지수승을 해서 각 문자의 ASCII코드를 더합니다.
      abc 를 입력하면 96354이 나옵니다.

      • 2013.10.23 20:57 신고

        바로 위 "지나가다"님의 코멘트는 잘못 된 내용입니다.
        문자열이 달라도 같은 hashCode가 나올 수 있습니다.

        그 이유는 위의 글에 나와 있습니다.

    6. cobus 2015.11.27 11:10 신고

      글 내용을 잘 이해를 못하신건지 아니면 .hashCode()를 맹신하시는건지...
      본문에 String class의 hashCode() 를 사용했을 때 같은 값이 나오는 예제를 추가하면 논란이 없을 것 같습니다.

      약간 수정되어야 할 부분은 hashCode() 값이 중복될 수 있는 이유를 int의 수와 String의 표현 가능 수가 차이가 나서 중복이 생길 수 있다고 하셨는데 사실은 ASCII code값을 이용해 비트연산의 합으로 hash값을 만들다보니 길이와 상관없이 Hash값 중복이 발생할 수 있습니다. 본문에 추가하면 좋을것 같은 hashCode() 중복 문자열은 아래와 같습니다.

      String a = "Z@S.ME";
      String b = "Z@RN.E";
      if(a.hashCode() == b.hashCode()) {
      System.out.println("same hashcode");
      } else {
      System.out.println("different hashcode");
      }

      결과: same hashcode

      • 2015.12.15 19:41 신고

        cobus님 //
        댓글 감사합니다. ^_^

        저희 의도는 int의 표현 가능 갯수보다 String은 표현가능 갯수가 더 많기 때문에
        String을 int로 mapping 시킨다면 당연히, int 값이 겹치는 경우가 발생할 수 밖에 없다라는 의미로 적었습니다. ( 원론적인건 코드가 필요 없으니까요 ^_^ )

        제가 그 의미를 정확하게 표현하지 못했네요 ^_^

        이 글을 읽으시는 분이 코멘트 남겨 주신 내용을 읽으시면,
        훨씬 더 잘 이해할 수 있을것으로 보입니다. ^_^
        자세한 설명과 구체적인 예를 들어 코멘트 남겨 주셔서 고맙습니다. ^_^

        해당 글에, cobus 님의 코멘트를 확인하라는 내용을 추가 했습니다. ^_^

    자바에서 Swing을 사용할때 별 생각없이 만들다 보면,
    EDT가 아닌곳에서 Swing Component에 대한 작업을 많이 하려고 한다.

    다른 Thread에서 Swing Component에 대해서 어떠한 작업을 했을때,
    dead lock이 일어나기 전에는,
    어느곳에서 그런것을 썼는지 일일이 알아 내기가 힘들다.

    Print 관련 자료를 찾다가 TroubleshootingGuide for Java를 발견해서
    살짝 보았는데 참고해 두고 담에 체크할때 먹으면 좋아 보여서 긁어 놓는다.

    TroubleshootingGuide for Java SE 6DesktopTechnologies
    http://java.sun.com/javase/6/webnotes/trouble/TSG-Desktop/TSG-Desktop.pdf

    4.2.1 IncorrectThreading
    Randomexceptions and painting problems are usually the result of incorrect threading usage of Swing. All access to Swing components, unless speciically noted in the javadoc,must be done on the event dispatch thread. This includes anymodels (TableModel, ListModel, and others) that are attached to Swing components.

    The best way to check for bad usage of Swing is by way of an instrumented RepaintManager,as illustrated by the following code:

    public class CheckThreadViolationRepaintManager extends RepaintManager {

        // it is recommended to pass the complete check
        private boolean completeCheck = true;

        public boolean isCompleteCheck() {
            return completeCheck;
        }

        public void setCompleteCheck(boolean completeCheck) {
            this.completeCheck = completeCheck;
        }

        public synchronized void addInvalidComponent(JComponent component) {
            checkThreadViolations(component);
            super.addInvalidComponent(component);
        }

        public void addDirtyRegion(JComponent component, int x, int y, int w, int h) {
            checkThreadViolations(component);
            super.addDirtyRegion(component, x, y, w, h);
        }

        private void checkThreadViolations(JComponent c) {

            if (!SwingUtilities.isEventDispatchThread() && (completeCheck || c.isShowing())) {

                Exception exception = new Exception();
                boolean repaint = false;
                boolean fromSwing = false;
                StackTraceElement[] stackTrace = exception.getStackTrace();

                for (StackTraceElement st : stackTrace) {
                    if (repaint && st.getClassName().startsWith("javax.swing.")) {
                        fromSwing = true;
                    }
                    if ("repaint".equals(st.getMethodName())) {
                        repaint = true;
                    }
                }

                if (repaint && !fromSwing) {
                    //no problems here, since repaint() is thread safe
                    return;
                }

                exception.printStackTrace();
            }
        }
    }
    1. 무적조로™ 2008.11.05 12:59 신고

      어떤 내용인지 감이 잘 안잡힌다 ㅋㅋ
      다시 자세히 설명 좀 해보삼..

      • Chan 2008.11.08 00:55 신고

        뭐.. ㅎㅎ 굳이 깊이 알 필요는 없는.. ㅎㅎ
        알아두면 좋은거고. ㅎㅎ

        1. Swing에서 무언가 작업을 할때에는 EDT에서 해야 한다.
        2. 스윙에서 무슨 작업을 하고나면 보통 repaint를 호출하거나, 혹은 호출이 되는데
        3. 이때 만약 EDT가 아닌 Thread에서 호출하게 되면, 에러를 출력하는 코드

        정도 되는것 같은데. ㅎㅎ

        아직 한번도 안써봐서 ^^;;

    Font 및 Character 관련글을 찾아보면, CJK에 대해서 많이 나온다.

    여기서 CJK란 중국, 일본, 한국을 뜻한다.
    ( 아주 가끔씩 혹은 거의 그러지 않지만, CJKV라고도 하는데 V는 베트남을 이야기 한다. )

    http://en.wikipedia.org/wiki/CJK


    CJK characters

    CJK is a collective term for Chinese, Japanese, and Korean, which constitute the main East Asian languages. The term is used in the field of software and communications internationalization.

    The term CJKV means CJK plus Vietnamese, which in the past used Han t?/Chinese characters and Ch? Nom prior to adopting Qu?c Ng?.


    CJK의 특징은 한자(漢字)를 사용한다는것이다.
    ( J 와 K의 경우에는 자신의 문자들이 있지만, 한자를 같이사용(병용)하고 있다. )

    그리고 이쪽을 보다보면 Kanji 며 Hanzi 며.. 이런 단어가 나온다.
    아무튼 간단하게 조사.



    위에 말했다시피 CJKV 는 한자를 같이 사용하고 있다.
    하지만 문자를 같이 사용할뿐 발음은 서로 다르게 하고 있다.
    ( 마치, 한국 사람이 모택동이라고 부르고, 중국사람은 마오쩌뚱이라고 부르는것 처럼. )

    hanzi나 kanji는 한자를 이야기 할때, 언어권별로 다른 발음을 영어로 나타낸것이다.


    한자( Chinese character, Han character )의 발음을

    중국 : Hanzi
    일본 : Kanji
    한국 : Hanja
    베트남 : Han t?

    라고 한다.

    http://en.wikipedia.org/wiki/Chinese_characters

    Chinese character

    A Chinese character, Han character or Hanzi (simplified Chinese: ?字; traditional Chinese: 漢字; pinyin: Hanzi) is a logogram used in writing Chinese (hanzi), Japanese (kanji), less frequently Korean (hanja), and formerly Vietnamese (han t?).


    즉, 쉽게 이해하려고 하면,
    Hanzi 던 Kanji 던 Hanja던, 모두 한자를 이야기 하는거다.
    단지 발음이 달라서 정확하게 표현하려고 하는거다.



    더 정확하게 이야기 하면 Kanji와 Hanzi, 그리고 Hanja는 같지 않다.

    세 나라 모두 다 한자를 사용하기는 하지만, 그 한자의 모양새나 발음이 다르다.
    일본에서는 한자를 간단하게 변형해서 쓰기 때문에 Kanji와 Hanzi 가 같다고 볼 는 없다.


    끝.


    참고로. 한자뿐만 아니라, 로마자도 서로 다르게 표현한다.

    http://en.wikipedia.org/wiki/Romaja
    Romaja literally means Roman letters in Korean, and refers to the Roman alphabet.


    http://en.wikipedia.org/wiki/Romanization_of_Japanese
    The romanization of Japanese or r?maji (ロ?マ字?) (listen ) is the use of the Latin alphabet to write the Japanese language



    다시 참고로, 특정 언어를 로마자(알파벳)으로 바꾸는것으로 romanization 라고 한다.

    http://en.wikipedia.org/wiki/Romanization
    In linguistics, romanization (or latinization, also spelled romanisation or latinisation) is the representation of a word or language with the Roman (Latin) alphabet, or a system for doing so, where the original word or language uses a different writing system (or none).
    1. 옷장수 2008.09.04 09:30 신고

      깊게 들어가면 일본,한국,중국 각각의 나라에서만 쓰는 한자 있는걸로 아는뎅~

      • Chan 2008.09.04 10:08 신고

        그렇지요~ ㅎㅎ.
        세나라 모두 다 한자를 사용하기는 하지만, 그 한자의 모양새라 발음이 다르다. 라고 적혀 있지만,
        옷장수님 말씀처럼, 고유한 한자인 "문자"를 가지고 있기도 합니다. ㅎ.
        ( 뭐 틀리면.. 저도 잘.. ㅎㅎ )

    몇일전 테스트 주도 개발이라는 책을 다 읽었다.

    1장부터 16장까지는 은행에서 사용하는 돈(통화)에 관련된 작업을
    어떻게 TDD로 만들어 가는지를 실제 코드를 보여주면서 보여주고 있다.

    17장 부터는 TDD 자체에 대한 이야기로 되어 있다.

    이전에 적었던 부분을 빼고, 책을 읽으면서 줄쳐 놓은 부분을 옮겨 본다.
    • 이러한 접근 방식은 "깔끔한 코드" 부분을 먼저 해결한 후에, "작동하는" 부분을 해결해 가면서 배운것들을 설계에 반영하느라 허둥거리는 아키텍쳐 주도개발과는 정반대다.
    • 시스템이 크다면, 당신이 늘 건드리는 부분들은 절대적으로 견고해야 한다. 그래야 나날이 수정할때 안심할 수 있기 때문이다.
    • "다음에 할일은 무엇인가?"에 관련된 또 다른 질문은 "어떤 테스트들이 추가로 더 필요할까?"다.
    • 그리고 지금 이 글을 쓰는 동안 "Expression(수식)" 메타포를 생각했는데, 설계가 기존과는 완전히 다른 방향으로 흘렀다. .... Expression 메타포는 중복되는 통화를 합치는 세세한 일단의 문제에서 날 해방시켰다. 나는 수식의 성능에 대해 우려 했지만 최적화를 시작하지 전에 어느정도의 사용 통계를 볼때까지 기다리는것에 만족한다.
    • 아주 확신에 찬 사람과 정말 얼렁뚱땅 넘어가는 사람들 제외한다면, 아무리 작은 변화라도 테스트 하지 않고 릴리즈 하지 않는다. ( 만든것만 테스트하는 임시 테스트 코드 )
    • 당신이 변화를 테스트 할 수 있다고 해도, 실제로 변화를 테스트하는 것은 "테스트를 가지고 있다."는 것과 똑같지 않다. ( 계속 유지되는 자동화된 테스트 )
    • 이 경우엔 '테스트'를  '자동화된 테스트'로 치환하면 된다. "내가 이걸 고치면 뭔가 다른 부분을 망가트리지 않았을까?" .... 자동화된 테스트가 있다면 스트레스를 받기 시작할때 테스트를 실행할것이다. .... 테스트를 실행하면 즉시 좋은 느낌을 받게 되고 그러면 작업중에 에허를 낼 일도 줄게 되며, 스트레스도 적어진다.
    • 테스트를 실행하는 것이 서로 어떤 식으로 영향을 미쳐야 좋은가? 아무 영향이 없어야 한다. .... 앞 부분에서 실행된 테스트가 실패한 후 그 영향으로 다음 테스트부터는 시스템이 예측 불가능한 상태에 놓이는 경우가 허다하다.
    • 테스트는 전체 애플리케이션을 대상으로 하는것 보다 좀 더 작은 스케일로 하는것이 좋다. 어쨌건 주된 교훈은 각각의 테세트는 다른 테스트와 완전히 독립적이어야 한다는것이다. 즉, 문제가 하나면 테스트도 하나만 실패해야하고, 문제가 둘이면 테스트도 두개만 실패해야 한다.
    • 테스트를 언제 작성하는것이 좋을까? 테스트 대상이 되는 코드를 작성하기 직전에 작성하는것이 좋다. 코드를 작성한 후에는 테스트를 만들지 않을것이다.
    • 시스템을 배갈할 때 무슨 일 부터 하는가? 완료된 시스팀이 어떨 거라고 알려주는 이야기 부터 작성한다. ( USER STORY를 말한다. 기존의 방법론의 요구사항 문서와 비슷한데 훨씬 짧고 비격식적이다. )
    • 테스트 할 때 어떤 데이터를 사용해야 하는가? 테스트를 읽을 때 쉽고 따라가기 좋을 만한 데이터를 사용하라. .... 1과 2 사이에 어떠한 개념적 차이도 없다면 1을 사용하라.
    • 시스템이 여러 입력을 다루어야 한다면 테스트 역시 여러 입력을 반영해야 한다.
    • 길을 잃은 느낌이 들땐 어떻게 해야 할까? 코드를 다 지워버리고 처음부터 다시 해 보자.

    더 많은 내용에 줄을 그어 놓았지만, 그만 옮겨적어야겠다.


    아직은 "테스트 주도 개발"에서 "테스트"에 대해서만 와 닿았고, "주도"에 대해서는 잘 모르겠다.
    다음에 정리를 다시하면서 "주도" 부분도 다시 한번 알아 봐야 겠다.
    1. leonid 2008.03.19 19:01 신고

      안녕하세요. 이번에 TDD에 대해서 공부를 시작해보았습니다. 아직까지는 온라인에 있는 글과 동영상들만 봤지만 역시 책을 사서 읽어야 할 것 같네요 :)

      • Chan 2008.03.19 22:34 신고

        어이쿠~ 답변 감사합니다. ^_^

        제가 내린 결론은
        TDD는 아직잘 모르겠지만, 확실히 느낀건 TestCase는 많으면 많을 수록 좋다 였습니다. ^_^

    내용에 대해서 책임지지 않습니다. ㅋㅋ


    CP949는 MS-Windows에서 정한 한글을 나타내는 문자set 이다.
    index가 주루룩 나열되어 있고, 거기다가 한글이 하나씩 들어 있는것이다.


    원래 글자를 나타낼때에는 index는 총 2byte로 구성되어져 있어도 문제가 없을것 같았나 보다.
    하지만 2byte만을 가지고는 세계의 모든 글자를 표시 할 수 없기 때문에
    CP( CodePage ) 라는 구분을 해 주어, CP에 따라서 다른 글자가 보이도록 한다는거다.


    CodePage는 국가 마다 다르다.
    하나의 index가 있을때 CodePage를 바꾸어 버리면 서로 다른 모양의 글자가 나온다는것이다.

    9A98이라는 index가 있을때
    이것을 CP49 (Korean) 로 인식하고 보면 "슆"이 나온다. 하지만
    이것을 CP932 (Japanese Shift-JIS) 로 인식하고 보면 "口"가 나온다.


    CP949에 있는 글자들은 Unicode 처럼 조합할 수 있도록 제공해 주지 않는다.
    즉, 하나의 index는 완전한 하나의 글자를 이루게 되어 있다.
    ( 반면 유니코드는 초성 "ㄱ", 중성 "ㅏ" 처럼 글자를 조합해서 나타낼 수 있게도 제공해 준다. )


    CP949는 "EUC-KR http://en.wikipedia.org/wiki/EUC-KR#EUC-KR "의 확장판이고
    EUC-KR은 "완성형 한글(KSC5601) http://www.itscj.ipsj.or.jp/ISO-IR/149.pdf + 기타등등문자 "의 확장판이다.
    "완성형한글(KSC5601)" 역시 index가 정해져 있고 완전한 하나의 글자가 배치되어 있지만,
    "똠" 과 같이 표현하지 못하는 문자들이 매우 많았다.
    그래서 CP949는 "완성형한글(KSC5601)"에 더 많은 글자를 추가하게 된 것이다.



    CP949에서는 Unicode에서 제공해주는 글자보다 덜 제공해 주므로,
    CP949에서 나타나지는 글자들은 모두 Unicode로 변환이 가능하나,
    반대로 Unicode에서 제공되는 글자를 CP949에서는 표현하지 못할 수 있다.



    여담.
    사실 CodePage라는 것은 IBM에서 먼저 만들었다.
    그리고 시간이 지난뒤 MS에서 한글을 표현하기 위해서 CP949를 정했는데,
    Java에서는 이를 MS에서 만든것이라서 MS949로 말하게 된다.

    + Recent posts