Java中集合的简介和选择

Thinking in Java 阅读笔记,回顾集合部分

Java集合

在Java中集合主要分为:

Collection(List,Set,Queue)

Map(HashMap,SortedMap)
其中List和Set可分为:

List(Stack,ArrayList,LinkedList)

Set(HashSet,TreeSet,SortedSet)

Collection接口

该接口包含许多方法,这些方法也可以通过Set和List执行

boolean add(T) 为容器中添加一个元素,若添加失败则返回false

boolean addAll(Collection<? extends T>) 添加参数中的所有元素,只要添加了任意元素就会返回true

void clear() 清除容器中的所有元素

boolean contains(T) 判断容器是否具有类型T的参数

boolean containsAll(Collection<? extends T>) 如果容器持有次参数中的所有元素,则返回true

boolean isEmpty() 是否为空

Iterator iterator() 返回一个可以遍历容器的元素

boolean remove(Object) 移除此元素的一个实例

int size() 返回元素数目

Object[] toArray() 将容器中的值填充到数组中返回

Set接口

存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性

HashSet(建议使用) 为快速查找而设计的Set,存入HashSet的元素必须定义hashCode()

TreeSet 保持次序的Set,底层为树结构,使用它可以从Set中提取有序的序列,元素必须实现comparable接口

LinkedHashSet 具有HashSet的查询速度。且内部使用链表维护顺序,在遍历时会按元素插入次序显示,元素无需定义hashCode()方法

SortedSet

SortedSet可以保证元素处于排序状态,增加了以下方法

Object first() 返回容器中的第一个元素

Object lst() 返回容器中的最后一个元素

SortedSet的意思是按对象的比较函数对元素排序,而不是元素插入的次序,插入顺序可以用LinkedHashSet来保存

队列

Queue在Java SE5中只有LinkedList和PriorityQueue两个实现,它们的差异在于排序行为

优先队列PriorityQueue

初始化时传入比较器或者类型支持comparable接口,每次出队会根据比较策略弹出相应元素

Map

Map的思想是维护键-值(对)关联,可以通过键来查找值

标准Java类库中包含了Map的几种基本实现,包括:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentiyHashMap ; 这些在效率、键值对的保存及呈现次序,对象的保存周期、映射表如何在多线程程序中工作和判定键等价的策略方面都有不同

性能

当在get()中使用线性搜索时,执行速度很慢,而HashMap正是在此处提升执行速度

HashMap使用散列码来取代对键的缓慢搜索。(散列表原理)

散列码是相对唯一的,用以代表对象的int值,它是通过将该对象的某些信息转换而生成的

基本的Map实现

  1. HashMap:Map基于散列表的实现,插入和查询的开销是固定的,可以通过构造器设置容量和负载因子来调整性能
  2. LinkedHashMap:类似HashMap,使用链表维护内部次序,迭代遍历时取得键值对的顺序和插入顺序相同,可以通过构造器调整为LRU(最近最少使用)的顺序获取数据;只比HashMap慢一点
  3. TreeMap:基于红黑树实现,查看“键”或“键值对”时它们会被排序。TreeMap的特点是,得到的结果是经过排序的
  4. WeakHashMap:弱键映射,允许释放映射所指向的对象;如果没有引用指向某个键,则该键会被回收
  5. ConcurrentHashMap:线程安全的Map,不涉及同步加锁
  6. IdentityHashMap:使用==代替equals()对键进行比较的Map,为特殊情况设计

在Map中对键的要求与对Set中的元素要求一样,不能重复,需要包含equals()方法;如果键被用于散列map,则需要包含hashCode()方法;如果用于TreeMap,则需要实现Comparable

如果要使用自己的类作为HashMap的键,必须同时重载HashCode()和equals()

若不重载equals(),对象默认比较是通过地址比较的,两个属性完全相同的实例将无法被视为同一个键

散列

散列价值在于速度:散列使得查询得以快速进行。在使用ArrayList实现的map中,键没有按照任何特定顺序排序,所以只能简单的线性查询,而线性查询效率非常低。要提升查询效率,解决方案之一是保持键的排序状态,使用二分查找进行查询。散列则更进一步,它将键保存在某处,以便能很快找到。存储一组信息最快的数据结构是数组,所以用它来表示键的信息(不是键本身)。

在数组中,通过键对象生成一个数字,将其作为数组的下表。这个数字就是散列码。由hashCode()方法生成。

于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能保证没有冲突(特例),那么就有了一个完美的散列函数。通常冲突由外部链接处理,数组并不直接保存值,而是保存值的list(邻接表,处理冲突 时使用链表法,将计算结果相同元素连接在数组中的同一位置),如果散列函数好的话,数组的每个位置就只有较少的值,只需要对很少的元素进行比较,提高了查询速度。

设计hashCode()

设计hashCode()时最重要的因素就是,对同一个对象调用该方法时都应该生成同一个值

要生成一个使用得当hashCode(),必须生成速度快,而且必须有意义。也就是基于对象内容生成散列码。散列码不必是独一无二的, 应该更关注生成速度,而不是唯一性,但是通过hashCode()和equals()方法要能完全确定对象身份

一个好的hashCode()应该产生分布均匀的散列码,如果散列码都集中在一块,在散列表中某些区域负载会很重,效率不如均匀分布的散列函数快。

如何选择接口的不同实现

实际上只有四种容器:Map,List,Set和Queue,但是每种接口都有不同实现版本

容器之间的区别在于背后的底层实现,也就是在于接口是由什么数据结构实现的。

例如ArrayList和LinkedList都实现了List接口,所以他们基本的List操作是相同的。然而ArrayList底层由数组支持,而LinkedList是由双向链表实现的,其中每个对象包含数据的同时还包含对前一个和后一个元素的引用。如果要经常在表中插入或删除元素,LinkedList比较合适,否则就应该使用随机读取更快的ArrayList

Set可被实现为TreeSet,HashSet,LinkedHashSet,每一种都有不同的行为:HashSet是最常用的,查询速度最;LinkedHashSet保持元素插入的次序;TreeSet基于TreeMap,生成一个总是处于排序状态的Set,可以根据不同情境选择不同的实现。

List的选择

对于大量的随机访问,ArrayList是更好的选择,它在各种大小的列表中访问速度都很快速和一致,而链表实现的LinkedList则效率较低

对于插入和删除元素,当列表较大时,ArrayList开销变得非常大,因为要调整数组,需要移动其他引用的位置,而LinkedList开销则较低,并且不会随着列表增大而增大

Set的选择

HashSet在性能上总是比TreeSet好。TreeSet存在的唯一原因是它可以保持元素的排序状态,所以只有当需要一个排好序的Set时,才应该使用TreeSet。对于插入操作,LinkedHashSet比HashSet代价要高,这是由于维护链表带来的额外开销造成的。

Map的选择

TreeMap总是比HashMap要慢,与使用TreeSet一样,TreeMap总是保证有序。当时用Map时,第一选择是HashMap,除非要求Map始终保持有序。

LinkedHashMap在插入时比HashMap慢一点,因

为它维护散列结构的同时还要维护链表,但是它的迭代速度更快。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值