Java集合-List、Set、Map等详解

一、集合框架体系

集合的理解与好处

 -数组扩容麻烦,保存的数据往往为同一类型

 -集合可以动态保存任何多个对象,使用方便

 -集合提供了一系列方便的操作对象和方法:add、remove、set、get等

Java的集合类很多 主要分为Collection单列集合 和Map双列集合(键值对)

Collection 是一个接口 继承了Iterable接口

Collection下有两个重要接口 – List    -Set 其中有许多重要的实现方法

Collection 实现类的特点

Collection接口遍历元素方式1-使用Iterator迭代器

 -Iterator对象称为迭代器,主要用于遍历Collection集合中的元素

 -所有实现了Collection接口的集合类都有一个iterator()方法(这个方法是由Collection父接口Iterable而来),用于返回一个实现了Iterator接口的对象,即可以返回一个迭代器

 -Iterator结构:

      

 -Iterator仅用于遍历集合,本身并不存放对象

迭代器执行原理

调用next()方法时,必须先调用hasNext()判断,不然会抛异常

迭代器案例:(book是一个自写类)

这里iterator的while循环可以用快捷键itit自动生成

遍历完成后的iterator迭代器指向最后一个元素

使用语句iterator  = col.iterator()可以将指针再次置于集合首部

Collection接口遍历元素方式2-使用增强for循环

 -增强for循环其实就是简化版的迭代器,本质一样,可以代替迭代器,但只能用于遍历Collection集合或数组

 -基本语法为:for(元素类型 元素名 :集合名或数组名){ 访问元素 }

 -增强for循环的底层仍然是迭代器(会直接调用iterator()),用它主要是它写起来比迭代器简便

增强for循环案例:

Map是另一种重要的集合接口 其中也有几种重要的实现方法

具体内容在下面Map环节介绍

 

二、Collection – List

1、List接口

List接口基本介绍

 -List集合类中的元素有序(即太你家顺序和取出顺序一致)、且可重复

 -List集合中每个元素都有其对应的顺序索引,即支持索引

 -List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素

 -JDK API中List中有许多实现类,常用的有ArrayList  LinkedList  Vector

List接口的常用方法

 -void add(int index , Object ele)在index索引插入元素

 -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的遍历方式除了上面提到的迭代器和增强for循环,还有一种普通for循环,因为list接口里的实现类底层都是数组,可以这么遍历

2、ArrayList

ArrayList的一些小知识

-ArrayList可以放空值也就是null,并且可以加入多个

-ArrayList是由数组实现数据存储

-ArrayList基本等同于Vector,只是ArrayList是线程不安全的,但ArrayList执行效率高,在多线程情况下不建议使用ArrayList

ArrayList底层结构与源码

 -ArrayList中维护了一个Object类型的数组

 -当创建ArrayList对象时,如果使用的是无参构造器,则初始数组的容量为0,第一次添加则扩容为10,如果需要再次扩容,则扩容后的数组容量为1.5倍

 -如果构造指定参数容量,则扩容时为原容量的1.5倍

如果向了解更多,可以去查看ArrayList的底层源码

 

3、Vector

Vector基本介绍:

--Vector是在JDK1.0时出现 , 而ArrayList是在JDK1.2

 -Vector的操作与ArrayList基本一致

 -Vector的底层也是一个Object类型的数组

 -Vector是线程同步的,也就是线程安全的,Vector类的操作方法带有synchronized

 -由于加了线程锁,Vector的效率不如ArrayList

 -初始化与扩容机制类似ArrayList,无参构造的初始容量为10,不过每次扩容都是2倍扩

Ps(底层源码中有个capacityIncrement变量定义扩容大小,默认为0,如果使用者不指定,就默认扩容为oldCapacity,也就是两倍扩)

扩容的核心源码:

4、LinkedList

LinkedList的介绍:

 -LinkedList底层实现了双向链表和双端队列特点

 -可以添加任意重复元素,如null

 -线程不安全,没有实现线程同步

 -LinkedList中维护了两个属性first 和last分别指向首节点和尾节点,还有一个size属性记录长度

 -每个节点(Node对象)里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,在雨中实现双向链表

-使用LinkedList元素的添加与删除不是通过数组完成,效率较高

-linkedlist.remove()默认删除第一个

底层源码运行刨析:

再添加一个元素时:

List.remove()的核心源码:

先走removeFirst:

再走unlinkFirst将f指向的双向链表的第一个节点拿掉

讨论ArrayList和LinkedList比较

 -ArrayList底层结构是可变数组,增删的效率低,改查的效率高(数组特性)

 -LinkedList底层结构是双向链表,增删效率高,改查效率低

 -一般来说,在程序中80-90%都是查询,大部分情况用ArrayList

三、Collection – Set

1.Set接口

Set基本介绍:

 -添加到里面的元素无序,没有索引

 -取出的顺序是固定的,不会每次取的时候顺序都不一样

 -不允许重复元素,所以最多包含一个null

 -JDK中实现Set的类有:HashSet LinkedSet TreeSet 等等

 -和List接口一样,Set接口也是Collection的子接口,因此常用方法和Collection一样

Set接口的遍历方式:

 -可以使用迭代器

 -可以使用增强for循环

 -不能使用索引的方式获取

2.HashSet

HashSet介绍:

 -HashSet实现了Set接口

 -HashSet的底层实际上是HashMap

 -可以存放null值,但只可存放一个

 -HashSet不保证元素有序,取决于hash后再确定索引结果

 -不能有重复对象

HashSet注意的点:

-HashSet在执行add方法时返回一个布尔值,代表是否添加成功

-在HashSet里面加入属性一样的其它类的对象时是可以的,因为这两个对象不算一个对象

-在HashSet里面加入两次一样的String对象是不行的这里取决于源码

这是一道经典面试题

HashSet底层说明,分析HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)(拉链法解决Hash冲突)

如上图所示数组+链表(当链表的容量大于一个值时,JDK会将其变成一个红黑树) 存储效率会非常高

源码说明:

 -HashSet底层是HashMap

 -添加一个元素时,先得到hash值,会转成索引值

 -找到存储数据表table,看这个索引位置是否已经存放有元素

 -如果没有,直接加入

 -如果有,调用equals进行比较,如果相同,就放弃添加,如果不同,则进行table位置元素链表的下一位进行比较,一直比较到最后一位,都不同就添加到链表末尾(前面问题的核心就在这里,如果是相同的字符串,equals会认为它相同,而属性相同的类的equals不会相同)

 -在JDK8中如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8)并且table的大小超过MIN_TREEIFY_CAPACITY(默认64),就会进行红黑树化,如果table的大小没有超过这个值,就先扩table表,默认是按两倍。

根据putval()函数分析,如果这个put方法返回的null,则代表元素插入成功,如果不是空,就代表应放入的位置已经存在其它元素,返回会是这个元素。 


这里去看hash函数得到的hash值可以发现,hash值明显不等于hashCode(),而是hashCode()值的一个右移16位异或运算

执行putval(重要)

table是放节点的数组

当table一开始容量为空时,容量会被初始化为16,当用了12个空间就会开始扩容resize()方法(会有0.75的缓冲加载因子,也可以说是4个空间的缓冲,因为怕突然有很多元素添加)扩容后table容量变为32,则扩容临界值就会变为24,依次类推

当table一开始容量为空时,容量会被初始化为16,当用了12个空间就会开始扩容resize()方法(会有0.75的缓冲加载因子,也可以说是4个空间的缓冲,因为怕突然有很多元素添加)扩容后table容量变为32,则扩容临界值就会变为24,依次类推

putVal()源码注释详解

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //当加入第一个元素时,map中并没有key-value,此时table为null
		//此时就将table初始化为容量为16的Node[]数组
        if ((tab = table) == null || (n = tab.length) == 0)
        	//使用resize函数生成tab[]数组
            n = (tab = resize()).length;
        //(n - 1) & hash 相当于对将hash % n,根据hash得到table的索引
//根据key计算得到的hash值,计算该key应该存放到table表的那个索引位置,
//并且把这个位置的对象赋给p
//再判断p是否为null
//如果p为空,表示还没有存放元素,就创建一个Node(key = 创建的 ,value = PRESENT)
//就放在该位置 tab[i] = newNode(hash, key, value, null);null代表后面还没有链其它元素 ,hash用于继续跟以后相同位置的节点比较
//这里的p指向当前table位置的元素
        if ((p = tab[i = (n - 1) & hash]) == null)
        //当table内对应位置没有元素的时候,实例化一个元素作为该位置的第一个元素
            tab[i] = newNode(hash, key, value, null);
        else {//当table内有元素时候,就需要解决hash冲突问题了
            Node<K,V> e; K k;
//table内的第一个元素的hash值与新加入key-value的hash相同的时候并且满足(加入的key与p指向的Node节点的key一样时)或(p指向的Node节点的key的equals()和准备加入的key执行比较后相同),就表示不能往里面添加了
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
			//再判断p是不是一棵红黑树,如果是,就用红黑树进行比较
            else if (p instanceof TreeNode)
//如果此时table内已经树化,使用putTreeVal方法加入元素,若存在相同的key的元素,则将引用返回
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
//如果table内是链表,则对链表后每一个元素进行比较,都不同就插入链表,这里使用尾插法 
//再把元素添加到链表后,立即判断该链表是否有8个节点,如果达到就调用treeifyBin()对当前这个链表进行树化转成红黑树,在进行树化时,还进行一个判断,如果table容量小于64,则会先对table进行扩容,这个判断扩容机制包含在treeifyBin()函数中
                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
                        	//bitCount大于树化的阈值,转化为红黑树
                            treeifyBin(tab, hash);
                        break;
                    }
//table内存在元素的key值与新加入key-value的key相同的时候,用e指向p(仅仅指向)
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)));
                        break;
                    p = e;
                }
            }
            //通过上面的code,若map中已经存在相同的key,
            //我们则将Node<K,V> e指向该key-value,即Node(TreeNode是Node的子类)
            //若e == null,则说明已经插入成功
            //若e != null, 则将e.value作为旧值返回,将e.value设置为value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                //putVal中的参数。若onlyIfAbsent为null或者oldValue为空时才替换,
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;//代表插入次数
        //插入后大于阈值,使用resize()进行调整(一开始为12,也就是)
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);//对于HashMap这是一个空方法,是给LinkedHashMap的等其它子类实现的方法,比如实现链表有序等等
        return null;
    }

总结:

 第一次添加HashSet添加元素:

 -创建HashMap ,执行add方法,执行put方法,执行putval方法

 -在putval方法内,创建一个table,初始化容量为16,计算元素hash值

 -在table对应位置添加新节点,保存hash,key,value,null值,hash用于比对,null代表后面没有链上其它元素

 -modCount++,表示插入次数+1,size++表示容量用了一个

 -判断size是否大于12(16*0.75),否,不执行扩容

 -put和add返回空,表示插入成功。

 第二次添加元素(不与第一次冲突):流程与第一次基本一样,插入后modCount++,size++

 第三次添加元素(与第一次冲突):

 -前几步与第一次一样,当进行到putval()函数时,新建一个Node对象存放需要存放的键值对,计算其hash值。

 -p指向hash值对应的table位置本来含有的元素,利用p对新加入的元素进行比对

 -通过equals函数得到两个字符串相同,则直接跳出,并将字符串作为返回值返回给put与add方法

 -代表加入失败

….

 第n次加入元素:会涉及到链表添加元素,和链表红黑树化以及table扩容问题,这里的红黑树化是用方法replacementTreeNode对指针p进行树化,已经细节已经在代码注释中给出。

总的来说,Java的哈希这一套机制是拉链法解决hash冲突典型代表。

                          

前面的HashSet相同字符串不会添加,而相同属性的两个对象会添加的原因就在于比对过程中的equals方法,在String类中重写的equals方法比对内容相同的字符串会去比对两个字符串在jvm中的内存地址是否相同,HashSet在添加String类型的元素时就调用String中重写的equals方法,所以就会导致String类型的重复元素无法添加,自定义类的对象的可以,所以在使用HashSet添加自定义类型的对象时就应该重写equals方法和HashCode()方法,(关于这里为什么要同时重写equals和HashCode()方法,在我发布的之前的博文中有详细介绍。)

重写HashCode()和equals方法一般要写在要放入HashSet的类中

这里给出一个例子:

 -写一个Employee类,有属性name和age重写equals()和HashCode()方法使这个类只要在name和age相同时就返回一样的HashCode

import java.util.HashSet;
import java.util.Objects;

public class MyHashSet {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();

        hashSet.add(new Employee("孙悟空",500));
        hashSet.add(new Employee("唐僧",1000));
        hashSet.add(new Employee("孙悟空",500));

        System.out.println("HashSet表:"+hashSet);

    }

}

class Employee{
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

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

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

    //如果name和age的值相同则返回相同的HashCode()
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age && Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {//HashCode取决于名字和年龄
        return Objects.hash(name, age);
    }
}

运行结果:

可以见到:孙悟空这个对象并没有添加两个,说明重写的equals方法和HashCode方法有效

在重写类的equals和HashCode对象时一般都用快捷键Alt+insert,选中equals和hashCode

表示用java8之后的写法

表示只要name和age一样就返回一样的hashCode

表示只要name和age一样,equals方法就认为一样

如果追进hashCode方法:

这里是一套成熟的数学公式,主要是防止不同对象的碰撞,这里不做过多讨论

最后注意:这一切的HashSet集合的动作都是建立在Java8的基础之上

3.LinkedHashSet

LinkedHashSet介绍:

 -LinkedHashSet是HashSet的子类

 -LinkedHashSet底层是一个LinkedHashMap,维护了一个数组+双向链表

 -LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使链表维护元素的次序,这使得元素看起来是以插入顺序保存的

 -LinkedHashSet不允许添加重复元素

底层机制:

-LinkedHashSet底层维护的一个LinkedHashMap(是HashMap子类)

 -LinkedHashMap底层是一个table表和双向链表保证元素的加入与取出顺序一致

 -添加第一次时,直接将数组table扩容到16,存放的节点类型是LinkedHashMap$Entry

 -数组是HashMap$Node[]存放是元素/数据是LinkedHashMap$Entry,说明这里后者肯定是前者的一个子类,这里就是数组多态的一个体现,子类对象可以存放到父类数组里面去

 -Entry里面才真正存放了before/after属性,也就是链表双向属性,Entry对HashMap的继承关系在内部类完成,是一个静态内部类,因为可以直接通过HashMap.Node访问。

      这里内部类的各种知识可参照:

(2条消息) 详解 Java 内部类___Hiro__的博客-CSDN博客icon-default.png?t=M666https://blog.csdn.net/Hacker_ZhiDian/article/details/82193100?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165777303416782390517441%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165777303416782390517441&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-82193100-null-null.142%5ev32%5epc_rank_34,185%5ev2%5econtrol&utm_term=%E5%86%85%E9%83%A8%E7%B1%BB&spm=1018.2226.3001.4187

 -在添加一样的元素时,进入到与之前HashSet一样的HashMap里面的add和put方法里面去然后进入到putval方法,与之前一样

LinkedHashSet想让相同属性的自写类不能加入与之前的HashSet一样,步骤完全一样

可以看到,这里取出的顺序与加入的顺序完全一致

4.TreeSet

TreeSet介绍:

 -当我们使用无参构造器创建TreeSet时,仍然是无序的

 -使用TreeSet的一个构造器,可以传入一个比较器Comperator(匿名内部类),指定排序规则。

 

例子:

源码解读:

 -构造器把传入的比较器对象,赋给了TreeMap的Comparator属性

 -添加元素调用TreeSet.add方法会执行到cpr,cpr就是一个匿名内部类的对象,最后使用的compare方法就是我们在定义TreeSet时传入的compare方法

 -当传入TreeSet一样的对象时,compare函数会返回0,key会覆盖,也就是相同对象不能加入TreeMap.

上一个例子如果按字符串长度来排序key值,compare函数的写法:

这里如果把比较方法改成这样,那“abc”也无法加入,因为abc的长度为3与“tom”一致,会导致compare方法返回0而无法加入TreeMap

四、Map

1.Map接口

Ps:Map的元素是由键值对构成,前面提到的HashSet底层也是Map元素,但不是键值对,是因为在使用HashSet时只只用了它的key,而value被一个静态常量present所代替

Map(JDK8)介绍:

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

 -Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中

 -Map中的key不允许重复,原因和HashSet一样

 -Map中的value可以重复

 -Map中的key可以为null,value也可以为null,key只能有一个,但value可以有多个null

 -Map中如果添加了key相同value不相同的元素,则会用后添加的value替换之前的value

 -常用String类型作为Map的key比较多

 -key和value之间存在单一映射一对一关系,即通过key总能找到对应的value

 -Map存放数据的key-value是放在一个Node中,因为Node实现了Entry接口,有些书上说一对key-value就是一个Entry(这不严谨)

难点:有些书上说key是存放在一个set中,value是存放在一个Collection中,实际上其实key和value还是存放在HashMap$Node中,set中存放的是key的引用,Collection中存放的也是value的引用。这里是为了使用者方便地遍历value和key数据,java提供了一个set和一个Collection把其引用做成key-value的引用做成一组对象放在Entry里面,再将Entry放在EntrySet集合里面。

简易来说就是:

 -k-v最后是由语句HashMap$Node node = newNode(hash , key , value , null)创建与存储

 -k-v为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素类型是Entry,而一个Entry对象就有k-v 也就是EntrySet<Entry<K,V>>;

从源码来分析这个问题:

这里Entry存放在一个set中set的类型是EntrySet中,而EntrySet又是HashMap的一个内部类

在entrySet定义的类型是Map.Entry,但实际上存放的还是由HashMap$Node类型转成的Entry

这里我们获取set的类型是HashMap$EntrySet , 获取entry的类型却是HashMap$Node

为什么可以这样做:因为源码中HashMap$Node实现了Entry接口:

从table表里取出的是HashMap$Node类型

从entrySet集合里取出的是HashMapEntry类型:

这里两者一一对应,且如果从地址角度来看,是由entrySet里面的元素指向table里面的元素,并没有创造一个新的数据:

做以上这些操作的根本原因还是因为java设计者认为把HashMap$Node对象存放到entrySet就方便我们遍历,因为Map.Entry提供了getKey()和getValue(),可以让我们单独地取出table里的key对象

从HashMap$Node中分别取出k和v:

这里分别取出的key和value的运行类型分别是HashMap$KeySet和HashMap$Values都是HashMap的内部类(它们又是继承是Set和Collection的抽象类)

Map接口常用方法:

 -put:添加

 -remove:根据键来删除映射

 -get:根据键来获得其值

 -size:获取元素个数

 -isEmpty:判断个数是否为0

 -clear:清除

 -containsKey:查找键是否存在

运行结果无序

Map的遍历方式:(重要方法:

-keySet:获取所有的键

 -entrySet:获取所有关系k-v

 -values:获取所有的值)

1、先取出所有的key,通过key取出所有的value

 -增强for循环:

-迭代器:

2、直接取出所有的value

-增强for循环:

-迭代器

3、通过EntrySet来获取k-v

-增强for循环:

这里从entrySet里取出来的是HashCode$Node类型,将其强转成Entry再使用getKey和getValue。

-迭代器

2.HashMap

HashMap介绍:

 -Map接口常用实现类:HashMap   Hashtable  Properties

 -HashMap是Map接口使用频率最高的实现类

 -HashMap是以k-v对来存储数据(HashMap$Node类型)

 -key不能重复,value可以,可以是null

 -如果添加相同的key,会覆盖原来的value,等同于替换key的value(具体怎么替换,可以参考之前HashSet里面提到的源码putVal方法)

 -不保证映射顺序,因为底层是hash表的方式存储

 -HashMap没有实现同步,因此线程不安全

 -底层是数组+链表+红黑树,具体细节看之前的源码解读

 

由于HashSet底层是HashMap,所以这里HashMap特性很多上篇都有:

 -HashMap底层维护了Node类型的数组table,默认为null

 -当创建对象时,加载因子(laodfactor)初始化为0.75

 -当添加key-val时,通过key的哈希值得到table的索引。然后判断改索引处是否又元素,没有元素直接添加,有元素则继续判断该元素的key和准备加入的key是否相等,如果相等,则直接替换val,如果不相等则判断是树结构还是链表结构,做出相应处理,如果添加时发现容量不够,则需扩容

 -第一次添加,初始化table的容量为16,临界扩容值为12

 -以后再次扩容,则需要扩容table容量为之前的2倍,临界值依然为最大值乘以加载因子

 -在java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认为8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认为64),就会进行树化(红黑树)

3.Hashtable

Hashtable介绍:

 -存放的元素是键值对:k-v

 -hashTable使用的方法基本和HashMap一样

 -hashTable是线程安全的

 -继承了dictionary类,实现了Map接口

 -hashTable的键与值都不能放null,会抛空指针异常

 -与HashMap一样,加入key一样的元素时,会替换其value

Hashtable底层结构源码:

 -底层有数组Hashtable$Entry[],初始化为11

 -有一个临界值threshold  8 = 11*0.75,table内元素大于此值会进行扩容(默认不是两倍扩容,有其自己的扩容机制[乘以2+1]),第一次扩容后容量会变成23,临界值变成17

 -方法addEntry(hash , key , value , index);添加k-v封装到Entry

底层put方法有synchronized锁,线程安全

不允许null的加入:

扩容机制:

与HashMap的一点区别:

4. ConcurrentHashMap

ConcurrentHashMap与HashMap、Hashtable对比:

 -在并发环境下,HashMap线程不安全,可能会形成环状链表(扩容时造成)会导致get操作时cpu空转,在并发环境中使用是非常危险的

 -Hashtable与HashMap几乎一样,但Hashtable不允许键值为null

 -Hashtable是线程安全的,但实现代价比较大,get/put所有相关方法都是synchronized的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞。

 -ConcurrentHashMap是线程安全的,每把锁只锁一段数据,也就是分段锁,当不同线程访问不同数据时,不会存在锁的竞争,对比Hashtable效率有不小的提升

ConcurreHashMap更多源码与细致思想,详见博客:

(2条消息) ConcurrentHashMap实现原理及源码分析_快乐小石头的博客-CSDN博客_concurrenthashmap实现原理icon-default.png?t=M666https://blog.csdn.net/weixin_43185598/article/details/87938882?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165777939316782246457797%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165777939316782246457797&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-87938882-null-null.142%5ev32%5epc_rank_34,185%5ev2%5econtrol&utm_term=concurrenthashmap%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86&spm=1018.2226.3001.4187

5.LinkedHashMap

参考之前的LinkedHashSet,因为LinkedHashSet底层就是LinkedHashMap,底层是数组+双向链表保证插入与取出的顺序一致

 

6.TreeMap

前面提到TreeSet的底层就是TreeMap,TreeSet初始化时会将Map的value初始化成一个静态不变参数present,所以TreeMap的许多特性与TreeSet基本一致

TreeMap简易介绍:

 -用默认构造器创建TreeMap时,元素无序

 -TreeMap构造器提供了一个Comparator的匿名内部类,可以重写compare方法来自定义key值排序

按key(字符串)字母顺序从小到大排序例子:

按字符串长度大小从小到大进行排序:

源码内容与之前的TreeSet一样。

7.Properties

Properties介绍:

 -Properties类继承自Hashtable类并且实现了Map接口,也是一种键值对的形式来保存数据

 -使用特点与Hashtable类似

 -Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改

 -xxx.properties文件通常作为配置文件

 -Porpreties也不能添加null

 -也是经过hash存表,也是无序

具体使用方法在我之前的JavaI/O流中有详细案例

五、Collections

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对象所有旧值

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值