Backend/Java

Java I/O (Input / Output)

가은파파 2021. 2. 21. 00:08

목표

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

학습할 것 (필수)

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

 

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

1. 스트림(Stream) 기반의 I/O

 

Byte Streams : 프로그램은 바이트 스트림을 8비트 I/O를 수행하기 위해 사용한다. 모든 바이트 스트림 클래스는 InputStream과 OutputStream의 자손 클래스이다. 많은 바이트 스트림 클래스가 있다. 

 

대표적으로 FileInputStream과 FileOutputStream이 있다. 다른 바이트 스트림 클래스도 같은 방법으로 사용할 수 있다.

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

public class CopyBytes {
    public static void main(String[] args) throws IOException {

        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("xanadu.txt");
            out = new FileOutputStream("outagain.txt");
            int c;

            while ((c = in.read()) != -1) {
                out.write(c);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }
}

위의 과정은 CopyBytes가 단순한 아래와 같은 과정의 inputstream의 read와 outputstream의 write의 Loop를 돈다.

단순한 byte stream의 input/output

*바이트 스트림은 사용이 끝났으면, 반드시 스트림을 Close()해야한다.

이를 통해 리소스 누수를 막을 수 있다. 생길 수 있는 에러 중 하나는 CopyBytes의 객체의 파일을 열 수 없는 경우이다. 해당하는 스트림 변수를 null값에서 변경할 수 없게 된다. 그러므로 반드시 close() 호출 전에 각 클래스에 스트림 변수를 포함시켜야 한다.

 

*바이트 스트림을 사용하면 안되는 경우

위 같은 경우는 character 데이터가 포함되어 있다. 이런 경우 제일 좋은 I/O 방법은 Character streams을 이용하는 것이다. 바이트 스트림은 오직 기본형 타입에 적용해야 한다.

 

Character Streams 

: Java 플랫폼은 character 변수는 Unicode 규칙을 사용한다. character stream I/O는 자동으로 로컬 문자set에서 내부적 포맷으로 해석된다. 바이트 스트림을 사용하는 대신 캐릭터 스트림을 사용하면 로컬문자 집합에 자동으로 적응하고 국제화 할 준비가 되어 있습니다.

 

국제화시키는 것이 우선순위는 아니지만 간단하게 character stream 클래스는 character-set 이슈 없이 사용할 수 있게 합니다.

 

Reader와 Writer의 상위클래스를 활용해 FileReader와 FileWriter 클래스를 활용해 아래와 같은 입출력이 가능하다.

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyCharacters {
    public static void main(String[] args) throws IOException {

        FileReader inputStream = null;
        FileWriter outputStream = null;

        try {
            inputStream = new FileReader("xanadu.txt");
            outputStream = new FileWriter("characteroutput.txt");

            int c;
            while ((c = inputStream.read()) != -1) {
                outputStream.write(c);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }
}

# Byte와 Character 스트림

 

바이트 스트림과 가장 큰 차이는 하지만 CopyCharacters는 the int variable를 character value 를16 bits 동안 가질 수 있고,CopyBytes는 byte value 를 8 bits를 가질 수 있다.

 

character stream도 물리적 I/O를 위해 바이트 스트림을 사용한다. 그럴 시에는 FileReader대신 FileInputStream을, FileWriter대신에 FileOutputStream을 사용한다.

 

InputStreamReader와 OutputStreamWriter

byte에서 character를 오고갈 수 있게 하는 '다리'역할을 하는 streams이 2가지 있다. 이 2가지 스트림은 미리 character-set으로 패키징이 되지 않을 경우 사용하면 좋다. socket 네트워크 기능을 활용해 byte stream에서 character stream으로 만들어주는 기능을 적용할 수 있다.

 

readLine()를 호출하여 1줄씩 I/O기능을 구현할 수 있다.

 

2.버퍼 (Buffer)

지금 까지 배운 unbuffered I/O는 read, write요청이 기본 OS에서 직접 처리된다. 이 경우는 비효율적이다. 직접 디스크 엑세스, 네트워크 행위 등 기타 operation 등이 상대적으로 비용이 많이드는 요청이기 때문이다.

 

이런 overhead를 줄이기 위해서 자바플랫폼은 buffered I/O stream을 사용한다. Buffered input stream은 데이터를 buffer로 알려진 메모리로 부터 읽는다. OS에서 io시스템 콜하는 횟수 자체가 줄어들어서 효율이 높아진다고 볼 수 있다.(버퍼라는 메모리에 쌓아놨다가 한번에 다녀와 io시스템콜 하는 횟수가 줄어든다고 볼 수 있다)

 

 

buffer가 비어 있을때, native input API를 호출하여 읽는다. 마찬가지로 Buffered output stream은 데이터가 full로 차있을 때 native api로 호출하여 write를 한다.

 

아래는 unbuffered I/O스트림을 buffered I/O 스트림으로 convert하는 경우이다. 포장해서 쓴다고 볼 수 있다.

inputStream = new BufferedReader(new FileReader("xanadu.txt"));
outputStream = new BufferedWriter(new FileWriter("characteroutput.txt"));

flushing Buffered Stream 주의

: 종종 중요한 순간에 입력되는 시간이 없이 비어있는 채로 buffer 상태로 write되는 경우가 있다. 이런 경우를 위해 flush()를 써서 출력 스트림을 flush하고 버퍼링된 출력 바이트를 강제로 write합니다. flush호출은 출력 스트림의 구현에 의해 이전에 작성된 바이트가 버퍼링된 경우 해당 바이트가 즉시 목적지까지 기록되어야 한다는 것을 나타내는 것이다.

 

further study : Scanning, Formatting.

 

3. Channel

NIO에서 버퍼기반의 channel의 인터페이스를 활용하면 양방향 통신이 가능해진다.

 

NIO의 버퍼, 채널, 셀렉터, 파일 입출력 예제는 아래 포스팅 참고

<추가예정>

# InputStream과 OutputStream

자바의 기본적인 데이터 입출력은 Java.io 패키지에서 제공함.

InputStream/OutputStream은 바이트단위 입출력을 위한 최상위 입출력 스트림 클래스

출처 : https://www.tutorialspoint.com/java

 

InputStream

 : 바이트 기반 입력 스트림의 최상위 클래스로 추상 클래스이다.

   모든 바이트 기반 입력 스트림은 이 클래스를 상속받아서 만들어짐.

   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

 : 바이트 기반 출력 스트림의 최상위 클래스로 추상클래스이다.

   모든 바이트 기반 출력 스트림 클래스는 이 클래스를 상속받아서 만들어짐.

   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바이트를 출력한다

 

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

JAVA에서는 스크린과 키보드를 통한 입출력 방법인 표준 입출력을 제공한다. 표준 입출력을 제공하는 클래스는 java.lang.System으로 멤버 변수인 in, out, err을 이용해서 표준 입력, 표준 출력, 표준 에러를 제공한다.

 

1. System.in

 

System.in은 InputStream 형태로 지정되어 있다. System 클래스는 자바 버추얼 머신을 구성하고 있는 표준 장치를 뜻하는 클래스이다. 자바 버추얼 머신은 그 자체가 완벽한 하나의 컴퓨터 플랫폼을 가정하고 있기 때문에 독립적으로 동작할 수 있는 구조를 표현하기 위하여 표준 입력과 표준 출력을 스스로의 System 클래스에 등록하여 사용한다.

 

여기에서 주목해야 할 부분은 System.in 변수의 타입이 InputStream 이라는 점이다.

InputStream 클래스는 최상위 클래스이면서 추상 클래스이다. 따라서 InputStream은 객체를 생성할 수 없는 클래스이다.

 

그런데도 System.in은 실제로 객체가 존재하고 있으며 이를 통하여 키보드 입력을 받을 수 있다. 이것은 변수의 타입은 선조 클래스이지만 실제 객체는 후손 객체이다. 자연스러운 형 변환이 지원되기 때문에 가능하다.

 

System.in을 통하여 접근되는 객체는 자바 버추얼 머신이 메모리로 올라오면서 미리 객체를 생성해 놓는, 대표적인 객체이다. 자료형이 InputStream이기 때문에 바이트 단위로만 입출력이 허용된다.

키보드에서 입력하는 자료는 때에 따라서 두 바이트가 합쳐져야 의미를 가지는 경우가 있다. 그래서 System.in을 통하여 읽을 경우에는 영문과 한글의 처리를 분리해서 구성해야 제대로 인식할 수 있다. 

 

2. System.out

 

System.out 변수는 표준 출력 장치 객체를 가리키는 대표적인 출력 변수이다. 자바 언어를 처음 배우자마자 사용하는 문장 중 하나가 System.out.println() 메소드다.

 

System.out은 PrintStream 타입으로 선언되어 있는데 PrintStream은 OutputStream 클래스의 후손 클래스로 Exception을 안전하게 처리한 메소드로만 구성되어 있다. 이런 이유로 System.out 을 이용하여 출력할 때는 try, catch 구문을 작성할 필요가 없다.

 

 

3. System.err

 

System.err 객체는 표준 에러 출력 장치를 의미하는데 일반적으로 System.out과 마찬가지로 모니터로 지정되는 경우가 많다. 일반적인 정상 출력은 System.out으로 나가고, 오류가 발생할 때 알려주어야 할 내용은 System.err로 나간다고 볼 수 있다. 이 변수의 타입도 PrintStream 클래스 타입으로 System.out을 사용하는 방법과 동일하다.

 

- System.in 으로 문자를 1바이트씩 입력받아서 출력한 후, 총 바이트를 출력한다.

- 윈도우에서는 Ctrl-Z 를 누르면 EoF 로 System.in.read()가 -1 로 인식하여 while() 루프를 빠져나온다.

 

- 여기서 한글 입력을 받지 못하는 문제가 있다.

- System.in.read() 는 1바이트씩 받지만 한글은 문자당 2바이트이다. 따라서 위와 같은 방식으로는 한글을 사용할 수 없다.

- 해결방법으로는 1바이트씩 입력받은 값을 배열에 저장한 후 유니코드 계산과정을 통해 한글 문자로 변환해야 한다.

하지만 이러한 작업을 하는것 보다는 Scanner나 InputStream을 사용하는 것이 낫다.

 

# 파일 읽고 쓰기

 

java.nio.file패키지에서 그리고 java.nio.file.attribute에서 전체적인 file I/O와 접근에 관하여 제공한다. 

 

Path- 대부분 오늘날 tree구조로 저장됩니다. 파일은 그 path에 의해 인식된다. - path는 상대적, 절대적 경로가 있다. 상대적 경로는 다른 경로와 combine이 반드시 필요합니다.

Path p1 = Paths.get("/tmp/foo");
Path p2 = Paths.get(args[0]);
Path p3 = Paths.get(URI.create("file:///Users/joe/FileTest.java"));
//The Paths.get method is shorthand for the following code:

Path p4 = FileSystems.getDefault().getPath("/users/sally");
//The following example creates /u/joe/logs/foo.log assuming your home directory is /u/joe, or C:\joe\logs\foo.log if you are on Windows.

Path p5 = Paths.get(System.getProperty("user.home"),"logs", "foo.log");

checking, deleting, copying, moving files

Path file = ...;
boolean isRegularExecutableFile = Files.isRegularFile(file) &
         Files.isReadable(file) & Files.isExecutable(file);
         
//삭제
try {
    Files.delete(path);
} catch (NoSuchFileException x) {
    System.err.format("%s: no such" + " file or directory%n", path);
} catch (DirectoryNotEmptyException x) {
    System.err.format("%s not empty%n", path);
} catch (IOException x) {
    // File permission problems are caught here.
    System.err.println(x);
}

//복사
import static java.nio.file.StandardCopyOption.*;
...
Files.copy(source, target, REPLACE_EXISTING);

//이동
import static java.nio.file.StandardCopyOption.*;
...
Files.move(source, target, REPLACE_EXISTING);

> java.io 패키지를 활용하는 기본적인 방법

 

1. 기본적인 방법

 

1-1.바이트 스트림 방식

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

public class CopyBytes {
    public static void main(String[] args) throws IOException {

        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("xanadu.txt");
            out = new FileOutputStream("outagain.txt");
            int c;

            while ((c = in.read()) != -1) {
                out.write(c);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }
}

 

1-2.character 스트림 방식

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyCharacters {
    public static void main(String[] args) throws IOException {

        FileReader inputStream = null;
        FileWriter outputStream = null;

        try {
            inputStream = new FileReader("xanadu.txt");
            outputStream = new FileWriter("characteroutput.txt");

            int c;
            while ((c = inputStream.read()) != -1) {
                outputStream.write(c);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }
}

 

2. 버퍼를 활용한 방법

inputStream = new BufferedReader(new FileReader("xanadu.txt"));
outputStream = new BufferedWriter(new FileWriter("characteroutput.txt"));

 

 

출처

1. docs.oracle.com/javase/tutorial/essential/io/fileOps.html

2. hyeonstorage.tistory.com/235

 

[JAVA] System 클래스(표준 입출력) : System.in, System.out, System.err

System 클래스(표준 입출력) : System.in, System.out, System.err JAVA에서는 스크린과 키보드를 통한 입출력 방법인 표준 입출력을 제공한다. 표준 입출력을 제공하는 클래스는 java.lang.System으로 멤버 변수..

hyeonstorage.tistory.com

 

'Backend > Java' 카테고리의 다른 글

[Java] 멀티스레드 와 멀티 프로세스  (0) 2021.03.03
Java NIO 알아보기  (0) 2021.03.01
JAVA 상속  (0) 2021.02.20
annotations  (0) 2021.02.06
Enum 자바의 열거형  (0) 2021.01.29