Java IO流大闯关--IO流的常用实现类

        这个系列的博客主要是对Java高级编程中IO流相关的知识点做一个梳理,内容主要包括File类、IO流原理及流的分类、文件流、缓冲流、转换流、标准输入输出流、打印流、数据流、对象流、随机存取文件流、NIO等几个大的模块。该系列博客的文章如下,推荐按顺序阅读:

Java IO原理与分类

  • I/O是Input/Output的缩写,用于处理设备之间的数据传输,如读/写文件,网络通讯等。Java程序中对于数据的输入/输出操作以“流(Stream)”的方式进行,java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
  • 输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
    输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。
    都是站在程序的角度来判断是输入还是输出的。

按照不同的分类,IO流种类不同,一般常见

  • 按操作数据单位不同分为:字节流(8bit),字符流(16bit)
  • 按数据流的流向不同分为:输入流。输出流
  • 按流的角色的不同分为:节点流,处理流。
    节点流是指直接从数据源或目的地读写数据,处理流是指不直接连接到数据源或目的地,而是直接连接在已经存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
    流的分类

四大基类

        Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
抽象基类.png

InputStream & Reader

        InputStream 和Reader是所有输入流的基类。程序中打开的文件IO资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显示关闭文件IO资源。

抽象类InputStream的重要方法

方法声明方法作用
int read()从输入流中读取数据的下一个字节。返回0到255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,返回值-1。
int read(byte[] b)从此输入流中将最多b.length个字节的数据读入一个byte数组中。如果因为已经到达流末尾而没有可用的字节,则返回值-1。否则以整数形式返回实际读取的字节数。
int read(byte[] b, int off, int len)将输入流中最多len个数据字节读入byte数组。尝试读取len字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值-1。
public void close() throws IOException关闭此输入流并释放与该流关联的所有系统资源。

抽象类Reader的重要方法

方法声明方法作用
int read()从输入流中读取数据的下一个字符。范围在0到65535之间。如果因为已经到达流末尾返回值-1。
int read(char[] cbuf)将字符读入数组。如果因为已经到达流末尾,则返回值-1。否则返回实际读取的字符数。
int read(char[] cbuf, int off, int len)将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果因为流位于文件末尾,则返回值-1,否则返回本次读取的字符数。
public void close() throws IOException关闭此输入流并释放与该流关联的所有系统资源。
OutputStream & Writer

        InputStream 和Reader是所有输出流的基类。

抽象类OutputStream的重要方法

  • void write(int b): 将指定的字节写入此输出流。
  • void write(byte[] b): 将b.length个字节从指定的byte[]数组写入此输出流。write(b)的常规协定是:应该与调用write(b,0,b.length)的效果完全相同。
  • void write(byte[] b, int off, int len): 将指定byte数组中从偏移量off开始的len个字节写入此输出流。
  • public void flush() throws IOException:刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。
  • public void close() throws IOException: 关闭此输出流并释放与该流关联的所有系统资源。

抽象类Writer的重要方法

  • void write(int c): 写入单个字符。
  • void write(char[] cbuf): 写入字符数组
  • void write(char[] cbuf, int off, int len): 写入字符数组的某一部分,从off开始,写入len个字符。
  • void write(String str): 写入字符串。
  • void write(String str, int off, int len): 写入字符串的某一部分
  • void flush():刷新该流的缓冲,则立即将它们写入预期目标。
  • public void close() throws IOException: 关闭输出流并释放与该流相关联的所有系统资源。

节点流(文件流)

读取文件的一般步骤

  1. 建立一个流对象,将已存在的一个文件加载进流。
  2. 创建一个临时存放数据的数组。
  3. 调用流对象的读取方法将流中的数据读入到数组中。
  4. 关闭资源。
/**
仅展示方法体内容
*/
	FileReader fr = null;
	try {
		fr = new FileReader(new File("c:\\test.txt"));
		char[] buf = new char[1024];
		int len;
		while ((len = fr.read(buf)) != -1) {
			System.out.print(new String(buf, 0, len));
		}
		} catch (IOException e) {
			System.out.println("read-Exception :" + e.getMessage());
		} finally {
			if (fr != null) {
				try {
					fr.close();
				} catch (IOException e) {
					System.out.println("close-Exception :" + e.getMessage());
			} 
		} 
	}

写入文件的一般步骤

  1. 建立一个流对象,建立数据存放所在的文件。
  2. 调用流对象的写入方法将数据写入流。
  3. 关闭流资源,并将流中的数据清空到文件中。
	FileWriter fw = null;
	try {
		fw = new FileWriter(new File("Test.txt"));
		fw.write("helloworld");
	} catch (IOException e) {
		e.printStackTrace();
	} finally {
		if (fw != null)
			try {
					fw.close();
				} catch (IOException e) {
					e.printStackTrace();
		} 
	}

注意点:

  • 定义文件路径时,注意:可以用“/”或者“\\”。
  • 在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文件将被覆盖。
  • 如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖,在文件内容末尾追加内容。
  • 在读取文件时,必须保证该文件已存在,否则报异常。
  • 字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
  • 字符流操作字符,只能操作普通文本文件。最常见的文本文件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。

        上面写了字符流的处理文本文件的关键代码,下面给出字节流处理非文本文件和图片,实际上利用字节流可以实现文本文件的复制。代码实际上都是大同小异,记住关键步骤即可,另外实际开发过程中都会使用第三方jar包调用工具方法,因此这里重点还是了解实现原理。

package com.learnjiawa.iostream;

import org.junit.Test;

import java.io.*;

/**
 * 测试FileInputStream和FileOutputStream的使用
 *
 * 结论:
 * 1. 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
 * 2. 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理
 */
public class FileInputOutputStreamTest {

    //使用字节流FileInputStream处理文本文件,可能出现乱码。
    @Test
    public void testFileInputStream() {
        FileInputStream fis = null;
        try {
            //1. 造文件
            File file = new File("hello.txt");

            //2.造流
            fis = new FileInputStream(file);

            //3.读数据
            byte[] buffer = new byte[5];
            int len;//记录每次读取的字节的个数
            while((len = fis.read(buffer)) != -1){

                String str = new String(buffer,0,len);
                System.out.print(str);

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis != null){
                //4.关闭资源
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }

    /*
    实现对图片的复制操作
     */
    @Test
    public void testFileInputOutputStream()  {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //
            File srcFile = new File("爱情与友情.jpg");
            File destFile = new File("爱情与友情2.jpg");

            //
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);

            //复制的过程
            byte[] buffer = new byte[5];
            int len;
            while((len = fis.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos != null){
                //
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }

    //指定路径下文件的复制
    public void copyFile(String srcPath,String destPath){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //
            File srcFile = new File(srcPath);
            File destFile = new File(destPath);

            //
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);

            //复制的过程
            byte[] buffer = new byte[1024];
            int len;
            while((len = fis.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos != null){
                //
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }


    }

    @Test
    public void testCopyFile(){

        long start = System.currentTimeMillis();

        String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";
        String destPath = "C:\\Users\\Administrator\\Desktop\\02-视频.avi";


//        String srcPath = "hello.txt";
//        String destPath = "hello3.txt";

        copyFile(srcPath,destPath);


        long end = System.currentTimeMillis();

        System.out.println("复制操作花费的时间为:" + (end - start));//618

    }

}

缓冲流

        为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8kb)的缓冲区。缓冲流要“套接”相应的节点流之上,根据数据操作单位可以把缓冲流分为BufferedInputStream和BufferedOutputStream,BufferedReader和BufferedWriter。
缓冲流

  • 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区。当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。
  • 向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。
  • 使用方法flush()可以强制将缓冲区的内容全部写入输出流,手动将buffer中内容写入文件。如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出。
  • 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流。
package com.learjiawa.iostream;

import org.junit.Test;

import java.io.*;

/**
 * 处理流之一:缓冲流的使用
 *
 * 1.缓冲流:
 * BufferedInputStream
 * BufferedOutputStream
 * BufferedReader
 * BufferedWriter
 *
 * 2.作用:提供流的读取、写入的速度
 *   提高读写速度的原因:内部提供了一个缓冲区
 *
 * 3. 处理流,就是“套接”在已有的流的基础上。
 */
public class BufferedTest {

    /*
    实现非文本文件的复制
     */
    @Test
    public void BufferedStreamTest() throws FileNotFoundException {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //1.造文件
            File srcFile = new File("爱情与友情.jpg");
            File destFile = new File("爱情与友情3.jpg");
            //2.造流
            //2.1 造节点流
            FileInputStream fis = new FileInputStream((srcFile));
            FileOutputStream fos = new FileOutputStream(destFile);
            //2.2 造缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.复制的细节:读取、写入
            byte[] buffer = new byte[10];
            int len;
            while((len = bis.read(buffer)) != -1){
                bos.write(buffer,0,len);

//                bos.flush();//刷新缓冲区

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            //要求:先关闭外层的流,再关闭内层的流
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

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

            }
            //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
//        fos.close();
//        fis.close();
        }



    }

    //实现文件复制的方法
    public void copyFileWithBuffered(String srcPath,String destPath){
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //1.造文件
            File srcFile = new File(srcPath);
            File destFile = new File(destPath);
            //2.造流
            //2.1 造节点流
            FileInputStream fis = new FileInputStream((srcFile));
            FileOutputStream fos = new FileOutputStream(destFile);
            //2.2 造缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.复制的细节:读取、写入
            byte[] buffer = new byte[1024];
            int len;
            while((len = bis.read(buffer)) != -1){
                bos.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            //要求:先关闭外层的流,再关闭内层的流
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

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

            }
            //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
//        fos.close();
//        fis.close();
        }
    }

    @Test
    public void testCopyFileWithBuffered(){
        long start = System.currentTimeMillis();

        String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";
        String destPath = "C:\\Users\\Administrator\\Desktop\\03-视频.avi";


        copyFileWithBuffered(srcPath,destPath);


        long end = System.currentTimeMillis();

        System.out.println("复制操作花费的时间为:" + (end - start));//618 - 176
    }


    /*
    使用BufferedReader和BufferedWriter实现文本文件的复制

     */
    @Test
    public void testBufferedReaderBufferedWriter(){
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            //创建文件和相应的流
            br = new BufferedReader(new FileReader(new File("dbcp.txt")));
            bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));

            //读写操作
            //方式一:使用char[]数组
//            char[] cbuf = new char[1024];
//            int len;
//            while((len = br.read(cbuf)) != -1){
//                bw.write(cbuf,0,len);
//    //            bw.flush();
//            }

            //方式二:使用String
            String data;
            while((data = br.readLine()) != null){
                //方法一:
//                bw.write(data + "\n");//data中不包含换行符
                //方法二:
                bw.write(data);//data中不包含换行符
                bw.newLine();//提供换行的操作

            }


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            if(bw != null){

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

            }
        }

    }

}

转换流

转换流提供了在字节流和字符流之间的转换。Java API提供了两个转换流:

  • InputStreamReader: 将InputStream转换为Reader
    实现将字节的输入流按指定字符集转换为字符的输入流,需要和InputStream“套接”。
    public InputStreamReader(InputStream, String charsetName)
    当不指定第二个参数时,默认选择utf-8。
  • OutputStreamWriter: 将Writer转换为OutputStream
    实现将字符的输出流按指定字符集转换为字节的输出流,需要和OutputStream“套接”。
    字节流中的数据都是字符时,转成字符流操作更高效。很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能。
    转换流

代码测试

package com.learnjiawa.iostream;

import org.junit.Test;

import java.io.*;

/**
 * 处理流之二:转换流的使用
 * 1.转换流:属于字符流
 *   InputStreamReader:将一个字节的输入流转换为字符的输入流
 *   OutputStreamWriter:将一个字符的输出流转换为字节的输出流
 *
 * 2.作用:提供字节流与字符流之间的转换
 *
 * 3. 解码:字节、字节数组  --->字符数组、字符串
 *    编码:字符数组、字符串 ---> 字节、字节数组
 * 4.字符集
 *ASCII:美国标准信息交换码。用一个字节的7位可以表示。
 *ISO8859-1:拉丁码表。欧洲码表用一个字节的8位表示。
 *GB2312:中国的中文编码表。最多两个字节编码所有字符
 *GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
 *Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表
 *示。
 *UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
 */
public class InputStreamReaderTest {

    /*
    此时处理异常的话,仍然应该使用try-catch-finally
    InputStreamReader的使用,实现字节的输入流到字符的输入流的转换
     */
    @Test
    public void test1() throws IOException {

        FileInputStream fis = new FileInputStream("dbcp.txt");
//        InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集
        //参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集
        InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//使用系统默认的字符集

        char[] cbuf = new char[20];
        int len;
        while((len = isr.read(cbuf)) != -1){
            String str = new String(cbuf,0,len);
            System.out.print(str);
        }

        isr.close();

    }

    /*
    此时处理异常的话,仍然应该使用try-catch-finally

    综合使用InputStreamReader和OutputStreamWriter
     */
    @Test
    public void test2() throws Exception {
        //1.造文件、造流
        File file1 = new File("dbcp.txt");
        File file2 = new File("dbcp_gbk.txt");

        FileInputStream fis = new FileInputStream(file1);
        FileOutputStream fos = new FileOutputStream(file2);

        InputStreamReader isr = new InputStreamReader(fis,"utf-8");
        OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");

        //2.读写过程
        char[] cbuf = new char[20];
        int len;
        while((len = isr.read(cbuf)) != -1){
            osw.write(cbuf,0,len);
        }

        //3.关闭资源
        isr.close();
        osw.close();


    }


}

参考文献

[1]Bruce Eckel.Java编程思想(第4版)[M].机械工业出版社,2008:525-589.

更多

对我的文章感兴趣,持续更新中…

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值