Java集合(三)

news2024/9/20 18:58:26

目录

Java集合(三)

Java双列集合体系介绍

HashMap类

HashMap类介绍

HashMap类常用方法

HashMap类元素遍历

LinkedHashMap类

LinkedHashMap类介绍

LinkedHashMap类常用方法

LinkedHashMap类元素遍历

Map接口自定义类型去重的方式

Set接口和Map接口无索引操作原因分析

HashMap无序但LinkedHashMap有序原因分析

Map练习案例

案例1:统计字符串每一个字符出现的次数

案例2:斗地主案例HashMap版本

哈希表结构存储过程分析

哈希表结构源码分析

使用无参构造创建HashMap对象

第一次插入元素

使用有参构造创建HashMap对象

哈希值相同时比较源码

TreeSet类

TreeMap类

HashTable与Vector

Properties类

Properties类介绍

Properties类特有方法


Java集合(三)

Java双列集合体系介绍

在Java中,双列集合的顶级接口是Map接口,其下有下面的分类:

  1. HashMap
  2. LinkedHashMap
  3. TreeMap
  4. HashTable
  5. Properties

其体系及特点如下图所示:

HashMap

HashMap类介绍

HashMap类是Map接口的实现类,其特点如下:

  1. key唯一但value不唯一
  2. 插入顺序与存储顺序不一定相同
  3. 没有索引方式操作元素的方法
  4. 线程不安全
  5. 可以存null

对应的数据结构为哈希表

需要注意,如果出现 key重复会保留最后一个键值对的 value,即「发生 value覆盖」

HashMap类常用方法

  1. V put(K key, V value):向HashMap中插入元素,返回被参数value覆盖的value
  2. V remove(Object key):根据key值移除HashMap中指定的元素,返回被删除的键值对对应的value
  3. V get(Object key):根据key值获取对应的value
  4. boolean containsKey(Object key):判断HashMap中是否还有指定key元素
  5. Collection<V> values():获取HashMap中所有的value,将其值存储到单列集合中

基本使用如下:

public class Test01 {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        // 1. V put(K key, V value):向HashMap中插入元素,返回被参数value覆盖的value
        System.out.println(map.put("老大", "张三"));
        System.out.println(map.put("老二", "李四"));
        System.out.println(map.put("老三", "王五"));
        // 2. V remove(Object key):根据key值移除HashMap中指定的元素,返回被删除的键值对对应的value
        System.out.println(map.remove("老二"));
        // 3. V get(Object key):根据key值获取对应的value
        System.out.println(map.get("老大"));
        // 4. boolean containsKey(Object key):判断HashMap中是否还有指定key元素
        System.out.println(map.containsKey("老大"));
        // 5. Collection<V> values():获取HashMap中所有的value,将其值存储到单列集合中
        Collection<String> values = map.values();
        for (String value : values) {
            System.out.println(value);
        }
    }
}

HashMap类元素遍历

HashMap遍历方式有以下两种:

  1. 通过key值获取到对应的value,通过Set<K> keySet()方法将获取到的key存入到Set中,再使用Set集合的迭代器获取对应的value
  2. 通过Set<Map.Entry<K,V>> entrySet()获取到HashMap中的键值对存入到Set中,再通过Map的内部静态接口Map.Entry中的getKey方法和getValue方法分别获取到Map中对应的键值对

基本使用如下:

public class Test02 {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put("老大", "张三");
        map.put("老二", "李四");
        map.put("老三", "王五");

        // 根据key获取value遍历
        Set<String> strings = map.keySet();
        for (String string : strings) {
            System.out.println(string+"="+map.get(string));
        }
        System.out.println();
        // 使用entrySet遍历
        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            System.out.println(entry.getKey()+"="+entry.getValue());
        }

    }
}

LinkedHashMap

LinkedHashMap类介绍

  1. key唯一但value不唯一
  2. 插入顺序与存储顺序相同
  3. 没有索引方式操作元素的方法
  4. 线程不安全
  5. 可以存null

对应的数据结构为哈希表+双向链表

LinkedHashMap类常用方法

因为LinkedHashMap类继承自HashMap类,所以常用方法与HashMap基本一致

  1. V put(K key, V value):向HashMap中插入元素,返回被参数value覆盖的value
  2. V remove(Object key):根据key值移除HashMap中指定的元素,返回被删除的键值对对应的value
  3. V get(Object key):根据key值获取对应的value
  4. boolean containsKey(Object key):判断HashMap中是否还有指定key元素
  5. Collection<V> values():获取HashMap中所有的value,将其值存储到单列集合中

基本使用如下:

public class Test03 {
    public static void main(String[] args) {
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        // 1. V put(K key, V value):向HashMap中插入元素,返回被参数value覆盖的value
        System.out.println(map.put("老大", "张三"));
        System.out.println(map.put("老二", "李四"));
        System.out.println(map.put("老三", "王五"));
        // 2. V remove(Object key):根据key值移除HashMap中指定的元素,返回被删除的键值对对应的value
        System.out.println(map.remove("老二"));
        // 3. V get(Object key):根据key值获取对应的value
        System.out.println(map.get("老大"));
        // 4. boolean containsKey(Object key):判断HashMap中是否还有指定key元素
        System.out.println(map.containsKey("老大"));
        // 5. Collection<V> values():获取HashMap中所有的value,将其值存储到单列集合中
        Collection<String> values = map.values();
        for (String value : values) {
            System.out.println(value);
        }
    }
}

LinkedHashMap类元素遍历

遍历方式与HashMap类一致:

HashMap遍历方式有以下两种:

  1. 通过key值获取到对应的value,通过Set<K> keySet()方法将获取到的key存入到Set中,再使用Set集合的迭代器获取对应的value
  2. 通过Set<Map.Entry<K,V>> entrySet()获取到HashMap中的键值对存入到Set中,再通过Map的内部静态接口Map.Entry中的getKey方法和getValue方法分别获取到Map中对应的键值对
public class Test04 {
    public static void main(String[] args) {
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("老大", "张三");
        linkedHashMap.put("老二", "李四");
        linkedHashMap.put("老三", "王五");

        // 根据key获取value遍历
        Set<String> strings = linkedHashMap.keySet();
        for (String string : strings) {
            System.out.println(string+"="+linkedHashMap.get(string));
        }
        System.out.println();
        // 使用entrySet遍历
        Set<Map.Entry<String, String>> entries = linkedHashMap.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            System.out.println(entry.getKey()+"="+entry.getValue());
        }
    }
}

Map接口自定义类型去重的方式

以下面的自定义类为例:

public class Person {
    private int age;
    private String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

以下面的测试为例:

public class Test05 {
    public static void main(String[] args) {
        LinkedHashMap<Person, String> personHashMap = new LinkedHashMap<>();
        personHashMap.put(new Person(18, "张三"), "老大");
        personHashMap.put(new Person(19, "李四"), "老二");
        personHashMap.put(new Person(20, "王五"), "老三");
        personHashMap.put(new Person(20, "王五"), "老三");

        // 遍历
        Set<Map.Entry<Person, String>> entries = personHashMap.entrySet();
        for (Map.Entry<Person, String> entry : entries) {
            System.out.println(entry.getKey()+"="+entry.getValue());
        }

    }
}

前面在Set部分提到HashSet的去重方式:

  1. 先计算元素的哈希值(重写hashCode方法),再比较内容(重写equals方法)
  2. 先比较哈希值,如果哈希值不一样,存入集合中
  3. 如果哈希值一样,再比较内容
    1. 如果哈希值一样,内容不一样,直接存入集合
    2. 如果哈希值一样,内容也一样,去重复内容,留一个存入集合

Map接口中也是同样的方式去重,所以对于自定义类型来说,一样需要重写equalshashCode方法

如果Person类没有重写hashCodeequals方法,此时Person对象比较方式是按照地址比较,所以对于第三个元素和第四个元素来说是两个元素,此时输出结果就会是下面的情况:

Person{age=18, name='张三'}=老大
Person{age=19, name='李四'}=老二
Person{age=20, name='王五'}=老三
Person{age=20, name='王五'}=老三

而重写了hashCodeequals方法后,就可以避免上面的问题:

Person{age=18, name='张三'}=老大
Person{age=19, name='李四'}=老二
Person{age=20, name='王五'}=老三

Set接口和Map接口无索引操作原因分析

哈希表中虽然有数组,但是SetMap却没有索引,因为存数据的时候有可能在同一个索引下形成链表,如果1索引上有一条链表,根据Set和Map的遍历方式:「依次遍历每一条链表」,那么要是按照索引1获取,此时就会遇到多个元素,无法确切知道哪一个元素是需要的,所以就取消了按照索引操作的机制

HashMap无序但LinkedHashMap有序原因分析

HashMap底层的哈希表是数组+单向链表+红黑树,因为单向链表只有一个节点引用执行下一个节点,此时只能保证当前链表上的节点元素可能与插入顺序相同,但是如果使用双向链表就可以解决这个问题,过程如下:

Map练习案例

案例1:统计字符串每一个字符出现的次数

统计字符串:abcdsaasdhubsdiwb中每一个字符出现的次数

思路:

遍历字符串依次插入HashMap<String, Integer>(或者LinkedHashMap<String, Integer>)中,这个过程中会出现两种情况:

  1. 字符不存在与HashMap中,属于第一次插入,将计数器记为1
  2. 字符存在于HashMap中,代表非第一次插入,将计数器加1重新插入到HashMap

代码实例:

public class Test01 {
    public static void main(String[] args) {
        String str = "abcdsaasdhubsdiwb";
        HashMap<Character, Integer> counter = new HashMap<>();

        // 将字符串存入数组中,注意存入的是字符,而不是字符对应的ASCII码
        char[] chars = str.toCharArray();
        for (char c : chars) {
            // 遍历HashMap,如果不存在字符就插入,存在就将value值加1
            if (!counter.containsKey(c)) {
                counter.put(c, 1);
            } else {
                counter.put(c, counter.get(c) + 1);
            }
        }

        // 打印结果
        Set<Character> characters = counter.keySet();
        for (Character character : characters) {
            System.out.println(character + "=" + counter.get(character));
        }

    }
}

案例2:斗地主案例HashMap版本

思路:

使用HashMap存储每一张牌(包括值和样式),key存储牌的序号(从0开始),value存储牌面,使用前面同样的方式组合牌并存入HashMap中,存储过程中每存一张牌,key位置的数值加1。为了保证可以打乱牌,需要将牌面对应的序号存入一个单列容器,再调用shuffle方法。打乱后的牌通过序号从HashMap中取出,此时遍历HashMap通过key获取value即可

其中,有些一小部分可以适当修改,例如每一个玩家的牌面按照序号排序,查看玩家牌可以通过调用一个函数完成相应的行为等

此处当 key是有序数值,会出现插入顺序与存储数据相同

示例代码:

public class Test_Poker02 {
    public static void main(String[] args) {
        // 创建花色
        String[] color = "黑桃-红心-梅花-方块".split("-");
        // 创建号牌
        String[] number = "2-3-4-5-6-7-8-9-10-J-Q-K-A".split("-");

        HashMap<Integer, String> count_poker = new HashMap<Integer, String>();

        int key = 2;// 从2开始,保留两张牌给大王和小王
        // 组合牌
        for (String c : color) {
            for (String n : number) {
                // 插入到HashMap中
                count_poker.put(key++, c+n);
            }
        }

        count_poker.put(0, "大王");
        count_poker.put(1, "小王");
        // 创建一个ArrayList专门存牌号,便于打乱牌面
        ArrayList<Integer> count = new ArrayList<>();
        Set<Integer> integers = count_poker.keySet();
        for (Integer integer : integers) {
            count.add(integer);
        }

        // 打乱牌号,从而实现打乱牌面
        Collections.shuffle(count);

        // 创建玩家
        ArrayList<Integer> player1 = new ArrayList<>();
        ArrayList<Integer> player2 = new ArrayList<>();
        ArrayList<Integer> player3 = new ArrayList<>();
        // 创建底牌
        ArrayList<Integer> last = new ArrayList<>();

        // 发牌
        for (int i = 0; i < count.size(); i++) {
            if(i >= 51) {
                last.add(count.get(i));
            } else if(i % 3 == 0) {
                player1.add(count.get(i));
            } else if(i % 3 == 1) {
                player2.add(count.get(i));
            } else if(i % 3 == 2) {
                player3.add(count.get(i));
            }
        }

        // 对玩家的牌进行排序
        Collections.sort(player1);
        Collections.sort(player2);
        Collections.sort(player3);

        // 显示玩家牌
        show("玩家1", player1, count_poker);
        show("玩家2", player2, count_poker);
        show("玩家3", player3, count_poker);
        show("底牌", last, count_poker);
    }

    public static void show(String name, ArrayList<Integer> any, HashMap<Integer, String> poker) {
        System.out.print(name + ":" +"[ ");
        for (Integer i : any) {
            System.out.print(poker.get(i)+" ");
        }
        System.out.println("]");
    }
}

哈希表结构存储过程分析

HashMap底层的数据结构是哈希表,但是不同的JDK版本,实现哈希表的方式有所不同:

  • JDK7时的哈希表:数组+单链表
  • JDK8及之后的哈希表:数组+链表+红黑树

以JDK8及之后的版本为例,存储过程如下:

  • 先计算哈希值,此处哈希值会经过两部分计算:1. 对象内部的hashCode方法计算一次 2. HashMap底层再计算一次
  • 如果哈希值不一样或者哈希值一样但内容不一样(哈希冲突),直接存入HashMap
  • 如果哈希值一样且内容也一样,则发生value覆盖现象

在Java中,HashMap在实例化对象时,如果不指定大小,则默认会开辟一个长度为16的数组。但是该数组与ArrayList一样,只有在第一次插入数据时才会开辟容量

而哈希表扩容需要判断加载因子loadfactor,默认负载因子loadfactor大小为0.75,如果插入过程中加载因子loadfactor超过了0.75,就会发生扩容

存储数据的过程中,如果出现哈希值一样,内容不一样的情况,就会在数组同一个索引位置形成一个链表,依次链接新节点和旧节点。如果链表的长度超过了8个节点并且数组的长度大于等于64,此时链表就会转换为红黑树,同样,如果后续删除节点导致元素个数小于等于6,红黑树就会降为链表

哈希表结构源码分析

查看源码时可能会使用到的常量:

static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认加载因子
static final int TREEIFY_THRESHOLD = 8; // 转化为红黑树的节点个数
static final int MIN_TREEIFY_CAPACITY = 64; // 转化为红黑树时最小的数组长度
static final int UNTREEIFY_THRESHOLD = 6; // 红黑树退化为链表的节点个数
static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 最小容量

查看源码时会看到的底层节点结构:

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
    // ...
}

使用无参构造创建HashMap对象

测试代码:

HashMap<String, String> map = new HashMap<>();

对应源码:

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

第一次插入元素

测试代码:

HashMap<String, String> map = new HashMap<>();
map.put("1", "张三");

对应源码:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, 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;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    
    // ...

    ++modCount;
    if (++size > threshold)
        resize();
    // ...
    return null;
}

// 创建新节点
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    return new Node<>(hash, key, value, next);
}

// 扩容
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        // ... 
    }
    else if (oldThr > 0)
        // ...
    else {               
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // ...
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // ...
    return newTab;
}
threshold代表扩容边界,是 HashMap中的成员变量,由容量和负载因子相乘计算得到

在上面的代码中,插入数据调用put方法,底层会调用putVal方法,进入putVal后,首先会判断当前表是否为空,而此时因为是第一次插入元素,元素还没有进入表中,当前表为空,所以会走第一个if语句,进入内部执行n = (tab = resize()).length;会执行resize方法为当前的tab扩容

进入resize方法中,当前的成员table即为HashMap底层的哈希表,因为不存在元素,所以为空,从而oldTab也为空,执行后面的代码后oldCapoldThr均为0,直接进入else语句中,将newCap置为16,同时将newThr赋值为12,执行完毕后,将成员threshold更新为newThr的值,将新容量16作为数组长度创建一个新的数组newTab,将newTab给成员变量table返回给调用处继续执行

此处需要注意,对于 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];来说,因为Java不支持创建泛型数组,所以先创建原始类型数组再通过强制转换将其转换为泛型数组

回到调用处n = (tab = resize()).length;,此时tab即为成员变量table的值,获取其长度即为16。接着执行第二个if语句,此处的i = (n - 1) & hash用于计算哈希表的映射位置,即数组索引,进入if语句,创建节点并插入到指定索引位置,改变size并比较是否超过threshold,此处未超过不用扩容,改变并发修改控制因子,返回被覆盖的null

使用有参构造创建HashMap对象

测试代码:

HashMap<String, String> map1 = new HashMap<>(5)

对应源码:

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    // ... 
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

当执行有一个参数的构造时,底层调用内部的有两个参数的构造,第一个参数即为初始容量大小,第二个参数传递默认的加载因子

在有两个参数的构造中,首先判断初始容量initialCapacity是否小于0,如果小于则抛出异常,再判断初始容量initialCapacity是否大于最大值,如果大于则修正为最大值。

在执行完所有判断后,将加载因子赋值给成员变量loadfactor,再根据初始容量计算扩容前最大的容量

哈希值相同时比较源码

Node<K,V> e; K k;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;

首先比较哈希值,如果哈希值相同,则比较key对应的value,为了防止出现key为空导致的空指针问题,先判断key不为空,再比较key

上面过程即「先比较哈希值,相同再比较内容」

TreeSet

TreeSet类是Set接口的实现类,其有如下的特点:

  1. 默认会对插入的数据进行排序
  2. 没有索引的方式操作元素
  3. 不可以存null
  4. 相同元素不重复出现
  5. 线程不安全

底层数据结构为红黑树

常用构造方法:

  1. 无参构造方法:TreeSet(),默认按照ASCII码对元素进行比较
  2. 使用传递比较器作为参数的有参构造方法:TreeSet(Comparator<? super E> comparator)

基本使用如下:

// 自定义类
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

// 测试
public class Test02 {
    public static void main(String[] args) {
        TreeSet<String> set = new TreeSet<>();
        set.add("1");
        set.add("112");
        set.add("13");

        // 使用增强for遍历
        for (String s : set) {
            System.out.println(s);
        }

        System.out.println();

        TreeSet<Person> people = new TreeSet<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        people.add(new Person("张三", 23));
        people.add(new Person("李四", 24));
        people.add(new Person("王五", 25));

        // 使用增强for遍历
        for (Person person : people) {
            System.out.println(person);
        }
    }
}

TreeMap

TreeMap类是Map接口的实现类,其特点如下:

  1. 默认会对插入的数据进行排序
  2. 没有索引的方式操作元素
  3. key唯一,value不唯一
  4. 不可以存null
  5. 相同元素不重复出现
  6. 线程不安全

底层数据结构为红黑树

常用构造方法:

  1. 无参构造方法:TreeMap(),默认按照ASCII码对元素进行比较
  2. 使用传递比较器作为参数的有参构造方法:TreeMap(Comparator<? super E> comparator)
// 自定义类
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

// 测试
public class Test02 {
    public static void main(String[] args) {
        TreeSet<String> set = new TreeSet<>();
        set.add("1");
        set.add("112");
        set.add("13");

        // 使用增强for遍历
        for (String s : set) {
            System.out.println(s);
        }

        System.out.println();

        TreeSet<Person> people = new TreeSet<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        people.add(new Person("张三", 23));
        people.add(new Person("李四", 24));
        people.add(new Person("王五", 25));

        // 使用增强for遍历
        for (Person person : people) {
            System.out.println(person);
        }
    }
}

HashTableVector

HashTable类是Map接口的实现类,其特点如下:

  1. key唯一,value可重复
  2. 插入顺序与存储顺序不一定相同
  3. 没有索引的方式操作元素
  4. 线程安全
  5. 不能存储null

底层数据结构:哈希表

Vector类是Collection接口的实现类,其特点如下:

  1. 元素插入顺序与存储顺序相同
  2. 有索引的方式操作元素
  3. 元素可以重复
  4. 线程安全

底层数据结构:数组

因为HashTableVector现在已经不经常使用了,所以使用及特点自行了解即可

Properties

Properties类介绍

Properties类是HashTable类的子类,其特点如下:

  1. key唯一,value可重复
  2. 插入顺序与存储顺序不一定相同
  3. 没有索引的方式操作元素
  4. 线程安全
  5. 不能存储null
  6. Properties类不是泛型类,默认元素是String类型

底层数据结构:哈希表

Properties类特有方法

常用方法与HashMap等类似,此处主要考虑特有方法:

  1. Object setProperty(String key, String value):存键值对
  2. String getProperty(String key):根据key获取对应的value
  3. Set<String> stringPropertyNames():将所有key对应的value存储到Set中,类似于HashMap中的KeySet方法
  4. void load(InputStream inStream):将流中的数据加载到Properties类中(具体见IO流部分)

基本使用如下:

public class Test08 {
    public static void main(String[] args) {
        Properties properties = new Properties();
        //Object setProperty(String key, String value)
        properties.setProperty("username","root");
        properties.setProperty("password","1234");
        System.out.println(properties);
        //String getProperty(String key)
        System.out.println(properties.getProperty("username"));
        //Set<String> stringPropertyNames()
        Set<String> set = properties.stringPropertyNames();
        for (String key : set) {
            System.out.println(properties.getProperty(key));
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2149752.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

grafana 使用常见问题

一、点击 panel 没有反应&#xff0c;没有出现 edit 选项。 方法一 将鼠标放在 panel 的任意位置&#xff0c;然后键盘输入 "e"&#xff0c;然后再次点击 title&#xff0c;即可出现选项框。 方法二 F12 查看当前 panel id&#xff0c;然后在浏览器 url 地址上拼接…

探索AI大模型的未来:电信运营商与云服务商的新征途@附58页PDF文件下载

在数字化浪潮的推动下&#xff0c;人工智能&#xff08;AI&#xff09;大模型正成为推动行业变革的关键力量。近日&#xff0c;腾讯云联合中国信通院及中国通信标准化协会发布了《2024年AI大模型应用发展研究报告》&#xff0c;深入探讨了电信运营商与云服务商在AI大模型领域的…

Unicode与UTF-8的关系

Unicode又称统一码&#xff0c;万国码。uni是一个英文词根&#xff0c;原型是one, 表示“单一, 一个”&#xff0c;所以unicode本意是“一个码”&#xff0c;就是让每个字符都有一个唯一的编码。它就像个武林盟主&#xff0c;把世上所有的语言符号一勺烩&#xff0c;一统了编码…

反相求和电路设计

1 简介 该电路可对两个输入信号进行求和&#xff08;相加&#xff09;&#xff0c;并将其在输出端反相。输入信号通常要求低阻抗源&#xff0c;因为该电路的输入阻抗由输入电阻R1和R2决定。反相放大器的共模电压等于连接到同相节点的电压。 2 设计目标 2.1 输入 2.2 输出 2.3…

9.4 溪降技术:带包下降

目录 9.4 携包下降概述观看视频课程电子书&#xff1a;携包下降在瀑布中管理背包扔背包滑索传送背包固定到安全带 V7 提示&#xff1a;将背包固定到安全带总结 9.4 携包下降 概述 在水流和悬崖边缘携包下降是最危险的情况&#xff01; 正如我们之前所学&#xff0c;在峡谷探险中…

Tomcat 后台弱⼝令部署war包

漏洞原理 在tomcat8环境下默认进⼊后台的密码为 tomcat/tomcat &#xff0c;未修改造成未授权即可进⼊后台&#xff0c;或者管理员把密码设置成弱⼝令。 影响版本 全版本(前提是⼈家存在弱⼝令) 环境搭建 8 cd vulhub-master/tomcat/tomcat8 docker-compose up -d 漏洞复…

AD9854 为什么输出波形幅度受限??

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&am…

【CSS in Depth 2 精译_035】5.5 Grid 网格布局中的子网格布局(全新内容)

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…

C++速通LeetCode中等第6题-找到字符串中所有字母异位词(滑动窗口最详细代码注释)

滑动窗口法&#xff1a; class Solution { public:vector<int> findAnagrams(string s, string p) {unordered_map<char,int> need,window;for(char c : p) need[c];int left 0,right 0;int valid 0;vector<int> res;//窗口数据更新while(right < s.s…

FDM3D打印系列——黑悟空打印

大家好&#xff0c;我是阿赵。   最近很火的黑神话悟空&#xff0c;阿赵我没有玩&#xff0c;原因是没时间。不过看着身边的同事都在玩&#xff0c;我也心痒难耐&#xff0c;所以我打印了一个悟空的模型&#xff0c;并且自己配了色。 打印这个悟空模型&#xff0c;过程是一波…

Telephony VOWIFI

1、VOWIFI框架 参考3GPP 23402文档, VOWIFI有如下相关架构设置。 1、S2a信任的WIFI热点 2、S2b非信任WIF热点 3、S2c直联核心WIF热点 目前使用比较多的为S2b非信任WIF热点。 2、EPDG建立过程 //Telephony Log IWLAN拨号 08-30 21:36:34.702857 1347 5131 D ConnectivityS…

【后端开发】JavaEE初阶——计算机是如何工作的???

前言&#xff1a; &#x1f31f;&#x1f31f;本期讲解计算机工作原理&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;【MySQL】MySQL中JDBC编程——MySQL驱动包安装——&#xff08;超详解&#xff09; &#x1f308;感兴趣的小伙伴看一看小编主…

深入理解ConcurrentHashMap

HashMap为什么线程不安全 put的不安全 由于多线程对HashMap进行put操作&#xff0c;调用了HashMap的putVal()&#xff0c;具体原因&#xff1a; 1、假设两个线程A、B都在进行put操作&#xff0c;并且hash函数计算出的插入下标是相同的&#xff1b; 当线程A执行完第六行由于时间…

基于milvus数据库的RAG-Demo

1.上传文本并将文本向量化 import os from django.conf import settings from langchain.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter, CharacterTextSplitter from langchain.vectorstores import Chroma from l…

数据结构-2.7.单链表的查找与长度计算

注&#xff1a;本文只探讨"带头结点"的情况(查找思路类似循环找到第i-1 个结点的代码) 一.按位查找&#xff1a; 1.代码演示&#xff1a; 版本一&#xff1a; #include<stdio.h> #include<stdlib.h> ​ ​ //定义单链表结点类型 typedef struct LNo…

契约锁与您相约2024新疆数字经济创新大会暨新疆数字丝路博览会

9月20日&#xff0c;由新疆数字经济联合会主办&#xff0c;多家行业协会及企业共同承办的“2024(第一届)新疆数字经济创新发展大会暨新疆数字丝路博览会”在新疆国际会展中心盛大开幕&#xff0c;活动期间&#xff0c;契约锁作为电子签章行业领先的服务商携数字可信系列产品亮相…

自然语言处理-基于注意力机制的文本匹配

背景&#xff1a; 任务三&#xff1a;基于注意力机制的文本匹配 输入两个句子判断&#xff0c;判断它们之间的关系。参考ESIM&#xff08;可以只用LSTM&#xff0c;忽略Tree-LSTM&#xff09;&#xff0c;用双向的注意力机制实现。 参考 《神经网络与深度学习》 第7章 Reaso…

MT6765/MT6762(R/D/M)/MT6761(MT8766)安卓核心板参数比较_MTK联发科4G智能模块

联发科Helio P35 MT6765安卓核心板 MediaTek Helio P35 MT6765是智能手机的主流ARM SoC&#xff0c;于2018年末推出。它在两个集群中集成了8个ARM Cortex-A53内核&#xff08;big.LITTLE&#xff09;。四个性能内核的频率高达2.3GHz。集成显卡为PowerVR GE8320&#xff0c;频率…

统一网关--gateway(仅供自己参考)

1、网关的概念&#xff1a; 2、网关的功能&#xff1a; &#xff08;1&#xff09;&#xff1a;身份认证和权限校验 &#xff08;2&#xff09;&#xff1a;服务路由&#xff08;具体的业务路由到具体的服务&#xff09;&#xff0c;负载均衡&#xff08;多台服务的话&#xff…

【用Java学习数据结构系列】对象的比较(Priority Queue实现的前提)

看到这句话的时候证明&#xff1a;此刻你我都在努力 加油陌生人 个人主页&#xff1a;Gu Gu Study 专栏&#xff1a;用Java学习数据结构系列 喜欢的一句话&#xff1a; 常常会回顾努力的自己&#xff0c;所以要为自己的努力留下足迹 喜欢的话可以点个赞谢谢了。 作者&#xff…