由于标题写不下了,那接下来我就来一一介绍吧。( 注意:重要的相关知识或者解释我都写到注释里了,便于大家对源码的解读)
首先让我们通过一个例子来看看 单例集合Collection与双列集合Map的区别吧:
package jihe.Collection_.com;
import java.util.ArrayList;
import java.util.HashMap;
@SuppressWarnings({"all"})//镇压警告
public class Collection_ {
public static void main(String[] args) {
//1. 集合主要是两组(单列集合,双列集合)
//2. Collection 接口有两个重要的子接口 List 和 Set ,他们的子类都是单列集合
//3. Map 接口的实现子类 是双列集合,存放的 K-V
// Collection
// Map
ArrayList arrayList = new ArrayList();
arrayList.add("风煞");
arrayList.add("tom");
arrayList.add(100);
System.out.println("arrayList="+arrayList);
HashMap hashMap = new HashMap();
hashMap.put("第一","北京");
hashMap.put("第二","上海");
hashMap.put("第三","深圳");
System.out.println("hashMap="+hashMap);
}
}
那么通过对单例集合Collection与双列集合Map的区别的简单了解,接下来先让我们来对Collection单列集合进行进一步了解吧
那我们先来看一下Collection 子接口及实现类的类图关系吧:
首先来看一下Collection 接口常用方法:
package jihe.Collection_.com;
// Collection 接口常用方法
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings({"all"})
public class Collection_01 {
public static void main(String[] args) {
List list=new ArrayList();
// 1. add:添加单个元素 都是对象
list.add("majunyi");
list.add(124);// 相当于 list.add( new Integer(124) );都是对象
list.add(true);
System.out.println("list="+list);
// 2. remove:删除指定元素
// list.remove(124); //删除某个指定对象元素 返回布尔值
list.remove(0);//删除第一个元素
System.out.println("list="+list);
// 3.contains:查找元素是否存在
System.out.println( list.contains(true));//true
// 4. size:获取元素个数
System.out.println(list.size());// 2
// 5.isEmpty:判断是否为空
System.out.println(list.isEmpty());// false
// 6. clear:清空
list.clear();
System.out.println("list="+list);// list=[]
// 7. addAll:添加多个元素
List list1 = new ArrayList();
list1.add("中国");
list1.add("majunyi");
list1.add(100);
list1.add(false);
list.addAll(list1);
System.out.println("list="+list);// list=[中国, majunyi, 100, false]
// 8.containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list1));// ture
// 9. removeAll:删除多个元素
list.add("张祎");
list.removeAll(list1);
System.out.println("list="+list);// 张祎
// 说明: 以上 ArrayList 实现类来演示
}
}
接下来让我们看一看Collection 子接口实现类的遍历方法-1.迭代器Iterator 2.增强 for 3.普通 for :
package jihe.Collection_.com;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
// Collection 迭代器 Iterator 遍历集合
@SuppressWarnings({"all"})
public class CollectionIterator {
public static void main(String[] args) {
List col =new ArrayList();
col.add(new Book("三国演义","罗贯中",10.2));
col.add(new Book("小李飞刀","古龙",7.4));
col.add(new Book("红楼梦","曹雪芹",15.7));
// col.add("majunyi"); 存的是对象,该句并不报错
// System.out.println("col="+col);
//现在我希望能够遍历col 集合
//1. 先得到 col 的迭代器
Iterator iterator= col.iterator();
//2. 使用 while 循环遍历集合 col
while(iterator.hasNext()){//判断是否还有数据
//返回下一个元素,类型是Object
Object obj=iterator.next();//编译类型为 Object
System.out.println("obj="+obj);// 运行类型为Book 多态问题
// 或者直接 System.out.println(iterator.next()); 也没有问题
}
//教大家一个快捷键 迅速生成 while 循环 输入 itit
//或者 敲 Ctrl+j 选取当前的 while 循环
//3. 当退出 while 循环后 此时 iterator 迭代器,指向最后一个对象元素
//不能再 iterator.next() 否则将抛出异常
//如果想再次迭代遍历集合 需要对迭代器进行重置
//即 iterator=col.iterator(); 即可进行第二次遍历
System.out.println("\n\n\n");
//另外还有一种简化的迭代器
//使用 增强 for 循环遍历 Collection 集合
for (Object obj:col) {
System.out.println("obj="+obj);
}
//同样 教大家一个快捷键 迅速生成增强 for 循环 输入 I 直接回车
//当然你也可以 敲 Ctrl+j 选取当前的增强 for 循环
System.out.println("\n\n\n");
//普通for
for (int i = 0; i < col.size(); i++) {
Object o=col.get(i);
System.out.println("o="+o);
}
}
}
@SuppressWarnings({"all"})
class Book{
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
接下来我用一个双向链表来为大家模仿一下LinkedList 的底层结构:
package jihe.Collection_.com;
@SuppressWarnings({"all"})
public class CollectionLinkedList {
public static void main(String[] args) {
//模拟一个双向链表
Node tom = new Node("tom");
Node majunyi = new Node("majunyi");
Node jake = new Node("jake");
//连接三个节点,形成双向链表
//tom->majunyi->jake
tom.next=majunyi;
majunyi.next=jake;
//jake->majunyi->tom
jake.prve=majunyi;
majunyi.prve=tom;
//fist and last
Node fist=tom;//头节点
Node last=jake;//尾节点
//在tom 和 majunyi 之间添加一个 zhangfei
Node zhangfei = new Node("zhangfei");
tom.next=zhangfei;//tom->zhangfei
zhangfei.prve=tom;//zhangfei->tom
zhangfei.next=majunyi;//zhangfei->majunyi
majunyi.prve=zhangfei;//majunyi->zhangfei
//删除 majunyi
zhangfei.next=jake;
jake.prve=zhangfei;
//演示遍历
//从头到尾
while (true){
if(fist==null){
break;
}
else{
System.out.println(fist);
fist=fist.next;
}
}
System.out.println("\n\n\n");
//从尾到头
while (true){
if(last==null){
break;
}
else{
System.out.println(last);
last=last.prve;
}
}
}
}
// 定义一个 Node 类,Node 对象,表示双向链表的一个节点
class Node{
public Object item;//真正存放数据
public Node next;//指向下一个节点
public Node prve;//指向上一个节点
public Node(Object name){
this.item=name;
}
public String toString(){
return "Node name="+item;
}
}
接下来让我们来看看List接口实现类的主要方法:
package jihe.Collection_.com;
// List 中的常用方法
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings({"all"})//镇压警告
public class CollectionList {
public static void main(String[] args) {
List list =new ArrayList();
// 1. List接口实现类 是有序的 而且对象元素是可以重复的 存取顺序相同
// add:添加方法
list.add("jake");
list.add("majunyi");
list.add("mary");
list.add("mary");//可以重复
System.out.println("list="+list);
//另外还有 addAll:添加多个元素
List list1 = new ArrayList();
list1.add("中国");
list1.add(100);
list1.add(false);
list.addAll(1,list1);
System.out.println("list="+list);// 输出 list=[jake, 中国, 100, false, majunyi, mary, mary]
// 相当于从第二个元素开始插入
// 2. List 集合中的每一个元素都有其对应的顺序索引,即支持索引
// Object get(int i) :取出第i+1个元素
System.out.println(list.get(1));//取出第二个元素
// 3. int indexOf(Object obj) :返回obj 在集合中第一次出现的位置
System.out.println(list.indexOf("majunyi"));// 4
// 4. int lastIndexOf(Object obj):返回obj 在集合中最后一次出现的位置
System.out.println(list.lastIndexOf("mary"));// 6
// 5. Object remove(int index):移除指定位置的元素
list.remove(6);
System.out.println("list="+list);// 输出 list=[jake, 中国, 100, false, majunyi, mary]
// 6. Object set(int index,Object obj):设置指定index 位置的元素为obj ,相当于是替换
// 注意:不能越界使用
list.set(3,true);
System.out.println("list="+list);// 输出 list=[jake, 中国, 100, true, majunyi, mary]
// 7. List subList(int formIndex, int toIndex): 返回从 forIndex 到 toIndex位置的子集合
List list2 = list.subList(1, 5);
System.out.println("list2="+list2);// 输出 list2=[中国, 100, true, majunyi]
//也就是说 subList(int formIndex, int toIndex) 返回的元素并不包含 toIndex位置的元素,
// 可以理解为返回 [formIndex,toIndex) 区间位置的元素
}
}
另外让我们来了解一下Set接口实现类的相关性质:
package jihe.Collection_.com;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
//Collection 中 Set 集合是无序的,且对象元素不能重复,也没有索引,即无法通过索引来获取元素,最多只能有一个空值
//方法和Collection接口相同,迭代器都相同 Iterator
// HashSet, LinkedHashSet,TreeSet
@SuppressWarnings({"all"})
public class CollectionSet_ {
public static void main(String[] args) {
Set set=new HashSet();
set.add("ject");
set.add("majunyi");
set.add(true);
set.add(null);
set.add(168);
System.out.println("set="+set);//输出 set=[null, majunyi, 168, ject, true]
//可以看出取出顺序和添加顺序不一致
// 但他的去除顺序是固定的也就是 下一次取出显示也为 set=[null, majunyi, 168, ject, true]
set.add("zhangfei");
System.out.println("set="+set+"\n\n\n");
set.remove("zhangfei");
//迭代器
Iterator iterator= set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next="+next);
// System.out.println(iterator.next());
}
System.out.println("\n\n\n");
//增强for 遍历
for (Object o :set) {
System.out.println(o);
}
//set 不能用普通for 遍历
}
}
同样我来为大家演示一下HashSet的底层Hashtabe的底层机制:(这里我建议大家去追一遍,顺便了解一下Set接口的实现类不能重复的原因和标准):
package jihe.Collection_.com;
@SuppressWarnings({"all"})
public class HashSet_ {
public static void main(String[] args) {
//模拟 HashSet->HashMap的底层 数组+单向链表+红黑树
// 1.创建一个数组,数据的类型是 Noed_
Noed_[] tabe = new Noed_[16];
System.out.println("tabe="+tabe);
// 2. 将john放到 tabe[2]
Noed_ john=new Noed_("john",null);
tabe[2]=john;
Noed_ jact=new Noed_("jact",null);
john.next=jact;//john->jact
Noed_ rose = new Noed_("rose",null);
jact.next=rose;//jact->rose
System.out.println("tabe="+tabe[2]);
Noed_ lucy=new Noed_("lucy",null);
tabe[1]=lucy;
Noed_ majunyi=new Noed_("majunyi",null);
lucy.next=majunyi;
System.out.println("tabe="+tabe[1]);
}
}
class Noed_{//节点,存储数据,可以指向下一个节点,从而形成链表(单项链表)
Object item;//存放数据
Noed_ next;//指向下一个节点
public Noed_(Object item, Noed_ next) {
this.item = item;
this.next = next;
}
@Override
public String toString() {
return "Noed_{" +
"item=" + item +
", next=" + next +
'}';
}
}
下面让我们来看看HashSet 元素加入的规则:
package jihe.Collection_.com;
//Set 添加
import java.util.HashSet;
import java.util.Set;
@SuppressWarnings({"all"})
//HashSet 的底层是HashMap,HashMap 的底层是(数组+单向链表+红黑树)
public class Set_HashSet {
public static void main(String[] args) {
Set set = new HashSet();
System.out.println(set.add("jact"));
System.out.println(set.add("jact"));//加入不了
System.out.println(set.add(new cat("tom")));
System.out.println(set.add(new cat("tom")));//没有问题 相当于使用匿名类生成对象,虽然内部属性相同,
// 但由于没有类名所以成功
//底层通过 equals 方法进行比较
System.out.println("set="+set);
//经典面试题
System.out.println(set.add(new String("has")));
System.out.println(set.add(new String("has")));//加入不成功
}
}
@SuppressWarnings({"all"})
class cat{
private String name;
public cat(String name) {
this.name = name;
}
@Override
public String toString() {
return "cat{" +
"name='" + name + '\'' +
'}';
}
}
接下来我们来看一个HashSet的练习,以及看看Set接口实现类不能重复的标准以及操作方法:
package jihe.Collection_.com;
//HashSet 底层源码 HashSet 练习
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
@SuppressWarnings({"all"})
public class HashSetSoure {
public static void main(String[] args) {
Test1();
Test2();
}
public static void Test1() {
/* 定义一个 Employee 类 该类包含 private: name,age 要求:
创建3个Employee对象 放入 HashSet中
当name,age值相同时,认为员工相同,不能添加入HashSet集合中
*/
HashSet hashSet = new HashSet();
hashSet.add(new Employee("milan", 18));
hashSet.add(new Employee("majunyi", 28));
hashSet.add(new Employee("milan", 17));
System.out.println("hashSet=" + hashSet);
}
public static void Test2(){
/* 定义Employee_类,该类包括 private: neme,sal,birthday(MyDate)类,
其中 birthday 为 MyDate类包括: year,month,day,要求:
创建3个Employee对象 放入 HashSet中
当name,birthday值相同时,认为员工相同,不能添加入HashSet集合中
*/
HashSet set=new HashSet();
set.add(new Employee_("majunyi",5000.67,new MyDate(2002,10,23)));
set.add(new Employee_("zhangfei",40056,new MyDate(2003,9,21)));
set.add(new Employee_("tom",6005.23,new MyDate(2001,7,17)));
set.add(new Employee_("zhangfei",40056,new MyDate(2003,9,21)));
set.add(new Employee_("shengjri",8004.32,new MyDate(2001,9,24)));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj="+obj);
}
}
}
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 boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee_)) return false;
Employee_ employee_ = (Employee_) o;
return Double.compare(employee_.sal, sal) == 0 && Objects.equals(name, employee_.name) && Objects.equals(birthday, employee_.birthday);
}
@Override
public int hashCode() {
return Objects.hash(name, sal, birthday);
}
@Override
public String toString() {
return "Employee_{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
}
class MyDate{
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.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 && month == myDate.month && day == myDate.day;
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
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 String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//当name,age值相同时,认为员工相同,不能添加入HashSet集合中
//那就必须在当 当name,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);
}
}
从以上例子我们可以看出,我们可以通过重写类的hashCode方法和equals方法来实现对Set接口实现类不能重复的标准的控制及操作。
接下来我们通过这个例子来分析一下HashSet的子类LinkedHashSet的底层和应用场景:
package jihe.Collection_.com;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
//HashSet子类
//LinkedHashSet 底层为 LinkedHashMap 底层维护 数组+双向链表 存取元素顺序相同,但不能重复
@SuppressWarnings({"all"})
public class LinkedHashSet_ {
public static void main(String[] args) {
Test1();
Test2();
}
public static void Test1(){
//分析LinkedHashSet 底层
Set set=new LinkedHashSet();
set.add(new String("AA"));
set.add(456);
set.add(456);//添加失败
set.add(new Customer("刘",1001));
set.add(123);
set.add("hsp");
System.out.println("set="+set);//加入顺序和取出顺序相同
}
public static void Test2(){
//创建Car 类,包含name ,price 要求:
//当name 和 price 相同时不能添加
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(new Car("奥迪",300000));
linkedHashSet.add(new Car("法拉利",1000000));
linkedHashSet.add(new Car("奥拓",10000));
linkedHashSet.add(new Car("奥迪",300000));
linkedHashSet.add(new Car("保时捷",70000000));
linkedHashSet.add(new Car("奥拓",10000));
Iterator iterator = linkedHashSet.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next="+next);
}
}
}
class Car{
private String neme;
private double price;//价格
public Car(String neme, double price) {
this.neme = neme;
this.price = price;
}
public String getNeme() {
return neme;
}
public void setNeme(String neme) {
this.neme = neme;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"neme='" + neme + '\'' +
", 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(neme, car.neme);
}
@Override
public int hashCode() {
return Objects.hash(neme, price);
}
}
class Customer{
private String name;
private int no;
public Customer(String name, int no) {
this.name = name;
this.no = no;
}
@Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
", no=" + no +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
}
下面我来为大家介绍TreeSet类,其中我为大家展示了他的排序规则以及相关源码的触发:
package jihe.Collection_.com;
import java.util.Comparator;
import java.util.TreeSet;
// 源码解读 可排序
// TreeSet 底层是 TreeMap
@SuppressWarnings({"all"})
public class TreeSet_ {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 下面 调用String 的comparatTo 比较字符串大小(与C语言中字符串比较strcmp()函数效果相同)即按照字典排序 从小到大
// 1. 若要从大到小 则交换下面的o1和o2
return ((String) o1).compareTo((String) o2);
// 2. 如果按照字符串长度大小排序
//return ((String) o1).length()-((String) o2).length();// 但是认为长度相同的字符串就是相同的元素
// 即认为 tom 和 jac 是相同的元素 ,不建议使用
}
});
// 添加数据
treeSet.add("majunyi");
treeSet.add("风煞");
treeSet.add("zhangyi");
treeSet.add("tom");
treeSet.add("tom");
System.out.println(treeSet);
/* 1. 当使用无参构造器创建 Treeset 对象时,仍然是无序的
2. 希望按照添加元素,按照字符串大小来排序
3. 那就需要使用TreeSet 的一个构造器,可以传入一个比较器(匿名内部类) 并指定排序方式
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return 0;
}
});
构造器将传入的比较对象赋值给 TreeMap 的属性this.comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
在 调用 treeSet.add("")时,底层执行
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就没有加入
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
}
*/
}
}
另外 让我们来看一道TreeSet的相关练习:
定义一个学生类(Student),它包含private成员变量name(姓名),id(学号),age(年龄)。每个属性都要写get,set方法,并重写 toString 方法输出 name, id,age,balance
要求:
创建Student类的 10个对象,并把这些对象放入 TreeSet 集合中(成员变量自定义)
按下面的要求对集合中的元素进行排序,并遍历输出:
-
按年龄从小到大排序
-
当年龄相同时,按学号从大到小排序
package jihe.zuoye;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
@SuppressWarnings({"all"})
public class Tree_Set {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (((Student) o1).getAge() != ((Student) o2).getAge()) {
return ((Student) o1).getAge() - ((Student) o2).getAge();
} else
return ((Student) o2).getId() - ((Student) o1).getId();
}
});
treeSet.add(new Student("tom", 202101, 15));
treeSet.add(new Student("jact", 2021021, 16));
treeSet.add(new Student("zhangfei", 202203, 17));
treeSet.add(new Student("风煞", 2022012, 18));
treeSet.add(new Student("smith", 20221025, 16));
treeSet.add(new Student("bhfdhjs", 2021045, 19));
treeSet.add(new Student("dfdjst", 202109, 14));
treeSet.add(new Student("nfhafrojiti", 2000156, 19));
treeSet.add(new Student("王刚", 2002031, 17));
treeSet.add(new Student("dfreejwgru", 2003051, 18));
Iterator iterator = treeSet.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("Student=" + next);
}
}
}
class Student {
private String name;
private int id;
private int age;
public Student(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
运行效果展示:
接下来就到了我们的双列集合Map,同样让我们来看看Map接口实现类的类图关系吧:
Map存储的是Key-Value键值对,其底层机制为 Node类,在上面HashSet的底层介绍中有讲到,那么我们还是再来对HashMap 的底层解读一边吧:
package jihe.Map_.com;
// HashMap 的底层为 数组+单向链表+红黑树
// 扩容机制和 HashSet 完全相同 底层为一个 tabe 数组 <K,V> 实际是 HashMap$Node 类,而且他还实现了 entry 接口
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
// 模拟 HashMap 扩容树化
@SuppressWarnings({"all"})
public class HashMap_ {
public static void main(String[] args) {
HashMap map = new HashMap();
for (int i=0;i<=12;i++){
map.put(new A(i),"majunyi"); //Value 可以重复
}
//12个Key->Value,因为 虽然hashCode 都为100,但没有重写equals 方法,所以有12个对象
Set set = map.entrySet();
for (Object o :set) {
Map.Entry entry=(Map.Entry) o;
System.out.println(entry);
}
}
}
class A{
private int num;
public A(int num) {
this.num = num;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
// 所有 的A对象 hashCode 都为100;
@Override
public int hashCode() {
return 100;
}
@Override
public String toString() {
return "A{" +
"num=" + num +
'}';
}
}
可能大家看不懂上面的entry遍历HashMap的实现,没关系,接下来我将会为大家详细介绍,以及为大家展示Map接口类的六大遍历方法:
package jihe.Map_.com;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@SuppressWarnings({"all"})
public class HashMapTest {
public static void main(String[] args) {
Map map=new HashMap();
/* 创建 Preson 类,包括 name, slo, id, 要求 添加三个员工对象
键 id
值 员工对象
用两种不同的遍历方法 输出工资大于等于18000的员工
*/
map.put(20221001,new Preson("jact",40002.23,202221001));
map.put(20221012,new Preson("tom",3000.45,202221012));
map.put(20222023,new Preson("majunyi",20005.65,20222023));
//获取 Value
// map.keySet() 方法
System.out.println("map.keySet()方法:");
// 1.增强 for
Set set = map.keySet();//得到的是 Key 通过 map.get(Key)=Value
for (Object o :set) {
Preson preson=(Preson) map.get(o);
if(preson.getSlo()>=18000){
System.out.println(preson);
}
}
System.out.println("\n\n\n");
// 2. 迭代器
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
Preson preson=(Preson) map.get(next);
if(preson.getSlo()>=18000){
System.out.println(preson);
}
}
// map.entrySet() 方法
System.out.println("\n\n\nmap.entrySet() 方法:");
Set set1 = map.entrySet();//得到 Node 键值对
// 1.增强 for
for (Object o :set1) {
Map.Entry entry=(Map.Entry) o;
Preson preson=(Preson) entry.getValue();
if(preson.getSlo()>=18000){
System.out.println(preson);
}
}
System.out.println("\n\n\n");
// 2. 迭代器
Iterator iterator1 = set1.iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
Map.Entry entry=(Map.Entry) next;
Preson preson=(Preson) entry.getValue();
if(preson.getSlo()>=18000){
System.out.println(preson);
}
}
}
}
class Preson{
private String name;
private double slo;
private long id;
public Preson(String name, double slo, long id) {
this.name = name;
this.slo = slo;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSlo() {
return slo;
}
public void setSlo(double slo) {
this.slo = slo;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Override
public String toString() {
return "Preson{" +
"name='" + name + '\'' +
", slo=" + slo +
", id=" + id +
'}';
}
}
另外还有 另一种遍历方式 Collection values = map.values(); 我没有为大家展示,因为他和Set set = map.keySet();//得到的是 Key,而且最重要的是他不能向下转型为Object 类和Set的实现类,因此不能通过 map.get(Key)=Value 来获取Value,因此不常用,我就不给大家展示了。
如果大家有兴趣那就请看下面这个例子,其中展示了Map接口实现类的六大遍历方式:
package jihe.Map_.com;
import java.util.*;
//Map 接口方法和六大遍历方式
// HashMap 键无序 ,Hashtabe 键无序, LinkedHashMap 键存取顺序有序相同
@SuppressWarnings({"all"})
public class MapTest {
public static void main(String[] args) {
Test2();
}
public static void Test0(){
//Map 接口特点
// 1. Map 和 Collection 并列,Map 实现用于保存具有映射关系的数据: Key-Value(双列元素)
Map map = new HashMap();
map.put("no1","风煞");
map.put("no2","张无忌");
map.put("no1","majunyi");//Map 中 Key 不能重复,若出现Key重复 则替换Value
map.put("no3","张无忌");//Map 中Value可以重复
map.put(null,null);//可以有空
System.out.println("map="+map);//无序,HashMap 双向链表
//输出: map={no2=张无忌, no1=majunyi, no3=张无忌}
map.put(75,"tom");
map.put("tom",46);
System.out.println("map="+map);
//Key 和 Value 之间存在单项一对一的关系,即通过Key 总能找到对应的Value
//通过 get 方法传入Key ,会返回对应的 Value
System.out.println(map.get("no1")+"\n\n\n");// 输出:majunyi
/* 1. Key-Value 是放在 HashMap$Node里的 node = newNode(hash, key, value, null)
2. K-V 为了方便程序员的遍历,还会 创建 EntrySet 集合,该集合存放元素的类型 Entry ,而一个Entry
对象就有K,V Entry<Entry<K,V>> 即: transient Set<Map.Entry<K,V>> entrySet;
3. entrySet 中,定义的类型是Map.Entry,但是实际上存放的是 HashMap$Node
这是因为 static class Node<K, V> implements Map.Entry<K,V>
4. 当把 HashMap$Node 对象存放到entrySet 方便我们的遍历,因为 Map.Entry 提供了重要方法
K getKey(); V getValue();
*/
Set set=map.entrySet();
System.out.println(set.getClass());
//为了从HashMap$Node 中取出K-V
// 1. 首先做一个向下转型
for (Object obj : set) {
//System.out.println("obj="+obj);
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey()+"-"+entry.getValue());
}
Set set1 = map.keySet();
System.out.println(set1.getClass());
Collection values = map.values();
System.out.println(values.getClass());
// 由于 Map 没有迭代器 因此需要将Map 进行封装成为数组便于遍历
}
public static void Test1(){
//Map 接口方法
Map map=new HashMap();
// 1. put:添加
map.put("邓超","孙俪");
map.put("王宝强","马蓉");
map.put("宋茜","马蓉");
map.put("刘玲博",null);
map.put(null,"刘亦菲");
map.put("鹿晗","关晓彤");
System.out.println("map="+map);
// 2. remove:根据键(Key)删除映射
map.remove(null);
System.out.println("map="+map);
// 3. get:根据键获取
System.out.println(map.get("鹿晗"));
// 4. size: 获取元素个数
System.out.println(map.size());
// 5. isEmpty:判断是否为空
System.out.println(map.isEmpty());
// 6. clear:清空所有
// 7. containsKey 和 containsValue :查找
System.out.println(map.containsKey("鹿晗"));
System.out.println(map.containsValue("孙俪"));
}
public static void Test2(){
//Map 六大遍历方法
Map map=new HashMap();
map.put("邓超","孙俪");
map.put("王宝强","马蓉");
map.put("王宝强","majnuyi");
map.put("宋茜","马蓉");
map.put("刘玲博",null);
map.put(null,"刘亦菲");
map.put("鹿晗","关晓彤");
System.out.println(" 第一组");
// 第一组. 先取出所有的Key 再取出 每一个Key 所对应的Value
// 1. 增强 for map.keySet方法转化为 Set 类
System.out.println("****第一种方式****");
Set set = map.keySet();//得到的是 Key 通过 map.get(Key)=Value
for (Object o :set) {
System.out.println(o +"-"+map.get(o));
}
// 2. 迭代器 利用上一步转化的 Set 集合
System.out.println("****第二种方式****");
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next+"-"+map.get(next));
}
System.out.println(" 第二组");
// 第二组:
Collection values = map.values();//无法反向取 Key 即 map.get(value) 会报错
// 1.增强 for
System.out.println("****第一种方式****");
for (Object o :values) {
System.out.println(o);
}
// 2. 迭代器
System.out.println("****第二种方式****");
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
System.out.println("next="+next);
}
System.out.println(" 第三组");
// 第三组:通过 EntrySet 来获取 K-V 即 map.entrySet方法转化为Set类
Set set1 = map.entrySet();
// 1. 增强 for
System.out.println("****第一种方式****");
for (Object o :set1) {// 运行多态 都为 HashMap$Node类对象
// System.out.println("o="+o);
Map.Entry entry=(Map.Entry) o;
//System.out.println("entry="+entry);
System.out.println(entry.getKey()+"-"+entry.getValue());
}
// 2. 迭代器
System.out.println("****第二种方式****");
Iterator iterator2 = set1.iterator();
while (iterator2.hasNext()) {
Object next = iterator2.next();
//System.out.println("next-"+next);
Map.Entry entry=(Map.Entry) next;
System.out.println(entry.getKey()+"-"+entry.getValue());
}
}
}
下面我来为大家介绍一下Hashtabe 的扩容规则:
package jihe.Map_.com;
import java.util.Hashtable;
/* Hashtabe 存放 K-V 键值对
hashtabe 的键值 K-V 都不能为 null
其他原则和HashMap 相同
Hashtabe(线程安全)synchronize 使用方法 和HashMap(线程不安全) 一样
*/
@SuppressWarnings({"all"})
public class Hashtabe_ {
public static void main(String[] args) {
Hashtable hashtable = new Hashtable();
// hashtable.put("majunyi",null); 异常
hashtable.put("majunyi",100);
hashtable.put("zhangfei",200);
hashtable.put("hello1",467);
hashtable.put("129",364);
hashtable.put(756,"5689");
hashtable.put("yiweiyi",345);
hashtable.put("maying","liyu");
hashtable.put("shijei","liwieyi");
hashtable.put(1233,7878);
// 简单说一下Hashtabe 的底层
// 1. 底层有数组 Hashtabe$Entry[] 数组,初始化大小为 11
// 2. 临界值 threshold 11*0.75=8;
// 3. 扩容:按照自己的扩容机制来扩容,并不是两倍
// 4. 执行方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry
// 5. 当 if (count >= threshold) 成立时,即当前值大于等于临界值时,就进行扩容
// 5. 按照 int newCapacity = (oldCapacity << 1) + 1; 的值进行扩容
}
}
下面来看看 properties 类及其作用:
package jihe.Map_.com;
/* properties 继承自 Hashtabe 类并实现了Map接口,K-V
使用特点和 Hashtabe 类似
Properties 还可以从xxx.properties文件中,加载到Properties 类对象并进行读取和修改
说明: 工作后 xxx.properties 文件通常作为配置文件,这个知识点在IO 流例举
*/
import java.util.Properties;
// Properties 方法
@SuppressWarnings({"all"})
public class MapProperties_ {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("john",100);
properties.put("majunyi",200);
properties.put("zhamgyi","majunyi");
// 1. put:添加 键值对(K-V)不能为空
// properties.put("majunyi",null); 空指针异常
System.out.println(properties); //输出 {majunyi=200, zhamgyi=majunyi, john=100}
// 通过 Key 获取对应值 properties.get();
System.out.println(properties.get("majunyi"));// 输出 200
// 删除
properties.remove("john");
System.out.println(properties); //输出 {majunyi=200, zhamgyi=majunyi}
// 修改
properties.setProperty("majunyi","zhangyi");
System.out.println(properties); // 输出 {majunyi=zhangyi, zhamgyi=majunyi}
}
}
最后让我们来看看 TreeMap类,在上面介绍 TreeSet的时候也有说到,那我们来进行进一步了解吧:
package jihe.Map_.com;
import java.util.Comparator;
import java.util.TreeMap;
// K-V 键值对 和TreSet 相同可以排序使元素存取有序 针对的都是(标准) Key
@SuppressWarnings({"all"})
public class TreeMap_ {
public static void main(String[] args) {
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 按照字符串大小
// 下面 调用String 的comparatTo 比较字符串大小 Key (与C语言中字符串比较strcmp()函数效果相同)即按照字典排序 从大到小
// 1. 若要从大到小 则交换下面的o1和o2
return ((String) o1).compareTo((String) o2);
// 2. 如果按照字符串长度大小排序 从小到大
// return ((String) o1).length()-((String) o2).length();// 但是认为长度相同的字符串就是相同的元素
// 即认为 (Key)tom 和(Key)jac 是相同的元素,不会发生Value替换,直接添加失败 ,不建议使用
}
});
treeMap.put("jact","杰克");
treeMap.put("tom","汤姆");
treeMap.put("smith","史密斯");
treeMap.put("majunyi","风煞");
// System.out.println(treeMap); // 输出:{jact=杰克, majunyi=风煞, smith=史密斯, tom=汤姆}
// 可以发现 现在是无序的
System.out.println(treeMap);
/*
1. 当使用无参构造器创建 Treeset 对象时,仍然是无序的
2. 希望按照添加元素,按照字符串大小来排序
3. 那就需要使用TreeMap 的一个构造器,可以传入一个比较器(匿名内部类) 并指定排序方式
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return 0;
}
});
构造器将传入的比较对象赋值给 TreeMap 的属性this.comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
*/
/*
2.1 put: 第一次添加,把K-V 封装到 Entry 对象,放入 root
Entry<K,V> t = root;
if (t == null) {
addEntryToEmptyMap(key, value);
return null;
}
2.2 以后添加
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {// 遍历所有的 Key,给Key找合适的位置
parent = t;
cmp = cpr.compare(key, t.key);// 动态绑定到我们的匿名内部类的compare
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else {// 如果遍历过程中,发现准备添加的Key 和当前已有的Key 相等,就不添加
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
}
*/
}
}
他的排序规则和TreeSet 一样只不过他的排序标准是参照Key 来实现的。
接下来就到了我们用来操作 List、Set、Map接口实现类的集合的 Collections 类啦,下面就让我们来看看他所具有的主要操作方法吧:
package jihe.Collenctions_;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/*
Collenctions 是一个操作 Set, List, Map 等集合的工具类
Collenctions 中提供了一系列静态对集合 进行排序,查询,和修改等操作
*/
@SuppressWarnings({"all"})
public class CollenctionsTest {
public static void main(String[] args) {
// 介绍 Collenction 的常用方法
// 创建一个 ArrayList 集合,作为测试例子
List list = new ArrayList();
list.add("tom");
list.add("majunyi");
list.add("smith");
list.add("king");
list.add("风煞");
System.out.println("list="+list);//输出 list=[tom, majunyi, smith, king, 风煞]
// Collection 中的所有方法都为静态方法,因此不需要创建对象,可直接用类名引用
// 1. reverse(List): 反转 List 中的元素顺序
Collections.reverse(list);
System.out.println("list="+list);//输出 list=[风煞, king, smith, majunyi, tom]
// 2. shuffle(List): 对List 集合元素进行随机排序
Collections.shuffle(list);
System.out.println("list="+list);// list=[tom, majunyi, smith, king, 风煞]
// 3. sort(List): 对List按照自然顺序排列
Collections.sort(list);
System.out.println("自然排序后 list="+list);// list=[king, majunyi, smith, tom, 风煞]
// 我们希望自定义排序规则:
// sort(List,Comparator):指定 Comparator 产生的顺序对list进行排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 下面 调用String 的comparatTo 比较字符串大小(与C语言中字符串比较strcmp()函数效果相同)即按照字典排序 从小到大
// 1. 若要从大到小 则交换下面的o1和o2
// return ((String) o1).compareTo((String) o2);
// 2. 如果按照字符串长度大小排序
return ((String) o1).length()-((String) o2).length();// 但是认为长度相同的字符串就是相同的元素
// 即认为 tom 和 jac 是相同的元素 ,不建议使用
}
});
System.out.println("按照字符串长度大小排序:list="+list); //list=[风煞, tom, king, smith, majunyi]
// 4. swap(List, int, int): 将指定 list 集合的 i 处元素和 j 处元素 进行交换
Collections.swap(list,0,1);
System.out.println("交换后list="+list);//交换后list=[tom, 风煞, king, smith, majunyi]
// 不能越界使用
// 5. Object max(Collection): 根据元素的自然排序,返回给定集合中的最大元素
System.out.println(Collections.max(list));//风煞
// 6. Object max(Collection,Comparator): 根据 Comparator 指定的顺序,返回集合中的最大元素
System.out.println(Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 下面 调用String 的comparatTo 比较字符串大小(与C语言中字符串比较strcmp()函数效果相同)即按照字典排序 从小到大
// 若要从大到小 则交换下面的o1和o2
return ((String) o1).compareTo((String) o2);
}
}));//风煞
// 7. Object max(Collection): 根据元素的自然排序,返回给定集合中的最小元素
// 8. Object max(Collection): 根据元素的自然排序,返回给定集合中的最小元素
// 9. int frequency(Collection,Object): 返回指定集合中指定元素的出现次数
System.out.println("majunyi 出现的次数"+Collections.frequency(list,"majunyi"));// 1
// 10. void copy(list dest, List src): 将src 中的内容复制到dest中 dest大小不能小与 src
// 所以必须先对 dest 赋值
ArrayList dest =new ArrayList();
for (int i=0 ;i<list.size(); i++){
dest.add("java");
}
//拷贝
Collections.copy(dest,list);
System.out.println("dest="+dest); //dest=[tom, 风煞, king, smith, majunyi]
// 11. boolean replaceAll(List list,Object oldVal, Object newVal): 使用新值替换 List 中的所有旧值
// 如果list 中有majnuyi 则替换为 tom
Collections.replaceAll(list,"majunyi","tom");
System.out.println("list="+list); //list=[tom, 风煞, king, smith, tom]
}
}
完啦!谢谢看完的朋友,那就送大家一个彩蛋吧
小彩蛋 🥚
public class HashSetSoure {
public static void main(String[] args) {
System.out.println(" ::## ##:: \n" +
"::::::## ##::::::\n" +
"::::::## ## : ## ##::::::\n" +
"::::::::## ##::##::## ##::::::::\n" +
"::::::::## ####::##::#### ##::::::::\n" +
":::::::::: ####::::::::::::::#### ::::::::::\n" +
":::::::::: ##::::::::::::::::::::::## ::::::::::\n" +
"::::::::::##@@::::::::::::::::::::::@@##::::::::::\n" +
"::::::::##@@##@@::::::::::::::::::@@##@@##::::::::\n" +
"::::::##@@######@@::::::::::::::@@######@@##::::::\n" +
" ##::##@@#### ##@@::::::::::@@## ####@@##::## \n" +
" ::##@@########@@::::::::::@@########@@##:: \n" +
" ####@@## ####@@::######::@@#### ##@@#### \n" +
" ##::@@####@@::##########::@@####@@::## \n" +
" ##::::@@@@::::::######::::::@@@@::::## \n" +
" :::::::::::::::::::::::::::::::::: \n" +
" ::@@@@@@@@@@@@@@@@@@@@@@@@@@:: \n" +
" ##::@@@@@@::## \n" +
" ##::::##@@##::::## \n" +
" ######::##@@##::###### \n" +
" ##::::::####@@####::::::## \n" +
" ######::##@@@@@@##::###### \n" +
" ######::##@@@@@@##::###### \n" +
" ##::::::##########::::::## \n" +
" ###### ###### ");
}