第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

為了賬號(hào)安全,請(qǐng)及時(shí)綁定郵箱和手機(jī)立即綁定
2.1 代碼塊

在 Markdown 文件中,在行首添加 4 個(gè)連續(xù)的空格,可將行內(nèi)容定義為代碼塊。實(shí)例 1:#### 使用空格定義代碼塊 pubic static void main(String[] args) { System.out.println("Hello world"); }其渲染結(jié)果如下:注意:Typora 編輯器在默認(rèn)模式(實(shí)時(shí)渲染模式)下無(wú)法通過(guò)此方法生成代碼塊,需要先切換到源代碼模式(「顯示」->「源代碼模式」)后添加空格。該源碼渲染輸出 html 的內(nèi)容如下:<pre><div><span><span>pubic static void main(String[] args) {</span></span></div><div><span><span> </span><span>System.out.println("Hello world");</span></span></div><div><span><span>}</span></span></div></pre>代碼塊的另一種定義方式是以三個(gè)連續(xù)的 「反引號(hào) “`”」 作為開始行和結(jié)束行。實(shí)例 2:#### 使用反引號(hào)定義代碼塊?```pubic static void main(String[] args) { System.out.println("Hello world");}?```其渲染結(jié)果如下:在這種方式下,我們還可以聲明代碼的高亮樣式,方法是在首行的連續(xù)的三個(gè) 「反引號(hào) “`”」 后面加入源代碼語(yǔ)言的標(biāo)記,比如:javascript、Markdown、json、java、c++、sql 等,具體支持的種類和使用的 Markdown 編輯器或渲染程序配置有關(guān)。實(shí)例 3:#### 使用反引號(hào)定義代碼塊,并定義高亮?```javapubic static void main(String[] args) { System.out.println("Hello world");}?```其渲染結(jié)果如下:

4.2 HashSet 實(shí)現(xiàn)類

HashSet類依賴于哈希表(實(shí)際上是HashMap實(shí)例,下面將會(huì)介紹)。HashSet中的元素是無(wú)序的、散列的。4.2.1 構(gòu)造方法HashSet():構(gòu)造一個(gè)新的空集;默認(rèn)的初始容量為 16(最常用),負(fù)載系數(shù)為 0.75;HashSet(int initialCapacity):構(gòu)造一個(gè)新的空集; 具有指定的初始容量,負(fù)載系數(shù)為 0.75;HashSet(int initialCapacity, float loadFactor):構(gòu)造一個(gè)新的空集; 支持的 HashMap 實(shí)例具有指定的初始容量和指定的負(fù)載系數(shù);HashSet(Collection<? extends E> c):構(gòu)造一個(gè)新集合,其中包含指定集合中的元素。4.2.2 常用成員方法HashSet的常用成員方法如下:boolean add(E e):如果指定的元素尚不存在,則將其添加到該集合中;boolean contains(Object o):如果此集合包含指定的元素,則返回 true,否則返回 false;boolean isEmpty():如果此集合不包含任何元素,則返回 true,否則返回 false;Iterator<E> iterator():返回此集合中元素的迭代器;boolean remove(Object o):從該集合中刪除指定的元素(如果存在);int size():返回此集合中的元素?cái)?shù)量。更多成員方法請(qǐng)翻閱官方文檔,下面我們將結(jié)合實(shí)例來(lái)介紹以上成員方法的使用。

4. 列出聯(lián)系人

本小節(jié)實(shí)現(xiàn)列出所有聯(lián)系人的功能,如下所示:def list_person(): for person in persons: print('%s,%s,%s' % (person['name'], person['address'], person['phone']))在第 1 行,定義函數(shù) list_person,實(shí)現(xiàn)列出所有聯(lián)系人的功能在第 2 行,遍歷列表 persons,循環(huán)變量 person 是一個(gè)字典在第 3 行,打印變量 person 的內(nèi)容對(duì)每個(gè)聯(lián)系人打印輸出一行,假設(shè)通訊錄中已經(jīng)存儲(chǔ)了張三和李四兩個(gè)聯(lián)系人,輸出如下:C:\> python addr-manage.py1. create person2. list all persons3. query person4. delete person5. quitEnter a number(1-5): 2張三,南京,12306李四,北京,10086在第 7 行,用戶選擇執(zhí)行功能 2在第 8 行,打印聯(lián)系人張三的信息在第 9 行,打印聯(lián)系人李四的信息查詢聯(lián)系人本小節(jié)實(shí)現(xiàn)查詢聯(lián)系人的功能,如下所示:def query_person(): name = input('name: ') for person in persons: if person['name'] == name: print('%s,%s,%s' % (person['name'], person['address'], person['phone']))在第 1 行,定義函數(shù) query_person,實(shí)現(xiàn)查詢聯(lián)系人的功能在第 2 行,獲取用戶輸入的 name在第 3 行,遍歷列表 persons,循環(huán)變量 person 是一個(gè)字典在第 4 行,如果用戶輸入的 name 和循環(huán)訪問(wèn) person 的 name 相同,則表示找到指定的 person在第 5 行,打印變量 person 的內(nèi)容

2.2 事務(wù)隔離級(jí)別

面試官提問(wèn):ACID 特性中的隔離性在 MySQL 中的具體定義是什么?題目解析:MySQL 提供了 4 種事務(wù)隔離級(jí)別,分別是:(1)讀未提交(Read Uncommitted):所有事務(wù)可以看到其他事務(wù)未提交的執(zhí)行結(jié)果;(2)讀已提交(Read Committed):所有事務(wù)只能看到其他事務(wù)已提交的執(zhí)行結(jié)果;(3)可重復(fù)讀(Repeatable Read):MySQL 默認(rèn)的隔離級(jí)別,所有事務(wù)能看到其他事務(wù)已提交后的修改后數(shù)據(jù),但是如果第一次讀取到這個(gè)修改后的數(shù)據(jù),如果其他事務(wù)繼續(xù)修改了數(shù)據(jù)并且提交,這個(gè)事務(wù)讀到的也是第一次讀到的值,不會(huì)讀到修改后的新值。(4)串行化(Serializable):最高隔離級(jí)別,可以理解為讓所有并發(fā)執(zhí)行的事務(wù)都進(jìn)入隊(duì)列,挨個(gè)串行執(zhí)行,永遠(yuǎn)不可能發(fā)生沖突。我們關(guān)注事務(wù),關(guān)注點(diǎn)在于不同事務(wù)的并發(fā)沖突,而且重點(diǎn)在于讀寫操作。對(duì)于同一條數(shù)據(jù),在執(zhí)行并發(fā)事務(wù)時(shí)可能會(huì)產(chǎn)生讀寫上的問(wèn)題,有三種:(1)臟讀(Dirty Read):如果事務(wù) A 更新了一份數(shù)據(jù),比如將記錄 a 更新為記錄 b,那么在事務(wù) B 中讀取到的記錄是 b,此時(shí)事務(wù) A 進(jìn)行了回滾操作,記錄 b 回滾為記錄 a,那么事務(wù) B 讀到的記錄 b 則是非法數(shù)據(jù)。(2)不可重復(fù)讀(Non-Repeatable Read):如果事務(wù) A 更新了一份數(shù)據(jù),比如將記錄 a 更新為記錄 b,那么在事務(wù) B 中讀取到的記錄是 b,此時(shí)事務(wù) A 繼續(xù)將記錄 b 更新為記錄 c,那么事務(wù) B 第二次讀到的記錄是 c,兩次讀取的結(jié)果不同。(3)幻讀(Phantom Read):如果事務(wù) B 查詢到了幾行數(shù)據(jù),此時(shí)事務(wù) A 又插入了幾行新數(shù)據(jù),那么事務(wù) B 會(huì)讀到多出來(lái)的幾行數(shù)據(jù),讀到了上次讀取沒出現(xiàn)的數(shù)據(jù)。4 種隔離級(jí)別對(duì)應(yīng)的問(wèn)題應(yīng)對(duì)能力如下表:隔離級(jí)別臟讀不可重復(fù)讀幻讀讀未提交???讀已提交???可重復(fù)讀???串行化???從解決問(wèn)題的能力上看,串行化能解決所有的并發(fā)讀寫問(wèn)題,但是串行執(zhí)行效率太低,比如在電商網(wǎng)站的秒殺商品下單流程,就會(huì)導(dǎo)致所有的用戶需要等某一個(gè)用戶執(zhí)行完下單操作后才能繼續(xù)搶購(gòu),不具有實(shí)戰(zhàn)意義。MySQL 默認(rèn)的隔離級(jí)別是可重復(fù)讀,這個(gè)級(jí)別能解決臟讀和不可重復(fù)讀的問(wèn)題,效率上相對(duì)比較快。讀未提交的執(zhí)行效率最高,但是數(shù)據(jù)的一致性保障最差, 一般不會(huì)在實(shí)戰(zhàn)中使用。在 MySQL 客戶端執(zhí)行 show variables like 'transaction_isolation'; 語(yǔ)句可查看隔離級(jí)別:MySQL 默認(rèn)隔離級(jí)別

2. Django 內(nèi)嵌 ORM 模型的聚合操作

在 Django 中聚合函數(shù)是通過(guò) aggregate 方法實(shí)現(xiàn)的,aggregate 方法返回的結(jié)果是一個(gè)字典。其支持的聚合函數(shù)如下:# 源碼位置 django/db/models/aggregates.py...__all__ = [ 'Aggregate', 'Avg', 'Count', 'Max', 'Min', 'StdDev', 'Sum', 'Variance',]...注意:第一個(gè)是基類,從 Avg 開始,是支持的聚合方法,每個(gè)聚合方法的處理對(duì)應(yīng)著一個(gè)類,而這些類分別繼承自 Aggregate 類。aggregate 方法的使用也非常簡(jiǎn)單,只需要在該方法內(nèi)添加需要執(zhí)行的聚合函數(shù)即可,同時(shí)我們還可以打印出聚合函數(shù)執(zhí)行的 SQL 語(yǔ)句,具體操作如下:(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from hello_app.models import Member>>> from django.db.models import Avg, Max, Min, Sum>>> from django.db import connection>>> Member.objects.all().aggregate(avg_age=Avg('age'),sum_age=Sum('age'), max_age=Max('age'), min_age=Min('age')){'avg_age': 29.392156862745097, 'sum_age': 2998.0, 'max_age': '40', 'min_age': '20'}>>> print(connection.queries[-1]['sql'])SELECT AVG(`member`.`age`) AS `avg_age`, SUM(`member`.`age`) AS `sum_age`, MAX(`member`.`age`) AS `max_age`, MIN(`member`.`age`) AS `min_age` FROM `member`注意:connection.queries 中保存的是最近執(zhí)行的 SQL 語(yǔ)句,我們?cè)趫?zhí)行完 Django 的 ORM 操作后,可以取出最后一次執(zhí)行的 SQL 語(yǔ)句進(jìn)行查看。此外,對(duì)于聚合的函數(shù),如果我們不知道屬性名,則會(huì)有默認(rèn)值:字段__聚合函數(shù)名。>>> from django.db.models import Count>>> Member.objects.all().aggregate(Count('age', distinct=True)){'age__count': 21}>>> print(connection.queries[-1]['sql'])SELECT COUNT(DISTINCT `member`.`age`) AS `age__count` FROM `member`相比前面在 MySQL 中執(zhí)行聚合函數(shù),我們這里缺少一個(gè) GROUP BY 功能。如果想要對(duì)數(shù)據(jù)庫(kù)中的記錄先分組然后再進(jìn)行某些聚合操作或排序時(shí),需要使用 annotate 方法來(lái)實(shí)現(xiàn)。與 aggregate 方法不同的是,annotate 方法返回結(jié)果的不僅僅是含有統(tǒng)計(jì)結(jié)果的一個(gè)字典,而是包含有新增統(tǒng)計(jì)字段的查詢集 (QuerySet)。下面是實(shí)現(xiàn)分組聚合的實(shí)例操作:>>> from django.db.models import Count, Avg, Sum, Max, Min>>> Member.objects.values('occupation').annotate(count=Count('age')).order_by('-count')<QuerySet [{'occupation': 'security', 'count': 15}, {'occupation': 'ui', 'count': 15}, {'occupation': 'product', 'count': 14}, {'occupation': 'leader', 'count': 14}, {'occupation': 'ops', 'count': 14}, {'occupation': 'web', 'count': 12}, {'occupation': 'teacher', 'count': 8}, {'occupation': 'server', 'count': 8}, {'occupation': 'java', 'count': 1}, {'occupation': 'c/c++', 'count': 1}]>>>> print(connection.queries[-1]['sql'])SELECT `member`.`occupation`, COUNT(`member`.`age`) AS `count` FROM `member` GROUP BY `member`.`occupation` ORDER BY `count` DESC LIMIT 21注意:上面的操作有如下說(shuō)明:annotate 方法前面的 values 中出現(xiàn)的字段正是需要 GROUP BY 的字段。values 方法中出現(xiàn)多個(gè)值,即對(duì)多個(gè)字段進(jìn)行 GROUP BY;annotate 方法的結(jié)果是一個(gè)查詢集 (QuerySet),這樣我們可以繼續(xù)在后面盜用 filter()、order_by() 等方法進(jìn)行進(jìn)一步過(guò)濾結(jié)果;order_by 方法是對(duì)前面的 QuerySet 按某些字段排序,類似于 SQL 中的 ORDER BY 操作。排序字段前面加上 “-” 表示按倒序順序,類似于 DESC 操作

2.2 Map 的屬性和方法

Map 提供的屬性和方法從增、刪、改、查幾個(gè)方面入手,主要有以下 5 種:方法名描述set接收鍵值對(duì),向 Map 實(shí)例中添加元素get傳入指定的 key 獲取 Map 實(shí)例上的值delete傳入指定的 key 刪除 Map 實(shí)例中對(duì)應(yīng)的值clear清空 Map 實(shí)例has傳入指定的 key 查找在 Map 實(shí)例中是否存在size屬性,返回 Map 實(shí)例的長(zhǎng)度Map 提供 size 屬性可以獲取 Map 實(shí)例上的長(zhǎng)度var map = new Map([["x", 1], ["y", 2], ["z", 3]]);console.log(map.size) // 3set() 方法為 Map 實(shí)例添加或更新一個(gè)指定了鍵(key)和值(value)的鍵值對(duì)。myMap.set(key, value);通常情況下不會(huì)一開始就初始化值,而是動(dòng)態(tài)地添加,或更新 Map 時(shí)需要用到 set 方法,可以新增和修改 Map 實(shí)例的值。而且 key 值可以是任意類型的,查看如下示例:var map = new Map();var str = 'string';var obj = {};var arr = [];var fun = function() {};map.set(str, '鍵的類型字符串');map.set(obj, '鍵的類型對(duì)象');map.set(arr, '鍵的類型數(shù)組');map.set(fun, '鍵的類型函數(shù)');上面的代碼中,我們定義了不同類型的變量,使用這些變量為 map 添加數(shù)據(jù)。相比 Object 對(duì)象,擴(kuò)展性更強(qiáng)了。另外還可以鏈?zhǔn)教砑渔I值對(duì):var map = new Map();map.set('a', 1).set('b', 2).set('c', 3);console.log(map); // Map(3) {"a" => 1, "b" => 2, "c" => 3}使用鏈?zhǔn)教砑渔I值對(duì)的方式比較簡(jiǎn)潔,如果需要添加多個(gè)值,建議使用這樣的方式去添加。get() 方法是接收一個(gè)指定的鍵(key)返回一個(gè) Map 對(duì)象中與這個(gè)指定鍵相關(guān)聯(lián)的值,如果找不到這個(gè)鍵則返回 undefined。myMap.get(key);使用上面的示例,可以通過(guò) get 方法獲取對(duì)應(yīng)的值:console.log(map.get('string')); // "鍵的類型字符串"console.log(map.get(str)); // "鍵的類型字符串"console.log(map.get(obj)); // "鍵的類型對(duì)象"console.log(map.get(arr)); // "鍵的類型數(shù)組"console.log(map.get(fun)); // "鍵的類型數(shù)組"上面的代碼可以看出,我們可以直接使用鍵的值去獲取 Map 實(shí)例上對(duì)應(yīng)的值,也可以通過(guò)定義變量的方式去獲取。has() 方法是用于判斷指定的鍵是否存在,并返回一個(gè) bool 值,如果指定元素存在于 Map 中,則返回 true,否則返回 false。myMap.has(key);實(shí)例:var map = new Map();map.set("a", 11);map.has("a"); // truemap.has("b"); // falsedelete() 方法用于移除 Map 實(shí)例上的指定元素,如果 Map 對(duì)象中存在該元素,則移除它并返回 true;否則如果該元素不存在則返回 false。myMap.delete(key);實(shí)例:var map = new Map();map.set("a", 11);map.delete("a"); // truemap.has("a"); // falseclear() 方法會(huì)移除 Map 對(duì)象中的所有元素,返回 undefined。myMap.clear(key);實(shí)例:var map = new Map();map.set("a", 11);map.clear(); // 返回 undefined這里需要注意的是 clear() 返回的值是 undefined 而不是 true 所以如果在判斷結(jié)果的時(shí)候需要注意這一點(diǎn)。

2. 鏈表成環(huán)問(wèn)題

面試官提問(wèn):給定一個(gè)單鏈表,判斷鏈表是否存在環(huán)?能否在不使用額外空間的情況下解決問(wèn)題?題目解析:鏈表成環(huán)問(wèn)題是來(lái)源于算法網(wǎng)站LeetCode的經(jīng)典題目,題目鏈接:https://leetcode.com/problems/linked-list-cycle/。暴力破解方法是使用額外的數(shù)組結(jié)構(gòu),數(shù)組每一個(gè)元素存儲(chǔ)鏈表的值以及節(jié)點(diǎn)哈希地址,在遍歷鏈表的時(shí)候,如果發(fā)現(xiàn)遍歷到相同哈希地址的節(jié)點(diǎn),說(shuō)明鏈表有環(huán),否則直到遍歷到鏈表末尾節(jié)點(diǎn)為止。但是面試官要求在原始數(shù)組上操作,空間復(fù)雜度控制在O(1)。使用單個(gè)指針則沒有記憶能力,所以肯定要使用兩個(gè)以上的指針遍歷鏈表,最常用解法就是快慢指針?lè)?。?jīng)典快慢指針的算法核心思路是:(1)初始定義:定義一個(gè)指針 slow,每次走一個(gè) Node 節(jié)點(diǎn);定義一個(gè)指針 fast,每次走兩個(gè) Node 節(jié)點(diǎn);(2)終止條件一:當(dāng)快指針走到了 Null 節(jié)點(diǎn),說(shuō)明鏈表沒有成環(huán);(3)終止條件二:當(dāng)快指針和慢指針同時(shí)走到了一個(gè)節(jié)點(diǎn),說(shuō)明該鏈表有環(huán);(4)附加計(jì)算一:當(dāng)滿足鏈表有環(huán)時(shí),將慢指針重置到鏈表頭部,然后快慢指針同時(shí)走,每次只走一個(gè)節(jié)點(diǎn),當(dāng)兩個(gè)指針再次相遇時(shí),相遇點(diǎn)即是鏈表的環(huán)入口;(5)附加計(jì)算二:當(dāng)滿足鏈表有環(huán)時(shí),停止快指針,每次慢指針走一個(gè) Node 節(jié)點(diǎn),當(dāng)兩個(gè)節(jié)點(diǎn)再次相遇時(shí),慢指針重新走的長(zhǎng)度即是鏈表長(zhǎng)度。最重要的環(huán)節(jié)在于如何證明慢指針和快指針會(huì)在環(huán)中相遇,我們假設(shè)一個(gè)通用的有環(huán)鏈表如下圖所示:環(huán)狀鏈表其中A—>B 的距離為 x,B—>C 的距離為y,C—>B 的距離為 z。假設(shè)慢指針走了a步,那么快指針走了2a步,因?yàn)橄嘤鰰r(shí)快指針已經(jīng)在環(huán)上循環(huán)了n次,所以滿足公式:2*(x+y) = x+y+n*(y+z),所以x+y = n * (y+z)??炻羔樦g的距離會(huì)逐漸增大,結(jié)果是要么快指針提前走到了鏈表末尾,要么是快指針剛好多走出n個(gè)鏈表長(zhǎng)度,說(shuō)明鏈表有環(huán)。我們?cè)诎装迳峡梢詫懗鏊惴▽?shí)現(xiàn),示例:public class Solution { public boolean hasCycle(ListNode head) { //如果鏈表為空或者只有一個(gè)節(jié)點(diǎn),肯定不成環(huán) if(head==null || head.next==null){ return false; } ListNode slow = head; head=head.next; while(head!=null && head.next!=null){ //當(dāng)快慢指針相遇,說(shuō)明鏈表有環(huán) if(slow==head){ return true; } slow=slow.next; head=head.next.next; } return false; }}算法的時(shí)間復(fù)雜度為O(N),其中N表示鏈表長(zhǎng)度,空間復(fù)雜度為O(1),因?yàn)闆]有使用額外輔助空間。

3. find

簡(jiǎn)介:顧名思義,就是用來(lái)在系統(tǒng)中查找文件的工具,可以指定一個(gè)基礎(chǔ)起始目錄,根據(jù)不同的選項(xiàng)查找不同的文件。語(yǔ)法:find path -option [ -print ] [ -exec -ok command ] {} \;原理:find 根據(jù)option在指定的系統(tǒng)路徑中查找文件,如果查找到與對(duì)應(yīng)的exec命令,則執(zhí)行對(duì)應(yīng)的command。print: find 命令將匹配的文件輸出到標(biāo)準(zhǔn)輸出;exec: find 命令對(duì)匹配的文件執(zhí)行該參數(shù)所給出的 shell 命令。相應(yīng)命令的形式為 ‘command’ {} ;,注意 {} 和 \;之間的空格;ok: 和 - exec 的作用相同,只不過(guò)以一種更為安全的模式來(lái)執(zhí)行該參數(shù)所給出的 shell 命令,在執(zhí)行每一個(gè)命令之前,都會(huì)給出提示,讓用戶來(lái)確定是否執(zhí)行;選項(xiàng)說(shuō)明:- -name filename #查找名為 filename 的文件- -perm #按執(zhí)行權(quán)限來(lái)查找- -user username #按文件屬主來(lái)查找- -group groupname #按組來(lái)查找- -mtime -n +n #按文件更改時(shí)間來(lái)查找文件,-n 指 n 天以內(nèi),+n 指 n 天以前- -atime -n +n #按文件訪問(wèn)時(shí)間來(lái)查 GIN: 0px">- -ctime -n +n #按文件創(chuàng)建時(shí)間來(lái)查找文件,-n 指 n 天以內(nèi),+n 指 n 天以前- -type b/d/c/p/l/f #查是塊設(shè)備、目錄、字符設(shè)備、管道、符號(hào)鏈接、普通文件- -size n [c] #查長(zhǎng)度為 n 塊 [或 n 字節(jié)] 的文件- -depth #使查找在進(jìn)入子目錄前先行查找完本目錄- -prune   #通常和 -path 一起使用,用于將特定目錄排除在搜索條件之外。過(guò)濾條件寫在其他條件前面。在此我們對(duì)命令支持的選項(xiàng)全部展開詳解,根據(jù)日常經(jīng)驗(yàn)結(jié)合實(shí)際案例列舉最常用的選項(xiàng)進(jìn)行說(shuō)明:實(shí)例:在當(dāng)前目錄尋找文件名稱以.txt結(jié)尾的文件并打印出來(lái)[root@master ~]# find ~ -name "*.txt" -print /root/kubesphere-all-advanced-2.0.2/scripts/os/requirements.txt/root/kubesphere-all-advanced-2.0.2/kubesphere/roles/storages/NFS-Server/files/nfs-server-provisioner/templates/NOTES.txt/root/kubesphere-all-advanced-2.0.2/kubesphere/roles/ks-devops/jenkins/files/jenkins/jenkins-update-center/templates/NOTES.txt/root/kubesphere-all-advanced-2.0.2/kubesphere/roles/ks-devops/harbor/files/harbor/harbor/templates/NOTES.txt/root/kubesphere-all-advanced-2.0.2/kubesphere/roles/metrics-server/files/metrics-server/templates/NOTES.txt/root/kubesphere-all-advanced-2.0.2/kubesphere/roles/openpitrix/files/openpitrix/kubernetes/password.txt查找 /usr/bin 目錄下大于 10M 的文件[root@master ~]# find /usr/bin -size +10000k -exec ls -ld {} \; -rwxr-xr-x. 1 root root 13606800 Jul 10 2018 /usr/bin/ceph-dencoder-rwxr-xr-x. 1 root root 15863688 Jul 10 2018 /usr/bin/ceph-objectstore-tool-rwxr-xr-x. 1 root root 15589080 Jul 10 2018 /usr/bin/ceph-osd-rwxr-xr-x. 1 root root 33073928 Feb 10 2019 /usr/bin/docker-rwxr-xr-x. 1 root root 38088856 Feb 10 2019 /usr/bin/docker-containerd-rwxr-xr-x. 1 root root 68608416 Feb 10 2019 /usr/bin/dockerd-rwxr-xr-x. 1 root root 20895160 Feb 10 2019 /usr/bin/docker-containerd-ctr-rwxr-xr-x. 1 root root 10785264 Jul 10 2018 /usr/bin/ceph-mon查找當(dāng)前目錄下權(quán)限為 777 的文件[root@master ~]# find . -perm 777 -print ./.helm/repository/cache/local-index.yaml./kubesphere-all-v2.1.0/k8s/extra_playbooks/inventory./kubesphere-all-v2.1.0/k8s/extra_playbooks/roles./kubesphere-all-v2.1.0/k8s/contrib/terraform/openstack/hosts

5. 爬蟲的學(xué)習(xí)基礎(chǔ)

學(xué)習(xí)爬蟲,我們需要如下的基礎(chǔ)知識(shí):Python 語(yǔ)言基礎(chǔ)數(shù)據(jù)庫(kù)基礎(chǔ)知識(shí)如果大家還沒有 Python 語(yǔ)言或者數(shù)據(jù)庫(kù)的基礎(chǔ),可以參考慕課網(wǎng)相關(guān)的 wiki 進(jìn)行學(xué)習(xí)。當(dāng)然,為了方便大家理解,我會(huì)在代碼中加入詳細(xì)的注釋,即使大家沒有 Python 語(yǔ)言基礎(chǔ),也可以先理解大致流程,然后查漏補(bǔ)缺,學(xué)習(xí)相應(yīng)的知識(shí)。有些同學(xué)也許會(huì)疑惑,為什么開發(fā)爬蟲一定要使用python語(yǔ)言呢,其他語(yǔ)言不可以嗎?這就不得不說(shuō) Python 的第三方的庫(kù)了,Python 之所以那么流行,正式因?yàn)樗蟹浅6嗟膸?kù),且這些庫(kù)性能和使用都比較簡(jiǎn)潔高效,配合著 Python 語(yǔ)言本身的高效,僅僅需要 10 行代碼左右就可實(shí)現(xiàn)一個(gè)簡(jiǎn)單的爬蟲,而用 java/C/C++ 等其他語(yǔ)言,至少要寫幾十行代碼,因此,使用 Python 開發(fā)爬蟲程序贏得了眾多程序員的青睞。舉個(gè)例子,比如,大家習(xí)慣了在樓下的便利店買飲料喝,雖然門口一公里外有更大的超市,我相信你也不愿意去買,因?yàn)樘闊┎粔虮憷_@正是 Python 語(yǔ)言成為爬蟲屆的主流語(yǔ)言的精髓所在。后面的學(xué)習(xí)中,我們會(huì)用到幾個(gè) Python 的第三方庫(kù),所謂第三方庫(kù),指的是相對(duì)于 Python 的官方庫(kù)而言(例如,系統(tǒng)“os”,時(shí)間“time”等庫(kù)\),由非官方發(fā)布的庫(kù),如 requests 等庫(kù),我們稱之為第三方庫(kù)。Python 的官方庫(kù)在安裝 Python 解釋器的時(shí)候已經(jīng)默認(rèn)安裝好了,而第三方庫(kù)需要我們?nèi)ナ謩?dòng)安裝。例如我們?cè)谂老x開發(fā)中會(huì)經(jīng)常用到的 Requests 庫(kù)。安裝第三方庫(kù)非常的簡(jiǎn)單,只需要在終端中執(zhí)行下面這條命令即可:pip install requests后面,再講到具體的庫(kù)的時(shí)候,還會(huì)進(jìn)行詳細(xì)的介紹。下面列舉了一下爬蟲開發(fā)中常用的 Python 的庫(kù)的對(duì)比:包簡(jiǎn)介urllibpython自帶的庫(kù),不需要安裝。 但是,urlib 在不同的 python 版本中,存在明細(xì)的區(qū)別,在實(shí)際開發(fā)中,太過(guò)繁瑣,而且無(wú)法對(duì) header 偽裝,容易被封掉,所以,現(xiàn)在使用的人數(shù)不是很多。requests與 urllib 相比,不僅具備了 url 的所用功能,更重要的的語(yǔ)法簡(jiǎn)潔優(yōu)雅,而且,在兼容上, 完全兼容python2 和 python3,非常方便。 同時(shí),它也可以對(duì)請(qǐng)求進(jìn)行偽裝。urllib3urllib3 庫(kù)提供一些 urllib 沒有的重要特性,比如說(shuō)線程安全,連接池,支持壓縮編碼等。這里推薦使用 requests 庫(kù),簡(jiǎn)單方便,上手容易,對(duì)于使用爬蟲的新手來(lái)說(shuō),非常的合適。如果沒有特殊說(shuō)明,我們后面的課程默認(rèn)使用 requests 庫(kù)。

2. Docker 介紹

Docker 是一個(gè)開源的應(yīng)用容器引擎,讓開發(fā)者可以打包他們的應(yīng)用以及依賴包到一個(gè)可移植的容器中,然后發(fā)布到任何流行的 Linux 系統(tǒng)或 Windows 系統(tǒng)上,也可以實(shí)現(xiàn)虛擬化,容器是完全使用沙箱機(jī)制,相互之間不會(huì)有任何接口。首先我們來(lái)簡(jiǎn)單了解一下 Docker 的組成。在 Docker 中有 4 個(gè)基本組成:Docker Image 鏡像Docker Container 容器Docker Client 客戶端Docker Daemon 守護(hù)進(jìn)程Docker 和 Zookeeper 同樣是 C/S 的架構(gòu),Docker 服務(wù)端通過(guò) Docker Daemon 守護(hù)進(jìn)程來(lái)維持長(zhǎng)時(shí)間的運(yùn)行,Docker Client 客戶端通過(guò) CLI 的 API 來(lái)對(duì) Docker 服務(wù)端的 Docker Image 鏡像和 Docker Container 容器來(lái)進(jìn)行操作。我們通過(guò) API 去 Docker Hub 的官網(wǎng)拉取 Image 鏡像,然后通過(guò) Image 鏡像來(lái)創(chuàng)建 Container 容器。在我們使用 Docker 來(lái)搭建 Zookeeper 集群的時(shí)候,也同樣的先去 Docker Hub 的官網(wǎng)拉取 Zookeeper 的鏡像,然后通過(guò)這個(gè) Zookeeper 鏡像就能創(chuàng)建 Zookeeper 的容器實(shí)例了。這個(gè) Zookeeper 的容器實(shí)例就是一個(gè) Zookeeper 服務(wù),我們就可以通過(guò) Zookeeper 客戶端來(lái)進(jìn)行連接和操作了。簡(jiǎn)單了介紹了 Docker ,接下來(lái)我們介紹如何安裝 Docker。

2. Class文件數(shù)據(jù)類型

根據(jù) Java 虛擬機(jī)規(guī)范的規(guī)定,Class 文件格式采用一種類似于 C 語(yǔ)言結(jié)構(gòu)體的偽結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù),這種偽結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無(wú)符號(hào)數(shù)和表。無(wú)符號(hào)數(shù):無(wú)符號(hào)數(shù)屬于基本的數(shù)據(jù)類型,以 u1、u2、u4、u8 來(lái)分別代表 1 個(gè)字節(jié)、2 個(gè)字節(jié)、4 個(gè)字節(jié)和 8 個(gè)字節(jié);無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字、索引引用、數(shù)量值或者按照 UTF-8 編碼構(gòu)成的字符串值;表:表是由多個(gè)無(wú)符號(hào)數(shù)或者其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類型,所有表都習(xí)慣性地以“info”結(jié)尾。表用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù),整個(gè) Class 文件本質(zhì)上就是一張表。Tips:無(wú)符號(hào)數(shù)和表這兩種類型的數(shù)據(jù),初次來(lái)看非常的抽象,從概念層面來(lái)看似乎很難理解。我們無(wú)需著急, 本節(jié)所講述的魔數(shù),次版本號(hào),主版本號(hào)以及常量池計(jì)數(shù)器皆為無(wú)符號(hào)數(shù)類型,而常量池為表類型,講解這些結(jié)構(gòu)時(shí),我會(huì)為大家提供示意圖,使學(xué)習(xí)者從感官上看到這兩種數(shù)據(jù)類型,從而徹底理解這兩種數(shù)據(jù)類型。

1.2 自動(dòng)內(nèi)存管理

在計(jì)算機(jī)發(fā)展的早期,硬件性能很差,為了最大程度的壓榨硬件性能,編程語(yǔ)言提供了手動(dòng)管理內(nèi)存的機(jī)制。手動(dòng)管理內(nèi)存的機(jī)制的優(yōu)點(diǎn)在于能夠有效規(guī)劃和利用內(nèi)存,其缺點(diǎn)在于太繁瑣了,很容易出錯(cuò)。隨著計(jì)算機(jī)的發(fā)展,硬件性能不斷提高,這時(shí)候出現(xiàn)的編程語(yǔ)言,例如:Java、C#、PHP、Python,則提供了自動(dòng)管理內(nèi)存的機(jī)制:程序員申請(qǐng)內(nèi)存后,不需要再顯式的釋放內(nèi)存,由編程語(yǔ)言的解釋器負(fù)責(zé)釋放內(nèi)存,從根本上杜絕了 “內(nèi)存泄漏” 這類錯(cuò)誤。在下面的 Python 程序中,在無(wú)限循環(huán)中不斷的申請(qǐng)內(nèi)存:class Person: def __init__(self, name, age): self.name = name self.age = agewhile True: person = Person('tom', 13)類 Person 包含兩個(gè)屬性:name 和 age在 while 循環(huán)中,使用類 Person 生成一個(gè)實(shí)例 person需要申請(qǐng)一塊內(nèi)存用于保存實(shí)例 person 的屬性Python 解釋器運(yùn)行這個(gè)程序時(shí),發(fā)現(xiàn)實(shí)例 person 不再被引用后,會(huì)自動(dòng)的釋放 person 占用的空間。因此這個(gè)程序可以永遠(yuǎn)的運(yùn)行下去,而不會(huì)把內(nèi)存耗盡。

2. 整型

整型就是整數(shù)類型,和數(shù)學(xué)中的整數(shù)意義相同,例如:// 聲明一個(gè)整型變量ageint age = 10;// 聲明一個(gè)整型變量 scoreint score = 100;整型除了 int 類型,還有 short 和 long 類型,也就是短整型和長(zhǎng)整型,他們用于存放不同范圍的整數(shù)值。需要注意的是,long 類型的值后面要以大寫字母 L 或小寫字母 l 結(jié)尾。請(qǐng)閱讀以下代碼:// 聲明一個(gè)短整型變量ashort a = 20;// 聲明一個(gè)長(zhǎng)整型變量blong b = 100l;// 聲明一個(gè)長(zhǎng)整型變量clong c = 100L;Tips:對(duì)于長(zhǎng)整型,推薦后面總以大寫字母 L 結(jié)尾,因?yàn)樾懽帜?l 與數(shù)字 1 容易混淆。和數(shù)學(xué)一樣,計(jì)算機(jī)中的整型也有進(jìn)制的概念,Java 可以通過(guò)以下幾種進(jìn)制系統(tǒng)表示:十進(jìn)制:基數(shù)為 10,由 0 到 9 組成; 這是我們?nèi)粘I钪惺褂玫臄?shù)字系統(tǒng);十六進(jìn)制:基數(shù)為 16,由數(shù)字 0 到 9 和字母 A 到 F 組成;二進(jìn)制:基數(shù)為 2,由數(shù)字 0 和 1 組成。在 Java 中,十六進(jìn)制和二進(jìn)制需要以前綴區(qū)分,前綴 0x 表示十六進(jìn)制,前綴 0b 表示二進(jìn)制,我們來(lái)看一個(gè)示例:// 十進(jìn)制表示數(shù)字10int decimalValue = 10;// 十六進(jìn)制表示數(shù)字10int hexadecimalValue = 0xA;// 二進(jìn)制表示數(shù)字10int binaryValue = 0b1010;

3. 對(duì)象級(jí)聯(lián)

OOP 代碼中經(jīng)常會(huì)出現(xiàn)類似于 A 對(duì)象引用 B 對(duì)象,B 對(duì)象引用 C 對(duì)象的現(xiàn)象。 類似于現(xiàn)實(shí)生活中的小王有一輛汽車,汽車有一把鑰匙……如果每一個(gè)用戶都有一輛汽車,用 OOP 描述,意味著 User 類中有一個(gè)對(duì) Car 的引用類型屬性。public class User { private String userName; private String userPassword; private Car car; //……}假設(shè) Car 類結(jié)構(gòu)如下:public class Car {private String carType;private String carColor;//……}在注冊(cè)時(shí),除了要輸入用戶信息之外,還需要指定用戶所擁有的汽車類型、顏色。那么,控制器是否能自動(dòng)綁定用戶以及汽車數(shù)據(jù)?Tips: 為什么注冊(cè)時(shí)要輸入汽車信息,不要糾結(jié),只是一個(gè)用來(lái)說(shuō)明問(wèn)題的例子。答案是肯定的。只需要在表單頁(yè)面中添加如下代碼,控制器端不做任何修改。如此,除了能接收用戶數(shù)據(jù)外,還能接收汽車的信息。<form action="user/register" method="post"> 用戶名:<input type="text" value="" name="userName" /> <br /> 密碼:<input type="password" value="" name="userPassword" /> <br /> 汽車類型:<input type="text" value="" name="car.carType" /> <br /> 汽車顏色:<input type="text" value="" name="car.carColor" /> <br /> <input type="submit" value="注冊(cè)" name="btnRegister" /> <input type="reset" value="重置" name="btnReset" /></form>也就是說(shuō),Spring MVC 支持對(duì)象級(jí)聯(lián)自動(dòng)數(shù)據(jù)綁定。Spring MVC 支持多層級(jí)的對(duì)象級(jí)聯(lián)。

2.3 多個(gè) K 線圖

由于 k 線圖的數(shù)據(jù)項(xiàng)只能通過(guò)這種 4 位數(shù)組的格式定義,這會(huì)導(dǎo)致 k 線圖與折線圖、柱狀圖、散點(diǎn)圖等其他直角坐標(biāo)圖表有些許不同:x、y 軸中必須有一條是類目軸,根據(jù)用戶習(xí)慣,通常會(huì)選擇 x 軸做類目軸;k 線圖無(wú)法通過(guò)series.data 推斷類目屬性,所以類目軸必須通過(guò) axis.data 項(xiàng)顯式聲明類目數(shù)據(jù);k 線圖的 series.data 與類目軸的 axis.data 根據(jù)數(shù)據(jù)出現(xiàn)的位置關(guān)聯(lián)。當(dāng)序列上只有一個(gè) k 線圖時(shí),問(wèn)題不大,但若要在同一坐標(biāo)系上渲染多個(gè) k 線圖時(shí),則需要對(duì)數(shù)據(jù)做一些額外的處理。比如,假設(shè)要展示下述四種蔬果的價(jià)格變化:[ { sku: '小臺(tái)農(nóng)芒果', data: [ ['2020-3-4', 9, 7, 14, 1], ['2020-3-6', 7, 3, 12, 1], ['2020-3-9', 3, 2, 8, 1], ['2020-3-12', 2, 1, 5, 1], ['2020-3-14', 1, 4, 6, 1], ['2020-3-16', 4, 7, 3, 1], ['2020-3-19', 7, 10, 3, 1], ['2020-3-21', 10, 12, 6, 1], ['2020-3-23', 12, 15, 5, 1], ['2020-3-25', 15, 13, 8, 1], ], }, { sku: '阿克蘇蘋果', data: [ ['2020-3-3', 6, 7, 13, 4], ['2020-3-6', 7, 5, 8, 3], ['2020-3-8', 5, 8, 11, 3], ['2020-3-9', 8, 11, 15, 2], ['2020-3-10', 11, 9, 13, 5], ['2020-3-11', 9, 12, 20, 2], ['2020-3-12', 12, 9, 16, 6], ['2020-3-15', 9, 11, 13, 6], ['2020-3-17', 11, 14, 19, 8], ['2020-3-19', 14, 17, 21, 11], ], }, { sku: '海南西州蜜瓜', data: [ ['2020-3-1', 16, 17, 23, 11], ['2020-3-2', 17, 15, 24, 10], ['2020-3-4', 15, 12, 20, 6], ['2020-3-7', 12, 9, 16, 3], ['2020-3-9', 9, 6, 12, 1], ['2020-3-12', 6, 3, 8, 1], ['2020-3-14', 3, 0, 5, 1], ['2020-3-17', 0, 2, 6, 1], ['2020-3-19', 2, 5, 8, 1], ['2020-3-21', 5, 8, 11, 1], ], }, { sku: '泰國(guó)椰青', data: [ ['2020-3-2', 18, 21, 22, 14], ['2020-3-3', 21, 19, 28, 13], ['2020-3-4', 19, 22, 27, 17], ['2020-3-6', 22, 21, 27, 17], ['2020-3-7', 21, 22, 24, 13], ['2020-3-9', 22, 21, 26, 16], ['2020-3-11', 21, 19, 23, 12], ['2020-3-13', 19, 21, 25, 15], ['2020-3-16', 21, 22, 23, 15], ['2020-3-18', 22, 20, 29, 19], ], },];注意,四個(gè)系列的統(tǒng)計(jì)時(shí)間不同,由于 k 線圖無(wú)法自動(dòng)推算類目軸的類別數(shù)據(jù),所以第一步需要收集所有類目值:function retriveDates(series) { const categories = []; const len = series.length; for (let i = 0; i < len; i++) { // 找出尚未收集的時(shí)間值 const dates = series[i].data .map(([date]) => date) .filter((date) => categories.findIndex((cat) => cat === date) < 0); // 批量插入 categories.splice(categories.length, 0, ...dates); } return categories;}第二步,需要根據(jù)應(yīng)用場(chǎng)景對(duì)收集到的類目值進(jìn)行排序,本例中為簡(jiǎn)便起見,將借助 moment 庫(kù)進(jìn)行時(shí)間排序:function sort(categories) { const format = 'YYYY-MM-DD'; return categories.sort((d1, d2) => moment(d1, format) - moment(d2, format));}第三步,有了類目數(shù)據(jù)之后,還需要整理系列數(shù)據(jù)的順序,使得數(shù)據(jù)與其對(duì)應(yīng)的類目能夠一一對(duì)應(yīng):function reschedule(series, categories) { return series.map(({ sku, data }) => { return { name: sku, data: categories.map((cat) => { const index = data.findIndex((c) => c === cat); return index >= 0 ? data[index].slice(1) : null; }), }; });}經(jīng)過(guò)上述步驟就可以得到所有類目值及調(diào)整后的系列數(shù)據(jù),完整代碼:1375示例效果:

1.2 Python 的特色和優(yōu)點(diǎn)

入門簡(jiǎn)單,對(duì)初學(xué)者友好在介紹 Python 語(yǔ)言時(shí),通常會(huì)提到 Python 是一門易學(xué)的編程語(yǔ)言,易學(xué)是 Python 最鮮明的特色。相對(duì)于 C、C++、Java 等編程語(yǔ)言,Python 的易學(xué)體現(xiàn)在它的學(xué)習(xí)曲線非常的平緩,如果學(xué)習(xí)曲線如果太陡峭,顯然不適合一般人去學(xué)習(xí)掌握,大部分人沒入門就放棄了。今年 11 月15 日,年過(guò)半百的 SOHO 中國(guó)董事長(zhǎng)潘石屹在微博上宣布,正式開始學(xué)習(xí)Python,占據(jù)了熱搜榜,表明了 Python 語(yǔ)言的簡(jiǎn)單易學(xué)已經(jīng)深入人心。高級(jí)語(yǔ)言,不用考慮底層硬件細(xì)節(jié)程序設(shè)計(jì)語(yǔ)言被分類為高級(jí)語(yǔ)言和低級(jí)語(yǔ)言:使用低級(jí)語(yǔ)言編寫程序時(shí),程序員需要考慮底層硬件細(xì)節(jié),例如:手工的分配和釋放內(nèi)存,程序編寫時(shí)繁瑣易錯(cuò);而使用高級(jí)語(yǔ)言編寫程序時(shí),高級(jí)語(yǔ)言會(huì)自動(dòng)的處理底層硬件細(xì)節(jié),例如:自動(dòng)的釋放不再使用的內(nèi)存,程序員不用考慮底層硬件細(xì)節(jié),專注于解決問(wèn)題本身。Python 是一種典型的高級(jí)語(yǔ)言,向程序員屏蔽了底層硬件細(xì)節(jié)。例如,Python 程序既可以在 x86 處理器上運(yùn)行,也可以在 arm 處理器上運(yùn)行,程序員不需要了解 x86 處理器和 arm 處理器的指令,就可以完成計(jì)算"1 加 2"這樣的任務(wù)。語(yǔ)法簡(jiǎn)潔直觀Python 語(yǔ)法接近自然語(yǔ)言,提供了很多簡(jiǎn)潔、直觀和易于理解的表達(dá)方式,非常適用來(lái)描述求解問(wèn)題的邏輯。實(shí)現(xiàn)相同程序功能時(shí),Python 語(yǔ)言的代碼行數(shù)僅相當(dāng)于其他語(yǔ)言的 1/5 至 1/10。更少的代碼行數(shù)、更簡(jiǎn)潔的表達(dá)方式可減少程序錯(cuò)誤以及縮短開發(fā)周期。強(qiáng)大的標(biāo)準(zhǔn)庫(kù)和第三方庫(kù)Python 語(yǔ)言可以將復(fù)雜的功能封裝為模塊(又稱為庫(kù)),將功能實(shí)現(xiàn)的細(xì)節(jié)隱藏起來(lái),使用該模塊(庫(kù))的程序員不需要了解實(shí)現(xiàn)的細(xì)節(jié)。通過(guò)調(diào)用模塊封裝好的功能,可以用僅僅幾行 Python 代碼實(shí)現(xiàn)某項(xiàng)復(fù)雜的功能,例如可以用一行代碼就實(shí)現(xiàn)一個(gè) Web 服務(wù)器。在 Python 的應(yīng)用領(lǐng)域中,如:web 開發(fā)、人工智能、網(wǎng)絡(luò)爬蟲、數(shù)據(jù)分析等領(lǐng)域,已經(jīng)存在了大量的模塊,程序使用這些模塊就可以輕松開發(fā)出應(yīng)用程序。Python 的標(biāo)準(zhǔn)庫(kù)是隨著 Pyhon 安裝的時(shí)候默認(rèn)自帶的庫(kù),提供了有文本處理、系統(tǒng)管理、網(wǎng)絡(luò)處理等功能。Python 的第三方庫(kù),是由各家廠商和 Python 愛好者開發(fā)的庫(kù),第三方庫(kù)需要下載后安裝到 Python 的安裝目錄下。強(qiáng)大的標(biāo)準(zhǔn)庫(kù)和第三方庫(kù),讓 Python 程序員能夠輕松實(shí)現(xiàn)各種復(fù)雜的功能。

3. 貪心算法求解背包問(wèn)題

首先,這里我們考慮背包的容量為 30,并給出這個(gè)問(wèn)題中我們考慮到的各類物品及對(duì)應(yīng)的重量和價(jià)值,如下:物品 n (i)12345 重量 w (i)105151020 價(jià)值 v (i)2030152510回顧一下我們?cè)谪澬乃惴ń榻B中提到的,能夠應(yīng)用貪心算法求解的問(wèn)題需要滿足兩個(gè)條件:最優(yōu)子結(jié)構(gòu)和貪心選擇,接下來(lái),我們就具體來(lái)看看在背包問(wèn)題中的最優(yōu)子結(jié)構(gòu)和貪心選擇分別是什么。首先,如果一個(gè)問(wèn)題的最優(yōu)解包含其子問(wèn)題的最優(yōu)解,則此問(wèn)題具備最優(yōu)子結(jié)構(gòu)的性質(zhì)。問(wèn)題的最優(yōu)子結(jié)構(gòu)性質(zhì)是該問(wèn)題是否可以用貪心算法求解的關(guān)鍵所在。對(duì)于背包問(wèn)題,很顯然它是滿足最優(yōu)子結(jié)構(gòu)性質(zhì)的,因?yàn)橐粋€(gè)容量為 c 的背包問(wèn)題必然包含容量小于 c 的背包問(wèn)題的最優(yōu)解的。其次,我們需要考慮在背包問(wèn)題中,我們應(yīng)該按照什么樣的貪心選擇進(jìn)行選取。很顯然,如果要使得最終的價(jià)值最大,那么必定需要使得選擇的單位重量的物品的價(jià)值最大。所以背包問(wèn)題的貪心選擇策略是:優(yōu)先選擇單位重量?jī)r(jià)值最大的物品,當(dāng)這個(gè)物品選擇完之后,繼續(xù)選擇其他價(jià)值最大的物品。這里的背包問(wèn)題可以用貪心算法實(shí)現(xiàn),是因?yàn)楸嘲x擇放入的物品可以進(jìn)行拆分,即并不需要放入整個(gè)物品,可以選擇放入部分物品,我們這樣的背包選擇問(wèn)題為部分背包問(wèn)題(可以只選擇物品的部分),與之對(duì)應(yīng)的另一種背包問(wèn)題為 0-1 背包問(wèn)題,這個(gè)時(shí)候整個(gè)物品不可以拆分,只可以選擇放入或者不放入,0-1 背包問(wèn)題用貪心算法并不能求得準(zhǔn)確的解,需要用動(dòng)態(tài)規(guī)劃算法求解。背包問(wèn)題求解詳解:在這里,我們按照優(yōu)先選擇單位重量?jī)r(jià)值最大的物品,所以第一步需要計(jì)算單位每個(gè)物品的單位價(jià)值,如下:unitValue(1) = 20/10 = 2unitValue(2) = 30/5 = 6unitValue(3) = 15/15 = 1unitValue(4) = 25/10 = 2.5unitValue(5) = 10/20 = 0.5所以我們有:unitValue(2) > unitValue(4) > unitValue(1) > unitValue(3) > unitValue(5)當(dāng)考慮背包的容量為 30 時(shí), 我們發(fā)現(xiàn)可以按照物品的單位價(jià)值進(jìn)行依次放入,直至背包容量放滿或者物品放完為止,放入的過(guò)程如下:物品類型放入重量背包使用容量背包剩余容量 25525410151511025535300按照如上步驟我們簡(jiǎn)單分析了一下背包問(wèn)題的求解過(guò)程,接下來(lái),我們看一下如何用代碼實(shí)現(xiàn)背包問(wèn)題。

4.2 Java 中有哪些關(guān)鍵字

關(guān)鍵字一律用小寫字母標(biāo)識(shí),Java 語(yǔ)言中定義了如下表所示的關(guān)鍵字:關(guān)鍵字說(shuō)明 abstract 表明類或者成員方法具有抽象屬性 assert 斷言,常用于程序的調(diào)試 boolean 基本數(shù)據(jù)類型:布爾類型 break 提前跳出一個(gè)塊 byte 基本數(shù)據(jù)類型,字節(jié)類型 case 用在 switch 語(yǔ)句之中,表示其中的一個(gè)分支 catch 用在異常處理中,用來(lái)捕捉異常 char 基本數(shù)據(jù)類型:字符類型 class 用于聲明一個(gè)類 const 保留關(guān)鍵字 continue 回到一個(gè)塊的開始處 default 默認(rèn),用在 switch 語(yǔ)句中,表明一個(gè)默認(rèn)的分支;JDK1.8 以后也作用于聲明接口函數(shù)的默認(rèn)實(shí)現(xiàn) do 用在 do-while 循環(huán)結(jié)構(gòu)中 double 基本數(shù)據(jù)類型:雙精度浮點(diǎn)數(shù)類型 else 用在條件語(yǔ)句中,表明當(dāng)條件不成立時(shí)的分支 enum 枚舉 extends 表明一個(gè)類型是另一個(gè)類型的子類型。對(duì)于類,可以是另一個(gè)類或者抽象類;對(duì)于接口,可以是另一個(gè)接口 final 用來(lái)說(shuō)明最終屬性,表明一個(gè)類不能派生出子類,或者成員方法不能被覆蓋,或者成員域的值不能被改變,用來(lái)定義常量 finally 用于處理異常情況,用來(lái)聲明一個(gè)基本肯定會(huì)被執(zhí)行到的語(yǔ)句塊 float 基本數(shù)據(jù)類型之一,單精度浮點(diǎn)數(shù)類型 for 一種循環(huán)結(jié)構(gòu)的引導(dǎo)詞 goto 保留關(guān)鍵字,沒有具體含義 if 條件語(yǔ)句的引導(dǎo)詞 implements 表明一個(gè)類實(shí)現(xiàn)了給定的接口 import 表明要訪問(wèn)指定的類或包 instanceof 用來(lái)測(cè)試一個(gè)對(duì)象是否是指定類型的實(shí)例對(duì)象 int 基本數(shù)據(jù)類型之一,整數(shù)類型 interface 接口 long 基本數(shù)據(jù)類型之一,長(zhǎng)整數(shù)類型 native 用來(lái)聲明一個(gè)方法是由與計(jì)算機(jī)相關(guān)的語(yǔ)言(如 C/C++/FORTRAN 語(yǔ)言)實(shí)現(xiàn)的 new 用來(lái)創(chuàng)建新實(shí)例對(duì)象 package 包 private 一種訪問(wèn)控制方式:私用模式 protected 一種訪問(wèn)控制方式:保護(hù)模式 public 一種訪問(wèn)控制方式:共用模式 return 從成員方法中返回?cái)?shù)據(jù) short 基本數(shù)據(jù)類型之一,短整數(shù)類型 static 表明具有靜態(tài)屬性 strictfp 用來(lái)聲明 FP_strict(單精度或雙精度浮點(diǎn)數(shù))表達(dá)式遵循 IEEE 754 算術(shù)規(guī)范 super 表明當(dāng)前對(duì)象的父類型的引用或者父類型的構(gòu)造方法 switch 分支語(yǔ)句結(jié)構(gòu)的引導(dǎo)詞 synchronized 表明一段代碼需要同步執(zhí)行 this 指向當(dāng)前實(shí)例對(duì)象的引用 throw 拋出一個(gè)異常 throws 聲明在當(dāng)前定義的成員方法中所有需要拋出的異常 transient 聲明不用序列化的成員域 try 嘗試一個(gè)可能拋出異常的程序塊 void 聲明當(dāng)前成員方法沒有返回值 volatile 表明兩個(gè)或者多個(gè)變量必須同步地發(fā)生變化 while 用在循環(huán)結(jié)構(gòu)中

2. 前言

前面的小節(jié)中,我們談到了外鍵是體現(xiàn)數(shù)據(jù)關(guān)系中的核心點(diǎn),那么定義好的外鍵如何被使用了?連接操作是使用外鍵最主要的方式,通過(guò)連接可以將兩個(gè)或多個(gè)擁有外鍵關(guān)聯(lián)的數(shù)據(jù)表的數(shù)據(jù)進(jìn)行合并,然后選擇需要的數(shù)據(jù)字段。SQL 有五種 連接 方式:內(nèi)連接(Inner),全外連接(Full Outer),左外連接(Left Outer),右外連接(Right Outer)和交叉連接(Cross)。本小節(jié),我們將學(xué)習(xí)五種連接中比較基礎(chǔ)的交叉連接和內(nèi)連接。本小節(jié)測(cè)試數(shù)據(jù)如下,請(qǐng)先在數(shù)據(jù)庫(kù)中執(zhí)行:DROP TABLE IF EXISTS imooc_class;CREATE TABLE imooc_class( id int PRIMARY KEY, class_name varchar(20));INSERT INTO imooc_class(id,class_name) VALUES(1,'SQL必知必會(huì)'), (2,'C語(yǔ)言入門'),(3,'JAVA高效編程'),(4,'JVM花落知多少');DROP TABLE IF EXISTS imooc_user;CREATE TABLE imooc_user( id int PRIMARY KEY, username varchar(20), class_id int references imooc_class(id));INSERT INTO imooc_user(id,username,class_id) VALUES(1,'pedro', 1), (2,'peter', 1),(3,'faker', 2), (4,'lucy', 4),(5,'jery', NULL);說(shuō)明: 我們分別新建了 imooc_class 表——課程表,和 imooc_user 表——用戶表;其中 imooc_user 表中的 class_id 作為外鍵指向 imooc_class 的主鍵 id;若 class_id 為 NULL 則表示該用戶暫時(shí)還未加入任何課程,否則 class_id 表示用戶參加課程的 id 。注意: 為了保證 SQL 可以在每個(gè)數(shù)據(jù)庫(kù)中執(zhí)行,所以沒有使用 AUTO_INCREMENT 等約束,但真實(shí)業(yè)務(wù)場(chǎng)景下請(qǐng)?zhí)砑由稀?/p>

2. 配置靜態(tài)IP和主機(jī)名

將所有虛擬機(jī)都啟動(dòng)起來(lái),在虛擬機(jī)中使用ip a 命令查看各個(gè)虛擬機(jī)的IP地址,由于默認(rèn)系統(tǒng)使用的DHCP服務(wù),網(wǎng)卡隨機(jī)獲取了當(dāng)前網(wǎng)段內(nèi)空閑的IP,地址我們希望各個(gè)節(jié)點(diǎn)的IP能固定起來(lái),不要隨意變動(dòng)。根據(jù)當(dāng)前的網(wǎng)絡(luò)狀態(tài)(192.168.1.0/24網(wǎng)段),我們計(jì)劃節(jié)點(diǎn)配置如下:Tips:不同的網(wǎng)絡(luò)環(huán)境獲取的網(wǎng)段、IP 地址也不一致,需要根據(jù)自己的實(shí)際情況調(diào)整,設(shè)定變更的IP地址需要確認(rèn)沒有被占用。虛擬機(jī)名稱IP地址主機(jī)名hostnamemaster-1192.168.1.200master1.nodeworker-1192.168.1.201worker1.nodeworker-2192.168.1.202worker2.nodeworker-3192.168.1.203worker3.node1. 設(shè)定 master-1 的主機(jī)名declare -x HOSTNAME="master1.node"hostname $HOSTNAMEcat << EOF > /etc/hostname$HOSTNAMEEOFcat << EOF > /etc/hosts127.0.0.1 $HOSTNAME192.168.1.200 master1.node192.168.1.201 worker1.node192.168.1.202 worker2.node192.168.1.203 worker3.nodeEOF2. 設(shè)定 master-1 的 IP 地址:# 默認(rèn)的網(wǎng)卡名稱就是enp0s3vi /etc/sysconfig/network-scripts/ifcfg-enp0s3TYPE="Ethernet"PROXY_METHOD="none"BROWSER_ONLY="no"BOOTPROTO="static"DEFROUTE="yes"IPV4_FAILURE_FATAL="no"NAME="enp0s3"DEVICE="enp0s3"ONBOOT="yes"# 指定IP地址、網(wǎng)關(guān)和掩碼,根據(jù)實(shí)際情況進(jìn)行修改IPADDR=192.168.1.200GATEWAY=192.168.1.1NETMASK=255.255.255.0DNS1=223.6.6.6保存更改后執(zhí)行:# 重載網(wǎng)口配置nmcli c reload輸入命令hostname 和ip a 查看master-1的主機(jī)名和IP更改。3. 對(duì)worker-1、 worker-2、worker-3節(jié)點(diǎn)進(jìn)行上述操作,一定要注意主機(jī)名和IP地址變更。

6. ls 命令參數(shù)介紹

下面列出比較常見的 ls 相關(guān)的參數(shù)僅供參考:ls 命令參數(shù)名稱功能與作用描述-a表示 –all, 列出目錄下的所有文件,包括以 . 開頭的隱藏文件。-A表示 -a,但不列出 .(表示當(dāng)前目錄) 和 .. (表示當(dāng)前目錄的上級(jí)目錄)。-c表示按照最后更改時(shí)間排序,可以使用 -l 顯示出時(shí)間。-f對(duì)輸出的文件不進(jìn)行排序。-g類似 -l,列出目錄信息。-G表示 –no-group,不列出任何有關(guān)組的信息。-h表示 –human-readable,與 -l 配合使用,如 ls -lh以容易理解的格式列出文件大小,如 10k、20M、100G。-i表示 –inode,打印出每個(gè)文件的 inode 號(hào)。-l除了文件名之外,還將文件的權(quán)限、所有者、文件大小等信息詳細(xì)列出來(lái),ls -l 可以簡(jiǎn)寫為 ll。-L表示 –dereference,當(dāng)顯示符號(hào)鏈接的文件信息時(shí),顯示符號(hào)鏈接所指示的對(duì)象而并非符號(hào)鏈接本身的信息。-m所有文件或目錄以 , 分隔,并填滿整行行寬。-o類似 -l,顯示文件的除組信息外的詳細(xì)信息。-r表示 –reverse,按照當(dāng)前排序順序相反的順序排列。-R表示 –recursive,遞歸出所有子目錄層。-s表示 –size,以塊大小為單位列出所有文件的大小。-S根據(jù)文件大小排序。

2.2 Numpy <a href="http://www.numpy.org">官網(wǎng)</a>

Numpy(Numerical Python)是一個(gè)開源的Python科學(xué)計(jì)算庫(kù),用于快速處理任意維度的數(shù)組。Numpy支持常見的數(shù)組和矩陣操作。Numpy 使用 ndarray 對(duì)象來(lái)處理多維數(shù)組,該對(duì)象是一個(gè)快速而靈活的大數(shù)據(jù)容器。使用 Python 列表可以存儲(chǔ)一維數(shù)組,通過(guò)列表的嵌套可以實(shí)現(xiàn)多維數(shù)組,那么為什么還需要使用Numpy 的 ndarray呢?這是因?yàn)閷?duì)于同樣的數(shù)值計(jì)算任務(wù),ndarray的計(jì)算速度比直接使用Python快很多,節(jié)約了時(shí)間。機(jī)器學(xué)習(xí)的最大特點(diǎn)就是大量的數(shù)據(jù)運(yùn)算,效率高是一個(gè)主要要求。簡(jiǎn)單說(shuō)一下 ndarray 為什么快?ndarray 在存儲(chǔ)數(shù)據(jù)的時(shí)候,數(shù)據(jù)與數(shù)據(jù)的地址都是連續(xù)的,這樣就給使得批量操作數(shù)組元素時(shí)速度更快。這是因?yàn)閚darray中的所有元素的類型都是相同的,而 Python 列表中的元素類型是任意的,所以ndarray 在存儲(chǔ)元素時(shí)內(nèi)存可以連續(xù),而 python 原生 lis 就只能通過(guò)尋址方式找到下一個(gè)元素;ndarray支持并行化運(yùn)算;Numpy 底層使用 C 語(yǔ)言編寫,內(nèi)部解除了GIL(全局解釋器鎖),其對(duì)數(shù)組的操作速度不受 Python解釋器的限制 ,所以,其效率遠(yuǎn)高于純 Python 代碼。關(guān)于 numpy 使用,請(qǐng)參考

1.1 CBV 的基本使用

前面我們已經(jīng)介紹了 CBV 的基本使用方法,其基本流程如下:定義視圖類 (TestView)該類繼承視圖基類 View,然后實(shí)現(xiàn)對(duì)應(yīng) HTTP 請(qǐng)求的方法。Django 在 View 類的基礎(chǔ)上又封裝了許多視圖類,如專門返回模板的 TemplateView 視圖類、用于顯示列表數(shù)據(jù)的 ListView 視圖類等等。這些封裝的是圖能夠進(jìn)一步減少大家的重復(fù)代碼,后面我會(huì)詳細(xì)介紹這些封裝的視圖類的使用以及其源碼實(shí)現(xiàn)。# 代碼路徑 hello_app/views.py# ...class TestView(View): def get(self, request, *args, **kwargs): return HttpResponse('hello, get\n') def post(self, request, *args, **kwargs): return HttpResponse('hello, post\n') def put(self, request, *args, **kwargs): return HttpResponse('hello, put\n') def delete(self, request, *args, **kwargs): return HttpResponse('hello, delete\n') @csrf_exempt def dispatch(self, request, *args, **kwargs): return super(TestView, self).dispatch(request, *args, **kwargs)配置 URLConf,如下:# 代碼路徑 hello_app/urls.py# ...urlpatterns = [ path('test-cbv/', views.TestView.as_view(), name="test-cbv")]注意:不是直接寫視圖類,而是要調(diào)用視圖類的 as_view() 方法,這個(gè) as_view() 方法返回的也是一個(gè)函數(shù)。啟動(dòng) Django 工程,測(cè)試:# 啟動(dòng)django服務(wù)(django-manual) [root@server first_django_app]# python manage.py runserver 0.0.0.0:8888Watching for file changes with StatReloaderPerforming system checks...System check identified no issues (0 silenced).April 15, 2020 - 07:08:32Django version 2.2.11, using settings 'first_django_app.settings'Starting development server at http://0.0.0.0:8888/Quit the server with CONTROL-C# 打開另一個(gè)xshell窗口,發(fā)送如下請(qǐng)求[root@server ~]# curl -XGET http://127.0.0.1:8888/hello/test-cbv/hello, get[root@server ~]# curl -XPOST http://127.0.0.1:8888/hello/test-cbv/hello, post[root@server ~]# curl -XPUT http://127.0.0.1:8888/hello/test-cbv/hello, put[root@server ~]# curl -XDELETE http://127.0.0.1:8888/hello/test-cbv/hello, delete

2.3 Anaconda 的安裝

1. 等待下載完成后,雙擊下載好的 .exe 文件,進(jìn)入到軟件的安裝界面:Pandas安裝12. “下一步”之后彈出界面,選擇“I Agree”,同意用戶協(xié)議:3. 彈出用戶類型選擇窗口,選擇默認(rèn)的“Just Me(recommended)”就可以,選擇好之后點(diǎn)擊下一步:4. 選擇自己電腦上的安裝目錄,因?yàn)槲募容^大,建議不要選擇安裝在C盤,選擇的目錄至少要有3G的存儲(chǔ)空間共使用,選擇好目錄后,點(diǎn)擊下一步:5. 此處兩處都要勾選,雖然第一個(gè)不是必須的,但是對(duì)于我們新手可以很好的節(jié)省配置環(huán)境變量的時(shí)間,第二個(gè)是勾選將anaconda作為默認(rèn)的python開發(fā)環(huán)境,勾選之后點(diǎn)擊“Install”進(jìn)行安裝:等待安裝的過(guò)程,這個(gè)根據(jù)電腦配置的不同,等待的時(shí)長(zhǎng)有快有慢,耐心等待即可:6. 等待安裝進(jìn)度條結(jié)束之后,會(huì)出現(xiàn)“Completed”提示詞,然后點(diǎn)擊"Next"下一步:7. 然后點(diǎn)擊"Next"下一步:8. 最后把默認(rèn)勾選的兩項(xiàng)取消勾選,再點(diǎn)擊"Finish",安裝就結(jié)束了:Anaconda 的安裝過(guò)程很簡(jiǎn)單,按照上面的步驟操作就可以了,因?yàn)?Anaconda 自身集成了很多的工具,以及 Python 開發(fā)語(yǔ)言,所以這里我們就不需要再單獨(dú)安裝 Python 了,可以看一下 Anaconda 目錄下集成的工具:

5. 元組

元組是一個(gè)有序的序列,元組中所有的元素放在 () 中間,并用逗號(hào)分開,例如:(1, 2, 3),一個(gè)包含 3 個(gè)整數(shù)的元組(‘a(chǎn)’, ‘b’, ‘c’),一個(gè)包含 3 個(gè)字符串的元組元組中的元素是順序排列的,可以通過(guò)索引訪問(wèn)元組的元素,例如:>>> tuple = (1, 2, 3)>>> tuple[0]1>>> tuple[1]2>>> tuple[2]3在第 1 行,創(chuàng)建了一個(gè)元組。在第 2 行,通過(guò) tuple[0],訪問(wèn)元組的第 0 個(gè)元素,元組的下標(biāo)從 0 開始。在第 4 行,通過(guò) tuple[1],訪問(wèn)元組的第 1 個(gè)元素。在第 6 行,通過(guò) tuple[2],訪問(wèn)元組的第 2 個(gè)元素。元組與列表的區(qū)別在于:列表創(chuàng)建后可以修改,元組創(chuàng)建后不可以修改。下面的程序首先創(chuàng)建列表 [1, 2, 3],然后修改的第 0 項(xiàng)元素,程序輸出表明修改成功了。>>> list = [1, 2, 3]>>> list[0] = 11>>> list[11, 2, 3]下面的程序首先創(chuàng)建元組 (1, 2, 3),然后修改的第 0 項(xiàng)元素,程序輸出表明修改失敗了。在第 6 行打印輸出 tuple,發(fā)現(xiàn) tuple 沒有發(fā)送變化。>>> tuple = (1, 2, 3)>>> tuple[0] = 11Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: 'tuple' object does not support item assignment>>> tuple(1, 2, 3)

1. 創(chuàng)建第一個(gè)Django應(yīng)用程序

在創(chuàng)建第一個(gè) Django 應(yīng)用程序之前,我們需要使用 pyenv 工具創(chuàng)建相應(yīng)的虛擬環(huán)境,操作如下:新建一個(gè)統(tǒng)一的目錄,用于存放 Django 工程代碼:[root@server ~]# mkdir django-manual[root@server ~]# cd django-manual/進(jìn)入虛擬環(huán)境,然后建立 django-manual 虛擬環(huán)境。一般而言每個(gè) Django 工程會(huì)創(chuàng)建一個(gè)虛擬環(huán)境,這樣避免各個(gè) Python 項(xiàng)目之間發(fā)生包沖突。建立好虛擬環(huán)境之后,激活虛擬環(huán)境。操作如下:[root@server django-manual]# pyenv versions system* 3.8.1 (set by /root/.pyenv/version) 3.8.1/envs/env-3.8.1 env-3.8.1 # 新建django-manual虛擬環(huán)境[root@server django-manual]# pyenv virtualenv 3.8.1 django-manualLooking in links: /tmp/tmpllz1yd5eRequirement already satisfied: setuptools in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (41.2.0)Requirement already satisfied: pip in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (19.2.3)# 手動(dòng)新建的虛擬環(huán)境[root@server django-manual]# pyenv activate django-manualpyenv-virtualenv: prompt changing will be removed from future release. configure `export PYENV_VIRTUALENV_DISABLE_PROMPT=1' to simulate the behavior.(django-manual) [root@server django-manual]#接下來(lái),我們需要安裝 Django 2.2.11 版本(提示: django 3.0 最近發(fā)布了,但是還處于初步完善階段,所以本次介紹以 Django 2.2.11 版本為準(zhǔn)):(django-manual) [root@server django-manual]# pip install django==2.2.11 -i https://pypi.tuna.tsinghua.edu.cn/simpleLooking in indexes: https://pypi.tuna.tsinghua.edu.cn/simpleCollecting django==2.2.11 Downloading https://pypi.tuna.tsinghua.edu.cn/packages/be/76/7ccbcf52366590ca76997ce7860308b257b79962a4e4fada5353f72d7be5/Django-2.2.11-py3-none-any.whl (7.5MB) |████████████████████████████████| 7.5MB 71kB/s Requirement already satisfied: sqlparse in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (from django==2.2.11) (0.3.1)Requirement already satisfied: pytz in /root/.pyenv/versions/3.8.1/envs/django-manual/lib/python3.8/site-packages (from django==2.2.11) (2019.3)Installing collected packages: djangoSuccessfully installed django-2.2.11WARNING: You are using pip version 19.2.3, however version 20.0.2 is available.You should consider upgrading via the 'pip install --upgrade pip' command.(django-manual) [root@server django-manual]# python -c "import django; print(django.__version__)"2.2.11這樣子,虛擬環(huán)境中就安裝好了 Django 2.2.11。Django 提供 django-admin 命令來(lái)幫助我們創(chuàng)建項(xiàng)目和應(yīng)用,我們只需要使用 django-admin 命令即可快速創(chuàng)建我們的第一個(gè) Django 項(xiàng)目:(django-manual) [root@server django-manual]# django-admin startproject first_django_app(django-manual) [root@server django-manual]# (django-manual) [root@server django-manual]# tree ..└── first_django_app ├── first_django_app │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py2 directories, 5 filesTips:盡量在 Linux 平臺(tái)上完成實(shí)驗(yàn),在 Windows 下操作在安裝 mysqlclient 模塊是會(huì)稍微有些工作要做。Django 項(xiàng)目可以由多個(gè)應(yīng)用(app)組成,每個(gè)應(yīng)用是一個(gè)邏輯上劃分,即將某一個(gè)功能模塊劃歸到這個(gè)應(yīng)用。創(chuàng)建一個(gè)應(yīng)用使用 django-admin starapp 應(yīng)用名即可:(django-manual) [root@server django-manual]# cd first_django_app/(django-manual) [root@server first_django_app]# django-admin startapp hello_app(django-manual) [root@server first_django_app]# tree ..├── first_django_app│ ├── __init__.py│ ├── settings.py│ ├── urls.py│ └── wsgi.py├── hello_app│ ├── admin.py│ ├── apps.py│ ├── __init__.py│ ├── migrations│ │ └── __init__.py│ ├── models.py│ ├── tests.py│ └── views.py└── manage.py3 directories, 12 files可以看到,在使用 django-admin 執(zhí)行創(chuàng)建 hello_app 應(yīng)用后,該命令給我們生成了 hello_app 以及若干代碼文件。為了能讓 Django 項(xiàng)目運(yùn)行起來(lái),我們需要調(diào)整下 settings.py 文件中的配置:# settings.py 中默認(rèn)使用 sqlite3...DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }}...# 現(xiàn)在調(diào)整成 mysql 數(shù)據(jù)庫(kù),讀者需要自行準(zhǔn)備mysql服務(wù)DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'django_manual', 'USER': 'store', 'PASSWORD': 'xxxxxxxxx', 'HOST': '180.76.152.113', 'PORT': '9000', }}有了數(shù)據(jù)庫(kù)支持,還需要在 Django 那邊安裝 mysql 相關(guān)的模塊包。通常安裝的是 mysqlclient 模塊:# 安裝相應(yīng)的依賴包(django-manual) [root@server first_django_app]# yum install mysql-devel -y(django-manual) [root@server first_django_app]# pip install mysqlclient -i https://pypi.tuna.tsinghua.edu.cn/simpleLooking in indexes: https://pypi.tuna.tsinghua.edu.cn/simpleCollecting mysqlclient Downloading https://pypi.tuna.tsinghua.edu.cn/packages/d0/97/7326248ac8d5049968bf4ec708a5d3d4806e412a42e74160d7f266a3e03a/mysqlclient-1.4.6.tar.gz (85kB) |████████████████████████████████| 92kB 22.2MB/s Installing collected packages: mysqlclient Running setup.py install for mysqlclient ... doneSuccessfully installed mysqlclient-1.4.6最后一件事情,在啟動(dòng) Django 服務(wù)之前,必須要先創(chuàng)建數(shù)據(jù)庫(kù)。Django 服務(wù)默認(rèn)并不會(huì)幫我們創(chuàng)建好數(shù)據(jù)庫(kù),我們必須手工建好數(shù)據(jù)庫(kù),然后再啟動(dòng) Django 服務(wù):[root@server ~]# mysql -u store -pxxxxxxxxx -h 180.76.152.113 -P9000Welcome to the MariaDB monitor. Commands end with ; or \g.Your MySQL connection id is 37328Server version: 5.7.26 MySQL Community Server (GPL)Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.MySQL [(none)]> CREATE DATABASE IF NOT EXISTS django_manual DEFAULT CHARSET utf8;Query OK, 1 row affected (0.00 sec)MySQL [(none)]> show databases;+--------------------+| Database |+--------------------+| information_schema || alarms || dashboard || django_manual || graph || mysql || performance_schema || sys || uic |+--------------------+15 rows in set (0.00 sec)MySQL [(none)]> exit;Bye# ---------------------------------------------------------------------------------------(django-manual) [root@server first_django_app]# python manage.py runserver 0.0.0.0:8888Watching for file changes with StatReloaderPerforming system checks...System check identified no issues (0 silenced).You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.Run 'python manage.py migrate' to apply them.March 13, 2020 - 07:29:06Django version 2.2.11, using settings 'first_django_app.settings'Starting development server at http://0.0.0.0:8888/Quit the server with CONTROL-C.在完成上述這些步驟后,基本的工程就搭建起來(lái)了。接下來(lái)我們從外面訪問(wèn)這個(gè)端口結(jié)果如下:第一次訪問(wèn) django 服務(wù)這個(gè)是 Django 在配置中做的一個(gè)白名單機(jī)制,它有一個(gè) ALLOWED_HOSTS 配置參數(shù),它用來(lái)設(shè)置訪問(wèn)服務(wù)的白名單。如果想要允許任何主機(jī)訪問(wèn),直接設(shè)置如下:(django-manual) [root@server first_django_app]# cat first_django_app/settings.py...DEBUG = TrueALLOWED_HOSTS = ['*']...另外,默認(rèn) setting.py 中的 DEBUG 參數(shù)為 True。正因?yàn)槿绱?,?qǐng)求報(bào)錯(cuò)才會(huì)有如此詳細(xì)的提示。在正真上線部署時(shí)候,這個(gè)參數(shù)一定要關(guān)閉。如果我設(shè)置如下參數(shù)再次從外部請(qǐng)求該 Django 服務(wù)時(shí),瀏覽器的輸出結(jié)果如下圖所示??梢钥吹剑孙@示一個(gè)冷冰冰 400 錯(cuò)誤,無(wú)任何提示。這樣屏蔽錯(cuò)誤信息,防止有人從錯(cuò)誤結(jié)果中推斷服務(wù)漏洞,達(dá)到滲透的目的。(django-manual) [root@server first_django_app]# cat first_django_app/settings.py...DEBUG = FalseALLOWED_HOSTS = ['127.0.0.1']...設(shè)置 Debug=False 的錯(cuò)誤輸出我們重新設(shè)置好 DEBUG 和 ALLOWED_HOSTS 參數(shù)后,再次請(qǐng)求 Django 服務(wù),可以得到 Dajngo 內(nèi)置的歡迎頁(yè)面,提示我們服務(wù)已經(jīng)正常啟動(dòng)和運(yùn)行。正常訪問(wèn) Django 服務(wù)現(xiàn)在,我們寫一個(gè)最簡(jiǎn)單的 Hello, World 字符串輸出到頁(yè)面上。改動(dòng) first_django_app/first_django_app/url.py 文件,這個(gè)文件是所有 url 請(qǐng)求路由的入口,所有的映射關(guān)系都會(huì)先通過(guò)這里:(django-manual) [root@server first_django_app]# pwd/root/django-manual/first_django_app(django-manual) [root@server first_django_app]# cat first_django_app/urls.py """注釋性文本,省略"""from django.contrib import adminfrom django.urls import path## 新導(dǎo)入模塊from django.http import HttpResponse## 視圖函數(shù)def hello_world(*args, **kwargs): return HttpResponse("Hello, world.", content_type="text/plain")urlpatterns = [ path('admin/', admin.site.urls), ####添加的url映射,由上面的hello_world()函數(shù)處理 path('hello/', hello_world),]再次啟動(dòng) Django 服務(wù),訪問(wèn) 8888 端口的 /hello/ 路徑,可以看到頁(yè)面出現(xiàn) “Hello, world.” 這樣的字符,說(shuō)明我們的第一個(gè) URL 接口完成。頁(yè)面輸出 Hello,World.

2.1 Map 基本說(shuō)明

在前面的 數(shù)據(jù)結(jié)構(gòu)擴(kuò)展 一節(jié)我們已經(jīng)了解到 Map 的基本使用。和 Set 一樣,Map 也是一個(gè)構(gòu)造函數(shù),不能直接使用,需要通過(guò) new 的方式來(lái)創(chuàng)建一個(gè) Map 實(shí)例。var map = Map([iterable]);Map 對(duì)象在插入數(shù)據(jù)時(shí)是按照順序來(lái)插入的,也就是說(shuō)在插入時(shí)會(huì)保持插入的位置,Object 的在插入數(shù)據(jù)時(shí)沒有順序概念。Map 對(duì)象可以被 for...of 循環(huán),在每次迭代后會(huì)返回一個(gè)形式為 [key,value] 的數(shù)組,這個(gè)我們?cè)谙旅娴睦又袝?huì)說(shuō)到。Map 的本質(zhì)其實(shí)還是一個(gè)對(duì)象,并且它也是繼承 Object 的,看下面的實(shí)例:var map = new Map([["x", 1], ["y", 2]]);console.log(map instanceof Object); //true從上面的代碼中可以看出 Object 在實(shí)例 map 的原型鏈上。在創(chuàng)建 Map 實(shí)例時(shí)可以接收一個(gè)數(shù)組或是一個(gè)可遍歷的對(duì)象作為參數(shù),這個(gè)參數(shù)內(nèi)的每一項(xiàng)是鍵值的組合 [key, value] 第一個(gè)值時(shí)鍵,第二個(gè)值時(shí)鍵的值。在初始化 Map 對(duì)象時(shí),如果默認(rèn)參數(shù)的數(shù)組中超過(guò)兩個(gè)以上的值不會(huì)被 Map 對(duì)象讀取。var map = new Map([["x", 1, 'a', 'b'], ["y", 2, 'c'], ["z", 3, 'd']]);console.log(map) // Map(3) {"x" => 1, "y" => 2, "z" => 3}上面的代碼中,從打印的結(jié)果可以看出,數(shù)組中超過(guò)的元素都會(huì)被自動(dòng)忽略。

3. substitute 替換命令

除了簡(jiǎn)單的快捷鍵替換之外。還有更加強(qiáng)大的命令式替換。這種方式為了處理更加復(fù)雜的場(chǎng)景,尤其是結(jié)合正則表達(dá)式。substitute 是 Vim 中最常用的搜索和替換的命令。可以縮寫為 s。替換公式::[range]s[ubstitute]/{pattern}/{string}/[flags]默認(rèn)情況下,s 只會(huì)作用域當(dāng)前行的第一個(gè)匹配項(xiàng)。flag選項(xiàng)選項(xiàng)含義備注g全局替換global(也只是針對(duì)當(dāng)前行全局而非全文)c每次替換前需要確認(rèn)confirme沒有匹配項(xiàng)時(shí)不顯示錯(cuò)誤errori表示忽略大小寫ignoreI表示區(qū)分大小寫range 選項(xiàng)選項(xiàng)含義備注%執(zhí)行范圍:全局這個(gè)才是真正意義上的全局n,mn到m 行替換:12,33s/time/ting/g$最后一行:12,$s/time/ting/gn;/pattern/n行到下一個(gè)匹配patter行:12;/ting/time/ting/g實(shí)操部分打開 main.go 文件,并將文件中的 time 全部替換為 ting:正則表達(dá)式實(shí)戰(zhàn)正則表達(dá)式會(huì)在后面章節(jié)中詳細(xì)講解。這里只是針對(duì)查找場(chǎng)景做展示。查找 main.go 文件中包含在小括號(hào)中的代碼:Tips: 需要注意這里查找標(biāo)識(shí)符后面的\v: 這個(gè)主要為了進(jìn)入 very magic 模式——也就是符合正常正則表達(dá)式規(guī)則的模式。詳細(xì)部分會(huì)在后面正則表達(dá)式中詳細(xì)講解。

3. 使用 rootfs

在 Linux 環(huán)境的 /root/test/ 目錄下,已經(jīng)有一份打包好的 rootfs,我們直接使用它即可。我們來(lái)探查一下它的目錄結(jié)構(gòu):tree -L 1發(fā)現(xiàn)它與宿主機(jī)的/目錄的結(jié)構(gòu)一致, 因?yàn)樗褪俏覀內(nèi)萜鲗⒁褂玫?。下面我們讓我們的容器使用這個(gè) rootfs,編寫一個(gè) helloworld 程序Tip: 下面我們會(huì)使用到 chroot, chroot 是在 unix 系統(tǒng)的一個(gè)操作,針對(duì)正在運(yùn)作的軟件行程和它的子進(jìn)程,改變它外顯的根目錄。一個(gè)運(yùn)行在這個(gè)環(huán)境下,經(jīng)由 chroot 設(shè)置根目錄的程序,它不能夠?qū)@個(gè)指定根目錄之外的文件進(jìn)行訪問(wèn)動(dòng)作,不能讀取,也不能更改它的內(nèi)容。Namespace Mount 事實(shí)上就是受到 chroot 的啟發(fā)改進(jìn)而來(lái)的。cd /root/test/## 運(yùn)行容器環(huán)境./container## chroot切換到rootfschroot /root/rootfs /bin/bash# 進(jìn)入容器中的home目錄,編寫代碼 helloworld.c并編譯執(zhí)行cd /homevim helloworld.c#include <stdio.h>int main(){ // printf() 中字符串需要引號(hào) printf("Hello, World!"); return 0;}保存后編譯運(yùn)行cc helloworld.c -o helloworld./helloworld我們將看到終端中顯示:hello, world!這樣我們獲得了一個(gè)文件系統(tǒng)目錄與宿主機(jī)隔離的環(huán)境,在這里,我們可以放心大膽地操作,不用擔(dān)心影響到宿主機(jī)的環(huán)境。至此,我們獲取了一個(gè)可以使用 C 語(yǔ)言編譯運(yùn)行應(yīng)用的容器。

2.1 Django 中和上傳文件相關(guān)的基礎(chǔ)類

這一節(jié)主要是來(lái)分析下 Django 中和上傳文件相關(guān)的代碼。首先介紹下幾個(gè)基礎(chǔ)類:FileProxyMixin 類:用于輔助文件上傳的 mixin 類。來(lái)看看其源碼長(zhǎng)相:# 源碼路徑: django/core/files/utils.pyclass FileProxyMixin: """ A mixin class used to forward file methods to an underlaying file object. The internal file object has to be called "file":: class FileProxy(FileProxyMixin): def __init__(self, file): self.file = file """ encoding = property(lambda self: self.file.encoding) fileno = property(lambda self: self.file.fileno) flush = property(lambda self: self.file.flush) isatty = property(lambda self: self.file.isatty) newlines = property(lambda self: self.file.newlines) read = property(lambda self: self.file.read) readinto = property(lambda self: self.file.readinto) readline = property(lambda self: self.file.readline) readlines = property(lambda self: self.file.readlines) seek = property(lambda self: self.file.seek) tell = property(lambda self: self.file.tell) truncate = property(lambda self: self.file.truncate) write = property(lambda self: self.file.write) writelines = property(lambda self: self.file.writelines) @property def closed(self): return not self.file or self.file.closed def readable(self): if self.closed: return False if hasattr(self.file, 'readable'): return self.file.readable() return True def writable(self): if self.closed: return False if hasattr(self.file, 'writable'): return self.file.writable() return 'w' in getattr(self.file, 'mode', '') def seekable(self): if self.closed: return False if hasattr(self.file, 'seekable'): return self.file.seekable() return True def __iter__(self): return iter(self.file)注意:可以看到,想要繼承這個(gè) Mixin 并正常使用,繼承的類應(yīng)該有實(shí)例屬性 file。這里 Mixin 中的屬性和我們?cè)?Python 中用 open()方法得到的文件對(duì)象的屬性幾乎一致,后面實(shí)驗(yàn)中可以得到佐證。File 類:專門為上傳文件的定義的基類,直接看源代碼。class File(FileProxyMixin): DEFAULT_CHUNK_SIZE = 64 * 2 ** 10 def __init__(self, file, name=None): self.file = file if name is None: name = getattr(file, 'name', None) self.name = name if hasattr(file, 'mode'): self.mode = file.mode def __str__(self): return self.name or '' def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self or "None") def __bool__(self): return bool(self.name) def __len__(self): return self.size @cached_property def size(self): if hasattr(self.file, 'size'): return self.file.size if hasattr(self.file, 'name'): try: return os.path.getsize(self.file.name) except (OSError, TypeError): pass if hasattr(self.file, 'tell') and hasattr(self.file, 'seek'): pos = self.file.tell() self.file.seek(0, os.SEEK_END) size = self.file.tell() self.file.seek(pos) return size raise AttributeError("Unable to determine the file's size.") def chunks(self, chunk_size=None): """ Read the file and yield chunks of ``chunk_size`` bytes (defaults to ``File.DEFAULT_CHUNK_SIZE``). """ chunk_size = chunk_size or self.DEFAULT_CHUNK_SIZE try: self.seek(0) except (AttributeError, UnsupportedOperation): pass while True: data = self.read(chunk_size) if not data: break yield data def multiple_chunks(self, chunk_size=None): """ Return ``True`` if you can expect multiple chunks. NB: If a particular file representation is in memory, subclasses should always return ``False`` -- there's no good reason to read from memory in chunks. """ return self.size > (chunk_size or self.DEFAULT_CHUNK_SIZE) # ... def open(self, mode=None): if not self.closed: self.seek(0) elif self.name and os.path.exists(self.name): self.file = open(self.name, mode or self.mode) else: raise ValueError("The file cannot be reopened.") return self def close(self): self.file.close()這里就能看到我們之前在實(shí)驗(yàn)1中用來(lái)保存上傳文件時(shí)用到的 chunks() 方法,我們現(xiàn)在通過(guò) Django 的命令行模式來(lái)使用下這個(gè) File 類,看它有哪些功能。(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from django.core.files import File接下來(lái),我們看到 File 類實(shí)例化時(shí)要關(guān)聯(lián)一個(gè)文件對(duì)象,我們使用之前實(shí)驗(yàn)1上傳的文件 upload.txt 作為實(shí)例化參數(shù):>>> fp = open('/root/test/django/upload.txt', 'r+')>>> f = File(fp)接下來(lái)我們就可以測(cè)試 File 對(duì)象中的各種屬性和方法了。具體操作如下:>>> f.name'/root/test/django/upload.txt'>>> f.size47# 按照20字節(jié)大小,判斷文件需不需要分塊讀入>>> f.multiple_chunks(20)True# 默認(rèn)塊大小64k,47字節(jié)太小了,所以不用分塊讀入>>> f.multiple_chunks()False我們可以使用 chunks() 方法分塊讀取文件內(nèi)容,然后做我們想做的事情,如下:>>> for c in f.chunks():... print('本次讀入:{}'.format(c))... 本次讀入:測(cè)試上傳文件xxxxxspyinx test upload>>> for c in f.chunks(20):... print('本次讀入:{}'.format(c))... 本次讀入:測(cè)試上傳文件xxxxxspyinx本次讀入: test upload上面測(cè)試了2種形式,一種不需要分塊讀如數(shù)據(jù),一口氣讀完所有內(nèi)容(因?yàn)槟J(rèn)的分塊大小大于文件內(nèi)容)。另一種則設(shè)置小一些分塊大小,這樣會(huì)每次讀取最多20字節(jié)內(nèi)容,依次打印讀取到的內(nèi)容。接下來(lái)我們看下和上傳相關(guān)的兩個(gè)文件類:TemporaryUploadedFile 和 InMemoryUploadedFile。這兩個(gè)類都是繼承自 UploadedFile,而 UploadedFile 又是繼承至 File 類的。# 源碼路徑: django/core/files/uploadedfile.pyclass UploadedFile(File): """ An abstract uploaded file (``TemporaryUploadedFile`` and ``InMemoryUploadedFile`` are the built-in concrete subclasses). An ``UploadedFile`` object behaves somewhat like a file object and represents some file data that the user submitted with a form. """ def __init__(self, file=None, name=None, content_type=None, size=None, charset=None, content_type_extra=None): super().__init__(file, name) self.size = size self.content_type = content_type self.charset = charset self.content_type_extra = content_type_extra def __repr__(self): return "<%s: %s (%s)>" % (self.__class__.__name__, self.name, self.content_type) def _get_name(self): return self._name def _set_name(self, name): # Sanitize the file name so that it can't be dangerous. if name is not None: # Just use the basename of the file -- anything else is dangerous. name = os.path.basename(name) # File names longer than 255 characters can cause problems on older OSes. if len(name) > 255: name, ext = os.path.splitext(name) ext = ext[:255] name = name[:255 - len(ext)] + ext self._name = name name = property(_get_name, _set_name)這個(gè)類相比于 File 基類主要是增加了多個(gè)實(shí)例屬性,其他方法到?jīng)]啥變化。接下里來(lái)看繼承這個(gè)類的兩個(gè) File 類:class TemporaryUploadedFile(UploadedFile): """ A file uploaded to a temporary location (i.e. stream-to-disk). """ def __init__(self, name, content_type, size, charset, content_type_extra=None): _, ext = os.path.splitext(name) file = tempfile.NamedTemporaryFile(suffix='.upload' + ext, dir=settings.FILE_UPLOAD_TEMP_DIR) super().__init__(file, name, content_type, size, charset, content_type_extra) def temporary_file_path(self): """Return the full path of this file.""" return self.file.name def close(self): try: return self.file.close() except FileNotFoundError: # The file was moved or deleted before the tempfile could unlink # it. Still sets self.file.close_called and calls # self.file.file.close() before the exception. passclass InMemoryUploadedFile(UploadedFile): """ A file uploaded into memory (i.e. stream-to-memory). """ def __init__(self, file, field_name, name, content_type, size, charset, content_type_extra=None): super().__init__(file, name, content_type, size, charset, content_type_extra) self.field_name = field_name def open(self, mode=None): self.file.seek(0) return self def chunks(self, chunk_size=None): self.file.seek(0) yield self.read() def multiple_chunks(self, chunk_size=None): # Since it's in memory, we'll never have multiple chunks. return False這兩段代碼非常簡(jiǎn)單,代碼展現(xiàn)的邏輯也非常清晰。TemporaryUploadedFile 打開的文件是臨時(shí)生成的文件,而 InMemoryUploadedFile 類對(duì)于上傳的文件會(huì)保存到內(nèi)存中。我們熟悉了這兩個(gè)類之后來(lái)對(duì)應(yīng)的處理上傳文件的 Handler,一個(gè)會(huì)使用 TemporaryUploadedFile 類使用臨時(shí)文件保存上傳的文件,另一個(gè)會(huì)使用 InMemoryUploadedFile 將上傳文件的內(nèi)容寫到內(nèi)存中:class TemporaryFileUploadHandler(FileUploadHandler): """ Upload handler that streams data into a temporary file. """ def new_file(self, *args, **kwargs): """ Create the file object to append to as data is coming in. """ super().new_file(*args, **kwargs) # 這個(gè)文件是打開臨時(shí)文件的句柄 self.file = TemporaryUploadedFile(self.file_name, self.content_type, 0, self.charset, self.content_type_extra) # 將受到的數(shù)據(jù)寫入到對(duì)應(yīng)的臨時(shí)文件中 def receive_data_chunk(self, raw_data, start): self.file.write(raw_data) # 處理文件完畢 def file_complete(self, file_size): # 文件指針,指向初始位置 self.file.seek(0) # 設(shè)置文件大小 self.file.size = file_size return self.fileclass MemoryFileUploadHandler(FileUploadHandler): """ File upload handler to stream uploads into memory (used for small files). """ def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None): """ Use the content_length to signal whether or not this handler should be used. """ # Check the content-length header to see if we should # If the post is too large, we cannot use the Memory handler. self.activated = content_length <= settings.FILE_UPLOAD_MAX_MEMORY_SIZE def new_file(self, *args, **kwargs): super().new_file(*args, **kwargs) if self.activated: self.file = BytesIO() raise StopFutureHandlers() def receive_data_chunk(self, raw_data, start): """Add the data to the BytesIO file.""" if self.activated: self.file.write(raw_data) else: return raw_data def file_complete(self, file_size): """Return a file object if this handler is activated.""" if not self.activated: return self.file.seek(0) return InMemoryUploadedFile( file=self.file, field_name=self.field_name, name=self.file_name, content_type=self.content_type, size=file_size, charset=self.charset, content_type_extra=self.content_type_extra )

直播
查看課程詳情
微信客服

購(gòu)課補(bǔ)貼
聯(lián)系客服咨詢優(yōu)惠詳情

幫助反饋 APP下載

慕課網(wǎng)APP
您的移動(dòng)學(xué)習(xí)伙伴

公眾號(hào)

掃描二維碼
關(guān)注慕課網(wǎng)微信公眾號(hào)