均摘选自JDK源码,俺的讲座《Java基础教程-手写JDK》会详细讲解这些知识点,你们不妨围观下:)java
String源码的第985行,equals方法中node
while (n--!= 0) { if (v1[i] != v2[i]) return false; i++; }
这段代码是用于判断字符串是否相等,但有个奇怪地方是用了i--!=0
来作判断,咱们一般不是用i++么?为何用i--呢?并且循环次数相同。缘由在于编译后会多一条指令:segmentfault
i-- 操做自己会影响CPSR(当前程序状态寄存器),CPSR常见的标志有N(结果为负), Z(结果为0),C(有进位),O(有溢出)。i > 0,能够直接经过Z标志判断出来。
i++操做也会影响CPSR(当前程序状态寄存器),但只影响O(有溢出)标志,这对于i < n的判断没有任何帮助。因此还须要一条额外的比较指令,也就是说每一个循环要多执行一条指令。 数组
简单来讲,跟0比较会少一条指令。因此,循环使用i--
,高端大气上档次。缓存
JDK源码在任何方法中几乎都会用一个局部变量来接受成员变量,好比网络
public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length;
由于局部变量初始化后是在该方法线程栈中,而成员变量初始化是在堆内存中,显然前者更快,因此,咱们在方法中尽可能避免直接使用成员变量,而是使用局部变量。ide
在ConcurrentHashMap中,锁segment的操做颇有意思,它不是直接锁,而是相似于自旋锁,反复尝试获取锁,而且在获取锁的过程当中,会遍历链表,从而将数据先加载到寄存器中缓存中,避免在锁的过程当中在便利,同时,生成新对象的操做也是放到锁的外部来作,避免在锁中的耗时操做this
final V put(K key, int hash, V value, boolean onlyIfAbsent) { /** 在往该 segment 写入前,须要先获取该 segment 的独占锁 不是强制lock(),而是进行尝试 */ HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);
scanAndLockForPut()源码编码
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) { HashEntry<K,V> first = entryForHash(this, hash); HashEntry<K,V> e = first; HashEntry<K,V> node = null; int retries = -1; // negative while locating node // 循环获取锁 while (!tryLock()) { HashEntry<K,V> f; // to recheck first below if (retries < 0) { if (e == null) { if (node == null) // speculatively create node //该hash位无值,新建对象,而不用再到put()方法的锁中再新建 node = new HashEntry<K,V>(hash, key, value, null); retries = 0; } //该hash位置key也相同,退化成自旋锁 else if (key.equals(e.key)) retries = 0; else // 循环链表,cpu能自动将链表读入缓存 e = e.next; } // retries>0时就变成自旋锁。固然,若是重试次数若是超过 MAX_SCAN_RETRIES(单核1多核64),那么不抢了,进入到阻塞队列等待锁 // lock() 是阻塞方法,直到获取锁后返回,不然挂起 else if (++retries > MAX_SCAN_RETRIES) { lock(); break; } else if ((retries & 1) == 0 && // 这个时候是有大问题了,那就是有新的元素进到了链表,成为了新的表头 // 因此这边的策略是,至关于从新走一遍这个 scanAndLockForPut 方法 (f = entryForHash(this, hash)) != first) { e = first = f; // re-traverse if entry changed retries = -1; } } return node; }
在判断对象是否相等时,可先用==,由于==直接比较地址,很是快,而equals的话会最对象值的比较,相对较慢,因此有可能的话,能够用a==b || a.equals(b)
来比较对象是否相等spa
transient是用来阻止序列化的,但HashMap源码中内部数组是定义为transient的
/** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
那岂不里面的键值对都没法序列化了么,网络中用hashmap来传输岂不是没法传输,其实否则。
Effective Java 2nd, Item75, Joshua大神提到:
For example, consider the case of a hash table. The physical
representation is a sequence of hash buckets containing key-value
entries. The bucket that an entry resides in is a function of the hash
code of its key, which is not, in general, guaranteed to be the same
from JVM implementation to JVM implementation. In fact, it isn't even
guaranteed to be the same from run to run. Therefore, accepting the
default serialized form for a hash table would constitute a serious
bug. Serializing and deserializing the hash table could yield an
object whose invariants were seriously corrupt.
怎么理解? 看一下HashMap.get()/put()知道, 读写Map是根据Object.hashcode()来肯定从哪一个bucket读/写. 而Object.hashcode()是native方法, 不一样的JVM里多是不同的.
打个比方说, 向HashMap存一个entry, key为 字符串"STRING", 在第一个java程序里, "STRING"的hashcode()为1, 存入第1号bucket; 在第二个java程序里, "STRING"的hashcode()有可能就是2, 存入第2号bucket. 若是用默认的串行化(Entry[] table不用transient), 那么这个HashMap从第一个java程序里经过串行化导入第二个java程序后, 其内存分布是同样的, 这就不对了.
举个例子,好比向HashMap存一个键值对entry, key="方老司", 在第一个java程序里, "方老司"的hashcode()为1, 存入table[1],好,如今传到另外一个在JVM程序里, "方老司" 的hashcode()有可能就是2, 因而到table[2]去取,结果值不存在。
HashMap如今的readObject和writeObject是把内容 输出/输入, 把HashMap从新生成出来.
char在Java中utf-16编码,是2个字节,而2个字节是没法表示所有字符的。2个字节表示的称为 BMP,另外的做为high surrogate和 low surrogate 拼接组成由4字节表示的字符。好比String源码中的indexOf:
//这里用int来接受一个char,方便判断范围 public int indexOf(int ch, int fromIndex) { final int max = value.length; if (fromIndex < 0) { fromIndex = 0; } else if (fromIndex >= max) { // Note: fromIndex might be near -1>>>1. return -1; } //在Bmp范围 if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { // handle most cases here (ch is a BMP code point or a // negative value (invalid code point)) final char[] value = this.value; for (int i = fromIndex; i < max; i++) { if (value[i] == ch) { return i; } } return -1; } else { //不然转到四个字节的判断方式 return indexOfSupplementary(ch, fromIndex); } }
因此Java的char只能表示utf16中的bmp部分字符。对于CJK(中日韩统一表意文字)部分扩展字符集则没法表示。
例如,下图中除Ext-A部分,char均没法表示。
此外还有一种说法是要用char,密码别用String,String是常量(即建立以后就没法更改),会保存到常量池中,若是有其余进程能够dump这个进程的内存,那么密码就会随着常量池被dump出去从而泄露,而char[]能够写入其余的信息从而改变,便是被dump了也会减小泄露密码的风险。
但我的认为你都能dump内存了难道是一个char可以防范的住的?除非是String在常量池中未被回收,而被其它线程直接从常量池中读取,但恐怕也是很是罕见的吧。