[19/03/24-星期日] 容器_Collection(集合、容器)之List(表,有顺序可重复)

时间:2023-03-10 02:47:03
[19/03/24-星期日] 容器_Collection(集合、容器)之List(表,有顺序可重复)

一、 概念&方法

Collection 表示一组对象,它是集中、收集的意思。Collection接口的两个子接口是List、Set接口。

由于List、Set是Collection的子接口,意味着所有List、Set的实现类都有上面的方法。

如参见代码中test01和test02方法

[19/03/24-星期日] 容器_Collection(集合、容器)之List(表,有顺序可重复)

【list接口】分为3种

List是有序、可重复的容器。List接口常用的实现类有3个:ArrayList(数组实现)、LinkedList(链表)和Vector(线程)。

有序:List中每个元素都有索引标记。可以根据元素的索引标记(在List中的位置)访问元素,从而精确控制这些元素。

可重复:List允许加入重复的元素。更确切地讲,List通常允许满足 e1.equals(e2) 的元素重复加入容器。

除了Collection接口中的方法,List多了一些跟顺序(索引)有关的方法,参见下表(测试代码test03):

[19/03/24-星期日] 容器_Collection(集合、容器)之List(表,有顺序可重复)

 1 /*
2 *测试list(接口) 需要用类去实现 如实现类ArrayList
3 * Collection接口的两个子接口是List、Set接口
4 */
5 package cn.sxt.collection;
6
7 import java.util.*;
8
9 public class Test_0324_List {
10 public static void main(String[] args) {
11 test03();
12
13
14 }
15 //测试Collection中单个容器的基本操作
16 public static void test01() {
17 Collection<String> c =new ArrayList<>();
18 System.out.println(c.size());//容器中元素的数量
19 System.out.println(c.isEmpty());//容器是否为空
20 c.add("Jack");//容器中增加元素
21 c.add("Tom");
22 System.out.println(c);//输出容器中的元素
23 System.out.println(c.contains("Tom"));//是不是包含Tom 是的话返回true
24
25 Object[] objs = c.toArray(); //把容器转成Object数组
26 System.out.println(Arrays.toString(objs));
27
28 c.remove("Jack");//把"Jack"的引用地址从对象c的容器中移除,并不是在内存中删除"Jack"
29 System.out.println(c);
30 c.clear();//清空所有元素
31 System.out.println(c.size());
32 }
33 //测试Collection中2个容器的操作
34 public static void test02() {
35 List<String> list01=new ArrayList<String>();//List可替换成Collection(因为前者是后者的子接口)
36 list01.add("a");
37 list01.add("b");
38 list01.add("c");
39
40 List<String> list02=new ArrayList<String>();
41 list02.add("a");
42 list02.add("e");
43 list02.add("f");
44
45 System.out.println("list01:"+list01);
46 System.out.println("list02:"+list02);
47
48 list01.addAll(list02);//最终元素落脚点在01容器中 取并集
49 System.out.println("把02的元素全部添加在01中(包括重复的):"+list01);
50 list01.removeAll(list02);//把01中 01和02都有的元素删掉(即a元素)
51 System.out.println("把01和02都有的元素删掉(即a元素):"+list01); //removeAll 移除共有的
52 list01.retainAll(list02);//取交集
53 System.out.println("把01和02都有的元素保留(其它删除):"+list01);//retainAll 保留共有的
54 System.out.println("01是否包含02中的所有元素:"+list01.contains(list02));//contains:包含的
55
56 }
57 //测试list接口中独有的方法 与索引相关
58 public static void test03() {
59 List<String> list=new ArrayList<String>();
60 list.add("A");
61 list.add("B");
62 list.add("C");
63 list.add("D");
64 System.out.println(list);
65
66 list.add(2, "小白");//在下标为2的位置插入一个新的元素,其它往后边移动
67 System.out.println(list);
68
69 list.remove(2);//把下标为2的位置的元素删除
70 System.out.println(list);
71
72 list.set(2, "菜鸟");//把下标为2的位置的元素替换掉
73 System.out.println(list);
74
75 System.out.println(list.get(2));//返回下标为2的处的元素
76
77 list.add("B");
78 System.out.println(list);
79
80 System.out.println(list.indexOf("B"));//返回元素"B"第一次出现时的下标
81 System.out.println(list.indexOf("F"));//返回元素"F"第一次出现时的下标,没有这个元素返回-1
82 System.out.println(list.lastIndexOf("B"));//返回元素"B"最后一次出现时的下标
83
84
85
86 }
89
90 }

ArrayList(数组)

1、底层是用数组实现的存储。 特点:查询效率高,增删效率低,线程不安全。ArrayList底层使用Object数组来存储元素数据。

所有的方法,都围绕这个核心的Object数组来开展。

2、 数组长度是有限的,而ArrayList是可以存放任意数量的对象,长度不受限制,那么它是怎么实现的呢?

本质上就是通过定义新的更大的数组,将旧数组中的内容拷贝到新数组,来实现扩容。ArrayList的Object数组初始化长度为10,

如果我们存储满了这个数组,需要存储第11个对象,就会定义新的长度更大的数组,并将原数组内容和新的元素一起加入到新数组中。

LinkedList(链表): 

1、底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。

双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。

所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。

 class  Node {
Node previous; //前一个节点
Object element; //本节点保存的数据
Node next; //后一个节点
}

[19/03/24-星期日] 容器_Collection(集合、容器)之List(表,有顺序可重复)

【代码示例】

 /*
*测试链表 增删改查
*
*/
package cn.sxt.collection; public class Test_0324_LinkList<E> { //加了泛型的主类
private Node firstNode; //同一个包下的Node类 链表的第一个节点
private Node lastNode; // 链表的最后一个节点
private int size=0; public static void main(String[] args) { //主方法
Test_0324_LinkList<String> list=new Test_0324_LinkList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
System.out.println(list);//toString的好处是在碰到“println”之类的输出方法时会自动调用,不用写出来。
//System.out.println(list.get(1));//调用下边的get方法 给个索引位置返回具体元素
//list.remove(0);
list.insert(2, "我");
System.out.println(list);
} //添加一个新节点的方法
public void add(E e) {
Node node=new Node(e);//node 为新建的节点
if (firstNode==null) {//如果链表为空,新进来一个元素,则第一元素和最后一个元素都是它,因为链表中就一个元素
firstNode=node;
lastNode=node; } else { //如果不为空,新进来一个元素,新元素的前置节点就是原来的最后一个节点,同时把新元素的后置指针置为空
node.previous=lastNode;
node.next=null;
lastNode.next=node;//原来最后一个节点的后置节点就变成一个新节点了
lastNode=node; //新的节点就变成最后一个节点了
//每多加个字符,size加1 代表链表长度
}
size++;
} private void checkRange(int index) { //索引封装
if (index<0||index>size-1) {//判断索引位置是否合法
throw new RuntimeException("索引数字不合法:"+index);//抛出异常
} } //给个索引位置,返回具体元素的某项指标, 这里是这个元素的数值
public E get(int index) {
System.out.println(size); //做测试用 测试size的大小 checkRange(index);
Node temp=getNode(index);
return (temp!=null)? (E)temp.element:null;// (E)强制转型 如果temp不为空返回冒号前边的temp.element 否则返回null
} //通过索引返回一个具体元素
public Node getNode(int index){
checkRange(index);
Node tempNode=null;
//优化,当链表中数据比较多时用着快速
if (index<=(size>>1)) { //size>>1 右移1位相当于除以2
tempNode=firstNode;
for (int i=0; i<index; i++) {//通过for循环把索引所在的位置节点找出来,然后输出元素值
tempNode=tempNode.next; //如果小于中间值从前往后搜
}
}else {
tempNode=lastNode;
for (int i=index-1;i>index; i--) {//大于中间值,从后往前搜
tempNode=tempNode.previous;
}
}
return tempNode;
}
//在链表中插入一个元素
public void insert(int index,E e){ //alt+shift+R 替换字母
checkRange(index);
Node newNode=new Node(e);
Node tempNode=getNode(index);
if (tempNode!=null) {
Node up=tempNode.previous; up.next=newNode;
newNode.previous=up; newNode.next=tempNode;
tempNode.previous=newNode; }
/*if (index==0){ //在第一个位置增加元素的情况。 待完善!
firstNode=newNode.next;
lastNode=newNode.previous; lastNode.next=newNode;
newNode.previous=lastNode;
}
*/
size++; } //在链表中删除一个元素
public void remove(int index) {
checkRange(index);
Node tempNode=getNode(index);
if (tempNode!=null) {
Node up=tempNode.previous;
Node down=tempNode.next;
if (up!=null) {
up.next=down; //把这个元素后一个元素的地址直接放在这个元素前一个元素的next指针域
}
if (down!=null){
down.previous=up;// 把这个元素前一个元素的地址直接放在这个元素后一个元素的previous指针域
}
if (index==0){ //删除第1个元素的情况。 下一个元素是第一个元素
firstNode=down;
}
if (index==size-1){//删除最后1个元素的情况
lastNode=up;
}
}
size--; } //输出链表中的元素 toString的好处是在碰到“println”之类的输出方法时会自动调用,不用写出来。
public String toString () {
StringBuilder sb=new StringBuilder("[");//字符串 对象sb初始化 初始为字符串"[",然后依次追加 Node tempNode=firstNode; //初始临时节点为链表的第一个节点
while (tempNode!=null) {//如果元素不为空则输出元素的数据
sb.append(tempNode.element+","); //append 追加单个字符
tempNode=tempNode.next; //通过next找到下一个元素
}
sb.setCharAt(sb.length()-1,']'); //把sb对象的最后单个一个字符设置为"]"
return sb.toString();
} }

Vecto(矢量、向量)

Vector底层是用数组实现的List,相关的方法都加了同步检查,因此“线程安全,效率低”。 比如,indexOf方法就增加了synchronized同步标记。

区别:

如何选用ArrayList、LinkedList、Vector?

1. 需要线程安全时,用Vector。

2. 不存在线程安全问题时,并且查找较多用ArrayList(一般使用它)。

3. 不存在线程安全问题时,增加或删除元素较多用LinkedList。