前端技术
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
[通过键名字符串访问嵌套在JSON对象中的...]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
JSON
JSON , JavaScript Object Notation,一种轻量级的数据交换格式,基于文本,易于人阅读和编写,同时也易于机器解析和生成。在Web应用开发中广泛应用,用于在网络间传输数据,其语法简洁、清晰,可表示数组、对象、字符串、数字、布尔值以及null等多种数据类型,并且支持日期和时间的ISO-8601格式表示。 UTC时间(Coordinated Universal Time) , 协调世界时,是一种国际标准的时间尺度,基于原子钟精确测量得到,不随地球自转变化而调整。在JSON中通常以ISO-8601格式(如1995-01-02T15:04:05.999Z)表示时间戳,其中“Z”代表零时区,即UTC时间。 ISO-8601 , 是国际标准化组织制定的日期和时间表示法标准,它定义了一套统一的日期和时间表示格式。在JSON中,时间戳通常按照ISO-8601格式进行序列化,例如\ 1995-01-02T15:04:05.999Z\ ,这种格式包括年月日小时分钟秒以及可选的小数秒部分,并可以通过加减时区偏移量来转换为不同地区的本地时间。 JavaScript Date对象 , 在JavaScript编程语言中,Date对象用于处理日期和时间,提供了获取当前日期和时间、设置日期和时间、计算日期与时间之间的时间差等功能。在处理JSON中的时间信息时,可以将符合ISO-8601格式的字符串转化为Date对象,进而进行各种时间相关的计算,包括时区转换等操作。 时区偏移量 , 是指特定地点或地区相对于UTC时间的标准时间差,通常用小时和分钟表示,例如+08:00表示东八区(北京时间)。在处理多时区的JSON数据时,需要计算并应用这个偏移量来实现时间的正确转换和显示。
2023-08-18 10:38:11
520
算法侠
JSON
JSON(JavaScript Object Notation) , 一种轻量级的数据交换格式,采用完全独立于语言的文本格式来存储和传输数据。在本文中,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
电脑达人
JSON
一、引言 JSON,全称JavaScript Object Notation,是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。在许多Web应用程序中,JSON被广泛用于数据交换。这篇文章将深入浅出地探讨如何查找JSON数组中的元素。 二、JSON数组的基本概念 首先,我们需要了解JSON数组的基本概念。JSON数组呀,你可别小瞧它,它其实就是一个有规矩的队列。在这个队列里,成员们可是五花八门,什么样的类型都有可能冒出来。比如常见的字符串、数字啦,还有那个爱走极端的布尔值(true/false),连“无中生有”的null也在其中凑热闹。更有意思的是,这个列表里的元素还可以嵌套其他的JSON数组或者JSON对象,是不是很神奇呢?下面是一个简单的JSON数组的例子: css var arr = [1, "hello", true, null]; 在这个例子中,arr是一个包含四个元素的JSON数组,分别是一个数字、一个字符串、一个布尔值和一个null值。 三、JSON数组的查找方法 有了基本的概念之后,我们就可以开始讨论如何查找JSON数组中的元素了。下面介绍几种常见的查找方法: 1. 使用for循环遍历数组 这是一种最基本的查找方法,通过for循环遍历数组,逐个比较元素,直到找到目标元素为止。 javascript function findElement(arr, target) { for (var i = 0; i < arr.length; i++) { if (arr[i] === target) { return i; } } return -1; } console.log(findElement([1, "hello", true, null], "hello")); // 输出:1 在这个例子中,findElement函数接受一个JSON数组和一个目标元素作为参数,返回目标元素在数组中的索引。如果找不到目标元素,则返回-1。 2. 使用Array.prototype.find()方法 ES6引入了一个新的全局方法——Array.prototype.find(),它可以用来查找满足指定条件的数组元素,并返回第一个匹配的元素。 javascript var arr = [1, "hello", true, null]; console.log(arr.find(function(item) { return item === "hello"; })); // 输出:"hello" 在这个例子中,arr.find()方法接受一个回调函数作为参数,该函数会被应用到数组的每个元素上,如果某个元素使回调函数返回true,则该元素会被返回。 3. 使用Array.prototype.includes()方法 ES6还引入了一个全局方法——Array.prototype.includes(),它可以用来判断数组是否包含指定的元素。 javascript var arr = [1, "hello", true, null]; console.log(arr.includes("hello")); // 输出:true 在这个例子中,arr.includes()方法接受一个参数作为参数,如果数组包含该参数,则返回true,否则返回false。 四、总结 JSON数组的查找方法有很多,具体使用哪种方法取决于实际情况。一般来说,如果只需要查找数组中的一个元素,那么使用for循环或者Array.prototype.find()方法都是不错的选择。如果需要判断数组是否包含某个元素,那么可以使用Array.prototype.includes()方法。希望这篇文章能对你有所帮助!
2024-01-31 11:10:52
558
梦幻星空-t
JSON
JSON 是一种小型的数据交换格式,在前端和后端编程中都被广泛应用。在编程过程中,我们经常需求根据某个前提检索 JSON 中的数据。然而,对于大型 JSON 数据,检索效能可能会成为一个重要的难题。下面我们来对比一下不同的检索方式在效能上的差别。 首先,我们以一个基础的 JSON 数据为例: { "users": [ { "id": 1, "name": "Alice", "age": 30 }, { "id": 2, "name": "Bob", "age": 25 }, { "id": 3, "name": "Charlie", "age": 35 } ] } 接下来,我们将应用三种不同的方式来检索这个 JSON 数据中年龄大于等于 30 的用户: (1)应用 for 循环循环 JSON 数据,检索符合前提的数据: const users = data.users; const result = []; for (let i = 0; i< users.length; i++) { if (users[i].age >= 30) { result.push(users[i]); } } (2)应用数组的 filter() 方式来筛选符合前提的数据: const users = data.users; const result = users.filter(user =>user.age >= 30); (3)应用 jsonpath 来检索符合前提的数据: const jsonpath = require('jsonpath'); const result = jsonpath.query(data, '$..[?(@.age >= 30)]'); 通过在相同的硬件前提下试验,我们得到了以下结论: (1)for 循环循环在加工 100000 条数据时需求 5.84 秒。 (2)数组的 filter() 方式在加工同样数目的数据时需求 1.55 秒。 (3)jsonpath 在同样的数据量下仅需 0.46 秒。 通过以上试验结论可以看出,应用 JSONPath 需求的耗时最少,其次是 filter() 方式,最慢的是 for 循环循环。当需求加工海量 JSON 数据时,在效能方面应用 JSONPath 会是最佳的选择。
2023-09-15 23:03:34
484
键盘勇士
转载文章
JSON数据 , JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它基于JavaScript的一个子集,采用完全独立于语言的文本格式来存储和表示数据。在本文语境中,JSON数据是网页源码中以特定结构嵌入的一段字符串,包含了歌曲的各种信息如歌手头像、分享内容、封面图片、歌手昵称以及MP3下载地址等关键元数据。通过解析这段JSON数据,可以方便地获取并展示这些信息。 cURL , cURL是一个强大的命令行工具和库,用于获取或发送数据,支持包括HTTP、HTTPS、FTP等众多协议。在PHP编程中,cURL扩展常被用来发起HTTP请求,获取远程服务器上的资源内容。本文中,curlGet函数就是利用PHP的cURL功能来获取指定URL页面的源代码,进而从中提取所需的JSON数据。 JSON解码 , JSON解码是指将JSON格式的字符串转换成PHP中的关联数组或对象的过程,以便程序能够处理和操作这些数据。在文章提供的PHP代码片段中,json_decode()函数被用来对从网页源码中提取到的JSON数据进行解码,将其转化为PHP数组结构,这样就可以直接通过数组索引或者属性名访问其中的各项信息了。例如,通过$jsonArr detail playurl 即可获取到mp3的下载地址。
2023-03-14 14:04:46
227
转载
转载文章
原生类型数组 , 在Kotlin中,原生类型数组是专门为特定基本数据类型设计的数组,如IntArray、BooleanArray和CharArray等。它们与通用Array类不同,原生类型数组在内存中存储的是对应类型的原始值,而非对象引用,因此避免了装箱和拆箱操作带来的性能开销,尤其适合处理大量数据或需要高效内存访问的场景。 拆装箱(Boxing and Unboxing) , 在编程语言中,拆装箱是指将基本数据类型(如整型、字符型)与对应的包装器对象(如Integer、Character)之间进行转换的过程。在Kotlin中,使用原生类型数组可以有效避免对基本数据类型进行不必要的拆箱和装箱操作,提高程序运行效率。 遍历(Traversal) , 遍历是一种常见的编程操作,指的是按照某种顺序访问集合(如数组、列表、映射等)中的每个元素,并执行相应的操作。在本文中提到的Kotlin数组遍历方式包括使用for循环结合indices属性、通过iterator迭代器以及使用forEach高阶函数等方式。 自然排序(Natural Sorting) , 自然排序通常是指根据数据本身的特性(例如数字大小、字符串字典序等)进行升序或降序排列的一种排序方法。在Kotlin中,数组可以通过sort()、sortedArray()和sorted()方法实现自然排序,这些方法会基于元素的Comparable接口实现进行排序,无需程序员显式指定比较规则。 反转(Reversal) , 反转数组操作指的是改变数组元素原有的顺序,即将数组的最后一个元素移动到第一个位置,第一个元素移动到最后一个位置,依次类推,最终得到一个元素顺序颠倒的新数组。在Kotlin中,可以使用reverse()、reversedArray()和reversed()方法来实现数组的反转操作。 排序算法(Sorting Algorithms) , 排序算法是一系列用于将一组数据按照特定顺序排列的方法。在Kotlin中,数组的sort()方法内部实现了一种高效的排序算法,能够自动对数组元素进行排序,而sortedArray()和sorted()方法则返回一个新的已排序数组,不影响原有数组内容。这些排序方法默认采用自然排序,对于自定义排序逻辑,可以通过传递Comparator作为参数实现。
2023-03-31 12:34:25
66
转载
转载文章
...n.cn/ 本文阅读对象:商户系统(在线购物平台、人工收银系统、自动化智能收银系统或其他)集成凉秋易支付涉及的技术架构师,研发工程师,测试工程师,系统运维工程师。 接口申请方式 共有两种接口模式: (一)普通支付商户 可以获得一个支付商户。请进行注册申请,申请之后会将商户ID和商户KEY给你! 协议规则 传输方式:HTTP 数据格式:JSON 签名算法:MD5 字符编码:UTF-8 [API]查询商户信息与结算规则 URL地址:http://pay.lqan.cn/api.php?act=query&pid={商户ID}&sign={签名字符串} 请求参数说明: 字段名变量名必填类型示例值描述 操作类型act是Stringquery此API固定值 商户IDpid是Int1001 签名字符串sign是String67d12af9ddbe38d9c7b0931ad102ca3c签名算法与支付宝签名算法相同 返回结果: 字段名变量名类型示例值描述 返回状态码codeInt11为成功,其它值为失败 商户IDpidInt1001所创建的商户ID 商户密钥keyString(32)89unJUB8HZ54Hj7x4nUj56HN4nUzUJ8i所创建的商户密钥 商户类型typeInt1此值暂无用 商户状态activeInt11为正常,0为封禁 商户余额moneyString0.00商户所拥有的余额 结算账号accountString1070077170@qq.com结算的支付宝账号 结算姓名usernameString张三结算的支付宝姓名 满多少自动结算settle_moneyString30此值为系统预定义 手动结算手续费settle_feeString1此值为系统预定义 每笔订单分成比例money_rateString98此值为系统预定义 [API]查询结算记录 URL地址:http://pay.lqan.cn/api.php?act=settle&pid={商户ID}&sign={签名字符串} 请求参数说明: 字段名变量名必填类型示例值描述 操作类型act是Stringsettle此API固定值 商户IDpid是Int1001 签名字符串sign是String67d12af9ddbe38d9c7b0931ad102ca3c签名算法与支付宝签名算法相同 返回结果: 字段名变量名类型示例值描述 返回状态码codeInt11为成功,其它值为失败 返回信息msgString查询结算记录成功! 结算记录dataArray结算记录列表 [API]查询单个订单 URL地址:http://pay.lqan.cn/api.php?act=order&pid={商户ID}&out_trade_no={商户订单号}&sign={签名字符串} 请求参数说明: 字段名变量名必填类型示例值描述 操作类型act是Stringorder此API固定值 商户IDpid是Int1001 商户订单号out_trade_no是String20160806151343349 签名字符串sign是String67d12af9ddbe38d9c7b0931ad102ca3c签名算法与支付宝签名算法相同 返回结果: 字段名变量名类型示例值描述 返回状态码codeInt11为成功,其它值为失败 返回信息msgString查询订单号成功! 易支付订单号trade_noString2016080622555342651凉秋易支付订单号 商户订单号out_trade_noString20160806151343349商户系统内部的订单号 支付方式typeStringalipayalipay:支付宝,tenpay:财付通, qqpay:QQ钱包,wxpay:微信支付 商户IDpidInt1001发起支付的商户ID 创建订单时间addtimeString2016-08-06 22:55:52 完成交易时间endtimeString2016-08-06 22:55:52 商品名称nameStringVIP会员 商品金额moneyString1.00 支付状态statusInt01为支付成功,0为未支付 [API]批量查询订单 URL地址:http://pay.lqan.cn/api.php?act=orders&pid={商户ID}&sign={签名字符串} 请求参数说明: 字段名变量名必填类型示例值描述 操作类型act是Stringorders此API固定值 商户IDpid是Int1001 查询订单数量limit否Int20返回的订单数量,最大50 签名字符串sign是String67d12af9ddbe38d9c7b0931ad102ca3c签名算法与支付宝签名算法相同 返回结果: 字段名变量名类型示例值描述 返回状态码codeInt11为成功,其它值为失败 返回信息msgString查询结算记录成功! 订单列表dataArray订单列表 [API]支付订单退款 URL地址:http://pay.lqan.cn/api.php?act=refund&pid={商户ID}&out_trade_no={商户订单号}&sign={签名字符串} 只支持微信官方、QQ钱包官方、当面付退款 请求参数说明: 字段名变量名必填类型示例值描述 操作类型act是Stringrefund此API固定值 商户IDpid是Int1001 商户订单号out_trade_no是Int1000 退款原因desc否String 退款金额money否Double20.00不填默认退全款 签名字符串sign是String67d12af9ddbe38d9c7b0931ad102ca3c签名算法与支付宝签名算法相同 返回结果: 字段名变量名类型示例值描述 返回状态码codeInt11为成功,其它值为失败 返回信息msgString退款成功! 发起支付请求 URL地址:http://pay.lqan.cn/submit.php?pid={商户ID}&type={支付方式}&out_trade_no={商户订单号}¬ify_url={服务器异步通知地址}&return_url={页面跳转通知地址}&name={商品名称}&money={金额}&sitename={网站名称}&sign={签名字符串}&sign_type=MD5 请求参数说明: 字段名变量名必填类型示例值描述 商户IDpid是Int1001 支付方式type是Stringalipayalipay:支付宝,tenpay:财付通, qqpay:QQ钱包,wxpay:微信支付 商户订单号out_trade_no是String20160806151343349 异步通知地址notify_url是Stringhttp://域名/notify_url.php服务器异步通知地址 跳转通知地址return_url是Stringhttp://域名/return_url.php页面跳转通知地址 商品名称name是StringVIP会员 商品金额money是String1.00 网站名称sitename否String某某某平台 签名字符串sign是String202cb962ac59075b964b07152d234b70签名算法与支付宝签名算法相同 签名类型sign_type是StringMD5默认为MD5 支付结果通知 通知类型:服务器异步通知(notify_url)、页面跳转通知(return_url) 请求方式:GET 特别说明:回调成功之后请输出 SUCCESS字符串,如果没有收到商户响应的SUCCESS字符串,系统将通过策略重新通知5次,通知频率为15s/60s/3m/30m/1h 请求参数说明: 字段名变量名必填类型示例值描述 商户IDpid是Int1001 易支付订单号trade_no是String20160806151343349021凉秋易支付订单号 商户订单号out_trade_no是String20160806151343349商户系统内部的订单号 支付方式type是Stringalipayalipay:支付宝,tenpay:财付通, qqpay:QQ钱包,wxpay:微信支付 商品名称name是StringVIP会员 商品金额money是String1.00 支付状态trade_status是StringTRADE_SUCCESS 签名字符串sign是String202cb962ac59075b964b07152d234b70签名算法与支付宝签名算法相同 签名类型sign_type是StringMD5默认为MD5 签名算法 请对参数按照键名进行降序排序(a-z)sign sign_type 和空值不进行签名!。 排序后请操作参数生成或拼接一个url请求字符串 例如 a=b&c=d&e=f (Url值不能携带参数!不要进行urlencode) 再将拼接好的请求字符串与平台生成的Key进行MD5加密得出sign签名参数 MD5 ( a=b&c=d&e=f + KEY ) (注意:+ 为各语言的拼接符!不是字符!) 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_39620334/article/details/115933932。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-12-18 16:55:58
91
转载
Sqoop
...哈哈,Sqoop这个名字一听就觉得挺酷的,对不?它就像个超级厉害的“中间人”,一边连着Hadoop那个大数据的世界,另一边又搭在传统的数据库上,两边都能玩得转! 说到Sqoop,它的主要功能就是从关系型数据库中抽取数据并导入到Hadoop生态系统中,或者反过来把Hadoop中的数据导出到关系型数据库里。对我来说,这简直就是个救星啊!毕竟我天天都要跟一堆 structured data(结构化数据)打交道,没有它,我的日子能过得下去才怪呢! 不过呢,事情并没有想象中那么顺利。话说有一次我用 Sqoop 做数据迁移的时候,发现了个让人挠头的问题——只要碰到某些特别的数据处理任务,作业就突然“罢工”了,也不知道是啥原因。这事儿可把我给整郁闷了,我都觉得自己的水平挺过关的了,没想到被一个看起来超简单的题目给绊住了,真是有点糗啊! 示例代码: bash sqoop import \ --connect jdbc:mysql://localhost:3306/mydatabase \ --username root \ --password mypassword \ --table employees \ --target-dir /user/hadoop/employees 这段代码看起来挺正常的,但我后来发现,当表中的数据量过大或者存在一些复杂的约束条件时,Sqoop就表现得不太友好。 --- 二、Sqoop作业失败的背后 接下来,让我们一起深入探讨一下这个问题。说实话,刚开始接触Sqoop那会儿,我对它是怎么工作的压根儿没弄明白,稀里糊涂的。我以为只要配置好连接信息,然后指定源表和目标路径就行了。但实际上,Sqoop并不是这么简单的工具。 当我第一次遇到作业失败的情况时,内心是崩溃的。屏幕上显示的错误信息密密麻麻,但仔细一看,其实都是些常见的问题。打个比方啊,Sqoop这家伙一碰到一些特别的符号,比如空格或者换行符,就容易“翻车”,直接给你整出点问题来。还有呢,有时候因为网络卡了一下,延迟太高,Sqoop就跟服务器说拜拜了,连接就这么断了,挺烦人的。 有一次,我在尝试将一张包含大量JSON字段的表导出到HDFS时,Sqoop直接报错了。我当时就在心里嘀咕:“为啥别的工具处理起来轻轻松松的事儿,到Sqoop这儿就变得这么棘手呢?”后来,我一咬牙,开始翻遍各种资料,想着一定要找出个解决办法来。 思考与尝试: 经过一番研究,我发现Sqoop默认情况下并不会对数据进行深度解析,这意味着如果数据本身存在问题,Sqoop可能无法正确处理。所以,为了验证这个假设,我又做了一次测试。 bash sqoop import \ --connect jdbc:mysql://localhost:3306/mydatabase \ --username root \ --password mypassword \ --table problematic_table \ --fields-terminated-by '\t' \ --lines-terminated-by '\n' 这次我特意指定了分隔符和换行符,希望能避免之前遇到的那些麻烦。嘿,没想到这次作业居然被我搞定了!中间经历了不少波折,不过好在最后算是弄懂了个中奥秘,也算没白费功夫。 --- 三、透明性的重要性 Sqoop到底懂不懂我的需求? 说到Sqoop的透明性,我觉得这是一个非常重要的概念。所谓的透明性嘛,简单来说,就是Sqoop能不能明白咱们的心思,然后老老实实地按咱们想的去干活儿,不添乱、不出错!显然,在我遇到的这些问题中,Sqoop的表现并不能让人满意。 举个例子来说,假设你有一个包含多列的大表,其中某些列的数据类型比较复杂(例如数组、嵌套对象等)。在这种情况下,Sqoop可能会因为无法正确识别这些数据类型而失败。更糟糕的是,它并不会给出明确的提示,而是默默地报错,让你一头雾水。 为了更好地应对这种情况,我在后续的工作中加入了更多的调试步骤。比如说啊,你可以先用describe这个命令去看看表的结构,确保所有的字段都乖乖地被正确识别了;接着呢,再用--check-column这个选项去瞅一眼,看看有没有重复的记录藏在里面。这样一来,虽然增加了工作量,但至少能减少不必要的麻烦。 示例代码: bash sqoop job --create my_job \ -- import \ --connect jdbc:mysql://localhost:3306/mydatabase \ --username root \ --password mypassword \ --table employees \ --check-column id \ --incremental append \ --last-value 0 这段代码展示了如何创建一个增量作业,用于定期更新目标目录中的数据。通过这种方式,可以有效避免一次性加载过多数据带来的性能瓶颈。 --- 四、总结与展望 与Sqoop共舞 总的来说,尽管Sqoop在某些场景下表现得不尽人意,但它依然是一个强大的工具。通过不断学习和实践,我相信自己能够更加熟练地驾驭它。未来的计划里,我特别想试试一些更酷的功能,比如说用Sqoop直接搞出Avro文件,或者把Spark整进来做分布式计算,感觉会超级带劲! 最后,我想说的是,技术这条路从来都不是一帆风顺的。遇到困难并不可怕,可怕的是我们因此放弃努力。正如那句话所说:“失败乃成功之母。”只要保持好奇心和求知欲,总有一天我们会找到属于自己的答案。 如果你也有类似的经历,欢迎随时交流!我们一起进步,一起成长! --- 希望这篇文章对你有所帮助,如果有任何疑问或者想要了解更多细节,请随时告诉我哦!
2025-03-22 15:39:31
93
风中飘零
转载文章
...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
转载
转载文章
...1.1 列表的局限 通过队列的 rpush 和 lpop 可以实现消息队列(队尾进队头出),但是消费者需要不停地调用 lpop 查看 List 中是否有等待处理的消息(比如写一个 while 循环)。 为了减少通信的消耗,可以 sleep()一段时间再消费,但是会有两个问题: 1、如果生产者生产消息的速度远大于消费者消费消息的速度,List 会占用大量的内存。 2、消息的实时性降低。 list 还提供了一个阻塞的命令:blpop,没有任何元素可以弹出的时候,连接会被阻塞。 基于 list 实现的消息队列,不支持一对多的消息分发。 1.2 发布订阅模式 除了通过 list 实现消息队列之外,Redis 还提供了一组命令实现发布/订阅模式。 这种方式,发送者和接收者没有直接关联(实现了解耦),接收者也不需要持续尝试获取消息。 1.2.1 订阅频道 首先,我们有很多的频道(channel),我们也可以把这个频道理解成 queue。订阅者可以订阅一个或者多个频道。消息的发布者(生产者)可以给指定的频道发布消息。只要有消息到达了频道,所有订阅了这个频道的订阅者都会收到这条消息。 需要注意的注意是,发出去的消息不会被持久化,因为它已经从队列里面移除了,所以消费者只能收到它开始订阅这个频道之后发布的消息。 下面我们来看一下发布订阅命令的使用方法。 订阅者订阅频道:可以一次订阅多个,比如这个客户端订阅了 3 个频道。 subscribe channel-1 channel-2 channel-3 发布者可以向指定频道发布消息(并不支持一次向多个频道发送消息): publish channel-1 2673 取消订阅(不能在订阅状态下使用): unsubscribe channel-1 1.2.2 按规则(Pattern)订阅频道 支持 ?和 占位符。? 代表一个字符, 代表 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
转载
JSON
JSON是一种小巧的资料交换格式,其扩展名为.json。 在开发过程中,我们经常将资料保存到JSON格式的文档内,并利用程序加载这些资料。 // 加载JSON文件例子 var fs = require('fs'); var data = fs.readFileSync('data.json'); var jsonData = JSON.parse(data); console.log(jsonData); 加载JSON文件时,必须利用fs(FS模块)模块的readFileSync方法,将文件路径传递作为参数,加载文件内容。接着我们利用JSON.parse方法,将文件内容转换为JavaScript对象。 在开发中,JSON文件是一种非常常用的资料保存方式。无论是保存一些配置信息,还是保存用户资料,JSON文件都可以胜任。由于JSON格式的资料比较简洁,网络传输成本较低,因此在前端开发中也大量利用JSON格式的资料进行接口交互。 总之,JSON扩展名为.json,是一种非常常用的资料保存方式,在前端开发中扮演着重要的角色。
2023-05-29 11:53:15
526
程序媛
JSON
JSON , JavaScript Object Notation,一种轻量级的数据交换格式,采用完全独立于语言的文本格式来存储和表示数据。JSON通过简洁、人性化的语法(类似于JavaScript对象字面量)来描述键值对集合,便于人阅读和编写,同时也易于机器解析和生成。在Web开发领域中,JSON广泛应用于前后端数据交互,使得不同平台之间能方便地传递结构化信息。 JavaScript 对象 , 在JavaScript编程语言中,对象是一种复杂数据类型,用于将相关数据和功能组织在一起。它是一个无序的属性集合,每个属性都有唯一的字符串键名和对应的值(可以是任何数据类型)。例如,在文章中提到的JavaScript对象 name: \ Tom\ , age: 18 ,其中\ name\ 和\ age\ 是属性名,\ Tom\ 和18是对应的属性值。 fetch API , Fetch API是现代浏览器提供的一种基于Promise的网络请求接口,用于从服务器获取资源。相较于传统的XMLHttpRequest对象,fetch提供了更简洁易用且符合现代异步编程模式的API。在文章中的示例代码中,fetch方法被用来从后端服务器获取JSON数据,然后通过.then(response => response.json())将响应体转换为JSON格式的数据,以便进一步处理和渲染到网页上。
2023-05-11 17:44:41
267
代码侠
转载文章
...篇文章相信你已经了解JSON的好处了,那么废话不多说直接进入主题。 Jackson是java中众多json处理工具的一个,比起常见的Json-lib,Gson要快一些。 Jackson的官网:http://jackson.codehaus.org/ 里面可以下载Jackson的Jar包 注意jackson依赖:Apache的commons-loggin。 下面聊一下Jackson的常见用法================================== Bean----->JSON public static String beanToJson(Object obj) throws IOException { // 这里异常都未进行处理,而且流的关闭也不规范。开发中请勿这样写,如果发生异常流关闭不了 ObjectMapper mapper = CommonUtil.getMapperInstance(false); StringWriter writer = new StringWriter(); JsonGenerator gen = new JsonFactory().createJsonGenerator(writer); mapper.writeValue(gen, obj); gen.close(); String json = writer.toString(); writer.close(); return json; } JSON------>Bean public static Object jsonToBean(String json, Class<?> cls) throws Exception {ObjectMapper mapper = CommonUtil.getMapperInstance(false); Object vo = mapper.readValue(json, cls); return vo; } 好了方法写完了咱们测试一下吧 看看他是否支持复杂类型的转换 public static void main(String[] args) throws Exception {// 准备数据 List<Person> pers = new ArrayList<Person>(); Person p = new Person("张三", 46); pers.add(p); p = new Person("李四", 19); pers.add(p); p = new Person("王二麻子", 23); pers.add(p); TestVo vo = new TestVo("一个容器而已", pers); // 实体转JSON字符串 String json = CommonUtil.beanToJson(vo); System.out.println("Bean>>>Json----" + json); // 字符串转实体 TestVo vo2 = (TestVo)CommonUtil.jsonToBean(json, TestVo.class); System.out.println("Json>>Bean--与开始的对象是否相等:" + vo2.equals(vo)); } 输出结果 Bean>>>Json----{"voName":"一个容器而已","pers":[{"name":"张三","age":46},{"name":"李四","age":19},{"name":"王二麻子","age":23}]} Json>>Bean--与开始的对象是否相等:true 从结果可以看出从咱们转换的方法是对的,本文只是对Jackson的一个最简单的使用介绍。接下来的几篇文章咱们深入研究一下这玩意到底有多强大! 相关类源代码: Person.java public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {super();this.name = name;this.age = age;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (obj == null) {return false;}if (getClass() != obj.getClass()) {return false;}Person other = (Person) obj;if (age != other.age) {return false;}if (name == null) {if (other.name != null) {return false;} } else if (!name.equals(other.name)) {return false;}return true;} } TestVo.java public class TestVo { private String voName; private List<Person> pers; public TestVo() { } public TestVo(String voName, List<Person> pers) { super(); this.voName = voName; this.pers = pers; } public String getVoName() { return voName; } public void setVoName(String voName) { this.voName = voName; } public List<Person> getPers() { return pers; } public void setPers(List<Person> pers) { this.pers = pers; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } TestVo other = (TestVo) obj; if (pers == null) { if (other.pers != null) { return false; } } else if (pers.size() != other.pers.size()) { return false; } else { for (int i = 0; i < pers.size(); i++) { if (!pers.get(i).equals(other.pers.get(i))) { return false; } } } if (voName == null) { if (other.voName != null) { return false; } } else if (!voName.equals(other.voName)) { return false; } return true; } } CommonUtil.java public class CommonUtil { private static ObjectMapper mapper; / 一个破ObjectMapper而已,你为什么不直接new 还搞的那么复杂。接下来的几篇文章我将和你一起研究这个令人蛋疼的问题 @param createNew 是否创建一个新的Mapper @return / public static synchronized ObjectMapper getMapperInstance(boolean createNew) { if (createNew) { return new ObjectMapper(); } else if (mapper == null) { mapper = new ObjectMapper(); } return mapper; } public static String beanToJson(Object obj) throws IOException { // 这里异常都未进行处理,而且流的关闭也不规范。开发中请勿这样写,如果发生异常流关闭不了 ObjectMapper mapper = CommonUtil.getMapperInstance(false); StringWriter writer = new StringWriter(); JsonGenerator gen = new JsonFactory().createJsonGenerator(writer); mapper.writeValue(gen, obj); gen.close(); String json = writer.toString(); writer.close(); return json; } public static Object jsonToBean(String json, Class<?> cls) throws Exception {ObjectMapper mapper = CommonUtil.getMapperInstance(false); Object vo = mapper.readValue(json, cls); return vo; } } 本篇文章为转载内容。原文链接:https://blog.csdn.net/gqltt/article/details/7387011。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-20 18:27:10
274
转载
JSON
JSON(JavaScript 对象表示法)是一种简洁的数据交换格式。在web服务端和客户端之间传递信息时,用JSON格式来展示数据可以便于数据的解读和获取。JSON数组是JSON对象的一种特殊形式,它由一组项组成,每个项都能够是对象、数组、文本、数值、布尔项或者null。 JSON数组通常由方括号包围,数组中的每个项以逗号隔开,比如: [ "John", "Mary", { "age": 30, "city": "New York" }, [1, 2, 3] ] 上述例子容纳了四个项:两个文本、一个容纳两个属性的对象、以及一个内嵌的数组。JSON数组可以容纳多个不同类型的项,甚至也可以容纳其他的JSON数组。 在JavaScript中,可以采用JSON.parse()函数将JSON文本解读成JavaScript对象或JSON数组。比如: var jsonString = '["John","Mary",{"age":30,"city":"New York"},[1,2,3]]'; var jsonObj = JSON.parse(jsonString); console.log(jsonObj); // ["John","Mary",{ age : 30, city : "New York" },[1,2,3]] 此外,在JavaScript对象中可以采用JSON.stringify()函数将JavaScript对象或JSON数组转化为JSON文本,比如: var obj = { name: "John", age: 30, city: "New York", children: ["Mike", "Lisa"] }; var jsonString = JSON.stringify(obj); console.log(jsonString); // {"name":"John","age":30,"city":"New York","children":["Mike","Lisa"]} 总之,JSON数组是一种便于数据传输和处理的格式,它的应用已经越来越广泛。在JavaScript中,可以便于地将JSON数组转化为JavaScript对象,并对其进行操作。
2023-06-02 13:33:53
291
数据库专家
JSON
...下,我们都希望可以将JSON数据从客户端发送到服务器端,并且从服务器端返回响应的数据。这就是Ashx的一个强大功能。 2. 什么是Ashx Ashx是ASP.NET中的一个组件,它可以用于处理HTTP请求。通过Ashx,我们可以创建自己的HTTP处理程序,实现定制的业务逻辑。 3. JSON是什么? JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它基于JavaScript的一个子集。它易于人阅读和编写,同时也易于机器解析和生成。 4. 如何使用Ashx处理JSON数据? 首先,我们需要定义一个Ashx方法来处理我们的请求。这个方法呐,它得接收一个叫“request”的小家伙作为参数,其实呢,这玩意儿就是一个HttpApplicationRequest对象,里头装着这次请求的所有详细信息,一丁点儿也没落下。 csharp public void HandleHttpRequest(HttpContext context) { // 获取请求的内容 string requestContent = context.Request.InputStream.ReadToEnd(); // 将请求内容转换为JSON对象 dynamic jsonObject = JsonConvert.DeserializeObject(requestContent); // 在这里处理你的JSON数据... // 返回响应 context.Response.Write("处理成功"); } 在这个方法中,我们首先获取了请求的内容,然后使用JsonConvert.DeserializeObject方法将其转换为一个动态类型的JSON对象。这样,我们就可以方便地访问和操作JSON数据了。 5. 总结 Ashx是一个强大的工具,可以帮助我们在ASP.NET中处理各种HTTP请求。尤其是当我们碰上要处理JSON数据这事儿,用Ashx可是能帮咱们省不少力,让事情变得轻松简单多了。当你把请求的内容成功转换成JSON格式后,就等于把它变成一个我们熟悉的.NET对象,这样一来,处理JSON数据就跟玩普通.NET对象一样简单轻松,毫无压力啦! 6. 深入探讨 然而,这只是一个基础的例子。实际上,我们可以使用Ashx做更多的事情。比如说,咱们可以在动手解析JSON数据之前,先给请求做个“体检”确认其靠谱性;又或者,在我们成功搞定数据之后,再添点额外的“小料”,让它更加饱满丰富。 此外,我们也需要注意安全问题。虽然“JsonConvert.DeserializeObject”这个小家伙能够自动挡下不少常见的JSON攻击招式,但我们仍然得瞪大眼睛,确保喂给它的数据确实是货真价实、没毛病的。 总的来说,Ashx是一个非常有用的工具,但我们也需要谨慎使用,以防止可能的安全问题。
2023-06-29 14:38:59
549
灵动之光-t
Golang
...错误处理机制,那就是通过error接口来表示错误。这个接口非常简单,只有一个方法Error(),用于返回一个字符串,这个字符串就是错误信息。 go type error interface { Error() string } 这种设计使得Go语言在处理错误时非常灵活。我们可以自定义任何类型的错误,并通过Error()方法返回具体的错误信息。但是有个重点啊:错误信息得尽量详细清楚,这样我们才能迅速找到问题出在哪。 2.1 错误信息的重要性 错误信息不仅仅是给程序员看的,它还可能被最终用户看到。因此,在编写错误信息时,我们需要考虑两方面: - 面向开发者:确保错误信息足够具体,能够帮助开发者迅速定位问题。 - 面向用户:保持友好性和简洁性,避免暴露过多的技术细节。 举个例子,假设你的应用程序需要从数据库读取数据,但数据库连接失败了。一个好的错误信息可能是:“无法连接到数据库,请检查您的网络连接或联系管理员。这种信息不仅说清楚了问题的来龙去脉(就是数据库连不上),还给咱指了个大概的解决方向呢。 3. 实践中的错误处理 在实际项目中,错误处理是一个贯穿始终的过程。从最简单的错误检查,到复杂的错误链路追踪,每一步都至关重要。让我们来看几个具体的例子,看看如何在Go中实现有效的错误处理。 3.1 基础的错误检查 最基本也是最常见的错误处理方式,就是在函数调用后立即检查返回的错误值。如果错误不为nil,则进一步处理。 go func main() { file, err := os.Open("test.txt") if err != nil { fmt.Println("打开文件失败:", err) return } defer file.Close() // 继续处理文件... } 在这个例子中,我们尝试打开一个名为“test.txt”的文件。如果文件不存在或者权限不足等导致操作失败,os.Open()会返回一个非空的错误对象。通过检查这个错误对象,我们可以及时发现并处理问题。 3.2 使用错误链路 在复杂的应用中,一个操作可能会触发多个后续步骤,每个步骤都可能产生新的错误。在这种情况下,错误链路(即错误传播)变得尤为重要。我们可以利用Go语言的多返回值特性来实现这一点。 go func readConfig(filePath string) (map[string]string, error) { file, err := os.Open(filePath) if err != nil { return nil, fmt.Errorf("打开配置文件失败: %w", err) } defer file.Close() var config map[string]string decoder := json.NewDecoder(file) if err := decoder.Decode(&config); err != nil { return nil, fmt.Errorf("解析配置文件失败: %w", err) } return config, nil } func main() { config, err := readConfig("config.json") if err != nil { log.Fatalf("读取配置文件失败: %v", err) } // 使用配置... } 在这个例子中,readConfig函数尝试打开并解析一个JSON格式的配置文件。如果任何一步失败,我们都会返回一个包含原始错误的错误对象。这样做不仅可以让错误信息更加完整,还便于我们在调用方进行统一处理。 3.3 自定义错误类型 虽然标准库提供的error接口已经足够强大,但在某些场景下,我们可能需要更丰富的错误信息。这时,可以定义自己的错误类型来扩展功能。 go type MyError struct { Message string Code int } func (e MyError) Error() string { return fmt.Sprintf("错误代码%d: %s", e.Code, e.Message) } func doSomething() error { return &MyError{Message: "操作失败", Code: 500} } func main() { err := doSomething() if err != nil { log.Printf("发生错误: %v", err) } } 在这个例子中,我们定义了一个自定义错误类型MyError,它包含了一个消息和一个错误码。这样做的好处是可以根据不同的错误码采取不同的处理策略。 4. 错误信息的最佳实践 最后,我想分享一些我在日常开发中积累的经验,这些经验有助于写出更好的错误信息。 - 明确且具体:错误信息应该直接指出问题所在,避免模糊不清的描述。 - 用户友好的:对于最终用户可见的错误信息,尽量使用通俗易懂的语言。 - 提供解决方案:如果可能的话,给出一些基本的解决建议。 - 避免泄露敏感信息:在生成错误信息时,注意不要暴露敏感数据,如密码或密钥。 结语 错误信息是我们与程序之间的桥梁,它能帮助我们更好地理解问题所在,并找到解决问题的方法。在Go语言里,错误处理不仅仅是个技术活儿,它还代表着一种态度——就是要做出高质量的软件的那种执着精神。希望通过这篇文章,你能在未来的项目中更加重视错误信息的处理,从而写出更加健壮和可靠的代码。 --- 以上内容结合了理论与实践,旨在让你对Go语言中的错误处理有更深的理解。记住,好的错误信息就像是一位优秀的导游,它能带你穿越迷雾,找到正确的方向。
2024-11-09 16:13:46
127
桃李春风一杯酒
JSON
JSON , JavaScript Object Notation,是一种轻量级的数据交换格式。它基于纯文本,采用完全独立于语言的、清晰简洁的语法来表示键值对集合、数组和其他复杂数据结构,易于阅读和编写,并且能够被机器(特别是JavaScript引擎)直接解析和生成,广泛应用于Web服务接口、配置文件、数据存储等领域中,实现不同系统间的数据交换。 Python字典 , 在Python编程语言中,字典是一种可变容器模型,且可存储任意类型对象(如字符串、数字、元组等)的无序集合。每个项由一个唯一的键和与之关联的值组成,键和值之间通过冒号分隔,各对键值之间用逗号分隔,并放在花括号内。在处理JSON数据时,JSON对象通常会被转换为Python字典,以便在Python程序内部进行操作和处理。 JSON注入 , 类似于SQL注入,JSON注入是一种安全漏洞,攻击者通过向应用提交恶意构造的JSON数据,利用目标应用程序未能有效验证或清理用户输入的问题,进而影响服务器端JSON解析器的行为,以达到篡改数据、执行非预期操作或获取敏感信息的目的。在Python中使用json.loads()方法解析JSON字符串时,应结合严格的数据验证和清理措施来防止JSON注入攻击。
2024-03-03 16:01:36
529
码农
JSON
JSON (JavaScript Object Notation) , JSON是一种轻量级的数据交换格式,基于JavaScript的一个子集,采用完全独立于语言的文本格式来存储和表示数据。在文章语境中,JSON由于其简洁、易于阅读和编写的特点,以及与JavaScript的天然亲和性(可以直接转换为JavaScript对象),成为现代Web服务和应用程序中数据传输和序列化的首选格式。其数据结构主要由键值对构成,通过花括号包裹,并使用逗号分隔不同的键值对。 XML (Extensible Markup Language) , XML是一种标记型语言,设计用于传输和存储数据,具有高度可扩展性和自描述性。在文中对比的场景下,XML通过标签和属性的方式来组织和描述数据结构,相较于JSON更为严谨且支持命名空间等复杂特性。然而,XML的语法相对复杂,体积较大,解析速度较慢,这在一定程度上限制了它在特定应用场景下的效率优势。 嵌套数据 , 嵌套数据是指在一个数据结构内部包含另一个相同类型或不同类型的数据结构,形成一种层次化的关系。在JSON中,嵌套数据体现为一个对象或数组可以作为另一个对象的值存在,例如文章示例中的JSON数据结构中,“age”和“gender”都是根对象内部的嵌套数据。这种结构使得JSON能够以更直观的方式表达复杂的数据关系,提高了数据处理和理解的便利性。
2023-10-22 23:34:21
516
程序媛
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
代码侠
转载文章
...s[0];} 原因,通过 console.log(JSON.stringify(res.tempFilePaths)); 可知 16:31:14.617 ["_doc/uniapp_temp_1587198502520/compressed/1587198670833.jpeg"] at pages\my\user-set-info.vue:81 res.tempFilePaths为数组,所以当上传一张图片时,取得图片临时地址为res.tempFilePaths[0]; 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_41884068/article/details/105601975。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-05 15:38:13
59
转载
Beego
...风格,它的基本原理是通过HTTP方法(GET, POST, PUT, DELETE)来对资源进行操作。这种设计风格使得API更易理解和使用。 三、Beego支持的特性 Beego不仅支持RESTful API的基本功能,还提供了一些额外的特性。比如,它有一个超级给力的路由机制,能妥妥地应对各种曲折复杂的URL路径;而且人家还特别贴心地支持数据库操作,让你轻轻松松就能把数据存到MySQL或者MongoDB这些数据库里去。 四、设计原则 以下是使用Beego开发RESTful API的一些设计原则: 1. 保持简单 RESTful API应该是简单的,易于理解和使用的。这意味着应该尽可能减少API的复杂性,并遵循RESTful API的设计原则。 2. 明确的状态 每一个HTTP请求都应该返回一个明确的状态。比如,假设你请求一个东西,如果这个请求一切顺利,就相当于你得到了一个“YES”,这时候,服务器会给你回个HTTP状态码200,表示“妥了,兄弟,你的请求我成功处理了”。而要是请求出岔子了,那就等于收到了一个“NO”,这时候,服务器可能会甩给你一个400或者500的HTTP状态码,意思是:“哎呀,老铁,你的请求有点问题,不是格式不对(400),就是服务器这边内部出了状况(500)。” 3. 使用标准的HTTP方法 HTTP定义了8种方法,包括GET, POST, PUT, DELETE, HEAD, OPTIONS, CONNECT和TRACE。应该始终使用这些方法,而不是自定义的方法。 4. 使用URI来表示资源 URI是统一资源标识符,它是唯一标识资源的方式。应该使用URI来表示资源,而不是使用ID或其他非唯一的标识符。 5. 使用HTTP头部信息 HTTP头部信息可以提供关于请求或响应的附加信息。应该尽可能使用HTTP头部信息来提高API的功能性。 6. 返回适当的格式 应该根据客户端的需求返回适当的数据格式,例如JSON或XML。 五、示例代码 以下是一个使用Beego创建RESTful API的简单示例: go package main import ( "github.com/astaxie/beego" ) type User struct { Id int json:"id" Name string json:"name" Email string json:"email" } func main() { beego.Router("/users/:id", &UserController{}) beego.Run() } type UserController struct{} func (u UserController) Get(ctx beego.Controller) { id := ctx.Params.Int(":id") user := &User{Id: id, Name: "John Doe", Email: "john.doe@example.com"} ctx.JSON(200, user) } 在这个示例中,我们首先导入了beego包,然后定义了一个User结构体。然后我们在main函数中设置了路由,当收到GET /users/:id请求时,调用UserController的Get方法。 在Get方法中,我们从URL参数中获取用户ID,然后创建一个新的User对象,并将其转换为JSON格式,最后返回给客户端。 这就是使用Beego创建RESTful API的一个简单示例。当然,这只是一个基础的例子,实际的API可能会更复杂。不过呢,只要你按照上面提到的设计原则来,就能轻轻松松地设计出既高效又超级好用的RESTful API,保证让你省心省力。
2023-08-12 16:38:17
511
风轻云淡-t
JQuery
... jQuery 中文字符编码转换的艺术 1. 引言 为什么需要中文转编码? 当我们深入探索jQuery的世界,尤其是在处理网页交互、数据传输以及DOM操作时,中文字符的正确编码与解码是我们无法回避的问题。在咱们做JavaScript和Web开发这行,由于一些陈年旧账和技术的迭代更新,浏览器之间的兼容性问题时不时就会冒个泡。所以啊,老铁们,确保字符串都以UTF-8这种格式编码,那可是相当关键的一环,可马虎不得!尤其是当你在URL查询参数、Ajax请求内容或JSON数据序列化过程中遇到包含中文字符的字符串时,不恰当的编码可能会导致乱码或数据丢失。本文将带你通过生动具体的示例,揭示如何运用jQuery巧妙地实现中文字符到UTF-8编码的转换。 2. 理解基础 字符编码与Unicode 首先,让我们对“字符编码”这个概念有个基本的认识。在计算机世界里,每个字符都有对应的数字编码,比如ASCII码对于英文字符,而Unicode则是一个包含了全球所有语言字符的统一编码方案。UTF-8是一种变长的Unicode编码方式,它能高效地表示各种语言的字符,特别是对于中文这种非拉丁字符集尤为适用。 3. jQuery不是万能钥匙 JavaScript原生方法 尽管jQuery提供了丰富的DOM操作接口,但在处理字符串编码问题上,并没有直接提供特定的方法。实际上,我们通常会借助JavaScript的内置函数来完成这一任务。这是因为,在JavaScript的大脑里,它其实早就把字符串用UTF-16编码(这货也是Unicode家族的一员)给存起来了。所以,在我们捣鼓JS的时候,更关心的是怎么把这些字符串巧妙地变身成UTF-8格式,这样一来它们就能在网络世界里畅行无阻啦。 javascript // 假设有一个包含中文的字符串 var chineseString = "你好,世界!"; // 转换为UTF-8编码的字节数组 // 注意:在现代浏览器环境下,无需手动转码,此步骤仅作演示 var utf8Bytes = unescape(encodeURIComponent(chineseString)).split('').map(function(c) { return c.charCodeAt(0).toString(16); }); console.log(utf8Bytes); // 输出UTF-8编码后的字节表示 上述代码中,encodeURIComponent 方法用于将字符串中的特殊及非ASCII字符转换为适合放在URL中的形式,其实质上就是进行了UTF-8编码。然后使用 unescape 反解这个过程,得到一个已经在内存中以UTF-8编码的字符串。最后将其转化为字节数组并输出十六进制表示。 4. 实战应用场景 Ajax请求与JSON.stringify() 在实际的jQuery应用中,如发送Ajax请求: javascript $.ajax({ url: '/api/some-endpoint', type: 'POST', contentType: 'application/json; charset=UTF-8', // 设置请求头表明数据格式及编码 data: JSON.stringify({ message: chineseString }), // 自动处理中文编码 success: function(response) { console.log('Data sent and received successfully!'); } }); 在这个例子中,jQuery的$.ajax方法配合JSON.stringify将包含中文字符的对象自动转换为UTF-8编码的JSON字符串,服务器端接收到的数据能够正确解码还原。 5. 总结与思考 虽然jQuery本身并未直接提供中文转UTF-8编码的API,但通过理解和熟练运用JavaScript的内建方法,我们依然可以轻松应对这类问题。尤其在处理跨语言、跨平台的数据交换时,确保字符编码的一致性和正确性至关重要。在实际动手操作的项目里,除了得把编码转换搞定,还千万不能忘了给HTTP请求头穿上“马甲”,明确告诉服务器咱们数据是啥样的编码格式,这样才能确保信息传递时一路绿灯,准确无误。下一次当你在jQuery项目中遇到中文编码难题时,希望这篇文章能成为你的得力助手,帮你拨开迷雾,顺利解决问题。记住,编码问题虽小,但关乎用户体验,不容忽视。
2023-04-05 10:17:37
308
凌波微步
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
wall message
- 向所有已登录用户发送消息。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
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
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"