From 79d7c747b0a326f315c45ffce2b2dbbe34309036 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Mon, 10 Sep 2018 08:25:58 +0800 Subject: [PATCH 001/122] =?UTF-8?q?[=E7=AE=97=E6=B3=95=E6=80=BB=E7=BB=93]?= =?UTF-8?q?=20=E6=90=9E=E5=AE=9A=20BAT=20=E9=9D=A2=E8=AF=95=E2=80=94?= =?UTF-8?q?=E2=80=94=E5=87=A0=E9=81=93=E5=B8=B8=E8=A7=81=E7=9A=84=E5=AD=90?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E7=AE=97=E6=B3=95=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...62\347\256\227\346\263\225\351\242\230.md" | 445 ++++++++++++++++++ 1 file changed, 445 insertions(+) create mode 100644 "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/[\347\256\227\346\263\225\346\200\273\347\273\223] \346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/[\347\256\227\346\263\225\346\200\273\347\273\223] \346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/[\347\256\227\346\263\225\346\200\273\347\273\223] \346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" new file mode 100644 index 00000000000..a034e88e95e --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/[\347\256\227\346\263\225\346\200\273\347\273\223] \346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" @@ -0,0 +1,445 @@ +## 说明 + +- 本文作者:wwwxmu +- 原文地址:https://www.weiweiblog.cn/13string/ +- 作者的博客站点:https://www.weiweiblog.cn/ (推荐哦!) + +考虑到篇幅问题,我会分两次更新这个内容。本篇文章只是原文的一部分,我在原文的基础上增加了部分内容以及修改了部分代码和注释。另外,我增加了爱奇艺 2018 秋招 Java:`求给定合法括号序列的深度` 这道题。所有代码均编译成功,并带有注释,欢迎各位享用! + +## 1. KMP 算法 + +谈到字符串问题,不得不提的就是 KMP 算法,它是用来解决字符串查找的问题,可以在一个字符串(S)中查找一个子串(W)出现的位置。KMP 算法把字符匹配的时间复杂度缩小到 O(m+n) ,而空间复杂度也只有O(m)。因为“暴力搜索”的方法会反复回溯主串,导致效率低下,而KMP算法可以利用已经部分匹配这个有效信息,保持主串上的指针不回溯,通过修改子串的指针,让模式串尽量地移动到有效的位置。 + +具体算法细节请参考: + +- **字符串匹配的KMP算法:** http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html +- **从头到尾彻底理解KMP:** https://blog.csdn.net/v_july_v/article/details/7041827 +- **如何更好的理解和掌握 KMP 算法?:** https://www.zhihu.com/question/21923021 +- **KMP 算法详细解析:** https://blog.sengxian.com/algorithms/kmp +- **图解 KMP 算法:** http://blog.jobbole.com/76611/ +- **汪都能听懂的KMP字符串匹配算法【双语字幕】:** https://www.bilibili.com/video/av3246487/?from=search&seid=17173603269940723925 +- **KMP字符串匹配算法1:** https://www.bilibili.com/video/av11866460?from=search&seid=12730654434238709250 + +**除此之外,再来了解一下BM算法!** + +> BM算法也是一种精确字符串匹配算法,它采用从右向左比较的方法,同时应用到了两种启发式规则,即坏字符规则 和好后缀规则 ,来决定向右跳跃的距离。基本思路就是从右往左进行字符匹配,遇到不匹配的字符后从坏字符表和好后缀表找一个最大的右移值,将模式串右移继续匹配。 +《字符串匹配的KMP算法》:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html + + +## 2. 替换空格 + +> 剑指offer:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 + +这里我提供了两种方法:①常规方法;②利用 API 解决。 + +```java +//https://www.weiweiblog.cn/replacespace/ +public class Solution { + + /** + * 第一种方法:常规方法。利用String.charAt(i)以及String.valueOf(char).equals(" " + * )遍历字符串并判断元素是否为空格。是则替换为"%20",否则不替换 + */ + public static String replaceSpace(StringBuffer str) { + + int length = str.length(); + // System.out.println("length=" + length); + StringBuffer result = new StringBuffer(); + for (int i = 0; i < length; i++) { + char b = str.charAt(i); + if (String.valueOf(b).equals(" ")) { + result.append("%20"); + } else { + result.append(b); + } + } + return result.toString(); + + } + + /** + * 第二种方法:利用API替换掉所用空格,一行代码解决问题 + */ + public static String replaceSpace2(StringBuffer str) { + + return str.toString().replaceAll("\\s", "%20"); + } +} + +``` + +## 3. 最长公共前缀 + +> Leetcode: 编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。 + +示例 1: + +``` +输入: ["flower","flow","flight"] +输出: "fl" +``` + +示例 2: + +``` +输入: ["dog","racecar","car"] +输出: "" +解释: 输入不存在公共前缀。 +``` + + +思路很简单!先利用Arrays.sort(strs)为数组排序,再将数组第一个元素和最后一个元素的字符从前往后对比即可! + +```java +//https://leetcode-cn.com/problems/longest-common-prefix/description/ +public class Main { + public static String replaceSpace(String[] strs) { + + // 数组长度 + int len = strs.length; + // 用于保存结果 + StringBuffer res = new StringBuffer(); + // 注意:=是赋值,==是判断 + if (strs == null || strs.length == 0) { + return ""; + } + // 给字符串数组的元素按照升序排序(包含数字的话,数字会排在前面) + Arrays.sort(strs); + int m = strs[0].length(); + int n = strs[len - 1].length(); + int num = Math.min(m, n); + for (int i = 0; i < num; i++) { + if (strs[0].charAt(i) == strs[len - 1].charAt(i)) { + res.append(strs[0].charAt(i)); + } else + break; + + } + return res.toString(); + + } + //测试 + public static void main(String[] args) { + String[] strs = { "customer", "car", "cat" }; + System.out.println(Main.replaceSpace(strs));//c + } +} + +``` + +## 4. 回文串 + +### 4.1. 最长回文串 + +> LeetCode: 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。在构造过程中,请注意区分大小写。比如`"Aa"`不能当做一个回文字符串。注 +意:假设字符串的长度不会超过 1010。 + + + +> 回文串:“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。——百度百科 地址:https://baike.baidu.com/item/%E5%9B%9E%E6%96%87%E4%B8%B2/1274921?fr=aladdin + +示例 1: + +``` +输入: +"abccccdd" + +输出: +7 + +解释: +我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。 +``` + +我们上面已经知道了什么是回文串?现在我们考虑一下可以构成回文串的两种情况: + +- 字符出现次数为双数的组合 +- 字符出现次数为双数的组合+一个只出现一次的字符 + +统计字符出现的次数即可,双数才能构成回文。因为允许中间一个数单独出现,比如“abcba”,所以如果最后有字母落单,总长度可以加 1。首先将字符串转变为字符数组。然后遍历该数组,判断对应字符是否在hashset中,如果不在就加进去,如果在就让count++,然后移除该字符!这样就能找到出现次数为双数的字符个数。 + +```java +//https://leetcode-cn.com/problems/longest-palindrome/description/ +class Solution { + public int longestPalindrome(String s) { + if (s.length() == 0) + return 0; + // 用于存放字符 + HashSet hashset = new HashSet(); + char[] chars = s.toCharArray(); + int count = 0; + for (int i = 0; i < chars.length; i++) { + if (!hashset.contains(chars[i])) {// 如果hashset没有该字符就保存进去 + hashset.add(chars[i]); + } else {// 如果有,就让count++(说明找到了一个成对的字符),然后把该字符移除 + hashset.remove(chars[i]); + count++; + } + } + return hashset.isEmpty() ? count * 2 : count * 2 + 1; + } +} +``` + + +### 4.2. 验证回文串 + +> LeetCode: 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 说明:本题中,我们将空字符串定义为有效的回文串。 + +示例 1: + +``` +输入: "A man, a plan, a canal: Panama" +输出: true +``` + +示例 2: + +``` +输入: "race a car" +输出: false +``` + +```java +//https://leetcode-cn.com/problems/valid-palindrome/description/ +class Solution { + public boolean isPalindrome(String s) { + if (s.length() == 0) + return true; + int l = 0, r = s.length() - 1; + while (l < r) { + // 从头和尾开始向中间遍历 + if (!Character.isLetterOrDigit(s.charAt(l))) {// 字符不是字母和数字的情况 + l++; + } else if (!Character.isLetterOrDigit(s.charAt(r))) {// 字符不是字母和数字的情况 + r--; + } else { + // 判断二者是否相等 + if (Character.toLowerCase(s.charAt(l)) != Character.toLowerCase(s.charAt(r))) + return false; + l++; + r--; + } + } + return true; + } +} +``` + + +### 4.3. 最长回文子串 + +> Leetcode: LeetCode: 最长回文子串 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。 + +示例 1: + +``` +输入: "babad" +输出: "bab" +注意: "aba"也是一个有效答案。 +``` + +示例 2: + +``` +输入: "cbbd" +输出: "bb" +``` + +以某个元素为中心,分别计算偶数长度的回文最大长度和奇数长度的回文最大长度。给大家大致花了个草图,不要嫌弃! + + +![](https://user-gold-cdn.xitu.io/2018/9/9/165bc32f6f1833ff?w=723&h=371&f=png&s=9305) + +```java +//https://leetcode-cn.com/problems/longest-palindromic-substring/description/ +class Solution { + private int index, len; + + public String longestPalindrome(String s) { + if (s.length() < 2) + return s; + for (int i = 0; i < s.length() - 1; i++) { + PalindromeHelper(s, i, i); + PalindromeHelper(s, i, i + 1); + } + return s.substring(index, index + len); + } + + public void PalindromeHelper(String s, int l, int r) { + while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { + l--; + r++; + } + if (len < r - l - 1) { + index = l + 1; + len = r - l - 1; + } + } +} +``` + +### 4.4. 最长回文子序列 + +> LeetCode: 最长回文子序列 +给定一个字符串s,找到其中最长的回文子序列。可以假设s的最大长度为1000。 +**最长回文子序列和上一题最长回文子串的区别是,子串是字符串中连续的一个序列,而子序列是字符串中保持相对位置的字符序列,例如,"bbbb"可以是字符串"bbbab"的子序列但不是子串。** + +给定一个字符串s,找到其中最长的回文子序列。可以假设s的最大长度为1000。 + +示例 1: + +``` +输入: +"bbbab" +输出: +4 +``` +一个可能的最长回文子序列为 "bbbb"。 + +示例 2: + +``` +输入: +"cbbd" +输出: +2 +``` + +一个可能的最长回文子序列为 "bb"。 + +**动态规划:** dp[i][j] = dp[i+1][j-1] + 2 if s.charAt(i) == s.charAt(j) otherwise, dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]) + +```java +class Solution { + public int longestPalindromeSubseq(String s) { + int len = s.length(); + int [][] dp = new int[len][len]; + for(int i = len - 1; i>=0; i--){ + dp[i][i] = 1; + for(int j = i+1; j < len; j++){ + if(s.charAt(i) == s.charAt(j)) + dp[i][j] = dp[i+1][j-1] + 2; + else + dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]); + } + } + return dp[0][len-1]; + } +} +``` + +### 5. 括号匹配深度 + +> 爱奇艺 2018 秋招 Java: +>一个合法的括号匹配序列有以下定义: +>1. 空串""是一个合法的括号匹配序列 +>2. 如果"X"和"Y"都是合法的括号匹配序列,"XY"也是一个合法的括号匹配序列 +>3. 如果"X"是一个合法的括号匹配序列,那么"(X)"也是一个合法的括号匹配序列 +>4. 每个合法的括号序列都可以由以上规则生成。 + +> 例如: "","()","()()","((()))"都是合法的括号序列 +>对于一个合法的括号序列我们又有以下定义它的深度: +>1. 空串""的深度是0 +>2. 如果字符串"X"的深度是x,字符串"Y"的深度是y,那么字符串"XY"的深度为max(x,y) +>3. 如果"X"的深度是x,那么字符串"(X)"的深度是x+1 + +> 例如: "()()()"的深度是1,"((()))"的深度是3。牛牛现在给你一个合法的括号序列,需要你计算出其深度。 + +``` +输入描述: +输入包括一个合法的括号序列s,s长度length(2 ≤ length ≤ 50),序列中只包含'('和')'。 + +输出描述: +输出一个正整数,即这个序列的深度。 +``` + +示例: + +``` +输入: +(()) +输出: +2 +``` + +思路草图: + + +![](https://user-gold-cdn.xitu.io/2018/9/9/165bc6fca94ef278?w=792&h=324&f=png&s=15868) + +代码如下: + +```java +import java.util.Scanner; + +/** + * https://www.nowcoder.com/test/8246651/summary + * + * @author Snailclimb + * @date 2018年9月6日 + * @Description: TODO 求给定合法括号序列的深度 + */ +public class Main { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + String s = sc.nextLine(); + int cnt = 0, max = 0, i; + for (i = 0; i < s.length(); ++i) { + if (s.charAt(i) == '(') + cnt++; + else + cnt--; + max = Math.max(max, cnt); + } + sc.close(); + System.out.println(max); + } +} + +``` + +## 6. 把字符串转换成整数 + +> 剑指offer: 将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。 + +```java +//https://www.weiweiblog.cn/strtoint/ +public class Main { + + public static int StrToInt(String str) { + if (str.length() == 0) + return 0; + char[] chars = str.toCharArray(); + // 判断是否存在符号位 + int flag = 0; + if (chars[0] == '+') + flag = 1; + else if (chars[0] == '-') + flag = 2; + int start = flag > 0 ? 1 : 0; + int res = 0;// 保存结果 + for (int i = start; i < chars.length; i++) { + if (Character.isDigit(chars[i])) {// 调用Character.isDigit(char)方法判断是否是数字,是返回True,否则False + int temp = chars[i] - '0'; + res = res * 10 + temp; + } else { + return 0; + } + } + return flag == 1 ? res : -res; + + } + + public static void main(String[] args) { + // TODO Auto-generated method stub + String s = "-12312312"; + System.out.println("使用库函数转换:" + Integer.valueOf(s)); + int res = Main.StrToInt(s); + System.out.println("使用自己写的方法转换:" + res); + + } + +} + +``` \ No newline at end of file From 1f1f3ad254da3c021aec136cc8b2b4d1f574e58d Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Mon, 10 Sep 2018 08:30:31 +0800 Subject: [PATCH 002/122] =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=AE=97?= =?UTF-8?q?=E6=B3=95=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...47\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/[\347\256\227\346\263\225\346\200\273\347\273\223] \346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" => "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" (100%) diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/[\347\256\227\346\263\225\346\200\273\347\273\223] \346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/[\347\256\227\346\263\225\346\200\273\347\273\223] \346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" rename to "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" From 7b512e8e23548f0c0c5ca80291abfca50d3d6879 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Mon, 10 Sep 2018 08:32:54 +0800 Subject: [PATCH 003/122] =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=AE=97?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...47\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" => "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" (100%) diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232 BAT \351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" rename to "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" From e1cb7a8b49f5c088e6b84aac228285a3ac7dac6f Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Mon, 10 Sep 2018 08:33:30 +0800 Subject: [PATCH 004/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E2=80=9C=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E5=B8=B8=E8=A7=81=E7=AE=97=E6=B3=95=E9=A2=98?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30fa7b4d796..9435ae1fc25 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ - [算法学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/算法.md) - [常见安全算法(MD5、SHA1、Base64等等)总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/常见安全算法(MD5、SHA1、Base64等等)总结.md) - + - [搞定 BAT 面试——几道常见的子符串算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/搞定BAT面试——几道常见的子符串算法题.md) ## :computer: 计算机网络与数据通信 - ### 网络相关 From fd5c9758b35ea1307df5f2f679c9987611d8054d Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Tue, 11 Sep 2018 09:10:28 +0800 Subject: [PATCH 005/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20ZooKeeper=20?= =?UTF-8?q?=E7=9A=84=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章 --- .../ZooKeeper.md" | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 "\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" new file mode 100644 index 00000000000..c23244c37d4 --- /dev/null +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" @@ -0,0 +1,180 @@ +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/56385654.jpg) +## 前言 + +相信大家对 ZooKeeper 应该不算陌生。但是你真的了解 ZooKeeper 是个什么东西吗?如果别人/面试官让你给他讲讲 ZooKeeper 是个什么东西,你能回答到什么地步呢? + +我本人曾经使用过 ZooKeeper 作为 Dubbo 的注册中心,另外在搭建 solr 集群的时候,我使用到了 ZooKeeper 作为 solr 集群的管理工具。前几天,总结项目经验的时候,我突然问自己 ZooKeeper 到底是个什么东西?想了半天,脑海中只是简单的能浮现出几句话:“①Zookeeper 可以被用作注册中心。 ②Zookeeper 是 Hadoop 生态系统的一员;③构建 Zookeeper 集群的时候,使用的服务器最好是奇数台。” 可见,我对于 Zookeeper 的理解仅仅是停留在了表面。 + +所以,**通过本文,希望带大家稍微详细的了解一下 ZooKeeper 。如果没有学过 ZooKeeper ,那么本文将会是你进入 ZooKeeper 大门的垫脚砖。如果你已经接触过 ZooKeeper ,那么本文将带你回顾一下 ZooKeeper 的一些基础概念。** + +最后,**本文只涉及 ZooKeeper 的一些概念,并不涉及 ZooKeeper 的使用以及 ZooKeeper 集群的搭建。** 网上有介绍 ZooKeeper 的使用以及搭建 ZooKeeper 集群的文章,大家有需要可以自行查阅。 + +## 一 什么是 ZooKeeper + +### ZooKeeper 的由来 + +**下面这段内容摘自《从Paxos到Zookeeper 》第四章第一节的某段内容,推荐大家阅读以下:** + +> Zookeeper最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,**雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。** +> +>关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的Pig项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家RaghuRamakrishnan开玩笑地说:“在这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,**雅虎的整个分布式系统看上去就像一个大型的动物园了,而Zookeeper正好要用来进行分布式环境的协调一一于是,Zookeeper的名字也就由此诞生了。** + + +### 1.1 ZooKeeper 概览 + +ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在“Yahoo!"上构建的,用于以简单而稳健的方式访问他们的应用程序。 后来,Apache ZooKeeper成为Hadoop,HBase和其他分布式框架使用的有组织服务的标准。 例如,Apache HBase使用ZooKeeper跟踪分布式数据的状态。**ZooKeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。** + +> **原语:** 操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。 + +**ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。** + +**Zookeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心。** 服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。如下图所示,在 Dubbo架构中 Zookeeper 就担任了注册中心这一角色。 + +![Dubbo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/35571782.jpg) + +### 1.2 结合个人使用情况的讲一下 ZooKeeper + +在我自己做过的项目中,主要使用到了 ZooKeeper 作为 Dubbo 的注册中心(Dubbo 官方推荐使用 ZooKeeper注册中心)。另外在搭建 solr 集群的时候,我使用 ZooKeeper 作为 solr 集群的管理工具。这时,ZooKeeper 主要提供下面几个功能:1、集群管理:容错、负载均衡。2、配置文件的集中管理3、集群的入口。 + + +我个人觉得在使用 ZooKeeper 的时候,最好是使用 集群版的 ZooKeeper 而不是单机版的。官网给出的架构图就描述的是一个集群版的 ZooKeeper 。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。 + +**为什么最好使用奇数台服务器构成 ZooKeeper 集群?** + +我们知道在Zookeeper中 Leader 选举算法采用了Zab协议。Zab核心思想是当多数 Server 写成功,则任务数据写成功。 + +①如果有3个Server,则最多允许1个Server 挂掉。 + +②如果有4个Server,则同样最多允许1个Server挂掉。 + +既然3个或者4个Server,同样最多允许1个Server挂掉,那么它们的可靠性是一样的,所以选择奇数个ZooKeeper Server即可,这里选择3个Server。12341234 + + + +## 二 关于 ZooKeeper 的一些重要概念 + +### 2.1 重要概念总结 + +- **ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。** +- **为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。** +- **ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟**(但是内存限制了能够存储的容量不太大,此限制也是保持znode中存储的数据量较小的进一步原因)。 +- **ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。**(“读”多于“写”是协调服务的典型场景。) +- **ZooKeeper有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。** +- ZooKeeper 底层其实只提供了两个功能:①管理(存储、读取)用户程序提交的数据;②为用户程序提交数据节点监听服务。 + +**下面关于会话(Session)、 Znode、版本、Watcher、ACL概念的总结都在《从Paxos到Zookeeper 》第四章第一节以及第七章第八节有提到,感兴趣的可以看看!** + +### 2.2 会话(Session) + +Session 指的是 ZooKeeper 服务器与客户端会话。**在 ZooKeeper 中,一个客户端连接是指客户端和服务器之间的一个 TCP 长连接**。客户端启动的时候,首先会与服务器建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期也开始了。**通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向Zookeeper服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的Watch事件通知。** Session的`sessionTimeout`值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,**只要在`sessionTimeout`规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。** + +**在为客户端创建会话之前,服务端首先会为每个客户端都分配一个sessionID。由于 sessionID 是 Zookeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的,因此,无论是哪台服务器为客户端分配的 sessionID,都务必保证全局唯一。** + +### 2.3 Znode + +**在谈到分布式的时候,我们通常说的“节点"是指组成集群的每一台机器。然而,在Zookeeper中,“节点"分为两类,第一类同样是指构成集群的机器,我们称之为机器节点;第二类则是指数据模型中的数据单元,我们称之为数据节点一一ZNode。** + +Zookeeper将所有数据存储在内存中,数据模型是一棵树(Znode Tree),由斜杠(/)的进行分割的路径,就是一个Znode,例如/foo/path1。每个上都会保存自己的数据内容,同时还会保存一系列属性信息。 + +**在Zookeeper中,node可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。而临时节点就不一样了,它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。**另外,ZooKeeper还允许用户为每个节点添加一个特殊的属性:**SEQUENTIAL**.一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。 + +### 2.4 版本 + +在前面我们已经提到,Zookeeper 的每个 ZNode 上都会存储数据,对应于每个ZNode,Zookeeper 都会为其维护一个叫作 **Stat** 的数据结构,Stat中记录了这个 ZNode 的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和 cversion(当前ZNode的ACL版本)。 + + +### 2.5 Watcher + +**Watcher(事件监听器),是Zookeeper中的一个很重要的特性。Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是Zookeeper实现分布式协调服务的重要特性。** + +### 2.6 ACL + +Zookeeper采用ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。Zookeeper 定义了如下5种权限。 + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/27473480.jpg) + +其中尤其需要注意的是,CREATE和DELETE这两种权限都是针对子节点的权限控制。 + +## 三 ZooKeeper 特点 + +- **顺序一致性:** 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。 +- **原子性:** 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。 +- **单一系统映像 :** 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。 +- **可靠性:** 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。 + +## 四 ZooKeeper 设计目标 + +### 4.1 简单的数据模型 + +ZooKeeper 允许分布式进程通过共享的层次结构命名空间进行相互协调,这与标准文件系统类似。 名称空间由 ZooKeeper 中的数据寄存器组成 - 称为znode,这些类似于文件和目录。 与为存储设计的典型文件系统不同,ZooKeeper数据保存在内存中,这意味着ZooKeeper可以实现高吞吐量和低延迟。 + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/94251757.jpg) + +### 4.2 可构建集群 + +**为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么zookeeper本身仍然是可用的。** 客户端在使用 ZooKeeper 时,需要知道集群机器列表,通过与集群中的某一台机器建立 TCP 连接来使用服务,客户端使用这个TCP链接来发送请求、获取结果、获取监听事件以及发送心跳包。如果这个连接异常断开了,客户端可以连接到另外的机器上。 + +**ZooKeeper 官方提供的架构图:** + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/68900686.jpg) + +上图中每一个Server代表一个安装Zookeeper服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 Zab 协议(Zookeeper Atomic Broadcast)来保持数据的一致性。 + +###4.3 顺序访问 + +**对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号,这个编号反应了所有事务操作的先后顺序,应用程序可以使用 ZooKeeper 这个特性来实现更高层次的同步原语。** **这个编号也叫做时间戳——zxid(Zookeeper Transaction Id)** + +### 4.4 高性能 + +**ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)** + +## 五 ZooKeeper 集群角色介绍 + +**最典型集群模式: Master/Slave 模式(主备模式)**。在这种模式中,通常 Master服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。 + +但是,**在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了Leader、Follower 和 Observer 三种角色**。如下图所示 + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/89602762.jpg) + + **ZooKeeper 集群中的所有机器通过一个 Leader 选举过程来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。** + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/77341396.jpg) + +## 六 ZooKeeper &ZAB 协议&Paxos算法 + +### 6.1 ZAB 协议&Paxos算法 + +Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外,在ZooKeeper的官方文档中也指出,ZAB协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为Zookeeper设计的崩溃可恢复的原子消息广播算法。 + +### 6.2 ZAB 协议介绍 + +**ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。** + +### 6.3 ZAB 协议两种基本的模式:崩溃恢复和消息广播 + +ZAB协议包括两种基本的模式,分别是 **崩溃恢复和消息广播**。当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进人恢复模式并选举产生新的Leader服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,**所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致**。 + +**当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个服务框架就可以进人消息广播模式了。** 当一台同样遵守ZAB协议的服务器启动后加人到集群中时,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加人的服务器就会自觉地进人数据恢复模式:找到Leader所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。正如上文介绍中所说的,ZooKeeper设计成只允许唯一的一个Leader服务器来进行事务请求的处理。Leader服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议;而如果集群中的其他机器接收到客户端的事务请求,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。 + +关于 **ZAB 协议&Paxos算法** 需要讲和理解的东西太多了,说实话,笔主到现在不太清楚这俩兄弟的具体原理和实现过程。推荐阅读下面两篇文章: + +- [图解 Paxos 一致性协议](http://blog.xiaohansong.com/2016/09/30/Paxos/) +- [Zookeeper ZAB 协议分析](http://blog.xiaohansong.com/2016/08/25/zab/) + +关于如何使用 zookeeper 实现分布式锁,可以查看下面这篇文章: + +- +[10分钟看懂!基于Zookeeper的分布式锁](https://blog.csdn.net/qiangcuo6087/article/details/79067136) + +## 六 总结 + +通过阅读本文,想必大家已从 **①ZooKeeper的由来。** -> **②ZooKeeper 到底是什么 。**-> **③ ZooKeeper 的一些重要概念**(会话(Session)、 Znode、版本、Watcher、ACL)-> **④ZooKeeper 的特点。** -> **⑤ZooKeeper 的设计目标。**-> **⑥ ZooKeeper 集群角色介绍** (Leader、Follower 和 Observer 三种角色)-> **⑦ZooKeeper &ZAB 协议&Paxos算法。** 这七点了解了 ZooKeeper 。 + +## 参考 + +- 《从Paxos到Zookeeper 》 +- https://cwiki.apache.org/confluence/display/ZOOKEEPER/ProjectDescription +- https://cwiki.apache.org/confluence/display/ZOOKEEPER/Index +- https://www.cnblogs.com/raphael5200/p/5285583.html +- https://zhuanlan.zhihu.com/p/30024403 + From f3a638267b69c8010493b72ab72815e9aa06d9a5 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Tue, 11 Sep 2018 09:14:03 +0800 Subject: [PATCH 006/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20=E2=80=9CZooKeeper?= =?UTF-8?q?=E2=80=9D=20=E7=9A=84=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ZooKeeper --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9435ae1fc25..9d5d8534096 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ - ### Spring相关 - [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) - + - [可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) ## :floppy_disk: 数据存储 - ### MySQL - [MySQL 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL.md) From 1e5ea372aa8408443216f733914d78ba2848618c Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Tue, 11 Sep 2018 16:48:16 +0800 Subject: [PATCH 007/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=A0=87=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d5d8534096..5c1e4c02839 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ - ### Spring相关 - [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) - - [可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) + - [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) ## :floppy_disk: 数据存储 - ### MySQL - [MySQL 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL.md) From 788cf82cc452421a019faf539663f39ac2cd5ec9 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Wed, 12 Sep 2018 09:59:53 +0800 Subject: [PATCH 008/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5c1e4c02839..e5392a3448f 100644 --- a/README.md +++ b/README.md @@ -55,10 +55,12 @@ - ### Linux相关 - [后端程序员必备的 Linux 基础知识](https://github.com/Snailclimb/Java-Guide/blob/master/操作系统/后端程序员必备的Linux基础知识.md) -## :pencil2: 主流框架 +## :pencil2: 主流框架/软件 -- ### Spring相关 +- ### Spring - [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) + +- ### ZooKeeper - [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) ## :floppy_disk: 数据存储 - ### MySQL From 69c53d0b56d0d7531f870b0049290e97105f2de4 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Thu, 13 Sep 2018 08:54:08 +0800 Subject: [PATCH 009/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=94=99=E5=88=AB?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" "b/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" index d5ecd9063fe..3bf2cd8b756 100644 --- "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" +++ "b/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" @@ -169,7 +169,7 @@ JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通 1. **句柄:** 如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息; ![使用句柄](https://user-gold-cdn.xitu.io/2018/4/27/16306b9573968946?w=786&h=362&f=png&s=109201) -2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对像的布局中就必须考虑如何防止访问类型数据的相关信息,reference 中存储的直接就是对象的地址。 +2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对像的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。 ![使用直接指针](https://user-gold-cdn.xitu.io/2018/4/27/16306ba3a41b6b65?w=766&h=353&f=png&s=99172) From 2f61492874407ee38e527345d150b4be70ce6769 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Thu, 13 Sep 2018 09:18:23 +0800 Subject: [PATCH 010/122] =?UTF-8?q?hashcode=E6=96=B9=E6=B3=95=E8=AE=B2?= =?UTF-8?q?=E8=A7=A3=E7=9A=84=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...21\250\357\274\2102018-8-7\357\274\211.md" | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" index 6214a716cb8..7917aa38ae1 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" @@ -236,7 +236,27 @@ public class test1 { 面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” ### hashCode()介绍 -hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的 内存地址。 +hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。 + +```java + /** + * Returns a hash code value for the object. This method is + * supported for the benefit of hash tables such as those provided by + * {@link java.util.HashMap}. + *

+ * As much as is reasonably practical, the hashCode method defined by + * class {@code Object} does return distinct integers for distinct + * objects. (This is typically implemented by converting the internal + * address of the object into an integer, but this implementation + * technique is not required by the + * Java™ programming language.) + * + * @return a hash code value for this object. + * @see java.lang.Object#equals(java.lang.Object) + * @see java.lang.System#identityHashCode + */ + public native int hashCode(); +``` 散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) From 564d7bd6ad655c97c8077956deeefcf37894bb98 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Thu, 13 Sep 2018 18:25:29 +0800 Subject: [PATCH 011/122] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ZooKeeper.md" | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" index c23244c37d4..cc5970a6b08 100644 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" @@ -41,13 +41,11 @@ ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在 **为什么最好使用奇数台服务器构成 ZooKeeper 集群?** -我们知道在Zookeeper中 Leader 选举算法采用了Zab协议。Zab核心思想是当多数 Server 写成功,则任务数据写成功。 +所谓的zookeeper容错是指,当宕掉几个zookeeper服务器之后,剩下的个数必须大于宕掉的个数的话整个zookeeper才依然可用。假如我们的集群中有n台zookeeper服务器,那么也就是剩下的服务数必须大于n/2。先说一下结论,2n和2n-1的容忍度是一样的,都是n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。 +比如假如我们有3台,那么最大允许宕掉1台zookeeper服务器,如果我们有4台的的时候也同样只允许宕掉1台。 +假如我们有5台,那么最大允许宕掉2台zookeeper服务器,如果我们有6台的的时候也同样只允许宕掉2台。 -①如果有3个Server,则最多允许1个Server 挂掉。 - -②如果有4个Server,则同样最多允许1个Server挂掉。 - -既然3个或者4个Server,同样最多允许1个Server挂掉,那么它们的可靠性是一样的,所以选择奇数个ZooKeeper Server即可,这里选择3个Server。12341234 +综上,何必增加那一个不必要的zookeeper呢。 @@ -120,7 +118,7 @@ ZooKeeper 允许分布式进程通过共享的层次结构命名空间进行相 上图中每一个Server代表一个安装Zookeeper服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 Zab 协议(Zookeeper Atomic Broadcast)来保持数据的一致性。 -###4.3 顺序访问 +### 4.3 顺序访问 **对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号,这个编号反应了所有事务操作的先后顺序,应用程序可以使用 ZooKeeper 这个特性来实现更高层次的同步原语。** **这个编号也叫做时间戳——zxid(Zookeeper Transaction Id)** @@ -138,7 +136,16 @@ ZooKeeper 允许分布式进程通过共享的层次结构命名空间进行相 **ZooKeeper 集群中的所有机器通过一个 Leader 选举过程来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。** -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/77341396.jpg) +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-13/91622395.jpg) + +**当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进人恢复模式并选举产生新的Leader服务器。这个过程大致是这样的:** + +1. Leader election(选举阶段):节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。 +2. Discovery(发现阶段):在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。 +3. Synchronization(同步阶段):同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后 +准 leader 才会成为真正的 leader。 +4. Broadcast(广播阶段) +到了这个阶段,Zookeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。 ## 六 ZooKeeper &ZAB 协议&Paxos算法 From 2099ea63abf44b9d090e22686aac6d45865f8d1b Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Fri, 14 Sep 2018 08:24:14 +0800 Subject: [PATCH 012/122] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" | 3 +++ 1 file changed, 3 insertions(+) diff --git "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" "b/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" index 3bf2cd8b756..baa5f5491cf 100644 --- "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" +++ "b/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" @@ -103,6 +103,9 @@ HotSpot 虚拟机中方法区也常被称为 **“永久代”**,本质上两 **JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。** +![](https://img-blog.csdn.net/20170329213804490?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvU3VnYXJfUmFpbmJvdw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)——图片来源:https://blog.csdn.net/wangbiao007/article/details/78545189 + + 推荐阅读: From cc5b7f38e0bd079940a6d46497c58635f4826434 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Fri, 14 Sep 2018 08:26:15 +0800 Subject: [PATCH 013/122] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" "b/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" index baa5f5491cf..ddd844266b4 100644 --- "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" +++ "b/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" @@ -103,7 +103,8 @@ HotSpot 虚拟机中方法区也常被称为 **“永久代”**,本质上两 **JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。** -![](https://img-blog.csdn.net/20170329213804490?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvU3VnYXJfUmFpbmJvdw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)——图片来源:https://blog.csdn.net/wangbiao007/article/details/78545189 +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/26038433.jpg) +——图片来源:https://blog.csdn.net/wangbiao007/article/details/78545189 From 9d544963505dbc19ad3fa922d92f8525014f5bcf Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Fri, 14 Sep 2018 09:13:16 +0800 Subject: [PATCH 014/122] =?UTF-8?q?final=E3=80=81static=E3=80=81this?= =?UTF-8?q?=E3=80=81super=E5=85=B3=E9=94=AE=E5=AD=97=E6=80=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tatic\343\200\201this\343\200\201super.md" | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 "Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" diff --git "a/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" "b/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" new file mode 100644 index 00000000000..cf6a0946b86 --- /dev/null +++ "b/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" @@ -0,0 +1,84 @@ +## final 关键字 + +**final关键字主要用在三个地方:变量、方法、类。** + +1. **对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。** + +2. **当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。** + +3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 + +## static 关键字 + +**static 关键字主要有以下四种使用场景:** + +1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()` +2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次. +3. **静态内部类(static修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。 +4. **静态导包(用来导入类中的静态资源,1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。 + +## this 关键字 + +this关键字用于引用类的当前实例。 例如: + +```java +class Manager { + Employees[] employees; + + void manageEmployees() { + int totalEmp = this.employees.length; + System.out.println("Total employees: " + totalEmp); + this.report(); + } + + void report() { } +} +``` + +在上面的示例中,this关键字用于两个地方: + +- this.employees.length:访问类Manager的当前实例的变量。 +- this.report():调用类Manager的当前实例的方法。 + +此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。 + + + +## super 关键字 + +super关键字用于从子类访问父类的变量和方法。 例如: + +```java +public class Super { + protected int number; + + protected showNumber() { + System.out.println("number = " + number); + } +} + +public class Sub extends Super { + void bar() { + super.number = 10; + super.showNumber(); + } +} +``` + +在上面的例子中,Sub 类访问父类成员变量 number 并调用其其父类 Super 的 `showNumber()` 方法。 + +**使用 this 和 super 要注意的问题:** + +- super 调用父类中的其他构造方法时,调用时要放在构造方法的首行!this 调用奔雷中的其他构造方法时,也要放在首行。 +- this、super不能用在static方法中。 + +**简单解释一下:** + +被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西**。 + + + +## 参考 + +- https://www.codejava.net/java-core/the-java-language/java-keywords +- https://blog.csdn.net/u013393958/article/details/79881037 From ab925402b9ac4821601c116aea8ce6d76adbbd55 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Fri, 14 Sep 2018 09:27:25 +0800 Subject: [PATCH 015/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20=E2=80=9Cstatic?= =?UTF-8?q?=E3=80=81final=E3=80=81this=E3=80=81super=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E5=AD=97=E6=80=BB=E7=BB=93=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e5392a3448f..6f0089c7f98 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ - ### Java/J2EE 基础 - [Java 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java基础知识.md) - [J2EE 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/J2EE基础知识.md) + - [static、final、this、super关键字总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/final、static、this、super.md) + - ### Java 集合框架 - [这几道Java集合框架面试题几乎必问](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) - [Java 集合框架常见面试题总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java集合框架常见面试题总结.md) From 8295879f6130e8c15a1d6f31d4094bb0a4d0bf90 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Fri, 14 Sep 2018 09:31:15 +0800 Subject: [PATCH 016/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20static=20=E5=85=B3?= =?UTF-8?q?=E9=94=AE=E5=AD=97=E6=80=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "Java\347\233\270\345\205\263/static.md" | 241 +++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 "Java\347\233\270\345\205\263/static.md" diff --git "a/Java\347\233\270\345\205\263/static.md" "b/Java\347\233\270\345\205\263/static.md" new file mode 100644 index 00000000000..e962fa7edce --- /dev/null +++ "b/Java\347\233\270\345\205\263/static.md" @@ -0,0 +1,241 @@ + +# static 关键字 + +## static 关键字主要有以下四种使用场景 + +1. 修饰成员变量和成员方法 +2. 静态代码块 +3. 修饰类(只能修饰内部类) +4. 静态导包(用来导入类中的静态资源,1.5之后的新特性) + +### 修饰成员变量和成员方法(常用) + +被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。 + +方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。 + + HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。 + + + +调用格式: + +- 类名.静态变量名 +- 类名.静态方法名() + +如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被方法。 + +测试方法: + +```java +public class StaticBean { + + String name; + 静态变量 + static int age; + + public StaticBean(String name) { + this.name = name; + } + 静态方法 + static void SayHello() { + System.out.println(Hello i am java); + } + @Override + public String toString() { + return StaticBean{ + + name=' + name + ''' + age + age + + '}'; + } +} +``` + +```java +public class StaticDemo { + + public static void main(String[] args) { + StaticBean staticBean = new StaticBean(1); + StaticBean staticBean2 = new StaticBean(2); + StaticBean staticBean3 = new StaticBean(3); + StaticBean staticBean4 = new StaticBean(4); + StaticBean.age = 33; + StaticBean{name='1'age33} StaticBean{name='2'age33} StaticBean{name='3'age33} StaticBean{name='4'age33} + System.out.println(staticBean+ +staticBean2+ +staticBean3+ +staticBean4); + StaticBean.SayHello();Hello i am java + } + +} +``` + + +### 静态代码块 + +静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次. + +静态代码块的格式是 + +``` +static { +语句体; +} +``` + + +一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。 + +![enter image description here](httpsimg-blog.csdn.net20180107161309133watermark2textaHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2hlbjEzNTc5ODY3ODMxfont5a6L5L2Tfontsize400fillI0JBQkFCMA==dissolve70gravitySouthEast) + +静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问. + + +### 静态内部类 + +静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着: + +1. 它的创建是不需要依赖外围类的创建。 +2. 它不能使用任何外围类的非static成员变量和方法。 + + +Example(静态内部类实现单例模式) + +```java +public class Singleton { + + 声明为 private 避免调用默认构造方法创建对象 + private Singleton() { + } + + 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问 + private static class SingletonHolder { + private static final Singleton INSTANCE = new Singleton(); + } + + public static Singleton getUniqueInstance() { + return SingletonHolder.INSTANCE; + } +} +``` + +当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance() `方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。 + +这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。 + +### 静态导包 + +格式为:import static + +这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法 + +```java + + + Math. --- 将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用 + 如果只想导入单一某个静态方法,只需要将换成对应的方法名即可 + +import static java.lang.Math.; + + 换成import static java.lang.Math.max;具有一样的效果 + +public class Demo { + public static void main(String[] args) { + + int max = max(1,2); + System.out.println(max); + } +} + +``` + + +## 补充内容 + +### 静态方法与非静态方法 + +静态方法属于类本身,非静态方法属于从该类生成的每个对象。 如果您的方法执行的操作不依赖于其类的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。 否则,它应该是非静态的。 + +Example + +```java +class Foo { + int i; + public Foo(int i) { + this.i = i; + } + + public static String method1() { + return An example string that doesn't depend on i (an instance variable); + + } + + public int method2() { + return this.i + 1; Depends on i + } + +} +``` +你可以像这样调用静态方法:`Foo.method1()`。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行:`Foo bar = new Foo(1);bar.method2();` + +总结: + +- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 +- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制 + +### static{}静态代码块与{}非静态代码块(构造代码块) + +相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。 + +不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。 + +一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的. + +Example + +```java +public class Test { + public Test() { + System.out.print(默认构造方法!--); + } + + 非静态代码块 + { + System.out.print(非静态代码块!--); + } + 静态代码块 + static { + System.out.print(静态代码块!--); + } + + public static void test() { + System.out.print(静态方法中的内容! --); + { + System.out.print(静态方法中的代码块!--); + } + + } + public static void main(String[] args) { + + Test test = new Test(); + Test.test();静态代码块!--静态方法中的内容! --静态方法中的代码块!-- + } +``` + +当执行 `Test.test();` 时输出: + +``` +静态代码块!--静态方法中的内容! --静态方法中的代码块!-- +``` + +当执行 `Test test = new Test();` 时输出: + +``` +静态代码块!--非静态代码块!--默认构造方法!-- +``` + + +非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。 + +### 参考 + +- httpsblog.csdn.netchen13579867831articledetails78995480 +- httpwww.cnblogs.comchenssyp3388487.html +- httpwww.cnblogs.comQian123p5713440.html \ No newline at end of file From a15483fe11cea07b583c73403ba7ff3c51ff8432 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Fri, 14 Sep 2018 09:34:49 +0800 Subject: [PATCH 017/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E2=80=9Cstatic?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E5=AD=97=E2=80=9D=E8=AF=A6=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f0089c7f98..f81d27bf060 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ - ### Java/J2EE 基础 - [Java 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java基础知识.md) - [J2EE 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/J2EE基础知识.md) - - [static、final、this、super关键字总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/final、static、this、super.md) + - [static、final、this、super关键字总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/final、static、this、super.md) - [static 关键字详解](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/static.md) - ### Java 集合框架 - [这几道Java集合框架面试题几乎必问](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) From 4d1f98e9255e0e6a1eb00c14c5af55091cd63ac2 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Fri, 14 Sep 2018 09:37:08 +0800 Subject: [PATCH 018/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=8E=92=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f81d27bf060..c098b931ed5 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ - ### Java/J2EE 基础 - [Java 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java基础知识.md) - [J2EE 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/J2EE基础知识.md) - - [static、final、this、super关键字总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/final、static、this、super.md) - [static 关键字详解](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/static.md) + - [static、final、this、super关键字总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/final、static、this、super.md) + - [static 关键字详解](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/static.md) - ### Java 集合框架 - [这几道Java集合框架面试题几乎必问](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) From ea13483493ca1138cc7817a687187902553a4c24 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Fri, 14 Sep 2018 09:51:32 +0800 Subject: [PATCH 019/122] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "Java\347\233\270\345\205\263/static.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/Java\347\233\270\345\205\263/static.md" "b/Java\347\233\270\345\205\263/static.md" index e962fa7edce..713efff8eae 100644 --- "a/Java\347\233\270\345\205\263/static.md" +++ "b/Java\347\233\270\345\205\263/static.md" @@ -83,7 +83,7 @@ static { 一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。 -![enter image description here](httpsimg-blog.csdn.net20180107161309133watermark2textaHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2hlbjEzNTc5ODY3ODMxfont5a6L5L2Tfontsize400fillI0JBQkFCMA==dissolve70gravitySouthEast) +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/88531075.jpg) 静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问. @@ -238,4 +238,4 @@ public class Test { - httpsblog.csdn.netchen13579867831articledetails78995480 - httpwww.cnblogs.comchenssyp3388487.html -- httpwww.cnblogs.comQian123p5713440.html \ No newline at end of file +- httpwww.cnblogs.comQian123p5713440.html From d7789b02529519772bbd1ea9af5b1aefc672b822 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Fri, 14 Sep 2018 10:04:59 +0800 Subject: [PATCH 020/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=A0=87=E7=82=B9?= =?UTF-8?q?=E7=AC=A6=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" index cc5970a6b08..0fc0abe3895 100644 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" @@ -45,7 +45,7 @@ ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在 比如假如我们有3台,那么最大允许宕掉1台zookeeper服务器,如果我们有4台的的时候也同样只允许宕掉1台。 假如我们有5台,那么最大允许宕掉2台zookeeper服务器,如果我们有6台的的时候也同样只允许宕掉2台。 -综上,何必增加那一个不必要的zookeeper呢。 +综上,何必增加那一个不必要的zookeeper呢? From 7f922cbbeb20e7f808a291733a812ddb0e039dd7 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 15 Sep 2018 09:34:31 +0800 Subject: [PATCH 021/122] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug=EF=BC=9Achar?= =?UTF-8?q?=E5=9C=A8Java=E4=B8=AD=E5=BA=94=E8=AF=A5=E5=8D=A0=E4=B8=A4?= =?UTF-8?q?=E4=B8=AA=E5=AD=97=E8=8A=82=20=EF=BC=88=E6=84=9F=E8=B0=A2issue?= =?UTF-8?q?=E6=8F=90=E9=86=92=EF=BC=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Java\345\237\272\347\241\200\347\237\245\350\257\206.md" | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 643550b3ed4..38e917ad627 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -126,7 +126,10 @@ Java 语言通过字节码的方式,在一定程度上解决了传统解释型 1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符 2. 含义上: 字符常量相当于一个整形值( ASCII 值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置) -3. 占内存大小 字符常量只占一个字节 字符串常量占若干个字节(至少一个字符结束标志) +3. 占内存大小 字符常量只占2个字节 字符串常量占若干个字节(至少一个字符结束标志) (**注意: char在Java中占两个字节**) + +> java编程思想第四版:2.2.2节 +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg) ## 9. 构造器 Constructor 是否可被 override From d593ae73b19abf9814f5357a0e80fcca53770289 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sun, 16 Sep 2018 15:21:37 +0800 Subject: [PATCH 022/122] =?UTF-8?q?=E8=A1=A5=E5=85=85=EF=BC=9A=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E7=BD=91=E9=A1=B5=E4=BB=8E=E8=AF=B7=E6=B1=82=E5=88=B0?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E7=9A=84=E6=95=B4=E4=B8=AA=E8=BF=87=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\227\346\234\272\347\275\221\347\273\234.md" | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" index 63f9962a784..70f39a1e0ac 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" @@ -266,9 +266,24 @@ TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 百度好像最喜欢问这个问题。 > 打开一个网页,整个过程会使用哪些协议 -图片来源:《图解HTTP》 +图解(图片来源:《图解HTTP》): ![状态码](https://user-gold-cdn.xitu.io/2018/4/19/162db5e985aabdbe?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) +一个网页从请求到最终显示的完整过程一般可以分为如下7个步骤: + +(1)在浏览器中输入网址; + +(2)发送至DNS服务器并获得域名对应的WEB服务器IP地址; + +(3)与WEB服务器建立TCP连接; + +(4)浏览器向WEB服务器的IP地址发送相应的HTTP请求; + +(5)WEB服务器响应请求并返回指定URL的数据,或错误信息,如果设定重定向,则重定向到新的URL地址; + +(6)浏览器下载数据后解析HTML源文件,解析的过程中实现对页面的排版,解析完成后在浏览器中显示基础页面; + +(7)分析页面中的超链接并显示在当前页面,重复以上过程直至无超链接需要发送,完成全部数据显示。 From 5f1011737b08b107d467fb194b4111185d16e595 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sun, 16 Sep 2018 15:24:27 +0800 Subject: [PATCH 023/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...27\346\234\272\347\275\221\347\273\234.md" | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" index 70f39a1e0ac..58018270601 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" @@ -2,41 +2,42 @@ - [一 OSI与TCP/IP各层的结构与功能,都有哪些协议](#一-osi与tcpip各层的结构与功能都有哪些协议) - - [五层协议的体系结构](#五层协议的体系结构) - - [1 应用层](#1-应用层) - - [域名系统](#域名系统) - - [HTTP协议](#http协议) - - [2 运输层](#2-运输层) - - [运输层主要使用以下两种协议](#运输层主要使用以下两种协议) - - [UDP 的主要特点](#udp-的主要特点) - - [TCP 的主要特点](#tcp-的主要特点) - - [3 网络层](#3-网络层) - - [4 数据链路层](#4-数据链路层) - - [5 物理层](#5-物理层) - - [总结一下](#总结一下) + - [五层协议的体系结构](#五层协议的体系结构) + - [1 应用层](#1-应用层) + - [域名系统](#域名系统) + - [HTTP协议](#http协议) + - [2 运输层](#2-运输层) + - [运输层主要使用以下两种协议](#运输层主要使用以下两种协议) + - [UDP 的主要特点](#udp-的主要特点) + - [TCP 的主要特点](#tcp-的主要特点) + - [3 网络层](#3-网络层) + - [4 数据链路层](#4-数据链路层) + - [5 物理层](#5-物理层) + - [总结一下](#总结一下) - [二 TCP 三次握手和四次挥手\(面试常客\)](#二-tcp-三次握手和四次挥手面试常客) - - [为什么要三次握手](#为什么要三次握手) - - [为什么要传回 SYN](#为什么要传回-syn) - - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack) - - [为什么要四次挥手](#为什么要四次挥手) + - [为什么要三次握手](#为什么要三次握手) + - [为什么要传回 SYN](#为什么要传回-syn) + - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack) + - [为什么要四次挥手](#为什么要四次挥手) - [三 TCP、UDP 协议的区别](#三-tcp、udp-协议的区别) - [四 TCP 协议如何保证可靠传输](#四-tcp-协议如何保证可靠传输) - - [停止等待协议](#停止等待协议) - - [自动重传请求 ARQ 协议](#自动重传请求-arq-协议) - - [连续ARQ协议](#连续arq协议) - - [滑动窗口](#滑动窗口) - - [流量控制](#流量控制) - - [拥塞控制](#拥塞控制) + - [停止等待协议](#停止等待协议) + - [自动重传请求 ARQ 协议](#自动重传请求-arq-协议) + - [连续ARQ协议](#连续arq协议) + - [滑动窗口](#滑动窗口) + - [流量控制](#流量控制) + - [拥塞控制](#拥塞控制) - [五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客)](#五-在浏览器中输入url地址---显示主页的过程(面试常客)) - [六 状态码](#六-状态码) - [七 各种协议与HTTP协议之间的关系](#七-各种协议与http协议之间的关系) - [八 HTTP长连接、短连接](#八-http长连接、短连接) - [写在最后](#写在最后) - - [计算机网络常见问题回顾](#计算机网络常见问题回顾) - - [建议](#建议) + - [计算机网络常见问题回顾](#计算机网络常见问题回顾) + - [建议](#建议) + 相对与上一个版本的计算机网路面试知识总结,这个版本增加了 “TCP协议如何保证可靠传输”包括超时重传、停止等待协议、滑动窗口、流量控制、拥塞控制等内容并且对一些已有内容做了补充。 @@ -269,7 +270,8 @@ TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 图解(图片来源:《图解HTTP》): ![状态码](https://user-gold-cdn.xitu.io/2018/4/19/162db5e985aabdbe?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) -一个网页从请求到最终显示的完整过程一般可以分为如下7个步骤: + +**一个网页从请求到最终显示的完整过程一般可以分为如下7个步骤:** (1)在浏览器中输入网址; @@ -329,13 +331,12 @@ Connection:keep-alive 非常推荐大家看一下 《图解HTTP》 这本书,这本书页数不多,但是内容很是充实,不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候,我们使用的教材是 《计算机网络第七版》(谢希仁编著),不推荐大家看这本教材,书非常厚而且知识偏理论,不确定大家能不能心平气和的读完。 -参考: - -[https://blog.csdn.net/qq_16209077/article/details/52718250](https://blog.csdn.net/qq_16209077/article/details/52718250) -[https://blog.csdn.net/zixiaomuwu/article/details/60965466](https://blog.csdn.net/zixiaomuwu/article/details/60965466) +### 参考 -[https://blog.csdn.net/turn__back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641) +- [https://blog.csdn.net/qq_16209077/article/details/52718250](https://blog.csdn.net/qq_16209077/article/details/52718250) +- [https://blog.csdn.net/zixiaomuwu/article/details/60965466](https://blog.csdn.net/zixiaomuwu/article/details/60965466) +- [https://blog.csdn.net/turn__back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641) From 89828796254e193f15125eabadc2e55f94c7e579 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sun, 16 Sep 2018 15:28:06 +0800 Subject: [PATCH 024/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=94=99=E5=88=AB?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" index 58018270601..39dd3c50452 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" @@ -279,7 +279,7 @@ TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** (3)与WEB服务器建立TCP连接; -(4)浏览器向WEB服务器的IP地址发送相应的HTTP请求; +(4)浏览器向WEB服务器发送相应的HTTP请求; (5)WEB服务器响应请求并返回指定URL的数据,或错误信息,如果设定重定向,则重定向到新的URL地址; From 4529ad99caa31081a7143530ae6a31ab58ed7004 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sun, 16 Sep 2018 16:10:32 +0800 Subject: [PATCH 025/122] =?UTF-8?q?get=20=E5=92=8C=20post=20=20=E7=9A=84?= =?UTF-8?q?=E5=8C=BA=E5=88=AB=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" | 4 ++++ 1 file changed, 4 insertions(+) diff --git "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" index da4a7e32e6f..665a441a227 100644 --- "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -78,6 +78,10 @@ Servlet接口定义了5个方法,其中**前三个方法与Servlet生命周期 补充:GET方式提交表单的典型应用是搜索引擎。GET方式就是被设计为查询用的。 +还有另外一种回答。推荐大家看一下: + +- https://www.zhihu.com/question/28586791 +- https://mp.weixin.qq.com/s?__biz=MzI3NzIzMzg3Mw==&mid=100000054&idx=1&sn=71f6c214f3833d9ca20b9f7dcd9d33e4#rd ## 什么情况下调用doGet()和doPost() Form标签里的method的属性为get时调用doGet(),为post时调用doPost()。 From 2fd210693890d90522c6a6d41e62eff0cbffa341 Mon Sep 17 00:00:00 2001 From: Jiawei <34996463+zeason@users.noreply.github.com> Date: Mon, 17 Sep 2018 17:12:28 +0900 Subject: [PATCH 026/122] =?UTF-8?q?Update=20final=E3=80=81static=E3=80=81t?= =?UTF-8?q?his=E3=80=81super.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正错别字 --- ...final\343\200\201static\343\200\201this\343\200\201super.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" "b/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" index cf6a0946b86..d891cc3346e 100644 --- "a/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" +++ "b/Java\347\233\270\345\205\263/final\343\200\201static\343\200\201this\343\200\201super.md" @@ -69,7 +69,7 @@ public class Sub extends Super { **使用 this 和 super 要注意的问题:** -- super 调用父类中的其他构造方法时,调用时要放在构造方法的首行!this 调用奔雷中的其他构造方法时,也要放在首行。 +- super 调用父类中的其他构造方法时,调用时要放在构造方法的首行!this 调用本类中的其他构造方法时,也要放在首行。 - this、super不能用在static方法中。 **简单解释一下:** From 183cb0a4d52d6c1af8e091ff78345578e8567b47 Mon Sep 17 00:00:00 2001 From: Jiawei <34996463+zeason@users.noreply.github.com> Date: Mon, 17 Sep 2018 18:22:11 +0900 Subject: [PATCH 027/122] =?UTF-8?q?Update=20Java=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E7=9F=A5=E8=AF=86.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 改善一些地方的易读性 --- ...345\237\272\347\241\200\347\237\245\350\257\206.md" | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 38e917ad627..dc6a1826666 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -145,7 +145,7 @@ Java 语言通过字节码的方式,在一定程度上解决了传统解释型 ### 封装 -封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果不想被外界方法,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。 +封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。 ### 继承 @@ -181,9 +181,9 @@ String 中的对象是不可变的,也就可以理解为常量,线程安全 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 **对于三者使用的总结:** -如果要操作少量的数据用 = String -单线程操作字符串缓冲区 下操作大量数据 = StringBuilder -多线程操作字符串缓冲区 下操作大量数据 = StringBuffer +1. 操作少量的数据 = String +2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder +3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer ## 13. 自动装箱与拆箱 **装箱**:将基本类型用它们对应的引用类型包装起来; @@ -244,7 +244,7 @@ new运算符,new创建对象实例(对象实例在堆内存中),对象 ## 24. 对象的相等与指向他们的引用相等,两者有什么不同? -对象的相等 比的是内存中存放的内容是否相等而引用相等 比较的是他们指向的内存地址是否相等。 +对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。 ## 25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是? From bb4edcbaaf46bdbff4a14e45bad00ae7ff2b3204 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Mon, 17 Sep 2018 22:25:25 +0800 Subject: [PATCH 028/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=EF=BC=9A=E2=80=9D=20?= =?UTF-8?q?Spring=E4=B8=ADbean=E7=9A=84=E4=BD=9C=E7=94=A8=E5=9F=9F?= =?UTF-8?q?=E4=B8=8E=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E2=80=9C=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E8=AE=B2=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 说明:该文章内容非个人原创哦! --- .../Spring Bean.md" | 449 ++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 "\344\270\273\346\265\201\346\241\206\346\236\266/Spring Bean.md" diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/Spring Bean.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/Spring Bean.md" new file mode 100644 index 00000000000..32278d88b69 --- /dev/null +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/Spring Bean.md" @@ -0,0 +1,449 @@ + + +- [前言](#前言) +- [一 bean的作用域](#一-bean的作用域) + - [1. singleton——唯一 bean 实例](#1-singleton——唯一-bean-实例) + - [2. prototype——每次请求都会创建一个新的 bean 实例](#2-prototype——每次请求都会创建一个新的-bean-实例) + - [3. request——每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效](#3-request——每一次http请求都会产生一个新的bean,该bean仅在当前http-request内有效) + - [4. session——每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效](#4-session——每一次http请求都会产生一个新的-bean,该bean仅在当前-http-session-内有效) + - [5. globalSession](#5-globalsession) +- [二 bean的生命周期](#二-bean的生命周期) + - [initialization 和 destroy](#initialization-和-destroy) + - [实现*Aware接口 在Bean中使用Spring框架的一些对象](#实现aware接口-在bean中使用spring框架的一些对象) + - [BeanPostProcessor](#beanpostprocessor) + - [总结](#总结) + - [单例管理的对象](#单例管理的对象) + - [非单例管理的对象](#非单例管理的对象) +- [三 说明](#三-说明) + + + +# 前言 +在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。 + +**Spring中的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢?** 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢? **Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于 JVM,每个 JVM 内只有一个实例。** + +# 一 bean的作用域 + +创建一个bean定义,其实质是用该bean定义对应的类来创建真正实例的“配方”。把bean定义看成一个配方很有意义,它与class很类似,只根据一张“处方”就可以创建多个实例。不仅可以控制注入到对象中的各种依赖和配置值,还可以控制该对象的作用域。这样可以灵活选择所建对象的作用域,而不必在Java Class级定义作用域。Spring Framework支持五种作用域,分别阐述如下表。 + + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/1188352.jpg) + +五种作用域中,**request、session** 和 **global session** 三种作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于 web 的 Spring ApplicationContext 环境。 + + + +### 1. singleton——唯一 bean 实例 + +**当一个 bean 的作用域为 singleton,那么Spring IoC容器中只会存在一个共享的 bean 实例,并且所有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回bean的同一实例。** singleton 是单例类型(对应于单例模式),就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,singleton 作用域是Spring中的缺省作用域。要在XML中将 bean 定义成 singleton ,可以这样配置: + +```xml + +``` + +也可以通过 `@Scope` 注解(它可以显示指定bean的作用范围。)的方式 + +```java +@Service +@Scope("singleton") +public class ServiceImpl{ + +} +``` + +### 2. prototype——每次请求都会创建一个新的 bean 实例 + +**当一个bean的作用域为 prototype,表示一个 bean 定义对应多个对象实例。** **prototype 作用域的 bean 会导致在每次对该 bean 请求**(将其注入到另一个 bean 中,或者以程序的方式调用容器的 getBean() 方法**)时都会创建一个新的 bean 实例。prototype 是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的 bean 应该使用 prototype 作用域,而对无状态的 bean 则应该使用 singleton 作用域。** 在 XML 中将 bean 定义成 prototype ,可以这样配置: + +```java + + 或者 + +``` +通过 `@Scope` 注解的方式实现就不做演示了。 + +### 3. request——每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效 + +**request只适用于Web程序,每一次 HTTP 请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效,当请求结束后,该对象的生命周期即告结束。** 在 XML 中将 bean 定义成 prototype ,可以这样配置: + +```java + +``` + +### 4. session——每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效 + +**session只适用于Web程序,session 作用域表示该针对每一次 HTTP 请求都会产生一个新的 bean,同时该 bean 仅在当前 HTTP session 内有效.与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的 HTTP session 中根据 userPreferences 创建的实例,将不会看到这些特定于某个 HTTP session 的状态变化。当HTTP session最终被废弃的时候,在该HTTP session作用域内的bean也会被废弃掉。** + +```xml + +``` + +### 5. globalSession + +global session 作用域类似于标准的 HTTP session 作用域,不过仅仅在基于 portlet 的 web 应用中才有意义。Portlet 规范定义了全局 Session 的概念,它被所有构成某个 portlet web 应用的各种不同的 portle t所共享。在global session 作用域中定义的 bean 被限定于全局portlet Session的生命周期范围内。 + +```xml + +``` + +# 二 bean的生命周期 + +Spring Bean是Spring应用中最最重要的部分了。所以来看看Spring容器在初始化一个bean的时候会做那些事情,顺序是怎样的,在容器关闭的时候,又会做哪些事情。 + +> spring版本:4.2.3.RELEASE +鉴于Spring源码是用gradle构建的,我也决定舍弃我大maven,尝试下洪菊推荐过的gradle。运行beanLifeCycle模块下的junit test即可在控制台看到如下输出,可以清楚了解Spring容器在创建,初始化和销毁Bean的时候依次做了那些事情。 + +``` +Spring容器初始化 +===================================== +调用GiraffeService无参构造函数 +GiraffeService中利用set方法设置属性值 +调用setBeanName:: Bean Name defined in context=giraffeService +调用setBeanClassLoader,ClassLoader Name = sun.misc.Launcher$AppClassLoader +调用setBeanFactory,setBeanFactory:: giraffe bean singleton=true +调用setEnvironment +调用setResourceLoader:: Resource File Name=spring-beans.xml +调用setApplicationEventPublisher +调用setApplicationContext:: Bean Definition Names=[giraffeService, org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#0, com.giraffe.spring.service.GiraffeServicePostProcessor#0] +执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=giraffeService +调用PostConstruct注解标注的方法 +执行InitializingBean接口的afterPropertiesSet方法 +执行配置的init-method +执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=giraffeService +Spring容器初始化完毕 +===================================== +从容器中获取Bean +giraffe Name=李光洙 +===================================== +调用preDestroy注解标注的方法 +执行DisposableBean接口的destroy方法 +执行配置的destroy-method +Spring容器关闭 +``` + +先来看看,Spring在Bean从创建到销毁的生命周期中可能做得事情。 + + +### initialization 和 destroy + +有时我们需要在Bean属性值set好之后和Bean销毁之前做一些事情,比如检查Bean中某个属性是否被正常的设置好值了。Spring框架提供了多种方法让我们可以在Spring Bean的生命周期中执行initialization和pre-destroy方法。 + +**1.实现InitializingBean和DisposableBean接口** + +这两个接口都只包含一个方法。通过实现InitializingBean接口的afterPropertiesSet()方法可以在Bean属性值设置好之后做一些操作,实现DisposableBean接口的destroy()方法可以在销毁Bean之前做一些操作。 + +例子如下: + +```java +public class GiraffeService implements InitializingBean,DisposableBean { + @Override + public void afterPropertiesSet() throws Exception { + System.out.println("执行InitializingBean接口的afterPropertiesSet方法"); + } + @Override + public void destroy() throws Exception { + System.out.println("执行DisposableBean接口的destroy方法"); + } +} +``` +这种方法比较简单,但是不建议使用。因为这样会将Bean的实现和Spring框架耦合在一起。 + +**2.在bean的配置文件中指定init-method和destroy-method方法** + +Spring允许我们创建自己的 init 方法和 destroy 方法,只要在 Bean 的配置文件中指定 init-method 和 destroy-method 的值就可以在 Bean 初始化时和销毁之前执行一些操作。 + +例子如下: + +```java +public class GiraffeService { + //通过的destroy-method属性指定的销毁方法 + public void destroyMethod() throws Exception { + System.out.println("执行配置的destroy-method"); + } + //通过的init-method属性指定的初始化方法 + public void initMethod() throws Exception { + System.out.println("执行配置的init-method"); + } +} +``` + +配置文件中的配置: + +``` + + +``` + +需要注意的是自定义的init-method和post-method方法可以抛异常但是不能有参数。 + +这种方式比较推荐,因为可以自己创建方法,无需将Bean的实现直接依赖于spring的框架。 + +**3.使用@PostConstruct和@PreDestroy注解** + +除了xml配置的方式,Spring 也支持用 `@PostConstruct`和 `@PreDestroy`注解来指定 `init` 和 `destroy` 方法。这两个注解均在`javax.annotation` 包中。为了注解可以生效,需要在配置文件中定义org.springframework.context.annotation.CommonAnnotationBeanPostProcessor或context:annotation-config + +例子如下: + +```java +public class GiraffeService { + @PostConstruct + public void initPostConstruct(){ + System.out.println("执行PostConstruct注解标注的方法"); + } + @PreDestroy + public void preDestroy(){ + System.out.println("执行preDestroy注解标注的方法"); + } +} +``` + +配置文件: + +```xml + + + +``` + +### 实现*Aware接口 在Bean中使用Spring框架的一些对象 + +有些时候我们需要在 Bean 的初始化中使用 Spring 框架自身的一些对象来执行一些操作,比如获取 ServletContext 的一些参数,获取 ApplicaitionContext 中的 BeanDefinition 的名字,获取 Bean 在容器中的名字等等。为了让 Bean 可以获取到框架自身的一些对象,Spring 提供了一组名为*Aware的接口。 + +这些接口均继承于`org.springframework.beans.factory.Aware`标记接口,并提供一个将由 Bean 实现的set*方法,Spring通过基于setter的依赖注入方式使相应的对象可以被Bean使用。 +网上说,这些接口是利用观察者模式实现的,类似于servlet listeners,目前还不明白,不过这也不在本文的讨论范围内。 +介绍一些重要的Aware接口: + +- **ApplicationContextAware**: 获得ApplicationContext对象,可以用来获取所有Bean definition的名字。 +- **BeanFactoryAware**:获得BeanFactory对象,可以用来检测Bean的作用域。 +- **BeanNameAware**:获得Bean在配置文件中定义的名字。 +- **ResourceLoaderAware**:获得ResourceLoader对象,可以获得classpath中某个文件。 +- **ServletContextAware**:在一个MVC应用中可以获取ServletContext对象,可以读取context中的参数。 +- **ServletConfigAware**: 在一个MVC应用中可以获取ServletConfig对象,可以读取config中的参数。 + +```java +public class GiraffeService implements ApplicationContextAware, + ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware, + BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware{ + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + System.out.println("执行setBeanClassLoader,ClassLoader Name = " + classLoader.getClass().getName()); + } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + System.out.println("执行setBeanFactory,setBeanFactory:: giraffe bean singleton=" + beanFactory.isSingleton("giraffeService")); + } + @Override + public void setBeanName(String s) { + System.out.println("执行setBeanName:: Bean Name defined in context=" + + s); + } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + System.out.println("执行setApplicationContext:: Bean Definition Names=" + + Arrays.toString(applicationContext.getBeanDefinitionNames())); + } + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + System.out.println("执行setApplicationEventPublisher"); + } + @Override + public void setEnvironment(Environment environment) { + System.out.println("执行setEnvironment"); + } + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + Resource resource = resourceLoader.getResource("classpath:spring-beans.xml"); + System.out.println("执行setResourceLoader:: Resource File Name=" + + resource.getFilename()); + } + @Override + public void setImportMetadata(AnnotationMetadata annotationMetadata) { + System.out.println("执行setImportMetadata"); + } +} +``` + +### BeanPostProcessor + +上面的*Aware接口是针对某个实现这些接口的Bean定制初始化的过程, +Spring同样可以针对容器中的所有Bean,或者某些Bean定制初始化过程,只需提供一个实现BeanPostProcessor接口的类即可。 该接口中包含两个方法,postProcessBeforeInitialization和postProcessAfterInitialization。 postProcessBeforeInitialization方法会在容器中的Bean初始化之前执行, postProcessAfterInitialization方法在容器中的Bean初始化之后执行。 + +例子如下: + +```java +public class CustomerBeanPostProcessor implements BeanPostProcessor { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + System.out.println("执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=" + beanName); + return bean; + } + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + System.out.println("执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=" + beanName); + return bean; + } +} +``` + +要将BeanPostProcessor的Bean像其他Bean一样定义在配置文件中 + +```xml + +``` + +### 总结 + +所以。。。结合第一节控制台输出的内容,Spring Bean的生命周期是这样纸的: + +- Bean容器找到配置文件中 Spring Bean 的定义。 +- Bean容器利用Java Reflection API创建一个Bean的实例。 +- 如果涉及到一些属性值 利用set方法设置一些属性值。 +- 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。 +- 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 +- 如果Bean实现了BeanFactoryAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 +- 与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。 +- 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法 +- 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。 +- 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。 +- 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法 +- 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。 +- 当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。 + +用图表示一下(图来源:http://www.jianshu.com/p/d00539babca5): + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/48376272.jpg) + +与之比较类似的中文版本: + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/5496407.jpg) + + +**其实很多时候我们并不会真的去实现上面说描述的那些接口,那么下面我们就除去那些接口,针对bean的单例和非单例来描述下bean的生命周期:** + +### 单例管理的对象 + +当scope=”singleton”,即默认情况下,会在启动容器时(即实例化容器时)时实例化。但我们可以指定Bean节点的lazy-init=”true”来延迟初始化bean,这时候,只有在第一次获取bean时才会初始化bean,即第一次请求该bean时才初始化。如下配置: + +```xml + +``` + +如果想对所有的默认单例bean都应用延迟初始化,可以在根节点beans设置default-lazy-init属性为true,如下所示: + +```xml + +``` + +默认情况下,Spring 在读取 xml 文件的时候,就会创建对象。在创建对象的时候先调用构造器,然后调用 init-method 属性值中所指定的方法。对象在被销毁的时候,会调用 destroy-method 属性值中所指定的方法(例如调用Container.destroy()方法的时候)。写一个测试类,代码如下: + +```java +public class LifeBean { + private String name; + + public LifeBean(){ + System.out.println("LifeBean()构造函数"); + } + public String getName() { + return name; + } + + public void setName(String name) { + System.out.println("setName()"); + this.name = name; + } + + public void init(){ + System.out.println("this is init of lifeBean"); + } + + public void destory(){ + System.out.println("this is destory of lifeBean " + this); + } +} +``` + life.xml配置如下: + +```xml + +``` + +测试代码: + +```java +public class LifeTest { + @Test + public void test() { + AbstractApplicationContext container = + new ClassPathXmlApplicationContext("life.xml"); + LifeBean life1 = (LifeBean)container.getBean("life"); + System.out.println(life1); + container.close(); + } +} +``` + +运行结果: + +``` +LifeBean()构造函数 +this is init of lifeBean +com.bean.LifeBean@573f2bb1 +…… +this is destory of lifeBean com.bean.LifeBean@573f2bb1 +``` + +### 非单例管理的对象 + +当`scope=”prototype”`时,容器也会延迟初始化 bean,Spring 读取xml 文件的时候,并不会立刻创建对象,而是在第一次请求该 bean 时才初始化(如调用getBean方法时)。在第一次请求每一个 prototype 的bean 时,Spring容器都会调用其构造器创建这个对象,然后调用`init-method`属性值中所指定的方法。对象销毁的时候,Spring 容器不会帮我们调用任何方法,因为是非单例,这个类型的对象有很多个,Spring容器一旦把这个对象交给你之后,就不再管理这个对象了。 + +为了测试prototype bean的生命周期life.xml配置如下: + +```xml + +``` + +测试程序: + +```java +public class LifeTest { + @Test + public void test() { + AbstractApplicationContext container = new ClassPathXmlApplicationContext("life.xml"); + LifeBean life1 = (LifeBean)container.getBean("life_singleton"); + System.out.println(life1); + + LifeBean life3 = (LifeBean)container.getBean("life_prototype"); + System.out.println(life3); + container.close(); + } +} +``` + +运行结果: + +``` +LifeBean()构造函数 +this is init of lifeBean +com.bean.LifeBean@573f2bb1 +LifeBean()构造函数 +this is init of lifeBean +com.bean.LifeBean@5ae9a829 +…… +this is destory of lifeBean com.bean.LifeBean@573f2bb1 +``` + +可以发现,对于作用域为 prototype 的 bean ,其`destroy`方法并没有被调用。**如果 bean 的 scope 设为prototype时,当容器关闭时,`destroy` 方法不会被调用。对于 prototype 作用域的 bean,有一点非常重要,那就是 Spring不能对一个 prototype bean 的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。** 不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法。但对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。**清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责**(让Spring容器释放被prototype作用域bean占用资源的一种可行方式是,通过使用bean的后置处理器,该处理器持有要被清除的bean的引用)。谈及prototype作用域的bean时,在某些方面你可以将Spring容器的角色看作是Java new操作的替代者,任何迟于该时间点的生命周期事宜都得交由客户端来处理。 + +**Spring 容器可以管理 singleton 作用域下 bean 的生命周期,在此作用域下,Spring 能够精确地知道bean何时被创建,何时初始化完成,以及何时被销毁。而对于 prototype 作用域的bean,Spring只负责创建,当容器创建了 bean 的实例后,bean 的实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的bean的生命周期。** + + +# 三 说明 + +本文的完成结合了下面两篇文章,并做了相应修改: + +- https://blog.csdn.net/fuzhongmin05/article/details/73389779 +- https://yemengying.com/2016/07/14/spring-bean-life-cycle/ + +由于本文非本人独立原创,所以未声明为原创!在此说明! \ No newline at end of file From e0e5c042f2e41824d6cfc7a797cf2b5f9e8315a2 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 17 Sep 2018 22:27:50 +0800 Subject: [PATCH 029/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=EF=BC=9A=E2=80=9D=20?= =?UTF-8?q?Spring=E4=B8=ADbean=E7=9A=84=E4=BD=9C=E7=94=A8=E5=9F=9F?= =?UTF-8?q?=E4=B8=8E=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E2=80=9C=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E8=AE=B2=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c098b931ed5..3052b7bec80 100644 --- a/README.md +++ b/README.md @@ -62,9 +62,11 @@ - ### Spring - [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) + - [Spring中bean的作用域与生命周期](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring Bean.md) - ### ZooKeeper - [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) + ## :floppy_disk: 数据存储 - ### MySQL - [MySQL 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL.md) From 9cc0e041c6775026c171f9e26ddd2265f3dab8eb Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Mon, 17 Sep 2018 22:31:18 +0800 Subject: [PATCH 030/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=90=8D=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpringBean.md" | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename "\344\270\273\346\265\201\346\241\206\346\236\266/Spring Bean.md" => "\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" (100%) diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/Spring Bean.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" similarity index 100% rename from "\344\270\273\346\265\201\346\241\206\346\236\266/Spring Bean.md" rename to "\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" From 46f51dbc133eb4ca976e73d7247250e350ba1db9 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 17 Sep 2018 22:31:14 +0800 Subject: [PATCH 031/122] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E5=A4=B1=E6=95=88=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3052b7bec80..460afcba073 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ - ### Spring - [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) - - [Spring中bean的作用域与生命周期](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring Bean.md) + - [Spring中bean的作用域与生命周期](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/SpringBean.md) - ### ZooKeeper - [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) From ba35260651eff16a1cbbef1cdff050d312785642 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Tue, 18 Sep 2018 16:38:17 +0800 Subject: [PATCH 032/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20ArrayList=20?= =?UTF-8?q?=E6=89=A9=E5=AE=B9=E6=9C=BA=E5=88=B6=E5=88=86=E6=9E=90=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ArrayList-Grow.md" | 347 ++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 "Java\347\233\270\345\205\263/ArrayList-Grow.md" diff --git "a/Java\347\233\270\345\205\263/ArrayList-Grow.md" "b/Java\347\233\270\345\205\263/ArrayList-Grow.md" new file mode 100644 index 00000000000..cb1ffdf5694 --- /dev/null +++ "b/Java\347\233\270\345\205\263/ArrayList-Grow.md" @@ -0,0 +1,347 @@ + +## 一 先从 ArrayList 的构造函数说起 + +**ArrayList有三种方式来初始化,构造方法源码如下:** + +```java + /** + * 默认初始容量大小 + */ + private static final int DEFAULT_CAPACITY = 10; + + + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + /** + *默认构造函数,使用初始容量10构造一个空列表(无参数构造) + */ + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + /** + * 带初始容量参数的构造函数。(用户自己指定容量) + */ + public ArrayList(int initialCapacity) { + if (initialCapacity > 0) {//初始容量大于0 + //创建initialCapacity大小的数组 + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) {//初始容量等于0 + //创建空数组 + this.elementData = EMPTY_ELEMENTDATA; + } else {//初始容量小于0,抛出异常 + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + + + /** + *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回 + *如果指定的集合为null,throws NullPointerException。 + */ + public ArrayList(Collection c) { + elementData = c.toArray(); + if ((size = elementData.length) != 0) { + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } + } + +``` + +细心的同学一定会发现 :**以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10。** 下面在我们分析 ArrayList 扩容时会降到这一点内容! + +## 二 一步一步分析 ArrayList 扩容机制 + +这里以无参构造函数创建的 ArrayList 为例分析 + +### 1. 先来看 `add` 方法 + +```java + /** + * 将指定的元素追加到此列表的末尾。 + */ + public boolean add(E e) { + //添加元素之前,先调用ensureCapacityInternal方法 + ensureCapacityInternal(size + 1); // Increments modCount!! + //这里看到ArrayList添加元素的实质就相当于为数组赋值 + elementData[size++] = e; + return true; + } +``` +### 2. 再来看看 `ensureCapacityInternal()` 方法 + +可以看到 `add` 方法 首先调用了`ensureCapacityInternal(size + 1)` + +```java + //得到最小扩容量 + private void ensureCapacityInternal(int minCapacity) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + // 获取默认的容量和传入参数的较大值 + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + + ensureExplicitCapacity(minCapacity); + } +``` +**当 要 add 进第1个元素时,minCapacity为1,在Math.max()方法比较后,minCapacity 为10。** + +### 3. `ensureExplicitCapacity()` 方法 + +如果调用 `ensureCapacityInternal()` 方法就一定会进过(执行)这个方法,下面我们来研究一下这个方法的源码! + +```java + //判断是否需要扩容 + private void ensureExplicitCapacity(int minCapacity) { + modCount++; + + // overflow-conscious code + if (minCapacity - elementData.length > 0) + //调用grow方法进行扩容,调用此方法代表已经开始扩容了 + grow(minCapacity); + } + +``` + +我们来仔细分析一下: + +- 当我们要 add 进第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的 list),因为执行了 `ensureCapacityInternal()` 方法 ,所以 minCapacity 此时为10。此时,`minCapacity - elementData.length > 0 `成立,所以会进入 `grow(minCapacity)` 方法。 +- 当add第2个元素时,minCapacity 为2,此时e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,`minCapacity - elementData.length > 0 ` 不成立,所以不会进入 (执行)`grow(minCapacity)` 方法。 +- 添加第3、4···到第10个元素时,依然不会执行grow方法,数组容量都为10。 + +直到添加第11个元素,minCapacity(为11)比elementData.length(为10)要大。进入grow方法进行扩容。 + +### 4. `grow()` 方法 + +```java + /** + * 要分配的最大数组大小 + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * ArrayList扩容的核心方法。 + */ + private void grow(int minCapacity) { + // oldCapacity为旧容量,newCapacity为新容量 + int oldCapacity = elementData.length; + //将oldCapacity 右移一位,其效果相当于oldCapacity /2, + //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍, + int newCapacity = oldCapacity + (oldCapacity >> 1); + //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量, + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE, + //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。 + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + // minCapacity is usually close to size, so this is a win: + elementData = Arrays.copyOf(elementData, newCapacity); + } +``` + +**int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!** 记清楚了!不是网上很多人说的 1.5 倍+1! + +> ">>"(移位运算符):>>1 右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源   + +**我们再来通过例子探究一下`grow()` 方法 :** + +- 当add第1个元素时,oldCapacity 为0,经比较后第一个if判断成立,newCapacity = minCapacity(为10)。但是第二个if判断不会成立,即newCapacity 不比 MAX_ARRAY_SIZE大,则不会进入 `hugeCapacity` 方法。数组容量为10,add方法中 return true,size增为1。 +- 当add第11个元素进入grow方法时,newCapacity为15,比minCapacity(为11)大,第一个if判断不成立。新容量没有大于数组最大size,不会进入hugeCapacity方法。数组容量扩为15,add方法中return true,size增为11。 +- 以此类推······ + +**这里补充一点比较重要,但是容易被忽视掉的知识点:** + +- java 中的 `length `属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性. +- java 中的 `length()` 方法是针对字符串说的,如果想看这个字符串的长度则用到 `length()` 这个方法. +- java 中的 `size()` 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看! + +### 5. `hugeCapacity()` 方法。 + +从上面 `grow()` 方法源码我们知道: 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。 + + +```java + private static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); + //对minCapacity和MAX_ARRAY_SIZE进行比较 + //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小 + //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小 + //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } +``` + + + +## 三 `System.arraycopy()` 和 `Arrays.copyOf()`方法 + + +阅读源码的话,我们就会发现 ArrayList 中大量调用了这两个方法。比如:我们上面讲的扩容操作以及`add(int index, E element)`、`toArray()` 等方法中都用到了该方法! + + +### 3.1 `System.arraycopy()` 方法 + +```java + /** + * 在此列表中的指定位置插入指定的元素。 + *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; + *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 + */ + public void add(int index, E element) { + rangeCheckForAdd(index); + + ensureCapacityInternal(size + 1); // Increments modCount!! + //arraycopy()方法实现数组自己复制自己 + //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量; + System.arraycopy(elementData, index, elementData, index + 1, size - index); + elementData[index] = element; + size++; + } +``` + +我们写一个简单的方法测试以下: + +```java +public class ArraycopyTest { + + public static void main(String[] args) { + // TODO Auto-generated method stub + int[] a = new int[10]; + a[0] = 0; + a[1] = 1; + a[2] = 2; + a[3] = 3; + System.arraycopy(a, 2, a, 3, 3); + a[2]=99; + for (int i = 0; i < a.length; i++) { + System.out.println(a[i]); + } + } + +} +``` + +结果: + +``` +0 1 99 2 3 0 0 0 0 0 +``` + +### 3.2 `Arrays.copyOf()`方法 + +```java + /** + 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。 + */ + public Object[] toArray() { + //elementData:要复制的数组;size:要复制的长度 + return Arrays.copyOf(elementData, size); + } +``` + +个人觉得使用 `Arrays.copyOf()`方法主要是为了给原有数组扩容,测试代码如下: + +```java +public class ArrayscopyOfTest { + + public static void main(String[] args) { + int[] a = new int[3]; + a[0] = 0; + a[1] = 1; + a[2] = 2; + int[] b = Arrays.copyOf(a, 10); + System.out.println("b.length"+b.length); + } +} +``` + +结果: + +``` +10 +``` + + +### 3.3 两者联系和区别 + +**联系:** + +看两者源代码可以发现 copyOf() 内部实际调用了 `System.arraycopy()` 方法 + +**区别:** + +`arraycopy()` 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 `copyOf()` 是系统自动在内部新建一个数组,并返回该数组。 + + + +## 四 `ensureCapacity`方法 + +ArrayList 源码中有一个 `ensureCapacity` 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢? + +```java + /** + 如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。 + * + * @param minCapacity 所需的最小容量 + */ + public void ensureCapacity(int minCapacity) { + int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) + // any size if not default element table + ? 0 + // larger than default for default empty table. It's already + // supposed to be at default size. + : DEFAULT_CAPACITY; + + if (minCapacity > minExpand) { + ensureExplicitCapacity(minCapacity); + } + } + +``` + +**最好在 add 大量元素之前用 `ensureCapacity` 方法,以减少增量从新分配的次数** + +我们通过下面的代码实际测试以下这个方法的效果: + +```java +public class EnsureCapacityTest { + public static void main(String[] args) { + ArrayList list = new ArrayList(); + final int N = 10000000; + long startTime = System.currentTimeMillis(); + for (int i = 0; i < N; i++) { + list.add(i); + } + long endTime = System.currentTimeMillis(); + System.out.println("使用ensureCapacity方法前:"+(endTime - startTime)); + + list = new ArrayList(); + long startTime1 = System.currentTimeMillis(); + list.ensureCapacity(N); + for (int i = 0; i < N; i++) { + list.add(i); + } + long endTime1 = System.currentTimeMillis(); + System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1)); + } +} +``` + +运行结果: + +``` +使用ensureCapacity方法前:4637 +使用ensureCapacity方法前:241 + +``` + +通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量从新分配的次数 \ No newline at end of file From 03058e981f987cba338f83aefdeaa88f4ad78e91 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 18 Sep 2018 16:40:29 +0800 Subject: [PATCH 033/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0ArrayList-Grow?= =?UTF-8?q?=E4=B8=80=E6=96=87=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 460afcba073..ba17106f175 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ - ### Java 集合框架 - [这几道Java集合框架面试题几乎必问](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) - [Java 集合框架常见面试题总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java集合框架常见面试题总结.md) - - [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md) + - [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md) + - [【面试必备】透过源码角度一步一步带你分析 ArrayList 扩容机制](https://github.com/Snailclimb/Java-Guide/blob/master/ArrayList-Grow/ArrayList.md) - [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/LinkedList.md) - [HashMap(JDK1.8)源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/HashMap.md) From c1ce439f1b7c53ee3d284bfcf19d52e66a025bc0 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 18 Sep 2018 16:42:06 +0800 Subject: [PATCH 034/122] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ba17106f175..8ffaa2913b1 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ - [这几道Java集合框架面试题几乎必问](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) - [Java 集合框架常见面试题总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java集合框架常见面试题总结.md) - [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md) - - [【面试必备】透过源码角度一步一步带你分析 ArrayList 扩容机制](https://github.com/Snailclimb/Java-Guide/blob/master/ArrayList-Grow/ArrayList.md) + - [【面试必备】透过源码角度一步一步带你分析 ArrayList 扩容机制](https://github.com/Snailclimb/JavaGuide/blob/master/Java相关/ArrayList-Grow.md) - [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/LinkedList.md) - [HashMap(JDK1.8)源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/HashMap.md) From cec783261a93bf4e7ad842c9018fdf889a5ca65b Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Thu, 20 Sep 2018 13:36:12 +0800 Subject: [PATCH 035/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=93=BE=E8=A1=A8?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=AE=97=E6=B3=95=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Leetcode-LinkList1.md" | 392 ++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 "\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" new file mode 100644 index 00000000000..ecec28253ac --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" @@ -0,0 +1,392 @@ + +# 1. 两数相加 + +### 题目描述 + +> Leetcode:给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。 +> +>你可以假设除了数字 0 之外,这两个数字都不会以零开头。 + +示例: + +``` +输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) +输出:7 -> 0 -> 8 +原因:342 + 465 = 807 +``` + +### 问题分析 + +Leetcode官方详细解答地址: + + https://leetcode-cn.com/problems/add-two-numbers/solution/ + +> 要对头结点进行操作时,考虑创建哑节点dummy,使用dummy->next表示真正的头节点。这样可以避免处理头节点为空的边界问题。 + +我们使用变量来跟踪进位,并从包含最低有效位的表头开始模拟逐 +位相加的过程。 + +![图1,对两数相加方法的可视化: 342 + 465 = 807342+465=807, 每个结点都包含一个数字,并且数字按位逆序存储。](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/34910956.jpg) + +### Solution + +**我们首先从最低有效位也就是列表 l1和 l2 的表头开始相加。注意需要考虑到进位的情况!** + +```java +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { val = x; } + * } + */ + //https://leetcode-cn.com/problems/add-two-numbers/description/ +class Solution { +public ListNode addTwoNumbers(ListNode l1, ListNode l2) { + ListNode dummyHead = new ListNode(0); + ListNode p = l1, q = l2, curr = dummyHead; + //carry 表示进位数 + int carry = 0; + while (p != null || q != null) { + int x = (p != null) ? p.val : 0; + int y = (q != null) ? q.val : 0; + int sum = carry + x + y; + //进位数 + carry = sum / 10; + //新节点的数值为sum % 10 + curr.next = new ListNode(sum % 10); + curr = curr.next; + if (p != null) p = p.next; + if (q != null) q = q.next; + } + if (carry > 0) { + curr.next = new ListNode(carry); + } + return dummyHead.next; +} +} +``` + +# 2. 翻转链表 + + +### 题目描述 +> 剑指 offer:输入一个链表,反转链表后,输出链表的所有元素。 + +![翻转链表](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/81431871.jpg) + +### 问题分析 + +这道算法题,说直白点就是:如何让后一个节点指向前一个节点!在下面的代码中定义了一个 next 节点,该节点主要是保存要反转到头的那个节点,防止链表 “断裂”。 + +### Solution + + +```java +public class ListNode { + int val; + ListNode next = null; + + ListNode(int val) { + this.val = val; + } +} +``` + +```java +/** + * + * @author Snailclimb + * @date 2018年9月19日 + * @Description: TODO + */ +public class Solution { + + public ListNode ReverseList(ListNode head) { + + ListNode next = null; + ListNode pre = null; + + while (head != null) { + // 保存要反转到头的那个节点 + next = head.next; + // 要反转的那个节点指向已经反转的上一个节点(备注:第一次反转的时候会指向null) + head.next = pre; + // 上一个已经反转到头部的节点 + pre = head; + // 一直向链表尾走 + head = next; + } + return pre; + } + +} +``` + +测试方法: + +```java + public static void main(String[] args) { + + ListNode a = new ListNode(1); + ListNode b = new ListNode(2); + ListNode c = new ListNode(3); + ListNode d = new ListNode(4); + ListNode e = new ListNode(5); + a.next = b; + b.next = c; + c.next = d; + d.next = e; + new Solution().ReverseList(a); + while (e != null) { + System.out.println(e.val); + e = e.next; + } + } +``` + +输出: + +``` +5 +4 +3 +2 +1 +``` + +# 3. 链表中倒数第k个节点 + +### 题目描述 + +> 剑指offer: 输入一个链表,输出该链表中倒数第k个结点。 + +### 问题分析 + +首先两个节点/指针,一个节点 node1 先开始跑,指针 node1 跑到 k-1 个节点后,另一个节点 node2 开始跑,当 node1 跑到最后时,node2 所指的节点就是倒数第k个节点。 + + +### Solution + +```java +/* +public class ListNode { + int val; + ListNode next = null; + + ListNode(int val) { + this.val = val; + } +}*/ + +//时间复杂度O(n),一次遍历即可 +//https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking +public class Solution { + public ListNode FindKthToTail(ListNode head,int k) { + //如果链表为空或者k小于等于0 + if(head==null||k<=0){ + return null; + } + // 声明两个指向头结点的节点 + ListNode node1 = head, node2 = head; + //记录节点的个数 + int count=0; + //记录k值,后面要使用 + int index=k; + //p指针先跑,并且记录节点数,当node1节点跑了k-1个节点后,node2节点开始跑, + //当node1节点跑到最后时,node2节点所指的节点就是倒数第k个节点 + while(node1!=null){ + node1=node1.next; + count++; + if(k<1){ + node2=node2.next; + } + k--; + } + //如果节点个数小于所求的倒数第k个节点,则返回空 + if(count Leetcode:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 + +**示例:** + +``` +给定一个链表: 1->2->3->4->5, 和 n = 2. + +当删除了倒数第二个节点后,链表变为 1->2->3->5. + +``` + +**说明:** + +给定的 n 保证是有效的。 + +**进阶:** + +你能尝试使用一趟扫描实现吗? + +该题在 leetcode 上有详细解答,具体可参考 Leetcode. + +### 问题分析 + +我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 LL 是列表的长度。只要我们找到列表的长度 L,这个问题就很容易解决。 + +![图 1. 删除列表中的第 L - n + 1 个元素](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/94354387.jpg) + +### Solution + +**两次遍历法** + +首先我们将添加一个 **哑结点** 作为辅助,该结点位于列表头部。哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部。在第一次遍历中,我们找出列表的长度 L。然后设置一个指向哑结点的指针,并移动它遍历列表,直至它到达第 (L - n) 个结点那里。我们把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点,完成这个算法。 + +```java +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { val = x; } + * } + */ +// https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/description/ +public class Solution { + public ListNode removeNthFromEnd(ListNode head, int n) { + // 哑结点,哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部 + ListNode dummy = new ListNode(0); + // 哑结点指向头结点 + dummy.next = head; + // 保存链表长度 + int length = 0; + ListNode len = head; + while (len != null) { + length++; + len = len.next; + } + length = length - n; + ListNode target = dummy; + // 找到 L-n 位置的节点 + while (length > 0) { + target = target.next; + length--; + } + // 把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点 + target.next = target.next.next; + return dummy.next; + } +} +``` + +复杂度分析: + +- 时间复杂度 O(L) :该算法对列表进行了两次遍历,首先计算了列表的长度 LL 其次找到第 (L - n)(L−n) 个结点。 操作执行了 2L-n2L−n 步,时间复杂度为 O(L)O(L)。 + +- 空间复杂度 O(1) :我们只用了常量级的额外空间。 + + + +**进阶——一次遍历法:** + +其实这种方法就和我们上面第四题找“链表中倒数第k个节点”所用的思想是一样的。**基本思路就是:** 定义两个节点 node1、node2;node1 节点先跑,node1节点 跑到第 n+1 个节点的时候,node2 节点开始跑.当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点(L代表总链表长度,也就是倒数第 n+1 个节点) + +```java +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { val = x; } + * } + */ +public class Solution { + public ListNode removeNthFromEnd(ListNode head, int n) { + + ListNode dummy = new ListNode(0); + dummy.next = head; + // 声明两个指向头结点的节点 + ListNode node1 = dummy, node2 = dummy; + + // node1 节点先跑,node1节点 跑到第 n+1 个节点的时候,node2 节点开始跑 + //当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点(L代表总链表长度,也就是倒数第 n+1 个节点) + while (node1 != null) { + node1 = node1.next; + if (n < 0) { + node2 = node2.next; + } + n--; + } + + node2.next = node2.next.next; + + return dummy.next; + + } +} +``` + + + + + +# 5. 合并两个排序的链表 + +### 题目描述 + +> 剑指offer:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 + +### 问题分析 + +我们可以这样分析: + +1. 假设我们有两个链表 A,B; +2. A的头节点A1的值与B的头结点B1的值比较,假设A1小,则A1为头节点; +3. A2再和B1比较,假设B1小,则,A1指向B1; +4. A2再和B2比较 +就这样循环往复就行了,应该还算好理解。 + +考虑通过递归的方式实现! + +### Solution + +**递归版本:** + +```java +/* +public class ListNode { + int val; + ListNode next = null; + + ListNode(int val) { + this.val = val; + } +}*/ +//https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking +public class Solution { +public ListNode Merge(ListNode list1,ListNode list2) { + if(list1 == null){ + return list2; + } + if(list2 == null){ + return list1; + } + if(list1.val <= list2.val){ + list1.next = Merge(list1.next, list2); + return list1; + }else{ + list2.next = Merge(list1, list2.next); + return list2; + } + } +} +``` + + From 6d156a72c5251abd28276035bb70ee73b282d813 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 20 Sep 2018 13:38:57 +0800 Subject: [PATCH 036/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=93=BE=E8=A1=A8?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=AE=97=E6=B3=95=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ffaa2913b1..76a01fd03fa 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,8 @@ - [算法学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/算法.md) - [常见安全算法(MD5、SHA1、Base64等等)总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/常见安全算法(MD5、SHA1、Base64等等)总结.md) - - [搞定 BAT 面试——几道常见的子符串算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/搞定BAT面试——几道常见的子符串算法题.md) + - [算法总结——几道常见的子符串算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/搞定BAT面试——几道常见的子符串算法题.md) + - [算法总结——几道常见的链表算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/Leetcode-LinkList1.md) ## :computer: 计算机网络与数据通信 - ### 网络相关 From 3481546300e72171f4abec3148f559cb5960845e Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Thu, 20 Sep 2018 14:55:42 +0800 Subject: [PATCH 037/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=92=8C=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E9=83=A8=E5=88=86=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Leetcode-LinkList1.md" | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" index ecec28253ac..096937f5be0 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" @@ -164,7 +164,9 @@ public class Solution { ### 问题分析 -首先两个节点/指针,一个节点 node1 先开始跑,指针 node1 跑到 k-1 个节点后,另一个节点 node2 开始跑,当 node1 跑到最后时,node2 所指的节点就是倒数第k个节点。 +> **链表中倒数第k个节点也就是正数第(L-K+1)个节点,知道了只一点,这一题基本就没问题!** + +首先两个节点/指针,一个节点 node1 先开始跑,指针 node1 跑到 k-1 个节点后,另一个节点 node2 开始跑,当 node1 跑到最后时,node2 所指的节点就是倒数第k个节点也就是正数第(L-K+1)个节点。 ### Solution @@ -180,42 +182,43 @@ public class ListNode { } }*/ -//时间复杂度O(n),一次遍历即可 -//https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking +// 时间复杂度O(n),一次遍历即可 +// https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking public class Solution { - public ListNode FindKthToTail(ListNode head,int k) { - //如果链表为空或者k小于等于0 - if(head==null||k<=0){ - return null; - } + public ListNode FindKthToTail(ListNode head, int k) { + // 如果链表为空或者k小于等于0 + if (head == null || k <= 0) { + return null; + } // 声明两个指向头结点的节点 ListNode node1 = head, node2 = head; - //记录节点的个数 - int count=0; - //记录k值,后面要使用 - int index=k; - //p指针先跑,并且记录节点数,当node1节点跑了k-1个节点后,node2节点开始跑, - //当node1节点跑到最后时,node2节点所指的节点就是倒数第k个节点 - while(node1!=null){ - node1=node1.next; - count++; - if(k<1){ - node2=node2.next; - } - k--; - } - //如果节点个数小于所求的倒数第k个节点,则返回空 - if(count Leetcode:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 **示例:** @@ -239,7 +242,8 @@ public class Solution { ### 问题分析 -我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 LL 是列表的长度。只要我们找到列表的长度 L,这个问题就很容易解决。 + +我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 L是列表的长度。只要我们找到列表的长度 L,这个问题就很容易解决。 ![图 1. 删除列表中的第 L - n + 1 个元素](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/94354387.jpg) @@ -247,7 +251,7 @@ public class Solution { **两次遍历法** -首先我们将添加一个 **哑结点** 作为辅助,该结点位于列表头部。哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部。在第一次遍历中,我们找出列表的长度 L。然后设置一个指向哑结点的指针,并移动它遍历列表,直至它到达第 (L - n) 个结点那里。我们把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点,完成这个算法。 +首先我们将添加一个 **哑结点** 作为辅助,该结点位于列表头部。哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部。在第一次遍历中,我们找出列表的长度 L。然后设置一个指向哑结点的指针,并移动它遍历列表,直至它到达第 (L - n) 个结点那里。**我们把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点,完成这个算法。** ```java /** @@ -286,16 +290,18 @@ public class Solution { } ``` -复杂度分析: +**复杂度分析:** -- 时间复杂度 O(L) :该算法对列表进行了两次遍历,首先计算了列表的长度 LL 其次找到第 (L - n)(L−n) 个结点。 操作执行了 2L-n2L−n 步,时间复杂度为 O(L)O(L)。 - -- 空间复杂度 O(1) :我们只用了常量级的额外空间。 +- **时间复杂度 O(L)** :该算法对列表进行了两次遍历,首先计算了列表的长度 LL 其次找到第 (L - n)(L−n) 个结点。 操作执行了 2L-n2L−n 步,时间复杂度为 O(L)O(L)。 +- **空间复杂度 O(1)** :我们只用了常量级的额外空间。 **进阶——一次遍历法:** + +> **链表中倒数第N个节点也就是正数第(L-N+1)个节点。 + 其实这种方法就和我们上面第四题找“链表中倒数第k个节点”所用的思想是一样的。**基本思路就是:** 定义两个节点 node1、node2;node1 节点先跑,node1节点 跑到第 n+1 个节点的时候,node2 节点开始跑.当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点(L代表总链表长度,也就是倒数第 n+1 个节点) ```java @@ -315,17 +321,17 @@ public class Solution { // 声明两个指向头结点的节点 ListNode node1 = dummy, node2 = dummy; - // node1 节点先跑,node1节点 跑到第 n+1 个节点的时候,node2 节点开始跑 - //当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点(L代表总链表长度,也就是倒数第 n+1 个节点) + // node1 节点先跑,node1节点 跑到第 n 个节点的时候,node2 节点开始跑 + // 当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点,也就是倒数第 n+1(L代表总链表长度) while (node1 != null) { node1 = node1.next; - if (n < 0) { + if (n < 1 && node1 != null) { node2 = node2.next; } n--; } - node2.next = node2.next.next; + node2.next = node2.next.next; return dummy.next; @@ -389,4 +395,3 @@ public ListNode Merge(ListNode list1,ListNode list2) { } ``` - From 2806b3e6b949fc0942bf76a80e0cab847c867ba0 Mon Sep 17 00:00:00 2001 From: chenjiayang Date: Thu, 20 Sep 2018 15:17:43 +0800 Subject: [PATCH 038/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=85=B3=E4=BA=8Ered?= =?UTF-8?q?lock=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=86=85=E5=AE=B9,=E8=B0=83=E6=95=B4=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=8C=85=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Redis/Redis.md" | 0 ...06\345\270\203\345\274\217\351\224\201.md" | 50 ++++++++++ ...04\345\217\257\350\241\214\344\271\210.md" | 94 +++++++++++++++++++ 3 files changed, 144 insertions(+) rename "\346\225\260\346\215\256\345\255\230\345\202\250/Redis.md" => "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" (100%) create mode 100644 "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" create mode 100644 "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" similarity index 100% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis.md" rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" new file mode 100644 index 00000000000..6a697a0adf8 --- /dev/null +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -0,0 +1,50 @@ +这篇文章主要是对 Redis 官方网站刊登的 [Distributed locks with Redis](https://redis.io/topics/distlock) 部分内容的总结和翻译。 + +## 什么是 RedLock + +Redis 官方站这篇文章提出了一种权威的基于 Redis 实现分布式锁的方式名叫 *Redlock*,此种方式比原先的单节点的方法更安全。它可以保证以下特性: + +1. 安全特性:互斥访问,即永远只有一个 client 能拿到锁 +2. 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区 +3. 容错性:只要大部分 Redis 节点存活就可以正常提供服务 + +## 怎么在单节点上实现分布式锁 + +> SET resource_name my_random_value NX PX 30000 + +主要依靠上述命令,该命令仅当 Key 不存在时(NX保证)set 值,并且设置过期时间 3000ms (PX保证),值 my_random_value 必须是所有 client 和所有锁请求发生期间唯一的,释放锁的逻辑是: + +```lua +if redis.call("get",KEYS[1]) == ARGV[1] then + return redis.call("del",KEYS[1]) +else + return 0 +end +``` + +上述实现可以避免释放另一个client创建的锁,如果只有 del 命令的话,那么如果 client1 拿到 lock1 之后因为某些操作阻塞了很长时间,此时 Redis 端 lock1 已经过期了并且已经被重新分配给了 client2,那么 client1 此时再去释放这把锁就会造成 client2 原本获取到的锁被 client1 无故释放了,但现在为每个 client 分配一个 unique 的 string 值可以避免这个问题。至于如何去生成这个 unique string,方法很多随意选择一种就行了。 + +## Redlock 算法 + +算法很易懂,起 5 个 master 节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作: + +1. 得到当前的时间,微妙单位 +2. 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value,这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间 +3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候,且它会计算申请锁消耗了多少时间,这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到,如果锁的持续时长(lock validity time)比流逝的时间多的话,那么锁就真正获取到了。 +4. 如果锁申请到了,那么锁真正的 lock validity time 应该是 origin(lock validity time) - 申请锁期间流逝的时间 +5. 如果 client 申请锁失败了,那么它就会在少部分申请成功锁的 master 节点上执行释放锁的操作,重置状态 + +## 失败重试 + +如果一个 client 申请锁失败了,那么它需要稍等一会在重试避免多个 client 同时申请锁的情况,最好的情况是一个 client 需要几乎同时向 5 个 master 发起锁申请。另外就是如果 client 申请锁失败了它需要尽快在它曾经申请到锁的 master 上执行 unlock 操作,便于其他 client 获得这把锁,避免这些锁过期造成的时间浪费,当然如果这时候网络分区使得 client 无法联系上这些 master,那么这种浪费就是不得不付出的代价了。 + +## 放锁 + +放锁操作很简单,就是依次释放所有节点上的锁就行了 + +## 性能、崩溃恢复和 fsync + +如果我们的节点没有持久化机制,client 从 5 个 master 中的 3 个处获得了锁,然后其中一个重启了,这是注意 **整个环境中又出现了 3 个 master 可供另一个 client 申请同一把锁!** 违反了互斥性。如果我们开启了 AOF 持久化那么情况会稍微好转一些,因为 Redis 的过期机制是语义层面实现的,所以在 server 挂了的时候时间依旧在流逝,重启之后锁状态不会受到污染。但是考虑断电之后呢,AOF部分命令没来得及刷回磁盘直接丢失了,除非我们配置刷回策略为 fsnyc = always,但这会损伤性能。解决这个问题的方法是,当一个节点重启之后,我们规定在 max TTL 期间它是不可用的,这样它就不会干扰原本已经申请到的锁,等到它 crash 前的那部分锁都过期了,环境不存在历史锁了,那么再把这个节点加进来正常工作。 + +## License +* 原文 [基于 Redis 的分布式锁 Redlock](https://zhuanlan.zhihu.com/p/40915772) \ No newline at end of file diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" new file mode 100644 index 00000000000..0a3fcc4ced3 --- /dev/null +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" @@ -0,0 +1,94 @@ +本文是对 [Martin Kleppmann](https://martin.kleppmann.com/) 的文章 [How to do distributed locking](https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html) 部分内容的翻译和总结,上次写 Redlock 的原因就是看到了 Martin 的这篇文章,写得很好,特此翻译和总结。感兴趣的同学可以翻看原文,相信会收获良多。 + +开篇作者认为现在 Redis 逐渐被使用到数据管理领域,这个领域需要更强的数据一致性和耐久性,这使得他感到担心,因为这不是 Redis 最初设计的初衷(事实上这也是很多业界程序员的误区,越来越把 Redis 当成数据库在使用),其中基于 Redis 的分布式锁就是令人担心的其一。 + +Martin 指出首先你要明确你为什么使用分布式锁,为了性能还是正确性?为了帮你区分这二者,在这把锁 fail 了的时候你可以询问自己以下问题: +1. **要性能的:**拥有这把锁使得你不会重复劳动(例如一个 job 做了两次),如果这把锁 fail 了,两个节点同时做了这个 Job,那么这个 Job 增加了你的成本。 +2. **要正确性的:**拥有锁可以防止并发操作污染你的系统或者数据,如果这把锁 fail 了两个节点同时操作了一份数据,结果可能是数据不一致、数据丢失、file 冲突等,会导致严重的后果。 + +上述二者都是需求锁的正确场景,但是你必须清楚自己是因为什么原因需要分布式锁。 + +如果你只是为了性能,那没必要用 Redlock,它成本高且复杂,你只用一个 Redis 实例也够了,最多加个从防止主挂了。当然,你使用单节点的 Redis 那么断电或者一些情况下,你会丢失锁,但是你的目的只是加速性能且断电这种事情不会经常发生,这并不是什么大问题。并且如果你使用了单节点 Redis,那么很显然你这个应用需要的锁粒度是很模糊粗糙的,也不会是什么重要的服务。 + +那么是否 Redlock 对于要求正确性的场景就合适呢?Martin 列举了若干场景证明 Redlock 这种算法是不可靠的。 + +## 用锁保护资源 +这节里 Martin 先将 Redlock 放在了一边而是仅讨论总体上一个分布式锁是怎么工作的。在分布式环境下,锁比 mutex 这类复杂,因为涉及到不同节点、网络通信并且他们随时可能无征兆的 fail 。 +Martin 假设了一个场景,一个 client 要修改一个文件,它先申请得到锁,然后修改文件写回,放锁。另一个 client 再申请锁 ... 代码流程如下: + +```java +// THIS CODE IS BROKEN +function writeData(filename, data) { + var lock = lockService.acquireLock(filename); + if (!lock) { + throw 'Failed to acquire lock'; + } + + try { + var file = storage.readFile(filename); + var updated = updateContents(file, data); + storage.writeFile(filename, updated); + } finally { + lock.release(); + } +} +``` + +可惜即使你的锁服务非常完美,上述代码还是可能跪,下面的流程图会告诉你为什么: + +![](https://martin.kleppmann.com/2016/02/unsafe-lock.png) + +上述图中,得到锁的 client1 在持有锁的期间 pause 了一段时间,例如 GC 停顿。锁有过期时间(一般叫租约,为了防止某个 client 崩溃之后一直占有锁),但是如果 GC 停顿太长超过了锁租约时间,此时锁已经被另一个 client2 所得到,原先的 client1 还没有感知到锁过期,那么奇怪的结果就会发生,曾经 HBase 就发生过这种 Bug。即使你在 client1 写回之前检查一下锁是否过期也无助于解决这个问题,因为 GC 可能在任何时候发生,即使是你非常不便的时候(在最后的检查与写操作期间)。 +如果你认为自己的程序不会有长时间的 GC 停顿,还有其他原因会导致你的进程 pause。例如进程可能读取尚未进入内存的数据,所以它得到一个 page fault 并且等待 page 被加载进缓存;还有可能你依赖于网络服务;或者其他进程占用 CPU;或者其他人意外发生 SIGSTOP 等。 + +... .... 这里 Martin 又增加了一节列举各种进程 pause 的例子,为了证明上面的代码是不安全的,无论你的锁服务多完美。 + +## 使用 Fencing (栅栏)使得锁变安全 +修复问题的方法也很简单:你需要在每次写操作时加入一个 fencing token。这个场景下,fencing token 可以是一个递增的数字(lock service 可以做到),每次有 client 申请锁就递增一次: + +![](https://martin.kleppmann.com/2016/02/fencing-tokens.png) + +client1 申请锁同时拿到 token33,然后它进入长时间的停顿锁也过期了。client2 得到锁和 token34 写入数据,紧接着 client1 活过来之后尝试写入数据,自身 token33 比 34 小因此写入操作被拒绝。注意这需要存储层来检查 token,但这并不难实现。如果你使用 Zookeeper 作为 lock service 的话那么你可以使用 zxid 作为递增数字。 +但是对于 Redlock 你要知道,没什么生成 fencing token 的方式,并且怎么修改 Redlock 算法使其能产生 fencing token 呢?好像并不那么显而易见。因为产生 token 需要单调递增,除非在单节点 Redis 上完成但是这又没有高可靠性,你好像需要引进一致性协议来让 Redlock 产生可靠的 fencing token。 + +## 使用时间来解决一致性 +Redlock 无法产生 fencing token 早该成为在需求正确性的场景下弃用它的理由,但还有一些值得讨论的地方。 + +学术界有个说法,算法对时间不做假设:因为进程可能pause一段时间、数据包可能因为网络延迟延后到达、时钟可能根本就是错的。而可靠的算法依旧要在上述假设下做正确的事情。 + +对于 failure detector 来说,timeout 只能作为猜测某个节点 fail 的依据,因为网络延迟、本地时钟不正确等其他原因的限制。考虑到 Redis 使用 gettimeofday,而不是单调的时钟,会受到系统时间的影响,可能会突然前进或者后退一段时间,这会导致一个 key 更快或更慢地过期。 + +可见,Redlock 依赖于许多时间假设,它假设所有 Redis 节点都能对同一个 Key 在其过期前持有差不多的时间、跟过期时间相比网络延迟很小、跟过期时间相比进程 pause 很短。 + +## 用不可靠的时间打破 Redlock +这节 Martin 举了个因为时间问题,Redlock 不可靠的例子。 + +1. client1 从 ABC 三个节点处申请到锁,DE由于网络原因请求没有到达 +2. C节点的时钟往前推了,导致 lock 过期 +3. client2 在CDE处获得了锁,AB由于网络原因请求未到达 +4. 此时 client1 和 client2 都获得了锁 + +**在 Redlock 官方文档中也提到了这个情况,不过是C崩溃的时候,Redlock 官方本身也是知道 Redlock 算法不是完全可靠的,官方为了解决这种问题建议使用延时启动,相关内容可以看之前的[这篇文章](https://zhuanlan.zhihu.com/p/40915772)。但是 Martin 这里分析得更加全面,指出延时启动不也是依赖于时钟的正确性的么?** + +接下来 Martin 又列举了进程 Pause 时而不是时钟不可靠时会发生的问题: + +1. client1 从 ABCDE 处获得了锁 +2. 当获得锁的 response 还没到达 client1 时 client1 进入 GC 停顿 +3. 停顿期间锁已经过期了 +4. client2 在 ABCDE 处获得了锁 +5. client1 GC 完成收到了获得锁的 response,此时两个 client 又拿到了同一把锁 + +**同时长时间的网络延迟也有可能导致同样的问题。** + +## Redlock 的同步性假设 +这些例子说明了,仅有在你假设了一个同步性系统模型的基础上,Redlock 才能正常工作,也就是系统能满足以下属性: + +1. 网络延时边界,即假设数据包一定能在某个最大延时之内到达 +2. 进程停顿边界,即进程停顿一定在某个最大时间之内 +3. 时钟错误边界,即不会从一个坏的 NTP 服务器处取得时间 + +## 结论 +Martin 认为 Redlock 实在不是一个好的选择,对于需求性能的分布式锁应用它太重了且成本高;对于需求正确性的应用来说它不够安全。因为它对高危的时钟或者说其他上述列举的情况进行了不可靠的假设,如果你的应用只需要高性能的分布式锁不要求多高的正确性,那么单节点 Redis 够了;如果你的应用想要保住正确性,那么不建议 Redlock,建议使用一个合适的一致性协调系统,例如 Zookeeper,且保证存在 fencing token。 + +## License +* 原文 [怎样做可靠的分布式锁,Redlock 真的可行么?](https://zhuanlan.zhihu.com/p/41327417) \ No newline at end of file From 757470eeccccc40726ffc9605367f1fefd3aabb0 Mon Sep 17 00:00:00 2001 From: jiayangchen Date: Thu, 20 Sep 2018 15:20:24 +0800 Subject: [PATCH 039/122] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=8C=85=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename "\346\225\260\346\215\256\345\255\230\345\202\250/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" => "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" (100%) diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" similarity index 100% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" From 227811711e733df3f40b30b0feb24c47187e99ac Mon Sep 17 00:00:00 2001 From: Young Chen <731463669@qq.com> Date: Thu, 20 Sep 2018 15:29:33 +0800 Subject: [PATCH 040/122] =?UTF-8?q?Update=20=E5=A6=82=E4=BD=95=E5=81=9A?= =?UTF-8?q?=E5=8F=AF=E9=9D=A0=E7=9A=84=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81?= =?UTF-8?q?=EF=BC=8CRedlock=E7=9C=9F=E7=9A=84=E5=8F=AF=E8=A1=8C=E4=B9=88.m?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\237\347\232\204\345\217\257\350\241\214\344\271\210.md" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" index 0a3fcc4ced3..2fc83e5fe44 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" @@ -3,8 +3,8 @@ 开篇作者认为现在 Redis 逐渐被使用到数据管理领域,这个领域需要更强的数据一致性和耐久性,这使得他感到担心,因为这不是 Redis 最初设计的初衷(事实上这也是很多业界程序员的误区,越来越把 Redis 当成数据库在使用),其中基于 Redis 的分布式锁就是令人担心的其一。 Martin 指出首先你要明确你为什么使用分布式锁,为了性能还是正确性?为了帮你区分这二者,在这把锁 fail 了的时候你可以询问自己以下问题: -1. **要性能的:**拥有这把锁使得你不会重复劳动(例如一个 job 做了两次),如果这把锁 fail 了,两个节点同时做了这个 Job,那么这个 Job 增加了你的成本。 -2. **要正确性的:**拥有锁可以防止并发操作污染你的系统或者数据,如果这把锁 fail 了两个节点同时操作了一份数据,结果可能是数据不一致、数据丢失、file 冲突等,会导致严重的后果。 +1. **要性能的:** 拥有这把锁使得你不会重复劳动(例如一个 job 做了两次),如果这把锁 fail 了,两个节点同时做了这个 Job,那么这个 Job 增加了你的成本。 +2. **要正确性的:** 拥有锁可以防止并发操作污染你的系统或者数据,如果这把锁 fail 了两个节点同时操作了一份数据,结果可能是数据不一致、数据丢失、file 冲突等,会导致严重的后果。 上述二者都是需求锁的正确场景,但是你必须清楚自己是因为什么原因需要分布式锁。 @@ -91,4 +91,4 @@ Redlock 无法产生 fencing token 早该成为在需求正确性的场景下弃 Martin 认为 Redlock 实在不是一个好的选择,对于需求性能的分布式锁应用它太重了且成本高;对于需求正确性的应用来说它不够安全。因为它对高危的时钟或者说其他上述列举的情况进行了不可靠的假设,如果你的应用只需要高性能的分布式锁不要求多高的正确性,那么单节点 Redis 够了;如果你的应用想要保住正确性,那么不建议 Redlock,建议使用一个合适的一致性协调系统,例如 Zookeeper,且保证存在 fencing token。 ## License -* 原文 [怎样做可靠的分布式锁,Redlock 真的可行么?](https://zhuanlan.zhihu.com/p/41327417) \ No newline at end of file +* 原文 [怎样做可靠的分布式锁,Redlock 真的可行么?](https://zhuanlan.zhihu.com/p/41327417) From 36196cec6f65628e93dd200fd5f2a09cec89eb2a Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 20 Sep 2018 15:29:51 +0800 Subject: [PATCH 041/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Leetcode-LinkList1.md" | 238 ++++++++++-------- 1 file changed, 131 insertions(+), 107 deletions(-) diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" index 096937f5be0..79b74441deb 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" @@ -1,3 +1,27 @@ + + +- [1. 两数相加](#1-两数相加) + - [题目描述](#题目描述) + - [问题分析](#问题分析) + - [Solution](#solution) +- [2. 翻转链表](#2-翻转链表) + - [题目描述](#题目描述-1) + - [问题分析](#问题分析-1) + - [Solution](#solution-1) +- [3. 链表中倒数第k个节点](#3-链表中倒数第k个节点) + - [题目描述](#题目描述-2) + - [问题分析](#问题分析-2) + - [Solution](#solution-2) +- [4. 删除链表的倒数第N个节点](#4-删除链表的倒数第n个节点) + - [问题分析](#问题分析-3) + - [Solution](#solution-3) +- [5. 合并两个排序的链表](#5-合并两个排序的链表) + - [题目描述](#题目描述-3) + - [问题分析](#问题分析-4) + - [Solution](#solution-4) + + + # 1. 两数相加 @@ -85,12 +109,12 @@ public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ```java public class ListNode { - int val; - ListNode next = null; + int val; + ListNode next = null; - ListNode(int val) { - this.val = val; - } + ListNode(int val) { + this.val = val; + } } ``` @@ -103,23 +127,23 @@ public class ListNode { */ public class Solution { - public ListNode ReverseList(ListNode head) { - - ListNode next = null; - ListNode pre = null; + public ListNode ReverseList(ListNode head) { - while (head != null) { - // 保存要反转到头的那个节点 - next = head.next; - // 要反转的那个节点指向已经反转的上一个节点(备注:第一次反转的时候会指向null) - head.next = pre; - // 上一个已经反转到头部的节点 - pre = head; - // 一直向链表尾走 - head = next; - } - return pre; - } + ListNode next = null; + ListNode pre = null; + + while (head != null) { + // 保存要反转到头的那个节点 + next = head.next; + // 要反转的那个节点指向已经反转的上一个节点(备注:第一次反转的时候会指向null) + head.next = pre; + // 上一个已经反转到头部的节点 + pre = head; + // 一直向链表尾走 + head = next; + } + return pre; + } } ``` @@ -127,23 +151,23 @@ public class Solution { 测试方法: ```java - public static void main(String[] args) { - - ListNode a = new ListNode(1); - ListNode b = new ListNode(2); - ListNode c = new ListNode(3); - ListNode d = new ListNode(4); - ListNode e = new ListNode(5); - a.next = b; - b.next = c; - c.next = d; - d.next = e; - new Solution().ReverseList(a); - while (e != null) { - System.out.println(e.val); - e = e.next; - } - } + public static void main(String[] args) { + + ListNode a = new ListNode(1); + ListNode b = new ListNode(2); + ListNode c = new ListNode(3); + ListNode d = new ListNode(4); + ListNode e = new ListNode(5); + a.next = b; + b.next = c; + c.next = d; + d.next = e; + new Solution().ReverseList(a); + while (e != null) { + System.out.println(e.val); + e = e.next; + } + } ``` 输出: @@ -185,33 +209,33 @@ public class ListNode { // 时间复杂度O(n),一次遍历即可 // https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking public class Solution { - public ListNode FindKthToTail(ListNode head, int k) { - // 如果链表为空或者k小于等于0 - if (head == null || k <= 0) { - return null; - } - // 声明两个指向头结点的节点 - ListNode node1 = head, node2 = head; - // 记录节点的个数 - int count = 0; - // 记录k值,后面要使用 - int index = k; - // p指针先跑,并且记录节点数,当node1节点跑了k-1个节点后,node2节点开始跑, - // 当node1节点跑到最后时,node2节点所指的节点就是倒数第k个节点 - while (node1 != null) { - node1 = node1.next; - count++; - if (k < 1 && node1 != null) { - node2 = node2.next; - } - k--; - } - // 如果节点个数小于所求的倒数第k个节点,则返回空 - if (count < index) - return null; - return node2; - - } + public ListNode FindKthToTail(ListNode head, int k) { + // 如果链表为空或者k小于等于0 + if (head == null || k <= 0) { + return null; + } + // 声明两个指向头结点的节点 + ListNode node1 = head, node2 = head; + // 记录节点的个数 + int count = 0; + // 记录k值,后面要使用 + int index = k; + // p指针先跑,并且记录节点数,当node1节点跑了k-1个节点后,node2节点开始跑, + // 当node1节点跑到最后时,node2节点所指的节点就是倒数第k个节点 + while (node1 != null) { + node1 = node1.next; + count++; + if (k < 1 && node1 != null) { + node2 = node2.next; + } + k--; + } + // 如果节点个数小于所求的倒数第k个节点,则返回空 + if (count < index) + return null; + return node2; + + } } ``` @@ -264,29 +288,29 @@ public class Solution { */ // https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/description/ public class Solution { - public ListNode removeNthFromEnd(ListNode head, int n) { - // 哑结点,哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部 - ListNode dummy = new ListNode(0); - // 哑结点指向头结点 - dummy.next = head; - // 保存链表长度 - int length = 0; - ListNode len = head; - while (len != null) { - length++; - len = len.next; - } - length = length - n; - ListNode target = dummy; - // 找到 L-n 位置的节点 - while (length > 0) { - target = target.next; - length--; - } - // 把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点 - target.next = target.next.next; - return dummy.next; - } + public ListNode removeNthFromEnd(ListNode head, int n) { + // 哑结点,哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部 + ListNode dummy = new ListNode(0); + // 哑结点指向头结点 + dummy.next = head; + // 保存链表长度 + int length = 0; + ListNode len = head; + while (len != null) { + length++; + len = len.next; + } + length = length - n; + ListNode target = dummy; + // 找到 L-n 位置的节点 + while (length > 0) { + target = target.next; + length--; + } + // 把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点 + target.next = target.next.next; + return dummy.next; + } } ``` @@ -314,28 +338,28 @@ public class Solution { * } */ public class Solution { - public ListNode removeNthFromEnd(ListNode head, int n) { - - ListNode dummy = new ListNode(0); - dummy.next = head; - // 声明两个指向头结点的节点 - ListNode node1 = dummy, node2 = dummy; - - // node1 节点先跑,node1节点 跑到第 n 个节点的时候,node2 节点开始跑 - // 当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点,也就是倒数第 n+1(L代表总链表长度) - while (node1 != null) { - node1 = node1.next; - if (n < 1 && node1 != null) { - node2 = node2.next; - } - n--; - } + public ListNode removeNthFromEnd(ListNode head, int n) { + + ListNode dummy = new ListNode(0); + dummy.next = head; + // 声明两个指向头结点的节点 + ListNode node1 = dummy, node2 = dummy; + + // node1 节点先跑,node1节点 跑到第 n 个节点的时候,node2 节点开始跑 + // 当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点,也就是倒数第 n+1(L代表总链表长度) + while (node1 != null) { + node1 = node1.next; + if (n < 1 && node1 != null) { + node2 = node2.next; + } + n--; + } - node2.next = node2.next.next; + node2.next = node2.next.next; - return dummy.next; + return dummy.next; - } + } } ``` From dc215294327b745091382b92b7a88e6cfa54ddd0 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 20 Sep 2018 15:30:38 +0800 Subject: [PATCH 042/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...62\347\256\227\346\263\225\351\242\230.md" | 343 +++++++++--------- 1 file changed, 180 insertions(+), 163 deletions(-) diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" index a034e88e95e..b767784e060 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" @@ -1,3 +1,20 @@ + + +- [说明](#说明) +- [1. KMP 算法](#1-kmp-算法) +- [2. 替换空格](#2-替换空格) +- [3. 最长公共前缀](#3-最长公共前缀) +- [4. 回文串](#4-回文串) + - [4.1. 最长回文串](#41-最长回文串) + - [4.2. 验证回文串](#42-验证回文串) + - [4.3. 最长回文子串](#43-最长回文子串) + - [4.4. 最长回文子序列](#44-最长回文子序列) + - [5. 括号匹配深度](#5-括号匹配深度) +- [6. 把字符串转换成整数](#6-把字符串转换成整数) + + + + ## 说明 - 本文作者:wwwxmu @@ -36,34 +53,34 @@ //https://www.weiweiblog.cn/replacespace/ public class Solution { - /** - * 第一种方法:常规方法。利用String.charAt(i)以及String.valueOf(char).equals(" " - * )遍历字符串并判断元素是否为空格。是则替换为"%20",否则不替换 - */ - public static String replaceSpace(StringBuffer str) { - - int length = str.length(); - // System.out.println("length=" + length); - StringBuffer result = new StringBuffer(); - for (int i = 0; i < length; i++) { - char b = str.charAt(i); - if (String.valueOf(b).equals(" ")) { - result.append("%20"); - } else { - result.append(b); - } - } - return result.toString(); - - } - - /** - * 第二种方法:利用API替换掉所用空格,一行代码解决问题 - */ - public static String replaceSpace2(StringBuffer str) { - - return str.toString().replaceAll("\\s", "%20"); - } + /** + * 第一种方法:常规方法。利用String.charAt(i)以及String.valueOf(char).equals(" " + * )遍历字符串并判断元素是否为空格。是则替换为"%20",否则不替换 + */ + public static String replaceSpace(StringBuffer str) { + + int length = str.length(); + // System.out.println("length=" + length); + StringBuffer result = new StringBuffer(); + for (int i = 0; i < length; i++) { + char b = str.charAt(i); + if (String.valueOf(b).equals(" ")) { + result.append("%20"); + } else { + result.append(b); + } + } + return result.toString(); + + } + + /** + * 第二种方法:利用API替换掉所用空格,一行代码解决问题 + */ + public static String replaceSpace2(StringBuffer str) { + + return str.toString().replaceAll("\\s", "%20"); + } } ``` @@ -93,36 +110,36 @@ public class Solution { ```java //https://leetcode-cn.com/problems/longest-common-prefix/description/ public class Main { - public static String replaceSpace(String[] strs) { - - // 数组长度 - int len = strs.length; - // 用于保存结果 - StringBuffer res = new StringBuffer(); - // 注意:=是赋值,==是判断 - if (strs == null || strs.length == 0) { - return ""; - } - // 给字符串数组的元素按照升序排序(包含数字的话,数字会排在前面) - Arrays.sort(strs); - int m = strs[0].length(); - int n = strs[len - 1].length(); - int num = Math.min(m, n); - for (int i = 0; i < num; i++) { - if (strs[0].charAt(i) == strs[len - 1].charAt(i)) { - res.append(strs[0].charAt(i)); - } else - break; - - } - return res.toString(); - - } + public static String replaceSpace(String[] strs) { + + // 数组长度 + int len = strs.length; + // 用于保存结果 + StringBuffer res = new StringBuffer(); + // 注意:=是赋值,==是判断 + if (strs == null || strs.length == 0) { + return ""; + } + // 给字符串数组的元素按照升序排序(包含数字的话,数字会排在前面) + Arrays.sort(strs); + int m = strs[0].length(); + int n = strs[len - 1].length(); + int num = Math.min(m, n); + for (int i = 0; i < num; i++) { + if (strs[0].charAt(i) == strs[len - 1].charAt(i)) { + res.append(strs[0].charAt(i)); + } else + break; + + } + return res.toString(); + + } //测试 - public static void main(String[] args) { - String[] strs = { "customer", "car", "cat" }; - System.out.println(Main.replaceSpace(strs));//c - } + public static void main(String[] args) { + String[] strs = { "customer", "car", "cat" }; + System.out.println(Main.replaceSpace(strs));//c + } } ``` @@ -161,23 +178,23 @@ public class Main { ```java //https://leetcode-cn.com/problems/longest-palindrome/description/ class Solution { - public int longestPalindrome(String s) { - if (s.length() == 0) - return 0; - // 用于存放字符 - HashSet hashset = new HashSet(); - char[] chars = s.toCharArray(); - int count = 0; - for (int i = 0; i < chars.length; i++) { - if (!hashset.contains(chars[i])) {// 如果hashset没有该字符就保存进去 - hashset.add(chars[i]); - } else {// 如果有,就让count++(说明找到了一个成对的字符),然后把该字符移除 - hashset.remove(chars[i]); - count++; - } - } - return hashset.isEmpty() ? count * 2 : count * 2 + 1; - } + public int longestPalindrome(String s) { + if (s.length() == 0) + return 0; + // 用于存放字符 + HashSet hashset = new HashSet(); + char[] chars = s.toCharArray(); + int count = 0; + for (int i = 0; i < chars.length; i++) { + if (!hashset.contains(chars[i])) {// 如果hashset没有该字符就保存进去 + hashset.add(chars[i]); + } else {// 如果有,就让count++(说明找到了一个成对的字符),然后把该字符移除 + hashset.remove(chars[i]); + count++; + } + } + return hashset.isEmpty() ? count * 2 : count * 2 + 1; + } } ``` @@ -203,26 +220,26 @@ class Solution { ```java //https://leetcode-cn.com/problems/valid-palindrome/description/ class Solution { - public boolean isPalindrome(String s) { - if (s.length() == 0) - return true; - int l = 0, r = s.length() - 1; - while (l < r) { - // 从头和尾开始向中间遍历 - if (!Character.isLetterOrDigit(s.charAt(l))) {// 字符不是字母和数字的情况 - l++; - } else if (!Character.isLetterOrDigit(s.charAt(r))) {// 字符不是字母和数字的情况 - r--; - } else { - // 判断二者是否相等 - if (Character.toLowerCase(s.charAt(l)) != Character.toLowerCase(s.charAt(r))) - return false; - l++; - r--; - } - } - return true; - } + public boolean isPalindrome(String s) { + if (s.length() == 0) + return true; + int l = 0, r = s.length() - 1; + while (l < r) { + // 从头和尾开始向中间遍历 + if (!Character.isLetterOrDigit(s.charAt(l))) {// 字符不是字母和数字的情况 + l++; + } else if (!Character.isLetterOrDigit(s.charAt(r))) {// 字符不是字母和数字的情况 + r--; + } else { + // 判断二者是否相等 + if (Character.toLowerCase(s.charAt(l)) != Character.toLowerCase(s.charAt(r))) + return false; + l++; + r--; + } + } + return true; + } } ``` @@ -254,28 +271,28 @@ class Solution { ```java //https://leetcode-cn.com/problems/longest-palindromic-substring/description/ class Solution { - private int index, len; - - public String longestPalindrome(String s) { - if (s.length() < 2) - return s; - for (int i = 0; i < s.length() - 1; i++) { - PalindromeHelper(s, i, i); - PalindromeHelper(s, i, i + 1); - } - return s.substring(index, index + len); - } - - public void PalindromeHelper(String s, int l, int r) { - while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { - l--; - r++; - } - if (len < r - l - 1) { - index = l + 1; - len = r - l - 1; - } - } + private int index, len; + + public String longestPalindrome(String s) { + if (s.length() < 2) + return s; + for (int i = 0; i < s.length() - 1; i++) { + PalindromeHelper(s, i, i); + PalindromeHelper(s, i, i + 1); + } + return s.substring(index, index + len); + } + + public void PalindromeHelper(String s, int l, int r) { + while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { + l--; + r++; + } + if (len < r - l - 1) { + index = l + 1; + len = r - l - 1; + } + } } ``` @@ -381,20 +398,20 @@ import java.util.Scanner; * @Description: TODO 求给定合法括号序列的深度 */ public class Main { - public static void main(String[] args) { - Scanner sc = new Scanner(System.in); - String s = sc.nextLine(); - int cnt = 0, max = 0, i; - for (i = 0; i < s.length(); ++i) { - if (s.charAt(i) == '(') - cnt++; - else - cnt--; - max = Math.max(max, cnt); - } - sc.close(); - System.out.println(max); - } + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + String s = sc.nextLine(); + int cnt = 0, max = 0, i; + for (i = 0; i < s.length(); ++i) { + if (s.charAt(i) == '(') + cnt++; + else + cnt--; + max = Math.max(max, cnt); + } + sc.close(); + System.out.println(max); + } } ``` @@ -407,39 +424,39 @@ public class Main { //https://www.weiweiblog.cn/strtoint/ public class Main { - public static int StrToInt(String str) { - if (str.length() == 0) - return 0; - char[] chars = str.toCharArray(); - // 判断是否存在符号位 - int flag = 0; - if (chars[0] == '+') - flag = 1; - else if (chars[0] == '-') - flag = 2; - int start = flag > 0 ? 1 : 0; - int res = 0;// 保存结果 - for (int i = start; i < chars.length; i++) { - if (Character.isDigit(chars[i])) {// 调用Character.isDigit(char)方法判断是否是数字,是返回True,否则False - int temp = chars[i] - '0'; - res = res * 10 + temp; - } else { - return 0; - } - } - return flag == 1 ? res : -res; - - } - - public static void main(String[] args) { - // TODO Auto-generated method stub - String s = "-12312312"; - System.out.println("使用库函数转换:" + Integer.valueOf(s)); - int res = Main.StrToInt(s); - System.out.println("使用自己写的方法转换:" + res); - - } + public static int StrToInt(String str) { + if (str.length() == 0) + return 0; + char[] chars = str.toCharArray(); + // 判断是否存在符号位 + int flag = 0; + if (chars[0] == '+') + flag = 1; + else if (chars[0] == '-') + flag = 2; + int start = flag > 0 ? 1 : 0; + int res = 0;// 保存结果 + for (int i = start; i < chars.length; i++) { + if (Character.isDigit(chars[i])) {// 调用Character.isDigit(char)方法判断是否是数字,是返回True,否则False + int temp = chars[i] - '0'; + res = res * 10 + temp; + } else { + return 0; + } + } + return flag == 1 ? res : -res; + + } + + public static void main(String[] args) { + // TODO Auto-generated method stub + String s = "-12312312"; + System.out.println("使用库函数转换:" + Integer.valueOf(s)); + int res = Main.StrToInt(s); + System.out.println("使用自己写的方法转换:" + res); + + } } -``` \ No newline at end of file +``` From 903efee91034f359190899c4d267db9f98bc4be0 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 20 Sep 2018 15:31:28 +0800 Subject: [PATCH 043/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...54\246\344\270\262\347\256\227\346\263\225\351\242\230.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" index b767784e060..b7b1c3145c8 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" @@ -9,7 +9,7 @@ - [4.2. 验证回文串](#42-验证回文串) - [4.3. 最长回文子串](#43-最长回文子串) - [4.4. 最长回文子序列](#44-最长回文子序列) - - [5. 括号匹配深度](#5-括号匹配深度) +- [5. 括号匹配深度](#5-括号匹配深度) - [6. 把字符串转换成整数](#6-把字符串转换成整数) @@ -346,7 +346,7 @@ class Solution { } ``` -### 5. 括号匹配深度 +## 5. 括号匹配深度 > 爱奇艺 2018 秋招 Java: >一个合法的括号匹配序列有以下定义: From 04b4662f78cc2623d108427d70f2f6840e83b813 Mon Sep 17 00:00:00 2001 From: Young Chen <731463669@qq.com> Date: Thu, 20 Sep 2018 15:35:49 +0800 Subject: [PATCH 044/122] =?UTF-8?q?Update=20Redlock=E5=88=86=E5=B8=83?= =?UTF-8?q?=E5=BC=8F=E9=94=81.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" | 3 --- 1 file changed, 3 deletions(-) diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" index 6a697a0adf8..b1742f2fbf9 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -45,6 +45,3 @@ end ## 性能、崩溃恢复和 fsync 如果我们的节点没有持久化机制,client 从 5 个 master 中的 3 个处获得了锁,然后其中一个重启了,这是注意 **整个环境中又出现了 3 个 master 可供另一个 client 申请同一把锁!** 违反了互斥性。如果我们开启了 AOF 持久化那么情况会稍微好转一些,因为 Redis 的过期机制是语义层面实现的,所以在 server 挂了的时候时间依旧在流逝,重启之后锁状态不会受到污染。但是考虑断电之后呢,AOF部分命令没来得及刷回磁盘直接丢失了,除非我们配置刷回策略为 fsnyc = always,但这会损伤性能。解决这个问题的方法是,当一个节点重启之后,我们规定在 max TTL 期间它是不可用的,这样它就不会干扰原本已经申请到的锁,等到它 crash 前的那部分锁都过期了,环境不存在历史锁了,那么再把这个节点加进来正常工作。 - -## License -* 原文 [基于 Redis 的分布式锁 Redlock](https://zhuanlan.zhihu.com/p/40915772) \ No newline at end of file From 8b9b23068ee0e3ba2530562763471f409151fa76 Mon Sep 17 00:00:00 2001 From: Young Chen <731463669@qq.com> Date: Thu, 20 Sep 2018 15:36:05 +0800 Subject: [PATCH 045/122] =?UTF-8?q?Update=20=E5=A6=82=E4=BD=95=E5=81=9A?= =?UTF-8?q?=E5=8F=AF=E9=9D=A0=E7=9A=84=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81?= =?UTF-8?q?=EF=BC=8CRedlock=E7=9C=9F=E7=9A=84=E5=8F=AF=E8=A1=8C=E4=B9=88.m?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" | 3 --- 1 file changed, 3 deletions(-) diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" index 2fc83e5fe44..043df96566d 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" @@ -89,6 +89,3 @@ Redlock 无法产生 fencing token 早该成为在需求正确性的场景下弃 ## 结论 Martin 认为 Redlock 实在不是一个好的选择,对于需求性能的分布式锁应用它太重了且成本高;对于需求正确性的应用来说它不够安全。因为它对高危的时钟或者说其他上述列举的情况进行了不可靠的假设,如果你的应用只需要高性能的分布式锁不要求多高的正确性,那么单节点 Redis 够了;如果你的应用想要保住正确性,那么不建议 Redlock,建议使用一个合适的一致性协调系统,例如 Zookeeper,且保证存在 fencing token。 - -## License -* 原文 [怎样做可靠的分布式锁,Redlock 真的可行么?](https://zhuanlan.zhihu.com/p/41327417) From 6160404953f85352248bdfbaca56862c8b790d68 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 20 Sep 2018 19:06:53 +0800 Subject: [PATCH 046/122] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=A2=9E=E5=8A=A0Red?= =?UTF-8?q?is=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 76a01fd03fa..04a15dd5a6a 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,11 @@ - ### MySQL - [MySQL 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL.md) - ### Redis - - [Redis 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis.md) - + - [Redis 总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redis.md) + - [Redlock分布式锁](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redlock分布式锁.md) + - [如何做可靠的分布式锁,Redlock真的可行么](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/如何做可靠的分布式锁,Redlock真的可行么.md)\ + - [春夏秋冬又一春之Redis持久化](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/春夏秋冬又一春之Redis持久化.md) + ## :punch: 架构 - ### 分布式相关 - [分布式学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/架构/分布式.md) From 1dcad6f98d8f6557ba8081ae9629c75c7cb55099 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Fri, 21 Sep 2018 21:53:00 +0800 Subject: [PATCH 047/122] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\347\241\200\347\237\245\350\257\206.md" | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index dc6a1826666..3df98ffc4c3 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -1,4 +1,3 @@ - - [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别) @@ -168,8 +167,23 @@ Java 语言通过字节码的方式,在一定程度上解决了传统解释型 **可变性**   -String 类中使用字符数组保存字符串,private final char value[],所以 String 对象是不可变的。StringBuilder 与 StringBuffer 都继承自AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,char[]value,这两种对象都是可变的。 -   +简单的来说:String 类中使用 final 关键字字符数组保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。 + +StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。 + +AbstractStringBuilder.java + +```java +abstract class AbstractStringBuilder implements Appendable, CharSequence { + char[] value; + int count; + AbstractStringBuilder() { + } + AbstractStringBuilder(int capacity) { + value = new char[capacity]; + } +``` + **线程安全性** @@ -379,5 +393,3 @@ final关键字主要用在三个地方:变量、方法、类。 这本书要常读,初学者可以快速概览,中等程序员可以深入看看 Java,老鸟还可以用之回顾 Java 的体系。这本书之所以厉害,因为它在无形中整合了设计模式,这本书之所以难读,也恰恰在于他对设计模式的整合是无形的。 - - From a9a7bbebec6e797611d457d855faadece0cc9b85 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Fri, 21 Sep 2018 22:14:36 +0800 Subject: [PATCH 048/122] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...27\346\234\272\347\275\221\347\273\234.md" | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" index 39dd3c50452..c724f4af508 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" @@ -271,21 +271,19 @@ TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** ![状态码](https://user-gold-cdn.xitu.io/2018/4/19/162db5e985aabdbe?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) -**一个网页从请求到最终显示的完整过程一般可以分为如下7个步骤:** +总体来说分为以下几个过程: -(1)在浏览器中输入网址; +1. DNS解析 +2. TCP连接 +3. 发送HTTP请求 +4. 服务器处理请求并返回HTTP报文 +5. 浏览器解析渲染页面 +6. 连接结束 -(2)发送至DNS服务器并获得域名对应的WEB服务器IP地址; +具体可以参考下面这篇文章: -(3)与WEB服务器建立TCP连接; +- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700) -(4)浏览器向WEB服务器发送相应的HTTP请求; - -(5)WEB服务器响应请求并返回指定URL的数据,或错误信息,如果设定重定向,则重定向到新的URL地址; - -(6)浏览器下载数据后解析HTML源文件,解析的过程中实现对页面的排版,解析完成后在浏览器中显示基础页面; - -(7)分析页面中的超链接并显示在当前页面,重复以上过程直至无超链接需要发送,完成全部数据显示。 From eeebf7d23f4895642cc806b96e195534f08f7f24 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Fri, 21 Sep 2018 22:18:10 +0800 Subject: [PATCH 049/122] =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" | 1 + 1 file changed, 1 insertion(+) diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" index c724f4af508..ba5fb91ecc9 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" @@ -157,6 +157,7 @@ ![TCP四次挥手](https://user-gold-cdn.xitu.io/2018/5/8/1633e1676e2ac0a3?w=500&h=340&f=jpeg&s=13406) 断开一个 TCP 连接则需要“四次挥手”: + - 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送 - 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号 - 服务器-关闭与客户端的连接,发送一个FIN给客户端 From 6b84b3ee720151999e19686d69fa9b4fbe4e7dbe Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 22 Sep 2018 07:34:44 +0800 Subject: [PATCH 050/122] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=B1=E6=95=88?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Redis/Redis.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" index f0269c05960..c03e7e11e10 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" @@ -141,7 +141,7 @@ Redis为单进程单线程模式,采用队列模式将并发访问变为串行 ### Redis持久化数据和缓存怎么做扩容? -**《redis的持久化和缓存机制》** :[https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/春夏秋冬又一春之Redis持久化.md](https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/春夏秋冬又一春之Redis持久化.md) +**《redis的持久化和缓存机制》** :[https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/R春夏秋冬又一春之Redis持久化.md](https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/春夏秋冬又一春之Redis持久化.md) 扩容的话可以通过redis集群实现,之前做项目的时候用过自己搭的redis集群 然后写了一篇关于redis集群的文章:**《一文轻松搞懂redis集群原理及搭建与使用》**:[https://juejin.im/post/5ad54d76f265da23970759d3](https://juejin.im/post/5ad54d76f265da23970759d3) From 15f670b2cb3b772ad3f156fde5768c50c480336c Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 22 Sep 2018 07:36:46 +0800 Subject: [PATCH 051/122] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=B1=E6=95=88?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Redis/Redis.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" index c03e7e11e10..c8c64052e16 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" @@ -141,7 +141,7 @@ Redis为单进程单线程模式,采用队列模式将并发访问变为串行 ### Redis持久化数据和缓存怎么做扩容? -**《redis的持久化和缓存机制》** :[https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/R春夏秋冬又一春之Redis持久化.md](https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/春夏秋冬又一春之Redis持久化.md) +**《redis的持久化和缓存机制》** :[https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/R春夏秋冬又一春之Redis持久化.md](https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/Redis/春夏秋冬又一春之Redis持久化.md) 扩容的话可以通过redis集群实现,之前做项目的时候用过自己搭的redis集群 然后写了一篇关于redis集群的文章:**《一文轻松搞懂redis集群原理及搭建与使用》**:[https://juejin.im/post/5ad54d76f265da23970759d3](https://juejin.im/post/5ad54d76f265da23970759d3) From 5046b32ced160f92a918c4f91f49477621b58533 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 22 Sep 2018 07:53:24 +0800 Subject: [PATCH 052/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...Java\345\237\272\347\241\200\347\237\245\350\257\206.md" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 3df98ffc4c3..b1e0d86c477 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -377,9 +377,9 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返 final关键字主要用在三个地方:变量、方法、类。 -1. ①对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。 -2. ②当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。 -3. ③使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为fianl。 +1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。 +2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。 +3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为fianl。 # Java基础学习书籍推荐 From dd31d98ecdf091c88675d6143cf55f69ba994854 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Mon, 24 Sep 2018 19:09:08 +0800 Subject: [PATCH 053/122] =?UTF-8?q?redis=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Redis/Redis\346\214\201\344\271\205\345\214\226.md" | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" => "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" (100%) diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" similarity index 100% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" From 5eda80bd2b6c76288d7cc42bb23c8e6e6bd0a789 Mon Sep 17 00:00:00 2001 From: jiayangchen Date: Tue, 25 Sep 2018 09:47:26 +0800 Subject: [PATCH 054/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0redis=204.0=E6=8C=81?= =?UTF-8?q?=E4=B9=85=E5=8C=96=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Redis/Redis\346\214\201\344\271\205\345\214\226.md" | 5 +++++ 1 file changed, 5 insertions(+) diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" index b2d30b1884b..fbad95556a0 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" @@ -104,6 +104,11 @@ auto-aof-rewrite-min-size 64mb 随着负载量的上升,或者数据的完整性变得 越来越重要时,用户可能需要使用到复制特性。 +## Redis 4.0 对于持久化机制的优化 +Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。 + +如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分就是压缩格式不再是 AOF 格式,可读性较差。 + 参考: 《Redis实战》 From 71a7068a96b292aa075ee9c9d8f872e32c17f52c Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Wed, 26 Sep 2018 19:36:40 +0800 Subject: [PATCH 055/122] =?UTF-8?q?=E5=AF=B9=20redis=20=E6=80=BB=E7=BB=93?= =?UTF-8?q?=20=E8=BF=99=E9=83=A8=E5=88=86=E5=86=85=E5=AE=B9=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E4=BA=86=E4=B8=80=E6=AC=A1=E6=94=B9=E5=8A=A8=E8=BE=83?= =?UTF-8?q?=E5=A4=A7=E7=9A=84=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Redis/Redis.md" | 282 ++++++++++++------ 1 file changed, 194 insertions(+), 88 deletions(-) diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" index c8c64052e16..41234cf55ac 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" @@ -1,67 +1,76 @@ -Redis 是一个使用 C 语言写成的,开源的 key-value 数据库。。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。目前,Vmware在资助着redis项目的开发和维护。 + -> ### 书籍推荐 +- [redis 简介](#redis-简介) +- [为什么要用 redis /为什么要用缓存](#为什么要用-redis-为什么要用缓存) +- [为什么要用 redis 而不用 map/guava 做缓存?](#为什么要用-redis-而不用-mapguava-做缓存) +- [redis 和 memcached 的区别](#redis-和-memcached-的区别) +- [redis 常见数据结构以及使用场景分析](#redis-常见数据结构以及使用场景分析) + - [1. String](#1-string) + - [2.Hash](#2hash) + - [3.List](#3list) + - [4.Set](#4set) + - [5.Sorted Set](#5sorted-set) +- [redis 设置过期时间](#redis-设置过期时间) +- [redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)](#redis-内存淘汰机制(mysql里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?)) +- [redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)](#redis-持久化机制(怎么保证-redis-挂掉之后再重启数据可以进行恢复)) +- [redis 事务](#redis-事务) +- [缓存雪崩和缓存穿透问题解决方案](#缓存雪崩和缓存穿透问题解决方案) +- [如何解决 Redis 的并发竞争 Key 问题](#如何解决-redis-的并发竞争-key-问题) +- [如何保证缓存与数据库双写时的数据一致性?](#如何保证缓存与数据库双写时的数据一致性?) +- [参考:](#参考:) -**《Redis实战》** + -**《Redis设计与实现》** -> ### 教程推荐 +### redis 简介 -**redis官方中文版教程**:[http://www.redis.net.cn/tutorial/3501.html](http://www.redis.net.cn/tutorial/3501.html) +简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以存写速度非常快,因此 redis 被广泛应用于缓存方向。另外,redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。 -**Redis 教程(菜鸟教程)**:[http://www.runoob.com/redis/redis-tutorial.html](http://www.runoob.com/redis/redis-tutorial.html) +### 为什么要用 redis /为什么要用缓存 -> ### 常见问题总结 +主要从“高性能”和“高并发”这两点来看待这个问题。 -就我个人而言,我觉得Redis的基本使用是我们每个Java程序员都应该会的。另外,如果需要面试的话,一些关于Redis的理论知识也需要好好的学习一下。学完Redis之后,对照着下面8点看看自己还有那些不足的地方,同时,下面7点也是面试中经常会问到的。另外,《Redis实战》、《Redis设计与实现》是我比较推荐的两本学习Redis的书籍。 +**高性能:** -1. **Redis的两种持久化操作以及如何保障数据安全(快照和AOF)** -2. **如何防止数据出错(Redis事务)** -3. **如何使用流水线来提升性能** -4. **Redis主从复制** -5. **Redis集群的搭建** -6. **Redis的几种淘汰策略** -7. **Redis集群宕机,数据迁移问题** -8. **Redis缓存使用有很多,怎么解决缓存雪崩和缓存穿透?** +假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可! +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-24/54316596.jpg) -## Redis常见问题分析与好文Mark -### 什么是Redis? -> Redis 是一个使用 C 语言写成的,开源的 key-value 数据库。。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。目前,Vmware在资助着redis项目的开发和维护。 +**高并发:** +直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。 -### Redis与Memcached的区别与比较 -1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。 -2 、Redis支持数据的备份,即master-slave模式的数据备份。 +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-24/85146760.jpg) -3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中 -4、 redis的速度比memcached快很多 +### 为什么要用 redis 而不用 map/guava 做缓存? -5、Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的IO复用模型。 -![Redis与Memcached的区别与比较](https://user-gold-cdn.xitu.io/2018/4/18/162d7773080d4570?w=621&h=378&f=jpeg&s=45278) +>下面的内容来自 segmentfault 一位网友的提问,地址:https://segmentfault.com/q/1010000009106416 -如果想要更详细了解的话,可以查看慕课网上的这篇手记(非常推荐) **:《脚踏两只船的困惑 - Memcached与Redis》**:[https://www.imooc.com/article/23549](https://www.imooc.com/article/23549) +缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。 -### Redis与Memcached的选择 -**终极策略:** 使用Redis的String类型做的事,都可以用Memcached替换,以此换取更好的性能提升; 除此以外,优先考虑Redis; +使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。 -### 使用redis有哪些好处? -(1) **速度快**,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) -(2)**支持丰富数据类型**,支持string,list,set,sorted set,hash +### redis 和 memcached 的区别 -(3) **支持事务** :redis对事务是部分支持的,如果是在入队时报错,那么都不会执行;在非入队时报错,那么成功的就会成功执行。详细了解请参考:《Redis事务介绍(四)》:[https://blog.csdn.net/cuipeng0916/article/details/53698774](https://blog.csdn.net/cuipeng0916/article/details/53698774) +对于 redis 和 memcached 我总结了下面四点。现在公司一般都是用 redis 来实现缓存,而且 redis 自身也越来越强大了! -redis监控:锁的介绍 +1. **redis支持更丰富的数据类型(支持更复杂的应用场景)**:Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。 +2. **Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。** +3. **集群模式**:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前是原生支持 cluster 模式的. +4. **Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。** -(4) **丰富的特性**:可用于缓存,消息,按key设置过期时间,过期后将会自动删除 -### Redis常见数据结构使用场景 +> 来自网络上的一张图,这里分享给大家! + +![redis 和 memcached 的区别](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-24/61603179.jpg) + + +### redis 常见数据结构以及使用场景分析 #### 1. String @@ -75,30 +84,43 @@ String数据结构是简单的key-value类型,value其实不仅可以是String #### 2.Hash > **常用命令:** hget,hset,hgetall 等。 -Hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。 +Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息: + +``` +key=JavaUser293847 +value={ + “id”: 1, + “name”: “SnailClimb”, + “age”: 22, + “location”: “Wuhan, Hubei” +} -**举个例子:** 最近做的一个电商网站项目的首页就使用了redis的hash数据结构进行缓存,因为一个网站的首页访问量是最大的,所以通常网站的首页可以通过redis缓存来提高性能和并发量。我用**jedis客户端**来连接和操作我搭建的redis集群或者单机redis,利用jedis可以很容易的对redis进行相关操作,总的来说从搭一个简单的集群到实现redis作为缓存的整个步骤不难。感兴趣的可以看我昨天写的这篇文章: +``` -**《一文轻松搞懂redis集群原理及搭建与使用》:** [https://juejin.im/post/5ad54d76f265da23970759d3](https://juejin.im/post/5ad54d76f265da23970759d3) #### 3.List > **常用命令:** lpush,rpush,lpop,rpop,lrange等 -list就是链表,Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,最新消息排行等功能都可以用Redis的list结构来实现。 +list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。 -Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 +Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 +另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。 #### 4.Set + > **常用命令:** sadd,spop,smembers,sunion 等 -set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的。 -当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。 +set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。 +当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。 +比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程,具体命令如下: -在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同喜好、二度好友等功能。 +``` +sinterstore key1 key2 key3 将交集存在key1内 +``` #### 5.Sorted Set > **常用命令:** zadd,zrange,zrem,zcard等 @@ -106,89 +128,173 @@ set对外提供的功能与list类似是一个列表的功能,特殊之处在 和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。 -**举例:** 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用Redis中的SortedSet结构进行存储。 +**举例:** 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。 + + +### redis 设置过期时间 + +Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。 + +我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间。 + +如果假设你设置了一批 key 只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的? + +**定期删除+惰性删除。** + +通过名字大概就能猜出这两个删除方式的意思了。 + +- **定期删除**:redis默认是每隔 100ms 就**随机抽取**一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载! +- **惰性删除** :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈! -### MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据(redis有哪些数据淘汰策略???) +但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? + +**redis 内存淘汰机制。** + +### redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?) + +redis 配置文件 redis.conf 中有相关注释,我这里就不贴了,大家可以自行查阅或者通过这个网址查看: [http://download.redis.io/redis-stable/redis.conf](http://download.redis.io/redis-stable/redis.conf) + +**redis 提供 6种数据淘汰策略:** -   相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略(回收策略)。redis 提供 6种数据淘汰策略: 1. **volatile-lru**:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 2. **volatile-ttl**:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 3. **volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 -4. **allkeys-lru**:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 +4. **allkeys-lru**:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的). 5. **allkeys-random**:从数据集(server.db[i].dict)中任意选择数据淘汰 -6. **no-enviction**(驱逐):禁止驱逐数据 +6. **no-enviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧! -### Redis的并发竞争问题如何解决? -Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法: +**备注: 关于 redis 设置过期时间以及内存淘汰机制,我这里只是简单的总结一下,后面会专门写一篇文章来总结!** - 1.客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。 -  - 2.服务器角度,利用setnx实现锁。 - - 注:对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;第二种需要用到Redis的setnx命令,但是需要注意一些问题。 +### redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复) -### Redis回收进程如何工作的? Redis回收使用的是什么算法? -**Redis内存回收:LRU算法(写的很不错,推荐)**:[https://www.cnblogs.com/WJ5888/p/4371647.html](https://www.cnblogs.com/WJ5888/p/4371647.html) +很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 -### Redis 大量数据插入 -官方文档给的解释:[http://www.redis.cn/topics/mass-insert.html](http://www.redis.cn/topics/mass-insert.html) +Redis不同于Memcached的很重一点就是,Redis支持持久化,而且支持两种不同的持久化操作。**Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)**.这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 -### Redis 分区的优势、不足以及分区类型 -官方文档提供的讲解:[http://www.redis.net.cn/tutorial/3524.html](http://www.redis.net.cn/tutorial/3524.html) +**快照(snapshotting)持久化(RDB)** -### Redis持久化数据和缓存怎么做扩容? +Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。 -**《redis的持久化和缓存机制》** :[https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/R春夏秋冬又一春之Redis持久化.md](https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/Redis/春夏秋冬又一春之Redis持久化.md) +快照持久化是Redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置: -扩容的话可以通过redis集群实现,之前做项目的时候用过自己搭的redis集群 -然后写了一篇关于redis集群的文章:**《一文轻松搞懂redis集群原理及搭建与使用》**:[https://juejin.im/post/5ad54d76f265da23970759d3](https://juejin.im/post/5ad54d76f265da23970759d3) +```conf -### Redis常见性能问题和解决方案: +save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 -1. Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件 -2. 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次 -3. 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内 -4. 尽量避免在压力很大的主库上增加从库 +save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 -### Redis与消息队列 ->作者:翁伟 -链接:https://www.zhihu.com/question/20795043/answer/345073457 +save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 +``` -不要使用redis去做消息队列,这不是redis的设计目标。但实在太多人使用redis去做去消息队列,redis的作者看不下去,另外基于redis的核心代码,另外实现了一个消息队列disque: antirez/disque:[https://github.com/antirez/disque](https://github.com/antirez/disque)部署、协议等方面都跟redis非常类似,并且支持集群,延迟消息等等。 -我在做网站过程接触比较多的还是使用redis做缓存,比如秒杀系统,首页缓存等等。 +**AOF(append-only file)持久化** +与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启: +```conf +appendonly yes +``` +开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。 +在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是: -## 好文Mark -**非常非常推荐下面几篇文章。。。** +```conf +appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 +appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 +appendfsync no #让操作系统决定何时进行同步 +``` -**《Redis深入之道:原理解析、场景使用以及视频解读》**:[https://zhuanlan.zhihu.com/p/28073983](https://zhuanlan.zhihu.com/p/28073983): -主要介绍了:Redis集群开源的方案、Redis协议简介及持久化Aof文件解析、Redis短连接性能优化等等内容,文章干货太大,容量很大,建议时间充裕可以看看。另外文章里面还提供了视频讲解,可以说是非常非常用心了。 +为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 -**《阿里云Redis混合存储典型场景:如何轻松搭建视频直播间系统》:**[https://yq.aliyun.com/articles/582487?utm_content=m_46529](https://yq.aliyun.com/articles/582487?utm_content=m_46529): -主要介绍视频直播间系统,以及如何使用阿里云Redis混合存储实例方便快捷的构建大数据量,低延迟的视频直播间服务。还介绍到了我们之前提高过的redis的数据结构的使用场景 +**Redis 4.0 对于持久化机制的优化** -**《美团在Redis上踩过的一些坑-5.redis cluster遇到的一些问》**:[http://carlosfu.iteye.com/blog/2254573](http://carlosfu.iteye.com/blog/2254573):主要介绍了redis集群的两个常见问题,然后分享了 一些关于redis集群不错的文章。 +Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。 -**参考:** +如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。 + + + +**补充内容:AOF 重写** + +AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样,但体积更小。 + +AOF重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任伺读入、分析或者写入操作。 + +在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作 + + +**更多内容可以查看我的这篇文章:** -https://www.cnblogs.com/Survivalist/p/8119891.html +- [https://github.com/Snailclimb/JavaGuide/blob/master/数据存储/Redis/Redis持久化.md](https://github.com/Snailclimb/JavaGuide/blob/master/数据存储/Redis/Redis持久化.md) -http://www.redis.net.cn/tutorial/3524.html -https://redis.io/ +### redis 事务 +Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。 +在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。 +### 缓存雪崩和缓存穿透问题解决方案 +**缓存雪崩** + +简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。 + +解决办法(中华石杉老师在他的视频中提到过,视频地址在最后一个问题中有提到): + +- 事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。 +- 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉 +- 事后:利用 redis 持久化机制保存的数据尽快恢复缓存 + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-25/6078367.jpg) + + +**缓存穿透** + +简介:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。 + +解决办法: 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。 + +参考: + +- https://blog.csdn.net/zeb_perfect/article/details/54135506[enter link description here](https://blog.csdn.net/zeb_perfect/article/details/54135506) + +### 如何解决 Redis 的并发竞争 Key 问题 + +所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同! + +推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能) + +基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。 + +在实践中,当然是从以可靠性为主。所以首推Zookeeper。 + +参考: + +- https://www.jianshu.com/p/8bddd381de06 + + +### 如何保证缓存与数据库双写时的数据一致性? + + +你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? + +一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况 + +串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。 + +**参考:** +- Java工程师面试突击第1季(可能是史上最好的Java面试突击课程)-中华石杉老师。视频地址见下面! + - 链接: https://pan.baidu.com/s/18pp6g1xKVGCfUATf_nMrOA + - 密码:5i58 +### 参考: +- redis设计与实现(第二版) From a15058a4258642ee91facae9d1a31af3eb8ab3b7 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 27 Sep 2018 19:11:50 +0800 Subject: [PATCH 056/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=A1=A8=E8=BF=B0?= =?UTF-8?q?=E9=94=99=E8=AF=AF=EF=BC=9AJava=E4=B8=AD=E6=9C=89=E5=80=BC?= =?UTF-8?q?=E4=BC=A0=E9=80=92=E5=92=8C=E5=BC=95=E7=94=A8=E4=BC=A0=E9=80=92?= =?UTF-8?q?->Java=E4=B8=AD=E5=8F=AA=E6=9C=89=E5=80=BC=E4=BC=A0=E9=80=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...21\250\357\274\2102018-8-7\357\274\211.md" | 172 +++++++----------- 1 file changed, 63 insertions(+), 109 deletions(-) diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" index 7917aa38ae1..36eaa60dff1 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" @@ -1,20 +1,16 @@ -## 一 Java中的值传递和引用传递(非常重要) +## 一 为什么 Java 中只有值传递? -**首先要明确的是:“对象传递(数组、类、接口)是引用传递,原始类型数据(整型、浮点型、字符型、布尔型)传递是值传递。”** -### 那么什么是值传递和应用传递呢? +首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。**按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。** 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。 -**值传递**是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。) +**Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。** -**引用传递**是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。) +**下面通过 3 个例子来给大家说明** -有时候面试官不是单纯问你“Java中是值传递还是引用传递”是什么啊,骚年?而是给出一个例子,然后让你写出答案,这种也常见在笔试题目中!所以,非常重要了,请看下面的例子: +### example 1 -### 值传递和应用传递实例 - -#### 1. 值传递 ```java public static void main(String[] args) { @@ -48,151 +44,109 @@ num2 = 20 **解析:** -在swap方法中,a、b的值进行交换,并不会影响到num1、num2。因为,a、b中的值,只是从num1、num2的复制过来的。 -也就是说,a、b相当于num1、num2的副本,副本的内容无论怎么修改,都不会影响到原件本身。 - -#### 2. 引用传递 - -```java -public static void main(String[] args) { - int[] arr = {1,2,3,4,5}; +![example 1 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/22191348.jpg) - change(arr); +在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。 - System.out.println(arr[0]); -} +**通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.** -public static void change(int[] array) { -//将数组的第一个元素变为0 - array[0] = 0; -} -``` -**结果:** - -``` -1 -0 -``` - -**解析:** - -无论是主函数,还是change方法,操作的都是同一个地址值对应的数组。 。因此,外部对引用对象的改变会反映到所有的对象上。 - -### 一些特殊的例子 - -#### 1. StringBuffer类型传递 +### example 2 ```java - // 测试引用传递:StringBuffer - @org.junit.Test - public void method1() { - StringBuffer str = new StringBuffer("公众号:Java面试通关手册"); - System.out.println(str); - change1(str); - System.out.println(str); + public static void main(String[] args) { + int[] arr = { 1, 2, 3, 4, 5 }; + System.out.println(arr[0]); + change(arr); + System.out.println(arr[0]); } - public static void change1(StringBuffer str) { - str = new StringBuffer("abc");//输出:“公众号:Java面试通关手册” - //str.append("欢迎大家关注");//输出:公众号:Java面试通关手册欢迎大家关注 - //str.insert(3, "(编程)");//输出:公众号(编程):Java面试通关手册 - + public static void change(int[] array) { + // 将数组的第一个元素变为0 + array[0] = 0; } ``` **结果:** ``` -公众号:Java面试通关手册 -公众号:Java面试通关手册 +1 +0 ``` **解析:** +![example 2](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/3825204.jpg) -很多要这个时候要问了:StringBuffer创建的明明也是对象,那为什么输出结果依然是原来的值呢? - -因为在`change1`方法内部我们是新建了一个StringBuffer对象,所以`str`指向了另外一个地址,相应的操作也同样是指向另外的地址的。 +array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。 -那么,如果将`change1`方法改成如下图所示,想必大家应该知道输出什么了,如果你还不知道,那可能就是我讲的有问题了,我反思(开个玩笑,上面程序中已经给出答案): -``` - public static void change1(StringBuffer str) { +**通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。** - str.append("欢迎大家关注"); - str.insert(3, "(编程)"); - - } -``` +**很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。** -#### 2. String类型传递 +### example 3 ```java - // 测试引用传递:Sring - @org.junit.Test - public void method2() { - String str = new String("公众号:Java面试通关手册"); - System.out.println(str); - change2(str); - System.out.println(str); - } +public class Test { - public static void change2(String str) { - // str="abc"; //输出:公众号:Java面试通关手册 - str = new String("abc"); //输出:公众号:Java面试通关手册 + public static void main(String[] args) { + // TODO Auto-generated method stub + Student s1 = new Student("小张"); + Student s2 = new Student("小李"); + Test.swap(s1, s2); + System.out.println("s1:" + s1.getName()); + System.out.println("s2:" + s2.getName()); } + public static void swap(Student x, Student y) { + Student temp = x; + x = y; + y = temp; + System.out.println("x:" + x.getName()); + System.out.println("y:" + y.getName()); + } +} ``` **结果:** ``` -公众号:Java面试通关手册 -公众号:Java面试通关手册 +x:小李 +y:小张 +s1:小张 +s2:小李 ``` -可以看到不论是执行`str="abc;"`还是`str = new String("abc");`str的输出的值都不变。 -按照我们上面讲“StringBuffer类型传递”的时候说的,`str="abc;"`应该会让str的输出的值都不变。为什么呢?因为String在创建之后是不可变的。 +**解析:** -#### 3. 一道类似的题目 +交换之前: -下面的程序输出是什么? -```java -public class Demo { - public static void main(String[] args) { - Person p = new Person("张三"); +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/88729818.jpg) - change(p); +交换之后: - System.out.println(p.name); - } +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/34384414.jpg) - public static void change(Person p) { - Person person = new Person("李四"); - p = person; - } -} -class Person { - String name; +通过上面两张图可以很清晰的看出: **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝** - public Person(String name) { - this.name = name; - } -} -``` -很明显仍然会输出`张三`。因为`change`方法中重新创建了一个`Person`对象。 +### 总结 -那么,如果把` change`方法改为下图所示,输出结果又是什么呢? +Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按 +值传递的。 -```java - public static void change(Person p) { - p.name="李四"; - } -``` -答案我就不说了,我觉得大家如果认真看完上面的内容之后应该很很清楚了。 +下面再总结一下Java中方法参数的使用情况: + +- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型》 +- 一个方法可以改变一个对象参数的状态。 +- 一个方法不能让对象参数引用一个新的对象。 + + +### 参考: + +《Java核心技术卷Ⅰ》基础知识第十版第四章4.5小节 ## 二 ==与equals(重要) From d424e4fe42143702462fe73facac5eabd1e2e0b5 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 27 Sep 2018 20:32:04 +0800 Subject: [PATCH 057/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 04a15dd5a6a..eb7106a8149 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ - ### 最最最常见的Java面试题总结 这里会分享一些出现频率极其极其高的面试题,初定周更一篇,什么时候更完什么时候停止。 - - [第一周(2018-8-7)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md) (值传递和引用传递、==与equals、 hashCode与equals) + - [第一周(2018-8-7)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md) (为什么 Java 中只有值传递、==与equals、 hashCode与equals) - [第二周(2018-8-13)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?、什么是反射机制?反射机制的应用场景有哪些?......) - [第三周(2018-08-22)](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) (Arraylist 与 LinkedList 异同、ArrayList 与 Vector 区别、HashMap的底层实现、HashMap 和 Hashtable 的区别、HashMap 的长度为什么是2的幂次方、HashSet 和 HashMap 区别、ConcurrentHashMap 和 Hashtable 的区别、ConcurrentHashMap线程安全的具体实现方式/底层具体实现、集合框架底层数据结构总结) - [第四周(2018-8-30).md](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第四周(2018-8-30).md) (主要内容是几道面试常问的多线程基础题。) From 66ed9fc1e1186ff144bb68f95ea87b2c0ffd0153 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 27 Sep 2018 20:32:27 +0800 Subject: [PATCH 058/122] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index eb7106a8149..65bacf3a257 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,7 @@ - [Redis 总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redis.md) - [Redlock分布式锁](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redlock分布式锁.md) - [如何做可靠的分布式锁,Redlock真的可行么](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/如何做可靠的分布式锁,Redlock真的可行么.md)\ - - [春夏秋冬又一春之Redis持久化](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/春夏秋冬又一春之Redis持久化.md) - + ## :punch: 架构 - ### 分布式相关 - [分布式学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/架构/分布式.md) From bcc0f411c0ccbf381d3c933f7bbd7f8f179b360c Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 29 Sep 2018 09:51:43 +0800 Subject: [PATCH 059/122] =?UTF-8?q?=E4=BB=80=E4=B9=88=E6=98=AF=E8=A7=A3?= =?UTF-8?q?=E9=87=8A=E6=80=A7=E8=AF=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Java\345\237\272\347\241\200\347\237\245\350\257\206.md" | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index b1e0d86c477..8b3f79433c1 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -101,7 +101,9 @@ Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了 ### 采用字节码的好处:  -Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。 +**Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。** + +> 解释性语言:解释型语言,是在运行的时候将程序翻译成机器语言。解释型语言的程序不需要在运行前编译,在运行程序的时候才翻译,专门的解释器负责在每个语句执行的时候解释程序代码。这样解释型语言每执行一次就要翻译一次,效率比较低。——百度百科 ## 5. Java和C++的区别 From a9f055233d60f959d3ee82129af6038dc13155cf Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 29 Sep 2018 11:07:33 +0800 Subject: [PATCH 060/122] =?UTF-8?q?=E6=9E=84=E9=80=A0=E5=99=A8Constructor?= =?UTF-8?q?=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 构造器Constructor不能被继承,因此不能被重写(Override),但是可以被重载(Overload) --- .../Java\345\237\272\347\241\200\347\237\245\350\257\206.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 8b3f79433c1..0fc49c238d5 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -134,7 +134,7 @@ Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了 ## 9. 构造器 Constructor 是否可被 override -在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override,但是可以 overload,所以你可以看到一个类中有多个构造函数的情况。 +在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。 ## 10. 重载和重写的区别 From f893ac1268cddc1a9fa02923405684b2507f0357 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 29 Sep 2018 11:21:38 +0800 Subject: [PATCH 061/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E6=8E=92=E7=89=88=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\347\241\200\347\237\245\350\257\206.md" | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 0fc49c238d5..6128c16483f 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -3,14 +3,14 @@ - [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别) - [面向过程](#面向过程) - [面向对象](#面向对象) -- [2. Java 语言有哪些特点?](#2-java-语言有哪些特点?) -- [3. 什么是 JDK?什么是 JRE?什么是 JVM?三者之间的联系与区别](#3-什么是-jdk?什么是-jre?什么是-jvm?三者之间的联系与区别) -- [4. 什么是字节码?采用字节码的最大好处是什么?](#4-什么是字节码?采用字节码的最大好处是什么?) +- [2. Java 语言有哪些特点](#2-java-语言有哪些特点) +- [3. 什么是 JDK 什么是 JRE 什么是 JVM 三者之间的联系与区别](#3-什么是-jdk-什么是-jre-什么是-jvm-三者之间的联系与区别) +- [4. 什么是字节码 采用字节码的最大好处是什么](#4-什么是字节码-采用字节码的最大好处是什么) - [先看下 java 中的编译器和解释器:](#先看下-java-中的编译器和解释器:) - [采用字节码的好处:](#采用字节码的好处:) - [5. Java和C++的区别](#5-java和c的区别) -- [6. 什么是 Java 程序的主类?应用程序和小程序的主类有何不同?](#6-什么是-java-程序的主类?应用程序和小程序的主类有何不同?) -- [7. Java 应用程序与小程序之间有那些差别?](#7-java-应用程序与小程序之间有那些差别?) +- [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同) +- [7. Java 应用程序与小程序之间有那些差别](#7-java-应用程序与小程序之间有那些差别) - [8. 字符型常量和字符串常量的区别](#8-字符型常量和字符串常量的区别) - [9. 构造器 Constructor 是否可被 override](#9-构造器-constructor-是否可被-override) - [10. 重载和重写的区别](#10-重载和重写的区别) @@ -18,18 +18,18 @@ - [封装](#封装) - [继承](#继承) - [多态](#多态) -- [12. String 和 StringBuffer、StringBuilder 的区别是什么?String 为什么是不可变的?](#12-string-和-stringbuffer、stringbuilder-的区别是什么?string-为什么是不可变的?) +- [12. String 和 StringBuffer、StringBuilder 的区别是什么 String 为什么是不可变的](#12-string-和-stringbuffer、stringbuilder-的区别是什么-string-为什么是不可变的) - [13. 自动装箱与拆箱](#13-自动装箱与拆箱) -- [14. 在一个静态方法内调用一个非静态成员为什么是非法的?](#14-在一个静态方法内调用一个非静态成员为什么是非法的?) +- [14. 在一个静态方法内调用一个非静态成员为什么是非法的](#14-在一个静态方法内调用一个非静态成员为什么是非法的) - [15. 在 Java 中定义一个不做事且没有参数的构造方法的作用](#15-在-java-中定义一个不做事且没有参数的构造方法的作用) - [16. import java和javax有什么区别](#16-import-java和javax有什么区别) -- [17. 接口和抽象类的区别是什么?](#17-接口和抽象类的区别是什么?) -- [18. 成员变量与局部变量的区别有那些?](#18-成员变量与局部变量的区别有那些?) +- [17. 接口和抽象类的区别是什么](#17-接口和抽象类的区别是什么) +- [18. 成员变量与局部变量的区别有那些](#18-成员变量与局部变量的区别有那些) - [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符?对象实体与对象引用有何不同?) - [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值?返回值在类的方法里的作用是什么?) -- [21. 一个类的构造方法的作用是什么?若一个类没有声明构造方法,该程序能正确执行吗?为什么?](#21-一个类的构造方法的作用是什么?若一个类没有声明构造方法,该程序能正确执行吗?为什么?) -- [22. 构造方法有哪些特性?](#22-构造方法有哪些特性?) -- [23. 静态方法和实例方法有何不同?](#23-静态方法和实例方法有何不同?) +- [21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 为什么](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法,该程序能正确执行吗-为什么) +- [22. 构造方法有哪些特性](#22-构造方法有哪些特性) +- [23. 静态方法和实例方法有何不同](#23-静态方法和实例方法有何不同) - [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等,两者有什么不同?) - [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?) - [26. == 与 equals\(重要\)](#26--与-equals重要) @@ -37,15 +37,14 @@ - [hashCode()介绍](#hashcode()介绍) - [为什么要有 hashCode](#为什么要有-hashcode) - [hashCode()与equals()的相关规定](#hashcode()与equals()的相关规定) -- [28. Java中的值传递和引用传递](#28-java中的值传递和引用传递) -- [29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么?](#29-简述线程,程序、进程的基本概念。以及他们之间关系是什么?) +- [28. 为什么Java中只有值传递](#28-为什么java中只有值传递) +- [29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么](#29-简述线程,程序、进程的基本概念。以及他们之间关系是什么) - [30. 线程有哪些基本状态?这些状态是如何定义的?](#30-线程有哪些基本状态?这些状态是如何定义的) - [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结) - [Java基础学习书籍推荐](#java基础学习书籍推荐) - ## 1. 面向对象和面向过程的区别 ### 面向过程 @@ -60,7 +59,7 @@ **缺点:** 性能比面向过程低 -## 2. Java 语言有哪些特点? +## 2. Java 语言有哪些特点 1. 简单易学; 2. 面向对象(封装,继承,多态); @@ -71,7 +70,7 @@ 7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便); 8. 编译与解释并存; -## 3. 什么是 JDK?什么是 JRE?什么是 JVM?三者之间的联系与区别 +## 3. 什么是 JDK 什么是 JRE 什么是 JVM 三者之间的联系与区别 这几个是Java中很基本很基本的东西,但是我相信一定还有很多人搞不清楚!为什么呢?因为我们大多数时候在使用现成的编译工具以及环境的时候,并没有去考虑这些东西。 @@ -87,7 +86,7 @@ 2. JDK 和 JRE 中都包含 JVM ; 3. JVM 是 java 编程语言的核心并且具有平台独立性。 -## 4. 什么是字节码?采用字节码的最大好处是什么? +## 4. 什么是字节码 采用字节码的最大好处是什么 ### 先看下 java 中的编译器和解释器:    @@ -115,11 +114,11 @@ Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了 - Java 有自动内存管理机制,不需要程序员手动释放无用内存 -## 6. 什么是 Java 程序的主类?应用程序和小程序的主类有何不同? +## 6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同 一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main()方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。 -## 7. Java 应用程序与小程序之间有那些差别? +## 7. Java 应用程序与小程序之间有那些差别 简单说应用程序是从主线程启动(也就是 main() 方法)。applet 小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟 flash 的小游戏类似。 @@ -164,7 +163,7 @@ Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了 在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。 -## 12. String 和 StringBuffer、StringBuilder 的区别是什么?String 为什么是不可变的? +## 12. String 和 StringBuffer、StringBuilder 的区别是什么 String 为什么是不可变的 **可变性**   @@ -206,7 +205,7 @@ String 中的对象是不可变的,也就可以理解为常量,线程安全 **拆箱**:将包装类型转换为基本数据类型; -## 14. 在一个静态方法内调用一个非静态成员为什么是非法的? +## 14. 在一个静态方法内调用一个非静态成员为什么是非法的 由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。 @@ -219,7 +218,7 @@ String 中的对象是不可变的,也就可以理解为常量,线程安全 所以,实际上java和javax没有区别。这都是一个名字。 -## 17. 接口和抽象类的区别是什么? +## 17. 接口和抽象类的区别是什么 1. 接口的方法默认是 public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法 2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定 @@ -227,7 +226,7 @@ String 中的对象是不可变的,也就可以理解为常量,线程安全 4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 -## 18. 成员变量与局部变量的区别有那些? +## 18. 成员变量与局部变量的区别有那些 1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰; 2. 从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存 @@ -242,17 +241,17 @@ new运算符,new创建对象实例(对象实例在堆内存中),对象 方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作! -## 21. 一个类的构造方法的作用是什么?若一个类没有声明构造方法,该程序能正确执行吗?为什么? +## 21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 为什么 主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。 -## 22. 构造方法有哪些特性? +## 22. 构造方法有哪些特性 1. 名字与类名相同; 2. 没有返回值,但不能用void声明构造函数; 3. 生成类的对象时自动执行,无需调用。 -## 23. 静态方法和实例方法有何不同? +## 23. 静态方法和实例方法有何不同 1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 @@ -330,14 +329,12 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返 5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) -## 28. Java中的值传递和引用传递 - -**值传递**是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。) +## 28. 为什么Java中只有值传递 -**引用传递**是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。) + [为什么Java中只有值传递?](https://github.com/Snailclimb/Java-Guide/blob/master/%E9%9D%A2%E8%AF%95%E5%BF%85%E5%A4%87/%E6%9C%80%E6%9C%80%E6%9C%80%E5%B8%B8%E8%A7%81%E7%9A%84Java%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93/%E7%AC%AC%E4%B8%80%E5%91%A8%EF%BC%882018-8-7%EF%BC%89.md) -## 29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么? +## 29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么 **线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。 From 421f84f9e4e65fad046a7f5878d8650872360501 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 29 Sep 2018 11:23:44 +0800 Subject: [PATCH 062/122] =?UTF-8?q?get=E5=92=8Cpost=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=AE=9E=E9=99=85=E4=B8=8A=E6=98=AF=E6=B2=A1=E6=9C=89=E5=8C=BA?= =?UTF-8?q?=E5=88=AB=EF=BC=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" | 3 +++ 1 file changed, 3 insertions(+) diff --git "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" index 665a441a227..f673332a1ce 100644 --- "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -66,6 +66,9 @@ Servlet接口定义了5个方法,其中**前三个方法与Servlet生命周期 参考:《javaweb整合开发王者归来》P81 ## get和post请求的区别 + +> 网上也有文章说:get和post请求实际上是没有区别,大家可以自行查询相关文章!我下面给出的只是一种常见的答案。 + ①get请求用来从服务器上获得资源,而post是用来向服务器提交数据; ②get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用"?"连接,而各个变量之间使用"&"连接;post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL; From 38690a8bc556954f378db7b7285c4ac309d0db49 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 29 Sep 2018 14:58:50 +0800 Subject: [PATCH 063/122] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=86=85=E5=AE=B9:Ra?= =?UTF-8?q?ndomAccess=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...40\344\271\216\345\277\205\351\227\256.md" | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" "b/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" index 6961e8083da..1184be84c16 100644 --- "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" +++ "b/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" @@ -18,9 +18,38 @@ - **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全; - **2. 底层数据结构:** Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向循环链表数据结构; - **3. 插入和删除是否受元素位置的影响:** ① **ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。** -- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而ArrayList 实现了RandmoAccess 接口,所以有随机访问功能。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。 +- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。 - **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。 +**补充内容:RandomAccess接口** + +```java +public interface RandomAccess { +} +``` + +查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。 + +在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法 + +```java + public static + int binarySearch(List> list, T key) { + if (list instanceof RandomAccess || list.size() Date: Sat, 29 Sep 2018 15:17:55 +0800 Subject: [PATCH 064/122] =?UTF-8?q?String=E3=80=81StringBuffer=E3=80=81Str?= =?UTF-8?q?ingBuilder=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 部分读者说这里没说明白 “String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?” ,我重新讲了一遍1 --- ...254\344\272\214\345\221\250(2018-8-13).md" | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\272\214\345\221\250(2018-8-13).md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\272\214\345\221\250(2018-8-13).md" index 2f6712bd320..88aa2a6ec4a 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\272\214\345\221\250(2018-8-13).md" +++ "b/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\272\214\345\221\250(2018-8-13).md" @@ -5,23 +5,38 @@ **可变性**   -String类中使用字符数组:`private final char value[]`保存字符串,所以String对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[]value,这两种对象都是可变的。 -   + +简单的来说:String 类中使用 final 关键字字符数组保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。 + +StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。 + +AbstractStringBuilder.java + +```java +abstract class AbstractStringBuilder implements Appendable, CharSequence { + char[] value; + int count; + AbstractStringBuilder() { + } + AbstractStringBuilder(int capacity) { + value = new char[capacity]; + } +``` + **线程安全性** -String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。 +String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。    **性能** -每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 +每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 **对于三者使用的总结:** - -- 如果要操作少量的数据用 = String -- 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder -- 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer +1. 操作少量的数据 = String +2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder +3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer #### String为什么是不可变的吗? 简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以: From ec28415ae469f3b23019d833a440099184baf941 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 29 Sep 2018 15:52:28 +0800 Subject: [PATCH 065/122] =?UTF-8?q?tableSizeFor=EF=BC=88=EF=BC=89=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tableSizeFor()方法保证了 HashMap 总是使用2的幂作为哈希表的大小 --- ...40\344\271\216\345\277\205\351\227\256.md" | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" "b/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" index 1184be84c16..59aa4af2820 100644 --- "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" +++ "b/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" @@ -123,9 +123,46 @@ static int hash(int h) { 1. **线程是否安全:** HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!); 2. **效率:** 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它; 3. **对Null key 和Null value的支持:** HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。 -4. **初始容量大小和每次扩充容量大小的不同 :** ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。 +4. **初始容量大小和每次扩充容量大小的不同 :** ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。 5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。 +**HasMap 中带有初始容量的构造函数:** + +```java + public HashMap(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal initial capacity: " + + initialCapacity); + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + this.loadFactor = loadFactor; + this.threshold = tableSizeFor(initialCapacity); + } + public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } +``` + +下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。 + +```java + /** + * Returns a power of two size for the given target capacity. + */ + static final int tableSizeFor(int cap) { + int n = cap - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } +``` + ## HashMap 的长度为什么是2的幂次方 为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648到2147483648,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash` ”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。 From ad8d43372b24416e409ddaad84d7ffe976682df7 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 1 Oct 2018 20:04:32 +0800 Subject: [PATCH 066/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=94=99=E5=88=AB?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "Java\347\233\270\345\205\263/ArrayList-Grow.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/Java\347\233\270\345\205\263/ArrayList-Grow.md" "b/Java\347\233\270\345\205\263/ArrayList-Grow.md" index cb1ffdf5694..79837e34553 100644 --- "a/Java\347\233\270\345\205\263/ArrayList-Grow.md" +++ "b/Java\347\233\270\345\205\263/ArrayList-Grow.md" @@ -340,8 +340,8 @@ public class EnsureCapacityTest { ``` 使用ensureCapacity方法前:4637 -使用ensureCapacity方法前:241 +使用ensureCapacity方法后:241 ``` -通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量从新分配的次数 \ No newline at end of file +通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量从新分配的次数 From e08f674f3eb7dd5bf5e7438291bd9e8e801bd362 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 2 Oct 2018 18:04:07 +0800 Subject: [PATCH 067/122] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=B8=AAbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" index e0a2fa594f7..6d7a484d658 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" @@ -47,7 +47,7 @@ Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去   **MyISAM:** B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。 -   **InnoDB:** 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引(非聚集索引),辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。**在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。** **因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。** PS:整理自《Java工程师修炼之道》 +   **InnoDB:** 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。**在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。** **因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。** PS:整理自《Java工程师修炼之道》 详细内容可以参考: From b49eb03758ebe60c89e09731584b0207ad1e90d7 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 2 Oct 2018 19:52:38 +0800 Subject: [PATCH 068/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E2=80=9CJava?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86=E2=80=9D=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\347\241\200\347\237\245\350\257\206.md" | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 6128c16483f..869ec111bf6 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -380,6 +380,40 @@ final关键字主要用在三个地方:变量、方法、类。 2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。 3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为fianl。 +## Java 中的异常处理 + +### Java异常类层次结构图 + +![Java异常类层次结构图](http://images2015.cnblogs.com/blog/641003/201607/641003-20160706232044280-355354790.png) + 在 Java 中,所有的异常都有一个共同的祖先java.lang包中的 **Throwable类**。Throwable: 有两个重要的子类:**Exception(异常)** 和 **Error(错误)** ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。 + +**Error(错误):是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。 + +这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。 + +**Exception(异常):是程序本身可以处理的异常**。Exception 类有一个重要的子类 **RuntimeException**。RuntimeException 异常由Java虚拟机抛出。**NullPointerException**(要访问的变量没有引用任何对象时,抛出该异常)、**ArithmeticException**(算术运算异常,一个整数除以0时,抛出该异常)和 **ArrayIndexOutOfBoundsException** (下标越界异常)。 + +**注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。** + +### Trowable类常用方法 +**public string getMessage()**:返回异常发生时的详细信息 +**public string toString()**:返回异常发生时的简要描述 +**public string getLocalizedMessage()**:返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同 +**public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息 + +### 异常处理总结 + +- try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。 +- catch 块:用于处理try捕获到的异常。 +- finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。 + +**在以下4种特殊情况下,finally块不会被执行:** + +1. 在finally语句块中发生了异常。 +2. 在前面的代码中用了System.exit()退出程序。 +3. 程序所在的线程死亡。 +4. 关闭CPU。 + # Java基础学习书籍推荐 **《Head First Java.第二版》:** From 715dd1e87d8783566d7bf41bab306783f6ae4fbc Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 2 Oct 2018 19:56:23 +0800 Subject: [PATCH 069/122] =?UTF-8?q?Java=E5=BA=8F=E5=88=97=E8=AF=9D?= =?UTF-8?q?=E4=B8=AD=E5=A6=82=E6=9E=9C=E6=9C=89=E4=BA=9B=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E4=B8=8D=E6=83=B3=E8=BF=9B=E8=A1=8C=E5=BA=8F=E5=88=97=E5=8C=96?= =?UTF-8?q?=20=E6=80=8E=E4=B9=88=E5=8A=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...va\345\237\272\347\241\200\347\237\245\350\257\206.md" | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 869ec111bf6..438c8cecb6e 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -380,7 +380,7 @@ final关键字主要用在三个地方:变量、方法、类。 2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。 3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为fianl。 -## Java 中的异常处理 +## 32 Java 中的异常处理 ### Java异常类层次结构图 @@ -414,6 +414,12 @@ final关键字主要用在三个地方:变量、方法、类。 3. 程序所在的线程死亡。 4. 关闭CPU。 +## 33 Java序列话中如果有些字段不想进行序列化 怎么办 + +对于不想进行序列化的变量,使用transient关键字修饰。 + +transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。 + # Java基础学习书籍推荐 **《Head First Java.第二版》:** From d059609a06e69e06d2f3adfc5ae29ade54ff33df Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 2 Oct 2018 19:56:58 +0800 Subject: [PATCH 070/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Java\345\237\272\347\241\200\347\237\245\350\257\206.md" | 5 +++++ 1 file changed, 5 insertions(+) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 438c8cecb6e..9e0a305547d 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -41,6 +41,11 @@ - [29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么](#29-简述线程,程序、进程的基本概念。以及他们之间关系是什么) - [30. 线程有哪些基本状态?这些状态是如何定义的?](#30-线程有哪些基本状态?这些状态是如何定义的) - [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结) +- [32 Java 中的异常处理](#32-java-中的异常处理) + - [Java异常类层次结构图](#java异常类层次结构图) + - [Trowable类常用方法](#trowable类常用方法) + - [异常处理总结](#异常处理总结) +- [33 Java序列话中如果有些字段不想进行序列化 怎么办](#33-java序列话中如果有些字段不想进行序列化-怎么办) - [Java基础学习书籍推荐](#java基础学习书籍推荐) From c279173450d4ee5e912dfd2eb32b2eced7370f5d Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 2 Oct 2018 19:57:52 +0800 Subject: [PATCH 071/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E6=8E=92=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...a\345\237\272\347\241\200\347\237\245\350\257\206.md" | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 9e0a305547d..14e4bcab954 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -401,10 +401,11 @@ final关键字主要用在三个地方:变量、方法、类。 **注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。** ### Trowable类常用方法 -**public string getMessage()**:返回异常发生时的详细信息 -**public string toString()**:返回异常发生时的简要描述 -**public string getLocalizedMessage()**:返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同 -**public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息 + +- **public string getMessage()**:返回异常发生时的详细信息 +- **public string toString()**:返回异常发生时的简要描述 +- **public string getLocalizedMessage()**:返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同 + **public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息 ### 异常处理总结 From 9ccc997686da5dba71e82c7f5054af88bfbbf0d8 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 2 Oct 2018 19:58:03 +0800 Subject: [PATCH 072/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E6=8E=92=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 33cf696ff92203fe56b7ce003f05c2376e35014b Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 2 Oct 2018 19:59:06 +0800 Subject: [PATCH 073/122] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E6=8E=92=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Java\345\237\272\347\241\200\347\237\245\350\257\206.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 14e4bcab954..efd92085742 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -405,7 +405,7 @@ final关键字主要用在三个地方:变量、方法、类。 - **public string getMessage()**:返回异常发生时的详细信息 - **public string toString()**:返回异常发生时的简要描述 - **public string getLocalizedMessage()**:返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同 - **public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息 +- **public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息 ### 异常处理总结 From 4a987bbfd50e5f514f64888901bbfe0f061f5386 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 6 Oct 2018 22:42:20 +0800 Subject: [PATCH 074/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E4=BA=A7?= =?UTF-8?q?=E5=93=81=E6=8E=A8=E8=8D=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 65bacf3a257..f21bb69ea29 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> **Java学习指南:** 一份涵盖大部分Java程序员所需要掌握的核心知识,正在一步一步慢慢完善,期待您的参与。 +> 推荐几个比较实用的服务:1. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取)、2. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.11533447.1097531.1.48875cfaRbfCkY&userCode=hf47liqn)、3. [最高¥650云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn) | Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | | :--------: | :----------: | :-----------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :----:| From 820d49f2221672d0cd3026a19622434c6ec77e03 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 6 Oct 2018 22:42:32 +0800 Subject: [PATCH 075/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E4=BA=A7?= =?UTF-8?q?=E5=93=81=E6=8E=A8=E8=8D=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 6b6eeb389d0d890b3246e87f75b0417a42d08522 Mon Sep 17 00:00:00 2001 From: Blank Date: Mon, 8 Oct 2018 16:17:05 +0800 Subject: [PATCH 076/122] Fix typo of class name. --- "Java\347\233\270\345\205\263/ArrayList.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/ArrayList.md" "b/Java\347\233\270\345\205\263/ArrayList.md" index 599545820c1..d6792edb241 100644 --- "a/Java\347\233\270\345\205\263/ArrayList.md" +++ "b/Java\347\233\270\345\205\263/ArrayList.md" @@ -22,7 +22,7 @@   ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。 -  ArrayList 实现了**RandmoAccess 接口**,即提供了随机访问功能。RandmoAccess 是 Java 中用来被 List 实现,为 List 提供**快速访问功能**的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。 +  ArrayList 实现了**RandomAccess 接口**,即提供了随机访问功能。RandomAccess 是 Java 中用来被 List 实现,为 List 提供**快速访问功能**的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。   ArrayList 实现了**Cloneable 接口**,即覆盖了函数 clone(),**能被克隆**。 From 52a115771ee4ee2255ef953bf4ebd0064f89b7bc Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Tue, 9 Oct 2018 08:07:15 +0800 Subject: [PATCH 077/122] =?UTF-8?q?=E3=80=90=E6=80=9D=E7=BB=B4=E5=AF=BC?= =?UTF-8?q?=E5=9B=BE-=E7=B4=A2=E5=BC=95=E7=AF=87=E3=80=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【思维导图-索引篇】搞定数据库索引就是这么简单 --- .../MySQL Index.md" | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" new file mode 100644 index 00000000000..dda0ba8bb62 --- /dev/null +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" @@ -0,0 +1,112 @@ + +# 思维导图-索引篇 + +> 系列思维导图源文件(数据库+架构)以及思维导图制作软件—XMind8 破解安装,公众号后台回复:**“思维导图”** 免费领取!(下面的图片不是很清楚,原图非常清晰,另外提供给大家源文件也是为了大家根据自己需要进行修改) + +![【思维导图-索引篇】](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/70973487.jpg) + +> **下面是我补充的一些内容** + +# 为什么索引能提高查询速度 + +> 以下内容整理自: +> 地址: https://juejin.im/post/5b55b842f265da0f9e589e79 +> 作者 :Java3y + +### 先从 MySQL 的基本存储结构说起 + +MySQL的基本存储结构是页(记录都存在页里边): + +![MySQL的基本存储结构是页](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/28559421.jpg) + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/82053134.jpg) + + - **各个数据页可以组成一个双向链表** + - **每个数据页中的记录又可以组成一个单向链表** + - 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录 + - 以其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录。 + +所以说,如果我们写select * from user where indexname = 'xxx'这样没有进行任何优化的sql语句,默认会这样做: + +1. **定位到记录所在的页:需要遍历双向链表,找到所在的页** +2. **从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了** + +很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。 + + +### 使用索引之后 + +索引做了些什么可以让我们查询加快速度呢?其实就是将无序的数据变成有序(相对): + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/5373082.jpg) + +要找到id为8的记录简要步骤: + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/89338047.jpg) + +很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 **“目录”** 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn)) + +其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。 + +# 关于索引其他重要的内容补充 + +> 以下内容整理自:《Java工程师修炼之道》 + + +### 最左前缀原则 + +MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city)o而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下: + +``` +select * from user where name=xx and city=xx ; //可以命中索引 +select * from user where name=xx ; // 可以命中索引 +select * from user where city=xx; // 法命中索引 +``` +这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 `city= xx and name =xx`,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的. + +由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。ORDERBY子句也遵循此规则。 + +### 注意避免冗余索引 + +冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如(name,city )和(name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。 + +MySQLS.7 版本后,可以通过查询 sys 库的 `schemal_r dundant_indexes` 表来查看冗余索引 + +### Mysql如何为表字段添加索引??? + +1.添加PRIMARY KEY(主键索引) + +``` +ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` ) +``` +2.添加UNIQUE(唯一索引) + +``` +ALTER TABLE `table_name` ADD UNIQUE ( `column` ) +``` + +3.添加INDEX(普通索引) + +``` +ALTER TABLE `table_name` ADD INDEX index_name ( `column` ) +``` + +4.添加FULLTEXT(全文索引) + +``` +ALTER TABLE `table_name` ADD FULLTEXT ( `column`) +``` + +5.添加多列索引 + +``` +ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` ) +``` + + +# 参考 + +- 《Java工程师修炼之道》 +- 《MySQL高性能书籍_第3版》 +- https://juejin.im/post/5b55b842f265da0f9e589e79 + \ No newline at end of file From 2227ffa32869a1a4b933cebfa034b190367953af Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 9 Oct 2018 08:09:31 +0800 Subject: [PATCH 078/122] =?UTF-8?q?add:=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E7=B4=A2=E5=BC=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f21bb69ea29..311292a0572 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ ## :floppy_disk: 数据存储 - ### MySQL - [MySQL 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL.md) + - [【思维导图-索引篇】搞定数据库索引就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL%20Index.md) - ### Redis - [Redis 总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redis.md) - [Redlock分布式锁](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis/Redlock分布式锁.md) From 1b3d6af2abe23d8eaec2987a549cbb703215ab71 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 11 Oct 2018 10:23:56 +0800 Subject: [PATCH 079/122] =?UTF-8?q?singleton=E7=9A=84=E5=BB=B6=E8=BF=9F?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpringBean.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" index 32278d88b69..09e3b5d261b 100644 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" @@ -36,7 +36,7 @@ ### 1. singleton——唯一 bean 实例 -**当一个 bean 的作用域为 singleton,那么Spring IoC容器中只会存在一个共享的 bean 实例,并且所有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回bean的同一实例。** singleton 是单例类型(对应于单例模式),就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,singleton 作用域是Spring中的缺省作用域。要在XML中将 bean 定义成 singleton ,可以这样配置: +**当一个 bean 的作用域为 singleton,那么Spring IoC容器中只会存在一个共享的 bean 实例,并且所有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回bean的同一实例。** singleton 是单例类型(对应于单例模式),就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,但我们可以指定Bean节点的 `lazy-init=”true”` 来延迟初始化bean,这时候,只有在第一次获取bean时才会初始化bean,即第一次请求该bean时才初始化。 每次获取到的对象都是同一个对象。注意,singleton 作用域是Spring中的缺省作用域。要在XML中将 bean 定义成 singleton ,可以这样配置: ```xml @@ -446,4 +446,4 @@ this is destory of lifeBean com.bean.LifeBean@573f2bb1 - https://blog.csdn.net/fuzhongmin05/article/details/73389779 - https://yemengying.com/2016/07/14/spring-bean-life-cycle/ -由于本文非本人独立原创,所以未声明为原创!在此说明! \ No newline at end of file +由于本文非本人独立原创,所以未声明为原创!在此说明! From 689098f35c18a1957c9db853b8928d7218c6b267 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 11 Oct 2018 10:24:03 +0800 Subject: [PATCH 080/122] =?UTF-8?q?singleton=E7=9A=84=E5=BB=B6=E8=BF=9F?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From ae5bb0119605279c4ca2da02c9044b40930ba2ef Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 11 Oct 2018 14:27:57 +0800 Subject: [PATCH 081/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 311292a0572..83692283c69 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> 推荐几个比较实用的服务:1. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取)、2. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.11533447.1097531.1.48875cfaRbfCkY&userCode=hf47liqn)、3. [最高¥650云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn) +> 推荐几个比较实用的阿里云服务,按需选择:1. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.11533447.1097531.1.48875cfaRbfCkY&userCode=hf47liqn)、2. [最高¥1000云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn)、3. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) | Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | | :--------: | :----------: | :-----------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :----:| From ff88ca212172f6bfd76f60a644e91078dfba2a35 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 11 Oct 2018 14:57:24 +0800 Subject: [PATCH 082/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 83692283c69..28553692c68 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> 推荐几个比较实用的阿里云服务,按需选择:1. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.11533447.1097531.1.48875cfaRbfCkY&userCode=hf47liqn)、2. [最高¥1000云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn)、3. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) +> 推荐几个比较实用的阿里云服务,按需选择:1. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.11533447.1097531.1.48875cfaRbfCkY&userCode=hf47liqn)、2. [最高¥1888云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn)、3. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) | Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | | :--------: | :----------: | :-----------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :----:| From 9fd08b3a5997c8b7488e31f6fb8a41b86a784517 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 11 Oct 2018 20:14:28 +0800 Subject: [PATCH 083/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E6=B4=BB?= =?UTF-8?q?=E5=8A=A8=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28553692c68..8e032ed18b0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> 推荐几个比较实用的阿里云服务,按需选择:1. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.11533447.1097531.1.48875cfaRbfCkY&userCode=hf47liqn)、2. [最高¥1888云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn)、3. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) +> 推荐几个比较实用的阿里云服务,按需选择:1. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.11533447.1097531.1.48875cfaRbfCkY&userCode=hf47liqn)、2. [最高¥1888云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn)、3. [企业级性能云服务器限时2折起](https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=hf47liqn)、4. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) | Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | | :--------: | :----------: | :-----------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :----:| From f0aa076d49fc259ae566979f20890f59b9c52ec8 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Thu, 11 Oct 2018 20:16:37 +0800 Subject: [PATCH 084/122] =?UTF-8?q?SpringMVC=20=E5=B7=A5=E4=BD=9C=E5=8E=9F?= =?UTF-8?q?=E7=90=86=E8=AF=A6=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...37\347\220\206\350\257\246\350\247\243.md" | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 "\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" new file mode 100644 index 00000000000..502ddba5b09 --- /dev/null +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" @@ -0,0 +1,267 @@ +### 先来看一下什么是 MVC 模式 + +MVC 是一种设计模式. + +**MVC 的原理图如下:** + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/60679444.jpg) + + + +### SpringMVC 简单介绍 + +SpringMVC 框架是以请求为驱动,围绕 Servlet 设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是 DispatcherServlet,它是一个 Servlet,顶层是实现的Servlet接口。 + +### SpringMVC 使用 + +需要在 web.xml 中配置 DispatcherServlet 。并且需要配置 Spring 监听器ContextLoaderListener + +```xml + + + org.springframework.web.context.ContextLoaderListener + + + + springmvc + org.springframework.web.servlet.DispatcherServlet + + + + contextConfigLocation + classpath:spring/springmvc-servlet.xml + + 1 + + + springmvc + / + + +``` + +### SpringMVC 工作原理(重要) + +**简单来说:** + +客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据(Moder)->将得到视图对象返回给用户 + + + +**如下图所示:** +![SpringMVC运行原理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/49790288.jpg) + +上图的一个笔误的小问题:Spring MVC 的入口函数也就是前端控制器 DispatcherServlet 的作用是接收请求,响应结果。 + +**流程说明(重要):** + +(1)客户端(浏览器)发送请求,直接请求到 DispatcherServlet。 + +(2)DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。 + +(3)解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。 + +(4)HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑。 + +(5)处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。 + +(6)ViewResolver 会根据逻辑 View 查找实际的 View。 + +(7)DispaterServlet 把返回的 Model 传给 View(视图渲染)。 + +(8)把 View 返回给请求者(浏览器) + + + +### SpringMVC 重要组件说明 + + +**1、前端控制器DispatcherServlet(不需要工程师开发),由框架提供(重要)** + +作用:**Spring MVC 的入口函数。接收请求,响应结果,相当于转发器,中央处理器。有了 DispatcherServlet 减少了其它组件之间的耦合度。用户请求到达前端控制器,它就相当于mvc模式中的c,DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性。** + +**2、处理器映射器HandlerMapping(不需要工程师开发),由框架提供** + +作用:根据请求的url查找Handler。HandlerMapping负责根据用户请求找到Handler即处理器(Controller),SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。 + +**3、处理器适配器HandlerAdapter** + +作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler +通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。 + +**4、处理器Handler(需要工程师开发)** + +注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler +Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。 +由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。 + +**5、视图解析器View resolver(不需要工程师开发),由框架提供** + +作用:进行视图解析,根据逻辑视图名解析成真正的视图(view) +View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。 +一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。 + +**6、视图View(需要工程师开发)** + +View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...) + +**注意:处理器Handler(也就是我们平常说的Controller控制器)以及视图层view都是需要我们自己手动开发的。其他的一些组件比如:前端控制器DispatcherServlet、处理器映射器HandlerMapping、处理器适配器HandlerAdapter等等都是框架提供给我们的,不需要自己手动开发。** + +### DispatcherServlet详细解析 + +首先看下源码: + +```java +package org.springframework.web.servlet; + +@SuppressWarnings("serial") +public class DispatcherServlet extends FrameworkServlet { + + public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver"; + public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver"; + public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver"; + public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping"; + public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter"; + public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver"; + public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator"; + public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver"; + public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager"; + public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT"; + public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER"; + public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER"; + public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE"; + public static final String INPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP"; + public static final String OUTPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP"; + public static final String FLASH_MAP_MANAGER_ATTRIBUTE = DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER"; + public static final String EXCEPTION_ATTRIBUTE = DispatcherServlet.class.getName() + ".EXCEPTION"; + public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound"; + private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties"; + protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY); + private static final Properties defaultStrategies; + static { + try { + ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); + defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); + } + catch (IOException ex) { + throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage()); + } + } + + /** Detect all HandlerMappings or just expect "handlerMapping" bean? */ + private boolean detectAllHandlerMappings = true; + + /** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */ + private boolean detectAllHandlerAdapters = true; + + /** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */ + private boolean detectAllHandlerExceptionResolvers = true; + + /** Detect all ViewResolvers or just expect "viewResolver" bean? */ + private boolean detectAllViewResolvers = true; + + /** Throw a NoHandlerFoundException if no Handler was found to process this request? **/ + private boolean throwExceptionIfNoHandlerFound = false; + + /** Perform cleanup of request attributes after include request? */ + private boolean cleanupAfterInclude = true; + + /** MultipartResolver used by this servlet */ + private MultipartResolver multipartResolver; + + /** LocaleResolver used by this servlet */ + private LocaleResolver localeResolver; + + /** ThemeResolver used by this servlet */ + private ThemeResolver themeResolver; + + /** List of HandlerMappings used by this servlet */ + private List handlerMappings; + + /** List of HandlerAdapters used by this servlet */ + private List handlerAdapters; + + /** List of HandlerExceptionResolvers used by this servlet */ + private List handlerExceptionResolvers; + + /** RequestToViewNameTranslator used by this servlet */ + private RequestToViewNameTranslator viewNameTranslator; + + private FlashMapManager flashMapManager; + + /** List of ViewResolvers used by this servlet */ + private List viewResolvers; + + public DispatcherServlet() { + super(); + } + + public DispatcherServlet(WebApplicationContext webApplicationContext) { + super(webApplicationContext); + } + @Override + protected void onRefresh(ApplicationContext context) { + initStrategies(context); + } + + protected void initStrategies(ApplicationContext context) { + initMultipartResolver(context); + initLocaleResolver(context); + initThemeResolver(context); + initHandlerMappings(context); + initHandlerAdapters(context); + initHandlerExceptionResolvers(context); + initRequestToViewNameTranslator(context); + initViewResolvers(context); + initFlashMapManager(context); + } +} + +``` + +DispatcherServlet类中的属性beans: + +- HandlerMapping:用于handlers映射请求和一系列的对于拦截器的前处理和后处理,大部分用@Controller注解。 +- HandlerAdapter:帮助DispatcherServlet处理映射请求处理程序的适配器,而不用考虑实际调用的是 哪个处理程序。- - - +- ViewResolver:根据实际配置解析实际的View类型。 +- ThemeResolver:解决Web应用程序可以使用的主题,例如提供个性化布局。 +- MultipartResolver:解析多部分请求,以支持从HTML表单上传文件。- +- FlashMapManager:存储并检索可用于将一个请求属性传递到另一个请求的input和output的FlashMap,通常用于重定向。 + +在Web MVC框架中,每个DispatcherServlet都拥自己的WebApplicationContext,它继承了ApplicationContext。WebApplicationContext包含了其上下文和Servlet实例之间共享的所有的基础框架beans。 + +**HandlerMapping** + +![HandlerMapping](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/96666164.jpg) + +HandlerMapping接口处理请求的映射HandlerMapping接口的实现类: + +- SimpleUrlHandlerMapping类通过配置文件把URL映射到Controller类。 +- DefaultAnnotationHandlerMapping类通过注解把URL映射到Controller类。 + +**HandlerAdapter** + + +![HandlerAdapter](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/91433100.jpg) + +HandlerAdapter接口-处理请求映射 + +AnnotationMethodHandlerAdapter:通过注解,把请求URL映射到Controller类的方法上。 + +**HandlerExceptionResolver** + + +![HandlerExceptionResolver](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/50343885.jpg) + +HandlerExceptionResolver接口-异常处理接口 + +- SimpleMappingExceptionResolver通过配置文件进行异常处理。 +- AnnotationMethodHandlerExceptionResolver:通过注解进行异常处理。 + +**ViewResolver** + +![ViewResolver](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/49497279.jpg) + +ViewResolver接口解析View视图。 + +UrlBasedViewResolver类 通过配置文件,把一个视图名交给到一个View来处理。 \ No newline at end of file From a6c6aaaee5af186f0bf55bd73bc75067202b7cff Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 11 Oct 2018 20:21:15 +0800 Subject: [PATCH 085/122] =?UTF-8?q?add=20SpringMVC=20=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E5=8E=9F=E7=90=86=E8=AF=A6=E8=A7=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e032ed18b0..02623f5249c 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ - ### Spring - [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) - [Spring中bean的作用域与生命周期](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/SpringBean.md) - + - [SpringMVC 工作原理详解](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/SpringMVC 工作原理详解.md) - ### ZooKeeper - [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) From 41f5755976a6cfd7acbdee2c31022ec27a9afd6a Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Thu, 11 Oct 2018 20:27:43 +0800 Subject: [PATCH 086/122] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20SpringMVC=20?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E5=8E=9F=E7=90=86=E8=AF=A6=E8=A7=A3=20?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 02623f5249c..4eda41425bb 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ - ### Spring - [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) - [Spring中bean的作用域与生命周期](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/SpringBean.md) - - [SpringMVC 工作原理详解](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/SpringMVC 工作原理详解.md) + - [SpringMVC 工作原理详解](https://github.com/Snailclimb/JavaGuide/blob/master/主流框架/SpringMVC%20%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3.md) - ### ZooKeeper - [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) From 8c05e2216baa739149ecd2108a0845c3311b8ea9 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Fri, 12 Oct 2018 07:52:07 +0800 Subject: [PATCH 087/122] =?UTF-8?q?Update=20SpringMVC=20=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E5=8E=9F=E7=90=86=E8=AF=A6=E8=A7=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...75\234\345\216\237\347\220\206\350\257\246\350\247\243.md" | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" index 502ddba5b09..0efcd3f9534 100644 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" @@ -1,3 +1,5 @@ +> 本文整理自网络,原文出处暂不知,对原文做了较大的改动,在此说明! + ### 先来看一下什么是 MVC 模式 MVC 是一种设计模式. @@ -264,4 +266,4 @@ HandlerExceptionResolver接口-异常处理接口 ViewResolver接口解析View视图。 -UrlBasedViewResolver类 通过配置文件,把一个视图名交给到一个View来处理。 \ No newline at end of file +UrlBasedViewResolver类 通过配置文件,把一个视图名交给到一个View来处理。 From 386bac0425c51b7bea485532d0e13478f946bdd5 Mon Sep 17 00:00:00 2001 From: feng qijun Date: Sat, 13 Oct 2018 23:44:51 +0800 Subject: [PATCH 088/122] =?UTF-8?q?Update=EF=BC=9A=20=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=92=8C=E6=8A=BD=E8=B1=A1=E7=B1=BB=E7=9A=84=E5=8C=BA=E5=88=AB?= =?UTF-8?q?=E6=98=AF=E4=BB=80=E4=B9=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Java 8 开始接口方法可以有默认实现 --- .../Java\345\237\272\347\241\200\347\237\245\350\257\206.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index efd92085742..222e09aca41 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -225,7 +225,7 @@ String 中的对象是不可变的,也就可以理解为常量,线程安全 ## 17. 接口和抽象类的区别是什么 -1. 接口的方法默认是 public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法 +1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法 2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定 3. 一个类可以实现多个接口,但最多只能实现一个抽象类 4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 From 0b39a2176d2bd8c07dbb1d5999cc233b4f8fe9dc Mon Sep 17 00:00:00 2001 From: feng qijun Date: Sun, 14 Oct 2018 00:01:42 +0800 Subject: [PATCH 089/122] =?UTF-8?q?Update=20Java=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E7=9F=A5=E8=AF=86.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix typo --- .../Java\345\237\272\347\241\200\347\237\245\350\257\206.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index efd92085742..11a83b87efd 100644 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -400,7 +400,7 @@ final关键字主要用在三个地方:变量、方法、类。 **注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。** -### Trowable类常用方法 +### Throwable类常用方法 - **public string getMessage()**:返回异常发生时的详细信息 - **public string toString()**:返回异常发生时的简要描述 From e70aa7d449b4e664b806e7741ce519e94485eeb1 Mon Sep 17 00:00:00 2001 From: lewis Date: Sun, 14 Oct 2018 15:59:09 +0800 Subject: [PATCH 090/122] =?UTF-8?q?fix=20:=20=E4=BF=AE=E5=A4=8D=E6=8B=BC?= =?UTF-8?q?=E5=86=99=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" "b/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" index 59aa4af2820..f934d914d5f 100644 --- "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" +++ "b/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" @@ -224,7 +224,7 @@ Node: 链表节点): 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 -**ConcurrentHashMap 是由 Segment 数组结构和 HahEntry 数组结构组成**。 +**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。 Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。 From fa64796572dac4dad017067dad565145a962e81f Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sun, 14 Oct 2018 22:51:20 +0800 Subject: [PATCH 091/122] =?UTF-8?q?Gitchat=E6=8E=A8=E8=8D=90:Java=20?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E5=BF=85=E5=A4=87=EF=BC=9A=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E7=9F=A5=E8=AF=86=E7=B3=BB=E7=BB=9F=E6=80=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4eda41425bb..df514d63dcc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ > 推荐几个比较实用的阿里云服务,按需选择:1. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.11533447.1097531.1.48875cfaRbfCkY&userCode=hf47liqn)、2. [最高¥1888云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn)、3. [企业级性能云服务器限时2折起](https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=hf47liqn)、4. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) +Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) + | Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | | :--------: | :----------: | :-----------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :----:| | [Java](#coffee-Java) | [数据结构与算法](#open_file_folder-数据结构与算法)|[计算机网络与数据通信](#computer-计算机网络与数据通信) | [操作系统](#iphone-操作系统)| [主流框架](#pencil2-主流框架)| [数据存储](#floppy_disk-数据存储)|[架构](#punch-架构)| [面试必备](#musical_note-面试必备)| [其他](#art-其他)| [说明](#envelope-该开源文档一些说明)| From 139350b55bb0200b6d8679bdc80d26db330b4fc7 Mon Sep 17 00:00:00 2001 From: Nightingale07 <42062214+Nightingale07@users.noreply.github.com> Date: Thu, 18 Oct 2018 10:22:01 +0800 Subject: [PATCH 092/122] Update LinkedList.md --- "Java\347\233\270\345\205\263/LinkedList.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/Java\347\233\270\345\205\263/LinkedList.md" "b/Java\347\233\270\345\205\263/LinkedList.md" index bf5e55ab8e4..15f67f88a00 100644 --- "a/Java\347\233\270\345\205\263/LinkedList.md" +++ "b/Java\347\233\270\345\205\263/LinkedList.md" @@ -28,8 +28,8 @@ List list=Collections.synchronizedList(new LinkedList(...)); ```java private static class Node { E item;//节点值 - Node next;//前驱节点 - Node prev;//后继节点 + Node next;//后继节点 + Node prev;//前驱节点 Node(Node prev, E element, Node next) { this.item = element; From 29d2e7ca04a831b335b64556f100f1ef46bf313f Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sat, 20 Oct 2018 12:10:11 +0800 Subject: [PATCH 093/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df514d63dcc..d4c4d672951 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> 推荐几个比较实用的阿里云服务,按需选择:1. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.11533447.1097531.1.48875cfaRbfCkY&userCode=hf47liqn)、2. [最高¥1888云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn)、3. [企业级性能云服务器限时2折起](https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=hf47liqn)、4. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) +> 推荐几个比较实用的阿里云服务,按需选择:1. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?userCode=hf47liqn)、2. [最高¥1888云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn)、3. [企业级性能云服务器限时2折起](https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=hf47liqn)、4. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) From ecce9b3963018387469ff1854e430855402a67b0 Mon Sep 17 00:00:00 2001 From: Snailclimb Date: Wed, 24 Oct 2018 15:30:28 +0800 Subject: [PATCH 094/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E2=80=9C=E7=A7=8B?= =?UTF-8?q?=E6=8B=9B=E6=80=BB=E7=BB=93=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2018 \347\247\213\346\213\233.md" | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 "\345\205\266\344\273\226/2018 \347\247\213\346\213\233.md" diff --git "a/\345\205\266\344\273\226/2018 \347\247\213\346\213\233.md" "b/\345\205\266\344\273\226/2018 \347\247\213\346\213\233.md" new file mode 100644 index 00000000000..a2b47de8eee --- /dev/null +++ "b/\345\205\266\344\273\226/2018 \347\247\213\346\213\233.md" @@ -0,0 +1,93 @@ + + +# 秋招历程流水账总结 + +笔主大四准毕业生,在秋招末流比较幸运地进入了一家自己非常喜欢一家公司——ThoughtWorks. + +![今天去签约在门外拍的照片](https://images.gitbook.cn/1433af10-d5f7-11e8-841a-4f0b0cc7be7b) + +从9-6号投递出去第一份简历,到10-18号左右拿到第一份 offer ,中间差不多有 1 个半月的时间了。可能自己比较随缘,而且自己所在的大学所处的位置并不是互联网比较发达的城市的原因。所以,很少会有公司愿意跑到我们学校那边来宣讲,来的公司也大多是一些自己没听过或者不太喜欢的公司。所以,在前期,我仅仅能够通过网上投递简历的方式来找工作。 + +零零总总算了一下,自己在网上投了大概有 10 份左右的简历,都是些自己还算喜欢的公司。简单说一下自己投递的一些公司:网上投递的公司有:ThoughtWorks、网易、小米、携程、爱奇艺、知乎、小红书、搜狐、欢聚时代、京东;直接邮箱投递的有:烽火、中电数据、蚂蚁金服花呗部门、今日头条;线下宣讲会投递的有:玄武科技。 + +网上投递的大部分简历都是在做完笔试之后就没有了下文了,即使有几场笔试自我感觉做的很不错的情况下,还是没有收到后续的面试邀请。还有些邮箱投递的简历,后面也都没了回应。所以,我总共也只参加了3个公司的面试,ThoughtWorks、玄武科技和中电数据,都算是拿到了 offer。拿到 ThoughtWorks 的 offer之后,后面的一些笔试和少部分面试都拒了。决定去 ThoughtWorks 了,春招的大部队会没有我的存在。 + + +我个人对 ThoughtWorks 最有好感,ThoughtWorks 也是我自己之前很想去的一家公司。不光是因为我投递简历的时候可以不用重新填一遍表格可以直接发送我已经编辑好的PDF格式简历的友好,这个公司的文化也让我很喜欢。每次投递一家公司几乎都要重新填写一遍简历真的很让人头疼,即使是用牛客网的简历助手也还是有很多东西需要自己重新填写。 + +说句实话,自己在拿到第一份 offer 之前心里还是比较空的,虽然说对自己还是比较自信。包括自己当时来到武汉的原因,也是因为自己没有 offer ,就感觉心里空空的,我相信很多人在这个时候与我也有一样的感觉。然后,我就想到武汉参加一下别的学校宣讲会。现在看来,这个决定也是不必要的,因为我最后去的公司 ThoughtWorks,虽然就在我租的房子的附近,但之前投递的时候,选择的还是远程面试。来到武汉,简单的修整了一下之后,我就去参加了玄武科技在武理工的宣讲会,顺便做了笔试,然后接着就是技术面、HR面、高管面。总体来说,玄武科技的 HR 真的很热情,为他们点个赞,虽然自己最后没能去玄武科技,然后就是技术面非常简单,HR面和高管面也都还好,不会有压抑的感觉,总体聊得很愉快。需要注意的是 玄武科技和很多公司一样都有笔试中有逻辑题,我之前没有做过类似的题,所以当时第一次做有点懵逼。高管面的时候,高管还专门在我做的逻辑题上聊了一会,让我重新做了一些做错的题,并且给他讲一些题的思路,可以看出高层对于应聘者的这项能力还是比较看重的。 + + + +中电数据的技术面试是电话进行的,花了1个多小时一点,个人感觉问的还是比较深的,感觉自己总体回答的还是比较不错的。 + +这里我着重说一下 ThoughtWorks,也算是给想去 ThoughtWorks 的同学一点小小的提示。我是 9.11 号在官网:https://join.thoughtworks.cn/ 投递的简历,9.20 日邮件通知官网下载作业,作业总体来说不难,9.21 号花了半天多的时间做完,然后就直接在9.21 号下午提交了。然后等了挺长时间的,可能是因为 ThoughtWorks 在管理方面比较扁平化的原因,所以总体来说效率可能不算高。因为我选的是远程面试,所以直接下载好 zoom 之后,等HR打电话过来告诉你一个房间号,你就可以直接进去面试就好,一般技术面试有几个人看着你。技术面试的内容,首先就是在面试官让你在你之前做的作业的基础上新增加一个或者两个功能(20分钟)。所以,你在技术面试之前一定要保证你的程序的扩展性是不错的,另外就是你在技术面试之前最好能重构一下自己写的程序。重构本身就是你自己对你写的程序的理解加强很好的一种方式,另外重构也能让你发现你的程序的一些小问题。然后,这一步完成之后,面试官可能会问你一些基础问题,比较简单,所以我觉得 ThoughtWorks 可能更看重你的代码质量。ThoughtWorks 的 HR 面和其他公司的唯一不同可能在于,他会让你用英语介绍一下自己或者说自己的技术栈啊这些。 + +![思特沃克可爱的招聘官网](https://images.gitbook.cn/83f765e0-d5f6-11e8-9c1a-919e09988420) + + +# 关于面试一些重要的问题总结 +另外,再给大家总结一些我个人想到一些关于面试非常重要的一些问题。 + +### 面试前 + +**如何准备** + + +运筹帷幄之后,决胜千里之外!不打毫无准备的仗,我觉得大家可以先从下面几个方面来准备面试: + +1. 自我介绍。(你可千万这样介绍:“我叫某某,性别,来自哪里,学校是那个,自己爱干什么”,记住:多说点简历上没有的,多说点自己哪里比别人强!) +2. 自己面试中可能涉及哪些知识点、那些知识点是重点。 +3. 面试中哪些问题会被经常问到、面试中自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) +4. 自己的简历该如何写。 + + + +另外,如果你想去类似阿里巴巴、腾讯这种比较大的互联网公司的话,一定要尽早做打算。像阿里巴巴在7月份左右就开始了提前批招聘,到了9月份差不多就已经招聘完毕了。所以,秋招没有参加到阿里的面试还是很遗憾的,毕竟面试即使失败了,也能从阿里难度Max的面试中学到很多东西。 + +**关于着装** + +穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。 + +**关于自我介绍** + +如果你简历上写的基本信息就不要说了,比如性别、年龄、学校。另外,你也不要一上来就说自己爱好什么这方面内容。因为,面试官根本不关心这些东西。你直接挑和你岗位相关的重要经历和自己最突出的特点讲就好了。 + + + +**提前准备** + +面试之前可以在网上找找有没有你要面试的公司的面经。在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。 + + +### 面试中 + +面试的时候一定要自信,千万不要怕自己哪里会答不出来,或者说某个问题自己忘记怎么回答了。面试过程中,很多问题可能是你之前没有碰到过的,这个时候你就要通过自己构建的知识体系来思考这些问题。如果某些问题你回答不上来,你也可以让面试官给你简单的提示一下。总之,你要自信,你自信的前提是自己要做好充分的准备。下面给大家总结一些面试非常常见的问题: + +- SpringMVC 工作原理 +- 说一下自己对 IOC 、AOP 的理解 +- Spring 中用到了那些设计模式,讲一下自己对于这些设计模式的理解 +- Spring Bean 的作用域和生命周期了解吗 +- Spring 事务中的隔离级别 +- Spring 事务中的事务传播行为 +- 手写一个 LRU 算法 +- 知道那些排序算法,简单介绍一下快排的原理,能不能手写一下快排 +- String 为什么是不可变的?String为啥要设计为不可变的? +- Arraylist 与 LinkedList 异同 +- HashMap的底层实现 +- HashMap 的长度为什么是2的幂次方 +- ConcurrentHashMap 和 Hashtable 的区别 +- ConcurrentHashMap线程安全的具体实现方式/底层具体实现 +- 如果你的简历写了redis 、dubbo、zookeeper、docker的话,面试官还会问一下这些东西。比如redis可能会问你:为什么要用 redis、为什么要用 redis 而不用 map/guava 做缓存、redis 常见数据结构以及使用场景分析、 redis 设置过期时间、redis 内存淘汰机制、 redis 持久化机制、 缓存雪崩和缓存穿透问题、如何解决 Redis 的并发竞争 Key 问题、如何保证缓存与数据库双写时的数据一致性。 +- 一些简单的 Linux 命令。 +- 为什么要用 消息队列 +- 关于 Java多线程,在面试的时候,问的比较多的就是①悲观锁和乐观锁②synchronized 和 ReenTrantLock 区别以及 volatile 和 synchronized 的区别,③可重入锁与非可重入锁的区别、④多线程是解决什么问题的、⑤线程池解决什么问题,为什么要用线程池 ⑥Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 ReenTrantLock 对比;⑦线程池使用时的注意事项、⑧AQS 原理以及 AQS 同步组件:Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock、⑨ReentranLock源码,设计原理,整体过程 等等问题。 +- 关于 Java 虚拟机问的比较多的是:①Java内存区域、②虚拟机垃圾算法、③虚拟机垃圾收集器、④JVM内存管理、⑤JVM调优这些问题。 + + +### 面试后 + +如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! + + + From ab2c176a2991b5102cf45ca8af4d15e9db7bf701 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Wed, 24 Oct 2018 15:34:17 +0800 Subject: [PATCH 095/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=EF=BC=9A=E2=80=9C?= =?UTF-8?q?=E7=A7=8B=E6=8B=9B=E6=80=BB=E7=BB=93=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 个人秋招流水账式总结! --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d4c4d672951..068ddcd191c 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,12 @@ Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbo - [个人阅读书籍清单](https://github.com/Snailclimb/Java-Guide/blob/master/其他/个人阅读书籍清单.md) - ### 技术方向选择 - - [选择技术方向都要考虑哪些因素](https://github.com/Snailclimb/Java-Guide/blob/master/其他/选择技术方向都要考虑哪些因素.md) + - [选择技术方向都要考虑哪些因素](https://github.com/Snailclimb/Java-Guide/blob/master/其他/选择技术方向都要考虑哪些因素.md) +- ### 2018 年秋招简单回顾 + - [结束了我短暂的秋招,说点自己的感受](https://github.com/Snailclimb/JavaGuide/blob/master/%E5%85%B6%E4%BB%96/2018%20%E7%A7%8B%E6%8B%9B.md) + + *** > # :envelope: 该开源文档一些说明 From fd508087d1fdcb6ef8a425ee04111c793f8bd8f3 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Wed, 24 Oct 2018 15:57:53 +0800 Subject: [PATCH 096/122] =?UTF-8?q?=E8=A1=A5=E5=85=85=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=B9=A6=E7=B1=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...73\344\271\246\347\261\215\346\270\205\345\215\225.md" | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git "a/\345\205\266\344\273\226/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" "b/\345\205\266\344\273\226/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" index f8cbf587ec5..fb53c0ff164 100644 --- "a/\345\205\266\344\273\226/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" +++ "b/\345\205\266\344\273\226/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" @@ -30,6 +30,9 @@ - :thumbsup: [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/) 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。 +- :thumbsup: [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/) + + 豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。 - [《Java程序员修炼之道》](https://book.douban.com/subject/24841235/) @@ -67,6 +70,11 @@ 很一般的书籍,我就是当做课后图书来阅读的。 +> ### 代码优化 + +- :thumbsup: [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/) + + 豆瓣 9.1 分,重构书籍的开山鼻祖。 > ### 课外书籍 From d3b8cb38df0da5bdbd1d18e067cb26b2a9345fc4 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Fri, 26 Oct 2018 08:04:12 +0800 Subject: [PATCH 097/122] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加和完善了一些内容 --- .../synchronized.md" | 145 ++++++++++++------ 1 file changed, 98 insertions(+), 47 deletions(-) diff --git "a/Java\347\233\270\345\205\263/synchronized.md" "b/Java\347\233\270\345\205\263/synchronized.md" index 25126b7a14e..5fe69ea5581 100644 --- "a/Java\347\233\270\345\205\263/synchronized.md" +++ "b/Java\347\233\270\345\205\263/synchronized.md" @@ -1,31 +1,105 @@ -本文是对 synchronized 关键字使用、底层原理、JDK1.6之后的底层优化以及和ReenTrantLock对比做的总结。如果没有学过 synchronized 关键字使用的话,阅读起来可能比较费力。两篇比较基础的讲解 synchronized 关键字的文章: +### synchronized关键字最主要的三种使用方式的总结 -- [《Java多线程学习(二)synchronized关键字(1)》](https://blog.csdn.net/qq_34337272/article/details/79655194) -- [《Java多线程学习(二)synchronized关键字(2)》](https://blog.csdn.net/qq_34337272/article/details/79670775) +- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** +- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 +- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能! -# synchronized 关键字的总结 +下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 -## synchronized关键字最主要的三种使用方式的总结 +面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单利模式的原理呗!” -- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** -- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 -- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能! -## synchronized 关键字底层实现原理总结 -- **synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 -- **synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。** 在 Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 +**双重校验锁实现对象单例(线程安全)** + +```java +public class Singleton { + + private volatile static Singleton uniqueInstance; + + private Singleton() { + } + + public static Singleton getUniqueInstance() { + //先判断对象是否已经实例过,没有实例化过才进入加锁代码 + if (uniqueInstance == null) { + //类对象加锁 + synchronized (Singleton.class) { + if (uniqueInstance == null) { + uniqueInstance = new Singleton(); + } + } + } + return uniqueInstance; + } +} +``` +另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。 + +uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行: + +1. 为 uniqueInstance 分配内存空间 +2. 初始化 uniqueInstance +3. 将 uniqueInstance 指向分配的内存地址 + +但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。 + +使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 + + +###synchronized 关键字底层原理总结 + + + +**synchronized 关键字底层原理属于 JVM 层面。** + +**① synchronized 同步语句块的情况** + +```java +public class SynchronizedDemo { + public void method() { + synchronized (this) { + System.out.println("synchronized 代码块"); + } + } +} + +``` + +通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。 + +![synchronized 关键字原理](https://images.gitbook.cn/abc37c80-d21d-11e8-aab3-09d30029e0d5) + +从上面我们可以看出: - > 所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等. 而唯一可以做这些事情的就是操作系统, +**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 +**② synchronized 修饰方法的的情况** -## synchronized关键字底层优化总结 +```java +public class SynchronizedDemo2 { + public synchronized void method() { + System.out.println("synchronized 方法"); + } +} -JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 +``` + +![synchronized 关键字原理](https://images.gitbook.cn/7d407bf0-d21e-11e8-b2d6-1188c7e0dd7e) + +synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 + + +在 Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 + + +### JDK1.6 之后的底层优化 + +JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 -### 偏向锁 +**①偏向锁** **引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉**。 @@ -33,13 +107,13 @@ JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋 但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。 -### 轻量级锁 +**② 轻量级锁** 倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。**轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。** 关于轻量级锁的加锁和解锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。 **轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁!** -### 自旋锁和自适应自旋 +**③ 自旋锁和自适应自旋** 轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。 @@ -55,29 +129,28 @@ JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋 另外,**在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了**。 -### 锁消除 +**④ 锁消除** 锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。 -### 锁粗化 +**⑤ 锁粗化** 原则上,我们再编写代码的时候,总是推荐将同步快的作用范围限制得尽量小——只在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。 -## ReenTrantLock 和 synchronized 关键字的总结 +### Synchronized 和 ReenTrantLock 的对比 -推荐一篇讲解 ReenTrantLock 的使用比较基础的文章:[《Java多线程学习(六)Lock锁的使用》](https://blog.csdn.net/qq_34337272/article/details/79714196) -### 两者都是可重入锁 +**① 两者都是可重入锁** 两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 -### synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API +**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 -### ReenTrantLock 比 synchronized 增加了一些高级功能 +**③ ReenTrantLock 比 synchronized 增加了一些高级功能** 相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** @@ -87,28 +160,6 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团 如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。 -### 性能已不是选择标准 +**④ 性能已不是选择标准** 在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。**JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作**。 - - -## 参考 - -- 《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版第13章 -- 《实战Java虚拟机》 -- https://blog.csdn.net/javazejian/article/details/72828483#commentBox -- https://blog.csdn.net/qq838642798/article/details/65441415 -- http://cmsblogs.com/?p=2071 - - - - - - - - - - - - - From d366ee5146ae63e5d5c32e5f6142ca7d0a73f3e0 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Fri, 26 Oct 2018 08:08:27 +0800 Subject: [PATCH 098/122] =?UTF-8?q?=E6=AC=A2=E8=BF=8E=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E6=88=91=E7=9A=84Java=E5=B9=B6=E5=8F=91=E6=80=BB=E7=BB=93?= =?UTF-8?q?=E7=9A=84=20Gitchat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "Java\347\233\270\345\205\263/synchronized.md" | 4 ++++ 1 file changed, 4 insertions(+) diff --git "a/Java\347\233\270\345\205\263/synchronized.md" "b/Java\347\233\270\345\205\263/synchronized.md" index 5fe69ea5581..6d729d4b1dc 100644 --- "a/Java\347\233\270\345\205\263/synchronized.md" +++ "b/Java\347\233\270\345\205\263/synchronized.md" @@ -1,3 +1,7 @@ +以下内容摘自我的 Gitchat :[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb),欢迎订阅! + +Github 地址:[https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/synchronized.md](https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/synchronized.md) + ### synchronized关键字最主要的三种使用方式的总结 - **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** From 6a5f7d6db995a20ccbf1b46dcb1abedb5bb17624 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Fri, 26 Oct 2018 08:09:06 +0800 Subject: [PATCH 099/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 068ddcd191c..0e07edd35f3 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbo - ### Java 多线程 - [多线程系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/多线程系列.md) - - [值得立马保存的 synchronized 关键字总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/synchronized.md) + - [Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/synchronized.md) - ### Java IO 与 NIO - [Java IO 与 NIO系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java%20IO与NIO.md) From 87ee3a1d86fe96cdbe7dad381b29d02f4532da78 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Fri, 26 Oct 2018 08:46:08 +0800 Subject: [PATCH 100/122] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=80=9D=E7=BB=B4?= =?UTF-8?q?=E5=AF=BC=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "Java\347\233\270\345\205\263/synchronized.md" | 2 ++ 1 file changed, 2 insertions(+) diff --git "a/Java\347\233\270\345\205\263/synchronized.md" "b/Java\347\233\270\345\205\263/synchronized.md" index 6d729d4b1dc..dfca675f14a 100644 --- "a/Java\347\233\270\345\205\263/synchronized.md" +++ "b/Java\347\233\270\345\205\263/synchronized.md" @@ -2,6 +2,8 @@ Github 地址:[https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/synchronized.md](https://github.com/Snailclimb/JavaGuide/edit/master/Java相关/synchronized.md) +![Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/%E4%BA%8C%20%20Synchronized%20%E5%85%B3%E9%94%AE%E5%AD%97%E4%BD%BF%E7%94%A8%E3%80%81%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86%E3%80%81JDK1.6%20%E4%B9%8B%E5%90%8E%E7%9A%84%E5%BA%95%E5%B1%82%E4%BC%98%E5%8C%96%E4%BB%A5%E5%8F%8A%20%E5%92%8CReenTrantLock%20%E7%9A%84%E5%AF%B9%E6%AF%94.png) + ### synchronized关键字最主要的三种使用方式的总结 - **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** From d2008230e309e14c1253fe9bda2bfb03f4b360a1 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sun, 28 Oct 2018 23:45:59 +0800 Subject: [PATCH 101/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e07edd35f3..45845a92e45 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ -> 推荐几个比较实用的阿里云服务,按需选择:1. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?userCode=hf47liqn)、2. [最高¥1888云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn)、3. [企业级性能云服务器限时2折起](https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=hf47liqn)、4. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) +> 阿里云技术有保障,在云服务技术上远远领先于国内其他云服务提供商。大家或者公司如果需要用到云服务器的话,推进阿里云服务器,下面是阿里云目前正在做的一些活动: + +1. [全民云计算:ECS云服务器2折起,1核1G仅需293元/年](https://promotion.aliyun.com/ntms/act/qwbk.html?userCode=hf47liqn) +2. [高性能企业级性能云服务器限时2折起,2核4G仅需720元/年](https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=hf47liqn) +3. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.8849694.home.4.27a24b70kENhtV&userCode=hf47liqn) +4. [最高¥1888云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn) +5. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) From 3575fa2dab79e9bc46959e8047e7985f7cdc15d1 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Sun, 28 Oct 2018 23:46:49 +0800 Subject: [PATCH 102/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45845a92e45..cc26c8a6b5e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> 阿里云技术有保障,在云服务技术上远远领先于国内其他云服务提供商。大家或者公司如果需要用到云服务器的话,推进阿里云服务器,下面是阿里云目前正在做的一些活动: +> 阿里云技术有保障,在云服务技术上远远领先于国内其他云服务提供商。大家或者公司如果需要用到云服务器的话,推荐阿里云服务器,下面是阿里云目前正在做的一些活动,错过这波,后续可能多花很多钱: 1. [全民云计算:ECS云服务器2折起,1核1G仅需293元/年](https://promotion.aliyun.com/ntms/act/qwbk.html?userCode=hf47liqn) 2. [高性能企业级性能云服务器限时2折起,2核4G仅需720元/年](https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=hf47liqn) From b28487e61ed6ee7c0fa06746c37648a24853d761 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 29 Oct 2018 12:46:58 +0800 Subject: [PATCH 103/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E5=8F=8C11?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E6=B4=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cc26c8a6b5e..998ea2d9b26 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn 阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!举个栗子:以原价1020元的云服务器ECS(t5 1c2g+1M+40G高效云盘) 为例,双11新用户专享价为199元,如果您成功拉一个新人拼团,您和团员都将享受折上9折优惠,199*90%=179.1元,将返还您和团员每人19.9元。随着您拉新人数增多,享受折扣将不断叠加, 当您参加的团中有6个及以上新用户时,您和团员即可享受折上5折优惠,即199元的云服务器再打5折,可享受99.5元的优惠价,将返还您和团员购买价减去99.5元的差价。希望大家加入拼团之后尽自己力量邀请时候身边的阿里云新人参团,这样你可以获得最高1111的红包,而且后面如果团队拉的新人在前100名的话就可以平分100w红包。老用户也可以选择参加下面的活动! + > 阿里云技术有保障,在云服务技术上远远领先于国内其他云服务提供商。大家或者公司如果需要用到云服务器的话,推荐阿里云服务器,下面是阿里云目前正在做的一些活动,错过这波,后续可能多花很多钱: 1. [全民云计算:ECS云服务器2折起,1核1G仅需293元/年](https://promotion.aliyun.com/ntms/act/qwbk.html?userCode=hf47liqn) From 53feb9a7bc2f80c8e2f74b8b905046d81a44ba3d Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 29 Oct 2018 12:56:39 +0800 Subject: [PATCH 104/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E5=8F=8C11?= =?UTF-8?q?=E6=B4=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 998ea2d9b26..bb18e14725c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn 阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!举个栗子:以原价1020元的云服务器ECS(t5 1c2g+1M+40G高效云盘) 为例,双11新用户专享价为199元,如果您成功拉一个新人拼团,您和团员都将享受折上9折优惠,199*90%=179.1元,将返还您和团员每人19.9元。随着您拉新人数增多,享受折扣将不断叠加, 当您参加的团中有6个及以上新用户时,您和团员即可享受折上5折优惠,即199元的云服务器再打5折,可享受99.5元的优惠价,将返还您和团员购买价减去99.5元的差价。希望大家加入拼团之后尽自己力量邀请时候身边的阿里云新人参团,这样你可以获得最高1111的红包,而且后面如果团队拉的新人在前100名的话就可以平分100w红包。老用户也可以选择参加下面的活动! +这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!举个栗子:以原价1020元的云服务器ECS(t5 1c2g+1M+40G高效云盘) 为例,双11新用户专享价为199元,如果您成功拉一个新人拼团,您和团员都将享受折上9折优惠,199*90%=179.1元,将返还您和团员每人19.9元。随着您拉新人数增多,享受折扣将不断叠加, 当您参加的团中有6个及以上新用户时,您和团员即可享受折上5折优惠,即199元的云服务器再打5折,可享受99.5元的优惠价,将返还您和团员购买价减去99.5元的差价。希望大家加入拼团之后尽自己力量邀请时候身边的阿里云新人参团,这样你可以获得最高1111的红包,而且后面如果团队拉的新人在前100名的话就可以平分100w红包。老用户也可以选择参加下面的活动! > 阿里云技术有保障,在云服务技术上远远领先于国内其他云服务提供商。大家或者公司如果需要用到云服务器的话,推荐阿里云服务器,下面是阿里云目前正在做的一些活动,错过这波,后续可能多花很多钱: From ae1f5d1a892189bffb445ddc3219335ffd6ffa3e Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 29 Oct 2018 12:58:00 +0800 Subject: [PATCH 105/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E5=8F=8C11?= =?UTF-8?q?=E6=B4=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb18e14725c..75a4de88386 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!举个栗子:以原价1020元的云服务器ECS(t5 1c2g+1M+40G高效云盘) 为例,双11新用户专享价为199元,如果您成功拉一个新人拼团,您和团员都将享受折上9折优惠,199*90%=179.1元,将返还您和团员每人19.9元。随着您拉新人数增多,享受折扣将不断叠加, 当您参加的团中有6个及以上新用户时,您和团员即可享受折上5折优惠,即199元的云服务器再打5折,可享受99.5元的优惠价,将返还您和团员购买价减去99.5元的差价。希望大家加入拼团之后尽自己力量邀请时候身边的阿里云新人参团,这样你可以获得最高1111的红包,而且后面如果团队拉的新人在前100名的话就可以平分100w红包。老用户也可以选择参加下面的活动! +阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 举个栗子:以原价1020元的云服务器ECS(t5 1c2g+1M+40G高效云盘) 为例,双11新用户专享价为199元,如果您成功拉一个新人拼团,您和团员都将享受折上9折优惠,199*90%=179.1元,将返还您和团员每人19.9元。随着您拉新人数增多,享受折扣将不断叠加, 当您参加的团中有6个及以上新用户时,您和团员即可享受折上5折优惠,即199元的云服务器再打5折,可享受99.5元的优惠价,将返还您和团员购买价减去99.5元的差价。希望大家加入拼团之后尽自己力量邀请时候身边的阿里云新人参团,这样你可以获得最高1111的红包,而且后面如果团队拉的新人在前100名的话就可以平分100w红包。老用户也可以选择参加下面的活动! > 阿里云技术有保障,在云服务技术上远远领先于国内其他云服务提供商。大家或者公司如果需要用到云服务器的话,推荐阿里云服务器,下面是阿里云目前正在做的一些活动,错过这波,后续可能多花很多钱: From ae872d0a9800a99bf666d0c3f029e909f0c7dda1 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 29 Oct 2018 13:26:34 +0800 Subject: [PATCH 106/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E6=B4=BB?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 75a4de88386..4ab430741a5 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,4 @@ -阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 举个栗子:以原价1020元的云服务器ECS(t5 1c2g+1M+40G高效云盘) 为例,双11新用户专享价为199元,如果您成功拉一个新人拼团,您和团员都将享受折上9折优惠,199*90%=179.1元,将返还您和团员每人19.9元。随着您拉新人数增多,享受折扣将不断叠加, 当您参加的团中有6个及以上新用户时,您和团员即可享受折上5折优惠,即199元的云服务器再打5折,可享受99.5元的优惠价,将返还您和团员购买价减去99.5元的差价。希望大家加入拼团之后尽自己力量邀请时候身边的阿里云新人参团,这样你可以获得最高1111的红包,而且后面如果团队拉的新人在前100名的话就可以平分100w红包。老用户也可以选择参加下面的活动! - -> 阿里云技术有保障,在云服务技术上远远领先于国内其他云服务提供商。大家或者公司如果需要用到云服务器的话,推荐阿里云服务器,下面是阿里云目前正在做的一些活动,错过这波,后续可能多花很多钱: - -1. [全民云计算:ECS云服务器2折起,1核1G仅需293元/年](https://promotion.aliyun.com/ntms/act/qwbk.html?userCode=hf47liqn) -2. [高性能企业级性能云服务器限时2折起,2核4G仅需720元/年](https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=hf47liqn) -3. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.8849694.home.4.27a24b70kENhtV&userCode=hf47liqn) -4. [最高¥1888云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn) -5. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) +阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) @@ -143,6 +135,17 @@ Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbo 本人会利用业余时间一直更新下去,目前还有很多地方不完善,一些知识点我会原创总结,还有一些知识点如果说网上有比较好的文章了,我会把这些文章加入进去。您也可以关注我的微信公众号:“Java面试通关手册”,我会在这里分享一些自己的原创文章。 另外该文档格式参考:[Github Markdown格式](https://guides.github.com/features/mastering-markdown/),表情素材来自:[EMOJI CHEAT SHEET](https://www.webpagefx.com/tools/emoji-cheat-sheet/)。 +## 阿里云活动推荐 + +阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 举个栗子:以原价1020元的云服务器ECS(t5 1c2g+1M+40G高效云盘) 为例,双11新用户专享价为199元,如果您成功拉一个新人拼团,您和团员都将享受折上9折优惠,199*90%=179.1元,将返还您和团员每人19.9元。随着您拉新人数增多,享受折扣将不断叠加, 当您参加的团中有6个及以上新用户时,您和团员即可享受折上5折优惠,即199元的云服务器再打5折,可享受99.5元的优惠价,将返还您和团员购买价减去99.5元的差价。希望大家加入拼团之后尽自己力量邀请时候身边的阿里云新人参团,这样你可以获得最高1111的红包,而且后面如果团队拉的新人在前100名的话就可以平分100w红包。老用户也可以选择参加下面的活动! + +> 阿里云技术有保障,在云服务技术上远远领先于国内其他云服务提供商。大家或者公司如果需要用到云服务器的话,推荐阿里云服务器,下面是阿里云目前正在做的一些活动,错过这波,后续可能多花很多钱: + +1. [全民云计算:ECS云服务器2折起,1核1G仅需293元/年](https://promotion.aliyun.com/ntms/act/qwbk.html?userCode=hf47liqn) +2. [高性能企业级性能云服务器限时2折起,2核4G仅需720元/年](https://promotion.aliyun.com/ntms/act/enterprise-discount.html?userCode=hf47liqn) +3. [拉1人拼团,立享云服务器¥234/年](https://promotion.aliyun.com/ntms/act/vmpt/aliyun-group/home.html?spm=5176.8849694.home.4.27a24b70kENhtV&userCode=hf47liqn) +4. [最高¥1888云产品通用代金券](https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=hf47liqn) +5. [阿里云建站服务](https://promotion.aliyun.com/ntms/act/jianzhanquan.html?userCode=hf47liqn)(企业官网、电商网站,多种可供选择模板,代金券免费领取) **你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号有大量资料,回复关键字“1”你可能看到想要的东西哦!:** From 8c5243bcbf1e30448d4b7c4fb83aff3108ca053c Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 29 Oct 2018 15:01:50 +0800 Subject: [PATCH 107/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ab430741a5..34908e05805 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! +【新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) From 32985c5cbeb0ed9e008bbad4b748a9a3a8710b58 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 29 Oct 2018 16:31:07 +0800 Subject: [PATCH 108/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E6=96=B0?= =?UTF-8?q?=E4=BA=BA=E4=B8=93=E5=B1=9E=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34908e05805..aad1aa9e502 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -【新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!划重点了:1核2G云服务器1年仅需99.5元!!!该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! +【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!**目前我的战队已经有5位新人了,现在是折上5折了。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) From 473a7a318091e1d2a5efe172fcdb2aecaf97e289 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 29 Oct 2018 19:59:55 +0800 Subject: [PATCH 109/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E6=B4=BB?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aad1aa9e502..2e89641c3fc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!**目前我的战队已经有5位新人了,现在是折上5折了。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! +【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) From f160d8ea632d767519b8f7a9351f0eb6cb989f1e Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 29 Oct 2018 20:13:42 +0800 Subject: [PATCH 110/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e89641c3fc..f16f2b2eb38 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! +【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦(加我微信才可以,不然找不到你人)!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) From db0513912fbd5f40a6e9104754861da056a72f60 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 29 Oct 2018 23:43:04 +0800 Subject: [PATCH 111/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f16f2b2eb38..2d715261ed7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有几乎平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦(加我微信才可以,不然找不到你人)!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! +【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦(加我微信才可以,不然找不到你人)!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) From f78642eb0eb34b7196ca7d4c24faec44af9866bc Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Mon, 29 Oct 2018 23:43:29 +0800 Subject: [PATCH 112/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d715261ed7..1e9b626476a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦(加我微信才可以,不然找不到你人)!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! +【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了也就是1折购买!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦(加我微信才可以,不然找不到你人)!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) From dd913a2cbb9e0a4342ce8d08f5e52a1c13266ada Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 30 Oct 2018 09:50:31 +0800 Subject: [PATCH 113/122] =?UTF-8?q?=E9=98=BF=E9=87=8C=E4=BA=91=E6=B4=BB?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1e9b626476a..b15a087b7bb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ 【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了也就是1折购买!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦(加我微信才可以,不然找不到你人)!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! +另外,老用户可以帮忙我拉新,帮我拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人我最后成功获得了返现,我会把返现都给你! + Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) | Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | From 3cbec506f19d7b549fa2852fbce2be150f084f3d Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 30 Oct 2018 09:58:53 +0800 Subject: [PATCH 114/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b15a087b7bb..868582c7d37 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ 【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了也就是1折购买!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦(加我微信才可以,不然找不到你人)!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! -另外,老用户可以帮忙我拉新,帮我拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人我最后成功获得了返现,我会把返现都给你! +另外,老用户可以帮忙我拉新,帮我拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人我最后成功获得了返现,我会把返现都给你!(我希望我的团队最后能够冲进前100,别的不多说!!!诚信!) Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) From ef86d09b6dc1d6016f7c198643d34bd407dd2c63 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 30 Oct 2018 10:34:27 +0800 Subject: [PATCH 115/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 868582c7d37..0ba9493b0f0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ 【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了也就是1折购买!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦(加我微信才可以,不然找不到你人)!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! -另外,老用户可以帮忙我拉新,帮我拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人我最后成功获得了返现,我会把返现都给你!(我希望我的团队最后能够冲进前100,别的不多说!!!诚信!) +另外,老用户和新用户可以帮忙拉新,拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人你会获得返现奖励,返现奖励直接到你的账户!(我希望我的团队最后能够冲进前100,别的不多说!!!诚信!) Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) From bf6917c86554fe61c9c49caab3f7697451d3099f Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 30 Oct 2018 10:37:22 +0800 Subject: [PATCH 116/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ba9493b0f0..4bedaf403a3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -【阿里云新人专属】阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了也就是1折购买!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦(加我微信才可以,不然找不到你人)!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! +阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了也就是1折购买!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦(加我微信才可以,不然找不到你人)!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! 另外,老用户和新用户可以帮忙拉新,拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人你会获得返现奖励,返现奖励直接到你的账户!(我希望我的团队最后能够冲进前100,别的不多说!!!诚信!) From 97a468c260fba19010ee413da4779b22d5fc56bf Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 30 Oct 2018 11:44:21 +0800 Subject: [PATCH 117/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bedaf403a3..37822141176 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了也就是1折购买!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!购买成功之后我还会给你退换一个红包哦(加我微信才可以,不然找不到你人)!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn (如果还有不懂的可以加我微信:bwcx9393 咨询) 文尾有详细说明哦! +阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了也就是1折购买!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn ! 另外,老用户和新用户可以帮忙拉新,拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人你会获得返现奖励,返现奖励直接到你的账户!(我希望我的团队最后能够冲进前100,别的不多说!!!诚信!) From d7817fcb0d65c2b4a6f9062f082097600f8130e1 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 30 Oct 2018 12:40:40 +0800 Subject: [PATCH 118/122] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 37822141176..a5ef76d1f24 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +> 看本文之前,推荐给大家一个阿里云双11活动,真的非常非常非常推荐,对于新人福利,阿里云这次真的是下血本了,建议阿里云新人一定一定一定不要错过。如果觉得这单纯是广告的话,你可以直接跳过看正文。 + 阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了也就是1折购买!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn ! 另外,老用户和新用户可以帮忙拉新,拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人你会获得返现奖励,返现奖励直接到你的账户!(我希望我的团队最后能够冲进前100,别的不多说!!!诚信!) From 84889fc0f2405e89a09c8637f17332aedf07a86d Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 30 Oct 2018 15:47:47 +0800 Subject: [PATCH 119/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5ef76d1f24..188430d261b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ > 看本文之前,推荐给大家一个阿里云双11活动,真的非常非常非常推荐,对于新人福利,阿里云这次真的是下血本了,建议阿里云新人一定一定一定不要错过。如果觉得这单纯是广告的话,你可以直接跳过看正文。 -阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有12位新人了,现在是折上5折了也就是1折购买!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn ! +阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有40多位新人了,现在是折上5折了也就是1折购买,已经达到了最低折扣!!!!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn ! 另外,老用户和新用户可以帮忙拉新,拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人你会获得返现奖励,返现奖励直接到你的账户!(我希望我的团队最后能够冲进前100,别的不多说!!!诚信!) From 80e4eb717ba45cff6070616bd9bc67ad2e42d757 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 30 Oct 2018 15:52:46 +0800 Subject: [PATCH 120/122] =?UTF-8?q?=E3=80=90=E5=BC=BA=E7=83=88=E6=8E=A8?= =?UTF-8?q?=E8=8D=90=E3=80=91=E9=98=BF=E9=87=8C=E4=BA=91=E6=B4=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 188430d261b..79c68ab12de 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ > 看本文之前,推荐给大家一个阿里云双11活动,真的非常非常非常推荐,对于新人福利,阿里云这次真的是下血本了,建议阿里云新人一定一定一定不要错过。如果觉得这单纯是广告的话,你可以直接跳过看正文。 -阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有40多位新人了,现在是折上5折了也就是1折购买,已经达到了最低折扣!!!!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!这是我的拼团团队地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn ! +阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有40多位新人了,现在是折上5折了也就是1折购买,已经达到了最低折扣!!!!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!这是我的拼团团队地址:[https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn](https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn) ! 另外,老用户和新用户可以帮忙拉新,拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人你会获得返现奖励,返现奖励直接到你的账户!(我希望我的团队最后能够冲进前100,别的不多说!!!诚信!) From aa9880a4c7ddca5dcaa7715e814ca327596c910c Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 30 Oct 2018 17:15:53 +0800 Subject: [PATCH 121/122] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 79c68ab12de..5d5b2f6b679 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> 看本文之前,推荐给大家一个阿里云双11活动,真的非常非常非常推荐,对于新人福利,阿里云这次真的是下血本了,建议阿里云新人一定一定一定不要错过。如果觉得这单纯是广告的话,你可以直接跳过看正文。 +> 福利:看本文之前,推荐给大家一个阿里云双11活动,真的非常非常非常推荐,对于新人福利,阿里云这次真的是下血本了,建议阿里云新人一定一定一定不要错过。如果觉得这单纯是广告的话(阿里云肯找我做广告就好了,嘿嘿),你可以直接跳过看正文。 阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分100w红包!**目前我的战队已经有40多位新人了,现在是折上5折了也就是1折购买,已经达到了最低折扣!!!!!!。** 划重点了: **1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元** 该折扣仅限新人!这是我的拼团团队地址:[https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn](https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn) ! From f469735ab2f628075d9d2e4507f7ba0d2567da93 Mon Sep 17 00:00:00 2001 From: SnailClimb Date: Tue, 30 Oct 2018 19:57:46 +0800 Subject: [PATCH 122/122] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 5d5b2f6b679..e5f58446a92 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ 另外,老用户和新用户可以帮忙拉新,拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包10元,最高1111元);②瓜分百万红包的机会(目前我的战队已经有29位新人,所以冲进前100的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人你会获得返现奖励,返现奖励直接到你的账户!(我希望我的团队最后能够冲进前100,别的不多说!!!诚信!) -Gitchat 推荐:[Java 程序员必备:并发知识系统总结](https://gitbook.cn/gitchat/activity/5bc2b6af56f0425673d299bb) - | Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | | :--------: | :----------: | :-----------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :----:| | [Java](#coffee-Java) | [数据结构与算法](#open_file_folder-数据结构与算法)|[计算机网络与数据通信](#computer-计算机网络与数据通信) | [操作系统](#iphone-操作系统)| [主流框架](#pencil2-主流框架)| [数据存储](#floppy_disk-数据存储)|[架构](#punch-架构)| [面试必备](#musical_note-面试必备)| [其他](#art-其他)| [说明](#envelope-该开源文档一些说明)|