目录

1、编码规范

1.1、static和final

1.2、不用map传参

1.3、用jackson代替fastjson

1.4、数组转list最高效的方式

1.5、list转数组

1.6、数组复制

1.7、hashmap指定大小

1.8、ArrayList指定大小

1.9、变量名后跟类型

1.10、常量规则

1.11、无线循环使用for(;;)而不是while(true)

1.12、使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历

1.13、boolean属性不要加is前缀

1.14、包装类之间用equals比较,不用==

1.15、用包装类代替基本类型

1.16、POJO类不要设置属性默认值

1.17、POJO类必须重写toString

1.18、重写equals和hashcode的情况

1.19、集合泛型定义时,在JDK7及以上,使用diamond语法或全省略

1.20、List/Set/Map初始化

2、小知识点

2.1、变量在内存中的存放位置

2.2、既然 JVM 有 Full GC,为什么还会出现 OutOfMemoryError?

2.3、序列化ID有什么用?

2.4、为什么在Java中有关密码的地方更加喜欢使用char[]而不是String

2.5、Integer类有缓存

2.6、BigDecimal一定不会丢失精度吗?

2.7、LinkedList如何实现链表?

2.8、java内部排序算法

2.9、subList与substring

2.10、java transient关键字

2.11、Map的key和value可以为null的情况

2.12、Map kv反转及按value排序

2.13、Mysql、Java按指定顺序排序

2.14、String.format格式化保留两位小数

2.15、BigDecimal指定舍入模式

2.16、解析Xml和Html

2.17、Fastjson序列化为任意复杂类型

2.18、precision和scale(规模)

2.19、正则贪心匹配与Pattern.LITERAL用法

2.20、自定义@标签并解析

2.21、不重写equals和hashcode如何进行对象去重

2.22、利用Java8 Stream进行分组后聚合

2.23、Java实现AES加解密

2.24、基于Apache Druid SQLParser解析SQL语句

2.25、打印GMT字符串


1、编码规范

1.1、static和final

1、public static boolean lock = false

这种写法不规范,有两种改法:

  • public static通常和final一起使用,可改为public static final boolean LOCK = false
  • 改为private static boolean lock = false,并添加get/set方法

2、实例方法里不能直接操作静态变量,而应该通过get/set方法去操作

3、日志变量应该声明为private static final Logger logger

1.2、不用map传参

map作为形参,可读性非常差

1.3、用jackson代替fastjson

  • fastjson虽然快,但快不了多了
  • 代码质量低
  • 在maven仓库的使用数量上,jackson也大于fastjson
  • springmvc默认引入json,最少依赖原则

1.4、数组转list最高效的方式

List<String> list = new ArrayList<>();
String[] arr = {"1","2","3"};
Collections.addAll(list, arr);

1.5、list转数组

用list的toArray方法,list.size一定要等于arr.length

// newArr与参数是同一个对象
String[] newArr = list.toArray(new String[list.size()]);

1.6、数组复制

System.arraycopy方法

1.7、hashmap指定大小

hashmap默认初始化大小为16,默认负载因子为0.75,当容纳超过16*0.75=12个键值对时,hashmap就会进行两倍扩容,扩容后hash数组长度为16*2=32。

当容纳超过32*0.75=24个键值对时,就会再进行扩容,以此类推。

因此,当我们能够提前知道或预估hashmap能存多少键值对时,最好要合理设置初始化大小。

什么是不合理设置呢?比如初始化大小设的太大,会浪费内存空间;如果设的太小,会造成多次扩容的消耗。

1.8、ArrayList指定大小

默认初始化大小是10

1.9、变量名后跟类型

如paramMap,paramList,paramArr

1.10、常量规则

类共享常量,写成private static final
类局部常量,指定final
由于string引用直接指向常量池,故不必非得写成全局常量,如果它共享概率很小,可以写成局部常量

1.11、无线循环使用for(;;)而不是while(true)

从效率上看,while(true)每次循环要判断循环条件,for(;;)循环没有判断,理论上节省机器指令

for(;;)编译之后只有一条指令,而while(true)有好几条

1.12、使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历

keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value

而entrySet只是遍历了一次就把key和value都放到了entry中,效率更高

如果是JDK8,使用Map.foreach方法

1.13、boolean属性不要加is前缀

POJO类中布尔类型的变量,都不要加is前缀

1.14、包装类之间用equals比较,不用==

由于Integer类有缓存,会复用已有对象

1.15、用包装类代替基本类型

POJO类属性使用包装类型

RPC方法的返回值和参数使用包装类型

局部变量使用基本类型

原因:数据库的查询结果可能是null,因为自动拆箱,用基本数据类型接收有NPE风险

1.16、POJO类不要设置属性默认值

POJO类就是纯粹的POJO,不要设置任何属性默认值

1.17、POJO类必须重写toString

如果继承了另一个POJO类,注意在前面加一下super.toString

在方法执行抛出异常时,可以直接调用POJO的toString()方法打印其属性值,便于排查问题

1.18、重写equals和hashcode的情况

因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法

如果自定义对象作为Map的键,那么必须重写hashCode和equals

String重写了hashCode和equals方法,所以我们可以非常愉快地使用String对象作为key来使用

1.19、集合泛型定义时,在JDK7及以上,使用diamond语法或全省略

菱形泛型,即diamond,直接使用<>来指代前边已经指定的类型。
diamond方式:
HashMap<String, String> userCache = new HashMap<>(16);
全省略方式:
ArrayList<User> users = new ArrayList(10);

1.20、List/Set/Map初始化

不要采用双大括号的方式初始化,如下所示:

new ArrayList(){{add("a");add("b");}}
new HashSet(){{add("a");add("b");}}
new HashMap<String,String>(){{put("a","b");put("a","b");}};

外层大括号实际上是匿名内部类的写法,内层大括号实际上是实例代码块。

这种写法不规范会多出额外的内存开销。

建议使用google guava库初始化:

Lists.newArrayList("a","b");
Sets.newHashSet("a","b");
// 不可变的Map,数据一旦确定,就不能增删
ImmutableMap.<String, String>builder().put("a", "b").put("a", "b").build()
HashMap<Integer, String> map = Maps.newHashMap(ImmutableMap.of(1, "张三", 2, "李四"));

2、小知识点

2.1、变量在内存中的存放位置

局部变量
    基本类型:引用和值都放在栈上
    对象类型:引用放在栈上,对象放在堆上
全局实例变量:
    基本类型:引用和值都放在堆上
    对象类型:引用和对象都放在堆上
全局静态变量:
    基本类型:引用和值放在堆上
    对象类型:引用和值放在堆上
java7:常量池和类的静态变量放在方法区
java8:常量池和类的静态变量放在堆上

2.2、既然 JVM 有 Full GC,为什么还会出现 OutOfMemoryError?

对于强引用,当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题
为什么我请了佣人来收拾房间,我的房间还是会堆满?那我还请佣人来干什么?他不是号称能把我房间里的垃圾都清理干净的么?
问题是如果您房间里堆的都是宝贝(或者看起来都是宝贝)的话,佣人也没辙

2.3、序列化ID有什么用?

序列化ID起着关键的作用,它决定着是否能够成功反序列化!

简单来说,java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。

在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常
如何定义:private static final long serialVersionUID = 8965308904607290970L;
如果没有显示定义:当我们一个实体类中没有显示的定义一个名为serialVersionUID、类型为long的变量时,Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID

2.4、为什么在Java中有关密码的地方更加喜欢使用char[]而不是String

String是不可变的,这也意味着在GC之前,你对这些数据不能做任何处理。因此,只要有人能够访问你的内存,那么String就有可能被他获取到。这也就是为什么要使用char数组。你可以显示地清除数据或者覆盖它。这样密码这种敏感数据即使GC还没有进行也不会再在系统留下痕迹

2.5、Integer类有缓存

public static void main(String[] args){  
   Integer a = 100;  
   Integer b = 100;  
   Integer c = 200;  
   Integer d = 200;  
   System.out.println(a==b);  
   System.out.println(c==d);  
}

上面的代码竟然输出:

true  
false

这确实太出乎意料了,同样的代码,只是数值不同(而且差别不太大的样子),就产生了不一样的输出,这也太离谱了。

原来,Integer中有一个静态内部类IntegerCache,在类加载的时候,它会把[-128, 127]之间的值缓存起来,而Integer a = 100这样的赋值方式,会首先调用Integer类中的静态valueOf方法,这个方法会尝试从缓存里取值,如果在这个范围之内就不用重新new一个对象了:

public static Integer valueOf(int i) {  
   if (i >= IntegerCache.low && i <= IntegerCache.high)  
       return IntegerCache.cache[i + (-IntegerCache.low)];  
   return new Integer(i);  
}

2.6、BigDecimal一定不会丢失精度吗?

//会丢失精度
        double a = 2.0;
        double b = 1.1;
        System.out.println(a-b);

        //会丢失精度
        BigDecimal b1 = new BigDecimal(2.0);
        BigDecimal b2 = new BigDecimal(1.1);
        BigDecimal res = b1.subtract(b2);
        System.out.println(res.doubleValue());

        //不会丢失精度
        BigDecimal b3 = new BigDecimal("2.0");
        BigDecimal b4 = new BigDecimal("1.1");
        BigDecimal res1 = b3.subtract(b4);
        System.out.println(res1.doubleValue());

        //不会丢失精度
        BigDecimal b5 = BigDecimal.valueOf(2.0);
        BigDecimal b6 = BigDecimal.valueOf(1.1);
        BigDecimal res2 = b5.subtract(b6);
        System.out.println(res2.doubleValue());

注意,要用BigDecimal类的BigDecimal(String)构造方法,或使用valueOf方法

2.7、LinkedList如何实现链表?

LinkedList有内部类Node 

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

2.8、java内部排序算法

Colletions.sort采用归并排序

Arrays.sort() : 基本类型数据使用快速排序法,对象数组使用归并排序

Java软件编码规范下载_Java软件编码规范下载

Java软件编码规范下载_Java软件编码规范下载_02

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。

不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面

快速排序思想:

Java软件编码规范下载_初始化_03

2.9、subList与substring

取值都是左闭右开,即[ )

2.10、java transient关键字

让被transient关键字修饰的成员属性变量不被序列化

2.11、Map的key和value可以为null的情况

ConcurrentHashMap和Hashtable的key和value都不允许为null。

TreeMap允许value为null,key不允许为null。HashMap允许key和value都可以为null

2.12、Map kv反转及按value排序

// MapUtils要引包Apache common-collections
Map<Integer, String> treeMap = new TreeMap<>(MapUtils.invertMap(map));

2.13、Mysql、Java按指定顺序排序

order by FIELD(frate_range,'<24%','24%','>24%'),FIELD(fmax_fq_num_range,'[1,3]','[6,9]','[12,15]','[18,24]','[36,+∞]','空值')

对于Java,可以用一个Map去维护元素与其对应的分值,当要将一组无序元素排序时,在compartor中比较两个元素时实际上是比较它们的分数。

2.14、String.format格式化保留两位小数

String s = String.format("%.2f%%", 99.1234567); // 如果是字符串则会报错

2.15、BigDecimal指定舍入模式

做除法时,要指定舍入模式,否则会报错:Non-terminating decimal expansion; no exact representable decimal result

比如1/3,是无限小数,如果不指定舍入模式肯定会报错。还可以指定保留多少位小数

BigDecimal b1 = BigDecimal.valueOf(100);
BigDecimal b2 = BigDecimal.valueOf(3);
System.out.println(b1.divide(b2,2, RoundingMode.HALF_UP));// HALF_UP与HALF_DOWN的区别是,前者遇5进1

HALF_EVEN的意思是:向最接近的数字舍入,如果距离相同,则向最接近的偶数舍入

在保留两位小数情况下:

  • 1.243,那么3自然是丢弃掉,输出1.24
  • 1.248,那么8自然是要进1,输出1.25
  • 1.245,5正好在中间位置,那么选择最接近的偶数,最接近的偶数就是4而不是6,那么输出1.24而不是1.26
  • 1.255,5正好在中间位置,那么选择最接近的偶数,最接近的偶数就是6而不是4,那么输出1.26而不是1.24

2.16、解析Xml和Html

虽然xml和html很相似,但两种仍然是不一样的东西,在具体解析时需要用到不同的框架。

解析xml可以用dom4j,而解析html可以用jsoup。

引入依赖:

<dependency>
	<groupId>org.dom4j</groupId>
	<artifactId>dom4j</artifactId>
	<version>2.1.1</version>
</dependency>
<dependency>
	<groupId>org.jsoup</groupId>
	<artifactId>jsoup</artifactId>
	<version>1.11.2</version>
</dependency>

编码:

// 解析xml
SAXReader reader=new SAXReader();
Document doc = reader.read(new File("你的xml文件路径"));
// 或者 Document doc = reader.read(new StringReader("你的xml文件内容"));
// 获取根元素
Element root = doc.getRootElement();
// 获取某个元素的属性
root.element("元素名").element("元素名").attribute("属性名").getValue()
// 打印element,XmlUtil是hutool里的工具类
org.w3c.dom.Document document = XmlUtil.parseXml(xml);
String s = XmlUtil.toStr(document);

// 解析html
File input = new File("你的html文件路径");
Document doc = Jsoup.parse(input, "UTF-8");
Elements ele = doc.getElementsByAttributeValue("class", "form-control");
ListIterator<Element> iterator = ele.listIterator();
while (iterator.hasNext()){
    Element next = iterator.next();
    System.out.println(next.attr("name"));
    System.out.println(next.hasAttr("required"));
    System.out.println(next.attr("value"));
}

2.17、Fastjson序列化为任意复杂类型

Map<String,List<UserBean>> map= 
    JSONObject.parseObject(source,
        new TypeReference<Map<String,List<UserBean>>>() {});

2.18、precision和scale(规模)

分别为:有效数字个数(精度)、小数个数

BigDecimal a = BigDecimal.valueOf(12.345);
System.out.println(a.precision());  // 5
System.out.println(a.scale());  // 3
a = a.setScale(2,RoundingMode.HALF_UP);
System.out.println(a.doubleValue());  // 12.35

2.19、正则贪心匹配与Pattern.LITERAL用法

Pattern.matches方法是匹配整个字符串。

String sourceStr = "select name,--\\n注释1\nage,---注释2\nsex from stu";
System.out.println("源字符串:\n"+sourceStr);
Pattern p = Pattern.compile("(--.*)\n");
Matcher m = p.matcher(sourceStr);
while (m.find()){
    System.out.println("匹配:\n"+m.group(1));
    // 方式1
    // sourceStr = sourceStr.replaceFirst(m.group(1),"");
    // 方式2
    sourceStr = Pattern.compile(m.group(1), Pattern.LITERAL).matcher(sourceStr).replaceFirst("");
    System.out.println("替换后:\n"+sourceStr);
}

2.20、自定义@标签并解析

问大家一个问题
<label mid='10001' min='小明' op='@' style='color:blue'>@小明</label>
你的身高是多少?
<label mid='10002' min='小红' op='@' style='color:blue'>@小红</label>
你的体重是多少?

前端将展示为:

问大家一个问题@小明你的身高是多少?@小红你的体重是多少?

然后将整个文本输送到后端解析。

后端先用正则表达式提取标签,然后再用dom4j解析标签属性。

SAXReader reader = new SAXReader();
Element root = reader.read(new StringReader(tag)).getRootElement();
root.attribute("id").getValue();
root.attribute("name").getValue();

2.21、不重写equals和hashcode如何进行对象去重

Arrays.asList(userBean1, userBean2,userBean3).stream().collect(
    Collectors.collectingAndThen(
        Collectors.toCollection(() -> new TreeSet<UserBean>((u1,u2)->{
            if(u1.getName().compareTo(u2.getName()) != 0){
                return u1.getName().compareTo(u2.getName());
            }
            if(u1.getAge().compareTo(u2.getAge()) != 0){
                return u1.getAge().compareTo(u2.getAge());
            }
               
            return 0;
        })),
        ArrayList::new
    )
)

这里利用了TreeSet自定义比较器+不重复的特性,将对象进行了去重。

比较器的逻辑为:对UserBean里的属性进行逐个比较,要求每个属性都重写了compareTo方法。

2.22、利用Java8 Stream进行分组后聚合

// 构造数据
List<Map<String,String>> list = new ArrayList<>();
list.add(Maps.newHashMap(ImmutableMap.of("name","李四","age","40")));
list.add(Maps.newHashMap(ImmutableMap.of("name","张三","age","20")));
list.add(Maps.newHashMap(ImmutableMap.of("name","张三","age","10")));
list.add(Maps.newHashMap(ImmutableMap.of("name","李四","age","50")));
list.add(Maps.newHashMap(ImmutableMap.of("name","张三","age","30")));

// 分组后聚合,groupingBy的三个参数分别为:分组的key、MapFactory、对分组后同一组的数据做处理
TreeMap<String, Optional<Map<String, String>>> map = list.stream().collect(
        Collectors.groupingBy(e -> e.get("name"), TreeMap::new, Collectors.minBy(Comparator.comparing(a -> Integer.valueOf(a.get("age")))
)));

// 分组后不聚合
// value不聚合,转成JSONArray,再转成json字符串
Map<String, String> dataMap = list.stream().collect(Collectors.groupingBy(e -> e.get("name"), Collectors.collectingAndThen(Collectors.toCollection(JSONArray::new), e -> JSONArray.toJSONString(e))));

// value不聚合,保持原样List<Map<String,String>>
Map<String, List<Map<String,String>>> dataMap2 = list.stream().collect(Collectors.groupingBy(e -> e.get("name"), Collectors.collectingAndThen(Collectors.toCollection(ArrayList::new), e -> e)));

// value不聚合,只提出age字段List<String>     
Map<String, List<String>> dataMap3 = list.stream().collect(Collectors.groupingBy(e -> e.get("name"), HashMap::new, Collectors.mapping(e -> e.get("age"), Collectors.toList())));

2.23、Java实现AES加解密

生成密钥:

public static byte[] key() throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
    // 比普通Random更安全,可传入种子,保证每次生成的密钥相同。或者指定固定的密钥位数从而不用random
    //SecureRandom random = new SecureRandom();
    keyGenerator.init(128);
    // 16字节
    SecretKey secretKey = keyGenerator.generateKey();
    byte[] encoded = secretKey.getEncoded();
    System.out.println(Base64.getEncoder().encodeToString(encoded));
    return encoded;
}

加密:

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE,new SecretKeySpec(Base64.getDecoder().decode(KEY),"AES"));
byte[] encryptBytes = cipher.doFinal(text.getBytes());
String encryptText = Base64.getEncoder().encodeToString(encryptBytes);
System.out.println(encryptText);

解密:

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE,new SecretKeySpec(Base64.getDecoder().decode(KEY),"AES"));
byte[] encryptBytes = Base64.getDecoder().decode(payload);
byte[] decryptBytes = cipher.doFinal(encryptBytes, 0, encryptBytes.length);
String text = new String(decryptBytes);

2.24、基于Apache Druid SQLParser解析SQL语句

首先,引入依赖。

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.18</version>
  <scope>compile</scope>
</dependency>

解析SQL语句中的表。

SQLStatementParser parser = new MySqlStatementParser(sql);
// 使用Parser解析生成AST,这里SQLStatement就是AST
SQLStatement sqlStatement = parser.parseStatement();

MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
sqlStatement.accept(visitor);
Map<TableStat.Name, TableStat> tables = visitor.getTables();
List<String> allTableName = new ArrayList<>();
for (TableStat.Name t : tables.keySet()) {
    allTableName.add(t.getName());
}

解析SQL中的where条件。

SQLStatementParser parser = new MySqlStatementParser(sql);
SQLStatement sqlStatement = parser.parseStatement();
SQLSelect sqlSelect = (SQLSelect)sqlStatement.getChildren().get(0);
MySqlSelectQueryBlock query = (MySqlSelectQueryBlock)sqlSelect.getQuery();
SQLExpr where = query.getWhere();
String sqlCondition = where.toString();

2.25、打印GMT字符串
 

SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
System.out.println(format.format(new Date())); // Thu, 20 Apr 2023 02:47:33 GMT