Whether programmers work, study, or things in life. Can follow such a principle: "do simple things repeatedly, and do the right things repeatedly." such efforts will make you go on the right path and avoid many detours. From a young driver to an old driver.
In the previous section, you should have mastered the capacity expansion principle of ArrayList, the System.arrayCopy method, and some ideas and methods of looking at the source code. This section is more about practicing the ideas and methods learned. It takes you to quickly touch the source code principles of other common methods of ArrayList and see some of their highlights. This section also allows you to briefly understand the fail fast mechanism and what the previous modCount did.
Familiar with the road, scan the set and get methods of ArrayList
First, you need to modify your Demo as follows:
import java.util.ArrayList; import java.util.List; public class ArrayListDemo { public static void main(String[] args) { List<String> hostList = new ArrayList<>(); hostList.add("host1"); hostList.add("host2"); hostList.add("host3"); System.out.println(hostList.set(1, "host4")); System.out.println(hostList.get(1)); } }
In the above code, suppose you add three host addresses to the hostList through the add method. Then the content of position 1 is replaced with the set method, and the return value is printed. After that, we call the get method to get the element of position 1 and check whether the replacement is successful. The above logic diagram shows the code:
An additional point needs to be mentioned here is that there is a principle of operation and maintenance, that is, after the operation is completed, the command and script must be checked! check! For example, you must get after you set here. In fact, it's not just operation and maintenance. You should do this many times, such as back testing online, checking after executing SQL, self testing code, etc... you must keep this idea in mind and draw inferences from one instance.
Without much to say, let's look directly at the source code. The first is the set method:
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; } E elementData(int index) { return (E) elementData[index]; } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private String outOfBoundsMsg(int index) { return "Index: "+index+", Size: "+size; }
You can know from comments or API usage that the set method is used to replace elements at a certain location. adopt
You can see the context of the set method in the source code:
- The first step, rangeCheck, is obviously a range check, which is a verification action;
- The second step is the elementData(int index) method. This method obtains the elements by array subscript. The basic array operation records the original value through oldValue;
- The third step is to assign values through the array subscript, elementData[index] =element;, Finally, the oldValue of the previous record is returned.
In fact, the source code is very simple, which more deeply reflects the principle of using arrays at the bottom of ArrayList. If you write a custom List by hand, you can refer to this idea.
The source code logic is shown in the figure below:
Next, let's take a quick look at the get method:
public E get(int index) { rangeCheck(index); return elementData(index); }
It can be seen that the context of the get method is simpler, that is, the range is checked and verified, and then the elements are obtained through the basic array operation and the array subscript.
It is worth mentioning that the methods of JDK source code encapsulation are not too long, very clear and reusable. This coding style is worthy of our reference. However, it can not be too concise, and the readability will be reduced. JDK has this problem, which is also because most JAVA giants like extremely concise code, which is understandable.
Here, the source code logic of set and get is shown in the following figure:
Change the soup without changing the dressing, remove series methods
I believe that after you have seen the add, get, set and other methods, you have become more and more proficient and on the road. Now let's take a look at the methods of the remove series of ArrayList. In fact, the underlying principle of the source code is the System.arraycopy.
The remove system method is shown in the following figure:
These are the remove methods in ArrayList. I won't write any examples. I believe you can read the source code directly.
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }
The context above is very clear. The two key lines are to calculate the number of moving elements and copy elements from the original array to the original array. You should already know what the other lines do, so I won't repeat them here.
The source code logic is shown in the figure below:
The copy of System.arraycopy is generally not easy to understand, so we can better understand it by taking an example:
You should be familiar with this sentence. Now you need to move 3 elements from the position 2 of the original array to the target array, and overwrite it from the position 1 of the target array. Here, both the source and target are themselves, and the result will become elementData [0,2,3,4,4].
The last sentence of the remove source code elementData[--size]= null; The array will become elementData [0,2,3,4,null], which allows the GC to help recycle the null value, and size -- the size of the array will be reduced by 1.
Is the method of remove(int index) very simple? Then you can see the difference between the remove(Obejct o) method and it:
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
The context of this remove method is mainly two ifs. There is a for loop in each if, which is traversing the entire array for value comparison. If the first matching element is found, the fastRemove(index) method is called and returned directly. What do you mean? Let's take an example:
public static void main(String[] args) { List<String> hostList = new ArrayList<>(); hostList.add("host1"); hostList.add("host2"); hostList.add("host3"); hostList.add("host2"); hostList.add(null); hostList.add(null); System.out.println("Before deletion:"+hostList); hostList.remove("host2"); //Only the first matching element is removed hostList.remove(null); //Only the first matching element is removed System.out.println("After deletion:"+hostList); }
From the output, we can know that only the first qualified element has been deleted. Note that if you want to delete all matching elements, you can use the removeIf() method. What did fastRemove do? It can be found that it is surprisingly similar to remove(int index), and there is no difference.
private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData,index+1,elementData, index, numMoved); elementData[--size] = null; }
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; return oldValue; }
The difference may be that rangeCheck and elementData(index) get elements.
Removehange and removeAll, you can go and see for yourself. It's really a change of soup without dressing, or just System.arraycopy. As for the removeIf method, we will talk about the fail fast mechanism in the next section, and we will briefly mention it in the next section.
Here you can summarize as follows:
Highlight method in the remove series: removeif()
In this section, let's finally look at the removeif() method. It actually has a good idea for everyone to learn from. Let's look directly at the code:
public boolean removeIf(Predicate<? super E> filter) { Objects.requireNonNull(filter); // figure out which elements are to be removed // any exception thrown from the filter predicate at this stage // will leave the collection unmodified int removeCount = 0; final BitSet removeSet = new BitSet(size); final int expectedModCount = modCount; final int size = this.size; for (int i=0; modCount == expectedModCount && i < size; i++) { @SuppressWarnings("unchecked") final E element = (E) elementData[i]; if (filter.test(element)) { removeSet.set(i); removeCount++; } } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } // shift surviving elements left over the spaces left by removed elements final boolean anyToRemove = removeCount > 0; if (anyToRemove) { final int newSize = size - removeCount; for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) { i = removeSet.nextClearBit(i); elementData[j] = elementData[i]; } for (int k=newSize; k < size; k++) { elementData[k] = null; // Let gc do its work } this.size = newSize; if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; } return anyToRemove; }
The above method is relatively long, but the whole context is still very clear:
The first step is to find the matching elements that meet the conditions through BitSet and for loop, and only record the position index into BitSet.
The second step is to exchange elements through the for loop as long as there are qualified elements. System.arrayCopy is not used here.
The third step is to set the useless position to null after the exchange is completed through the for loop.
Step 4 returns the number of deleted elements
You can enter the source code, try to draw its flow chart and practice. Here is a schematic diagram:
Here I would like to focus on its fail fast mechanism:
You can notice that modCount and expectedModCount have been used for judgment throughout the process. What is this used for? These two values mean that if removeIf is executed and the qualified elements are deleted, no other thread can modify the current ArrayList. If other threads perform add, remove and other operations, the modCount will certainly change. In the process of removeIf execution, if it is found that the modCount is inconsistent with the expectedModCount at the beginning of the execution method, it will report ConcurrentModificationException. Concurrent modification exception, resulting in deletion failure. Concurrency is a fail fast mechanism, which can make the current thread fail quickly without resource competition and lock. This also leads to the fact that the ArrayList collection class is not thread safe and cannot operate concurrently.
There are two main highlights of the whole removeIf: one is to use BitSet to record the location, which saves space and has de duplication. Many times, we only need to record the location or index, and there is no need to record the whole element. One is the application of fail fast mechanism, which skillfully maintains modCount to quickly fail when a resource is updated concurrently.
The entire remove series does not use copies except removeIf. When there are many or frequent copies of elements in the ArrayList, there are great performance problems, and remove(Objecto) deletes the first matching element, which should also be noted.
More importantly, we must be more and more familiar with the idea of reading the source code. First find out the context, and then look at the details. You can guess the method name, notes and experience, grasp the big and let go of the small, learn to give examples, draw pictures, etc. If you already feel familiar with the road, it means that you are already on the way to read the source code and start on the road. I believe that as long as you continue to follow the growth of JDK source code, it will lay a solid foundation for you to read more difficult source code later.
Golden sentence dessert
In addition to today's growth of knowledge and skills, it brings you a golden sentence dessert. End my sharing today: example is more important than persuasion.
In fact, many people, many times, are not watching what you say, but what you do. For example, when I came home one day, I always liked to throw away my coat and pants, but my wife was a clean person and always wanted me to hang my clothes on the hanger. But I'm always used to throwing it away, but she never complains that I've messed up the sofa or bed. She always hangs up her clothes. Over time, I think hanging up really makes the home cleaner and more comfortable. Later, I gradually hung my clothes on the hanger. In fact, this is the embodiment that example is more important than persuasion. If you want your children to eat, don't always don't play with your mobile phone. You have to do it yourself, don't you? If you want your child to read an article every day, you should do it yourself and read a growth story every day, don't you?
Finally, you can ask your colleagues or classmates after reading the source code. You can also share it and tell him.
Welcome to leave a message and communicate with me in the comment area. If you can, you can click the "watching" button to share it with more people in need.
(statement: the growth of JDK source code is based on JDK version 1.8, and some chapters will mention the characteristics of the old version)
This article is composed of blog one article multi posting platform OpenWrite release!