谈一谈Java中的IO流(详细版本)

什么是IO流,简答说就是将字符或者字节转换成流的方式进行输入或者输出。输入就是读取文件,输出就是写入文件,记清楚了!其中I就是Input,O就是Output的意思。

1.什么是文件File?

无论是我们转换成流写入文件,还是读取文件转换成流也好,其最终都是与文件打交道,那我们首先了解一下文件是什么?在Java中怎么表示。

(1)文件在Java中的表示

在Java中文件的表示是java.io.File类,这个File既表示文件也代表文件夹。在代码中怎么使用:

public static void main(String[] args) {
        String path1 = "C:\\Users\\Administrator\\Desktop\\test.txt";//写法一
        String path2 = "C:/Users/Administrator/Desktop/test.txt";//写法二
        File file1 = new File(path1);
        File file2 = new File(path2);
        System.out.println(file1);
        System.out.println(file2);
    }

其实很简单就直接new一个File对象即刻,构造参数为一个路径。此处我要解释一下这个路径的写法,上述代码写了两种,我们使用正斜杠'/'或者反斜杠'\'都行,但是在Java中,反斜杠它是转移字符,搭配上特殊的字符有特殊的含义例如'\n'就是代表换行的意思,所以如果使用反斜杠的时候需要将转义字符变成普通的就是在它前面再加一个'\',也就是双反才行。

(2)实战中怎么使用

在实际使用一般不是绝对路径,都是相对路径,也就是文件都是放到项目的资源目录resources下

那么此时的path就得写成相对路径来表示,有个快捷的方式,右键文件->Copy Path/Reference...
->Path From Content Root

复制完直接粘贴,这样直接省事,代码如下:

    public static void main(String[] args) {
        String path = "src/main/resources/test.txt";
        File file = new File(path);
        System.out.println(file);
    }

可以看出相对路径就是从src开始算的。

(3)常用方法介绍及使用

这里就不多少了,介绍两三个即可,一般也不怎么用。直接上代码展示。

    public static void main(String[] args) {
        String path = "src/main/resources/test.txt";
        File file = new File(path);
        System.out.println("toString方法的结果:"+file);//这个打印的就是toString方法,就是path
        System.out.println("文件是否存在:"+file.exists());//这个是判断这个文件是否存在,true:存在,false:不存在
        File[] files = file.listFiles();//主要是当file是文件夹时才调用的,是文件的话返回null
    }

2.IO流的详细讲解

我们要带着目的去学习,我们的目的很简单,就是将文件中的内容读取成我们Java中的字符串,没有目的,也没必要学,先看一下我们的目的,并且会经过那些过程。

接下来就按照我们需要的取一步一步的走,看看怎么转换的。

(1)输入的整个流程介绍:

Ⅰ.需要一个文件对吧,上面已经介绍了File,这个就很简单直接new File(path),留备用。
Ⅱ.字节流准备。我们使用的是FileInputStream,从文件->字节流,这一步也很简单,直接创建FileInputStream的时候将文件File作为参数传进去即可。大家可以先看下面介绍。例如:


Ⅲ.那第二步我们也准备好了,就开始第三步了,将字节流->字节, 这一步也不难,我们提前准备一个字节数组byte[],将字节流都读取到这个数组中来。FileInputStream已经提供了一个方法,在下面也有介绍,int read(),读取一个字节,返回的字符对象的ASCII值,没有的话,返回-1。如

 public static void main(String[] args) {
        String path = "src/main/resources/test.txt";
        File file = new File(path);//1.创建文件
        try {
            FileInputStream inputStream = new FileInputStream(file);//创建字节流
            //读取每一个字节
            int c = inputStream.read();
            System.out.println(c);
            System.out.println((char) c);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

上述我把它转换成了字符这个做法其实不对的,因为一个字符等于多个字节,例如一个汉字等于3个字节,你读取一个强转肯定会乱码的,所以不要这样做。
正确做法,是定一个字节数组,例如:

public static void main(String[] args) {
        String path = "src/main/resources/test.txt";
        File file = new File(path);//1.创建文件
        try {
            FileInputStream inputStream = new FileInputStream(file);//创建字节流
            //读取每一个字节
            byte[] bytes = new byte[100];
            int length = inputStream.read(bytes);
            System.out.println("读取了多少个字节"+length);
            String s = new String(bytes,0,length);
            System.out.println(s);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

那上述有个缺点,假如我定义的byte只有4,结果是什么呢?

结果很明显,出现了乱码,并且,因为1个汉字是3个字节,你定义4个,就读了4个字节,最后肯定就是不够两个汉字了,导致第二个汉字出不来,出现了乱码,所以我们需要把byte定义的足够大才行对吧。但是多大是大呢?这个没法定义。解决办法:要么你就定义的非常大,这个肯定不妥,第二种就是使用我们的FileRead,字符输入流。它与字节输入流的区别很明显,它每次读取一个字符,那肯定就不存在乱码的问题,但是也是需要多次进行读取,因为你不知道原始有多大,我们一般定义字符数组为1024,然后循环读取。直接上代码。

public static void main(String[] args) {
        String path = "src/main/resources/test.txt";
        File file = new File(path);//1.创建文件
        try {
            FileReader fileReader = new FileReader(file);//字符输入流
            char[] chars = new char[1024];
            int length = 0;
            StringBuffer stringBuffer = new StringBuffer();//可变字符串
            while ( (length = fileReader.read(chars)) != -1){
                stringBuffer.append(chars,0,length);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

这种写法对于读取我们普通文本例如.txt,或者.json常见文件,对于视频音频还是需要使用字节输入流,对于普通的文本直接使用上述的FileReader。

字节输入流FileInputStream

它是继承InputStream,它的父类也是个类,有人好奇了,那为什么不用,很简单,它的父类是个抽象类,不能被实例化,所以只能用FileInputStream。看看源码

再来看一看它有哪些常用方法
构造方法

其实常用就两个,一个直接传入文件路径,一个直接传入文件,仔细观察,其实那个传入文件路径的方法,它里面又调用了传入文件的方法。最终还是创建了一个文件,所以使用哪一种都行。
核心方法

字符输入流FileReader

(2)上述操作有什么值得优化的地方吗?

其实我认为本质上没什么优化的,但是存在即合理,接下来来了解一下BufferedReader,字符缓冲输入流,它与FileReader的区别是什么。BufferedReader一句话减少读取磁盘的次数而出现的

怎么减少的?假如我现在有一个文件是24KB吧。如果我定义数组是每次读取1024KB,结果是:
FileReader:24*1024/1024=24次。

BufferedReader:首先讲一下它的工作原理和读取原理吧。
它会多出一个缓冲区,就是说read方法调用是每次从缓冲区中拿去数据,那缓冲区的数据才是读取磁盘拿的,默认缓冲区大小是8KB,总次数其实24KB*8KB=3次,每一次缓冲区的数据也是需要8KB*1024/1024 = 8 ,总的下来就是24次。那区别很明显了。
虽然都是24次,但是前者是读取24次磁盘,后者是3次磁盘+21次内存。
总结:
FileReader:24次(磁盘)
BuffderedReader:3次(磁盘)+21次内存。
但是我们读取磁盘的IO速度是读取内存速度的千分之一,所以就会显得后者更快,更有优势。
 

但是真的是有优势吗?假如说我每次读取的不是1024KB而就是8KB呢?前者和后者都是3次,并且后者还得多一次从内存再读到我们定义的byte数组呢。此时我就感觉这个缓冲区真sb,完全是多余,还浪费时间去学习,带着问题我去网上搜了一下,只有很少的会说一下,真正测了以后,
其实这个结果还是后者更快一些,但是效果不是很明显,可能是它内部使用的装饰者模式的问题。
毕竟它出现毕竟有它的道理,但是大方向肯定是差不太多,但是出现了,我们尽量还是用缓冲区去读取吧,知道有这么个问题就行,如果大家知道到底为什么后者更快一些的话,直接评论。

输出流的话,基本和输入流的做法差不多,大家下去可以试一试。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值