分類
發燒車訊

SQlALchemy session詳解

系列文章:

概念

session用於創建程序和數據庫之間的會話,所有對象的載入和保存都需通過session對象 。

通過sessionmaker調用創建一個工廠,並關聯Engine以確保每個session都可以使用該Engine連接資源:

from sqlalchemy.orm import sessionmaker

# 創建session
DbSession = sessionmaker(bind=engine)
session = DbSession()

操作

session的常見操作方法包括:

  1. flush:預提交,提交到數據庫文件,還未寫入數據庫文件中
  2. commit:提交了一個事務,把內存的數據直接寫入數據庫
  3. rollback:回滾
  4. close:關閉

在事務處理時,需注意一下兩點:

  1. 在事務處理過程發生異常時,進行rollback操作,否則會在下次操作時報錯:
Can’t reconnect until invalid transaction is rolled back 
  1. 一般情況下,在一個事務處理完成之後要關閉session,以確保數據操作的準確性。

建議封裝上下文方法:

from contextlib import contextmanager

@contextmanager
def session_maker(session=session):
    try:
        yield session
        session.commit()
    except:
        session.rollback()
        raise
    finally:
        session.close()

調用:

def update_user():
    with session_maker() as db_session:
        db_session.query(Users).filter_by(name='test2').update({'email': 'test2@qq.com'})

線程安全

session不是線程安全的,並且我們一般session對象都是全局的,那麼在多線程情況下,當多個線程共享一個session時,數據處理就會發生錯誤。

為了保證線程安全,需使用scoped_session方法:

db_session = scoped_session(sessionmaker(bind=engine))

內部原理

session對象包含了三個重要的部分:

  1. 標識映射(Identity Map)
  2. 對象的狀態 / 狀態跟蹤
  3. 事務

標識映射

標識映射是與ORM關聯的集合,通過標識映射保證了數據庫操作的準確性。

具體的實現原理是:維護一個Python字典(IdentityMap),關聯這個Session對象到數據庫ID的映射,當應用程序想要獲取一個session對象時,若該對象不存在,標識映射會加載該對象並緩存,若該對象已存在,則直接獲取。這樣的好處是:

  1. 已經被請求過的session對象緩存下來,不需要連接加載多次,造成額外的開銷;
  2. 避免了數據不一致

狀態跟蹤

一個Session對象從創建到銷毀,依次經歷四種狀態,分別是:

  1. Transient:剛new出來的對象,還不在會話中,也沒有保存到數據庫。
  2. Pending:transient的對象調用add后,就會變成pending狀態,這時會加入sqlalchemy的監管範圍,數據並未更新到數據庫。
  3. Persistent:該狀態表明數據庫里已經記錄了該對象,在兩種情況下對象處於該狀態:一是通過flush()方法刷新pending對象,二是從數據庫query()得到對象。
  4. Detached:在會話中的事務提交之後,所有的對象都將是Detached狀態。

所謂的狀態跟蹤,就是跟蹤以上四個狀態,保證數據的準確性並在合理的時機丟棄對象以保證合理開銷,那麼具體是怎麼實現的呢?

我們可以看到,只有在pending狀態時,對象的內存數據和數據庫中的數據不一致,在Persistent狀態時,內存數據和數據庫數據已經一致,那麼此後任意時刻丟棄該對象數據都是可以的,這時就需要找個合適的時機丟棄對象,過早或過晚都有其缺陷。於是,就讓垃圾回收器來做決定,在內存不夠的時候釋放對象,回收內存。

Session對象採用了弱引用機制,所謂弱引用,就是說,在保存了對象的引用的情況下,對象仍然可能被垃圾回收器回收。在某一時刻通過引用訪問對象時,對象可能存在也可能不存在,如果對象不存在,就重新從數據庫中加載對象。而如果不希望對象被回收,只需要另外保存一個對象的強引用即可 。

session對象包括三個屬性:

  1. new:剛加入會話的對象
  2. dirty:剛被修改的對象
  3. deleted:在會話中被刪除的對象

三個屬性共同的特點就是內存的數據和數據庫數據不一致,也就是對象處於pending狀態,這也就表明了session保存了所有對象處於pending狀態的強引用。

以上。

代碼可參照:

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

spring 是如何注入對象的和bean 創建過程分析

文章目錄:

  1. 【本文】spring 是如何注入對象的

首先需要知道一個大致實現

  • 這個注入過程肯定是在 BeanPostProcessor 中實現的

  • spring 是在 beanFactory.getBean 進行 bean 實例化的,即懶加載

  • 根據第二條,也就是說在 getBean 的時候才會去調用所有 BeanPostProcessor

  • 第二篇文章說到,BeanFactory 的 refresh 過程只是註冊 BeanPostProcessor,真正執行在 getBean 方法中

  • MergedBeanDefinitionPostProcessor 也是一種 BeanPostProcessor 它重新弄了個一個生命周期函數,替代了 BeanPostProcessor 默認的生命周期函數,這麼看吧,我貼一小段源碼

    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof MergedBeanDefinitionPostProcessor) {
            MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
            bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
        }
    }

    它允許你在非 BeanFactoryProcess 中去修改 Bean 定義

  • InstantiationAwareBeanPostProcessor 也是一種 BeanPostProcessor 它也重新定義了一個生命周期函數,它允許把屬性值注入到屬性對象中

@Autowired 加載定義的過程

我們先不看 bean 的創建過程,就看 MergedBeanDefinitionPostProcessor 的實現子類,這裏看名字猜測 AutowiredAnnotationBeanPostProcessor 應該就是干這件事的,所以我們接下來可以直接看 AutowiredAnnotationBeanPostProcessor 的 postProcessMergedBeanDefinition 方法的代碼。

順着方法的調用,可以知道在 buildAutowiringMetadata 是真正查找這些註解的地方,最後 checkConfigMembersMember 註冊進了 bean 定義,具體如何查找的讀者自行查看源碼。

這裏只是將 Member 註冊進了 bean 定義,真正實例化在填充 Bean 的過程中,下面說到 bean 的創建過程可以知道是何時注入的。

Bean 的創建過程

前面說到 spring 是在 getBean 的過程中進行 Bean 創建的,創建 bean 分為幾個步驟

  1. 獲取 bean 定義
  2. new Bean()
  3. 執行生命周期函數 (前)
  4. 創建依賴項
  5. 填充 bean
  6. 執行生命周期函數(后)

入口為 BeanFactory.getBean ,BeanFactory 的實現類為 DefaultListableBeanFactory 這些你可以在 BeanFactory 的 refresh 過程中找到

根據源碼,如果 bean 還不存在時,就會執行 bean 的創建流程

獲取 bean 定義在這段源碼中

final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

緊跟着,根據 Bean 定義搜索其依賴項,並創建 bean ,可以看出是遞歸創建 bean

String[] dependsOn = mbd.getDependsOn();
for (String dep : dependsOn) {
    getBean(dep);
}

然後就創建 bean 了

if (mbd.isSingleton()) {
    createBean(beanName, mbd, args);
}

// 真正的執行在 doCreateBean 過程中
Object beanInstance = doCreateBean(beanName, mbdToUse, args);

創建 bean 第一步 new Bean

if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
}

創建 bean 第二步,執行所有的 processor ,包含 MergedBeanDefinitionPostProcessor ,所以在這一步註冊注入選項

applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);

創建 bean 第三步,填充 bean ,這裏做的 @Autowired 注入

populateBean(beanName, mbd, instanceWrapper);

最終的處理過程在 AutowiredAnnotationBeanPostProcessor 的 postProcessPropertyValues 函數中

metadata.inject(bean, beanName, pvs);

因為在前面已經獲取過依賴項,並且把其丟進了容器,所以這裡是直接用反射寫進去就可以了

創建 bean 第四步,初始化 bean ,這裡有一個方法注入,方法注入原來發生在初始化 bean 過程中,還有就是生命周期函數執行了,包含 BeanPostProcessor 的前置後置生命周期,初始化方法等

小說明 :AutowiredAnnotationBeanPostProcessor 即是 一個 MergedBeanDefinitionPostProcessor 也是一個 InstantiationAwareBeanPostProcessor

一點小推廣

創作不易,希望可以支持下我的開源軟件,及我的小工具,歡迎來 gitee 點星,fork ,提 bug 。

Excel 通用導入導出,支持 Excel 公式
博客地址:
gitee:

使用模板代碼 ,從數據庫生成代碼 ,及一些項目中經常可以用到的小工具
博客地址:
gitee:

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

《中國電動汽車標準化工作線圖》將於近日發佈

1月11日,《中國電動汽車標準化工作路線圖》評審會在北京召開。《路線圖》由中國汽車技術研究中心等單位聯合制定,將於近日發佈。

路線圖的定位

本版路線圖的起止階段為2015年至2025年,通過對2015年至2025年電動汽車及其相關產業的技術發展、產業化和市場化發展的判斷和預測,制定2015年至2025年中國電動汽車標準化工作的具體目標和工作計畫。在對國內外電動汽車標準和法規進行總結和歸納的基礎上,著重分析和研究中國電動汽車在未來的10年中,電動汽車及其相關產業的技術發展、產業化和市場化發展的標準需求,以此作為中國電動汽車快速健康發展的主要技術支撐。路線圖中標準化工作實施期限分為:緊急(2015年1月至2015年12月)、短期(2016年1月至2017年12月)、中期(2018年1月至2020年12月)和長期(2021年1月至2025年12月)共4個階段。由上述內容構成的本路線圖,將成為指導從2015年至2025年中國電動汽車標準化工作的綱領性檔。

路線圖的範圍

a)電動車輛:純電動汽車、混合動力電動汽車(包括插電式和增程式)、燃料電池電動汽車以及電動車輛相關零部件等;

b)基礎設施:充電樁、充電站、換電站和加氫站等;

c)相關產業:電動汽車及電池的運輸、回收利用、教育培訓、搶險救援等。

d) 本路線圖覆蓋基礎設施和相關產業的建設、運營、管理等。

路線圖的目標

路線圖的目標是有目的、有計劃、有步驟地建立起聯繫緊密、相互協調、層次分明、構成合理、相互支持、滿足應用需求的整體性、系統性、開放性的電動汽車標準體系,加速制定一系列由標準體系確定的具體標準,從而降低研究、生產、使用、維護及管理的成本和風險,使標準化工作發揮最佳效益,支撐和引導我國電動汽車產業的技術創新和快速發展,即:

——提出系統、科學的電動汽車標準體系;

——提出未來十年需要制修訂的標準專案;

——提出我國電動汽車標準及體系建設的實施步驟;

——促進科技創新;

——改變能源結構和推動節能減排;

——促進電動汽車大規模產業化;

——滿足科研、產業化、市場化運行和政府管理的需要;

——積極參與國際標準化工作,爭取中國標準走向世界。

路線圖的實施

通過對國內外電動汽車標準化工作的現狀的分析,為了滿足電動汽車產業的發展,未來標準的缺項進行了梳理,路線圖將通過表格和圖形的形式,對標準缺項進行系統的整理,按照時間序列來闡述從2015年1月至2025年12月共11年的標準制定計劃,計畫為4個階段(優先順序),分為緊急(2015年1月至2015年12月)、短期(2106年1月至2017年12月、中期(2018年1月至2020年12月)、長期(2021年1月至2025年12月)。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

重慶新建住宅將100%配備充電基礎設施

1月11日,重慶市政府辦公廳公佈《重慶市加快電動汽車充電基礎設施建設實施方案》。方案要求,新建住宅配建的停車庫必須100%建設電動汽車充電基礎設施或預留建設安裝條件。到2020年,主城區將累計建成不少於30座公共充換電站。

在主城區,凡新建的交通樞紐、超市賣場、商務樓宇,黨政機關、企事業單位辦公場所、學校、醫院、文化體育場館以及獨立用地的公共停車場、停車換乘(P+R)停車場等,按照不低於總停車位數量10%的比例,建設電動汽車充電基礎設施。

對已建成的住宅社區及上述場所,要通過改造、加裝等方式,到2020年,達到不低於10%的比例,提供充電基礎設施。

其他區縣(自治縣)城區要因地制宜,同樣按照不低於總停車位數量10%的比例,建設電動汽車充電基礎設施或預留建設安裝條件(包括預埋電力管線和預留電力容量)。

此外,還將建設電動汽車公共充換電站。到2020年,主城區原則上按服務半徑每1公里提供1座公共充換電站,累計建成不少於30座公共充換電站;其他每個區縣(自治縣)城區至少建成1座公共充換電站;每個重點旅遊景區至少建成1-2座公共充換電站;凡具備安全條件的加油站、加氣站、高速公路服務區等實現充換電設施全覆蓋。

不同場所執行不同電價 住宅區按合表用戶電價收費

對向電力企業直接報裝接電的經營性集中式電動汽車充換電設施用電,執行大工業用電價格,2020年前暫免收取基本電費;其他充電基礎設施用電按其所在場所執行分類目錄電價。其中,向電力企業報裝的居民住宅區充電基礎設施用電,執行居民用電價格中的合表用戶電價;對居民自用充電基礎設施的用電與家庭用電分表計量,執行居民生活用電價格,且不納入居民生活用電階梯電價計算範圍;黨政機關、企事業單位和社會公共停車場等場所的充電基礎設施用電,執行一般工商業及其他類用電價格。電動汽車充電基礎設施用電按重慶市峰穀分時電價相關政策執行。

電動汽車充電服務企業向使用者收取的充電服務費執行政府指導價,針對不同類別充電基礎設施,以電價為計費依據,服務費暫按每千瓦時不超過執行電價的50%收取,試行一年;試行期滿後,結合市場發展情況,逐步放開充電服務費,由市場競爭形成價格。

此外,在安裝充電設施時,也將簡化審批手續。個人在自有停車庫、停車位元,各居住區、單位在已有停車泊位安裝電動汽車充電設施的,無需辦理建設用地規劃許可證、建設工程規劃許可證和施工許可證。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

【algo&ds】2.線性表

1.線性表

線性表(英語:Linear List)是由n(n≥0)個元素()a[0],a[1],a[2]…,a[n-1]組成的。

其中:

  • 數據元素的個數n定義為表的長度 = “list”.length() (”list”.length() = 0(表裡沒有一個元素)時稱為空表)
  • 將非空的線性表(n>=1)記作:(a[0],a[1],a[2],…,a[n-1])
  • 數據元素a[i](0≤i≤n-1)只是個抽象符號,其具體含義在不同情況下可以不同

一個數據元素可以由若干個數據項組成。數據元素稱為記錄,含有大量記錄的線性表又稱為文件。這種結構具有下列特點:存在一個唯一的沒有前驅的(頭)數據元素;存在一個唯一的沒有後繼的(尾)數據元素;此外,每一個數據元素均有一個直接前驅和一個直接後繼數據元素。

2.線性表的存儲結構

  • 鏈表
    • 單鏈表
      • 動態單鏈表
      • 靜態單鏈表
    • 循環鏈表
      • 單循環鏈表
      • 雙循環鏈表
    • 靜態鏈表

3.順序表

利用數組的連續存儲空間順序存放線性表的各元素

3.1結構體定義

如果需要使用自定義的結構體來維護一個順序表,通常來講結構體的元素一般是一個固定大小的數組(可用長度足夠大),以及當前數組存放的元素個數,也即數組的長度

typedef struct LNode *List;
struct LNode {
    ElementType Data[MAXSIZE];
    int Last;//記錄順序表的最後一個元素的下標
} ;
struct LNode L;
List PtrL;

訪問結構體的成員

  • 訪問下標為 i 的元素:L.Data[i] 或 PtrL->Data[i]
  • 線性表的長度:L.Last+1 或 PtrL->Last+1
  • 指針變量PtrL還可以這樣訪問兩個屬性(*PtrL).Data[i](*PtrL).Last,不過這種訪問方式並不常用

而一般來講,為了簡單,不會去維護這樣一個結構體,(因為一旦維護了這個結構體,就需要去封裝相應的函數,比如說常見的插入、刪除、查找等操作),而是直接類似下面這樣

ElementType data[MaxSize];
int length;

定義一個足夠大的數組,然後定義一個對應關聯的變量來時刻維護數組的長度。

這兩種定義方式沒有什麼區別,一種是把常用操作封裝好,方便調用,另一種則是需要時刻自己維護對應的屬性。因為順序表的結構足夠簡單,所以不定義結構體也是可以的。

3.2順序表的常見操作

為了方便,這一節內容記錄的都是在定義的結構體基礎上,封裝的常見操作。

1.初始化

List MakeEmpty( ) {
    List PtrL;
    PtrL = (List )malloc( sizeof(struct LNode) );
    PtrL->Last = -1;
    return PtrL;
}

初始化的順序表,長度為0,所以Last為-1

2.查找

int Find( ElementType X, List PtrL ) {
    int i = 0;
    while( i <= PtrL->Last && PtrL->Data[i]!= X )
        i++;
    if (i > PtrL->Last) return -1; /* 如果沒找到,返回-1 */
    else return i; /* 找到后返回的是存儲位置 */
}

查找操作比較簡單,從順序表的第一個元素(下標為0開始)開始遍歷。

還有一種更加巧妙一點的實現方式,就是引入哨兵思想。

int Find( ElementType X, List PtrL ) {
    PtrL->Data[0] = x;//順序表第一個元素就是哨兵,賦值為x
    int i = PtrL->Last;//從最後一個元素開始遍歷
    while( PtrL->Data[i]!= X )
        i--;
    return i;
}

這樣做的好處很明顯,少了邊界的判斷,可以優化時間複雜度,編碼也更加簡單。

注意:這裏把下標為0的元素設置為哨兵,則要求順序表從下標為1開始存儲。而且,函數如果沒有找到,則一定返回i=0

3.插入

看圖示應該要注意,移動的方向是從后往前移,如果從前往後移,則Data[i]=Data[i+1]=…=Data[n],因為後面的元素都被前面移過來的元素給覆蓋了。

void Insert( ElementType X, int i, List PtrL ) {
    int j;
    if ( PtrL->Last == MAXSIZE-1 ) { /* 表空間已滿,不能插入*/
        printf("表滿");
        return;
    }
    if ( i < 1 || i > PtrL->Last+2) { /*檢查插入位置的合法性*/
        printf("位置不合法");
        return;
    }
    for ( j = PtrL->Last; j >= i-1; j-- )
        PtrL->Data[j+1] = PtrL->Data[j]; /*將 ai~ an倒序向後移動*/
    PtrL->Data[i-1] = X; /*新元素插入*/
    PtrL->Last++; /*Last仍指向最後元素*/
    return;
}

為什麼這裏需要判斷順序表的空間是否已滿?

因為這個數組,是在初始化之後就固定了數組可容納的元素個數MaxSize,一旦超出,則程序下標就會越界。C++提供了動態數組vector,可以很方便的支持動態擴展數組長度,而且基本的插入刪除等操作都封裝好了,可以很方便的使用。

4.刪除

同樣的,需要注意元素移動的方向,如果從后往前移,則最後面的元素會一直覆蓋到Data[i]。

void Delete( int i, List PtrL ) {
    int j;
    if( i < 1 || i > PtrL->Last+1 ) { /*檢查空表及刪除位置的合法性*/
        printf (“不存在第%d個元素”, i );
        return ;
    }
    for ( j = i; j <= PtrL->Last; j++ )
        PtrL->Data[j-1] = PtrL->Data[j]; /*將 ai+1~ an順序向前移動*/
    PtrL->Last--; /*Last仍指向最後元素*/
    return;
}

5.排序

因為排序算法比較多,本文不展開講解,可以參考以下博文,內容包括了常見的十大排序算法的算法分析,時間複雜度和空間複雜度分析以及c實現和動圖圖解。

4.鏈表

不要求邏輯上相鄰的兩個元素物理上也相鄰;通過“鏈”建立起數據元素之間的邏輯關係。插入、刪除不需要移動數據元素,只需要修改“鏈”。

4.1單鏈表

typedef struct LNode *List;
struct LNode {
    ElementType Data;
    List Next;
};
struct Lnode L;
List PtrL;
1.求表長
int Length ( List PtrL ) {
    List p = PtrL; /* p指向表的第一個結點*/
    int j = 0;
    while ( p ) {
        p = p->Next;
        j++; /* 當前p指向的是第 j 個結點*/
    }
    return j;
}

時間複雜度O(n)

2.查找

按序查找

List FindKth( int K, List PtrL ) {
    List p = PtrL;
    int i = 1;
    while (p !=NULL && i < K ) {
        p = p->Next;
        i++;
    }
    if ( i == K ) return p;
    /* 找到第K個,返回指針 */
    else return NULL;
    /* 否則返回空 */
}

時間複雜度O(n)

按值查找

List Find( ElementType X, List PtrL ) {
    List p = PtrL;
    while ( p!=NULL && p->Data != X )
        p = p->Next;
    return p;
}

時間複雜度O(n)

3.插入

(1)先構造一個新結點,用s指向;
(2)再找到鏈表的第 i-1個結點,用p指向;
(3)然後修改指針,插入結點 ( p之後插入新結點是 s)

List Insert( ElementType X, int i, List PtrL ) {
    List p, s;
    if ( i == 1 ) { /* 新結點插入在表頭 */
        s = (List)malloc(sizeof(struct LNode)); /*申請、填裝結點*/
        s->Data = X;
        s->Next = PtrL;
        return s; /*返回新表頭指針*/
    }
    p = FindKth( i-1, PtrL ); /* 查找第i-1個結點 */
    if ( p == NULL ) { /* 第i-1個不存在,不能插入 */
        printf("參數i錯");
        return NULL;
    } else {
        s = (List)malloc(sizeof(struct LNode)); /*申請、填裝結點*/
        s->Data = X;
        s->Next = p->Next; /*新結點插入在第i-1個結點的後面*/
        p->Next = s;
        return PtrL;
    }
}
4.刪除

(1)先找到鏈表的第 i-1個結點,用p指向;
(2)再用指針s指向要被刪除的結點(p的下一個結點);
(3)然後修改指針,刪除s所指結點;
(4)最後釋放s所指結點的空間。

List Delete( int i, List PtrL ) {
    List p, s;
    if ( i == 1 ) { /* 若要刪除的是表的第一個結點 */
        s = PtrL; /*s指向第1個結點*/
        if (PtrL!=NULL) PtrL = PtrL->Next; /*從鏈表中刪除*/
        else return NULL;
        free(s); /*釋放被刪除結點 */
        return PtrL;
    }
    p = FindKth( i-1, PtrL ); /*查找第i-1個結點*/
    if ( p == NULL ) {
        printf("第%d個結點不存在", i-1);
        return NULL;
    } else if ( p->Next == NULL ) {
        printf("第%d個結點不存在", i);
        return NULL;
    } else {
        s = p->Next; /*s指向第i個結點*/
        p->Next = s->Next; /*從鏈表中刪除*/
        free(s); /*釋放被刪除結點 */
        return PtrL;
    }
}

4.2雙鏈表

雙向鏈表,又稱為雙鏈表,是的一種,它的每個數據結點中都有兩個,分別指向直接後繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。

typedef struct DuLNode {
    ElemType data;
    struct DuLNode *prior, *next;
} DuLNode, *DuLinkList;

4.3循環鏈表

4.3.1單循環鏈表

存儲結構和單鏈表相同。

typedef struct LNode {
    ElemType data;
    struct LNode *next;
} LNode, *LinkList;

// 設立尾指針的單循環鏈表的12個基本操作
void InitList(LinkList *L) { // 操作結果:構造一個空的線性表L
    *L = (LinkList)malloc(sizeof(struct LNode)); // 產生頭結點,並使L指向此頭結點
    if (!*L) // 存儲分配失敗
        exit(OVERFLOW);
    (*L)->next = *L; // 指針域指向頭結點
}

void DestroyList(LinkList *L) { // 操作結果:銷毀線性表L
    LinkList q, p = (*L)->next; // p指向頭結點
    while (p != *L) { // 沒到表尾
        q = p->next;
        free(p);
        p = q;
    }
    free(*L);
    *L = NULL;
}

void ClearList(LinkList *L) /* 改變L */ { // 初始條件:線性表L已存在。操作結果:將L重置為空表
    LinkList p, q;
    *L = (*L)->next; // L指向頭結點
    p = (*L)->next; // p指向第一個結點
    while (p != *L) { // 沒到表尾
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = *L; // 頭結點指針域指向自身
}

Status ListEmpty(LinkList L) { // 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE
    if (L->next == L) // 空
        return TRUE;
    else
        return FALSE;
}

int ListLength(LinkList L) { // 初始條件:L已存在。操作結果:返回L中數據元素個數
    int i = 0;
    LinkList p = L->next; // p指向頭結點
    while (p != L) { // 沒到表尾
        i++;
        p = p->next;
    }
    return i;
}

Status GetElem(LinkList L, int i, ElemType *e) { // 當第i個元素存在時,其值賦給e並返回OK,否則返回ERROR
    int j = 1; // 初始化,j為計數器
    LinkList p = L->next->next; // p指向第一個結點
    if (i <= 0 || i > ListLength(L)) // 第i個元素不存在
        return ERROR;
    while (j < i) { // 順指針向後查找,直到p指向第i個元素
        p = p->next;
        j++;
    }
    *e = p->data; // 取第i個元素
    return OK;
}

int LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) { // 初始條件:線性表L已存在,compare()是數據元素判定函數
    // 操作結果:返回L中第1個與e滿足關係compare()的數據元素的位序。
    //           若這樣的數據元素不存在,則返回值為0
    int i = 0;
    LinkList p = L->next->next; // p指向第一個結點
    while (p != L->next) {
        i++;
        if (compare(p->data, e)) // 滿足關係
            return i;
        p = p->next;
    }
    return 0;
}

Status PriorElem(LinkList L, ElemType cur_e, ElemType *pre_e) { // 初始條件:線性表L已存在
    // 操作結果:若cur_e是L的數據元素,且不是第一個,則用pre_e返回它的前驅,
    //           否則操作失敗,pre_e無定義
    LinkList q, p = L->next->next; // p指向第一個結點
    q = p->next;
    while (q != L->next) { // p沒到表尾
        if (q->data == cur_e) {
            *pre_e = p->data;
            return TRUE;
        }
        p = q;
        q = q->next;
    }
    return FALSE; // 操作失敗
}

Status NextElem(LinkList L, ElemType cur_e, ElemType *next_e) { // 初始條件:線性表L已存在
    // 操作結果:若cur_e是L的數據元素,且不是最後一個,則用next_e返回它的後繼,
    //           否則操作失敗,next_e無定義
    LinkList p = L->next->next; // p指向第一個結點
    while (p != L) { // p沒到表尾
        if (p->data == cur_e) {
            *next_e = p->next->data;
            return TRUE;
        }
        p = p->next;
    }
    return FALSE; // 操作失敗
}

Status ListInsert(LinkList *L, int i, ElemType e) /* 改變L */ { // 在L的第i個位置之前插入元素e
    LinkList p = (*L)->next, s; // p指向頭結點
    int j = 0;
    if (i <= 0 || i > ListLength(*L) + 1) // 無法在第i個元素之前插入
        return ERROR;
    while (j < i - 1) { // 尋找第i-1個結點
        p = p->next;
        j++;
    }
    s = (LinkList)malloc(sizeof(struct LNode)); // 生成新結點
    s->data = e; // 插入L中
    s->next = p->next;
    p->next = s;
    if (p == *L) // 改變尾結點
        *L = s;
    return OK;
}

Status ListDelete(LinkList *L, int i, ElemType *e) /* 改變L */ { // 刪除L的第i個元素,並由e返回其值
    LinkList p = (*L)->next, q; // p指向頭結點
    int j = 0;
    if (i <= 0 || i > ListLength(*L)) // 第i個元素不存在
        return ERROR;
    while (j < i - 1) { // 尋找第i-1個結點
        p = p->next;
        j++;
    }
    q = p->next; // q指向待刪除結點
    p->next = q->next;
    *e = q->data;
    if (*L == q) // 刪除的是表尾元素
        *L = p;
    free(q); // 釋放待刪除結點
    return OK;
}

void ListTraverse(LinkList L, void(*vi)(ElemType)) { // 初始條件:L已存在。操作結果:依次對L的每個數據元素調用函數vi()
    LinkList p = L->next->next; // p指向首元結點
    while (p != L->next) { // p不指向頭結點
        vi(p->data);
        p = p->next;
    }
    printf("\n");
}

4.3.2雙循環鏈表

// 線性表的雙向鏈表存儲結構
typedef struct DuLNode {
    ElemType data;
    struct DuLNode *prior, *next;
} DuLNode, *DuLinkList;

// 帶頭結點的雙向循環鏈表的基本操作(14個)
void InitList(DuLinkList *L) {
    // 產生空的雙向循環鏈表L
    *L = (DuLinkList)malloc(sizeof(DuLNode));
    if (*L)
        (*L)->next = (*L)->prior = *L;
    else
        exit(OVERFLOW);
}

void DestroyList(DuLinkList *L) {
    // 操作結果:銷毀雙向循環鏈表L
    DuLinkList q, p = (*L)->next; // p指向第一個結點
    while (p != *L) { // p沒到表頭
        q = p->next;
        free(p);
        p = q;
    }
    free(*L);
    *L = NULL;
}

void ClearList(DuLinkList L) { // 不改變L
    // 初始條件:L已存在。操作結果:將L重置為空表
    DuLinkList q, p = L->next; // p指向第一個結點
    while (p != L) { // p沒到表頭
        q = p->next;
        free(p);
        p = q;
    }
    L->next = L->prior = L; // 頭結點的兩個指針域均指向自身
}

Status ListEmpty(DuLinkList L) {
    // 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE
    if (L->next == L && L->prior == L)
        return TRUE;
    else
        return FALSE;
}

int ListLength(DuLinkList L) {
    // 初始條件:L已存在。操作結果:返回L中數據元素個數
    int i = 0;
    DuLinkList p = L->next; // p指向第一個結點
    while (p != L) { // p沒到表頭
        i++;
        p = p->next;
    }
    return i;
}

Status GetElem(DuLinkList L, int i, ElemType *e) {
    // 當第i個元素存在時,其值賦給e並返回OK,否則返回ERROR
    int j = 1; // j為計數器
    DuLinkList p = L->next; // p指向第一個結點
    while (p != L && j < i) { // 順指針向後查找,直到p指向第i個元素或p指向頭結點
        p = p->next;
        j++;
    }
    if (p == L || j > i) // 第i個元素不存在
        return ERROR;
    *e = p->data; // 取第i個元素
    return OK;
}

int LocateElem(DuLinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) {
    // 初始條件:L已存在,compare()是數據元素判定函數
    // 操作結果:返回L中第1個與e滿足關係compare()的數據元素的位序。
    // 若這樣的數據元素不存在,則返回值為0
    int i = 0;
    DuLinkList p = L->next; // p指向第1個元素
    while (p != L) {
        i++;
        if (compare(p->data, e)) // 找到這樣的數據元素
            return i;
        p = p->next;
    }
    return 0;
}

Status PriorElem(DuLinkList L, ElemType cur_e, ElemType *pre_e) {
    // 操作結果:若cur_e是L的數據元素,且不是第一個,則用pre_e返回它的前驅,
    // 否則操作失敗,pre_e無定義
    DuLinkList p = L->next->next; // p指向第2個元素
    while (p != L) { // p沒到表頭
        if (p->data == cur_e) {
            *pre_e = p->prior->data;
            return TRUE;
        }
        p = p->next;
    }
    return FALSE;
}

Status NextElem(DuLinkList L, ElemType cur_e, ElemType *next_e) {
    // 操作結果:若cur_e是L的數據元素,且不是最後一個,則用next_e返回它的後繼,
    // 否則操作失敗,next_e無定義
    DuLinkList p = L->next->next; // p指向第2個元素
    while (p != L) { // p沒到表頭
        if (p->prior->data == cur_e) {
            *next_e = p->data;
            return TRUE;
        }
        p = p->next;
    }
    return FALSE;
}

DuLinkList GetElemP(DuLinkList L, int i) { // 另加
    // 在雙向鏈表L中返回第i個元素的地址。i為0,返回頭結點的地址。若第i個元素不存在,
    // 返回NULL
    int j;
    DuLinkList p = L; // p指向頭結點
    if (i < 0 || i > ListLength(L)) // i值不合法
        return NULL;
    for (j = 1; j <= i; j++)
        p = p->next;
    return p;
}

Status ListInsert(DuLinkList L, int i, ElemType e) {
    // 在帶頭結點的雙鏈循環線性表L中第i個位置之前插入元素e,i的合法值為1≤i≤表長+1
    // 改進算法2.18,否則無法在第表長+1個結點之前插入元素
    DuLinkList p, s;
    if (i < 1 || i > ListLength(L) + 1) // i值不合法
        return ERROR;
    p = GetElemP(L, i - 1); // 在L中確定第i個元素前驅的位置指針p
    if (!p) // p=NULL,即第i個元素的前驅不存在(設頭結點為第1個元素的前驅)
        return ERROR;
    s = (DuLinkList)malloc(sizeof(DuLNode));
    if (!s)
        return OVERFLOW;
    s->data = e;
    s->prior = p; // 在第i-1個元素之後插入
    s->next = p->next;
    p->next->prior = s;
    p->next = s;
    return OK;
}

Status ListDelete(DuLinkList L, int i, ElemType *e) {
    // 刪除帶頭結點的雙鏈循環線性表L的第i個元素,i的合法值為1≤i≤表長
    DuLinkList p;
    if (i < 1) // i值不合法
        return ERROR;
    p = GetElemP(L, i); // 在L中確定第i個元素的位置指針p
    if (!p) // p = NULL,即第i個元素不存在
        return ERROR;
    *e = p->data;
    p->prior->next = p->next; // 此處並沒有考慮鏈表頭,鏈表尾
    p->next->prior = p->prior;
    free(p);
    return OK;
}

void ListTraverse(DuLinkList L, void(*visit)(ElemType)) {
    // 由雙鏈循環線性表L的頭結點出發,正序對每個數據元素調用函數visit()
    DuLinkList p = L->next; // p指向頭結點
    while (p != L) {
        visit(p->data);
        p = p->next;
    }
    printf("\n");
}

void ListTraverseBack(DuLinkList L, void(*visit)(ElemType)) {
    // 由雙鏈循環線性表L的頭結點出發,逆序對每個數據元素調用函數visit()
    DuLinkList p = L->prior; // p指向尾結點
    while (p != L) {
        visit(p->data);
        p = p->prior;
    }
    printf("\n");
}

4.4靜態鏈表

前面講解的都是動態鏈表,即需要指針來建立結點之間的連接關係。而對有些問題來說結點的地址是比較小的整數(例如5位數的地址),這樣就沒有必要去建立動態鏈表,而應使用方便得多的靜態鏈表。
靜態鏈表的實現原理是hash,即通過建立一個結構體數組,並令數組的下標直接表示結點的地址,來達到直接訪問數組中的元素就能訪問結點的效果。另外,由於結點的訪問非常方便,因此靜態鏈表是不需要頭結點的。靜態鏈表結點定義的方法如下:

struct Node{
    typename data;//數據域
    int next;//指針域
}node[size];

參考資料:

  • 《算法筆記》
  • 《數據結構和算法》-極客時間專欄

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

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

分類
發燒車訊

Hazel,自動整理文件,讓你的 Mac 井井有條

原文地址

讓我們從實際需求出發,看看問題出在哪裡,並在此基礎上認識和學習使用 Hazel。

電腦隨着使用時間的增長,其中的文件也在瘋狂的增長,時間長了也就會出現各種混亂:大量文件堆放在一起,舊文件很少清理,分不清哪些文件還有用,找不到需要的文件等等。

今天我們就以「下載」和「桌面」為例,聊一聊如何整理我們的電腦。

Downloads:下載的文件很少處理,時間一長就各種堆積…… 

Desktop:經常把臨時文件存放在此,方便拖拽使用,但時間一長,就是各種凌亂……

既然知道了問題所在,那麼我們就來着手整理吧。

理清整理思路

首先是確定整理思路,比如如何界定一個文件是否還有用,如何界定它屬於什麼分類等,對應的操作一般是刪除(比如不再需要的或重複的文件)或存檔(學習資料或工作材料等分類存儲),知道如何處理一個文件就很好辦了,剩下的就都是體力活兒。

雖然這不是一件特別麻煩的事,但是我們也經常忘記或「懶得整理」。這有點類似於打掃房間,當我們沒有時間或者經常忘記時,可以買一台掃地機器人幫助我們打掃,同樣的,在 Mac 上也有這樣一台「機器人」,它就是 Hazel。

Hazel 是什麼?

Hazel 是一款可以自動監控並整理文件夾的工具,其官網的介紹就是簡單的一句話:Automated Organization for Your Mac。

它的使用有點類似於網絡服務 IFTTT,你可以設定一個 if 條件,如果被監控的文件夾出現符合條件的項,那麼對其執行 then 的操作(也可以通過郵箱的收件過濾規則來理解)。

Hazel 不是一款新工具,它已經有了很長的歷史,其第一個版本在 2006 年底就已經發布,在今年 5 月 4 號,Hazel 發布了 4.0 版本,新增了規則同步(文末會有介紹)、規則搜索等一系列實用功能。

Hazel 具體能做什麼?

先為大家簡單羅列一些 Hazel 能做到的事情:

  • 根據文件創建的時間,自動將文件進行顏色標記(比如將最近的文件標記為藍色)
  • 自動的用特定軟件打開某個特定文件(比如下載 BT 種子后,自動用迅雷打開下載)
  • 自動刪除已下載過的 BT 種子文件
  • 根據文件的類型,自動轉移到相應的文件夾中(比如圖片移動到照片文件夾,電影移動到視頻文件夾等)
  • 自動刪除某些特定文件(比如標題中含有固定內容且創建日期在很早以前的)
  • 自動將壓縮文件解壓
  • 自動幫你清理文件的緩存
  • 自動幫你整理照片,可以按照「年 – 月」來分類存儲到相應文件夾
  • 自動把文件夾中的內容上傳到 FTP 等網絡服務中
  • 自動將照片導入 Photos,自動將音樂導入 iTunes 
  • ……

以上只是列舉的一些場景能夠實現的功能,再加上 Hazel 支持 AppleScript、JavaScript、Automator workflow 等代碼指令,令其擴展性更上一層樓,可以做到的事情也可以說只剩下想象力這道門檻了。

介紹了不少,下面我們就從 Hazel 的安裝和實際設置來為大家做一個簡單的入門指南。

Hazel 的安裝

前往下載最新版本,按照提示安裝,完成后 Hazel 會出現在系統設置中(在應用程序中可找不到哦)。
Hazel 是一款收費軟件,初次安裝后可以免費試用 14 天,此時可以選擇加載一些簡單的默認規則以幫助你快速上手(當然看完這篇文章也就可以不用加載了)。

操作后 Hazel 會給我們彈出警告信息:在激活這些規則之前,一定要先檢查它們。具體的方法下面會提及。

Hazel 的界面和基礎應用

注:文末提供了文中所有 Hazel 規則的打包下載地址,如果你對文中介紹的規則感興趣,可以直接下載使用。

Hazel 的主界面包含三部分,分別是設置文件夾規則的 Folders 頁面,設置垃圾箱規則的 Trash 頁面和其他信息頁(Info),今天主要給大家講解文件夾規則設置頁面。

在 Folders 中包含三部份:設置監控的文件夾(圖中 1),設置該文件夾下的具體規則(圖中 2),設置該文件夾的重複文件處理(圖中 3),圖 1 部分右側的 icon 分別表示「暫停規則執行」和「同步」,建議嘗試新規則的時候先暫停執行再進行調試。

以整理「下載」文件夾為例,我個人的需求有如下幾條:

  • 最近的下載文件用顏色標籤提醒
  • 超過 3 天的文件不再是新文件,去掉顏色標籤
  • 對存放超過 3 周的文件需進行處理,將滿足此條件的文件用紅色標記提醒
  • 自動刪除已使用的 .torrent 文件
  • 將手機截屏的圖片單獨存放

上面幾條是梳理自己的整理需求后,選擇的可以被 Hazel 自動執行的。此時回到 Hazel,我們點擊左下角的加號新增「下載」文件夾,隨後在右側 Rules 區域點擊加號新增規則。

標記最新下載文件

下圖是規則設置界面,圖 1 部分設置規則名稱和註釋;圖 2 部分設置監控條件,此時設置的是文件添加時間在最後匹配時間之前(新文件添加后暫未被匹配,所以一定是早於匹配時間);圖 3 部分設置執行的動作,此時是將匹配出來的文件標記藍色標籤,並且同時可以被其他規則匹配。

標記舊文件

超過 3 天的文件,不再是我需要關注的內容,將其中的藍色標籤去掉:

標記待處理文件

對「下載」文件夾,我需要對超過 3 周未處理的文件進行處理,要麼歸檔要麼刪除,需要進行人工判斷的時候我使用紅色標記來提醒自己:

刪除 .torrent 文件

在使用 BT 下載之後,留在文件夾的種子文件也就沒有什麼用了,為了防止誤刪設置了 5 天的期限,注意圖中綠色符號,那是點擊了 Preview 后的效果,建議設置規則的時候多使用 Preview 功能來檢查條件設置是否正確,特別是那些複雜的符合條件。

自動移動手機截屏文件

工作關係,經常需要在手機上截屏上傳到電腦使用(使用 AirDrop 上傳到「下載」中),這類圖片的處理一般是超過一周后移動到桌面文件夾中再進行集中處理:

上面介紹了「下載」文件夾的整理思路和執行;對於「桌面」文件夾的整理,我的思路一般是不輕易自動刪除(防誤刪),而是統一到分類文件夾中集中處理。將文檔存放於「文檔」中,將圖片存放於「圖片」中等等,都是非常簡單和基礎的設置,就不做過多介紹;

下面說一下我對源文件的處理,這裏涉及到條件的嵌套使用:

圖中使用了嵌套條件,具體的操作是鼠標長按右側加號(也可按住 Option 後點擊),即可增加嵌套條件組。

附上桌面整理后截圖:

Hazel 中級應用

除了以上的基礎使用,Hazel 還可作用於更加廣泛的場景,下面以自動解壓自動清理緩存為例。

自動解壓

下載壓縮包后不用手動解壓,Hazel 會自動創建文件夾(按照壓縮包的名稱命名),並將壓縮包和解壓后的文件存放於此:

有三點需要為大家說明:

  • 設置標籤是為了防止壓縮文件有損壞而導致 Hazel 陷入循環執行中;
  • 不能設置自動刪除,因為 Hazel 會自動選中解壓后的文件,此時的刪除也只是把解壓后的文件刪掉;
  • 使用默認的「Unarchive」操作也可解壓,不過在解壓 .zip 文件後會自動將壓縮包刪掉,所以我這裏使用了第三方的免費解壓軟件  代替(注意:在第一次執行時需要權限設置);不介意刪除壓縮包的同學使用默認的解壓操作即可。

此規則參考了  的博客,特此感謝。

自動清理緩存

以 QQ 為例,QQ 會把群消息中的圖片自動保存到本地,時間一長這個文件夾就很容易達到幾個 G 的大小,這時候 Hazel 又可以派上用處了。

首先找到你的 QQ 文件夾,可嘗試如下路徑(本人 Mac 系統 10.11)

/Users/用戶名/Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ

將路徑中的「用戶名」換成自己的,然後在 Finder 中按住「⌘ + Shift + G」,把路徑粘貼到輸入框中點擊「前往」即可。

如果路徑沒問題,就可以在 Hazel 中添加此文件夾了,點擊添加按鈕彈出選擇文件夾界面后,使用上述快捷鍵和路徑同樣可以快速選定,添加後設置如下兩條規則,第一條規則的作用是讓所有子文件夾都可以適配規則並執行操作;第二條規則是把超過 500M 的子文件夾進行刪除操作,且不會直接刪除父文件夾。

至此,QQ 緩存文件的自動清理就設置完成了,其他軟件緩存也可以進行類似的規則設計,不過一定要注意確保這裏面沒有你需要的文件,否則一旦刪除要找回也是頗為麻煩的。

更多用法

如前文所說,Hazel 能做到的不止這些場景,還有用戶用它來整理照片,利用 AppleScript 執行更加複雜的工作流程等等,這裏僅當作拋磚引玉,歡迎大家分享自己的用法,並且以後也會有更多關於 Hazel 使用技巧的文章。 

其他功能

管理垃圾箱

在 Hazel 的 Trash 頁面,可以進行一些垃圾箱的設置,比如將其中超過一周的文件刪除,保持垃圾箱大小控制在 2GB 左右,選擇刪除時是否使用安全刪除功能,以及卸載應用時檢測其附屬文件夾等等;這方面的功能筆者並不常用,在此不做過多介紹。

刪除應用時檢測相關文件,並可選擇一併刪除。作用類似於 。

同步規則

同步功能在 4.0 終於推出,現在也可以方便的使用在多台電腦上了。點擊左側面板中的齒輪圖標,選擇 Rule Sync Options 即可打開同步界面(也可在文件夾上右鍵選擇 Rule Sync Options)。

同步需要配合第三方同步網盤使用,當前文件夾若是第一次使用同步,需要設置同步文件存放路徑,點擊 Set up new sync file 即可。如果要使用同步的文件,在界面中點擊 Use existing sync file 即可。

Hazel 的下載

Hazel 是一款收費軟件(),五月初的時候發布了 4.0 版本,單獨購買是 $32,Family Pack $49,從 3.0 版本升級需要 $10。初次下載可以免費試用 14 天,建議大家先試用再購買。

最後給大家提供我自己的 Hazel 設置,你可以導入后調整為適合自己的規則再使用:。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

PowerMock學習(一)之PoweMock的入門

關於powermock

在TDD領域Mock框架有很多,比如EasyMock,JMock,Mockito。可能有些同學會好奇了,為什麼要重點把powermock拿出來呢,因為powermock可以解決前面三種框架不能解決的問題,而且powermock也是是單元測試中極其強大的測試框架。

powermock特點

  • 主要圍繞着Junit、TestNg測試框架開展進行
  • 對所依賴的Jar包非常的苛刻,出現jar包的衝突或者不一致就不能使用
  • PowerMock也是一種Mock,主要是解決其他Mock不能解決的問題,通俗的講,就是專治各種不服

powermock入門實例

1、引入依賴jar包

<dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>1.6.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito</artifactId>
            <version>1.6.1</version>
            <scope>compile</scope>
        </dependency>

 

2、實際案例

模擬場景:新增學生操作

先建一個名為StudentService的類,用來模擬服務調用操作,在這個類中新增一個方法,來模擬查詢總共有多少個學生。

具體示例代碼如下:

package com.rongrong.powermock.service;

import com.rongrong.powermock.dao.StudentDao;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/17 21:13
 */
public class StudentService {
    private StudentDao studentDao;

    public StudentService(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    /**
     * 獲取學生個數
     *
     * @param studentDao
     */
    public int getTotal(StudentDao studentDao) {
        return studentDao.getTotal();
    }
}

可以看出創建service需要傳遞StudentDao這個類,接着我們再來創建StudentDao這個類,用於進行新增操作。

具體示例代碼如下:

package com.rongrong.powermock.dao;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/17 21:15
 */
public class StudentDao {
    public int getTotal() {
        throw new UnsupportedOperationException();
    }
}

 

仔細看,你會發現,你肯定調不了dao了,這回傻了吧,哈哈哈!!!

你會好奇這塊為啥我要拋出UnsupportedOperationException異常呢,因為我就想模擬服務不可用的情況(實際中經常會遇到可能由於某種原因(沒有完成,或者資源不存在等)無法為 Service 服務),這樣的情況,難道我們就不測試了嗎?

那我還是乖乖的把測試用例寫完,並測試下吧,下面我們再來創建一個名為StudentServiceTest的測試類。

具體示例代碼如下:

package com.rongrong.powermock.service;

import com.rongrong.powermock.dao.StudentDao;
import org.testng.annotations.Test;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/17 21:19
 */
public class StudentServiceTest {
    @Test
    public void testAddStudent() {
        StudentDao studentDao = new StudentDao();
        StudentService studentService = new StudentService(studentDao);
        studentService.getTotal(studentDao);
    }

}

 

上面的測試用例肯定會執行失敗,那我們也來執行下看,效果如下圖:

 

我們先將這個報錯,腦補為鏈接不上數據庫,問題很明顯,數據庫掛了,就是連接不上了,等着服務器好了得三天後,可是今晚領導就要看功能實現,你該怎麼辦?無法測試service,難道就真的結束了嗎?
答案是否定的,此時我們用powermock便可完美解決問題,接下來我們請出powermock登場。

具體代碼如下:

package com.rongrong.powermock.service;

import com.rongrong.powermock.dao.StudentDao;
import org.powermock.api.mockito.PowerMockito;
import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/17 21:19
 */
public class StudentServiceTest {

    @Test
    public void testGetStudentTotal() {
        StudentDao studentDao = PowerMockito.mock(StudentDao.class);
        PowerMockito.when(studentDao.getTotal()).thenReturn(666);
        StudentService studentService = new StudentService(studentDao);
        int total = studentService.getTotal(studentDao);
        Assert.assertEquals(total, 666);
    }


}

 

這時再次運行,你會發現神奇般的運行通過,結果如下圖所示:

 

是不是很神奇,很驚喜,沒錯,這個框架就是這麼強大。

我們可以這樣理解mock就是創建一個假的該對象,然後需要你告訴這個對象調用某個方法的時候返回某個你指定的值即可。

到此,一個簡單powermock入門結束,如您覺得好,請繼續關注我,謝謝支持!

 

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

北汽新能源全新戰略:十三五末期產能達80萬輛以上

在產業佈局方面,北汽新能源將以“1(北京采育基地)+2(常州基地、青島基地)+I(北汽集團內部傳統乘用車生產基地)+P(社會合作夥伴生產資源)>80”為基礎,在十三五末期形成80萬輛以上生產能力,年產銷50萬輛規模,打造年營業收入600億元、上市市值1000億元的企業。

在研發方面,北汽新能源將推進“1496”戰略,1:打造1個世界級科技創新中心,具備正向開發能力;4:構建4層次研發體系;9:在研發總部建成9大研發中心;5:整合全球資源,組建5大海外研發中心。此外,在北汽新能源的目標中,其還將爭創新能源汽車行業最佳雇主品牌,價值鏈研發團隊人數達到5000以上,專業領域國際級人才占比達到10%以上,經營團隊中具有國際視野的複合型人才占比60%以上。

產品方面,北汽新能源將構建3大技術平臺(共用平臺、協同平臺、全新平臺),同樣將實現3大維度(大中小、高中低、234)全面發展,打造三款年銷量突破10萬輛的明星車型。基於國內當前新能源汽車市場發展特點,北汽新能源短期內聚焦A級以下車型,並根據市場發展適時啟動B、C級高端車型儲備開發。

今年,北汽新能源將推出首款純電動6萬元高性價比國民純電動車也將在2016年迎來投放,實現不靠地方補貼國內市場全覆蓋。2017年,公司將推出採用全鋁框架超輕量化車身設計的EX系列純電動精品微型小車。

根據規劃,北汽新能源未來每年將研發4-6款新車型,形成“2、3、4”(即續航里程200公里、300公里、400公里)、“高、中、低”(即高檔、中檔、標配三個級別)、“大、中、小”(即車體大小)完整新能源汽車產品組合,為消費者提供更加豐富的選擇。

密集的產品投放加上車型續航能力不斷升級,這些利好都將為北汽新能源持續保持國內純電動市場第一、全球前四的發展目標奠定堅實基礎。而在擴張產能的同時,有效利用起北汽集團以及合作夥伴的資源,則可以大幅縮減開支,獲得更高的利潤。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

財政部等五部委發佈十三五新能源汽車充電基礎設施獎勵政策

1月19日從財政部獲悉,財政部、科技部、工業和資訊化部、發展改革委、國家能源局等五部委聯合發佈《關於“十三五”新能源汽車充電基礎設施獎勵政策及加強新能源汽車推廣應用的通知》,旨在加快推動新能源汽車充電基礎設施建設,培育良好的新能源汽車應用環境,2016-2020年中央財政將繼續安排資金對充電基礎設施建設、運營給予獎補。

通知規定獎補物件,中央財政充電基礎設施建設運營獎補資金是對充電基礎設施配套較為完善、新能源汽車推廣應用規模較大的省(區、市)政府的綜合獎補。

通知確定獎勵資金使用範圍,獎補資金應當專門用於支持充電設施建設運營、改造升級、充換電服務網路運營監控系統建設等相關領域。地方應充分利用財政資金杠杆作用,調動包括政府機關、街道辦事處和居委會、充電設施建設和運營企業、物業服務等在內的相關各方積極性,對率先開展充電設施建設運營、改造升級、解決充電難題的單位給予適當獎補,並優先用於支援《國務院辦公廳關於加快電動汽車充電基礎設施建設的指導意見》確定的相關重點任務。

通知設定新能源充電設施獎勵標準,對於大氣污染治理重點省市獎勵最高,2016年大氣污染治理重點省市推廣量3萬輛,獎補標準9000萬元,超出門檻部分獎補最高封頂1.2億元。2020年大氣污染治理重點省市獎勵門檻7萬輛,獎補標準1.26億元。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

分類
發燒車訊

必知必會-存儲器層次結構

相信大家一定都用過各種存儲技術,比如mysql,mongodb,redis,mq等,這些存儲服務性能有非常大的區別,其中之一就是底層使用的存儲設備不同。作為一個程序員,你需要理解存儲器的層次結構,這樣才能對程序的性能差別瞭然於心。今天帶大家了解下計算機系統存儲器的層次結構。

存儲技術

首先了解下什麼是存儲器系統?

實質上就是一個具有不同容量、成本和訪問時間的存儲設備的層次結構。從快到慢依次為:CPU寄存器、高速緩存、主存、磁盤;

這裏給大家介紹一組數據,讓大家有一個更清晰的認識:

如果數據存儲在CPU寄存器,需要0個時鐘周期就能訪問到,存儲在高速緩存中需要4~75個時鐘周期。如果存儲在主存需要上百個周期,而如果存儲在磁盤上,大約需要幾千萬個周期! — 出自 CSAPP

接下來一起深入了解下計算機系統涉及的幾個存儲設備:

隨機訪問存儲器

隨機訪問存儲器(RAM)分為靜態RAM (SRAM) 和動態RAM(DRAM)。SRAM的速度更快,但也貴很多,一般不會超過幾兆字節,通常用來做告訴緩存存儲器。DRAM就是就是我們常說的主存。

訪問主存

數據流是通過操作系統中的總線的共享电子電路在處理器和DRAM之間來來回回。每次CPU和主存之間的數據傳送都是通過一系列複雜的步驟完成,這些步驟成為總線事務。讀事務是將主存傳送數據到CPU。寫事務從CPU傳送數據到主存。

總線是一組并行的導線,能攜帶地址、數據和控制信號。下圖展示了CPU芯片是如何與主存DRAM連接的。

那麼我們在加載數據和存儲數據時,CPU和主存到底是怎樣交互實現的呢?

首先來看一個基本指令,加載內存數據到CPU寄存器中:

movq A,%rax

將地址A的內容加載到寄存器%rax中,這個命令會使CPU芯片上稱為總線接口(bus interface)的電路在總線上發起讀事務,具體分為三個步驟:

  1. CPU將地址A放到系統總線上,I/O橋將信號傳遞到內存總線。詳情看下下圖a
  2. 主存感覺到內存總線上的地址信號,從內存總線讀地址,從DRAM取出數據字,將其寫到內存總線。I/O橋將內存總線信號翻譯成系統總線信號,沿着系統總線傳遞到CPU總線接口。下圖b
  3. CPU感覺到系統總線上的數據,從總線上讀數據,並將數據複製到寄存器%rax。下圖c

隨機訪問存儲器,有個缺點是當斷電后,DRAM和SRAM會丟失它們的信息,因此為易失性存儲。

磁盤存儲

磁盤是廣為使用的保存大量數據的存儲設備,目前我們家用電腦,動輒也都是1T的。它相比於基於RAM的只有幾百或幾千兆字節的存儲器來說,雖然大但是讀寫性能差。時間為毫秒級,比DRAM讀慢了10萬倍,比SRAM慢了100萬倍。

磁盤構造

磁盤是由盤片構成的。每個盤片有兩面。表面覆蓋著磁性記錄材料。盤片中央是一個可以旋轉的主軸(spindle),它使盤片可以以固定的速率旋轉,通常是5400~15000轉每分鐘,磁盤通常包含多個盤片,密封在一個容器內。

如上圖,我們可以看到,表面被劃分為很多同心圓,稱為磁道。磁道又被劃分為很多扇區,每個扇區具有相同的數據位(通常512字節)。扇區之間有間隙隔開,用來存儲標識扇區的格式化位。

多個盤片封裝在一起到一個容器中,就是我們平時用的硬盤,稱為磁盤驅動器。

磁盤容量

容量很好理解,就是磁盤一共可以存儲的數據位。根據磁盤的構造,我們得出磁盤的容量由下面因素決定:

  • 記錄密度(recording density,位/英寸):磁道一英寸可以放入的位數。
  • 磁道密度(track density,道/英寸):從中心主軸向外的半徑上,一英寸可以有多少磁道。
  • 面密度(areal density,位/平方英寸):記錄密度與磁道密度的乘積。

通過上面的了解,增加磁盤容量其實就是增加面密度,近些年面密度每隔幾年就會翻倍。下面大家可以看一下這個磁盤容量的計算公式:

磁盤容量=字節數/扇區 * 平均扇區數/磁道 * 磁道數/表面 * 表面數/盤片 * 盤片數/磁盤

結合一個例子方便各位理解:

假如我們有一個磁盤,有5個盤片,每個扇區512字節,沒個面20000條磁道,每條磁道 300 個扇區,那麼容量計算為:

磁盤容量 = 512 * 300 * 20000 * 2 * 5 = 30720000000字節=30.72G

磁盤操作

磁盤讀寫操作靠的是讀寫頭來讀寫存儲在磁性表面的位,它在傳動臂的一端,通過這個傳動臂沿着半徑前後移動,從而讀取不同的磁盤上數據,這個過程就成為尋道(seek)

通過上圖可以清晰的了解到,在讀取數據的時候,首先通過傳動臂沿着半徑將讀寫頭移動到對應表面的磁道上,而表面一直在以固定的速率旋轉,讀取指定扇區的數據(磁盤是以扇區大小來讀寫數據)。因為對於數據訪問來說,消耗時間主要集中在:尋道時間、旋轉時間和傳送時間。

  • 尋道時間:即移動傳動臂到包含目標扇區的磁道上所需的時間;
  • 旋轉時間:即尋道完成后,等待目標扇區的第一個位旋轉到讀寫頭下的時間;
  • 傳送時間:即扇區第一個位開始位於讀寫頭下,到最後一個位所需的時間;

這裏給出一個書上寫的結論,訪問一個磁盤扇區中512字節的時間主要是尋道時間和旋轉延遲。也就是訪問扇區中第一個字節花費很長時間,剩下的幾乎不用時間。

這裏大家可能有疑問,CPU是如何讀取磁盤的數據到主存的,這就需要了解I/O總線。他們通過多種適配器連接到總線,而I/O總線連接了內存和CPU。如下圖所示:

也就是I/O總線連接各種I/O設備、主存等。

固態硬盤

固態硬盤也就是俗稱的SSD(Solid State Disk),是一種基於閃存的存儲技術,目前常用的日常PC都用它來代替了磁盤,獲取更快的速度。

SSD是內部由閃存構成,一個閃存由B個塊的序列組成,每個塊由P頁組成。通常頁的大小是512字節~4KB,塊由32~128頁組成,塊的大小為16KB~512KB。

SSD的隨機讀比寫快很多,是因為:

  1. 在寫的時候,只有一頁所屬的整個塊被擦除之後才能寫。而擦除塊需要較長時間,1ms級的,比讀取高一個數量級。
  2. 如果寫的頁P已經有數據,那麼這個塊中所有帶數據的頁都必須被複制到一個新的已經擦除過的塊,然後才能對頁P寫操作。

在大約進行100000次重複寫之後,塊會被磨損,不能在使用,所以這也是網上建議保存固態磁盤不要頻繁格式化,作為系統盤的原因。

局部性

現在計算機頻繁的使用基於SRAM的告訴緩存,為了彌補處理器-內存之間的差距,這種方法行之有效是因為局部性這個基本屬性。

程序的局部性原理是指程序在執行時呈現出局部性規律,即在一段時間內,整個程序的執行僅限於程序中的某一部分。相應地,執行所訪問的存儲空間也局限於某個內存區域。局部性原理又表現為:時間局部性和空間局部性。時間局部性是指如果程序中的某條指令一旦執行,則不久之後該指令可能再次被執行;如果某數據被訪問,則不久之後該數據可能再次被訪問。空間局部性是指一旦程序訪問了某個存儲單元,則不久之後。其附近的存儲單元也將被訪問。

上面我們介紹了內存和磁盤的讀取邏輯,因此一旦某個數據被訪問過,很快的時間內再次被訪問,則會有緩存等手段,提高訪問效率。

因此我們程序中應該尊村下列普遍方法:

  1. 重複引用相同變量的程序有良好的時間局部性;
  2. 總是順序訪問數據,跨越的步長越小,則程序的空間局部性越好。
  3. 對於取指令來說,循環有好的時間和空間局部性。循環體越小,循環迭代次數越多,局部性越好。

比如一個for循環,這是平時經常使用到的場景。假設它訪問一個同一個數組元素,那麼這個數組就是當前階段的訪問工作集,在緩存夠大的情況下,它是可以直接命中緩存的。

存儲器層次結構

上面主要介紹了存儲技術和計算機軟件一些基本的和持久的屬性:

  • 存儲技術:不同的存儲技術的訪問時間差異很大。速度較快的技術每字節的成本要比速度慢技術高,而且容量越小。CPU和主存之間的速度差距在增大;
  • 計算機軟件:一個便攜良好的程序傾向於展示出良好的局部性。

而現在計算機系統中,硬件和軟件這些基本屬性互相補充的很完美,即高層從底層走,存儲設備變得更慢、更便宜和更大,頂層的是CPU寄存器,CPU可以在一個時鐘周期內訪問他們,接下來是高速緩存SRAM、主存等 。

看上圖所示,其中心思想就是:對於每個k,位於k層的更快更小的存儲設備是作為位於k+1層更大更慢設備的緩存。

概括來說,基於緩存的存儲器層次結構行之有效,因為較慢的存儲設備比較快的設備更便宜,還因為程序傾向於展示局部性。

  • 利用時間局部性:由於時間局部性,同一數據可能會被多次使用,在第一次使用緩存不命中后就被複制到緩存中,後面在訪問時性能就比第一次快很多。
  • 利用空間局部性:存儲設備底層都有塊的概念,作為基本的讀取單位。通常塊包含多個數據,由於空間局部性,後面對該塊中其他對象的訪問即命中緩存,彌補首次訪問塊複製的消耗;

總結

今天,這篇文章主要學習了計算機存儲器的相關知識。

  1. 常用的存儲技術,以及計算機是如何操作這些存儲設備中的數據的。
  2. 講解了程序中的局部性原理,時間局部性和空間局部性。方便大家寫出更快的程序。
  3. 最後學習了整個計算機系統的存儲器層次結構。存儲系統其實就是一個多級緩存系統,上層的存儲設備昂貴,容量小,價格貴,但是速度快,作為下一層設備的緩存。

閱讀更多內容,請瀏覽我的個人小站:

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!