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


127

코드를 작성 중입니다.

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();

1
해당되지 않는 Java 6 질문에 대해서는 stackoverflow.com/questions/884007/…
Raedwald

2
예제에는 스트림을 연 순서대로 닫지 않기 때문에 데이터 손실을 유발할 수있는 버그가 있습니다. 를 닫을 때 버퍼링 된 데이터를 기본 스트림 BufferedWriter에 작성해야 할 수도 있습니다 (이 예제에서는 이미 닫혀 있습니다). 이러한 문제를 피하는 것은 답변에 표시된 리소스로 시도 방식 의 또 다른 이점입니다 .
Joe23

답변:


150

모든 스트림 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에서 왔습니다.)


7
스트림을 생성하는 동안 예외가 발생할 수 있다는 점에 +1하지만 사실 현실적으로 메모리 부족 예외 또는 똑같이 심각한 문제가 발생한다는 점에 유의해야합니다 (이 시점에서는 실제로 중요하지 않습니다) 스트림을 닫으면 응용 프로그램이 종료되기 때문에) 또는 IOException을 발생시키는 GZIPOutputStream입니다. 나머지 생성자에는 확인 된 예외가 없으며 런타임 예외가 발생할 가능성이있는 다른 상황이 없습니다.
Jules

5
@Jules : 예, 이러한 특정 스트림에 대해서는 실제로입니다. 좋은 습관에 관한 것입니다.
TJ Crowder

2
@ PeterLawrey : 나는 나쁜 습관을 사용하는 것에 동의하지 않거나 스트림 구현에 의존하지 않습니다. :-) 이것은 YAGNI / no-YAGNI 구별이 아니며, 신뢰할 수있는 코드를 만드는 패턴에 관한 것입니다.
TJ Crowder 2019

2
@ PeterLawrey : 신뢰하지 않는 것에 대한 위의 것도 없습니다 java.io. 일부 스트림 (일반화, 일부 리소스) 은 생성자에서 발생합니다. 따라서 여러 리소스를 개별적으로 열어야 후속 리소스가 좋은 습관 일 경우 안정적으로 닫힐 수 있습니다. 당신 이 동의하지 않으면 그것을하지 않기로 선택할 수 있습니다 .
TJ Crowder 2019

2
@PeterLawrey : 따라서 예외를 문서화 한 경우에 대해 구현의 소스 코드를 살펴보고 "아, 실제로는 그렇지 않습니다. .. "를 입력하고 몇 글자를 입력 하시겠습니까? 우리는 회사에 큰 시간을 보냅니다. :-) 게다가, 방금 보았고 이것은 이론적이지 않습니다 : GZIPOutputStream의 생성자가 스트림에 헤더를 씁니다. 그리고 던질 수 있습니다. 따라서 이제는 글을 던진 후 스트림을 닫으 려고 노력할 가치가 있다고 생각합니다 . 예 : 열었습니다. 최소한 닫으려고 노력해야합니다.
TJ Crowder 2019

12

가장 바깥 쪽 스트림을 닫을 수 있습니다. 실제로 모든 스트림을 랩핑 할 필요는 없으며 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를 즉시 트리거하여 어쨌든 파일을 닫습니다.


1
이것이 실패 할 수있는 상황에 대해서는 TJ Crowder의 답변을 참조하십시오.
TimK

@TimK는 파일이 생성되는 위치의 예를 제공하지만 나중에 스트림이 실패하고 그 결과를 알려줍니다. 실패의 위험은 매우 낮으며 그 영향은 사소합니다. 필요 이상으로 더 복잡하게 만들 필요가 없습니다.
Peter Lawrey

1
GZIPOutputStream(OutputStream)문서 IOException를보고 소스를 보면 실제로 헤더를 씁니다. 따라서 이론적으로는 해당 생성자가 던질 수 없습니다. FileOutputStream던지기에 쓰는 후에 기본을 열어 두는 것이 좋다고 느낄 수 있습니다 . 난 아니야
TJ Crowder 2019

1
@TJCrowder 숙련 된 전문 자바 스크립트 개발자 (및 다른 언어를 사용하는 사람)는 누구나 모자를 벗습니다. 나는 그것을 할 수 없었다. ;)
Peter Lawrey

1
이것을 다시 방문하기 위해 다른 문제는 파일에서 GZIPOutputStream을 사용하고 finish를 명시 적으로 호출하지 않으면 밀접한 구현에서 호출된다는 것입니다. 이것은 시도에 있지 않습니다 ... 마침내 마무리 / 플러시에 예외가 발생하면 기본 파일 핸들이 닫히지 않습니다.
robert_difalco

6

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

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

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

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

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

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

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


2
이것은 스트림을 구성 하는 오류가 없다고 가정 하는데, 이는 열거 된 스트림에 대해서는 사실 일 수도 있고 아닐 수도 있지만 일반적으로 확실 하지는 않습니다 .
TJ Crowder 2019

6

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

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

4
동의하지만,이 방법의 이점을 강조하고 OP가 아동 / 내부 시내를 폐쇄해야하는 경우 그 요점에 대답 할 수 있습니다.
MadProgrammer

5

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


1
Grzegorz Żur의 답변에 대한 의견을 참조하십시오.
TJ Crowder 2019

5

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

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


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.