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

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);// 链表树化
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

所以HashMap的数据结构就是由数组+链表+红黑树构成的(JDK1.8中)。

Java8中改进:
1.JDK8 新增了红黑树特性,参考下文:https://blog.csdn.net/u011240877/article/details/53358305
阈值:

  • 链表数大于8时,会变成红黑树存储
  • 当红黑树元素降到6时会变为链表存储

红黑树规则
- 根节点必然是黑色
- 最短路径中所包含的黑色节点=最长路径中包含的黑色节点
- 叶子结点到根节点不能同时拥有两个相连的红色节点

2.put方法,由以前的头插法改为尾插法
https://www.jianshu.com/p/df2322a6ef05


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等都是有序集合。

有序的Map之LinkedHashMap、TreeMap


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实现原理?

并发容器之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保证了可见性。适合多读少写的场景。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值