资讯专栏INFORMATION COLUMN

HashMap,HashSet,Hashtable,Vector,ArrayList 的关系

microcosm1994 / 3012人阅读

摘要:继承的类,泛型为时,注意和其他的类型不同。因为是线程安全简单来说,是个一维数组。同样,指定和,如果中间发生变化则会抛出异常。最后,可以,然后,使用基类可以实现和的快速赋值。线程安全也是线程安全的,和一样,连函数都丧心病狂地同步了。

这么几个比较常用的但是比较容易混淆的概念同出于 java.util 包。本文仅作几个类的浅度解析。
(本文基于JDK1.7,源码来自openjdk1.7。)

├── Collection
│   ├── List
│   │   ├── ArrayList
│   │   ├── Vector
│   │   └── LinkedList and so on;
│   Set
│   ├── HashSet
│   └── LinkedHashSet and so on;
└── Map
    ├── Hashtable
    ├── HashMap
    └── WeakHashMap and so on;

Collection和Map

按理来说和这两者没有什么特别关系。然而本文中仍然将这两个混在一起:

第一,HashSet,HashMap,Hashtable长得太像了,没有理由不写再一起。

第二,HashSet是个Set,其实骨子里是个Map。

List

继承List的类,泛型为Integer时,注意和其他的类型不同。(因为Index是Integer)

线程安全

简单来说,List是个一维数组。Vector和ArrayList区别在于:

Vector是个线程安全的,而ArrayList不是。这点看源码可以看出来,Vector中各种synchronized,甚至连size属性都丧心病狂地synchronized了。

同样,Iterator指定Vector和ArrayList,如果中间发生变化则会抛出ConcurrentModificationException异常。
不仅仅是List,很多地方的Iterator都有这个异常,

自增大小

ArrayList没办法设置自增大小,但是仍然可以自增。
Vector可以设置自增大小。

ArrayList的自增函数:

Vector的自增函数:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    //在这里,如果没有设置自增大小的话,那么默认自增大小则是原来的Vector大小。
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

ArrayList自增函数:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //新长度则是旧长度的大小加上一个移位运算(一半的大小)。
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

所以可以简单的认为Vector默认自增一倍的长度,ArrayList则是默认自增一半的长度。

同样,对他们的源码分析:
Vector可以设置自增大小,而ArrayList则无法设置(没有这个构造方法)。

public Vector(int initialCapacity, int capacityIncrement) 

最后,可以 List list=new ArrayList<>();,然后list=new Vector<>();,使用基类List可以实现ArrayList和Vector的快速赋值。

Map

Map可以当成是简单的“Key-Value”数据库,例如memcached和redis。

  

Hashtable基于Directory,有种说法是Directory已经过时,所以更推荐使用Map。

线程安全

Hashtable也是线程安全的,和Vector一样,连size函数都丧心病狂地同步了。

public synchronized int size() {                                                                                                             
    return count;
}       

HashMap不是线程安全的,当然也可以用其他的黑科技实现同步,例如SynchronizedMap和ConcurrentHashMap。
那个以后再说……

空值

HashMap允许在Key和Value中都出现null值。例如:

v.toString()
{null=null, 1=66, 2=null, 3=4d, 4=0f, 5=null}

而Hashtable则不允许K/V的任何一个出现null值,但是允许空字符串。

Hashtable v=new Hashtable();
v.put("3s",null); 

直接报错:java.lang.NullPointerException。
这个和内部实现有关,Hashtable需要用到hashCode,所以必须要确保K/V不为null。

相关代码写死在源码里:

public synchronized V put(K key, V value) {
 // Make sure the value is not null
 if (value == null) {
     throw new NullPointerException();
 }
 //下面对hashCode做点事情。
Set

HashSet本质上是一个Collection,类似于List,是列表/集合,不是K-V的Map,但是它骨子里是一个HashMap……

这么说可能会更易于理解:
HashSet对外是“类”的集合,实际上是内部维护了一个HashMap进行实现。

实际上存储的是两个:hashCode和类本身(字符串/自定义类等)。

HashSet进行add的时候,会先进行验证hashCode:
(HashSet进行add操作实际上是对Map的put操作)

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);//本函数里有hashCode
    int i = indexFor(hash, table.length);
    for (Entry e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
一个对HashSet的自定义类的操作:
 class Name  
{  
    private String first;   
    private String last;   

    public Name(String first, String last)   
    {   
        this.first = first;   
        this.last = last;   
    }   

    public boolean equals(Object o)   
    {   
        if (this == o)   
        {   
            return true;   
        }   

    if (o.getClass() == Name.class)   
        {   
            Name n = (Name)o;   
            return n.first.equals(first)   
                && n.last.equals(last);   
        }   
        return false;   
    }   
}  

public class HashSetTest  
{  
    public static void main(String[] args)  
    {   
        Set s = new HashSet();  
        s.add(new Name("abc", "123"));  
        System.out.println(  
            s.contains(new Name("abc", "123")));  
    }  
}   

代码来自java中HashSet详解

  

粗看上去是会返回true的,实际返回false。因为 HashSet 判断两个对象相等的标准除了要求通过 equals() 方法比较返回 true 之外,还要求两个对象的 hashCode() 返回值相等。而上面程序没有重写 Name 类的 hashCode() 方法,两个 Name 对象的 hashCode() 返回值并不相同,因此 HashSet 会把它们当成 2 个对象处理,因此程序返回 false。如果想返回true,需要重写equals方法和hashCode方法。

  

至于hashCode,就是另一篇文章才解释得清的了。

集合操作

Set其实做集合操作更加简单。例如:

import java.util.*;
public class SetOperation {
       public static void main(String[] args) {
            Set result = new HashSet();
            Set set1 = new HashSet(){{
                add(1);
                add(3);
                add(5);
            }};

            Set set2 = new HashSet(){{
                add(1);
                add(2);
                add(3);
            }};

            result.clear();
            result.addAll(set1);
            result.retainAll(set2);
            System.out.println("交集:"+result);

            result.clear();
            result.addAll(set1);
            result.removeAll(set2);
            System.out.println("差集:"+result);

            result.clear();
            result.addAll(set1);
            result.addAll(set2);
            System.out.println("并集:"+result);

        }
}

输出:

交集:[1, 3]
差集:[5]
并集:[1, 2, 3, 5]
参考文档

hashSet中的equals方法和hashCode方法

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64382.html

相关文章

  • Java集合总结

    摘要:概述集合类主要有大分支,及。不能保证元素的排列顺序,顺序有可能发生变化不是同步的集合元素可以是但只能放入一个是接口的唯一实现类,可以确保集合元素处于排序状态。如果这两个的通过比较返回,新添加的将覆盖集合中原有的,但不会覆盖。 概述 Java集合类主要有2大分支,Collection及Map。Collection体系如下: https://upload-images.jianshu......

    toddmark 评论0 收藏0
  • 这几道Java集合框架面试题在面试中几乎必问

    摘要:若遇到哈希冲突,则将冲突的值加到链表中即可。之后相比于之前的版本,之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值默认为时,将链表转化为红黑树,以减少搜索时间。有序,唯一红黑树自平衡的排序二叉树。 本文是最最最常见Java面试题总结系列第三周的文章。主要内容: Arraylist 与 LinkedList 异同 ArrayList 与 Vector 区别 HashMap的底层...

    bigdevil_s 评论0 收藏0
  • 关于java集合框架总结

    摘要:是哈希表实现的,中的数据是无序的,可以放入,但只能放入一个,两者中的值都不能重复,就如数据库中唯一约束。四和的相同点和区别相同两者都是基于哈希表实现的内部也都是通过单链表解决冲突问题同样实现了序列化和克隆接口区别继承的父类不同。 一.Arraylist与LinkedList有什么区别? 1、ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率...

    Coding01 评论0 收藏0
  • 集合小记

    摘要:解決沖突开放定址法拉链法表解決沖突开放定址法再哈希法链地址法建立公共溢出区并发包中的线程安全的集合容器线程安全的,不允许为,默认个的数组,每个中实现就是了,通过定位。基于数组,线程安全的集合类,容量可以限制。 List   List 元素是有序的、可重复,实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。   ArrayList:动态数组...

    alaege 评论0 收藏0

发表评论

0条评论

microcosm1994

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<