I/O操作2
复习
1、Collections工具类,提供很多静态方法用来操作各种集合对象。
Arrays工具类,提供很多静态方法用来操作各种数组对象。
2、比较器,用来给对象指定大小关系,主要用于对象的排序,查找最大最小。
内部比较器(一个类只能有一个内部比较器)、外部比较器(任意)
比较器的返回是一个int类型。如果只考虑前后关系的话,那么返回正数,代表在别人之后;返回负数,代表在别人之前;返回0,代表没有位置差异。
3、泛型的语法
所谓泛型 --- 类型参数化。
泛型类、泛型接口、泛型方法三种使用方式。
4、Java的I/O操作,采用的是流模型,在模型中有三个关键点:数据源、目的地和流类型。
提供了四大父类:InputStream、OutputStream、Reader、Writer;它们每一个都确定了三个关键点中的两个。正是因为还有1个点没有确定,所以它们都是抽象类。
而它们子类就帮助他们把另外一个点给确定下来了,比如:
FileInputStream。InputStream确定了输入的目的地是程序,流类型是字节流;然后FileInputStream确定了数据源是文件(File)。
4、所有的I/O流,在操作的过程中,都要按4步来走。
4-1、选择流类型
通过传递的数据类型选择:如果是文本数据,选择字符流;如果是二进制数据,选择字节流。
通过传递的方向选择:输入还是输出
最后通过另一个端点到底是谁,选择合适的子类流。
4-2、产生流对象 -- new出来
4-3、操作流对象 -- read 或 write
4-4、关闭流(别忘了)
注意
1、一旦有输入输出操作,一定会有try-catch-fianlly;
2、最好的习惯是new了对象以后,先写关闭代码,再把光标调到中间去写操作代码。
文件拷贝
try-with-resource
在所有的I/O操作中,最讨厌的一件事情就是,我们需要再catch后面加上finally代码,然后固定的执行流的关闭操作。用了几个流就要关几个,每次调用它们的close方法的时候又有try-catch语句。
在JDK7当中,提出了新的语法"try-with-resource"。它的效果是当我们在try块中操作需要关闭的资源(不仅仅是针对I/O流操作,包括以后各位要常用的数据库链接等等)的时候,可以自己去调用close行为。
语法:
try( 产生资源对象--new动作 ){
使用资源对象
}catch(异常){
异常处理
}
与普通的try-catch-finally在使用的变化点就两个:
1、try后面多打上一对(),里面new资源对象;
2、不用写资源的关闭动作了。
当我们需要用到多个要求最终关闭的资源,那么只需要在try的()里面new出它们多个,中间用";"分隔。
try(new出第一个资源;
new出第二个资源;
......){
}catch(异常){
异常处理
}
自定义的资源类如何使用try-with-resource
要想让我们自定义的资源类同样能够享有try-with-resource的待遇,那么我们必须让这个类实现一个java.lang.AutoCloseable的接口。
该接口只提供了一个方法:close方法,供实现类重写。同时,这个接口不仅仅只是提供统一的关闭行为外观,还起到了标识接口的作用。
public class MyRs implements AutoCloseable{
@Override
public void close(){
}
}
通过实际的测试,我们发现,这个close行为在try块当中执行的时候不管是否发生异常,都一定会被执行,其效果与卸载finally当中是一样的。
try-with-resource的本质
其实它就是一个语法糖,通过编译后的class文件,我们反编译回来看代码,发现编译后其实没有try-with-resource的语法了。
这说明这种语法仅存在于源代码文件当中,编译后它还是普通的try-catch语句,然后在try的最后和catch的最后都有一句close的代码。
注意
1、如果在try的()内有多个资源,那么这些资源的关闭顺序应该是从后往前的;
2、如果我们使用了多个资源,且这多个资源有对接的效果。那么要注意,最好是给每个资源单独new出来,不要直接使用拼接new对象的方式。(参见下一个小结)如果采用拼接new的方式,那么只有第一根管道会被关闭,后面接的那根会被漏掉;所以最好给单独申明。
3、try-with-resource当中如果发生异常,那么是先执行close动作,然后再执行catch块当中的代码。而传统的try-catch-finally,如果发生异常,是先执行catch块当中的代码,然后再关闭close动作。
4、try-with-resource的语法当中,还是允许在最后添加finally块。只不过在这个finally块当中,我们不用再进行关闭资源的动作了,可以根据需要书写其他的“必须要执行的动作”。
对象的序列化和反序列化
在前面的ATM机项目当中,我们使用了Properties来对数据进行了持久化操作。但是这种方式操作数据的是持久,非常不好。
1、麻烦。因为在程序当中,我们往往操作的都是对象,但是在properties文件当中,数据是字符串。所以,我们不得不加上大量的代码完成这两种类型之间的转换;
2、不安全。properties文件是文本文件,意味着只要打开它,就能看懂它。
所以,今天我们要来换一换。
在Java当中,提供了一种“对象序列化和反序列”的技术。它指的是:
1、对象序列化 --- 把内存中的Java对象以二进制流的形式输出出去。
2、对象反序列化 --- 把输入进来的对象二进制流直接生成为内存中的一个对象。
注意
1、无论是序列化还是反序列化的概念中,都跟文件没有关系。在它们的概念里面,一个是把对象用对象二进制流输出,但没有说输出到哪里去;一个是把输入的对象二进制流转为一个对象,也没有说这个输入流是哪里来的。
2、对象的反序列化 是 Java当中,各位同学学到的第二种产生对象的方式。
序列化API -- ObjectOutputStream
在上一节课,我们讲了流的分类,当时介绍了两种分类:
1、按方向:输入流 输出流;
2、按传递单位:字节流 字符流 现在我们可以看到第三种流的分类:
3、按功能: 节点流 操作流
节点流:比如FileInputStream,它控制了输入字节流(InputStream)的数据源是文件。 FileOutputStream,它控制了输出字节流(OutputStream)的目的地是文件。因此这种管道以及具备了I/O流操作所需要的全部三要素,因此可以直接使用。
而我们的ObjectOutputStream它的作用是进行对象序列化,也就是说它的任务是把对象转成二进制流输出,但是没有指定输出到哪里去。那么,也就是说它只是完成了一个转换操作,但还没有确定传输的另一个节点,所以不能单独使用。所以,它被称之为“操作流”。而操作流是不能够单独直接使用的,所以必须要至少接一个节点流。 因此,我们看到ObjectOutputStream压根儿就没有提供公共无参构造,而是一个公共带参构造,要求至少还要传递一个节点流进去进行流的对接。
反序列化API -- ObjectInputStream
它的操作与对象序列化类似,也是两个流的对接动作。ObjectInputStream只是一个操作流,只负责把对象流转成对象这个操作,而没有控制对象流从哪里来的。所以要对接一个输入的节点流,控制哪个节点作为数据源读数据。
注意:
1、如果一个类要参与序列化,那么它应该实现Serializable的接口;并且,它所有的属性都要实现这个接口;JDK自带的常用类,基本上都已经实现了这个接口。
2、JavaBean的书写规范中,除了:
必须要有公共无参构造;
必须为私有属性提供符合命名规范的get/set方法;
应该实现Serializable接口。
3、如果有批量数据需要参与序列化,那么就把它们放到一个集合类当中去,然后序列化整个集合对象。JCF当中提供的集合类,自己本身也都实现了Serializable接口。
4、序列化以后,不要去修改类的代码,如果修改了,那么反序列化回来的时候,会认为两个的版本的类型不一致了,会报错。
文件类File
参见代码:
public class TestFile {
public static void main(String[] args) {
File f = new File("data");
boolean flag = f.exists();//判断文件是否存在
if(flag){
flag = f.isDirectory();//判断是否是目录
flag = f.isFile();//判断是否是文件
if(flag){
String str = f.getPath();//获取文件相对路径
str = f.getAbsolutePath();//获取文件绝对路径
str = f.getName();//获取文件名
long l = f.length();//获取文件的大小--字节
long mills = f.lastModified();//获取文件上次修改时间
f.deleteOnExit();//文件删除 -- 先判断
f.isHidden();//获取该文件是否是隐藏文件
System.out.println(mills);
}else{
String[] subFileNames = f.list();//罗列该目录下的所有子文件(包括子目录)的名字
for(String sub : subFileNames){
System.out.println(sub);
}
File[] subFiles = f.listFiles();//罗列该目录下所有子文件(包括子目录)的File对象
for(File sub : subFiles){
System.out.println(sub.getName() + " " + sub.length() + " "
+ (sub.isFile()?"文件":"目录"));
}
}
}else{
try {
f.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}