Top Ten Myths about Java I/O


Top Ten Myths about Java I/O

Elliotte Rusty Harold

Javapolis

Wednesday, November 13, 2002

elharo@metalab.unc.edu

http://www.cafeaulait.org/


#10: Java can't talk to the serial and parallel ports

import javax.comm.*;
import java.util.*;
import java.io.*;


public class PortTyper {

  public static void main(String[] args) {

    if (args.length < 1) {
      System.out.println("Usage: java PortTyper portName");
      return;
    }

    try {    
      CommPortIdentifier com = 
       CommPortIdentifier.getPortIdentifier(args[0]);
      CommPort thePort  = com.open("PortOpener", 10);
      CopyThread input = 
       new CopyThread(System.in, thePort.getOutputStream());
      CopyThread output = 
       new CopyThread(thePort.getInputStream(), System.out);
      input.start();
      output.start();

    }
    catch (Exception e) {
      System.out.println(e);
    }

  }

}

class CopyThread extends Thread {

  InputStream theInput;
  OutputStream theOutput;

  CopyThread(InputStream in) {
    this(in, System.out);
  }

  CopyThread(OutputStream out) {
    this(System.in, out);
  }

  CopyThread(InputStream in, OutputStream out) {
    theInput = in;
    theOutput = out;
  }

  public void run() {

    try {
      byte[] buffer = new byte[256];
      while (true) {
        int bytesRead = theInput.read(buffer);
        if (bytesRead == -1) break;
        theOutput.write(buffer, 0, bytesRead);
      }
    }
    catch (IOException e) {
      System.err.println(e);
    }

  }

}

#9: All files are text / All text is ASCII

FileInputStream fis = new FileInputStream("symbol.txt");
InputStreamReader isr = new InputStreamReader(fis, "MacSymbol");

#8: Streams are thread safe

Here's the writeInt() method from java.io.DataOutputStream:

public final void writeInt(int v) throws IOException {
  OutputStream out = this.out;
  out.write((v >>> 24) & 0xFF);
  out.write((v >>> 16) & 0xFF);
  out.write((v >>>  8) & 0xFF);
  out.write((v >>>  0) & 0xFF);
  written += 4;
}

Solution:

  1. Never use the same stream in more than one thread

  2. Don't chain multiple filters in parallel to one underlying stream (series is OK)


#7: File dialogs are weak

import java.io.*;
import javax.swing.*;
import javax.swing.filechooser.*;


public class DirectoryChooser {

  public static void main(String[] args) {
    
    JFileChooser fc = new JFileChooser();
    fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
    
    int result = fc.showOpenDialog(new JFrame());
    if (result == JFileChooser.APPROVE_OPTION) {
      File dir = fc.getSelectedFile();
      String[] contents = dir.list();
      for (int i = 0; i < contents.length; i++) {
        System.out.println(contents[i]);
      }
    }
    
    System.exit(0);

  }
  
}

#6: File objects represent files

The following program returns various information about a file that can't exist on any platform!

import java.io.*;


public class FileInfo {

  public static void main(String[] args) {
  
    File f = new File("-What/a\bad file name?!" + '\u0000'); 
    System.out.println("getName: " + f.getName());
    System.out.println("getPath: " + f.getPath());
    System.out.println("getAbsolutePath: " + f.getAbsolutePath());
    System.out.println("getParent: " + f.getParent());
    if (f.isFile()) {
      System.out.println(f.getName() + " is a file.");
    }
    else if (f.isDirectory()) {
      System.out.println(f.getName() + " is a directory.");
    }
    else {
      System.out.println("What is this?");
    }
    if (f.isAbsolute()) {
      System.out.println(f.getName() + " is an absolute path.");
    }
    else {
      System.out.println(f.getName() + " is not an absolute path.");
    } 
  
  }

}

#5: File systems look like Unix


#4: The Console Matters

public class Homework1 {

  public static void main(String[] args) {
 
    System.out.println(
      "Please enter numbers, 1 to a line, -1 to quit");
    double sum = 0.0;
    while (true) {
      int i = System.readInt();
      if (i == -1) break;
      sum = sum + readInt();   
    } 
    System.out.println("The sum is " + sum);
  }

}

The right way:

public class Homework1 {

  public static void main(String[] args) {
 
    double sum = 0;
    for (int i = 0; i < args.length; i++) {
      sum = sum + Double.parseDouble(args[i]);   
    } 
    System.out.println("The sum is " + sum);
  }

}

#3: println() is a good idea


#2: Java can't format numbers

import java.text.*;


public class PrettierTable {

  public static void main(String[] args) {
  
    NumberFormat myFormat = NumberFormat.getNumberInstance();
    FieldPosition fp = new FieldPosition(NumberFormat.INTEGER_FIELD);
    myFormat.setMaximumIntegerDigits(3);
    myFormat.setMaximumFractionDigits(2);
    myFormat.setMinimumFractionDigits(2);
    
    System.out.println("Degrees  Radians  Grads");
    for (double degrees = 0.0; degrees < 360.0; degrees++) {
      String radianString = myFormat.format(Math.PI * degrees / 180.0, 
       new StringBuffer(), fp).toString();
      radianString = getSpaces(3 - fp.getEndIndex()) + radianString;
      String gradString = myFormat.format(400 * degrees / 360, 
       new StringBuffer(), fp).toString();
      gradString = getSpaces(3 - fp.getEndIndex()) + gradString;
      String degreeString = myFormat.format(degrees, 
       new StringBuffer(), fp).toString();
      degreeString = getSpaces(3 - fp.getEndIndex()) + degreeString;
      System.out.println(degreeString + "  " + radianString 
       + "  " + gradString);
    }
    
  }
    
  public static String getSpaces(int n) {
  
    StringBuffer sb = new StringBuffer(n);
    for (int i = 0; i < n; i++) sb.append(' ');
    return sb.toString();
    
  }
  
}

java PrettyTable | more
Degrees Radians Grads
000.00  000.00  000.00
001.00  000.01  001.11
002.00  000.03  002.22
003.00  000.05  003.33
004.00  000.06  004.44
005.00  000.08  005.55
006.00  000.10  006.66
007.00  000.12  007.77
008.00  000.13  008.88
009.00  000.15  010.00
010.00  000.17  011.11
011.00  000.19  012.22
012.00  000.20  013.33
013.00  000.22  014.44
014.00  000.24  015.55
015.00  000.26  016.66
016.00  000.27  017.77
017.00  000.29  018.88
018.00  000.31  020.00
019.00  000.33  021.11
020.00  000.34  022.22 

#1: Java I/O is Slow

  1. Buffer all streams

  2. Buffer all readers

  3. Use the new I/O APIs if possible:


Non Blocking I/O Example: Old Way

import java.net.*;
import java.io.*;
import java.util.*;

public class SingleFileHTTPServer extends Thread {

  private byte[] content;
  private byte[] header;
  private int port = 80;

  public SingleFileHTTPServer(String data, String encoding, 
   String MIMEType, int port) throws UnsupportedEncodingException {    
    this(data.getBytes(encoding), encoding, MIMEType, port);
  }

  public SingleFileHTTPServer(byte[] data, String encoding, 
   String MIMEType, int port) throws UnsupportedEncodingException {
    
    this.content = data;
    this.port = port;
    String header = "HTTP/1.0 200 OK\r\n"
     + "Server: OneFile 1.0\r\n"
     + "Content-length: " + this.content.length + "\r\n"
     + "Content-type: " + MIMEType + "\r\n\r\n";
    this.header = header.getBytes("ASCII");

  }

  
  public void run() {
  
    try {
      ServerSocket server = new ServerSocket(this.port); 
      System.out.println("Accepting connections on port " 
        + server.getLocalPort());
      System.out.println("Data to be sent:");
      System.out.write(this.content);
      while (true) {
        
        Socket connection = null;
        try {
          connection = server.accept();
          OutputStream out = new BufferedOutputStream(
                                  connection.getOutputStream()
                                 );
          InputStream in   = new BufferedInputStream(
                                  connection.getInputStream()
                                 );
          // read the first line only; that's all we need
          StringBuffer request = new StringBuffer(80);
          while (true) {
            int c = in.read();
            if (c == '\r' || c == '\n' || c == -1) break;
            request.append((char) c);
            // If this is HTTP/1.0 or later send a MIME header

          }
          if (request.toString().indexOf("HTTP/") != -1) {
            out.write(this.header);
          }         
          out.write(this.content);
          out.flush();
        }  // end try
        catch (IOException e) {   
        }
        finally {
          if (connection != null) connection.close(); 
        }
        
      } // end while
    } // end try
    catch (IOException e) {
      System.err.println("Could not start server. Port Occupied");
    }

  } // end run


  public static void main(String[] args) {

    try {
        
      String contentType = "text/plain";
      if (args[0].endsWith(".html") || args[0].endsWith(".htm")) {
        contentType = "text/html";
      }
      
      InputStream in = new FileInputStream(args[0]);
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      int b;
      while ((b = in.read()) != -1) out.write(b);
      byte[] data = out.toByteArray();
        
      // set the port to listen on
      int port;
      try {
        port = Integer.parseInt(args[1]);
        if (port < 1 || port > 65535) port = 80;
      }  
      catch (Exception e) {
        port = 80;
      }  
      
      String encoding = "ASCII";
      if (args.length > 2) encoding = args[2]; 
       
      Thread t = new SingleFileHTTPServer(data, encoding,
       contentType, port);
      t.start();         

    }
    catch (ArrayIndexOutOfBoundsException e) {
      System.out.println(
       "Usage: java SingleFileHTTPServer filename port encoding");
    }
    catch (Exception e) {
      System.err.println(e);
    }
  
  }

}

Non Blocking I/O Example: New Way

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.Iterator;
import java.net.*;

public class SingleFileHTTPServerNewIO {

  private ByteBuffer contentBuffer;
  private int port = 80;

  public SingleFileHTTPServerNewIO(ByteBuffer data, String encoding, 
   String MIMEType, int port) throws UnsupportedEncodingException {
    
    this.port = port;
    String header = "HTTP/1.0 200 OK\r\n"
     + "Server: OneFile 1.0\r\n"
     + "Content-length: " + data.limit() + "\r\n"
     + "Content-type: " + MIMEType + "\r\n\r\n";
    byte[] headerData = header.getBytes("ASCII");

    this.contentBuffer = ByteBuffer.allocateDirect(data.limit() + headerData.length);
    contentBuffer.put(headerData);
    contentBuffer.put(data);
    
  }
  
  public void run() throws IOException {
  
    ServerSocketChannel serverChannel = ServerSocketChannel.open();
    ServerSocket  serverSocket = serverChannel.socket();
    Selector selector = Selector.open();
    InetSocketAddress localPort = new InetSocketAddress(port);
    serverSocket.bind(localPort);
    serverChannel.configureBlocking(false);
    serverChannel.register(selector, SelectionKey.OP_ACCEPT);

    while (true) {
      int n = selector.select();
      if (n == 0)  continue;

      Iterator keys = selector.selectedKeys().iterator();
      while (keys.hasNext()) {
        SelectionKey key = (SelectionKey) keys.next();
        if (key.isAcceptable()) {
          ServerSocketChannel server = (ServerSocketChannel) key.channel();
          SocketChannel channel = server.accept();
          registerChannel(selector, channel, SelectionKey.OP_READ);
          sendFile(channel); 
        }
        keys.remove();
      }
    }
  }

  private void registerChannel (Selector selector, SelectableChannel channel, int ops)
    throws IOException {
    if (channel == null) return;
    channel.configureBlocking(false);
    channel.register(selector, ops);
  }

  private ByteBuffer buffer = ByteBuffer.wrap(new byte[4096]);

  private String readHeader(SelectionKey key) throws IOException {

     SocketChannel channel = (SocketChannel) key.channel();
     int count;

     buffer.clear();
     
     // read the HTTP header
     StringBuffer request = new StringBuffer(80);
     while ((count = channel.read(buffer)) > 0) {
        buffer.flip();
        byte[] data = buffer.array();
        request.append(new String(data, 0, buffer.limit()-1, "ASCII"));    
        System.out.println(request);
        if (request.toString().endsWith("\n")) return request.toString();
        
        buffer.clear();
      }

      if (count < 0) channel.close();
      return request.toString();
      
  }

  private void sendFile (SocketChannel channel) throws IOException {
     contentBuffer.clear(); // this actually resets but does not clear data
     channel.write(contentBuffer);
     channel.close();
  }  

  public static void main(String[] args) {

    try {
      String contentType = "text/plain";
      if (args[0].endsWith(".html") || args[0].endsWith(".htm")) {
        contentType = "text/html";
      }
      
      FileInputStream fin = new FileInputStream(args[0]);
      FileChannel in = fin.getChannel();
      MappedByteBuffer input = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
        
      // set the port to listen on
      int port;
      try {
        port = Integer.parseInt(args[1]);
        if (port < 1 || port > 65535) port = 80;
      }  
      catch (Exception e) {
        port = 80;
      }  
      
      String encoding = "ASCII";
      if (args.length > 2) encoding = args[2]; 
       
      SingleFileHTTPServerNewIO server = new SingleFileHTTPServerNewIO(input, encoding,
       contentType, port);
      server.run();         

    }
    catch (Exception ex) {
      ex.printStackTrace();
      System.err.println(ex);
    }
  
  }

}

Memory Mapped I/O Example: Old Way

import java.io.*;

public class FileCopier {

  public static void main(String[] args) {

    if (args.length != 2) {
      System.err.println("Usage: java FileCopier infile outfile");
			return;
    }
    try {
      copy(args[0], args[1]);
    }
    catch (IOException e) {
      System.err.println(e);
    }

  }

  public static void copy(String inFile, String outFile) 
   throws IOException {

    InputStream in = null;
    OutputStream out = null;
    
    try {
      in  = new FileInputStream(inFile);
      out = new FileOutputStream(outFile);
      for (int b = in.read(); b != -1; b = in.read()) {
         out.write(b);    
      }
    }
    finally {
      try {
        if (in != null) in.close();
      }
      catch (IOException e) {}
      try {
        if (out != null) out.close();
       }
      catch (IOException e) {}
    }

  }

}


Memory Mapped I/O Example: New Way

import java.io.*;
import java.nio.*;
import java.nio.channels.*;

public class NewFileCopier {

  public static void main(String[] args) {

    if (args.length != 2) {
      System.err.println("Usage: java NewFileCopier infile outfile");
			return;
    }
    try {
      copy(args[0], args[1]);
    }
    catch (IOException e) {
      System.err.println(e);
    }

  }

  public static void copy(String inFile, String outFile) 
   throws IOException {

    FileInputStream fin = null;
    RandomAccessFile fout = null;
    
    try {
      fin  = new FileInputStream(inFile);
      fout = new RandomAccessFile(outFile, "rw");
      
      FileChannel in = fin.getChannel();
      FileChannel out = fout.getChannel();
      
      MappedByteBuffer input = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
      MappedByteBuffer output = out.map(FileChannel.MapMode.READ_WRITE, 0, in.size());
      output.put(input);
      
    }
    finally {
      try {
        if (fin != null) fin.close();
      }
      catch (IOException e) {}
      try {
        if (fout != null) fout.close();
       }
      catch (IOException e) {}
    }

  }

}


Real Problems with Java I/O


Summing Up


To Learn More


Index | Cafe au Lait |Cafe con Leche

Copyright 1999, 2002 Elliotte Rusty Harold
elharo@metalab.unc.edu
Last Modified October 9, 2002