();
+ TreeNode p = root;
+ TreeNode pre = null;// 用于保存中序遍历序列的上一节点
+ boolean isFirst = true;
+ while(p!=null||!stack.isEmpty()){
+ while(p!=null){
+ stack.push(p);
+ p = p.left;
+ }
+ p = stack.pop();
+ if(isFirst){
+ root = p;// 将中序遍历序列中的第一个节点记为root
+ pre = root;
+ isFirst = false;
+ }else{
+ pre.right = p;
+ p.left = pre;
+ pre = p;
+ }
+ p = p.right;
+ }
+ return root;
+ }
+ }
+
+## 重建二叉树
+
+ * 题目描述
+ 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
+ */
+
+ 解析:首先,头结点一定是先序遍历的首位,并且该节点把中序分为左右子树,根据这个规则,左子树由左边数组来完成,右子树由右边数组来完成,根节点由中间节点来构建,于是便有了如下的递归代码。该题的难点就在于边界的判断。
+
+ public TreeNode reConstructBinaryTree(int [] pre, int [] in) {
+ if(pre.length == 0||in.length == 0){
+ return null;
+ }
+ TreeNode node = new TreeNode(pre[0]);
+ for(int i = 0; i < in.length; i++){ + if(pre[0] == in[i]){ + node.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i+1), Arrays.copyOfRange(in, 0, i));//为什么不是i和i-1呢,因为要避免出错,中序找的元素需要再用一次。 + node.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i+1, pre.length), Arrays.copyOfRange(in, i+1,in.length)); + } + } + return node; + } + +## 树的子结构 + + /** + * Created by 周杰伦 on 2018/3/27. + * 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构) + */ + + 解析:本题还是有点难度的,子结构要求节点完全相同,所以先判断节点是否相同,然后使用先序遍历进行递判断,判断的依据是如果子树为空,则说明节点都找到了,如果原树节点为空,说明找不到对应节点,接着递归地判断该节点的左右子树是否符合要求. + + public class 树的子结构 { + + public boolean HasSubtree(TreeNode root1, TreeNode root2) { + boolean res = false; + if (root1 != null && root2 != null) { + if (root1.val == root2.val) { + res = aHasb(root1, root2); + } + if (res == false) { + res = HasSubtree(root1.left,root2); + } + if (res == false) { + res = HasSubtree(root1.right,root2); + } + return res; + } + else return false; + } + public boolean aHasb(TreeNode t1, TreeNode t2){ + if (t2 == null) return true; + if (t1 == null) return false; + if (t1.val != t2.val) return false; + + return aHasb(t1.left,t2.left) && aHasb(t1.right,t2.right); + } + } + + +## 镜像二叉树 + + /** + * Created by 周杰伦 on 2017/3/19.操作给定的二叉树,将其变换为源二叉树的镜像。 + 输入描述: + 二叉树的镜像定义:源二叉树 + 8 + / \ + 6 10 + / \ / \ + 5 7 9 11 + 镜像二叉树 + 8 + / \ + 10 6 + / \ / \ + 11 9 7 5 + */ + + + 解析:其实镜像二叉树就是交换所有节点的左右子树,所以使用遍历并且进行交换即可。 + + /** + public class TreeNode { + int val = 0; + TreeNode left = null; + TreeNode right = null; + + public TreeNode(int val) { + this.val = val; + + } + + } + */ + public class 镜像二叉树 { + public void Mirror(TreeNode root) { + if(root == null)return; + if(root.left!=null || root.right!=null) + { + TreeNode temp=root.left; + root.left=root.right; + root.right=temp; + } + Mirror(root.left); + Mirror(root.right); + + } + + +## 树的层次遍历 + +也就是从上到下打印节点,使用队列即可完成。 + +## 二叉树的深度 + +经典遍历。 + +## 判断是否平衡二叉树 + +判断左右子树的高度差是否 <= 1即可。 + +## 二叉搜索树的后序遍历 + +题目描述 +输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。 + +解析:这题其实也非常巧妙。二叉搜索树的特点就是他的左子树都比根节点小,右子树都比跟节点大。而后序遍历的根节点在最后,所以后续遍历的第1到N-1个节点应该是左右子树的节点(不一定左右子树都存在)。 + +后续遍历的序列是先左子树,再右子树,最后根节点,那么就要求,左半部分比根节点小,右半部分比根节点大,当然,左右部分不一定都存在。 + +所以,找出根节点后,首先找出左半部分,要求小于根节点,然后找出右半部分,要求大于根节点,如果符合,则递归地判断左右子树到的根节点(本步骤已经将左右部分划分,割据中间节点进行递归),如果不符合,直接返回false。 + +同理也可以判断前序遍历和中序遍历。 + + public class 二叉搜索树的后序遍历序列 { + public static void main(String[] args) { + int []a = {7,4,6,5}; + System.out.println(VerifySquenceOfBST(a)); + } + public static boolean VerifySquenceOfBST(int [] sequence) { + if (sequence == null || sequence.length == 0) { + return false; + } + return isBST(sequence, 0, sequence.length - 1); + } + public static boolean isBST(int []arr, int start, int end) { + if (start>= end) return true;
+ int root = arr[end];
+ int mid = start;
+ for (mid = start;mid < end && arr[mid] < root;mid ++) { + + } + for (int i = mid;i < end; i ++) { + if (arr[i] < root)return false; + } + return isBST(arr, start, mid - 1) && isBST(arr, mid, end - 1); + } + } + +## 二叉树中和为某一值的路径 + +/** + * Created by 周杰伦 on 2018/3/29. + * 题目描述 + 输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。 + */ + + 解析:由于要求从根节点到达叶子节点,并且要打印出所有路径,所以实际上用到了回溯的思想。 + + 通过target跟踪当前和,进行先序遍历,当和满足要求时,加入集合,由于有多种结果,所以需要回溯,将访问过的节点弹出访问序列,才能继续访问下一个节点。 + + 终止条件是和满足要求,并且节点是叶节点,或者已经访问到空节点也会返回。 + + + public class 二叉树中和为某一值的路径 { + private ArrayList
> listAll = new ArrayList>();
+ private ArrayList list = new ArrayList();
+ public ArrayList> FindPath(TreeNode root,int target) {
+ if(root == null) return listAll;
+ list.add(root.val);
+ target -= root.val;
+ if(target == 0 && root.left == null && root.right == null)
+ listAll.add(new ArrayList(list));
+ FindPath(root.left, target);
+ FindPath(root.right, target);
+ list.remove(list.size()-1);
+ return listAll;
+ }
+
+ static int count = 0;
+ static Stack path = new Stack();
+ static Stack stack = new Stack();
+ static ArrayList> lists = new ArrayList();
+ }
+
+## 二叉树的下一个节点
+
+给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
+
+ 解析:给出一个比较好懂的解法,中序遍历的结果存在集合中,找到根节点,进行中序遍历,然后找到该节点,下一个节点就是集合后一位
+
+ public TreeLinkNode GetNext(TreeLinkNode TreeLinkNode)
+ {
+ return findNextNode(TreeLinkNode);
+ }
+ public TreeLinkNode findNextNode(TreeLinkNode anynode) {
+ if (anynode == null) return null;
+ TreeLinkNode p = anynode;
+ while (p.next != null) {
+ p = p.next;
+ }
+ ArrayList list = inOrderSeq(p);
+ for (int i = 0;i < list.size();i ++) { + if (list.get(i) == anynode) { + if (i + 1 < list.size()) { + return list.get(i + 1); + } + else return null; + } + } + return null; + + } + static ArrayList
list = new ArrayList
();
+ public static ArrayList inOrderSeq(TreeLinkNode TreeLinkNode) {
+ if (TreeLinkNode == null) return null;
+ inOrderSeq(TreeLinkNode.left);
+ list.add(TreeLinkNode);
+ inOrderSeq(TreeLinkNode.right);
+ return list;
+ }
+
+## 对称的二叉树
+
+请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
+
+解析,之前有一题是二叉树的镜像,递归交换左右子树即可求出镜像,然后递归比较两个树的每一个节点,则可以判断是否对称。
+
+ boolean isSymmetrical(TreeNode pRoot)
+ {
+ TreeNode temp = copyTree(pRoot);
+ Mirror(pRoot);
+ return isSameTree(temp, pRoot);
+ }
+
+
+ void Mirror(TreeNode root) {
+ if(root == null)return;
+ Mirror(root.left);
+ Mirror(root.right);
+ if(root.left!=null || root.right!=null)
+ {
+ TreeNode temp=root.left;
+ root.left=root.right;
+ root.right=temp;
+ }
+
+
+ }
+ boolean isSameTree(TreeNode t1,TreeNode t2){
+ if(t1==null && t2==null)return true;
+ else if(t1!=null && t2!=null && t1.val==t2.val) {
+ boolean left = isSameTree(t1.left, t2.left);
+ boolean right = isSameTree(t1.right, t2.right);
+ return left && right;
+ }
+ else return false;
+ }
+
+ TreeNode copyTree (TreeNode root) {
+ if (root == null) return null;
+ TreeNode t = new TreeNode(root.val);
+ t.left = copyTree(root.left);
+ t.right = copyTree(root.right);
+ return t;
+ }
+## 把二叉树打印成多行
+
+题目描述
+从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
+
+解析:1 首先要知道到本题的基础思想,层次遍历。
+
+2 然后是进阶的思想,按行打印二叉树并输出行号,方法是,一个节点last指向当前行的最后一个节点,一个节点nlast指向下一行最后一个节点。使用t表示现在遍历的节点,当t = last时,表示本行结束。此时last = nlast,开始下一行遍历。
+
+同时,当t的左右子树不为空时,令nlast = t的左子树和右子树。每当last 赋值为nlast时,行号加一即可。
+
+## 按之字形顺序打印二叉树
+
+请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
+
+解析:1 首先要知道到本题的基础思想,层次遍历。
+
+2 然后是进阶的思想,按行打印二叉树并输出行号,方法是,一个节点last指向当前行的最后一个节点,一个节点nlast指向下一行最后一个节点。使用t表示现在遍历的节点,当t = last时,表示本行结束。此时last = nlast,开始下一行遍历。
+
+同时,当t的左右子树不为空时,令nlast = t的左子树和右子树。每当last 赋值为nlast时,行号加一即可。
+
+3 基于第2步的思想,现在要z字型打印,只需把偶数行逆序即可。所以把每一行的数存起来,然后偶数行逆置即可。
+
+ ArrayList
> Print(TreeNode pRoot) {
+ LinkedList queue = new LinkedList
();
+ TreeNode root = pRoot;
+ if(root == null) {
+ return new ArrayList();
+ }
+ TreeNode last = root;
+ TreeNode nlast = root;
+ queue.offer(root);
+ ArrayList list = new ArrayList();
+ list.add(root.val);
+ ArrayList one = new ArrayList();
+ one.addAll(list);
+ ArrayList> lists = new ArrayList();
+ lists.add(one);
+ list.clear();
+
+ int row = 1;
+ while (!queue.isEmpty()){
+
+ TreeNode t = queue.poll();
+
+ if(t.left != null) {
+ queue.offer(t.left);
+ list.add(t.left.val);
+ nlast = t.left;
+ }
+ if(t.right != null) {
+ queue.offer(t.right);
+ list.add(t.right.val);
+ nlast = t.right;
+ }
+ if(t == last) {
+ if(!queue.isEmpty()) {
+ last = nlast;
+ row ++;
+ ArrayList temp = new ArrayList();
+ temp.addAll(list);
+ list.clear();
+ if (row % 2 == 0) {
+ Collections.reverse(temp);
+ }
+ lists.add(temp);
+
+ }
+ }
+ }
+
+ return lists;
+ }
+
+## 序列化和反序列化二叉树
+
+解析:序列化和反序列化关键是要确定序列化方式。我么使用字符串来序列化。
+
+用#代表空,用!分隔左右子树。
+
+比如 1
+ 2 3
+ 4 5
+
+使用先序遍历
+序列化结果是1!2!4!###3!#5!##
+
+反序列化先让根节点指向第一位字符,然后左子树递归进行连接,右子树
+
+ public class Solution {
+ public int index = -1;
+ StringBuffer sb = new StringBuffer();
+
+ String Serialize(TreeNode root) {
+ if(root == null) {
+ sb.append("#!") ;
+ }
+ else {
+ sb.append(root.val + "!");
+ Serialize(root.left);
+ Serialize(root.right);
+ }
+
+ return sb.toString();
+ }
+ TreeNode Deserialize(String str) {
+ index ++;
+ int len = str.length();
+ if(index>= len) {
+ return null;
+ }
+ String[] strr = str.split("!");
+ TreeNode node = null;
+ if(!strr[index].equals("#")) {
+ node = new TreeNode(Integer.valueOf(strr[index]));
+ node.left = Deserialize(str);
+ node.right = Deserialize(str);
+ }
+ return node;
+ }
+ }
+
+## 二叉搜索树的第k个结点
+
+ 解析:二叉搜索树的中序遍历是有序的,只需要在中序中判断数字是否在第k个位置即可。
+ 如果在左子树中发现了,那么递归返回该节点,如果在右子树出现,也递归返回该节点。注意必须要返回,否则结果会被递归抛弃掉。
+
+ TreeNode KthNode(TreeNode pRoot, int k)
+ {
+ count = 0;
+ return inOrderSeq(pRoot, k);
+ }
+ static int count = 0;
+ public TreeNode inOrderSeq(TreeNode treeNode, int k) {
+ if (treeNode == null) return null;
+ TreeNode left = inOrderSeq(treeNode.left, k);
+ if (left != null) return left;
+ if (++ count == k) return treeNode;
+ TreeNode right = inOrderSeq(treeNode.right, k);
+ if (right != null) return right;
+ return null;
+ }
+
+# 栈和队列
+
+## 用两个队列实现栈,用两个栈实现队列。
+
+
+简单说下思路
+
+1 两个栈实现队列,要求先进先出,入队时节点先进入栈A,如果栈A满并且栈B空则把全部节点压入栈B。
+
+出队时,如果栈B为空,那么直接把栈A节点全部压入栈B,再从栈B出栈,如果栈B不为空,则从栈B出栈。
+
+2 两个队列实现栈,要求后进先出。入栈时,节点先加入队列A,出栈时,如果队列B不为空,则把头结点以后的节点出队并加入到队列B,然后自己出队。
+
+如果出栈时队列B不为空,则把B头结点以后的节点移到队列A,然后出队头结点,以此类推。
+
+## 包含min函数的栈
+
+/**
+ * 设计一个返回最小值的栈
+ * 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。
+ * Created by 周杰伦 on 2017年3月22日.
+ */
+
+ 解析:这道题的解法也是非常巧妙的。因为每次进栈和出栈都有可能导致最小值发生改变。而我们要维护的是整个栈的最小值。
+
+ 如果单纯使用一个数来保存最小值,会出现一种情况,最小值出栈时,你此时的最小值只能改成栈顶元素,但这个元素不一定时最小值。
+
+ 所以需要一个数组来存放最小值,或者是一个栈。
+
+ 使用另一个栈B存放最小值,每次压栈时比较节点值和栈B顶端节点值,如果比它小则压栈,否则不压栈,这样就可以从b的栈顶到栈顶依次访问最小值,次小值。以此类推。
+
+ 当最小值节点出栈时,判断栈B顶部的节点和出栈节点是否相同,相同则栈B也出栈。
+
+ 这样就可以维护一个最小值的函数了。
+
+ 同理,最大值也是这样。
+
+
+ public class 包含min函数的栈 {
+ Stack stack=new Stack();
+ Stack minstack=new Stack();
+
+ public void push(int node) {
+ if(stack.isEmpty())
+ {
+ stack.push(node);
+ minstack.push(node);
+ }
+ else if(node stack = new Stack();
+ int j = 0;
+ int i = 0;
+ while (i < pushA.length) { + stack.push(pushA[i]); + i++; + while (!stack.empty() && stack.peek() == popA[j]) { + stack.pop(); + j++; + } + if (i == pushA.length) { + if (!stack.empty()) { + return false; + } else return true; + } + } + return false; + } + + +# 排序和查找 + +## 旋转数组的最小数字 + + 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。 + + 解析:这题的思路很巧妙,如果直接遍历复杂度为O(N),但是使用二分查找可以加快速度,因为两边的数组都是递增的最小值一定在两边数组的边缘,于是通过二分查找,逐渐缩短左右指针的距离,知道左指针和右指针只差一步,那么右指针所在的数就是最小值了。 + 复杂度是O(logN) + + //这段代码忽略了三者相等的情况 + public int minNumberInRotateArray(int [] array) { + if (array.length == 0) return 0; + if (array.length == 1) return array[0]; + int min = 0; + + int left = 0, right = array.length - 1; + //只有左边值大于右边值时,最小值才可能出现在中间 + while (array[left]> array[right]) {
+ int mid = (left + right)/2;
+ if (right - left == 1) {
+ min = array[right];
+ break;
+ }
+ //如果左半部分递增,则最小值在右侧
+ if (array[left] < array[mid]) { + left = mid; + } + //如果右半部分递增,则最小值在左侧。 + //由于左边值比右边值大,所以两种情况不会同时发生 + else if (array[right]> array[mid]) {
+ right = mid ;
+ }
+ }
+ return array[min];
+
+ }
+
+
+ 注意:但是当arr[left] = arr[right] = arr[min]时。三个数都相等无法确定最小值,此时只能遍历。
+
+# 递归
+
+## 斐波那契数列
+
+1递归做法
+
+2记忆搜索,用数组存放使用过的元素。
+
+3DP,本题中dp就是记忆化搜索
+
+## 青蛙跳台阶
+
+一次跳两步或者跳一步,问一共多少种跳法到达n级,所以和斐波那契数列是一样的。
+
+## 变态跳台阶
+
+一次跳1到n步,问一共几种跳法,这题是找数字规律的,一共有2^(n-1)种方法
+
+## 矩形覆盖
+
+和上题一样,也是找规律,答案也是2^(n-1)
+
+# 位运算
+
+## 二进制中1的个数
+
+ * Created by 周杰伦 on 2018年6月29日.
+ * 题目描述
+ * 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
+
+ 解析:
+ 1 循环右移数字n,每次判断最低位是否为1,但是可能会导致死循环。
+
+ 2 使用数字a = 1和n相与,a每次左移一位,再与n相与得到次低位,最多循环32次,当数字1左移32次也会等于0,所以结束循环。
+
+ 3 非常奇葩的做法,把一个整数减去1,再与原整数相与,会把最右边的一个1变成0,于是统计可以完成该操作的次数即可知道有多少1了。
+
+ public class 二进制中1的个数 {
+ public static int NumberOf1(int n) {
+ int count = 0;
+ while (n != 0) {
+ ++count;
+ n = (n - 1) & n;
+ }
+ return count;
+ }
+ }
+
+## 数组中只出现一次的数字
+
+题目描述
+一个整型数组里除了一个数字之外,其他的数字都出现了两次。请写程序找出这一个只出现一次的数字。
+
+解析:左神称之为神仙题。
+
+利用位运算的异或操作^。
+由于a^a = 0,0^b=b,所以。所有数执行异或操作,结果就是只出现一次的数。
+
+## 不用加减乘除做加法
+
+解析:不用加减乘,那么只能用二进制了。
+
+两个数a和b,如果不考虑进位,则0 + 1 = 1,1 + 1 = 0,0 + 0 = 0,这就相当于异或操作。
+如果考虑进位,则只有1 + 1有进位,所以使用相与左移的方法得到每一位的进位值,再通过异或操作和原来的数相加。当没有进位值的时候,运算结束。
+ public static int Add(int num1,int num2) {
+ if( num2 == 0 )return num1;
+ if( num1 == 0 )return num2;
+
+ int temp = num2;
+ while(num2!=0) {
+ temp = num1 ^num2;
+ num2 = (num1 & num2)<<1; + num1 = temp; + } + return num1; + } + +# 回溯和DFS + +## 矩阵中的路径 + +题目描述 +请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。 + +解析:回溯法也就是特殊的dfs,需要找到所有的路径,所以每当到达边界条件或抵达目标时,递归返回,由于需要保存路径中的字母,所以递归返回时需要删除路径最后的节点,来保证路径合法。不过本题只有一个解,所以找到即可返回。 + + public class 矩阵中的路径 { + public static void main(String[] args) { + char[][]arr = {{'a','b','c','e'},{'s','f','c','s'},{'a','d','e','e'}}; + char []str = {'b','c','c','e','d'}; + System.out.println(hasPath(arr, arr.length, arr[0].length, str)); + } + static int flag = 0; + public static boolean hasPath(char[][] matrix, int rows, int cols, char[] str) + { + int [][]visit = new int[rows][cols]; + StringBuilder sb = new StringBuilder(); + for (int i = 0;i < rows;i ++) { + for (int j = 0;j < cols;j ++) { + if (matrix[i][j] == str[0]) { + visit[i][j] = 1; + sb.append(str[0]); + dfs(matrix, i, j, visit, str, 1, sb); + visit[i][j] = 0; + sb.deleteCharAt(sb.length() - 1); + } + } + } + return flag == 1; + } + public static void dfs(char [][]matrix, int row, int col, int [][]visit, char []str, int cur, StringBuilder sb) { + if (sb.length() == str.length) { + // System.out.println(sb.toString()); + flag = 1; + return; + } + + int [][]pos = {{1,0},{-1,0},{0,1},{0,-1}}; + for (int i = 0;i < pos.length;i ++) { + int x = row + pos[i][0]; + int y = col + pos[i][1]; + if (x>= matrix.length || x < 0 || y>= matrix[0].length || y < 0) { + continue; + } + if (visit[x][y] == 0 && matrix[x][y] == str[cur]) { + sb.append(matrix[x][y]); + visit[x][y] = 1; + dfs(matrix, x, y, visit, str, cur + 1, sb); + sb.deleteCharAt(sb.length() - 1); + visit[x][y] = 0; + } + } + } + + +## 机器人的运动范围 + +题目描述 +地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子? + + 解析:这是一个可达性问题,使用dfs方法,走到的每一格标记为走过,走到无路可走时就是最终的结果。每次都有四个方向可以选择,所以写四个递归即可。 + + public class Solution { + static int count = 0; + public static int movingCount(int threshold, int rows, int cols) + { + count = 0; + int [][]visit = new int[rows][cols]; + dfs(0, 0, visit, threshold); + return count; + } + + public static void dfs(int row, int col, int[][]visit, int k) { + if (row>= visit.length || row < 0 || col>= visit[0].length || col < 0) { + return; + } + if (sum(row) + sum(col)> k) {
+ return;
+ }
+
+ if (visit[row][col] == 1){
+ return;
+ }
+
+ visit[row][col] = 1;
+ count ++;
+ dfs(row + 1,col,visit, k);
+ dfs(row - 1,col,visit, k);
+ dfs(row,col + 1,visit, k);
+ dfs(row,col - 1,visit, k);
+
+ }
+
+ public static int sum(int num) {
+ String s = String.valueOf(num);
+ int sum = 0;
+ for (int i = 0;i < s.length();i ++) { + sum += Integer.valueOf(s.substring(i, i + 1)); + } + return sum; + } + } \ No newline at end of file diff --git "a/md/346円223円215円344円275円234円347円263円273円347円273円237円345円255円246円344円271円240円346円200円273円347円273円223円.md" "b/md/346円223円215円344円275円234円347円263円273円347円273円237円345円255円246円344円271円240円346円200円273円347円273円223円.md" new file mode 100644 index 0000000..2740f29 --- /dev/null +++ "b/md/346円223円215円344円275円234円347円263円273円347円273円237円345円255円246円344円271円240円346円200円273円347円273円223円.md" @@ -0,0 +1,304 @@ +--- +title: 操作系统学习总结 +date: 2018-07-09 22:33:03 +tags: + - 操作系统 +categories: + - 后端 + - 技术总结 +--- +##这部分内容主要是基于一些关于操作系统基础的学习总结,内容不全面,只讲述了其中的一小部分,后续会再补充,如有错误,还请见谅。 +# 操作系统 + +## CPU +cpu是中央处理器,他是计算机的核心。 +cpu通过和寄存器,高速缓存,以及内存交互来执行程序。 + +主板分为南桥和北桥,北桥主要是内存总线,通往内存。 +而南桥主要是慢速设备的IO总线,包括硬盘,网卡等IO设备。 + +32位cpu最多寻址4g内存,而64位cpu目前来说没有上限。 + +### 程序执行过程 +cpu发出指令,将硬盘上的一段程序读入内存,由于cpu和硬盘的速度差距更大,一般使用中断,dma等方式来将硬盘数据载入内存。 +然后cpu通过寄存器以及指令集执行指令,cpu读取内存上的代码,内存上的方法执行是一个栈调用的过程。 + +### 高速缓存 读写缓冲区 +为了弥补cpu和内存的速度差,cpu配有多级缓存。 + +一般有一级缓存和二级缓存,缓存根据局部性原理把经常使用的代码加载如缓存,能比直接访问内存快上几百倍。 + +同样的,内存和硬盘间的速度差距也很大,需要通过读写缓冲区来进行速度匹配,内存写入磁盘时先写入缓冲区,读数据时从缓冲区读取硬盘准备好的数据。 + + +## 内存管理和虚拟内存 + +由于程序的大小越来越大,而计算机想要支持多道程序,当一个程序遇到IO操作时转而去执行另一个程序,这就要求内存中装有多个程序的代码了。 + +然而程序的内存消耗与日俱增,同时装载多个程序越来越困难,所以人们提出了,只在程序需要使用到的时候再把他装入内存,平时把代码放在硬盘中即可。 + +### 分页 + +由于内存需要装载硬盘中的数据,所以需要约定一个存储单元,操作系统把它叫做页,一个页一般长度是8kb或者16kb。内存从硬盘读取数据时读取的是一个页或多个页。 + +### 虚拟内存 + + +由于这些代码持久化在硬盘中,占用了一部分空间,并且需要运行时会加载到内存中,所以这部分的空间一般也成为虚拟内存的空间(大小)。 + +### 页表和页面置换算法 + +为了知道每个程序对应的代码存在硬盘中的位置,操作系统需要维护一个程序到页面的映射表,cpu要内存加载一个页面时,首先要访问页表,来得知页面在硬盘中的位置,然后让内存去该位置把对应的页面调入内存中。 + +为了提升页面调入的效率,也使用了多种的页面置换算法,比如lru最近最久未使用,fifo先进先出,时钟法,多级队列法等。 + +当然,为了进一步提高效率,还会使用多级页表的方式,不过一般需要硬件维护一个页表映射页面的快速转换结构,以便能迅速地完成页面解析和调度。 + +### 中断和缺页中断 + +计算机提供一系列中断指令与硬件进行交互,操作系统可以使用这些中断去控制硬件,比如使用中断通知cpu硬盘的IO操作已经准备好,键盘通过一次又一次的中断来输入字符。 + +中断一般适用于快速返回的字符设备,比如鼠标键盘,而硬盘这类耗时IO操作,使用中断后cpu仍然需要等待硬盘把数据装入内存。是非常缓慢的。 + +于是才会使用DMA来完成IO操作,DMA额外提供一个处理器去处理IO操作,当硬盘数据加载到内存中后再去通知CPU操作已完成。 + + 缺页中断就是因为内存中没有cpu需要访问的页面,必须根据页表到硬盘中获取页面,并根据置换算法进行页面调度。如果找不到对应页面,则程序执行会报错。 + +### 分页和分段 + +分页是上述所说的,通过内存和硬盘的约定,将调度单元设定为一个页面。 + + 分段则不同,分段并不是物理意义上的分段,而是逻辑上把代码使用到的空间划分成多个部分,比如受保护部分,公开部分,核心部分等,这样可以更好地描述每个段的代码信息,为使用者提供便利,为了支持程序员的这种需求,操作系统加入了分段的概念,将代码对应的一段虚拟内存划分为不同的逻辑段。 + + 同时为了根据不同段来以不同方式访问内存,操作系统需要另外维护一个段表,以用于段的映射。 + +由于分段只是逻辑上的概念,所以底层的内存分页仍然是必不可少的,因此在逻辑段的基础上,物理上又会划分为多个页,一个段中可能包含了多个页面。 + +因此,完善的虚拟内存管理器需要支持段页表,先映射段,再映射页面。 + +## 进程与线程 + +### 进程 +进程是资源分配的资本单位,操作系统为进程开辟一段内存空间,内存空间从高位向低位,包括函数调用栈,变量以及其他区域。cpu根据这些信息配合寄存器进行函数调用和程序执行。 + +### 多进程 +由于计算机是分时系统,所以多进程的使用不可避免,操作系统需要进行进程的切换,方法是内存指针指向新位置,保存原来的进程信息,同时刷新寄存器等数据。然后开始执行新的进程. + +一般操作系统会使用pcb结构来记录进程的信息和上下文。通过他和cpu配合来完成进程切换。 + +### 线程 +线程是系统调度的基本单位,没有线程以前,一般会使用多进程模型,一个程序往往需要多个进程配合使用,但是多进程之间并没有共享内存,导致进程间通信非常麻烦。 + +比如文本输入工具,一边键入文字一边需要保存数据,如果是单进程执行,则每次输入都触发IO操作,非常费时,如果一个进程负责输入展示一个进程负责输入保存,确实速度很快,但是两个进程没办法共享数据。除非使用额外的通讯手段。 +### 多线程 + +而多线程就好办了,多线程都是基于进程产生的,线程被创建后只需要分配少量空间支持堆栈操作即可,同时线程还共享进程中的内存,所以一般使用进程分配资源,线程进行调度的方式。 + + 操作系统对线程的支持: + 一般情况下操作系统都支持线程,并且可以创建内核级线程,内核可以识别线程并且为其分配空间,进行线程调度。 + 但是内核级线程实现比较复杂,使用起来也不甚方便。 + + 所以往往开发人员会使用用户级线程,用户级线程比如Java的多线程,通过简单的api实现,当需要操作系统支持时,才使用底层调用api接口进行内核级的系统调用。 + + 但是一般情况下用户级线程是不被内核识别的,也就是说,用户级线程会被内核认为是一个进程,从而执行进程的调度。这样的话就没有意义了。 + + 所以一般情况下用户级线程会映射到对应的内核级线程中,内核为进程创建一定数量的内核级线程以供使用。Java中的线程基本上都是使用这种方式实现的。 + +### 线程通信和进程通信 + +线程通信一般只需要使用共享内存的方式即可实现。 + +而进程通信则需要额外的通信机制。 + +> 1 信号量,一般多进程间的同步使用信号量来完成,系统为临界区添加支持并发量为n的信号量,多进程访问临界区资源时,首先需要执行p操作来减少信号量,如果信号量等于0则操作失败,并且挂起进程,否则成功进入临界区执行。
+>
+> 当进程退出临界区时,执行v操作,将信号量加一,并唤醒挂起的进程进行操作。
+>
+> 2 管程,管程是对信号量的一个包装,避免使用信号量时出错。
+>
+> 3 管道,直接连接两个进程,一个进程写入管道,另一个进程可以读取管道,但是他不支持全双工,并且只能在父子进程间使用,所以局限性比较大
+>
+> 4 消息队列
+>
+> 操作系统维护一个消息队列,进程将消息写入队列中,其他进程轮询消息队列看是否有自己的消息,增加了轮询的开销,但是提高了消息的可靠性和易用性,同时支持了订阅消息。
+>
+> 5 socket
+>
+> socket一般用于不同主机上的进程通信,双方通过ip和port的方式寻址到对方主机并找到监听该端口的进程,为了完成通信,他们先建立tcp连接,在此基础上交换数据,也就完成了进程间的通信。
+
+### 进程调度
+
+不小心把这茬忘了,进程调度算法有几种,fifo先来先服务,短作业优先,时间片轮转,优先级调度,多级反馈队列等。
+基本上可以通过名字知道算法的大概实现。
+
+### 死锁
+
+死锁的必要条件:
+
+ 1互斥:资源必须是互斥的,只能给一个进程使用
+
+ 2占有和等待:占有资源时可以请求其他资源
+
+ 3不可抢占:资源占有时不会被抢
+
+ 4环路等待:有两个以上的进程组成一个环路,每个进程都在等待下一个进程的资源释放。
+
+死锁的处理方法:
+
+1鸵鸟
+
+2死锁预防
+
+在程序运行之前预防发生死锁。
+
+ (一)破坏互斥条件
+
+ 例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。
+
+ 这样子就破坏了互斥条件,转而使用单个队列串行执行操作。
+
+ (二)破坏占有和等待条件
+
+ 一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。
+
+ 分配了全部资源就不需要去等待其他资源。
+
+ (三)破坏不可抢占条件
+
+ 允许抢占式调度。
+
+ (四)破坏环路等待
+
+ 给资源统一编号,进程只能按编号顺序来请求资源。
+
+ 按正确顺序请求资源,就不会发生死锁。
+3死锁避免
+
+==银行家算法用于在程序运行时避免发生死锁。==
+
+银行家算法用于在程序运行时判断资源的分配情况是否是安全状态,如果某一步骤使程序可能发生死锁,银行家算法会拒绝该操作执行,从而避免进入不安全状态。
+
+(一)安全状态
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/ed523051-608f-4c3f-b343-383e2d194470.png)
+
+图 a 的第二列 Has 表示已拥有的资源数,第三列 Max 表示总共需要的资源数,Free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源(图 b),运行结束后释放 B,此时 Free 变为 5(图 c);接着以同样的方式运行 C 和 A,使得所有进程都能成功运行,因此可以称图 a 所示的状态时安全的。
+
+定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。
+
+(二)单个资源的银行家算法
+
+一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/d160ec2e-cfe2-4640-bda7-62f53e58b8c0.png)
+
+上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。
+
+4死锁检测和恢复
+
+==银行家算法检测程序并且阻止死锁发生,而死锁检测和恢复则
+不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。==
+
+(一)每种类型一个资源的死锁检测
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/b1fa0453-a4b0-4eae-a352-48acca8fff74.png)
+
+上图为==资源分配图==,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。
+
+如果有环则说明有死锁。
+
+(二)每种类型多个资源的死锁检测
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png)
+
+==资源分配矩阵==
+
+上图中,有三个进程四个资源,每个数据代表的含义如下:
+
+* E 向量:资源总量
+* A 向量:资源剩余量
+* C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量
+* R 矩阵:每个进程请求的资源数量
+
+如果存在请求数量无法被满足时,就会出现死锁。
+
+==(三)死锁恢复==
+
+利用抢占恢复,允许其他进程抢占资源。
+
+利用回滚恢复,回滚进程操作,释放其资源。
+
+通过杀死进程恢复,杀死进程后释放资源。
+
+## IO和磁盘
+
+磁盘是块设备,键盘是字符设备,网卡是网络设备。他们都接在IO总线上,属于慢速设备。
+
+### 磁盘和寻址
+
+磁盘的结构比较复杂,主要通过扇区,盘面和磁头位置决定当前访问的磁盘位置。
+
+cpu为了能够访问磁盘内容,首先要把磁盘的内容载入内存中,于是要和内存约定统一的寻址单元,cpu指定一个起始位置,访问该位置以后的n个存储单元,一般存储单元是一个页,16K或者8K。
+
+这一操作中,指向起始位置需要随机寻址,而接下来的访问操作是顺序访问,磁盘的随机读写和顺序读写的速度差距是很大的。所以一般会通过缓冲区来缓存IO数据。
+
+ 磁盘内部一般也会分为很多部分,比如操作系统会将磁盘做一个分区,使用磁盘的一些位置存储元数据信息,以保证磁盘能够支持操作系统以及文件系统。
+
+ 一般在物理分区的起始位置会有一个引导区和分区表,BIOS自动将磁盘中引导区的内核程序载入内存,此时操作系统才开始运行,并且根据分区表操作系统可以知道每个分区的起始位置在哪。
+
+读写一个磁盘块的时间的影响因素有:
+
+旋转时间(主轴旋转磁盘,使得磁头移动到适当的扇区上)
+寻道时间(制动手臂移动,使得磁头移动到适当的磁道上)
+实际的数据传输时间
+其中,寻道时间最长,因此磁盘调度的主要目标是使磁盘的平均寻道时间最短。
+
+1. 先来先服务
+FCFS, First Come First Served
+
+按照磁盘请求的顺序进行调度。
+
+优点是公平和简单。缺点也很明显,因为未对寻道做任何优化,使平均寻道时间可能较长。
+
+2. 最短寻道时间优先
+SSTF, Shortest Seek Time First
+
+优先调度与当前磁头所在磁道距离最近的磁道。
+
+虽然平均寻道时间比较低,但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。具体来说,两边的磁道请求更容易出现饥饿现象。
+
+3. 电梯算法
+SCAN
+
+电梯总是保持一个方向运行,直到该方向没有请求为止,然后改变运行方向。
+
+电梯算法(扫描算法)和电梯的运行过程类似,总是按一个方向来进行磁盘调度,直到该方向上没有未完成的磁盘请求,然后改变方向。
+
+因为考虑了移动方向,因此所有的磁盘请求都会被满足,解决了 SSTF 的饥饿问题。
+
+### IO设备
+
+除了硬盘以外,还有键盘,网卡等IO设备,这些设备需要操作系统通过驱动程序来进行交互,驱动程序用于适配这些设备。
+
+为了执行IO操作,内核一般要为IO设备提供一个缓存区,比如网卡的IO操作,会为socket提供一个缓存区,当多个socket使用一个缓冲区进行通信,就是复用了缓冲区,也就是IO复用的一种方式。
+
+同时,内核还会维护一个IO请求的列表,当IO请求就绪时,让几个线程去执行IO操作,实现了线程的复用。
+
+### 文件系统
+
+文件系统是基于底层存储建立的一个树形文件结构。比较经典的是Linux的文件系统,首先在硬盘的超级块中安装文件系统,磁盘引导时会加载文件系统的信息。
+
+linux使用inode来标识任意一个文件。inode存储除了文件名以外的文件信息,包括创建时间,权限,以及一个指向磁盘存储位置的指针,那里才是真正存放数据的地方。
+
+一个目录也是一个inode节点。
+
+详细阐述一次文件访问的过程:
+
+ 首先用户ls查看目录。由于一个目录也是一个文件,所以相当于是看目录文件下有哪些东西。
+
+ 实际上目录文件是一个特殊的inode节点,它不需要存储实际数据,而只是维护一个文件名到inode的映射表。
+
+ 于是我们ls到另一个目录。同理他也是一个inode。我们在这个inode下执行vi操作打开某个文件,于是linux通过inode中的映射表找到了我们请求访问的文件名对应的inode。
+
+ 然后寻道到对应的磁盘位置,读取内容到缓冲区,通过系统调用把内容读到内存中,最后进行访问。
+
diff --git "a/md/350円256円241円347円256円227円346円234円272円347円275円221円347円273円234円345円255円246円344円271円240円346円200円273円347円273円223円.md" "b/md/350円256円241円347円256円227円346円234円272円347円275円221円347円273円234円345円255円246円344円271円240円346円200円273円347円273円223円.md"
new file mode 100644
index 0000000..cae10ae
--- /dev/null
+++ "b/md/350円256円241円347円256円227円346円234円272円347円275円221円347円273円234円345円255円246円344円271円240円346円200円273円347円273円223円.md"
@@ -0,0 +1,640 @@
+---
+title: 计算机网络学习总结
+date: 2018年07月09日 22:32:57
+tags:
+ - 计算机网络
+categories:
+ - 后端
+ - 技术总结
+---
+##这部分内容主要是基于一些关于计算机网络基础的学习总结,内容不全面,只讲述了其中的一小部分,后续会再补充,如有错误,还请见谅。
+
+# 计算机网络常见概念
+
+## 网卡和路由器
+
+网卡是一个有mac地址的物理设备,通过mac地址与局域网内的交换机通信,交换机可以识别mac地址。
+
+而单纯的中继器,集线器,双绞线等设备只识别物理层设备。
+
+路由器则工作在3层ip层,必须要有ip才能工作,所以路由器每一个接口都对应一个ip,维护一个可以识别ip的路由表,进行ip数据报转发。
+
+## 交换机
+交换机具有自学习能力,学习的是交换表的内容。交换表中存储着 MAC 地址到接口的映射。
+
+## 以太网
+以太网是一种星型拓扑结构局域网。
+
+早期使用集线器进行连接,它是一种物理层设备,作用于比特而不是帧,当一个比特到达接口时,集线器重新生成这个比特,并将其能量强度放大,从而扩大网络的传输距离。之后再将这个比特向其它所有接口。特别是,如果集线器同时收到同时从两个不同接口的帧,那么就发生了碰撞。
+
+目前以太网使用交换机替代了集线器,它不会发生碰撞,能根据 MAC 地址进行存储转发。
+
+## 虚拟局域网VLAN
+正常情况下,局域网中的链路层广播在整个局域网可达,而vlan可以在物理局域网中划分虚拟局域网,使广播帧只有在vlan当中的主机才能收到。
+
+虚拟局域网可以建立与物理位置无关的逻辑组,只有在同一个虚拟局域网中的成员才会收到链路层广播信息,例如下图中 (A1, A2, A3, A4) 属于一个虚拟局域网,A1 发送的广播会被 A2、A3、A4 收到,而其它站点收不到。
+
+## DHCP协议(动态主机配置协议)
+
+首先DHCP是为了让主机获得一个ip地址,所以主机会发一个0.0.0.0为发送方,255.255.255.255为接收方的ip数据报,也就是广播数据报,并且广播数据包只在局域网中有效,然后链路层解析为数据帧,发送给局域网内的DHCP服务器。
+
+## ARP协议
+arp负责把ip地址解析成局域网内的一个mac地址,只在局域网中有效。逆arp则把mac地址解析成ip地址。
+
+网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/66192382-558b-4b05-a35d-ac4a2b1a9811.jpg)
+
+ARP 实现由 IP 地址得到 MAC 地址。
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/b9d79a5a-e7af-499b-b989-f10483e71b8b.jpg)
+
+每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到硬件地址的映射表。
+
+> 如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射,此时主机 A 通过广播的方式发送 ARP 请求分组,主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到 MAC 地址的映射。
+
+## 网关和NAT
+当需要和外部局域网访问时,需要经过网关服务器以便兼容不同协议栈。局域网内部使用内网ip,经过网关时要转成外网ip,网关会帮你完成改写操作,当收到数据报时,网关又会帮你把ip改为内网ip。这种修改ip隐藏内部网络的方式叫做NAT。
+
+nat穿透的方式是主机和网关服务器协定一个ip地址作为主机服务的ip,所以主机可以通过这个ip和外网交流。
+
+## DNS协议和http请求过程
+访问一个域名时,会发送dns报文请求(应用层)给本地的DNS服务器,解析出域名对应的ip,然后三次握手建立连接,(当然TCP数据报由本地局域网经过网关转给外网,再经过多次路由才到达目标主机),然后发送http请求获得响应报文
+
+## ICMP
+ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/e3124763-f75e-46c3-ba82-341e6c98d862.jpg)
+
+ICMP 报文分为差错报告报文和询问报文。
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/aa29cc88-7256-4399-8c7f-3cf4a6489559.png)
+
+
+ 1. Ping
+ Ping 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。
+
+ Ping 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报。
+
+ 2. Traceroute
+ Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径,事实上,traceroute也封装着无法交付的udp,和ping类似。。
+
+源主机向目的主机发送一连串的 IP 数据报,每个数据包的ttl时间不同,所以可以跟踪每一跳路由的信息。
+
+==但是因为数据报封装的是无法交付的UDP报文,因此目的主机要向源主机发送 ICMP终点不可达差错报告报文。之后源主机知道了到达目的主机所经过的路由器 IP地址以及到达每个路由器的往返时间。==
+
+## 虚拟专用网VPN和内网ip
+
+由于 IP 地址的紧缺,一个机构能申请到的 IP 地址数往往远小于本机构所拥有的主机数。并且一个机构并不需要把所有的主机接入到外部的互联网中,机构内的计算机可以使用仅在本机构有效的 IP 地址(专用地址)。
+
+有三个专用地址块:
+
+* 10.0.0.0 ~ 10.255.255.255
+* 172.16.0.0 ~ 172.31.255.255
+* 192.168.0.0 ~ 192.168.255.255
+
+这些ip也称为内网ip,用于局域网间的通信,只能通过网关抵达公网。
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/1556770b-8c01-4681-af10-46f1df69202c.jpg)
+
+使用隧道技术实现vpn。
+
+ 原理是;普通的内网ip无法被访问到,一般可以使用nat技术让网关作为中转人,而ip数据报也会改写成网关服务器的地址。
+
+ 如果想让数据报保留内网地址,并且实现跨公网访问,那么只能通过隧道技术,把内网数据报加密包装在公网ip数据报中,然后通过公网ip抵达对方的专用网络,进行拆包和发送。
+
+ 为什么vpn能翻墙呢,因为我们通过对vpn服务器的连接,可以将内网ip数据报装在里面,发送给vpn,vpn解析后再发送给真正的服务器。
+
+ 由于本地网关阻拦了某些网站的请求,所以我们要把这个请求加密封装,然后通过隧道把数据发给一个海外服务器,让他真正完成请求。
+
+## 应用层
+
+应用层的协议主要是http,ftp这类协议,http访问超文本html,而ftp访问文件系统。
+
+### http
+
+通过浏览器可以方便地进行dns解析,建立tcp连接,发送http请求,得到http响应,这些工作都是浏览器完成的。
+
+### http1.0 1.1和2.0
+
+#### 1.0和1.1的主要变化
+
+ 1 http1.0经过多年发展,在1.1提出了改进。
+ 首先是提出了长连接,http请求可以在一次tcp连接中不断发送。
+
+ 2 然后是http1.1支持只发送header而不发送body。原因是先用header判断能否成功,再发数据,节约带宽,事实上,post请求默认就是这样做的。
+
+ 3 http1.1的host字段。由于虚拟主机可以支持多个域名,所以一般将域名解析后得到host。
+
+#### http1.0和http2.0的区别。
+
+ http2.0变化巨大。
+
+ 1 http支持多路复用,同一个连接可以并发处理多个请求,方法是把http数据包拆为多个帧,并发有序的发送,根据序号在另一端进行重组,而不需要一个个http请求顺序到达。
+
+ 2 http2.0支持服务端推送,就是服务端在http请求到达后,除了返回数据之外,还推送了额外的内容给客户端。
+
+ 3HTTP2.0压缩了请求头,同时基本单位是二进制帧流,这样的数据占用空间更少。
+
+ 4http2.0只适用于https场景,因为其在http和tcp中间加了一层ssl层。
+
+### get和post
+
+ get和post本质都是http请求,只不过对他们的作用做了界定和适配,并且让他们适应各自的场景。
+
+ 1本质区别是get只是一次http请求,post先发请求体再发请求体,实际上是两次请求
+
+ 2表面区别:
+
+ get可以cache而post不能,因为浏览器是这么安排的
+
+ 一般设计get是幂等的而post不是
+
+ get的参数放在url传递,而post放在请求体里,因为get没有请求体。
+ 所以get请求不安全,并且有长度限制(url不能太长),而post几乎没有限制,请求体可以很大。
+
+###
+
+
+#### session和cookie
+
+并且浏览器还维护了cookie以便记录用于对网站的一些信息,下次请求时在http报文中带上这些数据,服务器接收以后根据cookie中的sessionid获取对应的session即可
+
+#### token
+
+session一般维护在内存中,有时候也会持久化到数据库,但是如果session由单点维护可能出现宕机等情况,于是一般会采用分布式的方案。
+
+
+ session存放的几种方案。
+ 0 存在内存中。用sessionid标识用户。
+ 这样的session十分依赖于cookie。如果浏览器禁用了cookie则session无用武之地。
+
+ 当然也可以把内容存在数据库里,缺点是数据库访问压力较大。
+
+ 1有做法会将session内容存在cookie中,但前提是经过了加密,然后下次服务器对其进行解密,但是这样浏览器需要维护太多内容了。
+
+ 2当用户登录或者执行某些操作,则使用用户的一部分字段信息进行加密算法得到一串字符串成为token,用于唯一标识用户,或者是某些操作,比如登录,支付,服务端生成该token返回给用户,用户提交请求时必须带上这个token,就可以确认用户信息以及操作是否合法了。
+
+ 这样我们不需要存session,只需要在想得到用户信息时解密token即可。
+
+ token还有一个好处就是可以在移动端和pc端兼容,因为移动端不支持cookie。
+
+ 3token和oauth。经常有第三方授权登录的例子,本质就是使用token。首先我们打开授权登录页,登陆后服务端返回token,我们提交第三方的请求时,带上这个token,第三方不知道他是啥意思,并且token过段时间就过期了。
+
+#### cas单点登录
+
+单点登录是为了多个平台之间公用一个授权系统,做法是,所有登录都要指向统一登录服务,登陆成功以后在认证中心建立session,并且得到ticket,然后重定向页面,此时页面也会向认证中心确认ticket是否合法,然后就可以访问其他系统的页面了。
+
+从而访问其他系统时,由于已经有了认证中心的cookie,所以直接带上ticket访问即可。
+
+每次访问新系统时需要在认证中心注册session,然后单点退出时再把这些session退出,才能实现用户登出。
+
+## web安全和https
+
+### 密码加密
+
+MD5等加密方法可以用来对密码进行加密。一般还会加盐
+
+### xss跨站脚本攻击
+
+利用有输入功能网站的输入框来注入JavaScript脚本代码,用户访问该页面时会自动执行某些脚本代码,导致cookie等个人信息泄露,可能会被转发到其他网站。
+
+解决办法是对输入进行检验,利用一个些工具类就可以做到。
+
+### 跨站点请求伪造csrf
+首先用户访问了一个网站并登陆,会把cookie保留在浏览器,
+然后某些网站用一些隐性链接诱导用户点击,点击时发送请求会携带浏览器中的cookie,比如支付宝的账号密码,通过该cookie再去伪造一个支付宝支付请求,达到伪造请求的目的。
+
+解决这个问题的办法就是禁止js请求跨域名。但是他为ajax提供了特殊定制。
+
+### SQL 注入攻击
+1. 概念
+服务器上的数据库运行非法的 SQL 语句,主要通过拼接来完成。
+
+3. 防范手段
+(一)使用参数化查询
+
+以下以 Java 中的 PreparedStatement 为例,它是预先编译的 SQL 语句,可以传入适当参数并且多次执行。由于没有拼接的过程,因此可以防止 SQL 注入的发生。
+
+(二)单引号转换
+
+将传入的参数中的单引号转换为连续两个单引号,PHP 中的 Magic quote 可以完成这个功能。
+
+### 拒绝服务攻击
+拒绝服务攻击(denial-of-service attack,DoS),亦称洪水攻击,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。
+
+分布式拒绝服务攻击(distributed denial-of-service attack,DDoS),指攻击者使用网络上两个或以上被攻陷的电脑作为"僵尸"向特定的目标发动"拒绝服务"式攻击。
+
+DDoS攻击通过大量合法的请求占用大量网络资源,以达到瘫痪网络的目的。
+
+这种攻击方式可分为以下几种:
+
+ 通过使网络过载来干扰甚至阻断正常的网络通讯;
+ 通过向服务器提交大量请求,使服务器超负荷;
+ 阻断某一用户访问服务器;
+ 阻断某服务与特定系统或个人的通讯。
+
+攻击现象
+
+ 被攻击主机上有大量等待的TCP连接;
+ 网络中充斥着大量的无用的数据包;
+ 源地址为假 制造高流量无用数据,造成网络拥塞,使受害主机无法正常和外界通讯;
+ 利用受害主机提供的传输协议上的缺陷反复高速的发出特定的服务请求,使主机无法处理所有正常请求;
+ 严重时会造成系统死机。
+
+总体来说,对DoS和DDoS的防范主要从下面几个方面考虑:
+
+ 尽可能对系统加载最新补丁,并采取有效的合规性配置,降低漏洞利用风险;
+
+ 采取合适的安全域划分,配置防火墙、入侵检测和防范系统,减缓攻击。
+
+ 采用分布式组网、负载均衡、提升系统容量等可靠性措施,增强总体服务能力。
+### https
+
+https博大精深,首先先来看看他的基础知识
+
+ 1对称加密和非对称加密
+
+ 对称加密两方使用同一把密钥加密和解密,传输密钥时如果丢失就会被破解。
+
+ 2非对称加密两方各有一把私钥,而公钥公开,A用私钥加密,把公钥和数据传给B,B用公钥解密。同理,B用私钥对数据进行加密,返回给A,A也用公钥进行解密。
+
+ 3非对称加密只要私钥不丢就很安全,但是效率比较低,所以一般使用非对称加密传输对称加密的密钥,使用对称加密完成数据传输。
+
+ 4数字签名,为了避免数据在传输过程中被替换,比如黑客修改了你的报文内容,但是你并不知道,所以我们让发送端做一个数字签名,把数据的摘要消息进行一个加密,比如MD5,得到一个签名,和数据一起发送。然后接收端把数据摘要进行md5加密,如果和签名一样,则说明数据确实是真的。
+
+ 5数字证书,对称加密中,双方使用公钥进行解密。虽然数字签名可以保证数据不被替换,但是数据是由公钥加密的,如果公钥也被替换,则仍然可以伪造数据,因为用户不知道对方提供的公钥其实是假的。
+
+ 所以为了保证发送方的公钥是真的,CA证书机构会负责颁发一个证书,里面的公钥保证是真的,用户请求服务器时,服务器将证书发给用户,这个证书是经由系统内置证书的备案的。
+
+
+
+ 6 https过程
+
+ 用户发送请求,服务器返回一个数字证书。
+
+ 用户在浏览器端生成一个随机数,使用证书中的公钥加密,发送给服务端。
+
+ 服务端使用公钥解密该密文,得到随机数。
+
+ 往后两者使用该随机数作为公钥进行对称加密。
+
+
+ 番外:关于公钥加密私钥解密与私钥加密公钥解密说明
+ 第一种是签名,使用私钥加密,公钥解密,用于让所有公钥所有者验证私钥所有者的身份并且用来防止私钥所有者发布的内容被篡改.但是不用来保证内容不被他人获得.
+
+ 第二种是加密,用公钥加密,私钥解密,用于向公钥所有者发布信息,这个信息可能被他人篡改,但是无法被他人获得.搜索
+
+
+## 传输层
+
+ UDP 和 TCP 的特点
+ 用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信。
+
+ 传输控制协议 TCP(Transmission Control Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一)。
+
+TCP是传输层最重要的协议。
+>
+> 由于网络层只提供最大交付的服务,尽可能地完成路由转发,以及把链路层报文传送给任意一台主机。他做的工作很专注,所以不会提供其他的可靠性保证。
+>
+> 但是真实网络环境下随时会发生丢包,乱序,数据内容出错等情况,这些情况必须得到处理,于是我们使用传输层tcp来解决这些问题。
+
+### UDP报文
+
+伪首部的意义:伪首部并非TCP&UDP数据报中实际的有效成分。伪首部是一个虚拟的数据结构,其中的信息是从数据报所在IP分组头的分组头中提取的,既不向下传送也不向上递交,而仅仅是为计算校验和。
+
+
+
+首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。
+
+### TCP 首部格式
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/55dc4e84-573d-4c13-a765-52ed1dd251f9.png)
+
+* **序号** :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。
+
+* **确认号** :期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。
+
+* **数据偏移** :指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。
+
+* **确认 ACK** :当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。
+
+* **同步 SYN** :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。
+
+* **终止 FIN** :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。
+
+* **窗口** :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。
+*
+
+### 三次握手和四次挥手
+ 为了保证tcp的可靠传输,需要建立起一条通路,也就是所谓连接。这条通路必须保证有效并且能正确结束。
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/e92d0ebc-7d46-413b-aec1-34a39602f787.png)
+
+
+
+ 三次握手
+
+ 1 首先客户端发送连接请求syn,携带随机数x。
+ 2 服务端返回请求ack,x + 1,说明服务端对x进行了回复。
+ 3 客户端返回请求ack,y,说明接受到了信息并且开始传输数据,起始数据为y。
+
+ 客户端状态时syn_send和establish
+ 服务端则是从listen到syn_rcvd,再到establish
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/f87afe72-c2df-4c12-ac03-9b8d581a8af8.jpg)
+
+ 四次挥手
+
+ 1 首先客户端请求断开连接,发送fin请求,服务端返回fin的ack,继续处理断开前需要处理完的数据。
+
+ 2 过了一会,服务端处理完数据发送给客户端ack,表明已经关闭,客户端最后再发一个ack给服务端,如果服务端已关闭则无反应,客户端经过两个ttl后挥手完毕,确认服务端断开。这两个ttl成为time wait状态,用于确定服务端真的关闭。
+
+ 3 客户端发完fin后的状态从establish变为fin1——wait,服务端发完ack后的状态从establish变为closewait。
+
+ 4 客户端收到第一个ack后进入fin_2wait状态,服务端过了一会发送last——ack给客户端,说明关闭好了,客户端收到ack后进入timewait,然后发送ack。双方都closed。
+
+### 半连接syn和洪泛法攻击
+
+黑客开启大量的syn请求而不发送ack,服务端开启半连接等待ack,直到资源耗尽,所以必须检测来访ip
+### 为什么要三次握手
+
+三次握手的原因
+
+第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。
+
+也就是说,如果只有两次握手,服务端返回ack后直接通信,那么如果客户端因为网络问题没有收到ack,可能会再次请求连接,但时服务端不知道这其实是同一个请求,于是又打开了一个连接,相当于维护了很多的无用连接。
+### time wait的作用
+
+1 需要服务端可靠地终止连接,如果处于time_wait客户端发给服务端的ack报文丢失,则服务端会再发一次fin,此时客户端不应该关闭。
+
+2 保证迟来的tcp报文有时间被丢弃,因为2msl里超时抵达的报文都会被丢弃。
+
+## 可靠传输协议
+
+TCP协议有三个重要属性。
+
+ 可靠传输,主要通过有序接收,确认后发送,以及超时重传来实现,并且使用分片来提高发送效率,通过检验和避免错误。
+
+ 流量控制,主要通过窗口限制接收和发送速率。
+
+ 拥塞控制,主要通过不同拥塞状态的算法来处理拥塞,一开始发的比较慢,然后指数增加,当丢包时再降低速度,重新开始第一阶段,避免拥塞。
+
+总结以下就是几个特点:
+
+TCP 可靠传输
+
+ TCP 使用超时重传来实现可靠传输:
+
+ 1 如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。
+
+
+ 2 滑动窗口可以连续发送多个数据再统一进行确认。
+
+ 因为发送端希望在收到确认前,继续发送其它报文段。比如说在收到0号报文的确认前还发出了1-3号的报文,这样提高了信道的利用率。
+
+ 3 滑动窗口只重传丢失的数据报
+
+ 但可以想想,0-4发出去后可能要重传,所以需要一个缓冲区维护这些报文,所以就有了窗口。
+
+ 4每当完成一个确认窗口往前滑动一格,可以传新的一个数据,因此可以顺序发送顺序确认
+
+TCP 流量控制
+
+ 流量控制是为了控制发送方发送速率,保证接收方来得及接收。
+
+ 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。
+
+TCP 拥塞控制
+
+ 如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接受,而拥塞控制是为了降低整个网络的拥塞程度。
+
+ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。
+
+ 一般刚开始时慢开始,然后拥塞避免,出现个别丢包时(连续三个包序号不对),
+
+ 则执行快重传,然后进入快恢复阶段,接着继续拥塞避免。如果发生多次超时也就是拥塞时,直接进入慢开始。
+>
+>这种情况下,只是丢失个别报文段,而不是网络拥塞,因此执行快恢复,令 ssthresh = cwnd/2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。
+
+ ==发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,注意拥塞窗口与发送方窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口==。
+
+滑动窗口协议综合实现了上述这一些内容:
+
+为什么要使用滑动窗口,因为滑动窗口可以实现可靠传输,流量控制和拥塞控制(拥塞控制用的是拥塞窗口变量)
+
+### tcp的粘包拆包
+
+tcp报文是流式的数据,没有标识数据结束,只有序号等字段,tcp协议自动完成数据报的切分。由于tcp使用缓冲区发送,又没有标识结束,当缓冲区的数据没清空又有新数据进来,就会发生粘包,如果数据太大存装不下,就会被拆包。
+
+## 网络层
+
+## IP 数据报格式
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/85c05fb1-5546-4c50-9221-21f231cdc8c5.jpg)
+
+* **版本** : 有 4(IPv4)和 6(IPv6)两个值;
+
+* **首部长度** : 占 4 位,因此最大值为 15。
+
+* **总长度** : 包括首部长度和数据部分长度。
+
+* **生存时间** :TTL,它的存在是为了防止无法交付的数据报在互联网中不断兜圈子。以路由器跳数为单位,当 TTL 为 0 时就丢弃数据报。
+
+==* **协议** :指出携带的数据应该上交给哪个协议进行处理,例如 ICMP、TCP、UDP 等。==
+
+* **首部检验和** :因为数据报每经过一个路由器,都要重新计算检验和,因此检验和不包含数据部分可以减少计算的工作量。
+
+
+* **片偏移** : 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/23ba890e-e11c-45e2-a20c-64d217f83430.png)
+
+总结:
+
+ ip层只保证尽最大努力交付,他所承载的一切都是对路由,转发,已经网络传输最友好的设计。
+
+ 路由器负责记录路由表和转发ip数据报,路由表记录着ip地址和下一跳路由的端口的对应关系。
+
+ 由于路由聚合的缘故,一般用170.177.233.0/24就可以标识好几个网络了。
+
+ 以前会使用A,B,C类地址,和子网,现在直接使用地址聚合,前24位是网络号,后面8位是主机号。
+
+ ## 某个聚合路由地址划分网络给n台机器,是否符合要求。。
+
+ 要看这个网络中的主机号能否达到n个。
+
+### IP 地址编址方式
+
+IP 地址的编址方式经历了三个历史阶段:
+
+* 分类
+* 子网划分
+* 无分类
+
+### [](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C.md#1-%E5%88%86%E7%B1%BB)1\. 分类
+
+由两部分组成,网络号和主机号,其中不同分类具有不同的网络号长度,并且是固定的。
+
+IP 地址 ::= {< 网络号>, < 主机号>}
+
+[](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/cbf50eb8-22b4-4528-a2e7-d187143d57f7.png)
+
+2. 子网划分
+
+
+ 通过在主机号字段中拿一部分作为子网号,把两级 IP 地址划分为三级 IP 地址。注意,外部网络看不到子网的存在。
+
+ IP 地址 ::= {< 网络号>, < 子网号>, < 主机号>}
+
+ 要使用子网,必须配置子网掩码。一个 B 类地址的默认子网掩码为 255.255.0.0,如果 B 类地址的子网占两个比特,那么子网掩码为 11111111 11111111 11000000 00000000,也就是 255.255.192.0。
+
+3. 无分类
+
+
+ 无分类编址 CIDR 消除了传统 A 类、B 类和 C 类地址以及划分子网的概念,使用网络前缀和主机号来对 IP 地址进行编码,网络前缀的长度可以根据需要变化。
+
+ IP 地址 ::= {< 网络前缀号>, < 主机号>}
+
+ CIDR 的记法上采用在 IP 地址后面加上网络前缀长度的方法,例如 128.14.35.7/20 表示前 20 位为网络前缀。
+
+ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为网络前缀的长度。
+
+一个 CIDR 地址块中有很多地址,一个 CIDR 表示的网络就可以表示原来的很多个网络,并且在路由表中只需要一个路由就可以代替原来的多个路由,减少了路由表项的数量。
+
+把这种通过使用网络前缀来减少路由表项的方式称为路由聚合,也称为 构成超网 。
+
+在路由表中的项目由"网络前缀"和"下一跳地址"组成,在查找时可能会得到不止一个匹配结果,应当采用最长前缀匹配来确定应该匹配哪一个。
+
+总结
+
+ 使用分类法的ip必须标识是哪一类地址,比较麻烦,而且一旦设置为某类地址它就只能使用那一部分地址空间了。
+
+ 使用子网掩码可以避免使用分类并且更灵活地决定网络号和主机号的划分。但是需要配置子网掩码,比较复杂。
+
+ CIDR 138.1.2.11/24
+ 使用CIDR避免了子网划分,直接使用后n位作为网络号,简化了子网的配置(实际上用n代替了子网掩码)。并且在路由器中可以使用地址聚合,一个ip可以聚合多个网络号。
+
+### ip分片详谈
+
+在TCP/IP分层中,数据链路层用MTU(Maximum Transmission Unit,最大传输单元)来限制所能传输的数据包大小,MTU是指一次传送的数据最大长度,不包括数据链路层数据帧的帧头,如以太网的MTU为1500字节,实际上数据帧的最大长度为1512字节,其中以太网数据帧的帧头为12字节。
+
+当发送的IP数据报的大小超过了MTU时,IP层就需要对数据进行分片,否则数据将无法发送成功。
+
+IP分片的实现
+
+> IP分片发生在IP层,不仅源端主机会进行分片,中间的路由器也有可能分片,因为不同的网络的MTU是不一样的,如果传输路径上的某个网络的MTU比源端网络的MTU要小,路由器就可能对IP数据报再次进行分片。而分片数据的重组只会发生在目的端的IP层。
+
+==避免IP分片==
+> 在网络编程中,我们要避免出现IP分片,那么为什么要避免呢?原因是IP层是没有超时重传机制的,如果IP层对一个数据包进行了分片,只要有一个分片丢失了,只能依赖于传输层进行重传,结果是所有的分片都要重传一遍,这个代价有点大。由此可见,IP分片会大大降低传输层传送数据的成功率,所以我们要避免IP分片。
+>
+
+ 对于UDP包,我们需要在应用层去限制每个包的大小,一般不要超过1472字节,即以太网MTU(1500)—UDP首部(8)—IP首部(20)。
+
+ 对于TCP数据,应用层就不需要考虑这个问题了,因为传输层已经帮我们做了。
+
+在建立连接的三次握手的过程中,连接双方会相互通告MSS(Maximum Segment =Size,最大报文段长度),MSS一般是MTU—IP首部(20)—TCP首部(20),每次发送的TCP数据都不会超过双方MSS的最小值,所以就保证了IP数据报不会超过MTU,避免了IP分片。
+
+3. 外部网关协议 BGP
+
+
+ BGP(Border Gateway Protocol,边界网关协议)
+
+ AS 之间的路由选择很困难,主要是因为互联网规模很大。并且各个 AS 内部使用不同的路由选择协议,就无法准确定义路径的度量。并且 AS 之间的路由选择必须考虑有关的策略,比如有些 AS 不愿意让其它 AS 经过。
+
+ BGP 只能寻找一条比较好的路由,而不是最佳路由。
+
+ 每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。
+
+### 路由选择协议和算法
+>
+> 路由选择协议
+> 路由选择协议都是自适应的,能随着网络通信量和拓扑结构的变化而自适应地进行调整。
+>
+> 互联网可以划分为许多较小的自治系统 AS,一个 AS 可以使用一种和别的 AS 不同的路由选择协议。
+>
+> 可以把路由选择协议划分为两大类:
+>
+> 自治系统内部的路由选择:RIP 和 OSPF
+> 自治系统间的路由选择:BGP
+
+
+总结:
+
+ 1. 内部网关协议 RIP
+ RIP 是一种基于距离向量的路由选择协议。距离是指跳数,直接相连的路由器跳数为 1,跳数最多为 15,超过 15 表示不可达。
+
+ RIP 按固定的时间间隔仅和相邻路由器交换自己的路由表,经过若干次交换之后,所有路由器最终会知道到达本自治系统中任何一个网络的最短距离和下一跳路由器地址。
+
+ 2. 内部网关协议 OSPF
+ 开放最短路径优先 OSPF,是为了克服 RIP 的缺点而开发出来的。
+
+ 开放表示 OSPF 不受某一家厂商控制,而是公开发表的;最短路径优先表示使用了 Dijkstra 提出的最短路径算法 SPF。
+
+OSPF 具有以下特点:
+
+ 计算出最短路径,然后向本自治系统中的所有路由器发送信息,这种方法是洪泛法。
+
+ 发送的信息就是与相邻路由器的链路状态,链路状态包括与哪些路由器相连以及链路的度量,度量用费用、距离、时延、带宽等来表示。
+
+ 变化时,路由器才会发送信息。
+
+ 所有路由器都具有全网的拓扑结构图,并且是一致的。相比于 RIP,OSPF 的更新过程收敛的很快。
+
+总结:
+
+ AS是一个自治域,一般是指相似度很大公用一个协议的路由器族,比如同一个运营商的网络。
+
+ 因特网中AS之间的路由选择协议是BGP。
+
+ AS内的路由选择协议有RIP和OSPF。
+
+ RIP两两交换,最后大家都同步。
+
+ OSPF找到最短路径。告诉大家。
+
+## 链路层
+链路层最主要是指局域网内的网络交互了,使用mac地址通过交换机进行通信,其中用得最多的局域网协议就是以太网。
+
+链路层使用MTU表示最大传输帧长度,报文长度不能超过MTU,否则会进行分片,比如比较大的IP数据报就会被分片,为了避免被分片。一般要控制IP报文长度。
+
+广播:
+
+要理解什么是广播风暴,就必须先理解网络通信技术。 网络上的一个节点,它发送一个数据帧或包,被传输到由广播域定义的本地网段上的每个节点就是广播。
+
+> 网络广播分为第2层广播和第3层广播。第2层广播也称硬件广播,用于在局域网内向所有的结点发送数据,通常不会穿过局域网的边界(路由器),除非它变成一个单播。广播将是一个二进制的全1或者十六进制全F的地址。而第3层广播用于在这个网络内向所有的结点发送数据。
+
+帧的传输方式,即单播帧(Unicast Frame)、多播帧(Multicast Frame)和广播帧(Broadcast Frame)。
+
+ 1、单播帧
+ 单播帧也称"点对点"通信。此时帧的接收和传递只在两个节点之间进行,帧的目的MAC地址就是对方的MAC地址,网络设备(指交换机和路由器)根据帧中的目的MAC地址,将帧转发出去。
+
+ 2、多播帧
+ 多播帧可以理解为一个人向多个人(但不是在场的所有人)说话,这样能够提高通话的效率。多播占网络中的比重并不多,主要应用于网络设备内部通信、网上视频会议、网上视频点播等。
+
+ 3、广播帧
+ 广播帧可以理解为一个人对在场的所有人说话,这样做的好处是通话效率高,信息一下子就可以传递到全体。在广播帧中,帧头中的目的MAC地址是"FF.FF.FF.FF.FF.FF",代表网络上所有主机网卡的MAC地址。
+
+ 广播帧在网络中是必不可少的,如客户机通过DHCP自动获得IP地址的过程就是通过广播帧来实现的。而且,由于设备之间也需要相互通信,因此在网络中即使没有用户人为地发送广播帧,网络上也会出现一定数量的广播帧。
+
+ 同单播和多播相比,广播几乎占用了子网内网络的所有带宽。网络中不能长时间出现大量的广播帧,否则就会出现所谓的"广播风暴"(每秒的广播帧数在1000以上)。拿开会打一个比方,在会场上只能有一个人发言,如果所有人都同时发言的话,会场上就会乱成一锅粥。广播风暴就是网络长时间被大量的广播数据包所占用,使正常的点对点通信无法正常进行,其外在表现为网络速度奇慢无比。出现广播风暴的原因有很多,一块故障网卡就可能长时间地在网络上发送广播包而导致广播风暴。
+
+ 使用路由器或三层交换机能够实现在不同子网间隔离广播风暴的作用。当路由器或三层交换机收到广播帧时并不处理它,使它无法再传递到其他子网中,从而达到隔离广播风暴的目的。因此在由几百台甚至上千台电脑构成的大中型局域网中,为了隔离广播风暴,都要进行子网划分。
+ 使用vlan完全可以隔离广播风暴。
+
+> 在交换以太网上运行TCP/IP环境下:
+> 二层广播是在数据链路层的广播,它 的广播范围是二层交换机连接的所有端口;二层广播不能通过路由器。
+>
+> 三层广播就是在网络层的广播,它的范围是同一IP子网内的设备,子网广播也不能通过路由器。
+>
+> 第三层的数据必须通过第二层的封装再发送,所以三层广播必然通过二层广播来实现。
+>
+> 设想在同一台二层交换机上连接2个ip子网的设备,所有的设备都可以接收到二层广播,但三层广播只对本子网设备有效,非本子网的设备也会接收到广播包,但会被丢弃。
+
+广播风暴(broadcast storm)
+
+简单的讲是指当广播数据充斥网络无法处理,并占用大量网络带宽,导致正常业务不能运行,甚至彻底瘫痪,这就发生了"广播风暴"
+
+。一个数据帧或包被传输到本地网段 (由广播域定义)上的每个节点就是广播;由于网络拓扑的设计和连接问题,或其他原因导致广播在网段内大量复制,传播数据帧,导致网络性能下降,甚至网络瘫痪,这就是广播风暴。
+
+要避免广播风暴,可以采用恰当划分VLAN、缩小广播域、隔离广播风暴,还可在千兆以太网口上启用广播风暴控制,最大限度地避免网络再次陷入瘫痪。
\ No newline at end of file
From 15b71a95047d41a4b068f624442a415ac0434868 Mon Sep 17 00:00:00 2001
From: How_2_Play_Life <362294931@qq.com>
Date: Mon, 9 Jul 2018 22:52:40 +0800
Subject: [PATCH 19/23] Create README.md
---
README.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 66 insertions(+)
create mode 100644 README.md
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f7b1daf
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+| I | II | III | IV | V | VI | VII | VIII | IX |
+| :------: | :---------: | :-------: | :---------: | :---: | :---------:| :---------: | :---------: | :---------:|
+| 算法[:pencil2:](#算法-pencil2) | 操作系统[:computer:](#操作系统-computer)|网络[:cloud:](#网络-cloud) | 数据库[:floppy_disk:](#数据库-floppy_disk)| Java[:couple:](#Java-couple) |JavaWeb [:coffee:](#JavaWeb-coffee)| 分布式 [:sweat_drops:](#分布式-sweat_drops)| 设计模式[:hammer:](#设计模式-hammer)| Hadoop[:speak_no_evil:](#Hadoop-speak_no_evil)|
+
+## 算法 :pencil2:
+
+> [Mysql原理与实践总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Mysql%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93.md)
+
+## 操作系统 :computer:
+
+> [操作系统学习总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93.md)
+
+> [Mysql原理与实践总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Mysql%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93.md)
+
+## 网络 :cloud:
+
+> [计算机网络学习总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93.md)
+
+## 数据库 :floppy_disk:
+
+> [Mysql原理与实践总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Mysql%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93.md)
+
+> [Redis原理与实践总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Redis%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93.md)
+
+## Java :couple:
+
+> [Java核心技术总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Java%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%E6%80%BB%E7%BB%93.md)
+
+> [Java集合类总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Java%E9%9B%86%E5%90%88%E7%B1%BB%E6%80%BB%E7%BB%93.md)
+
+> [Java并发技术总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Java%E5%B9%B6%E5%8F%91%E6%80%BB%E7%BB%93.md)
+
+> [JVM原理学习总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/JVM%E6%80%BB%E7%BB%93.md)
+
+> [Java网络与NIO总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Java%E7%BD%91%E7%BB%9C%E4%B8%8ENIO%E6%80%BB%E7%BB%93.md)
+
+## JavaWeb :coffee:
+
+> [JavaWeb技术学习总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/JavaWeb%E6%8A%80%E6%9C%AF%E6%80%BB%E7%BB%93.md)
+
+> [Spring与SpringMVC源码解析](https://github.com/h2pl/Java-Tutorial/blob/master/md/Spring%E4%B8%8ESpringMVC%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E6%80%BB%E7%BB%93.md)
+
+## 分布式 :sweat_drops:
+
+> [分布式理论学习总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/%E5%88%86%E5%B8%83%E5%BC%8F%E7%90%86%E8%AE%BA%E6%80%BB%E7%BB%93.md)
+
+> [分布式技术学习总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/%E5%88%86%E5%B8%83%E5%BC%8F%E6%8A%80%E6%9C%AF%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93.md)
+
+## 设计模式 :hammer:
+
+## Hadoop :speak_no_evil:
+
+> [Hadoop生态学习总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Hadoop%E7%94%9F%E6%80%81%E6%80%BB%E7%BB%93.md)
+
+
+
+## 后记
+
+**关于仓库**
+本仓库是笔者在准备 2018 年秋招复习过程中的学习总结,内容以Java后端的知识总结为主,每个部分都会有笔者更加详细的原创文章可供参考,欢迎查看。
+
+**关于转载**
+本仓库内容使用到的资料都会在最后面的参考资料中给出引用链接,希望您在使用本仓库的内容时也能给出相应的引用链接。
+
+
+
From a226ce9a12754fc621dbf0432b5416eb813d110c Mon Sep 17 00:00:00 2001
From: 724888 <362294931@qq.com>
Date: Mon, 9 Jul 2018 23:12:32 +0800
Subject: [PATCH 20/23] update
---
...46344円271円240円346円200円273円347円273円223円.md" | 189 ++++++++++++++++++
1 file changed, 189 insertions(+)
create mode 100644 "md/350円256円276円350円256円241円346円250円241円345円274円217円345円255円246円344円271円240円346円200円273円347円273円223円.md"
diff --git "a/md/350円256円276円350円256円241円346円250円241円345円274円217円345円255円246円344円271円240円346円200円273円347円273円223円.md" "b/md/350円256円276円350円256円241円346円250円241円345円274円217円345円255円246円344円271円240円346円200円273円347円273円223円.md"
new file mode 100644
index 0000000..d1196eb
--- /dev/null
+++ "b/md/350円256円276円350円256円241円346円250円241円345円274円217円345円255円246円344円271円240円346円200円273円347円273円223円.md"
@@ -0,0 +1,189 @@
+---
+title: 设计模式学习总结
+date: 2018年07月09日 23:05:07
+tags:
+ - 设计模式
+categories:
+ - 后端
+ - 技术总结
+---
+设计模式基础学习总结
+这篇总结主要是基于我之前设计模式基础系列文章而形成的的。主要是把重要的知识点用自己的话说了一遍,可能会有一些错误,还望见谅和指点。谢谢
+
+更多详细内容可以查看我的专栏文章:设计模式学习
+https://blog.csdn.net/a724888/article/category/6780980
+
+
+# 设计模式
+
+# 创建型模式
+
+创建型模式
+创建型模式的作用就是创建对象,说到创建一个对象,最熟悉的就是 new 一个对象,然后 set 相关属性。但是,在很多场景下,我们需要给客户端提供更加友好的创建对象的方式,尤其是那种我们定义了类,但是需要提供给其他开发者用的时候。
+
+## 单例
+
+ 单例模式保证全局的单例类只有一个实例,这样的话使用的时候直接获取即可,比如数据库的一个连接,Spring里的bean,都可以是单例的。
+
+ 单例模式一般有5种写法。
+
+ 第一种是饿汉模式,先把单例进行实例化,获取的时候通过静态方法直接获取即可。缺点是类加载后就完成了类的实例化,浪费部分空间。
+
+ 第二种是饱汉模式,先把单例置为null,然后通过静态方法获取单例时再进行实例化,但是可能有多线程同时进行实例化,会出现并发问题。
+
+ 第三种是逐步改进的方法,一开始可以用synchronized关键字进行同步,但是开销太大,而后改成使用volatile修饰单例,然后通过一次检查判断单例是否已初始化,如果未初始化就使用synchronized代码块,再次检查单例防止在这期间被初始化,而后才真正进行初始化。
+
+ 第四种是使用静态内部类来实现,静态内部类只在被使用的时候才进行初始化,所以在内部类中进行单例的实例化,只有用到的时候才会运行实例化代码。然后外部类再通过静态方法返回静态内部类的单例即可。
+
+ 第五种是枚举类,枚举类的底层实现其实也是内部类。枚举类确保每个类对象在全局是唯一的。所以保证它是单例,这个方法是最简单的。
+
+## 工厂模式
+
+ 简单工厂一般是用一个工厂创建多个类的实例。
+
+ 工厂模式一般是指一个工厂服务一个接口,为这个接口的实现类进行实例化
+
+ 抽象工厂模式是指一个工厂服务于一个产品族,一个产品族可能包含多个接口,接口又会包含多个实现类,通过一个工厂就可以把这些绑定在一起,非常方便。
+
+## 原型模式
+
+ 一般通过一个实例进行克隆从而获得更多同一原型的实例。使用实例的clone方法即可完成。
+
+## 建造者模式
+
+ 建造者模式中有一个概念叫做链式调用,链式调用为一个类的实例化提供便利,一般提供系列的方法进行实例化,实际上就是将set方法改造一下,将原本返回为空的set方法改为返回this实例,从而实现链式调用。
+
+ 建造者模式在此基础上加入了builder方法,提供给外部进行调用,同样使用链式调用来完成参数注入。
+
+# 结构型模式
+
+
+结构型模式
+前面创建型模式介绍了创建对象的一些设计模式,这节介绍的结构型模式旨在通过改变代码结构来达到解耦的目的,使得我们的代码容易维护和扩展。
+
+## 桥接模式
+
+有点复杂。建议参考原文
+
+## 适配器模式
+
+适配器模式用于将两个不同的类进行适配。
+
+适配器模式和代理模式的异同
+
+ 比较这两种模式,其实是比较对象适配器模式和代理模式,在代码结构上,
+ 它们很相似,都需要一个具体的实现类的实例。
+ 但是它们的目的不一样,代理模式做的是增强原方法的活;
+ 适配器做的是适配的活,为的是提供"把鸡包装成鸭,然后当做鸭来使用",
+ 而鸡和鸭它们之间原本没有继承关系。
+
+ 适配器模式可以分为类适配器,对象适配器等。
+
+ 类适配器通过继承父类就可以把自己适配成父类了。
+ 而对象适配器则需要把对象传入另一个对象的构造方法中,以便进行包装。
+
+## 享元模式
+
+/ 享元模式的核心在于享元工厂类,
+// 享元工厂类的作用在于提供一个用于存储享元对象的享元池,
+// 用户需要对象时,首先从享元池中获取,
+// 如果享元池中不存在,则创建一个新的享元对象返回给用户,
+// 在享元池中保存该新增对象。
+
+//享元模式
+// 英文是 Flyweight Pattern,不知道是谁最先翻译的这个词,感觉这翻译真的不好理解,我们试着强行关联起来吧。Flyweight 是轻量级的意思,享元分开来说就是 共享 元器件,也就是复用已经生成的对象,这种做法当然也就是轻量级的了。
+//
+// 复用对象最简单的方式是,用一个 HashMap 来存放每次新生成的对象。每次需要一个对象的时候,先到 HashMap 中看看有没有,如果没有,再生成新的对象,然后将这个对象放入 HashMap 中。
+//
+// 这种简单的代码我就不演示了。
+
+## 代理模式
+
+// 我们发现没有,代理模式说白了就是做 "方法包装" 或做 "方法增强"。
+// 在面向切面编程中,算了还是不要吹捧这个名词了,在 AOP 中,
+// 其实就是动态代理的过程。比如 Spring 中,
+// 我们自己不定义代理类,但是 Spring 会帮我们动态来定义代理,
+// 然后把我们定义在 @Before、@After、@Around 中的代码逻辑动态添加到代理中。
+
+## 外观模式
+
+外观模式一般封装具体的实现细节,为用户提供一个更加简单的接口。
+
+通过一个方法调用就可以获取需要的内容。
+
+## 组合模式
+
+//组合模式用于表示具有层次结构的数据,使得我们对单个对象和组合对象的访问具有一致性。
+
+//直接看一个例子吧,每个员工都有姓名、部门、薪水这些属性,
+// 同时还有下属员工集合(虽然可能集合为空),
+// 而下属员工和自己的结构是一样的,
+// 也有姓名、部门这些属性,
+// 同时也有他们的下属员工集合。
+
+ class Employee {
+ private String name;
+ private String dept;
+ private int salary;
+ private List subordinates; // 下属
+ }
+
+## 装饰者模式
+
+## 装饰者
+装饰者模式把每个增强类都继承最高级父类。然后需要功能增强时把类实例传入增强类即可,然后增强类在使用时就可以增强原有类的功能了。
+
+和代理模式不同的是,装饰者模式每个装饰类都继承父类,并且可以进行多级封装。
+
+
+# 行为型模式
+
+行为型模式
+行为型模式关注的是各个类之间的相互作用,将职责划分清楚,使得我们的代码更加地清晰。
+
+## 策略模式
+
+策略模式一般把一个策略作为一个类,并且在需要指定策略的时候传入实例,于是我们可以在需要使用算法的地方传入指定算法。
+
+## 命令模式
+
+命令模式一般分为命令发起者,命令以及命令接受者三个角色。
+
+命令发起者在使用时需要注入命令实例。然后执行命令调用。
+
+命令调用实际上会调用命令接收者的方法进行实际调用。
+
+比如遥控器按钮相当于一条命令,点击按钮时命令运行,自动调用电视机提供的方法即可。
+
+## 模板方法模式
+
+模板方法一般指提供了一个方法模板,并且其中有部分实现类和部分抽象类,并且规定了执行顺序。
+
+实现类是模板提供好的方法。而抽象类则需要用户自行实现。
+
+模板方法规定了一个模板中方法的执行顺序,非常适合一些开发框架,于是模板方法也广泛运用在开源框架中。
+
+## 状态模式
+
+少见。
+
+## 观察者模式和事件监听机制
+
+观察者模式一般用于订阅者和消息发布者之间的数据订阅。
+
+一般分为观察者和主题,观察者订阅主题,把实例注册到主题维护的观察者列表上。
+
+而主题更新数据时自动把数据推给观察者或者通知观察者数据已经更新。
+
+但是由于这样的方式消息推送耦合关系比较紧。并且很难在不打开数据的情况下知道数据类型是什么。
+
+知道后来为了使数据格式更加灵活,使用了事件和事件监听器的模式,事件包装的事件类型和事件数据,从主题和观察者中解耦。
+
+主题当事件发生时,触发该事件的所有监听器,把该事件通过监听器列表发给每个监听器,监听得到事件以后,首先根据自己支持处理的事件类型中找到对应的事件处理器,再用处理器处理对应事件。
+
+
+
+## 责任链模式
+
+责任链通常需要先建立一个单向链表,然后调用方只需要调用头部节点就可以了,后面会自动流转下去。比如流程审批就是一个很好的例子,只要终端用户提交申请,根据申请的内容信息,自动建立一条责任链,然后就可以开始流转了。
+
From 96cca0ac09726f15a2c19732aa655fda3f738739 Mon Sep 17 00:00:00 2001
From: How_2_Play_Life <362294931@qq.com>
Date: Mon, 9 Jul 2018 23:16:24 +0800
Subject: [PATCH 21/23] Update README.md
---
README.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index f7b1daf..4bb6502 100644
--- a/README.md
+++ b/README.md
@@ -4,13 +4,13 @@
## 算法 :pencil2:
-> [Mysql原理与实践总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Mysql%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93.md)
+> [剑指offer算法总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/%E5%89%91%E6%8C%87offer.md)
## 操作系统 :computer:
> [操作系统学习总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93.md)
-> [Mysql原理与实践总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Mysql%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93.md)
+> [Linux内核与基础命令学习总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/Linux%E5%86%85%E6%A0%B8%E4%B8%8E%E5%9F%BA%E7%A1%80%E5%91%BD%E4%BB%A4%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93.md)
## 网络 :cloud:
@@ -47,6 +47,7 @@
> [分布式技术学习总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/%E5%88%86%E5%B8%83%E5%BC%8F%E6%8A%80%E6%9C%AF%E5%AE%9E%E8%B7%B5%E6%80%BB%E7%BB%93.md)
## 设计模式 :hammer:
+> [设计模式学习总结](https://github.com/h2pl/Java-Tutorial/blob/master/md/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93.md)
## Hadoop :speak_no_evil:
From 13b1e9dbfba8979140d913642de437e40ab108ee Mon Sep 17 00:00:00 2001
From: How_2_Play_Life <362294931@qq.com>
Date: 2018年7月24日 18:14:08 +0800
Subject: [PATCH 22/23] Update README.md
---
README.md | 30 +++++++++++++++++++++++-------
1 file changed, 23 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 4bb6502..ca8c799 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,26 @@
+## 声明
+
+**关于仓库**
+
+本仓库是笔者在准备 2018 年秋招复习过程中的学习总结,内容以Java后端的知识总结为主,其中大部分都是笔者根据自己的理解总结而来的。
+
+其中有少数内容可能会包含瞎XX说,语句不通顺,内容不全面等各方面问题,还请见谅。
+
+每个部分都会有笔者更加详细的原创文章可供参考,这些文章也被我发表在CSDN技术博客上,整理成博客专栏,欢迎查看━(*`∀ ́*)ノ亻!
+
+具体内容请见我的CSDN技术博客:https://blog.csdn.net/a724888
+
+也可以来我个人技术小站逛逛:https://h2pl.github.io/
+
+**关于转载**
+
+转载的时候请注明一下出处就行啦。
+
+另外我这个仓库的格式模仿的是@CyC2018 大佬的仓库
+
+并且其中一篇LeetCode刷题指南也是fork这位大佬而来的。我只是自己刷了一遍然后稍微加了一些解析,站在大佬肩膀上。
+
+
| I | II | III | IV | V | VI | VII | VIII | IX |
| :------: | :---------: | :-------: | :---------: | :---: | :---------:| :---------: | :---------: | :---------:|
| 算法[:pencil2:](#算法-pencil2) | 操作系统[:computer:](#操作系统-computer)|网络[:cloud:](#网络-cloud) | 数据库[:floppy_disk:](#数据库-floppy_disk)| Java[:couple:](#Java-couple) |JavaWeb [:coffee:](#JavaWeb-coffee)| 分布式 [:sweat_drops:](#分布式-sweat_drops)| 设计模式[:hammer:](#设计模式-hammer)| Hadoop[:speak_no_evil:](#Hadoop-speak_no_evil)|
@@ -55,13 +78,6 @@
-## 后记
-
-**关于仓库**
-本仓库是笔者在准备 2018 年秋招复习过程中的学习总结,内容以Java后端的知识总结为主,每个部分都会有笔者更加详细的原创文章可供参考,欢迎查看。
-
-**关于转载**
-本仓库内容使用到的资料都会在最后面的参考资料中给出引用链接,希望您在使用本仓库的内容时也能给出相应的引用链接。
From 4eca6dfa30c4a192a1bc49a3c6910a7d0daceece Mon Sep 17 00:00:00 2001
From: How_2_Play_Life <362294931@qq.com>
Date: 2018年7月24日 18:23:21 +0800
Subject: [PATCH 23/23] Update README.md
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index ca8c799..04868b6 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,11 @@
**关于仓库**
-本仓库是笔者在准备 2018 年秋招复习过程中的学习总结,内容以Java后端的知识总结为主,其中大部分都是笔者根据自己的理解总结而来的。
+本仓库是笔者在准备 2018 年秋招复习过程中的学习总结,内容以Java后端的知识总结为主,其中大部分都是笔者根据自己的理解加上个人博客总结而来的。
其中有少数内容可能会包含瞎XX说,语句不通顺,内容不全面等各方面问题,还请见谅。
-每个部分都会有笔者更加详细的原创文章可供参考,这些文章也被我发表在CSDN技术博客上,整理成博客专栏,欢迎查看━(*`∀ ́*)ノ亻!
+每篇文章都会有笔者更加详细的一系列博客可供参考,这些文章也被我发表在CSDN技术博客上,整理成博客专栏,欢迎查看━(*`∀ ́*)ノ亻!
具体内容请见我的CSDN技术博客:https://blog.csdn.net/a724888