Java大佬是怎样炼成的 8

八、异常处理和I/O

8.1 异常处理

8.1.1 异常概述

  • 异常处理使得程序可以处理非预期的情景,并且继续正常的处理。
  • 异常是从方法抛出的。方法的调用者可以捕获以及处理该异常
  • 使用异常处理的优点。它能使方法抛出一个异常给它的调用者,并由调用者处理该异常。如果没有这个能力,那么被调用的方法就必须自己处理异常或者终止该程序。
  • 异常处理最根本的优势就是将检测错误(由被调用的方法完成)从处理错误(由调用方法完成)中分离出来。

8.1.2 异常类型

  • 异常是对象,而对象都采用类来定义。异常的根类是 java.lang.Throwable
    在这里插入图片描述
  • 类名 Error、Exception 和 RuntimeException 有时候容易引起混淆。这三种类都是异常,这里讨论的错误都发生在运行时
  • Throwable 类是所有异常类的根。所有的 Java 异常类都直接或者间接地继承自Throwable。可以通过继承 Exception 或者 Exception 的子类来创建自己的异常类。
  • 异常类可以分为三种主要类型:
    1、系统错误:是由 Java 虚拟机抛出的,用 Error 类表示。Error 类描述的是内部系统错误。这样的错误很少发生。如果发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也不能做。
    2、异常:是用 Exception 类表示的,它描述的是由程序和外部环境所引起的错误,这些错误能被程序捕获和处理。
    3、运行时异常:是用 RuntimeException 类表示的,它描述的是程序设计错误,例如,错误的类型转换、访问一个越界数组或数值错误。运行时异常通常是由 Java 虚拟机抛出的。
  • RuntimeException、Error 以及它们的子类都称为免检异常(unchecked exception )。所有其他异常都称为必检异常(checked exception), 意思是指编译器会强制程序员检査并通过 try- catch 块处理它们,或者在方法头进行声明

8.1.3 异常的处理

  • Java 的异常处理模型基于三种操作:
    1、 声明一个异常(declaring an exception)
    2、 抛出异常(throwing an exception)
    3、 捕获一个异常(catching an exception)
  • 如果方法没有在父类中声明异常,那么就不能在子类中对其进行继承来声明异常
  • 通常,JavaAPI 中的每个异常类至少有两个构造方法:一个无参构造方法和一个带可描述这个异常的 String 参数的构造方法。该参数称为异常消息, 它可以用 getMessage()获取
  • 声明异常的关楗字是 throws, 抛出异常的关键字是 throw。
  • 从一个通用的父类可以派生出各种异常类。如果一个 catch 块可以捕获一个父类的异常对象,它就能捕获那个父类的所有子类的异常对象。
  • 在 catch 块中异常被指定的顺序是非常重要的。如果父类的 catch 块出现在子类的catch 块之前,就会导致编译错误。
  • Java 强迫程序员处理必检异常。如果方法声明了一个必检异常(即 Error 或Runtime Exception 之外的异常),就必须在 try-catch 块中调用它,或者在调用方法中声明要抛出异常
  • 在异常事件中,执行仍然会继续。如果处理器没有捕获到这个异常,程序就会突然中断

8.1.4 finally子句

  • 无论异常是否产生,finally 子句总是会被执行的。
  • 在任何情况下,finally 块中的代码都会执行,不论 try 块中是否出现异常或者是否被捕获。考虑下面三种可能出现的情况:
    1、如果 try 块中没有出现异常,执行 finally, 然后执行 try 语句的下一条语句
    2、如果 try 块中有一条语句引起异常,并被 catch 块捕获,然后跳过 try 块的其他语句,执行 catch 块和 finally 子句。执行 try 语句之后的下一条语句。
    3、如果 try 块中有一条语句引起异常,但是没有被任何 catch 块捕获,就会跳过 try 块中的其他语句,执行 finally 子句,并且将异常传递给这个方法的调用者
  • 即使在到达 finally 块之前有一个 return 语句,finally 块还是会执行。
  • 使用 finally 子句时可以省略掉 catch 块

8.1.4 何时使用异常

  • 当姆误需要被方法的调用者处理的时候,方法应该抛出一个异常
  • 异常处理将错误处理代码从正常的程序设计任务中分离出来,这样,可以使程序更易读、更易修改。但是,应该注意,由于异常处理需要初始化新的异常对象,需要从调用栈返回,而且还需要沿着方法调用链来传播异常以便找到它的异常处理器,所以,异常处理通常需要更多的时间和资源。
  • 异常出现在方法中。如果想让该方法的调用者处理异常,应该创建一个异常对象并将其抛出。如果能在发生异常的方法中处理异常,那么就不需要抛出或使用异常。
  • — 般来说,一个项目中多个类都会发生的共同异常应该考虑作为一种异常类。对于发生在个别方法中的简单错误最好进行局部处理,无须抛出异常。
  • 有一点要把握住,不要把异常处理用作简单的逻辑测试。

8.1.5 重新抛出异常

  • 如果异常处理器不能处理一个异常,或者只是简单地希望它的调用者注意到该异常,Java 允许该异常处理器重新抛出异常
try{
...
}catch(Exception e){
	throw e;
}

8.1.6 链式异常

  • 和其他异常一起抛出一个异常,构成了链式异常
catch(Exception e){
	throw new Exception("....",e);//先执行...信息,在执行e
}

8.1.7 创建自定义异常

  • 可以通过派生 java.lang.Exception 类来定义一个自定义异常类
  • 可以扩展 RuntimeException 声明一个自定义异常类吗?可以,但这不是一个好方法,因为这会使自定义异常成为免检异常。最好使自定义异常必检,这样,编译器就可以在程序中强制捕获这些异常

8.2 I/O流

8.2.1 File类

  • File 类包含了获得一个文件 / 目录的属性,以及对文件 / 目录进行改名和删除的方法
  • 在 Windows 中目录的分隔符是反斜杠()。但是在 Java 中,反斜杠是一个特殊的字符,应该写成 \的形式
  • 构建一个 File 实例并不会在机器上创建一个文件。不管文件是否存在,都可以创建任意文件名的 File 实例。可以调用 File 实例上的 exists() 方法来判断这个文件是否存在。
  • 斜杠(/)是 Java 的目录分隔符,这点和 UNIX 是一
    样的。

8.2.1 文件的输入和输出

  • 使用 Scanner 类从文件中读取文本数据,使用 PrintWriter 类向文本文件写入数据
使用 try-with-resources 自动关闭资源
try( 声明和创建资源){
使用资源来处理文件 ;
}
  • 行分隔符字符串是由系统定义的,在 Windows 平台上是\r\n, 而在 UNIX 平台上是 \n。为了得到特定平台上的行分隔符,使用:

8.2.2 从web上读取数据

  • 首先要使用:java.net.URL 类的这个构造方法,为该文件创建一个URL 对象
  • 创建一个 URL 对象后,可以使用 URL 类中定义的 openStream()方法来打开输入流
  • 现在可以从输人流中读取数据了,如同从本地文件中读取一样。

8.2.3 在 Java 中如何处理文本I/O

  • 使用 Scanner 类读取文本数据,使用 PrintWriter 类写文本数据

8.2.4 文本I/O和二进制I/O

  • 二进制 I/O 不涉及编码和解码,因此比文本 I/O 更加高效
二进制I/O
  • 抽象类 InputStream 是读取二进制数据的根类,抽象类 OutputStream 是写入二进制数据的根类。
    -
  • 二进制 I/O 类 中 的 所 有 方 法 都 声 明 为 抛 出 java.io.IOException 或 java.io.IOException 的子类。
  • 当流不再需要使用时,记得使用 closeO 方法将其关闭,或者使用 try-with-resource语句自动关闭。不关闭流可能会在输出文件中造成數据受损,或导致其他的程序设计错误。
  • FilelnputStream 类 的 实 例 可 以 作 为 参 数 去 构 造 一 个 Scanner 对 象, 而FileOutputStream 类的实例可以作为参数构造一个 PrinterWriter 对象。可以创建一个
    PrinterWriter 对象来向文件中追加文本。如果 temp.txt 不存在,就会创建这个文件。如果 temp.txt 文件已经存在,就将新数据追加到该文件中
FilterlnputStream/ FilterOutputStream
  • 过滤器数据流( filter stream) 是为某种目的过滤字节的数据流。基本字节输人流提供的读取方法 read 只能用来读取字节。如果要读取整数值、双精度值或字符串,那就需要一个过滤器类来包装字节输入流。使用过滤器类就可以读取整数值、双精度值和字符串,而不是字节或字符。FilterlnputStream 类和 FilterOutputStream 类是过滤数据的基类。需要处理基本数值类型时,就使用 DatalnputStream 类和 DataOutputStream 类来过滤字节,应该按存储的顺序和格式读取文件中的数据。
二进制 I/O 中的字符与字符串
  • — 个统一码由两个字节构成。writerCharCchar O 方法将字符 c 的统一码写入输出流。writerChars(String s) 方法将字符串 s 中所有宇符的统一码写到输出流中。
    writeBytes(String s) 方法将字符串 s 中每个字符统一码的低字节写到输出流。统一码的高字节被丢弃。
    writeBytes 方法适用于由 ASCII 码字符构成的字符串,因为 ASCII 码仅存储统一码的低字节。如果一个字符串包含非 ASCII 码的字符,必须使用 writeChars 方法实现写入这个字符串。
  • UTF-8 的修改版方案分别使用 1 字节、2字节或 3 字节来存储字符。如果字符的编码值小于或等于 0x7F 就将该字符编码为一个宇节,如果字符的编码值大于 0X7F 而小于或等于 0X7FF 就将该字符编码为两个字节,如果该字符的编码值大于0X7FF 就将该字符编码为三个字节。
  • UTF-8 字符起始的几位表明这个字符是存储在一个字节、两个字节还是三个字节中。如果首位是 0, 那它就是一个字节的字符。如果前三位是 110, 那它就是两字节序列的第一个宇节。如果前四位是 1110, 那它就是三字节序列的第一个字节。UTF-8 字符之前的两个字节用来存储表明字符串中的字符个数的信息。
  • UTF-8 格式具有存储每个 ASCII 码就节省一个字节的优势,因为一个统一码字符的存储需要两个字节,而在 UTF-8 格式中 ASCII 字符仅占一个字节。如果一个长字符串的大多数字符都是普通的 ASCII 字符,采用 UTF-8 格式存储更加高效
BufferedlnputStream/BufferedOutputStream
  • BufferedlnputStream 类和 BufferedOutputStream 类可以通过减少磁盘读写次数来提高输入和输出的速度。
  • 使用 BufferdlnputStream 时,磁盘上的整块数据一次性地读入到内存中的缓冲区中。然后从缓冲区中将个别的数据传递到程序中。
  • 使用BufferedOutputStream, 个别的数据首先写人到内存中的缓冲区中。当缓冲区已满时,缓冲区中的所有数据一次性写入到磁盘中。
    在这里插入图片描述
  • 应该总是使用缓冲区 1/0 来加速输入和输出。对于小文件,我们可能注意不到性能的提升。但是,对于超过 100MB 的大文件,我们将会看到使用缓冲的 I/O 带来的实质性的性能提升。

8.2.5 对象I/O

  • ObjectlnputStream 类和 ObjectOutputStreara 类可以用于读写可序列化的对象
  • 完全可以用 ObjectlnputStream 和ObjectOutputStream 类代替 DatalnputStream 类和DataOutputStream 类。
  • 必须以数据写人文件时的顺序和格式从文件中读取这些数据
Serializable 接口
  • 并不是每一个对象都可以写到输出流。可以写入输出流中的对象称为可序列化的( serializable) ,因为可序列化的对象是java.io.Serial’izable 接口的实例,所以,可序列化对象的类必须实现 Serializable 接口。
  • 当存储一个可序列化对象时,会对该对象的类进行编码。编码包括类名、类的签名、对象实例变量的值以及该对象引用的任何其他对象的闭包,但是不存储对象静态变量的值
  • 非序列化的数据域
    如果一个对象是 Serializable 的实例,但它包含了非序列化的实例数据域,那么可以序列化这个对象吗?答案是否定的。为了使该对象是可序列化的,需要给这些数据域加上关键字 transient, 告诉 Java 虚拟机将对象写入对象流时忽略这些数据域。思考下面的类:
public class C implements java.1o.Serializable {
private int vl;
private static double v2;
private transient A v3 = new A();
}
class A { }// A is not serializable
  • 当 c 类的一个对象进行序列化时,只需序列化变量v1。因为 v2 是一个静态变量,所以没有序列化。因为 v3 标记为 transient, 所以也没有序列化。如果 v3 没有标记为
    transient, 将会发生java.io.NotSerializableException
  • 重复的对象如果一个对象不止一次写入对象流,会存储对象的多份副本吗?答案是不会。第一次写入一个对象时,就会为它创建一个序列号。Java 虚拟机将对象的所有内容和序列号一起写入对象流。以后每次存储时,如果再写入相同的对象,就只存储序列号。读出这些对象时,它们的引用相同,因为在内存中实际上存储的只是一个对象
  • 如果数组中的所有元素都是可序列化的,这个数组就是可序列

8.2.6 随机访问数据

  • Java 提供了 RandomAccessFile 类,允许从文件的任何位置进行数据的读写
  • 当创建一个 RandomAccessFile 时,可以指定两种模式( “r"或"rw”) 之一。模式"r"表明这个数据流是只读的,模式"rw"表明这个数据流既允许读也允许写。
RandomAccessFile raf = new RandomAccessFile("test.dat", "rw");
  • 以调用 raf .seek(position) 方法将文件指针移到指定的位置
    在这里插入图片描述
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值