精华内容
下载资源
问答
  • C/C++/Java/C#的基础类型模型定义

    千次阅读 2012-11-30 11:02:26
    C/C++仅仅定义基本数据类型的关系(字长:CHAR DATETYPE LP32 ILP32 LP64 ILP64 LLP64 JAVA C# CHAR 8 8 8 8 8 16 16 ...

    C/C++仅仅定义基本数据类型的关系(字长:CHAR<=SHORT<=INT<=LONG),并没有严格定义它们的字长,根据编译器的不同实现,它们的字长如下表所示:

    DATETYPE

    LP32

    ILP32

    LP64

    ILP64

    LLP64

    JAVA

    C#

    CHAR

    8

    8

    8

    8

    8

    16

    16

    BYTE

    N/A

    N/A

    N/A

    N/A

    N/A

    8

    8

    SHORT

    16

    16

    16

    16

    16

    16

    16

    _INT32

    N/A

    32

    N/A

    N/A

    N/A

    N/A

    N/A

    INT

    16

    32

    32

    64

    32

    32

    32

    LONG

    32

    32

    64

    64

    32

    64

    64

    LONG LONG

    64

    64

    64

    64

    64

    N/A

    N/A

    POINTER

    32

    32

    64

    64

    64

    N/A

    N/A

    FLOAT

    32

    32

    32

    32

    32

    32

    32

    DOUBLE

    64

    64

    64

    64

    64

    64

    64

    BOOL

    T/F

    T/F

    T/F

    T/F

    T/F

    T/F

    T/F

             上表中,LP64、ILP64、LLP64是64位平台上字长的数据模型,ILP32和LP32是32位平台上字长的数据模型。

    •  LP64指的是LONG/POINTER字长为64位;
    • ILP64指的是INT/LONG/POINT字长为64位;
    • LLP64指的是LONGLONG/POINTER字长为64位;
    • ILP32指的是INT/LONG/POINTER字长为32位;
    • LP32指的是LONG/POINT字长是32位的,INT字长为16位。

    注1:32位Windows采用的是ILP32数据模型,64位Windows采用LLP64数据模型。

    注2:32位的Linux/Unix使用ILP32数据模型,64位Linux/Unix使用LP64数据模型。

    注3:为了增加代码的移植性,打印无符号整形数,不管申明时是如何定义的,统一使用 %lu。

    注4:为了保证平台的通用性,代码中尽量不要使用long数据库型。

    注5:使用INT时也可以使用intptr_t来保证平台的通用性,它在不同的平台上编译时长度不同,但都是标准的平台长度,比如:64位机器上长度为8字节,32位机器上长度为4字节。

    注6:编写代码时要尽量使用sizeof来计算数据类型的大小。      

    注7:ssize_t和size_t分别是signsize_t和unsigned signed size of computer word size。它们也是表示计算机的字长,在32位机器上是int型,在64位机器上long型,从某种意义上来说它们等同于intptr_t和 uintptr_t。

     

    WINDOWS下数据类型,定义如下:

    类型

    定义

    类型

    定义

    DWORD32

    32位无符号整数

    DWORD64

    64位无符号整数

    INT32

    32位有符号整数

    UINT32

    32位无符号整数

    INT64

    64位有符号整数

    UINT64

    64位无符号整数

    LONG32

    32位有符号整数

    ULONG32

    32位无符号整数

    LONG64

    64位有符号整数

    ULONG64

    64位无符号整数

    DWORD_PTR

    指针精度无符号长整型

     

     

    HALF_PTR

    指针大小的一半,有符号

    UHALF_PTR

    指针大小的一半,无符号

    INT_PTR

    指针进度有符号整数

    UINT_PTR

    指针进度无符号整数

    LONG_PTR

    指针进度的有符号长整型

    ULONG_PTR

    指针进度的无符号长整型

    SIZE_T

    指针可用的最大字节数

    SSZIE_T

    有符号SIZE_T

    LPARAM

    与LONG_PTR为同义词

    WPARM

    与UINT_PTR为同义词

    POINTER_32

    32位指针类型

    POINTER_64

    64位指针类型

    展开全文
  • 文章目录模型的保存方法第一步:实例化第二步:模型的保存案例训练了一半模型继续训练第一步:加载之前的模型案例 模型的保存方法 第一步:实例化 在会话的前面 # 添加一个saver保存模型!!!!!!!!!!!!...

    模型的保存方法

    第一步:实例化

    在会话的前面

    # 添加一个saver保存模型!!!!!!!!!!!!!!
    saver = tf.train.Saver()#实例化
    

    第二步:模型的保存

    saver.save(sess, "./summary/ckpt/linear/linear_regression.ckpt")
    

    案例

    import tensorflow as tf
    
    with tf.variable_scope("lr_model"):
        def linear_regression():#定义名字
            # 1)准备好数据集:y = 0.8x + 0.7 100个样本
            # 特征值X, 目标值y_true
            with tf.variable_scope("original_data"):#定义名字
                X = tf.random_normal(shape=(100, 1), mean=2, stddev=2, name="original_data_x")
                # y_true [100, 1]
                # 矩阵运算 X(100, 1)* (1, 1)= y_true(100, 1)
                y_true = tf.matmul(X, [[0.8]], name="original_matmul") + 0.7
    
            # 2)建立线性模型:
            # y = W·X + b,目标:求出权重W和偏置b
            # 3)随机初始化W1和b1
    
            with tf.variable_scope("linear_model"):#定义名字
                weights = tf.Variable(initial_value=tf.random_normal(shape=(1, 1)), name="weights")
                bias = tf.Variable(initial_value=tf.random_normal(shape=(1, 1)), name="bias")
                y_predict = tf.matmul(X, weights, name="model_matmul") + bias
    
            # 4)确定损失函数(预测值与真实值之间的误差)-均方误差
            with tf.variable_scope("loss"):#定义名字
                error = tf.reduce_mean(tf.square(y_predict - y_true), name="error_op")
    
            # 5)梯度下降优化损失:需要指定学习率(超参数)
            # W2 = W1 - 学习率*(方向)
            # b2 = b1 - 学习率*(方向)
            with tf.variable_scope("gd_optimizer"):#定义名字
                optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01, name="optimizer").minimize(error)
    
            # 2)收集变量
            tf.summary.scalar("error", error)##生成准确率标量图
            tf.summary.histogram("weights", weights)#用来显示直方图信息
            tf.summary.histogram("bias", bias)#用来显示直方图信息
    
            # 3)合并变量
            merge = tf.summary.merge_all()
    
            # 添加一个saver保存模型!!!!!!!!!!!!!!
            saver = tf.train.Saver()#实例化
    
            # 初始化变量
            init = tf.global_variables_initializer()
            # 开启会话进行训练
            with tf.Session() as sess:
                # 运行初始化变量Op
                sess.run(init)
                print("随机初始化的权重为%f, 偏置为%f" % (weights.eval(), bias.eval()))
                # 1)创建事件文件【重要】
                file_writer = tf.summary.FileWriter(logdir="./summary", graph=sess.graph)
                # 训练模型
                for i in range(3000):
                    sess.run(optimizer)
                    print("第%d步的误差为%f,权重为%f, 偏置为%f" % (i, error.eval(), weights.eval(), bias.eval()))
                    # 4)运行合并变量op
                    summary = sess.run(merge)
                    file_writer.add_summary(summary, i)#i保留每一次的数量
                    # 训练过程比较长,保存一下,只会保存最近的五个!!!!!!!!
                    saver.save(sess, "./summary/ckpt/linear/linear_regression.ckpt")
    
    
    
    
    if __name__ == '__main__':
        linear_regression()
    
    

    在这里插入图片描述

    训练了一半的模型继续训练

    情景:我们之前已经训练了3000步了,保存了,但是结果不是很满意,想要继续在3000的基础上训练

    第一步:加载之前的模型

    在训练之前

    #加载历史模型
    saver.restore(sess, "./summary/ckpt/linear/linear_regression.ckpt")
    

    案例

    import tensorflow as tf
    
    with tf.variable_scope("lr_model"):
        def linear_regression():#定义名字
            # 1)准备好数据集:y = 0.8x + 0.7 100个样本
            # 特征值X, 目标值y_true
            with tf.variable_scope("original_data"):#定义名字
                X = tf.random_normal(shape=(100, 1), mean=2, stddev=2, name="original_data_x")
                # y_true [100, 1]
                # 矩阵运算 X(100, 1)* (1, 1)= y_true(100, 1)
                y_true = tf.matmul(X, [[0.8]], name="original_matmul") + 0.7
    
            # 2)建立线性模型:
            # y = W·X + b,目标:求出权重W和偏置b
            # 3)随机初始化W1和b1
    
            with tf.variable_scope("linear_model"):#定义名字
                weights = tf.Variable(initial_value=tf.random_normal(shape=(1, 1)), name="weights")
                bias = tf.Variable(initial_value=tf.random_normal(shape=(1, 1)), name="bias")
                y_predict = tf.matmul(X, weights, name="model_matmul") + bias
    
            # 4)确定损失函数(预测值与真实值之间的误差)-均方误差
            with tf.variable_scope("loss"):#定义名字
                error = tf.reduce_mean(tf.square(y_predict - y_true), name="error_op")
    
            # 5)梯度下降优化损失:需要指定学习率(超参数)
            # W2 = W1 - 学习率*(方向)
            # b2 = b1 - 学习率*(方向)
            with tf.variable_scope("gd_optimizer"):#定义名字
                optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01, name="optimizer").minimize(error)
    
            # 2)收集变量
            tf.summary.scalar("error", error)##生成准确率标量图
            tf.summary.histogram("weights", weights)#用来显示直方图信息
            tf.summary.histogram("bias", bias)#用来显示直方图信息
    
            # 3)合并变量
            merge = tf.summary.merge_all()
    
            # 添加一个saver保存模型!!!!!!!!!!!!!!
            saver = tf.train.Saver()#实例化
    
            # 初始化变量
            init = tf.global_variables_initializer()
            # 开启会话进行训练
            with tf.Session() as sess:
                # 运行初始化变量Op
                sess.run(init)
                print("随机初始化的权重为%f, 偏置为%f" % (weights.eval(), bias.eval()))
                # 第2999步的误差为0  .000000,权重为0 .799999, 偏置为0 .700002
                # 1)创建事件文件【重要】
                file_writer = tf.summary.FileWriter(logdir="./summary", graph=sess.graph)
    
                #加载历史模型
                saver.restore(sess, "./summary/ckpt/linear/linear_regression.ckpt")
    
                # 训练模型
                for i in range(1000):
                    sess.run(optimizer)
                    print("第%d步的误差为%f,权重为%f, 偏置为%f" % (i, error.eval(), weights.eval(), bias.eval()))
                    # 4)运行合并变量op
                    summary = sess.run(merge)
                    file_writer.add_summary(summary, i)#i保留每一次的数量
                    # 训练过程比较长,保存一下,只会保存最近的五个!!!!!!!!
                    saver.save(sess, "./summary/ckpt/linear/linear_regression.ckpt2")
    
    
    
    
    if __name__ == '__main__':
        linear_regression()
    
    

    在这里插入图片描述

    展开全文
  • 区块链定义: 区块链是以比特币为代表的数字加密货币体系的核心支撑技术。区块链技术的核心优势是去中心化,能够通过运用数据加密、时间戳、分布式共识和经济激励等手段,在节点无需互相信任的分布式系统中实现基于...
    • 阅读自区块链技术发展现状与展望(袁勇 王飞跃)的报告并节选其中内容

    区块链简介

    1. 区块链定义:
    • 区块链是以比特币为代表的数字加密货币体系的核心支撑技术。区块链技术的核心优势是去中心化,能够通过运用数据加密、时间戳、分布式共识和经济激励等手段,在节点无需互相信任的分布式系统中实现基于去中心化信用的点对点交易、协调与协作,从而为解决中心化机构普遍存在的高成本、低效率和数据存储不安全等问题提供了解决方案。
    1. 区块链特点:
    • 去中心化、时序数据、集体维护、可编程和安全可信等特点。
    • 去中心化:区块链数据的验证、记账、存储、维护和传输等过程均是基于分布式系统结构, 采用纯数学方法而不是中心机构来建立分布式节点间的信任关系, 从而形成去中心化的可信任的分布式系统。
    • 时序数据:: 区块链采用带有时间戳的链式区块结构存储数据, 从而为数据增加了时间维度, 具有极强的可验证性和可追溯性。
    • 集体维护:区块链系统采用特定的经济激励机制来保证分布式系统中所有节点均可参与数据区块的验证过程 (如比特币的 “挖矿” 过程), 并通过共识算法来选择特定的节点将新区块添加到区块链。
    • 可编程: 区块链技术可提供灵活的脚本代码系统, 支持用户创建高级的智能合约、货币或其他去中心化应用.。例如, 以太坊 (Ethereum) 平台即提供了图灵完备的脚本语言以供用户来构建任何可以精确定义的智能合约或交易类型。
    • 安全可信:区块链技术采用非对称密码学原理对数据进行加密, 同时借助分布式系统各节点的工作量证明等共识算法形成的强大算力来抵御外部攻击、保证区块链数据不可篡改和不可伪造, 因而具有较高的安全性。
    1. 区块链技术的未来发展脉络:
    • 区块链技术将会经历以可编程数字加密货币体系为主要特征的区块链 1.0 模式、以可编程金融系统为主要特征的区块链 2.0 模式和以可编程社会为主要特征的区块链 3.0 模式。
    1. 比特币现状:
    • 目前, 一般认为区块链技术正处于2.0 模式的初期, 股权众筹和 P2P 借贷等各类基于区块链技术的互联网金融应用相继涌现. 然而, 上述模式实际上是平行而非演进式发展的, 区块链 1.0模式的数字加密货币体系仍然远未成熟, 距离其全球货币一体化的愿景实际上更远、更困难. 目前, 区块链领域已经呈现出明显的技术和产业创新驱动的发展态势, 相关学术研究严重滞后、亟待跟进。(截止到 2016 年 2 月, 以万方数据知识服务平台为中文数据源、以 Web of Science 和 EI Village 为英文数据源的文献检索显示, 目前篇名包含关键词 “区块链/blockchain” 的仅有 2 篇中文和 9 篇英文文献. )

    比特币与区块链概述

    1. 比特币本质:
    • 由分布式网络系统生成的数字货币, 其发行过程不依赖特定的中心化机构, 而是依赖于分布式网络节点共同参与一种称为工作量证明(Proof of work, PoW) 的共识过程以完成比特币交易的验证与记录。

    • PoW 共识过程 (俗称挖矿, 每个节点称为矿工) 通常是各节点贡献自己的计算资源来竞争解决一个难度可动态调整的数学问题, 成功解决该数学问题的矿工将获得区块的记账权, 并将当前时间段的所有比特币交易打包记入一个新的区块、按照时间顺序链接到比特币主链上. 比特币系统同时会发行一定数量的比特币以奖励该矿工, 并激励其他矿工继续贡献算力。

    • 比特币的流通过程依靠密码学方法保障安全. 每一次比特币交易都会经过特殊算法处理和全体矿工验证后记入区块链, 同时可以附带具有一定灵活性的脚本代码 (智能合约)以实现可编程的自动化货币流通。

    1. 比特币和区块链具有的五个关键要素
    • 即公共的区块链账本、分布式的点对点网络系统、去中心化的共识算法、适度的经济激励机制以及可编程的脚本代码
    1. 数字加密货币面临的两个重要问题
    • 双重支付问题和拜占庭问题
    • 双重支付问题:双重支付问题又称为 “双花”, 即利用货币的数字特性两次或多次使用“同一笔钱” 完成支付. 传统金融和货币体系中, 现金因是物理实体, 能够自然地避免双重支付;其他数字形式的货币则需要可信的第三方中心机构(如银行) 来保证。 区块链技术的贡献是在没有第三方机构的情况下, 通过分布式节点的验证和共识机制解决了去中心化系统的双重支付问题, 在信息传输的过程同时完成了价值转移。
    • 拜占庭问题:拜占庭将军问题是分布式系统交互过程普遍面临的难题, 即在缺少可信任的中央节点的情况下, 分布式节点如何达成共识和建立互信。区块链通过数字加密技术和分布式共识算法, 实现了在无需信任单个节点的情况下构建一个去中心化的可信任系统. 与传统中心机构(如中央银行) 的信用背书机制不同的是, 比特币区块链形成的是软件定义的信用, 这标志着中心化的国家信用向去中心化的算法信用的根本性变革.
    1. 比特币的流通过程
    • 比特币凭借其先发优势, 目前已经形成体系完备的涵盖发行、流通和金融衍生市场的生态圈与产业链 (如图 1 所示), 这也是其长期占据绝大多数数字加密货币市场份额的主要原因. 比特币的开源特性吸引了大量开发者持续性地贡献其创新技术、方法和机制; 比特币各网络节点 (矿工) 提供算力以保证比特币的稳定共识和安全性, 其算力大多来自于设备商销售的专门用于 PoW 共识算法的专业设备(矿机). 比特币网络为每个新发现的区块发行一定数量的比特币以奖励矿工, 部分矿工可能会相互合作建立收益共享的矿池, 以便汇集算力来提高获得比特币的概率. 比特币经发行进入流通环节后, 持币人可以通过特定的软件平台 (如比特币钱包) 向商家支付比特币来购买商品或服务, 这体现了比特币的货币属性; 同时由于比特币价格的涨跌机制使其完全具备金融衍生品的所有属性, 因此出现了比特币交易平台以方便持币人投资或者投机比特币. 在流通环节和金融市场中, 每一笔比特币交易都会由比特币网络的全体矿工验证并记入区块链。

    这里写图片描述

    区块链的基础模型和关键技术

    1.区块链基础架构模型:

    • 区块链技术的基础架构模型如图 2 所示. 一般说来, 区块链系统由数据层、网络层、共识层、激励层、合约层和应用层组成. 其中, 数据层封装了底层数据区块以及相关的数据加密和时间戳等技术; 网络层则包括分布式组网机制、数据传播机制和数据验证机制等; 共识层主要封装网络节点的各类共识算法; 激励层将经济因素集成到区块链技术体系中来, 主要包括经济激励的发行机制和分配机制等; 合约层主要封装各类脚本、算法和智能合约, 是区块链可编程特性的基础; 应用层则封装了区块链的各种应用场景和案例. 该模型中, 基于时间戳的链式区块结构、分布式节点的共识机制、基于共识算力的经济激励和灵活可编程的智能合约是区块链技术最具代表性的创新点。

    这里写图片描述

    这里写图片描述

    2.数据层:

    • 数据区块:如图 3 所示, 每个数据区块一般包含区块头 (Header) 和区块体 (Body) 两部分. 区块头封装了当前版本号 (Version)、前一区块地址(Prev-block)、当前区块的目标哈希值(Bits)、当前区块PoW共识过程的解随机数 (Nonce)、Merkle根(Merkle-root)以及时间戳(Timestamp) 等信息. 比特币网络可以动态调整 PoW 共识过程的难度值, 最先找到正确的解随机数 Nonce 并经过全体矿工验证的矿工将会获得当前区块的记账权. 区块体则包括当前区块的交易数量以及经过验证的、区块创建过程中生成的所有交易记录. 这些记录通过 Merkle 树的哈希过程生成唯一的 Merkle 根并记入区块头。

    • 链式结构:取得记账权的矿工将当前区块链接到前一区块, 形成最新的区块主链. 各个区块依次环环相接, 形成从创世区块到当前区块的一条最长主链, 从而记录了区块链数据的完整历史, 能够提供区块链数据的溯源和定位功能, 任意数据都可以通过此链式结构顺藤摸瓜、追本溯源. 需要说明的是, 如果短时间内有两个矿工同时 “挖出” 两个新的区块加以链接的话, 区块主链可能会出现暂时的 “分叉”现象, 其解决方法是约定矿工总是选择延长累计工作量证明最大的区块链. 因此, 当主链分叉后, 后续区块的矿工将通过计算和比较, 将其区块链接到当前累计工作量证明最大化的备选链上, 形成更长的新主链, 从而解决分叉问题。

    • 时间戳:区块链技术要求获得记账权的节点必须在当前数据区块头中加盖时间戳, 表明区块数据的写入时间. 因此, 主链上各区块是按照时间顺序依次排列的. 时间戳技术本身并不复杂, 但其在区块链技术中的应用是具有重要意义的创新. 时间戳可以作为区块数据的存在性证明 (Proof of existence),有助于形成不可篡改和不可伪造的区块链数据库,从而为区块链应用于公证、知识产权注册等时间敏感的领域奠定了基础. 更为重要的是, 时间戳为未来基于区块链的互联网和大数据增加了时间维度, 使得通过区块数据和时间戳来重现历史成为可能。

    • 哈希函数:区块链通常并不直接保存原始数据或交易记录, 而是保存其哈希函数值, 即将原始数据编码为特定长度的由数字和字母组成的字符串后记入区块链. 哈希函数 (也称散列函数) 具有诸多优良特点, 因而特别适合用于存储区块链数据. 例如, 通过哈希输出几乎不能反推输入值 (单向性), 不同长度输入的哈希过程消耗大约相同的时间 (定时性) 且产生固定长度的输出 (定长性), 即使输入仅相差一个字节也会产生显著不同的输出值 (随机性) 等. 比特币区块链通常采用双 SHA256 哈希函数, 即将任意长度的原始数据经过两次 SHA256 哈希运算后转换为长度为 256 位 (32 字节) 的二进制数字来统一存储和识别. 除上述特点外, SHA256 算法还具有巨大的散列空间 (2256) 和抗碰撞 (避免不同输入值产生相同哈希值) 等特性, 可满足比特币的任何相关标记需要而不会出现冲突。

    • Merkle树:Merkle 树是区块链的重要数据结构, 其作用是快速归纳和校验区块数据的存在性和完整性. Merkle 树通常包含区块体的底层 (交易) 数据库, 区块头的根哈希值 (即Merkle 根) 以及所有沿底层区块数据到根哈希的分支. Merkle 树运算过程一般是将区块体的数据进行分组哈希, 并将生成的新哈希值插入到 Merkle 树中, 如此递归直到只剩最后一个根哈希值并记为区块头的 Merkle 根. 最常见的 Merkle 树是比特币采用的二叉 Merkle 树, 其每个哈希节点总是包含两个相邻的数据块或其哈希值, 其他变种则包括以太坊的 Merkle patricia tree 等. Merkle 树有诸多优点: 首先是极大地提高了区块链的运行效率和可扩展性, 使得区块头只需包含根哈希值而不必封装所有底层数据, 这使得哈希运算可以高效地运行在智能手机甚至物联网设备上; 其次是 Merkle 树可支持 “简化支付验证” 协议, 即在不运行完整区块链网络节点的情况下, 也能够对 (交易) 数据进行检验.例如, 为验证图 3 中交易 6, 一个没有下载完整区块链数据的客户端可以通过向其他节点索要包括从交易 6 哈希值沿 Merkle 树上溯至区块头根哈希处的哈希序列 (即哈希节点 6, 5, 56, 78, 5 678, 1 234) 来快速确认交易的存在性和正确性. 一般说来, 在 N个交易组成的区块体中确认任一交易的算法复杂度仅为 log2N. 这将极大地降低区块链运行所需的带宽和验证时间, 并使得仅保存部分相关区块链数据的轻量级客户端成为可能。

    • 非对称加密:非对称加密是为满足安全性需求和所有权验证需求而集成到区块链中的加密技术,常见算法包括 RSA、Elgamal、Rabin、D-H、ECC(即椭圆曲线加密算法) 等. 非对称加密通常在加密和解密过程中使用两个非对称的密码, 分别称为公钥和私钥. 非对称密钥对具有两个特点, 首先是用其中一个密钥 (公钥或私钥) 加密信息后, 只有另一个对应的密钥才能解开; 其次是公钥可向其他人公开、私钥则保密, 其他人无法通过该公钥推算出相应的私钥. 非对称加密技术在区块链的应用场景主要包括信息加密、数字签名和登录认证等, 其中信息加密场景主要是由信息发送者 (记为 A) 使用接受者 (记为 B) 的公钥对信息加密后再发送给 B, B 利用自己的私钥对信息解密. 比特币交易的加密即属于此场景; 数字签名场景则是由发送者 A 采用自己的私钥加密信息后发送给 B, B 使用 A 的公钥对信息解密、从而可确保信息是由 A 发送的; 登录认证场景则是由客户端使用私钥加密登录信息后发送给服务器, 后者接收后采用该客户端的公钥解密并认证登录信息。

    3.网络层:

    • 组网方式:区块链系统的节点一般具有分布式、自治性、开放可自由进出等特性, 因而一般采用对等式网络 (Peer-to-peer network, P2P 网络) 来组织散布全球的参与数据验证和记账的节点. P2P 网络中的每个节点均地位对等且以扁平式拓扑结构相互连通和交互, 不存在任何中心化的特殊节点和层级结构, 每个节点均会承担网络路由、验证区块数据、传播区块数据、发现新节点等功能. 按照节点存储数据量的不同, 可以分为全节点和轻量级节点. 前者保存有从创世区块到当前最新区块为止的完整区块链数据, 并通过实时参与区块数据的校验和记账来动态更新主链. 全节点的优势在于不依赖任何其他节点而能够独立地实现任意区块数据的校验、查询和更新, 劣势则是维护全节点的空间成本较高。轻量级节点则仅保存一部分区块链数据,并通过简易支付验证方式向其相邻节点请求所需的数据来完成数据校验。

    • 数据传播协议:任一区块数据生成后, 将由生成该数据的节点广播到全网其他所有的节点来加以验证. 现有的区块链系统一般根据实际应用需求设计比特币传播协议的变种, 例如以太坊区块链集成了所谓的 “幽灵协议” 以解决因区块数据确认速度快而导致的高区块作废率和随之而来的安全性风险. 根据中本聪的设计, 比特币系统的交易数据传播协议包括如下步骤而导致的高区块作废率和随之而来的安全性风险. 根据中本聪的设计, 比特币系统的交易数据传播协议包括如下步骤:

        1. 比特币交易节点将新生成的交易数据向全网所有节点进行广播;
        1. 每个节点都将收集到的交易数据存储到一个区块中;
        1. 每个节点基于自身算力在区块中找到一个具有足够难度的工作量证明;
        1. 当节点找到区块的工作量证明后, 就向全网所有节点广播此区块;
        1. 仅当包含在区块中的所有交易都是有效的且之前未存在过的, 其他节点才认同该区块的有效性;
        1. 其他节点接受该数据区块, 并在该区块的末尾制造新的区块以延长该链条, 而将被接受区块的随机哈希值视为先于新区块的随机哈希值。
    • 数据验证机制:P2P 网络中的每个节点都时刻监听比特币网络中广播的数据与新区块. 节点接收到邻近节点发来的数据后, 将首先验证该数据的有效性. 如果数据有效, 则按照接收顺序为新数据建立存储池以暂存尚未记入区块的有效数据, 同时继续向邻近节点转发; 如果数据无效, 则立即废弃该数据, 从而保证无效数据不会在区块链网络继续传播. 以比特币为例, 比特币的矿工节点会收集和验证P2P 网络中广播的尚未确认的交易数据, 并对照预定义的标准清单, 从数据结构、语法规范性、输入输出和数字签名等各方面校验交易数据的有效性, 并将有效交易数据整合到当前区块中; 同理, 当某矿工“挖” 到新区块后, 其他矿工节点也会按照预定义标准来校验该区块是否包含足够工作量证明, 时间戳是否有效等; 如确认有效, 其他矿工节点会将该区块链接到主区块链上, 并开始竞争下一个新区块.

    4.共识层

    • 区块链技术的核心优势之一就是能够在决策权高度分散的去中心化系统中使得各节点高效地针对区块数据的有效性达成共识。

    下面将介绍一下PoW、PoS和DPOS三种共识机制

    • **PoW共识:**其核心思想是通过引入分布式节点的算力竞争来保证数据一致性和共识的安全性. 比特币系统中, 各节点 (即矿工) 基于各自的计算机算力相互竞争来共同解决一个求解复杂但验证容易的 SHA256 数学难题 (即挖矿), 最快解决该难题的节点将获得区块记账权和系统自动生成的比特币奖励. 该数学难题可表述为: 根据当前难度值, 通过搜索求解一个合适的随机数 (Nonce) 使得区块各元数据的双 SHA256 哈希值小于或等于目标哈 希值. 比特币系统通过灵活调整随机数搜索的难度值来控制区块的平均生成时间为10分钟左右. 一般说来, PoW共识的随机数搜索过程如下:

      • 步骤 1. 搜集当前时间段的全网未确认交易, 并增加一个用于发行新比特币奖励的 Coinbase 交易,形成当前区块体的交易集合;
      • 步骤 2. 计算区块体交易集合的 Merkle 根记 入区块头, 并填写区块头的其他元数据, 其中随机数 Nonce 置零;
      • 步骤 3. 随机数 Nonce 加 1; 计算当前区块头的双 SHA256 哈希值, 如果小于或等于目标哈希值, 则成功搜索到合适的随机数并获得该区块的记账权; 否则继续步骤3直到任一节点搜索到合适的随机数为止;
      • 步骤 4. 如果一定时间内未成功, 则更新时间戳和未确认交易集合、重新计算 Merkle 根后继续搜索.
    • 符合要求的区块头哈希值通常由多个前导零构成, 目标哈希值越小, 区块头哈希值的前导零越多, 成功找到合适的随机数并“挖”出新区块的难度越大. 据区块链实时监测网站Blockchain.info显示, 截止到2016年2月, 符合要求的区块头哈希值一般有17个前导零, 例如第398346号区块哈希值为“0000000000000000077f754f22f21629a7975cf···”. 按照概率计算, 每 16 次随机数搜索将会有找到一 个含有一个前导零的区块哈希值, 因而比特币目前 17 位前导零哈希值要求 1617 次随机数搜索才能找 到一个合适的随机数并生成一个新的区块. 由此可 见, 比特币区块链系统的安全性和不可篡改性是由 PoW 共识机制的强大算力所保证的, 任何对于区块数据的攻击或篡改都必须重新计算该区块以及其后所有区块的SHA256难题, 并且计算速度必须使得伪造链长度超过主链, 这种攻击难度导致的成本将远超其收益。

    • PoS共识机制: PoS 共识是为解决PoW共识机制的资源浪费和安全性缺陷而提出的替代方案. PoS 共识本质上是采用权益证明来代替PoW中的基于哈希算力的工作量证明, 是由系统中具有最高权益而非最高算力的节点获得区块记账权. 权益体现为节点对特定数量货币的所有权, 称为币龄或币天数 (Coin days). 币龄是特定数量的币与其最后一次交易的时间长度的乘积, 每次交易都将会消耗掉特定数量的币龄. 例如, 某人在一笔交易中收到10个币后并持有10天, 则获得100币龄; 而后其花掉5个币后, 则消耗掉 50 币龄. 显然, 采用PoS共识机制的系统在特定时间点上的币龄总数是有限的, 长期持币者更倾向于拥有更多币龄, 因此币龄可视为其在PoS系统中的权益. 此外, PoW共识过程中各节点挖矿难度相同, 而 PoS 共识过程中的难度与交易输入的币龄成反比, 消耗币龄越多则挖矿难度越低. 节点判断主链的标准也由 PoW 共识的最高累计难度转变为最高消耗币龄, 每个区块的交易都会将其消耗的币龄提交给该区块, 累计消耗币龄最高的区块将被链接到主链. 由此可见, PoS 共识过程仅依靠内部币龄和权益而不需要消耗外部算力和资源, 从根本上解决了PoW共识算力浪费的问题, 并且能够在一定程度上缩短达成共识的时间, 因而比特币之后的许多竞争币均采用PoS共识机制。

    • DPoS 共识机制: DPoS 共识机制的基本思路类似于“董事会决策”, 即系统中每个股东节点可以将其持有的股份权益作为选票授予一个代表, 获得票数最多且愿意成为代表的前101个节点将进入“董事会”, 按照既定的时间表轮流对交易进行打包结算并且签署 (即生产) 一个新区块. 每个区块被签署之前, 必须先验证前一个区块已经被受信任的代表节点所签署. “董事会” 的授权代表节点可以从每笔交易的手续费中获得收入, 同时要成为授权代表节点必须缴纳一定量的保证金, 其金额相当于生产一个区块收入的100倍. 授权代表节点必须对其他股东节点负责, 如果其错过签署相对应的区块, 则股东将会收回选票从而将该节点 “投出” 董事会. 因此, 授权代表节点通常必须保证 99% 以上的在线时间以实现盈利目标. 显然, 与 PoW 共识机制必须信任最高算力节点和 PoS 共识机制必须信任最高权益节点不同的是, DPoS 共识机制中每个节点都能够自主决定其信任的授权节点且由这些节点轮流记账生成新区块, 因而大幅减少了参与验证和记账的节点数量, 可以实现快速共识验证。

    5.激励层:

    • 区块链共识过程通过汇聚大规模共识节点的算力资源来实现共享区块链账本的数据验证和记账工作, 因而其本质上是一种共识节点间的任务众包过程. 去中心化系统中的共识节点本身是自利的, 最大化自身收益是其参与数据验证和记账的根本目标. 因此, 必须设计激励相容的合理众包机制, 使得共识节点最大化自身收益的个体理性行为与保障去中心化区块链系统的安全和有效性的整体目标相吻合. 区块链系统通过设计适度的经济激励机制并与共识过程相集成, 从而汇聚大规模的节点参与并形成了对区块链历史的稳定共识.
    • 以比特币为例, 比特币PoW 共识中的经济激励由新发行比特币奖励和交易流通过程中的手续费两部分组成, 奖励给PoW共识过程中成功搜索到该区块的随机数并记录该区块的节点. 因此, 只有当各节点通过合作共同构建共享和可信的区块链历史记录、并维护比特币系统的有效性, 其获得的比特币奖励和交易手续费才会有价值. 比特币已经形成成熟的挖矿生态圈, 大量配备专业矿机设备的矿工积极参与基于挖矿的PoW共识过程, 其根本目的就是通过获取比特币奖励并转换为相应法币来实现盈利.
    • 发行机制: 比特币系统中每个区块发行比特币的数量是随着时间阶梯性递减的. 创世区块起的每个区块将发行 50 个比特币奖励给该区块的记账者, 此后每隔约4年 (21 万个区块) 每区块发行比特币的数量降低一半, 依此类推, 一直到比特币的数量稳定在上限 2100 万为止。比特币交易过程中会产生手续费, 目前默认手续费是万分之一个比特币, 这部分费用也会记入区块并奖励给记账者. 这两部分费用将会封装在每个区块的第一个交易 (称为 Coinbase 交易) 中. 虽然现在每个区块的总手续费相对于新发行比特币来说规模很小 (通常不会超过 1 个比特币), 但随着未来比特币发行数量的逐步减少甚至停止发行, 手续费将逐渐成为驱动节点共识和记账的主要动力. 同时, 手续费还可以防止大量微额交易对比特币网络发起的 “粉尘” 攻击, 起到保障安 全的作用.
    • 分配机制: 比特币系统中, 大量的小算力节点通常会选择加入矿池, 通过相互合作汇集算力来提高 “挖” 到新区块的概率, 并共享该区块的比特币和手续费奖励. 据Bitcoinmining.com统计, 目前 已经存在13种不同的分配机制. 主流矿池通常采用PPLNS(Pay per last N shares)、PPS(Pay per share)和PROP(PROPortionately)等机制. 矿池将各节点贡献的算力按比例划分成不同的股份 (Share), 其中 PPLNS 机制是指发现区块后, 各合 作节点根据其在最后 N 个股份内贡献的实际股份 比例来分配区块中的比特币; PPS 则直接根据股份 比例为各节点估算和支付一个固定的理论收益, 采用此方式的矿池将会适度收取手续费来弥补其为各节点承担的收益不确定性风险; PROP 机制则根据节点贡献的股份按比例地分配比特币. 矿池的出现是对比特币和区块链去中心化趋势的潜在威胁, 如何设计合理的分配机制引导各节点合理地合作、避免出现因算力过度集中而导致的安全性问题是亟待解决的研究问题.

    6.合约层:

    • 合约层封装区块链系统的各类脚本代码、算法 以及由此生成的更为复杂的智能合约. 如果说数据、 网络和共识三个层次作为区块链底层 “虚拟机” 分别承担数据表示、数据传播和数据验证功能的话, 合约层则是建立在区块链虚拟机之上的商业逻辑和算法, 是实现区块链系统灵活编程和操作数据的基础. 包括比特币在内的数字加密货币大多采用非图灵完备的简单脚本代码来编程控制交易过程, 这也是智能合约的雏形; 随着技术的发展, 目前已经出现以太坊等图灵完备的可实现更为复杂和灵活的智能合约的脚本语言, 使得区块链能够支持宏观金融和社会系统的诸多应用.
    • 比特币采用一种简单的、基于堆栈的、从左向右处理的脚本语言, 而一个脚本本质上是附着在比特币交易上的一组指令的列表. 比特币交易依赖于两类脚本来加以验证, 即锁定脚本和解锁脚本, 二者的不同组合可在比特币交易中衍生出无限数量的控制条件. 其中, 锁定脚本是附着在交易输出值上的 “障碍”, 规定以后花费这笔交易输出的条件; 解锁脚本则是满足被锁定脚本在一个输出上设定的花费条件的脚本, 同时它将允许输出被消费.举例来说, 大多数比特币交易均是采用接受者的公钥加密和私钥解密, 因而其对应的 P2PKH (Pay to public key hash) 标准交易脚本中的锁定脚本即是使用接受者的公钥实现阻止输出功能, 而使用私钥对应的数字签名来加以解锁.
    • 比特币脚本系统可以实现灵活的交易控制. 例如, 通过规定某个时间段 (如一周) 作为解锁条件, 可以实现延时支付; 通过规定接受者和担保人必须共同私钥签名才能支配一笔比特币, 可以实现担保交易; 通过设计一种可根据外部信息源核查某概率事件是否发生的规则并作为解锁脚本附着在一定数量的比特币交易上, 即可实现博彩和预测市场等类型的应用; 通过设定 N 个私钥集合中至少提供 M 个私钥才可解锁, 可实现 M −N 型多重签名, 即 N 个潜在接受者中至少有 M 个同意签名才可实现支付. 多重签名可广泛应用于公司决策、财务监督、中介担保甚至遗产分配等场景.
    • 比特币脚本是智能合约的雏形, 催生了人类历史上第一种可编程的全球性货币. 然而, 比特币脚本系统是非图灵完备的, 其中不存在复杂循环和流控制, 这在损失一定灵活性的同时能够极大地降低复杂性和不确定性, 并能够避免因无限循环等逻辑炸弹而造成拒绝服务等类型的安全性攻击. 为提高脚本系统的灵活性和可扩展性, 研究者已经尝试在比特币协议之上叠加新的协议, 以满足在区块链上构建更为复杂的智能合约的需求. 以太坊已经研发出一套图灵完备的脚本语言, 用户可基于以太坊构建任意复杂和精确定义的智能合约与去中心化应用, 从而为基于区块链构建可编程的金融与社会系统奠 定了基础.

    区块链的应用场景

    区块链主要应用于数字货币、数据存储、数据鉴证、金融交易、资产管理和选举投票共六个场景

    • **数据存储:**区块链的高冗余存储 (每个节点存储一份数据)、去中心化、高安全性和隐私保护等特点使其特别适合存储和保护重要隐私数据, 以避免因中心化机构遭受攻击或权限管理不当而造成的大规模数据丢失或泄露. 与比特币交易数据类似地, 任意数据均可通过哈希运算生成相应的Merkle树并打包记入区块链, 通过系统内共识节点的算力和非对称加密技术来保证安全性. 区块链的多重签名技术可以灵活配置数据访问的权限, 例如必须获得指定 5 个人中 3 个人的私钥授权才可获得访问权限. 目前, 利用区块链来存储个人健康数据 (如电子病历、基因数据等) 是极具前景的应用领域, 此外存储各类重要 电子文件 (视频、图片、文本等) 乃至人类思想和意识等也有一定应用空间.
    • **数据鉴定:**区块链数据带有时间戳、由共识节 点共同验证和记录、不可篡改和伪造, 这些特点使得区块链可广泛应用于各类数据公证和审计场景. 例如, 区块链可以永久地安全存储由政府机构核发的各类许可证、登记表、执照、证明、认证和记录等, 并可在任意时间点方便地证明某项数据的存在性和一定程度上的真实性. 包括德勤在内的多家专业审计公司已经部署区块链技术来帮助其审计师实现低成本和高效地实时审计; Factom 公司则基于区块链设计了一套准确的、可核查的和不可更改的审计公 证流程与方法.
    • **金融交易:**区块链技术与金融市场应用有非常 高的契合度. 区块链可以在去中心化系统中自发地产生信用, 能够建立无中心机构信用背书的金融市场, 从而在很大程度上实现了 “金融脱媒”, 这对第三方支付、资金托管等存在中介机构的商业模式来说是颠覆性的变革; 在互联网金融领域, 区块链特别适合或者已经应用于股权众筹、P2P网络借贷和互联网保险等商业模式; 证券和银行业务也是区块链的重要应用领域, 传统证券交易需要经过中央结算机构、银行、证券公司和交易所等中心机构的多重协调, 而利用区块链自动化智能合约和可编程的特点, 能够极大地降低成本和提高效率, 避免繁琐的中心化清算交割过程, 实现方便快捷的金融产品交易; 同时, 区块链和比特币的即时到帐的特点可使得银行实现比 SWIFT 代码体系更为快捷、经济和安全的跨境转账; 这也是目前 R3CEV 和纳斯达克等各大银行、证券商和金融机构相继投入区块链技术研发的重要原因.
    • **资产管理:**区块链在资产管理领域的应用具有 广泛前景, 能够实现有形和无形资产的确权、授权和实时监控. 对于无形资产来说, 基于时间戳技术和不可篡改等特点, 可以将区块链技术应用于知识产权保护、域名管理、积分管理等领域; 而对有形资产来说, 通过结合物联网技术为资产设计唯一标识并部署到区块链上, 能够形成 “数字智能资产”, 实现基于区块链的分布式资产授权和控制. 例如, 通过对房屋、车辆等实物资产的区块链密钥授权, 可以基于特定权限来发放和回收资产的使用权, 有助于 Airbnb 等房屋租赁或车辆租赁等商业模式实现自动化的资产交接; 通过结合物联网的资产标记和识别技术, 还可以利用区块链实现灵活的供应链管理和产品溯源等功能.
    • **选举投票:**投票是区块链技术在政治事务中的代表性应用. 基于区块链的分布式共识验证、不可篡改等特点, 可以低成本高效地实现政治选举、企业股东投票等应用; 同时, 区块链也支持用户个体对特定议题的投票. 例如, 通过记录用户对特定事件是否发生的投票, 可以将区块链应用于博彩和预测市场等场景; 通过记录用户对特定产品的投票评分与建议, 可以实现大规模用户众包设计产品的 “社会制造” 模式等.

    **根据实际应用场景和需求, 区块链技术已经演化出三种应用模式, 即公共链(Public blockchain)、联盟链(Consortiumblockchain) 和私有链(Private blockchain). **

    • 公共链是完全去中心化的区块链, 分布式系统的任何节点均可参与链上数据的读写、验证和共识过程, 并根据其 PoW 或 PoS 贡献获得相应的经济激励. 比特币是公共链的典型代表.
    • 联盟链则是部分去中心化 (或称多中心化) 的区块链, 适用于多个实体构成的组织或联盟, 其共识过程受到预定义的一组节点控制, 例如生成区块需要获得10个预选的共识节点中的5个节点确认;
    • 私有链则是完全中心化的区块链, 适用于特定机构的内部数据管理与审计等, 其写入权限由中心机构控制, 而读取权限可视需求有选择性地对外开放.

    区块链的现存问题

    • 安全问题:

      • , 基于 PoW 共识过程的区块链主要面临的是 51% 攻击问题, 即节点通过掌握全网超过 51% 的算力就有能力成功篡改和伪造区块链数据. 以比特币为例, 据统计中国大型矿池的算力已占全网总算力的 60% 以上, 理论上这些矿池可以通过合作实施 51% 攻击, 从而实现比特币的双重支付. 虽然实际系统中为掌握全网 51% 算力所需的成本投入远超成功实施攻击后的收益, 但 51% 攻击的安全性威胁始终存在. 基于 PoS 共识过程在一定程度上解决了 51% 攻击问题, 但同时也引入了区块分叉时的 N@S (Nothing at stake) 攻击问题.
      • 区块链的非对称加密机制也将随着数学、密码学和计算技术的发展而变的越来越脆弱. 据估计, 以目前天河二号的算力来说, 产生比特币 SHA256 哈希算法的一个哈希碰撞大约需要 248 年, 但随着量子计算机等新计算技术的发展, 未来非对称加密算法具有一定的破解可能性, 这也是区块链技术面临的潜在安全威胁.
      • 区块链的隐私保护也存在安全性风险. 区块链系统内各节点并非完全匿名, 而是通过类似电子邮件地址的地址标识 (例如比特币公钥地址) 来实现数据传输. 虽然地址标识并未直接与真实世界的人物身份相关联, 但区块链数据是完全公开透明的, 随着各类反匿名身份甄别技术的发展, 实现部分重点目标的定位和识别仍是有可能的.
    • 效率问题:

      • 区块膨胀问题:区块链要求系统内每个节点保存一份数据备份, 这对于日益增长的海量数据存储来说是极为困难的. 以比特币为例, 完全同步自创世区块至今的区块数据需要约 60GB 存储空间.
      • 交易效率问题:比特币区块链目前每秒仅能处理 7 笔交易, 这极大地限制了区块链在大多数金融系统高频交易场景中的应用 (例如 VISA 信用卡每秒最多可处理 10000 笔交易).
      • 交易确认时间问题: 比特币区块生成时间为10分钟, 因而交易确认时间一般为10分钟, 这在一定程度上限制了比特币在小额交易和时间敏感交易中的应用.
    • 资源问题:

      • PoW 共识过程高度依赖区块链网络节点贡献的算力, 这些算力主要用于解决 SHA256 哈希和随机数搜索, 除此之外并不产生任何实际社会价值, 因而一般意义上认为这些算力资源是被 “浪费” 掉了, 同时被浪费掉的还有大量的电力资源. 随着比特币的日益普及和专业挖矿设备的出现, 比特币生态圈已经在资本和设备方面呈现出明显的军备竞赛态势, 逐渐成为高耗能的资本密集型行业, 进一步凸显资源消耗问题的重要性.
    • 博弈问题:

      • 区块链网络作为去中心化的分布式系统, 其各节点在交互过程中不可避免地会存在相互竞争与合作的博弈关系, 这在比特币挖矿过程中尤为明显. 通常来说, 比特币矿池间可以通过相互合作保持各自稳定的收益. 然而, 矿池可以通过称为区块截留攻击 (Block withholding attacks) 的方式、通过伪装为对手矿池的矿工、享受对手矿池的收益但不实际贡献完整工作量证明来攻击其他矿池, 从而降低对手矿池的收益. 如果矿池相互攻击, 则双方获得的收益均少于不攻击对方的收益. 当矿池收益函数满足特定条件时, 这种攻击和竞争将会造成 “囚徒困境”博弈结局.
      • 此外, 正如前文提到的, 区块链共识过程本质上是众包过程, 如何设计激励相容的共识机制, 使得去中心化系统中的自利节点能够自发地实施区块数据的验证和记账工作, 并提高系统内非理性行为的成本以抑制安全性攻击和威胁, 是区块链有待解决的重要科学问题.

    基于区块链的智能合约

    • 智能合约是区块链的核心构成要素 (合约层), 是由事件驱动的、具有状态的、运行在可复制的共享区块链数据账本上的计算机程序, 能够实现主动或被动的处理数据, 接受、储存和发送价值, 以及控制和管理各类链上智能资产等功能.

    • 智能合约作为一种嵌入式程序化合约, 可以内置在任何区块链数据、交易、 有形或无形资产上, 形成可编程控制的软件定义的系统、市场和资产. 智能合约不仅为传统金融资产的发行、交易、创造和管理提供了创新性的解决方案, 同时能够在社会系统中的资产管理、合同管理、监管执法等事务中发挥重要作用.

    • 智能合约的运作机理如图 5 所示: 通常情况下, 智能合约经各方签署后, 以程序代码的形式附着在区块链数据 (例如一笔比特币交易) 上, 经 P2P 网络传播和节点验证后记入区块链的特定区块中. 智能合约封装了预定义的若干状态及转换规则、触发合约执行的情景 (如到达特定时间或发生特定事件等)、特定情景下的应对行动等. 区块链可实时监控智能合约的状态, 并通过核查外部数据源、确认满足特定触发条件后激活并执行合约.

    这里写图片描述

    • 智能合约的应用举例

      • 互联网金融领域的股权众筹或 P2P 网络借贷等商业模式可以通过区块链和智能合约加以实现. 传统方式是通过股权众筹或 P2P 借贷的交易所或网络平台作为中心机构完成资金募集、管理和投资, 实际操作过程中容易出现因中心机构的信用缺失而导致的资金风险. 利用智能合约, 这些功能均可以封装在去中心化可信的区块链上自动执行. 区块链可记录每一笔融资, 当成功达到特定融资额度时计算每个投资人的股权份额, 或在一段时间内未达到融资额度时自动将资金退还给投资人.
      • 再如, 通过将房屋和车辆等实体资产进行非对称加密, 并嵌入含有特定访问控制规则的智能合约后部署在区块链上, 使用者符合特定的访问权限或执行特定操作 (如付款) 后就可使用这些资产, 这能够有效解决房屋或车辆租赁商业模式中资产交接和使用许可方面的痛点.
    • 智能合约特点及意义

      • 智能合约具有自治、自足和去中心化等特征. 自 表示合约一旦启动就会自动运行, 而不需要其他签署方进行任何干预; 自足则意味着合约能够通过提供服务或发行资产来获取资金, 并在需要时使用这些资金; 去中心化则意味着智能合约是由去中心化存储和验证的程序代码而非中心化实体来保障执行的合约, 能在很大程度上保证合约的公平和公正性.
      • 智能合约对于区块链技术来说具有重要的意义. 一方面, 智能合约是区块链的激活器, 为静态的底层区块链数据赋予了灵活可编程的机制和算法, 并为构建区块链 2.0 和 3.0 时代的可编程金融系统与社会系统奠定了基础; 另一方面, 智能合约的自动化和可编程特性使其可封装分布式区块链系统中各节点的复杂行为, 成为区块链构成的虚拟世界中的软件代理机器人, 这有助于促进区块链技术在各类分布式人工智能系统中的应用, 使得基于区块链技 术构建各类去中心化应用 (Decentralized application, Dapp)、去中心化自治组织(Decentralized autonomous organization, DAO)、去中心化自治公司 (Decentralized autonomous corporation, DAC) 甚至去中心化自治社会 (Decentralized autonomous society, DAS) 成为可能.
      • 就现状而言, 区块链和智能合约技术的主要发展趋势是由自动化向智能化方向演化. 现存的各类智能合约及其应用的本质逻辑大多仍是根据预定义场景的 “IF-THEN” 类型的条件响应规则, 能够满足目前自动化交易和数据处理的需求. 未来的智能合约应具备根据未知场景的 “WHAT-IF” 推演、计算实验和一定程度上的自主决策功能, 从而实现由目前 “自动化” 合约向真正的 “智能” 合约的飞跃.
    展开全文
  • Java内存模型

    万次阅读 多人点赞 2019-10-04 15:09:15
    Java内存模型(JMM)的介绍 在上一篇文章中总结了线程的状态和基本操作,对多线程已经有一点基本的认识了,如果多线程编程只有这么简单,那我们就不必费劲周折的去学习它了。在多线程中稍微不注意就会出现线程安全...

    Java 内存模型试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。

    Java内存模型(JMM)的介绍

    什么是线程安全?在<<深入理解Java虚拟机>>中看到的定义。原文如下:
    当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替运行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获取正确的结果,那这个对象是线程安全的。

    关于定义的理解是一个仁者见仁智者见智的事情。出现线程安全的问题一般是因为主内存和工作内存数据不一致性重排序导致的,而解决线程安全的问题最重要的就是理解这两种问题是怎么来的,那么,理解它们的核心在于理解Java内存模型(JMM)。

    在多线程条件下,多个线程肯定会相互协作完成一件事情,一般来说就会涉及到多个线程间相互通信告知彼此的状态以及当前的执行结果等,另外,为了性能优化,还会涉及到编译器指令重排序和处理器指令重排序。下面会一一来聊聊这些知识。

    内存模型抽象结构

    线程间协作通信可以类比人与人之间的协作的方式,在现实生活中,之前网上有个流行语“你妈喊你回家吃饭了”,就以这个生活场景为例,小明在外面玩耍,小明妈妈在家里做饭,做完饭后准备叫小明回家吃饭,那么就存在两种方式:

    小明妈妈要去上班了十分紧急这个时候手机又没有电了,于是就在桌子上贴了一张纸条“饭做好了,放在…”小明回家后看到纸条如愿吃到妈妈做的饭菜,那么,如果将小明妈妈和小明作为两个线程,那么这张纸条就是这两个线程间通信的共享变量,通过读写共享变量实现两个线程间协作;

    还有一种方式就是,妈妈的手机还有电,妈妈在赶去坐公交的路上给小明打了个电话,这种方式就是通知机制来完成协作。同样,可以引申到线程间通信机制。

    通过上面这个例子,应该有些认识。在并发编程中主要需要解决两个问题:1. 线程之间如何通信;2.线程之间如何完成同步(这里的线程指的是并发执行的活动实体)。通信是指线程之间以何种机制来交换信息,主要有两种:共享内存和消息传递。这里,可以分别类比上面的两个举例。Java内存模型是共享内存的并发模型,线程之间主要通过读-写共享变量来完成隐式通信。如果程序员不能理解Java的共享内存模型在编写并发程序时一定会遇到各种各样关于内存可见性的问题。

    哪些是共享变量

    在Java程序中所有实例域,静态域和数组元素都是放在堆内存中(所有线程均可访问到,是可以共享的),而局部变量,方法定义参数和异常处理器参数不会在线程间共享。共享数据会出现线程安全的问题,而非共享数据不会出现线程安全的问题。关于JVM运行时内存区域在后面的文章会讲到。

    JMM抽象结构模型

    我们知道CPU的处理速度和主存的读写速度不是一个量级的(CPU的处理速度快很多),为了平衡这种巨大的差距,每个CPU都会有缓存。因此,共享变量会先放在主存中,每个线程都有属于自己的工作内存,并且会把位于主存中的共享变量拷贝到自己的工作内存,之后的读写操作均使用位于工作内存的变量副本,并在某个时刻将工作内存的变量副本写回到主存中去。JMM就从抽象层次定义了这种方式,并且JMM决定了一个线程对共享变量的写入何时对其他线程是可见的。

    在这里插入图片描述

    如图为JMM抽象示意图,线程A和线程B之间要完成通信的话,要经历如下两步:

    1. 线程A从主内存中将共享变量读入线程A的工作内存后并进行操作,之后将数据重新写回到主内存中;
    2. 线程B从主存中读取最新的共享变量

    从横向去看看,线程A和线程B就好像通过共享变量在进行隐式通信。这其中有个意思的问题,如果线程A更新后数据并没有及时写回到主存,而此时线程B读到的是过期的数据,这就出现了“脏读”现象。可以通过同步机制(控制不同线程间操作发生的相对顺序)来解决或者通过volatile关键字使得每次volatile变量都能够强制刷新到主存,从而对每个线程都是可见的。

    主内存与工作内存

    处理器上的寄存器的读写的速度比内存快几个数量级,为了解决这种速度矛盾,在它们之间加入了高速缓存。

    加入高速缓存带来了一个新的问题:缓存一致性。如果多个缓存共享同一块主内存区域,那么多个缓存的数据可能会不一致,需要一些协议来解决这个问题。

    所有的变量都存储在主内存中,每个线程还有自己的工作内存,工作内存存储在高速缓存或者寄存器中,保存了该线程使用的变量的主内存副本拷贝。

    线程只能直接操作工作内存中的变量,不同线程之间的变量值传递需要通过主内存来完成。

    内存间交互操作

    Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作。

    1. lock(锁定):作用于主内存中的变量,它把一个变量标识为一个线程独占的状态;
    2. unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
    3. read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便后面的load动作使用;
    4. load(载入):作用于工作内存中的变量,它把read操作从主内存中得到的变量值放入工作内存中的变量副本
    5. use(使用):作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作;
    6. assign(赋值):作用于工作内存中的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作;
    7. store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送给主内存中以便随后的write操作使用;
    8. write(操作):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

    内存模型三大特性

    1. 原子性

    Java 内存模型保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性,例如对一个 int 类型的变量执行 assign 赋值操作,这个操作就是原子性的。但是 Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据(long,double)的读写操作划分为两次 32 位的操作来进行,即 load、store、read 和 write 操作可以不具备原子性。

    有一个错误认识就是,int 等原子性的类型在多线程环境中不会出现线程安全问题。前面的线程不安全示例代码中,cnt 属于 int 类型变量,1000 个线程对它进行自增操作之后,得到的值为 997 而不是 1000。

    为了方便讨论,将内存间的交互操作简化为 3 个:load、assign、store。

    下图演示了两个线程同时对 cnt 进行操作,load、assign、store 这一系列操作整体上看不具备原子性,那么在 T1 修改 cnt 并且还没有将修改后的值写入主内存,T2 依然可以读入旧值。可以看出,这两个线程虽然执行了两次自增运算,但是主内存中 cnt 的值最后为 1 而不是 2。因此对 int 类型读写操作满足原子性只是说明 load、assign、store 这些单个操作具备原子性。

    AtomicInteger 能保证多个线程修改的原子性。

    使用 AtomicInteger 重写之前线程不安全的代码之后得到以下线程安全实现:

    public class AtomicExample {
        private AtomicInteger cnt = new AtomicInteger();
    
        public void add() {
            cnt.incrementAndGet();
        }
    
        public int get() {
            return cnt.get();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        final int threadSize = 1000;
        AtomicExample example = new AtomicExample(); // 只修改这条语句
        final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < threadSize; i++) {
            executorService.execute(() -> {
                example.add();
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println(example.get());
    }
    

    输出结果

    1000
    

    synchronized关键字

    除了使用原子类之外,也可以使用 synchronized 互斥锁来保证操作的原子性。它对应的内存间交互操作为:lock 和 unlock,在虚拟机实现上对应的字节码指令为 monitorenter 和 monitorexit。

    public class AtomicSynchronizedExample {
        private int cnt = 0;
    
        public synchronized void add() {
            cnt++;
        }
    
        public synchronized int get() {
            return cnt;
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        final int threadSize = 1000;
        AtomicSynchronizedExample example = new AtomicSynchronizedExample();
        final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < threadSize; i++) {
            executorService.execute(() -> {
                example.add();
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println(example.get());
    }
    

    输出结果

    1000
    

    volatile关键字

    public class VolatileExample {
        private static volatile int counter = 0;
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 10000; i++)
                            counter++;
                    }
                });
                thread.start();
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(counter);
        }
    }
    

    开启10个线程,每个线程都自加10000次,如果不出现线程安全的问题最终的结果应该就是:10*10000 = 100000;可是运行多次都是小于100000的结果,问题在于 volatile并不能保证原子性,在前面说过counter++这并不是一个原子操作,包含了三个步骤:1.读取变量counter的值;2.对counter加一;3.将新值赋值给变量counter。如果线程A读取counter到工作内存后,其他线程对这个值已经做了自增操作后,那么线程A的这个值自然而然就是一个过期的值,因此,总结果必然会是小于100000的。

    如果让volatile保证原子性,必须符合以下两条规则:

    1. 运算结果并不依赖于变量的当前值,或者能够确保只有一个线程修改变量的值;
    2. 变量不需要与其他的状态变量共同参与不变约束

    2. 可见性

    可见性指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的。

    主要有三种实现可见性的方式:

    • volatile,通过在指令中添加lock指令,以实现内存可见性。
    • synchronized,当线程获取锁时会从主内存中获取共享变量的最新值,释放锁的时候会将共享变量同步到主内存中。
    • final,被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),那么其它线程就能看见 final 字段的值。

    对前面的线程不安全示例中的 cnt 变量使用 volatile 修饰,不能解决线程不安全问题,因为 volatile 并不能保证操作的原子性。

    3. 有序性

    有序性是指:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

    volatile 关键字通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前。

    synchronized 关键字同样可以保证有序性,它保证每个时刻只有一个线程执行同步代码,相当于是让线程顺序执行同步代码。

    总结

    synchronized:具有原子性,有序性和可见性

    volatile:具有有序性和可见性

    final:具有可见性

    内存屏障

    我们都知道,为了性能优化,JMM在不改变正确语义的前提下,会允许编译器和处理器对指令序列进行重排序,那如果想阻止重排序要怎么办了?答案是可以添加内存屏障。

    内存屏障

    JMM内存屏障分为四类

    屏障类型 指令示例 说明
    LoadLoad Barriers Load1;LoadLoad;Load2 确保Load1的数据的装载先于Load2及所有后续装载指令的装载
    StoreStoreBarriers Store1;StoreStore;Store2 确保Store1数据对其他处理器可见(刷新到内存)先于Store2及所有后续存储指令的存储
    LoadStore Barriers Load1;LoadStore;Store2 确保Load1的数据的装载先于Store2及所有后续存储指令的存储
    StoreLoad Barriers Store1;StoreLoad;Load2 确保Store1的数据对其他处理器可见(刷新到内存)先于Load2及所有后续的装载指令的装载

    Java编译器会在生成指令系列时在适当的位置会插入内存屏障指令来禁止特定类型的处理器重排序。为了实现volatile的内存语义,JMM会限制特定类型的编译器和处理器重排序,JMM会针对编译器制定volatile重排序规则表:

    在这里插入图片描述

    "NO"表示禁止重排序。为了实现volatile内存语义时,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎是不可能的,为此,JMM采取了保守策略:

    1. 在每个volatile写操作的前面插入一个StoreStore屏障;
    2. 在每个volatile写操作的后面插入一个StoreLoad屏障;
    3. 在每个volatile读操作的后面插入一个LoadLoad屏障;
    4. 在每个volatile读操作的后面插入一个LoadStore屏障。

    需要注意的是:volatile写是在前面和后面分别插入内存屏障,而volatile读操作是在后面插入两个内存屏障

    StoreStore屏障:禁止上面的普通写和下面的volatile写重排序;

    StoreLoad屏障:防止上面的volatile写与下面可能有的volatile读/写重排序

    LoadLoad屏障:禁止下面所有的普通读操作和上面的volatile读重排序

    LoadStore屏障:禁止下面所有的普通写操作和上面的volatile读重排序

    下面以两个示意图进行理解,图片摘自相当好的一本书《Java并发编程的艺术》。

    在这里插入图片描述

    在这里插入图片描述

    先行发生原则

    上面提到了可以用 volatile 和 synchronized 来保证有序性。除此之外,JVM 还规定了先行发生原则,让一个操作无需控制就能先于另一个操作完成。

    1. 单一线程原则

    Single Thread rule

    在一个线程内,在程序前面的操作先行发生于后面的操作。

    2. 管程锁定规则

    Monitor Lock Rule

    一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。

    3. volatile 变量规则

    Volatile Variable Rule

    对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。

    4. 线程启动规则

    Thread Start Rule

    Thread 对象的 start() 方法调用先行发生于此线程的每一个动作。

    5. 线程加入规则

    Thread Join Rule

    Thread 对象的结束先行发生于 join() 方法返回。

    6. 线程中断规则

    Thread Interruption Rule

    对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。

    7. 对象终结规则

    Finalizer Rule

    一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。

    8. 传递性

    Transitivity

    如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。

    展开全文
  • 高斯混合模型(GMM)及其EM算法的理解

    万次阅读 多人点赞 2017-03-02 18:43:36
    一个例子高斯混合模型(Gaussian Mixed Model)指的是多个高斯分布函数的线性组合,理论上GMM可以拟合出任意类型的分布,通常用于解决同一集合下的数据包含多个不同的分布的情况(或者是同一类分布但参数不一样,...
  • 混合高斯模型

    千次阅读 2018-07-12 18:14:25
    三、高斯混合模型(Gaussian Mixed Model) 3.1 高斯混合模型(GMM) 概念 3.2 GMM 用于聚类分析 3.3 GMM 似然函数 参数估计 GMM 似然函数(log-likelihood function)推导 : GMM 总结 3.4 EM算法 一、标准差椭圆 ...
  • 技术接受模型(TAM,Technology Acceptance Model)

    万次阅读 多人点赞 2015-11-23 15:32:46
    技术接受模型(Technology Acceptance Model,简称TAM)是由美国学者戴维斯(Fred D. Davis, 1986)根据理性行为理论(Theory of Reasoned Action,简称TRA)在信息系统/计算机技术领域发展而来,用于解释和预测人们...
  • 模型评估

    千次阅读 2017-01-07 20:13:12
    ROC曲线  接收器操作特性曲线(receiver operating characteristic curve),曲线上各...概念定义: 真正(TruePositive , TP)被模型预测为正的正样本; 假负(FalseNegative , FN)被模型预测为负的正样本; 假正(Fal
  • 统计学习方法——K近邻模型

    万次阅读 多人点赞 2017-03-09 22:28:13
    0. 写在前面在这一讲的讨论班中,我们将要讨论一下K近邻模型。...1. K近邻定义k近邻算法,也成为KNN算法,是一种基本分类与回归算法。它在基本实现上,使用的是多数表决的惰性学习过程。也就是它实际上
  • 使用Flask部署机器学习模型

    千次阅读 2020-07-09 16:57:53
    部署机器学习模型是每个ML项目的一个关键 学习如何使用Flask将机器学习模型部署到生产中 模型部署是数据科学家访谈中的一个核心话题 介绍 我记得我早期在机器学习领域的日子。我喜欢处理多个问题,对机器学习...
  • 一文看懂25个神经网络模型

    万次阅读 多人点赞 2017-06-17 10:26:08
    光是知道各式各样的神经网络模型缩写(如:DCIGN、BiLSTM、DCGAN……还有哪些?),就已经让人招架不住了。因此,这里整理出一份清单来梳理所有这些架构。其中大部分是人工神经网络,也有一些完全不同的怪物。尽管...
  • Java并发编程(四)Java内存模型

    千次阅读 2016-05-29 10:39:11
    此前我们讲到了线程、同步以及volatile关键字,对于Java的并发编程我们有必要了解下Java的内存模型,因为Java线程之间的通信对于工程师来言是完全透明的,内存可见性问题很容易使工程师们觉得困惑,这篇文章我们来...
  • Java对象模型

    千次阅读 2020-02-06 14:44:49
    Java对象模型
  • 深入理解盒模型

    千次阅读 2016-11-27 14:11:54
    1.父元素设置 /* Safari, Opera, and Chrome */ display:-webkit-box; /* Firefox */ display:-moz-box;...2.box-orient 定义模型的布局方向 horizontal 水平显示 vertical 垂直显示 3.box-direction 元
  • 模板编译模型

    千次阅读 2010-07-07 17:28:00
    标准C++为编译模板代码定义了两种模型。所有编译器都支持第一种模型,称为“包含”模型(inclusion compilation model);只有一些编译器支持第二种模型,“分别编译”模型(separate compilation model)。
  • TCP/IP五层网络架构及OSI参考模型

    万次阅读 多人点赞 2018-02-05 10:26:40
    国际标准化组织(ISO)制定了OSI模型,该模型定义了不同计算机互联的标准,是设计和描述计算机网络通信的基本框架。OSI模型把网络通信的工作分为7层,OSI的7层从上到下分别是 7 应用层 6 表示层 5 会话层 4 ...
  • pytorch如何使用半精度模型部署

    千次阅读 2020-04-25 13:04:06
    背景 pytorch作为深度学习的计算框架正得到越来越多的应用.我们除了在模型训练阶段应用外,最近也把pytorch应用在了部署上.在部署时,为了减少计算量,可以考虑使用16位浮点模型,而训练...在pytorch中,一般模型定义都...
  • obj模型

    千次阅读 2015-08-15 16:57:52
    这篇文章给大家讲Obj模型里一些基本功能的完善,包含Cg着色语言,矩阵转换,光照,多重纹理,法线贴图的运用.  在上篇中,我们用GLSL实现了基本的phong光照,这里用Cg着色语言来实现另一钟Blinn-phong光照模型,平常我们说...
  • CSS学习—盒模型和布局模型

    千次阅读 2017-07-06 11:48:02
    模型与布局模型是网页设计的基础知识之一,现在将所学的笔记记录于此博客中。
  • 预训练模型

    千次阅读 2019-06-20 17:10:57
    1 预训练模型由来 预训练模型是深度学习架构,已经过训练以执行大量数据上的特定任务(例如,识别图片中的分类问题)。这种训练不容易执行,并且通常需要大量资源,超出许多可用于深度学习模型的人可用的资源,我就...
  • 关于JVM内存模型的理解

    万次阅读 多人点赞 2018-03-09 23:47:16
    一、概念 1、JVM内存模型 首先老规矩,祭上一张自己画的内存模型图 画的比较简陋,简单介绍一下,整个JVM...这两个大区内部根据JVM规范定义又分为以下几个区: 方法区(Method Area) 方法区主要是放一下...
  • 信用评分模型(R语言)

    万次阅读 多人点赞 2016-04-23 10:45:57
    本文详细的介绍了信用评分卡的开发流程,开发语言为R语言,python版本请见:一行代码搞定信用评分模型(python) python版实例和数据请见我的github:https://github.com/chengsong990020186/CreditScoreModel,如...
  • 状态空间模型

    万次阅读 2016-02-03 17:50:30
    一、状态空间模型简述 状态空间模型是动态时域模型,以隐含着的时间为自变量。状态空间模型包括两个模型: 一是状态方程模型,反映动态系统在输入变量作用下在某时刻所转移到的状态; 二是输出或量测方程模型,它将...
  • 蒙特卡洛模拟Ising模型

    万次阅读 多人点赞 2018-03-21 21:47:09
    蒙特卡洛模拟XY伊辛模型(python)      前言故事  世界上最早的通用电子计算机之一----ENIAC在发明后即被用于曼哈顿计划,乌拉姆敏锐地意识到在计算机的帮助下,可通过重复数百次模拟过程的方式来对概率...
  • Java内存模型与线程

    千次阅读 2018-08-07 11:28:07
    Java内存模型与线程 Java内存模型与线程 Start 硬件的效率与一致性 Java内存模型 主内存与工作内存 内存间交互操作 对于volatile型变量的特殊规则 可见性 禁止指令重排序优化 对于long和double型变量的特殊...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 60,191
精华内容 24,076
关键字:

一半模型的定义