[Java I/O 流] 带你一起玩转输入/输出流(下)

写在前面

  • 阅读本篇前请先阅读<[Java I/O 流] 带你一起玩转输入/输出流(上)>https://blog.csdn.net/SolarL/article/details/88976894
  • IO流是用来处理设备之间的数据传输,Java对数据的操作是通过流的方式,流按流向分为两种:输入流,输出流。
  • 流按操作类型分为两种:
  • 字节流 : 字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的;
  • 字符流 : 字符流只能操作纯字符数据,比较方便。

1. I/O 流体系中常用类

<1> 字节流

  • 字节流的抽象父类为: InputStream、OutputStream;
  • 若要向对文件进行操作,显然抽象类是无法实现的,只能使用其子类 FileInputStream 和 FileOutPutStream来实例化对象。

<2> 字符流

  • 字符流的抽象父类为:Reader、Writer
  • 同字节流,若要向对文件进行操作,显然抽象类是无法实现的,只能使用其子类 FileReader 和 FileWriter 来实例化对象。

<3> IO 流书写规范要求
这里有必要敲黑板强调: 所有的流操作,使用前,需要导入IO 包中的类,使用时,需进行IO 异常处理,使用后,需显式地关闭流来释放资源。
JDK 7 以后,只要实现AutoCloseable接口的类,都可以通过自动关闭资源的try 语句来关闭这些流。

<4> 输入输出流体系中常用流的分类
在这里插入图片描述
注:上表内容参考自《疯狂Java 讲义》,表中红色粗体标出的类代表节点流,必须直接与指定的物理节点关联;蓝色斜体标出的类代表抽象基类,无法直接创建实例。

2. InputStream 类和 Reader 类

  • InputStream 类和 Reader 类都是抽象类,本身无法创建对象,可以通过它们用于读取文件的输入流FileInputStream 和 FileReader 来实现,但它们都是节点流,需要需要与指定的文件进行关联,这就我们之前所说的File 类密不可分。
  • 通过API 对比两个类发现,这两个类的功能基本是一样的,都通过 read() 方法进行输入和读操作,直到返回-1,停止操作。
//创建一个文件输入流对象,并关联aaa.txt
FileInputStream fis = new FileInputStream( "aaa.txt" );
//定义变量,记录每次读到的字节
int b;
while((b=fis.read()) != -1){
     //打印每一个字节
     System.out.println(b);    
}

3. OutputStream 类和 Writer 类

  • OutputStream 类和 Writer 类都是抽象类,同样无法创建对象,可以通过它们用于读取文件的输入流FileOutputStream 和 FileWriter 来实现,但它们都是节点流,需要需要与指定的文件进行关联,这就我们之前所说的File 类密不可分。
  • OutputStream 与Writer 也非常相似,都是通过 write() 方法进行输出和读操作。
  • OutputStream类的write()方法在这里插入图片描述
  • Writer类的write()方法在这里插入图片描述
 FileWriter fr = new FileWriter( "bbb.txt" );
            fr.write( 1 );
            fr.write( "string" );
            fr.write( new char[]{'a','b','c'} );

4. 字节流的多种拷贝方式及异常处理

<1> FileInputStream读取,FileOutputStream写出,字节流一次读写一个字节复制音频(效率太低)

public class Main {
    public static void main(String[] args) throws IOException {

        FileInputStream fis = new FileInputStream( "XXX.mp3" );    //创建输入流对象,关联XXX.mp3
        FileOutputStream fos = new FileOutputStream( "copy.mp3" );//创建输出流对象,关联copy.mp3

        int b;
        while ((b = fis.read()) != -1) {
            fos.write( b );
        }

        fis.close();   //关闭流
        fos.close();   //关闭流
    }
}

**<2> 小数组读取(推荐)

  • write(byte[] b)
  • write(byte[] b, int off, int len)写出有效的字节个数
  • 此处流的处理为 JDK 1.6 及之前的处理方法,try finally嵌套的目的是能关一个尽量关一个。也可以同第三种一样采用try 语句实现自动关闭流。
import java.io.*;
import java.nio.file.Paths;

/**
 * @Auther: SolarL
 * @Date: 2019/4/3
 * @Description: com.sunlong.file
 * @version: 1.0
 */
public class Main {
    public static void main(String[] args) throws IOException {
        //异常处理(小数组拷贝)
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            File srcFile = Paths.get( "E:", "XXX.mp3" ).toFile();
            File descFile = new File( "E:" + File.separator + "copy.mp3" );
            fis = new FileInputStream( srcFile );
            fos = new FileOutputStream( descFile );

            byte[] arr = new byte[1024 * 8];
            int len;
            while ((len = fis.read( arr )) != -1) {
                fos.write( arr, 0, len );
            }
        } finally {
            try {
                if (fis != null)
                    fis.close();
            } finally {      //try finally嵌套的目的是能关一个尽量关一个
                if (fos != null)
                    fos.close();
            }
        }
    }
}

<3>采用带缓冲的字节流读取(BufferedInputStream 和 BufferedOutputStream)

  • BufferedInputStream
    • BufferedInputStream内置了一个缓冲区(数组),从BufferedInputStream中读取一个字节时, BufferedInputStream会一次性从文件中读取8192个, 存在缓冲区中, 返回给程序一个,程序再次读取时, 就不用找文件了, 直接从缓冲区中获取,直到缓冲区中所有的都被使用过, 才重新从文件中读取8192个。
  • C.BufferedOutputStream
    • BufferedOutputStream也内置了一个缓冲区(数组),程序向流中写出字节时, 不会直接写到文件, 先写到缓冲区中,直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。
  • 此处异常处理采用 JDK 7 提供的方法,在try()中创建的流对象必须实现了AutoCloseable这个接口,如果实现了,在try后面的{}(读写代码)执行后就会自动调用,流对象的close方法将流关掉 。
public class Main {
    public static void main(String[] args) {
        //实现AutoCloseable接口的类,都可以通过自动关闭资源的try语句来关闭这些流
        try (
                FileInputStream fis = new FileInputStream(
                        new File( "D:" + File.separator + "XXX.mp3" ) );   //创建文件输入流对象,关联XXX.mp3
                BufferedInputStream bis = new BufferedInputStream( fis );    //创建缓冲区对fis装饰

                FileOutputStream fos = new FileOutputStream( "D:" + File.separator +"copy.mp3" );   //创建输出流对象,关联copy.mp3
                BufferedOutputStream bos = new BufferedOutputStream( fos );   //创建缓冲区对fos装饰 )
        ) {

            int b;
            while ((b = bis.read()) != -1) {
                bos.write( b );
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 小数组的读写和带Buffered的读取哪个更快?
    答:定义小数组如果是8192个字节大小和Buffered比较的话,定义小数组会略胜一筹,因为读和写操作的是同一个数组,而Buffered操作的是两个数组。
    <4>案例:文件拷贝
    从键盘接收两个文件夹路径,把其中一个文件夹包含内容拷贝到另一个文件夹中
package com.sunlong.java;

import java.io.*;

public class FileText3 {
    //从键盘接收两个文件夹路径,把其中一个文件夹包含内容拷贝到另一个文件夹中
    public static void main(String[] args) throws IOException {
        //从键盘接收两个文件夹路径
        File src = FileTest.getDir();
        File dest = FileTest.getDir();
        if (src.equals( dest )){
            System.out.println("目标文件夹是原文件夹的子文件");
        }else {
            copyFileDir( src, dest );
        }
    }

    //把其中一个文件夹包含内容拷贝到另一个文件夹中
    /*
     * 1.在目标文件夹中创建原文件夹
     * 2.获取原文件夹中所有文件及文件夹,存储到File数组中
     * 3.遍历数组
     * 4.如果是文件就用IO流读写
     * 5.如果是文件夹就递归调用
     * */
    public static void copyFileDir(File src, File dest) throws IOException {
//       1.在目标文件夹中创建原文件夹
        File newFile = new File( dest, src.getName() );
        newFile.mkdir();

        File [] subFiles = src.listFiles();

        for (File subFile : subFiles) {
            if (subFile.isFile()){
                BufferedInputStream bis = new BufferedInputStream( new FileInputStream( subFile ) );
                BufferedOutputStream bos = new BufferedOutputStream(
                        new FileOutputStream( new File( newFile, subFile.getName() ) ) );
                int b;
                while ((b=bis.read()) != -1){
                    bos.write( b );
                }
                bis.close();
                bos.close();
            }else{
                copyFileDir( subFile, newFile );
            }
        }
    }
}

5. 带缓冲的字符流(BufferedReader 和 BufferedWriter)

<1> BufferedReader 和 BufferedWriter

  • BufferedReader 解决的是 InputStream 类的缺陷,BufferedReader 的 readLine() 方法可以读取一行字符(不包含换行符号)
  • BufferedWriter 的 newLine() 可以输出一个跨平台的换行符号 “\r\n”
public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader( new FileReader( "aaa.txt" ) );
    BufferedWriter bw = new BufferedWriter( new FileWriter( "ddd.txt" ) );

    String line;
    while((line = br.readLine()) != null){
        bw.write( line );
        //bw.write("\r\n");        //只支持windows系统
        bw.newLine();              //写出回车换行符,跨平台支持

    }
    br.close();
    bw.close();
}
  • 案例:获取一个文本上每个字符出现的次数,将结果写在times.txt上(IO 与集合)
public static void demo04() throws IOException {
   
    //1.创建带缓冲的输入流对象
    BufferedReader br = new BufferedReader( new FileReader( "aaa.txt" ) );
    //2.创建双列集合对象TreeMap
    TreeMap<Character, Integer> tm = new TreeMap<>(  );
    //3.将读到的字符存储到双列集合,存储时要作判断,如果不包含这个键,将键和值1存储,包含则值加一
    int ch;
    while((ch= br.read()) != -1){
        char c = (char) ch;
       /* if(!tm.containsKey( c )){
            tm.put( c,1 );
        }else{
            tm.put( c, tm.get( c ) + 1 );
        }*/
        tm.put( c,!tm.containsKey( c ) ? 1:tm.get( c ) +1 );
    }
    //4.关闭输入流
    br.close();
    //5.创建输出流对象
    BufferedWriter bw = new BufferedWriter( new FileWriter( "Times.txt" ) );
    // 6.遍历集合的内容写到times.txt中
    for (Character key : tm.keySet()) {
        switch (key){
            case '\t':
                bw.write( "\\t" + "="+ tm.get( key ) );
                break;
            case '\n':
                bw.write( "\\n" + "="+ tm.get( key ) );
                break;
            case '\r':
                bw.write( "\\r" + "="+ tm.get( key ) );
                break;
            default:
                bw.write( key + "="+ tm.get( key ) );
                break;
        }

        bw.newLine();
    }
    // 7.关闭输出流
    bw.close();
}

<2> java.util.Scanner类

  • Scanner解决了 BufferedReader 类的缺陷(替换了BufferedReader类,而且更好的实现了InputStream的操作),构造方法:public Scanner(InputStream source)。
  • 使用Scanner还可以接收各种数据类型,并且帮助用户减少转型处理。
    在这里插入图片描述
 public static void main(String[] args) {
        //  默认键盘输入
        Scanner scanner = new Scanner( System.in );     
        //从指定文件text.txt 输入
        try (Scanner scanner1 = new Scanner(
                new FileInputStream(
                        new File( "E:" + File.separator + "text.txt" ) ) )) {
            System.out.println( scanner1.nextLine() );
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
 }

6. 打印流(PrintStream 和 PrintWriter)

  • 打印流解决的就是OutputStream的设计缺陷,属于OutputStream功能的加强版。
  • 打印流分为字节打印流:PrintStream、字符打印流:PrintWriter,以后使用PrintWriter几率较高。
  • 打印流的设计属于装饰设计模式:核心依然是某个类的功能,但是为了得到更好的操作效果,让其支持的功能更多一些。(后续将会有专门文章详细介绍装饰设计模式)
  • 方法实现
    在这里插入图片描述
package com.sunlong.file;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.nio.file.Paths;

/**
 * @Auther: SolarL
 * @Date: 2019/4/3
 * @Description: com.sunlong.file
 * @version: 1.0
 */
public class Print {
    public static void main(String[] args) {
        try (
                PrintStream ps = new PrintStream(
                        new FileOutputStream( Paths.get( "E:", "text.txt" ).toFile() ) );
        ) {
            ps.printf( "%.2f", 1.2333 );//格式化输出 1.23
            ps.print( "aaa" );  //传入字符串
            ps.print( true );   //传入布尔值
            ps.print( 1110 );   //传入int数
            ps.print( 'a' );    //传入字符
            ps.print( new char[2] );  //传入字符数组
            ps.print( new Person()); //传入自定义对象
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class Person{
    
}
  • 综合第五、第六点,以后除了二进制文件拷贝的处理之外,那么只要是针对程序的信息输出都是用打印流(PrintStream、PrintWriter),信息输出使用Scanner。

上篇及本篇就是Java I/O 的输入/输出体系相关知识,如何使用 File 类来访问本地文件系统,Java 不同IO 流的功能,以及几种典型流的用法,其余流在此不再介绍。
[Java I/O 流] 带你一起玩转输入/输出流(上)跳转链接:https://blog.csdn.net/SolarL/article/details/88976894

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值