Java持久化和命令行怎么用

Java持久化和命令行怎么用

这篇文章主要讲解了“Java持久化和命令行怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java持久化和命令行怎么用”吧!

数据库选择

我们这里使用的是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,代码如下:

packageone.wangwei.blockchain.util;importcom.esotericsoftware.kryo.Kryo;importcom.esotericsoftware.kryo.io.Input;importcom.esotericsoftware.kryo.io.Output;/***序列化工具类**@authorwangwei*@date2018/02/07*/publicclassSerializeUtils{/***反序列化**@parambytes对象对应的字节数组*@return*/publicstaticObjectdeserialize(byte[]bytes){Inputinput=newInput(bytes);Objectobj=newKryo().readClassAndObject(input);input.close();returnobj;}/***序列化**@paramobject需要序列化的对象*@return*/publicstaticbyte[]serialize(Objectobject){Outputoutput=newOutput(4096,-1);newKryo().writeClassAndObject(output,object);byte[]bytes=output.toBytes();output.close();returnbytes;}}

持久化

上面已经说过,我们这里使用RocksDB,我们先写一个相关的工具类RocksDBUtils,主要的功能如下:

  • putLastBlockHash:保存最新一个区块的Hash值

  • getLastBlockHash:查询最新一个区块的Hash值

  • putBlock:保存区块

  • getBlock:查询区块

注意:BoltDB 支持 Bucket 的特性,而RocksDB 不支持,所以需要我们自己使用Map来做一个映射。

RocksDBUtils

packageone.wangwei.blockchain.store;importcom.google.common.collect.Maps;importone.wangwei.blockchain.block.Block;importone.wangwei.blockchain.util.SerializeUtils;importorg.rocksdb.RocksDB;importorg.rocksdb.RocksDBException;importjava.util.Map;/***存储工具类**@authorwangwei*@date2018/02/27*/publicclassRocksDBUtils{/***区块链数据文件*/privatestaticfinalStringDB_FILE="blockchain.db";/***区块桶前缀*/privatestaticfinalStringBLOCKS_BUCKET_KEY="blocks";/***最新一个区块*/privatestaticfinalStringLAST_BLOCK_KEY="l";privatevolatilestaticRocksDBUtilsinstance;publicstaticRocksDBUtilsgetInstance(){if(instance==null){synchronized(RocksDBUtils.class){if(instance==null){instance=newRocksDBUtils();}}}returninstance;}privateRocksDBdb;/***blockbuckets*/privateMap<String,byte[]>blocksBucket;privateRocksDBUtils(){openDB();initBlockBucket();}/***打开数据库*/privatevoidopenDB(){try{db=RocksDB.open(DB_FILE);}catch(RocksDBExceptione){thrownewRuntimeException("Failtoopendb!",e);}}/***初始化blocks数据桶*/privatevoidinitBlockBucket(){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(RocksDBExceptione){thrownewRuntimeException("Failtoinitblockbucket!",e);}}/***保存最新一个区块的Hash值**@paramtipBlockHash*/publicvoidputLastBlockHash(StringtipBlockHash){try{blocksBucket.put(LAST_BLOCK_KEY,SerializeUtils.serialize(tipBlockHash));db.put(SerializeUtils.serialize(BLOCKS_BUCKET_KEY),SerializeUtils.serialize(blocksBucket));}catch(RocksDBExceptione){thrownewRuntimeException("Failtoputlastblockhash!",e);}}/***查询最新一个区块的Hash值**@return*/publicStringgetLastBlockHash(){byte[]lastBlockHashBytes=blocksBucket.get(LAST_BLOCK_KEY);if(lastBlockHashBytes!=null){return(String)SerializeUtils.deserialize(lastBlockHashBytes);}return"";}/***保存区块**@paramblock*/publicvoidputBlock(Blockblock){try{blocksBucket.put(block.getHash(),SerializeUtils.serialize(block));db.put(SerializeUtils.serialize(BLOCKS_BUCKET_KEY),SerializeUtils.serialize(blocksBucket));}catch(RocksDBExceptione){thrownewRuntimeException("Failtoputblock!",e);}}/***查询区块**@paramblockHash*@return*/publicBlockgetBlock(StringblockHash){return(Block)SerializeUtils.deserialize(blocksBucket.get(blockHash));}/***关闭数据库*/publicvoidcloseDB(){try{db.close();}catch(Exceptione){thrownewRuntimeException("Failtoclosedb!",e);}}}

创建区块链

现在我们来优化 Blockchain.newBlockchain 接口的代码逻辑,改为如下逻辑:

代码如下:

/***<p>创建区块链</p>**@return*/publicstaticBlockchainnewBlockchain()throwsException{StringlastBlockHash=RocksDBUtils.getInstance().getLastBlockHash();if(StringUtils.isBlank(lastBlockHash)){BlockgenesisBlock=Block.newGenesisBlock();lastBlockHash=genesisBlock.getHash();RocksDBUtils.getInstance().putBlock(genesisBlock);RocksDBUtils.getInstance().putLastBlockHash(lastBlockHash);}returnnewBlockchain(lastBlockHash);}

修改 Blockchain 的数据结构,只记录最新一个区块链的Hash值

publicclassBlockchain{@GetterprivateStringlastBlockHash;privateBlockchain(StringlastBlockHash){this.lastBlockHash=lastBlockHash;}}

每次挖矿完成后,我们也需要将最新的区块信息保存下来,并且更新最新区块链Hash值:

/***<p>添加区块</p>**@paramdata*/publicvoidaddBlock(Stringdata)throwsException{StringlastBlockHash=RocksDBUtils.getInstance().getLastBlockHash();if(StringUtils.isBlank(lastBlockHash)){thrownewException("Failtoaddblockintoblockchain!");}this.addBlock(Block.newBlock(lastBlockHash,data));}/***<p>添加区块</p>**@paramblock*/publicvoidaddBlock(Blockblock)throwsException{RocksDBUtils.getInstance().putLastBlockHash(block.getHash());RocksDBUtils.getInstance().putBlock(block);this.lastBlockHash=block.getHash();}

到此,存储部分的功能就实现完毕,我们还缺少一个功能:

检索区块链

现在,我们所有的区块都保存到了数据库,因此,我们能够重新打开已有的区块链并且向其添加新的区块。但这也导致我们再也无法打印出区块链中所有区块的信息,因为,我们没有将区块存储在数组当中。让我们来修复这个瑕疵!

我们在Blockchain中创建一个内部类 BlockchainIterator ,作为区块链的迭代器,通过区块之前的hash连接来依次迭代输出区块信息,代码如下:

publicclassBlockchain{..../***区块链迭代器*/publicclassBlockchainIterator{privateStringcurrentBlockHash;publicBlockchainIterator(StringcurrentBlockHash){this.currentBlockHash=currentBlockHash;}/***是否有下一个区块**@return*/publicbooleanhashNext()throwsException{if(StringUtils.isBlank(currentBlockHash)){returnfalse;}BlocklastBlock=RocksDBUtils.getInstance().getBlock(currentBlockHash);if(lastBlock==null){returnfalse;}//创世区块直接放行if(lastBlock.getPrevBlockHash().length()==0){returntrue;}returnRocksDBUtils.getInstance().getBlock(lastBlock.getPrevBlockHash())!=null;}/***返回区块**@return*/publicBlocknext()throwsException{BlockcurrentBlock=RocksDBUtils.getInstance().getBlock(currentBlockHash);if(currentBlock!=null){this.currentBlockHash=currentBlock.getPrevBlockHash();returncurrentBlock;}returnnull;}}....}

测试

/***测试**@authorwangwei*@date2018/02/05*/publicclassBlockchainTest{publicstaticvoidmain(String[]args){try{Blockchainblockchain=Blockchain.newBlockchain();blockchain.addBlock("Send1.0BTCtowangwei");blockchain.addBlock("Send2.5moreBTCtowangwei");blockchain.addBlock("Send3.5moreBTCtowangwei");for(Blockchain.BlockchainIteratoriterator=blockchain.getBlockchainIterator();iterator.hashNext();){Blockblock=iterator.next();if(block!=null){booleanvalidate=ProofOfWork.newProofOfWork(block).validate();System.out.println(block.toString()+",validate="+validate);}}}catch(Exceptione){e.printStackTrace();}}}/*输出*/Block{hash='0000012f87a0510dd0ee7048a6bd52db3002bae7d661126dc28287bd6c23189a',prevBlockHash='0000024b2c23c4fb06c2e2c1349275d415efe17a51db24cd4883da0067300ddf',data='Send3.5moreBTCtowangwei',timeStamp=1519724875,nonce=369110},validate=trueBlock{hash='0000024b2c23c4fb06c2e2c1349275d415efe17a51db24cd4883da0067300ddf',prevBlockHash='00000b14fefb51ba2a7428549d469bcf3efae338315e7289d3e6dc4caf589d79',data='Send2.5moreBTCtowangwei',timeStamp=1519724872,nonce=896348},validate=trueBlock{hash='00000b14fefb51ba2a7428549d469bcf3efae338315e7289d3e6dc4caf589d79',prevBlockHash='0000099ced1b02f40c750c5468bb8c4fd800ec9f46fea5d8b033e5d054f0f703',data='Send1.0BTCtowangwei',timeStamp=1519724869,nonce=673955},validate=trueBlock{hash='0000099ced1b02f40c750c5468bb8c4fd800ec9f46fea5d8b033e5d054f0f703',prevBlockHash='',data='GenesisBlock',timeStamp=1519724866,nonce=840247},validate=true

命令行界面

CLI 部分的内容,这里不做详细介绍,具体可以去查看文末的Github源码链接。大致步骤如下:

配置

添加pom.xml配置

<project>...<dependency><groupId>commons-cli</groupId><artifactId>commons-cli</artifactId><version>1.4</version></dependency>...<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.0</version><configuration><archive><manifest><addClasspath>true</addClasspath><classpathPrefix>lib/</classpathPrefix><mainClass>one.wangwei.blockchain.cli.Main</mainClass></manifest></archive><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs></configuration><executions><execution><id>make-assembly</id><!--thisisusedforinheritancemerges--><phase>package</phase><!--指定在打包节点执行jar包合并操作--><goals><goal>single</goal></goals></execution></executions></plugin>...</project>

项目工程打包

$mvnclean&&mvnpackage

执行命令

#打印帮助信息$java-jarblockchain-java-jar-with-dependencies.jar-h#添加区块$java-jarblockchain-java-jar-with-dependencies.jar-add"Send1.5BTCtowangwei"$java-jarblockchain-java-jar-with-dependencies.jar-add"Send2.5BTCtowangwei"$java-jarblockchain-java-jar-with-dependencies.jar-add"Send3.5BTCtowangwei"#打印区块链$java-jarblockchain-java-jar-with-dependencies.jar-print

感谢各位的阅读,以上就是“Java持久化和命令行怎么用”的内容了,经过本文的学习后,相信大家对Java持久化和命令行怎么用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

发布于 2022-01-06 23:22:18
收藏
分享
海报
0 条评论
44
上一篇:如何进行AMS1117-3.3V电源模块的基本使用 下一篇:Java语言怎么实现工作量证明机制
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码