1.集合的出现:
由于数组的长度不可变,这时我们需要一个特殊的类,这些类可以存储任意类型的对象,并且长度可变,这些类统称为集合。
集合按照存储结构可以分为两大类:分别是单列集合Collection和双列集合Map,在本篇文章中主要讲的是Collection集合。
Collection集合特点如下:
- Collection是所有单列集合类的根接口,它用于存储符合某种规则的元素。
- 它有两个子接口:Set和List
Set 和 List的区别:
- Set的特点是元素无序且不可重复
- List的特点是元素有序且元素可重复
其中Collection、List、Set都是接口类型,而它们其余的子类为具体的实现类。
Collection接口通用的一些方法示例:
public static void main(String[] args) {
//创建一个集合类对象,并且使用具体的实现类
Collection<Integer> coll = new ArrayList<>();
coll.add(3);//向集合中添加元素3
coll.clear();//清空集合,但集合本身还存在,[]
// coll.removeAll(coll);//删除集合所有元素,功能同上
System.out.println(coll.isEmpty());//判断集合是否为空,是返回true,不是返回false
coll.add(4);
System.out.println(coll);//打印集合所有元素
System.out.println(coll.contains(4));//判断是否包含4这个元素
coll.add(5);
coll.add(6);
coll.add(7);
coll.remove(7);//删除元素7
Iterator it = coll.iterator();//使用迭代器遍历该集合所有元素
while (it.hasNext()){
System.out.println(it.next());//打印每一个元素
}
System.out.println(coll.size());//获取该集合元素个数
}
List接口:
- List接口继承自Collection接口,是单列集合的一个重要分支,将实现了List接口的对象称为List集合。
- List集合中允许出现重复的元素,所有的元素以一种线性的方式进行存储,在程序中可以通过索引来访问集合中的指定元素。
- List集合还有一个特点就是元素有序,即元素的存入顺序和元素的取出顺序一致。
List的通用方法示例:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("AMD");
list.add("青菜");
list.add("胡萝卜");
list.add(0,"小米椒");
list.add("AMD");
System.out.println(list.get(1));
list.set(2,"金针菇");
list.remove(0);
System.out.println(list);
System.out.println(list.indexOf("AMD"));
System.out.println(list.lastIndexOf("AMD"));
//List的实现类都可以通过以下方式遍历集合
System.out.println("======遍历集合的三种方式======");
//for循环
for (int i = 0; i < linked.size(); i++) {
System.out.println(linked.get(i));
}
//迭代器
System.out.println("------------");
Iterator<String> it = linked.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
//foreach
System.out.println("------------");
for (String s : linked){
System.out.println(s);
}
}
ArrayList:
- ArrayList和数组一样,都有索引,它的索引从0开始,size-1结束。
- ArrayList的底层是使用一个数组来保存元素,在其内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素。所以在增加或删除指定的位置的元素的时候,会导致创建新的数组,效率会很低下,因此不适合做大量的增删操作。
- 因为其含有索引的特殊性,如果是使用查找功能居多,可以采用ArrayList集合。
ArrayList的通用方法示例:
public static void main(String[] args) {
//创建一个ArrayList集合
ArrayList<String> list = new ArrayList<>();
list.add("AMD");//添加元素
list.add("Per01");
list.add("Per02");
list.add("Per03");
list.add("AMD");//AMD,YES!!!
list.remove(2);//删除索引为2的元素Per02
list.set(1,"YES");//将索引为1的元素值替换为YES
System.out.println(list.get(1));//获取索引值为1的元素
System.out.println(list.size());//获取集合的容量大小
System.out.println(list.indexOf("AMD"));//返回“AMD”在集合中第一次出现的索引位置
System.out.println(list.lastIndexOf("AMD"));//返回“AMD”在集合中最后一次出现的索引位置
}
在上面讲到,如果向ArrayList集合存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素。
那么如何分配数组,分配多大数组呢?这就不得不提及到ArrayList的扩容机制
ArrayList的扩容机制:
在查询相关资料和对源码的分析得知:
- 创建ArrayList对象的时候:如果没有指定集合的容量,集合的默认容量为0
- 初始化:当第一次往里添加元素时,ArrayList将通过构造方法初始化一个容量为10的数组
- 扩容的时机:当往集合中添加第11个元素(超过当前数组的初始容量10)时,会进行扩容,扩容的容量为原始的1.5倍,即数组容量扩容变成(15、22、33、49…)
- 如果新增后的容量超过当前扩容的容量,即1.5倍的容量大小不够(例如使用addAll()方法,添加进来整个集合)。则集合容量为新增后所需的最小容量(即元素有多少个,容量就为多少)。
- 如果增加0.5倍后的新容量超过限制的容量,则用所需的最小容量(元素的个数)与限制的容量进行比对判断,如果超过则指定为Integer的最大值,否则指定为限制容量大小(如何理解: 当元素的个数超过了当前容量,需要进行扩容,变成原来的1.5倍大小时,如果超过了限制的容量(即规定的最大容量),会进行一个判断,判断你的元素是否能把1.5倍的集合填满,如果填满了,会使用Integer的最大值来作为容量的大小。否则,没有填满,就会采用MAX_ARRAY_SIZE,即所需的最小容量(元素的个数))
- 扩容的方式:当数组进行扩容的时候,会以新的容量(即1.5倍)建一个原数组的拷贝,修改原数组,使用Arrays.copyOf方法将elementData数组指向这个进行扩容后的新数组,原数组被抛弃,被GC进行垃圾回收。
上面我们提到,ArrayList的查询元素速度很快,但是在增删元素的时候效率会比较低,为了克服这种局限性,我们不得不提到它的另外的一个好兄弟LinkedList
LinkedList:
- 由于Linked是List的实现类,所以它也有索引,从0开始,size-1结束
- LinkedList集合的底层是维护了一个双向循环链表,链表中的每一个元素都使用引用的方式来记住它的前一个元素和后一个元素,从而可以将所有的元素彼此连接起来
- 当插入一个新元素或删除某一个结点时,只需要修改元素之间的这种引用关系即可。正是因为这样的存储结构,所以LinkedList集合对于元素的增删操作有着很高的效率
LinkedList集合新增元素和删除元素的过程:
新增一个元素时:元素1和元素2在集合中彼此为前后关系,在它们之间新增一个元素时,只需要让元素1记住它后面的元素是新元素,让元素2记住它前面的元素是新元素即可。
删除一个元素时:想要删除元素1和元素2之间的元素3,只需要让元素1和元素2相互引用,变成前后关系就可以了。
LinkedList通用方法示例:
public static void main(String[] args) {
//创建LinkedList集合
LinkedList<String> linked = new LinkedList<>();
linked.add("stu1");//添加元素
linked.add("stu2");
linked.add("stu3");
System.out.println(linked);//默认调用toString方法,打印集合所有元素
linked.add(1,"Student");//指定索引位置插入元素
linked.addFirst("First");//集合第一个位置插入元素
linked.addLast("Last");
System.out.println(linked.get(2));//获取索引为2的元素
System.out.println(linked.getFirst());//获取第一个元素
System.out.println(linked.getLast());
linked.remove(3);//移除索引为3的元素
linked.removeFirst();//移除第一个元素
linked.removeLast();
}
扩展: ListIterator接口:
我们知道,通过Iterator迭代器提供的hasNext()方法和next()这个两个方法可以实现集合中元素的迭代,且迭代的方向是从集合中的第一个元素向最后一个元素迭代,也就是所谓的正向迭代。为了使迭代的方式更加多元化,JDK还定义了一个ListIterator迭代器,它是Iterator接口的子类,通过此接口可以实现反向迭代集合元素。
不过要注意的是:ListIterator迭代器只能用于List集合
代码演示:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("data_1");
list.add("data_2");
list.add("data_3");
list.add("data_4");
System.out.println(list);
System.out.println("=============");
ListIterator<String> it = list.listIterator(list.size());
while (it.hasPrevious()) {
System.out.println(it.previous());
}
}
Enumeration接口:
现在,我们遍历集合的时候可以使用 Iterator 接口,但在JDK1.2以前还没有 Iterator 接口的时候,遍历集合需要 Enumeration 接口,它的用法和 Iterator 类似,在很多的程序中依然使用Enumeration,所以了解是很有必要的。
Vector集合:
- Vector集合是List的一个实现类,它的用法与 ArrayList 完全相同
- 它和 ArrayList 区别在于:Vector集合是线程安全的,ArrayList 集合是线程不安全的
- 在 Vector 类中提供了一个elements()方法用于返回Enumeration对象,通过Enumeration对象就可以遍历集合中的元素
代码演示:
public static void main(String[] args) {
Vector<String> v = new Vector<>();
v.add("stu1");
v.add("stu2");
v.add("stu3");
Enumeration en = v.elements();
while (en.hasMoreElements()) {
System.out.println(en.nextElement());
}
}