Java中I/O操作主要是指使用Java进行输入,输出操作. Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。
数据流是一串连续不断的数据的集合,就象水管里的水流,在水管的一端一点一点地供水,而在水管的另一端看到的是一股连续不断的水流。数据写入程序可以是一段、一段地向数据流管道中写入数据,这些数据段会按先后顺序形成一个长的数据流。对数据读取程序来说,看不到数据流在写入时的分段情况,每次可以读取其中的任意长度的数据,但只能先读取前面的数据后,再读取后面的数据(不能随机读取)。不管写入时是将数据分多次写入,还是作为一个整体一次写入,读取时的效果都是完全一样的。
简而言之:数据流是一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。
当程序需要读取数据的时候,就会建立一个通向数据源的连接,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会建立一个通向目的地的连接。
数据流分类:
流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种: 1) 字节流:数据流中最小的数据单元是字节 2) 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。
Java I/O主要包括如下3层次:
- 流式部分——最主要的部分。如:OutputStream、InputStream、Writer、Reader等
- 非流式部分——如:File类、RandomAccessFile类和FileDescriptor等类
- 其他——文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。
主要类如下:
- File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
- InputStream(字节流,二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
- OutputStream(字节流,二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
- Reader(字符流,文本格式操作):抽象类,基于字符的输入操作。
- Writer(字符流,文本格式操作):抽象类,基于字符的输出操作。
- RandomAccessFile(随机文件操作):它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
最详细的结构图如下:
下面对java io 中涉及的主要类及用法进行介绍:
一、字节流
1. InputStream 和 OutputStream
InputStream 和 OutputStream为各种输入输出字节流的基类,所有字节流都继承这两个基类。
2. FileInputStream 和 FileOutputStream
这两个从字面意思很容易理解,是对文件的字节流操作,也会最常见的IO操作流。
例如通过FileInputStream和FileOutStream进行文件复制的代码
private static void copyFileUsingFileStreams(File source, File dest) throws IOException { InputStream input = null; OutputStream output = null; try { input = new FileInputStream(source); output = new FileOutputStream(dest); byte[] buf = new byte[1024]; int bytesRead; while ((bytesRead = input.read(buf)) > 0) { output.write(buf, 0, bytesRead); } } finally { input.close(); output.close(); } }
注:上面的文件复制方式并不是最优的,比较好的方法是通过NIO中的Channels进行操作,附上 Apache Commons-IO中FileUtils文件复制的源码
private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException { if (destFile.exists() && destFile.isDirectory()) { throw new IOException("Destination '" + destFile + "' exists but is a directory"); } FileInputStream fis = null; FileOutputStream fos = null; FileChannel input = null; FileChannel output = null; try { fis = new FileInputStream(srcFile); fos = new FileOutputStream(destFile); input = fis.getChannel(); output = fos.getChannel(); long size = input.size(); long pos = 0; long count = 0; while (pos < size) { count = (size - pos) > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : (size - pos); pos += output.transferFrom(input, pos, count); } } finally { IOUtils.closeQuietly(output); IOUtils.closeQuietly(fos); IOUtils.closeQuietly(input); IOUtils.closeQuietly(fis); } if (srcFile.length() != destFile.length()) { throw new IOException("Failed to copy full contents from '" + srcFile + "' to '" + destFile + "'"); } if (preserveFileDate) { destFile.setLastModified(srcFile.lastModified()); } }
3. ByteArrayInputStream 和 ByteArrayOutputStream
该类从内存中的字节数组中读取数据,它的数据源是一个字节数组,它们分别继承自InputStream 和 OutputStream。
ByteArrayInputStream类的构造方法包括:
ByteArrayInputStream(byte[] buf)--------参数buf指定字节数组类型的数据源。
ByteArrayInputStream(byte[] buf, int offset, int length)-----参数buf指定字节数组类型数据源,参数offset指定从数组中开始读取数据的起始下标位置,length指定从数组中读取的字节数。
private static byte[] readWithByteArray(byte[] dataSource) { ByteArrayInputStream in = null; ByteArrayOutputStream out = null; try { in = new ByteArrayInputStream(dataSource); out = new ByteArrayOutputStream(); int len = 0; byte[] buffer = new byte[1024]; while ((len = in.read(buffer, 0, buffer.length)) != -1){ out.write(buffer, 0, len); } return out.toByteArray(); } catch (IOException e) { e.printStackTrace(); } finally { try { in.close(); } catch (IOException e1) { } try { out.close(); } catch (IOException e1) { } } }
4. FilterInputStream 和 FilterOutputStream
过滤器字节流FilterInputStream/FilterOutputStream 的作用是用来“封装其它的输入流/输出流,并为它们提供额外的功能”,这里用到了装饰器模式(Decorator),它的主要用途在于给一个对象动态的添加一些额外的职责。与生成子类相比,它更具有灵活性。
以FilterInputStream为例,它的构造器中传入的是InputStream对象,可以对ByteArrayInputStream、FileInputStream进行装饰。
//一般方法 int avaliable();查看当前流中可供读取的字节数。 void close();关闭当前流、释放所有与当前流有关的资源。 synchronized void mark(int readlimit);标记当前流的读取的位子。 boolean markSupport();查看当前流是否支持mark。 int read();读取当前流中的下一个字节、并以整数形式返回、若读取到文件结尾则返回-1。 int read(byte[] b);将当前流中的字节读取到字节数组b中、返回实际读取的字节数 int read(byte[] b, int off, int len);将当前流中的len个字节读取到从下标off开始存放的字节数组b中。 synchronized reset();重置当前流的读取位置到最后一次调用mark方法标记的位置。 long skip(long n);跳过(抛弃)当前流中n个字节。返回实际抛弃的字节数。
它的常用的子类有BufferedInputStream和DataInputStream。
BufferedInputStream的作用就是为“输入流提供缓冲功能,以及mark()和reset()功能”。
DataInputStream 是用来装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。应用程序可以使用DataOutputStream(数据输出流)写入由DataInputStream(数据输入流)读取的数据。
5.DataInputStream和DataOutputStream
DataInputStream 是数据输入流,它继承于FilterInputStream。
DataOutputStream 是数据输出流,它继承于FilterOutputStream。
二者配合使用,“允许应用程序以与机器无关方式从底层输入流中读写基本 Java 数据类型”。
/** * DataOutputStream的API测试函数 */ private static void testDataOutputStream() { DataOutputStream out = null; try { File file = new File("file.txt"); out = new DataOutputStream(new FileOutputStream(file)); out.writeBoolean(true); out.writeByte((byte)0x41); out.writeChar((char)0x4243); out.writeShort((short)0x4445); out.writeInt(0x12345678); out.writeLong(0x0FEDCBA987654321L); out.writeUTF("abcdefg"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { out.close(); } catch(IOException e) { } } } /** * DataInputStream的API测试函数 */ private static void testDataInputStream() { DataInputStream in = null; try { File file = new File("file.txt"); in = new DataInputStream(new FileInputStream(file)); System.out.printf("byteToHexString(0x8F):0x%s\n", byteToHexString((byte)0x8F)); System.out.printf("charToHexString(0x8FCF):0x%s\n", charToHexString((char)0x8FCF)); System.out.printf("readBoolean():%s\n", in.readBoolean()); System.out.printf("readByte():0x%s\n", byteToHexString(in.readByte())); System.out.printf("readChar():0x%s\n", charToHexString(in.readChar())); System.out.printf("readShort():0x%s\n", shortToHexString(in.readShort())); System.out.printf("readInt():0x%s\n", Integer.toHexString(in.readInt())); System.out.printf("readLong():0x%s\n", Long.toHexString(in.readLong())); System.out.printf("readUTF():%s\n", in.readUTF()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { in.close(); } catch(IOException e) { } } }
6.BufferedInputStream和BufferedOutputStream
上面通过FileInputStream和FileOutStream进行文件复制的例子中,
使用了一个byte数组来作为数据读入的缓冲区,以文件存取为例,硬盘存取的速度远低于内存中的数据存取速度。为了减少对硬盘的存取,通常从文件中一次读入一定长度的数据,而写入时也是一次写入一定长度的数据,这可以增加文件存取的效率。
java.io.BufferedInputStream与java.io.BufferedOutputStream可以为InputStream、OutputStream类的对象增加缓冲区功能。
构建BufferedInputStream实例时,需要给定一个InputStream类型的实例,实现BufferedInputStream时,实际上最后是实现InputStream实例。
同样地,在构建BufferedOutputStream时,也需要给定一个OutputStream实例,实现BufferedOutputStream时,实际上最后是实现OutputStream实例。
BufferedInputStream的数据成员buf是一个位数组,默认为2048字节。当读取数据来源时,例如文件,BufferedInputStream会尽量将buf填满。当使用read()方法时,实际上是先读取buf中的数据,而不是直接对数据来源作读取。当buf中的数据不足时,BufferedInputStream才会再实现给定的InputStream对象的read()方法,从指定的装置中提取数据。
BufferedOutputStream的数据成员buf是一个位数组,默认为512字节。当使用write()方法写入数据时,实际上会先将数据写至buf中,当buf已满时才会实现给定的OutputStream对象的write()方法,将buf数据写至目的地,而不是每次都对目的地作写入的动作。
同样以文件复制的为例(带缓冲):
public static void main(String[] args) { try { BufferedInputStream bis=new BufferedInputStream(new FileInputStream("f:/a.mp3")); BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("f:/b.mp3")); byte[] b=new byte[1024]; int len=0; while((len=bis.read(b))!=-1){ bos.write(b,0,len); } } catch (FileNotFoundException e) { System.out.println("文件找不到"); e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ if(null!=bos){ bos.close(); } if(null!=bis){ bis.close(); } } }
二、字符流
未完待续。。。
参考:
http://www.importnew.com/23708.html
http://blog.csdn.net/wangbaochu/article/details/53484042