关于集合的初步总结

1 集合的分类

集合主要分为单列和双列集合:

1 单列的顶层父类接口Collection,子类分为接口List和接口Set
  • a. List :ArrayList, LinkedList, Vector
  • b. Set: HashSet, LinkedHashSet
单列集合的区别

List有序且可重复而Set无序且不可重复,但LinkedHashSet是有序的,按照装入数据的顺序迭代
其中ArrayList与Vector底层是数组实现,而LinkedList是链表实现,而HashSet则是HashTable实现
如果需要查阅,ArrayList速度更快(有角标),而当需要增删,则LinkedList更快(只需要修改节点,不需要扩容)

2 双列集合的顶层父类接口为Map

所有Map类都是双列,即储存键值对的集合,key不可重复,且没有顺序

  • a HashMap 与 HashTable .
  • b LinkedHashMap
双列集合的区别

HashMap与HashTable的主要区别:HashMap线程不安全,但是效率高而HashTable线程安全,效率低(线程与安全的问题涉及到锁,即同步与异步的问题)
LinkedHashMap是HashMap的子类,但是他是有序的,也是按照装入数据的顺序迭代

2 集合的遍历

(1)有角标集合的遍历

ArrayList与Vector这种有角标的有序集合有三种遍历形式,即for循环,for each循环与迭代器

for-each
public static void m1() {
    	  ArrayList<Integer> arrayList =new ArrayList<>();
          arrayList.add(1);
          arrayList.add(1);
          arrayList.add(2);
          arrayList.add(4);
          arrayList.forEach((x)->System.out.println(x));
          }
for 循环
public static void m1() {
    	  ArrayList<Integer> arrayList =new ArrayList<>();
          arrayList.add(1);
          arrayList.add(1);
          arrayList.add(2);
          arrayList.add(4);
          for(int i=0; i<arrayList.size(); i++){
                   System.out.println(arrayList.get(i));
                 }
          }
迭代器(见下面)

(2)无序集合的遍历

Map与Set的子类都是无序集合,所以普通for循环无法遍历,只能用for each与迭代器,其中for each与有序集合操作一致

迭代器循环
public static void m2() throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		HashMap<Integer, String> hashMap =new HashMap<Integer, String>();
		hashMap.put(12, "sad");
		hashMap.put(13, "sad2");
		hashMap.put(124, "s21ad");
		Set<Integer> enSet =hashMap.keySet();
		Iterator<Integer> iterator =enSet.iterator();
		while (iterator.hasNext()) {
			Integer integer =iterator.next();
			System.out.println(hashMap.get(24));
			System.out.println(hashMap.get(integer));
		}

迭代器循环需注意:
1 迭代器的next()方法用一次边移动到了下一个元素,所以一次循环只能使用一次next()方法
2 一个迭代器只能使用一次,使用完后迭代器中便没有元素了,要再使用必须重新创建迭代器
3 HashMap和HashTable本身都没有迭代器,是使用了entrySet()或者KeySet()方法得到了Set集合,再使用Set中的迭代器来遍历的

3 集合的常见方法啊

(1)增加元素
单列集合用add(),双列集合用put(),其中有角标的可以使用add(index int ,Object obj),在指定位置插入。
(2)删除元素
使用remove()方法,与add类似,需注意的是如果是遍历删除在有角标集合中的元素,for循环应从后往前删除或者使用迭代器删除,否则角标易错
(3)改变元素
单列使用Set方法,双列直接用put()解决
(4)查阅元素
单列用contains()方法,双列分为containsKey()与containsValue()

2019.9.18关于各种集合数据结构的补充

最近有空翻阅了一些关于各类集合数据结构的源代码及相关文章,对集合有了进一步的理解。
(1) 数组实现的数组:ArrayList及Vector
这两个集合都是动态数组实现的,在源代码中都是创建了一个object[],来实现数据的增减。由于Vector源代码中add以及get方法全都使用了sychronized锁住,所以是线程同步,速度较慢,而ArrayList则没有,所以线程不安全但速度快。
(2)链表实现:LinkedList
LinkedList底层是链表,而且是由双向链表实现
(3)数组+链表:hashset,hashmap,hashtable
hashSet的底层数据结构就是hashMap,所以三者的数据结构一样,都是数组加链表,只不过hashSet的数据是储存在hashMap中的key值里的,而value是一个空的object,因为key值不能重复,所以hashSet不能重复
关于hashMap的数据结构,可以理解为底层是一个数组,数组的每一个单位储存的是链表,即一个Entry<K,V>键值对。在使用put方法时,其实首先是调用key.hashcode()得到hashcode返回值,以此得到索引确定数组储存的位置,此时再使用equal方法比较key,如果Key相同则直接覆盖value,但是不同的key值也有可能生成同样的hashcode值,即hash冲突,所以在每一个数组的位置上,如果发生了此类情况,即后来的链表成为插入之前储存在此的链表,成树形增长。而具体调用时,所以用get()方法时,会先匹配hashcode值,然后还要用equal()方法比较key值,才能取得value。
在这里插入图片描述
另外,为了尽量让链表的树能均匀分布,而不是在某一个索引上集中,所以hashMap中的Hash算法是 index = key.hashcode() & (hashMap.length-1)
也就是key对数组长度的取模运算,这个运算只有当hashmap的长度为2的n次方时,上述取模运算结果才会达到分布均匀的效果,这也就是为什么hashmap的自动扩容和初始化时,长度永远是2的指数倍,而hashmap中,capacity对应的是桶的容量,也就是数组长度,而不是元素个数,元素个数对应的是映射数量,这也就是为什么hashmap元素数量使用的是size()而不是length。

2019.9.27关于hashmap中hash算法的补充

在这里在插在入图片描述
这是HashMap中的关于key的hash算法,他使用了object.hashcode算法然后还要与自己进行了16位无符号右移后的值进行按位异运算
因为hashcode算法得到的值是32位的,其变化其实主要在高位,但是实际hashmap中数组的长度是远远低于这个数,如果直接在后面的index运算中按位与运算

static int indexFor(int h, int length) {
    return h & (length-1);
}

那么因为低位的重复率高,导致整个Index的重复率很高,降低了hashmap效率
而当他使用自己对自己的右移运算按位异运算,等于是高位和低位的异运算,其实是把高位的影响传到了低位,从而大大降低了Index计算的重复率

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        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);
            //如果数组中对应index上没有元素,直接储存
        else {
        //走到此代码块,即已经发生了hash碰撞,这个时候p是hash碰撞的节点
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
                //index相同,hash值相同,key值相同,则先将e赋值为p,记住这个节点,并将K赋值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);
                        //index相同,key相同,碰撞节点后面没有.next的,直接头前插入
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        //TREEIFY_THRESHOLD是等于8的,超过8会变成红黑树节点
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                    //找了key完全相等的链表,直接新节点替换旧的
                }
                 if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
                //key相同,value新的覆盖旧的
            }
            }

从代码来分析JDK1.8以后链表插入变成了尾插!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值