diff --git a/notes/Java 容器.md b/notes/Java 容器.md index 9e6e2ac3..9918979a 100644 --- a/notes/Java 容器.md +++ b/notes/Java 容器.md @@ -8,6 +8,7 @@ * [三、源码分析](#三源码分析) * [ArrayList](#arraylist) * [Vector](#vector) + * [CopyOnWriteArrayList](#copyonwritearraylist) * [LinkedList](#linkedlist) * [HashMap](#hashmap) * [ConcurrentHashMap](#concurrenthashmap) @@ -25,23 +26,23 @@ ### 1. Set -- HashSet:基于哈希实现,支持快速查找,但不支持有序性操作,例如根据一个范围查找元素的操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的; +- HashSet:基于哈希实现,支持快速查找,但不支持有序性操作,例如根据一个范围查找元素的操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。 -- TreeSet:基于红黑树实现,支持有序性操作,但是查找效率不如 HashSet,HashSet 查找时间复杂度为 O(1),TreeSet 则为 O(logN); +- TreeSet:基于红黑树实现,支持有序性操作,但是查找效率不如 HashSet,HashSet 查找时间复杂度为 O(1),TreeSet 则为 O(logN)。 - LinkedHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序。 ### 2. List -- ArrayList:基于动态数组实现,支持随机访问; +- ArrayList:基于动态数组实现,支持随机访问。 -- Vector:和 ArrayList 类似,但它是线程安全的; +- Vector:和 ArrayList 类似,但它是线程安全的。 - LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。 ### 3. Queue -- LinkedList:可以用它来支持双向队列; +- LinkedList:可以用它来实现双向队列。 - PriorityQueue:基于堆结构实现,可以用它来实现优先队列。 @@ -85,14 +86,14 @@ java.util.Arrays#asList() 可以把数组类型转换为 List 类型。 public static List asList(T... a) ``` -如果要将数组类型转换为 List 类型,应该注意的是 asList() 的参数为泛型的变长参数,因此不能使用基本类型数组作为参数,只能使用相应的包装类型数组。 +应该注意的是 asList() 的参数为泛型的变长参数,不能使用基本类型数组作为参数,只能使用相应的包装类型数组。 ```java Integer[] arr = {1, 2, 3}; List list = Arrays.asList(arr); ``` -也可以使用以下方式生成 List。 +也可以使用以下方式使用 asList(): ```java List list = Arrays.asList(1,2,3); @@ -235,12 +236,12 @@ public synchronized E get(int index) { } ``` -### 2. ArrayList 与 Vector +### 2. 与 ArrayList 的区别 - Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制; - Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。 -### 3. Vector 替代方案 +### 3. 替代方案 为了获得线程安全的 ArrayList,可以使用 `Collections.synchronizedList();` 得到一个线程安全的 ArrayList。 @@ -255,7 +256,15 @@ List synList = Collections.synchronizedList(list); List list = new CopyOnWriteArrayList<>(); ``` -CopyOnWriteArrayList 是一种 CopyOnWrite 容器,从以下源码看出:读取元素是从原数组读取;添加元素是在复制的新数组上。读写分离,因而可以在并发条件下进行不加锁的读取,读取效率高,适用于读操作远大于写操作的场景。 +## CopyOnWriteArrayList + +### 读写分离 + +写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。 + +写操作需要加锁,防止同时并发写入时导致的写入数据丢失。 + +写操作结束之后需要把原始数组指向新的复制数组。 ```java public boolean add(E e) { @@ -264,7 +273,7 @@ public boolean add(E e) { try { Object[] elements = getArray(); int len = elements.length; - Object[] newElements = Arrays.copyOf(elements, len + 1); + Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; @@ -276,13 +285,26 @@ public boolean add(E e) { final void setArray(Object[] a) { array = a; } +``` +```java @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; } ``` +### 适用场景 + +CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读操作的性能,因此很适合读多写少的应用场景。 + +但是 CopyOnWriteArrayList 有其缺陷: + +- 内存占用:在写操作时需要复制一个新的数组,使得内存占用为原来的两倍左右; +- 数据不一致:读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。 + +所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景。 + ## LinkedList ### 1. 概览 diff --git a/notes/Linux.md b/notes/Linux.md index 27094e5c..705b9b0d 100644 --- a/notes/Linux.md +++ b/notes/Linux.md @@ -871,7 +871,9 @@ $ ls -al /etc | less ## 提取指令 -cut 对数据进行切分,取出想要的部分。切分过程一行一行地进行。 +cut 对数据进行切分,取出想要的部分。 + +切分过程一行一行地进行。 ```html $ cut @@ -891,7 +893,7 @@ root pts/1 192.168.201.254 Thu Feb 5 22:37 - 23:53 (01:16) $ last | cut -d ' ' -f 1 ``` -示例 2:将 export 输出的讯息,取出第 12 字符以后的所有字符串。 +示例 2:将 export 输出的信息,取出第 12 字符以后的所有字符串。 ```html $ export @@ -906,7 +908,7 @@ $ export | cut -c 12 ## 排序指令 -**sort** 进行排序。 +**sort** 用于排序。 ```html $ sort [-fbMnrtuk] [file or stdin] @@ -1204,7 +1206,7 @@ pid_t wait(int *status) 如果成功,返回被收集的子进程的进程 ID;如果调用进程没有子进程,调用就会失败,此时返回 -1,同时 errno 被置为 ECHILD。 -参数 status 用来保存被收集的子进程退出时的一些状态,如果我们对这个子进程是如何死掉的毫不在意,只想把这个子进程消灭掉,可以设置这个参数为 NULL: +参数 status 用来保存被收集的子进程退出时的一些状态,如果对这个子进程是如何死掉的毫不在意,只想把这个子进程消灭掉,可以设置这个参数为 NULL: ```c pid = wait(NULL); @@ -1218,7 +1220,7 @@ pid_t waitpid(pid_t pid, int *status, int options) 作用和 wait() 完全相同,但是多了两个可由用户控制的参数 pid 和 options。 -pid 参数指示一个子进程的 ID,表示只关心这个子进程的退出 SIGCHLD 信号。如果 pid=-1 时,那么和 wait() 作用相同,都是关心所有子进程退出的 SIGCHLD 信号。 +pid 参数指示一个子进程的 ID,表示只关心这个子进程退出的 SIGCHLD 信号。如果 pid=-1 时,那么和 wait() 作用相同,都是关心所有子进程退出的 SIGCHLD 信号。 options 参数主要有 WNOHANG 和 WUNTRACED 两个选项,WNOHANG 可以使 waitpid() 调用变成非阻塞的,也就是说它会立即返回,父进程可以继续执行其它任务。 @@ -1238,7 +1240,7 @@ options 参数主要有 WNOHANG 和 WUNTRACED 两个选项,WNOHANG 可以使 w 系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。 -要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时所有的僵尸进程就会变成孤儿进程,从而被 init 所收养,这样 init 就会释放所有的僵死进程所占有的资源,从而结束僵尸进程。 +要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 所收养,这样 init 就会释放所有的僵死进程所占有的资源,从而结束僵尸进程。 # 参考资料 diff --git a/notes/一致性.md b/notes/一致性.md index 7209dccf..b00de8ff 100644 --- a/notes/一致性.md +++ b/notes/一致性.md @@ -91,7 +91,7 @@ ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE 主要用于实现分布式事务,分布式事务指的是事务操作跨越多个节点,并且要求满足事务的 ACID 特性。 -通过引入协调者(Coordinator)来调度参与者的行为,,并最终决定这些参与者是否要真正执行事务。 +通过引入协调者(Coordinator)来调度参与者的行为,并最终决定这些参与者是否要真正执行事务。 ## 运行过程 diff --git a/notes/分布式问题分析.md b/notes/分布式问题分析.md index 4f019525..dfeb7691 100644 --- a/notes/分布式问题分析.md +++ b/notes/分布式问题分析.md @@ -216,7 +216,9 @@ HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的 ### 2. DNS 重定向 -使用 DNS 作为负载均衡器,根据负载情况返回不同服务器的 IP 地址。大型网站基本使用了这种方式做为第一级负载均衡手段,然后在内部使用其它方式做第二级负载均衡。 +使用 DNS 作为负载均衡器,根据负载情况返回不同服务器的 IP 地址。 + +大型网站基本使用了这种方式做为第一级负载均衡手段,然后在内部使用其它方式做第二级负载均衡。 缺点: diff --git a/notes/剑指 offer 题解.md b/notes/剑指 offer 题解.md index 6f786cbe..e1655922 100644 --- a/notes/剑指 offer 题解.md +++ b/notes/剑指 offer 题解.md @@ -90,12 +90,20 @@ ## 题目描述 -在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为 7 的数组 {2, 3, 1, 0, 2, 5},那么对应的输出是第一个重复的数字 2。 +在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 -要求复杂度为 O(N) + O(1),也就是时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。牛客网讨论区这一题的首票答案使用 nums[i] + length 来将元素标记,这么做会有加法溢出问题。 +```html +Input: +{2, 3, 1, 0, 2, 5} + +Output: +2 +``` ## 解题思路 +要求复杂度为 O(N) + O(1),也就是时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。牛客网讨论区这一题的首票答案使用 nums[i] + length 来将元素标记,这么做会有加法溢出问题。 + 这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素放到第 i 个位置上。 以 (2, 3, 1, 0, 2, 5) 为例: @@ -158,7 +166,11 @@ Given target = 20, return false. ## 解题思路 -从右上角开始查找。因为矩阵中的一个数,它左边的数都比它小,下边的数都比它大。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间。 +从右上角开始查找。矩阵中的一个数,它左边的数都比它小,下边的数都比它大。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间。 + +当前元素的查找区间为左下角的所有元素,例如元素 12 的查找区间如下: + +

复杂度:O(M + N) + O(1) @@ -261,25 +273,14 @@ public ArrayList printListFromTailToHead(ListNode listNode) { } ``` -### 使用 Collections.reverse() - -```java -public ArrayList printListFromTailToHead(ListNode listNode) { - ArrayList ret = new ArrayList<>(); - while (listNode != null) { - ret.add(listNode.val); - listNode = listNode.next; - } - Collections.reverse(ret); - return ret; -} -``` - ### 使用头插法 利用链表头插法为逆序的特点。 -头结点和第一个节点的区别:头结点是在头插法中使用的一个额外节点,这个节点不存储值;第一个节点就是链表的第一个真正存储值的节点。 +头结点和第一个节点的区别: + +- 头结点是在头插法中使用的一个额外节点,这个节点不存储值; +- 第一个节点就是链表的第一个真正存储值的节点。 ```java public ArrayList printListFromTailToHead(ListNode listNode) { @@ -302,6 +303,20 @@ public ArrayList printListFromTailToHead(ListNode listNode) { } ``` +### 使用 Collections.reverse() + +```java +public ArrayList printListFromTailToHead(ListNode listNode) { + ArrayList ret = new ArrayList<>(); + while (listNode != null) { + ret.add(listNode.val); + listNode = listNode.next; + } + Collections.reverse(ret); + return ret; +} +``` + # 7. 重建二叉树 [NowCoder](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) @@ -322,22 +337,23 @@ inorder = [9,3,15,20,7] 前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为树的左子树中序遍历结果,右部分为树的右子树中序遍历的结果。 ```java -private Map inOrderNumsIndexs = new HashMap<>(); // 缓存中序遍历数组的每个值对应的索引 +// 缓存中序遍历数组的每个值对应的索引 +private Map inOrderNumsIndexs = new HashMap<>(); public TreeNode reConstructBinaryTree(int[] pre, int[] in) { for (int i = 0; i < in.length; i++) inOrderNumsIndexs.put(in[i], i); - return reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1); + return reConstructBinaryTree(pre, 0, pre.length - 1, 0, in.length - 1); } -private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int[] in, int inL, int inR) { +private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL, int inR) { if (preL > preR) return null; TreeNode root = new TreeNode(pre[preL]); int inIndex = inOrderNumsIndexs.get(root.val); int leftTreeSize = inIndex - inL; - root.left = reConstructBinaryTree(pre, preL + 1, preL + leftTreeSize, in, inL, inL + leftTreeSize - 1); - root.right = reConstructBinaryTree(pre, preL + leftTreeSize + 1, preR, in, inL + leftTreeSize + 1, inR); + root.left = reConstructBinaryTree(pre, preL + 1, preL + leftTreeSize, inL, inL + leftTreeSize - 1); + root.right = reConstructBinaryTree(pre, preL + leftTreeSize + 1, preR, inL + leftTreeSize + 1, inR); return root; } ``` @@ -350,16 +366,6 @@ private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int[] in, 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 -## 解题思路 - -① 如果一个节点的右子树不为空,那么该节点的下一个节点是右子树的最左节点; - -

- -② 否则,向上找第一个左链接指向的树包含该节点的祖先节点。 - -

- ```java public class TreeLinkNode { int val; @@ -373,6 +379,16 @@ public class TreeLinkNode { } ``` +## 解题思路 + +① 如果一个节点的右子树不为空,那么该节点的下一个节点是右子树的最左节点; + +

+ +② 否则,向上找第一个左链接指向的树包含该节点的祖先节点。 + +

+ ```java public TreeLinkNode GetNext(TreeLinkNode pNode) { if (pNode.right != null) { @@ -398,7 +414,7 @@ public TreeLinkNode GetNext(TreeLinkNode pNode) { ## 题目描述 -用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。队列中的元素为 int 类型。 +用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。 ## 解题思路 @@ -442,7 +458,7 @@ public int pop() throws Exception {

-递归方法是将一个问题划分成多个子问题求解,动态规划也是如此,但是动态规划会把子问题的解缓存起来,避免重复求解子问题。 +递归是将一个问题划分成多个子问题求解,动态规划也是如此,但是动态规划会把子问题的解缓存起来,从而避免重复求解子问题。 ```java public int Fibonacci(int n) { @@ -521,11 +537,11 @@ public int JumpFloor(int n) { ```java public int JumpFloor(int n) { - if (n <= 1) + if (n <= 2) return n; - int pre2 = 0, pre1 = 1; - int result = 0; - for (int i = 1; i <= n; i++) { + int pre2 = 1, pre1 = 2; + int result = 1; + for (int i = 2; i < n; i++) { result = pre2 + pre1; pre2 = pre1; pre1 = result; diff --git a/notes/数据库系统原理.md b/notes/数据库系统原理.md index 87d210a9..cabc2f97 100644 --- a/notes/数据库系统原理.md +++ b/notes/数据库系统原理.md @@ -70,7 +70,7 @@ 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。 -可以通过数据库备份和恢复来实现,在系统发生奔溃时,使用备份的数据库进行数据恢复。 +可以通过数据库备份和恢复来实现,在系统发生崩溃时,使用备份的数据库进行数据恢复。 ---- @@ -79,9 +79,9 @@ - 只有满足一致性,事务的执行结果才是正确的。 - 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时要只要能满足原子性,就一定能满足一致性。 - 在并发的情况下,多个事务并发执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 -- 事务满足持久化是为了能应对数据库奔溃的情况。 +- 事务满足持久化是为了能应对数据库崩溃的情况。 -

+

## AUTOCOMMIT diff --git a/pics/a58e294a-615d-4ea0-9fbf-064a6daec4b2.png b/pics/a58e294a-615d-4ea0-9fbf-064a6daec4b2.png new file mode 100644 index 00000000..fdefb823 Binary files /dev/null and b/pics/a58e294a-615d-4ea0-9fbf-064a6daec4b2.png differ