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.

ObjectInputStream and ObjectOutputStream sugar

There are also helper methods for Object input/output classes as this example shows:

@Immutable class Point implements Serializable { int x, y }

def file = new File('points.dat')
def square = [ new Point(10, 10),
               new Point(20, 10),
               new Point(20, 20),
               new Point(10, 20) ]
file.withObjectOutputStream { oos ->
    oos.writeObject(square)
}
file.withObjectInputStream(getClass().classLoader){ ois ->
    def saved = ois.readObject( )
    assert square == saved
}