IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java中的集合 -> 正文阅读

[Java知识库]Java中的集合

以下为本人观看尚硅谷Java学习视频所做的笔记


?

Java集合框架概述

集合、数组都是对多个数据进行存储操作的结构,简称Java容器
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储

一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有一些弊端,而Java集合就像一种容器,可以动态地把多个对象的引用放入容器中。

  • 数组在内存存储方面的特点:
    • 数组初始化以后,长度就确定了。
    • 数组声明的类型,就决定了进行元素初始化时的类型
  • 数组在存储数据方面的弊端:
    • 数组初始化以后,长度就不可变了,不便于扩展
    • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数
    • 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
    • 数组存储的数据是有序的、可以重复的,不能满足村粗无需、不可重复数据的需求 → 存储数据的特点单一

Java集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。

Java集合可分为Collection和Map两种体系

面试会经常问同一种集合类型的不同实现类的区别,例如问ArrayList和LinkedList的区别等等,所以应该都要掌握它们的底层实现原理

  • Collection接口:单列数据,定义了存取一组对象的方法的集合
    • List:元素有序、可重复的集合(“动态”数组)
      • ArrayList
      • LinkedList
      • Vector
    • Set:元素无需、不可重复的集合
      • HashSet
      • LinkedHashSet
      • TreeSet
  • Map接口:双列数据,保存具有映射关系“key-value对”的集合
    • HashMap(在面试中高频被问,例如底层实现原理等,区分jdk7和jdk8)
    • LinkedHashMap
    • TreeMap
    • Hashtable
    • Properties

?

Collection接口方法

1、添加
? add(Object obj)
? addAll(Collection coll)
2、获取有效元素的个数
? int size()
3、清空集合
? void clear()
4、是否是空集合
? boolean isEmpty()
5、是否包含某个元素
? boolean contains(Object obj): 是通过元素的equals方法来判断是否
是同一个对象
? boolean containsAll(Collection c): 也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
6、删除
? boolean remove(Object obj): 通过元素的equals方法判断是否是
要删除的那个元素。只会删除找到的第一个元素
? boolean removeAll(Collection coll): 取当前集合的差集
7、取两个集合的交集
? boolean retainAll(Collection c): 把交集的结果存在当前集合中,不
影响c
8、集合是否相等
? boolean equals(Object obj)
9、转成对象数组
? Object[] toArray()
10、获取集合对象的哈希值
? hashCode()
11、遍历
? iterator(): 返回迭代器对象,用于集合遍历

以下代码示例 Collection接口部分方法的使用

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;

public class Sample {
    public static void main(String[] args) {
        Collection coll = new ArrayList();

        //add(Object e):将元素e添加到集合coll中
        coll.add("A");
        coll.add("B");
        coll.add(123);//自动装箱
        coll.add(new Date());

        //size():获取添加元素的个数
        System.out.println(coll.size());//4

        //addAll(Collection coll):将coll集合中的元素添加到当前集合中
        Collection coll1 = new ArrayList();
        coll1.add(456);
        coll1.add("C");
        coll1.addAll(coll);
        System.out.println(coll1);//[456, C, A, B, 123, Fri Jan 21 02:01:35 CST 2022]

        //clear():清空集合元素
        coll.clear();
        System.out.println(coll);//[]

        //isEmpty():判断集合是否为空
        System.out.println(coll.isEmpty());//true
        System.out.println(coll1.isEmpty());//false

        //contains():判断当前集合中是否包含obj
        System.out.println(coll1.contains(123));//true

        //containsAll(Collection coll):判断形参coll中的所有元素是否存在于当前集合中
        coll.add("A");
        coll.add("B");
        System.out.println(coll1.containsAll(coll));//true

        //remove(Object obj):从当前集合中移除obj元素
        System.out.println(coll);//[A, B]
        System.out.println(coll.remove("C"));//false
        System.out.println(coll.remove("A"));//true
        System.out.println(coll);//[B]

        //removeAll(Collection coll):从当前集合中移除coll中的所有元素(差集)
        coll1.removeAll(coll);
        System.out.println(coll1);//[456, C, A, 123, Fri Jan 21 03:08:59 CST 2022]

        //retainAll(Collection coll):交集:获取当前集合和coll集合的交集
        coll1.add("B");
        coll1.retainAll(coll);
        System.out.println(coll1);//[B]

        //equals(Collection coll):要想返回true,需要当前集合和形参集合的元素都相同(ArrayList元素顺序也要相同)
        System.out.println(coll.equals(coll1));//true
        coll.clear();
        coll.add("A");
        coll.add("B");
        coll1.clear();
        coll1.add("B");
        coll1.add("A");
        System.out.println(coll.equals(coll1));//false,因为顺序不同

        //hashCode():返回当前对象的哈希值
        System.out.println(coll.hashCode());//3042

        //toArray():将集合转换为数组
        Object[] arr = coll.toArray();
        for (Object obj : arr) {
            System.out.println(obj);
        }
        //A
        //B
    }
}

contains方法和remove方法注意点:
contains(Object obj)方法是通过调用obj对象所做类的equals方法来判断是否存在元素的,remove方法移除前的对比也是同理,所以会存在以下情况:

import java.util.ArrayList;
import java.util.Collection;

class Person {
    private String name;
    private int age;

    Person(String n, int a) {
        name = n;
        age = a;
    }
}

public class ContainsTest {
    public static void main(String[] args) {
        Collection coll = new ArrayList();
        coll.add(new String("Tom"));
        System.out.println(coll.contains(new String("Tom")));//true

        Person tom = new Person("Tom", 12);
        coll.add(tom);
        System.out.println(coll.contains(tom));//true

        coll.add(new Person("Jerry", 13));
        System.out.println(coll.contains(new Person("Jerry", 13)));//false

        System.out.println(coll.remove(tom));//true
        System.out.println(coll.remove(new Person("Jerry", 13)));//false
    }
}

第一个打印true是因为String类重写了equals方法,equals方法比较的是String对象中的内容而不是String对象的地址。

第二个打印true是因为集合coll中存在tom对象

第三个打印false是因为Person类没有重写equals方法,所以使用的是Object类的equals方法,比较的是对象的地址值,而两个对象都是匿名对象,地址值不同,所以打印false。如果想让这种情况打印true,则需要重写Person类的equals方法

结论:向Collection接口的实现类对象中添加数据obj,要求obj所在类重写equals()

?
数组转换为集合:调用Arrays类的静态方法asList()

import java.util.Arrays;
import java.util.List;

public class Sample {
    public static void main(String[] args) {
        List<String> list = Arrays.asList(new String[]{"A", "B", "C"});
        System.out.println(list);//[A, B, C]

        List<Integer> list1 = Arrays.asList(123, 456);
        System.out.println(list1);//[123, 456]
    }
}

注意:如果asList的参数是int类型的数组,会发现返回的list只有一个元素且打印的是int数组的地址,因为它把int[]作为存储元素的类型,即返回的List是List<Int[]>,而如果使用的是包装类Integer,则是List<Integer>

import java.util.Arrays;
import java.util.List;

public class Sample {
    public static void main(String[] args) {
        List list = Arrays.asList(new int[]{123, 456});
        System.out.println(list);//[[I@85ede7b]

        List list1 = Arrays.asList(new Integer[]{123, 456});
        System.out.println(list1);//[123, 456]
    }
}

?

Iterator迭代器接口

lterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。

GOF给迭代器模式的定义为: 提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。 类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。

Collection接口继承了java.lang.lterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了lterator接口的对象。

lterator仅用于遍历集合,lterator木身并不提供承装对象的能力。如果需要创建lterator对象,则必须有一个被迭代的集合。

集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。

  • hasNext():判断是否还有下一个元素
  • next():①迭代器指针下移 ②将下移以后集合在指针位置上的元素返回
  • remove():在集合中删除迭代器指针上的元素,不同于集合直接调用remove方法

在这里插入图片描述
remove方法的注意点:

  • lterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。
  • 如果还未调用next()或在上一次调用next方法之后已经调用了remove方法,再调用remove(即此时迭代器指针指向的是空元素)都会报IllegalStateException。

示例代码:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Sample {
    public static void main(String[] args) {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(false);

        Iterator iterator = coll.iterator();
        //方式一,但开发一般不这么写
//        System.out.println(iterator.next());//123
//        System.out.println(iterator.next());//456
//        System.out.println(iterator.next());//Tom
//        System.out.println(iterator.next());//false
//        //System.out.println(iterator.next());//报异常NoSuchElementException

        //方式二:但开发一般也不这么写
//        for (int i = 0; i < coll.size(); i++) {
            System.out.println(iterator.next());
        }

        //方式三:推荐写法
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

?

使用foreach循环遍历集合元素

  • Java 5.0提供了foreach循环迭代访问Collection和数组
  • 遍历操作不需获取Collection或数组的长度,无需使用索引访问元素
  • 遍历集合的底层调用Iterator完成操作
  • foreach还可以用来遍历数组
    在这里插入图片描述

注意以下代码:

public class Sample {
    public static void main(String[] args) {
        String[] arr = new String[]{"MM", "MM", "MM"};
        //方式1:普通for循环
        for (int i = 0; i < arr.length; i++) {
            arr[i] = "GG";
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);//输出都是"GG"
        }
        //方式2:增强for循环
        for (String s : arr) {
            s = "AA";
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);//输出仍然都是"GG"
        }
    }
}

方式2之所以没修改数组本身的值,是因为在循环中是将数组中的值赋值给了新的String对象s,然后在循环中修改的是对象s的值,原数组的值没变

?

Collection子接口一:List

  • 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
  • List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引
  • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据予号存取容器中的元素。
  • JDK API中List接口的实现类常用的有: ArrayList、LinkedList和Vector

|—Collection接口:单列集合,用来存储一个一个的对象
? |—List接口:存储有序的、可重复的数据 → “动态”数组,替换原有的数组
? ? |—ArrayList:作为List接口的主要实现类,线程不安全,效率高,底层使用Object[]存储
? ? |—LinkedList:底层使用双向链表存储,对于频繁的插入、删除操作,使用此类效率比ArrayList高
? ? |—Vector:作为List接口的古老实现类,线程安全的,效率低,底层使用Object[]存储(jdk1.0就有了,而List接口、ArrayList、LinkedList都是jdk1.2才有的)

面试题:ArrayList、LinkedList、Vector三者的异同?
同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
不同:见上

LinkedList: 双向链表,内部没有声明数组,而是定义了Node类型的first和last, 用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构。Node除了保存数据,还定义了两个变量:

  • prev变量记录前一个元素的位置
  • next变量记录下一个元素的位置

?

ArrayList的源码分析

jdk7情况下

ArrayList list = new ArrayList(); → 在这个语句下,底层创建了长度是10的Object[]数组elementData
list.add(1); → elementData[0] = new Integer(123);

list.add(11); → 如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。

结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)

jdk8情况下

ArrayList list = new ArrayList(); → 底层Objec[] elementData初始化为{},并没有创建长度为10的数组
list.add(123); → 第一次调用add()时,底层才创建了长度为10的数组,并将数据123添加到elementData中

后续的添加和扩容操作与jdk7无异

小结

jdk7中的ArrayList对象的创建类似于单例的饿汉式,而jdk8中的ArrayList对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存

?

LinkedList的源码分析

LinkedList list = new LinkedList(); → 内部声明了Node类型的first和last属性,默认值为null
list.add(123); → 将123封装到Node中,创建了Node对象
其中,Node的定义体现了LinkedList是双向链表,其定义为:

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

?

Vector的源码分析

jdk7和djk8中使用Vector()构造器创建对象时,底层都创建了长度为10的数组(注意与ArrayList区分)。在扩容方面,默认扩容为原来的2倍(ArrayList是1.5倍)

?

List接口中的常用方法

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

  • void add(int index, Object ele): 在index位置插入ele元素
  • boolean addAll(int index, Collection eles): 从index位置开始将eles中的所有元素添加进来
  • Object get(int index): 获取指定index位置的元素
  • int indexOf(Object obj): 返回obj在集合中首次出现的位置,找不到的话返回-1
  • int lastlndexOf(Object obj): 返回obj在当前集合中末次出现的位置,找不到的话返回-1
  • Object remove(int index): 移除指定index位置的元素,并返回此元素 【注意这个是重载方法,与Collection类的remove方法区分,这里删除的是index位置的元素】
  • Object set(int index, Object ele): 设置指定index位置的元素为ele
  • List subList(int fromIndex, int tolndex): 返回从fromIndex到tolndex位置(不包含toIndex上的元素,是左闭右开的)的子集合

总结:
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:①Iterator迭代器方式 ②增强for循环 ③普通循环

注意:
以下代码之所以第一个输出3而第二个输出1,是因为当使用add方法且参数为list对象时,是将list对象整一个作为一个元素,而不是将参数list对象中的元素添加到当前list对象中,要与addAll方法区分

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Sample {
    public static void main(String[] args) {
        List list = new ArrayList();
        List list1 = Arrays.asList(1, 2, 3);

        list.addAll(list1);
        System.out.println(list.size());//3
        System.out.println(list);//[1, 2, 3]

        list.clear();
        list.add(list1);
        System.out.println(list.size());//1
        System.out.println(list);//[[1, 2, 3]]
    }
}

?

List的一个面试小题

问以下代码的输出:
在这里插入图片描述
其实就是在考验代码标红处的remove方法是依据index删除还是删除值为2的元素

答案是[1,2],这是因为由于add方法没有进行重载所以在add(2)中是进行了2的自动装箱,而remove方法是有重载的,所以这里传入的2就没必要进行自动装箱,直接调用的是remove(int index)方法。如果是要删除值为2的元素,写法应该是 list.remove(new Integer(2));

?

Collection子接口二:Set

  • Set接口是Collection的子接口,Set接口没有提供额外的方法
  • Set集合具有无序性:不等于随机性。以HashSet为例,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定
  • Set集合具有不可重复性:不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败
  • Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法

添加元素的过程(以HashSet为例):
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种计算出在HashSet底层数组中的存放位置(即为索引位置),判断数组此位置上是否已经有元素:
? 如果此位置上没有其他元素,则元素a添加成功 → 情况1
? 如果次位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
? ? 如果hash值不同,则元素a添加成功 → 情况2
? ? 如果hash值相同,进而需要调用元素a所在类的equals()方法:
? ? ? equals()返回true,元素a添加失败
? ? ? equals()返回false,元素a添加成功 → 情况3

对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
jdk7:元素a放在数组中,指向原来的元素
jdk8:原来的元素在数组中,指向元素a
巧记:七上八下
HashSet底层:数组+链表(HashMap)

|—Collection接口:单列集合,用来存储一个一个的对象
? |—Set接口:存储无序的、不可重复的数据 → 高中讲的“集合”
? ? |—HashSet:作为Set接口的主要实现类,线程不安全,可以存储null值
? ? ? |—LinkedHashSet:作为HashSet的子类,遍历其内部顺序时,可以按添加的顺序遍历
? ? |—TreeSet:可以按照添加对象指定属性进行排序
?

重写hashCode()方法

原则:

  • 在程序运行时,同一个对象多次调用hashCode()方法应该返回相同的值。
  • 当两个对象的equals()方法比较返回true时,这两个对象的hashCode()方法的返回值也应相等
  • 技巧:对象中用作equals()方法比较的Field,都应该用来计算hashCode值
  • 当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是要改写hashCode(),根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法,它们仅仅是两个对象
  • 因此,违反了**“相等的对象必须具有相等的散列码”**
  • 结论: 复写equals方法的时候一般都需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。

以Eclipse/IDEA为例,在自定义类中可以调用工具自动重写equals和hashCode。问题: 为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?

  • 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高(减少冲突)
  • 并且31只占用5bits, 相乘造成数据溢出的概率较小
  • 31可以由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化(提高算法效率)
  • 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)

HashSet

  • HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类
  • HashSet按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能
  • HashSet具有以下特点:
    • 不能保证元素的排列顺序
    • HashSet不是线程安全的
    • 集合元素可以是 null
  • HashSet集合判断两个元素相等的标准: 两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等
  • 对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Objectobj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。

HashSet底层使用的是HashMap来实现的,即使用HashMap的key来记录HashSet中的每一个元素,但同时对应的value也不是设为null,而是通过new一个static的Object()对象,没有特殊含义但用于避免调用时发生空指针问题,同时使用的是static修饰,所以所有的HashSet元素作为key在HashMap中映射到的都是同一个Object对象(这里理解就可以了,面试一般不会问)

LinkedHashSet

  • LinkedHashSet是HashSet的子类
  • LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的(但它仍然是存储无序的数据,但凡是用hashCode值来决定元素的存储位置的,都是无序的,不要跟插入顺序搞混
  • LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
  • LinkedHashSet不允许集合元素重复。

TreeSet

  • 向TreeSet中添加的数据,要求是相同类的对象(不然没法进行排序)
  • TreeSet是 SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态
  • TreeSet和TreeMap底层都是使用红黑树结构存储数据,特点是:有序,查询速度比List快
  • 新增的方法如下:(了解)
    • Comparator comparator()
    • Object first()
    • Object last()
    • Object lower(Object e)
    • Object higher(Object e)
    • SortedSet subSet(fromElement, toElement)
    • SortedSet headSet(toElement)
    • SortedSet tailSet(fromElement)
  • TreeSet两种排序方法:
    • 自然排序(即实现Comparable接口,且自然排序中比较两个对象是否相等的标准:compareTo()是否返回0,而不是使用equals())
    • 定制排序(使用Comparator,且定制排序中比较两个对象是否相等的标准:compare()是否返回0,而不是使用equals())
    • 默认情况下,TreeSet采用自然排序。

Set的两道面试题

1、在List内去除重复数字值,要求尽量简单

答案:直接使用HashSet,注意这样做的话list内的对象如果是自定义类,记得重新写hashCode方法和equals方法
在这里插入图片描述
2、请问以下代码的输出是什么
在这里插入图片描述
答案:(如果答案看不懂,就看尚硅谷java视频p547)要注意Person类已经重写了hashCode方法和equals方法,下面是每一个输出的解析:
第一个输出中,p1对象并没有被移除,这是因为p1的name属性值进行了改变,其哈希值也有变化,但是p1在set中存储的位置仍然是使用之前的哈希值计算得来的,所以在调用remove方法时,使用p1新的哈希值进行检测p1是否存在于set中,所以检测不到,p1没有被移除
第二个输出中,添加新对象,这时候是会成功添加的,set里面会存在两个name都是"CC"的对象,产生了重复元素。原因是因为在加入新的对象时,使用新对象的哈希值判断是否存在这个元素,而p1存储的位置是使用p1之前的哈希值计算得来的,所以判断结果是没有重复存在这个元素,所以会添加成功
第三个输出中,添加新对象,这时候也是会成功添加的。因为在加入新的对象时,使用新对象的哈希值判断是否存在这个元素,计算得出的存储位置跟p1的存储位置是相同的,发现这个存储位置上存在元素,会调用新对象的equals方法与p1进行比较,结果两人的哈希值不同,所以会添加成功
?

Map接口

|—Map:双列数据,存储key-value对的数据,类似于高中的函数 y = f(x)
? |—HashMap:作为Map的主要实现类,线程不安全,效率高,可以存储null的key和value
? ? |—LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素的,对于频繁的遍历操作,执行效率高于HashMap
? |—TreeMap:保证按照添加的key-value对进行排序,实现排序遍历,此时考虑key的自然排序或定制排序,因为具有排序特点,底层使用的是红黑树
? |—Hashtable:作为古老的实现类,线程安全的,效率低,不能存储null的key和value(jdk1.0才有,Map、HashMap、TreeMap都是在jdk1.2出现的,LinkedHashMap是在jdk1.4出现的)
? ? |—Properties:常用来处理配置文件,key和value都是String类型
?
面试题:
1、HashMap和Hashtable的异同?
答:见上
2、CurrentHashMap与Hashtable的异同?(暂时不讲)

Map的结构

一个key-value对为一个Entry对象
与函数 y = f(x) 进行类比,x为key,y为value,(x, y)是Entry

  • Map中的key:无序的、不可重复的,使用Set存储所有的key,具体使用哪种Set的实现类会使key有不同的特征 → key所在的类要重写equals()和hashCode()(以HashMap为例)
  • Map中的value:无序的、可重复的,使用Collection存储所有的value → value所在的类要重写equals()(这是因为查找value依据的是key,所以value不用重写hashCode())
  • Map中的Entry:无序的、不可重复的,使用Set存储所有的entry

HashMap

HashMap的底层:
jdk7及之前:数组+链表
jdk8:数组+链表+红黑树

高频面试题:HashMap的底层实现原理
以jdk7为例说明:
HashMap map = new HashMap():在实例化之后,底层创建了长度为16的一维数组Entry[] table
map.put(key, value):首先,调用key所在类的hashCode()计算key哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置:
? 如果此位置上的数据为空,此时的key-value添加成功 → 情况1
? 如果此位置上的数据不为空,意味着此位置上存在一个或多个数据(以链表形式存在),比较key和已经存在的一个或多个数据的哈希值
? ? 如果key的哈希值与已经存在的数据的哈希值不相同,此时key-value添加成功 → 情况2
? ? 如果key的哈希值与已经存在的某一个数据的哈希值相同,继续进行比较:
? ? ? 如果equals()返回false:此时key-value添加成功 → 情况3
? ? ? 如果equals()返回true:使用新的键值对替换原本的键值对,实现了修改功能

补充:关于情况2和情况3,此时的key-value和原来的数据以链表的方式存储

在不断的添加过程中,会涉及到扩容问题,当已存储个数超出临界值(threshold,是通过数组长度乘加载因子计算得到的,默认加载因子为0.75,则threshold值为12)且要新存放的位置非空值时(即已经有存放元素),会进行扩容,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来

jdk8与jdk7在底层实现方面的不同处:
1、new HashMap():底层没有创建一个长度为16的数组
2、jdk8中的数组是Node[],而不是Entry[]
3、首次使用put方法时,底层才创建长度为16的数组
4、jdk7底层结构只有数组+链表,jdk中的底层结构是数组+链表+红黑树,当数组的某一个索引位置上的元素以链表形式存在的数据个数大于8且当前数组长度大于64时,此时此索引位置上的所有数据改为使用红黑树存储(提高了查找效率)
5、这里也存在七上八下的区别,参照HashSet

HashMap源码中存在的一些变量的解释:
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,默认是16
DEFAULT_LOAD_FACTOR: HashMap的默认加载因子,值是0.75
threshold: 扩容的临界值,为容量×填充因子 → 16 × 0.75 = 12
TREETFY_ THRESHOLD: Bucket中链表长度大于该默认值时转化为红黑树,值是8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,值是64

对于HashMap的源码分析其实还包含很多这里没有提到的内容,有兴趣可以看尚硅谷java视频p552和p553听老师的源码分析

LinkedHashMap

底层实现原理:LinkedHashMap中的Entry继承HashMap中的Node,同时添加了新的before和after,用于记录添加的先后顺序(双向链表)
在这里插入图片描述

Map中的常用方法

添加、删除、修改操作:

  • Object put(Object key,Object value): 将指定key-value添加到(或修改)当前map对象中
  • void putAll(Map m): 将m中的所有key-value对存放到当前map中
  • Object remove(Object key): 移除指定key的key-value对,并返回value
  • void clear(): 清空当前map中的所有数据

元素查询的操作:

  • Object get(Object key): 获取指定key对应的value
  • boolean containsKey(Object key): 是否包含指定的key
  • boolean containsValue(Object value): 是否包含指定的value
  • int size(): 返回map中key-value对的个数
  • boolean isEmpty(): 判断当前map是否为空
  • boolean equals(Object obj): 判断当前map和参数对象obj是否相等

元视图操作的方法:

  • Set keySet(): 返回所有key构成的Set集合
  • Collection values(): 返回所有value构成的Collection集合
  • Set entrySet(): 返回所有key-value对构成的Set集合

元视图操作方法其实对应的就是key、value、entry各自的实现,我们上面讲到key、entry是用Set实现的,value是用Collection实现的,于是就有对应的方法

元视图操作方法示例代码:

import java.util.*;

public class Sample {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("AA", 123);
        map.put(45, 1234);
        map.put("BB", 56);

        //遍历所有的key集:keySet()
        Set set = map.keySet();
        Iterator iterator = set.iterator();
        while (iterator.hasNext())
            System.out.print(iterator.next() + " ");

        System.out.println();

        //遍历所有的value集:values()
        Collection values = map.values();
        for (Object obj : values)
            System.out.print(obj + " ");

        System.out.println();

        //遍历所有的key-value:
        //方式一:使用entrySet()
        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()) {
            Object obj = iterator1.next();
            //entrySet集合中的元素都是entry
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + " --> " + entry.getValue());
        }
        //方式二:key集
        Set set2 = map.keySet();
        Iterator iterator2 = set2.iterator();
        while (iterator2.hasNext()) {
            Object key = iterator2.next();
            Object value = map.get(key);
            System.out.println(key + " ===> " + value);
        }
    }
}

输出:

AA BB 45
123 56 1234
AA --> 123
BB --> 56
45 --> 1234
AA ===> 123
BB ===> 56
45 ===> 1234

总结:
增:put(Object key, Object value)
删:remove(Object key)
改:put(Object key, Object value)
查:get(Object key)
插:没有,因为是无序的
长度:size()
遍历:keySet() / values() / entrySet()

TreeMap

向TreeMap中添加key-value,要求key必须是由同一个类创建的对象,因为要按照key进行排序:自然排序 / 定制排序
注意:自然排序和定制排序都只能按照key排序,key是由Set实现的,所以详细的排序方法可以参考TreeSet,都是通过实现Comparable或Comparator

Properties

  • Properties类是Hashtable的子类,该对象用于处理属性文件
  • 由于属性文件里的key、value都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
  • 存取数据时,建议使用setProperty(String key, String value)方法和getProperty(String key)方法

?

Collections工具类

  • Collections是一个操作Set、List和Map等集合的工具类(类似的,操作数组的工具类是Arrays)
  • Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法

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

注意copy方法的使用:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Sample {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);

        //以下方式报异常:IndexOutOfBoundsException("Source does not fit in dest")
        //List dest = new ArrayList();
        //Collections.copy(dest,list);

        //正确写法:
        List dest = Arrays.asList(new Object[list.size()]);
        Collections.copy(dest, list);
        System.out.println(dest);//[1, 2, 3]
    }
}

上文所述后来出现的高效率的集合都是线程不安全的,Collections类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Sample {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);

        //返回的list1即是线程安全的List
        List list1 = Collections.synchronizedList(list);
    }
}

面试题:Collection和Collections的区别

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-01-25 10:26:52  更:2022-01-25 10:28:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:53:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码