一、java基础篇
1.接口和抽象类的区别
- 相似点
- 接口和抽象类都不能被实例化
- 实现接口或者抽象类的子类都必须实现这些抽象方法
- 不同点
- 抽象类可以包含普通方法和代码块, 接口里只能包含抽象方法, 静态方法和默认方法
- 抽象类可以有构造方法, 而接口没有
- 抽象类中的成员变量可以是各种类型, 接口中的成员变量只能是public static final类型, 并且必须辅助
2.重载和重写的区别
- 重载发生在同一个类中, 方法名相同、参数列表、返回类型、权限修饰符可以不同.
- 重写发生在子类中, 方法名、参数列表、返回类型都相同, 权限修饰符要大于父类方法, 声明异常范围要小于父类方法, 但是final和private修饰的方法不可重写
3.==和equals的区别
- ==比较基本类型时, 比较的是值, ==比较引用类型时, 比较的是内存地址
- equals是Object类的方法, 本质上与==一样, 但是有些类重写了equals方法, 比如String的equals被重写后, 比较的是字符值, 另外重写了equals后, 也必须重写hashcode方法
4.异常处理机制
- 使用try catch finaly捕获异常, finaly中的代码一定会执行, 捕获异常后程序会继续执行
- 使用throws声明该方法可能会抛出的异常类型, 出现异常后, 程序终止
5.HashMap原理
HashMap在JDK1.8之后是基于数组+链表+红黑树来实现的, 特点是key不能重复, 可以为null, 线程不安全
一、HashMap存取原理
- 计算key的hash值, 然后进行二次hash, 根据二次hash结果找到对应的索引位置
- 如果这个位置有值, 先进去equals比较, 如果结果为true则取代这个元素, 如果结果为false, 就使用高低位平移法将节点插入链表
二、HashMap的扩容机制
当添加某个元素后,数组的总的添加元素数大于了 数组长度 * 0.75(默认,也可自己设定),数组长度扩容为两倍。(如开始创建HashMap集合后,数组长度为16,临界值为16 * 0.75 = 12,当加入元素后元素个数超过12,数组长度扩容为32,临界值变为24)
6.HashMap线程不安全的原因
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
其中第六行代码是判断是否出现Hash碰撞, 假设两个线程A、B都在进行put操作, 并且通过Hash函数计算出的插入下标都是相同的, 当线程A执行完第六行代码后, 由于时间片耗尽被挂起, 而线程B得到了时间片后在该下标处插入了元素, 完成了正常的插入, 然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。
简单来说:put要进行两个动作
- 判断是否有Hash碰撞
- 通过Hash函数计算的插入下标处, 插入值
这两个动作是分开的, 不具备原子性(可以简单理解为put方法没加锁)
除此之前,还有就是代码的第38行处有个++size,我们这样想,还是线程A、B,这两个线程同时进行put操作时,假设当前HashMap的zise大小为10,当线程A执行到第38行代码时,从主内存中获得size的值为10后准备进行+1操作,但是由于时间片耗尽只好让出CPU,线程B快乐的拿到CPU还是从主内存中拿到size的值10进行+1操作,完成了put操作并将size=11写回主内存,然后线程A再次拿到CPU并继续执行(此时size的值仍为10),当执行完put操作后,还是将size=11写回内存,此时,线程A、B都执行了一次put操作,但是size的值只增加了1,所有说还是由于数据覆盖又导致了线程不安全。文章来源:https://www.toymoban.com/news/detail-422530.html
7.想要线程安全的HashMap怎么办
- 使用ConcurrentHashMap(并发的HashMap)
- 使用HashTable
- Collections.synchronizedHashMap()方法
8.HashTable和HashMap的区别
- HashTable的每个方法都用synchronized修饰,因此是线程安全的,但同时读写效率很低
- HashTable的Key不允许为null
- HashTable只对key进行一次hash,HashMap进行了两次Hash
- HashTable底层使用的数组加链表
9.ConcurrentHashMap原如何保证的线程安全
JDK1.8:采用CAS+Synchronized保证线程安全,每次插入数据时判断在当前数组下标是否是第一次插入,是就通过CAS方式插入,然后判断f.hash是否=-1,是的话就说明其他线程正在进行扩容,当前线程也会参与扩容;删除方法用了synchronized修饰,保证并发下移除元素安全
ConcurrentHashMap原理详解(太细了)文章来源地址https://www.toymoban.com/news/detail-422530.html
10.ArrayList和LinkedList的区别
- ArrayList底层是使用动态数组, 默认容量为10, 当元素数量达到容量时, 生成一个新的数组, 大小为前一次的1.5倍, 然后将原来的数组copy过来, 因为数组在内存中是连续的地址, 所以ArrayList查找数据更快, 由于扩容机制
到了这里,关于Java八股文的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!