Friday, October 29, 2021

Java tutorial: Exploring java.io Package

Chapters

InputStream, OutputStream, Reader and Writer

In this tutorial, we will explore the streams in java.io package. We will discuss streams that we may use in our future projects. First off, let's start with InputStream and OutputStream.

InputStream is an abstract class that is the superclass of all classes representing an input stream of bytes whereas OutputStream is an abstract class that is the superclass of all classes representing an output stream of bytes. Reader is an abstract class for reading character streams and Writer is an abstract class for writing to character streams. Now, let's talk about some of their subclasses.

Note: After using a stream, don't forget to close it. Use the close() method to close a stream to free up memory. Every stream has close() method.

FileInputStream and FileOutputStream

FileInputStream is meant for reading bytes from a file whereas FileOutputStream is meant for writing bytes to a file. These streams operate files such as video, images, audio, etc. They can read/write characters but other streams are much better at reading/writing characters such as FileReader and FileWriter.
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class SampleClass{
  
  public static void main(String[] args){
    File source = new File("C:\\test\\img.png");
    File target = new File("C:\\test\\folder1\\"+
                           "imgCopy.png");
    
    try(FileInputStream fis = 
        new FileInputStream(source);
        FileOutputStream fos = 
        new FileOutputStream(target)){
        
        //readAllBytes() method Reads all
        //remaining bytes from the input stream.
        //this method is intended for simple
        //cases where it is convenient to read
        //all bytes into a byte array.
        System.out.println("Reading bytes...");
        byte[] bytes = fis.readAllBytes();
        
        System.out.println("File size(bytes):"+
                           bytes.length);
                           
        System.out.println("Writing bytes...");
        //write() method writes bytes to the
        //target file
        fos.write(bytes);
        System.out.println("Done!");
    }
    catch(Exception e){
      e.printStackTrace();
    }
    
  }
}
Another way to read/write bytes is to read/write one byte at a time. Though, this kind of read/write process is inefficient especially when reading/writing large files.
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class SampleClass{
  
  public static void main(String[] args){
    File source = new File("C:\\test\\img.png");
    File target = new File("C:\\test\\folder1\\"+
                           "imgCopy.png");
    
    try(FileInputStream fis = 
        new FileInputStream(source);
        FileOutputStream fos = 
        new FileOutputStream(target)){
        
        int num = 0;
        
        //read one byte per loop
        //read() method returns a byte
        //of data. It returns -1 if the
        //end of a file is reached.
        while((num = fis.read()) != -1)
          fos.write(num);
        
        System.out.println("Done!");
    }
    catch(Exception e){
      e.printStackTrace();
    }
    
  }
}
Another way to read/write data is to create a buffer. This method is more efficient than the two previous methods. Buffer is like a container that contains set of bytes of data.
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class SampleClass{
  
  public static void main(String[] args){
    File source = new File("C:\\test\\img.png");
    File target = new File("C:\\test\\folder1\\"+
                           "imgCopy.png");
    
    try(FileInputStream fis = 
        new FileInputStream(source);
        FileOutputStream fos = 
        new FileOutputStream(target)){
        
        //a buffer with a size of 1024 bytes
        //or 1kb
        byte[] bytes = new byte[1024];
        
        //this read(byte[] b) method tries to
        //fill up the array parameter with
        //fresh bytes of data from the
        //inputstream per loop. Returns the
        //total number of bytes read from
        //the buffer, or -1 if there is no
        //more data because the end of the
        //file has been reached
        while(fis.read(bytes) != -1)
          fos.write(bytes);
        
        System.out.println("Done!");
    }
    catch(Exception e){
      e.printStackTrace();
    }
    
  }
}
What is a buffer?

A buffer is a temporary storage of data. A buffer can help improve the efficiency of a data transfer. For example, when we read data from a file, sometimes, transferring of data is faster than receiving the data and vice-versa. Without a buffer, our program may experience slow down or interruption due to varying rates of tranferring and receiving data. By using buffer, we can minimize these slow down or interruption. A buffer acts as a placeholder of data between two or more entities. In this way, data transfer happens in the buffer instead of direct data transfer between entities.

Buffer size can vary based on a task our program is doing. In the example above, I use 1024 bytes of 1kb(in binary) because it's one of the standard buffer sizes and it's suitable for transferring small files. Though, you can increase the buffer size if you want to. If you're doing a complex task regarding data transfer or transferring very large files, you may wanna do some testing and analyze the most suitable buffer size that your program can take advantage of.

ByteArrayInputStream and ByteArrayOutputStream

ByteArrayInputStream contains an internal buffer that contains bytes that may be read from the stream. ByteArrayOutputStream implements an output stream in which the data is written into a byte array. The buffer automatically grows as data is written to it. These two streams are sometimes used in conjunction with other streams.
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

public class SampleClass{
  
  public static void main(String[]args)
                     throws IOException{
    
    ByteArrayInputStream bai = null;
    ByteArrayOutputStream bao = null;
    try{
      
      byte[] buf = {96,97,98,99,100};
  
      //ByteArrayInputStream that takes
      //a byte array as constructor's
      //argument. the argument is used 
      //as inputstream's internal buffer
      bai = new ByteArrayInputStream(buf);
  
      int num = 0;
      System.out.println("ByteArrayInputStream");
      while((num = bai.read()) != -1)
        System.out.print((char)num);
      System.out.println("\n");
  
      //ByteArrayOutputStream
      bao = new ByteArrayOutputStream();
      //write some bytes into the
      //outputstream internal buffer
      for(int i = buf.length; i > 0; i--)
        bao.write(buf[i-1]);
      
      //get the bytes from the outpustream's
      //internal buffer
      byte[] bytes = bao.toByteArray();
      
      System.out.println("ByteArrayOutputStream");
      for(byte b : bytes)
        System.out.print((char)b);
      System.out.println("\n");
  
    }
    finally{
      if(bai != null)
        bai.close();
      if(bao != null)
        bao.close();
    }
    
  }
}

Result
ByteArrayInputStream
'abcd

ByteArrayOutputStream
dcba'
InputStreamReader and OutputStreamWriter

InputStreamReader is a bridge from byte streams to character streams: It reads bytes and decodes them into characters using a specified charset. the default charset may be used if there's no specified charset.

On the other hand, OutputStreamWriter is also a bridge from byte streams to character streams that encodes characters into bytes using a specified charset. Default charset may be used if there's no specified charset.

Note: For top efficiency, consider wrapping an InputStreamReader/OutputStreamWriter within a BufferedReader/BufferedWriter. For example:
BufferedReader in = new BufferedReader(new InputStreamReader(anInputStream));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(anOutputStream));

This example demonstrates InputStreamReader and OutputStreamWriter.
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;

public class SampleClass{

  public static void main(String[] args)
  throws IOException{
  
    byte[] bytes = {97,98,99,100,101};
    byte[] encodeChar = null;
    String decoded = "";
    String orig = "";
    String encoded = "";
    
    try(InputStreamReader isr =
        new InputStreamReader(
              new ByteArrayInputStream(bytes),
              "UTF-16");
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        OutputStreamWriter osw = new OutputStreamWriter(bao)){
              
        int num = 0;
        while((num = isr.read()) != -1){
          decoded += String.valueOf(num);
          decoded += " ";
          osw.write(num);
        }
        //Flush the OutputStreamWriter so
        //the bytes will be written into
        //the specified OutputStream.
        osw.flush();
        
        encodeChar = bao.toByteArray();
        
        for(byte b : bytes){
          orig += String.valueOf(b);
          orig += " ";
        }
        
        for(byte b: encodeChar){
          encoded += String.valueOf(b);
          encoded += " ";
        }
        
        System.out.println("Default charset: " +
                           Charset.defaultCharset().
                           displayName());
        System.out.println("Original bytes: " + orig);
        System.out.println("Decoded into "+
                           isr.getEncoding()+": "+
                           decoded);
        System.out.println("Encoded into "+
                           osw.getEncoding()+": "+
                           encoded);
    }
    
  }
}

Result(Note: Result may vary)
Default charset: windows-1252
Original bytes: 97 98 99 100 101
Decoded into UTF-16: 25185 25699 65533
Encoded into cp1252: 63 63 63
FileReader and FileWriter

FileReader reads text from a file and FileWriter writes text to a file. They use specified charset to decode and encode. Otherwise, they may use the default charset if a charset is not specified.
import java.io.File;
import java.io.IOException;
import java.io.FileReader;
import java.io.FileWriter;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    File source = new File("C:\\test\\readTest.txt");
    File target = new File("C:\\test\\writeTest.txt");
    try(FileReader fr = new FileReader(source);
        FileWriter fw = new FileWriter(target)){
        
        int num = 0;
        while((num = fr.read()) != -1)
          fw.write((char)num + "\r\n");
    }
    System.out.println("Done!");
    
  }
  
}
CharArrayReader and CharArrayWriter

CharArrayReader is used for reading characters from a char array type whereas CharArrayWriter is used for writing characters to a char array type.
import java.io.IOException;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
  
    char[] input = {'a','b','c','d'};
    char[] output = null;
    
    CharArrayReader car = null;
    CharArrayWriter caw = null;
    
    try{
      car = new CharArrayReader(input);
      caw = new CharArrayWriter();
      
      int num = 0;
      System.out.println("Input");
      while((num = car.read()) != -1){
        System.out.print((char)num);
        caw.write(num + 1);
      }
      System.out.println();
      
      System.out.println("Output");
      System.out.println(caw.toString());
      
    }
    catch(Exception e){
      e.printStackTrace();
    }
    finally{
      if(car != null)
        car.close();
      if(caw != null)
        caw.close();
    }
    
  }
}

Result
Input
abcd
Output
bcde
BufferedReader and BufferedWriter

BufferedReader reads characters from another stream whereas BufferedWriterwrites bytes to another stream. They have internal buffers and the buffer sizes can be adjusted. The default size(8192 or 8kb) may be used if the buffers sizes are not specified. Wrapping character-based streams with this buffered streams increases the efficiency of read/write operations.

Streams like InputStreamReader, OutputStreamWriter, FileReader and FileWriter benefit from these character-based buffered streams. These character-based streams may hog our system resources. Wrapping them around character-based buffered streams will make them operate efficiently.
import java.io.File;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    File source = new File("C:\\test\\readTest.txt");
    File target = new File("C:\\test\\writeTest.txt");
    try(BufferedReader reader = new BufferedReader(
                                new FileReader(source));
        BufferedWriter writer = new BufferedWriter(
                                new FileWriter(target))){
        String str = "";
        
        //readLine() is a bufferedReader's method that
        //reads a line of text.
        while((str = reader.readLine()) != null){
          //write the data to the other text file
          writer.write(str);
          //create newline
          writer.newLine();
        }
        
        System.out.println("Done!");
    }
  }
}
BufferedInputStream and BufferedOutputStream

BufferedInputStream reads bytes from another stream whereas BufferedOutputStream write bytes to another stream. These streams add functionalities to other byte-based streams. Also, BufferedInputStream and BufferedOutputStream have internal buffer.
import java.io.File;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    File source = new File("C:\\test\\img.png");
    File target = new File("C:\\test\\imgCopy.png");
    try(BufferedInputStream bis = new BufferedInputStream(
                                  new FileInputStream(source));
        BufferedOutputStream bos = new BufferedOutputStream(
                                  new FileOutputStream(target))){
        int num = 0;
        while((num = bis.read()) != -1)
          bos.write(num);
        System.out.println("Done!");
    }
    
  }
}
PrintStream and PrintWriter

PrintStream and PrintWriter add new functionalities to other output streams. For byte-based output streams, use PrintStream. For character-based output streams, use PrintWriter.

This example demonstrates PrintStream.
import java.io.File;
import java.io.IOException;
import java.io.CharArrayReader;
import java.io.FileOutputStream;
import java.io.PrintStream;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    File target = new File("C:\\test\\writeTest.txt");
    char[] characters = {'!','a','~','c','i'};
    try(CharArrayReader car = 
        new CharArrayReader(characters);
        PrintStream ps = 
        new PrintStream(
            new FileOutputStream(target),
            true,
            "UTF-16")){
        
        int num = 0;
        while((num = car.read()) != -1)
          ps.println(num); 
    }
    System.out.println("Done!");
    
  }
}
This example demonstrates PrintWriter.
import java.io.File;
import java.io.IOException;
import java.io.CharArrayReader;
import java.io.FileWriter;
import java.io.PrintWriter;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    File target = new File("C:\\test\\writeTest.txt");
    char[] characters = {'!','a','~','c','i'};
    try(CharArrayReader car = 
        new CharArrayReader(characters);
        PrintWriter ps = 
        new PrintWriter(
            new FileWriter(target),
            true)){
        
        int num = 0;
        while((num = car.read()) != -1)
          ps.println((char)num); 
    }
    System.out.println("Done!");
    
  }
}
PipedInputStream and PipedOutputStream

PipedInputStream and PipedOutputStream should be connected with each other and they should operate on different threads. It's not recommended for them to operate on the same thread as it may cause deadlock. A pipe stream is said to be broken if a thread where the pipe stream is working on is not alive. We use PipedOutputStream to write bytes into the pipe and we use PipedInputStream to read the written bytes from the pipe.
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class SampleClass{
  
  public static void main(String[] args)
                     throws IOException{
  
    PipedOutputStream pos = new PipedOutputStream();
    PipedInputStream pis = new PipedInputStream(pos);
    
    Thread t1 = new Thread(() -> {
      
      try{
        
        for(int i = 0; i < 5; i++){
          pos.write(97 + i);
          Thread.sleep(50);
        }
        pos.close();
      }
      catch(Exception e){
        e.printStackTrace();
      }
      
    });
    
    Thread t2 = new Thread(() -> {
      try{
        
        int num = 0;
        while((num = pis.read()) != -1){
          System.out.print(num + " ");
          Thread.sleep(50);
        }
        pis.close();
      }
      catch(Exception e){
        e.printStackTrace();
      }
      
    });
    
    t1.start();
    t2.start();
    
  }
}

Result
97 98 99 100 101
PipedReader and PipedWriter

PipedReader and PipedWriter are the character-based stream versions of PipedInputStream and PipedOutputStream.
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;

public class SampleClass{
  
  public static void main(String[] args)
                     throws IOException{
  
    PipedWriter pw = new PipedWriter();
    PipedReader pr = new PipedReader(pw);
    
    Thread t1 = new Thread(() -> {
      
      try{
        
        for(int i = 0; i < 5; i++){
          pw.write(97 + i);
          Thread.sleep(50);
        }
        pw.close();
      }
      catch(Exception e){
        e.printStackTrace();
      }
      
    });
    
    Thread t2 = new Thread(() -> {
      try{
        
        int num = 0;
        while((num = pr.read()) != -1){
          System.out.print((char)num);
          System.out.print(" ");
          Thread.sleep(50);
        }
        pr.close();
      }
      catch(Exception e){
        e.printStackTrace();
      }
      
    });
    
    t1.start();
    t2.start();
    
  }
}

Result
a b c d e
PushBackInputStream and PushBackReader

A PushbackInputStream adds functionality to another input stream, namely the ability to "push back" or "unread" bytes, by storing pushed-back bytes in an internal buffer.
import java.io.IOException;
import java.io.PushbackInputStream;
import java.io.ByteArrayInputStream;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
  
    byte[] bytes = {75,10,25,30,45,55,65,80};
    byte[] byteMod = new byte[bytes.length];
    
    try(ByteArrayInputStream bai = 
        new ByteArrayInputStream(bytes);
        PushbackInputStream pis =
        new PushbackInputStream(bai)){
        
        int num = 0;
        int i = 0;
        while((num = pis.read()) != -1){
          
          if((num % 5) == 5){
             byteMod[i] = (byte)(num - 5);
             i++;
             
             //read one step ahead
             num = pis.read();
             if(num != -1){
               if((num % 5) == 0){
                 byteMod[i] = (byte)(num + 15);
                 i++;
               }
               else{
                 //push back the data that
                 //has been read
                 pis.unread(num);
               }
             }
               
          }
          else if((num % 5) == 0){
            byteMod[i] = (byte)(num + 5);
            i++;
          }
            
        }
        
        System.out.println("Original Bytes");
        for(byte b : bytes)
          System.out.print(b + " ");
        
        System.out.println("\n");
        System.out.println("Modified Bytes");
        for(byte b : byteMod)
          System.out.print(b + " ");
    }
  }
}

Result
Original Bytes
75 10 25 30 45 55 65 80

Modified Bytes
80 15 30 35 50 60 70 85
PushBackReader is the character-based stream version of PushbackInputStream.
import java.io.IOException;
import java.io.PushbackReader;
import java.io.CharArrayReader;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
  
    char[] chars = {'b','a','b','b','a','g','e'};
    StringBuilder sb = new StringBuilder();
    
    try(CharArrayReader car = 
        new CharArrayReader(chars);
        PushbackReader pr =
        new PushbackReader(car)){
        
        int num = 0;
        while((num = pr.read()) != -1){
          
          if((char)num == 'b' ){
             char temp = (char)num;
             
             //read one step ahead
             num = pr.read();
             if(num != -1){
               if((char)num == 'b'){
                 sb.append("gg");
               }
               else{
                 sb.append(temp);
                 //push back the data that
                 //has been read
                 pr.unread(num);
               }
             }
               
          }
          else sb.append((char)num);
          
        }
        
        System.out.println("Original Characters");
        for(char c : chars)
          System.out.print(c);
        System.out.println("\n");
        
        System.out.println("Modified Characters");
        System.out.println(sb.toString());
    }
  }
}

Result
Original Characters
babbage

Modified Characters
baggage
RandomAccessFile

Instances of RandomAccessFile class support both reading and writing to a random access file. A random access file behaves like a large array of bytes stored in the file system. This class has file pointer. This pointer points to an index of the array of bytes in the file system. The pointer starts at the beginning of the array and move past the bytes read until an EOF(End Of File) has been reached.

Instantiating a RandomAccessFile requires a mode that determines the operation of the instance. These are the modes that we can apply:
  • "r" - Open for reading only. Invoking any of the write methods of the resulting object will cause an IOException to be thrown.
  • "rw" - Open for reading and writing. If the file does not already exist then an attempt will be made to create it.
  • "rws" - Open for reading and writing, as with "rw", and also require that every update to the file's content or metadata be written synchronously to the underlying storage device.
  • "rwd" - Open for reading and writing, as with "rw", and also require that every update to the file's content be written synchronously to the underlying storage device.
My explanation here is simplified. Check RandomAccessFile for more information.
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Random;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    File file = new File("C:\\test\\fileTest.txt");
    try(RandomAccessFile raf = 
        new RandomAccessFile(file,"rw")){
        
        //In a simple text file,
        //file length(in bytes) corresponds to the
        //number of characters in that file
        long ln = raf.length();
        System.out.println("File length: " + ln);
        
        char[] vowels = {'a','e','i','o','u'};
        Random rand = new Random();
        
        //reading and writing random bytes from the file
        for(int i = 0; i < 5; i++){
          raf.seek(rand.nextInt((int)ln));
          System.out.println((char)raf.read() + " at index: "
                             + " " + (raf.getFilePointer()-1));
          
          //after a call to read() or write(), file pointer
          //will past the index where a byte is read/written.
          //To point the file pointer back to the previous
          //index we need to move it back by one step.
          //Thus, there's -1 
          //in this expression: raf.getFilePointer()-1
          raf.seek(raf.getFilePointer()-1);
          
          char replacement = vowels[rand.nextInt(vowels.length)];
          raf.write(replacement);
          System.out.println("Replaced by: " + replacement);
          System.out.println();
          
        }
        
    }
  }
}
seek() method repositions the file pointer at the specified index. getFilePointer() returns the index where the file pointer is pointing. The example above replaces any characters in the file with vowels.

Create Dummy File Using RandomAccessFile

We can create a dummy file. The dummy file that we're going to create is a file that has gibberish bytes in it.
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    File file = new File("C:\\test\\dummy.dum");
    
    //In binary
    //1024 bytes = 1 kilobyte
    //1024 kilobytes = 1 megabyte
    //In decimal
    //1000 bytes = 1 kilobyte
    //1000 kilobytes = 1 megabyte
    //Windows uses the binary size
    //I'm not sure if there are operating
    //systems that use the decimal size
    long dummySize = 1024L * (1024L * 4L);
    try(RandomAccessFile raf = 
        new RandomAccessFile(file,"rw")){
        
        //This method sets the total length
        //of the file that we're going to
        //create. Undefined bytes are added
        //in the process
        raf.setLength(dummySize);
    }
    System.out.println("dummy file created!");
  }
}
SequenceInputStream

SequenceInputStream is a stream that uses multiple input streams as its source of data. It reads from the first one until it reaches EOF(End Of File), then reads from the second one and so on.
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.SequenceInputStream;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    FileInputStream fileOne = 
    new FileInputStream("C:\\test\\test1.txt");
    
    FileInputStream fileTwo =
    new FileInputStream("C:\\test\\test2.txt");
    
    FileOutputStream target = 
    new FileOutputStream("C:\\test\\output.txt");
    
    SequenceInputStream sis = 
    new SequenceInputStream(fileOne, fileTwo);
    
    
    try{
      
      int num = 0;
      while((num = sis.read()) != -1)
        target.write((char)num);
    }
    finally{
      fileOne.close();
      fileTwo.close();
      target.close();
      sis.close();
    }
    System.out.println("Done!");
    
  }
}
To read more than two files, we need to use the Enumeration interface.
import java.util.Vector;
import java.util.Enumeration;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.SequenceInputStream;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    FileInputStream fileOne = 
    new FileInputStream("C:\\test\\test1.txt");
    
    FileInputStream fileTwo =
    new FileInputStream("C:\\test\\test2.txt");
    
    FileInputStream fileThree =
    new FileInputStream("C:\\test\\test3.txt");
    
    FileOutputStream target = 
    new FileOutputStream("C:\\test\\output.txt");
    
    Vector<InputStream> streams = 
    new Vector<>();
    
    streams.add(fileOne);
    streams.add(fileTwo);
    streams.add(fileThree);
    
    Enumeration<InputStream> e =
    streams.elements();
    
    SequenceInputStream sis = 
    new SequenceInputStream(e);
    
    try{
      
      int num = 0;
      while((num = sis.read()) != -1)
        target.write((char)num);
    }
    finally{
      fileOne.close();
      fileTwo.close();
      target.close();
      sis.close();
    }
    System.out.println("Done!");
    
  }
}
StringReader and StringWriter

StringReader is used to read from a String object and StringWriter is used to write to a String object.
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
  
    String input = "I am a String!";
    String output = null;
    
    StringReader reader = new StringReader(input);
    StringWriter writer = new StringWriter();
    
    try{
    
      int num = 0;
      
      //read/write string input
      while((num = reader.read()) != -1)
        writer.write(num);
      
      //get the written strings written
      //to the writer
      output = writer.toString();
    }
    finally{
      reader.close();
      writer.close();
    }
    System.out.println("Input: " + input);
    System.out.println("Output: " + output);
    
  }
}

Result
Input: I am a String!
Output: I am a String!
Console

Console class can access any character-based console device, if any, associated with the current Java virtual machine. This class provides a functionality to read password that is encrypted on the console screen.
import java.io.Console;

public class SampleClass{

  public static void main(String[] args){
    
    //System.console) returns the 
    //console associated with the
    //jvm
    Console c = System.console();
    
    if(c != null){
      char[] pass = null;
      
      System.out.println("Console Test!");
      System.out.println();
      
      System.out.print("Enter name: ");
      String name = c.readLine();
      System.out.println("name: " + name);
      System.out.println();
      
      System.out.print("Enter password");
      if((pass = c.readPassword())
          != null){
          StringBuilder sb = 
          new StringBuilder(String.valueOf(pass));
          
          //java documentation recommends 
          //manually removing characters
          //in the array where the inputted
          //sensitive data is stored to
          //minimize the lifetime of the
          //sensitive data
          java.util.Arrays.fill(pass, ' ');
          
          System.out.print("Password: ");
          System.out.println(sb.toString());
          
          //delete the characters in sb
          //StringBuilder after you're done
          //using it to minimize the lifetime
          //of the sensitive data
          sb.setLength(0);
      }
    }
    
  }
  
}
StreamTokenizer

The StreamTokenizer class takes an input stream and parses it into "tokens", allowing the tokens to be read one at a time. The parsing process is controlled by a table and a number of flags that can be set to various states. The stream tokenizer can recognize identifiers, numbers, quoted strings, and various comment styles.

These fields are important for us to understand.
  • nval - If the current token is a number, this field contains the value of that number.
  • sval - If the current token is a word token, this field contains a string giving the characters of the word token.
  • TT_EOF - A constant indicating that the end of the stream has been read.
  • TT_EOL - A constant indicating that the end of the line has been read.
  • TT_NUMBER - A constant indicating that a number token has been read.
  • TT_WORD - A constant indicating that a word token has been read.
  • ttype - After a call to the nextToken method, this field contains the type of the token just read.
This example demonstrate StreamTokenizer and the usage of the fields above.
import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.FileReader;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
  
     try(FileReader reader =
         new FileReader("C:\\test\\test.txt")){
         
         StreamTokenizer tokenizer =
         new StreamTokenizer(reader);
         
         int currToken = tokenizer.nextToken();
         
         //if the current token is equal to TT_EOF
         //then our program reaches the end of stream
         //no characters are available to be read
         //anymore.
         while(currToken != StreamTokenizer.TT_EOF){
           
           //After nextToken() is invoked, ttype will 
           //hold a value that determines the type
           //of the token that we got from the 
           //invocation of nextToken()
           if(tokenizer.ttype == StreamTokenizer.TT_NUMBER){
             //nval field holds the number value of the
             //current token
             System.out.println("number: " + tokenizer.nval);
           }
           else if(tokenizer.ttype == StreamTokenizer.TT_WORD){
             //sval field holds the string value of the 
             //current token if the curren token is
             //considered as a word
             System.out.println("word: " + tokenizer.sval);
           }
           else{
             //if ttype is not a number or a word then, 
             //current token is just an ordinary character
             System.out.println("character: " + 
                                (char)currToken);
           }
           
           currToken = tokenizer.nextToken();
         }
     }
  }
}
Content of test.txt
This is a t3st!
3 Two 1 Zero

Result
word: This
word: is
word: a
word: t3st
character: !
number: 3.0
word: Two
number: 1.0
word: Zero

Now, change the content of test.txt to this
"This" is a t3st!
And the result would be
character: "
word: is
word: a
word: t3st
character: !

Where's the word "This"? you might ask. Streamtokenizer considers words that are wrapped around quotes(single or double) as String. To get that "This" word, change this code snippet
else if(tokenizer.ttype == StreamTokenizer.TT_WORD){
  //sval field holds the string value of the 
  //current token if the curren token is
  //considered as a word
  System.out.println("word: " + tokenizer.sval);
}
To this
else if(tokenizer.ttype == StreamTokenizer.TT_WORD ||
        tokenizer.ttype == '\'' ||
        tokenizer.ttype == '"'){
  //sval field holds the string value of the 
  //current token if the curren token is
  //considered as a word
  System.out.println("word: " + tokenizer.sval);
}
And the result would be
word: This
word: is
word: a
word: t3st
character: !

Not all characters are considered as "ordinary" characters by the StreamTokenizer. To make StreamTokenizer recognize those characters, we need to manually specify them as ordinary characters by using the ordinaryChar() method. You can call this method after instantiating a StreamTokenizer object.
Example
StreamTokenizer tokenizer = new StreamTokenizer(reader);
tokenizer.ordinaryChar('/');

In addition, an instance has four flags. These flags indicate:
  • Whether line terminators are to be returned as tokens or treated as white space that merely separates tokens.
  • Whether C-style comments are to be recognized and skipped.
  • Whether C++-style comments are to be recognized and skipped.
  • Whether the characters of identifiers are converted to lowercase.
StreamTokenizer recognizes C++ and C comment style. StreamTokenizer skips those comments if it sees one. For example, when tokenizer recognizes the "\\" or the C++ comment style, tokenizer will skip text past "\\" until our program moves on to the next line. when tokenizer recognizes the "/* */" or the C comment style, tokenizer will skip all the characters within "/* */".

By default, tokenizer ignores those comments. Though, we can enable flags to make tokenizer recognize C++ and C comments by calling the slashSlashComments() for C++ comment style and slashStarComments() for C comment style.

Invoke lowerCaseMode() method to enable/disable a flag that determines whether or not word token are automatically lowercased. Call eolIsSignificant() method to enable/disable a flag that determines whether or not ends of line are treated as tokens.

DataInputStream and DataOutputStream

DataInputStream reads primitive data types that are written by DataOutputStream. DataOutputStream writes primitive data types for DataInputStream to read. DataInputStream reads written data in machine-independent way. Thus, a java application from different machine can use DataInputStream to read the written data that we have written in our machine.
import java.io.File;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    File file = new File("C:\\test\\test.dat");
    try(FileInputStream fis =
        new FileInputStream(file);
        DataInputStream das = 
        new DataInputStream(fis);
        FileOutputStream fos =
        new FileOutputStream(file);
        DataOutputStream dos =
        new DataOutputStream(fos)){
        
        float[] numbers = {2.5f,4.2f,6.6f,
                           8.1f,10.0f};
        boolean bool = true;
        
        //Write primitive types
        dos.writeInt(numbers.length);
        dos.writeBoolean(bool);
        for(int i = 0; i < numbers.length; i++)
          dos.writeFloat(numbers[i]);
        
        //Read primitive types
        //read the source data according to
        //the order of how the source data
        //have been written
        int ln = das.readInt();
        
        System.out.println("int: " + ln);
        System.out.println("boolean: " + das.readBoolean());
        System.out.print("floats: ");
        for(int i = 0; i < ln; i++)
          System.out.print(das.readFloat() + " ");
        
    }
  }
}

Result
int: 5
boolean: true
floats: 2.5 4.2 6.6 8.1 10.0
ObjectOutputStream and ObjectInputStream

ObjectOutputStream and ObjectInputStream are used for serializing/deserializing objects. The usage of these two classes are demonstrated in the Serialization and Deserialization topic.

No comments:

Post a Comment