zoukankan      html  css  js  c++  java
  • 21 Java学习之字节流(InputStream和OutPutStream)

    一.流的分类

    1、从功能上:输入流、输出流

    2、从结构上:字节流、字符流

    3、从来源上:节点流、过滤流

      其中InputStream/OutputStream是为字节流而设计的,Reader/Writer是为字符流而设计的。处理字节或者二进制对象使用字节流,处理字符或者字符串使用字符流。

            在最底层,所有的输入/输出都是字节形式的,基于字符的流只在处理字符的时候提供方便有效的方法。

      节点流是从特定的地方读写的流,例如磁盘或者内存空间,也就是这种流是直接对接目标的。

      过滤流是使用节点流作为输入输出的,就是在节点流的基础上进行包装,新增一些特定的功能。

    二. 什么是输入流和输出流?

            输入和输出流的概念其实都是针对内存(note:或者直接理解为我们的程序)说的。比如:我们常用来打印到控制台的命令System.out.println()它就是out,对于内存来说,把字符串打印到屏幕上就是从内存流向了屏幕的控制台;而等待用户输入命令确实System.in就是从键盘将字符输入到内存中。

    因此:  从内存出:out (输出流)             进入到内存:in(输入流)

    note:因为从内存到屏幕,就是写文件的流向;从硬盘到内存就是读文件的流向。

           如果时网络访问中,我们请求访问网页就是in,因为我们访问页面的时候时需要抓取该页面的一个html文件,因此是从网络到内存的流向;倘若有一个登陆页面,那么就是从内存到服务器了,因为需要从内存写数据到登陆界面,即out.

     三. InputStream

           其中有底色标注的为节点流,无底色标注的为过滤流,其中FilterInputStream在JDK中的定义为:包含其他一些输入流,它将这些流用作其基本数据源,可以直接传输数据或提供一些额外的功能,这个类本身并不经常被我们使用,常用的是它的子类。(note:上面InputStream的实现类没有例完,只是常用的)

    定义了字节输入模式的抽象类,该类提供了三个重载的read方法:

    我们可以看到,三个read方法中,其中有一个是抽象的。那在这里思考这样一个问题:为什么只有第一个是抽象的, 其他两个是具体的?

    因为后面两个方法内部最终会去调用第一个方法,所以在InputStream派生类中只需要重写第一个方法就可以了。在这里可以看到第一个read方法是与具体的I/O设备相关的,需要子类去实现。

     1. 常用的字节输入流

    • InputStream  
    • FileInputStream
    • BufferedInputStream (BufferedInputStream不是InputStream的直接实现子类,是FilterInputStream的子类)

    它们的区别与用途:

    (1)InputStream:是抽象基类。其中定义了几个特别常用的方法,比如read和close方法。

    (2)FileInputStream:主要用来操作文件的输入流。

    (3)BufferedInputStream:一般读取是从硬盘里面读取数据;而带有缓冲区之后,BufferedInputStream是提前将数据封装到了内存中,因此内存中操作数据会更快,从而拥有比非缓冲区更高的效率。

    2. 读写数据的逻辑顺序

    (1)open a stream

    (2)while more information

    (3)read/wirte information

    (4)close the stream

    3. FileInputStream用法

    例1:

     1 package com.test.a;
     2 
     3 import java.io.FileInputStream;
     4 import java.io.IOException;
     5 import java.io.InputStream;
     6 
     7 public class Test {
     8     public static void main(String args[]) throws IOException {
     9         InputStream is = new FileInputStream("C:\Users\hermioner\Desktop\test.txt");
    10         int length = 0;
    11         byte[] buffer = new byte[20];
    12         StringBuffer stringBuffer = new StringBuffer();
    13         while (-1 != (length = is.read(buffer, 0, 20))) {//一个中文字符占两个字节
    14             stringBuffer.append(new String(buffer, 0, length, "GBK"));// WINDOWS中用ANSI代表,
    15             System.out.println(stringBuffer);
    16         }
    17         System.out.println(stringBuffer);
    18         is.close();
    19 
    20     }
    21 }
    22 
    23 
    24 /**
    25  * 1.创建一个文件输入流 is
    26  * 2.创建一个字节数组,用它来存放每次读取到内存中的内容,最多读取20个字节
    27  * 3.必须要加入GBK,否则会乱码。
    28  * 4.读完以后就关闭流is
    29  * 
    30  * */
    View Code
    1 输出:
    2 
    3 
    4 少年强则国强,国强少
    5 少年强则国强,国强少年则更强
    6 少年强则国强,国强少年则更强
    View Code

    test.txt文件中的内容是:

    少年强则国强,国强少年则更强     (note:以ANSI格式保存的)

    例2:

     1 package com.test.a;
     2 
     3 import java.io.FileInputStream;
     4 import java.io.IOException;
     5 
     6 public class TestSerialversionUID {
     7     public static void main(String[] args) throws IOException {
     8         FileInputStream fileInputStream = new FileInputStream("C:\Users\hermioner\Desktop\test.txt");
     9         byte[] buffer = new byte[2];
    10         int readNums = fileInputStream.read(buffer);//代表一次最多读取多少个字节
    11         System.out.println(readNums);
    12         System.out.println(String.valueOf(buffer[1]));// 将buffer数组中的元素转换成字符串输出,字符串输出的值实际上是对应元素的assic码,比如buffer[1]=b,就输出98
    13         String string = new String(buffer, 0, buffer.length, "GBK");// 如果是中文,只能打印出一个中文,英文的话可以打印两个出来
    14         System.out.println(string);
    15         fileInputStream.close();
    16 
    17     }
    18 }
    19 
    20 
    21 2
    22 98
    23 ab
    View Code

    4.BufferedInputStream用法

    BufferedOutputStream(OutputStream out); //使用默认大小、底层字节输出流构造bos。默认缓冲大小是 8192 字节( 8KB )

    BufferedOutputStream(OutputStream out, int size); //使用指定大小、底层字节输出流构造bos

     1 package com.test.a;
     2 
     3 import java.io.BufferedInputStream;
     4 import java.io.FileInputStream;
     5 import java.io.IOException;
     6 
     7 public class Test {
     8     public static void main(String args[]) throws IOException {
     9         FileInputStream fileInputStream=new FileInputStream("C:\Users\hermioner\Desktop\test.txt");
    10         BufferedInputStream bufferedInputStream=new BufferedInputStream(fileInputStream);
    11         byte buffer[]=new byte[20];
    12         int len=0;
    13         while((len=bufferedInputStream.read(buffer))!=-1) {
    14             System.out.println(new String(buffer,0,len,"GBK"));
    15         }
    16         
    17         bufferedInputStream.close();
    18         fileInputStream.close();
    19 
    20     }
    21 }
    22 
    23 
    24 少年强则国强,国强少
    25 年则更强
    View Code

    5. 为什么需要BufferedInputStream?

    BufferedInputStreamBufferedOutputStream这两个类分别是FilterInputStreamFilterOutputStream的子类,作为装饰器子类,使用它们可以防止每次读取/发送数据时进行实际的写操作,代表着使用缓冲区

    BufferedInputStream是一个带有缓冲区的输入流,通常使用它可以提高我们的读取效率,现在我们看下BufferedInputStream的实现原理: 
    BufferedInputStream内部有一个缓冲区,默认大小为8*1024B,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源(譬如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容部分或全部返回给用户.由于从缓冲区里读取数据远比直接从物理数据源(譬如文件)读取速度快,所以BufferedInputStream的效率很高! 
     
    不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!这就是inputstream与bufferedinputstream的区别

    同时正因为它们实现了缓冲功能,所以要注意在使用BufferedOutputStream写完数据后,要调用flush()方法或close()方法,强行将缓冲区中的数据写出。否则可能无法写出数据。与之相似还BufferedReaderBufferedWriter两个类。

    BufferedInputStreamBufferedOutputStream类就是实现了缓冲功能的输入流/输出流。使用带缓冲的输入输出流,效率更高,速度更快。

    (1)何时用flush

    那么什么时候flush()才有效呢?
    答案是:当OutputStream是BufferedOutputStream时。

    当写文件需要flush()的效果时,需要
    FileOutputStream fos = new FileOutputStream(“c:a.txt”);
    BufferedOutputStream bos = new BufferedOutputStream(fos);
    也就是说,需要将FileOutputStream作为BufferedOutputStream构造函数的参数传入,然后对BufferedOutputStream进行写入操作,才能利用缓冲及flush()。

    查看BufferedOutputStream的源代码,发现所谓的buffer其实就是一个byte[]。
    BufferedOutputStream的每一次write其实是将内容写入byte[],当buffer容量到达上限时,会触发真正的磁盘写入。
    而另一种触发磁盘写入的办法就是调用flush()了。

    1.BufferedOutputStream在close()时会自动flush
    2.BufferedOutputStream在不调用close()的情况下,缓冲区不满,又需要把缓冲区的内容写入到文件或通过网络发送到别的机器时,才需要调用flush.

    我的理解:往磁盘写数据有两种情况:一种是从流中而来的数据已经写满了缓存,这个是时候buffer会自动写入磁盘;另外一种是通过调用flush强行把缓存中的数据写入到磁盘。   并且我们在程序中定义了一个一定容量的byte数组,用它来装载从流而来的数据,这实际上表示每次向stream表示的缓存中放入了多少数据,多次的累加来达到缓存的最大值以后,缓存就会自动像磁盘写入了。我们实际上可以使用默认缓存或者自定义缓存容量大小的。可以用我们自定义的byte数组来记录读取了多少数据,看看缓存是否快要达到了等操作,它相当于是一个像缓存中一次次送数据的。(实际上送数据的是流)


    6. 节点流和处理流的关闭顺序

    问题:

    (1)JAVA的IO流使用了装饰模式,关闭最外面的流的时候会自动调用被包装的流的close()方吗?  是

    (2)如果按顺序关闭流,是从内层流到外层流关闭还是从外层到内层关闭?  (从外层到内层:即buffered--->非buffered)

    问题一解释:

     1 如下例子代码:
     2   FileInputStream is = new FileInputStream(".");   
     3   BufferedInputStream bis = new BufferedInputStream(is);  
     4   bis.close();
     5 
     6  
     7 从设计模式上看:
     8 java.io.BufferedInputStream是java.io.InputStream的装饰类。
     9 BufferedInputStream装饰一个 InputStream 使之具有缓冲功能,is要关闭只需要调用最终被装饰出的对象的 close()方法即可,因为它最终会调用真正数据源对象的 close()方法。
    10  
    11 BufferedInputStream的close方法中对InputStream进行了关闭,下面是jdk中附带的源代码:
    12  java.io.BufferedInputStream的api:
    13 close
    14 public void close()throws IOException 关闭此输入流并释放与该流关联的所有系统资源。
    15 
    16  public void close() throws IOException {
    17         byte[] buffer;
    18         while ( (buffer = buf) != null) {
    19             if (bufUpdater.compareAndSet(this, buffer, null)) {
    20                 InputStream input = in;
    21                 in = null;
    22                 if (input != null)
    23                     input.close();
    24                 return;
    25             }
    26             // Else retry in case a new buf was CASed in fill()
    27         }
    28     }
    View Code

    因此,可以只调用外层流的close方法关闭其装饰的内层流,验证例子:

     1 public static void main(String[] args) throws Exception {
     2         FileOutputStream fos = new FileOutputStream("d:\a.txt");
     3         OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
     4         BufferedWriter bw = new BufferedWriter(osw);
     5         bw.write("java IO close test");
     6          
     7         bw.close();
     8       
     9      }
    10 
    11 验证ok
    View Code

    问题二解释:

     1 如下例子: 
     2 public static void main(String[] args) throws Exception {
     3         FileOutputStream fos = new FileOutputStream("d:\a.txt");
     4         OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
     5         BufferedWriter bw = new BufferedWriter(osw);
     6         bw.write("java IO close test");
     7        
     8         //从内带外顺序顺序会报异常
     9         fos.close();
    10         osw.close();
    11         bw.close();
    12 
    13      }
    14 报出异常:
    15 
    16 Exception in thread "main" java.io.IOException: Stream closed
    17     at sun.nio.cs.StreamEncoder.ensureOpen(StreamEncoder.java:26)
    18     at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:99)
    19     at java.io.OutputStreamWriter.write(OutputStreamWriter.java:190)
    20     at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:111)
    21     at java.io.BufferedWriter.close(BufferedWriter.java:246)
    22     at com.my.test.QQ.main(QQ.java:22)
    23 
    24 如下例子:
    25 
    26  public static void main(String[] args) throws Exception {
    27         FileOutputStream fos = new FileOutputStream("d:\a.txt");
    28         OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
    29         BufferedWriter bw = new BufferedWriter(osw);
    30         bw.write("java IO close test");
    31 
    32         // 从外到内顺序关闭ok
    33         bw.close();
    34         osw.close();
    35         fos.close();
    36     }
    37 
    38 验证ok
    View Code

    一般情况下是:先打开的后关闭,后打开的先关闭

    另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b

    例如处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b

    当然完全可以只关闭处理流,不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法

    如果将节点流关闭以后再关闭处理流,会抛出IO异常;

     四. OutPutStream

    1. 常用的字节输出流

    • OutputStream
    • FileoutputStream
    • BufferedOutputStream (BufferedOutputStream 不是OutputStream的直接实现子类,是FilterOutputStream的子类)

    它们的区别与用途:

    (1)OutputStream: 字节输出流的基类。在这个类中常用的方法有wirte、close和flush(即刷新输出流,把数据马上写到输出流中)

    (2)FileOutputStream:用于写文件的输出流

    (3)BufferedOutputStream:同BufferedInputStream,可以提高效率。

    2. FileOutputStream的用法

     1 package com.test.a;
     2 
     3 import java.io.FileOutputStream;
     4 import java.io.IOException;
     5 
     6 public class Test {
     7     public static void main(String args[]) throws IOException {
     8         FileOutputStream fileOutputStream=new FileOutputStream("C:\Users\hermioner\Desktop\test.txt",true);
     9         String string="为了中国梦,为了民族的伟大复兴,为了老百姓的幸福";
    10         byte b[]=string.getBytes();
    11         fileOutputStream.write(b);
    12         fileOutputStream.close();
    13     }
    14 }
    15 
    16 
    17 
    18 //note:上面的true代表在原来路径下文本末尾追加字段,如果不写或者false则表示在文件的开头写,即完成了覆盖。如果上面给定的路径不存在,则会新创建。
    View Code

    3.BufferedOutputStream的用法

     1 public class Test {
     2     public static void main(String args[]) throws IOException {
     3         FileOutputStream fileOutputStream=new FileOutputStream("C:\Users\hermioer\Desktop\test.txt");
     4         BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(fileOutputStream);
     5         String string="为了中国梦,为了民族的伟大复兴,为了老百姓的幸福";
     6         byte b[]=string.getBytes();
     7         bufferedOutputStream.write(b);
     8         bufferedOutputStream.close();//BufferedOoutputStream中实际上没有close,调用的是父类的close。并且close方法中还调用了flush方法
     9     }
    10 }
    View Code

    4. 补充DataInputStream和DataOutputStream

     1 package com.test.a;
     2 
     3 import java.io.DataInputStream;
     4 import java.io.DataOutputStream;
     5 import java.io.FileInputStream;
     6 import java.io.FileOutputStream;
     7 import java.io.IOException;
     8 
     9 public class TestSerialversionUID {
    10     public static void main(String[] args) throws IOException {
    11         FileOutputStream out = new FileOutputStream("C:\Users\hermioner\Desktop\test.txt");
    12            //DataOutputStream可以将各种各样的数据转换为二进制
    13            DataOutputStream dout = new DataOutputStream(out);
    14            String name = "Tom";
    15            int num = 100;
    16            float f = 100.8f;
    17            double d = 10088.00d;
    18            char c='a';
    19            //为了让解析工具知道这个字符串有多长,还会在字符串前面加前缀。
    20            //表示这个字符串有多长,8个字节的字符串,还有两个字节的前缀,会写入10个字节
    21            dout.writeUTF(name);
    22            //4个字节
    23            dout.writeInt(num);
    24            //4个字节
    25            dout.writeFloat(f);
    26            //8个字节
    27            dout.writeDouble(d);
    28            dout.writeChar(c);
    29            FileInputStream in = new FileInputStream("C:\Users\hermioner\Desktop\test.txt");
    30            DataInputStream din = new DataInputStream(in);
    31            String readUTF = din.readUTF();
    32            System.out.println(readUTF);
    33            int readInt = din.readInt();
    34            System.out.println(readInt);
    35            float readFloat = din.readFloat();
    36            System.out.println(readFloat);
    37            double readDouble = din.readDouble();
    38            System.out.println(readDouble);
    39            char readChar=din.readChar();
    40            System.out.println(readChar);
    41 
    42     }
    43 }
    44 
    45 
    46 Tom
    47 100
    48 100.8
    49 10088.0
    50 a
    View Code
    • DataInputStream是数据输入流,读取的是java的基本数据类型。

    • FileInputStream是从文件系统中,读取的单位是字节。

     

    参考文献:

    https://www.cnblogs.com/dongguacai/p/5658388.html

    https://www.cnblogs.com/progor/p/9357676.html

    https://blog.csdn.net/zhaoyanjun6/article/details/54894451

    https://www.cnblogs.com/qqzy168/p/3670915.html

    https://blog.csdn.net/android_zyf/article/details/68343953

  • 相关阅读:
    石油采集
    石油采集
    Redis 笔记与总结7 PHP + Redis 信息管理系统(用户信息的增删改查)
    数据分析电子商务B2C全流程_数据分析师
    数据分析电子商务B2C全流程_数据分析师
    数据挖掘中分类算法小结_数据分析师
    大数据分析或提升企业税务职能价值
    大数据可视化必须避免的三种常见错误
    大数据可视化必须避免的三种常见错误
    数据分析帮你预知商机
  • 原文地址:https://www.cnblogs.com/Hermioner/p/9771986.html
Copyright © 2011-2022 走看看