java单列集合

文章详细介绍了Java集合框架中的核心概念,包括Collection和Map接口,以及List、Set系列的实现类如ArrayList、LinkedList、HashSet、TreeSet等。讨论了这些集合的特性,如添加、删除、遍历元素的方法,以及迭代器和增强for循环的使用。还提到了泛型在集合中的应用,以及如何通过hashCode和equals方法实现数据去重。此外,还简述了数据结构如栈、队列、数组和链表的基本概念。
摘要由CSDN通过智能技术生成

集合

分类

集合包括Collection单列集合,和Map的双列集合

方法

  1. add 吧给定的对象添加到当前集合中,返回值是boolean

  2. clear 清空集合中所有的元素

  3. remove 把给定的对象在当前集合中删除(参数是要删除的值)

  4. contains 判断当前集合中是否包含给定的对象。底层是使用equals方法判断对象是否一致,如果存的是自定义对象,没有重写equals方法,那么默认使用Object类中的equals方法进行判断,而Object类中equals方法,依赖地址值进行判断

    collection包含的公共方法

    isEmpty 判断当前集合是否为空 size 返回集合中元素的个数/集合的长度

    集合的分类:List添加的元素是有序的、可重复、有索引 Set:无序、不重复、无索引

    List Set ArrayList LinkedList Vector HashSet TreeSet LinedHashSet

    集合遍历的三种方式

    1.迭代器遍历:迭代其在遍历集合的时候是不依赖索引的

    迭代器:Iterator hasNext() 判断当前位置是否有元素 next()获取当前位置的元素,并将迭代器对象移向下一个位置

    public static void main(String[] args) {
            Collection<String> coll = new ArrayList<>();
            coll.add("aaa");
            System.out.println(coll);
            coll.clear();
            System.out.println(coll);
            // 迭代器
            // 获取一个迭代器对象,默认指向集合下标为0的位置
            Iterator<String> it = coll.iterator();
            // 判断是否有下一个对象
            boolean flag = it.hasNext();
            // 获取当前的值,并将指针移向下一个数据的位置
            String str = it.next();
            System.out.println(str);
    ​
             // 正常迭代器循环
            // 1. 创建迭代器对象,并将指针指向集合内第一个值的位置
            Iterator<String> it2 = coll.iterator();
            // 2. 循环判断  条件是判断当前指针后是否还有下一个值
            while (it2.hasNext()){
                // 获取当前指针下的值,并将指针往后移动一个位置
                String s = it2.next();
            }
    ​
        }

    细节:

    1. 迭代器遍历完毕,指针不会复位

    2. 循环中只能用一次next方法

    3. 迭代器遍历时,不能用集合的方法进行增加或者删除

      只能使用迭代器的方法删除,不能使用集合的remove方法删除,不能进行添加

    4. 如果要第二次遍历集合,只能再次获取一个新的迭代器对象

      增强for循环遍历

      1.增强for的底层就是迭代器,为了简化迭代器的代码书写

      2.原理内部就是一个Iterator迭代器

      3.所有的单列集合和数组才能用增强for进行遍历

      // 增强for循环
      // 2. 利用增强for循环进行遍历
      // s是第三方变量,再循环的时候一次表示集合中的每一个数据
      for (String s : coll){
                  // 在循环体期间修改s的值,炳辉改变集合中原本的数据
          // 如果在循环体内将s的值进行修改,底层的迭代器会将s进行第二次赋值
              }

    Lambda表达式遍历

     // lambda演变
     // 1. 匿名内部类
    // forEach底层是一个for循环,把得到的每个元素传递给accept方法
            coll.forEach(new Consumer<String>() {
                @Override
                // 
                public void accept(String s) {
                    System.out.println(s);
                }
            });
    // lambda表达式
     coll.forEach(s -> System.out.println(s));
    ​

    Collection 是单列集合的顶层集合,所有方法被List和Set系列集合共享

    常见的成员方法:add,clear,remove,contains,inEmpty,size

    三种通用的遍历方式:迭代器:遍历的过程需要删除元素的时候可以选择,增强for循环,lambda表达式

    List系列集合:有序(存和取的顺序一致)、有索引、可重复

    增:add(索引,数据):再次集合的指定位置插入指定的元素,原来的元素一次后移

    删:remove(索引):直接删除元素/通过索引删除元素 如果调用方法的时候,方法出现了重载的现象,会优先调用与形参与实参相同的方法,可以考虑手动将传入的参数向上转型()封装

    改:set(索引,数据) :返回被修改的元素

    查:get(索引):

    List集合的遍历方式

    1.迭代器遍历

    2.列表带迭起遍历 previous:返回列表中的前一个元素

    在遍历的过程中可以添加元素:使用迭代器本身的添加的方法进行添加元素

    3.增强for遍历

    4.lambda表达式遍历

    5.普通for循环遍历

    Set系列集合:

    无序:存取顺序不一致

    不可重复:可以去除重复

    无索引:没有带索引的方法,所以不能使用普通For循环遍历,也不能通过索引来获取元素

    实现类

    hashSet:无序、不重复、无索引

    LinkedHashSet:有序、不重复、无索引

    TreeSet:可排序、不重复、无索引

    方法:

    add(E e):把给定的对象添加到当前集合

    clear():清空集合中所有的元素

    remove(E e):把给定的对象在当前集合中删除

    只能根据对象删除,不能根据索引删除

    contains(Object obj) 判断当前集合是否为空

    isEmpty():判断当前集合是否为空

    size():返回集合中元素的个数/集合的长度

    遍历

    1.迭代器

    2.增强for

    3.lambda表达式

    HashSet

    无序、不重复、无索引

    直接使用Collection中的API

    LinkedHashSet是HashSet的子类

    直接使用Collection中的API

    有序、不重复、无索引

    TreeSet

    遍历的方式:迭代器,增强for,lambda表达式

    直接使用Collection中的API

    不可重复,无索引,可排序

    默认按照从小到大顺序排序

    底层是基于红黑树的数据结构实现排序的,增删改查性能都较好

    默认遍历就是排序(从小到大)

    对于字符或字符串类型,是按照其对应的ASCII码表中的数字升序进行排序

    当添加自定义对象类型的数据时,需要手动添加默认的比较大小的规则

    默认排序规则 让自定义类去实现Comparable,重写里面的抽象方法,在指定比较规则

    public int comparaTo(自定义对象 o){
            // 指定排序规则
            // 假设以自定义对象的Age属性进行排序
            return this.getAge() - o.getAge();
    }
    // this表示当前要参加的元素
    //o 表示已经在红黑树存在的元素
    // 返回值:
    // 负数:认为添加的元素是小的,存左边
    // 正数:认为要添加的元素是大的,存右边
    // 0 :认为当前要添加的元素已经存在,舍弃

    比较器排序,创建TreeSet对象的时候,传递比较器Compartor指定规则,默认第一种规则,如果第一种满足不了就第二种

    // 实现Comparator接口  重写compare(String o1 , String o2)方法
    // 自定义排序规则
    ​

    如果方式一和方式二同时存在的情况,默认以方式二进行比较

    使用场景

    如果想要集合中的元素可重复(ArrayList)

    如果增删操作多于查询操作(LinkerList)

    如果对集合中元素去重(HashSet)

    如果想去重和保证存储顺序(LinkedHashSet)效率低于HashSet

    堆积各种的元素进行排序(TreeSet)

    增强forEach底层最右侧的参数是this 也就是遍历方法的调用者本身

    数据结构:计算机存储、组织数据的方式

    栈、队列、数组、链表、二叉树、二叉查找树、平衡二叉树、红黑树

    栈:后进先出,先进后出

    后进先出,先进后出 // 一端开口 / 栈顶元素,一端封闭 栈底元素

    数据进入栈模型的过程称为:压/进栈

    数据离开栈模型的过程称为:弹/出栈

    队列:先进先出,后进后出

    两端开口 先进先出,后进后出

    数据从后端进入队列模型的过程称为:入队列

    数据从前端出队列模型的过程称为:出队列

    数组:查询快,增删慢

    查询速度快: 查询数据时通过地址值和索引定位,查询任意位置的数据耗时相同,元素在内存中是连续存储的

    删除效率低:要将原始数据删除,同时后面的每个数据前移

    添加效率低:添加位置后的每个数据后移,再添加元素

    链表:查询慢,增删比较快

    头节点会记录自身的地址,同时记录下一个数据的地址,每个节点内存储的是一个自身的数据和下一个数据的地址,^表示空地址

    链表中的每一个节点是独立的对象,在内存中是不连续的,每个节点包含数据值和下一个节点的地址

    单向链表

    双向链表:既记录上一个节点的地址,也记录后一个节点的地址

    查询第N个元素时,会先判断N距离头部近还是底部近,随后再决定从前往后找还是从后往前找

    一个节点存储父节点地址,左子结点地址,右子节点地址,没有子节点或者没有父节点则记为null

    度:每个节点的字节数量

    二叉树中:任意节点的度<=2

    树高:树的总层数

    根节点:最顶层的节点

    左/右子节点:左/右下方的节点

    根节点的左/右子树:以根节点的左/右子结点开始,其下面所有的子节点

    二叉查找树

    每个子节点最多只能有两个子节点

    任意节点左子树上的值都小于当前节点

    任意节点右子树上的值都大于当前节点

    存储规则:

    小的存左边

    大的存右边

    一样的不存

    二叉树的遍历方法

    1.前序遍历

    从根节点开始,按照当前节点,左子节点,右子节点的顺序遍历

    2.中序遍历(最终结果是从小到大的顺序)

    从根节点开始,按照左子结点,当前节点,右子节点顺序遍历

    3.后序遍历

    从根节点开始,按照左子结点,有子节点,当前节点顺序遍历

    4.层序遍历

    从根节点开始,一层一层遍历

    二叉查找树弊端:旋转次数多,造成添加时资源的浪费

    可能会出现单向链表的情况

    平衡二叉树:任意节点左右子树高度差不超过1

    通过旋转机制来保证任意左右子树高度差不超过1

    确定支点:从添加的节点开始,不断往父节点找不平衡的节点

    左旋:

    1.以不平衡的点作为支点

    2.把支点左旋降级,变成左子结点

    当层级比较复杂时:将跟节点的右侧往左拉

    原先的右子节点变成新的父节点,并把多余的左子节点让出来,给已经降级的根节点当右子节点

    3.晋升原来的右子节点

    右旋:

    1.以不平衡的点作为支点

    2.把支点右旋降级,变成右子结点

    当层级比较复杂时:将跟节点的右侧往右拉

    原先的左子节点变成新的父节点,并把多余的右子节点让出来,给已经降级的根节点当左子节点

    3.晋升原来的右子节点

    触发时机:当添加一个节点之后,该树不在是一个平衡二叉树

    触发左/右旋的时机

    1.左左:当根节点左子树的左子树有节点插入,导致二叉树不平衡 , 一次右旋

    2.左右:当根节点左子树的右子树有节点插入,导致二叉树不平衡,先局部左旋,再整体右旋

    3.右右:当根节点右子树的右子树有节点插入,导致二叉树不平衡,一次左旋

    4.右左:当根节点的右子树的左子树有节点插入,导致二叉树不平衡,先局部右旋,再整体左旋

    小结:

    二叉树,无规律,查询效率低-->二叉搜索树,容易出现单向链表的情况--->平衡二叉树(本质是一个二叉查找树)

    红黑树:增删改查性能都很好

    每个节点的组成:父节点地址/值/左右子节点地址/颜色

    1.红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构、

    2.特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色

    3.每一个节点可以使红或者黑,红黑树不是高度平衡的,他的平衡式通过“红黑规则”进行实现的

    是一个二叉查找树

    但是不是高度平衡的

    条件:特有的红黑规则

    红黑规则

    1.每一个节点或是红色的,或者是黑色的

    2.根节点必须是黑色的

    3.如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点是黑色的

    4.如果某一个节点是红色的,那么他的子节点必须是黑色的(不能出现两个红色节点相连的情况)

    5.对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

    后代叶节点:其节点后代中所有的nil的叶节点

    简单路径:一路往前

    添加节点(默认红色的,效率高)

    源码

    ArrayList底层原理

    1.利用空参创建的集合,在底层创建一个默认长度为0的数组,数组名为elementData,有一个参数为size

    2.添加第一个元素时,底层才会创建一个新的长度为10的数组,size既表示元素的个数也表示下次存入数据时的起始位置

    3.存满时,会扩容1.5倍,然后将原数组的数据copyOf至新数组

    4.如果一次添加多个元素,1.5倍放不下,则新创建的数组的长度以实际为准

    Array底层的add方法:

    调用add方法需要传入三个参数:参数一E e 表示当前要添加的元素

    参数二: Object[] elementData :表示集合底层的数组的名字

    参数三:int s:集合的长度/当前元素应存的位置

    第一次添加元素

    先判断s是否与数组的长度相同,如果相同,则调用grow方法

    随后gorw会调用一个有参构造方法grow(size +1)

    记录原来的老容量--> 第一次添加元素-->比较老容量与默认长度相比,随后创建一个默认长度为10的数组

    第n次添加元素(需扩容的情况)

    grow()方法扩容,无参构造方法内会将数组长度+1,表示当前可存入数据后的最小长度

    记录原来的老容量-->调用ArraysSupport.newLength(oldCapacity(老容量),minCapacity - oldCapacity(理论上至少要新增的容量) , oldCapacity >> 1(等于Old除于2,默认增加一半));

    newLength()方法内部

    先将第二个参数和第三个参数进行比较:在集合中,不仅仅是一次添加一个元素,还可以一次添加多个元素

    int prefLength = oldLength + Math.max(minGrowth , prefGrowth)

    第一种 情况:如果一次添加一个元素,那么第二个参数一定是1,表示此时数组只要扩容一个单位就可以了

    第二种情况:如果一次添加多个元素,假设100,那么第二个参数是100,表示此时数组需要扩容100个单位才可以

    prefLength:表示数组真正的长度

    Arrays.copyOf(elementData,newCapacity)

    1.会根据第二个参数创建新的数组

    2.把第一个参数中的所有数据,全部拷贝到新数组中

    随后返回,再将需要添加的元素添加至新数组中,size+1

    LinkedList集合

    底层数据结构是双向链表,查询慢,增删快

    add默认添加到列表末尾

    addFirst 在列表开头插入指定的元素

    addLast 将指定的元素追加到此列表尾部

    getFirst 返回此列表中的第一个元素

    getLast 返回此列表中的最后一个元素

    removeFirst 从此列表中删除并返回第一个元素

    removeLast 从此列表中删除并返回最后一个元素

    底层实现

    Node类

    E item 表示当前要存储的数据

    Node<E> next 表示下一个要存储的地址值

    Node<E> prev 表示上一个值的地址值

    成员变量

    size 表示集合的长度/也表示节点的总个数

    first 头节点

    last 尾节点

    LineLast

    第一次调用

    先将Last拿出来,赋给一个局部变量L

    创建一个节点对象(l为null , e表示当前要添加的元素,) 表示在内存内创建了一个前后节点为空,中间为要添加的元素的值的一个节点对象

    将新创建的节点对象的地址值赋给last

    如果l == null 则将新节点的地址值赋给first

    第二次添加节点

    如果l!=null, 那么将l.next重新赋值为新节点的地址值

    迭代器底层

    默认属性:cursor:光标/指针/ 默认指向0索引位置

    lastRet:刚刚操作索引位置/表示上一次操作的索引

    int expectedModCount = modCount

    checkForComodification

    校验现在集合变化的次数和集合创建时变化的次数

    ModCoun表示集合变化的次数,调用add/remove次变量都是自增

    当我们创建迭代器对象的时候,就会把这个次数高速迭代器

    当调用next方法时,会校验这个数和迭代器创建的时候的数是否相同,如果不同则抛出异常

    modCount:集合变化的次数

    hasNext 判断当前元素后是否还有元素

    cursor != size 表示当指针位置与集合长度相同,即为没有下一个元素了

    Object[] elementData = ArrayList.this.elementData;

    获取集合顶部的数组(地址值)赋值给局部数组

    cursor = i+1;移动指针

    return (E)elementData[lastRet = i] // 返回数组中的该索引上的元素

    HashSet底层原理

    1.HashSet集合底层采取哈希表存储数据

    哈希表是对于增删改查数据性能都比较好的结构

    JDK8之前:数组+链表

    JDK8开始:数组+链表+红黑树

    Hash值:对象的整数表现形式

    1.根据hashCode方法算出来的int类型的整数

    2.该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算

    3.一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值

    对象的HashCode特点

    1.如果没有重写hashCode方法,不同对象计算处的哈希值是不同的(因为父类Object是根据地址值来计算hash值的)

    2.如果已经重写hashCode方法,不同对象只要属性值相同,计算出来的哈希值就是一样的

    3.在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(hash碰撞)

    HashSet底层

    1.创建一个默认长度为16,默认加载因子0.75(当数组内拥有的数据数量占数组大小的0.75时开始扩容,扩容至原来数组大小的两倍)的数组,数组名table

    2.根据元素的哈希值跟数组的长度计算出应存入的位置(int index = (数组长度 - 1) & 哈希值)

    3.判断当前位置是否为null,如果是null直接存入,如果不为null,表示有元素,则调用equals方法比较属性值,如果属性值一样,则会舍弃当前要存入的值,如果不一样,存入数组,形成链表

    JDK8之前:新元素存入数组,老元素挂在新元素下面

    JDK8以后:新元素直接挂在老元素下面

    当链表长度大于8且数组长度大于等于64时,自动将链表转为红黑树

    如果集合中存储的是自定义对象,必须要重写hashCode和equals方法

    1.HashSet为什么存和取的顺序不一样

    因为HashSet是通过数组加链表的形式存储的,所以存取无顺序

    2.HashSet为什么没有索引

    因为HashSet是通过数组加链表组成的,无法定义索引

    3.HashSet是利用什么机制保证数据去重的

    hashCode方法和equals方法

    LinkedHashSet底层原理

    效率比HashSet低

    有序

    保证存储和取出的元素顺序一致

    原理:底层数据结构依然是哈希表,只是每个元素有额外多了一个双链表的机制记录存储的顺序

    遍历的时候是先遍历双向链表

    底层是基于哈希表,使用双向链表记录添加顺序

    泛型

    泛型只能支持引用数据类型

    不能写基本数据类型,无法转换为Object

    在指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型

    如果不写泛型,类型默认为Object

    如果没有给集合指定类型,默认所有数据类型都是Object类型,此时可以往集合中添加任意类型的数据,获取数据的时候,无法使用它的特有行为

    java中的泛型是伪泛型,当集合真正添加的时候还是将限定的泛型转变为Object类型,在取的时候还是会再将Object类型转换为泛型限定的类型

    泛型的擦除

    把运行时期的问题提前到编译期间,避免了强制类型转换可能出现的异常

    泛型的定义:类后面,方法后面,接口后面 , 返回值位置

    同一个类中的泛型使用的替代泛型的字母需要全部统一

    修饰符 class 类名<类型(泛型)>

    返回值类型为泛型的 修饰符 泛型 方法名 方法体

    形参为泛型的 修饰符 返回值 方法名(泛型 形参)方法体

    泛型方法:在方法声明定义自己的泛型

    此时他可以接收任意数据类型的数据

    修饰符 <泛型> 返回值类型 方法名(形参)方法体

    泛型接口

    修饰符 interface 接口名 <泛型>

    1. 实现类给出具体的类型

    2. 泛型延续,创建对象时在确定具体的类型

    泛型的继承和通配符

    如果类型不确定,只能传递某个继承体系中的,可以使用通配符

    泛型不具备继承性,但是数据具备继承性

    调用泛型的方法,必须和其定义的泛型类型保持一致,即使存在继承关系也不行

    ? 表示不确定的类型

    ?extends E 可以传毒E或者E所有的子类类型

    ? super E 可以传递E或者E所有父类类型

细节:

1.泛型中不能写基本数据类型

2.指定泛型的具体类型后,传递数据时,可以传入该类型和他的子类类型

3.如果不写泛型,类型默认是Object

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值