Java(Day26)_데이터 입출력_입출력 스트림_I/O Stream_바이트 출력 스트림_Output Stream_

  • by

<데이터 입출력>

I/O 스트림(I/O Stream)

: 데이터는 키보드를 통해 입력하거나 파일 또는 프로그램에서 입력할 수 있습니다.

대조적으로 데이터는 모니터를 통해 출력될 수 있으며 파일에 저장하거나 다른 프로그램으로 전송할 수 있습니다.

이것을 총칭하여 데이터 입출력이라고합니다.

※ Stream API 와 I/O Stream 은 다른 것임을 인식해야 한다


출처: https://velog.io/@mmy789

△Java는 입력 스트림과 출력 스트림을 통해 데이터를 입출력합니다.

△ 스트림이란 한 방향으로 들어가 데이터가 흐르는 것을 말하며, 위 그림과 같이 데이터는 출발지에서 나온다.

도착지로 유입

– 프로그램 기준으로 데이터가 들어가면 입력 스트림(Input Stream), 데이터가 나오면 출력 스트림(OutputStream)

– 프로그램이 다른 프로그램과 데이터를 교환하려면 둘 다 입력 및 출력 스트림이 필요합니다.


출처: https://velog.io/@mmy789

출처: https://velog.io/@mmy789

– 어떤 데이터를 입출력할지에 따라 스트림은 다음의 2종류로 나눌 수 있다

바이트 스트림: 이미지, 멀티미디어, 문자 등 모든 종류의 데이터를 입출력할 때 사용

문자 스트림: 문자만 입출력할 때 사용

– Java는 java.io 패키지로 데이터 입출력에 대한 라이브러리를 제공합니다.

▼ java.io 패키지는 바이트 스트림과 문자 스트림을 아래 표와 같이 이름으로 단락지어 제공한다


출처: https://velog.io/@mmy789

– 바이트 입출력 스트림의 최상위 클래스는 InputStream 와 OutputStream 이다

– 이 클래스를 상속하는 아이 클래스에는 접미사로 InputStream 또는 OutputStream 가 붙는다

(ex. 이미지와 같은 바이너리 파일의 입출력 스트림 클래스는 FileInputStream 와 FileOutputStream 이다)

– 문자 입출력 스트림의 최상위 클래스는 Reader와 Writer

– 이 클래스를 상속하는 서브 클래스에는 접미사로 Reader 또는 Writer 가 붙는다

(ex. 텍스트 파일의 입출력 스트림 클래스는 FileReader와 FileWriter입니다)


출처: https://velog.io/@dev-taewon-kim

		// Input Output Stream
		
		//데이터 형태 : 바이트 스트림, 문자 스트림
		//방향 : 인풋 스트림, 아웃풋 스트림
		
		//1)바이트 단위 인풋 스트림 : InputStream
		//2)바이트 단위 아웃풋 스트림 : OutputStream
		//3)문자 단위 인풋 스트림 : Reader
		//4)문자 단위 아웃풋 스트림 : Writer


바이트 출력 스트림 (OutputStream)

– OutputStream 는 바이트 출력 스트림의 최상위 클래스로 추상 클래스입니다.

– 모든 바이트 스트림 클래스는, 이 OutputStream 클래스를 상속해 작성됩니다.


출처: https://velog.io/@mmy789

– OutputStream 클래스에는, 모든 바이트 출력 스트림이 디폴트로 가지지 않으면 안되는 메소드가 정의되고 있습니다.

▼ OutputStream 클래스의 주요 메서드


출처: https://velog.io/@dev-taewon-kim

1바이트 출력_ Write(int b) 메서드

write(int b) 메소드는 파라미터 int(4bytes) 로 종료 1byte 만을 출력한다

– 파라미터가 int형이므로 4bytes 모드를 송신하는 것은 아니다


출처: https://velog.io/@dev-taewon-kim

(예)

	public static void main(String() args) {
		try {
			//데이터 도착지를 test1.db 파일로 하는 바이트 출력 스티림 생성
			OutputStream os = new FileOutputStream("output/test1.db");
			
			byte a = 10;
			byte b = 20;
			byte c = 30;
			
			//1byte 씩 출력
			os.write(a);
			os.write(b);
			os.write(c);
			
			//내부 버퍼에 잔류하는 바이트를 출력하고 버퍼를 비움
			os.flush();
			//출력 스트림을 닫아 사용한 메모리를 해제
			os.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

△FileOutputStream constructor 은, 주어진 파일을 생성할 수 없는 경우에 IOException 를 발생시킨다

△ write(), flush(), close() 메소드도 IOException 가 발생할 가능성이 있으므로, 예외 처리를 실시해야 한다 (try-catch)

△ OutputStream 는 내부에 작은 버퍼 (buffer) 를 가지고 있어 write() 메소드가 불려 가면 버퍼에 바이트를 우선 포함해,

버퍼가 가득 차면 순서대로 바이트를 출력합니다.

△ flush() 메소드는 내부 버퍼에 남아 있는 모든 바이트를 출력해 버퍼를 비우는 역할을 한다

△내부 버퍼를 사용하는 이유는 출력 성능을 향상시키기 위한 것이다.

△ 출력 스트림을 사용하지 않게 되었을 때는, close() 메소드를 호출해 출력 스트림이 사용하고 있던 메모리를 해방

바이트 배열 출력_ Write(byte( ) b) 메서드

: 통상 1 바이트를 출력하는 것은 드물고, 통상 바이트 배열을 통째로 출력하는 경우가 많다

write(byte( ) b) 메소드는, 파라미터로서 주어진 배열의 모든 바이트를 출력합니다.


출처: https://velog.io/@dev-taewon-kim

– 배열의 일부를 출력하고 싶은 경우 write(byte( ) b, int off, int len) 방법을 사용할 수 있습니다.

– 이 메소드는 b(off) 로부터 len 개의 바이트를 출력한다


출처: https://velog.io/@dev-taewon-kim

	public static void main(String() args) {
		try (var os = new FileOutputStream("output/output7.txt")) {
			//한 바이트씩 쓰기
			os.write(30);
			os.write(16563);
			
			// 여러 바이트 쓰기
			byte() data = {3, 3, 3, 3, 3, 3, 3, 3, 3};
			os.write(data);
			
			//여러바이트 쓰기 (배열의 일부분)
			os.write(data, 0, 3); //3bytes
			os.write(data, 4, 5); //5bytes
			os.write(data, 0, data.length); // 9bytes
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


바이트 입력 스트림(InputStream)

– InputStream 는 바이트 입력 스트림의 최상위 클래스로, 추상 클래스입니다.

– 모든 바이트 입력 스트림은 InputStream 클래스를 상속하여 작성됩니다.


출처: https://velog.io/@mmy789

– InputStream 클래스에는, 바이트 입력 스트림이 디폴트로 가지지 않으면 안되는 메소드가 정의되고 있습니다.

▼ InputStream 클래스의 주요 메서드


1바이트 읽기_ read()

– read() 메소드는 입력 스트림로부터 1byte 를 읽어들여, int(4byte) 타입으로 돌려준다

– 따라서 반환된 4바이트 중 1바이트에만 데이터가 포함됩니다.

(ex. 입력 스트림로부터 5 개의 바이트가 들어 있으면, 아래의 이미지와 같이 read() 메소드로 1byte 씩 5 회 읽어낼 수 있다)


출처: https://velog.io/@dev-taewon-kim

– 더 이상 입력 스트림로부터 바이트를 읽을 수 없는 경우는, read() 메소드는 -1 을 돌려줍니다.

읽을 수 있는 마지막 바이트까지 반복 1바이트씩 읽을 수 있다

InputStream is = ...;
while (true) {
	int data = is.read();    //1 바이트를 읽고 리턴
    if (data == -1) break;   //-1을 리턴했을 경우 while 문 종료
}

– FileInputStream 생성자는, 주어진 파일이 존재하지 않는 경우에 FileNotFoundException를 발생시킨다

– 그리고 read(), close() 메서드에서 IOException이 발생할 수 있으므로 두 예외를 모두 처리해야합니다.


바이트 배열로 읽는다 _ read(byte( ) b)

– read(byte( ) b) 메소드는 입력 스트림로부터 주어진 배열의 길이만큼 바이트를 읽어 배열에 포함합니다.

읽은 바이트 수를 반환합니다.


출처: https://velog.io/@dev-taewon-kim

– read(byte( ) b) 도 입력 스트림로부터 바이트를 읽을 수 없게 되면 -1 을 돌려줍니다.

이것을 이용하면, 읽을 수 있는 마지막 바이트까지 반복 읽을 수 있다

InputStream is = ...;
byte() data = new byte(100);
while (true) {
	int num = is.read(data);   //최대 100byte를 읽고, 읽은바이트는 배열 data 저장, 읽은 수는 리턴
    if (num == -1) break;      //-1을 리턴하면 while 문 종료
}

– 많은 양의 바이트를 읽는 read(byte()b) 메소드를 사용하는 것이 좋습니다.

– 입력 스트림로부터 100개의 바이트가 들어온다면, read() 메소드는 100회를 반복해 읽어야 하지만,

read(byte( ) b) 메서드는 한번 읽으면 배열 길이만 읽으므로 읽기 횟수가 크게 줄어듭니다.

(파일 복사 예)

ㄴ 파일 복사의 원리는 FileInputStream에서 읽은 바이트를 즉시 FileOutStream에 출력합니다.

	public static void main(String() args) throws Exception{
		String originalFileName = "output/bread.jpg";
		String targetFileName = "output/bread_copy3.jpg";
		
		//입출력 스트림 생성
		InputStream is = new FileInputStream(originalFileName);
		OutputStream os = new FileOutputStream(targetFileName);
		
		//읽은 바이트를 저장할 배열 생성
		byte() data = new byte(1024);
		while(true) {
			//최대 1024바이트를 읽고 배열에 저장, 읽은 바이트는 리턴
			int num = is.read(data);
			//파일을 다 읽으면 while문 종료
			if(num == 1) break;
			//읽은 바이트 수만큼 출력
			os.write(data, 0, num);
		}
		
		//내부 버퍼 잔류 바이트를 출력하고 버퍼를 비움
		os.flush();
		os.close();
		is.close();
		
		System.out.println("복사가 잘 되었습니다.

"); }

– Java 9에서보다 편리하게 입력 스트림에서 출력 스트림으로 바이트 복사 transferTo() 방법

InputStream에 추가됨

▼아래의 코드를 1행으로 옮겨놓는 것이 가능

	byte() data = new byte(1024);
		while(true) {
			int num = is.read(data);
			if(num == 1) break;
			os.write(data, 0, num);
		}

is.transferTo(os);


문자 입출력 스트림

– 바이트 입출력 스트림인 InputStream 및 OutputStream에 대응하는 문자 입출력 스트림

리더그리고 Writer

– 입출력되는 단위가 문자인 것을 제외하고, 바이트 입출력 스트림과 사용 방법은 동일

문자 출력_Writer

– Writer는 문자 출력 스트림의 최상위 클래스이며 추상 클래스입니다.

– 모든 문자 출력 스트림 클래스는 Writer 클래스를 상속하여 작성됩니다.


출처: https://velog.io/@mmy789

– Writer 클래스에는, 모든 캐릭터 출력 스트림이 디폴트로 가지지 않으면 안되는 메소드가 정의되고 있습니다

▼ Writer 클래스의 주요 메소드


출처: https://velog.io/@mmy789

– Writer는 OutputStream과 사용 방법은 동일하지만 출력 단위가 문자(char)

– 캐릭터 라인을 출력하는 write(String str) 메소드를 추가로 제공

(하나의 문자열, 문자 배열, 문자열을 각각 출력하는 방법의 예)

	public static void main(String() args) {
		try {
			//문자 기반 출력 스트림 생성
			Writer writer = new FileWriter("output/output1.txt");
			
			//1문자씩 출력
			char a="A";
			writer.write(a);
			char b = 'B';
			writer.write(b);
			
			//char 배열 출력
			char() arr = { 'C', 'D', 'E' };
			writer.write(arr);
			
			//문자열 출력
			writer.write("FGH");
			
			//버퍼에 잔류하고 있는 문자들을 출력하고, 버퍼를 비움
			writer.flush();
			
			//출력 스트림을 닫고 메모리 해제
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

문자 읽기_리더

– Reader는 문자 입력 스트림의 최상위 클래스이며 추상 클래스입니다.

– 모든 문자 입력 스트림 클래스는 Reader 클래스를 상속하여 생성됩니다.


출처: https://velog.io/@mmy789

– Reader 클래스에는, 문자 입력 스트림이 디폴트로 가지지 않으면 안되는 메소드가 정의되고 있습니다.

▼ Reader 클래스의 주요 메소드


출처: https://velog.io/@mmy789

– Reader는 InputStream과 사용 방법은 동일하지만 출력 단위가 문자(char)

(문자를 하나씩 읽거나 문자 배열로 읽는 방법의 예)

	public static void main(String() args) {
		try {
			Reader reader = null;
			
			//1 문자씩 읽기
			reader = new FileReader("output/writer2.txt");
			while (true) {
				int data = reader.read();
				if (data == -1) break;
				System.out.println((char) data);
			}
			reader.close();
			System.out.println();
			
			//문자 배열 읽기
			reader = new FileReader("output/writer2.txt");
			char() data = new char(100);
			while(true) {
				int num = reader.read(data);
				if (num == -1) break;
				for (int i = 0; i < num; i++) {
					System.out.println(data(i));
				}
			}
			reader.close();
		} catch (FileNotFoundException e ) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


보조 스트림

: 2차 스트림은 다른 스트림과 연동하여 다양한 유용한 기능을 제공하는 스트림입니다.

보조 스트림은 그 자체가 입출력을 실행할 수 없기 때문에, 입출력 소스로부터 직접 생성된 입출력 스트림에

연결하여 사용해야 함


출처: https://velog.io/@dev-taewon-kim

– I/O 스트림에 2차 스트림을 연결하려면 2차 스트림을 작성할 때 생성자 매개변수로 I/O 스트림을 제공합니다.


▼ 예를 들어 바이트 입력 스트림인 FileInputStream 에 InputStreamReader 보조 스트림을 연결하는 코드는 이하와 같다

InputStream is = new FileInputStream("...");
InputStreamReader reder = new InputStreamReader(is);

– 2차 스트림은 다른 2차 스트림과 연관되며 스트림 체인으로 구성할 수 있습니다.


출처: https://velog.io/@eunyeong560

▼ 예를 들어 문자 변환 보조 스트림인 InputStreamReader 에 BufferedReader 보조 스트림을 연결하는 코드는

다음과 같이

InputStream is = new FileInputStream("...");
InputStreamReader reader = new InputStreamReader(is);
BufferedReader br = new BufferedReader(reader);

▼자주 사용되는 보조 스트림



성능 향상 스트림_ Buffered

– CPU와 메모리가 아무리 우수하더라도 하드디스크의 입출력이 느려지면 프로그램의 실행 성능은 하드디스크의 처리속도로

맞추다

– 네트워크로 데이터를 전송할 때도 느린 네트워크 환경이라면 컴퓨터 사양이 아무리 좋더라도 메신저와 게임 속도는

늦을 수밖에 없다

– 위의 문제에 대한 완전한 해결책은 불가능하지만 프로그램이 I / O 소스와 직접 작업하지 않고 중간에 메모리 버퍼 (buffer)

그리고 작업함으로써 실행 성능을 향상시킬 수 있습니다.


출처: https://hudi.blog/

△출력 스트림의 경우, 직접 하드 디스크에 데이터를 보내지 않고 메모리 버퍼에 데이터를 보내는 것으로 출력 속도를

개선할 수 있는

△ 버퍼는 데이터가 축적되기를 기다린 후 가득 차면 데이터를 한 번에 하드 디스크로 보냅니다.

출력 횟수 감소


출처: https://hudi.blog/

△입력 스트림에서도 버퍼를 사용하면 읽기 성능이 좋아진다

△ 하드 디스크에서 직접 읽는 것보다 메모리 버퍼에서 읽는 것이 더 빠릅니다.

△상기와 같이 메모리 버퍼를 제공하여 프로그램의 실행 성능을 향상시키는 보조 스트림이 있다.

로바이트 스트림에 BufferedInputStream, BufferedOutputStream예,

로문자 스트림에 BufferedReader, BufferedWriter

▼ 보조 스트림을 연결하는 방법

BufferedInputStream bis = new BufferedInputStream(바이트 입력 스트림);
BufferedOutputStream bos = new BufferedOutputStream(바이트 출력 스트림);
BufferedReader br = new BufferedReader(문자 입력 스트림);
BufferedWriter bw = new BufferedWriter(문자 출력 스트림);