슬기로운 개발생활

온라인 자바 스터디 #13 - I/O 입출력 스트림

by coco3o
반응형

목표

자바의 Input과 Ontput에 대해 학습하세요.

학습할 것 (필수)

  • 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
  • InputStream과 OutputStream
  • Byte와 Character 스트림
  • 표준 스트림 (System.in, System.out, System.err)
  • 파일 읽고 쓰기

스트림(Stream)  /  버퍼(Buffer)  /  채널(Channel) 기반의 I/O

I / O란?

  • Input과 Output의 약자로 입·출력을 의미한다.
  • 입출력의 간단한 예로 키보드로 텍스트를 입력하고, 모니터로 입력한 텍스트를 출력하는 것임

스트림 (Stream)

Input  ㅡ>  Output

 

위의 식에서 ' ㅡ> '은 스트림을 의미함

 

Input , Output 구분없이  어느  한쪽에서  다른  쪽으로  데이터를  보내려면  일종의  다리역할( ㅡ> )을  하는  친구가  필요하다.

 

그  친구가  바로  스트림( ㅡ> )  이다.

 

 

추가적으로  스트림의  어원은  연속적인  데이터의  흐름을  물에  비유하여  붙여진  이름이라고  한다.

 

가장  큰  특징으로는  물이  한방향으로  흐르는  것과  같이  스트림은  단방향통신만  가능하다.

 

즉,  하나의  스트림으로  입출력을  동시에  처리할  수  없다.

 

만약  입출력을  동시에  처리하고  싶으면  입력  스트림  1개,  출력  스트림  1개  총  2개의  스트림을  생성하면 된다.

 

[정리]

-  단방향으로  연속적이게  흘러가는  형태  :  출발지에서  나와서  도착지로  들어감

-  프로그램이  출발지냐  도착지냐에  따라  스트림  종류가  다름

(1)  데이터를 입력 받을 때 (도착지)  :  입력  스트림(InputStream)

(2)  데이터를 출력할 때 (출발지)  :  출력  스트림(OutputStream)

-  프로그램이  네트워크  상의  다른  프로그램과  데이터  교환을  위해  두  스트림이  모두  필요함

  스트림은  단방향  통신의  특징을  갖기  때문에,  하나의  스트림으로  입출력을  동시에  할  수  없다.

 

출처 : https://velog.io/@ljs0429777/Java-IO

 


 

버퍼 (Buffer)

버퍼란  데이터를  전송하는  저 · 고속  장치간의  속도차이를  줄여주는  역할을  하는  중간  저장소라고  생각하면  된다.

 

출처 : https://blog.naver.com/swoh1227/222237603565

I/O에서  버퍼는  보조스트림이라고도  불리며  위와  같이  데이터를  묶어서  전송하는  역할을  하게  된다.

 

이는  CPU와  외부장치간의  속도차이를  줄여주어  입출력에  대한  속도를  향상시켜주는  기능을  한다.

 

* 보조스트림  :  입출력 대상이 되는 파일이나 네트워크를 직접 읽거나 쓸 수 없는 스트림(단순 보조기능만을 수행하는 스트림)

 

 

Buffer를 사용하는 이유

하드디스크의  속도는  매우  느리다.

 

또한  하드디스크  뿐  아니라  키보드, 모니터와  같은  외부장치의  속도는  시간이  많이  걸리는  작업이다.

 

고로  버퍼링없이  키보드가  눌릴  때마다  눌린  정보를  바로  목적지를  이동시켜주는  것보다 

 

중간에  Memory Buffer를  두어서  data를  한데  묶어서  이동시키는  것이  효율적이고  빠르다.

 

그냥  전송하게  되면  CPU와  성능  차이가  많이  나서  비효율적이다.

 

unBuffered  /  Buffered

버퍼를  쓰지  않는  입출력을  unBuffered  I/O라고  한다.

 

read나  write는  운영체제에  의하여  요청  즉시  처리된다만  이것은  매우  비효율적인  방식이다.

 

입출력  요청은  디스크  접근이나  네트워크  접근과  같은  매우  시간이  많이  걸리는  동작을  요구한다.

 

디스크  >  (한 번 읽을 때 끝까지 읽어 놓는다.) > 버퍼(데이터 저장 창고) > (하나씩 꺼내 쓰고 다 떨어지면 다시 디스크에서 읽는다) > 프로그램

 

자바에선  오버헤드를  줄이기  위하여  버퍼링된  스트림(buffered I/O)를  제공한다.

 

버퍼  입력  스트림은  버퍼  메모리  영역에서  데이터를  읽고  버퍼가  비었을  때만  하드디스크나  네트워크에서  읽는다.

 

버퍼  출력  스트림은  데이터를  버퍼에  쓰는데  버퍼가  가득  찼을  경우에만  하드디스크나  네트워크에  쓴다.

 

'버퍼가  없는'  스트림을  '버퍼가  있는'  스트림으로  변경하려면  버퍼  스트림  객체를  생성하면서  '생성자의  인수'로  버퍼가  없는

 

스트림  객체를  전달하면  된다.  간단히  아래  예제와  같다.

 

inputStream =  new BufferedReader(new  FileReader("abc.txt") );

outputStream = new BufferedWriter(new  FileWriter("def.txt") );

기존의  FileReader,  FileWriter를  BufferedReader,  BufferedWriter를  이용하여  버퍼가  있는  스트림으로  변경했다.

 

자바에서  버퍼를  추가하는  4개의  버퍼  스트림이  제공된다.

  바이트 스트림 문자 스트림
입력 BufferedInputStream BufferedReader
출력 BufferedOutputStream BufferedWriter

 

BufferedReader

일정량  사이즈로  '한 번에' 읽어온  후  버퍼에  보관.  사용자가  요구할  때  버퍼에서  읽어오게  한다.

Scanner의  버퍼  사이즈는  1024  chars  VS  bufferedReader의  버퍼  사이즈는  8192  chars

  • enter만  경계로  인식
  • 받은  데이터가  String으로  고정.  입력받은  데이터를  타입  변환/파싱  해야  함
  • 많은  데이터를  입력받을  경우  효율  좋음

Buffer

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

1.  BufferReader는  오직  String으로만  값을  받지만,  큰  데이터에  있어서는  Scanner보다  효율이  좋다.

2.  값을  오직  String으로  받기  때문에  String이  아닌  다른 값으로  값을  받을  때에는  형변환이  필요하다.

3.  br.readLine( )으로  값을  받고,  형변환을  해주어야  한다는  사실만  상기하면  기억하기  쉽다.

4.  public static void main(String args[]) throws  IOException  BufferedReader  는  예외처리를  꼭  해주어야  한다.

try  &  catch  문을  활용해서  하기도  하지만,  대부분  IOException을  활용해서  한다.

 

 

BufferedWriter  bw =  new BufferedWriter(new  OutputStreamWriter(System.out) );   //선언
String  s  =  "abcdef";  // 출력할 문자열
bw.write(s + "\n");  // 출력

bw.flush( );   // 남아있는 데이터를 모두 출력시킴
bw.close( );  // 스트림을 닫음

버퍼  스트림은  버퍼를  가지고  있기에  버퍼가  다  채워지지  않았어도  버퍼를  쓰는  경우가  있기  때문에 

bw.flush( )  메소드를  이용하여  버퍼에  있는  데이터를  강제로  출력장치로  보낼  수  있다.

 

BufferedWirter의 경우  사용을  마치면  close( )를  하여  자원할당을  해제하여야  한다.

또한 println의 ln처럼 자동개행이 없기 때문에 개행을 하려면 \n을 통해 해주어야 한다.

 

 


 

채널 (Channel)

채널은  스트림의  향상된  버전으로  단방향이  아닌  양방향으로  접근이  가능하다. 

또한  스트림과  다르게  채널은  비동기적인  특징으로  닫고  중단할  수  있다.

 

채널의 종류
종류 설명
FileChannel 파일 입출력 채널
Pipe.SinkChannel 파이프에 데이터를 출력하는 채널
Pipe.SourceChannel 파이프로 부터 데이터를 입력받는 채널
ServerSocketChannel 클라이언트의 연결 요청을 처리하는 서버 소켓 채널
SocketChannel 소켓과 연결된 채널
DatagramChannel DatagramSocket과 연결된 채널

 

NIO의 Channel 클래스

NIO(New I/O)의  Channel은  Buffer에  있는  내용을  다른  어디론가  보내거나  다른  어딘가에  있는  내용을  Buffer로  읽어들이기  위해  사용된다. 예를 들면  네트워크  프로그래밍을  할  때  ByteBuffer로  Packet을  작성  후  Socket으로  흘려 보낼 때나,Socket을  통해  들어온  내용을  BtyeBuffer에  저장할  때 Channel을  사용한다.이런 Channel을  ServerSocketChannel  이나  Socket Channel 이라고  한다.

 

FileChannel

ServerSocketChannel이나 SocketChannel의 경우 Selector를 이용하여 Non-Blocking 하게 입출력을 수행 할 수 있지만,FileChannel은 Blocking만  가능하다. 이 점은 운영체제나  시스템 마다  File 입출력시 Non-Blocking을  지원해주지 않는 시스템이 있어 그런 것이라고 한다.

 

FileChannel은  File에  있는  내용을  ByteBuffer로  불러오거나  ByteBuffer에  있는  내용을  File에 쓰는 역할을 한다.

이에 대해 알아보기 전에 알아야 할것은 

  • Channel은 직접 인스턴스화 할 수가 없다.
  • InputStream/OutputStream에서 만들어야 한다.

FileChannel을 얻는 방법은 다음과 같다.

FileInputStream fis = new FileInputStream("test.txt");
FileChannel cin = fis.getChannel();

FileOutputStream fos = new FileOutputStream("test.txt");
FileChannel cout = fos.getChannel();

RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");
FileChannel cio = raf.getChannel();

위에서 보는 것과 같이 InputStream이나 OutputStream을 통해 FileChannel을 얻을 수 있다.

단순히 Input,OutputStream 외에도 RandomAccessFile과 같이 FileHandling하는 객체에도 getChannel()메소드만 있다면 FileChannel을 얻을 수 있다.

 

FileInputStream이나 FileOutputStream의 경우, 순차적으로 읽을 땐 적당하지만,

파일 내용을 이리저리 탐색하면서 처리할때는 매우 불편하고 효율적이지 않을 것이다.

따라서 이런 경우엔 파일 임의의 지점에서 읽거나 쓸 수 있는 RandomAccessFile 클래스를 사용하자.

 

FileChannel에서 읽고 쓰는 방법은 다음과 같다.

FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("Output.txt");

ByteBuffer buf = ByteBuffer.allocateDirect(10);
FileChannel cin = fis.getChannel();

FileChannel cout = fos.getChannel();

cin.read(buf);  //  channel에서 읽어 buf에 저장
buf.flip();
cout.write(buf);  //  buf의 내용을 channel에 저장

read함수를 쓰면, position위치에서부터 limit 위치까지의 내용을 FileInputStream의 내용으로 채워진다.

write함수를 쓰면, position위치에서부터 limit 위치까지의 내용을 FileOutputStream에 출력한다.

 


InputStream과 OutputStream 그리고 Byte와 Character 스트림

Java  I/O  패키지  주요  클래스  및  설명

Java.io. 패키지의 주요 클래스 설명
File 파일 시스템의 파일 정보를 얻기 위한 클래스
Console 콘솔로부터 문자를 입출력하기 위한 클래스
InputStream / OutputStream 바이트 단위 입출력을 위한 최상위 입출력 스트림 클래스
FileInputStream / FileOutputStream 바이트 단위 입출력을 위한 하위 스트림 클래스 
DataInputStream / DataOutputStream
ObjectInputStream / ObjectOutputStream
PrintStream
BufferedInputStream / BufferedOutputStream
Reader / Writer 문자 단위 입출력을 위한 최상위 입출력 스트림 클래스
FileReader / FileWriter 문자 단위 입출력을 위한 하위 스트림 클래스
InputStreamReader / OutputStreamWriter
PrinterWriter
BufferedReader / BufferedWriter

 

바이트 단위 스트림(Byte Stream)

InputStream,  OutputStream  둘다  바이트  기반  입출력  스트림의  최상위  클래스로  추상 클래스이다.

 

관련된  모든  바이트  기반  입출력  스트림은 이 클래스를  상속받아서 만들어진다.

 

바이트단위로 데이터를  전송하며  입출력  대상에  따라  제공하는  클래스가  다르다.

 

그림,  멀티미디어,  문자  등  모든  종류의  데이터를  주고  받을  수가  있다.

출처 : https://velog.io/@ljs0429777/Java-IO


InputStream
OutputStream
입출력을 위한 바이트 스트림의 최상위 추상 클래스
FileInputStream
FileOutputStream
파일 입출력을 위한 바이트 스트림 클래스
DataInputStream
DataOutputStream
자바 기본형(primitive) 데이터를 입출력 하기 위한 클래스
BufferedInputStream
BufferedOutputStream
입출력 스트림에 버퍼링 기능을 추가한 클래스
PrintStream System.out을 통해 콘솔로 출력하기 위한 클래스

InputStream의 메소드

메소드 설명
int available() 현재 읽을 수 있는 바이트 수를 반환한다
void close() 현재 열려있는 InputStream을 닫는다
void mark(int readlimit) InputStream에서 현재의 위치를 표시해준다
boolean markSupported() 해당 InputStream에서 mark()로 지정된 지점이 있는지에 대한 여부를 확인한다
abstract int read() InputStream에서 한 바이트를 읽어서 int값으로 반환한다
int read(byte[] b) byte[] b 만큼의 데이터를 읽어서 b에 저장하고 읽은 바이트 수를 반환한다
int read(byte[] b, int off, int len) len만큼 읽어서 byte[] b의 off위치에 저장하고 읽은 바이트 수를 반환한다
void reset() mark()를 마지막으로 호출한 위치로 이동
long skip(long n) InputStream에서 n바이트만큼 데이터를 스킵하고 바이트 수를 반환한다

OutputStream의 메소드

메소드 설명
void close() OutputStream을 닫는다
void flush() 버퍼에 남아있는 출력 스트림을 출력한다
void write(byte[] b) 버퍼의 내용을 출력한다
void write(byte[] b, int off, int len) b배열 안에 있는 시작 off부터 len만큼 출력한다
abstract void write(int b) 정수 b의 하위 1바이트를 출력한다

문자 단위 스트림(Character Stream)

 

Reader,  Writer  둘 다  문자  데이터  기반  입출력의  최상위  클래스이다. 

 

관련된  모든  텍스트  기반  입출력은  이  클래스를  상속받아서  만들어  진다.

 

문자 데이터를  입출력할  때  사용하는  문자 기반의  스트림이다.

 

오로지  문자  데이터를  주고  받기  위해  특화되어있다.

 

문자  기반  스트림은  기존의  바이트  기반  스트림에서 

InputStream 을 Reader로, OutputStream을 Writer로  변경하면  사용할 수 있다.

 

출처 : https://velog.io/@ljs0429777/Java-IO


Reader
Writer
입출력을 위한 문자 스트림의 최상위 추상 클래스
FileReader
FileWriter
파일 입출력을 위한 문자 스트림 클래스
BufferedReader
BufferedWriter
입출력 스트림에 버퍼링 기능을 추가해주는 스트림
PrintWriter 출력을 위한 동작을 지원하는 문자 스트림
InputStreamReader
OutputStreamWriter
바이트와 문자 변환을 위한 입출력 스트림

보조 스트림

스트림의  기능을  보완하기  위해  나온  녀석이다. (입출력  성능  속도  향상,  데이터  포멧  등)

 

1.  실제  데이터를  주고  받지  않는다.

2.  데이터를  주고  받을  수  없기  때문에  먼저  스트림을  생성한  후  사용해야  한다.

 

 아래의  코드는  보조  스트림  활용  예시이다.

DataInputStream  dataInputStream  =  new  DataInputStream(  new  FileInputStream("test.txt") );

 

표준 스트림 (System.in, System.out, System.err)

  • Java에서 콘솔과 같은 표준 입출력 장치를 위해 System이라는 표준 입출력 클래스를 정의하고 있다.
  • java.lang 패키지에 포함되어 있는 System 클래스는 표준 입출력을 위해 다음과 같은 클래스 변수를 제공한다.
System 클래스
클래스 변수 입출력 스트림 설명
System.in InputStream 표준 입력 스트림 / 키보드로 데이터 입력 받음
System.out PrintStream 표준 출력 스트림 / 모니터로 데이터 출력 시킴 - println() / print() / printf()
System.err PrintStream 표준 에러 출력 스트림

 

표준 출력 스트림
  • print()
  • println()
  • printf()
public class Example01 {
    public static void main(String[] args) {
        System.out.print("print");
        System.out.println("println");
        System.out.print("print와 println의 차이점입니다.");
    }
}
printprintln
print와 println의 차이점입니다.
Process finished with exit code 0
public class Example01 {
    public static void main(String[] args) {
        System.out.printf("%d %d %d %d %d", 1,2,3,4,5);
    }
}
1 2 3 4 5
Process finished with exit code 0

 

표준 입력 스트림

System.in은 콘솔로부터의 입력을 받는 표준 스트림을 가리키는 상수이다.

 

InputStream으로 byte 단위로 입력 받을 수 있다.

 

아래와 같이 System.in.read();는 int 형으로 입력 받는다.

import java.io.*;
public class Example01 {
    public static void main(String[] args) throws IOException{
        System.out.print("입력 : ");
        int ch = System.in.read();
        System.out.println("입력한 문자의 아스키 코드 값 : " + ch);
    }
}
입력 : A
입력한 문자의 아스키 코드 값 : 65

Process finished with exit code 0
import java.io.*;
public class Example01 {
    public static void main(String[] args) throws IOException{
        System.out.print("입력 : ");
        int ch = System.in.read();
        System.out.println("출력 : " + (char)ch);
    }
}
입력 : A
출력 : A

Process finished with exit code 0

여기서 한글을 입력한다면 아래와 같은 결과를 볼 수 있다.

입력 : 하
출력 : í

Process finished with exit code 0

-  한글은 유니코드로 2byte 단위이기 때문에 InputStream 으로는 1byte 밖에 읽지 못해 원하지 않는 값이 출력 된다.

 

InputStream 은 ByteStream 으로 한글을 입력받는데 있어 어려움이 있다.

 

이 경우에는 CharacterStream 인 InputStreamReader를 활용할 수 있다.

import java.io.*;
public class Example01 {
    public static void main(String[] args) throws IOException{
        System.out.print("입력 : ");
        InputStreamReader inputStreamReader = new InputStreamReader(System.in);
        int ch = inputStreamReader.read();
        System.out.println("출력 : " + (char)ch);
    }
}
입력 : 하
출력 : 하

Process finished with exit code 0

만약 2byte가 넘는 데이터를 읽고 싶다면 BufferedReader 를 활용 하자.

import java.io.*;
public class Example01 {
    public static void main(String[] args) throws IOException{
        System.out.print("입력 : ");
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String ch = bufferedReader.readLine();
        System.out.println("출력 : " + ch);
    }
}
입력 : 하이하이 안녕하세요
출력 : 하이하이 안녕하세요

Process finished with exit code 0

혹은 Scanner 클래스를 사용할 수도 있다.

하지만, BufferedReader가 실무에서 더 중요하고 많이 쓰인다고 한다.

 

표준 에러 출력 스트림

표준 에러 출력 장치를 가리키는 상수이다.


파일 읽고 쓰기

File Write

파일을 쓸 때는 아래의 객체를 사용한다.

FileWriter, BufferedWriter

 

FileWriter 객체와 BufferedWrite 객체를 생성해준다.

여기서 FileWriter 객체를 생성할 때 생성자에 true를 주면 파일 이어서 쓰기가 가능하다.

default는 false (false로 하면 파일을 새로 쓰게 됨)

실행을 해보면, 파일 생성 후 파일 생성여부를 체크하도록 구현해두어 

아래처럼 콘솔창에 "coco3o.txt 파일이 있습니다" 문자열이 출력된다.

coco3o.txt 파일이 해당 자바 프로젝트 경로에 파일이 생성된다.


File Read

 

파일을 읽을때는 아래 객체를 사용한다.

FileReader, BufferedReader

 

FileReader의 생성자에는 파일경로를 작성한다.

지금은 자바프로젝트 경로에 있으므로 파일명만 작성해도 읽어낼 수 있으나, 다른 경로에 있을 경우 상대경로를 입력해주면 된다.

아래와 같이 coco3o.txt 파일을 정상적으로 읽어 내었다.

파일에 문자열이 2번 작성된 것은 위의 File Writer를 같이 실행하였기 때문


 

references :

sujl95.tistory.com/71

m.blog.naver.com/PostView.nhn?blogId=lobolook&logNo=220091808317&proxyReferer=https:%2F%2Fwww.google.com%2F

blog.naver.com/swoh1227/222244309304

eincs.com/2009/08/java-nio-bytebuffer-channel/

develop-im.tistory.com/54

velog.io/@jaden_94/13%EC%A3%BC%EC%B0%A8-%ED%95%AD%ED%95%B4%EC%9D%BC%EC%A7%80-IO

vmpo.tistory.com/63

 

 

 

 

반응형

블로그의 정보

슬기로운 개발생활

coco3o

활동하기