首页 > 代码库 > java 集合类

java 集合类

1. Java集合架构图

 技术分享

 

上图所示:Java有两类集合,线性表(Collection子类)和键值对(Map子类)

2. Java集合架构图-2

 技术分享

 

3. 线性表

ArrayList、Vector的引入

l  数组在使用上有局限性:创建后无法改变存储大小。

代码示意:int[] iAry=new int[3]; iAry[0]=0;iAry[3];//err

l  此时可用动态数组类ArrayList或Vector:

ArrayList<Integer> ary=new ArrayList<Integer> ();

ary.add(0);…ary.add(3);

ary空间无限制,当空间无法容纳时会重新开辟更大空间。

l  ArrayList和Vector的异同:

相同点:

都是基于数组来实现的;都可从指定位置取得元素,从容器的末尾

增加和删除元素都非常的有效,能在一个常数级的时间(O(1))内完成。但是

从一个其他的位置增加和删除一个元素颇费时,需要的时间为O(n-i),这里

的n代表元素个数,i代表要增加和删除的元素所在的位置。

差别:

 1、Vector法都是同步的,即线程安全,而ArrayList不考虑线程安全,ArrayList的性能比Vector好;

 2、 当添加元素超容量时,Vector容量翻倍,而ArrayList增加50%(更节约内存空间)。 所以,在单线程里,尽量用ArrayList。

l  ArrayList的优缺点

以数组实现。节约空间,但数组有容量限制。超出限制时会增加50%容量,用System.arraycopy()复制到新的数组,因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为10的数组。

按数组下标访问元素--get(i)/set(i,e) 的性能很高,这是数组的基本优势。

直接在数组末尾加入元素--add(e)的性能也高,但如果按下标插入、删除元素--add(i,e), remove(i), remove(e),则要用System.arraycopy()来移动部分受影响的元素,性能就变差了,这是基本劣势。

 

l  LinkedList的结构

 技术分享

 

l  LinkedList的优缺点

以双向链表实现。链表无容量限制,但双向链表本身使用了更多空间,也需要额外的链表指针操作。

按下标访问元素--get(i)/set(i,e) 要悲剧的遍历链表将指针移动到位(如果i>数组大小的一半,会从末尾移起)。

插入、删除元素时修改前后节点的指针即可,但还是要遍历部分链表的指针才能移动到下标所指的位置,只有在链表两头的操作--add(), addFirst(),removeLast()或用iterator()上的remove()能省掉指针的移动。

 技术分享

 

 

l  ArrayList与LinkedList的比较

ArrayList基于数组:便于索引,但不便于插入删除(需要移动元素);

LinkedList基于链表:插入和删除只需修改指针,但不能随机存取(需从head找起)

代码理解:

List<String> shenXiao = new LinkedList<String>(); shenXiao.add("子(zǐ)");

shenXiao.add("丑(chǒu)");

shenXiao.add("卯(mǎo)"); shenXiao.add("辰(chén)");

shenXiao.add("巳(sì)"); shenXiao.add("午(wǔ)");

shenXiao.add("未(wèi)"); shenXiao.add("申(shēn)");

shenXiao.add("酉(yǒu)"); shenXiao.add("戌(xū)");

shenXiao.add("亥(hài)"); shenXiao.add(2, "(yín)");

System.out.println(shenXiao);//自动遍历[..寅(yín)..亥(hài)]

 

l  Set

作为Collection接口的重要子接口,Set接口是一个不包含重复元素,且元素排列无序的集合,并且最多包含一个 null 元素,也被称为集。

Set接口的方法和Collection接口的方法大体相同,但在遍历时只能通过Iterator()方法获取迭代器进行迭代。

Set中的add方法不同于Collection接口,有一个boolean类型的返回值,当集合中含有与某个元素equals相等的元素时返回false,无法添加,否

则返回true。

Set中的Iterator方法返回的返回的元素没有特定的顺序(除非此 set 是某个提供顺序保证的类的实例)。

 技术分享

 

 

l  HashSet

HashSet类是Set接口的重要实现类,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。

Set接口是通过equals方法来比较两元素是否相同的,但是如果有1000个元素,那么再添加一个元素,就要比较1000次,效率严重降低。所以HashSet类底层采用了HashMap来存储元素,而HashMap采用了哈希表的原理。

因此HashSet类按照哈希算法来存取对象,当向集合中加入一个新对象时,会调用对象的HashCode()方法得到对象的哈希码,然后根据这个码计算出对象在集合中存储的位置。 HashCode()方法实际上是返回的对象存储的物理地址。

所以总的来说,HashSet是通过两元素的HashCode()方法和equals()方法来判断两元素是否相等的。

所以当我们使用HashSet添加元素时一定要重写该元素的HashCode方法和equals方法。并且,参与计算两个方法返回值的关键属性应该相同。 代码示例:HashSetDemo.java

 

l  TreeSet

TreeSet是一个有序集合,TreeSet中的元素将按照升序排列,缺省是按照自然排序进行排列,意味着TreeSet中的元素要实现Comparable接口。

在类上implement Comparable重写compareTo()方法,在方法内定义比较算法, 根据大小关系, 返回正数负数或零,在使用TreeSet存储对象的时候, add()方法内部就会自动调用compareTo()方法进行比较。

代码:自定义类做元素

必须实现Comparable,否则TreeSet无法add()

 技术分享

 

l  HashMap

对于 HashMap 及其子类而言,它们采用 Hash 算法来决定集合中元素的存储位置。

当系统开始初始化 HashMap 时,系统会创建一个Entry 数组,这个数组里可以存储元素的位置被称为“桶(bucket)”,每个 bucket 都有其指定索引,系统可以根据其索引快速访问该 bucket 里存储的元素。

无论何时,HashMap 的每个“桶”只存储一个元素(也就是一个 Entry),由于Entry 对象可以包含一个引用变量(就是 Entry 构造器的的最后一个参数)用于指向下一个 Entry,因此可能出现的情况是:HashMap 的 bucket 中只有一个Entry,但这个 Entry 指向另一个 Entry ——这就形成了一个 Entry 链。如图所示:

 技术分享

 

 

l  TreeMap和HashMap的差别

HashMap通过hashcode对其内容进行快速查找;而TreeMap基于红黑树实现的,其所有的元素都保持着某种固定的顺序,如自然顺序或自定义顺序。

在Map中插入、删除和定位元素,HashMap是最好的选择。但如果要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。

注:

1.同样的对比有TreeSet和HashSet,因为TreeSet基于TreeMap, HashSet基于HashMap

2.使用HashMap要求添加的键类需明确实现hashCode()、 equals()。

 技术分享

 

 

l  键值对-Hash算法

Hash表引入原因:数组寻址容易,插入和删除困难; 而链表寻址困难,插入和删除容易。哈希表可综合两者的优点,即,寻址容易,插入删除也容易的数据结构。

Hash(散列)算法,把任意长度的输入,用算法函数变换成固定长度的输出 (散列值)。不同的输入可能会散列成相同散列值。

Hash表有多种实现,以拉链法(链表的数组)解释: ? 左边数组,数组每个元素中 包括一个指针,指向一个链表的头。根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素。

散列法:元素特征转变为数组下标的方法就是散列法,上图使用除法散列法:index = value % 13

 技术分享

 

键值对-Hash算法  HashCode的理解

Hashcode(散列码):每个对象都会有一个int型的hashcode,代表它在内存中的位置。

Object中的equals方法是指比较两个对象是不是指向同一个引用。hashcode方法返回hashCode的值;Object规范里规定:如果两个对象根据equals(Object)方式是相等的,那么这两个对象的hashCode一定要相等,所以记住:重写equals方法后,请重写hashCode方法。

 

l  键值对

HashMap和Hashtable

Hashtable继承自Dictionary类,而HashMap是Map接口的一个实现。

代码:(HashMap改为Hashtable,练习)

 技术分享

 

 

主要差别:

1 HashMap允许null key和null value,而hashtable不允许;

2 HashMap是Hashtable的轻量级实现(非线程安全的实现),效率高;

3 空间:加载因子默认都为0.75;初始容量Hashtable为11,而HashMap为16。 增加方式Hashtable为 oldSize*2+1,HashMap为oldSize *2。

 

l  枚举器访问集合对象

Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。

例如,如果没有使用Iterator,遍历一个数组的方法是使用索引:

for(int i=0; i<array.size(); i++) { ... get(i) ... } 而访问一个链表(LinkedList)又必须使用while循环:while((e=e.next())!=null) { ... e.data() ... } 以上两种方法客户端都必须事先知道集合的内部结构,访问代码和集合本身是紧耦合。无法将访问逻辑从集合类和客户端代码中分离出来,每一种集合对应一种遍历方法,客户端代码无法复用。更恐怖的是,如果以后需要把ArrayList更换为LinkedList,则原来的客户端代码必须全部重写。

为解决以上问题,Iterator模式总是用同一种逻辑来遍历集合:

for(Iterator it = c.iterater(); it.hasNext(); ) { ... } 这样一来客户端自身不维护遍历集合的"指针",所有的内部状态(如当前元素位置,是否有下一个元素)都由Iterator来维护,而这个Iterator由集合类通过工厂方法生成,因此,它知道如何遍历整个集合。

客户端从不直接和集合类打交道,它总是控制Iterator,向它发送"向前","向后","取

通过枚举器访问线性集合 

 技术分享

 

通过枚举器访问HashMap

 技术分享

 

 

泛型

 

泛型:即参数化类型,所操作的数据类型被指定为一个参数。 ?

这种参数类型可以用在类、接口和方法的创建中(即,泛型类、泛型接口、

泛型方法)。

泛型好处:编译时可检测类型错误;避免了强制类型转换(自动和隐式 ?

的),提高代码的重用性。

例:

 技术分享

 

以上的List就是泛型接口、ArrayList就是泛型类、 add()和get()是泛型方法

注:集合框架,都已经成为泛型化了。

泛型的规则 

 技术分享

泛型定义代码举例

 技术分享

 

 

类型通配符?引入原因:

 技术分享

 

泛型的继承,代码说明:

 技术分享

 

 

l  浅拷贝与Clone

有时我们需要复制对象:

 技术分享

 

测试:

 技术分享

 

 

l  浅拷贝与Clone(续)

分析,从上面代码运行结果看:

成员变量为基本类型(外加String类型—内容不变性),只要super.clone()就可

拷贝。但数组或复杂类型(引用类型)时,就必须实现深度clone

l  深度clone(本例):

 技术分享

技术分享

 

 

 

l  深度clone()

容器类成员,需逐个元素复制,以ArrayList为例

 技术分享

 

 

java 集合类