python基本操作-文件、目錄及路徑_包裝設計

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

目錄

  • 1 前言
  • 2 文件夾操作
    • 2.1 查詢操作
    • 2.2 創建操作
    • 2.3 刪除操作
    • 2.4 修改操作
  • 3 文件操作
    • 3.1 查詢操作
    • 3.2 創建操作
    • 3.3 修改操作
    • 3.4 刪除
  • 4 路徑操作
  • 5 示例應用
    • 5.1 批量修改文件名
    • 5.2 遍歷目錄及子目錄下所有指定擴展名的文件
    • 5.3 按修改時間排序指定目錄下的文件
  • 6 總結
  • 參考資料
  • 往期文章

使用python的os模塊,簡單方便完成對文件夾、文件及路徑的管理與訪問操作。

1 前言

在最近開發中,經常需要對文件進行讀取、遍歷、修改等操作,想要快速、簡單的完成這些操作,我選擇用 python 。通過 python 的標準內置 os 模塊,只需要幾行代碼,即可完成想要的操作。經過對 os 的使用,本文把 os 模塊的常用的操作進行總結,主要分為以下幾個劃分:

  • 文件夾操作:即文件夾的創建、修改(改名/移動),查詢(查看、遍歷)、刪除等。
  • 文件操作:即文件的創建、修改、讀取、刪除等。
  • (文件夾/文件)路徑操作:即文件夾或文件的路徑操作,如絕對路徑,文件名與路徑分割,擴展名分割等

本文涉及常用 的 os 函數的使用展示,主要使用 python 交互模式下進行代碼說明。後續操作默認已經引入 os 模塊,如下:

import os

2 文件夾操作

以本地 E://pythontest 目錄作為演示目錄,此目錄下當前文件如下:

test
 │ test.txt
 └─test-1
     test-1.txt

testtest-1 是文件夾,test.txttest-1.txt 是文件。

2.1 查詢操作

熟悉 linux 同學應該對 ls / pwd / cd 等操作不陌生,對應的 python 也有對應的方法,主要包括:

  • listdir : 文件及目錄列表
  • getcwd :獲取當前目錄
  • chdir :更換目錄
  • stat :文件及目錄基本信息
  • walk :遞歸遍歷目錄
>>> os.chdir("E://pythontest")  # 更改目錄
>>> os.getcwd()                 # 獲取當前目錄
'E:\\pythontest'
>>> os.listdir("test")          # 文件及目錄列表,相對路徑
['test-1', 'test.txt']          
>>> os.listdir("E://pythontest/test")  # 文件及目錄列表,絕對路徑
['test-1', 'test.txt']
>>> os.stat("test")             # 獲取目錄信息
os.stat_result(st_mode=16895, st_ino=4503599627377599, st_dev=266147611, st_nlink=1, st_uid=0, st_gid=0, st_size=0, st_atime=1590833033, st_mtime=1590832647, st_ctime=1590832207)
>>> os.stat("test/test.txt")    # 獲取文件信息
os.stat_result(st_mode=33206, st_ino=2251799813692354, st_dev=266147611, st_nlink=1, st_uid=0, st_gid=0, st_size=4, st_atime=1590832653, st_mtime=1590832609, st_ctime=1590832598)

其中 stat 函數返回的是文件或者目錄的基本信息,具體如下:

  • st_mode: inode 保護模式
  • st_ino: inode 節點號。
  • st_dev: inode 駐留的設備。
  • st_nlink: inode 的鏈接數。
  • st_uid: 所有者的用戶ID。
  • st_gid: 所有者的組ID。
  • st_size: 普通文件以字節為單位的大小
  • st_atime: 上次訪問的時間。
  • st_mtime: 最後一次修改的時間。
  • st_ctime: 創建時間。

日常使用中,我們一般使用 st_size 、st_ctime 及 st_mtime 獲取文件大小,創建時間,修改時間。另外,我們看到輸出的時間是秒數,在這裏提一下,關於日期的轉換處理。

(1)秒數轉日期時間格式字符串

>>> import time                              # 引入time模塊
>>> timestruct = time.localtime(1590803070)  # 轉換為時間結構體
>>> print(timestruct)
time.struct_time(tm_year=2020, tm_mon=5, tm_mday=30, tm_hour=9, tm_min=44, tm_sec=30, tm_wday=5, tm_yday=151, tm_isdst=0)
>>> time.strftime("%Y-%m-%d %H:%M:%S",timestruct)   # 格式化時間
'2020-05-30 09:44:30'

(2)格式日期時間字符串轉秒數

>>> import datetime               # 引入datetime模塊
>>> timeobject = datetime.datetime.strptime("2020-05-23 10:00:00","%Y-%m-%d %H:%M:%S") #解析時間字符串為時間對象
>>> timeseconds=time.mktime(timeobject.timetuple())  # 獲取時間秒數
>>> print(int(timeseconds))       # 轉為int显示
1590199200
  • 遍歷操作

    walk 函數對目錄進行遞歸遍歷,返回 root,dirs,files,分別對應當前的遍歷的目錄,此目錄中的子目錄及文件。

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

    網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

>>> data = os.walk("test")               # 遍歷test目錄
>>> for root,dirs,files in data:         # 遞歸遍歷及輸出
...    print("root:%s" % root)
...    for dir in dirs:
...       print(os.path.join(root,dir))
...    for file in files:
...       print(os.path.join(root,file))
...
root:test
test\test-1
test\test-2
test\test.txt
root:test\test-1
test\test-1\test-1.txt
root:test\test-2
test\test-2\test-2.txt

2.2 創建操作

  • mkdir :新建單個目錄,若目錄路徑中父目錄不存在,則創建失敗

  • makedirs :新建多個目錄,若目錄路徑中父目錄不存在,則自動創建

>>> os.mkdir("test")
>>> os.mkdir("test1/test1-1")          # 父目錄不存在,報錯
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [WinError 3] 系統找不到指定的路徑。: 'test1/test1-1'
>>> os.makedirs("test1/test1-1")       # 父目錄不存在,自動創建
>>> os.listdir("test1")
['test1-1']

2.3 刪除操作

  • rmdir :刪除單個空目錄,目錄不為空則報錯
  • removedirs : 按路徑刪除遞歸多級空目錄,目錄不為空則報錯
>>> os.rmdir("test1")                         # 若目錄不為空,報錯
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [WinError 145] 目錄不是空的。: 'test1'
>>> os.rmdir("test1/test1-1")
>>> os.removedirs("test1/test1-1")            # 刪除多級空目錄
>>> os.listdir(".")
['test']

由於刪除空目錄的限制,更多的是使用 shutil 模塊中的 rmtree 函數,可以刪除不為空的目錄及其文件。

2.4 修改操作

  • rename :重命名目錄或文件,可修改文件或目錄的路徑(即移動操作),若目標文件目錄不存在,則報錯。
  • renames :重命名目錄或文件,若目標文件目錄不存在,則自動創建
>>> os.makedirs("test1/test1-1")
>>> os.rename("test1/test1-1","test1/test1-2")     # test1-1 修改為test1-2
>>> os.listdir("test1")
['test1-2']
>>> os.rename("test1/test1-2","test2/test2-2")     # 由於test2目錄不存在,報錯
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [WinError 3] 系統找不到指定的路徑。: 'test1/test1-2' -> 'test2/test2-2'
>>> os.renames("test1/test1-2","test2/test2-2")    # renames可自動創建不存在的目錄
>>> os.listdir("test2")
['test2-2']

如果目標路徑文件已經存在,那麼os.rename()和os.renames()都會報錯:FileExistsError: [WinError 183] 當文件已存在時,無法創建該文件。

3 文件操作

3.1 查詢操作

  • open/read/close :文件讀取
  • stat :文件信息,詳細見前面文件夾中的 stat 說明
>>> f = os.open("test/test.txt", os.O_RDWR|os.O_CREAT)  # 打開文件
>>> str_bytes = os.read(f,100)                          # 讀100字節
>>> str = bytes.decode(str_bytes)                       # 字節轉字符串
>>> print(str)
test write data
>>> os.close(f)                                         # 關閉文件

注意 open/read/close 需要一起操作,其中 open 操作需要指定模式,上述是以讀寫模式打開文件,若文件不存在則創建文件。各模式具體如下:

flags — 該參數可以是以下選項,多個使用 “|” 隔開:

  • os.O_RDONLY: 以只讀的方式打開
  • os.O_WRONLY: 以只寫的方式打開
  • os.O_RDWR : 以讀寫的方式打開
  • os.O_NONBLOCK: 打開時不阻塞
  • os.O_APPEND: 以追加的方式打開
  • os.O_CREAT: 創建並打開一個新文件
  • os.O_TRUNC: 打開一個文件並截斷它的長度為零(必須有寫權限)
  • os.O_EXCL: 如果指定的文件存在,返回錯誤
  • os.O_SHLOCK: 自動獲取共享鎖
  • os.O_EXLOCK: 自動獲取獨立鎖
  • os.O_DIRECT: 消除或減少緩存效果
  • os.O_FSYNC : 同步寫入
  • os.O_NOFOLLOW: 不追蹤軟鏈接

3.2 創建操作

前面已提到,使用 open ,指定模式, 若文件不存在,則創建。有點類似 linux 操作中的 touch。

>>> f = os.open("test/test.txt", os.O_RDWR|os.O_CREAT)   # 若文件不存在,則創建
>>> os.close(f)

3.3 修改操作

  • open/write/close :寫入文件內容
  • rename ,renames : 與前面介紹的修改名稱、移動操作一致。
>>> f = os.open("test/test.txt", os.O_RDWR|os.O_CREAT)     # 打開文件
>>> os.write(f,b"test write data")                         # 寫入內容
15
>>> os.close(f)                                   # 關閉文件

3.4 刪除

  • remove :刪除文件,注意不能刪除目錄(使用 rmdir/removedirs)
>>> os.remove("test/test-1")       # 刪除目錄報錯
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [WinError 2] 系統找不到指定的文件。: 'test/test1'
>>> os.remove("test/test.txt")     # 刪除文件
>>> os.listdir("test")
['test-1']

4 路徑操作

在使用文件或目錄過程中,經常需要對文件及目錄路徑進行處理,因此,os 中有一個子模塊 path,專門就是處理路徑操作的。主要有以下操作:

  • abspath :返回絕對路徑
>>> os.path.abspath("test")
'E:\\pythontest\\test'
  • exists :判斷文件或目錄是否存在
>>> os.path.exists("test")
True
>>> os.path.exists("test/test.txt")
False
>>> os.path.exists("test/test-1/test-1.txt")
True
  • isfile/isdir :判斷是否為文件/目錄
>>> os.path.isdir("test")
True
>>> os.path.isfile("test/test-1/test-1.txt")
True
  • basename/dirname:獲取路徑尾部和路徑頭部。其實就是以路徑中最後一個 / 為分割符,分為頭(head) 和尾(tail)兩部分,tail 是 basename 返回的內容,head 是 dirname 返回的內容。經常用於獲取文件名,目錄名等操作
>>> os.path.basename("test/test-1/test-1.txt")   # 文件名
'test-1.txt'
>>> os.path.basename("test/test-1/")     # 空內容
''
>>> os.path.basename("test/test-1")      # 目錄名
'test-1'
>>> os.path.dirname("test/test-1/test-1.txt")   # 文件所在目錄路徑
'test/test-1'
>>> os.path.dirname("test/test-1/")   # 目錄路徑
'test/test-1'
>>> os.path.dirname("test/test-1")   # 父目錄路徑
'test'
  • join :合成路徑,即把兩個參數使用系統路徑分割符進行連接,形成完整路徑。
>>> os.path.join("test","test-1")   # 連接兩個目錄
'test\\test-1'
>>> os.path.join("test\\test-1","test-1.txt")   # 連接目錄與文件名
'test\\test-1\\test-1.txt'
  • split :分割文件名和文件夾,即把 path 以最後一個斜線”/”為分隔符,切割為 head 和 tail ,以 (head, tail) 元組的形勢返回。
>>> os.path.split("test/test-1")     # 分割目錄
('test', 'test-1')
>>> os.path.split("test/test-1/")    # 以/結尾的目錄分割
('test/test-1', '')
>>> os.path.split("test/test-1/test-1.txt")  # 分割文件
('test/test-1', 'test-1.txt')
  • splitext :分割路徑名和文件擴展名,把path 以最後一個擴展名分隔符“.”分割,切割為 head 和 tail ,以 (head, tail) 元組的形勢返回。注意與 split 的區別是分隔符的不同。
>>> os.path.splitext("test/test-1")  
('test/test-1', '')
>>> os.path.splitext("test/test-1/") 
('test/test-1/', '')
>>> os.path.splitext("test/test-1/test-1.txt")  # 區分文件名及擴展名
('test/test-1/test-1', '.txt')
>>> os.path.splitext("test/test-1/test-1.txt.tmp") # 以最後的"."為分割點
('test/test-1/test-1.txt', '.tmp')

5 示例應用

下面以一些平時使用到的場景,對前面的操作函數進行綜合使用。

5.1 批量修改文件名

def batch_rename(dir_path):
    itemlist = os.listdir(dir_path)
    # 獲取目錄文件列表
    for item in itemlist:
        # 連接成完整路徑
        item_path = os.path.join(dir_path, item)
        print(item_path)
        # 修改文件名
        if os.path.isfile(item_path):
            splitext = os.path.splitext(item_path)
            os.rename(item_path, splitext[0] + "-副本" + splitext[1])

5.2 遍歷目錄及子目錄下所有指定擴展名的文件


def walk_ext_file(dir_path,ext):
    # 遍歷
    for root, dirs, files in os.walk(dir_path):
        # 獲取文件名稱及路徑
        for file in files:
            file_path = os.path.join(root, file)
            file_item = os.path.splitext(file_path)
            # 輸出指定擴展名的文件路徑
            if ext == file_item[1]:
                print(file_path)

5.3 按修改時間排序指定目錄下的文件

def sort_file(dir_path):
    # 排序前
    itemlist = os.listdir(dir_path)
    print(itemlist)
    # 正向排序
    itemlist.sort(key=lambda filename: os.path.getmtime(os.path.join(dir_path, filename)))
    print(itemlist)
    # 反向排序
    itemlist.sort(key=lambda filename: os.path.getmtime(os.path.join(dir_path, filename)), reverse=True)
    print(itemlist)
    # 獲取最新修改的文件
    print(itemlist[0])

6 總結

在需要對文件或者目錄進行操作時,python 是一個簡單快速選擇。本文通過 python 的標準內置 os 模塊及子模塊 os.path 的常用方法進行介紹,最後結合使用場景進行綜合使用。相信已經滿足大家對文件及目錄操作的大部分需求。

參考資料

  • python之os模塊:https://www.cnblogs.com/yufeihlf/p/6179547.html
  • Python OS 文件/目錄方法: https://www.runoob.com/python/os-file-methods.html
  • Python os.path() 模塊: https://www.runoob.com/python/python-os-path.html

往期文章

  • MinIO 的分佈式部署
  • 利用MinIO輕鬆搭建靜態資源服務
  • 搞定SpringBoot多數據源(3):參數化變更源
  • 搞定SpringBoot多數據源(2):動態數據源
  • 搞定SpringBoot多數據源(1):多套源策略
  • java開發必學知識:動態代理
  • 2019 讀過的好書推薦

我的公眾號(搜索Mason技術記錄),獲取更多技術記錄:

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

從輾轉相除法到求逆元,數論算法初體驗_網頁設計

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天是算法和數據結構專題的第22篇文章,我們一起來聊聊輾轉相除法。

輾轉相除法又名歐幾里得算法,是求最大公約數的一種算法,英文縮寫是gcd。所以如果你在大牛的代碼或者是書上看到gcd,要注意,這不是某某黨,而是指的輾轉相除法。

在介紹這個算法之前,我們先來看下最大公約數問題。

暴力解法

這個問題應該很明確了,我們之前數學課上都有講過。給我們紙筆讓我們求都沒有問題,分解因數找下共同的部分,很快就算出來了。但是用代碼實現怎麼做呢?

用代碼實現的話,首先排除分解因數的方法。因為分解因數複雜度太高了,也很容易想明白,既然要分解因數,那麼首先需要獲得一定量的質數吧。有了質數之後還要遍歷質數,將整數一點一點分解,顯然很麻煩,還不如直接暴力了。暴力解法並不複雜,我們直接從1開始遍歷,記錄下來同時能夠整除這兩個數的最大數即可。我們暴力的範圍也不大,從1到n。

很容易寫出代碼:

def gcd(a, b):
    ret = 0
    for i in range(min(a, b)):
        if a % i == 0 and b % i == 0:
            ret = i
    return ret

這個很簡單,也許你可能還會想出一些優化,比如說首先判斷一下a和b之間是否有倍數關係,如果有的話直接就可以得到結果了。再比如說我們i的遍歷範圍其實可以不用到min(a, b),如果a和b沒有倍數關係的話min(a, b) / 2就可以了。這些都是沒有問題的,但是即使加上了這些優化依然改變不了這是一個O(n)算法的本質。

比如說a是1e9,b是1e9-1,毫無疑問這樣的做法會超時。

輾轉相除法

接下來就輪到正主——輾轉相除法出場了,這個算法在《九章算術》當中曾經出現過,叫做更相減損術。不管叫什麼,原理都是一樣的,它的最核心本質是下面這個式子:

\[gcd(a, b) = gcd(b, r), a = bq + r \]

這個式子就是著名的歐幾里得定理,這裏的r可以看成是a對b取余之後的結果,也就是說a和b的最大公約數等於b和r的最大公約數。這樣我們就把a和b的gcd轉移成了b和r,然後我們可以繼續轉移,直到這兩個數之間存在倍數關係的時候就找到了答案。

在我們寫代碼之前,我們先來看一下這個定理的證明。

我們假設u同時整除a和b,顯然這樣的u一定存在,因為u至少可以是1,所以:

\[\begin{aligned} a = su, b = tu \\ r = a – bq = su – tuq = (s – tq) u\\ \end{aligned} \]

所以可以得到u也整除r,同樣我們可以證明能夠整除b和r的整數也可以整除a。我們假設v可以同時整除b和r:

\[\begin{aligned} b = sv, r = tv\\ a = bq + r = svq + tv = v(sq + t) \end{aligned} \]

這樣我們就得到了v也可以整除a。也就是說a和b的每一個因子都是b和r的因子,同樣b和r的每一個因子也是a和b的因子,那麼可以得出a和b的最大公約數就是b和r的最大公約數。

以上就是歐幾里得定理的簡單證明,如果看不懂也沒有關係,我們記住這個定理的內容就可以了。

接下來就是用代碼實現了,我們把這個公式套進遞歸當中非常容易:

def gcd(a, b):
    if a < b:
        a, b = b, a
        
   	if a % b == 0:
        return b
    return gcd(b, a % b)

我們首先判斷了a和b的大小關係,如果a小於b的話,我們就交換它們的值,保證a大於b。如果a和b取模的結果為0,那麼說明a已經是b的倍數了,顯然它們之間的最大公約數就是b。

但其實我們沒有必要判斷a和b的大小,我們假設a小於b,那麼顯然a % b = a,於是會遞歸調用b和a % b,也就是b和a,也就是說算法會自動調整兩者的順序。這麼一來,這個代碼還可以進一步簡化,只需要一行代碼

def gcd(a, b):
    return a if b == 0 else gcd(b, a % b)

所以聽到有人說自己用一行代碼實現了一個算法,不要覺得它在裝逼,有可能他真的寫了一個gcd。

拓展歐幾里得

拓展歐幾里得本質上就是gcd,只是在此基礎上做了一定的拓展,從而來解決不定方程。不定方程就是ax + by = c的方程,方程要有解充要條件是(a, b) | c,也就是說a和b的最大公約數可以整除c

也就是說求解ax + by = gcd(a, b)的解。假如說我們找到了這樣一組解x0和y0,那麼x0 + (b / gcd) * t和y0 – (a / gcd) * t也是方程的解,這裏的t可以取任意整數。

我們代入算一下即可:

\[\begin{aligned} a*(x_0 + (b / gcd) * t) + b*(yo-(a/gcd)*t) \\ a*x_0+ b*y_0 + abt / gcd – abt/gcd = gcd \end{aligned} \]

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

\[\begin{aligned} \end{aligned} \]

所以我們求出了這樣的x0和y0之後就相當於求出了無數組解,那麼這個x0和y0怎麼求呢,這就需要用到gcd算法了。

我們觀察一下gcd算法的遞歸代碼,可以發現算法的終止條件是a=gcd,b=0。對於這樣的a和b來說,我們已經找到了一組解使得ax+by=gcd,比如很明顯,x=1,y=0。實際上y可以為任何值,因為b=0。

我們回到遞歸的上一層的a和b,假設我們已經求出了b和a%b的最大公約數,並且求出了一組解x0和y0。使得b*x0 + (a%b)* y0 = gcd。那麼我們能不能倒推得到a和b時候的解呢?

因為a % b = a – (a/b)*b,這裏的/是整除計算的意思,我們代入:

\[\begin{aligned} gcd &= b*x_0 + (a\%b)*y_0 \\ &= b*x_0 + (a – (a/b)*b)*y_0 \\ &= b*x_0 + a*y_0 – (a/b)*b*y_0 \\ &= a*y_0 + b*(x_0 – (a/b)*b*y_0) \end{aligned} \]

顯然對於a和b來說,它的一組解就是y0和x0 – (a/b)*b*y0,我們把這幾行計算加在代碼當中即可,非常簡單:

def exgcd(a, b, x=1, y=0):
    # 當b=0的時候return
    if b == 0:
        return a, x, y
    # 遞歸調用,獲取b, a%b時的gcd與通項解
    gcd, x, y = exgcd(b, a%b, x, y)
    # 代入,得到新的通項解
    x, y = y, x - a//b*y
    return gcd, x, y

這裏我建議大家不要死記代碼,都去推導一下遞歸的這個推導公式。這個公式搞明白了,即使代碼記不住也沒有關係,後面臨時用到的時候再推導也可以。不然的話,即使背下來了代碼也不記得什麼意思,如果碰到的場景稍微變動一下,可能還是做不出來。

逆元與解逆元

拓展歐幾里得算法我們理解了,但是好像看不出來它到底有什麼用。一般情況下我們也碰不到讓我們計算通解的情況,但其實是有用的,用的最多的一個功能就是計算逆元

在解釋逆元之前先來看一個問題,我們有兩個數a和b,和一個模底數p。我們可以得到(a + b) % p = (a%p + b%p)%p,也可以得到 (a – b)%p = (a%p – b%p)%p。甚至還可以得到 (a*b)% p =(a%p * b%p) %p,這些都是比較明確的,但是(a / b) % p = (a % p / b % p) % p,這個式子成立嗎?

最後的式子是不成立的,因為模數沒有除法的傳遞性,我們可以很方便舉出反例。比如a是20, b是10,p是4,(a/b)%p=2,而(a %p / b%p) % p = 0。

這就導致了一個問題,假如說我們在一連串計算當中,由於最終的結果特別大,我們無法存儲精確的值,希望存儲它關於一個模底數取模之後的結果。但是我們的計算當中又涉及除法,這個時候應該怎麼辦?

這個時候就需要用到逆元了,逆元也叫做數論倒數。它其實起到一個和倒數類似的效果,假設a關於模底數p的逆元是x,那麼可以得到:ax = 1 (mod p)

所以我們想要算 (a / b) % p,可以先求出b的逆元假設是inv(b),然後轉化成(a%p * inv(b)%p)%p。

這個逆元顯然不會從天上掉下來,需要我們設計算法去求出來,這個用來求的算法就用到拓展歐幾里得,我們下面來看一下推導過程。

假設a和b互質,那麼gcd(a, b) = 1,代入:

\[\begin{aligned} ax + by &= 1\\ ax \% b + by \% b &= 1 \% b\\ ax\%b &= 1\%b\\ ax &= 1 \pmod b \end{aligned} \]

所以x是a關於b的逆元,反之可以證明y是b關於a的逆元。

這麼計算是有前提的,就是a和b互質,也就是說a和b的最大公約數為1。否則的話這個計算是不成立的,也就是說a沒有逆元。那麼整個求解逆元的過程其實就是調用拓展歐幾里得的過程,把問題說清楚花了很多筆墨,但是寫成代碼只有兩三行:

def cal_inv(a, m):
    gcd, x, y = exgcd(a, m)
    # 如果gcd不為1,那麼說明沒有逆元,返回-1
    return (x % m + m) % m if gcd == 1 else -1

在return的時候我們對x的值進行了縮放,這是因為x有可能得到的是負數,我們把它縮放到0到m的範圍當中。

逆元的求解方法除了拓展歐幾里得之外,還有一種算法,就是利用費馬小定理。根據費馬小定理,在m為質數的時候,可以得到

\[a^{m-1}\equiv 1 \pmod m \]

等式兩邊同時除以a,也就是乘上a的逆元,可以得到:

\[a^{m-2} \equiv inv(a) \pmod m \]

也就是說我們求出\(a^{m-2}\)然後再對m取模就得到了a的逆元,我們使用快速冪可以很方便地求出來。但是這個只有m為質數的時候才可以使用。

總結

今天我們聊了歐幾里得定理聊了輾轉相除法還聊了拓展歐幾里得和求解逆元,雖然這些內容單獨來看並不難,合在一篇文章當中量還是不小的。這些算法底層的基礎知識是數論,對於沒有參加過競賽的同學來說可能有些陌生,但是它也是算法領域一個很重要的分支。

如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

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

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

【JVM】垃圾回收的四大算法_貨運

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

GC垃圾回收

JVM大部分時候回收的都是新生代(伊甸區+倖存0區+倖存1區)。按照回收的區域可以分成兩種類型:Minor GC和Full GC(MajorGC)。

  • Minor GC:只針對新生代區域的GC,大多數Java對象的存活率都不高,Minor GC非常頻繁,回收速度快。
  • Full GC:發生在老年代的GC,經常會伴隨至少一次的Minor GC(但不一定會),Full GC掃描的範圍更廣泛,Full GC的速度比Minor GC慢10倍以上。

 

 

GC四大算法

引用計數法

對於單個對象來說,當有引用發生,引用計數器就+1;當丟失引用,引用計數器就-1。當引用數減到0的時候,說明對象不再有用,被垃圾回收。引用計數法缺點是每次對對象賦值都要維護引用計數器,且計數器本身也有一定的消耗,難以處理引用循環(例如:對象雙方互相引用,但實際上二者為空,此時雙方引用都不為空)。JVM的實現一般不採用這種方式。

複製算法

年輕代中使用的是Minor GC,這種Minor GC採用的是複製算法。複製的思想是將內存分為2快,每次只用其中一塊,當這一塊內存用完,就將或者的對象複製到另一塊上面,複製算法不會產生內存碎片

HotSpot JVM中年輕代可以分成三個部分:Eden區、Survivor0區,Survivor1區,默認比例為8:1:1。Survivor的兩個區在邏輯上可以視為from區和to區,每次GC後會交換from區和to區,在Eden區和from區滿之前,to區始終是為空的區。如果to區也被填滿了,所有對象移動到老年代。

新創建的對象一般會被分配到伊甸區,經過一次Minor GC后,如果對象還存活,就會被移到Survivor區。from區的對象如果繼續存活,且能夠被另一塊倖存區to區容納,則使用複製算法將這些仍然存活的的對象複製到另一塊倖存區to區中,然後清理使用過的Eden和from區(下一次分配就從to區開始,to區成為下一次GC的from區),且這些對象的年齡設置為1,以後對象在倖存區每經歷一次Minor GC,對象的年齡就會+1,當對象的年齡到達某個閾值的時候,這些對象就會進入老年代。(閾值默認是15,可以通過-XX:MaxTenuringThreshhold來設定對象在新生代在存活的次數)。

這種算法的優點了不會產生內存碎片,缺點是浪費內存空間,在HotSpot虛擬機中8:1:1的比例下,可用內存為80%+10%,有10%的內存會被浪費掉。如果對象存活率很高,就需要將所有對象都複製一邊,並重置引用地址。

標記清除(Mark-Sweep)

老年代一般是由標記清除 或者 標記清除和標記整理的混合實現的。

標記清除算法分為兩個步驟,先標記出要回收的對象,然後統一回收這些對象。

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

優點是節約內存空間,不需要額外空間。缺點是兩次掃描,標記和清除的效率都不高,耗時嚴重。標記清除後會產生大量不連續的內存碎片。內存碎片會導致以後程序需要分配大對象的時候,找不到足夠的連續內存,導致提前觸發GC。

 標記整理(Mark-Compact)

和標記清除一樣,先標記出要回收的對象,然後讓存活對象都向一端移動,直接清理掉端邊界 以外的內存。

優點是沒有內存碎片,缺點是效率不高,需要標記存活對象還要整理存活對象的引用地址,從效率上來說是不如複製算法的。

還有一種折衷的方案,將標記清除和標記整理算法相結合,一般直接標記清除,當GC達到一定次數的時候,進行一次標記整理,從而減少了移動對象的成本,又有處理內存碎片的步驟。

總結

效率排名:複製算法>標記清除>標記整理

內存整齊度:複製算法=標記整理>標記清理

內存利用率:標記整理=標記清理>複製算法

四種算法各有優劣,一般的JVM實現會採用分代收集算法,根據不同代所具有的不同特點使用不同的算法。

年輕代的特點是區域較小,對象存活率低,適合使用複製算法。複製算法的效率只和當前存活對象的大小有關,適用於年輕代的回收,內存利用率不高的問題HotSopt通過兩個survivor的設計進行和緩解,新生代可用容量為80%+10%,只有10%的內存被浪費掉。

老年代的特點是區域較大,對象存活率高,適合使用標記清除/標記整理算法。

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

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

15萬買合資SUV必看!這款車居然是歐洲最暢銷車型之一!_貨運

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

而且作為一款SUV它的離地間隙達到了將近20cm這也保證了它具備一定實力的通過性,可以適合歐洲主要以丘陵為主的道路,以及中國多路況的局面,這也是為什麼逍客的銷量在中國銷量也不差的原因。逍客配備的是一款2。0L自然吸氣發動機,最大馬力達到150ps,以及一台模擬七速的CVT變速箱,動力總成的賬面參數並不突出,但是對於家用來說,動力輸出已經完全可以滿足日常的用車需求。



歐洲,世界汽車工業誕生的搖籃,孕育出了寶馬、奔馳、大眾等等世界知名的汽車品牌,但是來自日產的逍客,居然成為了歐洲最暢銷的SUV車型之一,而且逍客在國內的銷量也不低,平均月銷量都過萬台,這車究竟為什麼會如此受歡迎?

外觀:其貌不揚卻迎合觀眾

熟悉歐洲車的朋友都知道,除了法系車以外,以德系車為代表的汽車外觀設計都是普遍偏向保守,這其實也跟歐洲人普遍的審美還是偏向保守嚴謹有關。

逍客的外觀其實沒有什麼突出的亮點,日產家族式的前臉,用整體性更強的幾何板塊營造出一個不過不失,中庸圓潤的車輛外形,這也十分符合一台家用SUV應有的定位。

逍客的車主年齡層次涵蓋十分廣泛,從65后的中年人到85后的年輕人,購買逍客的不在少數,正式這種其貌不揚但是老少通殺的外觀設計才能做到真正迎合消費者的普遍審美。

操控:駕控靈活,可適應路況眾多

作為一台緊湊型SUV,逍客的軸距並不算短,達到了2645mm,但是車身的前後懸較短,營造出了一個較為短小精悍的外觀,

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

而且由於逍客的操控特性建立得十分靈敏,導致逍客的操控感受十分靈敏輕便。談不上什麼運動感和駕駛樂趣,但是逍客卻可以成為一家人所有會開車的人士都比較好上手的一台SUV。

而且作為一款SUV它的離地間隙達到了將近20cm這也保證了它具備一定實力的通過性,可以適合歐洲主要以丘陵為主的道路,以及中國多路況的局面,這也是為什麼逍客的銷量在中國銷量也不差的原因。

逍客配備的是一款2.0L自然吸氣發動機,最大馬力達到150ps,以及一台模擬七速的CVT變速箱,動力總成的賬面參數並不突出,但是對於家用來說,動力輸出已經完全可以滿足日常的用車需求。

內飾:樸實無華,舒適至上

日產的汽車在內飾層面不會體現過多的設計感,走量的車型普遍都是以很樸素的樣式進行設計,中控功能區的按鍵布局合理簡潔,內飾的溝通感做得不錯,但是方向盤尺寸偏大而且握感很細,在駕駛的時候或許需要駕駛員做更多的適應。

逍客的乘坐空間布局不算很大,後排空間顯得比較局促,但是勝在座椅的貼合程度很高,所以乘坐起來很舒適,各種布局都體現了逍客是一款適合家用的SUV。

全文總結:什麼車是一台好車,相信不少人眼裡都會有不同的答案。有些人注重的是品牌情懷,有人注重的是各種突出的性能,而更多人可能選擇是一款足夠實用,簡單,而且不過不失的車型,SUV作為國人家庭接受度程度比較高的車型,日產的逍客或許沒有什麼吸引的品牌效應,甚至有不少排斥日系的朋友會對其不屑一顧,但就事論事的說,逍客作為15萬級的合資家用SUV來說,可以作為一款不錯的選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

海峽兩岸學者講述家文化_網頁設計公司

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

《回家》 薛仁明 白劍峰著 紅旗出版社

近日,《回家》一書由紅旗出版社出版,作者為台灣作家薛仁明和人民日報高級記者白劍峰。這是海峽兩岸學者首次聯袂出版的“家文化”專著。本書從中華傳統文化的視角解讀了“回家”的內涵和意義。《回家》以家庭為切入點,從飲食男女到四時祭祀,從鄉愁鄉思到祠堂家譜,從居住空間到民俗風情,從家風家教到家國情懷,涵蓋了家庭的方方面面,將中華傳統文化融入日常生活的細節之中,剖析了中國人的文化基因,闡述了中國人的家國情懷。本書分為家居、家祭、家鄉、家庭、家風、家教、家國七章,站在中華幾千年文明的高度,涉及儒釋道及諸子百家,體現了儒家“修身、齊家、治國、平天下”的人生理想。書中有美食的味道,有節氣的韻律,有建築的光影,有祭祀的虔敬,有生死的感悟,有血脈的綿續,有鄉愁的記憶,有夫妻的恩愛,有教子的良方,既是一本中華傳統文化的“濃縮讀本”,也是一本家庭生活的“百科全書”。

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

讀懂時代,從讀懂語言開始_網頁設計公司

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

時節如流,歲月不居。年終歲末,不同機構相繼發布年度漢字、漢語盤點,國家語言資源監測與研究中心發布了中國媒體十大流行語:我和我的祖國、金色十年、學習強國、中美經貿磋商、最美奮鬥者、硬核、垃圾分類、先行示範區、基層減負年、我太難了。《咬文嚼字》編輯部、《語言文字周報》等機構公布的年度流行語與之有重合也有不同。

年度流行語盤點,既是語言文字研究領域的一件盛事,也是整個文化界的一樁“雅事”,而且被大眾看成是觀察中國社會、洞悉世道人心的一扇窗口。因此,年復一年,如期而至,廣受關注。今年的年度流行語形態各異,既有“我和我的祖國”“最美奮鬥者”“硬核”等與時代大局大勢同頻共振的“鐘鼓之音”,也有諸如“我太難了”“好嗨呦”之類,反映社會風尚、大眾心態、百姓心聲的“網言網語”。“大珠小珠落玉盤”,盡顯漢語言文字的豐富性。

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

過去的2019年,我們為共和國70年的輝煌成就喝彩,愛國主義情感讓我們熱淚盈眶,“我和我的祖國”在大江南北唱響。我們感動於千千萬萬奮鬥者“擼起袖子加油干”的蓬勃朝氣,也為基層幹部切實減輕了負擔鬆了一口氣;我們為偉大祖國的欣欣向榮而驕傲,也每每因工作的壓力、生活的不易感到焦慮……不論社會如何變化,總有一些與每個人息息相關的國家大事、社會大潮引領着我們的注意力,總有一些標誌性的人和事讓我們產生情感的交集;總有一些共同的情感把我們的心凝聚在一起。也因此,在人口基數如此龐大的中國,每年總有一些被普遍認同的年度流行語。

過去的2019年,我們的社交方式、信息獲取方式乃至生活方式,都在發生更深刻改變。人們在為奮鬥者喝彩的同時,也對所謂的“996工作制”產生質疑;既熱衷於以“X千萬條,Y第一條”造句,也在類似“我不要你覺得,我要我覺得”的表達中展現着自我意識的增強、個體個性的鮮明;盤他、雨女無瓜、檸檬精、斷舍離等等流行語,讓很多人感到摸不着頭腦,一些人卻高度認同、非常默契,這提示我們,分眾化的社交趨勢更加明顯,不同社交圈的人往往有着全然不同的文化生態和話語體系。如果把這種巨大的差異性放在70年歲月變遷的歷史長河中去觀察,我們就會看到,這種千差萬別、百花齊放、形態各異,展現的正是生活選擇的自由、社會文化的多元、人的個性的舒展。“和而不同,各美其美”是社會繁榮進步的體現。

語言是時代的風向標,讀懂時代,當從讀懂語言開始。十多年來,每至歲末,一串串閃現在語言文字大潮中的流行語被“打撈”出來。這種“打撈”,實際上是對國家大勢、世界風雲、民生實事、社會熱點的“打撈”,是對時代變化、社會變遷的“打撈”。一個個時代流行語鋪展在面前,嘴角輕揚,看似波瀾不驚地咂摸間,往往蘊含着人們對自身生活、社會變革與人類發展的深長思考,往往寄予着擁抱更美好生活、更美好時代的蓬勃進取心。“只爭朝夕,不負韶華”,新年已至,讓我們整裝出發。(作者:李思輝,系華中科技大學新聞評論研究中心特聘研究員)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

專家學者聚焦古民居保護傳承:像對老人一樣善待古民居_包裝設計

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

城市化進程加快,使得自然村落加速消散。如何保護傳統古民居,促進建築遺產良性有序傳承,成為文化收藏界深思的話題。

在彙集了眾多徽派古民居的聞道園,業界專家、收藏者11日舉行了一個小型沙龍,共同聚焦古民居的文化傳承及活態保護。

同濟大學國家歷史文化名城研究中心特約研究員居平表示,古民居是中國人的傳統住宅,體現了獨特的中華文化,其中蘊含的文化藝術可謂國粹。古民居體現了中國人的生活方式、人際交往,以獨特的方式呈現中國之美。由此,留住古民居也是留住了“中國文化的根”。

知名建築保護專家阮儀三在接受採訪時認為,注重真實性、整體性、可讀性、永續性、獨特性是老房子收藏保護的最高境界。他亦坦言,實在非拆不可,只能異地保護,花功夫拆,給每一片瓦、每一塊磚都編上號。收藏學專家祝君波也認為,異地收藏和保護並非上策,但比之於“毀”,是一種妥協的辦法。

坐落在上海寶山羅店鎮的聞道園,規劃面積600餘畝,被業界譽為“古建築異地保護的典範”。沙龍採訪之餘,記者探訪了這座洋溢着古典之美的園林。行走其間,記者看到,園內錯落有致地佇立了數十座古色古香的徽州老宅、古橋、古亭、戲台……庭院里、房間里懸挂、擺放着匾額、傢具等“老物件”。清代咸豐九年的永濟橋、道光八年的狀元樓、乾隆年間的雕花樓……在這裏靜靜地佇立着,散發著厚重的歷史氣息,青石砌成的門牆洋溢着濃濃的古意,石磨盤連成的小路蜿蜒神相遠處,置身寧靜古樸的飛檐古宅群之間,在垂柳、碧波映襯下,彷彿時空倒轉,現代人與崇尚自然的古人有了神交。

聞道園創始人王衛告訴記者,目前園中仍有包括明代大夫第、明代金絲楠木翰林院、清代大祠堂等在內的百餘棟古民居原材料還靜待搭建。

談及收藏這些古民居的初衷,王衛十分感慨:收藏古宅的初衷不僅源於對徽式古建築的喜愛,更出於對老宅飄零凋敝現狀的痛心。20多年前,他與朋友在安徽鄉間遊歷,黛瓦、粉壁、馬頭牆的徽派古民居建築之美深深吸引着他們。承載着數百年歷史滄桑的飛檐、雕花韻味雋永,讓人回味無窮。可是當時在安徽的一些農村,修復一幢稍有規模的古宅所需費用,憑當地人的年收入,根本無力承受。日久天長,一些古宅或受潮霉爛,或坍塌廢棄。不少精美木雕被住戶丟棄。

王衛說,眼睜睜看着不少古宅被用作豬圈、養蠶場,心中不僅是痛楚,還有對傳統文化失落的擔憂。於是,他將早年做生意所得積蓄投入古民居的收藏。為了盡可能多地保留下這些古民居,王衛曾一度賣掉房產,來收藏別人眼中的“爛木頭”。

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

王衛告訴記者,很多古民居在被收藏的時候已然破敗不堪,甚至已經倒塌。不少古民居或被白蟻蛀蝕、或被風雨侵蝕,毀損嚴重,脆弱飄搖地存在於世間,如同風燭殘年的老者。有些古民居只剩下主要構件。在王衛看來,每一棟古民居都是不可再生的財富,不僅記載着歷史變遷,更飽含文化傳承的價值。王衛將這些當地人眼中的“廢物”,當做寶貝,一件不落地運回上海。或者按原貌維修重建,或者進行防腐、防蛀處理后暫時保存起來,等待日後逐一恢復原貌。

在收集古民居的20餘年裡,一百多棟古民居建材被王衛用卡車分300多次運到上海。王衛說,遇到特別粗大的冬瓜梁,因為鄉村道路設施限制,無法用吊機搬動,所以只能靠人力靠人力搬運。一根冬瓜梁常需要20多個壯漢共同搬動。

據了解,一些古民居的部件,因為年代久遠,經多年風霜侵蝕,無法再使用,所以在建設過程中,王衛和工作人員按照原部件的材質和形態,四處尋覓最為相似的部件。他說,這是一個艱辛的而糾結的過程。

如今,20多棟徽派古民居已經建好,成為上海“水泥鋼筋”森林中,別具特色的徽派古民居觀賞地。王衛賦予每一棟重煥新生的古民居不同的“職責”。有的成為傳播傳統文化的文創中心,有的作為人們修心的禪修堂,還有的變身書院、畫院和大師工作室。聞道園也成為書畫家、文人墨客青睞的創作、展示作品場所。王衛說,重生的古民居只有在現代人的使用中才能得到最好的保護。

談及未來,王衛期待一棟棟古民居逐漸恢復修建,古建築藝術重煥光彩。他希望這些古民居成為傳統文化的彰顯之地。

不過,如今,待建的百餘棟古民居原材料暴露在自然環境中,這令王衛十分着急。古民居全部重建需要時間,而原本就已經十分破敗凋敝的老木料雖經修復,但已經不起風霜烈日的摧殘。王衛說,如果在自然環境中再次霉爛,這些古民居終將難逃毀滅的厄運。

作為文化研究學者,居平坦言,在現實中,古民居的保護與當地居民實際需求,以及與相關機構、個人利益產生衝突。這對古民居保護確實不利。她呼籲,要像善待老人一樣善待、愛護古民居。

談及還未恢復建造的古民居原材料,阮儀三指出,應給這些寶貴的原材料提供相對穩定的存儲環境。對古民居的保護不僅要動用高科技手段,還要進行活態保護。(陳靜)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

曹工說mini-dubbo(2)–分析eureka client源碼,想辦法把我們的服務提供者註冊到eureka server(上)_網頁設計

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

前言

eureka是spring cloud Netflix技術體系中的重要組件,主要完成服務註冊和發現的功能;那現在有個問題,我們自己寫的rpc服務,如果為了保證足夠的開放性和功能完善性,那肯定要支持各種註冊中心。目前我們只支持redis註冊中心,即服務提供者,在啟動的時候,將自身的ip+端口信息寫入到redis,那,我們是否註冊到 eureka中呢?

這個想法可行嗎?可行。eureka client 和eureka server間,無非是網絡通信,既然是網絡通信,那就有網絡協議,那我們的應用,只要遵照eureka server的協議來,就可以接入。

另外,eureka server沒有採用spring mvc來實現,而是採用了jersey框架,這個框架啥意思呢,可以理解為對Restful的實現。我從網上找了一段(https://www.jianshu.com/p/88f97b90963c):

SpringMVC在開發REST應用時,是不支持JSR311/JSR339標準的。如果想要按照標準行事,最常用的實現了這兩個標準的框架就是Jersey和CxF了。但是,因為Jersey是最早的實現,也是JSR311參考的主要對象,所以,可以說Jersey就是事實上的標準(類似Hibernate是JPA的事實上的標準),也是現在使用最為廣泛的REST開發框架之一。

因為eureka server採用了jersey,所以eureka client最終也是使用了配套的jersey client來和服務端通信。

所以,eureka client,裏面其實依賴了一堆jersey的包:

注意,上面的jersey-client、jersey-core等包,其group id都是這樣的:

    <dependency>
      <groupId>com.sun.jersey</groupId>
      <artifactId>jersey-client</artifactId>
      <version>1.19.1</version>
      <scope>runtime</scope>
    </dependency>

但是,不知道為啥,eureka client中,最終並沒有完全使用jersey-client,而是使用了

    <dependency>
      <groupId>com.sun.jersey.contribs</groupId>
      <artifactId>jersey-apache-client4</artifactId>
      <version>1.19.1</version>
      <scope>runtime</scope>
    </dependency>

這個包,內部引入了:

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.1.1</version>
        </dependency>

這個包,你可以簡單理解為,jersey-client變成了一個接口,jersey-apache-client4是它的一個實現,實現里,用了httpClient去實現。

httpClient,沒有幾個java同學不知道吧?這麼做是可行的,因為你最終通信,還是http,不管你服務端框架,是jersey、還是spring mvc、甚至以前的struts,這都不重要。

所以,大家在下面的源碼中看到jersey的時候,腦海里可以有這麼一張圖。從上層到底層的接口,分別是:

CloudEurekaClient
       ...
DiscoveryClient  
	...
EurekaClient
	...
JerseyClient
	...
HttpClient	

在此之前,我們還是先分析下eureka client 註冊到eureka server的源碼。

源碼環境

minidubbo代碼和相關博文在:
曹工說mini-dubbo(1)–為了實踐動態代理,我寫了個簡單的rpc框架
https://gitee.com/ckl111/mini-dubbo

代碼很簡單,不過還是給個代碼鏈接吧:

https://gitee.com/ckl111/all-simple-demo-in-work-1/tree/master/eureka-client

主要就是在pom.xml中,引入:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

然後啟動類:

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}

源碼分析

spring.factory支持自動配置

因為前面的pom,引入了如下jar包:

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-netflix-eureka-client</artifactId>
		</dependency>

該jar包的META-INF\spring.factories中,有如下幾行:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration

我們看到,key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,value是逗號分割的列表,這裏面都是需要被自動裝配的配置類,其中,我們看第三行的:

org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration

這個類,是自動裝配的配置類,我們可以簡單一覽:

@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@Import(DiscoveryClientOptionalArgsConfiguration.class)
@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
		CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
public class EurekaClientAutoConfiguration {

裏面一堆@ConditionalOn***,主要是看該配置類是否生效。

我們不管,這裏條件是滿足的,所以,看具體java文件里有什麼要裝配的內容,裏面內容較多,我們關注我們需要關注的:

		@Bean(destroyMethod = "shutdown")
		@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
		@Lazy
		public EurekaClient eurekaClient(ApplicationInfoManager manager,
				EurekaClientConfig config, EurekaInstanceConfig instance,
				@Autowired(required = false) HealthCheckHandler healthCheckHandler) {
			ApplicationInfoManager appManager;
			if (AopUtils.isAopProxy(manager)) {
				appManager = ProxyUtils.getTargetObject(manager);
			}
			else {
				appManager = manager;
			}
            // 1
			CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager,
					config, this.optionalArgs, this.context);
			cloudEurekaClient.registerHealthCheck(healthCheckHandler);
			return cloudEurekaClient;
		}

這裡會自動裝配一個EurekaClient類型的bean,(從返回值可以看出來),而具體的類型呢,從上面的1處,可以看出,具體類型是CloudEurekaClient。

所以,我們開始看1處,這個CloudEurekaClient是怎麼new出來的。

CloudEurekaClient的創建

先看看其繼承結構:

我們這個CloudEurekaClient,位於spring-cloud-netflix-eureka-client-2.1.5.RELEASE包。

而其父類DiscoveryClient和接口EurekaClient,位於eureka-client-1.9.13

大致能分析出,CloudEurekaClient的底層實現是eureka,其本身,是一個膠水,集成 spring 和 Netflix。

CloudEurekaClient的構造函數

	public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
			EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs<?> args,
			ApplicationEventPublisher publisher) {
        // 1
		super(applicationInfoManager, config, args);
        // 2
		this.applicationInfoManager = applicationInfoManager;
		this.publisher = publisher;
		this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class,
				"eurekaTransport");
		ReflectionUtils.makeAccessible(this.eurekaTransportField);
	}

我們看1處,調用了父類的構造函數;2處下面的幾行,主要是對本類中的幾個field進行賦值,這幾個字段,我們不關心,所以,直接看父類的構造函數吧。

DiscoveryClient的構造函數

    public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) {
        this(applicationInfoManager, config, args, ResolverUtils::randomize);
    }

    public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, EndpointRandomizer randomizer) {
        // 1
        this(applicationInfoManager, config, args, null, randomizer);
    }

上面兩個,都是重載。1處調用的,我們接下來會重點分析。

步驟1:一堆field賦值

DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
    	// 0	
        if (args != null) {
            this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
            this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
            this.eventListeners.addAll(args.getEventListeners());
            this.preRegistrationHandler = args.preRegistrationHandler;
        } else {
            this.healthCheckCallbackProvider = null;
            this.healthCheckHandlerProvider = null;
            this.preRegistrationHandler = null;
        }
        
        this.applicationInfoManager = applicationInfoManager;
        InstanceInfo myInfo = applicationInfoManager.getInfo();
		// 1
        clientConfig = config;
        staticClientConfig = clientConfig;
        transportConfig = config.getTransportConfig();
        instanceInfo = myInfo;
        if (myInfo != null) {
            appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
        } else {
            logger.warn("Setting instanceInfo to a passed in null value");
        }
		...

這一堆都是根據入數,來對類中的field進行賦值。比如

0處,主要是一些健康檢查的東西;1處,config類型為 com.netflix.discovery.EurekaClientConfig,這裏主要是eureka client的一些配置,比如我們在yml中配置了eureka.client.*之類的,就會到這裏。

步驟2:判斷是否要獲取eureka server中的服務提供者信息

	// 1
	if (config.shouldFetchRegistry()) {
            this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }

1處,可以看出來,是根據config中的shouldFetchRegistry進行判斷,是否要去獲取eureka server。

然後進行了一些監控指標的初始化。

步驟3:判斷是否要註冊到eureka server

    	// 1    
		if (config.shouldRegisterWithEureka()) {
            this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }

同上。

步驟4:如果既不註冊,也不獲取,則處理基本結束

		// 1
		if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
            logger.info("Client configured to neither register nor query for data.");
            scheduler = null;
            heartbeatExecutor = null;
            cacheRefreshExecutor = null;
            eurekaTransport = null;
            instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());

            DiscoveryManager.getInstance().setDiscoveryClient(this);
            DiscoveryManager.getInstance().setEurekaClientConfig(config);
			// 2
            return;  // no need to setup up an network tasks and we are done
        }
  • 1處,既不註冊,也不從eureka server獲取
  • 2處,直接結束

步驟5:定義三個線程池

            //1 default size of 2 - 1 each for heartbeat and cacheRefresh
            scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());
			// 2
            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff
			// 3 
			cacheRefreshExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff
  • 1處,定義一個用於服務提供者信息的緩存刷新的定時線程池
  • 2處,定義一個心跳線程池
  • 3處,這個看起來也是用於緩存刷新的

步驟6:創建eurekaTransport對象

com.netflix.discovery.DiscoveryClient#scheduleServerEndpointTask
// 1
eurekaTransport = new EurekaTransport();
// 2
scheduleServerEndpointTask(eurekaTransport, args);
  • 1處,eurekaTransport是一個field,該類主要封裝了幾個後續通信要使用的底層client。

    
        private static final class EurekaTransport {
            private ClosableResolver bootstrapResolver;
            private TransportClientFactory transportClientFactory;
    		// 1.1
            private EurekaHttpClient registrationClient;
            private EurekaHttpClientFactory registrationClientFactory;
    		// 1.2
            private EurekaHttpClient queryClient;
            private EurekaHttpClientFactory queryClientFactory;
    

    1.1處,這個應該是註冊用的,也是我們需要的;

    1.2處,應該是查詢信息用的。

  • 調用了當前類的方法scheduleServerEndpointTask,且把eurekaTransport傳入了

步驟7:schedule周期任務

創建抽象工廠

因我們只是new了eurekaTransport,沒有對其field進行任何賦值,所以,這個scheduleServerEndpointTask總,有個地方對其field進行賦值。

com.netflix.discovery.DiscoveryClient#scheduleServerEndpointTask
// 1
TransportClientFactories transportClientFactories =new Jersey1TransportClientFactories();

// 2
eurekaTransport.transportClientFactory = transportClientFactories.newTransportClientFactory(clientConfig, additionalFilters, applicationInfoManager.getInfo(), sslContext, hostnameVerifier)

  • 1處,就是new了一個抽象工廠,抽象工廠,我個人理解是工廠的工廠,其產出的東西,不是直接的最終對象,而是另一種工廠。

    TransportClientFactories 是一個接口,主要包含了如下方法:

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

    擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

    	public TransportClientFactory newTransportClientFactory(
            	final EurekaClientConfig clientConfig,
            	final Collection<F> additionalFilters,
                final InstanceInfo myInstanceInfo,
                final Optional<SSLContext> sslContext,
                final Optional<HostnameVerifier> hostnameVerifier);
    

    主要5個參數,排除掉最後的倒數2個,可選參數,剩3個。分別是:eurekaClient的配置bean,額外的filter集合,當前實例信息。

具體工廠的職責

  • 2處,就是利用1處創建的抽象工廠,來生成我們需要的工廠。

    這裏,我們可以先看看,最終我們需要的工廠,是什麼樣的。

    /**
     * A low level client factory interface. Not advised to be used by top level consumers.
     *
     * @author David Liu
     */
    public interface TransportClientFactory {
    
        EurekaHttpClient newClient(EurekaEndpoint serviceUrl);
    
        void shutdown();
    
    }
    

    newClient這個方法,聽名字,就是一個創建客戶端的,創建客戶端,需要什麼參數呢?總得知道要連接到哪個eureka server服務器吧,服務器地址是啥吧?沒錯,參數EurekaEndpoint serviceUrl可以給我們提供需要的這些:

    package com.netflix.discovery.shared.resolver;
    
    public interface EurekaEndpoint extends Comparable<Object> {
    	// 1
        String getServiceUrl();
    	// 2
        String getNetworkAddress();
    	// 3
        int getPort();
    
        boolean isSecure();
    
        String getRelativeUri();
    
    }
    
    
    • 1處,獲取url
    • 2處,獲取網絡地址
    • 3處,獲取端口

    基本對於我們一個客戶端來說,需要的參數就這些。

    說完了newClient的參數,再來看看響應:

    
    /**
     * Low level Eureka HTTP client API.
     *
     * @author Tomasz Bak
     */
    public interface EurekaHttpClient {
    
        EurekaHttpResponse<Void> register(InstanceInfo info);
    
        EurekaHttpResponse<Void> cancel(String appName, String id);
    
        EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus);
    
        EurekaHttpResponse<Void> statusUpdate(String appName, String id, InstanceStatus newStatus, InstanceInfo info);
    
        EurekaHttpResponse<Void> deleteStatusOverride(String appName, String id, InstanceInfo info);
    
        EurekaHttpResponse<Applications> getApplications(String... regions);
    
        EurekaHttpResponse<Applications> getDelta(String... regions);
    
        EurekaHttpResponse<Applications> getVip(String vipAddress, String... regions);
    
        EurekaHttpResponse<InstanceInfo> getInstance(String appName, String id);
    
        EurekaHttpResponse<InstanceInfo> getInstance(String id);
    
        void shutdown();
    }
    

    看到了嗎,各種註冊、取消、發送心跳、狀態更新啥的,這幾本涵蓋了eureka client的所有操作了,沒錯,我們就是需要這麼個東西。

創建具體工廠

看完了我們需要的工廠的功能,我們馬上來看看這麼厲害的工廠怎麼創建出來?

com.netflix.discovery.shared.transport.jersey.Jersey1TransportClientFactories#newTransportClientFactory(...)
        
	@Override
    public TransportClientFactory newTransportClientFactory(
        	EurekaClientConfig clientConfig,
            Collection<ClientFilter> additionalFilters,
        	InstanceInfo myInstanceInfo,
        	Optional<SSLContext> sslContext,
        	Optional<HostnameVerifier> hostnameVerifier) {
    	// 2.1
        final TransportClientFactory jerseyFactory = JerseyEurekaHttpClientFactory.create(
                clientConfig,
                additionalFilters,
                myInstanceInfo,
                new EurekaClientIdentity(myInstanceInfo.getIPAddr()),
                sslContext,
                hostnameVerifier
        );
        // 2.2
        final TransportClientFactory metricsFactory = MetricsCollectingEurekaHttpClient.createFactory(jerseyFactory);
		// 2.3
        return new TransportClientFactory() {
            @Override
            public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) {
                return metricsFactory.newClient(serviceUrl);
            }

            @Override
            public void shutdown() {
                metricsFactory.shutdown();
                jerseyFactory.shutdown();
            }
        };
    }
  • 2.1處,調用JerseyEurekaHttpClientFactory的create 靜態方法,生成了一個工廠
  • 2.2處,對生成的工廠,進行了包裝,看名稱,應該是包裝了統計相關信息。
  • 2.3處,對2.2處生成的工廠,用匿名內部類進行了包裝,調用匿名內部類的newClient時,直接代理給了metricsFactory;而shutdown方法,則主要是關閉 metricsFactory 和 jerseyFactory 工廠。

所以,我們現在要看看,2.1處,是怎麼創建工廠的。

com.netflix.discovery.shared.transport.jersey.JerseyEurekaHttpClientFactory#create
    
    public static JerseyEurekaHttpClientFactory create(
    	EurekaClientConfig clientConfig,
        Collection<ClientFilter> additionalFilters,
    	InstanceInfo myInstanceInfo,                                                       		 AbstractEurekaIdentity clientIdentity) {
        // 1
    	boolean useExperimental = "true".equals(clientConfig.getExperimental("JerseyEurekaHttpClientFactory.useNewBuilder"));
		// 2
        JerseyEurekaHttpClientFactoryBuilder clientBuilder = (useExperimental ? experimentalBuilder() : newBuilder())
                .withAdditionalFilters(additionalFilters)
                .withMyInstanceInfo(myInstanceInfo)
                .withUserAgent("Java-EurekaClient")
                .withClientConfig(clientConfig)
                .withClientIdentity(clientIdentity);
    	// 3
        clientBuilder.withClientName("DiscoveryClient-HTTPClient");
		// 4
        return clientBuilder.build();
    }
  • 1處,砍斷是否要使用實驗性的builder
  • 2處,創建對應的builder,並把我們的參數,通過with*方法,設置進去
  • 3處,設置客戶端名稱
  • 4處,生成客戶端工廠
com.netflix.discovery.shared.transport.jersey.JerseyEurekaHttpClientFactory.JerseyEurekaHttpClientFactoryBuilder#build

    
        @Override
        public JerseyEurekaHttpClientFactory build() {
    		// 1
            Map<String, String> additionalHeaders = new HashMap<>();
    		// 2
            if (allowRedirect) {
                additionalHeaders.put(HTTP_X_DISCOVERY_ALLOW_REDIRECT, "true");
            }
            if (EurekaAccept.compact == eurekaAccept) {
                additionalHeaders.put(EurekaAccept.HTTP_X_EUREKA_ACCEPT, eurekaAccept.name());
            }
			
            // 3
            return buildLegacy(additionalHeaders, systemSSL);
        }

這裏就是弄了個hashmap,設置了幾個header進去,然後3處,調用buildLegacy。

com.netflix.discovery.shared.transport.jersey.JerseyEurekaHttpClientFactory.JerseyEurekaHttpClientFactoryBuilder#buildLegacy
    
        private JerseyEurekaHttpClientFactory buildLegacy(Map<String, String> additionalHeaders, boolean systemSSL) {
    		// 1
            EurekaJerseyClientBuilder clientBuilder = new EurekaJerseyClientBuilder()
                    .withClientName(clientName)
                    .withUserAgent("Java-EurekaClient")
                    .withConnectionTimeout(connectionTimeout)
                    .withReadTimeout(readTimeout)
                    .withMaxConnectionsPerHost(maxConnectionsPerHost)
                    .withMaxTotalConnections(maxTotalConnections)
                    .withConnectionIdleTimeout((int) connectionIdleTimeout)
                    .withEncoderWrapper(encoderWrapper)
                    .withDecoderWrapper(decoderWrapper);
			...
            
			// 2
            EurekaJerseyClient jerseyClient = clientBuilder.build();
    		// 3
            ApacheHttpClient4 discoveryApacheClient = jerseyClient.getClient();
            addFilters(discoveryApacheClient);
			// 4
            return new JerseyEurekaHttpClientFactory(jerseyClient, additionalHeaders);
        }
  • 1處,通過我們傳入的一些參數,以及該類自身的一些field,比如connectionTimeout、readTimeout、maxTotalConnections、maxConnectionsPerHost這些,構造一個builder。

    這些參數,已經看出來,是網絡通信所需要的東西了

  • 2處,通過1處的builder,調用build,拿到了EurekaJerseyClient類型的對象,可以說,這裏其實是已經把客戶端構造好了。也就是說,在構造這個工廠的過程中,其實已經在生成對應的產品了

  • 3處,對2處拿到的客戶端,做一些處理

  • 4處,將2處拿到的客戶端,封裝到了工廠的一些field中,後續調用工廠生產產品的時候,直接從field中取就行了。

        public JerseyEurekaHttpClientFactory(EurekaJerseyClient jerseyClient, Map<String, String> additionalHeaders) {
            this(jerseyClient, null, -1, additionalHeaders);
        }
    	private JerseyEurekaHttpClientFactory(EurekaJerseyClient jerseyClient,
                                              ApacheHttpClient4 apacheClient,
                                              long connectionIdleTimeout,
                                              Map<String, String> additionalHeaders) {
            this.jerseyClient = jerseyClient;
            this.apacheClient = jerseyClient != null ? jerseyClient.getClient() : apacheClient;
            this.additionalHeaders = additionalHeaders;
        }
    

所以,我們的重點,要放在2處的build身上。

	com.netflix.discovery.shared.transport.jersey.EurekaJerseyClientImpl.EurekaJerseyClientBuilder#build
	public EurekaJerseyClient build() {
            MyDefaultApacheHttpClient4Config config = new MyDefaultApacheHttpClient4Config();
            try {
                // 1
                return new EurekaJerseyClientImpl(connectionTimeout, readTimeout, connectionIdleTimeout, config);
            } catch (Throwable e) {
                throw new RuntimeException("Cannot create Jersey client ", e);
            }
        }

接下來看1處:

public EurekaJerseyClientImpl(int connectionTimeout, int readTimeout, final int connectionIdleTimeout,ClientConfig clientConfig) {
        try {
            jerseyClientConfig = clientConfig;
            // 1
            apacheHttpClient = ApacheHttpClient4.create(jerseyClientConfig);
            // 2
            HttpParams params = apacheHttpClient.getClientHandler().getHttpClient().getParams();

            HttpConnectionParams.setConnectionTimeout(params, connectionTimeout);
            HttpConnectionParams.setSoTimeout(params, readTimeout);
			
        } catch (Throwable e) {
            throw new RuntimeException("Cannot create Jersey client", e);
        }
    }
  • 1處,創建com.sun.jersey.client.apache4.ApacheHttpClient4類型的對象

    該類型,就位於:

        <dependency>
          <groupId>com.sun.jersey.contribs</groupId>
          <artifactId>jersey-apache-client4</artifactId>
          <version>1.19.1</version>
          <scope>runtime</scope>
        </dependency>
    
    
        public static ApacheHttpClient4 create(final ClientConfig cc) {
            return new ApacheHttpClient4(createDefaultClientHandler(cc), cc);
        }
    

    這裏的createDefaultClientHandler(cc),裏面會去創建HttpClient。

    private static ApacheHttpClient4Handler createDefaultClientHandler(final ClientConfig cc) {
    		...
    
    		// 1
            final DefaultHttpClient client = new DefaultHttpClient(
                    (ClientConnectionManager)connectionManager,
                    (HttpParams)httpParams
            );
    
            ...
    		
            return new ApacheHttpClient4Handler(client, cookieStore, preemptiveBasicAuth);
        }
    

    這裏面細節省略了部分,主要就是1處,創建了HttpClient,這個就是平時我們用來發http請求的那個。

  • 2處,設置一些參數,這裏的HttpParams,從哪兒取出來的?apacheHttpClient.getClientHandler().getHttpClient()。這裏取到的,已經是HttpClient了。

    到此為止,我們可以看看httpParams中有哪些header:

在具體工廠基礎上,對註冊用的工廠進行封裝

        com.netflix.discovery.DiscoveryClient#scheduleServerEndpointTask
        // 1    
		if (clientConfig.shouldRegisterWithEureka()) {
            EurekaHttpClientFactory newRegistrationClientFactory = null;
            EurekaHttpClient newRegistrationClient = null;
            // 2
            newRegistrationClientFactory = EurekaHttpClients.registrationClientFactory(
                eurekaTransport.bootstrapResolver,
                eurekaTransport.transportClientFactory,
                transportConfig
            );
            // 3
            newRegistrationClient = newRegistrationClientFactory.newClient();
            // 4
            eurekaTransport.registrationClientFactory = newRegistrationClientFactory;
            eurekaTransport.registrationClient = newRegistrationClient;
        }

我們前面的n步,已經把通信用的客戶端,及對應的工廠,都已經創建出來了,為啥這裏又要創建什麼工廠。

簡單來說,前面的工廠,造出來的客戶端,通信是沒問題了;但是,你通信失敗了,要重試嗎,重試的話,換哪一台呢?你每次通信是成功,還是失敗,還是超時,需要統計嗎?一個生產級的框架,是要有這些功能的。

所以,這裏主要是進行一些上層的封裝。

ok,繼續分析上面的代碼。

  • 1處,判斷是否要註冊到eureka
  • 2處,生成一個工廠,該工廠負責生產:註冊用的客戶端
  • 3處,使用2處拿到的工廠,創建註冊用的客戶端
  • 4處,把3處拿到的客戶端,存儲到eurekaTransport的field中。

繼續深入2處。

    com.netflix.discovery.shared.transport.EurekaHttpClients#canonicalClientFactory
	static EurekaHttpClientFactory canonicalClientFactory(
        final String name,
        final EurekaTransportConfig transportConfig,
        final ClusterResolver<EurekaEndpoint> clusterResolver,
        final TransportClientFactory transportClientFactory) {
		// 1
        return new EurekaHttpClientFactory() {
            // 2
            @Override
            public EurekaHttpClient newClient() {
                // 3
                return new SessionedEurekaHttpClient(
                        name,
                        RetryableEurekaHttpClient.createFactory(...),
                        transportConfig.getSessionedClientReconnectIntervalSeconds() * 1000
                );
            }

            @Override
            public void shutdown() {
                wrapClosable(clusterResolver).shutdown();
            }
        };
    }
  • 1處,返回了一個工廠對象
  • 2處,工廠里重寫了newClient
  • 3處,返回了一個包裝過的EurekaClient。

可以看下這裏返回的SessionedEurekaHttpClient類。

這裏就是裝飾器模式,對enreka進行了層層封裝,和 java 的 io 流那樣理解就對了。

在具體工廠基礎上,對查詢用的工廠進行封裝

		// 1
		if (clientConfig.shouldFetchRegistry()) {
            EurekaHttpClientFactory newQueryClientFactory = null;
            EurekaHttpClient newQueryClient = null;
            // 2
            newQueryClientFactory = EurekaHttpClients.queryClientFactory(
                eurekaTransport.bootstrapResolver,
                eurekaTransport.transportClientFactory,
                clientConfig,
                transportConfig,
                applicationInfoManager.getInfo(),
                applicationsSource,
                endpointRandomizer
            );
            // 3
            newQueryClient = newQueryClientFactory.newClient();
            eurekaTransport.queryClientFactory = newQueryClientFactory;
            eurekaTransport.queryClient = newQueryClient;
        }

這裏的代碼,和上面基本相似。只不過,這裡是給查詢用的,所謂查詢,就是去eureka server獲取信息,比如服務提供者列表啥的。

  • 1處,判斷是否要去eureka server獲取
  • 2處,創建查詢用的工廠
  • 3處,利用2處拿到的工廠,創建查詢客戶端

步驟8:去eureka server獲取服務提供者信息

我們終於把步驟7講完了,實在有點長。

com.netflix.discovery.DiscoveryClient#DiscoveryClient(...)
    
// 1    
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
    // 2
    fetchRegistryFromBackup();
}

這裏1處,就是判斷要不要去獲取,如果要的話,就調用fetchRegistry(false)

2處,如果1處沒取到,則要從backup地方去取。這塊可以自己定製backup策略。

註冊到eureka server

        if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
            // 1
            if (!register() ) {
                throw new IllegalStateException("Registration error at startup. Invalid server response.");
            }
        }

這裡會判斷是否要註冊,是否要在初始化的時候註冊,如果要的話,進入1處,進行註冊。

初始化周期執行的任務

        // finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
        initScheduledTasks();

看這裏註釋,初始化的任務包括:集群解析、心跳、實例信息註冊、周期從eureka server獲取信息等。

周期任務:獲取服務提供者信息

if (clientConfig.shouldFetchRegistry()) {
            // registry cache refresh timer
            int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
            int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "cacheRefresh",
                            scheduler,
                            cacheRefreshExecutor,
                            registryFetchIntervalSeconds,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new CacheRefreshThread()
                    ),
                    registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }

默認30s一次。

周期任務:定時發心跳,向eureka server進行renew

            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

            // Heartbeat timer
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);

這個也是30s。

心跳包,基本就是個put請求,裏面攜帶了2個參數。

@Override
    public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
        String urlPath = "apps/" + appName + '/' + id;
        ClientResponse response = null;
        try {
            WebResource webResource = jerseyClient.resource(serviceUrl)
                    .path(urlPath)
                    .queryParam("status", info.getStatus().toString())
                    .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());

周期任務:InstanceInfoReplicator

這個任務,默認也是30s執行一次。

            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize

這個任務,其實現了runnable,註釋如下:


/**
 * A task for updating and replicating the local instanceinfo to the remote server. Properties of this task are:
 * - 1 configured with a single update thread to guarantee sequential update to the remote server
 * - 2 update tasks can be scheduled on-demand via onDemandUpdate()
 * - 3 task processing is rate limited by burstSize
 * - 4 a new update task is always scheduled automatically after an earlier update task.  However if an on-demand task is started, the scheduled automatic update task is discarded (and a new one will be scheduled after the new
 *   on-demand update).
 *
 *   @author dliu
 */
class InstanceInfoReplicator implements Runnable 
  • 1處,配置了一個單線程,保證向遠程eureka server,順序更新
  • 2處,通過本類的onDemandUpdate,可以強行插入一個任務,而無需通過定時執行
  • 3處,限流相關
  • 4處,執行完一個周期任務后,馬上會給自己安排下一個周期任務

其run方法:

    public void run() {
        try {
            // 1
            discoveryClient.refreshInstanceInfo();

            Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
            if (dirtyTimestamp != null) {
                // 2
                discoveryClient.register();
                instanceInfo.unsetIsDirty(dirtyTimestamp);
            }
        }finally {
            // 3
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }
  • 1處,刷新實例信息
  • 2處,如果有需要的話,向eureka server進行註冊
  • 3處,調度下一次任務

初始化結束

基本,這個CloudEurekaClient構造就結束了,後續就依靠其開啟的一堆定時任務去進行工作。

總結

eureka client的初始化就講了這麼多,註冊還沒講,留帶下一講吧。

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

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

Python裝飾器的一點解讀_貨運

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

  版權申明:本文為博主窗戶(Colin Cai)原創,歡迎轉帖。如要轉貼,必須註明原文網址

  http://www.cnblogs.com/Colin-Cai/p/12977127.html 

  作者:窗戶

  QQ/微信:6679072

  E-mail:6679072@qq.com

  理論上,函數是一等公民(first class function)的語言都可以使用函數式編程,從而利用算子(高階函數)來做裝飾器。

  裝飾器一般是這樣一個算子,它接受一個函數作為參數,返回另外一個函數。裝飾器,顧名思義,就是把一個函數“裝飾”一下,得到另外一個函數。為何要裝飾一下呢?目的一般是可能設計上需要對函數做一些改裝,比如原函數輸出結果需要再加工加工,或者原函數的輸入參數傳入不一樣,或者兩者兼有之,等等。

 

  迭代是編程中常用的手段,它的計算方式表現為狀態的不斷變換,且狀態的變換具有唯一性。

  比如我們使用Scheme來表示迭代。

;stat代表當前狀態,next代表狀態改變函數,final?代表判斷狀態是否終止
(define (iterate-orgin stat next final?)
  (if (final? stat)
      stat
      (iterate-orgin (next stat) next final?)))

;將next函數和final?函數封成一個函數
(define (iterate stat f-stat)
  (iterate-orgin stat (f-stat 'next) (f-stat 'final)))

;最終我們需要的迭代函數
(define (it f-stat)
  (lambda (stat) (iterate stat f-stat)))

  

  以上構造出一個算子it,就是用來“裝飾”迭代的函數。

  我們構造一個對list求和的迭代:

  可以每次把list的前面兩個相加,比如對(1 2 3 4 5)求和,經過以下狀態:

  (1 2 3 4 5)

  (3 3 4 5)

  (6 4 5)

  (10 5)

  (15)

  15

  得到最後結果15。

  代碼可以如下:

(define (make-sum-func sym)
 (if (eq? sym 'next);next函數
  (lambda (lst)
   (if (pair? lst)
    (if (null? (cdr lst))
     (car lst)
     (cons (+ (car lst) (cadr lst)) (cddr lst)))
    lst))
  (if (eq? sym 'final?);final?函數
   (lambda (lst) (not (pair? lst)))
   '())))

 

  然後測試一下((it make-sum-func) ‘(1 2 3 4 5)),得到最後結果15。

  上面兩個函數寫在一起,我們還可以再分離一下。

  

;定義一個打包函數
(define (wrap-next-final next final?)
 (lambda (sym)
  (if (eq? sym 'next)
   next
   (if (eq? sym 'final?)
    final?
    '()))))

;下面next和final?兩個函數可以分開寫
(define make-sum-next
 (lambda (lst)
  (if (pair? lst)
   (if (null? (cdr lst))
    (car lst)
    (cons (+ (car lst) (cadr lst)) (cddr lst)))
   lst)))

(define make-sum-final?
 (lambda (lst) (not (pair? lst))))

;於是函數就可以下面這樣表示
(define make-sum-func (wrap-next-final make-sum-next make-sum-final?))

 

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

  總而言之,裝飾器就是這樣一類算子。

 

  Python也是這樣,只是Python提供了@這樣的語法,實際上是個語法糖,與其說是簡寫,倒是更像是個語法提醒這是一個裝飾器。

  我們這次希望來显示一下mysym,還是用求和。

  先寫一個簡單的mysum函數用於求和: 

def mysum(*args):
    ret = 0
    for i in args:
        ret += i
    return ret

 

  做一個算子,來擴充它的輸入參數:

  這裏需要用來判斷一個對象是否是可迭代對象,

  from collections import Iterable

  然後,如果判斷對象x是否是可迭代對象,只需要:

  isinstance(x, Iterable)

  算子如下:

from collections import Iterable
def args_expan(f):
    def f2(*args):
        lst = []
        for i in args:
            if isinstance(i, Iterable):
                lst.append(f(*i))
            else:
                lst.append(i)
        return f(*lst)
    return f2

 

  然後在mysum前添加裝飾器標誌

@args_expan
def mysum(*args):
    ret = 0
    for i in args:
        ret += i
    return ret

 

  測試如下:

print(mysum(1,2,3,4,5))
print(mysum((1,2,3,4,5)))
print(mysum([1,2,3,4,5]))
print(mysum(range(1,6)))
print(mysum(map(lambda x:x+1, range(5))))
print(mysum(filter(lambda x:x<6, range(10))))

#構造了一個生成器
def gen_range(a, b):
    while a < b:
        yield a
        a += 1

print(mysum(\
    filter(lambda x:x<6, range(10)), \
    6, \
    [7,8], \
    (9, 10), \
    map(lambda x:x+11, range(10)), \
    gen_range(21,101)))

 

  運行得到:

15
15
15
15
15
15
5050

  終於看到,函數功能已被擴充。

  這個裝飾器還可以裝飾別的函數,比如乘積、統計等。

 

  從而,裝飾器就是這樣一個算子,一般用來改造函數的輸入或輸出,避免重複寫代碼。

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

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

春節從冬至開始_網頁設計公司

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

晉城麵塑 資料圖片

臘月里蒸麥芽饃 資料圖片

臘月里蒸麥芽饃 資料圖片

過年是從冬至開始的。從冬至到臘八,從臘八到小年,從小年到大年,人們沿襲着祖先傳下來的風俗,操持着,準備着,迎接着,春節的氣氛也在幸福的忙碌中漸漸達到頂點。年俗是文化,也是人情,生生不息,代代相傳。

夫冬至之節,陽氣始萌

“天時人事日相催,冬至陽生春又來。”這是杜甫的七律《小至》的兩句詩。杜工部是對的,春天是隨着冬至來到人間的。

如果認為春節代表春天的到來,那就錯了。其實我們所過的春節僅僅只是春天的一個節日。春節到來的時候,人間早已經屬於春天了。春節只是給了我們一個春天已經來到人間的強烈感覺。或者說,春節只是慶祝春天已經來到人間的一個隆重儀式。

而真正意義上的春天,是從冬至開始的。我們在這裏所說“真正意義”,是指中國古代哲學中的實在意義。我們的先哲們認為,宇宙的萬事萬物無不在陰陽交替、互動、對立、統一中生存和消長。整個世界,是由陰和陽組成的。陰與陽,渾藏於天地間,是中國古代哲學的文化內核。陰與陽也因此易位於冬至和夏至兩個節日之間。夏至之後,日漸短,夜漸長,陰漸盛,陽漸衰,所有事物,所有生命,都在漸趨低谷,即“萬物收斂”。冬至之後,晝漸長,夜漸短,“一天長一線,十天長一截”。然而,冬至過後,天氣會一天比一天冷。從冬至節那一天起,就開始了數九天。“一九二九,關門袖手”“三九四九,凍破碓臼”,天氣的確是越來越冷了。

“夫冬至之節,陽氣始萌。”雖然正值數九寒天,春潮卻在暗中涌動。陽氣在暗中上升,物候的腳步在不知不覺中邁向了春天。春如初胎,從冬至那天起,已經沉靜地安附於大自然的宮室里,正如《西遊記》中邵康節所說:“冬至子之半,天心無改移。一陽初動處,萬物未生時。”“五九六九,沿河看柳。”“七九河開,八九雁來。”“九九加一九,耕牛遍地走。”說明陽氣在暗中悄悄上升,春天在不聲不響走近人間。這一點也被羅貫中寫進了《三國演義》。程昱入告曹操:“今日東南風起,宜預提防。”操笑曰:“冬至一陽生,來複之時,安得無東南風?”把一場赤壁之戰放在冬至,顯得波瀾壯闊,意味無窮。

中國“四大名著”無不拿冬至說事。曹雪芹在《紅樓夢》更是把冬至說到特別:“這年正是十一月三十日冬至。到交節的那幾日,賈母、王夫人、鳳姐兒日日差人去看秦氏,回來的人都說:‘這幾日也沒見添病,也不見甚好。’王夫人向賈母說:‘這個癥候,遇着這樣大節不添病,就有好大的指望了。’”說明冬至是個不同尋常節令,不光宇宙間萬物在蘇醒、發生、成長,就連病疽疾癘也一樣不會放過其生命活躍起來的機會。

《水滸傳》似乎沒有必要寫到冬至,但施耐庵並沒有放過這一個重要節點:“聞知今上大張燈火,與民同樂,慶賞元宵。自冬至后,便造起燈,至今才完。”我們先不說作者在敘事中來上這麼一筆的用意是什麼,施耐庵給了我們一個信息:春節乃至元宵節都是從冬至開始的。

其實春節不只是從冬至開始的,春節在中國古代與冬至是並肩同行的。

《詩經·七月》:“七月流火,九月授衣。一之日觱發,二之日栗烈。無衣無褐,何以卒歲?三之日於耜,四之日舉趾。”“一之日”是周曆和豳歷的正月。而周曆和豳歷的正月正是夏曆的十一月,就是我們現在所過的冬至的月份。而今的冬至在十一月末,或十二月初,春節則在正月初一,之間相隔時間近一個月,這並不是春節與冬至分道揚鑣,而是我們選擇了夏曆,即農曆。所以,春節是從冬至開始的,有它的歷史淵源,也有它的文化根據。

摔老南瓜

今天,儘管春節姍姍來遲於冬至之後,但人們並沒有忘記春節是從冬至開始的。尤其是在我的家鄉晉東南,依然回蕩着歷史的流響餘韻,也總是忘不了想方設法給冬至塗上一點文化色彩,讓冬至節沉染在豐富的文化意蘊中。

冬至節的五更天,天還不明,就能聽到各家各戶“嗵嗵”的響聲傳出來。那是家家都在摔老南瓜。秋天收穫老南瓜的時候,家家都要挑好的大的收藏起來,等到冬至的五更天,把老南瓜從架上取下來,抱到炕頭上,高高舉起,重重地摔到地上,南瓜子濺得到處都是。驚醒的孩子們會問父母,冬至為什麼摔老南瓜?父母會告訴孩子們冬至摔老南瓜的故事:

傳說有人要在冬至那天夜裡刺殺孔子,藏匿孔子那一家人,暗中放走了孔子,在孔子睡覺的枕頭上放了個老南瓜,用被子蓋上,偽裝成孔子正在睡覺。刺客夜入寢室,舉刀便砍,只聽“咔嚓”一聲,半個老南瓜“嗵”的一聲滾落到地上,刺客以為大功造成。於是,冬至摔老南瓜就成了祀孔的一種特殊形式。

冬至節,村子里和小學校也要祀孔。冬至那天學校放假一天,說是放假,學生並不離開學校,只是不準翻書而已。意思是,翻書會迷了孔夫子的眼。學生要穿上新衣,跟着村幹部和老師攜酒脯祀孔。祀孔結束后,村幹部陪老師吃油疙麻雜燴菜,學生回家吃餃子。我們把餃子叫“凍耳朵”,意思是,吃了凍耳朵,耳朵不怕凍壞。

冬至摔成碎塊的老南瓜,可以煮成“紅小豆老南瓜”吃。色質特別好的,留給“五豆”和“臘八”。臘月初五是“五豆節”,小節。家家吃紅豆小米撈飯,即用紅豆和小米一起燜的乾飯。五豆是冬至到臘八的一個小小跳板。

臘八粥

臘月初八,家家吃“臘八粥”。

“粥”是外鄉人的叫法,我們不叫粥,實質上也不是粥。我們叫黏米飯,也叫黏飯,又叫甜飯,實實在在的“飯”,能夠用筷子夾起來的。黏米與小米顏色一樣,黃燦燦的,顆粒大小也相同,外地人用眼睛是分辨不出來的。黏米做成的“黏米飯”特別“黏”,特別香。

臘月初七晚上,母親就要煮紅豆。紅紅的蘭花炭火,煮豆的鍋在火上嘩啦嘩啦響,孩子們的夢裡也是黏米飯香。

半夜時分,豆子煮熟了,母親會將老南瓜、黏米、紅薯、紅棗、核桃、柿餅、柿圪蓮,花生、紅糖、白糠等依次入鍋。什麼東西什麼時間放,既不能早,也不能遲。比如花生,早了不脆,遲了不熟;比如柿餅柿圪蓮,時間不對,不但不甜,還會發澀。做黏米飯最好用砂鍋,攪黏米飯最好用木板。用鐵傢伙做出來的黏米飯顏色不鮮明,吃起來味道也不對。對着黏米飯鍋不能說話,不能咳嗽,如果把唾沫星兒噴到黏米飯里,黏米飯就會“涎”成米是米、水是水,不融和,也不黏稠。

早晨,軟米飯做好了,各家各戶要相互饋贈。送贈黏米飯的任務大都是孩子們的事。一碗碗紅紅的黏米飯,像一盞盞的小紅燈籠,像一捧捧的火焰,像一朵朵鮮艷的花,在飛雪中,在白雪皚皚的大街小巷裡,燃燒,跳躍,綻放,如竄梭一般地來來去去。每碗黏米飯都是滿滿的,偶爾會滴一點在雪地上,像掉在雪地上的一塊紅紅的火炭,像一枝火的花朵,讓雪天顯得溫暖,讓雪天顯得爛漫,點燃着鄉村裡永遠的鄉愁。

家家戶戶都做黏米飯,家家戶戶相互饋贈,那是一種風俗。你送我一碗,我送你一碗,“親戚箢還箢,鄰家碗還碗”。那是鄉里鄉親熱絡感情、鞏固關係的一種最簡單、最樸素的一種方式。千年不變,也不應該變。

學生都要給老師送軟米飯,老師收很多黏米飯,凍起來,天天拿鏊子焐着吃,放一點油,焐出一層薄薄的皮,又香又甜,天下風味。

也有送黏米飯給村子里的藥鋪商鋪的,藥鋪商鋪也會準備一些核桃、紅棗、花生、柿餅之類回贈。

為什麼要吃臘八粥?為什麼要吃黏米飯?歷來傳說不一。但最好緣由應該是紀念佛祖成道日,效法牧女獻乳糜於佛的故事。不過,這也是人們賦予臘八節的一種文化色調。我看,其蘊包含在《詩經》里。

《詩經》有言:“物其多矣,維其嘉矣。物其旨矣,維其偕矣。物其有矣,維其時矣。”冬至時節,乃至整個臘月天,各種糧食果蔬藏儲豐富,是乃“物其多矣”“物其旨矣”“物其有矣”;選擇節日,如冬至、五豆、臘八、小年,做成合節的諸如黏米飯、豆撈飯、火燒之類的飯食,乃是“維其嘉矣”“維其偕矣”“維其時矣”。也就說,臘八吃黏米飯才有臘八氣氛,才是臘八的味道。也如八月十五吃月餅,三月三吃春餅,端午節吃粽子,此即“物其有矣,維其時矣”。

冬至說儒,臘八說佛,小年以至大年,就應該說“道”了。佛、儒、道,三家文化,融化了我家鄉的臘月天。

過了臘八,接着就是小年。臘月二十三,祭灶的日子到了。

祭灶祭哪個?祭祝融。《周禮》說:“顓頊氏有子日黎,為祝融,祀以為灶神。”漢之後,灶王爺掌握了人們的禍福、壽夭,出現了“灶神晦日歸天,白人罪”的說法。人們小心奉祀灶君,傳說灶君每年臘月二十三都要到天帝那兒去陳說人間的善與惡,因此各家各戶要為灶王爺送行,打些火燒給灶爺做乾糧,弄些麩皮草料給灶爺秣灶馬,在火口上塗些糖餳給老灶爺糊嘴巴,好讓老灶爺到天帝那兒多說好話,把天帝所降的吉祥帶回人間。

“二十三祭罷灶,夾上包袱往回趵。”住在娘家的媳婦,祭過灶之後,就該回到婆婆家準備過年了。

“好了!好了!到跟前兒都好了!”

過了冬至節,男人們忙着殺豬,宰羊,割肉,買菜,辦年貨。女人碾米,磨面,扯布,買衣線,做新衣,做新鞋。過了臘月二十三,就要掃屋子,做豆腐,坐蒸鍋,蹲油鍋。

晉城不缺香炭,火邊圍一圈香炭,把火燒得旺旺的,女人們各顯身手,蒸棗山、棗供、棗花、豬、羊、小兔兒、小魚兒、小刺蝟,炸菱花、饊花。所蒸所煮,都是敬神的供品。棗山像個大青蛙,背上許多面花兒。每朵花上都有一個紅棗,看起來像一朵盛開的大紅花。雜獻只是一個大饅頭,上邊摞幾層面花兒,最上面安一個大紅棗。比雜獻小一點的叫棗花兒,只有一層花,花上邊只是一個酸棗。棗山和雜獻上的花兒,有盤花,有剪花。盤花像牡丹,剪花像菊花。把筷子頭破開,析出一朵“小梅花”。在小小的瓷碟子里化一點品紅,把“小梅花”在品紅里蘸蘸,在棗山和雜獻上點上朵朵小梅花。將高粱秸破成兩半,摳去瓤子,便成為一個彎彎的月牙。把月牙兒蘸一點品綠,在“小梅花”的四角點四片,好花有了綠恭弘=叶 恭弘扶持,所有的獻供便飛一片春色。每朵花都有兩層三層,看上去重重疊疊,有點“層巒聳翠”的意思。

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

給女兒女婿蒸兩個大口禮饃,個子比頭盔大。把揉好的面用細繩從頂向下勒一個十字,蒸熟的口禮饃如四瓣盛開的花朵,點綴上品紅品綠,顯得喜氣洋洋,一片祥和。

給自己家裡每個人蒸一個“人口禮饃”,其中一個裡邊放個“制錢”,其餘放上一個紅棗。初七人日,誰拿到放“制錢”的口禮饃,誰運氣就是最好的,福氣也是最大的,大事小事都會成為家裡的靠山。

煮一鍋玉茭豆兒,蒸些豆餡饃、菜餡饃。從初二到初五,每天早晨喝玉茭豆兒,吃餡饃。

為了一個年節,家家戶戶都會忙得團團轉,腳步總是匆匆的。人與人見了面總要關切地問一聲:“準備好了嗎?”總是笑着回答:“好了!好了!到跟前兒都好了!”

洗了澡,理了發。糊了窗戶,架起正火。貼上對聯,掃了院子,就到大年三十了。

“二十三日去,初一五更回”

三十的晚上,家家戶戶吃“面恭弘=叶 恭弘”。脫玉茭、大白菜、紅蘿蔔、豆腐、豆芽,一鍋煮熟。寬寬的麵條,不放蔥蒜,只放花椒,因為要獻祖先。先盛一碗獻祖宗。燃上一炷香,大門口放三個炮,接祖宗回家過年,祖先就會跟着香煙走進大門,走進屋門。祖先畢竟是泉鄉之物,沒有香煙引路,門神不會放祖先進入家門。

把祖先請到祖先桌上,給祖先獻上一碗面恭弘=叶 恭弘,俗稱“爺奶奶湯兒”,讓祖宗先喝口湯,歇歇氣,等五更天敬神時,再給祖宗上獻。

從臘月二十三到年三十,人間是沒有神的,諸神都到天帝那裡參加年會了。這一段時間,人間是無忌諱的,人們可以為所欲為,特別是結婚辦喜事,不用“擇好兒”,想哪天辦就哪天辦。

臘月三十,祖宗也回來了,家人與祖宗一起,早早就要睡覺了。睡覺時放一個“關門炮”,說明這一年已經過完了,關門了,再開門的時候就是春節了。

神回到人間的時間,是正月初一的五更天,是所謂的“二十三日去,初一五更回”。初一五更,起床先放一個開門炮,春節的大門從此就打開了。

不論農家、官家與商家,不論窮家與富家,家家供奉灶爺,供奉天地爺、家堂老爺、財神爺,其他便不相同了。有的家庭供奉不知名姓的神仙,總稱仙家老爺。很多人家都供奉佛爺,讀書人供奉孔夫子,木匠供奉魯班,鐵匠供奉老君。

其實行走在我們桑梓間的不止佛、儒、道,還有天地、山神、龍王、馬王、牛王、青龍、白虎、紫姑、門神、樹神、花神、五道神、土地神,等等。當然,林林總總的神仙,也都出身於道家,平時不聞不問,過年時,不知道從哪裡蹣跚走來,在一元始旦,萬象更新,人世間充滿歡樂充滿希望的時候,同眾庶一起歡度春節,增加節日的氣氛,也讓人們的心靈有所依傍,靈魂有所棲止,精神有所寄託。

初一五更,點正火,接神,焚香。天地爺自是“滿斗焚香”,一炷炷的單香插得如滿天星斗。其他佛家道家的神仙都是三炷香,或者一炷整香。祖先不是神,所以不可與神一起受享獻饗。各種獻供只能等敬罷神,酌回來才能獻給祖先享用。

正火點着之後,青煙裊裊,火光衝天,火星直冒,紅光滿園,柏樹枝的香氣格外讓人心清氣爽。孩子們會圍着正火又跑又跳,又喊又笑,小臉一張張映得通紅,朝氣與喜氣沖溢在整個院子里。焚香的同時放鞭炮,孩子們手裡會提個小小的紅燈籠,到處搶鞭炮。大人們會告訴孩子,大年初一去抱住大椿樹,高聲喊:“椿樹娘,椿樹娘,你長高了做大梁,我長高了領衣裳。”希望椿樹帶領孩子與孩子一起長高。

初一五更,男人敬神,女人打火做飯。大年初一早晨多有吃拉麵的,意思全家互相拉着點,拉緊點,不要丟了一個,不要少了一個。女人拉麵,男人批蒜苗。白生生的面,拉得又長又細。頭一碗先送本院鄰居,鄰居也會送一碗過來,是鄰裡間年節時的一種交往。

吃過飯,父母要給孩派壓歲錢,還要教孩子怎麼拜年。同輩人見了面,也要互相說一聲:“拜年了!”“春節好!”“恭喜發財!”村子上的老會頭要飾鑼集中八音會,耍會,唱戲。

初五晚上要送祖先。端上半碗爺奶奶湯,燃上一炷香,隨着香煙裊裊,把祖宗送到大門口,放三個炮,年節基本就結束了。

元宵拱燈棚

說是年基本結束了,就是還沒有完全結束。還有個正月十五鬧元宵。

掛燈是我們家鄉一個古老的傳統,不僅家家門口掛燈,整個村子里也掛燈。我們村裡有好幾百對花燈,全部掛在後街上。後街從東到西二里長,燈棚用紅、黃、藍三色小布編成小方花格子作燈棚,把燈並排掛在燈棚下,橫看是一對一對的,順着街道看過去,像兩條火龍,從東頭一直蜿蜒到西頭。

吃晚飯時,鑼聲一響,人們都知道,要髮蠟了。二里長街,分段管理,負責髮蠟。髮蠟,就是點燃蠟燭的意思。蠟燭是用羊油掂的。熬上一大鍋羊油,把棉花做成的燭芯纏在蠟柱上,往大鍋里一沾一掂,反覆多次,便掂成一支蠟燭。白蠟是本色,紅蠟是添加了顏料。紅蠟白蠟都搖紅,把一條長街搖得輝煌壯麗。

蠟是年年要掂的,燈也是年年要掛的。即使最困難時期,也沒有停了掛燈。人們說:“討吃看煙火,肚飢眼歡樂。”可以不吃飯,卻不能不掛燈,好不容易又一年了。也許是想證明人這一年又活過來了。

燈棚下每隔十來步就有一爐正火。燒正火用的是香煤香炭,也叫蘭花香炭,也叫白煤,傳說是英國女王燒壁爐必用之物。燒正火不能用臭炭,否則會嗆得人打噴嚏,既出洋相,又不吉祥。

傍晚時分,把大塊的蘭花香炭添足,正火很快就燒起來,一堆火焰熊熊,一堆金光燦爛。天上月光,棚下燈光,街邊火光,加上社鼓笙簫,一派金碧輝煌。

後街的燈全是紗燈,繪有《三國演義》《封神榜》《水滸傳》人物情節。觀燈的人從燈棚下拱來拱去,拱燈棚大都是年輕人。拄着棍子的老人,引着孫兒老叟,傴僂着腰的老嫗,把孩子架在膀子上的父親,梳了圓頭的母親,大都圍在燈棚下,每盞燈下都會圍一個圈,仄着耳朵,聽老人們說些三國水滸的故事。

年輕人很少站在燈棚下看那些老故事。他們有自己的故事。“月上柳梢頭,人約黃昏后”,是他們故事的藍本。他們總是以“拱燈棚”,演繹他們的故事。

“拱燈棚”是小鎮一個古老的風俗。按風俗,不管男女老少,正月十五拱燈棚會吉祥如意,會免除許許多多的大災小難。

不管是男孩子還是女孩子,他們總是三個一群兩個一夥,貌似看燈,卻心不在焉。

女孩子們總手拉着手“拱燈棚”。從東頭拱到西頭,再從西頭拱到東頭。人前,不管內心多麼豐富,表面都是很文靜的,悄悄謐謐的,總是很害羞的樣子。如果不得不笑,也只是趕緊把嘴捂住,彎下腰,或者背過身,悄抿了嘴,齒頰之間發出一點吹涼風一樣的“噝噝”聲。當然,有時候也難免會笑出聲來,無非是忽然想起了,或者忽然看到了可笑的人和事,實在是忍不住了,就笑出來。一個人“疙啼”一聲,會傳染女孩子們無端“疙啼疙啼”笑成一堆。

有話說的時候,也只是悄聲細語,低聲喁喁。心裏卻熱烈。本來是三個兩個相跟着,牽着手,卻不知道什麼時候就少了一個。不知道其中哪個女孩什麼時候就拱出了燈棚,拱到了月光照不到,燈光也照不到的地方去了。

當然,過來人都知道,那燈火闌珊的角落有女孩子和男孩子的故事。他們知道,入春了,驚蟄了,野草也該句萌了。

藿谷洞的燈與後街不同,藿谷洞的燈不是為大眾看的,是專奉“四奶奶”即送子娘娘的,觀燈的人大都是母親或祖母,因此藿谷洞的燈上沒有故事,燈下也沒有故事,很雅靜,很純凈,很雅緻,雅緻到脫俗。燈的形式也很單純,一式宮燈,兩種燈罩。布罩和玻璃。布罩是綠裙紅褲,很鮮艷;玻璃罩繪有梅、蘭、竹、菊,石榴,荷花,魚兒,鳥兒,題些“玉燭豐年”“雨暘時若”“天地交泰”的文字。品位很高,有一點閨閣氣。倘若天空中有雪花飄下來,那才是真正的天上人間。後街的燈如果代表“俗”,那麼藿谷洞的燈就是一個“雅”。從後街到藿谷洞,就是一個雅俗共賞。

藿谷洞長不足百步,南瀕大箕河,北出後街。南北街口各有一爐正火,助“四奶奶”看烘火,供祖母或母親們熏手熏腳。母親們和奶奶們常常會搬個椅子或板床,穩穩噹噹坐在正火跟前,一邊烤火,一邊陪着“四奶奶”看燈。

俗話說三個女人一台戲,但那時候的女人卻沒戲,既不調笑,也不說穢語。所有的奶奶和母親們絕不開玩笑的。她們的心那時候是非常虔敬的,是懷着無限神聖的。她們心中的“四奶奶”就在燈下,護佑着他們的兒孫。不管誰家裡的孩子,都是四奶奶殿里來的,都是四奶奶的侍兒。四奶奶是鄉村女人們心中最可愛,最可敬,最可親,最嚴肅的一位女神。她們會拿五色紙給四奶奶糊些被子褥子、衣裳、鞋襪。四奶奶應該是三寸金蓮,因此那鞋也糊得小辣椒一樣,很俊,很俏。除了鋪、蓋、穿、戴,還要給四奶奶上“銀兩”,即錫箔之類。

供品是“桃”。面蒸的,洋紅洋綠點綴些花恭弘=叶 恭弘,給人的感覺是喜氣盈盈,這叫“壽桃”,是寶塔狀的。還有“油桃”,偏形的,卧式的。蒸熟之後,在鍋里放點油,燒熱,把“桃”背在油鍋里擦一擦,又香又好看。最簡單最聖潔的供品是“香米茶飯”。名字看上去太詩意了!把小米炒開花,放點水,熗一下,用酒盅一盅一盅扣在盤子里,像一個個小沙包。

祖母和母親們在用心觀燈。那綠褲紅裙之間所包含的,那玻璃罩所罩的,是仙緣,是仙機。她們在和“四奶奶”一起度過元宵,她們在恬靜中陪伴着她們心中的“四奶奶”。她們也知道“四奶奶”不是世界上最高和最有權威的神或仙,但“四奶奶”最喜歡孩子,是一位慈祥的奶奶。為了孩子,為了孫子,她們願意那麼陪着。那樣陪着,她們的心裏是安定的,是穩貼的。她們的內心永遠有那麼一團神聖。“漠虛靜以恬愉兮,澹無為而自得。聞赤松之清塵兮,願承風乎遺則。貴真人之休德兮,美往世之登仙。”這才是她們的真心,雖然她們並未讀過《楚辭》。

掛起燈的時候,能有雪花飄落,那是再美不過了。那叫“瑞雪兆豐年”,最富詩意。

到此,年就算完全結束了。“九九加一九,耕牛遍地走”,到一心一意種莊稼的時候了。

(作者:卓 然,系山西省晉城市作協名譽主席)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境