Java IO流学习
Java流操作有关的类或接口:
Java流类图结构:
流的概念和作用?流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
在IO中涉及的设备文件包括文件、控制台、网络链接等,这其中又根据流的方向可以将两端的设备文件分为数据源对象和接收端对象
- 数据源对象:有能力产出数据
- 接收端对象:有能力接收数据
而IO流实际上屏蔽了在实际设备中的处理数据的细节,这些处理方式也叫通信方式可以包括顺序、随机存取、缓冲、二进制、按字符、按字节、按行等。
字符流和字节流。io默认都是直接操作字节的,多用于读取或书写二进制数据,这些类的基类为InputStream或OutputStream。而字符流操作的是为了支持Unicode编码,用于字符国际化,一个字符占用两个字节,这些类的基类为Reader或Writer。java的io在jdk1.1以后添加了字符流的支持,为我们直接操作字符流提供了方便。
最明智的做法是尽量尝试使用Reader和Writer,一旦程序代码无法成功编译,我们就会发现自己不得不使用面向字节的类库。
根据数据的流向,流分为输入流和输出流,这里指的都是内存,内存输入为读,输出为写,I读O写。
Java提供了针对不同情况的处理流的类,以便于我们直观地进行数据操作,这些类就在java的io包之中。下面介绍java io包的类,整个io包大量应用了装饰模式。在实际使用过程中,涉及多层套用的构造函数必然都会有自己的一个装饰模式的架构,包括被装饰类(原构建基类),装饰超类,装饰具体类,用的过程中懂得去区分理解会让你更加灵活地使用它们。
- 节点流:文件(File),管道(Piped)和数组(Array)(他们每个类都分别包括输入输出和字节字符四种流)
- 处理流:其余的都是处理类,他们都是属于节点流的装饰类,下面我整理了一个关于处理流的表格。
</tr>
<tr class="even">
<td>转化流</td>
<td>InputStreamReader/OutputStreamWriter</td>
<td>2</td>
<td>可将字节流转化为字符流</td>
</tr>
<tr class="odd">
<td>基本类型</td>
<td>DataXXXStream</td>
<td>2(IO替换XXX)</td>
<td>可传输基本类型数据</td>
</tr>
<tr class="even">
<td>对象流</td>
<td>ObjectXXXStream</td>
<td>2(IO替换XXX)</td>
<td>可传输对象类型数据(序列化)</td>
</tr>
<tr class="odd">
<td>打印流</td>
<td>PrintStream/PrintWriter</td>
<td>2</td>
<td>包含print和println的输出流</td>
</tr>
<tr class="even">
<td>合并流</td>
<td>SequenceInputStream</td>
<td>1</td>
<td>可逻辑串联其他输入流</td>
</tr>
<tr class="odd">
<td>行号读入流</td>
<td>LineNumberReader</td>
<td>1</td>
<td>可得到一个携带行号的字符读入流</td>
</tr>
<tr class="even">
<td>推回输入流</td>
<td>PushbackInputStream/PushbackReader</td>
<td>2</td>
<td>可将输入流push back或unread一个字节</td>
</tr>
<tr class="odd">
<td>字符串读写流</td>
<td>StringWriter/StringReader</td>
<td>2</td>
<td>可在缓冲区读写字符串</td>
</tr>
注意:
- 默认命名规则:字节流是输入输出(Input/Output),字符流是读写(Writer/Reader),当又有输入输出又有读写的时候,那一定是转化流(InputStreamReader/OutputStreamWriter)。
- 默认都是操作字节,所有操作字符的类都需要先经过转化流将字节流转为字符流再使用。
- LineNumberInputStream已过时,因为它是基于字节输入流的,而错误假定字节能充分表示字符,现已被LineNumberReader取代。
- StringBufferInputStream已过时,因为此类未能正确地将字符转换为字节,现已被StringReader取代。
- 这里面还有一个过滤流的概念,它也包括输入输出字节字符四种流,我在上面没有表示出来。它是一个抽象类,作为“装饰器”的接口,其中,“装饰器”为其他输入输出字符字节类提供有用功能。
我们知道在字符流处理类加入java io类库之前,所有的类都是面向字节流的,在jdk1.1以后,添加了字符流的支持,根据“开闭原则”,所以在不改变原有类的基础上,有了转化流:InputStreamReader和OutputStreamWriter,这两个类正是所谓的“适配器类”,InputStreamReader可以吧InputStream转换为Reader,而OutputStreamWriter可以将OutputStream转换为Writer。字节流和字符流都有各自独立的一整套继承层次结构,而通过适配器类,可以在不改变原有类的前提下有效将他们结合起来。
Java I/O类库需要多种不同功能的组合,存在filter类的原因就是抽象类filter是所有装饰器类的基类,装饰器必须具有和它所装饰对象相同的接口。FilterInputStream和FilterOutputStream是用来提供装饰器类接口以控制特定输入流(InputStream)和输出流(OutputStream)的两个类,他们的名字并不是很直观,包括DataInput/OutputStream,BufferedInput/OutputStream,LineNumberInputStream,PushbackInputStream,PrintStream等,这些过滤流类在下面都会有详细介绍。FilterInputStream和FilterOutputStream分别自I/O类库中的基类InputStream和OutputStream派生而立,这两个类是装饰器的必要条件(以便能为所有正在被修饰的对象提供通用接口)。
节点流是直接与数据源相连,进行IO操作,文件是数据源中最常见的,下面介绍文件相关的内容。
File类可以表示文件也可以表示文件夹目录,直接展示一段代码,就懂了。
+<span class="hljs-keyword">import java.io.<span class="hljs-type">File;
<span class="hljs-comment">/**
-
基于磁盘IO操作的类 java.io.File
-
-
可以表示文件,也可以表示文件夹目录
-
-
@author Evsward
-
*/
public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">FileS <span class="hljs-keyword">extends <span class="hljs-title">IOBaseS {
<span class="hljs-meta">@Test
public void testFileMethods() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
logger.info(<span class="hljs-string">"Start testing file methods.");
<span class="hljs-type">File file = <span class="hljs-keyword">new <span class="hljs-type">File(root);
<span class="hljs-keyword">if (!file.exists())
<span class="hljs-comment">/**
- 创建目录 mkdir();
*/
file.mkdir();
<span class="hljs-keyword">if (file.isDirectory()) {
<span class="hljs-type">File file1 = <span class="hljs-keyword">new <span class="hljs-type">File(root+<span class="hljs-string">"/UME.txt");
<span class="hljs-type">File file2 = <span class="hljs-keyword">new <span class="hljs-type">File(root+<span class="hljs-string">"/HongXing.txt");
<span class="hljs-comment">/**
* 创建文件 createNewFile();
*/
file1.createNewFile();
file2.createNewFile();
<span class="hljs-type">File file3 = <span class="hljs-keyword">new <span class="hljs-type">File(root+<span class="hljs-string">"/Cinema");
file3.mkdir();
<span class="hljs-comment">/**
* 列出文件路径下的所有文件(包括文件和目录)
*/
<span class="hljs-type">File[] files = file.listFiles();
<span class="hljs-keyword">for (<span class="hljs-type">File f : files) {
<span class="hljs-comment">/**
* 判断该文件路径是否为目录
*/
<span class="hljs-keyword">if (f.isDirectory()) {
logger.info(<span class="hljs-string">"The directory in 'Files' is: " + f.getName());
} <span class="hljs-keyword">else {
logger.info(<span class="hljs-string">"The file in 'Files' is: " + f.getName());
}
logger.info(<span class="hljs-string">"Whose path is: " + f.getAbsolutePath());
}
} <span class="hljs-keyword">else {
logger.info(<span class="hljs-string">"FileS is not a directory!");
}
logger.info(<span class="hljs-string">"Complete testing file methods.");
<span class="hljs-comment">/**
* 输出:
* 15:12:56[testFileMethods]: Start testing file methods.
* 15:12:56[testFileMethods]: The file in 'Files' is: HongXing.txt
* 15:12:56[testFileMethods]: Whose path is: /home/work/github/mainbase/resource/StudyFile/HongXing.txt
* 15:12:56[testFileMethods]: The directory in 'Files' is: Cinema
* 15:12:56[testFileMethods]: Whose path is: /home/work/github/mainbase/resource/StudyFile/Cinema
* 15:12:56[testFileMethods]: The file in 'Files' is: UME.txt
* 15:12:56[testFileMethods]: Whose path is: /home/work/github/mainbase/resource/StudyFile/UME.txt
* 15:12:56[testFileMethods]: Complete testing file methods.
*/
}
}
为了下面测试方便,我们在FileS类中加入一个静态方法,用来初始化目录:
然后,File还支持过滤器的功能,例如我们想要列出某目录下的所有文本文件的名字。
@<span class="hljs-function">Override
<span class="hljs-keyword">public boolean <span class="hljs-title">accept<span class="hljs-params">(File dir,String name) {
<span class="hljs-keyword">return Pattern.matches(filterStr,name);
}
});
<span class="hljs-keyword">for (String s : <span class="hljs-built_in">list)
logger.info(s);
}
<span class="hljs-comment">/**
* 输出:
*
* 12:57:17[testFileFilter]: start testing file filter.
*
* 12:57:17[testFileFilter]: HuaYi.txt
*
* 12:57:17[testFileFilter]: HongXing.txt
*
* 12:57:17[testFileFilter]: UME.txt
*/</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
正常的String list[] = file.list(); 我们都能用到,它的意思是列出该目录下的所有文件名,如果我们想过滤这些文件名列出我们只想要的文本文件名,则需要通过匿名内部类创建一个FilenameFilter接口的实现类对象,创建时必须实现其方法accept方法,该方法是一个回调函数,每一个查找出来的文件名都要回调该方法,是否能够通过全靠accept的布尔返回值所决定,这也是策略模式的体现,因为在accept方法体的具体实现留给了我们去自定义,所以这个函数更加有了一个“漏斗”的作用。我们只想得到文本文件名,那么定义一个正则表达式去过滤即可。
关于java的正则表达式,未来会写博文专门细说。
File其他方法的测试:
fl = Arrays.asList(file.listFiles());
logger.info(
输出:
一个用于操作数组或列表的实用打印输出类,其实这种写法我们自己也会经常在业务代码中写出。
<span class="hljs-keyword">import java.util.Arrays;
<span class="hljs-keyword">import java.util.Collection;
<span class="hljs-keyword">public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">PPrint {
<span class="hljs-keyword">public <span class="hljs-keyword">static <span class="hljs-type">String pFormat(<span class="hljs-type">Collection<?> <span class="hljs-built_in">c) {<span class="hljs-comment">// 泛型方法
<span class="hljs-keyword">if (<span class="hljs-built_in">c.size() == <span class="hljs-number">0)
<span class="hljs-keyword">return <span class="hljs-string">"[]";
<span class="hljs-type">StringBuilder result = new <span class="hljs-type">StringBuilder(<span class="hljs-string">"[");
<span class="hljs-keyword">for (<span class="hljs-type">Object elem : <span class="hljs-built_in">c) {
<span class="hljs-keyword">if (<span class="hljs-built_in">c.size() != <span class="hljs-number">1) {
result.append(<span class="hljs-string">"n");
}
result.append(elem);
}
<span class="hljs-keyword">if (<span class="hljs-built_in">c.size() != <span class="hljs-number">1) {
result.append(<span class="hljs-string">"n");
}
result.append(<span class="hljs-string">"]");
<span class="hljs-keyword">return result.<span class="hljs-built_in">toString();
}
<span class="hljs-comment">/**
* 打印一个可视化的集合
*
* @param c
*/
<span class="hljs-keyword">public <span class="hljs-keyword">static void pprint(<span class="hljs-type">Collection<?> <span class="hljs-built_in">c) {
<span class="hljs-type">System.out.<span class="hljs-built_in">println(pFormat(<span class="hljs-built_in">c));
}
<span class="hljs-comment">/**
* 打印一个可视化的数组
*
* @param c
*/
<span class="hljs-keyword">public <span class="hljs-keyword">static void pprint(<span class="hljs-type">Object[] <span class="hljs-built_in">c) {
<span class="hljs-type">System.out.<span class="hljs-built_in">println(pFormat(<span class="hljs-type">Arrays.asList(<span class="hljs-built_in">c)));
}
}
<h4 id="文件流">2.文件流
文件流包括输入输出字符字节四种类,首先来看文件字节流处理。
+<span class="hljs-keyword">import java.io.<span class="hljs-type">BufferedOutputStream;
<span class="hljs-comment">/**
-
字节流的学习
-
-
基于字节I/O操作的基类:InputStream和OutputStream
-
-
对应的缓存类:BufferedInputStream和BufferedOutputStream
-
-
出入的主语是“内存”,出内存就是写入文件,入内存就是读取文件
-
-
@author Evsward
-
*/
public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">ByteStreamS <span class="hljs-keyword">extends <span class="hljs-title">IOBaseS {
<span class="hljs-meta">@Test
<span class="hljs-comment">/**
-
使用输出流OutputStream.write,将内存中的内容写入设备文件(这里的设备文件为File:磁盘文件)
*/
public void testWrite2OutputStream() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">OutputStream fos = <span class="hljs-keyword">new <span class="hljs-type">FileOutputStream(root+<span class="hljs-string">"/UME.txt");<span class="hljs-comment">//找不到该文件会自动创建(包括路径)
<span class="hljs-comment">/**
- 内容中的字符串内容content
*/
<span class="hljs-type">String content = <span class="hljs-string">"哈哈哈n嘿嘿";
fos.write(content.getBytes());<span class="hljs-comment">// 直接写入字节
fos.close();<span class="hljs-comment">// 操作完注意将流关闭
<span class="hljs-comment">/**
- 文件后面追加内容,构造函数加第二个参数true
*/
<span class="hljs-type">OutputStream fosadd = <span class="hljs-keyword">new <span class="hljs-type">FileOutputStream(root+<span class="hljs-string">"/UME.txt",<span class="hljs-literal">true);
fosadd.write(<span class="hljs-string">" 你好".getBytes());
fosadd.close();
}
<span class="hljs-meta">@Test
<span class="hljs-comment">/**
- 使用输入流读取InputStream.read,将设备文件(这里的磁盘文件是File)读到内存buffer中去。
*/
public void testRead2InputStream() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
int bufferSize = <span class="hljs-number">200;
<span class="hljs-type">FileInputStream fis = <span class="hljs-keyword">new <span class="hljs-type">FileInputStream(root+<span class="hljs-string">"/UME.txt");
byte buffer[] = <span class="hljs-keyword">new byte[bufferSize];
int length;
<span class="hljs-keyword">while ((length = fis.read(buffer,<span class="hljs-number">0,bufferSize)) > <span class="hljs-number">-1) {
<span class="hljs-type">String str = <span class="hljs-keyword">new <span class="hljs-type">String(buffer,length);
logger.info(str);
}
fis.close();<span class="hljs-comment">// 操作完注意将流关闭
<span class="hljs-comment">/**
- 输出:
- 13:41:02[testInputStreamS]: 举杯邀明月床前明月光
*/
}
}
可以将数据流从数据源中处理完毕都存入内存缓冲区,然后统一一次性与底层IO进行操作,可以有效降低程序直接操作IO的频率,提高io执行速度。以下为缓冲区处理流的使用。
缓冲区处理流:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,* 一次性写入,降低占用IO的频率
避免每次和硬盘打交道,提高数据访问的效率。
*/
@<span class="hljs-function">Test
<span class="hljs-keyword">public <span class="hljs-keyword">void <span class="hljs-title">testWrite2BufferedOutputStream<span class="hljs-params">() throws IOException {
<span class="hljs-comment">// OutputStream为基类
OutputStream fosaddOnce = <span class="hljs-keyword">new FileOutputStream(root+<span class="hljs-string">"/UME.txt");
OutputStream bs = <span class="hljs-keyword">new BufferedOutputStream(fosaddOnce);
bs.write(<span class="hljs-string">"举杯邀明月".getBytes());
bs.flush();<span class="hljs-comment">// 每次flush会将内存中数据一齐刷入到外部文件中,但不会close该流。
bs.write(<span class="hljs-string">"床前明月光".getBytes());
<span class="hljs-comment">/**
- close方法除了有关闭流的作用,在其关闭流之前也会执行一次flush。
- 注意一定要先关闭BufferedOutputStream,再关闭FileOutputStream,从外到内打开,要从内到外关闭。
*/
bs.close();
fosaddOnce.close();<span class="hljs-comment">// 两个流都要关闭
}
我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能。(这就是装饰器模式)
下面再看字符流的处理,这里也包括了文件字符读写流(FileWriter/FileReader)的应用。
+<span class="hljs-keyword">import java.io.<span class="hljs-type">BufferedReader;
<span class="hljs-comment">/**
-
字符流的学习
-
-
基于字符I/O操作的基类: Reader和Writer
-
-
对应的缓存类:BufferedReader和BufferedWriter
-
-
@author Evsward
-
*/
public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">CharacterStreamS <span class="hljs-keyword">extends <span class="hljs-title">IOBaseS {
<span class="hljs-meta">@Test
<span class="hljs-comment">/**
-
OutputStreamWriter,字节到字符的转化桥梁,转化过程中需指定编码字符集,否则采用默认字符集。
*/
public void testWriter() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-comment">// 文件输出流不变
<span class="hljs-type">FileOutputStream fos = <span class="hljs-keyword">new <span class="hljs-type">FileOutputStream(root + <span class="hljs-string">"HongXing.txt");
<span class="hljs-comment">/**
- 输出流写入类(这是比起字节流多出来的类)专门用来写入字符流,注意字符编码的参数
-
- 如果只保留fos一个参数,编码默认为工作区默认编码,这里是“UTF-8",
-
- 字节编码为字符 -> 请转到 http://www.cnblogs.com/Evsward/p/huffman.html#ascii编码
-
- 为了保证写入和读取的编码统一,请每次都要指定编码
-
- 输出体系中提供的两个转换流,用于实现将字节流转换成字符流。
*/
<span class="hljs-type">OutputStreamWriter osw = <span class="hljs-keyword">new <span class="hljs-type">OutputStreamWriter(fos);
<span class="hljs-comment">// 缓存写入类,对应BufferedOutputStream
<span class="hljs-type">BufferedWriter bw = <span class="hljs-keyword">new <span class="hljs-type">BufferedWriter(osw);
bw.write(<span class="hljs-string">"感时花溅泪,恨别鸟惊心");
bw.close();
osw.close();
fos.close();
<span class="hljs-comment">/**
- 终版:将close部分缩短
*/
<span class="hljs-type">BufferedWriter bwA = <span class="hljs-keyword">new <span class="hljs-type">BufferedWriter(
<span class="hljs-keyword">new <span class="hljs-type">OutputStreamWriter(<span class="hljs-keyword">new <span class="hljs-type">FileOutputStream(root + <span class="hljs-string">"HongXing.txt",<span class="hljs-literal">true),<span class="hljs-string">"UTF-8"));<span class="hljs-comment">// 注意层级,加入指定编码的参数
bwA.write(<span class="hljs-string">"n烽火连三月,家书抵万金");
bwA.close();
}
<span class="hljs-meta">@Test
<span class="hljs-comment">/**
-
InputStreamReader,字节到字符的转化桥梁,转化过程中需指定编码字符集,否则采用默认字符集。
*/
public void testReader() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">FileInputStream fis = <span class="hljs-keyword">new <span class="hljs-type">FileInputStream(root + <span class="hljs-string">"HongXing.txt");
<span class="hljs-comment">/**
- 输出流读取类(这是比起字节流多出来的类)专门用来读取字符流,注意字符编码的参数要与写入时的编码相同,否则会乱码
-
- 输入体系中提供的两个转换流,用于实现将字节流转换成字符流。
*/
<span class="hljs-type">InputStreamReader isr = <span class="hljs-keyword">new <span class="hljs-type">InputStreamReader(fis,<span class="hljs-string">"UTF-8");
<span class="hljs-type">BufferedReader br = <span class="hljs-keyword">new <span class="hljs-type">BufferedReader(isr);
<span class="hljs-type">String str;<span class="hljs-comment">// 这里也可以用StringBuilder来累计文件的全部内容,最后输出。
<span class="hljs-keyword">while ((str = br.readLine()) != <span class="hljs-literal">null) {
logger.info(str);
}
br.close();<span class="hljs-comment">// 显示调用close方法关闭流
isr.close();
fis.close();
<span class="hljs-comment">/**
- 终版:将close部分缩短
*/
<span class="hljs-type">BufferedReader brA = <span class="hljs-keyword">new <span class="hljs-type">BufferedReader(
<span class="hljs-keyword">new <span class="hljs-type">InputStreamReader(<span class="hljs-keyword">new <span class="hljs-type">FileInputStream(root + <span class="hljs-string">"HongXing.txt"),<span class="hljs-string">"UTF-8"));
<span class="hljs-type">String strA;
<span class="hljs-keyword">while ((strA = brA.readLine()) != <span class="hljs-literal">null) {
logger.info(strA);
}
brA.close();
<span class="hljs-comment">/**
- 输出: 15:04:07[testReader]: 感时花溅泪,恨别鸟惊心 15:04:07[testReader]:
- 烽火连三月,家书抵万金
*/
}
<span class="hljs-comment">/**
- File提供了支持字符读写的封装类:FileWriter和FileReader
- 所以不必每次都使用InputStreamReader和OutputStreamWriter来转换。
*/
<span class="hljs-meta">@Test
public void testFileWriter() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">FileWriter fw = <span class="hljs-keyword">new <span class="hljs-type">FileWriter(root + <span class="hljs-string">"HongXing.txt",<span class="hljs-literal">true);
fw.write(<span class="hljs-string">"n杜甫《春望》");
fw.close();
}
<span class="hljs-meta">@Test
public void testFileReader() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">FileReader fr = <span class="hljs-keyword">new <span class="hljs-type">FileReader(root + <span class="hljs-string">"HongXing.txt");
<span class="hljs-comment">// FileReader直接read方法没有readLine方便,所以套上装饰类BufferedReader借它的readLine用一用
<span class="hljs-type">BufferedReader br = <span class="hljs-keyword">new <span class="hljs-type">BufferedReader(fr);
<span class="hljs-type">String str;
<span class="hljs-keyword">while ((str = br.readLine()) != <span class="hljs-literal">null) {
logger.info(str);
}
br.close();
fr.close();
}
}
注意:File提供了支持字符读写的封装类:FileWriter和FileReader,所以不必每次都使用InputStreamReader和OutputStreamWriter来转换。
RandomAccessFile 是任意位置进入文件的意思,适用于由大小已知的记录组成的文件,它有一个seek方法定义了文件的位置,所以要注意在对文件进行RandomAccessFile操作时,要记住文件的内容的位置和大小,否则会发生内容复写更改的后果。
+<span class="hljs-keyword">import java.io.<span class="hljs-type">File;
<span class="hljs-comment">/**
-
RandomAccessFile:有一个指针seek,对文件任意位置操作的类
-
-
@author Evsward
-
*/
public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">RandomAccessFileS <span class="hljs-keyword">extends <span class="hljs-title">IOBaseS {
<span class="hljs-meta">@Before
public void testWrite2RAFile() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">FileS.initEV(root + <span class="hljs-string">"/access");<span class="hljs-comment">// 首先清空access文件。
<span class="hljs-type">RandomAccessFile raf = <span class="hljs-keyword">new <span class="hljs-type">RandomAccessFile(root + <span class="hljs-string">"/access",<span class="hljs-string">"rw");<span class="hljs-comment">// rw是采用读写的方式打开文件
logger.info(raf.length());
<span class="hljs-type">Student <span class="hljs-type">Jhon = <span class="hljs-keyword">new <span class="hljs-type">Student(<span class="hljs-number">1001,<span class="hljs-string">"Jhon",<span class="hljs-number">26,<span class="hljs-number">1.85d);
<span class="hljs-type">Student <span class="hljs-type">Jack = <span class="hljs-keyword">new <span class="hljs-type">Student(<span class="hljs-number">1002,<span class="hljs-string">"Jack",<span class="hljs-number">25,<span class="hljs-number">1.75d);
<span class="hljs-type">Jhon.write(raf);<span class="hljs-comment">// 写入文件以后,指针到当前文本结尾
<span class="hljs-comment">// 当前seek是从seek(raf.length)开始的
logger.info(raf.length());
<span class="hljs-type">Jack.write(raf);<span class="hljs-comment">// 继续写入,指针继续移动到末尾,相当于追加
logger.info(raf.length());
raf.close();
}
<span class="hljs-meta">@After
public void testReadRAFile() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">RandomAccessFile raf = <span class="hljs-keyword">new <span class="hljs-type">RandomAccessFile(root + <span class="hljs-string">"/access",<span class="hljs-string">"r");
<span class="hljs-comment">// 获取raf时,seek就是在文件开始位置
logger.info(raf.length());
<span class="hljs-type">Student <span class="hljs-type">Lily = <span class="hljs-keyword">new <span class="hljs-type">Student();
<span class="hljs-type">Lily.read(raf);
logger.info(<span class="hljs-type">Lily);
<span class="hljs-type">Lily.read(raf);
logger.info(<span class="hljs-type">Lily);
<span class="hljs-comment">// 读入次数是有限的,一定要预先知道最多读几次,否则会报EOFException。
<span class="hljs-type">Lily.read(raf);
logger.info(<span class="hljs-type">Lily);
raf.close();
<span class="hljs-comment">/**
- 输出: 16:14:30[testReadRAFile]: id:1001 name:Jhon age:26 height:1.85
*/
}
}
RandomAccessFile就像数据库一样,是将数据一条一条的写入的,而这些数据必须是类对象,不能是基本类型数据,因为该类对象要写两个方法write和read用来将对象写入RandomAccessFile,这两个方法也不是继承于谁,可以起别的名字,调用的时候也是自己调用。
<span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-title">Student<span class="hljs-params">() {
}
<span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-title">Student<span class="hljs-params">(<span class="hljs-keyword">long id,String name,<span class="hljs-keyword">int age,<span class="hljs-keyword">double height) {
<span class="hljs-keyword">super();
<span class="hljs-keyword">this.id = id;
<span class="hljs-keyword">this.name = name;
<span class="hljs-keyword">this.age = age;
<span class="hljs-keyword">this.height = height;
}
<span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-keyword">void <span class="hljs-title">write<span class="hljs-params">(RandomAccessFile raf) <span class="hljs-keyword">throws IOException {
raf.writeLong(id);
raf.writeUTF(name);<span class="hljs-comment">// 采用UTF的编码方式写入字符串
raf.writeInt(age);
raf.writeDouble(height);
}
<span class="hljs-comment">/**
- 要严格按照写入顺序读取,这也是ORM的意义
-
- <span class="hljs-doctag">@param raf
- <span class="hljs-doctag">@throws IOException
*/
<span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-keyword">void <span class="hljs-title">read<span class="hljs-params">(RandomAccessFile raf) <span class="hljs-keyword">throws IOException {
<span class="hljs-keyword">this.id = raf.readLong();
<span class="hljs-keyword">this.name = raf.readUTF();
<span class="hljs-keyword">this.age = raf.readInt();
<span class="hljs-keyword">this.height = raf.readDouble();
}
}
按照类的结构,将字段按顺序写入文件的任意位置,读取的时候也要按照相同的顺序读出。
- RandomAccessFile 追加内容追加的时候要使用seek方法,将光标定位到文件末尾,如果不重新定位,默认从起点开始写入,就会覆盖原有数据。
- RandomAccessFile 在任意位置插入数据
插入数据的时候,RandomAccessFile并没有提供相关的方法,由于在旧的位置写入数据会覆盖原有数据,所以我们要将插入位置后面的数据缓存到一个临时文件中,插入数据以后再将临时文件的内容接着被插入的数据后面。
- @BeforeClass –> @Before –> @Test –> @After –> @AfterClass
- @BeforeClass和@AfterClass只执行一次,且必须为static void
- @Ignore 在执行整个类的时候会跳过该方法
下面我们定义一个完整测试流程:先初始化一个空白文件,然后添加两行数据Jhon和Jack,然后在他俩中间插入Hudson,最后读出该文件数据,验证结果输出为:
这一对类可以直接写入java基本类型数据(没有String),但写入以后是一个二进制文件的形式,不可以直接查看。DataOutputStream / DataInputStream是常用的过滤流类,如果对象的序列化是整个对象转换为一个字节序列的话,DataOutputStream / DataInputStream就是将字段序列化,转为二进制数据。下面看代码。
+<span class="hljs-keyword">import java.io.<span class="hljs-type">BufferedInputStream;
public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">DataStream <span class="hljs-keyword">extends <span class="hljs-title">IOBaseS {
<span class="hljs-meta">@Test
<span class="hljs-comment">/**
* DataOutputStream,可以直接写入java基本类型数据(没有String),但写入以后是一个二进制文件的形式,不可以直接查看。
*
* 文本文件是二进制文件的特殊形式,这是通过转储实现的,相关内容请转到
* http://www.cnblogs.com/Evsward/p/huffman.html#二进制转储
*/
public void testWrite2DataOutputStream() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">OutputStream fosaddOnce = <span class="hljs-keyword">new <span class="hljs-type">FileOutputStream(root + <span class="hljs-string">"/UME.txt");
<span class="hljs-type">OutputStream bs = <span class="hljs-keyword">new <span class="hljs-type">BufferedOutputStream(fosaddOnce);
<span class="hljs-type">DataOutputStream dos = <span class="hljs-keyword">new <span class="hljs-type">DataOutputStream(bs);
dos.writeInt(<span class="hljs-number">22);
dos.writeShort(<span class="hljs-number">1222222222);
dos.writeLong(<span class="hljs-number">20L);
dos.writeByte(<span class="hljs-number">3);
dos.writeChar(<span class="hljs-number">42);
dos.close();
bs.close();
fosaddOnce.close();
<span class="hljs-comment">/**
* 终版:上面的close阶段要从内向外关闭三次,比较麻烦,下面直接采用装饰模式标准写法,套接对象。
* 套接对象:最里面的一定是节点流,它之外的无论几层都是处理流
* FileOutputStream:属于节点流,其他节点流还包括管道和数组,剩下的都是处理流
* BufferedOutputStream:缓冲技术(也属于处理流) DataOutputStream:处理流
*/
<span class="hljs-type">DataOutputStream dosA = <span class="hljs-keyword">new <span class="hljs-type">DataOutputStream(<span class="hljs-keyword">new <span class="hljs-type">BufferedOutputStream(<span class="hljs-keyword">new <span class="hljs-type">FileOutputStream(root + <span class="hljs-string">"/UME.txt")));
dosA.writeInt(<span class="hljs-number">22);
dosA.writeShort(<span class="hljs-number">65538);<span class="hljs-comment">// DataOutputStream并不会检查数据是否越界,越界的数据按照二进制方式截取,只保留界限以内的数据。
dosA.writeLong(<span class="hljs-number">20L);
dosA.writeByte(<span class="hljs-number">3);
dosA.writeChar(<span class="hljs-number">42);
dosA.writeDouble(<span class="hljs-number">3.1415926);
dosA.close();<span class="hljs-comment">// 只关闭一次。
}
<span class="hljs-meta">@Test
<span class="hljs-comment">/**
* 通过DataInputStream处理流读取二进制文件,一定要按照写入的顺序去读取java基本类型的文件内容,否则会出现乱码或者不准确的信息
*/
public void testRead2DataInputStream() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">DataInputStream dis = <span class="hljs-keyword">new <span class="hljs-type">DataInputStream(<span class="hljs-keyword">new <span class="hljs-type">BufferedInputStream(<span class="hljs-keyword">new <span class="hljs-type">FileInputStream(root + <span class="hljs-string">"/UME.txt")));
logger.info(dis.readInt());
<span class="hljs-comment">/**
* 即使存入越界的树65538,也不会报错,因为超出部分不会被存入,存入的只是超出的部分。
* short类型占据16位的空间,因此将65538转为二进制数,超出16位的部分自动截掉,只保留16为以内的数据,所以就变成了2。
*/
logger.info(dis.readShort());
logger.info(dis.readLong());
logger.info(dis.readByte());
logger.info(dis.readChar());
logger.info(dis.readDouble());
dis.close();
<span class="hljs-comment">/**
* 输出:
* 13:39:03[testDataInputStream]: 22
* 13:39:03[testDataInputStream]: 2
* 13:39:03[testDataInputStream]: 20
* 13:39:03[testDataInputStream]: 3
* 13:39:03[testDataInputStream]: *
* 13:39:03[testDataInputStream]: 3.1415926
*/
}
}
注释比较齐全,这里不再过多赘述。
数据传输过程中,都会默认采用二进制文件的方式,因为计算机的底层识别方式就是二进制,不依赖任何运行环境或是程序设计语言,所以这是实现数据传输跨平台跨网络的基础。序列化可以直接将java对象转化为一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象,这一过程甚至可以通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。
如果要让一个类的对象可以成功序列化,那么它必须实现Serializable接口,这个接口就像Cloneable接口一样,他们只是一个空的标识接口。
下面这段话用来描述对象序列化真的非常受启发。
当你创建对象时,只要你需要,它就会一直存在,但是在程序终止时,无论如何它都不会继续存在。尽管这么做肯定是有意义的,但是仍旧存在某些情况,如果对象能够在程序不运行的情况下仍能存在并保存其信息,那将非常有用。这样,在下次运行程序时,该对象将被重建并且拥有的信息与在程序上次运行时它所拥有的信息相同。当然,你可以通过将信息写入文件或数据库来达到相同的效果,但是在使万物都成为对象的面向对象的精神中,如果能够将一个对象声明是“持久性”的,并为我们处理掉所有细节,那将会显得十分方便。
相对于数据库存储那种重量级持久性来说,对象的序列化可以实现轻量级持久性。
持久性意味着一个对象的生命周期并不取决于程序是否正在执行,它可以生存于程序的调用之间。
轻量级是因为不能用某个关键字来简单定义一个对象,并让系统自动维护其他细节问题,相反,对象必须在程序中显示地序列化和反序列化。
如果需要一个更严格的持久性机制,可以考虑像Hibernate之类的工具。TODO: 会有一篇文章深入介绍Hibernate。
对象序列化的意义:
- 支持java的远程方法调用RMI,它使存活于其他计算机上的对象使用起来就像是存活在本机上一样。
- 对于java bean,一定要保存下来它在设计阶段对他的状态信息的配置,在程序启动时进行后期恢复,这中具体工作就是由对象序列化完成的。
<span class="hljs-comment">/**
-
Serializable为标记接口,表示这个类的对象可以被序列化。
-
-
@author Evsward
-
*/
<span class="hljs-keyword">public <span class="hljs-keyword">class <span class="hljs-title">Student <span class="hljs-title">extends <span class="hljs-title">IOBaseS <span class="hljs-title">implements <span class="hljs-title">Serializable {
<span class="hljs-comment">/**
- 类中的声明
-
- transient和static的变量不会被序列化
*/
<span class="hljs-comment">/**
- 序列号:避免重复序列化
- 如果serialVersionUID被修改,反序列化会失败。
- 当程序试图序列化一个对象时,会先检查该对象是否已经被序列化过,只有该对象从未(在本次虚拟机中)被序列化,系统才会将该对象转换成字节序列并输出。
*/
<span class="hljs-keyword">private <span class="hljs-keyword">static final <span class="hljs-keyword">long serialVersionUID = <span class="hljs-number">-6861464712478477441L;
<span class="hljs-keyword">private <span class="hljs-keyword">long id;
<span class="hljs-keyword">private String name;
<span class="hljs-keyword">private <span class="hljs-keyword">int age;
<span class="hljs-keyword">private transient <span class="hljs-keyword">double height;
<span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-title">Student(<span class="hljs-params">) {
}
<span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-title">Student(<span class="hljs-params"><span class="hljs-keyword">long id,<span class="hljs-keyword">double height) {
super();
<span class="hljs-keyword">this.id = id;
<span class="hljs-keyword">this.name = name;
<span class="hljs-keyword">this.age = age;
<span class="hljs-keyword">this.height = height;
}
@<span class="hljs-function">Override
<span class="hljs-keyword">public String <span class="hljs-title">toString(<span class="hljs-params">) {
StringBuilder sb = <span class="hljs-keyword">new StringBuilder();
sb.append(<span class="hljs-string">"id:");
sb.append(<span class="hljs-keyword">this.id);
sb.append(<span class="hljs-string">" ");
sb.append(<span class="hljs-string">"name:");
sb.append(<span class="hljs-keyword">this.name);
sb.append(<span class="hljs-string">" ");
sb.append(<span class="hljs-string">"age:");
sb.append(<span class="hljs-keyword">this.age);
sb.append(<span class="hljs-string">" ");
sb.append(<span class="hljs-string">"height:");
sb.append(<span class="hljs-keyword">this.height);
<span class="hljs-keyword">return sb.toString();
}
<span class="hljs-comment">/**
- 相当于重写了ObjectOutputStream.writeObject方法,ObjectOutputStream写入该对象的时候会调用该方法
-
- 作用:可以在序列化过程中,采用自定义的方式对数据进行加密
-
- 参考源码:
-
- public final void writeObject(Object obj) throws IOException {
if (enableOverride) {// 如果发现参数Object有重写该方法,则去执行重写的方法,否则继续执行本地方法。
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj,false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
-
-
- readObject方法的分析同上。
-
- @param out
- @throws IOException
*/
<span class="hljs-function"><span class="hljs-keyword">private final <span class="hljs-keyword">void <span class="hljs-title">writeObject(<span class="hljs-params">ObjectOutputStream <span class="hljs-keyword">out) throws IOException {
logger.info(<span class="hljs-string">"Start writing data to Object.");
<span class="hljs-keyword">out.writeLong(<span class="hljs-keyword">this.id);
<span class="hljs-comment">/**
- 下面的writeObject是StringBuffer源码中的:
-
- readObject is called to restore the state of the StringBuffer from a stream.
private synchronized void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
java.io.ObjectOutputStream.PutField fields = s.putFields();
fields.put("value",value);
fields.put("count",count);
fields.put("shared",false);
s.writeFields();
}
*/
<span class="hljs-keyword">out.writeObject(<span class="hljs-keyword">new StringBuffer(name));
<span class="hljs-keyword">out.writeInt(<span class="hljs-keyword">this.age);
<span class="hljs-keyword">out.writeDouble(<span class="hljs-keyword">this.height);<span class="hljs-comment">// 这里重写以后,就忽略了transient的设置
}
<span class="hljs-function"><span class="hljs-keyword">private final <span class="hljs-keyword">void <span class="hljs-title">readObject(<span class="hljs-params">ObjectInputStream <span class="hljs-keyword">in) throws IOException,ClassNotFoundException {
logger.info(<span class="hljs-string">"Start reading data to Object.");
<span class="hljs-keyword">this.id = <span class="hljs-keyword">in.readLong();
<span class="hljs-comment">/**
- 下面的readObject是StringBuffer源码中的:
-
- readObject is called to restore the state of the StringBuffer from a stream.
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException,ClassNotFoundException {
java.io.ObjectInputStream.GetField fields = s.readFields();
value = (char[])fields.get("value",null);
count = fields.get("count",0);
}
*/
<span class="hljs-keyword">this.name = ((StringBuffer) <span class="hljs-keyword">in.readObject()).toString();
<span class="hljs-keyword">this.age = <span class="hljs-keyword">in.readInt();
<span class="hljs-keyword">this.height = <span class="hljs-keyword">in.readDouble();
}
}
这个类内容比较多,因为为了更好的去分析,我将jdk的源码也粘贴到注释区域了。但是不影响,文末会有源码位置,感兴趣的同学可以去下载源码查看。在这个类中,我们重写了readObject和writeObject方法,外部ObjectXXXStream在调用的时候会先找对象类中是否有重写的readObject和writeObject方法,如果有则使用没有才调用自己内部默认的实现方法。
+<span class="hljs-keyword">import java.io.<span class="hljs-type">FileInputStream;
<span class="hljs-comment">/**
-
研究对象序列化(跨平台,跨网络的基础)
-
-
内存 -> 磁盘/网络
-
-
Java对象 -> 二进制文件
-
-
@author Evsward
-
*/
public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">ObjectStreamS <span class="hljs-keyword">extends <span class="hljs-title">IOBaseS {
<span class="hljs-comment">/**
- ObjectOutputStream:对象流(对象序列化),不同于DataOutputStream是操作基本类型。
- 测试序列化对象存储结构
*/
<span class="hljs-meta">@Test
public void testWriteSerialObject() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">FileS.initEV(root + <span class="hljs-string">"/access");<span class="hljs-comment">// 先将access文件清空。
<span class="hljs-type">ObjectOutputStream oos = <span class="hljs-keyword">new <span class="hljs-type">ObjectOutputStream(<span class="hljs-keyword">new <span class="hljs-type">FileOutputStream(root + <span class="hljs-string">"/access"));
<span class="hljs-type">Student <span class="hljs-type">Lu = <span class="hljs-keyword">new <span class="hljs-type">Student(<span class="hljs-number">2001,<span class="hljs-string">"Luxa",<span class="hljs-number">31,<span class="hljs-number">1.81d);
<span class="hljs-comment">// 可以写入不同的序列化对象数据,但要记录写入顺序
oos.writeObject(<span class="hljs-type">Lu);
oos.close();
<span class="hljs-comment">/**
- access内容:由于写入的是一个二进制文件,所以打开是乱码
-
- ?í^@^Esr^@^PjavaS.IO.Student??.2<95>×3^?^B^@^DI^@^CageD^@^FheightJ^@^BidL^@^Dnamet^@^RLjava/lang/String;xp^@^@^@^_?ü??<8f>(?^@^@^@^@^@^@^G?t^@^DLuxa
*/
}
<span class="hljs-comment">/**
- ObjectInputStream:对象反序列化
- 读取二进制文件(序列号文件)
-
- @throws IOException
- @throws ClassNotFoundException
*/
<span class="hljs-meta">@Test
public void testReadSerialObject() <span class="hljs-keyword">throws <span class="hljs-type">IOException,<span class="hljs-type">ClassNotFoundException {
<span class="hljs-type">ObjectInputStream ois = <span class="hljs-keyword">new <span class="hljs-type">ObjectInputStream(<span class="hljs-keyword">new <span class="hljs-type">FileInputStream(root + <span class="hljs-string">"/access"));
<span class="hljs-comment">// 可以读取不同的对象的数据,但是要按照写入顺序去读取。
<span class="hljs-type">Student s = (<span class="hljs-type">Student) ois.readObject();
logger.info(s);
ois.close();
<span class="hljs-comment">/**
- ①输出:
-
- 10:24:08[testReadSerialObject]: id:2001 name:Luxa age:31 height:1.81
-
- ②若height属性变量被声明为transient,则该变量在序列化过程中不会被写入,为初始值。输出为:
-
- 10:29:34[testReadSerialObject]: id:2001 name:Luxa age:31 height:0.0
*/
}
}
transient变量可标识出来不被序列化的字段,这些字段可能携带敏感信息,例如密码。但是这个关键字在我们重写了writeObject和readObject方法以后,不好使了。
序列化的使用忠告:
- 谨慎实现Serializable接口,需要随时保持uuid的时效性以及一致性,对于结构性框架中要避免不可序列化的类与已序列化的类之间的继承关系。
- 要考虑实现自定义的序列化形式,正如以上代码中我们所做的那样。
- 保护性的编写readObject方法。加入异常处理,让无效的序列化对象在反序列化过程中终端抛出异常。
首先请问java的标准输入流是什么?是InputStream,正确。那么java的标准输出流是什么?是OutputSteam?No!而是PrintStream。
因为标准输入输出流是System类的定义,System中有三个字段,in是InputStream类型,对应的是标准输入流,err和out都是PrintStream对象,out对应的是标准输出流。我们常用的System.out.println(data)方法的返回值就是PrintStream对象,此流默认输出在控制台,也可以重定向输出位置:
PrintWriter就是PrintStream的字符操作的版本。PrintStream都是针对字节流进行操作的,如果要操作字符流,可以使用PrintWriter。下面直接看代码的注释吧。
+<span class="hljs-keyword">import java.io.<span class="hljs-type">FileOutputStream;
public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">PrintStreamS <span class="hljs-keyword">extends <span class="hljs-title">IOBaseS {
<span class="hljs-comment">/**
* 打印流的使用非常类似于FileWriter,但是它支持更多的方法,同时也有着丰富的构造方法。
*/
<span class="hljs-meta">@Test
public void testPrintStream() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">FileS.initEV(root + <span class="hljs-string">"HongXing.txt");
<span class="hljs-comment">// PrintStream p = new PrintStream(root + "HongXing.txt");
<span class="hljs-comment">// PrintStream p = new PrintStream(new File(root + "HongXing.txt"));
<span class="hljs-comment">// PrintStream p = new PrintStream(new FileOutputStream(root +
<span class="hljs-comment">// "HongXing.txt"),true,"UTF-8");
<span class="hljs-type">PrintStream p = <span class="hljs-type">System.out;<span class="hljs-comment">// 数据源切换到控制台,标准输出,相当于System.out.xxx();
p.append(<span class="hljs-string">"海上升明月");
p.println(<span class="hljs-string">"润物细无声");
p.print(<span class="hljs-string">"当春乃发生");
p.write(<span class="hljs-string">"无敌心头好".getBytes());
p.flush();<span class="hljs-comment">// 刷入内存数据到数据源
<span class="hljs-type">System.out.write(<span class="hljs-string">"asdfas".getBytes());
p.close();
<span class="hljs-comment">/**
* 输出:
*
* 海上升明月润物细无声
*
* 当春乃发生无敌心头好
*/
}
<span class="hljs-comment">/**
* PrintWriter与PrintStream的两点区别:
*
* write方法一个是写入字节,一个是写入字符。
*
* 一般来讲,使用PrintStream多一些。
*/
<span class="hljs-meta">@Test <span class="hljs-comment">// 如果忘记写该注解,执行JUnit会报错initializationError
public void testPrintWriter() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">FileS.initEV(root + <span class="hljs-string">"HongXing.txt");
<span class="hljs-comment">// PrintWriter p = new PrintWriter(root + "HongXing.txt");
<span class="hljs-comment">// PrintWriter p = new PrintWriter(new File(root + "HongXing.txt"));
<span class="hljs-comment">// 第二个参数为autoflush,如果为true的话,println、printf和format会自动执行flush。
<span class="hljs-comment">// PrintWriter p = new PrintWriter(new FileOutputStream(root + "HongXing.txt"),true);
<span class="hljs-type">System.setOut(<span class="hljs-keyword">new <span class="hljs-type">PrintStream(<span class="hljs-keyword">new <span class="hljs-type">FileOutputStream(root + <span class="hljs-string">"HongXing.txt")));<span class="hljs-comment">// 输出重定向,从默认的控制台转到文件,这也是日志系统的基本思想。
<span class="hljs-type">PrintWriter p = <span class="hljs-keyword">new <span class="hljs-type">PrintWriter(<span class="hljs-type">System.out,<span class="hljs-literal">true);<span class="hljs-comment">// 将PrintWriter的打印位置改到标准输出
p.append(<span class="hljs-string">"海上升明月");
p.println(<span class="hljs-string">"润物细无声");
p.print(<span class="hljs-string">"当春乃发生");
p.write(<span class="hljs-string">"无敌心头好");<span class="hljs-comment">// 这是与PrintStream唯一区别了
p.flush();<span class="hljs-comment">// PrintWriter也支持刷入操作
p.close();
}
<span class="hljs-comment">/**
* 测试标准输入输出
*/
public void testStandardIO() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">BufferedReader stdin = <span class="hljs-keyword">new <span class="hljs-type">BufferedReader(<span class="hljs-keyword">new <span class="hljs-type">InputStreamReader(<span class="hljs-type">System.in));
<span class="hljs-type">String str;
<span class="hljs-keyword">while ((str = stdin.readLine()) != <span class="hljs-literal">null && str.length() != <span class="hljs-number">0)
logger.info(str);
}
}
<h3 id="五sequenceinputstream">五、SequenceInputStream
合并流,有两种构造函数:
- 传入两个InputStream类型的对象
- 传入一个枚举的InputStream类型的对象的集合,将它们合并起来进行操作
合并流以后,操作可以是读写到另一个文件,或者打印到控制台。下面看代码:
+<span class="hljs-keyword">import java.io.FileInputStream;
<span class="hljs-keyword">public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">SequenceInputStreamS <span class="hljs-keyword">extends <span class="hljs-title">IOBaseS {
<span class="hljs-comment">/**
* 合并两个读入的字节流
*
* <span class="hljs-doctag">@throws IOException
*/
<span class="hljs-meta">@Test
<span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-keyword">void <span class="hljs-title">testSequenceInputStream<span class="hljs-params">() <span class="hljs-keyword">throws IOException {
<span class="hljs-comment">// buffer的空间要设定为2的次方才能有效分割,否则会出现某汉字被中途分割显示不完整的情况,
<span class="hljs-keyword">int bufferSize = <span class="hljs-number">16;
InputStream is1 = <span class="hljs-keyword">new FileInputStream(root + <span class="hljs-string">"UME.txt");
InputStream is2 = <span class="hljs-keyword">new FileInputStream(root + <span class="hljs-string">"HongXing.txt");
SequenceInputStream sis = <span class="hljs-keyword">new SequenceInputStream(is1,is2);<span class="hljs-comment">// 构造参数必须为InputStream
<span class="hljs-keyword">byte[] buffer = <span class="hljs-keyword">new <span class="hljs-keyword">byte[bufferSize];
<span class="hljs-keyword">while (sis.read(buffer,bufferSize) != -<span class="hljs-number">1) {
<span class="hljs-comment">// 开始读合并后的数据流,这里可以针对这些数据流做任何操作(读写到任何文件或者打印到控制台)
String str = <span class="hljs-keyword">new String(buffer,bufferSize);
logger.info(str);<span class="hljs-comment">// 打印到控制台
}
is1.close();
is2.close();
sis.close();
}
<span class="hljs-meta">@Test
<span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-keyword">void <span class="hljs-title">testMergeEnumInputStream<span class="hljs-params">() <span class="hljs-keyword">throws IOException {
<span class="hljs-comment">// 实际上它可以合并不同类型数据,然而如果是对象流的话,读取时涉及反序列化工作,要找准与其他数据的分割点,比较麻烦。
InputStream is1 = <span class="hljs-keyword">new FileInputStream(root + <span class="hljs-string">"UME.txt");
InputStream is2 = <span class="hljs-keyword">new FileInputStream(root + <span class="hljs-string">"HongXing.txt");
InputStream is3 = <span class="hljs-keyword">new FileInputStream(root + <span class="hljs-string">"HuaYi.txt");
ArrayList<InputStream> list = <span class="hljs-keyword">new ArrayList<InputStream>();
list.add(is1);
list.add(is2);
list.add(is3);
Iterator<InputStream> it = list.iterator();<span class="hljs-comment">// 传入一个迭代器用于创建枚举
SequenceInputStream sis = <span class="hljs-keyword">new SequenceInputStream(<span class="hljs-keyword">new Enumeration<InputStream>() {
<span class="hljs-meta">@Override
<span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-keyword">boolean <span class="hljs-title">hasMoreElements<span class="hljs-params">() {
<span class="hljs-keyword">return it.hasNext();
}
<span class="hljs-meta">@Override
<span class="hljs-function"><span class="hljs-keyword">public InputStream <span class="hljs-title">nextElement<span class="hljs-params">() {
<span class="hljs-keyword">return it.next();
}
});
<span class="hljs-keyword">int bufferSize = <span class="hljs-number">32;
<span class="hljs-keyword">byte[] buffer = <span class="hljs-keyword">new <span class="hljs-keyword">byte[bufferSize];
<span class="hljs-keyword">while (sis.read(buffer,bufferSize);
logger.info(str);<span class="hljs-comment">// 打印到控制台
}
is1.close();
is2.close();
is3.close();
sis.close();
}
}
<h3 id="六linenumberreader">六、LineNumberReader
LineNumberReader是可以将读入的字符流加入行号,下面看代码。
<span class="hljs-keyword">import java.io.<span class="hljs-type">FileReader;
<span class="hljs-keyword">import java.io.<span class="hljs-type">IOException;
<span class="hljs-keyword">import java.io.<span class="hljs-type">LineNumberReader;
<span class="hljs-keyword">import org.junit.<span class="hljs-type">Test;
public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">LineNumberReaderS <span class="hljs-keyword">extends <span class="hljs-title">IOBaseS {
<span class="hljs-meta">@Test
public void testLineNumberReader() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">FileReader fr = <span class="hljs-keyword">new <span class="hljs-type">FileReader(root + <span class="hljs-string">"UME.txt");
<span class="hljs-comment">// 构造参数为Reader
<span class="hljs-type">LineNumberReader lnr = <span class="hljs-keyword">new <span class="hljs-type">LineNumberReader(fr);
lnr.setLineNumber(<span class="hljs-number">1);<span class="hljs-comment">// 设置行号从2开始。
<span class="hljs-type">String str;
<span class="hljs-keyword">while ((str = lnr.readLine()) != <span class="hljs-literal">null) {
<span class="hljs-comment">// 核心方法:lnr.getLineNumber(),获得行号
logger.info(<span class="hljs-string">"行号:" + lnr.getLineNumber() + <span class="hljs-string">" 内容:" + str);
}
fr.close();
lnr.close();
<span class="hljs-comment">/**
* 输出:
*
* 12:11:27[testLineNumberReader]: 行号:2 内容:举杯邀明月
*
* 12:11:27[testLineNumberReader]: 行号:3 内容:床前明月光
*/
}
}
<h3 id="七数组io-pushbackinputstream-pushbackreader">七、数组IO / PushbackInputStream / PushbackReader
把单一字符推回输入流。这里采用了数组的读取方法,同样分为字节和字符,字节数组流为ByteArrayInputStream,字符数组流为CharArrayReader。
<span class="hljs-keyword">import java.io.<span class="hljs-type">ByteArrayInputStream;
<span class="hljs-keyword">import java.io.<span class="hljs-type">CharArrayReader;
<span class="hljs-keyword">import java.io.<span class="hljs-type">IOException;
<span class="hljs-keyword">import java.io.<span class="hljs-type">PushbackInputStream;
<span class="hljs-keyword">import java.io.<span class="hljs-type">PushbackReader;
<span class="hljs-keyword">import org.junit.<span class="hljs-type">Test;
public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">PushBackS <span class="hljs-keyword">extends <span class="hljs-title">IOBaseS {
<span class="hljs-meta">@Test
public void testPushbackInputStream() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">String content = <span class="hljs-string">"Superman VS Batman";
<span class="hljs-comment">// 构造参数为一个字节数组
<span class="hljs-type">ByteArrayInputStream bais = <span class="hljs-keyword">new <span class="hljs-type">ByteArrayInputStream(content.getBytes());
<span class="hljs-comment">// 构造参数为一个InputStream对象。
<span class="hljs-type">PushbackInputStream pbis = <span class="hljs-keyword">new <span class="hljs-type">PushbackInputStream(bais);
pbis.unread(<span class="hljs-string">"Ssdfasdf".getBytes(),<span class="hljs-number">1);<span class="hljs-comment">// 将S推到源字符串的最前方
<span class="hljs-comment">// pr.unread('S');// 这里的'S'是按照整型值操作
int n;
<span class="hljs-type">String str = <span class="hljs-string">"";
<span class="hljs-keyword">while ((n = pbis.read()) != <span class="hljs-number">-1) {
str += (char) n;
<span class="hljs-comment">// pbis.unread(n);将刚读出来的字符再推回去,就会死循环。
}
logger.info(str);
pbis.close();
bais.close();
<span class="hljs-comment">/**
- 输出:
-
- 12:32:48[testPushBackInputStream]: SSuperman VS Batman
*/
}
<span class="hljs-meta">@Test
<span class="hljs-comment">/**
* PushbackInputStream的字符流版本
*/
public void testPushbackReader() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-comment">// 构造参数为Reader对象,使用字符数组读取
<span class="hljs-type">PushbackReader pr = <span class="hljs-keyword">new <span class="hljs-type">PushbackReader(<span class="hljs-keyword">new <span class="hljs-type">CharArrayReader(<span class="hljs-string">"go go Gan.".toCharArray()));
pr.unread(<span class="hljs-string">"Ssdfasdf".toCharArray(),<span class="hljs-number">1);<span class="hljs-comment">// 将S推到源字符串的最前方
<span class="hljs-comment">// pr.unread('S');// 这里的'S'是按照整型值操作
int n;
<span class="hljs-type">String str = <span class="hljs-string">"";
<span class="hljs-keyword">while ((n = pr.read()) != <span class="hljs-number">-1) {
str += (char) n;
<span class="hljs-comment">// pr.unread(n);将刚读出来的字符再推回去,就会死循环。
}
logger.info(str);
pr.close();
<span class="hljs-comment">/**
* 输出:
*
* 12:45:55[testPushbackReader]: Sgo go Gan.
*/
}
}
<h3 id="八stringwriter-stringreader">八、StringWriter / StringReader
这没什么太多可说的,就是对一个字符串的读写操作,一般很少单独使用,因为直接使用String就可以将他们代替,然而当需要一个流的时候,可以与其他IO流进行组合使用。
+<span class="hljs-keyword">import java.io.<span class="hljs-type">IOException;
public <span class="hljs-class"><span class="hljs-keyword">class <span class="hljs-title">StringIOS <span class="hljs-keyword">extends <span class="hljs-title">IOBaseS {
<span class="hljs-meta">@Test
public void testStringWriter() <span class="hljs-keyword">throws <span class="hljs-type">IOException {
<span class="hljs-type">StringWriter sw = <span class="hljs-keyword">new <span class="hljs-type">StringWriter();
sw.write(<span class="hljs-string">"Hello");
sw.append(<span class="hljs-string">"A");
sw.close();
logger.info(sw);
<span class="hljs-type">StringReader sr = <span class="hljs-keyword">new <span class="hljs-type">StringReader(<span class="hljs-string">"Hello");
int c;
<span class="hljs-type">StringBuilder sb = <span class="hljs-keyword">new <span class="hljs-type">StringBuilder();
<span class="hljs-keyword">while ((c = sr.read()) != <span class="hljs-number">-1) {
sb.append((char)c);
}
logger.info(sb);
<span class="hljs-comment">/**
* Output:
*
* 12:56:47[testStringWriter]: HelloA
*
* 12:56:47[testStringWriter]: Hello
*/
}
}
<h2 id="java-io基础的总结">总结
- 《Java编程思想》
- 《effective java》
- JDK API Document
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|