Java异常、集合与IO机制

10-1 Java异常体系 10-2 Java异常要点分析 10-3 Collection体系 10-4 HashMap 10-5 ConcurrentHashMap 10-6 J.U.C包的梳理 10-7 Java的IO机制

10-1 Java异常体系

  • 异常处理机制主要回答了三个问题

    • What:异常类型回答了什么被抛出
    • Where:异常堆栈跟踪回答了在哪抛出
    • Why:异常信息回答了为什么被抛出
  • Error和Exception的区别?
    Java 异常体系

    • Error:程序无法处理的系统错误,编译器不做检查
    • Exception:程序可以处理的异常,捕获后可能回复
    • 总结:前者时程序无法处理的错误,后者是可以处理的异常
    • 从责任角度看:
      • Error属于JVM需要负担的责任
      • RuntimeException是程序应该负担的责任
      • CheckedException可检查异常是java编译器应该负担的责任
  • 常见异常

    • RuntimeException:不可预知的,程序应当自行避免(比如加入if(name!=null))
      • NullPointerException - 空指针引用异常
      • ClassCastException - 类型强制转换异常
      • IllegalArgumentException - 传递非法参数异常
      • IndexOutOfBoundsException - 下标越界异常
      • NumberFormatException - 数字格式异常
    • 非RuntimeException:可预知的,从编译器校验的异常
      • ClassNotFoundException 找不到指定class的异常
      • IOexception IO操作异常
      • FileNotFoundException 找不到指定文件的异常
    • Error常见的异常:
      • NoClassDefFoundError 找不到class定义的异常
        • class或jar不存在
        • 类文件存在但是在不同的域中
        • 大小写问题,javac无视大小写
      • StackOverflowError 深递归导致栈被消耗尽而抛出的异常
      • OutOfMemoryError 内存溢出异常

10-2 Java异常要点分析

  • Java 的异常处理机制

    • 抛出异常:创建异常对象,交由运行时系统处理
    • 捕获异常:寻找合适的异常处理器处理异常,否则终止运行
  • Java 异常的处理原则

    • 具体明确:抛出的异常应能通过异常类名和 message 准确说明异常的类型和产生异常的原因;
    • 提早抛出:应尽可能早的发现并抛出异常,便于精确定位问题;
    • 延迟捕获:异常的捕获和处理应尽可能延迟,让掌握更多信息的作用域来处理异常。
  • 高效合理的异常处理框架

    在用户看来,应用系统发生的所有异常都是应用系统内部的异常

    • 设计一个通用的继承自RunntimeException 异常来统一处理
    • 其余异常都统一转译为上述异常 AppException
    • 在 catch 之后,抛出上述异常的子类,并提供足以定位的信息
    • 由前端接收 AppException 做统一处理
  • Java 异常处理消耗性能的地方

    • try-catch 块影响 JVM 的优化
    • 异常对象实例需要保存栈快照等信息,开销较大

10-3 Collection体系

Java 集合框架体系

  • 集合之 List 和 Set
    集合之 List 和 Set

10-4 HashMap

Map

  • HashMap(JDK8以前):数组+链表;(JDK8以后):数组+链表+红黑树

  • put 方法的逻辑

    1. 如果 HashMap 未被初始化过,则初始化
    2. 对 Key 求 Hash 值,然后再计算下标
    3. 如果没有碰撞,直接放入桶中
    4. 如果碰撞了,以链表的方式链接到后面
    5. 如果链表长度超过阀值,就把链表转成红黑树
    6. 如果链表长度低于6,就把红黑树转回链表
    7. 如果节点已经存在就替换旧值
    8. 如果桶满了(容量16*加载因子0.75),就需要 resize(扩容两倍后重排)
  • HashMap;如何有效减少碰撞

    • 扰动函数:促使元素位置分布均匀,减少碰撞几率
    • 使用 final 对象,并且采用合适的 equals() 和hashCode() 方法(String、Interger)
    • HashMap从获取 hash 到散列的过程
      static final int hash(Object key) {
          int h;
          return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
      }
      

      HashMap从获取 hash 到散列的过程

  • HashMap:扩容的问题

    • 多线程环境下,调整大小会存在条件竞争,容易造成死锁
    • rehashing 是一个比较好事的过程

10-5 ConcurrentHashMap

  • 如何优化 Hashtable

    • 通过锁细粒度化,将整锁拆解成多个锁进行优化
  • ConcurrentHashMap:CAS+synchronized 使锁更细化

  • put 方法的逻辑

    1. 判断 Node[] 数组是否初始化,没有则进行初始化操作
    2. 通过 hash 定位数组的索引坐标,是否有 Node 节点,如果没有则使用 CAS 进行添加(链表的头结点),添加失败则进入下次循环。
    3. 检查到内部正在扩容,就帮助它一块扩容
    4. 如果 f!=null,则使用 synchronized 锁住 f 元素(链表/红黑二叉树的头元素)
      • 如果使 Node(链表结构),则执行链表的添加操作
      • 如果使 TreeNode(树形结构),则执行树添加操作
    5. 判断链表长度已经达到临界值8,当节点数超过这个值就需要吧链表转换为树结构
  • HashMap、Hashtable、ConccurentHashMap 三者的区别:

    • HashMap 线程不安全,数组+链表+红黑树
    • Hashtable 线程安全,锁住整个对象,数组加链表
    • ConccurentHashMap 线程安全,CAS+同步锁,数组+链表+红黑树
    • HashMap 的 key、value 均可为 null,而其他两个类不支持

10-6 J.U.C包的梳理

  • java.util.concurrent:提供了并发编程的解决方案
    • CAS 是 java.util.concurrent.atomic 包的基础
    • AQS 是 java.util.concurrent.locks 包以及一些常用类比如 Semophore、ReentrantLock 等类的基础
  • J.U.C 包的分类
    • 线程执行器 executor
    • 锁 locks
    • 原子变量类 aotmic
    • 并发工具类 tools
    • 并发集合 collections
  • 并发工具类
    • 闭锁 CountDownLatch
    • 栅栏 CyclicBarrier
    • 信号量Semaphore
    • 交换器 Exchanger

10-7 Java的IO机制

  • Block-IO:InputStream 和 OutputStream,Reader 和 Writer
    BIO

    • BIO 特点:IO 执行的两个阶段都被阻塞了
    • BIO 好处:代码简单、直观
    • BIO 缺点:IO 的效率和扩展性存在瓶颈
  • NonBlock-IO:构建多路复用的、同步非阻塞的 IO 操作
    NIO

    • NIO 核心
      • Channels(网络文件传输、大数据传输)
        • FileChannel
        • DatagramChannel
        • SocketChannel
        • ServerSocketChannel
      • Buffers
        • ByteBuffer
        • CharBuffer
        • DoubleBuffer
        • FloatBuffer
        • IntBuffer
        • LongBuffer
        • ShortBuffer
        • MappedBytrBuffer
      • Selectors
  • IO多路复用:调用系统级别的 select\poll\epoll
    IO多路复用

    • select、poll、epoll 的区别

      支持一个进程所能打开的最大连接数FD 剧增后带来的 IO 效率问题消息传递方式
      select单个进程所能打开的最大连接数由 FD_SETIZE宏定义,其大小使32个整数的大小(在32位的机器上,大小使3232,64位的机器上为3264),我们可以对其进行修改,然后重新编译内核,但是性能无法保证,需要做进一步测试因为每次调用时都会对连接进行线性遍历,所以随着 FD 的增加会造成遍历速度的“线性下降”的性能问题内核需要将消息传递到用户空间,需要内核的拷贝动作
      poll本质上和 select 没有区别,但是它没有最大连接数的限制,原因使它是基于链表来存储的同上同上
      epoll虽然连接数有上限,但是很大,1G 内存的机器上可以打开10万左右的连接由于 epoll 是更具每个 fd 上的 callback 函数来实现的,只有活跃的 socket 才会主动调用 callback,所以在活跃 socket 较少的情况下,使用epoll 不会有“线性下降”的性能问题,但是所有 socket都很活跃的情况下,可能会游性能问题通过内核和用户空间共享一块内存来实现,性能较高
  • Asynchronous IO:基于事件和回调机制
    AIO

    • AIO 如何进一步加工处理结果
      • 基于回调:实现 CompletionHandler 接口,调用时触发回调函数
      • 返回 Future:通过 isDone() 查看是否准备好,通过 get() 等待返回数据
  • BIO、NIO、AIO 对比

    属性\模型阻塞 BIO非阻塞 NIO异步 AIO
    blocking阻塞并同步非阻塞但同步非阻塞并同步
    线程数(Server:Client)1:11:N0:N
    复杂度简单较复杂复杂
    吞吐量
Comment