晨曦之星 2020-05-29
分页在我们平时处理数据的时候,是非常常见的操作,尤其是SQL,比如使用limit m,n这样的语句,又比如在elastic search之中,我们需要对search的结果进行分页,这都是一些很常见的场景,其实数据的分页,最根本的原因就是,如果当数据量很大的时候,我们如果一次性放入到内存之中操作,这样必然会对内存,数据库,前端展示等方面造成很大的压力,降低用户体验,甚至无法得到正确的结果,所以我们需要分页,把一个巨大的数据,按照我们的规划,多次获取这个数据,使用“蚂蚁搬家”的方式获取数据,每次我们拿到的数据的量,就叫做是页面大小,而我们每一次拿到的是不同的数据,这些数据是承载在不同的记录之中,这个记录就叫做页面编号。抛开数据库和ES不说,我们平时常用的List和Map,其实也是可以分页的,通过对于List和Map的分页,我们可以去理解分页的原理。
分页的两个关键问题,一个是页面大小,也就是一页的数据量有多少,二是按照上述的页面大小,一个数据可以分多少个页面,我们可以通过如下例子分析,比如我们有15个数据,如果每页有5个数据,也就是页面大小设置为5,那么就有15/5=3
,并且15%5=0
,这意味着,我们的规划,可以完全分页,而没有凑不满一页的情况;如果我们每页有7个数据,那么就有15/7=2
,并且15%7=1
,那么就意味着,我们有2个完整的页面,同时,还有一个残缺的页面,这个页面只有1个数据。
所以在分页的时候,需要确定可以分多少页,更深一个层次就是意味着,能不能完整的分页。如果可以,那么就是数据数量/页面大小
个页面,如果不能完整分页,那么就(数据数量/页面大小)+1
个页面。这就是问题的关键,一般而言,我们是要从0页面开始,这比较符合我们的一般操作习惯,还有就是边界情况,页面不能小于0,也不能大于我们可以分页的页面。分析完毕之后,上代码:
public class PageUtil { /** * 获取分页的总数 * * @param ls 需要分页的list * @param pageSize 页面大小 * @return 按照此页面分页可以得到的分页数量 */ public static <T> int getIteratorNum(List<T> ls, int pageSize) { int base = ls.size() / pageSize; boolean complete = (ls.size() % pageSize) == 0 ? true : false; if (complete == true) { return base; } else { return base + 1; } } /** * 获取分页之后,该页的数据 * * @param list 需要分页的List数据 * @param pageSize 页面大小 * @param pageNo 页面编码 * @param <T> T参数类型 * @return pageNo页面的内容,相当于是一个子List * @throws Exception exception */ public static <T> List<T> list2Page(List<T> list, int pageSize, int pageNo) throws Exception { // 获取当前分页条件下的页面数量 int allPageNumber = getIteratorNum(list, pageSize); if (pageNo < 0 || pageNo > allPageNumber) { // 也可直接报错 throw new Exception("page number is out of boundary!"); } else if (pageNo >= 0 && pageNo < allPageNumber - 1) { return list.subList(pageNo * pageSize, pageNo * pageSize + pageSize); } else { // (pageNo > 0 && pageNo == iteratorNum) return list.subList(pageNo * pageSize, list.size()); } } /** * 获取分页的总数 * * @param map 需要分页的map * @param pageSize 页面大小 * @return 按照此页面分页可以得到的分页数量 */ public static <T,V> int getIteratorNum(Map<T,V> map, int pageSize) { int base = map.size() / pageSize; boolean complete = (map.size() % pageSize) == 0 ? true : false; if (complete == true) { return base; } else { return base + 1; } } /** * map的分页 * * @param map 需要分页的数据 * @param pageSize 页面大小 * @param pageNo 要获取的页面编码 * @param <T> T参数类型 * @param <V> V参数类型 * @return pageNo页面的内容,相当于是一个子Map * @throws Exception exception */ public static <T,V> Map<T, V> map2Page(Map<T,V> map, int pageSize, int pageNo) throws Exception { // 获取当前分页条件下的页面数量 int allPageNumber = getIteratorNum(map, pageSize); // 需要先把Map转换成List, 然后去遍历,这样可以让操作有顺序的进行 Set<T> ts = map.keySet(); List<T> keyList = new ArrayList<>(); List<V> valueList = new ArrayList<>(); for (T t: ts){ keyList.add(t); valueList.add(map.get(t)); } if (pageNo < 0 || pageNo > allPageNumber) { throw new Exception("page number is out of boundary!"); } else if (pageNo >= 0 && pageNo < allPageNumber - 1) { List<T> tempKeyList = keyList.subList(pageNo * pageSize, pageNo * pageSize + pageSize); List<V> tempValueList = valueList.subList(pageNo * pageSize, pageNo * pageSize +pageSize); Map<T, V> tvHashMap = new HashMap<>(); for (int i =0; i< tempKeyList.size(); i++){ // 1.此处可以Map的方式get, 2.可以通过list按顺序获取 tvHashMap.put(tempKeyList.get(i), tempValueList.get(i)); } return tvHashMap; } else { List<T> tempKeyList = keyList.subList(pageNo * pageSize, map.size()); List<V> tempValueList = valueList.subList(pageNo * pageSize, map.size()); Map<T, V> tvHashMap = new HashMap<>(); for (int i =0; i< tempKeyList.size(); i++){ // 1.此处可以Map的方式get, 2.可以通过list按顺序获取 tvHashMap.put(tempKeyList.get(i), tempValueList.get(i)); } return tvHashMap; } } }
对于List和Map,其实是Java层面的内容,和分页的逻辑关系不大。需要注意的是,对于Map分页的时候,我们使用两个List来取的Key和此Key对应的Value,当然我们可以通过Map.get(key)这种方式来获取,按照上述的使用两个list,其实是意义对应的,key和value的关系是一一对应的,他们之间的对应关系和在两个list之中的顺序,是一致的。