set集合
1.概述
set集合是Collection的一个子类,是一个无序且元素唯一的集合。由于Set集合是一个接口,所以我们在实例化的时候使用其子类HashSet。
2.set集合的基本使用测试(存储对象并遍历)
public static void main(String[] args) {
//创建对象
Set<String> st = new HashSet<String>();
//添加元素
st.add("hello");
st.add("world");
st.add("java");
st.add("world");
st.add("java");
//遍历
for(String str:st){
System.out.println(str);
}
}
//注意:
1.由于set集合具有无序性,所以HashSet不保证set的迭代顺序,特别是不保证该顺序的永恒不变。
2.set集合遍历的时候无法使用普通for循环来获取输出,只能够使用迭代器和增强for。
HashSet集合
1.概述
HashSet集合是set集合的子类,所以它也存在set集合的无序和唯一的俩个特性。特别是它不保证迭代顺序永远不变。
2.HashSet是如何保证数据唯一性的?(源代码分析)
源码以及解析:
实现步骤:
问题:为什么存储字符串的时候,字符串内容相同的只存储了一个呢?
通过查看add方法的源码,我们知道这个方法底层依赖 两个方法:hashCode()和equals()。
步骤:
首先比较哈希值
如果相同,继续走,比较地址值或者走equals()
如果不同,就直接添加到集合中
按照方法的步骤来说:
先看hashCode()值是否相同
相同:继续走equals()方法
返回true: 说明元素重复,就不添加
返回false:说明元素不重复,就添加到集合
不同:就直接把元素添加到集合
如果类没有重写这两个方法,默认使用的Object()。一般来说不同相同。
而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉。只留下一个。
代码结构:
//最开始接口位置
interface Collection {
...
}
interface Set extends Collection {
...
}
class HashSet implements Set {
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) { //e=hello,world
return map.put(e, PRESENT)==null;
}
}
class HashMap implements Map {
public V put(K key, V value) { //key=e=hello,world
//看哈希表是否为空,如果空,就开辟空间
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//判断对象是否为null
if (key == null)
return putForNullKey(value);
int hash = hash(key); //和对象的hashCode()方法相关
//在哈希表中查找hash值
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//这次的e其实是第一次的world
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
//走这里其实是没有添加元素
}
}
modCount++;
addEntry(hash, key, value, i); //把元素添加
return null;
}
transient int hashSeed = 0;
final int hash(Object k) { //k=key=e=hello,
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode(); //这里调用的是对象的hashCode()方法
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
}
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("world");
3.HashSet使用案例:存储自定义对象并遍历
public static void main(String[] args) {
//创建对象
HashSet<String> st = new HashSet<String>();
//添加元素
st.add("hello");
st.add("world");
st.add("java");
//遍历
for(String str:st){
System.out.println(str);
}
}
//使用方法和步骤与一般的集合相似
LinkedHashSet类
1.概述
LinkedHashSet是一个元素有序且唯一的集合。其中由链表保证元素的有序,由哈希表保证元素的唯一。
2.LinkedHashSet基本使用(存储自定义对象并遍历)
public static void main(String[] args) {
//创建对象
LinkedHashSet<String> st = new LinkedHashSet<String>();
//添加元素
st.add("hello");
st.add("world");
st.add("java");
//遍历
for(String str:st){
System.out.println(str);
}
}
TreeSet类
1.概述
TreeSet类是能够对存储的元素进行某一种特殊规则来进行排序的集合,由于它是set集合的一个子类,所以具备元素唯一的特点。至于选择哪一种排序,取决于构造方法。
常见的排序有俩种:(1)自然排序;(2).比较器排序。
2.TreeSet自然排序使用测试(存储自定义对象并遍历)
public static void main(String[] args) {
//创建对象
TreeSet<Integer> ts = new TreeSet<Integer>();
ts.add(12);
ts.add(11);
ts.add(12);
ts.add(13);
ts.add(15);
ts.add(17);
ts.add(10);
ts.add(37);
ts.add(16);
for(int i:ts){
System.out.println(i);
}
}
//输出结果:
10
11
12
13
15
16
17
37
//由此可见是排序好且无重复的集合
3.TreeSet如何保证排序源码解析(自然排序)
interface Collection {...}
interface Set extends Collection {...}
interface NavigableMap {
}
class TreeMap implements NavigableMap {
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;
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();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
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 TreeSet implements Set {
private transient NavigableMap<E,Object> m;
public TreeSet() {
this(new TreeMap<E,Object>());
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
}
真正的比较是依赖于元素的compareTo()方法,而这个方法是定义在 Comparable里面的。所以,你要想重写该方法,就必须是先 Comparable接口。这个接口表示的就是自然排序。注意:默认无参构造就是使用自然排序。
原理如下图:
4.TreeSet比较器排序存储自定义对象并遍历以及原理分析
//创建学生对象
public class Student {
//姓名
private String name;
//年龄
private int age;
//设置带参构造
public Student(String name,int age){
this.name = name;
this.age = age;
}
//省略getXxx()和setXxx()方法
}
//对学生对象的比较器排序以及测试
public static void main(String[] args) {
//创建对象,最好使用匿名内部类的方法来实现(把要比较的内容返回即可),如果需要重复,那么就需要定义外部类,这里是内部类
TreeSet<Student> tst = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 姓名长度
int num = s1.getName().length() - s2.getName().length();
// 姓名内容
int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
: num;
// 年龄
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
});
//添加方法
Student st = new Student("张三",12);
Student st1 = new Student("李四",13);
Student st2 = new Student("王五",14);
Student st3 = new Student("赵六",15);
Student st4 = new Student("王二麻子",16);
tst.add(st);
tst.add(st1);
tst.add(st2);
tst.add(st3);
tst.add(st4);
//遍历
for(Student getSt:tst){
System.out.println(getSt.getName()+"-----"+getSt.getAge());
}
}
//结果:张三-----12
李四-----13
王五-----14
赵六-----15
王二麻子-----16
补充:如果是该对象需要被反复调用,那么这一个时候就不再适合使用内部类,需要使用外部类。
TreeSet<Student> tst = new TreeSet<Student>(new MyComparator<Student>());
//实现外部类:
public class MyComparator implements Comparator<Student>{
@Override
public int compare(Student s1, Student s2) {
// 姓名长度
int num = s1.getName().length() - s2.getName().length();
// 姓名内容
int num2 = num == 0 ? s1.getName().compareTo(s2.getName())
: num;
// 年龄
int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
return num3;
}
}
5.TreeSet集合总结
(1).唯一性:是根据比较的返回值是否是0来决定的。(俩个元素在添加的时候是否一样,如果是,那么就是返回0)
(2).有序:
A:自然排序(元素具备比较性)
让元素所属的类实现自然排序接口comparable
B:比较器排序(集合具备比较性)
让集合的构造方法接受一个比较器接口的子类对象Comparator