List基础--这一篇全了解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/w372426096/article/details/82585837

为什么需要集合

  首先,Java是一门面向对象的语言,奉承一切皆对象的思想,在实际开发过程中,免不了要经常操作对象,而且会同时操作多个甚至大量对象,这时就需要一个专门存储这些对象的容器,这样就可以利用容器对象的特性来方便的进行操作。
  其次,在Java中还有数组,也是一种容器,但和集合相比,数组长度不可变,只能存储同一种类型元素
  所以,Java提供了集合的概念,相比于数组,集合长度可变,可存储多种类型元素只能存储引用类型元素;同时不同的集合容器,有不同的实现方法和特性,可以更加方便的对对象进行操作。

简介:

List接口为Collection直接接口。List所代表的是有序的Collection,即它用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

为什么元素“有序”、“可重复”呢?
  首先,List的数据结构就是一个序列,存储内容时直接在内存中开辟一块连续的空间,然后将空间地址与索引对应
  其次,List接口的实现类在实现插入元素时,都会根据索引进行排列,如ArrayList,本质是一个数组,插入时元素互不干扰,自然可以重复。
  由于List继承了Collection,所以就具有了Collection的方法:

int size();//集合中元素个数
isEmpty();//是否为空
boolean contains(Object o);//比较地址是否相等
Iterator<E> iterator();//迭代器方法,可用于遍历
Object[] toArray();//转成数组
boolean add(E e);//向list尾部添加新元素
boolean remove(Object o);//删除元素
boolean containsAll(Collection<?> c);//集合A是否包含集合B(A为调用者)
boolean addAll(Collection<? extends E> c);//将集合B中元素添加到集合A中尾部(A为调用者)
boolean removeAll(Collection<?> c);//将两集合中的交集元素删除
int hashCode();//求哈希值

 同时,也具有自己独特的方法(对索引的操作):

E get(int index);//取出对应索引的元素
E set(int index, E element);//往指定索引中插入指定元素,并将原元素覆盖
void add(int index, E element);//往指定索引插入指定元素,其他元素依次后移一位
E remove(int index);//删除索引位置的元素,此位置后的元素前移一位
int indexOf(Object o);//获取元素的索引,若有重复元素,则取最靠前的
int lastIndexOf(Object o);//从后往前数,第一个元素的索引
ListIterator<E> listIterator();//list独有迭代器,迭代式可以add元素
List<E> subList(int fromIndex, int toIndex);//截取原集合元素生成新的集合,值域为[fromIndex,toIndex)

ArrayList

       ArrayList是一个动态数组允许任何符合规则的元素插入甚至包括null。每一个ArrayList都有一个初始容量(10),该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。

       size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间运行,也就是说,添加 n 个元素需要 O(n) 时间(由于要考虑到扩容,所以这不只是添加元素会带来分摊固定时间开销那样简单)。

ArrayList擅长于随机访问。同时ArrayList是非同步的

LinkedList

       实现List接口的LinkedList与ArrayList不同,ArrayList是一个动态数组,而LinkedList是一个双向链表。所以它除了有ArrayList的基本操作方法外还额外提供了get,remove,insert方法在LinkedList的首部或尾部。

       由于实现的方式不同,LinkedList不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。这样做的好处就是可以通过较低的代价在List中进行插入和删除操作

       与ArrayList一样,LinkedList也是非同步的。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List: 
List list = Collections.synchronizedList(new LinkedList(...));

Vector

       与ArrayList相似,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。

Stack

       Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

什么时候会用到List

  如果涉及到“栈”、“队列”、“链表”等操作,应该考虑用List,具体用哪个具体分析:需要快速插入或删除,用LinkedList;需要快速访问,用ArrayList;多线程环境,且List可能同时被多个线程操作,用vector。
  当然,对于线程安全的List,也可以用CopyOnWriteArrayList,基于数组实现的线程安全(ReentrantLock加锁)的写时复制集合,性能比vector高,适合高并发的读多写少的场景
  CopyOnWrite容器,是一个写时复制容器,当我们往一个容器添加元素时,不是直接往当前容器添加,而是先将容器copy一份,复制出新的容器,然后对新的容器里的元素进行操作,最后将原容器的引用指向新的容器。所以,这是一种读写分离的思想,读和写不同的容器。缺点:若写操作频繁,会频繁复制容器,从而影响性能。

 使用List可能出现的问题

  1.NullPointException和IndexOutOfBoundsException问题。List是一个容器对象,在业务逻辑中经常定义List容器,盛放多个业务对象,当容器中没有任何元素时,调用容器操作索引方法,则出现索引越界异常;当作为返参容器时,并没有查到数据,会返回null,此时就容易出现空指针异常。
  2.Arrays.asList方法。我们经常用此API将数组转成List对象,但稍不注意就会报异常。
  情况一:方法调用的入参为基本数据(byte、short、int、long、char、double、float、boolean)类型的数组时,结果会返回一个元素数量为1的集合,和原数据的元素个数无关,这样是因为此方法接收泛型参数,而基本类型不支持泛型化,而数组支持,所以那为1的元素是整个数组,实质存的是此数组的引用地址
  情况二生成新的List集合后,调用add()方法,会出现UnsupportedOperationException异常。抽象类AbstractList定义了对List操作的方法如add等,但用此方法生成的list集合并没有实现它们,所以会报异常。解决:用new关键字创建一个List容器对象,而Arrays.asList作为构造参数入参传入。

后续可以接着看:

List相关源码解析:https://blog.csdn.net/w372426096/article/details/82586738

List相关面试题:https://blog.csdn.net/w372426096/article/details/82586932

List相关性能测试:https://blog.csdn.net/w372426096/article/details/78057233

SynchronizedList和Vector的区别:https://blog.csdn.net/w372426096/article/details/80679665

猜你喜欢

转载自blog.csdn.net/w372426096/article/details/82585837