Java集合之Set集合

 

 

Set集合和List集合的区别?

Set集合:不允许元素重复,唯一的(元素可以为null) ,不能保证迭代的顺序恒久不变(底层哈希表和hascode),无序(存储和取出不一致)
List集合:允许元素重复,并且存储特点:有序性(存储和取出一致)

 

 

一.HashSet集合

 

 

[需求1]通过Set集合存储字符串并遍历

public static void main(String[] args) {
		
		//创建Set集合对象,子实现类:HashSet集合
		Set<String> set = new HashSet<String>();
		
		//添加元素
		set.add("hello") ;
		set.add("java") ;
		set.add("world") ;
		//看他的唯一性,多存储一些元素
		set.add("hello") ;
		set.add("java") ;
		set.add("world") ;
		
		//遍历
		for(String s: set) {
			System.out.println(s);
		}
	}

 

从上述需求发现:Set集合存储元素的时候,可以保证元素的唯一性,原因什么?

HashSet集合的add方法底层依赖于双列集合HashMap,它依赖于两个方法,HashCode()方法和equals()方法
       先比较字符串的HashCode()码值一样,再比较equals()方法

       如果hasCode码值一样,还要比较内容是否相同,由于存储String,重写了equals()方法

//HashSet集合的add方法的源码

inteface Collection{
	
}


interface Set extends Collection{
	
	
}

class HashSet implements Set{
	private  HashMap<E,Object> map;
	
	//创建HashSet集合对象的时候,起始底层创建了一个HashMap集合对象
	 public HashSet() {
        map = new HashMap<>();
    }
    
    
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;			//add方法底层依赖于HashMap集合的
    }
}


class HashMap<K,V> implements Map<K,V>{
		
		public V put(K key, V value) {
      	  return putVal(hash(key), key, value, false, true);
   		}
   		
   		
   			//putVal方法
   		 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
                
        Node<K,V>[] tab; Node<K,V> p; int n, i;	//Node是一种键值对对象
        
        //判断哈希表示是为空,如果为空,得到哈希表长度;set集合有元素的情况,哈希表不为空
        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;			//k--->传入的元素
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))	
                						//一系列判断,主要还是eqauls()方法
                e = p;	//e =p = key 
            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			mapping:映射	(Servlet  servlet-mapping)
                V oldValue = e.value;		//e.value=key ="hello"	,"world","java","world","java"
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;			//返回的oldValue:永远是第一次存储的那个元素
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
}
哈希方法
static final int hash(Object key) {	//传入的字符串元素		"hello","java","world" ,"hello"....
        int h;	//哈希码值 
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);		//无符号右移
        						底层依赖于第一个方法hashCode()         						
    }

 

[需求2]set集合存储自定义对象并遍历

public static void main(String[] args) {
			Set<Student> set = new HashSet<Student>() ;
			
			//创建学生对象
			Student s1 = new Student("高圆圆", 27) ;
			Student s2 = new Student("高圆圆", 28) ;
			Student s3 = new Student("文章", 30) ;
			Student s4 = new Student("马伊琍", 39) ;
			Student s5 = new Student("高圆圆", 27) ;
			
			//存储到集合中
			set.add(s1) ;
			set.add(s2) ;
			set.add(s3) ;
			set.add(s4) ;
			set.add(s5) ;
			
			//遍历
			for(Student s : set) {
				System.out.println(s.getName()+"---"+s.getAge());
			}
		}

 

  按照正常套路,发现遍历集合的时候输出重复元素,怎么解决?

 

 

         现在是自定义的类,HashSet集合的add()方法本身依赖于hashCode()和equals()方法,在Student类中并没重写这两个方法,             解决:重写这两个方法!

[注意]Set集合存储自定义对象遍历,需要在自定义类中重写hashCode()和equals()方法!

         String类本身重写了equals方法,所以不需要再重写了!

二.LinkedHashSet集合

 

 

如果在开发中,元素唯一性,并且还要保证元素有序(存储和取出一致),使用LinkedHashSet集合

LinkedHashSet集合:
 *  底层是一种链接列表和哈希表组成
 *  可以保证元素的唯一性,是由哈希表决定的(hashCode()和equals())
 *  可以保证元素的迭代顺序一致(有序),存储和取出一致,是由链表决定

public static void main(String[] args) {
		
		//创建LinkedHashSet集合对象
		LinkedHashSet<String> link = new LinkedHashSet<String>() ;
		
		//添加元素
		link.add("hello") ;
		link.add("java") ;
		link.add("world") ;
		link.add("world") ;
		link.add("world") ;
		link.add("java") ;
		
		
		//增强for遍历
		for(String s: link) {
			System.out.println(s);
		}
	}

 

输出结果:

hello
java
world

三.TreeSet集合

 

 

1.TreeSet集合默认情况下是通过自然顺序对集合中的元素排序.

2.TreeSet:可以保证元素唯一并且元素排序(Integer类型的元素自然升序)
 *      自然排序
 *      比较器排序

[需求1]给TreeSet集合存储以下元素:20,23,22,18,17,19,24...

public static void main(String[] args) {
		//创建一个集合对象TreeSet集合对象
		TreeSet<Integer> ts = new TreeSet<Integer>() ;
		
		//给集合中存储元素
		ts.add(20) ;		//add()方法底层的源码是一个Map接口实例
		ts.add(22) ;
		ts.add(18) ;
		ts.add(23) ;
		ts.add(24) ;
		ts.add(17) ;
		ts.add(19) ;
		ts.add(18) ;
		ts.add(24) ;
		
		//遍历
		for(Integer i : ts) {
			System.out.print(i +" ");
		}
	}

 

3.TreeSet集合的add的方法的源码(重点)

interface Collection{
}


interface Set  extends Collection{

}


interface Map{

	V put(K key, V value);
}

inteface NavigableMap exnteds Map{
	V put(K key, V value);
}

class TreeMap implements NavigableMap extends Map{
	private transient Entry<K,V> root;		//根节点:作为键值对对象
	
	 public V put(K key, V value) {	//key= 20,22,23,17,18,19,24,18,24
        Entry<K,V> t = root;	//Entry<k,V> t= this.root;	//20
        if (	
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;			//cmp变量
        Entry<K,V> parent;	//parent =root = 根节点
        // split comparator and comparable paths		//如果有根节点,分两种方式:comparable接口	和Compartor(比较排序)
        				
        				//比较器排序		
        Comparator<? super K> cpr = comparator;		//创建了Compartor实例,属于比较函数(实现比较器排序)
        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;	
                //创建了实例Comparable接口的实例对象(自己实现类)	//
            do {
                parent = t;	//	开始存储:先存储根节点
                //k,除过根节点后面的元素和根节点进行比较
                cmp = k.compareTo(t.key); //底层依赖于就是Compareable接口中的compareTo比较
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;s
                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;			//如果没有元素 ,返回nulll
    }
	
	
}

 

class TreeSet  implements Set{
	
	 private transient NavigableMap<E,Object> m;
	  public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    	}
}

 

通过对TreeSet集合的add的方法的源码分析,得出结论:

使用TreeSet集合的自然排序进行操作,该集合中数据类型一定要实现Comparable接口里面的comparTo()方法!

 

 

[需求2]使用TreeSet集合存储自定义对象(Student类型),并遍历!

分析:

如何排序?
       按照学生的年龄从小到大进行排序 (主要条件)
唯一性:
      如果成员变量的值一样认为是同一个对象

//对于自定义的类型,要实现自然排序,必须自定义的类型必须实现Comparable
//实现接口中的方法,compareTo() :比较方法
public class Student  implements Comparable<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 int compareTo(Student s) {	//源码 cmp = k.compareTo(t.key)
//		return 0;
		//按照某种规则,前提必须有这规则
		//主要条件:按照年龄从小到大
		int num  = this.age - s.age ; //如果年龄相等,不一定是同一个人
		//需要自己分析次要条件;
		//年龄相同,姓名的内容不一定相同,比较姓名
		int num2 = num==0 ? this.name.compareTo(s.getName()) : num ;
		return num2 ;
	}
	
	
}

 

public static void main(String[] args) {
		//创建TreeSet集合对象
		TreeSet<Student> ts  = new TreeSet<Student>() ;//无参构造的方式自然排序
		
		//创建学生对象
		Student s1 = new Student("gaoyuanyuan",27) ;	
		Student s2 = new Student("liushishi",38) ;
		Student s3 = new Student("gaoyuanyuan",28) ;
		Student s4 = new Student("wanglihong",35) ;
		Student s5 = new Student("wanglihong",30) ;
		Student s6 = new Student("fengqingy",38) ;
		Student s7 = new Student("gaoyuanyuan",27) ;
		
		//java.lang.ClassCastException: org.westos_03.Student cannot be cast to java.lang.Comparable
		ts.add(s1) ;
		ts.add(s2) ;
		ts.add(s3) ;
		ts.add(s4) ;
		ts.add(s5) ;
		ts.add(s6) ;
		ts.add(s7) ;
		
		//遍历
		for(Student s:ts) {
			System.out.println(s.getName()+"---"+s.getAge());
		}
		
	}

 

4.TreeSet集合的比较器排序

TreeSet集合的构造方式不同,使用的排序方式也不同
 *  自然排序:自定义的类实现Compareable接口,然后创建TreeSet对象,通过无参构造形式创建对象
 *  比较器排序 :public TreeSet(Comparator<E> comparator)

           两种方式:
                1)自定义一个类,该类实现Comparator接口,重写Comparator接口中的compare()方法
                2)直接使用接口匿名内部类的方式实现

[需求]

使用比较器排序的方式去,遍历Student对象,并且按照姓名长度进行比较

public static void main(String[] args) {
		
		//创建TreeSet集合对象
		//直接使用匿名内部类的方式实现
		TreeSet<Student> ts = 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 s1 = new Student("gaoyuanyuan", 27) ;
		Student s2 = new Student("zhangguorong",29) ;
		Student s3 = new Student("wuqilong", 40) ;
		Student s4 = new Student("liushishi", 28) ; 
		Student s5 = new Student("fengqingy", 29) ;
		Student s6 = new Student("gaoyuanyuan", 22) ;
		Student s7 = new Student("gaoyuanyuan", 27) ;
		
		
		ts.add(s1) ;
		ts.add(s2) ;
		ts.add(s3) ;
		ts.add(s4) ;
		ts.add(s5) ;
		ts.add(s6) ;
		ts.add(s7) ;

		//遍历
		for(Student s:ts) {
			System.out.println(s.getName()+"----"+s.getAge());
		}
	}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值