• 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。

  • 相关阅读:
    nginx 与 lua 开发笔记
    使用openresty + lua 搭建api 网关(一)安装openresty ,并添加lua模块
    Java使用POI接口读写Excel文件方法
    React常用的库安装
    Android手机捉包
    ffmpeg压缩视频
    用 TypeScript 写 React & Redux
    yarn使用
    Angular 组件引入其他css不生效
    Webstrom技巧
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/5216588.html
Copyright © 2020-2023  润新知