赞
踩
ArrayList集合介绍:
List接口的可调整大小的数组实现。
一.ArrayList底层数据结构——数组:
数组:一旦初始化长度就不可以发生改变。
add:
数组的增加元素怎么做?eg:已知数组长度为4,分别是9,5,2,7,如何追加一个元素?
答:创建一个新数组,把原来4个长度的数组元素 一 一 拷贝进来,然后在新数组后面添加一个元素。
delete:
eg:已知数组长度为4,分别是9,5,2,7,如何删除索引1对应的元素?
删除索引1对应元素5,然后让这之后的元素往前移动。变成9,2,7.整个数组长度发生改变,元素位置也发生改变了。
update:
eg:已知数组长度为4,分别是9,5,2,7,如何将索引1元素修改为6?
直接根据索引就能修改。
get:
eg:已知数组长度为4,分别是9,5,2,7,如何获取数组索引1对应的元素?
直接根据索引取出对应元素。
数组优缺点:
1.数组查询快,根据地址和索引直接获取元素。
2.数组增删慢,每次都要创建新数组且移动元素位置。
二.ArrayList继承与实现关系
4个实现:
(1)实现了Serializable标记接口。
(2)实现了Clonable标记接口。(想要克隆必须实现clonable接口,重写clone方法。克隆出来的对象和原对象地址不等,内容相等)
(3) 实现RandomAccess标记接口。
(4)实现list接口。
1个继承:
(1)继承AbstractList类。-提供list接口的框架。
三.ArrayList随机访问和顺序访问效率对比。
随机访问效率比顺序访问(迭代器)效率高。(因为ArrayList实现了RandomAccess接口,只有实现了这个接口,才会这样)
- public class Demo01 {
- public static void main(String[] args) {
- List<String>list=new ArrayList<>();
- for(int i=0;i<100000;i++)
- list.add(i+"a");
- //测随机访问时间
- //开始t
- long startTime=System.currentTimeMillis();
- for(int i=0;i<100000;i++)
- list.get(i);
- //结束t
- long endTime=System.currentTimeMillis();
- System.out.println(endTime-startTime);
-
- //测顺序访问时间
- startTime=System.currentTimeMillis();
- Iterator<String>it=list.iterator();
- while(it.hasNext())
- {
- it.next();
- }
- endTime=System.currentTimeMillis();
- System.out.println(endTime-startTime);
- }
- //最后输出为1 2
- }
三.ArrayList分析
3.1构造方法
Constructor | Constructor描述 |
ArrayList() | 构造一个初始容量为10的空列表 |
ArrayList(int initialCapacity) | 构造具有指定初始容量的空列表 |
ArrayList(Collection<?extends E> c) | 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序 |
- //DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的数组
- public ArrayList(){
- this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
- }
3.2 添加方法
方法名 | 描述 |
public boolean add(E e) | 将指定元素追加到这个列表的末尾 |
public void add(int index,E element) | 将此列表中的指定位置插入指定的元素 |
public boolean addAll(Collection<? extends E>c ) | 按指定集合的Iterator返回的顺序将指定集合中的所有元素 追加到此列表的末尾 |
public boolean addAll(int index,Collection<? extends E>c) | 将指定集合中的所有元素插入到此列表中,从指定的位置开始 |
方法一:
- public boolean add(E e)
- {
- //校验扩容
- ensureCapacityInternal(size+1);
- elementData[size++]=e;
- return true;
- }
- private void ensureCapacityInternal(int minCapacity){
- ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));
- }
- private static int calculateCapacity(Object[]elementData,int minCapacity){
- if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
- //默认容量10和前一个方法的size+1取最大值,赋值给最小容量
- minCapaticy=Math.max(DEFAULT_CAPACITY,minCapacity);
- }
- return minCapacity;
- }
- private void ensureExplicitCapacity(int minCapacity){
- //修改集合的次数++
- modCount++;
- //判断容量够不够,不够就扩容
- if(minCapacity-elementData.length>0)
- grow(minCapacity);
- }
- private void grow(int minCapacity){
- //将存元素的数组的长度赋给旧容量
- int oldCapacity=elementData.length;
- //新容量为旧容量的1.5倍
- int newCapacity=oldCapacity+(oldCapacity>>1);
- //看新容量-最小容量是不是<0
- if(newCapacity-minCapacity<0)
- newCapacity=minCapacity
- //新值-Integer的最大值是否>0
- if(newCapacity-MAX_ARRAY_SIZE>0)
- newCapacity=hugeCapacity(minCapacity);
- //调用copyOf进行元素的一个拷贝
- elementData=Arrays.copyOf(elementData,newCapacity);
- }
- private static int hugeCapacity(int minCapacity){
- if(minCapacity<0)
- throw new OutOfMemoryError();
- return (minCapacity>MAX_ARRAY_SIZE)?Integer.MAX_VALUE:MAX_ARRAY_SIZE;
- }
方法二:
- public void add(int index,E element){
- //检验索引
- rangeCheckForAdd(index);
- //校验扩容,同上一个方法
- ensureCapacityInternal(size+1);
- //指定索引为index,从index+1拷贝,拷贝size-index个
- System.arraycopy(elementData,index,elementData,index+1,size-index);
- elementData[index]=element;
- size++;
- }
- private void rangeCheckForAdd(int index){
- //检验索引有没有大于数组长度或者小于0
- if(index>size||index<0)
- throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
- }
方法三:
- public boolean addAll(Collection<? extends E> c){
- //把集合转成数组
- Object[] a=c.toArray();
- //有数据集合的长度赋给numNew
- int numNew=a.length;
- //size是新集合的长度,numNew是c长度
- ensureCapacityInternal(size+numNew);
- //把a数组元素拷贝到elementData中
- System.arraycopy(a,0,elementData,size,numNew);
- size+=numNew;
- return numNew!=0;
- }
方法四:
- public boolean addAll(int index,Collection<? extends E> c){
- //检验索引
- rangeCheckForAdd(index);
- //将集合(数据源)转成数组
- Object[] a=c.toArray();
- //记录集合长度
- int numNew=a.length;
- //校验扩容
- ensureCapacityInternal(size+numNew);
- //numMoved代表要移动元素的个数=集合真实长度-索引位置
- int numMoved=size-index;
- //判断是否需要移动元素
- if(numMoved>0)
- //源数组,从哪个位置,移动到目标数组,目标数组起始位置,要复制目标数组个数
- //这一步只是移动,数据源的元素还没有复制
- System.arraycopy(elementData,index,elementData,index+numNew,numMoved);
- //这才是真正将数据源中所有数据添加到数据目的
- System.arraycopy(a,0,elementData,index,numNew);
- size+=numNew;
- return numNew!=0;
- }
3.3 修改(set)方法
public E set(int index,E element):根据索引修改集合元素
- public E set(int index,E element){
- //校验索引
- rangeCheck(index);
- //把索引对应元素取出来赋值给oldValue
- E oldValue=elementData(index);
- //把要修改后的值存到数组中
- elementData[index]=element;
- //返回被替换的元素
- return oldValue;
- }
- private void rangeCheck(int index){
- if(index>=size)
- throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
- }
3.4 获取(get)方法
public E get (int index) :根据索引获取元素
- public E get(int index){
- rangeCheck(index);
- return elementData[index];
- }
3.5 toString方法
它不是ArrayList的方法,而是AbstractCollection的方法。但是ArrayList继承了AbstractList,AbstractList继承了AbstractCollection
public String toString():把集合所有数据转换成字符串
- public String toString(){
- //获取迭代器
- Iterator<E>it=iterator();
- //判断迭代器是否有元素
- if(!it.hasNext())
- return "[]";
- //创建StringBuilder
- StringBuilder sb=new StringBuilder();
- sb.append('[');
- //无限循环
- for(;;){
- //调用迭代器的next方法取出元素,且向下移动
- E e=it.next();
- sb.append(e==this?"(this Collection)" :e);
- //没有元素了,追加],且把整个缓冲区数据转成字符串,且结束死循环
- if(!it.hasNext())
- return sb.append(']').toString();
- sb.append(',').append(' ');
- }
3.6 迭代器
public Iterator<E> iterator()普通迭代器
用迭代器遍历集合每个元素:
- public static void main(String[] args) {
- List<String>list=new ArrayList<>();
- list.add("Hello");
- list.add("every");
- list.add("body!");
- Iterator<String>it=list.iterator();
- while(it.hasNext())
- {
- String s=it.next();
- System.out.println(s);
- }
-
- }
源码:
- public Iterator<E>iterator(){
- //创建了一个对象
- return new Itr();
- }
- private class Itr implements Iterator<E>{
- int cursor;//光标,默认值0
- int lastRet=-1;
- //将集合实际修改次数赋给预期修改次数
- int expectedModCount=modCount;
- //判断集合是否有元素
- public boolean hasNext(){
- //判断光标是否不等于size
- return cursor!=size;
- }
-
- public E next(){
- //校验预期修改次数是否和实际修改次数一样
- checkForComodification();
- //光标赋给i
- int i=cursor;
- //判断是否大于集合size,说明没有元素了
- if(i>=size)
- throw new NoSuchElementException();
- //把集合存储数据数组的地址赋值给该方法的局部变量
- Object[] elementData=ArrayList.this.elementData;
- //进行判断,如果条件满足就会产生并发修改异常
- if(i>=elementData.length)
- throw new ConcurrentModificationException();
- //光标向下移动
- cursor=i+1;
- //从数组中取出元素并返回
- return(E)elementData[lastRet=i];
- }
通过迭代器遍历查找元素,如果有就删除(删除的是最后一个元素):
- public static void main(String[] args) {
- List<String>list=new ArrayList<>();
- list.add("Hello");
- list.add("my");
- list.add("friend");
- Iterator<String>it=list.iterator();
- while(it.hasNext())
- {
- String s=it.next();
- if(s.equals("friend"))
- list.remove("friend");
- }
- }
会有并发修改异常。
为什么?
add()完以后modCount++,实际修改次数=3,预期修改次数=3,删除完元素,modCount++,实际修改次数=4,下次再遍历时,(cursor=3)!=(size=2),因此还会往下执行,调用next方法,然后校验预期修改次数和实际修改次数不一样,就会有异常。
通过迭代器遍历查找元素,如果有就删除(删除的是集合倒数第二个元素):
结果:成功,没有异常
为什么?
因为hasNext()中光标的值cursor=size的值,不会调用next(),就不会产生并发修改异常。
3.6 1 remove()
这个方法是个默认方法,jdk得达到1.8
default void remove():删除集合中元素
1.迭代器调用remove方法删除元素,实际上还是调用集合自己的remove方法删除元素
2.调用remove方法每次都给预期修改次数赋值,不会产生并发修改异常
- //迭代器自带方法
- public void remove(){
- if(lastRet<0)
- throw new IllegalStateException();
- checkForComodification();
- try{
- ArrayList.this.remove(lastRet);
- cursor=lastRet;
- //实际修改次数赋值给预期修改次数
- expectedModCount=modCount;
- }catch(IndexOutOfBoundsException ex){
- throw new ConcurrentModificationException();
- }
- }
- //集合删除的方法
- public E remove(int index){
- //校验索引
- rangeCheck(index);
- //实际修改次数++
- modCount++;
- //取出修改索引的数值赋给oldValue;
- 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;
- }
3.7clear()
public void clear():清空集合所有数据
- public void clear(){
- modCount++;
- for(int i=0;i<size;i++)
- elementData[i]=null;
- size=0;
- }
3.8 contains()
public boolean contains(Object o):判断集合是否包含指定元素
- public boolean contains(Object o){
- return indexOf(o)>=0;
- }
- public int indexOf(Object o){
- //判断要找的元素是否为null
- if(o==null){
- //遍历集合
- for(int i=0;i<size;i++)
- if(elementData[i]==null)
- //找到之后返回该元素索引
- return i;
- }else{
- //遍历集合
- for(int i=0;i<size;i++)
- //拿要找的元素和集合元素比较
- if(o.equals(elementData[i]))
- //返回该元素所在集合索引
- return i;
- }
- return -1;
- }
3.9 isEmpty()
public boolean isEmpty():判断集合是否为空
- public boolean isEmpty(){
- return size==0;
- }
四.面试题
1.ArrayList怎么扩容的?
第一次扩容10,以后每次是原容量的1.5倍
2.ArrayList频繁扩容导致性能急剧下降,怎么办?
指定初始容量,就不用扩容了。
3.ArrayList插入或删除元素一定比LinkedList慢吗?
不一定。
ArrayList的remove源码:
- //集合删除的方法
- public E remove(int index){
- //校验索引
- rangeCheck(index);
- //实际修改次数++
- modCount++;
- //取出修改索引的数值赋给oldValue;
- 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;
- }
LinkedList的remove源码
- public E remove(int index){
- checkElementIndex(index);
- return unlink(node(index));
- }
- private void checkElementIndex(int index){
- if(!isElementIndex(index))
- throw new IndexOutOfBoundsException();
- }
- private boolean IsElementIndex(int index){
- return index>=0&&index<size;
- }
- //找元素的方法
- Node<E>node(int index){
- //判断index是否小于集合长度的一半
- if(index<(size>>1)){
- //如果小于就把第一个节点赋给x
- Node<E>x=first;
- //从头往后找,获取下一个节点
- for(int i=0;i<index;i++)
- x=x.next;
- return x;
- }else{
- Node<E>x=last;
- //从后往前找
- for(int i=size-1;i>=0;i--)
- x=x.prev;
- return x;
- }
- E unlike(Node<E>x){
- //这个节点的值
- final E element =x.item;
- //下一个节点
- final Node<E>next=x.next;
- //上一个节点
- final Node<E>prev=x.prev;
-
- if(prev==null){
- first=next;
- }else{
- prev.next=next;
- x.prev=null;
- }
-
- if(next==null){
- last=prev;
- }else{
- next.prev=prev;
- x.next=null;
- }
-
- x.item=null;
- size--;
- modCount++;
- return element;
- }
由此看来效率不见得比ArrayList快多少。
4.ArrayList是线程安全的吗?什么时候需要同步?
不是。解决方法:synchronized、Lock或者用vector。
集合不是成员变量时,不需要同步,每一个线程都有一个独立的方法,不是共享数据,就不用加锁。
反之,成员变量是被所有线程共享的,所以需要。
5.如何复制一个ArrayList到另一个ArrayList去?
(1)clone()方法
(2)ArrayList构造方法
(3)addAll()
6.已知成员变量集合存储N多用户名称,在多线程情况下,用迭代器读取集合数据时如何保证还可以正常写入数据到集合?
有个集合可以让读写分离:CopyOnWriteArrayList
7.ArrayList和LinkedList的区别?
ArrayList:
基于动态数组的数据结构。
对于随机访问get,set,ArrayList优于LinkedList
对于随机操作的add,remove,ArrayList不一定比LinkedList慢(因为ArrayList底层是基于动态数组,因此不用每次add和remove的时候都需要创建新数组)
LinkedList:
基于链表的数据结构
对于顺序操作,LinkedList不一定比ArrayList慢
对于随机操作,LinkedList效率明显比较低
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。