Java基础知识点

Java集合框架知识点总结(一)

2017-05-09  本文已影响678人  Chuck_Hu

说到Java的集合框架,最先会想到Collection和Collections,之后再是Collection的具体实现如有继承关系的List<E>,Set<E>和非继承关系的Map<K, V>。在Chuck加入春招大军以来的两个多月中,几乎所有面试都会问到对集合框架的理解,绝对算得上Java笔试面试中的高频考点。希望通过本文对自己的知识进行总结方便日后温习,也希望能帮助到正在征战各大企业面试的各位朋友。

(一)基础概念

集合统称Collection,是Java API里一个重要的接口,在Java各种开发中发挥巨大作用。其下的具体实现由List<E>,Set<E>和Map<K, V>三种。其中List<E>和Set<E>继承自Collection,'E'指泛型,在创建集合时指定集合中存储的元素类型(不指定默认Object,不提倡)。Map<K, V>是键值对映射容器,'K'、'V'也指泛型,与List和Set相似。

List、Set和Map的区别:

前两者与Map的区别显而易见,前两者属于单个元素级别的存储,而Map是键值对的存储方式。那么List和Set的区别呢?在不分析源码的情况下,可以先理解为List采用线性结构存储,元素可以为null。Set采用离散存储方式,不允许存储值为null的元素且存储的元素不能重复。在接下来的文章中对List、Set和Map源码分析便可以看出他们的显著区别和联系。

Collection和Collections的区别:

这种问题属于集合框架送分题,一般问这种题的面试官要么是带你预热(好人啊!),要么就是不懂Java(比如腾讯的面试官)。Collection是集合类List和Set的父接口,规定了集合的通用方法,不同子类的具体实现略有不同。Collections是集合工具类,提供了一系列static方法辅助操作,常用的有排序sort()、转化为线程安全的synchronizedXXX()等。

(二)集合类源码分析之ArrayList

List类中,Chuck主要讲解ArrayList和LinkedList两个具有代表性也最常用的List集合。首先,ArrayList采用的是Object数组的形式存放元素,由于是数组形式存放,其随机访问的性能优异,但可能会存在空间浪费,并且在不断插入过程中会调整数组大小。
以下是给出的ArrayList类的定义:

public class ArrayList<E> extends AbstractList<E>
 implements List<E>, RandomAccess, Cloneable, java.io.Serializable

继承自AbstractList<E>,同时实现了List<E>接口,从Cloneable和Serializable得知ArrayList也支持克隆和序列化等操作。(这些面试时候问到可以说,细节是面试的加分项),RandomAccess是一个标记接口,未实现任何内容,感兴趣的读者可以自行查看源码。

ArrayList中的两大核心属性:

/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData;// non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*@serial
*/
private int size;

Object类型的数组elementData和size,前者是ArrayList对象存储的容器,从注解中可以看出,数组创建采用懒加载的方式,只有在第一次插入元素时才创建。后者是当前存储的元素个数,除了通过size()方法获取集合大小,size属性在很多时候会有用到。

构造函数

//直接指定大小的方式创建
publicArrayList(intinitialCapacity) {
  if(initialCapacity > 0) {
    this.elementData = newObject[initialCapacity];
  }else if(initialCapacity == 0) {
    this.elementData = EMPTY_ELEMENTDATA;
  }else{
    throw newIllegalArgumentException("Illegal Capacity: "+
    initialCapacity);
  }
}
//未指定大小创建
publicArrayList() {
  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//传入集合的方式创建
publicArrayList(Collection c) {
  elementData = c.toArray();
  if((size = elementData.length)  != 0) {
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
  if(elementData.getClass()  !=  Object[].class)
    elementData = Arrays.copyOf(elementData, size, Object[].class);
  }else{
    // replace with empty array.
    this.elementData = EMPTY_ELEMENTDATA;
  }
}

无论哪种方式,源代码都很通俗易懂,指定大小的情况下,要做的就是验证传入参数是否合法,实际上就是对代码鲁棒性的保证,合法则创建数组,否则抛出异常即可。
而未传入初始大小的情况下,老版本中调用this(10)的方式创建初始大小为10的Object数组。Chuck的jdk 1.8中将elementData赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}

可见1.8中的改变是创建一个空数组。
传入集合初始化的方式其实就是将原有集合转化为数组,通过Arrays.copyOf()函数赋值给新数组直接初始化。
其实一般情况下讲到这里就已经挺长时间了,如此细致的理解已经不错了,但ArrayList中常用的add()方法是整个类中比较复杂且开发中最常用到的方法,在此给出深入的讲解:

public boolean add(E e) {
  ensureCapacityInternal(size + 1);// Increments modCount!!
  elementData[size++] = e;
  return true;
}

ensureCapacityInternal(size + 1)方法顾名思义,因为要插入新元素了集合的大小肯定会+1,所以该函数的作用就是验证插入新元素是否会超出数组大小限制,若会超出则先进性扩容再进行插入。

private void ensureCapacityInternal (int minCapacity) {
  if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  }
  ensureExplicitCapacity(minCapacity);
}

这里注意,DEFAULTCAPACITY_EMPTY_ELEMENTDATA是前文在未传入初始大小情况下进行构造是的空Object数组,这里将对elementData进行初始化,DEFAULT_CAPACITY是一个值为10的int型常量,如果之前是未传入初始大小构造集合,就在这步构造一个大小为10的Object数组。
ensureExplicitCapacity()函数又是什么作用?

private void ensureExplicitCapacity(int minCapacity) {
  modCount++;
  // overflow-conscious code
  if(minCapacity - elementData.length > 0)
    grow(minCapacity);
}

modCount是ArrayList的一个int型属性,很多方法里都会有它的自增操作,其实就是记录ArrayList备操作次数的一个变量。函数中判断如果当前规模小于需求的规模,调用grow函数。

private void grow(int minCapacity) {
// overflow-conscious code
  intoldCapacity = elementData.length;
  intnewCapacity = oldCapacity + (oldCapacity >> 1);
  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);
}

扩容过程也不难,先试图将数组变为原先的1.5倍,若还达不到预期的大小就直接指定为预期大小即可,之后就是数组的新建和复制操作,不再赘述。再把目光返回add()函数,接下来只需要插入新元素并返回即可。
add()函数算是整个ArrayList中比较复杂的业务流程了,其它函数addAll(),remove()等就请各位读者感兴趣的话自行学习。以上知识点对付ArrayList类的面试题绰绰有余。接下来的文章中还将谈到另一个List的代表类LinkedList。

上一篇下一篇

猜你喜欢

热点阅读