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

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保证了可见性。


posted on 2018-03-16 10:10  菜码农先生  阅读(124)  评论(0编辑  收藏  举报