Java集合初体验(谨个人学习记录)

概述

Java 集合可分为 Collection 和 Map 两种体系

  • Collection 接口 :单列数据 。定义了存取一组对象的方法的集合
    • List :有序列表 ,可重复的集合
    • Set :无序列表 ,不可重复的集合
  • Map 接口 :双列数据 。保存具有映射关系 “key” :“value” 键值对 的集合

Collection 接口继承树

image-20201203102347808

Map 接口继承树

{name:"张三"} >> 键值对

image-20201203102458325

Collection 接口方法

接口方法 作用
add(Object obj)
addAll(Collection coll)
添加元素
int size() 获取有效元素的个数
void clear() 情况集合
boolean isEmpty() 是否是空集合
boolean contains(Object obj) 是否包含某个元素 ,通过 equals() 判断是否为同一个对象
boolean containsAll(Collection coll) 是否包含某个元素 ,拿两个集合的元素挨个比较
boolean remove(object obj) 删除元素 ,删除第一个匹配的元素
boolean retainAll(Collection c) 取两个集合的交集
Object[] toArray() 转成对象数组
iterator() 迭代器对象 ,集合遍历

Collection 子接口之一 :List接口 概述

  • 使用 List 替代 数组 。
  • 元素有序 且 元素可以重复
  • 每个元素对应一个索引值
  • 常用实现类 :ArrayList 、LinkedList 、Vector [线程安全]

ArrayList 源码分析 【底层使用 数组存储 】查询快 ,使用索引值查 [线程不安全 ]

​ jdk 1.7 :

​ 构造器初始化的时候.就创建一个 长度为 10 的数组容量 Object[] elemrntData;

​ jdk 1.8 :

​ 构造器初始化的时候并不会创建数组长度 ,而是在第一次 调用 list.add(); 方法的时候,才去创建数组长度为 10

​ 后续操作与 jdk 1.7 无异

LinkedList 源码分析 【底层使用 双向链表存储】 增删快 ,[线程不安全 ]

private static class Node<E> {
    E item;	// 当前元素
    Node<E> next;  // 下一个指针指向
    Node<E> prev;  // 上一个指针指向 

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

List 接口方法

接口方法 作用
void add(int index, Object ele); 在 index 索引位置插入 元素 ele
boolean addAll(int index, Collection eles); 从 index 索引位置开始将集合 eles 中的所有元素添加进来
Object get(int index); 获取指定 index 索引值位置的元素
int indexOf(Object obj); 返回元素 obj 在集合中首次出现的位置
int lastIndexOf(Object obj); 返回元素 obj在当前集合中最后一次出现的的位置
Object remove(int index); 移除指定 index索引值位置的元素 ,并返回此元素
Object set(int index, Object ele); 设置指定 index索引值位置的元素为 ele
List subList(int fromIndex, int toIndex); 返回从 fromIndex 到 toIndex 索引值位置的子集合 。

**总结 :List 常用方法 **

​ 增 :add(Object obj); / add(int index, Object obj);

​ 删 :remove(int index); / remove(Object obj);

​ 改 :set(int index, Object ele);

​ 查 :get(int index);

​ 长度 :size();

​ 遍历 :

​ ① 、迭代器 Iterator

Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next);
}

​ ②、forEach

for (Object obj : list) {
    System.out.println(obj);
}

​ ③、for 循环

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

Collection 子接口之二 :Set 接口

简介 :

  • Set 接口是Collection的子接口 ,Set 接口没有提供额外的方法
  • Set 集合不允许包含相同的元素 ,否则会添加操作失败
  • Set b判断两个对象是否相同 根据 equals() 方法 。而不是根据 “==” 运算符 。

常用实现类之一 :HashSet

HashSet 底层 :数组 + 链表 的结构

  • HashSet 按照 Hash 算法来存储集合中的元素 ,具有很好的 存取 、查找 、删除 性能 。

  • 特点 :

    • 无序列表

    无序性 :不等于随机性 。

    ​ 是指 存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值确定存储位置

    • 不是线程安全
    • 元素可以为 null
  • HashSet 集合判断两个个元素相等的标准

    • 两个对象通过 hashCode() 方法比较相等 。并且两个对象的 equals() 方法返回值相等 。
  • 存放在 Set 容器中的对象 ,必须重写 equals() 方法和 hashCode(Object obj); 方法

  • 不可重复性

    ​ 是指 保证添加的元素按照 equals() 判断时 ,不能返回 true . 即:相同的元素只能添加一个 。

  • 添加元素的过程 :以HashSet 为例 :

    ​ 向HashSet 中添加元素a ,首先调用元素a所在类的hashCode() 方法 ,计算元素a的哈希值 ,

    ​ 此哈希值接着通过某个算法计算出在 HashSet 底层数组中的存放位置 (索引位置),判断

    ​ 数组对应索引位置是否已经有元素 :

    ​ 情况一 :

    ​ 如果此位置上没有其他元素 ,则元素a添加成功 。

    ​ 情况二 :

    ​ 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值 :

    ​ 如果hash值不相同 ,则元素a添加成功 。

    ​ 情况三 :

    ​ 如果hash值相同 ,进而需要调用元素a所在类的equals() 方法 :

    ​ equals() 返回 true ,元素 a 添加失败 。

    ​ equals() 返回 false ,则元素 a 添加成功 。


Set 实现类之三 :LinkedHashSet

  • LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置 。但它同时使用双向链表维护元素的次序 。
  • LinkedHashSet 插入性能略低 HashSet ,但在 迭代器访问 Set 里的全部元素时有很好的性能 。
  • 不允许集合元素重复 。

Set 实现类之三 :TreeSet

  • TreeSet 是 SortedSet 接口的实现类 。TreeSet 可以确保元素处于排序状态 。
  • TreeSet 底层使用 红黑树 数据结构存储数据 。
  • TreeSet 两种排序方法 :自然排序定制排序 。默认情况下 TreeSet 采用自然排序

instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例

1、向 TreeSet 中添加数据 ,要求必须是相同类的对象 ,例如 :要么就都是整型 ,要么都是字符串 ,不能参半

2、两种排序方式 :自然排序(实现 Comparable 接口) 和 定制排序(Comparator

3、自然排序中 ,比较两个对象是否相同的标准 :comparmTo() 返回0

1、自然排序

public class User implements Comparable {
    private Integer age;
    // ... 省略set get 
    @Override
    public int compareTo(Object o) {
        if (o instanceof  User) {
            User user = (User) o;
            return this.age.compareTo(user.age);
        } else {
            throw new RuntimeException("类型不匹配 ");
        }
    }
    /*
    	Set seen = new TreeSet();
        seen.add(new User(12));
        seen.add(new User(123));
        seen.add(new User(456));
    */

2、定制排序

Comparator comparator = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof User && o2 instanceof User) {
                    User u1 = (User) o1;
                    User u2 = (User) o2;
                    return Integer.compare(u1.getAge(), u2.getAge());  // 从小到大
                    // return Integer.compare(u1.getAge(), u2.getAge());	从大到小
                } else {
                    throw new RuntimeException("类型不匹配 ");
                }
            }
        };
        Set seen = new TreeSet(comparator);
        seen.add(new User(12));
        seen.add(new User(123));
        seen.add(new User(456));

Map :接口

简介 :

HashMap [主要实现类],线程不安全,可以存储nul键值对

  • key :无序的,不可用重复 ,使用 Set 存储所有的 keu ,存储类对象 需要 重写类的 hashCode 和 equals
  • value :无序的,可重复的 ,使用 Collection 存储 value 。存储类对象需要重写 equals
  • 一个键值对key:value 构成一个 Entry 对象 entry无序的,不可重复读, 使用 Set 存储entry 。

LinkedHashMap [对于频繁的遍历] ,效率高于HashMap

TreeMap [排序 使用 key作为排序条件]

Hashtable [线程安全,效率低] jdk1.0 ,目前不推荐使用 。

面试题

HashMap 的底层 :在jdk7及以前 是 数组 + 链表 结构

​ 在 jdk1.8 是 数组+链表+红黑树 结构

HashMap的底层实现原理 ?以 jdk 1.7 为例

HashMap map = new HashMap();

​ 在实例化以后 ,底层创建了长度为 16 的一维数组 Entry[] table.

map.put(key1,value1);

​ 首先,调用 key1 所在类的hashCode() 计算 key1 的哈希值 ,此哈希值经过算法计算后,得到在Entry数组中的存放位置 。

​ 如果此位置上的数据为空 ,此时的 key1-value1 添加成功 --- 情况 1


​ 如果此位置上的数据不为空 ,(此位置已存在数据 ,),比较 key1 和已经存在的数据的哈希值 :

​ 如果 key1 的哈希值与已经存在的数据的哈希值不相同 ,此时 key1-value1 添加成功 --- 情况 2


​ 如果 key1 的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同 ,则继续比较 :调用 key1 所在类的equals(key2)方法 ,比较 :

​ 如果equals() 返回 false ,此时 key1-value1 添加成功 --- 情况 3

​ 如果 equals() 返回true ,使用 value1 替换 value2 .

扩容问题 :扩容为 原来的两倍 。

补充 :关于情况2和情况3 :此时 key1-value1 和原来的数据以链表的形式存储

接口方法 作用
添加、删除、修改操作
Object put(Object key, Object value) 将指定key-value添加(修改)到当前map对象中
void putAll(Map m) 将 Map m 中的所有key-value键值对存放到当前map中
Object remove(Object key) 移除指定 key 的 key-value键值对 ,并返回 value
void clear() 清空当前 map 中的所有数据
元素查询操作
Object get(Object key) 获取指定 key 对应的 value
boolean containsKey(Object key) 是否包含指定的 key
boolean containsValue(Object value) 是否包含指定的 value
int size() 返回 map 中 key-value 键值对的个数
boolean isEmpty() 判断当前 map 是否为空
boolead equals(Object obj) 判断当前 map 和参数对 obj 是否相等
元视图操作
Set keySet() 返回所有 key 构成的 Set 集合
Collection values() 返回所有的 value 构成的 Collection 集合
Set entrySet() 返回 所有 key-value 键值对 构成的 Set 集合

面试题

谈谈你对 HashMap 中 put/get方法的认识 ?

再谈谈 HashMap 的扩容机制 ?默认大小是多少?什么是负载因子(或填充比)

什么是吞吐临界值 (或阀值 、threshold)

HashMap 源码中的常量

image-20201203190522475


Map 源码分析 :jdk 1.8 (未完)

简要说明

jdk 1.8 初始化容量 是在 第一次 put 的时候 对数组进行初始化 。

jdk 1.7 是在构造器初始化容量大小 .

1.8 HashMap 结构图

image-20201204134705519

  • :JDK 1.8 中 涉及的 数据结构

1、位桶数组 (定义存储数据的数组) 【数组 】

transient Node<K,V>[] table;	// 键值对 <k,v>

2、数组元素 Node<k,v> ,实现类 Entry 接口 【链表 】

Node 是单向链表,Node<k,v>是一个内部类 ,相当于是 一个 节点类

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

    // 构造器 Hash值 ,键 ,值 ,下一个节点 (当前节点的下一个节点引用 。)
    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    // 判断两个 Node 是否相等 ,若 key 和 value 都相等 ,返回true 。可以与自身比较为true /
    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

3、红黑树 【树结构】

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // 父节点
    TreeNode<K,V> left;	 // 左子树
    TreeNode<K,V> right;  // 右子树 
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;	// 树节点颜色属性 
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }

    /**
         * Returns root of tree containing this node.
         */
    // 返回当前节点的 根节点 。
    final TreeNode<K,V> root() {
        for (TreeNode<K,V> r = this, p;;) {
            if ((p = r.parent) == null)
                return r;
            r = p;
        }
    }
  • 二 : 构造器
// 1、初始化时 指定初始容量和负载因子 .如果指定的(初始容量为负数或者负载因子非正).抛出异常 IllegalArgumentException
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}
// 2、指定初始容量 使用默认的负载因子 0.75f
public HashMap(int initialCapacity) {
    // 调用的还是 两个参数的构造器 
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 3、没有参数 ,默认初始容量 16 ,默认负载因子 0.75f
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
// 4、用 m 的元素初始化散列映射 
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}
  • 三 : HashMap 的存取机制

1、get(Object key); 如何 getValue 值 【获取指定 key 对应的 value】

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; 		// Entry 对象数组 
        Node<K,V> first, e; 	// 在 tab 数组中经过散列的第一个位置
        int n; 
        K k;
        // 找到插入的第一个 Node节点 ,方法是 hash值和 n-1 相与 ,tab[(n - 1) & hash])
        // 在一条链上的hash值是相同的 
        if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
            // 检查第一个 Node节点 是不是要找的 Node
            if (first.hash == hash && // always check first node
                // 判断条件是 hash值要相同 ,并且 key值相同 
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            // 检查 first 后面的 Node
            if ((e = first.next) != null) {
                if (first instanceof TreeNode) // 如果是树节点的一个实例 ,则调用树的方法 查找目标节点 
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                /*遍历链表.找到 key值和 hash值 都相等的 Node*/
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        // 没有返回 null
        return null;
    }

get(key) 方法时获取 key 的 hash值 ,计算 hash&(n-1) 得到在链表数组中的存储位置 [ first=tab[hash&(n-1)] ] ,

先判断 first 的 key 是否等于参数 key ,不等就遍历后面的链表找到相同的 key值 返回对应的 value值即可 .

**2、put(key, value); ** 【将指定key-value添加(修改)到当前map对象中】

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; // 位桶初始化 默认容量 16
    // 如果 tab在((n-1)&hash)的值是空 ,就新建一个节点插入到该位置 
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    /*表示有冲突,开始处理冲突*/
    else {
        Node<K,V> e; K k;
        // 检查第一个Node, P是不是要找的值 
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode) // 如果p 已经是树节点的一个实例 ,即这里已经是树结构了
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else { // p与新节点既不完全相同 ,也不是 TreeNode 实例 .普通节点
            for (int binCount = 0; ; ++binCount) {
                /*指针为空,就挂在后面 */
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null); // 指向 新节点 ,将新节点插入到链表尾部 
                    /*如果冲突的个数已经达到8个 ,判断是否需要改变冲突的存储结构
                      	treeifyBin 首先判断当前的 HashMap 长度 ,如果个数不足64,只进行扩容
                      		resize(),扩容tab,如果个数达到64,将冲突的存储结构变为红黑树 .
                    */
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                /*如果有相同的 key值就结束遍历 .*/ 
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;	// 相当于 pNode = pNode.next ,循环变量 
            }
        }
        /*如果链表上有相同的 key值 新的value 替换(覆盖) 旧的value*/
        if (e != null) { // existing mapping for key 键的现有映射 ,键的 value 存在 
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e); // 用于 LinkedHashMap
            return oldValue;	// 返回存在的 value值 
        }
    }
    ++modCount;
    /*如果当前大小大于门限 ,门限是初始容量的0.75倍*/
    if (++size > threshold)
        resize();	// 扩容两倍
    afterNodeInsertion(evict); // 用于 LinkedHashMap
    return null;
}

1、判断键值对数组 tab[] 是否为空或为null ,否则以默认大小 resize() ;

if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

2、根据键值 key 计算hash值 得到插入的数组索引 i ,如果 tab[i] == null ,直接新建节点添加 . 不为null 转入第三步

if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

3、判断当前数组中处理 hash 冲突的方式是链表还是红黑树(check 第一个节点类型即可),分别处理 .

  • 四 :resize() 扩容机制

构造 hash 表时,如果不指定初始大小 ,默认容量大小是 16 (Node 数组大小16) ,如果Node[] 数组中的元素达到(填充比 * Node.length) , 则重新调整 HashMap 大小 变为原来的两倍大小 ,【扩容很耗时!】

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length; // 第一次put oldCap=0
    int oldThr = threshold; // 当前临界值 threshold=0 赋值给 oldThr
    int newCap, newThr = 0;
    // 如果旧表的长度不为空
    if (oldCap > 0) {	// 第一次put 不进去 
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        /*把新表的长度设置为旧表长度的两倍 ,newCap=2*oldCap */
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            /*把新表的门限设置为旧表门限的两倍 ,newThr = 2*oldThr */
            newThr = oldThr << 1; // double threshold
    }
    /*如果旧表的的长度是0,说明是第一次初始化表*/
    else if (oldThr > 0) // initial capacity was placed in threshold // 第一次put 不进去 
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults // 第一次put 进去 
        newCap = DEFAULT_INITIAL_CAPACITY; // 将默认容量16 赋值给 newCap
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);// newThr=(默认容量*加载因子)=12
    }
    if (newThr == 0) {  
        float ft = (float)newCap * loadFactor;	// 新表长度 * 加载因子 
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr; // 将计算出的阀值赋值给临界值
    @SuppressWarnings({"rawtypes","unchecked"})
    /*开始构造新表 ,初始化表中的数据 .*/
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // new Node ,数组出现容量是16 newCap=16 ,
    table = newTab; // 把新表赋值给 table
    /*如果旧表不为空 ,把旧表中的数据移动到新表中 */
    if (oldTab != null) {	
        /*遍历原来的旧表*/
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null) // 说明旧表这个Node 没有链表 ,直接存放在新表的 e.hash & (newCap - 1)位置 .
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode) // e 是 树结构的实例 ,则进行红黑树的重hash分布 
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                /*如果e后面有链表,表示e后面链着单链表 ,需要遍历链表 .将每个节点重新计算在新表的位置,并进行搬运 .*/
                else { // preserve order 保证顺序
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;	// 记录下一个节点 
                        /*新表是旧表的两倍容量 ,实际上把链表分为两队 ,
                        		e.hash & oldCap偶数一对 , e.hash & oldCap奇数一对 */
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {	//lo队不为null ,放在新表原位置
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {	//hi队不为null ,放在新表 j + oldCap 位置 
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;	// 返回新表 
}
  • 五 :JDK1.8 使用红黑树的改进

在 jdk 1.7 中 ,HashMap 结构为 数组+链表 。

在 jdk 1.8 中 ,HashMap 处理 “碰撞” 情况增加了 红黑树这种数据结构 ,当碰撞节点较少,采用链表存储 ,当 阀值 > 8 时 ,

并且 数组个数达到 64 ,则采用红黑树 (特点 : 查询时间是 O(logn)),存储 ,将链表存储转换成红黑树存储 。

​ 最坏的情况即 所有的key 都映射到同一个 位桶(数组索引位置)中 ,HashMap就退化成一个链表 ,查询时间从O(1) 到 O(n)

升级为 红黑树(也是一个二叉树)

​ 使用 hash值 作为树的分支变量 ,如果两个 hash 值不相等 ,但指向同一个数组索引 ,hash 值 较大的会插入右子树 。

​ 如果 hash 值相等 ,key 值 最好实现了Comparable ,可以按顺序插入 (自然排序 、定制排序 ) .

遍历方式 :

// 第一种 此方式可以同时取出 key value 的值 
Iterator<Map.Entry<String,Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, Integer> next = iterator.next();
    System.out.println(next.getKey() + next.getValue());
}
// 第二种 先取出 key 的值 ,然后通过get(key)方法 获取value 效率较低 
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
    String next = iterator.next();
    System.out.println(next + map.get(next));
}

LinkedHashMap (了解)

简介 :

​ LinkedHashMap 是 HashMap 的子类 ,内部也是调用父类的 put 方法 ,只是 创建Node 节点的时候 ,调用 LinkedHashMap 本身的 newNode()方法

// LinkedHashMap 部分源码 
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

TreeMap (了解)

简介 :

​ 向 TreeMap 中添加 key-value ,要求 key 必须由同一个类创建的对象 。

​ 因为要按照 key 进行排序 :自然排序 、定制排序 。(也只能说 key 排序 ,value排序无效 )

Properties 处理属性文件

Properties 是 Hashtable 的子类

Properties pros = new Properties();
FileInputStream fs = new FileInputStream("类路径下的 properties后缀文件 ");
pros.load(fs); // 加载流对应的文件
String name = pros.getProperties("key");
String age = pros.getProperties("value");
posted @ 2020-12-04 16:44  san只松鼠  阅读(139)  评论(0)    收藏  举报