Rookie show you the source code - can't understand you hit me ArrayList source code analysis (based on java 8)

Keywords: Programming less JDK Windows

Article directory

It's not hard to read the source code.

How to learn programming well? How to write high quality code? How to improve your programming ability quickly? And so on, under a series of questions, we usually see the same answer - look at the source code. But, every time you click on the source code of JDK, it shuts down again in less than five minutes. Why? Because I can't understand, because it's too boring and so on. To sum up, it's a painful thing to look at the source code.

In fact, the reason why I find it painful to read the source code is not that the source code is too difficult. In fact, those who write JDK are very good programmers, and their code is clear logic, naming specifications, basically no comment can also very clearly express what a piece of code is, not to mention the rich comments in the source code. So the fundamental reason is not that the code written by the gods is obscure and difficult to understand.

Learning source code is far less difficult than you think. Take ArrayList as an example, first of all, see its structure.

  • Member variables
  • Construction method
  • Core method
  • Inner class

When we study the source code of a class, we first focus on these blocks. Membership variables are not difficult. It is easy to know their uses by naming and annotation.

software environment

  • Windows 10
  • JDK 1.8.0_192
  • IDEA 2018.2

Membership variables:

private static final long serialVersionUID = 8683452581122892189L;
//Default capacity
private static final int DEFAULT_CAPACITY = 10;
//An array of empty objects, used to construct an array of empty objects
private static final Object[] EMPTY_ELEMENTDATA = {};
//An empty object array, which is used to initialize the object array by default constructor
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//An array of objects for storing data
transient Object[] elementData; 
//Number of elements in List (not the same as capacity)
private int size;

Construction method

Starting with the construction method, it's not difficult to see what you did when you created an ArrayList object.

//By default, create an empty list object
public ArrayList() {
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//Constructing method with initial capacity parameter to create a list object with specified capacity
public ArrayList(int initialCapacity) {
	if (initialCapacity > 0) {
		this.elementData = new Object[initialCapacity];
	} else if (initialCapacity == 0) {
		this.elementData = EMPTY_ELEMENTDATA;
	} else {
		throw new IllegalArgumentException("Illegal Capacity: "+
										   initialCapacity);
	}
}

//Create a list object with the specified element by constructing the method with the specified element
public ArrayList(Collection<? extends E> c) {
	elementData = c.toArray();
	if ((size = elementData.length) != 0) {
		if (elementData.getClass() != Object[].class)
			elementData = Arrays.copyOf(elementData, size, Object[].class);
	} else {
		this.elementData = EMPTY_ELEMENTDATA;
	}
}

The three constructions of ArrayList are very simple and straightforward. Basically, people with programming experience will not have much difficulty in understanding them.

Core method

Next, we look at its core approach. The three most closely related approaches are add, get and remove. ArrayList's get and remove methods are basically at a level of difficulty with their construction methods, so let's take a brief look at:

get method

//Get the corresponding elements according to the index
public E get(int index) {
	rangeCheck(index);
	return elementData(index);
}

//Judge if an index has subscripts that cross the bounds
private void rangeCheck(int index) {
	if (index >= size)
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

OK, as we said above, get method is so simple and direct.

remove method

The remove method is also more complex than the get method at the level of a constructor.

public E remove(int index) {
	//Check if subscripts are out of bounds
	rangeCheck(index);

	modCount++;
	E oldValue = elementData(index);

	int numMoved = size - index - 1;
	if (numMoved > 0)
		//Element Data Source Array, index+1 Start Location, Element Data Target Array, index Target Array Start Location, numMoved Number of Elements to Replicate
		System.arraycopy(elementData, index+1, elementData, index,
						 numMoved);
	//Empty the end element and wait for garbage collection
	elementData[--size] = null; 

	return oldValue;
}

A picture is better than a thousand words.

ArrayList has several remote methods, but they are similar, so I won't dwell on them here. If you understand one, you'll have no problem with the rest.

add method

Next let's look at the add method:

public boolean add(E e) {
	//Ensure adequate capacity before adding elements
	ensureCapacityInternal(size + 1);  
	elementData[size++] = e;
	return true;
}
private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

//Computational capacity expansion
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
	modCount++;

	if (minCapacity - elementData.length > 0)
		//Capacity expansion
		grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
	int oldCapacity = elementData.length;
	
	//Shift operations in Java are based on binary operations, so moving one bit to the right is equivalent to dividing by two.
	//The new capacity is 1.5 times that of the original.
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	
	//If the new capacity is less than the minimum capacity, then the minimum capacity is taken as the new capacity.
	if (newCapacity - minCapacity < 0)
		newCapacity = minCapacity;
	
	//If the new capacity is larger than MAX_ARRAY_SIZE, perform hugeCapacity() to compare min Capacity with MAX_ARRAY_SIZE
	//If minCapacity is greater than MAX_ARRAY_SIZE, the new capacity is Integer.MAX_VALUE, otherwise, the new capacity size is MAX_ARRAY_SIZE.
	if (newCapacity - MAX_ARRAY_SIZE > 0)
		newCapacity = hugeCapacity(minCapacity);

	elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
	if (minCapacity < 0) // overflow
		throw new OutOfMemoryError();
		
	//If minCapacity is large, use Integer.MAX_VALUE as the size of the new array
	//If MAX_ARRAY_SIZE is large, take MAX_ARRAY_SIZE as the size of the new array
	return (minCapacity > MAX_ARRAY_SIZE) ?
		Integer.MAX_VALUE :
		MAX_ARRAY_SIZE;
}

MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8

Next, we analyze a complete process of adding extensions. When an ArrayList object list adds the first element, the calculateCapacity() method obtains a value of 10 for minCapacity. Then enter the grow() method, where new Capacity is less than MAX_ARRAY_SIZE, list is expanded to 10, list.size() = 1.

Then add 2, 3, 4... Up to the Eleventh element, it was found that the capacity was insufficient and needed to be expanded again. Since the new capacity of each expansion is 1.5 times the original capacity, after entering growth (), new capacity = 15, list.size() = 11. And so on.

End

OK, the core content of ArrayList has been analyzed. How about that? Do you think it's simple and easy to understand? If this article is helpful to you, please give me a compliment to encourage me to continue to share more and better content with you!

Posted by kellz on Sat, 26 Jan 2019 11:24:14 -0800