Explain in detail the use posture of two paging traversal in Java

  • 2021-09-05 00:12:51
  • OfStack

In daily development, the scenario of paging traversal iteration can be said to be very common, such as scanning tables, fishing for 100 pieces of data at a time, then traversing these 100 pieces of data, and executing a certain business logic in turn; After these 100 pieces are executed, load another 100 pieces of data until the scan is completed

So what can we do to achieve the above scenario of paging iterative traversal

This article will introduce two gestures

Conventional usage With the help of Iterator's use posture

1. Data query simulation

First of all, mock1 has the logic of obtaining data by paging, directly and randomly generating data, and controlling the return of up to 3 pages


public static int cnt = 0;

private static List<String> randStr(int start, int size) {
  ++cnt;
  if (cnt > 3) {
    return Collections.emptyList();
  } else if (cnt == 3) {
    cnt = 0;
    size -= 2;
  }

  System.out.println("======================= start to gen randList ====================");
  List<String> ans = new ArrayList<>(size);
  for (int i = 0; i < size; i++) {
    ans.add((start + i) + "_" + UUID.randomUUID().toString());
  }
  return ans;
}

2. Basic implementation

For this scenario, the most common and simple and intuitive implementation method

while dead loop Internal traversal

private static void scanByNormal() {
  int start = 0;
  int size = 5;
  while (true) {
    List<String> list = randStr(start, size);
    for (String str : list) {
      System.out.println(str);
    }

    if (list.size() < size) {
      break;
    }
    start += list.size();
  }
}

3. Iterator implementation

Next, a more interesting way is introduced, which is realized by the traversal characteristics of iterators. First, a general paging iterator is customized


public static abstract class MyIterator<T> implements Iterator<T> {
  private int start = 0;
  private int size = 5;

  private int currentIndex;
  private boolean hasMore = true;
  private List<T> list;

  public MyIterator() {
  }

  @Override
  public boolean hasNext() {
    if (list != null && list.size() > currentIndex) {
      return true;
    }

    //  The current data has been loaded, try to load the following 1 Batch 
    if (!hasMore) {
      return false;
    }

    list = load(start, size);
    if (list == null || list.isEmpty()) {
      //  Data is not loaded, end 
      return false;
    }

    if (list.size() < size) {
      //  The number of returned pieces is less than the limit, indicating that there is more data to load 
      hasMore = false;
    }

    currentIndex = 0;
    start += list.size();
    return true;
  }

  @Override
  public T next() {
    return list.get(currentIndex++);
  }

  public abstract List<T> load(int start, int size);
}

Next, with the help of the above iterator, we can easily realize our requirements


private static void scanByIterator() {
  MyIterator<String> iterator = new MyIterator<String>() {
    @Override
    public List<String> load(int start, int size) {
      return randStr(start, size);
    }
  };

  while (iterator.hasNext()) {
    String str = iterator.next();
    System.out.println(str);
  }
}

Then the question comes, where is the advantage of the above usage over the previous one?

Change double-layer cycle to single-layer cycle

Next, the key point is that after jdk1.8 introduces the function method + lambda, it provides a more concise use gesture


public class IteratorTestForJdk18 {

  @FunctionalInterface
  public interface LoadFunc<T> {
    List<T> load(int start, int size);
  }

  public static class MyIterator<T> implements Iterator<T> {
    private int start = 0;
    private int size = 5;

    private int currentIndex;
    private boolean hasMore = true;
    private List<T> list;
    private LoadFunc<T> loadFunc;

    public MyIterator(LoadFunc<T> loadFunc) {
      this.loadFunc = loadFunc;
    }

    @Override
    public boolean hasNext() {
      if (list != null && list.size() > currentIndex) {
        return true;
      }

      //  The current data has been loaded, try to load the following 1 Batch 
      if (!hasMore) {
        return false;
      }

      list = loadFunc.load(start, size);
      if (list == null || list.isEmpty()) {
        //  Data is not loaded, end 
        return false;
      }

      if (list.size() < size) {
        //  The number of returned pieces is less than the limit, indicating that there is more data to load 
        hasMore = false;
      }

      currentIndex = 0;
      start += list.size();
      return true;
    }

    @Override
    public T next() {
      return list.get(currentIndex++);
    }
  }
}

To use gestures in jdk 1.8 and beyond, 1 line of code is enough


private static void scanByIteratorInJdk8() {
  new MyIterator<>(IteratorTestForJdk18::randStr)
    .forEachRemaining(System.out::println);
}

Is this comparison very obvious? From now on, paging iterative traversal will no longer need lengthy double iterations


Related articles: