字节流流总结
字节流流总结
InputStream(抽象类:表示所有字节输入流的父类)
|-FileInputStream(主要用于图像数据之类的原始字节流)
|-FilterInputStream(简单的重写InputStream方法)
|-BufferedInputStream(提供缓冲等功能)
|-PipedInputStream(主要用于多线程)
OutputStream(抽象类:表示所有字节输出流的父类)
|-FileOutputStream(主要用于图像数据之类的原始字节流)
|-FilterOutputStream(简单的重写OutputStream方法)
|-BufferedOutputStream(提供缓冲等功能)
|-PrintStream(打印各种数据值得表示形式)
|-PipedOutputStream(主要用于多线程)
1、关于BufferedInputStream和FileInputStream的区别
在FileInputStream里有一个说明是说此方法将阻塞,意思就是说在你读一个文件输入流的时候,当读到某个位置的时候,如果做一些其他处理(比如说接受一部分字节做一些处理等等)这个时候输入流在什么位置就是什么位置,不会继续往下读,而BufferedInputStream虽然也有一个read方法,但是从名字就可以看出,它带有一个缓冲区,它是一个非阻塞的方法,在你读到某个位置的时候,做一些处理的时候,输入流可能还会继续读入字节,这样就达到了缓冲的效果。
public class SS {
public static void main(String[] args) throws Exception {
File f = new File("d:\\大型数据库文件.mdf");
FileInputStream fis = new FileInputStream(f);
//如果下面的语句使用BufferedOutputStream来修饰则带来更好的性能现。
FileOutputStream fos = new FileOutputStream("e:\\" + f.getName());
int length = 0;
byte[] b = new byte[1024];
while((length = fis.read(b)) != -1)
{
fos.write(b, 0, length);
}
fos.close();
fis.close();
}
}
2、管道流
当需要在两个线程中读写数据的时候,由于线程的并发执行,读写的同步问题可能会发生困难,这时候可以使用管道,管道事实上是一个队列。
管道是由系统维护的一个缓冲区,当然程序员也可以自己直接指定该缓冲区的大小(只需要设置管道流类中的PIPE_SIZE属性的值)。当生产者生产出数据后,只需要将数据写入管道中,消费者只需要从管道中读取所需要的数据。利用管道的这种机制,可以将一个线程的输出结果直接连接到另一个线程的输入端口,实现两者之间的数据直接传送。
线程1
线程2
临时文件
管道
管道的连接:
方法之一是通过构造函数直接将某一个程序的输出作为另一个程序的输入,在定义对象时指明目标管道对象
PipedInputStream pInput=new PipedInputStream();
PipedOutputStream pOutput= new PipedOutputStream(pInput);
方法之二是利用双方类中的任一个成员函数 connect()相连接
PipedInputStream pInput=new PipedInputStream();
PipedOutputStream pOutput= new PipedOutputStream();
pinput.connect(pOutput);
管道的输入与输出:
输出管道对象调用write()成员函数输出数据(即向管道的输入端发送数据);而输入管道对象调用read()成员函数可以读起数据(即从输出管道中获得数据)。这主要是借助系统所提供的缓冲机制来实现的。
实例:Java的管道的输入与输出
import java.io.*;
public class PipedIO //程序运行后将sendFile文件的内容拷贝到receiverFile文件中
{
public static void main(String args[])
{
try
{
//构造读写的管道流对象
PipedInputStream pis=new PipedInputStream();
PipedOutputStream pos=new PipedOutputStream();
//实现关联
pos.connect(pis);
//构造两个线程,并且启动。
new Sender(pos,"c:\\text2.txt").start();
new Receiver(pis,"c:\\text3.txt").start();
}
catch(IOException e)
{
System.out.println("Pipe Error"+ e);
}
}
}
//线程发送
class Sender extends Thread
{
PipedOutputStream pos;
File file;
//构造方法
Sender(PipedOutputStream pos, String fileName)
{
this.pos=pos;
file=new File(fileName);
}
//线程运行方法
public void run()
{
try
{
//读文件内容
FileInputStream fs=new FileInputStream(file);
int data;
while((data=fs.read())!=-1)
{
//写入管道始端
pos.write(data);
}
pos.close();
}
catch(IOException e)
{
System.out.println("Sender Error" +e);
}
}
}
//线程读
class Receiver extends Thread
{
PipedInputStream pis;
File file;
//构造方法
Receiver(PipedInputStream pis, String fileName)
{
this.pis=pis;
file=new File(fileName);
}
//线程运行
public void run()
{
try
{
//写文件流对象
FileOutputStream fs=new FileOutputStream(file);
int data;
//从管道末端读
while((data=pis.read())!=-1)
{
//写入本地文件
fs.write(data);
}
pis.close();
}
catch(IOException e)
{
System.out.println("Receiver Error" +e);
}
}
}
3、PrintStream与PrintWriter
PrintStream在OutputStream基础之上提供了增强的功能,即可以方便地输出各种类型的数据(而不仅限于byte型)的格式化表示形式。PrintStream的方法从不抛出IOEceptin
PrintWriter提供了PrintStream的所有打印方法,其方法也从不抛出IOException。与PrintStream的区别:作为处理流使用时,PrintStream只能封装OutputStream类型的字节流,而PrintWriter既可以封装OutputStream类型的字节流,还能够封装Writer类型的字符输出流并增强其功能。
class IODemo {
public static void main(String[] args) {
try{
FileReader fr=new FileReader("a.txt");
BufferedReader br=new BufferedReader(fr);
FileWriter fw=new FileWriter("33.txt");
PrintWriter pw=new PrintWriter(fw);
String s=br.readLine();
while(null!=s) {
//PrintWriter的println方法 相当于
//BufferedWriter 的write() + newLine()
pw.println(s);
s=br.readLine();
}
br.close();
pw.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
如果将上面的PrintWriter换成PrintStream会报错,因为PrintStream只能封装字节流,不能封装Writer类对象。
注:如果对输出流的格式有特殊要求,使用 PrintStream, PrintWriter显然会比较方便
4、InputStream读取数据问题
关于InputStream.read()
在从数据流里读取数据时,为图简单,经常用InputStream.read()方法。这个方法是从流里每次只读取读取一个字节,效率会非常低。 更好的方法是用InputStream.read(byte[] b)或者InputStream.read(byte[] b,int off,int len)方法,一次读取多个字节。
关于InputStream类的available()方法
要一次读取多个字节时,经常用到InputStream.available()方法,这个方法可以在读写操作前先得知数据流里有多少个字节可以读取。需要注意的是,如果这个方法用在从本
地文件读取数据时,一般不会遇到问题,但如果是用于网络操作,就经常会遇到一些麻烦。比如,Socket通讯时,对方明明发来了1000个字节,但是自己的程序调用available()方法却只得到900,或者100,甚至是0,感觉有点莫名其妙,怎么也找不到原因。其实,这是因为网络通讯往往是间断性的,一串字节往往分几批进行发送。本地程序调用available()方法有时得到0,这可能是对方还没有响应,也可能是对方已经响应了,但是数据还没有送达本地。对方发送了1000个字节给你,也许分成3批到达,这你就要调用3次available()方法才能将数据总数全部得到。
如果这样写代码:
int count = in.available();
byte[] b = new byte[count];
in.read(b);
在进行网络操作时往往出错,因为你调用available()方法时,对发发送的数据可能还没有到达,你得到的count是0。
需要改成这样:
int count = 0;
while (count == 0) {
count = in.available();
}
byte[] b = new byte[count];
in.read(b);
关于InputStream.read(byte[] b)和InputStream.read(byte[] b,int off,int len)这两个方法都是用来从流里读取多个字节的,有经验的程序员就会发现,这两个方法经常读取不到自己想要读取的个数的字节。比如第一个方法,程序员往往希望程序能读取到b.length个字节,而实际情况是,系统往往读取不了这么多。仔细阅读Java的API说明就发现了,这个方法并不保证能读取这么多个字节,它只能保证最多读取这么多个字节(最少1个)。因此,如果要让程序读取count个字节,最好用以下代码:
byte[] b = new byte[count];
int readCount = 0; // 已经成功读取的字节的个数
while (readCount < count) {
readCount += in.read(bytes, readCount, count - readCount);
}
用这段代码可以保证读取count个字节,除非中途遇到IO异常或者到了数据流的结尾(EOFException)