Java学习(九)
Java I/O流
字节流:8bit,InputStream
、OutputStream
为抽象基类
字符流:16bit,Reader
、Writer
做抽象基类
Java同样有着C/C++文件位置指针的类似概念
通常的,打开一个文件之类的、从/向特定IO设备(磁盘、网络等)读写的流,称为节点流。
处理流:对已存在的流做封装,使用起来更简便
-
抽象基类输入流
InputStream
int read()
:从输入流读取单个字节,返回所读取的字节数据int read(byte[] b)
:读取b.length
个字节,并存储在数组b
中,返回实际读取的字节数int read(byte[] b, int off, int len)
:从输入流读取len
个字节,并从b[off]
位置开始存储在数组b中,返回实际读取的字节个数当返回为
-1
时,表示输入流读取完毕Reader
int read()
:从输入流读取单个字符,返回所读取的字符数据int read(char[] cbuf)
:类似上文int read(char[] cbuf, int off, int len)
:类似上文 -
抽象基类输出流
OutputStream和Writer公共的方法
void write(int c)
:将指定的字节/字符输出到输出流中void write(byte[]/char[] buf)
:将字节数组/字符数组中的数据输出到指定输出流void write(byte[]/char[] buf, int off, int len)
:将字节数组/字符数组buf
中从off
位置开始,长度为len
的字节/字符输出到输出流中Writer因为操作单位是字符,所以有额外的两个操作
String
类对象的方法void write(String str)
:将str输出到指定输出流中void write(String str, int off, int len)
:将str从off位置开始,长度为len的字符输出到指定输出流中 -
节点流
举例:
字节输入流:
FileInputStream
、ByteArrayInputStream
、PipedInputStream
字节输出流:上述改成
Output
字符输入流:
FileReader
、CharArrayReader
、PipedReader
、StringReader
字符输出流:上述改成
Writer
-
处理流
使用方法:直接采用物理IO节点作为构造器参数。
示例:
public class PrintStreamTest { public static void main(String[] args) { try{ FileOutputStream fos = new FileOutputStream("test.txt"); PrintStream ps = new PrintStream(fos); { ps.println("str"); ps.println(new PrintStreamTest()); } } catch (IOException ioe) { ioe.printStackTrace(); } } }
-
缓冲流
增加缓冲功能,当调用
flush()
时,才将缓冲区内容写入实际物理节点 -
转换流
只有字节转字符,没有字符转字节。
比如,键盘输入时,由于都是字符数据,所以可以将
System.in
(InputStream
类的实例)转换成InputStreamReader
字符输入流 -
推回输入流
PushbackInputStream
和PushbackReader
void unread(byte[]/char[] buf)
:将一个字节/字符数组的内容推回到推回缓冲区内,使得刚才读取的内容可重复读取void unread(byte[]/char[] b, int off, int lne)
:将一个字节/字符数组里从off
开始,长度为len
字节/字符的内容推回到推回缓冲区内void unread(int b)
:将一个字节/字符推回到推回缓冲区内推回流调用
read()
方法时,总是先从推回缓冲区内读取。可以利用这个特性每次只读取目标字符串之前的内容,即将一个字符串作为读取的界限
-
重定向
System类提供了三种重定向的方法
static void setErr(PrintStream err)
static void setIn(InputStream in)
static void setOut(PrintStream out)
-
JVM读取其他进程数据
Process类提供了三种方法
InputStream getErrorStream()
InputStream getInputStream()
OutputStream getOutputStream()
输入输出流容易混淆,应该站在主程序的角度看问题,如果主程序想要往子进程写,则应该获取子进程的输出流,否则获取输入流。
Extra:IDEA下,启动java子进程,需要用
-classpath
选项指定具体.class
文件的路径,否则找不到程序,而且PrintStream是有编码问题的,只有输入输出为英文时才没问题,否则需要做一定的修改。 -
随机读写文件的RandomAccessFile
RandomAccessFile
类能够自由访问文件的任意位置,可以自由定义文件记录指针。但仅能访问(读写)文件,不能读写其他IO节点。
-
对象序列化
目的:将对象保存到磁盘中,使对象脱离程序独立存在,常用于Web端。
把Java对象转化成平台无关的二进制流(序列化),可通过这个二进制流恢复成原来的Java对象(反序列化)。
实现:对象的序列化必须实现
Serializable
或Externalizable
接口-
实现
Serializable
让目标类实现该标记接口即可,无需实现任何方法,如:
class Person implements java.io.Serializable /*只需要在此处实现标记接口即可*/ { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } ...... } public class WriteObject { public static void main(String[] args) { try( ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("object.txt"))) { Person per = new Person("XXX", 500); oos.writeObject(per); }/*这样就将序列化的Person对象写入IO流中,完成序列化*/ catch (IOException ex) { ex.printStackTrace(); } try( ObjectInputStream ois = new ObjectInputStream( new FileInputStream("object.txt"))) { Person p = (Person)ois.readObject();/*默认是Objcet类对象, 强制类型转换可以得到实际的对象*/ }/*这样就将序列化的Person对象读出IO流,完成反序列化*/ catch (IOException ex) { ex.printStackTrace(); } } }
和普通文件一样,如果写入了多个对象,那么反序列化也必须按照顺序还原。
几条原则:
- 一个可序列化类有多个父类时,这些父类必须也是可序列化的或者有无参数的构造器,否则反序列化会抛出
InvalidClassException
异常。 - 如果父类仅仅是带有无参数构造器,那么父类定义的成员变量值不会序列化到二进制流中
- 如果可序列化的目标类的成员变量含有其他类型(非String类、非基本类型)的引用类型,那么这个引用类也必须是可序列化的,因为有着递归序列化的机制
当目标类实例的成员变量引用了相同的对象时,序列化会改为输出序列化编号而不是序列化对象。
但这引入了问题:如果反序列化先还原了目标对象,并且修改了目标对象的值,后续反序列化还原出来的含有序列化编号的对象的参照物仍然是原来文件内的序列化对象。
自定义序列化:
-
transient
关键字,只能修饰实例变量,使得对应变量不序列化 -
通过重写如下方法可以自定义序列化:
private void writeObject(java.io.ObjectOutputStream out)throws IOException
:自主决定序列化的内容,默认调用out.defaultWriteObject
private void readObject(java.io.ObjectInputStream in)throws IOException, ClassNotFoundException
:自主决定反序列化内容,默认调用in.defaultReadObject
private void readObjectNodata()throws ObjectStreamException
:反序列化对象版本不同时调用(比如新类比对象流中的实例变量少或多,可以不更新serialVersionUID
),得到的值要么是null
要么是0
(少)或者忽略掉(多),java为序列化类提供一个private static final serialVersionUID
值,用于标识序列化版本,这个值可以自己改动(默认由JVM提供),以保证正确地反序列化
单例类、枚举类的反序列化
应该提供
readResolve()
方法,保证反序列化对象正常,因为反序列化恢复Java对象时,并不需要构造器,而且该方法应该尽量写在最后的子类,否则会被还原为父类对象。 - 一个可序列化类有多个父类时,这些父类必须也是可序列化的或者有无参数的构造器,否则反序列化会抛出
-
Externalizable接口
该方式完全由程序员决定存储和恢复对象数据,且在声明了实现该接口后,还必须实现
void readExternal(ObjectInput in)
和void writeExternal(ObjectOutput out)
这两个方法 -
不会被序列化的内容
方法、类变量、
transient
实例变量都不会被序列化 -
反序列化对象时必须有序列化对象的
.class
文件
-
-
NIO
NIO即新IO,采用内存映射文件来处理IO(模拟OS的方式),其核心类是
Channel
和Buffer
类。Channel
是对传统IO的模拟,所有的数据都要通过Channel
传输,Channel与传统InputStream
、OutputStream
的最大区别是提供了一个map()
方法,来将内存映射文件,或者说用Buffer
映射文件。发送到
Channel
和从Channel
中读取的数据都必须首先放到Buffer
中。-
Buffer类
该类是个抽象类。
可通过
static XxxBuffer allocate(int capacity)
来得到一个容量为Capacity
的XxxBuffer
对象。Buffer的三大重要概念:
- capacity:表示缓冲区的容量最大值,一经创建不能改变
- limit:第一个不能读出或写入缓冲区的位置索引。从limit索引开始的数据不可访问。
- position:下一个可以被访问的缓冲区索引,初始值为
0
- mark:在position之前的一个索引,可以直接将position定位到mark处
重要的方法:
void flip();
:将limit设为position,并让position置为0,为输出数据做好准备。void clear();
:将position置为0,并将limit设为capacity,为读取数据做准备。所有子类提供的读写方法:
void put();
:向Buffer写入数据void get();
向Buffer读入数据这两个方法都有绝对和相对读写,相对:从position处开始读写,然后更新position;绝对:根据索引向Buffer读写数据,不会改变position的值,此时需要给出索引值作为形参
Extra:只有
ByteBuffer
提供了allocateDirect()
方法创建直接Buffer,这种Buffer创建成本高,但执行效率高,适合长生存期。 -
Channel
两个特性:
Channel
可以直接将指定文件的部分或全部直接映射成Buffer
- 只能通过
Buffer
来访问Channel
只能通过传统的流节点
InputStream
、OutputStream
的getChannel()
方法来获取对应的Channel
,RandomAccessFile
也可以通过此方法得到Channel
,但是读写取决于其打开方式。常用的方法:
map()
:将Channel对应的部分或全部数据映射为MappedByteBuffer
read()
:从Channel读,向Buffer写write()
:向Channel写,从Buffer读
-
NIO.2
提供了
Files
、Paths
两个工具类
-
-
字符集
Java默认使用Unicode字符集,可以使用Charset来处理字节序列和字符序列(字符串)之间的转换关系
-
文件锁
FileChannel
提供的lock()
和tryLock()
可以得到FileLock
对象。区别:
-
lock()
:当试图锁定某个文件时,如果得不到文件锁,则程序阻塞。 -
tryLock()
:尝试锁定文件,返回文件锁或null
可以使用如下内容锁定文件的部分内容:
-
lock(long position, long size, boolean shared)
-
tryLock(long position, long size, boolean shared)
shared
为true时,表示为共享锁,否则为排他锁,如果没有形参则为默认情况,是排他锁。
-