⦁  입출력(I/O)과 스트림(STREAM)

 

▶ 입출력

- 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고받는 것

 

  스트림

- 두 대상을 연결하고 데이터를 전송하는데 사용되는 연결통로 (14장의 스트림과는 다른 개념임)

- 스트림은 단방향통신만 가능하므로 입력스트림(input stream)과 출력스트림(output stream)이 따로 필요함

- 스트림은 먼저 보낸 데이터를 먼저 받으며 중간에 건너뜀 없이 연속적으로 데이터를 주고 받음.

 

[입력스트림과 출력스트림의 종류]

 

[InputStream과 OutputStream에 정의된 읽기와 쓰기를 수행하는 메소드]

 

 

read() 는 추상메소드이다.

그런데 read(byte[] b, int off, int len) 메소드의 내용을 보면 read()를 호출하고 있다.

따라서 read()는 반드시 구현되어야 하는 핵심 메소드이다. 

 

 

 보조스트림

- 스트림의 기능을 향상시키거나 새로운 기능을 추가하기 위해 사용한다.

- 독립적으로 입출력을 수행할 수 없다.

// 먼저 기반스트림을 생성한다.
FileInputStream fis = new FileInputStream("test.txt");
// 기반스트림을 이용해서 보조스트림을 생성한다.
BufferedInputStream bis = new BufferedInputStream(fis);

bis.read(); // 보조스트림인 BufferedInputStream으로부터 데이터를 읽는다.

보조스트림이 직접 입력기능을 수행하는 것 처럼 보이지만 실제 입력기능은 BufferedInputStream과 연결된 FileInputStream이 수행하고 보조스트림인 BufferedInputStream은 버퍼만을 제공함.

 

[보조스트림의 종류]

모든 보조스트림은 InputStream과 OutputStream의 자손들이므로 입출력방법이 같다.

 
문자기반 스트림 : Reader, Writer
 
- 입출력 단위가 문자(char, 2 byte)인 스트림. 문자기반 스트림의 최고조상
- Java는 한 문자를 의미하는 char형이 2 byte이기 때문에 바이트(1byte) 기반 스트림(InputStream, OutputStream) 보다는 문자기반 스트림(Reader, Writer)을 사용하는 것이 좋다.

 

  바이트기반 스트림

 

InputStream(바이트기반 입력스트림의 최고 조상) 의 메서드

 

OutputStream(바이트기반 출력스트림의 최고 조상)의 메서드

 

ex)

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;

public class Ex15_1 {

	public static void main(String[] args) {
		byte[] inSrc = {0,1,2,3,4,5,6,7,8,9};
		byte[] outSrc = null;
		
		ByteArrayInputStream input = new ByteArrayInputStream(inSrc);
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		
		
		int data = 0;
		
		while((data = input.read())!=-1)
			output.write(data); // void write(int b)
		
		outSrc = output.toByteArray(); // 스트림의 내용을 byte배열로 반환한다.
		
		System.out.println("Input Source : "+ Arrays.toString(inSrc));
		System.out.println("Output Source : "+ Arrays.toString(outSrc));

	}

}

 

 

 

ex) read와 write 메소드

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;

public class Ex15_2 {

	public static void main(String[] args) {
		byte[] inSrc = {0,1,2,3,4,5,6,7,8,9};
		byte[] outSrc = null;
		byte[] temp = new byte[10];
		
		ByteArrayInputStream input = new ByteArrayInputStream(inSrc);
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		
		// 최대 temp.length개의 데이터를 input스트림에서 읽어와서 temp의 index 0부터 저장한다.
		input.read(temp,0,temp.length);  
		
		// temp의 index5부터 5개의 데이터를 output스트림에 쓴다.
		output.write(temp,5,5);
		
		outSrc = output.toByteArray();
		
		System.out.println(Arrays.toString(inSrc));
		System.out.println(Arrays.toString(temp));
		System.out.println(Arrays.toString(outSrc));
		

	}

}

 

 

 

ex)

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;

public class Ex15_4 {

	public static void main(String[] args) {
		byte[] inSrc = {0,1,2,3,4,5,6,7,8,9};
		byte[] outSrc = null;
		byte[] temp = new byte[4];
		
		ByteArrayInputStream input = new ByteArrayInputStream(inSrc);
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		
		while(input.available() > 0 ) {
			try {
//				input.read(temp); // 배열의 크기만큼 값을 읽어온다
//				output.write(temp); // 배열의 크기만큼 값을 쓴다. 
				
				int len = input.read(temp);
				output.write(temp,0,len); // 마지막에 8,9만 output 스트림에 쓰인다
				
			} catch (IOException e) {}
			
		}
		
		outSrc = output.toByteArray();
		
		System.out.println("Input Source : " + Arrays.toString(inSrc));
		System.out.println("temp : " + Arrays.toString(temp));
		System.out.println("Output Source : " + Arrays.toString(outSrc));

	}

}

4개씩 값을 읽어오게 되는데, 기존의 방식대로 하게 되면 4개씩 읽어오다가 마지막에 남은 2개의 값을 마저 output에 쓸 때 8, 9, 6, 7 을 쓰게 된다. 왜냐하면 8, 9를 이전에 있던 4, 5, 6, 7에 덮어쓰기 때문이다.(성능때문에 덮어씀)

그러므로 len에 읽어온 개수를 저장하고 그 개수만큼만(여기서는 2개) 쓰도록 코드를 작성하면 된다.

 

 

FileInputStream과 FileOutputStream

 

- 파일(file)에 데이터를 입출력하는 바이트기반 스트림

 

ex) 파일 내용을 읽어서 그대로 출력하기

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FileViewer {

	public static void main(String[] args) throws IOException {
		FileInputStream fis = new FileInputStream("C:\\Users\\K\\Desktop\\Java1\\자바의 정석\\ch15\\FileViewer.java");
		// 읽을 파일의 디렉토리를 생성자로 넣어준다
        
		int data = 0;
		
		while((data=fis.read())!=-1) {
			char c = (char)data;
			System.out.print(c);
		}
	}

}

 

 

 

ex) 파일의 내용을 그대로 다른 파일에 복사하여 저장하기

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy {

	public static void main(String[] args) {
		try {
			FileInputStream fis = new FileInputStream("FileCopy.java");
			FileOutputStream fos = new FileOutputStream("FileCopy.bak");
			
			int data = 0;
			while((data = fis.read())!=-1) {
				fos.write(data);
			}
			
			fis.close();
			fos.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

}

 

 

 

⦁  바이트기반의 보조스트림

 FilterInputStream과 FIlterOutputStream

- 모든 바이트기반 보조스트림의 최고 조상

- 보조스트림은 자체적으로 입출력을 수행할 수 없다.

- 상속을 통해 FilterInputStream / FilterOutputStream의 read()와 write()를 원하는 기능대로 오버라이딩 해야 한다.

public class FilterInputStream extends InputStream {
	protected volatile InputStream in;
    protected FilterInputStream(InputStream in) {
    	this.in = in;
    }
    
    public int read() throws IOException {
    	return in.read();
        }
        ...
}

 

FilterInputStream의 자손 : BufferedInputStream, DataInputStream, PushbackInputStream 등

FilterOutputStream의 자손 : BufferedOutputStream, DataOutputStream, PrintStream 등

 

 

  BufferedInputStream과 BufferedOutputStream

- 입출력 효율을 높이기 위해 버퍼(byte[])를 사용하는 보조스트림

- 보조스트림을 닫으면 기반스트림도 닫힌다.

public class FilterOutputStream extends OutputStream {
	protected OutputStream out;
    public FilterOutputStream(OutputStream out) {
    	this.out = out;
    }
    ...
    public void close() throws IOException {
    	try {
        	flush();
        } catch (IOException ignored) {}
        out.close(); // 기반 스트림의 close()를 호출한다.
    }
}

 

 

 

ex) BufferedOutputStream

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BufferedOutputStreamEx1 {

	public static void main(String[] args) {
		try {
			FileOutputStream fos = new FileOutputStream("123.txt");
			// BufferedOutputStream의 버퍼 크기를 5로 한다.
			BufferedOutputStream bos = new BufferedOutputStream(fos, 5);
			// 파일 123.txt에 1부터 9까지 출력한다.
			for (int i='1'; i<='9'; i++) {
				bos.write(i);
				
			}
			
            // bos.close(); 를 추가해야 함
            // bos.close();
            
			fos.close(); // 만약 bos.close(); 를 쓴다면 이 부분은 없어도 됨.
            
			
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		

	}

}

실행 결과로 12345만 123.txt 파일에 저장된다.

왜냐하면 버퍼에 6789가 남아있는 상태로 프로그램이 종료되기 때문이다.

bos.close()를 호출해야 버퍼에 남아있던 모든 내용이 출력된다. 

그리고 보조스트림의 close() 메소드는 기반스트림을 닫는 close() 메소드를 호출해주기 때문에 따로 기반스트림을 닫을 필요 없이 보조스트림의 close() 메소드만 호출해도 된다.

 

 

▶ DataInputStream과 DataOutputStream

- 기본형 단위로 읽고 쓰는 보조스트림

- 각 자료형의 크기가 다르므로 출력할 때와 입력할 때 순서에 주의

 

ex) DataOutputStream 활용

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataOutputStreamEx1 {

	public static void main(String[] args) {
		FileOutputStream fos = null;
		DataOutputStream dos = null;
		
		try {
			fos = new FileOutputStream("sample.dat");
			dos = new DataOutputStream(fos);
			dos.writeInt(10);
			dos.writeFloat(20.0f);
			dos.writeBoolean(true);
			
			dos.close();
		} catch (IOException e) {
			
			e.printStackTrace();
		}
		

	}

}

 

SequenceInputStream

- 여러 입력 스트림을 연결해서 하나의 스트림처럼 다룰 수 있게 해준다.

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Arrays;
import java.util.Vector;

public class Ex15_7 {

	public static void main(String[] args) {
		byte[] arr1 = {0,1,2};
		byte[] arr2 = {3,4,5};
		byte[] arr3 = {6,7,8};
		byte[] outSrc = null;
		
		Vector v = new Vector();
		v.add(new ByteArrayInputStream(arr1));
		v.add(new ByteArrayInputStream(arr2));
		v.add(new ByteArrayInputStream(arr3));
		
		SequenceInputStream input = new SequenceInputStream(v.elements());
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		
		int data = 0;
		
		try {
			while((data = input.read())!= -1) {
				output.write(data);
			}
		} catch (IOException e) {}
		
		outSrc = output.toByteArray();
		
		System.out.println("Input Source1 :" + Arrays.toString(arr1));
		System.out.println("Input Source2 :" + Arrays.toString(arr2));
		System.out.println("Input Source3 :" + Arrays.toString(arr3));
		System.out.println("Output Source :" + Arrays.toString(outSrc));
	}

}

(Vector에 저장된 순서대로 입력되므로 순서에 주의해야 한다)

 

 

PrintStream

- 데이터를 다양한 형식의 문자로 출력하는 기능을 제공하는 보조스트림

- System.out과 System.err이 PrintStream이다.

- PrintStream보다 PrintWriter가 더 다양한 언어의 문자를 처리하는데 적합하므로 가능하면 PrintWriter를 사용하는게 좋다.

 

(자주 쓰는 print 과 println 메소드를 갖고 있다)

printf에 사용되는 다양한 옵션(%d, %s 등등)은 Java API 문서에서 Formatter클래스를 참고하면 된다.


출처 - Java의 정석 3rd Edition (남궁 성 지음)

+ Recent posts