前言
输入/输出(I/O)是我们学习编程的必经之路。往往程序的本质就是对数据进行处理,而一些数据以便持久化而存储于文件中,有些数据用于临时使用存于内存中。换句话说,都知道一般游戏会有存档存储游戏数据,那么了解了这里的I/O,就能实现游戏的存档问题了。
必须说的是,由于Java的输入输出并不是很难,这里简单提一下流的使用,更多是作为笔记,记录各种Java流而已。
流式的输入/输出
流的概念
Java中,数据以流的方式传输。流是从源到目的地的字节的有序序列。流中字节依据先进先出存取数据。
流可以理解为一条单向的河流,一端是源,一头是目的地,数据是通过河流从源流到了目的地。
流的读写过程
读操作:打开流→流中存在数据将其读取出来→关闭流
读操作:打开流→流中存在数据对其写入数据→关闭流
流的分类
Java中的流的各种类操作都在java.io
包中。
一般Java中流常指的是字节流。
依据端点分类
- 节点流:以特定源如磁盘文件,内存某个区域或线程之间的管道(可以同时进行读写操作,进程间通讯)为端点构造的输入/输出流,它是一种最基本的流。
- 过滤流:以其他已经存在的流为端点构造的输入/输出流(可以在其他流中插入使用),称为过滤流或处理流,它要对其相连的另一个流进行某种转换。
依据传输数据的单位分类
- 字节流:流数据以8位字节为单位进行读写,以
InputStream
和OutputStream
为基础流。 - 字符流:流数据以16位字符为单位进行读写,以
Reader
和Writer
为基础类。
流的类层次图
(基本方法就不说了)
流的嵌套
往往为了提高流的利用,以及减少流的调用次数,可以嵌套流进行使用。
例如:
- 输入流嵌套:文件流(
FileInputStream
)为了提高效率将文件的内容写入缓冲流(BufferedInputStream
)然后缓冲流中的数据以数据类型流(DataInputStram
)将数据根据基本数据类型写到程序中。 - 输出流嵌套:从程序中以数据类型流(
DataOutputStream
)写入缓冲流(BufferedOutputStream
)再以文件流(FileOutputStream
)将数据写入文件中/节点。
常用输入/输出流类
(这里就写几个比较特别的类)
管道流
管道流可以实现线程间数据的直接连接。管道由管道输出端和管道输入端连接而成,例如以下:
其实质是将一个进程的管道输出端连接到另一个进程的管道输入端。
管道流的创建
两种方式:
- 第一种
1
2
3
4// 创建管道输入流
PipedInputStream pin = new PipedInputStream();
// 创建管道输出流,并连接输入流
PipedOutputStream pout = new PipedOutputStream(pin); - 第二种
1
2
3PipedInputStream pin = new PipedInputStream();
PipedOutputStream pout = new PipedOutputStream();
pin.connect(pout); // 或者 pout.connect(pin);文件
(仅展示比较重要的部分)
创建File对象
public File(String pathname)
:其中的参数可以是绝对路径(例如:"F:\work\sourse\text.txt"
)也可以是相对路径(一般是相对于当前目录,例如当前目录下的sourse中的text.txt文件:''sourse\text.txt''
)- 后面的其他参数重构就不提了,主要是要注意,路径的间隔符是
\
,有时候是\\
这里需要注意一下。
随机存取文件
RandomAccessFile类概述
RandomAccessFile
类与同包中的输入/输出不相关。因此一些作用于流的过滤流无法作于RandomAccessFile
类,这是它的不便之处。
随机存取类的创建
构造方法:
1 | public RandomAccessFile(String name,String mode) |
这里说一下mode的取值:
- r :以只读方式打开
- rw:以读写方式打开
- rwd:以读写方式打开,并要求对文件内容的更新要同步地写到底层存储器
- rws:与rwd类似,还可以更新文件的元数据(MetaData)
随机存取类的一些操作
随机存取以文件指针来读取文件内容
long getFilePointer()
返回当前文件指针,从文件开头算起 的绝对位置。void seek(long pos)
将文件指针指到只等位置,pos是相对于文件头的绝对偏移量long length()
- 返回文件长度。由此可与文件指针长度比较,来判断是否到文件尾
int skipBytes(int n)
从当前位置跳过n个字节,返回实际跳过的字节数
对象的串行化
概念
将java程序中的对象保存在外存中,成为对象永久化。
通过ObjectInputStream
和ObjectOutputStream
对对象读和写。
对象永久化关键在于将其以一种串行格式表示出来,以便后面读取对象能将其重构。所以对java对象的读写过程称为对象序列化(object serialization)。
读写操作
比较简单自行查找API即可。
值得注意的是读操作可以只读取对象的数据,读操作读取对象前,要先构造对象(创建对应的对象)然后用readObject()
获取对象,最好进行强制类型转换。
构造可串行化对象的类
也就是需要串行化的类最好实现串行化接口(Serializable
),这样其对象就可串行化。
java中的Serializable
接口的定义
1 | package java.io; |
实际Serializable
是个空接口,目的是标识类为可串行化。
当然可以自行定制扩充,操作就是接口的扩充。
串行化对敏感信息的保护
有时候一些程序会存在一些比较敏感的数据,在串行化的时候可能也会对这些敏感数据进行串行化,这样可能使得数据在流中被截取的风险,或者一些特殊数据如果恢复会造成程序混乱。
为此,可以对这些数据变量定义为private transient
。transient
和static
是不会进行串行化与反串行化,这样就保证了敏感信息不会写入流中。
串行化的注意事项
transient
关键字的使用
对于一些类,存在瞬时状态的变量,则无法保存状态并且这些数据往往没有保存价值。例如一个Tread
对象或一个FileInputStream
对象,对于这些字段,必须用transient
标明,否则编译器报错- 串行化对象存储或传输中的安全
串行化可能涉及存在将对象存在磁盘或网络上发送数据,这样就有安全问题。使用transient
关键字来防止串行化。
Java NIO
这是一个针对需要高性能应用而提供的高速I/O操作API。
这个详细,以后有机会等我弄明白了再添加。
参考文献
非常感谢这些文献:
[1]:《Java语言程序设计(第3版)》—郎波—清华大学出版社