以下纯属个人见解,如有不妥请及时指正以免误导他人!!!未完待续….
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保证了可见性。