【優(yōu)雅代碼】14-guava精選方法及eventBus觀察者模式源碼解析
歡迎關(guān)注b站賬號/公眾號【六邊形戰(zhàn)士夏寧】,一個要把各項(xiàng)指標(biāo)拉滿的男人。該文章已在github目錄收錄。
屏幕前的大帥比和大漂亮如果有幫助到你的話請順手點(diǎn)個贊、加個收藏這對我真的很重要。別下次一定了,都不關(guān)注上哪下次一定。
- 視頻講解
- 可直接運(yùn)行的完整代碼
- 上一篇linkedList插入真的比arrayList快么
- 下一篇guavaCache本地緩存使用及源碼解析
1.背景
google的guava是非常經(jīng)典的工具類,但是java經(jīng)過多年的發(fā)展,不少方法已經(jīng)有了優(yōu)秀的替代方案,以下分享是個人在日常開發(fā)的時候依然覺得非常經(jīng)典的方法。
2.基本工具Basic utilities
因?yàn)镺ptional和stream的流行,這個工具類里面的方法基本都用不上了。
3.集合Collections(重要)
這塊主要介紹guava的集合,工具類雖然是對apache的補(bǔ)充,但是過于花里胡哨。
3.1不可變集合
- 常規(guī)用法
public static void immutableOrdinary() {
// 常規(guī)創(chuàng)建
Set<Integer> set = ImmutableSet.of(1, 2, 3, 1);
List<Integer> list = ImmutableList.of(1, 2, 3, 1);
// map的k-v連續(xù)的寫法還是非常舒服的
Map<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 1);
// 循環(huán)創(chuàng)建
ImmutableSet.Builder<Integer> builder = ImmutableSet.<Integer>builder();
for (int i = 0; i < 10; i++) {
builder.add(i);
}
Set<Integer> build = builder.build();
// 常規(guī)list轉(zhuǎn)不可變,值得注意的是Collections.unmodifiableList()
List<Integer> collect = Stream.of(1, 2, 3, 4).collect(Collectors.toList());
List<Integer> integers = ImmutableList.copyOf(collect);
}
- 以近乎同樣的方式插入性能對比
public static void effectiveList() {
StopWatch sw = new StopWatch();
sw.start("listAdd");
Stream.Builder<Integer> builder1 = Stream.builder();
for (int i = 0; i < 10000; i++) {
builder1.add(i);
}
List<Integer> list = builder1.build().collect(Collectors.toList());
sw.stop();
sw.start("ImmutableListAdd");
ImmutableList.Builder<Integer> builder = ImmutableList.builder();
for (int i = 0; i < 10000; i++) {
builder.add(i);
}
List<Integer> guava = builder.build();
sw.stop();
System.out.println(sw.prettyPrint());
}
- 可以看到插入速度非常明顯的不可變集合要更快
---------------------------------------------
ns % Task name
---------------------------------------------
072909616 081% listAdd
017632120 019% ImmutableListAdd
- 獲取單個元素的速度對比
List<Object> listUn = Collections.unmodifiableList(list);
sw.start("listGet");
list.get(0);
sw.stop();
sw.start("listUnGet");
listUn.get(0);
sw.stop();
sw.start("guavaGet");
guava.get(0);
sw.stop();
- list可太慢了,其它兩個效率差不多,guava還是要更快一點(diǎn)
---------------------------------------------
000018405 089% listGet
000001323 006% listUnGet
000000936 005% guavaGet
- 循環(huán)性能對比
List<Object> listUn = Collections.unmodifiableList(list);
sw.start("listFor");
IntStream.range(0, 10000).boxed().forEach(list::get);
sw.stop();
sw.start("listUnFor");
IntStream.range(0, 10000).boxed().forEach(listUn::get);
sw.stop();
sw.start("guavaFor");
IntStream.range(0, 10000).boxed().forEach(guava::get);
sw.stop();
- 和上面的結(jié)果一致
---------------------------------------------
007031946 053% listFor
003760153 028% listUnFor
002458426 019% guavaFor
如上所述,這就是我非常喜歡guava的原因
- 區(qū)別對比
// 常規(guī)list轉(zhuǎn)不可變,值得注意的是Collections.unmodifiableList()如果原list被改變不可變是會被改變的
list.remove(0);
// 即list和unmodifiableList都會少一個
3.2新集合類型
public static void newCollections(){
// 這里只介紹BiMap和Table。Multiset、Multimap這兩個就是嵌了list進(jìn)去
BiMap<Integer,String> biMap=HashBiMap.create();
biMap.put(1,"張三");
biMap.put(2,"李四");
biMap.put(3,"王五");
biMap.put(4,"趙六");
biMap.put(5,"李七");
biMap.put(6,"小小");
Integer result = biMap.inverse().get("趙六");
// 輸出結(jié)果4
System.out.println(result);
// ===========================================================
// table是個很有意思的數(shù)據(jù)結(jié)構(gòu),很有啟發(fā)性思維,雖然我也不知道這個有啥用
/*
* Company: IBM, Microsoft, TCS
* IBM -> {101:Mahesh, 102:Ramesh, 103:Suresh}
* Microsoft -> {101:Sohan, 102:Mohan, 103:Rohan }
* TCS -> {101:Ram, 102: Shyam, 103: Sunil }
*
* */
//create a table
Table<String, String, String> employeeTable = HashBasedTable.create();
//initialize the table with employee details
employeeTable.put("IBM", "101","Mahesh");
employeeTable.put("IBM", "102","Ramesh");
employeeTable.put("IBM", "103","Suresh");
employeeTable.put("Microsoft", "111","Sohan");
employeeTable.put("Microsoft", "112","Mohan");
employeeTable.put("Microsoft", "113","Rohan");
employeeTable.put("TCS", "121","Ram");
employeeTable.put("TCS", "102","Shyam");
employeeTable.put("TCS", "123","Sunil");
//所有行數(shù)據(jù)
System.out.println(employeeTable.cellSet());
//所有公司
System.out.println(employeeTable.rowKeySet());
//所有員工編號
System.out.println(employeeTable.columnKeySet());
//所有員工名稱
System.out.println(employeeTable.values());
//公司中的所有員工和員工編號
System.out.println(employeeTable.rowMap());
//員工編號對應(yīng)的公司和員工名稱
System.out.println(employeeTable.columnMap());
}
輸出如下
4
[(IBM,101)=Mahesh, (IBM,102)=Ramesh, (IBM,103)=Suresh, (Microsoft,111)=Sohan, (Microsoft,112)=Mohan, (Microsoft,113)=Rohan, (TCS,121)=Ram, (TCS,102)=Shyam, (TCS,123)=Sunil]
[IBM, Microsoft, TCS]
[101, 102, 103, 111, 112, 113, 121, 123]
[Mahesh, Ramesh, Suresh, Sohan, Mohan, Rohan, Ram, Shyam, Sunil]
{IBM={101=Mahesh, 102=Ramesh, 103=Suresh}, Microsoft={111=Sohan, 112=Mohan, 113=Rohan}, TCS={121=Ram, 102=Shyam, 123=Sunil}}
{101={IBM=Mahesh}, 102={IBM=Ramesh, TCS=Shyam}, 103={IBM=Suresh}, 111={Microsoft=Sohan}, 112={Microsoft=Mohan}, 113={Microsoft=Rohan}, 121={TCS=Ram}, 123={TCS=Sunil}}
3.3集合工具類
public static void Collections() {
// 個人覺得比較又用的有如下幾個方法,前幾個可以看做是redis交集、并集的內(nèi)存實(shí)現(xiàn)。后面是數(shù)據(jù)庫笛卡爾積的內(nèi)存實(shí)現(xiàn)
// 交集、并集、
Set<Integer> set1 = Stream.of(1, 2, 3, 4).collect(Collectors.toSet());
Set<Integer> set2 = Stream.of(3, 4, 5, 6).collect(Collectors.toSet());
// 求set1的差集
System.out.println(Sets.difference(set1, set2));
// 求set1和set2差集的并集
System.out.println(Sets.symmetricDifference(set1, set2));
// 求交集
System.out.println(Sets.intersection(set1, set2));
// 笛卡爾積
System.out.println(Sets.cartesianProduct(set1, set2));
// 笛卡爾積
List<Integer> list1 = Stream.of(1, 2, 3, 4).collect(Collectors.toList());
List<Integer> list2 = Stream.of(2, 3, 4, 5).collect(Collectors.toList());
System.out.println(Lists.cartesianProduct(list1, list2));
}
輸出如下
[1, 2]
[1, 2, 5, 6]
[3, 4]
[[1, 3], [1, 4], [1, 5], [1, 6], [2, 3], [2, 4], [2, 5], [2, 6], [3, 3], [3, 4], [3, 5], [3, 6], [4, 3], [4, 4], [4, 5], [4, 6]]
4.緩存(重要)
該部分單獨(dú)在下一篇分享。
5.函數(shù)式風(fēng)格Functional idioms
同2一樣,在stream的強(qiáng)力作用下不怎么用了
6.并發(fā)Concurrency
Futrue部分已經(jīng)有了CompletableFuture(在第4節(jié)thread有介紹),非常好用。Service部分功能是很強(qiáng),但這東西一般情況是真的用不上。
限流部分在下下篇分享
7.字符串處理Strings
apache的也不差,這塊都差不多
8.原生類型Primitives
功能看起來很棒,但是目前還沒找到實(shí)際使用場景
9.區(qū)間Ranges
功能看起來很棒,但是目前還沒找到實(shí)際使用場景
10.I/O
apache的IOUtils用起來比這個要簡單
11.散列Hash(因業(yè)務(wù)而異)
這個里面提供的布隆過濾,在有有需要的場景的時候還是可以用的。哈希算法在處理文件一致性校驗(yàn)等也有一席之地。
該部分單獨(dú)在下下一篇分享。
12.事件總線EventBus(重要)
超級容易的觀察者模式,有用到的可以用這東西,寫起來舒心不少。這一塊的設(shè)計(jì)思想個人還是非常喜歡的,覺得有必要展開一下。需要注意的是@Subscribe才會被通知,依賴的是方法參數(shù)和投遞的對象參數(shù)一致。
12.1代碼使用
- 創(chuàng)建被觀察者
public static class eventBusObject {
@Subscribe
public void listenStr1(String str) {
System.out.println(str + "listenStr1");
}
@Subscribe
public void listenStr2(String str) {
System.out.println(str + "listenStr2");
}
@Subscribe
public void listenObj(Object str) {
System.out.println(str + "listenStr1");
}
@Subscribe
public void listenInt1(Integer str) {
System.out.println(str + "listenInt1");
}
public void listenInt2(Integer str) {
System.out.println(str + "listenInt2");
}
}
- 方法通知
public static void eventBus() {
EventBus eventBus = new EventBus("eventBusTest");
eventBus.register(new eventBusObject());
eventBus.post(100);
eventBus.post("我是字符串");
}
- 輸出結(jié)果
100listenInt1
100listenStr1
我是字符串listenStr1
我是字符串listenStr2
我是字符串listenStr1
12.2核心源碼
- 注冊
// 點(diǎn)擊進(jìn)入register方法如下
public void register(Object object) {
subscribers.register(object);
}
// 再點(diǎn)擊進(jìn)入register方法如下,這里主要是反射,然后就存起來
void register(Object listener) {
// 核心方法反射獲取@Subscribe該注解的方法,并將傳入的class進(jìn)行分類
Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);
for (Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {
Class<?> eventType = entry.getKey();
Collection<Subscriber> eventMethodsInListener = entry.getValue();
CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
if (eventSubscribers == null) {
CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<>();
eventSubscribers =
MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
}
eventSubscribers.addAll(eventMethodsInListener);
}
}
- 調(diào)用
// 主方法調(diào)用post
public void post(Object event) {
// 該方法反符合event的迭代集合
Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
if (eventSubscribers.hasNext()) {
dispatcher.dispatch(event, eventSubscribers);
} else if (!(event instanceof DeadEvent)) {
// the event had no subscribers and was not itself a DeadEvent
post(new DeadEvent(this, event));
}
}
// 主方法調(diào)用getSubscribers方法
Iterator<Subscriber> getSubscribers(Object event) {
// 該方法返回event所有的父級對象,最上級即為Object,下面就是取兩者交集進(jìn)行組裝
ImmutableSet<Class<?>> eventTypes = flattenHierarchy(event.getClass());
List<Iterator<Subscriber>> subscriberIterators =
Lists.newArrayListWithCapacity(eventTypes.size());
for (Class<?> eventType : eventTypes) {
CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
if (eventSubscribers != null) {
// eager no-copy snapshot
subscriberIterators.add(eventSubscribers.iterator());
}
}
return Iterators.concat(subscriberIterators.iterator());
}
// 回到主方法dispatcher.dispatch進(jìn)行調(diào)用,該方法標(biāo)記為2.1
@Override
void dispatch(Object event, Iterator<Subscriber> subscribers) {
checkNotNull(event);
checkNotNull(subscribers);
Queue<Event> queueForThread = queue.get();
queueForThread.offer(new Event(event, subscribers));
if (!dispatching.get()) {
dispatching.set(true);
try {
Event nextEvent;
while ((nextEvent = queueForThread.poll()) != null) {
while (nextEvent.subscribers.hasNext()) {
// 核心方法調(diào)用,標(biāo)記為2.1.1
nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
}
}
} finally {
dispatching.remove();
queue.remove();
}
}
}
// 從2.1.1這里就可以看到把任務(wù)直接丟到線程池
final void dispatchEvent(final Object event) {
executor.execute(
new Runnable() {
@Override
public void run() {
try {
invokeSubscriberMethod(event);
} catch (InvocationTargetException e) {
bus.handleSubscriberException(e.getCause(), context(event));
}
}
});
}
13.數(shù)學(xué)運(yùn)算Math
一般情況用不到,apache下也有Math的包,功能基本一致
14.反射Reflection(次重要)
spring里面也帶了這塊的工具類,個人感覺spring的在處理常規(guī)情況下已經(jīng)非常優(yōu)秀了,這邊補(bǔ)充了泛型方向的,用起來也比較簡單,不過鑒于平常使用的機(jī)會不多就不展開了。