【Java SE】IO流


1.File类

①File类的一个对象代表一个文件或一个文件目录

②File类声明在java.io下

1.1 FIle类的声明

路径分隔符

Windows和DOS系统默认使用'',UNIX和URL使用'/'

由于Java支持跨平台运行,File类为了解决这个问题动态提供了一个常量:public static final String separator 根据操作系统动态地提供分隔符

构造器
File(String pathname)
File(String parnter, String child)
File(File file, String child)

构造器创建的仅仅是内存层面的几个对象,在main方法中路径相较于当前工程,在单元测试时路径则相较于当前Module。

1.2 File类的常用方法

String getAbsolutePath() 获取绝对路径
String getPath() 获取路径
String getName()
String getParent()
long length() 长度,单位为字节
lastModified() 最后一次修改时间
String[] list() 以String的形式返回下一层目录中的文件或者目录的名字
File[] listFiles() 返回下一层目录中的文件或者目录的File对象
boolean file1.renameTo(file2) file1改名(且移动)为file2,要求file1存在且file2不存在
boolean isDirectory()
boolean isFile()
boolean exists() 当前File对象是不是存在于硬盘
boolean canRead()
boolean canWrite()
boolean isHidden()
boolean createNewFile()
boolean mkdir() 如果文件存在就不创建了,上层目录如果不存在也不创建。
boolean mkdirs() 上层文件目录不存在一并创建。
boolean delete() java的删除不走回收站,删除时文件目录内不能包含文件或者文件目录。

File类的常用方法并未涉及到文件的写入和读取文件内容的操作,当需要读取和写入文件内容,必须使用IO流来完成。

2.IO流

Input/Output的缩写,用于处理设备之间的数据传输。如读、写文件、网络通信等。Java中对于数据的输入输出是以流(stream)的方式进行的。java.io下提供了各种“流”类和接口,用于获取不同种类的数据,并将这些数据以标准的方法输出。

2.1 流的分类

操作数据单位不同分为:字节流(8bit)、字符流(16bit)

数据流的流向不同分为:输入流(字节流:InputStream,字符流:Reader)、输出流(字节流:OutputStream,字符流:Writer),四种被称作抽象基类。

流的角色不同分为:节点流(直接作用在文件上)、处理流(作用在已有流基础之上)

2.2 流的体系结构

2.2.1 节点流(文件流)
FileReader:文件字符读入流
        File file1 = new File("Hello.txt");
        FileReader fr = null;
        try {
            //提供具体的流
            fr = new FileReader(file1);
            int data;
            //数据的读入
            while((data = fr.read()) != -1) {
                System.out.print((char) data);

            }
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //流的关闭
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

读入的文件一定要存在,否则会报FileNotFoundException异常

对read()方法的升级:len = fr.read(cbuf),返回读取到字符数组cbuf的字符个数

                int len;
                while((len = fr.read(cbuf)) != -1) {
//                    方法一
//                    System.out.print(new String(cbuf, 0, len));

//                    方法二
//                    for(int i = 0;i < len;i++)
//                        System.out.print(cbuf[i]);
//                    方法三,最后一次为 1,2,3,\r,\n
                    for(int i = 0;i < cbuf.length;i++)
                        System.out.print(cbuf[i]);
FileWriter:文件字符写出流
        File file = new File("Hello1.txt");

        FileWriter fw = new FileWriter(file);

        fw.write("qwerdfb");

        fw.close();

写出操作文件可以不存,如果文件不存在会自动创建,如果存在每次会对已存在文件进行覆盖

FileWriter fw = new FileWriter(file, true);

流使用的构造器第二个参数表示若已存在文件,则对文件进行追加而不是覆盖文件

        FileWriter fw = null;
        FileReader fr = null;
        try {
            File srcFile = new File("Hello.txt");
            File destFile = new File("Hello1.txt");

            fw = new FileWriter(destFile, true);
            fr = new FileReader(srcFile);

            int len;
            char[] cbu = new char[5];
            while((len = fr.read(cbu)) != -1) {
                fw.write(cbu, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

对于图片等字节数据,基本单位不是字符而是二进制,不能使用字符流处理:

FileInputStream:字节读入流

FileOutputStream:字节写出流

        FileOutputStream fw = null;
        FileInputStream fr = null;
        try {
            File srcFile = new File("1.png");
            File destFile = new File("2.png");

            fw = new FileOutputStream(destFile);
            fr = new FileInputStream(srcFile);

            int len;
            byte[] bytes = new byte[5];
            while((len = fr.read(bytes)) != -1) {
                fw.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

对于文本文件(.txt,.java,.c,.cpp),使用字符流处理

对于非文本文件(视频,音频,.doc,.ppt),使用字节流处理。文本文件使用字节流处理的话,可能会出现乱码,比如一个汉字占3个字节,读取用的数组不足三个字节的话就读不到完整的一个汉字因而出现乱码。但是如果非文本文件不在内存中进行读取的话,单纯使用字符流复制是造不成乱码的。

2.2.2 处理流:套接在已有的流之上的流
1.缓冲流:提高文件的读写效率
缓冲字节流:BufferedInputStream、BufferedOutputStream
    @Test
    public void BufferedStreamTest() {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            File srcFile = new File("1.png");
            File destFile = new File("2.png");

            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);

            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            int len;
            byte[] bytes = new byte[5];
            while((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bis.close();
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                fis.close();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

资源关闭时要先关闭外部缓冲流,再关闭内部节点流,缓冲流关闭时也会对内部流进行关闭,因此内部流的关闭可以省略。

bos.write()方法隐式地调用了flush(),flush的作用是刷新缓冲区,在缓冲区满的时候对数据进行一次性输出。

    @Test
    public void copyFileTest() {
        long startTime = System.currentTimeMillis();

        copyFile("1.png", "2.png");

        long endTime = System.currentTimeMillis();

        System.out.println("花费时间为:" + (endTime - startTime) + "ms");


        startTime = System.currentTimeMillis();

        copyFileByBuffer("1.png", "3.png");

        endTime = System.currentTimeMillis();

        System.out.println("使用缓冲流花费时间为:" + (endTime - startTime        ) + "ms");
    }
    花费时间为:123ms
    使用缓冲流花费时间为:4ms
缓冲字符流:BufferedReader、BufferedWriter
    @Test
    public void BufferedRWTest() {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            File srcFile = new File("Hello.txt");
            File destFile = new File("Hello1.txt");

            FileReader fr = new FileReader(srcFile);
            FileWriter fw = new FileWriter(destFile);

            br = new BufferedReader(fr);
            bw = new BufferedWriter(fw);

            int len;
            char[] buffer = new char[5];
            while((len = br.read(buffer)) != -1) {
                bw.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

使用缓冲流readLine(),以String的形式进行读入操作:

            String str;
            while((str = br.readLine()) != null) {
                //方式一
//                bw.write(str + "\n");
                //方式二
                bw.write(str);
                bw.newLine();
            }
2.转换流:InputStreamReader、OutputStreamWriter

InputStreamReader:将一个字节的输入流转换为一个字符的输入流。对应解码过程。

OutputStreamWriter:将一个字符的输出流转换为一个字节的输出流。对应编码过程。

原因是文本文件可以使用字节流读取,才设计了转换流

InputStreamReader

    @Test
    public void test() {
        File file = new File("Hello.txt");

        InputStreamReader isr = null;
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            isr = new InputStreamReader(fis, "utf-8");

            int len;
            char[] cs = new char[5];
            while((len = isr.read(cs)) != -1) {
                System.out.print(new String(cs, 0, len));
            }

        } catch (IOException e) {
            e.printStackTrace();
            try {
                isr.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }

转换流第二个参数为解码字符集,对应文本文件保存时的编码。

OutputStreamWriter

    @Test
    public void test() {
        File file = new File("Hello.txt");
        File destFile = new File("Hello3.txt");

        FileInputStream fis = null;
        FileOutputStream fos = null;

        InputStreamReader isr = null;
        OutputStreamWriter osw = null;

        try {
            fis = new FileInputStream(file);
            isr = new InputStreamReader(fis);

            fos = new FileOutputStream(destFile);
            osw = new OutputStreamWriter(fos);

            int len;
            char[] chs = new char[10];

            while((len = isr.read(chs)) != -1) {
                osw.write(chs, 0, len);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                osw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

字符集

ASCII 美国标准信息交换码,用一个字节的7位表示,其他方式均兼容ASCII
ISO8859-1 拉丁码、欧洲码表,用一个字节的8位表示
GBK2312 中文编码表,最多两个字节编码所有字符
GBK 中文编码表,最多两个字节编码所有字符,通过首位1、0的方式判断一个字符占一个还是两个字节
Unicode 国际标准码,为每一个字符分配一个字符码,所有文字都用两个字节表示
UTF-8 解决了Unicode编码字节个数问题,变长编码方式,一个到四个字节表示一个字符,每8个位传输数据,汉字占三个字节

3.标准输入、输出流:字节流

System.in: 类型为InputStream字节写入流

System.out: 类型为PrintStream字节读出流

        //字节流转换为字符流
        InputStreamReader isr = new InputStreamReader(System.in);

        BufferedReader br = new BufferedReader(isr);

        try {
            while(true) {
                String str = br.readLine();
                if("e".equalsIgnoreCase(str) || "exit".equalsIgnoreCase(str))
                    break;
                System.out.println(str.toUpperCase());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

System.in(InputStream 字节写入流) --InputStreamReader 转换流--> BufferedReader 字符读出缓冲流

面试题: 用标准输入流模拟Scanner

public class MyInput {
    private InputStreamReader isr = new InputStreamReader(System.in);
    private BufferedReader br = new BufferedReader(isr);



    public int readInt() {
        int result = 0;
        try {
            result = Integer.parseInt(br.readLine());
        } catch (NumberFormatException e) {
            System.out.println("输入不是整形!");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    public double readDouble() {
        Double result = null;
        try {
            result = new Double(br.readLine());
        } catch (NumberFormatException e) {
            System.out.println("输入不是Double类型!");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result.intValue();
    }


    public static void main(String[] args) {
        MyInput myInput = new MyInput();
        while(true) {
            int result1 = myInput.readInt();
            System.out.println(result1);

            double result2 = myInput.readDouble();
            System.out.println(result2);
        }
    }
}
4.打印流
        File file = new File("Hello.txt");

        PrintStream ps = null;
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            ps = new PrintStream(fos);

            if(ps != null) {
                System.setOut(ps);
            }

            for(int i = 0;i <= 255;i++) {
                System.out.print((char) i);
                if(i % 50 == 0)
                    System.out.println();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            ps.close(); 
        }

System.setOut(PrintStream)可以设置读出为打印流处理的节点流(文件流)

5. 数据流

DataOutputStream、DataInputStream

    //将内存中的数据经被数据流处理过的节点流写入文件
    @Test
    public void test() {
        try {
            DataOutputStream dos = new DataOutputStream(new FileOutputStream("Hello1.txt"));
            dos.writeUTF("刘");
            dos.writeInt(25);
            dos.writeBoolean(true);

            dos.flush();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }

    }

    //将文件中的数据经被数据流处理过的节点流读入内存
    @Test
    public void test2() {
        try {
            DataInputStream dis = new DataInputStream(new FileInputStream("Hello1.txt"));
            System.out.println(dis.readUTF());
            System.out.println(dis.readInt());
            System.out.println(dis.readBoolean());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
6. 对象流

ObjectInputStream和ObjectOutputStream:用于存储和读取基本数据类型数据或对象的处理流。

序列化: 用ObjectOutputStream类保存基本数据类型或对象的机制。

反序列化: 用ObjectInputStream类读取基本数据类型或对象的机制。

注意: 二者均不能序列化static和transient修饰的成员变量。

对象的序列化机制允许将内存中的Java对象转换成平台无关的二进制流,从而允许把这些二进制流永久地保存在磁盘上,或则通过网络将这种二进制流传输到另一个网络节点。当其他程序获取到这种二进制流,就能将其转换为相应的对象。

    @Test
    public void test3() {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("Object.txt"));
            oos.writeObject(new String("宇田多光"));
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void test4() {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("Object.txt"));

            Object object = ois.readObject();
            System.out.println(object);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
对自定义类进行序列化和反序列化:

自定义类能够序列化需要满足的要求:

1.需要实现接口Serializable

2.当前类提供一个全局常量:serialVersionUID

3.保证类中的所有属性也能够序列化(基本数据类型和String默认可序列化),static、trasient(static不归对象而归类所有,trasient为Java设计不可序列化)

    @Test
    public void test3() {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("Object.txt"));
            oos.writeObject(new String("宇田多光"));
            oos.flush();
            oos.writeObject(new Person("宇多田光", 32));
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void test4() {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("Object.txt"));

            Object object = ois.readObject();
            System.out.println(object);

            Person p = (Person) ois.readObject();
            System.out.println(p);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

serialVersionUID的意义:

用来表明类的不同版本的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。如果类中没有显示定义这个全局常量,那么它的值由Java运行时环境根据类的内部细节自动生成,因此类发生改变时serialVersionUID也会跟着变化,可能导致对象反序列化失败。

7. RandomAccessFile implements DataInput, DataOutput

RandomAccessFile类直接继承于java.lang.Object类,支持随机访问方式,程序可以直接跳到文件的任意地方来读、写文件,即既可以作为输入流又可以作为输出流。写文件时若文件存在则进行从头覆盖。

8.NIO

NIO是面向缓冲区的、基于通道的操作,而IO是面向流的    。以更加高效的操作进行文件的读写操作。

8.1Path

早期的Java只提供一个File类来访问文件系统,File类的功能有限,所提供的方法性能也不高,而且大多数方法在出错时仅返回失败而不抛出异常。Path可以看做是File的升级版本,实际引用的资源也可以不存在。

File file = new File(".\\IOT.\\data\\index.html");
        System.out.println(file.exists());

        Path path = Paths.get(".\\IOT.\\data\\index.html");
        System.out.println(path.toString());

//        Path和File可以相互转换
        Path newpath = file.toPath();

        File newfile = path.toFile();