Set接口
概述
一个不包含重复元素的 collection。更确切地讲,set 不包含满足
e1.equals(e2)
的元素对
e1
和e2
,并且最多包含一个 null 元素
特点
Set
接口是无序的Set
是继承于Collection
的接口。它是一个不允许有重复元素的集合。Set
可以存储null值,但是null不能重复- Set的实现类都是基于
Map
来实现的(HashSet
是通过HashMap
实现的,TreeSet
是通过TreeMap
实现的)。
代码示例
Set<String> set = new HashSet<>();
set.add("ab");
set.add("ac");
set.add("ba");
set.add("bc");
System.out.println(set);
运行结果: [ab, bc, ac, ba]
HashSet
概述
此类实现
Set
接口,由哈希表(实际上是一个HashMap
实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null
元素。
特点
-
底层数据结构是 哈希表,HashSet的本质是一个"没有重复元素"的集合,它是通过
HashMap
实现的。HashSet中含有一个"HashMap类型的成员变量"map,在HashSet中操作函数,实际上都是通过map实现的。 -
哈希表保证唯一 依赖hashcode和equals方法
【
原理:
首先判断hashCode是否相同
不相同
就存储到集合中
相同
比较equals方法是否相同
相同 就不存储
不相同就以链表的方式存储到集合中】
-
哈希表导致元素存储无序主要因为系统通过哈希算法计算出来的索引和对象本身的hashCode本身有关,所以这个整数值是无序的,从而存储到集合中自然就是无序的
底层源码分析示例
HashSet<String> hs = new HashSet<>();
set.add("ab");
set.add("ac");
set.add("ba");
set.add("bc");
class HashSet {
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
}
class HashMap {
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// 哈希算法和存储对象本身有关
static final int hash(Object key) {
int h;
// 哈希值和对象的hashCode有关
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 判断元素是否唯一取决于哈希值和equals方法
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
}
class String {
private int hash; // Default to 0
// 字符串hashCode方法的重写规则
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
// ab
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i]; // 31 * 0 + 97
h = 31 * 97 + 98
}
hash = h;
}
return h;
}
// 字符串equals方法的重写规则
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
}
HashSet存储自定义对象去除重复元素需要重写 hashCode和equals方法
public class HashSetDemo02 {
public static void main(String[] args) {
HashSet<Student> hs = new HashSet<>();
hs.add(new Student("zhangsan", 18));
hs.add(new Student("lisi", 20));
hs.add(new Student("zhangsan", 18));
hs.add(new Student("wangwu", 30));
for (Student s : hs) {
System.out.println(s);
}
}
}
class Student {
private String name;
private int age;
public Student() {
super();
}
public Student(String name, int age) {
super();
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 "Student [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
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 (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
程序运行结果
Student [name=lisi, age=20]
Student [name=zhangsan, age=18]
Student [name=wangwu, age=30]
TreeSet
概述
基于
TreeMap
的NavigableSet
实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的Comparator
进行排序,具体取决于使用的构造方法。
特点
-
TreeSet 是一个有序的并且可排序的集合,它继承于AbstractSet抽象类,实现了NavigableSet, Cloneable, java.io.Serializable接口。
-
TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。
-
TreeSet是非同步的,线程不安全的,效率高。
-
二叉树保证元素唯一
【
第一个元素进来作为根节点存储
后面元素进来和根节点比较
大了,放在元素的右边
小了,放在元素的左边
相等,设置原值
】
-
二叉树保证元素可排序 --> 利用二叉树中序遍历取元素的特点。
底层源码分析示例
// 使用TreeSet无参构造方法实现自然排序
TreeSet<Integer> ts = new TreeSet<>();
// 使用TreeSet带Comparator接口参数的构造方法实现比较其排序
// TreeSet<Student> ts = new TreeSet<>(new StudentComparator());
ts.add(40);
ts.add(38);
ts.add(43);
ts.add(42);
ts.add(37);
ts.add(44);
ts.add(39);
ts.add(38);
ts.add(44);
class TreeSet {
private transient NavigableMap<E,Object> m;
private static final Object PRESENT = new Object();
public TreeSet() {
this(new TreeMap<E,Object>());
}
// Comparator<? super E> comparator = new StudentComparator();
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
// NavigableMap<E,Object> m = new TreeMap<>(comparator);
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
// NavigableMap<E,Object> m = new TreeMap<E,Object>();
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
}
class TreeMap implements NavigableMap<K,V> {
private transient Entry<K,V> root;
private final Comparator<? super K> comparator;
public TreeMap() {
comparator = null;
}
// Comparator<? super K> comparator = new StudentComparator();
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
public V put(K key, V value) {
Entry<K,V> t = root;
// 如果第一个元素进来,作为根节点存储
if (t == null) {
compare(key, key); // type (and possibly null) check
// 创建根节点
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
// 创建父节点
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator; // cpr = null
if (cpr != null) {
// 比较器排序
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
// 自然排序
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key; 38 Integer
do {
// 根节点作为父节点
parent = t;
cmp = 38 k.compareTo(t.key 40); -1
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
}
class Integer implements Comparable<Integer>{
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
}
interface Comparable {
public int compareTo(T o);
}
interface Comparator {
int compare(T o1, T o2);
}
排序标准模板写法代码实例
/*
* 2.键盘录入5个学生信息(姓名,年龄,语文成绩,数学成绩,英语成绩),按照总分从高到低输出到控制台
* 注:总分相同等情况下按照语文成绩排序,其次是数学成绩、英语成绩、年龄、姓名
*/
public class TreeSetDemo04 {
public static void main(String[] args) {
// 匿名内部类实现Comparator接口方式创建TreeSet对象
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 排序标准模板写法
Collator cmp = Collator.getInstance(java.util.Locale.CHINA);
double num1 = s1.getTotalScore() - s2.getTotalScore();
double num2 = (num1 == 0) ? s1.getChineseScore() - s2.getChineseScore() :num1;
double num3 = (num2 == 0) ? s1.getMathScore() - s2.getMathScore() : num2;
double num4 = (num3 == 0) ? s1.getEnglishScore() - s2.getEnglishScore() : num3;
double num5 = (num4 == 0) ? s1.getAge() - s2.getAge() : num4;
double num6 = (num5 == 0) ? cmp.compare(s1.getName(), s2.getName()): num5;
return (num6 < 0) ? -1 : ((num6 == 0) ? 0 : 1);
}
});
for (int i = 0; i < 30; i++) {
String name = RandomValue.getChineseName();
int age = RandomValue.getRandomNum(18, 20);
double chineseScore = RandomValue.getRandomNum(88, 90);
double mathScore = RandomValue.getRandomNum(90, 91);
double englishScore = RandomValue.getRandomNum(88, 89);
Student s = new Student(name, age, chineseScore, mathScore, englishScore);
ts.add(s);
}
for (Student s : ts) {
System.out.println(s);
}
}
}
class Student {
private String name;
private int age;
private double chineseScore;
private double mathScore;
private double englishScore;
此处省略构造方法...
public double getTotalScore() {
return this.chineseScore + this.mathScore + this.englishScore;
}
此处省略get/set方法...
}
public class RandomValue {
public static String base = "abcdefghijklmnopqrstuvwxyz0123456789";
private static String firstName = "赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅皮卞齐康伍余元卜顾孟平黄和穆萧尹姚邵湛汪祁毛禹狄米贝明臧计伏成戴谈宋茅庞熊纪舒屈项祝董梁杜阮蓝闵席季麻强贾路娄危江童颜郭梅盛林刁钟徐邱骆高夏蔡田樊胡凌霍虞万支柯咎管卢莫经房裘缪干解应宗宣丁贲邓郁单杭洪包诸左石崔吉钮龚程嵇邢滑裴陆荣翁荀羊於惠甄魏加封芮羿储靳汲邴糜松井段富巫乌焦巴弓牧隗山谷车侯宓蓬全郗班仰秋仲伊宫宁仇栾暴甘钭厉戎祖武符刘姜詹束龙叶幸司韶郜黎蓟薄印宿白怀蒲台从鄂索咸籍赖卓蔺屠蒙池乔阴郁胥能苍双闻莘党翟谭贡劳逄姬申扶堵冉宰郦雍却璩桑桂濮牛寿通边扈燕冀郏浦尚农温别庄晏柴瞿阎充慕连茹习宦艾鱼容向古易慎戈廖庚终暨居衡步都耿满弘匡国文寇广禄阙东殴殳沃利蔚越夔隆师巩厍聂晁勾敖融冷訾辛阚那简饶空曾毋沙乜养鞠须丰巢关蒯相查后江红游竺权逯盖益桓公万俟司马上官欧阳夏侯诸葛闻人东方赫连皇甫尉迟公羊澹台公冶宗政濮阳淳于仲孙太叔申屠公孙乐正轩辕令狐钟离闾丘长孙慕容鲜于宇文司徒司空亓官司寇仉督子车颛孙端木巫马公西漆雕乐正壤驷公良拓拔夹谷宰父谷粱晋楚阎法汝鄢涂钦段干百里东郭南门呼延归海羊舌微生岳帅缑亢况后有琴梁丘左丘东门西门商牟佘佴伯赏南宫墨哈谯笪年爱阳佟第五言福百家姓续";
private static String girl = "秀娟英华慧巧美娜静淑惠珠翠雅芝玉萍红娥玲芬芳燕彩春菊兰凤洁梅琳素云莲真环雪荣爱妹霞香月莺媛艳瑞凡佳嘉琼勤珍贞莉桂娣叶璧璐娅琦晶妍茜秋珊莎锦黛青倩婷姣婉娴瑾颖露瑶怡婵雁蓓纨仪荷丹蓉眉君琴蕊薇菁梦岚苑婕馨瑗琰韵融园艺咏卿聪澜纯毓悦昭冰爽琬茗羽希宁欣飘育滢馥筠柔竹霭凝晓欢霄枫芸菲寒伊亚宜可姬舒影荔枝思丽 ";
private static String boy = "伟刚勇毅俊峰强军平保东文辉力明永健世广志义兴良海山仁波宁贵福生龙元全国胜学祥才发武新利清飞彬富顺信子杰涛昌成康星光天达安岩中茂进林有坚和彪博诚先敬震振壮会思群豪心邦承乐绍功松善厚庆磊民友裕河哲江超浩亮政谦亨奇固之轮翰朗伯宏言若鸣朋斌梁栋维启克伦翔旭鹏泽晨辰士以建家致树炎德行时泰盛雄琛钧冠策腾楠榕风航弘";
/**
* 随机返回任意范围的整数
*/
public static int getRandomNum(int start, int end) {
return (int) (Math.random() * (end - start + 1) + start);
}
/**
* 随机返回中文姓名
*/
public static String getChineseName() {
String name_sex = "";
int index = getRandomNum(0, firstName.length() - 1);
String first = firstName.substring(index, index + 1);
int sex = getRandomNum(0, 1);
String str = boy;
int length = boy.length();
if (sex == 0) {
str = girl;
length = girl.length();
name_sex = "女";
} else {
name_sex = "男";
}
index = getRandomNum(0, length - 1);
String second = str.substring(index, index + 1);
int hasThird = getRandomNum(0, 1);
String third = "";
if (hasThird == 1) {
index = getRandomNum(0, length - 1);
third = str.substring(index, index + 1);
}
return first + second + third;
}
}
- 注意中文排序可以使用
Collator
类处理 - 建议使用三目运算符进行排序
- 针对返回值是
double
类型的,可以考虑使用三目转换成int类型的结果
LinkeHashSet
概述
List
接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括null
)。除了实现
List接口外,
LinkedList类还为在列表的开头及结尾
get、
remove和
insert` 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。
特点
- 底层数据结构是 链表和哈希表
- 链表保证元素有序
- 哈希表保证元素唯一
代码示例
LinkedHashSet<String> lhs = new LinkedHashSet<>();
lhs.add("中国");
lhs.add("美国");
lhs.add("美国");
lhs.add("德国");
System.out.println(lhs);
程序运行结果:
[中国, 美国, 德国]