import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws Exception
{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int K = Integer.parseInt(st.nextToken());
Queue<Integer> tmp = new LinkedList<>();
Queue<Integer> ans = new LinkedList<>();
for(int i = 1; i <= N; ++i) tmp.add(i);
while(!tmp.isEmpty())
{
for(int i = 1; i < K; ++i) tmp.add(tmp.poll());
ans.add(tmp.poll());
}
System.out.print("<");
while(ans.size() > 1)System.out.print(ans.poll() + ", ");
System.out.print(ans.poll() + ">");
}
}
설명
핵심 코드는 아래의 코드이다.
for(int i = 1; i < K; ++i) tmp.add(tmp.poll());
ans.add(tmp.poll());
만약 K = 3이라면 1 2 3 4 5 6 7 중에서 맨 앞 두 개를 잘라내기 후 붙여넣는다. -> 3 4 5 6 7 1 2
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다.
입출력 스트림
자바는 입력 스트림과 출력 스트림을 통해 데이터를 입출력한다.
스트림은 단 방향으로 데이터가 흐르는 것을 말한다.
입력 스트림 : 입력 장치 -> 프로그램
출력 스트림 : 프로그램 -> 출력 장치
프로그램을 기준으로 데이터가 들어오면 입력스트림, 데이터가 나가면 출력 스트림이 된다.
프로그램이 다른 프로그램과 데이터를 교환하려면 양쪽 모두 입력 스트림과 출력 스트림이 필요하다.
어떤 데이터를 입출력하느냐에 따라 스트림은 아래의 두 종류로 구분할 수 있다.
바이트 스트림 : 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 입출력할 때 사용
문자 스트림 : 문자만 입출력할 때 사용
두 스트림 모두 바이트를 전송한다. 또한 바이트 스트림도 문자를 입출력할 수 있지만 문자로 변환하는 과정이 추가적으로 필요하다.
자바는 데이터 입출력과 관련된 라이브러리를 java.io 패키지에서 제공하고 있다.
java.io 패키지는 바이트 스트림과 문자 스트림을 아래와 같이 이름으로 구분해서 제공한다.
바이트 입출력 스트림의 최상위 클래스는 InputStream과 OutputStream이다.
이 클래스를 상속받는 자식 클래스에는 접미사로 InputStream과 OutputStream이 붙는다.
문자 입출력 스트림의 최상위 클래스는 Reader와 Writer이다.
이 클래스를 상속받는 하위 클래스에는 접미사로 Reader 또는 Writer가 붙는다.
예를 들어 이미지와 같은 바이너리 파일의 입출력 스트림 클래스는 FileInputStream과 FileOutputStream
예를 들어 텍스트 파일의 입출력 스트림 클래스는 FileReader와 FileWriter이다.
바이트 출력 스트림
OutputStream은 바이트 출력 스트림의 최상위 클래스로 추상 클래스이다.
모든 바이트 출력 스트림 클래스는 이 OutputStream 클래스를 상속받아서 만들어진다.
OutputStream 클래스에는 모든 바이트 출력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.
1 byte 출력
write(int b) 메소드는 매개값 int(4 byte)에서 끝 1 byte만 출력한다.
매개변수가 int 타입이므로 4 바이트를 모두 보내는 것은 아니다.
따라서 byte의 범위인 -128~127 이외의 수가 들어오면 끝의 한 바이트를 제외한 값은 날라간다.
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class WriteExample {
public static void main(String[] args) {
try {
OutputStream os = new FileOutputStream("C:/Temp/test1.db"); //폴더는 있어야하고, 파일은 없으면 생성함
//1 바이트씩 출력(1 바이트씩 파일에 저장됨)
byte a = 10;
byte b = 20;
byte c = 30;
os.write(a);
os.write(b);
os.write(c);
os.flush(); //내부 버퍼에 있는 바이트를 출력하고 버퍼를 비움
os.close(); //출력 스트림을 닫음, 버퍼를 출력하고 비우는 기능도 있음
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileOutputStream 생성자는 주어진 파일을 생성할 수 없으면 IOException을 발생시킨다.
write(), flush(), close() 모두 예외처리를 해주어야 한다.
OutputStream은 내부에 작은 버퍼를 가지고 있다. write() 메소드가 호출되면 버퍼에 바이트를 우선 저장하고, 버퍼가 차면 순서대로 바이트를 출력한다. flush() 메소드는 내부 버퍼에 잔류하는 모든 바이트를 출력하고 버퍼를 비우는 역할을 한다. 출력 스트림을 더 이상 사용하지 않을 때에는 close() 메소드를 호출해서 출력 스트림이 사용했던 메모리를 해제해야 한다. 아니면 리소스 자동 닫기로 반드시 스트림을 닫아야 한다.
FileOutputStream은 두 번째 매개값에 true를 주면 새로운 내용을 뒤에 추가시키는 것이고(이어 쓰기), false를 주면 처음부터 다시 쓰게 된다.
바이트 배열 출력
보통 바이트 배열을 통째로 출력하는 경우가 많다.
write(byte[] b) 메소드는 매개값으로 주어진 배열의 모든 바이트를 출력한다.
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class WriteExample {
public static void main(String[] args) {
try {
OutputStream os = new FileOutputStream("C:/Temp/test2.db");
byte[] array = { 10, 20, 30 };
os.write(array); //배열의 모든 바이트를 출력
os.flush(); //버퍼에 있는 바이트를 출력하고 버퍼를 비움
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
만약 배열의 일부분을 출력하고 싶다면 write(byte[] b, int off, int len) 메소드를 사용하면 된다.
이 메소드는 b[off]부터 len개의 바이트를 출력한다.
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class WriteExample {
public static void main(String[] args) {
try {
OutputStream os = new FileOutputStream("C:/Temp/test3.db");
byte[] array = { 10, 20, 30, 40, 50 };
os.write(array, 1, 3);
os.flush();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
바이트 입력 스트림
InputStream은 바이트 입력 스트림의 최상위 클래스로, 추상 클래스이다.
모든 바이트 입력 스트림은 InputStream 클래스를 상속받아 만들어진다.
InputStream 클래스에는 바이트 입력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.
1 byte 읽기
read() 메소드는 입력 스트림으로부터 1 byte를 읽고 intI(4 byte) 타입으로 리턴한다.
따라서 4byte중 끝 1 byte에만 데이터가 들어 있다.
예를 들어 입력 스트림에서 5개의 바이트가 들어온다면 아래와 같이 read() 메소드로 1 바이트씩 5번 읽을 수 있다.
더 이상 입력 스트림으로부터 바이트를 읽을 수 없다면 read() 메소드는 -1을 리턴한다.
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class ReadExample {
public static void main(String[] args) {
try {
InputStream is = new FileInputStream("C:/Temp/test1.db");
while(true) {
int data = is.read(); //한 바이트씩 읽어옴
if(data == -1) break; //-1을 받으면 탈출
System.out.println(data);
}
is.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
10
20
30
*/
FileInputStream 생성자는 주어진 파일이 존재하지 않을 경우 FileNotFoundException을 발생시킨다.
read(), close() 메소드에서 IOException이 발생할 수 있으므로 두 가지 예외를 모두 처리해야 한다.
IOException의 자식 자식 클래스가 FileNotFoundException이므로 IOException만 예외 처리해 주어도 된다.
바이트 배열로 읽기
read(byte[] b) 메소드는 입력 스트림으로부터 주어진 배열의 길이만큼 바이트를 읽고 배열에 저장한 다음 읽은 바이트 수를 리턴한다.
예를 들어 입력 스트림에 5개의 바이트가 들어오면 아래와 같이 길이 3인 배열로 두 번 읽을 수 있다.
read(byte[] b) 역시 입력 스트림으로부터 바이트를 더 이상 읽을 수 없다면 -1을 리턴한다.
많은 양의 바이트를 읽을 때는 read(byte[] b) 메소드를 사용하는 것이 좋다.
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class ReadExample {
public static void main(String[] args) {
try {
InputStream is = new FileInputStream("C:/Temp/test2.db");
byte[] data = new byte[100];
while(true) {
int num = is.read(data); //최대 100바이트를 읽고, 읽은 바이트는 data에 저장, 읽은 수는 리턴
if(num == -1) break;
for(int i=0; i<num; i++) {
System.out.println(data[i]);
}
}
is.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
10
20
30
*/
파일 복사 예제
아래의 예제는 파일 복사이다.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class CopyExample {
public static void main(String[] args) throws Exception {
String originalFileName = "C:/Temp/test.jpg"; //복사할 파일
String targetFileName = "C:/Temp/test2.jpg"; //복사될 파일의 이름과 경로 지정
//입출력 스트림 생성
InputStream is = new FileInputStream(originalFileName);
OutputStream os = new FileOutputStream(targetFileName);
byte[] data = new byte[1024];
while(true) {
int num = is.read(data);
if(num == -1) break;
os.write(data, 0, num);
}
os.flush();
os.close();
is.close();
System.out.println("복사가 잘 되었습니다.");
}
}
/*
복사가 잘 되었습니다.
*/
test.jpg의 크기는 41.4KB(=42393.6 byte)이고, data 배열의 크기가 1024 이므로 for문을 42번 돌게 된다. 또한 0.4 byte(약 400 byte)는 num이 400정도가 되므로 os.write(data, 0, 400) 으로 처리된다.
복사가 잘 된 것을 확인할 수 있다.
Java 9부터 좀 더 편리하게 입력 스트림에서 출력 스트림으로 바이트를 복사하는 transferTo() 메소드가 InputStream에 추가되었다.
byte[] data = new byte[1024];
while(true) {
int num = is.read(data);
if(num == -1) break;
os.write(data, 0, num);
}
//////위 코드를 아래의 한줄로 대체 가능
is.transferTo(os);
문자 입출력 스트림
문자 입출력 스트림으로 Reader와 Writer가 있다.
입출력되는 단위가 문자인 것을 제외하고는 바이트 입출력 스트림과 사용 방법은 동일하다.
문자 출력
Writer는 문자 출력 스트림의 최상위 클래스로, 추상 클래스이다.
모든 문자 출력 스트림 클래스는 Writer 클래스를 상속받아서 만들어진다.
Writer 클래스에는 모든 문자 출력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.
Writer는 OutputStream과 사용 방법은 동일하지만, 출력 단위가 문자(char)이다.
또한 문자열을 출력하는 writer(String str) 메소드를 추가로 제공한다.
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class WriteExample {
public static void main(String[] args) {
try {
//문자 기반 출력 스트림 생성
Writer writer = new FileWriter("C:/Temp/test.txt");
//1 문자씩 출력
char a = 'A';
writer.write(a);
char b = 'B';
writer.write(b);
//char 배열 출력
char[] arr = { 'C', 'D', 'E' };
writer.write(arr);
//writer.write(arr, 0, arr.length); //위와 같은 코드
//문자열 출력
writer.write("FGH");
//버퍼에 잔류하고 있는 문자들을 출력하고, 버퍼를 비움
writer.flush();
//출력 스트림을 닫고 메모리 해제
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
문자 읽기
Reader는 문자 입력 스트림의 최상위 클래스로, 추상 클래스이다.
모든 문자 입력 스트림 클래스는 Reader 클래스를 상속받아서 만들어진다.
Reader 클래스에는 문자 입력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.
public class ReadExample {
public static void main(String[] args) {
try {
Reader reader = null;
//1 문자씩 읽기
reader = new FileReader("C:/Temp/test.txt"); //텍스트 파일로부터 문자 입력 스트림 생성
while(true) {
int data = reader.read();
if(data == -1) break;
System.out.print((char)data);
}
reader.close();
System.out.println();
//문자 배열로 읽기
reader = new FileReader("C:/Temp/test.txt"); //텍스트 파일로부터 문자 입력 스트림 생성
char[] data = new char[100]; // 읽은 문자를 저장할 배열 생성
while(true) {
int num = reader.read(data); //읽은 문자는 배열에 저장, 읽은 문자 수는 리턴
if(num == -1) break;
for(int i=0; i<num; i++) { //읽은 문자 수만큼 출력
System.out.print(data[i]);
}
}
reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.util.Scanner;
import java.util.Stack;
public class Main {
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
for(int i = 0; i < T; ++i)
{
boolean ans = func(sc.next()); //nextLine()이 아니라 next()를 사용
if(ans == true) System.out.println("YES");
else System.out.println("NO");
}
}
public static boolean func(String str)
{
Stack<Character> stack = new Stack<>();
for (int i = 0; i < str.length(); ++i)
{
char tmp = str.charAt(i);
if (tmp == '(') stack.push(tmp); //'(' 가 들어오면 push
else if (stack.empty()) return false; //')' 가 들어오면 pop을 해야하는데 스택이 비었다면 VPS가 아니다.
else stack.pop(); //')' 가 들어오면 pop
}
if (stack.empty()) return true; //'('의 수만큼 ')'의 수가 정상적으로 빠졌다면 스택은 비어있어야 한다.
else return false;
}
}
public class Student implements Comparable<Student> { //Comparable 구현 클래스
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() { return name; }
public int getScore() { return score; }
@Override
public int compareTo(Student o) { //Comparable 인터페이스 메소드 구현
return Integer.compare(score, o.score);
}
}
SortingExample.java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class SortingExample {
public static void main(String[] args) {
//List 컬렉션 생성
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("홍길동", 30));
studentList.add(new Student("신용권", 10));
studentList.add(new Student("유미선", 20));
//점수를 기준으로 오름차순으로 정렬한 새 스트림 얻기
studentList.stream()
.sorted( )
.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
System.out.println();
//점수를 기준으로 내림차순으로 정렬한 새 스트림 얻기
studentList.stream()
.sorted(Comparator.reverseOrder())
.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
}
}
/*
신용권: 10
유미선: 20
홍길동: 30
홍길동: 30
유미선: 20
신용권: 10
*/
Comparator를 이용한 정렬
요소 객체가 Comparable을 구현하고 있지 않다면, 비교자를 제공하면 요소를 정렬시킬 수 있다.
비교자는 Comparator 인터페이스를 구현한 객체를 말하는데, 명시적인 클래스로 구현할 수도 있지만 간단하게 람다식으로 작성할 수도 있다.
sorted((o1, o2) -> { ... })
중괄호 안에는 o1이 o2보다 작으면 음수, 같으면 0, 크면 양수를 리턴하도록 작성하면 된다.
public class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() { return name; }
public int getScore() { return score; }
}
SortingExample.java
import java.util.ArrayList;
import java.util.List;
public class SortingExample {
public static void main(String[] args) {
//List 컬렉션 생성
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("홍길동", 30));
studentList.add(new Student("신용권", 10));
studentList.add(new Student("유미선", 20));
//점수를 기준으로 오름차순으로 정렬한 새 스트림 얻기
studentList.stream()
.sorted((s1, s2) -> Integer.compare(s1.getScore(), s2.getScore()))
.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
System.out.println();
//점수를 기준으로 내림차순으로 정렬한 새 스트림 얻기
//매개값을 반대로 주면 내림차순 정렬
studentList.stream()
.sorted((s1, s2) -> Integer.compare(s2.getScore(), s1.getScore()))
.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
}
}
/*
신용권: 10
유미선: 20
홍길동: 30
홍길동: 30
유미선: 20
신용권: 10
*/
요소를 하나씩 처리(루핑)
루핑은 스트림에서 요소를 하나씩 반복해서 가져와 처리하는 것을 말한다.
루핑 메소드에는 peek()와 forEach()가 있다.
peek()와 forEach()는 동일하게 요소를 루핑 하지만 peek()은 중간 처리 메소드이고, forEach()는 최종 처리 메소드이다.
public class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() { return name; }
public int getScore() { return score; }
}
ReductionExample.java
import java.util.Arrays;
import java.util.List;
public class ReductionExample {
public static void main(String[] args) {
List<Student> studentList = Arrays.asList(
new Student("홍길동", 92),
new Student("신용권", 95),
new Student("감자바", 88)
);
//방법1
int sum1 = studentList.stream()
.mapToInt(Student :: getScore)
.sum();
//방법2
int sum2 = studentList.stream()
.map(Student :: getScore)
.reduce(0, (a, b) -> a+b);
System.out.println("sum1: " + sum1);
System.out.println("sum2: " + sum2);
}
}
/*
sum1: 275
sum2: 275
*/
요소 수집
스트림은 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드인 collect()를 제공한다.
이 메소드를 이용하면 필요한 요소만 컬렉션에 담을 수 있고, 요소들을 그룹핑한 후에 집계도 할 수 있다.
필터링한 요소 수집
Stream의 collect(Collector<T, A, R> collector) 메소드는 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하고, 이 컬렉션을 리턴한다.
매개값인 Collector는 어떤 요소를 어떤 컬렉션에 수집할 것인지를 결정한다.
리턴 타입
메소드(매개변수)
인터페이스
R
collect(Collector<T, A, R> collector)
Stream
타입 파라미터
T : 요소
A : accumulator (누적기)
R : 요소가 저장될 컬렉션
풀어서 해석하면 T 요소를 A 누적기가 R에 저장한다는 의미이다.
List, Set, Map 등이 R에 들어가면 별도의 A를 제공할 필요가 없다.
Collector의 구현 객체는 아래와 같이 Collectors 클래스의 정적 메소드로 얻을 수 있다.
아래는 Student 스트림에서 이름을 키로, 점수를 값으로 갖는 Map 컬렉션을 생성하는 코드이다.
Map<String, Integer> map = totalList.stream()
.collect(
Collectors.toMap(
s -> s.getName(), //Student 객체에서 키가 될 부분 리턴
s -> s.getScore() //Student 객체에서 값이 될 부분 리턴
)
);
요소 병렬 처리란 멀티 코어 CPU 환경에서 전체 요소를 분할해서 각각의 코어가 병렬적으로 처리하는 것을 말한다.
요소 병렬 처리의 목적은 작업 처리 시간을 줄이는 것에 있다.
자바는 요소 병렬 처리를 위해 병렬 스트림을 제공한다.
동시성과 병렬성
멀티 스레드는 동시성 또는 병렬성으로 실행된다.
동시성 : 멀티 작업을 위해 멀티 스레드가 하나의 코어에서 번갈아 가며 실행
병렬성 : 멀티 작업을 위해 멀티 코어를 각각 이용해서 병렬로 실행
동시성은 한 시점에 하나의 작업만 실행한다.
병렬성은 한 시점에 여러 개의 작업을 병렬로 실행하기 때문에 동시성보다는 좋은 성능을 낸다.
병렬성은 아래와 같이 두 가지로 나뉜다.
데이터 병렬성
작업 병렬성
데이터 병렬성 데이터 병렬성은 전체 데이터를 분할해서 서브 데이터셋으로 만들고 이 서브 데이터셋들을 병렬 처리해서 작업을 빨리 끝내는 것을 말한다. 서브 데이터셋은 멀티 코어의 수만큼 쪼개어 각각의 데이터들을 분리된 스레드에서 병렬처리 한다. 자바 병렬 스트림은 데이터 병렬성을 구현한 것이다.
작업 병렬성 작업 병렬성은 서로 다른 작업을 병렬 처리하는 것을 말한다. 작업 병렬성의 대표적인 예는 서버 프로그램이다. 서버는 각각의 클라이언트에서 요청한 내용을 개별 스레드에서 병렬로 처리한다.
포크조인 프레임워크
자바 병렬 스트림은 요소들을 병렬처리하기 위해 포크 조인 프레임워크를 사용한다.
포크조인 프레임워크는 포크 단계에서 전체 요소들을 서브 요소셋으로 분할하고, 각각의 서브 요소셋을 멀티 코어에서 병렬로 처리한다.
조인 단계에서는 서브 결과를 결합해서 최종 결과를 만들어낸다.
예를 들어 쿼드 코어 CPU에서 병렬 스트림으로 요소들을 처리할 경우 먼저 포크 단계에서 스트림의 전체 요소들을 4개의 서브 요소셋으로 분할한다.
그리고 각각의 서브 요소셋을 개별 코어에서 처리하고, 조인 단계에서는 3번의 결합 과정을 거쳐 최종 결과를 산출한다.
병렬 처리 스트림은 포크 단계에서 요소를 순서대로 분할하지 않는다.
이해하기 쉽도록 위 그림에서는 앞에서부터 차례대로 4등분 했지만, 내부적으로 요소들을 나누는 알고리즘이 있다.
포크조인 프레임워크는 병렬 처리를 위해 스레드풀을 사용한다. 각각의 코어에서 서브 요소셋을 처리하는 것은 작업 스레드가 해야 하므로 스레드 관리가 필요하다.
포크조인 프레임워크는 ExecutorService의 구현 객체인 ForkJoinPool을 사용해서 작업 스레드를 관리한다.
병렬 스트림 사용
자바 병렬 스트림을 이용할 경우에는 백그라운드에서 포크조인 프레임워크가 사용되기 때문에 개발자는 매우 쉽게 병렬 처리를 할 수 있다.
병렬 스트림은 아래의 두 가지 메소드로 얻을 수 있다.
parallelStream() 메소드는 컬렉션(List, Set)으로부터 병렬 스트림을 바로 리턴한다.
parallel()메소드는 기존 스트림을 병렬 처리 스트림으로 변환한다.
아래의 예제는 1억개의 점수에 대한 평균을 얻을 때 일반 스트림과 병렬 스트림의 처리 시간을 측정한 예제이다.
import java.util.Stack;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Stack<Integer> stack = new Stack<Integer>(); //스택 생성
int K = sc.nextInt();
for(int i = 0; i < K; i++) {
int input = sc.nextInt();
if(input == 0) stack.pop(); //0이면 팝
else stack.push(input); //0이 아니면 푸시
}
int sum = 0;
for(int i = 0; i < stack.size(); ++i) {
sum += stack.elementAt(i); //스택의 모든 원소를 더함
}
System.out.println(sum);
}
}
이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다.
스트림이란
Java 8부터 컬렉션 및 배열의 요소를 반복 처리하기 위해 스트림을 사용할 수 있다.
스트림은 요소들이 하나씩 흘러가면서 처리된다는 의미를 가지고 있다.
Stream은 Iterator와 비슷한 반복자이지만, 아래와 같은 차이점을 가지고 있다.
- 내부 반복자이므로 처리 속도가 빠르고 병렬 처리에 효율적이다. (내부에서 멀티 스레딩으로 처리) - 람다식으로 다양한 요소 처리를 정의할 수 있다. - 중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있다.(필터링 후 원하는 데이터만 추출 및 가공)
List 컬렉션의 stream() 메소드로 Stream 객체를 얻고, forEach() 메소드로 요소를 어떻게 처리할지를 람다식으로 제공한다.
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
public class StreamExample {
public static void main(String[] args) {
//Set 컬렉션 생성
Set<String> set = new HashSet< >();
set.add("홍길동");
set.add("신용권");
set.add("감자바");
//Stream을 이용한 요소 반복 처리
Stream<String> stream = set.stream();
stream.forEach( name -> System.out.println(name) ); //forEach()에 람다식 제공
}
}
/*
홍길동
신용권
감자바
*/
내부 반복자
외부 반복자 : for문과 Iterator는 컬렉션의 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리한다.
내부 반복자 : 스트림은 요소 처리 방법을 컬렉션 내부로 주입 시켜서 요소를 반복 처리한다.
내부 반복자는 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있다.
하나씩 처리하는 순차적 외부 반복자보다는 효율적으로 요소를 반복시킬 수 있는 장점이 있다.
내부 반복자는 멀티 스레딩 환경에서도 공유 자원을 안전하게 처리한다. 예를 들어, 공유 객체가 1000개이고 내부 반복자가 스레드를 2개 생성했다면, 각각 500개씩 분담 해서 처리한다.
아래의 예제는 List 컬렉션의 내부 반복자를 이용해서 병렬 처리하는 방법을 보여준다.
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class ParallelStreamExample {
public static void main(String[] args) {
//List 컬렉션 생성
List<String> list = new ArrayList< >();
list.add("홍길동");
list.add("신용권");
list.add("감자바");
list.add("람다식");
list.add("박병렬");
//병렬 처리
Stream<String> parallelStream = list.parallelStream(); //병렬 스트림 얻기
parallelStream.forEach( name -> {
System.out.println(name + ": " + Thread.currentThread().getName()); //처리하는 스레드 이름 출력
} );
}
}
/*
감자바: main
박병렬: main
람다식: main
홍길동: ForkJoinPool.commonPool-worker-2
신용권: ForkJoinPool.commonPool-worker-1
*/
중간 처리와 최종 처리
스트림은 하나 이상 연결될 수 있다.
아래의 그림을 보면 컬렉션의 오리지널 스트림 뒤에 필터링 중간 스트림이 연결 되고, 그 뒤에 매핑 중간 스트림이 연결된다.
이와 같이 스트림이 연결되어 있는 것을 스트림 파이프 라인이라고 한다.
중간 처리 과정 : 최종 처리를 위해 요소를 필터링 하거나 요소를 변환(매핑), 정렬 하는 작업을 수행
최종 처리 과정 : 중간 처리에서 정제된 요소들을 반복하거나, 집계(카운팅, 총합, 평균 등) 작업을 한다.
아래의 그림은 이름과 점수를 가지는 Studet 객체 스트림에서 중간 처리를 통해 score 스트림으로 변환한 후 최종 집계 처리로 score 평균을 구하는 과정을 나타낸다.
public class Student {
private String name;
private int score;
public Student (String name, int score) {
this.name = name;
this.score = score;
}
public String getName() { return name; }
public int getScore() { return score; }
}
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class StreamExample {
public static void main(String[] args) throws Exception {
Path path = Paths.get(StreamExample.class.getResource("data.txt").toURI()); //StreamExample 클래스 기준으로 data.txt 상대경로를 얻고 URI 객체를 리턴
Stream<String> stream = Files.lines(path, Charset.defaultCharset()); //스트림을 얻음, 문자셋 지정
stream.forEach(line -> System.out.println(line) );
stream.close();
}
}
/*
{"pno":1, "name":"상품1", "company":"멋진회사", "price":1558}
{"pno":2, "name":"상품2", "company":"멋진회사", "price":4671}
{"pno":3, "name":"상품3", "company":"멋진회사", "price":470}
{"pno":4, "name":"상품4", "company":"멋진회사", "price":9584}
{"pno":5, "name":"상품5", "company":"멋진회사", "price":6868}
*/
URI와 URL URI (Uniform Resource Identifier) : 리소스 “자원 자체”를 식별하는 고유한 문자열 시퀀스 URL (Uniform Resource Locator) : 자원(리소스)의 “위치” 를 나타내기 위한 규약
요소 걸러내기 (필터링)
필터링은 요소를 걸러내는 중간 처리 기능이다.
필터링은 요소를 걸러내는 중간 처리 기능이다.
필터링 메소드에는 disinct()와 filter()가 있다.
disinct()
요소의 중복을 제거
객체 스트림일 경우, equals() 메소드의 리턴 값이 true이면 동일한 요소로 판단
IntStream, LongStream, DoubleStream은 같은 값일 경우 중복을 제거
filter()
매개값으로 주어진 Predicate가 false를 리턴하는 요소는 스트림에서 제거한다.
Predicate는 함수형 인터페이스로, 아래와 같은 종류가 있다.
모든 Predicate는 매개값을 조사한 후 boolean을 리턴하는 test() 메소드를 가지고 있다.
Predicate<T>를 람다식으로 표현하면 아래와 같다.
T -> {...return true}
또는
T -> true; // 리턴문만 있을 경우 다른거 다 생략 가능
사용 예제
FilteringExample.java
import java.util.ArrayList;
import java.util.List;
public class FilteringExample {
public static void main(String[] args) {
//List 컬렉션 생성
List<String> list = new ArrayList<>();
list.add("홍길동"); list.add("신용권");
list.add("감자바"); list.add("신용권"); list.add("신민철");
//중복 요소 제거
list.stream()
.distinct()
.forEach(n -> System.out.println(n));
System.out.println();
//신으로 시작하는 요소만 필터링
list.stream()
.filter(n -> n.startsWith("신"))
.forEach(n -> System.out.println(n));
System.out.println();
//중복 요소를 먼저 제거하고, 신으로 시작하는 요소만 필터링
list.stream()
.distinct()
.filter(n -> n.startsWith("신"))
.forEach(n -> System.out.println(n));
}
}
/*
홍길동
신용권
감자바
신민철
신용권
신용권
신민철
신용권
신민철
*/
요소 변환 (매핑)
매핑은 스트림의 요소를 다른 요소로 변환하는 중간 처리 기능이다.(타입 변환)
요소를 다른 요소로 변환
mapXxx() 메소드는 요소를 다른 요소로 변환한 새로운 스트림을 리턴한다.
mapXxx() 메소드의 종류는 아래와 같다.
매개타입인 Function은 함수형 인터페이스로, 아래와 같은 종류가 있다.
모든 Function은 매개값을 리턴값으로 매핑(변환)하는 applyXxx() 메소드를 가지고 있다.
Function <T, R>을 람다식으로 표현하면 아래와 같다.
T -> {...return R}
또는
T -> R; // 리턴문만 있을 경우 다른거 다 생략 가능
사용 예제
Student.java
public class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() { return name; }
public int getScore() { return score; }
}
MapExample.java
import java.util.ArrayList;
import java.util.List;
public class MapExample {
public static void main(String[] args) {
//List 컬렉션 생성
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("홍길동", 85));
studentList.add(new Student("홍길동", 92));
studentList.add(new Student("홍길동", 87));
//Student를 score 스트림으로 변환
studentList.stream()
.mapToInt(s -> s.getScore())
.forEach(score -> System.out.println(score));
}
}
/*
85
92
87
*/
기본 타입 간의 변환이거나 기본 타입 요소를 Wrapper 객체 요소로 변환하려면 아래와 같은 간편화 메소드를 사용할 수도 있다.
flatMapXxx() 메소드는 하나의 요소를 복수 개의 요소들로 변환한 새로운 스트림을 리턴한다.
flatMap() 메소드의 종류는 아래와 같다.
사용예제
FlatMappingExample.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FlatMappingExample {
public static void main(String[] args) {
//문장 스트림을 단어 스트림으로 변환
List<String> list1 = new ArrayList< >();
list1.add("this is java");
list1.add("i am a best developer");
list1.stream().
flatMap(data -> Arrays.stream(data.split(" ")))
.forEach(word -> System.out.println(word));
System.out.println();
//문자열 숫자 목록 스트림을 숫자 스트림으로 변환
List<String> list2 = Arrays.asList("10, 20, 30", "40, 50");
list2.stream()
.flatMapToInt(data -> {
String[] strArr = data.split(",");
int[] intArr = new int[strArr.length];
for (int i = 0; i < strArr.length; i++) {
intArr[i] = Integer.parseInt(strArr[i].trim());
}
return Arrays.stream(intArr);
})
.forEach(number -> System.out.println(number));
}
}
/*
this
is
java
i
am
a
best
developer
10
20
30
40
50
*/