这篇文章主要讲解了“Java持久化和命令行怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java持久化和命令行怎么用”吧!
延寿ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为创新互联建站的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:18982081108(备注:SSL证书合作)期待与您的合作!
数据库选择
我们这里使用的是Java来实现,BoltDB不支持Java,这里我们选用 Rocksdb 。
数据结构
在我们开始实现数据持久化之前,我们先要确定我们该如何去存储我们的数据。为此,我们先来看看比特币是怎么做的。
简单来讲,比特币使用了两个"buckets(桶)"来存储数据:
- blocks. 描述链上所有区块的元数据. 
- chainstate. 存储区块链的状态,指的是当前所有的 - UTXO(未花费交易输出)以及一些元数据.
“在比特币的世界里既没有账户,也没有余额,只有分散到区块链里的UTXO。”
详见:《精通比特币》第二版 第06章节 —— 交易的输入与输出
此外,每个区块数据都是以单独的文件形式存储在磁盘上。这样做是出于性能的考虑:当读取某一个单独的区块数据时,不需要加载所有的区块数据到内存中来。
在 blocks 这个桶中,存储的键值对:
- 'b' + 32-byte block hash -> block index record - 区块的索引记录 
- 'f' + 4-byte file number -> file information record - 文件信息记录 
- 'l' -> 4-byte file number: the last block file number used - 最新的一个区块所使用的文件编码 
- 'R' -> 1-byte boolean: whether we're in the process of reindexing - 是否处于重建索引的进程当中 
- 'F' + 1-byte flag name length + flag name string -> 1 byte boolean: various flags that can be on or off - 各种可以打开或关闭的flag标志 
- 't' + 32-byte transaction hash -> transaction index record - 交易索引记录 
在 chainstate 这个桶中,存储的键值对:
- 'c' + 32-byte transaction hash -> unspent transaction output record for that transaction - 某笔交易的UTXO记录 
- 'B' -> 32-byte block hash: the block hash up to which the database represents the unspent transaction outputs - 数据库所表示的UTXO的区块Hash(抱歉,这一点我还没弄明白……) 
由于我们还没有实现交易相关的特性,因此,我们这里只使用 block 桶。另外,前面提到过的,这里我们不会实现各个区块数据各自存储在独立的文件上,而是统一存放在一个文件里面。因此,我们不要存储和文件编码相关的数据,这样一来,我们所用到的键值对就简化为:
- 32-byte block-hash -> Block structure (serialized) - 区块数据与区块hash的键值对 
- 'l' -> the hash of the last block in a chain - 最新一个区块hash的键值对 
(查看更加详细的解释)
序列化
RocksDB的Key与Value只能以byte[]的形式进行存储,这里我们需要用到序列化与反序列化库 Kryo,代码如下:
package one.wangwei.blockchain.util;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
/**
 * 序列化工具类
 *
 * @author wangwei
 * @date 2018/02/07
 */
public class SerializeUtils {
    /**
     * 反序列化
     *
     * @param bytes 对象对应的字节数组
     * @return
     */
    public static Object deserialize(byte[] bytes) {
        Input input = new Input(bytes);
        Object obj = new Kryo().readClassAndObject(input);
        input.close();
        return obj;
    }
    /**
     * 序列化
     *
     * @param object 需要序列化的对象
     * @return
     */
    public static byte[] serialize(Object object) {
        Output output = new Output(4096, -1);
        new Kryo().writeClassAndObject(output, object);
        byte[] bytes = output.toBytes();
        output.close();
        return bytes;
    }
}持久化
上面已经说过,我们这里使用RocksDB,我们先写一个相关的工具类RocksDBUtils,主要的功能如下:
- putLastBlockHash:保存最新一个区块的Hash值 
- getLastBlockHash:查询最新一个区块的Hash值 
- putBlock:保存区块 
- getBlock:查询区块 
注意:BoltDB 支持 Bucket 的特性,而RocksDB 不支持,所以需要我们自己使用Map来做一个映射。
RocksDBUtils
package one.wangwei.blockchain.store;
import com.google.common.collect.Maps;
import one.wangwei.blockchain.block.Block;
import one.wangwei.blockchain.util.SerializeUtils;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import java.util.Map;
/**
 * 存储工具类
 *
 * @author wangwei
 * @date 2018/02/27
 */
public class RocksDBUtils {
    /**
     * 区块链数据文件
     */
    private static final String DB_FILE = "blockchain.db";
    /**
     * 区块桶前缀
     */
    private static final String BLOCKS_BUCKET_KEY = "blocks";
    /**
     * 最新一个区块
     */
    private static final String LAST_BLOCK_KEY = "l";
    private volatile static RocksDBUtils instance;
    public static RocksDBUtils getInstance() {
        if (instance == null) {
            synchronized (RocksDBUtils.class) {
                if (instance == null) {
                    instance = new RocksDBUtils();
                }
            }
        }
        return instance;
    }
    private RocksDB db;
    /**
     * block buckets
     */
    private Map blocksBucket;
    private RocksDBUtils() {
        openDB();
        initBlockBucket();
    }
    /**
     * 打开数据库
     */
    private void openDB() {
        try {
            db = RocksDB.open(DB_FILE);
        } catch (RocksDBException e) {
            throw new RuntimeException("Fail to open db ! ", e);
        }
    }
    /**
     * 初始化 blocks 数据桶
     */
    private void initBlockBucket() {
        try {
            byte[] blockBucketKey = SerializeUtils.serialize(BLOCKS_BUCKET_KEY);
            byte[] blockBucketBytes = db.get(blockBucketKey);
            if (blockBucketBytes != null) {
                blocksBucket = (Map) SerializeUtils.deserialize(blockBucketBytes);
            } else {
                blocksBucket = Maps.newHashMap();
                db.put(blockBucketKey, SerializeUtils.serialize(blocksBucket));
            }
        } catch (RocksDBException e) {
            throw new RuntimeException("Fail to init block bucket ! ", e);
        }
    }
    /**
     * 保存最新一个区块的Hash值
     *
     * @param tipBlockHash
     */
    public void putLastBlockHash(String tipBlockHash) {
        try {
            blocksBucket.put(LAST_BLOCK_KEY, SerializeUtils.serialize(tipBlockHash));
            db.put(SerializeUtils.serialize(BLOCKS_BUCKET_KEY), SerializeUtils.serialize(blocksBucket));
        } catch (RocksDBException e) {
            throw new RuntimeException("Fail to put last block hash ! ", e);
        }
    }
    /**
     * 查询最新一个区块的Hash值
     *
     * @return
     */
    public String getLastBlockHash() {
        byte[] lastBlockHashBytes = blocksBucket.get(LAST_BLOCK_KEY);
        if (lastBlockHashBytes != null) {
            return (String) SerializeUtils.deserialize(lastBlockHashBytes);
        }
        return "";
    }
    /**
     * 保存区块
     *
     * @param block
     */
    public void putBlock(Block block) {
        try {
            blocksBucket.put(block.getHash(), SerializeUtils.serialize(block));
            db.put(SerializeUtils.serialize(BLOCKS_BUCKET_KEY), SerializeUtils.serialize(blocksBucket));
        } catch (RocksDBException e) {
            throw new RuntimeException("Fail to put block ! ", e);
        }
    }
    /**
     * 查询区块
     *
     * @param blockHash
     * @return
     */
    public Block getBlock(String blockHash) {
        return (Block) SerializeUtils.deserialize(blocksBucket.get(blockHash));
    }
    /**
     * 关闭数据库
     */
    public void closeDB() {
        try {
            db.close();
        } catch (Exception e) {
            throw new RuntimeException("Fail to close db ! ", e);
        }
    }
} 创建区块链
现在我们来优化 Blockchain.newBlockchain 接口的代码逻辑,改为如下逻辑:

代码如下:
/** *创建区块链
* * @return */ public static Blockchain newBlockchain() throws Exception { String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash(); if (StringUtils.isBlank(lastBlockHash)) { Block genesisBlock = Block.newGenesisBlock(); lastBlockHash = genesisBlock.getHash(); RocksDBUtils.getInstance().putBlock(genesisBlock); RocksDBUtils.getInstance().putLastBlockHash(lastBlockHash); } return new Blockchain(lastBlockHash); }
修改 Blockchain 的数据结构,只记录最新一个区块链的Hash值
public class Blockchain {
    
    @Getter
    private String lastBlockHash;
    private Blockchain(String lastBlockHash) {
        this.lastBlockHash = lastBlockHash;
    }
}每次挖矿完成后,我们也需要将最新的区块信息保存下来,并且更新最新区块链Hash值:
/** *添加区块
* * @param data */ public void addBlock(String data) throws Exception { String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash(); if (StringUtils.isBlank(lastBlockHash)) { throw new Exception("Fail to add block into blockchain ! "); } this.addBlock(Block.newBlock(lastBlockHash, data)); } /** *添加区块
* * @param block */ public void addBlock(Block block) throws Exception { RocksDBUtils.getInstance().putLastBlockHash(block.getHash()); RocksDBUtils.getInstance().putBlock(block); this.lastBlockHash = block.getHash(); }
到此,存储部分的功能就实现完毕,我们还缺少一个功能:
检索区块链
现在,我们所有的区块都保存到了数据库,因此,我们能够重新打开已有的区块链并且向其添加新的区块。但这也导致我们再也无法打印出区块链中所有区块的信息,因为,我们没有将区块存储在数组当中。让我们来修复这个瑕疵!
我们在Blockchain中创建一个内部类 BlockchainIterator ,作为区块链的迭代器,通过区块之前的hash连接来依次迭代输出区块信息,代码如下:
public class Blockchain {
 
    ....
    
    /**
     * 区块链迭代器
     */
    public class BlockchainIterator {
        private String currentBlockHash;
        public BlockchainIterator(String currentBlockHash) {
            this.currentBlockHash = currentBlockHash;
        }
        /**
         * 是否有下一个区块
         *
         * @return
         */
        public boolean hashNext() throws Exception {
            if (StringUtils.isBlank(currentBlockHash)) {
                return false;
            }
            Block lastBlock = RocksDBUtils.getInstance().getBlock(currentBlockHash);
            if (lastBlock == null) {
                return false;
            }
            // 创世区块直接放行
            if (lastBlock.getPrevBlockHash().length() == 0) {
                return true;
            }
            return RocksDBUtils.getInstance().getBlock(lastBlock.getPrevBlockHash()) != null;
        }
        
        /**
         * 返回区块
         *
         * @return
         */
        public Block next() throws Exception {
            Block currentBlock = RocksDBUtils.getInstance().getBlock(currentBlockHash);
            if (currentBlock != null) {
                this.currentBlockHash = currentBlock.getPrevBlockHash();
                return currentBlock;
            }
            return null;
        }
    }   
    
    ....    
}测试
/**
 * 测试
 *
 * @author wangwei
 * @date 2018/02/05
 */
public class BlockchainTest {
    public static void main(String[] args) {
        try {
            Blockchain blockchain = Blockchain.newBlockchain();
            blockchain.addBlock("Send 1.0 BTC to wangwei");
            blockchain.addBlock("Send 2.5 more BTC to wangwei");
            blockchain.addBlock("Send 3.5 more BTC to wangwei");
            for (Blockchain.BlockchainIterator iterator = blockchain.getBlockchainIterator(); iterator.hashNext(); ) {
                Block block = iterator.next();
                if (block != null) {
                    boolean validate = ProofOfWork.newProofOfWork(block).validate();
                    System.out.println(block.toString() + ", validate = " + validate);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/*输出*/
Block{hash='0000012f87a0510dd0ee7048a6bd52db3002bae7d661126dc28287bd6c23189a', prevBlockHash='0000024b2c23c4fb06c2e2c1349275d415efe17a51db24cd4883da0067300ddf', data='Send 3.5 more BTC to wangwei', timeStamp=1519724875, nonce=369110}, validate = true
Block{hash='0000024b2c23c4fb06c2e2c1349275d415efe17a51db24cd4883da0067300ddf', prevBlockHash='00000b14fefb51ba2a7428549d469bcf3efae338315e7289d3e6dc4caf589d79', data='Send 2.5 more BTC to wangwei', timeStamp=1519724872, nonce=896348}, validate = true
Block{hash='00000b14fefb51ba2a7428549d469bcf3efae338315e7289d3e6dc4caf589d79', prevBlockHash='0000099ced1b02f40c750c5468bb8c4fd800ec9f46fea5d8b033e5d054f0f703', data='Send 1.0 BTC to wangwei', timeStamp=1519724869, nonce=673955}, validate = true
Block{hash='0000099ced1b02f40c750c5468bb8c4fd800ec9f46fea5d8b033e5d054f0f703', prevBlockHash='', data='Genesis Block', timeStamp=1519724866, nonce=840247}, validate = true命令行界面
CLI 部分的内容,这里不做详细介绍,具体可以去查看文末的Github源码链接。大致步骤如下:
配置
添加pom.xml配置
... ... commons-cli commons-cli 1.4 ... org.apache.maven.plugins maven-assembly-plugin 3.1.0 true lib/ one.wangwei.blockchain.cli.Main jar-with-dependencies make-assembly package single 
项目工程打包
$ mvn clean && mvn package
执行命令
# 打印帮助信息 $ java -jar blockchain-java-jar-with-dependencies.jar -h # 添加区块 $ java -jar blockchain-java-jar-with-dependencies.jar -add "Send 1.5 BTC to wangwei" $ java -jar blockchain-java-jar-with-dependencies.jar -add "Send 2.5 BTC to wangwei" $ java -jar blockchain-java-jar-with-dependencies.jar -add "Send 3.5 BTC to wangwei" # 打印区块链 $ java -jar blockchain-java-jar-with-dependencies.jar -print
感谢各位的阅读,以上就是“Java持久化和命令行怎么用”的内容了,经过本文的学习后,相信大家对Java持久化和命令行怎么用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是创新互联,小编将为大家推送更多相关知识点的文章,欢迎关注!
当前题目:Java持久化和命令行怎么用
网站路径:http://www.scyingshan.cn/article/gepjos.html

 建站
建站
 咨询
咨询 售后
售后
 建站咨询
建站咨询 
 