Java集合框架

集合概述

集合框架图

在这里插入图片描述

1.Collection接口

1.1Collection集合框架图

在这里插入图片描述

1.2Collection常用方法详解

1.2.1基本方法

add

向集合中添加一个任意类型的元素(未指定泛型) 如果指定泛型只能添加指定的数据
返回boolean 表示是否添加成功

public boolean add(E e)      
size

返回集合中的元素个数(注意不是集合的长度)

int size();
addAll

向一个集合中添加另一个集合中的全部数据

boolean addAll(Collection<? extends E> c);
isEmpty

判断集合中是否有元素 本质就是判断元素个数是否是0

public boolean isEmpty() {
    return size == 0;
}
clear

将集合中的元素全部清空

void clear();
contains

判断集合中是否存在某一元素 会挨个进行比较,如果传入的是对象的话,比较的是对象的equals()方法,一般都会重写equals方法比较内容,默认是比较地址(Object里定义的)

boolean contains(Object o);
containsAll

传入一个集合,判断传入集合中的数据是否在本集合中全部存在

boolean containsAll(Collection<?> c);
remove

移除集合中的某一元素

boolean remove(Object o);
removeAll

移除本集合中与传入集合中相同的元素

boolean removeAll(Collection<?> c);
retainAll

找到本集合与传入集合中相同的元素,并返回给当前集合

A.retainAll(B) 找到A集合与B集合的交集,并把交集的值设置给A集合

 boolean retainAll(Collection<?> c);
equals

(重写过的) 判断两个集合的元素是否相同,如果是List的话要求元素的顺序也必须相同,因为List集合的特点是有序可重复,如果是Set的话不要求顺序一致,Set集合的特点无序不可重复

boolean equals(Object o);
toArray()

将集合转换为一个Object类型的数组

Object[] toArray();
Arrays.asList

这个方法是将数组转换为集合

1.2.2迭代器Iterator

  • 是Collection实现的Iterable接口中的iterator()方法,可以自定义
  • Iterator一个接口,就是集合的一个迭代器,本质就像指针一样,当调用iterator()方法产生一个迭代器时,指针指向集合中第一个元素的前面,先判断下一个位置是否有元素,有元素指针下移,取出数据,没有数据直接结束.
Collection collection = new ArrayList();
collection.add("1");
collection.add(2);
collection.add(3);

	//注意: 每次调iterator()方法都会返回一个新的迭代器(指针)
 Iterator iterator = collection.iterator();

//遍历
while(iterator.hasNext()){
  Object obj = iterator.next(); //如果指定泛型的话直接就是确定的类型,没有指定泛型就是  Object类型
}


//remove方法 在遍历的时候删除元素
   iterator = collection.iterator(); //申请一个新的迭代器 
    while (iterator.hasNext()){
//   iterator.remove(); //异常 指针还没指向数据 没有数据可删
     iterator.next(); //指针下移
     iterator.remove();
//   iterator.remove(); //异常 删了两次
}

1.2.3增强循环

  • 底层还是迭代器
  • 数组也可以使用增强循环遍历
while(iterator.hasNext()){
    //迭代器只能用来遍历 不能对数据进行操作 因为只是取出数据
   Object obj = iterator.next(); 
    System.out.println(obj);    
}

for(Obj obj : Objs){ //这一步操作相当于调用迭代器的next()方法取出值后赋给一个新的变量然后去操作新变量  所以说增强for循环里面对数据的操作不会改变原有数据 
 System.out.println (obj);     
}

1.3List接口(JDK1.2)

1.3.1List概述

  • List存储有序 可重复的数据,典型的实现有ArrayList LinkedList Vector
ArrayList/LinkedList/Vector三者的异同
  • 同:都实现了List接口,存储的数据特点都是 有序 可重复的 可以存储null值

  • 1、ArrayList:JDK1.2 线程不安全,效率高,底层使用Object[] (elementData)存储数据

    2、LinkedList: JDK1.2 底层是双向链表频繁的增删效率比ArrayList高 查找效率低

    3、Vector: JDK1.0 (古老的集合)使用极少 线程安全效率低 底层使用Object[]存储数据

1.3.1/5List接口中常用方法概述

List除了从Collection集合继承的方法外,还添加了一些根据索引来操作集合的方法

add addAll

要求插入的索引位置前面必须有元素,并且不能在数据为null的位置上插入数据


void add(int index, E element); //在指定的位置插入元素 后面元素后移
boolean addAll(Collection<? extends E> c); //从指定的位置开始插入元素

-----//测试代码
List list = new ArrayList();
list.add(0, "sd");
list.add(1, 123123); //如果没有上面的 add(0,xx) 出异常,要插入的元素的索引位置必须小于等于集合中的元素个数 下面是源码
         //    if (index > size || index < 0)
         //  throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

list.add(2, 989292);
List testList = new ArrayList();
testList.add(2);
testList.add(2);
list.addAll(2,testList);
System.out.println(list);

//输出 
[sd, 123123, 2, 2, 989292]

get

获取指定位置的元素 要求传入的索引位置上必须有元素

E get(int index);
indexOf

获取某个元素在集合首次出现的位置

int indexOf(Object o);
lastIndexOf

获取某个元素在集合中最后一次出现的位置

int lastIndexOf(Object o);
remove

移除指定索引处的元素 返回这个元素

Object remove(int index) 
set

替换指定索引位置上的元素为ele,要求插入的位置上原先必须有元素

Object set(int index,Object ele)
subList

返回一个区间中的数据 左闭右开 不改变原有集合

List subList(int fromIndex,int toIndex)

常用方法

  • 增:add(Object obj)
  • 删 : remove(int index)/remove(Object obj)
  • 改 : set(int index,Object ele)
  • 查 : get(int index)
  • 插 : add(int index)
  • 长度 : size()
  • 清空: clear()
  • 遍历: 1.迭代器 2.增强for循环 3.普通循环

1.3.2ArrayList

1.3.2.1源码分析
JDK7:
  • 当调用无参构造器时,底层就造了一个长度为10的Object数组
  • 扩容:

类似于StringBuffer/SringBuilder,每次添加元素都会先判断容量足够,不足的话就去扩容,默认第一次扩容为原长度的1.5倍,如果长度还不够,直接把你需要的长度给你,但是要判断你需要的长度是不是比规定的最大长度都大,最大长度默认是Integer.MAX_VALUE - 8,如果所需长度比最大长度大再次进行判断,判断你的长度是不是比Integer的最大值都大,true的话直接抛出OOM,如果比最大长度大但是没有超出Integer的最大长度,就把规定的最大长度给你,长度确定后就调用Arrays.copyOf方法将原数组中的内容赋值到新数组中

MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
JDK8:

跟JDK7不同的的一点就是8在创建ArrayList对象时长度初始为0,近似与延迟加载

  • ArrayList list = new ArrayList(); 
    //调用空参的构造器默认的长度是0 当添加数据时长度赋为10
    
    ArrayList list = new ArrayList(123); 
    //调用带参的构造器底层会造一个指定长度的数组
    
    size()方法返回的是集合中元素的个数 
    

    只有向集合里面添加数据时才会扩容为10,扩容方面跟JDK7相同

扩容流程图

https://www.processon.com/diagraming/60966631e0b34d254ceaeff5

1.3.3LinkedList

1.3.3.1源码分析

底层是双向链表 Node节点存储数据

增删基本都是链表的基本操作

//私有的静态内部类
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;
    }
}

1.3.4Vector

1.3.4.1源码分析

JDK7和8通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为2倍

1.4.Set接口

1.4.1Set概述

  • set存储 无序 不可重复的数据,典型的实现有 HashSet LinkedHashSet TreeSet
  • TreeSet不能存储null值,HashSet和LinkedHashSet可以存储null值,但只能存一个(不可重复性)
  • set接口是Collection的子接口,set接口中没有提供具体额外的方法 都是Collection接口中的方法
  • set集合不允许包含相同的元素,如果使着把两个相同的元素加入同一个Set集合中,会添加失败
  • set判断两个对象是否相同不是使用==运算符,而是根据equals方法
  • 本质底层new了一个hashMap使用的就是Map中的key那一列
  • set在查找 删除 时都是先按照元素的哈希值计算出的索引位置去找,而不会去一个个遍历比较 因为每次扩容后元素位置会发生变化,所以在查找时必须重新计算哈希值得到元素的索引位置

无序跟不可重复的含义

  • 无序性 不等于随机性 存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值计算出在数组中的位置存放
  • 不可重复性 保证添加的对象按照equals方法判断时不能返回true,即相同的元素只能有一个

hashCode()方法

如果一个类没有重写hashCode方法,默认使用的是Object类中的hashCode(),是一个native方法,本质是就是计算出对象在内存中的存储位置,也就是说,即使是同一个类的不同对象(即使属性值都是一样的)计算出的hashCode也不一样,那么如果在set集合中添加数据时就有了大问题,比如我Person类的两个对象,属性值完全一样,但是这俩对象的hashCode不一样,那么在往set集合中添加时直接就可以添加成功,也就不用比equals了,但是这俩对象属性是一样的,就保证不了不可重复性

所以说,我们想往set集合中添加数据,必须重写hashCode,保证一个类属性值相同的对象的哈希值是一样的,那么我们就可以比较equals了,那么一样的对象只会添加一个,也就保证了不可重复性

添加元素的过程(以HashSet为例)

1、我们向HashSet中添加一个元素a时,首先调用元素所在类的hashCode()方法,计算元素a的哈希值

2、通过元素a的哈希值再次通过某种算法计算出a在HashSet中的存储位置(即为索引位置)

3、判断该位置上是否已经有元素

3/1 ——>如果该位置上没有其他元素,直接存储a (存储情况一)

3/2 ——>如果该位置上已经有其他元素如b(可能不止一个),就比较元素a和b的哈希值

4、判断a和b的哈希值是否相同

4/1 ——>如果a和b的哈希值不同,那么a也添加成功(以链表的形式存储)(存储情况二)

4/2 ——>如果a和b的哈希值相同,就比较a和b的equals方法

5、判断a和b的equals结果是否相同

5/1 ——>如果a和b的equals结果是true,说明a和b是一个元素,a插入失败

5/2 ——>如果结果是false,就插入a(链表形式)(存储情况三)

对于存储成功的情况二和三而言,元素a是和已存在的元素以链表的方式存储

JDK7中:元素a放到数组中,指向原来的元素

JDK8中:原先的元素不动,指向元素a

要求

1、向Set中添加数据,其所在的类一定要重写hashCode()和equals()方法

2、重写的hashCode()和equals方法尽可能保持一致:相等的对象必须具有相同的散列码

注:String已经重写过了这两个方法,往set集合里添加时直接搂就完了.

1.4.2HashSet(最常用)

1.4.2.1概述
  • 底层数据结构也是数组+链表(JDK7)JDK8见HashMap 默认长度为16

  • 数据无序 不可重复 线程不安全 可以存储null值

1.4.3LinkedHashSet

1.4.3.1概述
  • 继承于HashSet

  • 底层数据结构也是数组+;链表 默认长度为16

  • 遍历其内部数据时可以按照数据的添加顺序遍历(顺序的存储顺序无序)

    原因是:在添加数据时,每个数据还维护了两个引用,记录了这个数据添加的前一个数据和后一个数据,目的就是为了在遍历时可以按照添加顺序遍历 对于频繁的遍历操作效率高于HashSet

1.4.4TreeSet

1.4.4.1概述
  • 跟HashSet和LinkedHashSet不一样,TreeSet只能存储同一个类型的值(天生自带泛型)因为可以按照对象的指定属性排序

  • 底层数据结构是红黑树(二叉排序树)

  • 可以按照添加对象的指定属性进行排序(默认使用的是类重写的(自然排序)Comparable接口的方法)

    可以定制排序使用 Comparator接口

  • TreeSet判断元素是否相同比较的不是equals(不使用hashCode),而是按照你实现Comaprable接口重写的compareTo方法判断或者是传入的Comparator来判断,返回0说明相同,那么另外的元素就添加不进去

1.4.4.2TreeSet定制排序与元素添加规则
public class TreeSetTest {


    public static void main(String[] args) {
        Comparator<Person> comparator = new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                if (o1.age > o2.age) {
                    return -1;
                } else if (o1.age < o2.age) {
                    return 1;
                } else {
                    return 0;
                }
            }
        };
        Set set = new TreeSet(comparator);
        set.add(new Person("sd",23));
        set.add(new Person("sda", 232));
 //这个元素添加不了 因为我们使用的是定制排序比较的是年龄,这俩的年龄相同就不会放进集合
     //   set.add(new Person("ssdasdad", 232)); 
        for (Object o : set) {
            System.out.println(o);
        }
    }
}

1.5Collection框架总结

1、集合Collection中如果存储的是自定义类的对象,需要自定义类重写哪个方法?

  • 如果实现是List的话,需要自定义类重写equals方法

  • 如果实现是Set的话

    HashSet和LinkedHashSet需要自定义类重写hashCode和equals方法

    TreeSet(树结构)需要自定义类重写Comparable或者Comparator中的方法

判断两个对象是否相等的依据就是上述方法

2、Collection遍历的三种方式

1、迭代器

2、增强for循环

3、foreach()方法遍历(lambda表达式,函数式接口)

HashSet的一个经典试题

    @Test
    public void test3(){
        Set set = new HashSet();
        Person p1 = new Person("AA", 1001);
        Person p2 = new Person("BB", 1002);
        
        set.add(p1);
        set.add(p2);
           
        p1.name = "CC";


      //移除不掉p1 因为删除前会先根据哈希值查找 因为现在p1的哈希值已经变了,找的那个位置没有元素就删除是啊比
        boolean remove = set.remove(p1);
        System.out.println(set);
        System.out.println(remove);

        //可以插入成功 算出的哈希值根据算法算出的索引位置没有元素
        set.add(new Person("CC", 1001));
        System.out.println(set);

        //插入成功, equals为false可以插入
        set.add(new Person("AA", 1001));
        System.out.println(set);
        
        //总结 在删除时会先查询要删元素的哈希值,查出之后根据算法查出索引位置,去那个位置去删 而不是根据遍历一个个元素 
        
    }

总结:set的查找不是一个个遍历,过程就像添加一样先算哈希值然后计算索引位置,并且一个元素一旦插入了某个位置,不管这个元素的值后来发生变化,这个元素的位置也不会变化

2.Map(JDK1.2)

2.1框架图

在这里插入图片描述

2.2概述

存储键值对 相当于数学中的函数 y=f(x) key不能相同 value可以相同典型的实现有

  • HashMap(最常用 线程不安全 JDK1.2 可以存储null的key和value key不能重复
  • LinkedHashMap(继承HashMap JDK1.4) 保证在遍历时,可以按照添加的元素进行遍历 原理类似LinkedHashSet,存放的数据多了两个引用. 频繁的遍历可以使用LinkedHashMap
  • TreeMap(JDK1.2) 按照key-value进行排序,实现排序遍历,按照key排序(自然排序和定制排序) 底层是红黑树(二叉排序树)
  • HashTable(古老的实现类基本不使用 线程安全 JDK1.0 不能存储null的key和value)
  • Properties(继承Hashtable) 常用来处理配置文件,key和value都是String类型

Map的K-V的理解

  • key是无序不可重复,使用Set存储所有的key 要求key所在的类要重写equals和hashCode(HashMap/LinkedHashMap)
  • value可重复
  • 一个K-V对其实就是一个Entry(Node)对象,Entry类里有两个属性,一个是key,一个是value
  • Entry的特点是无序不可重复的

常用方法

size

map中的底层数组的元素个数,非数组长度

put

添加一个元素,如果key重复,覆盖相同key的value值

putAll

将一个map中的所有k-v添加到本map中(要求泛型一致)

remove

根据key将一个键值对移除

clear

将map中的底层数组数据全部清空

get

根据key获取指定key的value值

containsKey

判断是否存在指定的key

containsValue

判断是否包含指定的value

isEmpty

判断底层数组中的元素的个数是否大于0

equals

判断两个map中的k-v是否一致

toString

重写过的,打印一下map中的k-v

Map中的遍历操作

先看HashMap中的属性 下面的三个方法都是分别返回了这三个值

transient Set<K>        keySet;  //表示map中所有的key构成的set集合
transient Collection<V> values;  //表示map中所有的value构成的values集合
transient Set<Map.Entry<K,V>> entrySet; //底层数组的一个个Node对象 Node实现了Map中的Entry接口
keySet()

获取map中所有的key构成的Set集合,map有一个keySet属性,实际上就是存储所有的key,通过调用get方法继而获取所有的key

Set<String> keys = map.keySet();
for (String key : keys) {
    System.out.println(key+"="+map.get(key));
}	
values()

获取所有的value构成的Collection集合

Collection<Object> values = map.values();
for (Object value : values) {
    System.out.println(value);
}
entrySet()

获取map中的一个个的Entry对象, 调用entry的get方法获取所有的key-value

Set<Map.Entry<String, Object>> entries = map.entrySet();
for (Map.Entry<String, Object> entry : entries) {
    System.out.println(entry.getKey());
    Object value = entry.getValue();
    System.out.println(value);
}

2.3HashMap

HashMap底层结构及底层实现原理

  • JDK7及之前:数组+链表
-----数据添加过程-----
HashMap map = new HashMap();
1、通过调用无参的构造器创建了一个长度是16的一个 Entry[] table
//Entry是Map的一个内部类,里面存了 K-V
2、当调用map.put(key1,value1),生成一个Entry对象,e1首先调用e1里key1的hashCode()计算key1的哈希值(不完全是这个算法,实际上是内部自己的哈希算法,调用了key的hashCode方法),此哈希值经过某种算法计算后,得到e1在table里的存放位置,但是要判断存放位置是否已存在Entry对象
 3/1如果此位置没有Entry对象 直接添加成功
 3/2如果此位置已经存在Entry对象(可能不止一个(以链表形式存储))e2,那么调用e1的key(key1)的hashCode方法跟e2的key进行比较,判断哈希值是否相同
    4/1 如果跟e2的key哈希值不同,那么添加成功(以链表形式)
    4/2 如果跟e2的key的哈希值相同 那么继续比较equals方法
  5/1如果e1的key(key1)跟e2的key进行equals返回false,那么添加成功(以链表形式)
  5/2如果e1的key(key1)跟e2的key进行equals返回true,那么后来的e1的value会覆盖原来的Entry对象e2的value(这里跟HashSet不一样 hashSet是直接添加失败)

 ------扩容-----
    在不断的添加过程中,会涉及到扩容问题,当超出临界值并且添加的位置的数据是非null,默认的扩容方式,扩容为原来的2倍,并将原有数据复制过来--重新计算所有元素的哈希值重新进行数据的分布

  • JDK8: 数组+链表+红黑树

跟JDK7的不同

1、new HashMap()创建对象时,底层没有创建一个长度为16的数组,

2、底层的数组改成了 Node[] 而不是Entry[]

3、首次调用put()方法时,底层才会创建一个长度为16的数组(类似ArrayList)

4、jdk7底层结构只有数组+链表,8中数组+链表+红黑树

当数组的某一索引位置上的元素以链表形式存在的数据个数>7,且当前数组的长度 >64时,才会将索引位置上的所有数据改为红黑树存储(查找效率高),如果链表形式存在个数大于7,但是数组的长度没有大于64,那么不进行转红黑树,进行扩容

面试题

1.HashMap的底层实现原理put的流程

重要属性

//底层存储数据的Node数组 长度总是2的n次幂
transient Node<K,V>[] table;

//存储具体元素的集 底层会把我们传入的KV包装成一个个的Node,Node实现了Entry接口
transient Set<Map.Entry<K,V>> entrySet;

//Node数组中的元素个数,非数组长度
transient int size;

transient int modCount;

//扩容的临界阈值 = 容量*加载因子 
//即当某一时刻底层数组的长度*加载因子的值>threshold时那么数组就开始扩容
int threshold;

//加载因子
final float loadFactor;

一些常量

//默认的容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//最大容量 2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;

//默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//某个索引位置上的数据形成的链表长度大于8时 转为红黑树(hashMap长度必须大于64否则只扩容) 树化阈值
static final int TREEIFY_THRESHOLD = 8;

//索引位置上的红黑树的node数量如果小于6 那么转为链表
static final int UNTREEIFY_THRESHOLD = 6;

//某个索引位置的数据形成的链表想要转为红黑树HashMap的最小长度 
static final int MIN_TREEIFY_CAPACITY = 64;

存储

底层是使用Node数组存储数组 数组名叫table;

transient Node<K,V>[] table;  

Node结构是HashMap中的一个内部类 Node<K,V> 实现了Map接口中的Entry接口,一个元素的结构如下

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash; //元素的哈希值
    final K key; //元素的key
    V value;   //元素的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;
    }
}

空参构造器及put流程:

1、调用空参构造器 默认只会将加载因子赋值为默认的0.75而不会创建一个Node数组
 public HashMap() {
       this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
2、计算出key的哈希值 源码
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//1、为什么要使用高16位与16位进行异或呢? ^异或(转换为32位的二进制数 两两比较 相同取0 不同取1)
  h>>>16就是右移16位,用于取出HashCode的高16位
    由于寻址算法是(hash值 & 数组的长度-1) 一般的length的长度绝大多数小于216次方,所以始终是hashCode的低16位甚至更低参与运算,那么hashCode的高16位就完全浪费了,如果要是高16位也参与运算,会让的到的下标更加散列。所以说让hashCode的值和他自己的高16位进行异或运算,让高16位也参与到运算中,
      
//2、为什么不使用&或者|呢?
      因为&|的结果偏向0或者1,并不是均匀的概念,所以使用^(异或)
      
计算出元素在数组中的存储位置 (数组的长度-1 & 元素的key的哈希值)
[i = (n - 1) & hash])  &运算(转为322进制 2个都是1的才取1 其他情况全部取0)
                       |运算(转为322进制 只要有1的就取1 其他情况取0)
    
  由于初始容量和扩容的原因,数组的长度一定是2的n次幂,那么长度-1转换为2进制也很特别,高位都是0,低位都是1,比如16的二进制是10000,那么16-1的二进制就是01111&与运算是两个都是1的话才是1,所以说它与hash值进行&运算,只管后面的1就行(因为前面都是0),即算出的下标的范围就是0-15,刚好就是下标的范围,
   	所以说就可以理解为什么key的hash值为什么使用hashcode的高16位异或低16位,因为在计算下标的时候,高16位甚至更多是完全用不上的,所以我们想要高16位也参与运算,使得到的下标值更加散列
2、将计算出的哈希值,key ,value,还有两个参数传入到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;
    //第一步 先判断Node数组是否被创建 JDK8是懒加载机制 put时才会创建数组
    //第一次put数组是null 先去扩容resize()
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
   
3、第一次put需要扩容 初始容量设置为16 阈值为16*0.75=12
newCap = DEFAULT_INITIAL_CAPACITY; 
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
4、put操作(非第一次)

四种情况

1、索引位置没有元素

直接将我们的key value包装成一个Node直接添加成功

//判断计算出的索引位置上没有元素,直接将我们的KV包装成一个Node,放在数组中
if ((p = tab[i = (n - 1) & hash]) == null)
    //底层调用的还是Node的构造器 传入我们key的哈希值,key和value 以及null
    //因为该位置上没有构成链表,所以这个元素不指向其他元素 就是null
    tab[i] = newNode(hash, key, value, null);
2、索引位置有元素,
1经过判断元素跟索引位置上的元素的哈希值相同,且key相同,说明key真的相同,直接将元素的value替换为新put进来的value值
Node<K,V> e; K k;
if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;
	....
 e.value = value; //替换value值
2、索引位置有元素,经过判断元素属于TreeNode类型,说明此时的索引位置上已经形成了红黑树,就把这个元素放入到红黑树中
else if (p instanceof TreeNode)
    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
3然后进行遍历索引位置上的链表,

大致流程;

1、遍历整个链表找到了一个key相同的元素 直接替换value

2、没有找到相同的key,尾插法插入到链表末尾 然后判断链表的长度是否超过了规定的转树的长度,

  • 如果超出了,在判断底层数组长度是否超过64,超过了64才将链表转为树,否则只扩容
  • 如果没有超出,不做操作
//遍历索引位置上的链表
for (int binCount = 0; ; ++binCount) {
    //如果遍历了整个链表没有发现key相同的,就将这个元素以链表的形式插入到链表中(JDK8尾插法)
    if ((e = p.next) == null) {
        //先讲元素以链表的形式插入到最后 然后进行判断链表长度
        p.next = newNode(hash, key, value, null);
        //判断链表的长度是否超出了转换为树的条件之一   链表长度>=8
          //超出了就去判断数组的长度是否大于64 大于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;
}
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;

    //在这里判断 是否数组的长度大于 MIN_TREEIFY_CAPACITY 默认值是 64
    //只有长度超过64的话 才会将链表转为红黑树,否则只是resize()扩容
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    
    
    //这里进行 将链表转为树
    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);
    }
}

5、索引位置有元素,且经过判断元素不同,当准备以链表存在时,判断链表长度大于8,且数组容量超过了64,那么将链表转为红黑树、

2.谈谈你对HashMap中put/get的认识,谈谈HashMap的扩容机制,默认大小是多少?什么是负载因子(或填充比)?什么是吞吐临界值(或阈值,threshold)

2、HashMap和Hashtable的异同

3、CurrentHashMap(并发HashMap 分段锁技术)

2.4LinkedHashMap输出有序原理

存储 是LinkedHashMap中的一个内部类 Entry继承了HashMap中的Node

HashMap中的Node实现了Map接口中的Entry接口

在Node的基础上多了两个属性 Entry类型的 before和after 记录了添加前后的元素

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);
    }
}

2.5TreeMap

1、只能存储同一类元素(跟TreeSet一样)

2、底层是树,有排序,判断key的标准是Comparable 或者comparator

3、只能按照key排序,不能按照value排序

2.6Properties

1、Hashtable的一个子类 该对象用于处理配置文件

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

3、在存储数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法

常用:

load(InputStream us) 将文件加载到Properties中

getProperty(String key) 获取key

Properties pros = new Properties();
FileInputStream fis = new FileInputStream("某properties配置文件");
pros.load(fis); //将流加载到properties中
String name = pros.getProperty("name"); //获取指定的key
System.out.println(name);

2.7Collections(操作Collection和Map的)工具类

1、提供了一系列的静态方法对集合中的元素进行排序,查询,修改等操作,还提供了对集合对象设置不可变,对集合对象实现同步控制等方法

常用方法
1reverse(List)List集合中的元素进行反转

2shuffle(List)List集合元素进行随机排序

3sort(List)List集合中的元素进行自然排序(Comparable)

4sort(List,Comparator)List集合中的元素进行定制排序(Comparator)

5swap(List,int i,int j)将list集合中的i处的元素与j位置的元素进行交换

6Object max(Collection) 根据元素的自然排序,返回给定集合中的最大元素

7Object max(Collection,Comparator) 根据元素的定制排序,返回最大元素

8Object min(Collection) 根据元素的自然排序,返回给定集合中的最小元素

9Object max(Collection,Comparator) 根据元素的定制排序,返回最小元素

10int frequency(Collection,Object tar)返回集合中指定元素出现的次数

11void copy(List dest,List src) 将集合src中的内容赋值到集合dest中

12boolean replaceAll(List list,Object oldVal,Object newVal)使用集合中的指定元素
同步控制方法

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

shstart7

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

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

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

打赏作者

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

抵扣说明:

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

余额充值