java.io
输入流、输出流
字节流-字符流
序列化:将内存中的java对象转换为二进制字节流
Java的IO使用了装饰器模式
15.1 File类
表示与平台无关的文件和目录
/ 斜线
\ 反斜线
\ 双斜线表示windows下路径
/ 单反斜线,也可以表示windows下路径
String[] list() 方法 所有子文件名和路径名
File[] listFiles() 方法
FilenameFilter接口包含一个accept方法,该方法将依次对指定File的所有子目录或者文件进行迭代
函数式接口(只有一个抽象方法),都可以使用Lambda表达式去创建实现该接口的对象 P668
15.2 理解Java的IO流
Java的流主要包括字节流(InputStream、OutputStream)和字符流(Reader、Writer),他们都是抽象的基类
输入流 InputStream Reader
输出流 OutputStream Writer
从程序运行所在内存的角度划分的
字节流,操作的数据单元是8位的字节
字符流,操作的数据单元是16位的字符
节点流是指与特定的IO设备发生读写数据的流,也称为低级流;
处理流则用于对一个已存在的流进行连接或封装,通过封装后的流来实现读写功能,处理流也称为高级流。
处理流:消除不同节点流之间的实现差异
处理流是一种典型的装饰器设计模式
处理流主要以增加缓冲的方式来提高输入输出的效率并且可能提供了一系列便捷的方法来一次输入输出大批量的内容,处理流可以嫁接在任何已存在的流的基础上
15.3 字节流和字符流
字节流(InputStream、OutputStream)
字符流(Reader、Writer)
FileInputStream、FileOutputStream
FileReader、FileWriter
Java 7中的IO资源类都实现了AutoCloseable接口,可以通过try语句关闭IO流。
使用Java的IO流执行输出时,要关闭输出流,关闭输出流除了保证流的物理资源被回收外,可能还可以将输出流缓冲区中的数据flush到物理节点里,
因为在执行close方法之前,自动执行输出流的flush方法。
关闭输入输出资源时,只需关闭最上层的流即可,系统会自动关闭被该处理流包装的节点流
15.4 输入输出体系
如何识别处理流:如果流的构造器不是一个物理节点,而是已经存在的流,那么这个流一定是处理流
节点流;都是以物理IO节点为构造器参数的
Java的输入输出体系有将近40个类
字节流功能比字符流强大
输入输出内容是文本内容,用字符流
输入输出内容是二进制内容,用字节流
Windows下简体中文默认使用GBK字符集,Linux下默认使用UTF-8字符集
计算机内只有两种文件:二进制文件和文本文件
PrintStream
转换流
InputStreamReader和OutputStreamWriter将字节流转换成字符流
System.in代表标准输入,是InputStream类的实例,使用InputStreamReader将其转换成字符输入流,然后再次包装成BufferedReader(具有缓冲功能)
BufferedReader:经常把读取文本内容的数据流包装成BufferedReader,可以方便地读取输入流文本内容
推回输入流
PushbackInputStream和PushbackReader,提供了unread方法,将字节、字符推回到缓冲区,从而允许重复读取。
这两个推回输入流都带有一个推回缓冲区,读取也是先从推回缓冲区读取,完全读取了推回缓冲区的内容后,还没装满read所需的数组时才会从原输入流中读取。
15.5 重定向标准输入/输出
Java的标准输入输出,分别通过System.in和System.out来代表,默认分别代表键盘和显示器
static void setErr(PringStream err):
static void setIn(InputStream in):
static void setOut(PrintStream out):
System.setOut(ps); 把标准输出流设置为ps上
15.6 Java虚拟机读写其他进程的数据
Process p = Runtime.getRuntime().exec(“java ReadStandard”);
Process类提供了InputStream getErrorStream():获取子进程的错误流;
InputStream getInputStream():获取子进程的输入流;
OutputStream getOutputStream():获取子进程的输出流;
要站在本程序的角度看子程序来决定是输入还是输出。
15.7 RandomAccessFile
java.io.RandomAccessFile
RandomAccessFile可以自由访问文件的任意位置(与InputStream和Reader依次向后读取相区分)
既可以输入也可以输出
文件位置指针定位后进行输出会覆盖原有内容,需要借用缓冲方案(数组、临时文件等)来实现追加功能
最大局限:只能读写文件,不能读写其他IO
void seek(long pos)
设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
long getFilePointer()
返回此文件中的当前偏移量
RandomAccessFile的这个方法
void write(byte[] b, int off, int len)
将 len 个字节从指定 byte 数组写入到此文件,并从偏移量 off 处开始。
写文件一定要用这个~~
15.8 对象序列化
Java对象—>与平台无关的二进制流(存在磁盘中或者在网络上直接传输)
序列化机制可以使对象脱离程序,而单独存在
Serialize 序列化 Java对象写入IO流
Deserilize 反序列化 IO流中恢复该Java对象
要序列化必须实现Serializable(标记接口,无需实现任何方法)或Externalizable接口。
RMI Remote Method Invoke 远程方法调用 JavaEE的基础
所有需要在网络上传输的对象类,所有要保存到磁盘的对象类,都必须可序列化
建议,程序创建的每一个JavaBean类,都实现Serilizable类
使用对象流,来实现序列化
java.io.ObjectOutputStream 序列化对象,输出
void writeObject(Object obj)
将指定的对象写入 ObjectOutputStream。
java.io.ObjectInputStream 反序列化,输入
Object readObject()
从 ObjectInputStream 读取对象。
使用序列化机制向文件中写入了多个java对象,使用反序列化机制恢复对象时必须按实际写入的顺序读取;
反序列化要提供Java对象所属类的class文件,反序列化无需调用构造器来初始化Java对象
当一个可序列化类有多个父类时(直接或间接),这些父类要么有无参数的构造器,要么也是可序列化的;
如果父类不可序列化,只是带有无参数的构造器,则父类中定义的Field值不会序列化到二进制流中。
对象引用的序列化
如果类的Field类型是引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的Field的类也是不可序列化的。
当多个对象引用了同一个对象,多次序列化引发的问题。
解决:所有保存到磁盘中的对象,都有一个序列化编号
当多个对象引用了同一个对象时,不会多次序列化引用对象,只是在初次序列化时会序列化该对象,之后直接输出一个序列化编号
潜在的问题,先序列化,然后修改引用对象,再序列化时,只会输出一个编号,不会将改动输出到序列化对象中去
自定义序列化
递归序列化
1包含一些敏感信息,或者某个Field类型是不可序列化的,不希望对该Field进行序列化;可以在Field前面使用transient关键字修饰,这样反序列化时该Field将返回0或者null等,就无法取得该Field值。
*transient只能修饰实例变量
2在类中定义如下方法
writeObject方法负责写入特定类的实例状态,以便相应的readObject方法可以恢复它,重写该方法可以完全获得对序列化机制的控制,可以自主决定哪些Field需要序列化,需要怎样序列化,默认会调用out.defaultWriteObject来保存Java对象的各Field,从而可以实现序列化Java对象那个状态的目的。
readObject与此相反用于反序列化,默认调用in.defaultReadObjectlai恢复Java对象的非静态和非瞬态Field。
当序列化流不完整时,readObjectNoData方法可以用来正确地初始化反序列化的对象,例如接收方使用的反序列化类的版本不同于发送方,或者接收方版本扩展的类不是发送方版本扩展的类,或者序列化流被篡改时,系统都会调用readObjectNoData方法来初始化反序列化的对象。
3writeReplace()
Java的序列化机制保证在序列化某个对象之前,先调用该对象的writeReplace()方法,
如果该方法返回另一个Java对象,则系统转为序列化另一个对象
系统在序列化某个对象之前,总是先调用该对象的writeReplace()方法,再调用该对象的writeObject方法来来保存该对象的状态
readResolve()方法
这个方法会紧随着readObject之后被调用,其返回值会代替原来反序列化的对象
另一种自定义序列化机制
实现Externalizable接口,自定义,自由度提升,但辩称复杂度提升
注意:
1、对象的类名、实例Field都会被序列化;
2、方法、static Field、transient Field都不会被序列化,反序列化对象时必须有序列化对象的class文件;
3、通过文件、网络来读取序列化后的对象时,必须按实际写入的顺序读取
一个类升级后,反序列化(依赖于原来的类)还能成功吗?如何解决兼容性?
建议:人为添加private static final long serialVersionUID标识该Java类的序列化版本
只要serialVersionUID值一样,序列化机制就认为这两个类是同一个序列化版本,可顺利反序列化
如果不人为指定 serialVersionUID,JVM根据类信息计算,那么修改后的类的计算结果与修改前的类的计算结果往往不同。造成反序列化失败
**修改方法、静态Field或瞬态Field,反序列化不受任何影响
修改了类中非瞬态的实例变量:
1、int—>float
2、类中变量多了,可以兼容
3、类中变量少了,可以兼容
15.9 NIO
新IO采用内存映射的方式来处理输入\输出,模拟虚拟内存
Channel和Buffer是新IO中(NIO)的两个核心对象。Channel是对传统的输入输出系统的模拟,在新IO系统中所有的数据都需要通过通道传输;
Channel与传统的InputStream、OutputStream最大区别在于它提供了一个map方法将一块数据映射到内存
Buffer可以理解成一个容器,本质是一个数组,发送到Channel或者从Channel中取出的数据必须先放到Buffer中
java.nio.Buffer
Buffer是一个抽象类,有除boolean外的XxxBuffer类,通过static XxxBuffer allocate(int capacity):创建一个容量为capacity的XxxBuffer对象。
ByteBuffer类还有一个子类MappedByteBuffer,用于表示Channel将磁盘文件的部分或全部内容映射到内存中后得到的结果,通常由Channel的map()方法返回。
0 <= 标记 <= 位置 <= 限制 <= 容量
Buffer中三个重要概念:
容量capacity,表示Buffer的最大数据容量,创建后不能更改;
界限limit,第一个不应该被读出或者写入的缓冲区位置索引;
位置position,用于指明下一个可以被读出或者写入的缓冲区位置索引。
- flip()方法将limit设置为position所在的位置,并将position设为0,为输出数据做好了准备;
- 即将Buffer中没有数据的存储空间封印起来
- clear()将position设置为0,将limit设为capacity,为装入数据做好准备
put()和get()方法,用于向Buffer中放入和取出数据
static ByteBuffer allocateDirect(int capacity)
分配新的直接字节缓冲区
直接Buffer,只有ByteBuffer才有;创建成本高,但读取效率高。
java.nio.channels.FileChannel
程序不能直接访问Channel中的数据,包括读取、写入都不行,Channel只能与Buffer进行交互。Channel是通过节点流进行的getChannel方法来返回对应的Channel。
Channel最常用的3类方法
map(),将Channel对应的部分或全部数据,映射到ByteBuffer
read()读取Buffer数据
write()将数据写入Buffer中
static class FileChannel.MapMode : 枚举(只读、读写)
字符集和Charset
java.nio.charset.Charset
Encode 编码 —>字节
Decode 解码 —>字符
字符序列<——————>字节序列
Java默认使用Unicode字符集
Charset字符集
GBK 简体中文
BIG5繁体中文
java.nio.charset.CharsetDecoder
decode()方法 ByteBuffer—>CharBuffer
java.nio.charset.CharsetEncoder
encode()方法 CharBuffer或String—>ByteBuffer
Java 7 新增了一个StandardCharsets类,包含了最常用字符集对应的Charset静态对象
**也可以直接用Charset对象直接调用encode,decode方法进行编码解码(无需生成CharsetDecoder和CharsetEncoder对象)
文件锁FileLock
lock() 阻塞
tryLock() 非阻塞
FileLock来支持文件锁定功能,FileChannel提供lock(),tryLock()方法可以获得文件锁对象,lock如果无法得到文件锁将阻塞,而tryLock则返回null。
release()方法释放文件锁
15.10 NIO.2
Java7开始