List
List接口和常用方法
基本介绍
List接口是Collection接口的子接口
- List集合类中的元素有序–即添加顺序和取出顺序一致、且可重复
public class Journey {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("kerwin");
list.add("A");
list.add("B");
list.add("C");
list.add("kerwin");
list.add(true);
System.out.println("list="+list);
}
}
//输出
list=[kerwin, A, B, C, kerwin, true]
- List集合中的每一个元素都有其对应的顺序索引,即支持索引
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("kerwin");
list.add("A");
list.add("B");
list.add("C");
list.add("kerwin");
list.add(true);
System.out.println("list="+list);
System.out.println(list.get(2));//B
}
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
- List接口的实现类有ArrayList 、 LinkedList 、Vector
常用方法
import java.util.ArrayList;
import java.util.List;
public class Journey {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("kerwin");
list.add("A");
list.add(true);
list.add(0,"Blue");
System.out.println("list="+list);//输出如下
// list=[Blue, kerwin, A, true]
List list2 = new ArrayList();
list2.add(false);
list2.add(1110);
list2.addAll(0,list);
System.out.println("list2="+list2);//输出如下
// list2=[Blue, kerwin, A, true, false, 1110]
System.out.println(list.get(0));//Blue
System.out.println(list2.get(4));//false
System.out.println(list.indexOf("Blue"));//0
list.add("kerwin");
System.out.println("第二次新加了个元素kerwin后的list="+list);//输出如下
// 第二次新加了个元素kerwin后的list=[Blue, kerwin, A, true, kerwin]
System.out.println(list.lastIndexOf("kerwin"));//4
System.out.println(list.indexOf("kerwin"));//1
list.remove("Blue");
System.out.println("移除了一个元素后的list="+list);
// 移除了一个元素后的list=[kerwin, A, true, kerwin]
System.out.println(list.set(0, "A"));//设置指定的index 0 位置处的元素为 A,输出 kerwin
//如果.set(index,ele)中的index不存在则会报数组越界异常,所以index必须存在才行
System.out.println(list);//此时输出如下
// [A, A, true, kerwin]
System.out.println(list.subList(1, 3));//输入如下,取[1,3)区间的元素
// [A, true]
}
}
练习
public class Journey {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("kerwin");
list.add(1110);
list.add(true);
list.add("hello");
list.add(1110);
list.add(3.14);
System.out.println("list="+list);
// list=[kerwin, 1110, true, hello, 1110, 3.14]
//在3号位插入hsp,注意3号位是指第三个元素,其索引是2,list中的索引是从0开始的哈
list.add(2,"hsp");
System.out.println("添加了hsp后的list="+list);
// 添加了hsp后的list=[kerwin, 1110, hsp, true, hello, 1110, 3.14]
//获得第5个元素
System.out.println(list.get(4));//hello
//删除第6个元素
list.remove(5);//1110
System.out.println("list="+list);
// list=[kerwin, 1110, hsp, true, hello, 3.14]
//修改第7个元素
System.out.println(list.size());//6
list.add("我是第七个元素");
System.out.println(list);
// [kerwin, 1110, hsp, true, hello, 3.14, 我是第七个元素]
list.set(6,"我是被改动之后的七号元素");
System.out.println("此刻的list="+list);
// 此刻的list=[kerwin, 1110, hsp, true, hello, 3.14, 我是被改动之后的七号元素]
//使用迭代器遍历集合
System.out.println("使用迭代器遍历,可得:=======================");
//创建迭代器,注意不是直接new哈
Iterator iterator = list.iterator();
//联想到迭代器的结构,判断是否还有下一个元素,若有,则下移,并return取出
//顺便一提,可以用快捷键一键生成itit
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
//输出如下
/**
* kerwin
* 1110
* hsp
* true
* hello
* 3.14
* 我是被改动之后的七号元素
*/
}
}
List的三种遍历方式
【ArrayList、LinkedList、Vector】
说明:使用LinkedList完成 ,其使用方式和ArrayList一样
public class Journey {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//下面三种List接口的实现子类都可以完美适配底下三种遍历方式
// LinkedList list = new LinkedList();
// List list = new Vector();
List list = new ArrayList();
for (int i = 0; i < 5; i++) {
list.add(i);
}
list.add("bingo!!");
System.out.println(list);
//1.使用迭代器遍历
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
System.out.println("+++++++++++++++++++++++++++++++++++++++");
//2.使用增强for循环
for (Object o : list) {
System.out.println("增强for得到元素-->" + o);
}
System.out.println("========================================");
//3.使用普通for循环
for (int i = 0; i < list.size(); i++) {
System.out.println("normalFor===》" + list.get(i));
}
}
}
练习
public class Journey {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//使用ArrayList实现
// List list = new ArrayList();
//使用Vector实现
// List list = new Vector();
//使用LinkedList实现
List list = new LinkedList();
list.add(new Book("红楼梦","曹雪芹",100));
list.add(new Book("西游记","吴承恩",10));
list.add(new Book("水浒传","施耐庵",19));
list.add(new Book("三国志","罗贯中",80));
list.add(new Book("西游记","吴承恩",10));
for (Object o :list) {
System.out.println(o);
}
//遍历结果如下:
/**
* 名称:红楼梦 价格:100.0 作者:曹雪
* 名称:西游记 价格:10.0 作者:吴承恩
* 名称:水浒传 价格:19.0 作者:施耐庵
* 名称:三国志 价格:80.0 作者:罗贯中
* 名称:西游记 价格:10.0 作者:吴承恩
*/
//按价格排序,从高到低
//如何对集合进行排序呢?
sort(list);
System.out.println("======排序后的输出=======");
for (Object o :list) {
System.out.println(o);
}
//输出结果如下
/**
* ======排序后的输出=======
* 名称:西游记 价格:10.0 作者:吴承恩
* 名称:西游记 价格:10.0 作者:吴承恩
* 名称:水浒传 价格:19.0 作者:施耐庵
* 名称:三国志 价格:80.0 作者:罗贯中
* 名称:红楼梦 价格:100.0 作者:曹雪芹
*/
}
//静态方法
@SuppressWarnings({"all"})
public static void sort(List list){
//使用冒泡排序框架
int size = list.size();
for(int e=size-1;e>0;e--){
for(int j=0;j<e;j++){
//取出对象
// Object o = list.get(j);
//注意这里要向下转型,不然不是Book对象,咋获得其属性呢?
//这里不需要使用中间变量哈,可以直接用set实现互换
Book book1 = (Book)list.get(j);//原先的list.get(j)的返回类型是Object类型的
Book book2 = (Book)list.get(j+1);
if(book1.getPrice()>book2.getPrice()){
list.set(j,book2);
list.set(j+1,book1);
}
}
}
}
}
ArrayList的注意事项
- 可以存放所有类型的元素,甚至是空元素null,ArrayList并且可以放入多个元素
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(null);
list.add("kerwin");
list.add(null);
for (Object o :list) {
System.out.println(o);
}
/**
* null
* kerwin
* null
*/
}
-
ArrayList是由数组来实现数据存储的
-
ArrayList基本等同于Vector,除了ArrayList是线程不安全(但是其执行效率高啊hhhh)的外;
在多线程情况下,不建议使用ArrayList
//ArrayList是线程不安全的,以下是其add方法源码,可见并没有同步关键字修饰
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//而这是Vector底层的add方法源码,可以看到是有同步关键字synchronized修饰的
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
ArrayList底层操作机制源码分析
@SuppressWarnings({"all"})
public class Journey {
public static void main(String[] args) {
//使用无参构造器创建ArrayList对象
ArrayList list = new ArrayList();
// ArrayList list = new ArrayList(8);
for (int i = 1; i <=10; i++) {
list.add(i);//这里会自动装箱,进入Integer的valueOf方法
//之后,再执行这个add方法,而add方法内部也不是上来就把元素丢进数组里,
//而是先通过ensureCapacityInternal方法来确保加了一个元素后,不会越界
//然后才放进elementData数组中
}
for (int i = 11; i <= 15; i++) {
list.add(i);
}
list.add(100);
list.add(200);
list.add(null);
for (Object o :list) {
System.out.println(o);
}
}
}
-
ArrayList中维护了一个Object类型的数组elementData
拓展:transient–瞬间的、短暂的,其表示被transient修饰的属性不会被序列化,会被忽略
public ArrayList() {
//创建了一个空的elementData数组={}
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
//这里再进入,就会发现起初这里传给它的DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个{}
}
- 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍
//calculateCapacity()--->grow()--->ensureExplicitCapacity()-->ensureCapacityInternal()--->add()
/**
调用顺序总结:
calculateCapacity() 计算出所需的最小容量。
结果传递给 ensureExplicitCapacity(),判断是否需要扩容。
如果需要扩容,ensureExplicitCapacity() 调用 grow() 方法进行扩容。
扩容完成后,所有调用链返回到 add() 方法,最终新元素被成功添加。
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;//最后完成第一次扩容后,elementData就有了10个空间,size起初等于0,
//把要放的数据放进去,然后size++,变为1,表示此时的数组中有了一个数据
return true;
}
private void ensureCapacityInternal(int minCapacity) {//注意这里是最少需要的容量,比如你第一次add,那么最少需要一个
//容量,嗯哼?如果你加了10个元素了,那么再add一次,底层到这儿就会是11个,因为只有11个才能将你之前的10个和
//现在准备新add的那一个元素全放进去;
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果当前数组是默认的空数组,则直接返回max(默认初始化的容量10,当前所需要的容量),否则返回所需容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//DEFAULT_CAPACITY这里是10,而minCapacity是1
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//这里的minCapacity是当前需要的最小容量,你当前要加进去一个元素,自然这里的值就是1了
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
//这里记录的是被修改的次数
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//10-0>0,所以进入动态扩容机制
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
//获取当前数组的容量
int oldCapacity = elementData.length;//0
//新数组扩容为原来的1.5倍,这里我们是第一次扩容,所以走的是第一个if逻辑分支
int newCapacity = oldCapacity + (oldCapacity >> 1);//0+0/2=0
//检查新的容量是否满足最小需求minCapacity,
//如果不满足,也就是扩容后小于所需容量,则直接将 newCapacity
//设置为 minCapacity。这样可以确保扩容后的数组至少能容纳当前所有的元素。
if (newCapacity - minCapacity < 0)//0<10,所以newCapacity就被赋成10了;
newCapacity = minCapacity;
//检查是否超过最大允许的数组大小
//如果新的容量超过了这个最大值,就调用 hugeCapacity(minCapacity),
//可能会根据系统的限制或特定情况处理超大数组的分配。
if (newCapacity - MAX_ARRAY_SIZE > 0)//此时不满足,所以跳过
newCapacity = hugeCapacity(minCapacity);
//复制数组到新容量,elementData起初是{},newCapacity是10,
//随后elementData容量就成了10,里面是空数据
elementData = Arrays.copyOf(elementData, newCapacity);
//这里的扩容是保留原有数据的前提下,再给你多搞几个空间出来,
//类似于给你搬家,换个大房子,原先的东西也一并带过来
}
扩容机制的要点
初始容量:在首次添加元素时,ArrayList
会初始化其内部数组到默认容量(10)。
动态扩容:每当数组需要扩容时,ArrayList
会扩展到原来容量的 1.5 倍,以减少频繁扩容的开销。
最小容量保证:扩容后的新容量至少满足当前所需的最小容量 minCapacity
。
最大容量检查:确保扩容后的容量不超过 JVM 所能支持的最大数组大小。
数组复制:通过扩展数组并复制原有数据,ArrayList
保证了动态数组的灵活性和稳定性。
- 如果使用的是指定大小的构造器,则初始elementData的容量大小为指定大小,如果需要再次扩容,则直接扩容elementData为1.5倍
@SuppressWarnings({"all"})
public class Journey {
public static void main(String[] args) {
//使用有参构造器创建ArrayList对象
ArrayList list = new ArrayList(8);
for (int i = 1; i <=10; i++) {
list.add(i);//这里会自动装箱,进入Integer的valueOf方法
//之后,再执行这个add方法,而add方法内部也不是上来就把元素丢进数组里,
//而是先通过ensureCapacityInternal方法来确保加了一个元素后,不会越界
//然后才放进elementData数组中
}
for (int i = 11; i <= 15; i++) {
list.add(i);
}
list.add(100);
list.add(200);
list.add(null);
for (Object o :list) {
System.out.println(o);
}
}
}
//debug源码,进入ArrayList的内部,如下,此时指定的容量是8,所以走第一个分支,给elementData扩容成8
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);
}
}