[JavaSE] 第十二章 IO流

12.1 File 类的使用

  • File 类声明在 java.io 包下
  • File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
  • File 对象可以作为参数传递给流的构造器

12.1.1 常用的构造器

  • public File(String pathname)
    • 以 pathname 为路径创建 File 对象,可以是绝对路径或者相对路径
  • public File(String parent,String child)
    • 以 parent 为父路径,child 为子路径创建 File 对象
  • public File(File parent,String child)
    • 根据一个父 File 对象和子文件路径创建 File 对象
    @Test
    public void test1() {
        //构造器一
        File file1 = new File("data/hello.txt.txt");//相对路径
        File file2 = new File("D:\\shangguiguProject\\JavaProject\\data\\hello.txt.txt");//绝对路径

        System.out.println(file1);

        //构造器二
        File file3 = new File("D:\\shangguiguProject", "JavaProject");

        //构造器三
        File file4 = new File(file3, "data/hello.txt.txt");

    }

12.1.2 路径分隔符

  • 路径中的每级目录之间用一个路径分隔符隔开
  • 路径分隔符和系统有关:
    • windows 和 DOS 系统默认使用 \ 来表示
    • UNIX 和 URL 使用 / 来表示
    • 在 Windows 中使用 / 也可以

12.1.3 常用方法

  • public String getAbsolutePath():获取绝对路径
  • public String getPath():获取路径
  • public String getName():获取名称
  • public String getParent():获取上层文件目录路径。若无,返回 null
  • public long length():获取文件长度(即:字节数)。不能获取目录的长度。
  • public long lastModified():获取最后一次的修改时间,毫秒值
@Test
public void test2() {
    File file1 = new File("../data/hello.txt");
    File file2 = new File("D:/word");

    System.out.println(file1.getAbsolutePath());
    System.out.println(file1.getPath());
    System.out.println(file1.getName());
    System.out.println(file1.getParent());
    System.out.println(file1.length());
    System.out.println(file1.lastModified());

    System.out.println();

    System.out.println(file2.getAbsolutePath());
    System.out.println(file2.getPath());
    System.out.println(file2.getName());
    System.out.println(file2.getParent());
    System.out.println(file2.length());
    System.out.println(file2.lastModified());
}
  • public String[] list():获取指定目录下的所有文件或者文件目录的名称数组
  • public File[] listFiles():获取指定目录下的所有文件或者文件目录的 File 数组
@Test
public void test3() {
    File file1 = new File("D:\\shangguiguProject\\JavaProject");
    String[] list = file1.list();

    System.out.println(Arrays.toString(list));

    File[] files = file1.listFiles();
    assert files != null;
    for (File file: files) {
        System.out.println(file);
    }
}
  • public boolean renameTo(File dest):把文件重命名为指定的文件路径,要保证返回 true,需要 file1 在硬盘中是存在的,且 file2 不能在硬盘中存在
@Test
public void test4() {
    File file1 = new File("../data/hello.txt.txt");
    File file2 = new File("D:/word/hi.txt");

    boolean rename = file1.renameTo(file2);
    System.out.println(rename);
}
  • public boolean isDirectory():判断是否是文件目录
  • public boolean isFile():判断是否是文件
  • public boolean exists():判断是否存在
  • public boolean canRead():判断是否可读
  • public boolean canWrite():判断是否可写
  • public boolean isHidden():判断是否隐藏
@Test
public void test5() {
    File file1 = new File("../data/hello.txt.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());

    System.out.println();
    File file2 = new File("../data");

    System.out.println(file2.isDirectory());
    System.out.println(file2.isFile());
    System.out.println(file2.exists());
    System.out.println(file2.canRead());
    System.out.println(file2.canWrite());
    System.out.println(file2.isHidden());
}
  • public boolean createNewFile():创建文件。若文件存在,则不创建,返回 false
  • public boolean mkdir():创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
  • public boolean mkdirs():创建文件目录。如果上层文件目录不存在,一并创建
  • public boolean delete():删除文件或者文件夹
@Test
public void test6() throws IOException {
    //文件创建
    File file1 = new File("../data/hi.txt");
    if (! file1.exists()) {
        file1.createNewFile();
        System.out.println("创建成功");
    } else {//文件存在
        file1.delete();
        System.out.println("删除成功");
    }
}

@Test
public void test7() {
    //文件目录的创建
    File file1 = new File("../data/io2/io");
    boolean mkdir = file1.mkdir();
    if (mkdir) {
        System.out.println("创建成功");
    }

    boolean mkdirs = file1.mkdirs();
    if (mkdirs) {
        System.out.println("多层目录创建成功");
    }

    //删除目录时该目录下不能有子目录或文件
    boolean delete = file1.delete();
    if (delete) {
        System.out.println("目录删除成功");
    }
}

12.2 IO 流原理及流的分类

12.2.1 Java IO 原理

  • I/O 是 Input/Output 的缩写, I/O 技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等

  • Java 程序 中,对于数据的输入/输出操作以 流(stream) 的方式进行

  • java.io 包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据

  • 输入 input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中

  • 输出 output:将程序(内存)数据输出到磁盘、光盘等存储设备中

12.2.2 流的分类

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

  • 按数据流的流向不同分为:输入流,输出流

  • 按流的角色的不同分为:

    • 节点流:直接从数据源或目的地读写数据
    • 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上
  • IO 流体系:如下表:

    分类 字节输入流 字节输出流 字符输入流 字符输出流
    抽象基类 InputStream OutputStream Reader Writer
    访问文件 FileInputStream FileOutputStream FileReader FileWriter
    访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
    访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
    访问字符串 StringReader StringWriter
    缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
    转换流 InputStreamReader OutputStreamWriter
    对象流 ObjectInputStream ObjectOutputStream
    过滤流 FilterInputStream FilterOutputStream FilterReader FilterWriter
    打印流 PrintStream PrinterWriter
    推回输入流 PushbackInputStream PushbackReader
    特殊流 DataInputStream DataOutputStream
  • InputStream 和 Reader 是所有 输入流 的基类

    • InputStream:

      • int read():从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1
      • int read(byte[] buffer):从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数
      • int read(byte[] buffer, int off, int len):将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1
    • Reader:

      • int read():读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个

        字节的Unicode码),如果已到达流的末尾,则返回 -1

      • int read(char[] cbuf):将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数

      • int read(char[] cbuf, int off, int len):将字符读入数组的某一部分。存到数组 cbuf 中,从 off 处开始存储,最多读 len 个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

  • OuputStream 和 Writer 是所有 输出流 的基类

    • OutputStream 和 Writer 也非常相似
      • void write(int b/int c);
      • void write(byte[] b/char[] cbuf);
      • void write(byte[] b/char[] buff, int off, int len);
      • void flush();:刷新该流的缓冲,则立即将它们写入预期目标
      • void close(); 需要先刷新,再关闭此流
  • 因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组,即以 String 对象作为参数

    • void write(String str);

    • void write(String str, int off, int len);

  • 程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源

  • FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader

  • FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter

12.3 节点流(文件流)

  • 对于非文本文件(图片、视频、.doc、.ppt)使用字节流进行处理
  • 文本文件(.txt、.java)使用字符流处理

12.3.1 字符输入流

@Test
public void fileReaderTest() {
    //1.实例化 File 类的对象,指明要操作的文件
    File file = new File("hello.txt");
    //2.提供具体的流
    FileReader fileReader = null;
    try {
        fileReader = new FileReader(file);
        //3.数据的读入
        /*int read = fileReader.read();
        while (read != -1) {
            System.out.print((char) read);
            read = fileReader.read();
        }*/
        int data;
        while ((data = fileReader.read()) != -1) {
            System.out.print((char)data);
        }

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.关闭流
        try {
            assert fileReader != null;
            fileReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

//对read()操作升级:使用read的重载方法
@Test
public void testFileReader1() {
    //1.File类的实例化
    File file = new File("hello.txt");
    FileReader reader = null;
    try {
        //2.FileReader流的实例化
        reader = new FileReader(file);
        //3.读入的操作
        //read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回 -1
        char[] cbuf = new char[5];
        int len;
        while ((len = reader.read(cbuf)) != -1) {
            //方式一
            //for (int i = 0; i < len; i++) {
            //	System.out.print(cbuf[i]);
            //}

            //方式二
            //错误的写法
            //String str = new String(cbuf);
            //System.out.print(str);
            //正确的写法
            String str2 = new String(cbuf, 0, len);
            System.out.print(str2);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.关闭流
        try {
            assert reader != null;
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

12.3.2 字符输出流

/*
从内存中写出数据到硬盘的文件中

说明:
1.输出操作,对应的File可以不存在
2.File对应的硬盘中的文件如果不存在,则会自动创建此文件
  如果存在:
     如果流使用的构造器是:FileWriter(file, false) / FileWriter(file):对原有文件进行覆盖
     如果流使用的构造器是:FileWriter(file, true): 对原有文件进行追加
     */
    @Test
    public void fileWriterTest() {
        //1.提供File类的对象,指明写出到的文件
        File file = new File("hello2.txt");
        FileWriter fileWriter = null;

        try {
            //2.提供FileWriter的对象,用于数据的写出
            fileWriter = new FileWriter(file, false);

            //3.写出的操作
            fileWriter.write("HelloWorld\n");
            fileWriter.write("Hello Java");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭流
            try {
                assert fileWriter != null;
                fileWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void testFileReaderFileWriter() {
        //1.创建File类的对象,指明读入和写出的文件
        File inputFile = new File("hello.txt");
        File outputFile = new File("hello_copy.txt");
        FileReader fileReader = null;
        FileWriter fileWriter = null;

        try {
            //2.创建FileReader类和FileWriter类的对象
            fileReader = new FileReader(inputFile);
            fileWriter = new FileWriter(outputFile);

            //3.数据的读入和写出操作
            char[] cbuf = new char[5];
            int len;//记录每次读入cbuf数组中的字符的个数
            while ((len = fileReader.read(cbuf)) != -1) {
                //每次写入len个字符
                fileWriter.write(cbuf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭流
            try {
                assert fileWriter != null;
                fileWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fileReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

12.3.3 字节输入、输出流

/*
实现对图片的复制
 */
@Test
public void testFileInputOutputStream() {
    //1.创建File类的对象,指明读入和写出的文件
    File inputFile = new File("../data/photo/u029.png");
    File outputFile = new File("../data/photo/u2.png");

    //2.创建FileReader类和FileWriter类的对象
    FileInputStream fis = null;
    FileOutputStream fos = null;

    try {
        fis = new FileInputStream(inputFile);
        fos = new FileOutputStream(outputFile);

        //3.数据的读入和写出操作
        byte[] cbuf = new byte[5];
        int len;//记录每次读入cbuf数组中的字节的个数
        while ((len = fis.read(cbuf)) != -1) {
            //每次写入len个字符
            fos.write(cbuf, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.关闭流
        try {
            assert fos != null;
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

12.4 缓冲流

  • 为了提高数据读写的速度,Java API 提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用 8192 个字节(8Kb)的缓冲区

image-20220110203427967

  • 缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为
    • BufferedInputStream 和 BufferedOutputStream
    • BufferedReader和 BufferedWriter
  • 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
  • 当使用 BufferedInputStream 读取字节文件时,BufferedInputStream 会一次性从文件中读取 8192 个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个 8192 个字节数组。
  • 使用方法 flush() 可以强制将缓冲区的内容全部写入输出流
  • flush() 方法的使用:手动将 buffer 中内容写入文件
  • 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流
  • 如果是带缓冲区的流对象的 close() 方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出

12.4.1 字节缓冲流

    public void bufferedInputOutputStream(String srcPath, String destPath) {
        //1.实例化File类
        File srcFile = new File(srcPath);
        File destFile = new File(destPath);

        //2.造流
        FileInputStream fis = null;
        FileOutputStream fos = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //2.1 造节点流
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            //2.2 造缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.操作数据
            byte[] bytes = new byte[1024];
            int len;

            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
                //bos.flush();//刷新缓冲区
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭流:后用先关。先关闭外层的流,再关闭内层的流
            try {
                assert bos != null;
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //说明关闭外层流的同时,内存流也会自动的进行关闭。
            //关于内存流的关闭,我们可以省略
            //fos.close();
            //fis.close();
        }
    }

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

        String srcPath = "C:\\Users\\14533\\Pictures\\QQplayerPic\\毕设录屏.mp4";
        String destPath = "C:\\Users\\14533\\Pictures\\QQplayerPic\\复制.mp4";

        bufferedInputOutputStream(srcPath, destPath);

        long endTime = System.currentTimeMillis();
        System.out.println("复制操作所花费的时间为:" + (endTime - startTime));//140
    }

12.4.2 字符缓冲流

	/*
    使用 BufferedReader 和 BufferedWriter 实现文本文件的复制
     */
    @Test
    public void testBufferedReaderBufferedWriter() {
        //1.创建文件和响应的流
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            br = new BufferedReader(new FileReader(new File("dbcp.txt")));
            bw = new BufferedWriter(new FileWriter(new File("dbcp2.txt")));

            //2.读写操作
            //方式一:使用 char[] 数组
/*            char[] cbuf = new char[1024];
            int len;
            while ((len = br.read(cbuf)) != -1) {
                bw.write(cbuf, 0, len);
            }*/

            //方式二:使用 String
            String data;
            while ((data = br.readLine()) != null) {
                //readLine():不包含换行
                //方式一:换行
                bw.write(data + "\n");

                //方式二:换行
                //bw.write(data);
                //bw.newLine();//提供换行的操作
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //3.关闭流
            try {
                assert bw != null;
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

12.5 转换流

  • 转换流提供了在字节流和字符流之间的转换

  • Java API 提供了两个转换流:

    • InputStreamReader:将 InputStream 转换为 Reader
    • OutputStreamWriter:将 Writer 转换为 OutputStream
  • 字节流中的数据都是字符时,转成字符流操作更高效

  • 很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能

12.5.1 InputStreamReader

  • 实现将字节的输入流按指定字符集转换为字符的输入流
  • 构造器
    • public InputStreamReader(InputStream in)
    • public InputStreamReader(InputStream in,String charsetName)
@Test
public void test1() throws IOException {

    FileInputStream fis = new FileInputStream("dbcp.txt");
    //InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符编码
    //参数二指明了字符集,具体使用哪个字符集,取决于文件保存时使用的字符集
    InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);

    char[] chars = new char[20];
    int len;
    while ((len = isr.read(chars)) != -1) {

        String str = new String(chars, 0, len);
        System.out.println(str);
    }

    isr.close();
}

12.5.2 OutputStreamWriter

  • 实现将字符的输出流按指定字符集转换为字节的输出流
  • 构造器
    • public OutputStreamWriter(OutputStream out)
    • public OutputStreamWriter(OutputStream out,String charsetName)
@Test
public void test2() throws IOException{
    FileInputStream fis = new FileInputStream("dbcp.txt");
    FileOutputStream fos = new FileOutputStream("dbcp_gbk.txt");

    InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
    OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK");

    char[] cbuf = new char[20];
    int len;
    while ((len = isr.read(cbuf)) != -1) {
        osw.write(cbuf, 0, len);
    }

    osw.close();
    isr.close();
}

12.6 标准输入、输出流

  • System.inSystem.out 分别代表了系统标准的输入和输出设备

  • 默认输入设备是:键盘,输出设备是:显示器

  • System.in 的类型是 InputStream

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

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

  • public static void setIn(InputStream in)

  • public static void setOut(PrintStream out)

public static void main(String[] args) {
    //方法二:使用 System.in 实现
    InputStreamReader isr = new InputStreamReader(System.in);
    BufferedReader br = new BufferedReader(isr);

    while (true) {
        String data = null;
        try {
            System.out.println("请输入字符串:");
            data = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
            break;
        }
        assert data != null;
        System.out.println(data.toUpperCase());
    }

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

12.7 打印流

  • 实现将基本数据类型的数据格式转化为字符串输出
  • 打印流:PrintStreamPrintWriter
    • 提供了一系列重载的 print()println() 方法,用于多种数据类型的输出
    • PrintStream 和 PrintWriter 的输出不会抛出 IOException 异常
    • PrintStream 和 PrintWriter 有自动 flush 功能
    • PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类
	@Test
    public void test2() {
        PrintStream ps = null;
        try {
            FileOutputStream fos = new FileOutputStream(new File("D:\\word\\text.txt"));
            // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
            ps = new PrintStream(fos, true);
            if (ps != null) {// 把标准输出流(控制台输出)改成文件
                System.setOut(ps);
            }
            for (int i = 0; i <= 255; i++) { // 输出ASCII字符
                System.out.print((char) i);
                if (i % 50 == 0) { // 每50个数据一行
                    System.out.println(); // 换行
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                ps.close();
            }
        }
    }

12.8 数据流

  • DataInputStream 和 DataOutputStream,分别“套接”在 InputStream 和 OutputStream 子类的流上

  • 作用:用于读取或写出基本数据类型的变量或字符串

@Test
public void test3() throws Exception{
    DataOutputStream dos = new DataOutputStream(new FileOutputStream("test.txt"));

    dos.writeUTF("Java");
    dos.flush();//刷新操作,将内存中的数据写入文件
    dos.writeInt(23);
    dos.flush();
    dos.writeBoolean(false);
    dos.flush();

    dos.close();
}

/*
将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中
注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致
 */
@Test
public void test4() throws IOException {
    DataInputStream dis = new DataInputStream(new FileInputStream("test.txt"));

    String s1 = dis.readUTF();
    System.out.println(s1);
    int i = dis.readInt();
    System.out.println(i);
    boolean s = dis.readBoolean();
    System.out.println(s);

    dis.close();
}

12.9 对象流

  • ObjectInputStream 和 OjbectOutputSteam

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

  • 序列化:ObjectOutputStream保存内存中的对象 -> 存储中的的文件、网络传输出去

  • 反序列化:ObjectInputStream读取存储中的文件、通过网络接收过来 -> 内存中的对象

  • ObjectOutputStreamObjectInputStream 不能序列化 statictransient 修饰的成员变量

12.9.1 对象的序列化

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

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

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

谈谈对 java.io.Serializable 接口的理解:

Serializable 接口用于序列化,是一个空方法的接口,可以看作是一个标识,实现了 Serializable 接口的对象,可以将它们装换成一系列字节,并可以恢复原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在 Windows 机器上创建一个对象,对其序列化,然后通过网络发给一台 Unix 机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节

12.9.2 序列化

  • 若某个类实现了 Serializable 接口,该类的对象就是可序列化的
    • 创建一个 ObjectOutputStream
    • 调用 ObjectOutputStream 对象的 writeObject(对象) 方法输出可序列化对象
    • 注意写出一次,操作 flush() 一次
  • 如果某个类的属性不是基本数据类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的 Field 的类也不能序列化
/*
Person 需要满足如下的要求,方可序列化
1.需要实现接口:Serializable
2.当前类提供一个全局常量:serialVersionUID
3.除了当前 Person 类需要实现 Serializable 之外,还必须保证其内部所有属性也
 必须是可序列化的(默认情况下,基本数据类型可序列化)

补充:ObjectOutputStream 和 ObjectInputStream 
	不能序列化 static 和 transient 修饰的成员变量
 */
public class Person implements Serializable {

    private static final long serialVersionUID = -6987513259817358L;

    private static String name;
    private transient int age;
    private int id;
    private Account account;

    public Person() {
    }

    public Person(String name, int age, int id, Account account) {
        this.name = name;
        this.age = age;
        this.id = id;
        this.account = account;
    }

    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 getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Account getAccount() {
        return account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                ", account=" + account +
                '}';
    }
}

class Account implements Serializable {
    private static final long serialVersionUID = -526528799939956L;

    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    public Account() {
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "balance=" + balance +
                '}';
    }
}
	/*
    序列化的过程:将内存中的 Java 对象保存到磁盘中或通过网络传输出去
    使用 ObjectOutputStream 实现
     */
    @Test
    public void testObjectOutputStream() {
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("data.dat"));

            oos.writeObject(new String("Java语言"));
            oos.flush();//刷新操作

            oos.writeObject(new Person("张三", 23, 1, new Account(4500)));
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                assert oos != null;
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

12.9.3 反序列化

    /*
    反序列化:将磁盘文件中的对象还原为内存中的一个 Java 对象
    使用 ObjectInputStream 来实现
     */
    @Test
    public void testObjectInputStream() {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("data.dat"));

            String str = (String) ois.readObject();
            Person p = (Person) ois.readObject();

            System.out.println(str);
            System.out.println(p);

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                assert ois != null;
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

12.10 随机存储文件流

  • RandomAccessFile 声明在 java.io 包下,但直接继承于 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 读写。如果文件不存在则会去创建文件,如果存在则不会创建

	@Test
    public void test1() {
        RandomAccessFile raf2 = null;
        try {
            RandomAccessFile raf1 = new RandomAccessFile(new File("../data/photo/u029.png"), "r");
            raf2 = new RandomAccessFile(new File("../data/photo/u029_1.png"), "rw");

            byte[] buffer = new byte[1024];
            int len;

            while ((len = raf1.read(buffer)) != -1) {
                raf2.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            try {
                assert raf2 != null;
                raf2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                raf2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /*
    通过指针定位,在指定的位置追加内容且保留后面已存在的内容
    */
    @Test
    public void test2() throws IOException{

        RandomAccessFile raf1 = new RandomAccessFile(new File("hello_1.txt"), "rw");

        raf1.seek(3);//将指针调到角标为3的位置

        StringBuilder builder = new StringBuilder((int)new File("hello_1").length());
        byte[] buffer = new byte[20];
        int len;
        while ((len = raf1.read(buffer)) != -1) {
            builder.append(new String(buffer, 0, len));
        }
        //调回指针
        raf1.seek(3);
        raf1.write("zzz".getBytes());

        //将StringBuilder中的数据写入到文件中
        raf1.write(builder.toString().getBytes());
        raf1.close();
    }

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

12.11.1 Path、Paths 类的使用

public class PathTest {

    //如何使用Paths实例化Path
    @Test
    public void test1() {
        Path path1 = Paths.get("d:\\nio\\hello.txt");//new File(String filepath)

        Path path2 = Paths.get("d:\\", "nio\\hello.txt");//new File(String parent,String filename);

        System.out.println(path1);
        System.out.println(path2);

        Path path3 = Paths.get("d:\\", "nio");
        System.out.println(path3);
    }

    //Path中的常用方法
    @Test
    public void test2() {
        Path path1 = Paths.get("d:\\", "nio\\nio1\\nio2\\hello.txt");
        Path path2 = Paths.get("hello.txt");

//		String toString() : 返回调用 Path 对象的字符串表示形式
        System.out.println(path1);

//		boolean startsWith(String path) : 判断是否以 path 路径开始
        System.out.println(path1.startsWith("d:\\nio"));
//		boolean endsWith(String path) : 判断是否以 path 路径结束
        System.out.println(path1.endsWith("hello.txt"));
//		boolean isAbsolute() : 判断是否是绝对路径
        System.out.println(path1.isAbsolute() + "~");
        System.out.println(path2.isAbsolute() + "~");
//		Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
        System.out.println(path1.getParent());
        System.out.println(path2.getParent());
//		Path getRoot() :返回调用 Path 对象的根路径
        System.out.println(path1.getRoot());
        System.out.println(path2.getRoot());
//		Path getFileName() : 返回与调用 Path 对象关联的文件名
        System.out.println(path1.getFileName() + "~");
        System.out.println(path2.getFileName() + "~");
//		int getNameCount() : 返回Path 根目录后面元素的数量
//		Path getName(int idx) : 返回指定索引位置 idx 的路径名称
        for (int i = 0; i < path1.getNameCount(); i++) {
            System.out.println(path1.getName(i) + "*****");
        }

//		Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
        System.out.println(path1.toAbsolutePath());
        System.out.println(path2.toAbsolutePath());
//		Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
        Path path3 = Paths.get("d:\\", "nio");
        Path path4 = Paths.get("nioo\\hi.txt");
        path3 = path3.resolve(path4);
        System.out.println(path3);

//		File toFile(): 将Path转化为File类的对象
        File file = path1.toFile();//Path--->File的转换

        Path newPath = file.toPath();//File--->Path的转换

    }
}

12.11.2 Files 类的使用

public class FilesTest {

   @Test
   public void test1() throws IOException{
      Path path1 = Paths.get("d:\\nio", "hello.txt");
      Path path2 = Paths.get("atguigu.txt");
      
//    Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
      //要想复制成功,要求path1对应的物理上的文件存在。path1对应的文件没有要求。
//    Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);
      
//    Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
      //要想执行成功,要求path对应的物理上的文件目录不存在。一旦存在,抛出异常。
      Path path3 = Paths.get("d:\\nio\\nio1");
//    Files.createDirectory(path3);
      
//    Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
      //要想执行成功,要求path对应的物理上的文件不存在。一旦存在,抛出异常。
      Path path4 = Paths.get("d:\\nio\\hi.txt");
//    Files.createFile(path4);
      
//    void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
//    Files.delete(path4);
      
//    void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束
      Files.deleteIfExists(path3);
      
//    Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
      //要想执行成功,src对应的物理上的文件需要存在,dest对应的文件没有要求。
//    Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE);
      
//    long size(Path path) : 返回 path 指定文件的大小
      long size = Files.size(path2);
      System.out.println(size);

   }

   @Test
   public void test2() throws IOException{
      Path path1 = Paths.get("d:\\nio", "hello.txt");
      Path path2 = Paths.get("atguigu.txt");
//    boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
      System.out.println(Files.exists(path2, LinkOption.NOFOLLOW_LINKS));

//    boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
      //不要求此path对应的物理文件存在。
      System.out.println(Files.isDirectory(path1, LinkOption.NOFOLLOW_LINKS));

//    boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件

//    boolean isHidden(Path path) : 判断是否是隐藏文件
      //要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。
//    System.out.println(Files.isHidden(path1));

//    boolean isReadable(Path path) : 判断文件是否可读
      System.out.println(Files.isReadable(path1));
//    boolean isWritable(Path path) : 判断文件是否可写
      System.out.println(Files.isWritable(path1));
//    boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
      System.out.println(Files.notExists(path1, LinkOption.NOFOLLOW_LINKS));
   }

   /**
    * StandardOpenOption.READ:表示对应的Channel是可读的。
    * StandardOpenOption.WRITE:表示对应的Channel是可写的。
    * StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略
    * StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常
    *
    * @author shkstart 邮箱:shkstart@126.com
    * @throws IOException
    */
   @Test
   public void test3() throws IOException{
      Path path1 = Paths.get("d:\\nio", "hello.txt");

//    InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
      InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);

//    OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
      OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE,StandardOpenOption.CREATE);


//    SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
      SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);

//    DirectoryStream<Path>  newDirectoryStream(Path path) : 打开 path 指定的目录
      Path path2 = Paths.get("e:\\teach");
      DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2);
      Iterator<Path> iterator = directoryStream.iterator();
      while(iterator.hasNext()){
         System.out.println(iterator.next());
      }


   }
}
posted @   Aunean  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示