`

利用java序列化实现基于文件的快速索引

阅读更多
       前言:本文介绍一种利用java序列化与反序列化的基于文件的快速索引.项目中可能会存在这样的需求场景:业务上需要从数万条记录中快速检索出满足条件的某条记录,而这数万条记录是随时变化的,比如抓包工具,时时刻刻接收不同的报文,将这些临时动态数据放入数据库性价比极低,引入Memcached等缓存工具又有杀鸡焉用牛刀的感觉,因此考虑直接将记录存入本地临时文件,采取一定的协议方式加快索引。

本篇文章重点关注以下问题:

  • 规定数据存放协议,加快索引
  • java利用序列化与反序列化实现对象的读写
  • 对象、字节数组、文件之间的转换
  • 整合Demo

1. 数据在文件中的存放协议

       在面向对象的语言中,将数据以对象的形式保存极为常见,而通过序列化把对象直接保存在文件或数据库中,再反序列化得到对象,中间过程无需自己解析也极为方便,但是因为对象序列化后需要保存类信息,存储空间开销较大,如何建立快速索引,避免不必要的开销则至关重要。

       首先规定每条记录的存储格式:索引(4byte) + 记录长度(4byte) + 数据(变长);

  • 索引为记录的唯一标识,如若命中索引,则返回数据部分;如若为命中,继续查找下一记录。(索引大小可自定义)
  • 记录长度为数据部分所占的存储空间,程序可根据此字段跳过此条记录数据部分的读取。(记录长度的大小可自定义)
  • 数据部分存储数据,不同对象可能会占用不同的空间。
     算法:
  1. 写文件:以流的方式写文件,将每条记录以上述协议一条一条写入文件,即可将对象直接序列化至文件,也可先将对象转换成字节数组后保存至文件。
  2. 读文件:以流的方式读文件,从文件头开始扫描,每条记录只读前8byte(索引+记录长度),如若命中索引,则读取数据部分后直接返回对象;如若未命中记录,则skip(跳过)数据部分,继续下一条数据的比对。
      此方式主要是在读数据时避免读取无用数据,毕竟对象序列化后所占的空间较大。

2. java利用序列化与反序列化实现对象的读写

2.1 定义实体类

       实体类为需要序列化保存的数据,需要注意的是所有序列化保存的对象必须实现Serializable接口(Externalnalizable也行)。

/**
 * 实体类保存信息
 * @author Administrator
 */
public class PersonEntity implements Serializable {
    private static final long serialVersionUID = -5802989937522643573L;
    
    private String name;
    private int    age;
    
    public PersonEntity(String name, int age) {
        this.name = name;
        this.age  = age;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "PersonEntity [name=" + name + ", age=" + age + "]";
    }
    
}

 2.2 序列化、反序列化对象,并实现数据读写

/**
 * 将对象向文件中写、读
 * @author Administrator
 */
public class Main {
    
    private static String FILE_PATH = "E:/temp.cache";
    private static File   FILE_TEMP;
    
    static {
        try {
            FILE_TEMP = new File(FILE_PATH);
            FILE_TEMP.delete();
            FILE_TEMP.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        
        try (   FileOutputStream   outF = new FileOutputStream(FILE_TEMP);
                ObjectOutputStream out = new ObjectOutputStream(outF)) {
            /* 准备写入文件的对象(数据部分). */
            PersonEntity entity1 = new PersonEntity("熊燕子", 26);
            PersonEntity entity2 = new PersonEntity("军", 25);
            /* 写第一条记录. */
            out.writeInt(1);
            out.writeInt(64);
            out.writeObject(entity1);
            /* 写第二条记录. */
            out.writeInt(2);
            out.writeInt(128);
            out.writeObject(entity2);
            /* 将数据刷入文件. */
            out.flush();
        }
        
        try (   FileInputStream   inF = new FileInputStream(FILE_TEMP);
                ObjectInputStream in  = new ObjectInputStream(inF)) {
            
            /* 读第一条记录. */
            int          no1     = in.readInt();
            int          len1    = in.readInt();
            PersonEntity entity1 = (PersonEntity) in.readObject();
            /* 读第二条记录. */
            int          no2     = in.readInt();
            int          len2    = in.readInt();
            PersonEntity entity2 = (PersonEntity) in.readObject();
            
            System.out.println("no1 = " + no1 + "; len1 = " + len1 + "; " + entity1);
            System.out.println("no2 = " + no2 + "; len2 = " + len2 + "; " + entity2);
        }

    }
}

      上述运行结果为:

       从运行结果可以看出对象被正确写入文件、正确从文件中读取,但是上述过程并没有发挥快速检索的优势,仍然是从流中依次读取每个字段。分析ObjectOutputStream、ObjectInputStream的API,貌似并不支持对象的skip方法,因此,可以考虑将对象转化为字节数组写入流中,从流中读取对象的字节数组后转为我们所需要的对象。那么,关键问题就转为如何实现对象和字节数组之间的等价转换。

3. 对象、字节数组、文件之间的转换

        直接上转换的工具类:

package com.wj.serializable;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Byte_File_Object {
    
    /**
     * 将文件内容转化为字节数组
     * @param file      文件
     * @return
     * @throws IOException 
     */
    public static byte[] getBytesFromFile (File file) throws IOException {
        if (file == null) return null;
        
        try (   FileInputStream       inF = new FileInputStream(file);
                ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            byte[] bs      = new byte[1024];
            int    readLen = 0;
            while ((readLen = inF.read(bs)) != -1) {
                out.write(bs, 0, readLen);
            }
            return out.toByteArray();
        }
    }
    
    /**
     * 把字节数组保存为一个文件
     * @param bs        待保存的字节数组
     * @param filePath  文件路径
     * @return
     * @throws IOException 
     */
    public static File getFileFromBytes(byte[] bs, String filePath) throws IOException {
        File file = new File(filePath);
        try (   FileOutputStream     fileOutPutStream   = new FileOutputStream(file);
                BufferedOutputStream bufferOutputStream = new BufferedOutputStream(fileOutPutStream)) {
            bufferOutputStream.write(bs);
        }
        return file;
    }
    
    /**
     * 将字节数组转化为对象
     * @param objBytes      待转化的对象
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static Object getObjectFromBytes(byte[] objBytes) throws IOException, ClassNotFoundException {
        if (objBytes == null || objBytes.length == 0) return null;
        
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(objBytes);
        ObjectInputStream    objectInputStream    = new ObjectInputStream(byteArrayInputStream);
        
        return objectInputStream.readObject();
    }
    
    /**
     * 将对象转化为字节数组
     * @param obj
     * @return
     * @throws IOException
     */
    public static byte[] getBytesFromObject(Serializable obj) throws IOException {
        if (obj == null) return null;
        
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream    objectOutputStream    = new ObjectOutputStream(byteArrayOutputStream);
        
        objectOutputStream.writeObject(obj);
        return byteArrayOutputStream.toByteArray();
    }
}

      只要了解java流的读写API都是基于装饰者模式,很容易理解上述代码,对象、文件、字节数组之间的转化也很简单。下面将对象的序列化读写操作和上述对象、字节数组之间的转化相结合,实现基于文件存储的快速索引。

 4. 整合利用java序列化与反序列化的基于文件的快速索引Demo

package com.wj.serializable;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 快速索引Demo
 * 按照每条记录的协议:索引(4byte) + 数据长度(4byte) + 数据部分(变长) 的格式写入三条记录
 * 读取数据的时候:
 *      * 第一条数据假设命中,读取数据部分;
 *      * 第二条记录假设未命中,跳过数据部分;
 *      * 第三条记录假设命中,读取数据部分
 * 若可正确读取,则表示此方式可行(性能项目中实测提升名明显,这里未做测试)
 */
public class QuickIndex {

    private static String FILE_PATH = "E:/temp.cache";
    private static File   FILE_TEMP;
    
    static {
        try {
            FILE_TEMP = new File(FILE_PATH);
            FILE_TEMP.delete();
            FILE_TEMP.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        /** 第一部分连续写入三条记录. */
        try (   FileOutputStream   outF = new FileOutputStream(FILE_TEMP);
                ObjectOutputStream out = new ObjectOutputStream(outF)) {
            /* 准备写入文件的对象(数据部分). */
            PersonEntity entity1 = new PersonEntity("熊燕子", 26);
            PersonEntity entity2 = new PersonEntity("小王", 25);
            PersonEntity entity3 = new PersonEntity("大王", 49);
            
            /* 写第一条记录. */
            byte[] objBytes1 = Byte_File_Object.getBytesFromObject(entity1);
            int    no1       = 1;
            int    len1      = objBytes1.length;
            out.writeInt(no1);
            out.writeInt(len1);
            out.write(objBytes1);
            /* 写第二条记录. */
            byte[] objBytes2 = Byte_File_Object.getBytesFromObject(entity2);
            int    no2       = 2;
            int    len2      = objBytes2.length;
            out.writeInt(no2);
            out.writeInt(len2);
            out.write(objBytes2);
            /* 写第三条记录. */
            byte[] objBytes3 = Byte_File_Object.getBytesFromObject(entity3);
            int    no3       = 3;
            int    len3      = objBytes3.length;
            out.writeInt(no3);
            out.writeInt(len3);
            out.write(objBytes3);
            /* 将数据刷入文件. */
            out.flush();
        }

        /** 第二部分读取记录(第二条记录skip). */
        try (   FileInputStream   inF = new FileInputStream(FILE_TEMP);
                ObjectInputStream in  = new ObjectInputStream(inF)) {
            
            /* 读第一条记录. */
            int          no1        = in.readInt();
            int          len1       = in.readInt();
            byte[]       byteArray1 = readAccurateLenBytesFromInStream(in, len1);
            PersonEntity entity1    = (PersonEntity) Byte_File_Object.getObjectFromBytes(byteArray1);
            
            /* 读第二条记录. */
            int no2  = in.readInt();
            int len2 = in.readInt();
            in.skip(len2);  // 跳过第二条记录的数据部分
            
            /* 读第三条记录. */
            int          no3        = in.readInt();
            int          len3       = in.readInt();
            byte[]       byteArray3 = readAccurateLenBytesFromInStream(in, len3);
            PersonEntity entity3    = (PersonEntity) Byte_File_Object.getObjectFromBytes(byteArray3);
            
            System.out.println("no1 = " + no1 + "; len1 = " + len1 + "; " + entity1);
            System.out.println("no2 = " + no2 + "; len2 = " + len2 + "; ");
            System.out.println("no3 = " + no3 + "; len3 = " + len3 + "; " + entity3);
        }
        
    }

    /**
     * 从输入流中读取指定长度的字节,保存到字节数组中
     * @param in        输入流
     * @param len       读取的字节数
     * @return          返回字节数组
     * @throws IOException
     */
    private static byte[] readAccurateLenBytesFromInStream(ObjectInputStream in, int len) throws IOException {
        byte[] bs   = new byte[len];
        int tempLen = 0;
        int readLen = 0;
        while (true) {
            tempLen =  in.read(bs, readLen, len - readLen);
            readLen += tempLen;
            if (readLen == len) break;
        }
        return bs;
    }
    
}

        运行结果为:


        可以发现,读取流的过程中,成功跳过第二条记录的数据部分,正确读出第三条记录。

        代码中的相关说明已经很详细,我这里也不做过多阐述,不过其中有一点值得一提,java从输入流以阻塞方式读取数据时,受操作系统保存文件的分区或网络传输的影响,一次读取的字节数组大小并不确定,因此提取readAccurateLenBytesFromInStream方法从流中读取指定大小的字节数组,从而正确转化为对象。

 

代码下载地址:http://pan.baidu.com/s/1dE5HMH7,密码:h7hl

  • 大小: 9.2 KB
  • 大小: 10.2 KB
分享到:
评论

相关推荐

    使用Java实现一个基于内存的英文全文检索搜索引擎【100012394】

    内存里建立好的索引对象可以序列化到文件,同时可以从文件里反序列化成内存里的索引对象; 可以在控制台输出索引的内容。 功能 2:基于构建好的索引,实现单个搜索关键词的全文检索,包含的子功能包括: 根据搜索...

    java源码包---java 源码 大量 实例

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    Java思维导图xmind文件+导出图片

    理解通信协议传输过程中的序列化和反序列化机制 基于框架的RPC通信技术 WebService/ApacheCXF RMI/Spring RMI Hession 传统RPC技术在大型分布式架构下面临的问题 分布式架构下的RPC解决方案 Zookeeper ...

    android 快速的搜索手机文件引擎 java I/O的应用

    本篇文章将深入探讨如何利用Java I/O在Android上实现高效的文件搜索功能。 1. **Java I/O基础** - Java I/O库提供了大量的类和接口,如InputStream、OutputStream、Reader、Writer等,用于处理不同类型的输入和...

    java版二级文件系统

    这些信息可以通过自定义的数据结构存储,并利用Java的持久化机制(如序列化或数据库)来保存。 2. 文件的逻辑结构和物理结构:文件的逻辑结构是用户看到的,如顺序结构、链接结构等;物理结构则是实际在磁盘上的...

    java完美解析pdm

    Java完美解析PDM,这是一种利用Java编程语言对Power Designer创建的PDM(Physical Data Model,物理数据模型)文件进行解析的技术。PDM是数据库设计工具Power Designer中的一个重要概念,它通常包含了数据库表、列、...

    利用java实现绘图板/画图板

    【标题】"利用Java实现绘图板/画图板"主要涉及的是在Java编程环境中创建一个用户界面(UI)组件,让用户能够进行图形绘制。这个主题涵盖了Java GUI(图形用户界面)的基础知识,特别是使用Java Swing库来构建这样的...

    java源码包2

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    java源码包3

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    java毕业设计-基于java的聊天系统的设计于实现(系统+论文).zip

    Java的Serializable接口可以实现对象的序列化,而反序列化则将字节流还原为可操作的对象。 7. **安全性** 对于任何涉及用户信息的系统,安全性都是重要的一环。可能涉及到的包括数据加密、防止SQL注入等措施。 8....

    成百上千个Java 源码DEMO 4(1-4是独立压缩包)

    密钥 Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存...

    java源码包4

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    成百上千个Java 源码DEMO 3(1-4是独立压缩包)

    密钥 Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存...

    人工智能-项目实践-搜索引擎-基于java开发的倒排索引的搜索引擎demo

    倒排索引是搜索引擎的核心组件,它的基本思想是将每个词在文档中的位置作为索引,从而实现快速定位到包含特定词的文档。具体步骤如下: 1. 分词:对输入的文档进行分词,将连续的有意义的字符序列(如单词)提取...

    JAVA上百实例源码以及开源项目源代码

    密钥 Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存...

    基于java实现的图书管理系统源码+数据库.zip

    开发者可能使用了Java Swing或JavaFX来构建图形用户界面(GUI),并利用Java的类库进行文件操作和网络通信。 2. **MVC(Model-View-Controller)设计模式**:在大型应用程序中,MVC模式被广泛采用,以分离业务逻辑...

    java开源包4

    6、支持多种通信框架(Mina/Netty/Grizzly),支持多种序列化/反序列化(Java/Hessian/PB); 7、支持自定义通信协议,可完全替换NFS-RPC自带的协议。 淘宝开放平台JAVA版SDK top4java 设计原则 容易维护扩展(不...

    基于java的开源搜索系统 Red-Piranha.zip

    - **序列化与反序列化**:使用Java的`Serializable`接口进行对象的序列化和反序列化,便于在网络间传输数据。 - **框架与库**:可能集成Spring、Apache Lucene等Java框架和库,简化开发工作。 4. **部署与使用** ...

    Java编写的DOS操作界面学生信息管理系统

    相反,`FileInputStream`和`ObjectInputStream`用于从文件读取字节流并反序列化回对象。 3. **没有GUI**:这个管理系统没有图形用户界面,而是采用DOS命令行进行交互。用户通过输入特定的命令来进行年级和班级的...

Global site tag (gtag.js) - Google Analytics