JDK《JDK+集合+IO学习》重点

  1. JDK
  2. 集合
    1. ArrayList
    2. LinkedList
    3. HashMap
  3. I/O
    1. 五大 I/O 模型比较

JDK

八大基本数据类型?byte,short,int,long,float,double,char,boolean(1个字节)

array[1][2]是第几行几列?1行2列

String类?字符串不可变。实现原理:内部的private final char[]字段,以及没有任何修改char[]的方法。

StringBuffer?StringBuild?

Object类?getClass(),equals()、toString()、hashCode()、clone(),wait()、notify()、notifyAll(),finalize()。

异常继承体系?

1598511670173

反射用法?通过Class实例获取class信息的方法称为反射(Reflection)。String.class。obj.getClass()。Class.forName(“java.lang.String”); 或者Class对象,该类有getFeilds等方法。

动态代理用法?Proxy.newProxyInstance(ClassLoader, Interface,InvocationHandler)

元注解?4个。Java提供的使用反射API读取Annotation的方法。class.isAnnotationPresent(Class class)

集合

ArrayList

源码:

  • 数据结构:使用一个数组名为elementData来存数据。
  • 构造时不传初始数量则数组取{}。传了数量则默认改数量长度。
  • 扩容时,每次扩容1.5倍:capcity + (capacity >> 1)。小于10则一次性扩容到10,否则扩容1.5倍。扩容时考虑Integer.max限制。扩容时机是,发现不够用的时候才会扩容。
  • 扩容调用Arrays.copyOf(),进一步调用System.arraycopy,底层是调用native方法,效率较高。
  • 扩容是老数组复制到新数组,操作代价很高。所以尽量赋初值,避免扩容。
  • 也可根据实际需求,通过调用ensureCapacity()方法来手动增加ArrayList实例的容量。
  • 缩容:需要注意的一个点是,remove缩容后,需要对elementData的最后的值赋full,避免内层泄露。因为缩容是后面的元素整体向前Copy。

Fail-Fast机制:ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

LinkedList

源码:

  • 数据结构:底层通过双向链表实现
  • 双向链表的每个节点用内部类Node表示。
  • LinkedList通过firstlast引用分别指向链表的第一个和最后一个元素。
  • getFirst(), getLast():直接拿。已经缓存在变量first和last上了。为空时报错。
  • removeFirst():注意手动赋值first.next为null,以帮助GC。
  • removeLast():注意手动赋值last.prev为null,以帮助GC
  • remove(e):删除第一次出现的这个元素,没有则返回false。判断的依据是equals。由于LinkedList可存放null元素,也可以删除null。
  • remove(index):判断改下标有无元素,有的话直接unlink()即可。
  • **add(E e)**:直接加到末尾。
  • add(int index, E element),该方法是在指定下表处插入元素,需要先通过线性查找找到具体位置,然后修改相关引用完成插入操作。
  • addAll(index, c):考虑到效率问题,不是一个一个加。而是直接在最后面继续构造链表。
  • clear():为了让GC更快可以回收放置的元素,需要将node之间的引用关系赋空。

HashMap

HashSet里面有一个HashMap(适配器模式),所以底层是HashMap实现。key是元素,value全部用一个final object填充。

HashMap的key和value都可以放null。

初始容量(inital capacity,默认16)和负载系数(load factor,决定什么时候扩容,默认0.75)。

如果要将自定义的对象放入到HashMapHashSet中,需要**@Override** hashCode()equals()方法。

table.length必须是2的指数:是为了hash(k)&(table.length-1)等价于hash(k)%table.length。提升性能。原因是HashMap要求table.length必须是2的指数,因此table.length-1就是二进制低位全是1,跟hash(k)相与会将哈希值的高位全抹掉,剩下的就是余数了。

源码:

  • 数据结构:数组 + 链表 + 红黑树。元素用Node内部类,有 key,value,hash 和 next 四个属性。红黑树时转为TreeNode。可根据第一个节点数据类型是 Node 还是 TreeNode 来判断该位置下是链表还是红黑树。
  • get()方法:
    1. 找到buckets。(用当N是2的倍数时, M & (N-1) 等价 M % N ,简化了运算)。
    2. 判断是链表还是红黑树,遍历找到元素,根据equals判断相等,返回。
  • put()方法:
    1. 查找有没有该元素,有则直接返回。
    2. 如果还没有,则使用头插法,将元素插入头部。
    3. 插入前,会考虑是否需要扩容并重新Hash。
  • 扩容方法:
  • remove()方法:
    1. 先找到key值对应的entry
    2. 删除该entry(修改链表的相应引用)。

put()方法详解

  1. 数组为空?扩容,默认扩容到16
  2. 第一个元素相等?先标记,最后根据入参,决定是否覆盖。返回倒是都是返回旧值。
  3. 第一个元素为TreeNode?调用红黑树遍历
  4. 插入链表最后面。插入的是第8个?链表转为红黑树。
  5. 链表中找到相等的Node?先标记,最后根据入参,决定是否覆盖。返回倒是都是返回旧值。
  6. 最后整体判断Size,看是否超过阈值threshold需要扩容。

resize()扩容方法详解

  1. 获取容量
    1. new HashMap() 后的第一次put():取默认容量、默认阈值。
    2. new HashMap(int x) 后的第一次put():将阈值直接赋值给容量,此时二者相等。阈值是构造方法设置的,取第一个大于x的2的指数。
    3. 普通扩容:容量、阈值各 * 2。
  2. 扩容
    1. 桶内只有1个元素:直接copy。
    2. 桶内为红黑树:调用红黑树的split()方法分裂到新数组。
    3. 桶内为链表:依次重新计算桶位置,分裂为2个链表。假设老容器桶位置为j,则两个链表依次存入 j 和 capacity + j 位置。

红黑树算法?后续补充。

1.7 HashMap 使用纯链表,没有引入红黑树。在扩容时,如果多线程操作,可能造成环形链表,导致CPU飙升。

I/O

五大 I/O 模型比较

  • 阻塞
  • 非阻塞
  • I/O多路复用
  • 信号驱动
  • 异步

image-20220630213452270

同步、异步、阻塞:

  • 同步 I/O: 应用进程在调用 recvfrom 操作时会阻塞。
  • 异步 I/O: 不会阻塞。

阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O 都是同步 I/O,虽然非阻塞式 I/O 和信号驱动 I/O 在等待数据阶段不会阻塞,但是在之后的将数据从内核复制到应用进程这个操作会阻塞。


转载请注明来源