我们从学习c语言开始,就已经开始使用“数组“,数组是保存一组对象的最有效的方式,如果你想保存一组基本的数据类型,也推荐这种方式,但是数组具有固定的尺寸,而在一般情况中,我们在写代码的时候,不知道需要多少个对象,或者 是否需要更复杂的方式来存储对象,因此,数组尺寸固定这样一个条件就过于受限了。在Java中,有一套相当完整的容器来解决这个问题,其中基本的类型是List、Set、Queue和Map,这些对象称为集合类,但是由于Java的类库中使用了Collection这个名字来指代类库的一个特殊子集,所以,我们常用“容器”来称呼他们
1、基本概念
1)Collection:一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序来保存元素,Set不能有重复的元素
2)Map:一组成对的“键值对”对象,允许用键来查找值
1.2、Map和Collection的区别
- Map 存储的是键值对形式的元素,键唯一,值可以重复。
- Collection 存储的是单列元素,子接口Set元素唯一,子接口List元素可重复。
- Map集合的数据结构值针对键有效,跟值无关,Collection集合的数据结构是针对元素有效
2、Map(映射)理解
2.1、Map实现
HashMap | Map基于散列表的实现(取代了HashTable)。插入和查询“键值对”的开销是固定的,可以通过构造器设置容量和负载因子,以调整容器的性能 |
---|---|
LinkedHashMap | 类似于HashMap,但是迭代器遍历他时,取得“键值对”的顺序是其插入次序,或者是最近最少使用的次序,只比HashMap慢一点,而在迭代器访问时反应更快,因为它使用链表维护内部次序 |
TreeMap | 基于红黑树的实现,查看“键”或“键值对”时,他们会被排序。TreeMap的特点在于,所得到的的结果都是经过排序的。TreeMap是唯一的带有subMap()方法的Map,他可以返回一个子树 |
WeakHashMap | 弱键映射,允许释放映射所指向的对象,这是为解决某类特殊问题而设计的,如果映射之外没有引用某个“键”,则此键可以被垃圾收集器回收 |
ConCurrentHashMap | 一种线程安全的Map,不涉及同步加锁 |
2.2、Map的常用方法
- 插入(put、putAll())
- 删除(remove())
- 获取(entrySet()、get()、keySet()、size()、values())
- 判断(containsKey()、containsValue()、equals()、isEmpty())
- 清除(clear())
- 替换(replace(),replace(K key, V oldValue, V newValue)
打印类型
//打印Map类型
public static void test(Map<Integer,String> map){
System.out.println(map.getClass().getSimpleName());
}
public static void main(String[] args) {
test(new HashMap<Integer, String>());
test(new LinkedHashMap<Integer, String>());
}
插入
//插入方法
public static void printKeys(Map<Integer,String > map){
System.out.println("size="+map.size());
System.out.println("Keys="+map.keySet());
}
public static void test(Map<Integer,String> map){
System.out.println(map.getClass().getSimpleName());
map.put(1,"hello");
map.put(2,"world");
map.put(3,"!!!");
printKeys(map);
}
public static void main(String[] args) {
test(new HashMap<Integer, String>());
test(new LinkedHashMap<Integer, String>());
}
删除
//删除方法
public static void printKeys(Map<Integer,String > map){
System.out.println("size="+map.size());
System.out.println("Keys="+map.keySet());
}
public static void test(Map<Integer,String> map){
System.out.println(map.getClass().getSimpleName());
map.put(1,"hello");
map.put(2,"world");
map.put(3,"!!!");
printKeys(map);
map.remove(2);
printKeys(map);
}
public static void main(String[] args) {
test(new HashMap<Integer, String>());
test(new LinkedHashMap<Integer, String>());
}
获取
//获取方法
public static void printKeys(Map<Integer,String > map){
//主要在这里
System.out.println("size="+map.size());
System.out.println("Keys="+map.keySet());
System.out.println("values="+map.values());
System.out.println("get="+map.get(1));
System.out.println("entrySet="+map.entrySet());
}
public static void test(Map<Integer,String> map){
System.out.println(map.getClass().getSimpleName());
map.put(1,"hello");
map.put(2,"world");
map.put(3,"!!!");
printKeys(map);
}
public static void main(String[] args) {
test(new HashMap<Integer, String>());
test(new LinkedHashMap<Integer, String>());
}
判断
//判断比较containsKey()、containsValue(),一个比较“键”,一个比较“值”
public static void printKeys(Map<Integer,String > map){
System.out.println("size="+map.size());
System.out.println("Keys="+map.keySet());
}
public static void test(Map<Integer,String> map){
System.out.println(map.getClass().getSimpleName());
map.put(1,"hello");
map.put(2,"world");
map.put(3,"!!!");
printKeys(map);
System.out.println(map.containsKey(3));
System.out.println(map.containsKey(6));
System.out.println(map.containsValue("!!!"));
System.out.println(map.containsValue("..."));
}
public static void main(String[] args) {
test(new HashMap<Integer, String>());
test(new LinkedHashMap<Integer, String>());
}
//equals用来比较两个map是否相等
public static void printKeys(Map<Integer,String > map){
System.out.println("size="+map.size());
System.out.println("Keys="+map.keySet());
}
public static void test(Map<Integer,String> map){
Map<Integer,String> map2=new HashMap<>();
System.out.println(map.getClass().getSimpleName());
map.put(1,"hello");
map.put(2,"world");
map.put(3,"!!!");
printKeys(map);
System.out.println(map.equals(map2));
map2.put(1,"hello");
map2.put(2,"world");
map2.put(3,"!!!");
System.out.println(map.equals(map2));
}
public static void main(String[] args) {
test(new HashMap<Integer, String>());
test(new LinkedHashMap<Integer, String>());
}
//判空
public static void printKeys(Map<Integer,String > map){
System.out.println("size="+map.size());
System.out.println("Keys="+map.keySet());
}
public static void test(Map<Integer,String> map){
System.out.println(map.getClass().getSimpleName());
map.put(1,"hello");
map.put(2,"world");
map.put(3,"!!!");
printKeys(map);
System.out.println(map.isEmpty());
map.remove(1,"hello");
map.remove(2,"world");
map.remove(3,"!!!");
printKeys(map);
System.out.println(map.isEmpty());
}
public static void main(String[] args) {
test(new HashMap<Integer, String>());
test(new LinkedHashMap<Integer, String>());
}
清空
//清空
public static void printKeys(Map<Integer,String > map){
System.out.println("size="+map.size());
System.out.println("Keys="+map.keySet());
}
public static void test(Map<Integer,String> map){
System.out.println(map.getClass().getSimpleName());
map.put(1,"hello");
map.put(2,"world");
map.put(3,"!!!");
printKeys(map);
map.clear();
printKeys(map);
}
public static void main(String[] args) {
test(new HashMap<Integer, String>());
test(new LinkedHashMap<Integer, String>());
}
代替
public static void printKeys(Map<Integer,String > map){
System.out.println("size="+map.size());
System.out.println("Keys="+map.keySet());
System.out.println("value="+map.values());
}
public static void test(Map<Integer,String> map){
System.out.println(map.getClass().getSimpleName());
map.put(1,"hello");
map.put(2,"world");
map.put(3,"!!!");
printKeys(map);
map.replace(1,"2");
map.replace(4,"2");
printKeys(map);
}
public static void main(String[] args) {
test(new HashMap<Integer, String>());
test(new LinkedHashMap<Integer, String>());
}
3、HashMap的简单介绍
3.1、HashMap内部结构
HashMap在JDK1.8之后是基于哈希表和红黑树实现的,JDK1.8之前只基于哈希表
3.2、基本映射操作
HashMap也被称为散列映射,散列映射比较函数只能作用于键,与键关联的值不能进行散列或比较
TreeMap被称为树散列,树散列用键的整体顺序对元素进行排序,并将其组织成搜索树
Map中的方法,HashMap全都有
3.3、HashMap源码解析
内部属性
负载因子:final float loadFactor(默认为0.75f)
实际容量:int threshold= loadFactor*tab.length;
树化阈值:int TREEIFY_THRESHOLD=8
解除树化阈值:int UNTREEIFY_THRESHOLD=6
HashMap也采用懒加载策略,第一次put时初始化哈希表
树化逻辑:索引下标对应的链表长度达到阈值8,并且当前哈希表长度达到64才会树化,否则只是调用resize方法进行哈希表扩容,扩容为原先数组的2倍
负载因子过大会导致哈希冲突明显增加,节省内存
负载因子过小会导致哈希表频繁冲突,内存利用率低
JDK1.8为何要引入红黑树?
当链表长度过长,会将哈希表查找的时间复杂度退化为O(n)
树化保证即便在哈希冲突严重时,查找时间复杂度也为O(logn)
当红黑树节点个数在扩容或删除元素时减少为6以下,在下次resize过程中会将红黑树退化为链表
4、TreeMap
TreeMap是SortedMap唯一实现,可以确保键处于排序状态,这使得它具有额外的功能,这些功能由SortedMap接口中的下列方法提供:
方法 | 功能 |
---|---|
Comparator comparator() | 返回当前 Map使用的Comparator,或者返回null,表示以自然方式排序 |
T firstKey() | 返回Map中的第一个键 |
T LastKey() | 返回Map中的最末一个键 |
Sorted subMap(formKey,toKey) | 生成Map的子集,范围由fromKey(包含)到toKey(不包含)的键确定 |
Sorted headMap(toKey) | 生成此Map的子集,由键小于toKey的所有键值对组成 |
Sorted tailMap(fromKey) | 生成此Map的子集,由键大于fromKey的所有键值对组成 |
TreeMap中键值对都是按键的次序排列的,TreeMap中的次序是有意义的,因此“位置”的概念才有意义,所以才能取得第一个和最后一个元素,并且可以提取Map的子集
5、LinkedHashMap
LinkedHashMap用来记住插入顺序,这样就可以避免在散列表中的项从表面上看是随机排列的,如上图,当条目插入到表中,,就会并入双向链表中
6、equals
HashMap使用equals( )判断当前的键是否与表中存在的键相同,正确的equals( )必须满足下列5个条件:
性质 | 含义 |
---|---|
自反性 | 对于任意x,x.equals(x)一定返回true |
对称性 | 对任意的x和y,如果x.equals(y)返回true,那么,y.equals(x)也返回true |
传递性 | 对于任意的x、y、z,如果x.equals(y)返回true,y.equals(z)也返回true,那么,x.equals(z)一定返回true |
一致性 | 对于任意的x,y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致,要么一致是true,要么一致是flase |
null | 对任何不是null的x,x.equals(null)一定返回flase |
hashCode和equals的关系
hashCode:取得任意一个对象的哈希码
equals:比较两个对象是否相等
hashCode返回值相等的两个对象,equals不一定相等(x与f(x)的关系)
equals返回值相等的两个对象,hashCode一定相等