首页 文章 精选 留言 我的

精选列表

搜索[知识资产沉淀],共10000篇文章
优秀的个人博客,低调大师

AI 知识:TF-IDF 技术

摘要 TF-IDF(Term Frequency-Inverse Document Frequency)是一种常见的统计方法,用于评估一个词对于一个文档集或一个语料库中的其中一份文档的重要性。本文将全面阐述TF-IDF的通俗理解、技术原理、应用场景,并做以总结。 通俗理解 TF-IDF是一种量化文本中关键词重要性的指标,其核心思想在于:如果某个词在一篇文章中出现次数较多(词频高),并且在其他文章中出现次数较少(文档频率低),则认为这个词对于这篇文章来说是重要的。简而言之,TF-IDF衡量的是某个词在特定文档中与整个语料库中的重要性。 技术原理 TF-IDF的计算由两部分组成:词频(TF)和逆文档频率(IDF)。 **词频(TF)**指的是一个词在文档中出现的次数,它是一个直观的衡量标准,表示词在文档中的相对重要性。 **逆文档频率(IDF)**是衡量词的普遍重要性的一个指标,它由一个词在所有文档中出现的频率的倒数的对数得到。计算公式如下: 其中,( t ) 是一个词,( N ) 是文档总数,( df(t) ) 是包含词 ( t ) 的文档数。 TF-IDF的最终得分则是TF和IDF的乘积: 应用场景 TF-IDF有多种应用场景,以下是一些典型的用途: 文本挖掘:在文本挖掘中,TF-IDF可以用来识别文档中的关键词汇,帮助理解文档的主要内容。 信息检索:在搜索引擎中,TF-IDF可以帮助评估查询词与文档的相关性,进而改进搜索结果的排名。 自然语言处理:在自然语言处理任务中,比如分类、聚类等,TF-IDF常被用于特征提取,将文本数据转换为机器学习模型可以处理的数值数据。 推荐系统:在推荐系统中,TF-IDF可以用于分析用户评论,提取产品特征,以提高推荐的准确性。 总结 TF-IDF是一个强大的文本分析工具,它可以帮助我们从大量的文本数据中提取关键信息,并应用于多种场景,如文本挖掘、信息检索和自然语言处理等。通过计算词频和逆文档频率的乘积,我们可以得到一个词在特定文档中的相对重要性,进而实现对文本数据的有效分析和处理。 🔥 热门文章推荐(2AGI.NET) 开启零样本学习新时代 作者:2AGI 2025年1月12日 Anthropic 重磅推荐:构建有效的代理 作者:2AGI 2025年1月11日 挖掘文本价值:TF-IDF技术 作者:2AGI 2025年1月11日 天天AI-20250110 作者:2AGI 2025年1月10日 Google AI 智能体白皮书,超详细解读(内附下载) 作者:2AGI 2025年1月9日 天天AI-20250109 作者:2AGI 2025年1月9日 天天AI-20250108 作者:2AGI 2025年1月8日 天天AI-20250107 作者:2AGI 2025年1月7日 天天AI-20250106 作者:2AGI 2025年1月6日

优秀的个人博客,低调大师

涨知识了!

@[toc] 松哥之前写过文章跟大家介绍过用 MyCat 实现 MySQL 的分库分表,不知道有没有小伙伴研究过,MySQL 其实也自带了分区功能,我们可以创建一个带有分区的表,而且不需要借助任何外部工具,今天我们就一起来看看。 1. 什么是表分区 小伙伴们知道,MySQL 数据库中的数据是以文件的形势存在磁盘上的,默认放在 /var/lib/mysql/ 目录下面,我们可以通过 show variables like '%datadir%'; 命令来查看: 我们进入到这个目录下,就可以看到我们定义的所有数据库了,一个数据库就是一个文件夹,一个库中,有其对应的表的信息,如下: 在 MySQL 中,如果存储引擎是 MyISAM,那么在 data 目录下会看到 3 类文件:.frm、.myi、.myd,作用如下: *.frm:这个是表定义,是描述表结构的文件。 *.myd:这个是数据信息文件,是表的数据文件。 *.myi:这个是索引信息文件。 如果存储引擎是 InnoDB, 那么在 data 目录下会看到两类文件:.frm、.ibd,作用分别如下: *.frm:表结构文件。 *.ibd:表数据和索引的文件。 无论是哪种存储引擎,只要一张表的数据量过大,就会导致 *.myd、*.myi 以及 *.ibd 文件过大,数据的查找就会变的很慢。 为了解决这个问题,我们可以利用 MySQL 的分区功能,在物理上将这一张表对应的文件,分割成许多小块,如此,当我们查找一条数据时,就不用在某一个文件中进行整个遍历了,我们只需要知道这条数据位于哪一个数据块,然后在那一个数据块上查找就行了;另一方面,如果一张表的数据量太大,可能一个磁盘放不下,这个时候,通过表分区我们就可以把数据分配到不同的磁盘里面去。 MySQL 从 5.1 开始添加了对分区的支持,分区的过程是将一个表或索引分解为多个更小、更可管理的部分。对于开发者而言,分区后的表使用方式和不分区基本上还是一模一样,只不过在物理存储上,原本该表只有一个数据文件,现在变成了多个,每个分区都是独立的对象,可以独自处理,也可以作为一个更大对象的一部分进行处理。 需要注意的是,分区功能并不是在存储引擎层完成的,常见的存储引擎如 InnoDB、MyISAM、NDB 等都支持分区。但并不是所有的存储引擎都支持,如 CSV、FEDORATED、MERGE 等就不支持分区,因此在使用此分区功能前,应该对选择的存储引擎对分区的支持有所了解。 2. 分区的两种方式 不同于 MyCat 中既可以垂直切分又可以水平切分,MySQL 数据库支持的分区类型为水平分区,它不支持垂直分区。 2.1 水平切分 先来一张简单的示意图,大家感受一下什么是水平切分: 假设我的 DB 中有 table-1、table-2 以及 table-3 三张表,水平切分就是拿着我 40 米大刀,对准黑色的线条,砍一剑或者砍 N 剑! 砍完之后,将砍掉的部分放到另外一个数据库实例中,变成下面这样: 这样,原本放在一个 DB 中的 table 现在放在两个 DB 中了,观察之后我们发现: 两个 DB 中表的个数都是完整的,就是原来 DB 中有几张表,现在还是几张。 每张表中的数据是不完整的,数据被拆分到了不同的 DB 中去了。 这就是数据库的水平切分,也可以理解为按照数据行进行切分,即按照表中某个字段的某种规则来将表数据分散到多个库之中,每个表中包含一部分数据,即水平切分不改变表结构。 2.2 垂直切分 先来一张简单的示意图,大家感受一下垂直切分: 所谓的垂直切分就是拿着我 40 米大刀,对准了黑色的线条砍。砍完之后,将不同的表放到不同的数据库实例中去,变成下面这个样子: 这个时候我们发现如下几个特点: 每一个数据库实例中的表的数量都是不完整的。 每一个数据库实例中表的数据是完整的。 这就是垂直切分。一般来说,垂直切分我们可以按照业务来划分,不同业务的表放到不同的数据库实例中。 MySQL 数据库支持的分区类型为水平分区。 此外,MySQL 数据库的分区是局部分区索引,即一个分区中既存放了数据又存放了索引,目前,MySQL数据库还不支持全局分区(数据存放在各个分区中,但是所有数据的索引放在一个对象中)。 3. 为什么需要表分区 可以让单表存储更多的数据。 分区表的数据更容易维护,可以通过清除整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作。 部分查询能够从查询条件确定只落在少数分区上,查询速度会很快。 分区表的数据还可以分布在不同的物理设备上,从而高效利用多个硬件设备。 可以使用分区表来避免某些特殊瓶颈,例如 InnoDB 单个索引的互斥访问、ext3 文件系统的 inode 锁竞争。 可以备份和恢复单个分区。 分区的限制和缺点: 一个表最多只能有 1024 个分区。 如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来。 分区表无法使用外键约束。 NULL 值会使分区过滤无效。 所有分区必须使用相同的存储引擎。 4. 分区实践 说了这么多,来个例子看一下。 首先我们先来查看一下当前的 MySQL 是否支持分区。 在 MySQL5.6.1 之前可以通过命令 show variables like '%have_partitioning%' 来查看 MySQL 是否支持分区。如果 have_partitioning 的值为 YES,则表示支持分区。 从 MySQL5.6.1 开始,have_partitioning 参数已经被去掉了,而是用 SHOW PLUGINS 来代替。若有 partition 行且 STATUS 列的值为 ACTIVE,则表示支持分区,如下所示: 确认我们的 MySQL 支持分区后,我们就可以开始分区啦! 接下来我们来看几种不同的分区策略。 4.1 RANGE 分区 RANGE 分区比较简单,就是根据某一个字段的值进行分区。不过这个字段有一个要求,就是必须是主键或者是联合主键中的某个字段。 例如根据 user 表的 id 进行分区: 当 id 小于 100,数据插入 p0 分区; 当 id 大于等于 100 小于 200 的时候,插入 p1 分区; 如果 id 大于等于 200 则插入 p2 分区。 上面的规则涉及到了 id 的所有范围了,如果没有第三条规则,那么插入一个 id 为 300 的记录时,就会报错。 建表 SQL 如下: create table user( id int primary key, username varchar(255) )engine=innodb partition by range(id)( partition p0 values less than(100), partition p1 values less than(200), partition p2 values less than maxvalue ); 表创建成功后,我们进入到 /var/lib/mysql/test08 文件夹中,来看刚刚创建的表文件: 可以看到,此时的数据文件分为好几个了。 在 information_schema.partitions 表中,我们可以查看分区的详细信息: 也可以自己写个 SQL 去查询: select * from information_schema.partitions where table_schema='test08' and table_name='user'\G 每一行展示一个分区的信息,包括分区的方式、该区的范围、分区的字段、该区目前有几条记录等等。 RANGE 分区有一个比较典型的使用场景,就是按照日期对表进行分区,例如同一年注册的用户放在一个分区中,如下: create table user( id int, username varchar(255), password varchar(255), createDate date, primary key (id,createDate) )engine=innodb partition by range(year(createDate))( partition p2022 values less than(2023), partition p2023 values less than(2024), partition p2024 values less than(2025) ); **注意,createDate 是联合主键的一员。**如果 createDate 不是主键,只是一个普通字段,那么创建时就会抛出如下错误: 现在,如果我们要查询 2022 年注册的用户,系统就只会去搜索 p2022 这个分区,通过 explain 执行计划可以证实我们的想法: 如果想要删除 2022 年注册的用户,则只需要删除该分区即可: alter table user drop partition p2022; 由上图可以看到,删除之后,数据就没了。 4.2 LIST 分区 LIST 分区和 RANGE 分区类似,区别在于 LIST 分区是基于列值匹配一个离散值集合中的某个值来进行选择,而非连续的。举个例子大家看下就明白了: 假设我有一个用户表,用户有性别,现在想按照性别将用户分开存储,男性存储在一个分区中,女性存储在一个分区中,SQL 如下: create table user( id int, username varchar(255), password varchar(255), gender int, primary key(id, gender) )engine=innodb partition by list(gender)( partition man values in (1), partition woman values in (0)); 这个表将来就两个分区,分别存储男性和女性,gender 的取值为 1 或者 0,gender 如果取其他值,执行就会出错,最终执行结果如下: 这样分区之后,将来查询男性或者查询女性效率都会比较高,删除某一性别的用户时删除效率也高。 4.3 HASH 分区 HASH 分区的目的是将数据均匀地分布到预先定义的各个分区中,保证各分区的数据量大致都是一样的。在 RANGE 和 LIST 分区中,必须明确指定一个给定的列值或列值集合应该保存在哪个分区中;而在 HASH 分区中,MySQL 自动完成这些工作,用户所要做的只是基于将要进行哈希分区的列指定一个表达式,并且分区的数量。 使用 HASH 分区来分割一个表,要在 CREATE TABLE 语句上添加 PARTITION BY HASH (expr),其中 expr 是一个字段或者是一个返回整数的表达式;另外通过 PARTITIONS 属性指定分区的数量,如果没有指定,那么分区的数量默认为 1,另外,HASH 分区不能删除分区,所以不能使用 DROP PARTITION 操作进行分区删除操作。 create table user( id int, username varchar(255), password varchar(255), gender int, primary key(id, gender) )engine=innodb partition by hash(id) partitions 4; 4.4 KEY 分区 KEY 分区和 HASH 分区相似,但是 KEY 分区支持除 text 和 BLOB 之外的所有数据类型的分区,而 HASH 分区只支持数字分区。 KEY 分区不允许使用用户自定义的表达式进行分区,KEY 分区使用系统提供的 HASH 函数进行分区。 当表中存在主键或者唯一索引时,如果创建 KEY 分区时没有指定字段系统默认会首选主键列作为分区字段,如果不存在主键列会选择非空唯一索引列作为分区字段。 举个例子: create table user( id int, username varchar(255), password varchar(255), gender int, primary key(id, gender) )engine=innodb partition by key(id) partitions 4; 4.5 COLUMNS 分区 COLUMN 分区是 5.5 开始引入的分区功能,只有 RANGE COLUMN 和 LIST COLUMN 这两种分区;支持整形、日期、字符串;这种分区方式和 RANGE、LIST 的分区方式非常的相似。 COLUMNS Vs RANGE Vs LIST 分区: 针对日期字段的分区不需要再使用函数进行转换了。 COLUMN 分区支持多个字段作为分区键但是不支持表达式作为分区键。 COLUMNS 支持的类型 整形支持:tinyint、smallint、mediumint、int、bigint;不支持 decimal 和 float。 时间类型支持:date、datetime。 字符类型支持:char、varchar、binary、varbinary;不支持text、blob。 举个例子看下: create table user( id int, username varchar(255), password varchar(255), gender int, createDate date, primary key(id, createDate) )engine=innodb PARTITION BY RANGE COLUMNS(createDate) ( PARTITION p0 VALUES LESS THAN ('1990-01-01'), PARTITION p1 VALUES LESS THAN ('2000-01-01'), PARTITION p2 VALUES LESS THAN ('2010-01-01'), PARTITION p3 VALUES LESS THAN ('2020-01-01'), PARTITION p4 VALUES LESS THAN MAXVALUE ); 这是 RANGE COLUMNS,分区值是连续的。 再来看 LIST COLUMNS 分区,这个就类似于枚举了: create table user( id int, username varchar(255), password varchar(255), gender int, createDate date, primary key(id, createDate) )engine=innodb PARTITION BY LIST COLUMNS(createDate) ( PARTITION p0 VALUES IN ('1990-01-01'), PARTITION p1 VALUES IN ('2000-01-01'), PARTITION p2 VALUES IN ('2010-01-01'), PARTITION p3 VALUES IN ('2020-01-01') ); 5. 常见分区命令 添加分区: alter table user add partition (partition p3 values less than (4000)); -- range 分区 alter table user add partition (partition p3 values in (40)); -- lists分区 删除表分区(会删除数据): alter table user drop partition p30; 删除表的所有分区(不会丢失数据): alter table user remove partitioning; 重新定义 range 分区表(不会丢失数据): alter table user partition by range(salary)( partition p1 values less than (2000), partition p2 values less than (4000)); 重新定义 hash 分区表(不会丢失数据): alter table user partition by hash(salary) partitions 7; 合并分区:把 2 个分区合并为一个,不会丢失数据: alter table user reorganize partition p1,p2 into (partition p1 values less than (1000)); 6. 小结 不知道小伙伴们是否还记得松哥 2019 年写的 MyCat 教程(公众号江南一点雨后台回复 2019 有文章索引),这些分区策略是不是和 MyCat 中的策略非常相似呀?感兴趣的小伙伴赶紧去试一把吧~ 参考资料: https://www.cnblogs.com/dw3306/p/12620O42.html

优秀的个人博客,低调大师

AWS 数据加密知识点

一、S3 加密 服务端加密 服务器端加密是由接收数据的应用程序或服务在数据目的地对数据进行加密。Amazon S3 在将您的数据写入数据中心内的磁盘时会在对象级别上加密这些数据,并在您访问这些数据时为您解密这些数据。只要您验证了您的请求并且拥有访问权限,您访问加密和未加密对象的方式就没有区别。例如,如果您使用预签名的 URL 来共享您的对象,那么对于加密和解密对象,该 URL 的工作方式是相同的。此外,在您列出存储桶中的对象时,列表 API 会返回所有对象的列表(无论对象是否加密)。 注意您不能对同一个对象应用不同类型的服务器端加密。 您有三个互斥选项,具体取决于您选择如何管理加密密钥: 具有 Amazon S3 托管密钥的服务器端加密 (SSE-S3) 当您使用具有 Amazon S3 托管密钥的服务器端加密 (SSE-S3) 时,每个对象均使用唯一密钥加密。作为额外的保护,它将使用定期轮换的主密钥对密钥本身进行加密。Amazon S3 服务器端加密使用可用的最强数据块密码之一(即 256 位高级加密标准 (AES-256))来加密您的数据。有关更多信息,请参阅使用具有 Amazon S3 托管加密密钥的服务器端加密 (SSE-S3) 保护数据。 每个对象使用唯一数据秘钥加密 在 AWS Key Management Service 中存储客户主密钥 (CMK) 的服务器端加密 (SSE-KMS) 在 AWS Key Management Service 中存储客户主密钥 (CMK) 的服务器端加密 (SSE-KMS) 与 SSE-S3 类似,使用该服务具有一些额外的好处,但也要额外收取费用。使用 CMK 需要单独的权限,该密钥可进一步防止未经授权地访问 Amazon S3 中的对象。SSE-KMS 还向您提供审核跟踪,显示您的 CMK 的使用时间和使用者。此外,您还可以创建和管理客户托管 CMK,或者使用您、服务和区域独有的 AWS 托管 CMK。有关更多信息,请参阅使用具有 AWS Key Management Service 中存储的 CMK 的服务器端加密 (SSE-KMS) 保护数据。 每个对象使用唯一数据秘钥加密 具有客户提供密钥的服务器端加密 (SSE-C) 使用具有客户提供密钥的服务器端加密 (SSE-C) 时,您管理加密密钥,而 Amazon S3 管理加密(在它对磁盘进行写入时)和解密(在您访问您的对象时)。有关更多信息,请参阅通过使用客户提供的加密密钥的服务器端加密 (SSE-C) 保护数据。 所有对象使用相同的秘钥加密(客户端提供的) 客户端加密 客户端加密 是在将数据发送到 Amazon S3 之前加密数据的行为。要启用客户端加密,您可以选择以下方法: 使用 AWS KMS 中存储的 CMK 在上传对象时 — 通过使用客户主密钥 (CMK) ID,客户端先向 AWS KMS 发送请求以获取可用于加密对象数据的 CMK。AWS KMS 返回两个随机生成的数据密钥版本: 客户端用于加密对象数据的数据密钥的纯文本版本 客户端将作为对象元数据上传到 Amazon S3 的同一数据密钥的密码 blob 下载对象时 — 客户端首先从 Amazon S3 下载加密的对象以及作为对象元数据存储的数据密钥的密码 blob 版本。然后,客户端将密码 blob 发送到 AWS KMS 以获取密钥的纯文本版本,以便让客户端解密对象数据。 客户端将为其上传的每个对象获取一个唯一的数据密钥。 使用在应用程序中存储的主密钥 上传对象时 — 将客户端主密钥提供给 Amazon S3 加密客户端。该客户端仅使用该主密钥来加密客户端随机生成的数据加密密钥。该过程的工作方式如下所示: Amazon S3 加密客户端在本地生成一个一次性对称密钥 (也称为“数据加密密钥”或“数据密钥”)。它使用数据密钥加密单个 Amazon S3 对象的数据。该客户端将为每个对象生成一个单独的数据密钥。 该客户端使用您提供的主密钥来加密数据加密密钥。客户端会将加密的数据密钥及其材料说明作为对象元数据的一部分上传。该客户端利用材料描述来确定要用于解密的客户端主密钥。 该客户端将加密数据上传到 Amazon S3 并在 Amazon S3 中将加密数据密钥保存为对象元数据 (x-amz-meta-x-amz-key)。 下载对象时 — 该客户端从 Amazon S3 下载加密的对象。通过使用对象元数据中的材料说明,该客户端将确定要用于解密数据密钥的主密钥。该客户端将使用该主密钥解密数据密钥,然后使用该数据密钥对对象进行解密。 您提供的客户端主密钥可以是对称密钥,也可以是公有/私有密钥对。 SSE-KMS 需要额外的费用,使用 SSE-S3/SSE-C 免费

优秀的个人博客,低调大师

物联网无线技术知识

云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 如今,人们越来越多地谈论物联网设备,但这些设备如何与网络通信?让我们看看这篇文章的内容! 物联网(IoT)系统由连接到互联网的智能设备组成,这些设备使用处理器、传感器和通信硬件等集成系统来发送所采集的数据。物联网设备通过连接到将信息发送到云进行分析的网关来共享数据。这些设备使用的连接协议在很大程度上取决于要实现的特定物联网应用程序。 现在,世界上到处都是无线连接,有不同的技术:Wi-Fi、蓝牙、LoRa、Zigbee、Z-Wave、NFC等。在本文中,我们将分析物联网应用中最常见的标准:蓝牙低能耗(blue)、Wi-Fi和LoRa。可以根据要实现的应用来区分这些无线技术。 Wi-Fi设备非常适合需要短距离和高速数据传输的应用。另一方面,对于需要低能量和短程传输的应用,BLE技术是最好的解决方案。LoRa技术用于需要远距离数据传输和低能耗的系统。 物联网:用于高速数据传输的Wi-Fi Wi-Fi技术适用于所有有高速数据传输要求的应用。这无疑是世界上应用最广泛的无线传输技术。平板电脑、笔记本电脑、手机、智能电视、照相机、打印机、汽车等设备现在都采用Wi-Fi技术。 Wi-Fi网络是基于IEEE802.11标准的本地无线网络(WLAN),信号通过2、4和5GHz之间的频率传输。带有Wi-Fi模块的设备通过接入点相互连接。设备向接入点发送访问请求的信号,并且只有在认证和关联过程之后才允许数据传输。 物联网:蓝牙低能耗、低能耗数据传输 通过蓝牙低能耗(BLE)的数据传输可用于任何不需要高速的应用。该技术可用于无线充电应用、健康监测、可穿戴设备和智能家居。蓝牙技术作为一种无线传输方式,诞生于RS-232串行协议的有线数据传输。从BT4.0开始,这项技术开始支持低功耗设备(BLE)。 市场上有可以同时支持经典蓝牙(BT)和低功耗蓝牙的双模设备。BLE的能耗可估计为经典蓝牙的1%至50%,这就是为什么它们特别适用于物联网应用的原因。BLE设备估计电池寿命为1到40个月。例如,用于室内定位系统的iBeaconBT设备可以轻松工作数年。 LoRa,远程传输协议 LoRa(Long-Range)技术适用于所有需要小数据包远程传输的应用,在城市环境中可以估计到5到10公里的距离,在农村地区可以估计到30公里的距离。LoRa协议包括不同的应用领域:智能农业、智能建筑(失水检测、结构健康监测)和智能房屋管理(恒温器、洒水器、门锁)。LoRa使用源自CSS(线性扩频)调制的扩展频谱调制。所使用的无线电频段低于1GHz,在欧洲为433MHz和868MHz。 这些连接性技术是物联网领域应用最广泛的技术,为了提高连接性,可以在同一个项目中同时采用Wi-Fi和LoRa。例如,数据可以通过LoRa设备收集,然后通过Wi-Fi传输到网络服务器。 【云栖号在线课堂】每天都有产品技术专家分享!课程地址:https://yqh.aliyun.com/live 立即加入社群,与专家面对面,及时了解课程最新动态!【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK 原文发布时间:2020-05-14本文作者:Cassie 编译本文来自:“千家网”,了解相关信息可以关注“千家网”

优秀的个人博客,低调大师

Python机器学习小知识:lambda

关于Lambda的定义:Lambdas are one line functions. They are also known as anonymous functions in some other languages. You might want to use lambdas when you don’t want to use a function twice in a program. They are just like normal functions and even behave like them. 大意为:lambda函数也叫匿名函数,即没有具体名称的函数,它允许快速定义单行函数,可以用在任何需要函数的地方。这区别于def定义的函数。 lambda调用方式为:lambda [arg1 [, agr2,.....argn]] : expression) lambda与def函数的区别: 1)lambda只是一个表达式,而def则是一个语句。lambda一般用来定义简单的函数,而def可以定义复杂的函数。 2)def创建的方法是有名称的,而lambda没有。 3)lambda会返回一个函数对象,但这个对象不会赋给一个标识符,而def则会把函数对象赋值给一个函数名。 4)lambda表达式的冒号(:)后面,只能有一个表达式,def则可以有多个。 5)像if或for或print等语句不能用于lambda中,def可以。 例如:add = lambda x, y: x + y print(add(3, 5)) Output: 8 List排序a = [(1, 2), (4, 1), (9, 10), (13, -3)]a.sort(key=lambda x: x[1])print(a) Output: [(13, -3), (4, 1), (1, 2), (9, 10)]

优秀的个人博客,低调大师

【Vue_01】基础知识

一、Vue 介绍 1. 作者介绍 2. Vue 简介 ① Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。 ② Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。 ③ Vue 借鉴了 Angular 的模板和双向绑定技术;借鉴了 react 的组件化和虚拟 DOM 技术。 ④ MVVM 模式是 Model-View-ViewModel 的缩写,Model 代表数据模型,定义数据操作的业务逻辑,View 代表视图层,负责将数据模型渲染到页面上,ViewModel 通过双向绑定把 View 和 Model 进行同步交互,不需要手动操作DOM 的一种设计思想 3. MVVM 介绍 M: 即Model,模型,包括数据和一些基本操作 V: 即View,视图,页面渲染结果 VM:即View-Model,模型与视图间的双向操作(无需开发人员干涉) 在MVVM之前,开发人员从后端获取需要的数据模型,然后要通过DOM操作Model渲染到View中。而后当用户操作视图,我们还需要通过DOM获取View中的数据,然后同步到Model中。 而MVVM中的VM要做的事情就是把DOM操作完全封装起来,开发人员不用再关心Model和View之间是如何互相影响的, 只要我们Model发生了改变,View上自然就会表现出来。当用户修改了View,Model中的数据也会跟着改变。把开发人员从繁琐的DOM操作中解放出来,把关注点放在如何操作Model上。 二、快速使用 1. Vue 起步 创建一个 HTML 文件,引入: <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- 生产环境版本,优化了尺寸和速度 --> <script src="https://cdn.jsdelivr.net/npm/vue"></script> 【注意】使用自闭合标签可能会出现问题 2. 声明式渲染 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>第一个程序</title> <script src="vue.js" ></script> </head> <body> <div id="app"> <!-- 插值表达式 --> <h2>姓名:{{name}},年龄:{{age}}</h2> </div> <script> // 创建vue实例 var app = new Vue({ el:"#app", // el即element,该vue实例要渲染的页面元素 data:{ // 渲染页面需要的数据 name : "王大锤", age : 250 } }); </script> </body> </html> 此时我们已经成功创建了一个 Vue 应用,打开 HTML 页面会显示 Hello Vue! 。这就是声明式渲染,Vue 的核心是:采用简洁的模板语法声明式地将数据渲染进 DOM 的系统。这里的核心思想就是没有繁琐的 DOM 操作,例如 jQuery 中,我们需要先找到 div 节点,获取到 DOM 对象,然后进行一系列的节点操作。 三、Vue实例 1. 创建Vue实例 var app = new Vue({ // Vue实例 el:"#app", // Vue可以对id为app的div元素进行渲染,该div之外的无法控制 data:{ // 数据,存放可能会用到的数据 name : "王大锤", age : 250 }, methods: { // 方法,声明可能会用到的方法 test() { console.log("测试方法"); } } }); 2. 双向绑定 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>第一个程序</title> <script src="vue.js" ></script> </head> <body> <div id="app"> 请输入年龄:<input type="text" v-model="age"> <h2>姓名:{{name}},年龄:{{age}}</h2> </div> <script> var app = new Vue({ el:"#app", data:{ name : "王大锤", age : 250 } }); </script> </body> </html> 其中 input 的的变化可以影响到 age,age 的变化也可以影响到 input 3. 生命周期 【生命周期钩子函数】 beforeCreated:在 Vue 实例化之前调用,也可以将他理解为初始化函数。在 Vue1.0 时,这个函数的名字就是 init。 created:在创建实例之后进行调用。 beforeMount:页面加载完成,没有渲染。此时页面还是 {{name}} mounted:他的功能就是:在 dom 文档渲染完毕之后将要执行的函数,该函数在 Vue1.0 版本中名字为 compiled。可以将他理解为原生 js 中的 window.onload=function({.,.}) ,所以也可以理解为 jquery 中的 $(document).ready(function(){….})。此时页面中的 {{name}} 已被渲染 beforeDestroy:该函数将在销毁实例前进行调用 。 destroyed:改函数将在销毁实例后进行调用。 beforeUpdate:组件更新之前。 updated:组件更新之后。 4. this var app = new Vue({ el:"#app", data:{ num : 10 }, methods: { test() { let a = num; // 拿不到值 let b = this.num ++; // 可以拿到值 } } }); 四、模板语法 1. 插值表达式 {{ }} <div id="app"> <h2>姓名:{{name}},年龄:{{age}}</h2> </div> 2. v-model <div id="app"> <!-- 双向绑定 --> <input type="text" v-model="num"> <span>{{num}}</span> </div> <script> var app = new Vue({ el:"#app", data:{ num : 200 } }); </script> 3. v-test / v-html <div id="app"> <!-- 纯文本展示 --> <span v-text="num"></span> <!-- 解析为html --> <span v-html="num"></span> </div> <script> var app = new Vue({ el:"#app", data:{ num : "<h1>Hello Vue!</h1>" } }); </script> 4. v-on 语法 <div id="app"> {{num}} <br/> <!-- 绑定事件 --> <button v-on:click="add">add</button> <!-- 简写形式 --> <button @click="add">add</button> </div> <script> var app = new Vue({ el:"#app", data:{ num : 1 }, methods: { add() { this.num ++; } } }); </script> 事件修饰符 .prevent:阻止默认事件发生 <!-- 阻止默认链接跳转,使用 add 方法处理该事件,也可不处理 --> <a href="" @click.prevent="add">链接</a> 5. v-for 语法 <div id="app"> <lu> <!-- 遍历集合 --> <li v-for="item in list"> {{item}} </li> <hr/> <!-- 遍历集合,拿到索引 --> <li v-for="(item, index) in list"> {{index + 1}}---{{item}} </li> <hr/> <!-- 遍历对象,拿到 key,拿到索引 --> <li v-for="(item, key, index) in user"> {{index + 1}}---{{key}}---{{item}} </li> </lu> </div> <script> var app = new Vue({ el:"#app", data:{ list: ["唐三藏", "熏悟空", "猪八盖", "洒悟净", "小百龙"], user: {name: "王大锤", age: 23} } }); </script> 注意 我们一般会加 key 来提高效率,理想的 key 是每项都有且唯一的 <li v-for="(item, index) in lsit" :key="index"> {{index + 1}}---{{item}} </li> 6. v-if 语法 <div id="app"> <span v-if="flag">10086</span> <span v-if="!flag">10001</span> </div> <script> var app = new Vue({ el:"#app", data:{ flag: true } }); </script> 注意 v-if 和 v-for 在一起使用时,v-for 的优先级高于 v-if <div id="app"> <span v-for="item in list" v-if="item.age > 24">{{item}}</span> </div> <script> var app = new Vue({ el:"#app", data:{ list: [ {name: "001", age: 23}, {name: "002", age: 24}, {name: "003", age: 25} ] } }); </script> v-else-if、v-else 可以与 v-if 连用,但是之间不允许出现任何其他标签 <div id="app"> <!-- 中间间不允许出现任何其他标签 --> <!-- ########## --> <span v-if="num > 80">优秀</span> <span v-else-if="num > 60">及格</span> <span v-else>不合格</span> <!-- ########## --> </div> <script> var app = new Vue({ el:"#app", data:{ num: 61 } }); </script> 7. v-show 与 v-if 类似,但是 v-show 不满足条件时是将标签隐藏 8. v-bind 不使用 v-bind 时,变量会被认为时字符串,而不会解析为值 <!-- 动态绑定 img 地址 --> <div id="app"> <!-- 响应的更新属性 --> <img v-bind:src="url"> <!-- 简写形式 --> <img :src="url"> </div> <script> var app = new Vue({ el:"#app", data:{ url: "http://pic1.win4000.com/pic/0/4a/ff62f14ce3.jpg" } }); </script> --------------------------- <!-- 动态改变字体大小 --> <div id="app"> <!-- 此处要使用驼峰命名法 即:fontSize 而不能使用 font-size --> <span v-bind:style="{fontSize:size}">你好</span> <button @click="add">加大</button> </div> <script> var app = new Vue({ el:"#app", data:{ size: "10px", num: 10 }, methods: { add() { this.num += 5, this.size = this.num + "px" } } }); </script> 五、常用特性 1. 计算属性 computed 计算属性用法与方法相似,但是计算属性有缓存数据,只有在它的相关依赖发生改变时才会重新求值。而且计算属性一定要有返回值。 <div id="app"> {{add}} </div> <script> var app = new Vue({ el:"#app", computed: { add() { return 10 } } }); </script> 2. 监听器 watch <div id="app"> <input type="text" v-model="msg"> </div> <script> var app = new Vue({ el:"#app", data: { msg: "" }, watch: { <!-- 监听 msg --> msg(newValue, oldValue) { console.log("旧数据:" + oldValue + "\t" + "新数据:" + newValue) } } }); </script>

优秀的个人博客,低调大师

最全的微服务知识科普

作者 | 董鹏 阿里巴巴技术专家 微服务 好处:实现跨团队的解耦,实现更高的并发(目前单机只能实现 c10k)不用再拷贝代码,基础服务可以公用,更好的支持服务治理,能够更好的兼容云计算平台。 RPC rpc:像调用本地方法一样调用远程函数; 客户端:一般利用动态代理生成一个接口的实现类,在这个实现类里通过网络把接口名称、参数、方法序列化后传出去,然后控制同步调用还是异步调用,异步调用需要设置一个回调函数; 客户端还需要维护负载均衡、超时处理、连接池管理等,连接池维护了和多个 server 的连接,靠此做负载均衡,当某个服务器宕机后去除该连接。请求上下文维护了请求 ID 和回调函数,超时的请求当回复报文到达后由于找不到请求上下文就会丢弃。 服务端:维护连接,网络收到请求后反序列化获得方法名称,接口名称,参数名称后通过反射进行调用,然后将结果在传

优秀的个人博客,低调大师

Java并发核心知识讲解

并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能。它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰、思维缜密,这样才能写出高效、安全、可靠的多线程并发程序。 一、共享性数据共享性是线程安全的主要原因之一。如果所有的数据只是在线程内有效,那就不存在线程安全性问题,这也是我们在编程的时候经常不需要考虑线程安全的主要原因之一。但是,在多线程编程中,数据共享是不可避免的。最典型的场景是数据库中的数据,为了保证数据的一致性,我们通常需要共享同一个数据库中数据,即使是在主从的情况下,访问的也同一份数据,主从只是为了访问的效率和数据安全,而对同一份数据做的副本 二、原子性原子性就是指对数据的操作是一个独立的、不可分割的整体。换句话说,就是一次操作,是一个连续不可中断的过程,数据不会执行的一半的时候被其他线程所修改。保证原子性的最简单方式是操作系统指令,就是说如果一次操作对应一条操作系统指令,这样肯定可以能保证原子性。但是很多操作不能通过一条指令就完成。 三、互斥性资源互斥是指同时只允许一个访问者对其进行访问,具有唯一性和排它性。我们通常允许多个线程同时对数据进行读操作,但同一时间内只允许一个线程对数据进行写操作。所以我们通常将锁分为共享锁和排它锁,也叫做读锁和写锁。如果资源不具有互斥性,即使是共享资源,我们也不需要担心线程安全。 四、有序性 为了提高性能,编译器和处理器可能会对指令做重排序。重排序可以分为三种: (1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。 (2)指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。 (3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行 五、可见性要理解可见性,需要先对JVM的内存模型有一定的了解,JVM的内存模型与操作系统类似 本文对Java 并发编程中的理论基础进行了讲解,有些东西在后续的分析中还会做更详细的讨论,如可见性、顺序性等。后续的文章都会以本章内容作为理论基础来讨论。如果大家能够很好的理解上述内容,相信无论是去理解其他并发编程的文章还是在平时的并发编程的工作中,都能够对大家有很好的帮助。 参考资料下载:https://pan.baidu.com/s/1qiPjCaoY490QyXBsshP9qA 提取码: ed7p

优秀的个人博客,低调大师

java部分基础知识汇总

1、受检异常和非受检异常的区别? 所有的异常都是继承至Throwable,包括Error和Exception两个大类 Error: 不用捕获,通常是一些底层和硬件的错误,与程序本身无关Exception: 非受检异常:程序本身的异常,如果不主动捕获的,会由jvm去进行处理 受检异常:IOException/SQLException ,必须要去捕获的异常 当发生异常时,可以通过try catch去捕获异常,或通过throws抛出去。 eg:在用户修改密码时,对于开放的临时用户的密码时不允许修改的,修改的时候可以抛出RejectException这样去做,之前调用的代码就会发生变化,因为异常是主逻辑的补充逻辑,修改一个补充逻辑就改了主逻辑,这样是不行的实现类的变更会影响到调用者,这样破坏了封装性。因为一行代码多个捕获条件,使用降低了代码的可读性。这是在使用受检异常时可能会引发的问题。 什么时候使用受检异常? 例如 IOException/SQLException ,必须要去捕获的异常 。 受检异常可以转化为非受检异常,当受检异常威胁到系统的安全性、稳定性的时候必须要去处理。2、软引用、弱引用、虚引用、强引用的对比? 强引用:new Object(); 不会被回收,宁愿抛出OOM异常也不去回收。 public class ReferenceDemo { static Object strongRef = new Object(); public static void main(String[] args) { Object obj = strongRef; strongRef=null; System.gc(); System.out.println("gc之后:"+obj); } } 之前当前这个类会卸载的时候才会被回收,当obj=null的时候可以被回收。回收的机制有两个:脱离作用域、这个对象被设置为null的时候。 软引用:SoftReference,用来描述一些有用但并不是必需的对象,对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。弱引用:WeakReference,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。 WeakReference<String> sr = new WeakReference<String>(new String("hello")); System.out.println(sr.get()); System.gc(); //通知JVM的gc进行垃圾回收 System.out.println(sr.get()); 虚引用:(PhantomReference)并不影响对象的生命周期,在任何时候都可能被垃圾回收器回收。。可以用来跟踪对象看时候被释放,释放之后可以做一些处理。 3、Integer的原理? 对于Integer,如果想要交换a和b的值,那么如果用下面的swap方法是无法进行交换的。因为java中是按值传递的。如果传递的是基本类型,那么函数接收的是函数原始值的副本,因此如果改变这个函数的值,只是改变了原始值的副本,而原始值不变.如果传的是引用类型,那么函数接收的是原始引用类型的内存地址而不是值的副本,因此如果改变这个函数的值,会改变原始值. public class Swap { public static void main(String[] args) { Integer a=1,b=2; System.out.println("before:a="+a+",b="+b); swap(a,b); System.out.println("after:a="+a+",b="+b); } //交换两个数的值 public static void swap(Integer i1,Integer i2){ Integer tmp = i1; i1=i2; i2=tmp; } } 结果:before:a=1,b=2after:a=1,b=2 Integer源码中:定义了value是final的。 private final int value; 解决方案:使用反射来处理 //交换两个数的值 public static void swap(Integer i1,Integer i2) throws Exception { Field field=Integer.class.getDeclaredField("value"); field.setAccessible(true); int tmp=i1.intValue(); field.set(i1,i2.intValue()); field.set(i2,tmp); } 结果:before:a=1,b=2after:a=2,b=2 但是反射也会出现一个问题,就是只换成功了一个,另一个却还是2,交换后应该是1了。 我们可以看到Integer a=1;其实有一个装箱操作,相当于Integer a=Integer.valueOf(1);找到到Integer中的这个valueOf里面的操作: public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } 这里可以看到有一个IntegerCache,如果i在这个范围内,就直接从缓存中取值,缓存中的范围是-128到127 static final int low = -128; int h = 127; 引申:在-128在127之间,是true,其他的都是false Integer i1=-129; Integer i2=-129; System.out.println(i1==i2); false Integer i1=1; Integer i2=1; System.out.println(i1==i2); true 最终解决方案:避免进行自动装箱操作 //交换两个数的值 public static void swap(Integer i1,Integer i2) throws Exception { Field field=Integer.class.getDeclaredField("value"); field.setAccessible(true); Integer tmp=new Integer(i1.intValue()); field.set(i1,i2.intValue()); field.set(i2,tmp); } 或者: field.setInt(i1,i2.intValue()); field.setInt(i2,tmp); 4、动态代理的原理? jdk动态代理必须要实现接口,cglib并不需要。 jdk动态代理 public class DynamicProxy implements InvocationHandler { private Object target; public Object bind(Object target){ this.target = target; return Proxy.newProxyInstance( target.getClass().getClassLoader(),target.getClass().getInterfaces(),this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("记录日志开始"); Object result = method.invoke(target,args); System.out.println("记录日志结束"); return result; } } CGLIB public class CglibDynamicProxy implements MethodInterceptor { private Object target; public Object getInstance(Object target){ this.target=target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("记录日志开始"); Object result = methodProxy.invoke(o,objects); System.out.println("记录日志结束"); return result; } } 5、设计模式的落地 6大原则:单一职责(一个类只做一个事情)、李氏替换(子类可以扩展父类的功能,但不能改变父类原有的功能)、依赖倒置、接口隔离、开闭原则(对扩展开放)、迪米特法则(类之间互相知道的越少越好,将逻辑封装到类的内部) 策略模式:应用场景:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。 特点 :行为型模式 最终执行结果是固定的。执行过程和执行逻辑不一样。 6、List和Set的区别? List: 有序并且允许重复Set: 不允许重复且无序ArrayList: 可变长度动态数组,可以动态扩容 ArrayList的内部结构: transient Object[] elementData; 绕过transient进行序列化,因为通过重写了readObject(java.io.ObjectInputStream s)和writeObject来对elementData进行序列化,数组长度大于0的时候才进行序列化操作。如果这个数组没有长度,则默认是空的,初始化是10。动态扩容每次会递增1.5倍,当删除一个元素的时候让右边的元素每次左移一位,同时将元素size减1 。List允许重复:每次是以数据下标的方式去递增,不会覆盖掉之前的数据。 Set的数据结构:常用的是HashSet和TreeSet 7、ClassLoader加载机制 程序在启动时不是一次性加载所有的类到内存中,而是根据需要的时候再去加载。类装载器经过验证、准备、解析,最后初始化。类装载器分为启动类、扩展类、系统类装载器、自定义装载器。启动顺序是bootstrap->extension->application->user classload分别装载核心类库、/lib/ext下目录的类、classpath。装载类的时候会有双亲委派装载。 8、乐观锁和悲观锁 锁的目的是保证在多线程并行执行的时候对共享资源访问的安全性。乐观锁:它认为每次去获取共享资源的时候是不会有冲突的,不会主动去加锁,是通过CAS或者加versoin来实现。 CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。 version机制: update from table set name=?,version=version+1 where version=0${version} and id=? 悲观锁:每次都加锁,会阻塞线程。 9、AIO、BIO、NIO BIO:Blocking IO 同步阻塞IO(JDK1.4之前都是这种) 同步IO:是否要亲自去监听操作,不断进行关注是否已经完成对应的操作改进方案:在IO操作的时候进行创建线程处理。 NIO:Non-Blocking IO 同步非阻塞IO。Channel、Buffer、Selector先通过Channel登记,等需要进行IO操作的时候再去创建一个Thread进行读写操作。 阻塞的理解:在单线程的环境中,如果IO操作没有完成,那么当前线程就一定会等待在这边,不能做其他操作。 非阻塞的理解: 无论当前IO是否完成,都会去返回一个结果。通过一个缓存来保证。Buffer: 通过数组的方式来实现 ,记录整个数据的移动过程。可以临时去保存这些数据。 总之:同步,要自己不间断的询问是否已经完成非阻塞:IO操作不需要再阻塞在那边,而是直接返回。 AIO: aysnc IO 异步非阻塞IO用户如果触发了IO操作之后,程序这个时候就委托给操作系统来完成,当操作系统完成这个IO操作之后,再告诉自己。 10、spring中对象注入的几种方式和区别? 注解注入属性、构造方法注入、set方法注入 11、mysql的事务隔离级别? ACID:原子性,一致性,隔离性,持久性脏读、不可重复读、幻读 12、 HashMap源码分析? 数组加链表的存储方式 transient Node[] table; hash算法的作用:为了node节点落点的一个前戏计算 经过map组装后的,会优先进入数组中,HashMap的默认数组大小是16,位置是落在0-15之间,通过hash算法,得到数组的下标的整型数,key.hashCode,得到了int32的数,通过高16位和低16位进行一个异或运算。 static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);} put的过程(key,value这边,put时候的流程是什么样的) if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length; 先通过hash算法得到一个result,然后去判断一下当前node节点的数组是否为空,如果为空就先去初始化这个数组的大小,将数组的默认大小16赋值给newCap。 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); 然后根据key,value值组装成node节点,通过hash计算出来的结果与上01111(15),算出来的结果是在0000-01111之间的,也就是0-15之间。这里为什么不用取模, hash%16 和hash&15的性能对比:对于计算机来说,用&的效率会更加高。 然后不为null,如果key相同,只需要把value值进行覆盖就行了;红黑树:在jdk1.8开始使用,因为当链表不断加长之后,当下次put元素之后,要不断对比的值,这样路程会很长,会影响效率,所以不要让链表的长度过长;当过长之后,当超过8之后,就把这个形式转换为红黑树,类似于二叉树,就是会把节点颜色变成红色或者黑色, 最后还需要判断数组的大小,如果数组过大时,还需要进行扩容,扩容因子是0.75,如果使用的容量大于等于12的时候,就进行扩容,2倍扩容,原因是因为需要位置尽可能均匀的分散在每个数组上,要进行异或运算,必须是n的2次幂,让数组的大小为2*n,可以使使用效率增加。 对这个新的数据进行使用,需要将原来数组中的元素移动到新的数组上,1) 数组下面有元素,下面为空的,就直接计算hash值,然后放到新数组上面去,2) 如果数组位置有元素,是红黑树的话, 就将这个红黑树进行拆分((TreeNode)e).split(this, newTab, j, oldCap);3)如果数组下面的是链表,就对它循环遍历,e.hash & oldCap的方式是用于计算在数组中下标的位置,移动的标准是原来的hash倒数第5位为0时,就等于多了一个16的大小,直接省略掉了计算的过程,这样新的链表的节点的位置只有可能在两个地方:1 是在原来的位置 2 原来的位置加上oldCap. Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } 全部源码如下: final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) //如果是红黑树了结构就直接使用红黑树进行处理 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st //如果长度超过8,就讲这个链表转为红黑树处理 treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; //扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } 红黑树的方式是从jdk1.8中开始引用,在jdk7中:当我们给put()方法传递键和值时,HashMap会由key来调用hash()方法,返回键的hash值,计算Index后用于找到bucket(哈希桶)的位置来储存Entry对象。 当get时要按顺序遍历链表的每个 Entry,直到找到想获取的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),那HashMap必须循环到最后才能找到该元素。 这和红黑树的方式使用相比性能开销很大。 13、红黑树算法 (1)每个节点或者是黑色,或者是红色。(2)根节点是黑色。(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!](4)如果一个节点是红色的,则它的子节点必须是黑色的。(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。 它的时间复杂度是O(lgn), 14、如何停止一个线程 1、interrupt方式 private static int i; public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ while(!Thread.currentThread().isInterrupted()){ i++; } System.out.println("num-"+i); }); thread.start(); TimeUnit.SECONDS.sleep(2); thread.interrupt(); } 2、设置flag的方式 private static int i; private volatile static boolean stop = false; public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ while(!stop){ i++; } System.out.println("num-"+i); }); thread.start(); TimeUnit.SECONDS.sleep(2); stop=true; } 15、Thread.join的实现原理 join:等待线程执行完成,获得一个线程执行结果。原理: public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } } 16、ThreadLocal的分析 ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。每个线程都是一个一个引用副本。它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题。 原理: 每个Thread的对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。在该类中,我觉得最重要的方法就是两个:set()和get()方法。当调用ThreadLocal的get()方法的时候,会先找到当前线程的ThreadLocalMap,然后再找到对应的值。set()方法也是一样。 public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } 17、volatile 用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile具有可见性、有序性,不具备原子性。可见性:当多个线程访问同一个变量x时,线程1修改了变量x的值,线程1、线程2…线程n能够立即读取到线程1修改后的值。有序性:即程序执行时按照代码书写的先后顺序执行。在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。volatile会禁止指令重排。volatile不会让线程阻塞,响应速度比synchronized高,这是它的优点。原理是通过内存屏障去实现是,是基于jvm代码中,多了ACC_VOLATILT。18、缓存穿透的原理 当缓存体系建立起来之后,如果每次查询的都不在缓存中,那么数据库就会收到大量的并发请求,例如当在大流量流入时,可能因为频繁访问存储层导致DB直接宕机,这样会形成被人利用不存在的key频繁攻击应用的漏洞。 解决方案: 最为常简的是采用布隆过滤器,将所有可能存在的数据哈希到一个足够发的 bigmap 中,一个一定不存在的数据会被该 bigmap 拦截掉,从而避免对底层存储系统造成查询压力。另一种更为简单的方法,如果一个查询返回的数据为空(无论数据为空,或是系统故障),将空结果进行缓存,设置一个最长不超过五分钟的过期时间。缓存雪崩 设置缓存时采用了相同的过期时间,导致缓存在某时刻同时失效,请求全部转向DB,DB瞬时压力过重雪崩。Redis宕机,导致客户端的请求之间流向DB,拖垮DB。解决方案: 简单方案就是将缓存失效时间分散开,我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。保持缓存层服务器的高可用。 监控、集群、哨兵。当一个集群里面有一台服务器有问题,让哨兵踢出去。依赖隔离组件为后端限流并降级。19、幂等 一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。 业务开发中,经常会遇到重复提交的情况,无论是由于网络问题无法收到请求结果而重新发起请求,或是前端的操作抖动而造成重复提交情况。 在交易系统,支付系统这种重复提交造成的问题有尤其明显,比如: 用户在APP上连续点击了多次提交订单,后台应该只产生一个订单; 向支付宝发起支付请求,由于网络问题或系统BUG重发,支付宝应该只扣一次钱。 很显然,声明幂等的服务认为,外部调用者会存在多次调用的情况,为了防止外部多次调用对系统数据状态的发生多次改变,将服务设计成幂等。 20、数据库和缓存双写如何保证数据一致性 最经典的做法: 1、读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应;2、更新的时候,先删除缓存,然后再更新数据库。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册