详解集合框架和各个实现类的源码分析

目录

一 集合框架的概述

2.1 数组在存储多个数据方面的特点

2.2 数组在存储多个数据方面的缺点

 2.3 集合存储的特点

二、集合框架

1、Collection接口:单列接口,用来存储一个一个的对象

 1)List接口:存储有序的、可重复的数据  (类似于 动态数组)

     ①ArrayList  :作为List接口的主要实现类,线程不安全,效率高;底层使用Object[ ] elementData存储,允许null值

     ②LinkedList  : 底层使用双向链表存储;对于频繁的插入、删除操作,使用此类效率比ArryList高,允许null值,线程不安全

     ③Vector :  作为LIst接口的古老实现类,线程安全,效率低;底层使用Object[]elementData存储

 2)set接口:存储无序的、不可重复的数据  (类似高中讲的集合)

①HashSet :作为Set接口的主要实现类,线程不安全:可以存储一个null值,底层:基于HashMap实现,而HashMap底层基于哈希表实现,哈希表(数组+链表的结构)

②LinkedHashSet : 作为HashSet的子类,遍历其内部子类是,可以按照添加的顺序遍历,只能允许一个null值

③TreeSet : 可以按照添加对象的指定属性进行排序,不允许null值

2、Map接口:双列集合,用来存储一对(key-value)一对的数据  (类似于高中讲的函数)

   1)HashMap : 作为Map的主要实现类;线程不安全,效率高;存储null的key和value

   2)TreeMap:保证按照添加的key—-value对进行排序,实现排序遍历(底层使用红黑树)

   3)  Hashtable :作为古老的实现类;线程安全的;效率低;不能存储null的key和value

List和String数组之间的直接转换


一 集合框架的概述

1、集合 、数组都是对多个数据进行存储操作的结构,简称java容器

说明:此时的存储,主要是指内存层面的存储,不涉及到持久化的存储

详解集合框架和各个实现类的源码分析_第1张图片

2.1 数组在存储多个数据方面的特点

> 一旦被初始化以后,其长度也就确定了

> 数组一旦定义好,其元素类型也就确定了,我们也就只能操作指定类型的数据了

比如;String[] arr;int [] arr1;

2.2 数组在存储多个数据方面的缺点

>一旦初始化以后,其长度不能修改

>数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不变,同时效率不高

>获取数组中实际元素的个数的需求,数组没有现成的属性和方法可用

>数组存储数据的特点:有序、可重复,对于无序、不可重复的需求不能满足

>数组的功能性较弱(没有方法,只有一个属性,只能存值取值,遍历)

 >基本类型数组可以存储基本类型, 引用类型数组可以存储引用类型

 2.3 集合存储的特点

       1 长度不固定,可以存储多个数据

       2 允许存储多种数据 , (但是,经验上大部分还是存值同一类型)

       3 有些集合允许重复元素,有些集合不允许重复

       4 有些集合有顺序,有些集合无序

       5 集合提供了丰富的API来操作集合,及集合中的元素

       6 集合只能存储引用数据类型

二、集合框架

1、Collection接口:单列接口,用来存储一个一个的对象

 1)List接口:存储有序的、可重复的数据  (类似于 动态数组)

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位置的子集合
 

     ①ArrayList 作为List接口的主要实现类,线程不安全,效率高;底层使用Object[ ] elementData存储,允许null值

          a、ArrayList源码分析(jdk 7 情况下)

               ArrayList list = new ArrayList(); // 底层创建了长度是10的Object[]数组elementData

                 list.add(123);  //elementData[0]=new Integer(123);

                  ....

                 list.add(11);//如果此次的添加导致底层elementData数组容量不足,则扩容,

                 默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中

结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity);

             b、ArrayList源码分析(jdk 8 情况下)   

                  ArrayList list = new ArrayList();//底层Object[]数组elementData初始化为{},并没有创建长度为10的数组

                 list.add(123);//第一次调用add()时,底层才创建了长度为10的数组,并将123添加到elementData数组中

                 后续添加和扩容与jdk 7 无异

详解集合框架和各个实现类的源码分析_第2张图片

     ②LinkedList  : 底层使用双向链表存储;对于频繁的插入、删除操作,使用此类效率比ArryList高,允许null值,线程不安全

 LinkedList源码分析:

  LinkedList list m new linkedList();  内部声明了Node类型的first和Last属性,默认值为 null               

    list.add(123);//将123封装到Node中,创建了Node对象。
       其中,Node定义为:
                private static class Node (
                           E  item;
                           Node next;

                           Node prev;
                           Node(Node prev, E eLement,Node next){

                          ithis.item = eLement;
                          this.next = next;

                          this.prev = prev;

                         }

                  }

详解集合框架和各个实现类的源码分析_第3张图片

     ③Vector :  作为LIst接口的古老实现类,线程安全,效率低;底层使用Object[]elementData存储

①、②和③的异同?

同:三各类都实现了List接口,存储数据的特点相同、可重复的数据

不同:见上

 2)set接口:存储无序的、不可重复的数据  (类似高中讲的集合)

Set接口是Collection的子接口,set接口没有提供额外的方法
Set集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败。
Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法

不可重复性;保证添加的元素按照equal()判断时,不能返回true。即相同元素只能添加一个

Set没有下标操作的方法 

添加元素的过程:以Hashset为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值按者通过某种算法计算出HashSet底层数组中的存放位置(即为:索引位置),判断
数组此位置上是否已经有元素:
        如果此位置上没有其他元素,则元素添加成功。
        如果此位置上有其他元素b(成以健表形式存在的事个元素),则比较元素a与元素b的hash值:
                 如果hash值不相同,则元素a添加成功。
                如果hash值相同,进而需要调用元素a所在类的equlas()方法:
                                        equals()返回true,元素a添加失败
                                        equals()返回false,则元素a添加成功。

①HashSet :作为Set接口的主要实现类,线程不安全:可以存储一个null值,底层:基于HashMap实现,而HashMap底层基于哈希表实现,哈希表(数组+链表的结构)

HashSet是 Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。

HashSet按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
HashSet具有以下特点:
≥不能保证元素的排列顺序

>HashSet不是线程安全的

≥集合元素可以是null
HashSet集合判断两个元素相等的标准:两个对象的equals方法相等,并且hashCode方法返回值也相等。
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Objectobj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的放列码”。

详解集合框架和各个实现类的源码分析_第4张图片

详解集合框架和各个实现类的源码分析_第5张图片

②LinkedHashSet : 作为HashSet的子类,遍历其内部子类是,可以按照添加的顺序遍历,只能允许一个null值

LinkedHashSet 是 HashSet的子类
LinkedHashSet 根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
LinkedHashSet 插入性能略低于 HashSet,但在迭代访问Set里的全部元素时有很好的性能。
LinkedHashSet 不允许集合元素重复。
对于频繁的遍历操作LinkedHashSet的效率要高于HashSet

③TreeSet : 可以按照添加对象的指定属性进行排序,不允许null值

TreeSet是 SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。TreeSet底层使用红黑树结构存储数据
新增的方法如下:(了解)
>Comparator comparator()>Object first()
>Object last()
>Object lower(Object e)>Object higher(Object e)
>SortedSet subSet(fromElement, toElement)>SortedSet headSet(toElement)
>SortedSet tailSetfromElement)
TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。
两种排序方法来定制集合内元素相同的标准

详解集合框架和各个实现类的源码分析_第6张图片

a) TreeSet的自然排序

在自然排序中比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()

要想自定义自然排序有如下:

实现Comparable接口,重写compareTO()方法

详解集合框架和各个实现类的源码分析_第7张图片详解集合框架和各个实现类的源码分析_第8张图片

 题目需求如下:

详解集合框架和各个实现类的源码分析_第9张图片

/**
 * @Description TODO
 * @Created by Administrator
 * @Date 2021/7/26 20:06
 */
public class Worker implements Comparable{
    private int age;
    private String name;
    private double salary;

    public Worker() {
    }

    public Worker(int age, String name, double salary) {
        this.age = age;
        this.name = name;
        this.salary = salary;
    }

    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 double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public void work() {
        System.out.println(name + "work");
    }

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

//    @Override
//    public boolean equals(Object o) {
//        if (this == o) return true;
//        if (o == null || getClass() != o.getClass()) return false;
//        Worker worker = (Worker) o;
//        return age == worker.age &&
//                Double.compare(worker.salary, salary) == 0 &&
//                Objects.equals(name, worker.name);
//    }

    //重写compareTo方法
    @Override
    public int compareTo(Object o) {
        if(o instanceof Worker){
            Worker worker=(Worker)o;
            //先比较年龄,
            int compare = Integer.compare(this.age, worker.age);
            //如果年龄相等,再比较工资
            if(compare==0){
                int compare1 = Integer.compare((int) this.salary, (int) worker.salary);
                //如果工资相等,再比较姓名
                if(compare1==0){
                    return this.name.compareTo(worker.name);
                }
                else { //工资不相等,返回工资比较结果
                    return compare1;
                }
            }else {  //年龄不相等,返回年龄排序结果
                return compare;
            }
        }else {
            return 0;
        }
    }
}

/**
 * @Description TODO
 * @Created by Administrator
 * @Date 2021/7/26 19:33
 */
public class TestSet {
    public static void main(String args[]) {

        TreeSet set = new TreeSet<>();
        set.add(new Worker(18,"zhang3",1500));
        set.add(new Worker(18,"li4",1500));
        set.add(new Worker(18,"wang5",1600));
        set.add(new Worker(17,"zhao6",2000));
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            Worker next = iterator.next();
            System.out.println(next);
        }
    }
}

详解集合框架和各个实现类的源码分析_第10张图片

 b) TreeSet的定制排序

在定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()

 要想实现定制排序需要如下两个步骤:

1、实现Comparator接口,重写compare方法

2、使用TreeSet的有参构造方法,传入Comparator对象

详解集合框架和各个实现类的源码分析_第11张图片

下面给个示例,利用定制排序按照员工的工资从很小到大排序

public class TestSet {
    public static void main(String args[]) {

        Comparator com = new Comparator() {
            //按照工资从小到大排序
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof Worker && o2 instanceof Worker){
                    Worker w1 = (Worker)o1;
                    Worker w2 = (Worker)o2;
                    return Integer.compare((int)w1.getSalary(),(int)w2.getSalary());
                }else {
                    return 0;
                }
            }
        };
        
        TreeSet set = new TreeSet<>(com);
        set.add(new Worker(18,"zhang3",1500));
        set.add(new Worker(18,"li4",1400));
        set.add(new Worker(18,"wang5",1600));
        set.add(new Worker(17,"zhao6",2000));
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            Worker next = iterator.next();
            System.out.println(next);
        }
    }
}

详解集合框架和各个实现类的源码分析_第12张图片

2、Map接口:双列集合,用来存储一对(key-value)一对的数据  (类似于高中讲的函数)

Map与Collection并列存在。用于保存具有映射关系的数据:key-value

 Map 中的 key 和 value 都可以是任何引用类型的数据

 Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应 的类,须重写hashCode()和equals()方法

 常用String类作为Map的“键”

 key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到 唯一的、确定的 value

 Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和 Properties。其中,HashMap是 Map 接口使用频率最高的实现类

 常用方法

 添加、删除、修改操作:

 Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中

 void putAll(Map m):将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是否为空

 boolean equals(Object obj):判断当前map和参数对象obj是否相等

元视图操作的方法:

 Set keySet():返回所有key构成的Set集合

 Collection values():返回所有value构成的Collection集合

 Set entrySet():返回所有key-value对构成的Set集合

   1)HashMap : 作为Map的主要实现类;线程不安全,效率高;存储null的key和value

底层存储:数组+链表  (jdk 7及之前)   数组+链表+红黑树(jdk 8)

只能有一个null的key,可以有多个null的value 

HashMap集合判断两个元素相等的标准:两个对象的equals方法相等,并且hashCode方法返回值也相等。 

 底层实现原理

 jdk 7   时  (扩容时默认扩容为原来的2倍)

HashMap map = new HashMap():
在实例化以后,底层创建7长度是16的一-维数组Entry[] table.
...可能已经执行过多次put...
map. put(key1, value1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry 数组中的存放位置。
      如果此位置上的数据为空,此时的key1-value1 添加成功。
      如果此位置上的数据不为空,(意味者此位业上存在一个成多个数据(以链表形式存在))。比较key1和已经存在的一个或多个数据的哈希值:
      如果key1的哈用值与已经存在的数据的哈用值都不相同,此时key1-value1 调加成功。
      如果key1的哈希值和已经存在的某一个数据(key2-value2) 的哈希值相同,继续比较:调用key1所在类的equals(key2)
                     如果equals()返回false;此时key1 value1添加成功。
                     如果equals()返回true:使用value1替换value2。

jdk 8 时

jdk8相较于jdk7在底层实现方面的不同:
1. new HashMap():底层没有创建一个长度为16的数组
2. jdk 8底层的数组是: Node[], 而非Entry[]
3.首次调用put()方法时, 底层创建长度为16的数组
4. jdk7底层结构只有:数组+链表。jdk8 中底层结构:数组+链表+红黑树。
当数组的某一个索引位置上的元素以链表形式存在的数据个数> 8且当前数组的长度> 64时,
         此时此索引位置上的所有数据改为使用红黑树存储。
 

源码分析

1. HashMap 的数据结构

    在 java 编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引 用),所有的数据结构都可以用这两个基本结构来构造的,HashMap 也不例外。HashMap 实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

详解集合框架和各个实现类的源码分析_第13张图片

从上图中可以看出,HashMap 底层就是一个数组结构,数组中的每一项又是一个链表。 当新建一个 HashMap 的时候,就会初始化一个数组。

2. HashMap 的存取实现:

1)存储

详解集合框架和各个实现类的源码分析_第14张图片

 详解集合框架和各个实现类的源码分析_第15张图片

public V put(K key, V value) {
2. // HashMap 允许存放 null 键和 null 值。
3. // 当 key 为 null 时,调用 putForNullKey 方法,将 value 放置在数组第一个位置。
4. if (key == null)
5. return putForNullKey(value);
6. // 根据 key 的 keyCode 重新计算 hash 值。
7. int hash = hash(key.hashCode());
8. // 搜索指定 hash 值在对应 table 中的索引。
9. int i = indexFor(hash, table.length);
10. // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
11. for (Entry e = table[i]; e != null; e = e.next) {
12. Object k;
13. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
14. V oldValue = e.value;
15. e.value = value;
16. e.recordAccess(this);
17. return oldValue;
18. }
19. }
20. // 如果 i 索引处的 Entry 为 null,表明此处还没有 Entry。
21. modCount++;
22. // 将 key、value 添加到 i 索引处。
23. addEntry(hash, key, value, i);
24. return null;
25. }

     从上面的源代码中可以看出:当我们往 HashMap 中 put 元素的时候,先根据 key 的 hashCode 重新计算 hash 值,根据 hash 值得到这个元素在数组中的位置(即下标),如 果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新 加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到 此数组中的该位置上。                addEntry(hash, key, value, i)方法根据计算出的 hash 值,将 key-value 对放在数组 table 的 i 索引处。addEntry 是 HashMap 提供的一个包访问权限的方法,代码如下:

void addEntry(int hash, K key, V value, int bucketIndex) {
2. // 获取指定 bucketIndex 索引处的 Entry
3. Entry e = table[bucketIndex];
4. // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entr
y
5. table[bucketIndex] = new Entry(hash, key, value, e);
6. // 如果 Map 中的 key-value 对的数量超过了极限
7. if (size++ >= threshold)
8. // 把 table 对象的长度扩充到原来的 2 倍。
9. resize(2 * table.length);
10. }

    当系统决定存储 HashMap 中的 key-value 对时,完全没有考虑 Entry 中的 value,仅仅 只是根据 key来计算并决定每个 Entry的存储位置。我们完全可以把 Map 集合中的 value 当 成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。

      hash(int h)方法根据 key 的 hashCode 重新计算一次散列。此算法加入了高位计算,防 止低位不变,高位变化时,造成的 hash 冲突。

 static int hash(int h) {
2. h ^= (h >>> 20) ^ (h >>> 12);
3. return h ^ (h >>> 7) ^ (h >>> 4);
4. }

       我们可以看到在 HashMap 中要找到某个元素,需要根据 key 的 hash 值来求得对应数 组中的位置。如何计算这个位置就是 hash 算法。前面说过 HashMap 的数据结构是数组和 链表的结合,所以我们当然希望这个 HashMap 里面的 元素位置尽量的分布均匀些,尽量 使得每个位置上的元素数量只有一个,那么当我们用 hash 算法求得这个位置的时候,马上 就可以知道对应位置的元素就是我们要的,而不用再去遍历链表,这样就大大优化了查询的 效率。 对于任意给定的对象,只要它的 hashCode() 返回值相同,那么程序调用 hash(int h) 方 法所计算得到的 hash 码值总是相同的。我们首先想到的就是把 hash 值对数组长度取模运 算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,在 HashMap 中是这样做的:调用 indexFor(int h, int length) 方法来计算该对象应该保存 在 table 数组的哪个索引处。indexFor(int h, int length) 方法的代码如下:

1. static int indexFor(int h, int length) {
2. return h & (length-1);
3. }

    这个方法非常巧妙,它通过 h & (table.length -1) 来得到该对象的保存位,而 HashMap 底层数组的长度总是 2 的 n 次方,这是 HashMap 在速度上的优化。在 HashMap 构造器中 有如下代码:

1. int capacity = 1;
2. while (capacity < initialCapacity)
3. capacity <<= 1;

       这段代码保证初始化时 HashMap 的容量总是 2 的 n 次方,即底层数组的长度总是为 2 的 n 次方。当 length 总是 2 的 n 次方时,h& (length-1)运算等价于对 length 取模,也就是 h%length,但是&比%具有更高的效率。 这看上去很简单,其实比较有玄机的,我们举个例子来说明: 假设数组长度分别为 15 和 16,优化后的 hash 码分别为 8 和 9,那么&运算后的结果如下:

详解集合框架和各个实现类的源码分析_第16张图片

       从上面的例子中可以看出:当它们和 15-1(1110)“与”的时候,产生了相同的结果, 也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8 和 9 会被放到数组中 的同一个位置上形成链表,那么查询的时候就需要遍历这个链 表,得到 8 或者 9,这样就 降低了查询的效率。同时,我们也可以发现,当数组长度为 15 的时候,hash 值会与 15-1 (1110)进行“与”,那么 最后一位永远是 0,而 0001,0011,0101,1001,1011,0111, 1101 这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可 以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率! 而当数组长度为 16 时,即为 2 的 n 次方时,2 n -1 得到的二进制数的每个位上的值都为 1, 这使得在低位上&时,得到的和原hash的低位相同,加之hash(int h)方法对key的hashCode 的进一步优化,加入了高位计算,就使得只有相同的 hash 值的两个值才会被放到数组中的同一个位置上形成链表。所以说,当数组长度为 2 的 n 次幂的时候,不同的 key 算得得 index 相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的, 查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。

       根据上面 put 方法的源代码可以看出,当程序试图将一个 key-value 对放入 HashMap 中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两 个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两 个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原 有 Entry 的 value,但 key 不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返 回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位 于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。

2) 读取:

1. public V get(Object key) {
2. if (key == null)
3. return getForNullKey();
4. int hash = hash(key.hashCode());
5. for (Entry e = table[indexFor(hash, table.length)];
6. e != null;
7. e = e.next) {
8. Object k;
9. if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
10. return e.value;
11. }
12. return null;
13. }

       有了上面存储时的 hash 算法作为基础,理解起来这段代码就很容易了。从上面的源代 码中可以看出:从 HashMap 中 get 元素时,首先计算 key 的 hashCode,找到数组中对 应位置的某一元素,然后通过 key 的 equals 方法在对应位置的链表中找到需要的元素。

      归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体 就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对, 当需要存储一个 Entry 对象时,会根据 hash 算法来决定其在数组中的存储位置,在根据 equals 方法决定其在该数组位置上的链表中的存储位置;当需要取出一个 Entry 时,也会根据 hash 算法找到其在数组中的存储位置,再根据 equals 方法从该位置上的链表中取出 该 Entry。

3. HashMap 的性能参数:

HashMap 包含如下几个构造器:

HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。 HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子 为 0.75 的 HashMap。 HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一 个 HashMap。

HashMap 的基础构造器 HashMap(int initialCapacity, float loadFactor)带有两个参数,它 们是初始容量 initialCapacity 和加载因子 loadFactor。

initialCapacity:HashMap 的最大容量,即为底层数组的长度。

loadFactor:负载因子 loadFactor 定义为:散列表的实际元素数目(n)/ 散列表的容量(m)。

负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越 高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是 O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小, 那么散列表的数据将过于稀疏,对空间造成严重浪费。

          ①LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。

               原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素

                对于频繁的遍历操作,此类执行效率高与HashMap

   2)TreeMap:保证按照添加的key—-value对进行排序,实现排序遍历(底层使用红黑树)

TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。 TreeMap 可以保证所有的 Key-Value 对处于有序状态。

TreeSet底层使用红黑树结构存储数据

TreeMap 的 Key 的排序:

自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有 的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException

定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口

 TreeMap判断两个key相等的标准:两个key通过compareTo()方法或 者compare()方法返回0。 

   3)  Hashtable :作为古老的实现类;线程安全的;效率低;不能存储null的key和value

Properties 类是 Hashtable 的子类,该对象用于处理属性文件

由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型

存取数据时,建议使用setProperty(String key,String value)方法和 getProperty(String key)方法

Properties pros = new Properties();

pros.load(new FileInputStream("jdbc.properties"));

String user = pros.getProperty("user");

System.out.println(user);

List和String数组之间的直接转换

List-->String数组

 Object[] toArray()
          按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组。
T[]
toArray(T[] a)
          按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。

一般采用第二个重载方法,指定需要转化的数组类型

String[] strings1 = new String[list.size()];
        list.toArray(strings1);

注:list.size()是指当前集合的长度,一般采用的是ArrayList集合

String数组-->List

static
List
asList(T... a)
          返回一个受指定数组支持的固定大小的列表。
String[] str={"adf","df","sde"};
List strings = Arrays.asList(str);

存空值问题



List集合:底层是一个数组,因此可以存放多个null


Set集合:因为在添加的时候会调用squal()方法,如果相同就会添加失败,因此所有Set集合只能存放一个null值。TreeSet不可以存放,因为他是排序,


Map集合:HashMap、LinkedHashMap可以存放一个key=null,多个value=null的值,因为若加入第二个key=null的值也会被覆盖,HashTable、TreeMap依然不可以存空。

你可能感兴趣的:(java,java)