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()。
异常继承体系?
反射用法?通过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通过
first
和last
引用分别指向链表的第一个和最后一个元素。 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)。
如果要将自定义的对象放入到HashMap
或HashSet
中,需要**@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()
方法:- 找到buckets。(用当N是2的倍数时, M & (N-1) 等价 M % N ,简化了运算)。
- 判断是链表还是红黑树,遍历找到元素,根据equals判断相等,返回。
put()
方法:- 查找有没有该元素,有则直接返回。
- 如果还没有,则使用头插法,将元素插入头部。
- 插入前,会考虑是否需要扩容并重新Hash。
- 扩容方法:
remove()
方法:- 先找到
key
值对应的entry
- 删除该
entry
(修改链表的相应引用)。
- 先找到
put()方法详解:
- 数组为空?扩容,默认扩容到16。
- 第一个元素相等?先标记,最后根据入参,决定是否覆盖。返回倒是都是返回旧值。
- 第一个元素为TreeNode?调用红黑树遍历
- 插入链表最后面。插入的是第8个?链表转为红黑树。
- 链表中找到相等的Node?先标记,最后根据入参,决定是否覆盖。返回倒是都是返回旧值。
- 最后整体判断Size,看是否超过阈值threshold需要扩容。
resize()扩容方法详解:
- 获取容量
- new HashMap() 后的第一次put():取默认容量、默认阈值。
- new HashMap(int x) 后的第一次put():将阈值直接赋值给容量,此时二者相等。阈值是构造方法设置的,取第一个大于x的2的指数。
- 普通扩容:容量、阈值各 * 2。
- 扩容
- 桶内只有1个元素:直接copy。
- 桶内为红黑树:调用红黑树的split()方法分裂到新数组。
- 桶内为链表:依次重新计算桶位置,分裂为2个链表。假设老容器桶位置为j,则两个链表依次存入 j 和 capacity + j 位置。
红黑树算法?后续补充。
1.7 HashMap 使用纯链表,没有引入红黑树。在扩容时,如果多线程操作,可能造成环形链表,导致CPU飙升。
I/O
五大 I/O 模型比较
- 阻塞
- 非阻塞
- I/O多路复用
- 信号驱动
- 异步
同步、异步、阻塞:
- 同步 I/O: 应用进程在调用 recvfrom 操作时会阻塞。
- 异步 I/O: 不会阻塞。
阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O 都是同步 I/O,虽然非阻塞式 I/O 和信号驱动 I/O 在等待数据阶段不会阻塞,但是在之后的将数据从内核复制到应用进程这个操作会阻塞。
转载请注明来源