Java8中的流式简单总结

在大量使用Java8中的流式操作之后,觉得用起来还挺舒服的,所以正好趁这个机会总结下。

使用Java8的lambda表达式的时候,需要先把集合转为一种流,也就是调用 stream 方法,但是 stream 却是 Collection 类里面的一个方法,也就是只有 Collection 的子类才可以使用,所以 Map 集合是使用不了的,同理,对于数组,可以通过Arrays.stream() 方法来讲数组转为一个Stream,这样也可以使用Stream里面的方法了,

将集合转为流

下面介绍几个很常用的方法来介绍流式操作的便捷性。

1
2
3
4
5
6
List<String> stringList = new ArrayList<>();
stringList.add("a1");
stringList.add("b1");
stringList.add("a2");
stringList.add("b2");
stringList.stream();
阅读更多

JDK1.7中的ConcurrentHashMap实现细节(二)

简介

在JDK1.7向JDK1.8升级的过程中,ConcurrentHashMap由原来的可重入锁和CAS锁直接被替换为synchronized关键字了,虽然说在功能上都是完全一致的,但是在这里一直都有一个疑惑,既然在1.7的使用过程中没什么问题,那到底是出于什么原因要将其替换呢。

JDK1.7中的ConcurrentHashMap

在JDK1.7中,其结构是由一个可重入锁Segment数组和每一个节点下的HashEntry数组来实现的。结构图如下:

由于 segment 是一个锁,所以如果在并发的过程中,多个线程尝试向一个 segment 中的 HashEntry 进行插入的时候,只能有一个线程会获取到锁,其他的线程会被阻塞直至锁被释放,所以这个容器是一个并发安全的。

阅读更多

JDK1.8下ConcurrentHashMap的一些理解(一)

在JDK1.8里面,ConcurrentHashMap在put方法里面已经将分段锁移除了,转而是CAS锁和synchronized

ConcurrentHashMap是Java里面同时兼顾性能和线程安全的一个键值对集合,同属于键值对的集合还有HashTable以及HashMap
HashTable是一个线程安全的类,因为它的所有public方法都被synchronized修饰,这样就导致了一个问题,就是效率太低。

虽然HashMapJDK1.8的并发场景下触发扩容时不会出现成环了,但是会出现数据丢失的情况。
所以如果需要在多线程的情况下(多读少写))使用Map集合的话,ConcurrentHashMap是一个不错的选择。

ConcurrentHashMap在JDK1.8的时候将put()方法中的分段锁Segment移除,转而采用一种CAS锁和synchronized来实现插入方法的线程安全。
如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//省略相关代码
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
//省略相关代码
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
阅读更多

JDK1.7和1.8中的HashMap区别

Jdk1.7和1.8中,HashMap的一些关键点几乎重写了。

主要变更点:

1. hash扰动算法

在jdk1.7的时候,HahMap的hash扰动算法如下:

1
2
3
4
5
6
7
8
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}

阅读更多

Java集合学习之HashSet

简介

在一般的使用中,HashSet经常用于数据的去重,例如我们有一个List,这个List里面有一些重复的数据,于是我们便可以这样操作

1
2
3
4
5
6
7
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("a");
Set<String> set =new HashSet<>();
set.addAll(list);

此时,在Set里面,只会有一个a元素。

底层

阅读更多

java异步IO的回调机制

异步

在Java的nio里面,经常遇到的一个词语是回调,一个主线程负责分发各种请求至子线程,同时子线程处理完毕之后通知主线程,这其中就涉及到了回调机制。

在Java中,异步IO的回调方式主要是CallBackFuture

由于Future获取结果是一种阻塞的方式,所以本次就主要来了解Callback回调方式的运行机制。

由于在异步IO里面,主线程不需要等待子线程来获取结果,所以可以极大的提高程序运行的效率,但是子线程必须在完成之后通知父线程,于时这就引出了回调。

阅读更多

Java的volatile和MESI协议

使用过Java多线程的都知道,volatile可以确保多线程永远都可以读取到最新的变量。但是却无法保证一个操作的原子性
关于这部分可以先从硬件部分说起:

硬件

单核CPU

在现代计算机中,CPU的速度是远远快于内存的,如果不加以任何处理,此时CPU就会一直在等待内存的IO,从而导致一些资源的浪费。所以就有了高速缓冲区(cache),但是在Cache里面肯定是不会将内存的所有数据都拷贝一份,因为cacahe只有几十kb的大小。

这就造成了一个现象,也就是说每一个核心都会有一个共享的内存区域和一个自己Cache区域。

阅读更多

Java通过getResourceAsStream()读取不到文件的原因

首先出现这个原因的时候,需要弄清楚工程目录和编译目录。

工程目录

以IDEA为例,在IDEA里面,我们写代码的地方就是一个工程目录,常见的例如src下面的各种java文件,这种目录就可以称之为一个工程目录,例如如下所示:

工程目录主要存放的是一些配置文件或者一些java文件之类的,而经jvm编译之后的目录便是编译目录了

编译目录

阅读更多

将Jackson替换成Fastjosn

记得有一次的面试是。如何在Spring中将JackSon 替换为 FastJson,emmmm…当时的回答是只需要替换 pom.xml,然后在使用的时候引入FastJosn就行了,但是在当时显然没有理解到面试官的意图,既然面试官强调的是如何替换,那么修改pom.xml很显然不是面试官所想要的答案,那还有什么答案呢?

有一个方法可能是面试官想要的,那就是重写Spring的HttpMesageConverter方法,在这个方法里面引入FastJson的配置,然后替换掉Spring默认的Jackson

替换方式有几种,一种是返回一个HttpMesageConverter,另一种是继承WebMvcConfigurerAdapter 来实现 configureMessageConverters

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
// 输出空置字段
SerializerFeature.WriteMapNullValue,
// list字段如果为null,输出为[],而不是null
SerializerFeature.WriteNullListAsEmpty,
// 数值字段如果为null,输出为0,而不是null
SerializerFeature.WriteNullNumberAsZero,
// Boolean字段如果为null,输出为false,而不是null
SerializerFeature.WriteNullBooleanAsFalse,
// 字符类型字段如果为null,输出为"",而不是null
SerializerFeature.WriteNullStringAsEmpty);

fastJsonConfig.setCharset(Charset.forName("UTF-8"));
fastJsonConfig.setDateFormat("yyyy-MM-dd hh:mm:ss");
FastJsonHttpMessageConverter Converter = new FastJsonHttpMessageConverter();
Converter.setFastJsonConfig(fastJsonConfig);
HttpMessageConverter<?> converter = Converter;
converters.add(0,converter);
super.configureMessageConverters(converters);
}
阅读更多

HashMap得一点总结

HashMapp为什么在Hash的时候减1

在Java的Hashmap中有如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}

上面有一行是 first = tab[(n - 1) & hash]) != null

HashMap为什么在传入另一个Map时加一

阅读更多