Java I/O总结

Java中I/O操作主要是指使用Java进行输入,输出操作. Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。

数据流是一串连续不断的数据的集合,就象水管里的水流,在水管的一端一点一点地供水,而在水管的另一端看到的是一股连续不断的水流。数据写入程序可以是一段、一段地向数据流管道中写入数据,这些数据段会按先后顺序形成一个长的数据流。对数据读取程序来说,看不到数据流在写入时的分段情况,每次可以读取其中的任意长度的数据,但只能先读取前面的数据后,再读取后面的数据(不能随机读取)。不管写入时是将数据分多次写入,还是作为一个整体一次写入,读取时的效果都是完全一样的。

简而言之:数据流是一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。

当程序需要读取数据的时候,就会建立一个通向数据源的连接,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会建立一个通向目的地的连接。

数据流分类:

流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种: 1) 字节流:数据流中最小的数据单元是字节 2) 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。

Java I/O主要包括如下3层次:

  1. 流式部分——最主要的部分。如:OutputStream、InputStream、Writer、Reader等
  2. 非流式部分——如:File类、RandomAccessFile类和FileDescriptor等类
  3. 其他——文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。

主要类如下:

  1. File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
  2. InputStream(字节流,二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
  3. OutputStream(字节流,二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
  4. Reader(字符流,文本格式操作):抽象类,基于字符的输入操作。
  5. Writer(字符流,文本格式操作):抽象类,基于字符的输出操作。
  6. 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();读取当前流中的下一个字节、并以整数形式返回、若读取到文件结尾则返回-1int 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

posted @ 2018-02-28 23:37  李志的妈妈  阅读(214)  评论(0编辑  收藏  举报