JavaIO流
编辑时间:2021/03/27
读完本节:大概花费20钟,共3275词
1.对象流
-
ObjectInputStream和ObjectOutputStream
- 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来
-
序列化:用ObjectOutputStream类保存基本数据类型数据或对象的机制
-
反序列化:用ObjectInputStream类读取基本数据类型数据或对象的机制
-
ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量,原因是static修饰的类和变量是共享的在对象实例化之前;transient一旦修饰某个变量,则表示这个变量不再是对象持久化的一部分,该变量内容在序列化后无法获得访问,transient关键字只能修饰变量,不能修饰方法和类,同时本地变量不能被transient修饰,变量如果是用户自定义类的变量,则该类需要实现Serializable接口
-
对象的序列化机制:该机制允许内存中的Java对象转换成与平台无关的二进制流,从而允许把这种二进制流持久的保存在硬盘上,或通过网络将这种二进制流传输到另一个网络节点。当其他程序获取了这中二进制流,就可以恢复成原来内存中的Java对象
-
序列化的好处在于可以将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可以被还原
-
序列化时**RMI(Remote Method Invoke - 远程方法调用)**过程的参数和返回值都必须实现的机制,而RMI是JavaEE的基础。因此序列化机制是JavaEE平台的基础
-
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现Serializable或者Externalizable,否则会抛出NotSerializableException异常
-
对象的序列化:
- 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
- private static final long serialVersionUID;
- serialVersionUID用来表明类的不同版本间的兼容性。即其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容
- 如果没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的,若类的实例化变量做了修改,serialVersionUID可能发生变化,导致已经序列化的对象无法反序列化,因此需要用户显示定义声明
- Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行序列化,否则就会出现序列化版本不一致的异常:InvaildCastException
- 除了当前类需要实现Serializable接口外,还必须保证颞部所有的属性也必须是可序列化的,基本数据类型默认可序列化,自定义类需要用户实现Serializable接口和提供serialVersionUID,自定义类内部还有自定类,也需要进行相同的操作
- 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
-
ObjectInputOutputStreamTest
import org.junit.Test; import java.io.*; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/27 14:56 * @Description: */ public class ObjectInputOutputStreamTest { @Test /** 序列化过程:将内存中的Java对象保存到磁盘中或通过网络传输出去 * 使用ObjectOutputStream */ public void testObjectOutputStream(){ ObjectOutputStream oos = null; try { //1. 创建ObjectOutputStream的实例化对象 oos = new ObjectOutputStream(new FileOutputStream("object.dat")); //2. 向oos对象中写入一个字符串对象 oos.writeObject(new String("buttercup")); //刷新操作 oos.flush(); //向oos对象中写入一个Person对象 oos.writeObject(new Person("Biggy", 22)); oos.flush(); //向oos对象中写入一个带Account的对象 oos.writeObject(new Person("biggy", 22, 1001, new Account(12145.0))); oos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if(oos != null){ try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } } @Test /** 反序列化过程:将磁盘中或网络中下载的对象还原为内存中的一个Java对象 * 使用ObjectInputStream实现 */ public void testObjectInputStream(){ ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream("object.dat")); Object obj = ois.readObject(); String str = (String)obj; System.out.println(str); Person p = (Person)ois.readObject(); System.out.println(p); Person p1 = (Person)ois.readObject(); System.out.println(p1); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { if(ois != null){ try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
Person.java& Account.java
import java.io.Serializable; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/27 15:05 * @Description: */ public class Person implements Serializable { public static final long serialVersonUID = 4859126156521L; private String name; private int age; private 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, 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{ private static final long serialVersionUID = 45649815L; 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 + '}'; } }
2.随机存取文件流(RandomAccessFile)
-
RandomAccessFile声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput这两个接口,意味着这个类既可以写也可以读,既可以作为输入流、也可以是输出流
-
RandomAccessFile类支持“随机访问”的方式,程序可以直接跳到文件的任意地方进行读、写文件
- 支持只访问文件的部分内容
- 可以向已存在的文件后追加内容
-
RandomAccessFile对象包含一个记录指针,用以标识当前读写处的位置;RandomAccessFile类的对象可以自由移动记录指针
- long getFilePointer():获取文件记录指针的当前位置
- void seek(long pos):将文件记录指针定位到pos位置
-
RandomAccessFile的构造器:
- public RandomAccessFile(File file, String mode)
- public RandomAccessFile(String name, String mode)
- 创建RandomAccessFile类的实例需要指定一个mode参数,该参数指定RandomAccessFile的访问模式
- r:以只读的方式打开
- rw:打开以便读取和写入
- rwd:打开以便读取和写入,同步文件内容的更新
- rws:打开以便读取和写入,同步文件和元数据的更新
- 每次write数据时,”rw“模式不会立即写到硬盘中;而”rwd“模式下,数据会被立刻写入硬盘。如果写数据的过程中发生异常,”rwd“模式中已被write的数据被保存到硬盘,而”rw“模式下数据则全部丢失
- 如果模式为只读”r“。则不会创建文件,而是会读取一个已经存在的文件,如果读取的文件不存在则会出现异常。如果模式为”rw“读写模式,若文件不存在,则会去创建文件,反之,存在则不会创建。
- 对于已经存在的文件,将数据写出文件的过程中,会对原有文件的数据从头进行覆盖
-
RandomAccessFile读写测试
import org.junit.Test; import java.io.IOException; import java.io.RandomAccessFile; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/27 15:57 * @Description: */ public class RandomAccessFileTest { @Test public void test1(){ RandomAccessFile raf1 = null; RandomAccessFile raf2 = null; try { //1. 创建随机存取文件流的对象,并指定实例化对象的读取模式 raf1 = new RandomAccessFile("UnlimitedProgress.jpg", "r"); raf2 = new RandomAccessFile("UnlimitedProgress.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(raf2 != null){ try { raf2.close(); } catch (IOException e) { e.printStackTrace(); } }if(raf1 != null){ try { raf1.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
RandomAccessFile对已有文件覆盖测试,以及使用RandomAccessFile实现”插入“效果
import org.junit.Test; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/27 15:57 * @Description: */ public class RandomAccessFileTest { @Test public void test2(){ RandomAccessFile raf1 = null; try { raf1 = new RandomAccessFile("hello.txt", "rw"); //先提供一个hello.txt文件内容为“abcdefghijklmnopq” raf1.write("xyz".getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { if(raf1 != null){ try { raf1.close(); } catch (IOException e) { e.printStackTrace(); } } } } @Test /** 从指定位置开始覆盖文件内容 */ public void test3(){ RandomAccessFile raf1 = null; try { raf1 = new RandomAccessFile("hello.txt", "rw"); //先提供一个hello.txt文件内容为“abcdefghijklmnopq” //将指针调到角标为三的位置,从第四个字母开始覆盖 raf1.seek(3); raf1.write("xyz".getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { if(raf1 != null){ try { raf1.close(); } catch (IOException e) { e.printStackTrace(); } } } } @Test /** 使用RandomAccessFile实现数据的插入效果 */ public void test4(){ RandomAccessFile raf1 = null; try { raf1 = new RandomAccessFile("hello1.txt","rw"); raf1.seek(3); //保存指针3后面的数据到StringBuilder sb中 StringBuilder sb = new StringBuilder((int) new File("hello1.txt").length()); byte[] buffer = new byte[20]; int len; while ((len = raf1.read(buffer)) != -1){ sb.append(new String(buffer,0,len)); } //调回指针,写入xyz raf1.seek(3); raf1.write("xyz".getBytes()); //将StringBuilder中的数据写入到文件中 raf1.write(sb.toString().getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { //关闭流资源 if(raf1 != null){ try { raf1.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
3.NIO.2中Path、Paths、Files类的使用
-
JavaNIO(New IO, Non-Blocking IO)是从Java1.4版本开始引入的一套新的IO API,可以替代Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的操作、基于通道的操作;而IO是面向流的操作。NIO将以更加高效的方式进行文件的读写操作
-
JavaAPI中提供了两套NIO,一套是针对标准的输入输出NIO,另一套就是网络编程NIO
-
java.nio.channels.Channel下有以下常用子类:
- FileChannel:处理本地文件
- SocketChannel:TCP网络编程客户端的Channel
- ServerSoctketChannel:TCP网络编程的服务器端的Channel
- DatagramChannel:UDP网络编程中发送端和接收端的Channel
-
NIO.2:随着JDK1.7的发布,Java对NIO进行了扩展,增强了对文件处理和文件系统特性的支持,以至于之为NIO.2
-
Path、Paths、Files核心API:
-
早期Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高,而且,大多数方法在出错时只返回事变,并不会提供异常信息
-
NIO.2为了弥补这种不足,引入了Path接口,代表一个与平台无关的路径,描述了目录结构中文件的位置。Path可以看成时File类的升级版本,实际引用的资源也可以不存在
-
JDK1.7以前:
-
import java.io.File;
File file = new File(“index.html”);
-
-
JDK1.7以后:
-
import.java.nio.fiie.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类常用的方法:
- 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 move(Path src, Path dest, CopyOption … how):将src移动到dest位置
- long size(Path path):返回path指定文件的大小
-
File类常用方法:用于判断
- boolean exists(Path path, LinkOption … opts):判断文件是否存在
- boolean isDriectory(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, LinkOption … opts):获取指定文件的连接,how指定打开方式
- DirectoryStream<path> newDirectoryStream(Path path):打开path指定的目录
- InputStream newInputStream(Path path, LinkOption … opts):获取InputStream的对象
- OutputStream newOutputStream(Path path, LinkOption … opts):获取OutputStream的对象
-