集合的整理

放一张集合关系图

1、开始

java集合 就像个容器 把多个对象丢进去(所以会有人称这个为java的容器)  存在于java.util包下 总的来说由Collection 进行延伸的  也就是根接口

2、Collection接口

继承Iterable接口

注意:Iterable(java.lang) 和  terator(java.util)区别

Iterable是接口  terator是迭代器  实现Iterable接口  才可以调用Iterator方法 对象才可以使用foreach

Iterable 接口在java.lang包下  这个接口下有Iterator<T> iterator();方法  

          Iterator 是迭代器  迭代器有两个核心的方法  boolean hasNext()和E next()  可以用这个遍历ArrayList  Set  LinkedList

List<String> str=new ArrayList<>();
str.add("aaa");
str.add("bbbb");
Iterator<String> it=str.iterator();
while(it.hasNext()){
System.err.println("需要先next 然后再remove 否则会报错 java.lang.IllegalStateException==="+it.next());
it.remove();

Iterator   迭代器  可以一边遍历一边进行删除操作

Iterable 可以调用Iterator()方法 实现功能返回迭代器Iterator  另外还有增强for循环的方法(forEach(Consumer<? super T> action))

        为啥Collection不直接实现Iterator而是先实现Iterable

       Iterator接口的核心方法依赖于迭代器当前迭代位置,如果Collection直接实现Iterator接口 会导致集合对象中包含当前迭代位置的数据  当集合被不同方法使用时  迭代位置就不能预置

每一次调用Iterable的Iterator()方法,都会返回一个从头开始的Iterator对象,各个Iterator对象之间不会相互干扰,这样保证了可以同时对一个数据结构进行多个遍历。这是因为每个循环都是用了独立的迭代器Iterator对象。(为啥collection继承Iterable而不是Iterator)

          继承Collection接口 的有三个:List Set Queue   里面有

3、List  集合 

有序的容器 保持每个元素的插入顺序 可以允许重复的对象  可以插入多个null元素  常用的实现类有ArrayList LinkedList Vector

关键是查看源代码

1)ArrayList  

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

动态数组 可以快速查询  数组型的集合 有索引查询比较快 但是删除后需要重新排序  所以速度比较慢(看下面源码 )

查询很快的原因(通过下标进行读取)

@SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

    /**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

  移除很慢的原因 (删除之后需要排序)

 public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
 private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
//这里用了这个方法  也就是删除某个位置元素  后面的往前复制
//public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
        elementData[--size] = null; // clear to let GC do its work
    }

ArrayList插入之前需要判断容量  进行扩容处理   如果没有超过容量(初始容量为10) 增加数据  ArrayList速度还是很快的  超过容量  涉及到 大量数据的复制过程   所以速度会很慢

ArrayList 是非同步的  如果多个线程  在同一个位置对它进行操作   会容易出错  线程不安全

2)LinkedList

基于链表实现的 实现了Deque接口   可以被当做堆栈 队列或者双向队列使用 可以快速插入和删除  也是线程不安全的

重要的是节点的操作

 public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

3)Vector

当集合中有多线程对集合元素进行操作时 用这个   但是现在一般使用CopyOnWriteArrayList 

有synchronized 修饰  可以同步线程安全 允许元素为null

4、Set集合

 无序  不可重复   只能允许最多为一个null

5、Map集合

键值对形式存储 key可以为null  最多一个

Set集合和Map集合放一起  基本上都会对这两个进行对比

1)HashSet和HashMap

a、HashSet  实现Set接口  继承AbstractSet 抽象类

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

b、HashMap实现Map接口  继承AbstractMap抽象类

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

HashSet 存储对象之前重写hashCode()和equals()方法  目的是去重  

为啥重写equals()还要重写hashCode()  重写了equals()保证属性值相等,如果不重写hashCode()  就可能导致  对象不一样  两个对象的内存值   还是不能去重

c、HashSet源码里面创建了一个HashMap类型的map对象 可以说是一个特殊的HashMap  添加对象用的是add()方法   没有add(int c ,E e)这个方法  因为HashSet没有下标  从源码看  底层实现是map.pu()方法,元素存放在map集合的Key里面  这也就是为啥不能重复  只允许一个null     HashMap 使用的是 public V put(K key, V value)

public HashSet() {
        map = new HashMap<>();
    }
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

2)HashTable和HashMap

a、HashTable 继承Directionary抽象类  实现Map接口   HashMap实现Map接口  继承AbstractMap抽象类  HashTable 有Contains()方法  HashMap  去掉了 改成了containsvalue()和containsKey()

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable

b、HashTable不允许为null指  看源码里面有判断  HashMap可以接受null值 但是只允许一个

//HashTable
public synchronized V put(K key, V value) {
        // Make sure the value is not null  确保value不为null  否则抛异常  
        //在contains(Object value)方法里面也有这个判断
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

c、HashTable 线程安全  方法基本上都是synchronized进行修饰 HashMap线程不安全,使用的时候需要调用Collection类里面的synchronizedMap()静态方法 

//这是Collection类里面关于Map加锁的静态方法
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }

d、迭代器   HashMap的迭代器是fail-fast迭代器  而HashTable 的Enumerator 迭代器则不是  

final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)//modCount 修改次数  对hashMap修改一次 增加一次
//迭代器初始化过程中会将modCount 赋值给迭代器的expectedModCount
                throw new ConcurrentModificationException();//亮着不相等  别的线程修改了会抛异常
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

 

e、初始容量和填充因子(见下面代码) 增加方式:HashTable  old*2+1   HashTable  是2的指数

  /**
     * Constructs a new, empty hashtable with a default initial capacity (11)
     * and load factor (0.75).
     */
    public Hashtable() {
        this(11, 0.75f);//HashTable初始容量为11 填充因子0.75
    }
//HashMap 初始容量16  填充因子0.75
 /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

 

f、HashTable 直接使用对象的hashcode   HashMap 重新计算hashcode(这个涉及到数据结构的内容  后期再完善修改)

public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();//不能存null值
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();//先获取放进去的元素的key的hashcode
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
//迭代寻找key  找到就替换
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);//这里就是添加  如果容量不够就进行扩容 具体代码后面分析
        return null;
    }
//HashMap
 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
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);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//红黑树节点 采用TreeNode<K,V>进行实现  JDK 1.8 的优化目的主要是:减少 Hash冲突 & 提高哈希表的存、取效率
            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
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();//扩容  后面和hashTable的addEntry()方法进行分析
        afterNodeInsertion(evict);
        return null;
    }


/**
     * HashTable  数据存储
     */
    private transient Entry<?,?>[] table;

扩容问题

3)TreeSet和TreeMap

6、Queue 队列集

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值