教学视频:https://edu.51cto.com/course/5667.html?source=so
目录
文件/IO流(文件操作流)
内存流(内存操作流使用不多)
打印流
System类对IO的支持(输出:system.out.print(),输入:system.in)
IO高级应用(重点是Scanner的应用)
对象序列化(序列化与反序列化)
文件/IO流
IP操作类的核心五个类:
文件操作类:File
流操作类 :IntputStream、OutputStream、Reader、Writer
一个接口 : Serializable
一、File文件
File类是唯一一个与文件本身操作有关的类,即通过File可以实现文件的创建、删除、列表等操作;
(一般只对文件操作,不能对文件内容操作)
例子:操作File
File考虑路径的时候要注意,"\"要“\\”,如:e:\\这里要双斜杠
如:
这个要是放到linux下执行回报错,这是windows的分隔符
使用windows和linux下的操作通用写法加上:File.separator代表斜杠
问题二
必须存在目录后才可以创建文件;
exists()这个方法是确认文件是否存在;
mkdir这个方法是适合只有一个父类时,如:e:\\test.txt,如果该目录下存在多级文件夹,那么就用mkdirs方法,如:e:\\hello\\nihao\\test.txt,代码如下:
日后如果要进行文件操作,必须要确保文件的存在;
直接看代码例子2:路径下有个图片约16.1M
结果如下:
对于文件大小这样子我们如法读懂,我们需要转换下格式
double放哪个位置都可以,可这样输出结果:这样还是读不懂,我们需要保留两位小数需要这样改写(先记住):
输出的结果就是:
我们再来看看加条该图片修写的日期:
输出的结果:这样也看不是很清楚,就需要改写下:
输出结果就显然易见咯:
扩展其他知识:
以上的操作方法适当了解即可,不用特意去记,查API,
需要记的方法操作有:判断父目录、判断文件夹、判断文件是否存在、删除;
列出e盘下的(app文件夹里的所有文件,一有文件夹就打开读取、这里采用递归方法)
运行中会出现空指针,这是因为e盘下可以会有系统的隐藏文件夹、这些文件夹是加密不能打开,所有程序运行时会报错
这需要修改下,再次运行后就不会出错了:
需要记的方法操作有:判断父目录、判断文件夹、判断文件是否存在、删除;
二、IO流
如果要进行文件内容操作必须依靠数据流,数据流分为两种流:
1.字节流:IntputStream(字节输入流)、OutputStream(字节输出流)
2.字符流:Reader(字符输入流)、Writer(字符输出流)
字节流和字符流的区别
这两种的区别就好比数据的BLOB与CLOB的区别?
首先要明确一点,通过任何终端(网络、文件)读取或者输出的数据都一定是字节(可以直接存储在磁盘上),但是字符经过内存处理后的数据(才能存到磁盘上)。
字符输入:字节(磁盘)>自动转换为>字符(内存);
字符输出:字符(内存)>自动转换为>字节(磁盘)。
例子:
OutputStream范例代码(在下面),把close()去掉(文件也删除),执行的时候,会新生成文件并写入内容(能直接存储)
Writer范例代码(在下面),把close()去掉(文件也删除),执行的时候,会新生成文件内容为空(不能直接存储)
在字符流输出的时候,所有的内容实际上都只是输出到了缓冲区中(内存)。在使用close()方法会将缓冲区的数据进行输出,如果没有关闭,将无法进行输出,此时可以利用flush()进行强制刷新如:out.flush();
字符使用到了缓冲区,而字节流没有使用缓冲区;
如果处理中文使用字符流,其他任何数据都使用字节流。
输出流和输入流的区别:
从上面可以知道,无论是字节流还是字符流,输出流执行都是写到文件中去的,输入流执行都是从文件读取内容显示在控制台的。
输出流都是直接输出,输入流是直接读取
OutputStream(字节输出流)
字节输出流只要是操作byte数据为主的,首先来观察java.io.OutputStream类定义结构:
画图描述:抽象要斜体表示,最后没有改
最早在使用OutputStream类操作的时候还没有Closeable和Flushable,它们在java1.5后才有,所以对于这两个接口基本可以忽略了(出现比较晚),除了close()与flush()之外的两个方法之外,还定义有三个重要的输出:
但是OutpuStream是一个抽象类(1.0就提供了,所以没有这么复杂),那么按照抽象类的基本原则来讲,如果想要取得OutputStream类的实例化对象,那么一定需要依靠子类,如果要进行文件的输出操作则可以使用FileOutputStream。
在这个类中提供有两个常用的构造方法:
构造方法:覆盖文件
构造方法:追加文件
范例:实现文件的输出
执行在这个路径下打开该文件,就可以看到内容:
那么在进行输出也只输出部分内容:
在路径下打开文件,因为2个字节为1个字符,就是5个字,10个字节:
或者还可以使用循环方式进行单个字节的输出,代码如下:
输出的结果:
但是发现每当执行完成之后所以的内容都被覆盖了,所以也可以进行追加操作。
范例:追加操作
加上true就是我们的追加操作
结果,:
但是追加是后面追加,我们可以换行
多按几次运行按钮,再次输出结果:
IntputStream(字节输入流)
IntputStream可以实现数据的读取操作,在java中IntputStream定义如下:
画图描述:抽象要斜体表示,最后没有改
在IntputStream类中定义有三个数据的读取操作方法:
|--每次执行此方法将读取单个字节数据(一个一个字节的读取),如果已经读取完成了,最后返回的值是-1
|--读取一组,每次讲数据读取到数据之中,那么会返回一个读取长度的数据,如果没有数据则返回长度为-1,要考虑两种情况:
1.要读取的内容大于开辟的数组内容,长度就是整个数组的长度(杯子的容积要小于纲的容积,杯子一下装满了,长度就杯子的长度);
2.要读取的内容小于开辟的数组内容,长度就是权保部最后的内容长度,数组装不满(最后缸一直喝最后只剩一点了,一打水只剩下半碗水了);
|--每次读取内容到部分字节数组,只允许读取满限制的数组的字节个数,此方法依然会返回读取的数组长度。
IntputStream是一个抽象类,所以要进行文件的读取使用FileIntputStream子类,子类的定义:
范例:实现数据的读取
输出:
但是,后面的都是存在很多空格,这是因为1024,杯子大于缸的容量,
需要这么修改,将部分字节内容变为字符串:
这才是对的结果:
IutputStream类中有一个read()方法,这个方法可以实现单个字节数据的读取操作,于是下面利用循环的方式才有此方法实现单个字节数据的读取。
范例:读取单个字节
一个个读取,知道值为-1才知道读取结束。foot ++是因为do……while循环至少循环一次,标识要加1
结果:
以上的do……while循环操作,的确是进行了数据源的读取,但是太麻烦了,所以在实际的开发会利用while循环实现读取操作。
范例:利用while循环修改(日后开发中读取使用最多的循环方法)
下面重要步骤要记住:
结果是一样的:
Writer(字符输出流)
文件存在磁盘是使用字节,在网络上保存都是使用字节,字符并不是在磁盘上保存的,它是经过处理后才保存的。
Writer是抽象类,是进行字符输出操作的使用的类,java1.1后才出现(字节是1.0就出现),理论上依靠字节就够了,有人觉得不方便才出现字符流,80%不会考虑,什么时候用字符?输出中文,字节也可以,就是比字符麻烦;
之所以会提供一个writer类主要是因为这个类的输出方法有一个特别好用的:
输出字符串:能直接输出Strinbg,这是最大唯一的好处
Writer要进行文件字符流操作,应该是FileWriter,里面一定有 一个构造方法:
不用查api文档了
范例:使用Writer输出数据
有些逻辑不能动,见红色标记
输出:
虽然Writer类提供有字符数组的输出操作能力,本质上,Writer类就意味着字符串的直接输出,字符流是最适合操作中文的,但并不是意味着字节流就无法操作中文。
Reader(字符输入流)
Reader是抽象类,观察定义,没有直接读取String(Writer中有直接输出String),这是为什么?
比如人吃东西,然后拉出来,把拉出来想象的是Writer,吃进去的是Reader,所以不可能提供一下子吃很多,这导致内存会爆掉,要控制输入。
在Reader类中也提供一系列的read()方法
数据读取:
范例:读取数据
结果:
三、转换流(非重点)
为此在java中提供两个转换流:
观察这两个类定义的结构以及构造方法:
或
范例:观察转型(记住)
执行结果:
实际讲解这种转换流目的不在于使用,而是进一步观察类的继承结构,
保存在磁盘上的数据一定都是字节数据。
总结:
字节流和字符流之间是可以实现互相转换的,字符流一定是在内存中经过处理得来的结果。
四、把前面所学的应用:
是模拟dos系统中的copy命令完成。
大案例:编写一个拷贝程序,可以实现任意的文件拷贝操作,通过初始化参数输入拷贝的源文件路径以及拷贝目标文件路径,本程序暂时不考虑类的设计(在main文件写)
拷贝命令:
如果要想实现这种拷贝的操作,可以有一下两种实现的思路:
1.开辟一个数组,将所需要的拷贝内容读取到数组之中,而后一次性输出到目标文件;
2.采用边读编写方式进行拷贝,不是一次性读取;
第一种的问题在于,如果文件量大小没有问题,5M左右,如果文件量一大,基本上内存就被占满。内存处理
范例:初期实现
做个介绍:
写个拷贝的方法自定义的(就是功能)
初期的实现写完了,接下来拷贝文字!
在e:\my.txt的文件内容,拷贝到e:\hello.txt
先执行一次,才能配置。右键-->Run As-->Run Configura……如图所示:
结果是:
看下文件是否拷贝
打开hello.txt文件
在试试拷贝图片
看结果实际很慢。
虽然实现了拷贝操作,但是这样的拷贝操作无法忍受。如果每次只拷贝单个字节,这是不可能的事情,利用数组来提升拷贝的性能,可以将数据读取到数组中,然后一次性将数组输出
都是一组一组读,每组8个,就算不满8页可以,读取到最后都是-1,
范例:修改拷贝方法
一组组读,满的就是数组长度,不满的读到多少是多少,知道等于-1时结束。
结果:
常见字符编码
1.计算机只认识0、1,计算机知识略
乱码产生分析
乱码的本质就在于编码与解码的方式不统一。
内存操作流
对于IO操作,目前为止实际上只学习了一种文件流的操作。但是这种操作的特点都是以文件为终端来处理,但是现在开发中如果不希望你产生文件,但依然需要你使用IO操作。那么此时就可以利用内存作为操作流。
内存流分两组:
1.字节内存流:
2.字符内存流:
范例:内存流操作
str的内容实现小写操作
内存流也要实行close操作
输出结果:
没有文件产生,发生了IO操作。
范例:两文件合并
结果:
以上操作不能合并大文件;
要大写
打印流
System类对IO的支持(理解)
数据输出:1.system.out.println()、2.system.err.println(),第2中很少用了,他们的区别是颜色上的区别
数据输入:system.in,这个输入内容大小的不确定性,也没有什么好讲的,Scanner常用
IO高级应用
缓冲输入流:BufferedReader
在JDK1.5之前,BufferedReader是一个非常重要的数据读取的操作类,也就是说如果要进行输入流的操作,尤其是文字操作,都建议应该使用BufferedReader类完成,这就是在1.5之前,1.5之后它的地位以及被剥脱了。我们还需要了解它
BufferedReader是Reader的子类,复杂进行缓冲区的读取,由于本身属于字符流,适合输入文字信息。
BufferedReader的继承结构(IO里继承结构很烦人,稍微记下,万一会面试):
在BufferedReader类中提供两个重要方法:
1.构造方法:
2.数据读取方法:
为什么不用BufferedInputStream,而要用BufferedReader,这是因为BufferedInputStream没有readLine()的方法。
BufferedReader返回String意味着什么,正则验证能使、能向各种类型能转换了吧!String是优先考虑的;
范例:BufferedReader实现数据输入
输入要使用system.in,而system.in是InputStream类型;
BufferedReader类的结构需要接受Reader类型,那么要将字节输入流变为字符输入流,使用InputStreamReader
执行:
从这代码可以看出,是不是跟Scanner简直一样啊,所以说BufferedReader都是早期的技术了,我们只需了解下即可,后面几乎都不用它了。
其他的没有太多了解的必要,略!
扫描流Scanner(重点知识):
java1.5之后提供一个java.util.Scanner类,称Scanner为扫描流,观察下构造方法:
构造方法:
当取得Scanner类的实例化对象之后,那么下面可以进行数据读取操作
Scanner默认最古老方式是利用String来接收数据,但是很多时候内容肯是数据,或者输入的内容还需要我们的验证,那么我们在这种情况下Scanner类发生了一下变化,增加了一推的hasNextXxxx()、nextXxxx()的方法。
范例:获取键盘输入的数据
运行,输入内容后按回车:
注意:如果以下这么输入,结果:
这是因为Scanner默认情况下会将空格和回车作为读取分割符,有个方法可以设置分割符:
代码如下,设置分割符为回车“\n”:
所以键盘输入空格后不影响结果:
Scanner本身支持各种数据类型
范例:设置年龄输入
注意,设置分割符会有个问题,代码如下:
这是因为你输入3的时候,已经带有回车符\n,所以删除分割符那行后就没有问题了
代码如下:
输入非数字试试:
范例:要求输入生日
输入错误内容:
输入正常的:
以上只是用键盘做了Scanner功能的演示,意义不大;
范例:利用Scanner读取文件
如果println是换行的意思,因为上面有了\n,在换行会多了一个换行而已。
读取文件内容为:
对象序列化(重要)
对象序列化指的是将在内存中保存的对象变为二进制数据流,这就意味着对象可以保存在文件中,或者进行各种传输操作,但是并不是所有的类对象都可以被序列化,如果某一个类的对象需要被序列化,则这个类必须实现java.io.Seralizble接口.但是这个接口没有任何的方法定义,因为其描述的是一种能力,属于标识接口。
什么是序列化?
将对象转换为字节。
什么是反序列化?
将字节转换回对象。
什么时候使用序列化?
当我们想要持久化对象的时候。当我们希望对象存在于JVM的生存期之后。
记住:
序列化对象时,只保存对象的状态,而不保存对象的类文件或方法。
当您序列化一个2字节的对象时,您会看到51个字节的序列化文件。
范例:定义可以被序列化定义的类
要记住Seralizble这个单词,其他的没有什么特别
这里出现Person警告,按Ctrl键进行修正,系统提供两种解决方案
这两个是序列版本的UID,这是什么意思呢?它在最早版本只得是你可以在JRE版本使用,意义不大,只是不用这个警告
最常用的是选中下面那种,我们习惯于压制,
就是类似直接添加注解的意思。
以后Person类对象就可以被传输或者保存在文件之中了。
以上的代码除了类的接口implements Serializble外,其他地方没有发生任何的改变,这就是序列化含义所在;
序列化操作(利用ObjectOutputStream)
要记住对象序列化不可能有字符流,因为序列化通过二进制传输,二进制都是字节数据,所以依靠这个父类。
范例:序列化对象
反序列化(利用ObjectInputStream)
利用ObjectInputStream输出的文件信息都是二进制数据,但是这个数据大部分看不懂的,所以要将数据还原,那么必须用ObjectInputStream进行反序列化;
范例:反序列化操作
序列化和反序列化都是操作一个文件
输出反序列化内容:
不一定所有的类都有序列化,只有一种百分之百使用序列化操作,那就是简单java类
Transient关键字不被序列化(一般不考虑用这个,知道就行)
默认情况下,一个对象的所有竖向都一定要被序列化下来,那么现在如果某些属性不想被序列化,那就使用transient关键字
范例:观察transient关键字的使用
要想不被序列化,就必须加入transient关键字
//
如:
调用验证下:
由于transient关键字作用,所以当数据被反序列化后,此数据为其对应类型的默认值。age=0是默认值
来源:oschina
链接:https://my.oschina.net/u/4342169/blog/4310843