표준 함수와 파일 입출력 (2/2)

장똑대·2022년 5월 2일

Do it! 코틀린 프로그래밍 [셋째마당, 코틀린 표준 라이브러리의 활용] 학습

1-2. 코틀린의 표준 라이브러리

📌 apply() 함수

  • public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this}
  • 특정 객체를 생성하면서 함께 호출해야 하는 초기화 코드가 있는 경우 사용
  • 람다식이 확장 함수로 처리
fun main() {
	data class Person(var name: String, var skills: String)
    ver person = Person("Kildong", "Kotlin")
    
    val returnObj = person.apply {
    	this.name = "Sean" // this는 person 객체
        skills = "Java" // this 생략 가능
    }
}

📌 run() 함수

  • public inline fun <R> run(block: () -> R): R = return block()
  • public inline fun <T, R> T.run(block: T.() -> R): R = return block()
  • apply()와 똑같이 동작하지만 수신객체가 아닌 run블록의 마지막 라인을 return
fun main() {
    data class Person(var name: String, var skills: String)

    var person = Person("Kildong", "Kotlin")
    
    val returnObj = person.apply { 
        name = "Sean"
        skills = "Java"
        "success" //사용되지 않음, person객체 반환
    }
    
    val returnObj2 = person.run { 
        name = "Dooly"
        skills = "C#"
        "success" // 문자열 반환
    }
}

📌 with() 함수

  • public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
  • 인자로 받는 객체를 이어지는 block의 receiver로 전달하며 결괏값을 반환
  • run() 함수와 기능이 거의 동일한데, with()함수에서는 receiver로 전달할 객체를 처리하므로 객체의 위치가 달라짐
fun main() {
    data class Person(var name: String, var skills: String)

    var person = Person("Kildong", "Kotlin")

    val result = with(person) {
        name = "Sean"
        skills = "Java"
    }
}

📌 use() 함수

  • public inline fun <T: Closeable?, R> T.use(block: (T) -> R): R
  • 객체를 사용한 후 close() 함수를 자동적으로 호출해 닫아줌
  • T의 자료형을 보면 Cloaseable?로 block은 닫힐 수 있는 객체를 지정해야함
fun main() {
    PrintWriter(FileOutputStream("d:\\test\\output.txt")).use {
        it.println("hello")
    }
}

✏️2. 파일 입출력✏️

2-1. 표준 입출력의 기본 개념

📌readLine() 함수

fun main() {
    print("Enter: ")
    val input = readLine()!!
    println("You entered: $input")
}

-> 표준 입력 장치인 콘솔로부터 입력받아 input에 String형으로 할당
-> 입력에 실패할 경우 null 가능성이 생기기 때문에 !! 혹은 ?를 사용해 NPE 발생 여부 처리
-> 입력받은 값들은 문자열 값이 기본

📌 Kotlin의 입출력 API

처리 목적관련 자바 라이브러리
파일 처리java.io.File
바이트 단위의 입력 처리java.io.InputStream
바이트 단위의 출력 처리java.io.OutputStream
문자 기반 읽기 처리java.io.Reader
문자 기반 쓰기 처리java.io.Writer
버퍼를 가진 읽기 처리java.io.BufferedReader
  • 이러한 라이브러리는 파일이나 콘솔과 같은 스트림(Stream)에서 읽거나 쓸 수 있는 API 제공
    -> 스트림 : 데이터가 강물에 띄운 것처럼 흘러간다는 의미. 데이터가 머물러 있지 않고 전달되는 개념
  • InputStream, Reader, Writer를 쓸 때는 사용 완료 후 반드시 닫아주어야 함

📌 자바의 io, nio 개념

  • java.io : 입출력을 위한 기본적인 패키지
  • java.nio(New Input Output) : 기능이 대폭 확장된 패키지

  • java.io와 java.nio 비교
구분java.iojava.nio
입출력스트림(Stream)방식채널(Channel) 방식
버퍼 방식넌버퍼(Non-buffer)버퍼(Buffer)
비동기 지원지원 안 함(블로킹 방식)지원함(넌블로킹 지원)
  • 스트림 방식 : 발생한 데이터를 바로 전송
  • 채널 방식 : 여러 개의 수로를 사용해 병목 현상을 줄임
  • 버퍼는 송/수신 사이에 임시적으로 사용하는 공간이 있는지에 따라 결정
  • 공간이 있는 버퍼 방식은 좀 더 유연한 처리가 가능

📌 스트림과 채널

  • 스트림은 데이터가 흘러가는 방향성에 따라 입력 스트림과 출력 스트림으로 구분
  • 채널 방식은 양방향으로 입력과 출력이 모두 가능하기 때문에 입출력을 별도로 지정하지 않아도 됨

📌 넌버퍼와 버퍼 방식

  • 스트림 방식에서는 1바이트를 쓰면 입력 스트림이 1바이트를 읽음
    -> 버퍼를 사용해 다수의 데이터를 읽는 것보다 느리게 동작
  • nio에서는 기본적으로 버퍼를 사용하는 입출력을 하기 때문에 데이터를 일일이 읽는 것보다 나은 성능을 보여줌

📌 블로킹과 넌블로킹

  • 블로킹(Blocking) : 공간이 비워지거나 채워지기 전까지 쓰고 읽을 수 없기 때문에 호출한 코드에서 계속 멈춰 있는 것
    -> 스스로 빠져나올 수 없음
  • 넌블로킹(Non-blocking) : 메인 코드의 흐름을 방해하지 않도록 입출력 작업 시 스레드나 비동기 루틴에 맡겨 별개의 흐름으로 작업하게 되는 것
    -> 스스로 빠져나와 다른 작업을 진행할 수 있음
    -> 코드가 복잡해질 수 있음

2-2. 파일에 쓰기

📍 URI(Uniform Resource Identifier) : URL과 비슷하게 사용되는데 각종 자원의 위치를 식별하기 위해 사용하는 규약


📌 File의 PrintWriter

  • PrintWriter
    • 파일에 출력하는 메서드(print(), printf(), write() 등..)를 제공하고 있어 기존에 콘솔에 출력하듯이 바이트 단위로 파일에 쓸수 있음
    • null인 내용을 파일에 쓸 수 있음
  • BufferedWriter
    • 버퍼를 사용해 데이터를 메모리 영역에 두었다가 파일에 쓰는 효율적인 파일 쓰기를 지원
    • null 입력시 NPE를 발생할 수 있음
fun main() {
    val outString: String = "안녕하세요!"
    
    val path = "D:\\test\\testfile.txt"
    
    // use() 내부적으로 close()를호출
    File(path).printWriter().use { it.println(outString) }
    
    // 메모리의 특정 공간에 저장한 뒤 파일로 다시 쓰여짐
    File(path).bufferedWriter().use { it.write(outString) }
}

📌 File의 writeText()

  • writeText() : 코틀린에서 확장해 감싼 메서드로 제공
    -> 감싼(wrapped) 메서드 : 기존에 존재하는 메서드를 또 다른 메서드로 감싼 후 기능을 추가해 편리하게 사용할 수 있게 한 메서드
  • FileOutputStream 사용
val file = File(path)
file.writeText(outString)
file.appendText(" Do great work!") // 파일에 문자열을 추가

2-3. 파일에서 읽기

📌 File의 FileReader

fun main() {
    val path = "D:\\test\\testfile.txt"

    try {
        val read = FileReader(path)
        println(read.readText())
    } catch (e: Exception) {
        println(e.message)
    }
}

-> read의 멤버 메서드 readText() 를 통해 읽어옴
-> readText()는 내부적으로 StringWriter()를 호출해 텍스트를 메모리로 가져온 후 그 내용을 반환

📌 코틀린의 파일 읽기

fun main() {
    val path = "D:\\test\\testfile.txt"

    val file = File(path)
    val inputStream: InputStream = file.inputStream()
    val text = inputStream.bufferedReader().use { it.readText() }
    print(text)
}

⬇️ BufferedReader로만 구성

fun main() {
    val path = "D:\\test\\testfile.txt"

    val bufferedReader: BufferedReader = File(path).bufferedReader()
    val inputString = bufferedReader.use { it.readText() }
    print(inputString)
}

-> file객체를 생략하고 BufferedReader로만 구성
-> 줄단위로 처리하려면 use() 대신 useLines()를 사용할 수 있음
-> 마찬가지로 useLines()는 처리가 완료된 뒤 파일을 닫아줌

📌 copyTo() 함수

  • 파일에 대한 복사 작업을 처리
  • 목적지인 target에 파일을 버퍼 크기만큼 한 번에 복사
  • File(path).copyTo(File("D:\\test\\testfile.txt"))
    -> 만일 복사하려는 첫 번째 대상인 path가 없으면 FileNotFoundException 오류가 발생
  • 복사할 대상은 오로지 파일이어야만 함
  • 파일 용량이 클 경우 복사되는 도중에 블로킹하면서 멈출 수 있음
profile
장똑대와 안드로이드

0개의 댓글