Using RandomAccess interface skillfully, the performance of collection traversal can be improved dozens of times

Posted by sarbas on Sun, 20 Feb 2022 15:48:14 +0100

CSDN blog expert is a high-quality creator in the Java field focusing on various technical fields. Wechat search [Chen Pi's JavaLib] and learn more technical articles, interview materials and technical e-books in time after paying attention.

preface

Suppose you were asked to define a method for others to call. Its function is to traverse the incoming collection. How would you implement it? The method is defined as follows:

private void traverse(List<Integer> list) {
     
}

Scheme 1: use for loop subscript positioning to obtain set elements?

private void traverse(List<Integer> list) {
    for (int i = 0; i < list.size(); i++) {
        list.get(i);
    }
}

Scheme 2: use iterators to get collection elements?

private void traverse(List<Integer> list) {
    for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();) {
        iterator.next();
    }
}

Scheme verification

For the above two schemes, we verify them, because the List implementation classes often used in our development are ArrayList and LinkedList. Therefore, when the two collection types are passed in, the collection traversal performance is verified.

package com.nobody;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * @Description
 * @Author Mr.nobody
 * @Date 2021/4/25
 * @Version 1.0
 */
public class Demo {
    public static void main(String[] args) {
		// Collection size
        int ListSize = 200000;
		// Initializing the ArrayList collection
        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < ListSize; i++) {
            arrayList.add(i);
        }
        System.out.println("ArrayList Type: ");
        traverseWithFor(arrayList);
        traverseWithIterator(arrayList);

		// Initialize LinkedList collection
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < ListSize; i++) {
            linkedList.add(i);
        }
        System.out.println("LinkedList Type: ");
        traverseWithFor(linkedList);
        traverseWithIterator(linkedList);
    }

    private static void traverseWithFor(List<Integer> list) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            list.get(i);
        }
        System.out.println("traverseWithFor:" + (System.currentTimeMillis() - startTime));
    }

    private static void traverseWithIterator(List<Integer> list) {
        long startTime = System.currentTimeMillis();
        for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();) {
            iterator.next();
        }
        System.out.println("traverseWithIterator:" + (System.currentTimeMillis() - startTime));
    }
}

The output results are as follows: when the collection type is ArrayList, it is more efficient to traverse the collection using the for subscript positioning (scheme 1); When the collection type is LinkedList, it is more efficient to use iterator (scheme 2) to traverse the collection. In particular, if the LinkedList set is large and traversed with the for subscript, the efficiency is extremely poor.

ArrayList Type: 
traverseWithFor:9
traverseWithIterator:15

LinkedList Type: 
traverseWithFor:35548
traverseWithIterator:12

RandomAccess interface

Through the above verification, it is found that you cannot simply use one of the schemes to traverse the incoming collection, because the implementation class of the List of the incoming parameters is unknown. Then we can judge the specific implementation class of the incoming parameters to decide which traversal algorithm to use.

private static void traverse(List<Integer> list) {
    if (list instanceof ArrayList) {
        for (int i = 0; i < list.size(); i++) {
            list.get(i);
        }
    } else if (list instanceof LinkedList) {
        for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();) {
            iterator.next();
        }
    }
}

The above is an ideal situation. If the implementation classes of List will increase or decrease over time, don't you often modify this method to increase or decrease the condition judgment of instanceof?

So in jdk1 After 4, the RandomAccess interface appears, which is an empty tag interface.

package java.util;

public interface RandomAccess {
}

Its function is that if the implementation class of a collection implements this tag interface, it indicates that this implementation class supports fast random access (usually fixed time). The main purpose of RandomAccess interface is to allow general algorithms to change their behavior, so as to provide good performance when accessing lists randomly or continuously.

Generally speaking, when randomly accessing or sequentially traversing a collection, we can decide to use different traversal strategies according to whether the collection implementation class implements the RandomAccess interface. It is officially encouraged to implement the RandomAccess interface for those collection classes whose traversal presents linear access time and random access is a fixed time.

For example, the ArrayList class implements the RandomAccess interface, while the LinkedList class does not implement the RandomAccess interface.

The official also stated that if the collection class implements the RandomAccess interface, it is recommended to use for (int, I = 0, n = list. Size(); i < n; I + +) traversal, avoid using Iterator iterator to traverse the collection. Conversely, if the collection class is Sequence List (such as LinkedList), iterators are recommended.

package com.nobody;

import java.util.*;

/**
 * @Description
 * @Author Mr.nobody
 * @Date 2021/4/25
 * @Version 1.0
 */
public class Demo {
    public static void main(String[] args) {

        // Collection size
        int ListSize = 200000;
        // Initializing the ArrayList collection
        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < ListSize; i++) {
            arrayList.add(i);
        }
        System.out.println("ArrayList Type: ");
        traverseWithFor(arrayList);
        traverseWithIterator(arrayList);
        traverse(arrayList);

        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < ListSize; i++) {
            linkedList.add(i);
        }
        System.out.println("LinkedList Type: ");
        traverseWithFor(linkedList);
        traverseWithIterator(linkedList);
        traverse(linkedList);
    }

    private static void traverseWithFor(List<Integer> list) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            list.get(i);
        }
        System.out.println("traverseWithFor:" + (System.currentTimeMillis() - startTime));
    }

    private static void traverseWithIterator(List<Integer> list) {
        long startTime = System.currentTimeMillis();
        for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();) {
            iterator.next();
        }
        System.out.println("traverseWithIterator:" + (System.currentTimeMillis() - startTime));
    }
    
    private static void traverse(List<Integer> list) {
        long startTime = System.currentTimeMillis();
        if (list instanceof RandomAccess) {
            for (int i = 0; i < list.size(); i++) {
                list.get(i);
            }
        } else {
            for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();) {
                iterator.next();
            }
        }
        System.out.println("traverse:" + (System.currentTimeMillis() - startTime));
    }
}

The output results are as follows. It is found that after using RandomAccess skillfully, no matter which implementation class of List, traversing the collection can obtain good performance.

ArrayList Type: 
traverseWithFor:12
traverseWithIterator:15
traverse:12

LinkedList Type: 
traverseWithFor:27189
traverseWithIterator:6
traverse:7

summary

  • If the implementation class of a List implements the RandomAccess tag interface, it indicates that this implementation class supports fast random access (usually fixed time). The main purpose of RandomAccess interface is to allow general algorithms to change their behavior, so as to provide good performance when accessing lists randomly or continuously.
  • It is officially encouraged to implement the RandomAccess interface for those collection classes whose traversal presents linear access time and random access is a fixed time.
  • If the collection class implements the RandomAccess interface, it is recommended to use for (int, I = 0, n = list. Size(); i < n; I + +) traversal, otherwise use Iterator iterator to traverse the collection.
  • But now the machine performance is very high. When the set size is very small, the performance of the two traversal schemes is similar. If the amount of data is huge, you can make decisions according to the situation. In short, teaching students according to their aptitude must not be blindly abused.

Topics: Java Algorithm