for (TreeNode<K,V> x = b, next; x != null; x = next) { next = (TreeNode<K,V>)x.next; //The next value of the incoming node is assigned to the next node of the current treebin node, which is used for data synchronization x.left = x.right = null; //null both the left subtree and the right subtree if (r == null) { x.parent = null;//Parent node is empty x.red = false; // Node that does not belong to red subtree r = x; //Store the red node temporarily for the next recycling } else { K k = x.key; //If it is not the root node, it is the next subtree node (belonging to the next value) int h = x.hash; // If it is not the root node, take out the hash value Class<?> kc = null; for (TreeNode<K,V> p = r;;) { int dir, ph; // K pk = p.key;//Get the key value of the root node if ((ph = p.hash) > h) // Get the hash value and judge whether it belongs to the hash value less than the root node dir = -1; //It belongs to the left subtree, else if (ph < h) // Otherwise, it belongs to the right subtree dir = 1; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) // When the hash values are equal, compare the class and key values dir = tieBreakOrder(k, pk); TreeNode<K,V> xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { // Judge whether the comparison result is less than zero and the left and right subtrees are null x.parent = xp;// Assign a value to the parent node if (dir <= 0) xp.left = x; // Judge whether it belongs to the left subtree (< = 0) else xp.right = x;// Judge whether it belongs to the right subtree (> 0) // Carry out the balanced insertion mechanism (in fact, establish a balanced relationship) r = balanceInsertion(r, x); break; } } } } this.root = r; // Root node assert checkInvariants(root);
}
### [](https://gitee.com/vip204888/java-p7)ForwardingNode One for connecting two table Node class for. It contains a nextTable Pointer to the next table.**And this node's key value next All pointers are null,its hash Value is-1. It's defined here find The method is from nextTable Instead of searching with itself as the head node, query the node in**
/**
- A node inserted at head of bins during transfer operations.
*/
static final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable; ForwardingNode(Node<K,V>[] tab) { super(MOVED, null, null, null); this.nextTable = tab; } // Loop through to find the corresponding hash and key values Node<K,V> find(int h, Object k) { // loop to avoid arbitrarily deep recursion on forwarding nodes outer: for (Node<K,V>[] tab = nextTable;;) { Node<K,V> e; int n; // If it cannot be located or located to null, it returns directly if (k == null || tab == null || (n = tab.length) == 0 || (e = tabAt(tab, (n - 1) & h)) == null) return null; for (;;) { int eh; K ek; // Traverse to find and compare the hash value and key value to analyze whether they are equal. if ((eh = e.hash) == h && ((ek = e.key) == k || (ek != null && k.equals(ek)))) // Return directly after finding return e; //If the hash value is - 1 or - 2 if (eh < 0) { // If it is forwardingNode, it means - 1 if (e instanceof ForwardingNode) { //Get directly to the next table. tab = ((ForwardingNode<K,V>)e).nextTable; continue outer; } else return e.find(h, k); } //Until null, return directly if ((e = e.next) == null) return null; } } }
}
[](https://gitee.com/vip204888/java-p7)Unsafe and CAS ----------------------------------------------------------------------------- > **stay ConcurrentHashMap You can see it everywhere Unsafe, Extensive use Unsafe.compareAndSwapXXX This method uses a CAS The algorithm realizes the operation of modifying the value without lock, which can greatly reduce the performance consumption of lock agent**. > The basic idea of this algorithm is to constantly compare whether the variable value in the current memory is equal to a variable value you specify. If it is equal, accept the modified value you specify, otherwise reject your operation. Because the value in the current thread is not the latest value, your modification is likely to overwrite the modification results of other threads. This is similar to the optimistic lock, SVN The idea is similar. ### []( https://gitee.com/vip204888/java-p7 )Unsafe static block unsafe The code block controls the modification of some attributes, such as the most commonly used ones SIZECTL . In this version concurrentHashMap In, it is widely used CAS Method to modify variables and attributes. utilize CAS Lock free operation can greatly improve performance.
//The offset of each field attribute can be obtained from the corresponding data object by offset + first address
private 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 = 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); }
}
#### []( https://gitee.com/vip204888/java-p7 )Three core methods ConcurrentHashMap Three atomic operations are defined to operate on nodes at specified locations. It is these atomic operations that ensure ConcurrentHashMap Thread safe.
@SuppressWarnings("unchecked")
//Get the Node at i position
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
* utilize CAS Algorithm settings i In position Node Node. Concurrency can be achieved because it specifies the value of the original node * stay CAS In the algorithm, it will compare whether the value in memory is equal to the value you specify. If it is equal, it will accept your modification, otherwise it will reject your modification * Therefore, the value in the current thread is not the latest value. This modification may overwrite the modification results of other threads, which is somewhat similar to SVN
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) { return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
//Use the volatile method to set the value of the node location
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
[](https://gitee.com/vip204888/java-p7) initialization method initTable --------------------------------------------------------------------------------- * about ConcurrentHashMap Generally speaking, calling its constructor is just setting some parameters. * whole table The initialization of is to ConcurrentHashMap Occurs when an element is inserted into the. * Such as call put,computeIfAbsent,compute,merge When waiting for a method, the call time is to check table==null. 1. The initialization method mainly applies the key attributes sizeCtl If this value <0,Indicates that other threads are initializing, so this operation is abandoned. 2. It can also be seen here ConcurrentHashMap Initialization of can only be completed by one thread. 3. If you get initialization permission, use CAS Method will sizeCtl Set as-1,Prevent other threads from entering. After initializing the array, the sizeCtl Change the value of to 0.75 \* n,The source code is as follows:
/**
- Initializes table, using the size recorded in sizeCtl.
*/
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { //sizeCtl indicates that another thread is initializing and suspending the thread. For the initialization of table, only one thread can be in progress. if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {//Use CAS method to set the value of sizectl to - 1, indicating that this thread is initializing try { if ((tab = table) == null || tab.length == 0) { int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = tab = nt; sc = n - (n >>> 2);//It is equivalent to 0.75*n. set a capacity expansion threshold } } finally { sizeCtl = sc; } break; } } return tab;
}
### []( https://gitee.com/vip204888/java-p7 )Capacity expansion method transfer * ConcurrentHashMap When the capacity is insufficient, it is necessary to table Capacity expansion. * The basic idea of this method is similar to HashMap It is very similar, but it is much more complex because it supports concurrent capacity expansion. * The reason is that it supports multi-threaded capacity expansion without locking. * I think the purpose of this is not just to meet concurrent Instead, we hope to use concurrent processing to reduce the time impact of capacity expansion. Because the operation of copying from one "array" to another "array" is always involved in capacity expansion. If this operation can be carried out concurrently, it is really excellent. #### []( https://gitee.com/vip204888/java-p7 )The whole capacity expansion operation is divided into two parts * The first part is to build a nextTable,Its capacity is twice that of the original, and this operation is completed by a single thread. This single thread guarantee is through`RESIZE_STAMP_SHIFT`This constant is guaranteed by one operation, which will be mentioned later; * **The second part is to table Copy elements from to nextTable In, multithreading is allowed**. #### []( https://gitee.com/vip204888/java-p7 )Let's take a look at how a single thread is completed: * Its general idea is the process of traversal and replication. Firstly, the number of times to traverse is obtained according to the operation i,Then use tabAt Method obtain i Elements of location: * If this position is empty, it is in**primary table**Medium i Position put**forwardNode node**,So is this**Key points to trigger concurrent capacity expansion**; * If this position is Node Node( fh>=0),**If it is the head node of a linked list, a reverse linked list is constructed**,Put them separately nextTable of i and i+n On the location of * **If this position is TreeBin Node( fh<0),Also do a reverse order processing and judge whether it is necessary untreefi,Put the processing results separately nextTable of i and i+n On the location of** * **After traversing all the nodes, the replication is completed. At this time, let nextTable As new table,And update sizeCtl 0 for new capacity.75 Times to complete capacity expansion**. #### []( https://gitee.com/vip204888/java-p7 )Let's take another look at how multithreading is accomplished: * If the traversed node is forward The node continues to traverse backward, and the mechanism of locking the node completes the control of multithreading. * Multithreading traverses a node, processes a node, and puts the value of the corresponding point set by forward,Another thread sees forward,Just traverse backwards. * This completes the replication. It also solves the problem of thread safety. The design of this method really makes me worship. ![img](https://img-blog.csdnimg.cn/img_convert/4c6dcc27cab4a76ef0abfa4f1f4b7671.png)
/**
- A transitional table can only be used during capacity expansion
*/
private transient volatile Node<K,V>[] nextTable;
/**
-
Moves and/or copies the nodes in each bin to new table. See
-
above for explanation.
*/
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride; if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; // subdivide range if (nextTab == null) { // initiating try { @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];//Construct a nextTable object with twice the original capacity nextTab = nt; } catch (Throwable ex) { // try to cope with OOME sizeCtl = Integer.MAX_VALUE; return; } nextTable = nextTab; transferIndex = n; } int nextn = nextTab.length; ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);//Construct a connected node pointer for flag bit boolean advance = true;//If the key attribute of concurrent capacity expansion is equal to true, it indicates that this node has been processed boolean finishing = false; // to ensure sweep before committing nextTab for (int i = 0, bound = 0;;) { Node<K,V> f; int fh; //The function of the while loop body is to control i -- through i -- to traverse the nodes in the original hash table in turn 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) { //If all nodes have finished copying, assign nextTable to table and empty the temporary object nextTable nextTable = null; table = nextTab; sizeCtl = (n << 1) - (n >>> 1);//The capacity expansion threshold is set to 1.5 times the original capacity, which is still 0.75 times the current capacity return; } //The CAS method is used to update the capacity expansion threshold, where the sizectl value is reduced by one, indicating that a new thread is added to participate in the capacity expansion operation 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 } } //If the traversed node is null, it is put into the ForwardingNode pointer else if ((f = tabAt(tab, i)) == null) advance = casTabAt(tab, i, null, fwd); //If you traverse the ForwardingNode node, it indicates that this point has been processed. Skip it directly. This is the core of controlling concurrent expansion else if ((fh = f.hash) == MOVED) advance = true; // already processed else { //Node locking synchronized (f) { if (tabAt(tab, i) == f) { Node<K,V> ln, hn; //If FH > = 0, it proves that this is a Node node if (fh >= 0) { int runBit = fh & n; //The following work is to construct two linked lists. One is the original linked list, and the other is the reverse order of the original linked list Node<K,V> lastRun = f; for (Node<K,V> p = f.next; p != null; p = p.next) { 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); } //Insert a linked list at the i position of the nextTable setTabAt(nextTab, i, ln); //Insert another linked list at the i+n position of nextTable setTabAt(nextTab, i + n, hn); //Inserting a forwardNode node at the i position of the table indicates that the node has been processed setTabAt(tab, i, fwd); //Set advance to true and return to the while loop above to perform i-- operations advance = true; } //Processing the TreeBin object is similar to the above procedure 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; //Construct positive order and reverse order linked lists 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; } } //If the tree structure is no longer needed after capacity expansion, it is inversely converted to a linked list structure 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; //Insert a linked list at the i position of the nextTable setTabAt(nextTab, i, ln); //Insert another linked list at the i+n position of nextTable setTabAt(nextTab, i + n, hn); //Inserting a forwardNode node at the i position of the table indicates that the node has been processed setTabAt(tab, i, fwd); //Set advance to true and return to the while loop above to perform i-- operations advance = true; } } } } }
}
### []( https://gitee.com/vip204888/java-p7 )Put method > ConcurrentHashMap The most commonly used is put and get Two methods 1. according to hash Calculate the value of this newly inserted point table Location in i,If i If the position is empty, put it in directly, otherwise judge, if i The position is a tree node. Insert a new node in the way of a tree, otherwise i Insert to the end of the linked list. 2. **ConcurrentHashMap One of the most important differences is that we still use this idea ConcurrentHashMap not allow key or value by null value**. * **In addition, since multithreading is involved, put The method is a little more complicated. There are two possible situations in multithreading** 3. **If one or more threads are ConcurrentHashMap For capacity expansion, the current thread should also enter the capacity expansion operation**. * The reason why this expansion operation can be detected,**Because transfer Method is inserted on an empty node forward node**,If the position to be inserted is detected**forward**If the node is occupied, it helps to expand the capacity; * **If it is detected that the node to be inserted is not empty and not empty forward Node, lock this node to ensure thread safety**. Although this has some impact on efficiency, it will be better than hashTable of synchronized Much better. * **The overall process is to first define not allowed key or value by null For each placed value, first use spread Method pair key of hashcode Once hash Calculation to determine this value in table Location in**. If this position is empty, it can be placed directly without locking. If there is a node at this location, it indicates that something has happened hash Collision, first judge the type of this node. If it is a linked list node( fh>0),The resulting node is hash The head node of the linked list composed of nodes with the same value. You need to traverse backward in turn to determine the location of the newly added value. If encountered hash Value and key If the values are consistent with the newly added node, you only need to update value Value. Otherwise, traverse backward in turn until the end of the linked list is inserted into this node. If the length of the linked list after adding this node is greater than 8, the linked list will be converted into a red black tree. If the type of this node is already a tree node, directly call the insertion method of the tree node to insert a new value.
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//null key or value is not allowed if (key == null || value == null) throw new NullPointerException(); //Calculate hash value int hash = spread(key.hashCode()); int binCount = 0; //When does the loop insert successfully and jump out for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; //If the table is empty, initialize the table if (tab == null || (n = tab.length) == 0) tab = initTable(); //Calculate the position in the table according to the hash value else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //If this position has no value, put it directly without locking if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } //When a table join point is encountered, you need to consolidate the table else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { V oldVal = null; //The node here can be understood as the head node of the linked list with the same hash value synchronized (f) { if (tabAt(tab, i) == f) { //fh > 0 indicates that this node is a linked list node, not a tree node if (fh >= 0) { binCount = 1; //Traverse all nodes of the linked list here for (Node<K,V> e = f;; ++binCount) { K ek; //If the hash value and the key value are the same, modify the value value of the corresponding node if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } Node<K,V> pred = e; //If you traverse to the last node, it proves that the new node needs to be inserted, so you insert it at the end of the linked list if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value, null); break; } } } //If the node is a tree node, the value is inserted in the form of a tree 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; } } } } if (binCount != 0) { //If the length of the linked list has reached the critical value 8, you need to convert the linked list into a tree structure if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } //Add the number of elements of the current ConcurrentHashMap + 1 addCount(1L, binCount); return null;
}
### []( https://gitee.com/vip204888/java-p7 )Helptransfer method This is a method to assist in capacity expansion. When this method is called, the current ConcurrentHashMap There must be already nextTable Object, get this first nextTable Object, calling transfer method. Look back at the above transfer Method can be seen that when this thread enters the capacity expansion method, it will directly enter the replication phase.
/**
- Helps transfer if a resize is in progress.
*/
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc; if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) { int rs = resizeStamp(tab.length);//Calculate an operation check code while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) { if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
summary
Generally, large enterprises like this have several rounds of interviews, so they must take some time to collect and sort out the company's background and corporate culture. As the saying goes, "know yourself and know the enemy. You will never be defeated in a hundred battles". Don't go to the interview blindly. Many people are concerned about how to talk about salary with HR.
Here's a suggestion. If your ideal salary is 30K, you can talk to HR about 33~35K instead of exposing your cards at once. However, you can't say it so directly. For example, your company was 25K. You can tell HR about the original salary and what you can give me? You said I hope to have a 20% salary increase.
Finally, say a few more words about the recruitment platform. In short, before submitting your resume to the company, please confirm what the company is like. Go to Baidu first. Don't be trapped. There are some ill intentioned advertising parties waiting for you on each platform. Don't be fooled!!!
Provide [free] Java architecture learning materials, including: Spring, Dubbo, MyBatis, RPC, source code analysis, high concurrency, high performance, distributed, performance optimization, advanced architecture development of microservices, etc.
A full set of advanced Java data points are available here for free
There are also Java core knowledge points + a full range of architects' learning materials and videos + a big factory interview Treasure + interview template, you can receive + Ali, NetEase, Tencent, Iqiyi, Kwai, Iqiyi, beep, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li Yuan, +Spring, the source code of the interview, and the book is about the real e-book of the Java structure.
if a resize is in progress.
*/
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc; if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) { int rs = resizeStamp(tab.length);//Calculate an operation check code while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) { if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
summary
Generally, large enterprises like this have several rounds of interviews, so they must take some time to collect and sort out the company's background and corporate culture. As the saying goes, "know yourself and know the enemy. You will never be defeated in a hundred battles". Don't go to the interview blindly. Many people are concerned about how to talk about salary with HR.
Here's a suggestion. If your ideal salary is 30K, you can talk to HR about 33~35K instead of exposing your cards at once. However, you can't say it so directly. For example, your company was 25K. You can tell HR about the original salary and what you can give me? You said I hope to have a 20% salary increase.
Finally, say a few more words about the recruitment platform. In short, before submitting your resume to the company, please confirm what the company is like. Go to Baidu first. Don't be trapped. There are some ill intentioned advertising parties waiting for you on each platform. Don't be fooled!!!
Provide [free] Java architecture learning materials, including: Spring, Dubbo, MyBatis, RPC, source code analysis, high concurrency, high performance, distributed, performance optimization, advanced architecture development of microservices, etc.
A full set of advanced Java data points are available here for free
There are also Java core knowledge points + a full range of architects' learning materials and videos + a big factory interview Treasure + interview template, you can receive + Ali, NetEase, Tencent, Iqiyi, Kwai, Iqiyi, beep, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li, Li Yuan, +Spring, the source code of the interview, and the book is about the real e-book of the Java structure.