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