使用输入机制 允许程序记录运行时读取外部数据,(磁盘,关盘等存储介质),用户输入
使用输出允许程序记录运行状态,将程序数据输出到磁盘、关盘等介质
java io流使用了一种装饰设计模式,它将IO流分成底层字节流和上层处理流,其中节点流和底层物理存储节点直接关联,程序在将处理流包装成处理流,从而包装程序使用输入输出流来访问不同节点
15.1.1 访问文件和目录
File类可以使用文件路径字符串来创建File实例,该路径可是相对路径也可以是绝对路径
UNIX/Linux/BSD等系统上,如果路径是( / ) 则表明是绝对路径
1. 输入流和输出流(以内存来划分, 以服务器来划分)
输入流 :只能从中读取数据不能写入数据
输出流 :只能想其中写入数据,而不能读取数据
java的输入流主要由InputStream和Reader作为基类
输出流主要是用OutputStream和 Writer作为基类
2. 字节流和字符流
字符流主要有InputStream和OutputStream作为基类
字符流主要是Reader 和 Writer 作为基类
3. 节点流和处理流
按照流的角色来分,可以分为节点流和处理流
可以从一个特定的IO设备(磁盘、网络)读/写数据的流,称为节点流(低级流)
处理流则用用对一个已经存在的流进行连接或者封装。通过封装后的流来实现数据的读写功能(高级流)
java把所有的有序数据抽象成流模型,简化了输入输出处理,理解了流的概念模型也就了解两类javaIO
JAVA 的处理流模型则体现java输入/输出流的灵活,处理流的功能主要体现在以下两个方面
1.功能的提高:主要增加缓冲的方式来提高输入/输出的效率
2.操作的便捷:处理流可能提供了一系列的便捷方法来一次输入/输出大量流
通过处理流java无需理会输入输出的是磁盘还是网络
InputStream 和Reader是所有输入流的抽象基类,本身不能创建实例来执行输入,但他们分别有一个读取文件的输入流 FileInputStream 和FileReader
使用java的IO流执行输出是,不要忘记关闭输出流
1.关闭输出流可以保证流的物理资源被关闭
2.可能还可以将输出流缓存区中的数据flush到物理节点里
处理流可以隐藏底层设备撒上节点流的差距,并对外提供更加方便的输入/输出方法,让程序员只需关心高级流的操作
使用处理流是的典型思路是 使用处理流来包装节点流,程序通过处理流来执行输入输出的功能,让节点流与底层的I/0设备,文件交换
只要流的构造器参数不是一个物理节点而是一个已将存在的流,那么这种流就一定是处理流
PrintStream类的输出功能非常强大,通常如果需要输出文本内容,都应该讲输出流包装成PrintStream
使用处理流包装底层的节点流后,只要关闭最上层的处理流即可,系统会自动关闭节点流
管道流主要是用来实现线程之间的通信问题
缓存流 可以提高输入输出效率,增加缓冲流功能后需要使用flush()才可以将缓冲区的内容写入实际的物理节点
输入输出流体系中还提供了两个转换流,这两个转换流用于实现将字节流转换成字符流
InputStreamReader将字节输入流转换为字符输入流,
OutputStreamWriter 将字节输出流转换成字符输出流
java 使用 System.in 代表标准输入,这个标准输入流式 InputStream类的实例,键盘输入的内容都是文本内容可以使用 InputStreamReader将其转换成字符流,普通的Reader读取输入内容时依然不太方便,可以将Reader再次包装成BufferedReader 利用BufferedReader可以一次读取一行内容
经常把读取文本内容的输入流包装成BufferedReader,用来方便的读取输入流的文本内容
PushbackInputStream 和PushbackReader 流 这两个推回输入流都带有一个推回缓冲区,当程序调用者两个流的 unread()方法时,系统会把指定数组的内容推回到该缓冲区 ,而推回输入流每次调用read()方法总是先从缓冲区读取,只有完全读取,但read()还没装满时,才会从原输入流读取
默认缓冲区的长度为1
不在使用键盘作为输入,不在使用屏幕作为输入
System类里提供了三个重定向方法
使用Runtime对象的exec()方法可以运行平台上的其他程序,该方法产生一个Process对象, Process对象代表由该java程序启动的java的子进程
子进程读取程序中的数据 使用的是输出流
RandomAccessFile是java输入输出体系中最丰富的文件内容访问类,它提供了众多方法来访问文件内容,也可以向文件输出数据,与普通的输入输出流不同的是,RandomAccessFile支持随机访问,程序可以直接跳到文件任意地方来读
如果要想文件后追加内容可以使用RandomAccessFile
RandomAccessFile只能读取文件,不能读取其他的io节点
RandomAccessFile对象包含了一个记录指针,用于标记当前读写的位置。
RandomAccessFile不能像文件指定位置插入内容,如果需要插入则可以通过把插入点后的内容输入缓冲区,插入完毕后再将缓冲区内的内容写到文件后面
多线程断点的网络下载工具就可以通过RandomAccessFile类来实现
对象系列化的目标是将对象保存在磁盘中,或允许在网络中直接传输对象。对象系列化机制允许吧内存中的java对象转换成平台无关的二进制流 通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流,都可以将这种二进制流恢复成原来的java对象
系列化机制允许将实现系列化的java对象转换为字节系列,这些字节系列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象,系列化机制使得对象可以脱离程序的运行而独立存在,
所有可能在网络上传播的对象的类都应该是可系列化的
通过实现Serializable接口来实现对象系列化,程序可以通过如下两个步骤来实现对象系列化
1.创建一个ObjectOutputStream 这个输出流是一个处理流
2.调用 ObjectOutputStream对象的writeObject()方法输出可系列化的对象
反系列化步骤
1.创建一个ObjectInputStream输入流,这个输入流是一个处理流
2.调用ObjectInputStream对象的readObject() 方法读取流中的对象
反序列化读取的仅仅是java对象的数据,不是java类,因此采用反序列化恢复java对象时,必须提供对象所属于的class对象,
反系列化机制无需通过构造器来初始化java对象
如果使用系列化机制想文件中写入多个java对象,使用反系列机制恢复对象时,必须按实际写的顺序读取
当一个可系列化对象有多个父类时,这些父类要么有无参数的构造器要么也是可系列化的
如果系列化类引用的对象没有系列化,那么该类也无法系列化
程序系列化算法 如下
1.所有保存在磁盘中的对象都有一个系列化编号
2.当程序试图系列化一个对象时,程序将先检查对象是否已经被系列化过,如果该对象从未被系列化过,系统才会将对象转化为字节系列并输出
3.如果对象已经系列化过,程序将只是直接输出一个系列化编号
新IO采用内存映射文件的方式来处理输入输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样访问文件了,模拟了操作系统上的虚拟内存的概念,
Channel(通道)和Buffer(缓存) 是NIO的两个核心对象,Channel是对传统的输入输出系统的模拟,在NIO系统中国所有数据都需要通过通道传输,与传统的输入输出系统模拟,Channel与传统的InputStream 和OutputStream(面向流输的处理)最大的区别是提供了一个map()方法,通过该方法可以直接将一块数据映射到内存中(面向块的处理)
Buffer可以理解为一个容器,本质是一个数组,发送到Channel中的所有对象都必须首先放到Buffer中,从Channel读取数据也必须先放到Buffer中
NIO还提供了一个用于将Unicode字符串映射成字节系列以及逆映射操作的Charset类,也提供了用于支持非阻塞式输入输出的Selector 类
Buffer中有三个重要的概念 容量 界限 和位置
Buffer中有两个重要方法 flip() 将limit设置为position所在位置,并将position设为0 为从Buffer中取出数据做好准备
clear() 将position置为0,将limit置为capacity
put 和get来访问Buffer中的数据时,分为相对和绝对两种
相对 从buffer中的当前position出开始读写,然后将position的值按处理元素的个数增加
绝对 直接根据索引执行Buffer中读取或写入数据 不影响position的值
通过allocate() 方法创建的Buffer对象是普通的Buffer
ByteBuffer还提供了一个allocateDirect()方法来创建直接Buffer
直接创建Buffer的成本很高,所以只适用于长生存期的buffer
Channel类似于传统的流对象,但与传统的流对象有两个主要区别
1. Channel 可以直接将指定文件的部分或者全部直接映射成Buffer
2. 程序不能直接访问Channel中的数据,只能通过Buffer进行交互
所有的Channel都不应该直接通过构造器直接创建,而是通过传统的节点InputStream...的getChannel()方法来获取,
Channel中最常用的三类方法是map() read() write() 其中map()方法用于将Channel对应
的部分或者全部数据映射成ByteBuffer read() write() 用来读取数据
newDecoder() 代表该Charset 解码器
newEncoder() 代表该Charset 编码器
文件锁可以有效的阻止多个进程并发修改同一个文件
在NIO中java提供了FileLock来支持文件锁定功能,在FileChannel中提供的lock()/tryLock()方法可以获得文件锁FileLock对象
lock() 当试图锁定某一个文件时,如果无法得到文件锁,程序将一直阻塞,
而 try lock() 是尝试锁定文件,它将直接返回而不是阻塞,如果获得文件锁,则返回文件锁,否则返回null
文件锁虽然可以用于控制并发访问,但是对于高并发访问的情景,还是推荐使用数据库来保存程序信息
文件锁还需要指出的几点
1. 在某一些平台上,文件锁仅是建议性的(即使一个文件不能获得文件锁,它也可以读写)
2. 在某一些平台上,不能同步的锁定一个文件并把它映射到内存中
3. 文件锁是由java虚拟机所持有的,如果两个java程序使用同一个java虚拟机允许,则他们不能对同时同一个文件进行加锁
4. 在某一些平台上关闭FileChannel时,会释放jvm 在该文件中的所有锁
java 7对原有的NIO进行了重大的改进,主要有
1. 提供了全面的文件IO和文件系统访问支持
2. 基于异步的Channel 的IO
在以前的java版本中,如果要遍历指定目录下的所有文件和子类目录,只能使用递归进行遍历,但这种方式不仅复杂,而且性能也不高
Files 工具类提供了更方便的方法遍历文件和子目录
在java之前的版本如果程序需要监视文件的变化,则可以考虑启动一条后台线程,这条后台线程每隔一段时间去遍历一次指定目录的文件,如果和上次遍历结果不一样就认为发生了变化,但这种方式不仅十分繁琐,而且性能也不高