-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
147 lines (103 loc) · 95.4 KB
/
search.xml
File metadata and controls
147 lines (103 loc) · 95.4 KB
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[JDK1.8源码之ConcurrentHashMap]]></title>
<url>%2Fposts%2Fjava%2FJDK1.8%E6%BA%90%E7%A0%81%E4%B9%8BConcurrentHashMap.html</url>
<content type="text"><![CDATA[什么是ConcurrentHashMap以下观点都是建立在JDK1.8之上。 我们知道HashMap是非线程安全的,所以JDK给我们提供了几种线程安全的Map,HashTable,Collections.SynchronizedMap,ConcurrentHashMap几种线程安全的Map来使用。HashTable是每个方法都是synchronized修饰的。Collections.SynchroinzedMap则是用一个Object来当锁,每个方法里都使用synchronized(obj)来锁定。 这里没有理解以上的2种方法有什么区别,HashTable是作用在方法上的,所以锁的是this对象,后者是锁的Object,有什么区别吗? 最后一种ConcurrentHashMap则是使用了最新的锁技术来实现的。 实现原理源码实现变量123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101// 最大的阀值 private static final int MAXIMUM_CAPACITY = 1 << 30; // 如果不指定长度,默认的长度 private static final int DEFAULT_CAPACITY = 16; // 数组最大长度 static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // private static final int DEFAULT_CONCURRENCY_LEVEL = 16; //与HashMap一样,负载因子也是0.75 当数组中有75%都有数据时就进行数组扩容 private static final float LOAD_FACTOR = 0.75f; // 当数组单个位置链表长度超过此值之后,会修改为树结构 static final int TREEIFY_THRESHOLD = 8; //当树结构节点小于6时,会修改为链表结构 static final int UNTREEIFY_THRESHOLD = 6; /** * The smallest table capacity for which bins may be treeified. * (Otherwise the table is resized if too many nodes in a bin.) * The value should be at least 4 * TREEIFY_THRESHOLD to avoid * conflicts between resizing and treeification thresholds. */ static final int MIN_TREEIFY_CAPACITY = 64; /** * Minimum number of rebinnings per transfer step. Ranges are * subdivided to allow multiple resizer threads. This value * serves as a lower bound to avoid resizers encountering * excessive memory contention. The value should be at least * DEFAULT_CAPACITY. */ private static final int MIN_TRANSFER_STRIDE = 16; /** * The number of bits used for generation stamp in sizeCtl. * Must be at least 6 for 32bit arrays. */ private static int RESIZE_STAMP_BITS = 16; /** * The maximum number of threads that can help resize. * Must fit in 32 - RESIZE_STAMP_BITS bits. */ private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1; /** * The bit shift for recording size stamp in sizeCtl. */ private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; /* * Encodings for Node hash fields. See above for explanation. */ static final int MOVED = -1; // hash for forwarding nodes static final int TREEBIN = -2; // hash for roots of trees static final int RESERVED = -3; // hash for transient reservations static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash // cpu数量 static final int NCPU = Runtime.getRuntime().availableProcessors(); /** * The array of bins. Lazily initialized upon first insertion. * Size is always a power of two. Accessed directly by iterators. */ transient volatile Node<K,V>[] table; /** * The next table to use; non-null only while resizing. */ private transient volatile Node<K,V>[] nextTable; /** * Base counter value, used mainly when there is no contention, * but also as a fallback during table initialization * races. Updated via CAS. */ private transient volatile long baseCount; // 阀值用来控制数组是否要扩容,-1时表示正在初始化,-(1+n表示有几个线程在操作) private transient volatile int sizeCtl; /** * The next table index (plus one) to split while resizing. */ private transient volatile int transferIndex; /** * Spinlock (locked via CAS) used when resizing and/or creating CounterCells. */ private transient volatile int cellsBusy; /** * Table of counter cells. When non-null, size is a power of 2. */ private transient volatile CounterCell[] counterCells; UnSafe相关123456789101112131415161718192021222324252627282930313233343536private static final sun.misc.Unsafe U;private static final long SIZECTL;private static final long TRANSFERINDEX;private static final long BASECOUNT;private static final long CELLSBUSY;private static final long CELLVALUE;private static final long ABASE;private static final int ASHIFT;static { try { U = sun.misc.Unsafe.getUnsafe(); Class<?> k = ConcurrentHashMap.class; //获取sizeCtl变量在内存中的偏移量 SIZECTL = U.objectFieldOffset (k.getDeclaredField("sizeCtl")); TRANSFERINDEX = U.objectFieldOffset (k.getDeclaredField("transferIndex")); BASECOUNT = U.objectFieldOffset (k.getDeclaredField("baseCount")); CELLSBUSY = U.objectFieldOffset (k.getDeclaredField("cellsBusy")); Class<?> ck = CounterCell.class; CELLVALUE = U.objectFieldOffset (ck.getDeclaredField("value")); Class<?> ak = Node[].class; ABASE = U.arrayBaseOffset(ak); int scale = U.arrayIndexScale(ak); if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); } catch (Exception e) { throw new Error(e); }} 构造方法1234567891011121314public ConcurrentHashMap(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException(); /** 这里直接判断了如果指定的值大于了最大长度的1/2,就直接等于最大值了 * 这里的 MAXIMUM_CAPACITY >>> 1 等于 MAXIMUM_CAPACITY / 2 * initialCapacity + (initialCapacity >>> 1) + 1 * 这里我没明白为什么要做这些操作,可能是为了求出在做散列时 * 更合适的值 */ int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); this.sizeCtl = cap;} 构造方法都很类似,这里我只举例一个。 put方法123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException(); //根据key的hashcode再次求hash int hash = spread(key.hashCode()); int binCount = 0; //开始操作数组 这里是个死循环 会在插入数据成功后break掉 for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; //数组并没有在构造方法里初始化,所以在第一次put的时候,会初始化数组 if (tab == null || (n = tab.length) == 0) //这里我理解的是这样的,因为外层是个死循环,所以第一次执行到这的时候会进行初始化 //然后就进入了下一次循环,因为已经初始化完毕了,所以就会进入别的分支判断 tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { /** 进入这个判断的条件是在数组下标位置上没有节点,就证明是个新节点可以直接插入 * 所以就进入了这层,在这层插入的时候,因为只用了一个if判断是用的cas操作 * 所以有可能会失败,当失败时,还是因为外层是个死循环,所以会一直执行这个插入 * 直到插入成功,break掉 * 并且f 和i 也顺便在这里进行了赋值 i等于数组长度-1 与上 hash * f 等于 数组 i 位置上的元素 * * / 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 { /** * 到这个判断,就证明数组下标位置现在是有节点的,所以会有2种操作,链表和树的操作 * 首先在这里锁住了首节点 */ V oldVal = null; synchronized (f) { //由与上面f 和 i 已经赋值过了,所以这里再次进行了确认是否是同一个对象 if (tabAt(tab, i) == f) { if (fh >= 0) { binCount = 1; // 以下这个循环是对链表进行循环 for (Node<K,V> e = f;; ++binCount) { K ek; // 如果hash值 和key 完全相等,就证明是同一个对象 // 如果没有设置替换新值到这里就结束了 // 如果设置了就替换新值,并且结束 if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } //这里是循环完整个链表都没有发现有相等的key //直接在链表最后插入新的节点,并且结束 Node<K,V> pred = e; if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value, null); break; } } } else if (f instanceof TreeBin) { Node<K,V> p; binCount = 2; //这里判断,如果节点是树类型的,则把节点放入到树结构中 //同链表一样,如果设置了需要覆盖新值,更新一下 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } //binCount 在树中是写死的2 在链表中会随着操作的增加而增加 if (binCount != 0) { //如果超过了设置的值,就需要把链表转换成树结构 if (binCount >= TREEIFY_THRESHOLD) //转树操作,但里面还会有扩容 treeifyBin(tab, i); // 因为上边的操作存储了oldVal,如果不为空直接返回此值,结束 if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; } put方法总结: 存放对象的时候,第一次操作,会先初始化底层的数组对象,然后再进行后续的操作在存放元素的时候有这么几种情况:1.如果计算出的hash值与上数组长度得出的下标位置为null,则可以直接插入2.如果当前元素的hash值为MOVED,就证明有线程在进行扩容,就帮助一起扩容3.都没有的情况下,证明当前下标存在其他元素,则进入元素追加,如果是链表就进行链表的操作,如果是树,就进行树的操作,最后添加元素完成。4.添加完元素,如果需要进行树的转换,进行转换。5.增加计数器,完成所有操作。 treeifyBin方法 123456789101112131415161718192021222324252627282930private final void treeifyBin(Node<K,V>[] tab, int index) { Node<K,V> b; int n, sc; if (tab != null) { //如果当前数组长度小于64,会直接扩容2倍,而不把当前节点轩换为树。 if ((n = tab.length) < MIN_TREEIFY_CAPACITY) tryPresize(n << 1); //获取index位置的节点,并且判断hash值为正常的节点,因为有可能存在-1等情况 else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { //锁住首节点 synchronized (b) { if (tabAt(tab, index) == b) { TreeNode<K,V> hd = null, tl = null; //遍历所有节点,转换为树节点 for (Node<K,V> e = b; e != null; e = e.next) { TreeNode<K,V> p = new TreeNode<K,V>(e.hash, e.key, e.val, null, null); if ((p.prev = tl) == null) hd = p; else tl.next = p; tl = p; } //然后把TreeNode包装成TreeBin做为数组下标的对象 setTabAt(tab, index, new TreeBin<K,V>(hd)); } } } } } treeifyBin方法总结: 如果当前数组长度小于64,则不进行树元素的转换,直接扩容成2倍,如果不是的话,那就对节点进行转换,从链表节点,转换为树节点。 tryPresize方法 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253private final void tryPresize(int size) {//根据给出的长度,计算出实际的长度 int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(size + (size >>> 1) + 1); int sc; //sc>=0的条件下才会进入SizeCtl的具体值见定义 while ((sc = sizeCtl) >= 0) { Node<K,V>[] tab = table; int n; //数组没有初始化的情况,也就是一次都没有初始化,我个人感觉不会执行到这呢? if (tab == null || (n = tab.length) == 0) { n = (sc > c) ? sc : c; //把sizeCtl值修改为-1,为正在处理中 if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { //这里再次判断了2个对象是否是一个对象,估计是为了防止其他对象操作? if (table == tab) { //初始化数组 @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = nt; sc = n - (n >>> 2); } } finally { //最后修改sizeCtl的值,这里为什么没有用CAS操作,我理解是因为前边的if里已经修改为了-1,其他的线程不能操作,所以这里只用了普通的赋值 sizeCtl = sc; } } } //如果没到0.75数,或者已经大于最大值,停止此方法 else if (c <= sc || n >= MAXIMUM_CAPACITY) break; //到这里就证明数组是已经初始化过了的,并且需要扩容 else if (tab == table) { int rs = resizeStamp(n); //这里是判断sc的值小于0证明在处理 if (sc < 0) { Node<K,V>[] nt; //没看懂什么意思 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0) break; //把sizeCtl值+1并且进行扩容 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) transfer(tab, nt); } //(rs << RESIZE_STAMP_SHIFT) + 2)没看懂求出来的这个值是什么,sc的值替换成这个之后,也进行扩容 else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) transfer(tab, null); } } } tryPresize方法总结: 这里就是对数组的一些异常情况做了兼容处理,最终保证扩容完成。 transfer方法 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { int n = tab.length, stride; //求出处理数组的线程数,最大是16个线程 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; // subdivide range if (nextTab == null) { // initiating //构造nextTab,长度为原来的2倍 try { @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; nextTab = nt; } catch (Throwable ex) { // try to cope with OOME //如果失败,则设置sc的最大值为int的最大值 sizeCtl = Integer.MAX_VALUE; return; } nextTable = nextTab; //这里我理解的就是,因为是原来的2倍,所以转换的下标是n transferIndex = n; } int nextn = nextTab.length; //构造节点元素,用来标明此节点是正在处理的节点,此节点的hash值为-1 ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); boolean advance = true; boolean finishing = false; // to ensure sweep before committing nextTab for (int i = 0, bound = 0;;) { Node<K,V> f; int fh; while (advance) { int nextIndex, nextBound; if (--i >= bound || finishing) advance = false; else if ((nextIndex = transferIndex) <= 0) { i = -1; advance = false; } else if (U.compareAndSwapInt (this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) { bound = nextBound; i = nextIndex - 1; advance = false; } } if (i < 0 || i >= n || i + n >= nextn) { int sc; if (finishing) { nextTable = null; table = nextTab; sizeCtl = (n << 1) - (n >>> 1); return; } if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) return; finishing = advance = true; i = n; // recheck before commit } } else if ((f = tabAt(tab, i)) == null) advance = casTabAt(tab, i, null, fwd); else if ((fh = f.hash) == MOVED) advance = true; // already processed else { //锁住当前元素节点 synchronized (f) { //再次确认节点是否相等,防止其他线程修改此节点 if (tabAt(tab, i) == f) { Node<K,V> ln, hn; //链表 if (fh >= 0) { //这里我不太理解为什么是&n,因为在普通put值的时候是&(n-1),这里求出的其实也是个下标位置 int runBit = fh & n; Node<K,V> lastRun = f; for (Node<K,V> p = f.next; p != null; p = p.next) { //这里对链表后面的元素也都进行了求值,但我个人理解这里的b应该绝对等于runBit才对啊,因为他们在put的时候被放入了同一个桶,就应该是hash值相同才对的 int b = p.hash & n; if (b != runBit) { runBit = b; lastRun = p; } } if (runBit == 0) { ln = lastRun; hn = null; } else { hn = lastRun; ln = null; } for (Node<K,V> p = f; p != lastRun; p = p.next) { int ph = p.hash; K pk = p.key; V pv = p.val; if ((ph & n) == 0) ln = new Node<K,V>(ph, pk, pv, ln); else hn = new Node<K,V>(ph, pk, pv, hn); } setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } else if (f instanceof TreeBin) { TreeBin<K,V> t = (TreeBin<K,V>)f; TreeNode<K,V> lo = null, loTail = null; TreeNode<K,V> hi = null, hiTail = null; int lc = 0, hc = 0; for (Node<K,V> e = t.first; e != null; e = e.next) { int h = e.hash; TreeNode<K,V> p = new TreeNode<K,V> (h, e.key, e.val, null, null); if ((h & n) == 0) { if ((p.prev = loTail) == null) lo = p; else loTail.next = p; loTail = p; ++lc; } else { if ((p.prev = hiTail) == null) hi = p; else hiTail.next = p; hiTail = p; ++hc; } } ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K,V>(lo) : t; hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K,V>(hi) : t; setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } } } } } } transfer方法总结:> addCount方法 12345678910111213141516171819202122232425262728293031323334353637383940private final void addCount(long x, int check) { CounterCell[] as; long b, s; //使as等于counterCells 并且不等于null //或者在修改baseCount时失败,才会进入此方法 //这里的x值,我个人理解的也就是此次增加了几个元素。 if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { CounterCell a; long v; int m; boolean uncontended = true; if (as == null || (m = as.length - 1) < 0 || (a = as[ThreadLocalRandom.getProbe() & m]) == null || !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { fullAddCount(x, uncontended); return; } if (check <= 1) return; s = sumCount(); } if (check >= 0) { Node<K,V>[] tab, nt; int n, sc; while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) { int rs = resizeStamp(n); if (sc < 0) { if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0) break; if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) transfer(tab, nt); } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) transfer(tab, null); s = sumCount(); } } } remove方法get方法其他相关总结]]></content>
</entry>
<entry>
<title><![CDATA[JDK源码之LinkedHashMap]]></title>
<url>%2Fposts%2Fjava%2FJDK%E6%BA%90%E7%A0%81%E4%B9%8BLinkedHashMap.html</url>
<content type="text"><![CDATA[LinkedHashMap使用场景我们都知道平常使用的HashMap是存放无顺序的,但当我们需要有顺序的HashMap的时候呢?所以JDK提供了LinkedHashMap和TreeMap2种有序的Map供我们使用。虽然以前一直都看过说LinkedHashMap是通过链表实现的,但一直没有去源码里探索究竟,今天看了之后原来和自己理解的不完全一样,理论的这些东西,还是自己真正去研究过才有底气。LinkedHashMap还支持插入顺序和访问顺序2种方式,默认的就是按照插入顺序排序的,如果需要按照访问顺序排序,在初始化时设置accessOrder为true即可。通过这个访问顺序我们也可以实现简单的LRU算法。 源码概要LinkedHashMap继承了HashMap,所以好多方法都是直接用的HashMap中的,在控制底层数据这方面,并没有选择自己去实现,而是通过重写了HashMap中的某些节点方法,来完成相应的功能。 比如通过重写一些对节点的操作来实现了自己的链表结构,增,删,改,查,几乎都是用的HashMap中的方法,但是在遍历的时候,以及在需要对象顺序的地方都重写了自己有序实现,这样即保持了功能性,又避免了开发重复的功能。 源码分析类变量12345transient LinkedHashMap.Entry<K,V> head;transient LinkedHashMap.Entry<K,V> tail;final boolean accessOrder; 这里我们可以看到,有两个链表节点,一个头一个尾,是用来维护链表的关系的。accessOrder是用来控制访问顺序的。至于这里变量为什么要用transient来修饰不知道为什么,虽然知道这个变量是用来控制在序列化时,该属性不会被写入文件。 初始化123456public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } 除了最基本的构造方法之外,LinkedHashMap还提供了一个可以指定顺序的构造方法,我们可以看到这里构造方法也都是调用的HashMap中的构造方法,accessOrder就是可以指定是按插入排序还是按照访问排序。 put方法LinkedHashMap并没有重写put方法,而是重写了put方法中的newNode方法,所以底层的数据存储和HashMap其实是一样的。123456Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); return p; } 在这里我们可以看到,除了正常的新建节点之外,还调用了linkNodeLast(p)这个方法,这里就是主要的具体实现了,就是通过这里来自己维护了一个链表。12345678910private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; } } 如果是首个节点,head和tail是指向同一个节点的。 如果不是首节点,则进入正常的双向链表结构。这里其实就是前驱和后继节点都互相指向,前驱节点也有指向后继节点的连接,后继节点也有指向前驱节点的连接。不向单向链表一样,只是前节点有后节点的连接。每次放入一个新的节点,都会把自己设置为尾节点。所以链表的添加速度还是很快的。 关于HashMap如果大家看过我之前的关于HashMap源码的文章,会发现有几个我没弄懂的地方,没想到是在LinkedHashMap中使用的。afterNodeAccess()和afterNodeInsertion()这2个方法,在HashMap中都是空实现,原来是为了在LinkedHashMap中使用。 afterNodeAccess这个方法在HashMap put方法中是如果数据已经存在才会调用的。123456789101112131415161718192021222324void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } } 这里还是比较绕的,让我们来一一的分析。if (accessOrder && (last = tail) != e)这个判断的条件是设置的是访问顺序排序,并且当前操作的这个节点是不是最后一个节点。因为在HashMap中几乎所有对节点操作的方法都调用了此方法。12345LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; 这个赋值看起来也比较恶心,这里拆开分析下。 首先,当前操作的节点e赋值给了p,也就是当前操作节点。 p.before赋值给了b,也就是当前操作节点的前驱节点。 p.after赋值给了a,也就是当前操作节点的后继节点。 p.after给了null,证明把p设置为了尾节点。 在后面的判断条件中又分了不同的情况,在不同的判断我会用不同的图画一下。 1234if (b == null) head = a; else b.after = a; 这个判断的意思是 如果b==null就证明该节点是头节点,所以直接把head设置为了该节点的后继节点。 如果b!=null,就证明该节点不是头节点,所以前驱节点的后继设置为了自己节点的后继也就是a。这个可以说起来比较绕,意思就是,把自己从中间移除了,把前驱和后继连接了起来。 举例说明一下: 上图这里假设我们操作的节点是head节点,所以b是等于null的,所以代码里直接把head设置为了a,在图中也就是data1变成了首节点。 上图这里我们假设操作的节点是data1节点,所以b是不等于null的,所以把b.after也就是head的后继设置为data2。但p本身也就是data1的前驱还没有设置,我这里图没有体现出来。 1234if (a != null) a.before = b; else last = b; 这个判断的意思是 如果a==null,就证明该节点p是尾节点,所以把b赋值给了last,至于这里为什么这么弄,最下面判断会提及。这里我有个问题,因为我感觉这里是防御式编程,如果a==null的情况最外层的判断应该都进不来。如果大家知道请给我留言指教 如果a!=null就证明不是尾节点,把当前节点p的后继节点也就是a的前驱设置为自己节点的前驱。这个和上图第2个是相辅的,上图只是设置了前驱节点的连接,这里是设置了后继节点的连接。 同样,如下图: 上图如果当前操作的是尾节点,则a就等于null,所以设置了last=b。 如上图,如果操作的非尾节点,这里继续假设我们操作的是data1,因为是非尾节点,所以把a.before设置为了b,设置完成后如图所示,head和data2连接上了,因为在上边的判断中head的后继已经设置过了,因此到这里基本前驱和后继的关系已经建立成功了,再往下就是把当前的操作节点p设置为尾节点。 123456if (last == null) head = p; else { p.before = last; last.after = p; } 如果last==null就证明还没有存放数据,所以直接设置了head=p,也就是首节点。 如果last!=null,就证明已经有数据存在,所以设置了当前操作节点p的前驱为last,last的后继设置为p,也就是把2个节点互相关联。last的取值有2个,一个是尾节点,也就是刚进入时候的大判断,一个是上边提到过的a==null的时候,所以last始终是最后一个节点。 如果上图,这里继续假设我们操作的是data1,因为last取舍是tail,所以last不等于null,所以这里就把p和last互相连接最终形成了图下半部分的结构,其实就是data1换了个位置,换成最后了。 12tail = p;++modCount; 最后,把tail设置成最后操作的节点p,并且修改次数+1。 afterNodeInsertion1234567void afterNodeInsertion(boolean evict) { // possibly remove eldest LinkedHashMap.Entry<K,V> first; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } } 这里没太看明白,为什么要删除首节点?而且这里的removeEldestEntry方法是返回fasle的,估计是让自己去实现,重写这个类吧。 newTreeNode同node,不做过多解释。 remove方法12345678910111213void afterNodeRemoval(Node<K,V> e) { // unlink LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.before = p.after = null; if (b == null) head = a; else b.after = a; if (a == null) tail = b; else a.before = b; } 同样,remove方法也是重写了afterNodeRemoval。这里其实就是一个前驱后继的关联这里就不多说了。 get方法12345678public V get(Object key) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return null; if (accessOrder) afterNodeAccess(e); return e.value; } get方法这里是重写了的,这里同理,先是直接调用HashMap中的查找方法,如果需要设置访问顺序排序,就进行操作,否则,直接返回数据。 迭代器增,删,改,查的操作,其实和HashMap很类似,并且也都是共用了一些父类的方法。因为自己给护了一套链表,所以在迭代器中,这些东西就是自己的实现了。 示例代码: 1234567891011public static void main(String[] args) { Map map = new LinkedHashMap(); map.put("0", "a"); map.put("1", "b"); map.put("2", "c"); Iterator it = map.entrySet().iterator(); while (it.hasNext()){ System.out.println(it.next()); } } 输出结果:0=a 1=b 2=c entrySet()返回的是LinkedEntrySet这个类。此类是LinkedHashMap中的内部类。LinkedEntrySet中的iterator方法返回的是LinkedEntryIterator这个内部类。LinkedEntryIterator类最终继承了LinkedHashIterator这个类,所以最终的实现都在这里。 1234567891011121314151617181920212223242526272829303132333435363738abstract class LinkedHashIterator { LinkedHashMap.Entry<K,V> next; LinkedHashMap.Entry<K,V> current; int expectedModCount; LinkedHashIterator() { next = head; expectedModCount = modCount; current = null; } public final boolean hasNext() { return next != null; } final LinkedHashMap.Entry<K,V> nextNode() { LinkedHashMap.Entry<K,V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); current = e; next = e.after; return e; } public final void remove() { Node<K,V> p = current; if (p == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); current = null; K key = p.key; removeNode(hash(key), key, null, false, false); expectedModCount = modCount; } } 这里可以看到经典的迭代器实现,我们最开始学的时候自己写迭代器就是这样的。构造的时候把next=head所以判断hasNext很简单,只要判断next是否为空即可,因为在执行next方法的时候,next的引用指向也会变的。 nextNode()方法首先是判断是否安全删除的,这里是这样的如果在迭代器中,使用了Map自带的删除,迭代器就会抛出错误,所以,在迭代器中,只能使用迭代器的删除功能。然后设置current为当前节点,把next引用指向更新为下一个节点引用,返回节点。 总结看完源码之后,更新了我对LinkedHashMap的认识,以前只是认为简单的双向链表实现的,没想到和HashMap有着莫大的关系。 个人认为LinkedHashMap虽然实现了有序,但是多维护了一层链表,所以我认为它的性能有可能会比HashMap是差的,但我用JDK1.8做了一下实验,插入100W条数据LinkedHashMap几乎是HashMap的一半。这里我自己就不是很能理解了,为什么多维护了数据,性能反而更快了呢? 这里我们也看到LinkedHashMap的优点和缺点几乎和HashMap是一样的,所以如果我们需要有序的Map,这是个很好的选择。]]></content>
</entry>
<entry>
<title><![CDATA[JDK源码之ThreadLocal]]></title>
<url>%2Fposts%2Fjava%2FJDK%E6%BA%90%E7%A0%81%E4%B9%8BThreadLocal.html</url>
<content type="text"><![CDATA[ThreadLocal介绍一般我们都是如果发现有资源需要共享的时候,在多个线程之间要互相共享数据的时候,我们可以使用ThreadLocal来实现。因为存入ThreadLocal中的数据是和每个线程绑定的,所以不会存在数据竞争的问题了。 使用场景例子如下: 123456789101112131415161718192021222324252627282930public class ThreadLocalTest { public static void main(String[] args) { executeThread(); } private static void executeThread() { ExecutorService executor = Executors.newFixedThreadPool(10); executor.submit(new MyThread()); executor.submit(new MyThread()); executor.submit(new MyThread()); executor.shutdown(); } static class MyThread extends Thread { ThreadLocal threadLocal = new ThreadLocal(); public void run() { String str = Thread.currentThread() + "_" + Math.random(); threadLocal.set(str); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadLocal.get()); } }} 具体实现set方法12345678public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } 可以看到这里面的逻辑很简单,得到当前线程,根据当前线程获取map,如果map存在,则直接替换,如果不存在则去初始化map并且放入其中。这里核心的代码是在初始化map时createMap方法和map.set(),我们进入方法其中看一下这两个方法。 1234567ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } 可以看到createMap中最后调用了ThreadLocalMap类初始化,这个类看起来还是很简单的和HashMap类似,先建立一个数组,然后对key值取hash值并且对数组长度取与计算,求出在数组中的位置,然后把对象放入数组位置,最后设置阀值。 1234567891011121314151617181920212223242526272829303132private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } set方法和HashMap的put方法类似,只不过是这个没有链表了。会先在原有数组中循环寻找当前key,如果找到,则赋新值,返回值。如果在循环过程中,遇到有null的,还会顺便清理掉。如果在数组中没有找到,就证明是新的。把在上面求到的i下标值,设置为新的对象。设置之后如果到达阀值会进行一系列的扩容操作,这里就不细说了,因为和HashMap类似。 get方法12345678910111213public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } 这里的核心方法是map.getEntry(this)和setInitialValue(),后者的方法和上面的set方法类似是为了初始化map就不细说了,这里我们主要说下map.getEntry(this)。 12345678private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } 可以看到这里主要就是在数组中寻找,如果能直接找到则直接返回,如果不能直接找到,则尝试去当前下标之后找,具体的查找方法请看getEntryAfterMiss个人理解的意思就是去不断的查找并且顺便清理为空的,如果找不到返回null。 remove方法12345public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } 其实到这里我们已经发现了,ThreadLocal主要的操作都是在对ThreadLocalMap的操作。这里同理,核心的代码是m.remove(this),也就是map中的remove方法。 1234567891011121314private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } } 可以看到这里的方法其实也很简单,就是根据hashcode算出的下标开始在数组中查找,如果找到删除的对象,直接设置为null,这里我个人理解是这样的因为它放到数组的Entry对象是继承WeakReference的,所以当设置为null之后,垃圾回收器在回收时会优先回收。这里是个人理解的,如有不正确请各位指正。 Tips我们看下来ThreadLocal的代码,其实就是对数组的操作进行了封装,所以说如果有大量的写的话会产生和ArrayList类似的问题,就是不断的扩容。所以在使用时应该注意,不要有太大量的写入,并且在这里也没有看到像ArrayList那样可以指定长度的地方。以上就是个人对此类的理解,如果有不正确之处请大家指正共同进步。]]></content>
</entry>
<entry>
<title><![CDATA[hexo博客主动推送到百度站长平台]]></title>
<url>%2Fposts%2Fhexo%2Fhexo%E5%8D%9A%E5%AE%A2%E4%B8%BB%E5%8A%A8%E6%8E%A8%E9%80%81%E5%88%B0%E7%99%BE%E5%BA%A6%E7%AB%99%E9%95%BF%E5%B9%B3%E5%8F%B0.html</url>
<content type="text"><![CDATA[推送百度站长平台百度站长平台有几种方式提交自己的链接 主动推送(实时) 自动推送 sitemap 或者自己手工提交,这里主要说以上三种方式。 主动推送第一种方式使用了baidu_url_submitter这个工具:首先在hexo根目录安装插件npm install hexo-baidu-url-submit --save然后在根目录_config.yml中增加12345baidu_url_submit: count: 1 ## 提交的链接数 host: www.zhishuo.info ## 在百度站长平台中注册的域名 token: token ## 百度站长平台里的token,在链接提交-自动提交-主动推送中有 path: baidu_urls.txt ## 这个是会自动生成要推送的url地址 count是控制几条数据会生成在baidu_urls.txt中,这里推荐首次使用时填写你所有博客的数量,然后修改为1即可。这里还要注意一个问题,站点配置文件中url: http://www.zhishuo.info这里一定要带上www要不然推送不成功。最后把deploy调整一下,就可以正常使用了。12345deploy: - type: git repository: https://github.com/imkratos/imkratos.github.io.git branch: master - type: baidu_url_submitter # baidu push hexo g会生成baidu_urls.txt文件。 hexo d会在部署完git代码之后,把新增的url也会推送到百度。 自动推送 由于我是使用的hexo next主题,next主题中自带了此功能,在next主题根目录下_config.yml中找到baidu_push: false改为true即可。 sitemap 此种方式好像被github官方禁止了,博主添加了发现效果不是很明显,需要使用的同学们可自行查找。 baidu_url_submitter作者]]></content>
</entry>
<entry>
<title><![CDATA[hexo博客next主题修改静态资源为CDN]]></title>
<url>%2Fposts%2Fhexo%2Fhexo%E5%8D%9A%E5%AE%A2next%E4%B8%BB%E9%A2%98%E4%BF%AE%E6%94%B9%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E4%B8%BACDN.html</url>
<content type="text"><![CDATA[问题博主最近看到博客打开速度非常慢,点开chrome的开发者工具查看是由于css,js,图片加载太慢,故css,js换成了国内的cdn,图片换成了七牛云。打开主题配置文件_config.yml以下为修改方式:123456789101112131415161718192021222324252627282930vendors: # Internal path prefix. Please do not edit it. _internal: vendors # Internal version: 2.1.3 jquery: //cdn.bootcss.com/jquery/2.1.3/jquery.min.js # Internal version: 2.1.5 # Fancybox: http://fancyapps.com/fancybox/ fancybox: //cdn.bootcss.com/fancybox/2.1.5/jquery.fancybox.pack.js fancybox_css: //cdn.bootcss.com/fancybox/2.1.5/jquery.fancybox.min.css # Internal version: 1.0.6 fastclick: //cdn.bootcss.com/fastclick/1.0.6/fastclick.min.js # Internal version: 1.9.7 lazyload: //cdn.bootcss.com/jquery_lazyload/1.9.7/jquery.lazyload.min.js # Internal version: 1.2.1 velocity: //cdn.bootcss.com/velocity/1.3.1/velocity.min.js # Internal version: 1.2.1 velocity_ui: //cdn.bootcss.com/velocity/1.3.1/velocity.ui.min.js # Internal version: 0.7.9 ua_parser: //cdn.bootcss.com/UAParser.js/0.7.12/ua-parser.min.js # Internal version: 4.4.0 # http://fontawesome.io/ fontawesome: //cdn.bootcss.com/font-awesome/4.6.2/css/font-awesome.min.css 最终打开时间由原来的20-40s,缩短到现在的5s左右,可能不稳定,但足够了。 以下是几个国内比较好的cdn网站 http://www.bootcdn.cn/ https://www.staticfile.org/ http://cdn.code.baidu.com/]]></content>
</entry>
<entry>
<title><![CDATA[JDK源码之HashMap]]></title>
<url>%2Fposts%2Fjava%2FJDK%E6%BA%90%E7%A0%81%E4%B9%8BHashMap.html</url>
<content type="text"><![CDATA[什么是HashMapHashMap是一个可以提供O(1)时间复杂度的数据结构,由数组和链表数据结构组成。在对存入的key进行hash之后,然后用hash值在数组上确定一个位置,把value对象以Node节点形式放入到数组的链表当中。 jdk1.8之后对此做了优化,因为如果发生了数据倾斜,可能会使数组某个下标的Node链表非常长,因为链表查询起来比较慢,所以1.8之后修改了,当Node链表长度大于8时,会把该下标位置的链表数据结构修改为红黑树的结构来保证查询的速度。当数据长度小于8时,会再修改为链表。 使用场景个人理解使用场景应该是在不需要复杂的查询,只需要一个Key对应一个Value,写入少的场景。因为像HashMap,ArrayList这种数据结构都提供了自动扩容的功能,像HashMap的负载因子是0.75,也就是当数组中75%的位置都有值以后会进行扩容。每次扩容的时候都涉及到每个数据的rehash和数组的复制,所以当写入数据量非常大的时候,会不断的进行rehash和复制,有可能会造成CPU占用率非常高(这只是个人平时学习的理解,如果有不对之处请大家指正)。 所以个人感觉HashMap的使用场景也是读多写少的场景,可以提供很快速度的读,写入的速度也可以,但如果提前知道Map里要放入多少数据,最好在new对象的时候,就手动指定出长度,这样可以避免rehash,从来变相提高使用效率。 源码实现 jdk1.8版本以下的代码都是代码在上面,解释在下面。 变量声明1234/** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 这个值是HashMap默认初始化的长度,也就是16长。 123456/** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */ static final int MAXIMUM_CAPACITY = 1 << 30; //1073741824 这里按注释看,应该是HashMap支持的最大长度,如果超过这个值,则使用这个值。 1234/** * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f; 负载因子,当数组中有值的位数超过此阀值时,进行扩容。 123456789/** * The bin count threshold for using a tree rather than list for a * bin. Bins are converted to trees when adding an element to a * bin with at least this many nodes. The value must be greater * than 2 and should be at least 8 to mesh with assumptions in * tree removal about conversion back to plain bins upon * shrinkage. */ static final int TREEIFY_THRESHOLD = 8; 当数组单个位置超过此值后,会把数据结构修改为红黑树。 123456/** * The bin count threshold for untreeifying a (split) bin during a * resize operation. Should be less than TREEIFY_THRESHOLD, and at * most 6 to mesh with shrinkage detection under removal. */ static final int UNTREEIFY_THRESHOLD = 6; 当小于这个值时,修改为链表。 1234567/** * The smallest table capacity for which bins may be treeified. * (Otherwise the table is resized if too many nodes in a bin.) * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts * between resizing and treeification thresholds. */ static final int MIN_TREEIFY_CAPACITY = 64; ??? 1transient Node<K,V>[] table; 存放元素的数组。 12345678910/** * The next size value at which to resize (capacity * load factor). * * @serial */ // (The javadoc description is true upon serialization. // Additionally, if the table array has not been allocated, this // field holds the initial array capacity, or zero signifying // DEFAULT_INITIAL_CAPACITY.) int threshold; 这个变量是用来存放阀值的,也就是数组长度*0.75的值。 初始化12345678910111213141516171819202122232425262728293031323334353637383940/** * Constructs an empty <tt>HashMap</tt> with the default initial capacity * (16) and the default load factor (0.75). */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }/** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and the default load factor (0.75). * * @param initialCapacity the initial capacity. * @throws IllegalArgumentException if the initial capacity is negative. */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and load factor. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } 初始化时,如果是空的构造方法,会只设置负载因子的值为默认的0.75. 如果指定的initialCapacity超过MAXIMUM_CAPACITY,则值就为MAXIMUM_CAPACITY tableSizeFor函数会求出一个值作为HashMap的容量 123456789101112/** * 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; } 初始化完毕 put方法12345678910111213141516171819202122232425262728293031323334353637383940414243444546public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } 说下参数的意思,hash就是通过key计算出的hash值,Key Value不用多说就是我们要放入的Key和Value,onlyIfAbsent这里我理解应该是,如果Key存在是否要替换对应Key的Value,这里传入的是false,evict应该是,是否自动扩容,默认是true。 声明变量tab数组,就是需要存放节点的数组。p则是放在数组下标位置的节点。n,i数组长度和位置下标。 代码逻辑12if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; 如果数组没有初始化或者长度为0,则进行初始化。resize()方法中的初始化代码 扩容机制12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182/** * Initializes or doubles table size. If null, allocates in * accord with initial capacity target held in field threshold. * Otherwise, because we are using power-of-two expansion, the * elements from each bin must either stay at same index, or move * with a power of two offset in the new table. * * @return the table */ final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; 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); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } 123456789if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } 这里判断现有的数组长度是否为0,如果当前数组长度大于0,并且大于或等于MAXIMUM_CAPACITY,则threshold等于int的最大值,返回当前数组。 newCap的值等于当前数组的长度*2old<<1,如果这个新值小于MAXIMUM_CAPACITY并且当前数组的长度 >= DEFAULT_INITIAL_CAPACITY也就是16,newThr = oldThr << 1;这句话的意思是新的阀值=老阀值*2。其实以上的意思就是如果符合条件了,把新数组的长度和阀值都扩大2倍。 12else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; 这里的意思是,如果老数组长度不大于0,并且老阀值大于0,把老阀值的值赋给新的数组长度,这里我没理解为什么要这么做。 1234else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } 如果以上条件都不满足,就会进行默认的初始化。也就是数组长度等于16,阀值等于12。 12345if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } 至此如果新的阀值还是0的话,会再次进行初始化,公式还是一样的,先根据新的数组长度*负载因子计算出阀值,如果新的数据长度小于长度最大值,并且三目表达式用来判断是否过界,如果不过界,返回计算的值,如果过界,返回int最大值。123threshold = newThr;Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab; 到这里,基本上阀值和新的数组长度都已经初始化完了,创建了新的数组,开始初始化对象了。如果是构造方法里调用这里,到这里就已经结束了,因为数组,阀值,都已经初始化完毕了。再下面的代码是扩容的时候会进行调用的。 123456789101112131415161718192021222324252627282930313233343536373839404142if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; 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); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } 到第7行为止,是表示如果老数组的下标位置只有一个节点没有链表也没有红黑树,就会把该位置的数组赋值给新数组,在新数组的位置是hash值&数组的长度(这里我以前一直认为是取余)。 如果是红黑树,则进行树拆分((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 红黑树12345678910111213141516171819202122232425262728293031323334353637383940414243444546final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { TreeNode<K,V> b = this; // Relink into lo and hi lists, preserving order TreeNode<K,V> loHead = null, loTail = null; TreeNode<K,V> hiHead = null, hiTail = null; int lc = 0, hc = 0; for (TreeNode<K,V> e = b, next; e != null; e = next) { next = (TreeNode<K,V>)e.next; e.next = null; if ((e.hash & bit) == 0) { if ((e.prev = loTail) == null) loHead = e; else loTail.next = e; loTail = e; ++lc; } else { if ((e.prev = hiTail) == null) hiHead = e; else hiTail.next = e; hiTail = e; ++hc; } } if (loHead != null) { if (lc <= UNTREEIFY_THRESHOLD) tab[index] = loHead.untreeify(map); else { tab[index] = loHead; if (hiHead != null) // (else is already treeified) loHead.treeify(tab); } } if (hiHead != null) { if (hc <= UNTREEIFY_THRESHOLD) tab[index + bit] = hiHead.untreeify(map); else { tab[index + bit] = hiHead; if (loHead != null) hiHead.treeify(tab); } } } 这里我大概理解是这样的,先对所有的节点进行遍历,然后通过一定条件来判断是否小于等于UNTREEIFY_THRESHOLD这个阀值,如果小于就切换为普通的链表,如果大于就继续在树上添加节点,由于博主对树现在理解不是很好,这里暂且这样,以后我会指定研究一下树的节构。 123456789101112131415161718192021222324252627282930else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; 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); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } 这里就是链表节构了,也是把链表循环先读取出来。但是这里为什么分成了2个链表?并且分别放在了不同的位置上。至此,resize()方法执行完毕,让我们再回来继续看put方法。 以上我们写了resize()中详细的方法 有初始化数组的长度和阀值 对数组进行扩容时对链表,单节点,以及树不同的处理 12if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); 这里是数组长度对hash值求与值,如果该位置为null证明该位置没有放入元素,则放入新的元素。 1234Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; 这里判断,如果hash值相等并且key值相等,或者key的equest相等,就直接替换该位置的值。 12else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 如果该位置的结构是树的,则调用树的插入方法,关于树的方法博主在这里暂不讨论,因为博主也不是特别理解树结构,以后会补充。 123456789101112for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } 如果是链表的话,循环所有的链表,如果有相等的key,直接替换该key对应的值,如果循环到最后也没有相等的,在链表上插入一个新的node节点,同时判断是否满足到达TREEIFY_THRESHOLD的条件,如果到达,会把数据结构变更为红黑树。 1234567if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } 如果到这里e已经存在了,因为e等于新放入的node节点。就把该节点移到链表最后一个,并且直接返回value值。 12345++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; 记录修改的次数modCount是用来控制非法修改hashmap里的值,来抛出ConcurrentModificationException。并且判断数组长度是否大于阀值,如果大于了就要调用resize()进行扩容,afterNodeInsertion没有理解作用是什么,至此put方法全部执行完毕。 get方法12345678910111213141516171819final 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; } put方法核心的代码是这里,这里是判断如果在数据里找到当前key的位置之后,首先判断hash值,和key是否都相等,如果都相等,直接返回此对象,如果不相等还分2种情况一种是树结构,会调用树的查找方法,另外一种是链表,则会对链表不断的循环判断,直到找到相等元素为止。如果最后都没有找到,则返回null。 remove方法12345678910111213141516171819202122232425262728293031323334353637383940final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node<K,V> node = null, e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) node = ((TreeNode<K,V>)p).getTreeNode(hash, key); else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; } remove方法和get方法类似,核心代码是首先也是在数组里找位置,如果当前位置首个节点是这个key,就把值给node,如果当前值不是,则进行后续查找。如果是树,调用树的查找方法,把查找到的值给node,如果是链表,则对链表进行循环查找,找到之后把值给node。最后判断如果node不这代,并且value值相等。则会进行删除动作,如果是树节构就调用树的删除方法,如果数组的位置首节点是要删除的值,则直接把next的值给当前位置,如果是链表后续的值,则把要删除的key的下一个节点,和上一个节点连接,自己就会被删除掉。最后增加修改次数,size减掉。返回删除的节点。]]></content>
</entry>
<entry>
<title><![CDATA[JDK源码之CopyOnWriteArrayList]]></title>
<url>%2Fposts%2Fjava%2FJDK%E6%BA%90%E7%A0%81%E4%B9%8BCopyOnWriteArrayList.html</url>
<content type="text"><![CDATA[什么是CopyOnWriteArrayListCopyOnWriteArrayList是一个线程安全的List,和它相同的还有这几个,Vectory,SynchronizedList。它们都是实现了List接口的线程安全类。 CopyOnWriteArrayList使用的是一种写时复制的算法,也就是说在执行add方法的时候,并不像传统的ArrayList一样在当前数组直接操作,而是在执行add方法的时候,会把原来的数组复制并且长度+1,然后把新值设置到新的数组中,然后把新数组设置为当前使用状态,由于数组是volatile修饰的,所以JVM会自动来保证数组的可见性。 这样使此List在读取时可以不用加锁,提高读取效率,但是在添加的时候效率会很低下。所以我感觉CopyOnWriteArrayList的使用场景就是读多写少的场景,甚至在尽可能的情况下,不去写。这样才能发挥此数据结构的最大优点。 源码实现1234/** The lock protecting all mutators */final transient ReentrantLock lock = new ReentrantLock();/** The array, accessed only via getArray/setArray. */private transient volatile Object[] array; 数组结构和可重入锁,由于数组是volatile修饰的,所以JVM会利用CPU的缓存失效功能,将对象保持强一致性。假设有4个CPU,因为每个CPU都有属于自己的高速缓存,在此类对象进行更改时,JVM会调用CPU方法,将所有CPU的缓存失效,并且把修改后的内容刷回主存来保持此对象的强一致性,不知道这里我理解的有没有问题,如果有问题请各位指正。123456/** * Creates an empty list. */public CopyOnWriteArrayList() { setArray(new Object[0]);} 默认初始化,是一个0长度的数组。 1234567891011121314151617181920/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return {@code true} (as specified by {@link Collection#add}) */ public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } } 这里便是添加元素的代码,这里使用了重入锁,重入锁的意思是如果当前线程已经拿到了锁,再次获取此对象上的锁的时候,也会获得到锁不会排队。 上锁以后,先得到当前的数组,并取得当前的长度,然后调用Arrays.copyOf方法,复制数组并且长度+1,然后把新的元素加到新的数组中,设置新的数组为当前使用的数组。返回添加成功,并且释放锁。 1234567891011121314151617181920212223242526272829/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). Returns the element that was removed from the list. * * @throws IndexOutOfBoundsException {@inheritDoc} */public E remove(int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; E oldValue = get(elements, index); int numMoved = len - index - 1; if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { Object[] newElements = new Object[len - 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return oldValue; } finally { lock.unlock(); }} 删除这里基本同增加一样,也是先加锁,然后再操作。 这里不同的是使用了int numMoved = len - index - 1;这个值来判断删除的元素是不是最后一个元素,如果是最后一个元素也就是numMoved=0,直接把原来的数组复制长度-1即可。 如果不是最后一个元素,则是分开复制的,先复制删除元素前面的数据,再复制删除元素后面的数据,最后形成新的数组,设置并返回,最后解锁。 123456789101112131415161718192021222324252627/** * Replaces the element at the specified position in this list with the * specified element. * * @throws IndexOutOfBoundsException {@inheritDoc} */public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); E oldValue = get(elements, index); if (oldValue != element) { int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics setArray(elements); } return oldValue; } finally { lock.unlock(); }} 修改操作也类似,先加锁,得到原值,如果原值和修改的元素相等,其实感觉什么也没干,看这里注释只是为了保持volatile的语法。如果不等的话拷贝一个新的数组,在新的数组上修改值,把新的数组设置为当前使用的数组,返回修改之前的元素,解锁,完成。 12345678/** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} */public E get(int index) { return get(getArray(), index);} 下标查找,这里看到和普通的数组没什么区别,就是普通的下标查找。 总结从源码来看,CopyOnWriteArrayList增,删,改,都会复制一下新的数组然后再设置回去,所以强烈建议此数据结构的使用场景基本是只读的,否则在大量操作的情况下性能应该会很慢(待以后数据验证)。]]></content>
</entry>
<entry>
<title><![CDATA[hexo next主题添加LeanCloud统计功能]]></title>
<url>%2Fposts%2Fhexo%2Fhexo-next%E4%B8%BB%E9%A2%98%E6%B7%BB%E5%8A%A0LeanCloud%E7%BB%9F%E8%AE%A1%E5%8A%9F%E8%83%BD.html</url>
<content type="text"><![CDATA[遇到的问题网上能查到很多next主题添加LeanCloud主题的方法,但我看都是说在站点的_config.yml中添加1234leancloud_visitors: enable: true app_id: appid app_key: key 与是我也在站点的_config.yml中添加了,但不起作用。与是我又去主题的目录中添加了,报错。最后找到原因,是因为主题的_config.yml配置文件中已经自带了,在主题的_config.yml配置文件中308行左右,在这里直接配置即可。 参考资料next主题添加LeanCloud]]></content>
</entry>
<entry>
<title><![CDATA[mac下使用springli创建springboot应用]]></title>
<url>%2Fposts%2Fjava%2Fmac%E4%B8%8B%E4%BD%BF%E7%94%A8springli%E5%88%9B%E5%BB%BAspringboot%E5%BA%94%E7%94%A8.html</url>
<content type="text"><![CDATA[环境准备生成springboot有以下几种方式 maven gradle springli spring官网可以点击生成脚手架 spring官网介绍说springli是最快的方式,所以我使用了springli来构建。 mac安装springli我使用的是brew安装,如果没使用过brew请移步brew. brew安装springli12$ brew tap pivotal/tap$ brew install springboot]]></content>
</entry>
<entry>
<title><![CDATA[使用hexo生成博客在github js css 404解决方案]]></title>
<url>%2Fposts%2Fhexo%2F%E4%BD%BF%E7%94%A8hexo%E7%94%9F%E6%88%90%E5%8D%9A%E5%AE%A2%E5%9C%A8github-js-css-404%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.html</url>
<content type="text"><![CDATA[问题博主最近又开始写博客了,发现使用hexo next 主题,上传到github上之后,所有的vendors文件夹下的资源访问404,博主查了一些资料没有解决,故给github官方写了一封邮件,官方这样回复 Thanks for reaching out! We recently updated to Jekyll v3.3, which ignores the vendor folder by default. If you’re not using Jekyll, you can add a .nojekyll file to the root of your repository to disable Jekyll from building your site. Once you do that, your site should build with your vendor folder. 所以根据提示,在要目录下建立.nojekyll文件即可恢复正常,另外在hexo里,如果把文件放在source目录下,不会被生成到public目录中,根据github上网友的提示,把.nojekyll文件放在hexo主目录.deploy_git/ 文件夹下即可正常使用hexo d 上传,并且此文件也会上传到根目录。 2016-11-7号更新next主题源码更新了,把vendors目录改名了,更新为最新代码即可解决。]]></content>
</entry>
<entry>
<title><![CDATA[读spring源码深度解析(二)]]></title>
<url>%2Fposts%2Fjava%2F%E8%AF%BBspring%E6%BA%90%E7%A0%81%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90(%E4%BA%8C).html</url>
<content type="text"><![CDATA[不明白DefaultListableBeanFactory.java中使用了AccessController.doPrivileged()方法,个人理解此方法好像是以当前上下文执行一些代码。javaUtilOptionalClass这个属性,是用了JDK8的java.util.Optional,此类是用来避免在Java中出现各种null的问题,但不知道为什么是写在了静态代码块中,而且是以动态加载的方式来加载的。 初始类从DefaultListableBeanFactory.java类的getBean()中,开始对整个工厂类进分析。 静态导入,如果你多次用到某个工具类的静态方法,可以使用静态导入,这样使代码更整洁美观。SimpleAliasRegistry.java这个类实现了对Alias的一些操作。BeanFactory.java接口定义一些对Bean的基本操作。DefaultSingletonBeanRegistry.java对spring对Bean的一些操作做了各种封装,创建,消毁,依赖等。AbstractAutowireCapableBeanFactory.java对AutowireCapableBeanFactory定义的方法进行了实现,此类XmlBeanDefinitionReader.java实现了用xml解析资源文件,并且实现了DefaultListableBeanFactory.java类 xml如果要使用DTD文件来验证的话,需要在xml文件头中加入<!-- DOCTYPE 在解析DTD和XSD时,使用的是不同的机制,如果是DTD的话,会直接取到systemid在当前目录下寻找,如果是xsd时,会在META/schemas中找到对应的systemid 在解析xml时,如果设置了ResourceLoader,就使用用户设置的,如果没有设置,就使用默认的ResourceEntityResolver解析,解析Xml是使用的SAX方法解析,解析出来Document之后,会把Document进行解析,进行Bean的注册。 在注册的时候,使用了一个class类的cast方法,感觉这个方法使用的比较好,以前没有见过这种强制转换类型的使用,使用的DefaultBeanDefinitionDocumentReader.java来注册Bean,这里注册Bean的那些Map,是使用的SimpleBeanDefinitionRegistry.java类,如果没有指定应该是DefaultSingletonBeanRegistry.java?,注册的时候还会返回此次注册的类的数量。创建这个对象XmlReaderContext不知道是什么作用,里面是对XmlBeanDefinitionReader的一些封装,解析的时候还留了一前一后,供子类自己实现。DefaultBeanDefinitionDocumentReader.java中是对xml的解析,先根据标签来,然后再解析其中所有的属性,把xml组装成Java对象GenericBeanDefinition.java 解析Bean标签过程,获取id和name,如果没有指定id,就用name做这个类的key检查类名的唯一性判断此xml是否配置了一系列Spring的属性,并且在对应的Java对象中,设置对应的属性。]]></content>
</entry>
<entry>
<title><![CDATA[阅读ActiveMQ源码总结]]></title>
<url>%2Fposts%2Fjava%2F%E9%98%85%E8%AF%BBActiveMQ%E6%BA%90%E7%A0%81%E6%80%BB%E7%BB%93.html</url>
<content type="text"><![CDATA[准备工作 下载源码git clone https://github.com/apache/activemq.git 使用maven编译源码并且下载依赖mvn clean install 导入到开发工具eclipsemvn eclipse:eclipse 或者idea mvn idea:idea 默认会去maven中央仓库下载jar包,如果下载速度慢可以翻墙或者改成开源中国的镜像仓库12345678<mirrors> <mirror> <id>CN</id> <name>OSChina Central</name> <url>http://maven.oschina.net/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror></mirrors> 遇到的问题 idea导入之后无任何显示,事实证明楼主太着急,等编译之后就有了。。 客户端启动 java客户端代码我是按照《Java消息服务》这本书中的例子完成的 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 public class Chat implements javax.jms.MessageListener{ private TopicSession pubSession; private TopicPublisher publisher; private TopicConnection connection; private String username; /** * @param topicFactory * @param topicName * @param username * @throws Exception */ public Chat(String topicFactory,String topicName,String username) throws Exception { //使用JNDI? InitialContext ctx = new InitialContext(); //拿到工场 TopicConnectionFactory conFactory = (TopicConnectionFactory)ctx.lookup(topicFactory); //创建连接 TopicConnection connection = conFactory.createTopicConnection(); TopicSession pubSession = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); TopicSession subSession = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); Topic chatTopic = (Topic)ctx.lookup(topicName); TopicPublisher publisher = pubSession.createPublisher(chatTopic); String selector = "username=zhishuo"; TopicSubscriber subscriber = subSession.createSubscriber(chatTopic,selector,true); subscriber.setMessageListener(this); this.connection = connection; this.pubSession = pubSession; this.publisher = publisher; this.username = username; connection.start(); } public void onMessage(Message message) { try { TextMessage textMessage = (TextMessage)message; System.out.println(textMessage.getText()); System.out.println(message.getJMSDestination()); } catch (Exception e) { e.printStackTrace(); } } /** * 发送消息 * @param text * @throws JMSException */ protected void writeMessage(String text) throws JMSException { TextMessage textMessage = pubSession.createTextMessage(username+":"+text); publisher.publish(textMessage); } public void close() throws JMSException { connection.close(); } public static void main(String[] args) { try { if(args.length!=3){ System.out.println("something is missing"); } // topicFactory, topicName, username Chat chat = new Chat(args[0],args[1],args[2]); BufferedReader commondLine = new BufferedReader(new InputStreamReader(System.in)); while(true){ String s = commondLine.readLine(); if(s.equalsIgnoreCase("exit")){ chat.clone(); System.exit(0); }else{ chat.writeMessage(s); } } } catch (Exception e) { e.printStackTrace(); } }} 还需要在classpath中加入jndi.properties 1234567891011#配置实现工场java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory#配置activemq地址java.naming.provider.url=tcp://localhost:61616#这里是配置的安全机制?java.naming.security.principal=systemjava.naming.security.credentials=manager#订阅主题的名称,可以以逗号,隔开connectionFactoryNames=TopicCF#topictopic.topic1=jms.topic1 启动Chart类main方法时,传入TopicCF topic1 zhishuo这样客户端就可以正常启动,并且发送消息了,如果启动多个客户端,然后按回车发送消息,所有的人都可以看到。 代码分析 InitialContext ctx = new InitialContext();这里是JMS给出的公用调用方法,new对象时调用了默认的init()方法。myProps = (Hashtable<Object,Object>) ResourceManager.getInitialEnvironment(environment);里面主要是去加载当前环境变量和一些JVM设置的环境变量参数。 主要初始化都在getDefaultInitCtx()--->NamingManager.getInitialContext(myProps);中完成,会在环境变量配置参数中找到String INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial";key值,由于我们这里配置的是java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory,所以实例化的也就是ActiveMQInitialcontextFactory,最后再调用ActiveMQInitialcontextFactory.getInitialContext(env)这里传过去的参数是环境变量。 我们来到ActiveMQInitialcontextFactory.getInitialContext(env)方法中,此方法便是真正用来初始化队列,主题1234567891011121314151617181920212223242526272829303132333435363738394041424344454647public Context getInitialContext(Hashtable environment) throws NamingException { // lets create a factory Map<String, Object> data = new ConcurrentHashMap<String, Object>(); String[] names = getConnectionFactoryNames(environment); for (int i = 0; i < names.length; i++) { ActiveMQConnectionFactory factory = null; String name = names[i]; try { factory = createConnectionFactory(name, environment); } catch (Exception e) { throw new NamingException("Invalid broker URL"); } /* * if( broker==null ) { try { broker = factory.getEmbeddedBroker(); } * catch (JMSException e) { log.warn("Failed to get embedded * broker", e); } } */ data.put(name, factory); // 实际上是 tocipIF ActiveMQConnectionFactory } createQueues(data, environment); createTopics(data, environment); /* * if (broker != null) { data.put("destinations", * broker.getDestinationContext(environment)); } */ data.put("dynamicQueues", new LazyCreateContext() { private static final long serialVersionUID = 6503881346214855588L; @Override protected Object createEntry(String name) { return new ActiveMQQueue(name); } }); data.put("dynamicTopics", new LazyCreateContext() { private static final long serialVersionUID = 2019166796234979615L; @Override protected Object createEntry(String name) { return new ActiveMQTopic(name); } }); return createContext(environment, data); } getConnectionFactoryNames方法中,是为了遍历并且初始化connectionFactoryNames配置的Value值,我们这里配置的connectionFactoryNames=TopicCF,这里用到了StringTokenizer感觉很好用,平时自己都是用string的split,没想到还有这种用法。然后遍历刚才解析的Value值,并且最终创建ActiveMQConnectionFactory对象返回。把创建好的对像放入Hashmap中,map.put(TopicCF,ActiveMQConnectionFactory), failover://tcp://localhost:61616 这是此对象初始化时url属性的默认值初始化的时候修改成了tcp://localhost:61616 这里是每一个value值都对应一个自己的工厂类。 接下来创建Queues和Topics查找配置文件中以queue.和topic.开头的,并且生成ActiveMQQueue对象。我们这里配置的jms.topic1最后也放入map.put(topic1,ActiveMQTopic)。 接下来创建了动态的队列和主题,不知何用。dynamicQueuesdynamicTopics并且都放入了map最后创建了ReadOnlyContext对象,并且把相关的数据绑定都传入其中。至此第一句代码初始化完成。 (TopicConnectionFactory)ctx.lookup(topicFactory)这里是去加载实现工厂的类,由于上一个初始化最终返回的是ReadOnlyContext对象,所以这里调用的是ReadOnlyContext.lookup方法,我们来看public Object lookup(String name) throws NamingException {方法,这里其实就是根据传入的名称,取出相应的工厂,由于我们在启动时传入的工厂类名是TopicCF 所以,这里就是取出刚才放入的ActiveMQConnectionFactory工厂类,类图如下 conFactory.createTopicConnection(),这里是调用的ActiveMQConnectionFactory中的方法,该类中初始化了传输协议模式 123456789if (scheme.equals("auto")) { connectBrokerUL = new URI(brokerURL.toString().replace("auto", "tcp")); } else if (scheme.equals("auto+ssl")) { connectBrokerUL = new URI(brokerURL.toString().replace("auto+ssl", "ssl"));} else if (scheme.equals("auto+nio")) { connectBrokerUL = new URI(brokerURL.toString().replace("auto+nio", "nio")); } else if (scheme.equals("auto+nio+ssl")) { connectBrokerUL = new URI(brokerURL.toString().replace("auto+nio+ssl", "nio+ssl")); } 如果以上都没有配置,默认使用TCP协议传输,在TransportFactory.connect()-->findTransportFactory()中,查找资源对应的配置协议,如果没有查到,使用默认的在private static final FactoryFinder TRANSPORT_FACTORY_FINDER = new FactoryFinder("META-INF/services/org/apache/activemq/transport/");此文件目录中,因为这里是用的tcp://localhost:61616所以这里使用的就是META-INF/services/org/apache/activemq/transport/tcp文件,此文件中实际配置的是TCP的传输器1class=org.apache.activemq.transport.tcp.TcpTransportFactory 然后调TcpTransportFactory.doConnect()进行连接,至此传输器创建完成。 接下来创建ActiveMQConnection连接 最后启动连接,并且设置一些默认值,至此创建连接完成,并返回连接。 TopicSession pubSession = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);创建发布和订阅Session,最终调用的是ActiveMQConnection.createTopicSession(),创建ActiveMQSession对像并返回。 Topic chatTopic = (Topic)ctx.lookup(topicName);加载主题,这里的值为topic1,因为在前边初始化的时候已经放入了对像,所以这里的值为ActiveMQTopic,调用ActiveMQSession.createPublisher() 和 createSubscriber() 创建发布者ActiveMQTopicPublisher和消费者ActiveMQTopicSubscriber。 创建完成之后,把自己设置为订阅者的监听器,最终启动连接,客户端启动代码分析完成,因为有一些自己也不是很理解,所以未能表达。 服务端启动 以下是服务端启动的类图调用关系: 这里还了解了java -D 是可以把一些参数设置到JVM中的 服务端启动这里充分体现了命令设计模式的案例,我认为是很好的学习例子,最终调用的是StartCommand.runTask()方法,类中有如下几个重要功能 初始化broker 添加JVM shutdownhook 停止时进行清理 下面来看broker = BrokerFactory.createBroker(configURI);此类中在创建工厂时也使用了文件配置的方法由于默认是使用的xbean:activemq.xml资源文件,所以在创建工厂时就是实例化的META-INF/services/org/apache/activemq/broker/xbean文件中配置的类,1class=org.apache.activemq.xbean.XBeanBrokerFactory 实际调用的是XBeanBrokerFactory.createBroker()此类中创建了Spring的ResourceXmlApplicationContext实现对象,但不知这里为何要这样。这里最后还调用了spring框架中refresh方法,此方法会初始化spring整个框架。最终创建一个BrokerService返回,broker中包含了所有上下文环境。 KahaDBPersistenceAdapter 调用关系图 KahaDBPersistenceAdapter 用例图 最后启动,至此服务启动完成。 问题]]></content>
</entry>
<entry>
<title><![CDATA[archLinux安装遇到的问题]]></title>
<url>%2Fposts%2Flinux%2FarchLinux%E5%AE%89%E8%A3%85%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98.html</url>
<content type="text"><![CDATA[pacman -Syy 更新之后才能用 启动ssh服务需要安装openssh,pacman -S openssh 启动sshd服务systemctl start sshd 设置为开机启动服务systemctl enable sshd.service 安装vim之后,显示libncursesw.so.6不能找到,运行pacman -Syu升级即可]]></content>
</entry>
<entry>
<title><![CDATA[读activeMQ源码(一)]]></title>
<url>%2Fposts%2Fjava%2F%E8%AF%BBactiveMQ%E6%BA%90%E7%A0%81(%E4%B8%80).html</url>
<content type="text"><![CDATA[最近的学习到了消息的阶段,查了下网上的资料,并没有很多具体介绍某一个消息中间件的源码是如何实现,帮自己下了源码想分析下去,希望能坚持。 源码GIT下载地址 下载完之后如果要导入eclipse 执行mvn eclipse:eclipse 或者导入IDEA mvn idea:idea 说下个人对rpc和消息的理解: 这2种方式从架构上就是不同的,rpc一般都是同步的,消息都是异步的,用在不同的场景上。]]></content>
</entry>
<entry>
<title><![CDATA[mac树莓派安装kaliLinux]]></title>
<url>%2Fposts%2F%E6%A0%91%E8%8E%93%E6%B4%BE%2Fmac%E6%A0%91%E8%8E%93%E6%B4%BE%E5%AE%89%E8%A3%85kaliLinux.html</url>
<content type="text"><![CDATA[树莓派到手大概有一周了,想折腾下kali linux,记录下安装步骤。 准备工作,必须品: 树莓派板子一个 5V电源一个 8G SD卡一张 kali linux img 下载地址 选择 “RaspberryPi 2” 版本即可 无线网卡或者网线 显示屏 windows安装怎样在Windows下将Kali安装到SD卡上 下载普通版的树莓派专用Kali Linux,并解压img。 下载名为Win32DiskImager的压缩包并解压其中exe后缀的软件。将SD卡插入你的PC中(记得用读卡器)。 双击打开Win32DiskImager.exe,如果你用的是WIN7或WIN8,则需要点击鼠标右键并选择“以管理员身份运行”。 如果该软件无法自动侦测到你的SD卡,你就要在右上角的下拉菜单中找到SD卡并手动选择它。 在软件的图像文件部分点击小文件夹的图标,找到你刚刚下载的Raspbian.img文件。 点击“写入 or write”按键,Win32DiskImager就会帮你完成其它步骤。安装过程结束后,你就可以拔出SD卡然后将其插入树莓派了。 mac or linux 安装怎样在OSX下将Kali安装到SD卡上 下载普通版的树莓派专用Kali Linux,并解压img。 SD卡插入电脑 使用df -h 命令查看你插入的SD卡,一般类似 /dev/disk0s4 每个人的机器数字是不一样的 使用diskutil list查看SD卡的名字,一般类似 /dev/disk4 然后使用diskutil unmount /dev/disk0s4 命令,卸载已经挂载的SD卡 使用diskutil list查看是否已经卸载,并使用sudo dd bs=4m if=kali-linux.img(这里是你解压img的名字) of=/dev/rdisk2(这里是你SD卡的位置) 写入完成就可以了,然后执行diskutil unmountDisk /dev/disk2推出SD卡。!!!这一步一定要执行,否则可能不会正常开机。你就可以拔出SD卡然后将其插入树莓派了。 树莓派开机 插入SD卡后可正常开机,kali linux 默认用户名密码 root toor 一定要自行修改,否则很可能会被人猜出来的。 说下坑 我查了网上各种资料,都没有给出IMG的下载地址,官网的armhf不能用好么,查了查文档也没找到在哪写的armhf和armel的区别。最终使用了上面我给出的下载地址,下完之后才可以用。 还有,下载之后要验证一下和官网的验证值是否一致,否则有可能自己就成了老黑客的肉鸡,验证方式下载网站已经给出。]]></content>
</entry>
<entry>
<title><![CDATA[树莓派ntp时间同步问题]]></title>
<url>%2Fposts%2F%E6%A0%91%E8%8E%93%E6%B4%BE%2F%E6%A0%91%E8%8E%93%E6%B4%BEntp%E6%97%B6%E9%97%B4%E5%90%8C%E6%AD%A5%E9%97%AE%E9%A2%98.html</url>
<content type="text"><![CDATA[今天刚到手树莓派,看了下时间,于是上网搜了下时间同步的问题。然而我按照网上修改的同步服务器并不能解决问题,于是自己试着改了一下。 前提:你要选择正确了时区,运行sudo raspi-config 选择第4项,回车,继续选择第2个,回车,然后选择Aisa,回车,再次选择重庆或者上海,这样时区就调整好了。网上大部分都说在/etc/ntpd.conf 下 server 0.debain.pool.ntp.org iburst server 1.debain.pool.ntp.org iburst server 2.debain.pool.ntp.org iburst server 3.debain.pool.ntp.org iburst 调整服务器为server asia.pool.ntp.org iburst,我自己试了并不可以,于是我参考了一下我的Mac,用了苹果的时间同步服务器,最终修改为server time.apple.com iburst然后重启ntp服务sudo service ntp restart即可。]]></content>
</entry>
<entry>
<title><![CDATA[读spring源码深度解析]]></title>
<url>%2Fposts%2Fjava%2F%E8%AF%BBspring%E6%BA%90%E7%A0%81%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90.html</url>
<content type="text"><![CDATA[工作也有几年了,以前也尝试过想要读spring源码,但由于前几年没有参考资料,所以很难去理解,最近发现一本spring源码深度解析,怀着试试看的心情,踏上了源码之路。 记录一下自己遇到的问题,初次读完之后感觉到了spring的强大之处,处处都是设计模式,把代码拆的很分散,但理解上就很难了。 源码下载,spring最新的git源码地址为spring源码 由于spring3.X以后就是使用gradle构建了,所以还需要安装gradle,Mac OS 可以使用HomeBrew brew install gradle 即可 安装好gradle以后,可以在想要查看的spring项目下执行gradle eclipse 生成eclipse的配置文件 最后在Eclipse->import->existing Eclipse projects进行导入就可以了 书中有一个查看类实现的接口图,一开始不知道在哪使用,后来发现对着想要查看的类右击,出现选项菜单,点击->Open Type Hierarchy 即可,默认是显示的子类实现,然后点击上方小图标show the supertype Hierarchy即可 书中作者看到了哪个类中没有做明确提示,如果找不到需要自行搜索,我用的STS,commond+shift+l 可按关键字搜索]]></content>
</entry>
<entry>
<title><![CDATA[hexo博客提交到搜索引擎]]></title>
<url>%2Fposts%2Fhexo%2Fhexo%E5%8D%9A%E5%AE%A2%E6%8F%90%E4%BA%A4%E5%88%B0%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E.html</url>
<content type="text"><![CDATA[使用了hexo搭建博客之后,于是想把博客地址提交到搜索引擎当中,让搜索引擎来收录,在提交搜索引擎的时候遇到了一些问题记录下来。让搜索引擎收录的方式有以下几种: 提交某个html文件到你的博客根目录下 添加网站的meta标签 CNAME方式 当我使用第1种方式时,发现其实上传的文件是在index文件中包含了进来,所以搜索引擎不会识别,所以使用了第2种方式。在使用第2种方式时,是需要给index.html添加meta标签,使用hexo的话,如果直接修改.deploy是不可以的,因为执行hexo clean,hexo g两条命令后,是会把文件覆盖的。所以我直接修改了主题中的模板文件,这样在生成index.html的时候就会自动生成进来了。 修改themes-tyrant-layout-_partial-head.ejs文件把meta内容直接加入head即可。 ps:tyrant为博主使用的主题,修改时只需要修改自己主题下的此文件即可。]]></content>
</entry>
<entry>
<title><![CDATA[hexo 搭建 github pages 问题]]></title>
<url>%2Fposts%2Fhexo%2Fhexo-%E6%90%AD%E5%BB%BA-github-pages--%E9%97%AE%E9%A2%98.html</url>
<content type="text"><![CDATA[由于自己想搭建一个免费的博客,所以找到了万能的github,但在搭建的过程中遇到了几个小问题,所以记下来基本搭建的方法都是参考于利用github搭建博客这个博主已经写的很全了,搭建的过程就不一一提了说下遇到的问题 hexo上传后博客还是官方的建立了github pages的项目,并且使用了官方的建立建好了一个博客,官方默认会在master分支上开出一个gh-pages分支,然后查了官网资料说所有的内容都是显示gh-pages分支上的,但是我用hexo却传不到gh-pages分支上,并且传到了master分支内容也不会显示,后来找到原因是因为我建立的项目名字不是以#你的github帐户.github.io#命名的,所以会使hexo的上传程序传到master分支上也不会起作用,把项目名修改之后就可以了。 添加多说的问题在官网注册后,复制了多说代码,按教程做完之后发现多说模块不显示,所以查看了comment.ejs源代码<% if (config.disqus_shortname && page.comments){ %> 这里是2个判断,把config.disqus_shortname && 删掉之后,就可以正常使用了。]]></content>
</entry>
<entry>
<title><![CDATA[我为什么开始写博客]]></title>
<url>%2Fposts%2Funcategorized%2F%E6%88%91%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BC%80%E5%A7%8B%E5%86%99%E5%8D%9A%E5%AE%A2.html</url>
<content type="text"><![CDATA[最近参加了几场印象笔记的活动,听大家分享了一些自己坚持做的事,由于最近自己也在整理自己琐碎的事情,感觉虽然自己在技术上一直在积累但那都是自己的眼光和认识,如果与大家分享出来之后这样可以帮助自己有很大的提升。最近这一年来,在新的公司里自己有了很大的成长,但是发现这些成长记录其实只记录在我的印象笔记里或者是自己的经验上,并没有真正的分享出来,以后在这里记录自己遇到的问题以及一些正在学的技术,自己的理解和学习记录。希望在这里可以记录下自己技术成长的轨迹,以便和大家共同成长。]]></content>
</entry>
</search>