Markdown 基本语法介绍

Markdown 是我很喜欢的一个轻量级标记语言,但也因为不常写,所以有些语法记得不是很清楚,经常写的时候需要查一些资料,所以这次就把一些简单的常用的语法做个笔记。 在介绍 markdown 语法之前,先写一点废话。 一. markdown是什么? 简单的一句话就是,Markdown 可谓是程序员必备的一种写作格式!你还在用 word 写文档么?简直 low 爆了,赶紧抛弃,从现在开始,立刻,马上,学习下 markdown。 二. markdown有什么好处? 简单说,语法简单、再也不用专注排版、兼容 html、还有其他的的很多很强大的功能(这么厉害,你的博客为啥不用?因为 WordPress 原生编辑器不支持 markdown,也一直没有安装第三方的插件,所以就没有) 三. markdown 编辑器有哪些? 个人最喜欢的开发工具是,GitHub 出品的 Atom,自带提示;之前也在 Windows 下用过 markdownpad,也还行,mac 下有很多人推荐 mou,另外据说 Sublime 也不错,还有一些在线工具据说也不错。 四. markdown 语法 标题 1 到 6:#1 到 #6 代码示例: \# 标题一 \### 标题三 区块:> 代码示例: > 这是区块 > > 第二行区块 斜体:*斜体* 代码示例: \*斜体\* 加粗:**加粗** 代码示例: \*\*该部分加粗\*\* 删除线:删除线 示例代码: ~~这是加删除线的文字~~ 换行和分段 换行:只需在行末加两个空格键和一个回车键即可换行。快捷键:control + 回车键 分段:段落之间空一行即可 ...

July 15, 2018 · 1 min · 196 words · Bridge Li

Git 配置多个用户身份和强制检查各个项目用户名邮箱设置

今天的文章比较简单,1. 就是为 Git 单个项目做身份配置,就是配置单独的邮箱和用户名。因为我们平时可能会在不同的几个项目中工作,各个项目的用户名可能不同,最基本的就是公司的项目和我们自己在 GitHub 上玩,所以为了保证日志的准确性和提交时无误,最好对各个项目设置。以前没有研究过,所以就一只默认用公司的用户名玩,但一直感觉不太好,2. 在提交时,user.name, user.email会进入日志。这些信息,是追踪代码变更的关键,所以必须配置,偶然看见秋大有篇文章写这个,试了一些不错,记录一下。 全局配置和项目配置 全局配置信息在: ~/.gitconfig 项目配置在项目目录下的: ./.git/config 配置多个用户身份 git config –global 操作全局配置, 不带 –global 选项的话,会尝试相对于当前目录下找:./git/config, 找不到的话,报错,所以如此一来,我们就可以: \# for global setting git config &#8211;global user.name xxx git config &#8211;global user.email xxx@xxx.com \# for repository git config user.name xxxx git config user.email xxxx@xxx.com 另,我们都知道 Linux 下,我们可以通过 alias 命令设置别名(之前一直就不住这个命令,每次都是查一下,后来一朋友说多好记啊 ali as,阿里 as,就再也忘不了了),我们可以通过一个短命令把 git 日志重新格式化一下,至于其他的像:git status,git commit 是否重命名就看习惯了(看过有人把常错的单词都重命名为正确的,省时间,机智) git config &#8211;global alias.lg "log &#8211;color &#8211;graph &#8211;pretty=format:&#8217;%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset&#8217; &#8211;abbrev-commit &#8211;" 强制检查各个项目用户名邮箱设置 使用 pre-commit 钩子进行检查,所以在 .git/hooks 中添加一个 pre-commit 文件(注意权限),内容如下: ...

July 1, 2018 · 1 min · 176 words · Bridge Li

org.apache.ibatis.builder.IncompleteElementException: Could not find parameter map

上周和同事一块开发一个功能模块,在开发中拉下来同事代码,在测试的时候,突然跑不通了,报错信息如下: org.apache.ibatis.builder.IncompleteElementException: Could not find parameter map java.util.Map at org.apache.ibatis.builder.MapperBuilderAssistant.getStatementParameterMap(MapperBuilderAssistant.java:320) at org.apache.ibatis.builder.MapperBuilderAssistant.addMappedStatement(MapperBuilderAssistant.java:296) at org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode(XMLStatementBuilder.java:109) at org.apache.ibatis.session.Configuration.buildAllStatements(Configuration.java:753) at org.apache.ibatis.session.Configuration.hasStatement(Configuration.java:723) at org.apache.ibatis.session.Configuration.hasStatement(Configuration.java:718) at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:201) at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:48) at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:59) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:52) at com.sun.proxy.$Proxy40.insertSelective(Unknown Source) at com.weidai.urge.service.disposition.BidDispositionOrderServiceTest.testInsertSelective(BidDispositionOrderServiceTest.java:48) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: java.lang.IllegalArgumentException: Parameter Maps collection does not contain value for java.util.Map at org.apache.ibatis.session.Configuration$StrictMap.get(Configuration.java:853) at org.apache.ibatis.session.Configuration.getParameterMap(Configuration.java:625) at org.apache.ibatis.builder.MapperBuilderAssistant.getStatementParameterMap(MapperBuilderAssistant.java:318) &#8230; 39 more 看报错信息,也就是自己跑一个 junit test,测试批量插入,突然他就不行了,再看看自己写的批量插入没问题啊,我也没用 map,感觉好奇怪,就网上搜了一下这个问题,原来确实有问题,也确实在这个配置文件中,提示的错误也对,就是提示的位置不对。mybatis 中只要有任何一个地方报错,都无法通过。最后搜了一下发现是刚刚来下来的代码,同事新增了一个方法上将 parameterType 写成了 parameterMap 了 ...

June 2, 2018 · 1 min · 153 words · Bridge Li

上传 Java 库到 Maven central repository

之前看过 Trinea 写过一篇文章,如果上传 Java 库到 Maven central repository,前一段时间感觉公司封装的 mybatis-generator 不好用,完全没有解决原生的 mybatis-generator 的问题,所以就重新做了一次封装,主要是加了查询分页,然后就想到是不是可以上传到 Maven central repository 玩玩,看了一下 Trinea 的这篇文章感觉挺简单的(原文见后面参考资料),但实际上还是有一些坑,具体的可以看 Trinea 的这篇文章,我主要写一下遇到的一些坑。 先说明一下,pom 文件请参考我的配置:https://github.com/bridgeli/mybatis-generator-plugin/blob/master/pom.xml,这里面所有的配置都是必须的,也是最少的了吧,自己写的时候修改一些内容,适配自己的就好了,Trinea 写的那个 pom 还是比较适合 Android。 发布 release 版本的时候,使用命令 mvn release:clean release:prepare release:perform 执行这个命令的时候,可能会遇到三个问题。 Git 报错,使用 Git 几年了,进入会遇到这样的错误。错误信息: Unable to commit files [ERROR] Provider message: [ERROR] The git-add command failed. [ERROR] Command output: 实在不知道咋回事,可能是我有些文件没提交,反正提交之后就好了,看到有人说他报错是因为他不是 Git 管理的项目,我作为 Git 脑残粉,肯定是的,可能就是因为没有提交吧,反正最后就这么莫名其妙的解决了。 提示没有 snapshot 版本,所以执行这个命令的时候,版本号和发布 snapshot 一样就好,而不是自己手动改成 release 版。 因为此时会发布一个 release 版本,会有 tag 推送到 GitHub 有可能会遇到 401,看到这个 error code,应该都清楚咋回事,没权限。不知道,病急乱投医,我是在 GitHub 上添加了一个 GPG key 并且在 pom 文件中新增一个 plugin。这个问题有人说是账号和密码错误,但是我很肯定我的账号和密码一点也没有错。 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-gpg-plugin</artifactId> <version>1.5</version> <executions> <execution> <id>sign-artifacts</id> <phase>verify</phase> <goals> <goal>sign</goal> </goals> </execution> </executions> </plugin> 最终解决了这个问题,关于这个问题,可能是我操作的问题没有看到过别人说过,另外在 GitHub 上他的颜色一直还是:灰的,没有显示使用过,所以可能是我操作的有问题,不需要在 GitHub 上添加这个 GPG key,具体大家可以测一下,我后期也会测试。 另,需要说明的是,生成 GPG key 的时候用到的邮箱和我在 GitHub 上的邮箱,我用的不是同一个邮箱,建议还是用同一个吧,省得出莫名其妙的问题。 ...

May 20, 2018 · 1 min · 193 words · Bridge Li

Java Thread 同步

之前遇到一个问题,就是如何让线程同步,由于自己多线程的东西实在不懂,所以不知道怎么办,但感觉应该是一个很简单的东西,所以就从网上搜一下资料,原来如此简单,直接调用 join 方法就好了。写篇博客记录一下 join 的使用方法。 作用 Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。具体看代码: package cn.bridgeli.demo; public class ThreadTest { public static void main(String[] args) throws InterruptedException { ThreadJoinTest t1 = new ThreadJoinTest("bridgeli"); ThreadJoinTest t2 = new ThreadJoinTest("liqiao"); t1.start(); /** * join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是: * 程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕 * 所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会 * * join方法可以传递参数,join(10000)表示main线程会等待t1线程10毫秒,10毫秒过去后, * main线程和t1线程之间执行顺序由串行执行变为普通的并行执行 */ t1.join(10000); t2.start(); } } package cn.bridgeli.demo; public class ThreadJoinTest extends Thread { public ThreadJoinTest(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(this.getName() + ":" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } 上面注释也大概说明了 join 方法的作用:在 主线程 中调用了 t1 线程的 join() 方法时,表示只有当 t1 线程执行完毕时,主线程 才能继续执行,也就是开始执行 t2。注意,join 方法其实也可以传递一个参数给它的,表示:如果 主线程 在 t1 执行 1000 毫秒之后,继续执行,也就是开启 t2 线程。 ...

May 12, 2018 · 3 min · 446 words · Bridge Li

介绍一个 Mybatis 插件:mybatis-generator-plugin

在实际开发中,我们都是先建表,然后根据表生成对应的 Java 类,现在很流行的 ORMapping 框架是: Mybatis,所以我们需要生成 entity、mapper 和 xml,我们都知道有一个插件是:mybatis-generator,使用它就可以很方便的生成这些结构化的重复性基础性的代码,但是他有一个问题,生成的查询没有分页,所以很烦。然后我搜索了一些资料,重新封装了一下,重新命名为:mybatis-generator-plugin,具体源码放在了 GitHub 上: https://github.com/bridgeli/mybatis-generator-plugin 大家可以 clone 下来,deploy 到自己公司的私服,或者 install 到本地使用。具体就是在 pom 文件中。具体用法 在 pom 文件中添加如下内容: <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.5</version> <dependencies> <dependency> <groupId>cn.bridgeli</groupId> <artifactId>mybatis-generator-plugin</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <configuration> <verbose>true</verbose> <overwrite>true</overwrite> <configurationFile>src/main/resources/generatorConfig.xml</configurationFile> </configuration> </plugin> </plugins> </build> 其中:configurationFile 配置 generatorConfig.xml 文件的位置,我们生成的对应的文件,都是根据这个文件来的。所以第二步就是: 在对应的文件下添加该文件,它的模板如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!&#8211; 请替换本地jar路径 此文件不要上传 &#8211;> <classPathEntry location="/Users/bridgeli/.m2/repository/mysql/mysql-connector-java/5.1.33/mysql-connector-java-5.1.33.jar" /> <!&#8211; mvn mybatis-generator:generate &#8211;> <context id="oneHour" targetRuntime="cn.bridgeli.plugin.IntrospectedTableMyBatis3ImplExt"> <property name="suppressAllComments" value="true" /> <property name="useActualColumnNames" value="false" /> <!&#8211; 格式化java代码 &#8211;> <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter" /> <!&#8211; 格式化XML代码 &#8211;> <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter" /> <!&#8211; 配置插件 &#8211;> <plugin type="cn.bridgeli.mybatis.MySQLLimitPlugin"></plugin> <commentGenerator> <!&#8211; 是否去除自动生成的注释 true:是 : false:否 &#8211;> <property name="suppressAllComments" value="true" /> </commentGenerator> <!&#8211;数据库连接的信息:驱动类、连接地址、用户名、密码 &#8211;> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://ip:port/databasename" userId="username" password="password"> </jdbcConnection> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!&#8211; 配置model生成位置 &#8211;> <javaModelGenerator targetPackage="cn.bridgeli.entity" targetProject="src/main/java"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!&#8211; 配置sqlmap生成位置 &#8211;> <sqlMapGenerator targetPackage="cn/bridgeli/mapper" targetProject="src/main/resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!&#8211; 配置mapper接口生成位置 &#8211;> <javaClientGenerator type="XMLMAPPER" targetPackage="cn.bridgeli.mapper" targetProject="src/main/java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!&#8211; 配置表信息 &#8211;> <table tableName="table_name" enableCountByExample="true" domainObjectName="DomainObjectName" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="true"> <generatedKey column="ID" sqlStatement="MySQL" identity="true" /> </table> </context> </generatorConfiguration> 这里面需要说明就是: ...

April 29, 2018 · 2 min · 260 words · Bridge Li

介绍一个强大易用的日期和时间库:Joda-Time

在 Java 中处理日期和时间是很常见的需求,基础的工具类就是我们熟悉的 Date 和 Calendar,之前我也曾经写过一篇文章利用这两个类,怎么处理时间,然而这些工具类的 api 使用并不是很方便和强大,于是就诞生了Joda-Time 这个专门处理日期时间的库。而且 Joda-Time 很优秀,用了他之后再也停不下来,其在 Java 8 出现前的很长时间内成为 Java 中日期时间处理的事实标准,用来弥补 JDK 的不足。项目中要想使用 Joda-Time 很简单,只需要引入依赖: <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.9.9</version> </dependency> 下面说一些常用的例子,供平时参考 创建任意时间对象 DateTime dateTime=new DateTime(2018, 03, 31, 16, 57,55); 创建当前时间对象 DateTime dateTime=new DateTime(); 格式化时间输出 DateTime dateTime = new DateTime(); System.out.println(dateTime.toString("yyyy-MM-dd HH:mm:ss")); 解析文本格式时间 DateTimeFormatter format = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); DateTime dateTime = DateTime.parse("2018-03-31 17:01:45", format); 在当前日期上加上 90 天并输出结果 DateTime dateTime = new DateTime(); System.out.println(dateTime.plusDays(90).toString("yyyy-MM-dd HH:mm:ss"); 获取今天的开始时间 DateTime nowTime = new DateTime(); DateTime startOfDay = nowTime.withTimeAtStartOfDay(); 获取今天的结束时间 DateTime nowTime = new DateTime(); DateTime endOfDay = nowTime.millisOfDay().withMaximumValue(); 计算两个日期的相隔天数 DateTime nowTime = new DateTime(); DateTime futureTime = new DateTime(2019, 10, 1, 0, 0, 0); Int days = Days.daysBetween(nowTime, futureTime).getDays(); 本月的第一天和最后一天 DateTime dateTime = new DateTime(); DateTime firstDateTimeOfMonth = dateTime.dayOfMonth().withMinimumValue().withTimeAtStartOfDay(); DateTime lastDateTimeOfMonth = dateTime.dayOfMonth().withMaximumValue().withTimeAtStartOfDay(); 上个月的第一天和最后一天 DateTime dateTime = new DateTime(); DateTime startDate = dateTime.plusMonths(-1).dayOfMonth().withMinimumValue().withTimeAtStartOfDay(); DateTime endDate = dateTime.plusMonths(-1).dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue(); 在每天的6:30处理一些东西 DateTime dt=new DateTime().withHourOfDay(6).withMinuteOfHour(30).withSecondOfMinute(0); 在每月的7号的6:30处理一些东西 DateTime dt=new DateTime().withDayOfMonth(7).withHourOfDay(6).withMinuteOfHour(30).withSecondOfMinute(0); 在每年的8月的7号的6:30处理一些东西 DateTime dt=new DateTime().withMonthOfYear(8).withDayOfMonth(7).withHourOfDay(6).withMinuteOfHour(30).withSecondOfMinute(0); 获取年终 月、日、时、分、秒、毫秒 DateTime dt = new DateTime(); //获取当前时间的年 int year = dt.getYear(); //获取当前时间的月 int month = dt.getMonthOfYear(); //获取当前时间是一年中的第几天 int dayOfYear = dt.getDayOfYear(); //获取一个月中的天 int day = dt.getDayOfMonth(); //获取一周中的周几 int week = dt.getDayOfWeek(); //一天中的第几小时(取整) int hour = dt.getHourOfDay(); //获取星期年 int weekOfyear = dt.getWeekyear(); //当前时间的秒中的毫秒 int ms = dt.getMillisOfSecond(); //获取当前时间的秒 int second = dt.getSecondOfDay(); //获取当前时间的毫秒 long millis = dt.getMillis(); 判断时间跨度是否包含当前时间,某个时间 Interval interval = new Interval(new DateTime(2018, 1, 1, 0, 0, 0), new DateTime(2018, 3, 30, 0, 0, 0)); System.out.println(interval.containsNow()); boolean contained = interval.contains(new DateTime("2012-03-01")); 与JDK互转换 //通过jdk时间对象构造 Date date = new Date(); DateTime dateTime = new DateTime(date); Calendar calendar = Calendar.getInstance(); dateTime = new DateTime(calendar); // Joda-time 各种操作&#8230;.. dateTime = dateTime.plusDays(1) // 增加天 .plusYears(1)// 增加年 .plusMonths(1)// 增加月 .plusWeeks(1)// 增加星期 .minusMillis(1)// 减分钟 .minusHours(1)// 减小时 .minusSeconds(1);// 减秒数 // 计算完转换成jdk 对象 Date date2 = dateTime.toDate(); Calendar calendar2 = dateTime.toCalendar(Locale.CHINA); 解析 CST 格式的时间字符串 DateTimeFormatter format = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss zzz yyyy"); DateTime dateTime = DateTime.parse("Fri Mar 30 00:00:00 CST 2018", format); 当然作为一个强大的 时间日期 处理工具类,他还有更多更强大的 API,可以参看: ...

March 31, 2018 · 2 min · 310 words · Bridge Li

记一次使用 lombok 小小的成长感悟

公司项目里面用了 lombok,感觉这个东西真是个好东西,然后公司也用的简单,所以也没仔细看文档就开始想当然的用了,然后就悲剧了,今天就记录一下这件事,写一下经验教训,具体怎么用,大家可以看最后的参考。 lombok 有一个很好用的注解:@Data,当时以为这个注解就是相当于:@Getter和@Setter,所以有一次要重写 equals 和 hashcode 方法,然后就让 IDE 自动生成了,当时也没仔细看生成的是什么样子,然后就发现了 bug,仔细一看生成的 equals 方法原来是这样的: package cn.bridgeli.demo; import lombok.Data; import java.util.Objects; /** * Created by bridgeli on 2018/2/25. */ @Data public class LomBokTest { private Integer id; private String username; @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } if (!super.equals(o)) { return false; } LomBokTest that = (LomBokTest) o; return Objects.equals(id, that.id); } @Override public int hashCode() { return Objects.hash(super.hashCode(), id); } } 当时还以为是 idea 的 bug,因为是 idea 自动生成的,之前用 eclipse 自动生成从没问题,eclipse 自动生成的是这样的: ...

February 25, 2018 · 2 min · 346 words · Bridge Li

NullPointerException in Java with no StackTrace

这周一个项目遇到一个问题,同事查看日志发现抛出:NullPointerException,却没有堆栈信息,然后同事感觉很奇怪,因为打日志的方法,打印的确实是:e,而不是很多人不明所以的打印的:e.getMessage()。然后我看了一下想起来我看过某本书上说过的,JIT 优化。当某个异常抛出很多次之后,由于 Java 虚拟机 JIT 优化,会省略堆栈信息。往上面翻日志肯定可以会找到报错的地方,当然会出现报错的信息太多,比较难翻。写这篇文章的本来想找找那本书,参考一下的,结果忘了是那本书了,一时没找到,不过这个问题虽然不是非常常见,但是网上还是有很多说明的,所以就简单说说 JVM 有一个参数:OmitStackTraceInFastThrow 来控制是否开启此优化。关于此参数的简单说明: JVM参数-XX:-OmitStackTraceInFastThrow参数可以关掉JVM对堆栈信息的优化。如果设置了这个参数,那么异常堆栈就能完整输出了。 “在服务器中的VM编译器现在提供准确的所有的“冷”内置异常堆栈回溯功能。为了性能考虑,当这些异常被抛出很多次时,这个方法会被重新编译,此后编译器将使用一种更快的抛出异常的方式,即抛出预先分配好的不带堆栈信息的异常。要完全关闭掉这种预分配的异常,就需要使用-XX:-OmitStackTraceInFastThrow参数。” 相关stackoverflow讨论:https://stackoverflow.com/questions/2411487/nullpointerexception-in-java-with-no-stacktrace 笨神小程序 JVMPocket 对此参数的解释截图: 另,看到网上有部分人建议关闭此参数,个人是建议的: 由于Java 在虚拟机中运行,天生是比 c 要慢的,为了优化此问题,那些 JVM 大神们搞出了 JIT 优化,对性能有了大幅提升。 异常信息使我们应该特别关注了,当出现了异常应该尽快发现问题解决问题,而不是这个异常发生很多次了但是一直不知道。 打详细堆栈信息是很影响性能的,如果这个异常出现很多次了,没必要每次都打出来异常,看一个地方就知道了,而不是让他一直打,影响性能。 那有人说了当遇到此问题时,堆栈信息看不到,日志又比较难翻怎么办?我们好像也不能动态的添加 JVM 的启动参数,所以保险起见还是关闭此参数。个人认为:这个异常这么常出现,而且是 JIT 的优化导致看不到堆栈信息了,可以找一台机器重启一下就好了,没必要因小失大,我们从笨神的小程序也可以看到 JVM 默认是开启此参数的,开启肯定是有其道理的。

January 7, 2018 · 1 min · 33 words · Bridge Li

你假笨JVM参数 – 007 UseGCLogFileRotation NumberOfGCLogFiles GCLogFileSize

你假笨的第七次分享,也是你假笨在 2017 年的最后一次关于 JVM 的分享: 序号:007 时间:2017-08-10 参数: -XX:UseGCLogFileRotation -XX:NumberOfGCLogFiles -XX:GCLogFileSize 含义: 这次分享了3个设置滚动记录GC日志的参数 通过参数-Xloggc:xxx可指定GC日志文件路径 普通情况下,GC日志文件内容会不断积累,进程重启后日志文件会被覆盖 这次分享的3个参数在设置-Xloggc参数的前提下有效 -XX:UseGCLogFileRotation Enabled GC log rotation, requires -Xloggc. 打开或关闭GC日志滚动记录功能,要求必须设置 -Xloggc参数 -XX:NumberOfGCLogFiles Set the number of files to use when rotating logs, must be >= 1. The rotated log files will use the following naming scheme, .0, .1, …, .n-1. 设置滚动日志文件的个数,必须大于1 日志文件命名策略是,.0, .1, …, .n-1,其中n是该参数的值 -XX:GCLogFileSize The size of the log file at which point the log will be rotated, must be >= 8K. 设置滚动日志文件的大小,必须大于8k 当前写日志文件大小超过该参数值时,日志将写入下一个文件 ...

December 31, 2017 · 1 min · 92 words · Bridge Li