7.1Set接口的基本介绍
1)无序(添加和取出的顺序不一致),没有索引。
2)不允许重复元素,即最多包含一个null。
3)Set接口常用的实现类有TreeSet、HashMap。
Set是Collection的子接口,所以常用的方法与Collection接口一致。
Set set = new HashSet();
set.add("lutao");
set.add("lutao1");
set.add("lutao2");
set.add("lutao3");
set.remove("lutao");
set.size();//获取长度 //等等方法
8.1HashSet类的特点
1)HashSet实现的是Set接口即(无序、元素不可重复)
2)HashSet底层实际是HashMap
3)HashMap的底层结构是(数组+链表+红黑树)
底层是new了一个HashMap 机制 |
package com.lt.Set_;
import java.util.HashMap;
import java.util.HashSet;
@SuppressWarnings({"all"})
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("Java");
hashSet.add("Java");
System.out.println(hashSet);
//1.执行add()
public boolean add(E e) {
return map.put(e, PRESENT)==null; final PRESENT = new Object();
} //在HashSet中是一个固定的默认值,在Map中才起作用。
//2.执行put(K key, V value) key是添加的元素,value是固定值
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//3.执行hash(Object key) 得到的hash值决定该元素在table的位置
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)(无符号右移16位);
//4.执行putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)
HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
//table 就是HashMap 的一个数组,类型是Node[]
//判断该tab是否为null,如果是则进行第一次扩容 扩容到 16.
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//判断该元素存入的位置是否已经存在元素,如果不存在,则直接将newNode(hash, key, value, null)存入该位置
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
HashMap.Node<K,V> e; K k;
//如果链表的第一个索引位置的元素与即将加入的元素hash值相同,
// 并且即将加入的key与p指向的Node结点的key是同一个对象,
// 或者它们两者调用的equals比较后相同,则不能添加。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断 p是否是一颗红黑树,如果是则调用PutTreeVal添加
else if (p instanceof HashMap.TreeNode)
e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果该key加入的位置已经存在一条链表则循环比较每一个元素,如果都不相同,则将key添加到链表最后。
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果该条链表的已经存在8个结点了,那么则调用treeifBin(tab.hash)判断是否达到转成红黑树的条件。
// ①即单条链表长度已经到达8,②并且table的大小>=64。
// 如果第一个条件成立,第二个条件不成立,则先将table扩容
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;
//首先e = p.next , p = e, 比较完当前元素后,重新指向下一个元素
p = e;
}
}
//如果key重复,才会进入到这里
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount; 记录添加次数
//每次添加完后 size+1.
//当临界值为12时,如果table的长度已经达到12,再次添加一个元素的话,则需要调用resize()扩容。
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
//执行resize()扩容
}
}
9.1LinkedHashSet类的说明
1)LinkedHashSet是HashSet的子类;
2)LinkedHashSet底层是一个LinkedHashMap,底层维护的是数组+双链表的结构;
3)LinkedHashSet跟HashSet一样根据哈希值运算得到在数组的位置,但底层的次序是由双向链表的前后缀维护元素的次序,所以元素是有序的。
LinkedHashSet添加元素的底层源码与HashSet相差无几,就不再解读了。
1)在LinkedHashSet中维护了一个hash表和双向链表 (维护了head和tail两个属性,分别指向链表的头尾。) 2)每一个结点有一个before和after,分别指向结点的前后缀,形成一个双向链表 3)添加元素的规则与HashSet一致。 |
9.2扩展
重写equals 和hashCode
判断当name和price两者都相同的情况下,才被认为是相同的元素。
package com.lt.Set_;
import java.util.LinkedHashSet;
import java.util.Objects;
public class LinkedHashSetExrecise {
public static void main(String[] args) {
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(new Car("奥拓",1000));
linkedHashSet.add(new Car("奥迪",300000));
linkedHashSet.add(new Car("法拉利",1000000000));
linkedHashSet.add(new Car("奥迪",300000)); //name 和price 都与前面相同,添加失败
linkedHashSet.add(new Car("保时捷",700000000));
linkedHashSet.add(new Car("奥迪",300000));
for (Object o : linkedHashSet) {
System.out.println(o);
}
}
}
class Car{
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
@@@省略get set和toString方法
//重写equals 和hashCode
//判断当name和price两者都相同的情况下,才被认为是相同的元素。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Car)) return false;
Car car = (Car) o;
return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
}
10.1Map 接口实现类的特点
1)Map和Collection是并列,即两者之间没有关系。
2)Map用于保存具有一对一映射关系的数据:key-value
3)Map中的key和value任意的引用类型(即Object),封装到HashMap$Node对象中
4)Map中的Key不可以重复,value可以重复,(key只可以有一个null,value可以有多个value)
5)key和value是一对一的关系,即通过key一定可以找到value
10.2Map接口常用方法
Map map = new HashMap();
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
map.put("lt","kqh");
map.put("lt","null"); //当添加的key值相同是,后的value会替换前面的value。
System.out.println(map);
//remove根据键删除关联的值
map.remove(null);
System.out.println(map);
//get 根据key获取value的值
System.out.println("根据key获取value的值"+map.get("宋喆"));
//size 获取集合中元素的数量
System.out.println("获取集合中元素的数量"+map.size());
//isEmpty 判断元素个数是否为0
System.out.println("判断元素个数是否为0"+map.isEmpty());
//containsKey判断键是否存在
System.out.println(" "+map.containsKey("lt"));
//clear 清空集合
map.clear();
System.out.println(map);
10.3Map接口的六大遍历方法
Node、Entry、EntrySet三者之间的关系。
Node结点是实际存储key-value的对象,Entry是Map的一个静态内部接口,里面有getkey,getvalue等方法。
entrySet()是一个返回key-value集合的方法。(在有大量元素的情况下,使用entrySet()遍历,效率会更高)
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
//第一组 先取出key集合,然后再用增强for和迭代器遍历
Set set = map.keySet();
//(1)增强for
for (Object key : set) {
System.out.print(" "+map.get(key));
}
System.out.println();
//(2)迭代器
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.print(" "+map.get(key));
}
System.out.println();
//第二组 直接把values取出
// (3)
Collection collection = map.values();
for (Object values : collection) {
System.out.print(" "+values);
}
System.out.println();
// (4)
Iterator iter = collection.iterator();
while (iter.hasNext()) {
Object values1 = iter.next();
System.out.print(" "+values1);
}
System.out.println();
// 第三组直接在
// (5)
Set entrySet = map.entrySet();
for (Object o : entrySet) {
Map.Entry key = (Map.Entry) o;
System.out.print(" " + key.getValue());
}
System.out.println();
// (6)
Iterator iter1 = entrySet.iterator();
while (iter1.hasNext()) {
Object next = iter1.next();
Map.Entry key = (Map.Entry) next;
System.out.print(" "+key.getValue());
}
System.out.println();
System.out.println();
11.1HashMap说明
1)HashMap是Map接口使用频率最高的实现类;
2)HashMap是Map的实现类所以是以key-value对的方法存储数据,key不可以重复,value可以重复;
3)底层是以hash表的方式存储数据,所以不保证数据映射的顺序;
4)HashMap没有实现同步,线程不安全.
HashMap的源码以及扩容机制等在前面的HashSet(本质是HashMap)中,讲过了(大致上是一样的,可以自己看看,研究一下)。
11.2Hashtable说明
1)Hashtable的key和value都不可以为null,否则会报NullPointerException
2)Hashtable的基本使用与HashMap一样。
3)Hashtable是线程安全级的(synchronized)。
所以当value和key为null是会报空指针异常 |
11.3Properties说明
1)Properties类继承Hashtable类并且实现了Map接口。
2)Properties通常用于从xxx.properties文件中,加载数据到Properties对象中,并进行读取和修改。
3)xxx.properties文件通常作为配置文件。
12.1TreeSet
public static void main(String[] args) {
//1. 当我们使用无参构造器,创建TreeSet 时,仍然是无序的
//2.如果希望添加的元素,按照字符串大小来排序
//3. 使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)
// 并指定排序规则
/*
1. 构造器把传入的比较器对象,赋给了TreeSet 的底层的TreeMap 的属性this.comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 在调用treeSet.add("tom"), 在底层会执行到
if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
do {
parent = t;
//动态绑定到我们的匿名内部类(对象)compare
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果相等,即返回0,这个Key 就没有加入
return t.setValue(value);
} while (t != null);
}
*/
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//匿名内部类中,返回o1-o2 即按照从小到大顺序输出。
return ((String) o1).length() - ((String) o2).length();
}
});
treeSet.add("jack");
treeSet.add("tom");//3
treeSet.add("lt");
treeSet.add("a");
treeSet.add("abc");//3 在从写的内部类中,比较的是字符串的长度,所以字符串长度相等的字符串不能再次添加。
System.out.println(treeSet);
}
13.1开发中如何选择集合实现类
在开发中,选择哪一个集合实现类,主要取决于业务的操作特点,然后根据集合实现类特性进行选择:
1.先判断存储的类型(一组对象[单列]或一组键值对[双列]
2.一组对象[单列]:Collection接口
允许重复:List接口
增删多:LinkedList
改查多:ArrayList
不允许重复:Set接口
无序:HashSet(数组+链表+红黑树)
排序:TreeSet
插入和取出顺序一致:LinkedHashSet
3.一组键值对[双列]:Map
键无序:HashMap
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMao
读取文件:Properties
14.1Collections工具类说明
1)Collection是一个操作Set、List和Map等集合的工具类
2)Collection中提供了一系列的静态方法(可以直接用类调用)对集合进行排序、查询和修改等操作。
package com.lt;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@SuppressWarnings({"all"})
public class Collections_ {
public static void main(String[] args) {
ArrayList<String> slist = new ArrayList<>();
slist.add("lutao1");
slist.add("lutao2");
slist.add("lutao3");
slist.add("lutao4");
slist.add("lutao5");
slist.add("lutao");
/*排序*/
// Collections.reverse(slist);
// System.out.println("反转"+slist);
// Collections.shuffle(slist);
// System.out.println("随机"+slist);
// Collections.sort(slist);
// System.out.println("按照自然顺序"+slist);
//按照指定顺序顺序
// Collections.sort(slist, new Comparator<String>() {
// @Override
// public int compare(String o1, String o2) {
// return o2.compareTo(o1);
// }
// });
// System.out.println("按照指定顺序"+slist);
// Collections.swap(slist,0,1);
// System.out.println("交换后"+slist);
/*查找、替换*/
// System.out.println("返回集合中最大的元素"+Collections.max(slist));
// System.out.println("按照指定顺序返回最大值"+Collections.max(slist, new Comparator<String>() {
// @Override
// public int compare(String o1, String o2) {
// return o2.compareTo(o1);
// }
// }));
// System.out.println("返回集合中最小值"+Collections.min(slist));
// System.out.println("按照指定顺序返回最小值"+Collections.min(slist, new Comparator<String>() {
// @Override
// public int compare(String o1, String o2) {
// return o1.compareTo(o2);
// }
// }));
// System.out.println("返回集合中指定元素出现的次数"+Collections.frequency(slist,"lutao"));
// ArrayList list = new ArrayList<>(8);
// for (int i = 0; i < slist.size()+1; i++) {
// list.add(null);
// }
// //再底层中,要求copy的集合长度大于被复制的集合
// Collections.copy(list,slist);
// System.out.println(list);
// Collections.replaceAll(slist,"lutao","shuaige");
// System.out.println("使用新值替换集合中的所以旧值"+slist);
}
}