目录
一、什么是集合?
二、集合接口
三、List集合
四、ArrayList容器类
1. 常用方法
1.1 增加
1.2 查找
int size()
E get(int index)
int indexOf(Object c)
boolean contains(Object c)
boolean isEmpty()
List SubList(int fromindex,int toIndex)
boolean equals(Object o)
1.3 修改
E set(int index, E element)
1.1.4 删除
boolean removeAll(Collection c)--- 差集
boolean retainAll(Collection c) --交集
1.5 其他方法
clone()
sort()
List和Array转换
2. 遍历
2.1 for循环
2.2 增强for循环
2.3 迭代器
Iterator的remove
3. 初始化
无参初始化
有参初始化
五、ArrayList底层源码解析及扩容机制(⭐)
【特点】
【数据结构】
扩容机制:
总结:
小结
一、什么是集合?
什么是集合?集合就是“由若干个确定的元素所构成的整体”,在程序中,一般代表保存若干个元素(数据)的某种容器类。在数学中,我们经常遇到集合的概念。例如:
●有限集合:
- 一个班所有的同学构成的集合;
- 一个网站所有的商品构成的集合;
- ...
●无限集合:
- 全体自然数集合:1,2,3,……
- 有理数集合;
- 实数集合;
- ...
为什么要在计算机中引入集合呢?这是为了便于处理一组类似的数据,例如:
- 计算所有同学的总成绩和平均成绩;
- 列举所有的商品名称和价格;
- ……
在Java中,如果一个Java对象可以在内部持有(保存)若干其他Java对象,并对外提供访问接口,我们把这种Java对象的容器称为集合。很显然,Java的数组也可以看作是一种集合:既然Java提供了数组这种数据类型,可以充当集合,那么,我们为什么还需要其他集合类?这是因为数组有如下限制:
- 长度开始时必须指定,且一旦指定,不能更改;即数组长度很难扩容
- 保存的必须为同一类型的元素;
- 数组的方法和功能不完善,查看数组中的具体长度,只有length属性知道内存大小,不知道具体的元素个数。
public static void main(String[] args) { String[] menu = new String[10]; menu[0] = "麻婆豆腐"; menu[1] = "麻辣烫"; menu[2] = "null"; System.out.println(menu.length);//10 ArrayList<String> arrayList = new ArrayList<String>(); for (int i = 0; i < 3; i++) { arrayList.add("麻婆豆腐"); arrayList.add("麻辣烫"); arrayList.add("null"); // arrayList.add( 12 ); // arrayList.add( 12.23 ); // arrayList.add( true ); // arrayList.add(new Date()); } System.out.println(arrayList);//[麻婆豆腐, 麻辣烫, null, 麻婆豆腐, 麻辣烫, null, 麻婆豆腐, 麻辣烫, null] System.out.println(arrayList.size());//9 }
因此,我们需要各种不同类型的集合类来处理不同的数据,例如:
- 可变大小的顺序链表;
- 保证无重复元素的集合;
- ...
二、集合接口
Java标准库自带的java.util包提供了集合相关的接口和实现类:Collection接口,它是除Map接口外所有其他集合类的根接口。
Collection接口:collection接口是单例集合的跟接口,英文含义集中、收集
单列集合是将数据进行一个一个的存储,存储的是值
Java的java.util包主要提供了以下三种类型的集合:
- List:一种有序列表的集合;
- Set:一种保证没有重复元素的集合;
- Map:一种通过键值(key-value)查找的映射表集合,例如,根据Student的name查找对应Student的Map。
Java集合的设计有几个特点:首先实现了接口和实现类相分离,例如,有序表的接口是List,具体的实现类有ArrayList,LinkedList等;其次支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素,例如:
List<String> list = new ArrayList<>(); // 只能放入String类型
最后,Java访问集合总是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。
另外,由于Java的集合设计非常久远,中间经历过大规模改进,我们要注意到有一小部分集合类是遗留类,不应该继续使用:
- Hashtable:一种线程安全的Map实现;
- Vector:一种线程安全的List实现;
- Stack:基于Vector实现的LIFO的栈;
三、List集合
在集合类中,List是最基础的一种集合:它是一种有序列表。List的行为和数组几乎完全相同:List内部按照放入元素的先后顺序存放,每个元素都可以通过索引确定自己的位置,List的索引和数组一样,从0开始。
List的特点:
- 元素有序的-即存入顺序和取出顺序是一致的(注意区分有序和排序)
- 是可以重复的
我们观察List<E>接口,可以看到几个主要的接口方法:
- 在末尾添加一个元素:boolean add(E e)
- 在指定索引添加一个元素:boolean add(int index, E e)
- 删除指定索引的元素:E remove(int index)
- 删除某个元素:boolean remove(Object e)
- 获取指定索引的元素:E get(int index)
- 获取链表大小(包含元素的个数):int size()
四、ArrayList容器类
1. 常用方法
方法名 说明 public boolean add(E e)
将指定的参数元素追加到集合的末尾 public void add(int index ,E e)
在集合的指定位置添加指定的元素(插入元素) public void addAll(E object)
用于将指定集合中所有元素添加到当前集合中 boolean addAll(int index,Collection<? extends E> c) 从指定的位置开始,将指定 collection 中的所有元素插入到此列表中
方法名 说明 public boolean remove(Object o)
删除指定的元素,成功则返回true public E remove(int index)
删除指定索引位置的元素,返回被删除的元素 public E set(int index,E e)
修改指定索引位置的元素,返回修改前的元素 public E get(int index)
获取指定索引对应的元素 public int size()
获取结合中元素个数
1.1 增加
- boolean add(E,e)添加指定元素到集合尾部
//泛型使用的目的:帮助我们建立类型安全的集合,添加元素的时候传入实际的类型 //泛型使用好处: 代码可读性增强,程序更安全 //泛型集合--泛型必须是引用数据类型 ArrayList<String> arrayList =new ArrayList<String>(); //add(元素)添加元素到集合的尾部 boolean b = arrayList.add("z张三"); System.out.println("添加z张三是否成功:"+b); arrayList.add("l李四"); arrayList.add("w王五"); arrayList.add("r任六"); arrayList.add("z张三"); System.out.println(arrayList); 运行结果: 添加z张三是否成功:true [z张三, l李四, w王五, r任六, z张三]
- void add(int index, E element)添加新元素到集合指定的下标位置
//add(int index, E element)添加新元素到集合指定的下标位置 arrayList.add(0,"孙二娘"); arrayList.add(3,"刘备"); System.out.println(arrayList);//[孙二娘, z张三, l李四, 刘备, w王五, r任六, z张三]
- boolean addAll(Collection<? extends E> c) 添加集合C 内所有元素到当前集合
//使用Arrays.asList方法快速生成一个List集合 //Arrays.asList返回的不是java.util下的ArrayList List<String> list = Arrays.asList("李白","高适","李清照","杜甫"); //boolean addAll(Collection<? extends E> c) 添加集合C 内所有元素到当前集合 arrayList.addAll(list); System.out.println(arrayList);//[孙二娘, z张三, l李四, 刘备, w王五, r任六, z张三, 李白, 高适, 李清照, 杜甫]
- boolean addAll(int index,Collection<? extends E> c) 从指定的位置开始,将指定 collection 中的所有元素插入到此列表中
//boolean addAll(Collection<? extends E> c) 添加集合C 内所有元素到当前集合 arrayList.addAll(list); System.out.println(arrayList);//[李白, 高适, 李清照, 杜甫, 孙二娘, z张三, l李四, 刘备, w王五, r任六, z张三, 李白, 高适, 李清照, 杜甫]
1.2 查找
int size()
查集合的长度,具体的元素个数
ArrayList<String> arrayList = new ArrayList<String>(); arrayList.addAll(Arrays.asList("李白","高适","李清照","杜甫")); //1.获取元素的个数 int size = arrayList.size(); System.out.println("元素的个数为:"+size);//元素的个数为:4
E get(int index)
获取下标中的元素
//2.E get(int index)获取下标中的元素, System.out.println("首元素:"+arrayList.get(0));//首元素:李白 System.out.println("尾元素:"+arrayList.get(arrayList.size()-1));//尾元素:杜甫
int indexOf(Object c)
查找找到指定元素的下标位置,如果不存在,则返回数组-1
//3.int indexOf(Object c) 查找找到指定元素的下标位置,如果不存在,则返回数组-1 int index =arrayList.indexOf("高适1"); System.out.println("高适所对应的下标位置:"+index);//高适所对应的下标位置:-1
boolean contains(Object c)
判断集合中是否存在元素
//4.boolean contains(Object c) System.out.println("高适是否存在:"+arrayList.contains("高适1"));//高适是否存在:false
boolean isEmpty()
判断集合是否为空
//5.判断集合是否为空boolean isEmpty() System.out.println("集合是否为空:"+arrayList.isEmpty());//集合是否为空:false
List<E> SubList(int fromindex,int toIndex)
subList是List接口中定义的一个方法,该方法主要用于返回一个集合中的一段、可以理解为截取一个集合中的部分元素,他的返回值也是一个List。
//6.List<E> SubList(int fromindex,int toIndex) List<String> subList = arrayList.subList(0, 3); System.out.println(subList);//[李白, 高适, 李清照]
boolean equals(Object o)
ArrayList.equals(Object obj)
方法比较的是两个列表(ArrayList或其它类型的List)的内容是否相等,即两个列表的大小必须相同,并且在相同索引位置上的元素也必须相等。//7.equals() boolean bequals = arrayList.equals(subList); System.out.println(bequals);//false
1.3 修改
E set(int index, E element)
修改指定索引位置的元素,返回修改前的元素
ArrayList<String> arrayList = new ArrayList<String>(); arrayList.addAll(Arrays.asList("李白", "高适", "李清照", "杜甫")); System.out.println("原集合的内容:" + arrayList); // 修改指定下标的元素 String result = arrayList.set(0, "王维"); System.out.println("修改的元素:" + result); System.out.println("修改后的集合:" + arrayList);
1.1.4 删除
- E remove(int index):根据指定索引删除元素,并把删除的元素返回 如果下标超过集合的最大下标,输出IndexOutOfBoundsException
// E remove(int index):根据指定索引删除元素,并把删除的元素返回 String oldValue = arrayList.remove(0); System.out.println("删除的下标为0的元素为:" + oldValue);//删除的下标为0的元素为:李白
- boolean remove(Object o):从集合中删除指定的元素,删除一个就返回 如果查到或者删除1个就返回,并不是删除所有相同的集合元素
//boolean remove(Object o):从集合中删除指定的元素,删除一个就返回 boolean b = arrayList.remove("高适"); System.out.println("删除元素高适是否成功:" + b);//删除元素高适是否成功:true
- void clear():删除集合中的所有元素,此集合仍旧存在,集合元素长度变0
//清空元素 arrayList.clear(); System.out.println(arrayList.size());//0
boolean removeAll(Collection<?> c)--- 差集
从集合中删除一个指定的集合元素:
删除A集合中存在B集合中的所有相同的元素,如果有删除返回TrueArrayList<String> list1 = new ArrayList<String>(); list1.addAll(Arrays.asList("周杰伦", "郭德刚", "张学友", "于适")); ArrayList<String> list2 = new ArrayList<String>(); list2.addAll(Arrays.asList("周杰伦", "王一博", "张学友", "吴磊")); // boolean removeAll(Collection<?> c)--- 差集 boolean b = list1.removeAll(list2); System.out.println(b);//true System.out.println(list1);//[郭德刚, 于适] System.out.println(list2);//[周杰伦, 王一博, 张学友, 吴磊]
boolean retainAll(Collection<?> c) --交集
保留集合A和集合B中相同的元素,删除不同的元素,谁调用操作的是就是谁
ArrayList<String> list1 = new ArrayList<String>(); list1.addAll(Arrays.asList("周杰伦", "郭德刚", "张学友", "于适")); ArrayList<String> list2 = new ArrayList<String>(); list2.addAll(Arrays.asList("周杰伦", "王一博", "张学友", "吴磊")); // boolean retainAll(Collection<?> c) --交集 boolean result=list1.retainAll(list2); System.out.println(result);//true System.out.println(list1);//[周杰伦, 张学友] System.out.println(list2);//[周杰伦, 王一博, 张学友, 吴磊]
1.5 其他方法
clone()
克隆一个集合,得到的一个长度,个数,内容,顺序完全一致的集合,单是赋值了一份
ArrayList<String> list1 = new ArrayList<String>(); list1.addAll(Arrays.asList("z周杰伦", "g郭德刚", "d张学友", "y于适")); System.out.println("原集合:" + list1);//原集合:[z周杰伦, g郭德刚, d张学友, y于适] // list.clone() 克隆一个集合,得到的一个长度,个数,内容,顺序完全一致的集合,单是赋值了一份 ArrayList<String> cloneList = (ArrayList<String>) list1.clone(); System.out.println("克隆的集合:" + cloneList);//克隆的集合:[z周杰伦, g郭德刚, d张学友, y于适]
sort()
对list中的内容进行排序,需要自定义排序规则
// list.sort() 对list中的内容进行排序,需要自定义排序规则 list1.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); System.out.println(list1);//[d张学友, g郭德刚, y于适, z周杰伦]
List和Array转换
- object[] toArray()直接返回一个Object[]数组:
// object[] toArray() Object[] objects = list1.toArray(); System.out.println("objects:"+Arrays.toString(objects));//objects:[d张学友, g郭德刚, y于适, z周杰伦]
- T[] toArray(T[] a)传入一个类型相同的Array,List内部自动把元素复制到传入的Array中:
// T[] toArray(T[] a)以较长的长度为 String[] arrayList = list1.toArray(new String[0]); System.out.println("arrayList:"+Arrays.toString(arrayList));//arrayList:[d张学友, g郭德刚, y于适, z周杰伦]
2. 遍历
2.1 for循环
和数组类型,我们要遍历一个List,完全可以用for循环根据索引配合get(int)方法遍历:
ArrayList<String> arrayList = new ArrayList<String>(); arrayList.addAll(Arrays.asList("李白", "高适", "李清照", "杜甫")); // 1.遍历 for (int i = 0; i < arrayList.size(); i++) { System.out.print(arrayList.get(i) + " "); } 运行结果:李白 高适 李清照 杜甫
2.2 增强for循环
只要实现了Iterable接口的集合类都可以直接用for each循环来遍历,Java编译器本身并不知道如何遍历集合对象,但它会自动把for each循环变成Iterator的调用,原因就在于Iterable接口定义了一个Iterator<E> iterator()方法,强迫集合类必须返回一个Iterator实例。
ArrayList<String> arrayList = new ArrayList<String>(); arrayList.addAll(Arrays.asList("李白", "高适", "李清照", "杜甫")); // 2.for each for (String str : arrayList) { System.out.print(str + "_"); } 运行结果:李白_高适_李清照_杜甫_
2.3 迭代器
是访问数据的模型,主要用来遍历Colllection集合。使用迭代器Iterator来访问List。Iterator本身也是一个对象,但它是由List的实例调用iterator()方法的时候创建的。Iterator对象知道如何遍历一个List,并且不同的List类型,返回的Iterator对象实现也是不同的,但总是具有最高的访问效率。
Iterator对象有两个方法:
- boolean hasNext()判断是否有下一个元素,
- E next()返回下一个元素。
因此,使用Iterator遍历List代码如下:
// 3.迭代器 // 3.1 获取当前集合的“普通的”迭代器对象 Iterator<String> it = arrayList.iterator(); // 通过迭代器对象判断是否存在下一个元素 while(it.hasNext()) { System.out.print(it.next()+" ");//获取下一个元素 } System.out.println(); 运行结果:李白 高适 李清照 杜甫
使用List迭代器(ListIterator)遍历:
继承了Iterator,是List集合派系所特有的迭代器,遍历方式与Iterator遍历类似,但也有其特殊之处:它内部含有hasPrevious()方法和previous()方法,可以配合使用进行倒序遍历,前提是必须要先正序遍历让指针移至最后,否则倒叙遍历没有效果
//3.2获取当前集合的"List迭代器" //从指定的下标开始正序遍历元素 ListIterator<String> listIt = arrayList.listIterator(0); while(listIt.hasNext()) { System.out.print(listIt.next()+" ");//获取下一个元素 } System.out.println(); //逆序遍历集合 ListIterator<String> listIt1 = arrayList.listIterator(4); while(listIt1.hasPrevious()) { System.out.print(listIt1.previous()+" "); } 运行结果: 李白 高适 李清照 杜甫 杜甫 李清照 高适 李白
Iterator的remove
- ①在使用迭代器的remove()操作时,会将更新后的modCount给expectedModCount,两者会得到同步,但是在调用集合的remove()方法后,两个不会进行同步,进而导致在checkForComodification()校验时不通过,抛出java.util.ConcurrentModificationException异常。
- ②所以,在单线程下使用迭代器是没有问题的,但是在多线程下同时操作集合就不允许了,可以通过fail-fast快速失败机制,快速判断是否存在同时操作问题。因此,集合在多线程下使用是不安全的。
Collection c2 = new ArrayList(); c2.add("abc"); c2.add("def"); c2.add("xyz"); Iterator it2 = c2.iterator(); while(it2.hasNext()){ Object o = it2.next(); // 删除元素 // 删除元素之后,集合的结构发生了变化,应该重新去获取迭代器 // 但是,循环下一次的时候并没有重新获取迭代器,所以会出现异常:java.util.ConcurrentModificationException // 出异常根本原因是:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合变化了) //c2.remove(o); // 直接通过集合去删除元素,没有通知迭代器。(导致迭代器的快照和原集合状态不同。) // 使用迭代器来删除可以吗? // 迭代器去删除时,会自动更新迭代器,并且更新集合(删除集合中的元素)。 it2.remove(); // 删除的一定是迭代器指向的当前元素。 System.out.println(o); } System.out.println(c2.size()); //0
3. 初始化
无参初始化
- 无参构造初始化:内部数组初始化成空数组
// 1.无参构造初始化:内部数组初始化成空数组 ArrayList<String> list1 =new ArrayList<String>();
有参初始化
- ArrayList(int initialCapacity)初始化成长度为指定容量的Object[]数组
// 2.ArrayList(int initialCapacity)初始化成长度为指定容量的Object[]数组 ArrayList<String> list2 =new ArrayList<String>(10); System.out.println(list2.size());//0
- ArrayList(Collection<? extends E> c) 内部的数组被集合进行初始化
// 3.ArrayList(Collection<? extends E> c) 内部的数组被集合进行初始化 ArrayList<String> list3=new ArrayList<String>(Arrays.asList("abc","def")); System.out.println(list3);//[abc, def]
五、ArrayList底层源码解析及扩容机制(⭐)
List的主要实现类,底层使用Object[]存储,适用于频繁的查找工作,线程不安全。
【特点】
【数据结构】
底层是基于数组实现的,随着元素的增加而动态扩容
使用场景:适合数据连续性遍历,读多写少的场景
ArrayList<String> list = new ArrayList<String>(0); //第一次往无参构造的数组list中添加元素的时候,数组首次扩容10 list.add("zkt"); list.add("zkt"); list.add("zkt"); list.add("zkt"); list.add("zkt"); list.add("zkt"); list.add("zkt"); list.add("zkt"); list.add("zkt"); list.add("zkt"); //... //后面每次容量不足时扩容为容量的1.5倍 list.add("张kt"); System.out.println(list);//[zkt, zkt, zkt, zkt, zkt, zkt, zkt, zkt, zkt, zkt, 张kt] list.addAll(Arrays.asList("张三","李四","王五"));
扩容机制:
- 当需要添加元素时,ArrayList会先判断当前容量是否足够,如果不足够,则需要进行扩容操作。
- 扩容操作的第一步是调用
方法,该方法用于确定扩容后的最小容量。
- 如果ArrayList为空,则选择默认容量和传入容量的较大值作为新容量。
- 然后调用
ensureExplicitCapacity
方法,该方法用于判断是否需要进行扩容操作。- 在
ensureExplicitCapacity
方法中,修改操作次数加1,然后判断如果需要的最小容量大于当前容量,则进行扩容操作。- 扩容操作的第二步是调用
grow
方法,该方法用于计算新的容量并进行扩容。- 首先获取当前的容量,然后计算新的容量,一般是原容量的1.5倍,并向上取整。
- 如果新容量小于所需的最小容量,则直接使用最小容量作为新容量。
- 如果新容量超过了最大容量限制,则使用最大容量作为新容量。
- 最后,通过调用
Arrays.copyOf
方法,将原始数组拷贝到新数组中,实现扩容。底层代码:
属性:
private static final int DEFAULT_CAPACITY = 10;// 默认的初始容量是10 // 空元素数组 private static final Object[] EMPTY_ELEMENTDATA = {};//有参构造所创建 // 默认容量的空数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//无参构造所创建的 // 存储元素的数组 transient Object[] elementData;//底层为Object类型的数组,存储的元素都在此。 // 集合容量 private int size;
构造方法 :
空参构造:
构造一个初始容量的空数组。
// 1.构造一个初始容量的空数组。 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
单参(int)构造:
如果参数initialCapacity大于0,给elementData 初始化为长度为initialCapacity的数组;若等于0,初始化为一个长度为0的数组;若小于0则抛出异常
// 2.构造具有指定初始容量的数组。 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); } }
单参(Collection)构造:
传入容器参数,将c容器内的内容赋值给elementData,如果c无内容,则将一个空数组赋值给elementData,同时初始化数组长度size
//3.构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序 public ArrayList(Collection<? extends E> c) { // 将集合构造中的集合对象转成数组 Object[] a = c.toArray(); if ((size = a.length) != 0) { if (c.getClass() == ArrayList.class) { // 类型为ArrayList则直接赋值 elementData = a; } else { //如果不一样,使用Arrays的copyOf方法进行元素的拷贝 elementData = Arrays.copyOf(a, size, Object[].class); } } else { // replace with empty array. elementData = EMPTY_ELEMENTDATA; } }
扩容源码:
进入add方法:
先执行ensureCapacityInternal确保数组elementData的容量足够,再将e添加进elementData
// 添加一个元素 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e;//e 操作对象; elementData 底层操作的数组;size 默认大小0 return true; }
进入ensureCapacityInternal方法:
其中calculateCapacity方法返回elementData需要的最低长度,该整数作为参数传入ensureExplicitCapacity方法中,用于判断是否需要扩容
// 1.主体函数 private void ensureCapacityInternal(int minCapacity) { // 对容量进行判断 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
进入calculateCapacity方法:
如果elementData 为空,则返回max(10, minCapacity),否则直接返回minCapacity
// 2.通过最小容量和默认容量求出较大值 (主要用于第一次扩容:如果是无参构造方式创建的数组对象,第一次添加元素的时候扩容到大小为10) private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
进入ensureExplicitCapacity方法:
如果elementData所需的最小长度大于目前实际的长度,则进入grow方法,进行扩容
// 3.判断是否需要进行扩容 private void ensureExplicitCapacity(int minCapacity) { // 实际修改集合次数+1 (在扩容的过程中没用,主要是用于迭代器中) modCount++; // 判断当前最小容量是否大于数组长度 if (minCapacity - elementData.length > 0) // 将计算出来的容量传递给核心扩容方法 grow(minCapacity); }
进入grow方法:
新数组长度=max(旧数组长度*1.5,数组所需最低长度)。如果新数组长度过大(大于Integer.MAX_VALUE - 8),进入hugeCapacity
// 4.核心扩容方法 private void grow(int minCapacity) { // 记录数组的实际长度 int oldCapacity = elementData.length; // 核心扩容算法,原容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); // 判断新容量是否小于当前最小容量(第一次调用add方法必然小于) if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 判断新容量是否大于最大数组长度,如果 if (newCapacity - MAX_ARRAY_SIZE > 0) // 条件满足就计算出一个超大容量 newCapacity = hugeCapacity(minCapacity); // 调用数组工具类方法,创建一个新数组,将新数组的地址赋值给elementData elementData = Arrays.copyOf(elementData, newCapacity); }
进入hugeCapacity方法:
如果minCapacity溢出,抛出异常,否则返回一个合法的大的值
private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
总结:
ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。(不是原数组,而是新数组然后给予数组对象地址)。
默认情况下,新的容量会是原容量的1.5倍。 新容量=旧容量右移一位(相当于除于2)在加上旧容量
ArrayList 的底层是用动态数组来实现的。我们初始化一个ArrayList 集合还没有添加元素时,其实它是个空数组,只有当我们添加第一个元素时,内部会调用扩容方法并返回最小容量10,也就是说ArrayList 初始化容量为10。 当前数组长度小于最小容量的长度时(前期容量是10,当添加第11个元素时就就扩容),便开始可以扩容了,ArrayList 扩容的真正计算是在一个grow()里面,新数组大小是旧数组的1.5倍,如果扩容后的新数组大小还是小于最小容量,那新数组的大小就是最小容量的大小,后面会调用一个Arrays.copyof方法,这个方法是真正实现扩容的步骤。
小结
Collection(I) 单例,一个一个的存储
List集合(I)有序,可重复
实现类:ArrayList:
所属的包: java.util.*
构造方法:
- ArrayList<E>()
- ArrayList<E>(int init)
- ArrayList<E>(Collection<E> c)
添加元素:
- boolean add(E)---添加元素到集合的末尾
- void add(index,E)---添加元素到指定的位置
- boolean addAll(Collection c)---添加集合到集合的末尾
- boolean addAll(int index,Collection c)---添加集合到集合的末尾
查看元素:
- boolean contains(E) --- 是否包含某个元素
- int indexOf(Object c)--- 查找某个元素存在的下标位置
- E get(int index)---查找对应的下标元素
- boolean isEmpty()
- size() -- 查看元素个数
- List<E> subList(int for,int end)--截取集合
- boolean equals()--
修改元素:oldValue set(int index,E newElement)
删除元素:
- E remove(int index)
- boolean remove(Object obj)
- clear()
- boolean removeAll(Collection c)
- boolean retainAll(Colleciont c)
其他方法:
- clone()克隆一个集合
- sort(Comparator) 排序
- Object[] toArray()
- T[] toArray(T[])
元素遍历:
- for
- foreach
- 普通迭代器 Iterator it = list.iterator();
- hasNext
- next
- list迭代器:ListIterator it = list.listIterator(9);
LinkedList
Vector
Stack
Set集合(I)无序,不可重复
Queue队列(I)先进先出