首页 > 代码库 > Java IO(一)

Java IO(一)

一、流的概念

流(Stream)的概念源自UNIX中管道的概念,管道是一条不间断的字节流,用来实现程序或进程之间的通信。一个流必有源端和目的端(可以是内存、磁盘文件等。)流的源端和目的端可以简单的看成字节的生产者和消费者。


二、流的分类

根据<script type="math/tex" id="MathJax-Element-541">\color{red}{读写位置}</script>流分为:

  1. <script type="math/tex" id="MathJax-Element-542">\color{blue}{结点流}</script>:接从指定的位置(如磁盘文件或内存区域)读或写
  2. <script type="math/tex" id="MathJax-Element-543">\color{blue}{过滤器}</script>:输入流往往是以其它输入流作为它的输入源,经过过滤或处理后再以新的输入流的形式提供给用户,输出流的原理也类似。

根据<script type="math/tex" id="MathJax-Element-544">\color{red}{流的方向}</script>分为:

  1. <script type="math/tex" id="MathJax-Element-545">\color{blue}{输入流}</script>:用户可以从输入流中读取信息 ,但是不能往里写信息。
  2. <script type="math/tex" id="MathJax-Element-546">\color{blue}{输出流}</script>:用户可以往里写信息,但是不能从中读出信息。

根据<script type="math/tex" id="MathJax-Element-547">\color{red}{操作对象的类型是字符还是字节}</script>分为两类:

  1. <script type="math/tex" id="MathJax-Element-548">\color{blue}{字节流}</script>
  2. <script type="math/tex" id="MathJax-Element-549">\color{blue}{字符流}</script>

Java IO流的继承结构图如下:
技术分享

三、常用流的详解

文件操作类:File

在讲解流之前,先讲解一下之后和流操作紧密相关的文件操作类File<script type="math/tex" id="MathJax-Element-324">\color{red}{File类}</script>
File类是一个与文件本身操作相关的类——文件的创建、删除、重命名、取得文件大小和修改日期等。


File类中常用的构造方法和操作方法

No. 方法 类型 描述
1 public File(String pathName) 构造 给定一个要操作文件的完整路径
2 public boolean exists() 普通 判定给定路径是否存在
3 public File getParentFile() 普通 找到一个指定路径的父路径
4 public boolean mkdirs() 普通 创建指定目录
5 public boolean createNewFile() 普通 创建文件
6 public boolean delete() 普通 删除文件
7 public boolean isDirectory() 普通 判断给定路径是否是文件夹
8 public boolean isFile() 普通 判断给定路径是否是文件
9 public boolean isHidden() 普通 判断是否隐藏
10 public boolean renameTo(File dest) 普通 为文件重命名
11 public long lastModified() 普通 文件的最后一次修改时间
12 public long length() 普通 取得文件的大小
13 public String getName() 普通 取得文件名称
14 public [] File listFiles() 普通 将目录中的文件以File对象数组的方式返回

代码示例

import java.util.Date;
import java.text.SimpleDateFormat;
import java.io.File;
public class TestDemo{
    public static void main(String [] args)throws Exception{
        File file = new File("/home/linyimin/DaSi/java/demo.txt");
        // 如果文件存在,获取并输出文件信息
        if(file.exists()){
            getInfo(file);
        }
        // 如果不存在,先判断文件的父路径是否存在,不存在先创建目录在创建文件,否则直接创建文件
        else if(file.getParentFile().exists()){
            // 创建文件
            file.createNewFile();
            // 获取文件信息
            getInfo(file);
        }
        else{
            // 创建目录
            file.getParentFile().mkdirs();
            // 创建文件
            file.createNewFile();
            // 获取文件信息
            getInfo(file);
        } 
        // 为文件重命名,并取得重命名后的文件信息
        File f = new File("/home/linyimin/DaSi/java/TestDemo.txt");
        file.renameTo(f);
        File newFile = new File("/home/linyimin/DaSi/java/TestDemo.txt");
        System.out.println("崇重命名之后的文件信息:");
        getInfo(newFile);
        // 删除文件
        newFile.delete();
    }

    /*
     * 获取文件的各种信息
     * @param file 目标操作文件
    **/
    public static void getInfo(File file){
        // 判断指定路径是否是目录
        System.out.println(file.getName() + (file.isDirectory()?"是一个目录":"不是一个目录"));
        // 判断是否是文件
        System.out.println(file.getName() + (file.isFile()?"是一个文件":"不是一个文件"));
        // 判断是否为隐藏文件
        System.out.println(file.getName() + (file.isHidden()?"是隐藏文件":"不是隐藏文件"));
        // 获取文件的最后修改时间
        long temp = file.lastModified();
        // 将long型数据转换称Date型数据
        Date date = new Date(temp);
        // 将Date型数据转换称时间字符串
        String str = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(date);
        System.out.println(str);
        // 输出文件大小
        System.out.println(file.getName() + ":" + file.length() + "B");
    }
}

字节流

  • 所有的InputStream<script type="math/tex" id="MathJax-Element-325">\color{red}{字节输入流都直接或间接继承自InputStream抽象类}</script>,输入流作为数据的来源,我们可以通过输入流的read方法读取字节数据;

  • 所有的OutputStream<script type="math/tex" id="MathJax-Element-326">\color{red}{输出流都直接或间接继承自OutputStream抽象类}</script>,输出流接收数据,可以通过write方法写入字节数据。

对于InputStream、OutputStream类而言,本身定义的是一个抽象类(abstract class),按照抽象类的使用原则来讲,需要定义抽象类的子类,根据功能的不同,使用不同的子类完成。<script type="math/tex" id="MathJax-Element-327">\color{red}{按照面向对象的开发原则,子类要为抽象类进行对象的实例化,而后调用的方法以父类中定义的方法为主,方法的具体实现由实例化这个父类的子类完成的。所以对于子类用户最关心的是构造方法}</script>下面主要讲解几种常用的IO操作和其对应定义的子类。

文件操作:FileInputStream & FileOutputStream

  • 程序通过FileInputStream<script type="math/tex" id="MathJax-Element-377">\color{red}{FileInputStream能够将文件作为数据源,读取文件中的流}</script>,通过File对象或文件路径等初始化,在其构造函数中,如果传入的File对象(或与其相对应的文件路径所表示的File对象)不存在或是一个目录而不是文件或者由于其他原因无法打开读取数据,都会导致在初始化阶段导致抛出FileNotFoundException异常;

  • 程序可以通过FileOutputStream<script type="math/tex" id="MathJax-Element-378">\color{red}{FileOutputStream向文件中写入数据}</script>,也需要通过File对象或文件路径对其初始化,如同FileInputStream ,如果传入的File对象(或与其相对应的文件路径所表示的File对象)是一个目录而不是文件或者由于其他原因无法创建该文件写入数据,都会导致在初始化阶段抛出FileNotFoundException异常

文件操作流的常用方法

类名称 No. 方法名称 类型 描述
FileInputStream<script type="math/tex" id="MathJax-Element-379">\color{blue}{FileInputStream}</script> 1 public FileInputStream(File file) <script type="math/tex" id="MathJax-Element-380">\color{red}{构造}</script> 实例化FileInputStream,用于从指定文件中读取数据
FileInputStream<script type="math/tex" id="MathJax-Element-381">\color{blue}{FileInputStream}</script> 2 public FileInputStream(Stirng name) <script type="math/tex" id="MathJax-Element-382">\color{red}{构造}</script> 实例化FileInputStream,用于从指定文件路径中读取数据
InputStream 3 public void close() 普通 关闭输入流
InputStream 4 public int read() 普通 从指定输入流中读取一个字节数据
InputStream 5 public int read(byte [] data) 普通 从指定流中读取多个字节
InputStream 6 public int read(byte [] data, int off, int len) 普通 从指定流中读取指定多个字节
FileOutputStream<script type="math/tex" id="MathJax-Element-383">\color{blue}{FileOutputStream}</script> 7 public FileOutputStream(File file) <script type="math/tex" id="MathJax-Element-384">\color{red}{构造方法}</script> 实例化FileOuputStream,用于新建数据
FileOutputStream<script type="math/tex" id="MathJax-Element-385">\color{blue}{FileOutputStream}</script> 8 public FileOutputStream(File file,boolean append) <script type="math/tex" id="MathJax-Element-386">\color{red}{构造方法}</script> 实例化FileOutputStream,用于追加数据
OutputStream 9 public void close() 普通 关闭输出流
OutputStream 10 public abstract void write(int b) 普通 向文件输出单个字节
OutputStream 11 public void write(byte [] b) 普通 向文件输出一组字节数据
OutputStream 12 public void write(byte [] b, int off, int len) 普通 向文件输出部分字节数据

代码示例

import java.io.File;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class TestDemo{
    public static void main(String [] args)throws Exception{
        // 指定操作文件
        File fileOut = new File("/home/linyimin/DaSi/java/OutputStream.txt");
        File fileIn = new File("/home/linyimin/DaSi/java/InputStream.txt");
        // 实例化FileOutputStream,向文件添加数据
        OutputStream out = new FileOutputStream(fileOut);
        // 实例化FileInputStream,从文件中读取数据
        InputStream in = new FileInputStream(fileIn);
        String str = "Hello World.";
        // 将字符串转换称字节数组
        byte [] data = http://www.mamicode.com/str.getBytes();"hljs-comment">// 向文件中写如数据
        out.write(data);
        // 从文件中读取数据并输出
        int len = 0;
        byte [] temp = new byte[1024];
        while(len != -1){
            len = in.read(temp);
            if(len != -1){
                System.out.println(new String(temp, 0, len));   
            }
        }
        // 关闭输出流
        out.close();
        // 关闭输入流
        in.close();
    }
}

在以上的例子中,通过FileInputStream的read()方法从/home/linyimin/DaSi/java/InputStream.txt文件中读取信息,通过FileOutputStream的write()方法向/home/linyimin/DaSi/java/InputStream.txt文件写入“Hello World.”。在此操作中如果InputStream.txt已经存在,会先清空内容在写入信息,如果不存在,会自动创建InputStream.txt文件后在写入数据。如果希望在已存在的文件之后添加数据,应使用public FileOutputStream(fileOut,true)构造方法进行实例化对象,表示向已有文件中追加写入数据而不是覆盖已有数据。


内存操作:ByteArrayInputStream & ByteArrayOutputStream

在某个操作需要发生IO操作,但又不希望有一些临时文件产生,可以使用内存操作流完成,即以内存为操作的终端,以发生IO操作关系。用于以IO流的方式来完成对字节数组内容的读写,来支持类似内存虚拟文件或者内存映射文件的读写。

  • ByteArrayInputStream:字节数组输入流,继承自InputStream,它会在内存中创建一个字节数组缓冲区,实例化后的字节数组会保存在此缓冲区中。也就是使<script type="math/tex" id="MathJax-Element-338">\color{red}{使用一个字节数组当中所有的数据作为数据源,程序可以像输入流方式一样读取字节,可以看作一个虚拟文件,用文件的方式读取里面的数据}</script>。
  • ByteArrayOutputStream:字节数组输出流,继承自OutputStream,是与ByteArrayInputStream相对应的输出流。此类实现一个字节输出流,数据被写入一个byte字节数组。缓冲区会随着数据的不断写入而自动增长。

内存操作类的常用方法

类名称 No. 方法 类型 描述
ByteArrayInputStream<script type="math/tex" id="MathJax-Element-339">\color{red}{ByteArrayInputStream}</script> 1 public ByteArrayInputStream(byte [] buf) <script type="math/tex" id="MathJax-Element-340">\color{blue}{构造}</script> 将字节流转换称输入流
ByteArrayInputStream<script type="math/tex" id="MathJax-Element-341">\color{red}{ByteArrayInputStream}</script> 2 public int read() 普通 从输入流中读取一个字节数据
ByteArrayInputStream<script type="math/tex" id="MathJax-Element-342">\color{red}{ByteArrayInputStream}</script> 3 public int read(byte [] data) 普通 从指定流中读取多个字节
ByteArrayInputStream<script type="math/tex" id="MathJax-Element-343">\color{red}{ByteArrayInputStream}</script> 4 public int read(byte [] data, int off, int len) 普通 从指定流中读取指定多个字节
ByteArrayOutputStream<script type="math/tex" id="MathJax-Element-344">\color{blue}{ByteArrayOutputStream}</script> 5 public ByteArrayOutputStream() <script type="math/tex" id="MathJax-Element-345">\color{red}{构造}</script> 创建一个32个字节
ByteArrayOutputStream<script type="math/tex" id="MathJax-Element-346">\color{blue}{ByteArrayOutputStream}</script> 6 public ByteArrayOutputStream(int size) <script type="math/tex" id="MathJax-Element-347">\color{red}{构造}</script> 根据参数指定大小创建缓冲区
ByteArrayOutputStream<script type="math/tex" id="MathJax-Element-348">\color{blue}{ByteArrayOutputStream}</script> 7 public void write(int b) 普通 往输出流中写入一个字节数据
ByteArrayOutputStream<script type="math/tex" id="MathJax-Element-349">\color{blue}{ByteArrayOutputStream}</script> 8 public void write(byte [] b) 普通 往输出流中写入一个字节数组数据
ByteArrayOutputStream<script type="math/tex" id="MathJax-Element-350">\color{blue}{ByteArrayOutputStream}</script> 9 public void write(byte [] b, int off, int len) 普通 往输出流中写入指定多个字节数据
ByteArrayOutputStream<script type="math/tex" id="MathJax-Element-351">\color{blue}{ByteArrayOutputStream}</script> 10 public String toString() 普通 使用默认编码将缓冲区数据编码成字符串并返回
ByteArrayOutputStream<script type="math/tex" id="MathJax-Element-352">\color{blue}{ByteArrayOutputStream}</script> 11 public byte [] toByteArray() 普通 将缓冲区数据通过字节数组返回
代码示例
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class TestDemo{
    public static void main(String [] args)throws Exception{
        String str = "abcdefghijklmnopqrstuvwsyz";
        // 将字符串转换成字节数组
        byte [] data = http://www.mamicode.com/str.getBytes();"hljs-comment">// 将字节数组转换成输入流,即将数据输出到内存
        InputStream in = new ByteArrayInputStream(data);
        // 字节输出流的实例化,即从内存读取数据
        OutputStream out = new ByteArrayOutputStream();
        // 从ByteArrayInputStream中读取多个字节数据
        byte [] b = new byte[1024];
        int len = in.read(b);
        System.out.println(new String(b, 0, len));
        // 重置标志位置,从第一个字节开始读取字节数据
        in.reset();
        // 从ByteArrayInputStream中一个字节接一个字节读取数据
        int temp = in.read();
        while(temp != -1){
            // 往输出流写入从输入流中读取的数据,并把数据变为大写
            out.write(Character.toUpperCase(temp));
            System.out.print((char)temp + " ");
            temp = in.read();
        }
        System.out.println();
        // 从输出流中获取数据,并转换成字符数据
        System.out.println(out.toString());
    }
}

程序运行结果:
abcdefghijklmnopqrstuvwsyz
a b c d e f g h i j k l m n o p q r s t u v w s y z
ABCDEFGHIJKLMNOPQRSTUVWSYZ


在上面的例子中,我们通过字符串获取字节数组将其作为ByteArrayInputStream的数据流来源,然后通过读取ByteArrayInputStream的数据,将读到的数据写入到ByteArrayOutputStream中。


键盘操作:System.in

System.in是System类中的一个InputStream类型的常量,用于系统输入。系统输入针对标准的输入设备——键盘,也就是俗称的键盘输入数据,由于System.in是InputStream类型数据,所以接收的数据是字节型。通过调用read()函数完成键盘输入数据操作。


代码示例
import java.io.IOException;
import java.io.InputStream;

public class TestDemo{
    public static void main(String [] args) throws IOException{
        InputStream in = System.in;
        byte [] data = http://www.mamicode.com/new byte[1024];
        // 从键盘输入多个数据,以回车键结束输入
        System.out.println("输入多个字节数据:");
        int len = in.read(data);
        System.out.println(new String(data, 0, len));
        System.out.println("一个字节接一个字节输入数据:");
        int temp = in.read();
        while(temp != -1){
            // 键入回车键,则退出输出
            if(temp == ‘\n‘){
                break;
            }
            // 将输入的字母变为大写字母
            System.out.print((char)Character.toUpperCase(temp) + " ");
            // 从键盘输入一个字节数据
            temp = in.read();
        }
    }
}

程序运行结果:
输入多个字节数据:
Hello 中国
Hello 中国

一个字节接一个字节输入数据:
Hello 中国
H E L L O ? ? - ? ? ?


在上面的例子中,可以发现从键盘输入数据时,既可以多个字节数据同时输入,也可以一个接一个字节输入,但应注意的是,<script type="math/tex" id="MathJax-Element-387">\color{red}{在单个字节输入时,中文输出会发生乱码}</script>,一般在进行中文操作时,应使用字符流完成。


缓冲区操作:BufferedInputStream & BufferedOutputStream

  • BufferedInputStream是缓冲输入流,继承于FilterInputStream。作用是为其它输入流提供缓冲功能。BufferedInputStream会将输入流数据分批读取,每次读取一部分数据到缓冲中,操作完缓冲中的数据之后,在从输入流中读取下一部分的数据。即BufferedInputStream内部有一个字节数组缓冲区,每次执行read操作的时候就从这buf中读取数据,从buf中读取数据没有多大的开销。如果buf中已经没有了要读取的数据,那么就去执行其内部绑定的InputStream的read方法,而且是一次性读取很大一块数据,以便填充满buf缓冲区。于我们在执行BufferedInputStream的read操作的时候,很多时候都是从缓冲区中读取的数据,这样就大大减少了实际执行其指定的InputStream的read操作的次数,也就提高了读取的效率。

  • BufferedOutputStream是输出缓冲流,与BufferedInputStream相对,继承于FilterOutputStream。作用是为输出流提供缓冲功能。BufferedOutputStream内部有一个字节缓冲区buf,在执行write操作时,将要写入的数据先一起缓存在一起,将其存入字节缓冲区buf中,当buf被填充完毕的时候会调用BufferedOutputStream的flushBuffer方法,该方法会通过调用其绑定的OutputStream的write方法将buf中的数据进行实际的写入操作并将buf的指向归零(可以看做是将buf中的数据清空)。如果想让缓存区buf中的数据理解真的被写入OutputStream中,可以调用flush方法,flush方法内部会调用flushBuffer方法。由于buf的存在,会大大减少实际执行OutputStream的write操作的次数,优化了写的效率。

缓冲操作的常用方法

类名称 No. 方法 类型 描述
BufferedInputStream<script type="math/tex" id="MathJax-Element-354">\color{red}{BufferedInputStream}</script> 1 public BufferedInputStream(InputStream in) <script type="math/tex" id="MathJax-Element-355">\color{blue}{构造}</script> 实例化BufferedInputStream,使用默认缓冲区大小
BufferedInputStream<script type="math/tex" id="MathJax-Element-356">\color{red}{BufferedInputStream}</script> 2 public BufferedInputStream(InputStream in, int size) <script type="math/tex" id="MathJax-Element-357">\color{blue}{构造}</script> 实例化BufferedInputStream,指定缓冲区大小
BufferedInputStream<script type="math/tex" id="MathJax-Element-358">\color{red}{BufferedInputStream}</script> 3 public int read() 普通 从输入流中读取一个字节数据
BufferedInputStream<script type="math/tex" id="MathJax-Element-359">\color{red}{BufferedInputStream}</script> 4 public int read(byte [] data) 普通 从指定流中读取多个字节
BufferedInputStream<script type="math/tex" id="MathJax-Element-360">\color{red}{BufferedInputStream}</script> 5 public int read(byte [] data, int off, int len) 普通 从指定流中读取指定多个字节
BufferedOutputStream<script type="math/tex" id="MathJax-Element-361">\color{blue}{BufferedOutputStream}</script> 6 public BufferedOutputStream(OutputStream out) <script type="math/tex" id="MathJax-Element-362">\color{red}{构造}</script> 实例化BufferedOutputStream,使用默认缓冲区大小
BufferedOutputStream<script type="math/tex" id="MathJax-Element-363">\color{blue}{BufferedOutputStream}</script> 7 public BufferedOutputStream(OutputStream out, int size) <script type="math/tex" id="MathJax-Element-364">\color{red}{构造}</script> 实例化BufferedOutputStream,指定缓冲区大小
BufferedOutputStream<script type="math/tex" id="MathJax-Element-365">\color{blue}{BufferedOutputStream}</script> 8 public void write(int b) 普通 向输出缓冲区写入一个字节数据
BufferedOutputStream<script type="math/tex" id="MathJax-Element-366">\color{blue}{BufferedOutputStream}</script> 9 public void write(byte [] data) 普通 向输出缓冲区写入多个字节数据
BufferedOutputStream<script type="math/tex" id="MathJax-Element-367">\color{blue}{BufferedOutputStream}</script> 10 public void write(byte [] data, int off, int len) 普通 向输出缓冲取写入指定多个字节
BufferedOutputStream<script type="math/tex" id="MathJax-Element-368">\color{blue}{BufferedOutputStream}</script> 11 public void flush() 普通 清空缓冲区,缓存区buf中的数据理解真的被写入OutputStream中

代码示例

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestDemo{
    public static void main(String [] args) throws IOException{
        File fio = new File(File.separator + "home" + File.separator + "linyimin" + File.separator + "DaSi" +
                            File.separator + "java" + File.separator + "InputStream.txt");
        File fout = new File(File.separator + "home" + File.separator + "linyimin" + File.separator + "DaSi" +
                        File.separator + "java" + File.separator + "OutputStream.txt");
        // InputStream.txt不存在则创建文件
        if(!fio.exists()){
            fio.createNewFile();
        }
        // OutputStream.txt不存在则创建文件
        if(!fout.exists()){
            fout.createNewFile();
        }
        // 实例化BufferedInputStream
        BufferedInputStream ibuf = new BufferedInputStream(new FileInputStream(fio));
        // 实例化BufferedOutputStream
        BufferedOutputStream  obuf = new BufferedOutputStream(new FileOutputStream(fout));
        byte [] data = http://www.mamicode.com/new byte[1024];
        // 从BufferedInputStream中多个字节数据
        int len = ibuf.read(data);
        while(len != -1){
            // 向BufferedOutputStream中写入多个数据
            obuf.write(data, 0, len);
            // 继续从BufferedInputStream中读取多个字节数据
            len = ibuf.read(data);
        }
        // 清空输出缓存区
        obuf.flush();
        // 关闭输出输入流
        ibuf.close();
        obuf.close();
    }
}

 程序运行结果:
程序运行前:OutputStream.txt文件不存在,InputStream.txt文件的内容为:
Hello World.
1234567890
程序运行后:OutputStream.txt文件的内容为:
Hello World.
1234567890


在上面的例子中,实现了将InputStream.txt拷贝到OutputStream.txt。其实不通过BufferedInputStream 和 BufferedOutputStream也可以完成这样的工作,使用这个两个类的好处是提升了文件拷贝的效率。下面进行验证:

  • 直接使用InputStream和OutputStream完成大文件赋值

代码

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
public class TestDemo{
    public static void main(String [] args) throws IOException{
        File fio = new File("/home/linyimin/Downloads/linux_11gR2_database_2of2.zip");
        File fout = new File("/home/linyimin/Downloads/linux_11gR2_database_2of2-copy.zip");
        InputStream in = null;
        OutputStream out = null;
        // 创建文件
        try{
            fout.createNewFile();
            in = new FileInputStream(fio);
            out = new FileOutputStream(fout);
        }
        catch(Exception e){
            e.printStackTrace();
        }
        // 获取复制文件大小,并转换成单位M,保留两位小数
        double length = new BigDecimal(fio.length() / 1024.0 / 1024).divide(new BigDecimal(1), 2, BigDecimal.ROUND_HALF_UP).doubleValue();
        // 文件复制开始时间
        long start = System.currentTimeMillis();
        // 从输入流中读取多个字节数据
        byte [] data = http://www.mamicode.com/new byte[1024];
        int len = in.read(data);
        while(len != -1){
            // 将从输入流中读出的数据写入输出流中
            out.write(data);
            // 继续从输入流中读取数据
            len = in.read(data);
        }
        // 文件复制结束时间
        long end = System.currentTimeMillis();
        // 将文件复制时间转换成秒并保留三位小数
        double time = new BigDecimal((end - start) / 1000.0).divide(new BigDecimal(1), 3, BigDecimal.ROUND_HALF_UP).doubleValue();
        System.out.println("复制大小为:" + length + "M的文件共花费时间:" + time + "s");

    }
}

程序运行结果:

复制大小为:949.25M的文件共花费时间:49.048s


  • 使用缓冲区完成大文件的复制

代码

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
public class TestDemo{
    public static void main(String [] args) throws IOException{
        File fio = new File("/home/linyimin/Downloads/linux_11gR2_database_2of2.zip");
        File fout = new File("/home/linyimin/Downloads/linux_11gR2_database_2of2-copy.zip");
        InputStream in = null;
        OutputStream out = null;
        // 创建文件
        try{
            fout.createNewFile();
            in = new BufferedInputStream(new FileInputStream(fio));
            out = new BufferedOutputStream(new FileOutputStream(fout));
        }
        catch(Exception e){
            e.printStackTrace();
        }
        // 获取复制文件大小,并转换成单位M,保留两位小数
        double length = new BigDecimal(fio.length() / 1024.0 / 1024).divide(new BigDecimal(1), 2, BigDecimal.ROUND_HALF_UP).doubleValue();
        // 文件复制开始时间
        long start = System.currentTimeMillis();
        // 从输入流中读取多个字节数据
        byte [] data = http://www.mamicode.com/new byte[1024];
        int len = in.read(data);
        while(len != -1){
            // 将从输入流中读出的数据写入输出流中
            out.write(data);
            // 继续从输入流中读取数据
            len = in.read(data);
        }
        // 文件复制结束时间
        long end = System.currentTimeMillis();
        // 将文件复制时间转换成秒并保留三位小数
        double time = new BigDecimal((end - start) / 1000.0).divide(new BigDecimal(1), 3, BigDecimal.ROUND_HALF_UP).doubleValue();
        System.out.println("复制大小为:" + length + "M的文件共花费时间:" + time + "s");

    }
}

程序运行结果:

复制大小为:949.25M的文件共花费时间:32.93s


比较以上两个程序的执行结果,使用缓冲区操作的效率确实要提高不少。


管道流操作:PipeInputStream & PipeOutputStream

管道流操作主要用于两个线程之间进行管道通信。PipedInputStream和PipedOutputStream一般是结合使用的,一般在一个线程中执行PipedOutputStream 的write操作,而在另一个线程中执行PipedInputStream的read操作。单独使用PipedInputStream或单独使用PipedOutputStream时没有任何意义的,必须将二者通过connect方法(或在构造函数中传入对应的流)进行连接绑定,如果单独使用其中的某一个类,就会触发IOException: PipeNotConnected.

代码示例

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class TestDemo{
    public static void main(String [] args)throws Exception{
        // 实例化PipedOutputStream
        final PipedOutputStream out = new PipedOutputStream();
        final PipedInputStream in = new PipedInputStream(out);
        // 使用匿名内部类实现线程t1
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run(){
                try {
                    out.write("Hello Pipe.".getBytes());
                } 
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        // 使用匿名内部类实现线程t2
        Thread t2 = new Thread(new Runnable(){
            @Override
            public void run(){
            int len = 0;
            try {
                // 从线程t1中的输出流中读取数据
                while((len = in.read()) != -1){
                    System.out.print((char)len + " ");
                }
            } 
            catch (IOException e) {
            }
            System.out.println();
            }
            });
        // 启动线程 
        t1.start();
        t2.start();
    }
}

程序运行结果
H e l l o P i p e .


在上面的程序中,我们创建了两个线程,并通过构造方法将PipedInputStream流和PipedOutputStream绑定。线程t1在运行时往输出流中写入字节数据,而线程t2在运行时阻塞式的执行read操作,等待获取数据,并输出。

对象序列化和反序列化操作:ObjectInputStream & ObjectOutputStream

  • 对象序列化指的是将在内存中保存的对象数据(主要指的是一个对象里面所包含的属性内容)进行二进制传输的一种操作.
  • 反序列化:将已经被序列化的数据反序列化为对象.

注:java.io.Serializable.Serializable<script type="math/tex" id="MathJax-Element-369">\color{red}{要实现序列化,对象所在类必须实现java.io.Serializable接口.Serializable接口没有方法,属于一种标识接口,表示一种一种能力.}</script>

如果一个类已经实现了序列化接口,那么此类的对象就可以经过二进制数据流进行传输,但是还需要对象输出流ObjectOutputStream和对象输入流ObjectInputStream完成对象的输入和输出.

  • ObjectOutputStream具有一系列writeXXX方法,在其构造函数中可以传入一个OutputStream,可以方便的向指定的输出流中写入基本类型数据以及String,比如writeBoolean、writeChar、writeInt、writeLong、writeFloat、writeDouble、writeCharts、writeUTF等,除此之外,ObjectOutputStream还具有writeObject方法。在执行writeObject操作时将对象进行序列化成流,并将其写入指定的输出流中。
  • ObjectInputStream与ObjectOutputStream相对应,ObjectInputStream有与OutputStream中的writeXXX系列方法完全对应的readXXX系列方法,专门用于读取OutputStream通过writeXXX写入的数据。

ObjectOutputStream和ObjectInputStream常用方法

类名称 No. 方法 类型 描述
ObjectOutputStream<script type="math/tex" id="MathJax-Element-370">\color{red}{ObjectOutputStream}</script> 1 public ObjectOutputStream(OutputStream out) <script type="math/tex" id="MathJax-Element-371">\color{blue}{构造}</script> 实例化指定输出流的对象
ObjectOutputStream<script type="math/tex" id="MathJax-Element-372">\color{red}{ObjectOutputStream}</script> 2 public void writeObject(Object obj) 普通 向指定输流写入数据
ObjectInputStream<script type="math/tex" id="MathJax-Element-373">\color{blue}{ObjectInputStream}</script> public ObjectInputStream(InputStream in) <script type="math/tex" id="MathJax-Element-374">\color{red}{构造}</script> 实例化指定输入流的对象
ObjectInputStrea<script type="math/tex" id="MathJax-Element-375">\color{blue}{ObjectInputStream}</script> public void readObject(Object obj) 普通 从指定输入流读出数据

代码示例-序列化

import java.io.File;
import java.io.Serializable;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

// 定义可以被序列化的类
class Person implements Serializable{
    private String name;
    private int age;

    public Person(String name, int age){
        this.name = name;
        this.age =  age;
    }
}
public class TestDemo{
    public static void main(String [] args) throws FileNotFoundException, IOException{
        // 实例化序列化对象
        Person per = new Person("张三", 20);
        File file = new File("/home/linyimin/DaSi/java/serializable.txt");
        // 实例化ObjectOutputStream对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        // 序列化对象
        oos.writeObject(per);
        // 关闭输出流
        oos.close();

    }
}

程序运行结果:
文件serializable.txtx中的内容:
\AC\ED\00sr\00Person:H3\BC\B0\FC\00I\00ageL\00namet\00Ljava/lang/String;xp\00\00\00t\00张三


本程序使用ObjectOutputStream将一个已经实例化的对象序列化到文件中.


代码示例-反序列化

import java.io.File;
import java.io.Serializable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

//定义可以被序列化的类
class Person implements Serializable{
    private String name;
    private int age;
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString(){
        return "姓名:" + this.name + "   年龄:" + this.age;
    }
}
public class TestDemo{
    public static void main(String [] args) throws FileNotFoundException, IOException, ClassNotFoundException{
        // 实例化ObjectInputStream对象
        File fio = new File("/home/linyimin/DaSi/java/serializable.txt");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fio));
        // 实现反序列化
        Person per = (Person) ois.readObject();
        ois.close();
        System.out.println(per);
    }
}

程序运行结果:

姓名:张三 年龄:20


本程序通过ObjectInputStream类将之前已经被序列化的数据反序列化为对象.

字符流

字符流和字节流十分相似,主要区别在于:

  • 字节流是针对字节的操作,而字符流是针对字符的操作
  • 字节流在进行IO操作时,直接针对的是操作的数据终端(如文件),而字符流是针对缓存区(可以理解为内存)的操作,然后由缓冲区操作终端(如文件)

下面主要介绍字符流较于字节流的区别之处:

使用Writer类进行输出的最大方便之处在于:可以直接输出字符串数据,而不像OutputStream类一样需要调用getBytes()函数将字符串转换成字节数组.

代码示例

import java.io.File;
import java.io.Writer;
import java.io.FileWriter;
public class TestDemo{
    public static void main(String [] args)throws Exception{
        File file = new File("/home/linyimin/DaSi/java/out.txt");
        // 如果文件不不存在,创建文件
        if(!file.exists()){
            file.createNewFile();
        }
        // 实例化FileWriter对象
        Writer out = new FileWriter(file);
        String str = "Hello World.";
        // 直接向文件中输出字符串
        out.write(str);
        // 需要关闭输出流,数据才会从缓存区写到文件中
        out.close();

    }
}

程序运行结果

创建out.txt文件,并写入”Hello World."


本程序通过FileWriter完成直接将字符串写入指定文件中.

转换流

所谓转换流指的是字节流向字符流转换的操作流.由InputStreamReader和OutputStreamWriter两个类完成.

  • InputStreamReader继承自Reader: InputStream –> Reader(使用构造方法完成)
  • OutputStreamWriter继承自Writer:OutputStream –> Writer(使用构造方法完成)

代码示例

import java.io.File;
import java.io.Writer;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class TestDemo{
    public static void main(String [] args) throws IOException{
        File file = new File("/home/linyimin/DaSi/java/out.txt");
        // 实例化字节输出流
        OutputStream out = new FileOutputStream(file);
        // 将字节输出流转换成字符输出流
        Writer wout = new OutputStreamWriter(out);
        String str = "使用OutputStreamWriter完成输出流转换操作";
        // 直接向文件输出字符串
        try {
            wout.write(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 关闭输出流,完成IO操作
        wout.close();
    }
}

程序运行结果

向文件out.txt中输出字符串"使用OutputStreamWriter完成输出流转换操作"


注:使<script type="math/tex" id="MathJax-Element-551">\color{blue}{处理各种数据都可以通过字节流完成,而在处理中文时使用字符流会更好}</script>

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Java IO(一)