Java 集合,也叫做容器,主要由两大接口派生:Collection 接口,主要用于存放单个元素,另一个是 Map 接口,主要存放键值对
根据每个集合的特点来选择合适的集合,比如【需要存放键值对的时候,使用 Map 接口下的集合,需要排序则是 TreeMap,不需要则 HashMap】【存放的是单个元素时,选择 Collection 接口下的集合,元素可以重复选择 List,可以不重复则选择 Set】
ArrayList 可以存储任何类型的对象,包括 null,但不建议,因为没有意义,反而有什么忘记做判断空时,会导致空指针异常
以无参数构造方法创建 ArrayList 的时候,实际上初始化赋值的是一个空数组,当真正对数组进行添加元素的时候,才会分配容量,数组容量扩为 10。有参构造函数创建,传入带初始容量参数,那分配的数组容量就是传入的实参数。传入一个集合,那么就会将这个集合转为数组,作为初始数组【如果转为数组不是 Object 类型的,会重新拷贝这个数组将类型改为 Object】。ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(因为扩容新数组的长度是 旧数组长度 + 就数组长度/2,偶数则1.5倍,基数会丢掉小数部分)
一般需要对一个集合使用自定义排序的时候,就要重写 compareTo() 或者 compare() 方法。如果需要同时实现两种排序方式,比如分别对员工的年龄和入职日期来排序,可以重写 compareTo() 和 自制的 Comparator中的排序方法,也可以使用两个 Comparator 自制排序
HashMap 和 TreeMap 都继承自 AbstractMap,TreeMap 比 HashMap 多了对集合中的元素根据键排序的能力【继承NavigableMap】和对集合内元素的搜索能力【继承SortedMap】
底层是数组和链表结合在一起使用,HashMap 通过 key 的 hashcode 值,再通过 hashcode 判断当前元素存放的位置,如果存在元素,判断该元素与要存入的元素 hashcode 值以及 key 是否相同,相同则直接覆盖,不相同就通过拉链法解决冲突【拉链法就是将链表和数组相结合,也就是说创建一个链表数组,数组中每一个都是一个链表,若遇到哈希冲突,则将冲突的值加入链表中】
在解决哈希冲突有所裱花,当链表长度大于 8 (转红黑树前会判断,如果数组长度小于 64,则会先进行数组扩容,而不是转红黑树)时,将链表转化为红黑树,以减少搜索时间
JDK 1.7 之前是由于当有多个元素需要进行扩容的时候,多个线程同时对链表进行操作,头插法可能会导致链表中的节点指向错误的位置,从而形成一个环形链表,进而使得查询元素的操作陷入死循环无法结束。JDK 1.8 为解决这个问题,次啊用了尾插法来避免链表倒置,使得插入的节点永远都是存放在链表的末尾,避免了链表中的环形结构【但不建议在多线程情况使用 HashMap,因为还是会存在数据覆盖的问题,并发环境下使用 ConcurrentHashMap】
比如:两个线程 1,2,同时进行 put 操作,并且发生了哈希冲突。不同线程可能在不同的时间获得 CPU 执行的机会,当线程 1 执行完哈希冲突判断后,由于时间耗尽挂起,线程 2 先完成了插入操作。随后线程 1 获得执行时间,由于之前已经进行过了 hash 碰撞的判断,所以此时会直接插入,这就导致了线程 2 插入的数据被线程 1 覆盖了