(1)自定义类实现Comparable接口
import java.util.Comparator; import java.util.HashMap; import java.util.TreeMap; class Solution { public static void main(String[] args) { TreeMap<Student, Integer> treeMap = new TreeMap<>(); treeMap.put(new Student(1,"xixi"), 1); treeMap.put(new Student(1,"haha"), 1); } } class Student implements Comparable<Student>{ private int age; private String name; public Student(int age, String name) { this.age = age; this.name = name; } @Override public int compareTo(Student o) { return this.age - o.age; } }(2)创建TreeMap对象时,指定比较器 Comparator
import java.util.Comparator; import java.util.HashMap; import java.util.TreeMap; class Solution { public static void main(String[] args) { TreeMap<Student, Integer> treeMap = new TreeMap<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getAge() - o2.getAge(); } }); treeMap.put(new Student(1,"xixi"), 1); treeMap.put(new Student(1,"haha"), 1); } } class Student{ private int age; private String name; public Student(int age, String name) { this.age = age; this.name = name; } public int getAge() { return age; } public String getName() { return name; } }服务器:
操作系统:CentOs内存:2GB第一种情况:put元素之前,会判断HashMap是否为空,或者长度为0
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;第二种情况:put元素之后,看当前元素的总数是否大于阈值
//jdk 1.8源码,可以看出,HashMap在put元素之后,会计算当前元素的总数是否大于阈值 //(阈值 = 容量 * 负载因子) if (++size > threshold) resize(); (3)扩容之后的容量 对于第一种情况:初始化的扩容,扩容之后为16; 对于第二种情况:扩容为原来的两倍。 (4)扩容之后,旧的hash表中的元素怎么移到新的hash表中 ------判断如果是红黑树节点,则调用TreeNode的split方法,来判断是否需要将原来的红黑树节点拆分为两个节点 ------else:jdk1.8之前:
***先新建一个数组,数组长度为原数组的2倍 ***循环遍历原数组的每一个键值对,得到键的hash然后与新数组的长度进行&运算得到新数组的位置。然后把键值对放到对应的位置。 ***(需要遍历原hash表中的每个元素)jdk1.8之后
我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”。
这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块就是JDK1.8新增的优化点。有一点注意区别,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是JDK1.8不会倒置。
Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; //这一步是否为0只需要看元素的二进制数对应数组长度的二进制数1那个位置是否为0. if ((e.hash & oldCap) == 0) { //e.hash & oldCap 就是新增的bit位 if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; //这里很重要,新的位置为原老所处的位置+原数组的长度 } (5)为什么都是2的N次幂的大小 在源码中,向集合中添加元素时,会使用(n - 1) & hash的计算方法来得出该元素在集合中的位置(n就是tab.length)。当hash表的长度为2的幂次方的时候,n-1的二进制形式就是…1111 1111111(低位就是连续的1),这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞。new一个对象的具体过程:
(1)类加载检查 当虚拟机遇到一条new指令之后,它会根据指令参数到常量池中查看是否可以定位到类的符号引用,如果没有,则进行类加载过程
(2)在堆空间上分配内存 一个对象所需的空间大小,在类加载阶段就已经知道,分类堆内存的过程就是划分出一块对象大小的内存出来。根据堆内存空间是否规整(所用的垃圾收集器是否有整理功能),为对象分配内存有两种方式:如果堆内存规整,则使用碰撞指针法(指针指向使用和未使用空间的临界位置);如果不是规整的,则使用空闲列表法(列表记录哪些空间没有被使用)
(3)初始化零值 将分配到的内存空间都初始化零值,这步可以保证对象的实例变量不用初始化就可以使用
(4)设置对象头 对象头包含了对象的一些必要设置,比如属于哪个类、对象的hash值、GC分代年龄等等
(5)执行init方法 这步是将对象按照程序员的意愿进行初始化。
简化的回答: Instence instence = new Instence(); 发生了啥? (1)在堆空间分配内存 (2)在堆空间上初始化对象 (3)将堆空间的地址赋值给引用变量
多线程环境下,分配内存可能出现并发问题,怎么解决? (1)CAS+失败重试 :保证指针操作的原子性 (2)本地线程分配缓冲(Thread Local Allocation Buffer, TLAB) :每个线程在分配内存时,JVM会为每个内存预留一部分堆内存空间,这部分的空间叫做本地线程分配缓冲,只有在申请TLAB才需要同步锁定。(一个线程可能进行多次对象内存空间分配)
(占坑,我自己也搞不明白)
联合索引本质: 当创建 (a,b,c)联合索引时,相当于创建了(a)单列索引,(a,b)联合索引以及 (a,b,c)联合索引,想要索引生效的话,只能使用 a和a,b和a,b,c三种组合;
(1) 什么是线程池?使用线程池的好处?
(2)线程池类、构造函数的核心参数
(3)线程池的原理
(4)Excutors提供的四个线程池
(5)如何设置线程池初始容量
线程的创建 了解spring吗 了解servlet吗 8. java I/O相关的简单介绍一下 9. 关于maven说一下 10. Hashmap了解吗 11. 那你说说关于红黑树 12. Java 反射机制 13. 网络层次 HTTP TCP 14. 对称加密 非对称加密 15. 乐观锁 悲观锁 16. 设计模式 说几种
