標籤: 網站設計公司

  • Linux開機過程

    Linux開機過程

    相關內容

    開機過程

      開機過程指的是從按下電源鍵開始,到進入系統登錄畫面前所經歷的過程。

    MBR與磁盤分區

      在目前x86的系統架構中,系統硬盤位於第0號磁道:0到511KB的區塊為MBR(硬盤中的每一個磁道容量為512KB),開機管理程序使用這塊區域來儲存第一階段開機引導程序(stage1)。接着位於1到62號磁道作為第1.5階段的開機引導程序(stage1.5),從第63號磁道開始才是操作系統的分區。

      主引導記錄(MBR,Master Boot Record)是位於磁盤最前邊的一段引導(Loader)代碼。它負責磁盤操作系統(DOS)對磁盤進行讀寫時分區合法性的判別、分區引導信息的定位,它由磁盤操作系統(DOS)在對硬盤進行初始化時產生。

      MBR的內容分為三部分:第一部分是0到445KB,是計算機的基礎導引程序,也稱為第一階段的導引程序;接着446KB到509KB為磁盤分區表,由四個分區表項構成(每個16個字節)。負責說明磁盤上的分區情況。內容包括分區標記、分區的起始位置、分區的容量以及分區的類型。最後一部分為結束標誌只佔2KB,其值為AA55,存儲時低位在前,高位在後。

    從百度百科借了張圖:

     

     

    MBR中緊跟在主引導程序后的主分區表這64字節(01BE~01FD)中包含了許多磁盤分區描述信息,尤其是01BE~01CD這16字節,包含了分區引導標誌bootid、分區起始源頭beghead、分區起始扇區relsect、分區起始柱面begcy1、操作系統類型systid、分區結尾磁頭endhead、分區結尾扇區begsect、分區結尾柱面begcy1、分區扇區起始位置relsect、分區扇區總數numsect。

    其中分區引導標誌bootid表示當前分區是否可以引導,若為0x0,則表示該分區為非活動區;若為0x80,則為可開機啟動區。若有多個開機啟動區,則由用戶開機時的選擇而定(如GRUB的菜單)。

    分區扇區起始位置relsect表示分區中第一個扇區相對於磁盤起始點的偏移位置。

    開機管理程序

    linux上的開機管理程序有LiLO和GRUB,前者是早期的產物,在近年來的Linux操作系統都以GRUB作為默認軟件包。

    GNU GRUB(GRand Unified Bootloader簡稱“GRUB”)是一個來自GNU項目的多操作系統啟動程序。GRUB是多啟動規範的實現,它允許用戶可以在計算機內同時擁有多個操作系統,並在計算機啟動時選擇希望運行的操作系統。GRUB可用於選擇操作系統分區上的不同內核,也可用於向這些內核傳遞啟動參數。

    運行層級

    運行層級(run level)共有7個,分別為0、1、2、3、4、5、6,其中0表示關機、1表示單人模式、6表示重新啟動。中間的2、3、4、5因Linux發行商而異。

    過程解析

     從按下電源開始到登錄畫面中所有的過程。

     登錄程序依序分為BIOS、GRUB、內核加載、與init程序四個步驟。

    BIOS

    當按下電源按鈕后,系統就會運行BIOS檢測,包含檢查系統的硬件配置、執行系統診斷程序、找出系統硬盤,把第0號磁道中的開機導引程序加載到內存中,之後就由GRUB接手後續的開機程序。

    GRUB

    GRUB是一個較大的程序,本身容量超過MBR的限制(512KB),因此GRUB將開機程序分割為stage1、stage2,並在1與2之中加上選用的程序stage1.5,如e2fs_stage1_5、fat_stage1_5等。

    由BIOS接手后的GRUB,會由stage1轉接到stage2(或stage1.5),並找出和載入位於/boot的內核文件。內核文件位於/boot之下。

    接着會將內存映像文件(.img)加載到內存中,並使用cpio命令將內容解壓縮到/boot之下。如果硬件的功能都別編入內核中,這個動作是不需要的;但若編譯為模塊且必須在開機時加載,這個步驟就是必要的。

    將內核與必要的映像文件加載后,系統開機的過程就交給內核處理了。

    內核載入

     內核接手系統開機的程序之後,會進行初始化,包括檢測硬件、設置硬件設備、時鐘設定、加載模塊等,這動作完成後會釋放出曾佔用的內存空間。

     接着啟動文件系統相關的設定,首先會掛接根目錄(“/”),再讀取分區表(/etc/fstab)並掛接所有的分區與啟動SWAP。最後系統啟動/sbin/init程序,並運行硬件與軟件相關的系統常駐程序。

     內核在開機的作用到此告一段落。

    init程序

    Init是系統的第一個進程,因此PID為0,也是所有進程的父進程,init啟動後會先執行etc/rc.d/rc.sysinit,並讀取配置文件/etc/inittab中的設定

     init的具體內容可參考:

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

    【其他文章推薦】

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

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

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

  • 蘋果電動車傳技術性問題,將延至2021年亮相

    蘋果(Apple)電動車開發案「泰坦計畫」(Project Titan)又有新傳聞,不過卻是一項令人失望的消息,據悉因技術性問題,故蘋果電動車亮相時間恐延至2021年!

    日本蘋果情報網站gori.me 22日報導,期待在2020年東京奧運駕駛蘋果電動車的夢想恐將破滅,據The Information網站21日指出,蘋果電動車亮相時間已被延後至2021年。

    報導指出,之前曾傳出蘋果電動車最快將在2019年發表、或是將在2020年開始進行生產,不過據The Information指出,蘋果雖持續朝上述2020年的目標進行研發,但因技術性問題,故蘋果電動車亮相時間已延至2021年。蘋果電動車企劃始於2014年,據悉目前參與該企劃的蘋果員工達約1,000人。

    gori.me指出,Google計畫於2020年發表自動駕駛車,因此5年後的IT業界主戰場或許不是智慧手機、也不是穿戴裝置,而是有可能在「車子」。

    根據嘉實XQ全球贏家系統報價,蘋果21日下跌0.53%、收99.43美元,4個交易日來首度走跌。

    9to5Mac、Electrek 4月19日獨家報導,蘋果已聘請特斯拉(Tesla)前任汽車工程副總裁暨英國豪華車商奧斯頓馬丁(Aston Martin)的前任首席工程師Chirs Porritt,而Porritt將負責研究「特殊方案」。大家都知道,所謂的特殊方案就是指蘋果的電動車開發案「泰坦計畫」。

    AppleInsider 4月18日引述法蘭克福廣訊報(Frankfurter Allgemeine Zeitung)報導,蘋果已在柏林設立秘密開發實驗室,目前在當地擁有15-20名工程、軟體、硬體、行銷背景的德國汽車業頂尖人才。報導指出,蘋果進軍汽車業的第一款產品將是電動車、但初期不具備自駕功能。

    不過,MarketWatch 5月26日報導,Edison Investment Research科技分析師Richard Windsor表示,蘋果先前也曾花大錢研發蘋果電視,最後卻從未發布,蘋果車的結局應該也一樣。華爾街日報報導,蘋果曾有意推出55~65吋的蘋果電視,但是因為產品缺乏特色,打消上市念頭,投入心血全數泡湯。

    Windsor強調,蘋果車問題更大,蘋果發現打造汽車比想像更困難,車業門檻極高、管制多、蘋果又缺乏清楚的獲利計畫。儘管蘋果資本雄厚,口袋極深,就算如此,要打入車業也不容易。

    (本文內容由授權提供)

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

    【其他文章推薦】

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

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

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

  • linux與Windows進程控制

    linux與Windows進程控制

    進程管理控制

    這裏實現的是一個自定義timer用於統計子進程運行的時間。使用方式主要是

    timer [-t seconds] command arguments

    例如要統計ls的運行時間可以直接輸入timer ls,其後的arguments是指所要運行的程序的參數。如:timer ls -al。如果要指定程序運行多少時間,如5秒鐘,可以輸入timer -t 5 ls -al。需要注意的是,該程序對輸入沒有做異常檢測,所以要確保程序輸入正確。

    Linux

    程序思路

    1. 獲取時間

      時間獲取函數使用gettimeofday,精度可以達到微秒

      struct timeval{
           long tv_sec;*//秒*
           long tv_usec;*//微秒*
      }
    2. 子進程創建

      1. fork()函數

        #include <sys/types.h>
        #include <unistd.h>
        pid_t fork(void);

        fork調用失敗則返回-1,調用成功則:

        fork函數會有兩種返回值,一是為0,一是為正整數。若為0,則說明當前進程為子進程;若為正整數,則該進程為父進程且該值為子進程pid。關於進程控制的詳細說明請參考:

      2. exec函數

        用fork創建子進程后執行的是和父進程相同的程序(但有可能執行不同的代碼分支),子進程往往要調用一種exec函數以執行另一個程序。當進程調用一種exec函數時,該進程的用戶空間代碼和數據完全被新程序替換,從新程序的啟動例程開始執行。調用exec並不創建新進程,所以調用exec前後該進程的id並未改變。
        其實有六種以exec開頭的函數,統稱exec函數:

        #include <unistd.h>
        int execl(const char *path, const char *arg, ...);
        int execlp(const char *file, const char *arg, ...);
        int execle(const char *path, const char *arg, ..., char *const envp[]);
        int execv(const char *path, char *const argv[]);
        int execvp(const char *file, char *const argv[]);
        int execve(const char *path, char *const argv[], char *const envp[]);

        這些函數如果調用成功則加載新的程序從啟動代碼開始執行,不再返回,如果調用出錯則返回-1,所以exec函數只有出錯的返回值而沒有成功的返回值。

      3. waitwaitpid

        一個進程在終止時會關閉所有文件描述符,釋放在用戶空間分配的內存,但它的PCB還保留着,內核在其中保存了一些信息:如果是正常終止則保存着退出狀態,如果是異常終止則保存着導致該進程終止的信號是哪個。這個進程的父進程可以調用wait或waitpid獲取這些信息,然後徹底清除掉這個進程。我們知道一個進程的退出狀態可以在Shell中用特殊變量$?查看,因為Shell是它的父進程,當它終止時Shell調用wait或waitpid得到它的退出狀態同時徹底清除掉這個進程。
        如果一個進程已經終止,但是它的父進程尚未調用wait或waitpid對它進行清理,這時的進程狀態稱為殭屍(Zombie)進程。任何進程在剛終止時都是殭屍進程,正常情況下,殭屍進程都立刻被父進程清理了。
        殭屍進程是不能用kill命令清除掉的,因為kill命令只是用來終止進程的,而殭屍進程已經終止了。

      #include <sys/types.h>
      #include <sys/wait.h>
      pid_t wait(int *status);
      pid_t waitpid(pid_t pid, int *status, int options);

      若調用成功則返回清理掉的子進程id,若調用出錯則返回-1。父進程調用wait或waitpid時可能會:

      • 阻塞(如果它的所有子進程都還在運行

      • 帶子進程的終止信息立即返回(如果一個子進程已終止,正等待父進程讀取其終止信息)
      • 出錯立即返回(如果它沒有任何子進程)

      這兩個函數的區別是:

      • 如果父進程的所有子進程都還在運行,調用wait將使父進程阻塞,而調用waitpid時如果在options參數中指定WNOHANG可以使父進程不阻塞而立即返回0
      • wait等待第一個終止的子進程,而waitpid可以通過pid參數指定等待哪一個子進程

    源代碼

    timer源代碼

    #include <math.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/time.h>
    #include <unistd.h>
    #include <wait.h>
    #include <ctime>
    #include <iostream>
    #include <cstring>
    //程序假定輸入完全正確,沒有做異常處理
    //mytime [-t number] 程序
    using namespace std;
    //調用系統時間
    struct timeval time_start;
    struct timeval time_end;
    
    void printTime();
    
    void newProcess(const char *child_process, char *argv[], double duration);
    
    int main(int argc, char const *argv[])
    {
        double duration = 0;
        char **arg;
        int step = 2;
        if (argc > 3 && (strcmp((char *)"-t", argv[1]) == 0)) //如果指定了運行時間
        {
            step = 4;
            duration = atof(argv[2]); //沒有做異常處理
        }
    
        arg = new char *[argc - step + 1];
        for (int i = 0; i < argc - step; i++)
        {
            arg[i] = new char[100];
            strcpy(arg[i], argv[i + step]);
        }
        arg[argc - step] = NULL;
    
        newProcess(argv[step - 1], arg, duration);
        return 0;
    }
    
    void printTime()
    {
        //用以記錄進程運行的時間
        int time_use = 0;  // us
        int time_left = 0; // us
        int time_hour = 0, time_min = 0, time_sec = 0, time_ms = 0, time_us = 0;
        gettimeofday(&time_end, NULL);
    
        time_use = (time_end.tv_sec - time_start.tv_sec) * 1000000 + (time_end.tv_usec - time_start.tv_usec);
        time_hour = time_use / (60 * 60 * (int)pow(10, 6));
        time_left = time_use % (60 * 60 * (int)pow(10, 6));
        time_min = time_left / (60 * (int)pow(10, 6));
        time_left %= (60 * (int)pow(10, 6));
        time_sec = time_left / ((int)pow(10, 6));
        time_left %= ((int)pow(10, 6));
        time_ms = time_left / 1000;
        time_left %= 1000;
        time_us = time_left;
        printf("此程序運行的時間為:%d 小時, %d 分鐘, %d 秒, %d 毫秒, %d 微秒\n", time_hour, time_min, time_sec, time_ms, time_us);
    }
    
    void newProcess(const char* child_process, char **argv, double duration)
    {
        pid_t pid = fork();
        if (pid < 0) //出錯
        {
            printf("創建子進程失敗!");
            exit(1);
        }
        if (pid == 0) //子進程
        {
            execvp(child_process, argv);
        }
        else
        {
            if (abs(duration - 0) < 1e-6)
            {
                gettimeofday(&time_start, NULL);
                wait(NULL); //等待子進程結束
                printTime();
            }
            else
            {
                gettimeofday(&time_start, NULL);
                // printf("sleep: %lf\n", duration);
                waitpid(pid, NULL, WNOHANG);
                usleep(duration * 1000000); // sec to usec
                int kill_ret_val = kill(pid, SIGKILL);
                if (kill_ret_val == -1) // return -1, fail
                {
                    printf("kill failed.\n");
                    perror("kill");
                }
                else if (kill_ret_val == 0) // return 0, success
                {
                    printf("process %d has been killed\n", pid);
                }
                printTime();
            }
        }
    }

    測試源代碼

    #include <iostream>
    #include <ctime>
    #include <unistd.h>
    using namespace std;
    int main(int argc, char const *argv[])
    {
        for(int n = 0; n < argc; n++)
        {
            printf("arg[%d]:%s\n",n, argv[n]);
        }
        sleep(5);
        return 0;
    }

    測試

    1. 自行編寫程序測試

    2. 系統程序測試

    3. 將timer加入環境變量

      這裏僅進行了臨時變量修改。

    Windows

    在Windows下進行父子進程的創建和管理在api調用上相較Linux有一定難度,但實際上在使用管理上比Linux容易的多。

    CreateProcess

    #include <Windows.h>
    BOOL CreateProcessA(
      LPCSTR                lpApplicationName,
      LPSTR                 lpCommandLine,
      LPSECURITY_ATTRIBUTES lpProcessAttributes,
      LPSECURITY_ATTRIBUTES lpThreadAttributes,
      BOOL                  bInheritHandles,
      DWORD                 dwCreationFlags,
      LPVOID                lpEnvironment,
      LPCSTR                lpCurrentDirectory,
      LPSTARTUPINFOA        lpStartupInfo,
      LPPROCESS_INFORMATION lpProcessInformation
    );

    源代碼實現

    timer程序

    // 進程管理.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
    //
    
    #include <iostream>
    #include <wchar.h>
    #include <Windows.h>
    #include <tchar.h>
    using namespace std;
    
    
    void printTime(SYSTEMTIME* start, SYSTEMTIME* end);
    void newProcess(TCHAR* cWinDir, double duration);
    
    int _tmain(int argc, TCHAR *argv[])
    {
        TCHAR* cWinDir = new TCHAR[MAX_PATH];
        memset(cWinDir, sizeof(TCHAR) * MAX_PATH, 0);
    
        printf("argc:   %d\n", argc);
    
        int step = 1;
        double duration = 0;
        if (argc > 1)
        {
            if (argv[1][0] == TCHAR('-') && argv[1][1] == TCHAR('t') && argv[1][2] == TCHAR('\0'))
            {
                step = 3;
                duration = atof((char*)argv[2]);
            }
        }
        //printf("printf content start: %ls\n", argv[1]);
        int j = 0;
        for (int i = 0, h = 0; i < argc - step; i++)
        {
            wcscpy_s(cWinDir + j, MAX_PATH - j, argv[i + step]);
            for (h = 0; argv[i + step][h] != TCHAR('\0'); h++);
            j += h;
            cWinDir[j++] = ' ';
            //printf("%d : %d\n", i, j);
            //printf("printf content start: %ls\n", cWinDir);
        }
        cWinDir[j - 2] = TCHAR('\0');
        //printf("printf content start: %ls\n", cWinDir);
    
        newProcess(cWinDir,duration);
    
        return 0;
    }
    
    
    void printTime(SYSTEMTIME* start, SYSTEMTIME* end)
    {
        int hours = end->wHour - start->wHour;
        int minutes = end->wMinute - start->wMinute;
        int seconds = end->wSecond - start->wSecond;
        int ms = end->wMilliseconds - start->wMilliseconds;
        if (ms < 0)
        {
            ms += 1000;
            seconds -= 1;
        }
        if (seconds < 0)
        {
            seconds += 60;
            minutes -= 1;
        }
        if (minutes < 0)
        {
            minutes += 60;
            hours -= 1;
        }
        //由於僅考慮在一天之內,不考慮小時會變成負數的情況
        printf("runtime: %02dhours %02dminutes %02dseconds %02dmilliseconds\n", hours, minutes, seconds, ms);
    }
    
    void newProcess(TCHAR* cWinDir, double duration)
    {
        PROCESS_INFORMATION pi;
        STARTUPINFO si;
        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);
        ZeroMemory(&pi, sizeof(pi));
        
    
        SYSTEMTIME start_time, end_time;
        memset(&start_time, sizeof(SYSTEMTIME), 0);
        memset(&end_time, sizeof(SYSTEMTIME), 0);
        GetSystemTime(&start_time);
    
            //建議大家不要單獨傳入lpApplicationName,而是將程序名放入cWinDir中
            //這樣會自動搜索PATH
        if (CreateProcess(
            NULL,       //lpApplicationName.若為空,則lpCommandLine必須指定可執行程序
                        //若路徑中存在空格,必須使用引號框定
            cWinDir,    //lpCommandLine
                        //若lpApplicationName為空,lpCommandLine長度不超過MAX_PATH
            NULL,       //指向一個SECURITY_ATTRIBUTES結構體,這個結構體決定是否返回的句柄可以被子進程繼承,進程安全性
            NULL,       //  如果lpProcessAttributes參數為空(NULL),那麼句柄不能被繼承。<同上>,線程安全性
            false,      //  指示新進程是否從調用進程處繼承了句柄。句柄可繼承性
            0,          //  指定附加的、用來控制優先類和進程的創建的標識符(優先級)
                        //  CREATE_NEW_CONSOLE  新控制台打開子進程
                        //  CREATE_SUSPENDED    子進程創建后掛起,直到調用ResumeThread函數
            NULL,       //  指向一個新進程的環境塊。如果此參數為空,新進程使用調用進程的環境。指向環境字符串
            NULL,       //  指定子進程的工作路徑
            &si,        //  決定新進程的主窗體如何显示的STARTUPINFO結構體
            &pi         //  接收新進程的識別信息的PROCESS_INFORMATION結構體。進程線程以及句柄
        ))
        {
        }
        else
        {
            printf("CreateProcess failed (%d).\n", GetLastError());
            return;
        }
    
    
        //wait untill the child process exits
        if (abs(duration - 0) < 1e-6)
            WaitForSingleObject(pi.hProcess, INFINITE);//這裏指定運行時間,單位毫秒
        else
            WaitForSingleObject(pi.hProcess, duration * 1000);
    
        GetSystemTime(&end_time);
    
        printTime(&start_time, &end_time);
    
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }

    測試程序

    #include <iostream>
    #include <Windows.h>
    using namespace std;
    int main(int argc, char* argv[])
    {
        for (int n = 0; n < argc; n++)
        {
            printf("arg[%d]:%s\n", n, argv[n]);
        }
        Sleep(5*1000);
        return 0;
    }

    測試

    1. 自行編寫程序測試

    2. 系統程序測試

    3. 添加至環境變量

    參考資料

    Windows

    Linux

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • [ASP.NET Core 3框架揭秘] 文件系統[3]:物理文件系統

    [ASP.NET Core 3框架揭秘] 文件系統[3]:物理文件系統

    ASP.NET Core應用中使用得最多的還是具體的物理文件,比如配置文件、View文件以及作為Web資源的靜態文件。物理文件系統由定義在NuGet包“Microsoft.Extensions.FileProviders.Physical”中的PhysicalFileProvider來構建。我們知道System.IO命名空間下定義了一整套針操作物理目錄和文件的API,實際上PhysicalFileProvider最終也是通過調用這些API來完成相關的IO操作。

    public class PhysicalFileProvider : IFileProvider, IDisposable
    {   
        public PhysicalFileProvider(string root);   
         
        public IFileInfo GetFileInfo(string subpath);  
        public IDirectoryContents GetDirectoryContents(string subpath); 
        public IChangeToken Watch(string filter);
    
        public void Dispose();   
    }

    一、PhysicalFileInfo

    一個PhysicalFileProvider對象總是映射到某個具體的物理目錄上,被映射的目錄所在的路徑通過構造函數的參數root來提供,該目錄將作為PhysicalFileProvider的根目錄。GetFileInfo方法返回的IFileInfo對象代表指定路徑對應的文件,這是一個類型為PhysicalFileInfo的對象。一個物理文件可以通過一個System.IO.FileInfo對象來表示,一個PhysicalFileInfo對象實際上就是對該對象的封裝,定義在PhysicalFileInfo的所有屬性都來源於這個FileInfo對象。對於創建讀取文件輸出流的CreateReadStream方法來說,它返回的是一個根據物理文件絕對路徑創建的FileStream對象。

    public class PhysicalFileInfo : IFileInfo
    {
        ...
        public PhysicalFileInfo(FileInfo info);    
    }

    對於PhysicalFileProvider的GetFileInfo方法來說,即使我們指定的路徑指向一個具體的物理文件,它並不總是會返回一個PhysicalFileInfo對象。PhysicalFileProvider會將一些場景視為“目標文件不存在”,並讓GetFileInfo方法返回一個NotFoundFileInfo對象。具體來說,PhysicalFileProvider的GetFileInfo方法在如下的場景中會返回一個NotFoundFileInfo對象:

    • 確實沒有一個物理文件與指定的路徑相匹配。
    • 如果指定的是一個絕對路徑(比如“c:\foobar”),即Path.IsPathRooted方法返回True。
    • 如果指定的路徑指向一個隱藏文件。

    顧名思義,具有如下定義的NotFoundFileInfo類型表示一個“不存在”的文件。NotFoundFileInfo對象的Exists屬性總是返回False,而其他的屬性則變得沒有任何意義。當我們調用它的CreateReadStream試圖讀取一個根本不存在的文件內容時,會拋出一個FileNotFoundException類型的異常。

    public class NotFoundFileInfo : IFileInfo
    {
        public bool Exists => false;   
        public long Length => throw new NotImplementedException();   
        public string PhysicalPath => null;  
        public string Name { get; }   
        public DateTimeOffset LastModified => DateTimeOffset.MinValue;
        public bool IsDirectory => false; 
    
        public NotFoundFileInfo(string name) => this.Name = name;
        public Stream CreateReadStream() => throw new FileNotFoundException($"The file {Name} does not exist.");
    }

    二、PhysicalDirectoryInfo

    PhysicalFileProvider利用一個PhysicalFileInfo對象來描述某個具體的物理文件,而一個物理目錄則通過一個PhysicalDirectoryInfo的對象來描述。既然PhysicalFileInfo是對一個FileInfo對象的封裝,那麼我們應該想得到PhysicalDirectoryInfo對象封裝的就是表示目錄的DirectoryInfo對象。如下面的代碼片段所示,我們需要在創建一個PhysicalDirectoryInfo對象時提供這個DirectoryInfo對象,PhysicalDirectoryInfo實現的所有屬性的返回值都來源於這個DirectoryInfo對象。由於CreateReadStream方法的目的總是讀取文件的內容,所以PhysicalDirectoryInfo類型的這個方法會拋出一個InvalidOperationException類型的異常。

    public class PhysicalDirectoryInfo : IFileInfo
    {   
        ...
        public PhysicalDirectoryInfo(DirectoryInfo info);
    }

    三、PhysicalDirectoryContents

    當我們調用PhysicalFileProvider的GetDirectoryContents方法時,如果指定的路徑指向一個具體的目錄,那麼該方法會返回一個類型為PhysicalDirectoryContents的對象。PhysicalDirectoryContents是一個IFileInfo對象的集合,該集合中包括所有描述子目錄的PhysicalDirectoryInfo對象和描述文件的PhysicalFileInfo對象。PhysicalDirectoryContents的Exists屬性取決於指定的目錄是否存在。

    public class PhysicalDirectoryContents : IDirectoryContents
    {
        public bool Exists { get; }
        public PhysicalDirectoryContents(string directory);
        public IEnumerator<IFileInfo> GetEnumerator();
        IEnumerator IEnumerable.GetEnumerator();
    }

    四、NotFoundDirectoryContents

    如果指定的路徑並不指向一個存在的目錄,或者指定的是一個絕對路徑,GetDirectoryContents方法都會返回一個Exsits為False的NotFoundDirectoryContents對象。如下所示的代碼片段展示了NotFoundDirectoryContents類型的定義,如果我們需要使用到這麼一個類型,可以直接利用靜態屬性Singleton得到對應的單例對象。

    public class NotFoundDirectoryContents : IDirectoryContents
    {    
        public static NotFoundDirectoryContents Singleton { get; }  = new NotFoundDirectoryContents();
        public bool Exists => false;
        public IEnumerator<IFileInfo> GetEnumerator()  => Enumerable.Empty<IFileInfo>().GetEnumerator();
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

    五、PhysicalFilesWatcher

    我們接着來談談PhysicalFileProvider的Watch方法。當我們調用該方法的時候,PhysicalFileProvider會通過解析我們提供的Globbing Pattern表達式來確定我們期望監控的文件或者目錄,並最終利用FileSystemWatcher對象來對這些文件實施監控。這些文件或者目錄的變化(創建、修改、重命名和刪除等)都會實時地反映到Watch方法返回的IChangeToken上。

    PhysicalFileProvider的Watch方法中指定的Globbing Pattern表達式必須是針對當前根目錄的相對路徑,我們可以使用“/”或者“./”前綴,也可以不採用任何前綴。一旦我們使用了絕對路徑(比如“c:\test\*.txt”)或者“../”前綴(比如“../test/*.txt”),不論解析出來的文件是否存在於PhysicalFileProvider的根目錄下,這些文件都不會被監控。除此之外,如果我們沒有指定Globbing Pattern表達式,PhysicalFileProvider也不會有任何的文件會被監控。

    PhysicalFileProvider針對物理文件系統變化的監控是通過如下這個PhysicalFilesWatcher對象實現的,其Watch方法內部會直接調用PhysicalFileProvider的CreateFileChangeToken方法,並返回得到的IChangeToken對象。這是一個公共類型,如果我們具有監控物理文件系統變化的需要,可以直接使用這個類型。

    public class PhysicalFilesWatcher: IDisposable
    {
        public PhysicalFilesWatcher(string root, FileSystemWatcher fileSystemWatcher, bool pollForChanges);
        public IChangeToken CreateFileChangeToken(string filter);
        public void Dispose();
    }

    從PhysicalFilesWatcher構造函數的定義我們不難看出,它最終是利用一個FileSystemWatcher對象(對應於fileSystemWatcher參數)來完成針對指定根目錄下(對應於root參數)所有子目錄和文件的監控。FileSystemWatcher的CreateFileChangeToken方法返回的IChangeToken對象會幫助我們感知到子目錄或者文件的添加、刪除、修改和重命名,但是它會忽略隱藏的目錄和文件。最後需要提醒的是,當我們不再需要對指定目錄實施監控的時候,記得調用PhysicalFileProvider的Dispose方法,該方法會負責將FileSystemWatcher對象關閉。

    六、小結

    我們藉助下圖所示的UML來對由PhysicalFileProvider構建物理文件系統的整體設計做一個簡單的總結。首先,該文件系統使用PhysicalDirectoryInfo和PhysicalFileInfo對類型來描述目錄和文件,它們分別是對DirectoryInfo和FileInfo(System.IO.FileInfo)對象的封裝。

    PhysicalFileProvider的GetDirectoryContents方法返回一個PhysicalDirectoryContents 對象(如果指定的目錄存在),組成該對象的分別是根據其所有子目錄和文件創建的PhysicalDirectoryInfo和PhysicalFileInfo對象。當我們調用PhysicalFileProvider的GetFileInfo方法時,如果指定的文件存在,返回的是描述該文件的PhysicalFileInfo對象。至於PhysicalFileProvider的Watch方法,它最終利用了FileSystemWatcher來監控指定文件或者目錄的變化。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • CSS:CSS彈性盒子布局 Flexible Box

    CSS:CSS彈性盒子布局 Flexible Box

    一、簡介

    flexbox:全稱Flexible Box, 彈性盒子布局。可以簡單實現各種伸縮性的設計,它是由伸縮容器和伸縮項目組成。任何一個元素都可以指定為flexbox布局。這種新的布局方案在2009年是由W3C組織提出來的,在此之前,Web開發一般使用基於盒子模型的傳統頁面布局,依賴定位屬性、流動屬性和显示屬性來解決,參看鏈接:。彈性盒子布局的出現,極大的方便了開發者,在如今的ReactNative開發中,也已經被引入使用。

    伸縮流布局結構圖如下:

    彈性盒子布局具備的特徵:

    1、伸縮容器的子元素稱為伸縮項目,伸縮項目使用伸縮布局來排版。伸縮布局和傳統布局不一樣,它按照伸縮流方向布局。

    2、伸縮容器由兩條軸構成,分別為主軸(main axis)和交叉軸(cross axis)。主軸既可以用水平軸,也可以是豎直軸,根據開發者需要來決定。

    3、主軸的起點叫main start,終點叫main end,主軸的空間用main size表示。

    4、交叉軸的起點叫cross start,終點叫cross end,交叉軸的空間用cross size表示。

    5、默認情況下,伸縮項目總是沿着主軸方向排版,從開始位置到終點位置。至於換行显示,則通過設置伸縮屬性來實現。

    6、伸縮容器的屬性有:display、flex-direction、flex-wrap、flex-flow、justify-content、align-items、align-content

    7、伸縮項目的屬性有: order、flex-grow、flex-shrink、flex-basis、flex、align-self

     

    二、伸縮容器的屬性,全局設置排版

    HTML:[注意:下面的演示截圖項目個數會根據需要選擇性註釋“flex-item”,有時用不到5個]

    <!DOCTYPE html>
    <html>
    <head>
        <title>Flexbox</title>
        <!--  採用外聯方式導入css文件 -->
        <link rel="stylesheet" type="text/css" href="./css_test.css">
    </head>
    <body>
        <span class="flex-container"> 
            <span class="flex-item" id="item1" style="color:white;font-size:20px">1</span>
            <span class="flex-item" id="item2" style="color:white;font-size:20px">2</span>
            <span class="flex-item" id="item3" style="color:white;font-size:20px">3</span>
            <span class="flex-item" id="item4" style="color:white;font-size:20px">4</span>
            <span class="flex-item" id="item5" style="color:white;font-size:20px">5</span>
        </span>
    </body>
    </html> 

    1、display:決定元素是否為伸縮容器

    • flex:產生塊級伸縮容器
      .flex-container {
           display: flex;
       }
    • inline-flex:產生行內塊級伸縮容器
    •  .flex-container {
           display: inline-flex;
       }

    2、flex-direction:指定伸縮容器主軸的方向

    • row:水平方向,從左到右
       .flex-container {
           display: flex;
           flex-direction: row;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; 
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • row-reverse:水平方向,從右到左
       .flex-container {
           display: flex;
           flex-direction: row-reverse;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; 
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • column:豎直方向,從上到下
       .flex-container {
           display: flex;
           flex-direction: column;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px; 
           background-color: green;
           margin: 1px;
       }

    • column-reverse:豎直方向,從下到上
       .flex-container {
           display: flex;
           flex-direction: column-reverse;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px; 
           background-color: green;
           margin: 1px;
       }

    3、flex-wrap:指定伸縮容器主軸方向空間不足時,決定是否換行以及換行方式

    • nowarp:不換行
      .flex-container {
           display: flex;
           flex-direction: row;
           flex-wrap: nowrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; //下圖單行狀態寬度被重新計算
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • warp:換行,若主軸為水平方向,換行方向是從上到下
       .flex-container {
           display: flex;
           flex-direction: row;
           flex-wrap: wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • wrap-reverse:換行,若主軸為水平方向,換行方向是從下到上
       .flex-container {
           display: flex;
           flex-direction: row;
           flex-wrap: wrap-reverse;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    4、flex-flow:flex-direction和flex-wrap的縮寫,同時指定伸縮容器主軸方向和換行設置

    • row nowrap:默認主軸是水平方向,從左到右,且不換行
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; //下圖單行狀態寬度被重新計算
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    5、justify-content:決定伸縮項目沿着主軸線的對齊方式

    • flex-start:與主軸線起始位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           justify-content: flex-start;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • flex-end:與主軸線結束位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           justify-content: flex-end;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • center:與主軸線中間位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           justify-content: center;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • space-between:平均分配到主軸線里,第一個項目靠齊起始位置,最後一個項目靠齊終點位置
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           justify-content: space-between;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • sapce-around:平均分配到主軸線里,兩端保留一半的空間
      .flex-container {
           display: flex;
           flex-flow: row wrap;
           justify-content: space-around;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    6、align-items:決定伸縮項目不能換行時沿着交叉軸線的對齊方式

    • flex-start:與交叉軸線起始位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           align-items: flex-start;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; //下圖單行狀態寬度被重新計算
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • flex-end:與交叉軸線結束位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           align-items: flex-end;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; //下圖單行狀態寬度被重新計算
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • center:與交叉軸線中間位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           align-items: center;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • baseline:根據基線對齊
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           align-items: baseline;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item1 {
           padding-top: 25px;
       }
      
       #item2 {
           padding-top: 20px;
       }
        
       #item3 {
           padding-top: 15px;
       }
      
       #item4 {
           padding-top: 10px;
       }
        
       #item5 {
           padding-top: 5px;
       }

    • stretch:沿着交叉軸線拉伸填充整個伸縮容器
       .flex-container {
           display: flex;
           flex-flow: row nowrap;
           align-items: stretch;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;//此時可以設置寬度,但不能設置高度,否則無法拉伸
           background-color: green;
           margin: 1px;
       }

    7、align-content:決定伸縮項目可以換行時沿着交叉軸線的對齊方式,flex-warp:warp一定要開啟

    • flex-start:與交叉軸線起始位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:flex-start;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • flex-end:與交叉軸線結束位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:flex-end;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • center:與主軸線中間位置靠齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:center;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • space-between:平均分配到主軸線里,第一行項目靠齊起始位置,最後一行項目靠齊終點位置
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:space-between;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • sapce-around:所有行平均分配到主軸線里,兩端保留一半的空間
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:space-around;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }

    • stretch:沿着交叉軸
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           align-content:stretch;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px; //不要設置高度,不然無法拉伸
           background-color: green;
           margin: 1px;
       }

     

    三、伸縮項目的屬性,單個設置排版

    1、order:定義伸縮項目的排列順序。數值越小,排列越靠前,默認值為0。

    • 表達式 order: integer;
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item4 {
           order: -1;
       }
      
       #item5 {
           order: -2;
       }

    2、flex-grow:定義伸縮項目的放大比例,默認值為0,表示即使存在剩餘空間,也不放大。

    • 表達式 flex-grow: number;
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item2 {
           flex-grow: 1; //空間不足,item2不會放大
       }
      
       #item4 {
           flex-grow: 1; //item4放大填滿剩餘空間
       }

    3、flex-shrink:定義伸縮項目的收縮比例,默認值為1。

    • 表達式 flex-shrink: numer;
      .flex-container {
           display: flex;
           flex-flow: row nowrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item4 {
           flex-shrink:3; //單行,空間有限,item4縮小為原來的1/3
       }
      
       #item5 {
           flex-shrink:4;  //單行,空間有限,item5縮小為原來的1/5
      }

    4、flex-basis:定義伸縮項目的基準值,剩餘空間按照比例進行伸縮,默認auto。

    •  auto
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item5 {
           flex-basis:auto;
       }

                

    • flex-basis: length 
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item5 {
           flex-basis:200px;
       }

    5、flex:是flex-grow、flex-shrink、flex-basis的縮寫,默認值 0 1 auto。

    • none: 不設置
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item2 {
           flex: none; /* 等同於 flex: 0 0 auto */
       }

    • flex-grow flex-shrink flex-basis: 設置放大或縮小或基準線
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item2 {
           flex: 1; /* 等同於 flex: 1 1 auto 或者 等同於 flex: auto*/
       }

    6、align-self:用來設置伸縮項目在交叉軸的對齊方式。

    • auto:自動對齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item3 {
           align-self: auto;
       }

    • flex-start: 向交叉軸的開始位置對齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item3 {
           align-self: flex-start;
       }

    • flex-end: 向交叉軸的結束位置對齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item3 {
           align-self: flex-end;
       }

    • center: 向交叉軸的中間位置對齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item3 {
           align-self: center;
       }

    • baseline:向交叉軸的基準線對齊
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           height: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item1 {
           align-self: baseline;
           margin-top: 50px;
       }
      
       #item2 {
           align-self: baseline;
       }

    • stretch: 在交叉軸拉伸填滿伸縮容器
       .flex-container {
           display: flex;
           flex-flow: row wrap;
           width: 160px;
           height: 160px;
           background-color: red;
       }
      
       .flex-item {
           width: 50px;
           background-color: green;
           margin: 1px;
       }
      
       #item1 {
           align-self: stretch;
       }
      
       #item2 {
           align-self: stretch;
       }
      
       #item3 {
           align-self: stretch;
       }

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • 蘋果的第一個汽車專利由BAE公司授權 像坦克

    蘋果的第一個汽車專利由BAE公司授權 像坦克

    儘管蘋果公司一直三緘其口,但是對於傳聞中的蘋果電動汽車項目,已經快成為了公開的秘密。現在,蘋果的首個關於汽車技術的專利也被人曝光,不過看起來與我們期望的距離似乎有點遙遠。  
      近日,美國專利商標局通過了一批蘋果公司的新專利,其中一項專利顯示了一種採用履帶以及軌槽設計的交通工具草圖。這項專利其實是兩個貨箱之間的接駁原理,駕駛員可以在極端寒冷空氣條件下,直接,通過加熱裝置,控制第一個車廂的轉向構件及包括一個連接機制的第二個車廂。   據悉,這項專利由瑞典軍用坦克製造商BAE公司授權給蘋果,因此至少目前來看肯定不會被使用在普通的消費和商業領域。   文章來源:騰訊數碼

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

    【其他文章推薦】

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

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

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

  • 【集合系列】- 深入淺出的分析TreeMap

    【集合系列】- 深入淺出的分析TreeMap

    一、摘要

    在集合系列的第一章,咱們了解到,Map的實現類有HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties等等。

    本文主要從數據結構和算法層面,探討TreeMap的實現。

    二、簡介

    Java TreeMap實現了SortedMap接口,也就是說會按照key的大小順序對Map中的元素進行排序,key大小的評判可以通過其本身的自然順序(natural ordering),也可以通過構造時傳入的比較器(Comparator)。

    TreeMap底層通過紅黑樹(Red-Black tree)實現,所以要了解TreeMap就必須對紅黑樹有一定的了解,在《集合系列》文章中,如果你已經讀過紅黑樹的講解,其實本文要講解的TreeMap,跟其大同小異。

    紅黑樹又稱紅-黑二叉樹,它首先是一顆二叉樹,它具有二叉樹所有的特性。同時紅黑樹更是一顆自平衡的排序二叉樹。

    對於一棵有效的紅黑樹二叉樹,主要有以下規則:

    • 1、每個節點要麼是紅色,要麼是黑色,但根節點永遠是黑色的;
    • 2、每個紅色節點的兩個子節點一定都是黑色;
    • 3、紅色節點不能連續(也即是,紅色節點的孩子和父親都不能是紅色);
    • 4、從任一節點到其子樹中每個恭弘=叶 恭弘子節點的路徑都包含相同數量的黑色節點;
    • 5、所有的恭弘=叶 恭弘節點都是是黑色的(注意這裏說恭弘=叶 恭弘子節點其實是上圖中的 NIL 節點);

    這些約束強制了紅黑樹的關鍵性質:從根到恭弘=叶 恭弘子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這棵樹大致上是平衡的。因為操作比如插入、刪除和查找某個值的最壞情況時間都要求與樹的高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的,而不同於普通的二叉查找樹。所以紅黑樹它是複雜而高效的,其檢索效率O(log n)。下圖為一顆典型的紅黑二叉樹。

    在樹的結構發生改變時(插入或者刪除操作),往往會破壞上述條件3或條件4,需要通過調整使得查找樹重新滿足紅黑樹的條件。

    調整方式主要有:左旋、右旋和顏色轉換!

    2.1、左旋

    左旋的過程是將x的右子樹繞x逆時針旋轉,使得x的右子樹成為x的父親,同時修改相關節點的引用。旋轉之後,二叉查找樹的屬性仍然滿足。

    2.2、右旋

    右旋的過程是將x的左子樹繞x順時針旋轉,使得x的左子樹成為x的父親,同時修改相關節點的引用。旋轉之後,二叉查找樹的屬性仍然滿足。

    2.3、顏色轉換

    顏色轉換的過程是將紅色節點轉換為黑色節點,或者將黑色節點轉換為紅色節點,以滿足紅黑樹二叉樹的規則!

    三、常用方法介紹

    3.1、get方法

    get方法根據指定的key值返回對應的value,該方法調用了getEntry(Object key)得到相應的entry,然後返回entry.value

    算法思想是根據key的自然順序(或者比較器順序)對二叉查找樹進行查找,直到找到滿足k.compareTo(p.key) == 0entry

    源碼如下:

    final Entry<K,V> getEntry(Object key) {
            //如果傳入比較器,通過getEntryUsingComparator方法獲取元素
            if (comparator != null)
                return getEntryUsingComparator(key);
            //不允許key值為null
            if (key == null)
                throw new NullPointerException();
            //使用元素的自然順序
                Comparable<? super K> k = (Comparable<? super K>) key;
            Entry<K,V> p = root;
            while (p != null) {
                int cmp = k.compareTo(p.key);
                if (cmp < 0)
                    //向左找
                    p = p.left;
                else if (cmp > 0)
                    //向右找
                    p = p.right;
                else
                    return p;
            }
            return null;
    }

    如果傳入比較器,通過getEntryUsingComparator方法獲取元素

    final Entry<K,V> getEntryUsingComparator(Object key) {
                K k = (K) key;
            Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                Entry<K,V> p = root;
                while (p != null) {
                    //通過比較器順序,獲取元素
                    int cmp = cpr.compare(k, p.key);
                    if (cmp < 0)
                        p = p.left;
                    else if (cmp > 0)
                        p = p.right;
                    else
                        return p;
                }
            }
            return null;
    }

    測試用例:

    public static void main(String[] args) {
            Map initMap = new TreeMap();
            initMap.put("4", "d");
            initMap.put("3", "c");
            initMap.put("1", "a");
            initMap.put("2", "b");
            //默認自然排序,key為升序
            System.out.println("默認 排序結果:" + initMap.toString());
    
            //自定義排序,在TreeMap初始化階段傳入Comparator 內部對象
            Map comparatorMap = new TreeMap<String, String>(new Comparator<String>() {
    
                @Override
                public int compare(String o1, String o2){
                    //根據key比較大小,採用倒敘,以大到小排序
                    return o2.compareTo(o1);
                }
            });
            comparatorMap.put("4", "d");
            comparatorMap.put("3", "c");
            comparatorMap.put("1", "a");
            comparatorMap.put("2", "b");
    
            System.out.println("自定義 排序結果:" + comparatorMap.toString());
    }

    輸出結果:

    默認 排序結果:{1=a, 2=b, 3=c, 4=d}
    自定義 排序結果:{4=d, 3=c, 2=b, 1=a}

    3.2、put方法

    put方法是將指定的key, value對添加到map里。該方法首先會對map做一次查找,看是否包含該元組,如果已經包含則直接返回,查找過程類似於getEntry()方法;如果沒有找到則會在紅黑樹中插入新的entry,如果插入之後破壞了紅黑樹的約束,還需要進行調整(旋轉,改變某些節點的顏色)。

    源碼如下:

    public V put(K key, V value) {
            Entry<K,V> t = root;
            //如果紅黑樹根部為空,直接插入
            if (t == null) {
                compare(key, key); // type (and possibly null) check
    
                root = new Entry<>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
            int cmp;
            Entry<K,V> parent;
            // split comparator and comparable paths
            Comparator<? super K> cpr = comparator;
            //如果比較器,通過比較器順序,找到插入位置
            if (cpr != null) {
                do {
                    parent = t;
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            else {
                //通過自然順序,找到插入位置
                if (key == null)
                    throw new NullPointerException();
                    Comparable<? super K> k = (Comparable<? super K>) key;
                do {
                    parent = t;
                    cmp = k.compareTo(t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            //創建並插入新的entry
            Entry<K,V> e = new Entry<>(key, value, parent);
            if (cmp < 0)
                parent.left = e;
            else
                parent.right = e;
            //紅黑樹調整函數
            fixAfterInsertion(e);
            size++;
            modCount++;
            return null;
    }

    紅黑樹調整函數fixAfterInsertion(Entry<K,V> x)

    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;
        while (x != null && x != root && x.parent.color == RED) {
            //判斷x是否在樹的左邊,還是右邊
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                //如果x的父親的父親的右子樹是紅色,違反了紅色節點不能連續
                if (colorOf(y) == RED) {
                    //進行顏色調整,以滿足紅色節點不能連續規則
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK); 
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    //如果x的父親的右子樹等於自己,那麼需要進行左旋轉
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);  
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                //跟上面的流程正好相反
                //獲取x的父親的父親的左子樹節點
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                //如果y是紅色節點,違反了紅色節點不能連續
                if (colorOf(y) == RED) {
                    //進行顏色轉換
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    //如果x的父親的左子樹等於自己,那麼需要進行右旋轉
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        //根節點一定為黑色
        root.color = BLACK;
    }

    上述代碼的插入流程:

    • 1、首先在紅黑樹上找到合適的位置;
    • 2、然後創建新的entry並插入;
    • 3、通過函數fixAfterInsertion(),對某些節點進行旋轉、改變某些節點的顏色,進行調整;

    調整圖解:

    3.3、remove方法

    remove的作用是刪除key值對應的entry,該方法首先通過上文中提到的getEntry(Object key)方法找到 key 值對應的 entry,然後調用deleteEntry(Entry<K,V> entry)刪除對應的 entry。由於刪除操作會改變紅黑樹的結構,有可能破壞紅黑樹的約束,因此有可能要進行調整。

    源碼如下:

    public V remove(Object key) {
            Entry<K,V> p = getEntry(key);
            if (p == null)
                return null;
    
            V oldValue = p.value;
            deleteEntry(p);
            return oldValue;
    }

    刪除函數 deleteEntry()

    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;
        if (p.left != null && p.right != null) {// 刪除點p的左右子樹都非空。
            Entry<K,V> s = successor(p);// 後繼
            p.key = s.key;
            p.value = s.value;
            p = s;
        }
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);
        if (replacement != null) {// 刪除點p只有一棵子樹非空。
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;
            p.left = p.right = p.parent = null;
            if (p.color == BLACK)
                fixAfterDeletion(replacement);// 調整
        } else if (p.parent == null) {
            root = null;
        } else { //刪除點p的左右子樹都為空
            if (p.color == BLACK)
                fixAfterDeletion(p);// 調整
            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

    刪除后調整函數fixAfterDeletion()的具體代碼如下:

    private void fixAfterDeletion(Entry<K,V> x) {
        while (x != root && colorOf(x) == BLACK) {
            //判斷當前刪除的元素,是在x父親的左邊還是右邊
            if (x == leftOf(parentOf(x))) {
                Entry<K,V> sib = rightOf(parentOf(x));
                //判斷x的父親的右子樹,是紅色還是黑色節點
                if (colorOf(sib) == RED) {
                    //進行顏色轉換
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }
                //x的父親的右子樹的左邊是黑色節點,右邊也是黑色節點
                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    //設置x的父親的右子樹為紅色節點,將x的父親賦值給x
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    //x的父親的右子樹的左邊是紅色節點,右邊也是黑色節點
                    if (colorOf(rightOf(sib)) == BLACK) {
                        //x的父親的右子樹的左邊進行顏色調整,右旋調整
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    //對x進行左旋,顏色調整
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;
                }
            } else { // 跟前四種情況對稱
                Entry<K,V> sib = leftOf(parentOf(x));
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }
                if (colorOf(rightOf(sib)) == BLACK &&
                    colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }
        setColor(x, BLACK);
    }

    上述代碼的刪除流程:

    • 1、首先在紅黑樹上找到合適的位置;
    • 2、然後刪除entry;
    • 3、通過函數fixAfterDeletion(),對某些節點進行旋轉、改變某些節點的顏色,進行調整;

    四、總結

    TreeMap 默認是按鍵值的升序排序,如果需要自定義排序,可以通過new Comparator構造參數,重寫compare方法,進行自定義比較。

    以上,主要是對 java 集合中的 TreeMap 做了寫講解,如果有理解不當之處,歡迎指正。

    五、參考

    1、JDK1.7&JDK1.8 源碼
    2、
    2、

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • ThreadLocal深度解析和應用示例

    ThreadLocal深度解析和應用示例

    開篇明意

      ThreadLocal是JDK包提供的線程本地變量,如果創建了ThreadLocal<T>變量,那麼訪問這個變量的每個線程都會有這個變量的一個副本,在實際多線程操作的時候,操作的是自己本地內存中的變量,從而規避了線程安全問題。

      ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地線程”。其實,ThreadLocal並不是一個Thread,而是Thread的一個局部變量,也許把它命名ThreadLocalVariable更容易讓人理解一些。

      來看看官方的定義:這個類提供線程局部變量。這些變量與正常的變量不同,每個線程訪問一個(通過它的get或set方法)都有它自己的、獨立初始化的變量副本。ThreadLocal實例通常是類中的私有靜態字段,希望將狀態與線程關聯(例如,用戶ID或事務ID)。

    源碼解析

      1.核心方法之   set(T t)

     1     /**
     2      * Sets the current thread's copy of this thread-local variable
     3      * to the specified value.  Most subclasses will have no need to
     4      * override this method, relying solely on the {@link #initialValue}
     5      * method to set the values of thread-locals.
     6      *
     7      * @param value the value to be stored in the current thread's copy of
     8      *        this thread-local.
     9      */
    10     public void set(T value) {
    11         Thread t = Thread.currentThread();
    12         ThreadLocalMap map = getMap(t);
    13         if (map != null)
    14             map.set(this, value);
    15         else
    16             createMap(t, value);
    17     }

    解析:

      當調用ThreadLocal的set(T t)的時候,代碼首先會獲取當前線程的 ThreadLocalMap(ThreadLocal中的靜態內部類,同時也作為Thread的成員變量存在,後面會進一步了解ThreadLocalMap),如果ThreadLocalMap存在,將ThreadLocal作為map的key,要保存的值作為value來put進map中(如果map不存在就先創建map,然後再進行put);

      2.核心方法值 get()

    /**
         * Returns the value in the current thread's copy of this
         * thread-local variable.  If the variable has no value for the
         * current thread, it is first initialized to the value returned
         * by an invocation of the {@link #initialValue} method.
         *
         * @return the current thread's value of this thread-local
         */
        public T get() {
           Thread t = Thread.currentThread();
           ThreadLocalMap map = getMap(t);        //此處和set方法一致,也是通過當前線程獲取對應的成員變量ThreadLocalMap,map中存放的是Entry(ThreadLocalMap的內部類(繼承了弱引用))
        
    if (map != null) {
          ThreadLocalMap.Entry e = map.getEntry(this);
          if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
          }
        }
        return setInitialValue();
    }

    解析:

      剛才把對象放到set到map中,現在根據key將其取出來,值得注意的是這裏的map裏面存的可不是鍵值對,而是繼承了WeakReference<ThreadLocal<?>> 的Entry對象,關於ThreadLocalMap.Entry類,後面會有更加詳盡的講述。

    核心方法之  remove()

        /**
         * Removes the current thread's value for this thread-local
         * variable.  If this thread-local variable is subsequently
         * {@linkplain #get read} by the current thread, its value will be
         * reinitialized by invoking its {@link #initialValue} method,
         * unless its value is {@linkplain #set set} by the current thread
         * in the interim.  This may result in multiple invocations of the
         * {@code initialValue} method in the current thread.
         *
         * @since 1.5
         */
         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }

    解析:

      通過getMap方法獲取Thread中的成員變量ThreadLocalMap,在map中移除對應的ThreadLocal,由於ThreadLocal(key)是一種弱引用,弱引用中key為空,gc會回收變量value,看一下核心的m.remove(this);方法

            /**
             * Remove the entry for key.
             */
            private void remove(ThreadLocal<?> key) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1); //定義Entry在數組中的標號 for (Entry e = tab[i];              //通過循環的方式remove掉Thread中所有的Entry
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {   
                    if (e.get() == key) {
                        e.clear();
                        expungeStaleEntry(i);
                        return;
                    }
                }
            } 

     

     

     

    靈魂提問

      問:threadlocal是做什麼用的,用在哪些場景當中?  

        結合官方對ThreadLocal類的定義,threadLocal主要滿足某些變量或者示例是線程隔離的,但是在相同線程的多個類或者方法中都能使用的到,並且當線程結束時該變量也應該銷毀。通俗點講:ThreadLocal保證每個線程有自己的數據副本,當線程結束后可  以獨立回收。由於ThreadLocal的特性,同一線程在某地方進行設置,在隨後的任意地方都可以獲取到。從而可以用來保存線程上下文信息。常用的比如每個請求怎麼把一串後續關聯起來,就可以用ThreadLocal進行set,在後續的任意需要記錄日誌的方法裏面進行get獲取到請求id,從而把整個請求串起來。     使用場景有很多,比如:

    • 基於用戶請求線程的數據隔離(每次請求都綁定userId,userId的值存在於ThreadLoca中)
    • 跟蹤一個請求,從接收請求,處理到返回的整個流程,有沒有好的辦法   思考:微服務中的鏈路追蹤是否利用了ThreadLocal特性
    • 數據庫的讀寫分離
    • 還有比如Spring的事務管理,用ThreadLocal存儲Connection,從而各個DAO可以獲取同一Connection,可以進行事務回滾,提交等操作。

        
    問:如果我啟動另外一個線程。那麼在主線程設置的Threadlocal值能被子線程拿到嗎?     原始的ThreadLocal是不具有繼承(或者說傳遞)特性的     
    問:那該如何解決ThreadLocal無法傳遞的問題呢?     用ThreadLocal的子類 InheritableThreadLocal,InheritableThreadLocal是具有傳遞性的

      /**
      * 重寫Threadlocal類中的getMap方法,在原Threadlocal中是返回
      * t.theadLocals,而在這麼卻是返回了inheritableThreadLocals,因為
      * Thread類中也有一個要保存父子傳遞的變量
      */ ThreadLocalMap getMap(Thread t) {
    return t.inheritableThreadLocals; }
        /**  * 同理,在創建ThreadLocalMap的時候不是給t.threadlocal賦值  *而是給inheritableThreadLocals變量賦值  *  */
        void createMap(Thread t, T firstValue) {
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }

    解析:因為InheritableThreadLocal重寫了ThreadLocal中的getMap 和createMap方法,這兩個方法維護的是Thread中的另外一個成員變量  inheritableThreadLocals,線程在創建的時候回複製inheritableThreadLocals中的值 ;

    /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
      //Thread類中維護的成員變量,ThreadLocal會維護該變量
    ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */
    //Thread中維護的成員變量 ,
    InheritableThreadLocal 中維護該變量
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;


     

    //Thread init方法中的關鍵代碼,簡單來說是將父類中inheritableThreadLocals中的值拷貝到當前線程的inheritableThreadLocals中(淺拷貝,拷貝的是value的地址引用)
     if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

    總結

    • ThreadLocal類封裝了getMap()、Set()、Get()、Remove()4個核心方法。
    • 通過getMap()獲取每個子線程Thread持有自己的ThreadLocalMap實例, 因此它們是不存在併發競爭的。可以理解為每個線程有自己的變量副本。
    • ThreadLocalMap中Entry[]數組存儲數據,初始化長度16,後續每次都是1.5倍擴容。主線程中定義了幾個ThreadLocal變量,Entry[]才有幾個key。
    • Entry的key是對ThreadLocal的弱引用,當拋棄掉ThreadLocal對象時,垃圾收集器會忽略這個key的引用而清理掉ThreadLocal對象, 防止了內存泄漏。

        tips:上面四個總結來源於其他技術博客,個人認為總結的比較合理所以直接摘抄過來了

    拓展:

      ThreadLocal在線程池中使用容易發生的問題: 內存泄漏,先看下圖

      

      每個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當把threadlocal實例置為null以後,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因為存在一條從current thread連接過來的強引用. 只有當前thread結束以後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將全部被GC回收.

      所以得出一個結論就是只要這個線程對象被gc回收,就不會出現內存泄露,但在threadLocal設為null和線程結束這段時間不會被回收的,就發生了我們認為的內存泄露。其實這是一個對概念理解的不一致,也沒什麼好爭論的。最要命的是線程對象不被回收的情況,這就發生了真正意義上的內存泄露。比如使用線程池的時候,線程結束是不會銷毀的,會再次使用的。就可能出現內存泄露。  

      PS.Java為了最小化減少內存泄露的可能性和影響,在ThreadLocal的get,set的時候都會清除線程Map里所有key為null的value。所以最怕的情況就是,threadLocal對象設null了,開始發生“內存泄露”,然後使用線程池,這個線程結束,線程放回線程池中不銷毀,這個線程一直不被使用,或者分配使用了又不再調用get,set方法,那麼這個期間就會發生真正的內存泄露。 

     

    1. JVM利用設置ThreadLocalMap的Key為弱引用,來避免內存泄露。
    2. JVM利用調用remove、get、set方法的時候,回收弱引用。
    3. 當ThreadLocal存儲很多Key為null的Entry的時候,而不再去調用remove、get、set方法,那麼將導致內存泄漏。
    4. 當使用static ThreadLocal的時候,延長ThreadLocal的生命周期,那也可能導致內存泄漏。因為,static變量在類未加載的時候,它就已經加載,當線程結束的時候,static變量不一定會回收。那麼,比起普通成員變量使用的時候才加載,static的生命周期加長將更容易導致內存泄漏危機。

     

      參考鏈接:

     

    在線程池中使用ThreadLocal

    通過上面的分析可以知道InheritableThreadLocal是通過Thread()的inint方法實現父子之間的傳遞的,但是線程池是統一創建線程並實現復用的,這樣就好導致下面的問題發生:

    •   線程不會銷毀,ThreadLocal也不會被銷毀,這樣會導致ThreadLoca會隨着Thread的復用而復用
    •   子線程無法通過InheritableThreadLocal實現傳遞性(因為沒有單獨的調用Thread的Init方法進行map的複製),子線程中get到的是null或者是其他線程復用的錯亂值(疑問點還沒搞清楚原因,後續補充::在異步線程中會出現null的情況,同步線程不會出現)     

        ps:線程池中的線程是什麼時候創建的?

     

      解決方案:

        下面兩個鏈接有詳細的說明,我就不重複寫了,後續我會將本文進一般優化並添加一些例子來幫助說明,歡迎收藏,關於本文有不同的意見歡迎評論指正……

        

        

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • [NLP] Adaptive Softmax

    [NLP] Adaptive Softmax

    1. Overview

    Adaptive softmax算法在鏈接1中的論文中提出,該算法目的是為了提高softmax函數的運算效率,適用於一些具有非常大詞彙量的神經網絡。

    在NLP的大部分任務中,都會用到softmax,但是對於詞彙量非常大的任務,每次進行完全的softmax會有非常大的計算量,很耗時(每次預測一個token都需要O(|V|)的時間複雜度)。

    所以paper中提出adaptive softmax來提升softmax的運算效率。

    1) 該算法的提出利用到了單詞分佈不均衡的特點(unbalanced word distribution)來形成clusters, 這樣在計算softmax時可以避免對詞彙量大小的線性依賴關係,降低計算時間;

    2) 另外通過結合現代架構和矩陣乘積操作的特點,通過使其更適合GPU單元的方式進一步加速計算。

    2. Introduction

    2.1 一般降低softmax計算複雜度的兩種方式

    1) 考慮原始分佈:近似原始概率分佈或近似原始概率分佈的子集

    2) 構造近似模型,但是產生準確的概率分佈。比如:hierarchical softmax.

    (上述兩個方法可以大致區分為:一是準確的模型產生近似概率分佈,二是用近似模型產生準確的概率分佈)

    2.2 本文貢獻點

    paper中主要採用的上述(2)的方式,主要借鑒的是hierarchical softmax和一些變型。與以往工作的不同在於,本paper結合了GPU的特點進行了計算加速。主要

    1. 定義了一種可以生成 近似層次模型 的策略,該策略同時考慮了矩陣乘積運算的計算時間。這個計算時間並非與矩陣的維數是簡單線性關係。

    2. 在近來的GPU上對該模型進行了實證分析。在所提出的優化算法中也包含了對實際計算時間模型的定義。

    3. 與一般的softmax相比,有2× 到 10× 的加速。這等價於在同等算力限制下提高了準確度。另外一點非常重要,該paper所提出的這種高效計算的方法與一些通過并行提高效率的方法相比,在給定一定量的training data下,對準確度並沒有損失。

    3. Adaptive Softmax 

    3.1 矩陣乘積運算的計算時間模型

    hidden states: (B × d); Word representation: (d × k); 這兩個矩陣的乘積運算時間: g(k, B)。其中B是batch size, d: hidden layer 的size, k vectors的數量。

    (1) 固定B,d, 探究k值大小與g(k)的關係:

    在K40、 M40兩種GPU model中進行實驗,發現k在大約為$k_0 \approx 50$的範圍之內,g(k)是常數級別的,在$k > k_0$后,呈線性依賴關係。其計算模型為:

    同樣的,關於B的計算時間的函數也呈現這樣的關係。這可以表示,矩陣相乘時,當其中一個維度很小時,矩陣乘法是低效的。

    如何理解呢?比如對於 $k_1 = 2^2$, $k_2 = 2^4$,它們均在同樣的常量時間內完成運算,那麼顯然對於$k1$量級有算力被浪費了。

    那麼這也說明,一些words的層次結構,其每個節點只具有少量的子節點(比如huffman 編碼的tree),是次優的。

    (2) 探究batch size B值大小與g(B)的關係:

    同樣的,當探究計算用時與batch size $B$,的關係時,發現在矩陣乘法運算中,其中一個維度很小時計算並不高效。因此會導致:

    (i) 在層次結構中,因為每個節點只有少數一些孩子節點 (比如Huffman 樹),其計算效率是次優的;

    (ii) 對於按照詞頻劃分clusters后,那些只包含稀有詞的cluster,被選擇的概率為p, 並且其batch size也縮減為 $p B$,也會出現以上所述的低效的矩陣乘積運算問題。

    (3) 本文的計算模型

    所以paper中綜合考慮了k與B,提出了下面的計算模型:

    對於Adaptive Softmax, 其核心思想是根據詞頻大小,將不同詞頻區間的詞分為不同的clusters,按照詞頻高的優先訪問的原則,來對每個cluster整體,進而對cluster中的每個詞進行訪問,進行softmax操作。

    那麼paper中首先以2個clusters為例,對該模型進行講解,之後推廣到多個clusters的一般情況。

    3.2 Two-Clusters Case

    根據Zipf定律,在Penn TreeBank中 20%的詞,可以覆蓋一般文檔中87%的出現過的詞。

    直觀地,可以將字典$V$中的詞分為$V_h$、$V_t$兩個部分。其中$V_h$表示高頻詞集合(head),$V_t$表示低頻詞集合(tail)。一般地,$|V_h| << |V_t|$,$P(V_h) >> P(V_t)$。

    (1) Clusters 的組織

    直觀地可以想到對於這兩個clusters有兩種組織方式:(i) 兩個clusters都是2層的樹結構,(ii) 將head部分用一個短列表保存,對tail部分用二層樹結構。何種方式更優可以就其計算效率和準確率進行比較:

    在準確率方面,(i) 較之 (ii) 一般有 5 ~ 10 %的下降。原因如下:

    對於屬於cluster $c$的每個詞$w$的概率計算為: 

    若採用(i):  $P(c | h) * P(w | c, h)$

    若採用(ii): 對於高頻詞head部分可以直接計算獲得 $P(w | h)$,其更為簡單直接。

    因此,除非(i)(ii)的計算時間有非常大的差別,否則選擇(ii)的組織方式。

    (2) 對計算時間的縮短

     

    圖2. Two clusters示意圖

    如圖2所示,$k_h = |V_h|, k_t = k – k_h, p_t = 1 – p_h$

    (i) 對於第一層:對於batch size 為B的輸入,在head中對$k_h$個高頻詞以及1個單位的二層cluster的向量(加陰影部分),共$k_h + 1$個向量,做softmax,

    這樣占文檔中$p_h$的詞可以基本確定下來,在head 的 short list 部分找到對應的單詞;

    而如果與陰影部分進行softmax值更大,表明待預測的詞,在第二層的低頻詞部分;

    第一層的計算開銷為:$g(k_h + 1, B)$

    (ii) 在short list 中確定了 $p_h × B$ 的內容后,還剩下 $p_t B$的輸入需要在tail中繼續進行softmax來確定所預測的詞。

    第二層中需要對$k_t$個向量做softmax;

    第二層的計算開銷為:$g(k_t, pB)$

    綜上,總的計算開銷為:$$C = g(k_h + 1, B) + g(k_t, p_t B)$$

    相比於最初的softmax,分母中需要對詞典中的每個詞的向量逐一進行計算;採用adaptive softmax可以使得該過程分為兩部分進行,每部分只需對該部分所包含的詞的向量進行計算即可,降低了計算用時。

    論文中的Figure 2显示了,對於$k_h$的合理劃分,最高可以實現相較於完全softmax的5x加速。

    (3) 不同cluster的分類器能力不同

    由於每個cluster基本是獨立計算的,它們並不要求有相同的能力(capacity)。

    一般可以為高頻詞的cluster予以更高的capacity,而可以適當降低詞頻詞的cluster的capacity。因為低頻詞在文檔中很少出現,因此對其capacity的適當降低並不會非常影響總體的性能。

    在本文中,採取了為不同的clusters共享隱層,通過加映射矩陣的方式降低分類器的輸入大小。一般的,tail部分的映射矩陣將其大小從$d$維縮小到$d_t = d / 4$維。

    3.3 一般性情況

    在3.2中以分為2個cluster為例,介紹了adaptive softmax如何組織並計算的,這裏將其拓展為一般情況,即可以分為多個clusters時的處理方式。

    假設將整個詞典分為一個head部分和J個詞頻詞cluster部分,那麼有:

    $$V = V_h \cup V_1 … V_j, V_i \cap V_j = \phi$$

    其中$V_h$在第一層,其餘均在第二層,如圖Figure 3所示。

    每個cluster中的詞向量的數目為$k_i = |V_i|$,單詞$w$屬於某個cluster的概率為:$p_i = \sum_{w \in V_i} p(w)$.

    那麼,

    計算head部分的時間開銷為:$C_h = g(J + k_h, B)$

    計算每個二層cluster的時間開銷為:$\forall_i, C_i = g(k_i, p_i B)$

    那麼總的時間開銷為:$C = g(J + k_h, B) + \sum_i g(k_i, p_i B) \tag8$

     

    回顧公式(7):

    $$g(k,B) = max(c + \lambda k_0 B_0, c + \lambda k B) \tag7$$

    這裡有兩部分,一個是常數部分$c + \lambda k_0 B_0$,一個是線性變換部分$c + \lambda k B$。

    通過3.1可知,在常數部分GPU的算力並未得到充分利用,因此應盡量避免落入該部分。那麼需要滿足:

    $kB \geq k_0B_0$,這樣在求max時,可以利用以上的第二部分。

    將(8)式代入(7)的第二部分得:

     

    那麼接下來的目標就是根據(10) 以最小化時間開銷C. 

    在(10)中,$J ,B$是固定的,那麼可以重點關注$\sum_i p_ik_i$與$k_h$。

    (1) $\sum_i p_ik_i$

    假設$p_{i + j} = p_i + p_j$, 那麼 $p_jk_j = (p_{i + j} – p_i) k_j$

    $p_ik_i + p_jk_j = p_i(k_i – k_j) + p_{i + j}k_j \tag{11}$  

    假設$k_i > k_j, p_{i + j}$, $k_j$均固定,那麼(11)中只有$p_i$可變,可以通過減小$p_i$的方式使(11)式減小。=> 也即$k_i > k_j$ 且 $p_i$盡量小,即包含越多詞的cluster,其概率越小。

    (2) $k_h$

    減小$k_h$可以使(10)式減小。 => 即為可以使高頻詞所在的cluster包含更少的詞。

    綜上,給定了cluster的個數J,與batch size B,可以通過給大的cluster分配以更小的概率降低時間開銷C.

    另外paper中講到可以用動態規劃的方法,在給定了J后,對$k_i$的大小進行劃分。

    cluster的劃分個數$J$。paper中實驗了採用不同的J所導致的不同計算時間。如圖Figure 4所示。雖然在$10 ~ 15$區間內效果最好,但$ > 5$之後效果沒有非常明顯的提升。所以文中建議採用2~5個clusters。

    實驗主要在text8, europarl, one billion word三個數據集上做的,通過比較ppl發現,adaptive softmax仍能保持低的ppl,並且相比於原模型有2x到10x的提速。

      

    參考鏈接:

    1. Efficient softmax approximation for GPUs: 

    Some of us get dipped in flat, some in satin, some in gloss. But every once in a while you find someone who’s iridescent, and when you do, nothing will ever compare. ~ Flipped

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • Spring框架學習總結(上)

    Spring框架學習總結(上)

    目錄

    @

    1、Spring的概述

    在學習SSM框架中,我建議初學者最好先學Spring框架,其次mybatis接着springMVC,先學mybatis當然也是可以的,今天我們就以絕對優雅的姿態闖進Spring世界,系好安全帶,準備好了嗎,出發了哦!!!咳咳….平時開發接觸最多的估計就是IOC容器,它可以裝載bean(所謂的bean也就是我們java中的類,當然也包括servicedao裏面),有了這個機制,我們就不用在每次使用這個類的時候為它初始化,很少看到鍵字new。另外spring的aop,事務管理等等都是我們經常用到的,可見Spring的尤為重要的作用Spring的核心是控制反轉(IoC)和面向切面(AOP)

    1.1什麼是Spring

    肯定有熊dei會問SE/EE開發的一站式框架所謂的一站式是什麼意思,(哼,人類,我早就猜到你會問了)
    所謂一站式框架指的是有EE開發的每一層解決方案。

    WEB層 :SpringMVC

    Service層 :Spring的Bean管理,Spring聲明式事務

    DAO層 :Spring的Jdbc模板,Spring的ORM模塊

    1.2為什麼學習Spring

    俗話說,人狠話不多(兄嘚看圖)

    1.3Spring的版本

    Spring3.x、Spring4.x和Spring5.x

    1.4Spring的體繫結構

    正所謂,人狠話不多(兄嘚看圖)

    2、Spring的入門(IOC)

    2.1什麼IOC

    一說起IOC我就想起了武哥對IOC的理解的幾個例子,可謂通俗易懂,非常適合剛入門Spring的兄嘚!有興趣的可以去了解了解武哥,武哥博客:https://blog.csdn.net/eson_15

    IOC(Inverse of Control):控制反轉,也可以稱為依賴倒置。
    控制反轉:將對象的創建權反轉給(交給)Spring。

    所謂依賴,從程序的角度看,就是比如A要調用B的方法,那麼A就依賴於B,反正A要用到B,則A依

    賴於B。所謂倒置,你必須理解如果不倒置,會怎麼著,因為A必須要有B,才可以調用B,如果不倒

    置,意思就是A主動獲取B的實例:B b = new B(),這就是最簡單的獲取B實例的方法(當然還有各種

    設計模式可以幫助你去獲得B的實例,比如工廠、Locator等等),然後你就可以調用b對象了。所

    以,不倒置,意味着A要主動獲取B,才能使用B;到了這裏,就應該明白了倒置的意思了。倒置就是

    A要調用B的話,A並不需要主動獲取B,而是由其它人自動將B送上門來。

    2.2通俗理解IOC

    形象的舉例就是:
    通常情況下,假如你有一天在家裡口渴了,要喝水,那麼你可以到你小區的小賣部去,告訴他們,你需要一瓶水,然後小賣部給你一瓶水!這本來沒有太大問題,關鍵是如果小賣部很遠,那麼你必須知道:從你家如何到小賣部;小賣部里是否有你需要的水;你還要考慮是否開着車去;等等等等,也許有太多的問題要考慮了。也就是說,為了一瓶水,你還可能需要依賴於車等等這些交通工具或別的工具,問題是不是變得複雜了?那麼如何解決這個問題呢?
    解決這個問題的方法很簡單:小賣部提供送貨上門服務,凡是小賣部的會員,你只要告知小賣部你需要什麼,小賣部將主動把貨物給你送上門來!這樣一來,你只需要做兩件事情,你就可以活得更加輕鬆自在:
    第一:向小賣部註冊為會員。
    第二:告訴小賣部你需要什麼。

    這和Spring的做法很類似!Spring就是小賣部,你就是A對象,水就是B對象
    第一:在Spring中聲明一個類:A
    第二:告訴Spring,A需要B

    假設A是UserAction類,而B是UserService類

    <bean id="userService" class="org.leadfar.service.UserService"/>
    <bean id="documentService" class="org.leadfar.service.DocumentService"/>
    <bean id="orgService" class="org.leadfar.service.OrgService"/>
     
    <bean id="userAction" class="org.leadfar.web.UserAction">
         <property name="userService" ref="userService"/>
    </bean>

    在Spring這個商店(工廠)中,有很多對象/服務:userService,documentService,orgService,也有很多會員:userAction等等,聲明userAction需要userService即可,Spring將通過你給它提供的通道主動把userService送上門來,因此UserAction的代碼示例類似如下所示:

    package org.leadfar.web;
    public class UserAction{
         private UserService userService;
         public String login(){
              userService.valifyUser(xxx);
         }
         public void setUserService(UserService userService){
              this.userService = userService;
         }
    }
    

    在這段代碼裏面,你無需自己創建UserService對象(Spring作為背後無形的手,把UserService對象通過你定義的setUserService()方法把它主動送給了你,這就叫依賴注入!),當然咯,我們也可以使用註解來注入。Spring依賴注入的實現技術是:動態代理

    2.3下載Spring的開發包以及解壓說明

    官網下載:http://spring.io/
    什麼?不會下載?what???
    好吧,已打包好了QAQ:https://pan.baidu.com/s/18wyE-5SRWcCu12iPOX56pg
    什麼?沒有網盤?what???
    有事請燒香謝謝…

    解壓之後,文件說明:
    docs :Spring的開發規範和API
    libs :Spring的開發的jar和源碼
    schema :Spring的配置文件的約束

    2.4創建web項目,引入jar包

    2.5創建普通接口和實現類

    創建普通接口,定義一個eat方法

    package com.gx.sping;
    
    public interface IUserDao {
    
        public void eat();
    }
    

    創建普通實現類

    package com.gx.sping;
    
    public class UserDaoimpl implements IUserDao {
      @Override
        public void eat() {
            // TODO Auto-generated method stub
            System.out.println(用戶eat了");
        }
    }
    

    2.6Spring的IOC底層實現原理

    創建普通接口和類出現的問題:
    如果底層的實現切換了,需要修改源代碼,能不能不修改程序源代碼對程序進行擴展?
    重點來了,要想不改變源碼,Spring的IOC就能實現!如下圖:Spring的IOC底層實現

    2.7將實現類交給Spring管理

    1、在classpath下(也就是src)創建一個XML文件

    2、文件名最好統一叫applicationContext.xml

    3、其xml文件的內容頭為schema約束

    4、約束文件位置在spring的解壓路徑下lspring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html\xsd-configuration.htm

    5、不要求xml文件的內容頭能夠背出來,但要了解的是你要知道它是怎麼來的

    6、xml文件的內容頭添加后,將實現類交給Spring管理

    applicationContext.xml配置文件如下

    <?xml version="1.0" encoding="UTF-8"?>
    <beans 
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
     <!-- 實現類UserDaoimpl交給Spring管理 -->
         <bean id="IuserDao" class="com.gx.Ioc.UserDaoimpl" ></bean>
    </beans>

    2.8編寫測試類

    package com.gx.Ioc;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class SpingDemo1 {
        @Test
        public void demo11() {
            // 面向接口傳統方式
            UserDaoimpl userdao = new UserDaoimpl();
            userdao.eat();
        }
           //Spring的bean管理方式
        @Test
        public void demo22() {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            IUserDao userdao = (IUserDao) applicationContext.getBean("IuserDao");
            userdao.eat();
    
        }
    
    }

    兄嘚,如果測試不成功最好看看二者是否對應!!!

    兄dei,到這裏,Spring的入門(IOC)算是入門了,是不是覺得很有成就感啊?

    拉倒吧! 我都不好意思說了.(兄dei,我錯了,是我飄了,呀呀呀,兄dei別打臉鴨QAQ)

    但是我依舊是阻止不了你驕傲的心.

    那就頂我,讓我感受感受你的驕傲!哈哈哈QAQ

    2.9 IOC和DI

    IOC不是什麼技術,而是一種設計思想,IOC能指導我們如何設計出松耦合、更優良的程序。傳統應用程序都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難於測試;有了IoC容器后,把創建和查找依賴對象的控制權交給了Spring容器,由容器進行注入組合對象,所以對象與對象之間是鬆散耦合,這樣利於功能復用,更重要的是使得程序的整個體繫結構變得非常靈活。
    IOC:控制反轉,將對象的創建權反轉給了Spring。
    DI:依賴注入,前提必須有IOC的環境,Spring管理這個類的時候將類的依賴的屬性注入(設置)進來。比如說下面講到的Spring的屬性注入其實就是典型的DI

    所謂繼承:is a

    Class A{
    
    }
    Class B extends A{
    
    }

    所謂依賴:

    Class A{
    
    }
    Class B{
        public void xxx(A a){
    
    }
    }
    

    所謂聚合:has a

    3、Spring的工廠類

    3.1Spring工廠類的結構

    3.2老版本的工廠類:BeanFactory

    BeanFactory:調用getBean的時候,才會生成類的實例。

    3.3新版本的工廠類:ApplicationContext

    ApplicationContext:加載配置文件的時候,就會將Spring管理的類都實例化
    ApplicationContext有兩個實現類
    1、ClassPathXmlApplicationContext :加載類路徑下的配置文件
    2、FileSystemXmlApplicationContext :加載文件系統下的配置文件

    4、Spring的配置

    4.1XML的提示配置(Schema的配置)

    在XML文件中要使用各種標籤來給spring進行配置,博主我這佩奇腦袋怎麼可能記住spring中所有的標籤呢,不怕不怕,博主我會配置XML的提示配置QAQ,會了這一招就算兄dei你是喬治腦袋也不用擔心(再說了我看兄dei各各英俊瀟洒,玉樹臨風,聰明絕頂…咳咳,暴露了暴露了)

    4.2Bean的相關的配置(< bean >標籤的id和name的配置)

    id :使用了約束中的唯一約束。裏面不能出現特殊字符的。上面提及到了要與getbean參數值對應

    name :沒有使用約束中的唯一約束(理論上可以出現重複的,但是實際開發不能出現的)。裏面可以出現特殊字符。

    4.3Bean的生命周期的配置(了解)

    init-method :Bean被初始化的時候執行的方法
    destroy-method :Bean被銷毀的時候執行的方法(Bean是單例創建,工廠關閉)

    4.4Bean的作用範圍的配置(重點)

    scope屬性Bean的作用範圍

    scope屬性值如下(主要用的是前二者)
    singletonscope屬性的默認值,Spring會採用單例模式創建這個對象。
    prototype多例模式。(Struts2和Spring整合一定會用到)
    request :應用在web項目中,Spring創建這個類以後,將這個類存入到request範圍中。
    session :應用在web項目中,Spring創建這個類以後,將這個類存入到session範圍中。
    globalsession :應用在web項目中,必須在porlet環境下使用。但是如果沒有這種環境,相對於session。

    5、Spring的屬性注入

    首先,創建幾個普通類

    com.gx.spring.demo.Car
    public class Car {
        private String name;
        private Double price;
        
        public Car(String name, Double price) {
            super();
            this.name = name;
            this.price = price;
        }
        @Override
        public String toString() {
            return "Car [name=" + name + ", price=" + price + "]";
        }
    }
    com.gx.spring.demo.Car2
    /**
     * 用作set方法的屬性注入類
     */
    public class Car2 {
        private String name;
        private Double price;
        public void setName(String name) {
            this.name = name;
        }
        public void setPrice(Double price) {
            this.price = price;
        }
        @Override
        public String toString() {
            return "Car2 [name=" + name + ", price=" + price + "]";
        }
        
    }
    
    com.gx.spring.demo.Person 
    /**
     * 用作set方法的對象屬性注入類
     */
    public class Person {
        private String name;
        private Car2 car2;
        public void setName(String name) {
            this.name = name;
        }
        public void setCar2(Car2 car2) {
            this.car2 = car2;
        }
        @Override
        public String toString() {
            return "Employee [name=" + name + ", car2=" + car2 + "]";
        }
    }
    

    5.1構造方法的方式的屬性注入

    構造方法的屬性注入
    constructor-arg 標籤用於配置構造方法的屬性注入
    name :參數的名稱
    value:設置普通數據
    ref:引用數據,一般是另一個bean id值

    當然了,構造方法的方式的屬性注入也支持對象屬性的注入,標籤中對應屬性也是ref
    如果只有一個有參數的構造方法並且參數類型與注入的bean類型匹配,那就會注入到該構造方法中

    applicationContext.xml中配置:

    <!-- 構造方法的方式 -->
        <bean id="car" class="com.gx.spring.demo.Car">
            <constructor-arg name="name" value="瑪莎拉蒂"/>
            <constructor-arg name="price" value="800000"/>
        </bean>

    測試方法:

        /**
         * 構造方法方式的普通屬性注入方法
         */
        public void demo1(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            Car car = (Car) applicationContext.getBean("car");
            System.out.println(car);
        }

    5.2Set方法的方式的屬性注入【開發常用】

    Set方法的普通屬性注入
    property 標籤用於配置Set方法的屬性注入
    name :參數的名稱
    value:設置普通數據
    ref:引用數據,一般是另一個bean id值

    applicationContext.xml中配置:

    <!-- set方法的方式 -->
    <bean id="car2" class="com.gx.spring.demo.Car2">
            <property name="name" value="法拉利黃金跑車"/>
            <property name="price" value="10000000"/>
    </bean> 

    測試方法:

    @Test
        /**
         * set方法方式的屬性注入
         */
        public void demo2(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            Car2 car2 = (Car2) applicationContext.getBean("car2");
            System.out.println(car2);
        }

    Set方法設置對象類型的屬性
    applicationContext.xml中配置:

    <!-- set方法注入對象類型的屬性 -->
    <bean id="Person" class="com.gx.spring.demo.Person">
                <!-- value:設置普通類型的值,ref:設置其他的類的id或name-->
            <property name="name" value="濤哥"/>
            <property name="car2" ref="car2"/>
        </bean> 

    測試方法:

    @Test
        /**
         * set方法注入對象類型
         */
        public void demo3(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            Person person= (Person) applicationContext.getBean("Person");
            System.out.println(person);
        }

    5.3註解的方式屬性注入【開發常用】

    @Component (作用在類上通用:組件)
    @Component(“userService”)相當於< bean id=”userService” class=”…”>

    衍生:
    @Controller Web層
    @Service 業務層
    @Repository 持久層
    這三個註解是為了讓標註類本身的用途清晰

    屬性注入的註解 ( 可以沒有set方法
    普通類型屬性:@Value

    對象類型屬性:@Resource對應bean中的id)= @Autowired(類型)+ @Qualifier(名稱)

    5.3.1註解的理解

    額,初學框架,註解二字可能對於大部分熊dei來說,太過於陌生,註解其實就是在一個類、方法、屬性上,使用@註解名稱,就比如是我們最熟悉的接實現口中的方法默認會有一個 @Override (熊dei,這樣理解能接受?)

    5.3.2註解的jar包導入

    Spring3.x註解的jar包
    在Spring3.x的版本中,使用註解開發,只需要 spring核心基礎四包外 + log4j包 + 1個依賴包 即可

    Spring4.x註解的jar包
    然而在Spring4.x版本之後則需在 再添加一個要引入 spring-aop 的 jar 包,因為,spring4.x版本中一些註解的依賴方法封裝在spring-aop 的 jar 包中

    5.3.3引入註解的context約束

    所謂約束就是就是就是約束啦(搽汗),其中bean約束是最基本的約束!(下圖也可以看出)

    引入約束:(引入 context 的約束):

    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd">
    </beans>
    5.3.4編寫相關的類
    public interface UserDao {
    public void sayHello();
    }
    public class UserDaoImpl implements UserDao {
    @Override
    public void sayHello() {
    System.out.println("Hello Spring...");
    } }
    5.3.5配置註解掃描

    Spring的註解開發:組件掃描(不使用類上註解的時候可以不用組件掃描
    使用註解方式,需要開啟組件掃描< context:component-scan base-package=直接包名或者包名.類名/>,當然開發中一般都是base-package=包名,畢竟這樣可以掃描整個包,方便開發
    Spring 的註解開發:組件掃描(類上註解: 可以直接使用屬性注入的註解)

    <!-- Spring 的註解開發:組件掃描(類上註解: 可以直接使用屬性注入的註解) -->
    <context:component-scan base-package="com.gx.spring.demo1"/>
    5.3.6 在相關的類上添加註解

    1、使用類上註解方式@Component(value=“userDao”),相當於< bean id=”userDao class=”com.gx.類名”>< /bean>
    當然value屬性名可以省去直接@Component(”userDao”),當然@Component(“value值任意寫建議取的要有意義”)
    2、註解方式可以沒有set方法

    @Component(value="userDao")  //相當於配置了<bean id="userDao" class="com.gx.UserDaoImpl "></bean>
    public class UserDaoImpl implements UserDao {
    @Override
    public void sayHello() {
    System.out.println("Hello Spring Annotation...");
    } }
    5.3.7 編寫測試類
    @Test
    public void demo3() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao = (UserDao) applicationContext.getBean("userDao");
    userDao.sayHello();
    }

    5.4P名稱空間的屬性注入(Spring2.5以後)

    通過引入p名稱空間完成屬性的注入:
    寫法:
    普通屬性 p:屬性名=”值”
    對象屬性 p:屬性名-ref=”值”

    P名稱空間的約束引入

    使用p名稱空間

    5.5 SpEL的屬性注入(Spring3.0以後)

    SpEL:Spring Expression Language,Spring的表達式語言。
    語法: #{SpEL}

    5.6集合類型屬性注入(了解)

    集合類型屬性配置:
    集合的注入都是在< property>標籤中添加子標籤
    數組:< array >
    List:< list >
    Set:< set >
    Map:< map > ,map存放k/v 鍵值對,使用 描述
    Properties:< props> < prop key=””>< /prop>
    普通數據:< value >
    引用數據:< ref >

        <!-- Spring的集合屬性的注入============================ -->
        <!-- 注入數組類型 -->
        <bean id="collectionBean" class="com.gx.spring.demo.CollectionBean">
            <!-- 數組類型 -->
            <property name="arrs">
                <list>
                    <value>天才</value>
                    <value>王二</value>
                    <value>冠希</value>
                </list>
            </property>
            
            <!-- 注入list集合 -->
            <property name="list">
                <list>
                    <value>李兵</value>
                    <value>趙如何</value>
                    <value>鄧鳳</value>
                </list>
            </property>
            
            <!-- 注入set集合 -->
            <property name="set">
                <set>
                    <value>aaa</value>
                    <value>bbb</value>
                    <value>ccc</value>
                </set>
            </property>
            
            <!-- 注入Map集合 -->
            <property name="map">
                <map>
                    <entry key="aaa" value="111"/>
                    <entry key="bbb" value="222"/>
                    <entry key="ccc" value="333"/>
                </map>
            </property>
        </bean>

    6、Spring的分模塊開發的配置

    分模塊配置:
    在加載配置文件的時候,加載多個,沒錯,這就是傳說中的騷操作,堪稱開掛級別的操作(當然,這是可以的不是開掛)

    在一個配置文件中引入多個配置文件,簡直優秀!!!

    到這裏,恭喜恭喜,各位熊dei以優雅的儀式感闖進Spring世界,對Spring的IOC以及DI有了一定了解了,是不是也很期待Spring的Aop吶,畢竟Spring的核心是控制反轉(IOC)和面向切面(AOP)。

    (哎哎,別打..別打..別打臉….)

    如果本文對你有一點點幫助,就請點個讚唄,手留余香,謝謝!

    最後,歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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