到處是map、flatMap,啥意思?
最近入職一個有趣的年輕同事,提交了大量大量的代碼。翻開git記錄一看,原來是用了非常多的java8的語法特性,重構了代碼。用的最多的,就是map、flatMap之類的。
但是其他小夥伴不願意了,雖然有的人感覺代碼變的容易懂了,但有更多的人感覺代碼變的很晦澀。
那感覺就像是:脫了褲子放屁,多此一舉。
這些函數的作用域,根據級別,我覺得可以分為三類。簡直是無所不在。
不要過分使用
我也不知道這些函數是從什麼時候流行起來的,但它們與函數編程的關係肯定是非常密切的。好像是2004年的Scala開始的。
沒什麼神奇的,它們全部是語法糖,作用是讓你的程序更簡潔。你要是想,完全可以用多一點的代碼去實現。不要為了炫技刻意去使用,物極必反,用不好的話,產生的效果會是非常負面的。比如java,它並不是一門函數編程語言,那麼lambda就只是一種輔助;而你用java那一套去寫Lisp代碼的話,也只會不倫不類。
但語言還是要融合的,因為潮流就是這樣。不去看他們背後的設計,我們僅從api的語義表象,橫向看一下它們所表達的東西。
我們首先看一下其中的共性(注意:邏輯共性,並不適合所有場景),然後拿幾個典型的實現,看一下在這個星球上,程序員們的表演。
這些抽象的概念
這些函數的作用對象,據說是一種稱之為流的東西。那流到底是一種什麼東西呢?請原諒我用一些不專業的話去解釋。
不論是在語言層面還是分散式數據結構上,它其實是一個簡單的數組。它有時候真的是一個簡單的數組,有時候是存在於多台機器的分散式數組。在下文中,我們統稱為數組流。
我們簡單分為兩類。
語言層面的:比如Java的Stream分散式層面的:比如Spark的RDD
它們都有以下幾個比較重要的點。
函數可以作為參數
C語言當然是沒問題的,可以把函數作為指針傳入。但在不久之前,在Java中,這還得繞著彎子去實現(使用java概念中的Class去模擬函數,你會見到很多Func1、Func0這樣奇怪的java類)。
函數作參數,是使得代碼變得簡潔的一個必要條件。我們通常的編程方法,大多是順序執行一些操作。
array = new Array()array = func1(array)if(func2(array)){ array = func3(array)}array = func4(array)
而如果函數能夠當參數,我就能夠盡量的將操作平鋪。最終,還是要翻譯成上面的語句進行執行的。
array = new Array()array.stream().map(func1).filter(func2).flatMap(func3).sorted(func4)...
編程模式完全變了,函數也有了語義。
sequential parallel
如果我們的數組流太大,對於單機來說,就有順序處理和並行處理兩種方式。
通常,可以通過parallel函數進入並行處理模式。對於大多數本地操作,開了並行不見得一定會快。
java中使用ForkJoin那一套,線程的速度,你知道的…
而對於分散式數據流來說,本來就是並行的,這種參數意義就不大了。
函數種類
一般作用在數據流上的函數,會分為兩類。
轉換。Transformation動作。Action
轉換,典型的特點就是lazy。
只有action執行的時候,才會真正參與運算。所以,你可以認為這些轉換動作是一套被緩衝的操作。典型的函數如:map、flatMap等。它們就像烤串一樣被串在一起,等著被擼。
動作。真正觸發代碼的運行,上面的一系列轉換,也會像開了閘的洪水一樣,一瀉而下。典型的如reduce函數,就是這種。
以上的描述也不盡然,比如python的map,執行後就可以輸出結果。這讓人很沒面子啊。
map reduce
談到map和reduce,大家就不約而同的想到了hadoop。然而,它不僅僅是大數據中的概念。
對於它倆的概念,我們僅做下面兩行介紹。
map
將傳入的函數依次作用到序列的每個元素,並把結果作為新的數組流返回。
reduce
reduce類似於一個遞歸的概念。最終會歸約成一個值。看看這個公式:)
reduce([p1,p2,p3,p4],fn) = reduce([fn(p2,p4),fn(p1,p3)])
具體還是看谷歌的經典論文吧。
《MapReduce: Simplified Data
Processing on Large Clusters》
https://ai.google/research/pubs/pub62
你能訪問么?:)
map flatMap
這兩個函數經常被使用。它們有如下區別:
map
把數組流中的每一個值,使用所提供的函數執行一遍,一一對應。得到元素個數相同的數組流。
flatMap
flat是扁平的意思。它把數組流中的每一個值,使用所提供的函數執行一遍,一一對應。得到元素相同的數組流。只不過,裡面的元素也是一個子數組流。把這些子數組合併成一個數組以後,元素個數大概率會和原數組流的個數不同。
程序員們的表演
java8種的Stream
java8開始,加入了一個新的抽象,一個稱之為流的東西:Stream。配合lambda語法,可以使代碼變的特別的清爽、乾淨(有木有發現都快成了Scala了)。
一個非常好的嚮導:
https://stackify.com/streams-guide-java-8/
Spark的RDD操作
spark的核心數據模型就是RDD,是一個有向無環圖。它代表一個不可變、可分區、其內元素可並行計算的集合。
它是分散式的,但我們可以看下一個WordCount的例子。
JavaRDDtextFile = sc.textFile(hdfs://...);
JavaPairRDDcounts = textFile
.flatMap(s - Arrays.asList(s.split( )).iterator())
.mapToPair(word - new Tuple2(word, 1))
.reduceByKey((a, b) - a b);
counts.saveAsTextFile(hdfs://...);
多麼熟悉的Api啊,你一定在Hadoop里見過。
Flink 的 DataStream
Flink程序是執行分散式集合轉換(例如,filtering, mapping, updating state, joining, grouping, defining windows, aggregating)的常規程序。Flink中的DataStream程序是實現在數據流上的transformation。
我們同樣看一下它的一段代碼。
DataStreamtuple2 counts =// split up the lines in pairs (2-tuples) containing: (word,1)text.flatMap(new Tokenizer())// group by the tuple field 0 and sum up tuple field 1.keyBy(0).sum(1);
kafka stream的操作
kafka已經變成了一個分散式的流式計算平台。他抽象出一個KStream和KTable,與Spark的RDD類似,也有類似的操作。
KStream可以看作是KTable的更新日誌(changlog),數據流中的每一個記錄對應資料庫中的每一次更新。
我們來看下它的一段代碼。
KTablewordCounts = textLines
.flatMapValues(value - Arrays.asList(value.toLowerCase().split(\\W )))
.groupBy((key, value) - value)
.count();
wordCounts.toStream().to(streams-wordcount-output, Produced.with(stringSerde, longSerde));
RxJava
RxJava是一個基於觀察者模式的非同步任務框架,經常看到會被用到Android開發中(服務端採用的也越來越多)。
RxJava再語言層面進行了一些創新,有一部分忠實的信徒。
語言層面的lambda
當然,對Haskell這種天生的函數編程語言來說,是自帶光環的。但其他的一些語言,包括腳本語言,編譯性語言,也吸收了這些經驗。
它們統稱為lambda。
Python
作為最流行的腳本語言,python同樣也有它的lambda語法。最基本的map、reduce、filter等函數同樣是存在的。
JavaScript
js也不能拉下,比如Array.prototype.*()等。它該有的,也都有了。
End
另外還有很多很多,就不一一羅列了。話說,這些函數可以申請專利么?我很喜歡,雖然我很少用。
※當Golang遇到高並發秒殺,世界開始變得簡單
※MySQL存儲引擎、MyISAM、InnoDB
TAG:千鋒JAVA開發學院 |