Map、Set、List集合差别及联系详解
提到集合之前,先说说数组Array和集合的区别:
(1)数组是⼤⼩固定的,并且同⼀个数组只能存放类型⼀样的数据(基本类型/引⽤类型)
(2)JAVA集合可以存储和操作数⽬不固定的⼀组数据。
(3)若程序时不知道究竟需要多少对象,需要在空间不⾜时⾃动扩增容量,则需要使⽤容器类库,array不适⽤。
FYI:使⽤相应的toArray()和Arrays.asList()⽅法可以相互转换。
⼀、集合
集合类存放于java.util包中。
集合类存放的都是对象的引⽤,⽽⾮对象本⾝,出于表达上的便利,我们称集合中的对象就是指集合中对象的引⽤(reference)。
集合类型主要有3种:set(集)、list(列表)和map(映射)。
⼀、这三者什么关系呢
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
1.Collection接⼝
Collection是最基本的集合接⼝,⼀个Collection代表⼀组Object,即Collection的元素(Elements)。Java SDK不提供直接继承⾃Collection的类,Java SDK提供的类都是继承⾃Collection 的“⼦接⼝”如List
和Set。
所有实现Collection接⼝的类都必须提供两个标准的构造函数:⽆参数的构造函数⽤于创建⼀个空的Collection,有⼀个 Collection参数的构造函数⽤于创建⼀个新的Collection,这个新的Collection与传⼊的Collection有相同的元素。后⼀个构造函数允许⽤户复制⼀个Collection。
如何遍历Collection中的每⼀个元素?不论Collection的实际类型如何,它都⽀持⼀个iterator()的⽅法,该⽅法返回⼀个迭代⼦,使⽤该迭代⼦即可逐⼀访问Collection中每⼀个元素。典型的⽤法如下:
Iterator it = collection.iterator(); // 获得⼀个迭代⼦
while(it.hasNext()) {
Object obj = it.next(); // 得到下⼀个元素
}
iterator接⼝:
由Collection接⼝派⽣的两个接⼝是List和Set。
sortedlist2.Set
Set接⼝同样是Collection接⼝的⼀个⼦接⼝,它表⽰数学意义上的集合概念。Set中不包含重复的元素,即Set中不存两个这样的元素e1和e2,使得e1.equals(e2)为true。由于Set接⼝提供的数据结构是数学意义上集合概念的抽象,因此它需要⽀持对象的添加、删除,⽽不需提供随机访问。故Set接⼝与Collection的接⼝相同。
Set接⼝继承Collection接⼝,⽽且它不允许集合中存在重复项。所有原始⽅法都是现成的,没有引⼊新⽅法。具体的Set实现类依赖添加的对象的⽅法来检查等同性。
HashSet:使⽤HashMap的⼀个集的实现。虽然集定义成⽆序,但必须存在某种⽅法能相当⾼效地到⼀个对象。使⽤⼀个HashMap对象实现集的存储和检索操作是在固定时间内实现的.
TreeSet:在集中以升序对对象排序的集的实现。这意味着从⼀个TreeSet对象获得第⼀个迭代器将按升序提供对象。TreeSet类使⽤了⼀个TreeMap.
为优化 HashSet 空间的使⽤,您可以调优初始容量和负载因⼦。TreeSet 不包含调优选项,因为树总是平衡的,保证了插⼊、删除、查询的性能为log(n)。
HashSet 和 TreeSet 都实现 Cloneable 接⼝。
当您要从集合中以有序的⽅式抽取元素时,TreeSet实现会有⽤处。为了能顺利进⾏,添加到TreeSet的元素必须是可排序的。 Set的演⽰:
import java.util.*;
public class SetExample {
public static void main(String args[]) {
Set set = new HashSet();
set.add("Bernadine");
set.add("Elizabeth");
set.add("Gene");
set.add("Elizabeth");
set.add("Clara");
System.out.println(set);
Set sortedSet = new TreeSet(set);
System.out.println(sortedSet);
}
}
运⾏程序产⽣了以下输出。请注意重复的条⽬只出现了⼀次,列表的第⼆次输出已按字母顺序排序。
[Gene, Clara, Bernadine, Elizabeth]
[Bernadine, Clara, Elizabeth, Gene]
3.List
List接⼝继承了Collection 接⼝以定义⼀个允许重复项的有序集合。该接⼝不但能够对列表的⼀部分进⾏处理,还添加了⾯向位置的操作。
实际上有两种List: ⼀种是基本的ArrayList,其优点在于随机访问元素,另⼀种是更强⼤的LinkedList,它并不是为快速随机访问设计的,⽽是具有⼀套更通⽤的⽅法。
List : 次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多⽅法,使得能够向List中间插⼊与移除元素(这只推荐LinkedList使⽤。)⼀个List可以⽣成ListIterator,使⽤它可以从两个⽅向遍历List,也可以从List中间插⼊和移除元素。 ArrayList : 由数组实现的List。允许对元素进⾏快速随机访问,但是向List中间插⼊与移除元素的速度很慢。ListIterator只应该⽤来由后向前遍历ArrayList,⽽不是⽤来插⼊和移除元素。因为那⽐LinkedList开销要⼤很多。
LinkedList : 对顺序访问进⾏了优化,向List中间插⼊与删除的开销并不⼤,随机访问则相对较慢。(使⽤ArrayList代替。)还具有下列⽅法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 这些⽅法 (没有在任何接⼝或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使⽤。
Vector:实现⼀个类似数组⼀样的表,⾃动增加容量来容纳你所需的元素。使⽤下标存储和检索对象就象在⼀个标准的数组中⼀样。你也可以⽤⼀个迭代器从⼀个Vector中检索对象。Vector是唯⼀的同步容器类!!当两个或多个线程同时访问时也是性能良好的。
Stsck: 这个类从Vector派⽣⽽来,并且增加了⽅法实现栈!⼀种后进先出的存储结构。
⾯向位置的操作包括插⼊某个元素或 Collection 的功能,还包括获取、除去或更改元素的功能。在 List 中搜索元素可以从列表的头部或尾部开始,如果到元素,还将报告元素所在的位置。
void add(int index, Object element)
boolean addAll(int index, Collection collection)
Object get(int index)
int indexOf(Object element)
int lastIndexOf(Object element)
Object remove(int index)
Object set(int index, Object element)
List 接⼝不但以位置友好的⽅式遍历整个列表,还能处理集合的⼦集:
ListIterator listIterator()
ListIterator listIterator(int startIndex)
List subList(int fromIndex, int toIndex)
处理subList()时,位于fromIndex的元素在⼦列表中,⽽位于toIndex的元素则不是,提醒这⼀点很重要。以下for-loop 测试案例⼤致反映了这⼀点:
for (int i=fromIndex; i<toIndex; i++) {
// process element at position i
}
List⽤法⽰例:
其中set⽅法返回的是被替换的内容。
4.List和Set对⽐
Linked 改快读慢
Array 读快改慢
Hash 两都之间
Collection是集合接⼝
|————Set⼦接⼝:⽆序,不允许重复。
|————List⼦接⼝:有序,可以有重复元素。
区别:Collections是集合类
Set和List对⽐:
Set:检索元素效率低下,删除和插⼊效率⾼,插⼊和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查元素效率⾼,插⼊删除元素效率低,因为会引起其他元素位置改变。
Set和List具体⼦类:
Set
|————HashSet:以哈希表的形式存放元素,插⼊删除速度很快。
List
|————ArrayList:动态数组
|————LinkedList:链表、队列、堆栈。
Array和java.util.Vector
Vector是⼀种⽼的动态数组,是线程同步的,效率很低,⼀般不赞成使⽤。
5.Map
Map接⼝不是Collection接⼝的继承。⽽是从⾃⼰的⽤于维护键-值关联的接⼝层次结构⼊⼿。按定义,该接⼝描述了从不重复的键到值的映射。
我们可以把这个接⼝⽅法分成三组操作:改变、查询和提供可选视图。
改变操作允许您从映射中添加和除去键-值对。键和值都可以为null。但是,您不能把Map作为⼀个键或值添加给⾃⾝。
Object put(Object key, Object value)返回值是被替换的值。
Object remove(Object key)
void putAll(Map mapping)
void clear()
查询操作允许您检查映射内容:
Object get(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
int size()
boolean isEmpty()
提供可选视图⽅法允许您把键或值的组作为集合来处理。
public Set keySet()
public Collection values()
public Set entrySet()
因为映射中键的集合必须是唯⼀的,您⽤Set⽀持。因为映射中值的集合可能不唯⼀,您⽤Collection⽀持。最后⼀个⽅法返回⼀个实现Map.Entry接⼝的元素Set。
Map.Entry 接⼝
Map的entrySet()⽅法返回⼀个实现Map.Entry接⼝的对象集合。集合中每个对象都是底层Map中⼀个特定的键-值对。
通过这个集合迭代,您可以获得每⼀条⽬的键或值并对值进⾏更改。但是,如果底层Map在Map.Entry接⼝的setValue()⽅法外部被修改,此条⽬集就会变得⽆效,并导致迭代器⾏为未定义。
HashMap 类和 TreeMap 类
“集合框架”提供两种常规的Map实现:HashMap和TreeMap。和所有的具体实现⼀样,使⽤哪种实现取
决于您的特定需要。在Map中插⼊、删除和定位元素,HashMap是最好的选择。但如果您要按顺序遍历键,那么TreeMap会更好。根据集合⼤⼩,先把元素添加到HashMap,再把这种映射转换成⼀个⽤于有序键遍历的TreeMap可能更快。使⽤HashMap要求添加的键类明确定义了hashCode()实现。有了TreeMap实现,添加到映射的元素⼀定是可排序的。
为了优化HashMap空间的使⽤,您可以调优初始容量和负载因⼦。这个TreeMap没有调优选项,因为该树总处于平衡状态。
HashMap和TreeMap都实现Cloneable接⼝。
Hashtable类和Properties类是Map接⼝的历史实现。
HashTable:实现⼀个映象,所有的键必须⾮空。为了能⾼效的⼯作,定义键的类必须实现hashcode()⽅法和equal()⽅法。这个类是前⾯java实现的⼀个继承,并且通常能在实现映象的其他类中更好的使⽤。
HashMap:实现⼀个映象,允许存储空对象,⽽且允许键是空(由于键必须是唯⼀的,当然只能有⼀个)。
WeakHashMap:实现这样⼀个映象:通常如果⼀个键对⼀个对象⽽⾔不再被引⽤,键/对象对将被舍
弃。这与HashMap形成对照,映象中的键维持键/对象对的⽣命周期,尽管使⽤映象的程序不再有对键的引⽤,并且因此不能检索对象。
TreeMap:实现这样⼀个映象,对象是按键升序排列的。
映射的使⽤⽰例:
以下程序演⽰了具体Map类的使⽤。该程序对⾃命令⾏传递的词进⾏频率计数。HashMap起初⽤于数据存储。后来,映射被转换为TreeMap以显⽰有序的键列列表。
import java.util.*;
public class MapExample {
public static void main(String args[]) {
Map map = new HashMap();
Integer ONE = new Integer(1);
for (int i=0, n=args.length; i<n; i++) {
String key = args[i];
Integer frequency = ((key);
if (frequency == null) {
frequency = ONE;
} else {
int value = frequency.intValue();
frequency = new Integer(value + 1);
}
map.put(key, frequency);
}
System.out.println(map);
Map sortedMap = new TreeMap(map);
System.out.println(sortedMap);
}
}
结果:
//⽆序输出:
{prescribed=1, a=1, time=2, any=1, no=1, shall=1, nor=1, peace=1, owner=1, soldier=1, to=1, the=2, law=1, but=1, manner=1, without=1, house=1, in=4, by=1, consent=1, war=1, quartered=1, be=2, of=3} //有序输出:
{a=1, any=1, be=2, but=1, by=1, consent=1, house=1, in=4, law=1, manner=1, no=1, nor=1, of=3, owner=1, peace=1, prescribed=1, quartered=1, shall=1, soldier=1, the=2, time=2, to=1, war=1, without=1}解疑:
1、什么是Iterator
⼀些集合类提供了内容遍历的功能,通过java.util.Iterator接⼝。这些接⼝允许遍历对象的集合。依次操作每个元素对象。当使⽤ Iterators时,在获得Iterator的时候包含⼀个集合快照。通常在遍历⼀个Iterator的时候不建议修改集合本省。
2、Iterator与ListIterator有什么区别?
Iterator:只能正向遍历集合,适⽤于获取移除元素。ListIerator:继承Iterator,可以双向列表的遍历,同样⽀持元素的修改。
3、什么是HaspMap和Map?
Map是接⼝,Java 集合框架中⼀部分,⽤于存储键值对,HashMap是⽤哈希算法实现Map的类。
4、HashMap与HashTable有什么区别?对⽐Hashtable VS HashMap
两者都是⽤key-value⽅式获取数据。Hashtable是原始集合类之⼀(也称作遗留类)。HashMap作为新集合框架的⼀部分在Java2的1.2版本中加⼊。它们之间有⼀下区别:
● HashMap和Hashtable⼤致是等同的,除了⾮同步和空值(HashMap允许null值作为key和value,⽽Hashtable不可以)。
● HashMap没法保证映射的顺序⼀直不变,但是作为HashMap的⼦类LinkedHashMap,如果想要预知的顺序迭代(默认按照插⼊顺序),你可以很轻易的置换为HashMap,如果使⽤Hashtable就没那么容易了。
● HashMap不是同步的,⽽Hashtable是同步的。
●迭代HashMap采⽤快速失败机制,⽽Hashtable不是,所以这是设计的考虑点。
5、在Hashtable上下⽂中同步是什么意思?
同步意味着在⼀个时间点只能有⼀个线程可以修改哈希表,任何线程在执⾏hashtable的更新操作前需要获取对象锁,其他线程等待锁的释放。
6、什么叫做快速失败特性
从⾼级别层次来说快速失败是⼀个系统或软件对于其故障做出的响应。⼀个快速失败系统设计⽤来即时报告可能会导致失败的任何故障情况,它通常⽤来停⽌正常的操作⽽不是尝试继续做可能有缺陷的。当有问题发⽣时,快速失败系统即时可见地发错错误告警。在Java中,快速失败与iterators有关。如果⼀个iterator在集合对象上创建了,其它线程欲“结构化”的修改该集合对象,并发修改异常(ConcurrentModificationException)抛出。
7、怎样使Hashmap同步?
HashMap可以通过Map m = Collections.synchronizedMap(hashMap)来达到同步的效果。
8、什么时候使⽤Hashtable,什么时候使⽤HashMap
基本的不同点是Hashtable同步HashMap不是的,所以⽆论什么时候有多个线程访问相同实例的可能时,就应该使⽤Hashtable,反之使⽤HashMap。⾮线程安全的数据结构能带来更好的性能。
如果在将来有⼀种可能—你需要按顺序获得键值对的⽅案时,HashMap是⼀个很好的选择,因为有HashMap的⼀个⼦类 LinkedHashMap。所以如果你想可预测的按顺序迭代(默认按插⼊的顺序),你可以很⽅便⽤LinkedHashMap替换HashMap。反观要是使⽤的Hashtable就没那么简单了。同时如果有多个线程访问HashMap,Collections.synchronizedMap()可以代替,总的来说HashMap更灵活。
9、为什么Vector类认为是废弃的或者是⾮官⽅地不推荐使⽤?或者说为什么我们应该⼀直使⽤ArrayList⽽不是Vector
你应该使⽤ArrayList⽽不是Vector是因为默认情况下你是⾮同步访问的,Vector同步了每个⽅法,你⼏乎从不要那样做,通常有想要同步的是整个操作序列。同步单个的操作也不安全(如果你迭代⼀个Vector,你还是要加锁,以避免其它线程在同⼀时刻改变集合).⽽且效率更慢。当然同样有锁的开销即
使你不需要,这是个很糟糕的⽅法在默认情况下同步访问。你可以⼀直使⽤Collections.sychronizedList来装饰⼀个集合。
事实上Vector结合了“可变数组”的集合和同步每个操作的实现。这是另外⼀个设计上的缺陷。Vector还有些遗留的⽅法在枚举和元素获取的⽅法,这些⽅法不同于List接⼝,如果这些⽅法在代码中程序员更趋向于想⽤它。尽管枚举速度更快,但是他们不能检查如果集合在迭代的时候修改了,这样将导致问题。尽管以上诸多原因,Oracle也从没宣称过要废弃Vector。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论