본문 바로가기
ICIA 수업일지

2021.06.29 수업일지

by 주성씨 2021. 7. 3.

- 컬렉션 프레임워크

+ 다시 보기
- Iterator
- treeMap
- hashMap -> Iterator 없음
- Map 계열은 key와 Value에서 key를 통해서 데이터를 추출한다.

 

- 스레드와 동기화

1. 프로세스와 쓰레드


- 프로세스는 실행 중인 프로그램을 의미한다.
- 스레드는 프로세스 내에서 별도의 실행 흐름을 갖는 대상이다.
- 모든 프로세스에는 한 개 이상의 스레드가 존재하며 작업을 수행한다.
- 프로세스 내에서 둘 이상의 스레드를 생성하는 것이 가능하다.
- 사실 스레드는 모든 일의 기본 단위이다. main 메서드를 호출하는 것도 프로세스 생성시 함께 생성 되는 main 쓰레드를 통해서 이뤄진다.
- 별도의 쓰레드 생성을 위해서는 별도의 스레드 클래스를 정의해야 한다.
- 스레드 클래스는 Thread를 상속하는 클래스를 의미한다.
- start 메서드가 호출되면 스레드가 생성되고, 생성된 스레드는 run 메서드를 호출한다.

※ Thread? ; 적절한 스레드의 양을 통해서 프로세스를 효율적으로 이용할 수 있다.
※ Thread를 사용하는 이유? 스레드를 사용하면 동시에 여러 개의 코드를 수행할 수 있고 동시에 엄청난 양이 들어오는 예를 들어 채팅 서비스나 생산 데이터를 수집하는 스마트 팩토리와 같은 경우에서 하나씩 처리하면 엄청난 시간이 걸리기 때문에 스레드를 사용하여 많은 양을 효율적으로 처리할 수 있기 떄문이다. 다만 쓰레드를 사용 시 주의할 점은 한 번에 많은 코드를 수행할수록 컴퓨터에 부하가 심해지고 남에게 필요한 잉여 자원을 내가 가지고 있게 되어 서로 무한정 대기하는 교착상태에 빠질 수 있는 문제가 있으므로 주의해야 한다.

예제 1)

package ex1;

public class Ex1_Thread {
	// 메인 쓰레드
	public static void main(String [] args) {
		
	}
		
}
package ex1;

// 쓰레드 클래스 생성
// 조건 : 제공되는 쓰레드 클래스를 상속해야한다.
// 예외 할때 익셉션 클래스를 상속하는 것처럼
// 메인 메서드처럼 실행이 되는 중심 쓰레드가 필요하다.
// 오바리이딩 임플리먼트 -> 쓰레드 -> run 으로 메인 쓰레드를 생성 

public class ShowThread extends Thread {
	String threadName;

	public ShowThread(String name) {
		threadName = name;
	}

	@Override
	public void run() {
		// 처리할 새로운 쓰레드 실행 내용
		// 이를 통해서 업무를 줌
		for (int i = 0; i < 100; i++) {
			System.out.println("안녕하세요. " + threadName + "입니다.");
			try {
				sleep(100); // 1/1000 초 단위로 실행흐름을 일시적으로 멈춘다.
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	} //run 메서드

}

ㄴ CPU가 스레드를 처리할 때 하나만 실행할 수도 있기 때문에 sleep( )을 통해 잠깐 멈춰서 다른 스레드에도 기회를 준다. 데이터를 가져올 때의 시간 동안 다른 스레드를 사용해야 효율적인 운영이 가능하다는 것을 보여주기 위해서 sleep을 통해서 보여준 것이다.

 

예제 1_2)

package ex1;

public class Ex1_Thread {
	// 메인 쓰레드
	public static void main(String [] args) {

		ShowThread st1 = new ShowThread("바이러스 검사 쓰레드"); //인스턴스 생성
		ShowThread st2 = new ShowThread("악성코드 검사 쓰레드"); //인스턴스 생성
		
//		st1.run(); 런으로 실행하는 것으로 착각할 수 있지만 아니다.
		
		st1.start(); // start를 통해서 메모리 및 CPU 리소스 할당을 받는다. -> run 호출(자동)
		st2.start();
		// 지금까지 총 메인, 바이러스, 악성코드 쓰레드가 생성된것이다.
		
	}
		
}

ㄴ 인스턴스와 스레드를 한 줄로 쓸 수 있다.

		new ShowThread("추가 쓰레드").start();

 

출력)

안녕하세요. 바이러스 검사 쓰레드입니다.
안녕하세요. 악성코드 검사 쓰레드입니다.
안녕하세요. 바이러스 검사 쓰레드입니다.
안녕하세요. 악성코드 검사 쓰레드입니다.
안녕하세요. 바이러스 검사 쓰레드입니다.
안녕하세요. 악성코드 검사 쓰레드입니다.
안녕하세요. 바이러스 검사 쓰레드입니다.
안녕하세요. 악성코드 검사 쓰레드입니다.
안녕하세요. 바이러스 검사 쓰레드입니다.
안녕하세요. 악성코드 검사 쓰레드입니다.
안녕하세요. 바이러스 검사 쓰레드입니다.
안녕하세요. 악성코드 검사 쓰레드입니다.
안녕하세요. 바이러스 검사 쓰레드입니다.
안녕하세요. 악성코드 검사 쓰레드입니다.
안녕하세요. 바이러스 검사 쓰레드입니다.
안녕하세요. 악성코드 검사 쓰레드입니다.
안녕하세요. 바이러스 검사 쓰레드입니다.
안녕하세요. 악성코드 검사 쓰레드입니다.
안녕하세요. 바이러스 검사 쓰레드입니다.
안녕하세요. 악성코드 검사 쓰레드입니다.

ㄴ 먼저 출발하는 스레드가 꼭 먼저 실행되는 게 아니다.

 

2. 스레드를 생성하는 두 번째 방법


- Runnable 인터페이스를 구현하는 클래스의 인스턴스를 대상으로 Thread 클래스의 인스턴스를 생성한다. 이 방법은 상속할 클래스가 존재하라 때 유용하게 사용된다.
- join 메서드가 호출되면, 해당 스레드의 종료를 기다리게 된다.
- 위 예제에서 main 스레드가 join 메서드를 호출하지 않았다면, 추가로 생성된 두 쓰레드가 작업을 완료하기 전에 값을 참조하여 쓰레기 값이 출력될 수 있는 단점이 있다.

예제 2) 유인물 77page 예제 2번을 예제 1번처럼 바꾼다면 아래와 같다.
예제 2번은 일반 클래스를 인터페이스를 통해서 스레드 역할을 하는 것과 같은 효과를 내기 위해서 Runnable을 사용했다.

package ex1;

public class Ex2_Thread2 {

	public static void main(String[] args) {
		AdderThread at1 = new AdderThread(1, 500);
		AdderThread at2 = new AdderThread(501, 1000);
		at1.start();
		at2.start();

		try {
			at1.join();
			at2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("1~1000까지의 합:" + (at1.getNum() + at2.getNum()));
	}

}
package ex1;

// 일반 클래스
// 다중상속 X
// 다중상속이 안되는 대신에
// 인터페이스를 통해서 다중상속의 효과를 낸다.
public class AdderThread extends Thread {

	int num;

	public void addNum(int n) {
		num += n;
	}

	public int getNum() {
		return num;
	}

	int start, end;

	public AdderThread(int s, int e) {
		start = s;
		end = e;
	}

	@Override
	public void run() {
		// 1 ~ 500 || 501 ~ 1000합 구하기
		for (int i = start; i <= end; i++)
			addNum(i);

	}
}

 

3. 스레드의 특성


1) 쓰레드의 스케줄링과 우선순위 컨트롤
- 우선순위가 높은 스레드의 실행을 우선시한다.
- 우선순위가 동일할 때는 CPU의 할당시간을 나눈다.
- 메서드 getPriority의 반환 값을 통해서 스레드의 우선순위를 확인할 수 있다.
- 우선순위와 관련해서 별도의 지시를 하지 않으면, 동일한 우선순위의 스레드들이 생성된다.

2) 낮은 우선순위의 쓰레드 실행
- 스레드가 CPU의 할당을 필요로 하지 않을 경우. CPU를 다른 스레드에게 양보한다.

3) 쓰레드의 라이프 사이클 (생명 주기)

- Runnable 상태의 쓰레드 만이 스케줄러에 의해 스케줄링 가능하다.

- 그리고 앞서 보인 sleep, join 메서드의 호출로 인해서 스레드는 Blocked 상태가 된다.

- 한번 종료된 쓰레드는 다시 Runnable 상태가 될 수 없지만, Blocked 상태의 쓰레드는 조건이 성립되면 다시 Runnable 상태가 된다.

 

4) 스레드의 메모리 구성

- 메인 쓰레드 먼저 실행. 메서드 영역에 클래스 정보 클래스 클래스 변수가 할당 스택은 메모리 쌓임. 힙 영역은 메모리 할당. A, B 스레드에서는 스텍 영역에만 할당. 메서드와 힙은 메인 쓰레드에서 공유 왜?

-

- 모든 스레드는 스택을 제외한 메서드 영역과 힙ㅂ을 공유한다. 따라서 이 두 영역을 통해서 데이터를 주고 받을 수 있다.

- 스택은 쓰레드 별로 독립적일 수밖에 없는 이유는, 스레드의 실행이 메서드의 호출을 통해서 이뤄지고, 메서드의 호출을 위해서 사용되는 메모리 공간이 스택이기 때문이다. 스택이 개별적으로 쌓이기 때문에 섞어서 쌓을 수 없기 때문에 개별적으로 사용해야 한다.

 

프로세스와 스레드의 메모리 구조의 차이점

ㄴ 스레드가 code 영역을 하기 때문에 한 프로세스 내부의 스레드들은 프로세스가 가지고 있는 함수를 자연스럽게 모두 호출할 수 있다.
ㄴ 뿐만 아니라 스레드는 data, heap 영역을 공유하기 때문에 IPC(inter process communication) 없이도 스레드 간의 통신이 가능하다. 동일한 프로세스 내부에 존재하는 스레드 A, B가 통신하기 위해 heap 영역에 메모리 공간을 할당하고, 두 스레드가 자유롭게 접근한다고 생각하면 된다.

 

- 동기화

1. 쓰레드의 메모리 접근방식과 그에 따른 문제점

- 한 번에 한놈만 들어갈 수 있는 구역을 임계 구역이라고 하며 둘 이상의 스레드가 들어가지 못하게 임계 구역으로 만들어야 한다. 이를 동기화라고 한다. 전산에서의 동기화는 순서를 제어해서 임계 구역에 하나의 스레드만 들어갈 수 있게 하여 충돌을 방지하는 것을 동기화라고 한다.

2. 스레드의 동기화 기법1 : synchronized 기반 동기화 메서드

▶ 동기화 메서드의 선언
- synchronized 선언으로 인해서 아래 예제의 addNum 메서드는 스레드에 안전한 함수가 된다.
- 동기화 메서드는 한 순간에 하나의 스레드만 호출이 가능하다.

예제)

package ex1;

class Sum {
	int num;

	public Sum() {
		num = 0;
	}

	// 메서드 동기화 1
//	public synchronized void addNum(int n) {
//		num += n;
//	}

	// 메서드 동기화 2
	// 특정 코드만 임계구역으로 설정하고 싶을때
	public synchronized void addNum(int n) {
		// 블럭 동기화
		synchronized (this) {
			num += n;
		}
		// 기타 동기화
	}

	public int getNum() {
		return num;
	}
} // sum end

public class Ex3_ThreadHeapMultiAccess extends Thread {
	Sum sumInst;
	int start, end;

	public Ex3_ThreadHeapMultiAccess(Sum sum, int s, int e) {
		sumInst = sum;
		start = s;
		end = e;
	}

	public void run() {
		for (int i = start; i <= end; i++)
			sumInst.addNum(i);
	}
}

ㄴ this는 현재 인스턴스 임계 구역에 들어갈 수 있는 열쇠라고 생각하면 된다. 인스턴스마다 열쇠는 하나이다.

 

- 동기화에 사용되는 인스턴스는 하나이며, 이 인스턴스에는 하나의 열쇠만이 존재한다.

- 동기화의 대상은 인스턴스이며, 인스턴스의 열쇠를 획득하는 순간 모든 동기화 메서드에는 타 스레드의 접근이 불가능하다. 따라서 메서드 내에서 동기화가 필요한 영역이 매우 제한적이라면 메서드 전부를 synchronized로 선언하는 것은 적절하지 않다.

 

 

5. 스레드 접근 순서의 동기화 필요성

예제 4)

package thread;

// 메인 쓰레드, 메인 메서드
public class NewsPaperStory {
	public static void main(String[] args) {
		NewsPaper paper = new NewsPaper();
		NewsReader reader = new NewsReader(paper);
		NewsWriter writer = new NewsWriter(paper);
		
		reader.start();
		writer.start();
		try {
			reader.join();
			writer.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	} // mian end
} // class end

 

package thread;

public class NewsPaper {
	String todayNews;

	public void setTodayNews(String news) {
		todayNews = news;
	}

	public String getTodayNews() {
		return todayNews;
	}
}

 

package thread;

// 뉴스읽기 쓰레드
public class NewsReader extends Thread {
	NewsPaper paper;

	public NewsReader(NewsPaper paper) {
		this.paper = paper;
	}
	
	@Override
	public void run() {
		System.out.println("오늘의 뉴스: " + paper.getTodayNews());
	}
}
package thread;

// 뉴스쓰기 쓰레드
public class NewsWriter extends Thread {
	NewsPaper paper;

	public NewsWriter(NewsPaper paper) {
		this.paper = paper;
	}

	@Override
	public void run() {
		paper.setTodayNews("자바의 열기가뜨겁습니다.");
	}

}

 

+ 읽기 쓰기 순서를 고정하고 싶을 때 + 동기화 + 모든 대기상태 깨우기

package thread;

public class NewsPaper {
	String todayNews;
	boolean isTodayNews = false; // 디폴트 값 없으면 펄스 있으면 트루로 반환 예정

	// 쓰기 쓰레드 호출
	public void setTodayNews(String news) {
		todayNews = news;
		isTodayNews = true; // 오늘의 뉴스가 있으면 트루
		synchronized (this) {
			notify(); // 대기상태의 하나의 읽기 쓰레드를 꺠움
//			notifyAll(); // 모든 대기상태의 일기 쓰레드를 깨움
		}
	}

	// 읽기 쓰레드 호출
	public String getTodayNews() {
		if (isTodayNews == false) {
			try {
				synchronized (this) {
					wait(); // 오브젝트 클래스 안에 들어있음. 읽기 쓰레드 대기상태로
					// 대기상태는 여러개가 가능
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return todayNews;
	}
}

 

- 파일과 I/O 스트림***

일반적으로 데이터를 저장할 때 서버(DB)에 저장하지 예전과 같이 파일로 특정 저장장치에 저장하지 않는다. 그렇기에 파일로 저장하는 방법은 적당히 배우도록 하자.

※ I/O 스트림(input output steam; 입력 출력 흐름)

1. input output의 범위와 간단한 해당 모델의 소개


- 일반적인 입출력의 대상/도구
ㄴ 키보드와 모니터 / 하드디스크에 저장되어 있는 파일 / USB와 같은 외부 메모리 장치 / 네트워크로 연결되어 있는 컴퓨터 / 사운드카드, 오디오 카드와 같은 멀티미디어 장치 / 프린터, 팩시밀리와 같은 출력장치
- 입출력 대상이 달라지면 프로그램상에서의 입출력 방식도 달라지는 것이 보통이다. 그런데 자바에서는 입출력 대상에 상관없이 입출력의 진행방식이 동일하도록 별도의 'I/O 모델'을 정의하고 있다.
- I/O 모델의 정의로 인해서 입출력 대상의 차이에 따른 입출력 방식의 차이는 크지 않다. 기본적인 입출력의 형태는 동일하다. 그리고 이것이 JAVA의 I/O 스트림이 갖는 장점이다.

- 자바 스트림의 큰 분류 -

-  데이터의 입력을 위해서는 입력 스트림을, 출력을 위해서는 출력 스트림을 형성해야 한다. 여기서 말하는 스트림이라는 것도 인스턴스의 생성을 통해서 형성된다.

- 입력 출력 CPU의 간단한 관계도 -

 

1) 파일 기반의 입력 스트림 형성

- 파일 run.exe 대상의 입력 스트림 생성.
- 스트림의 생성은 결국 인스턴스의 생성
- FileInputStream in = new FileInputStream("run.exe");
- FileInputStream은 인풋 스트림의 조상

 

- InputStream 클래스는 바이트 단위로 데이터를 읽는 모든 입력 스트림 클래스의 최상위 클래스

- OutputStream 클래스는 바이트 단위로 데이터를 쓰는 모든 출력 스트림 클래스의 최상위 클래스

 

▶ InputStream 클래스의 대표적인 두 메서드

	public abstract int read() throws IOException
	public void close() throws IOException

▶ 파일 대상의 입력 스트림 생성
- InputStream in = new FileInputStream("run.exe");
- int bData = in.read(); // 오버라이딩에 의해 FileInputStream의 read 메서드 호출

	InputStream in = new FileInputStream("run.exe");
	int bData = in.read(); // 오버라이딩에 의해 FileInputStream의 read 메서드 호출

2) 파일 대상의 출력 스트림 형성
- 입출력 스트림은 대부분 쌍을 이룬다.

▶ OutputStream 클래스의 대표적인 메서드

	public abstract void write(int b) throws IOException
	public void close() throws IOException

▶ 파일 대상의 출력 스트림 생성 및 데이터 전송

	OutputStream out = new FileOutputStream("home.bin");
	out.write(1); // 4바이트 int형 정수 1의 하위 1바이트만 전달.
	out.write(2); // 4바이트 int형 정수 2의 하위 1바이트만 전달.
	out.close; // 출력 스트림 소멸

ㄴ write(); 는 한 번에 1바이트만 씩만 출력한다.

 

 

 

예제 1)

package ex3;

// 임포트 하기
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class FileTest {
	// 해당 파일이 없을 경우에 예외처리를 해야한다.
	public static void main(String[] args) throws IOException  {
		InputStream in = new FileInputStream("travel.mp4"); 
		// 자바소스와 같은 폴더에 만들것.
		// 해당 파일을 읽는다.
		OutputStream out = new FileOutputStream("copy.mp4"); 
		// 자동으로 생성된다.
		// 읽은 파일을 출력한다.
		
		int copyByte = 0;
		int bData;
		// 무한으로 돌리겠다. in.read() 입력으로부터 1바이트씩 읽겠다.
		while (true) {
			bData = in.read(); // 다 읽어서 더 이상 읽는게 없는 경우 -1을 반환
			if (bData == -1) // 그 -1을 읽고
				break; // 멈춤
			out.write(bData); // 읽은 1바이트를 출력한다.
			copyByte++;
		}
		in.close();
		out.close();
		System.out.println("복사된 바이트 크기 " + copyByte);
	}

}

+ 이 파일을 읽는 동아 얼마나 걸리는지 시간 측정을 한다면

 

package ex3;

// 임포트 하기
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class FileTest {
	// 해당 파일이 없을 경우에 예외처리를 해야한다.
	public static void main(String[] args) throws IOException  {
		InputStream in = new FileInputStream("travel.mp4"); 
		// 자바소스와 같은 폴더에 만들것.
		// 해당 파일을 읽는다.
		OutputStream out = new FileOutputStream("copy.mp4"); 
		// 자동으로 생성된다.
		// 읽은 파일을 출력한다.
		
		int copyByte = 0;
		int bData;
		// 무한으로 돌리겠다. in.read() 입력으로부터 1바이트씩 읽겠다.
		
		long start = System.currentTimeMillis();
		
		while (true) {
			bData = in.read(); // 다 읽어서 더 이상 읽는게 없는 경우 -1을 반환
			if (bData == -1) // 그 -1을 읽고
				break; // 멈춤
			out.write(bData); // 읽은 1바이트를 출력한다.
			copyByte++;
		}
		in.close();
		out.close();
		long end = System.currentTimeMillis();
		System.out.println("복사 경과 시간 : "+(end-start)/1000.0+"초");
		System.out.println("복사된 바이트 크기 : " + copyByte);
	}

}

 

출력)

복사 경과 시간 : 260.884초
복사된 바이트 크기 : 51171093

ㄴ 50MB를 단순 복사하고 카운팅 하는데 이렇게 오래 걸린다는 것은 개선의 필요가 있다.

- 실행 후 복사된 파일을 폴더 안에서 확인할 수 있다.

 

▶ 보다 빠른 속도의 파일 복사 프로그램
- 바이트 단위 read & write 메서드를 대신해서 배열 단위의 다음 두 메서드를 호출하는 것이 핵심

	public int read(byte[] b) throws IOException
	public void write(byte[] b, int off, int len) throws IOException

 

예제 1_2) 보다 빠르게 할 경우

package ex3;

// 임포트 하기
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class FileTest {
	// 해당 파일이 없을 경우에 예외처리를 해야한다.
	public static void main(String[] args) throws IOException {
		InputStream in = new FileInputStream("travel.mp4");
		// 프로젝트 폴더에 넣을것.
		// 해당 파일을 읽는다.
		OutputStream out = new FileOutputStream("copy2.mp4");
		// 자동으로 생성된다.
		// 읽은 파일을 출력한다.

		int copyByte = 0;
		int readLen;
		byte buf[] = new byte[1024]; // 1024바이트씩 읽겠다.
		// 무한으로 돌리겠다. in.read() 입력으로부터 1바이트씩 읽겠다.

		long start = System.currentTimeMillis();

		while (true) {
			readLen = in.read(buf); // 다 읽어서 더 이상 읽는게 없는 경우 -1을 반환
			// buf 만큼 1024만큼 읽어 들이겠다. 1024만큼 반환하겠다.
			if (readLen == -1) // 그 -1을 읽고
				break; // 멈춤
			out.write(buf, 0, readLen); 
			// buf배열에 인덱스[0]부터 readLen바이트를 저장한다.
			copyByte += readLen;
		}
		in.close();
		out.close();
		long end = System.currentTimeMillis();
		System.out.println("복사 경과 시간 : " + (end - start) / 1000.0 + "초");
		System.out.println("복사된 바이트 크기 : " + copyByte);
	}

}

ㄴ 이전 예제와의 가장 큰 차이점은 1KB 크기의 버퍼를 이용해서 데이터를 입출력 한다는 점이다. 실제로 속도의 향상을 느낄 수 있다.

출력)

복사 경과 시간 : 0.535초
복사된 바이트 크기 : 51171093

 

2. 필터 스트림의 이해와 활용


- 스트림의 기능을 추가해서 사용

	InputStream is = new FileInputStream("yourAsking.bin");
	byte[] buf = new byte[4];
	is.read(buf);

 

- 위의 코드는 파일로부터 4바이트를 읽어 들인다. 그러나 byte 단위의 배열 형태로만 읽어 들일 수 있다. 즉, 파일에 4바이트 크기의 int형 정수가 저장되어 있을 때, 이를 정수의 형태로 꺼내는 것은 불가능하다. 이를 위해서는 byte 단위로 4개의 바이트를 읽어 들인 다음에 이를 int형 데이터로 조합해야 한다.
- 이러한 조합을 중간에서 대신해 주는 스트림을 가리켜 필터 스트림이라 한다.
- 필터 스트림은 물이 뿜어져 나오는 호스에 연결된 샤워기 꼭지에 비유할 수 있다.

- 필터 스트림도 입력용과 출력용으로 구분된다.

 

예제 3)

package ex3;

import java.io.*;

public class FilterTest {
	public static void main(String[] args) throws IOException {
		
		DataOutputStream filterOut = new DataOutputStream(new FileOutputStream("data.bin"));
										// 필터 스트림 클래스; 바이트형은 온전한 데이터로 변환
		// 출력 스트림과 필터 스트림과의 연결!
		filterOut.writeInt(1275); // 4byte씩 출력
		filterOut.writeDouble(245.79); 
		filterOut.close();
		DataInputStream filterIn = new DataInputStream(new FileInputStream("data.bin")); //**
		
		// 입력 스트림과 필터 스트림과의 연결!
		int num1 = filterIn.readInt();
		double num2 = filterIn.readDouble();
		filterIn.close();
		System.out.println(num1);
		System.out.println(num2);
	}
}

 

출력)

1275
245.79

 

▶ 버퍼링 기능을 제공하는 필터 스트림 ; 배열이 내장되어 있어서 자동으로 배열을 적용해주는 필터

- BufferedInputStream은 입력버퍼, BufferedOutputStream은 출력버퍼를 제공하는 대표적인 필터 스트림 클래스이다.

- BufferedOutputStream 클래스는 버퍼가 꽉 차면, 출력 스트림으로 데이터를 한번에 전송한다.

- 출력할 데이터는 버퍼 필터가 full 상태여야 된다. 그런데 마지막에 더 이상 채워질 게 없어서 잉여 공간이 남는다면? 임의의 명령어를 내리거나 close(); (버퍼 출력 -> 스트림 종료)를 통해서 종료한다.

 

예제 3) 예제 1번에 필터를 장착한 경우

package ex3;

import java.io.*;

public class BufferFilterTest {

	public static void main(String[] args) throws IOException {
		InputStream in = new FileInputStream("travel.mp4");
		OutputStream out = new FileOutputStream("copy3.mp4");
		BufferedInputStream bin = new BufferedInputStream(in); //필터
		BufferedOutputStream bout = new BufferedOutputStream(out); //필터
		int copyByte = 0;
		int bData;

		long start = System.currentTimeMillis();

		while (true) {
			bData = bin.read();
			if (bData == -1)
				break;
			bout.write(bData);
			copyByte++;
		}
		bin.close();
		bout.close();

		long end = System.currentTimeMillis();
		System.out.println("복사 경과 시간 : " + (end - start) / 1000.0 + "초");
		System.out.println("복사된 바이트 크기 " + copyByte);
	}

}

 

출력)

복사 경과 시간 : 0.365초
복사된 바이트 크기 : 51171093

ㄴ 왜 적게 걸릴까? 하드 디스크는 CPU에 비해서 느리다. 예를 들어 한번 데이터를 옮길 때 배열에다가 대량의 데이터를 가지고 오기 때문에 적게 걸리는 것이다.

 

▶ 파일에 기본자료형 데이터 저장 + 버퍼링 ; 버퍼 필터는 중간에 들어가야 한다.

- 기본자료형 데이터의 입출력도 가능하고 버퍼링으로 인해 성능도 개선된다.

- DataOutputStream은 기본 자료형 데이터를 바이트 단위로 쪼개서 BufferdOutputStream으로 전달하고, BufferedOutputStream 은 바이트 단위의ㅡ 데이터를 FileOutputStream으로 전달한다.

- 주의할 점은 연결순서가 바뀌면 안된다. 이유는 BufferedOutputStream 스크림이 전달하는 바이트 단위의 데이터를 DataOutputStream이 받을 수 없기 때문이다.

예제 6) Flush의 사용

package ex3;

import java.io.*;

public class FlushTest {

	public static void main(String[] args) throws IOException {
		OutputStream out1 = new FileOutputStream("data1.bin");
		DataOutputStream dataOut = new DataOutputStream(out1);
		performanceTest(dataOut);
		dataOut.close(); // 출력버퍼 삭제(밀어내기) -> 스트림 종료
		
		OutputStream out2 = new FileOutputStream("data2.bin");			// 버퍼 크기 조정
		BufferedOutputStream bufFilterOut = new BufferedOutputStream(out2, 1024 * 100);
		DataOutputStream dataBufOut = new DataOutputStream(bufFilterOut);
		performanceTest(dataBufOut);
		dataBufOut.close();
	}
	
	public static void performanceTest(DataOutputStream dataOut) throws IOException {

		long startTime = System.currentTimeMillis();
		
		for (int i = 0; i < 1000; i++)
			for (int j = 0; j < 10000; j++)
				dataOut.writeDouble(12.345); // 약 80MB 크기의 데이터를 파일에 저장
		dataOut.flush(); // 출력버퍼의 마지막 데이터까지 완전히 전송이후 시간측정 위해
		// 출력 버퍼 밀어내기
		// 버퍼는 가득 채워서 이동하는게 좋다. 부득이할 경우에만 쓴다.
		// 자연스럽게 .close();로 밀어내는게 좋다.
		long endTime = System.currentTimeMillis();
		
		System.out.println("경과시간: " + (endTime - startTime));
	}
}

 

※ 원래는 System.out.println("");은 필터 printStream의 소속이다. 원칙은 class printwriter을 이용해서 출력해야 함이 맞다.

 

문 1) 콘솔에 출력하기 System.out의 println와 print 메서드를 사용해 왔다. 그런데 out은 Sytem 클래스에 다음과 같이 선언되어 있다. public static final PrintStream out; 즉, out은 PrintStream 인스턴스의 참조 변수이다.
 API문서에 보면 java.io.OutputStream ← java.io.FilterOutputStream ← java.io.PrintStream(필터 역할) 상속 구조를 알 수 있다. 따라서 System.out은 모니터를 의미하는 표준 출력 스트림에 PrintStream의 필터 스트림이 연결된 형태로 볼 수 있다.
 결국, System.out.println(24);라고 정수를 출력하면 PrintStream이 문자열의 형태로 변환해 줬던 것이다. "다양한 형태의 데이터를 문자열의 형태로 출력하거나(println), 문자열의 형 태로 조합하여 출력한다(printf)."
 아래 코드의 출력 결과를 파일 a.txt에 문자열의 형태로 저장되도록 변경해 보자. 또한 저장된 결과를 메모장으로 확인해 보자.

package ex3;

import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;

class MyInfo {
	String info;

	public MyInfo(String info) {
		this.info = info;
	}

	public String toString() {
		return info;
	}
}

public class PrintlnPrintf {

	public static void main(String[] args) throws FileNotFoundException {
		// 바이트 단위 스트림
		OutputStream out = new FileOutputStream("a.txt");
		// 출력속도 향상(선택)
		BufferedOutputStream bOut = new BufferedOutputStream(out);
		// 필터 클래스를 이용해서 문자 단위 스트림으로 변환
		PrintStream pOut = new PrintStream(bOut);
		
		MyInfo mInfo = new MyInfo("저는 자바 프로그래머입니다.");
		pOut.println("제 소개를 하겠습니다.");
		pOut.println(mInfo);
		pOut.printf("나이 %d, 몸무게 %dkg입니다.", 24, 72);
		pOut.close();
		
	} //main end

} //class end

 

+ 버퍼가 없는 경우

package ex3;

import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;

class MyInfo {
	String info;

	public MyInfo(String info) {
		this.info = info;
	}

	public String toString() {
		return info;
	}
}

public class PrintlnPrintf {

	public static void main(String[] args) throws FileNotFoundException {
		// 바이트 단위 스트림
		OutputStream out = new FileOutputStream("a.txt");
		// 출력속도 향상(선택)
//		BufferedOutputStream bOut = new BufferedOutputStream(out);
		// 필터 클래스를 이용해서 문자 단위 스트림으로 변환
		PrintStream pOut = new PrintStream(out);
		
		MyInfo mInfo = new MyInfo("저는 자바 프로그래머입니다.");
		pOut.println("제 소개를 하겠습니다.");
		pOut.println(mInfo);
		pOut.printf("나이 %d, 몸무게 %dkg입니다.", 24, 72);
//		pOut.close(); // 버퍼가 있는 경우에는 있어야 하지만 없으면 없어도 된다.
		
	} //main end

} //class end

출력)

'ICIA 수업일지' 카테고리의 다른 글

2021.07.01 수업일지  (0) 2021.07.03
2021.06.30 수업일지  (0) 2021.07.03
2021.06.28 수업일지  (0) 2021.07.03
2021.06.25 수업일지  (0) 2021.06.26
2021.06.24 수업일지  (0) 2021.06.26