안드로이드의 타겟은 모바일 환경이기 때문에, 프로그램을 만들거나 실행할때 몇몇 제약이 있다.

이야기 할 내용은 왠만해서는 접할 수 없는 안드로이드 프로그램의 메소드 갯수 제한에 관한 이야기다.

안드로이드 프로그램을 작성하기 위한 순서를 보자.

1. "자바언어"로 프로그램을 작성한다.
2. "자바 컴파일러"로 JavaVM의 Bytecode를 생성해 낸다. 
3. JavaVM의 Bytecode를 DalvikVM의 Bytecode로 변경하면서
4. .dex 파일을 만들어 낸다.
5. .dex 파일과 xml 파일과 각종 이미지들을 zip포맷으로 묶고, 확장자를 apk로 바꾼다.
( 5번 항목은 더 복잡한 내용이 있지만 여기서는 굳이 말할 필요가 없어서 대충. ㅋ )


안드로이드에서는 많은 class 및 jar들도 모두 한꺼번에 묶어서 .dex 파일로 묶어 주게 되어 있는데, 안드로이드 프로그램 배포 파일인  .apk 파일을  .zip 으로 확장자를 변경한 뒤에 압축을 풀어 보면 classes.dex 파일을 볼 수 있는데, 이것이 바로 DalvikVM의 Bytecode가 들어 있는 .dex 파일이다. 모든 class 파일과 jar 파일을 단 1개의 .dex 파일안에 다 넣게 되어 있다. ( 틀렸다면 제보해 주세요. )

이때, 발생 할 수 있는 문제가 바로 메소드 갯수 제한이다.

큰 프로그램을 작성하다 보면, 혹은 많은 양의 라이브러리를 가져다 쓰다 보면 당연히 프로그램의 덩치가 커지게 될 것이다. 이때 빌드를 돌리게 되면 위의  순서에서 2번 class파일이나 jar파일을 만드는데는 문제가 없으나, 3번 과정에서 dex 파일을 만들때 "format == null" 따위의 도저히 예측 할 수 없는 에러를 발생 시키고 죽어 버린다.

모바일프로그램이라 큰 프로그램을 짜지 않을것이라고 생각했는지, 아니면 가난한 리소스때문에 일부러 그렇게 설계했는지 몰라도 dex 파일에는 64k(6.5만개) 이상의  메소드가 정의 될 수 없도록 되어 있다. dex 파일 포맷에서 method index를 저장하는 공간이 16bit로 되어 있으므로 당연히 6.5만개 이상의 메소드를 만들게 되면, method index table의 공간이 부족하여 dex 파일을 못 만드는 것이다.

혹시나 6.5만개 이상의 메소드를 사용하는 프로그램을 만들게 되면 거기에 대한 대비책을 만들어야 한다.

1. 안드로이드에서 기본적으로 제공하는 라이브러리를 쓰자.
2. 프로그램 빌드시 실제로 사용되지 않는 라이브러리들을 제거 하자.
3. 필요 없는 메소드들을 생성하지 말자.
4. 그렇게 큰 프로그램은 모바일 프로그램으로 만들 생각을 하지 말자.


하지만, 프로그램에서 필요 없는 메소드들이 어디 있으랴? 좀 더 구조적이고 아름다운 코드를 짜기 위해서 메소드 갯수가 늘어 나는것은 어쩔 수 없는 일이다. 그러므로 좀 더 우아한 방법을 찾아야 하고, 그 방법이 바로 바로 ProGuard 이다.

ProGuard는 Java Bytecode를 난독화, 사용하지 않는 코드 제거, 최적화 등을 수행해 주는 오픈소스 툴이다. 구글 안드로이드측에서도  2.2 인가 부터 이 놈을 추천하고 있다. 해당 툴을 사용하여 최적화 및 사용하지 않는 코드를 제거하게 되면, 메소드 갯수가 많이 줄어 든다. 단, 최적화를 거쳤다면 메소드가 사라지기도 하고, com.ggaman.A.java 파일의 내용 및 메소드가 com.ggaman.B.java 으로 이동되어 있는 경우도 있다. 
그러므로 실제 출시된 제품의 디버깅이 힘든 점이 존재한다. 물론 옮겨진 위치나 변경된 정보를 로그로 남겨주지만 그거 비교하는 것도 얼마나 짜증나는 일이겠는가? 또한 ProGuard 역시 사람이 많든 프로그램이라 버그가 있어, 제대로 빌드를 만들어 주지 못하거나, 빌드는 되나 실행시 제대로 수행되지 않는 경우도 있다. 

하지만 6.5만개 이상의 메소드를 사용할 수 밖에 없는 경우에는 어쩔 수 없이 선택해야만 한다. 

ProGuard에 대한 설명을 하기 위해서 글을 적은것이 아니기 때문에  ProGuard에 관련된 더 자세한 사항은 아래 링크를 이용해 주시라.

구글의 ProGuard 검색 결과
http://www.google.co.kr/#q=proguard&newwindow=1



또 다른 방법도 있다. 문제 상황에 대해서 한번 더 생각해 보면 프로그램의 덩치가 크기 때문에 dex 파일을 만들때 메소드 갯수가 많아서 dex 파일을 못 만들어 내는 것이다. 그렇다면 dex 파일을 여러개 만들면 안될까?

몇 일 전 안드로이드 개발자 블로그에 이 문제를 해결 할 만한 방법이 올라 왔다.

dex 파일을 쪼개서 만들고, Custom Class Loader를 이용해서 dex 파일에 있는  class를 사용하는 방법이다. Android에서는 dex 파일을 읽기 위해서 DexClassLoader를 제공해 주고 있다. 이를 이용해서 파일로 저장되어 있는 dex파일세서 class를 읽어와서 사용할 수 있음을 보여준 예제이다.

다만 해당 방법은 항상 dex파일을 로컬 스토리지에 두고 사용해야 하므로 최초 수행시 dex파일을 로컬 스토리지로 복사해야 하는 점, 그리고 자바의 리플렉션을 이용해서 Class를 찾고 객체는 생성해주어야 하기 때문에 동작이 느린 이슈가 있을 수 있다. 

하지만 로컬스토리지 복사나 동작이 느린것것이 프로그램이 아예 빌드가 되지 않는 것 보다는 훨씬 더 좋은 선택으로 생각된다.

해당 블로그 글은 아래의 링크를 클릭해서 따라 가면 된다.



오랜만에 포스팅 끝. ㅋ.
(검색이 잘 되도록 제목 수정. ㅋ )
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
  1. 닉쑤 2011.08.03 03:23 신고

    저도 안드로이드 폰 씁니다 ㅎㅎㅎ

    하지만 개발 내용은 알고싶지 않은..ㅎㄷㄷ;

프로그래밍 도중에 이미지를 이용해서 작업을 하는 경우가 있다. 이때 이미지 변환중에서 가장 자주 사용되는 변환은, 이미지 크기 늘리기, 회전하기 등이 있겠지만, 이미지 자체를 변환 시키는 것으로는 GrayScale, 즉 회색화(?)시켜야 하는 경우가 있다.

우리는 프로그래밍할때 어떤색을 분리해 보라고 하면, 빛의 3원색으로 빨간색, 녹색, 파란색, 즉 RGB로 분리하게 된다. HTML에서 색을 표현할때도,  #FF9933(Red=0xFF, Green=0x99, Blue=0x33)등으로 표현하니깐 말이다. 그러므로 최소단위인, RGB를 이용해서 색의 변환을 수행해야 한다. 물론 GrayScale도 이 세가지 색깔을 이용해서 회색으로 만들어 줄 수 있다.


제일 간단한 방법

조금만 생각하면 누구나 떠 올릴 수 있는, 제일 간단한 방식으로는 Red, Green, Blue를 모두 더해서 그 평균값을 이용해서 회색을 만드는 방법이 있다.

gray = ( Red + Green + Blue ) / 3

RGB의 평균값으로 만들어낸 이미지

RGB의 평균값으로 만들어낸 이미지



요렇게 회색화된 이미지를 만들어 냈으니 끝.이라고 생각한다면 오산.


YUV 표현 방법

색의 표현방법은 우리가 일반적으로 사용하고, 알고 있는 RGB 세가지 색깔로 표현하는 방법 이외에 더 많은 방법이 존재한다. 그 중에 하나가 Yxx 색 표현 방법이다. 이때 Yxx는 여러종류가 있다. YPbPr, YCbCr, YIQ등이 있는데, 이것을 그냥 통칭해서 YUV라고 부르도록 하자.

RGB만 있으면 모든색을 표현할 수 있는데, 왜 YUV를 사용할까?

아주 오랜 옛날(?) 이야기가 되겠지만, 이전에는 흑백 텔레비젼을 사용했다. 흑백에서는 색깔이 중요한것이 아니라 오직 밝기만이 중요했다. 말 그대로 흑백이니깐 말이다. 그런데 흑백 텔레비젼만 계속 사용했으면 모르겠지만, 얼마 후 칼라텔레비젼에 나오고 나서 문제가 생겼다. 바로 색깔 정보를 전송해야 한다는것이다. 그러면 색깔 정보를 다 포함한 RGB를 모두 쏴 주면 될것 아닌가? 라고 생각하겠지만, 기존에 흑백 텔레비젼을 사용하던 사람들도 TV는 계속 잘 볼 수 있어야 할 것 아닌가?

그래서 기존에 흑백 TV들이 칼라 정보를 잘못 처리 하지 않도록 밝기 정보는 그대로 유지해야 하는 문제가 생겼다. 즉, 밝기 정보(Y)만을 따로 분리해서 전송하고, 색깔 정보(U,V)는 따로 보내는 방법을 사용하게 되었다. 그렇게해서 생겨난것이 YUV 이다.

그렇다면 YUV중 하나인 YCbCr 은 무슨 말일까?

YCbCr (ITU-R BT.709)

Y = 색의 밝기 정보 ( 기존 흑백 TV를 위한 정보 )
Cb = 색의 밝기 정보에서 파란색의 차이 ( blue-difference chroma compoents )
Cr = 색의 밝기 정보에서 빨간색의 차이 ( red-difference chroma compoents )

밝기정보, Cb, Cr 을 이용하면 실제로 보여주어야 하는 Color을 표현할 수 있다.
( 녹색은 밝기정보, blue, red 정보를 이용하여 계산해 낼 수 있으므로 굳이 전송하지 않는다. )

YPbPr이라는것도 있는데, YPrPb는 아날로그 시스템을 위한 표현방법이고, YCrCb는 디지털(CRT, LCDl, PDP등)을 위해서 따로 만들어둔 표현방법이다. 여기서는 디지털의 색표현방법인 YCrCb에 대해서만 이야기 하도록 하자.

이로써, 색을 표현할때에는 RGB를 이용한 색의 표현 방법 말고 YCrCb를 이용한 색의 표현방법이 있다는것을 알게 되었다. 색의 표현방법이 다르므로 이 표현방법을 이용해서 회색으로 만들어 주는 방법 역시 다르다.

우리가 회색으로 만들고자 하는 이미지는 RGB의 정보를 가지고 있고, 이를 YUV 값으로 변환하게 되면 이때 만들어진 Y 값은 자동적으로 밝기 정보를 가지게 된다. 이 Y 값인 밝기 정보만으로 gray scale 이미지를 만들 수 있다. 그렇다면 Y 값을 구하는 공식을 알아 보도록 하자.

YCrCb
Y = Red * 0.2126 + Geeen * 0.7152 + Blue * 0.0722

YPrPb
Y = Red * 0.299 + Green * 0.587  + Blue * 0.114

우리는 YCrCb만을 볼 것 이므로 각 R, G, B 정보를 이용해서 Y 를 추출해 내고, Y 밝기 값을 이용해서 이미지를 grayscale로 만들어 보면 아래와 같이 나온다. ( 위의 공식에서 RGB에 곱하는 값이 서로 다른 이유는 사람의 눈에 더 민감한 색에 더 많은 가중치를 주어서 계산하기 위해서 위와 같이 복잡한 식이 된다. )

RGB to YCrCb에서 Y값을 이용한 GrayScale Image

RGB to YCrCb에서 Y값을 이용한 GrayScale Image


그렇다면 RGB 평균을 내서 만들어낸 GraySacle이미지와 YCrCb를 이용해서 만들어낸 GrayScale이미지를 서로 비교해 보도록 하자. 나뭇잎 부분의 밝기를 비교해보면 서로 다른 명암을 가지는것을 알 수 있을 것이다.
RGB평균과 YCrCb에서 Y를 이용한 이미지 비교

RGB평균과 YCrCb에서 Y를 이용한 이미지 비교


위와 같이 YCrCb에서 Y 값인 밝기를 이용해서 GrayScale이미지를 만들어 낼 수 있었다.


다른 방법은 없을까?

그렇다면 색을 표현하는 방법은 RGB와 YCrCb밖일까? 당근 그렇지 않다. 그 중에서도 밝기를 따로 나타내고 있는 색 표현방법에는 HSL이나 HSV(혹은 HSB), HSI가 있다.

HSL = Hue, Saturation, Lighness
HSV = Hue, Saturation, Value ( 혹은 HSB = Hue, Saturation, Brightness )
HSI = Hue, Saturation, Intensity

각 마지막에 있는 L, B, V, I 는 모두 밝기 정보를 나타낸다.
이때 RGB를 HSL, HSV(HSB), HSI 로 바꾸는 공식은 모두 다른데, 이때 각 표현 방법중에서 밝기를 계산하는 공식은 다음과 같다.

L = ( Max(R,G,B)  + MIN(R,G,B) ) / 2
V(B) = Max(R, G, B)
I = ( R + G + B ) / 3  (우리가 일반적으로 생각하는 공식)

위에서 보다시피 HSx 방식을 이용하기만 해도 여러가지 공식으로 밝기 값을 처리 할 수 있다.



결론

GrayScale이미지를 만들어 낼때는 별로 생각없이 만들어 내는것이 일반적이다. 혹은 누가 이미 만들어 놓은 그래픽 라이브러리를 잘 가져다가 쓰고, "아~ 뭐 잘 나왔겠지."라고 생각하는것이 일반적이다. GrayScale 이미지를 하나 만들어 낼려고 해도 생각할것이 아주 많다는것을 알 수 있다. 위와 같은 정보를 알지 못하고 그냥 생각없이 만들어진 라이브러리를 가져다 쓰게 되면 내가 원하지 않던 결과를 낼 수도 있기 때문에 잘 생각하고 쓰도록 하자.

당장 급하다면 그냥 가져다 쓴다고 해도, 어느정도 시간이 느긋하다면 "어떻게해서 이렇게 나오는 것일까?"에 대해서 생각해 보는것이 어떨까?


자바에서의 Gray Scale

자바에서도 GrayScale이미지를 만들어 낼 수 있는 방법이 여러가지가 있다. 하지만 내가 원하는 결과를 내기 위해서는 공부가 좀 필요하리라 본다. 자바에서 이미지를 Gray scale로 만드는 방법을 보고 싶다면 아래의 이미지와 소스를 받아서 직접 실행해 보도록 하자.

자바에서 각종 GrayScale 방법

Java의 각종 GrayScale 방법



테스트용 이미지

테스트용 이미지





참고자료

아래의 자료를 참고하면 정리하는데 도움이 될 것이다.
HSL, HSV : http://en.wikipedia.org/wiki/HSL_and_HSV
YCrCb : http://en.wikipedia.org/wiki/YCbCr
RGB, YUV에 대한 설명 : http://cafe.naver.com/camuser/234 - 원본 문서를 못 찾겠음 T_T

==
옛날에 대충 적어 두었던 글을 살짝 정리해서 올림.
적을 글은 많은데.. 언제 다... ;;
저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
  1. 닉쑤 2010.08.02 07:50 신고

    오우 어려워 어려워~
    ㅡㅡ;

    역시 나는 프로그래머는 못되는거임? ㅋ

    • 2010.08.02 08:44 신고

      뭔... 그런걸 가지고... 그냥 하는거지... 궁금하니깐 찾아 보는거고...
      대신 영어 잘하잖아~ ㅎㅎ

  2. 닉쑤 2010.08.02 09:17 신고

    궁금하지 않아요. ㅋㅋ

    과연... 잘 할까요?

    서바이벌 잉글리쉬라는게 있음.. ㅋ

    • 2010.08.02 17:54 신고

      난 서바이벌하지 못할것 같아. ㅎㅎ.

  3. 如旻 2010.08.03 09:59 신고

    아- 저 사진!!
    우.. 오랜만이다.
    너의 역작!
    ㅋㅋㅋㅋㅋㅋㅋㅋ

    • 2010.08.03 10:14 신고

      풉.
      한때 똑딱이로도 저정도는 그냥 찍었는데..

      사진 안 찍은지 몇년. T_T

  4. 학부생 2012.02.07 17:15 신고

    감사합니다 좋은글 잘읽었습니다

  5. 컴퓨터공학도 2012.07.24 22:42 신고

    어휴..학술제 때 어플리케이션을 제작하는데, 영상처리가 필요해서 찾다가
    좋은 글을 보고 가네요 ㅎ
    너무 감사합니다!

    • 2012.07.26 17:04 신고

      영상처리까지야.
      그냥 알고 있는거 정리해서 적은건데요. ㅎㅎ

      방문 감사합니다.

  6. pica 2014.01.14 23:16 신고

    많은 도움이 되었습니다.
    궁금했던 부분이 많이 해결되었네요.
    감사합니다.

  7. 어벙이 2015.08.17 12:46 신고

    퍼갈게요.. 좋은글 보고 갑니다. `^^

여전히 책을 읽고 있다. 정확하게 말하면 요즘에는 거의 못 읽고 있다. ㅠ_ㅠ

  자바 병렬 프로그래밍 - 멀티코어를 100% 활용하는  더그 리 외 지음, 강철구 옮김

아무튼 이 책에는 BlockingQueue에 대한 설명이 잠시 나온다.
이를 이용하면 Producer/Consumer 패턴을 만들기 쉽다고 나와서 직접 코드를 짜 본다.

synchronized block를 이용한 생산자/소비자 패턴은 아래와 같다.
( 간단하게 짜 본거라.. 뭐.. 잘못 되었을 수도 있다. 책임 못짐. ㅎㅎ )

  1. import java.util.ArrayList;  
  2. import java.util.Random;  
  3.  
  4.  
  5. public class PlainProsumer {  
  6.     private static ArrayList<Integer> queue = new ArrayList<Integer>();  
  7.       
  8.     public static void main(String[] args) {  
  9.         Consumer c1 = new Consumer("1", queue); c1.start();  
  10.         Consumer c2 = new Consumer("2", queue); c2.start();  
  11.         Consumer c3 = new Consumer("3", queue); c3.start();  
  12.           
  13.         Producer p1 = new Producer(queue);  p1.start();  
  14.     }  
  15.       
  16.     // 생산자. - 무언가를 열심히 만들어 낸다.  
  17.     static class Producer extends Thread {  
  18.         // INDEX  
  19.         private volatile static int i = 1;  
  20.           
  21.         private ArrayList<Integer> queue;  
  22.           
  23.         public Producer(ArrayList<Integer> queue) {  
  24.             this.queue = queue;  
  25.         }  
  26.           
  27.         public void run() {  
  28.             // 0.5초씩 기다렸다가 데이터를 하나씩 넣어 주자.  
  29.             while(true) {  
  30.                 try {  
  31.                     Thread.sleep(new Random().nextInt(1000));  
  32.                 } catch (InterruptedException e) {  
  33.                     e.printStackTrace();  
  34.                 }  
  35.  
  36.                 synchronized (queue) {  
  37.                     // 데이터를 집어 넣고 나면, 데이터가 들어 갔다고 notify 시켜 줘야 한다.  
  38.                     // 그래야 소비자들 중에서 wait하고 있는 놈들을 깨울 수 있다.  
  39.                     queue.add(i++);  
  40.                     queue.notify();  
  41.                 }  
  42.             }  
  43.         }  
  44.     }  
  45.       
  46.     // 소비자.. 생산해 낸 것을 열심히 사용하자.  
  47.     static class Consumer extends Thread {  
  48.         private ArrayList<Integer> queue;  
  49.         private String name;  
  50.         public Consumer(String name, ArrayList<Integer> queue) {  
  51.             this.name = name;  
  52.             this.queue = queue;  
  53.         }  
  54.           
  55.         public void run() {  
  56.             while ( true ) {  
  57.                 synchronized (queue) {  
  58.                     try {  
  59.                         // 데이터가 들어 있지 않고 비었다면 데이터가 올때까지 기다리자.   
  60.                         if ( queue.isEmpty() ) {  
  61.                                 queue.wait();  
  62.                         }  
  63.                           
  64.                         // 생산자에서 데이터를 집어 넣고 notify해 줘서 wait를 벗어나 아래의 코드가 수행된다.  
  65.                         Integer index = queue.remove(0);  
  66.                         System.err.println("Consumer : " + name + "\tCount : " + index);  
  67.                           
  68.                     } catch (InterruptedException e) {  
  69.                         e.printStackTrace();  
  70.                     }  
  71.                 }  
  72.             }  
  73.         }  
  74.     }  
  75. }  

위의 코드를 확인해 보면 알 수 있다시피, queue를 사용할때 synchronized block를 사용하여 queue에 대한 권한을 획득한 뒤에, notify 및 wait를 해 주어야 한다. 이렇게 하면 괜히 코드가 복잡해 지고 synchronized block를 사용하게 되므로 하나의 block를 더 만들어 주어야 해서 코드에 점차 { } 가 많아져서 코드가 보기 어렵게 된다.

하지만 BlockingQueue를 사용하면 synchronized block를 사용하지 않고도 똑같은 구현을 할 수 있다.

  1. import java.util.Random;  
  2. import java.util.concurrent.ArrayBlockingQueue;  
  3. import java.util.concurrent.BlockingQueue;  
  4.  
  5.  
  6. public class BlockingProsumer {  
  7.     private static BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3);  
  8.       
  9.     public static void main(String[] args) {  
  10.         Consumer c1 = new Consumer("1", queue); c1.start();  
  11.         Consumer c2 = new Consumer("2", queue); c2.start();  
  12.         Consumer c3 = new Consumer("3", queue); c3.start();  
  13.           
  14.         Producer p1 = new Producer(queue);  p1.start();  
  15.     }  
  16.       
  17.     // 생산자. - 무언가를 열심히 만들어 낸다.  
  18.     static class Producer extends Thread {  
  19.         // INDEX  
  20.         private volatile static int i = 1;  
  21.           
  22.         private BlockingQueue<Integer> queue;  
  23.           
  24.         public Producer(BlockingQueue<Integer> queue) {  
  25.             this.queue = queue;  
  26.         }  
  27.           
  28.         public void run() {  
  29.             // 임의의 시간마다 데이터를 넣어 준다.  
  30.             while(true) {  
  31.                 try {  
  32.                     Thread.sleep(new Random().nextInt(500));  
  33.                     // 수정사항 - offer에서 put으로 변경
                       
    // 데이터를 넣고 나면 알아서 notify시켜 준다.
                        queue.put(i++);
                    } catch (InterruptedException e) {  
  34.                     e.printStackTrace();  
  35.                 }  
  36.   
  37.             }  
  38.         }  
  39.     }  
  40.       
  41.       
  42.     // 소비자.. 생산해 낸 것을 열심히 사용하자.  
  43.     static class Consumer extends Thread {  
  44.         private BlockingQueue<Integer> queue;  
  45.         private String name;  
  46.         public Consumer(String name, BlockingQueue<Integer> queue) {  
  47.             this.name = name;  
  48.             this.queue = queue;  
  49.         }  
  50.           
  51.         public void run() {  
  52.             while ( true ) {  
  53.                 try {  
  54.                     // queue에 data가 없으면 알아서 wait하고 있다.  
  55.                     Integer index = queue.take();  
  56.                     System.err.println("Consumer : " + name + "\tIndex : " + index);  
  57.                 } catch (InterruptedException e) {  
  58.                     e.printStackTrace();  
  59.                 }  
  60.             }  
  61.         }  
  62.     }  
  63.       
  64. }  

보다시피 BlockingQueue는 자기가 알아서 wait 상태로 들어 가고 notify를 하게 된다.
이러한 BlockingQueue의 기능을 이용하면 생산자 소비자 패턴을 좀 더 쉽게 만들 수 있다.

BlockingQueue는 대략 아래와 같은 기능을 가지고 있다.

1. queue에 data를 넣을때 가득 차 있으면, queue에 빈칸이 생길때까지 대기
boolean put(E o) throws InterruptedException;
boolean offer(E o)

2. queue에 data를 넣을때 가득 차 있으면, queue에 빈칸이 생길때까지 시간을 두고 대기
boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException;

3. queue에 data가 없을 경우, 데이터가 들어 올때까지 대기
E take() throws InterruptedException;

4. queue에 data가 없을 경우, 데이터가 들어 올때까지 시간을 두고 대기
E poll(long timeout, TimeUnit unit) throws InterruptedException;

사실은 BlockingQueue를 사용해서 생산자/소비자 패턴을 만드는 예제는 이미 BlockingQueue의 API문서에 소개 되고 있다 ^^ ( 즉, 나는 이미 있는 예제를 만든다고 삽질한거다. ㅎㅎ )
http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/BlockingQueue.html


그리고 아래의 링크를 따라 가면 적당한 예제 및 사용법을 볼 수 있다. ( 한글임 )
Core Java Technologies Tech Tips - QUEUE와 DELAYED 프로세싱
http://kr.sun.com/developers/techtips/c2004_1019.html#1
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

책을 한권 읽다가 발견한 내용이 있어 글을 적어 본다. 참고로 읽고 있는 책은 "자바 병렬 프로그래밍 - 멀티코어를 100% 활용하는" 이다. ( 아직 출간 된지 1년이 안된책이라 -_- 할인이 별로 안된다. 쩝... ) 아직은 읽고 있는 중이라, 다음에 다 읽으면 그때 정리해야지. ㅋ.

  자바 병렬 프로그래밍 - 멀티코어를 100% 활용하는  더그 리 외 지음, 강철구 옮김


각설하고 이 책을 읽다 보면, 멀티 스레드에서 버그가 많이 발생할 수 있다는 이야기를 하면서, FindBugs라는 프로그램에 대해서 설명이 아주 짧게 나온다. 이 FindBugs라는 프로그램은 Java 소스에서 잠재적으로 버그가 일어날만 한 부분에 경고를 보여 준다.

직접 프로그램을 설치 할 수도 있지만, 이클립스 플러그인도 있다. 이클립스 플러그인을 설치하는 법을 알아 보고, 어떤식으로 사용되는지 확인해 보도록 하자.

우선 FindBugs( http://findbugs.sourceforge.net/ )의 홈페이지에 접근해서 Download 해 보자.

사용자 삽입 이미지


좌측메뉴에서 Downloads( http://findbugs.sourceforge.net/downloads.html ) 를 클릭하게 되면
standard version과 eclipse plugin을 받을 수 있는 링크를 제공하고 있다. 우리는 이클립스 플러그인을 사용해 볼것인데, 이는 굳이 직접 다운로드 받지 않고, 이클립스에서 플러그인 업데이트를 통해서 설치 하도록 하자.

1. 이클립스를 띄운다.
2. Help -> Software Updates
3. Add Site에 주소를 입력한다. ( http://findbugs.cs.umd.edu/eclipse )
4. FindBugs 플러그인을 선택/설치 한다. ( 설치하는데 속도가 느려서 시간이 좀 걸린다. )

사용자 삽입 이미지

5. 설치한 뒤에는 이클립스를 재시작 한다고 한다.
설치 되었으니, 이제 프로그램을 간단하게 하나 짜 보도록 하자.
간단하게 아래의 코드정도면 어떨까?
( 이 코드에는 과연 어디가 문제가 될만한지 직접 찾아 보는것도 좋다. )

  1. public class BugClass {  
  2.     public static int ZERO = 0;  
  3.       
  4.     int i;  
  5.     int value;  
  6.       
  7.     public int getValue() {  
  8.         return value;  
  9.     }  
  10.       
  11.     public synchronized int setValue(int value) {  
  12.         this.value = value;  
  13.         return value;  
  14.     }  
  15.       
  16.     @Deprecated 
  17.     public boolean getTrue() {  
  18.         return true;  
  19.     }  
  20.       
  21.       
  22.     public static void main(String[] args) {  
  23.         BugClass bc = new BugClass();  
  24.         bc.setValue(bc.ZERO);  
  25.         bc.setValue(10);  
  26.           
  27.     }  
  28. }  

위의 코드는 어떤 문제가 존재할 수 있을까? 이제 FindBugs를 이용해서 이 코드에 잠재적으로 문제를 발생할 수 있는 부분이 어떤것인지 확인해 보자. FindBugs로 해당 프로젝트에서 마우스 오른버튼을 누르고, 아래쪽에 보이는 Find Bugs -> Find Bugs를 누르면 동작한다.

사용자 삽입 이미지


자.. 이제 Find Bugs가 찾아낸 "잠재적인 문제점"을 확인해 보도록 하자. Find Bugs를 누르게 되면 소스코드의 좌측에 벌레가 등장한다. 여기에 마우스를 올리면 어떠한 문제가 있을 수 있는지를 알려 주게 된다.

사용자 삽입 이미지

마우스를 올려서 일일이 볼 수 없지 않은가? 이클립스 하단에 있는 View에서 Problem 탭을 선택하면 어떠한 문제가 생겼는지 바로 볼 수 있도록 만들어 두었다.

사용자 삽입 이미지

글자가 작아서 안 보일텐데, 어떠한 문제가 있냐고 적혀 있는지 대략 살펴보면 아래와 같다.

H V MS: BugClass.ZERO isn't final but should be : FindBugs Problem (High Priority)
ZERO가 final로 선언되지 않았다. (중요)

M M UG: BugClass.getValue() is unsynchronized, BugClass.setValue(int) is synchronized    : FindBugs Problem (Normal Priority)
setValue메소드는 synchronized로 되었으나, getValue메소드는 unsynchronized되어 있다. (보통)

M P UuF: Unused field: BugClass.i : FindBugs Problem (Normal Priority)
i라는 놈을 쓰지도 않는다. (보통)


조금 더 편하게 보라고 FindBugs 이클립스 플러그인은 Perspective를 제공해 준다.
이클립스 메뉴에서 Window -> Open Perspective -> Other... 을 선택하자.
사용자 삽입 이미지


그림에서 보다시피 Find Bugs Perspective가 생긴것을 볼 수 있다. 이를 선택하면 이클립스가 Find Bugs Perspective로 변신을 한다. 이 Find Bugs의 Perspective는 하단에 "Bugs Details"라는 탭이 생긴다. 이 탭에는 현재 문제가 되는곳이 왜 문제가 되며, 그것을 수정하기 위해서는 어떻게 해야 하는가를 설명해 두어서, 문제를 해결하는데 도움을 준다.
사용자 삽입 이미지

버그 없는 프로그램은 있을 수 없다. 그렇기 때문에 안전한 프로그래밍을 해야 한다.
그 안전한 프로그래밍을 도와 줄 수 있는 툴이 바로 FindBugs이다.


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
  1. 나미 2009.01.14 16:56 신고

    훌륭한데...
    자바소스만 되는거지..? ㅡㅜ;
    요새 자바랑 거리먼 1人

    • Chan 2009.01.14 23:01 신고

      ㅋㅋ 내가 알기론~ ㅎㅎ 자바만~ ㅎㅎ

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

최근의 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만 지원을 하지 않고 있는것인가?
    ( 사실은 내가 잘 검색안해 봐서 그럴 수도 있다. - 엉뚱한 클래스를 뒤졌을 수도 있고. )

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

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    이 글도 재수 좋게 1월 첫번째 썬 개발자 뉴스에 실렸습니다. ( 개발자 이야기 부분 )
    단, 제목이 약간 이상하게 적혀 있지요. ( 나고수와 실전파 -(?) 자바 개발자가 많이 쓰는 Email은 어딜까? | 조찬제  ) 그래서 제가 좀 찾아 보았는데, 짱가님께서 쓰신 "[개발자와 커뮤니케이션] 나초보씨의 일화 두번째- 나고수와 실전파" 라는 글을 발견했습니다. 아마도, 짱가님의 글이 올라가야 하는데, 제 글이 잘못 메일에 실렸으리라 생각합니다. 저야 좋지만, 이것으로 인해 짱가님의 좋은글이 가려질것 같아 제 글이 시작하기 전에 먼저 링크를 남겨 둡니다. 해당 글은 "진짜 개발자의 이야기"를 다루고 있으니, 꼭 읽어 보세요 ^^

    ( 많아서 일부만 링크를 겁니다. ^^; )


    얼마전에 발행된 썬 개발자 뉴스레터(12월)에 우연찮게 내 글의 링크가 실렸다.

    [Java/Tip] String.intern()은 메모리를 아낄 수 있다?

    그래서 그런지 메일을 타고 들어온 링크가 급 늘었다.

    이때, 궁금.

    과연 Sun 개발자 뉴스레터를 받는 사람들은 어떤 메일을 가장 많이 쓸까?


    그래서 메일 관련 리퍼러 기록을 확인해 보았다.
    1. 물론 리퍼러라서 정확하지 않은 결과 일 수 있지만, 대략의 상황을 보자는거다.
    2. 다분히 낚시성 제목이라는건 안다. 그래, 낚시 한번 해 봤다.
    3. 60여개 정도의 데이터이니, 당연히 표본오차 +- 99%, 전혀 신뢰할 수 없는 수준이다.
    ( MBC FM, 2시의 데이트를 듣는다면, 박명수 톤으로 꼭 읽어 주길 부탁... ㅎㅎ )


    결과

    포털 및 대형
    google.com : 25회
    naver.com : 16회
    daum.net : 4회
    live.com : 4회
    korea.com : 1회
    yahoo.com : 1회
    paran.com : 1회
    unitel.com : 1회
    hanafos.com : 1회

    사용자 삽입 이미지

    위 결과로 보면, 거의 대부분이 gmail아니면 naver메일을 사용한다.
    Sun의 뉴스레터를 받아보는 사람들 중에서 Naver메일이 많다는것에 대해서 조금 의아스럽다.
    하지만, 뉴스레터를 받아보는 사람들은, 꼭 직장인뿐만 아니라,
    학생들도 많을꺼라고 생각해보면, 충분히 이해되는 수치인것 같다.

    그 다음이 daum과 live.com인데,
    daum은 한메일로 흥한뒤에,  많은 스팸으로 인해서 사용자들이 지쳐서 사용하기를 그만두었는지,
    그렇게 많이 들어오지는 않는다.( 물론 이것도 개발자들이기 때문에 그렇다고 볼 수 있을꺼다. )
    live.com으로 들어온 횟수가 무려 4회나 되는데, 누가 쓰는지 좀 궁금하다. -_-;;;
    live.com 메일을 쓰는 사람이 있기는 있구나 ;;;

    재미난것은 아직도 unitel아이디를 쓰고 있는 사람이 있는가 하면,
    hanafos처럼, ISP의 E-Mail을 사용하는 사람도 있다.
    ISP를 나중에 바꾸면 어쩔려고 그럴까? ( 물론 하나포스 직원이라면 할 말 없다. )



    그리고 아래는 기타 사이트로써 각 1회 들어온 리퍼러이다.
    crinity.com
    hanatour.com
    konkuk.ac.kr
    bs21.net
    naravision.net
    yna.co.kr
    samsung.net

    bs21.net은 부산광역시 홈페이지로 연결되고
    yna.co.kr은, 연합뉴스로 연결된다.
    건국대학교화 하나투어는 뭐 다들 알고 있을꺼고,
    삼성에서 메일을 통해서 들어온 경우도 있다는것을 볼 수 있었다.


    그래 맞다. 다분히 낚시성 제목에, 허접한 결론이다. ㅎ.
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    1. 지민아빠 2008.12.18 11:06 신고

      몇몇개는 안쓰는 메일계정 아닐까요? ^^

      썬 뉴스레터는 스펨용 메일계정으로 받고 있는 1人

      추가로, 하나포스 메일은 ISP와 상관 없다는 사실. (그냥 가입가능, ISP 바뀌어도 안 없어짐)

    2. 효린아빠 2009.01.08 18:22 신고

      어허...내가 hanafos.com를 10년넘게 쓰고 있는디.ㅋ
      옛날에는 hananet.net 이었죠.ㅋ
      저 hanafos.com이 나인가?ㅋㅋㅋ

      • Chan 2009.01.08 19:38 신고

        ㅋㅋ 그럴 수도 있을것 같습니다. ^^

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

    오늘도 기초시리즈.
    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의 대상이 될 가능성이 있겠네요.



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

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    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()) 으로 처리한다면 할 수 있겠지요.

    기초시리즈입니다.
    오늘은 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을 사용하도록 하자.



    --------

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

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    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일 추가 -
    오늘 또 어느 익명의 정의에 사도께서는 "이 딴 글은 내려버려라~" 고 적어 주셔서, 내용을 일부 수정합니다.
     
    어흑. 진짜 글을 내려 버려야 하나.. 쩝ㅋㅋㅋ


    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    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 님의 코멘트를 확인하라는 내용을 추가 했습니다. ^_^

    썬에서 테크 블로그를 모집한다고 한다.

    말이 테크 블로그지.
    그냥 Java나 Sun과 관련된 정보를 모아서 메타 블로그로 활용할것 같다.

    흠, 자바 관련 자료가 많으면 좋을텐데.. ㅎㅎ


    암튼 난 신청. ㅎ.

    신청하려면 아래 링크를..
    http://www.sdnkorea.com/suntechblogger/join

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    1. 쇼니 2008.11.17 19:07 신고

      찬군이 마니 포스팅하면 되자나~~ㅎ

      • Chan 2008.11.17 19:31 신고

        요즘에는 한달에 한건 올리기도 힘든데 말야~ ㅎㅎ

    코드인카페에 적었다고 옮겨 온글. 나중에 필요할것 같아서 검색편하게 하려고. ㅋㅋ
    http://cafe.naver.com/javacircle/29760
    ==============

    검색을 하다가 우연찮게 발견했는데.
    알아 두면 좋으실것 같아 남겨 둡니다.

    일반적으로 Java에서는 Native쪽을 잘 못 가져다 쓰지요.
    ( 웹브라우져, 미디어 플레이어, 플래시 플레이어 등등 .. )

    가져다 쓰려고 하면 고난도 기술이 필요하거나 한데,
    이미 만들어 둔 사람이 있네요.

    http://djproject.sourceforge.net/ns/

    물론 ; Windows 이외에는 잘 될런지는 책임 못 집니다.
    ( 제가 다 살펴 본게 아니라. ㅎㅎ )

    ========================================

    Native Swing Demo Application

    An integrated web browser.

    It is possible to control opening windows and navigation, execute javascript, and a special channel for messages allows to receive custom messages from custom pages.

    An integrated Flash Player.

    This integrated player can be controlled from the Swing application (play, pause, stop, variables)

    An integrated Multimedi Player (using VLC).

    This integrated player can be controlled from the Swing application (play, pause, stop)

    An integrated HTML editor (using FCK Editor).

    This integrated editor can be controlled from the Swing application (get/set HTML, save event notifications)

    An option allows to mix heavyweight and lightweight components while respecting their Z-order.

    Another option defers destruction until garbage collection or explicit disposal, to permit the removal and later re-addition of a native component to the interface.

    Here is another example of a component mix, in the form of a JDesktopPane.

    Note the artificial addition of a Swing button on top of a Web Browser.

    File associations utility.

    It is possible to query registered applications for file types, and to launch files with their associated program.

    ===============================================

    테스트 해 보시려면 아래 링크를 누르면 webstart로 실행 됩니다.
    여러개의 Native Object를 Java Frame안에서 겹쳐 놓을 수도 있고, 좋은것 같네요 ^_^

    Webstart

    Java Webstart

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    어제.. 하루 종일 집에서 뒹굴다가..
    이게 뭐하는 짓인가 싶어서, 오늘 도서관에 갔습니다.

    몇일전 팀장님께서 재미 있을거라면서 읽어 보라고 주신 책이 바로,
    "뉴욕의 프로그래머" 이지요..

      뉴욕의 프로그래머  임백준 지음
    뉴욕 월스트리트 금융회사에서 근무하는 프로그래머들의 이야기를 소설 형식으로 그렸다. 십수 명의 등장인물들이 저마다 독특한 개성과 프로그래밍 실력으로 만들어가는 이야기를 통해 프로그래머들의 창조적이고 예술적인 노동의 가치를 엿볼 수 있다.


    "프로그램은 이러한 방법으로 짜야한다." 라는 어려운 책이 아닙니다.
    그냥 뉴욕에 있는 프로그래머의 일을 다루는 소설책이죠 ^_^

    한국에서 건너간 "영우"라는 주인공(당연히 프로그래머)이 나오고
    그의 직업인 주식거래프로그램을 다루는 회사에서,
    일어 나는 일을 바탕으로 하고 있습니다.

    이것을 읽어 보면, 참으로 실무와 관련되는 내용이 많이 나옵니다.

    1. 인텔리j를 IDE로 쓰고
    2. Swing 으로 어플리케이션을 개발하고
    3. Swing과 관련되는 Event Dispatch Thread ( EDT ) 내용도 나오고
    4. 테스트를 위한 JUnit 도 나오고
    5. 실제 데이터처리와 UI를 구분하라는 이야기도 나오고,
    6. Java의 컨커런트패키지에 관련된 내용도 나오고
    7. 버그는 어떤것은 지금 당장 고쳐야 하고 어떤것은 나중에 고쳐야 하는지..

    등등 많이 등장합니다. ^_^


    Java라는 언어는 무시하고라도, 프로그램 회사에서 나오는 일반적인 체계라던지 등은,
    프로그래머를 꿈꾸는 초보자분들이나 대학생분들은 꼭 읽어 보았으면 하는 바람입니다.

    Java를 실무를 다루시는 분은 그냥 재미삼아 읽어 볼만 하구요.
    ( 특히 Client단이 아니라, Server단 분들은.. ^_^ )
    ( 제가 다니는 회사가 국내에 몇안되는 Java Client Application 개발 회사라 ;; ㅋ )
    ( 우리회사에 들어온 신입이라면 반드시 읽어 보라고 하고 싶은 책이네요 ^_^ )
    ( 사실 저도 이 책을 읽으면서 몇가지 느낀것이. ㅋㅋㅋㅋㅋㅋㅋ )

    다시 한번 말하지만, 이 책에는

    "프로그램은 이러한 방법으로 짜야 한다." 라고 적혀있지는 않습니다.

    하지만, 책을 다 읽고 나면
    "프로그램은 이러한 방법으로 짜야 하는구나~" 라고 느낄 수 있을것입니다.
    책의 절반이상이 버그와, 그 버그를 처리하는것에 대한 이야기가 나오거든요 ^_^

    절대 어려운 책이 아닙니다.
    가벼운 소설책 읽듯이 한번 읽어 보세요 ^_^

    자기가 프로그래머라면, 혹은 프로그래머를 꿈꾼다면,
    이 책을 지루하지 않게 읽을 수 있을것입니다. ^_^

    물론, 진짜 실무에 있으신 분들은, 이깟것이라고 생각할 수도 있겠죠 ^_^

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    웹브라우져에서 OpenOffice를 돌릴 수 있다는 엄청난 뉴스를 보았다.
    ( 울테오, 웹브라우저에 오픈오피스 도입 - http://www.zdnet.co.kr/news/internet/browser/0,39031243,39164171,00.htm )
    ( 원문 : http://www.news.com/8301-10784_3-9832336-7.html?tag=nefd.top )


    그래서 직접 찾아서 들어가봤다.
    Ulteo - http://www.ulteo.com/home/en/home?autolang=en

    사용자 삽입 이미지

    오옹~ 딱 가면 main페이지에 New로 오픈오피스를 웹브라우져에서 띄울 수 있다고 한다.
    좋다. 그럼 한번 해 보자.

    간단한 가입절차를 거치고나면 ( 이메일로 가입인증을 거친다. )
    로그인을 하고 다시 접속을 해 본다.
    ( http://www.ulteo.com/home/en/ooo?autolang=en )

    사용자 삽입 이미지

    어랏.. 애플릿이 없는데.. Java Console이 뜬다...
    저기 위에 보이는 초록색 Launch OpenOffice.org NOW! 이.. 자바애플릿으로 만들어져 있다.

    위에 있는 영어를 대략 보면..
    0. Sun VM을 반드시 써라.
    1. Chan 잘 왔다.
    2. 어떤거 띄울래?
    3. 실행 시킬라면 버튼 눌러.

    뭐 이런식으로 되어 있는데.
    Launch OpenOffice.org NOW! 이놈이 각종 jar를 다운로드 받는다.
    그중에 보면
    http://applet.ulteo.com/SSHVncApplet-0.2.9.4-3-signed.jar 요런 놈과
    http://applet.ulteo.com/SSHVncApplet-0.2.9.4-3-jdkbug-workaround-signed.jar 
    요런놈을 다운로드 받는다. jdk에 버그가 있어서 workaround 한것 같다.
    아마도 sun vm에서만 발생하는 버그라서 -_- 저렇게 sun VM을 걍 쓰라고 강제한것일까?

    암튼.. Launch를 시켜 보자.

    사용자 삽입 이미지

    오우.. 뜬다. 뜬다.... 신기하다~ 와..
    오픈오피스가.. 웹브라우져 안에서 쓰는구나~ 이야~
    신기하다.. 오우.. 이제 안되는 게 없구나~ 오홍~




    근데 -_- 좀 이상하다 -_-

    1. 웹브라우져 안에 있는데 타이틀 바가 있네~
    2. UI가 내가 쓰는 Window UI가 아니네~
    3. 마우스 아이콘은 왜 저렇게 못 생겼지??
    4. 메뉴가 뜨는데 왜 이렇게 시간이 많이 걸릴까?
    5. 네트웍 IO가 계속 일어나네~

    허억!!!!
    결정적으로 -_- 위에서 받은 jar 화일들을 다시 확인해 보니깐..
    화일명이 글쎄 ;;

    SSH     Vnc     Applet-0.2.9.4-3-signed.jar


    SSH     Vnc     Applet-0.2.9.4-3-jdkbug-workaround-signed.jar 


    이뭐병;;;;

    SSH로 VNC 접근해서 -_- 화면을 업데이트 해주는것을 가지고 -_-
    웹브라우져에서 OpenOffice를 돌린다고 이야기 하다니 ;;;


    화일도 온라인에 있는 문서만 열 수 있다.
    온라인에 화일을 올리려면 별도의 UI를 통해서 올릴 수 있다.

    사용자 삽입 이미지
    윗쪽 부분(JavaScript)은 온라인의 경로이고.. 보다시피 -_- .kde라던지 .qt , .gconf 등을 봐서 -_-
    걍 -_- 어디 서버에 있는 폴더 목록을 좌라락 긁어와서 보여주는 거고,

    아랫쪽(JavaApplet)은 로컬측의 화일을 선택해서 화일을 업로드 할 수 있도록 되어 있다.
    와방 불편하다.



    요런거 만들어 놓고.. ZDNet에서는 다음과 같이 쓰여져 있다.

    Ulteo, a company staffed by Linux veterans, on Wednesday launched the test version of a service that lets people run the OpenOffice.org desktop suite in the Firefox or Internet Explorer browsers.
    리눅스 베테랑들이 근무하는 회사 울테오(Ulteo)가 12일(미국시간) 파이어폭스나 인터넷 익스플로러 브라우저에 오픈오피스 데스크톱 스위트를 실행할 수 있는 서비스 테스트 버전을 출시했다.

    The service is designed to let people collaborate with OpenOffice documents online and use the open-source application suite without having to download it.
    이 서비스는 오픈오피스 문서와 온라인에서의 협력과 오픈소스 애플리케이션 스위트를 다운로드하지 않고 이용하기 위한 것이다.

    와.. 거짓말은 아니다. ; 진실만을 이야기하고 있다.

    "다운로드 하지 않고 이용하기 위한것" 이라는것은 정말로 거짓말이 아니다.
    진짜로 -_- OpenOffice를 다운로드 하지는 않는다. -_-

    원격 제어를 할 수 있는 VNC 만을 다운 받았을 뿐이다. -_-;;;;


    물론 VNC가 중요한게 아닐 수도 있다.
    서버측에서는 실제 UI를 띄우지 않고, 클라이언트측으로 UI 정보를 던져주게 하던지,
    가상적인 VM을 만들어서 Online Desktop을 만들던지 하는
    엄청난 획기적이고 정말로 "베타랑"만 할 수 있는 그런 일을 했는지도 모른다.


    하지만 -_- 결과적으로는
    VNC 로 -_- OpenOffice가 동작하는것을 원격 조정하는것 뿐이 더 되는가 -_-??


    좀, 너무했다. 싶다.
    걍, 자신들이 만들어 놓은 가상 Desktop에서
    그나마 유명한 OpenOffice를 돌리는것을 보여주고,
    시선을 좀 끌어 보려는 것으로 밖에 안 보인다.

    에효 -_- 완전 안습 ;;


    그래도 아직 웹오피스는 ThinkFree ( http://thinkfree.com ) 가 좋은것 같다.
    ( 프리미엄 에디션 - http://product.thinkfree.com/premium/ 이 나오면서 더 좋아졌다. 완전 강추 ㅋ )
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    1. 유겸애비 2007.12.13 07:18 신고

      프리미엄 우왕ㅋ굳ㅋ

    2. 랜덤여신 2007.12.16 05:02 신고

      글 처음 부분을 읽으면서는 오픈오피스를 자바 애플릿으로 구현한 것이 아닌가 싶어서 기대했는데, 정말 굉장하네요. 웃고 갑니다.

    선, 나스닥 거래명 ‘자바’로 변경

     선마이크로시스템스가 27일(현지시각)부터 나스닥(NASDAQ) 주식시장에서 통용되는 회사명칭(티커 ticker)을 ‘SUNW’에서 ‘자바(JAVA)’로 바꾼다. 선이 1986년 나스닥에 상장한 지 21년 만이다.

     자바는 선이 개발한 대표적인 컴퓨터 프로그래밍언어. 이 회사의 자체 집계에 따르면 PC와 휴대폰, 게임기 등 전 세계적으로 55억개 정보통신기기에 자바가 사용되고 있으며 자바 SW 개발자도 600만명이 넘는다.

     조너선 슈워츠 선 CEO는 자신의 블로그에서 주식거래명을 변경하기로 한 배경에 대해 “선을 아는 사람보다 자바를 아는 사람이 더 많기 때문”이라고 설명했다. 그러나 선마이크로시스템스라는 회사 공식명칭을 바꾸는 것은 절대 아니라고 슈워츠 CEO는 강조했다.

     뉴욕증권거래소나 나스닥에 상장한 회사들이 거래명을 바꾸는 경우는 간혹 있지만 이는 대부분 M&A 등으로 회사 정체성이 크게 달라졌을 때다. HP도 2002년 컴팩을 인수한 후 거래명을 ‘HWP’에서 ‘HPQ’로 바꾼 바 있다.

     조윤아기자@전자신문, forange@

    ○ [전자신문] 2007/08/26  17:18

    =======================

    바꾸었군아 -_-
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    다음의 내용을 가지고 테스트를 해 보자.

    1. public class A {
    2.     public String getString() {
    3.         return "call A.getString() Method";
    4.     }
    5. }

    1. public class B extends A {
    2.     public String getString() {
    3.         return "call B.getString() Method";
    4.     }
    5. }

    1. public class C {
    2.     public static void main(String... strs) {
    3.         B b = new B();
    4.         System.out.println ( b.getString() ) ;
    5.     }
    6. }


    이렇게 만든 상태에서 모두 컴파일을 하고, C 화일을 실행 시키면  "call B.getString() Method" 가 나온다.


     그 후에 B.java 화일은 그대로 두고 A 클래스에 메소드를 하나 추가 하여 다시 작성하자.

    1. public class A {
    2.     public String getString() {
    3.         return "call A.getString() Method";
    4.     }
    5.     // 아래의 메소드를 추가 한다.
    6.     public String getString2() {

              return "call A.getString2() Method";

          }  

    7. }


    그리고 C 화일을 수행 부분인 main 메소드를 다음과 같이 변경 시킨다.

    1. public class C {
    2.     public static void main(String... strs) {
    3.         B b = new B();
    4.         System.out.println( b.getString2() );
    5.     }
    6. }


    그리고 A 와 C 만을 컴파일 하고 ( B 는 꼭 그대로 둔다. ), C 클래스를 수행하면 어떤 결과가 날까?

    ( B class 는 아직까지 컴파일 되지 않았고, B의 부모인 A 만 새로운 메소드를 추가 하고 새롭게 컴파일 되었다. )


    결과는 다음과 같다. call A.getString2() Method


    B클래스는 전혀 새롭게 컴파일 하지 않았는데, 어떻게 A클래스에 있는 메소드를 알아서 호출해 주는것일까?


    물론 저렇게 만들지 않으면 정말로 불편하게 된다. 상위클래스에서 자식으로 넘겨 줄 수 있는 제한자를 가지고 있는 것들을 추가 할때마다 자식클래스들도 모두 다시 컴파일 해야 한다면 얼마나 큰 낭비인가? 하지만 그렇게 상식적으로 생각하지 말자. 잘못된 상식은 수두룩하니깐.


    그렇게 해서 내가 테스트 해 본 방법은 다음과 같다.


    • 직접 수행 해 보기. - 이미 위의 방법을 통해서 확인해 보았다.
    • 원래의 B.class 화일과 새롭게 컴파일 된 B.class 화일을 바이너리 비교
      • 비교해 보았으나 두개의 바이너리 내용이 똑 같음.


    이렇게 한다고 해도, 이것은 테스트 밖에 되지 않는다. 우연찮게 두개의 메소드 테이블이 같아서 똑같은 바이너라가 나올 수도 있다고 생각 할 수도 있다. 그러므로 좀 더 정확한 답을 찾아 보도록 하자.


    .class 화일 Format 를 직접 찾아 보자.

     The class File Format : http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html


     class 화일은 다음과 같은 구조로 이루어져 있다.

        ClassFile {
         u4 magic;
         u2 minor_version;
         u2 major_version;
         u2 constant_pool_count;
         cp_info constant_pool[constant_pool_count-1];
         u2 access_flags;
         u2 this_class;
         u2 super_class;
         u2 interfaces_count;
         u2 interfaces[interfaces_count];
         u2 fields_count;
         field_info fields[fields_count];
        u2 methods_count;
       
    method_info methods[methods_count];
         u2 attributes_count;
         attribute_info attributes[attributes_count];
        }


    그 중에서 우리가 관심이 있는 부분은 메소드이고, 그것과 관련 있는 부분은 아래의 두개이다.

    u2 method_count;

    method_info methods[methods_count];


    우선 method_count 에 대한 설명을 보자.

    methods_count
    The value of the methods_count item gives the number of method_info structures in the methods table.

    methods_count 는 methods 테이블에 있는 method_info 갯수란다. - 별로 중요하지 않군. 흠.


    methods[]
    Each value in the methods table must be a method_info (§4.6) structure giving a complete description of a method in this class or interface. If the method is not native or abstract, the Java virtual machine instructions implementing the method are also supplied.

    The method_info structures represent all methods declared by this class or interface type, including instance methods, class (static) methods, instance initialization methods (§3.9), and any class or interface initialization method (§3.9). The methods table does not include items representing methods that are inherited from superclasses or superinterfaces.


    우리가 원하는 내용을 찾아 버렸다. .class 화일에서는 methods[] 이 있고, 이 안에는 method_info가 들어 있는데, 이 method_info에는, 현재 클래스나 인터페이스에서 선언된 모든 메소드들이 들어 있다고 한다. 즉, 부모에 선언되어 있거나 하는 메소드들은 현재의 .class 화일에서 가지고 있지 않다는 말이 된다.


    == 추가한 부분 ==
    B.class 화일에서는 자기에게서 선언되지 않은, getString2() 라는 메소드 정보를 class 화일 안에 가지고 있지 않다.
    ( 왜냐하면 위에서 말했다 시피 .class 화일 안에는 자신에게서 선언된 메소드 정보만을 가지고 있어야 하기 때문이다. )

    C 클래스에서 B 객체의 getString2() 메소드를 호출하게 되면,
    B 객체에서는 자신의 메소드테이블을 검색해서 getString2() 가 있는지 확인하게 된다.

    하지만 B 객체에서는 getString2() 이라는 메소드가 존재하지 않기 때문에,
    부모인 A 객체에게 getString2() 을 요청하게 될 것이다.

    자식 클래스인 B에서 선언되지 않았지만, 부모 클래스인 A 에서 메소드를 추가하여도,
    C 클래스에서 A에서만 추가된 메소드를 B 에게 호출 하여도 문제가 없게 된다.

    즉, B 클래스는 전혀 수정없이 확장성 있게 코드를 작성할 수 있게 된다.



    그렇기 때문에,  위의 예에서 B.java 화일은 새롭게 컴파일 하지 않아도 정상적으로 동작하는게 옳다는 결론이 나온다.

    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    참여형 공부 블로그 : StudyLang.com
    출처 : http://www.studylang.com/5

    Java coffee 자바커피

    From Wikipedia, the free encyclopedia 무료백과사전 위키피디아

    Java coffee is a coffee produced on the island of Java. In the United States, the term "Java" by itself is slang for coffee generally. The Indonesian phrase Kopi Jawa refers not only to the origin of the coffee, but is used to distinguish the strong, black, very sweet coffee, with powdered grains in the drink, from other forms of the drink.
    자바커피는 자바섬에서 재배된 커피이다. 미국에서 "Java"라는 말은 일반적으로 커피를 뜻하는 "비속어"이다. 인도네시아어 "Kopi Jawa" 라는 단어는 원래 커피뿐만 아니라, 서로 다른 종류인 단단하거나, 검거나, 매우 단 커피, 가루가 뿌려진 커피를 구분짓는다.

    The Dutch began cultivation of coffee trees on Java (part of the Dutch East Indies) in the 17th century and it has been exported globally since. The coffee agricultural systems found on Java have changed considerably over time. A rust plague in the late 1880s killed off much of the plantation stocks in Sukabumi, before spreading to Central Java and parts of East Java. The Dutch responded by replacing the Arabica firstly with Liberica (a tough, but somewhat unpalatable coffee) and later with Robusta. Today Java's old colonial era plantations provide just a fraction of the coffee grown on the island.
    네덜란드는 17세기에 커피나무를 자바섬(네덜란드령 동인도제도 일부)에 경작을 시작했고, 세계적으로 수출하고 있다.  자바섬의 커피 농업 시스템은 한동안 상당하게 변화했다. 1980년말의 전염병인 녹병은 Central Java와 East Java의 일부에 옮겨지기 전에 Sukabumi에 있는 재배지의 나무줄기를 많이 고사시켰다.  네덜란드는 첫번째로 Arabica 와 함께 Liverica(거칠고 다소 맛 없는 커피)로, 후에는 Robusta로 교체는것으로 대응했다. 오늘날 자바의 오랜 식민 시대 농원은 섬에서 자란 일부 커피만을 제공한다.


    ======

    주요단어
    rust : 녹, 녹슬다, 녹병.
    plantation : 열대지방의 대규모 농원, 재배지, 식림지. 조림지.
    cultivation : 경작, 양성, 재배, 양식
    plague : 전염병, 천벌, 괴롭히다 - 플라그.
    Dutch : 네덜란드
    unpalatable : 입맛에 맞지 않는, 맛없는, 불쾌한
    colonial : 식민의, 식민지 풍의
    fraction : 파편, 단편, 조금, 소부분

    위키피디아의 영어를 한글로 번역한 것입니다.
    제가 영어를 정말로 못하기 때문에 많이 틀릴 수 있습니다.
    잘못 된 점이 있으면 언제든지 지적해 주십시오.
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    '공부 > 영어' 카테고리의 다른 글

    [영한번역용] 한명숙 전 한국 총리, 대선 출마 선언  (0) 2007.06.20
    Tabletote  (0) 2007.06.19
    Java Coffee  (0) 2007.06.17
    Homemade chocolates  (3) 2007.06.15
    Moore attacks US government  (4) 2007.06.14
    What Men Live By # 1  (4) 2007.06.13

    오랜만에 공부하는 포스팅 ㅎㅎ..

    Windows에서 Java의 FileOutputStream을 사용하게 되면,
    다음과 같은 네이티브 코드를 사용하게 된다.


    void
    fileOpen(JNIEnv *env, jobject this, jstring path, jfieldID fid, int flags)
    {
      DWORD access = 0;
      DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
      DWORD disposition = OPEN_EXISTING;
      DWORD flagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
      HANDLE h = NULL;
      int pathlen = 0;

      /* Note: O_TRUNC overrides O_CREAT */
      if (flags & O_TRUNC)        {    disposition = CREATE_ALWAYS;     }
      else if (flags & O_CREAT) {    disposition = OPEN_ALWAYS;         }

      if (flags & O_SYNC) {    flagsAndAttributes = FILE_FLAG_WRITE_THROUGH;    }

      if (flags & O_DSYNC) {  flagsAndAttributes = FILE_FLAG_WRITE_THROUGH;    }

      if (flags & O_RDONLY) { access = GENERIC_READ;   }

      if (flags & O_WRONLY) { access = GENERIC_WRITE;  }

      if (flags & O_RDWR)     { access = GENERIC_READ | GENERIC_WRITE;   }

      if (flags == 0)              {  access = GENERIC_READ;  }

      if (onNT) {
           WCHAR *pathbuf = pathToNTPath(env, path, JNI_TRUE);
           if (pathbuf == NULL) {
                  /* Exception already pending */
                 return;
           }

           h = CreateFileW(
               pathbuf,   /* Wide char path name */
               access,    /* Combine read and/or write permission */
               sharing,   /* File sharing flags */
               NULL,      /* Security attributes */
               disposition,         /* creation disposition */
               flagsAndAttributes,  /* flags and attributes */
               NULL);

           free(pathbuf);

      } else {

           WITH_PLATFORM_STRING(env, path, _ps) {
                 h = CreateFile(_ps, access, sharing, NULL, disposition,
                                       flagsAndAttributes, NULL);
           } END_PLATFORM_STRING(env, _ps);
    }

    if (h == INVALID_HANDLE_VALUE) {
           int error = GetLastError();
           if (error == ERROR_TOO_MANY_OPEN_FILES) {
                 JNU_ThrowByName(env, JNU_JAVAIOPKG "IOException",
                                   "Too many open files");
                return;
           }
           throwFileNotFoundException(env, path);
           return;
      }
      SET_FD(this, (jlong)h, fid);
    }


    위의 코드는 그리 어렵지 않다. 각 속성을 조합한 뒤에 결국
    Windows 에서 제공해주는 API인 CreateFile을 호출하게 된다.


    하지만 이 옵션들 중에서 조정이 불가능한 옵션이 있으니. 이 옵션은.

      DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE;

    이다.


    이 공유 속성은 내가 지금 쓰려고 하는 화일을
    다른 프로세스에서 접근하면 어떤 동작이 가능하게 할것인가를 나타내주는 속성이다.


    자바에서는 FileOutputStream을 만들거나 혹은  RandomAccessFile을 생성하게 되면,
    fileOpen(...) 함수를 사용하게 된다.
    결국 createFile을 쓰게 되는데, 이때 다른곳에서 접근하는 화일들에게도 쓰기 권한을 준 상태로 핸들을 만들게 된다.


    그러므로, 자바에서 FileOutputStream을 만들고, 쓰고 있는 도중에,
    다른 프로그램에서 해당 화일에 대해서 쓰기 권한을 가지고 화일을 열게 되면..
    (  DWORD access = GENERIC_WRITE; )
    화일이 깨질 수 있는 문제가 언제든지 발생할 수 있다.


    그러므로 FileOutputStream을 사용하여 화일을 쓸려고 할때에는,
    되도록이면 Channel을 구한뒤에 lock()을 건 뒤에 사용해주는것이 좋다.



    자바에서는  shared Lock 과 exclusive Lock을 제공하는데
    ( 정확하게 이야기 하면 os 에서 제공하는것이지만 ),


    자바에서 exclusive Lock를 잡고 있다면,

    다른 프로세스에서 CreateFile을 사용할때 공유 속성에 WRITE가 없다면
    핸들을 만드는것 조차 실패한다.

    공유속성에 WRITE가 있으면 핸들을 만드는것은 성공한다.
    하지만 exclusive Lock를 잡고 있기 때문에 WriteFile(..)등의 함수를 사용하여
    화일에 데이터를 쓰려고 하면 실패하게 된다.



    자바에서 shared Lock를 잡았을때에도
    다른 프로세스에서 해당 화일에 데이터를 쓸려고 열어도
    (  DWORD access = GENERIC_WRITE; )
    화일 핸들은 잘 만들어지나 WriteFile 에서 데이터를 쓰려고 할때 실패하게 된다.



    그러므로, 화일을 쓸때에는 lock를 잡고 쓰는것이 매우 안전하다고 볼 수 있다.


    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License
    1. 유야 2007.01.12 10:04 신고

      내가 광고 클릭해줬으니까 돈벌어서 맛난거사주삼 ㅋㅋ

      • Chan 2007.01.12 10:14 신고

        ㅎㅎ 감사 하삼. 자네가 첫번째 클릭자일세. ㅋㅋ

    2. 타오 2007.01.12 11:10 신고

      오홍~ 오홍~ +_+

    3. 유겸애비 2007.01.14 16:36 신고

      저도 클릭했어여. 이제 좀 쉬고 싶네요^^

    + Recent posts