我的编码备忘录
本文将主要记录在日常开发中遇到的各种问题。以技术类别进行章节划分,作为个人的编码备忘录随时进行查阅,并长期进行置顶。
问题排查
CPU异常飙高排查思路
- 查看占用cpu高的进程: 通过 top 命令找到 CPU 消耗最高的进程,并记住进程 ID {pid}。
top -M -n 2 -d 3 >{pid}/top.txt 查看top
- 再次通过 top -Hp {pid} 找到 CPU 消耗最高的线程 ID,并记住线程 ID(十进制).
- 通过 JDK 提供的 jstack 工具 dump 线程堆栈信息到指定文件中。
jstack {pid} >{pid}/jstack_1.txt 一次堆栈快照 备用
jstack {pid} >{pid}/jstack_2.txt 两次堆栈快照 备用
- 由于刚刚的线程 ID 是十进制的,而堆栈信息中的线程 ID 是16进制的,因此我们需要将10进制的转换成16进制的,并用这个线程 ID 在堆栈中查找。
使用 printf “%x\n” [十进制数字] ,可以将10进制转换成16进制。
- 通过刚刚转换的16进制数字从堆栈信息里找到对应的线程堆栈。就可以从该堆栈中看出端倪。
- 通过top查看当前进程cpu占用情况,找到cpu使用最高的进程PID
- 查看子进程情况
top -p 4606 -H
- 将 子进程id 转换成16进制
printf “%x \n” 4648 结果为1228
- 使用jstack查询具体出现问题的代码位置
jstack 4606|grep 1228 -C 30
- 根据打印结果定位到具体代码位置
JavaCore相关
该模块主要记录JavaCore相关的技术点
bigdecimal四舍五入
BigDecimal.ROUND_HALF_UP: 遇到.5的情况时往上近似,例: 1.5 ->;2
BigDecimal.ROUND_HALF_DOWN : 遇到.5的情况时往下近似,例: 1.5 ->;1
bigDecimal转换为百分比,保留若干小数
DecimalFormat decimalFormat = new DecimalFormat("0.00%");
BigDecimal decimal = new BigDecimal(count.intValue()).divide(new BigDecimal(allCount), 5, ROUND_HALF_UP);
String formatted = decimalFormat.format(sdPercent);
bigDecimal精确度
BigDecimal.setScale(5, BigDecimal.ROUND_HALF_UP) -->保留五位小数,最后一位遇到.5的情况时往上近似
bigDecimal除法
- Java的BigDecimal在使用除法(divide方法)时,应该手动指定精度和舍入的方式。
- 如果不指定精度和舍入方式,在除不尽的时候会报异常。
bigdecimal详解
这里直接参考别人的文章:
(BigDecimal的用法详解)[https://www.cnblogs.com/jpfss/p/8072379.html]
java8的optionnal
举个例子:
public static void main(String[] args) {
Double d = 2.2;
Optional.ofNullable(d).ifPresent(a -> {
System.out.println(a);
});
Double a = null;
System.out.println(Optional.ofNullable(a).orElse(2.2));
}
ofNullable:如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法:不会抛出NullPointerException
ifPresent:检查是否有值的。该方法除了执行检查,还接受一个Consumer(消费者) 参数,如果对象不是空的,就对执行传入的 Lambda 表达式:
orElse:如果有值则返回该值,否则返回传递给它的参数值
并发框架
这部分主要讲并发框架相关
- CompletableFuture用法
CompletableFuture需要单独总结,这里直接放一个参考链接。 CompletableFuture 使用详解
- Java线程池拒绝策略
- SimpleDateFormat为何线程不安全?
SimpleDateFormat线程不安全问题与ThreadLocal原理
集合框架
本模块主要记录集合相关的问题
如何对一个list进行subList操作
|
|
注意:subList看起来好像是取出了原list的子list,实际上仅仅是取到原list的引用。
subList方法的返回值,只是ArrayList的一个映像而已。
也就是说,当我们使用子集合subList进行元素的修改操作时,会影响原有的list集合。
如果要生成原有list的子集合,还是采用硬复制的方式比较好,也就是新建一个新的list,对原list集合进行解析后装载到新的list中。
集合如何进行排序操作?(如:如何对一个list中的元素进行排序)
集合排序,一般有三种方法。
利用集合框架提供的Collections.sort实现排序,待进行排序的实体需要实现比较器Comparable接口的compareTo方法。
返回当前入参的实体和this对象的属性差(该属性为排序依据),
属性差如果为负数表示当前入参比本身小,
属性差如果为0表示当前入参与本身相等,
属性差如果为正数表示当前入参比本身大,
最后调用Collections.sort(temp);返回的集合排序方式为自然排序。
通过调用 Collections.sort(List
list, Comparator<? super T> c) 方法,传入Comparator实现,这种方式下,实体不需要实现Comparable接口。对于JDK1.8,可以通过stream流实现排序,对象本身不需要实现Comparable接口,示例代码如下
//3.利用Java8的stream流和Comparator实现集合排序
list = list.stream().sorted(Comparator.comparing(Person::getAge)).collect(Collectors.toList());
数据库相关(包含Dao相关组件)
本章节主要记录数据库相关的技术点,包含Dao相关组件的使用,如JPA、Mybatis等
mybatis update自动追加逗号 “,”
一个推荐的SQL如下:
<update id="updateOne" parameterType="com.inspur.search.data.EntityRelation">
UPDATE ENTITY_RELATION
<trim prefix="set" suffixOverrides=",">
<if test="srcId!=null">SRC_ID=#{srcId},</if>
<if test="srcType!=null">SRC_TYPE=#{srcType},</if>
<if test="destId!=null">DEST_ID=#{destId},</if>
<if test="destType!=null">DEST_TYPE=#{destType},</if>
<if test="relType!=null">REL_TYPE=#{relType},</if>
<if test="status!=null">STATUS=#{status},</if>
<if test="snId!=null">SN_ID=#{snId},</if>
</trim>
WHERE id=#{id}
</update>
这种方式是动态SQL拼接,使用trim是为了删掉最后字段的“,”
不用单独写SET了,因为set被包含在trim中了
mybatis使用truncate语句
在mybatis中使用truncate语句刷新表。
<update id="truncateTable">
truncate table [表名]
</update>
mybatis使用@Param传参以及批量插入
@Param Parameter N/A 如果你的映射器的方法需要多个参数, 这个注解可以被应用于映射器的方法参数来给每个参数一个名字。否则,多参数将会以它们的顺序位置来被命名 (不包括任何 RowBounds 参数) 比如。 #{param1} , #{param2} 等 , 这是默认的。
如果使用 @Param(“person”), 则参数应该被命名为 #{person}。
这里重点总结一个场景,批量insert。对应sql如下:
<insert id="batchInsertIdList" parameterType="java.util.List">
insert into idlist_tmp (id, record_date) values
<foreach collection="tmpInfos" item="tmpInfo" separator=",">
(#{tmpInfo.id}, #{tmpInfo.recordDate})
</foreach>
</insert>
这里通过#{tmpInfo.id}指定insert参数为对象tmpInfo的某个属性
int batchInsert(@Param(value = "tmpInfos") List<TmpInfo> tmpInfos);
这里通过@Param指定insert的sql中的参数引用。
MySQL建表时设置timestamp精度到毫秒
CREATE TABLE `table1` (
`tab1_id` VARCHAR(11) DEFAULT NULL,
`create` TIMESTAMP(3) NULL DEFAULT NULL,
`create2` DATETIME(3) DEFAULT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8
设置精度的方式为:
TIMESTAMP(3)与 DATETIME(3)意思是保留3位毫秒数
TIMESTAMP(6)与 DATETIME(6)意思是保留6位毫秒数
修改字段精度的方式为:
ALTER TABLE tb_financial MODIFY CREATE_TIME DATETIME(3) DEFAULT NULL COMMENT '录入时间';
插入日期可以用NOW(3)来控制精确的毫秒数,如:SELECT CURRENT_TIMESTAMP(3);也是可以的
mybatis批量插入
MySQL的case when语法
语法如下:
select 字段1, 字段2,
case 字段3
when 值1 then 新值
when 值2 then 新值
end as 重新命名字段3的名字
from table
where ……
order by ……
如:
SELECT
m.id AS id0,
v.id AS id1,
v.user_id AS userId,
v.app_id AS appId
(
CASE
WHEN v.label = 0 THEN
'清晰'
WHEN v.label = 1 THEN
'模糊'
WHEN v.label = 2 THEN
'超清晰' ELSE '其他'
END
) AS '清晰度'
FROM
video_info v,
media_info m
WHERE
v.id = m.id
AND m.id IN (
'123123',
'123124',
'123125'
)
参考资料: MySQL 的CASE WHEN 语句使用说明
前端相关
人生苦短,不会点儿前端都没法儿混了。不喜欢也得会写点儿啊…
vue页面间传参
来自:https://blog.csdn.net/qq_29918313/article/details/82862548
A页面带着参数传给B页面,B页面带着该参数请求接口或者有其他用途
A页面:
/* 编辑 */
handleEdit (aa) {
let params = {
aaId: aa.aaId
}
this.$router.push({
path: '/bb/edit',
name: 'Edit',
params: params
})
},
B页面:
首先要接收A页面传递过来的参数:
let aaId = this.$route.params.aaId
接收方式就是代码中使用的this.$route.params.aaId。B页面中需要带着aaId请求数据,则直接使用即可。另外,跳转页面使用this.$router.push({})。
vue element获取一行数据
来自: https://blog.csdn.net/qq_33616027/article/details/90411872
使用slot-scope获取数据
在操作列,对操作按钮先用带有slot-scope属性的dom进行包装,即可获取当前行的数据,
具体的代码,除了操作列不同外,还需要删除el-table标签中绑定的*@row-click*方法,
剩下的都一样:
<el-table-column label="操作尝试2">
<template slot-scope="scope">
<el-button type="text" @click="checkDetail(scope.row)">查看详情</el-button>
</template>
</el-table-column>
<script>
export default {
name: "dengmiQuery",
data() {
return {
modifyForm:{
formLabelWidth:'120px',
mimian:'',
mimu:''
},
dengmiQueryForm: {
dialogVisible: false,
list: [],
}
};
},
methods: {
checkDetail(val){
console.log(val)
}
}
}
</script>
通过template slot-scope=“scope”来定义当前行的数据对象,然后通过scope.row来获取当前行的数据。
vue element视频播放组件
来自:https://blog.csdn.net/abelethan/article/details/89016678
搞视频相关业务的一定会接触播放组件
使用 vue-vedio-player
首先我们先安装这个插件
npm install vue-video-player -s
我们需要在main.js里面导入并引用
import VideoPlayer from 'vue-video-player' import 'vue-video-player/src/custom-theme.css' import 'video.js/dist/video-js.css' Vue.use(VideoPlayer)
html部分
<template> <div class='demo'> <video-player class="video-player vjs-custom-skin" ref="videoPlayer" :playsinline="true" :options="playerOptions"> </video-player> </div> </template>
js部分
<script> export default { data() { return { playerOptions: { //播放速度 playbackRates: [0.5, 1.0, 1.5, 2.0], //如果true,浏览器准备好时开始回放。 autoplay: false, // 默认情况下将会消除任何音频。 muted: false, // 导致视频一结束就重新开始。 loop: false, // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持) preload: 'auto', language: 'zh-CN', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") aspectRatio: '16:9', // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。 fluid: true, sources: [{ //类型 type: "video/mp4", //url地址 src: '' }], //你的封面地址 poster: '', //允许覆盖Video.js无法播放媒体源时显示的默认信息。 notSupportedMessage: '此视频暂无法播放,请稍后再试', controlBar: { timeDivider: true, durationDisplay: true, remainingTimeDisplay: false, //全屏按钮 fullscreenToggle: true } } } } } </script>
style部分
<style scoped> .demo{ display: inline-block; width: 600px; height: 338px; text-align: center; line-height: 100px; border: 1px solid transparent; border-radius: 4px; overflow: hidden; background: #fff; position: relative; box-shadow: 0 1px 1px rgba(0, 0, 0, .2); margin-right: 4px; } .demo:hover{ display: block; } </style>
工具技巧
本章节主要记录各种工具使用的技巧,如IDEA GIT等
跨主机进行文件复制
从本地服务器复制到远程服务器
复制文件:
$scp local_file remote_username@remote_ip:remote_folder
$scp local_file remote_username@remote_ip:remote_file
$scp local_file remote_ip:remote_folder
$scp local_file remote_ip:remote_file
指定了用户名,命令执行后需要输入用户密码;如果不指定用户名,命令执行后需要输入用户名和密码;
复制目录:
$scp -r local_folder remote_username@remote_ip:remote_folder
$scp -r local_folder remote_ip:remote_folder
第1个指定了用户名,命令执行后需要输入用户密码; 第2个没有指定用户名,命令执行后需要输入用户名和密码;
注解
从远程复制到本地的scp命令与上面的命令一样,只要将从本地复制到远程的命令后面2个参数互换顺序就行了。
使用示例
实例1:从远处复制文件到本地目录
$scp root@10.6.159.147:/opt/soft/demo.tar /opt/soft/
说明: 从10.6.159.147机器上的/opt/soft/的目录中下载demo.tar 文件到本地/opt/soft/目录中
实例2:从远处复制到本地
$scp -r root@10.6.159.147:/opt/soft/test /opt/soft/
说明: 从10.6.159.147机器上的/opt/soft/中下载test目录到本地的/opt/soft/目录来。
实例3:上传本地文件到远程机器指定目录
$scp /opt/soft/demo.tar root@10.6.159.147:/opt/soft/scptest
说明: 复制本地opt/soft/目录下的文件demo.tar 到远程机器10.6.159.147的opt/soft/scptest目录
实例4:上传本地目录到远程机器指定目录
$scp -r /opt/soft/test root@10.6.159.147:/opt/soft/scptest
说明: 上传本地目录 /opt/soft/test到远程机器10.6.159.147上/opt/soft/scptest的目录中
idea自动导包
IDEA自动导包配置方式如下:
在菜单中选择如下选项:
Settings→
Editor→
General→
Auto Import
然后勾选Add unambiguous imports on the fly以及Optimize imports on the fly
解释一下含义:
Add unambiguous imports on the fly: 快速添加明确的导入。
Optimize imports on the fly: 快速优化导入,优化的意思即自动帮助删除无用的导入。
打包运行相关
本章节主要记录打包相关的问题
如何通过命令行传递带空格的参数?
通过参数引用即可解决该问题,在UNIX环境下,通过引号将带空格的参数括起来即可,如:
$ java PrintFileSizes "/home/steve/Test File.txt"
Linux下找出进程正在侦听的端口号
在Linux下快速查到正在侦听的端口号,命令如下:
# 安装工具包,默认已安装,centos下为yum install
sudo apt install net-tools
# 查看侦听中的端口
sudo netstat -ltnp
nohup的作用
举个例子,有启动命令如下
nohup java -jar XXX.jar > /dev/null 2>&1 &
nohup表示:不挂断运行命令,当账户退出或终端关闭时,程序仍然运行。
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;这里表示”禁止输出”。我们应当在程序内配置日志打印。
其他用法:linux环境下nohup的执行jar
git 相关
本模块主要整理git相关操作
git更新带子module的工程
首先克隆父工程
git clone ssh://xxxxxx.git 查看子模块 git submodule 子模块前面有一个-,说明子模块文件还未检入(空文件夹)。
然后在父工程根路径下初始化子工程
git submodule init 初始化模块只需在克隆父项目后运行一次。
更新子工程
git submodule update
git更新子工程终极方案
cd project (自己工程目录根目录)
cd submodule-project (依赖的子工程根路径)
git pull origin branch (拉取子工程最新提交)
cd “your-project-root-path” (进入你自己工程的根目录)
git commit -m 'upgrade model' && git push -u origin your-branch (生成一个提交并push)
这样服务端的子工程也就随之发生更新
git 强制使用远程覆盖本地
git fetch --all && git reset --hard dev&& git pull
git去除某个历史提交
首先获取git提交记录的commitId
git rebase -i 20624e607934da393719a0a046c27a4be32944f1
skip更新为drop或者删除掉
git push origin master --force
方法2–递归方式
递归克隆整个项目
git clone ssh://xxxx.git assets --recursive
递归克隆整个项目,子模块已经同时更新了,一步到位。
Spring相关
这部分主要整理Spring框架相关的问题,包括SpringBoot
Spring中通过 @Value 设置默认值
1.1 字符串类型的属性设置默认值
@Value("${some.key:my default value}")
private String stringWithDefaultValue;
如果默认值设为空,也将会被设置成默认值。
@Value("${some.key:}")
private String stringWithBlankDefaultValue;
1.2 基本类型设置默认值
布尔类型
@Value("${some.key:true}")
private boolean booleanWithDefaultValue;
数字类型
@Value("${some.key:42}")
private int intWithDefaultValue;
1.3 包装类型设置默认值
布尔类型
@Value("${some.key:true}")
private Boolean booleanWithDefaultValue;
数字类型
@Value("${some.key:42}")
private Integer intWithDefaultValue;
1.4 数组的默认值使用逗号分割
@Value("${some.key:one,two,three}")
private String[] stringArrayWithDefaults;
@Value("${some.key:1,2,3}")
private int[] intArrayWithDefaults;
1.5 使用 Spring Expression Language (SpEL) 设置默认值
@Value("#{systemProperties['some.key'] ?: 'my default system property value'}")
private String spelWithDefaultValue;
这表示:在systemProperties属性文件中,如果没有设置 some.key 的值,my default system property value 会被设置成默认值。
Spring中HttpServletRequest的线程安全性
Spring中获取request的几种方法,及其线程安全性分析
SpringBoot 类加载机制
springboot应用启动原理(二) 扩展URLClassLoader实现嵌套jar加载
Spring Bean生命周期
一个对象具备完整的Spring生命周期才可以称之为一个Spring的bean,否则就只是个Java对象
参考文章:
剑指Spring源码(三)俯瞰Spring的Bean的生命周期(大众版)
Spring的BeanPostProcessor和BeanFactoryPostProcessor区别
BeanPostProcessor和BeanFactoryProcessor浅析
Spring强化
BeanPostProcessor —— 连接Spring IOC和AOP的桥梁
Jersey相关
这里记录Jersey相关的内容
Jersey常用方法
查看Jersey REST服务的WADL服务定义
查看WADL服务定义通过下方URL即可访问到。
http://ip:port/应用根路径/application.wadl
Json解析相关
这里主要解析Json解析相关的技术点。
Jackson整理
在Jackson中将JsonNode转换为Object
mapper.convertValue(jsonNode, MyPojo.class)
Jackson中的TypeReference
一般情况下如果没有TypeReference的话,JsonNode转换过来的是LinkedHashMap而不是对象本身,因此
需要使用TypeReference来进行Json的解析如:
List<TableColumns> columns = mapper.convertValue(params.get("columns"), new TypeReference<List<TableColumns>>() {}); 对于复杂Json的转换,需要通过TypeReference解析,TypeReference可以正确反序列化嵌套多层的List或Map,例如List<Map<String,String>>
举个实际的例子,
Map<String, MarkDetail> markDetailMap = objectMapper.convertValue(haveMarkDetails, new TypeReference<Map<String,MarkDetail>>() {}); Map<String, String> dataDetailMap = objectMapper.convertValue(dataDetailJsonNode, new TypeReference<Map<String, String>>() {});
可以看到,convertValue的第二个参数是一个TypeReference实例。通过这种方式能够将复杂json类型进行正确解析。
ffmpeg
ffmpeg的几个工具基本用法
ffmpeg是用于转码的应用程序。
一个简单的转码命令可以这样写:
将input.avi转码成output.ts,并设置视频的码率为640kbps
ffmpeg -i videoplayback.mp4 -b:v 640k output.ts
用于播放的应用程序。
一个简单的播放命令可以这样写:
播放test.avi
ffplay test.avi
ffprobe是用于查看文件格式的应用程序。
这个就不多介绍了。
ffprobe videoplayback.mp4
维护多个sshkey
在公司与开源社区之间切换,有多个git账户,因此需要一个策略用于维护多个sshkey
由于有两个账号,生成密钥时通常都是三个回车一撸到底,那么后执行的会覆盖先执行的。
三个回车中,第一个回车的意思是保存地址,那我们不直接回车,而是输入保存地址就可以。
具体步骤
生成第一个密钥
ssh-keygen -t rsa -C "myoschina@qq.com"
连续三个回车,将oschina的密钥默认保存
- 生成第二个密钥
在C盘/Users/用户名/.ssh下建立一个新的目录,如:github
接着运行命令生成第二个sshkey
ssh-keygen -t rsa -C "github@gmail.com"
出现Enter file in which to save the key时输入
/c/Users/用户名/.ssh/github/id_rsa
表示将本次生成的key保存在建立的github目录下
- 创建config文件
C盘/Users/用户名/.ssh下新建config文件,该文件没有后缀名的,用途是配置映射功能,填入下面代码:
#github配置
Host github.com
HostName github.com
IdentityFile c:\\Users\\xxxxx\\github\\.ssh\\id_rsa
PreferredAuthentications publickey
User usernameOfGithub
#gitoschina的配置
Host git.oschina.net
HostName git.oschina.net
IdentityFile c:\\Users\\xxxxx\\.ssh\\id_rsa
PreferredAuthentications publickey
User usernameOfOsChina
HostName是服务器域名,IdentityFile 是密钥的地址.
vim临时显示行号
如果只是临时显示vim的行号,只须按ESC键退出编辑内容模式,输入
:set number 后按回车键显示行号
行号显示只是暂时的,退出vim后再次打开vim就不显示行号了。
python相关
此模块主要整理python相关内容
1. init.py的作用
1、__init__.py是Python中package的标识
__init__.py 文件的一个主要作用是将文件夹变为一个Python模块,
Python 中的每个模块的包中,都有__init__.py 文件
2、批量引入(定义__all__用来模糊导入)
我们在python中导入一个包时,实际上是导入了它的__init__.py文件,
这样我们可以在__init__.py文件中批量导入我们所需要的模块,而不再需要一个一个的导入。
3、配置模块的初始化操作,这个文件也是一个正常的python代码文件,因此可以将初始化代码放入该文件中。
python中init.py文件的作用实例:
python的每个模块的包中,都有一个__init__.py文件,有了这个文件,
我们才能导入这个目录下的module。
__init__.py里面还是可以有内容的,我们在导入一个包时,
实际上导入了它的__init__.py文件。我们可以再__init__.py文件中再导入其他的包,或者模块。
这样,当我们导入这个包的时候,__init__.py文件自动运行。
帮我们导入了这么多个模块,我们就不需要将所有的import语句写在一个文件里了,
也可以减少代码量。不需要一个个去导入module了。
版权声明:
原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。