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技術記錄),獲取更多技術記錄:

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

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

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

一文讓你快速上手 Mockito 單元測試框架_台中搬家

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

台中搬家公司推薦超過30年經驗,首選台中大展搬家

前言

在計算機編程中,單元測試是一種軟件測試方法,通過該方法可以測試源代碼的各個單元功能是否適合使用。為代碼編寫單元測試有很多好處,包括可以及早的發現代碼錯誤,促進更改,簡化集成,方便代碼重構以及許多其它功能。使用 Java 語言的朋友應該用過或者聽過 Junit 就是用來做單元測試的,那麼為什麼我們還需要 Mockito 測試框架呢?想象一下這樣的一個常見的場景,當前要測試的類依賴於其它一些類對象時,如果用 Junit 來進行單元測試的話,我們就必須手動創建出這些依賴的對象,這其實是個比較麻煩的工作,此時就可以使用 Mockito 測試框架來模擬那些依賴的類,這些被模擬的對象在測試中充當真實對象的虛擬對象或克隆對象,而且 Mockito 同時也提供了方便的測試行為驗證。這樣就可以讓我們更多地去關注當前測試類的邏輯,而不是它所依賴的對象。

生成 Mock 對象方式

要使用 Mockito,首先需要在我們的項目中引入 Mockito 測試框架依賴,基於 Maven 構建的項目引入如下依賴即可:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.3.3</version>
    <scope>test</scope>
</dependency>

如果是基於 Gradle 構建的項目,則引入如下依賴:

testCompile group: 'org.mockito', name: 'mockito-core', version: '3.3.3'

使用 Mockito 通常有兩種常見的方式來創建 Mock 對象。

1、使用 Mockito.mock(clazz) 方式

通過 Mockito 類的靜態方法 mock 來創建 Mock 對象,例如以下創建了一個 List 類型的 Mock 對象:

List<String> mockList = Mockito.mock(ArrayList.class);

由於 mock 方法是一個靜態方法,所以通常會寫成靜態導入方法的方式,即 List mockList = mock(ArrayList.class)。

2、使用 @Mock 註解方式

第二種方式就是使用 @Mock 註解方式來創建 Mock 對象,使用該方式創需要注意的是要在運行測試方法前使用 MockitoAnnotations.initMocks(this) 或者單元測試類上加上 @ExtendWith(MockitoExtension.class) 註解,如下所示代碼創建了一個 List 類型的 Mock 對象(PS: @BeforeEach 是 Junit 5 的註解,功能類似於 Junit 4 的 @Before 註解。):

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
//@ExtendWith(MockitoExtension.class)
public class MockitoTest {

  @Mock
  private List<String> mockList;

  @BeforeEach
  public void beforeEach() {
    MockitoAnnotations.initMocks(this);
  }
}

驗證性測試

Mockito 測試框架中提供了 Mockito.verify 靜態方法讓我們可以方便的進行驗證性測試,比如方法調用驗證、方法調用次數驗證、方法調用順序驗證等,下面看看具體的代碼。

驗證方法單次調用

驗證方法單次調用的話直接 verify 方法后加上待驗證調用方法即可,以下代碼的功能就是驗證 mockList 對象的 size 方法被調用一次。

/**
 * @author mghio
 * @date: 2020-05-28
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
@ExtendWith(MockitoExtension.class)
public class MockitoVerifyTest {

  @Mock
  List<String> mockList;

  @Test
  void verify_SimpleInvocationOnMock() {
    mockList.size();
    verify(mockList).size();
  }
}
驗證方法調用指定次數

除了驗證單次調用,我們有時候還需要驗證一些方法被調用多次或者指定的次數,那麼此時就可以使用 verify + times 方法來驗證方法調用指定次數,同時還可以結合 atLeast + atMost 方法來提供調用次數範圍,同時還有 never 等方法驗證不被調用等。

/**
 * @author mghio
 * @date: 2020-05-28
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
@ExtendWith(MockitoExtension.class)
public class MockitoVerifyTest {

  @Mock
  List<String> mockList;

  @Test
  void verify_NumberOfInteractionsWithMock() {
    mockList.size();
    mockList.size();

    verify(mockList, times(2)).size();
    verify(mockList, atLeast(1)).size();
    verify(mockList, atMost(10)).size();
  }
}
驗證方法調用順序

同時還可以使用 inOrder 方法來驗證方法的調用順序,下面示例驗證 mockList 對象的 size、add 和 clear 方法的調用順序。

/**
 * @author mghio
 * @date: 2020-05-28
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
@ExtendWith(MockitoExtension.class)
public class MockitoVerifyTest {

  @Mock
  List<String> mockList;

  @Test
  void verify_OrderedInvocationsOnMock() {
    mockList.size();
    mockList.add("add a parameter");
    mockList.clear();

    InOrder inOrder = inOrder(mockList);

    inOrder.verify(mockList).size();
    inOrder.verify(mockList).add("add a parameter");
    inOrder.verify(mockList).clear();
  }
}

以上只是列舉了一些簡單的驗證性測試,還有驗證測試方法調用超時以及更多的驗證測試可以通過相關官方文檔探索學習。

驗證方法異常

異常測試我們需要使用 Mockito 框架提供的一些調用行為定義,Mockito 提供了 when(…).thenXXX(…) 來讓我們定義方法調用行為,以下代碼定義了當調用 mockMap 的 get 方法無論傳入任何參數都會拋出一個空指針 NullPointerException 異常,然後通過 Assertions.assertThrows 來驗證調用結果。

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
@ExtendWith(MockitoExtension.class)
public class MockitoExceptionTest {

  @Mock
  public Map<String, Integer> mockMap;

  @Test
  public void whenConfigNonVoidReturnMethodToThrowEx_thenExIsThrown() {
    when(mockMap.get(anyString())).thenThrow(NullPointerException.class);

    assertThrows(NullPointerException.class, () -> mockMap.get("mghio"));
  }
}

同時 when(…).thenXXX(…) 不僅可以定義方法調用拋出異常,還可以定義調用方法后的返回結果,比如 when(mockMap.get(“mghio”)).thenReturn(21); 定義了當我們調用 mockMap 的 get 方法並傳入參數 mghio 時的返回結果是 21。這裡有一點需要注意,使用以上這種方式定義的 mock 對象測試實際並不會影響到對象的內部狀態,如下圖所示:

雖然我們已經在 mockList 對象上調用了 add 方法,但是實際上 mockList 集合中並沒有加入 mghio,這時候如果需要對 mock 對象有影響,那麼需要使用 spy 方式來生成 mock 對象。

public class MockitoTest {

  private List<String> mockList = spy(ArrayList.class);

  @Test
  public void add_spyMockList_thenAffect() {
    mockList.add("mghio");

    assertEquals(0, mockList.size());
  }
}

斷點后可以發現當使用 spy 方法創建出來的 mock 對象調用 add 方法后,mghio 被成功的加入到 mockList 集合當中。

與 Spring 框架集成

Mockito 框架提供了 @MockBean 註解用來將 mock 對象注入到 Spring 容器中,該對象會替換容器中任何現有的相同類型的 bean,該註解在需要模擬特定bean(例如外部服務)的測試場景中很有用。如果使用的是 Spring Boot 2.0+ 並且當前容器中已有相同類型的 bean 的時候,需要設置 spring.main.allow-bean-definition-overriding 為 true(默認為 false)允許 bean 定義覆蓋。下面假設要測試通過用戶編碼查詢用戶的信息,有一個數據庫操作層的 UserRepository,也就是我們等下要 mock 的對象,定義如下:

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
@Repository
public interface UserRepository {

  User findUserById(Long id);

}

還有用戶操作的相關服務 UserService 類,其定義如下所示:

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
@Service
public class UserService {

  private UserRepository userRepository;

  public UserService(UserRepository userRepository) {
    this.userRepository = userRepository;
  }

  public User findUserById(Long id) {
    return userRepository.findUserById(id);
  }
}

在測試類中使用 @MockBean 來標註 UserRepository 屬性表示這個類型的 bean 使用的是 mock 對象,使用 @Autowired 標註表示 UserService 屬性使用的是 Spring 容器中的對象,然後使用 @SpringBootTest 啟用 Spring 環境即可。

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
@SpringBootTest
public class UserServiceUnitTest {

  @Autowired
  private UserService userService;

  @MockBean
  private UserRepository userRepository;

  @Test
  public void whenUserIdIsProvided_thenRetrievedNameIsCorrect() {
    User expectedUser = new User(9527L, "mghio", "18288888880");
    when(userRepository.findUserById(9527L)).thenReturn(expectedUser);
    User actualUser = userService.findUserById(9527L);
    assertEquals(expectedUser, actualUser);
  }
}

Mockito 框架的工作原理

通過以上介紹可以發現, Mockito 非常容易使用並且可以方便的驗證一些方法的行為,相信你已經看出來了,使用的步驟是先創建一個需要 mock 的對象 Target ,該對象如下:

public class Target {

  public String foo(String name) {
    return String.format("Hello, %s", name);
  }

}

然後我們直接使用 Mockito.mock 方法和 when(…).thenReturn(…) 來生成 mock 對象並指定方法調用時的行為,代碼如下:

@Test
public void test_foo() {
  String expectedResult = "Mocked mghio";
  when(mockTarget.foo("mghio")).thenReturn(expectedResult);
  String actualResult = mockTarget.foo("mghio");
  assertEquals(expectedResult, actualResult);
}

仔細觀察以上 when(mockTarget.foo(“mghio”)).thenReturn(expectedResult) 這行代碼,首次使用我也覺得很奇怪,when 方法的入參竟然是方法的返回值 mockTarget.foo(“mghio”),覺得正確的代碼應該是這樣 when(mockTarget).foo(“mghio”),但是這個寫法實際上無法進行編譯。既然 Target.foo 方法的返回值是 String 類型,那是不是可以使用如下方式呢?

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

Mockito.when("Hello, I am mghio").thenReturn("Mocked mghio");

結果是編譯通過,但是在運行時報錯:

從錯誤提示可以看出,when 方法需要一個方法調用的參數,實際上它只需要 more 對象方法調用在 when 方法之前就行,我們看看下面這個測試代碼:

@Test
public void test_mockitoWhenMethod() {
  String expectedResult = "Mocked mghio";
  mockTarget.foo("mghio");
  when("Hello, I am mghio").thenReturn(expectedResult);
  String actualResult = mockTarget.foo("mghio");
  assertEquals(expectedResult, actualResult);
}

以上代碼可以正常測試通過,結果如下:

為什麼這樣就可以正常測試通過?是因為當我們調用 mock 對象的 foo 方法時,Mockito 會攔截方法的調用然後將方法調用的詳細信息保存到 mock 對象的上下文中,當調用到 Mockito.when 方法時,實際上是從該上下文中獲取最後一個註冊的方法調用,然後把 thenReturn 的參數作為其返回值保存,然後當我們的再次調用 mock 對象的該方法時,之前已經記錄的方法行為將被再次回放,該方法觸發攔截器重新調用並且返回我們在 thenReturn 方法指定的返回值。以下是 Mockito.when 方法的源碼:

該方法裏面直接使用了 MockitoCore.when 方法,繼續跟進,該方法源碼如下:

仔細觀察可以發現,在源碼中並沒有用到參數 methodCall,而是從 MockingProgress 實例中獲取 OngoingStubbing 對象,這個 OngoingStubbing 對象就是前文所提到的上下文對象。個人感覺是 Mockito 為了提供簡潔易用的 API 然後才製造了 when 方法調用的這種“幻象”,簡而言之,Mockito 框架通過方法攔截在上下文中存儲和檢索方法調用詳細信息來工作的。

如何實現一個微型的 Mock 框架

知道了 Mockito 的運行原理之後,接下來看看要如何自己去實現一個類似功能的 mock 框架出來,看到方法攔截這裏我相信你已經知道了,其實這就是 AOP 啊,但是通過閱讀其源碼發現 Mockito 其實並沒有使用我們熟悉的 Spring AOP 或者 AspectJ 做的方法攔截,而是通過運行時增強庫 Byte Buddy 和反射工具庫 Objenesis 生成和初始化 mock 對象的。
現在,通過以上分析和源碼閱讀可以定義出一個簡單版本的 mock 框架了,將自定義的 mock 框架命名為 imock。這裡有一點需要注意的是,Mockito 有一個好處是,它不需要進行初始化,可以直接通過其提供的靜態方法來立即使用它。在這裏我們也使用相同名稱的靜態方法,通過 Mockito 源碼:

很容易看出 Mockito 類最終都是委託給 MockitoCore 去實現的功能,而其只提供了一些面向使用者易用的靜態方法,在這裏我們也定義一個這樣的代理對象 IMockCore,這個類中需要一個創建 mock 對象的方法 mock 和一個給方法設定返回值的 thenReturn 方法,同時該類中持有一個方法調用詳情 InvocationDetail 集合列表,這個類是用來記錄方法調用詳細信息的,然後 when 方法僅返回列表中的最後一個 InvocationDetail,這裏列表可以直接使用 Java 中常用的 ArrayList 即可,這裏的 ArrayList 集合列表就實現了 Mockito 中的 OngoingStubbing 的功能。
根據方法的三要素方法名、方法參數和方法返回值很容易就可以寫出 InvocationDetail 類的代碼,為了對方法在不同類有同名的情況區分,還需要加上類全稱字段和重寫該類的 equals 和 hashCode 方法(判斷是否在調用方法集合列表時需要根據該方法判斷),代碼如下所示:

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
public class InvocationDetail<T> {

  private String attachedClassName;

  private String methodName;

  private Object[] arguments;

  private T result;

  public InvocationDetail(String attachedClassName, String methodName, Object[] arguments) {
    this.attachedClassName = attachedClassName;
    this.methodName = methodName;
    this.arguments = arguments;
  }

  public void thenReturn(T t) {
    this.result = t;
  }

  public T getResult() {
    return result;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    InvocationDetail<?> behaviour = (InvocationDetail<?>) o;
    return Objects.equals(attachedClassName, behaviour.attachedClassName) &&
        Objects.equals(methodName, behaviour.methodName) &&
        Arrays.equals(arguments, behaviour.arguments);
  }

  @Override
  public int hashCode() {
    int result = Objects.hash(attachedClassName, methodName);
    result = 31 * result + Arrays.hashCode(arguments);
    return result;
  }
}

接下來就是如何去創建我們的 mock 對象了,在這裏我們也使用 Byte Buddy 和 Objenesis 庫來創建 mock 對象,IMockCreator 接口定義如下:

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
public interface IMockCreator {

  <T> T createMock(Class<T> mockTargetClass, List<InvocationDetail> behaviorList);

}

實現類 ByteBuddyIMockCreator 使用 Byte Buddy 庫在運行時動態生成 mock 類對象代碼然後使用 Objenesis 去實例化該對象。代碼如下:

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
public class ByteBuddyIMockCreator implements IMockCreator {

  private final ObjenesisStd objenesisStd = new ObjenesisStd();

  @Override
  public <T> T createMock(Class<T> mockTargetClass, List<InvocationDetail> behaviorList) {
    ByteBuddy byteBuddy = new ByteBuddy();

    Class<? extends T> classWithInterceptor = byteBuddy.subclass(mockTargetClass)
        .method(ElementMatchers.any())
        .intercept(MethodDelegation.to(InterceptorDelegate.class))
        .defineField("interceptor", IMockInterceptor.class, Modifier.PRIVATE)
        .implement(IMockIntercepable.class)
        .intercept(FieldAccessor.ofBeanProperty())
        .make()
        .load(getClass().getClassLoader(), Default.WRAPPER).getLoaded();

    T mockTargetInstance = objenesisStd.newInstance(classWithInterceptor);
    ((IMockIntercepable) mockTargetInstance).setInterceptor(new IMockInterceptor(behaviorList));

    return mockTargetInstance;
  }
}

基於以上分析我們可以很容易寫出創建 mock 對象的 IMockCore 類的代碼如下:

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
public class IMockCore {

  private final List<InvocationDetail> invocationDetailList = new ArrayList<>(8);

  private final IMockCreator mockCreator = new ByteBuddyIMockCreator();

  public <T> T mock(Class<T> mockTargetClass) {
    T result = mockCreator.createMock(mockTargetClass, invocationDetailList);
    return result;
  }

  @SuppressWarnings("unchecked")
  public <T> InvocationDetail<T> when(T methodCall) {
    int currentSize = invocationDetailList.size();
    return (InvocationDetail<T>) invocationDetailList.get(currentSize - 1);
  }
}

提供給使用者的類 IMock 只是對 IMockCore 進行的簡單調用而已,代碼如下:

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
public class IMock {

  private static final IMockCore IMOCK_CORE = new IMockCore();

  public static <T> T mock(Class<T> clazz) {
    return IMOCK_CORE.mock(clazz);
  }

  public static <T> InvocationDetail when(T methodCall) {
    return IMOCK_CORE.when(methodCall);
  }
}

通過以上步驟,我們就已經實現了一個微型的 mock 框架了,下面來個實際例子測試一下,首先創建一個 Target 對象:

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
public class Target {

  public String foo(String name) {
    return String.format("Hello, %s", name);
  }

}

然後編寫其對應的測試類 IMockTest 類如下:

/**
 * @author mghio
 * @date: 2020-05-30
 * @version: 1.0
 * @description:
 * @since JDK 1.8
 */
public class IMockTest {

  @Test
  public void test_foo_method() {
    String exceptedResult = "Mocked mghio";
    Target mockTarget = IMock.mock(Target.class);

    IMock.when(mockTarget.foo("mghio")).thenReturn(exceptedResult);

    String actualResult = mockTarget.foo("mghio");

    assertEquals(exceptedResult, actualResult);
  }

}

以上測試的可以正常運行,達到了和 Mockito 測試框架一樣的效果,運行結果如下:

上面只是列出了一些關鍵類的源碼,自定義 IMock 框架的所有代碼已上傳至 Github 倉庫 imock,感興趣的朋友可以去看看。

總結

本文只是介紹了 Mockito 的一些使用方法,這隻是該框架提供的最基礎功能,更多高級的用法可以去官網閱讀相關的文檔,然後介紹了框架中 when(…).thenReturn(…) 定義行為方法的實現方式並按照其源碼思路實現了一個相同功能的簡易版的 imock 。雖然進行單元測試有很多優點,但是也不可盲目的進行單元測試,在大部分情況下只要做好對項目中邏輯比較複雜、不容易理解的核心業務模塊以及項目中公共依賴的模塊的單元測試就可以了。

參考文章

Mockito
Objenesis
Byte Buddy

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

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

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

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

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

本文始發於個人公眾號: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%的內存被浪費掉。

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

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

※回頭車貨運收費標準

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

基於RBAC的權限控制淺析(結合Spring Security)_網頁設計公司

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

 

嗯,昨天面試讓講我的項目,讓我講講項目里權限控制那一塊的,講的很爛。所以整理一下。

按照面試官的提問流程來講:

一、RBAC是個啥東西了?

RBACRole-Based Access Control ),即基於角色的訪問控制模型,我的項目是基於RBAC0模型.由於之相對應的數據實體構成.由用戶表,角色映射表,角色表,權限表,權限映射表構成.

 

 

1 RBAC0模型圖

二、你可以講講權限控制大概執行流程嗎?

 用戶登錄之後首先進行身份驗證,成功之後獲取當前用戶的所有角色,之後根據角色加載對應的權限菜單,這裏默認不加載沒有權限的菜單,當存在直接輸入URL路徑的情況時,對於登錄用戶的每一個請求,都會通過鑒權處理,分析角色.最後通過權限的判斷分析是否可以訪問菜單資源.

在 spring Security,對用登錄的請先通過FilterInvocationSecurityMetadataSource的實現類獲取當前請求,分析需要的角色,該類的主要功能就是通過當前的請求地址,獲取該地址需要的用戶角色。

1、獲取當前訪問路徑的URL路徑

2、獲取所有資源URL,即所有的菜單URL路徑

3、當前的訪問URL和返回的每個URL基於Ant風格比較,如果相等,獲取當前訪問URL的所有角色。如果沒有相等的,定義資源為公告資源,並且給予一個公告資源的角色。

4、當為公共資源時,判斷用戶是否登錄。登錄放行。返回資源

5、當為角色資源時,登錄用戶的角色列表和該資源的角色列表進行比較,如果有相同角色,放行,返回資源

6、當即不是公共資源也沒有相匹配的角色的時候。拋異常,沒有權限

圖2 系統訪問控制流程圖

 代碼:

鑒權:

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    MenuService menuService;
    //路徑比較工具
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    Logger logger = Logger.getLogger("com.liruilong.hros.config.ustomFilterInvocationSecurityMetadataSource");
    /**
     * @return java.util.Collection<org.springframework.security.access.ConfigAttribute> * 返回值是 Collection<ConfigAttribute>,表示當前請求 URL 所需的角色。
     * @Author Liruilong
     * @Description 當前請求需要的角色,該方法的參數是一個 FilterInvocation, 開發者可以從 Filterlnvocation 中提取出當前請求的 URL,
     * @Date 18:13 2019/12/24
     * @Param [object]
     **/
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        //獲取當前請求路徑
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        logger.warning(requestUrl);
        //獲取所有的菜單url路徑
        List<Menu> menus = menuService.getAllMenusWithRole();
        // AntPathMatcher,主要用來實現 ant 風格的 URL 匹配。
         for (Menu menu : menus) {
            if (antPathMatcher.match(menu.getUrl(), requestUrl)) {
                //擁有當前菜單權限的角色
                List<Role> roles = menu.getRoles();
                String[] strings = new String[roles.size()];
                for (int i = 0; i < roles.size(); i++) {
                    strings[i] = roles.get(i).getName();
                }
                return SecurityConfig.createList(strings);
            }
        }
        // 沒匹配上的資源都是登錄,或者為公共資源
        return SecurityConfig.createList("ROLE_LOGIN");
    }

 

 @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        for (ConfigAttribute configAttribute : configAttributes) {
            String needRole = configAttribute.getAttribute();
            if ("ROLE_LOGIN".equals(needRole)) {
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new AccessDeniedException("尚未登錄,請登錄!");
                } else {
                    return;
                }
            }
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("權限不足,請聯繫管理員!");
    }

 

 

 

三、你可以把對應的SQL和表結構寫一下嗎?

 

加載所有的菜單資源;返回所有的菜單資源和對應的角色集合,Service端和訪問的URL的比較,存在判斷角色。(鑒權)


select m.*,r.`id` as rid,r.`name` as rname,r.`namezh` as rnamezh
from menu m,menu_role mr,role r
where m.`id`=mr.`mid` and mr.`rid`=r.`id` order by m.`id`
根據用戶ID返回當前用戶的全部菜單資源(授權)
   select m1.`id`,m1.url,m1.`path`,m1.`component`,m1.`iconCls`,m1.`name`,m1.`requireAuth`,m1.keepAlive,m1.enabled,
       m2.id as id2,m2.url as url2,m2.name as name2,m2.`component` as component2,m2.`iconCls` as iconCls2,m2.`keepAlive` as keepAlive2,m2.`path` as path2,m2.`requireAuth` as requireAuth2,m2.enabled as enabled2,m2.parentId as parentId2
       from menu m1,menu m2
       where m1.`id`=m2.`parentId` and m1.`id`!=1 and m2.`id`
       in(select mr.`mid` from hr_role h_r,menu_role mr where h_r.`rid`=mr.`rid` and h_r.`hrid`=#{hrId})
       and m2.`enabled`=true order by m1.`id`,m2.`id`

 

2 ERBAC數據實體關係圖

用戶登錄之後首先進行身份驗證,成功之後獲取當前用戶的所有角色,之後根據角色加載對應的權限菜單,這裏默認不加載沒有權限的菜單,當存在直接輸入URL路徑的情況時,對於登錄用戶的每一個請求,都會通過鑒權處理,分析角色.最後通過權限的判斷分析是否可以訪問菜單資源.

用戶表:

 

 角色表:

 

用戶角色映射表:

 

權資源表:

 

 

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

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

手持10萬元 到底現在購車還是過年前購車優惠多?_網頁設計

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

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

99萬)錢,再怎麼說跟4s優惠硬是差了1萬塊,也特別不想分期供車覺得錢被銀行賺了,所以想着年底把年終獎拿到了手后再買,但很怕過年時購車大軍會把優惠壓沒了,所以現在特別糾結。觀點分析:金九銀十除了描述房價之外,對於汽車同樣適用。

前言

2016年過去大半,還有兩個月,就要迎來全新的2017年,很多還沒有買車的朋友,肯定會在這段時間糾結一件事:究竟是趁着現在優惠更多把車買了練手幾個月等過年呢,亦或是等着11月廣州車展上市有更多可選新車?

觀點一:據說11月新上市車更多

黃小姐近期一直很想買一台代步小車,年頭的時候其實已經相中了豐田致炫,覺得空間夠用也容易好開,當時因為忙所以剛好沒空去跑4s。今年7月份以來居然收到了致炫出了新車的消息。跟她年頭一起買車的朋友都在後悔不已:早知道新致炫帶了CVT就等等再買啦,現在剛買的車就變老車了好失望,因為這點,黃小姐現在很糾結,想再多等等持幣觀望。

觀點分析:其實一般車企對新車更新速度都保持在1-2年左右,所以不必太過擔心“剛買的車又變舊了”。加之目前很多車企都用家族化前臉設計,所以即使半年度/年度小改款,車子外觀差異也並不明顯。像黃小姐的致炫改款情況,一般很少發生。

致炫改款前後外觀差距不大,單就一項變速箱從4AT變成CVT已經讓很多已購買老款的車主後悔不已。

觀點二:想等年終獎預算更多買好車

小李是公司業務員,每天都要來回跑不少地方,所以想買一台省油耐用有面子的車,於是想買新邁騰,想買的330tsi豪華型的配置(官方報價23.49萬)但僅有330tsi舒適型(官方報價20.99萬)錢,再怎麼說跟4s優惠硬是差了1萬塊,也特別不想分期供車覺得錢被銀行賺了,所以想着年底把年終獎拿到了手后再買,

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

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

但很怕過年時購車大軍會把優惠壓沒了,所以現在特別糾結。

觀點分析:金九銀十除了描述房價之外,對於汽車同樣適用。目前10月份正是優惠的大好時機,如果有像小李這樣的購車朋友,建議選用分期或是問朋友借些錢購車,畢竟很多4s店會聯手銀行減免分期利息,最多交些手續費就能把車開走,早用早享受不好嗎?還有消息指出,購置稅減半將在2016年12月31日結束,如果你意向車型是1.6升及以下排量車型,這些優惠就沒了哦。

2017款邁騰外觀好看,其實舒適型和豪華型價差2.5萬配置還是差距甚大的,這從內飾上就能看出端倪。

觀點三:手上有錢近期優惠大

陳先生存了大半年,加上前幾年存下來的錢,手上已經積攢了快10萬元,近期一直在看各種車型優惠,曾經對比過5月初的優惠,已經很值得入手了,可是一轉念想着金九銀十是熱銷月份,車企會不會在冷門月份例如12月份、1月份做優惠呢?所以陳先生現在特別糾結。

觀點分析:汽車銷售其實熱門促銷時比冷門促銷時車型更多,優惠也更大,大家都知道旺季是那個時候,所以那幾個月的銷量任務也會更重,甚至有4s會為了旺季臨時加緊和二手車商合作推出快速置換車計劃,也會在熱銷幾個月多招臨時促銷員,熱銷期的店內活動也會更多更火爆,所有的一切,都證明旺季有更豐富的資源,所以你能獲得的也會更多。

買車總之一句話,別糾結別猶豫,看中了就趕緊下手,早買早享受!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

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

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網頁設計為架站首選

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

能杜絕95%的變道追尾事故!這幾款9.98萬起的車型神了!_網頁設計公司

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

5T 手動尊貴型配備了併線輔助系統,對於如此碩大的SUV來說,盲區更加值得關注,加上DX7的車窗設計得不算大,A柱也不小,因此開DX7真的要多注意注意,這個帶併線輔助系統的東南DX7 2017款 1。5T 手動尊貴型售價12。99萬,使用1。

日產軒逸

指導價:9.98-15.90萬

軒逸是我們熟悉的國民家轎之一,每個月的銷量都十分高,省油安靜平順是我們對軒逸的最大印象,當然軒逸的尺寸也不小,4631*1760*1503mm的車身尺寸,2700mm的軸距都意味着這是一台大緊湊型轎車,因此它的車內空間表現也十分不錯,不過1.6L的發動機的版本動力不怎麼強,這也是它的短板所在。

軒逸 2016款 1.6XV CVT智尊版上就裝備了併線輔助系統,而且不止併線輔助,這款指導價14.68萬的1.6XV CVT智尊版還配備了車道偏離預警系統和主動剎車系統,因此這款車的安全性裝備也是十分豐富,LED近光燈、ESp等裝備也有,配置上這款車型十分豐富。

東南DX7

指導價:9.69-13.99萬

東南DX7算是大家比較熟悉的SUV了,外觀設計是DX7最大的亮點,

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

意大利設計大師操刀設計的造型看起來十分有氣場,加上4530*1900*1700mm的車身尺寸,2700mm的軸距營造出來的大車感十分充足,喜歡開大車的朋友一定不能錯過這車。

東南DX7 2017款 1.5T 手動尊貴型配備了併線輔助系統,對於如此碩大的SUV來說,盲區更加值得關注,加上DX7的車窗設計得不算大,A柱也不小,因此開DX7真的要多注意注意,這個帶併線輔助系統的東南DX7 2017款 1.5T 手動尊貴型售價12.99萬,使用1.5T發動機(156馬力/215牛米)搭配6擋手動變速箱,性價比還是很不錯的。分區空調/感應雨刷/GpS系統/車內氛圍燈/電動主駕駛座椅/前後雷達/全景影像也都有配備。

標緻308

指導價:9.97-15.97萬

如果說老款標緻308長得很帥的話,我想沒幾個人會贊同,但我說這一代308很帥的話,我想沒幾個人會反對,的確這代標緻308的造型相比老款發生了翻天覆地的變化,4590*1820*1488mm的車身尺寸不算小,尤其是1820mm的寬度使得整車看起來更加寬大,頗有幾分寬體轎跑既視感。

標緻308 2016款 1.6T 自動尊貴版指導價15.97萬,這款車採用了1.6T發動機加6擋手自一體變速箱的動力組合,這款車配備了併線輔助系統,搭配上法系車紮實的底盤,1.6T發動機動力強勁,因此標緻308十分適合跑高速。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

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

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

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

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

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

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

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

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

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

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

2020年全國鄉村春晚聯動“百縣萬村”_台中搬家公司

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

2019年12月26日,2020年全國鄉村春晚集中展示活動暨麗水鄉村春晚建設成果新聞發布會在北京召開。活動由文化和旅遊部公共服務司、全國公共文化發展中心、浙江省文化和旅遊廳和浙江省麗水市政府主辦。2020年全國鄉村春晚集中展示活動啟動儀式將於2020年1月8日在浙江麗水舉辦。屆時,以麗水為主會場,安徽省、四川省、廣東省、河南省等8省區同步啟動鄉村春晚活動。

此次活動將通過線下鄉村春晚年俗展示、線上互聯網科技互動形式,拉開2020年全國鄉村春晚“百縣萬村”區域聯動大幕。此外,全國鄉村“鬧”春晚展演展示活動也將在全國篩選最具區域特色的節目,到麗水主會場以斗藝和吆喝的形式,集中展示全國鄉村藝術普及成果。

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

在麗水,活動主要以鄉村春晚品牌建設為主導,同步舉辦“麗水味道”年味集市、“麗水山耕”年貨集市、“僑鄉中國年”等十大特色活動,形成文、農、旅大融合,通過網絡直播、主會場與分會場聯動、線上線下互動,全面展現社會發展成就。

據介紹,鄉村春晚起源於1981年由麗水市慶元縣舉水鄉月山村农民自編、自導、自演的“月山春晚”,比央視春晚還早兩年,至今已有30多年的歷史。經過幾十年的發展,麗水鄉村春晚點面結合、遍地開花,成為當地重要的公共文化品牌。(記者 杜潔芳)

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」