JavaLearn#(11)MapIterator迭代器Collections集合总结泛型
Posted LRcoding
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaLearn#(11)MapIterator迭代器Collections集合总结泛型相关的知识,希望对你有一定的参考价值。
1. Map概述
存储的键值对映射关系,根据 key 找到value
1.1 HashMap
- 采用 哈希表 (hashtable)存储结构
- 查询、存储、删除速度快
- key无序
- key 和 value 都可以为 null
1.2 LinkedHashMap
- 采用 哈希表 存储结构,同时用链表维护次序
- key 有序(添加顺序)
- key 和 value 都可以为 null
1.3 TreeMap
- 采用红黑树的存储结构
- key有序(自然顺序),查询速度比 List 快,没有 HashSet 快
- key 和 value 都不能为 null
2. Map的使用
2.1 map的常用操作
// 声明对象
Map<String, String> map = new HashMap<>();
// 放入数据
map.put("cn", "China");
// 获取数据,通过 key 获取数据,若没有,返回 null
map.get("cn");
// 遍历 map
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
2.2 存储国家简称和全称
// 创建一个 map 对象
Map<String, String> hashMap = new HashMap<>(); // key唯一,无序
Map<String, String> linkedHashMap = new LinkedHashMap<>(); // key唯一,有序(添加顺序)
Map<String, String> treeMap = new HashMap<>(); // key唯一,有序(自然顺序)
// 添加数据
hashMap.put("cn", "China");
hashMap.put("jp", "Japan");
hashMap.put("us", "the United States");
hashMap.put("us", "America"); // 同名的 key,第二次添加的会将第一次的覆盖
hashMap.put("uk", "the united kingdom");
hashMap.put("en", "the united kingdom"); // 同名的 value,不同的 key,都会存在
// 输出数据
System.out.println(hashMap);
System.out.println(hashMap.size());
// 遍历1: 获取所有的key ==> keySet() 返回结果是 Set类型 【不推荐使用】
Set<String> keySets = hashMap.keySet();
Iterator<String> iterators = keySets.iterator();
while (iterators.hasNext()) {
String key = iterators.next();
// 通过 key获取 value
System.out.println(key + "---->" + hashMap.get(key));
}
// 遍历2:得到所有的 key-value 组成的 set,直接遍历 set
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
2.3 存储学号和学生的映射
// map 的其他方法
map.remove(3); // 根据 key删除内容
map.clear(); // 清空 map
map.isEmpty(); // map 是否为空
map.containsKey(5); // map中的 key是否包含 5
Map<Integer, Student> map = new HashMap<>();
Student stu1 = new Student(1, "zhangsan", 87);
Student stu2 = new Student(2, "lisi", 90);
Student stu3 = new Student(3, "wangwu", 87);
Student stu4 = new Student(1, "zhaoliu", 87);
map.put(stu1.getSno(), stu1);
map.put(stu2.getSno(), stu2);
map.put(stu3.getSno(), stu3);
map.put(stu4.getSno(), stu4);
System.out.println(map.size()); // 关键字相同的覆盖
System.out.println(map.get(2));
Set<Map.Entry<Integer, Student>> entries = map.entrySet();
Iterator<Map.Entry<Integer, Student>> iterators = entries.iterator();
while (iterators.hasNext()) {
Map.Entry<Integer, Student> next = iterators.next();
System.out.println(next.getValue());
}
3. HashMap 的源码
JDK1.7
之前, HashMap底层就是一个 table数组 + 链表实现的哈希表存储结构
由源码(JDK1.7)知,链表中,每个节点都是一个 Entry(包含:键key、值value、键的哈希码hash、指向下一个节点的next)
// Entry 为 HashMap的内部类
static class Entry<K, V> implements Map.Entry<K, V> {
final K key; //key
V value; //value
Entry<K, V> next; //指向下一个节点的指针
int hash; //哈希码
}
JDK1.7 中 HashMap的主要成员变量:
public class HashMap<K, V> implements Map<K, V> {
// 哈希表主数组的默认长度 !!!
static final int DEFAULT_INITIAL_CAPACITY = 16;
// 默认的装填因子(链表中存的结点数 / 数组的长度)
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 主数组的引用 !!!
transient Entry<K, V>[] table;
// 【链表】中结点的数量
transient int size;
// 界限值、阈值(数组的长度 * 装填因子)
int threshold;
// 装填因子(可以使用默认的,也可以指定)
final float loadFactor;
// 无参构造方法
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); // 16 0.75
}
// 【重点】有参构造方法
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity !!!!! 长度一定是 2^n
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
}
注意
:主数组的长度,一定是 2^n
(所以当我们new 对象时,new HashMap(7),虽然指定了长度为7,但是实际长度为8)
3.1 put的源码
调用 put方法添加键值对,采用哈希表添加数据的原理(计算 key的哈希码,和 value无关)
- 第一步计算哈希码时,不仅调用了 key的 hashCode(),还进行了更复杂的操作,目的是尽量保证不同的key,得到不同的哈希码
- 第二步根据哈希码计算存储位置时,使用位运算提高效率(length必须是2^n)
- 第三步添加 Entry时,添加到链表的第一个位置,而不是链表末尾
- 如果在添加 Entry时,发现了相同的 key 已经存在,就使用新的 value 替代旧的 value,并且返回旧的 value
3.2 get的源码
- 调用 get方法根据 key获取 value
- 哈希表三步查询数据原理的具体实现
- 其实是根据 key找 Entry,再从 Entry中获取 value
3.3 JDK1.8的改变
当链表存储的数据个数,>= 8
的时候,不再采用链表,而是采用了红黑树存储结构,那么查询的时候,时间复杂度就由 O(n) 转变为 O(logn)
4. TreeMap 的源码
底层是一棵红黑树(二叉树、二叉查找树、二叉平衡树)
每个结点的结构:
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
boolean color = BLACK;
}
TreeMap的主要成员变量:
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable {
// 外部比较器(优先级高)
private final Comparator<? super K> comparator;
// 红黑树的结点数量
private transient int size = 0;
// 指向根结点的一个引用(存储的根节点的地址)
private transient Entry<K,V> root = null;
// 无参构造方法, 没有指定外部比较器
public TreeMap() {
comparator = null;
}
// 有参构造方法,指定外部比较器!!!
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
}
4.1 put 的源码
- 从根节点开始比较
- 添加的过程,就是构造二叉平衡树的过程,会自动平衡
- 平衡离不开比较:外部比较器优先,然后是内部比较器,如果两个都没有,则抛出异常
4.2 get 的源码
5. HashSet 和TreeSet 的源码
5.1 HashSet的源码
HashSet 的底层使用的是 HashMap,所以底层结构也是哈希表
add 方法:
5.2 TreeSet的源码
TreeSet的底层是 TreeMap,所以底层结构是 红黑树
add方法,同理是将新增的元素作为 key,放入map
6. Iterator 介绍及源码
-
专门用来遍历集合(Collection、List、Set,Map不可以)
-
实际上是迭代器设计模式的实现
-
常用方法:
boolean hasNext()
:判断是否还有可以访问的元素Object next()
:返回要访问的下一个元素void remove()
:删除上次访问的元素(lastRet 指向的元素)
-
实现了 Iterable接口的集合类,都可以使用迭代器遍历,集合类中要提供 iterator()方法
-
for-each 遍历集合时,底层使用的还是 Iterator
- for-each 遍历数组时,底层使用的是 for循环
ListIterator:
- 只针对于 List及其子类型
- 可以顺序遍历,还可以逆序遍历
- 在遍历过程中,可以 remove()、add()、set()
- 可以定位当前的索引位置 nextIndex()、 previousIndex()
7. Collections 工具类
- 唯一的构造方法为 private的,不允许在类的外部创建对象
- 提供了大量的 static方法,可以通过类名直接调用
// 1.添加元素 (一次性添加多个元素)
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 45, 56, 23, 78, 98);
// 2.排序
Collections.sort(list);
// 3.查找:必须自然有序
Collections.binarySearch(list, 78); // 折半查找(必须有序)
// 查找底层的实现,判断 list instanceof RandomAccess(接口,无任何元素,用于判断)
// 4.最大最小值
Collections.max(list);
Collections.min(list);
// 5.填充(将 list中的元素都填充为 100)
Collections.fill(list, 100);
// 6.复制(源list的长度,必须大于等于被拷贝list的长度)
List<Integer> list1 = new ArrayList<>();
Collections.addAll(list1, 1,2,3,4,5,6);
Collections.copy(list1, list); // list1: 45, 56, 23, 78, 98, 6
/** 7.同步【具体细节在多线程处!!!!】
* 第一代集合类 Vector、Hashtable 线程安全、效率低,适用多线程
* 第二代集合类 ArrayList、HashMap 线程不安全、效率高,适用单线程
*/
// 既希望效率高,又要线程安全?
// 方法1:如果是多线程的情况下,对第二代集合类加锁
list = Collections.synchronizedList(list); // 相当于 list = new SynchronizedRandomAccessList<>(list)
// 方法2:使用第三代的并发集合类 CopyOnWriteArrayList、 ConcurrentHashMap
8. 旧的集合类和新集合类
8.1 旧集合类
- Vector
- 实现原理和 ArrayList相同,都是长度可变的数组结构,可以互用
- 区别:
- Vector 是早期 JDK接口,ArrayList 是替代 Vector 的新接口
- Vecort 线程安全,效率低。 ArrayList 线程不安全
- 扩容时,Vector 增长 1倍,ArrayList 增长 1.5倍
- Hashtable
- 实现原理和 HashMap相同,底层都是哈希表,可以互用
- 区别:
- Hashtable 是早期JDK提供,HashMap 是新的
- Hashtable 继承 Dictionary类,HashMap 实现 Map接口
- Hashtable 线程安全,HashMap 线程不安全
- Hashtable 不允许 null 值,HashMap 允许 null 值
8.2 新一代并发集合类
在大量并发情况下,提高集合的效率和安全,使用 Lock锁,位于 java.util.concurrent 包下(JUC)
- ConcurrentHashMap
- CopyOnWriteArrayList
- CopyOnWriteArraySet
List<String> copy = new CopyOnWriteArrayList();
// 其他方法相同的使用方式
9. 集合的概念辨析
9.1 集合和数组的比较
数组不是面向对象的,集合有多种类型,可适用与不同场合
- 数组容量固定,且无法动态改变 ==》 集合类容量动态增长
- 数组可以存放基本数据类型和引用数据类型 ==》 集合只能存放引用数据类型(基本类型通过包装类)
- 数组无法判断其中实际存了多少元素,length 只是数组的容量 ==》 集合可以通过 size() 知道实际存了多少元素
- 数组仅使用顺序表 ==》 集合有顺序表、链表、哈希表、树等
- 集合以类的形式存在,具有封装、继承、多态等特征
9.2 ArrayList 和 LinkedList 的联系和区别
联系:
- 都实现了 List 接口
- 有序,不唯一(元素可以重复)
区别:
- ArrayList 底层是数组, LinkedList 底层是链表(双向链表)
- ArrayList 在内存中分配连续的空间,LinkedList 在内存中随意分配空间
- ArrayList 适用于遍历元素和随机访问元素多的情形,LinkedList 适用于添加和删除元素多的情形(前提也是必须先低效率查询才行)
9.3 哈希表的原理
HashMap 的底层原理(数组长度默认为16、必须为2^n,装填因子默认0.75,扩容2倍),查询快、添加快
哈希表的结构:
- JDK1.7 :数组 + 链表
- JDK1.8 :数组 + 链表/红黑树(链表长度>8时,转为红黑树)
哈希表添加原理:
- 计算哈希码( hashCode() )
- 计算存储位置(数组的索引)
- 存入指定位置(要处理冲突,借助 equals 进行比较)
哈希表查询原理:
- 同哈希表的添加原理
9.4 TreeMap的底层原理
底层为红黑树(二叉树、二叉查找树、二叉平衡树)
每个结点的结构:
添加原理:
- 从根节点开始比较
- 添加过程就是构造二叉平衡树的过程,自动平衡
- 平衡离不开比较:外部比较器优先,然后是内部比较器,否则报错
9.5 Collection 和 Collections 的区别
- Collection 是 Java 提供的集合接口,存储一组无序、不唯一的对象,有两个子接口,List 和 Set
- Collections 专门用来操作集合类
9.6 Vector 和 ArrayList 的区别
- Vector 和 ArrayList 实现原理相同,都是长度可变的数组
- Vector 线程安全,效率低。 ArrayList 线程不安全,效率高
- 扩容时,Vector 增长 1 倍。 ArrayList 增长 1.5 倍
9.7 Hashtable 和 HashMap 区别
- Hashtable 和 HashMap 实现原理相同,都是哈希表
- Hashtable 线程安全,效率低。 HashMap 线程不安全,效率高
- Hashtable 不允许 null 值。 HashMap 允许 null 值(ConcurrentHashMap 不允许为 null )
10. 集合总结
存储结构 | 是否有序 | 是否唯一 | 查询效率 | 添加/删除效率 | |
---|---|---|---|---|---|
ArrayList | 顺序表 | 有序(添加) | 不唯一 | 索引查询最高 | 低 |
LinkedList | 链表(双向链表) | 有序(添加) | 不唯一 | 低 | 高 |
HashSet | 哈希表 | 无序 | 唯一 | 最高 | 最高 |
HashMap | 哈希表 | key 无序 | key 唯一 | 最高 | 最高 |
LinkedHashSet | 哈希表 + 链表 | 有序(添加) | 唯一 | 最高 | 最高 |
LinkedHashMap | 哈希表 + 链表 | key 有序(添加) | key 唯一 | 最高 | 最高 |
TreeSet | 红黑树 | 有序(自然) | 唯一 | 中等 | 中等 |
TreeMap | 红黑树 | 有序(自然) | key 唯一 | 中等 | 中等 |
Collection | Map | |
---|---|---|
无序、不唯一 | collection | Map.values() |
无序、唯一 | HashSet | HashMap 的 key |
有序、不唯一 | ArrayList、LinkedList | |
有序、唯一 | TreeSet、LinkedHashSet | TreeMap的key、LinkedHashMap 的 key |
- 使用 LinkedList ,遍历时,使用 iterator迭代器效率高
11. 泛型
11.1 为什么使用泛型
没有使用之前:
- 不安全:添加元素时,不进行检查(宽进)
- 繁琐:获取元素时,需要强制类型转换 (严出)
使用之后:
- 安全:检查是否为该类型的(严进)
- 简单:直接获取即可 (宽出)
11.2 什么是泛型 geneic
JDK 1.5引入,即“参数化类型”
public class ArrayList<T> implements List<T> {} // 定义处,为类型【形参】
List<String> list = new ArrayList<>(); // 使用处,为类型【实参】
11.3 泛型接口
List<E> Collection<E> Iterable<T> Comparable<T> Comparator<T>
// 定义一个泛型接口
public interface List<T> {
public void add(E obj);
}
11.4 泛型类
ArrayList<E> LinkedList<E> HashMap<K, V>
// 定义一个泛以上是关于JavaLearn#(11)MapIterator迭代器Collections集合总结泛型的主要内容,如果未能解决你的问题,请参考以下文章