Set集合与Map体系

目录

Set集合

7.1 Set子接口

7.2 HashSet[重点]

7.3 LinkedHashSet

7.4 TreeSet

八、Map体系集合

8.1 Map接口

8.2 HashMap[重点]

8.3 Hashtable[不推荐]

8.4 Properties

8.5 TreeMap


Set集合

7.1 Set子接口

Set子接口特点:无序、无下标、元素不可重复。

循环方式:forEach循环或者迭代(因为没有下标所以不能使用for循环)

经典面试题:给定一个集合,如何快速将集合中所有重复的元素去掉?

  • 将集合元素直接放入Set中,通过Set特性可以去重。

7.2 HashSet[重点]

基于HashCode实现元素不可重复,当存入对象的HashCode值相同时,在通过equals方法判断,如果判定重复则不可存入

HashSet是无序的。

有序、无序、排序是什么意思?

有序:指存入元素时记得元素添加的顺序;

无序:指存入元素时不保留添加元素时的顺序;

排序:指添加后会按照给定的顺序(升序或降序)进行排列。

7.2.1 用法

HashSet去重时,通过HashCode和equals方法进行去重。(所以我们在使用HashSet时需要重写HashCode和equals方法以便得到我们想要的去重效果。)

HashSet 类位于 java.util 包中,使用前需要引入它,语法格式如下:

import java.util.HashSet;
HashSet<String> hashSet = new HashSet<String>();
​
hashSet.add("Hello");
hashSet.add("World");
hashSet.add("Hello");
hashSet.add("Chinese");
//我们添加了两次Hello,所以被判断为重复的元素,直接去重,看输出结果可知是无序的(不根据我们输入顺序来输出)
System.out.println(hashSet);//[Hello, Chinese, World
​
HashSet<String> hashSet1 = new HashSet<String>(hashSet);
System.out.println(hashSet1.equals(hashSet));//true
​
//HashSet的remove方法只能传入元素值进行删除,因为HashSet无序,返回值为Boolean类型
boolean remove = hashSet.remove("Hello");
System.out.println(hashSet);//[Chinese, World]
//元素个数
System.out.println(hashSet.size());//2
​
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("1");
arrayList.add("2");
arrayList.add("3");
arrayList.add("1");
System.out.println(arrayList);//[1, 2, 3, 1]
//去重ArrayList
HashSet<String> l = new HashSet<String>(arrayList);
System.out.println(l);//[1, 2, 3]

7.2.2 原理

HashSet底层实现依靠HashMap。

通过HashMap实现,先了解HashMap源码。、

HashSet去重其实是使用了HashMap中key值不能重复的原理实现的。

//定义了一个HashMap
private transient HashMap<E,Object> map;
//new了一个最小的对象
private static final Object PRESENT = new Object();
​
//HashSet无参构造方法,调用了HashMap的无参构造
public HashSet() {
    map = new HashMap<>();
}
//HashSet有参构造指定容量,调用HashMap的有参构造指定容量
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}
//添加数据就是调用了map的put方法添加,key为添加的值无法重复,value为定义的最小对象。
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
//调用HashMap的remove方法
public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
}

7.3 LinkedHashSet

链表实现的HashSet,按照链表进行存储,即可保留元素插入顺序。

用法与HashSet一致。

原理:

  • LinkedHashSet继承了HaashSet;

  • 在HashSet基础上添加了链表结构,记住了添加顺序,所以是有序的。底层中使用LinkedHashMap实现。

7.4 TreeSet

会将添加的元素进行排序,使用Comparable比较,比较的元素需要实现Comparable接口,通过比较的方式来去重。

采用树型结构来存放元素。

遍历时采用中序遍历。先左->根->最后右

遍历方式还有左序、中序、右序遍历。[可自行了解]

八、Map体系集合

8.1 Map接口

映射。使用key-value(键值对)来存储数据。

key不可重复,value可重复。当key重复时,会覆盖之前的值

常见方法:

v put(K key,V value):存入数据value,使用key作为名称;

get(K key):根据名称key查找value值;

keySet():得到所有的key名称的集合;

values():得到所有的value值的集合;

entrySet():得到所有键值对的集合。

8.2 HashMap[重点]

JDK 1.2添加,线程不安全,性能性对较好。

注意:允许null作为key和value。

jdk1.7使用数组+链表结构,既有数组优点(遍历块),又有链表优点(增删快)。

jdk1.8使用数组+链表+红黑树的结构。当链表到达一定长度就会转为红黑树提高效率。

8.2.1 常用方法

get()、size()、entrySet()

[注意]当添加对象相同时,希望其覆盖,需要重写equals和HashCode方法。

public class Demo {
​
    public static void main(String[] args) {
​
        HashMap<String,String> hashMap1 = new HashMap<String, String>();
        HashMap<Student,String> hashMap2 = new HashMap<Student, String>();
​
        //添加数据
        hashMap1.put("1","张三");
        hashMap1.put("2","李四");
        hashMap1.put("3","王五");
        hashMap1.put("1","赵六");
        //因为key值相同,所以赵六覆盖张三
        System.out.println(hashMap1);//{1=赵六, 2=李四, 3=王五}
​
        //删除
        hashMap1.remove("2");
        System.out.println(hashMap1);//{1=赵六, 3=王五}
​
        //修改,只需要添加相同的键修改不同的value即可完成修改
        hashMap1.put("1","李七");
        System.out.println(hashMap1);//{1=李七, 3=王五}
​
        //查询,根据key获取value值
        System.out.println(hashMap1.get("1"));//赵六
        System.out.println(hashMap1.get("2"));//李四
​
        //获取所有键的集合
        System.out.println(hashMap1.keySet());//[1, 3]
        //获取所有值的集合
        System.out.println(hashMap1.values());//[李七, 王五]
        //获取所有的键和值的集合
        System.out.println(hashMap1.entrySet());//[1=李七, 3=王五]
​
        //当对象为key值时,我们需要重写equals和HashCode方法达到覆盖目的,否则就不会覆盖
        hashMap2.put(new Student(1,"张三"),"1");
        hashMap2.put(new Student(2,"李四"),"2");
        hashMap2.put(new Student(3,"王五"),"3");
        hashMap2.put(new Student(1,"张三"),"3");
        for (Student s:hashMap2.keySet() ) {
            System.out.println("key值为:" + s.toString() + "value值为:" + hashMap2.get(s));
        }
        //key值为:Student{id=1, name='张三'}value值为:3
        //key值为:Student{id=2, name='李四'}value值为:2
        //key值为:Student{id=3, name='王五'}value值为:3
​
    }
​
}

8.2.2 原理

基本原理:

  • HashMap的每个元素都会封装为单向链表的node节点。整个map都是一个链表型数组,称为table(hash表);

  • 当无参构造创建HashMap时,会初始化loadFactor属性为默认的扩容因子0.75;

  • 当有参构造指定容量创建HashMap时,会初始化loadFactor属性为默认的扩容因子0.75,且会计算得到一个>=指定容量最小2的n次方数的临界点(判断初始容量是否超出所规定的最大值,超出则将设置的最大值定为初始容量,然后根据初始容量计算出>=初始容量的最小 2 的n次方数并赋值给扩容临界点)。

  • 扩容因子为0.75,即元素达到数组长度的3/4时,再添加新元素就会触发扩容,选择0.75的原因是在0.5到1之间,0.75是最合适能够乘以2的n次方得到整数的小数;

  • 无参构造第一次添加元素会初始化扩容数组大小为16,再添加元素;

  • 根据key的hashCode值低16位与高16位进行异或运算,再与数组长度求余数,得到元素应该存放在数组的索引位置;

  • 当某个链表上的元素>=8个,且数组长度>=64时才会变树,如果<64则用扩容代替树化;

  • 当数组扩容临界点>=设置的最大容量(2的30次方)时,直接指定临界点为int的最大值,不再进行扩容;

  • JDK1.7时采用头插法添加数据,JDK1.8开始采用尾插法添加数据;

  • 每次扩容为原来数组的2倍,会将原来map中的内容放到新的map中,并重新排列;

   //部分源码理解
    
    //默认数组容量
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    //最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //默认扩容因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //链表转为红黑树的阈值(临界点)
    static final int TREEIFY_THRESHOLD = 8;
    //红黑树转为链表的阈值(为什么两个阈值不一样,是为了防止在一个临界点一直转换)
    static final int UNTREEIFY_THRESHOLD = 6;
    //最小树形容量(当链表达到阈值后还需要满足最小树形容量才会转为红黑树)
    static final int MIN_TREEIFY_CAPACITY = 64;
    //table表示的是hashmap中的数组,数组中存储的是Node节点
    transient Node<K,V>[] table;
    //键值对个数
    transient int size;
    //修改次数
    transient int modCount;
    //扩容的临界点(数组容量tab*扩容因子loadFactor计算而来)
    int threshold;
    //数组扩容的因子
    final float loadFactor;
    
    //定义的静态内部类,数组中每个位置存储的都是Node节点,节点中存储了相关属性(jdk1.7使用是Entry内部类)
    //这里Node实现了Entry接口
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
​
        //Node节点中存储了(key的hash值,key,value,当前节点的下一节点地址)
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
​
        //此处省略部分源码,这里是实现Entry接口中的所有方法
    }
    
    //hashmap的无参构造方法
    public HashMap() {
        //将默认的扩容因子赋值给loadFactor
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    //hashMap有参构造方法,传入参数为指定的初始容量
    public HashMap(int initialCapacity) {
        //调用另一个有参构造(传入定义的初始容量和默认的扩容因子0.75)
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    
    //当指定容量创建hashMap会调用此方法或者有参构造指定初始容量和扩容因子会调用
    public HashMap(int initialCapacity, float loadFactor) {
        //判断设置的容量是否不规范(小于),不规范就报错(非法初始容量 Illegal initial capacity)
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        //判断设置的容量是否超出规定的最大容量(2的30次方)
        if (initialCapacity > MAXIMUM_CAPACITY)
            //超出的话就将初始容量规定为最大容量
            initialCapacity = MAXIMUM_CAPACITY;
        //判断扩容因子是否规范(<=0或不是数字),不规范就报错
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        //将扩容因子赋值给对象属性,因为这里的loadFactor为形参
        this.loadFactor = loadFactor;
        //根据初始容量再进行计算,然后赋值给扩容临界点(>=给定容量的最小 2 次方数)
        this.threshold = tableSizeFor(initialCapacity);
    }    
    //该方法用于返回>=给定目标容量的最小 2 次方数。
    static final int tableSizeFor(int cap) {
        int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
        //判断n是否为负数(当超出int最大值时成立)
        //否则再判断是否超出我们设置的最大值(2的30次方),超出则返回最大值,没超出则返回n+1
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }    
    
    //根据key值计算key的hash值方法
    static final int hash(Object key) {
        int h;
        //判断key是否为null,是null则hashCode值为0
        //不是null则计算key的hashCode值然后与key的hashCode值的高16位进行异或运算,得到一个计算后的值进行返回
        //这里跟高16位进行异或运算可以减少hashCode值的冲突(1.7版本高16位并没有进行运算)
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    //添加键值对
    public V put(K key, V value) {
        //通过计算key的hash值作为参数,然后返回putVal方法的值
        return putVal(hash(key), key, value, false, true);
    }
    //传入参数为(key的hash值,key,value,put时设置为false,put时设置为true)
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        //声明临时变量
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //数组赋值给临时变量,并判断数组是否为空(无元素时)或数组长度==0
        if ((tab = table) == null || (n = tab.length) == 0)
            //对数组进行扩容,然后将扩容后的长度赋值给n
            n = (tab = resize()).length;
        //求出key的hash值在数组中存储的索引位置,并判断该位置是否为null(是否有元素),并将该索引位置的节点赋值给p
        //这里的(n-1)&hash相当于(数组长度-1)%16得到余数,余数范围0-15
        //这里印证了为什么数组长度必须是2的次方数,因为(2的次方数-1)&hash运算只能得到0-(数组长度-1)的值
        if ((p = tab[i = (n - 1) & hash]) == null)
            //该索引位置没有节点时,直接将添加的key,value设置为该索引位置第一个节点
            tab[i] = newNode(hash, key, value, null);
        //如果数组不是空且里面有元素节点时执行
        else {
            Node<K,V> e; K k;
            //判断该索引位置的节点是否与添加key的hash值相等,且两个节点是否指向同一个内存地址并将key值赋值给k
            //或者添加的key是否为null,且两个key值是否相等
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //若相等就表示key重复,将存在的节点赋值给e
                e = p;
            //判断该索引位置存在的节点是否为红黑树
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //循环遍历该索引位置所有节点
                for (int binCount = 0; ; ++binCount) {
                    //判断遍历的当前节点是否为尾节点(尾结点的下一个节点为null),当为null就是尾结点
                    if ((e = p.next) == null) {
                        //若是尾结点就让尾结点的next(指向下一节点地址)属性设置为添加的节点
                        p.next = newNode(hash, key, value, null);
                        //添加后判断当前索引位置的节点是否达到了变红黑树的阈值8
                        //因为节点是从0开始遍历的当遍历到7是就表示已经有了8个节点
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //超出变树阈值8数组当前索引位置要变树
                            treeifyBin(tab, hash);
                        //添加成功后就跳出循环
                        break;
                    }
                    //若不是尾结点就判断当前遍历节点是否与添加的节点相等
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        //相等就跳出循环,表示key重复
                        break;
                    //切换到下一个节点
                    p = e;
                }
            }
            //判断e是否为null(当e不为空时表示该索引位置中有重复的key)
            if (e != null) { // existing mapping for key
                //得到覆盖前的value值
                V oldValue = e.value;
                //当put进入的这个方法时,这里!false为true
                if (!onlyIfAbsent || oldValue == null)
                    //添加的value覆盖之前的value
                    e.value = value;
                //put时该方法为空
                afterNodeAccess(e);
                //后面代码不执行,返回值为覆盖前的value值
                return oldValue;
            }
        }
        //修改次数+1
        ++modCount;
        //元素节点个数+1,判断是否到达数组扩容阈值
        if (++size > threshold)
            //扩容
            resize();
        put时该方法为空
        afterNodeInsertion(evict);
        //返回null
        return null;
    }
    //扩容数组
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //判断数组是否为null
        if (oldCap > 0) {
            //判断扩容前数组容量是否达到了容量最大值
            if (oldCap >= MAXIMUM_CAPACITY) {
                //达到则将int最大值设置为扩容临界点,然后返回扩容前数组
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //如果扩容前数组容量未达到容量最大值,然后将扩容前数组*2作为扩容后的数组容量
            //扩容后的容量必须<最大容量,且扩容前容量>=默认数组容量16
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                //扩容后的临界点为扩容前临界点*2
                newThr = oldThr << 1; // double threshold
        }
        //判断是否有扩容临界点值是否初始化(有参构造会进入此条件,有参构造初始化了扩容临界点)
        else if (oldThr > 0) // initial capacity was placed in threshold
            //初始化容量为扩容临界点
            newCap = oldThr;
        //当无参构造创建hashMap时走这条分支
        else {               // zero initial threshold signifies using defaults
            //初始化容量为默认的数组容量16
            newCap = DEFAULT_INITIAL_CAPACITY;
            //初始化扩容临界点为默认的扩容因子0.75*默认数组容量16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //判断扩容临界点是否为0(当走外层elseif分支时,并没有赋值newThr)
        if (newThr == 0) {
            //计算扩容临界点为数组容量*扩容因子
            float ft = (float)newCap * loadFactor;
            //判断容量和扩容临界点是否超出设置的最大值(2的30次方)
            //没有超出则将临界点设置为新计算的扩容临界点,超出则将扩容临界点设置为int的最大值
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        //对象的扩容临界点属性初始化
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        //根据前面计算出来的数组容量来new一个新的数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        //扩容后的数组赋值给table属性
        table = newTab;
        if (oldTab != null) {
        
            /**
                此处代码省略,当扩容前数组不是空时执行此处代码
                此处代码遍历数组中的所有节点,先判断索引位置是否为null,为null则表示没有元素直接遍历下一个索引;
                若有元素则再判断该元素后面是否存在下一个元素,当没有下一个元素就表示该索引位置只有一个节点;根据节点的hash值与扩容后数组容量-1进行&运算得到新的索引值,将这一个节点移动到新数组的该索引位置。
                若该索引位置不止一个节点时,再判断该索引位置是否为红黑树结构,红黑树移动过程如下:
                    1.红黑树移动到新数组的索引位置也最多有两种情况,跟链表一致分高位和低位;
                    2.遍历树的所有节点,分为高位红黑树和低位红黑树,并给高位和低位进行计数;
                    3.遍历结束后判断两个树中每个树的节点数量,当<=6,就将红黑树变为链表。红黑树变为链表只需要将树节点重新new成节点即可。
                
                
                若有多个节点且也不是红黑树结构,则该节点就是链表,将链表中所有节点移动到新数组中,移动过程如下:
                    1.老数组的单向链表结构移动到新数组中,索引位置可能会有两种变化,一种是索引位置不变,一种是索引位置+老数组长度,如下:
                    例如:当老数组长度为16,且单向链表在5索引位置上,那么当他移动到新数组32中,会将节点的hash值&(新数组容量-1),其计算的索引位置只与后5位二进制有关:
                    假如索引位置为5的hash值有53(110101)和21(10101),这两个数&(16-1)都为5,所以在长度为16的数组中索引位置为5,当此时扩容后需要移动到新数组容量为32中,那么53和21&(32-1)的结果有两种,一个是索引值为21,一个是索引值为5,其中我们将索引值为21的称为高位索引,索引值为5称为低位索引。
                    2.遍历一个索引位置的所有节点,利用hash值&(新数组容量-1)计算新的索引值,根据计算的新索引值最多可将一个链表分为两个链表(高位链表和低位链表),两个链表中都有头结点和尾结点,再根据尾结点判断链表是否为null,若是null则不用放到新数组中,若不是空则将尾结点的next属性置为null,然后将链表存入新数组对应的索引位置中。
                    
                
            */
​
        }
        //返回扩容后的数组
        return newTab;
    }
    
    //删除
    public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        //判断数组是否为null,数组长度是否大于0,并根据key的hash值来计算索引值获取第一个节点,判断该所索引值是否存在第一个节点
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            //判断删除的节点是否为该索引位置第一个节点
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                //判断该索引位置是否是树结构
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                //索引位置是链表结构
                else {
                    //循环遍历判断是否与删除key相等
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        //p中存放当前遍历节点的上一节点,因为当p的下一节点为要删除的key时,并不会执行到这一步,在上一步就break,而node中存放的是要删除的节点
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            //若链表或树中找到与删除key相同的节点
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                //判断是否为树节点
                if (node instanceof TreeNode)
                    //删除树节点后还会判断该索引位置的节点数是否<=6,若小于则变链表
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                //当删除的节点是索引位置第一个节点时,将删除节点的下一节点设置为第一个节点
                else if (node == p)
                    tab[index] = node.next;
                else
                    //当删除节点不是第一个节点时,将删除节点的下一节点赋值p的next属性
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                //返回值为要删除的节点
                return node;
            }
        }
        return null;
    }    
    
    //查询
    public V get(Object key) {
        Node<K,V> e;
        //根据查询key的hash值和key来获取数组中的节点,并判断数组中是否存在查询key,存在则返回该节点的value,不存在就返回null
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //判断数组是否为null,数组长度是否大于0,并根据key的hash值来计算索引值获取第一个节点,判断该所索引值是否存在第一个节点
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            //判断该索引的第一个节点的hash和查询key的hash是否一致,是否和查询key指向同一个内存地址,是否等于查询的key值
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                //若都符合说明key相同
                return first;
            //判断该索引位置是否只有一个节点
            if ((e = first.next) != null) {
                //判断该索引位置保存的是否为树
                if (first instanceof TreeNode)
                    //如果为树,就在树中查询
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                //如果是链表就循环遍历比较是否相同
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        //相同则返回该节点
                        return e;
                } while ((e = e.next) != null);
            }
        }
        //返回空
        return null;
    }    

8.3 Hashtable[不推荐]

Hashtable是在jdk1.0中,线程安全,性能不高。

用法与HashMap相同;

若需要使用安全的map推荐使用ConcurrentHashMap。

经典面试题:HashMap与Hashtable的区别?

  • Hashtable是jdk1.0中,线程安全,性能不高;而HashMap是jdk1.2中添加,线程不安全,但性能相对较高;

  • Hashtable中key和value都不能为null(空指针异常),HashMap中key和value都能为null;

8.4 Properties

特点:

  • 继承自Hashtable

  • 主要用来加载配置文件,一般由服务器执行,所以可以不用考虑多线程性能问题;

  • 主要对key和value进行限定了类型为String;

  • 添加了直接加载文件流的方法;

8.5 TreeMap

通过key进行排序,需要实现Comparable接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值