概念与内存
面向对象三大特性
重载和重写的区别
构造器不能被重写,但可以被重载
初始化
抽象类和接口的区别
基本类型与包装类
什么是基本类型,int、double这些是;Integer、Double这些是包装类。
==和equals的区别
成员变量和类变量
final关键字用法
深拷贝和浅拷贝
String
Error和Exception的区别
Object中的方法
clone():返回对象的浅拷贝??
getClass():获取Class类对象。
equals():比较值和对象。
hashcode():
wait()/notify()/notifyAll():
泛型要详细了解
Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
引入泛型的意义在于:适用于多种数据类型执行相同的代码(代码复用);泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
泛型使用:泛型类、泛型接口、泛型方法
泛型上下限:<? extends A>表示该类型参数可以是A(上边界)或者A的子类类型。
类型擦除是一个什么样的机制,怎么做的,为什么
反射要详细了解
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射基础:Class类和类加载
反射的使用:
class类对象获取
- 根据类名:类名.class
- 根据对象:对象.getClass()
- 根据全限定类名:Class.forName(全限定类名)
Constructor类、Field类、Method类常用方法,getMethod()这样的
数据结构
Java中数据结构在util.Collection和util.Map包下,包括三大类型:List、Set和Map。
Hashmap的插入和扩容
hashmap底层首先使用了一个hash数组,数组中的每个节点可以看做一个桶。一个桶中可以放入多个Node<K,V>键值对。
(一)插入的过程中有以下几个点,其中有几个是重点:
- 判断当前容量大小是否为空,如果为空(未设置容量初始值),则把容量扩充为16。
- 获取key的hashCode,使用hash函数,对hashCode进行扰动处理,计算出元素的下标。
这一步就比较重要了,在java8中的计算:一次位运算一次异或。
首先根据key的值得到hashcode
将hashcode高16位和低16位进行异或操作,增加低16位随机性。(扰乱函数?)
最后根据hashcode&(len-1)得到数组下标
一般用Integer、String 这种不可变类当 HashMap 当 key,而且 String 最为常用。
- 因为字符串是不可变的,所以在它创建的时候 hashcode 就被缓存了,不需要重新计算。这就是 HashMap 中的键往往都使用字符串的原因。
- 因为获取对象的时候要用到 equals() 和 hashCode() 方法,那么键对象正确的重写这两个方法是非常重要的,这些类已经很规范的重写了 hashCode() 以及 equals() 方法。
- 根据下标判断有无hash碰撞,如果没有,则直接放入桶中。
- 如果发生碰撞,比较两个key是否相同,相同则覆盖,不同则则以链表的方式插入到尾部(尾插法)。
- 如果插入后链表的长度超过了阈值(TREEIFY_THRESHOLD = 8),首先要看数组长度是否超过64,如果没有超过64则不转为红黑树而是扩容,否则把链表转为红黑树。
- 插入成功后,如果元素个数到达了阈值(size = 容量 * 阈值 ),则执行扩容操作判断(容量最大值为1<<31)
这里扩容的过程也是一个重点
下面的方法是java7中的代码,java8做了一定的改进
一个改进是: resize 之后,元素的位置在原来的位置,或者原来的位置 +oldCap (原来哈希表的长度)。不需要像 JDK1.7 的实现那样重新计算hash ,只需要看看原来的 hash 值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引 + oldCap ”。这个设计非常的巧妙,省去了重新计算 hash 值的时间。
void transfer(Entry[] newTable) {
Entry[] src = table; //src引用了旧的Entry数组
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组
Entry<K,V> e = src[j]; //取得旧Entry数组的每个元素
if (e != null) {
src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置
e.next = newTable[i]; //标记[1]
newTable[i] = e; //将元素放在数组上
e = next; //访问下一个Entry链上的元素
} while (e != null);
}
}
}
- 扩容成功后,对元素的下标进行重新计算。
(二)1.7和1.8HashMap的变化
(1)首先区别体现在hashmap插入元素put的过程中。 区别在两处:
解决哈希冲突时,JDK1.7 只使用链表,JDK1.8 使用链表+红黑树,当满足一定条件,链表会转换为红黑树。
这里有一个问题就是,为什么选用红黑树而不是其他结构??
链表插入元素时,JDK1.7 使用头插法插入元素,在多线程的环境下有可能导致环形链表的出现,扩容的时候会导致死循环。因此,JDK1.8使用尾插法插入元素,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了,但JDK1.8 的 HashMap 仍然是线程不安全的,具体原因会在另一篇文章分析。
这里也是很重要的是为什么会造成线程不安全、链表成环??
(2)其次就是上面提到的扩容过程的优化
(三)了解哪些hash算法
Hash函数是指把一个大范围映射到一个小范围,目的往往是为了节省空间,使得数据容易保存。 比较出名的有MurmurHash、MD4、MD5等等。
MD5加密把一个很长的字符串变为32位的。
HashMap
首先要介绍hashmap底层数据结构
默认初始容量是16,负载因子是0.75。HashMap 的容量必须是2的N次方,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传 9,容量为16。
提升在 hash 冲突严重时(链表过长)的查找性能,使用链表的查找性能是 O(n),而使用红黑树是 O(logn)。
其次要说清楚hashmap插入过程
然后要说明白hashmap的扩容过程
红黑树的特点也经常被问道 cloud.tencent.com/developer/n…
最后说下hashmap和其他几个兄弟的区别
List & Queue
ArrayList和LinkedList的区别
I/O
NIO之IO多路复用
Reactor模式讲挺好,但不是关于epoll的,pdai.tech/md/java/io/…
NIO概念
Netty-NIO+AIO框架
Linux I/O 模型
前四种 I/O 模型的主要区别在于第一个阶段,而第二个阶段是一样的: 将数据从内核复制到应用进程过程中,应用进程会被阻塞。
第三种当然是关注度最高的:
BIO:Blocking IO,阻塞式IO,最初传统常见的,问题是:
字节流和字符流
使用过字节流来处理图片,填写入职资料中的上传附件,不知道用的是什么,不是的是不是字符流?
Java I/O设计中的装饰着模式 pdai.tech/md/java/io/…