12.4 处理流的用法
Posted weststar
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了12.4 处理流的用法相关的知识,希望对你有一定的参考价值。
一、处理流的用法
下图显示了处理流的功能,它可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入/输出方法,让程序员之关系高级流的操作。
1.1、处理流的注意点
1、使用处理流来包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层的I/O设备、文件交互。
2、实际上我们要识别处理流非常简单,只要流的构造器参数不是一个物理节点,而是已经已经存在的流,那么这种流就一定是处理流;而所有节点流都是直接以物理IO节点作为构造器参数的。
3、程序使用处理流非常简单,通常只需要在创建处理流时传入一个节点流作为构造器参数即可,这样创建的处理流就是包装了该节点流的处理流。
1.2 处理流处理流的优势
1、对于开发人员来说,使用处理流进行输入/输出操作更加简单
2、使用处理流的执行效率更高
下面程序使用pritStream处理流来包装OutputStream,使用处理流后的处理流将在输出时更加方便。
package section4;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class PrintStreamTest
{
public static void main(String[] args)
{
try(
var pos=new FileOutputStream("srcsection4 est.txt");
var ps=new PrintStream(pos))
{
//使用PrintStream执行输出
ps.println("普通字符串");
//直接使用PrintStream输出对象
ps.println(new PrintStreamTest());
}
catch (IOException IOe)
{
IOe.printStackTrace();
}
}
}
test.txt文件的内容:
普通字符串
section4.PrintStreamTest@5f184fc6
上面程序先定义了一个节点输出流FileOutputStream,然后程序使用PrintStream包装了该节点的输出流,最后使用PrintStream输出字符串、输出对象......PrintStream的功能非常强大,前面使用得System.out得类型就是PrintStream。
使用处理流包装底层节点之后,关闭输入/输出流资源时,只要关闭最上层得处理流即可。关闭最上层得处理流时,系统会自动关闭被该处理流包装得节点
二、输入/输出流体系
2.1 输入/输出体系的分类
Java输入/输出流体系提高了近40个类,按功能进行分类,是由一定规律的:
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | ObjectInputStream | ObjectOutputStream | ||
抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
其中红色粗体代表节点流,必须与指定的物理节点相关联;蓝色粗体代表抽象基类,无法直接创建实例。
上面仅仅总结了输入/输出体系中位于java.io包下的流,还有一些诸如AudioInputStream、CipherInputStream、DeflaterInputStream、ZioInpuStream等具有访问音频文件、加密/解密、压缩/解压等功能的字节流,它们具有特殊功能,位于JDK的其他包下。
2.2 文本文件和二进制文件
通常来说,字节流的功能比字符流功能更强大,因为计算机里所有数据都是二进制的,而字节流可以处理所有二进制文件——但问题是,如果使用字节流处理文本文件,则需要将这些字符转化为字节,这就增加了编程的复杂度。所以通常有一个规则:如果输入/输出的内容是文本内容,则应该考虑使用字符流;如果进行输入/输出的内容是二进制内容,则应该考虑使用字节流。
所有能用记事本打开并看到其中的字符内容文件称为文本文件,反之称为二进制文件。
但实质上,计算机里所有文本都是二进制文件,文本文件只是二进制文件一种特例,当二进制文件里的恰好可以被正常接释成字符时,则该二进制文件就变成了文本文件。且希望看到正常的文本文件内容,则必须在打开文件时使用相同的字符集(Windows下简体中文默认使用GBK字符集,而Linux下简体中文默认使用UTF-8字符集)。
2.3 访问数组的流
上表中还列出了一种以数组为物理节点的节点流,字节流以字节数组为节点,字符流以字符数组为节点;这种以数组为物理节点的节点流除了在创建节点流对象需要闯入一个字节数组或则字符数组之外,用法上与文件流的完全类似。与此类似的是,字符流还可以使用字符串作为物理节点,用于实现从字符串读取内容,或将内容写入字符串(用StringBuffer充当字符串)的功能。下面程序示范了使用字符串作为物理节点的字符输入/输出的用法。
package section4;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
public class StringNodeTest
{
public static void main(String[] args)
{
var src="从明天起,做一个幸福的人
"
+"喂马、劈柴,周游世界
"
+"从明天起,关心粮食和蔬菜
"
+"我有一所房子,面朝大海,春暖花开
"
+"从明天起,和每一个亲人通信
"
+"告诉他们我的幸福
";
var cbuffer=new char[32];
var hasRead=0;
try(
var sr=new StringReader(src))
{
//采用循环方式读取的方式读取字符串
while((hasRead=sr.read(cbuffer))>0)
{
System.out.println(new String(cbuffer,0,hasRead));
}
} catch (IOException e) {
e.printStackTrace();
}
try(
//创建StringWriter时,实际上是一个以StringBuffer作为作为输出节点
//下面指定的20就是StringBuffer的初始长度
var sw=new StringWriter(20))
{
//调用StringWriter方法执行输出
sw.write("我有一个美丽的新世界,
");
sw.write("她在远方等我,
");
sw.write("那里有天真的孩子,
");
sw.write("还有姑娘的酒窝
");
System.out.println("----下面是sw字符串节点里的内容----");
System.out.println(sw.toString());
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
上面程序与前面使用的FileReader和FileWriter的程序基本类似,只是在创建StringReader、StringWriter对象时传入的是字符串节点,而不是文件节点。由于String是不可变的字符串对象,所以StringWriter使用StringBuffer作为输出节点。
2.4 访问管道的流
4个访问管道的流:PipedInputStream、PapedOutputStream、PipedReader、PipedWriter,它们都是用于实现进程之间的通信功能,分别是字节输入流、字节输出流、字符输入流、字符输出流。
2.5 缓冲流
4个缓冲流BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter则增加了缓存功能,增加缓存功能可以提高输入、输出效率,增加缓存功能后需要使用flush()才可以将缓存区的内容写入实际的物理节点。
2.6 对象流
对象流主要用于实现对象的序列化。
三、转换流
转换流用于实现将字节流转换为字符流,其中InputStreamReader将字节输出流转换为字符输出流;OutputStreamWriter将字节输出转换为字符输出流。
因为虽然字节流比字符流使用范围更广,但字符流比字节流操作更方便,如果已经有一个流是字符流了,也就是说是一个方便的流,没有必要转换为字节流。反之有一个字节流,但可以确定该字节流的内容都是文本,那么把它转换为字符流处理更加方便,所以只有字节流转换为字符流,没有字符流转换为字节流。
以下以获取键盘输入为例介绍转换流的用法。Java使用System.in作为标准输入,即键盘输入,但这个标准输入流是InputStream的实例,使用不太方便,而且键盘输入的内容都是文本内容,所以可以使用InputStreamReader将其准换为字符输入流,普通的Reader读取内容依然不太方便,可以将普通的Reader再次包装成BufferedReader,利用BufferedReader的ReadeLine()方法可以一次读取一行的内容。如下程序所示:
package section4;
import java.io.*;
public class KeyinTest
{
public static void main(String[] args)
{
try (
// 将Sytem.in对象转换成Reader对象
var reader = new InputStreamReader(System.in);
// 将普通Reader包装成BufferedReader
var br = new BufferedReader(reader))
{
String line = null;
// 采用循环方式来一行一行的读取
while ((line = br.readLine()) != null)
{
// 如果读取的字符串为"exit",程序退出
if (line.equals("exit"))
{
System.exit(1);
}
// 打印读取的内容
System.out.println("输入内容为:" + line);
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
上面程序将System.in标准输入流InputStram字节流转换为字符流输入流,然后再将其包装成BufferedReader,BufferedReader具有缓冲功能,它可以一次读取一行文本——以换行符为标志,如果没有读到换行符,则程序阻塞,等到读到换行符为止。
由于BufferedReader具有一个readLine()方法,可以很方便地读取一行内容,所以经常把读取文本内容的输入流包装成BufferedReader,由于方便地读取输入流的文本内容。
四、推回输入流
在输入/输出流体系中,有两个特殊的流与众不同,就是PushbackInputStream和PushbackReader,它们都提供了如下三个方法:
(1)void unread(byte[]/char[] buf):将一个字节/字符数组内容的推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。
(2)void unread(byte[]/char[] b, int off, int len):将一个字节/字符数组里从off开始,长度为len字节/字符的内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。
(3)void unread(int b) :将一个字节/字符的推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。
这两个推回输入流都带有一个推回缓冲区,当程序员调用这两个推回输入流的unread()方法时,系统将会把指定数组内容推回该缓存区里,而推回输入流每次调用read()方法总是先从推回缓冲区读取,只有完全读取了推回缓冲区里的内容后,但还没有装满read()所需的数组时,才会从原输入流读取。下图显示了这种推回输入流的处理示意图:
在创建一个PushbackInputStream和PushbackReader时需要指定推回缓冲区的大小,默认的推回缓冲区的长度为1,如果程序中推回缓冲区的内容超过了推回缓冲区的大小,将会引发Pushback buffer overflow的IOException异常。
下面程序试图找出程序中"new PushbackReader"字符串,当找到了该字符串后,程序只是打印出目标字符串之前的内容。
package section4;
import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;
public class PushbackTest
{
public static void main(String[] args)
{
try(
//创建一个PushbackReader对象,指定推回缓冲区的长度为64
var pr = new PushbackReader(new FileReader("srcsection4PushbackTest.java"),64)
)
{
var buf=new char[32];
//用于保存上次读取到的字符串
var lastContent="";
var hasRead=0;
while((hasRead=pr.read(buf))>0)
{
//将读取到的内容转换为字符串
var content=new String(buf,0,hasRead);
var tagetIndex=0;
//将上次读取到的字符串发和本次读取到的字符串相连
//查看是否包含目标字符串,如果包含目标字符串
if((tagetIndex=(lastContent+content).indexOf("new PushbackReader"))>0)
{
//将本次内容和上次内容一起推回缓冲区
pr.unread((lastContent+content).toCharArray());
//重新定义一个长度为targetIndex的cahr数组
if((tagetIndex>32))
{
buf=new char[tagetIndex];
}
//再次读取指定长度的内容(就是目标字符串之前的内容)
pr.read(buf,0,tagetIndex);
//打印读取内容
System.out.print(new String(buf,0,tagetIndex));
System.exit(0);
}
else
{
//打印上次读取内容
System.out.print(lastContent);
//将本次内容设为上次读取到的内容
lastContent=content;
}
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
以上是关于12.4 处理流的用法的主要内容,如果未能解决你的问题,请参考以下文章