今天看了下阿里巴巴java开发手册,对其中在工作未注意到的点做了一下整合:
1、对于
Integer var
= ?
在
-128
至
127
范围内的赋值,
Integer
对象是在
IntegerCache
.
cache
产生,会复用已有对象,这个区间内的
Integer
值可以直接使用
==
进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用
equals
方法进行判断。
2、循环体内,字符串的连接方式,使用
StringBuilder
的
append
方法进行扩展。 说明:反编译出的字节码文件显示每次循环都会
new
出一个
StringBuilder
对象,然后进行
append
操作,最后通过
toString
方法返回
String
对象,造成内存资源浪费。
反例:
String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}
(在平时经常就是对字符串进行"+"操作,未考虑内存资源问题)
3、如果自定义对象做为
Map
的键,那么必须重写
hashCode
和
equals
。 说明:
String
重写了
hashCode
和
equals
方法,所以我们可以非常愉快地使用
String
对象作为
key
来使用。
(原因:HashMap中,如果要比较key是否相等,要同时使用这两个函数!因为自定义的类的hashcode()方法继承于Object类,其hashcode码为默认的内存地 址,这样即便有相同含义的两个对象,比较也是不相等的)
4、使用集合转数组的方法,必须使用集合的
toArray(T[] array)
,传入的是类型完全一样的数组,大小就是
list
.
size()
。
说明:使用
toArray
带参方法,入参分配的数组空间不够大时,
toArray
方法内部将重新分配内存空间,并返回新数组地址
;
如果数组元素大于实际所需,下标为
[ list
.
size() ]
的数组元素将被置为
null
,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。 正例:
List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);
反例:直接使用
toArray
无参方法存在问题,此方法返回值只能是
Object[]
类,若强转其它类型数组将出现
ClassCastException
错误。
4、使用工具类
Arrays
.
asList()
把数组转换成集合时,不能使用其修改集合相关的方法,它的
add
/
remove
/
clear
方法会抛出
UnsupportedOperationException
异常。 说明:
asList
的返回对象是一个
Arrays
内部类,并没有实现集合的修改方法。
Arrays
.
asList
体现的是适配器模式,只是转换接口,后台的数据仍是数组。
String[] str = new String[] { "you", "wu" }; List list = Arrays.asList(str);
第一种情况:
list.add("yangguanbao");
运行时异常。 第二种情况:
str[0] = "gujin";
那么
list.get(0)
也会随之修改。
(需要注意)
5、泛型通配符
<?
extends T
>
来接收返回的数据,此写法的泛型集合不能使用
add
方法,而
<? super T>
不能使用
get
方法,做为接口调用赋值时易出错。 说明:扩展说一下
PECS(Producer Extends Consumer Super)
原则:第一、频繁往外读取内容的,适合用
<?
extends T
>
。第二、经常往里插入的,适合用
<? super T>
。
6、集合初始化时,指定集合初始值大小。 说明:
HashMap
使用
HashMap(int initialCapacity)
初始化,
正例:
initialCapacity
=
(
需要存储的元素个数
/
负载因子
) + 1
。注意负载因子(即
loader factor
)默认为
0.75
,
如果暂时无法确定初始值大小,请设置为
16
(即默认值)。
反例:
HashMap
需要放置
1024
个元素,由于没有设置容量初始大小,随着元素不断增加,容量
7
次被迫扩大,
resize
需要重建
hash
表,严重影响性能。
(在项目的监控服务数据处理过程中,由于数据量庞大,未初始化集合大小,导致底层数据数次扩容,效率降低,导致quart调度器部分任务出现延后执行的情况,在处理最大值及最小值时,对集合采用remove方法,也是效率偏低的一个原因)
7、使用
entrySet
遍历
Map
类集合
KV
,而不是
keySet
方式进行遍历。 说明:
keySet
其实是遍历了
2
次,一次是转为
Iterator
对象,另一次是从
hashMap
中取出
key
所对应的
value
。而
entrySet
只是遍历了一次就把
key
和
value
都放到了
entry
中,效率更高。如果是
JDK
8
,使用
Map
.
foreach
方法。 正例:
values()
返回的是
V
值集合,是一个
list
集合对象
;
keySet()
返回的是
K
值集合,是一个
Set
集合对象
;
entrySet()
返回的是
K
-
V
值组合集合。
(lambda表达式之前一直接触过,学习了相应的Map变量方式,确实简洁不少
//map遍历
Map<String,String> map = new HashMap<String,String>();
map.forEach((( id, val) -> print(val)));
)
8、【强制】线程池不允许使用Executors去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写同学更加明确线程池运行规则,避资源耗尽风险。 说明:Executors返回的线程池对象 返回的线程池对象 的弊端 如下 : 1)FixedThreadPool和 SingleThreadPoolPool Pool: 允许的请求队列长度为 Integer.MAX_VALUE,可 能会堆积大量的请求,从而导致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
(
云校园项目采用Executors去创建线程,以后尽量少使用)
8、并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用
version
作为更新依据。 说明:如果每次访问冲突概率小于
20%
,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于
3
次。
(监控服务,使用postgres数据库,由于并发,两个节点同时创建数据库表,导致事务出现了问题,后续数据插入不进去,进而导致数据丢失,可采用重试插入的方式来解决)
9、多线程并行处理定时任务时,
Timer
运行多个
TimeTask
时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用
ScheduledExecutorService
则没有这个问题。
(项目都是分布式,用的是quart调度器,以后单点环境时需注意)
10、volatile
解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。如果是
count
++
操作,使用如下类实现:
AtomicInteger count
=
new AtomicInteger(); count
.
addAndGet(
1
);
如果是
JDK
8
,推荐使用
LongAdder
对象,比
AtomicLong
性能更好
(
减少乐观锁的重试次数
)
。
(学习了处理变量同步的又一方式)