经验总结(四):java常用类库

Java常用类库

一:异常和错误分类

运行时异常:

  • NullPointerException - 空指针引用异常
  • ClassCastException - 类型强制转换异常
  • IllegalArgunmentException - 传递非法参数异常
  • IndexOutOfBoundsException - 下标越界异常
  • NumberFormatException - 数字格式异常

非运行时异常

  • ClassNotFoundException - 找不到指定class的异常
  • IOException - IO操作异常

错误

  • NoClassDefFoundError - 找不到class定义的异常
  • StackOverflowError - 深递归导致栈被耗尽而抛出的异常
  • OutOfMemoryError - 内存溢出异常

程序开发使用

  • 对于运行时异常,系统开发中可定义自身的BaseRuntime基类,使其他异常继承这个异常类,让前段端接收到自身定义的异常时便可处理,这里的处理时展示友好的弹窗文字等。
  • try-catch块会影响JVM的优化。
  • 遇到异常时,便会实例化一个Exception,并且对当前栈进行快照,开销较大。

二:Collection体系

排序(后续补充各种算法的实现)

  • 不稳定排序算法:快排、堆排。

Collection

  • List
    • 有序、可重复、可通过索引值操作元素
    • 底层是数组时:查询块,增删慢。ArrayList线程不安全,效率高。Vector线程安全,效率低
    • 底层是链表时:LinkedList查询慢,增删快。线程不安全,效率高。
    • ArrayList的get复杂度为O(1),其余都为O(n)
    • LinkedList的时间复杂度都为O(1),这里的复杂度包括查找的。
  • Set
    • 底层是哈希表HashSet保证元素唯一性。hashCode()、equals()
    • 底层是二叉树TreeSet 保证元素排序。自然排序,让对象所属的类(T)去实现comparable接口,无参构造。比较器接口comparator,带参构造。
    • 由于HashSet是特殊的HashMap。
    • TreeSet由于需要排序,所以性能比HashSet低。

HashMap(后续补充红黑树与链表的转换)

  • HashMap在java8以前为数组加链表的形式。位运算,长度为2的N次幂,首次使用时进行初始化。
  • java8以后为数据+链表+红黑树,通过TREEIFY_THRESHOLD来控制是否转换为红黑树,这种把最坏情况从O(n)提高到O(logn)。通过控制UNTREEIFY_THRESHOLD来控制红黑树转为链表

HashMap进行put方法时的逻辑

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

多线程下,如果多个线程都检测到需要resize(),锁竞争时会导致死锁。rehashing也是一个比较耗时的过程。

HashTable

实现与SynchronizedMap除了锁定的对象不同以外几乎没什么区别。

优化HashTable,也就要深入了解并发编程(后续会补充并发编程相关文章)

  • 通过锁细粒度化,将整锁拆解成多个锁进行优化

ConcurrentHashMap

concurrentHashMap就是将HashTable进行了优化。java8之前concurrentHashMap采用的是分段锁,java8之后concurrentHashMap采用CAS+synchronized使得锁更细化。

  • 使用volatile标示的sizeCtl,作为hash表扩容的标示量。-1代表正在初始化,-n代表有n-1个线程在进行扩容操作。正数代表下次扩容的大小。
  • ConcurrentHashMap的key不允许为null,这与HashMap相反。

ConcurrentHashMap:put方法的逻辑

  • 判断Node[]数组是否初始化,没有则进行初始化操作
  • 通过hash定位数组的索引坐标,是否有Node节点,如果没有则使用CAS进行添加(链表的头节点),添加失败则进入下次循环。
  • 检查到内部正在扩容,就帮助它一起扩容。
  • 如果f != null ,则使用synchronized锁住f元素(链表/红黑树的头元素)
    • 如果是Node(链表结构)则执行链表的添加操作。
    • 如果是TreeNode(树型结构)则执行树添加操作。
  • 判断链表长度已经达到临界值8,当然8是默认值,可以对其进行调整。当节点树超过这个值就系要把链表转换成树结构。

总结:

  • ConcurrentHashMap在1.8以后相比之前使用分段式锁Segment,它把锁拆得更细。
    • 使用无锁操作CAS插入头节点,失败则循环重试。
    • 如果头节点已经存在,则尝试获取头节点的同步锁,再进行操作。
  • HashMap线程不安全,数组+链表+红黑树
  • Hashtable线程安全,锁住整个对象,数组+链表
  • ConccurentHashMap线程安全,CAS+同步锁,数组+链表+红黑树

三:J.U.C包

J.U.C包提供了并发编程的解决方案.

  • CAS是java.util.concurrent.atomic包的基础
  • AQS是java.util.concurrent.locks包以及一些常用类比如Semophore,ReentrantLock等类的基础。

J.U.C包的分类

  • 线程执行器executor
  • 锁locks
  • 院子变量类atomic
  • 并发工具类tools
  • 并发集合collections

并发工具类

  • 闭锁 CountDownLatch
    • 让主线程等待一组时间发生后继续执行,事件指的是CountDownLatch里的countDown()方法。
  • 栅栏 CyclicBarrier
    • 阻塞当前线程,等待其他线程,所有线程必须同时达到栅栏位置后,才能继续执行。
    • 所有线程达到栅栏处,可以触发执行另外一个预先设置的线程。
  • 信号量 Semaphore
    • 控制某个资源可被同时访问的线程个数
  • 交换器 Exchanger
    • 两个线程达到同步点后,可以相互交换彼此的数据

BlockingQueue

BlockingQueue提供可阻塞的入队和出队操作。它主要用于生产者-消费者模式,在多线程场景时生产者线程在队列尾部添加元素,而消费者线程则在队列头部消费元素,通过这种方式能够达到将任务的生产和消费进行隔离的目的。

添加元素:

  • add()添加成功返回true,添加失败返回异常
  • offer()添加成功返回true,添加失败返回false,如果队列满了可以指定一个时间(等待的时间)
  • put()如果队列满了则等待,直到可以添加为止

取出元素:

  • take()从头部取出元素,如果队列为空,则一直等待。与put对应.
  • poll()从头部拉去元素,如果有则取出,可以指定等待时间,如果等待中断则抛出异常。对应offer()

remainingCapacity()获取当前队列剩余可存取元素。

remove()从队列中移除指定的对象。

cantains()判断队列中是否含有该对象。

drainTo()将队列中的对象转移到对应的集合中。

BlockingQueue队列的实现

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列;
  • LinkedBlockingQueue:一个由链表结构组成的由界/无界阻塞队列;
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列;
  • DealyQueue:一个使用优先级队列实现的无界阻塞队列;
  • SynchronousQueue:一个不存储元素的阻塞队列;
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列;
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列;

Java的IO机制

io的分类

  • BIO
    • Block-IO:InputStream和OutputStream,Reader和Writer。
  • NIO
    • NonBlock-IO : 构建多路复用的、同步非阻塞的IO操作。(同步、阻塞建议看一篇《如何给女朋友将同步与阻塞》文章)
    • NIO的核心为三部分:Channels、Buffers、Selectors
    • Channels分为:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel
    • FileChannel中:transferTo可以把FileChannel中的数据拷贝到另外一个Channel中;transferFrom可以把另外一个Channel中的数据拷贝到FileChannel中,它避免了两次用户态和内核态间的上下文切换,即“零拷贝”,效率较高。
    • Buffers分为:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、MappedByteBuffer
    • nio底层使用系统级的多路复用;select\poll\epoll,在单线程中可以同时处理多个网络io。
    • 一个进程中能打开的最大连接数:
      • select能打开最大连接数是32的整数倍,如32位操作系统则为32 * 32,64位操作系统则为32 * 64
      • poll由于底层是链表存储的所以没有最大限制
      • epoll连接数虽然有上线,但是很大。
    • FD剧增后带来的IO效率问题:
      • select、poll因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历素的的“线性下降”的性能问题
      • 由于epoll时根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll不会有“线性下降”的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。
    • 消息传递方式
      • select、poll内核需要将消息传递到用户空间,需要内核的拷贝动作
      • poll 通过内核和用户空间共享一块内存来实现,不需要拷贝,性能较高。
  • AIO
    • 俗称nio 2.0,它是基于事件和回掉机制
    • aio进一步加工处理结果
      • 基于回调:实现CompletionHandler接口,调用时触发回调函数
      • 返回Future:通过isDone()查看是否准备好,通过get()等待返回数据

                                                                                                生活要多点不自量力

你可能感兴趣的:(经验总结(四):java常用类库)