10.Set接口和常用方法
-
Set接口基本介绍
-
无序(添加和取出的顺序不一致),没有索引
取出的顺序虽然不是添加的顺序,但是它是固定的
-
不允许重复元素,所以最多只包含一个null
-
JDK API中set接口的实现类有:
-
-
Set接口的常用方法
和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样
-
Set接口的遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
-
可以使用迭代器
-
增强for
-
不能使用索引的方式来获取
-
11.HashSet
-
HashSet全面说明
-
HashSet实现了Set接口
-
HashSet实际上是HashMap
-
可以存放null值,但是只能有一个null
-
HashSet不保证元素是有序的,取决于hash后,在确定索引的结果(即不保证存放元素的顺序和取出顺序一致)
-
不能有重复元素和对象,在前面Set接口使用已经讲过。
-
-
HashSet底层分析
分析HashSet底层是HashMap,HashMap底层是(数组 + 链表 + 红黑树)
-
模拟HashSet底层(HashMap)
package com.mdklea.set_; public class HashSetStructure { public static void main(String[] args) { //模拟一个HashSet底层(HashMap的底层结构) Node[] nodes = new Node[16]; nodes[2] = new Node("jack",null); Node tom = new Node("tom",null); nodes[2].setNext(tom); } } class Node { private Object item; private Node next; public Object getItem() { return item; } public void setItem(Object item) { this.item = item; } public Node getNext() { return next; } public void setNext(Node next) { this.next = next; } public Node(Object item, Node next) { this.item = item; this.next = next; } }
-
分析HashSet的添加元素是如何实现的(hash() + equals())
size 就是我们每加入一个节点Node(k,v,h,next),size++;
当我们向hashset增加一个元素, ->Node -> 加入table,就算是增加了一个元素
-
练习
-
重写equals和hashCode 方法
package com.mdklea.set_; import java.util.HashSet; import java.util.Iterator; import java.util.Objects; public class HashSetExercise { @SuppressWarnings({"all"}) public static void main(String[] args) { HashSet hashSet = new HashSet(); hashSet.add(new Employee("m1",12)); hashSet.add(new Employee("m2",12)); hashSet.add(new Employee("m3",11)); Iterator iterator = hashSet.iterator(); while (iterator.hasNext()) { Object next = iterator.next(); System.out.println(next); } } } class Employee { private String name; private int age; public Employee(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return age == employee.age && Objects.equals(name, employee.name); } @Override public int hashCode() { return Objects.hash(name, age); } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
package com.mdklea.set_; import java.util.HashSet; import java.util.Iterator; import java.util.Objects; public class HashSetExercise02 { @SuppressWarnings({"all"}) public static void main(String[] args) { HashSet hashSet = new HashSet(); hashSet.add(new Employee("m1",25000,new Employee.MyDate(2000,1,3))); hashSet.add(new Employee("m2",25000,new Employee.MyDate(2014,1,4))); hashSet.add(new Employee("m3",25000,new Employee.MyDate(2024,1,2))); Iterator iterator = hashSet.iterator(); while (iterator.hasNext()) { Object next = iterator.next(); System.out.println(next); } } } @SuppressWarnings({"all"}) class Employee { private String name; private double sal; private MyDate birthday; public Employee(String name, double sal, MyDate birthday) { this.name = name; this.sal = sal; this.birthday = birthday; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSal() { return sal; } public void setSal(double sal) { this.sal = sal; } public MyDate getBirthday() { return birthday; } public void setBirthday(MyDate birthday) { this.birthday = birthday; } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", sal=" + sal + ", birthday=" + birthday + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return Objects.equals(name, employee.name) && Objects.equals(birthday, employee.birthday); } @Override public int hashCode() { return Objects.hash(name, birthday); } static class MyDate { int year; int mouth; int day; public MyDate(int year, int mouth, int day) { this.year = year; this.mouth = mouth; this.day = day; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMouth() { return mouth; } public void setMouth(int mouth) { this.mouth = mouth; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } @Override public String toString() { return "MyDate{" + "year=" + year + ", mouth=" + mouth + ", day=" + day + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyDate myDate = (MyDate) o; return year == myDate.year && mouth == myDate.mouth && day == myDate.day; } @Override public int hashCode() { return Objects.hash(year, mouth, day); } } }
12.LinkedHashSet
-
LinkedHashSet的全面说明
-
LinkedHashSet是HashSet的子类
-
LinkedHashSet底层是一个LinkedHashMap(HashMap的子类),底层维护了一个数组 + 双向链表
-
LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
-
LinkedHashSet不允许添加重复元素
-
-
LinkedHashSet底层机制图
-
底层解读
-
LinkedHashSet加入顺序和取出元素顺序一致
-
LinkedHashSet底层维护的是一个LinkedHashMap(HashMap的子类)
-
LinkedHashMap底层结构(数组table + 双向链表)
-
添加第一次时,直接将数组table扩容到16,存放的节点类型是LinkedHashMap$Entry,而table类型为LinkedHashMap$Node[],因此,存放节点应继承了LinkedHashMap$Node
-
数组是HashMap$Node[] 存放的元素/数据是LinkedHashMap$Entry类型
继承关系是在(静态)内部类完成的
-
-
课后练习
package com.mdklea.set_;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Objects;
public class LinkedHashSetExercise_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(new Car("奥迪",300000));
linkedHashSet.add(new Car("法拉利",3000000));
linkedHashSet.add(new Car("奥托",30000));
linkedHashSet.add(new Car("奥迪",300000));
linkedHashSet.add(new Car("奥迪",300000));
linkedHashSet.add(new Car("奥迪",300000));
System.out.println(linkedHashSet);
Iterator iterator = linkedHashSet.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}
class Car {
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) 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);
}
}
13.Map接口实现类的特点(很实用)
注意:这里讲的是JDK8的Map接口特点
-
Map与Collection并列存在,用于保存具有映射关系的数据。Key-Value
-
Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
-
Map中的Key不允许重复,原因和HashSet一样。当有相同的k就等价于替换
-
Map中的Value可以重复
-
Map中的Key可以为null,value也可以为null,注意:key为null,只能有一个,value为null,可以多个。
-
常用String类作为Map的key
-
key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
-
Map存放数据的key-value示意图,一对key-value是放在一个Node中的,又因为Node实现了Entry接口,有些书上也说一对key-value就是一个Entry
-
k-v 最后是HashMap$Node node = new Node(hash,key,value,null);
-
k-v 为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素类型 Entry,而一个Entry对象就有k,v EntrySet<Entry<K,V>>
-
entrySet中,定义的类型是May.Entry,但是实际上存放的还是HashMap$Node,这是因为HashMap&Node implements Map.Entry<k,v>;
-
当把HashMap$Node 对象存放到 entrySet 就方便我们的遍历,因为Map.Entry提供了重要方法 K getKey(); V getValue();
-
-
//Node 实现了Entry 接口 static class Node<K,V> implements Map.Entry<K,V> { }
-
Map接口常用方法
package com.mdklea.map_; import java.util.HashMap; import java.util.Map; public class MapMethods { @SuppressWarnings({"all"}) public static void main(String[] args) { Map map = new HashMap(); //put map.put("邓超"," "); map.put("邓超","孙俪"); map.put("王宝强","马蓉"); map.put("宋喆","马蓉"); map.put("刘令博",null); map.put(null,"刘亦菲"); map.put("鹿晗","关晓彤"); System.out.println("map = " + map); //remove 根据键删除映射关系 map.remove(null); System.out.println("map = " + map); //get: 根据键获取值 System.out.println(map.get("鹿晗")); //size 获取元素个数 System.out.println(map.size()); //isEmpty 判断个数是否为0 System.out.println(map.isEmpty()); //clear 清除 // map.clear(); // System.out.println(map); //containsKey 查找键是否存在 System.out.println(map.containsKey(null)); } }
-
Map接口的遍历方法
-
containsKey 查找键是否存在
-
keySet 获取所有的键
-
entrySet 获取所有关系k-v
-
values 获取所有的值
package com.mdklea.map_; import java.util.*; public class MapFor { @SuppressWarnings({"all"}) public static void main(String[] args) { Map map = new HashMap(); //put map.put("邓超"," "); map.put("邓超","孙俪"); map.put("王宝强","马蓉"); map.put("宋喆","马蓉"); map.put("刘令博",null); map.put(null,"刘亦菲"); map.put("鹿晗","关晓彤"); //第一组:先取出所有的key Set keySet = map.keySet(); for (Object key :keySet) { System.out.println(key + "-" + map.get(key)); } System.out.println("===================================="); //迭代器 Iterator iterator = keySet.iterator(); while (iterator.hasNext()) { Object key = iterator.next(); System.out.println(key + "-" +map.get(key)); } System.out.println("===================================="); //第二组 Collection values = map.values(); //可以使用所有collection遍历方法,除了for循环 for (Object value :values) { System.out.println(value); } System.out.println("==============================="); Iterator iterator1 = values.iterator(); while (iterator1.hasNext()) { Object value = iterator1.next(); System.out.println(value); } System.out.println("======================="); //values.get(); get方法不可用,不可用for循环遍历 //第三组 通过EntrySet 来获取 Set entrySet = map.entrySet(); Iterator iterator2 = entrySet.iterator(); while (iterator2.hasNext()) { Object o = iterator2.next(); //将entry转成map.entry Map.Entry entry = (Map.Entry)o; System.out.println(entry.getKey()); System.out.println(entry.getValue()); } System.out.println("==============================="); for (Object o :entrySet) { Map.Entry entry = (Map.Entry)o; // System.out.println(entry.getKey()); // System.out.println(entry.getValue()); System.out.println(entry.getClass());//HashMap$Node -实现>Map.Entry } } }
-
-
练习
package com.mdklea.map_; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class MapExercise { @SuppressWarnings({"all"}) public static void main(String[] args) { Map map = new HashMap(); Employee number1 = new Employee("NO1","m1",30000,"java"); Employee number2 = new Employee("NO2","m2",33000,"python"); Employee number3 = new Employee("NO5","wew",30000,"java"); Employee number4 = new Employee("NO1","m3",30000,"java"); Employee number5 = new Employee("NO1","m4",30000,"java"); map.put(number1.getId(),number1); map.put(number2.getId(),number2); map.put(number3.getId(),number3); map.put(number4.getId(),number4); map.put(number5.getId(),number5); Set set = map.entrySet(); Iterator iterator = set.iterator(); while (iterator.hasNext()) { Object o = iterator.next(); Map.Entry entry = (Map.Entry) o; System.out.println(entry.getKey() + "-" + entry.getValue()); } } } class Employee { private String id; private String name; private double sal; private String positon; public Employee(String id, String name, double sal, String positon) { this.id = id; this.name = name; this.sal = sal; this.positon = positon; } public Employee() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSal() { return sal; } public void setSal(double sal) { this.sal = sal; } public String getPositon() { return positon; } public void setPositon(String positon) { this.positon = positon; } @Override public String toString() { return "Employee{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", sal=" + sal + ", positon='" + positon + '\'' + '}'; } }
14.HashMap
-
HashMap小结
-
Map接口常有实现类:HashMap、Hashtable、Properties
-
HashMap是Map接口使用频率最高的实现类
-
HashMap是以key-value对的方式来存储数据的
-
key不能重复,但是值可以i重复,允许使用null键和null值
-
如果添加相同的key,则会覆盖原来的key-value,等同于修改(key不会替换,val会替换)
-
与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的
-
HashMap没有实现同步,因此是线性是不安全的
-
-
HashMap底层机制和源码分析
扩容机制和HashSet完全一致
-
HashSet底层维护了Node类型的数组table,默认为null
-
当创建对象时,将加载因子(loadfactor)初始化为0.75
-
当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容
-
第一次添加,则需要扩容table容量为16,临界值(threshold)为12
-
以后再扩容,则需要扩容table容量为原来的两倍,临界值为原来的2倍,即24,以此类推。
-
在java8中,如果一条链表的个数超过了TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认是64),就会进行树化(红黑树)
-
15.HashTable
-
HashTable的基本介绍
-
存放的元素是键值对:即k-v
-
HashTable的键和值都不能是null,否则会抛出NullPointerException
-
HashTable的使用方法基本上和 HashMap一样
-
HashTable是线性安全的,HashMap是线性不安全的
-
简单说明一下HashTable的底层
-
底层有数组 Hashtable$Entry[] 初始化大小为11
-
临界值 threshold 8 = 11 * 0.75
-
扩容:
-
-
-
HashMap和HashTable比较
16.Map接口实现类-Properities
-
基本介绍
-
Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据
-
他的使用特点和HashTable类似
-
Properities还可以用于从xxx.properties文件中,加载数据到properties类对象,并进行读取和修改
-
说明:工作后 xxx.propertiies文件通常为配置文件,这个知识点在IO流举例
-
-
解读
-
properities 继承 Hashtable
-
可以通过 k-v 存放数据 当然key和value不能为null
-
-
常用方法
17.总结-开发中如何选择集合实现类
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特别进行选择,分析如下:
-
先判断储存的类型(一组对象或一组键值对)
-
一组对象:Collection接口
允许重复
List
增删多:LinkedList[底层维护了一个双向链表]
改查多:ArrayList[底层维护Object类型的可变数组]
不允许重复
Set
无序:HashSet[底层是HashMap,维护了一个哈希表,即(数组 + 链表 + 红黑树)]
排序:TreeSet
插入和取出顺序一致:LinkedHashSet,维护数组 + 双向链表
-
一组键值对
键无序:HashMap[底层是:哈希表 jdk7:数组 + 链表,jdk8:数组 + 链表 + 红黑树]
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件:Properties
18.TreeSet
-
源码解读
-
构造器把传入的比较器对象,赋给了TreeSet的底层TreeMap的属性this.comparator
-
package com.mdklea.set_; import java.util.Comparator; import java.util.TreeSet; public class TreeSet_ { @SuppressWarnings({"all"}) public static void main(String[] args) { //当我们使用无参构造器床架treeset是仍然是无序的 //希望添加的元素按照字符串大小来排序 //使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类指定排序规则) // TreeSet treeSet = new TreeSet(); TreeSet treeSet = new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2) { //下面调用String的compareTo方法对字符串大小进行比较 String s1 = (String) o1; String s2 = (String) o2; //return (s1.compareTo(s2)); return s1.length() - s2.length();//如果用字符串长度比, //下方只有jack和tom可以加进去 } }); treeSet.add("jack"); treeSet.add("tom"); treeSet.add("hsp"); treeSet.add("md1"); System.out.println("treeSet = " + treeSet); } }
-
19.TreeMap
-
解读源码
-
构造器,把传入的实现了Comparator接口的匿名内部类(对象),传给了TreeMap的comparator
-
调用put方法
-
第一次添加,把k-v封装到Entry对象,放入root
-
以后添加
-
-
20.Collections工具类
-
Collections工具类介绍
-
Collections是一个操作List,Set,Map等集合的工具类
-
Collections中提供了一系列静态的方法对集合元素进行排序,查询和修改操作
-
-
排序操作:(均为static方法)
-
reverse(List):反转List中元素的顺序
-
shuffle(List):对List集合元素进行随机排序
-
sort(List):根据元素的自然顺序对指定的List集合元素按升序排序
-
sort(List,Compartor):根据指定的Comparator产生的顺序对List集合元素进行排序
-
swap(List,int,int)将指定list集合中的i处元素和j处元素进行交换
package com.mdklea.collections_; import java.util.*; public class Collections_ { @SuppressWarnings({"all"}) public static void main(String[] args) { //reverse() List list = new ArrayList(); list.add("tom"); list.add("smith"); list.add("king"); list.add("milan"); System.out.println(list); System.out.println("====================="); Collections.reverse(list); System.out.println(list); //Shuffle() Collections.shuffle(list); System.out.println(list); //sort() Collections.sort(list); System.out.println(list); //希望按照字符串长度大小排序 Collections.sort(list, new Comparator() { @Override public int compare(Object o1, Object o2) { //可以加入校验代码 String s1 = (String) o1; String s2 = (String) o2; return s1.length() - s2.length(); } }); System.out.println(list); } } public static void reverse(List<?> list) {}
观察底层可以发现这些方法只能传入list作为参数
-
-
查找、替换
-
Object max(collection):根据元素的自然顺序,返回给定集合中最大的元素
-
Object max(collection,Comparator):根据Comparator指定的顺序,返回给定集合中最大元素
-
Object min(collection)
-
Object min(collecion,Comparator)
-
int frequency(Collection,Object):返回指定集合中指定元素出现个数
-
void copy(List dest,List src):将src中的内容复制到dest中
-
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
package com.mdklea.collections_; import java.util.*; public class Collections_02 { @SuppressWarnings({"all"}) public static void main(String[] args) { Set set = new HashSet(); set.add(11); set.add(222); set.add(222); set.add(222); set.add(4); set.add(66); set.add(888); //max System.out.println(Collections.max(set)); //max 使得最大为最小(dog) System.out.println(Collections.max(set, new Comparator() { @Override public int compare(Object o1, Object o2) { int i1 = (int) o1; int i2 = (int) o2; return i2 - i1; } })); //min System.out.println(Collections.min(set)); //frequency System.out.println(Collections.frequency(set,222)); //注意:hashset中数字不可重复所以结果还是1 List list = new ArrayList(); List list1 = new ArrayList(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); //copy nCopies for(int i = 0; i < list.size(); i++) { list1.add(""); } //System.out.println(list1.size()); Collections.copy(list1,list); System.out.println(list1); //replaceAll Collections.replaceAll(list,5,7); System.out.println(list); } }
-
21.练习
package com.mdklea.homework_;
import java.util.ArrayList;
public class HomeWork01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
New new1 = new New("新冠确诊病例超千万,数百万印度教信徒赴恒河\"圣育\"引民众担忧");
New new2 = new New("男子突然想起2个月前钓起的鱼还在网兜里,捞起一看赶紧放生");
ArrayList newsList = new ArrayList();
newsList.add(new1);
newsList.add(new2);
for (int i = newsList.size() - 1; i >= 0; i--) {
New newAll = (New)newsList.get(i);
System.out.println(Regulate.processTitle(newAll.getTitle()));
}
}
}
class New {
private String title;
private String contant;
public New(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContant() {
return contant;
}
public void setContant(String contant) {
this.contant = contant;
}
@Override
public String toString() {
return title + '\n';
}
}
class Regulate {
public static String processTitle(String title) {
if (title == null){
return "";
}
if (title.length() > 15){
return title.substring(0,15) + "...";
}else return title;
}
}
-
-
HashSet的去重机制:HashCode() + equals(),底层先通过存入对象,进行运算先得到一个hash值,通过hash值得到对应的索引,如果发现table表索引所在的位置没有数据,就直接存放,如果有数据,就进行equals比较[遍历比较],如果比较后不相同,就加入,否则就不加入。
-
TreeSet的去重机制:如果你传入了一个Comparator匿名对象,就使用实现的compare去重,如果方法返回0,就认为是相同的元素/数据,就不添加,如果你没有传入一个Comparator匿名对象,则以你添加的对象实现的Compareable接口的compareTo去重。
-