2017年10月31日 星期二

volatile 和 原子性

※volatile


尤於 CPU 的速度比主存還快,所以每個 Thread 都會有一個專屬的 cache,將主存的資料從主存拷過去

volatile 能保證 1.內存可見性和 2.排序性
.volatile 只能宣告在全域變數
.內存可見性:volatile 有揮發性的意思,意思就是用完就丟,在 java 裡的意思是值有改變就去主存抓
以上面的圖來說,要是值改變了,就相當於沒有 CPU cache 了,所以會到主存去抓 (保下面的賣票例子不可用 volatile,因為沒有原子性)

.排序性:CPU 會將我們寫的程式碼重排,如寫在第一行,但不一定就是先執行,不過他保證結果是一樣的,但可惜只是單線程一樣,多線程有可能會不一樣,而加上這個關鍵字可以禁止 CPU 重排

.只能解決一寫多讀的情形

.++i 是原子性;i++ 不是原子性,可用 AtomicXXX 或 LongAdder 產生原子性,高併發時用 LongAdder 較快,否則 AtomicXXX 較快



public class App {
    private volatile boolean flag = false;
    
    public void xxx() {
        new Thread(() -> {
            try {
                Thread.sleep(200);
                flag = true;
                System.out.println("oooooooooooooo");
            } catch (InterruptedException e) {
            }
        }).start();
    
        new Thread(() -> {
            while (true) {
                if (flag) {
                    System.out.println("xxxxxxxxxxxxxx");
                    break;
                }
            }
        }).start();
    }
    
    public static void main(String... ss) {
        new App.xxx();
    }
}


※此例如果不加 volatile 有可能兩個 CPU 同時抓到,所以迴圈裡的 flag 永遠都是 false了

※此例也可用 synchronized 或 Lock,但效能比較差

※不代表 volatile 可取代 synchronized
volatile 並沒有互斥性,也不能保證原子性
互斥性:像 synchronized 和 Lock 就有,一次只能一個進入,但指的是相同的物件
例:HttpSession 可以鎖定成功;但 HttpServletRequest 就不可能鎖定成功


※賣票(必需要有互斥性)

public class AppTest {
    volatile AtomicInteger ticket = new AtomicInteger(10);
    // Integer ticket = 10;
    
    public static void main(String... s) {
        new AppTest().xxx();
    }
    
    public void xxx() {
        Runnable run = () -> {
            for (;;) {
                // if (ticket > 0) {
                System.out.println("票" + ticket.get());
                if (ticket.get() > 0) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        // System.out.println(--ticket);
                        System.out.println(ticket.decrementAndGet());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        };
    
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
}


※還沒加原子性:雖然在減 1 時,其他線程有看見,但有可能還沒寫,其他線程就讀到舊的,因為 volatile 沒有原子性

※加原子性的 AtomicInteger 後,使用兩個線程看起來好像可以,實際上還是有問題,因為此例是偶數票,每次少兩張,最後是 0,但用更多的線程去跑或改成單數票又不行了

.因為雖然加了原子性,但在改之前,其他線程還是讀的到 (資料庫的不可重覆讀)
如第 1 次,四個線程都讀到 10,然後都減 1,但有原子性,所以最後是 6
而第 2 次,四個線程都讀到  6,然後都減 1,還是有原子性,所以最後是 2
而第 3 次,四個線程都讀到  2,然後都減 1,還是有原子性,所以最後是 -2
也就是說一次只能有一個線程讀取,所以必需要有互斥性

如果沒有 if,只讓它一直加或一直減,最終會是正確的



※原子性

表示不能切割,如後置遞增/減
例:i++ 在底層運作是
int temp = i;
i = i+1;
return temp;
這三步是不能切割的,但 volatile 無法保證這一點



public class MyThread implements Runnable {
    private int increment;
    // private AtomicInteger increment = new AtomicInteger(0);
    
    @Override
    public void run() {
        try {
            Thread.sleep(200);
            System.out.println(increment++);
            // System.out.println(increment.getAndIncrement());
        } catch (InterruptedException e) {}
    }
    
    public static void main(String... ss) {
        MyThread my = new MyThread();
        for (int i = 0; i < 10; i++) {
            new Thread(my).start();
        }
    }
}

※increment 有可能會有同時進入 CPU 的情形,使用 volatile 也沒有用

※java 1.5 新增了 java.util.concurrent.atomic 套件,專門做原子的操作

※incrementAndGet方法就是前置遞增了,而遞減單字是 decrement

※原子性採用 CAS (compare and swap) 算法,屬於樂觀鎖

※CAS 有三個值v和a都是主存抓來的,b是替換值,只有 v 和 a 的值相等才會將 b 的值取代 v 的值(CPU2 的圖錯了,v=1, a=0 才對)

※以上圖為例,CPU1 從主存抓到 i 的值是 0,此時 CPU2也進來抓到0
這時又換 CPU1,比較過後是一樣的,所以更新為 1,i 更新成功為1
這時又換 CPU2,因為 i 是 volatile,所以 v=1, a=0, 什麼也不做
※如果是 CAS 自旋,那 CPU2 還會繼續,這是 v 和 a 都是 1,就看 b 是什麼就可以取代

※判斷和塞新值要看成一個做法,也就是說不會在判斷時,另一個 Thread 進來的情況

※此例當然也是可以用 synchronized,但 synchronized 效能比較差

※還可看高手寫的文章

2017年10月30日 星期一

git 常用命令和圖

本地 git 圖



※此篇都是以 windows 版本 1.9.5 操作的

官網連結
working directory = working tree(工作區):上圖的中間那兩個,切換分支會被 git 刪除或增加
staging area:staged = cached = index(暫存區),上圖的最右邊
.git 目錄(本地庫):commit 之後,就會到這個階段,裡面有所有歷史的 commit

※git config

git config 設定的鍵值對,有以下三種範圍
--global :當前使用者:%userprofile%\.gitconfig
--local :.git 下(預設)\config
--system :git 安裝目錄下,預設在 %userprofile%\AppData\Local\Programs\Git\mingw64\etc\gitconfig (所有登入的使用者)
.優先級:local > global > system
有 local 就抓 local,沒有就去 global 找,再沒有就去 system,都沒有就出現警告或錯誤


官方說需設定 user.name 和 user.email,commit 時會用到,如果不加不能 commit (會提示)
如 git config user.name xxx
--list = -l 查看所有變數,可再下上面三條其中之一的範圍命令
--unset 和 --unset-all 刪除一個鍵和多個鍵,可再下上面三條其中之一的範圍命令
別名
git config alias.st status 只要打上 alias.xx 內鍵命令,就能使用別名代替,以後只要用 st 就能代替 status 了


※基本命令

git status --short = -s:查看狀態
git commit --message = -m;--all = -a :提交,一起使用時,a 一定要在 m 前面,但如果此檔沒有 add 過是不行的
git rm --cached :從 unmodified 移到 untracked
git log --oneline --graph:查看提交記錄並顯示圖
git reflog:查看所有歷史記錄
git show:後面接 id 號,可看 commit 了什麼,id 號要用 log 或 reflog 查


※.gitignore

只會對 untracked 有效
在 Windows 新增檔案 .gitignore 會失敗,記得在最後加個「.」即可,副檔名記得要打開
內容如下範例:
.gitignore
*.log
!zz.log

這表示忽略本檔案和 log 結尾的檔案,但 zz.log 還是不忽略,注意第二行和第三行不能調換順序

預設是所有目錄、子目錄都會進行這樣的事,但如果只要根目錄下就好了,就要下絕對路徑
/.gitignore
/*.log
/!zz.log

最前面加個「/」即可

※如果已經 commit

git update-index --assume-unchanged fileName

如果又想反悔
git update-index --no-assume-unchanged fileName

查看所有已經 update-index 的檔案:git ls-files -v |grep '^h',windows 用 git ls-files -v |findstr /b h
或 git ls-files -v |findstr "^h"

注意:
1.雖然加了update-index --assume-unchanged,但有其他人更新了這個檔案,git pull 仍然會更新,只是方便 git add . 而已

2.ignore 必須在 untracked 使用才有效果

※將檔案加入上一個 commit,不會多一個 id,但會換掉id

git commit --amend --no-edit fileName


※暫存

git stash list:列出所有的暫存
git stash:暫存,但必需未 commit 的才可暫存,可多筆暫存,最新的是stash@{0},如果又stash,那最新的還是stash@{0},之前的會自動往下stash@{1},依此類推
git stash show:如果不加index,是 show 出最新的一筆
git stash apply:如果不加index,回復最新的一筆
git stash drop:如果不加index,刪除最新的一筆
git stash pop:apply + drop


※分支相關

git branch branchName (不會切換到新分支,切換用 git checkout)
git checkout -b branchName (會切換到新分支)
git branch --delete = -d 刪除本地分支
git branch --all = -a 看本地及遠端分支,不加只能看本地

※注意!如果已有 modified 、staged 的檔案
1.在已有分支的情形,無法切換
2.在沒有分支的情形,可以切過去,也能切回 master,
但其中一個分支對 modified 、staged 的檔案 commit 後,
另一個分支的 modified 、staged 的檔案內容,會自動退回上一次 commit 的內容,
從此以後,分支才沒有相關

※分支名預設是 master,是 git init 時預設的,但後期的 git 版本預設改成 main 了,
如果想改可用 git branch -m 或 -M,大 M 表示目標存存也覆蓋

.刪除遠端分支

git push --delete = -d origin 分支名稱
※git ls-remote 可以看到最新的所有遠端分支
git branch -r 可以看到從遠端複製下來的副本
但有一件很奇怪的事會使其他分支 pull 有問題
如果在本地創分支並提交到遠端,最後刪除了這個分支,然後要創造同名的分支,那其他分支 pull 時會強制更新,不會有問題
但有一種是斜線區隔的,如 bruce/aaa,bruce/bbb,這樣會產生資料夾 bruce,然後底下有兩個分支 aaa 和 bbb
假設我創分支 bruce 到遠端,最後刪除了,然後新增分支 bruce/aaa,自己的分支不會有問題,其他分支會無法 pull,錯誤圖如下:

這是 git branch -r 的問題
創建 bruce 分支的那個人,刪除了這個分支,會更新 git branch -r 的內容,但其他分支不知道,
然後創建 bruce/aaa 到遠端,git ls-remote 可以看到
其他分支 pull 時,認為 bruce 還在,但又有 bruce/aaa,bruce 變成資料夾了,就會報這個錯,
解決方法如上圖的紅框,下 git remote prune origin 即可,git branch -r 就會和遠端同步了


※合併

merge:

要合併到哪個分支,就要先切換 (checkout) 到那個分支
假設 master 分支有 a b 兩個檔案;而 develop 有 a c 兩個檔案
.如果切換到 develop 分支進行 merge,那 develop  分支會有 a b c 三個檔案;而 master 還是只有 a b 兩個檔案
.又如果是切換到 master 分支進行 merge,那 master 分支會有 a b c 三個檔案;而 develop 還是只有 a c 兩個檔案

例:git fetch 後 git merge origin master
.ff 和 --no-ff 的差別
git merge --ff 表示 fast forward,這是預設的,只會有分支開始到合併的所有提交記錄
git merge --no-ff --message = -m
--no-ff 除了有分支開始到合併的所有提交記錄,還會多一條 merge 的記錄(有衝突還會更多)
不加 --message,會跳出 vi 的畫面,這時打訊息內容也是一樣,同 commit 沒 -m 的道理
此時分支的畫面會有合併的圖,可用 git log --graph 查看


rebase:

rebase 後,log --graph 可以看到所有的分支都只剩一條了(但分支還在),而且有很多的 commit 都沒有了,速度也慢,唯一的優點就是只剩一條,看起來比較乾淨
如果想回復,還是可以用 reflogId 來回復

假設原本的圖是綠框(從左到右)

可以看出 rebase 變成一條了


※多功能的 checkout

git checkout 是將 HEAD 移動到指定的地方,所以可以(HEAD把他想成目前分支,.git資料夾裡有HEAD檔案,是純文字,可切換分支看裡面的變化)
1.切分支、創建分支
2.回退、退到之前的 tag
※分支名稱如果和檔案名稱一樣,就不能回退了,會以切分支為主

由於 checkout 有兩個功能,在後面的版本又多了兩個指令將這兩個功能分開,但 checkout 仍然可以用,多了 switch (切分支)和 restore(回退,由修改改成未修改)



※diff、difftool

比對兩個檔案,diff 是上下比對; difftool 會分成兩個視窗,左右比對

※未 commit 回退

checkout 從 modified -> unmodified
reset --mixed 從 staged -> modified,還會提示說 --mixed 已經 deprecated 了,可以不加
reset 的 soft 和 hard 會報錯


※已 commit 後回退

checkout:

git checkout 回退成功後,git log 、reflog 不會有變化

reset:

小心使用
git reset --hard 後面有5種
1. HEAD (回退最新的)
2. HEAD^^ (回退上二層,一個^表示一層)
3. HEAD~50 (回退50層,因為不想打很多的^)
4. gitlogId
5. HEAD@{number} (git reflog 看到的)
P.S. ^和~只能後退,不能前進

※reset 有三個容易搞混的參數 --hard --soft --mixed,預設是 --mixed
假設做了以下步驟
新增空檔案提交,提交訊息是 first
增加此檔內容 aaa,然後提交,訊息是 second
增加此檔內容 bbb,然後提交,訊息是 third
增加此檔內容 ccc,然後提交,訊息是 fourth
最後此檔的內容是 aaabbbccc

使用 git reset --soft HEAD^^
回退的指令是 commit 
檔案內容一樣,狀態是 stage(綠色),所以 diff --cached 有東西
log 沒有fourth
※不管回退多少內容都不會變,但已經 add,還沒 commit,所以是綠色的 

使用 git reset --mixed HEAD^^
回退的指令是 commit 和 add
檔案內容一樣,狀態是 modified(紅色),所以 diff 有東西
log 沒有fourth
※不管回退多少內容都不會變,但都還沒 add,所以是紅色的

使用 git reset --hard HEAD^^
回退的指令是 commit 和 add,然後再將檔案內容回復
檔案內容只有aaabbb,狀態是 unmodified,diff 和 diff --cached 都沒有 log 沒有fourth
※最好理解就這一個,回到哪時就是哪時

※回到伺服器上最新的版本:
git fetch origin master && git reset --hard origin master,不管有沒有 commit 都可以,如果確定是最新版的可以不下 git fetch


--merge
在 commit 之後編輯但還沒 add,此時 pull 下來,這時你覺得不滿意,想回到上一層,但用 --hard 會清空工作區(圖中間的二個),此時可以用 git reset --merge,會回退 pull 之前,且不會管尚未 add 的東西,但 add 會回退


revert:

從遠端 pull 完後,做了 reset 操作,但遠端不知道,此時 push 會有問題,所以 reset 要小心操作,但以這個需求,應該用 revert,reset 是回退到某一個 commit;而 revert 是增加一個 commit,所以 push 不會有問題,但不加參數會跳出討厭的 commit message edit
可下 git revert --no-edit xxx


checkout、restore、reset、revert區別

checkout:git log不變(modified->unmodified 紅色變無色)
restore --staged:git log不變(add->modified 綠色變紅色)
reset:git log 往後
revert:git log 往前



cherry-pick

檢櫻桃,意思就是將很多分支的其中某幾個 commit 合併到目前分支
假設有兩個分支 master 和 dev
dev 新增 ooo.txt,最後三個 commit 內容如下:
左內容右 hash
ccc                 e258
bbb                ffa7
aaa                 8frt

假設 master 只要 ooo.txt 內容為 bbb 和之前的內容,就可以切換到 master 後,下如下的指令:
git cherry-pick ffa7
如果要合併多個 commit,用空格隔開
這樣,master 分支裡就有 ooo.txt,內容是 bbb 和 aaa
有可能有衝突,衝突解決方式和之前一樣


※log

git log
--oneline 只顯示一行,只有 id 和提交訊息
--graph 最左邊有 git 的圖形
--author= 針對作者
fileName 針對某個檔案


※遠端

 .git 資料夾裡有個 config 檔,裡面是純文字檔
git remote add remoteName http~~
一做完這件事,config檔、git config、 git rermote --verbose = -v 都看得到
主要是因為網址太長了不容易記憶,所以取個別名,這樣以後就可以用這個別名上傳了,當然不設定也是可以的,只是每次都要複製網址 (除非你記的起來),預設叫 origin,是在 clone 時加的,不管遠端叫什麼名字,本機都是 origin,可以用 clone 的 --origin 來取遠端名稱
如果第一次 push,可以不叫 origin,但別人 clone 時,如沒有特別給 --origin 參數,會變成 origin

git remote set-url remoteName http~~ 可以修改別名
git remote --verbose 會看到 push 和 fetch,預設是一樣的,可以修改 push,用 --push 即可

git push 會提示要設定 push.default 的 key,value可以是 matching 或 simple
他的解釋是設定 matching 後,push 會上傳所有分支;simple 只會上傳目前的分支,但我試的時候,都只有上傳目前分支
git push --set-upstream = -u
git push --u remoteName branchName (每新開分支的第一次都要用 -u,之後只要 git push,branchName 可以多個,用空格隔開)
-u 後,config 檔會出現[branch "branchName"],表示已經有用過了,已後只要 git push 即可
但要注意 config 檔是「branchName」,所以每個分支第一次都要 -u
這個 -u 也會影響到 fetch
git pull = git fetch + git merge origin/branchName(用 git branch -a 查看)
git pull --rebase = git fetch + git rebase origin/branchName
git pull 只會更新自己分支的部分,其他分支只會知道有更新,但不會更新檔案內容
可以切換到其他分支下 git pull,檔案內容才會更新,但也可以在不切分支的情形使用
git pull origin 分支名 也可以

git push origin master:xxx 將本地的 master 分支推送到遠端的 xxx 分支,且遠端名叫 origin,但要注意遠端分支
1.不能有 xxx 的分支名
2.和本地名稱一樣可以

如果本地分支和遠端分支名稱一樣,可省略成 git push origin master

※clone

git clone下來後,只會有 master 分支,可以下 git ls-remote 分支名
產生遠端分支到本地分支:直接 checkout
假設遠端是 remotes/origin/dev,那只要 git checkout dev,這樣就可以了

※如果 clone 時,有加 --depth 1,這時會出 「error: pathspec 'xxx' did not match any file(s) known to git」的錯
此時只要先創建分支並切過去,然後 git pull 遠端名 分支名即可


※工具

可以使用 gitk 看,有畫面


※使用ssh

clone 時有 https 和 ssh 可選,但 ssh 需要設定才可以 clone,否則會出現 Please make sure you have the correct access rights and the repository exists.

首先要知道 Windows 放 ssh key 的目錄在 %userprofile%\.ssh; linux 在~/.ssh
使用 bash 有 ssh-keygen 這個命令可用
1.ssh-keygen -t rsa 會在目錄出現 id_rsa、id_rsa.pub 這兩個檔案
2.將 副檔名是 pub 的內容全部複製到 github 放 Key 的地方,如下圖:


Title 可不打,儲存後打 github 的密碼即可,此時已可使用 git clone ssh 地址,有提示要打yes,不能直接案 enter,用完會在邊錄多一個 known_hosts 檔案


※還可使用 ssh 測試,ssh -T git@github.com
這時會出現
Warning: Permanently added the RSA host key for IP address 'IP 地址' to the list of known hosts.
Hi bruce12452002! You've successfully authenticated, but GitHub does not provide shell access.
-T 表示 Disable pseudo-terminal allocation,禁止假的終端機分配
當使用 ssh 或 telnet 登錄時,系統給我們的終端就是假的終端機,禁止的意思是只能取得 shell 而已,很多環境變數都沒有,但測試已足夠


※如果重新使用 ssh-keygen 會針測到已有 key,會問你要不要覆蓋,如果打 y,會覆蓋
所以覆蓋或刪除檔案都必需重新做一次,否則一樣會出現權限不足的錯誤,如果別台電腦也想使用 ssh,將這兩個檔案複製到目錄即可

※如果密碼使用 SSH

ssh-keygen 有設定密碼,那每次 pull 或 push 都要打密碼
可以使用 ssh-agent 和 ssh-add 將密碼儲起來,這樣就不用打密碼了
1.eval `ssh-agent` 會顯示 Agent pid xxx
2.ssh-add 會提示要打密碼

※如果密碼使用 https

Windows 安裝完 git 是 credential.helper=manager 有 manager、cache、store 可選
可用 git config --list 查看
manager 會將密碼存在 windows 控制台的 credential manager,裡面有 Windows Credentials
   有安裝 git GUI 就會有 manager,意思是給 GUI 管理
cache 會將密碼儲 15 分鐘,可用 --timeout=300 改時間,單位為秒
store 會用明文的方式儲存,預設存在 ~/.git-credentials,可用 --file <path> 修改


※hook

.git 目錄裡有個 hooks 子目錄,裡面已經寫好了一些腳本,只要把「.sample」拿掉即可使用
pre 開頭表示什麼之前會執行;post 開頭表示什麼之後會執行

假設有一個特定的檔案,commit 前要顯示提示

※pre-commit 例一
#!/bin/bash
    
files=$(git diff --name-only --cached HEAD)
    
#使 read 可以使用
exec < /dev/tty
    
for fileName in ${files}; do
    if [ "$fileName" == 'test.txt' ]; then
        read -p "包含 test.txt, 真的要上傳嗎?()y|n)" u
        echo $u
        if [ "$u" != "y" ]; then
            echo "push fail"
            exit 1;
        fi
    fi
done
    
#回傳0表示繼續執行,非0結束
exit 0;

※但寫完這支,git 無法 push,所以先將 pre-commit 複製到 .git 的同一層,然後寫一支批次檔,此範例是微軟的
copy pre-commit .git\hooks\
del pre-commit
mklink /H pre-commit .git\hooks\pre-commit

複製完再刪除,才可以硬連結


※pre-commit 例二
#!/bin/bash
    
files=$(git diff --name-only --cached HEAD)
username=$(git config user.name)
path=src/main/resources/
account=(user1 user2 user3)
    
for fileName in ${files}; do
    if [ "$fileName" == "$path \\ba.java" -o "$fileName" == "$path \\bb.java" -o "$fileName" == 'c.java' ]; then
        for un in $account; do
            if [ "$un" == "$username" ]; then
                echo "你沒有權限修改" $fileName
                exit 1;
            fi
        done
    fi
done
    
#回傳0表示繼續執行,非0結束
exit 0;

※例一的例子,如果使用小烏龜,不用命令的方式,不支援鍵盤打字

※此範例是針對帳號,不能修改特定的檔案

※copy.cmd

move pre-commit .git\hooks\
del copy.cmd

※將 pre-commit 複製到 hooks 後,刪除自己

※還可以看我覺得寫的很好的 教學


※小技巧

※merge 部分檔案

merge 時,會將所有改變合併,如果只想 merge 某些檔案,可以如下使用

git checkout 分支名 檔名 檔名 檔名…
假設有個分支叫 dev,只有 a.txt 和 b.txt 想 merge 到 master 分支,操作如下:
git checkout master
git checkout dev a.txt b.txt
注意:沒有什麼衝不衝突,會直接將 master 的檔案覆蓋


※部分提交

如果已經都 add 了,那下 commit 會將已經 add 都提交
下 rm -r --cached xxx 可以將不想要 add 的,變成非 add 狀態,如果是檔案,可以不加 -r


※windows 針對資料夾底下的git專案pull

@echo off
setlocal
    echo 今仔日是 %date%
        
    FOR /D %%d IN (*) DO (
        cd %%d &&  echo ===============目前目錄是 %%d===============
    
        if exist == ".git" (
            git pull
        ) else (
            echo 根本沒git嘛!想唬哢我!
        )
        echo. && echo. && cd ..
    )
endlocal
pause

※副檔名為 cmd 或 bat 即可


※git add commit 原理


windows 每幾秒執行檔案查詢的批次程式碼:
@echo off
:begin
TREE /F
TIMEOUT /T 5
cls
goto begin
pause
存成 bat 檔並放在 .git 裡即可
 
linux 本來就有指令,所以不需寫程式
watch -n 1 -d find .
每一秒將當前目錄顯示出來



git add 後
會增加 index 檔案
還有在 objects 增加 hash 前 2 個數字的資料夾,裡頭有前 2 個 hash 之後檔案名稱

git cat-file -t 檔案的 hash,可看類型,blob
git cat-file -p 檔案的 hash,可看內容
git cat-file -s 檔案的 hash,可看檔案大小

git 只存檔案內容,檔案名稱不存的,如果檔案內容一樣,git 不會有變化

hash 使用的是 sha1,它是 160 長的
使用「類型 內容長度\0內容」來算出 hash 值的,內容長度(Byte)包括換行,可用 ls -lh 查看
如 「blob 15\0hello world」

git ls-file 可查看暫存區有什麼檔案
git ls-file -s 可將每個檔案的權限、hash、檔名列出來
原本的檔案修改完再 add,會產生新的 hash,舊的也還在,但新的 hash 會用在新的檔案
---

git commit 之後
會有兩個檔案,注意 commit 的訊息有 hash
git cat-file -t hash,可發現類型為 commit
再用 -p 看到檔案內容又有個 hash
然後再用 -t 看新的 hash 為 tree 型態
-p 內容為 commit 的檔案名稱和 hash

同時,refs\heads 也有一個叫此次提交的分支檔案,如 master,內容是 commit 的 hash
HEAD 這個檔案都是指向目前的分支 refs\heads\master,一切分支就會改變,
但也有不正常情況不會指向目前分支,如 checkout 到之前的 hash,此時狀態是 detached

2017年10月7日 星期六

開放封閉原則、單一職責原則

※開放封閉原則:

新增開放,修改封閉


public class People {
    public String function1(){
        return "功能1";
    }
    public String function2(){
        return "功能2";
    }
}
    
People p = new People();
System.out.println(p.function1());
System.out.println(p.function2());

※假設要新增功能3,就要修改了,這時就違返了開放封閉原則



public interface People {
    public String function();
}
    
public class Funtion1 implements People {
    @Override
    public String function() {
        return "功能1";
    }
}
    
public class Funtion2 implements People {
    @Override
    public String function() {
        return "功能2";
    }
}
    
People e1 = new Funtion1();
System.out.println(e1.function());
People e2 = new Funtion2();
System.out.println(e2.function());

※將 People 改成介面或抽象,以後都不會對 People 做修改
此時新功能只要新增子類,然後繼承 People




@FunctionalInterface
public interface People {
    public String function();
}
    
People e1 = () -> "功能1";
System.out.println(e1.function());
People e2 = () -> "功能2";
System.out.println(e2.function());

※如果父類  People 剛好只有一個功能要實作,還可以用 Java8,這時連子類都可以省略了



※單一職責原則

一個類別裡只要有一個功能就好,不要有太多的功能,假設類別裡有 3 個功能,那就拆成 3 個類別,所以使用時,只要針對要使用的功能去 new 就好了