目录
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() : 基本类型数据使用快速排序法,对象数组使用归并排序
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面
快速排序思想:
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