使用 Protostuff 来序列化、读取和写入文件, 速度很快。
这是我测试过的所有方法中最快的,它可以用作一个简单的本地文件数据库,可以储存二进制,或者纯文本文件,具体实现方式看个人喜好,这里本人采用二进制方式存储文件。
常规文件工具的读写方式有很多种,但是对于简单的读写,目前JAVA NIO方式是最快的。
FileUtil.java package protoBuf; import java.io.*; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.List; public class FileUtil { public static final int BUFSIZE = 1024 * 8; /** * 通过追加写入二进制数据 */ public static void writeByte2File(byte[] bytes, String writePath) { try { FileOutputStream fos = new FileOutputStream(writePath, true); fos.write(bytes); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 添加了在不关闭文件的情况下写入二进制数据 */ public static void writeByte2FileFlush(byte[] bytes, String writePath) { try { FileOutputStream fos = new FileOutputStream(writePath, true); fos.write(bytes); fos.flush(); } catch (Exception e) { e.printStackTrace(); } } /** * Java NIO 模式追加写入 * @param filepath * @param contentList 要写入的文件内容 * @param bufferSize 单次写缓冲区大小 默认4M 1024 * 1024 * 4 */ public static void write2FileChannel(String filepath, List<String> contentList, Integer bufferSize) { bufferSize = null == bufferSize ? 4194304 : bufferSize; ByteBuffer buf = ByteBuffer.allocate(bufferSize); FileChannel channel = null; try { File fileTemp = new File(filepath); File parent = fileTemp.getParentFile(); if (!parent.exists()) parent.mkdirs(); if (!fileTemp.exists()) fileTemp.createNewFile(); channel = new FileOutputStream(filepath, true).getChannel(); for (int i = 0; i < contentList.size(); i++) { buf.put((contentList.get(i) + "\r\n").getBytes()); } buf.flip(); // 切换到可读模式 while (buf.hasRemaining()) { channel.write(buf); } } catch (Exception e) { e.printStackTrace(); } finally { try { channel.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * NIO模式合并文件 */ public static void mergeFiles(File outFile, String[] files) { FileChannel outChannel = null; try { outChannel = new FileOutputStream(outFile).getChannel(); for (String f : files) { if (null != f) { FileChannel fc = new FileInputStream(f).getChannel(); ByteBuffer bb = ByteBuffer.allocate(BUFSIZE); while (fc.read(bb) != -1) { bb.flip(); outChannel.write(bb); bb.clear(); } fc.close(); } } } catch (IOException ioe) { ioe.printStackTrace(); } finally { try { if (outChannel != null) { outChannel.close(); } } catch (IOException ignore) { } } } /** * 添加二进制数据写入,使用固定流,不关闭文件 */ public static FileOutputStream writeByte2FileFlush2Stream(FileOutputStream fos, byte[] bytes, String writePath) { try { if (fos == null) { fos = new FileOutputStream(writePath, true); } fos.write(bytes); fos.flush(); } catch (Exception e) { e.printStackTrace(); } return fos; } /** * NIO方式一次将文件内容读入内存 */ public static byte[] readDataFromFile(String filePath) throws Exception { //get all data from file RandomAccessFile file = new RandomAccessFile(filePath, "rw"); FileChannel fileChannel = file.getChannel(); MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); byte res[] = new byte[buffer.capacity()]; buffer.get(res); return res; } public static void main(String[] args) { for(int i=0;i<100;i++){ writeByte2FileFlush(new String("test"+i).getBytes(), "E:\\testNull.txt"); } try{ byte[] file = readDataFromFile("E:\\testNull.txt"); System.out.println("file="+new String(file)); }catch(Exception e){ e.printStackTrace(); } } }
简单的对象包装类
WrapperUtil.java package protoBuf; public class WrapperUtil<T> { private T data; public static <T> WrapperUtil<T> builder(T data) { WrapperUtil<T> wrapper = new WrapperUtil<>(); wrapper.setData(data); return wrapper; } public T getData() { return data; } public void setData(T data) { this.data = data; } }
Protostuff 的主要工具类有序列化和反序列化方法,这些方法是加速数据处理的关键。
ProtoBufUtil.java package protoBuf; import com.google.common.collect.Maps; import io.protostuff.LinkedBuffer; import io.protostuff.ProtostuffIOUtil; import io.protostuff.Schema; import io.protostuff.runtime.RuntimeSchema; import org.springframework.objenesis.Objenesis; import org.springframework.objenesis.ObjenesisStd; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; public class ProtoBufUtil { private static Objenesis objenesis = new ObjenesisStd(true); /** * 需要使用包装类进行序列化/反序列化的类的集合 */ private static final Set<Class<?>> WRAPPER_SET = new HashSet<>(); /** * 序列化/反序列化包装类类对象 */ private static final Class<WrapperUtil> WRAPPER_CLASS = WrapperUtil.class; /** * 序列化/反序列化包装类模式对象 */ private static final Schema<WrapperUtil> WRAPPER_SCHEMA = RuntimeSchema.createFrom(WRAPPER_CLASS); /** * 缓存对象及对象schema信息集合 */ private static final Map<Class<?>, Schema<?>> CACHE_SCHEMA = Maps.newConcurrentMap(); /** * 预定义一些Protostuff无法直接序列化/反序列化的对象 */ static { WRAPPER_SET.add(List.class); WRAPPER_SET.add(ArrayList.class); WRAPPER_SET.add(CopyOnWriteArrayList.class); WRAPPER_SET.add(LinkedList.class); WRAPPER_SET.add(Stack.class); WRAPPER_SET.add(Vector.class); WRAPPER_SET.add(Map.class); WRAPPER_SET.add(HashMap.class); WRAPPER_SET.add(TreeMap.class); WRAPPER_SET.add(Hashtable.class); WRAPPER_SET.add(SortedMap.class); WRAPPER_SET.add(Map.class); WRAPPER_SET.add(Object.class); } public ProtoBufUtil() { } @SuppressWarnings({"unchecked"}) public static <T> byte[] serializer(T obj) { Class<T> cls = (Class<T>) obj.getClass(); LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); try { Schema<T> schema = getSchema(cls); return ProtostuffIOUtil.toByteArray(obj, schema, buffer); } catch (Exception e) { System.out.println("protobuf serializer fail"); throw new IllegalStateException(e.getMessage(), e); } finally { buffer.clear(); } } public static <T> T deserializer(byte[] bytes, Class<T> clazz) { try { T message = (T) objenesis.newInstance(clazz); Schema<T> schema = getSchema(clazz); ProtostuffIOUtil.mergeFrom(bytes, message, schema); return message; } catch (Exception e) { System.out.println("protobuf deserializer fail"); throw new IllegalStateException(e.getMessage(), e); } } /** * 注册需要使用包装类进行序列化/反序列化的 Class 对象 * * @param clazz 需要包装的类型 Class 对象 */ public static void registerWrapperClass(Class clazz) { WRAPPER_SET.add(clazz); } /** *获取序列化对象类型的schema * * @param cls 序列化对象的class * @param <T> 序列化对象的类型 * @return 序列化对象类型的schema */ @SuppressWarnings({"unchecked", "rawtypes"}) private static <T> Schema<T> getSchema(Class<T> cls) { Schema<T> schema = (Schema<T>) CACHE_SCHEMA.get(cls); if (schema == null) { schema = RuntimeSchema.createFrom(cls); CACHE_SCHEMA.put(cls, schema); } return schema; } /** * 序列化对象 * * @param obj 需要序列化的对象 * @param <T> 序列化对象的类型 * @return 序列化后的二进制数组 */ @SuppressWarnings("unchecked") public static <T> byte[] serializeCollect(T obj) { Class<T> clazz = (Class<T>) obj.getClass(); LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); try { Object serializeObject = obj; Schema schema = WRAPPER_SCHEMA; if (!WRAPPER_SET.contains(clazz)) { schema = getSchema(clazz); } else { serializeObject = WrapperUtil.builder(obj); } return ProtostuffIOUtil.toByteArray(serializeObject, schema, buffer); } catch (Exception e) { System.out.println("Exception"); throw new IllegalStateException(e.getMessage(), e); } finally { buffer.clear(); } } /** * 反序列化对象 * * @param data 需要反序列化的二进制数组 * @param clazz 反序列化后的对象class * @param <T> 反序列化后的对象类型 * @return 反序列化后的对象集合 * SerializeDeserializeWrapper wrapper = SerializeDeserializeWrapper.builder(list); * byte[] serializeBytes = ProtostuffUtils.serialize(wrapper); * long end4 = System.currentTimeMillis(); * SerializeDeserializeWrapper deserializeWrapper = ProtostuffUtils.deserialize(serializeBytes, SerializeDeserializeWrapper.class); */ public static <T> T deserializeCollect(byte[] data, Class<T> clazz) { try { if (!WRAPPER_SET.contains(clazz)) { T message = clazz.newInstance(); Schema<T> schema = getSchema(clazz); ProtostuffIOUtil.mergeFrom(data, message, schema); return message; } else { WrapperUtil<T> wrapper = new WrapperUtil<T>(); ProtostuffIOUtil.mergeFrom(data, wrapper, WRAPPER_SCHEMA); return wrapper.getData(); } } catch (Exception e) { System.out.println("deserialize exception"); throw new IllegalStateException(e.getMessage(), e); } } public static byte[] subBytes(byte[] src, int begin, int count) { byte[] bs = new byte[count]; for (int i = begin; i < begin + count; i++) bs[i - begin] = src[i]; return bs; } public static byte[] intToByteArray(int i) { byte[] result = new byte[4]; result[0] = (byte) ((i >> 24) & 0xFF); result[1] = (byte) ((i >> 16) & 0xFF); result[2] = (byte) ((i >> 8) & 0xFF); result[3] = (byte) (i & 0xFF); return result; } public static int byteArrayToInt(byte[] bytes) { int value = 0; for (int i = 0; i < 4; i++) { int shift = (3 - i) * 8; value += (bytes[i] & 0xFF) << shift; } return value; } }
测试类自定义了一个简单的数据结构来写入文件、读取文件、反序列化数据并将该数据放入 JAVA 对象中。 Protostuff 目前对于这个操作序列是最快的,并且对于数百万和数千万的数据量来说非常快。
ProtoUsage.java package protoBuf; import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ProtoUsage { public static final String filePath = "E:\\testByte"; //params: list for test public static void writeByte2File(List<Product> prodList){ try{ if(new File(filePath).exists()){ new File(filePath).delete(); } FileOutputStream fos = new FileOutputStream(filePath, true); for (Product prod : prodList) { byte data[] = ProtoBufUtil.serializer(prod); byte dataLeng[] = ProtoBufUtil.intToByteArray(data.length); FileUtil.writeByte2FileFlush2Stream(fos, dataLeng, filePath); FileUtil.writeByte2FileFlush2Stream(fos, data, filePath); } }catch(Exception e){ e.printStackTrace(); } } public static void main(String[] args) { try{ int testCount=5000000; List<Product> prodList = new ArrayList<Product>(); for(int i=0;i<testCount;i++){ Product prod = new Product(); prod.setId("product="+i); prod.setName("product has a test name: testNo("+i+")"); prodList.add(prod); } long start = System.currentTimeMillis(); //将测试数据写入文件 writeByte2File(prodList); System.out.println("Write data time cost:"+(System.currentTimeMillis()-start)); //开始读取文件。 // ProtoStuff最大的优势就是非常快的序列化和反序列化速度, // 这节省了程序的数据处理时间。 . //首先一次读取所有数据 long treatStart = System.currentTimeMillis(); byte res[] = FileUtil.readDataFromFile(filePath); List<Product> resultProd = new ArrayList<Product>(); //二进制文件数据结构 // 0016testtesttesttest0018testestestestteste //0016(保存的此数据的长度)testtesttesttest0018(保存的此数据的长度)testestestestteste int hasRead = 0;//处理的数据量 byte length[] = new byte[4];//单个数据对象的长度 while (res.length != hasRead) { length[0] = res[0 + hasRead]; length[1] = res[1 + hasRead]; length[2] = res[2 + hasRead]; length[3] = res[3 + hasRead]; hasRead += 4; int dataLength = ProtoBufUtil.byteArrayToInt(length); byte finalByte[] = ProtoBufUtil.subBytes(res, hasRead, dataLength); Product prod = ProtoBufUtil.deserializer(finalByte, Product.class); resultProd.add(prod); hasRead += dataLength; } System.out.println("Read and treat Cost time:"+(System.currentTimeMillis()-treatStart)); System.out.println("list size:"+resultProd.size()); // resultProd.forEach(System.out::println); }catch(Exception e){ e.printStackTrace(); } } } //Object for test class Product{ String id; String name; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "name: " + this.getName() + ", id: " + this.getId(); } }
下面是测试结果。 我测试了 500 万数据。 该文件仅写入一次。 我们在测试中使用了传统的文件写入方式,因为在真实场景中一次写入文件的操作非常少。 使用NIO或其他方式会更快。
可以看到,读出500万条数据,并逐条反序列化所花费的时间只有3秒,这就是Protostuff 的优势所在了,在一些只需要小型本地文件存储的地方,读写和处理速度会非常快,非常有用。
Write data time cost:20914 毫秒 Read and treat Cost time:3142 毫秒 list size:5000000
0票
开心
0票
板砖
0票
感动
0票
有用
0票
疑问
0票
难过
0票
无聊
0票
震惊