• JavaIO流04:对象流、随机存取文件流(RandomAccessFile类)


    一、对象流

    • ObjecInputStream 和 ObjectOutputStream
      • 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
    • 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制。
    • 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制。
    • ObjectOutputStream和ObjectInputStream不能序列化statictransient修饰的成员变量。

    对象的序列化

    • 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输出到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
    • 序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。
    • 序列化是RMI(Remote Method Invoke - 远程方法调用)过程的参数和返回值都必须实现的机制,而RMI是JavaEE的基础。因此序列化机制是JavaEE平台的基础
    • 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常。
      • Serializable
      • Externalizcble
    • 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量
      • private static final long serialVersionUID;
      • serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
      • 如果类没有显式定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。故建议,显式声明。
    • 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的,在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidClassException)

    对象流的使用:

    1.ObjectInputStream 和 ObjectOutputStream
    2.作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
    3.要想一个java对象是可序列化的,需要满足相应的要求。见Person.java
    4.序列化机制:
      对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输出到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

    public class ObjectInputOutputStreamTest {
        /*
        序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去。
        使用ObjectOutputStream实现
         */
        @Test
        public void testObjectOutputStreamtest() {
    
            ObjectOutputStream oos = null;
            try {
                oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
    
                oos.writeObject(new String("我爱北京天安门"));
                oos.flush(); //刷新操作
    
                oos.writeObject(new Person("刘德华", 23));
                oos.flush();
    
                oos.writeObject(new Person("张学友", 18, 1001, new Account(5000)));
                oos.flush();
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (oos != null) {
                    try {
                        oos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /*
        反序列化:将磁盘文件中的对象还原为内存中的java对象。
        使用ObjectInputStream实现
         */
        @Test
        public void testObjectInputStreamTest() {
    
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(new FileInputStream("object.dat"));
    
                Object obj = ois.readObject();
                String str = (String) obj;
    
                Person p1 = (Person) ois.readObject();
                Person p2 = (Person) ois.readObject();
    
                System.out.println(str); //我爱北京天安门
                System.out.println(p1); //Person{name='刘德华', age=23, id=0, acct=null}
                System.out.println(p2); //Person{name='张学友', age=18, id=0, acct=Account{balance=5000.0}}
                //因为id是static修饰的成员变量,所以不能进行序列化
    
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                if (ois != null) {
                    try {
                        ois.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    Preson类需要满足如下的要求,方可序列化
    1.需要实现接口:Serializable
    2.当前类提供一个全局常量:serialVersionUID
    3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部的所有属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)

    补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量。

    public class Person implements Serializable {
    
        public static final long serialVersionUID = 4787639798979L;
    
        private String name;
        private int age;
        private static int id;
        private Account acct;
    
        public Person() {
        }
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Person(String name, int age, int id) {
            this.name = name;
            this.age = age;
            this.id = id;
        }
    
        public Person(String name, int age, int id, Account acct) {
            this.name = name;
            this.age = age;
            this.id = id;
            this.acct = acct;
        }
    
        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 getAcct() {
            return acct;
        }
    
        public void setAcct(Account acct) {
            this.acct = acct;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", id=" + id +
                    ", acct=" + acct +
                    '}';
        }
    }
    
    class Account implements Serializable {
    
        public static final long serialVersionUID = 982348762387L;
    
        private double balance;
    
        public Account() {
        }
    
        public Account(double balance) {
            this.balance = balance;
        }
    
        public double getBalance() {
            return balance;
        }
    
        public void setBalance(double balance) {
            this.balance = balance;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "balance=" + balance +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    二、RandomAccessFile类

    • RandomAccessFile声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。
    • RandomAccessFile类支持“随机访问”的方式,程序可以直接跳到文件的任意地方来读、写文件。
      • 支持只访问文件的部分内容。
      • 可以向已存在的文件后追加内容。
    • RandomAccessFile类对象包含一个记录指针,用以标识当前读写处的位置。
      RandomAccessFile类对象可以自由移动记录指针:
      • ==long getFilePointer():==获取文件
      • ==void seek(long pos):==获取文件
    • 构造器:
      • public RandomAccessFile(File file, String mode)
      • public RandomAccessFile(String name, String mode)
    • 创建RandomAccessFile类实例需要指定一个mode参数,该参数指定RandomAccessFile的访问模式:
      • r:以只读模式打开
      • rw:打开以便读取和写入
      • rwd:打开以便读取和写入;同步文件内容的更新
      • rws:打开一边读取和写入;同步文件内容和元数据的更新
    • 如果模式为只读r,则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。如果模式为rw读写,如果文件不存在则会去创建文件,如果存在则不会创建。
    • 我们可以用RandomAccessFile这个类,来实现一个多线程断点下载的功能。用过下载工具的朋友们都知道,下载前都会建立两个临时文件,一个是与被下载文件大小相同的空文件,另一个是记录文件指针的位置文件。每次暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上一次的地方下载,从而实现断点下载或上传的功能。

    RandomAccessFile的使用

    1.RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口。
    2.RandomAccessFile既可以作为一个输入流,又可以作为一个输出流。
    3.RandomAccessFile作为输出流时:
      如果写出到的文件不存在,则在执行过程中自动创建;
      如果写出到的文件已存在,则会对原有文件内容进行覆盖。(默认情况下,从头开始覆盖)
    4.可以通过相关的操作,实现RandomAccessFile“插入”数据的效果。

    public class RandomAccessFileTest {
    
    	//读取数据
        @Test
        public void test1() {
    
            RandomAccessFile raf1 = null;
            RandomAccessFile raf2 = null;
            try {
                //1.造流
                raf1 = new RandomAccessFile(new File("2022.06.25.jpg"), "r");
                raf2 = new RandomAccessFile(new File("2022.06.25-random.jpg"), "rw");
                //2.读写过程
                byte[] buffer = new byte[1024];
                int len;
                while ((len = raf1.read(buffer)) != -1) {
                    raf2.write(buffer, 0, len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //3.关闭流
                if (raf1 != null) {
                    try {
                        raf1.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (raf2 != null) {
                    try {
                        raf2.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    	//写入数据
        @Test
        public void test2() {
    
            RandomAccessFile raf1 = null;
            try {
                raf1 = new RandomAccessFile(new File("hello.txt"), "rw");
    
                raf1.seek(3); //将指针调到角标为3的位置
                raf1.write("xyz".getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (raf1 != null) {
                    try {
                        raf1.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /*
        使用RandomAccessFile实现数据的插入效果
         */
        @Test
        public void test3() {
    
            RandomAccessFile raf1 = null;
            try {
                raf1 = new RandomAccessFile(new File("hello.txt"), "rw");
    
                raf1.seek(3); //将指针调到角标为3的位置
                //保存指针3后面的所有数据到StringBuilder中
                StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
                byte[] buffer = new byte[20];
                int len;
                while ((len = raf1.read(buffer)) != -1) {
                    builder.append(new String(buffer,0,len));
                }
    
                //调回指针,写入“xyz”
                raf1.seek(3);
                raf1.write("xyz".getBytes());
                //将StringBuilder中的数据写入到文件中
                raf1.write(builder.toString().getBytes());
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (raf1 != null) {
                    try {
                        raf1.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        //将StringBuilder替换为ByteArrayOutputStream
        @Test
        public void test4() {
    
            RandomAccessFile raf1 = null;
            try {
                raf1 = new RandomAccessFile(new File("hello.txt"), "rw");
    
                raf1.seek(3); //将指针调到角标为3的位置
    
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[10];
                int len;
                while ((len = raf1.read(buffer)) != -1) {
                    baos.write(buffer, 0, len);
                }
    
                //调回指针,写入“xyz”
                raf1.seek(3);
                raf1.write("xyz".getBytes());
                raf1.write(baos.toString().getBytes());
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (raf1 != null) {
                    try {
                        raf1.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    三、NIO.2中Path、Paths、Files类的使用(了解)

    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

    NIO.2

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

    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路径

    Path常用方法

    String toString():返回调用Path对象的字符串表示形式
    boolean startsWith(String path):判断是否以path路径开始
    boolean endsWith(String path):判断是否以path路径结束
    boolean isAbsolute():判断是否是绝对路径
    Path getParent():返回Path对象包含整个路径,不包含Path对象指定的文件路径
    Path getRoot():返回调用Path对象的根路径
    Path getFileName():返回与调用Path对象关联的文件名
    int getNameCount():返回Path根目录后面元素的数量
    Path getName(int idx):返回指定索引位置idx的路径名称
    Path toAbsolutePath():作为绝对路径返回调用Path对象
    Path resolve(Path p):合并两个路径,返回合并后的路径对应的Path对象
    File toFile():将Path转化为File类的对象

    Files类

    • java.nio.file.Files 用于操作文件或目录的工具类。
    • Files常用方法:
      Path copy(Path src, Path dest, CopyOption … how):文件的复制
      Path createDirectory(Path path, FileAttribute ... attr):创建一个目录
      Path createFile(Path path, FileAttribute … arr):创建一个文件

      void delete(Path path):删除一个文件/目录,如果不存在,执行报错
      void deleteIfExists(Path path):Path对应的文件/目录如果存在,执行删除
      Path move(Path src, Path dest, CopyOption … how):将src移动到dest位置
      long size(Path path):返回path指定文件的大小
    • Files常用方法:用于判断
      boolean exists(Path path, LinkOption … opts):判断文件是否存在
      boolean isDirectory(Path path, LinkOption … opts):判断是否是目录
      boolean isRegularFile(Path path, LinkOption … opts):判断是否是文件
      boolean isHidden(Path path):判断是否是隐藏文件
      boolean isReadable(Path path):判断文件是否可读
      boolean isWritable(Path path):判断文件是否科协
      boolean notExists(Path path, LinkOption … opts):判断文件是否不存在
    • Files常用方法:用于操作内容
      SeekableByteChannel newByteChannel(Path path, OpenOption … how):获取与指定文件的连接,how指定打开方法
      DirectoryStream newDirectoryStream(Path path):打开path指定的目录
      InputStream newInputStream(Path path, OpenOption … how):获取InputStream对象
      OutputStream newOutputStream(Path path, OpenOption … how):获取OutputStream对象
  • 相关阅读:
    尚硅谷ES6复习总结上(64th)
    Hive Join相关操作及常见函数
    StatusBar,状态栏设置中文
    [第十三篇]——Docker Compose
    3DTile是不是没有坐标的选择?
    Leecode1160: 拼写单词
    轻量级日志系统——Loki
    进程间通信学习笔记(有名管道和无名管道)
    启山智软/电商商城100%开源
    Wails简介
  • 原文地址:https://blog.csdn.net/m0_61467488/article/details/126323958