您好,登錄后才能下訂單哦!
這篇文章主要講解了“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,代碼如下:
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來做一個映射。
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<String, byte[]> 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
接口的代碼邏輯,改為如下邏輯:
代碼如下:
/** * <p> 創建區塊鏈 </p> * * @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值:
/** * <p> 添加區塊 </p> * * @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)); } /** * <p> 添加區塊 </p> * * @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配置
<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> <!-- this is used for inheritance merges --> <phase>package</phase> <!-- 指定在打包節點執行jar包合并操作 --> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> ... </project>
$ 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持久化和命令行怎么用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。