| StringTokenizer 사용법

StringTokenizer는 긴 문자열을 지정된 구분자를 기준으로 문자열을 슬라이싱하는데 사용된다.
100,200,300,400의 문자열을 , 구분자를 기준으로 슬라이싱하게 되면 4개의 문자열을 획득할 수 있다.
StringTokenizer의 경우 단 한개의 구분자를 사용해야 한다는 단점이 있으므로 복잡한 형태의 구분자로 문자열을 나누어야 할 때는  Scanner나 split를 사용해야 한다.

StringTokenizer 생성자 및 메소드는 다음 표와 같다.

생성자/메소드 설  명
StringTokenizer(String str, String delim) 문자열을 지정된 구분자로 나누는 StringTokenizer를 생성한다.
구분자는 토큰으로 간주되지 않음
StringTokenizer(String str, String delim, boolean returnDelims) 구분자도 토큰으로 간주
int countTokens() 전체 토큰의 수를 반환
boolean hasMoreTokens() 토큰이 남아있는지 알려 줌
String nextToken() 다음 토큰을 반환

| 소스코드

import java.util.StringTokenizer;

public class Main {

	public static void main(String[] args) {
		String source = "100,200,300,400";
		StringTokenizer st = new StringTokenizer(source, ",");
		while (st.hasMoreTokens()) {
			System.out.println(st.nextToken());
		}
	}

}

결과값

100
200
300
400

| Scanner 와 BufferedReader의 차이

이번 글은 자바를 처음 배울때 한번쯤은 써봤을 법한 클래스인 Scanner와 프로그래밍에 조금 더 익숙해지거나 특히 알고리즘 문제를 풀다가 자주 접하게 될 BufferedReader 클래스의 차이에 대해 알아보겠습니다.

결론부터 말하자면 가장 큰 차이점은 속도!!

 

package milk;

import java.util.Scanner;

public class study {

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);

		System.out.println("원하는 숫자를 입력하세요");
		String input = scanner.nextLine();
		int num = Integer.parseInt(input);

		System.out.println(num);
	}

}

 

우리는 보통 자바를 처음 배울때 콘솔에서 입력을 받기 위해 이러한 방식을 많이 사용했을 것이다. 하지만 Scanner는 편리하지만 속도가 느리다는 단점이 있어 특히 알고리즘 문제를 풀때 시간초과 에러를 내는 치명적인 단점이 있습니다. 하하지만  BufferedReader 를 아래와 같이 사용한다면 버퍼를 사용하기 때문에 입력 속도에서 확연히 줄일 수 있습니다.

package milk;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class study {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 선언
		String s = br.readLine(); 
		int i = Integer.parseInt(br.readLine()); 
		
		System.out.println("String : " + s);
		System.out.println("Int : " + i);
		
	}
}

 

 

 

각 언어마다 입력방식에 대한 속도가 차이가 조금씩 있습니다. 알고리즘 사이트에서 보면 C/C++이 JAVA 보다 실행속도가 빠른 이유가 여기에 있습니다.

입력방식에 따른 속도 차이






자바 프로그램 실행과정

- Java언어로 프로그래밍된 파일을 Java컴파일러가 가상 기계어 파일인 Java클래스 파일로 만든다. 다시 말해, 소스 코드를 Java바이트 코드로 번역한다. 이후 Java바이트 코드를 JVM이 읽고 실행하게 된다.

1.1. 바이트 코드란?

- JVM이 이해할 수 있는 언어로 변환된 자바 소스 코드를 의미한다. 자바 컴파일러에 의해 변환되는 코드의 명령어 크기가 1바이트라서 자바 바이트 코드라고 불린다. 이러한 자바 바이트 코드의 확장자는. class이며 자바 바이트 코드는 자바 가상 머신만 설치되어 있으면, 어떤 운영체제에서 라도 실행될 수 있다.

1.2. JVM이란?

- JVM은 JavaVirtual Machine의 줄임말로 wirte once, run erveywhere 즉 OS마다 따로 코드를 작성해야 하는 번거로움 업이 Java가 플랫폼에 독립적일 수 있게 만들어준다. 예를 들어 C 프로그램은 바로 기계어로 컴파일하므로 HW 기종에 맞게 컴파일되어야 한다. 이를 '플랫폼에 종속적'이다. 라고 한다. 반면 Java 프로그램은 중간 단계 언어로 컴파일하여 JVM만 각 OS만 설치되어 있다면 HW 기종에 상관없이 단 한 번만 컴파일하면 된다. 이를 '플랫폼에 독립적'이라고 한다. 간단히 말해 JVM은 Java 클래스 파일을 로드하고 바이트 코드를 해석하며, 메모리 등의 자원을 할당하고 관리하며 정보를 처리하는 작업을 하는 프로그램이다.

 

  • JRE는 자바 API와 JVM으로 구성되며, JVM의 역할은 자바 애플리케이션을 클래스 로더를 통해 읽어 들여서 자바 API와 함께 실행하는 것임.
  • 자바 프로그램을 컴파일하면, JVM은 클래스 로더를 이용해서 컴파일된 바이트 코드를 메모리로 읽어들인다.
    class Loader에 의해 바이트코드가 런타임 데이터 영역(Runtime Data Area)에 올라가게 되면 
    실행 엔진(Execution Engine)이 바이트코드를 한줄씩 실행시킨다. 이 과정 속에서 JVM은 필요에 따라 garbage collection 등의 작업을 수행하게 된다.
  • JVM의 메모리 구조는 활용 용도에 따라 여러 영역으로 구분되어 있는데, 메소드, 스택, 힙 영역이 프로그래밍과 관련이 있다.
  • 지정된 클래스에서 main(String[] args)를 호출한다. 
  • Java Compilerava Source파일을 JVM이 해석할 수 있는 Java Byte Code(. class)로 변경한다. 일반적인 윈도우 프로그램의 경우, Compile 이후 Assembly 언어로 구성된 파일이 생성된다.
  • Class LoaderJVM내로. class파일들을 Load 한다. Loading 된 클래스들을 Runtime Data Area에 배치된다. 일반적인 윈도우 프로그램의 경우 Load 과정은 OS가 주도한다.
  • Execution Engine은 Loading 된 클래스의 Bytecode를 해석한다. 이 과정에서 ByteCode가 BinaryCode로 변경된다. 일반적인 윈도우 프로그램의 경우 Assembier가 Assembly언어로 쓰인 코드 파일을 BinaryCode로 변경한다.
  • Runtime Data AreaJVM이 프로세스로써 수행되기 위해 OS로부터 할당받는 메모리 영역이다. 저장 목적에 따라 다음과 같이 5개로 나눌 수 있다.

 

  - Method Area
모든 Thread에게 공유된다. 클래스 정보, 변수 정보, Method정보, static변수 정보, 상수 정보 등이 저장되는 영역
  - Heap Area
모든 Thread에게 공유된다. new 명령어로 생성된 인스턴스와 객체가 저장되는 구역, 공간이 부족해지면 Garbage Collection이 실행된다.
  - Stack Area
각 스레드마다 하나씩 생성된다. Method안에서 사용되는 값들(매개변수, 지역변수, 리턴 값 등)이 저장되는 구역, 메서드가 호출될 때 LIFO로 하나씩 생성되고, 메서드 실행이 완료되면 LIFO로 하나씩 지워진다.
  - PC Register
각 스레드마다 하나씩 생성된다. CPU의 Register와 역할이 비슷하다. 현재 수행 중인 JVM명령의 주소 값이 저장된다.
  - Native Method Stack
각 스레드마다 하나씩 생성된다. 다른 언어(C/C++ 등)의 메서드 호출을 위해 할당되는 구역 언어에 맞게 Stack이 형성되는 구역이다. JNI(Java Native Interface)라는 표준 규약을 제공한다.

| final, finally, finalize 차이점

 

1. final

 

final은 사용되는 문맥에 따라 다르다.

변수에 적용하면 : 해당 변수의 값은 변경이 불가능해진다. 상수가 되기 때문

변수의 참조에 적용하면 : 참조 변수가 힙 내의 다른 객체를 가리키도록 변경할 수 없다.

 

메서드에 적용하면 : 해당 메서드를 상속받는 하위 클래스에서 오버라이딩 할 수 없다.(상속받는 하위 클래스에서도 변경이 되지 않아야 하는 메서드의 경우 final을 붙이면 된다.)

 

클래스에 적용하면 : 해당 클래스를 다른 클래스가 상속받을 수 없다. 즉, final 클래스의 하위 클래스를 정의할 수 없다. ->이건 언제 쓰는 걸까??

 

 

 

2. finally

 

finally는 try-catch 블록 뒤에 둘 수 있는 선택적인 블록인데 try-catch문이 끝나기전에 항상 꼭 실행되어야하는 로직이 있을 경우 finally절에 두면된다.

try-catch블록과 함께 사용되며 예외가 던져지더라도 항상 실행될 코드를 지정하기 위해 사용된다. finally블록은 try와 catch블록이 전부!!! 실행된 후, 그리고 제어 흐름이 원래 지점으로 돌아가기 전에 실행된다.(코드 확인할 것)

 즉, 보통 뒷 마무리 코드를 작성하는데 사용된다.

(DB연결을 하거나 I/O 스트림을 열었을때 다 사용하거나면 닫아주는 작업 등)

public class TryEx {

    public static String lem(){
        System.out.println("lem");
        return "return from lem";
    }// lem()
     
    public static String foo(){
        int x = 0; 
        int y = 5;
         
        try{
            System.out.println("start try");
            int b = y/x;
            System.out.println("end try");
            return "returned from try";
        }catch(Exception e) {
            System.out.println("catch");
            // catch문 실행 된 후(return문 까지 실행 된 후) finally문 실행된 후 마지막 catch문의 return 값이 반환된다.
            return lem() + " | returned from catch";
        }finally{
            System.out.println("finally");
        }
    }// foo()
     
    public static void bar(){
        System.out.println("start bar");
        String v = foo();
         
        System.out.println(v);
        System.out.println("end bar");

    }// bar()
     
    public static void main(String[] args){
        bar();
    }// main()
     
}// class
/*
 *      try 블럭을 실행하다가 예외를 만나면 catch 블럭으로 간 후, catch블럭이 실행된다. 이때 catch블럭의 return문까지 모두 다 실행 된 후, 
 *      finally블럭이 실행 된 다음에서야 return이 이루어지는 것을 확인할 수 있다.
 *      즉, finally 블럭은 try 블럭이 종료되는 순간 실행된다. 만약 try블럭 안에 return문을 넣더라도 finally블록은 try 블록이 종료되는
 *      순간 실행된다. try블럭에서 return | continue | break 문을 실행하거나 예외를 던진다거나해도 실행되는 것이다.
 * 
 *      단,  try/catch 블록 수행 중에 가상머신이 종료되거나
 *          try/catch를 수행하고 있던 쓰레드가 죽어버리면 finally블럭이 실행되지 않는다.
 * */

 

 

 
<결과>
start bar
start try
catch
lem
finally
return from lem | returned from catch
end bar
 
=>    즉, 코드 수행 순서가
  try문 실행-> 예외 발생 -> catch문 실행( return문에 있는 코드 까지 모두 실행) -> finally문 실행 -> return 수행
 
 
 

Q) Java의 finally 블록은 try-catch-finally의 try블록 안에 return 문을 넣어도 실행되나?

A) try 블럭을 실행하다가 예외를 만나면 catch 블럭으로 간 후, catch블럭이 실행된다. 이때 catch블럭의 return문까지 모두 다 실행 된 후, finally블럭이 실행 된 다음에서야 return이 이루어지는 것을 확인할 수 있다.

즉, finally 블럭은 try 블럭이 종료되는 순간 실행된다. 만약 try블럭 안에 return문을 넣더라도 finally블록은 try 블록이 종료되는 순간 실행된다. try블럭에서 return | continue | break 문을 실행하거나 예외를 던진다거나해도 실행되는 것이다.

 

단, try/catch 블록 수행 중에 가상머신이 종료되거나

 

  try/catch를 수행하고 있던 쓰레드가 죽어버리면 finally블럭이 실행되지 않는다.

 

 
 
3. finalize()
 
finalize() 메서드는 java garbage collector가 더 이상의 참조가 존재하지 않는 객체를 발견한 순간 호출하는 메서드다. 즉 더이상 사용되지 않는 객체가 있을때 메모리 낭비를 막기 위해서 garbage collector가 이 객체를 없애버리는데 이때 해당객체의 finallize 메서드를 호출해서 없앤다. 이 finallize()는 java.lang.Object클래스로 부터 상속받아 모든 클래스의 객체가 가지고 있는 메서드다. 즉, 만약 객체가 삭제되기 직전에 실행되어야 하는 로직같은게 있으면 Object 클래스에 정의된 finalize() 메서드를 오버라이딩하여 정의할 수 있다. 
 
하지만!! finalize()는 언제 호출될지 알 수 없기 때문에, finalize()를 오버라이딩 했다고해서 여기에 의존하는 방식의 코딩은 좋은 방법은 아닐 수 있다.

 

BufferedReader, BufferedWriter를 활용한 빠른 입출력

 

BufferedReader/BufferedWriter는 Buffer에 있는 IO 클래스입니다. 입력된 데이터가 바로 전달되지 않고 중간에 버퍼링이 된 후에 전달되됩니다. 출력도 마찬가지로 버퍼를 거쳐서 간접적으로 출력장치로 전달되기에 시스템의 데이터처리 효율성을 높여주며 버퍼스트림을InputStreamReader / OutputStreamWriter를 같이 사용하여 버퍼링을 하게 되면 입출력 스트림으로부터 미리 버퍼에 데이터를 갖다 놓기 때문에 보다 효율적인 입출력이 가능합니다.

▶BufferedReader 

Java를 처음 접하시는 분들이 주로 받는 입력방식은 Scanner입니다. Scanner를 통해 입력을 받을경우 Space Enter를 모두 경계로 인식하기에 입력받은 데이터를 가공하기 매우 편리합니다. 하지만 그에비해 BufferedReader는 Enter만 경계로 인식하고 받은 데이터가 String으로 고정되기때문에 입력받은 데이터를 가공하는 작업이 필요할경우가 많습니다. Scanner에 비해 다소 사용하기 불편하죠. 하지만 많은 양의 데이터를 입력받을경우 BufferedReader를 통해 입력받는 것이 효율면에서 훨씬 낫습니다. 입력시 Buffer 메모리줌으로써 작업속도 차이가 많이납니다.

 

BufferedReader 사용법

BufferedReader bf = new BufferedReader(new InputStreamReader(System.in)); //선언 
String s = bf.readLine(); //
String int i = Integer.parseInt(bf.readLine()); //Int

 선언은 위에 있는 예제와 같이 하시면 됩니다. 입력은 readLine();이라는 메서드를 활용하시면 되는데요. 여기서 주의할점이 두가지가 있습니다. 첫번째는 readLine()시 리턴값을 String으로 고정되기에 String이 아닌 다른타입으로 입력을 받을려면 형변환을 꼭 해주어야한다는 점입니다. 두번째는 예외처리를 꼭 해주어야한다는 점입니다. readLine을 할때마다 try & catch를 활용하여 예외처리를 해주어도 되지만 대개 throws IOException을 통하여 작업합니다.

 

Read한 데이터 가공

StringTokenizer st = new StringTokenizer(s); //StringTokenizer인자값에 입력 문자열 넣음 
int a = Integer.parseInt(st.nextToken()); //첫번째 호출 
int b = Integer.parseInt(st.nextToken()); //두번째 호출  
String array[] = s.split(" "); //공백마다 데이터 끊어서 배열에 넣음

 Read한 데이터는 Line단위로만 나눠지기에 공백단위로 데이터를 가공하려면 따로 작업을 해주어야하는데요. 위의 두가지 방법이 대표적입니다. 첫번째 방법으로는 StringTokenizer에 nextToken()함수를 쓰면 readLine()을 통해 입력받은 값을 공백단위로 구분하여 순서대로 호출할 수 있습니다. 두번째방법으로는 String.split()함수를 활용하여 배열에 공백단위로 끊어서 데이터를 넣고 사용하는 방식입니다.

 

▶BufferedWriter 

일반적으로 출력을할때 System.out.println(""); 방식을 사용하고는 합니다. 적은양의 출력일 경우 성능차이가 미미하겠지만 많은 양의 출력에서는 입력과 마찬가지로 Buffer를 활용해주시는것이 좋습니다.

 

BufferedWriter 사용법

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));//선언 String s = "abcdefg";//출력할 문자열 bw.write(s+"\n");//출력 bw.flush();//남아있는 데이터를 모두 출력시킴 bw.close();//스트림을 닫음

BufferedWriter 의 경우 버퍼를 잡아 놓았기 때문에 반드시 flush() / close() 를 반드시 호출해 주어 뒤처리를 해주어야합니다. 그리고 bw.write에는 System.out.println();과 같이 자동개행기능이 없기때문에 개행을 해주어야할 경우에는 \n를 통해 따로 처리해주어야합니다.

 

주요 Method

메서드명   기능
 BufferedReader(Reader rd)  rd에 연결되는 문자입력 버퍼스트림 생성
 BufferedWriter(Writer wt)   wt에 연결되는 문자출력 버퍼스트림 생성​
 int read()  스트림으로부터 한 문자를 읽어서 int 형으로 리턴
 int read(char[] buf)  문자배열 buf의 크기만큼 문자를 읽어들임.  읽어들인 문자 수를 리턴
 int read(char[] buf, int offset, int length)  buf의 offset위치에서부터 length 길이만큼 문자를 스트림으로부터 읽어들임​
 String readLine()  스트림으로부터 한 줄을 읽어 문자열로 리턴​​
 void mark()   현재우치를 마킹, 차 후 reset() 을 이용하여 마킹위치부터 시작함
 void reset()   마킹이 있으면 그 위치에서부터 다시시각, 그렇지 않으면 처음부터 다시시작
 long skip(int n)  n 개의 문자를 건너 뜀
 void close()  스트림 닫음
 void write(int c)  int 형으로 문자 데이터를 출력문자스트림으로 출력
 void write(String s, int offset, int length)  문자열 s를 offset 위치부터 length 길이만큼을 출력스트림으로 출력
 void write(char[] buf, int offset, int length)  문자배열 buf의 offset 위치부터 length 길이만큼을 출력스트림으로 출력​​​
 void newLine()  줄바꿈 문자열 출력
 void flush()   남아있는 데이터를 모두 출력시킴.
 

 

+ Recent posts