基于Java8的ArrayList常用方法详解
2018-04-02 17:40
766 查看
在Java里面ArrayList算得上是一个常用的数据结构,同时也是一个线程不安全的数据结构。
构造函数
一般常用的是第一种和第二种,第三种用的比较少。从代码能看出来,如果调用构造函数的时候,不传参数就使用默认的空对象内存(这有点不好形容,是分配了内存,但是没有写数据),传了参数就直接构造相应大小的数组,集合一类的直接转换成数组。构造函数没什么好讲的,就那样。
add方法
用过ArrayList的应该都用过add方法。add一共重载了两个方法。
add(E e)这个方法是默认从当前数据末尾插入数据。这里可以看出ArrayList和HashMap扩容策略的不同,ArrayList是先扩大了的数组再添加数据,而HashMap是先添加数据再扩大容器。之所以两者扩容策略不一样是因为HashMap在使用了数组3/4大小的时候就进行扩容,而ArrayList是要用完了整个数组才会扩容,并且ArrayList一次只增加一半的大小,这个比HashMap一次翻一倍扩大方式要相对节约一点点。附上ArrayList源码扩容策略的核心源码,HashMap请查看我之前的文章:浅谈Java8的HashMap的扩容策略。
add(int index,E e)和add(E e)的用法大同小异,多了一个数据移动过程。
remove方法
划重点!!!
remove也重载了两个方法。
看到这两个方法的区别了吧,一个是基于数组坐标移除,返回移除的值或者抛出异常,一个是基于对象本身的移除,返回的是true或者false。来一组很有意思的测试案例。
是移除索引为2的对象444这个数据还是说移除索引为0的对象2,凭直觉告诉我答案是什么?
答案是:移除索引为2的对象444。
再来一组更有意思的测试。
还是上面那个集合,分别执行这三组代码,结果是一样吗?答案是否定的,前两个会移出索引为2的对象444,第三个不会移除任何数据。基础的重要性在这两个问题体现的淋漓尽致。我承认这两个问题有一定的刁难成分在里面,但两个问题的答案全是干货,包含了Java5过后的自动装箱拆箱机制、基础数据类型的隐式转换。
基础数据类型隐式转换
Java5的自动装箱和拆箱机制
因为隐式转换的存在,前两个答案没有任何问题。我讲讲为什么第三种方式没有移除任何数据。首先long类型是不能自动转成int类型的,所以不能调用remove(int index)这个方法,那么就只能调用remove(Object o)这个方法,long的封装类型为Long,并且Long重写了equals方法。
源码如下
这个方法一直是返回的false,所以在remove(Object o)里面,不会执行任何的移除数据操作。
clear真的clear吗?
源码可以清楚的看见,只是把数组持有的对象充值为null。但这并不是数组为空的,来张图说明。
简单的说clear这个方法只是把过程二的引用给断开了,但过程一的引用还是存在的,当你ArrayList的数组扩充的很大的时候,clear方法并不能起到调用者想要的内存释放作用的。如果深究,这个只是断开了引用,要等到GC触发的时候才会真的回收内存的,当然这个就有点扯远了。
isEmpty、size、get、set
因为ArrayList是一个线程不安全的数据结构,所以这个两个方法上面并没有任何亮点,只有索引是否越界的检查。
ArrayList常用方法的讲解就到这里。
构造函数
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; } }
一般常用的是第一种和第二种,第三种用的比较少。从代码能看出来,如果调用构造函数的时候,不传参数就使用默认的空对象内存(这有点不好形容,是分配了内存,但是没有写数据),传了参数就直接构造相应大小的数组,集合一类的直接转换成数组。构造函数没什么好讲的,就那样。
add方法
用过ArrayList的应该都用过add方法。add一共重载了两个方法。
public boolean add(E e) { ensureCapacityInternal(size + 1); //确实容器数量足够 elementData[size++] = e; return true; }
public void add(int index, E element) { rangeCheckForAdd(index);//检查坐标是否合法,防止数组越界 ensureCapacityInternal(size + 1); // System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
add(E e)这个方法是默认从当前数据末尾插入数据。这里可以看出ArrayList和HashMap扩容策略的不同,ArrayList是先扩大了的数组再添加数据,而HashMap是先添加数据再扩大容器。之所以两者扩容策略不一样是因为HashMap在使用了数组3/4大小的时候就进行扩容,而ArrayList是要用完了整个数组才会扩容,并且ArrayList一次只增加一半的大小,这个比HashMap一次翻一倍扩大方式要相对节约一点点。附上ArrayList源码扩容策略的核心源码,HashMap请查看我之前的文章:浅谈Java8的HashMap的扩容策略。
if (minCapacity - elementData.length > 0) grow(minCapacity);
int newCapacity = oldCapacity + (oldCapacity >> 1);
add(int index,E e)和add(E e)的用法大同小异,多了一个数据移动过程。
remove方法
划重点!!!
remove也重载了两个方法。
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; return oldValue; }
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
看到这两个方法的区别了吧,一个是基于数组坐标移除,返回移除的值或者抛出异常,一个是基于对象本身的移除,返回的是true或者false。来一组很有意思的测试案例。
public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<Integer>(); list.add(2); list.add(333); list.add(444); list.add(555); list.remove(2); }
是移除索引为2的对象444这个数据还是说移除索引为0的对象2,凭直觉告诉我答案是什么?
答案是:移除索引为2的对象444。
再来一组更有意思的测试。
byte x1 = 2; list.remove(x1);
short x2 = 2; list.remove(x2);
long x3 = 2; list.remove(x3);
还是上面那个集合,分别执行这三组代码,结果是一样吗?答案是否定的,前两个会移出索引为2的对象444,第三个不会移除任何数据。基础的重要性在这两个问题体现的淋漓尽致。我承认这两个问题有一定的刁难成分在里面,但两个问题的答案全是干货,包含了Java5过后的自动装箱拆箱机制、基础数据类型的隐式转换。
基础数据类型隐式转换
byte a = 2; short b = a; int c =b; long d = c;
Java5的自动装箱和拆箱机制
int a = 2; Integer b = a; Integer c = 2; int d = c;
因为隐式转换的存在,前两个答案没有任何问题。我讲讲为什么第三种方式没有移除任何数据。首先long类型是不能自动转成int类型的,所以不能调用remove(int index)这个方法,那么就只能调用remove(Object o)这个方法,long的封装类型为Long,并且Long重写了equals方法。
源码如下
public boolean equals(Object obj) { if (obj instanceof Long) { return value == ((Long)obj).longValue(); } return false; }
这个方法一直是返回的false,所以在remove(Object o)里面,不会执行任何的移除数据操作。
clear真的clear吗?
public void clear() { modCount++; for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
源码可以清楚的看见,只是把数组持有的对象充值为null。但这并不是数组为空的,来张图说明。
简单的说clear这个方法只是把过程二的引用给断开了,但过程一的引用还是存在的,当你ArrayList的数组扩充的很大的时候,clear方法并不能起到调用者想要的内存释放作用的。如果深究,这个只是断开了引用,要等到GC触发的时候才会真的回收内存的,当然这个就有点扯远了。
isEmpty、size、get、set
public boolean isEmpty() { return size == 0; }
public int size() { return size; }
public E get(int index) { rangeCheck(index); return elementData(index); }
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
因为ArrayList是一个线程不安全的数据结构,所以这个两个方法上面并没有任何亮点,只有索引是否越界的检查。
ArrayList常用方法的讲解就到这里。
如有疑问,欢迎留言!
相关文章推荐
- JAVA处理日期时间常用方法详解
- java list详解及arrayList的四种遍历方法
- 基于JAVA中使用Axis发布/调用Webservice的方法详解
- java 遍历arrayList常用的四种方法
- JAVA中StringBuffer类常用方法详解
- Java中基于等待的调优方法详解(5)
- java中List,ArrayList,LinkedList的常用方法
- JAVA中StringBuffer类常用方法详解
- Java中Calendar时间操作常用方法详解
- Java读取XML文件常用方法 详解!(第二次更新)
- Java中常用加密/解密方法详解
- Java String常用方法详解
- java读书笔记:ArrayList源码详解(基于jdk1.8)
- Java中最常用到的排序方法详解
- Java中基于等待的调优方法详解(1)
- 基于序列化存取实现java对象深度克隆的方法详解
- Java中常用的集合类、ArrayList 、HashMap及其遍历方法
- Java中基于等待的调优方法详解
- Java初学_ArrayList常用方法
- Java中基于等待的调优方法详解