序列化框架-Kyro简述

网上有很多资料说 Kryo 只能在 Java 上使用,这点是不对的,事实上除 Java 外,Scala 和 Kotlin 这些基于 JVM 的语言同样可以使用 Kryo 实现序列化。

1.使用方法

(1)添加kyro依赖

<dependency>
   <groupId>com.esotericsoftware</groupId>
   <artifactId>kryo</artifactId>
   <version>5.2.0</version>
</dependency>

(2)基本使用概况

Kryo 类会自动执行序列化。Output 类和 Input 类负责处理缓冲字节,并写入到流中。

public class HelloKryo {
    static public void main(String[] args) throws Exception {
        Kryo kryo = new Kryo();
        kryo.register(SomeClass.class);
 
        SomeClass object = new SomeClass();
        object.value = "Hello Kryo!";
 
        Output output = new Output(new FileOutputStream("file.bin"));
        kryo.writeObject(output, object);
        output.close();
 
        Input input = new Input(new FileInputStream("file.bin"));
        SomeClass object2 = kryo.readObject(input, SomeClass.class);
        input.close();
        System.out.println(object2.value);
    }
 
    static public class SomeClass {
        String value;
    }
}

(3)Kyro注册

和很多其他的序列化框架一样,Kryo 为了提供性能减小序列化结果体积,提供注册的序列化对象类的方式。在注册时,会为该序列化类生成 int ID,后续在序列化时使用 int ID 唯一标识该类型。注册的方式如下:

或者

可以明确指定注册类的 int ID,但是该 ID 必须大于等于 0。如果不提供,内部将会使用 int++的方式维护一个有序的 int ID 生成。

2.线程不安全

Kryo 不是线程安全的。每个线程都应该有自己的 Kryo 对象、输入和输出实例。
因此在多线程环境中,可以考虑使用 ThreadLocal 或者对象池来保证线程安全性。

ThreadLocal解决线程不安全

ThreadLocal 是一种典型的牺牲空间来换取并发安全的方式,它会为每个线程都单独创建本线程专用的 kryo 对象。对于每条线程的每个 kryo 对象来说,都是顺序执行的,因此天然避免了并发安全问题。

创建方法如下:

static private final ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() {
   protected Kryo initialValue() {
      Kryo kryo = new Kryo();
      // 在此处配置kryo对象的使用示例,如循环引用等
      return kryo;
   };
};
 
Kryo kryo = kryos.get();

之后,仅需要通过 kryos.get() 方法从线程上下文中取出对象即可使用。

对象池解决线程不安全

「池」是一种非常重要的编程思想,连接池、线程池、对象池等都是「复用」思想的体现,通过将创建的“对象”保存在某一个“容器”中,以便后续反复使用,避免创建、销毁的产生的性能损耗,以此达到提升整体性能的作用。
Kryo 对象池原理也是如此。Kryo 框架自带了对象池的实现,整个使用过程不外乎创建池、从池中获取对象、归还对象三步,以下为代码实例。

// Pool constructor arguments: thread safe, soft references, maximum capacity
Pool<Kryo> kryoPool = new Pool<Kryo>(true, false, 8) {
   protected Kryo create () {
      Kryo kryo = new Kryo();
      // Kryo 配置
      return kryo;
   }
};
 
// 获取池中的Kryo对象
Kryo kryo = kryoPool.obtain();
// 将kryo对象归还到池中
kryoPool.free(kryo);

参数分析:

第一个参数用于指定是否在 Pool 内部使用同步,如果指定为 true,则允许被多个线程并发访问。
第三个参数适用于指定对象池的大小的。
第二个参数设置为 true,Kryo 池将会使用 java.lang.ref.SoftReference 来存储对象。这允许池中的对象在 JVM 的内存压力大时被垃圾回收。Pool clean 会删除所有对象已经被垃圾回收的软引用。当没有设置最大容量时,这可以减少池的大小。当池子有最大容量时,没有必要调用 clean,因为如果达到了最大容量,Pool free 会尝试删除一个空引用。

创建完 Kryo 池后,使用 kryo 就变得异常简单了,只需调用 kryoPool.obtain() 方法即可,使用完毕后再调用 kryoPool.free(kryo) 归还对象,就完成了一次完整的租赁使用。
理论上,只要对象池大小评估得当,就能在占用极小内存空间的情况下完美解决并发安全问题。如果想要封装一个 Kryo 的序列化方法,可以参考如下的代码


public static byte[] serialize(Object obj) {
    Kryo kryo = kryoPool.obtain();
    // 使用 Output 对象池会导致序列化重复的错误(getBuffer返回了Output对象的buffer引用)
    try (Output opt = new Output(1024, -1)) {
        kryo.writeClassAndObject(opt, obj);
        opt.flush();
        return opt.getBuffer();
    }finally {
        kryoPool.free(kryo);
    }
}

3.小结

相较于 JDK 自带的序列化方式,Kryo 的性能更快,并且由于 Kryo 允许多引用和循环引用,在存储开销上也更小。
只不过,虽然 Kryo 拥有非常好的性能,但其自身却舍去了很多特性,例如线程安全、对序列化对象的字段修改等。虽然这些弊端可以通过 Kryo 良好的扩展性得到一定的满足,但是对于开发者来说仍然具有一定的上手难度,不过这并不能影响其在 Java 中的地位。

本文转载于网络 如有侵权请联系删除

相关文章

  • 过度设计是罪恶的!

    原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留此信息。软件开发的哪个阶段最容易招人喷?如果你严格按照什么瀑布模式、敏捷模式开发的话,你会发现永远是概要设计的评审阶段。这个时候,屎山还没有成为既定的事实。多位理想主义达人,就会搬出各种规则、规范,来给你的方案下套子。他们是为了你的方案更好么?大多数情况未必。有的人,多说几句是为了凸显自己的价值;有的人是刚看了几本书,感觉不吐不快;还有的人,本身就是完美主义者,看不得你的方案有任何瑕疵。总结下来,完美主义者还是有点作用的。但当你把开发任务扔给这些指挥和挑刺的人,你会发现他们大多数不仅仅实现不了自己给套上的套子,连最基本的功能实现都是问题。每当这时候,我内心都会大喊:让这些假洋鬼子去死吧!组件替换问题如果我们的技术栈,选用的是MySQL,我们会采用JDBC、MyBatis、JPA等一系列的基础的编码工具。但这种选择,对追求接口和实现分离的同学来说,却是不可忍受的。这些人会搬出无数的理由,来说明,如果不加入一个中间层的话,代码是无法复用的。他们追求的是,如果你将来把数据库从MySQL切换到ElasticSearch,那么

  • TF-char5-TF2高级操作

    char5-TF高阶操作第五章主要是介绍了TensorFlow2的几个高阶操作,包含:合并与分割数据统计张量比较填充与复制数据限幅张量的高级操作数据加载及预处理合并与分割合并将多个张量在一个维度上合并成一个张量。合并有分为两种:拼接concatenate和堆叠stack。拼接tf.concat(x,axis)不会产生新的维度约束条件是:非合并的维度必须是一致的axis指定拼接的轴;x条件是待合并的张量importtensorflow a=tf.random.normal([4,8,5]) b=tf.random.normal([6,8,5]) tf.concat([a,b],axis=0)#结果是[10,8,5]复制堆叠tf.stack(x,axis)创建新的维度,新维度的位置是任意的可以同时堆叠多个张量进行堆叠的张量维度必须一致axis的用法和tf.expand_dims中相同:axis\geq0表示当前维度之前插入axis<0importtensorflow a=tf.random.normal([6,8]) b=tf.random.normal([6,8]) tf.st

  • 04.Android崩溃Crash库之Loop拦截崩溃和ANR

    目录总结01.能否利用Looper拦截崩溃02.思考几个问题分析03.App启动时自动开启Looper04.拦截主进程崩溃前沿上一篇整体介绍了crash崩溃库崩溃重启,崩溃记录记录,查看以及分享日志等功能。项目地址:https://github.com/yangchong211/YCAndroidTool欢迎star01.能否利用Looper拦截崩溃问题思考一下能否基于Handler和Looper拦截全局崩溃(主线程),避免APP退出。能否基于Handler和Looper实现ANR监控。测试代码如下所示publicclassAppextendsApplication{ @Override publicvoidonCreate(){ super.onCreate(); CrashTestDemo.test(); } }//测试代码复制publicclassCrashTestDemo{复制privatestaticlongstartWorkTimeMillis=0L;复制publicstaticvoidtest(){复制Looper.getMainLooper().setMessageLo

  • ORB-SLAM3来了!真有生之年!

    前言2015年,ORB-SLAM来了!2016年,ORB-SLAM2来了!时隔4年,产生了多少SLAMer...2020年,ORB-SLAM3来了!真有生之年!学SLAM的同学,应该没有不知道ORB-SLAM的,截止2020年7月24日,ORB-SLAM系列的谷歌引用量已达到4770=3053+1717!实属相当恐怖的数据值得说一下,ORB-SLAM和ORB-SLAM2的一作都是RaúlMur-Artal,但这位大佬应该已经毕业了,所以ORB-SLAM3由其同校应该也是同实验室的CarlosCampos完成。单看论文作者列表,猜测他们的导师都是:JuanD.Tardós ORB-SLAM3论文:https://arxiv.org/abs/2007.11898代码(已开源):https://github.com/UZ-SLAMLab/ORB_SLAM3ORB-SLAM3:第一个能够使用针孔和鱼眼镜头模型,使用单目,stereo和RGB-D相机执行视觉,视觉惯性和多地图SLAM的系统。第一个创新:基于特征的紧密集成的视觉惯性SLAM系统,即使在IMU初始化阶段,该系统也完全依赖于最大后验(

  • Apache HBase内核深度剖析

    前面一篇文章介绍了Kafka的具体内容,今天讲述一下HBase相关的知识。首先HBase作为大数据发展初期伴随Google三大论文问世的一个组件,在今天依旧被广泛的应用,今天我们来仔细的分析一下HBase的内部原理,了解一下HBase的具体内幕,以便在工作中更好使用它。以下内容涉及到的源码基于HBase的Master分支编译出的最新的3.0.0版本。HBase相关算法与数据结构基础知识 跳跃表暂时先不说跳跃表是什么,在Java里面有一个Map叫:ConcurrentSkipListMap,通过对HBase的源码跟踪,我们发现这些地方使用了它:简单的列了几个,但是观察这几个类所在的模块就可以发现,HBase从客户端,到请求处理,到元数据再到文件存储贯穿HBase的整个生命周期中的各个重要环节,都能看到它的身影,Map那么多,为何偏偏HBase选择了这个?接下来我们仔细分析下。在算法概念里面有一种数据结构叫跳跃表,顾名思义,之所以叫跳跃表就是因为在查找的时候可以快速的跳过部分列表,用来提升查找效率,跳跃表的查找效率可以和二叉树相比为O(log(N)),这个算法的实现在Java中的Concu

  • 2017-2018-20172309 《程序设计与数据结构》第八周学习总结

    2017-2018-20172309《程序设计与数据结构》第八周学习总结 一、教材学习内容总结 相信其它很多同学都是以小顶堆来介绍这一章内容,所以我将以大顶堆来介绍这章内容。 1.1堆的简单介绍: 堆的定义:(大顶堆) 堆实际上是一棵完全二叉树。 堆满足两个性质: 堆的每一个父节点都大于其子节点; 堆的每个左子树和右子树也是一个堆。 堆的分类: 堆分为两类: 最大堆(大顶堆):堆的每个父节点都大于其孩子节点; 最小堆(小顶堆):堆的每个父节点都小于其孩子节点; 例子: 堆的操作: 堆的定义是二叉树的拓展,因此他也继承了二叉树的所有操作。 操作列表(大顶堆为例): 操作 说明 addElement() 将给定元素添加到该堆中去 removeMax() 删除堆中最大的元素 findMax() 返回一个指向堆中最大元素的引用 addElement操作: addElement方法将给定的Comparable元素添加到堆中的恰当位置,且维持该堆的完全属性和有序属性。 因为堆是一棵完全二叉树,因此对于新插入的节点而言,就只有一

  • O(n)求最大间隙问题

    桶排序

  • flask之请求上下文

    请求上下文源码分析 第一阶段:将ctx(request,session)放到Local对象上 第二阶段:视图函数导入:request/session request.method -LocalProxy对象.method,执行getattr方法,getattr(self._get_current_object(),name) -self._get_current_object()返回returnself.__local(),self.__local(),在LocakProxy实例化的时候,object.__setattr__(self,'_LocalProxy__local',local),此处local就是:partial(_lookup_req_object,'request') -def_lookup_req_object(name): top=_request_ctx_stack.top#_request_ctx_stack就是LocalStack()对象,top方法把ctx取出来 iftopisNone: raiseRuntimeErr

  • 浅析容斥和DP综合运用

    浅析容斥和DP综合运用 前言 众所周知在数数题中有一种很重要的计数方法——容斥。但是容斥有一个很大的缺陷:枚举子集的复杂度过高。所以对于数据规模较大的情况会很乏力,那么我们就只能引入容斥DP。 复习一下容斥 什么情况下用容斥?容斥能干什么? 容斥的基本功能就是当你知道任意个指定集合的交集,你就能推出这些集合的并集。 形式化的来说,就是: \[\left|\bigcup_{i=1}^{n}A_{i}\right|=\sum_{i=1}^{n}\left|A_{i}\right|-\sum_{1\leqi<j\leqn}\left|A_{i}\capA_{j}\right|+\sum_{1\leqi<j<k\leqn}\left|A_{i}\capA_{j}\capA_{k}\right|-\cdots+(-1)^{n-1}\left|A_{1}\cap\cdots\capA_{n}\right| \]只使用容斥朴素算法 如果我们只会容斥,我们该怎么做?很显然根据上面的公式,我们需要枚举任意集合的组合方式,然后统计他们的答案,将他们加入答案。 比如说在【线上训练5】乘方中

  • 第二次冲刺周期站立会议(2)

    一.站立会议照片 二.任务进度   今天的任务是完善服务器的连接,并且完善页面,由于之前的页面太过简单,所以需要进行修饰美化。 三.任务看板 四.燃尽图

  • hadoop完全分布式模式搭建和hive安装

    简介 Hadoop是用来处理大数据集合的分布式存储计算基础架构。可以使用一种简单的编程模式,通过多台计算机构成的集群,分布式处理大数据集。hadoop作为底层,其生态环境很丰富。 hadoop基础包括以下四个基本模块: hadoop基础功能库:支持其他hadoop模块的通用程序包。 HDFS:一个分布式文件系统,能够以高吞吐量访问应用的数据。 YARN:一个作业调度和资源管理框架。 MapReduce:一个基于YARN的大数据并行处理程序。 当然,hadoop相关的项目很多,包括HBase,Hive,Spark,ZooKeeper等。 基础准备 基础准备工作包括:安装环境的准备,ssh配置 安装环境的准备 首先需要3台机器(后续也可以扩展),我准备的是虚拟机中的3个ubuntu16.04.2.内存1G. 猩猩爱宝贝儿

  • 如何在WIN7下进行LINUX虚拟机搭建

     Linux是一套免费使用和自由传播的类Unix操作系统,非常适用于搭建网络服务器等,我本人日常工作时,是使用的LINUX和WIN7双操作系统,但每次更换系统总要关机重启很不方便,所以也在WIN7下搭建了LINUX虚拟机。      本篇经验,是以UBUNTU为例,不过您搭建其他版本的LINUX系统,比如CENTOS等,也都适用。 工具/原料   VMware Ubuntu镜像文件/光盘 一.安装VMware   1 以下这是您必备的文件: 1)ubuntu镜像文件,.iso文件; 2)VMware安装文件。 操作步骤: 1)首先安装VMware,在WIN7上搭建一个虚拟环境。 2)在VMware的基础上,通过ISO镜像文件,安装LINUX操作系统。   2 首先,安装VMware,自然要双击VMware的安装包。经过一点运行之后,便出来这个界面。 点击下一步 3 继续点击下一步之后,会出来如图界面! 如果你是高手,点击自定义,如果新

  • (ETW) Event Tracing for Windows 入门 (含pdf下载)

      内容提纲 • ETW介绍 • ETW使用 • ETW监控本机Demo • ETW监控远程机器的思路 • 底层类库:EventSource介绍 • 底层类库:TraceEvent介绍 ETW是什么? 1.EventTracingforWindows(ETW):是由操作系统提供的一种通用的,系统开销较低(与性能日志和警报相比)的事件追踪手段,用以监控具有负载的系统的性能。  2.ETW主要用于必须频繁记录事件、错误、警告或审核的服务器应用程序。ETW提供用户模式的应用程序和内核模式的设备驱动程序所触发的事件追踪机制。此外,ETW还能够动态地启用或者禁用日志记录,便于进行详细的追踪,而无需重新启动操作系统或者应用程序。   ETW的历史 1.ETW最先在Windows2000中被引入。自那时以后,各种Windows操作系统核心和服务组件都通过ETW记录其活动,它现在是Windows平台上的关键系统仪表技术之一。在Windows7中,ETW得到了进一步的增强。  2.正是基于ETW的优秀性能

  • CentOS + Nodejs

    CentOS安装NodeJS 1、从官网下下载最新的nodejs,https://nodejs.org/en/download/   2、右键复制连接  https://nodejs.org/dist/v12.14.1/node-v12.14.1-linux-x64.tar.x cd/rootmkdirnodejscdnodejswgethttps://nodejs.org/dist/v12.14.1/node-v12.14.1-darwin-x64.tar.gztar-xvfnode-v12.14.1-darwin-x64.tar.gz复制   3、让npm和node命令全局生效   方式一:环境变量方式(这种方式似乎只对登录用户有效?)   1)、加入环境变量,在/etc/profile文件末尾增加配置 vi/ect/profileexportPATH=$PATH:/nodejs/node-v12.14.1-darwin-x64/bin复制   2)、执行命令使配置文件生效 source/etc/profile复制   方式

  • 关于webApi使用session

    1、关于webApi使用session     在Global.asax中注册session添加以下代码 publicoverridevoidInit() { //开启session this.PostAuthenticateRequest+=(sender,e)=>HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required); base.Init(); }复制 添加以后可以直接在webApi中使用session [HttpGet] publicHttpResponseMessagesetSession() { HttpContext.Current.Session["AA"]="AAAA"; returnreturnStringData("OK"); } [HttpGet] publicHttpResponseMessagegetSession() { HttpContext.Current.Session.Timeout=100;//获取和设置的过期时间,以分钟为单位 //web

  • 洛谷P4568 [JLOI2011]飞行路线(分层图最短路)

    题目传送门 题目大意 有\(n(1\leqn\leq10^4)\)个顶点,\(m(1\leqm\leq5\times10^4)\)条无向边,边权为\(wi(1\leqwi\leq10^3)\),最多可以将其中的\(k(0\leqk\leq10)\)条边的边权变为\(0\),求\(s-t\)的最短路。 思路 这是一道分层图最短路的模板题,有2种解法。 Solution1 建分层图,顶点\(i+j\timesn\)表示进行了\(j\)次变\(0\)操作后到达的\(i\)点,如果原图中有边\(u-v\),则在分层图中有边\((u+j\timesn)-(v+j\timesn)\),权值不变,同时还要连单向边\((u+j\timesn)->(v+(j+1)\timesn)\)和\((v+j\timesn)->(u+(j+1)\timesn)\),边权为0。 \(ans\)即为\(d[t+j\timesn](0\leqj\leqk)\)的最小值。 注意数组一定要开够!!! #include<bits/stdc++.h> usingnamespacestd; #definel

  • Javascript分享笔记

      Javascript 概念:   1.变量:内存中一块存储区域,这块区域中的值是可以改变的,并且我们可以给这块区域取一个名字; 2.对象:对象具有两大特性:1是静态特征2是其功能,当然具体到程序里面对象的静态特征称之为对象的属性,功能称之为对象的方法; 3.执行环境: 1.  全局执行环境:全局执行环境是最外围的执行环境,关闭浏览器或退出浏览器才会被销毁,所有全局变量都是作为Window对象的属性和方法创建的,全局变量和方法(函数)都被视为window对象; 2.局部执行环境:每个函数都有自己的执行环境,称为局部环境,函数执行时会创建函数执行环境,函数执行完毕会被销毁,在里面的函数和变量都是局部的; //执行环境://全局执行环境://1.在一个页面中,第一次载入JS代码时创建一个全局执行环境,//全局执行环境是最外围的执行环境,在Web浏览器中,全局执行环境被认为是window对象。//2.全局执行环境直到应用程序退出,也就是说关闭网页或浏览器时才会被销毁//3.因此,所有的全局变量和函数都是作为window对象的属性和方法创建的

  • python 脚本模拟tcp和udp客户端

    importsocket importtime #client #tcp客户端 client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #udp客户端 #client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) client.connect(('192.168.0.123',7007)) print("connectlocalhost7007...") whileTrue: #sendbuf="{\"a\":8,\"b\":9}'\0'" sendbuf="{\"a\":8,\"b\":9}'\0'" print("send:",sendbuf) client.send(sendbuf.encode('utf-8')) ifnotsendbuforsendbuf=='exit': break time.sleep(1) client.close() print('Connectionwasclosed...')复制  

  • 数据结构与算法复习——7、斐波那契堆和配对堆

    7、更多的堆   前面从二叉堆开始对经典的堆进行了复习,包括左偏树、斜堆和二项队列。这一篇介绍两种更优秀(也很经典)的堆:斐波那契堆和配对堆。 一、斐波那契堆的前置知识   之前提到,如果我们能找到堆中某个结点的位置,调整其键值也是一个非常重要的操作。对小根堆来讲(做为例子),增加键值是很难维持复杂度的,一般只可以减小键值,即Decrease。 我们已经知道,二叉堆和二项队列的Decrease操作都是$O(\logN)$的,但某些时候我们会频繁地需要Decrease,这就使我们期待一个更好的时间复杂度。斐波那契堆就是解决这个问题的一个经典办法。不过在介绍斐波那契堆之前,我们先来介绍两个操作,它们是斐波那契堆的灵感来源。 1、左偏树的Decrease   二叉堆和二项队列都可以用向上过滤来进行Decrease,但左偏树不可以。因为左偏树只保证右路径的长度,它的左路径可以任意长,从而如果对一个左路径上的结点向上过滤,最坏的复杂度将是$O(N)$。为了维持一个好的复杂度,左偏树用切除(Cut)来解决Decrease操作: 当Decrease操作应用于结点$X$时,如果它是树根则不需理会,否则

  • 网站创建(第一)

    在家没事干?10分钟拥有真正意义上属于自己的网站!网站搭建! 网站创建 域名申请相关知识点引入域名申请 服务器服务器购买将域名解析到服务器配置服务器 博客网站程序搭建 域名申请相关 知识点引入 什么是域名?   首先呢!域名是由一串字符组成的,域名会指向某一个ip地址。域名有什么用呢?根本作用就是为了方便记忆,便于用户访问网站 ip地址是什么?   我们这样理解,ip地址是互联网分配给主机的编号,如果我们的电脑可以比作电话的话,那么我们的ip地址你就可以把他当作电话号码。ip和域名之间的关系   一般用户上网的时候互联网就会给电脑或者手机分配一个随机的ip地址,作为上网的门牌号,所以一般可以通过ip地址找到网站。但是一般普通用户的话是禁用从网络上进行访问电脑的,这样做的目的也就是为了安全吧!   在我搭建完阿里云的服务器后,我有这样的疑问,既然每次上网的ip地址是互联网进行随机分配的,那么我购买域名进行解析下来的ip地址岂不是会变?但是后来我关掉

  • BZOJ 3925: [Zjoi2015]地震后的幻想乡

    3925:[Zjoi2015]地震后的幻想乡 TimeLimit: 10Sec  MemoryLimit: 256MBSubmit: 620  Solved: 366[Submit][Status][Discuss] Description  傲娇少女幽香是一个很萌很萌的妹子,而且她非常非常地有爱心,很喜欢为幻想乡的人们做一些自己力所能及的事情来帮助他们。 这不,幻想乡突然发生了地震,所有的道路都崩塌了。现在的首要任务是尽快让幻想乡的交通体系重新建立起来。幻想乡一共有n个地方,那么最快的方法当然是修复n-1条道路将这n个地方都连接起来。 幻想乡这n个地方本来是连通的,一共有m条边。现在这m条边由于地震的关系,全部都毁坏掉了。每条边都有一个修复它需要花费的时间,第i条边所需要的时间为ei。地震发生以后,由于幽香是一位人生经验丰富,见得多了的长者,她根据以前的经验,知道每次地震以后,每个ei会是一个0到1之间均匀分布的随机实数。并且所有ei都是完全独立的。 现在幽香要

相关推荐

推荐阅读