用 Java 做第一条教学链,目标不是复刻比特币,而是亲手实现三件事:把数据打成块、用 SHA-256 把块串起来、用简单的工作量证明让“改历史”变慢。整份程序可以只有三个类,在 JDK 17 和任意 IDE 里就能跑。跑通之后,你会对区块头、前一块哈希、nonce 和校验函数有具体感觉,再去看真实节点源码会轻松很多。
下面的例子是单机内存链,没有点对点广播,也没有钱包私钥。它适合当实验台:先看结构,再谈网络和共识。
准备环境和项目骨架
安装 JDK 17 或更高版本,用 IntelliJ IDEA、VS Code 或 Eclipse 新建普通 Java 项目即可,不必先上 Spring。建议包名例如 com.example.miniChain,三个类:Block、Chain、Main。哈希用标准库 MessageDigest,不额外引加密包。
新建类时先写哈希工具,后面每个块都要调用它。把“索引 + 时间戳 + 数据 + 前哈希 + nonce”拼成一个字符串再做 SHA-256,输出小写十六进制。拼接顺序一旦定下就不要改,否则新旧块对不上。
public final class HashUtil {
public static String sha256(String input) {
try {
var md = java.security.MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(input.getBytes(java.nio.charset.StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) sb.append(String.format("%02x", b));
return sb.toString();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}

先写区块:数据、前哈希和当前哈希
一个教学块至少包含:块高 index、时间戳、业务数据 data、前一块哈希 previousHash、随机数 nonce、本块哈希 hash。构造时先填字段,再调用 calculateHash() 得到当前哈希。创世块的 previousHash 用 64 个 0 占位。
public class Block {
private final int index;
private final long timestamp;
private final String data;
private final String previousHash;
private int nonce;
private String hash;
public Block(int index, String data, String previousHash) {
this.index = index;
this.timestamp = System.currentTimeMillis();
this.data = data;
this.previousHash = previousHash;
this.nonce = 0;
this.hash = calculateHash();
}
public String calculateHash() {
return HashUtil.sha256(index + timestamp + data + previousHash + nonce);
}
public void mine(int difficulty) {
String prefix = "0".repeat(difficulty);
while (!hash.startsWith(prefix)) {
nonce++;
hash = calculateHash();
}
}
public String getHash() { return hash; }
public String getPreviousHash() { return previousHash; }
public String getData() { return data; }
public int getIndex() { return index; }
}
mine 就是最简工作量证明:不断加 nonce,直到哈希前缀出现指定个数的 0。难度 3 在普通笔记本上通常一眨眼;难度 5 开始能感觉到循环。教学时用 3 或 4 足够,不必追主网级别的难度。
再写链条:创世块、出块和完整性检查
链用 List<Block> 保存。构造函数里立刻放入创世块并挖一次,保证第一块也满足难度。添加新块时,把链尾哈希当作 previousHash,挖完再追加。校验函数从第二块走到末尾,同时检查两件事:本块哈希是否重算一致,以及 previousHash 是否等于前一块的 hash。
public class Chain {
private final java.util.List<Block> blocks = new java.util.ArrayList<>();
private final int difficulty;
public Chain(int difficulty) {
this.difficulty = difficulty;
Block genesis = new Block(0, "genesis", "0".repeat(64));
genesis.mine(difficulty);
blocks.add(genesis);
}
public void addBlock(String data) {
Block prev = blocks.get(blocks.size() - 1);
Block next = new Block(prev.getIndex() + 1, data, prev.getHash());
next.mine(difficulty);
blocks.add(next);
}
public boolean isValid() {
for (int i = 1; i < blocks.size(); i++) {
Block cur = blocks.get(i);
Block prev = blocks.get(i - 1);
if (!cur.getHash().equals(cur.calculateHash())) return false;
if (!cur.getPreviousHash().equals(prev.getHash())) return false;
if (!cur.getHash().startsWith("0".repeat(difficulty))) return false;
}
return true;
}
public void print() {
for (Block b : blocks) {
System.out.printf("#%d %s%n prev=%s%n hash=%s%n data=%s%n",
b.getIndex(), "", b.getPreviousHash(), b.getHash(), b.getData());
}
}
}
校验失败只有两种常见原因:有人改了某块的 data 却没重挖后面所有块,或者你改了哈希拼接公式导致新旧块各算各的。打印时把 prev 和 hash 都打出来,肉眼就能看到后一块的 prev 正是前一块的 hash。

用主类把链跑起来
public class Main {
public static void main(String[] args) {
Chain chain = new Chain(4);
chain.addBlock("Alice pays Bob 5");
chain.addBlock("Bob pays Carol 2");
chain.print();
System.out.println("valid = " + chain.isValid());
}
}
运行后控制台会出现三块:创世块、两笔转账说明。每块哈希前面应有四个 0。把 isValid() 打成 true,说明链接和难度都成立。这就是最小可用的“区块链应用”:一个能追加、能自检的账本对象。
想观察防篡改,可临时给 Block 加一个包内可见的 setter,在 Main 里改第二块数据后再调用 isValid()。不重挖的话结果一定是 false,因为当前哈希与重算值不一致。若只改数据并重算当前哈希、不改后一块的 previousHash,后一块仍然断裂。这和教材里“改中间必须重做后面所有工作量”是同一件事,只是规模缩到三条记录。
把“应用”做像一点:交易对象和简单 API
教学链的 data 现在是字符串。下一步可改成交易对象:付款人、收款人、金额,再用 JSON 或自己的 toString() 参与哈希。注意:没有私钥签名之前,任何人都能构造“Alice pays Bob”。签名要引入密钥对,那是第二个实验,不要和第一条链绑在一起做。
如果想变成可访问的应用,用 JDK 自带的 com.sun.net.httpserver.HttpServer 或 Spring Boot 暴露两个接口即可:GET /chain 返回块列表,POST /mine 接收一段数据并出块。单机演示足够。真要多节点,需要gossip、选择主链、处理同时出块的分叉,那已经超出“第一个应用”的范围。
持久化也很直接:把每个 Block 写成一行 JSON 追加到文件,启动时读回并立刻 isValid()。文件被手工改过,程序应拒绝启动。教学价值在于:链的权威在校验函数,不在文件看起来完好。
写完后对照真实链,避免三个误区
第一,SHA-256 教学链没有默克尔树,交易一多,改一笔就要重哈希整块字符串。主网把交易树的根放进块头,验证单笔不必重放全部正文。第二,List<Block> 在一台 JVM 里,没有拜占庭对手,isValid() 为真只说明本地数据自洽。第三,难度前缀零是示意,真实难度是“哈希必须小于目标值”,用数值比较而不是只看字符串开头。
Java 生态里若要接现成网络,可研究 BitcoinJ、Web3j(连以太坊兼容链)或 Hyperledger Fabric 的 Java SDK。那些是客户端,默认你已经理解块和哈希。自己写过 100 行迷你链,再调 SDK 的 sendTransaction,才知道网络另一头的节点在校验什么。
第一条 Java 区块链应用的完成标准可以定得很具体:能出创世块、能追加两块、打印哈希链条、篡改后校验失败、提高难度时出块变慢。满足这五条,结构就已经在你的代码里,而不只在名词表里。之后加签名、加节点、加合约,都是在这块骨架上长功能。
本文链接地址:https://www.wwsww.cn/qkl/41479.html
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。



