【JavaSE】集合(Colleption与Map)

1.Java集合框架概述

一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有 一些弊端,而Java集合就像一种容器,可以 动态地 把多个对象的引用放入容器中。

  • 数组在内存存储方面的特点:
        1. 数组初始化以后,长度就确定了。
        2. 数组一旦确定了,其元素的类型也就确定了。也就只能操作指定类型的数据
  • 数组在存储数据方面的弊端:
        1.数组初始化以后,长度就不可变了,不便于扩展
        2.数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数(数组长度固定,元素个数不定)
        3.数组存储的数据是有序的、可以重复的。–>存储数据的特点单一
  • Java集合类可以用于存储数量不等的多个 对象,还可用于保存具有映射关系的
    关联数组。

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

  • Collection接口:单列集合,定义了存取一组对象的方法的集合
           1. List(子接口): 元素有序、可重复的集合 -->“动态”数组
               实现类:ArraysList、LinkedList、Vector
     
           2. Set(子接口): 元素无序、不可重复的集合 -->高中的“集合”
               实现类:HashSet、LinkedHashSet、TreeSet
     
  • Map接口:双列集合,保存具有映射关系“key-value对”的集合 -->y = f(x)[一对一、多对一]
      实现类:HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

在这里插入图片描述

 

2.Collection接口中的通用方法

向 Collection接口的实现类的对象中添加数据 obj时,要求 obj所在类要重写 equals()方法 (比较、删除、Set的添加等都要通过equals()来比较两个元素是否相等,其中Set还要额外重写HashCode()方法,因为它是 数组+链表结构)

1、添加
    add(Object obj)
    addAll(Collection coll)

2、获取有效元素的个数
    int size()

3、清空集合
    void clear()

4、是否是空集合
    boolean isEmpty()

5、是否包含某个元素
    boolean contains(Object obj):
    【是通过元素的equals方法来判断是否是同一个对象】

    boolean containsAll(Collection c):
    【也是调用元素的equals方法来比较的】判断 c集合中的元素是否都在当前集合中

6、删除
    boolean remove(Object obj):
    通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素

    boolean removeAll(Collection coll):
    取当前集合的差集:删除和 coll中相同的元素(去重)

    【通过元素的equals方法】

7、取两个集合的交集
    boolean retainAll(Collection c):
    把交集的结果存在当前集合中,不影响c:把相同的元素存到当前集合中,会替换当前集合原来的元素

8、集合是否相等
    boolean equals(Object obj)
    若是 list,要求顺序、元素相等;若是 set,要求元素相等

9、转成对象数组
    Object[] toArray()

10、获取集合对象的哈希值
    hashCode()

11、遍历
    iterator():
    返回迭代器对象,用于集合遍历

List 与 Set 通用的方法 …

增: boolean add(Object obj)
删: remove(Object obj),List 根据下标来删除,并返回该对象 Object;Set 根据对象来查找删除,返回删除的结果 boolean

 

3.Iterator迭代器接口

集合元素的遍历操作,使用 Iterator接口:
 
Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
 
Iterator仅用于遍历集合,Iterator木身并不提供承装对象的能力。如果需要创建
terator对象,则必须有一个被迭代的集合

 
集合对象每次调用 iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。

Iterator接口中的方法:
在这里插入图片描述

 

3.1 源码

ArrayList类中的 iterator()方法源码:


public Iterator<E> iterator() {
    return new Itr();
}

Itr 是ArrayList类中的一个内部类,实现了 Iterator接口,是它的实现类
iterator返回了实现 Iterator接口的实现类 Itr

 private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }
		...
    }

 

3.2 示例

hasNect()与next()实现遍历:

public class Main{
    public static void main(String[] args) {
        Collection coll = new ArrayList();
        coll.add("aaa");
        coll.add(123);
        coll.add(new Date());
        coll.add(true);

        Iterator iterator = coll.iterator();

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

//        方式1:
//        System.out.println(iterator.next());
//        System.out.println(iterator.next());
//        System.out.println(iterator.next());
//        System.out.println(iterator.next());

//        方式二:
//        for(int i=0;i<coll.size();i++){
//            System.out.println(iterator.next());
//        }

    }
}

 

3.3 iterator实现原理

iterator 开始指针指向 0 位置,iterator.hasNext()判断下一个位置有没元素,有返回true,iterator.next()将指针向下移,输出元素
在这里插入图片描述
 

3.4 Iterator接口remove()方法

可以在遍历的时候,删除集合中的元素 (iterator指向目标元素时,调用 remove()方法删除)
 
注意:
一定要在使用了 Iterator.next()之后使用 Iterator.remove(),因为开始在 0 位置
不能连续使用两次 Iterator.remove()方法

public class Main02 {
    public static void main(String[] args) {
        Collection coll = new ArrayList();
        coll.add("aaa");
        coll.add(123);
        coll.add(new Date());
        coll.add("aaa");
        coll.add(true);
        coll.add("aaa");

        Iterator iterator = coll.iterator();
        while(iterator.hasNext()){
            if("aaa".equals(iterator.next())){
                iterator.remove(); // 删除当前指向的元素
            }
        }

		// 使用一个新的迭代器遍历,上一个迭代器已指向末尾
        Iterator iterator1 = coll.iterator();
        while(iterator1.hasNext()){
            System.out.println(iterator1.next());
        }
        
        /* 123
		   Tue Apr 26 21:22:04 CST 2022
		   true */
    }
}

 

3.5.foreach循环遍历集合元素

Java5.0提供了 foreach循环迭代访问 Collection和数组。
 
遍历集合的底层调用 Iterator完成操作。
 
遍历操作不需获取 Collection或数组的长度,无需使用索引访问元素。
 
foreach还可以用来遍历数组。

 
for(类型 变量:数组/集合){ System.out.println(变量); }

public class Main{
	public static void main(String[] args){
		Collection coll = new ArrayList();
        coll.add("aaa");
        coll.add(123);
        coll.add(new Date());
        coll.add(true);

		for(Object obj:coll){
            System.out.println(obj);
        }
        /* aaa
           123
		   Tue Apr 26 21:22:04 CST 2022
		   true */
	}
}

 

4.Collection子接口:list

鉴于Java中数组用来存储数据的局限性,我们通常使用 List替代数组
 
Lst集合类中 元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
 
Lst容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
 
JDK API中 List接口的实现类常用的有:ArrayList、LinkedList和 Vector。

ArrayList、LinkedList和 Vector的异同 ?
 
相同点:
三个类都是实现了L1st接口,存储数据的特点相同:存储有序的、可重复的数据
 
不同:
ArrayList: 作为 List接口的主要实现类,线程不安全,效率高;底层使用 Object数组存储
LinkedList: 对于频繁的插入、删除操作,此类效率相比 ArrayList较高;底层使用双向链表存储。线程不安全
Vector: 作为 List的古老实现类:线程安全的,效率低

 

4.1 List接口常用方法

(在实现了Collection接口的方法后,添加了自身独有的方法 )

void add(int index,Object ele):在index位置插入element元素
boolean addALL(int index,Collection eles):从index位置开始将eLes中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int index0f(Object obj):返回obj在集合中首次出现的位置
int LastIndex(0f(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index,.Object ele):设置指定index位置的元素为eLe
List sublist(int fromIndex,int toIndex,):返回从fromIndextoIndex位置的子集合

总结:常用方法
增:add(Object obj) / add(int index,Object ele)
刪:remove(int index) / remove(Object obj)
改:set(int index,object ele)
查:get(int index),
长度:size()
遍历:1.普通的循环 2.Iterator.迭代器方式 3.增强for循环

 

4.2 ArrayList

ArrayList源码分析:

底层使用 Object数组存储:transient Object[] elementData;

JDK 7:
Arraylist list=new ArrayList(); // 底层创建了长度为 10的object[]数组eLementData
List.ad(123); // eLementData[0]new Integer(123);
List.add(11); // 如果此次的添加导致底层eLementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时会将原有数组中的数据复制到新的数组中。

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

JDK 8:
ArrayList List=new ArrayList(); // 底层object[]eLementData初始化为[].并没有创建长度为10的数组
List.add(123); // 第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到 eLementData中,后续的添加和扩容与JDK 7相同
 
小结:jdk7中的Arraylist的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存

 

4.3 ArrayList与LinkList

在这里插入图片描述
 

5.Collection子接口:Set

Set 接口是Collection的子接口,set接口没有提供额外的方法,使用的是Collection声明过的方法
 
Set 接口实现类对象 元素无序、不可重复
1.Set 接口对象不能存放重复的元素,可以存放null,但只能有一个
2.Set 接口对象存放数据无序(取出顺序固定)

常见的Set实现类:
HashSet: 作为 Set接口的主要实现类,线程不安全,可以存储 null值
LinkedHashSet: 作为 HaSet的子类,增加了前后指针,遍历其内部数据时,可以按照添加的顺序遍历
TreeSet: 可以按照添加的对象的指定属性,进行排序。

Set的无序性,不可重复性:
1.无序性: 存储的数据在底层 ‘数组’ 中不是按照 ‘数组’ 的索引存储,而是根据数据的哈希值
 
2.不可重复性: 先比较哈希值,哈希值相同再使用equals比较。(重写hashCode,让属性一样的对象的哈希值相同)

 

5.1 HashSet

HashSet的底层是HashMap,HashMap的底层是(数组+链表+红黑树)

HashSet扩容机制:
分析HashSet的添加元素底层是如何实现(hash()+equals()):
1.HashSet底层是HashMap
2.添加一个元素时,先得到hash值 – 会转成 -> 索引值
3.找到存储数据表table,看这个索引位置是否已经存放的有元素
4.如果没有,直接加入
5.如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添动加到最后
6.在Java8中,如果一条链表的元素个数超过TREEIFY THRESHOLD(默认是8),并且tabel的大小>=
MIN TREEIFY CAPACITY(默认64),
就会进行树化(红黑树)

存储结构示例(table):
在这里插入图片描述

public class Main{
    public static void main(String[] args) {
        Node[] table = new Node[16];

        Node node2= new Node("Node2_01",new Node("Node2_02", new Node("Node2_03", null)));
        // 散列表中索引为 2的链表
        table[2] = node2;
    }
}
class Node {
    Object o;
    Node next;

    public Node(Object o, Node next) {
        this.o = o;
        this.next = next;
    }
}

 

使用Set存放 person对象数据 (当属性 name、age都相同时,不能存入)
1、Person没有重写 HashCode()、equals()方法时:

class Person{
    String name;
    int age;

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class test {
    public static void main(String[] args) {
        Person person1 = new Person("张三",17);
        Person person2 = new Person("李四",17);
        Person person3 = new Person("张三",17);
        Set set = new HashSet();
        set.add(person1);
        set.add(person2);
        set.add(person3);
        System.out.println(set);
    }
}

输出结果是:[Person{name=‘张三’, age=17}, Person{name=‘张三’, age=17}, Person{name=‘李四’, age=17}]
因为 Set的特点(无序,不可重复),Person在没有重写 HashCode()和equals()方法时,先通过 HashCode()方法得到 Person对象的哈希值都是不相同的,所以存入三个。

2、Person重写 HashCode()、equals()方法时:

class Person{
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        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;
    }

    @Override
    public int hashCode() {
        return age;
    }
}
public class test {
    public static void main(String[] args) {
        Person person1 = new Person("张三",17);
        Person person2 = new Person("李四",17);
        Person person3 = new Person("张三",17);
        Set set = new HashSet();
        set.add(person1);
        set.add(person2);
        set.add(person3);
        System.out.println(set);
    }
}

输出结果是: [Person{name=‘张三’, age=17}, Person{name=‘李四’, age=17}]
通过重写 HashCode()和equals()方法,
 
重写后的 HashCode()方法如果 name和age(可选任意个属性) 相同时,返回相同的 hash值
[ person的hashcode值根据 属性 得到的hashcode值通过一定的算法得到,若 person的属性有自定义的类类型,则该类也要重写 hashcode()和equals(),让该类对象(person中的属性)在该类属性值相同时,返回相同的 hashcode()值。]

 
重写后的 equals()方法在 name和age 相同时,返回 true
存储数据时经过这两个方法,最终都通过两个属性值是否相同来判断两个元素是否相等

 

5.3 LinkHashSet

在原有的 HashSet基础上,添加了双向链表结构(每个元素都有前后指针),
优点,对于频繁的遍历操作,LinkHashSet效率高于HashSet

在这里插入图片描述

 

5.4 TreeSet

可以按照添加对象的指定属性,进行排序
(TreeSet只能添加相同类的对象,只有相同类的对象,才可以比较大小 【存储:树形结构】)

 
TreeSet中的比较两个元素的规则有所不同,不再是用equals()方法,而是用的是 compareTo()方法,即在添加时会进行比较比较排序,若与已有的某个元素比较的值相同时 (compareTo()方法返回0) 会无法添加成功,如:
name.compareTo(person.name) 返回0时认为这两个元素相同,要添加的元素经比较后无法添加成功

public class test {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet();
        treeSet.add("123");
        treeSet.add("234");
        treeSet.add("012");
        Iterator iterator = treeSet.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next()); // 012 123 234
            // String类型按照 String.comparaTo(实现了Comparable接口的compareTo方法)进行排序
        }	
    }
}

 

添加数据时,如果是自定义的类,需要有排序的规则才能添加成功:
1.自然排序(自定义类实现compareble接口,重写compareTo方法),
2.定制排序(在创建TreeSet时构造器中,添加compareble接口的实现类对象)

 
1.自然排序:

让自定义的类实现compareble接口,重写compareTo方法,compareTo方法中的既是添加时排序比较的规则

// 按照name顺序从小到大进行排序,其次age相同时也将其添加,按照age从小到大的顺序(二级排序)
class Person implements Comparable{
    String name;
    int age;

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

    @Override
    public int compareTo(Object o) {
        if(o instanceof Person){
            Person person = (Person)o;
            if(!name.equals(person.name)){
                return name.compareTo(person.name);
            }else{
                return Integer.compare(age,person.age);
            }
            // 从大到小顺序进行排序(return -name.compareTo(person.name);)
        }else{
            throw new RuntimeException("输入的类型不一致!");
        }
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Main{
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet();

        treeSet.add(new Person("absd",12));
        treeSet.add(new Person("aasd",13));
        treeSet.add(new Person("adsd",10));

        Iterator iterator = treeSet.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        /**
        *Person{name='aasd', age=13}
		*Person{name='absd', age=12}
		*Person{name='adsd', age=10}
        */
    }
}

 
2.定制排序:

按照添加的规则进行排序,CompareTor接口的实现类对象
TreeSet treeSet = new TreeSet(Comperator comparator)
若添加的 Comparator参数,将按照comparator中的规则对其进行排序;若没有添加该参数,则按照自然排序(继承comparable接口,重写comparaTo()方法中的排序规则) 对其进行排序

public class Main{
    public static void main(String[] args) {
        Comparator comparator = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof Person && o2 instanceof Person){
                    Person person1 = (Person)o1;
                    Person person2 = (Person)o2;
                    return Integer.compare(person1.age,person2.age);
                }else{
                    throw new RuntimeException("添加的数据类型不符合!");
                }
            }
        };

        TreeSet treeSet = new TreeSet(comparator);
        treeSet.add(new Person("a",12));
    }
}

 

6.Map接口

Map: 存放双列数据,存储 Key-value对的数据 — 类似于高中的函数: y=f(x)
 
Map实现类有:
Hashtable以及它的子类Properties,HashMap以及它的子类LinkedHashMap,TreeMap
 
1.Hashtable:作为最先的实现类(1.0);线程安全的,效率低;不能存储 null的key和value【被HashMap(1.2)更替,接而再出现LinkedHashMap(1.4)】
2.HashMap:作为Map的主要实现类;线程不安全的,效率高;可以存储 null的key和value。底层在jdk7之前是:数组+链表,之后是:数组+链表+红黑树
3.LinkedHashMap:在遍历 map元素时,可以按照添加的顺序实现遍历(添加了双向指针,对于频繁的遍历操作,效率高于HashMap)
4.TreeMap:按照添加的key-value进行排序,实现排序遍历。此时考虑key的自然排序和定制排序。底层使用红黑树结构存储。
5.Properties:常用来处理配置文件。key和value都是String类型

 
继承关系图:

在这里插入图片描述
 
Map结构的理解:

1.Map中的key: 无序的、不可重复的,使用 Set存储所有的kye
[ key所在的类要重写 equals()和 hashCode()方法 ],[ HashMap–>HashSet,LinkedHashMap–>LinkedHashSet ]

2.Map中的value:无序的、可重复的,使用Collectin存储所有的value
[ value所在类要重写 equals()方法 ]
3.一个键值对:key-value构成了一个Entry对象.[ 相当于函数( y=|x| ) ]
4.Map中的entry:无序的、不可重复的,使用Set存储所有的 entry对象

在这里插入图片描述
 

6.1 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集合

 

6.2 HashMap的底层实现原理

1.JDK7:数组+链表

1.HashMap map = new HashMap();
在实例化以后,底层创建了长度是16的一维数组Entry[] table.
 
2.map.put(key1,value1) {n次}
首先,通过 key1所在类的 hash(HashCode())方法得到哈希值,再经过算法得到在Entry数组中的存放位置,
若此位置上的数据为空,则此 key1-value1(entry1)添加成功;
若此位置的数据不为空,即此位置存在一个或多个数据(以链表的形势存在),如果 key1的哈希值与已经存在此位置链表中的所有数据哈希值都不相同,则此 key1-value1(entry1)添加成功。若 key1的哈希值与已经存在的某一个数据的哈希值相同,继续比较:
     调用 key1所在类的 equals()方法,比较:如果 equals()返回 false: 此时 key1-value1添加成功。如果 equals()返回true: 使用value1替换相同key的value值。

 
此位置不为空,key1-value1与原来的数据不相同的情况,key1-value1将以链表的方式存
储。若与原来的数据相同,则会替换原来的元素

2.JDK8:数组+链表+红黑树

new HashMap:底层没有创建一个长度为16的数组
JDK 8底层使用的数组是: Node[],而非Entry[]
首次调用put()方法时,底层创建长度为16的数组
当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储(提升查找效率)。

 

6.3 HashMap的源码分析

1.JDK 7:
构造过程:

HashMap map = new HashMap();

// DEFAULT INITIAL CAPACITY: HashMap的默认容量,16;
// DEFAULT LOAD FACTOR: HashMap的默认加载因子;
public HashMap(){
	this(DEFAULT_INITIAL_CAPACITY,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;
	if (loadFactor <=0 Float.isNaN(loadFactor))
		throw new IllegalArgumentException("Illegal load factor:"+loadFactor);
	// Find a power of 2 >initialcapacity
	int capacity 1;
	while (capacity < initialCapacity) // initialcapacity: 16(容量),如果调用是此有参构造器,指定此参数为15时,也会进行向左移一位,只到大于等于16
		capacity <<= 1;
	this.loadFactor = loadFactor; // 加载因子(默认0.75)
	threshold = (int)Math.min(capadity * loadFactor,MAXIMUM_CAPACITY + 1); // 16*0.75 = 12
	// threshold: 临界因子,影响扩容的大小,数组+链表的存储结构,不是达到数组的容量时才扩容的,而是达到一定的容量时进行扩容
	table = new Entry[capacity]; // Entry<K,V>[] table,底层用Entry数组存储
	useAltHashing sun.misc.VM.isBooted()&&
		(capacity >Holder.ALTERNATIVE_HASHING_THRESHOLD);
	init();
}

添加过程:

public V put(K key,V value){
	if (key = null)
		return putForNullKey(value);
	int hash = hashhash(key); // 计算 key的哈希值
	int i=indexFor(hash,table.length); // 拿到 key哈希值,通过一定的算法得到存储的下标
	for (Entry<K,V> e = table[i];e != null;e = e.next){ // 此位置有值的情况下,判断是否相同,如果相同,替换原来的 key上的value值,并在将此旧的value值返回
		Object k;
		if (e.hash =hash &&((k e.key)==keykey.equals(k))){
			V oldValue e.value;
			e.valuevalue;
			e.recordAccess(m:this);
			return oldValue;
		}
	} 
	modCount++;
	addEntry(hash,key,value,i); // 将此键值对添加到Entry数组中(哈希值,key,value,下标i)
	return null;
}

void addEntry(int hash,K key,V value,int bucketIndex){
	if ((size > threshold)&&(null != table[bucketIndex])){ // 在size大于扩容因子,要添加的元素在此位置上不为空的情况下进行扩容
		resize(newCapacity:2 table.length); // 扩容为原来的2倍
		hash = (null !key)?hash(key):0; // 扩容后重新计算此要添加的元素的哈希值
		bucketIndex indexFor(hash,table.length);
	}
	createEntry(hash,key,value,bucketIndex);
}

void createEntry(int hash,K key,V value,int bucketIndex){
	Entry<K,V> e = table[bucketIndex]; // 把原有的那个元素取出来
	table[bucketIndex] = new Entry<>(hash,key,value,e); // 把原来的元素作为新的元素的next出现
	size++;
}

static class Entry<K,V> implements Map.Entry<K,V>
	final K key
	V value;
	Entry<K,V> = next;
	int hash;
	// Creates new entry.
	Entry(int h,Kk,VV,Entry<K,V>n){
		value V;
		next n;
		key k;
		hash h;
	}
}

 
2.JDK 8:
构造过程:

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

添加过程:

public V put(K key, V value) {
	return putVal(hash(key), key, value, false, true); // hash(key)方法拿到 key的哈希值
}
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; // 首次调用 table末初始化,为null;resize()方法创建了容量为16的数组(同时此方法也是进行扩容的方法)
        if ((p = tab[i = (n - 1) & hash]) == null) // 判断要添加的此位置上是否有元素,若没有元素直接添加
            tab[i] = newNode(hash, key, value, null);
        else { // 否则存在元素
            Node<K,V> e; K k;
            if (p.hash == hash && // 哈希值相等的情况下
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p; // 与此位置元素相等的情况下,取此位置哈希值赋于e,再于后面替换此位置的元素,
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        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;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY; // 首次创建时到达这里,newCap = 16,newThr = 12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        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]; // 创建容量为16的数组
        table = newTab;
        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)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    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;
                            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) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab; // 返回创建(新)的数组
    }
final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize(); // 当表不为空时或者长度小于MIN_TREEIFY_CAPACITY(64)时不进行转化红黑树
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

DEFAULT_INITIAL_CAPACITY: HashMap的默认容量,16
DEFAULT_L0 AD_FACTOR: HashMap的默认加载因子:B.75
threshold: 扩容的临界值,= 容量*填充因子:16 * 0.75=>12

TREEIFY THRESHOLD: Bucket中连表长度大于核款认值,转化为红黑柄:8
MINTREEIFY CAPACITY: 桶中Node被树化时最小hash表容量:64

6.4 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);
        }
    }
public class Main{
    public static void main(String[] args) {
        Map map = new LinkedHashMap();
        map.put("12",123);
        map.put("14",122);
        map.put("10",1);

        System.out.println(map); // {12=123, 14=122, 10=1}
    }
}

 

6.5 TreeMap

TreeMap中是按照 key进行排序的,要求 key必须是同一个类的对象(自然排序、定制排序)。实现方式与 TreeSet相同

 

6.6 Properties

Properties是Hashtable的子类,该对象用于处理属性文件
由于属性文件里的 key、value都是字符串类型,所以 Properties中的 key、value都是 String类型

 

7.Collections工具类

Collections 是一个操作 Set、List和 Map的等集合的工具类
Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
 
排序操作(均为static方法):
      reverse(List): 反转List中元素的顺序
      shuffle(List): 对List集合元素进行随机排序
      sort(List): 根据元素的自然顺序对指定List集合元素按升序排序
      sort(List,Comparator): 根据指定的Comparator产生的顺序对List集合元素进行排序
      swap(List,int,int): 将指定list集合中的i处元素和j处元素进行交换

 
查找、替换:
      Object max(Collection): 根据元素的自然顺序,返回给定集合中的最大元素
      Object max(Collection,Comparator): 根据Comparator指定的顺序,返回给定集合中的最大元素
      Object min(Collection)
      Object min(Collection,Comparator)
      int frequency(Collection,Object): 返回指定集合中指定元素的出现次数
      void copy(List dest,List src): 将src中的内容复制到dest中
      boolean replaceAll(List list,Object oldVal,.Object newVal)): 使用新值替换 List对象的所有旧值

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

り澄忆秋、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值