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导致的