1.HashMap的存储不是按照插入顺序排列的
今天碰到了调用接口必须按照插入的顺序,原来用HashMap结果因为顺序不对,将HashMap改为LinkedHashMap就可以了
如果已知数组的大小,则应该对HashMap进行初始化,initialCapacity = (存储元素个数/负载因子)+1,一般负载因子=0.75,防止赋值时对HashMap扩容(扩容需要重新hash)
List list = new ArrayList(initialCapacity);
Map map = new HashMap(initialCapacity*4/3+1);
2.Date类型插到map中,转换为json字符时,会将日期转换成为一个Map值为{"date":7,"day":3,"hours":16,"minutes":14,"month":11,"nanos":0,"seconds":9,"time":1481098449000,"timezoneOffset":-480,"year":116}所以在取日期的时候需要注意,我现在的做法是取出map中的time字符串,new Date(时间戳)转换为日期
3.Mac下执行mvn clean package docker:build报错
Failed to execute goal com.spotify:docker-maven-plugin:0.2.9:build (default-cli) on project sample-config: Exception caught: java.util.concurrent.ExecutionException: com.spotify.docker.client.shaded.javax.ws.rs.ProcessingException: org.apache.http.conn.HttpHostConnectException: Connect to localhost:2375 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused -> [Help 1]
解决方法:
1).将Spotify plugin升级到0.4.13
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.4.13</version>
<configuration>
<skipDockerBuild>true</skipDockerBuild>
</configuration>
</plugin>
2).在执行mvn命令前先执行命令export DOCKER_HOST=unix:///var/run/docker.sock
4.Quarz多个定时调度实现,只要传入不同的schedulerName就可以创建多个定时任务工厂,每个调度各自启动自己的定时任务sf.getScheduler().start(),互不影响
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
import java.util.Properties;
/**
* @描述: 创建多个定时任务调度
*/
public class MySchedulerUtil {
private static Logger logger = LogManager.getLogger(MySchedulerUtil.class);
public static SchedulerFactory getSchedulerFactory(String schedulerName){
SchedulerFactory sf=null;
Properties props = new Properties();
props.put("org.quartz.scheduler.instanceName", schedulerName);
props.put("org.quartz.threadPool.threadCount", "10");
props.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
try {
sf = new StdSchedulerFactory(props);
} catch (SchedulerException e) {
logger.info(e);
}
return sf;
}
public static SchedulerFactory getSchedulerFactory(){
return new StdSchedulerFactory();
}
public static Scheduler getDefaultScheduler(){
Scheduler s=null;
try {
s=StdSchedulerFactory.getDefaultScheduler();
} catch (SchedulerException e) {
e.printStackTrace();
}
return s;
}
}
5.Spring Boot中aop注解无效
各种尝试感觉aop都无效,最后通过修改了切面处理类的类名问题得以解决,最后得出结论可能是函数名和类名相同让切面类注解@Aspect失效了,我原来类名叫做MergeUser,类中有一个函数mergeUser(),后来将类名修改为MergeUserAspect才让AOP切面生效。
我现在的应用场景是,sql查询经常要用到用户的用户ID,用户身份授权框架使用的是Spring Security
可以直接采用(Map<String,Object> map,@AuthenticationPrincipal MyUser user)这种方法,但是我还是要把user中的USER_ID封装到map中去,大多数函数都需要干这件事,太麻烦了,所以打算使用
自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MergeUser {}
aspect类
import org.MyUser;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Map;
@Component
@Aspect
public class MergeUserAspect {
@Pointcut("@annotation(org.MergeUser)")
public void mergeUser(){}
@Before(value="mergeUser() && args(map)")
public void invokeBefore(Map<String, Object> map){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
HttpSession session = request.getSession();
SecurityContextImpl securityContext=(SecurityContextImpl)session.getAttribute("SPRING_SECURITY_CONTEXT");
if (securityContext!= null) {
MyUser user=(MyUser) securityContext.getAuthentication().getPrincipal();
map.put("USER_ID",user.getUserId());
}
}
}
在Service中使用,也可以在Controller中使用
@MergeUser
public void doSometing(Map<String,Object> map){
//map中已经有了USER_ID用户ID了
}
其中通过HttpServlet获取用户的登录信息也可以使用下面的方法
UsernamePasswordAuthenticationToken userPrincipal=(UsernamePasswordAuthenticationToken)request.getUserPrincipal();
或者Authentication userPrincipal=(Authentication)request.getUserPrincipal();
MyUser user=(MyUser)userPrincipal.getPrincipal();
6.Maven打包时会对resource文件夹中的文件编码,会损坏掉一些文件,我用过的有Excel模板文件,font-awesome字体文件,需要在pom中添加过滤
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/*.xls</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<includes>
<include>**/*.xls</include>
</includes>
</resource>
</resources>
7.bean转map方法,下面的方法要求bean中list是同一种类型,否则会出现部分属性丢失,bean中有map也没有考虑到,暂时用不到就不完善了
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
public static Map<String, Object> transBean2Map(Object obj) {
if (obj == null) {
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
// 过滤class属性
if (!key.equals("class")) {
// 得到property对应的getter方法
Method getter = property.getReadMethod();
Object value = getter.invoke(obj);
if(value instanceof List){
List<Map<String, Object>> subList = new ArrayList<>();
for(Object obj1:(List)value)
{
if(obj1 instanceof Map) {
subList.add((Map)obj1);
}else if(isJavaBean(obj1)){
subList.add(transBean2Map(obj1));
}
}
map.put(key, subList.isEmpty()?value:subList);
}else {
map.put(key, value);
}
}
}
} catch (Exception e) {
System.out.println("transBean2Map Error " + e);
}
return map;
}
//简单的认为只要对象中不只包含get和set方法就是一个bean
public static Boolean isJavaBean(Object o){
boolean flag = false;
Method[] methods = o.getClass().getDeclaredMethods();
for (Method m : methods)
{
String name = m.getName();
//判断javabean中是否只有set/get方法
if (!name.startsWith("set") && !name.startsWith("get")) {
flag = true;
break;
}
}
return flag;
}
8.null+""="null",我们经常在字符串比较的时候"name".equals(null+"")采用这种写法,结果是正确的,但是判断空字符串如果使用StringUtils.isBlank(null+"")结果永远是true,这种写法是错误的,建议用Spring自带的StringUtils.isEmpty(字符串)
9.Integer不能转换为String,如年龄转换为字符串,(String)map.get("age")会报转换错误,但是如果为null1)采用map.get("age").toString()也会报没有该方法的错误2)map.get("age")+""="null",所以在转换的时候需要注意
10.URLEncoder和js的encodeURI得到的编码结果不一致,但在前后端互调的时候并没有问题,但是为什么会不一致呢?如果期望输出一样的值,先用new URI(null,null,value,null).getRawPath()这种写法替代URLEncoder.encode
System.out.println(java.net.URLEncoder.encode("b c","utf8"));//输出b+c
System.out.println(new java.net.URI(null,null,"b c",null).getRawPath());//输出b%20c
10.
java.lang.UnsupportedOperationException
java.lang.ClassCastException: java.util.Arrays$ArrayList cannot be cast to java.util.ArrayList
使用list.retainAll方法的时候发现报不支持的类型,原来就是Arrays.asList生成的List没有实现这个函数
所以一定要注意,解决办法一般就是new ArrayList<>(Arrays.asList(array))这样做一下转换
11.三目运算符,对象的包装类会出现null错误
public static class Bean{
Integer value;
}
Bean b=new Bean();
Integer a= null == b ? 0 : b.value;
上面的代码会出错,需要把0写成 new Integer(0)才行
12.Integer a=100,b=100, a==b,但是Integer a=200,b=200,a!=b,-128-127在Integer中是复用地址的
13. java string.replace(strA,null)会报错
14.Arrays.asList(null) 会报错
15.Comparison method violates its general contract
Collections.sort中比较器中一定要返回0,否则就可能产生这个BUG,图解异常,解决办法
16. i=0;i=i++ 结果i=0 大多数语言都会出现这个问题
解释如下: b = i++的执行步骤是, int tmp = i; i++; b=tmp; 其实也就是说赋值操作是在最后执行的
17. subList和List会相互影响,subList仍然是远List中部分的引用
18. list.parallelStream()是多线程,处理方法时不能使用不支持多线程的方法
不支持的方法有SimpleDateFormat.format,如果使用了的话偶尔会报下面的异常ArrayIndexOutOfBoundsException: 13,解决方法1)static对象取的时候加锁 2)每次都新建一个SimpleDateFormat 3)使用ThreadLocal 4)改用jdk1.8新的时间类 5)使用org.apache.commons.lang3.time.DateFormatUtils;注意org.apache.http.client.utils.DateUtils和org.apache.commons.httpclient.util.DateUtil,看源码是用的软引用实现的,但是该类使用的是美国locale+GMT时区,反正就是会比北京时区要小了8个小时
java.lang.ArrayIndexOutOfBoundsException: 13 at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:453)
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2397)
at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2312)
at java.util.Calendar.setTimeInMillis(Calendar.java:1804)
at java.util.Calendar.setTime(Calendar.java:1770)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:943)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:936)
at java.text.DateFormat.format(DateFormat.java:345)
19.trim()不能去除不间断空格
在解析Excel内容的时候发现一种空格叫做不间断空格,参考文章
1.不间断空格\u00A0,主要用在office中,让一个单词在结尾处不会换行显示,快捷键ctrl+shift+space ;
2.半角空格(英文符号)\u0020,代码中常用的;
3.全角空格(中文符号)\u3000,中文文章中使用;
为了正常解析Excel内容将取得的字符串的不间断空格替换为正常的半角空格," a ".replace("\u00A0"," ")
20.修改系统时间以后,java程序需要重启才能正确处理时间
服务器原来设的时区是EDT,修改为CST后,发现tomcat中的程序获取到的时间仍然是EDT的时间,将tomcat重启才能解决问题
查看SimpleDateFormat的源码:
Locale.getDefault(Locale.Category.FORMAT)中defaultFormatLocale对象都被缓存下来了
private volatile static Locale defaultLocale = initDefault();
private volatile static Locale defaultDisplayLocale = null;
private volatile static Locale defaultFormatLocale = null;
...
public static Locale getDefault(Locale.Category category) {
// do not synchronize this method - see 4071298
switch (category) {
case DISPLAY:
if (defaultDisplayLocale == null) {
synchronized(Locale.class) {
if (defaultDisplayLocale == null) {
defaultDisplayLocale = initDefault(category);
}
}
}
return defaultDisplayLocale;
case FORMAT:
if (defaultFormatLocale == null) {
synchronized(Locale.class) {
if (defaultFormatLocale == null) {
defaultFormatLocale = initDefault(category);
}
}
}
return defaultFormatLocale;
default:
assert false: "Unknown Category";
}
return getDefault();
}
21.变量名第一个字母小写,第二个字母大写,Jackson转为json字符串以后都变成小写了
private String xAxis; 用lombok注解@Data生成的是,getXAxis(); 如果用IDE自带的工具生成的get是getxAxis();
Jackson默认把变量前面连续大写字母全部转换为小写,String AAAb="fun",转为json字符串会变为{"aaab":"fun"}
为了保证json字符串的key和变量名一模一样,如果有第二个字母是大写的变量用IDE自带的工具生成get和set方法
22.float类型数据引起的bug
一个同事用float数据做比较产生了一个bug,发现float=0.6其实数值是比0.6要大的
float a = 0.6;
println(a == 0.6); //false
println(a > 0.6); //true,可以发现浮点数0.6其实比double的0.6要大
println(a == 0.6f); //true
println(a == (float)0.6); //true
println((double)a == (double)0.6); //false
double b = 0.6;
println(b == 0.6); //true
23.java.lang.VerifyError
导致的原因是jdk版本不一致,jar包重复,jar包版本冲突
java.lang.VerifyError: com/aliyun/openservices/log/common/Logs$LogGroup
上面是一个Java1.6的老项目在使用阿里云日志的时候报的错误,排查是因为老项目protobuf-java使用的是2.4.3,把版本修改为2.5.0问题就解决了
24.List多线程处理问题
1.读多写少的情况下多线程建议使用CopyOnWriteArrayList,会牺牲部分内存
2.只有写的情况,synchronizedList写入时加锁,在hbase中我先scan索引表得到了一堆rowId,然后再多线程根据rowId去查询,最后拼接成一个大的List,List list = Collections.synchronizedList(new ArrayList());
另外一种方案是用ConcurrentHashMap写入元素,然后用map.values()得到数组,这种方式理论上在多线程写入的时候效率更高
25.Java字符串长度限制
定义了一个很长的json字符串常量,发现不能编译通过
java的字符串有长度限制,在编译期,要求字符串常量池中的常量不能超过65535,并且在javac执行过程中控制了最大值为65534,2^16-1。
在运行期,长度不能超过Integer.MAX_VALUE的范围,2^31-1
26.FastJson将json字符串序列化成对象的坑
对象中如果有一个有参的构造函数,后续给对象添加一个属性,但是没有加入到构造函数中,json字符串中也增加了这个属性,JSONObject.parseObject解析json串的时候新增加的属性并不会被设置到对象中去
27.Collections.emptyList()没有实现addAll等方法,所以返回空数组的时候建议使用Guaua的Lists.newArrayList()方法
28.Character.isLetter()和Character.isLetterOrDigit()的坑
使用了StringUtils.isAlphanumeric方法来判断是否为纯数字+字母的字符串,发现中文也返回true了, 原来是Character.isLetterOrDigit把中文也当字符判断返回true导致的。
自己仿照StringUtils.isAlphanumeric实现函数来判断
public static boolean isAlphanumeric(String str) {
if (str == null) {
return false;
} else {
int sz = str.length();
for(int i = 0; i < sz; ++i) {
if (!CharUtils.isAsciiAlphanumberic(str.charAt(i))) {
return false;
}
}
return true;
}
}
29.String.split()方法的坑
"--".split("-")得到的结果数组长度是0,查看说明Trailing empty strings are therefore not included in the resulting array,如果期望保留空字符串需要改为"--".split("-", -1)
30.catch(Exception ex)会导致Error被忽略
下面的代码有一个坑,在finally如果把exception是否等于null来判定是否异常,会忽略Error类型的异常
Exception exception = null
try{
// ...don something
} catch(Exception ex) {
exception = ex;
} finally {
if(Objects.nonNull(exception)) {
// 处理异常
} else {
// 处理正常
}
}
31. 正则表达式是通过递归判断的,可能导致java.lang.StackOverflowError
有一段正则校验代码会报栈溢出错误,但是在自己电脑和测试环境没有复现,最后发现是由于线上服务增加了 -Xss512k导致的