Java集合框架知识总结

news2024/9/24 1:15:08

前言

Java集合框架主要由两个接口及其下面的实现类构成,这两个接口分别是Map接口Collection接口,下面先通过其对应的UML类图看下这两个接口的具体实现,如下

1、Map接口

在这里插入图片描述
Map接口的主要实现有我们熟悉的HashMapHashTable以及TreeMapConcurrentHashMap等,其中,AbstractMap提供了Map接口的大部分方法的实现,最大限度的减少实现Map接口的工作量,这在AbstractMap的开篇写道this class provides a skeletal implementation of the Map interface, to minimize the effort required to implement this interface.,下面,我们将详细介绍Map接口的几个实现类。

2、Collection接口

在这里插入图片描述
Collection接口实现了Iterator接口,它下面有三个主要接口继承其本身,List、Queue和Set,这三个接口是我们常见的实现类的接口,比如ArrayList、LinkedList、HashSet等等,其中的AbstractCollection、AbstractList等同上面的AbstractMap的作用。这里的Queue接口的实现我画出的是阻塞队列ArrayBlockingQueue和LinkedBlockingQueue,这两个队列我自己的应用是在线程池中设置阻塞队列,后面简单介绍一下。

至此,集合框架的两个大接口的常见实现有了一个大体的了解,下面,针对常用的集合进行具体介绍。

一、Map接口

1、HashMap

提到Map接口,第一个想到的就是HashMap了,对于其组成,有两个版本,jdk8之前,由数组+链表组成,数组是存储的主要位置,链表是为了解决哈希冲突;在jdk8之后,有了一个很大的变化,由数组+链表/红黑树组成,当链表长度大于树化阈值时,会转变成红黑树,从而减少搜索时间,下面基于jdk8做一个分析。

结构

DEFAULT_INITIAL_CAPACITY,默认初始容量(数组大小),16(1<<4)
MAXIMUM_CAPACITY,最大容量,1<<30
DEFAULT_LOAD_FACTOR,默认负载因子,0.75f
TREEIFY_THRESHOLD,树化阈值,8
UNTREEIFY_THRESHOLD,链表化阈值,6
MIN_TREEIFY_CAPACITY,最小树化节点数量,树化的另一个参数,当哈希表中的所有桶个数超过64时,才允许树化,64
threshold,扩容阈值,`capacity *  loadFactory`(容量*加载因子)
size,存储的元素个数,即k-v键值对的个数
modCount,散列表结构变化的次数,例如增删节点(修改值不算),用于快速失败机制

前面说过,HashMap由数组+链表/红黑树组成,其中,数组存储的是node链表,transient Node<K,V>[] table;,node节点如下组成

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash; //元素的hash值
        final K key; //键
        V value; //值
        Node<K,V> next; //指向下一个节点
    }

因此,我们先大概画一下HashMap的结构
在这里插入图片描述
数组的查询速度快,链表的插入速度快,下面,开始分析数据的插入、删除、扩容等相关内容,当某个链表的长度超过树化阈值(8)同时节点个数超过最小树化节点数(64)时,才转变成红黑树;如果没有达到最小树化节点数,那么考虑进行数组扩容(容量变成原来的2倍),而不是转变红黑树,从而减少搜索时间

传入初始化容量

当我们传入容量initialCapacity时,构造函数会调用一个tableSizeFor方法,进行数组的容量初始化,通过这个方法返回一个大于等于initialCapacity的2的幂次方的数,例如传入7,返回的是8

static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

这也引申出,数组的容量是2的幂次方数,这是因为通过哈希值(32位int类型数)计算数组下标时候,做法是hash&(n.length-1),因此,只有是2的n次方,进行-1操作才能保证2进制数每位都是1,这样按位与才能计算出非0的数,保证数据分散均匀

特点

HashMap存储是无序的;键和值都可以为null,但是只能有一个null键;线程不安全

hash算法&hash碰撞

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

hash算法:让key的hashcode异或hashcode无符号右移16位,这样做是为了让hashcode的高16位也能参与运算,从而使得散列均匀,减少hash碰撞
hash碰撞:两个不同的key,a.hashcode == b.hashcode,这样会去比较a.equals(b),如果true,那么覆盖值,否则,将节点插入链表尾部(jdk7插入头部)

put添加流程

注意,put方法是有返回值的,返回的是之前存储的value(即返回老值),hashmap是懒加载,在第一次put的时候才会创建数组

判断当前数组是否为空,若为空,调用resize方法初始化数组
根据key计算hash值,根据hash值找到数组下标位置(数组长度-1 & hash 计算)
	如果当前下标没有节点,将节点e放入当前位置
	当前位置存在节点p,如果p的hash值等于e的hash值(发生hash冲突)
			如果key存在,覆盖value值
			key不存在,要插入新节点,判断p是不是树节点
				如果是树节点,挂到树上
				否则,判断是否树化,若不树化插入链表的尾部,树化则挂到树节点
	完成这些,根据元素个数是否超过扩容阈值,是否扩容			

resize扩容流程

扩容是在hashmap容量 > 初始容量*负载因子 时发生的,扩容是将数据长度放大到原来的2倍,扩容后节点的存储位置要么是原位置,要么是原位置+原数组长度的位置,扩容分为两大步,计算新的数组容量和迁移旧数据

计算数组容量:
旧容量大于0
	旧容量大于等于最大容量
		扩容因子赋值为Int最大值,返回老容量
		否则新容量 = 2*旧容量,新容量小于最大容量且旧容量大于等于默认容量,新扩容阈值 = 2*旧阈值
旧容量小于0且旧扩容阈值大于0
	新容量 = 旧扩容阈值
旧容量和旧阈值都小于0(还未初始化)
	默认容量和默认阈值		
转移元素:
创建新数组,容量为之前的2倍
遍历旧数组
	如果只有头节点,直接计算新的位置并赋值
	如果是树结构,单独处理
	如果是链表,计算节点下标,链接到新数组的链表上			

存放自定义对象时,需要注意什么

存放自定义对象时,重写对象的hashcode和equals方法。首先我们知道hashmap的key是不允许重复,如果没有重写equals方法,我们看下如下代码

public static void main(String[] args) {
        user user1 = new user("张三");
        user user2 = new user("张三");
        HashMap<user,String>hashMap = new HashMap<>();
        hashMap.put(user1,"1");
        hashMap.put(user2,"2");
        System.out.println(hashMap.size());
    }

上面代码的运行结果是2,这里先引申下== 和 equals的区别

==比较的是两个对象地址是不是相等
	对于基本数据类型,比较的是值
	对于引用数据类型,比较的是内存地址
equals比较的是两个对象是否相等
	若未重写equals方法,那么作用等同于==,比较的是对象地址
	若重写equals方法,那么一般情况是重写比较两个对象的内容是否相等

因此,这里的user1和user2是两个对象,所以结果是2,那么我们重写了equals方法之后是否就输出1了呢?答案是否定的,因为hashmap在放入对象的时候是先通过key计算hash值的,再没重写hashcode方法时,调用的是Object类的一个native方法,返回的是不同值,因此计算数组下标不同,所以可以存进去。那么我们两个方法都重写之后,才能确定是同一个key

public class user {
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private String name;

    public user(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        user user = (user) o;
        return Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

是否线程安全?怎么做到线程安全?

HashMap是非线程安全的,可以通过Collections.synchronizedMap(hashMap);方法将HashMap转变成线程安全,这是因为SynchronizedMap内部维护了一个互斥锁mutex,通过synchronized对HashMap的s方法做了修饰;同时我们可以考虑使用ConcurrentHashMap保证线程安全

为什么String、Integer等包装类适合作为HashMap的key?

包装类的特性能够保证hash值的不可更改性和计算准确性,能够有效减少hash碰撞;包装类都是final类型,即保证key的不可更改性,不会存在获取hash值不同的情况;
包装类内部重写了hashCode()方法和equals()方法;

⑩如何实现HashMap的有序性

可以考虑使用LinkedHashMap或者TreeMap。以LinkedHashMap为例,LinkedHashMap的结构大体上和HashMap没什么两样,其Entry对象增加了两个属性,Entry<K,V> before, after;即增加了一个标识签后节点的before和after,形成了一个双向链表,这样,在存储数据的时候,就已经做到了有序存储

fail-fast机制

在遍历HashMap时删除元素或多线程下修改集合结构,会有一个ConcurrentModificationException报错,在前文中说到HashMap有一个modCount来记录结构修改次数,有一个expectedModCount是迭代器期望的修改次数,只有迭代器能修改这个次数,在每次迭代过程中,都会去判断modCount的值是否和expectedModCount相等,如果在迭代过程中add/remove元素,由于add/remove方法不是迭代器的方法,所以不会修改expectedModCount,这就导致了报错。这个机制适用于java.util包下的所有集合。那么,怎么做到一边遍历一遍删除呢?使用迭代器Iterator.remove方法或者倒序遍历删除。对应fail-fast机制,还有fail-safe机制,用在JUC下的容器中

如何确定一个较为合适的容量

在使用HashMap时,传入一个合适的初始容量可以有效避免扩容,可以参考putAll方法中的一个公式float ft = ((float)s / loadFactor) + 1.0F,只要这个ft不超过容量最大值就返回(int)ft,否则使用最大值。例如,我们期望存储7个元素,那么7/0.75+1最终得到10,然后又因为2的幂次方,因此找大于等于10且最接近的值,就是16,因此,传入16;当然,我们也可以使用Guava中提供的Maps.newHashMapWithExpectedSize(7)方法,直接初始化期望容量的HashMap

2、ConcurrentHashMap

结构

ConcurrentHashMap底层也是由数组+链表组成,在jdk7中ConcurrentHashMap由segment数组(即将hashEntry数组分成几个小段得到的数组)+hashEntry数组构成,每个segment加一把锁(继承ReentrantLock,可重入锁,默认并发度16),互不影响;在jdk8中,与HashMap构成相同,采用Node数组+链表/红黑树组成,采用cas+synchronized,锁的是链表的头节点/红黑树的根节点,最大限度的保证并发性

put流程

计算key的hash值,开始自旋,判断是否初始化数组
定位到Node,去拿首节点f
如果f为null,f = tabAt(tab, i = (n - 1) & hash),通过cas插入节点
如果f.hash==MOVED(-1),参与扩容
synchronized锁住f节点(头节点),判断是链表还是红黑树,插入节点
判断是否达到树化条件,树化操作

get流程&是否需要加锁

计算key的hash值,判断数组是否为空,取到对应位置节点e = tabAt(tab, (n - 1) & h)
如果是头节点直接返回
如果是红黑树,就搜索树节点返回
如果是链表,就遍历链表返回

get是不需要加锁的,这是因为Node节点的value和next指针都是通过volatile关键字修饰的,这个关键字保证了多线程下变量修改的可见性

是否允许key、value为null

答案是不允许,if (key == null || value == null) throw new NullPointerException();直接抛出空指针异常。因为concurrentHashMap是线程安全的,假设允许value为null,那么就会产生歧义(这个key存储的是null,这个key不存在),比如我A线程获取到了null,假设是没有这个key,那么contains(key)应该返回false,但是在A线程判断key是否存在时,B线程插入了key-null,那么A线程contains(key)返回的就是true了,线程不安全。因此,为了避免歧义,value不允许为null

3、HashTable

结构

HashTable底层是由Entry数组构成的,每个Entry对象中都有next属性指向下一个对象,因此形成了一个链表,即数组+链表

初始化&扩容

默认初始化容量为11,默认的负载因子为0.75f,如果传入容量为0,则设置为1。扩容后的新容量=旧容量*2+1newCapacity = (oldCapacity << 1) + 1,前提是计算出来的新容量不超过MAX_ARRAY_SIZE(最大数组容量,Integer.MAX_VALUE - 8)

get流程

计算key的hash值
根据hash值和数组长度计算key的数组下标
根据数组下标获取头节点,遍历链表返回对应节点

put流程

判断value是否为null,为null抛出空指针异常
获取key的hash值
根据hash值和数组长度计算下标
获取下标位置节点,逐个遍历
如果有相同key,覆盖值
否则添加节点

线程安全&是否允许key-value为null

HashTable是线程安全的,它的操作方法,例如put、get、containsKey、remove等都是添加了synchronized关键字修饰;key和value都不允许为null,这里的原因和上面ConcurrentHashMap同理

HashTable和HashMap的区别

HashMap线程不安全;HashTable线程安全
HashMap允许key和value为null;HashTable不允许
HashMap默认初始容量16,扩容后容量为之前的2倍;HashTable默认初始容量为11,扩容后容量为之前的2倍+1
HashMap在jdk8结构调整为数组+链表/红黑树;HashTable结构为数组+链表
HashMap效率比HashTable高,若要保证线程安全,考虑使用ConcurrentHashMap而非HashTable,HashTable是jdk1.0提出,HashMap是jdk1.2提出,因此,日常开发不要使用HashTable,基本被抛弃

二、Collection接口

1、ArrayList

结构

ArrayList底层是一个Object类型的数组,这也就导致了ArrayList增删慢,查询快的特性,默认数组长度为10,支持传入初始化容量,若传入值为0,则返回ArrayList内部定义的空数组Object[] EMPTY_ELEMENTDATA,如果不传入数组容量,实际上也是初始化一个空数组Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA,在第一次添加元素的时候进行容量的计算和初始化,先判断是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组,如果是,初始化默认容量和传入最小容量(数组长度+1)的最大值

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }

可以添加null元素,可以重复

扩容

ArrayList的扩容相对来说较为容易,首先计算新容量oldCapacity + (oldCapacity >> 1)
新容量 = 旧容量+旧容量的一半(旧容量右移一位),即新容量=1.5*旧容量
判断新容量是否小于最小容量(即扩容时传入的容量),如果小于,则保持当前容量
判断新容量是否大于最大容量(MAX_ARRAY_SIZE,值为Integer.MAX_VALUE - 8),如果大于,判断当前容量>最大容量(MAX_ARRAY_SIZE)?Integer.MAX_VALUE:MAX_ARRAY_SIZE
确定新容量后,通过Arrays.copyOf将旧数据复制到新数组

线程安全

ArrayList是非线程安全的,例如两个线程向同一个ArrayList添加元素,那么ArrayList可能最终处于不一致的状态,两个线程添加的元素之一可能无法添加到列表中。如果想要保证线程安全,那么考虑使用CopyOnWriteArrayList,在jdk1.0中还有vector,也实现了动态数组,使用了大量的synchronized保证线程安全,但是效率极低,目前很少使用,当然也可以使用Collections.synchronizedList方法处理,它也是通过synchronized关键字保证线程安全

补充:Vector
vector是线程安全的,操作方法通过synchronized关键字修饰,因此效率极低;vector默认初始容量是10,默认扩容为原来的2倍;vector底层也是Object类型的数组

2、CopyOnWriteArrayList

为什么线程安全

首先,CopyOnWriteArrayList和ArrayList在结构上没有太大的区别,我们注意到源码中有一个可重入锁的定义final transient ReentrantLock lock = new ReentrantLock();,而且数组是通过volatile关键字修饰的,保证了可见性,copyOnWrite翻译过来是写时复制,也就是说在操作写数据(增删改)操作时,是先通过重入锁加锁,然后复制一个新数组,对新数组进行操作,最后指向新数组。

3、LinkedList

结构

LinkedList底层是由node节点组成的双向链表,每个节点包含两部分:数据和对序列中前后节点的引用。添加元素可以为null,元素可以重复,添加时,从链表尾部添加;同时,LinkedList还实现了Deque接口,因此具有双端队列功能;非线程安全

ArrayList和LinkedList的区别

  • 底层结构:ArrayList底层是Object类型的数组;LinkedList底层是Node节点组成的双向链表
  • 增删元素:ArrayList受元素位置影响,默认是增加元素到数组尾部,时间复杂度O(1),如果固定位置,那就是O(n-i),需要移动其他元素;LinkedList删除元素的时间复杂度是O(1),只需要断开该元素的连接即可,增加元素默认到链表尾部,如果指定位置那时间复杂度接近O(n)
  • 快速随机访问:ArrayList由于底层是数组,通过数组下标可以实现快速访问;LinkedList不能快速随机访问

4、HashSet&LinkedHashSet

结构

HashSet底层定义了一个HashMap,它的无参构造器就是实现一个HashMap,代码如下public HashSet() {map = new HashMap<>()},因此,HashSet基本上就是靠HashMap的结构和接口实现的,存储的是HashMap中的key,不允许重复,可以为null,无序;HashSet的有参构造可以有三个参数,除了初始容量和加载因子这两个HashMap中的参数外,还有一个布尔类型的dummy参数,通过它来控制实现的map是HashMap还是LinkedHashMap,同理,LinkedHashSet底层就是实现的LinkedHashMap

为什么数据不重复

HashSet 内部使用 HashMap 存储元素,对应的键值对的键为 Set 的存储元素,值为一个默认的 Object 对象PRESENT,通过比较key的hash值和value来确定是否是同一个元素,也就是hashCode方法和equals方法

补充,hashCode和equals的关系:
两个对象相等,那么hashCode一定相等,equals方法返回true
两个对象的hashCode相等,这两个对象也不一定相等
如果重写了equals方法,那么一定要重写hashCode方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/488530.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

信息技术发展

OSI网络标准协议 物理层&#xff1a;联网的媒介 RS232 FDDI 数据链路层: 网络层接收到的数据分割成可被物理层传输的帧 IEEE802.3/.2 ATM 网络层&#xff1a;网络地址翻译成对应的物理地址&#xff0c;路由 IP ICMP IGMP IPX ARP 传输层&#xff1a;端到端的错误恢复和流量控制…

轻松提升投标技术分?smardaten高性价比原型服务受热捧

日前&#xff0c;某个交通领域的软件公司A遇到了难题&#xff0c;十多个在跟的项目需要在一个月内完成投标准备工作。 应用软件“强甲方需求”的大环境下&#xff0c;A公司又陷“投标高峰期”。 更具挑战性的是&#xff0c;其中&#xff0c;有5个项目要求应标企业提供真实系统的…

一文介绍Linux EAS

能量感知调度&#xff08;Energy Aware Scheduling&#xff0c;简称EAS&#xff09;是目前Android手机中Linux线程调度器的基础功能&#xff0c;它使调度器能预测其决策对CPU能耗的影响。依靠CPU的能量模型&#xff08;Energy Model&#xff0c;简称EM&#xff09;&#xff0c;…

steam搬砖,适合个人操作的创业项目

这个项目主就是在Steam平台买进CSGO道具&#xff0c;再放到网易buff平台卖出。因为进价低出价高&#xff0c;所以每卖出一件道具&#xff0c;利润都相当可观。 关键这玩意背靠Steam这个超大平台&#xff0c;日活几千万&#xff0c;几乎覆盖了市面上的所有热门游戏&#xff0c;…

动态规划——逆序对

逆序对Time Limit: 1000 MSMemory Limit: 5000 KB Description 给定一个长度为N的int型数组a[0,1,2,...N-1], 请计算逆序对个数.当i<j且a[i]>a[j], 则称a[i]与a[j]是一对逆序对.Input 第一行输入M表示包含M组测试数据&#xff0c;每组先输入N (N<50000), 接着输入N…

ios app真机测试到上架App Store详细教程-必看

​转载&#xff1a;https://blog.csdn.net/p312011150/article/details/89374401 ios app真机测试到上架App Store详细教程-必看 Appuploader常见问题 转存失败 重新上传 取消 上架基本需求资料 1、苹果开发者账号&#xff08;如还没账号先申请-苹果开发者账号申请教程&…

​​​​魔兽服务端自定义创建传送门教程

魔兽服务端自定义创建传送门教程 大家好我是艾西,今天跟大家分享下魔兽自定义传送门怎么创建。玩过魔兽的朋友都知道,魔兽这游戏内容多地图也非常大,一个老魔兽玩家很熟悉跑副本的情况下从这个地图到下一个地图都得跑半个小时,更何况对于很多得新手小伙伴了,所有顾及到大…

CUDA编程之矩阵乘法

文章目录 一、矩阵乘法回顾二、CUDA内存架构CUDA中的共享内存CUDA中的共享内存使用方法静态申请内存动态申请内存 三、分解矩阵乘法 / 平铺矩阵乘法四、实战代码DS_M 和 DS_N的索引方式解释 一、矩阵乘法回顾 CPU版本&#xff1a; GPU版本&#xff1a; 核函数如下&#xff1…

JeecgBoot低代码平台—默认模糊查询以及高级查询规则

JeecgBoot低代码开发平台&#xff0c;自己封装了一套查询过滤器&#xff0c;默认就支持模糊查询&#xff0c;只是需要前后加上*&#xff0c;虽然麻烦&#xff0c;但是这样是考虑到系统后期数据量大默认模糊查询会导致系统性能问题。当然如果你的系统数据量级别达不到这个情况&a…

高校大数据实验室解决方案

高校实验室建设总目标是搭建一站式教学服务平台&#xff0c;一站式教学服务平台概念是在深刻理解高校学科建设及存在的若干问题上提出。围绕着学科建设的各个方面&#xff0c;从专业开设、课程设置、师资培养、教学资源、实验环境、学生实训实习及就业创业等环节&#xff0c;提…

Android热修复原理与实战

作者&#xff1a;独孤狼 什么是热修复 在我们应用上线后出现bug需要及时修复时&#xff0c;不用再发新的安装包&#xff0c;只需要发布补丁包&#xff0c;在客户无感知下修复掉bug 怎么进行热修复 服务端&#xff1a;补丁包管理 用户端&#xff1a;执行热修复 开发端&#xff1…

Introduction to modern Cryptography 现代密码学原理与协议第一章笔记

加密的语法由三个算法组成:密钥产生&#xff0c;加密&#xff0c;解密 (1) 密钥产生算法Gen是一个概率算法&#xff0c;能够根据方案定义的某种分布方案分布选择并输出一个密钥k (2) 加密算法Enc,输入为密钥k和明文m&#xff0c;输出为密文c。把使用密钥k加密明文m记为Enck(m) …

【Vue 基础】尚品汇项目-06-vuex模块式开发

vuex是官方提供一个插件&#xff0c;状态管理库&#xff0c;集中式管理项目中组件共用的数据。 目录 一、安装 二、vuex的基本使用 三、vuex模块式开发 一、安装 安装命令&#xff1a; npm install vuex3.6.2 --save 如果安装错版本&#xff0c;要先卸载再重新安装&#…

认识Filter(过滤器)

Filter介绍 在计算机编程中&#xff0c;Filter&#xff08;过滤器&#xff09;是一种用于对数据流进行处理的软件组件。Filter 的作用是从输入流中获取数据&#xff0c;对其进行处理后再将其写入输出流中。Filter 组件通常用于数据校验、数据转换、数据压缩等方面&#xff0c;…

微服务知识2

CAP和BASE是分布式必备理论基础 CAP理论 一致性(C)&#xff1a;写操作之后进行读操作无论在哪个节点都需要返回写操作的值 可用性(A)&#xff1a;非故障的节点在合理的时间内返回合理的响应 分区容错性(P)&#xff1a;当出现网络分区后&#xff0c;系统能够继续工作&#x…

家用平价洗地机哪款好?国产性价比高的品牌

在当今社会&#xff0c;人们使用清洁电器已经非常普及&#xff0c;成为了人们日常清洁中必不可少的得力助手了。洗地机在我看来&#xff0c;它在清洁力度上做的十分优秀&#xff0c;无论是干湿垃圾还是顽固污渍&#xff0c;皆可以清洗到位&#xff1b;同时&#xff0c;洗地机配…

《花雕学AI》AI 人工智能伙伴关系的指南:遵循原则,实现实践,展望未来

引言&#xff1a;人工智能&#xff08;AI&#xff09;是指由人造的机器或系统所展现出的智能&#xff0c;它可以模拟或扩展人类的认知功能&#xff0c;如学习、推理、感知、交流等。 人工智能的发展和应用已经深刻地影响了社会、经济、文化和政治等各个领域&#xff0c;同时也带…

( 数组和矩阵) 697. 数组的度 ——【Leetcode每日一题】

❓697. 数组的度 难度&#xff1a;简单 给定一个非空且只包含非负数的整数数组 nums&#xff0c;数组的 度 的定义是指数组里任一元素出现频数的最大值。 你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组&#xff0c;返回其长度。 示例 1&#xff1a; 输…

Java异常处理传递规范总结

java 异常分类 Thorwable类&#xff08;表示可抛出&#xff09;是所有异常和错误的超类&#xff0c;两个直接子类为Error和Exception&#xff0c;分别表示错误和异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常&#xff0c; 这两种异常有很大的区别…

Linux 概述及常用命令(一)

1、Linux 简介 Linux 是一套免费使用和自由传播的类 Unix 操作系统&#xff08;主要用在服务器上&#xff09;&#xff0c;是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。 目前市面上较知名的发行版有&#xff1a;Ubuntu&#xff0c;RedHat&…