본문 바로가기
공부/이펙티브코틀린

아이템 9 - use 를 사용하여 리소스를 닫아라

by 띵커베르 2024. 4. 7.
728x90
  • 더 이상 필요하지 않을때, close 메서드를 사용해서 명시적으로 닫아야 하는 리소스가 있다 
    • InputStream / OutputStream
    • java.sql.Connection
    • java.io.Reader
    • java.new.Socket / java.uti.Scanner
    • 등등
  • 이러한 리소스들은 AutoCloseable 을 상속받은 Closeable 인터페이스를 구현하고 있다.이러한 모든 리소스는 최종적으로 리소스에 대한 레퍼런스가 없어질 때, 가바지 컬렉터가 처리한다. 하지만, 굉장히 느리며 그동안 리소스를 유지하는 비용이 많이 들어간다.
    • public abstract class InputStream implements Closeable {...}
    • public interface Closeable extends AutoCloseable {
          public void close() throws IOException;
      }
  • 기존구현은 try-finally 방식으로 많이 처리했지만, 이러한 코드는 굉장히 복잡하고 좋지 않다.
    • 리소스를 닫을 때 예외가 발생할 수도 있는데, 이러한 예외를 따로 처리하지 않기 때문이다.
    • 또한 try 블록과 finally 블록 내부에서 오류가 발생하면, 둘 중 하나만 전파된다. 둘다 전파될 수 있다면 좋을 것이지만, 이를 직접 구현하려면 코드가 굉장히 길고 복잡해진다.
  • use 키워드를 사용하여 close 하자.
  • 파일을 한 줄 씩 처리할 때 활용할 수 있는 useLines 함수도 제공한다 -> 메모리에 파일의 내용을 한 줄씩만 유지하므로, 대용량 파일도 적절하게 처리할 수 있다. 다만 파일의 줄을 한 번만 사용할 수 있다는 단점이 있다

 


  • use
    • 파일 전체를 한번에 메모리에 로드해야 하는 경우(ex: 전체파일을 String 으로 처리할 경우 등)
    • 바이너리 파일을 다루는 경우
    • 파일 내 특정 위치로 빠르게 접근하거나, 랜덤 엑세스가 필요한 경우(useLines 는 순차 처리에 최적화 되어 있음)
  • uselines
    • 파일의 모든 라인을 순차적으로 처리할 때
    • 파일의 크기가 크고, 메모리에 파일 전체를 한 번에 로드하지 못할 때
=== test Text
    Kotlin은 정적 타입 지정 언어입니다.
    Kotlin은 JVM에서 실행됩니다.
=== test Text

// use
@Test
fun useTest5() {
    readFileWithUse("/path")
    // ===== use =====
    // Kotlin은 정적 타입 지정 언어입니다.
    // Kotlin은 JVM에서 실행됩니다.
}

fun readFileWithUse(filePath: String) {
    FileInputStream(filePath).use { inputStream ->
        println("===== use =====")
        inputStream.bufferedReader().use { reader ->
            reader.lines().forEach { println(it) }
        }
    }
}

// useLines
@Test
fun useTest6() {
    readFileWithUseLines("/path")
    
    // ===== useLines =====
    // Kotlin은 정적 타입 지정 언어입니다.
    // ===== useLines =====
    // Kotlin은 JVM에서 실행됩니다.
}

fun readFileWithUseLines(filePath: String) {
    File(filePath).useLines { lines ->
        lines.forEach {
            println("===== useLines =====")
            println(it)
        }
    }
}

 

  • lineSequence
    • useLinse 와 비슷하지만 좀 더 세밀한 제어가 필요하다면 use 와 lineSequence 를 사용할 수 있다
    • lineSequence 은 자동으로 닫히지 않기때문에 use 와 함께 사용한다
@Test
fun useTest7() {
    readFileWithLinesSequence(path)  // Found an kotlin line: KOTLIN은 정적 타입 지정 언어입니다.
}

fun readFileWithLinesSequence(filePath: String) {
    val bufferedReader = File(path).bufferedReader()
    bufferedReader.use { reader ->
        val lineSequence = reader.lineSequence()
        val result = lineSequence.map { it.uppercase(Locale.getDefault()) }
            .find { it.startsWith("KOTLIN") }

        if (result != null) {
            println("Found an kotlin line: $result")
        }
    }
}

 

 

참고:

  • 직접 리소스 관리 vs GC 에 의해 관리
    • 운영체제의 커널 API를 호출하여 리소스를 사용한다면, 이러한 리소스의 생명주기를 개발자가 직접 관리해야 합니다.

 

생각:

  • use, useLinses 는 가끔 써봐서 알고 있었고 lineSequence 에 대한 이점을 좀 더 생각해 볼 수 있었다.
  • 데이터 베이스 연결, http 통신시 등에서 라이브러리가 리소스 해제를 잘 해주는것에 감사함을 느꼈다
  • 어쩌다 한번 쓰는 api 에 대해서는 책에 적혀있듯이 AutoCloseable 을 상속받은 Closeable 인터페이스를 구현하고 있다면 한번 더 리소스 해제를 해주고 있는지 혹은 직접 해야하는지 확인을 해보는게 좋을것 같다.
    • AutoCloseable과 Closeable 인터페이스를 구현한다는 것은 해당 객체를 자동으로 리소스를 해제할 수 있는 능력을 갖추고 있다는것을 의미한다.
728x90

댓글