java之集合

142 阅读9分钟

layout: post title: "java之集合" subtitle: " "java集合类"" date: 2018-10-03 06:00:00 author: "青乡" header-img: "img/post-bg-2015.jpg" catalog: true tags: - DataStructure&Algorithm

是什么

本质上是一个容器,用于放数据,存放数据的容器。

是一个数据结构,专门用于存放数据的。

内存里的数据结构,存放的是内存的数据。

作用

有2个,
1.往里面写数据
2.读里面的数据

最常用的ArrayList

2个主要操作,
1.写数据
首先看一下,我们平时最常用的往集合里写数据,而且一个接一个的写入数据,是按顺序的,即每次的数据都在写到集合的最后一个数据的后面,这样就不用移动任何数据,因为不是写到中间的位置,所以不用移动任何数据。

2.读数据
读的时候,是根据索引去读的,所有的集合数据结构当中,根据索引读数据的数据结构,速度是最快的。


底层原理
本质上是一个包装了各种数据类型的数组的类。

因为基于索引的数据结构,底层都是数组,封装了一下而已,所以数组索引是读最快的。

源码

如果需要插入数据和删除数据,从中间插入和删除,就使用链表集合LinkedList

优点
1.更新数据速度最快
方便插入和删除。因为不需要移动数据。只需要改变指针。

缺点 1.读数据很慢 因为索引是最快的,链表集合没有索引,需要遍历数据。虽然读的时候也是通过索引,但是本质上是遍历数据,因为链表结构只能从头结点移动指针指向索引指向的数据。

只能顺序查找。


源码

//ArrayList源码-读数据

E elementData(**int** index) {



        **return** (E) elementData[index]; //虚拟机支持数组和数组索引这种数据库结构。



    }

//LinkedList源码-读数据


/**



     * Returns the (non-null) Node at the specified element index.



     */



    Node<E> node(**int** index) {



        // assert isElementIndex(index);



        **if** (index < (size >> 1)) {



            Node<E> x = first;



            **for** (**int** i = 0; i < index; i++) //遍历数据 



                x = x.next; //作用是从第一个节点开始移动指针,直到到达索引指向的位置的节点



            **return** x;



        } **else** {



            Node<E> x = last;



            **for** (**int** i = size - 1; i > index; i--)



                x = x.prev;



            **return** x;



        }



    }


底层原理
双链表数据结构。

数据线程安全Vector

和数组集合一样。
唯一的不同,就是方法加了同步关键字,确保数据安全。

数据不能重复Set

不允许重复数据。

数据排序

List没有有排序的集合类。//List接口有sort()方法,所以实现类ArrayList和LinkedList都有实现sort()方法。

Set有,TreeSet extends SortedSet。//封装了TreeMap,本质上和TreeMap一样。 Map也有排序,TreeMap entends SortedMap。


源码实现?哪一种排序算法?

1.ArrayList

private static void mergeSort(Object[] src,
                                  Object[] dest,
                                  int low,
                                  int high,
                                  int off) { //jdk早期就是使用归并排序这种算法,后来版本有优化
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) { //1.递归的结束条件:这里是还剩下几个数据的时候结束递归调用(剩下的几个数据使用插入排序算法进行排序),其实也可以在只剩下1个数据的时候结束递归调用(只剩下一个数据,所以是有序的,无需排序,直接return)
            for (int i=low; i<high; i++)
                for (int j=i; j>low &&
                         ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }

        // Recursively sort halves of dest into src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off); //2.一直递归调用方法自己
        mergeSort(dest, src, mid, high, -off);

        //3.归并排序算法
        // If list is already sorted, just copy from src to dest.  This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }

        // Merge sorted halves (now in src) into dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }

2.TreeMap

public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) { //插入第一个元素
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        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 { //默认比较器(key的默认比较器,比如String实现了自己的compare()方法 )
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                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;
    }

是否允许为null

List/Set/Map,都有是否允许为null的类。

Set和List的区别?

除了是否允许重复数据以外,还有其他区别吗?底层实现是一样的吗?

如果是一样,那为什么不是ArraySet,LinkedSet?同步类是哪一个?所以底层实现肯定是不一样的。看名字就不知道不一样。

Set各种实现类的区别?

1.排序集合TreeSet
确保数据有序。

怎么确保集合数据不重复?封装了TreeMap。和任何Map一样,TreeMap可以确保数据不重复,而且是数据有序。

怎么确保数据有序?封装了TreeMap。

TreeMap的实现原理也是通过比较,一个数据一个数据的比较。所以速度很慢。

如果不需要数据有序的要求,不要使用排序集合,因为操作数据的速度很慢。

2.HashSet
输入:键
输出:哈希值 //作用:作为HashMap的键的索引

算法:哈希函数


底层原理
使用了哈希函数。
具体来说,是使用了HashMap,封装了HashMap。

集合的元素就是键,值是一个常量数据。

// Dummy value to associate with an Object in the backing Map

    private static final Object PRESENT = new Object();

//jdk源码-Hashtable

 **private** **void** addEntry(**int** hash, K key, V value, **int** index) {



        modCount++;



        Entry<?,?> tab[] = table;



        **if** (count >= threshold) {



            // Rehash the table if the threshold is exceeded



            rehash();



            tab = table;



            hash = key.hashCode();



            index = (hash & 0x7FFFFFFF) % tab.length;



        }



        // Creates the new entry.



        @SuppressWarnings("unchecked")



        Entry<K,V> e = (Entry<K,V>) tab[index];



        tab[index] = **new** Entry<>(hash, key, value, e);



        count++;



    }

使用哈希函数的好处是什么?或者说,封装HashMap的好处是什么?

HashMap键不能重复。所以,HashSet可以确保集合元素不重复。

HashMap为什么可以保证key不重复?看下面的代码。

//key相同会发生什么?1.相同key只插入一次 2.如果key相同,后面的value会覆盖全面的value
		Map<String,String> m = new HashMap<String,String>();
		m.put("a", "1");
		m.put("b", "2");
		m.put("a", "2");
		
		System.out.println(m); //输出{a=2, b=2}

因为插入数据的时候,会判断,a[key的hash]是否有值,如果没有值,就插入;如果有值,就继续判断是否value相同,value不同覆盖前面的值,值相同就不插入。

//jdk源码
if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

3.LinkedHashSet 与HashSet的区别?与HashSet的唯一区别就是,可以保证插入顺序。但是速度更快,因为要额外维护插入顺序。

实现原理就是双链表。类的名字反应了类的作用。

各自的应用场景?


怎么确保同步?
Collections.同步集合(集合类型);

任何一种不能同步的集合类(Collection/Set/List/Map),都可以通过这种方法得到一个同步的集合类。

具体的代码实现是,1.原始集合 2同步对象 ,1和2的组合,实现同步。

public static <T> Set<T> synchronizedSet(Set<T> s) { //得到一个同步集合类
        return new SynchronizedSet<>(s); 
    }
    

/**
     * @serial include
     */
    static class SynchronizedCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 3053995032091335093L;

        final Collection<E> c;  // Backing Collection
        final Object mutex;     // Object on which to synchronize //同步对象

        SynchronizedCollection(Collection<E> c) {
            this.c = Objects.requireNonNull(c);
            mutex = this;
        }

        SynchronizedCollection(Collection<E> c, Object mutex) {
            this.c = Objects.requireNonNull(c);
            this.mutex = Objects.requireNonNull(mutex);
        }

        public int size() {
            synchronized (mutex) {return c.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return c.isEmpty();}
        }
        public boolean contains(Object o) {
            synchronized (mutex) {return c.contains(o);}
        }
        public Object[] toArray() {
            synchronized (mutex) {return c.toArray();}
        }
        public <T> T[] toArray(T[] a) {
            synchronized (mutex) {return c.toArray(a);}
        }

        public Iterator<E> iterator() {
            return c.iterator(); // Must be manually synched by user!
        }

        public boolean add(E e) { //同步方法
            synchronized (mutex) {return c.add(e);} //同步代码块
        }
        public boolean remove(Object o) {
            synchronized (mutex) {return c.remove(o);}
        }

是否插入顺序

1.List ArrayList
是按索引,一个一个的插入,每次插入到最后。所以是插入顺序。

LinkedList 每次也是插入到最后。所以也是插入顺序。


总结
只要是每次插入到最后就是插入顺序。List都是插入顺序。

那什么情况不是插入顺序?用到了Hash就不是插入顺序。

最佳实践

1.数据重复List
数组ArrayList //读最快

双链表LinkedList //更新数据最快

插入数据有序 //所有集合类型都是插入有序的,只有映射才不是插入有序的,因为使用了哈希函数。

同步Vector //同步

数据有序TreeList //jdk没有。
1.为什么没有?

2.那怎么排序?
jdk Collections提供了排序方法。

static <T extends Comparable<? super T>> void	sort(List<T> list)
Sorts the specified list into ascending order, according to the natural ordering of its elements.


static <T> void	sort(List<T> list, Comparator<? super T> c)
Sorts the specified list according to the order induced by the specified comparator.

Collections类还提供了Set和Map的排序方法,虽然Set和Map都有自己的排序类TreetSet(底层也是封装的TreeMap)和TreeMap,但是TreeMap不同步。所以Collections类提供了同步排序方法。

static <K,V> SortedMap<K,V>	synchronizedSortedMap(SortedMap<K,V> m)
Returns a synchronized (thread-safe) sorted map backed by the specified sorted map.


static <T> SortedSet<T>	synchronizedSortedSet(SortedSet<T> s)
Returns a synchronized (thread-safe) sorted set backed by the specified sorted set.

dzone.com/articles/so…

2.数据不重复Set
1)共同点,就是首先要确保数据不重复
都封装了HashMap。映射可以确保键数据不重复。

2)类
HashSet //最基本的数据不重复

插入有序LinkedHashSet //1.数据不重复 2.确保插入顺序

数据有序TreeSet //1.数据不重复,实现原理是Map 2.数据有序,实现原理是TreeMap