본문 바로가기
코스웨어/11년 내장형하드웨어

[내장형]김수만_2011년7월27일_일일보고서(선생님의 채팅프로그램 소스코드(chat_server.c, chat_client.c)의 전체적인 흐름과 분석, 네트워크프로젝트 계획), JAVA String Class, toString(), charAt(), equals(), substring() 메소드를 사용한 예제4개

by 알 수 없는 사용자 2011. 7. 27.
728x90
반응형




솔로들을 위한 조금 슬픈 채팅프로그램... 구글번역을 써서 이상한 문장이 되어 버렸다. (ㅠㅠ)
A부터 Z까지 처음부터 코딩하려면 막막하니 일단 선생님께서 하사하신 소스코드를 실행, 분석한 뒤에 아이디어가 나오면..
그 부분에 대해 기능을 추가하고 문제를 수정하는 방식으로 프로젝트를 진행할 예정임.




char_server.c와 char_client.c 소스코드 분석


● chat_server.c

   1: // chat_server.c
   2:  
   3: #include <stdio.h>
   4: #include <fcntl.h>
   5: #include <stdlib.h>
   6: #include <signal.h>
   7: #include <sys/socket.h>
   8: #include <sys/file.h>
   9: #include <netinet/in.h>
  10: #include <string.h>
  11:  
  12: #define    MAXLINE        512
  13: #define    MAX_SOCK    64
  14:  
  15: int getmax(int);
  16: void removeClient(int);            //채팅 탈퇴 처리함수
  17:  
  18: char *escapechar = "exit";
  19: int max_fd1;                    //최대 소켓번호 +1
  20: int num_chat = 0;                //채팅 참가자 수
  21: int client_s[MAX_SOCK];            //채팅 참가자 소켓번호 목록
  22:  
  23:  
  24: int main(int argc, char *argv[])
  25: {
  26:     char rline[MAXLINE], my_msg[MAXLINE];
  27:     char *start = "Connected to chat_server \n";
  28:     int i, j, n;
  29:     int s;
  30:     int client_fd, client_len;
  31:  
  32:     fd_set read_fds;
  33:     struct sockaddr_in client_addr, server_addr;
  34:  
  35:     if(argc != 2)
  36:     {
  37:         fprintf(stderr, "사용법: %s Port \n", argv[0]);
  38:         exit(0);
  39:     }
  40:  
  41:     if((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  42:     {
  43:         fprintf(stderr, "Server: Can't open stream socket.\n");
  44:         exit(0);
  45:     }
  46:  
  47:     bzero((char *)&server_addr, sizeof(server_addr));
  48:     server_addr.sin_family = AF_INET;
  49:     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  50:     server_addr.sin_port = htons(atoi(argv[1]));
  51:  
  52:     if(bind(s, (struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
  53:     {
  54:         fprintf(stderr, "Server: Can't bind local address.\n");
  55:         exit(0);
  56:     }
  57:  
  58:     listen(s, 5);
  59:  
  60:     max_fd1 = s + 1;            //최대 소켓번호 + 1
  61:  
  62:     while(1)
  63:     {
  64:         FD_ZERO(&read_fds);
  65:         FD_SET(s, &read_fds);
  66:  
  67:         for(i = 0 ; i < num_chat ; i++)
  68:             FD_SET(client_s[i], &read_fds);
  69:  
  70:         max_fd1 = getmax(s) + 1;        //max_fd1 재계산
  71:         if(select(max_fd1, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0)
  72:         {
  73:             fprintf(stderr, "select error <= 0\n");
  74:             exit(0);
  75:         }
  76:  
  77:         if(FD_ISSET(s, &read_fds))
  78:         {
  79:             client_len = sizeof(client_addr);
  80:             client_fd = accept(s, (struct sockaddr *)&client_addr, &client_len);
  81:             if(-1 == client_fd)
  82:             {
  83:                 fprintf(stderr, "accept error\n");
  84:                 exit(0);
  85:             }
  86:  
  87:             // 채팅 클라이언트 목록에 추가
  88:             client_s[num_chat] = client_fd;
  89:             num_chat++;
  90:             send(client_fd, start, strlen(start), 0);
  91:             printf("%d번째 사용자 추가. \n", num_chat);
  92:         }
  93:  
  94:         //클라이언트가 보낸 메시지를 모든 클라이언트에게 방송
  95:         for(i = 0 ; i < num_chat ; i++)
  96:         {
  97:             if(FD_ISSET(client_s[i], &read_fds))
  98:             {
  99:                 if((n = recv(client_s[i], rline, MAXLINE, 0)) <= 0)
 100:                 {
 101:                     removeClient(i);
 102:                     continue;
 103:                 }
 104:  
 105:                 if(strstr(rline, escapechar) != NULL)    //종료문자 처리
 106:                 {
 107:                     removeClient(i);
 108:                     continue;
 109:                 }
 110:  
 111:                 //모든 채팅 참가자에게 메시지 방송
 112:                 rline[n] = '\0';
 113:                 for(j = 0 ; j < num_chat ; j++)
 114:                     send(client_s[j], rline, n, 0);
 115:  
 116:                 printf("%s", rline);
 117:             }
 118:         }
 119:     }
 120:         
 121:     return 0;
 122: }
 123:  
 124: //채팅 탈퇴 처리
 125: void removeClient(int i)
 126: {
 127:     close(client_s[i]);
 128:  
 129:     if(i != num_chat - 1)
 130:         client_s[i] = client_s[num_chat - 1];
 131:  
 132:     num_chat--;
 133:     printf("채팅 참가자 1명 탈퇴. 현 참가자 수 = %d \n", num_chat);
 134: }
 135:  
 136: //client_s[]내의 최대 소켓번호 얻기
 137: int getmax(int k)
 138: {
 139:     int max = k;
 140:     int r;
 141:  
 142:     for(r = 0 ; r < num_chat ; r++)
 143:     {
 144:         if(client_s[r] > max)
 145:             max = client_s[r];
 146:     }
 147:  
 148:     return max;
 149: }


● chat_client.c

   1: // chat_client.c
   2:  
   3: #include <stdio.h>
   4: #include <fcntl.h>
   5: #include <stdlib.h>
   6: #include <signal.h>
   7: #include <sys/socket.h>
   8: #include <sys/file.h>
   9: #include <netinet/in.h>
  10: #include <string.h>
  11:  
  12: #define    MAXLINE        512
  13: #define    MAX_SOCK    128
  14:  
  15: char *escapechar = "exit";
  16: char name[10];                //채팅에서 사용할 이름
  17:  
  18: int main(int argc, char *argv[])
  19: {
  20:     char line[MAXLINE], msg[MAXLINE + 1];
  21:     int n, pid;
  22:     int maxfd1;
  23:     int s;
  24:     fd_set read_fds;
  25:     struct sockaddr_in server_addr;
  26:  
  27:     if(argc != 4)
  28:     {
  29:         fprintf(stderr, "사용법: %s serverIP serverPort UserName \n", argv[0]);
  30:         exit(0);
  31:     }
  32:  
  33:     //채팅 참가자 이름 저장
  34:     sprintf(name, "[%s]", argv[3]);
  35:  
  36:     //소켓생성
  37:     if((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  38:     {
  39:         fprintf(stderr, "Client: Can't open stream socket.\n");
  40:         exit(0);
  41:     }
  42:  
  43:     //채팅 서버의 소켓주소 저장
  44:     bzero((char *)&server_addr, sizeof(server_addr));
  45:     server_addr.sin_family = AF_INET;
  46:     server_addr.sin_addr.s_addr = inet_addr(argv[1]);
  47:     server_addr.sin_port = htons(atoi(argv[2]));
  48:  
  49:     //연결 요청
  50:     if(connect(s, (struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
  51:     {
  52:         fprintf(stderr, "Server: Can't bind local address.\n");
  53:         exit(0);
  54:     }
  55:     else
  56:     {
  57:         printf("서버에 접속되었습니다. \n");
  58:     }
  59:     
  60:  
  61:     maxfd1 = s + 1;            //최대 소켓번호 + 1
  62:     FD_ZERO(&read_fds);
  63:     
  64:     while(1)
  65:     {
  66:         FD_SET(0, &read_fds);
  67:         FD_SET(s, &read_fds);
  68:  
  69:         if(select(maxfd1, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0)
  70:         {
  71:             fprintf(stderr, "select error <= 0\n");
  72:             exit(0);
  73:         }
  74:  
  75:         if(FD_ISSET(s, &read_fds))
  76:         {
  77:             int size;
  78:             if((size = recv(s, msg, MAXLINE, 0)) > 0)
  79:             {
  80:                 msg[size] = '\0';
  81:                 printf("%s \n", msg);
  82:             }
  83:         }
  84:  
  85:         if(FD_ISSET(0, &read_fds))
  86:         {
  87:             if(fgets(msg, MAXLINE, stdin))
  88:             {
  89:                 sprintf(line, "%s %s", name, msg);
  90:                 if(send(s, line, strlen(line), 0) < 0)
  91:                     printf("send() error \n");
  92:                 
  93:                 if(strstr(msg, escapechar) != NULL)    //종료문자 처리
  94:                 {
  95:                     printf("Good bye. \n");
  96:                     close(s);
  97:                     exit(0);
  98:                 }
  99:             }
 100:         }
 101:     }
 102:         
 103:     return 0;
 104: }


● 실행결과


 
상기의 스크린샷과 같이 이 프로그램들은 문제가 있다.
메세지을 입력하는 동안 다른 사용자(클라이언트)가 메세지를 보내면 커서가 위치한 곳 부터 수신받은 메세지가 덧붙여져..
보기가 좋지 않고 혼란스럽다.
그리고 새로운 사용자가 서버에 접속을 하여도 기존 접속자들에게 알려주지 않는다.
누가 접속해 있는지 모르는데 정상적인 대화가 가능할까?

다중접속테스트 실행결과...http://sumanaki.tistory.com/156


● 프로그램의 전체적인 흐름

 
TCP/IP통신의 초기단계인 socket( )과 bind( ), listen( )에 대한 설명은 생략합니다.
파란색점선은 제어신호의 이동을 나타내고 select( )의 블로킹이 풀리는 신호의 흐름을 표현한 것이다.
초록색실선은 Client에서 connect( )로 Server에 연결을 요청할 때 accept( )의 블로킹이 풀리는 것을 표현한 것이고,
                ※실제는 아래의 3way-handshaking 도식을 참조할 것.
빨간색실선은 사용자가 다른 사용자에게 메세지를 전송했을 때의 흐름을 표현한 것이다.
검은색실선은 프로그램의 흐름이고, 옅은 분홍색과 옅은 황색의 사각형박스는 그 부분이 반복된다는 것이다. (분홍색은 무한반복임)



● 소스코드분석

<서버>

  71:         if(select(max_fd1, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0)

71행: 서버측에서 select( )로 사용중인 모든 장치의 입력의 변화를 체크하여 수신이 되면 즉시 리턴하여 다음 단계로 갑니다.
       여기선 리턴값이 음수일 때 에러처리만 하고 타임아웃이 NULL이므로 타임아웃처리를 따로 할 필요가 없으니 else문을 쓰지 않음.
       그럼 어떤 때에 어떻게 서버는 수신을 받아 블로킹되어 있는 select( )를 푸는 것일까?

        

       TCP/IP통신에서 서버와 클라이언트간의 handshaking도를 보면 Client가 연결을 요청할 때 제어문자를 보내게 되어 있다.
       그러니 select( )에서 서버소켓을 감시하면 Client가 제어문자를 보냈을 때 블로킹이 풀리는 것이다.

<클라이언트>

  50:     if(connect(s, (struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)

50행: 클라이언트측에서 connect( )로 서버에 연결을 요청함.



<서버>

  77:         if(FD_ISSET(s, &read_fds))

77행: select( )에서 사용 중인 모든 장치의 수신을 검사하였다고 하여도 어떤 장치 – 어떤 파일디스크립터에,
       변화가 발생하였는지는 알 수 없다. 그러니 서버소켓을 검사하여 Client의 연결 요청이었는지를 확인한다. (제어문자)

  80:             client_fd = accept(s, (struct sockaddr *)&client_addr, &client_len);

80행: 만약 77행에서 검사하여 소켓에 수신된 값이 있었다면 accept( )로 Client의 연결요청을 수락한다.
       select( )로 변화를 감지했으므로 accept( )에 프로그램의 흐름이 도달할 쯤에는 accept( )가 바로 일을 할 수 있는 상황이 된다.

  87:             // 채팅 클라이언트 목록에 추가
  88:             client_s[num_chat] = client_fd;
  89:             num_chat++;
  90:             send(client_fd, start, strlen(start), 0);
  91:             printf("%d번째 사용자 추가. \n", num_chat);

87 ~ 91행: 이후 서버는 접속한 Client의 소켓디스크립터(파일디스크립터)를 리스트에 추가하고 접속인원수를 카운트한다.
             서버에 접속했다는 확인메세지로  "Connected to chat_server \n"문자열을 전송하고 서버의 터미널창에 접속수를 표시한다.


<클라이언트>

  69:         if(select(maxfd1, &read_fds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) < 0)

69행: 사용 중인 모든 장치를 검사하여 변화가 일어나면 블로킹을 풀고 if문 다음 명령문들을 실행한다.
       if문 내의 명령문들은 예외처리이니 설명을 생략한다.

  75:         if(FD_ISSET(s, &read_fds))
  78:             if((size = recv(s, msg, MAXLINE, 0)) > 0)

75행: 소켓디스크립터에 변화가 일어 났다면? 수신버퍼에 데이터가 들어왔다면 다음 명령문들을 실행한다.
78행: 서버로부터 데이터를 수신받아 msg라는 배열에 저장한다.  

  80:                 msg[size] = '\0';
  81:                 printf("%s \n", msg);

80행: 저수준입출력함수들은 문자열의 끝 표시를 해주지 않는다. 문자열의 끝을 알리기 위해 NULL문자를 끝에 삽입한다.
81행: 클라이언트의 모니터에 수신 받은 메시지를 출력한다.    

  85:         if(FD_ISSET(0, &read_fds))
  87:             if(fgets(msg, MAXLINE, stdin))
  89:                 sprintf(line, "%s %s", name, msg);
  90:                 if(send(s, line, strlen(line), 0) < 0)

85행: 0번 파일디스크립터인 stdin(표준입력장치 = 키보드)의 변화가 발생(엔터키입력)하였다면,
87행: 키보드로 부터 문자열을 입력받아 msg에 저장한다.
89행: name배열과 msg배열의 문자열을 합쳐서 line이라는 배열에 “%s %s”형식에 맞춰 저장한다.
90행: send( )로 소켓디스크립터s에 연결된 네트워크를 통해 line의 길이만큼 line에 저장된 문자열을 전송한다.


주의해서 볼 점은 75행과 85행이고 서로 I/O멀티플렉싱을 시분할로 처리하고 있다.
이렇게 서로 다른 시간대에 처리를 할 수 있는 이유는 모든 장치에는 다른 장치와 연결이 용이하도록 버퍼가 있기 때문이다.
하드웨어적인 버퍼가 있을 수 있고 소프트웨어적인 버퍼가 있을 수 있다.
(키보드의 경우는 소프트웨어적인 버퍼만 있다. 동기신호와 데이터만 날리는 단순한 구조로 되어 있다.)
버퍼에 데이터가 들어가는 것과 응용프로그램의 프로세스가 처리하는 것은 다르다.
프로세스가 처리하지 않아도 커널은 열심히 모든 장치의 데이터들을 특정영역에 보관해 준다. 그리고 제어에 필요한 신호도 발생시킨다.
꼭 신호를 발생시키지 않아도 특정영역에 있는 특정메모리의 값을 참조하면 그 변화를 알 수 있다.
(깊게 들어가면 어려우니 여기서 그만하겠다.)

이렇게 클라이언트는 단순히 채텅 채팅서버로 메시지를 보내고 받아 표시하는 행위만 한다.

<서버>

  94:         //클라이언트가 보낸 메시지를 모든 클라이언트에게 방송
  95:         for(i = 0 ; i < num_chat ; i++)
  97:             if(FD_ISSET(client_s[i], &read_fds))
  99:                 if((n = recv(client_s[i], rline, MAXLINE, 0)) <= 0)
 112:                 rline[n] = '\0';
 113:                 for(j = 0 ; j < num_chat ; j++)
 114:                     send(client_s[j], rline, n, 0);
 116:                 printf("%s", rline);

95행: 모든 장치의 변화를 체크하는 반복문.
97행: Server로 부터 수신한 데이터가 있는지 검사하여, 있다면,
99행: recv( )로 수신하여 rline배열에 저장.
112행: 문자열의 끝표시
113행: Server에 접속한 Client수 만큼 반복하여 리스트에 있는 소켓디스크립터를 통해 네트워크로 rline배열에 저장된 문자열을 전송.
116행: 전송한 문자열을 화면에 표시.

서버도 클라이언트처럼 단순한 일을 하는데..채텅 채팅서버 사용자가 보내온 메세지를 받아,
모든 사용자에게 보내는 에코 - 유니캐스트를 수행한다.

더 자세한 설명이 필요하면 댓글을 달아주세요.



 

네트워크 프로젝트 계획





 

7월26일...프로젝트 시작하며 선생님의 소스코드를 아무런 생각없이 타이핑.
           선생님: “수업을 여기까지입니다. 더 이상 가르쳐줄께 없삼. 알아서 하삼.”
           학생: “…”
7월27일...chat_server.c & char_client.c 소스코드 분석.
           프로젝트 스케쥴표 작성.
7월28일~29일...터미널창에 윈도우를 추가해보자.

8월1일~5일...방학 싱난다.

8월8일~9일...접속자관리기능추가와 베타테스트를 본격적으로 시작.
8월10일~12일...보고서작성과 마무리 테스트.

세부계획을 세우지 않은 이유는 별 생각이 없기 때문입니다!! 
 



Class String





● String Class로 객체를 만드는 법과 초기화

① String str = new String( ); 
    str = “Hello”;
② String str = new String(“Hello”);
③ String str = “Hello”;

①번은 new(C언어에서 malloc( )와 비슷)를 사용해 메모리 heap영역에 객체를 생성하고,
         메모리 String영역에 객체가 들어갈 공간을 가리키도록 하고 그 heap영역의 객체를 레퍼런스변수가 가리키도록 한다.
         ( Stack영역의 str → heap영역의 String객체(?) → String영역 )
         이렇게 생성된 String영역의 객체는 아무런 값도 가지고 있지 않는다. 
         str을 통해 String영역에 있는 객체(문자열)를 연결하고 싶으면 상기와 같이 문자열 연결 연산자 ‘=’를 사용하여 연결한다.

②번은 String Class의 생성자를 이용해 객체를 생성한 뒤에 레퍼런스변수 str에 연결하여 초기화까지 한 줄에 끝낸다.

③번은 메모리 String영역에 있는 객체를 Stack영역에 있는 레퍼런스변수 str이 바로 가르키도록 하여,
         ②번과 같이 객체의 생성과 초기화를 한 번에 하는 방법이다.




● J2SE 6 documentation에서 Class String에는 어떤 메소드가 있는지 알아보자.


<- 클릭하여 도움말을 열자.

도움말 파일이 없는 분들은 http://www.allimant.org/javadoc/ 에서 다운받으세요.






색인을 클릭하여 string을 입력하면 String클래스에 대한 설명과 인터페이스를 알 수 있다.
구글번역의 힘을 빌리면..

The String class represents character strings. All string literals in Java programs, such as "abc", are implemented as instances of this class.
“String 클래스는 문자 문자열을 나타냅니다. 예 : "ABC"로 Java 프로그램, 모든 문자열 리터럴이 클래스의 인스턴스로 구현됩니다.”

리터럴은 이름이 없는 상수(컴퓨터는 문자도 숫자)이고,
인스턴스는 일반적으로 어떤 집합에 대해서, 그 집합의 개별적인 요소. 객체 지향 프로그래밍(OOP)에서, 어떤 등급에 속하는 각 객체를 인스턴스라고 한다. 예를 들면 ‘목록(list)’이라는 등급을 정의하고 그 다음에 ‘본인 목록(my list)’이라는 객체를 생성(기억 장치 할당)하면 그 등급의 인스턴스가 생성된다. 또한 변수가 포함되어 있는 어떤 논리식의 변수에 구체적인 값을 대입하여 식을 만들면 원래 식의 인스턴스가 만들어진다. 이런 의미에서 인스턴스를 실현치라고 한다.  - 네이버지식백과

뭔가 더 아리송하다. –_-;



● 자주 사용하는 메소드들




charAt( )은 객체의 index – 1번째 문자만 잘라내 반환한다.
              신기한 점은 한글 2Bytes나 영문 1Bytes 모두 한 글자로 취급한다는 점이다.
              (C언어에서와 같이 생각하면 안 되겠다.)
compareTo( )는 현재 객체(문자열)와 다른 객체(문자열)를 비교하는 메소드이다.
concat( )에 대한 번역은 댓글을 달아주세요. :D
format( )은 sprintf( )와 비슷하게 문자열을 일정형식에 맞춰 주는 메소드인듯하다.




● 예제1

   1: // 20110727 
   2: package kr.ac.busanit;
   3:  
   4: class A {
   5:     
   6: }
   7:  
   8: public class StringTest1 {
   9:     public static void main(String[] args) {
  10:         // TODO Auto-generated method stub
  11:         String str = new String("Hello JAVA");        //객체생성과 동시에 초기화
  12:         String str4 = new String();                    //객체만 생성.
  13:         
  14:         //System.out.println(str4.charAt(0));  //이렇게 하면 에러남.
  15:                 
  16:         for(int i = 0 ; i < str.length(); i++) {    //한 글자씩 출력.
  17:             System.out.println(str.charAt(i));
  18:         }
  19:         
  20:         //해쉬코드확인
  21:         StringTest1 st1 = new StringTest1();        //객체만 생성.
  22:         System.out.println(st1.toString());            //객체가 가진 값을 문자열로 바꾸어 출력.
  23:         
  24:         A a = new A();                                //Class A 객체 a 생성.
  25:         System.out.println("a: " + a.toString());    //해쉬코드를 16진수로 출력.
  26:         System.out.println("a: " + a.hashCode());    //해쉬코드를 10진수로 출력.
  27:         A b = new A();                                //Class A 객체 b 생성.
  28:         System.out.println("b: " + b.hashCode());    
  29:         A c = new A();
  30:         System.out.println("c: " + c.hashCode());
  31:         
  32:         //해쉬코드가 같은 예
  33:         String str1 = new String("Java");
  34:         System.out.println("str1: " + str1.hashCode());
  35:         String str2 = new String("Java");
  36:         System.out.println("str2: " + str2.hashCode());
  37:         String str3 = new String("Java");
  38:         System.out.println("str3: " + str3.hashCode());        
  39:         //해쉬코드가 다른 예
  40:         String str5 = new String("Java1");
  41:         System.out.println("str5: " + str5.hashCode());
  42:         String str6 = new String("Java2");
  43:         System.out.println("str6: " + str6.hashCode());
  44:         String str7 = new String("Java3");
  45:         System.out.println("str7: " + str7.hashCode());        
  46:     }
  47: }


<실행결과>




<설명>

a, b, c, str, str1~7은 모두 객체가 아닌 레퍼런스 변수이다.
a, b, c는 class A틀에 찍은 객체로 모두 고유번호(해퀴 해쉬코드)를 가지고 있어 각각 다르고,
str1~3은 같은 하나의 객체를 참조하고 있는 객체를 가리킨다.
생성자에 각기 다른 문자를 넣은,
str5~7은 서로 다른 객체를 참조하고 있는 객체를 가리킨다.

☆ hashcode는 JVM이 class를 실행할 때 부여하는 고유한 ID와 같은 숫자이다.


● 예제2 

   1: // 20110727
   2: package kr.ac.busanit;
   3:  
   4: public class StringTest2 {
   5:     public static void main(String[] args) {
   6:         // TODO Auto-generated method stub
   7:         String str1 = "자바";
   8:         String str2 = "자바";
   9:         
  10:         System.out.println(str1.hashCode());
  11:         System.out.println(str2.hashCode());
  12:         
  13:         if(str1 == str2)
  14:             System.out.println("같음");
  15:         else
  16:             System.out.println("다름");        
  17:  
  18:         if(str1.equals(str2))
  19:             System.out.println("같음");
  20:         else
  21:             System.out.println("다름");    
  22:     }
  23: }

<실행결과>




<설명>



str1과 str2는 모두 메모리 stack영역에 위치하고 있고, 선언과 동시에 메모리 String영역의 문자열 “자바” 객체를 가리킨다.
모두 같은 값을 가지고 있으니 수치비교연산자로 비교하면 논리값이 참(true)이 된다.
equal( ) 메소드를 사용해도 가리키는 곳의 해쉬코드가 동일하므로 비교하면 논리값이 참(true)이 된다.



● 예제3

   1: // 20110727
   2: package kr.ac.busanit;
   3:  
   4: public class StringTest3 {
   5:     public static void main(String[] args) {
   6:         String str1 = new String("자바");
   7:         String str2 = new String("자바");
   8:                         
   9:         System.out.println(str1.hashCode());
  10:         System.out.println(str2.hashCode());
  11:         
  12:         if(str1 == str2)                //heap영역의 객체가 가리키는 곳의 문자열은 같으나
  13:             System.out.println("같음");    //stack영역의 레퍼런스변수의 값은 다르다.
  14:         else
  15:             System.out.println("다름");
  16:         
  17:         if(str1.equals(str2))            //equals메소드는 해쉬코드가 같은가 비교하는 함수
  18:             System.out.println("같음");
  19:         else
  20:             System.out.println("다름");
  21:         
  22:         StringBuffer sb1 = new StringBuffer("자바");
  23:         StringBuffer sb2 = new StringBuffer("자바");
  24:         
  25:         System.out.println(sb1.hashCode());
  26:         System.out.println(sb2.hashCode());
  27:         
  28:         if(sb1.equals(sb2))                //equals메소드는 해쉬코드가 같은가 비교하는 함수로
  29:             System.out.println("같음");    //만약 내용을 비교한다면 같다는 결과가 나와야 하나,
  30:         else                            //결과를 보면 다르다.
  31:             System.out.println("다름");    //해쉬코드가 다르기 때문에 이런 결과가 나온다.
  32:     }
  33:  
  34: }



<실행결과>




<설명>




메모리 String영역에 있는 문자열 “자바” 객체가 있으면,
new를 통해 메모리 heap영역에 그 객체를 가리키는 객체1과 객체2를 생성하고 객체를 가리킨다.
그 다음 메모리 stack영역에 레퍼런스 변수들을 생성하고 각 객체를 가리키도록 한다.

단순히 수치비교연산자를 사용해 레퍼런스 변수들을 비교하면 서로 다른 값을 가지고 있으므로 논리값이 거짓(false)가 된다.
equals( ) 메소드를 사용해 비교를 하면 최종적으로 가리키고 있는 String영역의 객체의 해쉬코드는 동일하므로 참(true)이 된다.

equals( ) 메소드가 내용을 비교하는 메소드가 아니란 것을 증명하기 위해...
StringBuffer클래스로 객체를 만들어 equals( )메소드로 해쉬코드가 다른 객체를 비교하니 논리값은 거짓(false)이 되었다.
즉, equals( )메소드는 내용을 비교하는 메소드가 아니라 해쉬코드가 같은지 알아보는 메소드이다.

☆ 책을 100% 믿지 말고 참고만 하자.
    이런 equals( ) 메소드와 같은 문제는 의심이 가면 직접 실험을 해보자. (아직은 기초지식이 부족해 조금 무리일듯함.)


● 예제4

   1: // 20110727
   2: package kr.ac.busanit;
   3:  
   4: public class StringTest4 {
   5:     public static void main(String[] args) {
   6:         // TODO Auto-generated method stub
   7:         String str = "뇌를 자극하는 자바";
   8:         
   9:         System.out.println(str.substring(3));
  10:         System.out.println(str.substring(3, 7));
  11:         System.out.println(str.charAt(9));
  12:         
  13:         str = "뇌를 자극하지 않는 자바";
  14:         
  15:         System.out.println(str);
  16:     }
  17: }

<실행결과>


<설명>



문자열의 인덱스는 0부터 시작하는 것에 주의하고,
substring( ) 메소드의 사용법은…


substring
public String substring(int beginIndex,
                        int endIndex)
Returns a new string that is a substring of this string. The substring begins at the specified beginIndex and extends to the character at index endIndex - 1. Thus the length of the substring is endIndex-beginIndex.

Examples:

 "hamburger".substring(4, 8) returns "urge"
 "smiles".substring(1, 5) returns "mile"
Parameters:
beginIndex - the beginning index, inclusive.
endIndex - the ending index, exclusive.
Returns:
the specified substring.
Throws:
IndexOutOfBoundsException - if the beginIndex is negative, or endIndex is larger than the length of this String object, or beginIndex is larger than endIndex.



뭔가 복잡해 보인다.
간단히 인자를 하나만 넣으면 그 인덱스의 문자부터 문자열의 끝까지 반환하고,
         인자를 두 개 넣으면 첫 인자의 인덱스부터 두 번째 인자의 인덱스까지 문자열을 잘라내 반환한다.

728x90