一、 概念&方法
Collection 表示一组对象,它是集中、收集的意思。Collection接口的两个子接口是List、Set接口。
由于List、Set是Collection的子接口,意味着所有List、Set的实现类都有上面的方法。
如参见代码中test01和test02方法
【list接口】分为3种
List是有序、可重复的容器。List接口常用的实现类有3个:ArrayList(数组实现)、LinkedList(链表)和Vector(线程)。
有序:List中每个元素都有索引标记。可以根据元素的索引标记(在List中的位置)访问元素,从而精确控制这些元素。
可重复:List允许加入重复的元素。更确切地讲,List通常允许满足 e1.equals(e2) 的元素重复加入容器。
除了Collection接口中的方法,List多了一些跟顺序(索引)有关的方法,参见下表(测试代码test03):
1 /*2 *测试list(接口) 需要用类去实现 如实现类ArrayList3 * 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、底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。
双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。
所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。
1 class Node { 2 Node previous; //前一个节点 3 Object element; //本节点保存的数据 4 Node next; //后一个节点 5 }
【代码示例】
1 /* 2 *测试链表 增删改查 3 * 4 */ 5 package cn.sxt.collection; 6 7 public class Test_0324_LinkList<E> { //加了泛型的主类 8 private Node firstNode; //同一个包下的Node类 链表的第一个节点 9 private Node lastNode; // 链表的最后一个节点 10 private int size=0; 11 12 public static void main(String[] args) { //主方法 13 Test_0324_LinkList<String> list=new Test_0324_LinkList<String>(); 14 list.add("a"); 15 list.add("b"); 16 list.add("c"); 17 list.add("d"); 18 System.out.println(list);//toString的好处是在碰到“println”之类的输出方法时会自动调用,不用写出来。 19 //System.out.println(list.get(1));//调用下边的get方法 给个索引位置返回具体元素 20 //list.remove(0); 21 list.insert(2, "我"); 22 System.out.println(list); 23 } 24 25 26 //添加一个新节点的方法 27 public void add(E e) { 28 Node node=new Node(e);//node 为新建的节点 29 if (firstNode==null) {//如果链表为空,新进来一个元素,则第一元素和最后一个元素都是它,因为链表中就一个元素 30 firstNode=node; 31 lastNode=node; 32 33 } else { //如果不为空,新进来一个元素,新元素的前置节点就是原来的最后一个节点,同时把新元素的后置指针置为空 34 node.previous=lastNode; 35 node.next=null; 36 lastNode.next=node;//原来最后一个节点的后置节点就变成一个新节点了 37 lastNode=node; //新的节点就变成最后一个节点了 38 //每多加个字符,size加1 代表链表长度 39 } 40 size++; 41 } 42 43 private void checkRange(int index) { //索引封装 44 if (index<0||index>size-1) {//判断索引位置是否合法 45 throw new RuntimeException("索引数字不合法:"+index);//抛出异常 46 } 47 48 } 49 50 //给个索引位置,返回具体元素的某项指标, 这里是这个元素的数值 51 public E get(int index) { 52 System.out.println(size); //做测试用 测试size的大小 53 54 checkRange(index); 55 Node temp=getNode(index); 56 return (temp!=null)? (E)temp.element:null;// (E)强制转型 如果temp不为空返回冒号前边的temp.element 否则返回null 57 } 58 59 //通过索引返回一个具体元素 60 public Node getNode(int index){ 61 checkRange(index); 62 Node tempNode=null; 63 //优化,当链表中数据比较多时用着快速 64 if (index<=(size>>1)) { //size>>1 右移1位相当于除以2 65 tempNode=firstNode; 66 for (int i=0; i<index; i++) {//通过for循环把索引所在的位置节点找出来,然后输出元素值 67 tempNode=tempNode.next; //如果小于中间值从前往后搜 68 } 69 }else { 70 tempNode=lastNode; 71 for (int i=index-1;i>index; i--) {//大于中间值,从后往前搜 72 tempNode=tempNode.previous; 73 } 74 } 75 return tempNode; 76 } 77 //在链表中插入一个元素 78 public void insert(int index,E e){ //alt+shift+R 替换字母 79 checkRange(index); 80 Node newNode=new Node(e); 81 Node tempNode=getNode(index); 82 if (tempNode!=null) { 83 Node up=tempNode.previous; 84 85 up.next=newNode; 86 newNode.previous=up; 87 88 newNode.next=tempNode; 89 tempNode.previous=newNode; 90 91 } 92 /*if (index==0){ //在第一个位置增加元素的情况。 待完善! 93 firstNode=newNode.next; 94 lastNode=newNode.previous; 95 96 lastNode.next=newNode; 97 newNode.previous=lastNode; 98 } 99 */ 100 size++; 101 102 } 103 104 //在链表中删除一个元素 105 public void remove(int index) { 106 checkRange(index); 107 Node tempNode=getNode(index); 108 if (tempNode!=null) { 109 Node up=tempNode.previous; 110 Node down=tempNode.next; 111 if (up!=null) { 112 up.next=down; //把这个元素后一个元素的地址直接放在这个元素前一个元素的next指针域 113 } 114 if (down!=null){ 115 down.previous=up;// 把这个元素前一个元素的地址直接放在这个元素后一个元素的previous指针域 116 } 117 if (index==0){ //删除第1个元素的情况。 下一个元素是第一个元素 118 firstNode=down; 119 } 120 if (index==size-1){//删除最后1个元素的情况 121 lastNode=up; 122 } 123 } 124 size--; 125 126 } 127 128 129 //输出链表中的元素 toString的好处是在碰到“println”之类的输出方法时会自动调用,不用写出来。 130 public String toString () { 131 StringBuilder sb=new StringBuilder("[");//字符串 对象sb初始化 初始为字符串"[",然后依次追加 132 133 Node tempNode=firstNode; //初始临时节点为链表的第一个节点 134 while (tempNode!=null) {//如果元素不为空则输出元素的数据 135 sb.append(tempNode.element+","); //append 追加单个字符 136 tempNode=tempNode.next; //通过next找到下一个元素 137 } 138 sb.setCharAt(sb.length()-1,']'); //把sb对象的最后单个一个字符设置为"]" 139 return sb.toString(); 140 } 141 142 143 }
Vecto(矢量、向量)
Vector底层是用数组实现的List,相关的方法都加了同步检查,因此“线程安全,效率低”。 比如,indexOf方法就增加了synchronized同步标记。
区别:
如何选用ArrayList、LinkedList、Vector?
1. 需要线程安全时,用Vector。
2. 不存在线程安全问题时,并且查找较多用ArrayList(一般使用它)。
3. 不存在线程安全问题时,增加或删除元素较多用LinkedList。