一、Set接口
1.1 Set接口的框架
Set接口:存储无序(添加和取出的顺序不一致)的、没有索引、不可重复的数据(所以最多包含一个null)
- LinkedHashSet:作为HashSet的子类,遍历其内部的数据时,可以按照添加的顺序遍历
- TreeSet:底层使用红黑树存储,可以按照添加对象的指定属性进行排序
- HashSet:
1.作为Set接口的主要实现类
2.线程不安全的
3.可以存储null值,但是只能有一个null
4.HashSet实际上是HashMap
new HashSet()-->
public HashSet() {
map = new HashMap<>();
}
5.HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即,不保证元素存放顺序和取出顺序一致)
6.不能有重复元素/对象
无序性:以HashSet为例说明,无序性不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值添加
不可重复性:保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个
1.2 HashSet源码解读
1.执行HashSet构造器
HashSet hashSet = new HashSet();
public HashSet() {
map = new HashMap<>();
}
2.执行add()
hashSet.add("java");
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//(static)PRESENT = new Object();:与HashMap中的value相对应的虚值,占位作用
3.执行put(),该方法会执行hash(key)得到key对应的hash值,算法是h = key.hashCode()) ^ (h >>> 16),所以hash值不等价于hashCode
public V put(K key, V value) {//key = "java",value = PRESENT共享
return putVal(hash(key), key, value, false, true);
}
4.执行putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//定义了辅助变量
//table就是HashMap的一个数组,类型是Node[]
//if语句表示如果当前table是null或者大小=0,就第一次扩容到16个空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据key得到的hash值,去计算该key应该存放到table表的哪个索引位置,并把这个位置的对象赋给辅助变量p
//(2)判断p是否为空
//(2.1)如果p为null,表示还未存放元素,就创建一个Node(key="java",value=PRESENT)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
我们向HashSet中添加元素a,首先调用元素a所在类的HashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为索引位置),判断数组此位置是否已经有元素:
- 如果此位置没有其他元素,则元素a直接添加成功—>情况1
- 如果此位置存在其他元素b(或者以链表形式存在多个元素),则比较元素a与b的哈希值:如果哈希值不同,则元素a添加成功—>情况2;如果哈希值相同,进而需要调用元素a所在类的equals()方法,equals()方法返回true则表示元素a添加失败,反之添加成功—>情况3
注:不同元素的哈希值不同,但是不同的哈希值通过hash()方法得到的索引值可能相同,所以才有了情况2余情况3
对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上的数据以链表的方式存储。
- jdk7中,元素a指向原来的元素
- jdk8中,原来的元素指向元素a
总结:七上八下
在Java8中,如果一条链表的元素个数为8,table为64,此时链表就会进行树化(红黑树)
1.3 案例
案例一:
使用HashSet存储字符串并遍历
package collection_.Set_;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SetMethod {
public static void main(String[] args) {
//创建集合对象
Set set = new HashSet();
//添加元素对象
set.add("hello");
set.add("world");
set.add("java");
set.add("java");//重复
set.add(null);
set.add(null);
//遍历集合对象
//方法一:转数组
// Object[] o = set.toArray();
// for (int i = 0; i < o.length; i++) {
// System.out.println(o[i]);
// }
//方法二:迭代器
Iterator i = set.iterator();
while (i.hasNext()) {
Object o = i.next();
System.out.println(o);
}
//方法三:增强for
// for (Object o : set) {
// System.out.println(o);
// }
}
}
- Set接口的实现类的对象(Set接口对象),不能存放重复元素,可以添加一个null
- Set接口对象存放数据是无序的(即添加的顺序和取出的顺序不一致)
- 取出的顺序虽然不是添加的顺序,但是它是固定的
案例二:
使用HashSet存储自定义对象并遍历
import java.util.HashSet;
class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
//创建集合对象
HashSet<Student> hashSet = new HashSet<>();
//创建元素对象
Student s1 = new Student("张三", 18);
Student s2 = new Student("李四", 19);
Student s3 = new Student("李四", 19);
//插入元素对象
hashSet.add(s1);
hashSet.add(s2);
hashSet.add(s3);
//遍历集合元素
for (Student s : hashSet) {
System.out.println(s);
}
}
}
1.4 由案例二引出的面试题
1.5 LinkedHashSet的使用
LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
优点:对于频繁的遍历操作,LinkedHashSet效率要高于HashSet
1.6 TreeSet自然排序(了解)
- 向TreeSet中添加的数据,要求是相同类的对象
- 两种排序方式:自然排序(实现Comparable接口)和定制排序(实现Comparator接口)
- 自然排序中,比较两个对象是否相同的标准为:compare To()返回0,不再是equals()
- 定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()
1.7 HashSet和TreeSet的区别
二、Map接口
2.1 Map实现类结构
Map: 双列数据,存储key-value对的数据,类似于高中的函数:y=f(x)
-
HashMap: 作为Map的主要实现类,线程不安全的,效率高;可存储null的key和value
LinkedHashMap: 保证遍历Map元素时,可以按照添加的顺序实现遍历;原因:在原有HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素;对于频繁的遍历操作,此类执行效率高于HashMap -
TreeMap: 保证按照添加的key-value对进行排序,实现排序遍历;此时考虑key的自然排序或者定制排序;底层使用红黑树
-
Hashtable: 作为古老的实现类,线程安全的,效率低;不能存储null的key和value
Properties: 常用来处理配置文件;key和value都是String类型
2.2 Map结构的理解
- Map中的key:无序的、不可重复的,使用Set存储所有的key
- Map中的value:无序的、可重复的,使用Collection存储所有的value
- 一个键值对构成了一个Entry对象
- Map中的entry:无序的、不可重复的,使用Set存储所有的entry
2.3 Map的常用功能
方法名 | 功能 | |
---|---|---|
映射功能 | public V put(K key,V value) | 将key映射到value,如果key存在,则覆盖value,并将原来的value返回 |
获取功能 | public V get(K key) | 根据指定的key值取得相应的value值,若没有此key值,返回null |
int size() | 返回对应关系的个数 | |
判断功能 | boolean containsKey(Object key) | 判断指定的key是否存在 |
boolean containsValue(Object Value) | 判断指定的value是否存在 | |
boolean isEmpty() | 判断是否有对应关系 | |
删除功能 | void clear() | 清空所有的对应关系 |
V remove(Object key) | 根据指定的key删除对应关系,并返回key所对应的值,如果没有删除成功返回null | |
遍历功能 | public set<Map.Entry<K,V>> entrySet() | 将Map集合变为Set集合 |
public Set<K> KeySet() | 返回所有Key值集合,Key不能重复 | |
public Collection<V> values() | 返回所有values值,value可以重复 |
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) {
//创建集合对象
Map<String, String> map = new HashMap<String, String>();
//添加映射关系
map.put("IT001", "张三");
map.put("IT002", "李四");
map.put("IT003", "王五");
//返回所有Key值集合
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key);
}
System.out.println("------------");
//返回所有values值
Collection<String> values = map.values();
for (String value : values) {
System.out.println(value);
}
}
}
2.4 Map的第一种遍历方式(keySet()
方法)
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) {
//创建集合对象
Map<String, String> map = new HashMap<String, String>();
//添加映射关系
map.put("IT001", "张三");
map.put("IT002", "李四");
map.put("IT003", "王五");
//遍历Map对象
//1.找到所有的key
Set<String> keys = map.keySet();
//2.遍历所有的key
for (String key : keys) {
//3.让每个key去找其对应的value
String value = map.get(key);
System.out.println("[key:" + key + " value:" + value+"]");
}
}
}
2.5 Map的第二种遍历方式(entrySet()
方法)
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) {
//创建集合对象
Map<String,String> map = new HashMap<String, String>();
//添加映射关系
map.put("IT001", "张三");
map.put("IT002", "李四");
map.put("IT003", "王五");
//遍历Map对象
//1.获取所有的Entry对象
Set<Map.Entry<String,String>> entries = map.entrySet();
//2.遍历包含所有Entry对象集合
for (Map.Entry<String,String> entry:entries) {
//3.获取每个单独Entry对象,通过Entry对象获取对应的Key和Value
String key = entry.getKey();
String value = entry.getValue();
System.out.println("[key:" + key + " value:" + value+"]");
}
}
}
2.6 使用HashMap存储自定义对象并去重
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class Test {
public static void main(String[] args) {
//创建HashMap对象
HashMap<Student,String> hashMap = new HashMap<Student,String>();
//创建Key对象
Student s1 = new Student("张三",18);
Student s2 = new Student("李四",20);
Student s3 = new Student("李四",20);
//添加映射关系
hashMap.put(s1,"班长");
hashMap.put(s2,"学委");
hashMap.put(s3,"学委");
//遍历HashMap对象
//1.获取所有的Entry对象
Set<Map.Entry<Student,String>> entries = hashMap.entrySet();
//2.遍历包含所有Entry对象集合
for (Map.Entry<Student,String> entry:entries) {
//3.获取每个单独Entry对象,通过Entry对象获取对应的Key和Value
Student key = entry.getKey();
String value = entry.getValue();
System.out.println("[key:" + key + " value:" + value+"]");
}
}
}
。