The implementation of basic type dynamic array

Linear structure

Several concepts

  • E1 means the first node with index 0, and the last node or element with index n
  • E1 is the precursor node of E2
  • E2 is the successor node of E1

Linear tables: arrays, linked lists, queues, hashes

An array in Java is a linear table that stores data sequentially. The memory address of the elements is contiguous, and the capacity of the array is determined at creation and cannot be modified. So how do you create a dynamic array?

Dynamic array implementation

Start by creating a Maven project and adding a test dependency to unit test an interface or method

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
Copy the code

Elements and the number of elements in the array size. Create a new dynamic array BasicArrayList, which contains the definitions, constructors,toString(), etc. Set the dynamic array to store only basic int data

public class BasicArrayList {

    private int size;

    private int[] elements;

    private static final int DEFAULT_CAPATICY = 10;

    public ArrayList(int capaticy){
        // If capaticy < 10 use the default capacity, otherwise use the custom capacity
        capaticy = (capaticy < DEFAULT_CAPATICY) ? DEFAULT_CAPATICY : capaticy;
        elements = new int[capaticy];
    }

    public ArrayList(a){
        elements = new int[DEFAULT_CAPATICY];
    }
    
    @Override
    public String toString(a) {
        return "ArrayList{" +
                "size=" + size +
                ", elements=" + Arrays.toString(elements) +
                '} '; }}Copy the code

The following methods need to be implemented:

void clear(a); // Clear all elements

int size(a); // Returns the number of elements in the array

boolean isEmpty(a); // Check whether the array is empty

boolean contains(T element); // Check whether the array contains

void add(T element); // Add elements to the end of the array

T get(int index); // Gets the element of the specified index

T set(int index, T element); // Sets the element at the specified position and returns the original element at that position

void add(int index, T element); // Inserts the element at the specified position

T remove(int index); // Remove the element at the specified position and return the element

int indexOf(T element); // Gets the index of the specified element
Copy the code

Start with simple methods size(),isEmpty(),get(int index)

public int size(a){
    return size;
}

public boolean isEmpty(a){
    return size == 0;
}

public int get(int index){
    // Index needs to be checked
    if (index < 0 || index >= size){
        throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
    }
    return elements[index];
}
Copy the code

Create a new test class BasicArrayListTest to test size() and isEmpty

public class BasicArrayListTest {

    @Test
    public void size(a) {
        BasicArrayList arrayList = new BasicArrayList(10);
        Assert.assertEquals(0,arrayList.size());
    }

    @Test
    public void isEmpty(a) {
        BasicArrayList arrayList = new BasicArrayList(10); Assert.assertTrue(arrayList.isEmpty()); }}Copy the code

Implement the set(),indexOf(),contains(), and clear() methods

public int set(int index, int element){
    // Check index
    if (index < 0 || index >= size){
        throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
    }

    int old = elements[index];
    elements[index] = element;
    return old;
}

// Define a constant to return when indexOf() does not find an element
privare static int final ELEMENT_NOT_FOUND = -1;


public int indexOf(int element){
    // Iterate over all elements
    for (int i = 0; i < size; i++) {
        if (elements[i] == element) return i;
    }
    return ELEMENT_NOT_FOUND;
}

public boolean contains(int element){
    // Call the indexOf method to see if the return value is -1
    returnindexOf(element) ! = ELEMENT_NOT_FOUND; }public void clear(a){
    size = 0;
}
Copy the code

Remove () method implementation

Note that the size is reduced by one after successful deletion

public int remove(int index){

    // Start with index
    if (index < 0 || index >= size){
        throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
    }

    int old = elements[index];
    // Only iterate over the parts that need to be moved, not the entire array
    for (int i = index + 1; i < size; i++) {
        elements[i - 1] = elements[i];
    }
    size --;
    return old;
}
Copy the code

The add method is added to the end of the array by default, regardless of capacity

public void add(int element){
    elements[size++] = element;
}
Copy the code

Test the method implemented above

public class BasicArrayListTest {

    BasicArrayList arrayList = null;

    @Before
    public void init(a){
        arrayList = new BasicArrayList();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(4);
        arrayList.add(3);
        arrayList.add(8);
        arrayList.add(5);
        arrayList.add(7);
        arrayList.add(6);
    }

    @Test
    public void size(a) {
        Assert.assertEquals(8,arrayList.size());
        System.out.println("Dynamic array arrayList size is:" + arrayList.size());
    }

    @Test
    public void isEmpty(a) {
        Assert.assertFalse(arrayList.isEmpty());
        System.out.println(Is the dynamic array arrayList empty? + arrayList.isEmpty());
    }

    @Test
    public void get(a) {
        int i = arrayList.get(1);
        Assert.assertEquals(2,arrayList.get(1));
        System.out.println("The element in the arrayList with index 1 is:" + arrayList.get(1));
    }

    @Test
    public void set(a) {
        System.out.println("Before modification," + arrayList.toString());
        arrayList.set(1.10);
        System.out.println("Replace the element at index 1 with 10," + arrayList.toString());
    }

    @Test
    public void indexOf(a) {
        Assert.assertEquals(7,arrayList.indexOf(6));
        System.out.println("The element at index 6 is:" + arrayList.indexOf(6));
    }

    @Test
    public void contains(a) {
        Assert.assertTrue(arrayList.contains(1));
        System.out.println(Does it contain element 1? + arrayList.contains(1));
    }

    @Test
    public void clear(a) {
        System.out.println("There is no greater clarity," + arrayList.toString());
        arrayList.clear();
        System.out.println("After clearing, get the element with index 0." + arrayList.get(1));
    }

    @Test
    public void remove(a) {
        System.out.println("Before deleting," + arrayList.toString());
        arrayList.remove(1);
        System.out.println("Drop the element in index 1," + arrayList.toString());
    }

    @Test
    public void add(a) {
        System.out.println(arrayList.toString());
        arrayList.add(9); System.out.println(arrayList.toString()); }}Copy the code

The add() method is overridden to add elements in specific locations

// Add elements at specific locations
public void add(int index, int element){
    // Index can be equal to size, which is equivalent to adding elements at the end
    if (index < 0 || index > size){
        throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
    }
    // To add an element to the specified index, move the index position and subsequent elements one index back to make room
    // Move the element, starting from the last one, otherwise the latter one will be covered by the previous one
    for (int i = size - 1; i > index; i--) {
        elements[i + 1] = elements[i];
    }
    elements[index] = element;
    / / to increase the size
    size ++;
}
Copy the code

test

@Test
public void add(a) {
    System.out.println(arrayList.toString());
    arrayList.add(9);
    System.out.println(arrayList.toString());
    arrayList.add(2.11);
    System.out.println(arrayList.toString());
}
Copy the code

0 represents the free position in the array

Add (int Element) can be optimized to

public void add(int element){

    // Add elements to the end of the array
    add(size,element);
}
Copy the code

Address the Achilles heel of arrays – inability to dynamically expand

If array capacity quickly filled, you will need to apply for a space to memory used to store data, make variable pointing to the new memory space, the original memory space without variable pointing to, will be recovered, recovery before need the original copy of the array data to the new array, and the capacity of the new array extension 2 times or a according to the use of appropriate multiples

Add a capacity expansion function

// Capacity expansion method 1: Use specified capacity expansion. In this method, you can determine when capacity expansion is required and pass in the capacity expansion size when calling
private void expansionCapacity(int newCapacity){ 
    int oldCapacity = elements.length;
    if (oldCapacity >= size + 1) return;

    // Create an array of new capacities
    // newCapacity = oldCapacity + (oldCapacity >> 1);
    int[] newElements = new int[newCapacity];
    // Copy the element
    for (int i = 0; i < size; i++) {
        newElements[i] = elements[i];
    }
    // Points to a new memory space

    elements = newElements;
    System.out.println("Capacity expansion succeeded." + oldCapacity + "Extend to" + newCapacity);
}
Copy the code

Add the add(int index, int Element) method after checking the index

// Add elements at specific locations
public void add(int index, int element){
    // Index can be equal to size, which is equivalent to adding elements at the end
    if (index < 0 || index > size){
        throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
    }

    // If the capacity reaches the critical point, the system automatically expands the capacity by 1.5 times
    expansionCapacity(elements.length + (elements.length >> 1));

    // Move the element, starting from the last one, otherwise the latter one will be covered by the previous one
    for (int i = size - 1; i > index; i--) {
        elements[i + 1] = elements[i];
    }
    elements[index] = element;
    / / to increase the size
    size ++;
}
Copy the code

Optimize again

Extract the judgment index as a static method in the utility class, create a utils package, and add the utility class CheckUtils

public class CheckUtils {

    private static void outOfBoundary(int index,int size){
        throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
    }
    public static void checkIndex(int index, int size){
        if (index < 0|| index >= size){ outOfBoundary(index, size); }}public static  void checkIndex4Add(int index, int size){
        if (index < 0|| index > size){ outOfBoundary(index, size); }}}Copy the code

The final BasicArrayList code is

public class BasicArrayList {

    // Number of elements
    private int size;

    / / array
    private int[] elements;

    private static final int DEFAULT_CAPATICY = 10;

    private static final int ELEMENT_NOT_FOUND = -1;

    public BasicArrayList(int capaticy){
        // If capaticy < 10 use the default capacity, otherwise use the custom capacity
        capaticy = (capaticy < DEFAULT_CAPATICY) ? DEFAULT_CAPATICY : capaticy;
        elements = new int[capaticy];
    }

    public BasicArrayList(a){
        elements = new int[DEFAULT_CAPATICY];
    }

    public int size(a){
        return size;
    }

    public boolean isEmpty(a){
        return size == 0;
    }

    public int get(int index){
        CheckUtils.checkIndex(index,size);
        return elements[index];
    }

    public int set(int index, int element){
        CheckUtils.checkIndex(index,size);

        int old = elements[index];
        elements[index] = element;
        return old;
    }

    public int indexOf(int element){
        for (int i = 0; i < size; i++) {
            if (elements[i] == element) return i;
        }
        return ELEMENT_NOT_FOUND;
    }

    public boolean contains(int element){
        returnindexOf(element) ! = ELEMENT_NOT_FOUND; }// Empty means that no element can be accessed
    public void clear(a){
        size = 0;
    }

    public int remove(int index){

        // Start with index
        CheckUtils.checkIndex(index,size);

        int old = elements[index];
        // Only iterate over the parts that need to be moved, not the entire array
        for (int i = index + 1; i < size; i++) {
            elements[i - 1] = elements[i];
        }
        size --;
        return old;
    }

    public void add(int element){

        // Add elements to the end of the array
        add(size,element);
    }

    // Add elements at specific locations
    public void add(int index, int element){
        // Index can be equal to size, which is equivalent to adding elements at the end
        CheckUtils.checkIndex4Add(index,size);

        // Add an element, expand if capacity exceeds
        // If the capacity reaches the critical point, it will be automatically expanded by 1.5 times
        expansionCapacity(elements.length + (elements.length >> 1));

        // Move the element, starting from the last one, otherwise the latter one will be covered by the previous one
        for (int i = size - 1; i > index; i--) {
            elements[i + 1] = elements[i];
        }
        elements[index] = element;
        / / to increase the size
        size ++;
    }

    // The expanded capacity is passed in at the discretion of the method
    private void expansionCapacity(int newCapacity){
        int oldCapacity = elements.length;
        if (oldCapacity >= size + 1) return;

        // Create an array of new capacities
        // newCapacity = oldCapacity + (oldCapacity >> 1);
        int[] newElements = new int[newCapacity];
        // Copy the element
        for (int i = 0; i < size; i++) {
            newElements[i] = elements[i];
        }
        // Points to a new memory space

        elements = newElements;
        System.out.println("Capacity expansion succeeded." + oldCapacity + "Extend to" + newCapacity);
    }

    private void ensureCapacity(int capacity){
        int oldCapacity = elements.length;
        if (oldCapacity >= capacity) return;
        // Create an array of new capacities
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        int[] newElements = new int[newCapacity];
        // Copy the element
        for (int i = 0; i < size; i++) {
            newElements[i] = elements[i];
        }
        // Points to a new memory space
        elements = newElements;
        System.out.println("Capacity expansion succeeded." + oldCapacity + "Extend to" + newCapacity);
    }

    @Override
    public String toString(a) {
        return "ArrayList{" +
                "size=" + size +
                ", elements=" + Arrays.toString(elements) +
                '} '; }}Copy the code

Test the add

@Test
public void add(a) {
    System.out.println(arrayList.toString());
    arrayList.add(9);
    System.out.println(arrayList.toString());
    arrayList.add(2.11);
    System.out.println(arrayList.toString());
    arrayList.add(12);
    System.out.println(arrayList.toString());
}
Copy the code

Use generics

In order for dynamic arrays to hold multiple types of data, it is necessary to modify them with generics, using T to represent generics, creating generic arrays with new Object[], and then forcing them to receive with T[], since Object is the parent of all classes, i.e

T[] elements = (T[]) new Object[];
Copy the code

Create a new Entity package and add an entity class Porsche with a name property, getter methods, setter methods, toString methods, and parameter and no-parameter constructors

public class Porsche {

    private String name;

    public String getName(a) {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Porsche(String name) {
        this.name = name;
    }

    public Porsche(a) {}}Copy the code

Object memory Management

Copy the BasicArrayList, rename it ArrayList, change int to generic T, and the dynamic array holds the object which is actually the access address of the object

Refactoring the clear method, clearing means that the pointer to the memory address stored in the array is all pointing to NULL

// Empty means that no element can be accessed
public void clear(a){
    for (int i = 0; i < size; i++) {
        elements[i] = null;
    }
    size = 0;
}
Copy the code

The remove method, which requires that the reference to the memory address in the last element be null

public T remove(int index){

    // Start with index
    if (index < 0 || index >= size){
        throw new IndexOutOfBoundsException("index=" + index + ",size=" + size);
    }

    T old = elements[index];
    // Only iterate over the parts that need to be moved, not the entire array
    for (int i = index + 1; i < size; i++) {
        elements[i - 1] = elements[i];
    }
    size --;
    // The last one points to empty
    elements[size] = null;
    return old;
}
Copy the code

The code of ArrayList after reconstruction is

public class ArrayList<T> {

    // Number of elements
    private int size;

    / / array
    private T[] elements;

    private static final int DEFAULT_CAPATICY = 10;

    private static final int ELEMENT_NOT_FOUND = -1;

    public ArrayList(int capaticy){
        // If capaticy < 10 use the default capacity, otherwise use the custom capacity
        capaticy = (capaticy < DEFAULT_CAPATICY) ? DEFAULT_CAPATICY : capaticy;
        elements = (T[]) new Object[capaticy];
    }

    public ArrayList(a){
        elements = (T[]) new Object[DEFAULT_CAPATICY];
    }

    public int size(a){
        return size;
    }

    public boolean isEmpty(a){
        return size == 0;
    }

    public T get(int index){
        CheckUtils.checkIndex(index,size);
        return elements[index];
    }

    public T set(int index, T element){
        CheckUtils.checkIndex(index,size);

        T old = elements[index];
        elements[index] = element;
        return old;
    }

    public int indexOf(T element){
        if (element == null) {for (int i = 0; i < size; i++) {
                if (elements[i] == null);
                returni; }}else {
            for (int i = 0; i < size; i++) {
                if (element.equals(elements[i])) returni; }}return ELEMENT_NOT_FOUND;
    }

    public boolean contains(T element){
        returnindexOf(element) ! = ELEMENT_NOT_FOUND; }// Empty means that no element can be accessed
    public void clear(a){
        for (int i = 0; i < size; i++) {
            elements[i] = null;
        }
        size = 0;
    }

    public T remove(int index){

        // Start with index
        CheckUtils.checkIndex(index,size);

        T old = elements[index];
        // Only iterate over the parts that need to be moved, not the entire array
        for (int i = index + 1; i < size; i++) {
            elements[i - 1] = elements[i];
        }
        size --;
        // The last one points to empty
        elements[size] = null;
        return old;
    }

    public void add(T element){

        // Add elements to the end of the array
        add(size,element);
    }

    // Add elements at specific locations
    public void add(int index, T element){
        // Index can be equal to size, which is equivalent to adding elements at the end
        CheckUtils.checkIndex4Add(index,size);

        // If the capacity reaches a critical point, the system automatically expands the capacity
        expansionCapacity(elements.length + (elements.length >> 1));

        // Move the element, starting from the last one, otherwise the latter one will be covered by the previous one
        for (int i = size; i > index; i--) {
            elements[i] = elements[i-1];
        }
        elements[index] = element;
        / / to increase the size
        size ++;
    }

    private void expansionCapacity(int newCapacity){
        int oldCapacity = elements.length;
       if (oldCapacity >= size + 1) return;

        // Create an array of new capacities
        // newCapacity = oldCapacity + (oldCapacity >> 1);
        T[] newElements = (T[]) new Object[newCapacity];
        // Copy the element
        for (int i = 0; i < size; i++) {
            newElements[i] = elements[i];
        }
        // Points to a new memory space
        elements = newElements;
        System.out.println("Capacity expansion succeeded." + oldCapacity + "Extend to" + newCapacity);
    }
    
    @Override
    public String toString(a) {
        return "ArrayList{" +
                "size=" + size +
                ", elements=" + Arrays.toString(elements) +
                '} '; }}Copy the code

For the indexOf method, we need to determine whether the objects are equal, so we need to override the equals method of the objects. Otherwise, no two objects have the same memory address. The Porsche entity class overrides the equals method

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null|| getClass() ! = o.getClass())return false;

    Porsche porsche = (Porsche) o;

    return name.equals(porsche.name);
}

@Override
public int hashCode(a) {
    return name.hashCode();
}
Copy the code

The indexOf method requires nulling for null handling

public int indexOf(T element){
    if (element == null) {for (int i = 0; i < size; i++) {
            if (elements[i] == null);
            returni; }}else {
        for (int i = 0; i < size; i++) {
            if (element.equals(elements[i])) returni; }}return ELEMENT_NOT_FOUND;
}
Copy the code

Test the ArrayList

public class ArrayListTest {

    ArrayList<Porsche> arrayList = null;

    @Before
    public void init(a){
        arrayList = new ArrayList<>();
        arrayList.add(new Porsche("Porsche 718"));
        arrayList.add(new Porsche("550Spyder"));
        arrayList.add(new Porsche("Macan"));
        arrayList.add(new Porsche("Taycan"));
        arrayList.add(new Porsche("Cayenne"));
        arrayList.add(new Porsche("Panamera"));
        arrayList.add(new Porsche("Porsche 911"));
        arrayList.add(new Porsche("Cayman"));
    }


    @Test
    public void size(a) {
        Assert.assertEquals(8,arrayList.size());
        System.out.println("Dynamic array arrayList size is:" + arrayList.size());
    }

    @Test
    public void isEmpty(a) {
        Assert.assertFalse(arrayList.isEmpty());
        System.out.println(Is the dynamic array arrayList empty? + arrayList.isEmpty());
    }

    @Test
    public void get(a) {
        Porsche porsche = arrayList.get(1);
        Assert.assertEquals(new Porsche("550Spyder"),arrayList.get(1));
        System.out.println("The element in the arrayList with index 1 is:" + arrayList.get(1));
    }

    @Test
    public void set(a) {
        System.out.println("Before modification," + arrayList.toString());
        arrayList.set(1.new Porsche("Taycan 2021"));
        System.out.println("Replace the element at index 1 with" + new Porsche("Taycan 2021") + "," + arrayList.toString());
    }

    @Test
    public void indexOf(a) {
        Assert.assertEquals(3,arrayList.indexOf(new Porsche("Taycan")));
        System.out.println("The element at index 6 is:" + arrayList.indexOf(new Porsche("Taycan")));
    }

    @Test
    public void contains(a) {
        Assert.assertTrue(arrayList.contains(new Porsche("Taycan")));
        System.out.println("Inclusive or not" + new Porsche("Taycan") +  ":" + arrayList.contains(new Porsche("Taycan")));
    }

    @Test
    public void clear(a) {
        System.out.println("There is no greater clarity," + arrayList.toString());
        arrayList.clear();
        System.out.println("After clearing, get the element with index 0." + arrayList.get(1));
    }

    @Test
    public void remove(a) {
        System.out.println("Before deleting," + arrayList.toString());
        arrayList.remove(1);
        System.out.println("Drop the element in index 1," + arrayList.toString());
    }

    @Test
    public void add(a) {
        System.out.println(arrayList.toString());
        arrayList.add(new Porsche("Boxster"));
        System.out.println(arrayList.toString());
        arrayList.add(2.new Porsche("Taycan 2020"));
        System.out.println(arrayList.toString());
        arrayList.add(new Porsche("Taycan 2022")); System.out.println(arrayList.toString()); }}Copy the code

Execute the add method

At this point, the custom dynamic array ArrayList is complete 🎉