准备知识点:
- java中涉及到集合的接口与实现类都位于java.util包下
-
集合中接口继承体系如下图所示:
java集合接口体系.png
-
集合中各个接口所对应的主要实现类如下图所示:
集合中接口所对应的主要实现类.png
笔试题目一:
代码如下,问控制台为什么只打印出abc、xyz?
class People {
String name;
public People(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public class HashSetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
set.add(new String("abc"));
set.add(new String("xyz"));
set.add(new String("abc"));
for (Iterator<String> ite = set.iterator(); ite.hasNext();) {
String next = ite.next();
System.out.println(next);
}
}
}
答案:因为HashSet集合的特性是元素唯一性,而HashSet底层是通过hashcode以及equals方法来判定对象是否唯一的,而String类覆写了equals方法以及hashcode方法这让new String("abc")与new String("abc")的hashcode以及equals方法的比较都为true从而被判定是重复元素无法添加到集合中。
准备知识点:
- HashSet底层是通过HashMap来实现的
- HashMap底层是通过Entry数组存储Entry链表来实现的
HashSet增加元素时java底层的操作流程如下:
- 调用HashMap.put方法来存储key、value
- 使用hash算法对key.hashCode()计算出一个hash值
- 让得到的hash值与数组长度 - 1进行&运算得到Entry数组索引位置(该位置用来存储Entry对象,该Entry对象封装了所要添加的key-value对)
- 得到Entry数组索引后,遍历该索引位置所指向的Entry链表中的Entry元素,如果计算得到的hash值与链表中对象的hash值相等且(key对象是同一个对象或者key对象的内容相等)那么当前所添加的value值覆盖链表中匹配到的元素的value值并返回老的value值,至此HashSet.add方法结束,如果遍历了Entry链表后发现没有一个元素符合条件那么就会将所添加的key对象与value对象封装成一个新的Entry对象并将所计算出来的Entry数组索引指向这个新的Entry对象,而这个新的Entry对象的next属性指向旧的链表,换言之将新生成的Entry对象插入到Entry数组所计算出的索引处所指向的链表头部。
总结:当向集合HashSet中增加对象时,首先集合计算要增加对象的hashCode码,根据该值来得到一个Entry数组索引位置用来存放当前的对象,当在该位置没有一个对象的话,那么HashSet认为该对象在集合中不存在,直接增加进去。如果在该位置有一个Entry对象存在的话,接着将准备增加到集合中的对象与该位置上的Entry链表中的各个Entry对象进行equals方法和hashcode值比较,如果迭代完毕后发现没有一个Entry对象的equals方法以及hashcode值与新增对象相符(注:即比较结果为true),那么集合认为集合中不存在该对象,直接将该对象封装成Entry对象并将其做为数组索引处所指向的Entry链表的头部元素,如果在迭代过程中发现有元素与新增的元素的equals比较以及hashcode值得比较都为true,那么集合认为集合中已经存在该对象了,不会再将该对象增加到集合中去了(注:不过对于HashMap它会将对象的value值替换匹配到的对象的value值并将旧的value值返回)
笔试题目二:
代码如下,控制台会输出什么,并进行相应的解释:
class People {
String name;
public People(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public class HashSetTest {
public static void main(String[] args) {
Set<People> set = new HashSet<People>();
set.add(new People("zhangsan"));
set.add(new People("lisi"));
set.add(new People("zhangsan"));
for (Iterator<People> ite = set.iterator(); ite.hasNext();) {
People next = ite.next();
System.out.println(next.getName());
}
}
}
答案:控制台会输出zhangsan、lisi、zhangsan。因为People类没有覆写equals以及hashcode方法,从而导致HashSet底层判定它们为不同元素,最终被存储到了集合中。
笔试题目三:
代码如下,控制台会输出什么?为什么?
class People {
String name;
public People(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (null == obj) return false;
if (obj instanceof People) {
People p = (People)obj;
return p.name.equals(this.name);
}
return false;
}
@Override
public int hashCode() {
return this.name.hashCode();
}
}
public class HashSetTest {
public static void main(String[] args) {
Set<People> set = new HashSet<People>();
set.add(new People("zhangsan"));
set.add(new People("lisi"));
set.add(new People("zhangsan"));
for (Iterator<People> ite = set.iterator(); ite.hasNext();) {
People next = ite.next();
System.out.println(next.getName());
}
}
}
答案:控制台会输出lisi、zhangsan。因为People类重写了equals方法以及hashcode方法,从而在HashSet底层new People("zhangsan")与new People("zhangsan")被判定为重复元素,故无法添加到集合中。
知识点罗列:
- 当重写equals方法的时候必须重写hashcode方法 (HashMap/HashSet都需要用到这个特性,否则这些对象要存储于HashMap/HashSet集合中的时候会出现重复元素存储的问题,即无法维持元素的唯一性。)
- 如果一个类的两个对象进行equals方法比较的时候返回true,那么这两个对象必须具有相同的hashcode。(否则存储到HashMap/HashSet里的时候还是无法维持元素的唯一性。注:判定元素重复的条件是:hashcode以及equals方法都返回true)
- transient修饰的变量无法被序列化到文件中,信息会丢失
- HashMap对于key为null的存储规则是将它存储在HashMap所维护的Entry数组的第一个元素的链表中(即:key为null的key-value永远存储在数组的第一个元素的链表中)
- java约定用关键字final修饰的常量名要大写
- 对于HashSet、HashMap来说,这样做就是为了提高查找效率,使得查找时间不随着set或者map的大小而改变
- HashMap所维护的Entry数组的默认长度是16
- HashMap负载因子默认为0.75f(根据经验得到,数组现存储元素个数/数组长度),主要是降低冲突的可能性,当超过该负载因子的时候会扩容数组,不让数组中的Entry链表长度过长而违背HashMap设计的初衷
网友评论