티스토리 뷰

📌 230816 TIL : 입출력 스트림, 스트림 공부, Reduce 연산 

 


 

📝 입출력 스트림 분류

1. 데이터(이진 데이터, 문자)의 종류의 따른 분류

1) 바이트 스트림 : 이진데이터(바이너리 데이터)를 처리하는 스트림

  • 바이트 입력 스트림 : InputStream - read() : 바이트 단위로 읽기.
  • 바이트 출력 스트림 : OutputStream - write() : 바이트 단위로 쓰기.
  • 파일 바이트 데이터를 읽기 : FileInputStream
  • 파일 바이트 데이터로 저장 : FileOutputStream

 

2) 문자 스트림 : 문자 데이터를 처리하는 스트림 (스트림 내부에서 문자 인코딩이 적용된다.)

  • 문자 입력 스트림 : Reader - read() : 문자 단위로 읽기.
  • 문자 출력 스트림 : Writer - write(), print(), println() : 문자 단위로 쓰기

 

2. 처리 방식에 따른 분류

1) 노드 스트림

  • 입출력 데이터에 가장 먼저 연결되는 스트림
  • InputStream, OutputStream, FileInputStream, FileOutputStream, Reader, Writer

 

2) 필터 스트림

  • 단독 사용 불가 -> 반드시 1) 노드 스트림에 연결하여 사용 => 생성자에서 연결해야함.
  • 매번 읽고 쓰는 것보다 버퍼에 저장하여 한번에 처리하는 것이 훨씬 효율적이기 때문에 사용한다. 
  • 버퍼는 한 줄씩 읽는다. 메소드 : readLine();
  • BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
//바이트 스트림 -> 문자 스트림 -> 버퍼 기능

System.out.print("문자열을 입력 하세요: ");
String line = bufferedReader.readLine();
System.out.println(line);

 

3) 브릿지 스트림

  • 바이트 입력(출력) 스트림을 문자 입력(출력) 스트림으로 변환.
  • 이걸 통해 한글도 입출력이 가능하다. 
  • 필터 스트림 계열임.
  • 바이트 입력 스트림 -> 문자 입력 스트림 : InputStreamReader
  • 바이트 출력 스트림 -> 문자 출력 스트림 : OutputStreamReader

 


📝  파일 핸들링 방법 

 

방법 1) 파일 데이터를 한글자씩 읽어오는 노드 스트림 방법

방법 2) 파일 데이터를 읽는 노드스트림 -> 버퍼에 저장하는 필터 스트림 

 

두 방법을 비교 해보았다. 

 

member.txt

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        /*
        * 파일 핸들링하기
        * 파일에 있는 데이터를 읽기 (이미지- 카피, 문자) - xml, json
        */

        //파일에서 문자 데이터를 읽어 오기 위한 스트림의 연결 (member.txt)

        //버퍼를 쓰면 3번만에 할 수 있는데 밑의 방법은 한글자씩 읽으므로 매번 돌아야해서 비효율.
        
        System.out.println("= 방법 1 =");
        FileReader fileReader = new FileReader("member.txt");
        int data;
        int cnt = 0;
        while ((data = fileReader.read()) != -1) {
            System.out.print((char)data);
            cnt++;
        }
        System.out.println("반복문 돈 횟수: " + cnt);
        
        System.out.println("= 방법 2 =");
        BufferedReader br = new BufferedReader(new FileReader("member.txt"));
        String line;
        cnt = 0;
        while((line = br.readLine()) != null) {
            System.out.println(line);
            cnt++;
        }
        System.out.println("반복문 돈 횟수: " + cnt);

    }
}

 

방법 2) 이가 한 줄씩 읽어오므로 방법 1보다 훨씬 반복문이 적게 돌기 때문에 효율적이다. 

 

 


 

📝 기타 스트림에 대해 

 

💡 스트림이란?

1) 자료에 대상과 관계없이 동일한 연산을 수행하도록 함. 

  • 배열, 컬렉션을 대상으로 연산을 수행함. 
  • 일관성 있는 연산으로 인해 자료의 처리를 쉽고 간단하게 함. 

2) 한번 생성한 스트림은 재사용이 불가능.

  • 자료에 대한 스트림을 생성하여 연산을 실행하면 스트림은 소모됨. 다른 연산을 수행하기 위해서는 스트림을 다시 생성해야함. 
  • 스트림의 연산은 기존 자료를 변경하지 않음. 
  • 자료에 대한 스트림을 생성하면 스트림이 사용하는 메모리 공간은 별도로 생성되므로 연산이 수행되도 기존 자료에 대한 변경은 발생하지 않음. 

3) 중간 연산과 최종 연산

  • 스트림의 연산은 중간 연산과 최종 연산으로 구분된다. 
  • 중간 연산은 여러개의 연산이 적용될 수 있지만, 최종연산은 단 한번의 연산만 적용된다. 또한 최종이 호출되어야, 중간연산들도 수행된다. 따라서 중간 연산에 대한 결과를 연산중에는 알 수 없다. 이를 ‘지연 연산’이라고도 한다. 
  • 중간 연산 : filter(), map(), sorted()
  • 최종 연산 : forEach(), count(), sum()

 

📝 사용 예시 

 

1) forEach 

int[] arr = {1,2,3,4,5};
Arrays.stream(arr).forEach(n->System.out.print(n+", "));

List<String> sList = new ArrayList<>();
sList.add("Tomas");
sList.add("Edward");
sList.add("Jack");
sList.stream().forEach(s-> System.out.print(s+"\t"));

 

2) sorted()

sList.stream().sorted().forEach(s-> System.out.print(s+"\t"));
//Edward	Jack	Tomas

 

3) filter()

//문자열 길이가 5이상인 문자열만 뽑아 출력 
sList.stream().filter(s->s.length()>=5).forEach(s-> System.out.print(s+"\t"));
//5	 6	4

 

4) map()

//문자열 길이만 뽑아 출력 
sList.stream().map(s->s.length()).forEach(n-> System.out.print(n+"\t"));
//Tomas	Edward

 

✏️ TravelCustomer 객체를 스트림으로 활용해보기 

class TravelCustomer {
    private String name;
    private int age, price;

    public TravelCustomer(String name, int age, int price) {
        this.name = name;
        this.age = age;
        this.price = price;
    }

    @Override
    public String toString() {
        return "TravelCustomer{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", price=" + price +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

public class TravelCustomerTest {
    public static void main(String[] args) {
        TravelCustomer customerLee = new TravelCustomer("이순신", 40, 100);
        TravelCustomer customerKim = new TravelCustomer("김유신", 20, 200);
        TravelCustomer customerHong = new TravelCustomer("홍길동", 13, 50);

        ArrayList<TravelCustomer> customers = new ArrayList<>();
        customers.add(customerLee);
        customers.add(customerKim);
        customers.add(customerHong);

        //이름만 출력하기
        System.out.println("= 고객 명단 출력 =");
        customers.stream().map(c->c.getName()).forEach(c-> System.out.print(c+"\t"));

        //여행비용 총합
        System.out.println("\n= 여행 비용 총합 =");
        System.out.println(customers.stream().mapToInt(c->c.getPrice()).sum());

        //나이가 20살 이상인 고객만 출력, 정렬
        System.out.println("= 20살 이상인 여행객들 =");
        customers.stream().filter(c-> c.getAge()>=20).map(c->c.getName()).sorted().forEach(c->System.out.print(c+"\t"));
    }
}

 


📝 Reduce 연산 

  • 정의된 연산이 아닌 프로그래머가 직접 구현한 연산을 적용.
  • 인터페이스 구조 : T reduce(T identify, BinaryOperator<T> accumulator)
  • 최종 연산으로 스트림의 요소를 소모하며 연산을 수행함. 
// 배열의 모든 요소의 합을 구하는 reduce 연산 : 
Arrays.stream(arr).reduce(0, (a,b) -> (a+b));

 

✏️ 사용예제 : 바이트가 가장 큰 문자열을 찾기 

class CompareString implements BinaryOperator<String> {
    @Override
    public String apply(String s, String s2) {
        if (s.getBytes().length >= s2.getBytes().length) return s;
        return s2;
    }
}
public class ReduceTest {
    public static void main(String[] args) {
        String[] greeting = {"안녕하세요!", "hello", "Good morning", "반갑습니다."};

        //익명 함수
        System.out.println(Arrays.stream(greeting).reduce("",(s1,s2) -> {
            if ( s1.getBytes().length >= s2.getBytes().length) return s1;
            else return s2;
        }));

        // BinaryOperator 인터페이스를 구현한 클래스를 활용한 예
        String s = Arrays.stream(greeting).reduce(new CompareString()).get();
        System.out.println(s);
    }
}