IO关系学习路线全在这了(持续更新20200516)

目录

一、比特、字节、字符概念铺垫

二、输入输出流的演变(字节流)

三、 InputStream/OutStream、FilterInputStream/FilterOutputStream、OutputStreamWriterInputStreamReader、BufferedWriter/BufferedReader关系图

四、文件读写BufferedWriter/BufferedReader(重要)

五、FileWriter/Reader的默认编码

六、文件读写BufferedWriter/BufferedReader指定编码(重要)

七、OutputStreamWriter写文件

八、OutputStreamWriter和BufferedWriter的区别

九、不用写finally的文件流try..catch(重要)

十、读写文件实战

(1)文件重命名失败

(2)多线程边读边写文件如何设计?


声明:优越G和喷子请自行关闭网页。


一、比特、字节、字符概念铺垫

比特、字节、字符、编码的概念

位/比特(bit)。是计算机数据存储的最小单位。每个二进制数字0或者1就是1个位;

字节。1个字节=8比特。它也是二进制0和1组成的.

字符。包括各国家文字(一个汉字、韩国字、日本字)、标点符号、图形符号、数字等。

A  ---用  0100 0001 表示

字符集。被收入某些标准的字符集合,比如Unicode字符集,GB2312字符集、ASCii字符集等。

编码。规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“编码”。GBK就是汉字编码,GB2312中一个汉字用2个字节表示。


二、输入输出流的演变(字节流)

 

介绍了概念,那么回到输入输出流 InputStream  OutStream,实际上就是一个字节流,输入输出字节流,并不能对文件进行交互。

因此有了FilterInputStream/FilterOutputStream,可以把文件转为文件流。

而文件流也不能直接变成字符串。因此需要桥梁Reader和Writer。

 

那么Java怎么完成文件读写的呢?

文字是一个字符,早期,Java只提供了字节流处理。为了文件读写,引入抽象类Reader和Writer两个类。

也就是字节与字符的转换桥梁!!

InputStreamReader/OutputStreamReader

FileReader,继承InputStreamReader,读取速度慢。/FileWriter

BufferedReader  行读取提高效率  -BufferedWriter 

文字写文件的过程

字符串-BufferedWriter(字符缓冲区,内存)-OutputStreamWriter(字符转字节)-FilterOutputStream(文件输出流)-文件

 

注意:写文件为了提高速度,会先放入缓冲区。

读文件则相反

三、 InputStream/OutStream、FilterInputStream/FilterOutputStream、OutputStreamWriter
InputStreamReader、BufferedWriter/BufferedReader关系图

IO关系学习路线全在这了(持续更新20200516)_第1张图片

四、文件读写BufferedWriter/BufferedReader(重要)

//写 (推荐)
 public void bufferedWriterWrite(String path, String content) {
        try (/*这是新写法,不用写finally关闭流,关闭流的动作由JVM自己管理,推荐*/
                FileWriter fileWritter = new FileWriter(path, true);//追加
                BufferedWriter bufferedWriter = new BufferedWriter(fileWritter);
        ) {
            bufferedWriter.write(content);
            bufferedWriter.newLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

//读 (推荐)
    public void bufferedWriterRead(String path, String content) {
        File file = new File(path);
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file))) {
            String s;
            while ((s = bufferedReader.readLine()) != null && !s.isEmpty()) {
                 System.out.println(s);//逐行读出,建议放到list里
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
//感受下写finally的恐惧
public void bufferedWriterWrite2(String path, String content) {
        FileWriter fileWritter = null;
        BufferedWriter bufferedWriter = null;
        try {
            fileWritter = new FileWriter(path, true);
            bufferedWriter = new BufferedWriter(fileWritter);
            bufferedWriter.write(content);
            bufferedWriter.newLine();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileWritter != null) {
                try {
                    fileWritter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedWriter != null) {
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

BufferWriter/Reader总结

支持文件追加或者覆盖

构造参数是Writer/Reader接口,本身不能指定

五、FileWriter/Reader的默认编码

默认取运行IDE项目编码,除非启动参数配置了 -Dfile.encoding="GBK"

如果默认运行环境编码是utf-8,但是又需要读写GBK编码,就不适用这个FileWriter/Reader

六、文件读写BufferedWriter/BufferedReader指定编码(重要)

    /**
     *   推荐  指定编码
     * @param path
     * @param content
     */
    public static void bufferedWriterRead2(String path, String content) {
        File file = new File(path);
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "GBK"));) {
            String s;
            while ((s = bufferedReader.readLine()) != null) {
                System.out.println(s);//逐行读出,建议放到list里
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void bufferedWriterWrite3(String path, String content) {
        File file = new File(path);
        try (
                BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "GBK"));
        ) {
            bufferedWriter.write(content);
            bufferedWriter.newLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

七、OutputStreamWriter写文件

这里提供读写文件的写法之一(针对字符串):

public static void writeFile(File file, String content) {
        // FileOutputStream会出现中文乱码,用 OutputStreamWriter
        OutputStreamWriter osw = null;
        try {
            osw = new OutputStreamWriter(new FileOutputStream(file), "gbk");
            osw.write(content);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                osw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void writeFile(String path, String content) {
        // FileOutputStream会出现中文乱码,用 OutputStreamWriter
        File file = new File(path);
        try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file, true), "gbk");) {
            osw.write(content);
            //osw.flush();  调用close会自动flush,而这种写法JVM会自动close
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

八、OutputStreamWriter和BufferedWriter的区别

OutputStreamWriter一个个字符写入磁盘,而BufferedWriter是先写入缓冲区,再一批次写入磁盘。

BufferedWriter本身无法控制编码,OutputStreamWriter可以指定编码。

九、不用写finally的文件流try..catch(重要)

遇到杠精同学、杠精领导、杠精同事,跟你说finally里面close失败了你是怎么处理的,试试这种写法:

try(..定义区){
  write..
}catch(IOException e){
 ..
}

以前都要写finally自己close,一不小心就忘了写了,遇到杠精还要考虑万一close失败的问题,现在这种写法,finally部分由JVM自己管理,该关闭的流JVM会自己关闭,省心省事。

十、读写文件实战

(1)文件重命名失败

原因1:重命名的路径文件夹不存在

原因2:如下例子,文件流还没关闭就执行重命名,就会失败。

public static void bufferedWriterWrite(String path, String content) {
        File file = new File(path);
        File file2 = new File("e:/a/tmp/test.txt");
        try (
                FileWriter fileWritter = new FileWriter(file, true);
                BufferedWriter bufferedWriter = new BufferedWriter(fileWritter);
        ) {
            bufferedWriter.write(content);
            bufferedWriter.newLine();
            file.renameTo(file2);//文件流还没关闭,不能重命名
        } catch (IOException e) {
            e.printStackTrace();
        }
            //file.renameTo(file2); 上面的那行放到这里就可以了
      
    }


public static void main(String[] args) {
        bufferedWriterWrite("e:/a/test.txt", "wo我们ww");
    }

(2)多线程边读边写文件如何设计?

 

最近做一个项目,有两个线程,一个线程写文件,另一个线程去读取落地的文件,设计不好的话,可能出现写一半就被读走的情况。

1.避免文件读写一半的情况

首先,写文件也是多个线程去写的,不然会很慢,设计思路就是每个线程共享一个计数器,然后写到批次文件里面(从1开始递增),写的时候先放到tmp文件夹。

然后,写完文件后,重命名的方式从tmp文件夹搬出来。(至于这个重命名的方式会不会出现被读走一半的情况,我不好验证,其他项目的同事是这么做的,前人经验)

读文件就从正式文件夹读,不要从tmp读。

2.如果程序停了怎么办

bufferreader可以从文件末尾追加内容,所以,计数器从1开始也没关系,内容追加进去就好了。

如果不想重新开始写,写的时候再记录一个文件,里面记录当前计数器编号,这样,就可以判断是否已经存在文件,如果存在就去编号记录文件去读取最新的编号,reset一下就可以从断点编号开始了。

3.文件计数器编号,要支持隔天刷新。 文件名格式参考:file-日期-批次号.txt

4.关于第1点还有更极致的办法

如果重命名搬文件,也会出现搬一半的情况,造成读文件的线程只读取到一半内容。那还可以这样设计,文件写完再去另一个文件夹finish去生成一个空文件,文件名为  xx.finish。

然后读文件的线程去遍历正式文件夹的时候,同时去检查finish文件下有没有存在同名文件,有才处理。处理完后可以删掉finish文件也可以不删。

5.读文件也是要分多线程的

写的时候是按照多个文件写的,读文件根据文件夹下的文件来分线程(循环扫描这个文件夹),开个线程池,每个文件分配一个线程去读这个文件。

 

你可能感兴趣的:(Java基础)