Map和Set常用的API
Map 常用API
put(K key, V value)
: 将指定的键值对插入到映射中。get(Object key)
: 返回与指定键关联的值,如果键不存在则返回null
。remove(Object key)
: 移除指定键及其对应的值。containsKey(Object key)
: 判断是否包含指定的键。containsValue(Object value)
: 判断是否包含指定的值。keySet()
: 返回映射中所有键的Set
视图。values()
: 返回映射中所有值的Collection
视图。entrySet()
: 返回映射中所有键值对的Set
视图。size()
: 返回映射中键值对的数量。clear()
: 清空映射中的所有键值对。isEmpty()
: 判断映射是否为空。
Set 常用API
add(E e)
: 向集合中添加指定元素,如果元素已存在则不会重复添加。remove(Object o)
: 移除集合中的指定元素。contains(Object o)
: 判断集合中是否包含指定元素。size()
: 返回集合中元素的数量。isEmpty()
: 判断集合是否为空。clear()
: 清空集合中的所有元素。iterator()
: 返回集合中元素的迭代器。addAll(Collection<? extends E> c)
: 将指定集合中的所有元素添加到此集合
练习
import java.util.*;
@SuppressWarnings({"all"})
public class Journey {
public static void main(String[] args) {
News news1 = new News("新冠确诊病例超千万,数百万印度信徒前往恒河\"圣浴\"引发民众担忧");
News news2 = new News("男子突然想起2个月前钓的鱼还在网兜中,捞起一看赶紧放生");
ArrayList list = new ArrayList();
list.add(news1);
list.add(news2);
//直接reverse可以的,但别忘了这样会直接改变list的内部结构
//有些情况下需要注意这一点,要求不改变list的内部结构时,可以for循环遍历
//当然,你也可以下面这样反转然后让一个对象来接收
// Collections.reverse(list);
int size = list.size();
for (int i = size-1; i >= 0; i--) {
News news = (News)list.get(i);
System.out.println(processTitle(news.getTitle()));
}
}
//专门写了个方法,处理现实新闻标题 process处理
public static String processTitle(String title){
if(title==null){
return "";
}
if(title.length()>15){
//别忘了是左闭右开哈
return title.substring(0,15)+"...";//[0,15)
}else{
return title;
}
}
}
class News{
private String title;
private String desc;
public News(String title) {
this.title = title;
}
@Override
public String toString() {
//只打印标题
return title ;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
//输出如下:
男子突然想起2个月前钓的鱼还在...
新冠确诊病例超千万,数百万印度...
public static void main(String[] args) {
ArrayList list = new ArrayList();
//添加
Car BMW = new Car("宝马", 400000);
Car Binli = new Car("宾利", 5000000);
list.add(BMW);
list.add(Binli);
System.out.println(list);
//[Car{name='宝马', price=400000.0}, Car{name='宾利', price=5000000.0}]
boolean b = list.contains(Binli);
System.out.println(b);//true
//获取元素个数
int size = list.size();
System.out.println(size);//2
//判断是否为空
System.out.println(list.isEmpty());//false
//clear 清空
// list.clear();
System.out.println(list.isEmpty());//true
ArrayList testlist = new ArrayList();
testlist.add(true);
testlist.add(1110);
list.addAll(0,testlist);
System.out.println(list);
//[true, 1110, Car{name='宝马', price=400000.0}, Car{name='宾利', price=5000000.0}]
//删除多个元素
list.removeAll(testlist);
System.out.println(list);
//[Car{name='宝马', price=400000.0}, Car{name='宾利', price=5000000.0}]
//迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
/**
* 重写toString之后输出结果:
* 品牌名:宝马 price:400000.0
* 品牌名:宾利 price:5000000.0
*/
//增强for
System.out.println("=====================增强for=================");
for (Object o :list) {
System.out.println(o);
}
/**
* 品牌名:宝马 price:400000.0
* 品牌名:宾利 price:5000000.0
*/
}
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put(new Employee("jack",650).getName(),new Employee("jack",650).getSal());
hashMap.put(new Employee("tom",1200).getName(),new Employee("tom",1200).getSal());
hashMap.put(new Employee("smith",2900).getName(),new Employee("smith",2900).getSal());
System.out.println(hashMap);
//{tom=1200.0, smith=2900.0, jack=650.0}
//改jack的工资
hashMap.put("jack",2600.0);
System.out.println(hashMap);
//{tom=1200.0, smith=2900.0, jack=2600}
//给员工加薪资100---keySet实现
Set keySet = hashMap.keySet();
for (Object key :keySet) {
//注意,下面这行,涨薪100就是更新value嘛,但是hashMap.get(key)
//返回的是Object,所以要向下转型
hashMap.put(key,(Double)hashMap.get(key)+100.0);
}
System.out.println(hashMap);
//{tom=1300.0, smith=3000.0, jack=2700.0}
//给员工再涨200---entrySet实现
Set set = hashMap.entrySet();
for (Object o :set) {
Map.Entry entry = (Map.Entry)o;
hashMap.put(entry.getKey(),(Double)entry.getValue()+200.0);
}
System.out.println(hashMap);
//{tom=1500.0, smith=3200.0, jack=2900.0}
//遍历集合中的所有员工
Set emps = hashMap.keySet();
System.out.println(emps);
//[tom, smith, jack]
//迭代器实现遍历
Set set1 = hashMap.entrySet();
Iterator iterator = set1.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
Map.Entry entry = (Map.Entry)obj;
System.out.println("员工姓名-"+entry.getKey()+"\t工资:"+entry.getValue());
}
//员工姓名-tom 工资:1500.0
//员工姓名-smith 工资:3200.0
//员工姓名-jack 工资:2900.0
//遍历集合中的所有工资
Collection values = hashMap.values();
System.out.println(values);
//[1500.0, 3200.0, 2900.0]
}
HashSet的去重机制: HashCode() 和 equals() 相配合,底层先通过存入一个对象,进行运算得到一个hash值,然后通过hash值得到索引,如果发现table表的索引所在的位置,没有数据,就直接存放进去,如果有数据,就进行equals比较(遍历比较),这里的equals的具体比较是可以由程序员来重写的,我们可以按照我们想要的规则来判定是否相等;如果比较后,不相同,那么可以加入,反之,则无法加入
TreeSet的去重机制:如果你传入了一个Comparator匿名对象,就使用实现的compare方法来实现去重,如果方法返回0,就认为是相同的数据/元素,就不添加了,如果没有传入一个Comparator匿名对象,则以你添加的对象实现的Compareable接口的compareTo方法去重
去重机制的核心——比较规则
TreeSet 的去重机制依赖于元素的比较规则,而比较规则有两种方式:
1.通过 Comparable 接口:如果元素类实现了 Comparable 接口,TreeSet 会调用元素的 compareTo() 方法来进行比较。例如:
class Person implements Comparable<Person> {
private String name;
private int age;
public int compareTo(Person other) {
// 按名字比较
return this.name.compareTo(other.name);
}
}
在这个例子中,当两个 Person 对象的 name 相同时,TreeSet 会认为它们是重复的,不会插入第二个元素。
2.通过 Comparator 接口:如果 TreeSet 构造时提供了一个 Comparator,则 TreeSet 会使用该 Comparator 的 compare() 方法来比较元素,而不是依赖于元素自身的 compareTo() 方法。例如:
TreeSet<Person> treeSet = new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
// 按年龄比较
return Integer.compare(p1.getAge(), p2.getAge());
}
});
通过这种方式,TreeSet 会根据年龄来去重,两个年龄相同的 Person 对象会被认为是相等的,不允许重复插入。
TreeSet treeSet = new TreeSet();
treeSet.add(new Person());
class Person{}
//代码分析题
因为 TreeSet() 的构造器中没有传入 Comparator,所以它默认认为插入的元素需要实现 Comparable 接口。
当你调用 treeSet.add(new Person()) 时,TreeSet 试图将 Person 对象转型为 Comparable。
但由于 Person 没有实现 Comparable 接口,无法完成强制类型转换,导致抛出了 ClassCastException。
为什么会抛出异常?
TreeSet 的工作原理是基于二叉树(通常是红黑树)来存储元素,因此它要求放入 TreeSet 的对象必须是 可比较的,
也就是说,放入的对象需要实现 Comparable 接口或者在构造 TreeSet 时提供一个 Comparator。
然而,在上面的代码中,Person 类并没有实现 Comparable 接口,也没有提供 Comparator,
所以当你尝试向 TreeSet 中添加一个 Person 对象时,TreeSet 无法确定如何比较这些 Person 对象。
这时,在执行 add() 方法时,程序会抛出 ClassCastException 异常。
TreeSet 底层在插入元素时,会调用以下方法:
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
其中 m 是 TreeMap,TreeMap 通过 compareTo() 或 compare() 方法来比较元素。而在比较的时候,
如果 Person 没有实现 Comparable 接口,那么 TreeSet 将无法进行元素的排序或比较,导致异常。
解决方案
要解决这个问题,必须确保 Person 类可以进行比较,通常有两种方法:
1.实现 Comparable 接口:
class Person implements Comparable<Person> {
@Override
public int compareTo(Person o) {
// 定义比较规则,例如按某个字段比较
return 0;
}
}
2.提供一个 Comparator:
TreeSet<Person> treeSet = new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
// 定义比较规则
return 0;
}
});
@SuppressWarnings({"all"})
public class Journey {
public static void main(String[] args) {
HashSet set = new HashSet();
person p1 = new person(1001, "AA");
person p2 = new person(1002, "BB");
set.add(p1);
set.add(p2);
System.out.println("只添加了p1、p2后的原始的set");
System.out.println(set);
p1.name = "CC";
set.remove(p1);
System.out.println("更改name,remove p1后,set为:");
System.out.println(set);
set.add(new person(1001,"CC"));
System.out.println("第二次,尝试新添加了1001 CC 后的set 为:");
System.out.println(set);
set.add(new person(1001,"AA"));
System.out.println("第三次,尝试新添加1001 AA后,此时的set为:");
System.out.println(set);
}
}
@SuppressWarnings({"all"})
class person{
public int id;
public String name;
public person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
person person = (person) o;
return id == person.id && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public String toString() {
return "person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
//输出如下:
只添加了p1、p2后的原始的set
[person{id=1002, name='BB'}, person{id=1001, name='AA'}]
更改name,remove p1后,set为:
[person{id=1002, name='BB'}, person{id=1001, name='CC'}]
第二次,尝试新添加了1001 CC 后的set 为:
[person{id=1002, name='BB'}, person{id=1001, name='CC'}, person{id=1001, name='CC'}]
第三次,尝试新添加1001 AA后,此时的set为:
[person{id=1002, name='BB'}, person{id=1001, name='CC'}, person{id=1001, name='CC'}, person{id=1001, name='AA'}]
//第一次修改后,输出的是两个Person对象
1. 更改 p1.name 后调用 set.remove(p1):
代码中,p1.name 被修改为 "CC" 后,set.remove(p1) 被调用。为了理解为什么这个操作没有成功移除元素,
我们需要回顾一下 HashSet 的工作原理:
HashSet 是基于 哈希表 的数据结构,它依赖于元素的 hashCode() 和 equals() 方法来确定是否存在某个元素。
当你调用 set.remove(p1) 时,HashSet 会通过 p1 的 hashCode() 找到相应的桶位置。然而,
此时 p1 的 name 属性已经从 "AA" 改成了 "CC",因此 p1 的 hashCode() 已经发生了改变,与最初插入的 p1 不同了。
因为 HashSet 依赖 hashCode() 来确定元素位置,而修改 name 后的 p1 的 hashCode() 不再匹配原始存储位置,
remove(p1) 找不到正确的位置,导致无法成功删除 p1。
因此,在第一次 remove(p1) 后,p1 并没有被删除,set 中的元素是:
[person{id=1002, name='BB'}, person{id=1001, name='CC'}]
//第二次修改新增后,输出的是三个person对象
2.添加 new person(1001, "CC")
HashSet 的工作原理
HashSet 基于 哈希表 实现,它通过两个方法来维护元素的唯一性:
hashCode():用于计算对象的哈希值(确定对象存储的桶位置)。
equals():用于判断两个对象是否相等。
在 HashSet 中,添加一个元素时,流程如下:
1.根据元素的 hashCode() 确定存储的桶位置。
2.在该位置上,用 equals() 检查是否已经存在相同的元素。
2.1如果 equals() 返回 true,认为是重复元素,不会添加。
2.2如果 equals() 返回 false,认为是不同元素,允许添加。
当你修改 p1.name = "CC" 后,p1 的 hashCode() 变了。因为 hashCode() 是根据 id 和 name 来计算的,
现在 p1 的 name 变成了 "CC",所以新的哈希值不同于最初插入时的值。
然而,HashSet 并不会去重新计算存储在它内部对象的哈希值,也就是说,虽然对象的属性变了,
但是它在 HashSet 中的存储位置仍然是基于原来的哈希值。
第一次 remove(p1) 失败:
当你尝试执行 set.remove(p1) 时,HashSet 会根据 p1 当前的 hashCode() 去查找它的存储位置。
但是,p1 的 hashCode() 已经变了,HashSet 会去错误的桶位置查找,而不是最初插入的桶位置。
由于在这个错误的桶位置上没有找到 p1,remove() 操作失败,p1 没有被删除。
修改了 p1.name 之后,p1 的哈希值发生了变化,但 HashSet 没有重新计算和更新该对象在集合中的位置。
因此,后续插入 new person(1001, "CC") 时,HashSet 认为它是一个新元素,导致集合中出现了两个 "CC"。
//第三次修改后,输出的是四个person对象
添加了一个新的 person(1001, "AA"),这个对象与 set 中的任何现有元素都不相等,
新的 person 对象,id 为 1001,name 为 "AA"。
这个新对象的 hashCode() 是基于 id = 1001 和 name = "AA" 计算出来的
当 HashSet 插入一个元素时,首先根据元素的 hashCode() 决定放入哪个哈希桶(存储位置)。
然后,它会在该位置上通过调用 equals() 方法来比较新元素和已有的元素,看看是否有重复的元素。
因为它的 name 为 "AA" 而不是 "CC",并且 equals() 和 hashCode() 都不同于现有的元素。
于是,这个对象被成功添加到 set 中;
说下ArrayList和Vector的各自的不同之处
底层结构 | 线程安全?效率? | 扩容机制 | |
---|---|---|---|
ArrayList | 可变数组 | 不安全,效率高 | 如果使用有参构造器,按照1.5倍扩容, 如果是无参构造器,第一次扩容到10, 第二次之后开始按照1.5倍扩容 |
Vector | 可变数组Object[ ] | 安全,效率不高 | 如果是无参,默认是10,满了之后,按照2倍扩容, 如果是指定大小创建Vector,则每次按照2倍扩容 |