java集合浅谈(一)
一、类库结构图概览
容器对象仅能持有对象引用(对象的指针),而不是Copy对象信息,从网上搜得几张Java中集合类库的结构图,如下所示:
二、解说Collection
2.1 Collection
(1)Collection是最基本的集合接口,由Collection接口派生的两个接口是List和Set。JDK提供的类都继承自Collection的“子接口”,如List和Set。
(2)所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数和有一个Collection参数的构造函数。前者用于创建一个空的Collection,后者用于创建一个新的Collection,允许用户复制一个Collection。
(3)不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,可逐一访问Collection中每一个元素。用法如下:
Iterator it = collection.iterator(); // 获得一个迭代子
while(it.hasNext()) {
Object obj = it.next(); // 得到下一个元素
}
2.2 Collection和Map区别:
(1)Collection类型,每个位置只有一个元素。
(2)Map类型,持有key-value形式的数据(键值对),即其元素是成对的对象。
三、细说List、Set、Map
3.1 List:以某种插入顺序来维护元素顺序,另外元素可以重复。
ArrayList:是用数组来实现的,读取速度快,插入与删除速度慢(因为插入与删除时要移动后面的元素),适合于随机访问。ArrayList初始化时不可指定容量,如果以new ArrayList()方式创建时,初始容量为10个;如果以new ArrayList(Collection c)初始化时,容量为c.size()*1.1,即增加10%的容量。当向ArrayList中添加一个元素时,先进行容器的容量调整,如果容量不够时,则增加至原来的1.5倍加1,再然后把元素加入到容器中,即以原始容量的0.5倍比率增加。
LinkedList:是用双向链表来实现的,删除与插入速度快,读取速度较慢,因为它读取时是从头向尾或从尾向头查找元素,适合于元素的插入与删除操作。
Vector:功能与ArrayList几乎相同,也是用数组来实现的,其添加、删除等都是基于线程同步的。当一个Iterator正在被使用,如果另一个线程改变了Vector的状态,这时将抛出ConcurrentModificationException异常,因此必须捕获该异常。Vector初始化时可以设定容量,如果以new Vector()方式创建,则其初始容量为10,超过容量时以2倍容量增加;如果以new Vector(Collection c)方式创建,则其初始容量为c.size()*1.1,超过时以2倍容量增加;如果以new Vector(int initialCapacity, int capacityIncrement),则以capacityIncrement容量单位增加。
Stack:Stack继承自Vector,实现一个后进先出的堆栈,线程同步。
3.2 Set:Set接口不保证维护元素的次序,随机访问不具有意义(List或数组具备随机访问性质),存入Set的每个元素必须是唯一的(即元素不可重复),也就是说加入Set的Object必须定义equals()方法以确保对象的唯一性。
HashSet:HashSet是最常用的,其查询速度最快(采用散列函数),存入HashSet的对象必须定义hashCode()方法,因为其内部以HashMap来实现, 它不保证集合的迭代顺序,特别是它不保证该顺序恒久不变。此类允许使用 null元素,其实现不是同步的。
LinkedHashSet:继承了HashSet,其内部使用LinkedHashMap实现(使用链表维护元素的顺序(哈希函数+链表)),在使用迭代器遍历LinkedHashSet时,结果会按元素插入的次序显示。
TreeSet:TreeSet实现了SortedSet接口,其内部以TreeMap来实现,生成一个总是处于排序状态的Set。
3.3 Map:Map提供的不是对象与数组的关联,而是对象和对象的关联。
TreeMap:键以某种排序规则排序,其内部以red-black(红-黑)树数据结构来实现,实现了SortedMap接口
HashMap: HashMap是以哈希表数据结构来实现的,查找对象时通过哈希函数计算其位置,它是为快速查询而设计的,其内部定义了一个hash表数组(Entry[] table),元素会通过哈希转换函数将元素的哈希地址转换成数组中存放的索引,如果有冲突,则使用散列链表的形式将所有相同哈希地址的元素串起来。
LinkedHashMap:继承HashMap,其内部实体LinkedHashMap.Entry继承自HashMap.Entry,LinkedHashMap.Entry在HashMap.Entry的基础上新增了两个实体引用(Entry before, after),这样实体可以相互串链起来形成链,并且在LinkedHashMap中定义了一个头节点(Entry header)用来指向循环双向链的第一个元素(通过after指向)与最后一个元素(通过before指向)。在添加一个元素时,先通过父类HashMap将元素加入到hash表数组里,然后再在链尾(header.before指向位置)添加(当然这一过程只是调整LinkedHashMap.Entry对象内部的before、after而已,并不是创建一个新的链表结构向里加);删除一个元素时,先从hash表数组中删除,再将被删除的元素彻底的从双向链中断开,其实在链中添加与删除操作与LinkedList是一样的。
WeakHashMap:WeakHashMap是一种改进的HashMap,若一个key不再被外部所引用,那么该key可以被GC回收。
Hashtable:Hashtable是以哈希表数据结构来实现的,解决冲突时与HashMap一样,也是采用了散列链表的形式,不过性能比HashMap要低。
3.4 集合中的键值是否允许为null
(1)List:可以有多个null。
(2)HashSet:能插入一个null(其内部以HashMap实现 ),忽略重复元素(即不插入重复元素)。
(3)TreeSet:不能插入null (其内部以TreeMap 实现 ) ,且元素不能重复,如果待插入的元素已经存在,则不插入。
(4)HashMap:允许一个null键与多个null值;若键重复,则覆盖以前值。
(5)HashTable:不允许null键与null值(否则运行进报空指针异常);若键重复,则覆盖以前值。
(6)TreeMap:不允许null键(实际上可以插入一个null键,如果这个Map里只有一个元素是不会报错的,因为一个元素时没有排序操作,也就不会报空指针异常,但如果插入第二个时就会立即报错),但允许多个null值;若键重复,则覆盖以前值。
此处看一个HashMap的简单示例:若键重复,则覆盖以前值
package com.test; import java.util.*; public class Test {
public static void main(String[] args) {
Map map = new HashMap();
map.put("Rajib Sarma", "100");
map.put("Rajib Sarma", "200");// The value "100" is replaced by "200".
map.put("Sazid Ahmed", "200"); Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
System.out.println(key);
System.out.println(val);
}
}
}
结果如下:
Sazid Ahmed
200
Rajib Sarma
200
四、应用场景
4.1 对List的选择
(1)对于随机查询与迭代遍历操作,数组比所有的容器都要快。
(2)从中间的位置插入和删除元素,LinkedList要比ArrayList快,特别是删除操作。
(3)Vector通常不如ArrayList快,且应该避免使用,它目前仍然存在于类库中的原因是为了支持过去的代码。
(4)最佳实践:将ArrayList作为默认首选,只有当程序的性能因经常从list中间进行插入和删除而变差的时候才去选择LinkedList。当然了,如果只是使用固定数量的元素,就应该选择数组了。
4.2 对Set的选择
(1)HashSet的性能总比TreeSet好(特别是最常用的添加和查找元素操作)。
(2)TreeSet存在的唯一原因是,它可以维持元素的排序状态,所以只有当你需要一个排好序的Set时,才应该使用TreeSet。
(3)对于插入操作,LinkedHashSet比HashSet略微慢一点:这是由于维护链表所带来额外开销造成的。不过,因为有了链表,遍历LinkedHashSet会比HashSet更快。
4.3 对Map的选择
(1)HashMap和Hashtable的效率大致相同(通常HashMap更快一点,所以HashMap有意取代Hashtable)。
(2)TreeMap通常比HashMap慢,因为要维护排序。
(3)HashMap正是为快速查询而设计的。
(4)LinkedHashMap比HashMap慢一点,因为它维护散列数据结构的同时还要维护链表。
五、Iterator
http://blog.csdn.net/chenssy/article/details/37521461
5.1 Iterator对ArrayList(LinkedList)的操作限制
(1)刚实例化的迭代器如果还没有进行后移操作(next)是不能马上进行删除与修改操作的。
(2)进行添加操作后是不能立即进行删除与修改操作的。
(3)进行删除操作后可以进行添加,但不能进行修改操作。
(4)进行修改后是可以立即进行删除与添加操作的。
(5)可以用ListIterator对集合连续添加与修改,但不能连续删除。
5.2 通过迭代器修改集合结构
在使用迭代器遍历集合时,我们不能通过集合本身来修改集合的结构(添加、删除),只能通过迭代器来操作。
下面是对HashMap进行删除操作的测试,其它集合也是这样:
package com.test; import java.util.*;
import java.util.Map.Entry; public class Test {
public static void main(String[] args) {
Map map = new HashMap();
map.put(1, 1);
map.put(2, 3);
Set entrySet = map.entrySet();
Iterator it = entrySet.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
/*
* 可以通过迭代器来修改集合结构,但前提是要在已执行过next或前移操作,
* 否则会抛异常:IllegalStateException
*/
//it.remove(); /*
* 抛异常:ConcurrentModificationException
* 不能使用集合本身来修改集合的结构
*/
// map.remove(entry.getKey());
}//end while
System.out.println(map);
}
}
六、HashMap
6.1 HashMap的数据结构
HashMap是一个数组和链表的结合体(在数据结构称“链表散列“),如下图所示:
当我们往HashMap中put元素的时候,先根据key的hash值得到这个元素在数组中的位置(即下标),然后就可以把这个元素放到对应的位置中了。如果这个元素所在的位置上已经存放有其他元素,那么在同一个位置上的元素将以链表的形式存放,新加入的元素放在链头,之前的元素放在链尾,如下图所示:
注意:与TreeMap不同,HashMap不保证元素顺序,根据需要该容器可能会对元素重新哈希,元素的顺序会被打散,因此不同时间迭代同一个HashMap的顺序可能会不同。
6.2 HashMap和Hashtable的区别
(1)在HashMap中,可以允许null作为键,且只可以有一个,否则覆盖,但可以有一个或多个值为null。当get()方法返回null时,既可以表示 HashMap中没有该键,也可以表示该键所对应的值为null,所以HashMap不能由get()方法来判断是否存在某个键,而应该用containsKey()方法来判断;而Hashtable不允许null键与null值。
(2)HashMap中hash数组的默认大小是16,而且一定是2的多少次方;HashTable中hash数组的默认大小是11,增加的方式是 int newCapacity = oldCapacity * 2 + 1;,即增加至2倍(而不是2倍加1,因为扩容是在增加元素前进行的,在扩容后会将新增元素放入容器中)。另外两者的默认负载因子都是0.75。
(3)HashMap是Map接口的一个实现类;Hashtable是Dictionary的子类
public class HashMap extends AbstractMap implements Map
public class Hashtable extends Dictionary implements Map
(4)HashMap中的方法在缺省情况下是非同步的,而Hashtable中的方法是同步的。在多线程应用程序中,我们应该使用Hashtable;而对于HashMap,则需要额外的同步机制(其实HashMap的同步问题可通过Collections的一个静态方法得到解决:Map Collections.synchronizedMap(Map m),当然也可以自己在使用地方加锁)。
注:java.util.concurrent.ConcurrentHashMap是HashMap的线程安全版,同HashMap相比,ConcurrentHashMap不仅保证了访问的线程安全性,而且在效率上与Hashtable相比,有较大的提高。
(5)两者遍历方式的内部实现不同,HashMap、Hashtable均使用了Iterator,而由于历史原因,Hashtable还使用了Enumeration的方式 。
(6)求哈希地址与哈希地址转hash数组(Entry table[])索引的方法不同,如下所示:
HashTable直接使用对象的hashCode:
int hash = key.hashCode();//直接使用键的hashCode方法求哈希值
//哈希地址转hash数组索引,先使用最大正int数与,这样将负转正数,再与数组长度求模得到存入的hash数组索引位置
int index = (hash & 0x7FFFFFFF) % tab.length;
HashMap重新计算hash值,而且用位运算&代替求模:
int hash = hash(k);
int i = indexFor(hash, table.length); static int hash(Object x) {
//以键本身的hash码为基础求哈希地址,但看不懂是什么意思
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
static int indexFor(int h, int length) {
return h & (length-1);//将哈希地址转换成哈希数组中存入的索引号
}
补充:
(1)当以自己的对象做为HashMap、HashTable、LinkedHashMap、HashSet 、LinkedHashSet 的键时,一定要重写hashCode ()与equals ()方法,因为Object的hashCode()是返回内存地址,且equals()方法也是比较内存地址,所以当要在这些hash集合中查找时,如果是另外new出的新对象是查不到的,除非重写这两个方法。因为AbstractMap类的containsKey(Object key)方法实现如下:
if (e.hash == hash && eq(k, e.key))//先比对hashcode,再使用equals
return true; static boolean eq(Object x, Object y) {
return x == y || x.equals(y);
}
Java中的集合框架的哈希是以一个对象查找另外一个对象,所以重写hasCode与equals方法很重要。String对象是可以作为键的,因为已重写了这两个方法。
(2)重写hashCode()与equals()这两个方法是针对哈希类,至于其它集合,如果要用public boolean contains(Object o)或containsValue(Object value)查找时,只需要实现equals()方法即可,他们都只使用对象的 equals方法进行比对,没有使用 hashCode方法。
(3)TreeMap/TreeSet:放入其中的元素一定要具有自然比较能力(即要实现java.lang.Comparable接口)或者在构造TreeMap/TreeSet时传入一个比较器(实现java.util.Comparator接口),如果在创建时没有传入比较器,而放入的元素也没有自然比较能力时,会出现类型转换错误(因为在没有较器时,会试着转成Comparable型)。
两种比较接口如下:
//自然比较器
public interface java.lang.Comparable {
public int compareTo(Object o);
} public interface java.util.Comparator {
int compare(Object o1, Object o2);
boolean equals(Object obj);
}
(4)在多线程环境下,可以使用Collections类的相应静态方法来包装相应的集合类,使它们线程安全:
A、public static Collection synchronizedCollection (Collection c)方法的实质是返回包装后的SynchronizedCollection子类。
B、使用Collections的synchronizedList、synchronizedMap、synchronizedSet等方法来获取经过包装了的同步集合(如:List list = Collections.synchronizedList(new LinkedList(...));或 Collections.synchronizedMap(originMap) )
Collections代码如下:
public class Collections { //... static Collection synchronizedCollection(Collection c, Object mutex) {
return new SynchronizedCollection(c, mutex);
} public static List synchronizedList(List list) {
//...
} static Set synchronizedSet(Set s, Object mutex) {
//...
} public static Map synchronizedMap(Map m) {
return new SynchronizedMap(m);
} //...
static class SynchronizedCollection implements Collection, Serializable { Collection c; // 对给定集合进行同步(包装)
Object mutex; // 对象锁,可以自己设置 //...
SynchronizedCollection(Collection c, Object mutex) {
this.c = c;
this.mutex = mutex;
} public int size() {
synchronized (mutex) {
return c.size();
}
} public boolean isEmpty() {
synchronized (mutex) {
return c.isEmpty();
}
}
//...
} static class SynchronizedList extends SynchronizedCollection implements List { List list; SynchronizedList(List list, Object mutex) {
super(list, mutex);
this.list = list;
} public Object get(int index) {
synchronized (mutex) {
return list.get(index);
}
}
//...
} static class SynchronizedSet extends SynchronizedCollection implements Set {
SynchronizedSet(Set s) {
super(s);
}
//...
}
//...
}
队列类图:
Deque(双端队列):两端都可以进出的队列。当我们约束从队列的一端进出时,就形成了另一种存取模式,它遵循先进后出原则,这就是栈结构。
参考资料
(1)http://www.blogjava.net/EvanLiu/archive/2007/11/12/159884.html
(2)http://www.cnblogs.com/CarpenterLee/p/5440428.html
(3)http://www.cnblogs.com/caca/p/java_Hashtable.html
java集合浅谈(一)的更多相关文章
- 【推荐】JAVA基础◆浅谈3DES加密解密
国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送)国内私 ...
- Java基础篇——集合浅谈
原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/10703558.html,否则将追究法律责任!!! Set(基于Map来实现的,不细说) H ...
- java多线程浅谈
当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 分这几种情况: 1.其他方法前是否加了synchronized关键字,如果没加,则能. 2 ...
- Java:浅谈InputStream的close方法
原则:最好在任何时候使用InputStream或者OutputStream的时候,在finally中调用close()方法,显式关闭. 一个典型的示例 InputStream in = null; t ...
- 【JAVA】浅谈java内部类
一.什么是内部类? 到底什么是内部类呢?通俗的讲,就是在类内部定义的类,包括定义在一个类的方法外面.方法里面或者代码块中. 二.为什么要使用内部类? 为什么我们要不走寻常路,把一个类定义在另一个类的内 ...
- 【JAVA】浅谈java枚举类
一.什么情况下使用枚举类? 有的时候一个类的对象是有限且固定的,这种情况下我们使用枚举类就比较方便? 二.为什么不用静态常量来替代枚举类呢? public static final int SEASO ...
- java - 异常浅谈
java提供异常处理机制中,可以分为RuntimeException和checked Exception两种. RuntimeException 是运行时异常,是程序本身无法解决的.例如,对于一个用户 ...
- 通过Java代码浅谈HTTP协议
最近刚看了http协议,想写点东西加深一下理解,如果哪儿写错了,请指正. 1 介绍 HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写.它的发展是万维网协会(W ...
- java序列化浅谈
首先大家进来第一个疑问肯定是"什么是序列化?为什么要使用序列化?怎么实现一个简单的序列化案例?" 1.序列化就是把对象以一种规范的二进制形式存在内存中,另一边以反序列化方式获取: ...
随机推荐
- POJ 3088 斯特林
题意:有一个n个按钮的锁,按下一些按钮打开门,有多少开门方式,其中,一些按钮可以选,可以不选,选中的按钮 可以分成一些集合,集合之间无序,是同时按下的. 分析: 1.首先选择 i 个按钮,组合数 2. ...
- Hive UDF 用户自定义函数 编程及使用
首先创建工程编写UDF 代码,示例如下: 1. 新建Maven项目 udf 本机Hadoop版本为2.7.7, Hive版本为1.2.2,所以选择对应版本的jar ,其它版本也不影响编译. 2. po ...
- css块元素的 display 属性 inline-block 的应用
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> < ...
- C# 动态改变webservice的访问地址
1.添加一个App.config配置文件. 2.配置服务http://Lenovo-PC:80/EvisaWS/WharfService?wsdl,那么在上面的文件中就会自动生成服务的配置: < ...
- HDU1069 Monkey and Banana
HDU1069 Monkey and Banana 题目大意 给定 n 种盒子, 每种盒子无限多个, 需要叠起来, 在上面的盒子的长和宽必须严格小于下面盒子的长和宽, 求最高的高度. 思路 对于每个方 ...
- flume ng 1.3 安装(转)
http://blog.csdn.net/hijk139/article/details/8308224 业务系统需要收集监控系统日志,想到了hadoop的flume.经过试验,虽说功能不算足够强大, ...
- kinect v2
http://www.tuicool.com/articles/NbmyyeU https://channel9.msdn.com/Blogs/raw-tech/Making-your-body-th ...
- Python中使用list和tuple
list: Python内置的一种数据类型是列表:list.list是一种有序的集合,可以随时添加和删除其中的元素. 比如,列出班里所有同学的名字,就可以用一个list表示: 变量classmates ...
- 学习WebSocket笔记
由于HTTP协议是无状态的,服务器只会响应来自客户端的请求,但是它与客户端之间不具备持续连接. 当用户在浏览器上进行操作时,可以请求服务器上的api:但是反过来不可以:服务端发生了一件事,无法将这个事 ...
- javascript 中数组的创建 添加 与将数组转换成字符串 页面三种提交请求的方式
创建js数组 var array=new Array(); Java中创建数组 private String[] array=new String[3]; 两个完全不同的,js中是可变长度的 添加内容 ...