集合操作是編程中使用頻率非常高的,所有有一款針對集合的操作工具是非常有必要的。通過框架提供的工具一方面可以減少開發相似功能的耗時;同時框架在安全與穩定性上更被推薦。
Guava Collect是Guava工具包中的一個子模塊,主要對jdk中的集合操作添加了一些簡易的API,同時也是對Collections工具類的擴展。當然Guava還定義了一些特定場景的數據結構以及一些針對jdk集合的優化,最典型的就是Immutable Collections(不可變集合),你會發現調用Guava API很多都是不可變的
意義
我們常見的集合類有:
- List
- Set
- Vector
- Stack
- Map
- Queue
集合是一種非常常見的數據結構,JDK在處理各種數據集時,提供了以上集合類型的數據結構以及其對應API方便開發者高效簡易地對數據對象操作
特色
guava主要提供了以下幾個方面的支持:
- 增加了不可變集合
- 不受信任的庫可以安全使用。
- 線程安全:可以被許多線程使用,沒有競爭條件的風險。
- 不需要支持突變,并且可以通過該假設節省時間和空間。所有不可變集合實現都比它們的可變兄弟更節省內存。(分析)
- 可以用作常數,期望它保持不變。
- 增加了新的集合類型
- Multiset 與普通的Set相比,提供了元素出現頻率的記錄。可用于元素出現次數的記錄
- Multimap 一個與Map相比,一個建可以對應對應多個值。與Spring中MultiValueMap一樣
- BiMap 鍵值都是唯一的Map
- Table 具有行、列的表格,數據視圖中可能更直觀。
- ClassToInstanceMap 鍵為Class,值為Class實例的特殊Map
- RangeSet 代表一組數據區間,類似數學中的 [1,9)
- RangeMap 與RangeSet類似,不過將其區間作為建,可以有自己的值。[1,9) -> 'VAL'
- 優化了常用的操作
- 集合的創建
ImmutableSet.of(elem ...)
Lists.newArrayList(elem ...)
Sets.newHashSet(elem ...)
Maps.newHashMap()
... - 常用的操作 判斷兩個集合是否相等:Iterables.elementsEqual()
集合分段處理:Lists.partition()
取集合的交集:Sets.intersection()
取集合的差集:Sets.difference()
...
- 集合的創建
使用
Guava Collect作為集合操作工具,我們主要從實際業務中了解其能夠幫助我們實現怎樣的需求,下面看下其API的使用情況:
假設我們有10000名學生,通過Faker生成這些模擬的學生數據數據:
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
獲取元素出現頻次。比如獲取男生與女生的學生數量分別為多少
@Test
public void multiset(){
Multiset multiset = HashMultiset.create();
students.forEach(student - > {
if(Objects.equals(student.getGender(),"男")){
multiset.add("男");
}else{
multiset.add("女");
}
});
System.out.println("學生中男生數量:"+ multiset.count("男"));
System.out.println("學生中女生數量:"+ multiset.count("女"));
}
Multimap
一個鍵對應多個值時。比如查看各個年齡的學生是哪些
@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
鍵和值都是唯一時。比如處理學生的郵箱和手機號,客戶互換鍵值位置
@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
二維表,通過行(鍵)、列(鍵)取值 比如可以以學生為行數據,其中id為行鍵,列名分別為學生屬性名稱
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(), "分數", 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
當值是鍵的類型實例時,通過該Map現在鍵值關系
@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
區間Set。比如通過學生分數確定學生等級
@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( " 學生:"+ student.getName() );
System.out.print( ",分數為:"+ 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類似,區別是添加了區間命名。和上面一樣
@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( " 學生:"+ student.getName() );
System.out.print( ",分數為:"+ student.getScore() );
System.out.print( ",等級為:"+ rangeMap.get(student.getScore()) +"n");
});
}
下面看下對常用集合的一些操作
當然我們首先需要將數據使用Guava Collect對應的數據結構來存儲數據,這樣才能使用其對應的API:
- 集合創建 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); // 拆解集合
- 計算 Iterables.frequency(list, elem); //元素出現的次數
- 集合的并集、交集、差集 // 并集 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();
- 索引 // 將元素中的子項作為索引,由于元素檢索 Maps.uniqueIndex() Multimaps.index()
Jdk中的集合操作
自從Jdk中引入了集合Stream的操作后,從很大程度上簡化了對集合的操作,以前大量代碼現在可能簡單幾行就能夠達到相同的效果,同時支持并發處理,一并提升了效率。
下面看下常見的集合基于stream操作,同樣以上面的學生為例:
遍歷 forEach
@Test
public void forEach(){
students.stream().forEach(System.out::println);
}
轉換 map
將元素轉換成其他類型。比如根據學生名稱、性別組成新的List;以id為鍵元素為值的Map或者學生姓名拼接的字符串等等
@Test
public void transform(){
// 轉換為數組
List< String > listResult = students.stream()
.map((val)- > val.getName() + ":" + val.getGender()).collect(Collectors.toList());
System.out.println( listResult );
// 轉換成String
String stringResult = students.stream().map(Student::getName).collect(Collectors.joining());
System.out.println( stringResult );
// 轉換成Map
Map< String, Student > mapResult = students.stream().collect(
// key ,value ,mergerOperation, initialization
Collectors.toMap(Student::getName,Student::self,(v1,v2)- >{
// 出現相同key時的合并規則
return null;
},HashMap::new)
);
System.out.println( mapResult );
}
過濾 filter
根據條件匹配滿足要求的元素。如找出分數大于80分的學生
@Test
public void filter(){
List< Student > filterResult = students.stream().filter((val)- >{
return val.getScore() >80;
}).collect(Collectors.toList());
System.out.println(filterResult);
}
拆解 flatMap
將二層級集合進行拆解,并成一級集合。如[[1,2,3],[4,5,6]] -> [1,2,3,4,5,6]
@Test
public void flatMap(){
//復合拆解
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
}
計算實現數據的匯總、求平均值、最大值...,當然主要針對數字(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();
// 歸約運算 fold . count、sum、min、max、average
DoubleSummaryStatistics doubleSummaryStatistics = students.stream().mapToDouble(Student::getScore).summaryStatistics();
}
@Test
public void reduce(){
// 結果和identity(初始值)類型相同
// identity accumulator combiner
Map result = students.stream().reduce(
new HashMap< String,Student >(), //初始值
(map, student) - > {
map.put(student.getId(),student);
return map;
},
(map1, map2) - > {
// 并發執行時的map合并
return null;
}
);
}
并發 parallel
上面的操作我們還可以使用parallel對stream并發處理
Arrays.asList().stream().parallel()...;
Arrays.asList().parallelStream()...;
分段處理對集合按固定規格分段處理,處理大批量數據時,結合parallel實現分段并發處理來提示效率
@Test
public void partition(){
List< String > list = new ArrayList< >();
int partition = 100; //每段100個元素
int part = list.size() / partition + (list.size() % partition==0? 0:1);
Stream.iterate(0, n - > n+1)
.limit(part)
.parallel() //并發
.map(index - > list.stream().skip(index * partition).limit(partition).parallel().collect(Collectors.toList()))
.forEach(System.out::println);
}
總結
本章主要介紹了Guava Collect部分,以及對集合操作的常用API,通過示例可以看到有其對JDK集合的擴展有了更廣泛與簡易的操作。同時在JDK引入 了Stream操作后,Guava Collect中的很多功能通過Stream也可以比較容易的實現了,當然具體如何選擇根據實際情況。需要注意的是Guava Collect中 返回的基本都是不可變的集合,這樣在對數據的操作會更加的安全。
-
模塊
+關注
關注
7文章
2674瀏覽量
47350 -
編程
+關注
關注
88文章
3596瀏覽量
93609 -
Collector
+關注
關注
0文章
3瀏覽量
5985 -
JDK
+關注
關注
0文章
81瀏覽量
16579
發布評論請先 登錄
相關推薦
評論