We can create streams of data from files, network resources, memory locations, etc, both input and output. To initially demonstrate the use of streams, we'll use streams around a file, both byte and Character streams. The methods introduced in these example can be used for any stream.
InputStreams and OutputStreams are streams of bytes:
def fos= new FileOutputStream('TestFile.txt') //These methods are available for all output streams, not just FileOutputStream: [ 21, 34, 43, 79 ].each{ fos.write(it) } //write out the lowest-order 8 bits of the supplied integer fos.flush() fos.write([69, 32, 22] as byte[]) fos.write([10, 11, 12, 13, 88, 89] as byte[], 3, 2) //write 2 bytes from array starting at index 3 fos.close() try{ fos.write(77); assert 0 }catch(e){ assert e instanceof IOException } //no writing after file closed //check the byte contents of the file with a File utility method: assert new File('TestFile.txt').readBytes().toList() == [ 21, 34, 43, 79, 69, 32, 22, 13, 88 ] def fis= new FileInputStream('TestFile.txt') //These methods are available for all input streams, not just FileInputStream: assert fis.available() == 9 //an estimate of bytes left for reading or skipping in the input stream assert fis.read() == 21 //actually, the next byte is returned as an integer fis.skip(2) //skip over, here, 2 bytes of data from the stream assert fis.available() == 6 def ba2= new byte[3] fis.read(ba2) assert ba2.toList() == [79, 69, 32] def ba3= new byte[6] assert fis.read(ba3, 3, 2) == 2 //fill ba3 with 2 elements from index 3, //return num of elements copied, here, 2 assert ba3.toList() == [0, 0, 0, 22, 13, 0] assert fis.read(ba3) == 1 //return num of elements copied, here, 1 assert ba3.toList() == [88, 0, 0, 22, 13, 0] assert fis.read(ba3) == -1 //return -1 if already at end-of-stream //true if this input stream support the mark() and reset() methods... if( fis.markSupported() ){ fis.reset() //reset reading to beginning of stream if mark() hasn't ever been called assert fis.read() == 21 fis.mark(0) //mark this position in the stream; argument has no meaning here fis.read(new byte[4]) fis.reset() //reset reading to where the last mark() method was called assert fis.read() == 34 } fis.close() try{ fis.read(); assert 0 }catch(e){ assert e instanceof IOException } new File('TestFile.txt').delete() // delete the file used by this example
Readers and Writers are streams of Characters:
def fw= new FileWriter('TestFile.txt') //These methods are available for all writers, not just for FileWriter: [ 'a', 'b' ].each{ fw.write(it as char) } //write out the supplied character [ 'cd', 'efg' ].each{ fw.write(it) } //write out the supplied string fw.flush() fw.write(['h', 'i', 'j'] as char[]) fw.write(['h', 'i', 'j', 'k', 'l', 'm'] as char[], 3, 2) //write 2 chars from array starting at index 3 fw.write('klmnopq', 2, 4) //write 4 chars from string starting at index 2 fw.append('q' as char). //these Java 5.0 methods allow chaining append('rstuv'). append('uvwxyz', 2, 6) //use subsequence from index 2 to index 6 of supplied string fw.close() try{ fw.write('z'); assert 0 }catch(e){ assert e instanceof IOException } //no writing after file closed assert new File('TestFile.txt').readLines() == [ 'abcdefghijklmnopqrstuvwxyz' ] def fr= new FileReader('TestFile.txt') //These methods are available for all readers, not just for FileReader: if(fr.ready()){ assert fr.read() == 'a' fr.skip(2) //skip over, here, 2 chars def ca2= new char[3] fr.read(ca2) assert ca2.toList()*.toString() == ['d', 'e', 'f'] def ca3= new char[6] assert fr.read(ca3, 3, 2) == 2 //fill ca3 with 2 elements from index 3, //return num of elements copied, here, 2 assert ca3.toList()*.toString() == ['\0', '\0', '\0', 'g', 'h', '\0'] //similar to InputStream method fr.skip(20) assert fr.read(ca3) == -1 //return -1 if already at end-of-stream //true if this input stream support the mark() and reset() methods... if( fr.markSupported() ){ fr.reset() //reset reading to beginning of stream if mark() hasn't ever been called assert fr.read() == 'a' as char fr.mark(0) //mark this position in the stream; argument has no meaning here fr.read(new char[4]) fr.reset() //reset reading to where the last mark() method was called assert fr.read() == 'b' as char } fr.close() try{ fr.read(); assert 0 }catch(e){ assert e instanceof IOException } } new File('TestFile.txt').delete() //delete the file used by this example
Closing Streams
When we write to an output stream or writer such as FileWriter, we should always close() it in some way:
//here, because we don't close() the FileWriter, if there's an IOException, //some written data may be lost... def fw= new FileWriter('TestFile1.txt') try{ fw.write('abc\r\ndefg') throw new IOException('') //simulate error on write() in previous line }catch(e){ } assert new File('TestFile1.txt').readLines().toList() == [] //nothing written because wasn't closed or flushed new File('TestFile1.txt').delete() assert new File('TestFile1.txt').exists() //not deleted because wasn't closed //here, we close() the FileWriter in a 'finally' block, not losing any written //data... def fw2= new FileWriter('TestFile2.txt') try{ try{ fw2.write('abc\r\ndefg') throw new IOException('') //simulate error on write() in previous line }finally{ fw2.close() //or flush() file so no data will be lost when exception thrown } }catch(e){ } assert new File('TestFile2.txt').readLines() == ['abc', 'defg'] //contents written OK new File('TestFile2.txt').delete() assert ! new File('TestFile2.txt').exists() //file deleted OK //using withWriter() always closes the File, whatever is thrown inside //closure... try{ new File('TestFile3.txt').withWriter(){ w-> w.write('abc\r\ndefg') throw new IOException('') //simulate error on write() in previous line } }catch(e){ } new File('TestFile3.txt').delete() assert ! new File('TestFile3.txt').exists() //deleted OK because withWriter() closed the file
We can choose from many such methods to read and write characters to streams, where the stream is always closed automatically. Here's some methods which use a Reader and/or Writer. Although these examples use Files, all these methods work for other streamed resources also.
new File('TestFile1.txt').withWriter{ w-> w<< 'abc' << 'def' //operator syntax w.leftShift('ghi').leftShift('jkl') //equivalent method name } //file overwritten because it already exists... new File('TestFile1.txt').withWriter('unicode'){ w-> w<< 'abcdefghij' } new File('TestFile1.txt').withWriterAppend('unicode'){ w-> w<< 'klmnop' //although appending, unicode marker 0xFEFF also added } //here, we'll use concatenation format for string because it's easier to read def fw= new FileWriter('TestFile1.txt') fw.withWriter{ w-> ['ab,cd\n' + 'efg\n' + 'hi,jk\n' + 'l', 'mn,op'].each{ w<< it } } new File('TestFile1.txt').withReader{ r-> assert r.read() == 'a' } def list= [] new File('TestFile1.txt').eachLine{ list<< it } assert list == ['ab,cd', 'efg', 'hi,jk', 'lmn,op'] assert new File('TestFile1.txt').readLines() == ['ab,cd', 'efg', 'hi,jk', 'lmn,op'] assert new File('TestFile1.txt').text == 'ab,cd\n' + 'efg\n' + 'hi,jk\n' + 'lmn,op' //property //filter lines from file, and write to writer... def fw2= new FileWriter('TestFile2.txt') new File('TestFile1.txt').filterLine(fw2){ line-> ! line.contains('g') } assert new File('TestFile2.txt').text == 'ab,cd\r\n' + 'hi,jk\r\n' + 'lmn,op\r\n' // \n was changed to \r\n for Windows def fw2a= new FileWriter('TestFile2.txt') new FileReader('TestFile1.txt').filterLine(fw2a){ line-> ! line.contains('g') } assert new File('TestFile2.txt').text == 'ab,cd\r\n' + 'hi,jk\r\n' + 'lmn,op\r\n' def fr2= new FileReader('TestFile2.txt') assert [fr2.readLine(), fr2.readLine()] == ['ab,cd', null] //known bug: only returns correctly on first call fr2.close() new FileReader('TestFile2.txt').withReader{ r-> def ca= new char[25] r.read(ca) assert ca.toList().join('').trim() == 'ab,cd\r\n' + 'hi,jk\r\n' + 'lmn,op' } def list2= [] new FileReader('TestFile2.txt').splitEachLine(','){ line-> list2<< line } assert list2 == [ ['ab', 'cd'], ['hi', 'jk'], ['lmn', 'op'] ] def fw2b= new FileWriter('TestFile2.txt') new FileReader('TestFile1.txt').transformLine(fw2b){ line-> if( line.contains(',') ) line += ',z' line } assert new File('TestFile2.txt').text == 'ab,cd,z\r\n' + 'efg\r\n' + 'hi,jk,z\r\n' + 'lmn,op,z\r\n' def fw2c= new FileWriter('TestFile2.txt') new FileReader('TestFile1.txt').transformLine(fw2c){ line-> if( line.contains(',') ) line += ',z' line } assert new File('TestFile2.txt').text == 'ab,cd,z\r\n' + 'efg\r\n' + 'hi,jk,z\r\n' + 'lmn,op,z\r\n' def fw2d= new FileWriter('TestFile2.txt') new FileReader('TestFile1.txt').transformChar(fw2d){ ch-> if(ch == ',') ch= '***' ch } assert new File('TestFile2.txt').text == 'ab***cd\n' + 'efg\n' + 'hi***jk\n' + 'lmn***op' // \n not converted [new File('TestFile1.txt'), new File('TestFile2.txt')].each{ it.delete() } //delete files created by this example
Some methods which use an input and/or output stream which, although using Files in the examples, all work for other streamed resources also:
new File('TestFile1.txt').withOutputStream{ os-> os<< ([95, 96] as byte[]) //operator syntax for byte arrays os.leftShift( [97, 98, 99] as byte[] ) //equivalent method name } assert new File('TestFile1.txt').readBytes().toList() == [95, 96, 97, 98, 99] def list= [] new File('TestFile1.txt').eachByte(){ b-> list<< b } assert list == [95, 96, 97, 98, 99] new FileOutputStream('TestFile1.txt').withStream{ os-> os.write([100, 101, 102, 103] as byte[]) } def list2= [] new FileInputStream('TestFile1.txt').eachByte(){ b-> list2<< b } assert list2 == [100, 101, 102, 103] new File('TestFile1.txt').withInputStream{ is-> def ba= new byte[5] is.read(ba) assert ba == [100, 101, 102, 103, 0] } new FileInputStream('TestFile1.txt').withStream{ s-> def ba= new byte[5] s.read(ba) assert ba == [100, 101, 102, 103, 0] } assert new FileInputStream('TestFile1.txt').text == 'defg' assert new FileInputStream('TestFile1.txt').getText('unicode') == '摥晧' new FileInputStream('TestFile1.txt').withReader{ r-> assert r.read() == 'd' } new FileOutputStream('TestFile2.txt').withWriter('unicode'){ w-> w<< '我是法国人' } assert new FileInputStream('TestFile2.txt').getText('unicode') == '我是法国人' new FileOutputStream('TestFile2.txt').withWriter{ w-> w<< new FileInputStream('TestFile1.txt') //send contents of input stream directly to output stream w<< 2.495 << '\n' //send an object to output stream as string, returning //a writer, then send another object to that writer w<< [3,4,5] //send another object to output stream as string, returning a writer } assert new FileInputStream('TestFile2.txt').text == 'defg2.495\n' + '[3, 4, 5]' def list3= [] new FileInputStream('TestFile2.txt').eachLine{ line-> list3<< line } assert list3 == ['defg2.495', '[3, 4, 5]'] new FileInputStream('TestFile2.txt').readLine() == 'defg2.495' new FileInputStream('TestFile2.txt').readLines() == ['defg2.495', '[3, 4, 5]'] def fw3= new FileWriter('TestFile3.txt') new FileInputStream('TestFile2.txt').filterLine(fw3){ line-> line.contains('g') } assert new File('TestFile3.txt').readLines() == ['defg2.495'] [ new File('TestFile1.txt'), new File('TestFile2.txt'), new File('TestFile3.txt')].each{ it.delete() }
Although the examples above are for files, they're all available for streams, readers, and writers around all other resources also.
Resource-specific Streams
When we met the FileInputStream, FileOutputStream, FileReader, and FileWriter in the above examples, we constructed them with a single String. We can also construct them with a file, and add an 'append' flag:
def fos= new FileOutputStream(new File('TestFile.txt'), true) //appends to the file fos= new FileOutputStream(new File('TestFile.txt'), false) //overwrites the file fos= new FileOutputStream(new File('TestFile.txt')) //overwrites the file fos= new FileOutputStream('TestFile.txt', true) //appends to the file fos= new FileOutputStream('TestFile.txt', false) //overwrites the file fos= new FileOutputStream('TestFile.txt') //overwrites the file def fis= new FileInputStream(new File('TestFile.txt')) fis= new FileInputStream('TestFile.txt') def fw= new FileWriter(new File('TestFile.txt'), true) //appends to the file fw= new FileWriter(new File('TestFile.txt'), true) //overwrites the file fw= new FileWriter(new File('TestFile.txt')) //overwrites the file fw= new FileWriter('TestFile.txt', true) //appends to the file fw= new FileWriter('TestFile.txt', false) //overwrites the file fw= new FileWriter('TestFile.txt') //overwrites the file def fr= new FileReader(new File('TestFile.txt')) fr= new FileReader('TestFile.txt')
There are many other streams, readers, and writers that wrap around specific resources. ByteArrayInputStream and ByteArrayOutputStream wrap around an array of bytes:
def bais= new ByteArrayInputStream( [33, 34, 35] as byte[] ) [33, 34, 35, -1].each{ assert bais.read() == it } def bais2= new ByteArrayInputStream( [33, 34, 35, 36, 37, 38, 39] as byte[], 2, 4 ) [35, 36, 37, 38, -1].each{ assert bais2.read() == it } def baos= new ByteArrayOutputStream() baos.write([100, 101, 102, 103] as byte[]) assert baos.size() == 4 assert baos.toByteArray().toList() == [100, 101, 102, 103] def baos2= new ByteArrayOutputStream(10) //we can specify initial size of internal buffer baos.writeTo( baos2 ) //we can writeTo any OutputStream assert baos2.toByteArray().toList() == [100, 101, 102, 103] assert baos2.toString() == 'defg' assert baos2.toString('unicode') == '摥晧' baos2.reset() assert baos2.toByteArray().toList() == []
CharArrayReader and CharArrayWriter wrap around an array of chars:
def car= new CharArrayReader( ['a', 'b', 'c'] as char[] ) ['a', 'b', 'c', -1].each{ assert car.read() == it } def car2= new CharArrayReader( ['a', 'b', 'c', 'd', 'e', 'f', 'g'] as char[], 2, 4 ) ['c', 'd', 'e', 'f', -1].each{ assert car2.read() == it } def caw= new CharArrayWriter() caw.write(['a', 'b', 'c', 'd'] as char[]) assert caw.size() == 4 assert caw.toCharArray().toList() == ['a', 'b', 'c', 'd'].collect{ it as char } def caw2= new CharArrayWriter(10) //we can specify initial size of internal buffer caw.writeTo( caw2 ) //we can writeTo any Writer assert caw2.toCharArray().toList() == ['a', 'b', 'c', 'd'].collect{ it as char } assert caw2.toString() == 'abcd' caw2.reset() assert caw2.toCharArray().toList() == []
StringReader and StringWriter wrap around a StringBuffer:
def sr= new StringReader( 'abcde' ) ['a', 'b', 'c', 'd', 'e', -1].each{ assert sr.read() == it } def sw= new StringWriter() sw= new StringWriter(10) //we can specify initial size of StringBuffer sw.write( 'abcde' ) assert sw.buffer.toString() == 'abcde' assert sw.toString() == 'abcde'
InputStreamReader and OutputStreamWriter are a reader and writer pair that forms the bridge between byte streams and character streams. An InputStreamReader reads bytes from an InputStream and converts them to characters using a character-encoding, either the default or one specified by name. Similarly, an OutputStreamWriter converts characters to bytes using a character-encoding and then writes those bytes to an OutputStream. In this example, we use a FileInputStream and FileOutputStream, but any InputStream or OutputStream could be used:
def wtr= new OutputStreamWriter(new FileOutputStream('TheOutput.txt')) wtr<< 'abc' wtr.close() def rdr= new InputStreamReader(new FileInputStream('TheOutput.txt')) def list= [] rdr.eachLine{ list<< it } assert list == ['abc'] println System.getProperty("file.encoding") //to see the default file encoding used wtr= new OutputStreamWriter(new FileOutputStream('TheOutput.txt'), 'unicode') wtr<< 'def' println wtr.encoding //perhaps, 'UTF-16', as 'unicode' above is an alias wtr.close() rdr= new InputStreamReader(new FileInputStream('TheOutput.txt'), 'unicode') println rdr.encoding list= [] rdr.eachLine{ list<< it } assert list == ['def']
The buffered streams, reader, and writer wrap around another, buffering the data read or written so as to provide for the efficient processing of bytes, characters, arrays, and lines. It's very useful for streams, readers, and writers whose input/output operations are costly, such as files.
def bos= new BufferedOutputStream(new FileOutputStream('TheOutput.txt')) println bos.buf.size() //see the size of the default buffer bos= new BufferedOutputStream(new FileOutputStream('TheOutput.txt'), 16384) //set the buffer size assert bos.buf.size() == 16384 bos= new File('TheOutput.txt').newOutputStream() //returns a buffered output stream def bis= new BufferedInputStream(new FileInputStream('TheOutput.txt')) bis= new BufferedInputStream(new FileInputStream('TheOutput.txt'), 16384) //set the buffer size bis= new File('TheOutput.txt').newInputStream() //returns a buffered input stream def bwtr= new BufferedWriter(new FileWriter('TheOutput.txt')) bwtr= new BufferedWriter(new FileWriter('TheOutput.txt'), 16384) //set the buffer size bwtr= new File('TheOutput.txt').newWriter() //returns a buffered writer bwtr= new File('TheOutput.txt').newWriter('unicode') bwtr= new File('TheOutput.txt').newWriter(true) //appends to the file bwtr= new File('TheOutput.txt').newWriter('unicode', true) //appends to the file def brdr= new BufferedReader(new FileReader('TheOutput.txt')) brdr= new BufferedReader(new FileReader('TheOutput.txt'), 16384) //set the buffer size brdr= new File('TheOutput.txt').newReader() //returns a buffered reader brdr= new File('TheOutput.txt').newReader('unicode') brdr= new FileInputStream('TheOutput.txt').newReader() def file= new File('TheOutput.txt') def wtr= file.newWriter() wtr.writeLine('abc') wtr.writeLine('def') wtr.newLine() //writes blank line wtr.close() def rdr= file.newReader() assert rdr.readLine() == 'abc' //doesn't return end-of-line characters assert rdr.text == 'def' + '\r\n' + '\r\n' //returns end-of-line characters
A SequenceInputStream joins two other streams together:
def f1= new File('TheOutput1.txt'), f2= new File('TheOutput2.txt') f1<< 'abcde'; f2<< 'fghij' def is1= new FileInputStream(f1), is2= new FileInputStream(f2) def sis= new SequenceInputStream(is1, is2) assert sis.text == 'abcdefghij'
SequenceInputStream can also join three or more streams together using a Vector. See the upcoming tutorial on multi-threading for more on Vectors:
def f1= new File('TheOutput1.txt'), f2= new File('TheOutput2.txt'), f3= new File('TheOutput3.txt') f1<< 'abc'; f2<< 'def'; f3<< 'ghij' def list=[ new FileInputStream(f1), new FileInputStream(f2), new FileInputStream(f3) ] def sis= new SequenceInputStream(new Vector(list).elements()) assert sis.text == 'abcdefghij'
A line-number reader keeps track of line numbers:
def w= new File('TheOutput.txt').newWriter() w.writeLine('abc'); w.writeLine('defg'); w.close() def lnr= new LineNumberReader(new FileReader('TheOutput.txt')) lnr= new LineNumberReader(new FileReader('TheOutput.txt'), 16384) //set the buffer size assert lnr.lineNumber == 0 assert lnr.readLine() == 'abc' assert lnr.lineNumber == 1 lnr.lineNumber= 4 assert lnr.readLine() == 'defg' assert lnr.lineNumber == 5
A pushback input stream allows read input to be pushed back on:
def ba= [7, 8, 9, 10, 11, 12, 13] as byte[] def pis= new PushbackInputStream(new ByteArrayInputStream(ba)) pis= new PushbackInputStream(new ByteArrayInputStream(ba), 1024) //or specify buffer size def ba2= new byte[3] pis.read(ba2) assert ba2.toList() == [7, 8, 9] pis.unread(2) pis.read(ba2) assert ba2.toList() == [2, 10, 11] pis.unread([3, 4, 5, 6] as byte[]) pis.read(ba2) assert ba2.toList() == [3, 4, 5] pis.read(ba2) assert ba2.toList() == [6, 12, 13]
A pushback reader provides a similar facility for characters:
def ca= ['g', 'h', 'i', 'j', 'k', 'l', 'm'] as char[] def prdr= new PushbackReader(new CharArrayReader(ca)) prdr= new PushbackReader(new CharArrayReader(ca), 1024) //or specify buffer size def ca2= new char[3] prdr.read(ca2) assert ca2.toList() == ['g', 'h', 'i'].collect{it as char} prdr.unread('b' as int) prdr.read(ca2) assert ca2.toList() == ['b', 'j', 'k'].collect{it as char} prdr.unread(['c', 'd', 'e', 'f'] as char[]) prdr.read(ca2) assert ca2.toList() == ['c', 'd', 'e'].collect{it as char} prdr.read(ca2) assert ca2.toList() == ['f', 'l', 'm'].collect{it as char} prdr.unread(['a', 'b', 'c', 'd', 'e', 'f', 'g'] as char[], 1, 4) //offset 1, length 4 of array prdr.read(ca2) assert ca2.toList() == ['b', 'c', 'd'].collect{it as char}
A DataOutputStream writes out Groovy structures as bytes, and a DataInputStream reads such bytes in as Groovy structures:
def baos= new ByteArrayOutputStream(30) def dos= new DataOutputStream(baos) assert dos.size() == 0 def bais= new ByteArrayInputStream( baos.buf ) def dis= new DataInputStream(bais) dos.writeBoolean( true ) assert baos.toByteArray().toList() == [1] //writes boolean as a 1-byte value assert dis.readBoolean() == true dos.writeByte( 200 ) //converted to -56, a 1-byte value assert baos.toByteArray().toList() == [1, -56] //'true', followed by '200 as byte' assert dis.readByte() == -56 dis.reset() //resets input stream dis.skipBytes(1) //we can skip bytes assert dis.readUnsignedByte() == 200 baos.reset() //flushes backing stream dis.reset() dos.writeBytes('abcdefg') //writes string as a sequence of bytes assert baos.toByteArray() as List == [97, 98, 99, 100, 101, 102, 103] dis.reset() def ba= new byte[5] dis.readFully(ba) //readFully() is converse of writeBytes() assert ba as List == [97, 98, 99, 100, 101] dis.reset() ba= new byte[5] dis.readFully(ba, 1, 2) //offset 1 and length 2 of ba assert ba as List == [0, 97, 98, 0, 0] baos.reset(); dis.reset() dos.writeChar('a' as int) //writes char as 2-byte value, high byte first assert baos.toByteArray() as List == [0, 97] assert dis.readChar() == 'a' baos.reset(); dis.reset() dos.writeChars('ab') //writes string as a sequence of characters assert baos.toByteArray() as List == [0, 97, 0, 98] baos.reset(); dis.reset() //DataInputStream has no readChars() method dos.writeShort(5000) //writes a short as two bytes, high byte first assert baos.toByteArray() as List == [19, -120] && 20*256 - 120 == 5000 assert dis.readShort() == 5000 dis.reset() dis.readUnsignedShort() == 5000 //similar to readUnsignedByte() baos.reset(); dis.reset() dos.writeInt(5000) //writes an integer as four bytes, high byte first assert baos.toByteArray() as List == [0, 0, 19, -120] assert dis.readInt() == 5000 baos.reset(); dis.reset() dos.writeLong(5000) //writes a long as eight bytes, high byte first assert baos.toByteArray() as List == [0, 0, 0, 0, 0, 0, 19, -120] assert dis.readLong() == 5000 baos.reset(); dis.reset() dos.writeDouble(123.456) //calls Double.doubleToLongBits(), writes as 8 bytes, high first println baos.toByteArray() as List assert dis.readDouble() == 123.456d baos.reset(); dis.reset() dos.writeFloat(123.456f) //calls Float.floatToIntBits(), writes as 4 bytes, high first println baos.toByteArray() as List assert dis.readFloat() == 123.456f baos.reset(); dis.reset() dos.writeUTF('abc') //writes using "modified UTF-8 encoding in a machine-independent manner" assert baos.toByteArray() as List == [0, 3, 97, 98, 99] //UTF-8 adds 0, 3 at beginning assert dis.readUTF() == 'abc' dis.reset() assert DataInputStream.readUTF(dis) == 'abc' //a static method to perform the same action
We'll meet more different types of streams, readers, and writers in the tutorials on Inheritance, Networking, Multi-threading, and others coming up.






