synchronized
来保证并发安全 。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树 。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))
synchronized
只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍 。
Java集合使用注意事项总结这篇文章根据《阿里巴巴 Java 开发手册》总结了关于集合使用常见的注意事项以及其具体原理 。
强烈建议小伙伴们多多阅读几遍,避免自己写代码的时候出现这些低级的问题
1. 集合判空《阿里巴巴 Java 开发手册》的描述如下:
判断所有集合内部的元素是否为空,使用这是因为isEmpty()
方法 , 而不是size()==0
的方式 。
isEmpty()
方法的可读性更好,并且时间复杂度为 O(1) 。绝大部分我们使用的集合的
size()
方法的时间复杂度也是 O(1),不过,也有很多复杂度不是 O(1) 的,比如 java.util.concurrent
包下的某些集合(ConcurrentLinkedQueue
、ConcurrentHashMap
...) 。下面是
ConcurrentHashMap
的 size()
方法和 isEmpty()
方法的源码 。public int size() {long n = sumCount();return ((n < 0L) ? 0 :(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int)n);}final long sumCount() {CounterCell[] as = counterCells; CounterCell a;long sum = baseCount;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum;}public boolean isEmpty() {return sumCount() <= 0L; // ignore transient negative values}
2. 集合转 Map《阿里巴巴 Java 开发手册》的描述如下:在使用java.util.stream.Collectors
类的toMap()
方法转为Map
集合时,一定要注意当 value 为 null 时会抛 空指针异常 。
class Person {private String name;private String phoneNumber;// getters and setters}List<Person> bookList = new ArrayList<>();bookList.add(new Person("jack", "18163138123"));bookList.add(new Person("martin", null));// 空指针异常bookList.stream().collect(Collectors.toMap(Person::getName,Person::getPhoneNumber));
下面我们来解释一下原因 。首先,我们来看
java.util.stream.Collectors
类的 toMap()
方法 ,可以看到其内部调用了 Map
接口的 merge()
方法 。public static <T,K, U,M extends Map<K , U>>Collector<T,?,M> toMap(Function<? super T,? extends K> keyMapper,Function<? super T,? extends U> valueMapper,BinaryOperator<U> mergeFunction,Supplier<M> mapSupplier) {BiConsumer<M,T> accumulator= (map,element) -> map.merge(keyMapper.apply(element),valueMapper.apply(element),mergeFunction);return new CollectorImpl<>(mapSupplier , accumulator,mapMerger(mergeFunction),CH_ID);}
Map
接口的 merge()
方法如下 , 这个方法是接口中的默认实现 。如果你还不了解 Java 8 新特性的话,请看这篇文章:《Java8 新特性总结》(opens new window)。
default V merge(K key,V value , BiFunction<? super V,? super V, ? extends V> remappingFunction) {Objects.requireNonNull(remappingFunction);Objects.requireNonNull(value);V oldValue = https://www.huyubaike.com/biancheng/get(key);V newValue = (oldValue == null) ? value :remappingFunction.apply(oldValue,value);if(newValue == null) {remove(key);} else {put(key, newValue);}return newValue;}
merge()
方法会先调用 Objects.requireNonNull()
方法判断 value 是否为空 。public static <T> T requireNonNull(T obj) {if (obj == null)throw new NullPointerException();return obj;}
解决方案使用Java8提供的Optional
类优雅地处理空指针问题//如果值为null则赋默认值空串Map<String, String> collect = bookList.stream().collect(Collectors.toMap(Person::getName, t -> Optional.ofNullable(t.getPhoneNumber()).orElse("")
3. 集合遍历《阿里巴巴 Java 开发手册》的描述如下:不要在 foreach 循环里进行元素的通过反编译你会发现 foreach 语法糖底层其实还是依赖remove/add
操作 。remove 元素请使用Iterator
方式,如果并发操作,需要对Iterator
对象加锁 。
Iterator
。不过 , remove/add
操作直接调用的是集合自己的方法,而不是
推荐阅读
- Java单例模式,看这一篇就够了
- 微信支付v3接口的 官方 Java SDK
- Java函数式编程:二、高阶函数,闭包,函数组合以及柯里化
- 夯实Java基础,一篇文章全解析线程问题
- 四 Java多线程-ThreadPool线程池-2
- 生成器函数 javascript异步编程之generator与asnyc/await语法糖
- Java Timer使用介绍
- 三 Java多线程-ThreadPool线程池
- 二 Java多线程-线程关键字
- 二 Java 编码那些事