前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[文本字段空字符串与NULL的区别]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
转载文章
...nces 另一表名(字段名).–>外键这个表连接着另外一个表的哪个键. 删除表: drop table 表名;–>表结构也删除了(也即是这个表没了) Truncate table 表名 --> 只删除表中数据,表结构不会删除. 2.In 与 not in 在或不在这个(1,3)里面,单个查询,只会查询(1或者3) 3.Between and 与 not … 和上面差不多,Between 1 and 3 但是这个是范围查询(1,3) 1-3 之间(包含1,3) 4.Like,模糊查询 “%” 代表任意字符,”_”代表单个字符. 5.Is Not null 与 is null 是否为空 6.And 与 or 一个是所有条件都要完成,or则是任何一个条件完成即可 7.Distinct 去重 8.Order by age asc 与 desc 排序,假如根据age排序,asc正序(升序默认),desc倒叙(降序) 9.Gruop by 分组查询,单独使用无意义,group_concat(字段),拼接,若是根据age group by 则会发现age一样的会出现在同一字段内 例如: : 最后要注意group by 后面的字段与所查字段的关系(一对一),当然还有having,having和where基本一样,只不过跟在group by后面. 10.Limit 分页查询 limit 0,5 .查询前5条数据,从0开始,5结束,但是5取不到,也即是取头不取尾. 11.聚合函数:count() 查询数据的总数据量 经常使用别名 例如:as total sum(字段)函数:求和…若字段为成绩,where条件或gruop by 为个人的id,那么查出的就是个人的成绩总分. AVG(字段),但是查的是平均分,min(字段)与max(字段) 查出最小或最大. 三者都类似sum(),当然max()与min()若是在最前面使用,就会当条件查询只会出来这一笔数据.例如: 12.Sql多表查询,内连接不只是inner join,平时写的from a表,b表 where 条件这也是内连接,意思就是两张表中数据都有才可以查询出来 13.而外连接分为左连接和右连接,意思是以左表或右表为主,假如两张表,左表数据多,右表数据少,且条件符合,则左连接的时候左表数据全部出来,右表没有的为null,反之也是一样. 14.Exist() 与 not exist() …()内的数据是否为空,若是为空则代表false,返回数据为空,若不为空,则代表true,正常查询. 15.Any 与 all 例如 age > any(age1,age2) 大于两者中的一个就可以,但是all的情况下则是全部大于.也就是相当于,any为大于最小的,all则是大于最大的就行了,当然若是小于号那就是另外一种情况了,另外分析. 16.Union,(也就是联合的意思,自带distinct,重复的去除)用法,例如两张表的id要全部查出来,则:select id from A union select id from B ,若Aid为1,2,3,Bid为1,2,4.则查出来的数据为1.2.3.4,若是union all,则不带distinct,用法一样,查出来以后为1.2.3.1.2.4. 17.给表取别名,表名 空格 别名 给字段取别名 字段名 as 别名. 18.Insert插入数据时若是使用insert into 表名 values();主键必须到写进去,当然与其他数据不相同即可,若是自增,可以写null.若是insert into 表名(字段)values(值),这时插入数据,字段不用写主键字段,写入其他数据字段名与值就可以完成数据的添加.(主键自己生成为前提,UUID,auto_increament都可以). 19.Insert into 插入多条数据时,其他与18一样,只不过由values()变成了values(),(),(); 20.索引是由数据库表中一列或多列组合而成,其作用提高对表数据的查询速度.像图书目录. 优缺点:优:提高了查询数据的效率.缺:创建和维护索引的时间增加了(内容改了,目录也要改). 21.索引分类:普通索引,唯一性索引UNIQUE(unique修饰,例如主键),全文索引FULLTEXT(创建在文本上,例如:char,varchar,varchar2等,mysql默认引擎不支持,),单列索引:单个字段建立索引,多列索引:多个字段创建一个索引,空间索引SPATIAL:不常用(mysql默认引擎不支持) 22.创建索引: index为关键字,或者key (1)可以index(字段名)–>普通索引 (2)Unique index(字段名)–>唯一索引 (3)Unique index 别名(字段名)–>取别名的唯一索引 (4)index 别名(字段名1,字段名2)–>取别名的多列索引 1.创建表的时候创建索引, 前三个为参数修饰,唯一性,全文,空间索引; 2.在已存在的表上创建索引,或者用ALTER TABLE 表名 ADD 索引,也就是用修改表的形式来创建索引 Create index 索引别名 on 表名(字段名) -->普通单列索引 Create index 索引别名 on 表名(字段名1,字段名2) -->多列索引 Create unique index 索引别名 on 表名(字段名) -->唯一单列索引 Alter table 表名 add +(1)|(2)|(3)|(4)即可. 23.删除索引: drop index 索引名 on 表名. 24.NOW(); mysql的函数,表示当前时间 25.视图:是一个虚拟的表,没有物理数据,是从其他表中导出的数据,当原表数据发生改变时,视图数据也会发生改变,反之也一样. (1)作用:操作简单化;增加数据安全性:不直接对表进行操作;提高表的逻辑性:原表修改字段对视图无影响. (2)创建视图:语法:create view 视图名 as 查询语句. 例如:create view vi as select id,name from user;–>这是把user中id,name字段的数据写入到vi视图中. 若是想自己定义字段名不用查出的字段名,可以如下面这样写. 例如:create view vi(vi_id,vi_name) as select id,name from user;–>这样的话id对应vi_id,name对应vi_name; 上面的都是单表的视图,多表的视图也是一样的,只不过后面的单表查询变成多表查询了. 建议创建视图后自己定义字段名,也即是定义别名. (3)查看视图: Describe(desc) 视图名–>查看视图基本信息 Show table status like ‘视图名’ --> 查看视图基本信息 Show create view 视图名 --> 视图详细信息,建表具体信息. 在view表中查看视图详细信息–>view 系统表 自带的. (4)修改视图:修改使徒的定义 Create or replace view 没有的话就创建,有的话就替换 例如:Create or replace view vi(id,name) as select语句. Alter view 只修改不能创建(也就是说视图必须存在的情况下才可修改) Alter view vi as select语句 (5)更新视图:视图是虚拟的,对视图进行的crud操作都会对原表的数据产生影响. 也就是说对视图的操作最后都会转换为对视图所连接那个表的操作. (6)删除视图:删除数据库中已存在的视图,视图为虚表,因此只会删除结构,不会删除数据. Drop view if exist 视图名. 26.触发器:由事件来触发某个操作,这些事件包括insert语句,update语句和delete语句.当数据库系统执行这些事件时,就会激活触发器执行相应的方法. 创建触发器:create trigger 触发器名 (before/after) 触发事件 on 表名 for each row sql语句. 这里的new是指代新插入的拿一条数据(更新的也算),若是old的话,指的是删除的那一条数据(更新之前的数据).(new和old属于过渡变量) 这条触发器的意思时:当t_book有插入数据时,就会根据新插入数据的id找到t_bookType的id,并试该条数据的bookNum加1. Begin与end写sql语句,中间可以写多条sql语句用分号;分隔开…也即是说语句要写完成,不能少分号. Delimiter | 设置分隔符,要不然好像只会执行begin与and之间的第一条sql语句. 查看触发器: 1.show triggers; 语句查看触发器信息.(查询所有的触发器) 2.在triggers表中查看触发器信息.(在数据库原始表triggers中可以查看) 删除触发器: Drop trigger 触发器名称 ; 27.函数: (1)日期函数: CURDATE()当前日期,CURTIME()当前时间,MONTH(d):返回日期d中的月份值,范围试1-12 (2)字符串函数:CHAR_LENGTH(s) 计算字段s值->字符串的长度.UPPER(s) 把该字段的值中所有英文都变成大写,LOWER(s) 和相面相反->把英文都变成小写. (3)数学函数:sum():求和,ABS(s) 求绝对值,SQRT(s):求平方根,mod(x,y),求余x/y (4)加密函数:PASSWORD(STR) 一般对密码加密 不可逆… MD5(STR) 普通加密 ,不可逆. ENCODE(str,pswd_str) 加密函数,结果是一个二进制文件,用blob类型的字段保存,pswd_str类似一个加密的钥匙,可以随便写. DECODE(被加密的值,pswd_str)–>对encode进行解密. 28.存储过程: (1)存储过程和函数:两者是在数据库中定义一些SQL语句的集合,然后直接调用这些存储过程和函数来执行已经定义好的SQL语句.存储过程和函数可以避免重复的写一些sql语句,而且存储过程是在mysql服务器中存储和执行的,减少客户端和服务器端的数据传输.(类似于java代码写的工具类.) (2)创建存储过程和函数: Create procedure 关键字 pro_book 存储过程名称, in 输入 bT 输入参数名称 int 输入参数类型 out 输出 count_num 输出参数名称 int 输入参数类型 Begin 过程开始 end过程结束 中间是sql语句, Delimiter 默认是分号,而他的作用就是若是遇见分号时就开始执行该过程(语句),但是一个存储过程可能有很多sql语句且以分号结束,若这样的情况下当第一条sql语句结束后就会开始执行该过程,产生的后果是创建过程时,执行到第一个分号就会开始创建,导致存储过程创建错误.(若是有多个参数,在多条sql中均有参数,第一条设置完执行了,而这时第二条的参数有可能还么有设置完成,导致sql执行失败.)因此,需要把默认执行过程的demiliter关键字的默认值改为其他的字符,例如上面的就是改为&&,(当然我认为上面就一条sql语句,改不改默认的demiliter的默认值都一样.) . 使用navicat的话不使用delimiter好像也是可以的. Reads sql data则是上面图片所提到的参数指定存储过程的特性.(这个是指读数据,当然还有写输入与读写数据专用的参数类型.)看下图 经常用contains sql (应该是可以读,) 这个是调用上面的存储过程,1为入参,@total相当于全局变量,为出参. 这是一个存储函数,create function 为关键字,fun_book为函数名称, 括号里面为传入的参数名(值)以及入参的类型.RETURNS 为返回的关键字,后面接返回的类型. BEGIN函数开始,END函数结束.中间是return 以及查询数据的sql语句, 这里是指把bookId 传进去,通过存储函数返回对应的书本名字, ---------存储函数的调用和调用系统函数一样 例如:select 存储函数名称(入参值) Select 为查询 func_book 为存储函数名 2为入参值. (3)变量的使用:declaer:声明变量的值 Delimiter && Create procedure user() Begin Declare a,b varchar2(20) ; — a,b有默认的值,为空 Insert into user values(a,b); End && Delimiter ; Set 可以用来赋值,例如: 可以从其他表中查询出对应的值插入到另一个表中.例如: 从t_user2中查询出username2与password2放入到变量a,b中,然后再插入到t_user表中.(当然这只是创建存储过程),创建完以后,需要用CALL 存储过程名(根据过程参数描写.)来调用存储过程.注意:这一种的写法只可以插入单笔数据,若是select查询出多笔数据,因为无循环故而会插入不进去语句,会导致倒致存储过程时出错.下面的游标也是如此. (4)游标的使用.查询语句可能查询出多条记录,在存储过程和函数中使用游标逐条读取查询结果集中的记录.游标的使用包括声明游标,打开游标,使用游标和关闭游标.游标必须声明到处理程序之前,并且声明在变量和条件之后. 声明:declare 游标名 curson for 查询sql语句. 打开:open 游标名 使用:fetch 游标名 into x, 关闭:close 游标名 ----- 游标只能保存单笔数据. 类似于这一个,意思就是先查询出来username2,与password2的值放入到cur_t_user2的游标中(声明,类似于赋值),然后开启->使用.使用的意思就是把游标中存储的值分别赋值到a,b中,然后执行sql语句插入到t_user表中.最后关闭游标. (5)流程控制的使用:mysql可以使用:IF 语句 CASE语句 LOOP语句 LEAVE语句 ITERATE 语句 REPEAT语句与WHILE语句. 这个过程的意思是,查询t_user表中是否存在id等于我们入参时所写的id,若有的情况下查出有几笔这样的数据并且把数值给到全局变量@num中,if判断是否这样的数据是否存在,若是存在执行THEN后面的语句,即使更新该id对应的username,若没有则插入一条新的数据,最后注意END IF. 相当于java中的switch case.例如: 这里想当然于,while(ture){ break; } 这里的意思是,参数一个int类型的参数,loop aaa循环,把参数当做主键id插入到t_user表中,每循环一次参入的参数值减一,直到参数值为0,跳出循环(if判断,leave实现.) 相当于java的continue. 比上面的多了一个当totalNum = 3时,结束本次循环,下面的语句不在执行,直接执行下一次循环,也即是说插入的数据没有主键为3的数据. 和上面的差不多,只不过当执行到UNTIL时满足条件时,就跳出循环.就如上面那一个意思就是当执行到totalNum = 1时,跳出循环,也就是说不会插入主键为0的那一笔数据 当while条件判断为true时,执行do后面的语句,否则就不再执行. (6)调用存储过程和函数 CALL 存储过程名字(参数值1,参数值2,…) 存储函数名称(参数值1,参数值2,…) (7)查看存储过程和函数. Show procedure status like ‘存储过程名’ --只能查看状态 Show create procedure ‘存储过程名’ – 查看定义(使用频率高). 存储函数查看也和上面的一样. 当然还可以从information_schema.Routines中(系统数据库表)查看存储过程与函数. (8)修改存储过程与函数: 修改存储过程comment属性的值 ALTER procedure 存储过程名 comment ‘新值’; (9)删除存储过程与函数: DROP PROCEDURE 存储过程名; DROP function 存储函数名; 29.数据备份与还原: (1)数据备份:数据备份可以保证数据库表的安全性,数据库管理员需要定期的进行数据库备份. 命令:使用mysqldump(下图),或者使用图形工具 Mysqldump在msql文件夹+bin+mysqldump.exe中,相当于一个小软件.执行的话是在dos命令窗操作的. 其实就是导出数据库数据,在navacat中可以如下图导出 (2)数据还原: 若是从navacat中就是把外部的.sql文件数据导入到数据库中去.如下图 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_42847571/article/details/102686087。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-26 19:09:16
83
转载
Shell
...要工具,它们可以保存文本、数值等各种类型的数据。在编写Shell脚本时,每个变量都有自己的小名儿。就像每个人都有自己的名字一样,你可以随时给这些变量“朋友”分配一个值,或者在脚本运行的过程中,只要叫出它们的名字,就能获取到它们当前的数值啦。如果试图访问一个未定义的变量,Shell通常会返回一个空字符串或触发错误。 2. 初级方法 测试变量是否为空 首先,我们可以尝试直接引用变量并检查其值是否为空来判断变量是否已定义。不过呢,这种方法并不是百分百合心意,因为就算你定义了变量这个小家伙,可要是从始至终都没给它喂过值,那在系统眼里,它就相当于个“空壳子”啦。 bash 定义一个变量,但不赋值 my_var= 检查变量是否为空 if [ -z "$my_var" ]; then echo "Variable 'my_var' is either undefined or empty." else echo "Variable 'my_var' is defined and has a value." fi 然而,这个方法并不能区分变量是否真的未定义还是仅仅被赋予了空值。所以,这就引出了更精确的方法。 3. 高级技巧 使用declare命令 在Shell中,declare命令可以用来查看和操作变量,其中包括检查变量是否已定义的功能。如果你想查看某个特定变量的具体信息,我们可以灵活运用那个 -v 参数。比方说,你敲入命令带上 -v 选项去查询一个变量,要是这个变量还没被定义过,系统就会俏皮地蹦出一条错误提示告诉你:“嘿,这个变量我还不认识呢!” bash 尝试查询一个可能未定义的变量 if declare -v my_maybe_undefined_var > /dev/null; then echo "Variable 'my_maybe_undefined_var' is defined." else echo "Variable 'my_maybe_undefined_var' is not defined." fi 这个方法的优点在于,无论变量值是否为空,只要它已被声明,都会认为是已定义。 4. 更进一步 使用set命令 另一种方式是使用set命令配合管道与grep命令查找变量名是否存在。尽管这种方法略显复杂,但在某些场景下也十分有用: bash 使用set命令输出所有环境变量列表,然后通过grep搜索特定变量名 if set | grep -q "^my_special_var="; then echo "Variable 'my_special_var' is defined." else echo "Variable 'my_special_var' is not defined." fi 这里,-q选项使得grep命令在匹配成功时不打印任何内容,仅根据匹配结果返回退出状态。如果找到匹配项(即变量已定义),则返回0,否则返回非零值。 结语 在Shell编程中,理解并熟练掌握如何判断变量是否已定义是一项基本且重要的技能。不同的方法适用于不同的情境,有时我们需要根据实际需求灵活运用。整个探索过程的核心,就是我们对Shell编程逻辑那股子钻劲儿和死磕精神,一边不断加深理解,一边持续优化实践,铆足了劲儿,下定决心一路通关到底。希望本文能帮助你更好地驾驭Shell变量,让每一次与Shell的对话都充满智慧与乐趣!
2023-07-08 20:17:42
34
繁华落尽
MySQL
...,明明已经设置了某个字段为 NOT NULL,但是在尝试插入数据时,却发现可以输入空白值。嘿,你知道这是怎么一回事儿吗?别急,接下来咱们要从各个角度全面剖析这个问题,并且还会贴心地提供一些解决办法! 二、什么是 NOT NULL? NOT NULL 是 MySQL 中的一个数据类型约束,用于强制字段不为空。当你在建立字段的时候,给它加上了“NOT NULL”的约束,这就意味着从此以后,只要你想往这个字段里插入数据,就绝对、必须得提供一个实实在在的有效值,不能为空!如果试图插入 NULL 或空字符串,MySQL 将会抛出一个错误。 三、为什么可以插入空白值? 在了解了 NOT NULL 的基本概念之后,我们来深入探究一下为什么可以在设置了 NOT NULL 的字段上插入空白值。 首先,我们需要知道,对于文本类型字段来说,MySQL 并没有区分空字符串和 NULL 值。换句话说,你要是尝试在不允许为空的文本框里塞进去一个空字符串,MySQL 还是会把它当作个有效值来对待。所以,就算你在插入信息的时候,随手敲了个空格或者回车键,放心好了,这些可都会被系统认作是有用的数据! 其次,MySQL 的数据验证是在 SQL 语句执行之前进行的,而不是在执行语句时进行的。这就意味着,如果你在插入数据时没有明确地指明要插入的值,MySQL 就会在运行时自动填充该值。对于 NOT NULL 字段来说,MySQL 通常会选择其默认值作为填充值。所以,即使你没有在插入操作中提供任何值,MySQL 也可能会将其填充为默认值,从而让你误以为自己成功地插入了一个空白值。 四、如何避免这种情况? 既然我们知道了为什么可以在设置了 NOT NULL 的字段上插入空白值,那么就可以采取相应的措施来避免这种情况的发生。 一种常见的做法是显式地指定你要插入的值。无论你是使用 INSERT INTO 语句还是 UPDATE 表达式,都应该清楚地指明要插入的值。如果你不确定某个字段的默认值是什么,可以使用 SHOW CREATE TABLE 语句查看表的详细信息。 另外,你也可以通过修改表的约束来限制插入操作。比如说,你完全可以考虑增加一个新栏目来专门存原始数据,然后在塞入新鲜数据之前,先瞅瞅这个位置是不是还空着没填呢。如果为空,你可以拒绝插入请求或者填充一个默认值。 五、总结 总的来说,虽然在 MySQL 中设置了 NOT NULL 的字段理论上不能包含空白值,但实际上却有可能发生这种情况。这是因为 MySQL 的数据验证是在 SQL 语句执行之前进行的,而默认值的选择也是自动完成的。为了避免出现这状况,咱们最好明确指出要塞进去的数值,或者换个法子给插入操作上个“紧箍咒”。希望这篇文章能够帮助到你们,谢谢阅读!
2023-04-18 15:27:46
87
风轻云淡_t
转载文章
...占位符。? 代表一个字符, 代表 0 个或者多个字符。 消费端 1,关注运动信息: psubscribe sport 消费端 2,关注所有新闻: psubscribe news 消费端 3,关注天气新闻: psubscribe news-weather 生产者,发布 3 条信息 publish news-sport yaoming publish news-music jaychou publish news-weather rain 2、Redis 事务 2.1 为什么要用事务 我们知道 Redis 的单个命令是原子性的(比如 get set mget mset),如果涉及到多个命令的时候,需要把多个命令作为一个不可分割的处理序列,就需要用到事务。 例如我们之前说的用 setnx 实现分布式锁,我们先 set,然后设置对 key 设置 expire, 防止 del 发生异常的时候锁不会被释放,业务处理完了以后再 del,这三个动作我们就希望它们作为一组命令执行。 Redis 的事务有两个特点: 1、按进入队列的顺序执行。 2、不会受到其他客户端的请求的影响。 Redis 的事务涉及到四个命令:multi(开启事务),exec(执行事务),discard (取消事务),watch(监视) 2.2 事务的用法 案例场景:tom 和 mic 各有 1000 元,tom 需要向 mic 转账 100 元。tom 的账户余额减少 100 元,mic 的账户余额增加 100 元。 通过 multi 的命令开启事务。事务不能嵌套,多个 multi 命令效果一样。 multi 执行后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当 exec 命令被调用时,所有队列中的命令才会被执行。 通过 exec 的命令执行事务。如果没有执行 exec,所有的命令都不会被执行。如果中途不想执行事务了,怎么办? 可以调用 discard 可以清空事务队列,放弃执行。 2.3 watch命令 在 Redis 中还提供了一个 watch 命令。 它可以为 Redis 事务提供 CAS 乐观锁行为(Check and Set / Compare and Swap),也就是多个线程更新变量的时候,会跟原值做比较,只有它没有被其他线程修改的情况下,才更新成新的值。 我们可以用 watch 监视一个或者多个 key,如果开启事务之后,至少有一个被监视 key 键在 exec 执行之前被修改了,那么整个事务都会被取消(key 提前过期除外)。可以用 unwatch 取消。 2.4 事务可能遇到的问题 我们把事务执行遇到的问题分成两种,一种是在执行 exec 之前发生错误,一种是在执行 exec 之后发生错误。 2.4.1 在执行 exec 之前发生错误 比如:入队的命令存在语法错误,包括参数数量,参数名等等(编译器错误)。 在这种情况下事务会被拒绝执行,也就是队列中所有的命令都不会得到执行。 2.4.2 在执行 exec 之后发生错误 比如,类型错误,比如对 String 使用了 Hash 的命令,这是一种运行时错误。 最后我们发现 set k1 1 的命令是成功的,也就是在这种发生了运行时异常的情况下, 只有错误的命令没有被执行,但是其他命令没有受到影响。 这个显然不符合我们对原子性的定义,也就是我们没办法用 Redis 的这种事务机制来实现原子性,保证数据的一致。 3、Lua脚本 Lua/ˈluə/是一种轻量级脚本语言,它是用 C 语言编写的,跟数据的存储过程有点类似。 使用 Lua 脚本来执行 Redis 命令的好处: 1、一次发送多个命令,减少网络开销。 2、Redis 会将整个脚本作为一个整体执行,不会被其他请求打断,保持原子性。 3、对于复杂的组合命令,我们可以放在文件中,可以实现程序之间的命令集复用。 3.1 在Redis中调用Lua脚本 使用 eval /ɪ’væl/ 方法,语法格式: redis> eval lua-script key-num [key1 key2 key3 ....] [value1 value2 value3 ....] eval代表执行Lua语言的命令。 lua-script代表Lua语言脚本内容。 key-num表示参数中有多少个key,需要注意的是Redis中key是从1开始的,如果没有key的参数,那么写0。 [key1key2key3…]是key作为参数传递给Lua语言,也可以不填,但是需要和key-num的个数对应起来。 [value1 value2 value3 …]这些参数传递给 Lua 语言,它们是可填可不填的。 示例,返回一个字符串,0 个参数: redis> eval "return 'Hello World'" 0 3.2 在Lua脚本中调用Redis命令 使用 redis.call(command, key [param1, param2…])进行操作。语法格式: redis> eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value command是命令,包括set、get、del等。 key是被操作的键。 param1,param2…代表给key的参数。 注意跟 Java 不一样,定义只有形参,调用只有实参。 Lua 是在调用时用 key 表示形参,argv 表示参数值(实参)。 3.2.1 设置键值对 在 Redis 中调用 Lua 脚本执行 Redis 命令 redis> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 gupao 2673 redis> get gupao 以上命令等价于 set gupao 2673。 在 redis-cli 中直接写 Lua 脚本不够方便,也不能实现编辑和复用,通常我们会把脚本放在文件里面,然后执行这个文件。 3.2.2 在 Redis 中调用 Lua 脚本文件中的命令,操作 Redis 创建 Lua 脚本文件: cd /usr/local/soft/redis5.0.5/src vim gupao.lua Lua 脚本内容,先设置,再取值: cd /usr/local/soft/redis5.0.5/src redis-cli --eval gupao.lua 0 得到返回值: root@localhost src] redis-cli --eval gupao.lua 0 "lua666" 3.2.3 案例:对 IP 进行限流 需求:在 X 秒内只能访问 Y 次。 设计思路:用 key 记录 IP,用 value 记录访问次数。 拿到 IP 以后,对 IP+1。如果是第一次访问,对 key 设置过期时间(参数 1)。否则判断次数,超过限定的次数(参数 2),返回 0。如果没有超过次数则返回 1。超过时间, key 过期之后,可以再次访问。 KEY[1]是 IP, ARGV[1]是过期时间 X,ARGV[2]是限制访问的次数 Y。 -- ip_limit.lua-- IP 限流,对某个 IP 频率进行限制 ,6 秒钟访问 10 次 local num=redis.call('incr',KEYS[1])if tonumber(num)==1 thenredis.call('expire',KEYS[1],ARGV[1])return 1elseif tonumber(num)>tonumber(ARGV[2]) thenreturn 0 elsereturn 1 end 6 秒钟内限制访问 10 次,调用测试(连续调用 10 次): ./redis-cli --eval "ip_limit.lua" app:ip:limit:192.168.8.111 , 6 10 app:ip:limit:192.168.8.111 是 key 值 ,后面是参数值,中间要加上一个空格和一个逗号,再加上一个空格 。 即:./redis-cli –eval [lua 脚本] [key…]空格,空格[args…] 多个参数之间用一个空格分割 。 代码:LuaTest.java 3.2.4 缓存 Lua 脚本 为什么要缓存 在脚本比较长的情况下,如果每次调用脚本都需要把整个脚本传给 Redis 服务端, 会产生比较大的网络开销。为了解决这个问题,Redis 提供了 EVALSHA 命令,允许开发者通过脚本内容的 SHA1 摘要来执行脚本。 如何缓存 Redis 在执行 script load 命令时会计算脚本的 SHA1 摘要并记录在脚本缓存中,执行 EVALSHA 命令时 Redis 会根据提供的摘要从脚本缓存中查找对应的脚本内容,如果找到了则执行脚本,否则会返回错误:“NOSCRIPT No matching script. Please use EVAL.” 127.0.0.1:6379> script load "return 'Hello World'" "470877a599ac74fbfda41caa908de682c5fc7d4b"127.0.0.1:6379> evalsha "470877a599ac74fbfda41caa908de682c5fc7d4b" 0 "Hello World" 3.2.5 自乘案例 Redis 有 incrby 这样的自增命令,但是没有自乘,比如乘以 3,乘以 5。我们可以写一个自乘的运算,让它乘以后面的参数: local curVal = redis.call("get", KEYS[1]) if curVal == false thencurVal = 0 elsecurVal = tonumber(curVal)endcurVal = curVal tonumber(ARGV[1]) redis.call("set", KEYS[1], curVal) return curVal 把这个脚本变成单行,语句之间使用分号隔开 local curVal = redis.call("get", KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal tonumber(ARGV[1]); redis.call("set", KEYS[1], curVal); return curVal script load ‘命令’ 127.0.0.1:6379> script load 'local curVal = redis.call("get", KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal tonumber(ARGV[1]); redis.call("set", KEYS[1], curVal); return curVal' "be4f93d8a5379e5e5b768a74e77c8a4eb0434441" 调用: 127.0.0.1:6379> set num 2OK127.0.0.1:6379> evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 num 6 (integer) 12 3.2.6 脚本超时 Redis 的指令执行本身是单线程的,这个线程还要执行客户端的 Lua 脚本,如果 Lua 脚本执行超时或者陷入了死循环,是不是没有办法为客户端提供服务了呢? eval 'while(true) do end' 0 为了防止某个脚本执行时间过长导致 Redis 无法提供服务,Redis 提供了 lua-time-limit 参数限制脚本的最长运行时间,默认为 5 秒钟。 lua-time-limit 5000(redis.conf 配置文件中) 当脚本运行时间超过这一限制后,Redis 将开始接受其他命令但不会执行(以确保脚本的原子性,因为此时脚本并没有被终止),而是会返回“BUSY”错误。 Redis 提供了一个 script kill 的命令来中止脚本的执行。新开一个客户端: script kill 如果当前执行的 Lua 脚本对 Redis 的数据进行了修改(SET、DEL 等),那么通过 script kill 命令是不能终止脚本运行的。 127.0.0.1:6379> eval "redis.call('set','gupao','666') while true do end" 0 因为要保证脚本运行的原子性,如果脚本执行了一部分终止,那就违背了脚本原子性的要求。最终要保证脚本要么都执行,要么都不执行。 127.0.0.1:6379> script kill(error) UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the scripttermination or kill the server in a hard way using the SHUTDOWN NOSAVE command. 遇到这种情况,只能通过 shutdown nosave 命令来强行终止 redis。 shutdown nosave 和 shutdown 的区别在于 shutdown nosave 不会进行持久化操作,意味着发生在上一次快照后的数据库修改都会丢失。 4、Redis 为什么这么快? 4.1 Redis到底有多快? 根据官方的数据,Redis 的 QPS 可以达到 10 万左右(每秒请求数)。 4.2 Redis为什么这么快? 总结:1)纯内存结构、2)单线程、3)多路复用 4.2.1 内存 KV 结构的内存数据库,时间复杂度 O(1)。 第二个,要实现这么高的并发性能,是不是要创建非常多的线程? 恰恰相反,Redis 是单线程的。 4.2.2 单线程 单线程有什么好处呢? 1、没有创建线程、销毁线程带来的消耗 2、避免了上线文切换导致的 CPU 消耗 3、避免了线程之间带来的竞争问题,例如加锁释放锁死锁等等 4.2.3 异步非阻塞 异步非阻塞 I/O,多路复用处理并发连接。 4.3 Redis为什么是单线程的? 不是白白浪费了 CPU 的资源吗? 因为单线程已经够用了,CPU 不是 redis 的瓶颈。Redis 的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。 4.4 单线程为什么这么快? 因为 Redis 是基于内存的操作,我们先从内存开始说起。 4.4.1 虚拟存储器(虚拟内存 Vitual Memory) 名词解释:主存:内存;辅存:磁盘(硬盘) 计算机主存(内存)可看作一个由 M 个连续的字节大小的单元组成的数组,每个字节有一个唯一的地址,这个地址叫做物理地址(PA)。早期的计算机中,如果 CPU 需要内存,使用物理寻址,直接访问主存储器。 这种方式有几个弊端: 1、在多用户多任务操作系统中,所有的进程共享主存,如果每个进程都独占一块物理地址空间,主存很快就会被用完。我们希望在不同的时刻,不同的进程可以共用同一块物理地址空间。 2、如果所有进程都是直接访问物理内存,那么一个进程就可以修改其他进程的内存数据,导致物理地址空间被破坏,程序运行就会出现异常。 为了解决这些问题,我们就想了一个办法,在 CPU 和主存之间增加一个中间层。CPU 不再使用物理地址访问,而是访问一个虚拟地址,由这个中间层把地址转换成物理地址,最终获得数据。这个中间层就叫做虚拟存储器(Virtual Memory)。 具体的操作如下所示: 在每一个进程开始创建的时候,都会分配一段虚拟地址,然后通过虚拟地址和物理地址的映射来获取真实数据,这样进程就不会直接接触到物理地址,甚至不知道自己调用的哪块物理地址的数据。 目前,大多数操作系统都使用了虚拟内存,如 Windows 系统的虚拟内存、Linux 系统的交换空间等等。Windows 的虚拟内存(pagefile.sys)是磁盘空间的一部分。 在 32 位的系统上,虚拟地址空间大小是 2^32bit=4G。在 64 位系统上,最大虚拟地址空间大小是多少? 是不是 2^64bit=10241014TB=1024PB=16EB?实际上没有用到 64 位,因为用不到这么大的空间,而且会造成很大的系统开销。Linux 一般用低 48 位来表示虚拟地址空间,也就是 2^48bit=256T。 cat /proc/cpuinfo address sizes : 40 bits physical, 48 bits virtual 实际的物理内存可能远远小于虚拟内存的大小。 总结:引入虚拟内存,可以提供更大的地址空间,并且地址空间是连续的,使得程序编写、链接更加简单。并且可以对物理内存进行隔离,不同的进程操作互不影响。还可以通过把同一块物理内存映射到不同的虚拟地址空间实现内存共享。 4.4.2 用户空间和内核空间 为了避免用户进程直接操作内核,保证内核安全,操作系统将虚拟内存划分为两部分,一部分是内核空间(Kernel-space)/ˈkɜːnl /,一部分是用户空间(User-space)。 内核是操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的权限。 内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中,都是对物理地址的映射。 在 Linux 系统中, 内核进程和用户进程所占的虚拟内存比例是 1:3。 当进程运行在内核空间时就处于内核态,而进程运行在用户空间时则处于用户态。 进程在内核空间以执行任意命令,调用系统的一切资源;在用户空间只能执行简单的运算,不能直接调用系统资源,必须通过系统接口(又称 system call),才能向内核发出指令。 top 命令: us 代表 CPU 消耗在 User space 的时间百分比; sy 代表 CPU 消耗在 Kernel space 的时间百分比。 4.4.3 进程切换(上下文切换) 多任务操作系统是怎么实现运行远大于 CPU 数量的任务个数的? 当然,这些任务实际上并不是真的在同时运行,而是因为系统通过时间片分片算法,在很短的时间内,将 CPU 轮流分配给它们,造成多任务同时运行的错觉。 为了控制进程的执行,内核必须有能力挂起正在 CPU 上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。 什么叫上下文? 在每个任务运行前,CPU 都需要知道任务从哪里加载、又从哪里开始运行,也就是说,需要系统事先帮它设置好 CPU 寄存器和程序计数器(ProgramCounter),这个叫做 CPU 的上下文。 而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。 在切换上下文的时候,需要完成一系列的工作,这是一个很消耗资源的操作。 4.4.4 进程的阻塞 正在运行的进程由于提出系统服务请求(如 I/O 操作),但因为某种原因未得到操作系统的立即响应,该进程只能把自己变成阻塞状态,等待相应的事件出现后才被唤醒。 进程在阻塞状态不占用 CPU 资源。 4.4.5 文件描述符 FD Linux 系统将所有设备都当作文件来处理,而 Linux 用文件描述符来标识每个文件对象。 文件描述符(File Descriptor)是内核为了高效管理已被打开的文件所创建的索引,用于指向被打开的文件,所有执行 I/O 操作的系统调用都通过文件描述符;文件描述符是一个简单的非负整数,用以表明每个被进程打开的文件。 Linux 系统里面有三个标准文件描述符。 0:标准输入(键盘); 1:标准输出(显示器); 2:标准错误输出(显示器)。 4.4.6 传统 I/O 数据拷贝 以读操作为例: 当应用程序执行 read 系统调用读取文件描述符(FD)的时候,如果这块数据已经存在于用户进程的页内存中,就直接从内存中读取数据。如果数据不存在,则先将数据从磁盘加载数据到内核缓冲区中,再从内核缓冲区拷贝到用户进程的页内存中。(两次拷贝,两次 user 和 kernel 的上下文切换)。 I/O 的阻塞到底阻塞在哪里? 4.4.7 Blocking I/O 当使用 read 或 write 对某个文件描述符进行过读写时,如果当前 FD 不可读,系统就不会对其他的操作做出响应。从设备复制数据到内核缓冲区是阻塞的,从内核缓冲区拷贝到用户空间,也是阻塞的,直到 copy complete,内核返回结果,用户进程才解除 block 的状态。 为了解决阻塞的问题,我们有几个思路。 1、在服务端创建多个线程或者使用线程池,但是在高并发的情况下需要的线程会很多,系统无法承受,而且创建和释放线程都需要消耗资源。 2、由请求方定期轮询,在数据准备完毕后再从内核缓存缓冲区复制数据到用户空间 (非阻塞式 I/O),这种方式会存在一定的延迟。 能不能用一个线程处理多个客户端请求? 4.4.8 I/O 多路复用(I/O Multiplexing) I/O 指的是网络 I/O。 多路指的是多个 TCP 连接(Socket 或 Channel)。 复用指的是复用一个或多个线程。它的基本原理就是不再由应用程序自己监视连接,而是由内核替应用程序监视文件描述符。 客户端在操作的时候,会产生具有不同事件类型的 socket。在服务端,I/O 多路复用程序(I/O Multiplexing Module)会把消息放入队列中,然后通过文件事件分派器(File event Dispatcher),转发到不同的事件处理器中。 多路复用有很多的实现,以 select 为例,当用户进程调用了多路复用器,进程会被阻塞。内核会监视多路复用器负责的所有 socket,当任何一个 socket 的数据准备好了,多路复用器就会返回。这时候用户进程再调用 read 操作,把数据从内核缓冲区拷贝到用户空间。 所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪(readable)状态,select() 函数就可以返回。 Redis 的多路复用, 提供了 select, epoll, evport, kqueue 几种选择,在编译的时 候来选择一种。 evport 是 Solaris 系统内核提供支持的; epoll 是 LINUX 系统内核提供支持的; kqueue 是 Mac 系统提供支持的; select 是 POSIX 提供的,一般的操作系统都有支撑(保底方案); 源码 ae_epoll.c、ae_select.c、ae_kqueue.c、ae_evport.c 5、内存回收 Reids 所有的数据都是存储在内存中的,在某些情况下需要对占用的内存空间进行回 收。内存回收主要分为两类,一类是 key 过期,一类是内存使用达到上限(max_memory) 触发内存淘汰。 5.1 过期策略 要实现 key 过期,我们有几种思路。 5.1.1 定时过期(主动淘汰) 每个设置过期时间的 key 都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的 CPU 资源去处理过期的 数据,从而影响缓存的响应时间和吞吐量。 5.1.2 惰性过期(被动淘汰) 只有当访问一个 key 时,才会判断该 key 是否已过期,过期则清除。该策略可以最大化地节省 CPU 资源,却对内存非常不友好。极端情况可能出现大量的过期 key 没有再次被访问,从而不会被清除,占用大量内存。 例如 String,在 getCommand 里面会调用 expireIfNeeded server.c expireIfNeeded(redisDb db, robj key) 第二种情况,每次写入 key 时,发现内存不够,调用 activeExpireCycle 释放一部分内存。 expire.c activeExpireCycle(int type) 5.1.3 定期过期 源码:server.h typedef struct redisDb { dict dict; / 所有的键值对 /dict expires; / 设置了过期时间的键值对 /dict blocking_keys; dict ready_keys; dict watched_keys; int id;long long avg_ttl;list defrag_later; } redisDb; 每隔一定的时间,会扫描一定数量的数据库的 expires 字典中一定数量的 key,并清除其中已过期的 key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得 CPU 和内存资源达到最优的平衡效果。 Redis 中同时使用了惰性过期和定期过期两种过期策略。 5.2 淘汰策略 Redis 的内存淘汰策略,是指当内存使用达到最大内存极限时,需要使用淘汰算法来决定清理掉哪些数据,以保证新数据的存入。 5.2.1 最大内存设置 redis.conf 参数配置: maxmemory <bytes> 如果不设置 maxmemory 或者设置为 0,64 位系统不限制内存,32 位系统最多使用 3GB 内存。 动态修改: redis> config set maxmemory 2GB 到达最大内存以后怎么办? 5.2.2 淘汰策略 https://redis.io/topics/lru-cache redis.conf maxmemory-policy noeviction 先从算法来看: LRU,Least Recently Used:最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。 LFU,Least Frequently Used,最不常用,4.0 版本新增。 random,随机删除。 如果没有符合前提条件的 key 被淘汰,那么 volatile-lru、volatile-random、 volatile-ttl 相当于 noeviction(不做内存回收)。 动态修改淘汰策略: redis> config set maxmemory-policy volatile-lru 建议使用 volatile-lru,在保证正常服务的情况下,优先删除最近最少使用的 key。 5.2.3 LRU 淘汰原理 问题:如果基于传统 LRU 算法实现 Redis LRU 会有什么问题? 需要额外的数据结构存储,消耗内存。 Redis LRU 对传统的 LRU 算法进行了改良,通过随机采样来调整算法的精度。如果淘汰策略是 LRU,则根据配置的采样值 maxmemory_samples(默认是 5 个), 随机从数据库中选择 m 个 key, 淘汰其中热度最低的 key 对应的缓存数据。所以采样参数m配置的数值越大, 就越能精确的查找到待淘汰的缓存数据,但是也消耗更多的CPU计算,执行效率降低。 问题:如何找出热度最低的数据? Redis 中所有对象结构都有一个 lru 字段, 且使用了 unsigned 的低 24 位,这个字段用来记录对象的热度。对象被创建时会记录 lru 值。在被访问的时候也会更新 lru 的值。 但是不是获取系统当前的时间戳,而是设置为全局变量 server.lruclock 的值。 源码:server.h typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS;int refcount;void ptr; } robj; server.lruclock 的值怎么来的? Redis 中有个定时处理的函数 serverCron,默认每 100 毫秒调用函数 updateCachedTime 更新一次全局变量的 server.lruclock 的值,它记录的是当前 unix 时间戳。 源码:server.c void updateCachedTime(void) { time_t unixtime = time(NULL); atomicSet(server.unixtime,unixtime); server.mstime = mstime();struct tm tm; localtime_r(&server.unixtime,&tm);server.daylight_active = tm.tm_isdst; } 问题:为什么不获取精确的时间而是放在全局变量中?不会有延迟的问题吗? 这样函数 lookupKey 中更新数据的 lru 热度值时,就不用每次调用系统函数 time,可以提高执行效率。 OK,当对象里面已经有了 LRU 字段的值,就可以评估对象的热度了。 函数 estimateObjectIdleTime 评估指定对象的 lru 热度,思想就是对象的 lru 值和全局的 server.lruclock 的差值越大(越久没有得到更新),该对象热度越低。 源码 evict.c / Given an object returns the min number of milliseconds the object was never requested, using an approximated LRU algorithm. /unsigned long long estimateObjectIdleTime(robj o) {unsigned long long lruclock = LRU_CLOCK(); if (lruclock >= o->lru) {return (lruclock - o->lru) LRU_CLOCK_RESOLUTION; } else {return (lruclock + (LRU_CLOCK_MAX - o->lru)) LRU_CLOCK_RESOLUTION;} } server.lruclock 只有 24 位,按秒为单位来表示才能存储 194 天。当超过 24bit 能表 示的最大时间的时候,它会从头开始计算。 server.h define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) / Max value of obj->lru / 在这种情况下,可能会出现对象的 lru 大于 server.lruclock 的情况,如果这种情况 出现那么就两个相加而不是相减来求最久的 key。 为什么不用常规的哈希表+双向链表的方式实现?需要额外的数据结构,消耗资源。而 Redis LRU 算法在 sample 为 10 的情况下,已经能接近传统 LRU 算法了。 问题:除了消耗资源之外,传统 LRU 还有什么问题? 如图,假设 A 在 10 秒内被访问了 5 次,而 B 在 10 秒内被访问了 3 次。因为 B 最后一次被访问的时间比 A 要晚,在同等的情况下,A 反而先被回收。 问题:要实现基于访问频率的淘汰机制,怎么做? 5.2.4 LFU server.h typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS;int refcount;void ptr; } robj; 当这 24 bits 用作 LFU 时,其被分为两部分: 高 16 位用来记录访问时间(单位为分钟,ldt,last decrement time) 低 8 位用来记录访问频率,简称 counter(logc,logistic counter) counter 是用基于概率的对数计数器实现的,8 位可以表示百万次的访问频率。 对象被读写的时候,lfu 的值会被更新。 db.c——lookupKey void updateLFU(robj val) {unsigned long counter = LFUDecrAndReturn(val); counter = LFULogIncr(counter);val->lru = (LFUGetTimeInMinutes()<<8) | counter;} 增长的速率由,lfu-log-factor 越大,counter 增长的越慢 redis.conf 配置文件。 lfu-log-factor 10 如果计数器只会递增不会递减,也不能体现对象的热度。没有被访问的时候,计数器怎么递减呢? 减少的值由衰减因子 lfu-decay-time(分钟)来控制,如果值是 1 的话,N 分钟没有访问就要减少 N。 redis.conf 配置文件 lfu-decay-time 1 6、持久化机制 https://redis.io/topics/persistence Redis 速度快,很大一部分原因是因为它所有的数据都存储在内存中。如果断电或者宕机,都会导致内存中的数据丢失。为了实现重启后数据不丢失,Redis 提供了两种持久化的方案,一种是 RDB 快照(Redis DataBase),一种是 AOF(Append Only File)。 6.1 RDB RDB 是 Redis 默认的持久化方案。当满足一定条件的时候,会把当前内存中的数据写入磁盘,生成一个快照文件 dump.rdb。Redis 重启会通过加载 dump.rdb 文件恢复数据。 什么时候写入 rdb 文件? 6.1.1 RDB 触发 1、自动触发 a)配置规则触发。 redis.conf, SNAPSHOTTING,其中定义了触发把数据保存到磁盘的触发频率。 如果不需要 RDB 方案,注释 save 或者配置成空字符串""。 save 900 1 900 秒内至少有一个 key 被修改(包括添加) save 300 10 400 秒内至少有 10 个 key 被修改save 60 10000 60 秒内至少有 10000 个 key 被修改 注意上面的配置是不冲突的,只要满足任意一个都会触发。 RDB 文件位置和目录: 文件路径,dir ./ 文件名称dbfilename dump.rdb 是否是LZF压缩rdb文件 rdbcompression yes 开启数据校验 rdbchecksum yes 问题:为什么停止 Redis 服务的时候没有 save,重启数据还在? RDB 还有两种触发方式: b)shutdown 触发,保证服务器正常关闭。 c)flushall,RDB 文件是空的,没什么意义(删掉 dump.rdb 演示一下)。 2、手动触发 如果我们需要重启服务或者迁移数据,这个时候就需要手动触 RDB 快照保存。Redis 提供了两条命令: a)save save 在生成快照的时候会阻塞当前 Redis 服务器, Redis 不能处理其他命令。如果内存中的数据比较多,会造成 Redis 长时间的阻塞。生产环境不建议使用这个命令。 为了解决这个问题,Redis 提供了第二种方式。 执行 bgsave 时,Redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求。 具体操作是 Redis 进程执行 fork 操作创建子进程(copy-on-write),RDB 持久化过程由子进程负责,完成后自动结束。它不会记录 fork 之后后续的命令。阻塞只发生在 fork 阶段,一般时间很短。 用 lastsave 命令可以查看最近一次成功生成快照的时间。 6.1.2 RDB 数据的恢复(演示) 1、shutdown 持久化添加键值 添加键值 redis> set k1 1 redis> set k2 2 redis> set k3 3 redis> set k4 4 redis> set k5 5 停服务器,触发 save redis> shutdown 备份 dump.rdb 文件 cp dump.rdb dump.rdb.bak 启动服务器 /usr/local/soft/redis-5.0.5/src/redis-server /usr/local/soft/redis-5.0.5/redis.conf 啥都没有: redis> keys 3、通过备份文件恢复数据停服务器 redis> shutdown 重命名备份文件 mv dump.rdb.bak dump.rdb 启动服务器 /usr/local/soft/redis-5.0.5/src/redis-server /usr/local/soft/redis-5.0.5/redis.conf 查看数据 redis> keys 6.1.3 RDB 文件的优势和劣势 一、优势 1.RDB 是一个非常紧凑(compact)的文件,它保存了 redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。 2.生成 RDB 文件的时候,redis 主进程会 fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘 IO 操作。 3.RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。 二、劣势 1、RDB 方式数据没办法做到实时持久化/秒级持久化。因为 bgsave 每次运行都要执行 fork 操作创建子进程,频繁执行成本过高。 2、在一定间隔时间做一次备份,所以如果 redis 意外 down 掉的话,就会丢失最后一次快照之后的所有修改(数据有丢失)。 如果数据相对来说比较重要,希望将损失降到最小,则可以使用 AOF 方式进行持久化。 6.2 AOF Append Only File AOF:Redis 默认不开启。AOF 采用日志的形式来记录每个写操作,并追加到文件中。开启后,执行更改 Redis 数据的命令时,就会把命令写入到 AOF 文件中。 Redis 重启时会根据日志文件的内容把写指令从前到后执行一次以完成数据的恢复工作。 6.2.1 AOF 配置 配置文件 redis.conf 开关appendonly no 文件名appendfilename "appendonly.aof" AOF 文件的内容(vim 查看): 问题:数据都是实时持久化到磁盘吗? 由于操作系统的缓存机制,AOF 数据并没有真正地写入硬盘,而是进入了系统的硬盘缓存。什么时候把缓冲区的内容写入到 AOF 文件? 问题:文件越来越大,怎么办? 由于 AOF 持久化是 Redis 不断将写命令记录到 AOF 文件中,随着 Redis 不断的进行,AOF 的文件会越来越大,文件越大,占用服务器内存越大以及 AOF 恢复要求时间越长。 例如 set xxx 666,执行 1000 次,结果都是 xxx=666。 为了解决这个问题,Redis 新增了重写机制,当 AOF 文件的大小超过所设定的阈值时,Redis 就会启动 AOF 文件的内容压缩,只保留可以恢复数据的最小指令集。 可以使用命令 bgrewriteaof 来重写。 AOF 文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的 AOF 文件。 重写触发机制 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb 问题:重写过程中,AOF 文件被更改了怎么办? 另外有两个与 AOF 相关的参数: 6.2.2 AOF 数据恢复 重启 Redis 之后就会进行 AOF 文件的恢复。 6.2.3 AOF 优势与劣势 优点: 1、AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis 最多也就丢失 1 秒的数据而已。 缺点: 1、对于具有相同数据的的 Redis,AOF 文件通常会比 RDB 文件体积更大(RDB 存的是数据快照)。 2、虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。在高并发的情况下,RDB 比 AOF 具好更好的性能保证。 6.3 两种方案比较 那么对于 AOF 和 RDB 两种持久化方式,我们应该如何选择呢? 如果可以忍受一小段时间内数据的丢失,毫无疑问使用 RDB 是最好的,定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。 否则就使用 AOF 重写。但是一般情况下建议不要单独使用某一种持久化机制,而是应该两种一起用,在这种情况下,当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。 本篇文章为转载内容。原文链接:https://blog.csdn.net/zhoutaochun/article/details/120075092。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-03-18 12:25:04
541
转载
MySQL
...t(11) NOT NULL AUTO_INCREMENT, name varchar(45) DEFAULT NULL, age int(11) DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 这个表将包含学生的ID、姓名和年龄字段。 步骤四:编写Sqoop脚本 现在我们可以使用Sqoop将HDFS中的数据导入到MySQL表中。以下是一个基本的Sqoop脚本示例: bash -sqoop --connect jdbc:mysql://localhost:3306/test \ -m 1 \ --num-mappers 1 \ --target-dir /user/hadoop/students \ --delete-target-dir \ --split-by id \ --as-textfile \ --fields-terminated-by '|' \ --null-string 'NULL' \ --null-non-string '\\N' \ --check-column id \ --check-nulls \ --query "SELECT id, name, age FROM students WHERE age > 18" 这个脚本做了以下几件事: - 使用--connect选项连接到MySQL服务器和测试数据库。 - 使用-m和--num-mappers选项设置映射器的数量。在这个例子中,我们只有一个映射器。 - 使用--target-dir选项指定输出目录。在这个例子中,我们将数据导出到/user/hadoop/students目录下。 - 使用--delete-target-dir选项删除目标目录中的所有内容,以防数据冲突。 - 使用--split-by选项指定根据哪个字段进行拆分。在这个例子中,我们将数据按学生ID进行拆分。 - 使用--as-textfile选项指定数据格式为文本文件。 - 使用--fields-terminated-by选项指定字段分隔符。在这个例子中,我们将字段分隔符设置为竖线(|)。 - 使用--null-string和--null-non-string选项指定空值的表示方式。在这个例子中,我们将NULL字符串设置为空格,将非字符串空值设置为\\N。 - 使用--check-column和--check-nulls选项指定检查哪个字段和是否有空值。在这个例子中,我们将检查学生ID是否为空,并且如果有,将记录为NULL。 - 使用--query选项指定要从中读取数据的SQL查询语句。在这个例子中,我们只选择年龄大于18的学生。 请注意,这只是一个基本的示例。实际的脚本可能会有所不同,具体取决于您的数据和需求。 步骤五:运行Sqoop脚本 最后,我们可以使用以下命令运行Sqoop脚本: bash -sqoop \ -Dmapreduce.job.user.classpath.first=true \ --libjars $SQOOP_HOME/lib/mysql-connector-java-8.0.24.jar \ --connect jdbc:mysql://localhost:3306/test \ -m 1 \ --num-mappers 1 \ --target-dir /user/hadoop/students \ --delete-target-dir \ --split-by id \ --as-textfile \ --fields-terminated-by '|' \ --null-string 'NULL' \ --null-non-string '\\N' \ --check-column id \ --check-nulls \ --query "SELECT id, name, age FROM students WHERE age > 18" 注意,我们添加了一个-Dmapreduce.job.user.classpath.first=true参数,这样就可以保证我们的自定义JAR包在任务的classpath列表中处于最前面的位置。 如果一切正常,我们应该可以看到一条成功的消息,并且可以在MySQL中看到导出的数据。 总结 本文介绍了如何使用Apache Sqoop将HDFS中的数据导出到MySQL数据库。咱们先给环境捯饬得妥妥当当,然后捣鼓出一个MySQL表,再接再厉,编了个Sqoop脚本。最后,咱就让这个脚本大展身手,把数据导出溜溜的。希望这篇文章能帮助你解决这个问题!
2023-04-12 16:50:07
247
素颜如水_t
转载文章
...t=utf8; 所有字段名,使用,所有的字符串使用''或者"" 查看建表语句 show create table book; 查看表结构 desc book; 修改表名 rename table book to book1; 修改表属性 ,引擎和字符集 alter table book1 engine=innodb charset=utf8; 添加字段 first after alter table book1 add(type varchar(20) comment '类型',numinput int(10) comment '进货量',numstore int(10) comment '库存量'); 修改字段名 bid bno alter table book1 change bid bno int(4); 修改顺序 pub 放到author后面 alter table book1 modify pub varchar(50) after author; 修改数据类型 bno int(4) -->int(10) alter table book1 modify bno int(10); 删除字段 alter table book1 drop 字段名; 删除表 drop table 表名; 插入语句 insert into book1(bno,bname,author,type) values(1001,'斗破苍穹','天蚕土豆','玄幻');insert into book1(bno,bname,author,type) values(1002,'全职高手','蝴蝶兰','网游竞技');insert into book1(bno,bname,author,type) values(1003,'鬼吹灯','天下霸唱','恐怖');insert into book1(bno,bname,author,type)values(1004,'西游记','吴承恩','4大名著');insert into book1(bno,bname,author,type)values(1005,'java基础','王克晶','达内学习手册'); update语句 把1005号书,修改成'天线宝宝',作者不详,类型少儿 把1004号书修改成'天龙八部',作者金庸,类型武侠 update book1 set bname="天线宝宝",author="作者不详",type="少儿" where bno=1005; 删除类型是'恐怖'的所有书籍 删除全表记录 删除表格 修改book名称为book_item rename table book to book_item; 在表格尾部添加字段price double(7,2) alter table book_item add price double(7,2); 把price字段的位置放到author之后 alter table book_item modify price double(7,2) after author; 把表中存在的数据添加价格,每本书都在100~1000之间,自定 update book_item set price=199 where bno=1001; 修改1001的价格为500元 把所有字段的null字段补全 update book_item set pub="达内出版社",numinput=500,numstore=100 where pub is null; 删除价格小于150的所有条目 删除所有数据 SQL分类 数据定义语言 DDL 重点 数据操纵语言 DML 重点 增 删 改 数据查询语言 DQL select 查 事务控制语言 TCL 数据库控制语言 DCL 数据定义语言 DDL - 负责数据结构定义,与创建数据库对象的语言- 常用create alter drop- DDL不支持事务,DDL语句执行之后,不能回滚 数据操纵语言 DML - 对数据库中更改数据操作的语句- select insert update delete--> CRUD 增删改查- 通常把select相关操作,单独出来,称之为DQL- DML支持事务,在非自动提交模式时,可以利用rollback回滚操作. 数据查询语言 DQL - 筛选,分组,连表查询 面试重点 TCL 和 DCL - 事务控制语句TCL- 负责实现数据库中事务支持的语言,commit rollback savepoint等指令- DCL数据库控制语言- 管理数据库的授权,角色控制等,grant(授权),revoke(取消授权) 练习: 案例:创建一张表customer(顾客) create table customer(cid int(4) primary key comment '顾客编号',cname varchar(50) comment '顾客姓名',sex char(5) comment '顾客性别',address varchar(50) comment '地址',phone varchar(11) comment '手机',email varchar(50) comment '邮箱'); show create table customer; 插入5条数据 insert into customer values(1001,'小明','男','楼上18号','123','123@163.com');insert into customer values(1002,'小红','女','楼上17号','1234','1234@163.com');insert into customer values(1003,'老王','男','楼上18号隔壁','1234','1234@163.com');insert into customer values(1004,'老宋','男','楼上17号隔壁','1234','1234@163.com');insert into customer values(1005,'小马','女','楼上17号隔壁','1234','1234@163.com'); -1 修改一条数据的姓名 小红的姓名 -2 修改一条数据的性别 老王的性别 -3 修改一条数据的电话 1001号的电话 -4 修改一条数据的邮箱 邮箱为123@163.com,改成323@163.com -5 查询性别为 男的所有数据 select from customer where sex="男"; -6 自定义DDL操作的需求,5道题,可以同上面book表的操作 数据库数据类型 主要包括5大类 整数类型 int, big int 浮点数类型 double decimal 字符串类型 char varchar text 日期类型 date datetime timestamp time year... 其他数据类型 set.... 字符串 - char(固定长度) 定长字符串 最多255个字节- 定多少长度,就占用多少长度- 多了放不进去,少了用空格补全- 不认识内容尾部的空格- varchar(最大长度) 变长字符串 最大65535字节,但是使用一般不超过255- 只要不超过定的长度,都可以放进去- 以内容真实长度为准- 认识内容尾部的空格- text 最大65535字节- blob 大数据对象,以二进制(字节)的方式存储 整数 tinyint 1字节 smallint 2字节 int 4字节 bigint 8字节 int(6)影响的是查询时显示长度(zerofill)不影响数据的保存长度 create table t1(id1 int,id2 int(5)); insert into t1 values(111111,111111); alter table t1 modify id1 int zerofill; alter table t1 modify id2 int(5) zerofill; insert into t1 values (1,1); float 4字节 double 8字节 double(8,2) 可能会产生精度的缺失 10.0/3 3.3333333336 decimal 不会缺失精度,但是使用的时候需要指定总长度和小数位数 日期 - date 年月日- time 时分秒- datetime 年月日时分秒,到9999年,而且需要手动输入,如果没有手动输入,就显示null.- timestamp 年月日时分秒,在没有数据手动插入时,自动填入当前时间.最大值2038- bigint 1970-1-1 0:0:0 格林威治时间 案例:创建表t,字段d1 date,d2 time,d3 datetime,d4 timestamp create table t(id int,d1 date,d2 time,d3 datetime,d4 timestamp);insert into t (d1,d2) values ('1910-01-10','12:32:12');insert into t values(1,'2018-12-21','15:12:00','1995-02-10 12:08:12','2030-10-10 15:19:32');insert into t values(2,'3018-01-25','15:12:34','9234-12-31 12:12:12','2030-12-31 12:12:12');insert into t values(2,'3018-01-25','15:12:34','9999-12-31 23:59:59','2030-12-31 12:12:12'); 练习 创建人物表,插入,修改,查询 create table person(id int(4) primary key,name varchar(50),age int(3));insert into person values(1,"梅超风",36);insert into person values(2,"洪七公",96);insert into person values(3,"杨过",40);insert into person values(4,"令狐冲",28);insert into person values(5,"张三丰",100);insert into person values(6,"张翠山",27);insert into person values(7,"张无忌",27);insert into person values(8,"赵敏",18);insert into person values(9,"独孤求败",250);insert into person values(10,"楚留香",36);1.案例:修改张三丰的name为刘备,id为11update person set name="刘备",id=11 where name="张三丰";2.案例:修改2号人物的的name为夏侯渊update person set name="夏侯渊" where id=2;3.案例:根据条件修改person表中的数据,修改id是6的数据中,姓名改为'任我行', 年龄改为39update person set name="任我行",age=39 where id=6;4.案例:修改姓名是‘楚留香'的数据,把id改为20,年龄改为19update person set id=20,age=19 where name="楚留香";5.案例:把person所有的数据的年龄全部改为20 update person set age=20;6.案例:修改id为7的数据,把id改为100,姓名改为杨过,年龄改为21update person set id=100,name="杨过",age=21 where id=7;7.案例:修改姓名是独孤求败,把年龄改为35update person set age=35 where name="独孤求败";8.案例:修改id=8的信息,把姓名改为房玄龄update person set name="房玄龄" where id=8;9.案例 :修改id为20并且年龄为20的人的姓名为刘德华(郑少秋也行)提示 where...and...update person set name="郑少秋" where id=20 and age=20; 查询 没有条件的简单查询 select from 表名;查询表中所有的数据 select from person; select from t; select from emp; select from dept; 查询某些列中的值 select name as '姓名' from person; select name as '姓名',age as '年龄' from person; select id as '编号',name as '姓名',age as '年龄' from person; 学习过程的编程习惯select from 表; 工作中的编程习惯select id,name,age from person; 查询emp表中所有员工的姓名,上级领导的编号,职位,工资 select ename,mgr,job,sal from emp; 查询emp表中所有员工的编号,姓名,所属部门编号,工资 select empno,ename,deptno,sal from emp; 查询dept表中所有部门的名称和地址 select dname,loc from dept; 如果忘记了mysql的用户名和密码怎么办 卸载重新装 不重装软件如何修改密码 1.停止mysql服务 2.cmd中输入一个命令 mysqld --skip-grant-tables; -通过控制台,开启了一个mysql服务 3.开启一个新的cmd -mysql -u root -p 可以不使用密码进入数据库 show databases;----mysql 5. use mysql; 6. update user set password=password('新密码') where user="root"; 7. 关闭mysqld这个服务/进程 8. 重启mysql服务 作业 mysql02,一天的代码重新敲一遍,熟悉emp和dept列名 本篇文章为转载内容。原文链接:https://blog.csdn.net/sinat_41915844/article/details/79770973。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-02-16 12:44:07
544
转载
VUE
...体,也可以是一个计算字段或方法。当公式的值为true时,该class将会被加入到组件上;当公式的值为false时,该class将会被从组件上剔除。 <div v-bind:class="{ active: isActive }"></div> computed: { isActive: function () { return trueOrFalse } } 但是,如果我们想剔除某个class,而不是全部替换掉,该怎么做呢?我们可以使用三元公式来实现此功能,对于想要剔除的class,可以使用空字符串或null来替代它。 <div v-bind:class="{ 'one-class': true, 'another-class': false, 'no-class': someCondition ? '' : null }"></div> 在这个例子中,当someCondition为false时,'no-class'这个class将会被从组件上剔除。
2023-07-15 17:19:02
197
键盘勇士
Java
在Java中,null和0都是常见的概念。但是它们在语义上存在着很大的区别,下面我们来逐一探讨。 null 在Java中,null代表实例引用不引用任何对象。详细来讲,当一个对象被定义但未被对象化时,其值为null。并且,当一个实例引用一个已被撤销或未定义的对象时,其值也为null。 public class NullDemo { public static void main(String[] args){ String str = null; if(str == null){ System.out.println("str为无值"); } } } 在上述代码中,我们将一个字符串实例引用赋值为null。然后通过检测引用是否为null来检测其是否引用了任何对象。 0 在Java中,0通常用于代表整型、浮点型和字符型等简单数据类型的初始值。详细来讲,当一个字段被定义但未被赋值时,其值为0。 public class ZeroDemo { public static void main(String[] args){ int num = 0; if(num == 0){ System.out.println("num为0"); } } } 在上述代码中,我们将一个整型字段赋值为0。然后通过检测字段是否为0来检测其是否被赋值。 总而言之,null和0在Java中具有不同的语义和用法。需要我们根据实际场景进行区别使用。
2023-08-23 11:18:12
334
键盘勇士
JSON
...采用完全独立于语言的文本格式来存储和表示数据,易于人阅读和编写,同时也易于机器解析和生成。在文中提到,由于JSON的简洁、易读易写性,使得其在开发中被广泛应用于数据传输与交换场景,通过JSON Util等工具能更方便地处理这类数据。 JSONObject , 在JSON语法结构中,JSONObject代表一个无序集合,由一系列键值对组成,键是字符串类型,值可以是各种数据类型,包括数字(整数或浮点数)、字符串、Boolean值、数组、另一个JSONObject,甚至是null。在Java JSON Util库中,JSONObject是一个用于表示JSON对象的核心类,可以通过构造方法将JSON字符串转换为JSONObject实例,并提供了一系列方法用于获取、设置、检查其中的属性值。 Java Bean , 在Java编程领域,Java Bean是一种特殊的Java类,通常指符合特定规范的对象。这些规范主要包括。
2023-01-02 22:55:10
560
逻辑鬼才
JSON
... } return null; } 上述代码运用Jackson库的ObjectMapper类来将JSON转化成Map,其中TypeReference用于明确转化的目标数据类型,从而获取一个包含键值对数据的Map。 运用方法很简便,调用该方法并传递JSON字符串即可: String json = "{\"name\":\"John\", \"age\":30, \"city\":\"New York\"}"; Map<String, Object> map = jsonToMap(json); System.out.println(map.get("name")); //打印John 在实际开发中,我们需要根据具体情况进行处理,例如处理JSON中的嵌套对象和数组等,这需要我们对JSON的结构有一定的了解。总之,将JSON转化成Map是一项基本的操作,掌握后能够有效地提高我们的开发效率。
2023-12-27 11:56:29
500
逻辑鬼才
转载文章
...处理可能存在的空值(NULL)问题。它接受两个参数,如果第一个参数为NULL,则返回第二个参数的值;否则返回第一个参数的值。在文章给出的例子SELECT CEIL(NVL(REPLACE( 3s元 , 元 , ), 0)) FROM dual;中,NVL函数尝试将REPLACE函数替换后可能为空的结果转换为默认值0,以避免因空值导致的错误。 REPLACE函数 , 在Oracle数据库环境下,REPLACE函数是一种字符串处理函数,用于查找并替换字符串中指定的目标子串。在上述案例中,REPLACE( 3s元 , 元 , )会将字符串 3s元 中的字符 元 替换为空字符串,即删除所有出现的 元 字符,目标是试图将包含货币单位的文本转换成纯数字形式以便进行后续的数值计算,但由于原始字符串中包含了非数字字符 s ,因此即使经过REPLACE函数处理,也无法直接传给CEIL函数作为有效的数值参数。
2023-11-18 18:54:51
343
转载
HTML
...每行代表一条记录,各字段数据由逗号(或其他特定字符如制表符等)分隔,便于计算机程序处理和交换数据。在本文中,CSV文件被用作存储包含网页链接等信息的数据集,方便进一步进行数据分析和处理。 正则表达式 , 正则表达式是一种强大的文本处理工具,它定义了一种模式匹配语言,用于在文本字符串中查找、替换或提取符合特定模式的子串。在文章中,使用Python编程中的re模块实现正则表达式功能,以匹配CSV文件中链接字段里的URL,并成功提取出纯链接部分。 Python csv模块 , csv是Python标准库中的一个模块,专门用来读取和写入CSV文件。通过csv模块,开发者可以便捷地处理CSV文件中的数据,包括读取每一行内容并将其转换为字典结构(如文中使用的DictReader方法),或者将数据写入CSV文件时保持正确的分隔格式。在本文实例中,csv模块帮助我们高效地打开、读取CSV文件,并配合正则表达式对其中的数据进行了有效处理。
2023-01-04 22:21:53
479
数据库专家
JSON
JSON是一种依据字符串的简洁信息传输格式,它具有清晰可读简便易记、容易理解和兼容多种编程语言等优点。在JSON中,我们可以采用JSON列表来记录一连串数据项。区别于常规的数组,JSON列表是一个非有序集合,即数组中的每个元素并不需要给定一个索引位置,而是可以采用键值组合形式进行键值组合的记录。 { "fruits": [ {"name": "apple", "color": "red", "shape": "round"}, {"name": "banana", "color": "yellow", "shape": "long"}, {"name": "orange", "color": "orange", "shape": "round"}, {"name": "grape", "color": "purple", "shape": "round"} ] } 在上面的JSON数据中,"fruits"是一个含有四个对象的JSON列表,每个对象都有相应的"name"、"color"、"shape"字段值。在实际采用JSON时,可以采用for-in循环来遍历这样的JSON列表,取得其中的键值组合信息。 var data = JSON.parse('{"fruits":[{"name":"apple","color":"red","shape":"round"},{"name":"banana","color":"yellow","shape":"long"},{"name":"orange","color":"orange","shape":"round"},{"name":"grape","color":"purple","shape":"round"}]}'); for(var i in data.fruits) { console.log("Name: " + data.fruits[i].name + ", Color: " + data.fruits[i].color + ", Shape: " + data.fruits[i].shape); } 上面的JavaScript代码演示了如何解读JSON数据,并采用for-in循环遍历取得数组中的每个对象及其字段值。最终显示的结果为: Name: apple, Color: red, Shape: round Name: banana, Color: yellow, Shape: long Name: orange, Color: orange, Shape: round Name: grape, Color: purple, Shape: round 因此,JSON的非有序集合的特性使得其在处理多个键值组合数据时非常方便,同时也增强了其灵活性。
2023-01-19 19:48:00
519
代码侠
JSON
...采用完全独立于语言的文本格式来存储和传输数据。在本文中,JSON被用来描述数据结构,其特点是易于阅读和编写,同时也易于机器解析和生成。通过键值对的形式组织数据,可以表示数组、对象、字符串、数值、布尔值以及null等数据类型。 JSON属性过滤器 , 这是一种编程工具或方法,用于从原始的JSON数据中选择性地提取指定的属性或字段,形成一个新的JSON对象。在处理大量且复杂的数据时,开发者可以根据实际需求使用JSON属性过滤器来提高代码运行效率,减少不必要的数据传输和处理,从而优化系统性能。 API响应处理 , API(Application Programming Interface)是应用程序接口的简称,在Web开发中通常指HTTP API,它定义了软件系统之间相互通信的规则。API响应处理则是指客户端获取到服务器端通过API返回的数据后,对其进行解析、验证和进一步操作的过程。在文中提到,JSON属性过滤器在API响应处理中起到了关键作用,帮助开发者从API返回的JSON数据中筛选出所需的部分信息,以便更好地满足前端页面展示或业务逻辑的需求。
2023-02-21 22:09:00
545
电脑达人
Datax
...column1 = NULL WHERE column2 = 'error'"; SqlRunner.run(sql, DatabaseType.H2); } 在这个例子中,我们通过SQL语句,将表中column2为'error'的所有记录的column1字段设为NULL。这样,我们就清除了这些异常数据的影响。 五、结论 在使用Datax进行数据处理时,我们需要关注数据的质量、正确性和完整性等问题。通过严谨地给数据“体检”、反复验证其真实性,再仔仔细细地给它“洗个澡”,我们就能确保数据的准确度和可靠性蹭蹭上涨,真正做到让数据靠谱起来。同时呢,我们也要持续地改进咱们的数据处理方法,好让它们能灵活适应各种不断变化的数据环境,跟上时代步伐。
2023-05-23 08:20:57
281
柳暗花明又一村-t
SpringBoot
...0”,当你想把它当成字符串来处理的时候,这家伙可能会调皮地变成一个空荡荡的啥都没有。 java // SpringBoot 部分 - 接收数据的Controller @PostMapping("/register") public ResponseEntity registerUser(@RequestBody Map formData) { String email = formData.get("email").toString(); // 如果email是数字0,这里会变成"" // ... } 2. 默认值 另一个可能的原因是,前端在发送数据前没有正确处理可能的空值或默认值。你知道吗,有时候在发邮件前,email这哥们儿可能还没人填,这时它就暂且是JavaScript里的那个神秘存在“undefined”。一到要变成JSON格式,它就自动变身为“null”,然后后端大哥看见了,贴心地给它换个零蛋。 3. 数据验证 SpringBoot的@RequestBody注解默认会对JSON数据进行有效性校验,如果数据不符合约定的格式,它可能被视作无效,从而转化为默认值。检查Model层是否定义了默认值规则。 java // Model层 public class User { private String email; // ...其他字段 @NotBlank(message = "Email cannot be blank") public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } 四、解决策略 1. 前端校验 确保在发送数据之前对前端数据进行清理和验证,避免空值或非预期值被发送。 2. 明确数据类型 在Vue.js中,可以使用v-model.number或者v-bind:value配合计算属性,确保数据在发送前已转换为正确的类型。 3. 后端配置 SpringBoot可以配置Jackson或Gson等JSON库,设置@JsonInclude(JsonInclude.Include.NON_NULL)来忽略所有空值。 4. 异常处理 添加适当的异常处理,捕获可能的转换异常并提供有用的错误消息。 五、结论 解决这个问题的关键在于理解数据流的每个环节,从前端到后端,每一个可能的类型转换和验证步骤都需要仔细审查。你知道吗,有时候生活就像个惊喜包,比如说JavaScript那些隐藏的小秘密,但别急,咱们一步步找,那问题的源头准能被咱们揪出来!希望这篇文章能帮助你在遇到类似困境时,更好地定位和解决“0”问题,提升开发效率和用户体验。 --- 当然,实际的代码示例可能需要根据你的项目结构和配置进行调整,以上只是一个通用的指导框架。记住,遇到问题时,耐心地查阅文档,结合调试工具,往往能更快地找到答案。祝你在前端与后端的交互之旅中一帆风顺!
2024-04-13 10:41:58
82
柳暗花明又一村_
转载文章
...提供一个表单(标签、文本框、密码框、单选按钮、复选框、按钮、下拉列表框、列表框、多行文本框等模拟注册界面,可以参考给定的图片布局)。 ①在第1个页面,输入相应内容、选择相应内容、选择出生日期后,自动计算年龄并显示到对应文本框中。 ②用户可以输入或者选择相关内容,点击“注册”按钮将输入和选择的数据传递给第2个JSP页面result.jsp。 (2)第2个页面,命名为result.jsp:通过request对象获得注册页面的信息,然后在该页面以表格形式显示出来。如下图所示 (建议,可以将用户信息编写成一个实体类) 2.具体代码 (1)login.jsp <%@ page contentType="text/html; charset=GB2312"%><HTML><body><center><h2>模拟注册页面</h2></center><font size=3><h3><form action="case03ssy2result.jsp" method=post><br>用户名:<input type="text" size="16" minlength="6" maxlength="16" aligin="left" name="username">   <b><i>用户名由6~16个字符组成,包括汉字,数字,字母等</i></b></br><p>密 码: <input type="password" size="16" minlength="6" maxlength="16" aligin="left" name="pwd">   <b><i>密码由6~16个字符组成,包括数字,字母等</i></b></p><p>性 别: <input type="radio" value="男" name="sex"/>男 <input type="radio" value="女" name="sex"/>女   年龄:<input type="text" size="4" name="age" id="age" style="background-color:grey" readonly><p>出生日期:<select name="year" id="year" onblur="changeAge()"> <% for(int y=1990;y<=2010;y++){ %><option value="<%=y %>"><%=y %></option><%}%></select>年<select name="month"><% for(int m=1;m<=12;m++){ %><option value="<%=m%>"><%=m %></option><%} %></select>月<select name="day"> <% for(int d=1;d<=31;d++){ %><option value="<%=d %>"><%=d %></option><%} %></select>日</p><p>爱 好:<input type="checkbox" value="唱歌" name="hobbies" />唱歌<input type="checkbox" value="听歌" name="hobbies" />听歌<input type="checkbox" value="篮球" name="hobbies" />篮球<input type="checkbox" value="乒乓球" name="hobbies" />乒乓球<input type="checkbox" value="足球" name="hobbies" />足球<input type="checkbox" value="羽毛球" name="hobbies" />羽毛球</p><p>所学课程:<select name="course" multiple="multiple" size="10"><option value="计算机科学导论">计算机科学导论</option><option value="C程序设计基础">C程序设计基础</option><option value="数据结构">数据结构</option><option value="操作系统原理">操作系统原理</option><option value="软件工程概论">软件工程概论</option><option value="算法分析与设计">算法分析与设计</option><option value="Java编程基础">Java编程基础</option><option value="计算机网络">计算机网络</option><option value="数据库系统原理及应用">数据库系统原理及应用</option><option value="软件设计">软件设计</option><option value="软件测试">软件测试</option><option value="Java Web应用程序开发">Java Web应用程序开发</option><option value="组网工程">组网工程</option><option value="软件项目管理">软件项目管理</option><option value="云计算与大数据技术">云计算与大数据技术</option><option value="粮油信息处理及模式识别">粮油信息处理及模式识别</option><option value="软件开发案例分析">软件开发案例分析</option><option value="软件交互设计">软件交互设计</option></select>按住Ctrl按钮来选择多个项目</p><p>个人简历:<textArea name="cv" rows="3" cols="35" align="top" ></textArea></p><p><center><input type="submit" value="注册" name="submit"></center></p></form></h3></font><script type="text/javascript">function changeAge() {console.log("调用了函数");var nowData = new Date();console.log(nowData.getUTCFullYear());var nowYear = nowData.getUTCFullYear();console.log(document.getElementById("year").value)var year = document.getElementById("year").value;var age = nowYear - year;var e = document.getElementById("age");e.value = age;}</script></body></HTML> (2)result.jsp <%@ page contentType="text/html; charset=GB2312"%><%! public String handleStr(String s){try{ byte [] bb=s.getBytes("GB2312");s=new String(bb);}catch(Exception exp){}return s;}%><HTML><body bgcolor=yellow><font size=3><% request.setCharacterEncoding("GB2312");String username=request.getParameter("username");String pwd=request.getParameter("pwd");String sex=request.getParameter("sex");String year=request.getParameter("year");String month=request.getParameter("month");String day=request.getParameter("day");String age=request.getParameter("age");String hobbies[]=request.getParameterValues("hobbies");String course[]=request.getParameterValues("course");String cv=request.getParameter("cv");%>注册个人信息如下:<br><table border=2><tr><td><% out.print("用户名");%></td><td><% out.print("密码"); %></td><td><% out.print("性别"); %></td><td><% out.print("出生日期"); %></td><td><% out.print("年龄"); %></td><td><% out.print("爱好"); %></td><td><% out.print("所学课程"); %></td><td><% out.print("个人简历"); %></td></tr><tr><td><% out.print(username); %></td><td><% out.print(pwd); %></td><td><% out.print(sex); %></td><td><% out.print(year+"年"+month+"月"+day+"日"); %></td><td><% out.print(age); %></td><td><% if(hobbies==null){out.println("无");}else{ for(int m=0;m<hobbies.length;m++){out.print(handleStr(hobbies[m])+" ");} }%></td><td><% if(course==null){out.println("无");}else{ for(int n=0;n<course.length;n++){out.print(handleStr(course[n])+" ");} }%></td><td><% out.print(cv); %></td></tr></table></font></body></HTML> 3.运行结果 4.总结分析 在大体功能实现的基础上,虽然实现了用户信息登录与记录,但是此界面只能输入并记录一个用户 ,无法实现多用户,有待改正。另外,在登录界面年龄下拉列表没用考录闰年与平年的区别,把每个月份都设置为了31天。 求大佬改正。 本篇文章为转载内容。原文链接:https://blog.csdn.net/Pluto_ssy/article/details/121049221。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-08-15 09:02:21
113
转载
转载文章
...获取下拉框被选中项的文本var文本值一下拉框对象.options[index].text;注:1.获取下拉框选中项的值时: (value)如果option标签设置了value属性值,则获取value属性对应的值;如果option标签未设置value属性值,则获取的是option双标签中的文本值2.下拉框的选中状态:选中状态: selected = selected、 selected、 selected = true未选中状态:不设置selected属性、 selected=false; <form id='myform' name="myform" action="" method="get">姓名:<input type="text" id="uname" name="uname" value="zs"/><br />密码:<input type= "password" id="upwd" name="upwd" value= "1234"/><br /><input type="hidden" id= "uno" name="uno" value="隐藏域"/>个人说明:<textarea name="intro" ></textarea><br><button type="button" onclick="getTxt();" >获取元素内容</button><hr><br><input type="text" name="inputName" class="test" value="aaa" /><input type="radio" name="rad" class="test" value="1" /> 男<input type="radio" name="rad" class="test" value="2" /> 女<button type="button" onclick="getRadio()">获取单选按钮</button><br><hr><br>全选/全不选: <input type="checkbox" id="control" onclick="checkAllOrNot()" /><button type="button" onclick= "checkFan()">反选</button><br><input type="checkbox" name= "hobby" value="sing" />唱歌<input type="checkbox" name= "hobby" value="dance" />跳舞<input type="checkbox" name= "hobby" value="rap" />说唱<button type="button" onclick="getCheckBox()">获取多选按钮</button><br><hr><br>来自:<select id="ufrom" name= "ufrom" ><option value = "" >请选择</option><option value = "Beijing" selected="selected" >北京</option><option value = "Shanghai">上海</option><option value = "Hangzhou">杭州</option></select><button type="button" onclick= "getSelect()" >获取下拉选项</button></form><script type=" text/javascript">function getTxt() {// 1. document.getElementById("id属性值");var uname = document.getElementById("uname").value;console.log(uname);// 2.表单对象.表单元表的name属性值;var pwd = document.getElementById("myform").upwd.value;console.log(pwd);// 3. document.getELementsByName("name属性值");var uno = document.getElementsByName("uno")[0].value;console.log(uno);// 4. document.getELementsByTagName("标签名/元素名");var intro = document.getElementsByTagName("textarea")[0].value;console.log(intro);}function getSelect() {//获取下拉框对象var ufrom = document.getElementById("ufrom");console.log(ufrom);//获取下拉框的下拉选项列表var opts = ufrom.options;console.log(opts);//获取下拉框被选中项的索引var index = ufrom.selectedIndex;console.log("选中项的下标:" + index);//获取下拉框被选中项的值var val = ufrom.value;console.log("被选中项的值:" + val);//通过选中项的下标获取下拉框被选中项的值var val2 = ufrom.options[index].value;console.log("被选中项的值:"+ val2);//获取下拉框被选中项的文本var txt=ufrom.options[index].text; console.log("被选中项的文本:"+ txt);}</script> 运行效果截图: 三、提交表单 提交表单一、使用普通按钮type="button"1.给按钮绑定click点击事件,绑定函数2.在函数中,进行表单校验(非空校验、 合法性校验等)3.如果校验通过,则手动提交表单表单对象.submit();二、使用提交按钮type="submit"1.给按钮绑定click点击事件,绑定函数2.函数需要有返回值,返回true或false (如果return false, 则表单不会提交:如果return true,则提交表单)onclick="return 函数名()"3.在函数中,进行表单校验(非空校验、 合法性校验等)4.如果校验通过,返回true;如果校验不通过,则返回false, 则表单不会提交:如果return true,则提交表单)三、使用提交按钮type="submit"1.给表单form元素绑定submit提交事件,绑定函数2.函数需要有返回值,返回true或false (如果return false, 则表单不会提交;如果return trueonsubmit="return函数名()" 3.在函数中,进行表单校验(非空校验、 合法性校验等)4.如果校验通过,返回true;如果校验不通过,则返回false <!--使用普逍按钮 type= "button"--><form id= 'myform' name= "myform" action="http://www.baidu.com" method="get" >姓名: <input name= "uname" id="uname"/> <span id = "msg" style="font-s1ze: 12px; color: red;"></span><br /><button type="button" onclick="submitForm1()">提交</button></form><!--使用提交按钮 type= "submit"--><form id= 'myform2' name= "myform2" action="http://www.baidu.com" method="get" >姓名: <input name= "uname2" id="uname2"/> <span id = "msg2" style="font-s1ze: 12px; color: red;"></span><br /><button type="submit" onclick="return submitForm2()">提交</button></form><!--使用提交按钮 type= "submit"--><form id= 'myform3' name= "myform3" action="http://www.baidu.com" method="get" onsubmit="return submitForm3()">姓名: <input name= "uname3" id="uname3"/> <span id = "msg3" style="font-s1ze: 12px; color: red;"></span><br /><button type="submit">提交</button></form><script type="text/javascript">// 表单校验// 提交表单function submitForm1() {//得到文本框的值var uname = document.getElementById("uname").value;//判断是否为空if (isEmpty(uname)) { //为空//设置提示信息(设置span元素的值)document.getElementById("msg").innerHTML="性名不能为空!" ;//阻止表单提交return;}//手动提交表单document.getElementById("myform").submit(); }function submitForm2() {//得到文本框的值var uname2 = document.getElementById("uname2").value;//判断是否为空if (isEmpty(uname2)) { //为空//设置提示信息(设置span元素的值)document.getElementById("msg2").innerHTML="性名不能为空!" ;//阻止表单提交return false;}return true;}function submitForm3() {//得到文本框的值var uname3 = document.getElementById("uname3").value;//判断是否为空if (isEmpty(uname3)) { //为空//设置提示信息(设置span元素的值)document.getElementById("msg3").innerHTML="性名不能为空!" ;//阻止表单提交return false;}return true;}/ 判断字符串是否为空如果为空,返回true如果非空,返回falsetrim() :字符串方法, 去除字符串前后空格@param {Object} str/function isEmpty(str) {//判断是否为空if (str == null || str.trim() == "") {return true;}return false;}</script> 运行效果截图: 四、原生Ajax实现流程 <!-- Ajax 异步无刷新技术原生Ajax的实现流程1.得到XMLHttpRequest对象var xhr = new XMLHttpRequest();2.打开请求xhr.open(method, uri, async) ;method:请求方式,通常是GEI|POSTurl:请求地址async:是否异步。如果是true表示异步,false表示同步3.发送请求xhr.send(params);params:请求时需要传递的参数如果是GET请求,设置nu11。 (GET请求的参数设置在url后面)如果是POST请求,无参数设置为null,有参数则设置参数4.接收响应xhr.status响应状态(200=响应成功, 404=资源末找到,500=服务器异常)xhr.responseText 得到响应结果 --> <script type="text/javascript">// 同步请求function text01() {// 1.得到XMLHttpRequest对象var xhr = new XMLHttpRequest();// 2.打开请求xhr.open("get", "js/date.json", false);// 3.发送请求xhr.send(null);// 4.判断响应状态if (xhr.status == 200) {console.log("响应成功");} else {console.log("状态码:" + xhr.status + ",原因:" + xhr.responseText)}console.log("同步请求...");}text01();// 异步请求function text02() {// 1.得到XMLHttpRequest对象var xhr = new XMLHttpRequest();// 2.打开请求xhr.open("get", "js/date.json", true);// 3.发送请求xhr.send(null);// 由于是异步请求,所以需要知道后台已经将请求处理完毕,才能获取响应结果// 遇过监听readyState的变化来得知后面的处理状态 4=完全处理xhr.onreadystatechange = function(){if(xhr.readyState == 4){// 4.判断响应状态if (xhr.status == 200) {// 得到响应结果 console.log(xhr.responseText);} else {console.log("状态码:" + xhr.status + ",原因:" + xhr.responseText)} }}console.log("异步请求...");}text02();</script> 运行效果截图: 本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_61507413/article/details/122895643。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-22 17:32:41
521
转载
Apache Lucene
...e与javalangNullPointerException: null 一、引言 初遇Lucene与NullPointer 嘿,朋友们!今天我们要聊聊一个非常有趣的技术话题——Apache Lucene。这是一款开源的全文搜索库,它在搜索引擎领域有着举足轻重的地位。话说在咱们聊Lucene之前,我得先吐槽一下最近在开发中遇到的一个超级烦人的bug——就是那个“javalangNullPointerException: null”。简直让人抓狂啊!这个异常常常会出现在我们的代码中,特别是在处理复杂数据结构时。那么,让我们一边学习如何优雅地使用Lucene,一边看看如何巧妙地避开NullPointerException吧! 二、Lucene的魅力所在 从概念到实践 首先,让我们来了解一下Lucene的基本概念。Lucene可真是个厉害的角色,它是个超级能打的文本搜索小能手,给咱们提供了全套的工具,不管是建索引、搜东西还是让搜索结果更给力,都能搞定!简单来说,Lucene就像是你电脑上的超级搜索引擎,但它的能力远不止于此。 2.1 创建你的第一个索引 在开始之前,你需要确保已经在你的项目中引入了Lucene的相关依赖。接下来,让我们通过一些简单的步骤来创建一个基本的索引: java import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; public class SimpleIndexer { public static void main(String[] args) throws Exception { // 创建内存中的目录,用于存储索引 Directory directory = new RAMDirectory(); // 创建索引配置 IndexWriterConfig config = new IndexWriterConfig(new StandardAnalyzer()); // 创建索引写入器 IndexWriter indexWriter = new IndexWriter(directory, config); // 创建文档对象 Document doc = new Document(); doc.add(new Field("content", "Hello Lucene!", Field.Store.YES, Field.Index.ANALYZED)); // 添加文档到索引 indexWriter.addDocument(doc); // 关闭索引写入器 indexWriter.close(); } } 在这个例子中,我们首先创建了一个内存中的目录(RAMDirectory),这是为了方便演示。接着,我们定义了索引配置,并使用StandardAnalyzer对文本进行分析。最后,我们创建了一个文档,并将它添加到了索引中。是不是很简单呢? 2.2 解决NullPointerException:预防胜于治疗 现在,让我们回到那个恼人的NullPointerException问题上。在用Lucene做索引的时候,经常会被空指针异常坑到,特别是当你试图去访问那些还没被初始化的对象或者字段时。为了避免这种情况,我们需要养成良好的编程习惯,比如: - 检查null值:在访问任何对象前,先检查是否为null。 - 初始化变量:确保所有对象在使用前都被正确初始化。 - 使用Optional类:Java 8引入的Optional类可以帮助我们更好地处理可能为空的情况。 例如,假设我们在处理索引文档时遇到了一个可能为空的字段,我们可以这样处理: java // 假设我们有一个可能为空的内容字段 String content = getContent(); // 这里可能会返回null if (content != null) { doc.add(new Field("content", content, Field.Store.YES, Field.Index.ANALYZED)); } else { System.out.println("内容字段为空!"); } 三、深入探索 Lucene的高级特性 3.1 搜索:不仅仅是查找 除了创建索引外,Lucene还提供了强大的搜索功能。让我们来看一个简单的搜索示例: java import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.Directory; public class SimpleSearcher { public static void main(String[] args) throws Exception { Directory directory = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(new StandardAnalyzer()); IndexWriter indexWriter = new IndexWriter(directory, config); Document doc = new Document(); doc.add(new Field("content", "Hello Lucene!", Field.Store.YES, Field.Index.ANALYZED)); indexWriter.addDocument(doc); indexWriter.close(); DirectoryReader reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader); QueryParser parser = new QueryParser("content", new StandardAnalyzer()); Query query = parser.parse("lucene"); TopDocs results = searcher.search(query, 10); for (ScoreDoc scoreDoc : results.scoreDocs) { System.out.println(searcher.doc(scoreDoc.doc).get("content")); } reader.close(); } } 这段代码展示了如何使用QueryParser解析查询字符串,并使用IndexSearcher执行搜索操作。通过这种方式,我们可以轻松地从索引中检索出相关的文档。 3.2 高级搜索技巧:优化你的查询 当你开始构建更复杂的搜索逻辑时,Lucene提供了许多高级功能来帮助你优化搜索结果。比如说,你可以用布尔查询把好几个搜索条件拼在一起,或者用模糊匹配让搜索变得更灵活一点。这样找东西就方便多了! java import org.apache.lucene.index.Term; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.FuzzyQuery; // 构建布尔查询 BooleanQuery booleanQuery = new BooleanQuery(); booleanQuery.add(new TermQuery(new Term("content", "hello")), BooleanClause.Occur.MUST); booleanQuery.add(new FuzzyQuery(new Term("content", "lucen")), BooleanClause.Occur.SHOULD); TopDocs searchResults = searcher.search(booleanQuery, 10); 在这个例子中,我们创建了一个布尔查询,其中包含两个子查询:一个是必须满足的精确匹配查询,另一个是可选的模糊匹配查询。这种组合可以显著提升搜索的准确性和相关性。 四、结语 享受编码的乐趣 通过这篇文章,我们不仅学习了如何使用Apache Lucene来创建和搜索索引,还一起探讨了如何有效地避免NullPointerException。希望这些示例代码和技巧能对你有所帮助。记住,编程不仅仅是一门技术,更是一种艺术。尽情享受编程的乐趣吧,一路探索和学习,你会发现自己的收获多到让人惊喜!如果你有任何问题或想法,欢迎随时与我交流! --- 以上就是关于Apache Lucene与javalangNullPointerException: null的讨论。希望能通过这篇文章点燃你对Lucene的热情,让你在实际开发中游刃有余,玩得更嗨!让我们一起继续探索更多有趣的技术吧!
2024-10-16 15:36:29
88
岁月静好
转载文章
...照UTF-8编码构成字符串值。 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性的以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表,它的数据项构成如下图。 2.魔数(Magic Number) 每一个Class文件的头4个字节成为魔数(Magic Number),它的唯一作用是确定这个文件是否是一个能被虚拟机接收的Class文件。很多文件存储标准中都是用魔数来进行身份识别,比如gif、png、jpeg等都有魔数。使用魔数主要是来识别文件的格式,相比于通过文件后缀名识别,这种方式准确性更高,因为文件后缀名可以随便更改,但更改二进制文件内容的却很少。Class类文件的魔数是Oxcafebabe,cafe babe?咖啡宝贝?至于为什么是这个, 这个名字在java语言诞生之初就已经确定了,它象征着著名咖啡品牌Peet's Coffee中深受欢迎的Baristas咖啡,Java的商标logo也源于此。 3.文件版本(Version) 在魔数后面的4个字节就是Class文件的版本号,第5和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)。Java的版本号是从45开始的,JDK1.1之后的每个JDK大版本发布主版本号向上加1(JDK1.0~1.1使用的版本号是45.0~45.3),比如我这里是十六进制的Ox0034,也就是十进制的52,所以说明该class文件可以被JDK1.8及以上的虚拟机执行,否则低版本虚拟机执行会报java.lang.UnsupportedClassVersionError错误。 4.常量池(Constant Pool) 在主版本号紧接着的就是常量池的入口,它是Class文件结构中与其他项目关联最多的数据类型,也是占用空间最大的数据之一。常量池的容量由后2个字节指定,比如这里我的是Ox001d,即十进制的29,这就表示常量池中有29项常量,而常量池的索引是从1开始的,这一点需要特殊记忆,因为程序员习惯性的计数法是从0开始的,而这里不一样,所以我这里常量池的索引范围是1~29。设计者将第0项常量空出来是有目的的,这样可以满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义。 通过javap -v命令反编译出class文件之后,我们可以看到常量池的内容 常量池中主要存放两大类常量:字面量和符号引用。比如文本字符、声明为final的常量值就属于字面量,而符号引用则包含下面三类常量: 类和接口的全限名 字段的名称和描述符 方法的名称和描述符 在之前的文章(详谈类加载的全过程)中有详细讲到,在加载类过程的第二大阶段连接的第三个阶段解析的时候,会将常量池中的符号引用替换为直接引用。相信很多人在开始了解那里的时候也是一头雾水,作者我也是,当我了解到常量池的构成的时候才明白真正意思。Java代码在编译的时候,是在虚拟机加载Class文件的时候才会动态链接,也就是说Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法获得真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。 常量池中每一项常量都是一张表,这里我只找到了JDK1.7之前的常量池项目类型表,见下图。 常量池项目类型表: 常量池常量项的结构总表: 比如我这里测试的class文件第一项常量,它的标志位是Ox0a,即十进制10,即表示tag为10的常量项,查表发现是CONSTANT_Methodref_info类型,和上面反编译之后的到的第一个常量是一致的,Methodref表示类中方法的符号引用。查上面《常量池常量项的结构总表》可以看到Methodref中含有3个项目,第一个tag就是上述的Ox0a,那么第二个项目就是Ox0006,第三个项目就是Ox000f,分别指向的CONSTANT_Class_info索引项和CONSTANT_NameAndType_info索引项为6和15,那么反编译的结果该项常量指向的应该是6和15,查看上面反编译的图应证我们的推测是对的。后面的常量项就以此类推。 这里需要特殊说明一下utf8常量项的内容,这里我以第29项常量项解释,也就是最后一项常量项。查《常量池常量项的结构总表》可以看到utf8项有三个内容:tag、length、bytes。tag表示常量项类型,这里是Ox01,表示是CONSTANT_Utf8_info类型,紧接着的是长度length,这里是Ox0015,即十进制21,那么再紧接着的21个字节都表示该项常量项的具体内容。特别注意length表示的最大值是65535,所以Java程序中仅能接收小于等于64KB英文字符的变量和变量名,否则将无法编译。 5.访问标志(Access Flags) 在常量池结束后,紧接着的两个字节代表访问标志(Access Flags),该标志用于识别一些类或者接口层次的访问信息,其中包括:Class是类还是接口、是否定义为public、是否定义为abstract类型、类是否被声明为final等。 访问标志表 标志位一共有16个,但是并不是所有的都用到,上表只列举了其中8个,没有使用的标志位统统置为0,access_flags只有2个字节表示,但是有这么多标志位怎么计算而来的呢?它是由标志位为true的标志位值取或运算而来,比如这里我演示的class文件是一个类并且是public的,所以对应的ACC_PUBLIC和ACC_SIPER标志应该置为true,其余标志不满足则为false,那么access_flags的计算过程就是:Ox0001 | Ox0020 = Ox0021 篇幅原因,未完待续...... 参考文献:《深入理解Java虚拟机》 END 本篇文章为转载内容。原文链接:https://javar.blog.csdn.net/article/details/97532925。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-01-09 17:46:36
645
转载
转载文章
...Empty()判空(null和size=0) 对于对象使用ObjectUtils.isEmpty()判定,可以尝试使用Optional.ofNullable() 对于数组使用ArrayUtils.isEmpty()判空(null和length=0) 对于字符串使用 StringUtils.isBlank()判空(null和空字符串) 工具类 使用hutool可以方便的进行文件类型的判断、唯一id(uuid,Snowflake)的生成、数据加密解密、二维码生成、图片加水印、BASE64编码解码、图片验证码等操作 集合 使用Arrays.asList()返回的list为数组的内部list,只允许遍历不允许增删,可以使用Stream流转换为list Collection和map对于仅遍历可以使用增强for循环和,但如果有删除为避免错误必须使用迭代器 foreach遍历不允许改变变量的地址,java的参数是值传递,修改了形参的地址并不影响原来的参数,故即使你修改了值也不会同步到原变量中,故操作的变量都显式或者隐式的定义为final JSON fastjson parseArray(String text, Class<T> clazz) 解析List parseObject(String text, Class<T> clazz) 解析Object JSON对于null、空白字符串、“null”会返回nullif (text == null) {return null;} else {DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance());JSONLexer lexer = parser.lexer;int token = lexer.token();ArrayList list;if (token == 8) {lexer.nextToken(); // nextToken() => ...if ("null".equalsIgnoreCase(ident)) this.token = 8;list = null;} } String toJSONString(Object object) 将对象转为String toJSONBytes(Object object, SerializerFeature... features) 将对象转为byte[] @JSONField() 可以忽略字段serialize ,别名映射name,日期格式化format等 jackson @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 设置Date到前台的格式 @JsonIgnore SpringMVC不会向前台传递该字段 ObjectMapper mapper = new ObjectMapper();String str = mapper.writeValueAsString(admin); // 对象转JSON字符串mapper.readValue(s,Admin.class ); // JSON字符串转对象 EasyExcel 官方API https://www.yuque.com/easyexcel/doc 使用类注解@ExcelIgnoreUnannotated配合@ExcelProperty操作 @ExcelProperty可以指定表头列名,列顺序和表头的合并 @ColumnWidth(10)可以指定列宽,其长度约为(中文length3+英文length1) @DateTimeFormat(value="yyyy-MM-dd HH:mm:ss")可以指定日期格式 自定义策略实现SheetWriteHandler工作表回调接口,在afterSheetCreate()工作表创建之后方法可以 设置列宽 自定义表头 新建单元格 自定义策略实现RowWriteHandler行回调接口,在afterRowDispose()行操作完之后方法可以 设置行高 设置行样式 自定义策略实现CustomerCellHandler单元格回调接口,在afterCellDispose()单元格操作完之后方法可以 根据行号,列宽甚至是单元格的值来设置单元格样式 可以对单元格的值获取和修改 样式通常包括内容格式、批注、背景色、自动换行、平和垂直居中、边框大小和颜色、字体实例(格式,颜色,大小,加粗等)等 自定义策略继承AbstractMergeStrategy单元格合并抽象类,在merge()方法中可以通过CellRangeAddress合并单元格 过于复杂的表格可以使用模板,配合写出write和填充fill一起使用 Mybatis 在mapper方法的@select中也是可以直接书写动态SQL的,但要使用<script></script>包裹,这样就不用在java文件和xml文件切换了,将@select中包裹的代码直接放到浏览器的控制台输出后会自动转义\n,\t,+,"等 动态sql中“<” 和 “>” 号要用转义字符 “<” 和 ”>“ (分号要带) 动态sql中test中表达式通常使用 test=“id != null and id != ‘’”,要注意的是字符串不能直接识别单引号,有两种方法使用id==“1001"或者id==‘1001’.toString(),另外参数如果是boolean,可以直接使用test=”!flag",如果判定集合的话可以使用 test=“list != null and list.size>0” 返回数据类型为Map只能接收一条记录,字段为键名,字段值为值,但通常是用实体类接收,或是使用注解@MapKey来进行每条记录的映射,效果等同于List用Stream流转Map foreach遍历list collection=“list” item=“vo” separator="," open="(" close=")"> {vo.id} foreach遍历map collection=“map” index=“key” item=“value”,{key}获取建,{value}获取值,$亦可 collection=“map.entrySet()” index=“key” item=“value”,同上 collection=“map.keys” item=“key”,{key}为键 不要使用where 1=1,使用动态where拼接,会自动剔除where后多余的and和or 单个参数时无论基本和引用并且未使用在动态SQL可以不加参数注解@Param,但一旦参数大于一个或者参数在动态SQL中使用就必须加@Param 并不是直接把参数加引号,而是变成?的形式交给prepareStatement处理,$直接使用值,当ORDER BY诸如此类不需要加引号的参数时,使用$代替,但为避免sql注入,该参数不能交由用户控制 Plus 官方API https://baomidou.com/guide/ @TableName 表名 @TableField(strategy = FieldStrategy.IGNORED) 更新不会忽略NULL值 @TableField(exist = false)表明该字段非数据字段,否则新增更新会报错 MybatisPlus对于单表的操作还是非常优秀的,在对单表进行新增或者更新的时候经常使用,但对于单表的查询业务上很少出现仅仅查询一张表的情况,但也会有,如果条件不大于3个还是可以使用的,多了倒没有直接写SQL来的方便了 MybatisPlus的批量插入也是通过for循环插入的,还是建议使用Mybatis的动态foreach进行批量插入 MybatisPlus的分页器会对方法中的参数判断,如果存在分页对象就先查询总数看是否大于0,然后拼接当前的数据库limit语句,所以如果我们分页对象为null,就可以实现不分页查询 Object paramObj = boundSql.getParameterObject();IPage page = null;if (paramObj instanceof IPage) { ……public static String getOriginalCountSql(String originalSql) {return String.format("SELECT COUNT(1) FROM ( %s ) TOTAL", originalSql);} ……originalSql = DialectFactory.buildPaginationSql(page, buildSql, dbType, this.dialectClazz); ……public String buildPaginationSql(String originalSql, long offset, long limit) {StringBuilder sql = new StringBuilder(originalSql);sql.append(" LIMIT ").append(offset).append(",").append(limit);return sql.toString();} IDEA 插件 Lombok : 快速生成getter、setter等 Alibaba Java Coding Guidelines :阿里规约扫描 Rainbow Brackets :彩色括号 HighlightBracketPair :高亮提示 MyBatisX :mabatisPlus提供的xml和mapper转换的插件,小鸟图标 CamelCase :大小写、驼峰、下划线、中划线转换插件 使用shift+Alt+u进行转换(很方便) 可以在Editor中设置CamelCase的转换,一般只保留下划线和驼峰两种 String Manipulation :字符串工具(未使用) RestfulToolkit http :Restful请求工具 打开idea,在右侧边栏会有一个标签(RestServices),打开可以看到里面是url路径 ctrl+\或者ctrl+alt+n会检索路径 Ctrl + Enter格式化json 没有记忆功能,也不能加token,只是查找请求路径使用 easycode :代码生成工具(个人觉得很好用,常用于生成实体类) 支持自定义模板 支持添加自定义列,不影响数据库 支持多表同时生成 支持自定义类型映射 支持配置导入导出 支持动态调试 支持自定义属性 Power Mode 11 :打字特效(纯属装逼) Nyan Progress Bar :漂亮的进度条(纯属装逼) Other Vo:数据持久化模型 Query:数据查询模型 Dto:数据传输模型 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_40910781/article/details/111416185。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-05-26 23:30:52
268
转载
转载文章
...ava 中的还是略有区别,因为类名和方法名/属性setter,getter方法名之间多了个伴生对象的名称或者 Companion 关键字。如何使其在调用的时候与 Java 中的调用看上去一样呢? Kotlin 为我们提供了 @JvmField 和 @JvmStatic 两个注解。@JvmField 使用在属性上,@JvmStatic 使用在方法上。如: class Test { 这样我们在 Java 代码中调用的时候就和 Java 类调用静态成员的形式一致了,Kotlin 代码调用方式不变: System.out.println(Test.flag); System.out.println(Test.add(1, 2)); const 关键字 在伴生对象中,我们可能需要声明一个常量,目的是等同于 Java 中的静态常量。有两种方式,一种是上面所提到的使用 @JvmField 注解,另一种则是使用 const 关键字修饰。这两种声明方式都等同于 Java 中 static final 所修饰的变量。如下代码: companion 扩展属性和扩展方法 扩展函数 Kotlin的扩展函数可以让你作为一个类成员进行调用的函数,但是是定义在这个类的外部。这样可以很方便的扩展一个已经存在的类,为它添加额外的方法 下面我们为String添加一个toInt的方法 package com.binzi.kotlin 在这个扩展函数中,你可以直接访问你扩展的类的函数和属性,就像定义在这个类中的方法一样,但是扩展函数并不允许你打破封装。跟定义在类中方法不同,它不能访问那些私有的、受保护的方法和属性。 扩展函数的导入 我们直接在包里定义扩展函数。这样我们就可以在整个包里面使用这些扩展,如果我们要使用其他包的扩展,我们就需要导入它。导入扩展函数跟导入类是一样的方式。 import 有时候,可能你引入的第三方包都对同一个类型进行了相同函数名扩展,为了解决冲突问题,你可以使用下面的方式对扩展函数进行改名 import com.binzi.kotlin.toInt as toInteger 扩展函数不可覆盖 扩展方法的原理 Kotlin 中类的扩展方法并不是在原类的内部进行拓展,通过反编译为Java代码,可以发现,其原理是使用装饰模式,对源类实例的操作和包装,其实际相当于我们在 Java中定义的工具类方法,并且该工具类方法是使用调用者为第一个参数的,然后在工具方法中操作该调用者 如: fun String?.toInt(): 反编译为对应的Java代码: public 扩展属性 类的扩展属性原理其实与扩展方法是一样的,只是定义的形式不同,扩展属性必须定义get和set方法 为MutableList扩展一个firstElement属性: var 反编译后的java代码如下: public static final Object getFirstElement(@NotNull List $this$firstElement) { 内部类 kotlin的内部类与java的内部类有点不同java的内部类可以直接访问外部类的成员,kotlin的内部类不能直接访问外部类的成员,必须用inner标记之后才能访问外部类的成员 没有使用inner标记的内部类 class A{ 反编译后的java代码 public 用inner标记的内部类 class A{ 反编译后的java代码 public 从上面可以看出,没有使用inner标记的内部类最后生成的是静态内部类,而使用inner标记的生成的是非静态内部类 匿名内部类 匿名内部类主要是针对那些获取抽象类或者接口对象而来的。最常见的匿名内部类View点击事件: //java,匿名内部类的写法 上面这个是java匿名内部类的写法,kotlin没有new关键字,那么kotlin的匿名内部类该怎么写呢? object : View.OnClickListener{ 方法的参数是一个匿名内部类,先写object:,然后写你的参数类型View.OnClickListener{} kotlin还有一个写法lambda 表达式,非常之方便: print( 数据类 在Java中没有专门的数据类,常常是通过JavaBean来作为数据类,但在Kotlin中提供了专门的数据类。 Java public 从上面的例子中可以看到,如果要使用数据类,需要手动写相应的setter/getter方法(尽管IDE也可以批量生成),但是从代码阅读的角度来说,在属性较多的情况下,诸多的seeter/getter方法还是不利于代码的阅读和维护。 Kotlin 在Kotlin中,可以通过关键字data来生成数据类: data 即在class关键字之前添加data关键字即可。编译器会根据主构造函数中的参数生成相应的数据类。自动生成setter/getter、toString、hashCode等方法 要声明一个数据类,需要满足: 主构造函数中至少有一个参数 主构造函数中所有参数需要标记为val或var 数据类不能是抽象、开发、密封和内部的 枚举类 枚举类是一种特殊的类,kotlin可以通过enum class关键字定义枚举类。 枚举类可以实现0~N个接口; 枚举类默认继承于kotlin.Enum类(其他类最终父类都是Any),因此kotlin枚举类不能继承类; 非抽象枚举类不能用open修饰符修饰,因此非抽象枚举类不能派生子类; 抽象枚举类不能使用abstract关键字修饰enum class,抽象方法和抽象属性需要使用; 枚举类构造器只能使用private修饰符修饰,若不指定,则默认为private; 枚举类所有实例在第一行显式列出,每个实例之间用逗号隔开,整个声明以分号结尾; 枚举类是特殊的类,也可以定义属性、方法、构造器; 枚举类应该设置成不可变类,即属性值不允许改变,这样更安全; 枚举属性设置成只读属性后,最好在构造器中为枚举类指定初始值,如果在声明时为枚举指定初始值,会导致所有枚举值(或者说枚举对象)的该属性都一样。 定义枚举类 / 定义一个枚举类 / 枚举类实现接口 枚举值分别实现接口的抽象成员 enum 枚举类统一实现接口的抽象成员 enum 分别实现抽象枚举类抽象成员 enum 委托 委托模式 是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承。 Java中委托: interface Printer { Kotlin: interface Printer { by表示 p 将会在 PrintImpl 中内部存储, 并且编译器将自动生成转发给 p 的所有 Printer 的方法。 委托属性 有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括: 延迟属性(lazy properties): 其值只在首次访问时计算; 可观察属性(observable properties): 监听器会收到有关此属性变更的通知; 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。 为了涵盖这些(以及其他)情况,Kotlin 支持 委托属性 。 委托属性的语法是: var : 在 by 后面的表达式是该 委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。 标准委托: Kotlin 标准库为几种有用的委托提供了工厂方法。 延迟属性 Lazy lazy() 接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托:第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。例如: val lazyValue: String 可观察属性 Observable Delegates.observable() 接受两个参数:初始值和修改时处理程序(handler)。每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值和新值: class User { 如果想拦截赋的新值,并根据你是不是想要这个值来决定是否给属性赋新值,可以使用 vetoable() 取代 observable(),接收的参数和 observable 一样,不过处理程序 返回值是 Boolean 来决定是否采用新值,即在属性被赋新值生效之前 会调用传递给 vetoable 的处理程序。例如: class User { 把属性存在map 中 一个常见的用例是在一个映射(map)里存储属性的值。这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。 例如: class User(map: Map 在上例中,委托属性会从构造函数传入的map中取值(通过字符串键——属性的名称),如果遇到声明的属性名在map 中找不到对应的key 名,或者key 对应的value 值的类型与声明的属性的类型不一致,会抛出异常。 内联函数 当一个函数被声明为inline时,它的函数体是内联的,也就是说,函数体会被直接替换到函数被调用地方 inline函数(内联函数)从概念上讲是编译器使用函数实现的真实代码来替换每一次的函数调用,带来的最直接的好处就是节省了函数调用的开销,而缺点就是增加了所生成字节码的尺寸。基于此,在代码量不是很大的情况下,我们是否有必要将所有的函数定义为内联?让我们分两种情况进行说明: 将普通函数定义为内联:众所周知,JVM内部已经实现了内联优化,它会在任何可以通过内联来提升性能的地方将函数调用内联化,并且相对于手动将普通函数定义为内联,通过JVM内联优化所生成的字节码,每个函数的实现只会出现一次,这样在保证减少运行时开销的同时,也没有增加字节码的尺寸;所以我们可以得出结论,对于普通函数,我们没有必要将其声明为内联函数,而是交给JVM自行优化。 将带有lambda参数的函数定义为内联:是的,这种情况下确实可以提高性能;但在使用的过程中,我们会发现它是有诸多限制的,让我们从下面的例子开始展开说明: inline 假如我们这样调用doSomething: fun main(args: Array<String>) { 上面的调用会被编译成: fun main(args: Array<String>) { 从上面编译的结果可以看出,无论doSomething函数还是action参数都被内联了,很棒,那让我们换一种调用方式: fun main(args: Array<String>) { 上面的调用会被编译成: fun main(args: Array<String>) { doSomething函数被内联,而action参数没有被内联,这是因为以函数型变量的形式传递给doSomething的lambda在函数的调用点是不可用的,只有等到doSomething被内联后,该lambda才可以正常使用。 通过上面的例子,我们对lambda表达式何时被内联做一下简单的总结: 当lambda表达式以参数的形式直接传递给内联函数,那么lambda表达式的代码会被直接替换到最终生成的代码中。 当lambda表达式在某个地方被保存起来,然后以变量形式传递给内联函数,那么此时的lambda表达式的代码将不会被内联。 上面对lambda的内联时机进行了讨论,消化片刻后让我们再看最后一个例子: inline 上面的例子是否有问题?是的,编译器会抛出“Illegal usage of inline-parameter”的错误,这是因为Kotlin规定内联函数中的lambda参数只能被直接调用或者传递给另外一个内联函数,除此之外不能作为他用;那我们如果确实想要将某一个lambda传递给一个非内联函数怎么办?我们只需将上述代码这样改造即可: inline 很简单,在不需要内联的lambda参数前加上noinline修饰符就可以了。 以上便是我对内联函数的全部理解,通过掌握该特性的运行机制,相信大家可以做到在正确的时机使用该特性,而非滥用或因恐惧弃而不用。 Kotlin下单例模式 饿汉式实现 //Java实现 懒汉式 //Java实现 上述代码中,我们可以发现在Kotlin实现中,我们让其主构造函数私有化并自定义了其属性访问器,其余内容大同小异。 如果有小伙伴不清楚Kotlin构造函数的使用方式。请点击 - - - 构造函数 不清楚Kotlin的属性与访问器,请点击 - - -属性和字段 线程安全的懒汉式 //Java实现 大家都知道在使用懒汉式会出现线程安全的问题,需要使用使用同步锁,在Kotlin中,如果你需要将方法声明为同步,需要添加@Synchronized注解。 双重校验锁式 //Java实现 哇!小伙伴们惊喜不,感不感动啊。我们居然几行代码就实现了多行的Java代码。其中我们运用到了Kotlin的延迟属性 Lazy。 Lazy内部实现 public 观察上述代码,因为我们传入的mode = LazyThreadSafetyMode.SYNCHRONIZED, 那么会直接走 SynchronizedLazyImpl,我们继续观察SynchronizedLazyImpl。 Lazy接口 SynchronizedLazyImpl实现了Lazy接口,Lazy具体接口如下: public 继续查看SynchronizedLazyImpl,具体实现如下: SynchronizedLazyImpl内部实现 private 通过上述代码,我们发现 SynchronizedLazyImpl 覆盖了Lazy接口的value属性,并且重新了其属性访问器。其具体逻辑与Java的双重检验是类似的。 到里这里其实大家还是肯定有疑问,我这里只是实例化了SynchronizedLazyImpl对象,并没有进行值的获取,它是怎么拿到高阶函数的返回值呢?。这里又涉及到了委托属性。 委托属性语法是:val/var : by 。在 by 后面的表达式是该 委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(和 setValue()——对于 var 属性)。 而Lazy.kt文件中,声明了Lazy接口的getValue扩展函数。故在最终赋值的时候会调用该方法。 internal.InlineOnly 静态内部类式 //Java实现 静态内部类的实现方式,也没有什么好说的。Kotlin与Java实现基本雷同。 补充 在该篇文章结束后,有很多小伙伴咨询,如何在Kotlin版的Double Check,给单例添加一个属性,这里我给大家提供了一个实现的方式。(不好意思,最近才抽出时间来解决这个问题) class SingletonDemo private constructor( 其中关于?:操作符,如果 ?: 左侧表达式非空,就返回其左侧表达式,否则返回右侧表达式。请注意,当且仅当左侧为空时,才会对右侧表达式求值。 Kotlin 智能类型转换 对于子父类之间的类型转换 先看这样一段 Java 代码 public 尽管在 main 函数中,对 person 这个对象进行了类型判断,但是在使用的时候还是需要强制转换成 Student 类型,这样是不是很不智能? 同样的情况在 Kotlin 中就变得简单多了 fun main(args: Array<String>) { 在 Kotlin 中,只要对类型进行了判断,就可以直接通过父类的对象去调用子类的函数了 安全的类型转换 还是上面的那个例子,如果我们没有进行类型判断,并且直接进行强转,会怎么样呢? public static void main(String[] args) { 结果就只能是 Exception in thread "main" java.lang.ClassCastException 那么在 Kotlin 中是不是会有更好的解决方法呢? val person: Person = Person() 在转换操作符后面添加一个 ?,就不会把程序 crash 掉了,当转化失败的时候,就会返回一个 null 在空类型中的智能转换 需要提前了解 Kotlin 类型安全的相关知识(Kotlin 中的类型安全(对空指针的优化处理)) String? = aString 在定义的时候定义成了有可能为 null,按照之前的写法,我们需要这样写 String? = 但是已经进行了是否为 String 类型的判断,所以就一定 不是 空类型了,也就可以直接输出它的长度了 T.()->Unit 、 ()->Unit 在做kotlin开发中,经常看到一些系统函数里,用函数作为参数 public .()-Unit与()->Unit的区别是我们调用时,在代码块里面写this,的时候,两个this代表的含义不一样,T.()->Unit里的this代表的是自身实例,而()->Unit里,this代表的是外部类的实例。 推荐阅读 对 Kotlin 与 Java 编程语言的思考 使用 Kotlin 做开发一个月后的感想 扫一扫 关注我的公众号如果你想要跟大家分享你的文章,欢迎投稿~ 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_39611037/article/details/109984124。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-06-23 23:56:14
470
转载
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
groups user
- 显示指定用户的所属组。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"