集合-Set<E>

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;
            }


        });
}       
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值