2017年11月27日 星期一

linux 用戶指令快速入門

※一般命令

echo xxx && ooo
ls  -l    --all = -a    --human-readable = -h  --inode = -i,看多檔用空隔隔開
pwd
whoami
cd
tree
date '+%Y/%m/%d %H:%M:%S',%a %b %Z
cal
mkdir
rmdir:刪除空目錄
cp --recursive = -r   --interactive = -i:windows為 xcopy /E /H 
mv --interactive = -i:windows為 move
rm --recursive = -r   --interactive = -i:
      windows 分兩步,為 [del|erase] /S /Q bbb && [rmdir|rd] /S /Q bbb 
touch xxx.txt,windows 為 type nul > xxx.txt
man: see also -> man [number] command
cat --number = -n
tac
tty
history    !!:上一個命令     !c:最接近c開頭命令    !號碼
file 查看是什麼檔案
more
less

grep,過濾結果,windows 有類似的,如 netstat -ano |find ":80"
-A -B -C 還蠻好用的
cat xxx.txt |grep abc -A 5:表示找xxx.txt裡有abc這行和之後的5行,找到多個會用「--」隔開
-B:之後
-C:前後,可以不打-C,直接打數字即可

|
head -行數,預設 10 行
tail -行數 --follow = -f:f 不能和 -行數一起使用,功能是畫面停住,等待新的訊息進來,然後顯示,Ctrl + c 可離開
ln


※vim

visual(視覺的) improved(改進過的),因為原本是 vi,改進過後是 vim
man vi 可查參數,再按 h 可查文檔

命令模式

剪下:[number]D
複製:[number]yy,yank 為使勁拉的意思
貼上:[number]p
刪除:[number]dd
上一次修改:u
下一次修改:Ctrl + r
上一頁:Ctrl + b (before)
下一頁:Ctrl + f (after)
到某一行:ngg 或打「:」加行數

切換成編輯模式:a、i、o、A、I、O
切換成搜尋取代模式::、/、?


編輯模式

回命令模式:Esc


搜尋取代模式

:

數字:跳到某一行
set nu:顯示行號,如果想一進去就有行號,可用臨時的 vi -c "set nu" 檔名,或用永遠的方法,在 ~裡,創建 .vimrc 或 .virc 檔案,將 set nu 打進去存檔,這樣以後開所有檔都有行號了,是針對當前使用者的設定,vi 開頭的用 vi; vim 開頭的 用 vim,當然也可以兩個檔案都打
set nonu
set ignorecase:預設搜尋、取代時,是有分大小寫的,可打上這個命令來忽略大小寫
set noignorecase
set nohl:no high light,搜尋完後,下次進去,預設會高亮,可用這個指令
w
q
q!

取代,如「1,$s/xxx/ooo/gci」,第一行到最後一行,將 xxx 取代成 ooo
    「.,$s/xxx/ooo/gci」:目前行到最後一行
    「%s/xxx/ooo/gci」:第一行到最後一行,和 1,$s 一樣
g 為全部取代,沒加只取代一個
c 為檢查,會有 y/n/a/q/l/^E/^Y 的提示,yn就不說了
    a 表示目前到之後都取代
    q 表示不取代,並且直接離開
    l 表示取代目前這一個,並且直接離開
    ^E 表示跳上一頁
    ^Y 表示跳下一頁
    但 ^E 和 ^Y 我不知道要怎麼按
i 為不區分大小寫,也可用 上面的 set ignorecase

:2,10y:複製 2~10 行
:2,10d:剪下 2~10 行
:2,10y co 15:複製 2~10 行並貼到 15 行
:2,10y m 15:剪下 2~10 行並貼到 15 行

/

尋找,如「/xxx」,搜尋 xxx,然後使用 n|N可往下往上搜尋
搜尋完整單字,如 do、doc、dock,使用 /do 時,三個都會找到
可用 /\<do\>,使用「<」「>」包起來,但要用跳脫字元「\」就可以找完整的單字
又如果下/\<[dD]o\>,那會找 do 和 Do

?

同 / 的尋找,但一開始是下往上尋找,一樣用n|N找


回命令模式:Esc

多檔編輯

vim -o,水平,使用 Ctrl + w 後,按左或右切換
vim -O,垂直,使用 Ctrl + w 後,按上或下切換
vim -p 檔一 檔二,命令模式使用 :tabn 或 :tabp 切換
vim 檔一 檔二,命令模式使用「:sp 檔名」,然後使用 Ctrl + w ,上或下切換


※alias

alias vim='vim --cmd "set nu"',但登出後就沒了
將上一行打在 ~/.bashrc 裡,然後使用 source ~/.bashrc 或者下次登入就會生效了
unalias 為相反的命令

/etc/profile 全域環境變數     ~/.bash_profile 使用者環境變數
/etc/bashrc 全域 bash shell    ~/.bashrc 使用者 bash shell


※標準輸出入、標準錯誤輸出

< 檔案
<< 文字結束
>、1>
>>、1>>
2>、>&
2>>、>>&
&>

wc < /etc/passwd 行、單字、容量
wc < test1 >> test2


※搜尋

which:查尋環境變數的可執行程序,結果只有路徑
whereis:自訂的資料庫,結果有可執行命令 設定檔 手冊
locate:自訂的資料庫(var/lib/mlocate),比 whereis 更詳細,模糊查尋
find:最後的絕招, find /home -name xxx
find 後面接要搜尋的路徑,不打就是當前的路徑
-atime
-mtime 3 前三天的那一天,檔案內容有被修改過的
  +3 3天之外
  -3 3天之內
-ctime

-user 帳號名
-group 用戶組
-nouser 不屬於任何用戶
-nogroup 不屬於任何用戶組

-name 檔名或目錄名,可用通配符,但要用「'」包起來,如 '*bru*'
-iname 檔名或目錄名(不分大小寫)

-type f,查找檔案,l、d為連結和目錄
-size +100k:大於等於100K的文件
-perm +7000:找特殊權限
-exec 找到後執行命令,一直找到「;」之前
{}:會被替換當前的檔名
\:每個系統的「;」有可能有不同的意義,所以加上這個跳脫字元
例:
find -name 'test[34]' -exec sed -i 's/aaa/zzz/g' {} \;
sed 指令說明
find 指令說明

搜檔案內容
find /etc -name "*.text" -exec grep -H "xxx" {} \; 在 /etc 下搜尋 .text 結尾的檔案,內容含有 xxx的,--with-filename = -H 為列出路徑


※ls -l  的訊息

drwx-w--wx 2 bruce root 4096

d、-、l 目錄 檔案 連結

rw-r-x-wx
三個一組,分別表示權限,擁有者 群組 其他

目錄
r:查看目錄內容
w:增刪改
x:進入目錄

檔案
r:查看檔案內容
w:增刪改
x:執行

2 為檔案數量,目錄至少有.和..,不包括子目錄;檔案一定是 1
擁有者是誰
群組名
檔案大小(Byte)
Mtime
檔名

除了 root 外,擁有者、群組、其他人只能有一個角色
例如有一個使用者,是 A 檔的擁有者也是群組,此時若擁有者的權限不足,並不會抓群組的權限

※mtime、ctime、atime 區別

mtime:檔案內容最後被修改的時間,ls -l
ctime:檔案的內容、屬性最後被修改的時間,如修改權限、修改擁有者、改檔名,ls -cl
atime:最後被讀取的時間,vi 開啟就會讀到了,但 echo 不會讀到,ls -ul,但在安裝作業系統時可以選擇不要更新 atime,可以增進效能


※su、su -、sudo

su:切換成 root,shell 不切換
su -:切換成 root,shell 也切換
以上兩個後面如果接使用者,就會切換到那個使用者,輸入的密碼為切換帳號的密碼

sudo:臨時切換成 root,必需先設定 /etc/sudoers,可用 vi /etc/sudoers 或者 visudo 編輯,
輸入的密碼為自己的密碼

帳號     來源主機名稱=(可切換的身份)     能下什麼指令(絕對路徑)
root ALL=(ALL) NOPASSWD: ALL
root 這個帳號從任何主機連進來都可用現在的設定,可切換任何身份執行任何命令
其中(ALL) 和 NOPASSWD: 可以不寫
(ALL) 表示切換哪個帳號的身份,不寫就是 root,ALL 表示任何帳號
NOPASSWD: 注意最後有個冒號,不寫預設是要打密碼的,打登入時的密碼,不是切換誰的密碼

xxx 192.168.0.1=(ooo) /aaa/bbb,/aaa/ccc
xxx 這個帳號從 192.168.0.1 這台主機連進來可用現在的設定,可切換成 ooo 
這個帳號,執行 /aaa/bbb 和 /aaa/ccc 這兩支程式

%zzz ALL=(ALL) ALL
加上「%」就變成群組了
zzz這個群組從任何主機連進來都可用現在的設定,可切換任何身份執行任何命令

sudo 指令說明



※背起來

rsync -avzhpr

a --archive
v --verbose
z --compress
h --human-readable
p --perms:preserve permissions
r --recursive


tar -zxvf -cvf

c --create
z --gunzip
x --extract
v --verbose
f --file

.tar.gz -> zxvf
.tar -> xvf
cvf -> 打包

rpm -ivh -Uvh -qa -e

i --info
h --hash
U --upgrade
q --query
a --all
e:erase,御載

rpm -Uvh
rpm -qa |grep httpd

rpm 必須自己解決依賴關係


yum [-y] [install | remove | update | search | list | list installed]

search:列出伺服器上可貨下載的
list:列出伺服器上可供下載和已安裝的
list installed:列出已安裝的
update:更新某個套件,如果後面不加套件,會整個系統更新

yum 底層還是 rpm,但會幫我們解決依賴關係



編譯

下載源碼後,如 nginx,然後 wget xxx 並解壓後進目錄
./configure --prefix=/xxx/ooo
make && make install
以上三步搞定,但有時候第一步有依賴問題會報錯,根據報錯的訊息下載,然後再重做一次,只要有錯就得重做
※「./」開頭是一定要加的,通常環境變數不會有設定你下載的路徑,在 linux 裡,只要是執行檔就是綠色的,執行時環境變數沒有時,就要加「./」,表示當前層
但有六個目錄例外:
bin
usr/bin
usr/local/bin

sbin
usr/sbin
usr/local/sbin

有錯時,如沒有 pcre,那就在下載時,後面加上 -devel,是 develop 的簡寫,通常開發會比較多東西
yum -y install pcre-devel

搞定後,去 --prefix 的目錄查看



2017年11月18日 星期六

設定 java 版本 (Maven 6)

官網文件

※方法一

<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>

※為什麼有 compilerVersion?可以到這篇看看


※方法二

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

※我試了官方的不行,還得把 version 刪除才可以


※方法三

<profiles>
    <profile>
        <id>javaVersion</id>
        <properties>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
        </properties>
        <!-- <activation> -->
        <!--     <activeByDefault>true</activeByDefault> -->
        <!-- </activation> -->
    </profile>
</profiles>
<activeProfiles>
    <activeProfile>javaVersion</activeProfile>
</activeProfiles>

※前面兩個方法都是在 pom.xml 設定,最好是在父 pom 設定,其他子 pom 就不用設了

※這個方法是在 settings.xml 設定,是全域的

※profile 寫好後並沒有生效,用 activeProfiles 標籤和註解的 activation 標籤都可以

2017年11月13日 星期一

私服 (Maven 5)

私人伺服器,簡稱私服



左邊為電腦,右邊為 maven central,中間為私服,每次都要連到 maven central 太慢了,所以才會架設中間的私服,讓每一台電腦用,如果私服沒有,那私服會去下載

如果只有一台電腦,那就沒差了,但可以練習用

此篇用的私服是 Sonatype Nexus,下載 OSS 版的來練習,這裡用的是 nexus-3.2.1-01-win64
下載解壓後會有兩個資料夾,將 nexus-3.2.1-01 資料夾裡的 bin 加到環境變數,然後執行 nexus /run,啟動有點久,出現了「Started Sonatype Nexus OSS 3.2.1-01」不要關掉,然後到 localhost:8081,預設帳密是 admin/admin123,可看官網文件的 Part1
換 port 可以到 nexus-3.2.1-01\etc\nexus-default.properties 改

type 有三種
group:可以將以下兩個類型,一個類型包括很多網址,可以合併成一個
hosted:本地倉庫
proxy:代理倉庫(maven-central)


1的圖示要登入才有,左圖的 Security \ Users 預設只有 admin 和 anonymous,可以先將 anonymous 關閉,也可以在這新增使用者、修改密碼…等操作


第 2 版可以全拉,但第 3 版不知道為什麼不行,只好拉二個了

maven-central 裡有個 Rebuild index 按鈕,因為一開始 本地倉庫可能以經有 jar 了,但現在才架好 nexus,那 nexus 肯定沒有這些 jar,所以可以按這個鈕,同步一下

打開 pom 檔,增加如下的設定



<repositories>
    <repository>
        <id>nexus2</id>
        <url>http://localhost:8081/repository/maven-public/</url>
        <releases>
            <enabled>false</enabled>
        </releases>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
    
    <repository>
        <id>central</id>
        <url>http://localhost:8081/repository/maven-central/</url>
    </repository>
</repositories>

※將網址貼到 url 裡。 尤於 url 只能寫一個,所以我寫了兩個 repository 了
如果是第 2 版,因為可以全拉到 group,所以可以只寫一個

※release、snapshots 標籤下的 enabled 標籤表示是否可以下載

※此時隨便找一個目前還沒有的jar,然後下 mvn install,在 console 就能看到從 nexus 下載了

※但如果沒有設定這個東西的人,就不會去 私服下載,所以最好把這個設定放在 settings.xml裡,所以 pom 檔可以刪了,改放在 settings.xml,然後將 settings.xml 複製給要用的人,但要注意設定完不會生效,必需要配置 activeProfile,如下:

<profiles>
    <profile>
        <id>nexus</id>
        <repositories>
            <repository>
                <id>nexus2</id>
                <url>http://localhost:8081/repository/maven-public/</url>
                <releases>
                    <enabled>false</enabled>
                </releases>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
    
            <repository>
                <id>central</id>
                <url>http://localhost:8081/repository/maven-central/</url>
            </repository>
        </repositories>
    </profile>
</profiles>
    
<activeProfiles>
    <activeProfile>nexus</activeProfile>
</activeProfiles>


※上面提供的 nexus 連結的 Part2 就幫我們設定好了,只要我們把 copy 的網址貼進去就行
裡面分成三個大 element
profiles:將 pom 檔裡的設定改成放在這,但不會生效
activeProfiles:必需配置這裡的設定才會將 profiles 對應的 id 生效
mirrors:如果不配置,私服關閉了,還是可以去 maven central 下載,可在 settings.xml 加設定,如下:
<mirrors>
    <mirror>
        <id>nexus</id>
        <mirrorOf>*</mirrorOf>
        <url>http://localhost:8081/repository/maven-central/</url>
    </mirror>
</mirrors>

※預設其中有一個 id 為 central 的,在 下載好的maven\lib\maven-model-builder-3.5.2.jar 的 org\apache\maven\model\pom-4.0.0.xml ,他預設 snapshot 的 enable 是 false,可在這裡覆寫

※如果要將自己寫的檔案發佈到私服還得在父 pom 檔裡設定  distributionManagement,這樣子之後就可以用 mvn deploy 發佈到私服,但是會出現「Error code 401, Unauthorized」,如下:

<distributionManagement>
    <repository>
        <id>dmRelease</id>
        <url>http://localhost:8081/repository/maven-releases/</url>
    </repository>
    
    <snapshotRepository>
        <id>dmSnapshot</id>
        <url>http://localhost:8081/repository/maven-snapshots/</url>
    </snapshotRepository>
</distributionManagement>



因為沒有帳號密碼,所以還得在 settings.xml 設定 servers element,裡面的 id要對要到 distributionManagement 裡的 repository 的 id,這要才能發佈到私服
<servers>
    <server>
        <id>dmRelease</id>
        <username>admin</username>
        <password>admin123</password>
    </server>
    
    <server>
        <id>dmSnapshot</id>
        <username>admin</username>
        <password>admin123</password>
    </server>
</servers>

※可以來這個路徑確認一下有沒有 deploy 成功,看你是releases 還 snapshots

※也可以用 Search >> Maven 搜尋

2017年11月4日 星期六

排序:冒泡、選擇、插入


※冒泡排序


元素會像泡泡一樣,往左或往右移動,取決於使用升序或降序,做法是判斷相鄰的元素

綠排為 index,藍排為值
以升序為例,先判斷 index 0和1,然後 index 1和2,一直到最後
每判斷一次,如果左值 > 右值就交換
內迴圈跑完就會產生最大的數在後面,所以內迴圈跑完就會少一次外迴圈



int count = 0;
int[] intArray = new int[] { 5, 4, 3, 2, 1 };
int len = intArray.length - 1;
    
for (int i = 0; i < len; i++) {
    boolean finish = true;
    for (int j = 0; j < len - i; j++) {
        count++;
        if (intArray[j] > intArray[j + 1]) {
            int temp = intArray[j];
            intArray[j] = intArray[j + 1];
            intArray[j + 1] = temp;
            finish = false;
        }
    }
    if (finish) break;
} System.out.println("迴圈跑幾次=" + count); System.out.println(Arrays.toString(intArray));

※外迴圈只有控制內迴圈跑完一次減少一次

※如果要排序的值有一些已經排好順序了,那 finish 變數就會看出效果


※選擇排序

選擇、插入 都是將資料分成已排序和未排序兩個部分
從未排序的元素選擇一個最大或最小值放入排序的元素裡,和插入排序相反

以最上面的圖為例
以升序為例,以 index 0為基底,判斷 index 1、index2…到最後
每判斷一次,如果左值 > 右值就交換
內迴圈跑完就會產生最小的數在前面,所以內迴圈跑完就會少一次外迴圈


int count = 0;
int[] intArray = new int[] { 5, 4, 3, 2, 1 };
int len = intArray.length - 1;
    
for (int i = 0; i < len; i++) {
    boolean finish = true;
    for (int j = 0; j < len - i; j++) {
        count++;
        if (intArray[i] > intArray[j + 1 + i]) {
            int temp = intArray[i];
            intArray[i] = intArray[j + 1 + i];
            intArray[j + 1 + i] = temp;
            finish = false;
        }
    }
    if (finish) break;
} System.out.println("迴圈跑幾次=" + count); System.out.println(Arrays.toString(intArray));

※和冒泡排序只差在 if 而已

※外迴圈是抓基底的來判斷


※插入排序

從未排序的元素第一個插入到排序的元素裡,和選擇排序相反

以最上面的圖為例
以升序為例:
第一次判斷 index 0和1
第二次判斷 index 1和2,再判斷 index 0和1
第三次判斷 index 2和3,再判斷 index 1和2,再判斷 index 0和1
依此類推...
每判斷一次,如果左值 > 右值就交換



int count = 0;
int[] intArray = new int[] { 3, 9, 4, 6, 1, 7, 2, 5, 8 };
    
for (int i = 0; i < intArray.length - 1; i++) {
    for (int j = i; j >= 0; j--) {
        boolean finish = true;
        count++;
        if (intArray[j] > intArray[j + 1]) {             int temp = intArray[j];             intArray[j] = intArray[j + 1];             intArray[j + 1] = temp;             finish = false;         }
        if (finish) break;
    }
}
System.out.println("迴圈跑幾次=" + count);
System.out.println(Arrays.toString(intArray));

※外迴圈 i 值給內迴圈 j

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 就好了