Java集合之双列集合

双列集合特点

  • 双列集合一次需要添加一对数据,分别是键和值
  • 键不能重复,值可以重复
  • 键和值是一一对应的,每一个键只能找到自己对应的值
  • 键 + 值这个整体称为“键值对”或者“键值对对象”,Java中叫“Entry对象”

双列集合的体系结构

 Map的常见API

  • V put(K key,V Value):添加元素【如果键存在,会把原有的键值对覆盖,并返回被覆盖的键值对的值,如果不存在,返回null】
  • V remove(Object key):根据键删除键值对,并返回被删除的键值对的值
  • void clear():清空所有的键值对
  • boolean containsKey(Object key):判断集合是否包含指定的键【键存在:true 不存在:false】
  • boolean containsValue(Object key):判断集合是否包含指定的值【键存在:true 不存在:false】
  • boolean isEmpty():判断集合是否为空
  • int size():集合的长度,集合中键值对的个数

Map集合的遍历方式

通过键找值

        Set<String> keys = map.keySet();
        for (String key : keys) {
            System.out.println(map.get( key ));
        }

键值对遍历

        Set<Map.Entry<String, String>> set = map.entrySet();
        for (Map.Entry<String, String> entry : set) {
            System.out.println(entry.getKey()+":"+entry.getValue());
        }

Lambda表达式遍历

        map.forEach( (key,value)-> System.out.println(key+":"+value) );

HashMap

  • HashMap是Map下面的实现类
  • 直接使用Map里面的方法即可
  • 键无序【存和取得顺序】、不重复、无索引
  • HashMap根HashSet底层原理一样,都是哈希表结构,唯一不同的是,会创建Entry对象,通过键计算哈希值,如果计算出来的哈希值是一样的,新的Entry对象会覆盖原有的Entry对象,其他的根HashSet一样的
  • 依赖hashCode方法和equals方法保证键的唯一
  • 如果键存储的是自定义对象,需重写hashCode方法和equals方法,如果值存储的自定义对象,就不需要重写hashCode方法和equals方法

LinkedHashMap

  • 由键决定:有序【存和取的顺序】、不重复、无索引
  • 原理:底层数据结构是哈希表,只是每个键值对额外多一个双向链表机制记录存储的顺序

TreeMap

  • TreeMap和TreeSet底层原理一样,都是红黑树结构
  • 由键决定:不重复、无索引、可排序【对键进行排序】
  • 默认按照键的从小到大进行排序,也可以自定义规则排序

两种排序方式

  • 实现Comparable接口,指定比较规则
  • 创建集合时传递Comparator比较器对象,指定比较规则

 HashMap源码分析

        Map<String,String> map = new HashMap<>();
        map.put( "ab","ba" );
        map.put( "ac","ca" );
        map.put( "ad","da" );
        map.put( "ae","ea" );
    public V put(K key, V value) {//key是键,value是值
        //如果覆盖了原来的键值对,就返回被覆盖的键值对的值,没有覆盖就返回null
        return putVal(hash(key), key, value, false, true);
    }
    //利用键计算对应的哈希值,在把计算出的哈希值做一些额外的处理
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
//hash:键的哈希值 key:键 value:值 onlyIfAbsent:如果键重复了是否保留【true:保留 false:不保留】
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
        Node<K,V>[] tab;//定义局部变量用来记录哈希表中数组的地址值
         Node<K,V> p;//临时的变量,用于记录键值对对象的地址值
         int n, i;//n:数组的长度 i:索引
        //tab = table将哈希表中数组的地址值赋值给局部变量tab
        if ((tab = table) == null || (n = tab.length) == 0)
            //1.如果是第一次添加元素,底层会创建一个默认长度16,加载因子0.75的数组
            //2.如果不是第一次添加,会看数组中的元素是否到达需要扩容的条件,
            //如果没有达到,会做任何操作,
            //如果达到了,底层会将数组扩容到原来的2倍,并把数据全部转移到新的哈希表中
            //并把当前数组的长度赋值给n
            n = (tab = resize()).length;
        //用数组的长度和键的哈希值进行计算,计算出当前键值对对象,在数组中应存入的位置
        i = (n - 1) & hash]
        //获取数组中对应元素的数据
        p = tab[i]
        if (p == null)
            //底层会创建一个键值对对象,直接放到数组中
            tab[i] = newNode(hash, key, value, null);
        //不是第一次添加元素是就走else
        else {
            Node<K,V> e; K k;
            //数组中键值对的哈希值与当前需要添加键值对的哈希值
            boolean f = p.hash == hash;
            if (f && ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                //判断数组中获取出来的键值对是不是红黑树中的节点
                //如果是,则调用putTreeVal,把当前节点按照红黑树的规则添加到树中
                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);
                        //判断当前链表长度是否超过8,如果超过8,调用方法treeifyBin
                        //treeifyBin底层还会继续判断
                        //判断数组长度是否大于等于64
                        //如果同时满足这两个条件,就会把这个链表转成红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //如果哈希值相同,就会调用equals方法比较内部的属性值是否相同
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
             //覆盖值需要走此处的if语句
            //如果e为null,表示不需要覆盖任何元素
            //如果e不为null,表示当前的键之一样的,值就会被覆盖
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    //左边:当前要添加的值
                    //右边:e的value值
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //threshold:记录的是数组的长度 * 0.75,就是哈希表的扩容机制
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        //表示当前没有覆盖任何元素
        return null;
    }

TreeMap源码分析

//TreeMap集合的get方法源码分析
static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;//表示键
    V value;//表示值
    Entry<K,V> left;//表示左节点的地址
    Entry<K,V> right;//表示右节点的地址
    Entry<K,V> parent;//表示父节点的地址
    boolean color = BLACK;//表示当前节点的颜色
}
public V get(Object key) {
	//调用getEntry方法获取Entry对象
	Entry<K,V> p = getEntry(key);
	//判断对象是否为null,为null返回null,不是则返回对象中的值
	return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
	//判断有没有传入comparator
	if (comparator != null)
		//调用getEntryUsingComparator方法,使用比较器做查询
		return getEntryUsingComparator(key);
	//判断传入的键是否为null
	if (key == null)
		//如果查询的键为null,抛出空指针异常
		throw new NullPointerException();
	@SuppressWarnings("unchecked")
	//把Object类型的键向下转型为Comparable
	Comparable<? super K> k = (Comparable<? super K>) key;
	//先把二叉树的根节点赋值给p
	Entry<K,V> p = root;
	//如果p不为空,一直循环作比较
	while (p != null) {
		//调用compareTo方法进行比较
		int cmp = k.compareTo(p.key);
		//如果局部变量cmp小于0,表示要查询的键小于节点的数值
		if (cmp < 0)
			//把p的左节点赋值给p对象
			p = p.left;
		//如果局部变量cmp大于0,表示要查询的键大于节点的数值
		else if (cmp > 0)
			//把p的右节点赋值给p对象
			p = p.right;
		else
			//要查找的键等于节点的值,就把当前Entry对象直接返回
			return p;
	}
	//没有找到要查找的结点的值,就会返回null
	return null;
}
//传入比较器的情况下
final Entry<K,V> getEntryUsingComparator(Object key) {
	@SuppressWarnings("unchecked")
	//把Object类型的key向下转型为对应的键的类型
	K k = (K) key;
	//初始化比较器对象【起名字】
	Comparator<? super K> cpr = comparator;
	if (cpr != null) {
		//把二叉树的根节点赋值给p对象
		Entry<K,V> p = root;
		//循环用要查找的键的值和节点中键的值作比较
		while (p != null) {
			//调用比较器的compare方法
			int cmp = cpr.compare(k, p.key);
			if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
    }
    return null;
}


//put()添加方法源码分析
public V put(K key, V value) {
	//获取根节点赋值给t
    Entry<K,V> t = root;
	//判断根节点是否为空
    if (t == null) {
		//为空就对key进行非空和类型检验
        compare(key, key);
		//新建节点
        root = new Entry<>(key, value, null);
		//把集合的长度设置为1
        size = 1;
		//记录集合被修改的次数
        modCount++;
		//添加成功就返回null
        return null;
    }
	//如果根节点不为空则执行以下代码
    int cmp;
    Entry<K,V> parent;
	//把比较器对象赋值给cpr
    Comparator<? super K> cpr = comparator;
	//判断比较器对象为空则执行一下代码
    if (cpr != null) {
        do {
			//把当前节点赋值给变量parent
            parent = t;
			//比较当前节点的键和要存储的键的大小
            cmp = cpr.compare(key, t.key);
			//要存储的键小于当前节点的键的值,则继续和当前节点的左节点进行比较
            if (cmp < 0)
                t = t.left;
			//要存储的键大于当前节点的键的值,则继续和当前节点的右节点进行比较
            else if (cmp > 0)
                t = t.right;
			//要存储的键等于当前节点的键的值,则调用setValue方法设置新的值,并结束循环
            else
                return t.setValue(value);
        } while (t != null);//循环直到遍历到叶子节点结束循环
    }
	//如果比较器对象不为空,执行以下代码
    else {
		//如果要保存的键为空,判处空指针异常
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
			//把键转型为Comparable类型
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
			//把当前节点赋值给变量parent
            parent = t;
			//比较要存储的键和当前节点的键的值
            cmp = k.compareTo(t.key);
			//要存储的键小于当前节点的键的值,则继续和当前节点的左节点进行比较
            if (cmp < 0)
                t = t.left;
			//要存储的键大于当前节点的键的值,则继续和当前节点的右节点进行比较
            else if (cmp > 0)
                t = t.right;
			//要存储的键等于当前节点的键的值,则调用setValue方法设置新的值,并结束循环
            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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来一沓Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值