3주차 Unit 8.2 — FileInputStream

Psj·2026년 5월 20일

F-lab

목록 보기
107/197

Unit 8.2 — FileInputStream

F-LAB JAVA · 3주차 · Phase 8 · Stream 실전


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • FileInputStream 의 정의와 InputStream 과의 관계는?
  • 5가지 생성자 의 차이는?
  • read() 의 정확한 동작과 반환값 은?
  • 파일 끝 (EOF) 의 -1 은 왜 -1 인가? (byte 범위와 충돌)
  • FileNotFoundException 과 IOException 의 차이는?
  • 자원 누수의 위험 과 try-with-resources 의 필요성은?
  • available() 메서드 의 정확한 의미와 한계는?
  • skip() 메서드 의 활용은?
  • mark/reset 미지원 의 이유는?

🎯 핵심 한 문장

FileInputStreamInputStream 의 파일 전용 구현으로 디스크의 파일을 1바이트씩 읽는 가장 기본적인 도구다.
read() 가 0~255 또는 -1 (EOF) 을 반환하며, byte 의 -128~127 범위와의 충돌 회피를 위해 int 로 표현.
자원 (파일 핸들) 을 사용하므로 try-with-resources 필수, 안 닫으면 OS 의 파일 핸들 누수.
1바이트씩 읽기는 매우 느려서 (1MB 파일 = 100만 번 system call) 실무에서는 byte[] 버퍼 또는 BufferedInputStream 으로 감싸서 사용 (Unit 8.3 ~ 9.2).
NIO.2 의 Files.newInputStream(Path) 가 현대적 대안 — Path 와 결합 + 명확한 예외.

비유 — 수도꼭지에서 물 받기

FileInputStream:
  파일이라는 수도꼭지에서 물 받기
  - 한 방울 (1바이트) 씩
  - 수도꼭지 (FileInputStream) 가 비어있으면 -1
  - 사용 후 잠그기 (close) 필수
  - 안 잠그면 수도 (파일 핸들) 새는 중

읽기 메서드:
  - read(): 1방울 (느림)
  - read(byte[]): 여러 방울 한 번에 (효율)

→ FileInputStream = 파일 → JVM 의 가장 기본 통로.


🧭 9개 섹션 로드맵

1. FileInputStream 의 정의
2. 5가지 생성자
3. read() 의 정확한 동작
4. 파일 끝 (EOF) 의 -1
5. 예외 처리 (FileNotFoundException, IOException)
6. 자원 관리와 try-with-resources
7. 추가 메서드 (available, skip, mark/reset)
8. NIO.2 의 대안과 실무 패턴
9. 면접 + 자기 점검

1️⃣ FileInputStream 의 정의

1.1 클래스 정의

package java.io;

public class FileInputStream extends InputStream {
    
    private final FileDescriptor fd;
    private final String path;
    
    // 생성자, 메서드, ...
}

핵심:

  • InputStream 의 자식 (구체 클래스)
  • java.io 패키지
  • 파일 디스크립터 + 경로 보관
  • Java 1.0 부터 존재

1.2 InputStream 계층

InputStream 의 계층:

InputStream (추상)
  ├── FileInputStream         ← 파일
  ├── ByteArrayInputStream   ← 메모리 바이트 배열
  ├── PipedInputStream        ← 파이프
  ├── FilterInputStream
  │   ├── BufferedInputStream
  │   ├── DataInputStream
  │   ├── PushbackInputStream
  │   └── 기타
  ├── ObjectInputStream       ← 직렬화
  └── SequenceInputStream     ← 연속

FileInputStream 의 위치:
  - InputStream 의 직접 자식
  - 파일 시스템의 파일을 추상화

1.3 FileInputStream 의 주요 메서드

public class FileInputStream extends InputStream {
    
    // 생성자 (다음 섹션)
    
    // 핵심 메서드
    public int read() throws IOException;
    public int read(byte[] b) throws IOException;
    public int read(byte[] b, int off, int len) throws IOException;
    
    // 추가 메서드
    public long skip(long n) throws IOException;
    public int available() throws IOException;
    public void close() throws IOException;
    
    // FileInputStream 만의 메서드
    public FileChannel getChannel();   // NIO Channel 로 변환
    public FileDescriptor getFD() throws IOException;
    
    // 상속 (InputStream)
    public byte[] readAllBytes() throws IOException;   // Java 9+
    public long transferTo(OutputStream out) throws IOException;   // Java 9+
}

1.4 기본 사용

// 가장 단순한 사용
public class BasicRead {
    
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("data.txt");
        
        try {
            int b;
            while ((b = fis.read()) != -1) {
                System.out.print((char) b);
            }
        } finally {
            fis.close();
        }
    }
}

// try-with-resources (권장)
public static void main(String[] args) throws IOException {
    try (FileInputStream fis = new FileInputStream("data.txt")) {
        int b;
        while ((b = fis.read()) != -1) {
            System.out.print((char) b);
        }
    }
}

1.5 FileInputStream 의 본질

FileInputStream = 파일 핸들 + 읽기 위치

내부:
  - FileDescriptor (OS 의 파일 핸들 추상화)
  - 파일 내 현재 읽기 위치 (file pointer)
  - 매 read() 가 OS 의 read 시스템 호출

OS 자원:
  - 각 OS 마다 파일 핸들 한도 (ulimit -n)
  - 한도 초과 시 "Too many open files" 에러
  - 명시적 close 필수

1.6 InputStream 의 자식으로서의 의미

// InputStream 의 자식 — 다형성 활용
public void process(InputStream in) throws IOException {
    int b;
    while ((b = in.read()) != -1) {
        // 처리
    }
}

// 다양한 InputStream 으로 호출 가능
process(new FileInputStream("file.txt"));   // 파일
process(new ByteArrayInputStream(bytes));    // 메모리
process(socket.getInputStream());            // 네트워크
process(System.in);                          // 표준 입력

// 모두 같은 인터페이스

1.7 ILIC 의 활용

// 1. 단순 파일 읽기
public byte[] readFileContent(String path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path)) {
        return fis.readAllBytes();   // Java 9+
    }
}

// 2. 추상화된 처리
public void processData(InputStream source) throws IOException {
    // FileInputStream, ByteArrayInputStream 등 모두 처리 가능
    int b;
    while ((b = source.read()) != -1) {
        // 처리
    }
}

// 3. 파일을 InputStream 으로 다른 메서드에 전달
public void uploadShipmentFile(String path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path)) {
        uploader.upload(fis);   // uploader 는 InputStream 받음
    }
}

1.8 자기 점검 답변

FileInputStream 의 정의와 위치는?

:
1. 정의:

  • InputStream 의 파일 전용 구현
  • java.io 패키지
  • Java 1.0+
  1. 본질:

    • 파일 디스크립터 + 위치
    • OS 의 파일 핸들 활용
    • 1바이트씩 read
  2. 다형성:

    • InputStream 으로 추상화
    • 다양한 소스에 동일 API
  3. 자원:

    • close 필수
    • try-with-resources 권장

2️⃣ 5가지 생성자

2.1 5가지 생성자

// 1. 경로 문자열
public FileInputStream(String name) throws FileNotFoundException;

// 2. File 객체
public FileInputStream(File file) throws FileNotFoundException;

// 3. FileDescriptor (저수준)
public FileInputStream(FileDescriptor fdObj);

자주 사용되는 3가지 + 변형들.

2.2 경로 문자열

// 절대 경로
FileInputStream fis1 = new FileInputStream("/var/data/file.txt");

// 상대 경로 (현재 작업 디렉토리 기준)
FileInputStream fis2 = new FileInputStream("data.txt");
// → 보통 프로젝트 루트 기준

// Windows
FileInputStream fis3 = new FileInputStream("C:\\Users\\user\\file.txt");
// 또는 forward slash 도 OK
FileInputStream fis4 = new FileInputStream("C:/Users/user/file.txt");

2.3 File 객체

// File 활용 — 더 유연
File file = new File("/var/data/file.txt");
FileInputStream fis = new FileInputStream(file);

// File 의 메서드 활용
if (file.exists() && file.canRead()) {
    FileInputStream fis = new FileInputStream(file);
}

// 부모 + 자식
File dir = new File("/var/data");
File f = new File(dir, "report.txt");
FileInputStream fis = new FileInputStream(f);

// Path → File 변환
Path path = Path.of("/var/data/file.txt");
FileInputStream fis = new FileInputStream(path.toFile());

2.4 FileDescriptor (저수준)

// FileDescriptor 활용 (드물게)
FileInputStream existing = new FileInputStream("file.txt");
FileDescriptor fd = existing.getFD();

// 같은 fd 로 새 FileInputStream
FileInputStream another = new FileInputStream(fd);
// 같은 파일 핸들 공유

// 표준 입력
FileInputStream stdin = new FileInputStream(FileDescriptor.in);

// 활용:
// - 저수준 파일 핸들 공유
// - 표준 입력의 직접 활용
// - 보통은 사용 안 함

2.5 생성자의 예외

// FileNotFoundException
try {
    FileInputStream fis = new FileInputStream("nonexistent.txt");
} catch (FileNotFoundException e) {
    // 1. 파일이 없음
    // 2. 디렉토리
    // 3. 권한 없음 (읽기 불가)
}

// FileNotFoundException 의 부모
// FileNotFoundException extends IOException

// 잡기
try {
    FileInputStream fis = new FileInputStream("file.txt");
} catch (FileNotFoundException e) {
    // 파일 못 찾음
} catch (IOException e) {
    // 기타 I/O 에러 (FileNotFoundException 도 잡힘, 부모니까)
}

// 더 일반적으로
try {
    FileInputStream fis = new FileInputStream("file.txt");
} catch (IOException e) {
    // 모든 I/O 에러
}

2.6 NIO.2 와 비교

// java.io
FileInputStream fis = new FileInputStream("file.txt");
// 또는
FileInputStream fis = new FileInputStream(new File("file.txt"));

// NIO.2 (Java 7+)
InputStream is = Files.newInputStream(Path.of("file.txt"));
// 또는 옵션 추가
InputStream is = Files.newInputStream(
    Path.of("file.txt"),
    StandardOpenOption.READ);

// 차이:
// - FileInputStream: 구체 타입
// - Files.newInputStream: InputStream 반환 (구체 타입 숨김)
// - 후자가 더 유연

// 명확한 예외
try {
    InputStream is = Files.newInputStream(Path.of("file.txt"));
} catch (NoSuchFileException e) {
    // 파일 없음 (FileNotFoundException 보다 명확)
} catch (AccessDeniedException e) {
    // 권한 없음 (별도 예외)
} catch (IOException e) {
    // 기타
}

2.7 생성자 비교

생성자매개변수특징
(String)경로 문자열가장 단순
(File)File 객체File 활용 가능
(FileDescriptor)저수준 fd드물게 사용

2.8 ILIC 의 활용

public class ShipmentFileReader {
    
    private final Path baseDir = Path.of("/var/shipment");
    
    // 1. 단순 경로
    public byte[] readSimple(String filename) throws IOException {
        try (FileInputStream fis = new FileInputStream(
                baseDir.resolve(filename).toString())) {
            return fis.readAllBytes();
        }
    }
    
    // 2. File 활용 (검증)
    public byte[] readWithValidation(String filename) throws IOException {
        File file = baseDir.resolve(filename).toFile();
        
        if (!file.exists()) {
            throw new FileNotFoundException("Not found: " + file);
        }
        if (!file.canRead()) {
            throw new IOException("Cannot read: " + file);
        }
        if (file.length() > 100_000_000) {
            throw new IOException("Too large: " + file);
        }
        
        try (FileInputStream fis = new FileInputStream(file)) {
            return fis.readAllBytes();
        }
    }
    
    // 3. NIO.2 권장
    public byte[] readModern(String filename) throws IOException {
        Path path = baseDir.resolve(filename);
        return Files.readAllBytes(path);
        // 가장 간결
    }
}

2.9 자기 점검 답변

FileInputStream 의 생성자 3가지는?

:
1. (String name):

  • 경로 문자열
  • 가장 단순
  1. (File file):

    • File 객체
    • 검증 가능
  2. (FileDescriptor fd):

    • 저수준
    • 핸들 공유

예외:

  • 생성자가 FileNotFoundException 던짐
  • IOException 의 자식

NIO.2 대안:

  • Files.newInputStream(Path)
  • 더 명확한 예외 계층

3️⃣ read() 의 정확한 동작

3.1 read() 의 시그니처

public int read() throws IOException;

// 동작:
// 1. 파일에서 1바이트 읽기
// 2. 0~255 의 int 로 반환
// 3. EOF 면 -1 반환

// Blocking:
// - 파일이면 거의 즉시 (디스크 I/O)
// - 네트워크라면 데이터 올 때까지

3.2 read() 의 동작 단계

사용자 코드:
  int b = fis.read();

JVM 내부:
  1. native 메서드 호출
  2. JNI 통해 OS 시스템 호출 (read())
  3. user → kernel 전환
  4. kernel 이 파일에서 1바이트 읽음
  5. kernel → user 전환
  6. int 로 반환 (0~255 또는 -1)
  
파일 위치:
  - 매 read() 후 위치 +1
  - 다음 read 는 다음 바이트

3.3 read() 의 시각화

파일 내용 (5바이트):
  [0x48, 0x65, 0x6C, 0x6C, 0x6F]
  ("Hello" — ASCII)

read 호출:
  fis.read();   // 72 (0x48, 'H')   파일 위치: 1
  fis.read();   // 101 (0x65, 'e')  파일 위치: 2
  fis.read();   // 108 (0x6C, 'l')  파일 위치: 3
  fis.read();   // 108 (0x6C, 'l')  파일 위치: 4
  fis.read();   // 111 (0x6F, 'o')  파일 위치: 5
  fis.read();   // -1 (EOF)         파일 위치: 5 (변화 없음)
  fis.read();   // -1 (이후도 계속 -1)

3.4 일반적 read 패턴

// 패턴 1: while + -1
try (FileInputStream fis = new FileInputStream("file.txt")) {
    int b;
    while ((b = fis.read()) != -1) {
        // 처리
        System.out.print((char) b);
    }
}

// 패턴 2: 캐스팅 주의
int b = fis.read();
if (b == -1) {
    // EOF
} else {
    byte data = (byte) b;   // -128 ~ 127 (signed)
    char c = (char) b;      // 0 ~ 65535 (unsigned)
    
    // 영문 (ASCII) 만 정상
    // 한글 등은 깨짐
}

// 패턴 3: 카운트
int count = 0;
while (fis.read() != -1) {
    count++;
}
// count = 파일 크기 (바이트)
// 하지만 fis.available() 또는 file.length() 가 더 빠름

3.5 read 의 비효율

// 1MB 파일을 1바이트씩
try (FileInputStream fis = new FileInputStream("1MB.dat")) {
    int b;
    while ((b = fis.read()) != -1) {
        // 1,048,576 번 호출
        // 각 호출 = 1 system call
        // 총 ~1초 이상 (오버헤드)
    }
}

// 더 빠른 방법 (Unit 8.3):
// 1. byte[] 활용
byte[] buf = new byte[8192];
int n;
while ((n = fis.read(buf)) != -1) {
    // 128 번 호출만
    // 8000배 빠름
}

// 2. BufferedInputStream (Unit 9.2):
BufferedInputStream bis = new BufferedInputStream(fis);
int b;
while ((b = bis.read()) != -1) {
    // 사용자는 1바이트씩 호출하지만
    // 내부적으로 8KB 단위로 OS 호출
}

3.6 read 의 반환값 처리

// 함정: byte 로 직접 캐스팅
byte b = (byte) fis.read();
// 0xFF 면 -1
// EOF 와 구분 불가

// 올바른 처리
int b = fis.read();
if (b == -1) {
    break;
}
byte data = (byte) b;   // 안전

// 또는 int 로 그대로 비교
int b = fis.read();
while (b != -1) {
    process(b);
    b = fis.read();
}

3.7 read 의 Blocking

// 일반 파일: 거의 즉시 (디스크 캐시)
try (FileInputStream fis = new FileInputStream("file.txt")) {
    int b = fis.read();   // 빠름 (μs)
}

// 네트워크 파일 (NFS): 느림 가능
// 외부 디스크: 느림 가능

// FIFO 파이프: Blocking
try (FileInputStream fis = new FileInputStream("/tmp/myfifo")) {
    int b = fis.read();   // 데이터 올 때까지 대기
}

// /dev/random: 데이터 부족 시 Blocking
try (FileInputStream fis = new FileInputStream("/dev/random")) {
    int b = fis.read();   // 엔트로피 부족 시 대기
}

3.8 ILIC 의 활용

// 1. 작은 파일 처리
public String readSmallFile(String path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path)) {
        // 작은 파일 (< 1MB) 만
        StringBuilder sb = new StringBuilder();
        int b;
        while ((b = fis.read()) != -1) {
            sb.append((char) b);
        }
        return sb.toString();
        // 영문만 정상, 한글 깨짐
    }
}

// 2. 바이트 카운트
public long countBytes(String path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path)) {
        long count = 0;
        while (fis.read() != -1) {
            count++;
        }
        return count;
    }
    // 비효율적
    // 더 좋은 방법: file.length() 또는 Files.size(path)
}

// 3. 특정 바이트 검색
public long findByte(String path, byte target) throws IOException {
    try (FileInputStream fis = new FileInputStream(path)) {
        long pos = 0;
        int b;
        while ((b = fis.read()) != -1) {
            if ((byte) b == target) return pos;
            pos++;
        }
        return -1;
    }
}

3.9 자기 점검 답변

read() 의 정확한 동작은?

:
1. 시그니처:

  • int read() throws IOException
  • 1바이트 읽기
  1. 반환:

    • 0~255: 정상 바이트
    • -1: EOF
  2. 동작:

    • native 메서드 → OS read 시스템 호출
    • 파일 위치 +1
    • Blocking (네트워크/파이프 등에서)
  3. 비효율:

    • 매 호출 = system call
    • 1MB 파일 = 100만 번 호출
    • byte[] 또는 BufferedInputStream 권장
  4. 함정:

    • byte 로 직접 캐스팅 X (0xFF == -1 충돌)
    • int 로 유지 → 비교 후 캐스팅

4️⃣ 파일 끝 (EOF) 의 -1

4.1 EOF 의 정의

EOF (End Of File):

  파일의 끝, 더 이상 읽을 데이터 없음.

InputStream 의 표시:
  - read() 가 -1 반환
  - read(byte[]) 가 -1 반환 (한 바이트도 못 읽음)

이유:
  - byte (0~255) 와 다른 값 필요
  - int 의 -1 활용 (32비트, byte 의 범위 밖)

4.2 -1 의 정밀

왜 -1 인가?

byte 의 범위:
  signed byte: -128 ~ 127
  unsigned byte: 0 ~ 255 (자바엔 없음)
  
  바이너리: 11111111 = 0xFF
  byte 로 보면 -1
  unsigned 로 보면 255

문제:
  byte 로 직접 비교 시
  if (b == -1) {  // EOF? 또는 0xFF 정상 데이터?
  }
  
  → 구분 불가

해결: int 로 확장
  read() 가 int 반환
  - 0xFF 데이터: int 의 255
  - EOF: int 의 -1
  → 명확히 구분

4.2 read() 의 반환 비교

// 잘못된 비교
byte b = (byte) fis.read();
if (b == -1) {   // ❌ 0xFF 와 혼동
    // EOF? 아니면 정상 데이터?
}

// 올바른 비교
int b = fis.read();
if (b == -1) {   // ✓ EOF 명확
    break;
}

// int 로 작업
byte data = (byte) b;   // 이제 안전하게 캐스팅

4.3 read(byte[]) 의 EOF

// read(byte[]) 의 반환
byte[] buf = new byte[1024];
int n = fis.read(buf);

// n 의 의미:
// - 양수: 실제 읽은 바이트 수 (1 ~ buf.length)
// - -1: EOF (한 바이트도 못 읽음)
// - 0: 드뭄 (보통 Blocking 에서)

while (n != -1) {
    // 0 ~ n-1 처리
    process(buf, 0, n);
    n = fis.read(buf);
}

// 마지막 읽기 함정:
// 파일 크기 1023 바이트, 버퍼 1024
// 첫 read: n=1023
// 두 번째 read: n=-1 (EOF)

4.4 EOF 의 지속성

try (FileInputStream fis = new FileInputStream("file.txt")) {
    // 파일을 끝까지 읽음
    while (fis.read() != -1) { }
    
    // 이후 계속 -1 반환
    int b = fis.read();   // -1
    b = fis.read();        // -1
    
    // 파일이 변경되어도 (다른 프로세스가)
    // 이미 닫힌 위치라 -1 유지
    // 다시 열거나 position 변경 필요
}

4.5 EOF 다른 신호와의 비교

다른 곳의 "끝" 신호:

InputStream.read():     -1 (EOF)
Reader.read():          -1 (EOF, char 의 -1)
BufferedReader.readLine(): null (없음)
Scanner.hasNext():      false
Iterator.hasNext():     false
DataInputStream.readUTF(): EOFException 예외

차이:
  - InputStream: int 의 -1 (반환값)
  - Reader: int 의 -1 (char 의 충돌 회피)
  - BufferedReader.readLine: null
  - 일부 (DataInputStream): 예외

4.6 EOF 의 처리 패턴

// 패턴 1: while 안에서 변수 할당 (Java 의 관용)
int b;
while ((b = fis.read()) != -1) {
    process(b);
}

// 패턴 2: 분리
while (true) {
    int b = fis.read();
    if (b == -1) break;
    process(b);
}

// 패턴 3: do-while
int b;
do {
    b = fis.read();
    if (b != -1) process(b);
} while (b != -1);

// 일반적으로 패턴 1 권장

4.7 EOF 와 예외의 차이

// 일반 EOF: -1 반환 (정상)
try (FileInputStream fis = new FileInputStream("file.txt")) {
    int b;
    while ((b = fis.read()) != -1) { }
    // 정상 종료
}

// I/O 에러: 예외
try (FileInputStream fis = new FileInputStream("file.txt")) {
    int b = fis.read();
    // 디스크 에러, 권한 변경 등
    // → IOException
} catch (IOException e) {
    // 에러 처리
}

// 즉:
// - 파일 끝까지: -1
// - 에러: 예외

4.8 ILIC 의 EOF 처리

// 표준 패턴
public byte[] readAll(InputStream is) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    
    int b;
    while ((b = is.read()) != -1) {
        baos.write(b);
    }
    
    return baos.toByteArray();
}

// 더 효율적 (byte[] 활용)
public byte[] readAllEfficient(InputStream is) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buf = new byte[8192];
    
    int n;
    while ((n = is.read(buf)) != -1) {
        baos.write(buf, 0, n);
    }
    
    return baos.toByteArray();
}

// 더 간단 (Java 9+)
public byte[] readAllSimple(InputStream is) throws IOException {
    return is.readAllBytes();
}

4.9 자기 점검 답변

파일 끝 (-1) 의 정확한 의미는?

:
1. -1 의 이유:

  • byte (0~255) 와 다른 값 필요
  • int 의 -1 활용
  • 0xFF 데이터와 구분
  1. 반환:

    • read(): 0~255 정상, -1 EOF
    • read(byte[]): 양수 정상, -1 EOF
  2. 함정:

    • byte b = (byte) read() 직접 캐스팅 X
    • int 로 비교 후 캐스팅
  3. 지속성:

    • 한 번 -1 이면 계속 -1
    • 파일 변경되어도 동일
  4. EOF vs 에러:

    • EOF: -1 (정상)
    • 에러: IOException

5️⃣ 예외 처리 (FileNotFoundException, IOException)

5.1 FileInputStream 의 예외 계층

Exception
  └── IOException
        ├── FileNotFoundException   ← 생성자
        ├── EOFException            ← DataInputStream 등
        ├── InterruptedIOException
        └── ... (기타)

5.2 FileNotFoundException

// 생성자가 던질 수 있음
public FileInputStream(String name) throws FileNotFoundException;

// 발생 원인:
// 1. 파일이 존재하지 않음
// 2. 디렉토리 (파일이 아님)
// 3. 권한 없음 (읽기 불가)

try {
    FileInputStream fis = new FileInputStream("nonexistent.txt");
} catch (FileNotFoundException e) {
    System.err.println("Not found: " + e.getMessage());
}

5.3 IOException

// read() 등 메서드가 던질 수 있음
public int read() throws IOException;

// 발생 원인:
// 1. 디스크 에러
// 2. 권한 변경 (도중에)
// 3. 닫힌 스트림
// 4. 인터럽트

try (FileInputStream fis = new FileInputStream("file.txt")) {
    int b;
    while ((b = fis.read()) != -1) {
        // ...
    }
} catch (IOException e) {
    System.err.println("I/O error: " + e.getMessage());
}

5.4 두 예외의 관계

// FileNotFoundException 은 IOException 의 자식
// IOException 만 잡으면 둘 다 잡힘

try {
    FileInputStream fis = new FileInputStream("file.txt");
    fis.read();
    fis.close();
} catch (IOException e) {
    // FileNotFoundException 도 여기서 잡힘
}

// 구체적으로 잡기 (권장)
try {
    FileInputStream fis = new FileInputStream("file.txt");
    fis.read();
    fis.close();
} catch (FileNotFoundException e) {
    // 파일 못 찾음
} catch (IOException e) {
    // 기타 I/O 에러
}

5.5 NIO.2 의 명확한 예외

// java.io
try {
    FileInputStream fis = new FileInputStream("file.txt");
} catch (FileNotFoundException e) {
    // 파일 없음? 디렉토리? 권한 없음?
    // 정확한 원인 모름
}

// NIO.2 — 명확한 구분
try {
    InputStream is = Files.newInputStream(Path.of("file.txt"));
} catch (NoSuchFileException e) {
    // 파일 없음
} catch (NotDirectoryException e) {
    // 디렉토리 관련
} catch (AccessDeniedException e) {
    // 권한 없음
} catch (IOException e) {
    // 기타
}

5.6 예외 처리 패턴

// 패턴 1: try-catch
public byte[] readSafe(String path) {
    try {
        try (FileInputStream fis = new FileInputStream(path)) {
            return fis.readAllBytes();
        }
    } catch (FileNotFoundException e) {
        log.warn("File not found: {}", path);
        return new byte[0];
    } catch (IOException e) {
        log.error("I/O error reading: {}", path, e);
        return new byte[0];
    }
}

// 패턴 2: throws (위로 전파)
public byte[] read(String path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path)) {
        return fis.readAllBytes();
    }
}

// 패턴 3: 변환
public byte[] readOrThrow(String path) {
    try (FileInputStream fis = new FileInputStream(path)) {
        return fis.readAllBytes();
    } catch (IOException e) {
        throw new RuntimeException("Failed to read: " + path, e);
    }
}

// 패턴 4: Optional
public Optional<byte[]> readOptional(String path) {
    try (FileInputStream fis = new FileInputStream(path)) {
        return Optional.of(fis.readAllBytes());
    } catch (IOException e) {
        return Optional.empty();
    }
}

5.7 예외와 자원 관리

// ❌ 자원 누수 가능
public byte[] readBad(String path) {
    try {
        FileInputStream fis = new FileInputStream(path);
        byte[] data = fis.readAllBytes();
        fis.close();
        return data;
    } catch (IOException e) {
        // close 안 됨!
        return null;
    }
}

// ✓ try-finally
public byte[] readOk(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    try {
        return fis.readAllBytes();
    } finally {
        fis.close();   // 예외 무관 항상 실행
    }
}

// ✓✓ try-with-resources (권장)
public byte[] readBest(String path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path)) {
        return fis.readAllBytes();
    }
    // 자동 close + Suppressed Exception
}

5.8 ILIC 의 예외 처리

@Service
public class ShipmentFileService {
    
    private final Path baseDir = Path.of("/var/shipment");
    
    // 1. 명확한 예외 처리
    public byte[] readShipmentFile(String filename) {
        Path path = baseDir.resolve(filename);
        
        try (FileInputStream fis = new FileInputStream(path.toFile())) {
            return fis.readAllBytes();
        } catch (FileNotFoundException e) {
            throw new ShipmentFileNotFoundException(filename);
        } catch (IOException e) {
            throw new ShipmentFileIOException(filename, e);
        }
    }
    
    // 2. NIO.2 권장
    public byte[] readShipmentFileModern(String filename) {
        Path path = baseDir.resolve(filename);
        
        try {
            return Files.readAllBytes(path);
        } catch (NoSuchFileException e) {
            throw new ShipmentFileNotFoundException(filename);
        } catch (AccessDeniedException e) {
            throw new ShipmentFileAccessException(filename);
        } catch (IOException e) {
            throw new ShipmentFileIOException(filename, e);
        }
    }
    
    // 사용자 정의 예외
    public static class ShipmentFileNotFoundException extends RuntimeException {
        public ShipmentFileNotFoundException(String filename) {
            super("Shipment file not found: " + filename);
        }
    }
    
    public static class ShipmentFileIOException extends RuntimeException {
        public ShipmentFileIOException(String filename, Throwable cause) {
            super("I/O error: " + filename, cause);
        }
    }
}

5.9 자기 점검 답변

FileInputStream 의 예외 처리는?

:
1. 두 가지 주요 예외:

  • FileNotFoundException: 생성자 (파일 없음/디렉토리/권한)
  • IOException: 메서드 (디스크 에러, 닫힌 스트림 등)
  1. 계층:

    • FileNotFoundException extends IOException
    • IOException 만 잡아도 둘 다
  2. NIO.2 권장:

    • NoSuchFileException
    • AccessDeniedException
    • 명확한 구분
  3. 자원 관리:

    • try-with-resources 필수
    • 예외 시에도 close 보장
    • Suppressed Exception

6️⃣ 자원 관리와 try-with-resources

6.1 파일 핸들의 자원성

파일 핸들 (File Handle):

  OS 가 관리하는 파일에 대한 참조.
  
특징:
  - 제한된 자원 (OS 마다 한도)
  - 명시적 해제 필요
  - 해제 안 하면 누수

OS 한도:
  - Linux 기본: 1024 (ulimit -n)
  - 일부 OS: 65536
  - 한도 초과 시: "Too many open files"

6.2 누수의 시나리오

// ❌ 자원 누수
public void readMany(List<String> paths) throws IOException {
    for (String path : paths) {
        FileInputStream fis = new FileInputStream(path);
        // 처리...
        // close 안 함!
    }
}
// 1만 파일 처리 → 1만 핸들 누수
// 한도 초과 → 에러

// ❌ 예외 시 누수
public byte[] readBad(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    byte[] data = fis.readAllBytes();   // ★ 여기서 예외 가능
    fis.close();
    return data;
    // 예외 시 close 안 됨
}

6.3 try-finally 패턴 (Java 6 이하)

public byte[] readSafe(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    try {
        return fis.readAllBytes();
    } finally {
        fis.close();   // 예외 무관 항상 실행
    }
}

// 여러 자원
public void copy(String src, String dest) throws IOException {
    FileInputStream fis = new FileInputStream(src);
    try {
        FileOutputStream fos = new FileOutputStream(dest);
        try {
            // 복사
            byte[] buf = new byte[8192];
            int n;
            while ((n = fis.read(buf)) != -1) {
                fos.write(buf, 0, n);
            }
        } finally {
            fos.close();
        }
    } finally {
        fis.close();
    }
}
// 중첩 → 가독성 ↓

6.4 try-with-resources (Java 7+)

// 단일 자원
public byte[] readBest(String path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path)) {
        return fis.readAllBytes();
    }
    // 자동 close
}

// 여러 자원
public void copy(String src, String dest) throws IOException {
    try (FileInputStream fis = new FileInputStream(src);
         FileOutputStream fos = new FileOutputStream(dest)) {
        byte[] buf = new byte[8192];
        int n;
        while ((n = fis.read(buf)) != -1) {
            fos.write(buf, 0, n);
        }
    }
    // fos, fis 자동 close (역순)
}

6.5 AutoCloseable 인터페이스

// FileInputStream 의 계층
FileInputStream
  extends InputStream
    implements CloseableAutoCloseable 의 자식

// Closeable
public interface Closeable extends AutoCloseable {
    void close() throws IOException;
}

// AutoCloseable
public interface AutoCloseable {
    void close() throws Exception;
}

// try-with-resources 의 조건:
// AutoCloseable 구현
// FileInputStream 은 자동으로

6.6 close 의 정밀

public void close() throws IOException {
    // 1. OS 의 close 시스템 호출
    // 2. 파일 핸들 해제
    // 3. 버퍼링된 데이터 flush (OutputStream)
    // 4. 자원 정리

    // 두 번 close 호출 OK (no-op)
    // 단, 자원 누수 방지 위해 한 번만 권장
}

6.7 자원 누수 모니터링

// 사용 중인 파일 핸들 확인 (Linux)
// $ lsof -p <PID>
// 또는
// $ ls /proc/<PID>/fd

// Java 코드에서
// java.lang.management.OperatingSystemMXBean (UnixOperatingSystemMXBean)
OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
if (os instanceof UnixOperatingSystemMXBean) {
    long open = ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount();
    long max = ((UnixOperatingSystemMXBean) os).getMaxFileDescriptorCount();
    System.out.println("Open FDs: " + open + " / " + max);
}

6.8 ILIC 의 자원 관리

@Service
public class ShipmentFileService {
    
    // 1. 단일 파일 처리
    public byte[] readFile(Path path) throws IOException {
        try (FileInputStream fis = new FileInputStream(path.toFile())) {
            return fis.readAllBytes();
        }
    }
    
    // 2. 대량 처리 — 각 파일을 try-with-resources
    public void processManyFiles(List<Path> paths) throws IOException {
        for (Path path : paths) {
            try (FileInputStream fis = new FileInputStream(path.toFile())) {
                processStream(fis);
            }
            // 각 파일 close 보장
        }
    }
    
    // 3. 파일 복사
    public void copyShipmentFile(Path src, Path dest) throws IOException {
        try (FileInputStream fis = new FileInputStream(src.toFile());
             FileOutputStream fos = new FileOutputStream(dest.toFile())) {
            
            fis.transferTo(fos);   // Java 9+
        }
        // 둘 다 자동 close
    }
    
    // 4. 모니터링
    public long countOpenFiles() {
        OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
        if (os instanceof UnixOperatingSystemMXBean unix) {
            return unix.getOpenFileDescriptorCount();
        }
        return -1;
    }
}

6.9 자기 점검 답변

FileInputStream 의 자원 관리는?

:
1. 자원성:

  • OS 의 파일 핸들 사용
  • 제한된 자원 (1024~)
  • 명시적 해제 필수
  1. 누수의 위험:

    • close 누락
    • 예외 시 close 안 됨
    • 한도 초과 → "Too many open files"
  2. 권장 패턴:

    • try-with-resources
    • AutoCloseable 활용
    • 자동 close + Suppressed Exception
  3. 다중 자원:

    • 세미콜론으로 구분
    • 역순 close
  4. 모니터링:

    • UnixOperatingSystemMXBean
    • lsof -p

7️⃣ 추가 메서드 (available, skip, mark/reset)

7.1 available() — 즉시 읽기 가능한 바이트 수

public int available() throws IOException;

// 반환:
// - 즉시 읽기 가능한 바이트 수 (대략)
// - Blocking 없이 read 가능한 양

// 파일에서:
try (FileInputStream fis = new FileInputStream("file.txt")) {
    int avail = fis.available();
    // 보통 파일 크기 - 현재 위치
    
    byte[] buf = new byte[avail];
    fis.read(buf);   // Blocking 없이
}

7.2 available 의 한계

// 함정 1: 정확한 파일 크기 아닐 수 있음
// - 파일이 매우 클 때
// - 네트워크 파일 시스템
// - 파이프

// 함정 2: 0 반환 가능
// - 데이터 없음 (스트림 끝 아님)
// - 0 ≠ EOF

// ❌ 잘못된 사용
byte[] buf = new byte[fis.available()];
fis.read(buf);
// 큰 파일에서 OutOfMemoryError 위험
// 또는 부분만 읽힘

// ✓ 권장: 파일 크기는 별도
long size = Files.size(path);
// 또는 file.length()

7.3 skip() — 바이트 건너뛰기

public long skip(long n) throws IOException;

// 매개변수: 건너뛸 바이트 수
// 반환: 실제 건너뛴 바이트 수

// 활용
try (FileInputStream fis = new FileInputStream("file.txt")) {
    fis.skip(100);   // 처음 100바이트 건너뛰기
    
    int b = fis.read();   // 101번째 바이트부터
}

7.4 skip 의 함정

// 함정: 요청한 만큼 다 건너뛸 보장 X
long skipped = fis.skip(1000);
// skipped < 1000 가능
// EOF 도달, 또는 일부만

// 정확히 n 바이트 건너뛰기
public static void skipExactly(InputStream is, long n) throws IOException {
    long remaining = n;
    while (remaining > 0) {
        long skipped = is.skip(remaining);
        if (skipped <= 0) {
            // skip 못 함 — read 로 대체
            if (is.read() == -1) {
                throw new EOFException();
            }
            remaining--;
        } else {
            remaining -= skipped;
        }
    }
}

// Java 12+ skipNBytes
fis.skipNBytes(1000);   // 정확히 1000, 부족하면 예외

7.5 mark/reset — FileInputStream 은 미지원

// FileInputStream 은 mark/reset 미지원
FileInputStream fis = new FileInputStream("file.txt");
fis.markSupported();   // false

fis.mark(100);   // 효과 없음
fis.reset();      // IOException

// 이유:
// - 파일은 일반적으로 seek 가능
// - 하지만 InputStream API 의 단순성
// - 필요시 RandomAccessFile 또는 FileChannel

// mark/reset 이 필요하면 BufferedInputStream 으로 감싸기
BufferedInputStream bis = new BufferedInputStream(fis);
bis.markSupported();   // true
bis.mark(100);          // 가능
// ... read ...
bis.reset();            // 다시 mark 위치로

7.6 FileChannel 변환

// FileInputStream → FileChannel (NIO)
try (FileInputStream fis = new FileInputStream("file.txt")) {
    FileChannel channel = fis.getChannel();
    
    // position 이동
    channel.position(100);
    
    // ByteBuffer 활용
    ByteBuffer buf = ByteBuffer.allocate(1024);
    channel.read(buf);
}

// 활용:
// - Random Access
// - Memory-mapped file
// - zero-copy (transferTo)

7.7 readAllBytes / transferTo (Java 9+)

// Java 9+ — InputStream 의 새 메서드

// readAllBytes: 모두 한 번에
try (FileInputStream fis = new FileInputStream("small.txt")) {
    byte[] all = fis.readAllBytes();
    // 작은 파일만 (큰 파일은 OOM)
}

// transferTo: 다른 OutputStream 으로 전송
try (FileInputStream fis = new FileInputStream("src.txt");
     FileOutputStream fos = new FileOutputStream("dest.txt")) {
    long copied = fis.transferTo(fos);
    // 효율적 복사
}

7.8 ILIC 의 활용

public class ShipmentBinaryReader {
    
    // 1. 헤더 건너뛰고 본문만 읽기
    public byte[] readContent(Path file) throws IOException {
        try (FileInputStream fis = new FileInputStream(file.toFile())) {
            fis.skip(128);   // 헤더 128바이트 건너뛰기
            return fis.readAllBytes();
        }
    }
    
    // 2. 특정 위치의 데이터 읽기
    public byte[] readChunk(Path file, long offset, int length) throws IOException {
        try (FileInputStream fis = new FileInputStream(file.toFile())) {
            fis.skipNBytes(offset);   // Java 12+
            byte[] buf = new byte[length];
            int n = fis.read(buf);
            return Arrays.copyOf(buf, n);
        }
    }
    
    // 3. 효율적 복사 (transferTo)
    public void backup(Path src, Path dest) throws IOException {
        try (FileInputStream fis = new FileInputStream(src.toFile());
             FileOutputStream fos = new FileOutputStream(dest.toFile())) {
            fis.transferTo(fos);
        }
    }
    
    // 4. FileChannel 활용
    public byte[] readWithChannel(Path file, long offset, int length) throws IOException {
        try (FileInputStream fis = new FileInputStream(file.toFile())) {
            FileChannel channel = fis.getChannel();
            ByteBuffer buf = ByteBuffer.allocate(length);
            channel.read(buf, offset);
            buf.flip();
            byte[] result = new byte[buf.remaining()];
            buf.get(result);
            return result;
        }
    }
}

7.9 자기 점검 답변

FileInputStream 의 추가 메서드는?

:
1. available():

  • 즉시 읽기 가능한 바이트
  • 파일 크기는 별도 (Files.size)
  1. skip(long n):

    • 바이트 건너뛰기
    • 실제 건너뛴 수 반환
    • skipNBytes (Java 12+) 정확히
  2. mark/reset:

    • FileInputStream 미지원
    • BufferedInputStream 으로 감싸기
  3. getChannel():

    • FileChannel 변환 (NIO)
    • Random Access, 메모리 매핑
  4. Java 9+:

    • readAllBytes (작은 파일)
    • transferTo (복사)

8️⃣ NIO.2 의 대안과 실무 패턴

8.1 NIO.2 의 대안

// java.io 의 FileInputStream
FileInputStream fis = new FileInputStream("file.txt");

// NIO.2 의 Files.newInputStream
InputStream is = Files.newInputStream(Path.of("file.txt"));

// 둘 다 InputStream 으로 작동
// 차이:
// - FileInputStream: 구체 클래스 (FileChannel 등 추가 메서드)
// - Files.newInputStream: 인터페이스 반환 (다형성 강조)

8.2 Files 의 풍부한 메서드

// 다양한 읽기 방법
Path path = Path.of("file.txt");

// 1. 모든 바이트
byte[] all = Files.readAllBytes(path);

// 2. 모든 텍스트
String text = Files.readString(path);
String text2 = Files.readString(path, StandardCharsets.UTF_8);

// 3. 모든 줄
List<String> lines = Files.readAllLines(path);
List<String> lines2 = Files.readAllLines(path, StandardCharsets.UTF_8);

// 4. Stream (lazy)
try (Stream<String> lines = Files.lines(path)) {
    lines.forEach(System.out::println);
}

// 5. 일반 InputStream
try (InputStream is = Files.newInputStream(path)) {
    // 처리
}

// 6. BufferedReader
try (BufferedReader reader = Files.newBufferedReader(path)) {
    // 처리
}

8.3 옵션 활용

// 다양한 옵션
InputStream is = Files.newInputStream(
    Path.of("file.txt"),
    StandardOpenOption.READ);   // 기본

// 추가 옵션
StandardOpenOption.READ           // 읽기
StandardOpenOption.WRITE          // 쓰기
StandardOpenOption.APPEND         // 이어쓰기
StandardOpenOption.TRUNCATE_EXISTING
StandardOpenOption.CREATE
StandardOpenOption.CREATE_NEW
StandardOpenOption.DELETE_ON_CLOSE
StandardOpenOption.SPARSE
StandardOpenOption.SYNC
StandardOpenOption.DSYNC

8.4 명확한 예외

// java.io
try {
    FileInputStream fis = new FileInputStream("file.txt");
} catch (FileNotFoundException e) {
    // 모호: 파일 없음? 권한 없음? 디렉토리?
}

// NIO.2
try {
    InputStream is = Files.newInputStream(Path.of("file.txt"));
} catch (NoSuchFileException e) {
    // 파일 명확히 없음
} catch (AccessDeniedException e) {
    // 권한 명확히 없음
} catch (NotDirectoryException e) {
    // 디렉토리 관련 명확
} catch (IOException e) {
    // 기타
}

8.5 실무 권장 패턴

실무 권장:

1. 작은 텍스트 파일 (< 10MB):
   String content = Files.readString(path, UTF_8);

2. 작은 바이너리:
   byte[] data = Files.readAllBytes(path);

3. 큰 텍스트 (Stream):
   try (Stream<String> lines = Files.lines(path, UTF_8)) {
       lines.forEach(...);
   }

4. 큰 바이너리 (chunk):
   try (InputStream is = Files.newInputStream(path)) {
       byte[] buf = new byte[8192];
       int n;
       while ((n = is.read(buf)) != -1) {
           process(buf, 0, n);
       }
   }

5. 한 줄씩 (BufferedReader):
   try (BufferedReader br = Files.newBufferedReader(path, UTF_8)) {
       String line;
       while ((line = br.readLine()) != null) {
           process(line);
       }
   }

8.6 옛 코드 마이그레이션

// 옛 코드 (java.io)
public byte[] readOld(String path) throws IOException {
    FileInputStream fis = null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
        fis = new FileInputStream(path);
        byte[] buf = new byte[8192];
        int n;
        while ((n = fis.read(buf)) != -1) {
            baos.write(buf, 0, n);
        }
        return baos.toByteArray();
    } finally {
        if (fis != null) fis.close();
    }
}

// 중간 (try-with-resources)
public byte[] readMiddle(String path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path);
         ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        byte[] buf = new byte[8192];
        int n;
        while ((n = fis.read(buf)) != -1) {
            baos.write(buf, 0, n);
        }
        return baos.toByteArray();
    }
}

// 새 코드 (NIO.2)
public byte[] readNew(String path) throws IOException {
    return Files.readAllBytes(Path.of(path));
}

8.7 InputStream 추상화 활용

// 함수가 InputStream 받음 — 다양한 소스 처리
public byte[] processStream(InputStream is) throws IOException {
    return is.readAllBytes();
}

// 다양한 호출
// 파일
try (InputStream is = Files.newInputStream(Path.of("file.txt"))) {
    processStream(is);
}

// 메모리
processStream(new ByteArrayInputStream("data".getBytes()));

// 네트워크
URL url = new URL("https://example.com");
try (InputStream is = url.openStream()) {
    processStream(is);
}

// JAR 리소스
try (InputStream is = MyClass.class.getResourceAsStream("/data.txt")) {
    processStream(is);
}

// 모두 InputStream 으로 통일

8.8 ILIC 의 현대 패턴

@Service
public class ShipmentFileService {
    
    private final Path baseDir = Path.of("/var/shipment");
    
    // 1. 작은 파일
    public String readSmallFile(String filename) throws IOException {
        return Files.readString(baseDir.resolve(filename), StandardCharsets.UTF_8);
    }
    
    // 2. 큰 파일 — Stream
    public long countLines(String filename) throws IOException {
        try (Stream<String> lines = Files.lines(baseDir.resolve(filename))) {
            return lines.count();
        }
    }
    
    // 3. 처리 함수
    public <R> R processFile(String filename, Function<InputStream, R> processor) 
            throws IOException {
        try (InputStream is = Files.newInputStream(baseDir.resolve(filename))) {
            return processor.apply(is);
        }
    }
    
    // 4. 명확한 에러 처리
    public byte[] readWithErrorHandling(String filename) {
        Path path = baseDir.resolve(filename);
        
        try {
            return Files.readAllBytes(path);
        } catch (NoSuchFileException e) {
            log.warn("File not found: {}", filename);
            return new byte[0];
        } catch (AccessDeniedException e) {
            log.error("Access denied: {}", filename);
            throw new SecurityException("Cannot read: " + filename);
        } catch (IOException e) {
            log.error("I/O error: {}", filename, e);
            throw new RuntimeException(e);
        }
    }
    
    // 5. 다중 소스
    public byte[] readAnySource(String source) throws IOException {
        InputStream is;
        if (source.startsWith("http")) {
            is = new URL(source).openStream();
        } else if (source.startsWith("classpath:")) {
            is = getClass().getResourceAsStream(source.substring(10));
        } else {
            is = Files.newInputStream(Path.of(source));
        }
        
        try (is) {
            return is.readAllBytes();
        }
    }
}

8.9 자기 점검 답변

NIO.2 의 대안과 실무 패턴은?

:
1. 대안:

  • Files.newInputStream(Path)
  • Files.readAllBytes(Path)
  • Files.readString(Path, Charset)
  • Files.lines(Path)
  1. 장점:

    • Path 와 결합
    • 명확한 예외 (NoSuchFileException 등)
    • Stream 통합
    • 간결한 API
  2. 권장 패턴:

    • 작은 파일: readAllBytes/readString
    • 큰 텍스트: Files.lines (Stream)
    • 큰 바이너리: newInputStream + 버퍼
    • 한 줄씩: newBufferedReader
  3. 추상화:

    • InputStream 으로 다양한 소스 처리
    • 파일, 메모리, 네트워크, 리소스

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
FileInputStream 정의?InputStream 의 파일 전용 구현
5가지 생성자?String, File, FileDescriptor + 변형
read() 반환?0~255 또는 -1 (EOF)
-1 이유?byte 와 충돌 회피
1바이트씩 비효율?system call 폭증
FileNotFoundException?IOException 의 자식, 생성자
자원 누수?close 누락, OS 한도
try-with-resources?자동 close, AutoCloseable
available()?즉시 읽기 가능 (대략)
skip()?바이트 건너뛰기
mark/reset?FileInputStream 미지원
getChannel()?FileChannel 변환 (NIO)
Files.newInputStream?NIO.2 대안

9.2 자기 점검 체크리스트

기본

  • FileInputStream 의 정의
  • InputStream 계층
  • 5가지 생성자

read

  • read() 의 동작
  • 반환값 (0~255, -1)
  • read(byte[]) 의 반환

EOF

  • -1 의 이유 (byte 충돌)
  • 처리 패턴
  • EOF vs 예외

예외

  • FileNotFoundException
  • IOException
  • NIO.2 의 명확한 예외

자원 관리

  • 파일 핸들의 자원성
  • try-with-resources
  • AutoCloseable

추가 메서드

  • available()
  • skip(), skipNBytes (Java 12+)
  • mark/reset 미지원
  • getChannel(), transferTo

NIO.2

  • Files.newInputStream
  • Files.readAllBytes
  • Files.lines

9.3 추가 심화 질문

Q1: FileInputStream 의 finalize 가 deprecated 된 이유?

답:

// Java 9+ 에서 deprecated
@Deprecated(since = "9")
protected void finalize() throws IOException {
    // 옛 자동 close 메커니즘
}
  • finalize 는 신뢰성 X (GC 가 늦으면)
  • 자원 누수 위험
  • try-with-resources 가 표준
  • Java 9+ 부터 deprecated, Java 18+ 부터 제거 가능성

Q2: 큰 파일을 readAllBytes 하면?

답:

  • 메모리에 한 번에 로드
  • 큰 파일 (수 GB) → OutOfMemoryError
  • Integer.MAX_VALUE 바이트 제한
  • 큰 파일은 Stream 또는 byte[] 청크
// 작은 파일만
if (Files.size(path) < 100_000_000) {   // 100MB
    return Files.readAllBytes(path);
}
// 큰 파일은 Stream 처리

Q3: FileInputStream 과 RandomAccessFile?

답:

  • FileInputStream: 순차 읽기, 단방향
  • RandomAccessFile: 임의 위치, 양방향
  • RandomAccessFile 은 더 유연
  • 하지만 InputStream 인터페이스 아님
  • 현대는 FileChannel 권장

Q4: read() 가 음수 반환할 수 있나? (-1 외)?

답:

  • 표준 InputStream.read() 는 0~255 또는 -1 만
  • byte 의 -128 ~ -1 (음수) 는 0xFF ~ 0x80 으로 매핑
  • 즉, int 의 음수는 -1 (EOF) 만
  • 사용자 정의 InputStream 도 이 계약 지켜야

Q5: FileInputStream 으로 디렉토리 열면?

답:

File dir = new File("/var");   // 디렉토리
FileInputStream fis = new FileInputStream(dir);
// FileNotFoundException
// "Is a directory" (Linux) 또는 비슷한 메시지
  • FileNotFoundException 던짐
  • FileInputStream 은 파일 전용
  • 디렉토리 순회는 File.listFiles 또는 Files.list

🎯 핵심 요약 — 3줄 정리

1. FileInputStream

  • InputStream 의 파일 전용 구현
  • 1바이트씩 read (0~255, -1 EOF)
  • try-with-resources 필수

2. -1 의 이유

  • byte 의 0xFF (=-1) 와 충돌
  • int 로 확장하여 명확히 구분
  • EOF 와 정상 데이터 분리

3. 권장 패턴

  • 새 코드: Files.newInputStream(Path) 또는 Files.readAllBytes(Path)
  • 큰 파일: Stream API (Files.lines)
  • 명확한 예외 처리 (NoSuchFileException 등)

📚 다음으로...

Unit 8.3 — byte[] 배열로 효율적 읽기

이번 Unit에서 1바이트씩 read 의 비효율을 봤다면, 다음은 byte[] 활용으로 효율화.

  • read(byte[]) 의 활용
  • 버퍼 크기 선택
  • 마지막 읽기 함정
  • BufferedInputStream 과의 비교

Phase 8 진행 상황

🚀 Phase 8 — Stream 실전
  ✅ Unit 8.1 System.in (한글 안 되는 이유)
  ✅ Unit 8.2 FileInputStream ← 여기
  ⏭ Unit 8.3 byte[] 배열로 효율적 읽기
  ⏭ Unit 8.4 FileOutputStream
  ⏭ Unit 8.5 한글 처리 (FileReader, InputStreamReader)
  ⏭ Unit 8.6 FileWriter (한글 쓰기)

3주차 누적 진행

✅ Phase 1 ~ 7 완주 (31 Unit)
🚀 Phase 8 — Stream 실전 (2/6 진행)

총: 33/43 Unit (약 77%)
profile
Software Developer

0개의 댓글