Java 输入与输出
流(IO)
“流”(Stream)是Java对输入输出源的一种抽象,表示了一种逐一读取或写出的处理方式。
- 输入流、输出流:通常以程序运行所在内存来区分
- 字节流、字符流:所操作的数据单元不同
- 节点流、处理流:直接关联IO设备的流称为节点流,而在节点流的基础上封装各种各样的处理方式后的流称为处理流,代码中表现为以节点流为构造器参数。处理流使得程序可以使用相同的代码来处理不同的数据源。
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
抽象基类 | Filter~ | Filter~ | Filter~ | Filter~ |
访问文件 | File~ | File~ | File~ | File~ |
访问数组 | ByteArray~ | ByteArray~ | CharArray~ | CharArray~ |
访问管道 | Piped~ | Piped~ | Piped~ | Piped~ |
访问字符串 | String~ | String~ | ||
缓冲流 | Buffered~ | Buffered~ | Buffered~ | Buffered~ |
转换流 | InputStream~ | OutputStream~ | ||
对象流 | Object~ | Object~ | ||
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushBack~ | |||
特殊流 | Data~ | Data~ |
基类类的通用方法
-
InputStream/Reader
-
int read()
-
int read(byte[]/char[] buf)
-
int read(byte[]/char[] buf, int off, int len)
InputStream is = ... length = ... byte[] buf = new byte[length]; for (int hasRead = 0; (hasRead = is.read(buf)) > 0; ) { <处理 buf 的 从 0 开始 hasRead 长度的数据> }
-
void mark(int readlimit)
-
void reset()
-
long skip(long n)
-
-
OutputStream/Writer
void write(int c)
void write(byte[]/char[] buf)
void write(byte[]/char[] buf, int off, int len)
void write(String str)
限于 Writervoid write(String str, int off, int len)
限于 Writer
常用的节点流
-
访问字符串:String~
-
访问字符字节数组:ByteArray~/CharArray~
-
访问文件对象:File~
File 对象
- 构造函数
File(String pathname)
File(String parent, String child)
File(File parent, String child)
File(URI uri)
根据路径字符串创建File对象,可以包含.
和..
表示当前和上级,也可以传入相对路径,参考是系统属性user.dir
(jvm工作路径)。不要以空字符""
来创建对象
- 常用函数
-
String getName()
-
String getPath()
-
String getAbsolutePath()
orFile getAbsoluteFile()
-
String getCanonicalPath()
orFile getCanonicalFile()
Canonical 具有唯一性,是绝对路径,且消除了路径中的
.
和..
-
String getParent()
orFile getParentFile()
-
boolean exists()
-
boolean canWrite()
-
boolean canRead()
-
boolean isFile()
-
boolean isDirectory()
-
boolean isAbsolute()
-
long length()
-
long lastModified()
-
boolean createNewFile()
-
boolean mkdir()
-
boolean delete()
-
String[] list()
orString[] list(FilenameFilter filter)
FilenameFilter 接口包含一个
boolean accept(File dir, String name)
-
File[] listFiles()
orFile[] listFiles(FileFilter filter)
FileFilter接口包含一个
boolean accept(File file)
-
void deleteOnExit()
-
- 构造函数
-
转换流:InputStreamReader OutputStreamWriter
- 以字节流为构造参数,默认使用平台编码,也可以指定编码集
- 自身可以作为参数去构造其他处理流
常用的处理流
处理流要继承 Filer~ 基类
- 缓冲流:Buffered~ 提供缓冲功能
- 输出基类中的 flush() 方法不做任何处理,缓冲流重写了该方法,用于刷新缓冲区
- BufferedReader 实例的 readLine() 用于读取一个文本行
- BufferedWriter 实例的 newLine() 用于输出一个行分隔符
- 打印流:PrintStream / PrintWriter 提供与打印相关的方法,比如向屏幕打印等
- 略
- 数据流:Data~ 提供了解析二进制数据的方法,比如可以直接获取读或写一个 int 等
- 略
标准输入输出流
- System.in:默认为键盘输入
- System.err / System.out:默认为屏幕输出,即控制台输出
System 的静态方法可以重定向:
static void setErr(PrintStream err)
static void setIn(InputStream in)
sattic void setOut(PrintStream out)
子进程(Process 对象)的输入输出:
InputStream getErrorStream()
获取子进程的错误输出流,对主进程是输入InputStream getInputStream()
获取子进程的输出流,对主进程是输入OutputStream getOutputStream()
获取子进程的输入流,对主进程是输出
RandomAccessFile
随机访问文件内容。与流相比,不是逐一读写,而是将文件内容放入内存,任意定位指针的访问方式。该类实现了DataInput、DataOutput 接口,所以可以像数据流那样,实现底层二进制数据和Java类型的交互。
-
添加了定位指针的方法:
-
long getFilePointer()
-
void seek(long pos)
-
-
创建该对象是可以指定文件访问模式,
"r"
只读"rw"
读写 -
写数据的时候是以覆盖的形式写入的。
Scanner
基于正则表达式的字符扫描解析器,可以以字符串、文件、输入流等源作为构造参数创建(二进制源需要指定编码),功能上和 DataInputStream 非常相似,不同的是 DataInputStream 解析的是二进制数据,且不需要扫描,比如要读一个 int 就直接解析当前的4个字节的数据;而 Scanner 每次扫描一个字符,碰到分割符进行解析,比如 "... 1234 34 ..."
将分割出 1234 和 34 进行解析。
boolean hasNextXxx()
Xxx nextXxx()
对象序列化
程序中,Java类对象是和运行环境绑定在一起的,运行环境包含了类的信息和运行基础等,可以操纵类对象。对象的序列化实现了对象自身数据的存储,脱离了运行环境而独立存在。
- 远程方法调用
- 停机配置还原
- …
序列化的一般流程
- 实现 Serializable 标记接口
- ObjectOutputStream 实例的 writeObject(Object obj) 完成序列化
- ObjectInputStream 实例的 readObject() 并强制类型转换,完成反序列化
深入理解序列化原理
-
本质上是对对象属性的记录,并没有保存程序运行所必须的类信息,所以反序列化时需要提供对应的类文件
-
对象的引用属性对象也必须是可序列化的类型
-
可以一次性序列化多个对象,反序列化时按照顺序读取
-
为了避免重复序列化一个对象,已序列化的对象都有一个序列化编号来标示内存中的对象。这样,反序列化后,原先指向同一个对象的两个引用,现在还是指向同一个对象。
陷阱:序列化过程中如果对象在序列化之后发生变化,这种变化将不可能被保存,即使是再次序列化
-
transient 修饰的实例变量不会被序列化,反序列化时的值为对象默认值
-
private static final long serialVersionUID
用于控制类文件的版本
自定义序列化
需要添加额外的特殊方法来完成自定义过程:
-
private void writeObject(ObjectOutputStream out) throws IOException
自定义序列化的过程,可以对属性进行加密,或者决定那些属性被序列化
-
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
自定义反序列化的过程,根据上面的方法按顺序进行解密等处理
-
private void readObjectNoData() throws ObjectStreamException
当数据异常,调用该方法初始化对象
-
private/protected Object writeReplace() throws ObjectStreamException
序列化过程中,完全替换当前对象,程序将序列化该方法返回值
-
private/protected Object readResolve() throws ObjectStreamException
反序列化过程中,完全替换当前对象,程序将返回该方法的返回值(主要用于枚举类的反序列化)
实现 Externalizable 接口,不同之处在与该接口是非标记的接口,需要实现两个方法和提供无参数的构造器:
void writeObject(ObjectOutput out) throws IOException
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
块(NIO)
“块”也是一种抽象,代表了一次性处理一块数据的处理方式,所以比“流”的处理效率更高。底层采用内存映射的方式处理输入输出。
这种处理方式是双向的
Buffer
代表内存中一段缓冲区,也就是那个“块”,程序和 Channel 都是通过访问缓冲区来实现输入输出。
Buffer 分为两个状态,IN 状态和 OUT 状态,对应图中的箭头方向。访问 Buffer 一般遵循以下四个步骤:
- 写入数据到 Buffer
- 调用
void flip()
进入 OUT 状态 - 从 Buffer 中读取数据
- 调用
void clear()
方法或者void compact()
方法进入 IN 状态
基本数据类型都有相对应的 Buffer 实现类,常用的 Buffer 实现类是 ByteBuffer。需要调用实现类的静态方法 static XxxBuffer allocate(int capacity)
得到一个 Buffer 对象。每一个实现类都添加了 get() 和 put() 方法类实现程序对缓冲区的写入和读取:
Xxx get()
Xxx get(int a)
(绝对)XxxBuffer get(Xxx[] dst)
XxxBuffer get(Xxx[] dst, int off, int length)
XxxBuffer put(Xxx xxx)
XxxBuffer put(int a, Xxx xxx)
(绝对)XxxBuffer put(Xxx[] dst)
XxxBuffer put(Xxx[] dst, int off, int length)
其他常用的方法有:
int capacity()
boolean hasRemaining()
int limit()
Buffer limit(int newlt)
Buffer mark()
int position()
Buffer position(int newps)
int remaining()
Buffer reset()
Buffer rewind()
static ByteBuffer allocateDirect()
创建一个直接 Buffer ,仅限 ByteBuffer
Channel
代表数据源,常用的实现类有:
- FileChannel:从文件读取数据的
- DatagramChannel:读写UDP网络协议数据
- SocketChannel:读写TCP网络协议数据
- ServerSocketChannel:可以监听TCP连接
所有的 Channel 需要通过 IO 节点流的 getChannel() 来创建,例如 FileInputStream、FileOutputStream、RandomAccessFile 的 getChannel() 返回 FileChannel 。每一个实现类都添加了 read() 和 write() 方法来实现 Channel 对缓冲区的读取和写入:
int read(ByteBuffer bbf)
int read(ByteBuffer bbf, long position)
int write(ByteBuffer bbf)
int write(ByteBuffer bbf, long position)
其他常用的方法:
MappedByteBuffer map(FileChannel.MapMode mode, long posision, long size)
void position(int pos)
Charset
编码集对象,使用流程:
Charset cs = Charset.forName("xxx");
CharSetEncoder ec = cs.newEncoder();
CharSetDecoder dc = cs.newDecoder();
ByteBuffer bbf = ec.encode(cbf);
CharBuffer cbf = dc.decode(bbf);