java io流层次_轻松掌握Java中IO流的核心使用思路

基本使用思路

当很多人学到IO的时候都特别懵,这也难怪,毕竟关于IO有各种流,记都要记糊涂了。其实只要换一个思维角度来看待IO流,还是不难的,甚至是非常容易和方便的,至少平常的应用不难。更深层次、更底层或者更高级的咱暂且不谈,这篇文章只介绍最基本的运用,让新手能熟悉得将IO流用到自己的项目中(其实不讲高级的原因是我不会(●′ω`●))

贴上代码之前咱们先捋一下IO的使用思路,为啥新手懵,因为流对象太多,开发时不知道用哪个对象合适,只要理清思路即可知道该使用哪些对象

IO,分为Input和Output,输入和输出。将硬盘上的文件读取到内存里来,为输入;将内存中的数据存储到硬盘上的文件里去,为输出。(这里能够处理的数据不光只有硬盘,还有其他设备,比如键盘或者网络流,但是咱们最常处理的就是硬盘上的文件,硬盘上的文件会操作之后,其他设备自然就会了)

无论输入还是输出,流程线是完全一致的,只是顺序不同:

**输入:拿到文件中的数据 >>> 开始读取数据 >>> 将数据“转移”到内存中去 >>> 在内存中操作数据

输出:拿到内存中的数据 >>> 开始读取数据 >>> 将数据“转移”到文件中去 >>> 文件中保存了数据**

所以,在使用IO流前,你先得确认第一个问题:明确数据源和目的地,即明确数据流的方向

明确数据流方向之后,咱们就得确认第二个问题:我要处理的是什么数据。 咱们处理的数据可以分为两大类:文本和非文本。 文本需要处理的数据为字符,非文本需要处理的数据为字节。

要处理非文本数据(字节)就用:

(输入)InputStream,(输出)OutputStream

要处理文本数据(字符)就用:

(输入)Reader,(输出)Writer

OK,这两个问题确认好后,基本上就知道要用哪个对象了。之前也说了,数据不光只有硬盘上的文件,其实还有其他设备,但是为了方便大家理解咱们就以硬盘上的文件来操作。 既然要操作文件,那就肯定要用到File,流也要用处理File设备的流,即:

(输入)FileInputStream,(输出)FileOutputStream

(输入)FileReader,(输出)FileWriter

不管是什么流,其中的方法基本都是一致的,输入数据就用 read(),输出数据就用 write(),一定要记住这一点哦,为啥java这么流行,就是因为不管你操作的是啥数据、啥设备、啥流,处理方式都是一样的:

明确数据源和目的地

需要处理的数据类型

再确认要处理的设备(一般是File,这里也只用File举例)

代码实战

注意哈,为了方便演示,代码中就没有写上 异常的捕捉和声明,正常使用的过程中是需要进行异常处理的!

简单的文本文件处理

好了咱们现在来实战,我要读取一个文本文件里的文字到我的内存中,该怎么操作?

文本文件,那就是FileReader或者FileWriter呗,读到内存里,那就是FileReader呗,看到没,该使用什么对象立马就确定好了。

/*假设现在硬盘有一个文件为1.txt

内容为:哈哈哈哈哈哈*/

// 首先咱们开始得创建一个FileReader对象

// 流创建的同时你肯定也要指定操作哪个东西嘛,所以

// 文件流的对象参数里自然就是放的File对象,或者文件路径,这里放的是文件路径

FileReader fr = new FileReader("src/1.txt");

// 还记得之前说的嘛,输入就用read()方法,所以这里咱就要调用read方法来开始读数据了

// 因为是文本文件,所以处理的是字符,read()方法则每次读取都是读的一个字符

// read()方法返回值是字符的字符编码,即int类型,所以声明一个int变量来接受

int len;

// 既然要读文本,自然就创建一个字符串来接受文件中的文本

String str = "";

// 开始循环读取数据,如果read()的返回值是-1,就代表没有内容可读取了,所以循环的判断条件即不为-1

while((len = fr.read()) != -1) {

// 每读一次,就将读取到的字符编码转换成字符存到我们刚才的字符串里

str += (char)len;

}

// 流都操作完了,就不要留着占资源了嘛,记得每次用完关掉流哦

fr.close();

// 循环完毕了,就代表所有文本已经读取完毕了,打印即可

System.out.println(str);

刚才的代码咋一看很复杂,其实内容非常简单,可以回顾一下之前说的流程线:

拿到数据 >>> 读取数据 >>> 操作数据,即

创建流对象 >>> 用read()方法读取数据 >>> 打印字符串

输入流过了一遍,咱再过一下输出流。我要将一个字符串输出到一个文本文件里,该怎么操作?

文本文件,那就是FileReader或者FileWriter呗,输出到文件里,那就是FileWriter呗:

// 老套路,创建一个流对象,流对象参数里放上文件路径

FileWriter fw = new FileWriter("src/1.txt");

// 记得之前说的嘛,输出用write()方法

fw.write("嘿嘿嘿嘿");

/*为啥这里不用循环呢,因为直接将要输出的所有数据都一次性给流了,read()是一个字符一个字符读,自然要用循环*/

// 输出完了,记得关闭流

fw.close();

/*这时候文件里的文本内容就变成了“嘿嘿嘿嘿”,要注意哦,这里输出是会覆盖原文件的文本的*/

看到没,三句话搞定,完全对应了流程线,是不是简单的一批?

拿到数据 >>> 输出数据 >>> 保存数据,即

创建流对象 >>> 用write()方法输出数据 >>> 文件内容已被覆盖

注意哈,上面我演示的是非常简单的输入和输出方法,运行性能也并不是特别好,但是先掌握这个,咱们慢慢来加难度。

文件复制

刚才咱们处理的是文本文件,那么如何处理非文本文件呢? 非文本文件,咱们就从文件的复制来开始入手。复制这个功能,肯定要将文件A的数据,转移到文件B(这个文件B是要自己创建),这代表既要输入又要输出,所以(输入)FileInputStream,(输出)FileOutputStream两个对象都要创建。

// 先创建一个文件读取流,流对象参数里放上需要复制的文件的路径

FileInputStream fis = new FileInputStream("src/1.gif");

// 再创建一个文件输出流,流对象参数里放上目标文件路径

// 文件输出的时候,如果没有该文件,则会自动创建文件(注意,读取的时候可不行,输入的源文件必须存在,否则报错)

FileOutputStream fos = new FileOutputStream("src/2.gif");

// 之前说过,字符流处理的数据是字符,字节流是字节,之前字符流的read()方法读取的是一个字符,那字节流的read()方法自然就是字节了

// 字节流read()方法返回的字节数据,即int类型,所以创建一个变量来接收

int len;

// 开始循环读取数据,操作方式和流程和字符类是一样的

while((len = fis.read()) != -1) {

// 每读取到一个字节,就写到目标文件中去

fos.write(len);

}

// 关闭流

fis.close();

fos.close();

就算创建了两个流对象,但是操作流程还是一样地简单:

拿到数据 >>> 输出数据 >>> 保存数据,即

创建流对象 >>> 读数据 >>> 写数据

在这里基本上就能印证之前的思路了:不管IO你要处理啥,怎样处理,本质的操作都是一样的!就算业务复杂的一批,无非就是 先读数据,再写(操作)数据

一定要记住这个基本的思路,思路解决后,咱们再来进行优化和进步!

上面复制文件的代码,虽然功能是可以完成,但是性能太慢太慢了!它是一个字节字节读取然后写入的,大家都知道,内存的读写速度要比硬盘的速度快得多!上面代码操作呢,完全就是在硬盘里进行读写,就算复制一个1MB的文件,只怕也要十几秒。所以上面的方式,只是为了让大家了解基本的操作,但是在实际运用中是不会这么用的。现在就介绍一下比较常用的方法来优化,下面代码要仔细看一下注释:

// 这个肯定是不变的,创建输入和输出流

FileInputStream fis = new FileInputStream("src/1.gif");

FileOutputStream fos = new FileOutputStream("src/2.gif");

// read()的返回值一直是int,所以得创建一个变量来接受,这个也不会变

int len;

// 这里是重点,为啥要创建一个字节数组呢,因为read()方法里面其实可以放参数,就可以放字节数组

// read()参数里放了字节数组后,就代表着将读取的数据全部先存放到数组里

// 说白了就是创建数组用来存读取的数据,也就是经常说的缓存,数组的初始化大小有多大,就代表能存多少数据

byte[] buf = new byte[1024];

// 开始循环读取数据到缓存数组里(这里就和之前有一点不同,多了一个参数)

// 这里返回值是还是int类型,之前返回的是一个字节数据,加了参数后,返回的就是输入的数据长度

while((len = fis.read(buf)) != -1) {

// 这里也是重点!write也可以放参数,之前放的是字节数据,当然也可以放字节数组

// 参数第一个代表要写的数据数组,第二个和第三个参数代表要写的长度,从0开始写,写到结尾

fos.write(buf,0,len);

}

// 关闭流

fis.close();

fos.close();

这种代码是比较常用的,运行速度比之前的代码快了很多很多,最重要的就是加入了一个缓存数组。之前代码是一个字节一个字节往硬盘里写,现在代码就是,先将内存里的缓存存满,然后再将缓存里的数据一次性给存入到硬盘里,说白了,就是读写硬盘的次数变少了,读写内存的次数变多了,自然而然速度就快了。

大家不要懵,一开始我就是在这里挺懵的,为啥好端端加个数组我开始完全弄不明白,在这里我举个例子大家就会清楚为什么了:

就好像在超市里购物,如果你看中一样东西,就立马得把那个东西拿到收银台先放着,然后再继续购物,又看中一个东西,又得跑到收银台放着,循环往复,最后再结账,这样是不是慢的一批。这个收银台就相当于硬盘,超市里的物品就相当于内存中的数据。而缓存是啥呢,就是购物车!有了购物车之后,你就能在超市里购物时,看中一个东西了,先放到购物车里然后再继续选购,直到你选购完毕再推着购物车里去收银台结账,这样效率就高多了!

这就是为什么要用到缓存机制了!在代码里,那个字节数组buf就相当于是购物车,先存够一定的数据,再跑到“硬盘”那里去“结账”。

对象的序列化与反序列化

谈到Java,就肯定要谈到面向对象,那么问题就来了,对象这种东西,又不是文本我该怎样去保存对象数据到文件里呢? Java当然贴心的为你提供了解决的方案:那就是对象的序列化。在这里,我只讲怎样用IO实现序列化,至于序列化的一些细节等我以后单独写一篇文章再说。

首先,咱们弄清楚一下序列化的定义,我看到有些同学在网上查询序列化相关的知识,越查越懵。其实懵是因为 在没有掌握基本的使用方法,却去了解使用原理,这样是绝对会懵的。

序列化,说白了就是将对象保存到本地文件上,反序列化,说白了就是将本地文件上的对象数据,读取出来到内存里:

序列化: 对象数据 >>> 本地文件

反序列化:本地文件 >>> 对象数据

是不是和IO的操作没啥区别,事实也确实如此,就是没啥本质的区别,只是因为要处理的是对象数据,所有就要用到序列化相关的流。在介绍序列化流前呢,咱们还是按照之前的思路来走一遍:

现在我要将对象数据存到本地文件里,对象数据是文本数据吗? 那肯定不是,所以就要用FileInputStream或者FileOutputStream呗。这里咱们要的是输出,那就是FileOutputStream嘛

// 老套路,创建一个输出流,设置好文件路径名(序列化不一定要这个文件后缀,其他的也可以,没有特别规定)

FileOutputStream fos = new FileOutputStream("src/obj.data");

假设咱们要存(序列化)的是数组,咋存呢,直接用write()吗?那肯定不行,字节流write()里只能放字节数组或者int类型的字节数据,放不了其他的玩意。这里只要额外加一个东西就好了,就是对象序列化流

// 需要保存(序列化)的数据

int[] array = {1,2,3};

// 老套路,创建一个输出流,设置好文件路径名

FileOutputStream fos = new FileOutputStream("src/obj.data");

/*注意,这里是重点了*/

// 创建一个对象输出流,构造方法里放的是一个输出流对象

// 这就代表着,我要处理的是对象,但是呢,我自己只能处理对象还处理不了文件

// 所以就得连接一个能处理文件的文件输出流

ObjectOutputStream oos = new ObjectOutputStream(fos);

// 调用序列化流对象的方法,参数里面就是你要序列化的对象数据,一句话序列化完毕了!

oos.writeObject(array);

// 关闭流

oos.close();

fos.close();

是不是处理对象的操作流程也是一样的?甚至比操作普通的文件还简单吧!

拿到数据 >>> 输出数据 ,即

创建序列化流对象 >>> 写数据

创建一个对象序列化流

连接一个文件输出流

开始写数据

演示了序列化,那反序列化呢,很简单嘛,将流程线反过来就好了:

获得文件 >>> 拿到数据 >>> 读取数据,即

创建反序列化流对象 >>> 读数据

// 老套路,要读数据嘛,创建一个文件输入流,设置好文件路径名

FileInputStream fis = new FileInputStream("src/obj.data");

// 创建一个反序列化流,就是把Output改成Input就可以了,记得连接输出流

ObjectInputStream oos = new ObjectInputStream(fis);

// 将对象数据读回来,注意哦,反序列化拿到的对象都是Object对象,所以要强制转换类型

int[] arrays = (int[])oos.readObject();

// 正常使用数据

System.out.println(arrays[1]);

是不是也特别简单?

创建一个对象反序列化流

连接一个文件输入流

开始读数据

总结

咱们再次回顾一下思路:

明确数据源和目的地

源:InputStream 或 Reader

目的地:OutputStream 或 Writer

需要处理的数据类型

源:是纯文本:Reader

否:InputStream

目的:是纯文本 Writer

否:OutputStream

再确认要处理的设备(一般是File,这里也只用File举例)

文件:File

对象:Object

键盘:System.in

控制台:System.out

是不是觉得很简单了?为啥很多人学到这就懵了呢,因为 在没有掌握基本的使用方法,却去了解使用原理,其实你只要先掌握基本的使用方法,然后慢慢了解就好了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值