写点什么

GS Collections 实例教程(第二部分)

  • 2015-01-23
  • 本文字数:9298 字

    阅读完需:约 31 分钟

GS Collections 实例教程(第一部分)中,我展示了几种运用 select 和 selectWith 的方法去筛选一个集合。

要调用方法 select,我们通常会传递一个 Lambda 表达式的 Predicate。同样来说,如果要调用方法 selectWith,我们会传递一个 Predicate2 作为方法引用。

在 GS Collections 中有许多方法接受 Predicate 参数,比如说 select、reject、detect,、anySatisfy、allSatisfy、noneSatisfy、count 以及 partition。

同样,也有许多方法接受 Predicate2 参数,例如 selectWith、rejectWith、detectWith、anySatisfyWith、allSatisfyWith、noneSatisfyWith、countWith 以及 partitionWith。

在实例教程(第二部分)中,我们会深入地研究对 Predicates 的调用和使用 Function 作为参数的转化性方法(例如 collect、flatCollect、groupBy、groupByEach)。

接下来,我们会展示一些例子,演示采用 GS Collections 的 API 去转化 object container 成为 primitive container,以及如何使用这些 primitive container。这些例子都是基于以下这个简单的领域模型。

以下例子为单元测试,须在 Java 8 下运行。

希望这些例子能激发你的兴趣,去深入探索 GS Collections 里面完善和丰富的 API。

例子 2:集合里面任意一个元素满足所给的条件吗?

使用 anySatisfy:

复制代码
@Test
public void doAnyPeopleHaveCats()
{
Predicate<person> predicate = person -> person.hasPet(PetType.CAT);
boolean result =
this.people.anySatisfy(predicate);
Assert.assertTrue(result);
boolean result1 =
this.people.anySatisfyWith(Person::hasPet, PetType.CAT);
Assert.assertTrue(result1);
}</person>

例子 3:集合里面所有元素满足所给的条件吗?

使用 allSatisfy:

复制代码
@Test
public void doAllPeopleHaveCats()
{
boolean result =
this.people.allSatisfy(person -> person.hasPet(PetType.CAT));
Assert.assertFalse(result);
boolean result1 =
this.people.allSatisfyWith(Person::hasPet, PetType.CAT);
Assert.assertFalse(result1);
}

例子 4:集合里面没有元素满足所给的条件吗?

使用 noneSatisfy:

复制代码
@Test
public void doNoPeopleHaveCats()
{
boolean result =
this.people.noneSatisfy(person -> person.hasPet(PetType.CAT));
Assert.assertFalse(result);
boolean result1 =
this.people.noneSatisfyWith(Person::hasPet, PetType.CAT);
Assert.assertFalse(result1);
}

例子 5:集合里面有多少元素满足所给的条件。

使用 count:

复制代码
@Test
public void howManyPeopleHaveCats()
{
int count =
this.people.count(person -> person.hasPet(PetType.CAT));
Assert.assertEquals(2, count);
int count1 =
this.people.countWith(Person::hasPet, PetType.CAT);
Assert.assertEquals(2, count1);
}

例子 6:寻找集合里面满足所给条件的第一个元素。

使用 detect:

复制代码
@Test
public void findPersonNamedMarySmith()
{
Person result =
this.people.detect(person -> person.named("Mary Smith"));
Assert.assertEquals("Mary", result.getFirstName());
Assert.assertEquals("Smith", result.getLastName());
Person result1 =
this.people.detectWith(Person::named, "Mary Smith");
Assert.assertEquals("Mary", result1.getFirstName());
Assert.assertEquals("Smith", result1.getLastName());
}

any、all、noneSatisfy 以及 detect 都是短路求值方法的例子,它们可以不用遍历每一个集合里的元素就可返回结果。比如说,anySatisfy 就像逻辑中的“”,只要找到一个满足条件的元素它就会判断为是,否则将检视整个集合而判断为否。

以下的例子也接受 predicates , 但不会进行短路操作。

例子 7:选取集合里面满足所给条件的所有元素。

在 GS Collections 实例教程(第一部分)中展示了许多这方面的例子。以下是利用 Person/Pet 领域对这个方法的回顾。用来过滤集合的方法称为 select。

复制代码
@Test
public void getPeopleWithCats()
{
MutableList<person> peopleWithCats =
this.people.select(person -> person.hasPet(PetType.CAT))
Verify.assertSize(2, peopleWithCats);
MutableList<person> peopleWithCats1 =
this.people.selectWith(Person::hasPet, PetType.CAT);
Verify.assertSize(2, peopleWithCats1);
}</person></person>

例子 8:寻找集合里所有不满足所给条件的元素。

使用 reject:

复制代码
@Test
public void getPeopleWhoDontHaveCats()
{
MutableList<person><person><person> peopleWithNoCats =
this.people.reject(person -> person.hasPet(PetType.CAT));
Verify.assertSize(5, peopleWithNoCats);
MutableList<person> peopleWithNoCats1 =
this.people.rejectWith(Person::hasPet, PetType.CAT);
Verify.assertSize(5, peopleWithNoCats1);
}</person></person></person></person>

例子 9:分隔集合里满足所给条件的元素和不满足所给条件的元素。

使用 partition:

复制代码
@Test
public void partitionPeopleByCatOwnersAndNonCatOwners()
{
PartitionMutableList<person><person><person><person><person> catsAndNoCats =
this.people.partition(person -> person.hasPet(PetType.CAT));
Verify.assertSize(2, catsAndNoCats.getSelected());
Verify.assertSize(5, catsAndNoCats.getRejected());
PartitionMutableList<person> catsAndNoCats1 =
this.people.partitionWith(Person::hasPet, PetType.CAT);
Verify.assertSize(2, catsAndNoCats1.getSelected());
Verify.assertSize(5, catsAndNoCats1.getRejected());
}</person></person></person></person></person></person>

在以上例子中,返回类型是特殊的 PartitionMutableList,它的父接口是 PartitionIterable。这个接口包含了两个方法:getSelected 和 getRejected。这些方法与 PartitionIterable 的子类型是共变的,所以对于 PartitionMutableList 来说,这两个方法的返回值是 MutableList。对于父类型 PartitionIterable 来说,它的返回值将会是 RichIterable。

以上总结了调用 Predicate 和 Predicate2 作为参数的 API。接下来让我们看看调用 Function 和 Function2 作为参数的 API。

例子 10:将一个集合从一种类型转化为另一种类型。

使用 collect:

复制代码
@Test
public void getTheNamesOfBobSmithPets()
{
Person person =
this.people.detectWith(Person::named, "Bob Smith");
MutableList<pet><person><person><person><person><person><person> pets = person.getPets();
MutableList<string> names =
pets.collect(Pet::getName);
Assert.assertEquals("Dolly, Spot", names.makeString());
}</string></person></person></person></person></person></person></pet>

在以上的例子中,我们先找到叫“Bob Smith”的人,取得他宠物的列表,然后再从列表中搜集宠物的名字,最终转化成为 MutableList。

例子 11:根据一个特性来展平一个集合。

如果你想从一个集合的集合中收集一个属性作为一个统一的集合,应当使用 flatCollect。

复制代码
@Test
public void getAllPets()
{
Function<Person, Iterable<PetType>> function = person ->
person.getPetTypes();
Assert.assertEquals(
UnifiedSet.newSetWith(PetType.values()),
this.people.flatCollect(function).toSet()
);
Assert.assertEquals(
UnifiedSet.newSetWith(PetType.values()),
this.people.flatCollect(Person::getPetTypes).toSet()
);
}

在第一个例子中,为了解释 flatCollect 期待的类型,我抽取了 Lambda 作为一个单独的变量。Collect 方法采用 Function<? super T, ? extends V> flatCollect 方法则采用 Function<? super T, ? extends Iterable>。换句话来说,传入 flatCollect 的 Function 必须返回 iterable 类型。

例子 12:根据某种 function 对一个集合进行分类。

使用 groupBy:

复制代码
@Test
public void groupPeopleByLastName()
{
Multimap<String, Person> byLastName =
this.people.groupBy(Person::getLastName);
Verify.assertIterableSize(3, byLastName.get("Smith"));
}

在上述的例子中,根据大家的姓氏进行分类。这个分类表明有 3 个人姓 Smith。groupBy 方法的返回值是 Multimap。Multimap 可以被想成等同于 Map<K, Iterable>。在 GS Collections 中有许多特殊化定义的 Multimap(例如 List、Set、 Bag、SortedBag、SortedSet),它们既有可变的也有不可变的类型。这个例子里使用了父类型 Multimap,但是也可以用特殊化的 ListMultimap 或者 MutableListMultimap。

例子 13:根据返回值是多个元素的 function 对一个集合进行分类。

使用 groupByEach:

复制代码
@Test
public void groupPeopleByTheirPets()
{
Multimap<PetType, Person> peopleByPets =
this.people.groupByEach(Person::getPetTypes);
RichIterable<Person> catPeople = peopleByPets.get(PetType.CAT);
Assert.assertEquals(
"Mary, Bob",
catPeople.collect(Person::getFirstName).makeString()
);
RichIterable<Person> dogPeople = peopleByPets.get(PetType.DOG);
Assert.assertEquals(
"Bob, Ted",
dogPeople.collect(Person::getFirstName).makeString()
);
}

类似于 flatCollect,groupByEach 方法采用了一个 Function<? super T, ? extends Iterable>。返回值 Multimap<K, T> 实际上就是一个多重索引,其中每个值都可以有多个索引。在这个例子中,我们根据大家所拥有的宠物种类对人进行分类。一个人可以拥有多种宠物,正是因为这个原因 Bob 同时出现在两个字串中。我在这里使用了 collect 把每个 Person 转换成他们的名字,然后使用 makeString 去转换成一个以逗号分隔的字串。makeString 还有其他的形式可以接受分隔符号、开始和末尾字符作为参数。

例子 14:对于基本类别的返回值进行求和。

可以使用 RichIterable 的四种 sumOf 方法之一:sumOfInt、sumOfFloat、sumOfLong 以及 sumOfDouble。

复制代码
@Test
public void getTotalNumberOfPets()
{
long numberOfPets = this.people.sumOfInt(Person::getNumberOfPets);
Assert.assertEquals(9, numberOfPets);
}

在以上的例子中,我们对每个人所拥有的宠物数量进行求和,结果是所有人所拥有的宠物的总和。如果是 ints 和 floats 求和,返回值是更大的类型 long 和 double。sumOfInt 方法采用了一个特殊形式的 Function 叫做 IntFunction。

复制代码
<a b3="" dict.youdao.com="" href="http:=" wiki="">
public interface IntFunction<T>
extends Serializable
{
int intValueOf(T anObject);
}
</a>

GS Collections 里所有的 Procedures,Functions,Predicates 都继承了 Serializable。这样一来,程序员不用编写自己的 Serializable 扩展就可以安全地序列化到磁盘上或是远程传递。

例子 15:对象集合(object collection)和原始集合(primitive collection)转化的流畅性。

如果你想从一个对象集合转化为原始集合,你可以使用任何一个 8 种专业化原始类型的 collect (collectInt / Float / Long / Double / Byte / Short / Char / Boolean) 。你可以只使用 collect 把一个原始集合转化为对象集合。这个功能为使用 API 增加了流畅性。

复制代码
@Test
public void getAgesOfPets()
{
IntList sortedAges =
this.people
.asLazy()
.flatCollect(Person::getPets)
.collectInt(Pet::getAge)
.toSortedList();
IntSet uniqueAges = sortedAges.toSet();
IntSummaryStatistics stats = new IntSummaryStatistics();
sortedAges.forEach(stats::accept);
Assert.assertTrue(sortedAges.allSatisfy(IntPredicates.greaterThan(0)));
Assert.assertTrue(sortedAges.allSatisfy(i -> i > 0));
Assert.assertFalse(sortedAges.anySatisfy(i -> i == 0));
Assert.assertTrue(sortedAges.noneSatisfy(i -> i < 0));
Assert.assertEquals(IntHashSet.newSetWith(1, 2, 3, 4), uniqueAges);
Assert.assertEquals(2.0d, sortedAges.median(), 0.0);
Assert.assertEquals(stats.getMin(), sortedAges.min());
Assert.assertEquals(stats.getMax(), sortedAges.max());
Assert.assertEquals(stats.getSum(), sortedAges.sum());
Assert.assertEquals(stats.getAverage(), sortedAges.average(), 0.0);
Assert.assertEquals(stats.getCount(), sortedAges.size());
}

在这个例子里面,我回答了许多关于名单上的人所拥有宠物的年龄问题。首先,我使用了 asLazy() 方法,这是一个谨慎的决定,因为我想尽量减少临时集合的数量。当然如果拿掉 asLazy() 的调用,上面的代码还是可以运行的。在这里 asLazy() 方法的调用完全是为了优化内存。然后,我调用了 flatCollect,这个方法把所有人的宠物收集在一个平展的集合中。接着,我调用了 collectInt,这个方法把 LazyIterable转化为 IntIterable。每一个 Pet 通过 getAge() 方法被转化成它的年纪。如果之前没有使用 asLazy() 方法那将会把一个 MutableList转化为 IntList。最后我调用了 toSortedList,这个方法接受 IntInterable 然后转化为一个 IntList 最后给 ints 排序。在 IntList 上调用 toSet 方法然后保存年龄作为一个 set。

以上展示了 GS Collections 里面原始集合的丰富性。在集合上还可以直接调用统计方法像 min、max、sum、average、median。在这里我还在 IntList 里使用了 Java 8 新的统计类叫 IntSummaryStatistics 。这是通过对 IntSumaryStatistics::accept 的方法引用而实现的。这个例子里还使用了之前例子中展示过的 any、all、noneSatisfy 方法, 只不过在这里它们被用于一个原始集合中。

例子 16:计算某个元素在一个集合的出现次数。

如果你想迅速的找出各个元素在集合里面的数量,可以把集合转化为一个 Bag。Bag 可以等同于 Map<K, Integer>, 在这里 Integer 是元素 K 的出现次数。Bag 像其它的集合一样有 add、remove 的方法,它还有特殊的方法去计算、加减一个元素的出现次数。Bag 就像一个允许重复元素的 Set 而且还保存了每个元素的出现次数。

复制代码
@Test
public void getCountsByPetType()
{
Bag<PetType> counts =
this.people
.asLazy()
.flatCollect(Person::getPets)
.collect(Pet::getType)
.toBag();
Assert.assertEquals(2, counts.occurrencesOf(PetType.CAT));
Assert.assertEquals(2, counts.occurrencesOf(PetType.DOG));
Assert.assertEquals(2, counts.occurrencesOf(PetType.HAMSTER));
Assert.assertEquals(1, counts.occurrencesOf(PetType.SNAKE));
Assert.assertEquals(1, counts.occurrencesOf(PetType.TURTLE));
Assert.assertEquals(1, counts.occurrencesOf(PetType.BIRD));
}

在当今的 JDK 里面没有像 Bag 一样的类型,同样的也缺少像 PartitionIterable 或是 Multimap。在 Java 8 Collectors 的方法中,以上的类型被模拟为 Map<K, Integer> (Collectors.counting), Map<Boolean, List (Collectors.partitioning) 以及 Map<K, List> (Collectors.groupingBy)。

例子 17:计算某个原始值在一个集合的出现次数。

如果你想找出某个原始值在集合里面的数量,可以使用 primitive Bag。

复制代码
@Test
public void getCountsByPetAge()
{
IntBag counts =
this.people
.asLazy()
.flatCollect(Person::getPets)
.collectInt(Pet::getAge)
.toBag();
Assert.assertEquals(4, counts.occurrencesOf(1));
Assert.assertEquals(3, counts.occurrencesOf(2));
Assert.assertEquals(1, counts.occurrencesOf(3));
Assert.assertEquals(1, counts.occurrencesOf(4));
Assert.assertEquals(0, counts.occurrencesOf(5));
}

在这个例子中,我从所有宠物的年龄集中创建了一个 IntBag。这样一来就可以使用 IntBag 附带的 occurencesOf 方法找出每一年龄的出现次数。这是实例教程(第二部分)的最后一个例子。

这些例子提供了一个小样本,演示了用 GS Collections 的 API 可以做什么样的事情。现在 RichIterable 接口有一百多个方法可供使用。这为 Java 开发员提供了一套极为丰富的功能去处理集合的问题。

在 2014 JavaOne 大会上, Craig Motlin 和我在“GS Collections and Java 8: Functional, Fluent, Friendly and Fun!”座谈会上讲解了几个例子去比较如何使用 Java 8 Stream 和 GS Collections 做相同的事情。你可以在 JavaOne 2014 大会网站 或是 GS Collections GitHub wiki 上找到这些演讲资料。

仅供参考,以下代码包含了前面的例子中所提到的各种测试类,你可以用来创建自己测试代码。

复制代码
import com.gs.collections.api.RichIterable;
import com.gs.collections.api.bag.Bag;
import com.gs.collections.api.bag.MutableBag;
import com.gs.collections.api.bag.primitive.IntBag;
import com.gs.collections.api.block.function.Function;
import com.gs.collections.api.block.predicate.Predicate;
import com.gs.collections.api.list.MutableList;
import com.gs.collections.api.list.primitive.IntList;
import com.gs.collections.api.multimap.Multimap;
import com.gs.collections.api.partition.list.PartitionMutableList;
import com.gs.collections.api.set.primitive.IntSet;
import com.gs.collections.impl.bag.mutable.HashBag;
import com.gs.collections.impl.block.factory.Predicates2;
import com.gs.collections.impl.block.factory.primitive.IntPredicates;
import com.gs.collections.impl.list.mutable.FastList;
import com.gs.collections.impl.set.mutable.UnifiedSet;
import com.gs.collections.impl.set.mutable.primitive.IntHashSet;
import com.gs.collections.impl.test.Verify
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.IntSummaryStatistics;
public class PersonTest
{
MutableList<Person> people;
@Before
public void setUp() throws Exception
{
this.people = FastList.newListWith(
new Person("Mary", "Smith").addPet(PetType.CAT, "Tabby", 2),
new Person("Bob", "Smith").addPet(PetType.CAT, "Dolly",
3).addPet(PetType.DOG, "Spot", 2),
new Person("Ted", "Smith").addPet(PetType.DOG, "Spike", 4),
new Person("Jake", "Snake").addPet(PetType.SNAKE, "Serpy", 1),
new Person("Barry", "Bird").addPet(PetType.BIRD, "Tweety", 2),
new Person("Terry", "Turtle").addPet(PetType.TURTLE, "Speedy",
1)
new Person("Harry", "Hamster").addPet(PetType.HAMSTER, "Fuzzy",
1).addPet(PetType.HAMSTER, "Wuzzy", 1)
);
}
public class Person
{
private String firstName;
private String lastName;
private MutableList<Pet> pets = FastList.newList();
private Person(String firstName, String lastName)
{
this.firstName = firstName;
this.lastName = lastName;
public String getFirstName()
{
return this.firstName;
}
public String getLastName()
{
return this.lastName;
}
public boolean named(String name)
{
return name.equals(this.getFirstName() + " " +
this.getLastName());
}
public boolean hasPet(PetType petType)
{
return
this.pets.anySatisfyWith(Predicates2.attributeEqual(Pet::getType), petType);
}
public MutableList<Pet> getPets()
{
return this.pets;
}
public MutableBag<PetType> getPetTypes()
{
return this.pets.collect(Pet::getType, HashBag.newBag());
}
public Person addPet(PetType petType, String name, int age)
{
this.pets.add(new Pet(petType, name, age));
return this;
}
public int getNumberOfPets()
{
return this.pets.size();
}
}
public class Pet
{
private PetType type;
private String name;
private int age;
public Pet(PetType type, String name, int age)
this.type = type;
this.name = name;
this.age = age;
}
public PetType getType()
{
return this.type;
}
public String getName()
{
return this.name;
}
public int getAge()
{
return this.age;
}
}
public enum PetType
{
CAT, DOG, HAMSTER, TURTLE, BIRD, SNAKE
}
}

关于作者

Donald Raab在高盛的信息技术部领导 JVM Architecture 小组,该小组隶属于 Enterprise Platforms 团队。Raab 是 JSR 335 专家组(Java 编程语言的 Lambda Expressions)的成员, 并且是高盛在 JCP(Java Community Process)执行委员会的代表之一。他于 2001 年作为技术架构师加入高盛信息技术部的会计 & 风险分析组。他在 2007 年被授予高盛的 Technology Fellow 头衔,并在 2013 年成为董事总经理。

译者钟 **** 新(Josaline Zhong), 现任高盛信息技术部的资产管理合规技术组副经理。作为 GS Collections 的贡献者和内部讲师之一,一直对 GS Collections 的推广和应用充满高度热忱。

请访问 www.gs.com/engineering 获取关于 GS Collections 和高盛信息技术部的更多信息。

披露

本文章反映的信息仅为高盛信息技所有,并非高盛其他部所持信息。其不得被依或被视为。除非明确标识,其表达点并非一定高盛所持点。高盛公司不担保、不保本文章的精确、完整或效用。接收者不本文章,除非在自担风险内。在未刊声明的情形下,本文章不得被转发、披露


感谢丁晓昀对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2015-01-23 22:112657

评论

发布
暂无评论
发现更多内容

技术管理 之 跨功能需求管理

码猿外

技术管理 非功能性需求 跨功能性需求

云原生安全系列 4:6个 Kubernetes 安全最佳实践

HummerCloud

Kubernetes 云原生安全

基于云基础设施快速部署 RocketMQ 5.0 集群

Apache RocketMQ

RocketMQ 云原生 消息队列

TiDB 6.5 LTS 发布 企业级关键能力跃升

极客天地

一文了解华为FusionInsight MRS HBase的集群隔离方案RSGroup

华为云开发者联盟

大数据 后端 华为云 企业号 1 月 PK 榜

基于开源体系的云原生微服务治理实践与探索

阿里巴巴云原生

阿里云 开源 云原生 service mesh

前端二面手写面试题总结

helloworld1024fd

JavaScript

2022大厂投资盘点:最大的投资就是减少投资

ToB行业头条

DTALK直播预约 | 金融行业嘉宾分享:金融机构数据治理实践路径

袋鼠云数栈

JS继承有哪些,你能否手写其中一两种呢?

helloworld1024fd

JavaScript

极狐GitLab与欧拉操作系统完成兼容认证,开源产业自主创新再突破!

openEuler

Linux 开源 操作系统 openEuler 资讯

高并发环境下构建缓存服务,你需要注意这6点

华为云开发者联盟

高并发 开发 华为云 企业号 1 月 PK 榜

强强联手 | 尚硅谷&腾讯云EMR离线数仓教程发布

小谷哥

【双机热备小知识】两台服务器可以做双机热备吗?

行云管家

高可用 双机热备

喜报!SelectDB 携手中航信移动科技有限公司、四川大数据技术服务中心,双双入选大数据“星河(Galaxy)”优秀案例

SelectDB

数据库 大数据 数据湖 云原生 云上架构

旅游业复苏在即,区块链赋能智慧旅游新体验

旺链科技

区块链 区块链技术 区块链技术应用

2022 Apache APISIX 年度记忆

API7.ai 技术团队

api 网关 APISIX 年终盘点 apache 社区

ChatGPT的一小步,NLP范式转变的一大步

OneFlow

人工智能 深度学习

OneFlow源码解析:静态图与运行时

OneFlow

人工智能 深度学习

程序员面试中一面、二面、三面有什么区别?

小小怪下士

Java 程序员 java面试

2023年AI十大展望:GPT-4领衔大模型变革,谷歌拉响警报,训练数据告急

OneFlow

人工智能 深度学习

双机热备的优点简单分析-行云管家

行云管家

高可用 双机热备

软件测试/测试开发丨iOS 自动化测试踩坑(一): 技术方案、环境配置与落地实践

测试人

ios xcode 软件测试 自动化测试 测试开发

华为云发布CodeArts TestPlan测试管理平台 守护产品质量之魂

科技热闻

为iframe正名,你可能并不需要微前端

阿里巴巴终端技术

前端 微前端 iframe

EMQ携“云边协同IIoT解决方案”亮相2022世界工业互联网产业大会

EMQ映云科技

人工智能 物联网 IoT 云边协同 企业号 1 月 PK 榜

读 NebulaGraph源码 | 查询语句 LOOKUP 的一生

NebulaGraph

图数据库 源码解读

Serverless时代的微服务开发指南:华为云提出七大实践新标准

华为云开发者联盟

微服务 云原生 后端 华为云 企业号 1 月 PK 榜

明天 9 点!Doris Summit 2022 拉开序幕,立即报名年度技术盛会!

SelectDB

数据湖 云原生 实时数仓 湖仓一体 数据库·

一文详解RocketMQ的存储模型

华为云开发者联盟

开发 华为云 企业号 1 月 PK 榜

GS Collections实例教程(第二部分)_Java_Donald Raab_InfoQ精选文章