java 集合知识结构整理

120 阅读10分钟

集合概述

  1. 数组:长度开始时必须指定,而且一旦指定,不能更改
  2. 保存的时候必须为同一类型的元素
  3. 使用数组进行增加元素的示意代码,比较麻烦
  4. 例如Preson :Preson[] pres=new Preson[1] 那么就只能放一个,或者编写动态数组,比较麻烦 集合结构

集合分为两类

  • Map
  • Collection Collection :图解关系 image.png

Map图解关系 image.png 如果是基本类型 那么这里会自动拆装箱 image.png

为什么说List是单列集合,map是双列集合:是因为值是一个一个进行放的,而map是K-V的方式

ArrayList arrayList=new ArrayList();
arrayList.add("jack");

HashMap   hashmap=new HashMap();
hashmap.put("1",12);
hashmap.put("2",13);

Collection

1. Collection接口实现类的特点:Public interface Collection<E>extends Iterable,Iterable是个最大父接口,这个类可以进行元素遍历, iterable对象称为迭代器,主要用于便利Collection集合中的元素,所有实现了Collection接口的集合类都有一个iterator方法,用于返回一个实现了iterator接口的对象,即可以返回一个迭代器,lterator仅用于遍历集合,lterator本身不存放对象
2. Collection实现子类可以存放多个元素,每个元素可以是Object(可以是对象)
3. 有些Collection的实现类,有些是有序的List ,有些不是有序的Set
4. Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的
5. Collection接口的方法:接口实现的子类进行使用,也就是说  List   Set都有的这些方法

常用方法

image.png

image.png

image.png

image.png

   Iterator t=arrayList.iterator();//得到一个集合的迭代器
while (t.hasNext()) { //HashNext判断是否还有下一个元素,
    Object next =  t.next()   ;//next()作用 1,下移 2,将下移以后的集合位置上的元素返回
    System.out.println(next);
}
  1. 注意:再使用迭代器时一定要先使用hasNext方法进行判断是否还有下一个值,否则算法再找到最后一个位置后找不到时会抛出找不到的异常 :NoSuchElementExcep...的一个错误,其意思也就是找不到,如果需要再次遍历需要重新进行赋值接收集合的迭代器对象
  2. 遍历集合还有其他方式,例如增强for ,和普通for 增强for的语法是:for(元素类型 元素名称:集合名或数组名){ 访问元素 },它的底层也是实现了iterator的接口
  3. for(int i =0;i<list.size();i++){list.get(i)}

使用冒泡排序从高到低排序 image.png

List接口

ArryList源码分析

  1. ArrayList可以加入null,并且是多个都可以
  2. ArrAyList是由数组来实现数据存储的
  3. ArrryList基本等同于Vector,除了ArrayList是线程不安全(执行效率高),再多线程情况下,不建议使用ArrayList ,因为没有线程安全控制,可以看源码没有Sy
  4. ArrayList中维护了一个Object类型的数组elementData(transient Object[] elementData // 表示瞬间,短暂的,表示该属性不会被序列化),什么是序列化? 见下一张笔记
ArryList源码分析ADD 扩容机制
  1. 当创建ArrayList对象时,如果使用的是无参构造器,则(源码中:Protected Object[] elementData)初始elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍
  • 看代码,无参构:创建了一个空的elementData数组={} image.png 先确认是否扩容,确认好后再进行赋值操作 image.png elementData是不是空数组,如果是第一次添加,则赋一个值,10 image.png

真正扩容,ModCount++记录集合被修改的次数,如果elementData大小不够就调用grow真正扩容 image.png 第一次并没有1.5倍扩容,直接等于10,因为算法再计算时0/任何数字都是等于0的,这里就做了一定的处理,如果是第二次 就是10+10/2,因为第一次elementData初始是0 ,无法计算所以第一次的容量就是初始给的默认的,第二次开始才会1.5倍计算,之后再进行数组copy扩容

image.png 7. 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容则直接扩容elementData为1.5倍

image.png

ArrayList 之Remove
 public E remove(int index) {
        //第一步先判断是否有越界,如果越界直接IndexOutOfBoundsException
        rangeCheck(index);

        modCount++;
        //把该元素从数组中提出
        E oldValue = elementData(index);
        //需要复制的长度
        int numMoved = size - index - 1;
        if (numMoved > 0)
        //原数组,从哪开始复制,目标数组,复制起始位置,长度。过程如下图:
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //赋值null等待回收
        elementData[--size] = null; 

        return oldValue;
    }
  • 比如删除下标3的元素
  • 原数组:elementData
  • 从哪开始复制:index+1 = 4
  • 目标数组:elementData
  • 复制起始位置:index =3
  • 长度: size - index - 1=6
  • 白话文:两个数组A B,要把B数组复制到A自己身上来,告诉方法我从A的哪里开始,赋值B的内容, B又从哪里开始截取复制给A,之后再把A的最后一个值设置为NULL 等待GC回收 image.png

Vector 底层源码分析

  1. 如果是无参构造,默认10,满后就按照2倍进行扩容,如果指定大小则每次直接按照2倍扩容
  2. vector 底层源码也是一个对象数组,Protected Object[] elementData
  3. Vector 是线程同步的,即线程安全,Vector类的操作方法带有synchronized
  4. 在开发中,需要线程同步安全时,考虑使用Vector
  5. Vector 与 ArrayList 区别: image.png

linkedList底层源码分析

  1. LinkedList底层实现了双向链表和双端队列特点 Ps:这里先背下来这句话 记住就行
  2. 可以添加任何元素(元素可以重复),包括null值
  3. 线程不安全,没有实现同步 初步理解一下链表
  • LinkdList中维护了两个属性first和last分别指向首节点和尾节点
  • 每个节点(Node对象),里面又维护了Prev、next、item三个属性,其中通过Prev指向前面一个,通过prev指向前一个,通过next指向后一个节点,最终实现双向链表
  • 所以LinkedList元素的添加和删除,不是通过数组完成的,相对来说效率较高但不适合查询
  • 模拟一个简单的双线链表

image.png image.png

  • 在学习LinkdList集合源码之前要先初步了解一下链表结构,下面模拟一下链表代码

初识链表

public class Haiyan{
   public static void main(String[] args) {
    //模拟一个简单的双向链表
       Node jack=new Node("jack");
       Node jack2=new Node("jack2");
       Node jack3=new Node("jack3");
       //链接三个结点,形成双向的链表
       //jack -> jack2->jack3
       //正向
       jack.next=jack2;
       jack2.next=jack3;

       //反向
       //jack3 -> jack2->jack
       jack3.next=jack2;
       jack2.next=jack;

       Node first=jack; //双向链表头  首节点
       Node last=jack3; //双向链表   尾节点

       while (true){
           if(first==null){
               break;
           }
           //输出first 信息
           System.out.println(first);
           //头部第一次是等于的jack的一个结点,而jack中的next指向的是下一个对象节点
           first=first.next;  //一直获取下一个 相当于在进行循环覆盖 例如第一次获取的是jack对象,拿到jack对象后 jack对象中包含下一个节点对象 可以无限套娃,如果是反向的则使用的就是last 与这个同理
       }
       
       //演示链表的添加对象/数据,是多么方便
       //要求,是在jack和jack2之间插入一个jack4,改变一下双边的指向
       //首先创建一个jack4的对象  next下一个    pre前一个进行双向绑定
       Node jack4=new Node("jack4");
       jack4.next=jack2;
       jack4.pre=jack;
       
       jack.next=jack4;
       jack2.pre=jack4;
       
   }
   
}
class Node{
   public Object item;//真正存放数据
   public  Node next;//指向后一个节点
   public Node pre;//指向前一个节点
   public  Node (Object name){
       this.item=name;
   }
   public String  toString(){
       return "Node name"+item;
   }
}
  • 从上面的代码中可以看出,如果使用链表的增删改的操作是非常快的,因为只是改变引用,不会操作扩容
LinkdeList 之add
  • 例如:一个LinkdeList add(1) 和 add(2) image.png
  • 首先:
  1. first last 是linkdList的属性 表示一个最前节点,和一个最最后面的一个节点
  2. prev next 是Node节点中的一个Node对象属性 用来关联前后引用
  • 第一次直接给node的item给了一个值,但node中的双向引用还没有指向,因为目前只有1个值,但是first 前 后last 都是指向当前1的这个node对象地址

image.png

image.png

  • 第二个值进入的时候就不一样了,第二个节点的pre会等于第一个节点last,因为last(最后一个节点标记)再最开始赋值的是1的引用,最后将last(最后)会指向2这个新节点,新的一个节点
  • 最终就变成了
  • 第一个节点中first等于自己1 ,last等于最后一个就是当前最新一个引用2
  • 2节点中的preV等于上一个节点的引用1,上一个节点1的next等于下一个节点2的引用

image.png

如果要加入新的数据,以此类推,图片中的last会等于每次的新节点然后进行相互关联

LinkdList之Remove
  • LikedList .remove 不传参数的话默认删除的是第一个节点
  • 调用removeFirst方法,指向双向列表的第一个节点, unLinkdList(Node f),将第一个节点对象引用设置为null等待GC回收

image.png

如果是指定删除,先判断从链表前面找这个位置的值还是从后面找,看到这里就可以瞎猜一下为啥查询这么慢了,因为要使用循环去找关联关系。

image.png

  • 找到这个节点后,把这个要删除的节点中的pervnext 取出来(这个东西可以找到删除结点的前后关系,后续使用),再交换节点指向时,会把要删除的当前节点的item=null 让GC去回收
  • 交换指向 加粗的标记(pervnext)的前后指向,是从要删除节点身上取出来的
  • prev节点的Node对象next= next节点
  • next节点的prev=prev节点

image.png

  • 简单记录一下获取/查询

image.png

  • 与ArrayList的区别比较

image.png

set接口

set接口基本介绍

  1. 无序(添加和取出的顺序是不一致的),没有索引
  2. 不允许重复元素,所以包含一个null
  3. JDk API中的set接口实现类有以下显示,但常用的是HashSet和TerrSet

image.png

image.png

  • Set的接口方法

image.png

HashSet

image.png image.png

  • 取出的顺序不是添加的顺序,但是它取出来的顺序是固定的,是因为底层实现是链表+数组+红黑树的方式实现的
  • 如果数据不是很大不会转到红黑树/这里不说红黑树

image.png

  • 在看源码之前先做个demo模拟一下数组+链表
  • 数组加链表模拟,先创建一个对象数组,然后可以存放16个,在一个下标是2的位置创建一个节点Node对象john,之后再创建jack 创建成功后挂载到john的Node-nextd对象上面,这个为2的位置就存放了两个,这样做的目的也是为了高效

image.png

  • 再来提高一下记忆,方便日后复习

image.png

image.png 看了一遍源码比较复杂,大概是这样的,记录一下自己目前的理解,如果未来再更高的层次后,再回来修改

  • 首先HashSet是数组加链表来实现的,而数组就是一个Node节点的一个数组,第一次添加的时候数组扩容和初始是16(这里会预加载:(长度*0.75),也就是说如果时16的话,那么再12的时候就会开始预加载一出来成为32,以此类推),而再创建一个值的时候,这个时候会使用一个HashCode计算出一个Hash值来再这个Node节点的数组中找出一个位置来进行存放,如果第二次放入的值的HashCode算出的Hash值算出来是和第一个是同一个Node位置,那么就会挂载到这个第一次存放的数据的next的节点后面(这里还会eq比较一个内容是否相等-大概是为了防止),挂载到屁股后面,以此类推
  • 表达能力欠佳呀,为什么会使用链表,这里除开该集合的特性不允许重复以外,还会牵扯到解决Hash碰撞的问题 image.png image.png

LinkedHashSet

LinkedHashSet是HashSet的下一层结构,是使用数组+双向链表和HashSet有些类似,但是它可以保证放入和取出的顺序一直,例如值是放再第一个位置的那么取出来就还是第一个位置,但是HashSet就不可以了,虽然HashSet也是用链表但是是单向链表和这个链表有所不同,源码中是每添加一次就会把关系进行关联,也就是说再HashSet的源码基础上又套了一层插入值的时候的一种关联 例如插入A B 那么B再插入的时候prev就会等于A,A的next就等于B... 进行了一个顺序关联

image.png

TreeSet

  • 以后补充

Map

image.png Map的接口特点 Map存放数据的Key-value示意图,一堆K-V是放在一个HashMap (内部类) Node中的,有因为Node实现Entry接口,有些书上也说一对K-V就是一个Entry

HashMap

image.png

image.png

image.png

记录一下循环

image.png

image.png

HashTable

image.png