Set<E>
JDK1.2
public interface Set<E> extends Collection<E>
不包含重复元素的collection。
- List 的特点
有序(存储顺序和去除顺序一致),可重复- Set
无序(存储顺序和取出顺序不一致),唯一
HashSet<E>
JDK 1.2
java.util.HashSet<E>
public class HashSet<E> extends Abstract Set<E> implements Set<E>, Cloneable, Serializable
此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。
HashSet ,它不保证set的迭代顺序,特别是它不保证该顺序恒久不变,
虽然set集合的元素无序,但是,作为集合来说,它肯定有自己的存储顺序。如果恰巧输出顺序和存储顺序一致,这并不能代表有序,可以多存储一些数据,检测输出结果。
//创建集合对象
Set<String> set = new HashSet<String>();
//创建并添加元素
set.add("hello");
set.add("world");
set.add("java");
set.add("java");//可以添加
//for增强
for(String s : set){
System.out.println(s);//hello java world
}
问题:为什么在存储字符串的时候,字符串内容相同的只存储了一个?
HashSet集合的add方法的源码:
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(){
//创建一个HashMap对象
map = new HashMap<>();
}
public boolean add(E e){ //e=hello
return map.put(e,PRESENT)==null;
}
}
//HashMap类
class HashMap implements Map{
transient int hashSeed =0;
final int hash(Object k){//key=e=hello
int h = hashSeed;
if(0 != h && k instanceof String){
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();//这里调用对象的hashCode 方法
h ^= (h >>> 20)^ (h>>> 12);//>>>:无符号右移,忽略符号位,空位都以0补齐,
return h ^ (h>>> 7) ^ (h>>>4);
}
public V put(K key,V value){ //key=e=hello,world
if(table==EMPTY_TABLE){ //判断哈希表是否为空
inflateTable(threshold);//如果空就开辟空间
}
if(key==null){ //判断对象是否为null
return putForNullKey(value);
}
int hash = hash(key); //如果对象不为null
//该方法和对象的hashCode()方法有关。
//在哈希表中查找哈希值
int i=indexFor(hash,table.length);
for(Entry<K,V> e = table[i];e != null;e=e.next){
//这里保证了哈希值相同,如果地址值相同或者equals一样时,无法添加元素
Object k;
if(e.hash==this.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);//把元素添加hello ,world
return null;
}
}
HashSet如何保证元素的唯一性(重写hashCode和equals方法)
底层数据结构是哈希表(元素是链表的数组)。
哈希表依赖于哈希值存储。
由HashSet的add方法源码知道添加功能底层依赖两个方法
- int hashCode()
- boolean equals(Object obj)
- 通过查看add方法的原码,这个方法的底层依赖两个方法:hashCode() 和 equals()
步骤:
- 首先比较哈希值
- 如果相同,继续走,比较地址值或走equals()
- 返回true,说明元素重复,就不添加
- 返回false 说明元素不重复,就添加到集合
- 如果不同,就直接添加到集合中
- 注意:如果类没重写这个方法,默认使用OBject的,一般来说不会相同。
而String类重写了hashCode()和equals ()方法,所以它就可以把内容相同的字符串去掉,只留一个。
哈希表:一个元素为链表的数组,综合了数组和链表的好处。
HashSet存储自定义对象(重写hashCode()和equals()方法)
LinkedHashSet 类概述
java.util.LinkedHashSet<E>
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, Serializable
- 具有可预知迭代顺序的set接口的哈希表和链接列表实现
- 数据结构有哈希表和链表
- 元素有序唯一
- 由链表保证元素有序
- 有哈希表保证元素唯一
// LinkedHashSet : 底层是链表和哈希表
// 保证了集合元素的唯一性和有序性
public class LinkedHashSetDemo {
public static void main(String[] args) {
//创建集合对象
//创建并添加元素
hs.add("hello");
hs.add("java");
hs.add("world");
hs.add("java");
//遍历
for(String s : hs){
System.out.println(s);//hello java world
}
}
}
TreeSet 概述
java.util.TreeSet<e>
public class TreeSet<E>extends AbstractSet<E>implements NavigableSet<E>, Cloneable, Serializable
- TreeSet集合的特点: 排序和唯一
- 基于TreeMap 的NavigableSet实现
- 使用元素的自然排序对元素进行排序
- 根据创建Set 时提供的Comparator 进行排序(比较器排序),
- 具体取决于使用的构造方法
如果一个方法的参数是接口,那么真正要的是接口的实现类的对象。
而匿名内部类就可予以实现这个东西。
public static void main(String[] args) {
//创建集合对象
//无参构造 自然排序
TreeSet<Integer> ts = new TreeSet<Integer>();
//创建元素并添加
ts.add(20);
ts.add(17);
ts.add(23);
//遍历
for(Integer i : ts){
System.out.println(i);//17 20 23
}
}
接口Comparator<T>
java.util.Comparator
public interface Comparator<T>
TreeMap<K,V>
类
JDK 1.2
java.util.TreeMap<K,V>
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, Serializable
基于红黑树(Red-Black tree)的 NavigableMap 实现。
该映射根据其键的自然顺序进行排序,
或者根据创建映射时提供的 Comparator 进行排序,
具体取决于使用的构造方法。
接口NavigableMap<K,V>
JDK 1.6
所有已知实现类: ConcurrentSkipListMap, TreeMap
java.util.NavigableMap<K,V>
public interface NavigableMap<K,V>extends SortedMap<K,V>
TreeSet 的add源码解析
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);
root = new Entry<>(key,value,null);
size=1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
if(apr ! = 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;
//Integer类实现了Comparato接口
do{
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++;
erturn 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;
}
}
//真正的比较是依赖于元素的comparaTo()方法,这个方法定义在Comparable 里面
//如果要重写该方法,就要先实现Comparable 接口,这个接口表示自然排序
TreeSet 集合保证元素排序和唯一性的原理
- 唯一性:是根据比较的返回是否是0 来决定的
- 排序:
- 自然排序(元素具备比价性)
- 让元素所属的类实现自然排序接口Comparable
- 比较排序(集合具备比较性)
- 让集合构造方法接收一个比较器接口的子类对象Comparator
自然排序
// TreeSet 存储自定义对象,并保证排序和唯一性
//问题
// 没有明确如何排序
// 自然排序(按照年龄从小到大)
// 没有明确元素的唯一性
// 成员变量值都相同即为同一个元素
public class TreeSetDemo2 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>();
//创建元素
Student s1 = new Student("林青霞",23);
Student s2 = new Student("朱茵",25);
Student s3 = new Student("杨澜",21);
ts.add(s1);
ts.add(s2);
ts.add(s3);
//遍历
for(Student s : ts){
System.out.println(s.getName()+" "+s.getAge());
}
}
}
//如果一个类的元素能够进行自然排序就一定要实现comparable接口
public class Student implements Comparable<Student>{
@override
public int compareTo(Student s){
//return 0;//相等只有根节点
//return 1;//每次都是比根节点大(正序)
//return -1;//每次都是比根节点小(倒序)
//这里的返回值,应该根据比较规则
int num = this.age - s.age;
//要添加的对象就是this
//年龄是主要条件,还要分析次要条件
//年龄相同的时候,还的去看姓名是否也相同
//如果年龄和姓名都相同,才是同一个元素
int num2 = num==0 ? this.name.compareTo(s.name):num;
//如果num==0;则num2=this.name.compareTo(s.name),否则,num2=num;
return num2;
}
}
练习2
@override
public int compareTo(Student s) {
// TODO Auto-generated method stub
//return 0;相同
//return 1;大于
//return -1;小于
//这里的返回值,应该根据比较规则
//主要条件
int num = (this.name.length())- (s.name.length());
//名字长度相同,不代表名字的内容相同
int num2 = (num==0) ? (this.name.compareTo(s.name)):num;
//还得判断年龄
int num3 = (num2 ==0 )? (this.age - s.age):num2;
return num3;
}
比较器排序
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;
}
}
如果一个方法的参数是接口,那么真正要的是接口的实现类的对象,而匿名内部类就可以实现。
public static void main(String[] args) {
//创建集合对象
//TreeSet<Student> ts = new TreeSet<Student>();自然排序
//比较器排序
//TreeSet<Student> ts = new TreeSet<Student>(new MyComparator());
//匿名类实现
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>(){
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;
}
});
}