Java集合类
ArrayList
juc线程安全
- Vector
- Collections.synchronizedList();
- new CopyOnWriteArrayList();
ArrayList 底层就是⼀个 Object[] 数组
ArrayList 底层数组默认初始化容量为 10
1、jdk1.8 中 ArrayList 底层先创建⼀个⻓度为 0 的数组
2、当第⼀次添加元素(调⽤ add() ⽅法)时,会初始化为⼀个⻓度为 10 的数组
当 ArrayList 中的容量使用完之后,则需要对容量进⾏扩容:
1、ArrayList 容量使⽤完后,会“⾃动”创建容量更⼤的数组,并将原数组中所有元素拷⻉过去,这会导致效率降低
2、优化:可以使⽤构造⽅法 ArrayList (int capacity) 或 ensureCapacity(int capacity) 提供⼀个初始化容量,避免
刚开始就⼀直扩容,造成效率较低
优点:
- 向 ArrayList 末尾添加元素(add() ⽅法)时,效率较⾼
- 查询效率⾼
缺点: - 扩容会造成效率较低(可以通过指定初始化容ᰁ,在⼀定程度上对其进⾏改善)
- 另外数组⽆法存储⼤数据ᰁ(因为很难找到⼀块很⼤的连续的内存空间)
- 向 ArrayList 中间添加元素(add(int index)),需要移动元素,效率较低
- 但是,向 ArrayList 中国位置增/删元素的情况较少时不影响;
- 如果增/删操作较多,可考虑改⽤链表
Linklist
LinkedList 底层是⼀个双向链表
优点: 增/删效率⾼
缺点: 查询效率较低
LinkedList 也有下标,但是内存不⼀定是连续的(类似C++重载[]符号,将循位置访问模拟为循秩访问)
LinkedList 可以调⽤ get(int index) ⽅法,返回链表中第 index 个元素
但是,每次查找都要从头结点开始遍历
Vector
**Vector 底层是数组,初始化容量为 10 **
扩容: 原容量使⽤完后,会进⾏扩容。新容量扩⼤为原始容量的 2 倍
Vector 是线程安全的(⾥⾯⽅法都带有 synchronized 关键字),效率较低,现在使⽤较少
如何将 ArrayList 变成线程安全的?
调⽤ Collections ⼯具类中的 static List synchronizedList(List list) ⽅法
Set
juc线程安全
- Collections.synchronizedSet();
- new CopyOnWriteArraySet();
1、jdk 1.5 引入,之前都是使用 Object[],引入后使用泛型
2、使⽤ Object[] 的缺点(2个)
- 获取⼀个值时必须进⾏强制类型转换
- 调⽤⼀个⽅法前必须使⽤ instanceof 判断对象类型
HashSet
HashSet实际上是一个HashMap实例,都是一个存放链表的数组。
它不保证存储元素的迭代顺序;此类允许使用null元素。
HashSet中不允许有重复元素 ,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个固定对象private static final Object PRESENT = new Object();
HashSet中add方法调用的是底层HashMap中的put()方法,而如果是在HashMap中调用put,首先会判断key是否存在,如果key存在则修改value值,如果key不存在这插入这个key-value。而在set中,因为value值没有用,也就不存在修改value值的说法.
因此往HashSet中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样HashSet中就不存在重复值。
所以判断key是否存在就要重写元素的类的equals()和hashCode()方法,当向Set中添加对象时,首先调用此对象所在类的hashCode()方法,计算次对象的哈希值,此哈希值决定了此对象在Set中存放的位置;若此位置没有被存储对象则直接存储,若已有对象则通过对象所在类的equals()比较两个对象是否相同,相同则不能被添加。
TreeSet
TreeSet是JAVA中集合的一种,TreeSet 是一个有序的集合,它的作用是提供有序的Set集合。
它继承于AbstractSet抽象类,实现了NavigableSet
一种基于TreeMap的NavigableSet实现。
因为TreeSet继承了AbstractSet抽象类,所以它是一个set集合,可以被实例化,且具有set的属性和方法。
TreeSet是基于TreeMap实现的。
TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。
TreeSet的性能比HashSet差,但是我们在需要排序的时候可以用TreeSet,因为他是自然排序也就是升序。下面是TreeSet实现代码,TreeSet只能通过迭代器迭代元素
Map
juc线程安全
- Collections.synchronizedMap();
- new ConcurruentHashMap();
**Map 以 (key ,value) 的形式存储数据:键值对 key 和 value 存储的都是对象的内存地址(引⽤) **
Map.Entry<K, V> 是 Map 的⼀个接⼝。接⼝中的内部接⼝默认是 public static 的。
遍历方式
- map.keySet(),通过key取value
- map.entrySet(),取出key和value(推荐)
HashMap
HashMap 底层是⼀个数组,数组中每个元素是⼀个单向链表(即,采⽤拉链法解决哈希冲突)
单链表的节点每个节点是 Node<K, V> 类型
同⼀个单链表中所有 Node 的 hash值不⼀定⼀样,但是他们对应的数组下标⼀定⼀样(数组下标利⽤哈希函数/哈希算法根据 hash值计算得到的 )
HashMap 是数组和单链表的结合体
- HashMap 默认初始化容量: 16
- 必须是 2 的次幂,这也是 jdk 官⽅推荐的
- 这是因为达到散列均匀,为了提⾼ HashMap 集合的存取效率,所必须的
- HashMap 默认加载因⼦:0.75
数组容量达到 3/4 (160.75)时,开始扩容2 - JDK 8 之后,对 HashMap 底层数据结构(单链表)进⾏了改进
- 如果单链表元素超过8个,则将单链表转变为红⿊树;
- 如果红⿊树节点数量⼩于6时,会将红⿊树重新变为单链表。
这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围。提高效率
put() 方法原理
- 先将 key, value 封装到 Node 对象中
- 底层会调⽤ key 的 hashCode() ⽅法得出 hash 值
- 通过哈希函数/哈希算法,将 hash 值转换为数组的下标
- 如果下标位置上没有任何元素,就把 Node 添加到这个位置上;
- 如果下标位置上有但链表,此时会将当前 Node 中的 key 与链表上每⼀个节点中的 key 进⾏ equals ⽐较
- 如果所有的 equals ⽅法返回都是 false,那么这个新节点 Node 将被添加到链表的末尾;
- 如果其中有⼀个 equals 返回了 true,那么链表中对应的这个节点的 value 将会被新节点 Node 的value 覆盖。(保证了不可重复)
注:
- HashMap 中允许 key 和 value 为 null,但是只能有⼀个(不可重复)!
- HashTable 中 key 和 value 都不允许为 null。 (线程安全)
get() 方法原理
- 先调⽤ key 的 hashCode() ⽅法得出 hash 值
- 通过哈希函数/哈希算法,将 hash 值转换为数组的下标
- 通过数组下标快速定位到数组中的某个位置:
- 如果这个位置上什么也没有(没有链表),则返回 null;
- 如果这个位置上有单链表,此时会将当前 Node 中的 key 与链表上每⼀个节点中的 key 进⾏ equals ⽐较。
- 如果所有的 equals ⽅法返回都是 false,那么 get ⽅法返回 null;
- 如果其中有⼀个 equals 返回了 true,那么这个节点的 value 便是我们要找的 value,此时 get ⽅法最终返回这个要找的 value。
放在 HashMap 中 key 的元素(或者放在 HashSet 中的元素)需要同时重写 hashCode() 和 equals() ⽅法!!!
同时重写 hashCode() 和 equals() ⽅法
重写 hashCode() ⽅法时要达到散列分布均匀!!!
如果 hashCode() ⽅法返回⼀个固定的值,那么 HashMap 底层则变成了⼀个单链表;
如果 hashCode() ⽅法所有返回的值都不同,此时 HashMap 底层则变成了⼀个数组。
这两种情况称之为,散列分布不均匀
equals 和 hashCode⽅法⼀定要同时重写
TreeMap
- TreeSet/TreeMap 是⾃平衡⼆叉树
- TreeSet/TreeMap 迭代器采⽤的是中序遍历⽅式 (有序)
⽆序,不可重复,但是可排序
**TreeSet/TreeMap中key 可以⾃动对 String 类型或8⼤基本类型的包装类型进⾏排序 **
TreeSet ⽆法直接对⾃定义类型进⾏排序
直接将⾃定义类型添加到 TreeSet/TreeMap中key 会报错 java.lang.ClassCastException
原因:是因为⾃定义没有实现 java.lang.Comparable 接⼝(此时,使⽤的是 TreeSet 的⽆参构造器)
对 TreeSet/TreeMap 中 key部分 元素,必须要指定排序规则。
主要有两种解决⽅案:
⽅法⼀: 放在集合中的⾃定义类型实现 java.lang.Comparable 接⼝,并重写 compareTo ⽅法
⽅法⼆: 选择 TreeSet/TreeMap 带⽐较器参数的构造器 ,并从写⽐较器中的 compare ⽅法
Properties
Properties 的 key 和 values 都是 String