I/O流之缓冲流

时间:2023-02-17 16:32:10


 字节流用于读写诸如图像数据之类的原始字节流。*以字节为单位读取文件,常用于读二进制文件,如图片、声音、影像等文件。
 字符流用于读写诸如文件数据之类的字符流。
IO的缓冲区的存在就是为了提高效率,把要操作的数据放进缓冲区,然后一次性把缓冲区的内容写到目的地,而不是写一次就往目的地写一次.
在这里要注意的是当我们关闭了缓冲区对象实际也关闭了与缓冲区关联的流对象.
BufferWriter
FileWriter fw =null;
try {
fw =new FileWriter("test.txt");
//使用缓冲区必须要与一个流对象相关联
BufferedWriter bw =new BufferedWriter(fw);
bw.write("hello world!");
//使用缓冲区的时候要注意刷新
bw.flush();
//关闭缓冲区的对象,实际上是关闭与它关联的流对象最好放在finally执行
bw.close();
catch (IOException e) {
e.printStackTrace();
}
其实BufferReader也是差不多的,这里就不多讲
FileReader fr =new FileReader("test.txt");
BufferedReader br =new BufferedReader(fr);
String line =null;
//注意readLine方法读取的内容不包括换行符
while((line=br.readLine())!=null){
System.out.println(line);
}
readLine原理:
无论是读一行,获取多个字符,最终都是在硬盘上一个一个读取,所以最终使用的还是read方法一次读一个的方法.

装饰模式:
class Person{  
public void eat(){  
System.out.println("吃饭");  
}  
}  
public class PersonEnhance{  
private Person p;
//把需要增强 的类传进去初始化
public PersonEnhance(Person p){
this.p=p;
}
public void enhanceEat(){
System.out.println("开胃酒");  
p.eat();  
System.out.println("甜点");  
}
}  
装饰模式就是在原有类的基础上把某个方法增强功能  
但是这让我想到了java 的动态代理,他也是在某个方法的基础上增加额外的功能,那么她们有什么区别呢?  
装饰类和被装饰的类是应该继承或实现相同的接口,而java的动态代理不是,
还有一个不同点就是动态代理可以横切多个面,也就是同时对多个方法进行增强.

通过装饰模式和继承的区别发现了程序设计之美,虽然继承也可以增强某个方法,但是它使得类的体系很臃肿,并且可扩展性不好
因为装饰模式中,我们可以把被装饰类的父类当作参数传进装饰类的构造方法内,那么你这一个装饰类就可以应用于这个体系的了,这也是java多态性的好处.
相比较之下使用装饰模式降低了类之间的关系.
装饰类是因为增强了已有的对象,具有的功能和已有的是相同的,是不过提供了更强的功能,所以装饰类和被装饰类通常属于一个体系中的.

在API中可以看到 BufferedReader 类还有一个子类 LineNumberReader

I/O流之缓冲流

通过API对得知,这是一个字符缓冲输出流,该类保持对行号的跟踪,可以通过该类的 setLineNumber(int)  and getLineNumber()  方法分别设置获取行号
例如程序:
publicstaticvoid main(String[] args)throws IOException{
FileReader fr =new FileReader("test.txt");
LineNumberReader lnr =new LineNumberReader(fr);
String num =null;
while((num=lnr.readLine())!=null){
System.out.println(lnr.getLineNumber()+":"+num);
}
}
控制台输出:

I/O流之缓冲流

但是我们也可以改变行号的开始值
publicstaticvoid main(String[] args)throws IOException{
FileReader fr =new FileReader("test.txt");
LineNumberReader lnr =new LineNumberReader(fr);
String num =null;
//设置行号的开始值为100
lnr.setLineNumber(100);
while((num=lnr.readLine())!=null){
System.out.println(lnr.getLineNumber()+":"+num);
}
}
输出结果为:

I/O流之缓冲流

LineNumberReader
EnhanceLineNumberReader
public String readLine() throws IOException {
lineNumber++;
StringBuilder buffer =new StringBuilder();
int i = 0;
while ((i =reader.read()) != -1) {
if ((char) i =='\r') {
continue;
}
if ((char) i =='\n') {
return buffer.toString();
}else {
buffer.append((char) i);
}
}
if (buffer.length() != 0) {
return buffer.toString();
}
returnnull;
}
书出结果是一样的
下面开始学习字节流
通过API文档字节流的*类为InputStream和OutputStream
首先来看一下FileOutputStream和FileInputStream

publicstaticvoid writeData()throws Exception{
OutputStream out =new FileOutputStream("D:\\test2.txt");
out.write("hello inputStream!".getBytes());
}
执行上面代码后,发现在D盘创建了test2.txt文件并且内容是hello inputStream!
从上面可以看出这和字符流是有区别的,因为当我们在使用字符流的时候,如果没有刷新并且没有关闭那么文件内容是空的,而这里刚好相反.
但是最好我们还是调用close方法,关闭资源.提高性能.

下面实现读取操作
public static void readData() throws Exception {
        InputStream is = new FileInputStream("D:\\test2.txt");
        int num = 0;
        while ((num = is.read()) != -1) {
            System.out.println((char) num);
        }
    }
但是这样效率比较低,因为读取一次写一次,我们可以使用缓冲
publicstaticvoid readData2()throws Exception {
    InputStream is =new FileInputStream("D:\\test2.txt");
    int num = 0;
    byte[] buffer =newbyte[1024];
    //把读取到的数据放进字节数组里面
    while ((num = is.read(buffer)) != -1) {
        System.out.println(new String(buffer, 0, num));
    }
}
在InputStream类中有这样一个方法 available ()返回int 他的作用是返回文件内容的长度 那么我们就可以这样读取数据,而不用while循环了
publicstaticvoid readData3()throws Exception {
    InputStream is =new FileInputStream("D:\\test2.txt");
    //返回文件的长度
    int num = is.available();
    把字节数组的长度定义成文件长度,那么这个数组就刚好装下这个文件了
    byte[] buffer =newbyte[num];
    is.read(buffer);
    System.out.println(new String(buffer));
}
但是这样有一个缺陷,如果一个文件非常大,那么这就会出现内存溢出了.所以这是用操作小型 的文件.
练习,复制一份图片:代码片段:
InputStream is = new FileInputStream("D:\\imagetest\\desk.jpg");
        OutputStream os = new FileOutputStream("E:\\desk1.jpg");
        byte[] buffer = new byte[1024];
        int readNum = 0;
        int a=0;
        while((readNum=is.read(buffer))!=-1){
            System.out.println(a++);
            os.write(buffer, 0, readNum);
        }
使用java缓冲输出流
BufferedOutputStream buffOs =new BufferedOutputStream(new FileOutputStream("F:\\KuGou\\baby2 - baby one more time.mp3"));
BufferedInputStream buffIs =new BufferedInputStream(new FileInputStream("F:\\KuGou\\baby - baby one more time.mp3"));
int len = 0;
while((len=buffIs.read())!=-1){
buffOs.write(len);
}
buffOs.close();
buffIs.close();

获取键盘录入:
System.out对应的是标准的输出设备一般指控制台
System.in对应的是标准输入设备:键盘
下面模拟一个键盘录入的功能:
publicstaticvoid main(String[] args)throws IOException {
InputStream is = System.in;
StringBuilder buffer =new StringBuilder();
int i = 0;
while (true) {
i = is.read();
if ('\r' == i)
continue;
if ('\n' == i) {
String value = buffer.toString();
//如果录入的是over那么则退出
if ("over".equals(buffer.toString()))
break;
System.out.println(value);
//清空缓冲区 以免下次录入时不会和前面录入的汇合
buffer.delete(0, buffer.length());
}
else {
buffer.append((char)i);
}
}
}
注意在输入流在读取数据的时候连回车也会读取的.在windows中\r\n代表换行 例如下面简单的程序
InputStream is = System.in;
System.out.println(is.read());
System.out.println(is.read());
控制台输出:
13
10
对于键盘录入功能我们可以使用更加简单的方式:因为他这个功能实际上就是读取一行 的操作:
那么就可以考虑使用readLine方法,然后该方法是字符六BufferedReader的方法
然而InputStream又是字节流.那么怎么办呢?
我们可以使用InputStreamReader类,这个类是字节流到字符流的桥梁,
publicstaticvoid main(String[] args)throws IOException {
InputStream is = System.in;
InputStreamReader isr =new InputStreamReader(is);
BufferedReader br =new BufferedReader(isr);
String line =null;
while((line=br.readLine())!=null){
if(line.equals("over")){
break;
}
System.out.println(line.toUpperCase());
}
}

对应的OutputStreamWriter是字符流向字节流转换的桥梁 也就是读进来的是字符,写进去的是字节,在上面的基础上我们可以这样改写:
publicstaticvoid main(String[] args)throws IOException {
InputStream is = System.in;
InputStreamReader isr =new InputStreamReader(is);
BufferedReader br =new BufferedReader(isr);
OutputStreamWriter osw =new OutputStreamWriter(System.out);
BufferedWriter bw =new BufferedWriter(osw);
String line =null;
while((line=br.readLine())!=null){
if(line.equals("over")){
break;
}
bw.write(line);
//注意使用字符流要注意flush
bw.flush();
//System.out.println(line.toUpperCase());
}
}
但是控制台输出为:

I/O流之缓冲流

发现输出的数据没有换行
当然我们可以在line后面加上\r\n
但是这是不跨品台的
我们可以这样解决:
我们可以使用
BufferedWriter 的newLine方法
在bw.write(line);后面加上bw.newLine(); 即可
总结: 下面总结一下IO的操作规律:
1,明确源和目的:
    源 :输入流,InputStream  Reader
    目的: 输入流 OutpuStream Writer
3当明确体系后,在明确使用哪个具体的对象
    通过设备来进行区分:
        源设备:  存 硬盘 键盘
        目的设备:  内存  硬盘 控制台
按行读取
while ((line = in.readLine())!= null)
    {
     result += "\n" + line;
    }

刷新对象输出流,将任何字节都写入潜在的流中
(些处为ObjectOutputStream) objOutputStm.flush();

请问Java中何时使用flush()刷新输出流呢
就是说在1.你向输出流写入东西之后,执行flush(),
目的是把缓冲区里的东西强行写入输出流.
因为有些带缓冲区的输出流要缓冲区满的时候才输出.
2.关闭流的时候这样也可以防止在关闭流时候抛出某个异常
flush() 是把缓冲区的数据强行输出,
(注意不要和frush()刷新混淆了)   
   主要用在IO中,即清空缓冲区数据,一般在读写流(stream)
的时候,数据是先被读到了内存中,再把数据写到文件中,
当你数据读完的时候不代表你的数据已经写完了,因为还有一
部分有可能会留在内存这个缓冲区中。这时候如果你搜索调用
了close()方法关闭了读写流,那么这部分数据就会丢失,
所以应该在关闭读写流之前先flush()。


Java PrintWriter数据写不进文件去


最近写抓取程序时,用到了printwriter来作为输出流,
将抓取的网页数据写入到文件中,可是后来在过程当中,发
现第一个网页的数据或是抓取少量数据时总是写不进文件当中,
源代码如下:

  printer.append("标题是:" + title + "\n");
//printer是PrintWriter类的一个对象,输出文件指定到d://text//output.txt
  try {
   printer.append("正文是:" + page.getBody().asString()+
 "\n");//将内容输出到文件中。
  } catch (NullPointerException e) {
   e = null;
   System.out.println("该链接没有body体,跳过此页了!");
  }
  printer.append("--------------------------
------------------------------------"
      + "\n");


可是这样的话,会发现当数据量很少时(比如只抓取一个网页)
总是不能将数据写入数据,后来经过反复debug模式的测试,
才最终发现,是因为PrintWriter写入数据时,是用到缓存的,
假如缓存不满时,它是不会自动向文件中写入数据的,只有当
缓冲区满时或是close时或是主动调用flush时才会真正的去写入文件。但是作为抓取程序,我不可能抓一次关一次,肯定是抓完统一的去关闭,这时就是问题的所在。

***printer.flush()这个方法了,不用关闭流,而是调用此方
法后它会自动去清空一下缓存使缓存数据写入文件中。
用流的时候,往往有缓冲区,只有缓冲区慢的时候,才会将
缓冲区里的数据写入到文件中去,只要文件不满或是没有调用
flush或是close方法的话,是没法将不满缓冲区的数据写入
文件中去的

比如64K的内存区域,缓冲区写满了再一次性写入磁盘之中
(或者发送给客户端浏览器)。flush方法一般是程序写入
完成时执行。随后跟着close方法。例如:// 取得输出流。
当然,看具体环境。PrintWriter out = Util.getWriter();
out.println("输出一些信息,可能很多");
out.flush();
out.close();