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

利用请求分页处理大数据:1亿条数据的存储与检索
本文介绍了如何结合请求分页存储管理方法处理大数据量问题。通过将一亿条数据分成组并利用磁盘存储,借助文件系统层次索引结构,设计出高效的存储和检索算法。讨论了组大小对性能的影响,并提供了一个简单的Java代码实现,展示在有限内存下处理大量数据的能力。

学过操作系统的人都知道请求分页存储管理方法,这也是为啥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行的数据量,这还是非常具有诱惑力的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值