Java 集合Collection——初学者参考,高手慎入(未完待续)

1.集合简介和例子

Collection,集合。和数学定义中的集合类似,把很多元素放在一个容器中,方便我们存放结果/查找等操作。

Collection集合实际上是很多形式集合的一个抽象。

例如十九大就要召开,那么到人民大会堂与会的人就是一个集合 ,这个集合里装载了许多元素,每个元素就是一个人大代表。

再比如北京的全聚德烤鸭非常出名,每天都有人排队购买,这个队也是一个集合。

上面这两个例子就展示了两个不同的集合,人大代表的集合其实就是一个简单的聚合,把若干元素集中在一起。第二个例子,排队就具有比较鲜明的特征了,先到的人可以先买到,有一个顺序的关系。

2.面向对象思想中的“抽象”

Colletion是一个抽象,Java编程设计中,抽象是很常用也很重要的方法。

那么,抽象是什么?

抽象,先从语文的角度看一下这个词汇,按照《咬文嚼字》拆解词语的方式,“抽象”,抽:抽取,抽出;象:现象,形象,象征,特征。现在把两个字组合起来,抽象的意思就是“抽取特征”。它的反义词是具体。

Collection集合是一种抽象。就像衬衫,裤子,内衣都可以称作衣服一样 ,衣服就是一种抽象。而之后要了解的数组(Array),链表(List),Set,队列(Queue),栈(Stack),等都是集合的一种。这些不同的集合,就如不同的衣服一样,有着不同的作用,然而他们都是集合。

Collection在Java中是一个接口,接口可以简单的理解为一种规范或者协议。

接口中定义了一些条款,所有遵守接口协议的人,都必须按照我给出的条款而必须具有某种功能。例如Collection中规定,首先能够成为一个容器,用来盛放一些数据,其次集合要有添加和删除元素的功能等等。于是集合的这些不同形式例如 数组/链表/队列 都可以放置数据,并且有添加元素和删除元素的功能。

也就是说,Collection接口描述了作为一个集合要拥有的特征,但是Collection接口本身并没有这些功能,这就是抽象,而数组,链表这些具体的集合拥有Collection的规定的特征,且具体地表示了一类集合,并能够执行操作。

例如生物学上的分类,对于每一个分类都是抽象:界,门,纲,目,科,属,种。例如对于人来说,人其实就是一个抽象,远到盘古夸父,秦皇汉武,唐宗宋祖,近到毛邓,爱因斯坦,卓别林,这里的每个伟人都是人的具体实现。人其实是属的名称,动物界,脊索动物门,脊椎动物亚门,哺乳纲,灵长目,人科,人属,智人种。很久之前,也存在其他的人种,到如今只剩下智人,再无奈也是物竞天择的结果。

楼已歪,扶正继续。

智人种给出了这么一个定义,脑容量大,直立行走,可以制造工具等。但是“人”本身只是一个一类东西的统称,规定了作为一个人应该有什么样的功能和属性,而真正能够使用这些功能和属性的,是我们每个具体的人。而对于每一个读者,都是动物这个抽象下的具体,你可以想象,你和天空中刚刚飞过的那只鸟,你膝盖上卧着的猫,甚至厕所里的蟑螂都实现了动物这个接口,而你和电脑旁的仙人掌,绿萝,口腔里的细菌却没有什么动物界交集。然而所有这些,又都实现了另一个更抽象的接口,生物。

没有最抽象,只有更抽象——也许这句话才是对“抽象”最好的解释。

我们应该如何抽象?

抽象的过程就是抽取特征的过程。例如我想要一块肉,也就是说我描述了肉的基本特征,需要来自可食用的动物,并且经过加工之后吃起来很香,当然这些是特征描述,也是某种意义的限制,限制了肉不仅要健康可食用,而且要好吃。脱离了案牍劳形,脱离了嘈杂忙乱,打电话给母亲:我想吃肉。于是,红烧肉,西红柿炖牛腩,小鸡炖蘑菇,糖醋鱼等等。显然,我说吃肉,只是一个抽象的表述,母亲根据我的喜好,给出了多个具体的肉的实现。

Java里的思想也是这样,所有的Java类都继承于Object类,就像所有具有生命的东西都是生物一样。抽象,使得Java语言,更能描述我们熟知的事物,使其更能贴近我们的语言,而不是冰冷的机器和陌生的二进制数字。

3.集合中最常见的两种结构——数组和链表

最常用的集合有数组,链表,Set,队列,栈,还有vector,vector在这里不做介绍。

首先举个例子说明数组和链表的区别。

 

 数据在内存里的存储方式,非常类似我们在一张田字格的纸张上书写汉字。

通常我们会从头开始,一个格子一个格子地按顺序写下去。就像左侧图片所示,在一段连续的田字格内写一行汉字。这个情形和数组非常的相似,数组也是在内存中分配了一段连续的空间用来存储数据。例如这段空间中存储了四个汉字“我爱红旗”。

而链表则相对嚣张跋扈一些,我自己的纸张,为什么不能随意写呢。但是问题也是显而易见的,如果我想让别人也看懂,那我必须将顺序标识出来,于是我不得不加了个箭头,只要按照箭头的方向就可以读了。当我想在存放链表的空间内再存放一个数组是比较困难的,因为我很难再找到一段比较长的连续空间。这样对比起来数组更有优势,数组更节省空间,数组不需要关心下一个汉字的位置,只要连续读下去就可以了。数组空间的紧凑为将来数据的存放也提供了很好环境。当然无论是数组还是链表,我都需要知道第一个字从哪里开始,所以我用一个标识来标记开头,即数据存储的起始位置。

从空间上看,数组明显是比较好的存储结构,也是日常我们使用最多的数据结构。

但是现在我发现,我不小心写错了这个句子,我本来是要写“我爱五星红旗”,漏掉了两个字"五星"。作为一个不折不扣的爱国青年,我迫不及待地希望把这两个字加上去。下面分别看下两个数据结构的操作。

 

 对于数组,好麻烦。假设我用铅笔写的,我需要把“红旗”两个字擦掉,然后在原来红旗的位置上写上五星。然后把“红旗”往后移,放在五星的后面。分解一下操作:首先我需要在插入“五星”的地方之后的所有汉字(这里是“红旗”)移除,然后我把“五星”两个字写进去,最后把之前移除的汉字“红旗”再追加到五星的后面。好麻烦是不是。。。

对于链表,反正我随便都能写,我随便挑两个我喜欢的位置,写上五星,然后把箭头的顺序稍微调整一下就够了,是不是很方便?

于是数组和链表的区别和优劣势就显而易见了。使用的时候,根据不同的场景选择对应的存储结构,如果只用来存放和查询数据,无疑数组更快,又不浪费空间。如果我要频繁地增加删除,当然链表更快。java中的队列Queue就是使用链表LinkedList来实现,入队和出队就是添加和删除的操作。

上面的介绍,基本属于科普了。现在整点干货出来仍然有点早,继续更深入地科普一下。

4.常见集合详细描述及其用法

java中简单数组和C/C++语言中是一样的。当然Java语言的特性封装、继承、多态中封装的特点为我们提供了极大的方便,JDK中对数组进行封装,方便我们进行遍历/查询/更新/删除等操作。

数组

在JDK中,数组的封装类为ArrayList。

首先看下一般的未封装数组。

数组,不是一组数字,而是一组数据。

在编码过程中,所有的变量一般都会有以下几个步骤:声明,初始化,赋值。

想象以下我们在田字格中书写一些内容。现在我想写一首诗,那么我可能书写的内容是汉字。我会背圆周率小数点后1000位,我想写圆周率炫耀一下我牛逼到惊人的记忆力,那么我书写的内容就是数字。我现在没什么事,就想学达芬奇画鸡蛋,学梵高乱抹色彩,学毕加索搞一搞抽象画,那么我书写的内容就是一幅画,至少呈现了某种奇特的形状。

假设我有一个书童,现在我突然要作诗,我对书童说,给我取文房四宝来。书童拿来文房四宝,问道:“少爷,您是要写诗还是要作画?”。“写诗”。“您是写哪种诗?”。我想着我要写的得意诗句,沉吟了下,说:“七言绝句。” 书童取出一副长七寸,宽五寸的纸放在我面前。我蘸足了墨,把第一行的标题写下:“贾生”。我要写的这个人,是贾谊,汉代名士,却郁郁不得志。念及此,挥笔从第二行开始写道,“宣室求贤访逐臣”,第三行,“贾生才调更无伦”,第四行“可怜夜半虚前席”,第五行,“不问苍生问鬼神”。

书童为什么这么问,因为书童想知道他需要为我装备多大的纸张,避免纸张的浪费。作为程序员,永远要把自己当做一个穷酸秀才,内存的使用永远要寸土寸金。即使我家财万贯,但水滴石穿,绳锯木断,积少成多的道理谁都懂。

想象书童就是电脑(更确切的说是JVM,Java虚拟机),纸张就是内存。我要告诉书童,我写诗,于是书童知道我是写字。这就是声明。我声明我要写一些字,即Char字符类型。用代码表示:

char[] words; // 声明类型

如果我想写一堆数字来记账,那么声明就会变成 int[] numbers; 

然后书童问了,你要写什么诗句,答曰:七言绝句。书童知道我绝对不是标题党,题目不会搞得比诗句还长。于是他认为我一个小楷占一寸见方大小,七言绝句,四行,每行七个字。28个字,标题给留上一行也够了,于是他给了一个长七寸,宽五寸的纸张,也就是能写35个字。

这个过程,就是初始化,初始化的作用有两个,一个是确定大小,另一个是赋上初始值。这里的作用主要是确定大小。

于是开始初始化,用代码表示:

words = new char[35]; // 初始化

 初始化了35的空间,保证存储空间够用的前提下又不浪费空间。

当然,声明和初始化是可以一起进行的,将以上两步合在一起。

char [] words = new char[35]; // 声明并初始化一个长度为35的char数组 

最后,我开始作诗了。我先写标题。

char[0] = '贾';
char[1] = '生';

 

写完标题的我,突然发现了一个非常严重的问题。我写诗之前必然胸有成竹,写诗我想一气呵成。这么一个字一个字的赋值,何时是个头。估计也写不出精彩的诗句了吧。于是我突然想,一行是一句诗,也就是一个字符串,加上标题,我用5个字符串,即5个String就可以了。于是我更改了声明,并初始化如下:

String[] words = new String[5];

 然后,我一气呵成地完成了这首诗:

words[0] = "贾生";
words[1] = "宣室求贤访逐臣";
words[2] = "贾生才调更无伦";
words[3] = "可怜夜半虚前席";
words[4] = "不问苍生问鬼神";

一气呵成,书童拍手不迭,叫道:“好诗!好诗!”。

注意一个区别:char是java中的基本类型,每个char只能装下一个字符,字符可以是字母,汉字,数字,也可以是各种标点,但是编码形式,是用单引号。而String不是基本类型,可以表示一个字符串,长度不限(当然不是绝对完全的不限),字符串的形式是用双引号括起。

我们看下数组的声明方式。

words是变量名,初始化大小的时候,要使用new,在java中,new的意义是申请空间。也就是说,我向书童索要一个足够我写下一首七言绝句的纸张。

然后为数组赋值,准确的说是为数组的元素赋值。数组首先是一个连续空间上的集合。数组的第一个元素的下标是0,不是1。

对于数组下标从0开始有很多原因和说法,当然我认为最合理的解释是偏移量。正如上面介绍的“我爱五星红旗” 的例子,有一个开始位置的标记,这个开始是“我”这个汉字的位置,“我”这个字相对于开始位置它的位置没有加1,它就在初始位置,就是第0个,而“爱”这个字,相对于初始位置,显然是在初始位置的后面1位,“五”是在初始位置后的第2位。与之对应,数组的第一个元素必然是从0开始,然后最后一个元素所属的下标,就是数组的长度减1,因为是从0开始的。就如相对论在物理学的应用,没有绝对的静止。在内存中没有绝对的位置,只有相对的位置,相对与开始位置是第几个。因此,将与开始位置的相对距离,作为数组的下标无疑是一种非常明智的做法。

数组的声明初始化方式很简单。在类型后面加方括号[]即表示了一个数组。

 

posted @ 2017-05-24 14:46  AaronCui  阅读(2010)  评论(0编辑  收藏  举报