[java] 중첩 된 각 OutputStream과 Writer를 개별적으로 닫아야합니까?

코드를 작성 중입니다.

OutputStream outputStream = new FileOutputStream(createdFile);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(gzipOutputStream));

다음과 같이 모든 스트림 또는 작가를 닫아야합니까?

gzipOutputStream.close();
bw.close();
outputStream.close();

아니면 마지막 스트림을 닫는 것이 좋을까요?

bw.close();



답변

모든 스트림 bw이 정상적으로 생성되었다고 가정하면, 해당 스트림 구현에서는 닫는 것이 좋습니다 . 그러나 그것은 큰 가정입니다.

리소스를 사용하여 try ( tutorial )를 사용하여 예외를 throw하는 후속 스트림을 구성하는 모든 문제가 이전 스트림을 중단시키지 않으므로 종료 호출을 갖는 스트림 구현에 의존 할 필요가 없습니다. 기본 스트림 :

try (
    OutputStream outputStream = new FileOutputStream(createdFile);
    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
    OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
    BufferedWriter bw = new BufferedWriter(osw)
    ) {
    // ...
}

더 이상 전화하지 않습니다 close.

중요 사항 : 자원으로 시도를 닫으 려면 스트림을 열 때 변수에 스트림을 지정 해야 합니다. 중첩을 사용할 수 없습니다. 중첩을 사용하는 경우 이후 스트림 중 하나 (예 :)를 생성하는 동안 예외가 발생 GZIPOutputStream하면 중첩 된 호출에 의해 생성 된 모든 스트림이 열린 채로있게됩니다. 에서 §14.20.3 JLS :

try-with-resources 문은 블록을 실행하기 전에 초기화되고 블록을 실행 한 후 초기화 된 역순으로 자동으로 닫히는 변수 (자원이라고 함)로 매개 변수화됩니다 .trytry

“variables” (내 강조) 라는 단어를 주목하십시오 .

예를 들어, 이렇게하지 마십시오 :

// DON'T DO THIS
try (BufferedWriter bw = new BufferedWriter(
        new OutputStreamWriter(
        new GZIPOutputStream(
        new FileOutputStream(createdFile))))) {
    // ...
}

GZIPOutputStream(OutputStream)생성자 의 예외 (던질 수도 IOException있고 기본 스트림에 헤더를 씁니다)가 FileOutputStream열려 있기 때문 입니다. 일부 자원에는 던질 수도 있고 그렇지 않은 생성자가 있기 때문에 별도로 나열하는 것이 좋습니다.

이 프로그램으로 JLS 섹션에 대한 해석을 다시 확인할 수 있습니다.

public class Example {

    private static class InnerMost implements AutoCloseable {
        public InnerMost() throws Exception {
            System.out.println("Constructing " + this.getClass().getName());
        }

        @Override
        public void close() throws Exception {
            System.out.println(this.getClass().getName() + " closed");
        }
    }

    private static class Middle implements AutoCloseable {
        private AutoCloseable c;

        public Middle(AutoCloseable c) {
            System.out.println("Constructing " + this.getClass().getName());
            this.c = c;
        }

        @Override
        public void close() throws Exception {
            System.out.println(this.getClass().getName() + " closed");
            c.close();
        }
    }

    private static class OuterMost implements AutoCloseable {
        private AutoCloseable c;

        public OuterMost(AutoCloseable c) throws Exception {
            System.out.println("Constructing " + this.getClass().getName());
            throw new Exception(this.getClass().getName() + " failed");
        }

        @Override
        public void close() throws Exception {
            System.out.println(this.getClass().getName() + " closed");
            c.close();
        }
    }

    public static final void main(String[] args) {
        // DON'T DO THIS
        try (OuterMost om = new OuterMost(
                new Middle(
                    new InnerMost()
                    )
                )
            ) {
            System.out.println("In try block");
        }
        catch (Exception e) {
            System.out.println("In catch block");
        }
        finally {
            System.out.println("In finally block");
        }
        System.out.println("At end of main");
    }
}

… 출력이 있습니다.

구성 예 $ InnerMost
예제 작성 $ Middle
구성 예 $ OuterMost
캐치 블록에서
마지막으로 차단
메인의 끝에서

거기에 대한 호출이 없습니다 close.

우리가 고치면 main:

public static final void main(String[] args) {
    try (
        InnerMost im = new InnerMost();
        Middle m = new Middle(im);
        OuterMost om = new OuterMost(m)
        ) {
        System.out.println("In try block");
    }
    catch (Exception e) {
        System.out.println("In catch block");
    }
    finally {
        System.out.println("In finally block");
    }
    System.out.println("At end of main");
}

그런 다음 적절한 close전화를 받습니다 .

구성 예 $ InnerMost
예제 작성 $ Middle
구성 예 $ OuterMost
예 $ 중간 마감
예 : $ InnerMost 마감
예 : $ InnerMost 마감
캐치 블록에서
마지막으로 차단
메인의 끝에서

(예,에 대한 두 번의 전화 InnerMost#close가 정확합니다. 하나는에서오고, 다른 하나는 Middletry-with-resources에서 왔습니다.)


답변

가장 바깥 쪽 스트림을 닫을 수 있습니다. 실제로 모든 스트림을 랩핑 할 필요는 없으며 Java 7 try-with-resources를 사용할 수 있습니다.

try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
                     new GZIPOutputStream(new FileOutputStream(createdFile)))) {
     // write to the buffered writer
}

YAGNI 또는 you-aint-gonna-need-it에 가입 한 경우 실제로 필요한 코드 만 추가해야합니다. 필요하다고 생각되는 코드를 추가해서는 안되지만 실제로는 유용한 기능이 없습니다.

이 예를 들어 보지 않으면 어떻게 될 수 있으며 어떤 영향을 미칠지 상상해보십시오.

try (
    OutputStream outputStream = new FileOutputStream(createdFile);
    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
    OutputStreamWriter osw = new OutputStreamWriter(gzipOutputStream);
    BufferedWriter bw = new BufferedWriter(osw)
    ) {
    // ...
}

open모든 실제 작업을 수행하기 위해 호출 하는 FileOutputStream으로 시작할 수 있습니다.

/**
 * Opens a file, with the specified name, for overwriting or appending.
 * @param name name of file to be opened
 * @param append whether the file is to be opened in append mode
 */
private native void open(String name, boolean append)
    throws FileNotFoundException;

파일을 찾을 수 없으면 닫을 기본 리소스가 없으므로 파일을 닫아도 아무런 차이가 없습니다. 파일이 존재하면 FileNotFoundException이 발생해야합니다. 따라서이 줄에서만 리소스를 닫으려고해도 얻을 수있는 것이 없습니다.

파일을 닫아야하는 이유는 파일이 성공적으로 열렸지만 나중에 오류가 발생하기 때문입니다.

다음 스트림을 보자 GZIPOutputStream

예외를 던질 수있는 코드가 있습니다

private void writeHeader() throws IOException {
    out.write(new byte[] {
                  (byte) GZIP_MAGIC,        // Magic number (short)
                  (byte)(GZIP_MAGIC >> 8),  // Magic number (short)
                  Deflater.DEFLATED,        // Compression method (CM)
                  0,                        // Flags (FLG)
                  0,                        // Modification time MTIME (int)
                  0,                        // Modification time MTIME (int)
                  0,                        // Modification time MTIME (int)
                  0,                        // Modification time MTIME (int)
                  0,                        // Extra flags (XFLG)
                  0                         // Operating system (OS)
              });
}

파일의 헤더를 씁니다. 이제 파일을 쓰기 위해 열 수는 있지만 8 바이트도 쓸 수는 없지만, 이런 일이 발생할 수 있다고 생각하면 나중에 파일을 닫지 않습니다. 파일이 닫히지 않으면 어떻게됩니까?

플러시되지 않은 쓰기를 얻지 못하고 버리고이 경우 버퍼에 성공적으로 쓰여지지 않은 바이트가 스트림에 작성되지 않습니다. 그러나 닫히지 않은 파일은 영원히 살지 않고 FileOutputStream은

protected void finalize() throws IOException {
    if (fd != null) {
        if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
            flush();
        } else {
            /* if fd is shared, the references in FileDescriptor
             * will ensure that finalizer is only called when
             * safe to do so. All references using the fd have
             * become unreachable. We can call close()
             */
            close();
        }
    }
}

파일을 전혀 닫지 않으면 어쨌든 즉시 닫히지 않습니다 (그리고 내가 말했듯이 버퍼에 남아있는 데이터는 이런 식으로 손실되지만 현재는 없습니다)

파일을 즉시 닫지 않은 결과는 무엇입니까? 정상적인 조건에서는 일부 데이터가 손실 될 수 있으며 파일 설명자가 부족할 수 있습니다. 그러나 파일을 만들 수는 있지만 아무 것도 쓸 수없는 시스템이 있다면 더 큰 문제가 있습니다. 즉, 실패한 사실에도 불구하고 왜이 파일을 반복해서 작성하려고하는지 상상하기 어렵습니다.

OutputStreamWriter와 BufferedWriter는 생성자에서 IOException을 발생시키지 않으므로 어떤 문제가 발생하는지 명확하지 않습니다. BufferedWriter의 경우 OutOfMemoryError가 발생할 수 있습니다. 이 경우 GC를 즉시 트리거하여 어쨌든 파일을 닫습니다.


답변

모든 스트림이 인스턴스화되면 가장 바깥 쪽 만 닫는 것이 좋습니다.

Closeable인터페이스 에 대한 문서는 메소드를 닫는 상태를 나타냅니다.

이 스트림을 닫고 관련된 모든 시스템 리소스를 해제합니다.

해제 시스템 리소스에는 닫는 스트림이 포함됩니다.

또한 다음과 같이 말합니다.

스트림이 이미 닫혀 있으면이 메소드를 호출해도 효과가 없습니다.

따라서 나중에 명시 적으로 닫으면 아무런 문제가 발생하지 않습니다.


답변

차라리 try(...)구문 (자바 7)을 사용하고 싶습니다.

try (OutputStream outputStream = new FileOutputStream(createdFile)) {
      ...
}


답변

마지막 스트림 만 닫으면 괜찮습니다. 닫기 호출도 기본 스트림으로 전송됩니다.


답변

아니요, 최상위 수준 Stream이거나 reader모든 기본 스트림 / 리더가 닫혀 있는지 확인합니다 .

최상위 스트림 의 close()메소드 구현 을 확인하십시오 .


답변

Java 7에는 try-with-resources 기능 이 있습니다 . 스트림을 명시 적으로 닫을 필요는 없습니다.