zoukankan      html  css  js  c++  java
  • Java IO流

    Java流类图结构:

    这里写图片描述

    流的概念和作用

    流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

    IO流的分类

    根据处理数据类型的不同分为:字符流和字节流
    根据数据流向不同分为:输入流和输出流

    字符流和字节流

    字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。

    字节流和字符流的区别:

    • 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
    • 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
    • 字节流:一次读入或读出是8位二进制。
    • 字符流:一次读入或读出是16位二进制。

    设备上的数据无论是图片或者视频,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。

    结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。

    输入流和输出流

    输入流只能进行读操作,输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。

    输入字节流 InputStream

    • InputStream 是所有的输入字节流的父类,它是一个抽象类。
    • ByteArrayInputStreamStringBufferInputStreamFileInputStream 是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。
    • PipedInputStream 是从与其它线程共用的管道中读取数据,与Piped 相关的知识后续单独介绍。
    • ObjectInputStream 和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角)。

    输出字节流 OutputStream

    • OutputStream 是所有的输出字节流的父类,它是一个抽象类。
    • ByteArrayOutputStreamFileOutputStream 是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。
    • PipedOutputStream 是向与其它线程共用的管道中写入数据。
    • ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流。

    总结:

    输入流:InputStream或者Reader:从文件中读到程序中;
    输出流:OutputStream或者Writer:从程序中输出到文件中;

    节点流

    节点流:直接与数据源相连,读入或读出。
    直接使用节点流,读写不方便,为了更快的读写文件,才有了处理流。

    这里写图片描述

    常用的节点流

    • 父 类 :InputStreamOutputStreamReaderWriter
    • 文 件 :FileInputStreamFileOutputStreamFileReaderFileWriter 文件进行处理的节点流
    • 数 组 :ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)
    • 字符串 :StringReaderStringWriter 对字符串进行处理的节点流
    • 管 道 :PipedInputStreamPipedOutputStreamPipedReadePipedWriter 对管道进行处理的节点流

    处理流

    处理流和节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流

    如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

    这里写图片描述

    常用的处理流

    • 缓冲流:BufferedInputStreanBufferedOutputStreamBufferedReaderBufferedWriter 增加缓冲功能,避免频繁读写硬盘。
    • 转换流:InputStreamReader OutputStreamReader实现字节流和字符流之间的转换。
    • 数据流:DataInputStreamDataOutputStream 等-提供将基础数据类型写入到文件中,或者读取出来。

    转换流

    InputStreamReader OutputStreamWriterInputStreamOutputStream作为参数,实现从字节流到字符流的转换。

    构造函数

    InputStreamReader(InputStream);        //通过构造函数初始化,使用的是本系统默认的编码表GBK。
    InputStreamWriter(InputStream,String charSet);   //通过该构造函数初始化,可以指定编码表。
    OutputStreamWriter(OutputStream);      //通过该构造函数初始化,使用的是本系统默认的编码表GBK。
    OutputStreamwriter(OutputStream,String charSet);   //通过该构造函数初始化,可以指定编码表。
    

    缓冲流-BufferedInputStream、BufferedOutputStream

    概念:

    有了InputStream为什么还要有BufferedInputStream?

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

    我们有必要知道不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!

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

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

    总结:

    • BufferedInputStream 是缓冲输入流。它继承于FilterInputStream。
    • BufferedInputStream 的作用是为另一个输入流添加一些功能,例如,提供“缓冲功能”以及支持mark()标记和reset()重置方法。
    • BufferedInputStream 本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。

    注意点:

    那么什么时候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()了。

    BufferedOutputStream在close()时会自动flush

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

    Code:

    package com.qhong.basic.ioStream;
    
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    /**
     * Created by qhong on 2019/2/1 16:51
     **/
    public class BufferedInputStreamTest {
    	public static void main(String[] args) throws IOException {
    
    		String filePath = "E:/abc.txt" ;
    		String filePath2 = "E:/def.txt" ;
    		File file = new File( filePath ) ;
    		File file2 = new File( filePath2 ) ;
    		copyFile( file , file2 );
    
    	}
    
    	/**
    	 * 复制文件
    	 * @param oldFile
    	 * @param newFile
    	 */
    	public static void copyFile( File oldFile , File newFile){
    		InputStream inputStream = null ;
    		BufferedInputStream bufferedInputStream = null ;
    
    		OutputStream outputStream = null ;
    		BufferedOutputStream bufferedOutputStream = null ;
    
    		try {
    			inputStream = new FileInputStream( oldFile ) ;
    			bufferedInputStream = new BufferedInputStream( inputStream ) ;
    
    			outputStream = new FileOutputStream( newFile ) ;
    			bufferedOutputStream = new BufferedOutputStream( outputStream ) ;
    
    			byte[] b=new byte[1024];   //代表一次最多读取1KB的内容
    
    			int length = 0 ; //代表实际读取的字节数
    			int i=1;
    			while( (length = bufferedInputStream.read( b ) )!= -1 ){
    				System.out.println("id:"+i+"   length:"+length);
    				//length 代表实际读取的字节数
    				bufferedOutputStream.write(b, 0, length );
    				i++;
    			}
    			//缓冲区的内容写入到文件
    			bufferedOutputStream.flush();
    		} catch (FileNotFoundException e) {
    			e.printStackTrace();
    		}catch (IOException e) {
    			e.printStackTrace();
    		}finally {
    			if( bufferedOutputStream != null ){
    				try {
    					bufferedOutputStream.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    
    			if( bufferedInputStream != null){
    				try {
    					bufferedInputStream.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    
    			if( inputStream != null ){
    				try {
    					inputStream.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    
    			if ( outputStream != null ) {
    				try {
    					outputStream.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    	}
    }
    

    Output:

    id:1   length:1024
    id:2   length:1024
    //....省略
    id:33   length:1024
    id:34   length:1024
    id:35   length:1024
    id:36   length:1024
    id:37   length:521
    

    计算,1024*36+521=37385,正好一致

    如何正确的关闭流

    上面的代码时这样关闭流的

    if( bufferedOutputStream != null ){
    				try {
    					bufferedOutputStream.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    
    			if( bufferedInputStream != null){
    				try {
    					bufferedInputStream.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    
    			if( inputStream != null ){
    				try {
    					inputStream.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    
    			if ( outputStream != null ) {
    				try {
    					outputStream.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    

    在处理流关闭完成后,我们还需要关闭节点流吗?

    让我们带着问题去看源码:
    - bufferedOutputStream.close();

      /**
         * Closes this input stream and releases any system resources
         * associated with the stream.
         * Once the stream has been closed, further read(), available(), reset(),
         * or skip() invocations will throw an IOException.
         * Closing a previously closed stream has no effect.
         *
         * @exception  IOException  if an I/O error occurs.
         */
        public void close() throws IOException {
            byte[] buffer;
            while ( (buffer = buf) != null) {
                if (bufUpdater.compareAndSet(this, buffer, null)) {
                    InputStream input = in;
                    in = null;
                    if (input != null)
                        input.close();
                    return;
                }
                // Else retry in case a new buf was CASed in fill()
            }
        }
    

    close()方法的作用
    1、关闭输入流,并且释放系统资源
    2、BufferedInputStream装饰一个 InputStream 使之具有缓冲功能,要关闭只需要调用最终被装饰出的对象的 close()方法即可,因为它最终会调用真正数据源对象的 close()方法。因此,可以只调用外层流的close方法关闭其装饰的内层流。

    那么如果我们想逐个关闭流,我们该怎么做?

    答案是:

    先关闭外层流,再关闭内层流。一般情况下是:先打开的后关闭,后打开的先关闭;

    另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b

    看懂了怎么正确的关闭流之后,那么我们就可以优化上面的代码了,只关闭外层的处理流。

    	if( bufferedOutputStream != null ){
    				try {
    					bufferedOutputStream.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    			if( bufferedInputStream != null){
    				try {
    					bufferedInputStream.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    

    缓冲流-BufferedReader、BufferedWriter

    跟上面的BufferedInputStream,BufferedOutputStream差不多,一个字节,一个字符

    code:

    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.Reader;
    import java.io.Writer;
    
    /**
     * Created by qhong on 2019/2/1 17:24
     **/
    public class BufferedReaderTest {
    
    	public static void main(String[] args) {
    
    		String filePath = "E:/abc.txt" ;
    		String filePath2 = "E:/123.txt" ;
    
    		File file = new File( filePath ) ;
    		File file2 = new File( filePath2 ) ;
    		copyFile( file , file2 );
    	}
    
    	private static void copyFile( File oldFile , File newFile ){
    		Reader reader = null ;
    		BufferedReader bufferedReader = null ;
    
    		Writer writer = null ;
    		BufferedWriter bufferedWriter  = null ;
    		try {
    			reader = new FileReader( oldFile ) ;
    			bufferedReader = new BufferedReader( reader ) ;
    
    			writer = new FileWriter( newFile ) ;
    			bufferedWriter = new BufferedWriter( writer ) ;
    
    			String result = null ; //每次读取一行的内容
    			int i=1;
    			while (  (result = bufferedReader.readLine() ) != null ){
    				System.out.println("num:"+i+"   result:"+result.length());
    				bufferedWriter.write( result );  //把内容写入文件
    				bufferedWriter.newLine();  //换行,result 是一行数据,所以每写一行就要换行
    				i++;
    			}
    
    			bufferedWriter.flush();  //强制把数组内容写入文件
    
    		} catch (FileNotFoundException e) {
    			e.printStackTrace();
    		}catch (IOException e) {
    			e.printStackTrace();
    		}finally {
    			try {
    				bufferedWriter.close();  //关闭输出流
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    
    			try {
    				bufferedReader.close();  //关闭输入流
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    

    Output:

    num:1   result:5
    num:2   result:4
    num:3   result:3
    num:4   result:2
    num:5   result:1
    //....省略 
    num:1023   result:9
    num:1024   result:0
    num:1025   result:2
    num:1026   result:2
    num:1027   result:3
    num:1028   result:16
    num:1029   result:16
    num:1030   result:0
    

    BufferedReader是对文档一行一行读取的

    转换流-InputStreamReader、OutputStreamWriter

    概念:

    • InputStreamReader 是字符流Reader的子类,是字节流通向字符流的桥梁。你可以在构造器重指定编码的方式,如果不指定的话将采用底层操作系统的默认编码方式,例如 GBK 等。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。一次只读一个字符。
    • OutputStreamWriter 是字符流Writer的子类,是字符流通向字节流的桥梁。每次调用 write()方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。一次只写一个字符。

    code

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    
    /**
     * Created by qhong on 2019/2/1 17:51
     **/
    public class InputStreamReaderTest {
    
    	public static void main(String[] args) {
    		String filePath = "E:/abc.txt";
    		String filePath2 = "E:/ghi.txt";
    		File file = new File(filePath);
    		File file2 = new File(filePath2);
    		copyFile(file, file2);
    	}
    
    	private static void copyFile(File oldFile, File newFile) {
    		InputStream inputStream = null;
    		InputStreamReader inputStreamReader = null;
    		OutputStream outputStream = null;
    		OutputStreamWriter outputStreamWriter = null;
    		try {
    			inputStream = new FileInputStream(oldFile); //创建输入流
    			inputStreamReader = new InputStreamReader(inputStream); //创建转换输入流
    			outputStream = new FileOutputStream(newFile); //创建输出流
    			outputStreamWriter = new OutputStreamWriter(outputStream); //创建转换输出流
    			int result = 0;
    			int i=1;
    			while ((result = inputStreamReader.read()) != -1) {  //一次只读一个字符
    				System.out.println("num:"+i +"  result:"+result);
    				outputStreamWriter.write(result); //一次只写一个字符
    				i++;
    			}
    			outputStreamWriter.flush();  //强制把缓冲写入文件
    		} catch (FileNotFoundException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			if (outputStreamWriter != null) {
    				try {
    					outputStreamWriter.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    			if (inputStreamReader != null) {
    				try {
    					inputStreamReader.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    	}
    }
    

    output:

    num:1  result:97
    num:2  result:97
    num:3  result:97
    //... 省略
    num:24145  result:26512
    num:24146  result:23454
    num:24147  result:25112
    num:24148  result:13
    num:24149  result:10
    num:24150  result:13
    num:24151  result:10
    

    可以看出该abc.txt为24151个字符,上一个代码里面看它的字节数为37385

    ByteArrayInputStream、ByteArrayOutputStream

    ByteArrayInputStream 可以将字节数组转化为输入流 。
    ByteArrayOutputStream可以捕获内存缓冲区的数据,转换成字节数组。

    ByteArrayInputStream code:

    public class ByteArrayInputStreamTest {
    	public static void main(String[] args) {
    		String mes = "hello,world" ;
    		byte[] b = mes.getBytes() ;
    
    		ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( b ) ;
    		int result = -1  ;
            int i=1;
    		while( ( result = byteArrayInputStream.read() ) != -1){
    			System.out.println("num:"+i +"   result:"+(char)result);
    			i++;
    		}
    
    		try {
    			byteArrayInputStream.close();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    Output:

    num:1   result:h
    num:2   result:e
    num:3   result:l
    num:4   result:l
    num:5   result:o
    num:6   result:,
    num:7   result:w
    num:8   result:o
    num:9   result:r
    num:10   result:l
    num:11   result:d
    

    ByteArrayOutputStream code:

    public class ByteArrayOutputStreamTest {
    
    	public static void main(String[] args) {
    		String mes = "你好,world" ;
    		byte[] b = mes.getBytes() ;
    
    		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream() ;
    		try {
    			byteArrayOutputStream.write( b );
    
    			FileOutputStream fileOutputStream = new FileOutputStream( new File( "E:/1234.txt" ) ) ;
    
    			byteArrayOutputStream.writeTo( fileOutputStream ) ;
    
    			fileOutputStream.flush();
    		} catch (FileNotFoundException e) {
    			e.printStackTrace();
    		}catch (IOException e) {
    			e.printStackTrace();
    		}finally{
    			try {
    				byteArrayOutputStream.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    

    参考:

    Java IO流学习总结一:输入输出流

    java基础io流——OutputStream和InputStream的故事(温故知新)

    Java IO流学习总结三:缓冲流-BufferedInputStream、BufferedOutputStream

    Java IO流学习总结四:缓冲流-BufferedReader、BufferedWriter

    Java IO流列表

  • 相关阅读:
    155. 最小栈
    160. 相交链表
    PAT 1057 Stack
    PAT 1026 Table Tennis
    PAT 1017 Queueing at Bank
    PAT 1014 Waiting in Line
    PAT 1029 Median
    PAT 1016 Phone Bills
    PAT 1010 Radix
    PAT 1122 Hamiltonian Cycle
  • 原文地址:https://www.cnblogs.com/hongdada/p/10346406.html
Copyright © 2011-2022 走看看