JAVA高级编程基础自学笔记---文件/IO流

心不动则不痛 提交于 2020-08-18 08:44:34

教学视频: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是默认值

 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!