Java 集合可分为 Collection 和 Map 两大体系:
-
Collection接口:用于存储一个一个的数据,也称
单列数据集合
。-
List子接口:用来存储有序的、可以重复的数据(主要用来替换数组,"动态"数组)
-
实现类:ArrayList(主要实现类)、LinkedList、Vector
-
-
Set子接口:用来存储无序的、不可重复的数据(类似于高中讲的"集合")
-
实现类:HashSet(主要实现类)、LinkedHashSet、TreeSet
-
-
-
Map接口:用于存储具有映射关系“key-value对”的集合,即一对一对的数据,也称
双列数据集合
。(类似于高中的函数、映射。(x1,y1),(x2,y2) ---> y = f(x) )-
HashMap(主要实现类)、LinkedHashMap、TreeMap、Hashtable、Properties
-
ArrayList 介绍
ArrayList:
•始于JDK1.2
•作为List接口的主要实现类,一般情况下我们如果希望将一些内容存储下来会优先考虑采用ArrayList。
•使用数组作为数据存储的载体,其原理也是数组。
•针对于元素的获取/遍历效率高。
•针对于元素的频繁增删改效率低。
ArrayLIst底层的数据结构是数组,这个数组的初始容量是10
。在第一次执行
add方法的时候,首先会执行一个ensureCapacityInterval方 法,在这个方法中,会首先执行一个计算容量的方法,在这个方法中,首先会 进行一次判断,对存放元素的数组 和 默认的一个空数组 进行比较,如果他们 两个是一个地址,就证明这存放元素的这个数组此时是空的,那么就会返回默 认值10。 然后会进行判断,由于此时数组的容量仍是零,在进行下一步对返回的 minCapacity
和
elementData 数组的长度进行比较的时候,由于 minCapacity > elementData.length
,会执行
if
语句内部的代码,执行grow 方法,在
grow方法中,会重新创建一个数组对象,这个数组对象就是初始容 量
10
,然后把这个数组对象赋值给
elementData
,此时
element对象的容量 就变成了
10
。
在
jdk1.6
版本中,
Array Iist
类在初始化的时候,容量会自动赋值为
10.前面我 介绍的是
jdk8
,这二者有所不同。如果按照
1.6的做法,那么每当实现一个 ArrayList对象的时候就要给十个容量,如果后面没有用到的话那么就会造成 资源上的浪费,而
1.8的做法只是在用的时候再初始化容量,可以一定程度上 避免资源浪费。
常用方法
add方法:
下
add
方法,add方法的主要流程就是第一步先对容量进行一个判断,如果容量够的情况下,就可以直接添加;如果容量不够,那么就需要扩容,进入到
grow方法中,在这个方法里,首先会计算出一个基于原来容量
1.5倍大的容量,然后在对这个新计算出来的容量进行比较,看看它是否满足所需容量的大小,如果满足最终容量,就会取用这个值,如果不能满足,那么就取能够满足需求的最小容量的值。在确定容量大小之后,再对这个值进行一次合法性校验,然后通过
java.util.Arrays
类中的CopyOf方法,将原数组中的元素拷贝到一个容量为扩容之后大小的新数组中,然后再进行添加操作就可以按序添加了。这就是
add
方法的流程。如图:
首先执行calculateCapacity方法
得到容量之后判断容量够不够
不够则执行grow方法
对于
ArrayLIst
来说,如果经常进行扩容是一种很浪费资源的行为,所以如果在允许
的情况下,可以提前定义好容量,减少扩容的这个过程,进而增强效率。
get(int index):获取指定index处的元素
public E get(int index) {
rangeCheck(index);<------首先,通过这行代码判断索引是否有效。
return elementData(index); <-------------------通过这行代码拿到指定索引的元素
}
E elementData(int index) {
return (E) elementData[index];
}
remove方法:
//通过index,得到需要移动元素数目,从移除索引的元素的后面的所有的元素用arraycopy方法,往前提一位,覆盖掉你想移除的那个 。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
//这个原理和上面的差不多,就是加一个for循环。
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);//除了没有返回值,和上面的remove方法基本相同。
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
indexof:这个方法很简单,就是从前往后遍历数组,调用equals方法(调用哪个equals决定你传入的形参 o 是什么类型的),遇到第一个与之相等的值 就返回index。
public int indexOf(Object o) {
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;
}
lastIndexOf:
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
//原理和indexOf一样,不过就是从后往前遍历数组罢了。
subList
方法:
subList
()方法执行的操作是对于ArrayList 进行部分截取,方法有两个参数
x,y
,截取的部分是
[x , y) 。方法的返回值是 一个
SubList
对象,这是一个
ArrayList
的内部类,继承自
AbstractList
接口。
在接返回值得时候时候一般是
List
类型。在进行
subList操作的时候要注意,操 作之后不能对原
arrayLIst进行修改,否则会抛出 concurrentModifyException
。因为
subList在截取的时候,并没有把原 arrayList
中的元素拿出来,而是和
arrayList共享一份数据。可以理解为这是两 个容器,
subLIst
是一个小容器,
arrayList是一个大容器,部分数据在小容器里,小容器在大容器里。所以
subList在对它里面的元素进行修改的时候,这时候我们读
arrayList里的元素发现,这里面被截取的那部分的元素也会随之改变。
可以看出元素的的地址是相同的,而这两个容器并不是一个地址,印证了之前的说
法。