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

  • 相关阅读:
    重新想象 Windows 8 Store Apps (15) 控件 UI: 字体继承, Style, ControlTemplate, SystemResource, VisualState, VisualStateManager
    重新想象 Windows 8 Store Apps (12) 控件之 GridView 特性: 拖动项, 项尺寸可变, 分组显示
    返璞归真 asp.net mvc (10) asp.net mvc 4.0 新特性之 Web API
    与众不同 windows phone (29) Communication(通信)之与 OData 服务通信
    与众不同 windows phone (33) Communication(通信)之源特定组播 SSM(Source Specific Multicast)
    与众不同 windows phone (27) Feature(特性)之搜索的可扩展性, 程序的生命周期和页面的生命周期, 页面导航, 系统状态栏
    与众不同 windows phone (30) Communication(通信)之基于 Socket TCP 开发一个多人聊天室
    返璞归真 asp.net mvc (12) asp.net mvc 4.0 新特性之移动特性
    重新想象 Windows 8 Store Apps (2) 控件之按钮控件: Button, HyperlinkButton, RepeatButton, ToggleButton, RadioButton, CheckBox, ToggleSwitch
    重新想象 Windows 8 Store Apps (10) 控件之 ScrollViewer 特性: Chaining, Rail, Inertia, Snap, Zoom
  • 原文地址:https://www.cnblogs.com/Hermioner/p/9771986.html
Copyright © 2011-2022 走看看