欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java开发笔记(六十七)清单:ArrayList和LinkedList

程序员文章站 2022-04-08 19:52:21
前面介绍了集合与映射两类容器,它们的共同特点是每个元素都是唯一的,并且采用二叉树方式的类型还自带有序性。然而这两个特点也存在弊端:其一,为啥内部元素必须是唯一的呢?像手机店卖出了两部Mate20,虽然这两部手机一模一样,但理应保存两条销售记录才是。其二,不管是哈希类型还是二叉类型,居然都不允许按照加 ......

前面介绍了集合与映射两类容器,它们的共同特点是每个元素都是唯一的,并且采用二叉树方式的类型还自带有序性。然而这两个特点也存在弊端:其一,为啥内部元素必须是唯一的呢?像手机店卖出了两部mate20,虽然这两部手机一模一样,但理应保存两条销售记录才是。其二,不管是哈希类型还是二叉类型,居然都不允许按照加入时间的先后排序,要知道现实生活中不乏各种先来后到的业务场景。为了更方便地应对真实场景中的各类需求,java又设计了清单list这么一种容器,用来处理集合与映射所不支持的业务功能。
提到清单,脑海里顿时浮现出从上往下排列的一组表格,例如购物清单、愿望清单、待办事项等等,它们的共同点一是都有序号,二是按线性排列。清单里的元素允许重复加入,并且根据入伙的时间顺序先后罗列,这些特征决定了清单是种贴近日常生活的简易容器。不过java中的list属于接口,实际开发用到的是它的一个实现类arraylist(列表,又称动态数组)。在某种程度上,列表的确跟数组很像,比如二者的内部元素都分配了整数序号/下标、都支持通过序号/下标来访问指定位置的元素等等。但列表贵为容器中的一员,自然拥有几点数组所不能比拟的优势,包括但不限于:


一、列表允许动态添加新元素,不管调用多少次add方法,也不必担心列表空间不够用的问题。下面代码便演示了如何声明列表实例并对其依次添加元素:

		// 创建一个列表(动态数组),其元素为mobilephone类型
		arraylist<mobilephone> list = new arraylist<mobilephone>();
		list.add(new mobilephone("华为", 5000)); // 第一个添加的元素,默认分配序号为0
		list.add(new mobilephone("小米", 2000)); // 第二个添加的元素,默认分配序号为1
		list.add(new mobilephone("oppo", 4000)); // 第三个添加的元素,默认分配序号为2
		list.add(new mobilephone("vivo", 1000)); // 第四个添加的元素,默认分配序号为3
		list.add(new mobilephone("vivo", 1000)); // 第五个添加的元素,默认分配序号为4

 

而数组的大小一经初始化设定就不可调整,除非另外给它分配新的数组空间;


二、数组只能对指定位置的元素进行修改操作,列表不但支持修改指定位置的元素(set方法),还支持在指定位置插入新元素(add方法),或者移除指定位置的元素(remove方法)。


三、数组只有两种遍历方式:按下标遍历、通过简化的for循环遍历。而列表支持多达四种的遍历方式,分别说明如下:
1、简化的for循环。该方式同样适用于数组和容器,具体的遍历代码示例如下:

		// 第一种遍历方式:简化的for循环同样适用于数组和容器
		for (mobilephone for_item : list) {
			system.out.println(string.format("for_item:%s %d",
					for_item.getbrand(), for_item.getprice()));
		}

 

2、迭代器遍历。该方式与利用迭代器遍历集合是一样,都要先获得当前容器的迭代器,然后依次调用迭代器的next逐个获取元素。利用迭代器遍历列表的代码如下所示:

		// 第一种遍历方式:简化的for循环同样适用于数组和容器
		for (mobilephone for_item : list) {
			system.out.println(string.format("for_item:%s %d",
					for_item.getbrand(), for_item.getprice()));
		}

 

3、索引遍历。这里的索引是以0开始的序号,对应于数组的下标,只不过列表通过get方法获取指定位置的元素,而数组通过方括号引用某个下标。下面是使用索引遍历列表的代码例子:

		// 第三种遍历方式:与数组通过下标访问相似,列表通过索引获取指定位置的元素
		for (int i = 0; i < list.size(); i++) {
			mobilephone index_item = list.get(i);
			system.out.println(string.format("index_item:%s %d",
					index_item.getbrand(), index_item.getprice()));
		}

 

4、foreach遍历。java8之后,每种容器都支持联合应用foreach与lambda表达式的遍历方式,该方式的遍历代码见下:

		// 第四种遍历方式:使用foreach方法夹带lambda表达式进行遍历
		list.foreach(each_item -> system.out.println(string.format(
				"each_item:%s %d", each_item.getbrand(), each_item.getprice())));

  

尽管列表对于大多数的业务场景来说够用了,可是仍旧无法满足部分特定的业务需求,因为arraylist默认把新元素添加到列表末尾,也不存在默认的删除操作。而在计算机科学常见的数据结构当中,至少还有两种是列表所不能实现的,其中一个叫做队列deque,另一个叫做栈stack。
队列取材于生活中的排队场景,譬如春运期间大家在火车站排队买车票,虽然有个别人嚷嚷着“我要插队”且自顾自地插了进去,也有人忍受不了漫长的等待而中途放弃排队改为骑单车回家,但多数人都会循规蹈矩地从队尾开始排队,买了票之后从队首离队。于是排队业务就抽象成为这么一种队列结构:添加时默认往末尾添加,删除时默认从开头删除。
至于栈则取材于计算机系统的寄存器操作,栈的特点是里面保存的数据为先进后出(同时也是后进先出),即最早添加的元素会被最后移除、最晚添加的元素会被最先移除。基于栈具有的数据先进后出特性,它常用于保存中断时的断点、保存子程序调用后的返回点、保存cpu的现场数据、在程序间传递参数等等。就栈作为一种容器的角色而言,每次添加的元素会默认加到开头,且每次删除操作会默认删去开头的元素,从而实现后进先出/先进后出的机制。
然而不管是队列还是栈,它们的存储形式都如同清单那样线性排列,区别在于数据进出的默认方位。因此java把队列、栈以及清单三者加以融合,推出了链表linkedlist(又称双端队列)这种数据结构,它一起实现了list与deque接口,并在某种程度上模拟了栈的功能,从而变成专治各种不服的万能清单。
作为清单大家族的一员,链表linkedlist的基本用法与列表arraylist相同,并基于它的三个祖宗分别进行了下列方法拓展:
1、在清单list的功能增强方面,补充了如下的扩展方法:
addfirst:添加到清单开头
addlast:添加到清单末尾
removefirst:删除清单开头的元素
removelast:删除清单末尾的元素
getfirst:获取清单开头的元素
getlast:获取清单末尾的元素
2、在队列queue的功能实现方面,提供了如下的队列方法:
offer:添加到队列末尾
offerfirst:添加到队列开头
offerlast:添加到队列末尾
peek:获取队列开头的元素
peekfirst:获取队列开头的元素
peeklast:获取队列末尾的元素
poll:删除队列开头的元素
pollfirst:删除队列开头的元素
polllast:删除队列末尾的元素
3、在栈stack的功能模拟方面,添加了如下的额外方法:
pop:队列开头的元素出栈,相当于方法removefirst和pollfirst
push:新元素入栈,相当于方法addfirst和offerfirst
总的来说,链表的数据存储兼顾清单和队列的组织结构,常用于对数据进出有特殊要求的场合,例如采取先进先出fifo的队列操作,以及采取先进后出filo的栈操作。



更多java技术文章参见《java开发笔记(序)章节目录