java List集合框架(collection,ArrayList,Vector,LinkedList)
List集合框架讲解
- 一、什么是集合框架
- 1、集合:
- 2、集合框架:
- 3、collection方法:
- 4、迭代:
- 4.1、使用for-each循环:
- 4.2、使用迭代器:
- 4.3、使用forEach()方法:
- 二、ArrayList集合
- 1、List集合:
- 2、ArrayList集合:
- 2.1、数组结构:
- 2.2、增长因子为1.5
- 三、Vector集合
- 1、数组结构:
- 2、增长因子为2:
- 3、线程同步:
- 4、枚举遍历:
- 四、LinkedList集合
- 1、链表结构(双向链表结构)
- 五、应用及补充
- 1、list集合的调优
- 2、使用LinkedList模拟出堆栈和队列的效果
- 2.1、堆栈
- 2.2、队列
- 3、去除集合框架ArrayList中的重复元素
一、什么是集合框架
在了解什么是集合框架之前我们想先来了解了解什么是集合。
1、集合:
通常情况下,把具有相同性质的一类东西,汇聚成一个整体,就可以称为集合。
比如:所有在上小学的人,小学生们。或者说所有的java程序员们。又或者说,老师们。像小学生,他们都具有相同的性质,如年纪小,在上学,打游戏坑队友。至于java程序员和老师的相同的性质,我就不在这里一一陈述了。
那么有了集合的概念,什么是集合框架呢?
2、集合框架:
集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。
-
接口:是代表集合的抽象数据类型。接口允许集合独立操纵其代表的细节。在面向对象的语言,接口通常形成一个层次。
-
实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构。
-
算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。
而且他们在被设计时,就被定下以下几个目标:
-
该框架必须是高性能的。基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的。
-
该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性。
-
对一个集合的扩展和适应必须是简单的。
所以,其实说简单些集合就是装数据的容器,数据多了用对象进行存储,对象多了用集合来进行存储。而存储数据的方式(数据结构)各有不同,所以存储的容器也就有多种,从而形成了集合框架这一体系。就比如箱子都是用来装东西的,但是由于有的箱子装的是金钱,所以叫保险柜。有的装衣服,所以叫做衣柜。
3、collection方法:
万变不离其宗,那么所有人都有自己血脉的起点,我们中国人都自称我们是炎黄子孙。那么在代码里有没有老祖宗呢?答案当然是,有。而且在Java中老祖宗的东西(public方法)是会一代一代传承下来的,而所有集合的老祖宗就是collection。那么collection的方法,其他集合也都可以使用。
接下来让我们看看collection有哪些方法吧!
4、迭代:
在我们把某些数据放入集合后,我们总有一天会想看看这些数据。那么我们该如何去看我们的数据呢?其实方法主要有三种。
4.1、使用for-each循环:
Collection<Object> ajj=new ArrayList<>();//声明一个集合ajj//ajj添加数据ajj.add("aa");ajj.add("bb");ajj.add("cc");ajj.add("dd");ajj.add("ee");ajj.add("ff");for (Object o : ajj) {//fore遍历System.out.println(o);//打印结果为:aa bb cc dd ee ff}
4.2、使用迭代器:
Collection<Object> ajj=new ArrayList<>();//声明一个集合ajj//ajj添加数据ajj.add("aa");ajj.add("bb");ajj.add("cc");ajj.add("dd");ajj.add("ee");ajj.add("ff");Iterator<Object> it = ajj.iterator();//声明一个迭代器进行遍历while (it.hasNext()) {//hasNext()方法:如果仍有元素可以迭代,则返回 true。System.out.println(it.next());//next()方法:返回迭代的下一个元素。//打印结果为:aa bb cc dd ee ff}
4.3、使用forEach()方法:
Collection<Object> ajj=new ArrayList<>();//声明一个集合ajj//ajj添加数据ajj.add("aa");ajj.add("bb");ajj.add("cc");ajj.add("dd");ajj.add("ee");ajj.add("ff");ajj.forEach(System.out::println);//打印结果为:aa bb cc dd ee ff
我们这里重点来讲一下迭代器,首先我们来讲讲什么是迭代,简单的,通俗的解释,老师在班上点人就是在迭代。而有一天老师终于发现天天数人好烦啊!所以,老师要求学生每天打卡上课。那么打卡机就是这个迭代器。
那么迭代器(Iterator)有哪些方法呢?
Collection<Object> ajj=new ArrayList<>();//声明一个集合ajj//ajj添加数据ajj.add("aa");ajj.add("bb");ajj.add("cc");ajj.add("dd");ajj.add("ee");ajj.add("ff");Iterator<Object> it = ajj.iterator();//声明一个迭代器boolean f=it.hasNext();//判断ajj集合中是否有元素。System.out.println(f);//打印结果为:true
Collection<Object> ajj=new ArrayList<>();//声明一个集合ajj//ajj添加数据ajj.add("aa");ajj.add("bb");ajj.add("cc");ajj.add("dd");ajj.add("ee");ajj.add("ff");Iterator<Object> it = ajj.iterator();//声明一个迭代器Object o= it.next();//返回迭代的下一个元素System.out.println(o);//打印结果为:aa
Collection<Object> ajj=new ArrayList<>();//声明一个集合ajj//ajj添加数据ajj.add("aa");ajj.add("bb");ajj.add("cc");ajj.add("dd");ajj.add("ee");ajj.add("ff");Iterator<Object> it = ajj.iterator();//声明一个迭代器System.out.println(ajj);//打印结果为:[aa, bb, cc, dd, ee, ff]Object o= it.next();//指针下移 System.out.println(o);//打印结果为:aait.remove();//删除 aa,remove()方法:从迭代器指向的ajj集合中移除迭代器返回的最后一个元素(aa)System.out.println(ajj);//打印结果为:[bb, cc, dd, ee, ff]
remove()方法:
remove()方法是一个比较特殊的方法,remove()方法删除 next()方法最后返回的元素。每次调用next()方法只能调用一次 remove()方法。如果对于每个 next()方法或在第一次调用next()之前被多次调用 remove()方法,它会抛出一个 IllegalStateException异常。所以他是要课next()方法配合起来使用的。
以上这些就是迭代器(Iterator)的方法。
二、ArrayList集合
在了解ArrayList集合集合之前我们要看看他的爸爸,List集合。
1、List集合:
List集合是Collection的儿子,所以在一定程度上List集合发挥了代码家族的特点,一代更比一代强。相当于Collection来说List集合是有下标的,而且元素是可重复的。而且对于collection的方法来说,List不但继承了Collection的所以方法,还拥有自己的特有方法。如以下方法:
以上就是List所特有的方法,相当于Collection而言。其实不光是方法,就连List的迭代器都和Collection不一样。Collection的迭代器是Iterator,而List的迭代器是listIterator。
看完这些方法你就可以方法,不同主要有以下几点:
-
iterator()方法在set和list接口中都有定义,但是ListIterator()仅存在于list接口中(或实现类中);
-
ListIterator有add()方法,可以向List中添加对象,而Iterator不能
-
ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
-
ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
-
都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
2、ArrayList集合:
ArrayList是List的实现类(List的儿子),所以在一般情况下List的方法ArrayList都可以使用。ArrayList的方法,在这里我就不一一陈述了。这里我们主要讨论一下ArrayList数据结构特点。
2.1、数组结构:
ArrayList的数据结构是数组结构,这也导致了ArrayList增删慢,查询快,有连续下标。
为什么会增删慢,查询快呢?我们来看看:
这是一个数组,如果我们将元素45删除,会发生什么呢?
就会变成这样,元素45被删除之后在45后面的元素的下标都会减一。增加反之。现在我的集合只有3-4个元素,但是如果是一个有1000个元素的集合呢?随意删除一个元素,其他元素的下标都要更改。因此才导致了ArrayList增删慢,查询快。
2.2、增长因子为1.5
什么是增长因子呢?其实集合是一个可以改变长度的数组,在ArrayList集合被实例化时,是有初始长度的(10),每当增加的元素,导致集合总长度超过10的时候,集合的长度就会乘以1.5。使元素始终可以存放到集合中。我们来看看以下代码:
package com.zking.collection;import java.lang.reflect.Field;
import java.util.ArrayList;/*** 探究list集合的增长因子* @author ajj**/
public class ListGrowthFactorDemo {public static void main(String[] args) throws Exception {ArrayList li=new ArrayList<>();//定义一个初始容器量为60的ArrayList集合for (int i = 1; i < 60; i++) {//循环li.add(i);//循环添加60次System.out.print(i+",");//打印出当前添加的元素getln(li);//打印出当前容量}}public static void getln(ArrayList li)throws Exception {Field f = li.getClass().getDeclaredField("elementData");f.setAccessible(true);Object obj=f.get(li);Object[] elementData=(Object[]) obj;System.out.println("当前容器的容量是:"+elementData.length);}
}
他的运行结果为:
从上面两张图我们可以发现,每次当集合的容量要不足时。他就马上将集合长度乘以1.5。以此来实现集合长度可变(变长不变短)。
三、Vector集合
Vector也是List的实现类(List的儿子),但是这个儿子不同,他有一个很奇特的特点是其他集合所没有的,也就是它和他和枚举有关系,它可以用枚举遍历数据,当然它也可以使用迭代器进行遍历。
1、数组结构:
这是Vector和ArrayList的相同点,他们都是数组结构。
2、增长因子为2:
相比ArrayList,Vector的增长因子更大,每次当集合的容量要不足时。他就马上将集合长度乘以2。以此来实现Vector集合长度可变(变长不变短)。
3、线程同步:
这也是Vector集合的一大特点之一。他的线程是同步,这怎么解释呢?
我们先来看看它为什么线程同步,以下是Vector的源代码。
我们可以明显的看到他的方法前面加了 synchronized(同步的),Vector的大多数方法都加了 synchronized(同步的),所以它可以实现线程同步。
4、枚举遍历:
Vector<Object> ve=new Vector<>();//定义一个Vector集合//添加数据ve.add("aa");ve.add("bb");ve.add("cc");ve.add("dd");ve.add("ee");Iterator<Object> it = ve.iterator();//定义迭代器while (it.hasNext()) {//进行集合判断System.out.println(it.next());//打印结果:aa bb cc dd ee}Enumeration<Object> el = ve.elements();//定义一个Enumeration(枚举)while (el.hasMoreElements()) {//hasMoreElements()方法:测试此枚举是否包含更多的元素。//nextElement()方法:如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。System.out.println(el.nextElement());//打印结果:aa bb cc dd ee}
由以上代码我们可知,Vector既可以用迭代器(iterator)进行遍历,又可以用枚举(Enumeration)进行遍历。在这里我们来浅淡了解一下枚举(Enumeration),其实在某种程度上而已,枚举和迭代器的功能上是相同的,都是为了打印出数据。而不同的是,迭代器在这方面上功能更强大(个人看法)。为什么这么说呢?因为枚举(Enumeration)接口只有两个方法,就是 nextElement()和hasMoreElements(),有且仅有这两个方法。
四、LinkedList集合
LinkedList集合和其他集合有着本质上的区别,因为LinkedList集合的数据结构是链表结构,准确的来说,是链表结构(单向链表、循环链表、双向链表)中的双向链表结构。
1、链表结构(双向链表结构)
那么双向链表结构是一种什么样的数据结构呢?我们来看看下面这两张图:
我们从上图的第二张图可以看出,这是一个双向链表结构,它有且仅有三个节点。每个节点中有三个属性,previous上一个节点,data当前节点,next下一个节点。如果我们在LinkedList集合插入三个元素,aa,bb,cc。那么,bb的previous为aa,bb的next为cc,而bb的data为bb。其中previous为null的为这个链表的头(第一个元素first),而next为null的这个链表的尾(最后一个元素last)。
那么双向链表结构的删除和增加都是怎样操作的呢?
由上图可知,在链表中插入15这个元素,元素10的next就会改为15,而20的previous也会改为15,从而组成一个新的链表,删除类似。
五、应用及补充
1、list集合的调优
结合以上的讲述我们都知道了,ArrayList的增长因子是1.5。并且当集合容量不足时会自动增长,但是我们看看最后的结果:
集合中的元素实际长度为59,而容器的长度已经达到了73。很明显有的长度浪费掉了。那么我们知道来个集合进行调优呢?我们来看看以下代码:
package com.test;import java.lang.reflect.Field;
import java.util.ArrayList;public class Test0526 {public static void main(String[] args) throws Exception {ArrayList<Object> li = new ArrayList<>(60);// 定义一个初始容器量为60的ArrayList集合for (int i = 1; i < 60; i++) {// 循环60li.add(i);// 循环添加60次System.out.print(i + ",");// 打印出当前添加的元素getln(li);// 打印出当前容量}}public static void getln(ArrayList<Object> li) throws Exception {Field f = li.getClass().getDeclaredField("elementData");f.setAccessible(true);Object obj = f.get(li);Object[] elementData = (Object[]) obj;System.out.println("当前容器的容量是:" + elementData.length);}
}
在上面我们就可以看出来,我在一开始定义集合的时候,就同时也定义了集合长度。这样一来,结果如下:
结果就是,元素实际长度为60,而集合长度也为60。当然这需要你一开始就知道要添加多元素,但是如果你不知道要添加多少元素时该怎么办呢?输入一个接近实际值的数就ok了。一般误差都不会很大的。
2、使用LinkedList模拟出堆栈和队列的效果
首先我们要了解一下,什么是堆栈?什么是队列?
2.1、堆栈
其实堆栈和队列仅仅是两种数据结构而已,堆栈这种数据结构很像弹夹。如果我们把子弹标上序号,我们往弹夹里压十颗子弹,最先压进去的1号子弹。最后一颗子弹是10号子弹。那么这个过程我们可以看成是我们往一个堆栈数据结构的集合,添加数据。从1到10,那么当我们装好子弹准备开枪时,最开始打出来的子弹是那一颗呢?当然是10号子弹,接下来依次9号,8号,7号,6号…1号。我们可以把这个过程看作我们从集合里遍历元素。
接下来我们来看看代码吧:
package com.zking.collection;import java.util.Iterator;
import java.util.LinkedList;public class LinkedListDemo {public static void main(String[] args) {Duizhan duizhan=new Duizhan();//定义一个具有堆栈存储结构特点的一个容器//增加数据duizhan.push("a1");duizhan.push("a2");duizhan.push("a3");duizhan.push("a4");duizhan.push("a5");//进行遍历duizhan.bianli();//遍历结果:a5 a4 a3 a2 a1与增加顺序相反}
}/*** 模拟LinkedList完成具有堆栈存储结构特点的一个容器**/
class Duizhan{private LinkedList<Object> ll=new LinkedList<>();//定义一个LinkedList集合public void push(Object obj){//定义增加方法ll.addFirst(obj);//永远将新增的元素放在集合的首位。}public void bianli() {//定义遍历方法Iterator<Object> it=ll.iterator();//定义迭代器while(it.hasNext()) {//进行元素是否为空判断System.out.println(it.next());//遍历元素}}
}
由以上我们可以得出,堆栈就是一种头入尾出的数据结构。
2.2、队列
队列这种数据结构是一种非常常见的数据结构,它比较像水管。比如说元素从A端进去从B出来,所以它的顺序是保持一致的。比如以A,B,C,D,E的顺序增加至集合就会以A,B,C,D,E的顺序遍历出来。
多说无益,我们来看代码:
package com.zking.collection;import java.util.Iterator;
import java.util.LinkedList;public class LinkedListDemo {public static void main(String[] args) {Duizhan duizhan=new Duizhan();//定义一个具有堆栈存储结构特点的一个容器//增加数据duizhan.push("a1");duizhan.push("a2");duizhan.push("a3");duizhan.push("a4");duizhan.push("a5");//进行遍历duizhan.bianli();//遍历结果:a1 a2 a3 a4 a5与增加顺序相同}
}/*** 模拟LinkedList完成具有堆栈存储结构特点的一个容器**/
class Duizhan{private LinkedList<Object> ll=new LinkedList<>();//定义一个LinkedList集合public void push(Object obj){//定义增加方法ll.addLast(obj);//永远将新增的元素放在集合的末位。 }public void bianli() {//定义遍历方法Iterator<Object> it=ll.iterator();//定义迭代器while(it.hasNext()) {//进行元素是否为空判断System.out.println(it.next());//遍历元素}}
}
由以上代码我们可以得出,队列数据结构,增加元素的顺序和遍历元素的顺序是一致的。
3、去除集合框架ArrayList中的重复元素
这个我们就话不多说上代码:
package com.zking.collection;import java.util.ArrayList;
import java.util.Iterator;public class ListEliminateRepeatDemo {public static void main(String[] args) {ArrayList<Object> list=new ArrayList<>();//定义一个ArrayList数据结构//添加数据list.add(new Person("aa", 15));list.add(new Person("bb", 16));list.add(new Person("cc", 17));list.add(new Person("dd", 18));list.add(new Person("vv", 18));list.add(new Person("aa", 15));ArrayList<Object> list2 = singleList(list);//进行去重复处理Iterator<Object> it = list2.iterator();//定义迭代器while (it.hasNext()) {//进行判空Person next = (Person)it.next();//进行数据转换System.out.println(next.getName());//打印结果为:aa bb cc dd vv。最后一个aa被去除。}}/*** 思路:* * 将原有的集合遍历* * 遍历后的元素存放到新的集合中* * 存放之前做一个字符串判断* * @param list* @return*/public static ArrayList<Object> singleList(ArrayList<Object> list) {//定义一个去除重复方法ArrayList<Object> newAL=new ArrayList<>();//定义一个ArrayList数据结构Iterator<Object> it=list.iterator();//定义迭代器while(it.hasNext()) {//进行判空Object next = it.next();//将元素取出if(!newAL.contains(next)){//进行元素重复判断,contains方法会自动调用底层源代码从而调用equals方法,同样的Remove也会调用。//判断next元素是否在newAL集合中已经出现过newAL.add(next);//如果没有出现过就将next元素添加至newAL集合中}}return newAL;//反回newAL集合}
}class Person{//定义一个内部类Personprivate String name;//定义属性String nameprivate int age;//定义属性int agepublic String getName() {//公开设/取方法return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Person(String name, int age) {this.name = name;this.age = age;}public boolean equals(Object obj){//重写equals方法if(obj instanceof Person) {//如果调用方法的类型是Person类型Person p=(Person) obj;//数据转换return this.getName().equals(p.getName())&&this.getAge()==p.getAge();//进行姓名和年龄的判断,如果一致则返回true。//在这里解释一下,比如"ajj".equals("akk")。那么这个ajj就是上面的this,而akk就是obj。简单地说谁点equals()方法,谁就是this。}return false;//} }
由以上代码,我们可以看出。contains()方法会自动调用equals()方法,所以我们要重写equals()方法进行判断,从而达到去重复的效果。