目录
1.集合输出
1.1 迭代输出:Iterator(重要)
1.2 双向迭代接口:ListIterator
1.3 Enumeration枚举输出
1.4 foreach输出
2. Map接口
2.1 HashMap接口
2.2 Hashtable
2.3 ConcurrentHashMap子类
2.4 Map集合使用Iterator输出(重点)
2.5 关于Map中key的说明
2.6 TreeMap子类
1.集合输出
集合的标准输出一共有四种手段:Iterator(单向迭代器)、ListIterator(双向迭代器)、Enumeration、foreach
1.1 迭代输出:Iterator(重要)
只能够由前向后进行内容的迭代处理
- 三个重要方法
- 判断是否有下一个元素: public boolean hasNext();
- 取得当前元素: public E next();
- 删除元素: public default void remove();
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
/**
* 遍历中删除元素
* Author:qqy
*/
public class Test10 {
public static void main(String[] args) {
Set<Person1> people = new TreeSet<>();
people.add(new Person1("Jack", 22));
people.add(new Person1("Alice", 18));
Iterator<Person1> iterator = people.iterator();
while (iterator.hasNext()) {
//需要先next,再remove
//满足条件 -> 删除
Person1 p = iterator.next();
if (p.getName().equals("Jack")) {
System.out.println(p);
//一个hasNext()后不能iterator.next()两次,两者成对出现,否则游标不变
// System.out.println(iterator.next());
iterator.remove();
}
}
System.out.println(people);
}
}
1.2 双向迭代接口:ListIterator
Iterator的子接口
- 两个重要方法
- 判断是否有上一个元素:public boolean hasPrevious();
- 取得上一个元素:public E previous();
- 将指定元素插入:boolean add(E e); -> 添加到游标之前,不影响后续的next。只有后续调用previous时,才会使用新元素。
- 用指定的元素替换由 next()或 previous()返回的最后一个元素:E set(int index, E element);
- 1.2.1 迭代器内容的修改
修改迭代器内容时(set()),不能写在next()之前
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* 迭代
* Author: qqy
*/
public class Test3 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("bonjour");
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
//iterator.add("+");
//修改迭代器内容set()时,不能写在next()之前,
// iterator.set("+"); //error
System.out.print(iterator.next() + ", "); //hello, bonjour,
//iterator.add("+");
// System.out.print(iterator.next() + ", "); //hello, bonjour,
iterator.set("+");
//对集合进行遍历并删除,用迭代器
//iterator.remove();
}
System.out.println();
for (String item : list) {
System.out.print(item + ", "); //+, +,
}
}
}
- 1.2.2 ListIterator的前后遍历
使用hasPrevious()前,要确定游标不位于集合的头,否则读取不到数据
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* ListIterator的前后遍历
* Author:qqy
*/
public class Test5 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
//list.listIterator() -> 返回列表中的列表迭代器(ArrayList的方法)
ListIterator<String> iterator = list.listIterator();
// A B C D
//^ (迭代器的初始位置)
//A -> B -> C -> D ->
System.out.println("从前往后迭代:");
while (iterator.hasNext()) {
System.out.print(iterator.next());
System.out.print(" -> ");
}
System.out.println();
//当游标在后面时,才能从后向前遍历
//D <- C <- B <- A <-
System.out.println("从后往前:");
while (iterator.hasPrevious()) {
System.out.print(iterator.previous());
System.out.print(" <- ");
}
}
}
1.3 Enumeration枚举输出
- 三个重要方法
- 判断是否有下一个元素:public boolean hasMoreElements();
- 取得元素:public E nextElement();
- 取得Enumeration接口对象:public Enumeration elements()
/**
* Enumeration
* Author:qqy
*/
public class Test11 {
public static void main(String[] args) {
Vector<String> vector = new Vector<>();
vector.add("Java");
vector.add("C++");
vector.add("Python");
Enumeration enumeration = vector.elements();
while (enumeration.hasMoreElements()) {
System.out.println(enumeration.nextElement());
}
}
}
1.4 foreach输出
遍历集合不可修改,否则会出现ConcurrentModificationException(并发修改异常)
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
/**
* foreach遍历集合
* Author:qqy
*/
public class Test9 {
public static void main(String[] args) {
Set<Person1> people = new TreeSet<>();
people.add(new Person1("Jack", 22));
people.add(new Person1("Alice", 18));
//for、while、foreach方式遍历集合(List,Set ...)
//遍历依赖于迭代器
//若修改集合,会出现ConcurrentModificationException(并发修改异常),线程不安全
for (Person1 person : people) {
//people.add(new Person1("Tom", 23));
System.out.println(person);
}
//遍历集合:Iterator 可以 add remove
Iterator<Person1> iterator = people.iterator();
while (iterator.hasNext()) {
// people.add(new Person1("Tom", 23));
//需要先next,再remove
System.out.println(iterator.next());
iterator.remove();
}
System.out.println(people);
}
}
class Person1 implements Comparable<Person1>{
private String name;
private Integer age;
public Integer getAge() {
return age;
}
public Person1(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Person1 o) {
return this.age - o.getAge();
}
}
2. Map接口
顶层接口,Map中会一次性保存两个对象,且这两个对象的关系:key=value结构。可以通过key找到对应的value内容。
- 四个常用子类: HashMap、 Hashtable、TreeMap、ConcurrentHashMap
2.1 HashMap接口
- 2.1.1 基本操作
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Map基本操作
* Author:qqy
*/
public class Test {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "java");
map.put("2", "C++");
map.put("3", "Python");
System.out.println(map.containsKey("1"));//true
System.out.println(map.containsKey("4"));//false
System.out.println(map.get("1"));//java
//1.HashMap key value 是可以为null
map.put(null, "key is null");
map.put("4", null);
map.put("4", "PHP");
System.out.println(map.size());//5 ∵key相等,对应的value会被覆盖掉
System.out.println(map.get("4"));//PHP
System.out.println(map.get("6"));//null -> key不存在,get获得null
//获取map中的所有key
System.out.println("获取所有的key:");
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key);
}
System.out.println("输出key=value:");
for (String key : keys) {
System.out.println(key + "=" + map.get(key));
}
System.out.println("输出所有的value:");
Collection<String> values = map.values();
for (String value : values) {
System.out.println(value);
}
}
}
- 2.1.2 源码解析
- 基本实现
HashMap可以看作数组和链表的复合结构,数组被分为一个个桶,每一个桶都被键值对所确定,哈希值相同的键值对,以链表的形式存储。
HashMap负责平衡桶的个数和桶中元素的个数,当两者个数都达到一定程度,则将链表树化。树的检索效率是极高的,但同时树的维护成本也是极高的。桶数量的改变会带来一定的性能损耗。
- 属性:
最大容量 MAXIMUM_CAPACITY:2^30
负载因子 DEFAULT_LOAD_FACTOR:0.75f
树化阈值 TREEIFY_THRESHOLD:8
最小树化容量 MIN_TREEIFY_CAPACITY:64
桶数量超过64 && 链表数量超过8 -> 树化为红黑树
- 方法
无参构造:只设置了负载因子,延迟加载,在put()中开辟空间,初始化容量16,阈值12
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public V put(K key, V value) {
//int类型的hash码;key(泛型);value;如果不存在,加入,如果存在,替换;剔除
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
//key等于null,为0;不等于null,调用对象的hashCode()
//将h的高16位和低16位进行异或,取得最终的hash码
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
resize()扩容
- 创建初始存储表
- 在容量不满足需求的时候,进行扩容
- 扩容后,需要将老的数组中的元素重新放置到新的数组
remove()
public V remove(Object key) {
Node<K,V> e;
//通过key删除
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
有参构造
//传初始化容量
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
- 容量、负载系数、树化
- 容量和负载因子决定了可用的桶的数量
- 负载因子 * 容量 > 元素数量 -> 容量需要 > 预估元素数量 / 负载因子,且是2的幂数
- 树化:如果容量小于 MIN_TREEIFY_CAPACITY,只会进行简单的扩容。如果容量大于 MIN_TREEIFY_CAPACITY ,则会进行树化改造。
2.2 Hashtable
- Hashtable和HashMap的区别
2.3 ConcurrentHashMap子类
利用Collections将HashMap包装成线程安全的集合。
- 早期ConcurrentHashMap
将桶进行分段(Segment),段的数量由并发等级确定,被自动调整到2的幂数值。构造且没有指定任何参数时,桶的默认初始化数量是16;指定了初始化容量的同时也指定了并发等级时,若容量小于并发等级,则将并发等级的大小作为容量。
- java8和之后的版本,ConcurrentHashMap变化
- 延迟加载
- 数据存储利用 volatile 来保证可见性 -> 立即可见,保证数据一致性
- 使用 CAS(比较和赋值) 等操作,在特定场景进行无锁并发操作,性能更高
- 底层优化常用手段:优化JDK的内部类、对性能有极高要求的类使用本地方法、内存上的优化:扩大内存,提高缓存
2.4 Map集合使用Iterator输出(重点)
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Author:qqy
*/
public class Test1 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "java");
map.put("2", "C++");
map.put("3", "Python");
//遍历map的三种方法
// keySet -> get ;
// 只获取value:values ;
// entrySet(foreach)
System.out.println("通过Entry遍历map");
Set<Map.Entry<String, String>> entries = map.entrySet();
//foreach、iterator ,Set没有下标,不能使用for循环
Iterator<Map.Entry<String, String>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println(entry.getKey() + ":" + entry.getValue());
}
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
//优化
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
2.5 关于Map中key的说明
通过key计算hashCode,再利用hashCode计算桶的下标。
- 采用自定义类作为key时,一定要记得覆写Object类的hashCode()与equals()方法。
2.6 TreeMap子类
TreeMap是一个可以排序的Map子类,它是按照Key的内容排序的。
- key是系统(JDK / 第三方)定义的类,从小到大排序,若要修改,构造TreeMap的构造方法传入比较器(Comparator)接口的实现对象(或用lambda表达式 )
- key是自定义的类,可以实现比较接口Comparable,也可以不实现,在使用时指定比较器(Comparator)接口的实现类对象
- 使用时指定Comparator接口比较灵活
import java.util.Map;
import java.util.TreeMap;
/**
* TreeMap
* Author:qqy
*/
public class Test2 {
public static void main(String[] args) {
//更改比较器,逆序
Map<Integer, String> map = new TreeMap<>((o1, o2) -> o1.compareTo(o2) * -1);
map.put(1, "Java");
map.put(2, "C++");
map.put(4, "PHP");
map.put(3, "C"); //由key决定如何排序,从大到小的排序
System.out.println(map);
}
}
- 有Comparable出现的地方,判断数据就依靠compareTo()方法完成,不再需要equals()与hashCode()
Map总结:
- Collection保存数据的目的一般用于输出(Iterator),Map保存数据的目的是为了根据key查找,找不到返回null。
- Map使用Iterator输出(Map.Entry的作用)
- HashMap数据结构一定要理解(链表、红黑树和桶)、HashMap与Hashtable区别