21天学会Java之(Java SE第十一篇):IO流(输入与输出)

I/O流的概念

在Java API中,可以从其中读入一个字节序列的对象称作输入流,而可以向其中写入一个字节序列的对象称作输出流。这些字节序列的来源地和目的地可以是文件,而且通常都是文件,但是也可以是网络连接,甚至是内存块。抽象类InputStream和OutputStream构成了输入/输出(I/O)类层次结构的基础。

下图是IO流常用的层次结构图(Java流家族各种输入/输出流类型超过60个,这里仅列出常用类,详情可以参考JDK API文档):

IO流常用的层次结构图

由图我们可以发现很多流都是成对的出现的,以下是我们常用到的类:

  1. InputStream和OutputStream:字节流的抽象类,以下这两个类的实现类都是以字节为单位操作的。
  2. FileInputStream和FileOutputStream:节点流,以字节为单位直接操作【文件】,也可称为文件流。
  3. ByteArrayInputStream和ByteArrayOutputStream:节点流,以字节为单位直接操作【字节数组对象】,也可称为字节数组流。
  4. ObjectInputStream和ObjectOutputStream:处理流,以字节为单位直接操作【对象】,也可称为对象流,也称为序列化/反序列化
  5. DataInputStream和DataOutputStream:处理流,以字节为单位直接操作【基本数据类型与字符串类型】,也可称为数据流。
  6. BufferedInputStream和BufferedOutputStream:处理流,将InputStream/OutputStream对象进行包装,增加缓存功能,提高读写效率
  7. Reader和Writer:字符流的抽象类,以下这两个类的实现类都是以字符为单位操作的。
  8. FileReader和FileWriter:节点流,以字符为单位直接操作【文本文件】
  9. BufferedReader和BufferedWriter:处理流,将Reader/Writer对象进行包装,增加缓存功能,提高读写效率
  10. InputStreamReader和OutputStreamWriter:处理流,将字节流对象转化成字符流对象。
  11. PrintStream:处理流,将OutputStream进行包装,它可以方便地输出字符,更加灵活。

下文中会逐一讲解这些类中的常用方法的使用。

I/O流的四大抽象类

InputStream/OutputStream和Reader/Writer类是所有I/O流的抽象父类,他们的作用就是操作对象单位的不同,前者的单位为字节,后者的单位为字符。

1、InputStream

InputStream表示字节输入流的所有类的抽象父类。因为它是一个抽象类,所以它不可以实例化。数据的读取需要由它的子类来实现。根据节点的不同,它派生了不同的节点流子类。

常用方法:

  • int read():读取一个字节的数据,并将字节的值作为int类型返回(0~255之间的一个值)。如果未读出字节则返回-1(返回值为-1表示读取结束)。
  • void close():关闭输入流对象,释放相关系统资源。

2、OutputStream

OutputStream表示字节输出流的所有类的抽象父类,它同样不可以实例化。输出流接收输出直接并将这些字节发送到某个目的地。

常用方法:

  • void write(int n):向目的地中写入一个字节。
  • void close():关闭输出流对象,释放相关系统资源。

3、Reader

Reader用于读取的字符流抽象类,数据单位为字符。

常用方法:

  • int read():读取一个字符的数据,并将字符的值作为int类型返回(0~65535之间的一个值,即Unicode值)。如果未读出字符则返回-1(返回值为-1表示读取结束)。
  • void close():关闭字符输入流对象,释放相关系统资源。

4、Writer

Writer用于写入的字符流抽象类,数据单位为字符。

常用方法:

  • void write(int n):向输出流中写入一个字符。
  • void close():关闭字符输出流对象,释放相关系统资源。

常用类的详解

所有常用类的操作大部分都可以分为四个步骤1.创建源|2.选择流|3.操作|4.释放资源,只要熟练这四个步骤,我们就能很熟练的掌握使用I/O流的操作。因为文章篇幅太长,本文中就简要介绍使用常用的几个方法,需要了解更详细的信息可以参考Java API文档。

文件字节流

FileInputStream通过字节的方式读取文件,适合读取所有类型的文件(如图像、视频、文本文件等)。Java中也提供了FileReader专门读取文本文件。

FileOutputStream通过字节的方式写数据到文件中,适合所有类型的文件。Java中也提供了FileWriter专门写入文本文件。

以下是FileInputStream的使用示例:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class TestFileInputStream03 {
    public static void main(String[] args) {
        //创建源
        File file=new File("abc.txt");
        //选择流
        InputStream is=null;
        try {
            is=new FileInputStream(file);
            //操作(读取)
            byte[] flush=new byte[1024];  //缓冲容器(1024字节=1KB,以1024为倍数方便维护)
            int len=-1;
            while ((len=is.read(flush))!=-1) {
                String str=new String(flush, 0, len);
                System.out.println(str);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //释放资源
                if (is!=null) {  //防止空指针异常
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

以下是FileOutputStream的使用示例:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class TestFileOutputStream {
    public static void main(String[] args) {
        //创建源
        File file=new File("dest.txt");
        OutputStream os=null;
        try {
            //选择流
            os=new FileOutputStream(file,true);
            String str="IO is so easy.\n";
            byte[] datas=str.getBytes();  //字符串-->字节数组(编码)
            //操作(写入)
            os.write(datas,0,datas.length);
            os.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //释放资源
                if (null!=os) {  //防止空指针异常
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

以下是使用文件字节输入、输出流实现文件拷贝:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class CopyFile {
    public static void main(String[] args) {
        copy("abc.txt", "COPY.txt");
    }

    public static void copy(String srcPath,String destPath) {
        //创建源
        File src=new File(srcPath);
        File dest=new File(destPath);
        InputStream is=null;
        OutputStream os=null;
        try {
            //选择流
            is=new FileInputStream(src);
            os=new FileOutputStream(dest);
            //操作
            byte[] flush=new byte[1024*1];
            int len=-1;
            while ((len=is.read(flush))!=-1) {
                os.write(flush, 0, len);
            }
            os.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //释放资源 分别关闭 先打开的后关闭
            try {
                if (os!=null) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (is!=null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }        
    }
}

文件字节流的注意事项

  1. 为了减少对硬盘的读写次数,提高效率,通常设置缓存数组。相应地,读取时使用的方法为read(byte[] b);写入时的方法为write(byte[] b,int off,int length)。
  2. 程序中如果遇到多个流,每个流都要单独关闭,防止其中一个流出现异常后导致其他流无法关闭的情况。

文件字符流

前面介绍的文件字节流可以处理所有的文件,但是字节流不能很好地处理Unicode字符,经常会出现“乱码”现象。所以处理文本文件时一般可以使用文件字符流,它以字符为单位进行操作。

FileReader使用示例:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class TestFileReader {
    public static void main(String[] args) {
        File file=new File("abc.txt");
        Reader reader=null;
        try {
            reader=new FileReader(file);
            char[] flush=new char[1024];
            int len=-1;
            while ((len=reader.read(flush))!=-1) {
                String str=new String(flush, 0, len);
                System.out.println(str);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (null!=reader) {
                    reader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

FileWriter使用示例:

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class TestFileWriter {
    public static void main(String[] args) {
        File file=new File("dest.txt");
        Writer writer=null;
        try {
            writer=new FileWriter(file);
            String str="IO is so easy!!!";
            //第一种写法
//            char[] datas=str.toCharArray();
//            writer.write(datas,0,datas.length);
            //第二种写法
//            writer.write(str);
            //第三种写法
            writer.append("IO is so easy").append("!!!").append(str);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (null!=writer) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

缓冲字节流

Java缓冲流本身并不具有I/O流的读写功能,只是在其他的流上加上缓冲功能以提高效率,就像是把其他的流包装起来一样,因此缓冲流是一种处理流(包装流)。

缓冲字节流的使用就是将其他的字节流包起来,下面用一个例子对比是否添加缓冲流的性能:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 加入缓冲字节流的文件复制
 * 缓冲字节流作为提高性能使用的(加入多次只能提高一次性能)
 * @author WHZ
 *
 */
public class BufferedCopy01 {

    public static void main(String[] args) {
        long t1=System.currentTimeMillis();
        copy01("A:/JAVA/编程工具/eclipse-java-2019-12-R-win32-x86_64.zip", "A:/java/1.zip");
        long t2=System.currentTimeMillis();
        System.out.println("未加入缓冲字节流耗时:"+(t2-t1)+"毫秒.");
        t1=System.currentTimeMillis();
        copy02("A:/JAVA/编程工具/eclipse-java-2019-12-R-win32-x86_64.zip", "A:/java/1.zip");
        t2=System.currentTimeMillis();
        System.out.println("加入缓冲字节流耗时:"+(t2-t1)+"毫秒.");
    }

    public static void copy01(String srcPath,String destPath) {
        //创建源
        File src =new File(srcPath);
        File dest=new File(destPath);
        //选择流
        InputStream is=null;
        OutputStream os=null;
        try{
            is=new FileInputStream(src);  //匿名对象
            os=new FileOutputStream(dest); //匿名对象
            byte[] flush=new byte[1024];
            int len=-1;
            while ((len=is.read(flush))!=-1) {  //用的while循环不是用if
                os.write(flush, 0, len);
            }
            os.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (null!=os) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (null!=is) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void copy02(String srcPath,String destPath) {
        //创建源
        File src =new File(srcPath);
        File dest=new File(destPath);
        //选择流
        InputStream is=null;
        OutputStream os=null;
        try{
            is=new BufferedInputStream(new FileInputStream(src));  //匿名对象
            os=new BufferedOutputStream(new FileOutputStream(dest)); //匿名对象
            byte[] flush=new byte[1024];
            int len=-1;
            while ((len=is.read(flush))!=-1) {  //用的while循环不是用if
                os.write(flush, 0, len);
            }
            os.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (null!=os) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (null!=is) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

缓冲字节流的注意事项

  1. 在关闭流时,应该先关闭最外层的包装流,即“先开启的后关闭”。
  2. 缓存区的默认大小是8192字节,也可以使用其他的构造器来指定大小。

缓冲字符流

BufferedReader和BufferedWriter同样增加了缓冲机制,大大提高了读写文本文件的效率,同时,提供了更方便的按行读取的方法readLine()以及写入方法writeLine()和newLine()。在处理文本时,可以使用缓冲字符流。

以下是使用缓冲字符流的拷贝文件的例子:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
 * 加入缓冲字符流的文件复制
 * 缓冲字符流作为提高性能使用的(加入多次只能提高一次性能)
 * @author WHZ
 *
 */
public class BufferedCopy02 {
    public static void main(String[] args) {
        copy("abc.txt", "copy.txt");
    }

    public static void copy(String srcPath,String destPath) {
        //创建源
        File src=new File(srcPath);
        File dest=new File(destPath);
        //选择流
        try(BufferedReader reader=new BufferedReader(new FileReader(src));
            BufferedWriter writer=new BufferedWriter(new FileWriter(dest));) {
            String line=null;
            while ((line=reader.readLine())!=null) {
                writer.write(line);
                writer.newLine();
            }
            writer.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

缓冲字符流的注意事项

  1. readLine()方法是BufferedReader特有的方法,可以对文本文件进行更加方便的读取操作。
  2. 写入一行后要记得使用newLine()方法换行。

字节数组流

ByteArrayInputStream和ByteArrayOutputStream经常用在需要流和数组之间转化的情况,FileInputStream把文件当作数据源,而ByteArrayInputStream则是把内存中的“某个字节数组对象”当作数据源。

ByteArrayInputStream的使用例子:

import java.io.ByteArrayInputStream;
import java.io.IOException;

public class TestByteArrayInputStream {
    public static void main(String[] args) {
        //1.创建源
        byte[] datas="talk in cheap show me your code".getBytes();
        //2.选择流
        ByteArrayInputStream bais=new ByteArrayInputStream(datas);
        byte[] flush=new byte[5];  //缓冲容器
        int len=-1;
        try {
            //3.操作
            while ((len=bais.read(flush))!=-1) {
                String str=new String(flush,0,len);
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //4.释放资源(可以不用)为了代码完整性,我们也可以加上
                if (null!=bais) {
                    bais.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

ByteArrayOutputStream的使用例子:

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class TestByteArrayOutputStream {
    public static void main(String[] args) {
        byte[] dest=null;
        //选择流(新增方法)
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        String str="show me your code";
        byte[] datas=str.getBytes();
        try {
            baos.write(datas);
            baos.flush();
            dest=baos.toByteArray();
            System.out.println(baos.size()+"-->"+new String(dest,0,dest.length));
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (baos!=null) {
                baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

字节数组流的注意事项

  1. 字节数组流可以不需要释放资源,但是为了代码完整性,我们也可以将释放资源的代码加上。
  2. 字节数组的数据源是内存中的“某个字节数组对象”。

数据流

数据流将“基础数据类型与字符串类型”作为数据源,从而允许程序以与机器无关的方式从底层输入/输出流中的操作Java基本数据类型与字符串类型。

以下是一个数据流使用的例子:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

/**
 * 数据流:写出后读取(读取的顺序与写出保持一致)
 * DataOutputStream    DataInputStream
 * @author WHZ
 *
 */
public class DataTest {
    public static void main(String[] args) throws IOException {
        //写出
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        DataOutputStream dos=new DataOutputStream(baos);
        //操作数据类型+数据
        dos.writeUTF("我命由我不由天!");
        dos.writeInt(666);
        dos.writeBoolean(false);
        dos.writeChar('v');
        byte[] datas=baos.toByteArray();
        //读取
        DataInputStream bis = new DataInputStream(new ByteArrayInputStream(datas));
        String string=bis.readUTF();
        int a=bis.readInt();
        boolean flag=bis.readBoolean();
        char c=bis.readChar();
        System.out.println(string+a+flag+c);
    }
}

数据流的使用注意事项

使用数据流时,读取顺序一定要与写入顺序一致,否则不能正确读取数据。

对象流(序列化/反序列化)

上栏中数据流仅能实现对基本数据类型和字符串类型的读写,并不能读取对象(字符串除外)。如果要对某个对象进行读写操作,则就需要使用对象流。

ObjectInputStream和ObjectOutputStream是以“对象”为数据源,但是必须对传输的对象进行序列化与反序列化操作。

以下是对象流的一个简单使用的例子(为了代码简单异常直接抛出了):

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;

/**
 * 序列化/反序列化(持久化)
 * 对象流(存储到文件):写出后读取(读取的顺序与写出保持一致)
 * ObjectOutputStream    ObjectInputStream
 * 不是所有的对象都可以序列化,序列化的对象需实现Serializable接口
 * transient关键字的使用
 * @author WHZ
 *
 */
public class ObjectTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //写出(序列化)
        ObjectOutputStream oos=new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("oos.ser")));
        //操作数据类型+数据
        oos.writeUTF("乾坤未定,");
        oos.writeInt(666);
        oos.writeBoolean(false);
        oos.writeChar('v');
        oos.writeObject("你我皆是黑马!");
        oos.writeObject(new Date());
        oos.writeObject(new Student("小王", 21, 3));
        oos.flush();
        oos.close();
        //读取(反序列化)
        ObjectInputStream ois=new ObjectInputStream(new BufferedInputStream(new FileInputStream("oos.ser")));
        String string=ois.readUTF();
        int a=ois.readInt();
        boolean flag=ois.readBoolean();
        char c=ois.readChar();
        Object str=ois.readObject();
        Object date=ois.readObject();
        Object stu=ois.readObject();
        System.out.println(string+a+flag+c);
        if (str instanceof String) {
            String strObj=(String)str;
            System.out.println(strObj);
        }
        if (date instanceof Date) {
            Date dateObj=(Date)date;
            System.out.println(dateObj);
        }
        if (stu instanceof Student) {
            Student stuObj=(Student)stu;
            System.out.println("姓名:"+stuObj.getName()+",年龄:"+stuObj.getAge()+",年级:"+stuObj.getGrade());
        }
        ois.close();
    }
}

class Student implements java.io.Serializable{
    private transient String name;  //transient指定该数据不需要序列化
    private int age;
    private int grade;
    public Student() {
    }
    public Student(String name, int age, int grade) {
        super();
        this.name = name;
        this.age = age;
        this.grade = grade;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public int getGrade() {
        return grade;
    }
    public void setGrade(int grade) {
        this.grade = grade;
    }
}

对象流的使用注意事项

  1. 对象流不仅可以读写对象,还可以读写基本数据类型。
  2. 使用对象流读写对象时,该对象必须经过序列化与反序列化。
  3. 系统提供的类(如Data等)已经实现了序列化接口,自定义类必须手动实现序列化接口。

Java对象的序列化与反序列化

作用

将Java对象转换为字节序列的过程称为对象的序列化;将字节序列回复为Java对象的过程称为对象的反序列化。

对象的序列化的作为如下:

  1. 持久化:把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中,例如休眠的实现、服务器的session的持久化,hibernate持久化对象等。
  2. 网络通信:在网络上传送对象的字节序列,例如服务器之间的数据通信、对象传递等。

序列化与反序列化在对象流中的使用

ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

ObjectInputStream代表对象输入流,它的readObject()方法可以从一个源输入流中读取字节序列,再将其反序列化为一个对象并返回。

只有实现了Serializable接口的类的对象才能被序列化。Serializable接口是一个空接口,只起标记作用。

注意事项

  1. static属性不参与序列化。
  2. 对象中的某些属性如果不想被序列化,不能使用static,而应使用transient修饰。
  3. 为了防止读和写的序列化ID不一致,一般指定一个固定的序列化ID。

转换流

InputStreamReader和OutputStreamWriter用来实现将字节流转化成字符流。例如下面的例子:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;

/**
 * 转换流:InputReaderStream和OutPutWriteStream
 * 1.以字符流的形式操作字节流(纯文本)
 * 2.指定字符集
 * @author WHZ
 *
 */
public class ConvertTest {
    public static void main(String[] args) {
        test();
    }
    public static void test() {
        //操作网络流,下载百度的源码
        try(BufferedReader reader=
                new BufferedReader(
                    new InputStreamReader(
                        new URL("http://www.baidu.com").openStream(),"UTF-8"));
            BufferedWriter writer=
                new BufferedWriter(
                    new OutputStreamWriter(
                        new FileOutputStream("baidu.html"),"UTF-8"));) {
            String msg=null;
            while ((msg=reader.readLine())!=null) {
                //System.out.println(msg);
                writer.write(msg);
                writer.newLine();
            }
            writer.flush();
        } catch (IOException e) {
            System.out.println("操作异常");
        }
    }
}

随意访问文件流

RandomAcessFile可以实现两个作用:

  1. 实现对一个文件的读和写操作。
  2. 可以访问文件的任意位置,不像其他流只能按照先后顺序读取。

随意访问文件流的三个核心方法:

  1. RandomAccessFile(String name,String mode):name用于确定文件,mode取r(读)或rw(可读写),通过mode可以确定流对文件的访问权限。
  2. seek(long a):用于定位流对象读写文件的位置,a确定读写位置距离文件开头的字节数。
  3. getFilePointer():用于获取流的当前读写位置。

以下是一个使用RandomAccessFile分割文件的例子:

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;

/**
 * RandomAccessFile面向对象解决分割文件
 * @author WHZ
 *
 */
public class SplitFile {
    //源头
    private File src;
    //目的地(文件夹)
    private String destDir;
    private List<String> destPaths;
    //每块的大小
    private int blockSize;
    //块数
    private int size;
    public SplitFile() {
    }
    public SplitFile(String srcPath, String destDir, int blockSize) throws IOException {
        super();
        this.src = new File(srcPath);
        this.destDir = destDir;
        this.blockSize = blockSize;
        this.destPaths=new ArrayList<String>();
        //初始化
        init();

    }

    private void init() {
        //总长度
        long len=src.length();
        //块数
        this.size=(int) Math.ceil(len*1.0/blockSize);
        //路径
        for (int i = 0; i < size; i++) {
            this.destPaths.add(destDir+"/"+i+this.src.getName());
        }
    }

    public void split() throws IOException {
        //总长度
        long len=this.src.length();
        System.out.println(this.size);
        int beginPos=0;
        int actualSize=(int)(blockSize>len?len:blockSize);
        for (int i = 0; i < size; i++) {
            beginPos=i*blockSize;
            if (i==size-1) {
                actualSize=(int) len;
            } else {
                actualSize=blockSize;
                len-=actualSize;
            }
            System.out.println(i+"---"+beginPos+"---"+actualSize);
            splitDetail(i, beginPos, actualSize);
        }
    }
    //指定第i块的起始分割位置和实际分割大小
    private void splitDetail(int i,int beginPos,int actualSize) throws IOException {
        RandomAccessFile raf=new RandomAccessFile(this.src,"r");
        RandomAccessFile raf2=new RandomAccessFile(this.destPaths.get(i),"rw");
        raf.seek(beginPos);
        byte[] flush=new byte[1024];
        int len=-1;
        while ((len=raf.read(flush))!=-1) {
            if (actualSize>len) {
                raf2.write(flush, 0, len);
                actualSize-=len;
            } else {
                raf2.write(flush, 0, actualSize);
                break;
            }
        }
        raf2.close();
        raf.close();
    }
    public static void main(String[] args) throws IOException {
        SplitFile sf = new SplitFile("src\\cn\\whz\\test\\SplitFile.java", "dest", 1080);
        sf.split();
    }
}

打印流

这个流仅需要了解即可,以下是一个简单的例子:

import java.io.BufferedOutputStream;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;

/**
 * 打印流:PrintStream
 * @author WHZ
 *
 */
public class PrintTest01 {
    public static void main(String[] args) throws FileNotFoundException {
        //打印流:System.out
        PrintStream ps=System.out;
        ps.println("打印流");
        ps.println(true);

        ps=new PrintStream(new BufferedOutputStream(new FileOutputStream("print.txt")),true);
        ps.println("打印流");
        ps.println(true);

        //重定向输出端
        System.setOut(ps);
        System.out.println("change");
        //重定向回控制台
        System.setOut(new PrintStream(new BufferedOutputStream(new FileOutputStream(FileDescriptor.out)),true));
        System.out.println("backing!");
        ps.clo***mons-io的使用

Apache-commons-io工具包提供了IOUtils/FileUtils,它可以非常方便地对文件和目录进行操作。大大提高实际开发中的工作效率(没有必要重复造轮子)。

## FileUtils类的常用方法

阅读FileUtils的API文档,可以归纳出以下在开发工作中比较常用的方法(详细的可以查看commons-io的API文档):

1. cleanDirectory:清空目录,但不删除目录。
2. contentEquals:比较两个文件的内容是否相同。
3. copyDirectory:将一个目录中的内容复制到另一个目录,可以通过FileFilter过滤需要复制的文件。
4. copyFile:将一个文件复制到一个新地址。
5. copyFileToDirectory:将一个文件复制到某个目录下。
6. copyInputStreamToFile:将一个输出流中的内容复制到某个文件。
7. deleteDirectory:删除目录。
8. deleteQuietly:删除文件。
9. listFiles:列出指定目录下的所有文件。
10. openInputStream:打开指定文件的输入流。
11. readFileToString:将文件内容作为字符串返回。
12. readLines:将文件内容按行返回到一个字符串数组中。
13. size:返回文件或目录的大小。
14. write:将字符串内容直接写到文件中。
15. writeByteArrayToFile:将字节数组内容写到文件中。
16. writeLines:将容器中元素的toString方法返回的内容依次写入文件。
17. writeStringToFile:将字符串内容写到文件中。

 ## IOUtils类的常用方法

1. buffer:将传入的流进行包装,变成缓冲流,并可以通过参数指定缓冲大小
2. closeQueitly:关闭流。
3. contentEquals:比较两个流中的内容是否一致。
4. copy:将输入流中的内容复制到输出流中,并指定字符编码。
5. copyLarge:将输入流中的内容复制到输出流中,适合用于大于2GB的内容复制。
6. lineIterator:返回可以迭代每一行内容的迭代器。
7. read:将输入流中的所有内容读入到字节数组中。
8. readFully:将输入流中的所有内容读入到字节数组中。
9. readLine:读取输入流内容中的一行。
10. toBufferedInputStream,toBufferedReader:将输入流转为带缓冲的输入流。
11. toByteArray,toCharArray:将输入流的内容转为字节数组、字符数组。
12. toString:将输入流或数组中的内容转化为字符串。
13. write:向流里面写入内容。
14. writeLine:向流里面写入一行内容。

## 常用方法的例子

### 读取内容

```java
import java.io.File;
import java.io.IOException;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;

public class CIORead {
    public static void main(String[] args) throws IOException {
        //读取到字符串
        String msg=FileUtils.readFileToString(new File("abc.txt"),"GBK");
        System.out.println(msg);
        System.out.println("----------------------");
        //读取到字节数组
        byte[] datas=FileUtils.readFileToByteArray(new File("abc.txt"));
        System.out.println(datas.length);
        System.out.println("----------------------");
        //容器逐条读取到字符串
        List<String> lines=FileUtils.readLines(new File("abc.txt"),"GBK");
        for (String string : lines) {
            System.out.println(string);
        }
        System.out.println("----------------------");
        //使用迭代器
        LineIterator it=FileUtils.lineIterator(new File("abc.txt"),"GBK");
        while (it.hasNext()) {
            System.out.println(it.nextLine());
        }
    }
}

写出内容

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FileUtils;

public class CIOWrite {
    public static void main(String[] args) throws IOException {
        //写出文件(最后一个参数不加默认是false即重写文件)
        FileUtils.write(new File("commons_IOTest.txt"), "百度\n", "GBK");
        FileUtils.writeStringToFile(new File("commons_IOTest.txt"), "阿里\n", "GBK",true);
        FileUtils.writeByteArrayToFile(new File("commons_IOTest.txt"), "腾讯\n".getBytes("GBK"),true);

        //写出列表
        List<String> strings=new ArrayList<String>();
        strings.add("李彦宏");
        strings.add("马云");
        strings.add("马化腾");
        //将容器里的内容逐行写出
        FileUtils.writeLines(new File("commons_IOTest.txt"), strings,true);
        //将容器里的内容用连接符连接起来写出
        FileUtils.writeLines(new File("commons_IOTest.txt"), strings, "---",true);
    }
}

拷贝文件/文件夹

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

public class CIOCopy {
    public static void main(String[] args) throws IOException {
        //复制文件
        FileUtils.copyFile(new File("test.png"), new File("COPY.png"));
        //复制文件到目录
        FileUtils.copyFileToDirectory(new File("test.png"), new File("dest"));
        //复制文件夹
        FileUtils.copyDirectory(new File("lib"), new File("test"));
        //复制文件夹到目录(作为子目录)
        FileUtils.copyDirectoryToDirectory(new File("lib"), new File("test"));
        //拷贝url内容
        FileUtils.copyURLToFile(new URL("http://www.163.com"), new File("163.txt"));
        //按照网页内容字符集格式读取然后再变为工程的字符集
        String datas=IOUtils.toString(new URL("http://www.baidu.com"), "UTF-8");
        //复制字节数组到文件
        FileUtils.copyToFile(
                new BufferedInputStream  //提高性能
                    (new ByteArrayInputStream(datas.getBytes()))  //将字符串转换成字节数组然后读取
                        ,new File("baidu.txt"));  //复制到的路径
    }
}

统计文件夹或文件夹大小

import java.io.File;

import org.apache.commons.io.FileUtils;

public class CIOMemory {
    public static void main(String[] args) {
        //文件大小
        long len =FileUtils.sizeOf(new File("src/cn/whz/commons_io/CIOMemory.java"));
        System.out.println(len);
        //目录大小
        len=FileUtils.sizeOf(new File("D:/eclipse-workspace/Commons_io"));
        System.out.println(len);
    }
}

遍历文件

import java.io.File;
import java.util.Collection;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.EmptyFileFilter;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.SuffixFileFilter;

public class CIOSearch {
    public static void main(String[] args) {
        test01();  //FileUtils.listFiles(new File("D:/eclipse-workspace/Commons_io")EmptyFileFilter.NOT_EMPTY, null);打印出第一层非空的文件
        test02();  //DirectoryFileFilter.INSTANCE打印出子孙级
        test03();  //new SuffixFileFilter("java")筛选后缀为java的文件
        test04();  //FileFilterUtils.or方法筛选后缀包含java或空文件
        test05();  //FileFilterUtils.and方法筛选后缀包含java且非空文件
    }
    //FileFilterUtils.and方法筛选后缀包含java且非空文件
    public static void test05() {
        Collection<File> files=FileUtils.listFiles(
                new File("D:/eclipse-workspace/Commons_io"), 
                FileFilterUtils.and(new SuffixFileFilter("java"),EmptyFileFilter.NOT_EMPTY), DirectoryFileFilter.INSTANCE);
        for (File file : files) {
            System.out.println(file.getAbsolutePath());
        }
    }
    //FileFilterUtils.or方法筛选后缀包含java或空文件
    public static void test04() {
        Collection<File> files=FileUtils.listFiles(
                new File("D:/eclipse-workspace/Commons_io"), 
                FileFilterUtils.or(new SuffixFileFilter("java"),EmptyFileFilter.EMPTY), DirectoryFileFilter.INSTANCE);
        for (File file : files) {
            System.out.println(file.getAbsolutePath());
        }
    }
    //new SuffixFileFilter("java")筛选后缀为java的文件
    public static void test03() {
        Collection<File> files=FileUtils.listFiles(
                new File("D:/eclipse-workspace/Commons_io"), 
                new SuffixFileFilter("java"), DirectoryFileFilter.INSTANCE);
        for (File file : files) {
            System.out.println(file.getAbsolutePath());
        }
    }
    //DirectoryFileFilter.INSTANCE打印出子孙级
    public static void test02() {
        Collection<File> files=FileUtils.listFiles(
                new File("D:/eclipse-workspace/Commons_io"), 
                EmptyFileFilter.NOT_EMPTY, DirectoryFileFilter.INSTANCE);
        for (File file : files) {
            System.out.println(file.getAbsolutePath());
        }
    }
    //EmptyFileFilter.NOT_EMPTY打印出第一层非空的文件
    public static void test01() {
        Collection<File> files=FileUtils.listFiles(
                new File("D:/eclipse-workspace/Commons_io"), 
                EmptyFileFilter.NOT_EMPTY, null);
        for (File file : files) {
            System.out.println(file.getAbsolutePath());
        }
    }
}

结语

IO流Java中一个特别重要的知识点,本篇内容对于IO流的讲解仅限于常用的使用方法,需要了解更详细的方法,可以参阅官方API文档。本篇至此完结,如有错误的地方,望在评论区指正。

全部评论

相关推荐

头像
04-17 09:29
已编辑
湖南农业大学 后端
睡姿决定发型丫:本硕末9也是0offer,简历挂了挺多,只有淘天 美团 中兴给了面试机会,淘天二面挂,美团一面kpi面,中兴一面感觉也大概率kpi(虽然国企,但一面0技术纯聊天有点离谱吧)
点赞 评论 收藏
分享
评论
2
2
分享

创作者周榜

更多
牛客网
牛客企业服务