Ezcho

[java] java IO - Byte단위 입출력 본문

Java

[java] java IO - Byte단위 입출력

Ezcho 2022. 11. 16. 11:35

Java I/O

java에서의 IO는 input Data와 Output Data로 나뉜다.

IO 는 입출력에 대한 인터페이스 이다.

출처: 프로그래머스 자바중급 강의

 

Byte 와 char

자바에서는  입출력을 Byte와 Char 형태로 할 수 있다.

 


Byte단위 입출력

Byte단위 입출력 클래스는 InputStream과 OutputStream이라는 추상클래스를 상속받아 만들어집니다.

크기: 1Byte

하위 추상 클래스:  InputStream과 OutputStream


1. InputStream

- 바이트 단위 입력 스트림의 최상위 추상 클래스

- 많은 추상 메서드가 선언되어 있고 이를 하위 스트림이 상속받아 구현함

 

하위 스트림

FileInputStream: 파일에서 Byte단위로 읽기

ByteArrayInputStream: 배열에서 Byte단위로 읽기

FilterInputStream:

 

2. OutputStream

- 바이트 단위 입력 스트림의 최상위 추상 클래스

- 많은 추상 메서드가 선언되어 있고 이를 하위 스트림이 상속받아 구현함

 

하위 스트림

FileOutputStream: 파일에 Byte단위로 쓰기

ByteArrayOutputStream: 배열에 Byte단위로 쓰기

FilterInputStream:


3. FileInputStream, FileOutputStream

파일에서 바이트 단위로 읽어오고, 파일에 바이트 단위로 쓰기

아래와 같은 기본구조를 갖추어야 한다.(자세한 읽기와 쓰기는 아래에서)

FileInputStream fis = null;
FileOutputStrem fos = null;
try{
	fis = new FileInputStream(“src/javaIO/ByteExam1.java”);
	fos = new FileOutputStream(“byte.txt”);
}
catch(Exception e){
	e.printStackTrace();

finally{
	try{
    	fis.close();
        fos.close();
    }
    catch(IOException e){
    	e.printStackTrace();
    }
}

예외처리는 필수되는데, 대표적인 예외로는

-파일존재유무, 파일이 열리지 않을 수 있다. 등의 문제가 존재한다.


4. DataInputStream과 DataOutputStream

위에서 사용햇던 FileInputStream과 FileOutputStream은 직접적으로 파일을 읽고 쓰는 추상 클래스이다.

하지만 이 클래스들은 바이트형식의 문자를 읽고, 쓸 수있기에 다양한 자료형을 읽을 수 없다는 단점이 존재한다.

즉, 우리가 Char이나 double형을 읽고싶은데, 이들을 1Byte단위로 표현되었을 때 읽는게 불가능하다.

그래서 우리가 이런 Byte단위의 데이터를 변환하기 위해서 위 클래스들을 사용한다고 생각해보자.

 

다시 정리를하면 

FileInputStream=>FIS, FileOutputStream=>FOS, DataInputStream=>DIS, DataOutputStream=>DOS 라고 하겠다.

출처: 프로그래머스 자바중급 강의

그림을 다시보자 FIS와 FOS는 장식대상 클래스이다. DIS와 DOS는 장식하는 클래스이다. 즉 둘은 상속관계이며 DIS와 DOS는 독립적일수는 없다는 것이다.  바이트단위 계산하는 FIS, FOS를 DIS 와 DOS는 우리가 사용하는 기본형 데이터타입으로 변환관계에 있다고 생각하는것이 좋다.

 

DataInputStream 과 DataOutputStream은 스스로 파일을 읽고 쓸 수 없다는 사실을 기억하자.

코드를 보자.

try{
            DataOutputStream dos = new DataOutputStream(new FileOutputStream("src/note.java"));
            DataInputStream dis = new DataInputStream(new FileInputStream("abc.txt"));
        }
        catch(Exception e){
            e.printStackTrace();
        }

DataInputStream 과 DataOutputStream은FileInputStream과 FileOutputStream을 인자로 받는다. 자세한 메서드는 다음글에서 알아보자.

 


5. DataInputStream, DataOutputStream, FileInputStream 그리고 FileOutputStream의 메서드 활용

앞에서 4개의 클래스에 대한 관계와 선언법을 알아보았다. 여기서는 이제 어떻게 활용을 하고 자세한 동작법에 대해 설명하겠다.

1.  FileInputStream 그리고 FileOutputStream 을 활용한 파일 복사

우선 FIS와 FOS를 사용해  input.txt의 데이터를 output.txt로 복사해보자.

선언과 예외처리에 대해서는 위에서 설명했으니 넘어가겠다.

코드를 보자,

    public class ByteIOExam1 {
        public static void main(String[] args){     
            FileInputStream fis = null; 
            FileOutputStream fos = null;        
            try {
                fis = new FileInputStream("input.txt");
                fos = new FileOutputStream("output.txt");

                int readData = -1; 
                while((readData = fis.read())!= -1){
                    fos.write(readData);
                }           
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

 

여기서 사용되는 FIS와 FOS의 메서드는 fis.read()와 fis.write()이다.

1. readData라는 변수를 선언한다. Byte단위로 읽지만,  fis.read();는 int를 반환한다. 

2. 그래서 readData를 int로 선언하여 그 값을 받는것이다.

3. 그리고 fis.read()메서드는 맨 마지막줄에서 -1을 리턴한다.

3. 즉, while문에서 fis.read()값을 readData로 정의하고, 그 값이 -1이 아닐때 반복한다고 되어있다.

4. 마지막 줄이 나올때까지 값을 읽는 while문인 것이다.

5. 이후 받은 readData값을 fos.write()메서드를 사용해 output.txt에 한바이트씩 쓰는것이다.

 

그럼 여기서 한가지 의문점이 생긴다

 

input.txt

더보기

1
2

3

input.txt가 위와 같을때 while문을 수정해서 한번 출력해보자.

            while((readData = fis.read())!= -1){
                fos.write(readData);
                System.out.println(readData);
            }

console

49

10
50
10
51

아래와 같이 나온다. 왜 이렇게 된걸까?

4Byte... (0001)

4Byte... (0010)

4Byte... (0011)

이고

readData값은. binary로

3Byte + 0000 0000 0000 0000 0000 0000 0000 1010
3Byte + 0000 0000 0000 0000 0000 0000 0011 0010
3Byte + 0000 0000 0000 0000 0000 0000 0000 1010
3Byte + 0000 0000 0000 0000 0000 0000 0011 0011

 

모르겠다. 일단 넘어가자

 

 

2.  DataInputStream, DataOutputStream 

 

DIS와 DOS의 대표적인 메서드는 read()와 write()가 존재한다.

FIS와 FOS로부터 파일을 읽어오고, 그 값들을 DataInputStream, DataOutputStream  이 형에 알맞게 표현해 주는 것이다.

DataInputStream dis = new DataInputStream(new FileInputStream("data.dat"));
int i = dis.readInt();          
boolean b = dis.readBoolean();          
double d = dis.readDouble();

DataOutputStream dos = new DataOutputStream(new FileOutputStream(“abc.txt”)
dos.writeInt(100);
dos.writeBoolean(true);
dos.writeDouble(1.324);

이런식으로 표현이 가능하다.

 

 

 

3. Try-with-resources를 통한 Resource의 자동 종료

정의: 자원을 자동으로 종료시켜준다.

우리는 이전에 file에서의 Byte단위 IO를 위해서 finally 문에서 사용했던 fis, fos 객체를 .close()를 통해 닫아주었다. 하지만 try-with-resource를 통해서 이 finally 문을 생략할 수 있다.

우선 코드부터 보자

try(DataOutputStream dos = new DataOutputStream(new FileOutputStream(“abc.txt”)){
	dos.writeInt(100);
	dos.writeBoolean(true);
	dos.writeDouble(1.324);
}
catch(Exception e){
	e.printStackTrace();
}

아래와 같이 try문 내부에 

DataOutputStream dos = new DataOutputStream(new FileOutputStream(“abc.txt”) 를 선언해주었다. 이를 통해 선언한 변수들은 try문 안에서 사용할 수 있다.

이후 코드가 try문을 벗어나면 try-with-resources가 try안에서 선언된 객체의 close()메소드 호출을 통해 자동으로 close()가 되는것이다. 즉 dos.close()를 해줄 필요가 없고 finally문을 생략하여 코드가 간결화 되는것이다.

 

단 모든 객체의 close()를 호출 해 주지 않기 때문에 사용할때 확인을 할 필요가 있다.

 

4. 512바이트 한번에읽기

 public class ByteIOExam1 {
        public static void main(String[] args){     

            long startTime = System.currentTimeMillis();    	//시작시간 체크    
            FileInputStream fis = null; 
            FileOutputStream fos = null;        
            try {
                fis = new FileInputStream("input.txt");
                fos = new FileOutputStream("output.txt");

                int readCount = -1; 			//readCount생성
                byte[] buffer = new byte[512]; //버퍼생성
                while((readCount = fis.read(buffer))!= -1){ //버퍼에 읽기
                    fos.write(buffer,0,readCount); //버퍼에 해당하는것을 readCount
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        long endTime = System.currentTimeMillis();				//종료시간 체크
        System.out.println(endTime-startTime); 					//시간계산
        }
    }

위의 문에서는 기존과 다르게 512Byte짜리 buffer를 생성해주었다.

그리고, 해당 버퍼에다 읽고 버퍼의 값들을 다시 쓰고 있다.

마지막으로 시간을 체크하고있다.

결과: 1

시간을 출력하면 위에서 했던 FOS, FIS 의 파일복사문보다 상당히 짧은 시간이 소요되는것을 알 수 있다.

위의 복사문은 결과: 4 이다.

그 이유가 무엇일까?

출처: 프로그래머스 자바 중급 강의

위의 그림을 따른다. buffer의 경우 크기가 512바이트이다.

만약 처음 방법대로 1byte씩 읽을경우 512바이트를 읽고 1바이트를 전달, 511바이트를 버린다.

하지만 512바이트짜리 buffer를 할당할 경우 512바이트를 한번에 읽어 시간이 훨씬 단축되는 것이다.

그래서 실제로 while문을 아래와 같이 바꾸고 결과를 출력하면

while((readCount = fis.read(buffer))!= -1){ //버퍼에 읽기
    fos.write(buffer,0,readCount);//버퍼에 해당하는것을 readCount
    System.out.println(readCount);

console

6
1

 

횟수가 현저히 줄어든것을 알 수 있다.


6.결과

Byte단위 입출력에 대해서 알아보았다.

Byte이기때문에 FIS, FOS로 우리가 직관적으로 값을 추정하기 힘들기 때문에 DIS, DOS를 통해 읽고 쓸 수 있었다.

상황에 따라 어떤 클래스를 잘 사용하는지 알고 메모리에서의 값동작들을 이해할필요가 있다고 느꼇다.

 

'Java' 카테고리의 다른 글

[Java] Thread와 상태제어  (1) 2022.11.25
[Java] Thread  (0) 2022.11.25
[java] 어노테이션  (0) 2022.11.09
[java] Date, Calendar클래스, Time 패키지  (0) 2022.11.09
[java] java.util패키지  (0) 2022.11.02
Comments