C# 레지스트리에 값을 쓰고, 읽고, 삭제하기

2007/09/28 19:31

서비 .NET & WPF ,

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;  // 레지스트리관련 클래스를 쓰기위해서 추가

namespace regiEx1
{
    class Program
    {
        static void Main(string[] args)
        {
            string regSubkey = "Software\\myTestKey";
            // 서브키를 얻어온다. 없으면 null
            RegistryKey rk = Registry.LocalMachine.OpenSubKey(regSubkey, true);
            // 없으면 서브키를 만든다.
            if (rk == null)                                                         
            {
                // 해당이름으로 서브키 생성
                rk = Registry.LocalMachine.CreateSubKey(regSubkey);                 
            }
            string[] strData = new string[] {"aaa","bbb","ccc"};
            // 서브키 아래 값 쓰기
            rk.SetValue("test", strData);
            // 서브키 아래 값 읽기
            string[] regStr = rk.GetValue("test") as string[];                     
           
            Console.WriteLine(regStr[1]);
            Console.ReadLine();

            // 서브키 삭제
            Registry.LocalMachine.DeleteSubKey(regSubkey);                         
        }
    }
}
2007/09/28 19:31 2007/09/28 19:31
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

[STAThread] 어트리뷰트는 뭘 의미하는거지?

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace Petzold.DisplaySomeText
{
    public class DisplaySomeText : Window
    {
        [STAThread]
        public static void Main()
        {
            Application app = new Application();
            app.Run(new DisplaySomeText());
        }
        public DisplaySomeText()
        {
            Title = "Display Some Text";
            Content = "Content can be simple text!";
        }
    }
}

와 같은 C# 코드에서 [STAThread] 가 의미하는 바는 :
기본적으로, VS .NET에 의해 만들어진 응용 프로그램의 Main()메소드에는 [STAThread] 어트리뷰트가 첨가되어 있다.
이 어트리뷰트는 해당 응용 프로그램이 COM형식을 이용하는 경우에 (단지 이 경우에만 해당하는 것인데) 해당 응용 프로그램이
단일 스레드 아파트(single threaded apartment, STA) 모델로 설정되어야 한다는 것을 런타임에게 알려주는 역할을 한다.
해당 응용 프로그램에서 COM 형식을 이용하지 않는다면, [STAThread] 어트리뷰트는 무시되기 때문에 삭제해도 무방하다.
2007/09/28 16:41 2007/09/28 16:41
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

BigDecimal의 필요성

부동 소수점 숫자 사용은 재미있는 작업이 될 수 있다. 일반적으로 양을 다룰 때 정수가 아니라면 double 형식을 사용하려고 한다. 정수인 경우 일반적으로 int 형식이면 충분하다. 값의 크기에 따라 float나 long도 효과적일 수 있다. 그러나 화폐를 다룰 경우, 이 형식들은 최악의 선택이 되는데, 정확한 값을 제공하지 않을 때도 있기 때문이다. 단지 이진수 형식에 저장 가능한 값만 제공한다. 다음은 합계를 계산하거나 할인을 반영하거나 부가가치세를 추가할 때 double을 사용하면 겪게 되는 문제를 보여주는 간단한 예제이다.

Calc 프로그램은 금액 $100.05로 시작한 다음 사용자에게 10% 할인을 적용하고 다시 5% 부가가치세를 추가한다. 부가가치세율은 다를 수 있지만, 이 예제에서는 5%를 사용한다. 그 결과를 보자면, 이 클래스는 NumberFormat 클래스를 사용하여 결과의 형식을 지정하는데, 이 결과는 통화로 표시되어야 한다.

import java.text.NumberFormat;

public class Calc {

  public static void main(String args[]) {
    double amount = 100.05;
    double discount = amount * 0.10;
    double total = amount - discount;
    double tax = total * 0.05;
    double taxedTotal = tax + total;
    NumberFormat money = NumberFormat.getCurrencyInstance();
    System.out.println("Subtotal : "+ money.format(amount));
    System.out.println("Discount : " + money.format(discount));
    System.out.println("Total : " + money.format(total));
    System.out.println("Tax : " + money.format(tax));
    System.out.println("Tax+Total: " + money.format(taxedTotal));
  }
}

모든 내부 계산에 double 형식을 사용하면 결과는 다음과 같다.

Subtotal : $100.05
Discount : $10.00

Total : $90.04
Tax : $4.50
Tax+Total: $94.55

가운데의 Total이 기대하는 값일 수 있으나, 맨 끝의 Tax+Total 값에서 끝난다. 할인은 $10.01이 되어야 $90.04의 금액을 얻는다. 해당 부가가치세를 추가하고 나면 최종 합계가 1센트 늘어나 있다. 세무 당국은 이 점을 이해하지 못할 것이다. 반올림 오류가 문제이다. 이 반올림 오류를 바탕으로 계산한 것이다. 다음은 형식이 지정되지 않은 값이다.

Subtotal : 100.05
Discount : 10.005

Total : 90.045
Tax : 4.50225
Tax+Total: 94.54725

형식이 지정되지 않은 값을 살펴보자면, 맨 처음 떠오르는 질문은 왜 90.045가 90.05 대신 90.04가 되었는가이다. (즉, 왜 10.005가 10.00으로 반올림되는가?) 이는 소위 RoundingMode에 의해 제어되는데, 이는 Java SE 1.6에 도입된 계수법으로서 이전 릴리스에서는 그러한 제어가 없었다. 통화를 위해 도입된 NumberFormat은 기본 반올림 모드가 HALF_EVEN이다. 즉, 남은 값이 등거리(equidistant)이면 짝수 쪽으로 라운딩된다. 이 계수법에 대한 Java 플랫폼 설명서에 따르면, 통계상 이 방법은 여러 차례의 계산을 거친 후 누적 오류를 최소화한다.

RoundingMode
계수법에서 사용 가능한 또 하나의 모드는 다음과 같다.

  • CEILING 항상 양수 무한대로 라운딩한다.
  • DOWN 항상 0으로 라운딩한다.
  • FLOOR 항상 음수로 라운딩한다.
  • UP 항상 0으로부터 벗어나서 라운딩한다.
  • HALF_DOWN 항상 가장 가까운 인접 수로 라운딩한다. 단, 두 인접 수 모두 등거리라면 버림한다.
  • HALF_UP 항상 가장 가까운 인접 수로 라운딩한다. 단, 두 인접 수가 모두 등거리라면 올림한다.
  • UNNECESSARY 라운딩할 필요 없이 정확한 결과를 선언한다.

이 문제의 해결 방법을 알아보기에 앞서, 약간 다른 결과를 살펴 보도록 한다. 70센트로 시작하고 할인이 없는 경우이다.

Total : $0.70
Tax : $0.03

Tax+Total: $0.74

70센트 거래의 경우, 단순히 라운딩 문제가 아니다. 형식 지정 없이 값을 보자면 다음과 같다.

Total : 0.7
Tax : 0.034999999999999996

Tax+Total: 0.735

부가가치세인 0.035는 double로 저장할 수 없다. 즉, 이진 형식에서 double로 표현할 수 없다.

BigDecimal
클래스는 float 및 double을 사용하는 부동 소수점 연산에서 생기는 문제 몇 가지에서 도움이 된다. BigDecimal 클래스는 사실상 무제한적인 정밀도로 부동 소수점 숫자를 저장한다. 이 데이터를 다루기 위해 add(value), subtract(value), multiply(value) 또는 divide(value, scale, roundingMode) 메소드를 호출한다.

BigDecimal
값을 출력하려면 setScale(scale, roundingMode)로 단위 및 라운딩 모드를 설정하거나 toString() 또는 toPlainString() 메소드를 사용한다. toString() 메소드는 과학적 표기를 사용할 수 있지만, toPlainString()은 그렇지 않다.

BigDecimal
을 사용하도록 프로그램을 변환하기 전에 그 생성 방법을 확인하는 것이 중요하다. 이 클래스에는 16개의 구성자가 있다. BigDecimal의 값을 double과 같은 primitive 객체에 저장할 필요는 없으므로 String으로부터 BigDecimal 객체를 만드는 것이 가장 좋다. 이 오류를 보여 주기 위해 간단한 예제를 소개한다.

  double dd = .35;
  BigDecimal d = new BigDecimal(dd);

  System.out.println(".35 = " + d);

예상했던 출력이 아닐 것이다.

  .35 = 0.34999999999999997779553950749686919152736663818359375

그보다는 여기서 보여주는 것처럼 string "35"를 직접 사용하여 BigDecimal을 만들어야 한다.

  BigDecimal d = new BigDecimal(".35");

그 결과 다음과 같은 출력을 얻는다.

 .35 = 0.35

값을 생성한 후 숫자의 단위와 라운딩 모드를 setScale()을 사용하여 명시적으로 설정할 수 있다. Java 플랫폼의 다른 Number 하위 클래스와 마찬가지로, BigDecimal은 변경할 수 없다. 따라서 setScale()을 호출할 경우 반환 값을 "저장"해야 한다.

  d = d.setScale(2, RoundingMode.HALF_UP);

BigDecimal
을 사용하여 수정된 프로그램은 다음과 같다. 계산마다 또 다른 BigDecimal을 사용하고 그 단위를 설정해야 달러 및 센트에 대한 수학 연산이 수행된다. 분할 페니를 사용하려는 경우 단위에서 3개의 소수 자리로 갈 수도 있지만, 꼭 그럴 필요는 없다.

import java.math.BigDecimal;
import java.math.RoundingMode;


public class Calc2 {
  public static void main(String args[]) {
    BigDecimal amount = new BigDecimal("100.05");
    BigDecimal discountPercent = new BigDecimal("0.10");
    BigDecimal discount = amount.multiply(discountPercent);
    discount = discount.setScale(2, RoundingMode.HALF_UP);
    BigDecimal total = amount.subtract(discount);
    total = total.setScale(2, RoundingMode.HALF_UP);
    BigDecimal taxPercent = new BigDecimal("0.05");
    BigDecimal tax = total.multiply(taxPercent);
    tax = tax.setScale(2, RoundingMode.HALF_UP);
    BigDecimal taxedTotal = total.add(tax);
    taxedTotal = taxedTotal.setScale(2, RoundingMode.HALF_UP);
    System.out.println("Subtotal : " + amount);
    System.out.println("Discount : " + discount);
    System.out.println("Total : " + total);
    System.out.println("Tax : " + tax);
    System.out.println("Tax+Total: " + taxedTotal);
  }
}

여기서는 NumberFormat이 사용되지 않았다. 하지만 통화 기호를 보여주고 싶다면 다시 추가할 수 있다.

이제 프로그램을 실행하면 훨씬 나은 계산이 수행된다.

Subtotal : 100.05
Discount : 10.01

Total : 90.04
Tax : 4.50
Tax+Total: 94.54

BigDecimal
은 이 예제에서 보여준 것보다 더 다양한 기능을 제공한다. 정수를 사용하는데 무한대의 정밀도가 필요한 경우를 위한 BigInteger 클래스도 있다. 두 클래스에 대한 Java 플랫폼 설명서에서는 단위, MathContext 클래스, 정렬 및 동등(equality)에 대한 정보를 비롯하여 이 클래스와 관련된 자세한 내용을 확인할 수 있다.

저자 JZ Ventures사의 사장 겸 대표 컨설턴트 John Zukowski

2007/08/30 16:00 2007/08/30 16:00
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

JavaMail :: 인증을 요하는 메일 SMTP에 접속하는 방법

2007/08/22 16:33

서비 JAVA , , ,

얼마전 인증을 해야하는 SMTP에 연결해야 했는데 그 방법 때문에 매우 고민했었습니다.
Mail API에서 Authenticator는 제공을 하면서 ID와 PW를 설정하는 부분이 없어서
참 난감했었습니다. 영어 실력이 짧아서 관련 Document를 읽어보다가 못찾고
다만 API에 있는 Session 클래스의
getInstance(Properties prop, Authenticator auth) 메소드가 마음에 걸려
혹시나 하는 마음으로 만들어 봤었는데 잘 되더군요.
sendmail 과 qmail 및 윈도기반 smtp server 몇 곳을 테스트 해봤는데 잘 되었습니다.

클래스 전체 소스는 다음과 같습니다.

/*
* MyAuthenticator.java
* 2002.05.27.
* Hyunho Kim(falllove@ducc.or.kr)
*
* Copyrightⓒ 2002 FancyArts Corps. All rights reserved.
*/

/**
* SMTP Authenticator
*/
public final class MyAuthenticator extends javax.mail.Authenticator {

    private String id;
    private String pw;

    public MyAuthenticator(String id, String pw) {
        this.id = id;
        this.pw = pw;
    }

    protected javax.mail.PasswordAuthentication getPasswordAuthentication() {
        return new javax.mail.PasswordAuthentication(id, pw);
    }

}

사용하실 때는 session을 얻기 전에 프로퍼티에 mail.smtp.auth 항목을 추가하시고
MyAuthenticator의 인스턴스를 하나 만드셔서 인자로 넘기시면 됩니다.

Properties prop = new Properties();
prop.put("mail.smtp.host", "smtp_host_name");
prop.put("mail.smtp.auth", "true");

MyAuthenticator auth = new MyAuthenticator("smpt_id", "smtp_pw");

// 새로운 메일 세션
Session mailSession = Session.getInstance(props, auth);
// or Session mailSession = Session.getDefaultInstance(props, auth);

정말 간단하죠^^;; 정말 암 것도 없는데 전에 이것을 하기 위해서 그 많은 글을 뒤적일
때는 못찾겠더라구요. 전 만들긴 했었지만 실제 서비스는 sendmail에서
localhost relay를 허용해서 사용하고 인증없이 발송하고 있습니다.
2007/08/22 16:33 2007/08/22 16:33
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

몇가지 사소한 넷빈즈 팁

Did you know that Control-K automatically completes any previously typed strings from within the same editor? Similarly, Control-L completes previously typed strings found after the insertion point. Press either shortcut repeatedly to cycle through all possible completions.

Use shortcuts to speed up your work: Typing a colon and then hiting the tabkey inserts a hash entry of the form :key => "value". Pressing # inside a double quoted string will insert #{} with the caret in the middle. See the lost of all Ruby keyboard shortcuts in NetBeans IDE.

Did you know that you can open files that are not part of a project in the Favorites window? Select Windows > Favorites for a customizable file browser. You can also choose Navigate > Go to File or Go to Type from the menu to search for files and resources in your projects.
2007/08/05 19:24 2007/08/05 19:24
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

컴포넌트 시스템과 클래스 로더 경계 :: Java Class Loader

어느 곳에서나 소프트웨어 개발자들은 단일 애플리케이션 개발 전략을 넘어서서 상호작용(interoperating) 애플리케이션 시스템 개발로 나아가고 있다. 현재 엔터프라이즈 개발자들이 당면하고 있는 문제들 중 하나는 최근의 WAR 와 EAR 파일 등 Web and Enterprise Archives 같은 단일 애플리케이션 디플로이먼트 구조에서 느슨하게 결합된 애플리케이션 컴포넌트 시스템으로 어떻게 옮겨갈 것인가 이다. 개발자들은 J2EE 플랫폼의 기본 기능과 호환됨과 동시에 코드 재사용을 늘리고 애플리케이션 복잡성을 줄이기 위해 컴포넌트 기반 애플리케이션 전략을 사용하길 원한다. JARs, WARs, EARs 등과 같이 플러그할 수 있는 컴포넌트 아카이브를 개발하는 것이 이 전략의 관건이 될 것이다.

그러나 J2EE에서의 컴포넌트 기반 애플리케이션 시스템 개발과 배포를 둘러싼 많은 함정이 도사리고 있다. 중요 함정 중 한가지는 Java 버추얼 머신에서 클래스 로더의 경계를 교차하여 기능하기 위한 컴포넌트 시스템을 얻는데 따르는 어려움이다.

클래스 로더는 환경 구획의 의미로 생각하면 된다. 명심해야 할 것은 클래스 로더는 하위 클래스 로더들을 생성할 수 있으며, 따라서 하위환경을 만들어낼 수 있다는 것이다. 이 하위 클래스 로더 환경의 클래스들은 윗 계급인 시스템 클래스 로더쪽을 볼 수 있지만 더 하위 계급의 클래스들은 볼 수 없다.

이런 종류의 다단계적 환경 구조는J2EE 서버 환경에서 매우 보편적이며 심지어 J2EE 기술을 둘러싼 다양한 규격에는 권장되고 있는 사항이다. 예를 들어 서블렛들은 각각 WAR 파일에 패키지화되어 있으며, 오버롤 시스템의 각각의 클래스 로더 컨텍스트에 디폴트값으로 주어져있다. 이는 WEB-INF/lib 디렉토리에 포함되어 있는 JAR 파일은 다른 웹 아카이브의 JAR 파일 안에 저장되어 있는 클래스들에 접근하지 못한다는 것을 의미한다. 단일 애플리케이션에 패키징하기 위해 WAR을 사용할 때 이 배열은 허용가능하나 올인원 전략을 넘어서서 좀 더 컴포넌트 기반 애플리케이션 아키텍처로 나아간다면 바로 문제가 되어버릴 것이다.

이런 문제들의 증상은 확실하지 않을 때가 많으므로, 클래스 로더 경계를 교차하여 컴포넌트를 전달하지 못한다는 것을 진단하기는 매우 어렵다. 예를 들어 웹 애플리케이션 아카이브의 서블렛들 사이로 전달시키고자 하는 Foo라는 이름의 오브젝트가 있다고 하자(어느 한 웹 아카이브 안의 서블렛에서부터 다른 웹 파카이브의 서블렛으로 전달됨을 의미). Foo컴포넌트를 위한 클래스 파일은 JAR 파일에 패키징되어 있으며 JAR 파일의 동일한 복사본이 각 웹 아카이브의 /WEB-INF/lib 디렉토리 안에 있다. 다음의 코드를 작성하자.

   /* This code runs in a servlet in WAR #1 */
   SystemScopeObjectCache cache = 
       SystemScopeObjectCacheFactory.getInstance();

   WARScopeFoo foo = new WARScopeFoo ();
   System.out.println(foo);
   cache.addToCache("myFooObject", foo);

   /* This code runs in a servlet in WAR #2 */
   SystemScopeObjectCache cache = 
       SystemScopeObjectCacheFactory.getInstance();
   Object o = cache.getFromCache("myFooObject");

   try {
     // the following throws a ClassCastException! 
     WARScopeFoo foo = (WARScopeFoo)o; 
        
   } catch(ClassCastException e) {
     e.printStackTrace();
   }

각 웹 아카이브의 서블렛에 접근가능한 WARScopeFoo 오브젝트를 위한 컴포넌트 캐시를 쉽게 생성할 수 있다. 그러나 만약 서블렛 A가 오브젝트의 인스턴스를 중앙 캐시로 전달하고, 다른 WAR의 서블렛 B가 캐시로부터 인스턴스를 꺼내 Object에서 WARScopeFoo로 전달하려한다면 시스템은 ClassCastException을 전달하게 될 것이다.

이 상황은 클래스 로더를 고려한 것이 아니라면 아무 의미가 없다. 서블렛 A 에서 참조하는 WARScopeFoo 클래스는 서블렛 B에서 참조하는 WARScopeFoo클래스와 전혀 연관 없는 다른 클래스 로더로부터 생성된다. 정확히 얘기하자면, 그 둘은 의도적으로 전혀 연관되어 있지 않다. 이것은 같은 서블렛 컨테이너에서 구동되는 웹 애플리케이션 간의 네임스페이스 보전성을 강화하기 위한 안전한 매커니즘이다.

클래스 로더가 충돌한다는 또 다른 신호는 시스템의 단일 클래스에서 다중 인스턴스를 찾았을 때이다. (단일 클래스에는 단 한 개의 클래스 인스턴스가 생성되어야 한다.) 기술적으로 단일 인스턴스는 그 클래스 로더 안에서만 독자적일 뿐이다. 따라서 클래스 로더 단계에서 단일성에 의지하는 것은 위험하다. 다음의 예제를 보자.

   /* MyServlet in WAR #1 */
   WARScopeSingleton cache = WARScopeSingleton.getInstance();
   WARScopeFoo foo = new WARScopeFoo ();
   cache.add("myFooObject", foo);
   System.out.println(cache.length()); //output is 1

   /* MyOtherServlet in WAR #2 */
   WARScopeSingleton cache = WARScopeSingleton.getInstance();
   System.out.println(cache.length()); //output is 0!

이 예제에서 코드는 단일 클래스에서의 오브젝트들을 저장한다. 그러나 단일 클래스에서의 스코프는 WAR 클래스 로더를 필연적으로 발생시킨다.

다행히 엔터프라이즈 Java 개발자들을 위해 이런 장애들을 극복할 몇 가지 방법이 있다. 첫번째 단계는 어떤 클래스 로더 경계가 늘 존재하는 것이 보장되는지 확인하고 그에 대한 전략을 계획하는 것이다. 모든 J2EE 환경에는 세 개의 클래스 로더 레벨이 구축되어 있다. 시스템 단계의 컨텍스트는 대부분 VM을 교차하며 J2SE와 J2EE 플랫폼의 클래스와 호환된다. 이것은 애플리케이션 서버가 자체적으로 구동하는 레이어이다.

다음 단계는 Enterprise Archive 컨텍스트인데, 이는 엔터프라이즈 애플리케이션의 모든 JAR와 WAR를 포함하고 있다.

마지막 단계는 Web Archive 컨텍스트이며, 이는 WAR 파일 /WEB-INF/classes디렉토리의 모든 파일과 /WEB-INF/lib 디렉토리의 모든 JAR 파일을 포함하고 있다. WAR 파일 안에 로딩된 모든 클래스가 상호 접근 가능함은 물론이고 EAR 파일과 System 클래스 로더의 클래스들과도 접근가능하지만 다른 WAR 파일 안에 로딩된 클래스들과는 접근될 수 없다.

따라서 웹 아카이브 간의 사용자 비즈니스 오브젝트를 공유하고자 한다면 오브젝트의 JAR 파일을 EAR 클래스 로더 컨텍스트에 위치시키고 WAR 파일의 /WEB-INF/lib 디렉토리는 피하는 게 좋다. 다음은 그 예이다.

   /* MyServlet in WAR #1 */
   EARScopeCache cache =EARScopeCache.getInstance();
   EARScopeFoo foo = new EARScopeFoo();
   cache.add("MyFooObject", foo);

   /* MyServlet in WAR #2 */
   EARScopeCache cache = EARScopeCache.getInstance();
   Object o = cache.get("myFooObject");
   EARScopeFoo foo = (EARScopeFoo)o; //SUCCESS!

이 작업의 이유는 각각의 서블렛은 상위 계급의 클래스 로더를 추구하고 WAR 클래스 로더 컨텍스트에 클래스들을 로딩하는 대신 같은 EARScopeCacheEARScopeFoo오브젝트를 찾는다는 데에 있다.

그러나, EAR 파일을 조합하는 것은 서블렛 규격의 참조 구현인 Tomcat 같은 어떤 컨테이너 에서는 옵션이 아니다. Tomcat은 EAR 파일에서 클래스들을 로딩하는 정보처리 기능이 없으며 따라서 다중 웹 아카이브에 클래스들을 교차할 수 있게 만들지 못한다. 그러나 Tomcat은 일반 클래스 로더를 보유하여 /common디렉토리의 모든 JAR 파일을 모든 웹 아카이브 컨텍스트위에 직접적으로 존재하여 접근가능한 클래스 로더 스페이스에 로딩한다.

생각해볼 수 있는 다른 기술로는 서로 다른 컴포넌트 하위 시스템 간에 데이터를 왕복전달하기 위해Java 배열을 사용하는 것이 있다. 두개의 애플리케이션으로부터의 컴포넌트가 바이트로 배열된 데이터를 저장할 수 있는 공유 장소가 있고, 버전 충돌이 잠재적으로 있다는 사실을 배제하면 이 전략은 빠르고 효과적이다.

원문 출처 : http://kr.sun.com/developers/techtips/e2004_0824.html#2

2007/07/22 13:39 2007/07/22 13:39
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

문자열 오브젝트의 길이는?

여러분의 텍스트 스트링의 길이는 얼마나 되는가? 사용자 입력이 데이터 필드 길이 제한 조건에 부합하는지 확인하기 위해선 그 답이 필요하다. 데이터베이스의 텍스트 필드는 입력을 일정한 길이로 제한하는 경우가 많으므로 텍스트를 제출하기 전에 길이를 확인할 필요가 있다. 이유야 어쨌든, 누구든지 이따금씩 텍스트 필드의 길이를 알아야 할 때가 있다. 많은 프로그래머들이 String 오브젝트의 length 메소드를 이용하여 그 정보를 얻으며, 대개의 경우 length 메소드는 올바른 솔루션을 제공한다. 하지만 이것이 String 오브젝트의 길이를 결정하는 유일한 방법은 아니며, 언제나 올바른 것도 아니다.

자바 플랫폼에서 텍스트 길이를 측정하는 데 세 개 이상의 일반적인 방법이 있다.

  1. char 코드 유닛의 수
  2. 문자 또는 코드 포인트의 수
  3. 바이트의 수


char
유닛 수 세기

자바 플랫폼은 유니코드 표준 을 사용하여 문자를 정의하는데, 이 유니코드 표준은 한때 고정 폭(U0000~UFFFF 범위 내의 16비트 값)으로 문자를 정의했다. U 접두사는 유효한 유니코드 문자 값을 16진수로 표시한다. 자바 언어는 편의상 char 타입에 대해 고정 폭 표준을 채택했고, 따라서 char 값은 어떠한 16비트 유니코드 문자라도 표현이 가능했다.

대부분의 프로그래머는 length 메소드에 대해 잘 알고 있고 있을 것이다. 아래의 코드는 예제 문자열에서 char 값의 수를 세는데, 예제 String 오브젝트에는 자바 언어의 \u 표기법으로 정의되는 몇 개의 단순 문자와 기타의 문자가 포함되어 있다는 점에 유의하기 바란다. \u 표기법은 16비트 char 값을 16진수로 정의하며 유니코드 표준에서 사용되는 U 표기법과 유사하다.

private String testString = "abcd\u5B66\uD800\uDF30";
int charCount = testString.length();
System.out.printf("char count: %d\n", charCount);

length 메소드는 String 오브젝트에서 char 값의 수를 센다. 이 예제 코드는 다음을 출력한다.

char count: 7


문자 유닛 수 세기

유니코드 버전 4.0이 UFFFF 이상 되는 상당히 큰 수의 새로운 문자를 정의하면서부터 16비트 char 타입은 더 이상 모든 문자를 표현할 수 없게 되었다. 따라서 자바 플랫폼 J2SE 5.0버전부터는 새로운 유니코드 문자를 16비트 char 값의 쌍(pair)으로 표현하며, 이를 surrogate pair 라고 부른다. 2개의 char 유닛이 U10000~U10FFFF 범위 내의 유니코드 문자를 대리 표현(surrogate representation)하는데, 이 새로운 범위의 문자를 supplementary characters 라고 한다.

여전히 단일 char 값으로 UFFFF까지의 유니코드 값을 표현할 수는 있지만, char 대리 쌍(surrogate pair)만이 추가 문자(supplementary characters)를 표현할 수 있다. 쌍의 최초값 또는 high 값은 UD800~UDBFF 범위에, 그리고 마지막값 또는 low 값은 UDC00~UDFFF 범위에 속한다. 유니코드 표준은 이 두 범위를 대리 쌍을 위한 특수 용도로 배정하고 있으며, 표준은 또한 UFFFF를 벗어나는 대리 쌍과 문자 값 사이의 매핑을 위한 알고리즘을 정의한다. 프로그래머는 대리 쌍을 이용하여 유니코드 표준 내의 어떠한 문자라도 표현할 수 있는데, 이런 특수 용도의 16비트 유닛을 UTF-16 이라고 부르며, 자바 플랫폼은 UTF-16을 사용하여 유니코드 문자를 표현한다. char 타입은 이제 UTF-16 코드 유닛으로, 반드시 완전한 유니코드 문자(코드 포인트)가 아니어도 된다.

length 메소드는 단지 char 유닛만을 세기 때문에 추가 문자는 셀 수 없다. 다행히도 J2SE 5.0 API에는 codePointCount(int beginIndex, int endIndex) 라는 새로운 String 메소드가 추가되었다. 이 메소드는 두 인덱스 사이에 얼마나 많은 유니코드 포인트(문자)가 있는지 알려주는데, 인덱스 값은 코드 유닛 또는 char 위치를 참조한다. 식 endIndex - beginIndex 의 값은 length 메소드가 제공하는 값과 동일하며, 이 둘의 차이는 codePointCount 메소드가 반환하는 값과 항상 동일하지는 않다. 텍스트에 대리 쌍이 포함되어 있는 경우에는 길이 수가 확실히 달라진다. 대리 쌍은 하나 또는 두 개의 char 유닛으로 된 단일 문자 코드 포인트를 정의한다.

하나의 문자열에 얼마나 많은 유니코드 문자 코드 포인트가 있는지 알아보려면 codePointCount 메소드를 사용하도록 한다.

private String testString = "abcd\u5B66\uD800\uDF30";
int charCount = testString.length();
int characterCount = testString.codePointCount(0, charCount);
System.out.printf("character count: %d\n", characterCount);

이 예제는 다음 내용을 출력한다.

character count: 6

testString 변수에는 두 개의 흥미로운 문자가 포함되어 있는데, 그것은 '배움(learning)'이라는 뜻의 일본어 문자와 GOTHIC LETTER AHSA 라는 이름의 문자이다. 일본어 문자는 유니코드 코드 포인트 U5B66을 사용하며, 이는 동일한 16진수 char 값 \u5B66을 가진다. 고딕체 글자의 코드 포인트는 U10330이고, UTF-16에서 고딕체 글자는 대리 쌍 \uD800\uDF30이다. 이 쌍은 단일 유니코드 코드 포인트를 표현하므로 전체 문자열의 문자 코드 포인트 수는 7이 아니라 6이 된다.


바이트 수 세기

String 안에는 얼마나 많은 바이트가 있을까? 그 대답은 사용되는 바이트 기반 문자 세트에 의해 결정된다. 바이트 수를 알아보려는 이유 중 하나는 데이터베이스의 문자열 길이 제한 조건을 충족하기 위해서이다. getBytes 메소드는 유니코드 문자를 바이트 기반 인코딩으로 변환하며, 이는 byte[] 를 반환한다. UTF-8 은 바이트 기반 인코딩의 일종이지만, 모든 유니코드 코드 포인트를 정확하게 표현할 수 있다는 점에서 대부분의 다른 바이트 기반 인코딩과는 구별된다.

다음 코드는 텍스트를 byte 값의 배열로 변환한다.

byte[] utf8 = null;
int byteCount = 0;
try {
  utf8 = str.getBytes("UTF-8");
  byteCount = utf8.length;
} catch (UnsupportedEncodingException ex) {
  ex.printStackTrace();

System.out.printf("UTF-8 Byte Count: %d\n", byteCount);

타깃 문자 세트가 생성될 바이트의 수를 결정한다. UTF-8 인코딩은 단일 유니코드 코드 포인트를 1~4개의 8비트 코드 유닛(=1 바이트)으로 변환한다. 문자 a , b , c , d 는 모두 4 바이트만을 요구하는 한편, 일본어 문자(한국어도 같음)는 3 바이트로 변환된다. 또한 고딕체 글자는 4 바이트를 차지한다. 결국 다음의 결과가 나오는 것을 알 수 있다.

UTF-8 Byte Count: 11









그림 1. 무엇을 세느냐에 따라 문자열의 길이가 달라진다.


요약

추가 문자를 사용하지 않으면 length codePointCount 의 반환 값 간에는 차이점이 없다고 할 수 있다. 하지만 UFFFF 범위를 넘어서는 문자를 사용할 경우에는 길이를 결정하는 다양한 방법에 관해 알아야 할 필요가 있다. 예컨대 미국에서 한국으로 제품을 발송할 경우에는 십중팔구 length codePointCount 가 서로 다른 값을 반환하는 상황이 발생한다. 데이터베이스의 문자 세트 인코딩과 일부 직렬화 포맷은 UTF-8을 최선의 방법으로 권장되고 있는데, 이 경우에도 역시 텍스트 길이는 달라진다. 결국 길이를 어떻게 활용할 것인지에 따라 다양한 측정 옵션을 사용할 수 있다.


추가 정보

본 테크 팁 주제에 관한 자세한 내용을 보려면 다음 리소스를 참조할 것(영문)


2007/07/03 20:57 2007/07/03 20:57
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

JAVA.UTIL.SCANNER로 텍스트 스캔하기

JAVA.UTIL.SCANNER로 텍스트 스캔하기

J2SE 5.0에는 일상적 태스크를 좀 더 쉽게 구현할 수 있도록 하는 클래스와 메소드들이 추가되었다. 이번 팁에서는 새로 추가된 java.util.Scanner클래스를 이용함으로써 일반 표현문을 사용하는 스트링과 프리미티브 타입을 읽고 파싱(parsing)하는 것이 어떻게 좀 더 쉬워졌는지 알아보도록 하자.

J2SE 5.0의 출시 이전에는 파일에서 텍스트를 읽으려면 다음의 TextReader 클래스 같은 코드를 작성해야했다.


  1. import java.io.BufferedReader;
  2. import java.io.FileReader;
  3. import java.io.IOException;
  4. import java.io.File;
  5.  
  6.  public class TextReader {
  7.   private static void readFile(String fileName) {
  8.     try {
  9.       File file = new File(fileName);
  10.       FileReader reader = new FileReader(file);
  11.       BufferedReader in = new BufferedReader(reader);
  12.       String string;
  13.       while ((string = in.readLine()) != null) {
  14.         System.out.println(string);
  15.       }
  16.       in.close();
  17.     } catch (IOException e) {
  18.       e.printStackTrace();
  19.     }
  20.   }
  21.  
  22.   public static void main(String[] args) {
  23.     if (args.length != 1) {
  24.       System.err.println("usage: java TextReader "
  25.         + "file location");
  26.       System.exit(0);
  27.     }
  28.     readFile(args[0]);
  29.   }
  30. }

이와 같은 클래스에서의 기본적인 접근법은 하드 드라이브의 실제 파일과 일치하는 File 오브젝트를 생성하는 것이다. 그리고 나서 그 파일과 관련된 FileReader와 그 FileReaderBufferedReader를 생성하고, 그 후 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의 코드를 간단하게 할 수 있다.


  1. import java.io.File;
  2. import java.io.FileNotFoundException;
  3. import java.util.Scanner;
  4.  
  5. public class TextScanner {
  6.  
  7.   private static void readFile(String fileName) {
  8.     try {
  9.       File file = new File(fileName);
  10.       Scanner scanner = new Scanner(file);
  11.       while (scanner.hasNext()) {
  12.         System.out.println(scanner.next());
  13.       }
  14.       scanner.close();
  15.     } catch (FileNotFoundException e) {
  16.       e.printStackTrace();
  17.     }
  18.   }
  19.  
  20.   public static void main(String[] args) {
  21.     if (args.length != 1) {
  22.       System.err.println("usage: java TextScanner1"
  23.         + "file location");
  24.       System.exit(0);
  25.     }
  26.     readFile(args[0]);
  27.   }
  28. }

TextScanner를 컴파일하고 다음과 같이 구동하자.

   java TextScanner TextSample.txt

다음과 같은 결과가 나타난다.

   Here
   is
   a
   small
   text
   file
   that
   you
   will
   use
   to
   test
   java.util.scanner.

TextScanner 는 파일로부터 Scanner 오브젝트를 생성한다. Scanner는 파일의 컨텐츠를 구획자 패턴을 이용하여 분해한다. 구획자 패턴의 디폴트 값은 흰 여백이다. 그 후 TextScannerScannerhasNext() 메소드를 호출한다. 이 메소드는 Scanner 입력값에 파일의 마지막 부분에 이를 때까지 다른 token이 있으면 'true'를 리턴한다. next() 메소드는 다음 token을 나타내는 스트링을 리턴한다. 따라서 TextScanner는 파일의 마지막부분에 이를 때까지 각 라인에서 next()에 의해 리턴되는 스트링을 프린트한다.

ScanneruseDelimiter 를 이용해 입력물을 토큰화하는 데 이용하는 구획자를 변경시킬 수도 있다. 메소드에 스트링 또는 java.util.regex.Pattern에 전달해주면 된다. 어떤 패턴들이 적절한 지에 대해서는 JavaDocs page for Pattern를 참조하기 바란다. 예를 들어 newline(\n)을 구획자로 이용하여 한번에 한 줄의 입력물을 읽을 수 있다. 다음은 새줄 문자를 구획자로 이용하는 수정된 readFile() 메소드이다.


  1.    private static void readFile(String fileName) {
  2.      try {
  3.        Scanner scanner = new Scanner(new File(fileName));
  4.        scanner.useDelimiter
  5.          (System.getProperty("line.separator"));
  6.        while (scanner.hasNext()) {
  7.          System.out.println(scanner.next());
  8.        scanner.close();
  9.      } catch (FileNotFoundException e) {
  10.        e.printStackTrace();
  11.      }
  12.    }

마지막 줄을 찾는 다른 옵션들도 있다. 예를 들어 새줄 문자로 끝나는 라인이나 캐리지 리턴(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의 현재 컨텐츠를 읽고 있다.


  1.    import java.net.URL;
  2.    import java.net.URLConnection;
  3.    import java.io.IOException;
  4.    import java.util.Scanner;
  5.  
  6.    public class WebPageScanner {
  7.      public static void main(String[] args) {
  8.        try {
  9.          URLConnection connection =
  10.            new URL("http://java.net").openConnection();
  11.          String text = new Scanner(
  12.            connection.getInputStream()).
  13.            useDelimiter("\\Z").next();
  14.        } catch (IOException e) {
  15.          e.printStackTrace();
  16.        }
  17.      }
  18.    }

Scanner 클래스로 스트링 이외의 것들도 다룰 수 있다. 프리미티브 타입으로 이루어진 데이터를 파싱하는 데에도 사용할 수 있다. 이에 대한 예제로, 다음의 세 라인을 Employee.data라는 이름의 파일(TextSample와 같은 디렉토리 안)에 저장하자.

   Joe, 38, true
   Kay, 27, true
   Lou, 33, false

이를 하나의 큰 스트링으로 취급하여 이 스트링을 파싱한 후에 대화문을 실행할 수도 있지만, 대신에 이 파일을 두가지 단계로 파싱해보자. 이는 다음의 클래스 DataScanner에 설명되어 있다.


  1.    import java.util.Scanner;
  2.    import java.io.File;
  3.    import java.io.FileNotFoundException;
  4.  
  5.    public class DataScanner {
  6.  
  7.      private static void readFile(String fileName) {
  8.        try {
  9.          Scanner scanner =
  10.            new Scanner(new File(fileName));
  11.          scanner.useDelimiter
  12.            (System.getProperty("line.separator"));
  13.          while (scanner.hasNext()) {
  14.            parseLine(scanner.next());
  15.          }
  16.          scanner.close();
  17.        } catch (FileNotFoundException e) {
  18.          e.printStackTrace();
  19.        }
  20.      }
  21.  
  22.      private static void parseLine(String line) {
  23.        Scanner lineScanner = new Scanner(line);
  24.       lineScanner.useDelimiter("\\s*,\\s*");
  25.        String name = lineScanner.next();
  26.        int age = lineScanner.nextInt();
  27.        boolean isCertified = lineScanner.nextBoolean();
  28.        System.out.println("It is " + isCertified +
  29.          " that " + name + ", age "
  30.          + age + ", is certified.");
  31.      }
  32.  
  33.      public static void main(String[] args) {
  34.        if (args.length != 1) {
  35.          System.err.println("usage: java TextScanner2"
  36.            + "file location");
  37.          System.exit(0);
  38.        }
  39.        readFile(args[0]);
  40.      }
  41.    }


DataScanner의 바깥쪽 Scanner 오브젝트는 한번에 한 라인씩 파일을 읽는다. readFile() 메소드는 각 라인을 두번째 스캐너에 전달하고, 이 두번째 스캐너는 콤마로 ,구획된 데이터를 파싱하고 콤마 양쪽의 흰 여백을 삭제한다. 다음 token이 특정타입의 token인지 아닌지 분석하여 다음 token을 그 타입의 인스턴스로 취급하도록 하는 hasNext()next()메소드들도 있다. 예를 들어 nextBoolean()은 다음 token을 boolean으로 취급하여 "true" 또는 "false" 스트링과 매치시킨다. 매칭이 이뤄지지 않으면 java.util.InputMismatchException이 던져진다. DataScannerparseLine() 메소드는 각 라인이 어떻게 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" 에 매치되지 않기 때문이다. 일반적인 표현문의 모든 애플리케이션에 해당되는 케이스이므로 패턴을 구축하는 데 있어서 특히 세심한 주의가 필요할 것이다.

Scanner에 대한 좀 더 자세한 정보는 formal documentation를 참고하기 바란다.

저자 : Daniel H. Steinberg
원문 출처 : http://kr.sun.com/developers/techtips/c2004_1201.html

2007/06/24 11:28 2007/06/24 11:28
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

Vector에 대한 소고

이 기사에서는 다른 알고리즘 관련 도서와 마찬가지로 알고리즘(설계)가 중요하다고 말한다. 전적으로 동의하는 바이다. 알고리즘은 중요하다.

하지만, 전산 경력이 6년이 넘어가도록 알고리즘이 문제가 되었던 적은 알고리즘 관련 수업에서 나오는 과제와 소프트웨어 엔지니어링 수업에서 나온 이상한 과제를 할 때 뿐이었다. 그렇게 중요한 것이라면, 경력의 대부분을 차지하는 현장 경험에서는 왜 알고리즘이 중요하는 사실을 몰랐을까? 적어도 알고리즘을 사용하지 않았을리는 없고, 그렇다면 남들이 해서 숨겨 놓은 알고리즘을 나도 모르게 사용한 것이 아닐까 생각된다. 이런 일들이 가장 잘 일어나는 곳은 데이터베이스와 자바 패키지가 아닐까 생각된다. 데이터베이스는 정렬과 Search를 단 몇 단어로 가능하게 만들지만, 정렬과 Search는 알고리즘에서 가장 중요한 분야 중에 하나이며, 아직도 연구가 끝나지 않은 분야이다. 자바에서 제공하는 표준 API중에도 알고리즘을 제공하는 것이 많다.

자바 표준 API 중에서 java.util 패키지는 알고리즘과 항상 단짝이 되어 나오는 자료 구조(Data Structure)의 Implementation들을 제공하는데, 그 중에 하나가 Vector이다. 실제 얘기에 들어가기 전에 몇 가지 재미난 데이터를 살펴보자.

그림1

위 그래프는 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)이다. 아래 두 그림은 그 차이를 보여준다.

그림2

위의 그림은 마지막에 입력되는 경우를 보여주고, 아래 그림은 첫번째 위치에 입력되는 경우이다. 아래의 경우에는 입력되어 있는 데이터의 수만큼 데이터가 이동해야 한다. 만약, 10번의 입력이 일어나면, 실제로는 10 + 9 + 8 + … + 2 + 1 = 10 * 9 / 2 번의 시간이 걸니는 것이다. 데이터가 커지면 커질수록 치루어야 하는 대가는 급속하게 커진다.

정말 모든 addLast가 일정한 시간이 들어가는지를 보여주는 것이 VectorInd.java이다. 데이터 입력 시간이 3 millisecond 이상 들어가는 경우에 소요된 시간을 프린트한 것이다. 백만번 중에 7번 발생했다. 처음과 마지막 데이터를 제외하면, 데이터가 두 배가 되어질 때 시간이 많이 걸리는 현상이 발생했다. 그 이유를 그림으로 설명하면 다음과 같다.

그림3

사용자에게 보이지는 않지만, Vector는 데이터를 저장하기 위해 Array를 만들었다. 만든 Array의 사이즈는 무한하지 않기 때문에 데이터를 계속 입력하면, 공간이 부족하게 되고, Vector는 이 때 원래 사이즈의 두 배의 크기의 Array를 만들고, 가지고 있던 데이터를 새로운 Array로 이동시킨다. 즉, Array에 데이터가 입력될 때, Array가 부족하면 기존에 저장된 데이터의 크기(Array 사이즈) 만큼의 데이터 이동이 일어나게 된다. 위 자료에서 보듯이, 데이터 사이즈가 두 배에 도달할 때마다 약 2 배 가량의 시간이 더 걸리는 것을 볼 수 있다.

이때가지의 관찰결과에 따르면, Vector의 인덱스가 작은 쪽에는 입력하는 것도, 삭제하는 것도 좋지 못하다. 그만큼의 댓가(실제 데이터 사이즈 - Index만큼의 데이터 이동)를 치루어야 하기 때문이다. 그리고, 데이터가 입력되는데 걸리는 시간을 반드시 일정한 시간이하로 낮추어야 하는 경우에는 일반적인 Vector를 사용할 수 없다.

실제로 Vector는 인덱스 관리가 편리한 Array에 불과한 것을 알 수 있다. 하지만 Array 작업시 문제가 되는 것들은 여전히 Vector에서도 문제가 된다. 그러므로 처음 넣은 데이터를 가장 먼저 지워야 되는 일에는 Vector를 사용하지 않는 것이 좋다. 이 작은 사실 하나가 위 그래프에서 볼 수 있듯이 데이터가 많은 경우, 실제 프로그램의 실행속도에는 엄청난 영향을 미치게 된다.

Note : VectorInd의 작업결과는 리눅스에서 실행한 것이다. 필자의 윈도우에서는 다른 실행결과가 나왔다. 출력되는 데이터의 모든 시간은 10이였다.

[VectorTest.java]

  1. import java.util.Vector;
  2. import java.util.Calendar;
  3.  
  4. public class VectorTest {
  5.  
  6.     public static void main(String[] args) {
  7.  
  8.          Vector target      = new Vector();
  9.          String obj          = new String("Vector Data");
  10.          Calendar   start, end;
  11.          long    interval   = 0;
  12.          long    LIMIT       = 0;
  13.          int      index       = 0;
  14.  
  15.          long    MAX = Long.parseLong(args[0]);
  16.          
  17.          for ( LIMIT = MAX / 10 ; LIMIT <= MAX ; LIMIT = LIMIT + MAX/10 ) {
  18.              start = Calendar.getInstance();
  19.              for ( index=0 ; index < LIMIT ; index++) {
  20.                   target.add(index, obj);
  21.              }
  22.              end    = Calendar.getInstance();
  23.  
  24.              interval = end.getTimeInMillis() - start.getTimeInMillis();
  25.              System.out.println(index + " " + interval);
  26.              target = new Vector();
  27.          }
  28.  
  29.          System.out.println("===========================");
  30.  
  31.          for ( LIMIT = MAX / 10 ; LIMIT <= MAX ; LIMIT = LIMIT + MAX/10 ) {
  32.              start = Calendar.getInstance();
  33.              for ( index=0 ; index < LIMIT ; index++) {
  34.                   target.add(0, obj);
  35.              }
  36.              end    = Calendar.getInstance();
  37.  
  38.              interval = end.getTimeInMillis() - start.getTimeInMillis();
  39.              System.out.println(index + " " + interval);
  40.              target = new Vector();
  41.          }
  42.     }
  43.  
  44.    
  45. }


[VectorInd.java]


  1. import java.util.Vector;
  2. import java.util.Calendar;
  3.  
  4. public class VectorInd {
  5.  
  6.     public static void main(String[] args) {
  7.    
  8.          Vector target = new Vector(30);
  9.          Object obj      = new Object();
  10.  
  11.          Calendar   start, end;
  12.          long         interval = 0;
  13.  
  14.          int LIMIT = Integer.parseInt(args[0]);
  15.  
  16.          for ( int index = 0 ; index <= LIMIT ; index++ ) {
  17.              start = Calendar.getInstance();
  18.              target.add(obj);
  19.              end    = Calendar.getInstance();
  20.              interval = end.getTimeInMillis() - start.getTimeInMillis();
  21.              if ( interval > 3 ) {
  22.                   System.out.println("Insert Time : " + interval);
  23.              }
  24.          }
  25.  
  26.     }
  27. }



저자 : 김대곤
원문 출처 : http://network.hanb.co.kr/view.php?bi_id=964
2007/06/24 11:17 2007/06/24 11:17
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

CLASSPATH에 없는 클래스 로딩

2007/06/18 01:43

서비 JAVA , , ,

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샘플을 보여준다..

  1. import java.io.*;
  2. import java.net.*;
  3. public class ClassLoading {
  4.   public static void main(String [] args) throws Exception {
  5.     // Create a File object on the root of the directory containing the class file
  6.     File file = new File("D:/_Develop/jmxSamples/customMBean/log4j-1.2.8.jar");
  7.      
  8.     try {
  9.       // Convert File to a URL
  10.       URL url = file.toURL();          // file:/D:/_Develop/jmxSamples/customMBean/log4j-1.2.8.jar
  11.       URL[] urls = new URL[]{ url };
  12.       System.out.println(urls);
  13.        
  14.       // Create a new class loader with the directory
  15.       ClassLoader cl = new URLClassLoader(urls);
  16.       System.out.println(cl);
  17.        
  18.       // Load in the class; Logger.class should be located in
  19.       // the directory file:/D:/_Develop/jmxSamples/customMBean/log4j-1.2.8.jar
  20.       Class cls = cl.loadClass("org.apache.log4j.Logger");
  21.       System.out.println(cls);
  22.      
  23.     } catch (MalformedURLException e) {
  24.       e.printStackTrace();
  25.     } catch (ClassNotFoundException e2) {
  26.       e2.printStackTrace();
  27.     }
  28.    
  29.   }
  30. }

위에서 보는 것처럼 디렉토리를 설정하거나 특정 jar파일을 사용할 수 있도록 작성한다.
특정파일이 가르키지 않으면 해당 디렉토리의 class파일들을 package형태로 참조하도록 할 수 있는데
해당 디렉토리에 대한 클래스 로딩 샘플을 아래와 같다.

  1. import java.io.*;
  2. import java.net.*;
  3. public class ClassLoading {
  4.   public static void main(String [] args) throws Exception {
  5.     // Create a File object on the root of the directory containing the class file
  6.     File file = new File("D:/_CVSDevelop/jca_hello_adapter/build/classes");
  7.      
  8.     try {
  9.       // Convert File to a URL
  10.       URL url = file.toURL();          // file:/D:/_CVSDevelop/jca_hello_adapter/build
  11.       URL[] urls = new URL[]{ url };
  12.       System.out.println(urls);
  13.        
  14.       // Create a new class loader with the directory
  15.       ClassLoader cl = new URLClassLoader(urls);
  16.       System.out.println(cl);
  17.        
  18.       // Load in the class; Test.class should be located in
  19.       // the directory file:/D:/_CVSDevelop/jca_hello_adapter/build/classes/com/bea/jca/test/Test
  20.       Class cls = cl.loadClass("com.bea.jca.test.Test");
  21.       System.out.println(cls);
  22.      
  23.     } catch (MalformedURLException e) {
  24.       e.printStackTrace();
  25.     } catch (ClassNotFoundException e2) {
  26.       e2.printStackTrace();
  27.     }
  28.    
  29.   }
  30. }

위와 같은 경우에는 classpath의 루트로 잡은 디렉토리를 기준의 package형태로 설정되 파일을
로딩하여 사용할수 있도록 한다.

이 이후의 코딩에는 class가 newInstance를 취한 후 method를 invoking해야 하는 과정을 거치게 되는데
한 가지 주의할 점은 해당 클래스를 반드시 reflection API를 이용하여 호출해야 한다는 점이다.

대략 아래의 코드정도를 이용하여 main 메소드등을 호출하는 클래스를 작성할 수 있을 것이다.

  1. public void invokeClass(String name, String[] args)
  2.     throws ClassNotFoundException,
  3.           NoSuchMethodException,
  4. {
  5.     Class c = loadClass(name);
  6.     Method m = c.getMethod("main", new Class[] { args.getClass() });
  7.     m.setAccessible(true);
  8.     int mods = m.getModifiers();
  9.     if (m.getReturnType() != void.class || !Modifier.isStatic(mods) ||
  10.         !Modifier.isPublic(mods)) {
  11.         throw new NoSuchMethodException("main");
  12.     }
  13.     try {
  14.         m.invoke(null, new Object[] { args });
  15.     } catch (IllegalAccessException e) {
  16.         // This should not happen, as we have disabled access checks
  17.     }
  18. }

reflection에 대한 샘플은 몇가지에 대하여 놀새~ 사이트에 이미 올려져 있으므로 참조하기 바란다.
위와 같은 샘플을 이용하게 되면 서버측 프로그램에 대한 작성을 해볼 수 있는 좋은 기회가 아닐까 한다.

원문 출처 : http://ienvyou.egloos.com/?doc=bbs/gnuboard.php&bo_table=sample_code&page=1&wr_id=68


2007/06/18 01:43 2007/06/18 01:43
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다