JAVA经常使用数据结构及原理分析

前不久面试官让我说一下怎么理解java数据结构框架,之前也看过部分源代码,balabala讲了一堆,如今总结一下。

java.util包中三个重要的接口及特点:List(列表)、Set(保证集合中元素唯一)、Map(维护多个key-value键值对,保证key唯一)。其不同子类的实现各有差异,如是否同步(线程安全)、是否有序。


经常使用类继承树:
这里写图片描写叙述

下面结合源代码解说经常使用类实现原理及相互之间的差异。

  • Collection (全部集合类的接口)
    List、Set都继承自Collection接口。查看JDK API,操作集合经常使用的方法大部分在该接口中定义了。
    这里写图片描写叙述

    • Collections (操作集合的工具类)
      对于集合类的操作不得不提到工具类Collections,它提供了很多方便的方法,如求两个集合的差集、并集、拷贝、排序等等。
      由于大部分的集合接口实现类都是不同步的。能够使用Collections.synchronized*方法创建同步的集合类对象。
      如创建一个同步的List:
      List synList = Collections.synchronizedList(new ArrayList());
      事实上现原理就是又一次封装new出来的对象,操作对象时用关键字synchronized同步。

      看源代码非常easy理解。


      Collections部分源代码:

      //Collections.synchronizedList返回的是静态类SynchronizedCollection的实例,终于将new出来的ArrayList对象赋值给了Collection<E> c。
      static class SynchronizedCollection<E> implements Collection<E>, Serializable {
      
              final Collection<E> c;  // Backing Collection
              final Object mutex;     // Object on which to synchronize
      
              SynchronizedCollection(Collection<E> c) {
                  if (c==null)
                      throw new NullPointerException();
                  this.c = c;
                  mutex = this;
              }
              //...
              public boolean add(E e) {
                  //操作集合时简单调用原本的ArrayList对象,仅仅是做了同步
                  synchronized (mutex) {return c.add(e);}
              }
              //...
      }
    • List (列表)

      • ArrayList、Vector是线性表。使用Object数组作为容器去存储数据的,加入了非常多方法维护这个数组。使其容量能够动态增长。极大地提升了开发效率。它们明显的差别是ArrayList是非同步的。Vector是同步的。不用考虑多线程时应使用ArrayList来提升效率。


        ArrayList、Vector 部分源代码:

        //ArrayList.add
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            //能够看出加入的对象放到elementData数组中去了
            elementData[size++] = e;
            return true;
        }
        //ArrayList.remove
        public E remove(int index) {
            rangeCheck(index);
        
            modCount++;
            E oldValue = elementData(index);
        
            int numMoved = size - index - 1;
            if (numMoved > 0)
                //移除元素时数组产生的空位由System.arraycopy方法将其后的全部元素往前移一位,System.arraycopy调用虚拟机提供的本地方法来提升效率
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // Let gc do its work
        
            return oldValue;
        }
        
        //Vector add方法上多了synchronized关键字
        public synchronized boolean add(E e) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = e;
            return true;
        }
      • LinkedList是链表,略懂数据结构就知道事实上现原理了。链表随机位置插入、删除数据时比线性表快,遍历比线性表慢。


        双向链表原理图:
        这里写图片描写叙述
        LinkedList部分源代码:

        //源代码非常清晰地表达了原理图
        public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
        {
            //头尾节点
            transient Node<E> first;
            transient Node<E> last;
        }
        //节点类
         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;
            }
        }

    由此可依据实际情况来选择使用ArrayList(非同步、非频繁删除时选择)、Vector(需同步时选择)、LinkedList(频繁在任何位置插入、删除时选择)。

  • Map(存储键值对。key唯一)

    • HashMap结构的实现原理是将put进来的key-value封装成一个Entry对象存储到一个Entry数组中,位置(数组下标)由key的哈希值与数组长度计算而来。假设数组当前下标已有值,则将数组当前下标的值指向新加入的Entry对象。
      有点晕。看图吧:
      这里写图片描写叙述
      看完图再看源代码。非常清晰。都不须要凝视。

      public class HashMap<K,V>
      extends AbstractMap<K,V>
      implements Map<K,V>, Cloneable, Serializable
      {
          transient Entry<K,V>[] table;
          public V put(K key, V value) {
              if (key == null)
                  return putForNullKey(value);
              int hash = hash(key);
              int i = indexFor(hash, table.length);
              //遍历当前下标的Entry对象链,假设key已存在则替换
              for (Entry<K,V> e = table[i]; e != null; e = e.next) {
              Object k;
              if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                  V oldValue = e.value;
                  e.value = value;
                  e.recordAccess(this);
                  return oldValue;
              }
          }
             addEntry(hash, key, value, i);
              return null;
          }
      }
      static class Entry<K,V> implements Map.Entry<K,V> {
          final K key;
          V value;
          Entry<K,V> next;
          int hash;
      }
    • TreeMap是由Entry对象为节点组成的一颗红黑树,put到TreeMap的数据默认按key的自然顺序排序,new TreeMap时传入Comparator自己定义排序。红黑树网上非常多资料,我讲不清,这里就不介绍了。

  • Set(保证容器内元素唯一性)
    之所以先讲Map是由于Set结构事实上就是维护一个Map来存储数据的。利用Map结构key值唯一性
    HashSet部分源代码:

    public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
    {    
    
        //无意义对象来作为Map的value 
        private static final Object PRESENT = new Object();
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
    }

    HashSet、TreeSet分别默认维护一个HashMap、TreeMap。

posted on 2016-02-25 12:25  gcczhongduan  阅读(155)  评论(0编辑  收藏  举报