zoukankan      html  css  js  c++  java
  • java基础-集合

    以下纯属个人见解,如有不妥请及时指正以免误导他人!!!未完待续….

    1.java集合中常用的类都有哪些,简单介绍一下?
    参见6


    2.ArrayList和LinkedList的区别,它们的原理?
         ArrayList和LinkedList都是List系的实现类,但是两者的实现原理不同:
    ArrayList是在动态数组的基础上实现的,既然是数组,那么随机访问的性能就会快,但是插入或者删除数据就会比较慢,比方说:如果操作是在某一位置插入元素的话,首先会判断size+1,是否需要扩容,如果需要就要扩容!注意扩容是需要进行数组复制拷贝的,然后,对elementData进行操作,操作过程是System.arraycopy对于index索引位置原数组中此位置后的元素移到 index + 1后的位置,空出index索引下的数组位置放置插入的元素!需要进行扩容、数组拷贝,所以才存在大家说的随机访问快,但是插入或者删除等修改会慢;下面看一下它扩容的代码细节:

    private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容1.5倍
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);//并且将原数组数据进行复制到新的数组
        }

    所以,初始化的时候默认为空数组,当第一次add元素的时候会进行容量初始化,如果已知需要的集合大小,最好初始化的时候置入构造函数,可以提升性能,避免频繁扩容。

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
     public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    private void ensureCapacityInternal(int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                //初始化为DEFAULT_CAPACITY = 10
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    
            ensureExplicitCapacity(minCapacity);
        }

    LinkedList的实现则是基于链表,不仅实现了List而且也实现了Deque,所以是双端链表;它的基础在于它的私有内部类:

    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;
            }
        }

    这是LinkedList实现链表的关键,一个节点包含着本身存储的数据和前后继节点,所以它的随机访问是遍历链表来查找节点,相较于ArrayList的数组结构基于角标访问性能差一些;但是它的增删操作却性能相对高一些,毕竟它只需要更改链表中节点的数据以及相应的前后继节点的变动,而不需要像ArrayList那样挪动数组来实现。

    此外还需注意,ArrayList和LinkedList都是非线程安全的集合类。


    3.HashMap是什么数据结构,他是怎么实现的?Java8做了什么改进?

    //HashMap维护了一个Entry类型的数组,一个Entry就是一个key-value对象
    transient Entry[] table;

    而Entry又是HashMap中的静态内部类 ,由代码可知它又含有成员变量Entry next维系成链表结构,代码如下:

    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
    
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
    
        public final K getKey() {
            return key;
        }
    
        public final V getValue() {
            return value;
        }
    
        public final V setValue(V newValue) {
        V oldValue = value;
            value = newValue;
            return oldValue;
        }
    }

    所以HashMap的数据结构就是由数组+链表构成的。

    JDK8 新增了红黑树特性,参考下文:
    https://blog.csdn.net/u011240877/article/details/53358305


    4.Hashtable和HashMap的区别?
    参见本文第九题


    5.Arrays和Collections的使用?
    Arrays此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。
    Collections此类提供了一系列方法操作集合对象,例如:排序,转化线程安全集合对象等。


    6.List、Map和Set他们的特点是什么?他们都有哪些典型子类?
    List和Set都实现了Collection接口,List的实现类常使用的主要有ArrayList,CopyOnWriteArrayList, LinkedList, Vector,其中ArrayList、LinkedList是按照元素插入的顺序,但是他们是非线程安全的,并且他们两个实现原理也不相同(请看本文第二题),Vector则是线程安全的集合类,它与ArrayList同样是基于动态数组原理实现,只不过他的操作元素的方法使用了synchronized关键字,所以线程安全,如非必要存在竞争,不要使用,同步会有些许的性能消损。CopyOnWriteArrayList同样也是ArrayList的一个线程安全的变体,但是它和Vector不同的是它使用显示锁

    public boolean add(E e) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len + 1);
                newElements[len] = e;
                setArray(newElements);
                return true;
            } finally {
                lock.unlock();
            }
        }

    Set特点是不存在重复元素。它的主要实现类有ConcurrentSkipListSet, CopyOnWriteArraySet, HashSet, LinkedHashSet, TreeSet ;其中HashSet是常用的,它由哈希表支持,其实它的实现是借助于HashMap,遍历无序,且线程不同步的类。TreeSet的无参构造实例是按照自然排序,也可以使用带有Comparator参数的构造实现自定义排序规则的Set。LinkedHashSet则是哈希表+链表实现,迭代有序。

    Map则是键值对集合,主要实现类由HashMap,LinkedHashMap,ConcurrentHashMap,TreeMap,Hashtable。


    7.常见的有序集合?
    比如说:带有放入顺序的ArrayXXX,LinkedXXX,以及TreeXXX等都是有序集合。


    8.如何实现集合的有序性?
    通过一下实例代码,看一下:

    Collections.sort(result, new Comparator<ReviewDetailBean>() {
        public int compare(ReviewDetailBean o1, ReviewDetailBean o2) {
            return o2.getTpgs() - o1.getTpgs();
        }
    });

    只要是实现Comparator接口,自定义compare方法完成对List的特定排序。


    9.HashMap、Hashtable和ConcurrentHashMap的区别? 2018-4-3
    HashMap是无序散列,并且它是非线程安全的!如果想用有序的,要用TreeMap默认是键的自然排序,以及LinkedHashMap为链表实现,顺序可预知。HashMap的底层数据结构是数组+链表,数组是Node[] table,而Node是它的内静态部类,这个结构就很熟悉了这是链表存有next后继节点。HashMap如果往同一键上加多个值,取到的是最后放进去的那个。

    static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            V value;
            Node<K,V> next;
    }

    而Hashtable由它非驼峰式的命名就知道他是一个历史遗留类。但是它是线程安全的,因为它的方法public synchronized V put(K key, V value) 等使用了synchronized 关键字修饰所以是线程安全的,但是synchronized 锁得是Hashtable的new实例对象,所以效率会慢。

    而相比起线程同步的ConcurrentHashMap则是使用了分段锁的实现,效率会比Hashtable高很多,因此如果要使用线程同步的Map,可以首先考虑ConcurrentHashMap。

    其他让Map线程安全的方法有:

    Collections.synchronizedMap(Map m)


    10.HashSet的源码实现:

    private transient HashMap<E,Object> map;
    
    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
    
    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<E,Object>();
    }
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }
    
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    由以上源码可见,HashSet是借助HashMap对象实现的,利用HashMap的keySet()。只不过,它的key-value为add的元素E 与 静态常量PRESENT 。


    11.ArrayList在foreach的时候remove元素为什么会抛出异常?
    https://blog.csdn.net/kevin_king1992/article/details/79888918


    12.ArrayList的扩容机制?

    //第一次添加元素容量初始为默认值10
    private static final int DEFAULT_CAPACITY = 10;
    private void grow(int minCapacity) {
    
        int oldCapacity = elementData.length;
        //oldCapacity >> 1,扩容原来的一半;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 并且会原先数组的拷贝到扩容后的数组,并赋值给transient Object[] elementData;
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    13.HashMap的扩容机制是什么样子的?
    HashMap的初始值是16,默认的负载因子是0.75

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    看一下put方法中陆续调用到了resize方法:

    if (oldCap > 0) {
         if (oldCap >= MAXIMUM_CAPACITY) {
             threshold = Integer.MAX_VALUE;
             return oldTab;
         }
         //newCap = oldCap << 1 扩容到原来2倍
         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                  oldCap >= DEFAULT_INITIAL_CAPACITY)
             newThr = oldThr << 1; // double threshold
     }
     else if (oldThr > 0) // initial capacity was placed in threshold
         newCap = oldThr;
     else {               // zero initial threshold signifies using defaults
         newCap = DEFAULT_INITIAL_CAPACITY;
         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
     }

    14.CopyOnWriteArrayList实现原理?

    public boolean add(E e) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len + 1);
                newElements[len] = e;
                setArray(newElements);
                return true;
            } finally {
                lock.unlock();
            }
        }

    从添加元素的方法来看,是利用了显示锁进行加锁控制,然后将数据数组进行了复制,长度加一,以来存放添加的数据。

     final transient ReentrantLock lock = new ReentrantLock();
    
     private transient volatile Object[] array;//数据数组

    再来看一下get方法,可见:

    public E get(int index) {
        return get(getArray(), index);
    }
    
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    get方法并不需要加锁,以达到高效高吞吐量。volatile 修饰的array保证了可见性。


  • 相关阅读:
    ROS探索总结(三十一)——ros_control
    ROS探索总结(四十二)——twist_mux多路切换器
    综合面试十大维度解析
    面试官实战-2-业务面试官必须掌握的面试方法及实战演练
    面试官实战-1-素质测评起源和分析
    好的招聘官
    好的候选人
    专题工作模板
    月周报模板
    学习记录模板
  • 原文地址:https://www.cnblogs.com/Kevin-1992/p/12608380.html
Copyright © 2011-2022 走看看