当前位置:   article > 正文

Java学习之ArrayList_java list add 排序乱了

java list add 排序乱了

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接口,只有实现了这个接口,才会这样)

  1. public class Demo01 {
  2. public static void main(String[] args) {
  3. List<String>list=new ArrayList<>();
  4. for(int i=0;i<100000;i++)
  5. list.add(i+"a");
  6. //测随机访问时间
  7. //开始t
  8. long startTime=System.currentTimeMillis();
  9. for(int i=0;i<100000;i++)
  10. list.get(i);
  11. //结束t
  12. long endTime=System.currentTimeMillis();
  13. System.out.println(endTime-startTime);
  14. //测顺序访问时间
  15. startTime=System.currentTimeMillis();
  16. Iterator<String>it=list.iterator();
  17. while(it.hasNext())
  18. {
  19. it.next();
  20. }
  21. endTime=System.currentTimeMillis();
  22. System.out.println(endTime-startTime);
  23. }
  24. //最后输出为1 2
  25. }

三.ArrayList分析 

3.1构造方法

ConstructorConstructor描述
ArrayList()构造一个初始容量为10的空列表
ArrayList(int initialCapacity)构造具有指定初始容量的空列表
ArrayList(Collection<?extends E> c)构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序

 

  1. //DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的数组
  2. public ArrayList(){
  3. this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  4. }

 

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)将指定集合中的所有元素插入到此列表中,从指定的位置开始

 

方法一:

  1. public boolean add(E e)
  2. {
  3. //校验扩容
  4. ensureCapacityInternal(size+1);
  5. elementData[size++]=e;
  6. return true;
  7. }
  1. private void ensureCapacityInternal(int minCapacity){
  2. ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));
  3. }
  1. private static int calculateCapacity(Object[]elementData,int minCapacity){
  2. if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
  3. //默认容量10和前一个方法的size+1取最大值,赋值给最小容量
  4. minCapaticy=Math.max(DEFAULT_CAPACITY,minCapacity);
  5. }
  6. return minCapacity;
  7. }
  1. private void ensureExplicitCapacity(int minCapacity){
  2. //修改集合的次数++
  3. modCount++;
  4. //判断容量够不够,不够就扩容
  5. if(minCapacity-elementData.length>0)
  6. grow(minCapacity);
  7. }
  1. private void grow(int minCapacity){
  2. //将存元素的数组的长度赋给旧容量
  3. int oldCapacity=elementData.length;
  4. //新容量为旧容量的1.5
  5. int newCapacity=oldCapacity+(oldCapacity>>1);
  6. //看新容量-最小容量是不是<0
  7. if(newCapacity-minCapacity<0)
  8. newCapacity=minCapacity
  9. //新值-Integer的最大值是否>0
  10. if(newCapacity-MAX_ARRAY_SIZE>0)
  11. newCapacity=hugeCapacity(minCapacity);
  12. //调用copyOf进行元素的一个拷贝
  13. elementData=Arrays.copyOf(elementData,newCapacity);
  14. }
  1. private static int hugeCapacity(int minCapacity){
  2. if(minCapacity<0)
  3. throw new OutOfMemoryError();
  4. return (minCapacity>MAX_ARRAY_SIZE)?Integer.MAX_VALUE:MAX_ARRAY_SIZE;
  5. }

方法二:

  1. public void add(int index,E element){
  2. //检验索引
  3. rangeCheckForAdd(index);
  4. //校验扩容,同上一个方法
  5. ensureCapacityInternal(size+1);
  6. //指定索引为index,从index+1拷贝,拷贝size-index个
  7. System.arraycopy(elementData,index,elementData,index+1,size-index);
  8. elementData[index]=element;
  9. size++;
  10. }
  1. private void rangeCheckForAdd(int index){
  2. //检验索引有没有大于数组长度或者小于0
  3. if(index>size||index<0)
  4. throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  5. }

方法三:

  1. public boolean addAll(Collection<? extends E> c){
  2. //把集合转成数组
  3. Object[] a=c.toArray();
  4. //有数据集合的长度赋给numNew
  5. int numNew=a.length;
  6. //size是新集合的长度,numNew是c长度
  7. ensureCapacityInternal(size+numNew);
  8. //把a数组元素拷贝到elementData中
  9. System.arraycopy(a,0,elementData,size,numNew);
  10. size+=numNew;
  11. return numNew!=0;
  12. }

方法四:

  1. public boolean addAll(int index,Collection<? extends E> c){
  2. //检验索引
  3. rangeCheckForAdd(index);
  4. //将集合(数据源)转成数组
  5. Object[] a=c.toArray();
  6. //记录集合长度
  7. int numNew=a.length;
  8. //校验扩容
  9. ensureCapacityInternal(size+numNew);
  10. //numMoved代表要移动元素的个数=集合真实长度-索引位置
  11. int numMoved=size-index;
  12. //判断是否需要移动元素
  13. if(numMoved>0)
  14. //源数组,从哪个位置,移动到目标数组,目标数组起始位置,要复制目标数组个数
  15. //这一步只是移动,数据源的元素还没有复制
  16. System.arraycopy(elementData,index,elementData,index+numNew,numMoved);
  17. //这才是真正将数据源中所有数据添加到数据目的
  18. System.arraycopy(a,0,elementData,index,numNew);
  19. size+=numNew;
  20. return numNew!=0;
  21. }

3.3 修改(set)方法

public E set(int index,E element):根据索引修改集合元素

  1. public E set(int index,E element){
  2. //校验索引
  3. rangeCheck(index);
  4. //把索引对应元素取出来赋值给oldValue
  5. E oldValue=elementData(index);
  6. //把要修改后的值存到数组中
  7. elementData[index]=element;
  8. //返回被替换的元素
  9. return oldValue;
  10. }
  1. private void rangeCheck(int index){
  2. if(index>=size)
  3. throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  4. }

3.4 获取(get)方法

public E get (int index) :根据索引获取元素

  1. public E get(int index){
  2. rangeCheck(index);
  3. return elementData[index];
  4. }

3.5 toString方法

它不是ArrayList的方法,而是AbstractCollection的方法。但是ArrayList继承了AbstractList,AbstractList继承了AbstractCollection

public String toString():把集合所有数据转换成字符串

  1. public String toString(){
  2. //获取迭代器
  3. Iterator<E>it=iterator();
  4. //判断迭代器是否有元素
  5. if(!it.hasNext())
  6. return "[]";
  7. //创建StringBuilder
  8. StringBuilder sb=new StringBuilder();
  9. sb.append('[');
  10. //无限循环
  11. for(;;){
  12. //调用迭代器的next方法取出元素,且向下移动
  13. E e=it.next();
  14. sb.append(e==this?"(this Collection)" :e);
  15. //没有元素了,追加],且把整个缓冲区数据转成字符串,且结束死循环
  16. if(!it.hasNext())
  17. return sb.append(']').toString();
  18. sb.append(',').append(' ');
  19. }

3.6 迭代器

  public Iterator<E> iterator()普通迭代器

用迭代器遍历集合每个元素:

  1. public static void main(String[] args) {
  2. List<String>list=new ArrayList<>();
  3. list.add("Hello");
  4. list.add("every");
  5. list.add("body!");
  6. Iterator<String>it=list.iterator();
  7. while(it.hasNext())
  8. {
  9. String s=it.next();
  10. System.out.println(s);
  11. }
  12. }

源码:

  1. public Iterator<E>iterator(){
  2. //创建了一个对象
  3. return new Itr();
  4. }

 

  1. private class Itr implements Iterator<E>{
  2. int cursor;//光标,默认值0
  3. int lastRet=-1;
  4. //将集合实际修改次数赋给预期修改次数
  5. int expectedModCount=modCount;
  6. //判断集合是否有元素
  7. public boolean hasNext(){
  8. //判断光标是否不等于size
  9. return cursor!=size;
  10. }
  11. public E next(){
  12. //校验预期修改次数是否和实际修改次数一样
  13. checkForComodification();
  14. //光标赋给i
  15. int i=cursor;
  16. //判断是否大于集合size,说明没有元素了
  17. if(i>=size)
  18. throw new NoSuchElementException();
  19. //把集合存储数据数组的地址赋值给该方法的局部变量
  20. Object[] elementData=ArrayList.this.elementData;
  21. //进行判断,如果条件满足就会产生并发修改异常
  22. if(i>=elementData.length)
  23. throw new ConcurrentModificationException();
  24. //光标向下移动
  25. cursor=i+1;
  26. //从数组中取出元素并返回
  27. return(E)elementData[lastRet=i];
  28. }

通过迭代器遍历查找元素,如果有就删除(删除的是最后一个元素):

  1. public static void main(String[] args) {
  2. List<String>list=new ArrayList<>();
  3. list.add("Hello");
  4. list.add("my");
  5. list.add("friend");
  6. Iterator<String>it=list.iterator();
  7. while(it.hasNext())
  8. {
  9. String s=it.next();
  10. if(s.equals("friend"))
  11. list.remove("friend");
  12. }
  13. }

会有并发修改异常。

为什么?

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方法每次都给预期修改次数赋值,不会产生并发修改异常

  1. //迭代器自带方法
  2. public void remove(){
  3. if(lastRet<0)
  4. throw new IllegalStateException();
  5. checkForComodification();
  6. try{
  7. ArrayList.this.remove(lastRet);
  8. cursor=lastRet;
  9. //实际修改次数赋值给预期修改次数
  10. expectedModCount=modCount;
  11. }catch(IndexOutOfBoundsException ex){
  12. throw new ConcurrentModificationException();
  13. }
  14. }
  1. //集合删除的方法
  2. public E remove(int index){
  3. //校验索引
  4. rangeCheck(index);
  5. //实际修改次数++
  6. modCount++;
  7. //取出修改索引的数值赋给oldValue;
  8. E oldValue=elementData(index);
  9. //移动元素个数=实际元素个数-索引之前的-这个索引的
  10. int numMoved=size-index-1;
  11. if(numMoved>0)
  12. System.arraycopy(elementData,index+1,elementData,index,numMoved);
  13. elementData[--size]=null;
  14. return oldValue;
  15. }

3.7clear()

public void clear():清空集合所有数据

  1. public void clear(){
  2. modCount++;
  3. for(int i=0;i<size;i++)
  4. elementData[i]=null;
  5. size=0;
  6. }

3.8 contains()

public boolean contains(Object o):判断集合是否包含指定元素

  1. public boolean contains(Object o){
  2. return indexOf(o)>=0;
  3. }
  1. public int indexOf(Object o){
  2. //判断要找的元素是否为null
  3. if(o==null){
  4. //遍历集合
  5. for(int i=0;i<size;i++)
  6. if(elementData[i]==null)
  7. //找到之后返回该元素索引
  8. return i;
  9. }else{
  10. //遍历集合
  11. for(int i=0;i<size;i++)
  12. //拿要找的元素和集合元素比较
  13. if(o.equals(elementData[i]))
  14. //返回该元素所在集合索引
  15. return i;
  16. }
  17. return -1;
  18. }

3.9 isEmpty()

public boolean isEmpty():判断集合是否为空

  1. public boolean isEmpty(){
  2. return size==0;
  3. }

四.面试题

1.ArrayList怎么扩容的?

第一次扩容10,以后每次是原容量的1.5倍

2.ArrayList频繁扩容导致性能急剧下降,怎么办?

指定初始容量,就不用扩容了。

3.ArrayList插入或删除元素一定比LinkedList慢吗?

不一定。 

ArrayList的remove源码:

  1. //集合删除的方法
  2. public E remove(int index){
  3. //校验索引
  4. rangeCheck(index);
  5. //实际修改次数++
  6. modCount++;
  7. //取出修改索引的数值赋给oldValue;
  8. E oldValue=elementData(index);
  9. //移动元素个数=实际元素个数-索引之前的-这个索引的
  10. int numMoved=size-index-1;
  11. if(numMoved>0)
  12. System.arraycopy(elementData,index+1,elementData,index,numMoved);
  13. elementData[--size]=null;
  14. return oldValue;
  15. }

LinkedList的remove源码

  1. public E remove(int index){
  2. checkElementIndex(index);
  3. return unlink(node(index));
  4. }
  1. private void checkElementIndex(int index){
  2. if(!isElementIndex(index))
  3. throw new IndexOutOfBoundsException();
  4. }
  1. private boolean IsElementIndex(int index){
  2. return index>=0&&index<size;
  3. }
  1. //找元素的方法
  2. Node<E>node(int index){
  3. //判断index是否小于集合长度的一半
  4. if(index<(size>>1)){
  5. //如果小于就把第一个节点赋给x
  6. Node<E>x=first;
  7. //从头往后找,获取下一个节点
  8. for(int i=0;i<index;i++)
  9. x=x.next;
  10. return x;
  11. }else{
  12. Node<E>x=last;
  13. //从后往前找
  14. for(int i=size-1;i>=0;i--)
  15. x=x.prev;
  16. return x;
  17. }
  1. E unlike(Node<E>x){
  2. //这个节点的值
  3. final E element =x.item;
  4. //下一个节点
  5. final Node<E>next=x.next;
  6. //上一个节点
  7. final Node<E>prev=x.prev;
  8. if(prev==null){
  9. first=next;
  10. }else{
  11. prev.next=next;
  12. x.prev=null;
  13. }
  14. if(next==null){
  15. last=prev;
  16. }else{
  17. next.prev=prev;
  18. x.next=null;
  19. }
  20. x.item=null;
  21. size--;
  22. modCount++;
  23. return element;
  24. }

由此看来效率不见得比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效率明显比较低

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/715472
推荐阅读
相关标签
  

闽ICP备14008679号