分類
發燒車訊

Python 3.9 beta2 版本發布了,看看這 7 個新的 PEP 都是什麼?

原作:Jake Edge

譯者:豌豆花下貓@Python貓

英文:https://lwn.net/Articles/819853/

隨着 Python 3.9.0b1 的發布,即開發周期中計劃的四個 beta 版本的首個,Python 3.9 的功能已經是完善了。在 10 月發布最終版本之前,還會有許多測試和穩定性方面的工作要做。

(譯註:beta1 版本發佈於 5 月 18 日,作者文章寫於 5 月 20,而到本篇譯文發布時,beta2 剛好在今天即 6 月 9 日發布,這是一個巧合!)

該發布說明中列出了被 3.9 接受的 7 個 Python 增強提案(PEP)。我們研究了其中的一些 PEP,看到有一些更新。現在似乎是一個介紹 Python 3.9 帶來的一些東西的好時機。

1、字符串操作

有時最簡單(表明上的)的事情最困難,或者至少會引起巨大的討論。其中大部分的爭議是關於命名(還能是什麼?),但是給標準字符串對象添加函數,來刪除前綴和後綴,這種想法是毫無爭議的。

是否可以將那些詞綴(前綴和後綴的統稱)指定為序列,以便在一次調用中處理多個詞綴,這一點尚不明確,最後它被從提案中刪除了,等待着其他人再次推動更改。

在 3 月底,Dennis Sweeney 在 python-dev 郵件列表上請求核心開發者支持 PEP 616(“字符串刪除前綴和後綴的方法”)。他指出了自 2019 年 3 月以來關於該話題的 python-ideas 討論。埃里克·史密斯(Eric V. Smith)同意支持該 PEP,這促使 Sweeney 發布並啟動了討論。

在最初版本中,他使用 cutprefix() 和 cutsuffix() 作為要添加給字符串對象的方法名。四種類型的 Python 對象將獲得新的方法:str(Unicode 字符串),byte(二進制序列),bytearray(可變的二進制序列)和 collections.UserString(字符串對象的一種封裝)。

它的寫法如下:

'abcdef'.cutprefix('abc') # 返回'def'
'abcdef'.cutsuffix('ef') # 返回'abcd'

針對命名部分,出現了一大堆的建議。基本上很少有人喜歡“cut”,因此“strip”、“strim”和“remove”被提出來了,並且都獲得了一些支持。

stripprefix() 以及 stripsuffix() 由於 PEP 中指出的一種理由,至少是被部分地反對了;現有的“strip”函數令人困惑,因此應避免重用該名稱。

str.lstrip() 和 str.rstrip() 方法也用於刪除前導字符和尾隨字符,但是它們對於真正在尋找 cutprefix() 功能的程序員來說是一個困惑的來源。

*strip() 在調用時接收一個字符串參數,但會將其視為一組字符,並從字符串開頭或結尾消除:

'abcdef'.lstrip('abc') # 返回“def”,符合預期
'abcbadefed'.lstrip('abc') # 返回'defed',完全不符合預期

最終,removeprefix() 和 removesuffix() 似乎佔據了上風,這正是 Sweeney 最終改成的版本。Guido van Rossum 也支持這些名字。

埃里克·法格倫(Eric Fahlgren)這樣搞笑地總結了命名的爭論:

我認為如果你先寫文檔,則名稱的選擇會更容易些:

cutprefix – 刪除指定的前綴。

trimprefix – 刪除指定的前綴。

stripprefix – 刪除指定的前綴。

removeprefix – 刪除指定的前綴。廢話

Sweeney 更新了 PEP,回應了許多評論,但還增加了提議將字符串元組作為詞綴的功能(可以在 PEP GitHub 倉庫中看到該版本)。

但是史蒂文·達普拉諾(Steven D’Aprano)不確定這樣做是否合理。他指出,唯一接受元組參數的字符串操作是 str.startswith() 和 str.endswith(),而它們不返回字符串(只是一個布爾值)。他懷疑添加這一種接收元組參數卻返回字符串的方法,因為無論選擇何種規則來處理元組,對於某些人來說都是“錯誤的”選擇。

例如:

這裏的困難在於,如果兩個或多個前綴都能匹配,則“剪切這些前綴中的一個”的概念是模稜兩可的。對 startwith 沒有區別:

 "extraordinary".startswith(('ex', 'extra'))

因為是從左到右,從最短到最大,甚至是隨機順序匹配都為True。但是對於 cutprefix,應該刪除哪個前綴?

如他所說,建議的規則是使用從左到右處理元組的第一個匹配字符串,但是有些人可能想要最長的匹配或最後一個匹配;這一切都取決於使用的上下文。他建議在提交添加此類行為之前,要給該功能更多的“浸泡時間”(譯註:即預備時間):“在添加多前綴/後綴的支持之前,我們首先應該對簡單的情況進行一些實際的體驗。”

伊桑·弗曼(Ethan Furman)同意達普拉諾(D’Aprano)的意見。但是維克托·斯汀納(Victor Stinner)強烈贊成元組參數的想法,只不過,他還想知道當傳入的元組有空字符串時,會怎麼處理。根據 PEP 提議,在處理元組時遇到空字符串(實際上可以匹配任何內容)只會返回原始字符串,這會導致令人驚訝的結果:

cutsuffix("Hello World", ("", " World"))    # 返回 "Hello World"
cutsuffix("Hello World", (" World", ""))    # 返回 "Hello"

這個例子不太明顯;詞綴不一定是硬編碼的,因此空字符串可能會溜進意想不到的位置。Stinner 建議如果遇到空字符串,則拋出 ValueError,類似於 str.split()。但是 Sweeney 決定完全刪除元組參數功能,以便“允許對此有更強見解的人在另外的 PEP 中提出並捍衛一系列的語義”。他在 3 月 28 日發布了該 PEP 的最新版本。

4 月 9 日,Sweeney 發起了一個指導委員會 issue,請求對其 PEP 進行評審。4 月 20 日,Stinner 代表委員會接受了該提案。

這是一個很小的更改,但值得花時間確保它具有長期適用的接口(和語義)。我們將在 Python 3.9 中看到 removeprefix() 和removesuffix()。

2、新解析器

並不令人感到驚訝的是,指導委員會已經接受了我們在 4 月中旬介紹過的 CPython 新解析器。PEP 617(“CPython 新的 PEG 解析器”)由 Python 創始人即前仁慈的獨裁者(BDFL) Guido van Rossum 以及 Pablo Galindo Salgado 和 Lysandros Nikolaou 共同提出。

它已經運行良好,並且在現有解析器的速度和內存使用方面提升了 10% 以內的性能。由於解析器是基於解析表達語法(PEG),因此也將簡化語言規範。CPython 現有的 LL(1) 解析器存在諸多缺點和一些 hack,新的解析器將會消除掉。

這一更改為 Python 超越 LL(1) 語法鋪平了道路,儘管現有語言並不完全是 LL(1)。這一更改不會太快,因為計劃是在 Python 3.9 的命令行中提供開關,保持現有解析器可用。

但是 Python 3.10 將刪除現有的解析器,這可能會導致語言變更。如果做了那些更改,那麼,其它的 Python 實現(例如 PyPy 和 MicroPython)就需要切換解析器的 LL(1) 實現,以便跟上語言規範的要求。這可能會使核心開發者暫停進行此類更改。

3、更多內容

我們在三月初查看了 PEP 615(“在標準庫中支持 IANA 時區數據庫”)。它將在標準庫中添加一個zoneinfo 模塊,該模塊將有助於從 IANA 時區數據庫中(也稱為“Olson數據庫”)獲取時區信息,以填充時區對象。在撰寫本文時,它看起來很順利。

在 3 月底,Paul Ganssle 請求就該 PEP 作出決議。他認為在一個有趣的時間範圍內接受它,可能會很有趣:

… 我希望(出於異想天開的原因)在 4 月 5 日(星期日)UTC 時間 02:00-04:00 或 13:00-17:30 之間接受它,因為這些時間代表着地球上某些地方的不明確時間(主要在澳大利亞)。還有另一個時機,那就是在 4 月 19 日星期日 UTC 01:00-03:00 之間,這段時間在西撒哈拉是不明確的。

他意識到這可能難以實現,它當然不是優先考慮的事。指導委員會沒有錯過第二個時間窗太多;Barry Warsaw 於 4 月 20 日宣布接受該 PEP。

Python 現在將具有一種機制來訪問系統的時區數據庫,以創建和處理時區。另外,Python 軟件包索引(PyPI)中有一個 tzdata 模塊,它為缺少 IANA 數據的系統提供這些數據;它將由 Python 核心開發者維護。

PEP 593(“靈活的函數和變量註釋”)添加了一種將上下文特定的(context-specific)元數據與函數和變量關聯的方法。實際上,type hint 註解已擠出了很多年前在 Python 3.0 中實現的 PEP 3107(“函數註釋”)中設想的其它用例。PEP 593 使用註解的(Annotated)類型提示為這些用例創建了一種新的機制。

PEP 585(“標準集合中的類型提示泛型”)提供了另一種清除方法。它將允許刪除在 typing 模塊中維護的一組并行的類型別名,以支持泛型。例如,type.List 類型將不再需要支持諸如“dict[str,list[int]]”之類的註解(例如,一個帶有字符串鍵和整數列表的值的字典)。

字典“加法”的聯合操作也會是 Python 3.9 的一部分。它曾不時引起爭議,但是 2 月中旬,PEP 584(“給字典添加聯合操作符”)被 Van Rossum 推薦採納。指導委員會迅速同意了,該特性於 2 月 24 日合入。

最後一個 PEP 是 PEP 602(“Python 的年度發布周期”)。如提案所書,它將發布節奏從每 18 個月更改為每年一次。但是,開發和發布周期是重疊的,因此整個功能開發需要 12 個月的時間。當第一個 Python 3.9 beta 版本發布時(即現在),Python 3.10 的功能開發就開始了。請繼續關注來年的下一輪 PEP。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

分類
發燒車訊

[原創][開源] SunnyUI.Net 主題

SunnyUI.Net, 基於 C# .Net WinForm 開源控件庫、工具類庫、擴展類庫、多頁面開發框架

  • Blog: https://www.cnblogs.com/yhuse
  • Gitee: https://gitee.com/yhuse/SunnyUI
  • GitHub: https://github.com/yhuse/SunnyUI
  • 幫助文檔目錄: https://www.cnblogs.com/yhuse/p/SunnyUI_Menu.html
  • 歡迎交流,QQ群:  56829229 (SunnyUI技術交流群) 

主題

1、Color 色彩

SunnyUI為了避免視覺傳達差異,使用一套特定的調色板來規定顏色,為你所搭建的產品提供一致的外觀視覺感受。主要顏色參照Element(https://element.eleme.cn/

  • 主色

SunnyUI主要品牌顏色是鮮艷、友好的藍色。

  • 輔助色

除了主色外的場景色,需要在不同的場景中使用(例如紅色表示危險的操作)。

  • 中性色

中性色用於文本、背景和邊框顏色。通過運用不同的中性色,來表現層次結構。

 

2、Rect邊框

我們對邊框進行統一規範,可用於按鈕、卡片、彈窗等組件里。

主要屬性如下:

  • RectColor:邊框顏色
  • RectDisableColor:控件不可用時邊框顏色
  • RectSides:邊框显示方向
  • 無:不显示邊框
  • 全部:显示全部邊框
  • 頂:显示頂部邊框
  • 底:显示底部邊框
  • 左:显示左側邊框
  • 右:显示右側邊框

注:邊框显示和圓角設置相關,如果一側的邊框兩端端點為圓角,則此邊框必定显示。

 

3、Radius圓角

我們提供了以下幾種圓角樣式,以供選擇。默認圓角大小為5px。

主要屬性如下:

Radius:圓角大小

RadiusSides:显示四個角圓角的显示與否

  • 圓角不显示

  • 默認圓角大小為5px

  • 圓角大小與控件高度相等時,显示大圓角

  • 可通過四個角圓角的設置,對控件組合显示

 

4、Font字體

默認字體為:微軟雅黑, 12pt

 

5、Style主題

SunnyUI包含 Element 風格主題 11 個,DotNetBar 主題 3 個,其他主題 2 個,包含主題管理組件 UIStyleManager,可自由切換主題。

  •  UIStyleManager

參考SunnyUI.Demo.exe,將UIStyleManager放置在主窗體上,通過選擇UIStyleManager的屬性Style,或者通過代碼設置統一主題風格。

UIStyleManager.Style = style;

 

  • Style主要屬性如下:

Style:設置主題風格

StyleCustomMode:是否為自定義主題,設置為False時使用UIStyleManager提供的統一主題風格,設置為Ture時可手動調整控件配色,不受UIStyleManager約束。

 

  • UIStyle.Blue

  • UIStyle.Green

  • UIStyle.Orange

  • UIStyle.Red

  • UIStyle.Gray

  • UIStyle.White

  • UIStyle.DarkBlue

  • UIStyle.Black

  • UIStyle.Office2010Blue

  • UIStyle.Office2010Silver

  • UIStyle.Office2010Black

  

原創文章,轉載請保留鏈接 Sunny’s blog

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

網頁設計最專業,超強功能平台可客製化

分類
發燒車訊

一行代碼引來的安全漏洞就讓我們丟失了整個服務器的控制權

之前在某廠的某次項目開發中,項目組同學設計和實現了一個“引以為傲”,額,有點擴張,不過自認為還說得過去的 feature,結果臨上線前被啪啪打臉,因為實現過程中因為一行代碼(沒有標題黨,真的是一行代碼)帶來的安全漏洞讓我們丟失了整個服務器控制權(測試環境)。多虧了上線之前有公司安全團隊的人會對代碼進行掃描,才讓這個漏洞被扼殺在搖籃里。

下面我們就一起來看看這個事故,啊,不對,是故事。

背景說明

我們的項目是一個面向全球用戶的 Web 項目,用 SpringBoot 開發。在項目開發過程中,離不開各種異常信息的處理,比如表單提交參數不符合預期,業務邏輯的處理時離不開各種異常信息(例如網絡抖動等)的處理。於是利用 SpringBoot 各種現成的組件支持,設計了一個統一的異常信息處理組件,統一管理各種業務流程中可能出現的錯誤碼和錯誤信息,通過國際化的資源配置文件進行統一輸出給用戶。

統一錯誤信息配置管理

我們的用戶遍布全球,為了給各個國家用戶比較好的體驗會進行不同的翻譯。具體而言,實現的效果如下,為了方便理解,以“找回登錄密碼”這樣一個業務場景來進行闡述說明。

假設找回密碼時,需要用戶輸入手機或者郵箱驗證碼,假設這個時候用戶輸入的驗證碼通過後台數據庫(可能是Redis)對比發現已經過期。在業務代碼中,只需要簡單的 throw new ErrorCodeException(ErrorCodes.AUTHCODE_EXPIRED) 即可。具體而言,針對不同國家地區不同的語言看到的效果不一樣:

  • 中文用戶看到的提示就是“您輸入的驗證碼已過期,請重新獲取”;
  • 歐美用戶看到的效果是“The verification code you input is expired, …”;
  • 德國用戶看到的是:“Der von Ihnen eingegebene Verifizierungscode ist abgelaufen, bitte wiederholen” 。(我瞎找的翻譯,不一定準)
  • ……

統一錯誤信息配置管理代碼實現

關鍵信息其實就在於一個 GlobalExceptionHandler,對所有Controller 入口進行 AOP 攔截,根據不同的錯誤信息,獲取相應資源文件配置的 key,並從語言資源文件中讀取不同國家的錯誤翻譯信息。

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BadRequestException.class)
    @ResponseBody
    public ResponseEntity handle(HttpServletRequest request, BadRequestException e){
        String i18message = getI18nMessage(e.getKey(), request);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Response.error(e.getCode(), i18message));
    }
    
    @ExceptionHandler(ErrorCodeException.class)
    @ResponseBody
    public ResponseEntity handle(HttpServletRequest request, ErrorCodeException e){
        String i18message = getI18nMessage(e.getKey(), request);
        return ResponseEntity.status(HttpStatus.OK).body(Response.error(e.getCode(), i18message));
    }
}

 

不同語言的資源文件示例

private String getI18nMessage(String key, HttpServletRequest request) {
   try {
       return messageSource.getMessage(key, null, LanguaggeUtils.currentLocale(request));
   } catch (Exception e) {
       // log
       return key;
   }
}

 

詳細代碼實現可以參考本人之前寫的這篇文章一文教你實現 SpringBoot 中的自定義 Validator 和錯誤信息國際化配置,上面有附完整的代碼實現。

基於註解的表單校驗(含自定義註解)

還有一種常見的業務場景就是後端接口需要對用戶提交的表單進行校驗。以“註冊用戶”這樣的場景舉例說明, 註冊用戶時,往往會提交昵稱,性別,郵箱等信息進行註冊,簡單起見,就以這 3 個屬性為例。

定義的表單如下:

public class UserRegForm {
 private String nickname;
 private String gender;
 private String email;
}

 

對於表單的約束,我們有:

  • 昵稱字段:“nickname” 必填,長度必須是 6 到 20 位;
  • 性別字段:“gender” 可選,如果填了,就必須是“Male/Female/Other/”中的一種。說啥,除了男女還有其他?對,是的。畢竟全球用戶嘛,你去看看非死不可,還有更多。
  • 郵箱: “email”,必填,必須滿足郵箱格式。

對於以上約束,我們只需要在對應的字段上添加如下註解即可。

public class UserRegForm {
 @Length(min = 6, max = 20, message = "validate.userRegForm.nickname")
 private String nickname;

 @Gender(message="validate.userRegForm.gender")
 private String gender;

 @NotNull
 @Email(message="validate.userRegForm.email")
 private String email;
}

 

然後在各個語言資源文件中配置好相應的錯誤信息提示即可。其中, @Gender 就是一個自定義的註解。

基於含自定義註解的表單校驗關鍵代碼

自定義註解的實現主要的其實就是一個自定義註解的定義以及一個校驗邏輯。 例如定義一個自定義註解 CustomParam

@Documented
@Constraint(validatedBy = CustomValidator.class)
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomParam {
    String message() default "name.tanglei.www.validator.CustomArray.defaultMessage";

    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default { };

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
    @interface List {
        CustomParam[] value();
    }
}

 

校驗邏輯的實現 CustomValidator

public class CustomValidator implements ConstraintValidator<CustomParam, String> {
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if (null == s || s.isEmpty()) {
            return true;
        }
        if (s.equals("tanglei")) {
            return true;
        } else {
            error(constraintValidatorContext, "Invalid params: " + s);
            return false;
        }
    }

    @Override
    public void initialize(CustomParam constraintAnnotation) {
    }

    private static void error(ConstraintValidatorContext context, String message) {
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
    }
}

 

上面例子只為了闡述說明問題,其中校驗邏輯沒有實際意義,這樣,如果輸入參數不滿足條件,就會明確提示用戶輸入的哪個參數不滿足條件。例如輸入參數 xx,則會直接提示:Invalid params: xx

這個跟第一部分的處理方式類似,因為現有的 validator 組件實現中,如果違反相應的約束也是一種拋異常的方式實現的,因此只需要在上述的 GlobalExceptionHandler中添加相應的異常信息即可,這裏就不詳述了。 這不是本文的重點,這裏就不詳細闡述了。 詳細代碼實現可以參考本人之前寫的這篇文章一文教你實現 SpringBoot 中的自定義 Validator 和錯誤信息國際化配置,上面有附完整的代碼實現。

場景重現

一切都顯得很完美,直到上線前代碼提交至安全團隊掃描,就被“啪啪打臉”,掃描報告反饋了一個嚴重的安全漏洞。而這個安全漏洞,屬於很高危的遠程代碼執行漏洞。

用前文提到的自定義 Validator,輸入的參數用: “1+1=${1+1}”,看看效果:

太 TM 神奇了,居然幫我運算出來了,返回 "message": "Invalid params: 1+1=2"

問題就出現在實現自定義註解進行校驗的這行代碼(如下圖所示):

其實,最開始的時候,這裏直接返回了“Invalid params”,當初為了更好的用戶體驗,要明確告訴用戶哪個參數沒有通過校驗,因此在輸出的提示上加上了用戶輸入的字段,也就是上面的"Invalid params: " + s,沒想到,這闖了大禍了(回過頭來想,感覺這裏沒必要這麼詳細啊,因為前端已經有相應的校驗了,正常情況下回攔住,針對不守規矩的用非常規手段來的接口請求,直接返回校驗不通過就行了,畢竟不是對外提供的 OpenAPI 服務)。

仔細看,這個方法實際上是 ConstraintValidatorContext這個接口中聲明的,看方法名字其實能知道輸入參數是一個字符串模板,內部會進行解析替換的(這其實也符合“見名知意”的良好編程習慣)。(教訓:大家應該把握好自己寫的每一行代碼背後實際在做什麼。)

/* ......
 * @param messageTemplate new un-interpolated constraint message
 * @return returns a constraint violation builder
 */
ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate);

 

這個 case,源碼調試進去之後,就能跟蹤到執行翻譯階段,在如下方法中: org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator.interpolateMessage

再往後,就是表達式求值了。

以為就這樣就完了嗎?

剛開始感覺,能幫忙算簡單的運算規則也就完了吧,你還能把我怎麼樣?其實這個相當於暴露了一個入口,支持用戶輸入任意 EL 表達式進行執行。網上通過關鍵字 “SpEL表達式注入漏洞” 找找,就能發現事情並沒有想象中那麼簡單。

我們構造恰當的 EL 表達式(注意各種轉義,下文的輸入參數相對比較明顯在做什麼了,實際上還有更多黑科技,比如各種二進制轉義編碼啊等等),就能直接執行輸入代碼,例如:可以直接執行命令,“ls -al”, 返回了一個 UNIXProcess 實例,命令已經被執行過了。

比如,我們執行個打開計算器的命令,搞個計算器玩玩~

我錄製了一個動圖,來個演示可能更生動一些。

這還得了嗎?這相當於提供了一個 webshell 的功能呀,你看想運行啥命令就能運行啥命令,例如 ping 本人博客地址(ping www.tanglei.name),下面動圖演示一下整個過程(從運行 ping 到 kill ping)。

我錄製了一個視頻,點擊這裏可以訪問。

豈不是直接創建一個用戶,然後遠程登錄就可以了。後果很嚴重啊,別人想幹嘛就幹嘛了。

我們跟蹤下對應的代碼,看看內部實現,就會“恍然大悟”了。

經驗教訓

幸虧這個漏洞被扼殺在搖籃里,否則後果還真的挺嚴重的。通過這個案例,我們有啥經驗和教訓呢?那就是作為程序員,我們要對每一行代碼都保持“敬畏”之心。也許就是因為你的不經意的一行代碼就帶來了嚴重的安全漏洞,要是不小心被壞人利用,輕則……重則……(自己想象吧)

此外,我們也應該看到,程序員需要對常見的安全漏洞(例如XSS/CSRF/SQL注入等等)有所了解,並且要有足夠的安全意識(其實有時候研究一些安全問題還挺好玩的,比如這篇《RSA算法及一種”旁門左道”的攻擊方式》就比較有趣)。例如:

  • 用戶權限分離:運行程序的用戶不應該用 root,例如新建一個“web”或者“www”之類的用戶,並設置該用戶的權限,比如不能有可執行 xx 的權限之類的。本文 case,如果權限進行了分離(遵循最小權限原則),應該也不會這麼嚴重。(本文就剛好是因為是測試環境,所以沒有強制實施)
  • 任何時候都不要相信用戶的輸入,必須對用戶輸入的進行校驗和過濾,又特別是針對公網上的應用。
  • 敏感信息加密保存。退一萬步講,假設攻擊者攻入了你的服務器,如果這個時候,你的數據庫賬戶信息等配置都直接明文保存在服務器中。那數據庫也被脫走了。

如果可能的話,需要對開發者的代碼進行漏洞掃描。一些常見的安全漏洞現在應該是有現成的工具支持的。另外,讓專業的人做專業的事情,例如要有安全團隊,可能你會說你們公司沒有不也活的好好的,哈哈,只不過可能還沒有被壞人盯上而已,壞人也會考慮到他們的成本和預期收益的,當然這就更加對我們開發者提高了要求。一些敏感權限盡量控制在少部分人手中,配合相應的流程來支撐(不得不說,大公司繁瑣的流程還是有一定道理的)。

畢竟我不是專業研究Web安全的,以上說得可能也不一定對,如果你有不同意見或者更好的建議歡迎留言參与討論。

這篇文章從寫代碼做實驗,到錄屏做視頻動圖等等耗時還蠻久的(好幾個周末的時間呢),原創真心不易,希望你能幫我個小忙唄,如果本文內容你覺得有所啟發,有所收穫,請幫忙點個“在看”唄,或者轉發分享讓更多的小夥伴看到。

精彩推薦
  • 一個由跨平台產生的浮點數bug | 有你意想不到的結果。
  • RSA算法及一種”旁門左道”的攻擊方式。
  • 震驚! 阿里的程序員也不過如此,竟被一個簡單的 SQL 查詢難住。
  • 面了7輪 Google,最終還是逃不脫被掛的命運。

文章首發於本人微信公眾號(ID:tangleithu),請感興趣的同學關注我的微信公眾號,及時獲取技術乾貨。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

分類
發燒車訊

亞馬遜6月毀林創紀錄 巴西太空署總督導下台

摘錄自2020年7月14日中央社報導

巴西政府免去國家太空署(INPE)地球觀測站總督導溫海斯(Lubia Vinhas)的職位,免職書由科技部長龐特斯(Marcos Pontes)簽署,刊登在今(13日)的國家公報。

溫海斯接受環球電視(TV Globo)訪問時說,她通過高考進入國家太空署服務23年了,所以就算被免去管理職位,也將繼續待在機關。溫海斯也表示不曉得為何被免職,說自己是通過國家公報才知道被調職。

上週巴西國家太空署公布報告,今年6月份的毀林警報數量創下2015年以來當月最高紀錄。今年上半年累計的警報顯示,亞馬遜遭破壞面積達3069.57平方公里,與2019年上半年相比增加25%。僅6月的毀林警報範圍就達1034.4平方公里。

溫海斯遭免職後,國際環保團體綠色和平組織(Greenpeace)發表聲明表示,基於巴西總統波索納洛(Jair Bolsonaro)政府先前的決定,溫海斯被免職並不意外,只是再次顯示「巴西政府是真理的敵人」。

巴西經濟部長葛德斯(Paulo Guedes)今天在經濟合作暨發展組織(OECD)線上會議中表示,巴西願意配合和幫助保護環境;如果巴西的環境政策出現錯誤或過度行為,也將予以糾正。

生物多樣性
國際新聞
巴西
亞馬遜雨林
森林

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

分類
發燒車訊

8000字長文讓你徹底了解 Java 8 的 Lambda、函數式接口、Stream 用法和原理

我是風箏,公眾號「古時的風箏」。一個兼具深度與廣度的程序員鼓勵師,一個本打算寫詩卻寫起了代碼的田園碼農!
文章會收錄在 JavaNewBee 中,更有 Java 後端知識圖譜,從小白到大牛要走的路都在裏面。公眾號回復『666』獲取高清大圖。

就在今年 Java 25周歲了,可能比在座的各位中的一些少年年齡還大,但令人遺憾的是,竟然沒有我大,不禁感嘆,Java 還是太小了。(難道我會說是因為我老了?)

而就在上個月,Java 15 的試驗版悄悄發布了,但是在 Java 界一直有個神秘現象,那就是「你發你發任你發,我的最愛 Java 8」.

據 Snyk 和 The Java Magazine 聯合推出發布的 2020 JVM 生態調查報告显示,在所有的 Java 版本中,仍然有 64% 的開發者使用 Java 8。另外一些開發者可能已經開始用 Java 9、Java 11、Java 13 了,當然還有一些神仙開發者還在堅持使用 JDK 1.6 和 1.7。

儘管 Java 8 發布多年,使用者眾多,可神奇的是竟然有很多同學沒有用過 Java 8 的新特性,比如 Lambda表達式、比如方法引用,再比如今天要說的 Stream。其實 Stream 就是以 Lambda 和方法引用為基礎,封裝的簡單易用、函數式風格的 API。

Java 8 是在 2014 年發布的,實話說,風箏我也是在 Java 8 發布后很長一段時間才用的 Stream,因為 Java 8 發布的時候我還在 C# 的世界中掙扎,而使用 Lambda 表達式卻很早了,因為 Python 中用 Lambda 很方便,沒錯,我寫 Python 的時間要比 Java 的時間還長。

要講 Stream ,那就不得不先說一下它的左膀右臂 Lambda 和方法引用,你用的 Stream API 其實就是函數式的編程風格,其中的「函數」就是方法引用,「式」就是 Lambda 表達式。

Lambda 表達式

Lambda 表達式是一個匿名函數,Lambda表達式基於數學中的λ演算得名,直接對應於其中的lambda抽象,是一個匿名函數,即沒有函數名的函數。Lambda表達式可以表示閉包。

在 Java 中,Lambda 表達式的格式是像下面這樣

// 無參數,無返回值
() -> log.info("Lambda")

 // 有參數,有返回值
(int a, int b) -> { a+b }

其等價於

log.info("Lambda");

private int plus(int a, int b){
  	return a+b;
}

最常見的一個例子就是新建線程,有時候為了省事,會用下面的方法創建並啟動一個線程,這是匿名內部類的寫法,new Thread需要一個 implements 自Runnable類型的對象實例作為參數,比較好的方式是創建一個新類,這個類 implements Runnable,然後 new 出這個新類的實例作為參數傳給 Thread。而匿名內部類不用找對象接收,直接當做參數。

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("快速新建並啟動一個線程");
    }
}).run();

但是這樣寫是不是感覺看上去很亂、很土,而這時候,換上 Lambda 表達式就是另外一種感覺了。

new Thread(()->{
    System.out.println("快速新建並啟動一個線程");
}).run();

怎麼樣,這樣一改,瞬間感覺清新脫俗了不少,簡潔優雅了不少。

Lambda 表達式簡化了匿名內部類的形式,可以達到同樣的效果,但是 Lambda 要優雅的多。雖然最終達到的目的是一樣的,但其實內部的實現原理卻不相同。

匿名內部類在編譯之後會創建一個新的匿名內部類出來,而 Lambda 是調用 JVM invokedynamic指令實現的,並不會產生新類。

方法引用

方法引用的出現,使得我們可以將一個方法賦給一個變量或者作為參數傳遞給另外一個方法。::雙冒號作為方法引用的符號,比如下面這兩行語句,引用 Integer類的 parseInt方法。

Function<String, Integer> s = Integer::parseInt;
Integer i = s.apply("10");

或者下面這兩行,引用 Integer類的 compare方法。

Comparator<Integer> comparator = Integer::compare;
int result = comparator.compare(100,10);

再比如,下面這兩行代碼,同樣是引用 Integer類的 compare方法,但是返回類型卻不一樣,但卻都能正常執行,並正確返回。

IntBinaryOperator intBinaryOperator = Integer::compare;
int result = intBinaryOperator.applyAsInt(10,100);

相信有的同學看到這裏恐怕是下面這個狀態,完全不可理喻嗎,也太隨便了吧,返回給誰都能接盤。

先別激動,來來來,現在咱們就來解惑,解除蒙圈臉。

Q:什麼樣的方法可以被引用?

A:這麼說吧,任何你有辦法訪問到的方法都可以被引用。

Q:返回值到底是什麼類型?

A:這就問到點兒上了,上面又是 Function、又是Comparator、又是 IntBinaryOperator的,看上去好像沒有規律,其實不然。

返回的類型是 Java 8 專門定義的函數式接口,這類接口用 @FunctionalInterface 註解。

比如 Function這個函數式接口的定義如下:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

還有很關鍵的一點,你的引用方法的參數個數、類型,返回值類型要和函數式接口中的方法聲明一一對應才行。

比如 Integer.parseInt方法定義如下:

public static int parseInt(String s) throws NumberFormatException {
    return parseInt(s,10);
}

首先parseInt方法的參數個數是 1 個,而 Function中的 apply方法參數個數也是 1 個,參數個數對應上了,再來,apply方法的參數類型和返回類型是泛型類型,所以肯定能和 parseInt方法對應上。

這樣一來,就可以正確的接收Integer::parseInt的方法引用,並可以調用Funcitonapply方法,這時候,調用到的其實就是對應的 Integer.parseInt方法了。

用這套標準套到 Integer::compare方法上,就不難理解為什麼即可以用 Comparator<Integer>接收,又可以用 IntBinaryOperator接收了,而且調用它們各自的方法都能正確的返回結果。

Integer.compare方法定義如下:

public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

返回值類型 int,兩個參數,並且參數類型都是 int

然後來看ComparatorIntBinaryOperator它們兩個的函數式接口定義和其中對應的方法:

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

@FunctionalInterface
public interface IntBinaryOperator {
    int applyAsInt(int left, int right);
}

對不對,都能正確的匹配上,所以前面示例中用這兩個函數式接口都能正常接收。其實不止這兩個,只要是在某個函數式接口中聲明了這樣的方法:兩個參數,參數類型是 int或者泛型,並且返回值是 int或者泛型的,都可以完美接收。

JDK 中定義了很多函數式接口,主要在 java.util.function包下,還有 java.util.Comparator 專門用作定製比較器。另外,前面說的 Runnable也是一個函數式接口。

自己動手實現一個例子

1. 定義一個函數式接口,並添加一個方法

定義了名稱為 KiteFunction 的函數式接口,使用 @FunctionalInterface註解,然後聲明了具有兩個參數的方法 run,都是泛型類型,返回結果也是泛型。

還有一點很重要,函數式接口中只能聲明一個可被實現的方法,你不能聲明了一個 run方法,又聲明一個 start方法,到時候編譯器就不知道用哪個接收了。而用default 關鍵字修飾的方法則沒有影響。

@FunctionalInterface
public interface KiteFunction<T, R, S> {

    /**
     * 定義一個雙參數的方法
     * @param t
     * @param s
     * @return
     */
    R run(T t,S s);
}

2. 定義一個與 KiteFunction 中 run 方法對應的方法

在 FunctionTest 類中定義了方法 DateFormat,一個將 LocalDateTime類型格式化為字符串類型的方法。

public class FunctionTest {
    public static String DateFormat(LocalDateTime dateTime, String partten) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(partten);
        return dateTime.format(dateTimeFormatter);
    }
}

3.用方法引用的方式調用

正常情況下我們直接使用 FunctionTest.DateFormat()就可以了。

而用函數式方式,是這樣的。

KiteFunction<LocalDateTime,String,String> functionDateFormat = FunctionTest::DateFormat;
String dateString = functionDateFormat.run(LocalDateTime.now(),"yyyy-MM-dd HH:mm:ss");

而其實我可以不專門在外面定義 DateFormat這個方法,而是像下面這樣,使用匿名內部類。

public static void main(String[] args) throws Exception {
  
    String dateString = new KiteFunction<LocalDateTime, String, String>() {
        @Override
        public String run(LocalDateTime localDateTime, String s) {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(s);
            return localDateTime.format(dateTimeFormatter);
        }
    }.run(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss");
    System.out.println(dateString);
}

前面第一個 Runnable的例子也提到了,這樣的匿名內部類可以用 Lambda 表達式的形式簡寫,簡寫后的代碼如下:

public static void main(String[] args) throws Exception {

        KiteFunction<LocalDateTime, String, String> functionDateFormat = (LocalDateTime dateTime, String partten) -> {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(partten);
            return dateTime.format(dateTimeFormatter);
        };
        String dateString = functionDateFormat.run(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss");
        System.out.println(dateString);
}

使用(LocalDateTime dateTime, String partten) -> { } 這樣的 Lambda 表達式直接返回方法引用。

Stream API

為了說一下 Stream API 的使用,可以說是大費周章啊,知其然,也要知其所以然嗎,追求技術的態度和姿勢要正確。

當然 Stream 也不只是 Lambda 表達式就厲害了,真正厲害的還是它的功能,Stream 是 Java 8 中集合數據處理的利器,很多本來複雜、需要寫很多代碼的方法,比如過濾、分組等操作,往往使用 Stream 就可以在一行代碼搞定,當然也因為 Stream 都是鏈式操作,一行代碼可能會調用好幾個方法。

Collection接口提供了 stream()方法,讓我們可以在一個集合方便的使用 Stream API 來進行各種操作。值得注意的是,我們執行的任何操作都不會對源集合造成影響,你可以同時在一個集合上提取出多個 stream 進行操作。

我們看 Stream 接口的定義,繼承自 BaseStream,機會所有的接口聲明都是接收方法引用類型的參數,比如 filter方法,接收了一個 Predicate類型的參數,它就是一個函數式接口,常用來作為條件比較、篩選、過濾用,JPA中也使用了這個函數式接口用來做查詢條件拼接。

public interface Stream<T> extends BaseStream<T, Stream<T>> {
  
  Stream<T> filter(Predicate<? super T> predicate);
  
  // 其他接口
}  

下面就來看看 Stream 常用 API。

of

可接收一個泛型對象或可變成泛型集合,構造一個 Stream 對象。

private static void createStream(){
    Stream<String> stringStream = Stream.of("a","b","c");
}

empty

創建一個空的 Stream 對象。

concat

連接兩個 Stream ,不改變其中任何一個 Steam 對象,返回一個新的 Stream 對象。

private static void concatStream(){
    Stream<String> a = Stream.of("a","b","c");
    Stream<String> b = Stream.of("d","e");
    Stream<String> c = Stream.concat(a,b);
}

max

一般用於求数字集合中的最大值,或者按實體中数字類型的屬性比較,擁有最大值的那個實體。它接收一個 Comparator<T>,上面也舉到這個例子了,它是一個函數式接口類型,專門用作定義兩個對象之間的比較,例如下面這個方法使用了 Integer::compareTo這個方法引用。

private static void max(){
    Stream<Integer> integerStream = Stream.of(2, 2, 100, 5);
    Integer max = integerStream.max(Integer::compareTo).get();
    System.out.println(max);
}

當然,我們也可以自己定製一個 Comparator,順便複習一下 Lambda 表達式形式的方法引用。

private static void max(){
    Stream<Integer> integerStream = Stream.of(2, 2, 100, 5);
    Comparator<Integer> comparator =  (x, y) -> (x.intValue() < y.intValue()) ? -1 : ((x.equals(y)) ? 0 : 1);
    Integer max = integerStream.max(comparator).get();
    System.out.println(max);
}

min

與 max 用法一樣,只不過是求最小值。

findFirst

獲取 Stream 中的第一個元素。

findAny

獲取 Stream 中的某個元素,如果是串行情況下,一般都會返回第一個元素,并行情況下就不一定了。

count

返回元素個數。

Stream<String> a = Stream.of("a", "b", "c");
long x = a.count();

peek

建立一個通道,在這個通道中對 Stream 的每個元素執行對應的操作,對應 Consumer<T>的函數式接口,這是一個消費者函數式接口,顧名思義,它是用來消費 Stream 元素的,比如下面這個方法,把每個元素轉換成對應的大寫字母並輸出。

private static void peek() {
    Stream<String> a = Stream.of("a", "b", "c");
    List<String> list = a.peek(e->System.out.println(e.toUpperCase())).collect(Collectors.toList());
}

forEach

和 peek 方法類似,都接收一個消費者函數式接口,可以對每個元素進行對應的操作,但是和 peek 不同的是,forEach 執行之後,這個 Stream 就真的被消費掉了,之後這個 Stream 流就沒有了,不可以再對它進行後續操作了,而 peek操作完之後,還是一個可操作的 Stream 對象。

正好藉著這個說一下,我們在使用 Stream API 的時候,都是一串鏈式操作,這是因為很多方法,比如接下來要說到的 filter方法等,返回值還是這個 Stream 類型的,也就是被當前方法處理過的 Stream 對象,所以 Stream API 仍然可以使用。

private static void forEach() {
    Stream<String> a = Stream.of("a", "b", "c");
    a.forEach(e->System.out.println(e.toUpperCase()));
}

forEachOrdered

功能與 forEach是一樣的,不同的是,forEachOrdered是有順序保證的,也就是對 Stream 中元素按插入時的順序進行消費。為什麼這麼說呢,當開啟并行的時候,forEachforEachOrdered的效果就不一樣了。

Stream<String> a = Stream.of("a", "b", "c");
a.parallel().forEach(e->System.out.println(e.toUpperCase()));

當使用上面的代碼時,輸出的結果可能是 B、A、C 或者 A、C、B或者A、B、C,而使用下面的代碼,則每次都是 A、 B、C

Stream<String> a = Stream.of("a", "b", "c");
a.parallel().forEachOrdered(e->System.out.println(e.toUpperCase()));

limit

獲取前 n 條數據,類似於 MySQL 的limit,只不過只能接收一個參數,就是數據條數。

private static void limit() {
    Stream<String> a = Stream.of("a", "b", "c");
    a.limit(2).forEach(e->System.out.println(e));
}

上述代碼打印的結果是 a、b。

skip

跳過前 n 條數據,例如下面代碼,返回結果是 c。

private static void skip() {
    Stream<String> a = Stream.of("a", "b", "c");
    a.skip(2).forEach(e->System.out.println(e));
}

distinct

元素去重,例如下面方法返回元素是 a、b、c,將重複的 b 只保留了一個。

private static void distinct() {
    Stream<String> a = Stream.of("a", "b", "c","b");
    a.distinct().forEach(e->System.out.println(e));
}

sorted

有兩個重載,一個無參數,另外一個有個 Comparator類型的參數。

無參類型的按照自然順序進行排序,只適合比較單純的元素,比如数字、字母等。

private static void sorted() {
    Stream<String> a = Stream.of("a", "c", "b");
    a.sorted().forEach(e->System.out.println(e));
}

有參數的需要自定義排序規則,例如下面這個方法,按照第二個字母的大小順序排序,最後輸出的結果是 a1、b3、c6。

private static void sortedWithComparator() {
    Stream<String> a = Stream.of("a1", "c6", "b3");
    a.sorted((x,y)->Integer.parseInt(x.substring(1))>Integer.parseInt(y.substring(1))?1:-1).forEach(e->System.out.println(e));
}

為了更好的說明接下來的幾個 API ,我模擬了幾條項目中經常用到的類似數據,10條用戶信息。

private static List<User> getUserData() {
    Random random = new Random();
    List<User> users = new ArrayList<>();
    for (int i = 1; i <= 10; i++) {
        User user = new User();
        user.setUserId(i);
        user.setUserName(String.format("古時的風箏 %s 號", i));
        user.setAge(random.nextInt(100));
        user.setGender(i % 2);
        user.setPhone("18812021111");
        user.setAddress("無");
        users.add(user);
    }
    return users;
}

filter

用於條件篩選過濾,篩選出符合條件的數據。例如下面這個方法,篩選出性別為 0,年齡大於 50 的記錄。

private static void filter(){
    List<User> users = getUserData();
    Stream<User> stream = users.stream();
    stream.filter(user -> user.getGender().equals(0) && user.getAge()>50).forEach(e->System.out.println(e));

    /**
     *等同於下面這種形式 匿名內部類
     */
//    stream.filter(new Predicate<User>() {
//        @Override
//        public boolean test(User user) {
//            return user.getGender().equals(0) && user.getAge()>50;
//        }
//    }).forEach(e->System.out.println(e));
}

map

map方法的接口方法聲明如下,接受一個 Function函數式接口,把它翻譯成映射最合適了,通過原始數據元素,映射出新的類型。

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

Function的聲明是這樣的,觀察 apply方法,接受一個 T 型參數,返回一個 R 型參數。用於將一個類型轉換成另外一個類型正合適,這也是 map的初衷所在,用於改變當前元素的類型,例如將 Integer 轉為 String類型,將 DAO 實體類型,轉換為 DTO 實例類型。

當然了,T 和 R 的類型也可以一樣,這樣的話,就和 peek方法沒什麼不同了。

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

例如下面這個方法,應該是業務系統的常用需求,將 User 轉換為 API 輸出的數據格式。

private static void map(){
    List<User> users = getUserData();
    Stream<User> stream = users.stream();
    List<UserDto> userDtos = stream.map(user -> dao2Dto(user)).collect(Collectors.toList());
}

private static UserDto dao2Dto(User user){
    UserDto dto = new UserDto();
    BeanUtils.copyProperties(user, dto);
    //其他額外處理
    return dto;
}

mapToInt

將元素轉換成 int 類型,在 map方法的基礎上進行封裝。

mapToLong

將元素轉換成 Long 類型,在 map方法的基礎上進行封裝。

mapToDouble

將元素轉換成 Double 類型,在 map方法的基礎上進行封裝。

flatMap

這是用在一些比較特別的場景下,當你的 Stream 是以下這幾種結構的時候,需要用到 flatMap方法,用於將原有二維結構扁平化。

  1. Stream<String[]>
  2. Stream<Set<String>>
  3. Stream<List<String>>

以上這三類結構,通過 flatMap方法,可以將結果轉化為 Stream<String>這種形式,方便之後的其他操作。

比如下面這個方法,將List<List<User>>扁平處理,然後再使用 map或其他方法進行操作。

private static void flatMap(){
    List<User> users = getUserData();
    List<User> users1 = getUserData();
    List<List<User>> userList = new ArrayList<>();
    userList.add(users);
    userList.add(users1);
    Stream<List<User>> stream = userList.stream();
    List<UserDto> userDtos = stream.flatMap(subUserList->subUserList.stream()).map(user -> dao2Dto(user)).collect(Collectors.toList());
}

flatMapToInt

用法參考 flatMap,將元素扁平為 int 類型,在 flatMap方法的基礎上進行封裝。

flatMapToLong

用法參考 flatMap,將元素扁平為 Long 類型,在 flatMap方法的基礎上進行封裝。

flatMapToDouble

用法參考 flatMap,將元素扁平為 Double 類型,在 flatMap方法的基礎上進行封裝。

collection

在進行了一系列操作之後,我們最終的結果大多數時候並不是為了獲取 Stream 類型的數據,而是要把結果變為 List、Map 這樣的常用數據結構,而 collection就是為了實現這個目的。

就拿 map 方法的那個例子說明,將對象類型進行轉換后,最終我們需要的結果集是一個 List<UserDto >類型的,使用 collect方法將 Stream 轉換為我們需要的類型。

下面是 collect接口方法的定義:

<R, A> R collect(Collector<? super T, A, R> collector);

下面這個例子演示了將一個簡單的 Integer Stream 過濾出大於 7 的值,然後轉換成 List<Integer>集合,用的是 Collectors.toList()這個收集器。

private static void collect(){
    Stream<Integer> integerStream = Stream.of(1,2,5,7,8,12,33);
    List<Integer> list = integerStream.filter(s -> s.intValue()>7).collect(Collectors.toList());
}

很多同學表示看不太懂這個 Collector是怎麼一個意思,來,我們看下面這段代碼,這是 collect的另一個重載方法,你可以理解為它的參數是按順序執行的,這樣就清楚了,這就是個 ArrayList 從創建到調用 addAll方法的一個過程。

private static void collect(){
    Stream<Integer> integerStream = Stream.of(1,2,5,7,8,12,33);
    List<Integer> list = integerStream.filter(s -> s.intValue()>7).collect(ArrayList::new, ArrayList::add,
            ArrayList::addAll);
}

我們在自定義 Collector的時候其實也是這個邏輯,不過我們根本不用自定義, Collectors已經為我們提供了很多拿來即用的收集器。比如我們經常用到Collectors.toList()Collectors.toSet()Collectors.toMap()。另外還有比如Collectors.groupingBy()用來分組,比如下面這個例子,按照 userId 字段分組,返回以 userId 為key,List 為value 的 Map,或者返回每個 key 的個數。

// 返回 userId:List<User>
Map<String,List<User>> map = user.stream().collect(Collectors.groupingBy(User::getUserId));

// 返回 userId:每組個數
Map<String,Long> map = user.stream().collect(Collectors.groupingBy(User::getUserId,Collectors.counting()));

toArray

collection是返回列表、map 等,toArray是返回數組,有兩個重載,一個空參數,返回的是 Object[]

另一個接收一個 IntFunction<R>類型參數。

@FunctionalInterface
public interface IntFunction<R> {

    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    R apply(int value);
}

比如像下面這樣使用,參數是 User[]::new也就是new 一個 User 數組,長度為最後的 Stream 長度。

private static void toArray() {
    List<User> users = getUserData();
    Stream<User> stream = users.stream();
    User[] userArray = stream.filter(user -> user.getGender().equals(0) && user.getAge() > 50).toArray(User[]::new);
}

reduce

它的作用是每次計算的時候都用到上一次的計算結果,比如求和操作,前兩個數的和加上第三個數的和,再加上第四個數,一直加到最後一個數位置,最後返回結果,就是 reduce的工作過程。

private static void reduce(){
    Stream<Integer> integerStream = Stream.of(1,2,5,7,8,12,33);
    Integer sum = integerStream.reduce(0,(x,y)->x+y);
    System.out.println(sum);
}

另外 Collectors好多方法都用到了 reduce,比如 groupingByminBymaxBy等等。

并行 Stream

Stream 本質上來說就是用來做數據處理的,為了加快處理速度,Stream API 提供了并行處理 Stream 的方式。通過 users.parallelStream()或者users.stream().parallel() 的方式來創建并行 Stream 對象,支持的 API 和普通 Stream 幾乎是一致的。

并行 Stream 默認使用 ForkJoinPool線程池,當然也支持自定義,不過一般情況下沒有必要。ForkJoin 框架的分治策略與并行流處理正好契合。

雖然并行這個詞聽上去很厲害,但並不是所有情況使用并行流都是正確的,很多時候完全沒這個必要。

什麼情況下使用或不應使用并行流操作呢?

  1. 必須在多核 CPU 下才使用并行 Stream,聽上去好像是廢話。
  2. 在數據量不大的情況下使用普通串行 Stream 就可以了,使用并行 Stream 對性能影響不大。
  3. CPU 密集型計算適合使用并行 Stream,而 IO 密集型使用并行 Stream 反而會更慢。
  4. 雖然計算是并行的可能很快,但最後大多數時候還是要使用 collect合併的,如果合併代價很大,也不適合用并行 Stream。
  5. 有些操作,比如 limit、 findFirst、forEachOrdered 等依賴於元素順序的操作,都不適合用并行 Stream。

最後

Java 25 周歲了,有多少同學跟我一樣在用 Java 8,還有多少同學再用更早的版本,請說出你的故事。

壯士且慢,先給點個贊吧,總是被白嫖,身體吃不消!

我是風箏,公眾號「古時的風箏」。一個兼具深度與廣度的程序員鼓勵師,一個本打算寫詩卻寫起了代碼的田園碼農!你可選擇現在就關注我,或者看看歷史文章再關注也不遲。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

分類
發燒車訊

大話計算機網絡一 聊聊UDP

引言

UDP是一個簡單的面向數據報運輸層協議

UDP不提供可靠性,它把應用程序傳給IP層得數據發送出去,不保證它們能達到目的地

UDP首部

端口號表示發送進程和接受進程

UDP長度字段指的是UDP首部和UDP數據的字節長度,該字段最小值為8字節

UDP長度是全長減去IP首部的長度

UDP檢驗和是一個端到端的檢驗和。它由發送端計算,然後由接收端驗證。其目的是為了發現UDP首部和數據在發送端到接收端之間發生的任何改動。

 

最大UDP數據報長度

理論上,IP數據報的最大長度是65535字節,這是由IP首部(圖3-1)16比特總長度字段所限制的。去除20字節的IP首部和8個字節的UDP首部,UDP數據報中用戶數據的最長長度為65507字節。但是,大多數實現所提供的長度比這個最大值小。

 

UDP校驗和

 

UDP和TCP在首部中都有覆蓋它們首部和數據的檢驗和。UDP的檢驗和是可選的,而TCP的檢驗和是必需的。

儘管UDP檢驗和的基本計算方法與我們在3.2節中描述的IP首部檢驗和計算方法相類似(16 bit字的二進制反碼和),但是它們之間存在不同的地方。首先,UDP數據報的長度可以為奇数字節,但是檢驗和算法是把若干個16 bit字相加。解決方法是必要時在最後增加填充字節0,這隻是為了檢驗和的計算(也就是說,可能增加的填充字節不被傳送)。

其次,UDP數據報和TCP段都包含一個12字節長的偽首部,它是為了計算檢驗和而設置的。偽首部包含IP首部一些字段。其目的是讓UDP兩次檢查數據是否已經正確到達目的地(例如,IP沒有接受地址不是本主機的數據報,以及IP沒有把應傳給另一高層的數據報傳給UDP)。UDP數據報中的偽首部格式如圖11-3所示。

 

在該圖中,我們特地舉了一個奇數長度的數據報例子,因而在計算檢驗和時需要加上填充字節。注意,UDP數據報的長度在檢驗和計算過程中出現兩次。

如果檢驗和的計算結果為0,則存入的值為全1(65535),這在二進制反碼計算中是等效的。如果傳送的檢驗和為0,說明發送端沒有計算檢驗和。

如果發送端沒有計算檢驗和而接收端檢測到檢驗和有差錯,那麼UDP數據報就要被悄悄地丟棄。不產生任何差錯報文(當IP層檢測到IP首部檢驗和有差錯時也這樣做)。

UDP檢驗和是一個端到端的檢驗和。它由發送端計算,然後由接收端驗證。其目的是為了發現UDP首部和數據在發送端到接收端之間發生的任何改動。

 

 

這個系列主要是對自己讀TCP/IP詳解 卷一 協議的筆記,推薦看完以後去閱讀一下這本又臭又厚的書

电子書的鏈接地址http://www.52im.net/topic-tcpipvol1.html

感謝這位站長的開源 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

分類
發燒車訊

重學 Java 設計模式:實戰裝飾器模式(SSO單點登錄功能擴展,增加攔截用戶訪問方法範圍場景)

作者:小傅哥
博客:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

對於代碼你有編程感覺嗎

很多人寫代碼往往是沒有編程感覺的,也就是除了可以把功能按照固定的流程編寫出流水式的代碼外,很難去思考整套功能服務的擴展性和可維護性。尤其是在一些較大型的功能搭建上,比較缺失一些駕馭能力,從而導致最終的代碼相對來說不能做到盡善盡美。

江洋大盜與江洋大偷

兩個本想描述一樣的意思的詞,只因一字只差就讓人覺得一個是好牛,一個好搞笑。往往我們去開發編程寫代碼時也經常將一些不恰當的用法用於業務需求實現中,當卻不能意識到。一方面是由於編碼不多缺少較大型項目的實踐,另一方面是不思進取的總在以完成需求為目標缺少精益求精的工匠精神。

書從來不是看的而是用的

在這個學習資料幾乎爆炸的時代,甚至你可以輕易就獲取幾個T的視頻,小手輕輕一點就收藏一堆文章,但卻很少去看。學習的過程從不只是簡單的看一遍就可以,對於一些實操性的技術書籍,如果真的希望學習到知識,那麼一定是把這本書用起來而絕對不是看起來。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述
itstack-demo-design-9-00 場景模擬工程;模擬單點登錄類
itstack-demo-design-9-01 使用一坨代碼實現業務需求
itstack-demo-design-9-02 通過設計模式優化改造代碼,產生對比性從而學習

三、裝飾器模式介紹

初看上圖感覺裝飾器模式有點像俄羅斯套娃、某眾汽車,而裝飾器的核心就是再不改原有類的基礎上給類新增功能。不改變原有類,可能有的小夥伴會想到繼承、AOP切面,當然這些方式都可以實現,但是使用裝飾器模式會是另外一種思路更為靈活,可以避免繼承導致的子類過多,也可以避免AOP帶來的複雜性。

你熟悉的場景很多用到裝飾器模式

new BufferedReader(new FileReader(""));,這段代碼你是否熟悉,相信學習java開發到字節流、字符流、文件流的內容時都見到了這樣的代碼,一層嵌套一層,一層嵌套一層,字節流轉字符流等等,而這樣方式的使用就是裝飾器模式的一種體現。

四、案例場景模擬

在本案例中我們模擬一個單點登錄功能擴充的場景

一般在業務開發的初期,往往內部的ERP使用只需要判斷賬戶驗證即可,驗證通過後即可訪問ERP的所有資源。但隨着業務的不斷髮展,團隊里開始出現專門的運營人員、營銷人員、數據人員,每個人員對於ERP的使用需求不同,有些需要創建活動,有些只是查看數據。同時為了保證數據的安全性,不會讓每個用戶都有最高的權限。

那麼以往使用的SSO是一個組件化通用的服務,不能在裏面添加需要的用戶訪問驗證功能。這個時候我們就可以使用裝飾器模式,擴充原有的單點登錄服務。但同時也保證原有功能不受破壞,可以繼續使用。

1. 場景模擬工程

itstack-demo-design-9-00
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── HandlerInterceptor.java
                └── SsoInterceptor.java
  • 這裏模擬的是spring中的類:HandlerInterceptor,實現起接口功能SsoInterceptor模擬的單點登錄攔截服務。
  • 為了避免引入太多spring的內容影響對設計模式的閱讀,這裏使用了同名的類和方法,盡可能減少外部的依賴。

2. 場景簡述

2.1 模擬Spring的HandlerInterceptor

public interface HandlerInterceptor {

    boolean preHandle(String request, String response, Object handler);

}
  • 實際的單點登錄開發會基於;org.springframework.web.servlet.HandlerInterceptor 實現。

2.2 模擬單點登錄功能

public class SsoInterceptor implements HandlerInterceptor{

    public boolean preHandle(String request, String response, Object handler) {
        // 模擬獲取cookie
        String ticket = request.substring(1, 8);
        // 模擬校驗
        return ticket.equals("success");
    }

}
  • 這裏的模擬實現非常簡單隻是截取字符串,實際使用需要從HttpServletRequest request對象中獲取cookie信息,解析ticket值做校驗。
  • 在返回的裏面也非常簡單,只要獲取到了success就認為是允許登錄。

五、用一坨坨代碼實現

此場景大多數實現的方式都會採用繼承類

繼承類的實現方式也是一個比較通用的方式,通過繼承后重寫方法,併發將自己的邏輯覆蓋進去。如果是一些簡單的場景且不需要不斷維護和擴展的,此類實現並不會有什麼,也不會導致子類過多。

1. 工程結構

itstack-demo-design-9-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── LoginSsoDecorator.java
  • 以上工程結構非常簡單,只是通過 LoginSsoDecorator 繼承 SsoInterceptor,重寫方法功能。

2. 代碼實現

public class LoginSsoDecorator extends SsoInterceptor {

    private static Map<String, String> authMap = new ConcurrentHashMap<String, String>();

    static {
        authMap.put("huahua", "queryUserInfo");
        authMap.put("doudou", "queryUserInfo");
    }

    @Override
    public boolean preHandle(String request, String response, Object handler) {
        // 模擬獲取cookie
        String ticket = request.substring(1, 8);
        // 模擬校驗
        boolean success = ticket.equals("success");

        if (!success) return false;

        String userId = request.substring(9);
        String method = authMap.get(userId);

        // 模擬方法校驗
        return "queryUserInfo".equals(method);
    }

}
  • 以上這部分通過繼承重寫方法,將個人可訪問哪些方法的功能添加到方法中。
  • 以上看着代碼還算比較清晰,但如果是比較複雜的業務流程代碼,就會很混亂。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_LoginSsoDecorator() {
    LoginSsoDecorator ssoDecorator = new LoginSsoDecorator();
    String request = "1successhuahua";
    boolean success = ssoDecorator.preHandle(request, "ewcdqwt40liuiu", "t");
    System.out.println("登錄校驗:" + request + (success ? " 放行" : " 攔截"));
}
  • 這裏模擬的相當於登錄過程中的校驗操作,判斷用戶是否可登錄以及是否可訪問方法。

3.2 測試結果

登錄校驗:1successhuahua 攔截

Process finished with exit code 0
  • 從測試結果來看滿足我們的預期,已經做了攔截。如果你在學習的過程中,可以嘗試模擬單點登錄並繼承擴展功能。

六、裝飾器模式重構代碼

接下來使用裝飾器模式來進行代碼優化,也算是一次很小的重構。

裝飾器主要解決的是直接繼承下因功能的不斷橫向擴展導致子類膨脹的問題,而是用裝飾器模式后就會比直接繼承顯得更加靈活同時這樣也就不再需要考慮子類的維護。

在裝飾器模式中有四個比較重要點抽象出來的點;

  1. 抽象構件角色(Component) – 定義抽象接口
  2. 具體構件角色(ConcreteComponent) – 實現抽象接口,可以是一組
  3. 裝飾角色(Decorator) – 定義抽象類並繼承接口中的方法,保證一致性
  4. 具體裝飾角色(ConcreteDecorator) – 擴展裝飾具體的實現邏輯

通過以上這四項來實現裝飾器模式,主要核心內容會體現在抽象類的定義和實現上。

1. 工程結構

itstack-demo-design-9-02
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── LoginSsoDecorator.java
                └── SsoDecorator.java

裝飾器模式模型結構

  • 以上是一個裝飾器實現的類圖結構,重點的類是SsoDecorator,這個類是一個抽象類主要完成了對接口HandlerInterceptor繼承。
  • 當裝飾角色繼承接口後會提供構造函數,入參就是繼承的接口實現類即可,這樣就可以很方便的擴展出不同功能組件。

2. 代碼實現

2.1 抽象類裝飾角色

public abstract class SsoDecorator implements HandlerInterceptor {

    private HandlerInterceptor handlerInterceptor;

    private SsoDecorator(){}

    public SsoDecorator(HandlerInterceptor handlerInterceptor) {
        this.handlerInterceptor = handlerInterceptor;
    }

    public boolean preHandle(String request, String response, Object handler) {
        return handlerInterceptor.preHandle(request, response, handler);
    }

}
  • 在裝飾類中有兩個重點的地方是;1)繼承了處理接口、2)提供了構造函數、3)覆蓋了方法preHandle
  • 以上三個點是裝飾器模式的核心處理部分,這樣可以踢掉對子類繼承的方式實現邏輯功能擴展。

2.2 裝飾角色邏輯實現

public class LoginSsoDecorator extends SsoDecorator {

    private Logger logger = LoggerFactory.getLogger(LoginSsoDecorator.class);

    private static Map<String, String> authMap = new ConcurrentHashMap<String, String>();

    static {
        authMap.put("huahua", "queryUserInfo");
        authMap.put("doudou", "queryUserInfo");
    }

    public LoginSsoDecorator(HandlerInterceptor handlerInterceptor) {
        super(handlerInterceptor);
    }

    @Override
    public boolean preHandle(String request, String response, Object handler) {
        boolean success = super.preHandle(request, response, handler);
        if (!success) return false;
        String userId = request.substring(8);
        String method = authMap.get(userId);
        logger.info("模擬單點登錄方法訪問攔截校驗:{} {}", userId, method);
        // 模擬方法校驗
        return "queryUserInfo".equals(method);
    }
}
  • 在具體的裝飾類實現中,繼承了裝飾類SsoDecorator,那麼現在就可以擴展方法;preHandle
  • preHandle的實現中可以看到,這裏只關心擴展部分的功能,同時不會影響原有類的核心服務,也不會因為使用繼承方式而導致的多餘子類,增加了整體的靈活性。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_LoginSsoDecorator() {
    LoginSsoDecorator ssoDecorator = new LoginSsoDecorator(new SsoInterceptor());
    String request = "1successhuahua";
    boolean success = ssoDecorator.preHandle(request, "ewcdqwt40liuiu", "t");
    System.out.println("登錄校驗:" + request + (success ? " 放行" : " 攔截"));
}
  • 這裏測試了對裝飾器模式的使用,通過透傳原有單點登錄類new SsoInterceptor(),傳遞給裝飾器,讓裝飾器可以執行擴充的功能。
  • 同時對於傳遞者和裝飾器都可以是多組的,在一些實際的業務開發中,往往也是由於太多類型的子類實現而導致不易於維護,從而使用裝飾器模式替代。

3.2 測試結果

23:50:50.796 [main] INFO  o.i.demo.design.LoginSsoDecorator - 模擬單點登錄方法訪問攔截校驗:huahua queryUserInfo
登錄校驗:1successhuahua 放行

Process finished with exit code 0
  • 結果符合預期,擴展了對方法攔截的校驗性。
  • 如果你在學習的過程中有用到過單點登陸,那麼可以適當在裏面進行擴展裝飾器模式進行學習使用。
  • 另外,還有一種場景也可以使用裝飾器。例如;你之前使用某個實現某個接口接收單個消息,但由於外部的升級變為發送list集合消息,但你又不希望所有的代碼類都去修改這部分邏輯。那麼可以使用裝飾器模式進行適配list集合,給使用者依然是for循環后的單個消息。

七、總結

  • 使用裝飾器模式滿足單一職責原則,你可以在自己的裝飾類中完成功能邏輯的擴展,而不影響主類,同時可以按需在運行時添加和刪除這部分邏輯。另外裝飾器模式與繼承父類重寫方法,在某些時候需要按需選擇,並不一定某一個就是最好。
  • 裝飾器實現的重點是對抽象類繼承接口方式的使用,同時設定被繼承的接口可以通過構造函數傳遞其實現類,由此增加擴展性並重寫方法里可以實現此部分父類實現的功能。
  • 就像夏天熱你穿短褲,冬天冷你穿棉褲,雨天挨澆你穿雨衣一樣,你的根本本身沒有被改變,而你的需求卻被不同的裝飾而實現。生活中往往比比皆是設計,當你可以融合這部分活靈活現的例子到代碼實現中,往往會創造出更加優雅的實現方式。

八、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式(多種類型商品發獎場景)
  • 2. 重學 Java 設計模式:實戰抽象工廠模式(替換Redis雙集群升級場景)
  • 3. 重學 Java 設計模式:實戰建造者模式(裝修物料組合套餐選配場景)
  • 4. 重學 Java 設計模式:實戰原型模式(多套試每人題目和答案亂序場景)
  • 5. 重學 Java 設計模式:實戰橋接模式(多支付渠道「微信、支付寶」與多支付模式「刷臉、指紋」場景)
  • 6. 重學 Java 設計模式:實戰組合模式(營銷差異化人群發券,決策樹引擎搭建場景)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

分類
發燒車訊

給女朋友講解什麼是Git

前言

在周六發現了Linus去Google演講的一個視頻,當時還發了一條朋友圈:

有興趣的同學也可以去看看,一點兒也不無聊,在線看Linus大佬懟人

https://www.bilibili.com/video/BV1xb411A7ac?from=search&seid=4239535088233137638

朋友圈的評論有幾個人問我女朋友

我又恰好給我女朋友科普過什麼是Git,所以這篇文章就有了。

Git介紹

Git是幹啥用的?它是一個版本控制軟件。

Git這個玩意三歪曾經還給女朋友給科普過(三歪會經常給女朋友說點技術的東西,我也不知道她到底聽懂了沒有,反正她每次都說好像有點懂了)。

當時情況是這樣的,某一天她跟三歪說:我做的Excel還沒保存,電腦藍屏死機了,東西全丟了。

於是三歪說:這…我也沒怎麼用過Excel這類的軟件,要不看看你用的WPS或者Office?有沒有相關的備份功能或者說是自動保存?

三歪順手找了一下WPS是有備份(自動保存)功能的,時間什麼的要自己定義一下。

三歪還補了一句:這種情況要是在程序員的手上感覺發生的概率會低一點,程序員習慣會按ctrl+s。甚至有的時候,看着看着網頁還會按ctrl+s。不過像我們寫代碼的工具(IDEA)都不用自己手動保存了….

過了一會,她說藍屏之前做的東西找不回來了,沒設置自動保存。

三歪又感嘆一句:我們寫代碼還有版本控制的軟件,在這個過程中會記錄每次修改的內容,誰改了什麼東西。誰改錯了,誰要背鍋,一個都不能跑

女朋友聽着三歪一頓亂吹,貌似也有點感興趣:“版本控制是什麼東西?這麼厲害的嗎?會計就經常要背鍋”

三歪:“其實也沒啥,就是我們一般寫代碼往往都是多人協作的。你們會計可能是每個人負責一張表(Excel),然後把已完成好的表傳給下一個人。而寫代碼的不一樣,我們都是在同一個項目裡邊編寫的,不會單獨等着某個人做完了,其他的人再開始動手”

女朋友:“嗯?然後呢”

三歪:“你可以理解成,我們多個人會在同一個目錄下編寫代碼,裡邊可能會做更改或者添加文件的操作。項目組裡的所有人都可以對這個目錄修改,改完了我們會提交,然後發布上線系統。”

女朋友:“啥?你是不是偏題了?這跟發布上線系統有啥關係?”

三歪:“哦,我還是舉個例子吧。就比如我們寫論文的時候可能要對論文不斷修改,我們的修改是基於原有的基礎上改的”

三歪繼續補充:”因為我們怕在原來的基礎改錯了東西,沒法恢復,所以,我們可能會有多個「畢業論文」的文件。而我們寫代碼的時候本身就是「多人協作」的,修改是無法避免的,我們不希望有多個文件的產生,又希望能夠記錄每次更改的內容。“

三歪:”更改的內容指的就是:基於原有的基礎上更改了什麼,以及提交者是誰。這樣子,我們就沒法甩鍋了。說白了就是,我們能知道的文件被改了什麼,以及誰改了“。

三歪:“到這裏,有問題嗎?”

女朋友:“嗯,沒問題,你繼續”

三歪:“「每一次的修改」我們稱為一個版本,它能夠實現版本與版本之間的來回穿梭。打個比方,我有篇文章寫了一周,這期間有10個版本,我能隨意回到我想要的版本。所以它叫做版本控制軟件”

女朋友:“我大致聽懂了,大概就是每一次修改都會被記錄下來,然後你們就可以知道每一次版本修改了什麼,是誰改的,如果做錯了,可以通過這個軟件回到想要的版本”

三歪:“嗯,就是這個意思”

女朋友:“那我想問個問題,你一直提到的「多人協作」是在同一個目錄下對文件修改的,然後可以看到彼此改了什麼。那你是在你的電腦上改,你的同事是在他的電腦上改的,你們是怎麼看到彼此改了什麼?這現在有這麼厲害的東西了嗎?“

三歪:”哦~你的意思大概就是:我們又不聯網,怎麼知道對方改了什麼,是這個意思吧?“

女朋友:“嗯,是的”

三歪:“你的理解是沒錯的,我們之間不聯網,是沒辦法知道對方改了什麼的。我漏了一點沒說,我們在改到一定程度下(比如說這個功能我們做完了、也可能做得差不多了),我們會把當前版本提交到遠程倉庫上”

三歪繼續補充:“可以發現的是,提交到遠程倉庫后,即便我們電腦壞了,我們可以從遠程倉庫再把這份數據拉取下來。”

女朋友:“所以呢?遠程倉庫到我這聽起來就是一個備份的功能吧?你們怎麼知道對方改了什麼?”

三歪:“是這樣的,我們從遠程倉庫拉取代碼的時候除了會把有變動的代碼同步到自己的電腦上,還會把所有修改的記錄也同步到自己的電腦上。所以說,我們會知道彼此修改的內容。”

女朋友:”聽着很有用啊,我平時用的word和excel可以用這個軟件嗎?即便我電腦壞了,我還可以去『遠程倉庫』拿到上一次我提交的數據,並且還有你所說的『版本控制』功能。又能備份,又能知道每次修改了什麼,很好用啊!“

三歪:”很可惜,像你們那種工作場景,可能用不上,也可以說不太適合用“

女朋友:”為什麼?“

三歪:”之前我也想要用定時任務+GitHub的方式去保存我在本地寫的文章,發出來之後,被一頓噴。GitHub你可以簡單理解為就是那個遠程倉庫,定時任務我就是讓它隔一段時間就保存一次“

女朋友:”我聽明白了,你想要的是備份功能,對吧?為什麼被噴的呢?“

三歪:”他們說我瞎整,這每隔一段時間就提交到GitHub,網絡開銷可多大啊。然後給我列出一系列的產品,比如說:「堅果雲」「Dropbox」「OneDrive」等等“

女朋友:”那你怎麼不用?“

三歪:”我哪知道啊,有信息差的呢。反正當時覺得自己寫個定時任務實現了,就沒多想了。“

女朋友:”那你是真的菜“

三歪:”哦“

三歪:”除了上面說的自動同步,你們的word、excel在用我們的工具也沒法查到歷史的版本記錄“

女朋友:”為啥?你不是說你們寫代碼都可以的嗎?為什麼word和excel就沒法查?“

三歪:”我們寫代碼的文件類型都是屬於文本文件,而你的word、excel本質上屬於二進制文件,很難去比對每次修改的差異,所以不支持“

女朋友:”那聽你這樣說,在我的場景里這個「版本控制軟件」沒啥用啊,它不能記錄像Word、Excel這種文件每次版本的差異,要想用它做備份,還不如現有的雲產品。“

三歪:”嗯,是的。其實現在雲產品也能提供版本控制的功能了,你用它們就足夠了。還有一點很重要的是,它有學習成本,可不是每個人都會用的。“

女朋友:”聽你說了這麼多,好像雲產品就很行啊,為啥你們還要「偏執」去用你說的那東西?“

三歪:“主要是我們寫代碼時遇到的問題會更多,我們用的「版本控制軟件」會更加靈活,它支持的功能會更多。”

女朋友:“對了,你都說了這麼久了,你們用的那個軟件叫啥啊?”

三歪:“Git

三歪瞎扯

其實本來想着直接講一下在工作中常用到的Git命令,但發現不太好寫(琢磨了很久

看三歪文章的人可能很多都是大學生,對Git本身了解可能就不太清楚,直接上Git的命令可能看不太懂,所以就分開了兩篇。

下一篇講一下三歪在工作中使用Git的糗事以及工作中是怎麼用Git的。

涵蓋Java後端所有知識點的開源項目(已有8K+ star):

  • GitHub
  • Gitee訪問更快

我是三歪,一個想要變強的男人,感謝大家的點贊收藏和轉發,下期見。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

真慘!連各大編程語言都擺起地攤了!

困難年年有,今年特別多。

公司要做一個新的網站,可預算有限,聽說為了生計,各大編程語言們都擺起了地攤兒,我決定去瞧瞧,看看能不能淘點做網站需要的東西。

選擇靜態web服務器

一進集市,這煙火氣就撲面而來,平時一個個端着架子的C++、Java、Python居然能放下身段,招呼叫賣,我還是頭一回見。

“老哥,需要來點什麼?”,C語言給我打起了招呼。

“我想要建個網站”,我回答到。

“那你可算來對地方了”,C語言攤主起身說到,“建網站總得需要一個Web服務器吧,你看這裏,apacheweb服務器,賣的可好了”

我搖了搖頭,“這個apache,之前有用過,是用的多進程模型,連接多了有些吃力啊?”

“老哥是行家啊,來看這一款我們最新推出的nginx服務器,採用epoll多路復用+事件驅動,性能強勁!上萬連接不在話下”,C語言攤主自豪的說到。

隨後攤主給我展示了這個nginx服務器的能力,果然不錯,我加入了購物車,繼續往前逛。

挑選web應用開發框架

沒走幾步來到 C# 的攤前。

“喲,老哥,你這是要做網站啊?”,C#攤主主動給我打起了招呼。

“你怎麼知道的?”,我好奇的問到。

“你這購物車裡不是裝了一個nginx嘛!既然做網站,可得試試我們家的.NET Framework哦,各種裝備,應有盡有。”,C#熱情的拉着我過去。

不過我還是拒絕了他:“實在不好意思,聽說你們家產品只能在Windows系統上面運行,不支持Linux,還是算了,我再看看別家”

C#攤主不肯放棄,“別呀,我們已經支持Linux了,您再看看,現在搞活動,免費送IIS服務器哦,你把那nginx退了吧,喂,再考慮一下啊·····”

不等他說完,我就溜走了,來到了Python的攤前。

Python攤主也看出了我要做網站,也推銷起他家的產品來。

“大哥,你做網站,肯定不想只做一個靜態的吧,來試試咱們家的Web框架做一個動態網站?咱Python家的產品,簡單、輕量又實惠。”,攤主熱情的說到。

“有哪些推薦的呢?”,我問到。

Python攤主指着攤位上的幾個產品說道:“有DjangoFlaskTornado這三款是現在主打拳頭產品,用了的都說好”

我正想蹲下仔細看看,背後傳來一個聲音:“這位大哥,擱這選Web開發框架吶?快來我這邊看看”

一邊說,一邊硬把我往後面拽。

來到他的攤位上,我一看原來是PHP攤主。

“咱PHP產品琳琅滿目,就是專門為做網站而生的,現在做活動,跳樓價只要9.9,錯過不再有!”

這PHP攤主好生能說,一頓猛誇把我說的暈頭轉向,不知怎的竟然就加入了購物車。

繼續向前,來到了Java的攤位,一個好大的攤位,擺放的東西也是看的人眼花繚亂。

“你這個攤位不錯啊,又寬敞人流又多”

“可不是咋的,剛為了搶這個攤位,跟PHP那傢伙還幹了一架呢。”,Java攤主笑着說到。

看到我購物車裡的東西,Java攤主也開始推銷起來:“大哥,這年頭怎麼還用PHP那傢伙的東西,趕緊去退了吧,咱Java攤里的東西都是大品牌,質量有保障!”

“這,不太好吧,這PHP也是大品牌啊”

Java攤主搖了搖頭,“他一個腳本語言怎麼跟我們比啊?大哥你看,我們有Spring、SpringMVC、SpringBoot、SpringCloud等等明星產品,用戶眾多,售後工作也到位。而且現在搞活動可以送tomcat服務器,你要是用戶量不多都可以把nginx退掉,省一筆錢。”

“看起來很厲害的樣子呢,我考慮一下”,我打算再去別的地方看看比較一下。

Java攤主一把拉住了我,“大哥,不說了,咱今天碰到是緣分,你做網站有很多服務是吧,得用到RPC吧,你今天下單,我再送你一套netty框架,又能幫你省一筆了”

Java攤主盛情難卻,我一時興起,買下了好幾個,購物車都裝滿了一大半了。

挑選數據庫

剛付完錢準備離開,背後又傳來一個聲音:“大哥,做網站你得用數據庫吧,快來這看看”

我尋聲望去,原來是 C++ 攤主在叫我。

“來看看我的MySQL數據庫,做網站必備!”

我看了一下產品說明書,感覺還不錯,看了下錢包,剛才在Java攤主那裡花費不少,有些囊中羞澀了,問到:“能不能優惠一點”

C++攤主一聽,臉上的笑容少了一半,“如果你選個MongoDB組個套餐,可以給你8折優惠”

“MongoDB?我要這個幹嘛”

攤主一聽來了勁頭,開始滔滔不絕:“有些數據啊他不適合存在數據庫里,比如文檔啊,JSON啊,這些東西你要用數據庫存儲,增加字段和查詢,可麻煩了,你用MongoDB就方便都多了······”

被他說了一通,感覺是得要個這個玩意兒。

攤主見我有些心動,又繼續推銷:“大哥看來真是行家,您做網站是不是有圖片音頻視頻需要存儲,我這裏還有一個對象存儲(OSS)系統CEPH,你看看要不要也一併帶上,我還是給您八折,怎麼樣?”

“實在不好意思,我這預算有些吃緊了,這個就算了吧”,我婉拒到。

“哎哎大哥您往這瞧,咱家也有對象存儲minio,現在市場推廣期,免費送了!”,旁邊的Golang攤主招呼了起來。

居然有免費這好事,我倒是想去看看。

C++攤主見狀小聲說到:“免費的你敢用,出了問題都找不到人,還是看看我的吧,直接給你六折,怎麼樣?”

我一想也是,正想下單買下,背後傳來一聲“且慢”!

我回頭一看,原來是剛才的Java攤主,“大哥,咱Java家的ElasticSearch也考慮一下唄。”

我回到Java攤主這邊,問到:“這又是個什麼?我需要用到嗎?”

Java攤主也開始給我掰扯起來:“咱家的ElasticSearch那可是搜索行家,你網站內容多了是不是需要個搜索功能啊,咱家的這個ES,全文搜索不在話下,秒級響應,做網站必備啊。看你是回頭客,給你九折!”

我正想做一個搜索功能,看來這個也是必不可少,也一起拿下了。

緩存服務器

我推着購物車準備回家了,今天真是滿載而歸。

來到集市出口,又碰到了一開始的C語言攤主,攤主一瞧揮着手喊道:“大哥,你還差個內存緩存系統,過來看看,Redis搞活動呢!哎,別走啊,Memcached虧本處理了,過來看看啊”

我一摸錢包,完蛋,嚴重超支了!我加快了步伐,匆忙離開······

彩蛋

看着我採購回來的一堆東西,老闆是氣不打一處來。

“咱們就做一個內網論壇,全公司不過100號人,你給我搞這麼多,幾個意思?”

“老闆,您聽我解釋···”

“解釋個啥,明天不用來了”

哦豁,丟了飯碗,我也去擺地攤了···

往期熱門回顧

因為一個跨域請求,我差點丟了飯碗

一個神秘URL釀大禍,差點讓我背鍋!

就為了一個原子操作,其他CPU核心都罷工了

完了!CPU一味求快出事兒了!

可怕!CPU竟成了黑客的幫凶!

哈希表哪家強?幾大編程語言吵起來了!

震撼!全網第一張源碼分析全景圖揭秘Nginx

一網打盡!每個程序猿都該了解的黑客技術大匯總

DDoS攻擊:無限戰爭

一個Java對象的回憶錄:垃圾回收

誰動了你的HTTPS流量?

路由器里的廣告秘密

一個HTTP數據包的奇幻之旅

我是一個流氓軟件線程

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

網頁設計最專業,超強功能平台可客製化

分類
發燒車訊

可愛的負擔…美國爆發「花栗鼠之亂」 到處打洞居民快瘋了

摘錄自2020年7月19日自由時報報導

據《福斯新聞》報導,緬因州內陸漁業與野生動植物小型哺乳動物專家韋伯(Shevenell Webb)表示,去(2019)年秋天產出大量的橡實,讓花栗鼠在春季繁衍後代時在地面上到處都可以找到食物,就這樣造成如今的花栗鼠嬰兒潮。韋伯說,花栗鼠真的很可愛,但同時也是破壞狂,不僅會挖洞破壞草坪和花園,有時還會溜進屋內造反。

佛蒙特州魚類和野生動物部門野生動植物多樣性主任帕倫(Steven Parren)則說,他監控的地區有太多橡實,以至於囓齒動物無法在冬天把它們全都藏起來,到了今(2020)年春天地面上還留有很多橡實,除了花栗鼠之外也造成松鼠、兔子等族群增加。

不過,人們不用太擔心這次的花栗鼠狂潮,因為小型哺乳類動物族群本來就很容易出現物種激增的事件,隨後就會迎來一陣消寂,更何況花栗鼠很容易成為貓頭鷹、老鷹、蛇類、狐狸和浣熊的獵物,野生花栗鼠平均只有3年壽命,比最高壽命少了許多。

※ 本文與 行政院農業委員會 林務局   合作刊登

生物多樣性
國際新聞
美國
食物鏈
生態平衡
處變不驚──與野生動物相遇
人與動物衝突事件簿

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案