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 モードでのappend write
* @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<>();
/**
* ラッパークラスのシリアル化/逆シリアル化 Class オブジェクト
*/
private static final Class<WrapperUtil> WRAPPER_CLASS = WrapperUtil.class;
/**
* ラッパー クラス スキーマ オブジェクトのシリアル化/逆シリアル化
*/
private static final Schema<WrapperUtil> WRAPPER_SCHEMA = RuntimeSchema.createFrom(WRAPPER_CLASS);
/**
* キャッシュオブジェクトとオブジェクトスキーマ情報の収集
*/
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);
}
/**
* シリアル化されたオブジェクトタイプのスキーマを取得します
* @param cls ラップする必要がある型 Class オブジェクト
* @param <T> シリアル化されたオブジェクトのタイプ
* @return シリアル化されたオブジェクト型のスキーマ
*/
@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 逆シリアル化されたオブジェクト クラス
* @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 万個のデータを使用してテストしました。ファイルは 1 回だけ書き込まれます。実際のシーンではファイルを 1 回書き込む操作はほとんどないため、テストでは従来のファイル書き込み方法を使用しました。 NIO または他の手段を使用すると、より高速になります。
Write data time cost:20914 ミリ秒
Read and treat Cost time:3142 ミリ秒
list size:5000000