散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
从上图中可以看出,哈希表实则是数组+链表的形式组成,数组指的上图中的01233456…,链表则指的是
HashSet扩展自AbstractSet并且实现Set接口。
import java.util.HashSet;
class Test{
public static void main(String []args) {
HashSet<Integer> hs = new HashSet<>();
hs.add(5);
hs.add(2);
hs.add(3);
System.out.println(hs.add(4));
System.out.println(hs.add(4)); //检验HashSet是否允许重复元素加入
System.out.println(hs); //检验HashSet是否有序
}
}
Ouput:
true
false
[2, 3, 4, 5]
说明HashSet不允许重复,无序。
public class Student{
private String name;
private int id;
public Student(){}
public Student(String name, int id)
{
super();
this.name = name;
this.id = id;
}
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;
}
@Override
public String toString()
{
return "Student [name=" + name + ", id=" + id + "]";
}
}
import java.util.HashSet;
class Test{
public static void main(String []args) {
HashSet<Student> hs = new HashSet<>();
hs.add(new Student("yiyi",1));
hs.add(new Student("feifei",2));
hs.add(new Student("lili",3));
System.out.println(hs.add(new Student("wawa",4)));
System.out.println(hs.add(new Student("wawa",4)));
System.out.println(hs);
}
}
Output:
true
true
[Student [name=yiyi, id=1], Student [name=wawa, id=4], Student [name=wawa, id=4], Student [name=lili, id=3], Student [name=feifei, id=2]]
问题:为什么是true,true。放入了两个一样的对象,为什么依然显示true,并且出现了出现了两次 Student [name=wawa, id=4], Student [name=wawa, id=4]。与上述HashSet不允许重复的特点相异。
为了解决这一问题,我们从底层原理说起
以int数据类型为例,假设我们要在hashSet中放入12,5,7,12,9,首先通过hashcode()方法计算它们的哈希值,根据哈希取模运算后(y=x%5),依次对应为2,0,2,2,4。 然后依次放入哈希表中,在放入时,有重复的数值比如2,则需要另外的方法equals()来判断是否相等,如果相等(重复)就不再放入,否则放入。
在存入int,string等类型时,Java帮我们写了hashcode()和equals()方法,所以才能显现出hashset的特点,无序单一性,我们存入自己定义的数据类型时,系统并没有相对于我们自定义数据类型的hashcode()和equals()方法。
因此,我们要想往hashSet里存入数据,就只能重写hashcode()和equals()方法!!
右键–>Source–>Generate hashCode() and equals()即可
public class Student{
private String name;
private int id;
public Student(){}
public Student(String name, int id)
{
super();
this.name = name;
this.id = id;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (id != other.id)
return false;
if (name == null)
{
if (other.name != null)
return false;
}
else if (!name.equals(other.name))
return false;
return true;
}
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;
}
@Override
public String toString()
{
return "Student [name=" + name + ", id=" + id + "]";
}
}
import java.util.HashSet;
import java.util.Iterator;
class Test{
public static void main(String []args) {
HashSet<Student> hs = new HashSet<>();
hs.add(new Student("yiyi",1));
hs.add(new Student("feifei",2));
hs.add(new Student("lili",3));
System.out.println(hs.add(new Student("wawa",4)));
System.out.println(hs.add(new Student("wawa",4)));
System.out.println(hs); //第一种打印方式,利用重写的toString()方法和Println()直接打印
System.out.println("------------");
for(Student s : hs) { //第二种打印方式,增强for循环
System.out.println(s);
}
System.out.println("------------"); //第三种打印方式,利用Iteator
Iterator<Student> it = hs.iterator();
while(it.hasNext()) {
Student s = it.next();
System.out.println(s);
}
}
}
Output:
true
false
[Student [name=feifei, id=2], Student [name=wawa, id=4], Student [name=yiyi, id=1], Student [name=lili, id=3]]
------------
Student [name=feifei, id=2]
Student [name=wawa, id=4]
Student [name=yiyi, id=1]
Student [name=lili, id=3]
------------
Student [name=feifei, id=2]
Student [name=wawa, id=4]
Student [name=yiyi, id=1]
Student [name=lili, id=3]
LinkedHashSet继承自HashSet
特点是:有序,唯一,效率高
TreeSet为使用树来进行储存的Set接口提供了一个工具,对象按升序储存,访问和检索是很快的。在存储了大量的需要进行快速检索的排序信息的情况下,TreeSet是一个不错的选择。
import java.util.Set;
import java.util.TreeSet;
class Test{
public static void main(String []args) {
Set<Integer> st = new TreeSet<>();
st.add(1);
st.add(4);
st.add(5);
st.add(2);
System.out.println(st.add(2)); //验证是否可以添加重复元素
System.out.println(st);
}
}
Output:
false
[1, 2, 4, 5]
import java.util.Set;
import java.util.TreeSet;
class Test{
public static void main(String []args) {
Set<String> st = new TreeSet<>();
st.add("java");
st.add("c");
st.add("pyhthon");
st.add("c++");
System.out.println(st.add("c++"));
System.out.println(st);
}
}
Output:
false
[c, c++, java, pyhthon]
说到按升序排列,肯定会想到内部比较器和外部比较器(如果不了解Java比较器可参考主页文章(https://blog.csdn.net/weixin_44551646/article/details/94741936)),事实上,Java在String和Integer类里重写了comparaTo方法,因此TreeSet可以对其进行升序排列。
那我们对自定义的TreeSet进行排序时,就需要自己重写比较方法。如果说没有重写任何比较器(内部或者外部)时,使用TreeSet进行操作会报错。
public class Student implements Comparable{
String name;
int age;
public Student(){}
public Student(String name, int age)
{
super();
this.name = name;
this.age = age;
}
@Override
public int compareTo(Object o)
{
Student stu =((Student)o);
return this.age-stu.age; //按照年龄升序排序
}
@Override
public String toString()
{
return "Student [name=" + name + ", age=" + age + "]";
}
}
import java.util.Set;
import java.util.TreeSet;
class Test{
public static void main(String []args) {
Set<Student> st = new TreeSet<>();
st.add(new Student("lili",18));
st.add(new Student("nana",19));
st.add(new Student("huahua",20));
st.add(new Student("baba",10));
System.out.println(st);
}
}
Output:
[Student [name=baba, age=10], Student [name=lili, age=18], Student [name=nana, age=19], Student [name=huahua, age=20]]
我们看的Output中的对象年龄按照从小到大的方式排列。
import java.util.Comparator;
public class Student {
String name;
int age;
public Student(){}
public Student(String name, int age)
{
super();
this.name = name;
this.age = age;
}
@Override
public String toString()
{
return "Student [name=" + name + ", age=" + age + "]";
}
}
class OutsideCompare implements Comparator{
@Override
public int compare(Object o1, Object o2)
{
Student st1 = ((Student) o1);
Student st2 = ((Student) o2);
return st1.age-st2.age;
}
}
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
class Test{
public static void main(String []args) {
OutsideCompare com = new OutsideCompare();
Set<Student> st = new TreeSet<>(com);
st.add(new Student("lili",18));
st.add(new Student("nana",19));
st.add(new Student("huahua",20));
st.add(new Student("baba",10));
System.out.println(st);
}
}
注意⚠️:
OutsideCompare com = new OutsideCompare();
也可以用多态的形式Comparator com =new OutsideCompare();
如果这里有疑问,强烈推荐去看主页文章,关于内部比较器和外部比较器(https://blog.csdn.net/weixin_44551646/article/details/94741936)Set st = new TreeSet<>(com);
即括号里的com。