Streams

혜령·2022년 1월 14일
0

Network Programming

목록 보기
1/2

Streams

Network Programming

네트워크 프로그램은 byte를 한 host에서 다른 host로 보내는 것을 말합니다.

여기서 byte의 관점에서만 살펴볼 수 있습니다. 그렇다면, client에서 data를 보내는 것은 어떤 file에 data를 쓰는 것과 동일하게 볼 수 있고, server가 보낸 data를 읽는 것은 file의 data를 읽는 것과 동일하게 볼 수 있습니다. byte입장에서는 대상만 달라질 뿐 같은 동작입니다.

따라서 Java에서는 Stream을 잘 이해해야 network programming을 잘할 수 있을 것입니다.

Stream이란?

Java에서 제공하는 IO operation입니다.

Java는 stream을 통해서 IO가 발생합니다. Stream은 data들의 sequence를 의미랍니다.

Java는 InputStream과 OutputStream을 지원하고, 읽어들일 source에 따라서 다른 종류의 Stream을 사용하게 됩니다. 모두 같은 Stream을 확장하는 것이라 쓰는 것과 읽는 것은 같은 메서드를 사용하게 됩니다.

java의 stream은 일반적으로 synchronous(동기화)됩니다. 다른 말로 blocking 모드라고도 합니다. 즉, 데이터를 읽거나 쓸 때, 해당 동작을 모두 완료하기까지 아래 코드가 block되어 기다리게 되는 것입니다.

반대로 기다리지 않고, 다른 line을 실행할 수 있는 모드는 Nonblocking I/O라고도 부릅니다. 해당 모드는 추후에 알아보도록 하겠습니다.

Output Streams

OutputStream Class

  • 클래스
    • abstract 클래스: 구현되지 않은 클래스, 그대로 생성해서 사용이 불가능
    • write 메서드 존재
  • OutputStream의 모든 subclass가 메서드를 그대로 사용한다.

Method

✏️ write(int b)

  • integer을 인자로 받아서 output stream에 unsigned byte [0, 255]로 쓰는 함수
  • [0, 255]이므로 mod 256을 해서 쓰여지게 된다.
  • abstract으로 선언되어 있으므로 모든 subclass가 implement한다.
  • println함수와 비교
    • write함수는 int를 받아서 아스키코드로 출력한다. (문자 출력)
    • println함수는 int를 받아서 그대로 int로 출력한다. (숫자 출력)

✏️ write(byte[] data)

  • 일정 수 의 byte를 누적한 다음 한번에 쓰도록 하는 함수이다.
  • 하나의 byte씩 쓰는 것은 overhead가 엄청나기 때문에 그것을 개선한 것이다.

✏️ flush()

  • byte를 모아서 버퍼에 쓰이는 것이 싫고 바로 쓰이게 하고 싶은 경우 사용하는 함수이다.
  • write는 바로 쓰여지는 것이 아니라 모아서 쓰지게 된다.
  • 어떤 함수는 auto-flush로 flush가 필요하지 않은 것도 있다.

✏️ close()

  • stream관련된 리소스를 release하게 된다.
  • close는 IOException을 throw하기 때문에 try-catch구문을 활용한다.

Polymorphism(다형성)

다형성이란 하나의 object가 여러 형태를 띄는 것입니다. outputStream에서 다형성을 활용하는 것을 살펴봅시다.

  • 인자로 outputStream을 받는 하나의 메서드를 정의합니다.
  • 그때 그때 원하는 대로 출력 source가 다른 outputStream을 전달합니다.
  • 상위 클래스가 하위 클래스를 감싸게 되어, 같은 메서드를 사용해서 원하는 source에 출력이 되도록 할 수 있습니다.
    • 같은 코드가 받는 것에 따라 화면에 File에 등등 원하는 곳에 출력이 됩니다.
  • 감싸게 되면 field는 superclass의 것을, 메서드는 override된 subclass의 것이 사용됩니다.
public class GenerateCharactersSingleByte {
    public static void main(String[] args) {
        try {
            generateCharacters(System.out);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void generateCharacters(OutputStream out) throws IOException {
        int firstPrintableCharacter = 33;
        int numberOfPrintableCharacters = 94;
        int numberOfCharactersPerLine = 72;
        int start = firstPrintableCharacter;
        int count = 0;
        
        while(count < 1000) {
            for (int i = start; i < start + numberOfCharactersPerLine; i++){
                out.write((byte)((i - firstPrintableCharacter) % numberOfPrintableCharacters + firstPrintableCharacter));
            }
            out.write((byte)'\r');
            out.write((byte)'\n');
            start = ((start + 1) - firstPrintableCharacter) % numberOfPrintableCharacters + firstPrintableCharacter;
            count++;
        }
    }
}

Input Streams

InputStream Class

  • 클래스
    • abstract 클래스: 구현되지 않은 클래스, 그대로 생성해서 사용이 불가능
    • read 메서드 존재
    • outputStream Class와 거의 대칭
  • InputStream의 모든 subclass가 메서드를 그대로 사용한다.
  • 다형성이 적용된다.
    • 문자열을 읽어들이는 메서드를 정의하고, 다양한 InputStream으로 인자를 받도록 합니다.
    • 여러 inputStream을 입력 인자로 주어 다양하게 읽어 들이는 source를 선택해서 사용하도록 할 수 있습니다.

Method

✏️ read()

  • 한 byte를 읽어서 [0, 255]의 int로 return
    • EOF를 indicate하기 위해서 int형
    • EOF는 -1을 return합니다.
  • wait and block: read가 완료될 때까지 아래 구문이 실행되지 않는다.
  • abstract으로 선언되어 있으므로 모든 subclass가 implement한다.
  • read를 통해서 문자를 입력해서 int로 받아들인다.
    • write로 출력을 하면 int형이 아스키코드(문자)로 출력이 된다.
    • print로 출력을 하면 숫자로 출력이 된다.

✏️ read(byte[] input)

  • inputstream으로 부터 읽어서 인자로 들어오는 input에 저장을 하게 된다.
  • 전달 인자의 크기만큼 읽으려고 시도한다. 모두 채워지지 않을 때도 있다.
  • block : 완료될 때까지 아래 구문 실행하지 않는다.
  • 읽을 것이 없으면 -1 return

✏️ available()

  • inputStream으로 부터 얼마나 많은 데이터를 읽어들일 수 있는지 반환한다.

✏️ skip(long n)

  • n바이트 skip하고 실제 스킵한 바이트를 return, EOF면 -1 return
  • 다 읽고 버리는 것보다 skip하는 것이 더 빠르다.

Filter Classes

InputStream과 OutputStream은 원시적은 클래스입니다. Filter Class는 여러 클래스들을 chaining해서 사용합니다. Chaining을 통해서 개발에 필요한 기능을 간단하게 제공합니다.

예를 들어서, 네트워크에서 생각해봅니다. 네트워크는 프로토콜마다 다른 data를 사용합니다. InputStream과 OutputStream은 byte만 있으니까 아스키코드나 Zip을 처리하려면 개발자가 직접 처리를 해야합니다. 이런 상황을 위해서 Java가 Filter Class를 제공해서 도와줍니다.

Filter class는 크게 byte stream과 한글과 같은 하나의 byte로 표현이 안되는 문자를 위한 character stream이 있습니다. character stream은 Reader과 Writer로 나누어집니다.

File Streams

  • File Stream은 Byte Stream의 일종입니다.
  • 파일로부터 데이터를 읽거나 쓰는 것을 도와주는 클래스입니다.
public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("filename1");
            fos = new FileOutputStream("filename2");
            int readCount = 0;
            byte[] buffer = new byte[512];

            while((readCount=fis.read(buffer)) != -1){
                //System.out.write(buffer, 0, readCount);
                fos.write(buffer, 0, readCount);
            }
        } catch (Exception ex){
            System.out.println(ex);
        } finally {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
  • read(byte[])를 사용한 이유
    • read의 문제점은 1byte를 읽을 때, 기본 읽는 단위(512btye)까지 다 읽게 됩니다. 그래서 1000btye를 읽으려면 512btye를 1000번 읽게 됩니다.
    • 한번에 512바이트 읽으라고 하면 2번을 통해서 1000바이트를 읽어서, 속도가 훨씬 빠릅니다.

Buffered Streams

Buffered Streams

어떠한 목적에 의해서 Stream을 묶어서 사용하는 chaining이 적용된 Stream입니다.

BufferedOutStream와 BufferedInputStream 클래스가 있고, 읽고 쓰기 전에 버퍼를 사용하게 됩니다.

public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int bufferSize)
public BufferedOutputStream(OutputStream out)
public BufferedOutputStream(OutputStream out, int bufferSize)

위와 같은 생성자를 가지고 있습니다. 파라미터로 inputStream이나 outputStream인자를 받습니다. 이를 보면 chaining이 적용된 것을 알 수 있고, 다형성도 나타난다는 것을 알 수 있습니다.

BufferedOuputStream Class

  • 클래스
  • write를 호출하면 쓰라고 명령한 데이터가 버퍼에 저장이 됩니다.
    • 저장되는 버퍼는 protected byte array인 buf field
  • 버퍼가 가득 차거나 flush가 호출되면 IO device에 버퍼의 내용을 한번에 내보냅니다.
  • Network에서 유용하게 사용됩니다.
    • 한번에 한 바이트씩 보내도 40바이트 오버헤드 붙으니까 한번에 다 보내는 것이 훨씬 효율적입니다.

BufferedInputStream Class

  • 클래스
  • read는 버퍼로부터 데이터를 읽게 됩니다.
    • 저장되는 버퍼는 protected byte array인 buf field
  • buf는 비어 있으면 source로부터 데이터를 읽습니다. 읽을 때는 가능한 많이 읽어들입니다.
  • 성능 향상
    • 1byte 읽으라 해도 더 많이 읽기 때문에, 1byte읽는 시간과 수백 byte 읽는 시간은 비슷
    • 따라서 한번에 읽는 것이 성능이 향상됩니다.
FileInputStream fis = null;
FileOutputStream fos = null;
try {
		fis = new FileInputStream(args[0]);
		fos = new FileOutputStream(args[1]);
		
		BufferedInputStream bis = new BufferedInputStream(fis);
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		            
		int readCount = 0;
		byte[] buffer = new byte[512];
		            
		while((readCount=bis.read(buffer)) !=-1){
				bos.write(buffer, 0, readCount);
		}
		bis.close();
		bos.close();
}
  • 동작 방식 정리
    • memory안에 buf라는 버퍼를 가지고 File(지정한 IO device)에서 data를 가능한 많이 읽어들여서 저장을 합니다.
    • read(buffer) 메서드가 실행되면 buf에 있는 data를 buffer로 옮겨오게 되는 것입니다.
    • buf는 비면 다시 IO device에서 data를 읽어옵니다.

Data Streams

Data Streams

  • DataInputStream과 DataOutputStream 클래스가 존재합니다.
  • java의 data type(int, float, double, boolean, short, byte)와 string을 binary format으로 읽거나 쓰는 메서드를 제공합니다.
  • 입력인자는 stream입니다. 이 클래스도 chaining되었다는 것을 알 수 있습니다.
public DataInputStream(InputStream in)
public DataOutputStream(OutputStream out)

DataOutputStream Class

  • 클래스
  • 여러 write메서드는 주어진 입력인자로 들어온 것을 주어진 형식에 맞게 쓰게 됩니다.
  • 모든 data는 big-endian format으로 쓰여집니다.
    • big-endian format:가장 낮은 주소에 가장 높은 byte가 들어간다.
  • char는 unsigned 2byte로 쓰여집니다.

DataInputStream Class

  • 클래스
  • 여러 read메서드는 주어진 타입으로 읽어옵니다.
  • readFully 메서드 : 한번에 여러개를 받아올 때 사용합니다. byte배열 사이즈만큼 읽게 됩니다.

Character Streams

Motivation

기존의 inputStream과 outputStream은 byte 단위로 읽고 쓰게 됩니다. 그러면 서로 다른 os는 다른 encoding방식을 사용할 수도 있기 때문에 문제가 생길 수 있습니다. 한 os에서 쓴 것을 다른 os에서 읽는 것이 불가능할 수 있습니다.

따라서 character stream을 제공하는 것입니다. java는 내부적으로 Character을 Unicode로 저장합니다. Reader와 Writer는 자동으로 local character set으로 번역을 해줍니다.

Writer

  • OutputStream과 같은 기능
  • abstract class - 직접 사용 불가능
  • write메서드 사용

OutputStreamWriter

  • java 프로그램으로부터 char를 받아서 주어진 인코딩에 따라 byte로 변환하여 스트림 씁니다.
  • 입력인자: OutputStream, encoding format(주어지지 않으면 default)
  • Getter 메서드로 인코딩 확인

Reader

  • InputStream과 같은 기능
  • abstract class - 직접 사용 불가능
  • read메서드 사용

InputStreamReader

  • inputstream으로 부터 바이트를 읽어서 다시 character로 변환해서 반환합니다.
  • 입력인자 InputStream, encoding format(주어지지 않으면 default)

예제코드

파일로부터 읽어와서 화면에 출력하는 예제입니다.

FileReader와 FileWriter 클래스도 존재하지만 항상 디폴트 인코딩을 사용하므로 아래 예제와 같이 하면 같은 동작을 수행할 수 있습니다.

public static void main(String[] args) {
		FileInputStream fis = null;
		InputStreamReader isr = null;
		OutputStreamWriter osw = null;

		try {
				fis = new FileInputStream("filename.txt");
				isr = new InputStreamReader(fis);
				osw = new OutputStreamWriter(System.out);
				char[] buffer = new char[512];
				int readCount = 0;
				while((readCount = isr.read(buffer)) != -1) {
				osw.write(buffer, 0, readCount);
				}
				fis.close();
				isr.close();
				osw.close();
		} catch (IOException e) {
				e.printStackTrace();
		}

}

Buffered

  • BufferedReader and BufferedWriter

  • BufferedInputStream과 하는 일이 동일한데, character기반입니다.

  • char 배열 사용

  • 프로그램이 읽어들일 때는 버퍼를 이용하고, 버퍼가 비면 source로부터 최대한 읽어 옵니다.

  • 쓸 때는 데이터가 버퍼에 들어가고, 버퍼가 꽉차면 내보내게 됩니다.

  • InputStreamReader만 쓰는 것보다, 버퍼를 이용하기 때문에 속도를 향상시킬 수 있습니다.

  • method

    • readLine: text의 한 line을 읽어들입니다.
    • newLine: os에 따른 line-separator을 씁니다.
      • network 프로토콜에서 사용하면 안된다.
profile
안녕하세요

0개의 댓글