markdown Java片段
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown Java片段相关的知识,希望对你有一定的参考价值。
## HashMap在多线程环境下有可能造成CPU满负荷
问题原链接:https://blog.csdn.net/xiaohui127/article/details/11928865 <br>
问题解析链接:https://blog.csdn.net/xiaohui127/article/details/11928865<br>
讲解HashMap源码比较详细的一个博客:https://blog.csdn.net/zxt0601/article/details/77413921
### 来稍微小总结一下HashMap的一些巧妙点
- HashMap的cap为2的n次方(默认为16),主要目的是为了使hash过程更简单(具体详解看后解释一),但也造成了hash不够均匀,
因为一个数除以一个奇数能更分散点,所以为了弥补这个缺点,加入了扰动函数,把求出来的hash值右移16位,
这样就可以充分利用高位的信息,使hash能更分散点(看解释二)。
- 为了得到最接近大于初始容量的2的n次方值,1.7用了while循环,把1一直左移1位直到大于等于给的初始值,
1.8之后进行了优化,把初始容量-1,然后一直右移1位再求或,再右移2位或一下,右移4位或以下,直到16位
这样能把n最高位为1的右边都变成1,然后再加1就刚好是2的n次方。具体看下面的jdk代码:
```
/** JDK 1.7
* Find a power of 2 >= initialCapacity
*/
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
```
```
/** JDK 1.8
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
```
来分析一下,为什么1.8效率更高,假设给的初始容量值是x,然后最接近大于它的2的幂是2^n
1.7需要n次位移和比较操作,所以总计算次数是2*n次
1.8里面是5次位移+5次或运算+1次减法+2次比较+1次加法= 大致14次
因此当容量大于2^7=128的时候,1.8的效率更高。
- 当某一个桶的链表元素大于等于8个的时候就从链表转红黑树结构,这是为了改善get操作,虽然插入删除会
造成维护开销。需要注意的是如果当某个桶的链表长度到达8的时候但总的桶的个数不足64,他会首先尝试resize
而不是直接将链表进行树化。
- 由于桶的长度永远是2的n次幂,这样在扩容的时候也有个好处,求新的index很方便,只要看key的hashCode在
2的n次幂上是0还是1就能知道新的index是多少,要么是原位置index,要么是原index+哈希桶的容量,具体是因为
求index是通过i = (table.length - 1) & hash来进行的,那么为什么有上面的结论就很明显了,举个例子:
原来的桶的容量为16,二进制表示 0000...10000,然后某个key的hash值是30,二进制表示为0000...11110
然后在原来的桶的index为30%16 =(0000...10000 - 1)& 0000...01111 = 0000...01111 & 0000...11110 =
0000...01110 = 14
好然后发生resize,容量变成了32=0000...100000,新的index为(0000...100000 - 1)& 0000...011110 =
0000...0*1*1111 & 0000...0*1*1110 = 0000...11110 = 30 = 14 + 16
看到没,新的index只要看加粗的那一位到底是0还是1就能知道到底是原位还是加上旧桶容量了,所以JDK里通过
*(e.hash & oldCap) 是不是为 0*来判断hash值在那一位是0还是1,自己再好好体会下吧。
```
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
```
- 看了下当树的元素在2-6之间,会转回链表(待求证)
## Java对象占用内存空间的问题
> 问题引用连接:https://blog.csdn.net/u013841764/article/details/51307547
一个Object对象占用空间8byte(待考证,貌似32位的是8字节,64位的是16字节),然后一个对象的引用占用4byte(64位机器也是这么大吗?)
所以Object obj= new Object()占用空间4+8=12byte,因为一般java在对象内存分配的时候都是8的整数倍,
所以就占用16byte的空间大小。这个内存占用是很恐怖的,它是使用基本类型的N倍(N>2),
有些类型的内存占用更是夸张(随便想下就知道了)。因此,可能的话应尽量少使用包装类。
## ArrayList源码解析
> 基于JDK1.8讲的比较细的一篇博客:
https://www.cnblogs.com/sachen/p/6804462.html<br>
这个特别优秀讲的很详细,不过也存在一些小小问题https://blog.csdn.net/zxt0601/article/details/77281231
Arraylist内部的一个并行遍历器ArrayListSpliterator,可以提供并行遍历ArrayList的能力:
https://blog.csdn.net/lh513828570/article/details/56673804
我觉得需要注意的几个地方,一个是内部有个SubList类,通过ArrayList的一个非静态方法subList可以获得
一个SubList对象,按照注解(a view of the portion of this list)和代码来看,得到的SubList对象其实
底层用的还是ArrayList里面的那个Object数组elementData,也就是说对SubList对象进行的修改,同样会影响到
ArrayList对象的数据,反之亦然。
```
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
```
ArrayList的增和删会导致modCount变化,从而会在迭代器遍历的时候触发fail-fast机制。
LinkedList也是一样。
## 关于ThreadLocal类
ThreadLocal用于每个线程都有各自的副本,互不影响,所以ThreadLocal提供一个线程(Thread)局部变量,访问到某个变量的每一个线程都拥有自己的局部变量。
说白了,ThreadLocal就是想在多线程环境下去保证成员变量的安全。
主要的概念是ThreadLocal对象,ThreadLocalMap对象,Thread对象,这几个之间的关系。
使用方法:
```
ThreadLocal<String> localStr = new ThreadLocal<>();
```
这里的localStr就会在不同的线程有自己的局部变量,互不影响。
但要注意,如果各自的副本指向的都是同一个引用对象则ThreadLocal对象就不起作用了(很明显,这种情况下
虽然都有各自的副本,但修改的其实都是同一个对象)。
另外ThreadLocal.ThreadLocalMap内部使用Entry类来存放value,Entry继承WeakReference<ThreadLocal<?>>,
代表Entry类里将ThreadLocal<?>作为弱引用(可以参考第二篇博客),因为Entry弱引用了ThreadLocal对象作为key,因此到时候回收的是
ThreadLocal对象,而非Entry对象,而且当key指向的对象被回收的时候,value很容易内存泄漏。
> 参考博客:
https://www.toutiao.com/a6586218943701058061
https://blog.csdn.net/cl534854121/article/details/80518070
## 关于Escape Analysis (针对Oracle Hotspot JVM)
Oracle的文档是这么说的
> Based on escape analysis, an object's escape state might be one of the following:
- GlobalEscape – An object escapes the method and thread. For example, an object stored in a static field, or, stored in a field of an escaped object, or, returned as the result of the current method.
- ArgEscape – An object passed as an argument or referenced by an argument but does not globally escape during a call. This state is determined by analyzing the bytecode of called method.
- NoEscape – A scalar replaceable object, meaning its allocation could be removed from generated code.<br>
After escape analysis, the server compiler eliminates scalar replaceable object allocations and associated locks from generated code.
The server compiler also eliminates locks for all non-globally escaping objects.
**It does not replace a heap allocation with a stack allocation for non-globally escaping objects.**
意思就是对非全局逃逸对象(ArgEscape和NoEscape)JVM也不会把对象分配到栈里面去,所以网上那些bbJVM会把
对象创建到栈上去的依据从哪来的?
> 具体参考链接:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/performance-enhancements-7.html#escapeAnalysis
## Java内部类(静态和非静态)
根据Oracle的说法,其实静态内部类应该称为静态嵌套类(static nested class),而普通内部类才叫内部类(inner class)。
> Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes.
Non-static nested classes are called inner classes.<br>
详见https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
简单来说:
- 普通内部类(也可以叫成员内部类)可以当成外部类的一个成员,其内部隐含了对外部类对象的引用,所以它
可以访问任何外部类的方法和属性(即时是private的),而外部类要用到内部类的属性必须先创建内部类的实例对象,
由于它是外部类的成员,所以声明使用的时候需要先创建外部类的对象,再通过对象去创建内部类的对象,
比如 Outer.Inner innerObj = new Outer().new Inner(),那么很明显其内部
不允许有static修饰的东西(因为按道理来说static的变量/方法可以通过类名.属性直接来访问,但普通内部类依赖
于外部类对象而存在,所以在创建外部类对象之前无法访问到这个static修饰的属性/方法,那就有矛盾了)
- 静态嵌套类可以当成一个普通的Top-Level的类来对待,只是需要通过new Outer.Inner()来创建静态嵌套类
对象,而且很明显它可以直接访问外部类的静态方法和属性(随便一个Top-Level的类不也这么干的嘛),
但是它不能访问非静态的方法和属性(同理)
以上是关于markdown Java片段的主要内容,如果未能解决你的问题,请参考以下文章