-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
68 lines (68 loc) · 45 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[git学习笔记]]></title>
<url>%2F2018%2F10%2F09%2Fgit%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%2F</url>
<content type="text"><![CDATA[前言 什么是Git?Git是目前世界上最先进的分布式版本控制系统(没有之一),而且是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何项目。 Git安装 在Debian或者Ubuntu Linux下的Git安装非常简单,直接一条命令搞定 1| sudo apt-get install git Windows下的模拟环境安装起来比较复杂,那么可以用牛人封装好的模拟环境加Git,叫msysgit,只需要下载一个exe然后双击安装 可从<https://git-for-windows.github.io/>下载,或者从廖雪峰老师的镜像下载,然后按默认选项安装 .安装完成后,在开始菜单里找到“Git”->“GitBash”,跳出一个类似命令行窗口的东西,就说明Git安装成功 成功安装之后,还需要配置一下全局个人信息: 12git config --global user.name "Your Name"git config --global user.email "[email protected]" 每次提交,都会记录这两个值,--global参数,表示你这台机器上所有的Git仓库都会使用这个配置可使用git config -l 查看全局配置信息 运行前配置 一般在新的系统上,我们都需要先配置下自己的 Git工作环境。配置工作只需一次,以后升级时还会沿用现在的配置。 配置文件如何生效 对于 Git 来说,配置文件的权重是仓库>全局>系统。Git会使用这一系列的配置文件来存储你定义的偏好,它首先会查找/etc/gitconfig文件(系统级),该文件含有对系统上所有用户及他们所拥有的仓库都生效的配置值。接下来Git 会查找每个用户的\~/.gitconfig文件(全局级)。最后 Git会查找由用户定义的各个库中Git目录下的配置文件.git/config(仓库级),该文件中的值只对当前所属仓库有效。以上阐述的三层配置从一般到特殊层层推进,如果定义的值有冲突,以后面层中定义的为准,例如:.git/config和/etc/gitconfig的较量中,.git/config取得了胜利。 查看配置 格式:git config [--local\|--global\|--system] -l example: 1234567891011# 查看当前生效的配置git config -l# 查看仓库级的配置git config --local -l# 查看全局级的配置git config --global -l# 查看系统级的配置git config --system -l 修改配置 格式:git config [--local\|--global\|--system] key value example: 12| git config --global user.name ybd| git config --global user.email [email protected] 创建仓库(Repository) 创建一个目录并进入,进行初始化仓库123| mkdir repo | cd repo | git init 目录下会多一个.git 的隐藏文件,现在要创建一个文件并提交到仓库1234567| touch read| vi read| # 按a进入编辑| # 输入Git is a distributed version control system| # 按下Esc,并输入 ":wq" 保存退出| git add README.md #添加文件到缓存区| git commit -m "first commit" #将缓存区的文件提交到本地仓库 多个文件提交可用git add -A然后再commit1234| ➜ repo git:(master) ✗ git commit -m "first commit" | [master (根提交) 201817f5] first commit | 1 file changed, 2 insertions(+) | create mode 105624 read 操作的自由穿越 要随时掌握工作区的状态:git status查看修改内容:git diff read查看版本历史信息 got log 或git log --pretty=oneline 版本穿越 退到上一个版本: git reset –hard HEAD^ 上上一个版本就是 HEAD^,当然往上100个版本写100个^比较容易数不过来,所以写成 HEAD~100要重返未来,查看命令历史:git reflog 修改管理 添加文件到缓存区:git add read或 git add -A然后提交:git commit -m "msg"查看状态:git status每次修改,如果不add到暂存区,那就不会加入到commit中 撤销修改 当你发现工作区的修改有错误的时候,可丢弃工作区的修改: git checkout – read 命令 git checkout --read意思就是,把 read 文件在工作区的修改全部撤销,这里有两种情况: 一种是read 自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态 一种是 read 已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态 总之,就是让这个文件回到最近一次 git commit 或 git add 时的状态注意!git checkout -- file 命令中的 --很重要,没有-- 就变成了切换分支了 当你发现该文件修改错误并且已经提交到了缓存区,这个时候可以把暂存区的修改撤销掉(unstage),重新放回工作区: git reset HEAD read 然后再丢弃工作区中的修改: git checkout – read git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本 删除文件 如果把工作区中的文件删除了,那么工作区和版本库就不一致,git status命令会立刻告诉你哪些文件被删除了现在有两个选择 一是确实要从版本库中删除该文件,那就用命令删掉,并且提交: 12| git rm read | git commit -m "delete" 另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本: | git checkout – read ||———————-| git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原” 远程仓库 那么学会了Git的基本操作之后,对于分布式管理我们还需要有一个远程仓库供大家一起共同开发,好在有一个全世界最大最神奇的同性交友网—— Github那么在使用Github之前呢,我们需要设置一下与Github的SSH通讯: 创建SSH Key(已有.ssh目录的可以略过) ssh-keygen -t rsa -C “youremail\@example.com” 你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码如果一切顺利的话,可以在用户主目录里找到.ssh 目录,里面有 id_rsa 和 id_rsa.pub 两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人 登陆GitHub,打开“Account settings”,“SSH Keys”页面,然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容,最后点“Add Key” 添加远程仓库 首先到Github创建一个仓库然后与本地关联: git remote add origin git\@github.com:your-name/repo-name.git 远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库 下一步,就可以把本地库的所有内容推送到远程库上: git push -u origin master 把本地库的内容推送到远程,用 git push 命令,实际上是把当前分支master推送到远程由于远程库是空的,我们第一次推送master分支时,加上了 -u 参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令 此后的推送都可以使用: git push 从远程仓库克隆 git git\@github.com:your-name/repo-name.git 标签管理 查看tag 列出所有tag: git tag 这样列出的tag是按字母排序的,和创建时间没关系。如果只是想查看某些tag的话,可以加限定: git tag -l v1.* 这样就只会列出1.X的版本。 创建tag 创建轻量级tag: git tag 1.0.1 这样创建的tag没有附带其他信息,与之相应的是带信息的tag,-m后面带的就是注释信息,这样在日后查看的时候会很有用,这种是普通tag,还有一种有签名的tag: git tag -a 1.0.1 -m ‘first version’ 除了可以为当前的进度添加tag,我们还可以为以前的commit添加tag:首先查看以前的commit git log –oneline 假如有这样一个commit:a5cbc2 updated readme这样为他添加tag git tag -a v1.1 a5cbc2 删除tag 很简单,知道tag名称后: git tag -d v1.0 删除远程分支: git push origin –delete tag \<tagname> 共享tag 我们在执行gitpush的时候,tag是不会上传到服务器的,比如现在的github,创建tag后gitpush,在github网页上是看不到tag的,为了共享这些tag,你必须这样: git push origin –tags 分支管理 分支相当与平行宇宙,互不干扰,哪天合并了就拥有了所有平行宇宙的特性 创建与合并分支 每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支 一开始的时候,master分支是一条线,Git用 master指向最新的提交,再用HEAD指向master ,就能确定当前分支,以及当前分支的提交点 当我们创建新的分支,例如dev 时,Git新建了一个指针叫 dev ,指向 master 相同的提交,再把 HEAD 指向dev ,就表示当前分支在dev 上 Git创建一个分支很快,因为除了增加一个 dev指针,改改HEAD的指向,工作区的文件都没有任何变化 当 HEAD指向dev ,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而 master指针不变 ![http://mdpost.jmzhang.top/mdpost/20181009/gitstudy3.png](http://mdpost.jmzhang.top/mdpost/20181009/gitstudy3.png) 查看分支:git branch创建分支:git branch \<name\>切换分支:git checkout \<name\>创建+切换分支:git checkout -b \<name\>合并某分支到当前分支:git merge \<name\>删除分支:git branch -d \<name\> 解决冲突 合并分支并不是每次都不会出问题,如不同的分支对同一个文件同一行都被修改过,就会出现以下情况 那么再次合并有可能会冲突 123456789101112131415➜ repo git:(master) git merge feature1 自动合并 read冲突(内容):合并冲突于 read自动合并失败,修正冲突然后提交修正的结果。➜ repo git:(master) ✗ git status 位于分支 master您有尚未合并的路径。 (解决冲突并运行 "git commit")未合并的路径: (使用 "git add <文件>..." 标记解决方案) 双方修改: read修改尚未加入提交(使用 "git add" 和/或 "git commit -a") 这种情况必须手动解决然后再次git add.,git commit -m "commit",打开文件可看到 123456Git is a version control<<<<<<< HEADCreating a new branch is quick & simple.=======Creating a new branch is quick AND simple.>>>>>>> feature1 Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,那么经过合意,不好意思,大师兄说了,在座的各位都是垃圾,于是改成 Git,too fast too simple 再提交1234| 1 | ➜ repo git:(master) ✗ git add read || 2 | ➜ repo git:(master) ✗ git commit -m "conflict fixed" || 3 | [master 8933f88] conflict fixed || 4 | ➜ repo git:(master) | ok了,再次add和 commit ,现在master分支和feature1分支变成了这样 多PC协同开发 当你从远程仓库克隆时,实际上Git自动把本地的 master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin 查看远程库的信息:查看简单信息:git remote查看详细信息:git remote -v查看远程仓库分支:git branch -r查看本地分支与远程分支的对应关系:git branch -vv 推送分支123git push origin <branch> git push -u origin <branch> # 第一次推送加-u可以把当前分支与远程分支关联起来 克隆分支并关联1234git clone [email protected]:youName/program.git # 默认克隆master分支到当前目录(包含分支文件目录)git clone -b <branch> [email protected]:youName/program.git ./# 克隆指定分支到指定文件目录下(不包含分支文件目录) 创建远程origin的dev 分支到本地 git checkout -b origin/ 关联本地分支与远程仓库分支 git branch –set-upstream origin/ 同步更新Github Fork的项目 1、fork项目并clone到本地 2、进入项目根目录 3、添加remote指向上游仓库 git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git 4、把上游项目fetch下来 git fetch upstream 5、merge到master12| git checkout master | git merge upstream/master 6、push到自己的远程仓库,搞定~ 最后 Git真的异常强大,但命令繁多,需多加练习 参考:廖雪峰老师的教程 附命令图]]></content>
<categories>
<category>git</category>
</categories>
<tags>
<tag>git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[常用的八种排序算法Java代码实现]]></title>
<url>%2F2018%2F09%2F06%2F%E5%B8%B8%E7%94%A8%E7%9A%84%E5%85%AB%E7%A7%8D%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95Java%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0%2F</url>
<content type="text"><![CDATA[Algorithm 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323package algorithm;import java.util.Arrays;public class EightAlgorithm { /** * * 常用的八种排序算法Java代码实现 * 时间:2018-9-5-下午9:14:30 * 邮件:[email protected] * @exception * *辅助记忆* * 时间复杂度记忆- * 冒泡、选择、直接 排序需要两个for循环,每次只关注一个元素,平均时间复杂度为O(n2)(一遍找元素O(n),一遍找位置O(n)) * 快速、归并、希尔、堆基于二分思想,log以2为底,平均时间复杂度为O(nlogn)(一遍找元素O(n),一遍找位置O(logn)) * 稳定性记忆-“快选希堆”(非稳定:快牺牲稳定性) * 排序算法的稳定性:排序前后相同元素的相对位置不变,则称排序算法是稳定的;否则排序算法是不稳定的。 */ public static void main(String[] args) { int []array={23,56,8,45,2,95,11,12,17,10,11}; System.out.println("length: "+array.length); insertSort(array); //1.直接插入 //sheelSort(array); //2.希尔排序 //selectSort(array); //3.简单选择排序 //heapSort(array); //4.堆排序 //bubbleSort(array); //5.冒泡排序 //quickSort(array, 0, array.length-1);//6.快速排序 //mergeSort(array); //7.归并排序 //sort(array,10); //8.基数排序 System.out.println(Arrays.toString(array)); } /** * 1.直接插入 经常碰到这样一类排序问题:把新的数据插入到已经排好的数据列中。 * 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定 * O(n^2) O(n^2) O(1) 是 * 将第一个数和第二个数排序,然后构成一个有序序列 * 将第三个数插入进去,构成一个新的有序序列。 * 对第四个数、第五个数……直到最后一个数,重复第二步。 * ***如何写成代码: * 1.首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1个数的那次不用插入。 * 2.设定插入数和得到已经排好序列的最后一个数的位数。insertNum和j=i-1。 * 3.从最后一个数开始向前循环,如果插入数小于当前数,就将当前数向后移动一位。 * 4.将当前数放置到空着的位置,即j+1。 */ public static void insertSort(int[] a) { int insertNum; // 要插入的数 for (int i = 1; i < a.length; i++) { insertNum = a[i]; int j = i - 1; while (j >= 0 && a[j] > insertNum) {// 将大于insertNum的数向后移动一格 a[j + 1] = a[j]; j--; } a[j + 1] = insertNum; } } /** * 2.希尔排序 对于直接插入排序问题,数据量巨大时。 * 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定 * O(nlogn) O(n^s) O(1) 不是 * 将数的个数设为n,取奇数k=n/2,将下标差值为k的数分为一组,构成有序序列。 * 再取k=k/2 ,将下标差值为k的数分为一组,构成有序序列。 * 重复第二步,直到k=1执行简单插入排序。 * ***如何写成代码: * 1.首先确定分的组数。 * 2.然后对组中元素进行插入排序。 * 3.然后将length/2,重复1,2步,直到length=0为止。 */ public static void sheelSort(int[] a) { int length = a.length; while (length != 0) { length = length / 2; System.out.println("length变化: " + length); for (int x = 0; x < length; x++) { // 分组个数 for (int i = x + length; i < a.length; i += length) { int j = i - length; // j为有序序列最后一位的位数 int insertNum = a[i]; // 要插入的元素 for (; j >= 0 && insertNum < a[j]; j -= length) { a[j + length] = a[j]; // 向后移动length位 } a[j + length] = insertNum; } } } } /** * 3.简单选择排序 常用于取序列中最大最小的几个数时。 * 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定 * O(n^2) O(n^2) O(1) 不是 * (如果每次比较都交换,那么就是交换排序;如果每次比较完一个循环再交换,就是简单选择排序。) * 遍历整个序列,将最小的数放在最前面。 * 遍历剩下的序列,将最小的数放在最前面。 * 重复第二步,直到只剩下一个数。 * ***如何写成代码: * 1.首先确定循环次数,并且记住当前数字和当前位置。 * 2.将当前位置后面所有的数与当前数字进行对比,小数赋值给key,并记住小数的位置。 * 3.比对完成后,将最小的值与第一个数的值交换。 * 4.重复2、3步。 */ public static void selectSort(int[] a) { int length = a.length; for (int i = 0; i < length; i++) { // 循环次数 int key = a[i]; int position = i; for (int j = i + 1; j < length; j++) { // 选出最小值和位置 if (a[j] < key) { key = a[j]; position = j; } } a[position] = a[i]; // 交换位置 a[i] = key; } } /** * 4.堆排序 对简单选择排序的优化。 * 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定 * O(nlogn) O(nlogn) O(1) 不是 * 将序列构建成大顶堆。 * 将根节点与最后一个节点交换,然后断开最后一个节点。 * 重复第一、二步,直到所有节点断开。 */ public static void heapSort(int[] a) { System.out.println("开始排序"); int arrayLength = a.length; // 循环建堆 for (int i = 0; i < arrayLength - 1; i++) { // 建堆 buildMaxHeap(a, arrayLength - 1 - i); // 交换堆顶和最后一个元素 swap(a, 0, arrayLength - 1 - i); System.out.println(Arrays.toString(a)); } } private static void swap(int[] data, int i, int j) { int tmp = data[i]; data[i] = data[j]; data[j] = tmp; } // 对data数组从0到lastIndex建大顶堆 private static void buildMaxHeap(int[] data, int lastIndex) { // 从lastIndex处节点(最后一个节点)的父节点开始 for (int i = (lastIndex - 1) / 2; i >= 0; i--) { // k保存正在判断的节点 int k = i; // 如果当前k节点的子节点存在 while (k * 2 + 1 <= lastIndex) { // k节点的左子节点的索引 int biggerIndex = 2 * k + 1; // 如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在 if (biggerIndex < lastIndex) { // 若果右子节点的值较大 if (data[biggerIndex] < data[biggerIndex + 1]) { // biggerIndex总是记录较大子节点的索引 biggerIndex++; } } // 如果k节点的值小于其较大的子节点的值 if (data[k] < data[biggerIndex]) { // 交换他们 swap(data, k, biggerIndex); // 将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值 k = biggerIndex; } else { break; } } } } /** * 5.冒泡排序 一般不用。 * 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定 * O(n^2) O(n^2) O(1) 是 * 将序列中所有元素两两比较,将最大的放在最后面。 * 将剩余序列中所有元素两两比较,将最大的放在最后面。 * 重复第二步,直到只剩下一个数。 * ***如何写成代码: * 1.设置循环次数。 * 2.设置开始比较的位数,和结束的位数。 * 3.两两比较,将最小的放到前面去。 * 4.重复2、3步,直到循环次数完毕。 * */ public static void bubbleSort(int[] a) { int length = a.length; int temp; for (int i = 0; i < length; i++) { for (int j = 0; j < length - i - 1; j++) { if (a[j] > a[j + 1]) { temp = a[j]; a[j] = a[j + 1]; a[j + 1] = temp; } } } } /** * 6.快速排序 要求时间最快时。 * 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定 * O(nlogn) O(n^2) O(logn) 不是 * 选择第一个数为p,小于p的数放在左边,大于p的数放在右边。 * 递归的将p左边和右边的数都按照第一步进行,直到不能递归。 */ public static void quickSort(int[] numbers, int start, int end) { if (start < end) { int base = numbers[start];// 选定的基准值 int temp; int i = start, j = end; do { while ((numbers[i] < base) && (i < end)) i++; while ((numbers[j] > base) && (j > start)) j--; if (i <= j) { temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; i++; j--; } } while (i <= j); if (start < j) quickSort(numbers, start, j); if (end > i) quickSort(numbers, i, end); } } /** * 7.归并排序 速度仅次于快排,内存少的时候使用,可以进行并行计算的时候使用。 * 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定 * O(nlogn) O(nlogn) O(n) 是 * 选择相邻两个数组成一个有序序列。 * 选择相邻的两个有序序列组成一个有序序列。 * 重复第二步,直到全部组成一个有序序列。 */ public static void mergeSort(int[] arr) { int[] temp = new int[arr.length];// 在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间 mergeSort(arr, 0, arr.length - 1, temp); } private static void mergeSort(int[] arr, int left, int right, int[] temp) { if (left < right) { int mid = (left + right) / 2; mergeSort(arr, left, mid, temp);// 左边归并排序,使得左子序列有序 mergeSort(arr, mid + 1, right, temp);// 右边归并排序,使得右子序列有序 merge(arr, left, mid, right, temp);// 将两个有序子数组合并操作 } } private static void merge(int[] arr, int left, int mid, int right, int[] temp) { int i = left;// 左序列指针 int j = mid + 1;// 右序列指针 int t = 0;// 临时数组指针 while (i <= mid && j <= right) { if (arr[i] <= arr[j]) { temp[t++] = arr[i++]; } else { temp[t++] = arr[j++]; } } while (i <= mid) {// 将左边剩余元素填充进temp中 temp[t++] = arr[i++]; } while (j <= right) {// 将右序列剩余元素填充进temp中 temp[t++] = arr[j++]; } t = 0; // 将temp中的元素全部拷贝到原数组中 while (left <= right) { arr[left++] = temp[t++]; } } /** * 8.基数排序 用于大量数,很长的数进行排序时。 * 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定 * O(N∗M) O(N∗M) O(M) 是 * 将所有的数的个位数取出,按照个位数进行排序,构成一个序列。 * 将新构成的所有的数的十位数取出,按照十位数进行排序,构成一个序列。 */ public static void sort(int[] number, int d) { int k = 0; int n = 1; int m = 1; int[][] temp = new int[number.length][number.length]; int[] order = new int[number.length]; while (m <= d) { for (int i = 0; i < number.length; i++) { int lsd = ((number[i] / n) % 10); temp[lsd][order[lsd]] = number[i]; order[lsd]++; } for (int i = 0; i < d; i++) { if (order[i] != 0) for (int j = 0; j < order[i]; j++) { number[k] = temp[i][j]; k++; } order[i] = 0; } n *= 10; k = 0; m++; } } }]]></content>
<categories>
<category>java</category>
<category>Algorithm</category>
</categories>
<tags>
<tag>java</tag>
<tag>Algorithm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[java关键字volatile]]></title>
<url>%2F2018%2F09%2F05%2Fjava%E5%85%B3%E9%94%AE%E5%AD%97volatile%2F</url>
<content type="text"><![CDATA[Volatile中文译为【易变的、不稳定的】,它被用来修饰那些被不同的线程访问和修改的变量。 学习volatile我们首先要来看一下Java内存模型(即Java Memory Model,简称JMM)。 Java内存模型中规定了所有的变量都存储在主内存中,每条java线程各自拥有自己的工作内存,线程的工作内存中保存了该线程使用到的主内存中的共享变量的副本,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程、主内存和工作内存的交互关系如上图所示。 Volatile 修饰的变量即为主内存中的共享变量,当一个共享变量被volatile修饰后,那么当这个变量在工作内存发生了变化后,必须要马上写到主内存中,而线程读取到是volatile修饰的变量时,必须去主内存中去获取最新的值,而不是读工作内存中主内存的副本,这就有效的保证了线程之间变量的可见性。 这就是volatile的第一个特性,内存可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 我们来看一段代码: 123456789101112131415161718192021222324252627282930public class VolatileTest01 { // a是一个验证结果的变量 private static int a=0; private static int b=0; static boolean stop=false;public static void main(String[] args) throws InterruptedException { ThreadA threadA=new ThreadA(); ThreadB threadB=new ThreadB(); threadA.start(); threadB.start(); } //线程A static class ThreadA extends Thread{ public void run(){ while(!stop) { System.out.println(a); a++; } } } //线程B static class ThreadB extends Thread{ public void run(){ b=666; stop=true; b=666; } }} 我们会发现,当线程B执行语句stop=true;后,会中断线程A的执行。但有没有可能A出现死循环呢?答案是有的,即当线程B更改了stop变量之后还没来得及将stop变量写入主存当中,线程B就转去做其他事了,这时线程A由于不知道线程B对stop变量的更改,因此会一直循环下去,虽然这种情况发生即概论很低,但是一旦发生了将造成很严重的后果。 使用volatile关键字后我们便可以解决这个问题,但是这样就可以保证线程安全了吗?答案是不能,我们来看一下下面这段代码: 1234567891011121314151617181920212223242526public class VolatileTest05 { public volatile static int num=0; public static void main(String[] args) { for(int i=0;i<10;i++){ new Thread( new Runnable() { public void run() { try { Thread.sleep(1); } catch (Exception e) { e.printStackTrace(); } for(int j=0;j<100;j++){ num++; } } }).start(); } try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } System.out.println("num=:"+num); }} 通过上面的代码我们创建10个线程,每个线程中让num自增100次。执行后你会发现结果并不是我们想的1000,而是小于1000.这是为什么呢?因为volatile并不能让一个操作变成原子操作。Num++本身并不是原子操作,转换为字节码是这样的: getstatic //读取静态变量(num) iconst_1 //定义常量1 iadd //num增加1 putstatic //把num结果同步到主内存 虽然每次get后得到的都是最新变量值,但是进行idd的时候,由于非原子性操作,其他线程可能已经将num++执行了很多次,此时这个线程更新出的num便已经不是我们所期望得到的num值。 JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,为了在不改变程序执行结果的前提下,优化程序的运行效率会对现有的指令顺序进行重新排序。 Volatile第二个特性即是可以防止指令重排序。 我们来看下面这个例子: boolean contextReady = false; 在线程A中执行: context = loadContext();//初始化context contextReady = true; 在线程B中执行: while( ! contextReady ){ sleep(200); } doAfterContextReady (context); 但如果一旦线程A中的指令重排序,会变成这样的执行顺序: boolean contextReady = false; 线程A: contextReady = true; context = loadContext();//初始化context 线程B: while( ! contextReady ){ sleep(200); } doAfterContextReady (context); 我们发现context并没有进行初始化线程B跳出循环等待后便执行了doAfterContextReady(context)方法,程序报错。 此时我们可以给contextReady 增加volatile方法,以此解决关于contextReady的指令重排序的问题。]]></content>
<categories>
<category>java</category>
</categories>
<tags>
<tag>java</tag>
<tag>volatile</tag>
</tags>
</entry>
<entry>
<title><![CDATA[TCP三次握手四次挥手]]></title>
<url>%2F2018%2F09%2F04%2FTCP%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E5%92%8C%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B%2F</url>
<content type="text"><![CDATA[由于网上关于TCP三次握手四次挥手的内容已经很丰富,所以我们今天趣味讲解一下TCP三次握手和四次挥手。 三次握手: 首先先上图: ![TCP三次握手] 由于我给客户端和服务端都上了色,所以暂且就叫他们小蓝(Client)和小绿(Server)就好了。 由于一次意外,两人都双目失明,看不见了。他们只能通过声音辨认对方。 小蓝首先叫了一声小绿(syn) 小绿听见听见小蓝在叫自己,便答应了一声自己在(ack),但小绿怕小蓝叫的是其他的小绿,便问小蓝是在叫自己吗(syn),小蓝听到后知道了是小绿,便进入(established)状态。 最后小蓝也答应了一声是自己(ack),小绿听到后进入(established)状态。 这其中还有两个中间态:syn_sent和syn_rcvd,这两个状态叫【半打开】状态,也就是两人呼唤对方。Syn_sent是客户端的半打开状态,sun_rcvd是服务端的半打开状态。 Syn_sent : syn package has been sent Syn_rcvd : syn package has been received 官方语言: TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态; TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号seq=x ,此时,TCP客户端进程进入了SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。 TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。 TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。 当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。 四次挥手: ![TCP四次挥手] 两人离别的时候, 小蓝说“我要走了”(FIN) 小绿回应“那好吧”(ACK) 此时小绿说了一些话,但发现小蓝真的不说话了,就说“我也走了”(FIN) 小蓝回应了一句“好的”(ACK) 官方语言: 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。 状态time_wait是一个非常特殊的状态,它是主动关闭的一方在回复完对方后进入的一个长期状态,这个状态标准的持续时间是4分钟,4分钟后才会进入到closed状态,释放套接字资源。不过在具体实现上这个时间是可以调整的。 这个后果就是持续4分钟的time_wait状态,不能释放套接字资源(端口),就好比守寡期,这段时间内套接字资源(端口)不得回收利用。 它的作用是重传最后一个ack报文,确保对方可以收到。因为如果对方没有收到ack的话,会重传fin报文,处于time_wait状态的套接字会立即向对方重发ack报文。 同时在这段时间内,该链接在对话期间于网际路由上产生的残留报文(因为路径过于崎岖,数据报文走的时间太长,重传的报文都收到了,原始报文还在路上)传过来时,都会被立即丢弃掉。4分钟的时间足以使得这些残留报文彻底消逝。不然当新的端口被重复利用时,这些残留报文可能会干扰新的链接。 4分钟就是2个MSL,每个MSL是2分钟。MSL就是maximium segmentlifetime——最长报文寿命。这个时间是由官方RFC协议规定的。至于为什么是2个MSL而不是1个MSL呢? 第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。 第二,防止“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。 最后一个问题,为什么建立连接是三次握手,关闭连接确是四次挥手呢? 建立连接的时候,服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。 而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。 四次挥手也并不总是四次挥手,中间的两个动作有时候是可以合并一起进行的,这个时候就成了三次挥手,主动关闭方就会从fin_wait_1状态直接进入到time_wait状态,跳过了fin_wait_2状态。 扩展:TCP数据传输 数据传输过程即是小蓝和小绿对话的过程。 只不过两人隔着河,且都看不见,所以当一方说话后(data)需要对方听到了回应一声(ACK)。 如果小蓝喊了一句,半天没听到小绿回复,小蓝就认为自己的话被大风吹走了,小绿没听见,所以需要重新喊话,这就是【tcp重传】。 也有可能是小绿听到了小蓝的话,但是小绿向小蓝的回复被大风吹走了,以至于小蓝没听见小绿的回复。小蓝并不能判断究竟是自己的话被大风吹走了还是小绿的回复被大风吹走了,小蓝也不用管,重传一下就是。 既然会重传,小绿就有可能同一句话听见了两次,这就是【去重】。【重传】和【去重】工作操作系统的网络内核模块都已经帮我们处理好了。 小蓝可以向小绿喊话,同样小绿也可以向小蓝喊话,因为tcp链接是「双工的」,双方都可以主动发起数据传输。不过无论是哪方喊话,都需要收到对方的确认才能认为对方收到了自己的喊话。 小蓝可能是个八卦,一说连说了八句话,这时候小绿可以不用一句一句回复,而是连续听了这八句话之后,一起向对方回复说前面你说的八句话我都听见了,这就是批量ack。但是小蓝也不能一次性说了太多话,小绿的脑子短时间可能无法消化太多,两人之间需要有协商好的合适的发送和接受速率,这个就是【TCP窗口大小】。 网络环境的数据交互同人类之间的对话还要复杂一些,它存在数据包乱序的现象。同一个来源发出来的不同数据包在「网际路由」上可能会走过不同的路径,最终达到同一个地方时,顺序就不一样了。操作系统的网络内核模块会负责对数据包进行排序,到用户层时顺序就已经完全一致了。 好了,今天就介绍到这里了,说的比较简单,深的等以后写,每天进步一点点,希望世界充满正能量!]]></content>
<categories>
<category>TCP</category>
</categories>
<tags>
<tag>TCP</tag>
<tag>网络</tag>
</tags>
</entry>
<entry>
<title><![CDATA[hexo+github搭建个人博客]]></title>
<url>%2F2018%2F09%2F03%2Fhexo%2Bgithub%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%2F</url>
<content type="text"><![CDATA[博主我用过很多云服务器、云主机搭建个人博客网站,但没用过github的page页,想尝试下,搞了一会就搭建完成了,总的来说很顺利,在这里分享给大家。 安装Hexo安装node.js node.js官方下载地址 从上面的链接下载node.js,并安装。 下载LTS那个即可 申请Github账号 Github注册页面 输入用户名,Email,密码,注册账号。比如我的用户名是:JasonZhangCauc 创建博客仓库 注意,仓库名应该为:用户名.github.io。比如,我的仓库名是:JasonZhangCauc.github.io。 安装git git下载地址 下载git安装文件,双击执行安装。 配置ssh 打开git bash终端。 设置user.name和user.email。 git config –global user.name “你的GitHub用户名” git config –global user.email “你的GitHub注册邮箱” 生成ssh密匙 ssh-keygen -t rsa -C “你的GitHub注册邮箱” 此时,在用户文件夹下就会有一个新的文件夹.ssh,里面有刚刚创建的ssh密钥文件id_rsa和id_rsa.pub。 将公匙添加到github上 详细教程自行baidu。 用户头像→Settings→SSH and GPG keys→New SSHkey→将id_rsa.pub中的内容复制到Key文本框中,然后点击Add SSHkey(添加SSH)按钮。 安装hexo 执行以下命令安装hexo。 # 安装hexo npm install hexo-cli g# 初始化博客文件夹 hexo init blog# 切换到该路径cd blog# 安装hexo的扩展插件 npm install# 安装其它插件 npm install hexo-server –save npm install hexo-admin –save npm install hexo-generator-archive –save npm install hexo-generator-feed –save npm install hexo-generator-search –save npm install hexo-generator-tag –save npm install hexo-deployer-git –save npm install hexo-generator-sitemap –save 初探hexo 第一次使用hexo,在本地创建服务器使用。 # 生成静态页面 hexo generate# 开启本地服务器 hexo s 打开浏览器,地址栏中输入:http://localhost:4000/,应该可以看见刚刚创建的博客了。 问题:为什么访问http://localhost:4000/,无反应? 解决方法:可能是由于端口问题引起的。使用Ctrl+C中断本地服务,使用命令hexos -p5000重新开启本地服务,访问http://localhost:5000/可以看到博客页面了。 将hexo博客部署到github上 修改配置文件blog/_config.yml,修改deploy项的内容,如下所示: # Deployment 注释## Docs: https://hexo.io/docs/deployment.htmldeploy: # 类型 type: git # 仓库 repo: git\@github.com:JasonZhangCauc/JasonZhangCauc.github.io.git # 分支 branch: master 注意:type: git中的冒号后面由空格。 注意:将git\@github.com:JasonZhangCauc/JasonZhangCauc.github.io.git中的用户名换成自己的用户名git\@github.com:github_username/github_username.github.io.git。 部署hexo 输入下面的命令将hexo博客部署到github中: # 清空静态页面 hexo clean# 生成静态页面 hexo generate# 部署 hexo deploy 打开网页,输入http://github_username.github.io,打开github上托管的博客。如我的博客地址是:http://JasonZhangCauc.github.io。 hexo命令缩写 hexo支持命令缩写,如下所示。hexo g等价于hexo generate。 hexo g:hexo generate hexo c:hexo clean hexo s:hexo server hexo d:hexo deploy hexo组合命令# 清除、生成、启动 hexo clean && hexo g -s# 清除、生成、部署 hexo clean && hexo g -d 附博客维护方法:http://theme-next.iissnan.com/getting-started.html]]></content>
<categories>
<category>hexo</category>
</categories>
<tags>
<tag>hexo</tag>
<tag>github</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hello World]]></title>
<url>%2F2018%2F09%2F02%2Fhello-world%2F</url>
<content type="text"><![CDATA[Hello! Friends! 铅笔 上传 下载 下载两倍大 java源站12345678910111213141516171819202122232425262728<head> <link rel="stylesheet" type="text/css" href="http://github.jmzhang.top/css/EnlighterJS.min.css" /> <script type="text/javascript" src="http://github.jmzhang.top/js/MooTools.min.js"></script> <script type="text/javascript" src="http://github.jmzhang.top/js/EnlighterJS.min.js" ></script> <meta name="EnlighterJS" content="Advanced javascript based syntax highlighting" data-indent="4" data-selector-block="pre" data-selector-inline="code.special" /></head><body> public class duiliecz { // 队列操作类 /** * @param args */ private int i = 0; // 队列长 private duilie top = new duilie(""); // 队列头 private duilie end = new duilie(""); // 队列尾 public void add(String s) { // 添加队列 duilie m = new duilie(s); if (i != 0) { m.setS(top.getS()); top.setS(m); } else { top.setS(m); end.setS(m); } i++; }</body> 居中人生乃是一面镜子,从镜子里认识自己,我要称之为头等大事,也只是我们追求的目的! default primary success info warning danger danger no-icon default primary success info warning danger 选项卡 1选项卡 2选项卡 3这是选项卡 1 呵呵哈哈哈哈哈哈哈哈…… 这是选项卡 2 ei 这是选项卡 3 哇,你找到我了!φ(≧ω≦*)♪~ This is Tab 1. This is Tab 2. This is Tab 3. Solution 2]]></content>
</entry>
</search>