https://kotlinlang.org logo
#getting-started
Title
# getting-started
s

Stefán Freyr Stefánsson

02/03/2021, 11:07 AM
Hello, I have a basic question about file streams. I want to concatenate multiple files into one. I see that the File object has
appendText
and
appendBytes
but what do I do if I want to append an input stream? There’s
copyTo
but that replaces the whole file and I don’t want to read the whole input stream into a byte array (for
appendBytes
) because of memory usage. Will I have to handle the buffering myself and write a decorator function (
File.appendStream(inputStream: InputStream)
) or does something else exist?
m

Matteo Mirk

02/03/2021, 11:27 AM
You can take advantage of another Kotlin extension on InputStream:
file.appendBytes(inputStream.readBytes())
If you want you can encapsulate it in an extension function.
Be warned that it will read all the stream into a ByteArray so if your stream is very big you should handle buffering manually.
s

Stefán Freyr Stefánsson

02/03/2021, 11:30 AM
right… that’s what I’m trying to be careful of… so I’ll have to do the manual buffering I guess. Do you know if
File.copyTo
handles buffering for you? I think it does.
m

Matteo Mirk

02/03/2021, 11:43 AM
Yeah in that case you have to. I’m not sure about copyTo but just check its source to confirm.
I guess you have to go down this road or similar (not tested):
Copy code
inputStream.buffered().use { 
    val buffer = ByteArray(4096) // choose size
    while (it.read(buffer) != -1 ) {
        file.appendBytes(buffer)
    }
}
v

Vampire

02/03/2021, 12:42 PM
Copy code
FileOutputStream(
    File("first"),
    true
).use { out ->
    File("second")
        .inputStream()
        .use {
            it.copyTo(out)
        }
}
Or as extension function
Copy code
fun File.appendStream(appendee: File) {
    FileOutputStream(this, true).use { out ->
        appendee
            .inputStream()
            .use {
                it.copyTo(out)
            }
    }
}
s

Stefán Freyr Stefánsson

02/03/2021, 2:05 PM
thanks for your help guys. I’ll try this stuff out and report back
👍 1
Allright… so I had already tried something similar but I keep getting an output file with size 0. Here’s my code:
Copy code
@Test
    fun appendToFile() {
        val file1 = File("foo")
        val file2 = File("bar")
        val output = File("foobar")
        output.createNewFile()

        println("file1[${file1.absolutePath} - ${file1.length()}], file2[${file2.absolutePath} - ${file2.length()}]")

        output.appendStream(file1)
        output.appendStream(file2)

        output.outputStream().close()

        println("output: ${output.absolutePath} - ${output.length()}")
    }

    fun File.appendStream(appendee: File) {
        FileOutputStream(this, true).use { out ->
            appendee
                .inputStream()
                .use {
                    it.copyTo(out)
                }
        }
    }
I’ve tried with output.outputStream().close() and without it as well.
@Vampire any idea why this wouldn’t be working (and yes I’ve verified that file1 and file2 exist, are there, and are not empty)
modified the code above a bit to print out some information… here’s the output:
Copy code
file1[/Users/stefan/work/gor/quarkus/file-transfer-service/foo - 10], file2[/Users/stefan/work/gor/quarkus/file-transfer-service/bar - 22]
output: /Users/stefan/work/gor/quarkus/file-transfer-service/foobar - 0
v

Vampire

02/03/2021, 2:22 PM
close
is not necessary due to
use
that is like try-with-resources in Java and automatically closes the stream in the end
I have no idea why it does not work and actually didn't try it
Works fine here
Copy code
file1[D:\Sourcecode\other\setup-wsl\foo - 3], file2[D:\Sourcecode\other\setup-wsl\bar - 3]
output: D:\Sourcecode\other\setup-wsl\foobar - 6
s

Stefán Freyr Stefánsson

02/03/2021, 2:28 PM
hmm… weird
I’m on Mac… maybe it’s just a bug
v

Vampire

02/03/2021, 2:29 PM
Maybe
You can even do
Copy code
fun File.appendStream(vararg appendees: File) {
     FileOutputStream(this, true).use { out ->
         appendees
             .forEach { appendee ->
                 appendee
                     .inputStream()
                     .use {
                         it.copyTo(out)
                     }
             }
     }
 }
and then
output.appendStream(file1, file2)
then it only needs to open the outputstream once, but shouldn't make too much difference
1
s

Stefán Freyr Stefánsson

02/03/2021, 2:31 PM
well I’ll be… this works!
but the other one doesn’t…
v

Vampire

02/03/2021, 2:32 PM
o_O
s

Stefán Freyr Stefánsson

02/03/2021, 2:32 PM
very weird… but now I can continue 🙂 thanks @Vampire!
v

Vampire

02/03/2021, 2:33 PM
Hm, maybe macOS has some strange stream handling because you open the outputstream too fast in succession or something like that?
No idea
You should report this, I just don't know to whom 😄
s

Stefán Freyr Stefánsson

02/03/2021, 2:33 PM
wait… wth… now it does work the other way…
I’m confused… but oh well…
almost certainly a PEBKAC but thanks a heap for the help
👌 1
n

nanodeath

02/03/2021, 5:13 PM
the JDK also has SequenceInputStream. maybe you can do
SequenceInputStream(is1, is2).copyTo(fos)
.
👍 1
1
3 Views