集合概述
- 数组:长度开始时必须指定,而且一旦指定,不能更改
- 保存的时候必须为同一类型的元素
- 使用数组进行增加元素的示意代码,比较麻烦
- 例如Preson :Preson[] pres=new Preson[1] 那么就只能放一个,或者编写动态数组,比较麻烦 集合结构
集合分为两类
- Map
- Collection
Collection :图解关系
Map图解关系
如果是基本类型 那么这里会自动拆装箱
为什么说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都有的这些方法
常用方法
Iterator t=arrayList.iterator();//得到一个集合的迭代器
while (t.hasNext()) { //HashNext判断是否还有下一个元素,
Object next = t.next() ;//next()作用 1,下移 2,将下移以后的集合位置上的元素返回
System.out.println(next);
}
- 注意:再使用迭代器时一定要先使用hasNext方法进行判断是否还有下一个值,否则算法再找到最后一个位置后找不到时会抛出找不到的异常 :NoSuchElementExcep...的一个错误,其意思也就是找不到,如果需要再次遍历需要重新进行赋值接收集合的迭代器对象
- 遍历集合还有其他方式,例如增强for ,和普通for 增强for的语法是:for(元素类型 元素名称:集合名或数组名){ 访问元素 },它的底层也是实现了iterator的接口
- for(int i =0;i<list.size();i++){list.get(i)}
使用冒泡排序从高到低排序
List接口
ArryList源码分析
- ArrayList可以加入null,并且是多个都可以
- ArrAyList是由数组来实现数据存储的
- ArrryList基本等同于Vector,除了ArrayList是线程不安全(执行效率高),再多线程情况下,不建议使用ArrayList ,因为没有线程安全控制,可以看源码没有Sy
- ArrayList中维护了一个Object类型的数组elementData(transient Object[] elementData // 表示瞬间,短暂的,表示该属性不会被序列化),什么是序列化? 见下一张笔记
ArryList源码分析ADD 扩容机制
- 当创建ArrayList对象时,如果使用的是无参构造器,则(源码中:Protected Object[] elementData)初始elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍
- 看代码,无参构:创建了一个空的elementData数组={}
先确认是否扩容,确认好后再进行赋值操作
elementData是不是空数组,如果是第一次添加,则赋一个值,10
真正扩容,ModCount++记录集合被修改的次数,如果elementData大小不够就调用grow真正扩容
第一次并没有1.5倍扩容,直接等于10,因为算法再计算时0/任何数字都是等于0的,这里就做了一定的处理,如果是第二次 就是10+10/2,因为第一次elementData初始是0 ,无法计算所以第一次的容量就是初始给的默认的,第二次开始才会1.5倍计算,之后再进行数组copy扩容
7. 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容则直接扩容elementData为1.5倍
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回收
Vector 底层源码分析
- 如果是无参构造,默认10,满后就按照2倍进行扩容,如果指定大小则每次直接按照2倍扩容
- vector 底层源码也是一个对象数组,Protected Object[] elementData
- Vector 是线程同步的,即线程安全,Vector类的操作方法带有synchronized
- 在开发中,需要线程同步安全时,考虑使用Vector
- Vector 与 ArrayList 区别:
linkedList底层源码分析
- LinkedList底层实现了双向链表和双端队列特点 Ps:这里先背下来这句话 记住就行
- 可以添加任何元素(元素可以重复),包括null值
- 线程不安全,没有实现同步 初步理解一下链表
- LinkdList中维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了Prev、next、item三个属性,其中通过Prev指向前面一个,通过prev指向前一个,通过next指向后一个节点,最终实现双向链表
- 所以LinkedList元素的添加和删除,不是通过数组完成的,相对来说效率较高但不适合查询
- 模拟一个简单的双线链表
- 在学习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)
- 首先:
- first last 是linkdList的属性 表示一个最前节点,和一个最最后面的一个节点
- prev next 是Node节点中的一个Node对象属性 用来关联前后引用
- 第一次直接给node的item给了一个值,但node中的双向引用还没有指向,因为目前只有1个值,但是first 前 后last 都是指向当前1的这个node对象地址
- 第二个值进入的时候就不一样了,第二个节点的pre会等于第一个节点last,因为last(最后一个节点标记)再最开始赋值的是1的引用,最后将last(最后)会指向2这个新节点,新的一个节点
- 最终就变成了
- 第一个节点中first等于自己1 ,last等于最后一个就是当前最新一个引用2
- 2节点中的preV等于上一个节点的引用1,上一个节点1的next等于下一个节点2的引用
如果要加入新的数据,以此类推,图片中的last会等于每次的新节点然后进行相互关联
LinkdList之Remove
- LikedList .remove 不传参数的话默认删除的是第一个节点
- 调用removeFirst方法,指向双向列表的第一个节点, unLinkdList(Node f),将第一个节点对象引用设置为null等待GC回收
如果是指定删除,先判断从链表前面找这个位置的值还是从后面找,看到这里就可以瞎猜一下为啥查询这么慢了,因为要使用循环去找关联关系。
- 找到这个节点后,把这个要删除的节点中的perv 和 next 取出来(这个东西可以找到删除结点的前后关系,后续使用),再交换节点指向时,会把要删除的当前节点的item=null 让GC去回收
- 交换指向 加粗的标记(perv, next)的前后指向,是从要删除节点身上取出来的
- 将prev节点的Node对象next= next节点
- 将next节点的prev=prev节点
- 简单记录一下获取/查询
- 与ArrayList的区别比较
set接口
set接口基本介绍
- 无序(添加和取出的顺序是不一致的),没有索引
- 不允许重复元素,所以包含一个null
- JDk API中的set接口实现类有以下显示,但常用的是HashSet和TerrSet
- Set的接口方法
HashSet
- 取出的顺序不是添加的顺序,但是它取出来的顺序是固定的,是因为底层实现是链表+数组+红黑树的方式实现的
- 如果数据不是很大不会转到红黑树/这里不说红黑树
- 在看源码之前先做个demo模拟一下数组+链表
- 数组加链表模拟,先创建一个对象数组,然后可以存放16个,在一个下标是2的位置创建一个节点Node对象john,之后再创建jack 创建成功后挂载到john的Node-nextd对象上面,这个为2的位置就存放了两个,这样做的目的也是为了高效
- 再来提高一下记忆,方便日后复习
看了一遍源码比较复杂,大概是这样的,记录一下自己目前的理解,如果未来再更高的层次后,再回来修改
- 首先HashSet是数组加链表来实现的,而数组就是一个Node节点的一个数组,第一次添加的时候数组扩容和初始是16(这里会预加载:(长度*0.75),也就是说如果时16的话,那么再12的时候就会开始预加载一出来成为32,以此类推),而再创建一个值的时候,这个时候会使用一个HashCode计算出一个Hash值来再这个Node节点的数组中找出一个位置来进行存放,如果第二次放入的值的HashCode算出的Hash值算出来是和第一个是同一个Node位置,那么就会挂载到这个第一次存放的数据的next的节点后面(这里还会eq比较一个内容是否相等-大概是为了防止),挂载到屁股后面,以此类推
- 表达能力欠佳呀,为什么会使用链表,这里除开该集合的特性不允许重复以外,还会牵扯到解决Hash碰撞的问题
LinkedHashSet
LinkedHashSet是HashSet的下一层结构,是使用数组+双向链表和HashSet有些类似,但是它可以保证放入和取出的顺序一直,例如值是放再第一个位置的那么取出来就还是第一个位置,但是HashSet就不可以了,虽然HashSet也是用链表但是是单向链表和这个链表有所不同,源码中是每添加一次就会把关系进行关联,也就是说再HashSet的源码基础上又套了一层插入值的时候的一种关联 例如插入A B 那么B再插入的时候prev就会等于A,A的next就等于B... 进行了一个顺序关联
TreeSet
- 以后补充
Map
Map的接口特点
Map存放数据的Key-value示意图,一堆K-V是放在一个HashMap (内部类) Node中的,有因为Node实现Entry接口,有些书上也说一对K-V就是一个Entry
HashMap
记录一下循环