- 浏览: 15743 次
- 性别:
- 来自: 杭州
文章分类
最新评论
首先对Set接口进行简要的说明。
存入Set的每个元素必须是惟一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set不保证维护元素的次序。Set与Collection有完全一样的接口。
在没有其他限制的情况下需要Set时应尽量使用HashSet,因为它对速度进行了优化。
下面是HashSet的定义:
HashSet继承了AbstractSet,实现了Set接口。其实AbstractSet已经实现Set接口了。AbstractSet继承自AbstractCollection,而AbstractCollection实现了Collection接口的部分方法,而Set接口和Collection接口完全一致,所以AbstractSet只是实现了AbstractCollection没有实现的Set接口的方法和重写了部分AbstractCollection已经实现的方法。
下面是HashSet定义的属性:
为什么会有一个HashMap<E,Object>定义的属性?
想一下HashMap有什么特点:基于哈希表,存储键值对,Key不能相同等等。Key不能相同!这个特点是不是和Set的元素不能相同和类似?如果将Set的元素当成Map的Key,是否就保证了元素的不重复?!答案是肯定的。但是Map存储键值对,Key有了,那么Value呢?这正是第二个属性PERSENT的意义。看到PERSENT属性时一个Object对象,且是static和final的,它的用途就是当做Value存进map中。
总结一下,HashSet的实现方式大致如下,通过一个HashMap存储元素,元素是存放在HashMap的Key中,而Value统一使用一个Object对象。
这样看来HashSet应该很简单,应该只是使用了HashMap的部分内容来实现。
下面看具体的其它代码来验证上面的猜想。
构造方法:
上面的构造方法都很简单,只有构造方法二中调用了addAll(Collection<? extends E> c)方法。该方法在AbstractCollection中定义,HashSet通过继承拥有该方法。
这个方法通过遍历c中的元素,然后调用add(E e)方法添加元素。
看add(E e)方法只是调用了HashMap(构造方法中提供了创建LinkedHashMap的方式,但是LinkedHashMap是继承HashMap的,put方法也是调用HashMap的put方法)的put方法将e当做Key,PERSENT当做Value加入到map中并根据返回值判断是否添加成功。
因为HashMap的put方法在Key已经存在的情况下返回的是对应的Value值,若Key不存在则返回的是null,所以根据返回的是null可以确定新元素被添加到HashSet中了,如果返回的是其他值则说明Key已经存在,即元素已经在HashSet中已经存在,add(E e)返回的结果为false。虽然add(E e)返回false说明了HashSet添加元素失败,但实际上其中的map中的内容已经被替换,原先的值被PERSENT代替。
如果原先的值就是null呢?其实不用考虑这个问题,因为通过HashSet添加的元素,Value的内容都是PERSENT,不会出现null的情况。
iterator()
很清楚了,返回的是HashMap中KeySet的迭代器。
size()
size()方法同样返回的是map的大小,所以HashSet根本就没定义size属性。
既然size()用的是map的大小,那么isEmpty()自然也是判断map。
这几个方法就不解释了。
remove(Object o)为什么还要判断结果呢?因为通过HashSet存入的元素,所对应的Value值都是PERSENT,如果传入的o不存在,map的remove方法返回为null,则对应的结果是HashSet的remove操作应该放回false,所以这里根据返回的结果判断是否移除成功。
只能感叹HashMap太强大了,HashSet是完全使用HashMap来实现的。
---------------------------------------------------------------------------------------------------------------------------------
LinkedHashSet源码分析
LinkedHashSet具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按照元素的插入次序显示。
看LinkedHashSet的内容。
LinkedHashSet继承自HashSet,HashSet基于HashMap实现,看LinkedHashSet类只是定义了四个构造方法,也没看到和链表相关的内容,为什么说LinkedHashSet内部使用链表维护元素的插入顺序(插入的顺序)呢?
注意这里的构造方法,都调用了父类HashSet的第五个构造方法:HashSet(int initialCapacity, float loadFactor, boolean dummy)。如果还记得上面的内容应该明白为什么是基于链表,下面再给出这个构造方法的内容。
区别于其他的HashSet的构造方法,这个方法创建的是一个LinkedHashMap。LinkedHashMap继承自HashMap,同时自身有一个链表结构用于维护元素顺序,默认情况使用的是插入元素(见《LinkedHashMap源码分析》),所以LinkedHashSet既有HashSet的访问速度(因为访问的时候都是通过HashSet的方法访问的),同时可以维护顺序。
下面是一个HashSet和LinkedHashSet维护元素顺序的例子。
存入Set的每个元素必须是惟一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set不保证维护元素的次序。Set与Collection有完全一样的接口。
在没有其他限制的情况下需要Set时应尽量使用HashSet,因为它对速度进行了优化。
下面是HashSet的定义:
1 public class HashSet<E> 2 extends AbstractSet<E> 3 implements Set<E>, Cloneable, java.io.Serializable
HashSet继承了AbstractSet,实现了Set接口。其实AbstractSet已经实现Set接口了。AbstractSet继承自AbstractCollection,而AbstractCollection实现了Collection接口的部分方法,而Set接口和Collection接口完全一致,所以AbstractSet只是实现了AbstractCollection没有实现的Set接口的方法和重写了部分AbstractCollection已经实现的方法。
下面是HashSet定义的属性:
1 private transient HashMap<E,Object> map; 2 private static final Object PRESENT = new Object();
为什么会有一个HashMap<E,Object>定义的属性?
想一下HashMap有什么特点:基于哈希表,存储键值对,Key不能相同等等。Key不能相同!这个特点是不是和Set的元素不能相同和类似?如果将Set的元素当成Map的Key,是否就保证了元素的不重复?!答案是肯定的。但是Map存储键值对,Key有了,那么Value呢?这正是第二个属性PERSENT的意义。看到PERSENT属性时一个Object对象,且是static和final的,它的用途就是当做Value存进map中。
总结一下,HashSet的实现方式大致如下,通过一个HashMap存储元素,元素是存放在HashMap的Key中,而Value统一使用一个Object对象。
这样看来HashSet应该很简单,应该只是使用了HashMap的部分内容来实现。
下面看具体的其它代码来验证上面的猜想。
构造方法:
1 // 构造方法一:调用默认的HashMap构造方法初始化map 2 public HashSet() { 3 map = new HashMap<E,Object>(); 4 } 5 // 构造方法二:根据给定的Collection参数调用HashMap(int initialCapacity)的构造方法创建一个HashMap(这个构造方法的HashMap的源码分析里已经描述过了) 6 // 调用addAll方法将c中的元素添加到HashSet对象中 7 public HashSet(Collection<? extends E> c) { 8 map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16)); 9 addAll(c); 10 } 11 // 构造方法三:构造一个指定初始化容量和负载因子的HashMap 12 public HashSet(int initialCapacity, float loadFactor) { 13 map = new HashMap<E,Object>(initialCapacity, loadFactor); 14 } 15 // 构造方法四:构造一个指定初始化容量的HashMap 16 public HashSet(int initialCapacity) { 17 map = new HashMap<E,Object>(initialCapacity); 18 } 19 // 构造方法五:构造一个指定初始化容量和负载因子的LinkedHashMap 20 // dummy参数被忽略,只是用于区分其他的,包含一个int、float参数的构造方法 21 HashSet(int initialCapacity, float loadFactor, boolean dummy) { 22 map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor); 23 }
上面的构造方法都很简单,只有构造方法二中调用了addAll(Collection<? extends E> c)方法。该方法在AbstractCollection中定义,HashSet通过继承拥有该方法。
1 public boolean addAll(Collection<? extends E> c) { 2 boolean modified = false; 3 Iterator<? extends E> e = c.iterator(); 4 while (e.hasNext()) { 5 if (add(e.next())) 6 modified = true; 7 } 8 return modified; 9 }
这个方法通过遍历c中的元素,然后调用add(E e)方法添加元素。
1 public boolean add(E e) { 2 return map.put(e, PRESENT)==null; 3 }
看add(E e)方法只是调用了HashMap(构造方法中提供了创建LinkedHashMap的方式,但是LinkedHashMap是继承HashMap的,put方法也是调用HashMap的put方法)的put方法将e当做Key,PERSENT当做Value加入到map中并根据返回值判断是否添加成功。
因为HashMap的put方法在Key已经存在的情况下返回的是对应的Value值,若Key不存在则返回的是null,所以根据返回的是null可以确定新元素被添加到HashSet中了,如果返回的是其他值则说明Key已经存在,即元素已经在HashSet中已经存在,add(E e)返回的结果为false。虽然add(E e)返回false说明了HashSet添加元素失败,但实际上其中的map中的内容已经被替换,原先的值被PERSENT代替。
如果原先的值就是null呢?其实不用考虑这个问题,因为通过HashSet添加的元素,Value的内容都是PERSENT,不会出现null的情况。
iterator()
1 public Iterator<E> iterator() { 2 return map.keySet().iterator(); 3 }
很清楚了,返回的是HashMap中KeySet的迭代器。
size()
1 public int size() { 2 return map.size(); 3 }
size()方法同样返回的是map的大小,所以HashSet根本就没定义size属性。
1 public boolean isEmpty() { 2 return map.isEmpty(); 3 }
既然size()用的是map的大小,那么isEmpty()自然也是判断map。
1 public boolean contains(Object o) { 2 return map.containsKey(o); 3 } 4 public void clear() { 5 map.clear(); 6 } 7 public Object clone() { 8 try { 9 HashSet<E> newSet = (HashSet<E>) super.clone(); 10 newSet.map = (HashMap<E, Object>) map.clone(); 11 return newSet; 12 } catch (CloneNotSupportedException e) { 13 throw new InternalError(); 14 } 15 }
这几个方法就不解释了。
1 public boolean remove(Object o) { 2 return map.remove(o)==PRESENT; 3 }
remove(Object o)为什么还要判断结果呢?因为通过HashSet存入的元素,所对应的Value值都是PERSENT,如果传入的o不存在,map的remove方法返回为null,则对应的结果是HashSet的remove操作应该放回false,所以这里根据返回的结果判断是否移除成功。
只能感叹HashMap太强大了,HashSet是完全使用HashMap来实现的。
---------------------------------------------------------------------------------------------------------------------------------
LinkedHashSet源码分析
LinkedHashSet具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按照元素的插入次序显示。
看LinkedHashSet的内容。
1 public class LinkedHashSet<E> 2 extends HashSet<E> 3 implements Set<E>, Cloneable, java.io.Serializable { 4 5 public LinkedHashSet(int initialCapacity, float loadFactor) { 6 super(initialCapacity, loadFactor, true); 7 } 8 9 public LinkedHashSet(int initialCapacity) { 10 super(initialCapacity, .75f, true); 11 } 12 13 public LinkedHashSet() { 14 super(16, .75f, true); 15 } 16 17 public LinkedHashSet(Collection<? extends E> c) { 18 super(Math.max(2*c.size(), 11), .75f, true); 19 addAll(c); 20 } 21 }
LinkedHashSet继承自HashSet,HashSet基于HashMap实现,看LinkedHashSet类只是定义了四个构造方法,也没看到和链表相关的内容,为什么说LinkedHashSet内部使用链表维护元素的插入顺序(插入的顺序)呢?
注意这里的构造方法,都调用了父类HashSet的第五个构造方法:HashSet(int initialCapacity, float loadFactor, boolean dummy)。如果还记得上面的内容应该明白为什么是基于链表,下面再给出这个构造方法的内容。
1 HashSet(int initialCapacity, float loadFactor, boolean dummy) { 2 map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor); 3 }
区别于其他的HashSet的构造方法,这个方法创建的是一个LinkedHashMap。LinkedHashMap继承自HashMap,同时自身有一个链表结构用于维护元素顺序,默认情况使用的是插入元素(见《LinkedHashMap源码分析》),所以LinkedHashSet既有HashSet的访问速度(因为访问的时候都是通过HashSet的方法访问的),同时可以维护顺序。
下面是一个HashSet和LinkedHashSet维护元素顺序的例子。
1 Set<String> linkedSet = new LinkedHashSet<String>(); 2 linkedSet.add("First"); 3 linkedSet.add("Second"); 4 linkedSet.add("Thrid"); 5 linkedSet.add("Fourth"); 6 System.out.println("LinkedHashSet:"+linkedSet); 7 Set<String> hashSet = new HashSet<String>(); 8 hashSet.add("First"); 9 hashSet.add("Second"); 10 hashSet.add("Thrid"); 11 hashSet.add("Fourth"); 12 System.out.println("HashSet:"+hashSet); 13 // LinkedHashSet:[First, Second, Thrid, Fourth] 14 // HashSet:[Fourth, Second, Thrid, First]
发表评论
-
java的动态性编程(四)——用 Javassist 进行类转换
2013-07-23 11:35 818Javassist 不仅是一个处理字节码的库,而且更因为它的另 ... -
java的动态性编程(三)——如何运用反射编程
2013-07-23 01:05 1072一开始,在真正进入编写实现代码的工作之前,我将首先定义 ... -
java的动态性编程(二)——引入Reflection
2013-07-22 13:13 807使用反射不同于常规的Java编程,其中它与 元数据--描述其它 ... -
java的动态性编程(一)——class和class load
2013-07-22 13:02 773本文中,我将讨论一些基本概念,它们是这些 Java 平台动态特 ... -
GC的优化提升系统性能
2013-07-22 12:52 1588项目背景 笔者处理某个大型电信项目的 CPU100% 的压力性 ... -
夜话java代码性能优化
2013-07-22 01:21 733写了多年程序,是否你 ... -
java容器类源码分析——LinkedList
2013-07-17 20:37 931LinkedList也和ArrayList一样 ... -
java容器类源码分析——LinkedHashMap
2013-07-16 17:15 791LinkedHashMap类似于HashMap,但是迭代遍历它 ... -
java容器类源码分析——ArrayList
2013-07-16 14:53 762ArrayList就是传说中的动态数组,就是Array的复杂版 ... -
java容器类源码分析——HashMap
2013-07-16 14:13 923在看HashMap源码之前先复 ... -
java容器类源码分析——TreeMap
2013-07-16 13:43 847TreeMap基于红黑树实现。查看“键”或“键值对”时 ...
相关推荐
本文将深入探讨Java中的两种重要容器类——`List`和`Set`,并分析它们之间的区别以及各自的适用场景。 #### 二、Java容器类List详解 **1. List接口简介** - `List`接口是`Collection`接口的一个子接口,主要特点...
### Java集合类与容器类详解 #### 一、引言 在Java编程中,集合类是一种非常重要的数据结构,用于存储一系列对象。相比于数组,集合类提供了更多的灵活性和功能,尤其是在处理未知数量的对象时更为方便。Java标准...
Java 容器类源码详解 Set Java 容器类源码详解 Set 是 Java 集合框架中的一种重要的集合类型,直接扩展自 Collection 接口。Set 表示由无重复对象组成的集合,在一个 Set 中,不能有两个引用指向同一个对象,或两个...
Java 容器是 Java 语言中的一种集合类库,主要包括 Collection 和 Map 两种类型。Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。 Collection Collection 是一种集合接口,提供了对集合...
// set容器接口的实现类有HashSet和 LinkedHashSet两个 // HashSet不保证迭代顺序, LinkedHashSet按照元素插入的顺序迭代. // 学习List对象容器的使用 // List容器中的对象允许重复 // 常用的list接口的实现类有...
首先,Set集合是一个不允许重复元素的集合,它有多种实现方式,其中包括HashSet、TreeSet和LinkedHashSet。HashSet是基于HashMap实现的,其元素存储在HashMap的key上,而value使用一个静态的默认对象。为了保证元素...
Set接口的主要实现类有HashSet、LinkedHashSet和TreeSet。每个实现类都有其特性,例如: 1. HashSet:不保证元素的顺序,允许null元素,但不允许重复元素。 2. LinkedHashSet:保持元素插入的顺序,允许null元素,...
在Java标准库中,`java.util`包是核心工具类库,包含了各种容器类、集合框架、日期时间处理、随机数生成、算法、比较器以及各种实用工具类。下面将详细介绍这些知识点。 1. 集合框架:Java中的集合框架是`java.util...
在Java编程中,容器是用来存储和管理对象的类或接口,...总之,Java中的容器类提供了灵活的方式来存储和操作对象,理解并熟练掌握List、Set、Map及其各自实现类的特性和使用场景,对于编写高效、可维护的代码至关重要。
Java容器类是Java集合框架的重要组成部分,主要用于存储和管理对象。在Java中,容器被分为两大类:Collection和Map。 1. **Collection接口**:Collection是所有单值容器的父接口,它定义了存储单一对象的基本操作。...
- - **Set接口**:无序且不包含重复元素的集合,如HashSet和LinkedHashSet。 - **Map接口**:键值对的集合,每个键都是唯一的,如HashMap和TreeMap。 3. **容器类的特性** - **动态大小**:与数组不同,容器类...
在这个主题下,我们将深入探讨Java中的核心容器类,包括数组、List、Set和Map,以及它们各自的特点和使用场景。 1. **数组**:数组是最基本的容器形式,它允许存储相同类型的数据元素,并通过索引来访问。数组提供...
9. **Java应用:两种Java容器类List和Set分析**:此文本文件可能对比了List和Set的使用情况,阐述了两者在不同情境下的优势和适用范围。 10. **Java容器简介**:这可能是对Java容器的入门介绍,涵盖了基本概念和...
ava基础 基础知识 面向对象基础 Java基本数据类型 string和包装类 final关键字特性 Java类和包 抽象类和接口 ...Java集合详解7:HashSet,TreeSet与LinkedHashSet Java集合详解8:Java集合类细节精讲 JavaWeb
- List、Set、Queue接口及其实现类:ArrayList、LinkedList、HashSet、LinkedHashSet、TreeSet、ArrayDeque等。 - Map接口及实现类:HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap等,理解它们的区别和使用...
在Java中,容器主要分为两大类:集合(Collections)和映射(Maps)。集合是单一元素的存储结构,而映射则是键值对的存储结构。在集合框架中,又可以细分为List、Set和Queue等接口,它们各自有特定的用途和行为。 1...
3. 泛型:泛型为容器类引入了类型参数,增强了类型安全,减少了类型转换的麻烦。 4. 异步编程与并发:Java提供了线程和并发工具,如ExecutorService、Semaphore等,帮助开发者编写高效且线程安全的代码。 四、Java ...
节点流和处理流 Java IO 的核心类 File Java IO 流对象 字节流对象InputStream OutputStream 字符流对象Reader Writer 字节流与字符流的转换新潮的 NIO 缓冲区(Buffer)通道(Channel) 示例:文件拷贝案例 BIO 和 NIO ...