【1月25日】从java集合框架的角度看数据结构

  1. 基本的数据结构
  2. JAVA集合概述
  3. JAVA集合框架的四个主要体系:Set,List,Queue和Map

1. 基本数据结构

数据结构是指计算机存储、组织数据的方式。这里面有两个内涵,一是数据的逻辑结构;二十数据的物理结构。
数据的逻辑结构反映了元素之间的逻辑关系,逻辑结构主要包括:
(1).线性结构
元素存在着一对一的相互关系。数组就是最简单暴力的线性结构,此外还有链表,再增加一些操作限制就是栈,队列等。
(2)树形结构
元素中存在着一对多的相互关系。常见的如二叉树。
(3)图形结构
元素中存在着多对多的相互关系。

2.JAVA集合概述

Java的集合类是一种位于java.util 下,可以用于存储数量不等的对象,并可以实现常用的数据结构,如栈、队列等。大致可以分为四个主要体系:Set,List,Queue和Map。其中Set就是数学意义上的“集合”,其中的元素无序且不可重复;List中的元素有序且可以重复;Queue是Java5之后新增的Collection的子接口,代表一种队列集合的实现;Map则代表有映射关系的集合。
在现实中,常常有保存多个数据的需求。面对这种需求,通常有两种解决方案,一是使用数组,二是使用集合类。前者的主要局限在于:数组的长度不可变化,一旦在数组初始化时指定了数组的长度,在保存数量可变化的数据情境下,数组就显得无能为力了;此外,数组也无法有效保存具有映射关系的数据(key-value,也被称为关联数组)。
集合类和数组的区别还在于,数组元素既可以是基本类型的值,也可以是对象(实质上还是对象的引用,在此处一般不做区分);而集合类只能保存对象(实质上也还是对象的引用,同上)。
Java的集合类主要由两个接口派生而出:CollectionMap
API中关于Collection接口的相关信息如下(JavaTM 2 Platform Standard Ed. 6):


Collection接口


进一步做出Collection、Map集合体系的继承树如下:


Collection集合体系的继承树


Map体系结构树


在访问操作上,对于List集合的元素,可以通过元素的索引来访问;Map元素可以通过没想元素的key来访问;由于Set中元素的特殊性,对于Set只能通过元素本身来访问了。
所有通用的 Collection 实现类(通常通过它的一个子接口间接实现 Collection)应该提供两个“标准”构造方法:一个是 void(无参数)构造方法,用于创建空 collection;另一个是带有 Collection 类型单参数的构造方法,用于创建一个具有与其参数相同元素新的 collection。实际上,后者允许用户复制任何 collection,以生成所需实现类型的一个等效 collection。尽管无法强制执行此约定(因为接口不能包含构造方法),但是 Java 平台库中所有通用的 Collection 实现都遵从它。
Collection是List、Queue、Set的父接口,该接口定义的操作方法可以用于操作List集合、Queue集合和Set集合。
API中,Collection中定义了如下的集合操作方法:
Collection接口

遍历集合的几种方法

(1)使用Lambda表达式遍历集合
java8为Iterable接口新增了一个forEach的默认方法,该方法所需的参数是一个函数式的接口。而Iterable是Collection的父接口,因此Collection集合也可以直接调用改方法。
当程序调用Iterable的forEach(Consumer action)遍历集合函数时,程序会依次将集合元素传给Consumer的accpet(T t)方法。正因为Consumer是函数式的接口,因此可以使用Lambda表达式来遍历集合元素。

public class CollectionEach{
    public static void main(String[] args){
        Collection books = new HashSet();
        books.add("book_one");
        books.add("book_two");
        // 调用forEach()遍历集合
        books.forEach(obj -> System.out.println("迭代集合元素:"+obj));
        }
    }

(2)使用java8增强的Iterator遍历集合元素
Iterator接口也是集合框架的成员,主要用于遍历Collection集合中的元素,因此Iterstor也被称为迭代器。
这里写图片描述

除了上述的方法之外,java8还新增了一个默认方法* forEachRemaining(Consumer action)*可以使用Lambda表达式来遍历集合元素。
**注意:**Iterator必须依附于Collection对象,其本身不提供盛装对象的能力。Iterator提供了两个方法来迭代访问Collection集合中的元素,并提供了一个remove()来删除上一个next()方法返回的集合元素。
(3)使用Lambda表达式遍历Iterator
(4)使用foreach()循环遍历集合元素
foreach循环可以使迭代访问元素更加敏捷,类似于Python的for i in range(100)。需要注意的是,foreach循环中的迭代变量也不是集合元素本身,系统只是依次把集合元素的值赋给迭代变量,因此在foreach循环最终修改迭代变量的值也没有实际意义。

JAVA集合框架的主要体系:Set

Set在概念上类似于数学意义上的“集合”,关于Set接口定义的具体方法如下:


类型方法说明
booleanadd(E e)如果 set 中尚未存在指定的元素,则添加此元素(可选操作)。
booleanaddAll(Collection c)如果 set 中没有指定 collection 中的所有元素,则将其添加到此 set 中(可选操作)。
voidclear()移除此 set 中的所有元素(可选操作)。
booleancontains(Object o)如果 set 包含指定的元素,则返回 true。
booleancontainsAll(Collection c)如果此 set 包含指定 collection 的所有元素,则返回 true。
booleanequals(Object o)比较指定对象与此 set 的相等性。
inthashCode()返回 set 的哈希码值。
booleanisEmpty()如果 set 不包含元素,则返回 true。
Iteratoriterator()返回在此 set 中的元素上进行迭代的迭代器。
booleanremove(Object o)如果 set 中存在指定的元素,则将其移除(可选操作)。
booleanremoveAll(Collection c)移除 set 中那些包含在指定 collection 中的元素(可选操作)。
booleanretainAll(Collection c) 仅保留 set 中那些包含在指定 collection 中的元素(可选操作)。
intsize() 返回 set 中的元素数(其容量)。
Object[]toArray()返回一个包含 set 中所有元素的数组。
T[]toArray(T[] a)返回一个包含此 set 中所有元素的数组;返回数组的运行时类型是指定数组的类型。

HashSet

HashSet是Set的典型实现,大多数情况下使用Set集合就是使用这个实现类。HashSet使用Hash算法来存储集合中的元素,因此具有良好的存取和查找性能。
HashSet具有以下特点:


  • 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也可能发生变化。
  • HashSet不是同步的,如果有多个线程访问并修改同一个HashSet,则必须通过代码来保证其同步
  • 集合的元素可以是null

当向HashSet集合中add一个元素的时候,HashSet会调用该对象的hashCode()方法得到对象的hashCode值,再根据hashCode的值决定该对象的存储位置。也就是说,如果两个元素通过equals()返回的值是相等的,但是hashCode()的值是不等的话,那么仍然可以add成功。这就与Set的规则冲突。
因此,要避免上述的情况出现。将一个对象放入到HashSet中时,如果需要重写对象对应类的equals()方法时,必须要也要同时重写其hashCode()方法。规则是:如果两个对象的equels()的值返回的是true,则hashCode()的返回值必须相同。
HashSet存储元素的位置称之为bucket,有人翻译成“”。如果多个元素的hashCode值相同,但是它们的equels()返回的是false,那么实际上会在这个位置上用链式结构来保存多个对象,也就是说,在一个“”里面放置了多个元素,这样会导致性能的下降。
下面给出重写hashCode()的基本规则


  • 同一对象多次调用hashCode()的返回值应该相同;
  • 当两个对象的equals()的返回值为true时,这个两个对象的hashCode()的返回值应该相同;
  • 对象中用作equals()方法比较标准的实例变量,都应该用于计算hashCode()的值。

LinkedHashSet

HashSet还有一个子类就是LinkedHashSet。它也是根据的元素的hashCode的值来决定元素的存取位置,但是塔同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序来保存的。也就是说,当遍历LinkedHashSet集合时,会以元素的添加顺序来访问集合中的元素。
这样做的好处是在迭代访问集合中的全部元素时,具有良好的性能。但同时也增加了额外的开销。
需要注意的是,虽然LinkedHashSet会维护元素的添加顺序,但是其还是Set,因此不允许有重复的元素。

TreeSet类

TreeSet是SortSet接口的实现类,因此可以确保集合处于排序状态。也就多了以下的一些方法:如comparator();last()(返回最后一个元素);first()(返回第一个元素);lower()(返回指定元素之前的元素);higher()(返回指定元素之后的元素)等。

Set小结

HashSet和TreeSet是Set的两个经典实现,那么在实际中应该如何选择呢?应该清楚的是,HashSet的性能总是要比TreeSet(特别是最常用的添加,查询元素等操作)。这是因为TreeSet需要额外的红黑树来维护集合元素的次序。因此,只有当需要保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。

JAVA集合框架的主要体系:List

List集合中的元素是有序且可重复的,并且可以通过索引来访问指定元素。List比较熟悉,一般方法也就不在此再次说明了。且说一下java8对于List集合增加的sort()replaceAll()方法。其中sort()需要一个Comparator对象来控制元素排序,同样可以使用Lambda表达式来作为参数。

ArrayList和Vector

ArrayList和Vector是List的两个典型实现。ArrayList和Vector类都是基于数组实现的List类,所以都封装了一个动态的、允许再分配的Object[]数组。ArrayList和Vector的对象使用initialCapacity参数的来设置数组的长度,并且initialCapacity会在“不够用”的时候自动增加。
一般情况下,无需去主动关心initialCapacity的值。但是如果需要添加大量元素时,可使用ensureCapacity(int minCapacity)的方法一步到位,以减少重分配的次数,增加性能。
关于两者的区别,可能要历史的沿革上面说起来了:d。Vector和ArrayList在用法上几乎相同,但是,Vector比ArrayList要“古老”得多。早在上古时期的KDK 1.0时代,彼此 Java 尚没有提供完整的集合框架,Vector就存在了。也因此,Vector有一些名字很长的方法。1.2 以后,Java提供了系统的集合框架,Vector就作为List的实现之一,但是仍然保留了一些“名字很长的方法”,这导致了Vector里面有一些功能重复的方法。
ArrayList,Vector主要区别为以下几点:
(1):Vector是线程安全的,源码中有很多的synchronized可以看出,而ArrayList不是。导致Vector效率无法和ArrayList相比。当多个线程访问一个ArrayList时,如果有超过一个线程修改了ArrayList,则必须手动保证该集合的同步性。在《疯狂 Java 》,更是提出了如下建议:“Vector的缺点很多”,“即使是为了保证List的线程安全,也不推荐使用Vector”。Collections工具类可以将ArrayList变成是线性安全的。
(2):ArrayList和Vector都采用线性连续存储空间,当存储空间不足的时候,ArrayList默认增加为原来的50%,Vector默认增加为原来的一倍;
(3):Vector可以设置capacityIncrement,而ArrayList不可以,从字面理解就是capacity容量,Increment增加,容量增长的参数。
(4):Vector有一个Stack(见Java的集合框架体系图) 子类。由于Stack是继承了Vector,所以同样Stack也是形成安全+性能较差的。如果需要使用“栈”这种数据结构,推荐使用ArrayQeque。

Arrays.ArraysList:固定长度的List

在Arrays的工具类中,提供了asList(Object…a)的方法。需要特别注意的是,这个asList(Object…a)返回的集合既不是ArraysList的实例,也不是Vector的实例。它是Arrays内部的ArraysList的实例。ArraysList是是一个固定长度的List,可以对其进行遍历访问,但是不可以进行修改操作,否则会报异常。

JAVA集合框架的主要体系:Queue

Queue用于模拟队列/栈此类的数据结构。队列的通俗定义是“先进先出”(FIFO)。对于一个队列而言,头部的元素存放的时间最久,尾部的元素存放的时间最短。offer操作可以将一个元素插入到队列的尾部,而通过poll操作可以返回首部的元素。
此接口定义在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。插入操作的后一种形式是专为使用有容量限制的 Deque 实现设计的;在大多数实现中,插入操作不能失败。
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值