用 Spark 挖掘购物篮关联与序列模式

用 Spark 挖掘购物篮关联与序列模式

频繁模式挖掘通常是分析大型数据集的第一步:找出经常出现的商品、商品组合或事件子序列,再据此观察数据中的规律。Spark 的 DataFrame API 提供两条不同的路线:FP-Growth 面向无序项集,PrefixSpan 面向有先后顺序的序列。二者都使用支持度筛选模式,但输入结构和结果含义不同。

本文译编自 Apache Spark 官方指南 Frequent Pattern Mining,发布维护方为 Apache Software Foundation,原页没有个人作者署名。2026 年 10 月 5 日读取的 latest 页面标为 Spark 4.2.0;本文同时核对了 v4.2.0 的两个完整 Python 示例。示例仅作静态审阅,未安装 Spark、启动作业或执行测试。

FP-Growth 从无序交易项集产生频繁项集、关联规则和预测;PrefixSpan 从有序事件项集产生频繁子序列,二者各自使用支持度阈值。
原创技术示意图:先区分交易内共同出现与事件先后顺序,再选择算法。

FP-Growth:不显式枚举候选项集

FP 是 frequent pattern 的缩写。Han 等人的论文《Mining frequent patterns without candidate generation》描述了 FP-Growth:先统计交易数据中各个项目的出现次数,找出频繁项目,再用 FP-tree 紧凑表示交易,最后从树中提取频繁项集。与 Apriori 一类方法不同,它不必显式生成代价高昂的候选集合。

Spark 文档将 FP-tree 称为 suffix tree;这里按算法常用名称称为“频繁模式树”,避免与字符串算法中的后缀树混淆。Spark 的 RDD 库 spark.mllib 实现了并行版本 PFP,参考 Li 等人的《PFP: Parallel FP-growth for query recommendation》。它根据交易后缀分配生长 FP-tree 的工作,以便在多机环境扩展;这不是对任意数据都能获得某个加速比的保证。下面使用的是 spark.ml 的 DataFrame 接口。

FP-Growth 的一个输入项集,是无序且元素唯一的集合。Spark 没有集合列类型,因此用数组表示。数组位置不代表购买顺序,同一交易内也不应把同一商品重复写入;需要次数或时间的信息,应在建模时另作处理。

参数 含义 容易混淆的边界
minSupport 一个项集成为频繁项集所需的最小支持度。某商品在 5 笔交易中出现 3 次,支持度是 3/5=0.6。 分母是交易数,不是所有商品出现次数之和。
minConfidence 从频繁项集生成关联规则时所需的最小置信度。X 出现 4 次,X 与 Y 共同出现 2 次,则 X ⇒ Y 的置信度为 2/4=0.5。 它过滤规则,不改变频繁项集挖掘结果。
numPartitions 分配挖掘工作所用的分区数。 未设置时,使用输入数据集的分区数;需要结合实际 executor 资源调节。

模型提供的三种结果

FPGrowthModel.freqItemsets 返回包含 items: array 和 freq: long 的 DataFrame。前者是项集,后者是它在交易中出现的次数。

associationRules 返回达到置信度阈值的规则。其中 antecedent 是前件,consequent 是后件,Spark 的后件数组始终只包含一个元素。其余三列是 confidence、lift 与 support:

confidence(X ⇒ Y) = support(X ∪ Y) / support(X)
lift(X ⇒ Y) = support(X ∪ Y) / (support(X) × support(Y))
support(X ⇒ Y) = support(X ∪ Y)

提升度把共同出现程度与两者各自的常见程度比较,不能把高置信度直接理解为很强的额外预测能力。例如 Y 本身几乎总出现,即使 X 与 Y 没有额外关系,X ⇒ Y 的置信度仍可能很高。关联规则描述观察到的共现,不证明 X 导致 Y。

transform 会检查每条交易:如果交易包含某条规则的全部前件,就把该规则的后件加入预测候选;对所有适用规则汇总后,去掉交易中已存在的项目。输出预测列的数据类型与 itemsCol 一致。它是规则补全,不是带校准概率的分类器,也不等同于已经评估过的个性化推荐系统。

完整 Python 示例:交易项集、规则与预测

下面保留官方示例的数据与参数,并把初始化、导入和结束会话放在同一段,补齐指南页面省略的运行上下文;注释改为中文。代码源于 ASF,适用 Apache License 2.0,修改是中文注释与版式整理。

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file for ownership.
# Licensed under the Apache License, Version 2.0:
# https://www.apache.org/licenses/LICENSE-2.0
# Provided on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
from pyspark.ml.fpm import FPGrowth
from pyspark.sql import SparkSession

if __name__ == "__main__":
    spark = SparkSession.builder.appName("FPGrowthExample").getOrCreate()

    df = spark.createDataFrame([
        (0, [1, 2, 5]),
        (1, [1, 2, 3, 5]),
        (2, [1, 2])
    ], ["id", "items"])

    fpGrowth = FPGrowth(itemsCol="items", minSupport=0.5, minConfidence=0.6)
    model = fpGrowth.fit(df)

    model.freqItemsets.show()
    model.associationRules.show()
    model.transform(df).show()
    spark.stop()

这里共有 3 笔交易,支持度门槛 0.5 意味着项集至少出现在 2 笔交易中。直接从输入可数出:1 与 2 各出现 3 次,5 出现 2 次,3 只出现 1 次,所以 3 不会进入频繁项集;{1,2,5} 出现 2 次,能够通过门槛。规则 {1,2} ⇒ {5} 的置信度为 2/3,大于 0.6,所以最后一笔只含 {1,2} 的交易可得到项目 5 的补全。这些是根据小数据手工推导的解释,不是本文运行 show() 得到的测试输出;分布式结果的行序也不应当作为正确性判断依据。

官方完整文件为 fpgrowth_example.py,指南还提供 Scala、Java 与 R 的等价调用,原始代码见文末附录。Python API 详见 FPGrowth API。

PrefixSpan:频繁子序列保留事件顺序

购物篮只关心同一交易里有什么;如果问题变成“先出现哪些事件,之后又出现哪些事件”,就需要序列模式挖掘。PrefixSpan 参考 Pei 等人的《Mining Sequential Patterns by Pattern-Growth: The PrefixSpan Approach》,通过前缀投影逐步增长模式。

输入由“序列的数组”组成:每行是一个序列,序列里的每个元素又是一个项集。因此 [[1,2],[3]] 表示先发生包含 1、2 的事件,再发生包含 3 的事件;[[1],[2,3]] 与它含义不同。外层顺序不能丢,内层仍按项集理解。

参数 作用
minSupport 成为频繁序列模式所需的最小支持度。
maxPatternLength 频繁序列模式的最大长度;超过该限制的模式不进入结果。模式长度按项目数量计算,不要与输入序列条数混淆。
maxLocalProjDBSize 前缀投影数据库可转入本地迭代处理的最大项目数量阈值,需要结合 executor 的可用资源调节;它不是字节数或硬性内存上限。
sequenceCol 序列列名,默认 sequence。该列为 null 的行会被忽略。
# ASF / Apache License 2.0; 中文注释与版式整理
from pyspark.ml.fpm import PrefixSpan
from pyspark.sql import Row, SparkSession

if __name__ == "__main__":
    spark = SparkSession.builder.appName("PrefixSpanExample").getOrCreate()
    sc = spark.sparkContext
    df = sc.parallelize([
        Row(sequence=[[1, 2], [3]]),
        Row(sequence=[[1], [3, 2], [1, 2]]),
        Row(sequence=[[1, 2], [5]]),
        Row(sequence=[[6]])
    ]).toDF()

    prefixSpan = PrefixSpan(
        minSupport=0.5,
        maxPatternLength=5,
        maxLocalProjDBSize=32000000
    )
    prefixSpan.findFrequentSequentialPatterns(df).show()
    spark.stop()

本例有 4 条序列,因此 0.5 的支持度要求模式至少出现在 2 条序列中。例如先有 1、后有 3 的模式 [[1],[3]] 在前两条序列中成立。模式在同一条序列中出现多次,不表示它贡献多条独立输入序列。返回结果是模式与出现频次,并不自动构成 FP-Growth 那样的关联规则预测模型。

官方完整文件为 prefixspan_example.py,参数定义见 PrefixSpan API。

从教学数据换到业务数据时的边界

先确定“交易”和“序列”的业务边界:同一用户的一天、一张订单和一次会话不是等价单位。FP-Growth 输入项集应去重;序列应先按可信时间或业务顺序组装,而不是依赖 DataFrame 的偶然行序。缺失值和空输入需要在数据质量步骤中明确处置,不能把 PrefixSpan 忽略 null 行误解为数据已自动修复。

很低的支持度、很长的序列和很大的单笔项集,都可能带来大量组合与内存压力。先用范围受控的数据观察模式数,再调整分区、支持度和长度限制。maxLocalProjDBSize 应配合 executor 资源观察,不能把文档中的 32000000 当成所有集群的安全默认值。

本次静态审查的示例使用内嵌数据,没有外部文件、SQL 字符串拼接、硬编码凭据或破坏性命令;未发现这些具体风险并不等于代码或依赖没有漏洞。getOrCreate() 会采用已有会话与环境配置,因此若自行演练,应显式确认使用隔离的本地环境,避免无意连接共享或生产集群。本文没有执行官方给出的 spark-submit 命令,也没有作吞吐或内存测试。

来源与许可

正文依据 Apache Spark 官方指南完整页面翻译、整理,并增加了术语澄清、手工推导与安全边界。原论文作者与链接如上保留。示例代码仍遵循 Apache License 2.0;完整许可证和 Apache Spark v4.2.0 NOTICE 见随稿文件及本页附录。本文配图为未完纪原创示意图,不是运行截图。

附录:官方 Scala、Java 与 R 示例

下列为同一源页的其他语言版本,保留原注释与代码,未执行;它们与前文 Python 示例表达相同的数据和参数。原文中的会话变量由对应 Spark 环境提供。

Scala / FP-Growth
import org.apache.spark.ml.fpm.FPGrowth

val dataset = spark.createDataset(Seq(
  "1 2 5",
  "1 2 3 5",
  "1 2")
).map(t => t.split(" ")).toDF("items")

val fpgrowth = new FPGrowth().setItemsCol("items").setMinSupport(0.5).setMinConfidence(0.6)
val model = fpgrowth.fit(dataset)

// Display frequent itemsets.
model.freqItemsets.show()

// Display generated association rules.
model.associationRules.show()

// transform examines the input items against all the association rules and summarize the
// consequents as prediction
model.transform(dataset).show()
Java / FP-Growth
import java.util.Arrays;
import java.util.List;

import org.apache.spark.ml.fpm.FPGrowth;
import org.apache.spark.ml.fpm.FPGrowthModel;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.types.*;

List<Row> data = Arrays.asList(
  RowFactory.create(Arrays.asList("1 2 5".split(" "))),
  RowFactory.create(Arrays.asList("1 2 3 5".split(" "))),
  RowFactory.create(Arrays.asList("1 2".split(" ")))
);
StructType schema = new StructType(new StructField[]{ new StructField(
  "items", new ArrayType(DataTypes.StringType, true), false, Metadata.empty())
});
Dataset<Row> itemsDF = spark.createDataFrame(data, schema);

FPGrowthModel model = new FPGrowth()
  .setItemsCol("items")
  .setMinSupport(0.5)
  .setMinConfidence(0.6)
  .fit(itemsDF);

// Display frequent itemsets.
model.freqItemsets().show();

// Display generated association rules.
model.associationRules().show();

// transform examines the input items against all the association rules and summarize the
// consequents as prediction
model.transform(itemsDF).show();
R / FP-Growth
# Load training data

df <- selectExpr(createDataFrame(data.frame(rawItems = c(
  "1,2,5", "1,2,3,5", "1,2"
))), "split(rawItems, ',') AS items")

fpm <- spark.fpGrowth(df, itemsCol="items", minSupport=0.5, minConfidence=0.6)

# Extracting frequent itemsets

spark.freqItemsets(fpm)

# Extracting association rules

spark.associationRules(fpm)

# Predict uses association rules to and combines possible consequents

predict(fpm, df)
Scala / PrefixSpan
import org.apache.spark.ml.fpm.PrefixSpan

val smallTestData = Seq(
  Seq(Seq(1, 2), Seq(3)),
  Seq(Seq(1), Seq(3, 2), Seq(1, 2)),
  Seq(Seq(1, 2), Seq(5)),
  Seq(Seq(6)))

val df = smallTestData.toDF("sequence")
val result = new PrefixSpan()
  .setMinSupport(0.5)
  .setMaxPatternLength(5)
  .setMaxLocalProjDBSize(32000000)
  .findFrequentSequentialPatterns(df)
  .show()
Java / PrefixSpan
import java.util.Arrays;
import java.util.List;

import org.apache.spark.ml.fpm.PrefixSpan;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.types.*;

List<Row> data = Arrays.asList(
  RowFactory.create(Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3))),
  RowFactory.create(Arrays.asList(Arrays.asList(1), Arrays.asList(3, 2), Arrays.asList(1,2))),
  RowFactory.create(Arrays.asList(Arrays.asList(1, 2), Arrays.asList(5))),
  RowFactory.create(Arrays.asList(Arrays.asList(6)))
);
StructType schema = new StructType(new StructField[]{ new StructField(
  "sequence", new ArrayType(new ArrayType(DataTypes.IntegerType, true), true),
  false, Metadata.empty())
});
Dataset<Row> sequenceDF = spark.createDataFrame(data, schema);

PrefixSpan prefixSpan = new PrefixSpan().setMinSupport(0.5).setMaxPatternLength(5);

// Finding frequent sequential patterns
prefixSpan.findFrequentSequentialPatterns(sequenceDF).show();
R / PrefixSpan
# Load training data

df <- createDataFrame(list(list(list(list(1L, 2L), list(3L))),
                           list(list(list(1L), list(3L, 2L), list(1L, 2L))),
                           list(list(list(1L, 2L), list(5L))),
                           list(list(list(6L)))),
                      schema = c("sequence"))

# Finding frequent sequential patterns
frequency <- spark.findFrequentSequentialPatterns(df, minSupport = 0.5, maxPatternLength = 5L,
                                                  maxLocalProjDBSize = 32000000L)
showDF(frequency)

附录:Apache 许可证与 NOTICE

本文包含 Apache Spark v4.2.0 官方文档代码示例的译编。以下为适用的许可证文本及官方 NOTICE;本页目录中也随附 LICENSE-2.0.txt 与 NOTICE.txt。

Apache License 2.0

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

Apache Spark NOTICE(v4.2.0)

Apache Spark
Copyright 2014 and onwards The Apache Software Foundation.

This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).


Export Control Notice
---------------------
This distribution includes cryptographic software. The country in which you currently reside may have
restrictions on the import, possession, use, and/or re-export to another country, of encryption software.
BEFORE using any encryption software, please check your country's laws, regulations and policies concerning
the import, possession, or use, and re-export of encryption software, to see if this is permitted. See
<http://www.wassenaar.org/> for more information.
The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this
software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software
using or performing cryptographic functions with asymmetric algorithms. The form and manner of this Apache
Software Foundation distribution makes it eligible for export under the License Exception ENC Technology
Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for
both object code and source code.
The following provides more details on the included cryptographic software:

This software uses Apache Commons Crypto (https://commons.apache.org/proper/commons-crypto/) to
support authentication, and encryption and decryption of data sent across the network between
services.


Metrics
Copyright 2010-2013 Coda Hale and Yammer, Inc.

This product includes software developed by Coda Hale and Yammer, Inc.
This product includes code derived from the JSR-166 project (ThreadLocalRandom, Striped64,
LongAdder), which was released with the following comments:

    Written by Doug Lea with assistance from members of JCP JSR-166
    Expert Group and released to the public domain, as explained at
    http://creativecommons.org/publicdomain/zero/1.0/
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容