File类
- File类能去新建删除重命名目录和文件,不能访问文件内容本身
- 默认情况以用户的工作路径来解释相对路径,就是运行jvm时所在的路径
- 使用文件路径字符串来创建File实例
- File类提供了操作文件和目录的方法,用File对象来调用,访问文件名相关方法,文件检测,获取常规文件信息,文件和目录操作的相关方法
- 使用相对路径的FIle对象获取父路径时可能引起错误,因为该方法返回将File对象对应的目录名文件名里最后一个子目录名子文件名删除后的结果
- windows路径字符串分隔符可以用\也可以用/
- File类的list()方法接受一个FilenameFilter参数,这个函数式接口包含一个accept(File dir, String name)方法用于对指定File的所有文件或目录进行迭代
理解IO流
- Java把不同的输入输出源抽象表述为流,stream是从起源到接收的有序数据
- 程序运行所在内存的角度分输入流和输出流,操作的数据单元分字节流字符流,流的角色分节点流处理流
- 可以从一个特定的IO设备读写数据的流叫节点流,又叫低级流,程序直接连接到实际的数据源,和实际的节点连接
- 处理流是对一个已存在的流进行连接或封装,通过封装后的流来实现数据读写,又叫高级流
- 处理流的好处只要使用相同的处理流,程序就可以用相同的代码访问不同的数据源,随着处理流所包装的节点流的变化,程序实际所访问的数据源也发生变化
- 处理流包装节点流是一种典型的装饰器模式,可以消除不同节点流实现差异,也可以方便方法输入输出数据,程序不用理会输入输出节点的种类,只需要包装成处理流就可以使用相同的代码来读写不同输入输出设备的数据
- 处理流性能提高,增加了缓冲的方式来提高输入输出效率,提供了一系列便捷方式一次输入输出批量数据,而不是一个字节或字符
- 四个抽象基类InputStream、Reader、OutputStream、Writer
- 输入流使用隐式指针表示开始读取的地方,输出流隐式记录指针表示即将放入的位置,都会随着读写移动
字节流和字符流
- InputStrean Reader是所有输入流的抽象基类,本身不能创建实例,作为所有输入流的模板,提供了多种read()方法
- 用于读取文件的节点流FileInputStream FileReader
- 程序打开的IO资源不属于内存里的资源需要显式关闭,可以放在try()里面自动关闭
- InputStrean Reader还提供了操作记录指针的方法 mark markSupported reset skip
- OutputStream Writer和上面的相似
- 关闭输出流除了资源回收还会将缓冲区的数据flush到物理节点中
- Windows平台换行符是\r\n Unix是\n
输入输出流体系
- 处理流隐藏底层设备上节点流的差异,提供更方便的输入输出方法
- 处理流的构造参数不是一个物理节点而是一个已经存在的流,而所有节点流都是直接以物理节点作为构造器参数的
- 通常需要输出文本内容都应该将输出流包装成PrintStream后进行输出
- 关闭最上层的处理流时系统会自动关闭被其包装的节点流
-
输入输出流体系
- 通常字节流功能比字符流功能强,但是处理文本内容会不方便
- 输入输出为文本内容时应该考虑字符流,二进制内容应该考虑字节流
- 字符流可以使用字符串作为物理节点,实现从字符串读取内容,或将内容写入字符串,StringWriter使用StringBuffer作为输出节点,因为String是不可变的字符串对象
- 转换流实现了将字节流转换成字符流
- 输入文本时,可将InputStrean类的实例,可以转换成字符输入流,再包装成处理流,方便使用
- 推回输入流PushbackInoutStream PushbackReader提供了unread方法实现讲读取的内容推回到推回缓冲区,read是先读取推回缓冲区中的内容
- 创建PushbackInoutStream PushbackReader时需要指定推回缓冲区的大小,默认为1,如果推回内容大于指定大小,会出现异常
重定向标准输入输出
- System类中提供了三个重定向标准输入输出的方法 setErr setIn setOut
System.setOut(ps);
System.out.println("...");//会输出到ps指定的地方
Java虚拟机读写其他进程的数据
- Runtime对象的exec()方法获取Process对象,此对象代表由该Java程序启动的子进程
- Process类提供了三个方法 getErrorStream getInputStream getOutoutStrean 获取子进程的节点流,这里的输入输出是对于程序而言的
RandomAccessFile
- 支持随机访问,可以直接跳转到任何地方读写数据
- 如果程序需要在已存在的文件后追加内容,应该使用RandomAccessFile,但是仍然不能向指定位置插入内容,同样会覆盖原有内容
- 但是只能读写文件,不能读写其他IO节点
- RandonAccessFile包含两个方法操作记录指针,getFilePoint seek ,也包含同InputStream和OutputStrean的read和write方法,另外提供readXxx WriteXxx方法来输入输出
- 两个构造器,一个使用文件名String参数,一个使用File参数,还需要指定访问模式mode参数,r rw rws rwd
对象序列化
- 对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象
- 对象序列化机制允许将内存中的对象转换成与平台无关的二进制流,使得对象可以脱离程序的运行而独立存在
- 序列化是将一个Java对象写入IO流中,反序列化是从IO流中恢复对象
- 序列化对象必须要让它的类是可序列化的,必须实现两个接口之一,Serializable Externalizable
- Serializable是一个标记接口,不需要实现任何方法,很多类都实现了此接口,建议创建的每个JavaBean类都实现了Serializable
- 序列化步骤
- 创建ObjectOutputStream,是个处理流
- 调用writeObject方法输出可序列化对象
- 反序列化步骤
- 创建ObjectInputStream输入流
- 调用readObject方法读取流中对象,返回Object类型的对象,如果知道类型,可以强制转换成真实类型
- 反序列化读取的是对象,所以还需要提供对象所属的class文件,否则会出现异常
- 反序列化机制无须通过构造器来初始化Java对象
- 当在一个文件序列化了多个对象,反序列化读取时应该按照实际写入的顺序读取
- 可序列化类的父类必须要么有无参数的构造器要么也是可序列化的,否则反序列化时会抛出异常
- 如果父类不可序列化但有无参数构造器,则父类中定义的成员变量值不会序列化到二进制流中
- 可序列化的类中引用类型变量的引用类也必须是可序列化的,递归序列化
- 特殊的序列化算法
- 所有保存到磁盘中的对象都有一个序列化编号
- 程序企图序列化一个对象时会检查该对象是否已经被序列化过,只有在本次虚拟机中没有被序列化过,才会序列化输出
- 已经序列化过的,程序只是输出一个序列化编号,而不是重新序列化该对象
- 当程序序列化一个可变对象时,序列化过一次之后即使改变了实例变量的值,也不会输出序列化
- transient关键字只能修饰实例变量,使序列化时不理会该实例变量,反序列化恢复时不能获取到该实例变量的值
-
实现自定义序列化
自定义重写readObject()方法哪些实例变量需要序列化,怎样序列化,重写writeObject()需要反序列化哪些实例变量和怎样反序列化
- readObject()和writeObject()两个方法对应,重写时应该注意相反处理,操作实例变量的顺序也应该一致,以便正确恢复
- 默认情况readObject()调用out.defaultWriteObject来各保存实例变量,writeObject()调用in.defaultReadObject来恢复对象的非瞬态实例变量
- 当序列化流不完整时,readObjectNoData()可以用来正确的初始化反序列化的对象
-
重写writeReplace()可以在序列该对象时将该对象替换成其他对象,由序列化机制调用,可以拥有访问权限,可以被子类继承
- 序列化机制保证在序列化某对象之前先调用writeReplace(),如果返回另一个对象,则转为序列化另一个对象
- 会先调用writeReplace(),如果返回另一个对象后会调用那个对象的writeReplace()直到不再返回另一个对象,最后调用writeObject()
- readResolve()接着readObject()之后调用,该返回值会代替原反序列化的对象,原来的对象被立即丢弃,在单例类和早期的枚举类中有效,都应该提供readResolve()
- 反序列化可以用来克隆对象,不需要构造器的新建对象
- readResolve()同样可以使用任意访问权限控制符,对于不是final类的重写readResolve()最好用private修饰该方法
- 实现Externalizable接口的自定义序列化机制完全由程序员决定存储和恢复对象数据
- Externalizable接口提供了readExternal writeExternal,强制自定义序列化,和readObject() writeObject类似
- 实现Externalizable接口的类的对象序列化反序列化操作同Serializable,一样调用ObjectOutputStream的writeObject()和ObjectInputStream的readObject()
- 实现Externalizable序列化类必须提供public的无参数构造器
- Externalizable性能略好,但是编程复杂度增加
- 对象序列化注意点
- 对象的类名和实例变量会被序列化,方法、类变量、transient修饰的实例变量不会被序列化
- 在实例变量前加static可以实现和transient一样的效果,但是不能这样用
- 必须保证可序列化的类的实例变量的类型也是可序列化的
- 反序列化对象时必须有序列化对象的class文件
- 反序列化按实际写入顺序读取
- 序列化机制允许为序列化类提供一个private static final 的serialVersionUID值,标识类的序列化版本
- 最好在每个要序列化的类中加入serialVersionUID,数值自己定义
- 不显示定义的话,JVM会根据类的相关信息计算,修改后会导致与之前计算结果不一致,从而造成反序列化因类版本不兼容而失败。
- 不同JVM计算方法也可能不同,,不利于程序在不同JVM上移植,导致反序列化失败
- 对象流中的对象比新类中包含更多的实例变量,多出的实例变量值会被忽略,序列化版本兼容
- 如果新类包含更多的实例变量,序列化版本也兼容,但反序列化得到的新对象的值都是null或0
NIO
NIO概述
- 面相流的输入输出系统一次只能处理一个字节,效率不高
- NIO采用内存映射文件的方式处理输入输出,将文件或文件的一段区域映射到内存中,像访问内存一样来访问文件,提高了效率
- Channel Buffer是NIO中两个核心对象
- Channel提供map()方法,直接将一块数据映射到内存中
- 发送到Channel的数据必须首先放到Buffer中,Buffer可以去读取Channel的数据,也可以使用Channel的map()将数据映射成Buffer
- NIO还有Unicode字符串映射成字节序列,Charset逆映射,支持非阻塞式输入输出的Selector
Buffer
- Buffer是一个抽象类,Buffer常用子类ByteBuffer和CharBuffer,XxxBuffer.allocate(capacity)返回对象
- ByteBuffer有一个子类MappedByteBuffer由Channel的map()方法返回
- Buffer三个概念
- capacity,缓冲区容量,创建后不能改变
- limit,界限,第一个不该被读写的索引
- position,位置,下一个被读写的位置索引
- Buffer同样支持mark,同IO流中的mark
- 0≤mark≤position≤limit≤capacity
- 默认开始时,position为0,limit为capacity
- flip()方法将limit设置为position的位置,并将position设置为0
- clear()方法设置position为0,limit为capacity,对Buffer执行clear()后,对象的数据依然存在
- Buffer常用方法capacity hasRemaining limit mark position remaining reset rewind
- 访问数据的常用方法 put() get(),分相对和绝对,相对是从position处读写数据,绝对是根据索引读写数据
- ByteBuffer提供allocateDirect()来创建直接Buffer,创建成本较高但读取效率好
Channel
- Channel可以将文件的部分或全部映射成Buffer
- 程序不能直接访问Channel,只能通过Buffer来交互
- Channel应该用传统节点的getChannel()来返回对应的Channel来创建
- Channel常用方法,read() write() map()
- MappedByteBuffer map(FileChannel.MapMode mode, long position, long size);第一个参数有只读、读写等模式
- RandomAccessFile的getChannel()返回的是只读还是读写取决于RandomAccessFile打开文件的模式
- 也可以不一次性map,使用Buffer分批读取数据,结合flip和clear也可以做到
字符集和Charset
- Charset类提供了字节序列和字符序列之间的转换关系,是不可变类
- forname()创建字符集对象,调用newDecoder()或newEncoder()返回解码器编码器对象,再调用decode()或encode()进行字节序列和字符序列的转换
- 便捷转换方法,使用Charset类的decode encode方法来转换
- String类的getBytes()也将字符串转换为字节序列
文件锁
- 文件锁避免多个进程并发修改同一个文件
- FileChannel提供lock() tryLock()获取文件锁FileLock对象
- lock()获取不到文件锁时会使程序一直阻塞,tryLock()将直接返回null而不会阻塞,直接使用lock() tryLock()获取排他锁
- Lock() tryLock()可以锁定文件的部分内容,参数shared表示共享锁,允许多个进程读取文件,但阻止其他进程获取该文件的排他锁
- release()释放文件锁
- 对于高并发访问情形,应该用数据库保存程序信息,而不是使用文件
- 在某些平台,文件锁不是强制性的,一个程序不能获得文件锁,也能对文件进行读写
- 某些平台,不能同步锁定一个文件并把它映射到内存中
- 文件锁是JVM所持有的,同一JVM的两个运行程序不能对同一个文件进行加锁
- 某些平台上关闭FileChannel时会释放次JVM的所有锁
NIO.2
- Java7对原有的NIO提供了全面的文件IO和文件系统的访问支持,提供了基于异步Channel的IO
- NIO.2引入了一个Path接口,提供了Files和Paths工具类
- Files提供大量便捷的静态工具方法操作文件,是一个高度封装的工具类,完成文件复制读取文件内容写入文件内容,Java8允许使用StreamAPI操作文件和目录内容
- Paths提供两个返回Path的静态工厂方法
- Files提供walkFileTree()遍历文件和子目录,需要FileVisitor文件访问器
-
遍历时会触发FileVisitor的方法,并会返回一个枚举类值表示后续行为
- 可以继承SimpleFileVisitor(FileVisitor的实现类)来实现自己的文件访问器,再根据需要重写指定方法
- Path类提供register()方法用WatchService对象监听指定目录下的指定类型事件的文件变化
- WatchService提供获取WatchKey的方法,poll() take(),如果程序需要一直监控,应该选择take方法,如果指定监控时间,应该选poll方法
- Java7在java.nio.file.attribute包下提供读取修改文件属性的方法
- 这些工具类主要分为XxxAttributeView文件属性视图,XxxAttributes文件属性集合,一般通过视图对象获取,方法详见API