上面的函數(shù)中我們已經(jīng)做了詳細的注釋,和前面第二張圖描述的過程是一致的。接下來我們來對該方法進行測試:PS C:\Users\spyinx\Desktop\學習教程\慕課網(wǎng)教程\算法慕課教程\code> pythonPython 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 22:45:29) [MSC v.1916 32 bit (Intel)] on win32Type "help", "copyright", "credits" or "license" for more information.>>> from sort_algorithms import get_num_position>>> nums = [10, 2, 16, 8, 4, 17, 12, 11]>>> get_num_position(nums, 0, len(nums) - 1) 3>>> nums [4, 2, 8, 10, 16, 17, 12, 11]可以看到,代碼確實實現(xiàn)了我們想要的結(jié)果,將比 10 小的全部移動到了左邊,比 10 大的全部移動到了右邊。接下來就是實現(xiàn)快速排序算法。從第一張圖中很明顯可以看到快排算法是一個遞歸的過程,因此我們用遞歸完成快排算法,代碼如下:# 代碼位置:sort_algorithms.pydef quick_sort(nums, start, end): """ 快速排序算法 """ if start >= end: return pos = get_num_position(nums, start, end) # 左邊遞歸下去,直到排好序 quick_sort(nums, start, pos - 1) # 右邊遞歸下去,直到排好序 quick_sort(nums, pos + 1, end)對于遞歸方法,后面會詳細介紹到。這里一定要注意,使用遞歸過程一定要先寫終止條件,不然函數(shù)無窮遞歸下去,就會導致堆棧溢出錯誤。接下來我們測試這個快排算法:>>> from sort_algorithms import quick_sort>>> nums = [8, 7, 9, 6, 11, 3, 12, 20, 9, 5, 1, 10]>>> quick_sort(nums, 0, len(nums) - 1)>>> nums[1, 3, 5, 6, 7, 8, 9, 9, 10, 11, 12, 20] 可以看到上面的代碼實現(xiàn)了我們想要的排序效果。接下來我們分析下快排算法,它被人們研究的最多,提出了許多改進和優(yōu)化的方案,我們簡單聊一下快排算法的優(yōu)化方案。
在 Kotlin 中使用 when 表達式替代了類似 C 語言的 switch-case 語句。其中最簡單的形式如下:fun eval(number: Number): String = when (number) { is Int -> "this is int number" is Double -> "this is double number" is Float -> "ths is float number" is Long -> "this is long number" is Byte -> "this is byte number" is Short -> "this is Short number" else -> "invalid number"}//多種條件判斷混合形式fun main(args: Array<String>) { println(descript("hello"))}fun descript(obj: Any): String = when (obj) { 1 -> "one" "hello" -> "hello word" is Long -> "long type" !is String -> "is not String" else -> { "unknown type" }}when 將它的參數(shù)與所有的分支條件順序比較,直到某個分支滿足條件。 when 既可以被當做表達式使用也可以被當做語句使用。如果它被當做表達式, 符合條件的分支的值就是整個表達式的值,如果當做語句使用, 則忽略個別分支的值。(像 if 一樣,每一個分支可以是一個代碼塊,它的值是塊中最后的表達式的值。)Tips:如果其他分支都不滿足條件將會求值 else 分支。 如果 when 作為一個表達式使用,則必須有 else 分支, 除非編譯器能夠檢測出所有的可能情況都已經(jīng)覆蓋了。如果很多分支需要用相同的方式處理,則可以把多個分支條件放在一起,用逗號分隔:fun eval(any: Any): String = when (any) { is Int, Double, Float, Long, Byte, Short -> "this is number" //多個分支條件放在一起,用逗號分隔 is Char -> "this is char" else -> "other"}when 也可以用來取代 if-else if 鏈。 如果不提供參數(shù),所有的分支條件都是簡單的布爾表達式,而當一個分支的條件為真時則執(zhí)行該分支:fun eval(number: Number) { when { number.isOdd() -> { println("this is odd number") } number.isEven() -> { println("this is even number") } else -> println("this is invalid number") }}
下面列舉了一些 useradd 命令參數(shù)作用:useradd 命令參數(shù)名稱功能與作用描述-b表示 --base-dir,新用戶主目錄的基目錄-c表示 --comment,給新用戶添加備注-d表示 --home-dir, 新賬戶的家目錄-D表示 --defaults,顯示或更改默認的 useradd 配置-e表示 --expiredate,用 YYYYY-MM-DD 格式指定一個賬戶過期的日期-f表示 --inactive_days,指定這個帳戶密碼過期后多少天這個賬戶被禁用,0表示密碼一過期就立即禁用,-1表示禁用這個功能-g表示 --gid GROUP,指定用戶登錄組的GID或組名-G表示 --groups,指定用戶除登錄組之外所屬的一個或多個附加組-k表示 --skel,使用此目錄作為骨架目錄-K表示 --key,不使用 /etc/login.defs 中的默認值-l表示 --no-log-init,不要將此用戶添加到最近登錄和登錄失敗數(shù)據(jù)庫-m表示 --create-home,創(chuàng)建用戶的家目錄-M表示 --no-create-home,不創(chuàng)建用戶的家目錄(當默認設(shè)置里指定創(chuàng)建時,才用到)-N表示 --no-user-group,不創(chuàng)建同名的組-o表示 --non-unique,允許使用重復(fù)的 UID 創(chuàng)建用戶-p表示 --password,為用戶賬戶指定默認密碼-r表示 --system,創(chuàng)建一個系統(tǒng)賬戶-s表示 --shell,shell 指定默認登錄 shell-u表示 --uid,為賬戶指定一個唯一的 UID-U表示 --user-group,創(chuàng)建與用戶同名的組-Z表示 --selinux-user,為 SELinux 用戶映射使用指定名字若使用 useradd 命令不指定參數(shù),則新添的用戶默認參數(shù)如下:新用戶 GID 默認為 100;新用戶的家目錄位于 /home/用戶名;新用戶密碼過期后不會被禁用;新用戶賬戶沒有被設(shè)置過期日期;新用戶將 bash shell 作為默認 shell。
我們學一樣技術(shù)或者一種工具,剛開始最好先了解一下它的來龍去脈。雖然無關(guān)使用,但通過它背后的故事可能會激發(fā)起我們學習它的濃厚興趣,從這一點來講還是有實際意義的。提起 Git,就不得不提起 Linus 和他的 Linux。計算機相關(guān)從業(yè)者大多都知道,linus 創(chuàng)建了 Linux 系統(tǒng),但是 Linux 系統(tǒng)并不是由 Linus 一個人完成的,它依賴于廣大的開發(fā)者源源不斷的貢獻代碼來共同開發(fā)維護。那么問題來了,這么多人來共同維護一個系統(tǒng),當時大家是怎樣來保持高效的協(xié)作呢?這么多人提交代碼,是怎么保證代碼不沖突呢?你可能會想到使用過的一些版本控制系統(tǒng),不過可能要讓你失望了,雖然當時有很多版本控制系統(tǒng)像 CVS、SVN 等,但是都被 linus 舍棄了,因為這些集中式的版本控制系統(tǒng)需要聯(lián)網(wǎng),而且速度很受影響。所以,很長一段時間內(nèi),都是靠 linus 來自己手工合并的(默默送上大拇指)。但是,Linux 系統(tǒng)越來越龐大,這么下去不是個問題。直到 2002 年左右,BitMover 公司將他們的商業(yè)的版本控制系統(tǒng) BitKeeper 給 Linux 社區(qū)免費使用,這下大家的工作量稍微減輕了些。但是好景不長,社區(qū)牛人聚集,還沒一兩個手腳亂動的?有人試圖破解 BitKeeper 的協(xié)議,但是被 BitMover 公司發(fā)現(xiàn)了,于是乎 BitMover 公司一怒之下收回了他們的使用權(quán)。本來單車變摩托,現(xiàn)在又騎單車了。不過,Linus 就是 Linus,怎么可能重走舊路呢,畢竟合并代碼手很疼。痛定思痛,沒花多久自己用 C 開發(fā)了一套分布式版本管理系統(tǒng),沒錯就是 Git!后來 Git 越來越流行,比如程序員大型交友網(wǎng)站 Github 使用的就是 Git 存儲。
Tips:類組成元素屬于擴展內(nèi)容,此處有一個整體認識即可。隨著面向?qū)ο蟮纳钊雽W習,知識點都會涵蓋。剛剛我們通過對現(xiàn)實生活中慕課網(wǎng)學生的分析,抽象出了慕課網(wǎng)學生類,并在類中定義了一些屬性和方法。那么除了屬性和方法,類中還可以有什么其他元素呢?下面我們來詳細介紹,每個類都可以由以下元素組成:成員屬性:也稱為字段,成員變量或?qū)嵗兞?,屬性是用以保存每個對象的數(shù)據(jù)的變量,例如每個慕課網(wǎng)學生都可能有昵稱、性別等。每個對象之間的屬性相互獨立,互不干擾;成員方法:也稱實例方法,成員方法是對對象執(zhí)行的操作,例如,一個慕課網(wǎng)學生有一種方法來進行發(fā)表評論;靜態(tài)變量:也稱為類屬性,它是同一個類的任何對象所共有的。例如,一個慕課網(wǎng)學生類中的最后一個被添加的學生 ID,可以用靜態(tài)變量標記。 一個類不管被實例化了多少對象,每個靜態(tài)變量在類中僅存在一次;靜態(tài)方法:也稱為類方法,靜態(tài)方法是不影響特定對象的方法;內(nèi)部類:可以將一個類包含在另一個類中,常用于該類僅提供給聲明它的類使用的情況;構(gòu)造方法:生成新對象的特殊方法;參數(shù)化類型:可以在定義期間將參數(shù)化類型分配給類。 參數(shù)化類型將替換為在類實例化時指定的類型。 它由編譯器完成。 它類似于 C 語言宏#define 語句,其中預(yù)處理器評估宏。
面試官提問: Redis zset 數(shù)據(jù)結(jié)構(gòu)的底層實現(xiàn)是什么?為什么要使用跳躍表?題目解析:在介紹跳躍表(SkipList,簡稱跳表)之前,我們可以以單鏈表數(shù)據(jù)結(jié)構(gòu)作為對比。 (單鏈表結(jié)構(gòu)示意圖)在單鏈表中,我們查詢一個元素的時間復(fù)雜度是 O (N),其中 N 是鏈表的長度,因為需要誒個遍歷節(jié)點,單鏈表不支持數(shù)組的隨機插入和刪除,也不支持數(shù)組的自動排序,顯然不適合作為 zset 的實現(xiàn)方式。跳躍表的基礎(chǔ)定義可參考維基百科定義參考定義,我們給出 C 語言的結(jié)構(gòu)體定義:typedef struct zskiplistNode { sds ele; double score; struct zskiplistNode *backward; struct zskiplistLevel { struct zskiplistNode *forward; unsigned int span; } level[];} zskiplistNode;typedef struct zskiplist { struct zskiplistNode *header, *tail; unsigned long length; int level;} zskiplist;候選人需要描述跳表的數(shù)據(jù)結(jié)構(gòu),可以通過畫圖或者其他方式給出定義。? (典型的跳躍表結(jié)構(gòu),圖片來自網(wǎng)絡(luò),侵刪)從結(jié)構(gòu)體可以看出,節(jié)點有不同的定義:sds:存儲的字符串對象;score:分數(shù),跳表中所有節(jié)點按照 score 由大到小排列;backward:指向后退節(jié)點的指針;forward:指向下一個節(jié)點的指針;level:數(shù)組,數(shù)組中的每一個元素包括了指向其他節(jié)點的指針,level 的長度在 1 到 32(2^5)之間。其中跳表的主要組成結(jié)構(gòu)有:表頭:表示跳表的入口;表尾:表示跳表的尾部,數(shù)值全部都是 NULL;節(jié)點:保存具體數(shù)值,并且具有層結(jié)構(gòu);層:就是上述 level 定義中的單個元素,保存前一個節(jié)點的指針,以及該層下個節(jié)點向前跨越的數(shù)值(span)。跳表的查詢過程本質(zhì)上是自上而下的二分查找,插入和查詢過程都相對復(fù)雜,這里不做贅述。在闡述基本定義之后,我們需要關(guān)注跳躍表的核心特點:本質(zhì)是隨機化數(shù)據(jù)結(jié)構(gòu),可以在對數(shù)(logN)時間內(nèi)完成對數(shù)據(jù)的查找、插入、刪除操作;跳躍表在 Redis 的唯一應(yīng)用,就是作為有序集合支撐底層數(shù)據(jù)結(jié)構(gòu)。
1979 年,MySQL 的歷史最早可以追溯到 1979 年,有一個人叫 Monty Widenius 在一個名為 TcX 的小公司打工并用 BASIC 設(shè)計了一個報表工具,可以在 4M 主頻和 16KB 內(nèi)存的計算機上運行。過了不久,又將此工具使用 C 語言重寫,移植到 Unix 平臺,當時,它只是一個很底層的面向報表的存儲引擎;1996 年,MySQL 1.0 發(fā)布,只面向一小撥人,相當于內(nèi)部發(fā)布。到了 96 年 10 月,MySQL 3.11.1 發(fā)布了;1999 - 2000 年,有一家公司在瑞典成立了,叫 MySQL AB (AB 是瑞典語“股份公司”的意思)。 雇了幾個人,與 Sleepycat 合作,開發(fā)出了 Berkeley DB 引擎, 因為 BDB 支持事務(wù)處理,所以,MySQL 從此開始支持事務(wù)處理了;2000 年 4 月,MySQL 對舊的存儲引擎進行了整理,命名為 MyISAM。同時,2001 年,Heikiki Tuuri 向 MySQL 提出建議,希望能集成他們的存儲引擎 InnoDB ,這個引擎同樣支持事務(wù)處理,還支持行級鎖;2004 年 10 月,發(fā)布了經(jīng)典的 4.1 版本。 2005 年 10 月,有發(fā)布了里程碑的一個版本,MySQL 5.0. 在5.0 中加入了游標,存儲過程,觸發(fā)器,視圖和事務(wù)的支持。在 5.0 之后的版本里,MySQL 明確地表現(xiàn)出邁向高性能數(shù)據(jù)庫的發(fā)展步伐;2011 年 4 月,MySQL 5.6 發(fā)布,作為被 Oracle 收購后,第一個正式發(fā)布并做了大量變更的版本(5.5版本主要是對社區(qū)開發(fā)的功能的集成),對復(fù)制模式,優(yōu)化器等做了大量的變更,其中最重要的主從 GTID 復(fù)制模式,大大降低了 MySQL 高可用操作的復(fù)雜性;2016 年 9 月,Oracle 決定跳過 MySQL 5.x 命名系列,并拋棄之前的 MySQL 6,7 兩個分支(6,7 是兩個從來沒有對外發(fā)布的兩個分支),直接進入 MySQL 8 版本命名,自此正式進入 MySQL 8.0 時代。
1985 年,著名美國黑客理查德?斯托曼創(chuàng)立了 GNU 項目計劃。 1985 年,同年查德?斯托曼創(chuàng)立了自由軟件基金會(Free Software Foundation)來為 GNU 計劃提供技術(shù)、法律以及資金支持。盡管 GNU 計劃大部分時候是由大家自愿無償貢獻的,但 FSF 還是會聘請程序員幫助編寫。當 GNU 計劃開始逐漸獲得成功時,一些商業(yè)公司開始介入開發(fā)和技術(shù)支持。當中最著名的就是之后被 Red Hat 紅帽兼并的 Cygnus Solutions。1990 年,成熟的 GNU 計劃已經(jīng)開發(fā)出的軟件包括了一個功能強大的文字編輯器 Emacs、C 語言編譯器 GCC 以及大部分 UNIX 系統(tǒng)的程序庫和工具。唯一依然沒有完成的重要組件,就是操作系統(tǒng)的內(nèi)核。1991 年,Linux 之父林納斯?托瓦茲編寫出了與 UNIX 兼容的 Linux 操作系統(tǒng)內(nèi)核并在 GPL 條款下發(fā)布。Linux 之后在網(wǎng)上廣泛流傳,許多程序員參與了開發(fā)與修改。1992 年,林納斯的 Linux 與其他 GNU 軟件開始結(jié)合,完全自由的 Linux 操作系統(tǒng)正式誕生。該操作系統(tǒng)往往被稱為 “GNU/Linux” 或簡稱 Linux。許多 UNIX 系統(tǒng)上也安裝了 GNU 軟件,因為 GNU 軟件的質(zhì)量比之前 UNIX 的軟件還要好。GNU 工具還被廣泛地移植到 Windows 和蘋果 Mac OS 上。現(xiàn)在,GNU 項目計劃十幾年以來已經(jīng)成為一個對軟件開發(fā)主要的影響力量,創(chuàng)造了無數(shù)的重要的工具,例如:編譯器,文本編輯器,甚至一個全功能的操作系統(tǒng)。這個工程是從 1984 年麻省理工學院的程序員理查德?斯托曼的想法得來的,他想要創(chuàng)建一個自由的和 UNIX 類似的操作環(huán)境。從那時開始,許多程序員聚集起來開始開發(fā)一個自由的、高質(zhì)量、易理解的軟件。
我們知道計算機網(wǎng)絡(luò)中連接的設(shè)備有很多,比如 PC、手機、打印機、路由器、交換機、網(wǎng)關(guān)等,通常把這些網(wǎng)絡(luò)設(shè)備叫做節(jié)點(Node)。每一個節(jié)點都分配有唯一的 IP 地址,用以標識此設(shè)備。IP 地址包含 32 位 IPv4 和 128 位 IPv6 兩個版本。由于 IP 地址是一串數(shù)字或者是字節(jié)序列,對計算機是友好的,但是對我們?nèi)祟惙浅2挥押茫焕趥鞑?、記憶。為此,計算機科學家又開發(fā)了一套 DNS 系統(tǒng),給每一臺計算機分配了唯一的、對人類友好的主機名字,通常叫做域名。比如,idcbgp.cn 是慕課網(wǎng)主站的域名。當然,有的主機會分配多個域名。人們常說生活沒有那么簡單,往往是解決了一個老問題,又引出了新問題。當你開發(fā)了 DNS 系統(tǒng)以后,我們?nèi)祟惔_實方便了,可是域名對計算機來說不方便,計算機更喜歡 IP 地址。這就又需要解決 IP 地址和域名之間相互解析、映射的問題,當然這些問題在 DNS 系統(tǒng)中都得到了妥善的處理。域名解析系統(tǒng)是一個分布式集群系統(tǒng),是一個樹形結(jié)構(gòu)。一次域名解析可能需要經(jīng)過本地緩存、本地域名服務(wù)器、遠程域名服務(wù)器之間多次交互。從上面的描述可以看出,IP 地址和域名之間的相互解析是一套非常復(fù)雜的機制。好在操作系統(tǒng)將這一套復(fù)雜的機制進行了封裝,以 API 的形式提供給網(wǎng)絡(luò)程序員,這樣極大的簡化了編程的復(fù)雜度。一般操作系統(tǒng)都提供了 C 語言接口 getaddrinfo 和 getnameinfo,前者的功能是通過域名獲取 IP 地址,后者的功能是通過 IP 地址獲取域名。在 Java 平臺中,java.net.InetAddress 類實現(xiàn)了完整的 IP 地址和域名之間的相互解析機制。
我們來看 leetcode 的第 15 題:三數(shù)之和。該題的難度為中等,題目內(nèi)容如下:給你一個包含 n 個整數(shù)的數(shù)組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有滿足條件且不重復(fù)的三元組。**注意:**答案中不可以包含重復(fù)的三元組。示例:給定數(shù)組 nums = [-1, 0, 1, 2, -1, -4],滿足要求的三元組集合為:[ [-1, 0, 1], [-1, -1, 2]]我們今天并不打算通過這道題的題解,因為這道題用遞歸算法是無法通過題解的,原因和之前一樣,算法的時間復(fù)雜度高,最后會超出時間限制。另外我們?nèi)サ艉竺娴淖⒁獠糠质马?,允許答案包含重復(fù)的三元組,我們使用遞歸算法相當于窮舉出所有可能的情況,判斷三元組的值是否能為 0。首先繼續(xù)我們的解題三部曲:函數(shù)定義,輸入和輸出:def three_sum(nums, target, count): """ 輸入: num: 輸入的數(shù)組 target: 目標值 count: 在數(shù)組中找到幾個數(shù)之和滿足target 輸出: []或者[[1,2,3], [-1,4,3]] 這樣的滿足條件的全部結(jié)果 """ res = [] # ... return res注意: 定義這樣的遞歸函數(shù)是經(jīng)過思考的,因為后續(xù)遞歸調(diào)用時需要依賴目標值 (target) 或元素個數(shù) (count) 這樣兩個參數(shù)。返回的參數(shù)要么為空,要么是所有找到的滿足條件的三元組的集合。接下來是遞歸方法的終止條件,首先考慮以下幾個終止條件:如果輸入的 nums 列表為空,那么直接返回 [];如果輸入的 count 等于1,就要開始判斷了,因為這個時候只需要判斷 target 是否在列表中存在即可;綜上,我們寫出終止條件的代碼:def three_sum(nums, target, count): """ 輸入: num: 輸入的數(shù)組 target: 目標值 count: 在數(shù)組中找到幾個數(shù)之和滿足target 輸出: []或者[[1,2,3], [-1,4,3]] 這樣的滿足條件的全部結(jié)果 """ res = [] ###################### 終止條件 ###################################### if not nums: return res if count == 1 and target in nums: return [[ target ]] elif count == 1 and target not in nums: # count等于1時,如果target沒有出現(xiàn)在剩余的nums中,說明不存在滿足條件的數(shù)組元素 return res ####################################################################### # 返回值 return res接下來最重要的,就是遞歸的公式了,遞歸的方向一定要朝著減小目標函數(shù)規(guī)模進行。很明顯,我們的遞歸應(yīng)該是這樣子:以 nums 的第一個元素為遞歸點,整個 nums 列表中和為 target 的 count 個元素的結(jié)果可以分為包含 nums[0] 和不包含 nums[0] 的結(jié)果組成,簡單點說就是:如果包含 nums[0],那么接下來的 nums[1:] 列表中我們就要找值為 target - nums[0] 的 count - 1 個元素,也即 three_sum(nums[1:], target - nums[0], count -1),然后我們還需要在得到的元組集中的最前面位置加上 nums[0];如果不包含 nums[0],那么就是在 nums[1:] 列表中找值為 target 的 count 個元素,用遞歸函數(shù)表示就是 three_sum(nums[1:], target, count);這樣找到的結(jié)果正是 count 個元素。組合上述兩個遞歸得到的結(jié)果,就得到了函數(shù) three_sum(nums, target, count) 的結(jié)果,代碼如下:res = []# 包含nums[0]t1 = three_sum(nums[1:], target - nums[0], count - 1)# 不包含nums[0]t2 = three_sum(nums[1:], target, count)if t1: for i in range(len(t1)): t = [nums[0]] t.extend(t1[i]) # 每個得到的結(jié)果前面加上 nums[0] res.append(t)if t2: for j in range(len(t2)): res.append(t2[j]) # 此時得到的res就是遞歸的最后結(jié)果綜合就可以得到遞歸遍歷所有三個元素和的情況并最終找出所有滿足條件結(jié)果的三元集:def three_sum(nums, target, count): res = [] # 終止條件 if not nums: return res if count == 1 and target in nums: # 一定要這樣寫 return [[ target ]] elif count == 1 and target not in nums: return res # 包含nums[0] t1 = three_sum(nums[1:], target - nums[0], count - 1) # 不包含nums[0] t2 = three_sum(nums[1:], target, count) if t1: for i in range(len(t1)): # 犯了一個巨大的錯誤,extend() 方法的使用,它無返回,只會擴充原數(shù)組 # res.append([nums[0]].extend(t1[i])) t = [nums[0]] t.extend(t1[i]) res.append(t) if t2: for j in range(len(t2)): res.append(t2[j]) return res調(diào)用該函數(shù)的方式如下:nums = [-1, 0, 1, 2, -1, -4]# 0 為目標值,3為多少個元素和為targetres = three_sum(nums, 0, 3)這樣的遞歸遍歷思想在窮舉中用的比較多,因為它以非常優(yōu)雅的方式簡化了窮舉代碼。不過這道題使用遞歸算法等價于窮舉法,時間復(fù)雜度為 O(n3)O(n^3)O(n3),因此顯得并不高效。對于最優(yōu)的解法讀者可以自行思考下然后解決它。
文件測試在我們編寫 shell 中與文件操作非常常用,熟練掌握文件操作可以在后續(xù)的 shell 編寫中得心應(yīng)手,例如 file 變量為:操作符說明舉例-dfile 檢測文件是否是目錄,如果是,則返回 true。[-d $file] 返回 false。-ffile 檢測文件是否是普通文件(既不是目錄,也不是設(shè)備文件),如果是,則返回 true。[-f $file] 返回 true。-cfile 檢測文件是否是字符設(shè)備文件,如果是,則返回 true。[-c $file] 返回 false。-bfile 檢測文件是否是塊設(shè)備文件,如果是,則返回 true。[-b $file] 返回 false。-gfile 檢測文件是否設(shè)置了 SGID 位,如果是,則返回 true。[-g $file] 返回 false。-ufile 檢測文件是否設(shè)置了 SUID 位,如果是,則返回 true。[-u $file] 返回 false。-kfile 檢測文件是否設(shè)置了粘著位 (Sticky Bit),如果是,則返回 true。[-k $file] 返回 false。-pfile 檢測文件是否是有名管道,如果是,則返回 true。[-p $file] 返回 false。-rfile 檢測文件是否可讀,如果是,則返回 true。[-r $file] 返回 true。-wfile 檢測文件是否可寫,如果是,則返回 true。[-w $file] 返回 true。-xfile 檢測文件是否可執(zhí)行,如果是,則返回 true。[-x $file] 返回 true。-sfile 檢測文件是否為空(文件大小是否大于 0),不為空返回 true。[-s $file] 返回 true。-efile 檢測文件(包括目錄)是否存在,如果是,則返回 true。[-e $file] 返回 true。例如:#!/bin/bashTEST_FILE="/etc/fstab"echo "檢測的文件為:${TEST_FILE}"echo "文件信息為:$(ls -l ${TEST_FILE})"if [ -r $TEST_FILE ]then echo "文件可讀"else echo "文件不可讀"fiif [ -w $TEST_FILE ]then echo "文件可寫"else echo "文件不可寫"fiif [ -x $TEST_FILE ]then echo "文件可執(zhí)行"else echo "文件不可執(zhí)行"fiif [ -f $TEST_FILE ]then echo "文件為普通文件"else echo "文件為特殊文件"fiif [ -d $TEST_FILE ]then echo "文件是個目錄"else echo "文件不是個目錄"fiif [ -s $TEST_FILE ]then echo "文件不為空"else echo "文件為空"fiif [ -e $TEST_FILE ]then echo "文件存在"else echo "文件不存在"fi返回為:檢測的文件為:/etc/fstab文件信息為:-rw-r--r--. 1 root root 500 Jan 17 14:23 /etc/fstab文件可讀文件可寫文件不可執(zhí)行文件為普通文件文件不是個目錄文件不為空文件存在
JConsole 是 JDK 自帶的 Java 進程監(jiān)控工具,Zookeeper 是基于 Java 的應(yīng)用程序,也支持 JMX( Java Management Extensions ),所以我們可以通過 JConsole 來對 Zookeeper 的運行狀態(tài)進行監(jiān)控。在使用 JConsole 開啟監(jiān)控之前,我們需要修改 Zookeeper 關(guān)于 JMX 的配置。如果是 Windows 平臺的需要修改啟動文件 zkServer.cmd,如果是 Linux 平臺則需要修改啟動文件 zkServer.sh。Windows 平臺修改 zkServer.cmd在 call %JAVA% 這一行中加入以下配置:# 對 jmx 開放的端口,要注意避免和其它端口沖突"-Dcom.sun.management.jmxremote.port=21810"# 關(guān)閉 ssl"-Dcom.sun.management.jmxremote.ssl=false"# 關(guān)閉身份驗證"-Dcom.sun.management.jmxremote.authenticate=false"zkServer.cmd 完整的配置如下:@echo offREM Licensed to the Apache Software Foundation (ASF) under one or moreREM contributor license agreements. See the NOTICE file distributed withREM this work for additional information regarding copyright ownership.REM The ASF licenses this file to You under the Apache License, Version 2.0REM (the "License"); you may not use this file except in compliance withREM the License. You may obtain a copy of the License atREMREM http://www.apache.org/licenses/LICENSE-2.0REMREM Unless required by applicable law or agreed to in writing, softwareREM distributed under the License is distributed on an "AS IS" BASIS,REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.REM See the License for the specific language governing permissions andREM limitations under the License.setlocalcall "%~dp0zkEnv.cmd"set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMainset ZOO_LOG_FILE=zookeeper-%USERNAME%-server-%COMPUTERNAME%.logecho oncall %JAVA% "-Dcom.sun.management.jmxremote.port=21810" "-Dcom.sun.management.jmxremote.ssl=false" "-Dcom.sun.management.jmxremote.authenticate=false" "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" "-Dzookeeper.log.file=%ZOO_LOG_FILE%" "-XX:+HeapDumpOnOutOfMemoryError" "-XX:OnOutOfMemoryError=cmd /c taskkill /pid %%%%p /t /f" -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %*endlocal Linux 平臺修改 zkServer.sh在第一個 ZOOMAIN 中添加以下配置:# 關(guān)閉僅本地連接-Dcom.sun.management.jmxremote.local.only=false# zookeeper地址-Djava.rmi.server.hostname=127.0.0.1 # 對 jmx 開放的端口,要注意避免和其它端口沖突-Dcom.sun.management.jmxremote.port=21810 # 關(guān)閉 ssl-Dcom.sun.management.jmxremote.ssl=false # 關(guān)閉身份驗證-Dcom.sun.management.jmxremote.authenticate=false # 開啟日志-Dzookeeper.jmx.log4j.disable=true添加完畢后第一個 ZOOMAIN 配置如下:ZOOMAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.port=21810 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dzookeeper.jmx.log4j.disable=true org.apache.zookeeper.server.quorum.QuorumPeerMain"配置完啟動文件,我們就可以重啟 Zookeeper 服務(wù)端,打開 JConsole 界面,輸入 Zookeeper 地址和開放的 JMX 端口,然后就能監(jiān)控 Zookeeper 的 Java 進程了。除了使用 JConsole 來監(jiān)控 Zookeeper 進程的運行狀態(tài)之外,我們還可以使用 Zookeeper 提供的四字監(jiān)控命令來查看Zookeeper 進程的運行狀態(tài),那么接下來我們就來學習 Zookeeper 的四字監(jiān)控命令。
Pandas 時間序列重采樣,是指將時間序列從一個頻率轉(zhuǎn)換為另外一個頻率的操作,主要是通過函數(shù) resample () 進行實現(xiàn)的,采樣又可以分為降采樣(高頻率的數(shù)據(jù)聚合到低頻率的數(shù)據(jù)),升采樣(低頻率數(shù)據(jù)轉(zhuǎn)換為高頻率數(shù)據(jù))。resample(rule, axis=0, closed=None, label=None, convention='start', kind=None, loffset=None, base=None, on=None, level=None, origin='start_day', offset=None)下面是該函數(shù)常用參數(shù)的說明:參數(shù)名說明 rule 轉(zhuǎn)換的偏移規(guī)則 axis 默認是縱軸 (axis=0)closed 降采樣時,哪一端是閉合的,默認是 rightlabel 降采樣時,設(shè)置聚合值的標簽 convention 重采樣時期時,低頻率轉(zhuǎn)換為高頻率所采用的約定,默認是 end下面我們通過代碼演示一下該函數(shù)的使用:# 導入pandas包import pandas as pddata_path="C:/Users/13965/Documents/myFuture/IMOOC/pandasCourse-progress/data_source/第25小節(jié)/execl數(shù)據(jù)demo.xlsx"# 解析數(shù)據(jù)data = pd.read_excel(data_path)print(data)# --- 輸出結(jié)果 --- 時間 人數(shù) 銷售數(shù)量0 2020-01-02 12:25:00 12 2341 2020-01-02 12:30:00 23 3002 2020-01-02 12:35:00 44 3453 2020-01-02 12:40:00 56 4014 2020-01-02 12:45:00 60 4205 2020-01-02 12:50:00 70 623# 將時間列的 str 值轉(zhuǎn)換為 時間戳索引data.index=pd.to_datetime(data['時間'])del(data['時間'])print(data)# --- 輸出結(jié)果 --- 人數(shù) 銷售數(shù)量時間 2020-01-02 12:25:00 12 2342020-01-02 12:30:00 23 3002020-01-02 12:35:00 44 3452020-01-02 12:40:00 56 4012020-01-02 12:45:00 60 4202020-01-02 12:50:00 70 623 # 1. 降采樣 我們之前的采樣的頻率為 5 分鐘,我們將采樣頻率調(diào)整為 10 分鐘ss=data.resample('10MIN').mean()print(ss)# --- 輸出結(jié)果 --- 人數(shù) 銷售數(shù)量時間 2020-01-02 12:20:00 12.0 234.02020-01-02 12:30:00 33.5 322.52020-01-02 12:40:00 58.0 410.52020-01-02 12:50:00 70.0 623.0 # 2.升采樣 我們將之前的采樣頻率 5 分鐘,改成 2 分鐘進行重采樣,這里注意 默認使用 NaN 進行填充ss=data.resample('3MIN').mean()print(ss)# --- 輸出結(jié)果 --- 人數(shù) 銷售數(shù)量時間 2020-01-02 12:24:00 12.0 234.02020-01-02 12:27:00 NaN NaN2020-01-02 12:30:00 23.0 300.02020-01-02 12:33:00 44.0 345.02020-01-02 12:36:00 NaN NaN2020-01-02 12:39:00 56.0 401.02020-01-02 12:42:00 NaN NaN2020-01-02 12:45:00 60.0 420.02020-01-02 12:48:00 70.0 623.0
根據(jù)上面的分析,我們來實現(xiàn)相應(yīng)的代碼。首先是完成獲取計算機的所有分類以及相應(yīng)的 URL 地址:def get_all_computer_book_urls(page_url): """ 獲取所有計算機分類圖書的url地址 :return: """ response = requests.get(url=page_url, headers=headers) if response.status_code != 200: return [], [] response.encoding = 'gbk' tree = etree.fromstring(response.text, etree.HTMLParser()) # 提取計算機分類的文本列表 c = tree.xpath("http://div[@id='wrap']/ul[1]/li[@class='li']/a/text()") # 提取計算機分類的url列表 u = tree.xpath("http://div[@id='wrap']/ul[1]/li[@class='li']/a/@href") return c, u我們簡單測試下這個函數(shù):[store@server2 chap06]$ python3Python 3.6.8 (default, Apr 2 2020, 13:34:55) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.>>> from china_pub_crawler import get_all_computer_book_urls>>> get_all_computer_book_urls('http://www.china-pub.com/Browse/')(['IT圖書網(wǎng)絡(luò)出版 [59-00]', '計算機科學理論與基礎(chǔ)知識 [59-01]', '計算機組織與體系結(jié)構(gòu) [59-02]', '計算機網(wǎng)絡(luò) [59-03]', '安全 [59-04]', '軟件與程序設(shè)計 [59-05]', '軟件工程及軟件方法學 [59-06]', '操作系統(tǒng) [59-07]', '數(shù)據(jù)庫 [59-08]', '硬件與維護 [59-09]', '圖形圖像、多媒體、網(wǎng)頁制作 [59-10]', '中文信息處理 [59-11]', '計算機輔助設(shè)計與工程計算 [59-12]', '辦公軟件 [59-13]', '專用軟件 [59-14]', '人工智能 [59-15]', '考試認證 [59-16]', '工具書 [59-17]', '計算機控制與仿真 [59-18]', '信息系統(tǒng) [59-19]', '電子商務(wù)與計算機文化 [59-20]', '電子工程 [59-21]', '期刊 [59-22]', '游戲 [59-26]', 'IT服務(wù)管理 [59-27]', '計算機文化用品 [59-80]'], ['http://product.china-pub.com/cache/browse2/59/1_1_59-00_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-01_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-02_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-03_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-04_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-05_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-06_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-07_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-08_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-09_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-10_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-11_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-12_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-13_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-14_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-15_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-16_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-17_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-18_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-19_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-20_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-21_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-22_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-26_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-27_0.html', 'http://product.china-pub.com/cache/browse2/59/1_1_59-80_0.html'])可以看到這個函數(shù)已經(jīng)實現(xiàn)了我們想要的結(jié)果。接下來我們要完成一個函數(shù)來獲取對應(yīng)分類下的所有圖書信息,不過在此之前,我們需要先完成解析單個圖書列表頁面的方法:def parse_books_page(html_data): books = [] tree = etree.fromstring(html_data, etree.HTMLParser()) result_tree = tree.xpath("http://div[@class='search_result']/table/tr/td[2]/ul") for result in result_tree: try: book_info = {} book_info['title'] = result.xpath("./li[@class='result_name']/a/text()")[0] book_info['book_url'] = result.xpath("./li[@class='result_name']/a/@href")[0] info = result.xpath("./li[2]/text()")[0] book_info['author'] = info.split('|')[0].strip() book_info['publisher'] = info.split('|')[1].strip() book_info['isbn'] = info.split('|')[2].strip() book_info['publish_date'] = info.split('|')[3].strip() book_info['vip_price'] = result.xpath("./li[@class='result_book']/ul/li[@class='book_dis']/text()")[0] book_info['price'] = result.xpath("./li[@class='result_book']/ul/li[@class='book_price']/text()")[0] # print(f'解析出的圖書信息為:{book_info}') books.append(book_info) except Exception as e: print("解析數(shù)據(jù)出現(xiàn)異常,忽略!") return books上面的函數(shù)主要解析的是一頁圖書列表數(shù)據(jù),同樣基于 xpath 定位相應(yīng)的元素,然后提取我們想要的數(shù)據(jù)。其中由于部分信息合在一起,我們在提取數(shù)據(jù)后還要做相關(guān)的處理,分別提取對應(yīng)的信息。我們可以從網(wǎng)頁中直接樣 HTML 拷貝下來,然后對該函數(shù)進行測試:提取圖書列表的網(wǎng)頁數(shù)據(jù)我們把保存的網(wǎng)頁命名為 test.html,放到與該代碼同級的目錄下,然后進入命令行操作:>>> from china_pub_crawler import parse_books_page>>> f = open('test.html', 'r+')>>> html_content = f.read()>>> parse_books_page(html_content)[{'title': '(特價書)零基礎(chǔ)學ASP.NET 3.5', 'book_url': 'http://product.china-pub.com/216269', 'author': '王向軍;王欣惠 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111261414', 'publish_date': '2009-02-01出版', 'vip_price': 'VIP會員價:', 'price': '¥58.00'}, {'title': 'Objective-C 2.0 Mac和iOS開發(fā)實踐指南(原書第2版)', 'book_url': 'http://product.china-pub.com/3770704', 'author': '(美)Robert Clair (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111484561', 'publish_date': '2015-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥79.00'}, {'title': '(特價書)ASP.NET 3.5實例精通', 'book_url': 'http://product.china-pub.com/216272', 'author': '王院峰 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111259794', 'publish_date': '2009-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥55.00'}, {'title': '(特價書)CSS+HTML語法與范例詳解詞典', 'book_url': 'http://product.china-pub.com/216275', 'author': '符旭凌 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111263647', 'publish_date': '2009-02-01出版', 'vip_price': 'VIP會員價:', 'price': '¥39.00'}, {'title': '(特價書)Java ME 游戲編程(原書第2版)', 'book_url': 'http://product.china-pub.com/216296', 'author': '(美)Martin J. Wells; John P. Flynt (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111264941', 'publish_date': '2009-03-01出版', 'vip_price': 'VIP會員價:', 'price': '¥49.00'}, {'title': '(特價書)Visual Basic實例精通', 'book_url': 'http://product.china-pub.com/216304', 'author': '柴相花 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111263296', 'publish_date': '2009-04-01出版', 'vip_price': 'VIP會員價:', 'price': '¥59.80'}, {'title': '高性能電子商務(wù)平臺構(gòu)建:架構(gòu)、設(shè)計與開發(fā)[按需印刷]', 'book_url': 'http://product.china-pub.com/3770743', 'author': 'ShopNC產(chǎn)品部 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111485643', 'publish_date': '2015-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥79.00'}, {'title': '[套裝書]Java核心技術(shù) 卷Ⅰ 基礎(chǔ)知識(原書第10版)+Java核心技術(shù) 卷Ⅱ高級特性(原書第10版)', 'book_url': 'http://product.china-pub.com/7008447', 'author': '(美)凱S.霍斯特曼(Cay S. Horstmann)????(美)凱S. 霍斯特曼(Cay S. Horstmann) (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787007008447', 'publish_date': '2017-08-01出版', 'vip_price': 'VIP會員價:', 'price': '¥258.00'}, {'title': '(特價書)Dojo構(gòu)建Ajax應(yīng)用程序', 'book_url': 'http://product.china-pub.com/216315', 'author': '(美)James E.Harmon (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111266648', 'publish_date': '2009-05-01出版', 'vip_price': 'VIP會員價:', 'price': '¥45.00'}, {'title': '(特價書)編譯原理第2版.本科教學版', 'book_url': 'http://product.china-pub.com/216336', 'author': '(美)Alfred V. Aho;Monica S. Lam;Ravi Sethi;Jeffrey D. Ullman (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111269298', 'publish_date': '2009-05-01出版', 'vip_price': 'VIP會員價:', 'price': '¥55.00'}, {'title': '(特價書)用Alice學編程(原書第2版)', 'book_url': 'http://product.china-pub.com/216354', 'author': '(美)Wanda P.Dann;Stephen Cooper;Randy Pausch (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111274629', 'publish_date': '2009-07-01出版', 'vip_price': 'VIP會員價:', 'price': '¥39.00'}, {'title': 'Java語言程序設(shè)計(第2版)', 'book_url': 'http://product.china-pub.com/50051', 'author': '趙國玲;王宏;柴大鵬 (著)', 'publisher': '機械工業(yè)出版社*', 'isbn': '9787111297376', 'publish_date': '2010-03-01出版', 'vip_price': 'VIP會員價:', 'price': '¥32.00'}, {'title': '從零開始學Python程序設(shè)計', 'book_url': 'http://product.china-pub.com/7017939', 'author': '吳惠茹 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111583813', 'publish_date': '2018-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥79.00'}, {'title': '(特價書)匯編語言', 'book_url': 'http://product.china-pub.com/216385', 'author': '鄭曉薇 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111269076', 'publish_date': '2009-09-01出版', 'vip_price': 'VIP會員價:', 'price': '¥29.00'}, {'title': '(特價書)Visual Basic.NET案例教程', 'book_url': 'http://product.china-pub.com/216388', 'author': '馬玉春;劉杰民;王鑫 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111272571', 'publish_date': '2009-09-01出版', 'vip_price': 'VIP會員價:', 'price': '¥30.00'}, {'title': '小程序從0到1:微信全棧工程師一本通', 'book_url': 'http://product.china-pub.com/7017943', 'author': '石橋碼農(nóng) (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111584049', 'publish_date': '2018-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥59.00'}, {'title': '深入分布式緩存:從原理到實踐', 'book_url': 'http://product.china-pub.com/7017945', 'author': '于君澤 (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111585190', 'publish_date': '2018-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥99.00'}, {'title': '(特價書)ASP.NET AJAX服務(wù)器控件高級編程(.NET 3.5版)', 'book_url': 'http://product.china-pub.com/216397', 'author': '(美)Adam Calderon;Joel Rumerman (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111270966', 'publish_date': '2009-09-01出版', 'vip_price': 'VIP會員價:', 'price': '¥65.00'}, {'title': 'PaaS程序設(shè)計', 'book_url': 'http://product.china-pub.com/3770830', 'author': '(美)Lucas Carlson (著)', 'publisher': '機械工業(yè)出版社', 'isbn': '9787111482451', 'publish_date': '2015-01-01出版', 'vip_price': 'VIP會員價:', 'price': '¥39.00'}, {'title': 'Visual C++數(shù)字圖像處理[按需印刷]', 'book_url': 'http://product.china-pub.com/2437', 'author': '何斌 馬天予 王運堅 朱紅蓮 (著)', 'publisher': '人民郵電出版社', 'isbn': '711509263X', 'publish_date': '2001-04-01出版', 'vip_price': 'VIP會員價:', 'price': '¥72.00'}]是不是能正確提取圖書列表的相關(guān)信息?這也說明我們的函數(shù)的正確性,由于也可能在解析中存在一些異常,比如某個字段的缺失,我們需要捕獲異常并忽略該條數(shù)據(jù),讓程序能繼續(xù)走下去而不是停止運行。在完成了上述的工作后,我們來通過對頁號的 URL 構(gòu)造,實現(xiàn)采集多個分頁下的數(shù)據(jù),最后達到讀取完該分類下的所有圖書信息的目的。完整代碼如下:def get_category_books(category, url): """ 獲取類別圖書,下面會有分頁,我們一直請求,直到分頁請求返回404即可停止 :return: """ books = [] page = 1 regex = "(http://.*/)([0-9]+)_(.*).html" pattern = re.compile(regex) m = pattern.match(url) if not m: return [] prefix_path = m.group(1) current_page = m.group(2) if current_page != 1: print("提取數(shù)據(jù)不是從第一行開始,可能存在問題") suffix_path = m.group(3) current_page = page while True: # 構(gòu)造分頁請求的URL book_url = f"{prefix_path}{current_page}_{suffix_path}.html" response = requests.get(url=book_url, headers=headers) print(f"提取分類[{category}]下的第{current_page}頁圖書數(shù)據(jù)") if response.status_code != 200: print(f"[{category}]該分類下的圖書數(shù)據(jù)提取完畢!") break response.encoding = 'gbk' # 將該分頁的數(shù)據(jù)加到列表中 books.extend(parse_books_page(response.text)) current_page += 1 # 一定要緩一緩,避免對對方服務(wù)造成太大壓力 time.sleep(0.5) return books最后保存數(shù)據(jù)到 MongoDB 中,這一步非常簡單,我們前面已經(jīng)操作過 MongoDB 的文檔插入,直接搬用即可:client = pymongo.MongoClient(host='MongoDB的服務(wù)地址', port=27017)client.admin.authenticate("admin", "shencong1992")db = client.scrapy_manualcollection = db.china_pub# ...def save_to_mongodb(data): try: collection.insert_many(data) except Exception as e: print("批量插入數(shù)據(jù)異常:{}".format(str(e)))正是由于我們前面生成了批量的 json 數(shù)據(jù),這里直接使用集合的 insert_many() 方法即可對采集到的數(shù)據(jù)批量插入 MongoDB 中。代碼的最后我們加上一個 main 函數(shù)即可:# ...if __name__ == '__main__': page_url = "http://www.china-pub.com/Browse/" categories, urls = get_all_computer_book_urls(page_url) # print(categories) books_total = {} for i in range(len(urls)): books_category_data = get_category_books(categories[i], urls[i]) print(f"保存[{categories[i]}]圖書數(shù)據(jù)到mongodb中") save_to_mongodb(books_category_data) print("爬取互動出版網(wǎng)的計算機分類數(shù)據(jù)完成")這樣一個簡單的爬蟲就完成了,還等什么,開始跑起來吧!!
以下命令說明按 subject 整理,并列出了每個 subject 適用的 verb 和選項組合。查看 APK 文件屬性命令選項說明apk summary輸出應(yīng)用 ID、版本代碼和版本名稱。apk file-size輸出 APK 的總文件大小。apk download-size輸出 APK 的下載大小估計值。apk features輸出 APK 用來觸發(fā) Play 商店過濾的功能。apk compare比較 apk-file 和 apk-file2 的大小。 --different-only:輸出存在差異的目錄和文件。 --files-only:不輸出目錄條目。 --patch-size:顯示逐個文件的補丁程序大小估計值,而不是原始差異。**查看 APK 文件系統(tǒng) **命令選項說明files list列出 APK 中的所有文件。files cat --file輸出文件內(nèi)容。必須使用 --file path 選項指定 APK 內(nèi)的路徑。查看清單中的信息命令選項說明manifest print以 XML 格式輸出 APK 清單。manifest application-id輸出應(yīng)用 ID 值。manifest version-name輸出版本名稱值。manifest version-code輸出版本代碼值。manifest min-sdk輸出最低 SDK 版本。manifest target-sdk輸出目標 SDK 版本。manifest permissions輸出權(quán)限列表。manifest debuggable輸出應(yīng)用是否可調(diào)試。訪問 DEX 文件信息命令選項說明dex list輸出 APK 中的 DEX 文件列表。dex references輸出指定 DEX 文件中的方法引用數(shù)。dex packages輸出 DEX 中的類樹。在輸出中,P、C、M 和 F 分別表示軟件包、類、方法和字段。–defined-only:在輸出中僅包含 APK 中定義的類。–files:指定要包含的 DEX 文件名。默認:所有 DEX 文件。–proguard-folder file:指定用于搜索映射的 Proguard 輸出文件夾。–proguard-mappings file:指定 Proguard 映射文件。–proguard-seeds file:指定 Proguard 種子文件。–proguard-usages file:指定 Proguard 用法文件。dex code --class以 smali 格式輸出類或方法的字節(jié)碼。輸出中必須包含類名,并且要輸出完全限定類名以進行反編譯??创鎯υ?APK 的資源命令選項說明resources packages輸出資源表中定義的軟件包列表。resources configs --type輸出指定 type 的配置列表。type 是資源類型,如 string。resources value --config --name --type輸出由 config、name 和 type 指定的資源的值。resources names --config --type輸出屬于某種配置和類型的資源名稱列表。resources xml --file以簡單易懂的形式輸出 XML 二進制文件。
這里我們將使用前面提到的 mysqlclient 模塊來操作 MySQL 數(shù)據(jù)庫。第一步安裝 mysqlclient 模塊:$ pip3 install mysqlclient -i https://pypi.tuna.tsinghua.edu.cn/simple 安裝好了之后,我們可以在 python 解釋器中導入下模塊:[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import MySQLdb>>> MySQLdb.__version__'1.4.6'>>> 我們事先準備好了一個 MySQL 服務(wù), 部署在云服務(wù)器上。本地安裝好 mysql 客戶端,然后通過如下方式連接 MySQL 數(shù)據(jù)庫:[shen@shen ~]$ mysql -h 180.76.152.113 -P 9002 -u store -pstore.123@mysql: [Warning] Using a password on the command line interface can be insecure.Welcome to the MySQL monitor. Commands end with ; or \g.Your MySQL connection id is 68920Server version: 5.7.26 MySQL Community Server (GPL) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> 新建一個數(shù)據(jù)庫,名為 django-manual,然后在該數(shù)據(jù)庫中新建了一個簡單的 user 表。接下來我們會使用 mysqlclient 模塊對該 user 表中的數(shù)據(jù)進行增刪改查操作:mysql> create database django_manual default charset utf8;Query OK, 1 row affected (0.14 sec)mysql> use django_manualDatabase changedMySQL [django_manual]> show tables;Empty set (0.00 sec)mysql> CREATE TABLE `user` ( -> `id` int(11) NOT NULL AUTO_INCREMENT, -> `name` char(30) NOT NULL, -> `password` char(10) NOT NULL, -> `email` char(30) NOT NULL, -> PRIMARY KEY (`id`) -> ) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARSET = utf8;mysql> show tables;+-------------------------+| Tables_in_django_manual |+-------------------------+| user |+-------------------------+1 row in set (0.00 sec)來看看如和使用 mysqlclient,模塊操作數(shù)據(jù)庫 django-manual。>>> import MySQLdb>>> conn = MySQLdb.connect(host='180.76.152.113', port=9002, user='store', passwd='store.123@', db='django_manual') # 連接數(shù)據(jù)庫>>> sql = "insert into user(`name`, `password`, `email`) values ('test', 'xxxxxx', '222@qq.com')" # 插入數(shù)據(jù)的sql語句>>> cur = conn.cursor() # 獲取游標>>> cur.execute(sql) # 執(zhí)行sql語句1 >>> conn.commit() # 提交操作# commit 成功后,去另一個窗口查看 mysql 中的數(shù)據(jù)庫數(shù)據(jù)mysql > select * from user;+----+------+----------+------------+| id | name | password | email |+----+------+----------+------------+| 10 | test | xxxxxx | 222@qq.com |+----+------+----------+------------+1 row in set (0.00 sec)這里我們可以看到 mysqlclient 模塊中的幾個常用方法:MySQLdb.connect() 方法:連接 mysql 數(shù)據(jù)庫,會在這里輸入 mysql 服務(wù)地址,開放端口,用戶名和密碼以及要使用到的數(shù)據(jù)庫名;conn.cursor():創(chuàng)建游標,固定做法;cur.execute():通過游標的 execute() 方法可以執(zhí)行 sql 語句,其返回值表示的是操作的記錄數(shù),比如這里我們新增了一條記錄,返回的值為1;conn.commit():對于數(shù)據(jù)庫有更新的動作,比如新增數(shù)據(jù)、修改數(shù)據(jù)和刪除數(shù)據(jù)等,最后需要使用 commit() 方法提交動作,而對于查詢操作而言則不需要。如果想自動 commit 動作,也是有辦法的:>>> conn = MySQLdb.connect(...)>>> conn.autocommit(True)>>> ...上面是新增單條記錄,我們也可以新增多條記錄,操作如下:>>> # 在前面的基礎(chǔ)上繼續(xù)執(zhí)行>>> conn.autocommit(True) # 設(shè)置自動提交>>> cur = conn.cursor()>>> data = (('user%d' % i, 'xxxxxx', '28%d@qq.com' % i) for i in range(10))>>> cur.executemany('insert into user(`name`, `password`, `email`) values (%s, %s, %s);', data)10# 在另一個窗口,可以看到 user 表中的記錄已經(jīng)有11條了select count(*) from user;+----------+| count(*) |+----------+| 11 |+----------+1 row in set (0.00 sec)這里插入多條數(shù)據(jù),使用的是游標的 executemany() 方法。如果在插入多條記錄中遇到異常,需要執(zhí)行回滾動作,一般寫法如下:conn = MySQLdb.connect(...)try: # 執(zhí)行動作 ...except Exception as e: conn.rollback()此外,我們一般用到的比較多的是查詢相關(guān)的操作。這里有游標的方法:fetchone():只取一條記錄,然后游標后移一位;fetchmany():取多條記錄,參數(shù)為獲取的記錄數(shù),執(zhí)行完后游標移動相應(yīng)位置;fetchall():取出 sql 執(zhí)行的所有記錄,游標移動至末尾;下面我們用前面生成的 11 條記錄來進行操作:>>> # 假設(shè)前面已經(jīng)獲得連接信息conn和游標cur>>> sql = 'select * from user where 1=1 and name like "user%"'>>> cur.execute(sql)10>>> data1 = cur.fetchone()>>> print(data1)(11, 'user0', 'xxxxxx', '280@qq.com')# 看到再次獲取一條記錄時,取得是下一條數(shù)據(jù)>>> data2 = cur.fetchone()>>> print(data2)(12, 'user1', 'xxxxxx', '281@qq.com')# 這次獲取5條數(shù)據(jù),從user2開始>>> data3 = cur.fetchmany(5)>>> print(data3)((13, 'user2', 'xxxxxx', '282@qq.com'), (14, 'user3', 'xxxxxx', '283@qq.com'), (15, 'user4', 'xxxxxx', '284@qq.com'), (16, 'user5', 'xxxxxx', '285@qq.com'), (17, 'user6', 'xxxxxx', '286@qq.com'))# 最后用fetchall()方法獲取最后的所有數(shù)據(jù),還剩下10-1-1-5=3條記錄>>> print(data4)((18, 'user7', 'xxxxxx', '287@qq.com'), (19, 'user8', 'xxxxxx', '288@qq.com'), (20, 'user9', 'xxxxxx', '289@qq.com'))# 游標指向最后位置,再次獲取時已經(jīng)沒有數(shù)據(jù)了>>> data5 = cur.fetchone()>>> print(data5)None通過上面的代碼演示,我想我們應(yīng)該理解游標的作用了,就是每執(zhí)行一次 fetch 函數(shù),對應(yīng)的游標會向后移動相應(yīng)位置。
在本章節(jié)由于 test 命令與運算符配合使用,與之前運算符章節(jié)有重復(fù),我們在此溫故知新,著手 test 命令來學習文件判斷。文件測試在我們編寫 Shell 中與文件操作非常常用,熟練掌握文件操作可以在后續(xù)的 Shell 編寫中得心應(yīng)手,例如 file 變量為:操作符說明舉例-dfile 檢測文件是否是目錄,如果是,則返回 true。[-d $file] 返回 false。-ffile 檢測文件是否是普通文件(既不是目錄,也不是設(shè)備文件),如果是,則返回 true。[-f $file] 返回 true。-cfile 檢測文件是否是字符設(shè)備文件,如果是,則返回 true。[-c $file] 返回 false。-bfile 檢測文件是否是塊設(shè)備文件,如果是,則返回 true。[-b $file] 返回 false。-gfile 檢測文件是否設(shè)置了 SGID 位,如果是,則返回 true。[-g $file] 返回 false。-ufile 檢測文件是否設(shè)置了 SUID 位,如果是,則返回 true。[-u $file] 返回 false。-kfile 檢測文件是否設(shè)置了粘著位 (Sticky Bit),如果是,則返回 true。[-k $file] 返回 false。-pfile 檢測文件是否是有名管道,如果是,則返回 true。[-p $file] 返回 false。-rfile 檢測文件是否可讀,如果是,則返回 true。[-r $file] 返回 true。-wfile 檢測文件是否可寫,如果是,則返回 true。[-w $file] 返回 true。-xfile 檢測文件是否可執(zhí)行,如果是,則返回 true。[-x $file] 返回 true。-sfile 檢測文件是否為空(文件大小是否大于 0),不為空返回 true。[-s $file] 返回 true。-efile 檢測文件(包括目錄)是否存在,如果是,則返回 true。[-e $file] 返回 true。例如:#!/bin/bashTEST_FILE="/etc/fstab"echo "檢測的文件為:${TEST_FILE}"echo "文件信息為:$(ls -l ${TEST_FILE})"if [ -r $TEST_FILE ]then echo "文件可讀"else echo "文件不可讀"fiif [ -w $TEST_FILE ]then echo "文件可寫"else echo "文件不可寫"fiif [ -x $TEST_FILE ]then echo "文件可執(zhí)行"else echo "文件不可執(zhí)行"fiif [ -f $TEST_FILE ]then echo "文件為普通文件"else echo "文件為特殊文件"fiif [ -d $TEST_FILE ]then echo "文件是個目錄"else echo "文件不是個目錄"fiif [ -s $TEST_FILE ]then echo "文件不為空"else echo "文件為空"fiif [ -e $TEST_FILE ]then echo "文件存在"else echo "文件不存在"fi返回為:檢測的文件為:/etc/fstab文件信息為:-rw-r--r--. 1 root root 500 Jan 17 14:23 /etc/fstab文件可讀文件可寫文件不可執(zhí)行文件為普通文件文件不是個目錄文件不為空文件存在
樂觀是一種積極的解決問題的態(tài)度。所謂樂觀鎖認為系統(tǒng)中的事務(wù)并發(fā)更新不會很頻繁,即使沖突了也沒事,大不了重新再來一次?;舅枷耄好看翁峤灰粋€事務(wù)更新時,查看要修改的數(shù)據(jù)從上次讀取以后有沒有被其它事務(wù)修改過,如果修改過,那么更新就會失敗。實現(xiàn)方案:在實體中增加一個版本控制字段,每次事務(wù)更新后就將版本 (Version) 字段的值加 1。Tips: 樂觀鎖本質(zhì)就是版本控制管理的實現(xiàn),記錄的每一次更新操作都會以版本遞增的方式進行記錄。一個事務(wù)在更新之前,先獲取記錄的當前版本號,更新時,如果版本還是最新的則可以更新,否則說明有事務(wù)比你先更新,則需要放棄?;蛘咧匦虏樵兊阶钚掳姹拘畔⒑笤俑?。所以,在樂觀鎖的實現(xiàn)中,沖突是常態(tài)。實現(xiàn)過程:在學生實體類中添加新屬性,用來記錄每次更新的版本號。public class Student implements Serializable {//省略…… @Versionprivate Long version;//省略…… }stu = (Student) session.get(Student.class, new Integer(1));System.out.println("當前版本號:"+stu.getVersion);//模擬延遲,如果在這個時間內(nèi)有其它事務(wù)進行了更新操作,此事務(wù)的更新不會成功Thread.sleep(30000);stu.setStuName("Hibernate");transaction.commit();好了,悲觀也好,樂觀也好,只是一種解決問題的態(tài)度。對于這兩種態(tài)度,咱們要總結(jié)一下。樂觀鎖:優(yōu)勢:性能好,并發(fā)性高。缺點:用戶體驗不好,可能會出現(xiàn)高高興興去更新,卻告知已經(jīng)有人捷足先登了。悲觀鎖:優(yōu)勢:鎖住記錄為我所用,沒修改完成之前,其他事務(wù)只能瞪眼瞧著,時間雖然延遲,至少心里有底。缺點:并發(fā)性不好,性能不高。Hibernate 的其它性能優(yōu)化:隨時使用 Session.clear()及時清除 Session 緩存區(qū)的內(nèi)容;1+N 問題 ( 一條 SQL 語句能解決的問題用了很多條 SQL 語句來實現(xiàn)) ;使用 Criteria 查詢可以解決這個問題;Lazy 加載:需要時,使用 get() 方法發(fā)出 SQL 語句。使用類似于 from Student s left join s.classRoom c 的關(guān)聯(lián)查詢語句。緩存使用:在對象更新、刪除、添加相對于查詢要少得多時, 二級緩存的應(yīng)用將不怕 n+1 問題,因為即使第一次查詢很慢,之后直接緩存命中也是很快的,剛好又利用了 n+1。
在該示例之中,我們會采用公開數(shù)據(jù)集合:泰坦尼克生存數(shù)據(jù)集。該數(shù)據(jù)集合以CSV格式保存,同時也被 TensorFlow 納入為內(nèi)置數(shù)據(jù)集,因此使用較為方便。該數(shù)據(jù)集合中包含了每個乘客的基本信息,比如名字、性別、年齡等,同時也包含了改乘客是否生還。我們所訓練的模型就是要根據(jù)乘客的基本信息來判斷改乘客最終是否生還。import tensorflow as tf# 獲取數(shù)據(jù)集train_path = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")valid_path = tf.keras.utils.get_file("eval.csv", "https://storage.googleapis.com/tf-datasets/titanic/eval.csv")# 通過CSV文件構(gòu)建數(shù)據(jù)集train_data = tf.data.experimental.make_csv_dataset(train_path, batch_size=32, label_name='survived', na_value="?", num_epochs=1, ignore_errors=True )valid_data = tf.data.experimental.make_csv_dataset(valid_path, batch_size=32, label_name='survived', na_value="?", num_epochs=1, ignore_errors=True )# 定義離散列并進行處理caetogries = {'sex': ['male', 'female'], 'class' : ['First', 'Second', 'Third'], 'deck' : ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'], 'embark_town' : ['Cherbourg', 'Southhampton', 'Queenstown'], 'alone' : ['y', 'n']}categorical_columns = []for feature, vocab in caetogries.items(): cat_col = tf.feature_column.categorical_column_with_vocabulary_list( key=feature, vocabulary_list=vocab) categorical_columns.append(tf.feature_column.indicator_column(cat_col))# 定義連續(xù)列numerical_names = {'age', 'n_siblings_spouses', 'parch', 'fare'}numerical_columns = []for feature in numerical_names: num_col = tf.feature_column.numeric_column(feature) numerical_columns.append(num_col)# 定義模型model = tf.keras.Sequential([ tf.keras.layers.DenseFeatures(categorical_columns + numerical_columns), tf.keras.layers.Dense(256, activation='relu'), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dense(1, activation='sigmoid'),])model.compile( loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])# 模型訓練model.fit(train_data, epochs=20)# 模型評估m(xù)odel.evaluate(valid_data)在該示例之中,我們首先獲取了 CSV 數(shù)據(jù)集合并且構(gòu)建了 dataset。然后我們便進行了至關(guān)重要的一步,我們對于離散數(shù)據(jù)和連續(xù)數(shù)據(jù)進行了預(yù)處理。對于離散數(shù)據(jù),我們進行了以下幾步處理:定義離散列以及其可能的值(我們稱作字典);使用 tf.feature_column.categorical_column_with_vocabulary_list 這個函數(shù) API 構(gòu)建 TensorFlow 能夠識別的離散列;將所有的離散列合在一起以待后面使用。對于連續(xù)數(shù)據(jù),我們同樣進行了以下幾步處理:定義連續(xù)列;使用 tf.feature_column.numeric_column 這個函數(shù) API 構(gòu)建 TensorFlow 能夠識別的連續(xù)列;將所有的連續(xù)列合在一起以待后面使用。最后在模型的第一層,我們添加了一個特征的預(yù)處理層,因為我們輸入的特征有的是連續(xù)值,有些是離散值,因此我們需要該層進行特征的預(yù)處理。最后我們得到的輸出為:Epoch 1/2020/20 [==============================] - 0s 2ms/step - loss: 0.7948 - accuracy: 0.6459......Epoch 19/2020/20 [==============================] - 0s 2ms/step - loss: 0.4619 - accuracy: 0.8022Epoch 20/2020/20 [==============================] - 0s 2ms/step - loss: 0.4661 - accuracy: 0.79909/9 [==============================] - 0s 2ms/step - loss: 0.4571 - accuracy: 0.7841[0.45707935094833374, 0.7840909361839294]于是我們可以看到,我們的模型最終在測試集合上達到了78.4%的準確率。
在說明求解最大子數(shù)組的整個過程之后,接下來,我們看看如何用 java 代碼實現(xiàn)最大子數(shù)組問題的求解。package divide_and_conquer;public class MaxSubarray { //內(nèi)部類,用來存儲最大子數(shù)組的返回結(jié)果, private static class Result { int low; int high; int sum; public Result(int low, int high, int sum) { this.low = low; this.high = high; this.sum = sum; } @Override public String toString() { return "Result{" + "low=" + low + ", high=" + high + ", sum=" + sum + '}'; } } private static Result FindMaxCrossSubarray(int[]A,int low, int mid, int high){ //尋找左邊的連續(xù)最大值及記錄位置 int leftSum = Integer.MIN_VALUE; int sum = 0; int maxLeft = mid; for (int i=mid; i>=low; i--){ sum = sum + A[i]; if(sum > leftSum){ leftSum = sum; maxLeft = i; } } //尋找右邊的連續(xù)最大值及記錄位置 int rightSum = Integer.MIN_VALUE; int maxRight = mid+1; sum = 0; for ( int j=mid+1; j<=high;j++){ sum = sum + A[j]; if(sum > rightSum){ rightSum = sum; maxRight = j; } } //返回跨越中間值的最大子數(shù)組結(jié)果 return new Result(maxLeft,maxRight,leftSum + rightSum); } public static Result FindMaxSubarray(int[] A, int low, int high){ //數(shù)組只有一個元素時的處理情況 if (high == low){ return new Result(low,high,A[low]); }else { //對應(yīng)思路中步驟1,找到中間元素 int mid = (low + high)/2; //對應(yīng)思路中步驟2,分別對應(yīng)a,b,c三種情況求解最大子數(shù)組結(jié)果 Result leftResult = FindMaxSubarray(A,low,mid); Result rightResult = FindMaxSubarray(A,mid+1,high); Result crossResult = FindMaxCrossSubarray(A,low,mid,high); //對應(yīng)步驟3,比較 if(leftResult.sum >= rightResult.sum && leftResult.sum >= crossResult.sum){ return leftResult; }else if (rightResult.sum >= leftResult.sum && rightResult.sum >= crossResult.sum){ return rightResult; }else { return crossResult; } } } public static void main(String[] args){ int[] A = {12, -3, -16, 20, -19, -3, 18, 20, -7, 12, -9, 7, -10}; System.out.println(FindMaxSubarray(A,0,A.length-1).toString()); }}運行結(jié)果如下:Result{low=6, high=9, sum=43}運行結(jié)果中的 low 表示最大子數(shù)組在數(shù)組 A 中的開始下標,high 表示最大子數(shù)組在數(shù)組 A 中的終止下標,sum 表示最大子數(shù)組的求和值,對應(yīng)到我們的實例數(shù)組 A 中,對應(yīng)的最大最大子數(shù)組為 [18,20,-7,12]。代碼中第 5 行至 25 行的 Result 內(nèi)部類,主要是用來存儲最大子數(shù)組的返回結(jié)果,定義了子數(shù)組的開始下標,結(jié)束下標,求和值。代碼的第 27 至 55 行是最大子數(shù)組跨越中間節(jié)點時候的最大子數(shù)組求解過程。代碼的第 58 至 78 行是整個最大子數(shù)組的求解過程。代碼的第 81 行和 82 行是求解最大子數(shù)組過程的一個示例,輸出最大子數(shù)組的求解結(jié)果。
在上一步我們知道了如何聲明和定義標簽了,那么接下來就是用這個標簽,如何把我們定義好的標簽貼到指定的代碼上。在 Kotlin 中使用注解和 Java 一樣。要應(yīng)用一個注解都是 @注解類名。@Target(AnnotationTarget.FUNCTION)@Retention(value = AnnotationRetention.RUNTIME)annotation class TestAnnotation(val value: Int)//和一般的聲明很類似,只是在class前面加上了annotation修飾符class Test { @TestAnnotation(value = 1000) fun test() {//給test函數(shù)貼上TestAnnotation標簽(添加TestAnnotation注解) //... }}在很多常見的 Java 或 Kotlin 框架中大量使用了注解,比如我們最常見的 JUnit 單元測試框架:class ExampleUnitTest { @Test //@Test注解就是為了告訴JUnit框架,這是一個測試方法,當做測試調(diào)用。 fun addition_isCorrect() { assertEquals(4, 2 + 2) }}在 Kotlin 中注解類中還可以擁有注解類作為參數(shù),不妨來看下 Kotlin 中對 @Deprecated這個注解源碼定義,以及它的使用。@Deprecated 注解在原來的 Java 基礎(chǔ)增強了一個 ReplaceWith 功能.??梢灾苯釉谑褂昧死系?API 時,編譯器可以根據(jù) ReplaceWith 中的新 API,自動替換成新的 API。這一點在 Java 中是做不到的,你只能點擊進入這個 API 查看源碼來正確使用新的 API。//@Deprecated注解比Java多了ReplaceWith功能, 這樣當你在調(diào)用remove方法,編譯器會報錯。使用代碼提示會自動IntelliJ IDEA不僅會提示使用哪個函數(shù)提示替代它,而且會快速自動修正。@Deprecated("Use removeAt(index) instead.", ReplaceWith("removeAt(index)"), level = DeprecationLevel.ERROR)//定義的級別是ERROR級別的,這樣當你在調(diào)用remove方法,編譯器會報錯。@kotlin.internal.InlineOnlypublic inline fun <T> MutableList<T>.remove(index: Int): T = removeAt(index)@Deprecated 注解的 remove 函數(shù)使用://Deprecated注解的使用fun main(args: Array<String>) { val list = mutableListOf("a", "b", "c", "d", "e") list.remove(3)//這里會報錯, 通過remove函數(shù)注解定義,這個remove函數(shù)在定義的level是ERROR級別的,所以編譯器直接拋錯}最后來看下 @Deprecated 注解的定義:@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS)@MustBeDocumentedpublic annotation class Deprecated( val message: String, val replaceWith: ReplaceWith = ReplaceWith(""),//注解類中構(gòu)造器可以使用注解類作為函數(shù)參數(shù) val level: DeprecationLevel = DeprecationLevel.WARNING)@Target()@Retention(BINARY)@MustBeDocumentedpublic annotation class ReplaceWith(val expression: String, vararg val imports: String)注意:注解類中只能擁有如下類型的參數(shù): 基本數(shù)據(jù)類型、字符串、枚舉、類引用類型、其他的注解類(例如Deprecated注解類中的ReplaceWith注解類)
好!現(xiàn)在為 ”Hibernate“ 同學選修新的課程。比如說,想選擇 DB 課程和 JAVA 課程。天呀,剛剛不是才解除了 JAVA 課程嗎。你管得著嗎,咱們就是這么任性,想解除就解除,想建立就建立 ,誰叫 Hibernate 這么方便呢。有了前面的知識,應(yīng)該難不倒我們了?,F(xiàn)在來看看 Hibernate 實例:HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>(); hibernateTemplate.template(new Notify<Student>() { @Override public Student action(Session session) { // 添加新學生 Student student =(Student)session.get(Student.class, new Integer(1)); // 查詢Java Course javaCourse = (Course) session.get(Course.class, new Integer(1)); // 查詢C Course dbCourse = (Course) session.get(Course.class, new Integer(3)); // 確定關(guān)系 student.getCourses().add(javaCourse); student.getCourses().add(dbCourse); return null; } });關(guān)系還是在程序中直接體現(xiàn),并且是由學生對象維護??梢赃M入數(shù)據(jù)庫,查看一下:還是再叮囑一下,關(guān)系只能由學生方維護的。此處,應(yīng)該有疑問,為什么只能是學生方,而不能是課程方,難道在 Java 的世界里也有命運一說。這個命運的安排是由開發(fā)者來決定的,在進行關(guān)聯(lián)注解時,那一方使用了 mappedBy 屬性,則這一方就是被動方,很好理解,這個屬性本身的含義就是說,聽對方的。所以說,誰是霸道總裁,看開發(fā)者的心情。理論上講,多對多中,兩者應(yīng)該是平等關(guān)系。剛剛的解除和重新建立都是對已經(jīng)存在的學生進行的。如果班里轉(zhuǎn)來了一名叫 ”HibernateTemplate“ 的新學生,他想選修 JAVA 和 DB,則應(yīng)該怎么操作呢?很簡單?。W生沒有,添加就是;課程信息有,從表中查詢就是;關(guān)系不存在,建立就是。下面便是如你所想的實例代碼:HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();hibernateTemplate.template(new Notify<Student>() { @Override public Student action(Session session) { // 添加新學生 Student student = new Student("HibernateTemplate", "男"); // 查詢Java課程 Course javaCourse = (Course) session.get(Course.class, new Integer(1)); System.out.println(javaCourse.getCourseName()); // 查詢DB課程 Course dbCourse = (Course) session.get(Course.class, new Integer(3)); System.out.println(dbCourse.getCourseName()); // 確定關(guān)系 student.getCourses().add(javaCourse); student.getCourses().add(dbCourse); return null; }});是的,這個代碼本身沒有問題。但是,這里會有一個小坑,這個坑沒有填的話,你可能會測試不成功。在學生類中,一定要記住對下面的屬性進行實例化:private Set<Course> courses=new HashSet<Course>();否則就會有空指針異常拋出來。前面沒有,是因為數(shù)據(jù)庫中有數(shù)據(jù),Hibernate 幫咱們自動實例化了。但現(xiàn)在是一個新學生,Hiberante 可不會幫你實例化他的課程集合屬性。也就是不能任何時候都依靠 Hibernate,它也有顧全不到的地方。為了讓你寬心,還是看一下數(shù)據(jù)庫中數(shù)據(jù)吧:
首先,我們需要明確兩個名詞概念: 基礎(chǔ)類型和實參類型。例如對于 List<String>, List 就是基礎(chǔ)類型而這里的 String 就是實參類型。然后,我們需要明確一下,這里的型變到底指的是什么?可以先大概描述一下,它反映的是一種特殊類型的對應(yīng)關(guān)系規(guī)則。是不是很抽象?那就先來看個例子,例如 List<String>和List<Any> 他們擁有相同的基礎(chǔ)類型,實參類型 String 和 Any 存在父子關(guān)系,那么是不是 List<String> 和 List<Any> 是否存在某種對應(yīng)關(guān)系呢?實際上,我們討論的型變也就是圍繞著這種場景展開的。有了上面的認識,進入正題為什么需要這種型變關(guān)系呢?來看對比的例子,我們需要向一個函數(shù)中傳遞參數(shù)。fun main(args: Array<String>) { val stringList: List<String> = listOf("a", "b", "c", "d") val intList: List<Int> = listOf(1, 2, 3, 4) printList(stringList)//向函數(shù)傳遞一個List<String>函數(shù)實參,也就是這里List<String>是可以替換List<Any> printList(intList)//向函數(shù)傳遞一個List<Int>函數(shù)實參,也就是這里List<Int>是可以替換List<Any>}fun printList(list: List<Any>) {//注意:這里函數(shù)形參類型是List<Any>,函數(shù)內(nèi)部是不知道外部傳入是List<Int>還是List<String>,全部當做List<Any>處理 list.forEach { println(it) }}上述操作是合法的,運行結(jié)果如下:如果我們上述的函數(shù)形參 List<Any> 換成 MutableList<Any> 會變成什么樣呢?fun main(args: Array<String>) { val stringList: MutableList<String> = mutableListOf("a", "b", "c", "d") val intList: MutableList<Int> = mutableListOf(1, 2, 3, 4) printList(stringList)//這里實際上是編譯不通過的 printList(intList)//這里實際上是編譯不通過的}fun printList(list: MutableList<Any>) { list.add(3.0f)//開始引入危險操作dangerous! dangerous! dangerous! list.forEach { println(it) }}我們來試想下,利用反證法驗證下,假如上述代碼編譯通過了,會發(fā)生什么,就會發(fā)生下面的可能出現(xiàn)類似的危險操作。就會出現(xiàn)一個 Int 或者 String 的集合中引入其他的非法數(shù)據(jù)類型,所以肯定是有問題的,故編譯不通過。因為我們說過在函數(shù)的形參類型 MutableList<Any> 在函數(shù)內(nèi)部它只知道是該類型也不知道外部給它傳了個啥,所以它只能在內(nèi)部按照這個類型規(guī)則來,所以在函數(shù)內(nèi)部 list.add(3.0f) 這行代碼時編譯通過的,向一個 MutableList<Any> 集合加入一個 Float 類型明顯說得過去的。通過對比上面兩個例子,大家有沒有思考一個問題就是為什么 List<String>、List<Int>替換List<Any> 可以,而 MutableList<String>、MutableList<Int>替換MutableList<Any> 不可以呢?實際上問題所說的類型替換其實就是型變 , 那大家到這就明白了為什么會存在型變了,型變更為了泛型接口更加安全,假如沒有型變,就會出現(xiàn)上述危險問題。那另一問題來了為什么有的型變關(guān)系可以,有的不可以呢?對于傳入集合內(nèi)部不會存在修改添加其元素的操作 (只讀),是可以支持外部傳入更加具體類型實參是安全的,而對于集合內(nèi)部存在修改元素的操作 (寫操作) 是不安全的,所以編譯器不允許。以上面例子分析,List<Any> 實際上一個只讀集合 (注意:它和 Java 中的 List 完全不是一個東西,注意區(qū)分),它內(nèi)部不存在 add,remove 操作方法,不信的可以看下它的源碼,所以以它為形參的函數(shù)就可以敞開大門大膽接收外部參數(shù),因為不存在修改元素操作所以是安全的,所以第一個例子是編譯 OK 的;而對于 MutableList<Any> 在 Kotlin 中它是一個可讀可寫的集合,相當于 Java 中的 List, 所以它的內(nèi)部存在著修改、刪除、添加元素的危險操作方法,所以對于外部傳入的函數(shù)形參它需要做嚴格檢查必須是 MutableList<Any> 類型。為了幫助理解和記憶,自己繪制了一張獨具風趣的漫畫圖幫助理解,這張圖很重要以致于后面的協(xié)變、逆變、不變都可以從它獲得理解。后面也會不斷把它拿出來分析最后為了徹底把這個問題分析透徹可以給大家看下 List<E> 和 MutableList<E> 的部分源碼public interface List<out E> : Collection<E> { // Query Operations override val size: Int override fun isEmpty(): Boolean override fun contains(element: @UnsafeVariance E): Boolean override fun iterator(): Iterator<E> // Bulk Operations override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean ... }public interface MutableList<E> : List<E>, MutableCollection<E> { // Modification Operations override fun add(element: E): Boolean override fun remove(element: E): Boolean // Bulk Modification Operations override fun addAll(elements: Collection<E>): Boolean ... }仔細對比下 List<out E> 和 MutableList<E> 泛型定義是不一樣的,他們分別對應(yīng)了協(xié)變和不變 , 至于什么是協(xié)變什么是逆變什么不變,我們后面會詳細講。
看到上面的運行 Demo,大家有沒有在思考一個問題 IOS 項目中的 ViewController 是怎么和 UI 組件綁定在一起的呢?我個人認為這個很重要,換句話說這就是 IOS 開發(fā)最基本的套路,如果這個都不弄明白的話,下面 Demo 開發(fā)就是云里霧里了,掌握了這個基本套路的話,作為一個 Android 開發(fā)者,你基本上就可以在 IOS 項目開發(fā)中任意折騰了。在 kotlin 目錄下新建一個 KNMapViewController 類,并且它去繼承 UIViewController 以及實現(xiàn) MKMapViewDelegateProtocol 接口,并重寫 viewDidLoad () 函數(shù)。并且在 viewDidLoad 函數(shù)實現(xiàn) map 地圖基本配置。//導入Kotlin以與Objective-C和一些Cocoa Touch框架互操作。import kotlinx.cinterop.*import platform.CoreLocation.CLLocationCoordinate2DMakeimport platform.Foundation.*import platform.MapKit.MKCoordinateRegionMakeimport platform.MapKit.MKCoordinateSpanMakeimport platform.MapKit.MKMapViewimport platform.MapKit.MKMapViewDelegateProtocolimport platform.UIKit.*@ExportObjCClass//注意: @ExportObjCClass注解有助于Kotlin創(chuàng)建一個在運行時可查找的類。class KNMapViewController: UIViewController, MKMapViewDelegateProtocol { @ObjCOutlet //注意: @ObjCOutlet注解很重要,主要是將mMapView屬性設(shè)置為outlet。這允許您將Main.storyboard中的MKMapview鏈接到此屬性。 lateinit var mMapView: MKMapView constructor(aDecoder: NSCoder) : super(aDecoder) override fun initWithCoder(aDecoder: NSCoder) = initBy(KNMapViewController(aDecoder)) override fun viewDidLoad() { super.viewDidLoad() val center = CLLocationCoordinate2DMake(32.07, 118.78) val span = MKCoordinateSpanMake(0.7, 0.7) val region = MKCoordinateRegionMake(center, span) with(mMapView) { delegate = this@KNMapViewController setRegion(region, true) } }}用 Xcode 打開項目中的 Main.storyboard, 刪除原來自動生成一些視圖組件 (如果你處于 AppCode 中開發(fā)項目,實際上直接在 AppCode 中雙擊 Main.storyboard 就會自動使用 Xcode 打開當前整個項目,并打開這個項目):給當前空的視圖綁定對應(yīng) ViewController, 這里是 KNMapViewController:4、在當前空的視圖中添加一個 map view 組件并且設(shè)置組件的約束條件。右擊組件 MKMapView 可以看到黑色對話框,里面 Referencing Outlets 還空的,說明當前 ViewController 沒有和 MKMapView 組件綁定:配置 outlet, 這里說下 AppCode 很坑爹地方,需要手動去 source code 中手動配置 outlet,選中 main.storyboard 右擊 open as 然后選擇打開 source code:在 view 和 viewController 結(jié)尾標簽之間配置 connection:配置的 code 如下:<connections> <outlet property="mMapView" destination="dest id" id="generate id"/></connections><!--property屬性值就是KNMapViewController中的mMapView變量名;destination屬性值是一個map view標簽中id(可以在subviews標簽內(nèi)的mapView標簽中找到id), id屬性則是自動生成的,可以按照格式自己之指定一個,只要不出現(xiàn)重復(fù)的id即可-->配置結(jié)果如下:檢驗是否綁定成功,回到 main.stroyboard 視圖,右擊組件查看黑色框是否出現(xiàn)如下綁定關(guān)系,出現(xiàn)了則說明配置成功。接著上述配置步驟,就可以回到 AppCode 中運行項目了:
元字符描述\將下一個字符標記符、或一個向后引用、或一個八進制轉(zhuǎn)義符。例如,“\n”匹配\n。“\n”匹配換行符。序列“\”匹配“\”而“(”則匹配“(”。即相當于多種編程語言中都有的“轉(zhuǎn)義字符”的概念。^匹配輸入字行首。如果設(shè)置了RegExp對象的Multiline屬性,^也匹配“\n”或“\r”之后的位置。$匹配輸入行尾。如果設(shè)置了RegExp對象的Multiline屬性,$也匹配“\n”或“\r”之前的位置。*匹配前面的子表達式任意次。例如,zo*能匹配“z”,也能匹配“zo”以及“zoo”。*等價于{0,}。+匹配前面的子表達式一次或多次(大于等于1次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等價于{1,}。?匹配前面的子表達式零次或一次。例如,“do(es)?”可以匹配“do”或“does”。?等價于{0,1}。{n}n是一個非負整數(shù)。匹配確定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的兩個o。{n,}n是一個非負整數(shù)。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o?!皁{1,}”等價于“o+”。“o{0,}”則等價于“o*”。{n,m}m和n均為非負整數(shù),其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”將匹配“fooooood”中的前三個o為一組,后三個o為一組。“o{0,1}”等價于“o?”。請注意在逗號和兩個數(shù)之間不能有空格。?當該字符緊跟在任何一個其他限制符(*,+,?,{n},{n,},{n,m})后面時,匹配模式是非貪婪的。非貪婪模式盡可能少地匹配所搜索的字符串,而默認的貪婪模式則盡可能多地匹配所搜索的字符串。例如,對于字符串“oooo”,“o+”將盡可能多地匹配“o”,得到結(jié)果[“oooo”],而“o+?”將盡可能少地匹配“o”,得到結(jié)果 [‘o’, ‘o’, ‘o’, ‘o’].匹配除“\n”和"\r"之外的任何單個字符。要匹配包括“\n”和"\r"在內(nèi)的任何字符,請使用像“[\s\S]”的模式。(pattern)匹配pattern并獲取這一匹配。所獲取的匹配可以從產(chǎn)生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中則使用$0…$9屬性。要匹配圓括號字符,請使用“\(”或“\)”。(?:pattern)非獲取匹配,匹配pattern但不獲取匹配結(jié)果,不進行存儲供以后使用。這在使用或字符來組合一個模式的各個部分時很有用。(?=pattern)非獲取匹配,正向肯定預(yù)查,在任何匹配pattern的字符串開始處匹配查找字符串,該匹配不需要獲取供以后使用。預(yù)查不消耗字符,也就是說,在一個匹配發(fā)生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預(yù)查的字符之后開始。(?!pattern)非獲取匹配,正向否定預(yù)查,在任何不匹配pattern的字符串開始處匹配查找字符串,該匹配不需要獲取供以后使用。例如“Windows(?!95(?<=pattern)非獲取匹配,反向肯定預(yù)查,與正向肯定預(yù)查類似,只是方向相反。*python的正則表達式?jīng)]有完全按照正則表達式規(guī)范實現(xiàn),所以一些高級特性建議使用其他語言如java、scala等(?<!patte_n)非獲取匹配,反向否定預(yù)查,與正向否定預(yù)查類似,只是方向相反。*python的正則表達式?jīng)]有完全按照正則表達式規(guī)范實現(xiàn),所以一些高級特性建議使用其他語言如java、scala等[xyz]字符集合。匹配所包含的任意一個字符。例如,“[abc]”可以匹配“plain”中的“a”。[^xyz]負值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“plin”任一字符。[a-z]字符范圍。匹配指定范圍內(nèi)的任意字符。例如,“[a-z]”可以匹配“a”到“z”范圍內(nèi)的任意小寫字母字符。注意:只有連字符在字符組內(nèi)部時,并且出現(xiàn)在兩個字符之間時,才能表示字符的范圍; 如果出字符組的開頭,則只能表示連字符本身.[^a-z]負值字符范圍。匹配任何不在指定范圍內(nèi)的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范圍內(nèi)的任意字符。\b匹配一個單詞的邊界,也就是指單詞和空格間的位置(即正則表達式的“匹配”有兩種概念,一種是匹配字符,一種是匹配位置,這里的\b就是匹配位置的)。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”;“\b1_”可以匹配“1_23”中的“1_”,但不能匹配“21_3”中的“1_”。\B匹配非單詞邊界?!癳r\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。\cx匹配由x指明的控制字符。例如,\cM匹配一個Control-M或回車符。x的值必須為A-Z或a-z之一。否則,將c視為一個原義的“c”字符。\d匹配一個數(shù)字字符。等價于[0-9]。grep 要加上-P,perl正則支持\D匹配一個非數(shù)字字符。等價于[^0-9]。grep要加上-P,perl正則支持\f匹配一個換頁符。等價于\x0c和\cL。\n匹配一個換行符。等價于\x0a和\cJ。\r匹配一個回車符。等價于\x0d和\cM。\s匹配任何不可見字符,包括空格、制表符、換頁符等等。等價于[ \f\n\r\t\v]。\S匹配任何可見字符。等價于[^ \f\n\r\t\v]。\t匹配一個制表符。等價于\x09和\cI。\v匹配一個垂直制表符。等價于\x0b和\cK。\w匹配包括下劃線的任何單詞字符。類似但不等價于“[A-Za-z0-9_]”,這里的"單詞"字符使用Unicode字符集。\W匹配任何非單詞字符。等價于“[^A-Za-z0-9_]”。\xn匹配n,其中n為十六進制轉(zhuǎn)義值。十六進制轉(zhuǎn)義值必須為確定的兩個數(shù)字長。例如,“\x41”匹配“A”。“\x041”則等價于“\x04&1”。正則表達式中可以使用ASCII編碼。\num匹配num,其中num是一個正整數(shù)。對所獲取的匹配的引用。例如,“(.)\1”匹配兩個連續(xù)的相同字符。\n標識一個八進制轉(zhuǎn)義值或一個向后引用。如果\n之前至少n個獲取的子表達式,則n為向后引用。否則,如果n為八進制數(shù)字(0-7),則n為一個八進制轉(zhuǎn)義值。\nm標識一個八進制轉(zhuǎn)義值或一個向后引用。如果\nm之前至少有nm個獲得子表達式,則nm為向后引用。如果\nm之前至少有n個獲取,則n為一個后跟文字m的向后引用。如果前面的條件都不滿足,若n和m均為八進制數(shù)字(0-7),則\nm將匹配八進制轉(zhuǎn)義值nm。\nml如果n為八進制數(shù)字(0-7),且m和l均為八進制數(shù)字(0-7),則匹配八進制轉(zhuǎn)義值nml。\un匹配n,其中n是一個用四個十六進制數(shù)字表示的Unicode字符。例如,\u00A9匹配版權(quán)符號(?)。\p{P}小寫 p 是 property 的意思,表示 Unicode 屬性,用于 Unicode 正表達式的前綴。中括號內(nèi)的“P”表示Unicode 字符集七個字符屬性之一:標點字符。其他六個屬性:L:字母;M:標記符號(一般不會單獨出現(xiàn));Z:分隔符(比如空格、換行等);S:符號(比如數(shù)學符號、貨幣符號等);N:數(shù)字(比如阿拉伯數(shù)字、羅馬數(shù)字等);C:其他字符。*注:此語法部分語言不支持,例:javascript。\< \>匹配詞(word)的開始(<)和結(jié)束(>)。例如正則表達式<the>能夠匹配字符串"for the wise"中的"the",但是不能匹配字符串"otherwise"中的"the"。注意:這個元字符不是所有的軟件都支持的。( )將( 和 ) 之間的表達式定義為“組”(group),并且將匹配這個表達式的字符保存到一個臨時區(qū)域(一個正則表達式中最多可以保存9個),它們可以用 \1 到\9 的符號來引用。
//使用序列fun main(args: Array<String>){ (0..100) .asSequence() .map { it + 1 } .filter { it % 2 == 0 } .find { it > 3 }}//使用普通集合fun main(args: Array<String>){ (0..100) .map { it + 1 } .filter { it % 2 == 0 } .find { it > 3 }}通過 decompile 上述例子的源碼會發(fā)現(xiàn),普通集合操作會針對每個操作都會生成一個 while 循環(huán),并且每次都會創(chuàng)建新的集合保存中間結(jié)果。而使用序列則不會,它們內(nèi)部會無論進行多少中間操作都是共享同一個迭代器中的數(shù)據(jù),想知道共享同一個迭代器中的數(shù)據(jù)的原理嗎?請接著看內(nèi)部源碼實現(xiàn)。6.1 使用集合普通操作反編譯源碼 public static final void main(@NotNull String[] args) { Intrinsics.checkParameterIsNotNull(args, "args"); byte var1 = 0; Iterable $receiver$iv = (Iterable)(new IntRange(var1, 100)); //創(chuàng)建新的集合存儲map后中間結(jié)果 Collection destination$iv$iv = (Collection)(new ArrayList(CollectionsKt.collectionSizeOrDefault($receiver$iv, 10))); Iterator var4 = $receiver$iv.iterator(); int it; //對應(yīng)map操作符生成一個while循環(huán) while(var4.hasNext()) { it = ((IntIterator)var4).nextInt(); Integer var11 = it + 1; //將map變換的元素加入到新集合中 destination$iv$iv.add(var11); } $receiver$iv = (Iterable)((List)destination$iv$iv); //創(chuàng)建新的集合存儲filter后中間結(jié)果 destination$iv$iv = (Collection)(new ArrayList()); var4 = $receiver$iv.iterator();//拿到map后新集合中的迭代器 //對應(yīng)filter操作符生成一個while循環(huán) while(var4.hasNext()) { Object element$iv$iv = var4.next(); int it = ((Number)element$iv$iv).intValue(); if (it % 2 == 0) { //將filter過濾的元素加入到新集合中 destination$iv$iv.add(element$iv$iv); } } $receiver$iv = (Iterable)((List)destination$iv$iv); Iterator var13 = $receiver$iv.iterator();//拿到filter后新集合中的迭代器 //對應(yīng)find操作符生成一個while循環(huán),最后末端操作只需要遍歷filter后新集合中的迭代器,取出符合條件數(shù)據(jù)即可。 while(var13.hasNext()) { Object var14 = var13.next(); it = ((Number)var14).intValue(); if (it > 3) { break; } } }6.2 使用序列 (Sequences) 惰性操作反編譯源碼1、整個序列操作源碼 public static final void main(@NotNull String[] args) { Intrinsics.checkParameterIsNotNull(args, "args"); byte var1 = 0; //利用Sequence擴展函數(shù)實現(xiàn)了fitler和map中間操作,最后返回一個Sequence對象。 Sequence var7 = SequencesKt.filter(SequencesKt.map(CollectionsKt.asSequence((Iterable)(new IntRange(var1, 100))), (Function1)null.INSTANCE), (Function1)null.INSTANCE); //取出經(jīng)過中間操作產(chǎn)生的序列中的迭代器,可以發(fā)現(xiàn)進行map、filter中間操作共享了同一個迭代器中數(shù)據(jù),每次操作都會產(chǎn)生新的迭代器對象,但是數(shù)據(jù)是和原來傳入迭代器中數(shù)據(jù)共享,最后進行末端操作的時候只需要遍歷這個迭代器中符合條件元素即可。 Iterator var3 = var7.iterator(); //對應(yīng)find操作符生成一個while循環(huán),最后末端操作只需要遍歷filter后新集合中的迭代器,取出符合條件數(shù)據(jù)即可。 while(var3.hasNext()) { Object var4 = var3.next(); int it = ((Number)var4).intValue(); if (it > 3) { break; } } }2、抽出其中這段關(guān)鍵 code,繼續(xù)深入:SequencesKt.filter(SequencesKt.map(CollectionsKt.asSequence((Iterable)(new IntRange(var1, 100))), (Function1)null.INSTANCE), (Function1)null.INSTANCE);3、把這段代碼轉(zhuǎn)化分解成三個部分://第一部分val collectionSequence = CollectionsKt.asSequence((Iterable)(new IntRange(var1, 100)))//第二部分val mapSequence = SequencesKt.map(collectionSequence, (Function1)null.INSTANCE)//第三部分val filterSequence = SequencesKt.filter(mapSequence, (Function1)null.INSTANCE)4、解釋第一部分代碼:第一部分反編譯的源碼很簡單,主要是調(diào)用 Iterable 中擴展函數(shù)將原始數(shù)據(jù)集轉(zhuǎn)換成 Sequence 對象。public fun <T> Iterable<T>.asSequence(): Sequence<T> { return Sequence { this.iterator() }//傳入外部Iterable<T>中的迭代器對象}更深入一層:@kotlin.internal.InlineOnlypublic inline fun <T> Sequence(crossinline iterator: () -> Iterator<T>): Sequence<T> = object : Sequence<T> { override fun iterator(): Iterator<T> = iterator()}通過外部傳入的集合中的迭代器方法返回迭代器對象,通過一個對象表達式實例化一個 Sequence,Sequence 是一個接口,內(nèi)部有個 iterator () 抽象函數(shù)返回一個迭代器對象,然后把傳入迭代器對象作為 Sequence 內(nèi)部的迭代器,也就是相當于給迭代器加了 Sequence 序列的外殼,核心迭代器還是由外部傳入的迭代器對象,有點偷梁換柱的概念。5、解釋第二部分的代碼:通過第一部分,成功將普通集合轉(zhuǎn)換成序列 Sequence,然后現(xiàn)在進行 map 操作,實際上調(diào)用了 Sequence 擴展函數(shù) map 來實現(xiàn)的val mapSequence = SequencesKt.map(collectionSequence, (Function1)null.INSTANCE)進入 map 擴展函數(shù):public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> { return TransformingSequence(this, transform)}會發(fā)現(xiàn)內(nèi)部會返回一個 TransformingSequence 對象,該對象構(gòu)造器接收一個 Sequence 類型對象,和一個 transform 的 lambda 表達式,最后返回一個 Sequence 類型對象。我們先暫時解析到這,后面會更加介紹。6、解釋第三部分的代碼:通過第二部分,進行 map 操作后,然后返回的還是 Sequence 對象,最后再把這個對象進行 filter 操作,filter 也還是 Sequence 的擴展函數(shù),最后返回還是一個 Sequence 對象。val filterSequence = SequencesKt.filter(mapSequence, (Function1)null.INSTANCE)進入 filter 擴展函數(shù):public fun <T> Sequence<T>.filter(predicate: (T) -> Boolean): Sequence<T> { return FilteringSequence(this, true, predicate)}會發(fā)現(xiàn)內(nèi)部會返回一個 FilteringSequence 對象,該對象構(gòu)造器接收一個 Sequence 類型對象,和一個 predicate 的 lambda 表達式,最后返回一個 Sequence 類型對象。我們先暫時解析到這,后面會更加介紹。7、Sequences 源碼整體結(jié)構(gòu)介紹代碼結(jié)構(gòu)圖 :圖中標注的都是一個個對應(yīng)各個操作符類,它們都實現(xiàn) Sequence 接口首先,Sequence 是一個接口,里面只有一個抽象函數(shù),一個返回迭代器對象的函數(shù),可以把它當做一個迭代器對象外殼。public interface Sequence<out T> { /** * Returns an [Iterator] that returns the values from the sequence. * * Throws an exception if the sequence is constrained to be iterated once and `iterator` is invoked the second time. */ public operator fun iterator(): Iterator<T>}Sequence 核心類 UML 類圖這里只畫出了某幾個常用操作符的類圖注意:通過上面的 UML 類關(guān)系圖可以得到,共享同一個迭代器中的數(shù)據(jù)的原理實際上就是利用 Java 設(shè)計模式中的狀態(tài)模式 (面向?qū)ο蟮亩鄳B(tài)原理) 來實現(xiàn)的,首先通過 Iterable 的 iterator () 返回的迭代器對象去實例化 Sequence,然后外部調(diào)用不同的操作符,這些操作符對應(yīng)著相應(yīng)的擴展函數(shù),擴展函數(shù)內(nèi)部針對每個不同操作返回實現(xiàn) Sequence 接口的子類對象,而這些子類又根據(jù)不同操作的實現(xiàn),更改了接口中 iterator () 抽象函數(shù)迭代器的實現(xiàn),返回一個新的迭代器對象,但是迭代的數(shù)據(jù)則來源于原始迭代器中。8、接著上面 TransformingSequence、FilteringSequence 繼續(xù)解析.通過以上對 Sequences 整體結(jié)構(gòu)深入分析,那么接著 TransformingSequence、FilteringSequence 繼續(xù)解析就非常簡單了。我們就以 TransformingSequence 為例://實現(xiàn)了Sequence<R>接口,重寫了iterator()方法,重寫迭代器的實現(xiàn)internal class TransformingSequence<T, R>constructor(private val sequence: Sequence<T>, private val transformer: (T) -> R) : Sequence<R> { override fun iterator(): Iterator<R> = object : Iterator<R> {//根據(jù)傳入的迭代器對象中的數(shù)據(jù),加以操作變換后,構(gòu)造出一個新的迭代器對象。 val iterator = sequence.iterator()//取得傳入Sequence中的迭代器對象 override fun next(): R { return transformer(iterator.next())//將原來的迭代器中數(shù)據(jù)元素做了transformer轉(zhuǎn)化傳入,共享同一個迭代器中的數(shù)據(jù)。 } override fun hasNext(): Boolean { return iterator.hasNext() } } internal fun <E> flatten(iterator: (R) -> Iterator<E>): Sequence<E> { return FlatteningSequence<T, R, E>(sequence, transformer, iterator) }}9、源碼分析總結(jié)序列內(nèi)部的實現(xiàn)原理是采用狀態(tài)設(shè)計模式,根據(jù)不同的操作符的擴展函數(shù),實例化對應(yīng)的 Sequence 子類對象,每個子類對象重寫了 Sequence 接口中的 iterator () 抽象方法,內(nèi)部實現(xiàn)根據(jù)傳入的迭代器對象中的數(shù)據(jù)元素,加以變換、過濾、合并等操作,返回一個新的迭代器對象。這就能解釋為什么序列中工作原理是逐個元素執(zhí)行不同的操作,而不是像普通集合所有元素先執(zhí)行 A 操作,再所有元素執(zhí)行 B 操作。這是因為序列內(nèi)部始終維護著一個迭代器,當一個元素被迭代的時候,就需要依次執(zhí)行 A,B,C 各個操作后,如果此時沒有末端操作,那么值將會存儲在 C 的迭代器中,依次執(zhí)行,等待原始集合中共享的數(shù)據(jù)被迭代完畢,或者不滿足某些條件終止迭代,最后取出 C 迭代器中的數(shù)據(jù)即可。
方法描述Math.abs返回一個數(shù)的的絕對值。Math.acos返回一個數(shù)的反余弦值(單位為弧度)。Math.acosh返回一個數(shù)字的反雙曲余弦值。Math.asin返回一個數(shù)值的反正弦(單位為弧度)。Math.asinh返回給定數(shù)字的反雙曲正弦值Math.atan返回一個數(shù)值的反正切(以弧度為單位)Math.atan2返回從原點(0,0)到(x,y)點的線段與x軸正方向之間的平面角度(弧度值)。Math.atanh函數(shù)返回一個數(shù)值反雙曲正切值。Math.cbrt返回任意數(shù)字的立方根。Math.ceil返回大于或等于一個給定數(shù)字的最小整數(shù)。Math.clz32返回一個數(shù)字在轉(zhuǎn)換成 32 無符號整形數(shù)字的二進制形式后, 開頭的 0 的個數(shù)。Math.cos返回一個數(shù)值的余弦值。Math.cosh返回數(shù)值的雙曲余弦函數(shù)。Math.exp函數(shù)返回 ex,x 表示參數(shù),e 是歐拉常數(shù)(Euler’s constant),自然對數(shù)的底數(shù)。Math.expm1函數(shù)返回 Ex - 1, 其中 x 是該函數(shù)的參數(shù), E 是自然對數(shù)的底數(shù)。Math.floor返回小于或等于一個給定數(shù)字的最大整數(shù)。Math.fround將任意的數(shù)字轉(zhuǎn)換為離它最近的單精度浮點數(shù)形式的數(shù)字。Math.hypot函數(shù)返回它的所有參數(shù)的平方和的平方根。Math.imul該函數(shù)將兩個參數(shù)分別轉(zhuǎn)換為 32 位整數(shù),相乘后返回 32 位結(jié)果,類似 C 語言的 32 位整數(shù)相乘。Math.log返回一個數(shù)的自然對數(shù)。Math.log10函數(shù)返回一個數(shù)字以 10 為底的對數(shù)。Math.log1p函數(shù)返回一個數(shù)字加1后的自然對數(shù) (底為 E), 即log(x+1)。Math.log2函數(shù)返回一個數(shù)字以 2 為底的對數(shù)。Math.max返回一組數(shù)中的最大值。Math.min返回零個或更多個數(shù)值的最小值。Math.pow函數(shù)返回基數(shù)(base)的指數(shù)(exponent)次冪,即 baseexponent。Math.random函數(shù)返回一個浮點, 偽隨機數(shù)在范圍從0到小于1,也就是說,從0(包括0)往上,但是不包括1(排除1)。Math.round函數(shù)返回一個數(shù)字四舍五入后最接近的整數(shù)。Math.sign函數(shù)返回一個數(shù)字的符號, 指示數(shù)字是正數(shù),負數(shù)還是零。Math.sin函數(shù)返回一個數(shù)值的正弦值。Math.sinh函數(shù)返回一個數(shù)字(單位為角度)的雙曲正弦值。Math.sqrt函數(shù)返回一個數(shù)的平方根。Math.tan返回一個數(shù)值的正切值。Math.tanh函數(shù)將會返回一個數(shù)的雙曲正切函數(shù)值。Math.trunc方法會將數(shù)字的小數(shù)部分去掉,只保留整數(shù)部分。
基礎(chǔ)概念:消息適配器,可能一說這個名詞,大家就感覺有點陌生,我們可以把消息適配器拆開來進行理解。首先,消息這一名詞就不用多說了,消息在我們的應(yīng)用程序中指的就是應(yīng)用程序中的數(shù)據(jù),在 RabbitMQ 中,指的就是在 RabbitMQ 中流轉(zhuǎn)的消息,也就是說,我們應(yīng)用程序中的數(shù)據(jù)在 RabbitMQ 中就被稱為消息。最后,我們再來看適配器。和之前一樣,我們先從日常生活中舉個例子,比如我們的筆記本電腦,在筆記本電腦上存在不同的對外暴露的類似USB形式的端口,這些端口不僅僅只有USB這一種類型,還有Type-C,以及Light端口等,根據(jù)我們的需要來連接這些端口所用的工具就可以看做是一個適配器。當我們需要將自己的電腦與大屏或投影儀設(shè)備相連接時,此時,我們需要一根連接在電腦和大屏或投影儀設(shè)備之間的連線,這根線我們通常稱為數(shù)據(jù)線,在有了這根數(shù)據(jù)線之后,我們就可以建立起一個在電腦和大屏或投影儀設(shè)備之間的連接,就可以將電腦的屏幕投影到大屏或投影儀設(shè)備上,這個過程就是我們的電腦適配大屏或投影儀設(shè)備的一個過程,而這中間的數(shù)據(jù)線就被可以被稱為適配器。如果我們沒有這個數(shù)據(jù)線來作為電腦與大屏或投影儀設(shè)備之間的適配器,那么通常情況下我們就不能將電腦屏幕來進行投影了(Wifi投影的情況除外)。在這一例子中,我們也可以這樣說:即需要一根數(shù)據(jù)線來將我們的電腦屏幕與大屏或投影儀設(shè)備進行適配,以達到投影電腦屏幕的目的。說白了,適配器其實就是為了適配兩種不同物品,來達到兩種或多種物品之間的協(xié)同工作的目的的一種技術(shù)手段,而這種技術(shù)手段可以是技術(shù)實物(比如數(shù)據(jù)線),也可以是一種可以在空氣中進行傳播的介質(zhì)(Wifi投屏)。消息適配器亦是如此,在 Spring-AMQP 中,消息適配器指的就是,為了監(jiān)聽 RabbitMQ 中的消息,從而將消息與具體的業(yè)務(wù)邏輯相結(jié)合,達到消息適配業(yè)務(wù)邏輯的目的的一種技術(shù)手段。在介紹完消息適配器的基礎(chǔ)概念之后,下面讓我們來看一下如何對消息適配器進行簡單的配置吧。
在前面的數(shù)據(jù)類型我們講過,在 Sass 中有一種數(shù)據(jù)類型是列表類型,那么列表函數(shù)就是用來操作列表的功能,下面我們一起來看下 Sass 提供了哪些列表函數(shù)。5.3.1 append($list, $val, $separator)append($list, $val, $separator) 函數(shù)用于向一個列表的末尾插入元素,它接收 3 個參數(shù),第 1 個參數(shù)是一個列表(以空格或者逗號分隔),第 2 個參數(shù)是要插入的值(也可以是一個列表),第 3 個參數(shù) $separator 表示返回的列表是否使用與 $list 相同的分隔符,這個默認是 auto ( 使用與$list相同的分隔符),這個函數(shù)的返回值也是一個列表。我們舉例看下:append(10 11 12, 13) //=> 10 11 12 13append((10,11,12), 13) //=> 10, 11, 12, 13這里需要注意的是,如果第 1 個參數(shù)你是使用逗號分隔的列表,那么你要將這個列表使用括號括起來!5.3.2 join($list1, $list2, $separator, $bracketed)join($list1, $list2, $separator, $bracketed) 函數(shù)用于連接兩個列表,上面我們講到向列表默認插入元素,如果插入的元素是一個列表,那么請你使用 join 函數(shù)。這個函數(shù)接收 4 個參數(shù),第 1 個參數(shù)是一個列表,第 2 個參數(shù)是要插入的列表,第 3 個參數(shù)是返回列表的分隔符,默認是 auto (列表將使用與$list1相同的分隔符(如果有分隔符),否則使用與$list2相同的分隔符,或者使用空格。不允許使用其他值),第 4 個參數(shù)是布爾值,用來設(shè)置返回的列表是否包含方括號。我們舉例來看下:join(5 6, 7 8) //=> 5 6 7 8join((5,6), (7,8)) //=> 5, 6, 7, 8join(5 6, 7 8, $bracketed: true) //=> [5 6 7 8]這里需要注意的是,如果列表類型的參數(shù)是使用逗號分隔的列表,那么你要將這個列表使用括號括起來!5.3.3 index($list, $value)index($list, $value) 函數(shù)用于返回 $value 在列表 $list 中的索引,如果在 $list 中沒有 $value 則返回 null。index(a b solid, b) //=> 2index(a b solid, solid) //=> 35.3.4 length($list)這個函數(shù)比較簡單,就是返回列表的長度。返回的值是 number 類型。length(a b solid) //=> 3length("") //=> 15.3.5 list-separator($list)這個函數(shù)用來返回列表的分隔符,如果沒有分隔符就返回空格 space 。list-separator(a b) //=> spacelist-separator((a,b)) //=> comma5.3.6 nth($list, $n)nth($list, $n) 這個函數(shù)用于通過索引在列表中取元素,第 1 個參數(shù)是一個列表,第 2 個參數(shù) $n 是索引位置,如果 $n 為負數(shù),則從列表的末尾開始計數(shù)。如果索引 $n 處沒有元素,則引發(fā)錯誤。nth(a b c d, 2) //=> b上面講解了一些比較常見的列表函數(shù),它很像 javascript 中提供的一些數(shù)組方法,在 Sass 中你可以對列表類型的數(shù)據(jù)使用各種各樣的列表函數(shù)來滿足你的需求。
HTML(HyperText Markup Language)是一種超文本標記語言;CSS(Cascading Style Sheets)簡稱為層疊樣式表。大家如果只是看這兩個名字的定義,估計仍然是一頭霧水。這倆東西到底是干啥的?我給大家舉一個例子就明白了,大家應(yīng)該都畫過畫吧,即使沒有親自畫過應(yīng)該也見過。畢竟吃豬肉和見豬跑你總得占一樣吧。在畫畫的時候,我們首先會拿畫筆勾勒出圖形的樣式骨架,之后會涂上一些好看的顏色讓畫變的更美觀。我們寫網(wǎng)頁的時候也是一樣的道理,HTML 就相當于畫筆,用于勾勒頁面的骨架。CSS 則是好看的顏色,讓頁面的樣式更加美觀。下面我們來認識一下頁面的骨架 HTML:Tips: 可以新建一個 txt 文件,將下面的代碼復(fù)制進去,然后將文件的后綴名改為 .html ,然后使用瀏覽器打開就可以看到效果:HTML 的標題<!DOCTYPE html> <html> ? <head> <title>歡迎訪問慕課網(wǎng)</title> <head> ? <body> ? <h>這是一個標題</h> <h3> <bold>這是一個加粗的標題</bold> </h3> ? <!-- 段落 --> <P>這是一個段落</P> <button>這是一個按鈕</button> </body> ? </html>效果如下圖所示:HTML 中添加圖片<!DOCTYPE html><html><head> <title>歡迎訪問慕課網(wǎng)</title> <head> <body> <h>這是一個標題</h> <h3> <bold>這是一個加粗的標題</bold> </h3> <!-- 段落 --> <P>這是一個段落</P> <button>這是一個按鈕</button> <!-- 下面是慕課網(wǎng)圖片 --> <img src="http://idcbgp.cn/static/img/index/logo.png" alt="圖片無法顯示!"> <!-- 超鏈接 --> <a href="http://idcbgp.cn/">慕課網(wǎng)</a> </body></html>效果如下圖所示:有序列表和無序列表<!DOCTYPE html><html><head> <title>歡迎訪問慕課網(wǎng)</title> <head> <body> <h>這是一個標題</h> <h3> <bold>這是一個加粗的標題</bold> </h3> <!-- 段落 --> <P>這是一個段落</P> <br> <br> <br> <button>這是一個按鈕</button> <!-- 下面是慕課網(wǎng)圖片 --> <img src="http://idcbgp.cn/static/img/index/logo.png" alt="圖片無法顯示!"> <img src="h" alt="圖片無法顯示!"> <!-- 屬性 href --> <a href="http://idcbgp.cn/">慕課網(wǎng)</a> <!-- 列表 --> <ul> <li>無序列表</li> <li>無序列表</li> <li>無序列表</li> <li>無序列表</li> <li>無序列表</li> </ul> <ol> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> </ol> </body></html>效果如下圖所示:表格<!DOCTYPE html><html><head> <title>歡迎訪問慕課網(wǎng)</title> <head> <body> <h>這是一個標題</h> <h3> <bold>這是一個加粗的標題</bold> </h3> <!-- 段落 --> <P>這是一個段落</P> <br> <br> <br> <button>這是一個按鈕</button> <!-- 下面是慕課網(wǎng)圖片 --> <img src="http://idcbgp.cn/static/img/index/logo.png" alt="圖片無法顯示!"> <img src="h" alt="圖片無法顯示!"> <!-- 屬性 href --> <a href="http://idcbgp.cn/">慕課網(wǎng)</a> <!-- 列表 --> <ul> <li>無序列表</li> <li>無序列表</li> <li>無序列表</li> <li>無序列表</li> <li>無序列表</li> </ul> <ol> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> </ol> <!-- 表格 --> <table> <thead> <tr> <td>a</td> <td>b</td> <td>c</td> </tr> </thead> <tbody> <tr> <td>001</td> <td>002</td> <td>003</td> </tr> </tbody> </table> </body></html>效果如下圖所示:表單<!DOCTYPE html><html><head> <title>歡迎訪問慕課網(wǎng)</title> <head> <body> <h>這是一個標題</h> <h3> <bold>這是一個加粗的標題</bold> </h3> <!-- 段落 --> <P>這是一個段落</P> <br> <br> <br> <button>這是一個按鈕</button> <!-- 下面是慕課網(wǎng)圖片 --> <img src="http://idcbgp.cn/static/img/index/logo.png" alt="圖片無法顯示!"> <img src="h" alt="圖片無法顯示!"> <!-- 屬性 href --> <a href="http://idcbgp.cn/">慕課網(wǎng)</a> <!-- 列表 --> <ul> <li>無序列表</li> <li>無序列表</li> <li>無序列表</li> <li>無序列表</li> <li>無序列表</li> </ul> <ol> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> </ol> <!-- 表格 --> <table> <thead> <tr> <td>a</td> <td>b</td> <td>c</td> </tr> </thead> <tbody> <tr> <td>001</td> <td>002</td> <td>003</td> </tr> </tbody> </table> <!-- 表單 --> <form> <div> <label>aaaa</label> <input type="text" name="ssss" placeholder="ssssss"> </div> <input type="submit" name="submit" value="提交"> </form> </body></html>網(wǎng)頁顯示結(jié)果如下: