BufferedReader를 닫아볼까?

yeezze·2022년 11월 14일
3

흥미로운 문제

오늘 스크럼 시간에 다른 팀원이 어제 본인이 겪은 오류에 대해서 이야기하면서 에러가 왜 나는지 잘 이해가 안된다고 얘기해주었다!

디버깅을 다같이 하면서 BufferedReader.close()에서 에러가 발생하는걸 확인하고 close()를 실행하면 안되나봐! 안 닫으면 되겠네! 하고 넘어갔는데 궁금해져서 혼자 내부 코드를 무한 디버깅하면서 알아보았다. 😵‍💫

에러 발생 코드

개발 중인 프로젝트는 Java 콘솔 프로그램이다.
Console 객체 내에 사용자의 키보드 입력값을 읽는 read() 메소드를 만들었다.

에러 공유

Q : 읽어야할 문자열을 다 읽고나면 BufferedReader를 닫아줬는데, 그 이후에 다시 새로운 문자열을 읽으려고 readLine() 메소드를 실행시키면 에러가 나요! 왜 닫으면 안되요? 왜 다시 안 읽어줘요?

나 : 그러게요 왜일까요 😋 모르겠당 한번 알아보자


먼저 우리는 BufferedReader를 이렇게 생성한다.

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

InputStreamReader는 스트림 값을 읽어온다.
System.in 객체는 사용자의 키보드 인풋값을 받아오는 Reader라는 인터페이스이다. 해당 객체는 개발자가 열지 않아도 JVM에 의해 항상 열려있다.

close를 하면 어떻게 될까?

한번 따라가보자!

  • BufferedReader.close()

  • InputStreamReader.close()

  • StreamDecoder

  • BufferedInputStream

여러 클래스를 거쳐 최종 목적지인 BufferedInputStream에 도착해서 close() 로직을 실행하게 된다.

여기서 코드를 보면 InputStream 인터페이스와 byte[] buf를 null로 바꿔주면서 System.in 객체가 null이 된다.

그리고 StreamDecoder의 close 변수도 true로 변한다.
이로써 BufferedReader 클래스의 in(br의 필드인 Reader 객체)과 char[] (사용자로부터 입력받은 내용의 byte 배열을 변환하여 저장하는) 배열 또한 null로 변한다.

그 다음! BufferedReader 새로 생성!

닫은 후에 새로운 BufferedReader 객체를 생성한다고 해보자.

한번 더 BufferedReader를 이렇게 생성한다.

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

여기서 System.in에 무엇이 들어갈까?

그렇다 방금 close를 실행했던 최종 목적지인 BufferedInputStream이 들어간다.


우리가 아까 닫아줬기 때문에 buf, in 모두 null이다.

InputStream in;
InputStream 인터페이스가 null이다. 인풋값을 읽어서 저장하는 버퍼도 null이다.

자 이제 누가 사용자의 키보드 인풋값을 읽어줄 것인가? 어디에 저장할 것인가?

새로 생성된 BufferedReader 객체를 보자

스크럼시간에 팀원들과 대충 이야기할 때는 InputStreamReader 생성 자체가 안되는 건줄 알았다. 자세히 보니 Reader 객체는 생성이 잘되었다.

하지만 그 안에 진짜 사용자의 키보드 값, 스트림값을 읽어오는 역할인! 동기화가 실행되는!
객체 BufferedInputStream 안의 InputStream가 null이다.
왜냐면! 아까 닫았으니까 ^^

BufferedReader class 새로 생성 완료됨 👌
InputStreamReader class 새로 생성 완료됨 👌
BufferedInputStream class 새로 생성 완료됨 👌
InputStream interface = null 😱

그래도 BufferedReader 객체는 생성됐으니까 읽어오라고 시켜보자


read() 메소드 안에 stream 인터페이스가 닫혀 있는지 체크하는 메소드가 맨 처음에 있다.


byte[] 배열 buf 변수를 체크한다.
당연히 null에 걸려서 IOException("Stream closed")를 뱉어버린다.

왜냐면? close()에서 닫았으니까~!~! 🎙

BufferedReader만 잠깐 닫았다가 새로 생성하면 다시 새롭게 읽어오는 객체가 열리겠다고 예상하고 코드를 짰지만 사실은 그게 아니였던 것이다.

해결

어떻게 해결해야할까?
검색하다가 본 페이지에 해결책이 나와있었다.

1. close()를 하지말자

Reason for not doing any of the above: Since the System.in object is opened by the JVM, you should leave it to the JVM to close it. If you close it and later on try to use System.in for any other reason, then you'll probably be surprised that you no longer can. Therefore, when it comes to closing a Scanner that is tied to System.in, DON'T. Leaving the closing of System.in to the JVM.

-> 해석
System.in 개체는 JVM에 의해 열리기 때문에 닫으려면 JVM에 맡겨야 합니다. 닫은 후 나중에 다른 이유로 System.in을 사용하려고 하면 더 이상 사용할 수 없다는 사실에 놀랄 것입니다. 따라서 System.in에 연결된 스캐너를 닫을 때는 닫지 마십시오. System.in의 종료를 JVM에 맡깁니다.

맞다.
System.in은 원래 열려있는데 닫아야하는 이유가?

사실 Java에서 BufferedReader는 사용하지 않으면 GC의 대상이 되기 때문에 개발자가 직접 close() 해주지 않아도 된다.

단, BufferedWriter의 경우 writing을 끝낸 후 적절하게 close()를 해주지 않는다면 정상적으로 writing이 되지 않는 경우가 많다고 한다.

참고 페이지에서 본 글

해당 강의에서 제가 close()를 해주어야 한다고 명시한 이유는, 자원 관리의 중요성 때문입니다. 자바에서는 자원관리를 쉽게 해주는 Garbage Collector가 있기 때문에 큰문제가 되지 않을 수 있습니다. Native언어의 경우 자원관리를 프로그래머가 직접 해야하는 경우가 많기 때문에, 자원관리를 해주는 습관을 들이는 것이 좋습니다.

2. static으로 선언하자

Declare the Scanner variable as a class (static) field. You will not get a warning about closing the Scanner(System.in) resource if you declare it as a class field. This makes the most sense because you really only need to create a Scanner that's tied to System.in one time and use it for all console input. After all, there's only one keyboard, so why would you need more than one Scanner to get input from it? So, your code would look like this:

흥미로웠던 말이었는데

-> 해석
System.in에 연결된 스캐너를 한 번만 만들어(static) 모든 콘솔 입력에 사용하면 되므로 이 방법이 가장 타당합니다. 결국 키보드는 하나뿐인데, 왜 입력을 받기 위해 스캐너가 둘 이상 필요합니까?

생각해보면 맞는 말이다.


나는 Scanner로 코드를 작성해서 이런 이슈가 없었는데 팀원분 덕분에 새롭게 알게된 것이 많아서 재밌었다.
결론적으로 close를 굳이 안 해도 될 것 같다.
콘솔 프로그램이기 때문에 이런 문제가 발생한 것 같은데 실제로 실무에서 BufferedReader를 자주 쓰나? 모르겠다
멘토님한테 나중에 여쭤봐야지
할게 많은데 이거 쳐다보는데 너무 시간을 많이 쓴 것 같아서 조금 아찔😂하긴 하지만 재밌었다!

틀린 내용은 알려주세욥

끝-

참고 페이지
https://coderanch.com/wiki/678613/Don-close-Scanner-tied-System
https://edu.goorm.io/qna/4699

profile
백엔드 개발자 😊

2개의 댓글

comment-user-thumbnail
2022년 11월 14일

오 감사합니다~!

1개의 답글