java基础进阶篇(五)_HashSet------【java源码栈】

目录

  • 一.概述
  • 二.源码分析
    • 1.HashSet 源码
    • 2.两个重要成员变量
  • 三.构造方法
    • 1.public HashSet();
    • 2.public HashSet(Collection<? extends E> c);
    • 3.public HashSet(int initialCapacity, float loadFactor);
    • 4.public HashSet(int initialCapacity);
    • 5.特殊的构造方法
  • 四.常用方法
    • 1.int size()
    • 2.boolean isEmpty()
    • 3.boolean contains(Object o)
    • 4.boolean add(E e)
    • 5.boolean remove(Object o)
    • 6.void clear()
    • 7.Object clone()
    • 8.spliterator()
    • 9.iterator()

一.概述

??对于HashSet而言,它是基于HashMap来实现的,底层采用HashMap来保存元素。HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个private static final Object PRESENT = new Object();。HashSet跟HashMap一样,都是一个存放链表的数组。

二.源码分析

1.HashSet 源码

public class HashSet<E> extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

??加上摘自HashSet顶部的一段注释:

/**
 * This class implements the <tt>Set</tt> interface, backed by a hash table
 * (actually a <tt>HashMap</tt> instance).  It makes no guarantees as to the
 * iteration order of the set; in particular, it does not guarantee that the
 * order will remain constant over time.  This class permits the <tt>null</tt>
 * element.
 */

??分析理解下,大致意思是(这捉鸡的English):
??1.HashSet : 内部存储一个泛型元素.
??2.HashSet 实现了Set 接口,提供了所有可选的 Set 操作.
??3.继承自AbstractSet,实现Set接口时需要实现的工作量大大减少了.
??4.实现了Cloneable 接口, 可以调用 clone() 方法来返回实例的 field-for-field 拷贝.
??5.实现了序列化接口,可以序列化
??6.由hash 表进行维护, 底层实际是一个HashMap 的实例对象.
??7.不保证插入时的顺序, HashSet 是有自己的一套排序的.
??8.允许null 元素.
??底层是支持键值对Key-Value 的HashMap, 但HashSet 内部却只支持一个元素, 因为HashSet 只占用了HashMap 的Key 进行数据存储.

2.两个重要成员变量

1.HashMap<E,Object> map

??底层用HashMap 的实例对象来维护:

private transient HashMap<E,Object> map;

2.PRESENT

??HashMap是保存键值对的,但HashSet实际上只保存了key, 因此创建一个PRESENT存储所有的HashSet 元素.

private static final Object PRESENT = new Object();

三.构造方法

1.public HashSet();

??构造一个新的空的HashSet, 底层是构造一个空的容量为16,负载因子是0.75的HashMap.

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * default initial capacity (16) and load factor (0.75).
 */
public HashSet() {
    map = new HashMap<>();
}

??比如:

HashSet<Object> set1 = new HashSet<Object>();
set1.add("a");
set1.add("c");
set1.add("b");
set1.add("1");
set1.add("3");
set1.add("2");
System.out.println(set1);

??输出结果:

[a, 1, b, 2, c, 3]

??顺序并非插入时的顺序, HashSet 是按照哈希函数计算出key 的存放坐标. 关于这部分后面章节再单独研究整理.

2.public HashSet(Collection<? extends E> c);

??构造一个包含指定集合中所有元素的新HashSet,

HashSet<Object> set2 = new HashSet<Object>(Arrays.asList("a","b","c"));
System.out.println(set2);

??输出结果:

[a, b, c]

3.public HashSet(int initialCapacity, float loadFactor);

??构造一个新的空set,其底层HashMap实例具有指定的初始容量和加载因子

HashSet<Object> set3 = new HashSet<Object>(10, 0.5f);

4.public HashSet(int initialCapacity);

??构造一个新的空set,其底层HashMap实例具有指定的初始容量和默认的加载因子 (0.75)

HashSet<Object> set4 = new HashSet<Object>(10);

5.特殊的构造方法

??该方法仅用于构造 LinkedHashSet,创建的是 LinkedHashMap 而非之前的 HashMap,用于维护 Set 内元素的顺序, 通过增加一个dummy 参数来调用该方法.


HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

??查看源码发现, 该方法除了指定初始容量initialCapacity; 负载因子 loadFactor, 多了一个参数dummy, 仅用于识别构造方法. 底层创建的是LinkedHashMap;

??关于LinkedHashMap后面章节会列出其原理和使用. 在此先简单介绍下. HashMap是无序的,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap了, LinkedHashMap 能保证内部key-value是有序的.

四.常用方法

HashSet 的方法很少, 使用相对简单,

1.int size()

??源码如下, 实际是返回底层map的key数量.

public int size() {
    return map.size();
}

2.boolean isEmpty()

??源码如下,判断元素数量是否为0, 不能判断null 的情况, 底层调用的是map 的isEmpty方法.

public boolean isEmpty() {
    return map.isEmpty();
}

3.boolean contains(Object o)

??源码如下, 判断是否包含参数元素, 底层调用map的contains方法.

public boolean contains(Object o) {
    return map.containsKey(o);
}

4.boolean add(E e)

??源码如下,添加元素, 不保证插入时候的顺序, 底层调用map的put方法,返回时添加了==null的判断.真出现返回false的情况时, 应该考虑下容量问题了.

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

5.boolean remove(Object o)

??源码如下,删除指定元素, 若该元素存在则返回true,否则返回false.

public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

??这里可能有人会疑问为什么map.remove(o)==PRESENT; 这俩会相等???

??思考下HashSet 的特点, 是利用了Map 的key 来存储自己的元素, map.remove(o)返回的是该key对应的value, 而set 没有在map 的value 上赋值, 源码底层默认给了空的Object类型,不是null, 和PRESENT 刚好相等.

// Dummy value to associate with an Object in the backing Map
// 伪造一个值去支持底层的map, 英语捉鸡, 大概就这意思...
private static final Object PRESENT = new Object();

6.void clear()

??源码如下, 清空set 元素, 使其size等于0.

public void clear() {
    map.clear();
}

7.Object clone()

??源码如下,复制一个set, 注意这里的复制仍然是浅复制, 若泛型不是基本类型的包装类, 则仅拷贝对象, 复制后的对象和复制前是同一个引用地址!

public Object clone() {
    try {
        HashSet<E> newSet = (HashSet<E>) super.clone();
        newSet.map = (HashMap<E, Object>) map.clone();
        return newSet;
    } catch (CloneNotSupportedException e) {
        throw new InternalError(e);
    }
}

??举例:

HashSet<Object> set1 = new HashSet<Object>();
set1.add("a");
set1.add("c");
set1.add("b");
System.out.println(set1);
HashSet<Object> set2 = (HashSet<Object>) set1.clone();
System.out.println(set2);
System.out.println(set2.equals(set1));

User user = new User("1","tom");
HashSet<User> userSet1 = new HashSet<User>();
userSet1.add(user);
HashSet<User> userSet2 = (HashSet<User>) userSet1.clone();
System.out.println("复制前, 修改user前, userSet1: " + userSet1);
Iterator<User> iterator = userSet1.iterator();
while (iterator.hasNext()) {
    User temp = iterator.next();
    temp.setId("2");
}
System.out.println("复制后, 修改user后, userSet2: " + userSet2);
System.out.println("复制前, 修改user后, userSet1: " + userSet1);

??输出结果:

[a, b, c]
[a, b, c]
true
复制前, 修改user前, userSet1: [User [id=1, name=tom]]
复制后, 修改user后, userSet2: [User [id=2, name=tom]]
复制前, 修改user后, userSet1: [User [id=2, name=tom]]

??复制后修改了userSet1, 把user id从1->2, 发现userSet2也被修改了, 所以慎用!

8.spliterator()

??源码如下, java8 之后出现的迭代器, 支持对集合进行并行遍历, 先列在这儿, 后面我会对多线程并发的情况下数组集合的遍历处理做总结.

public Spliterator<E> spliterator() {
    return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
}

9.iterator()

??源码如下, 底层利用了map的iterator 方法.

public Iterator<E> iterator() {
    return map.keySet().iterator();
}

原文地址:https://www.cnblogs.com/tingbogiu/p/12410152.html

时间: 2024-11-09 01:09:08

java基础进阶篇(五)_HashSet------【java源码栈】的相关文章

java基础进阶篇(七)_LinkedHashMap------【java源码栈】

目录 一.概述 二.特点 三.应用场合 四.构造方法 1.参数为空 2.accessOrder 五.源码结构分析 六.常见问题 1.如何实现的元素有序? 2.如何保证顺序的正确以及同步 3.如何实现两种顺序(插入顺序或者访问顺序)? 4.为什么重写containsValue()而不重写containsKey()? 七.常用方法 一.概述 ??LinkedHashMap是HashMap的子类,关于HashMap可以看下前面的章节:java基础进阶篇 HashMap public class Lin

java基础进阶篇(六)_HashTable------【java源码栈】

一.概述 ??前面介绍了HashMap的结构和原理,这里介绍个类似HashMap的结构Hashtable. ??HashTable 官方解释是HashMap的轻量级实现, 和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射. ??所以我们结合HashMap来介绍HashTable, 比较下两者的区别. ??HashTable 使用的很少, 它支持线程安全, 通过内部方法加上 synchronized 实现, 因此同步锁的密度太大了, 在实际情

自学Java的我在五分彩源码出售帝都的北漂奋斗史

卧室里家人已经熟睡五分彩源码出售 <Q> 2952777280[链接] huaxianym.com ,虽然已经是深夜11点多了,我却一点睡意都没有,窗外的马路上依然是车水马龙,我坐在电脑桌旁,带着耳机听着汪峰的<北京,北京>,想着MV里面里的那些人,高楼林立,街景复杂,平凡的男女,为了生活,奔波在这座城市的角角落落. 2012年,为了当年的梦想,我离开了自己工作了5年的古城西安,来到了这座年轻人都向往的城市–北京,从此开启了我的北漂生活. 这一晃就是7年,其实在大学毕业后,2007

Android进阶:五、RxJava2源码解析 2

上一篇文章Android进阶:四.RxJava2 源码解析 1里我们讲到Rxjava2 从创建一个事件到事件被观察的过程原理,这篇文章我们讲Rxjava2中链式调用的原理.本文不讲用法,仍然需要读者熟悉Rxjava基本的用法. 一.Rxjava2 的基本用法 Rxjava是解决异步问题的,它的链式调用让代码看起来非常流畅优.现在我们带上线程切换以及链式调用来看看.下面代码是示例: Observable .create(new ObservableOnSubscribe<String>() {

JAVA基础部分复习(五、JAVA反射)

关于反射: 1.需要了解jvm类的加载机制(java高级部分会详细介绍) 2.反射的API其实只要多看看API和源码,很容易就懂了. 下面是代码,简单讲解反射的使用: import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * JAVA反射机制(首先对于反射,建议大家去看一下javaapi,要懂得看源码,这样才能更深入了解反射的原理,并且能

Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

一.前言 今天我们继续来看破解apk的相关知识,在前一篇:Eclipse动态调试smali源码破解apk 我们今天主要来看如何使用IDA来调试Android中的native源码,因为现在一些app,为了安全或者效率问题,会把一些重要的功能放到native层,那么这样一来,我们前篇说到的Eclipse调试smali源码就显得很无力了,因为核心的都在native层,Android中一般native层使用的是so库文件,所以我们这篇就来介绍如何调试so文件的内容,从而让我们破解成功率达到更高的一层.

java基础,集合,Arraylist,源码解析(基础)

ArrayList 是什么,定义? 这是动态的数组,它提供了动态的增加和减少元素,实现了List接口(List实现Collection,所以也实现Collection接口)灵活的设置数组的大小等好处 内部如何实现 1 /** 2 * The array buffer into which the elements of the ArrayList are stored. 3 * The capacity of the ArrayList is the length of this array b

logback的使用和五分彩源码搭建详解

一.logback的介绍 Logback是由log4j创始人设计的另一个开源日志组件. logback-core:其它两个模块的基础模块:五分彩源码搭建,企 娥:217 1793 408 logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能 二.logback取代log4j的理由: 1

java web进阶篇(四) Tomcat数据源

动态web开发的最大特点是可以进行数据库的操作,传统的jdbc操作由于步骤重复性造成程序性能下降. 先来回顾JDBC的操作原理 1.加载数据库驱动程序,数据库驱动程序通过classpath配置. 2.通过DirverManager类取得数据库连接对象. 3.通过Connection实例化PreparedStatement对象,编写sql语句命令操作数据库. 4.数据库属于资源操作,操作完成后要关闭数据库以释放资源. 其实以上操作,1.2.4步骤是重复的,保留3,实际上就是数据源产生的原因. 数据