文章目录
异常处理
Throwable是所有错误和异常的顶级父类,有两个重要的子类:Exception(异常)和 Error(错误),而他们各自都包含大量子类。
- Q:error 和 exception 的区别?
Error 类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
Exception 类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
Exception分为编译时异常 CheckedException 和运行时异常 RuntimeException。
Java 程序必须显式处理 Checked 异常:
- 当前方法知道如何处理该异常,则用 try…catch 块来处理该异常。
- 当前方法不知道如何处理,则在定义该方法是声明 throws 抛出该异常。
而运行时异常是代码运行时才发行的异常,编译时不需要 try catch。如除数是 0 和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。
-
finally:被finally控制的语句体一定会执行(前提是JVM没有停止)。
-
Q:如果catch里面有return语句,请问finally的代码还会执行吗?
如果catch里面有return语句,finally的代码会执行,会在在return前执行。 -
Q:throw 和 throws 的区别?
- throw:
1) throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。
2) throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行 throw 一定是抛出了某种异常 - throws:
1) throws 语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。
2) throws 主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
3) throws 表示出现异常的一种可能性,并不一定会发生这种异常。
- final、 finally、 finalize 的区别?
- final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。
- finally:异常处理语句结构的一部分,表示总是执行。
- finalize: Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用。
File文件类
File类是文件和目录路径名的抽象表示形式,可以用来表示文件,也可以用来表示目录。File类提供了多种构造方法,File(String pathname)
,根据一个路径获取File对象是最常用的。File类提供了对文件和目录的所有操作方法,大致可以分为:创建,删除,重命名,判断,获取。
创建功能可以分为创建文件(createNewFile()
)和创建目录(mkdir()
,mkdirs()
)两种。三种创建方法的返回值均是boolean类型,在创建之前都会自行判断文件或目录是否存在,不存在才去创建,所以不需要我们自己去判断。其中创建目录时推荐使用mkdirs()方法,这个会自动帮你检查并创建不存在的上级目录,而mkdir()只能创建单层目录,有时就会发生文件路径错误的问题。
删除功能的方法是delete()
,返回值也是布尔类型,既可以删目录,也可以删文件。该删除功能不经过回收站,是彻底删除!所以使用时要非常小心。如果目录中还有文件存在时,是不可以被删除的。
重命名的功能是由renameTo(File dest)
方法完成的,这个方法要求传入一个封装了新路径的File实例,返回值是布尔类型。JDK的开发者对其功能进行了进一步扩充:如果只是文件名不一样,那就是重命名;如果路径和文件名都不一样,那就是先剪切再重命名。
判断功能提供了多个方面的判断,首先是类型判断(是目录还是文件?):isDirectory()
,isFile()
。其次还有文件状态判断:canRead()
是否可读,public boolean canWrite()
: \是否可写,public boolean isHidden()
是否隐藏。还有一个用的最多就是关于文件是否存在的判断:exists()
。涉及到File类,常见的逻辑都是先判断文件或目录是否存在,再根据返回结果进一步操作。这些方法的返回值也都是布尔类型。
File文件类中最重要的功能就是获取功能。首先是路径的获取,这其中包括了绝对路径,相对路径,父目录的路径,分别是:getAbsolutePath
,getPath()
,getParent()
;其次还有文件名的获取,包括父目录的文件名,分别是:getName()
,getParent()
;也可以后去父目录的File实例:getParentFile()
;获取最后一次的修改时间(毫秒值)lastModified
;获取文件大小(字节数)length()
;可以获取指定目录下的所有文件夹/文件的名称或者File实例:list()
,listFile()
;甚至可以获取次抽象路径名指定的分区大小,已使用的字节数和未分配的字节数:getTotalSpace()
,getUseSpace()
,getFreeSpace()
。
开发中常见的操作是筛选某个目录下的所有文件,找出符合要求的所有文件,比如:
- 判断E盘目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称
File file = new File("E://");
File[] list = file.listFiles();
while (list.length != 0) {
for (File f : list) {
if (f.isFile() && f.getName().endsWith(".jpg")) System.out.println(file);
}
}
为了方便这种需求,上述方法的重载方法list(FilenameFilter filter)
和listFile(FilenameFilter filter)
方法可以传入一个文件过滤器实例,直接将符合要求的文件或文件名返回,通常以匿名内部类的方法来指定过滤器:
File[] files = file.listFiles(new FilenameFilter() {
@Override // file和fileName
public boolean accept(File dir, String name) {
return dir.isFile() && name.endsWith(".jpg");
}
});
IO流
流:一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。
- Q:Java中有几种类型的流?
- 按照流的方向分:输入流(InputStream)和输出流(OutputStream);输入和输出是站在内存的输入和输出角度来讲的。
- 按照实现功能分:节点流(可以从或向一个特定的地方(节点)读写数据。例如FileReader)和处理流(是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写,如 BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接);
- 按照数据处理单元分:字节流和字符流。
字节流和字符流
- Q:字节流和字符流的区别?
- 处理数据的方式不同:字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在 UTF-8 码表中是 3 个字节)时,先去查指定的编码表,再将查到的字符返回。
- 处理单元和操作对象不同:字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组。
- 字节流可以处理所有类型数据,如:图片, MP3, AVI 视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
字节流的抽象基类是InputStream(输入流-读)和OutputStream(输出流-写);
字符流的抽象基类是Reader(输入流-读)和Writer(输出流,写)。
由抽象基类派生出来的子类名称都是以其父类名作为子类名的后缀。这句话很重要,由此可以很简单的从众多流中判断出是属于字节流还是字符流。
抽象基类都不能直接实例化,我们只能对其子类进行实例化。
字节流
字节流可以处理所有类型数据,如:图片, MP3, AVI 视频文件。
字节流的抽象基类是OutputStream和InputStream,二者都不能被直接实例化,这里面定义了字节流的一些基本功能需求。
OutputStream和InputStream
public abstract class OutputStream implements Closeable, Flushable{}
是字节输出流,用来写入数据,实现了Closeable接口和Flushable接口,定义了数据的写入方法,刷新方法,流的关闭方法:
输出流中我们主要关注write()方法:分为传入字节write(int b)
,传入字节数组write(byte[] b)
,传入部分字节数组write(byte[] b,int off,int len)
三种(意味着子类的write()方法也一样)。
public abstract class InputStream implements Closeable
是字节输入流,用来读取数据。InputStream实现了Closeable接口,定义了流的关闭方法,和相关的数据操作方法:
我们主要关注数据的读出方法read()
,read(byte[] b)
,read(byre[],int off,int len)
,它们分别表示读取一个字节,读取一个字节数组,读取数据到字节数组的某一段。read方法会在读取字节后返回读取到的字节数,如果返回-1,那就表示已经读完了,读取文件通常用这个来判断数据是否已经读完。
字节数组在这里充当缓冲区,这与OutputStream中的write方法所对应。此外还有部分辅助方法:
mark(int readlimit)
方法用来标记当前输入流的位置;available()
可以返回从该输入流中可以读取(或跳过)的字节数的估计值;skip(long n)
跳过并丢弃来自此输入流的 n字节数据;markSupported()
测试这个输入流是否支持 mark和 reset方法;reset()
将此流重新定位到上次在此输入流上调用 mark方法时的位置。
FileOutputStream和FileInputStream
FileInputStream和FileOutputStream是针对文件系统的数据传输而定制的字节流。
构造参数需要指定一个File类的对象或者直接指定文件名(路径):FileOutputStream(File file)
,FileOutputStream(String name)
;FileOutputStream(File file, boolean append)
用来追加写入(默认是换行)。
FileInputStream的构造方法:FileInputStream(File file)
,FileInputStream(String name)
。
如果是创建FileOutputStream对象来写入数据时,当文件不存在时会自动创建;
如果是创建FileInputStream对象来读出数据时,文件不存在就会报FileNotFoundException异常。
- 典型应用:复制文件。(主要看流的正确关闭写法和使用while循环读取数据)
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("E:\\我的图片和音乐\\疯狂动物城.mp4");
out = new FileOutputStream("D:\\疯狂动物城.mp4");
//创建一个缓冲区
byte[] bytes = new byte[1024];
//read方法返回-1表示读完
while ((in.read(bytes)) != -1) {
// 读取数据到缓冲区
out.write(bytes);
// 刷新
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally { // 不管是否遇到异常,finally异常一定会执行,在这里进行资源释放
try {
if (in != null) in.close();
if (out != null) out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
BufferedInputStream和BufferedOutputStream
字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,Java本身在设计的时候,也考虑到了这样的设计思想(装饰设计模式后面讲解),所以提供了字节缓冲流以提高读写效率。它只是一个缓冲区,真正的操作还是要靠流来进行,只是用它做个容器,其本身并没有什么特有的方法。
字节缓冲流就属于我们前面提到的处理流,在其构造是通常需要传入字节流(FileOutputStream和FileInputStream),同时可以指定缓冲区的大小。
BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out, int size)
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)
字符流
字符流适合来操作文本文件,字符流使用字节流读到一个或多个字节(中文对应的字节数是两个,在 UTF-8 码表中是 3 个字节)时,先去查指定的编码表,再将查到的字符返回。可以简单的理解为字符流=字节流+编码表。
编码与解码
字节数组和字符之间的转换就是编码和解码的过程。JDK默认的字符集是GBK,但是我们更推荐统一使用UTF-8编码。
String类的构造方法提供了把字节数组转换成字符的方法public String(byte[] bytes)
,public String(byte[] bytes, String charsetName)
,同时提供了public byte[] getBytes()
和public byte[] getBytes(String charsetName)
方法来把字符串转换为字节数组,有参方法用来指定字符集。
使用示例:
// 编码和解码要统一
byte[] bytes = "我真的是个好人".getBytes("utf-8");
String s = new String(bytes, "utf-8");
字符流用来处理字符数据,字符流提供了操作字符、字符数组或字符串的读写方法,其抽象基类是Reader和Writer。
Reader和Writer
public abstract class Reader implements Readable, Closeable{}
Reader和InputStream中的方法并没有出入。同样提供了三种类型的read()方法,操作对象变成了字符char而不是字节byte。
public abstract class Writer implements Appendable, Closeable, Flushable {}
与字节流输出流OutputStream比起来,Writer中提供了五种写入数据的方法,分别用来写入字符,字符数组,字符数组的一部分,字符串,字符串的一部分。同时提供了追加写入的方法append()。
转换流InputStreamReader和OutputStreamWriter
InputStreamReader和OutputStreamWriter分别是Reader和Writer的子类。
既然字符流是依托于字节流而存在的,那么必然要存在一定的机制可以将字节流转换成字符流。转换流InputStreamReader和OutputStreamWriter就是用来完成这一任务的,它们的构造参数可以传入字节流对象:
OutputStreamWriter(OutputStream out)
和OutputStreamWriter(OutputStream out,String charsetName)
InputStreamReader(InputStream is)
和InputStreamReader(InputStream is,String charsetName)
前者是用JDK默认的字符集GBK来编解码,后者可以指定字符集。
FileReader和FileWriter
FileWriter和FileReader分别是OutputStreamWriter和InputStreamReader的子类。很多情况下我们并不需要指定字符集,Java给我们提供了字符流便捷类。构造方法:
FileReader (String filePath)
,FileReader(File fileObj)
FileWriter (String filePath)
,FileWriter(File fileObj)
,FileWriter(String filePath, boolean append)
通过字符流便捷类来构造字符输出流很简单,首先要明确一点,所有的字符流都是处理流,其底层都是字节流。字符流便捷流对这一过程进行了封装,我们只需要指定File路径或者File对象即可。
比如,构造OutputStreamWriter对象时:
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D://a.txt"));
FileWriter对这一过程进行了封装,我们只需要指定文件路径即可:
FileWriter fw = new FileWriter("D://a.txt");
进入该构造方法就可以发现封装过程,它调用的仍是其父类的构造方法:
public FileWriter(String fileName) throws IOException {
super(new FileOutputStream(fileName));
}
值得注意的是,FileWriter多提供了一种构造方法 FileWriter(String filePath, boolean append)
可以指定写入的字符是否追加(append设为true追加)。
BufferedReader和BufferedWriter
前面已经讲到,使用缓冲区可以明显提高读写效率,与字节缓冲流的BufferedInputStream和BufferedOutputStream相对应,字符流也提供了自己的字符缓冲流。
BufferedWriter是高效的字符输出流,而BufferedReader是高效的字符输入流。
可想而知,二者的构造方法必定会要求传入其他字符流:public BufferedWriter(Writer out)
,public BufferedReader(Reader in)
。同时可以指定缓冲区的大小:BufferedWriter(Writer out, int sz)
,BufferedReader(Reader in, int sz)
。
- BufferedReader的特有方法:
String readLine()
,一次读取一行文字。很实用。 - BufferedWriter的特有方法:
void newLine()
,换行符。该换行符是具备系统兼容性,如果要在流中自己指定,那么 windows下的换行符是\r\n
,Linux下是\n
,Mac下是\r
。
练习题
Q:如何将一个 java 对象序列化到文件里?
序列化是为了解决对象流读写操作时所引发的问题。在 java 中能够被序列化的类必须先实现 Serializable 接口,该接口没有任何抽象方法只是起到一个标记作用。如果要将java对象序列化入文件,则必须使用对象输入流和对象输出流,即ObjectInputStream和ObjectOutputStream,它们同为处理流,要求传入其他字节流。使用示例:
//对象输出流 User类必须实现 Serializable 接口
ObjectOutputStream objectOutputStream =new ObjectOutputStream(new FileOutputStream(new File("D://objs")));
objectOutputStream.writeObject(new User("zhangsan", 100));
objectOutputStream.close();
//对象输入流
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D://obj")));
User user = (User)objectInputStream.readObject();
System.out.println(user);
objectInputStream.close();
Q:如何实现对象克隆?
首先搞清楚浅克隆和深克隆的区别:
在Java中对象的克隆有深克隆和浅克隆之分。有这种区分的原因是Java中分为基本数据类型和引用数据类型,对于不同的数据类型在内存中的存储的区域是不同的。基本数据类型存储在栈中,引用数据类型存储在堆中。浅克隆不能达到到完全复制、相互之间完全没有影响的目的。浅克隆不会克隆原对象中的引用类型,仅仅拷贝了引用类型的指向。深克隆则拷贝了所有。也就是说深克隆能够做到原对象和新对象之间完全没有影响。
浅克隆的实现只需要在引用类型所在的类实现Cloneable接口,并使用public访问修饰符重写clone方法即可。
深克隆采用IO流实现,采用IO流实现,使用ObjectOutputStream将对象写入文件,然后再采用ObjectInputStream读取出来。这里需要用到内存流,这样就不会在磁盘上产生一些文件,而且内存流不需要关闭。深克隆的方法我们可以自己实现:
public static <T extends Serializable> T clone(T obj) throws Exception {
// 内存输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 对象输出流
ObjectOutputStream oos = new ObjectOutputStream(baos);
// 往内存中写入对象
oos.writeObject(obj);
// 内存输入流,读取内存中的信息
ByteArrayInputStream bin = new ByteArrayInputStream(baos.toByteArray());
// 对象输入流
ObjectInputStream ois = new ObjectInputStream(bin);
// 返回
return (T) ois.readObject();
}