14.集合(单列集合)、相关数据结构和比较器

数据结构之哈希表

数据结构之哈希表 : hash (hashtable)
    
扩容原理 :
    1. 刚创建hash结构时,底层的数组长度是16(加载因子 : 0.75) [加载因子决定了扩容时机]
    当表中元素个数 >= (底层数组长度 * 加载因子) 个时, 底层的数组长度 翻倍 !
    2. 当底层数组扩容后,会重新计算每一个元素应该存储的位置; 
    3. 当添加的新元素所存储的表位置的值 是默认值 null 时, 直接添加;不考虑扩容!
    (jdk7版本是这样判断的,jdk8版本取消了此判断)
    4. 当一个哈希表位置的链表元素 == 8 时, 在JDK8中会把此哈希表索引位置的链表转换成红黑树 (红黑树提高了查找效率[红黑树可以排序])
    5. 当整个hash表中的元素个数<=64 时, 就算一个链表的元素个数>8 ,也不会转成红黑树    
    
当往一个底层数据结构是 hash表的容器中 添加第1024个元素 (不考虑特殊情况),请问底层哈希表数组的长度是多少 ? 
       答:2048

HashSet<E>的使用

构造方法:
    HashSet<E> 集合名 = new HashSet<>(); 

增删改查:
    增 : add
        boolean add(E e) : 添加元素,返回元素添加是否成功 (元素唯一)
        
    删 : remove
        boolean remove(Object obj) : 按照元素值删除元素,返回删除是否成功
        void clear(): 清空集合
        
    改 : set 因为没有索引所以不能修改指定位置的元素
    
    查 : get 因为没有索引所以不能获取指定位置的元素    
        int size() : 获取长度
        boolean isEmpty() : 判断集合是否为空
        boolean contains(Object obj) : 判断传入的元素是否存在于集合中
遍历:
    1. 转数组
         Object[] toArray()
    2. 普通迭代器
         Iterator<E> iterator() 
    3. 增强for (Iterable)
         集合名.for

HashSet<E>的去重原理(JDK7)

结论 : 
    1. 如果集合元素类型中没有重写equals 和 hashCode方法,那么Hash表(HashSet集合,HashMap集合)是按照元素的地址值进行去重; 如果地址值相同,则去重!
    //一般情况下,我们更希望Hash表按照元素的 属性值去重, 属性值相同则认为是相同元素,要求添加失败
    2. 在元素所在的类中重写equals 和 hashCode方法,就可以实现Hash表按照元素的属性值去重!! 

HashSet去重源码解析:

//目标 : 去重 !! -> 添加失败 -> add方法的返回值是 false

HashSet集合中add方法:

	public boolean add(E e) { // = stu4;
		//HashSet中的add方法 调用了 HashMap中的 put 方法
		//因为HashSet和HashMap 共用hash逻辑!
		//put 方法是HashMap添加元素的方法, 而e 作为了HashMap的键元素传递
		// HashMap的键集 其实就是一个HashSet集合
		
		/*
			map.put(e, PRESENT)==null
			add 方法的结果是 map.put(e, PRESENT)调用的结果是不是null!
				put方法的结果是null --> add 方法的结果就是 true -> 添加成功
				put方法的结果不是null --> add 方法的结果就是 false  -> 添加失败
				
			目标转移 : 想办法让map.put方法的结果不是null
		*/
        return this.map.put(e, PRESENT)==null;//Map集合是键--值
    }
	
	  
1.7版本JDK HashMap中的put方法源码:   //JDK8版本的put方法会先调用putVal方法,在putVal方法内编写添加逻辑
	/*
		K key = e; -> 重点!!
		V value = PRESENT;
	*/
	public V put(K key, V value) {
		//第一层 : 不能提前结束方法 -> 非重点关注
		/*
			this.table == EMPTY_TABLE
			当前调用方法的对象.底层数组 --> 获取当前集合对象的底层数组(底层哈希表)
			//现在的this 就是 HashSet对象底层的哈希表
			
			此判断在判断 -->  此时集合底层的数组是否是一个空数组
			为什么要判断底层数组是否是一个空数组: 为第一次添加做准备 , 因为第一次添加不需要去重!!
		*/
        if (table == EMPTY_TABLE) {//存在的意义 : 提高代码的效率
			//猜inflateTable(threshold)的作用 是为 第一次添加元素做准备!!
			
			/**
				现在的情况 : 第四次添加,所以此if判断的结果一定是 false,此方法不执行!!
			*/
			
            inflateTable(threshold);
        }	
		
		//第二层 : 能让方法提前结束 -> 重点关注
		/*
			要添加的元素 == null --> 非空校验
		*/
        if (key == null){		
			// putForNullKey(value) : 
			//1. 为添加元素值为null的键做操作
			//2. 为第二次添加null键去重
			//3. 区分hash表中的默认值null 和元素值 null
			/*
				HashSet 集合中可以有 null 元素,但是只能有一个null元素
				HashMap 集合中可以有 null 键,但是只能有一个null键
			*/
			
			/**
				现在的情况 : stu4 != null 此if判断的结果是false,不进if,不执行return
			*/
			return putForNullKey(value);
		}
		
		//第三层 : 不能提前结束方法 -> 非重点关注
		//int hash = hash(key);  -> 获取添加元素的hash值 
		/*
			一个对象的hash值如何获取 -> 对象.hashCode方法
				a. 如果对象的类没有重写hashCode方法,那么这个对象的hashCode值就是这个对象在内存中的十进制地址值
				b. 如果对象的类重写hashCode方法,那么这个对象的hashCode值就是 ???? (就要去看如何重写的hashCode方法)
		*/
        int hash = hash(key); //hash : 要添加元素的hash值
		
		//indexFor(hash, table.length) : Java中hash结构中的hash算法(决定每一个元素应该存放的表位置)
        int i = indexFor(hash, table.length); //i : 要添加元素应该存放在底层hash表的索引位置
				
		//第四层 : 能让方法提前结束 -> 重点关注		
		/*
			猜测循环在干嘛 : 获取此hash表i索引位置的每个元素  --> 为了一一和新元素做比较 --> 去重准备!!
			
			初始化语句: Entry<K,V> e = table[i]; //e -> 简单理解成: 此索引位置的老元素
			判断条件语句:  e != null 
			步进表达式(控制条件语句): e = e.next //依次获取下一个链表元素,并重新赋值给e
			循环体语句:
			
			e : 老元素
			key : 新元素
		*/
		for (Entry<K,V> e = table[i]; e != null; e = e.next) {
			
			//声明了一个局部变量 Object类型 变量名 k; 
			Object k;
			/**
				e.hash == hash && ((k = e.key) == key || key.equals(k)) : 核心去重逻辑
				
				&&: 双与 -> 有false则为false,短路效果 : 左边为false 右边不执行
				&&的左边 : e.hash == hash -> 比较老元素的hash值和新元素的hash值是否相等
				
				现实情况 : Student类中没有重写hashCode方法,所以调用Object类中的hashCode方法
					-> 现在的stu1-4的hashCode值都不一样(Object中的hashCode方法是在获取对象10进制的地址值)
					
				感谢Java提供了方法重写的特性!! 	
				手动重写hashCode方法 -> return 1; 所有的学生对象的hash值都是1,就可以走到&&右边去
				
				目前 : true && ...........
				
				&&右边 (目标是: &&的右边也为true) -> (k = e.key) == key || key.equals(k)
					
					|| : 有true则为true , 短路效果 : 左边为true 右边不执行
					
					||的左边 :  (k = e.key) == key ---> k: 代表的是老元素 key: 新元素
						老元素 == 新元素 : == 一定会比较2个对象的地址值
						
						现实情况 : 一定为false
						
				目前 : true && (false || ....)	

				||的右边 :  key.equals(k)
					-> 新元素.equals(老元素); --> 元素所在的类equals方法的逻辑
					
						现实情况是 Student 类中没有重写equals ,所以调用Object类中的equals 
							Object类中的equals : this == obj
							
					感谢Java提供了方法重写的特性,重写equals让其比较对象的属性值而不是对象的地址值	
					
				重写完毕后,Hash表就会按照 对象的属性值 进行去重了!	
			
			*/
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
				//目标: 去重  想要提前结束方法的!!! -> 进if !!!
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
				
                return oldValue;//大概率不是null
            }
        }	
		//第五层 : 不能提前结束方法 -> 非重点关注
        modCount++; // 统计集合底层数组被修改的次数,为多线程操作服务的
        addEntry(hash, key, value, i);.//双列集合中的元素准备 键值对对象!
		
		//第六层 : 不让其走到第六层, 因为返回null, HashSet中的add就返回true,就添加成功!
        return null;
    }

核心代码 :

for(遍历当前hash表i索引位置的链表元素){
    if(老元素的hash值 == 新元素的hash值 && (老元素 == 新元素 || 新元素.equals(老元素))){
        //能进来就去重,添加失败
        return oldValue;
     }
 }
    //从for出来就添加成功
    return null;

hash算法 : 如何获取一个对象的hash值逻辑就是hash算法

public int hashCode() {//高级的hash算法 : 根据对象的属性值获取对象的hash值
    //张三 -- 20 (330) , 王五 -- 30(650) , 赵六 -- 51 (279 + 51 = 330)
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + age;
    return result;
}

LinkedHashSet<E>的使用

LinkedHashSet<E> 是 HashSet<E>的子类集合 :
    底层数据结构 :链表 + hash表
     //加了链表结构后,集合从存取无序变成存取有序; 

数据结构之二叉树

数据结构之二叉查找树

 

数据结构之平衡二叉树

数据结构之红黑树

 TreeSet<E>集合

TreeSet<E>集合 的底层数据结构是 红黑树结构

红黑树结构 带来了 :
    1. 去重规则 -> 相减等于0则剔除元素
    2. 排序 -> 相减得负数放左边,相减得正数方法右边
    3. 提高了查找效率
    
所以 TreeSet<E>集合具备以下特点:
    1. 元素唯一
    2. 元素存取无序 (TreeSet带有排序功能) -> 元素能排序
    3. 无索引
    
hash表结构在JDK8时把底层由 (数组 + 链表) 改为 (数组 + 链表 + 红黑树) 的目的在于提高元素的查找效率    


构造方法:
    //按照泛型元素默认提供的排序规则对元素进行排序并存储
    TreeSet<E> 集合名 = new TreeSet<>();  //E类型的元素一定要提供排序规则
增删改查四类功能 : 
    没有新增任何方法,按照Set集合的常规方法使用
遍历:
    没有新的遍历方法,按照Set集合的常规方法使用
    
常见类型的默认排序规则 : 
    1. Integer : 自然升序
    2. String : ASCII码字的升序进行排序
        a. 汉字"无规律" -> 不知道每一个汉字的码表值
        b. 字符串中越靠前的字符优先级越高 

Comparable<E> 绑定比较器

Comparable<E> 绑定比较器 -> 接口
    抽象方法 : int compareTo(E e)
        
绑定比较器的使用步骤 :
    1. 让元素类型去实现 Comparable<E> 绑定比较器 //哪个类型实现比较器接口,比较器的泛型就是那个类型
    2. 重写 int compareTo(E e) 方法, 方法内的逻辑就是排序规则
    
排序规则 :
    升序 : this - o
    降序 : o - this 

Comparator<E> 独立比较器

Comparable<E> 绑定比较器
    抽象方法 : int compareTo(E o) //this - o 升序  o - this 降序

Comparator<E> 独立比较器 -> 接口 (级别 : 比绑定比较器Comparable高)
    抽象方法 : int compare(E o1,E o2)  //o1 : this (要添加的元素) o2 : o (老元素)
    排序规则 :
        升序 : o1 - o2
        降序 : o2 - o1
        
如何使用独立比较器 :
    场景例如 : TreeSet(Comparator<E> comparator) 

其他排序功能

数组的排序:
    Arrays 工具类中有 sort(数组对象) : 
        1. 数组对象中存储的是基本数据类型 : sort 的原理 -> 快速排序算法
        2. 数组对象中存储的是引用数据类型 : 
                a . sort 就需要元素的类提供排序规则
                b . static <T> void sort(T[] a, Comparator<T> c) 
 
List集合排序:
    Collections 单列集合操作的工具类型 
        1. sort(List<E> list)
        2. <T> void sort(List<T> list, Comparator<T> c) 

斗地主案例

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Demo {
    public static void main(String[] args) {
        //准备牌
        String[] flowers = {"♠","♥","♣","♦"};
        String[] numbers = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};

        ArrayList<String> box = new ArrayList<>();//准备牌盒

        for (String flower : flowers) {
            for (String number : numbers) {
                String poke = flower + number;
                box.add(poke);
            }
        }
         //System.out.println("box = " + box);

        //添加大小王
        box.add("joker");
        box.add("JOKER");
        //System.out.println("box = " + box);

        //洗牌
        Collections.shuffle(box);
        //System.out.println("box = " + box);

        //发牌
        //准备斗地主的人
        ArrayList<String> 周润发 = new ArrayList<>();
        ArrayList<String> 周星驰 = new ArrayList<>();
        ArrayList<String> 刘德华 = new ArrayList<>();
        ArrayList<String> 地主牌 = new ArrayList<>();

        //拿出地主牌
        地主牌.add(box.get(box.size() - 1));
        地主牌.add(box.get(box.size() - 2));
        地主牌.add(box.get(box.size() - 3));
        //System.out.println("地主牌 = " + 地主牌);

        //发牌
        for (int i = 0; i < box.size() - 3; i++) {
            if (i % 3 == 0){
                周润发.add(box.get(i));
            }else if (i % 3 == 1){
                周星驰.add(box.get(i));
            }else {
                刘德华.add(box.get(i));
            }
        }

        //理牌
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list,"3","4","5","6","7","8","9","1","J","Q","K","A","2","o","O");

        Comparator rule =new Comparator<String>(){

            @Override
            public int compare(String o1, String o2) {
                char c1 = o1.charAt(1);
                char c2 = o2.charAt(1);

                int i1 = list.indexOf(String.valueOf(c1));
                int i2 = list.indexOf(String.valueOf(c2));

                return i1 - i2;
            }
        };

        Collections.sort(周润发,rule);
        Collections.sort(周星驰,rule);
        Collections.sort(刘德华,rule);
        Collections.sort(地主牌,rule);

        //看牌
        System.out.println("周润发 = " + 周润发);
        System.out.println("周星驰 = " + 周星驰);
        System.out.println("刘德华 = " + 刘德华);
        System.out.println("地主牌 = " + 地主牌);



    }
}

运行结果:

Collections

Collections : 操作单列集合的工具类型

静态方法 :

void sort(List<E> e) : 按照E类型元素提供的默认排序规则对List集合中的元素进行排序
void sort(List<E> e,Comparator<E> comparator) : 按照独立比较器提供的排序规则对List集合中的元素进行排序
void shuffle(List<E> e) 随机打乱List集合中的元素顺序
void addAll(Collection<E> collection , E ... element): 批量添加
    
int binarySearch(List<? extends Comparable<? super T>> list, T key) : 利用二分查找法查找list集合中的key元素的索引位置 
    //先对List集合中的元素进行排序,排完序后再用二分查找法进行元素的查找
     注意 : 如果List集合的元素类型没有提供排序规则,此方法调用报错!
int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)   : 利用二分查找法查找list集合中的key元素的索引位置   
   注意 : 如果List集合的元素类型没有提供排序规则,会按照第三个参数独立比较器提供的规则对元素先排序后查找!
       
void copy(List<? super T> dest, List<? extends T> src)  : 把src集合中的所有元素复制到dest集合中
void fill(List<? super T> list, T obj)  : 把obj对象填充到list集合中
    
T max(Collection<T> coll)  : 集合元素求最值 //要求T类型的元素必须提供排序规则
T max(Collection<T> coll, Comparator<T> comp) :  : 集合元素求最值 
    
void reverse(List<?> list) : 翻转集合中的内容
    
void swap(List<?> list, int i, int j) :交换list集合中i索引和j索引位置的元素

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值