轻松掌握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这么流行,就是因为不管你操作的是啥数据、啥设备、啥流,处理方式都是一样的:

  1. 明确数据源和目的地
  2. 需要处理的数据类型
  3. 再确认要处理的设备(一般是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();

是不是处理对象的操作流程也是一样的?甚至比操作普通的文件还简单吧!
拿到数据 >>> 输出数据 ,即
创建序列化流对象 >>> 写数据

  1. 创建一个对象序列化流
  2. 连接一个文件输出流
  3. 开始写数据

演示了序列化,那反序列化呢,很简单嘛,将流程线反过来就好了:
获得文件 >>> 拿到数据 >>> 读取数据,即
创建反序列化流对象 >>> 读数据

// 老套路,要读数据嘛,创建一个文件输入流,设置好文件路径名
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]);

是不是也特别简单?

  1. 创建一个对象反序列化流
  2. 连接一个文件输入流
  3. 开始读数据

总结

咱们再次回顾一下思路:

  1. 明确数据源和目的地
  • 源:InputStream 或 Reader
  • 目的地:OutputStream 或 Writer
  1. 需要处理的数据类型
  • 源:是纯文本:Reader
    • 否:InputStream
  • 目的:是纯文本 Writer
    • 否:OutputStream
  1. 再确认要处理的设备(一般是File,这里也只用File举例)
  • 文件:File
  • 对象:Object
  • 键盘:System.in
  • 控制台:System.out

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

如果文章帮到了你,请评论告诉我,如果还有疑问,也请告诉我。当然,如果觉得文章中有哪些地方需要改进,十分欢迎留言探讨!

最后祝大家都称为大神,咱们一起成长一起进步!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容