제가 만든 서버에서 보낸 데이터가 xml 형식으로 바이트 스트림으로 해서 받는데 in.read로 받았더니 byte 버퍼에 태그단위로 하나씩 들어가는데다가 디버그해봤더니 in.read라는 문장은 받은 xml 태그 수만큼 반복해서 실행되고 전체 xml을 받아서 파싱하려고 Stringbuffer를 이용해서 append 하려고 했더니 이것도 안되네요..
안녕하세요 질문자님. 질문 내용을 제가 정확히 이해한 건진 모르겠습니다만 제가 이해한 내용은 보내는 서버 측에서는 예를 들어, "<tag>내용</tag>" 스타일의 문자열을 byte[]로 변환하여 outputStream.write( byte[] ); 하고 있고 클라이언트 측에서는
while ((readLength = inputStream.read(input, 0, input.length)) != -1) { stringBuffer.append( new String( input ) ); }
과 같은 동작을 하고 있다고 생각이 됩니다.
질문 내용 중 ' in.read라는 문장은 받은 xml 태그 수만큼 반복해서 실행되고' 라고 하신 걸로 미루어보아 데이터는 정상적으로 들어오고 있다고 판단됩니다. 받은 데이터를 가공하는데 어려움을 느끼고 계신 건 아닌가 하는 생각이 드네요.
우선 작성하신 클라이언트 쪽이 서버에서 보내주는 xml을 다 받으면 while 문을 빠져 나오는지 확인해 보시고요. 정상적으로 while문을 빠져 나온다면 유입된 Data를 가공하는 문제가 될 거고 그렇지 않다면 서버 측의 outputStream 에 write() 하는 부분을 확인하셔서 stream을 확실히 끝내고 있는지 확인하셔야 할 것 같습니다.
참고로 제가 작성한 소스를 첨부합니다. 첨부한 소스는 xml 형식의 문서를 읽어들여 byte 형식으로 전송하여 수신부에서 file과 문자열로 복원하는 코드가 작성되어 있습니다. Server.java 와 Client.java 는 일반적인 자바 bolocked Socket 프로그래밍 방식을 취하고 있습니다. 실제로 확인해야 하는 부분은 ByteArrayTransporter 클래스에서 클라이언트에 Data를 전송하는 부분과 Client 클래스에서 java.nio.ByteBuffer 를 이용하여 입력 데이터를 처리하는 부분 정도가 될 거 같습니다.
OpenJDK core-libs-dev 메일링 리스트에 재미있는 쓰레드가 진행되고 있습니다. ( 재미있다고 적긴 했지만 재미있기만 한 것은 아니지만요..) What methods should go into a java.util.Objects class in JDK 7? 이란 타이틀로 진행 중인 이 쓰레드의 내용인즉, '자바 개발자가 흔히 사용하는 유틸리티 성 메소드를 구현하는 java.util.Objects 같은 클래스를 만든다면 이 Objects 클래스에서 꼭 포함 했으면 하는 메소드는 무엇인가?' 하는 내용입니다. 썬社의 Joe Darcy로부터 시작된 이 쓰레드에는 많은 회신 메일로 해당 이슈에 대한 자바 개발자의 높은 관심도를 엿볼 수 있습니다.
Darcy는 그의 첫 번째 포스트에서 Null-safe 한 equals(arg1,arg2) 와 모든 primitive type에 대응하는 compareTo(arg1, arg2) 를 제안하고 있네요.
Andrew John Hughes 같은 경우엔 toString(arg) 메소드에 대해 자바 리플랙션을 통하여 해당 객체의 상세를 보여주면 어떻겠냐는 것과 비슷한 방법으로 clone() 메소드도 구현해 버리자는 내용을 제안했습니다.
이 쓰레드가 커뮤니티의 긍정적인 회신을 받고 있기만 한 것은 아닙니다. 이런 내용의 글을 접한 Stephan Oudmaijer 같은 사람은 infoQ의 해당 내용에 대한 기사에 댓글을 통해 stupid idea란 표현과 함께 그런 유틸성 메소드는 jakarta-commons에서 구현하도록 하고 제발 JDK는 그냥 내버려 뒀으면 좋겠다고 표현하고 있네요. ^^;
여러분도 평소에 '아.. 이런 메쏘드는 기본적으로 JDK에 있었으면 좋겠는데...' 하고 생각한게 하나 둘쯤은 있을 거라 생각되는데요.. 해당 메일링 리스트에 가입하셔서 의견을 피력해 보시는것도 재미있을것 같습니다.
자바 레퍼런스타입이 정말 call by reference에 의한 전달이라면 changeObject( TargetClass obj)이 수행되고 나면 tc는 null 이어야 하지만 그렇지 않죠. 실제로 자바도 reference type을 인자로 전달할 때에 reference를 넘기지만 바로 그 reference가 아니라 reference의 사본의 값을 넘기므로 진정한 의미에서 call by reference로 볼 수 없으며, 이는 위 코드로도 충분히 증명이 되고 있습니다.
사실, 자바에서 인자 전달이 call by value냐 call by reference냐 하는 해묵은 논쟁보다는 실제로 어떻게 인자가 전달되는지 이해하고 코드를 작성하는 게 더 중요하겠지요.
스트럿츠 2.1.이 릴리즈 되었습니다. 요즘 MS쪽 어플리케이션 개발 프로젝트를 하고있지만 여전히 자바쪽 흐름을 읽는것도 게을리 하고 있지 않습니다만, 쉽지 않네요.
스트럿츠 2.1은 많은 양의 코드를 Plug-In Framework로 옮기는 리펙토링, Convention plug-in에 의한 XML 설정 감소, 향상된 REST 지원에 초첨을 두고 진행되었습니다.
InfoQ에서 스트럿츠2 커미터인 Musachy Barroso씨와 인터뷰한 기사가 올라와서 포스팅 합니다.
2.0과 2.1의 차이점은 무엇인가요? 많은 수의 버그픽스가 있었습니다. 그리고 REST, Convention, Java Template과 같은 플러그인이 추가되었습니다.
많은 기능이 플러그-인 형태로 변경되었습니다. 이렇게 한 이유는 무엇인가요? 스트럿츠 코어에는 진정한 core만을 남기자는 아이디어에서 출발했습니다. 그리고 나머지는 플러그인에 집어 넣는거죠. 이렇게 하면서 코드의 유지보수가 쉬워 졌습니다. 그러면서 Dojo 플러그인 같은 것들은 더 이상 스트럿츠에서 지원하지 않습니다. 이런 변화는 제거된 플러그인을 사용하지 않는 개발자와 작은 footprint를 원하는 개발자 이외의 사람에게 직접적인 장점은 없습니다.
Ajax 태그를 depreciate 한 이유는 무엇인가요? Struts2의 ajax 태그는 Dojo 0.4.x에 기반하고 있습니다. 그것을 Dojo의 최신버전에 맞춰 포팅하는것은 모든 ajax 태그의 코드를 다시 작성해야한 다는것과도 같은 이야기입니다. Dojo의 새로운 버전은 너무 빨리 출시되고 마이너 버전에서도 변경되는 코드의 양이 너무 많습니다. 태그가 Dojo의 모든 기능을 다 포함하고 있지 않기 때문에 개발자는 주로 Dojo 라이브러리를 직접 사용하는 경향이 있습니다. 이런 이유들과 ajax 태그 개발 지원자의 부족으로 ajax 태그를 depreciate 했습니다.
어떤 이유로 CodeBehind 플러그인들을 Convention 플러그인으로 바꾸게 되었나요? Convention은 외부 프로젝트였고 늦게 Struts에 추가되었습니다. Convention은 좀 더 빠른 ClassPath Scanner, 더 나은 Configuration elements, 로깅, 다양한 configuration 옵션, configuation reloading, 더 나은 문서화 등을 지원합니다.
Java Template 플러그인은 무엇인가요? Java Template은 FreeMarker를 이용하여 java만으로 구현한 'simple theme' 구현체입니다. 이 플러그인의 태그는 재작성이 불가능한 약점이 있는 원래의 그것보다 4~5배 가량 빠르게 동작합니다.
다른 많은 Web Framework가 있는데 우리는 왜 Strtus2를 선택해야 하나요? 스트럿츠2는 아마도 가장 약한 커플링(loosely-coupled)으로 구현된 프레임워크일 겁니다. 많은 기능이 커스터마이징 없이, 혹은 약간의 커스터마이징만으로 사용할 수 있으며, 프레임워크를 익히기가 쉽습니다. 약한 커플링은 스트럿츠의 실체에 대한 지식 없이도 비지니스 로직을 작성 할 수 있도록 해 줍니다. 그러면서도 스트럿츠는 대용량 트래픽을 발생하는 사이트에서 유연한 확장성을 담보하고 있다는 점입니다.
마지막으로 한마디. 스트럿츠 2.1 출시까지 오랜 기간 공을들였습니다. 우리는 스트럿츠 프레임워크의 빌드와 릴리즈 프로세스 개선을 위해 정말 열심히 일하고 있습니다. 앞으로는 좀 더 짧은 주기로 새로운 버전이 릴리즈 되는것을 기대하셔도 좋습니다.
double을 퍼센트(%)로 표현하기위해 여러가지 방법을 이용할 수 있지만 java.text 패키지의 NumberFormat 클래스 API를 이용하면 아래와 같이 간단히 double값을 퍼센트 문자열로 변환할 수 있다.
package javacodesnipet;
import java.util.Locale; /** * * @author 신윤섭 */ public class NumberFormatPercent { public static void main(String[] args){ //아래 double 값을 퍼센트 문자열로 표현해 보자 double value = 0.343234532d; //NumberFormat 객체로 부터 PercentInstance를 얻어온다. java.text.NumberFormat pformat = java.text.NumberFormat.getPercentInstance(Locale.KOREA); //퍼센트로 표현할 소수점 이하의 자리수를 정한다. pformat.setMaximumFractionDigits ( 4 ); //자... 이제 double을 String형식의 퍼센트로 변환하자. String sPercent = pformat.format ( value); //값을 확인 해 보자 -> 34.3235% 과 같이 표현 된다. System.out.println(sPercent); } }
/** * Stream을 이용한 파일복사 코드 스니핏 * @author 신윤섭 */ public class StreamCopy {
/** * source에서 target으로의 파일 복사 * @param source * @param target */ public void copy(String source, String target) { //복사 대상이 되는 파일 생성 File sourceFile = new File( source );
/** * Buffer를 이용한 파일복사 코드 스니핏 * @author 신윤섭 */ public class BufferCopy {
/** * source에서 target으로의 파일 복사 * @param source * @param target */ public void copy(String source, String target) { //복사 대상이 되는 파일 생성 File sourceFile = new File( source );
try { //스트림 생성 inputStream = new FileInputStream(sourceFile); outputStream = new FileOutputStream(target); //버퍼 생성 bin = new BufferedInputStream(inputStream); bout = new BufferedOutputStream(outputStream);
//버퍼를 통한 스트림 쓰기 int bytesRead = 0; byte[] buffer = new byte[1024]; while ((bytesRead = bin.read(buffer, 0, 1024)) != -1) { bout.write(buffer, 0, bytesRead); }
/** * NIO Channel을 이용한 파일복사 코드 스니핏 * @author 신윤섭 */ public class ChannelCopy {
/** * source에서 target으로의 파일 복사 * @param source 복사할 파일명을 포함한 절대 경로 * @param target 복사될 파일명을 포함한 절대경로 */ public void copy(String source, String target) { //복사 대상이 되는 파일 생성 File sourceFile = new File( source );
try { //스트림 생성 inputStream = new FileInputStream(sourceFile); outputStream = new FileOutputStream(target); //채널 생성 fcin = inputStream.getChannel(); fcout = outputStream.getChannel();
//채널을 통한 스트림 전송 long size = fcin.size(); fcin.transferTo(0, size, fcout);
이상의 샘플코드를 이용하여 700Mbytes 짜리 파일을 5번 복사하여 그 시간을 측정해 본 결과는 아래와 같다. 테스트는 Win XP Pro sp3, Intel Core2 Duo 2GHz, 2Gbytes, 5400rpm의 노트북용 HDD 에서 JDK 1.6.0_06 을 이용하여 이루어졌다.
결과는 아래와 같다.
Stream을 이용한 파일 복사
Buffer를 이용한 파일 복사
Channel을 이용한 파일 복사
프로파일러를 이용하여 측정한 값이기 때문에 프로파일러의 처리량 만큼의 차이는 있겠지만 상대적 성능 비교 에는 문제가 없으리라 생각 된다. 실측에서도 예상대로 Stream , Buffer, Channel 순으로 파일 복사 시간이 줄어들고 있음을 볼 수있다.
이번에는 조금 더 들여다 보기로 하자. 700m짜리 파일을 한번 복사하는데 어떤 클래스와 메소들이 참여하고 있는지, 그리고 메소드가 몇번이나 호출되고 있는지 확인 해 보는것도 재미있을 것이다.
FileInputStream.read()실행에 대부분의 시간을 소비하고 있다.
스트림을 이용한 파일복사이다. FileInputStream.read()메소드가 71만여번 호출되고있으며 실행시간의 대부분을 이 메소드를 실행 하는데 소비하고 있음을 알 수 있다.
Stream보다는 나아졌다고는 하나 역시 read()메소드가 대부분의 실행시간을 소비하고 있다.
Buffer를 이용한 방법. 위의 Stream을 이용한 방법에 비해 수행시간이 약간은 줄어들었지만 이는 Buffer를 활용 함으로써 FileOutputStream.write() 수행시간을 줄인데 따른 성능 향상이며, FileInputStream.read()메소드는 약 9만번 호출되고 있다.
위 두 방식과는 확연히 다른 동작 성향을 보여주고 있다.
마지막으로 채널을 이용한 파일복사의 경우 위 두 경우와 비교하여 호출되는 메소드나 호출횟수등 전혀 다른 동작 성향을 보이고 있다. read 도 하지 않은채 FileDispatcher.write() 메소드를 단 한번 호출 하는것으로 파일 복사를 끝내고 있다. 이 FileDispatcher.write() 하부구조에서는 OS의 네이티브IO를 호출하고 있으리라 미루어 짐작할 수 있다.
이상으로 파일복사(스트림전송)의 세가지 방식과 그 성능에 대해 간략하게 알아보았다. 위 실험 결과는 크기가 비교적 큰 파일의 복사에서 나타나는 성향며, 다수의 작은 크기의 파일을 복사한다면 그 결과가 달라질 수도 있음을 밝혀둔다.
io작업이 필요한데 JDK 1.4 이상의 버전을 이용할 수 있다면 나은 성능을 보장하는 nio를 사용하지 않을 이유가 없어보인다.
아 그리고 BufferedInputStream 생성자의 인자로 넘겨준 InputStream은 BufferedInputStream의 close()가 호출 될 때
같이 close 됩니다. Channel 의 경우도 Lazy pattern 으로 생성되었다가 InputStream이 close 될 때 같이 close 되네요.
이 글을 보고 혹시나 해서 궁금해서 소스를 뒤져보니 그렇게 되어있네요. 덕분에 배우고 갑니다 ^^
자바로 파일을 복사할 수 있는 방법은 크게 3가지 정도가 있다. InputStream, OutputStream을 이용한 방법, Buffer를 이용한 방법, Channel을 이용한 방법이 그것이다. 물론 Buffer를 이용하면서도 단순히 Stream에 Buffer 필터를 적용할 수도, MappedByteBuffer를 쓸 수도 있고 Channel을 이용하면서도 inputChannel과 outputChannel을 이용하거나 transterTo()를 이용하는 등 다양한 방법을 구사할 수 있다. 여기서는 자바로 구현 할 수 있는 대표적인 파일 복사 코드를 살펴보고 각 코드간의 성능에 대한 이야기도 나눠 보도록 하겠다.
Java입문서등을 통하여 io (Input/Output)부분을 언급하며 나오는 개념이 Stream일 것이다. 스트림의 개념을 설명하고 처음 접하는 코드는 아래와 유사할 것이다. 파일을 인풋스트림으로 읽어들인 후 그 길이만큼 아웃풋 스트림에다 흘려보내는 방식으로 파일을 복사할 수 있다.
FileInputStream inputStream = new FileInputStream(file); FileOutputStream outputStream = new FileOutputStream(saveFullPath);
int bytesRead = 0; byte[] buffer = new byte[1024]; while ((bytesRead = inputStream.read(buffer, 0, 1024)) != -1) { outputStream.write(buffer, 0, bytesRead); }
outputStream.close(); inputStream.close();
* InputStream과 OutputStream을 이용한 기본적인 파일 복사 코드. 위 코드는 기본적인 Stream의 사용법을 잘 보여주고 있지만 성능상에 심각한 문제를 안고 있다. 파일크기(정확하게는 스트림의 길이)만큼 while문을 돌면서 끊임없이 읽고쓰기를 반복하고 있는데 이는 CPU, DISK모두에게 부담을 주는 결과를 초래한다.
이어지는 코드가 아마 가장 널리쓰이고 흔하게 볼수 있는 코드 일 것이다. 위에서 살펴본 Stream간의 데이터 전송이 썩 좋은 성능을 내지 못하기 때문에, 스트림을 버퍼를 장착(wrapping, chainning)하여 입출력 횟수를 줄여 성능 향상을 꾀하고 있다.
FileInputStream inputStream = new FileInputStream(file); FileOutputStream outputStream = new FileOutputStream(saveFullPath);
BufferedInputStream bin = new BufferedInputStream(inputStream); BufferedOutputStream bout = new BufferedOutputStream(outputStream);
int bytesRead = 0; byte[] buffer = new byte[1024];
* Stream에 Buffer Filter를 연결하여 성능을 향상. 위와같은 방법으로 충분히 만족할만 한가? 그렇다고 할수도있고 아니라고 할수도 있다. 위 두 방식은 스트림으로 데이터를 전송하는데 항상 cpu의 연산을 필요로 한다. 즉 스트림을 처리하는동안 cpu가 계속해서 명령을 처리 해줘야 한다는것이다.(비록 cpu사용율은 얼마 안될지 모르지만.. )
컴퓨터의 입장에서 본다면 IO는 상당히 느린 작업중의 하나이다. 이런 작업을 조금이라도 빨리 처리하기위해 하드웨어 혹은 운영체제 수준에서 많은 기법들을 제공하고 있다. 자바는 버전 1.4에 이르러서 기존 io와는 차별화된 nio(new io) 패키지가 추가되었는데 이 nio를 통하여 운영체제가 제공해 주는 향상된 io기능을 활용할 수 있게 되었다. 그 대표적인 것이 Channel과 Selector일 것이다. 아래와 같은 코드는 JDK 1.4이상부터 사용 가능하며 transferTo() 메소드를 호출하면 내부적으로 OS의 네이티브IO 기능을 활용하여 더욱 효율적인 스트림 전송이 가능하다.
FileInputStream inputStream = new FileInputStream(file); FileOutputStream outputStream = new FileOutputStream(saveFullPath);
마일스톤, 베타, 캔디데이트를 거쳐 드디어 넷빈즈 6.0이 정식 출시 되었습니다. 넷빈즈 6.0 출시에 발맞추어 5.0 출시때와 마찬가지로 netbeans.org의 화면도 변화가 있네요. (하지만 플러그인 탭으로 들어가면 아직까진 5 버전때의 화면을 볼 수 있습니다.)
새 디자인이 적용된 netbeans.org
아래는 앞으로 넷빈즈의 개발 로드맵입니다. 현재 정식출시된 6.0 버전의 마이너 업그레이드는 계속 되겠지만, 메이저 업그레이드는 한동안 없을 것 같군요.
넷빈즈 6.0 다운로드는 여기에서 할 수 있습니다. 다운로드 할 수 있는 패키지 형식도 다양해 졌습니다.. 지금까진 NetBeans only, NetBeans + J2SE, NetBeans + J2EE Server 세가지 종류의 패키지로 배포가 되었는데 이제는 Web & Java EE, Mobility, Java SE, Ruby, C/C++, All 등으로 좀더 특화/세분화 되었습니다. 물론 어떤 패키지를 받더라도 플러그인 다운로드를 통하여 다른 패키지의 기능을 추가할 수 있습니다.
SDN( Sun Developer Network ) 에 JAVA SE 6.0 성능 백서가 발표되었습니다.
성능 개선과 추가된 기능들, 새로운 플랫폼 지원, 개발 진행 방향이라는 세 가지 꼭지로 정리된 이 백서에는 지금까지 JDK가 그래 왔던 것처럼 성능향상을 위해 컴파일러, 버추얼머신, 가비지콜렉션, I/O, 자료구조와 알고리즘 개선 부분을 성능 향상의 포인트로 두고 있습니다.
특히 JAVA 6.0 SE에서는, Boot Class 로딩 속도의 향상이라든지, SWING GUI 어플리케이션에 Double Buffering이 기본 지원사양으로 포함되었다든지, 버추얼 머신이 시작되기 전 Splash Screen을 처리할 수 있는 기능 등이 포함되는 등, JAVA 데스크탑 어플리케이션을 위한 성능 향상부분에 많은 노력을 기울이고 있다는 것을 알 수 있는데요.
JAVA 데스크톱 어플리케이션이 특정분야에 한정되어있고, GUI 디자인도 서서히 MARK-UP 형식의 언어로 넘어가는 현실에서 Swing, AWT와같은 Form-Based GUI 디자인이 언제까지 계속될진 모르겠지만 Swing 데스크탑 어플리케이션 개발을 위한 지원이 계속되고 있다는 것은 반길만한 일이 아닌가 합니다.
J2SE 5.0에는 일상적 태스크를 좀 더 쉽게 구현할 수 있도록 하는 클래스와 메소드들이 추가되었다. 이번 팁에서는 새로 추가된 java.util.Scanner클래스를 이용함으로써 일반 표현문을 사용하는 스트링과 프리미티브 타입을 읽고 파싱(parsing)하는 것이 어떻게 좀 더 쉬워졌는지 알아보도록 하자.
J2SE 5.0의 출시 이전에는 파일에서 텍스트를 읽으려면 다음의 TextReader 클래스 같은 코드를 작성해야했다.
이와 같은 클래스에서의 기본적인 접근법은 하드 드라이브의 실제 파일과 일치하는 File 오브젝트를 생성하는 것이다. 그리고 나서 그 파일과 관련된 FileReader와 그 FileReader의 BufferedReader를 생성하고, 그 후 BufferedFile 리더를 사용하여 한번에 한 줄씩 읽는다.
실행되는 TextReader클래스를 보기위해서는 클래스에 대한 문서를 생성하여 읽고 파싱해야한다. 문서를 생성하기 위해서는 TextReader와 같은 디렉토리 안에 있는 TextSample.txt라는 파일에 다음과 같은 두 줄의 텍스트를 저장해야 한다.
Here is a small text file that you will
use to test java.util.scanner.
TextReader를 컴파일하고 다음을 입력하여 구동시켜보자.
java TextReader TextSample.txt
표준 출력으로 되돌아온 원본 파일을 보게 될 것이다.
프리미티브 타입과 스트링을 파싱하는 클래스인 java.util.Scanner를 이용하여 TextReader의 코드를 간단하게 할 수 있다.
Here
is
a
small
text
file
that
you
will
use
to
test
java.util.scanner.
TextScanner 는 파일로부터 Scanner 오브젝트를 생성한다. Scanner는 파일의 컨텐츠를 구획자 패턴을 이용하여 분해한다. 구획자 패턴의 디폴트 값은 흰 여백이다. 그 후 TextScanner는 Scanner의 hasNext() 메소드를 호출한다. 이 메소드는 Scanner 입력값에 파일의 마지막 부분에 이를 때까지 다른 token이 있으면 'true'를 리턴한다. next() 메소드는 다음 token을 나타내는 스트링을 리턴한다. 따라서 TextScanner는 파일의 마지막부분에 이를 때까지 각 라인에서 next()에 의해 리턴되는 스트링을 프린트한다.
Scanner의 useDelimiter 를 이용해 입력물을 토큰화하는 데 이용하는 구획자를 변경시킬 수도 있다. 메소드에 스트링 또는 java.util.regex.Pattern에 전달해주면 된다. 어떤 패턴들이 적절한 지에 대해서는 JavaDocs page for Pattern를 참조하기 바란다. 예를 들어 newline(\n)을 구획자로 이용하여 한번에 한 줄의 입력물을 읽을 수 있다. 다음은 새줄 문자를 구획자로 이용하는 수정된 readFile() 메소드이다.
마지막 줄을 찾는 다른 옵션들도 있다. 예를 들어 새줄 문자로 끝나는 라인이나 캐리지 리턴(enter키)과 newline으로 끝나는 라인들을 조사할 수 있다. "\r\n|\n" 일반 표현문을 이용하여 이를 실행할 수 있다. java.util.regex.Pattern의 JavaDocs는 또다른 라인 종결기들을 보여주므로 좀 더 복잡한 분석은 "\r\n|[\r\n\u2028\u2029\u0085]"표현문을 이용한다. 또한 Scanner 클래스의 hasNextLine()과 nextLine() 메소드를 이용할 수 있다. 어느 경우이던 수정된 TextScanner를 사용하면 결과물은 TextSample.txt의 컨텐츠와 레이아웃에 부합될 것이다. 다음을 참고하기 바란다.
Here is a small text file that you will
use to test java.util.scanner.
Scanner에 의해 사용된 구획자의 패턴을 간단하게 변경하여 큰 효과와 유연성을 얻을 수 있다. 예를 들어 다음의 구획자를 지정하면,
scanner.useDelimiter("\\z");
한번에 전체 파일을 읽는다. 이는 Pat Niemeyer가 java.net blog에서 제안하고 있는 요령과도 비슷하다. 몇 개의 중간 오브젝트를 생성하지 않고도 웹페이지의 모든 컨텐츠를 읽을 수 있는 것이다. 다음 WebPageScanner클래스의 코드는 java.net homepage의 현재 컨텐츠를 읽고 있다.
DataScanner의 바깥쪽 Scanner 오브젝트는 한번에 한 라인씩 파일을 읽는다. readFile() 메소드는 각 라인을 두번째 스캐너에 전달하고, 이 두번째 스캐너는 콤마로 ,구획된 데이터를 파싱하고 콤마 양쪽의 흰 여백을 삭제한다. 다음 token이 특정타입의 token인지 아닌지 분석하여 다음 token을 그 타입의 인스턴스로 취급하도록 하는 hasNext()나 next()메소드들도 있다. 예를 들어 nextBoolean()은 다음 token을 boolean으로 취급하여 "true" 또는 "false" 스트링과 매치시킨다. 매칭이 이뤄지지 않으면 java.util.InputMismatchException이 던져진다. DataScanner의 parseLine() 메소드는 각 라인이 어떻게 String, int, boolean으로 파싱되는지 보여준다.
Compile DataScanner. Then run it as follows:
DataScanner를 컴파일하고 다음과 같이 구동시키자.
java DataScanner Employee.data
다음과 같은 결과가 나타난다.
It is true that Joe, age 38, is certified.
It is true that Kay, age 27, is certified.
It is false that Lou, age 33, is certified.
콤마를 구획문자로 사용하고 싶을 것이다. 다시 말해 다음과 같이 시도해보려고 할 것이다.
lineScanner.useDelimiter(",");
이는 결국 InputMismatchException로 끝날 것이다. 이는 boolean으로 변경하려는 token에 여분의 공간이 포함되며 이 공간은 "true"나 "false" 에 매치되지 않기 때문이다. 일반적인 표현문의 모든 애플리케이션에 해당되는 케이스이므로 패턴을 구축하는 데 있어서 특히 세심한 주의가 필요할 것이다.
이 기사에서는 다른 알고리즘 관련 도서와 마찬가지로 알고리즘(설계)가 중요하다고 말한다. 전적으로 동의하는 바이다. 알고리즘은 중요하다.
하지만, 전산 경력이 6년이 넘어가도록 알고리즘이 문제가 되었던 적은 알고리즘 관련 수업에서 나오는 과제와 소프트웨어 엔지니어링 수업에서 나온 이상한 과제를 할 때 뿐이었다. 그렇게 중요한 것이라면, 경력의 대부분을 차지하는 현장 경험에서는 왜 알고리즘이 중요하는 사실을 몰랐을까? 적어도 알고리즘을 사용하지 않았을리는 없고, 그렇다면 남들이 해서 숨겨 놓은 알고리즘을 나도 모르게 사용한 것이 아닐까 생각된다. 이런 일들이 가장 잘 일어나는 곳은 데이터베이스와 자바 패키지가 아닐까 생각된다. 데이터베이스는 정렬과 Search를 단 몇 단어로 가능하게 만들지만, 정렬과 Search는 알고리즘에서 가장 중요한 분야 중에 하나이며, 아직도 연구가 끝나지 않은 분야이다. 자바에서 제공하는 표준 API중에도 알고리즘을 제공하는 것이 많다.
자바 표준 API 중에서 java.util 패키지는 알고리즘과 항상 단짝이 되어 나오는 자료 구조(Data Structure)의 Implementation들을 제공하는데, 그 중에 하나가 Vector이다. 실제 얘기에 들어가기 전에 몇 가지 재미난 데이터를 살펴보자.
위 그래프는 VectorTest.java의 실행결과를 보여주고 있다. 아래 데이터는 VectorInd.java의 실행결과를 보여주고 있다. > java VectorInd 1000000 Insert Time [749] : 11 Insert Time [61480] : 6 Insert Time [122880] : 48 Insert Time [245760] : 70 Insert Time [491520] : 113 Insert Time [983040] : 215 Insert Time [983917] : 18
소스에서도 알 수 있듯이, 그래프는 각 데이터 크기만큼 Vector에 add를 한 것이고, 변화 폭이 큰 것은 입력되는 위치가 Vector의 처음이고, 아래 그래프는 입력되는 위치가 Vector의 마지막이다. 똑같은 데이터를 입력하지만, 실행 속도는 엄청나게 차이가 난다. 이것을 알고리즘에서 사용하는 용어로 표현하면, 전자의 수행속도는 O(n*n), 후자는 O(n)로 표현된다. 루프를 제외하면, 각각 add에 걸리는 시간은 O(n), O(1)이다. 아래 두 그림은 그 차이를 보여준다.
위의 그림은 마지막에 입력되는 경우를 보여주고, 아래 그림은 첫번째 위치에 입력되는 경우이다. 아래의 경우에는 입력되어 있는 데이터의 수만큼 데이터가 이동해야 한다. 만약, 10번의 입력이 일어나면, 실제로는 10 + 9 + 8 + … + 2 + 1 = 10 * 9 / 2 번의 시간이 걸니는 것이다. 데이터가 커지면 커질수록 치루어야 하는 대가는 급속하게 커진다.
정말 모든 addLast가 일정한 시간이 들어가는지를 보여주는 것이 VectorInd.java이다. 데이터 입력 시간이 3 millisecond 이상 들어가는 경우에 소요된 시간을 프린트한 것이다. 백만번 중에 7번 발생했다. 처음과 마지막 데이터를 제외하면, 데이터가 두 배가 되어질 때 시간이 많이 걸리는 현상이 발생했다. 그 이유를 그림으로 설명하면 다음과 같다.
사용자에게 보이지는 않지만, Vector는 데이터를 저장하기 위해 Array를 만들었다. 만든 Array의 사이즈는 무한하지 않기 때문에 데이터를 계속 입력하면, 공간이 부족하게 되고, Vector는 이 때 원래 사이즈의 두 배의 크기의 Array를 만들고, 가지고 있던 데이터를 새로운 Array로 이동시킨다. 즉, Array에 데이터가 입력될 때, Array가 부족하면 기존에 저장된 데이터의 크기(Array 사이즈) 만큼의 데이터 이동이 일어나게 된다. 위 자료에서 보듯이, 데이터 사이즈가 두 배에 도달할 때마다 약 2 배 가량의 시간이 더 걸리는 것을 볼 수 있다.
이때가지의 관찰결과에 따르면, Vector의 인덱스가 작은 쪽에는 입력하는 것도, 삭제하는 것도 좋지 못하다. 그만큼의 댓가(실제 데이터 사이즈 - Index만큼의 데이터 이동)를 치루어야 하기 때문이다. 그리고, 데이터가 입력되는데 걸리는 시간을 반드시 일정한 시간이하로 낮추어야 하는 경우에는 일반적인 Vector를 사용할 수 없다.
실제로 Vector는 인덱스 관리가 편리한 Array에 불과한 것을 알 수 있다. 하지만 Array 작업시 문제가 되는 것들은 여전히 Vector에서도 문제가 된다. 그러므로 처음 넣은 데이터를 가장 먼저 지워야 되는 일에는 Vector를 사용하지 않는 것이 좋다. 이 작은 사실 하나가 위 그래프에서 볼 수 있듯이 데이터가 많은 경우, 실제 프로그램의 실행속도에는 엄청난 영향을 미치게 된다.
Note : VectorInd의 작업결과는 리눅스에서 실행한 것이다. 필자의 윈도우에서는 다른 실행결과가 나왔다. 출력되는 데이터의 모든 시간은 10이였다.
java.lang.reflect를 이용하면 우리가 원하는 클래스에 대한 invoke가 가능하다는 것은 알고 있을 것이다. 하지만 classpath에 등록안되어진 클래스들에 대해서는 어떻게 할 것인가?
일일이 사용자에게 클래스 패스를 설정하게 할수만은 없는 일이다.
보통의 엔진들을 보게 되면 install되어진 디렉토리의 위치만을 세팅하도록 하고 있다. set JAVA_HOME이라던지 set ANT_HOME이라던지..
쉘스크립트에 의하여 그러한 것들을 정의하여 java process를 띄우곤 하는데 그러면 내가 ant.jar등을 등록하지 않았음에도 불구하고 해당 애플리케이션들이 잘 작동하는 이유는 무엇일까? 그것은 바로 ClassLoader에 숨겨져 있다. 아래에서 보여지는 샘플코드는 classpath 프로퍼티에 등록이 되어지지 않은 클래스들에 대한 조작을 할 것이다.
그렇게 함으로서 이 글을 읽는 당신이 만든 애플리케이션이 별다른 클래스로딩 정책 없이도 작동이 될수 있겠다. 그러려면 또한 잘 알아야 하는것이 reflection API이거늘... 이부분에서는 그러한 것을 생략하고 URLClassLoader를 이용하여 디렉토리나 jar파일을 등록하여 가져오는 방법을 설명하도록 하겠다.
ClassLoader클래스는 이미 1.0API부터 존재해왔으면 URLClassLoader는 1.2에 새롭게 추가된 클래스이다. 우리가 사용하는 파일시스템이 URL이란 이름하에 조작이 될 수 있다는 것을 우선 명심하기 바란다. 왜냐면 file:/// 이란 URI를 사용하기 때문이다.
아래에서는 특정한 디렉토리 안의 jar파일에 대한 class loading샘플을 보여준다..
넷빈즈를 기동하고 메뉴에서 Tools -> Options 를 선택하면 아래와 같은 대화창이 뜬다.
항목들 참 많기도 하다...
큰 카테고리별로 보면
Building : Ant 설정 항목이다. 넷빈즈 4.1은 Ant 1.6.* 대를 번들 하고 있으며 이곳에서 다른 버전의 Ant를 설정 한다. 참고로 Ant 스크립트까지 여기서 설정 하진 않는다. 구체적인 Ant 스크립트는 프로젝트를 생성 한 후 각 프로젝트 별로 설정 한다. Collaboration : 넷빈즈 4.1 부터 번들되기 시작한 모듈로 넷빈즈에 채팅 기능이 들어가 있다고 생각 하면 된다.. 하지만 IDE에 단순 채팅 기능이 들어가 있다면 큰 의미가 없을 것이다. Collaboration 은 다른 넷빈즈 개발자와 함께 같은 소스를 공유하고 함께 수정,컴파일, 실행 해 볼 수 있다.. 자리를 바꿔가며 개발하는 XP가 아닌 자신의 PC에서 원격의 상대방과 함께 진행하는 XP가 가능하다. Editing : 말 그대로 IDE에 있는 편집기에 대한 각종 설정을 할 수 있다. IDE Configuration : IDE메뉴와 메뉴아이콘에 대한 설정이 들어있으며 메뉴의 이동 삭제 추가 등을 설정 한다. Source Creation and Managemenrt : 각종 소스 타입에 따른 템플릿과 버전관리 시스템을 관리한다. 기본적으로 등록되어 있는 템플릿만 100여개 남짓하며, 물론 기존 템플릿을 수정 하거나 자신만의 템플릿을 등록 하는것도 가능하다. 버전관리 시스템은 기본적으로 CVS, PVCS, Visual Source Safe 를 사용 할 수 있도록 준비 되어 있다. Testing : 넷빈즈에는 JUnit이 번들되어 있어 별다른 설정없이 유닛 테스트가 가능하며 Update Center ( http://www.yunsobi.com/tt/subby/37 )를 통해 NetBeans Extention JUnit 이나 Jellytools, Jemmy Module 같은 3rd-party 테스트 모듈을 추가 할 수 있다.
위 에서 각 옵션 항목에 대해 간략 히 소개 했다.. 이번 강좌에서 저 몹션들을 모두 다루는건 무리일 것이다. 이번 강좌에는 넷빈즈를 처음열고 꼭 수정해야만 한다고 생각되고, 개발자들이 IDE에서 가장 많이 접하는 Editing 항목만 다루려한다. 나머지 부분은 강좌를 진행하면서 필요할 경우 그때그때 언급하기로 하겠다.
Editing - Editor Settings 항목을 펼치면 각 파일 타입에 대한 항목을 볼 수 있다.
오른편의 Global Key Bindings 항목을 연 화면이다. 소스를 편집하는 데 필요한 기능과 그에대한 단축키를 설정 할 수 있다.
파일 타입에 해당하는 Editor 항목을 열면 오른편 메뉴에서 Abbreviations 항목을 설정 할 수 있다. 말그대로 약어를 등록 해둘 수 있다. 위 화면을 예로들자면 CSS파일 편집시 에디터에 bg 라고 타이핑 하면 background: 로 대치된다.
자.. 우리가 손봐야할 자바 소스 에티터 항목을 열었다. 참 많이도 설정 가능하다..
하나하나 다 소개 하고 싶지만 항목을 선택하면 아래쪽에 간략한 소개글이 나오니 필요한 부분을
취향에 맞게 고치면 되겠다.
여기서는 글 서두에서 밝힌대로 폰트 설정과 줄번호 항목에 대해서만 짚고 넘어가겠다.
줄번호 항복은 오른편 메뉴에서 위에서 아홉번째 Line Numbers 항목에 있다.
디폴트로 체크 해제된 상태이다. 체크하면 에디터에서 라인 번호가 표시된다.
그 다음, 위에서 네번째 Font & Colors 항목 을 클릭하면 아래와 같은 대화 상자가 뜬다.
여기서 취향에 맞는 폰트와 색상을 지정하면 된다. 기본적으로 Monospaced 12 Plain
폰트를 사용하고 있다. Monospaced 폰트가 마음에 들지 않는 사람이라면 왼편 신택스 항목의 첫번째 Default 항목에서 Font를 수정하면 모든 신택스에 대해 일괄적으로 변경이 적용되며 각 신택스에 대한 고유한 설정은 신택스 항목을 선택하고 따로 변경 해 주면 된다. 위 화면에서 예시한 Java Keyword의 경우 bold의 Monospaced 라 글자를 알아보기 힘들다. 오른편 폰트 에어리어 아래쪽의 Inherit 항목을 체크하면 Default 항목으로 선택되어 있는 Font 항목이 적용 된다. Background color 나 Foreground color 역시 각각 설정 가능하며 Inherit 항목을 체크 해 Default 값을 적용 할 수도 있다.
Java Keyword의 Font를 Default font인 Monospaced plain 을 inherit 했다.폰트가 깔끔해 졌다. HTML,JSP,XML 역시 위와 같은 방법으로 원하는 형태로 에디터를 설정 하면 된다.
자.. 여기서 끝이 아니다. 한가지 더 짚고 넘어갈게 있다. 이 상태에서 프로젝트를 생성해서 에디터를 열고 소스를 편집하면 잘 눈 치채지 못하지만 사용하다보면 '어, 이거 왜 이렇게 해놨지?' 하고 생각 하게되는 부분이 있다.
보통 다른 편집기에서 tab 키를 일반 공백문자 4칸 혹은 8칸 크기로 설정해서 사용 할것이다. 넷빈즈의 경우도 다르지않다. 디폴트로 4칸의 공백문자 크기를 갖는데 문제는 에디터에서 tab 키를 누르면 공백문자 크기만큼 커서가 전진 하지만 tab문자로 전진하지 않고 space bar 를 네번 눌러 전진 한것처럼 동작한다.
혼자 개발 할때는 별 문제 될게 없다. 넷빈즈 에디터에서 열면 들여쓰기가 잘 된걸로 표현 될테니까.. 하지만 보통은 협업으로 이루어지며, 다른 개발자도 넷빈즈를 사용한다고 볼 순 없다. 넷빈즈에서 에디팅한 소스를 다른 개발자가 다른 편집기에서 열어보면 짜증을 낼거다. code indent는 잘 맞추어져있는데 탭을쓰면되는 곳에 왜 스페이스로 들여쓰기를 했냐고.. 이 부분은 Editing - Indentation Engines 항목을 통해 교정이 가능하다.
Editing - Indentation Engines - Java Indentation Engine 항목을 클릭하여 오른편 Properties 항목을 보자. 첫번째 항목으로 Expand Tabs to Spaces 항목에 체크 되어 있다. 이 체크를 해제하면 비로소 tab 이 여타의 편집기에서 tab 키를 누른것처럼 기록 될 것이다. 항목 맨 아래쪽의 Statement Continuation Indent 는 여러줄에걸친 코드에서 다음줄로넘어간 경우 처음 탭 크기를 설정하는 부분이며 Number of Spaces per Indent 는 우리가 일반적으로 알고있는 tab에대한 space사이즈 설정부분이다.
마지막으로 짚고 넘어갈게 개발하다보면 자바소스를 특별히 인코딩 방식을 주어 개발해야하는 상황이 종종 발생한다.
Editing - Java Sources 에서 오른편 Default Encoding 항목에서 설정 할 수 있지만 특별한 경우가 아니라면 이곳에 인코딩 설정을 하는것은 추천하지 않는다. 지금까지 살펴본 옵션사항은 각각의 프로젝트에 따르게 적용되는부분이 아닌 넷빈즈 전체에 관한 설정이기 때문이다. 특별히 java 소스에 대한 인코딩이 필요한 프로젝트라면 프로젝트 생성 후 각 프로젝트에 대한 컴파일 인코딩옵션을 설정하는( -encoding UTF-8 , 이 방법 또한 이후 강좌에서 언급하겠다. ) 방식을 추천한다.