14.1 File类的使用

  • java.io.File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)。

1、常用构造器

    public File(String pathname)
  • 以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。

    • 绝对路径:是一个固定的路径,从盘符开始。
    • 相对路径:是相对于某个位置开始。(IDEA中相对于当前Project/module)
      public File(String parent, String child)
  • 以parent为父路径,child为子路径创建File对象。

    public File(File parent, String child)
  • 根据一个父File对象和子文件路径创建File对象。

  • 实例:

    import org.junit.jupiter.api.Test;
    
    import java.io.File;
    
    public class FileTest {
        @Test
        public void test() {
            // 此时仅仅是内存层面的对象,不用考虑是否存在此文件
            File file1 = new File("hello.txt");  // 相对于当前Project/module
            File file2 = new File("D:\\JavaSE\\hello.txt");
            System.out.println(file1);  // hello.txt
            System.out.println(file2);  // D:\JavaSE\hello.txt
            File file3 = new File("D:\\JavaSE", "JavaSenior");
            System.out.println(file3);  // D:\JavaSE\JavaSenior
            File file4 = new File(file3, "hi.txt");
            System.out.println(file4);  // D:\JavaSE\JavaSenior\hi.txt
        }
    }

    2、路径分隔符

  • 路径中的每级目录之间用一个路径分隔符隔开。

  • 路径分隔符和系统有关:

    • Windows和DOS系统默认使用“\”来表示。
    • UNIX和URL使用“/”来表示。
  • Java程序支持跨平台运行,因此路径分隔符要慎用。

  • 为了解决这个隐患,File类提供了一个常量:

    public static final String separetor;
  • 根据操作系统,动态的提供分隔符。

3、常用方法

  • File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。

  • 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的“终点”。

  • 获取功能:

    public String getAbsolutePath();  // 获取绝对路径
    public String getPaht();  // 获取路径
    public String getName();  // 获取名称
    public String getParent();  // 获取上层文件目录路径。若无,返回null
    public long length();  // 获取文件长度(即:字节数)。不能获取目录的长度。
    public long lastModified();  // 获取最后一次的修改时间,毫秒值
    // 如下两个方法适用于文件目录
    public String[] list();  // 获取指定目录下的所有文件或者文件目录的名称数组
    public File[] listFiles();  // 获取指定目录下的所有文件或者文件目录的File组
  • 案例:

    import org.junit.jupiter.api.Test;
    
    import java.io.File;
    import java.util.Arrays;
    
    public class FileTest {
        @Test
        public void test() {
            File file1 = new File("hello.txt");
            File file2 = new File("D:\\JavaSE\\hi.txt");
            System.out.println(file1.getAbsolutePath());    // D:\JavaSE\hello.txt
            System.out.println(file1.getName());    // hello.txt
            System.out.println(file1.getParent());  // null
            System.out.println(file1.length());     // 0
            System.out.println(file1.lastModified());   // 0
    
            System.out.println(file2.getAbsolutePath());    // D:\JavaSE\hi.txt
            System.out.println(file2.getName());    // hi.txt
            System.out.println(file2.getParent());  // D:\JavaSE
            System.out.println(file2.length());     // 0
            System.out.println(file2.lastModified());   // 0
    
            File file3 = new File("D:\\JavaSE");
            String[] list = file3.list();
            System.out.println(Arrays.toString(list));
            File[] files = file3.listFiles();
            System.out.println(Arrays.toString(files));
        }
    
    }
  • 重命名功能:

    public boolean renameTo(File dest);  // 把文件重命名为指定的文件路径
  • 案例:

    import org.junit.jupiter.api.Test;
    
    import java.io.File;
    
    public class FileTest {
        /**
         * 要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
         */
        @Test
        public void test() {
            File file1 = new File("hello1.txt");
            File file2 = new File("hello2.txt");
            boolean b = file1.renameTo(file2);
            System.out.println(b);
            System.out.println(file1);
        }
    }
  • 判断功能:

    public boolean isDirectory();  // 判断是否是文件目录
    public boolean isFile();  // 判断是否是文件
    public boolean exists();  // 判断是否存在
    public boolean canRead();  // 判断是否可读
    public boolean canWrite();  // 判断是否可写
    public boolean isHidden();  // 判断是否隐藏
  • 案例:

    import org.junit.jupiter.api.Test;
    
    import java.io.File;
    
    public class FileTest {
    
        @Test
        public void test() {
            File file1 = new File("hello.txt");
            System.out.println(file1.isDirectory());
            System.out.println(file1.isFile());
            System.out.println(file1.exists());
            System.out.println(file1.canRead());
            System.out.println(file1.canWrite());
            System.out.println(file1.isHidden());
        }
    
    }
  • 创建功能:

  • 注意:如果创建文件或文件目录没有写盘符路径,默认在项目路径下。

    public boolean createNewFile();  // 创建文件。若文件存在,则不创建,返回false
    public boolean mkdir();  // 创建文件目录,如果此文件目录存在,或此文件目录的上层目录不存在,就不创建。
    public boolean mkdirs();  // 创建文件目录,如果上层文件目录不存在,一并创建。
  • 删除功能:

  • 注意:

    • Java中的删除不走回收站。
    • 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录。
      public boolean delete();  // 删除文件或文件夹
  • 案例:

    import org.junit.jupiter.api.Test;
    
    import java.io.File;
    import java.io.IOException;
    
    public class FileTest {
    
        @Test
        public void test() throws IOException {
            // 文件的创建
            File file1 = new File("hello.txt");
            if (!file1.exists()) {
                file1.createNewFile();
                System.out.println("file创建成功");
            } else {
                file1.delete();
                System.out.println("file删除成功");
            }
            // 文件目录的创建
            File file2 = new File("dir");
            File file3 = new File("dirs\\dir");
            if (!file2.exists()) {
                file2.mkdir();
                System.out.println("dir创建成功");
            } else {
                file2.delete();
                System.out.println("dir删除成功");
            }
            if (!file3.exists()) {
                file3.mkdirs();
                System.out.println("dirs创建成功");
            } else {
                file3.delete();
                System.out.println("dirs删除成功");
            }
        }
    
    }

    4、练习

  • 在文件a的同目录下创建文件b。

    import org.junit.jupiter.api.Test;
    
    import java.io.File;
    import java.io.IOException;
    
    public class FileTest {
    
        @Test
        public void test() throws IOException {
            File file = new File("dirs\\a.txt");
            File destFile = new File(file.getParent(), "b.txt");
            destFile.createNewFile();
        }
    
    }

    14.2 IO流原理及流的分类

1、Java IO原理

  • I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件、网络通信等。
  • Java程序中,对于数据的输入/输出操作以“流(Stream)”的方式进行。
  • java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
  • 输入Input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
  • 输出Output:将程序(内存)数据输出到磁盘、光盘等存储设备中。

2、流的分类

图片说明

  • 按操作数据单位不同分为:字节流(8bit)、字符流(16bit)。
  • 按数据流的流向不同分为:输入流、输出流。
  • 按流的角色的不同分为:节点流、处理流。

3、流的体系结构

图片说明

14.3 节点流(或文件流)

  • FileReader、FileWriter、FileInputStream、FileOutputStream。
  • 步骤:
    1. File类的实例化;
    2. 流的实例化;
    3. 具体操作;
    4. 关闭流。
  • 对于文本文件(.txt、.java、.c、.cpp),使用字符流处理(也可以使用字节流,但是不能在内存中查看,可能会出现乱码)。
  • 对于非文本文件(.jgp、.mp3、.mp4、.avi、.doc、.ppt),使用字节流处理。

1、FileReader

  • read():返回读入的一个字符。如果达到文件末尾,返回-1。

  • read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1。

  • 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理。

  • 读入的文件一定要存在,否则就会包FileNotFoundException。

    import org.junit.jupiter.api.Test;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    
    public class FileTest {
    
        /**
         * 将hello.txt文件内容程序中,并输出到控制台。
         */
        @Test
        public void test1() {
            // 1、实例化File类的对象,指明要操作的文件
            File file = new File("hello.txt");
            // 2、提供具体的流
            FileReader fileReader = null;
            try {
                fileReader = new FileReader(file);
                // 3、数据的读入
                // read():返回读入的一个字符。如果达到文件末尾,返回-1。
    
    //        int data = fileReader.read();
    //        while (data != -1) {
    //            System.out.print((char)data);
    //            data = fileReader.read();
    //        }
                int data;
                while ((data = fileReader.read()) != -1) {
                    System.out.print((char) data);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fileReader != null) {
                        // 4、关闭流
                        fileReader.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 对read()操作升级:使用read的重载方法。
         */
        @Test
        public void test2() {
            FileReader fileReader = null;
            try {
                // 1、File类的实例化
                File file = new File("hello.txt");
                // 2、FileReader流的实例化
                fileReader = new FileReader(file);
                // 3、读入的操作
                // read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1。
                char[] data = new char[5];
                int len;
                while ((len = fileReader.read(data)) != -1) {
                    // 方式一:
    
    //                for (int i = 0; i < len; i++) {
    //                    System.out.print(data[i]);
    //                }
                    // 方式二:
                    String str = new String(data, 0, len);
                    System.out.print(str);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (fileReader != null) {
                    // 4、关闭流
                    try {
                        fileReader.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    2、FileWriter

  • 输出操作,对应的File可以不存在,并不会报异常。

  • File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。

  • File对应的硬盘中的文件如果存在:

    • 如果流使用的构造器是:FileWriter(file, false)或FileWriter(file),会对原有文件进行覆盖。

    • 如果流使用的构造器是:FileWriter(file),会在原有的文件上添加数据。

      import org.junit.jupiter.api.Test;
      import java.io.*;
      public class FileTest {
      
        /**
         * 从内存中写出数据到硬盘的文件里。
         */
        @Test
        public void test1() {
            FileWriter fileWriter = null;
            try {
                // 1、提供File类的对象,指明写出到的文件
                File file = new File("hello.txt");
                // 2、提供FileWriter的对象,用于数据的写出
                fileWriter = new FileWriter(file, true);
                // 3、写出的操作
                fileWriter.write("I have a dream!\n");
                fileWriter.write("you need have a dream!\n");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (fileWriter != null) {
                    // 4、关闭流
                    try {
                        fileWriter.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
      
        /**
         * 文件复制。
         */
        @Test
        public void test2() {
            FileReader reader = null;
            FileWriter writer = null;
            try {
                // 1、创建File类的对象,指明读入和写出的文件
                File srcFile = new File("hello.txt");
                File destFile = new File("hello1.txt");
                // 2、创建输入流和输出流的对象
                reader = new FileReader(srcFile);
                writer = new FileWriter(destFile);
                // 3、读写操作
                char[] cbuf = new char[5];
                int len;    // 记录每次读入到cbuf数组中的字符的个数
                while ((len = reader.read(cbuf)) != -1) {
                    writer.write(cbuf, 0, len); // 每次写出len个字符
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4、关闭流
                try {
                    if (writer != null) {
                        writer.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (reader != null) {
                        reader.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
      }
  • 不能通过字符流处理图片等字节文件的复制。

3、FileInputStream、FileOutputStream

    import org.junit.jupiter.api.Test;   
    import java.io.*;    
    public class FileTest {    
        /**
         * 使用字节流FileInputStream处理文本文件,可能出现乱码。
         */
        @Test
        public void test1() {
            FileInputStream fis = null;
            try {
                // 1、造文件
                File file = new File("hello.txt");
                // 2、造流
                fis = new FileInputStream(file);
                // 3、读数据
                byte[] buffer = new byte[5];
                int len;    // 记录每次读取的字节的个数
                while ((len = fis.read(buffer)) != -1) {
                    String str = new String(buffer, 0, len);
                    System.out.print(str);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (fis != null) {
                    // 4、关闭流
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        /**
         * 实现对图片的复制操作。
         */
        @Test
        public void test2() {
            FileInputStream fis = null;
            FileOutputStream fos = null;
            try {
                // 1、造文件
                File srcFile = new File("aaa.jpg");
                File destFile = new File("bbb.jpg");
                // 2、造流
                fis = new FileInputStream(srcFile);
                fos = new FileOutputStream(destFile);
                // 3、读写操作
                byte[] buffer = new byte[5];
                int len;
                while ((len = fis.read(buffer)) != -1) {
                    fos.write(buffer, 0, len);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4、关闭流
                try {
                    if (fos != null) {
                        fos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

4、复制文件

    import org.junit.jupiter.api.Test;

    import java.io.*;

    public class FileTest {

        /**
         * 复制文件
         * @param srcPath
         * @param destPath
         */
        public static void copyFile(String srcPath, String destPath) {
            FileInputStream fis = null;
            FileOutputStream fos = null;
            try {
                // 1、造文件
                File srcFile = new File(srcPath);
                File destFile = new File(destPath);
                // 2、造流
                fis = new FileInputStream(srcFile);
                fos = new FileOutputStream(destFile);
                // 3、读写操作
                byte[] buffer = new byte[5];
                int len;
                while ((len = fis.read(buffer)) != -1) {
                    fos.write(buffer, 0, len);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4、关闭流
                try {
                    if (fos != null) {
                        fos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Test
        public void test() {
            long start = System.currentTimeMillis();
            String srcPath = "aaa.jpg";
            String destPath = "bbb.jpg";
            copyFile(srcPath, destPath);
            long end = System.currentTimeMillis();
            System.out.println(end - start);
        }
    }

14.4 缓冲流

  • BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter。

  • 作用:提升流的读取、写入的速度。

  • 提高速度的原因:内部提供了一个缓冲区。

图片说明

  • 处理流,就是“套接”在已有的流的基础上。

    import org.junit.jupiter.api.Test;
    
    import java.io.*;
    
    public class BufferedTest {
        /**
         * 实现非文本文件的复制。
         */
        @Test
        public void test1() {
            FileInputStream fis = null;
            FileOutputStream fos = null;
            BufferedInputStream bis = null;
            BufferedOutputStream bos = null;
            try {
                // 1、造文件
                File srcFile = new File("aaa.jpg");
                File destFile = new File("bbb.jpg");
                // 2、造流
                // 2.1、造节点流
                fis = new FileInputStream(srcFile);
                fos = new FileOutputStream(destFile);
                // 2.2、造缓冲流
                bis = new BufferedInputStream(fis);
                bos = new BufferedOutputStream(fos);
                // 3、读写操作
                byte[] buffer = new byte[10];
                int len;
                while ((len = bis.read(buffer)) != -1) {
                    bos.write(buffer, 0, len);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4、关闭流:先关闭外层的流,再关闭内层的流
                // (关闭外层流的同时,内层流也会自动进行关闭,关于内层流的关闭,我们可以省略)
                try {
                    if (bos != null) {
                        bos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (bis != null) {
                        bis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (fos != null) {
                        fos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 实现文本文件的复制。
         */
        @Test
        public void test2() {
            FileReader fr = null;
            FileWriter fw = null;
            BufferedReader br = null;
            BufferedWriter bw = null;
            try {
                // 1、创建文件和相应的流
                br = new BufferedReader(new FileReader(new File("hello.txt")));
                bw = new BufferedWriter(new FileWriter(new File("hello1.txt")));
                // 2、读写操作
                // 方式一:使用char[]数组
    
    //            char[] cbuf = new char[1024];
    //            int len;
    //            while ((len = br.read(cbuf)) != -1) {
    //                bw.write(cbuf, 0, len);
    //            }
                // 方式二:
                String data;
                while ((data = br.readLine()) != null) {
    //                bw.write(data + "\n"); // data中不包含换行符
                    bw.write(data); // data中不包含换行符
                    bw.newLine();   // 提供换行的操作
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 3、关闭流
                if (bw != null) {
                    try {
                        bw.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (br != null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    14.5 转换流

  • InputStreamReader:将一个字节的输入流转换为字符的输入流(解码)。

  • OutputStreamWriter:将一个字符的输出流转换为字节的输出流(编码)。

  • InputStream —> InputStreamReader —> 内存 —> OutputStreamWriter —> OutputStream。

  • 属于字符流。

  • 作用:提供字节流与字符流之间的转换。

  • 字符集。

    import org.junit.jupiter.api.Test;
    
    import java.io.*;
    
    public class InputStreamReaderTest {
        /**
         * InputStreamReader
         */
        @Test
        public void test() {
            FileInputStream fis;
            InputStreamReader isr = null;
            try {
                fis = new FileInputStream("hello.txt");
    //            isr = new InputStreamReader(fis);   // 使用系统默认的字符集
                // 指定字符集,具体使用哪个字符集,取决于hello.txt保存时使用的字符集
                isr = new InputStreamReader(fis, "UTF-8");
                char[] cbuf = new char[10];
                int len;
                while ((len = isr.read(cbuf)) != -1) {
                    String str = new String(cbuf, 0, len);
                    System.out.print(str);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (isr != null) {
                    try {
                        isr.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /**
         * 综合使用InputStreamReader、OutputStreamWriter
         */
        @Test
        public void test2() {
            InputStreamReader isr = null;
            OutputStreamWriter osw = null;
            try {
                File file1 = new File("hello.txt");
                File file2 = new File("hello_gbk.txt");
                FileInputStream fis = new FileInputStream(file1);
                FileOutputStream fos = new FileOutputStream(file2);
                isr = new InputStreamReader(fis, "utf-8");
                osw = new OutputStreamWriter(fos, "gbk");
                char[] cbuf = new char[20];
                int len;
                while ((len = isr.read(cbuf)) != -1) {
                    osw.write(cbuf, 0, len);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (osw != null) {
                    try {
                        osw.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (isr != null) {
                    try {
                        isr.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }

    14.6 标准输入、输出流

  • System.in:标准的输入流,默认从键盘输入。

  • System.out:标准的输出流,默认从控制台输出。

  • System.in的类型是InputStream。

  • System.out的类型是PrintStream,是OutputStream的子类,FilterOutputStream的子类。

  • 重定向:通过System类的setIn,setOut方法对默认设备进行改变:

    • public static setIn(InputStream in)
    • public static void setOut(PrintStream out)
      import org.junit.jupiter.api.Test;
      

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.Scanner;

    public class OtherStreamTest {

    public static void main(String[] args) {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        try {
            while (true) {
                System.out.print("请输入字符串:");
                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();
            }
        }
    }

    }

    # 14.7 打印流
  • 实现将基本数据类型的数据格式转化为字符串输出。

  • 打印流:PrintStream、PrintWriter。

  • 提供了一系列重载的print()和println()方法,用于多种数据类型的输出。

  • PrintStream和PrintWriter的输出不会抛出IOException异常。

  • PrintStream和PrintWriter有自动flush功能。

  • PrintStream打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用PrintWriter类。

  • System.out返回的是PrintStream的实例。

14.8 数据流

  • 为了方便地操作Java语言的基本数据类型String的数据,可以使用数据流。

  • 数据流有两个类:(用于读取和写出基本数据类型、String类的数据)

    • DataInputStream、DataOutputStream
    • 分别“套接”在InputStream和OutputStream子类的流上
  • DataInputStream中的方法

    • boolean readBoolean()
    • char readChar()
    • double readDouble()
    • long readDouble()
    • long readLong()
    • String readUTF()
    • byte readByte()
    • float readFloat()
    • short readShort()
    • int readInt()
    • void readFully(byte[] b)
  • DataOutputStream中的方法

    • 将上述的方法的read改为相应的write即可。

      import org.junit.jupiter.api.Test;
      import java.io.*;
      import java.util.Scanner;
      public class OtherStreamTest {
        /**
         * 将内存中的字符串、基本数据类型的变量写出到文件中。
         */
        @Test
        public void test() {
            DataOutputStream dos = null;
            try {
                dos = new DataOutputStream(new FileOutputStream("data.txt"));
                dos.writeUTF("张三");
                dos.flush();    // 刷新操作,将内存中的数据写入文件。
                dos.writeInt(18);
                dos.flush();
                dos.writeBoolean(true);
                dos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (dos != null) {
                    try {
                        dos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
      
        /**
         * 将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。
         * 注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致。
         */
        @Test
        public void test1() {
            DataInputStream dis = null;
            try {
                dis = new DataInputStream(new FileInputStream("data.txt"));
                String name = dis.readUTF();
                int age = dis.readInt();
                boolean gender = dis.readBoolean();
                System.out.println("name = " + name + ", age = " + age + ", gender = " + gender);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (dis != null) {
                    try {
                        dis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
      }

      14.9 对象流

  • ObjectInputStream、ObjectOutputStream。

  • 字节流。

  • 用于存储和读取基本数据类型对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

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

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

  • ObjectOutputStream和ObjectInputStream不能序列化statictransient修饰的成员变量。

1、对象的序列化

  • 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其他程序获取了这种二进制流,就可以恢复成原来的Java对象。

  • 序列化的好处在于可以将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。

  • 序列化是RMI(Remote Method Invoke——远程方法调用)过程的参数和返回值都必须实现的机制,而RMI是JavaEE的基础。因此序列化机制是JavaEE平台的基础。

  • 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常。

    • Serializable
    • Externalizable
  • 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:

    • public static final long serialVersionUID = 4698985656L;
    • serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
    • 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。故建议显示声明。
  • 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本底相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常:

        java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = -4033550198229762186, local class serialVersionUID = 3231504709311679906

    2、示例

      import org.junit.jupiter.api.Test;
    
      import java.io.*;
    
      public class ObjectInputOutputStreamTest {
          /**
           * 序列化过程:将内存中的Java对象保存到磁盘中,或通过网络传输出去。
           * 使用ObjectOutputStream实现。
           */
          @Test
          public void test1() {
              ObjectOutputStream oos = null;
              try {
                  oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
                  oos.writeObject(new String("我爱北京***"));
                  oos.flush();    // 刷新操作
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              } finally {
                  if (oos != null) {
                      try {
                          oos.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
    
          /**
           * 反序列化过程:将磁盘文件中的对象还原为内存中的Java对象。
           * 使用ObjectInputStream实现。
           */
          @Test
          public void test2() {
              ObjectInputStream ois = null;
              try {
                  ois = new ObjectInputStream(new FileInputStream("object.dat"));
                  Object o = ois.readObject();
                  String str = (String) o;
                  System.out.println(str);
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              } finally {
                  if (ois != null) {
                      try {
                          ois.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      }

    3、自定义类实现序列化、反序列化

  • 自定义类能够序列化的要求:

    • 需要实现接口:Serializable。
    • 当前类提供一个全局常量:public static final long serialVersionUID。
    • 除了当前类实现Serializable接口之外,还必须保证其内部所有属性也必须是可序列化的(默认情况下,基本数据类型是可序列化的)。
  • ObjectOutputStream和ObjectInputStream不能序列化statictransient修饰的成员变量,序列化过程中其值为该属性类型的默认值(null、0等)。

  • 自定义Person类:

    import java.io.Serializable;
    
    public class Person implements Serializable {
        public static final long serialVersionUID = 4698985656L;
    
        private String name;
        private int age;
            private Account account;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Person() {
    
        }
    
        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;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
    }
    class Account implements Serializable {
            public static final long serialVersionUID = 46985656L;
    }
  • 序列化与反序列化测试:

    import org.junit.jupiter.api.Test;
    
    import java.io.*;
    
    public class ObjectInputOutputStreamTest {
        /**
         * 序列化过程:将内存中的Java对象保存到磁盘中,或通过网络传输出去。
         * 使用ObjectOutputStream实现。
         */
        @Test
        public void test1() {
            ObjectOutputStream oos = null;
            try {
                oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
                oos.writeObject(new Person("张三", 18));
                oos.flush();    // 刷新操作
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (oos != null) {
                    try {
                        oos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /**
         * 反序列化过程:将磁盘文件中的对象还原为内存中的Java对象。
         * 使用ObjectInputStream实现。
         */
        @Test
        public void test2() {
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(new FileInputStream("object.dat"));
                Object o = ois.readObject();
                System.out.println(o);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                if (ois != null) {
                    try {
                        ois.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }

    14.10 随机存取文件流

1、RandomAccessFile

  • java.io.RandomAccessFile直接继承于java.lang.Object类。

  • 实现了DataInput、DataOutput这两个接口,这意味着这个类既可以读也可以写。

  • RandomAccessFile类支持“随机访问”的方式,程序可以直接跳到文件的任意地方来读、写文件:

    • 支持只访问文件的部分内容。
    • 可以向已存在的文件后追加内容。
  • RandomAccessFile对象包含一个记录指针,用以标示当前读写出的位置。RandomAccessFile类对象可以自由移动记录指针:

    • long getFilePointer():获取文件记录指针的当前位置。
    • void seek(long pos):将文件记录指针定位到pos位置。
  • 构造器:

    • public RandomAccessFile(File file, String mode)
    • public RandomAccessFile(String name, String mode)
  • 创建RandomAccessFile类实例需要指定一个mode参数,该参数指定RandomAccessFile的访问模式:

    • r:以只读方式打开。
    • rw:以读取和写入方式打开。
    • rwd:以读取和写入方式打开;同步文件内容的更新。
    • rws:以读取和写入方式打开;同步文件内容和元数据的更新。
  • 如果模式为只读r,则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。

  • 如果模式为rw读写,如果文件不存在则会去创建文件,如果存在则不会创建。

  • 如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。

  • 如果写出到的文件存在,则会对原有文件内容进行覆盖(默认情况下,从头覆盖)。

  • 可以通过相关的操作,实现RandomAccessFile“插入”数据的效果。

    import org.junit.jupiter.api.Test;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    
    public class RandomAccessFileTest {
        @Test
        public void test1() {
            RandomAccessFile raf1 = null;
            RandomAccessFile raf2 = null;
            try {
                raf1 = new RandomAccessFile(new File("hello.txt"), "r");
                raf2 = new RandomAccessFile(new File("hello1.txt"), "rw");
                byte[] buffer = new byte[1024];
                int len;
                while ((len = raf1.read(buffer)) != -1) {
                    raf2.write(buffer, 0, len);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (raf2 != null) {
                    try {
                        raf2.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (raf1 != null) {
                    try {
                        raf1.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        @Test
        public void test2() {
            RandomAccessFile raf = null;
            try {
                raf = new RandomAccessFile(new File("hello1.txt"), "rw");
                raf.seek(3);    // 将指针跳到角标为3的位置
                raf.write("xyz".getBytes());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (raf != null) {
                    try {
                        raf.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /**
         * 使用RandomAccessFile实现数据的插入效果
         */
        @Test
        public void test3() {
            RandomAccessFile raf = null;
            try {
                raf = new RandomAccessFile(new File("hello1.txt"), "rw");
                raf.seek(3);    // 将指针跳到角标为3的位置
                // 保存指针3后面的所有数据到StringBuilder中
                StringBuilder builder = new StringBuilder((int) new File("hello1.txt").length());
                byte[] buffer = new byte[20];
                int len;
                while ((len = raf.read(buffer)) != -1) {
                    builder.append(new String(buffer, 0, len));
                }
                // 调回指针,写入"xyz"
                raf.seek(3);
                raf.write("xyz".getBytes());
                // 将StringBuilder中的数据写入到文件里
                raf.write(builder.toString().getBytes());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (raf != null) {
                    try {
                        raf.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }
  • 我们可以用RandomAccessFile这个类,来实现一个多线程断点下载的功能。下载前建立两个临时文件,一个是与被下载文件大小相同的空文件,另一个是记录文件指针的位置文件,每次暂停下载的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上一次的地方下载,从而实现断点下载或上传的功能。

14.11 NIO.2中Path、Paths、Files类的使用

1、Java NIO概述

  • Java NIO(New IO, Non-Blocking IO)是从Java1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
  • Java API中提供了两套NIO,一套是针对标准输入输出的NIO,另一套就是网络编程NIO
    • java.nio.channels.Channel
      • FileChannel:处理本地文件
      • SocketChannel:TCP网络编程的客户端的Channel
      • ServerSocketChannel:TCP网络编程的服务器端的Channel
      • DatagramChannel:UDP网络编程中发送端和接收端的Channel

2、NIO.2

  • 随着JDK7的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称它们为NIO.2。
  • 因为NIO提供的一些功能,NIO已经称为文件处理中越来越重要的部分。

3、Path、Paths、Files的核心API

  • 早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。

  • NIO.2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资源也可以不存在。

  • 在以前IO操作都是这样写的:

    import java.io.File;
    File file = new File("index.html");
  • 但在Java7中,我们可以这样写:

    import java.nio.file.Path;
    import java.nio.file.Paths;
    Path path = Paths.get("index.html");
  • 同时,NIO.2在java.nio.file包下还提供了Files、Paths工具类,Files包含了大量静态的工具方法来操作文件;Paths则包含了两个返回Path的静态工厂方法。

  • Paths类提供的静态get()方法用来获取Path对象:

    • static Path get(String first, String... more):用于将多个字符串串连成路径。
    • static Path get(URI uri):返回指定uri对应的Path路径。