Java面试题、八股文学习之路day05

41.说说你平时是怎么处理 Java 异常的

1. 捕获和处理异常(try-catch)

使用 try-catch 块来捕获可能抛出的异常。为了提高代码的可维护性,通常会捕获特定的异常类型,而不是使用通用的 Exception。

捕获异常时要根据实际情况决定是否处理异常,或者将其重新抛出。

try {
    // 可能抛出异常的代码
} catch (SpecificException e) {
    // 处理特定的异常
} catch (AnotherSpecificException e) {
    // 处理其他特定异常
}

2. 使用多个 catch 块

根据不同的异常类型,使用多个 catch 块来分别处理不同的异常。这样做有助于清晰地了解每个异常的处理方式。

try {
    // 可能抛出异常的代码
} catch (IOException e) {
    // 处理文件读取异常
} catch (SQLException e) {
    // 处理数据库连接异常
} catch (Exception e) {
    // 处理其他异常
}

3. 不要过度捕获异常

过度捕获异常(例如只使用 catch (Exception e))会掩盖潜在的问题,因此要尽量捕获特定的异常类型。

如果不打算处理异常,可以直接抛出或记录日志,但不要只是空处理。

try {
    // 可能抛出异常的代码
} catch (SpecificException e) {
    // 记录异常或重新抛出
    throw e; // 如果不打算处理,可以将异常重新抛出
}

4. 使用 finally 释放资源
在 finally 块中释放资源,如关闭文件、数据库连接等。finally 块无论是否发生异常都会执行。

FileReader fr = null;
try {
    fr = new FileReader("somefile.txt");
    // 处理文件
} catch (IOException e) {
    // 处理文件读取异常
} finally {
    // 释放资源
    if (fr != null) {
        try {
            fr.close();
        } catch (IOException e) {
            // 处理关闭文件时可能发生的异常
        }
    }
}

5. 异常链

在某些情况下,捕获到的异常可能需要被包装成另一个异常再抛出,形成一个异常链。这样做有助于保留原始异常的堆栈信息。

try {
    // 可能抛出异常的代码
} catch (IOException e) {
    throw new CustomException("Custom error message", e); // 将原始异常包装成新的异常
}

6. 自定义异常

有时需要创建自定义异常,以便更好地表达特定的错误场景。自定义异常通常是继承 Exception 或 RuntimeException 类。

class MyCustomException extends Exception {
    public MyCustomException(String message) {
        super(message);
    }
}

try {
    // 业务逻辑
    throw new MyCustomException("Something went wrong");
} catch (MyCustomException e) {
    // 处理自定义异常
    System.out.println(e.getMessage());
}

7. 日志记录

异常应该记录到日志中,以便后期排查和分析。日志记录通常使用日志框架,如 SLF4J、Log4j 或 java.util.logging。

try {
    // 可能抛出异常的代码
} catch (Exception e) {
    logger.error("An error occurred: ", e);
}

8. 避免异常控制流

异常不应当被用作正常的控制流。比如,不应该使用异常来控制程序流程或跳过某些操作。异常应该用于捕获异常的情况而非普通流程。

if (someCondition) {
    // 做某事
} else {
    // 处理错误逻辑,避免使用异常控制流
}

9. 异常的语义和层次结构

在不同的层次结构中,应该有明确的异常分类。例如,数据库层应该抛出 SQLException,服务层抛出 ServiceException,而 UI 层则抛出 UIException。

42. HashMap 的长度为什么是 2 的 N 次方呢?

为了能让 HashMap 存数据和取数据的效率高,尽可能地减少 hash 值的碰撞,也就是说尽量把数据能均匀的分配,每个链表或者红黑树长度尽量相等。

我们首先可能会想到 % 取模的操作来实现。

下面是回答的重点哟:

取余(%)操作中如果除数是 2 的幂次,则等价于与其除数减一的与(&)操作(也就是说 hash % length == hash
&(length - 1) 的前提是 length 是 2 的 n 次方)。并且,采用二进 制位操作 & ,相对于 %
能够提高运算效率。

这就是为什么 HashMap 的长度需要 2 的 N 次方了。

43. HashMap 与 ConcurrentHashMap 的异同

  1. 都是 key-value 形式的存储数据;
  2. HashMap 是线程不安全的,ConcurrentHashMap 是 JUC 下的线程安全的;
  3. HashMap 底层数据结构是数组 + 链表(JDK 1.8 之前)。JDK 1.8 之后是数组 + 链表 + 红黑
    树。当链表中元素个数达到 8 的时候,链表的查询速度不如红黑树快,链表会转为红黑树,红黑树查询速度快;
  4. HashMap 初始数组大小为 16(默认),当出现扩容的时候,以 0.75 *数组大小的方式进行扩容;
  5. ConcurrentHashMap 在 JDK 1.8 之前是采用分段锁来现实的 Segment + HashEntry ,Segment 数组大小默认是 16 ,2 的 n 次方;JDK 1.8 之后,采用 Node + CAS + Synchronized 来保证并发安全进行实现。

44.说说深拷贝和浅拷贝?

  • 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址。
  • 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存。
  • 使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
  • 最好是结合克隆已经原型模式联系在一起哈,记得复习的时候,把这几个联系起来的。

45.红黑树有哪几个特征?

红黑树的特征如下:

1. 节点的颜色
每个节点要么是红色,要么是黑色。

2. 根节点是黑色
红黑树的根节点必须是黑色。

3. 红色节点的子节点是黑色(无两个红色节点相邻)
红黑树不能有两个连续的红色节点,也就是说,任何红色节点的两个子节点必须是黑色。这保证了树的平衡。

4. 从任何节点到其叶子节点的路径上的黑色节点数相同(黑色高度相同)
从任意节点到其所有叶子节点的路径上,经过的黑色节点的数量必须相同。这个属性称为黑色高度一致性。

5. 叶子节点是黑色的(虚拟的叶子节点)
红黑树中的叶子节点是黑色的,并且叶子节点并不存储数据,通常被视作虚拟节点(有时也称作空节点或NIL节点)。

6. 每个节点的黑色高度相同
从任意节点到其所有叶子节点的路径上,经过的黑色节点数是相同的,这也意味着树的所有路径从根到叶子的黑色节点数是一样的。

总结:
红黑树的这些特征(特别是红色节点不能连续和黑色高度一致性)保证了树的平衡性,从而能够在最坏情况下保持 (O(log n)) 的操作时间复杂度。

Java篇结束,接下来开始的是JVM篇,敬请期待…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值