集合操作是編程中使用頻率非常高的,所有有一款針對集合的操作工具是非常有必要的。通過框架提供的工具一方面可以減少開發(fā)相似功能的耗時(shí);同時(shí)框架在安全與穩(wěn)定性上更被推薦。
Guava Collect是Guava工具包中的一個(gè)子模塊,主要對jdk中的集合操作添加了一些簡易的API,同時(shí)也是對Collections工具類的擴(kuò)展。當(dāng)然Guava還定義了一些特定場景的數(shù)據(jù)結(jié)構(gòu)以及一些針對jdk集合的優(yōu)化,最典型的就是Immutable Collections(不可變集合),你會發(fā)現(xiàn)調(diào)用Guava API很多都是不可變的
意義
我們常見的集合類有:
- List
- Set
- Vector
- Stack
- Map
- Queue
集合是一種非常常見的數(shù)據(jù)結(jié)構(gòu),JDK在處理各種數(shù)據(jù)集時(shí),提供了以上集合類型的數(shù)據(jù)結(jié)構(gòu)以及其對應(yīng)API方便開發(fā)者高效簡易地對數(shù)據(jù)對象操作
特色
guava主要提供了以下幾個(gè)方面的支持:
- 增加了不可變集合
- 不受信任的庫可以安全使用。
- 線程安全:可以被許多線程使用,沒有競爭條件的風(fēng)險(xiǎn)。
- 不需要支持突變,并且可以通過該假設(shè)節(jié)省時(shí)間和空間。所有不可變集合實(shí)現(xiàn)都比它們的可變兄弟更節(jié)省內(nèi)存。(分析)
- 可以用作常數(shù),期望它保持不變。
- 增加了新的集合類型
- Multiset 與普通的Set相比,提供了元素出現(xiàn)頻率的記錄??捎糜谠爻霈F(xiàn)次數(shù)的記錄
- Multimap 一個(gè)與Map相比,一個(gè)建可以對應(yīng)對應(yīng)多個(gè)值。與Spring中MultiValueMap一樣
- BiMap 鍵值都是唯一的Map
- Table 具有行、列的表格,數(shù)據(jù)視圖中可能更直觀。
- ClassToInstanceMap 鍵為Class,值為Class實(shí)例的特殊Map
- RangeSet 代表一組數(shù)據(jù)區(qū)間,類似數(shù)學(xué)中的 [1,9)
- RangeMap 與RangeSet類似,不過將其區(qū)間作為建,可以有自己的值。[1,9) -> 'VAL'
- 優(yōu)化了常用的操作
- 集合的創(chuàng)建
ImmutableSet.of(elem ...)
Lists.newArrayList(elem ...)
Sets.newHashSet(elem ...)
Maps.newHashMap()
... - 常用的操作 判斷兩個(gè)集合是否相等:Iterables.elementsEqual()
集合分段處理:Lists.partition()
取集合的交集:Sets.intersection()
取集合的差集:Sets.difference()
...
- 集合的創(chuàng)建
使用
Guava Collect作為集合操作工具,我們主要從實(shí)際業(yè)務(wù)中了解其能夠幫助我們實(shí)現(xiàn)怎樣的需求,下面看下其API的使用情況:
假設(shè)我們有10000名學(xué)生,通過Faker生成這些模擬的學(xué)生數(shù)據(jù)數(shù)據(jù):
List< Student > students = new ArrayList< >();
Faker faker = new Faker(Locale.CHINA);
@Before
public void init(){
Faker enFaker = new Faker();
Name name = faker.name();
IntStream.range(0,10000).forEach(index- >{
students.add(
Student.of()
.setId(String.valueOf(index+1))
.setName(name.name())
.setAge(faker.number().numberBetween(18,22))
.setGender(new String[]{"男","女"}[faker.number().numberBetween(0,2)])
.setAddress(faker.address().streetAddress())
.setScore(faker.number().randomDouble(3,50,100))
.setEmail( faker.internet().emailAddress(enFaker.name().username()))
.setTelephone(faker.phoneNumber().cellPhone())
);
});
}
Multiset
獲取元素出現(xiàn)頻次。比如獲取男生與女生的學(xué)生數(shù)量分別為多少
@Test
public void multiset(){
Multiset multiset = HashMultiset.create();
students.forEach(student - > {
if(Objects.equals(student.getGender(),"男")){
multiset.add("男");
}else{
multiset.add("女");
}
});
System.out.println("學(xué)生中男生數(shù)量:"+ multiset.count("男"));
System.out.println("學(xué)生中女生數(shù)量:"+ multiset.count("女"));
}
Multimap
一個(gè)鍵對應(yīng)多個(gè)值時(shí)。比如查看各個(gè)年齡的學(xué)生是哪些
@Test
public void multimap(){
ListMultimap< Integer, Student > multimap =
MultimapBuilder.hashKeys().arrayListValues().build();
students.forEach(student - > {
multimap.put(student.getAge(),student);
});
System.out.println( multimap.get(20) );
}
BiMap
鍵和值都是唯一時(shí)。比如處理學(xué)生的郵箱和手機(jī)號,客戶互換鍵值位置
@Test
public void biMap(){
BiMap biMap = HashBiMap.create();
students.forEach(student - > {
biMap.put(student.getEmail(),student.getTelephone());
});
BiMap inverse = biMap.inverse();// 鍵值更換
System.out.println( biMap );
System.out.println( inverse );
}
Table
二維表,通過行(鍵)、列(鍵)取值 比如可以以學(xué)生為行數(shù)據(jù),其中id為行鍵,列名分別為學(xué)生屬性名稱
ID | 姓名 | 年齡 | 性別 |
---|---|---|---|
1 | TOM | 22 | 男 |
@Test
public void table(){
Table< String, String, Object > weightedGraph = HashBasedTable.create();
students.forEach(student - > {
weightedGraph.put(student.getId(), "姓名", student.getName());
weightedGraph.put(student.getId(), "年齡", student.getAge());
weightedGraph.put(student.getId(), "性別", student.getGender());
weightedGraph.put(student.getId(), "郵箱", student.getEmail());
weightedGraph.put(student.getId(), "電話", student.getTelephone());
weightedGraph.put(student.getId(), "地址", student.getAddress());
weightedGraph.put(student.getId(), "分?jǐn)?shù)", student.getScore());
});
Map< String, Object > row = weightedGraph.row("1");
Map< String, Object > column = weightedGraph.column("姓名");
Set< Table.Cell< String, String, Object >> cells = weightedGraph.cellSet();
System.out.println( row );
System.out.println( column );
System.out.println( cells );
}
ClassToInstanceMap
當(dāng)值是鍵的類型實(shí)例時(shí),通過該Map現(xiàn)在鍵值關(guān)系
@Test
public void classToInstanceMap(){
ClassToInstanceMap< Number > numberDefaults = MutableClassToInstanceMap.create();
numberDefaults.put(Number.class,1);
Map< Class,Object > objectMap = new HashMap< >();
objectMap.put(Number.class,2);
}
RangeSet
區(qū)間Set。比如通過學(xué)生分?jǐn)?shù)確定學(xué)生等級
@Test
public void rangeSet(){
RangeSet< Double > ArangeSet = TreeRangeSet.create();
ArangeSet.add(Range.closed(90d,100d)); // [90,100]
RangeSet< Double > BrangeSet = TreeRangeSet.create();
BrangeSet.add(Range.closedOpen(80d,90d)); // [80,90)
RangeSet< Double > CrangeSet = TreeRangeSet.create();
CrangeSet.add(Range.closedOpen(70d,80d)); // [70,80)
RangeSet< Double > DrangeSet = TreeRangeSet.create();
DrangeSet.add(Range.closedOpen(60d,70d)); // [60,70)
RangeSet< Double > ErangeSet = TreeRangeSet.create();
ErangeSet.add(Range.lessThan(60d)); // [...,60)
students.forEach(student - > {
System.out.print( " 學(xué)生:"+ student.getName() );
System.out.print( ",分?jǐn)?shù)為:"+ student.getScore() );
String rank = "";
if(ArangeSet.contains(student.getScore())){
rank = "A";
}else if(BrangeSet.contains(student.getScore())){
rank = "B";
}else if(CrangeSet.contains(student.getScore())){
rank = "C";
}else if(DrangeSet.contains(student.getScore())){
rank = "D";
}else if(ErangeSet.contains(student.getScore())){
rank = "E";
}
System.out.print( ",等級為:"+ rank +"n");
});
}
RangeMap和RangeSet類似,區(qū)別是添加了區(qū)間命名。和上面一樣
@Test
public void rangeMap(){
RangeMap< Double, String > rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closed(90d,100d),"A"); // [90,100]
rangeMap.put(Range.closedOpen(80d,90d),"B"); // [80,90)
rangeMap.put(Range.closedOpen(70d,80d),"C"); // [70,80)
rangeMap.put(Range.closedOpen(60d,70d),"D"); // [60,70)
rangeMap.put(Range.lessThan(60d),"E"); // [...,60)
students.forEach(student - > {
System.out.print( " 學(xué)生:"+ student.getName() );
System.out.print( ",分?jǐn)?shù)為:"+ student.getScore() );
System.out.print( ",等級為:"+ rangeMap.get(student.getScore()) +"n");
});
}
下面看下對常用集合的一些操作
當(dāng)然我們首先需要將數(shù)據(jù)使用Guava Collect對應(yīng)的數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù),這樣才能使用其對應(yīng)的API:
- 集合創(chuàng)建 FluentIterable.of(elem ...)
Lists.newArrayList(elem ...)
Sets.newHashSet(elem ...)
Maps.newHashMap()
HashMultiset.create()
ArrayListMultimap.create()
Tables.newCustomTable(Maps.newLinkedHashMap(), () -> Maps.newLinkedHashMap()) - 條件過濾
FluentIterable.filter(predicate); FluentIterable.anyMatch(predicate); FluentIterable.allMatch(predicate); FluentIterable.firstMatch(predicate); - 拆分 Iterables.partition(list, pageSize); // 拆解集合
- 計(jì)算 Iterables.frequency(list, elem); //元素出現(xiàn)的次數(shù)
- 集合的并集、交集、差集 // 并集 Sets.union(set1, set2); // 交集 Sets.intersection(set1, set2); // 差集 set1為參考 Sets.difference(set1, set2); // 并集-交集 Sets.symmetricDifference(set1, set2); // 同上 Sets.difference(Sets.union(set1, set2),Sets.intersection(set1, set2) ); // 笛卡爾積 Sets.cartesianProduct(Arrays.asList(Sets.newHashSet(1, 2, 3), Sets.newHashSet(3, 4, 5, 6)); // Map,KV相同的部分 difference.entriesInCommon(); // 同K不同V difference.entriesDiffering(); // 左邊存在的右邊不存的K difference.entriesOnlyOnLeft(); // 右邊存在的左邊不存的K difference.entriesOnlyOnRight();
- 索引 // 將元素中的子項(xiàng)作為索引,由于元素檢索 Maps.uniqueIndex() Multimaps.index()
Jdk中的集合操作
自從Jdk中引入了集合Stream的操作后,從很大程度上簡化了對集合的操作,以前大量代碼現(xiàn)在可能簡單幾行就能夠達(dá)到相同的效果,同時(shí)支持并發(fā)處理,一并提升了效率。
下面看下常見的集合基于stream操作,同樣以上面的學(xué)生為例:
遍歷 forEach
@Test
public void forEach(){
students.stream().forEach(System.out::println);
}
轉(zhuǎn)換 map
將元素轉(zhuǎn)換成其他類型。比如根據(jù)學(xué)生名稱、性別組成新的List;以id為鍵元素為值的Map或者學(xué)生姓名拼接的字符串等等
@Test
public void transform(){
// 轉(zhuǎn)換為數(shù)組
List< String > listResult = students.stream()
.map((val)- > val.getName() + ":" + val.getGender()).collect(Collectors.toList());
System.out.println( listResult );
// 轉(zhuǎn)換成String
String stringResult = students.stream().map(Student::getName).collect(Collectors.joining());
System.out.println( stringResult );
// 轉(zhuǎn)換成Map
Map< String, Student > mapResult = students.stream().collect(
// key ,value ,mergerOperation, initialization
Collectors.toMap(Student::getName,Student::self,(v1,v2)- >{
// 出現(xiàn)相同key時(shí)的合并規(guī)則
return null;
},HashMap::new)
);
System.out.println( mapResult );
}
過濾 filter
根據(jù)條件匹配滿足要求的元素。如找出分?jǐn)?shù)大于80分的學(xué)生
@Test
public void filter(){
List< Student > filterResult = students.stream().filter((val)- >{
return val.getScore() >80;
}).collect(Collectors.toList());
System.out.println(filterResult);
}
拆解 flatMap
將二層級集合進(jìn)行拆解,并成一級集合。如[[1,2,3],[4,5,6]] -> [1,2,3,4,5,6]
@Test
public void flatMap(){
//復(fù)合拆解
List< Integer > result = Stream.of(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6))
.flatMap(subList - > subList.stream())
.collect(Collectors.toList());
System.out.println(result);// 1 2 3 4 5 6
}
計(jì)算實(shí)現(xiàn)數(shù)據(jù)的匯總、求平均值、最大值...,當(dāng)然主要針對數(shù)字(Number)類型
@Test
public void calculate(){
// 求和
double sum = students.stream().mapToDouble(Student::getScore).sum();
// 最大值
double max = students.stream().mapToDouble(Student::getScore).max().getAsDouble();
// 最小值
double min = students.stream().mapToDouble(Student::getScore).min().getAsDouble();
// 平均值
double avg = students.stream().mapToDouble(Student::getScore).average().getAsDouble();
// 歸約運(yùn)算 fold . count、sum、min、max、average
DoubleSummaryStatistics doubleSummaryStatistics = students.stream().mapToDouble(Student::getScore).summaryStatistics();
}
歸納計(jì)算 reduce
在很多語言中都存在的函數(shù),如python、javascript。數(shù)據(jù)的累加、map的功能
@Test
public void reduce(){
// 結(jié)果和identity(初始值)類型相同
// identity accumulator combiner
Map result = students.stream().reduce(
new HashMap< String,Student >(), //初始值
(map, student) - > {
map.put(student.getId(),student);
return map;
},
(map1, map2) - > {
// 并發(fā)執(zhí)行時(shí)的map合并
return null;
}
);
}
并發(fā) parallel
上面的操作我們還可以使用parallel對stream并發(fā)處理
Arrays.asList().stream().parallel()...;
Arrays.asList().parallelStream()...;
分段處理對集合按固定規(guī)格分段處理,處理大批量數(shù)據(jù)時(shí),結(jié)合parallel實(shí)現(xiàn)分段并發(fā)處理來提示效率
@Test
public void partition(){
List< String > list = new ArrayList< >();
int partition = 100; //每段100個(gè)元素
int part = list.size() / partition + (list.size() % partition==0? 0:1);
Stream.iterate(0, n - > n+1)
.limit(part)
.parallel() //并發(fā)
.map(index - > list.stream().skip(index * partition).limit(partition).parallel().collect(Collectors.toList()))
.forEach(System.out::println);
}
總結(jié)
本章主要介紹了Guava Collect部分,以及對集合操作的常用API,通過示例可以看到有其對JDK集合的擴(kuò)展有了更廣泛與簡易的操作。同時(shí)在JDK引入 了Stream操作后,Guava Collect中的很多功能通過Stream也可以比較容易的實(shí)現(xiàn)了,當(dāng)然具體如何選擇根據(jù)實(shí)際情況。需要注意的是Guava Collect中 返回的基本都是不可變的集合,這樣在對數(shù)據(jù)的操作會更加的安全。
-
模塊
+關(guān)注
關(guān)注
7文章
2655瀏覽量
47292 -
編程
+關(guān)注
關(guān)注
88文章
3565瀏覽量
93536 -
Collector
+關(guān)注
關(guān)注
0文章
3瀏覽量
5978 -
JDK
+關(guān)注
關(guān)注
0文章
81瀏覽量
16567
發(fā)布評論請先 登錄
相關(guān)推薦
評論