
일반적인 스트림은 한번 사용하면 재사용이 불가능합니다. 따라서 아래와 같이 코드를 설계하면 잘못된 동작을 하게 됩니다.
InputStream inputStream = new FileInputStream(file) // (1)
byte[] byteBuffer = toByteArray(inputStream) // (1)
use(byteBuffer) // (2)
makeUploadFile(inputStream) // (3)
스트림은 무한한 입력을 가정합니다. 따라서 InputStream을 Read하면 새로운 데이터의 흐름을 처리하지 이전의 스트림을 다시 사용하지 않습니다. 우리는 (1)에서 파일의 내용을 모두 읽었기 때문에 더 이상 들어올 값은 없고 (3)에는 빈 파일이 생성됩니다.
실제 예시 코드로 살펴보면 다음과 같습니다.
public class Main {
public static void main(String[] args) {
File file = new File("input.txt");
try (InputStream inputStream = new FileInputStream(file)) {
// 1. InputStream → byte[]
byte[] fileBytes = toByteArray(inputStream);
// 2. byte[] → List<String>
List<String> lines = byteArrayToLines(fileBytes);
System.out.println("lines = " + lines);
// 3. 이미 소비된 inputStream으로 CSV 파일 만들기 시도
File outputFile = makeCsv(inputStream);
System.out.println("outputFileLength = " + outputFile.length());
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] toByteArray(InputStream inputStream) throws Exception {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, bytesRead);
}
return buffer.toByteArray();
}
public static List<String> byteArrayToLines(byte[] bytes) throws Exception {
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
return lines;
}
public static File makeCsv(InputStream inputStream) throws Exception {
File outputCsv = new File("output.csv");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
BufferedWriter writer = new BufferedWriter(new FileWriter(outputCsv))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
}
return outputCsv;
}
}

public static void main(String[] args) {
File file = new File("input.txt");
try (InputStream inputStream = new FileInputStream(file)) {
// 1. InputStream → byte[]
byte[] fileBytes = toByteArray(inputStream);
// 2. byte[] → List<String>
List<String> lines = byteArrayToLines(fileBytes);
System.out.println("lines = " + lines);
// 3. byte[] 기반으로 CSV 파일 만들기
File outputFile = makeCsv(fileBytes);
System.out.println("outputFileLength = " + outputFile.length());
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] toByteArray(InputStream inputStream) throws Exception {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, bytesRead);
}
return buffer.toByteArray();
}
public static List<String> byteArrayToLines(byte[] bytes) throws Exception {
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
return lines;
}
public static File makeCsv(byte[] data) throws Exception {
File outputCsv = new File("output.csv");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data), StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(new FileWriter(outputCsv))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
}
return outputCsv;
}

public class BufferedInputStreamExample {
public static void main(String[] args) {
File file = new File("/Users/qwerty1434/Desktop/input.txt");
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file))) {
// 0. mark() 호출
bufferedInputStream.mark((int) file.length() + 1);
// 1. InputStream → byte[]
byte[] fileBytes = toByteArray(bufferedInputStream);
// 2. byte[] → List<String>
List<String> lines = byteArrayToLines(fileBytes);
System.out.println("lines = " + lines);
// 3. reset() 호출로 스트림 재사용 가능하게 함
bufferedInputStream.reset();
// 4. 스트림을 다시 사용해 CSV 파일 생성
File outputFile = makeCsv(bufferedInputStream);
System.out.println("outputFileLength = " + outputFile.length());
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] toByteArray(InputStream inputStream) throws Exception {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, bytesRead);
}
return buffer.toByteArray();
}
public static List<String> byteArrayToLines(byte[] bytes) throws Exception {
List<String> lines = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
return lines;
}
public static File makeCsv(InputStream inputStream) throws Exception {
File outputCsv = new File("/Users/qwerty1434/Desktop/output.csv");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
BufferedWriter writer = new BufferedWriter(new FileWriter(outputCsv))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
}
return outputCsv;
}
}

개인적으로 BufferedInputStream을 이용해 스트림을 재사용하는 것보단 바이트 배열로 파일을 만드는게 더 안전하단 생각이 들었습니다. BufferedInputStream의 mark()메서드를 여러 로직에서 사용하다 보면 원치 않은 곳으로 스트림이 되돌아갈 위험이 있어 보이고, 스트림 자체가 재사용보다 무한한 입력을 위한 것이라 생각됐기 때문입니다.