基于“请求分页”的大数量处理

来源:互联网 发布:常见网络进攻形式 编辑:程序博客网 时间:2024/06/06 21:40

学过操作系统的人都知道请求分页存储管理方法,这也是为啥1G内存的计算机能够处理超过1G的数据量的原因。最近做项目遇到大数据量处理的问题,结合请求分页算法的原来,成功的处理了一亿条数据。到这儿来分享一下给大家:

首先是一亿条数据的存储问题,这么多数据放在内存里,肯定会溢出的,更不用提数据的处理了。所以肯定要借助于磁盘存储了。结合操作系统的文件系统里的层次索引结构,我们可以设计出高效的存储方法。具体的算法思想如下:

先将一亿条数据分成10000组,那么1组有10000条数据。正常内存里放着几个组,内存里存放的组的上限比如说是10个,也就是内存最多放10组。如果要到的数据不在内存里,那么从磁盘里面取出来。假如当前内存里已经有10组了,那么就先要将最不活跃的那一组先从内存里销毁,再取出目标组代替之。我们需要建立一个关于组的索引,同时组内也要有一个索引。那么我们可以简单的通过组索引和组内索引,轻松的找到目标的位置。一般情况下,我们可以把一个组写入一个文件。

我们可以更近一步,设计出更多层次的结构。比如说:将10亿条数据分成10块,1块就是1亿条,再将一块分成10000组,一组10000条。这就是三层索引了。

我们需要注意的是:组的大小是个未知的值。如果组的大小设置的过小,那么组的数量会很多,实际的文件就会很多,假如有个遍历操作,需要建立的输入输出流数量就是文件的数量,那么重复的建立输入输出流就会非常的耗时耗资源。假如组的大小设置的过大,那么读取一个组的时候,将组文件转化成指定的数据结构就会很耗时。那么,获取一个给定值时,就会因此很慢。所以,组的大小,需要实际中根据性能要求去反复的调试、实验,以获取一个最佳值。

个人测试了好久,觉得最影响性能还是I/O操作。所以,应该考虑一些高效的I/O操作方法。比如说字节缓冲机制等等。

不多说:最简单的Java代码实现如下:

/** * 双层次索引顺序表 *  * 索引结果请不要用Iterator,用它效率非常差,Iterator是针对链表结构的,比如说LinkedList * @author zhou@since 2012-8-7下午5:33:00 */public class TwoLevelArrayList extends AbstractList {/** * ArrayList<Group> */private ArrayList groupArray;// 第一层次/** * 当前的group,它都是alive */private Group currentGroup;private int currentGroupIndex;private String cacheFilePath;private static final String FILE_EXTENSION = ".part";/** * cacheFile是个文件夹 *  * @param parentFile */public TwoLevelArrayList(File parentFile) {if (!parentFile.exists()) {parentFile.mkdirs();}if (!parentFile.isDirectory()) {parentFile.delete();throw new UnsupportedOperationException("cacheFile必须是个文件夹!");}String id = UUID.randomUUID().toString();// 生成一个临时文件夹cacheFilePath = parentFile.getAbsolutePath() + File.separator + id;File cacheFile = new File(cacheFilePath);cacheFile.mkdirs();// 默认是只存在一个分组的groupArray = new ArrayList(1);currentGroup = new Group(0);groupArray.add(currentGroup);currentGroupIndex = 0;}public Object get(int index) {int groupIndex = index / Group.MAXSIZE;// 组序号int inGroupIndex = index % Group.MAXSIZE;// 组内序号setCurrentGroup(groupIndex);return currentGroup.get(inGroupIndex);}public int size() {int groupSize = groupArray.size();if (groupSize == 1) {return ((Group)groupArray.get(0)).size();}// 最后一个Gruop里面的数量肯定少于Group.MAXSIZE,而之前的里面的数量肯定是等于Group.MAXSIZE的return Group.MAXSIZE * (groupSize - 1) + ((Group)groupArray.get(groupSize - 1)).size();}public Object set(int index, Object element) {int groupIndex = index / Group.MAXSIZE;// 组序号int inGroupIndex = index % Group.MAXSIZE;// 组内序号setCurrentGroup(groupIndex);return currentGroup.set(inGroupIndex, element);}public boolean add(Object o) {int groupSize = groupArray.size();Group lastgroup = ((Group)groupArray.get(groupSize - 1));int groupindex = lastgroup.size() == Group.MAXSIZE ? groupSize : groupSize - 1;setCurrentGroup(groupindex);return currentGroup.add(o);}public void add(int index, Object element) {int groupIndex = index / Group.MAXSIZE;// 组序号int inGroupIndex = index % Group.MAXSIZE;// 组内序号setCurrentGroup(groupIndex);currentGroup.add(inGroupIndex, element);}public int indexOf(Object o) {return -1;// 这个要支持吗?假如需要,必须考虑一个高效的遍历算法}public void clear() {currentGroup.clear();groupArray.clear();new File(cacheFilePath).delete();}public Iterator iterator() {throw new UnsupportedOperationException();}private void setCurrentGroup(int groupIndex) {if (currentGroupIndex == groupIndex) {return;} else {try {// first:先将当前的给sleep了sleepCurrentGroup();// second:将新的currentGroup给唤醒。如果新的currentGroup不存在,那么无需唤醒操作currentGroupIndex = groupIndex;if (currentGroupIndex >= groupArray.size()) {// 无需唤醒currentGroup = new Group(currentGroupIndex);groupArray.add(currentGroupIndex, currentGroup);} else {currentGroup = (Group)groupArray.get(currentGroupIndex);wakeupCurrentGroup();}} catch (Exception e) {e.printStackTrace();}}}private void sleepCurrentGroup() throws Exception {File file = new File(cacheFilePath + File.separator + currentGroupIndex + FILE_EXTENSION);if (!file.exists()) {file.createNewFile();}OutputStream outputStream = new FileOutputStream(file);GZIPOutputStream gzipouptStream = new GZIPOutputStream(outputStream);currentGroup.sleep(gzipouptStream);gzipouptStream.flush();gzipouptStream.close();outputStream.flush();outputStream.close();}private void wakeupCurrentGroup() throws Exception {File file = new File(cacheFilePath + File.separator + currentGroupIndex + FILE_EXTENSION);if (!file.exists()) {return;}InputStream inputStream = new FileInputStream(file);GZIPInputStream gzipInputStrean = new GZIPInputStream(inputStream);currentGroup.wakeup(gzipInputStrean);gzipInputStrean.close();inputStream.close();}}
组的数据结构:
import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.util.ArrayList;import java.util.Arrays;/** * Group是映射到一个文件的 * @author zhou @since 2012-8-7下午5:33:00 */public class Group {private int index;// 当前组所处于的位置/** * usually the Object of this ArrayList is an ArrayList */private ArrayList m_data;/** * 当前Group里最多放10000个Item */public static final int MAXSIZE = 10000;// 需要一个量来保存size,而不是通过计算m_data得到,因为一些死的Group里面的m_data是不存在的。不然会有性能影响// size是不依赖与m_data的,但是,却又与m_data.size()始终保持相等private int size;public Group(int index) {this.setIndex(index);m_data = new ArrayList();}/** * prepareData应该统一在一个组要进行任何一个操作时准备,而且应该放在外面处理,而不是在每个操作方法里去判断是否准备完毕。 * 如果在每个操作方法里判断,那么需要重复判断“操作数*MAXSIZE”次,很影响性能。但是在外面处理,就需要用的人特别小心了,不然一不小心就出错了 */public void wakeup(InputStream inputStream) {BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));String line = null;try {if (reader.ready()) { while (isNotEmpty((line = reader.readLine()))) {String[] args = line.split(",");m_data.add(Arrays.asList(args));}}reader.close();} catch (Exception e) {e.printStackTrace();} finally {trimToSize();}}public static boolean isNotEmpty(String str) {return str != null && str.length() != 0;}public Object get(int index) {return m_data.get(index);}public boolean add(Object o) {m_data.add(o);size++;return true;}public void add(int index, Object element) {m_data.add(index, element);size++;}public Object remove(int index) {size--;return m_data.remove(index);}public void trimToSize() {m_data.trimToSize();size = m_data.size();}public int size() {return size;}public void clear() {if (m_data != null) {m_data.clear();}size = 0;}public void sleep(OutputStream outputStream) {write2File(outputStream);m_data.clear();}private void write2File(OutputStream outputStream) {PrintWriter outWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream)));StringBuffer sb = new StringBuffer();for (int i = 0; i < size; i++) {ArrayList data = (ArrayList)m_data.get(i);Object ob = null;for (int j = 0, len = data.size() - 1; j < len; j++) {ob = data.get(j);sb.append(ob == null ? "&&&" : ob.toString() + ",");}ob = data.get(data.size() - 1);sb.append(ob == null ? "&&&" : ob.toString()+"\n");}sb.deleteCharAt(MAXSIZE);outWriter.print(sb.toString());outWriter.flush();outWriter.close();outWriter = null;}public int getIndex() {return index;}public void setIndex(int index) {this.index = index;}public Object set(int index, Object element) {return m_data.set(index, element);}}

import java.io.File;import java.util.ArrayList;//测试类:public class TestArrayList {public static void main(String[] args) {Runtime r = Runtime.getRuntime();System.out.println(r.maxMemory() / 1024 / 1024);// ArrayList list = new ArrayList();// int count = 5000000;// long start = System.currentTimeMillis();// while (count > -1) {// list.add("1");// count--;// }// System.out.println(System.currentTimeMillis() - start);String demopath_original = "D:\\test";TwoLevelArrayList list = new TwoLevelArrayList(new File(demopath_original));int count = 0;long start = System.currentTimeMillis();while (count < 5000000) {ArrayList xx = new ArrayList();xx.add("1" + "a" + count);xx.add("2");xx.add("3");xx.add("4");xx.add("5");xx.trimToSize();list.add(xx);count++;}System.out.println(System.currentTimeMillis() - start);start = System.currentTimeMillis();System.out.println(list.get(100000));//获取第100000个数据System.out.println(System.currentTimeMillis() - start);list.clear();}}
个人的eclipse启动的JVM的内存为63M
当然,Group里面放的数据和读取的操作可以自己定义。我个人试了下500W行的数据,分组大小为1W的情况,取一个不在当前内存里的组的情况下,花了200ms。主要的操作还都是I/O上,如果用缓冲机制,那么应该可以降到50ms一下。这就非常OK了。

大家可以看到只有63M的内存能轻松的读取、处理500W行的数据量,这还是非常具有诱惑力的。

原创粉丝点击