1. 程式人生 > >記憶體分頁.md

記憶體分頁.md

在專案當中,使用分頁方式分批次查詢資料並渲染到頁面是一種相當普遍的做法。目的是避免瀏覽器一次性載入過多的資料導致的頁面遲緩或者崩潰。另外,在資料總量過大的情況下,一次性將內容都查詢出來,查詢出來的結果最初是放在記憶體裡面的,記憶體也沒有這麼大,因此很容易導致記憶體不夠或宕機。

往往,專案中還存在另外一種分頁方式,也即記憶體分頁。記憶體分頁的資料總量往往是已經全部載入到記憶體中了的。因此可以想到,記憶體分頁場景中的需要進行分頁的資料物件並不大。 那麼問題就來了?我們為什麼還要進行分頁,是不是畫蛇添足多此一舉了? 事實上有沒有必要進行記憶體分頁得具體問題具體分析。筆者在寫此文之前,就已經總結出兩種不得不使用記憶體分頁機制的場景:

場景(1): 資料總量雖然不大,不足以對記憶體或效能造成威脅,但在使用資料進行計算的過程中產生的中間資料或最終結果總量巨大,計算產生的大量臨時資料或最終結果才是可能導致效能問題的關鍵原因。這種情況下我們也只能將總資料劃分成多個更小的批次進行處理,每次只處理一個批次,這樣可以使每一個小批次產生的臨時資料或計算結果不足以威脅到整個計算過程。當一個批次處理結束,臨時資料被銷燬之後,再進行下一批次的處理。

場景(2):一次性處理資料的耗時大於處理每一個小的批次資料的耗時:比如需要將這批資料作為引數呼叫某個介面,資料量大,介面提供方處理速度遲緩導致的超時無法獲取資料。這種情況下,分批分多次呼叫介面成功的概率大於一個批次單次呼叫介面的概率。

場景(3):第三方庫、框架本身的限制。比如sql語句的in 字句不能過長,否則會導致SQL異常。這種情況下,可能需要分多次進行查詢。

為此,本人在專案中封裝了一個專門對List進行分頁的工具類,同時也貢獻出來,希望對某些人有用!!!

(1)抽象父類:


/**
 * Created by lihong10 on 2017/11/28.
 */

import java.util.Collections;
import java.util.Iterator;
import java.util.List;


/**
 * 該類用於對連結串列中的資料進行分頁操作,採用迭代器設計模式實現。該類的其中一個子類 {@link UnRepeatablePage} 只允許分頁一次。
 * 另一個子類  {@link RepeatablePage}則允許分頁多次。
 *
 <p>該類的子類適用於停車場專案是要到的另外一種分頁方式,也即記憶體分頁。正如本文術語所解釋的,
 * 記憶體分頁的資料總量往往是已經全部載入到記憶體中了的。因此可以想到,記憶體分頁場景中的需要進行分頁的資料總量並不大。 那麼問題就來了?
 * 我們為什麼還要進行分頁,是不是畫蛇添足多此一舉了?筆者在寫作之前,就已經總結出兩種不得不使用記憶體分頁機制的場景:
 </p>
 <ul>
 <li>
 場景(1):  資料總量雖然不大,不足以對記憶體或效能造成威脅,但在使用資料進行計算的過程中產生的中間資料或最終結果總量巨大,
 計算產生的大量臨時資料或最終結果才是可能導致效能問題的關鍵原因。這種情況下我們也只能將總資料劃分成多個更小的批次進行處理,
 每次只處理一個批次,這樣可以使每一個小批次產生的臨時資料或計算結果不足以威脅到整個計算過程。當一個批次處理結束,
 臨時資料被銷燬之後,再進行下一批次的處理。
 </li>
 <li>
 場景(2):一次性處理資料的耗時大於處理每一個小的批次資料的耗時:比如需要將這批資料作為引數呼叫某個介面,
 資料量大,介面提供方處理速度遲緩導致的超時無法獲取資料。這種情況下,分批分多次呼叫介面成功的概率大於一個批次單次呼叫介面的概率。
 </li>
 <li>
 場景(3):第三方庫、框架本身的限制。比如sql語句的in 字句不能過長,否則會導致SQL異常。這種情況下,可能需要拆分為多次進行查詢。
 </li>
 </ul>
 * @author  lihong10
 * @since 2.9.1
 */
public abstract class Page<T> implements Iterator<List<T>>, Iterable<List<T>> {
    /**
     * 原集合
     */
    protected List<T> data;

    /**
     * 當前頁
     */
    protected int pageNo;

    /**
     * 該欄位僅用作迭代每一分頁的遊標
     */
    protected int cursor;

    /**
     * 總頁數
     */
    protected int totalPage;

    /**
     * 每頁條數
     */
    protected int pageSize;

    /**
     * 總資料條數
     */
    protected int totalCount;

    /**
     * 雙引數構造
     * @author lihong10 2017年12月2日 上午10:42:30
     * @param data
     * @param pageSize 分頁大小
     */
    public Page(List<T> data, int pageSize) {
        this(data, 1, pageSize);
    }

    /**
     * 三引數構造
     * @param data 被分頁的連結串列
     * @param pageNo 頁碼
     * @param pageSize 分頁大小
     */
    public Page(List<T> data, int pageNo, int pageSize) {
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("data can not be empty!");
        }

        if (pageSize <= 0) {
            throw new IllegalArgumentException("pageSize must >= 1");
        }

        this.data = data;
        this.totalCount = data.size();
        this.pageSize = pageSize;
        this.totalPage = (totalCount + pageSize - 1) / pageSize;
        if (pageNo <= 0) {
            pageNo = 1;
        } else if (pageNo > this.totalPage) {
            pageNo = totalPage;
        }
        this.pageNo = pageNo;
        this.cursor = 0;
    }

    /**
     * 返回迭代器物件
     * @author lihong10 2017年12月3日 上午10:42:30
     * @return
     */
    @Override
    public final Iterator<List<T>> iterator() {
        return this;
    }

    @Override
    public void remove() {
    }

    /**
     * @author lihong10 2017年12月3日 上午10:42:30
     * 得到pageNo表示的那一頁資料
     * @return
     */
    public final List<T> getPagedList() {
        check();
        int fromIndex = (pageNo - 1) * pageSize;
        if (fromIndex >= data.size()) {
            return Collections.emptyList();//空陣列
        }
        if (fromIndex < 0) {
            return Collections.emptyList();//空陣列
        }
        int toIndex = pageNo * pageSize;
        if (toIndex >= data.size()) {
            toIndex = data.size();
        }
        return data.subList(fromIndex, toIndex);
    }

    /**
     * @author lihong10 2017年12月3日 上午10:42:30
     * 根據頁數獲取指定分頁
     * @author lihong10 2017年11月30日 下午15:42:30
     * @param pageNo
     * @return
     */
    public final List<T> getPage(int pageNo)  {
        check();
        if (pageNo <= 0) {
            pageNo = 1;
        } else if (pageNo > totalPage) {
            pageNo = totalPage;
        }

        int fromIndex;
        int toIndex;
        if (pageNo * pageSize < totalCount) {// 判斷是否為最後一頁
            toIndex = pageNo * pageSize;
            fromIndex = toIndex - pageSize;
        } else {
            toIndex = totalCount;
            fromIndex = pageSize * (totalPage - 1);
        }

        List<T> objects = null;
        if (data != null && data.size() > 0) {
            objects = data.subList(fromIndex, toIndex);
        }

        return objects;
    }

    /**
     * 重置遊標,預設重置為0
     * @author lihong10 2017年12月3日 上午10:42:30
     * @return
     */
    public abstract  void  reset();

    /**
     * 重置遊標操作
     * @author lihong10 2017年12月3日 上午10:42:30
     * @param cursor
     * @return
     */
    public abstract void reset(int cursor);

    /**
     * 判斷某個方法是否允許被呼叫
     * @author lihong10 2017年12月3日 上午10:42:30
     * @return
     */
    public abstract void check();

    public int getPageSize() {
        return pageSize;
    }

    public List<T> getData() {
        return data;
    }

    public int getPageNo() {
        return pageNo;
    }

    public int getTotalPage() {
        return totalPage;
    }

    public int getTotalCount() {
        return totalCount;
    }
}

(2)可重複分頁的子類及示例:


/**
 * Created by lihong10 on 2017/11/28.
 */

import java.util.Arrays;
import java.util.List;

/**
 * 用法參見{@link UnRepeatablePage} 與  {@link Page}
 * @author lihong10 2017年12月4日 上午10:42:30
 * @param <T>
 */
public class RepeatablePage<T> extends Page<T> {

    public RepeatablePage(List<T> data, int pageSize) {
        this(data, 1, pageSize);
    }

    public RepeatablePage(List<T> data, int currentPage, int pageSize) {
        super(data, currentPage, pageSize);
    }

    @Override
    public boolean hasNext() {
        if (totalPage <= 0) {
            return false;
        }
        return cursor < totalPage;
    }

    @Override
    public List<T> next() {
        int pageNo = this.cursor + 1;
        List<T> page = getPage(pageNo);
        this.cursor++;
        return page;
    }

    public void check() {
    }

    public void reset() {
        this.cursor = 0;
    }

    public void reset(int cursor) {
        if (cursor < 0) {
            this.cursor = 0;
        } else if (cursor >= totalPage) {
            this.cursor = totalPage;
        } else {
            this.cursor = cursor;
        }
    }

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(new Integer[]{0, 1, 2, 3, 4, 5, 6});
        int pageSize = 3;//遍歷分頁時候,指定分頁大小是3
        RepeatablePage<Integer> page = new RepeatablePage<Integer>(list, pageSize);

        System.out.println("------------直接獲取某一頁------------------------");
        System.out.println(page.getPage(1));
        System.out.println(page.getPage(2));
        System.out.println(page.getPage(3));


        System.out.println("第1次遍歷結果是");
        for (List<Integer> li : page) { //for遍歷
            System.out.println(li);
        }

        System.out.println("第2次遍歷結果是");
        page.reset();
        for (List<Integer> li : page) { //遍歷前呼叫了重置狀態的reset方法,可以再次從頭開始遍歷
            System.out.println(li);
        }

        System.out.println("第3次遍歷結果是");
        for (List<Integer> li : page) { //遍歷前沒有呼叫reset方法,遍歷失敗
            System.out.println(li);
        }
    }

}


(3)不可重複分頁的子類及示例:


import java.util.Arrays;
import java.util.List;

/**
 * 用法參見{@link RepeatablePage} 與  {@link Page}
 * @author lihong10 2017年12月4日 上午10:42:30
 * @param <T>
 */
public class UnRepeatablePage<T> extends Page<T> {

    public UnRepeatablePage(List<T> data, int pageSize) {
        this(data, 1, pageSize);
    }


    public UnRepeatablePage(List<T> data, int pageNo, int pageSize) {
        super(data, pageNo, pageSize);
    }

    @Override
    public boolean hasNext() {
        return cursor < this.totalPage;
    }

    @Override
    public List<T> next() {
        cursor++;
        if (data == null) {
            return null;
        }

        List<T> subList = null;
        if (data.size() > pageSize) {
            subList = data.subList(0, pageSize);
            data = data.subList(pageSize, data.size());
        } else {
            subList = data.subList(0, data.size());
            data = null;
        }

        return subList;
    }

    public void check() {
        if (cursor > 0) {
            throw new IllegalStateException("the operation is not permitted if for cycle or next() method" +
                    " has benn called before ");
        }
    }

    @Override
    public void reset() {
        throw new UnsupportedOperationException("reset is not permitted !!!," +
                "if you have to, use RepeatablePage.class");
    }

    @Override
    public void reset(int cursor) {
        throw new UnsupportedOperationException("reset is not permitted !!!," +
                "if you have to, use RepeatablePage.class ");
    }

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(new Integer[]{0, 1, 2, 3, 4, 5, 6});
        int pageSize = 6;//遍歷分頁時候,指定分頁大小是3
        UnRepeatablePage<Integer> page = new UnRepeatablePage<Integer>(list, 3);

        System.out.println("------------遍歷前允許直接獲取某一頁------------------------");
        System.out.println(page.getPage(2));


        System.out.println("第1次遍歷結果是");
        for (List<Integer> li : page) { //for遍歷
            System.out.println(li);
        }
        System.out.println("------------遍歷過之後,不允許直接獲取某一頁------------------------");
        System.out.println(page.getPage(2));

        page.reset();//呼叫reset會丟擲異常,應為該子類不允許這個操作
        for (List<Integer> li : page) { //for遍歷
            System.out.println(li);
        }
    }

}