diff --git "a/Algorithm/KMP\347\256\227\346\263\225.py" "b/Algorithm/KMP\347\256\227\346\263\225.py" new file mode 100644 index 00000000..b5410f46 --- /dev/null +++ "b/Algorithm/KMP\347\256\227\346\263\225.py" @@ -0,0 +1,27 @@ + +# KMP算法的核心,是一个被称为部分匹配表PMT(Partial Match Table)的数组 + +# 关于前缀和后缀(字符串本身并不是自己的前/后缀) + Harry Potter + Harry 前缀集合 {"H", "Ha", "Har", "Harr"} + Potter 后缀集合 {"otter", "tter", "ter", "er", "r"} + +# PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度 + "aba" + 前缀 {"a", "ab"} 前缀集合 + 后缀 {"ba" ,"a"} 后缀集合 + PMT = {"a"} = 1 前后缀集合的交集中,最长的元素长度是1 + + + "ababa" + 前缀 {"a", "ab", "aba", "abab"} + 后缀 {"baba", "aba" ,"ba" ,"a"} + PMT = {"a", "aba"} = 3 + +# ababababca 的PMT + char 'a' 'b' 'a' 'b' 'a' 'b' 'c' 'a' + index 0 1 2 3 4 5 6 7 + value 0 0 1 2 3 4 0 1 + + +# 这TM啥啊这? \ No newline at end of file diff --git "a/Algorithm/Raft\347\256\227\346\263\225.java" "b/Algorithm/Raft\347\256\227\346\263\225.java" new file mode 100644 index 00000000..1c19c857 --- /dev/null +++ "b/Algorithm/Raft\347\256\227\346\263\225.java" @@ -0,0 +1,25 @@ +------------------------ +raft | +------------------------ + + # 分布式系统的一致性算法 + * 业界最著名的一致性算法就是大名鼎鼎的Paxos(Chubby的作者曾说过: 世上只有一种一致性算法, 就是Paxos + * Paxos 是出了名的难懂, 而Raft正是为了探索一种更易于理解的一致性算法而产生的 + + # https://raft.github.io/ + + # 总结 + —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + 特性 解释 + —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + 选举安全特性 对于一个给定的任期号,最多只会有一个领导人被选举出来 + —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + 领导人只附加原则 领导人绝对不会删除或者覆盖自己的日志,只会增加 + —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + 日志匹配原则 如果两个日志在相同的索引位置的日志条目的任期号相同,那么我们就认为这个日志从头到这个索引位置之间全部完全相同 + —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + 领导人完全特性 如果某个日志条目在某个任期号中已经被提交,那么这个条目必然出现在更大任期号的所有领导人中 + —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + 状态机安全特性 如果一个领导人已经在给定的索引值位置的日志条目应用到状态机中,那么其他任何的服务器在这个索引位置不会提交一个不同的日志 + —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + diff --git "a/Algorithm/lru\347\256\227\346\263\225.java" "b/Algorithm/lru\347\256\227\346\263\225.java" new file mode 100644 index 00000000..9d68d210 --- /dev/null +++ "b/Algorithm/lru\347\256\227\346\263\225.java" @@ -0,0 +1,74 @@ + + +# 最近最少使用算法(Least Recently Used 最近最少使用) + +# 一般用于缓存场景 + 1. 新数据插入到链表头部; + 2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部 + 3. 当链表满的时候,将链表尾部的数据丢弃 + + +# Java 实现 + * LinkedHashMap 本身就提供了数据访问排序以及移除最少访问key的功能 + + class LRULinkedHashMap extends LinkedHashMap { + + /** + * + */ + private static final long serialVersionUID = 4318100161701136149L; + + public static final float DEFAULT_LOAD_FACTOR = 0.75f; + + // 最大容量,超过该容量后会移除最近最少使用的元素 + private int maxCapacity; + + public LRULinkedHashMap(int maxCapacity) { + // 根据maxCapacity和加载因子计算hashmap的capactiy + // +1确保当达到cacheSize上限时不会触发hashmap的扩容 + super((int) Math.ceil(maxCapacity / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_LOAD_FACTOR, true); + this.maxCapacity = maxCapacity; + } + + // 超过最大容量后,尝试移除最少使用的key的策略 + + // 该方法会在每次执行添加操作后回调,如果返回true,则删除链表最后的一个元素 + @Override + protected boolean removeEldestEntry(java.util.Map.Entry eldest) { + return super.size() > this.maxCapacity; + } + } + +# python 实现 + +import collections +class LRUDict (object): + def __init__(self,size): + self.size = size + self.dict = collections.OrderedDict() + + def set(self,key,value): + if key in self.dict: + # key存在,直接替换 + self.dict[key] = value + else: + if len(self.dict) > self.size: + # 移除最后一个元素,也就是不常用的 + self.dict.popitem() + # key 不存在,执行新增 + self.dict[key] = value + # 新增/修改的元素都移动到顶部 + self.dict.move_to_end(key,False) + + def get(self,key): + if key in self.dict: + # 命中缓存,移到顶部 + self.dict.move_to_end(key,False) + return self.dict.get(key) + else: + return None + + def __str__(self, *args, **kwargs): + return self.dict.__str__() + + diff --git "a/Algorithm/\344\272\214\345\210\206\346\237\245\346\211\276.py" "b/Algorithm/\344\272\214\345\210\206\346\237\245\346\211\276.py" new file mode 100644 index 00000000..fc7b3305 --- /dev/null +++ "b/Algorithm/\344\272\214\345\210\206\346\237\245\346\211\276.py" @@ -0,0 +1,23 @@ +# collection 必须为升序排序的有序数组,不存在返回负数 +def indexedBinarySearch(collection, value): + low = 0 + high = len(collection) - 1 + while(low <= high): + mid = (low + high) // 2 + item = collection[mid] + if item > value: + high = mid - 1 + elif item < value: + low = mid + 1 + else: + return mid + return -(low + 1) + + + +# 获取中位数的下标,如果 start 和 end 过大的话, 在某些语言中可能会导致溢出, 需要换一种计算方式 + mid = low + (high - low) // 2 + + +# 如果支持无符号位移运算的话, 更简单 + mid = (low + high) >>> 1; \ No newline at end of file diff --git "a/Algorithm/\345\205\245\351\227\250.java" "b/Algorithm/\345\205\245\351\227\250.java" new file mode 100644 index 00000000..b108ce8b --- /dev/null +++ "b/Algorithm/\345\205\245\351\227\250.java" @@ -0,0 +1,52 @@ + +# 线性结构 + * 数组 + * 栈 + * 链表 + * 队列 + * 哈希表 + +# 树结构 + * 二叉树 + * 二分搜索树 + * AVL + * 红黑树 + * Treap + * Splay + * 堆 + * Trie(前缀树) + * 线段树 + * K-D树 + * 并查集 + * 哈夫曼树 + +# 图结构 + * 邻接矩阵 + * 邻接表 + +# 时间复杂度 + O(1) + * 运行一次就找到了(最优的算法) + + O(n) + * 数据有多长就需要运行多少次 + * 数据量增大几倍,耗时也增大几倍,比如常见的遍历算法 + + O(n^2) + * 就代表数据量增大n倍时,耗时增大n的平方倍,这是比线性更高的时间复杂度 + * 比如冒泡排序,就是典型的O(n^2)的算法,对n个数排序,需要扫描n × n次 + + O(logn) + * 数据增大n倍时,耗时增大logn倍(这里的log是以2为底的) + * 比如,当数据增大256倍时,耗时只增大8倍,是比线性还要低的时间复杂度 + + * 二分查找就是O(logn)的算法,每找一次排除一半的可能,256个数据中查找只要找8次就可以找到目标 + + O(nlogn) + * 就是n 乘以logn + * 当数据增大256倍时,耗时增大 256*8 =2048 倍 + * 这个复杂度高于线性低于平方,归并排序就是O(nlogn)的时间复杂度 + + + + \ No newline at end of file diff --git "a/Algorithm/\345\223\210\345\270\214\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.java" "b/Algorithm/\345\223\210\345\270\214\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.java" new file mode 100644 index 00000000..e8f20a1c --- /dev/null +++ "b/Algorithm/\345\223\210\345\270\214\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.java" @@ -0,0 +1,111 @@ + +# Hash一致性算法 + * 算法目的:将数据均匀的分散到各个节点中,并且尽量的在加减节点时能使受影响的数据最少 + + * 客户端在N多服务端节点中选择一个进行连接 + * 普通的 hash(key) % nodes.size 会带来一些问题 + * 当节点新增,删除后,会有大量的客户端会被重新分配 + + * 一致 Hash 算法是将所有的哈希值构成了一个环,其范围在 0 ~ 2^32-1(顺时针方向) + * 0 - 4294967295 + * 可以用节点的 IP,hostname 这样的唯一性字段作为 Key 进行 hash(key) + * 把节点散步到哈希环上 + + * 之后需要将数据定位到对应的节点上,使用同样的 hash 函数 将 Key 也映射到这个环上 + * 遍历这个数组直到找到一个数据大于等于当前客户端的 hash 值,就将当前节点作为该客户端所路由的节点 + * 如果没有发现比客户端大的数据就返回第一个节点(满足环的特性) + * 顺时针方向 + + * 使用虚拟节点,使用数据分布更加的均匀 + * 将每一个节点都进行多次 hash,生成多个节点放置在环上称为虚拟节点 + * 可以在 IP 后加上编号来生成哈希值 + +# Java Demo + +import java.util.SortedMap; +import java.util.TreeMap; + + +public class HashDemo { + public static void main(String[] args) { + System.out.println(consistentHashAlgorithmVirtual()); + } + + // 虚拟节点,解决环行圈儿中分布不均的问题 + public static String consistentHashAlgorithmVirtual() { + // 每个节点的虚拟节点数量 + int virtualCount = 5; + // 节点容器 + TreeMap treeMap = new TreeMap<>(); + // 节点 + String[] servers = new String[] { + "192.168.0.1:1024", + "192.168.0.2:1024", + "192.168.0.3:1024", + "192.168.0.4:1024", + "192.168.0.5:1024" + }; + for(String server : servers) { + // 为每个节点生成虚拟节点 + for(int x = 0 ;x < virtualCount ; x ++) { + String finalName = server + "&&" + x; + // 计算出每个节点的hash + int hashCode = Math.abs(finalName.hashCode()); + treeMap.put(hashCode, finalName); + System.out.println("hashCode=" + hashCode + " server=" + finalName); + } + } + + // 客户端表标识以及hashCode + String key = "client"; + int hashCode = Math.abs(key.hashCode()); + + System.out.println("hashCode=" + hashCode); + + // 大于该hash的map + SortedMap tailMap = treeMap.tailMap(hashCode); + if(!tailMap.isEmpty()) { + // 有,则使用第一个(大于该hash的第一个) + return tailMap.get(tailMap.firstKey()); + }else { + //没则使用所有节点的第一个 + return treeMap.get(treeMap.firstKey()); + } + } + + public static String consistentHashAlgorithm() { + // 节点容器 + TreeMap treeMap = new TreeMap<>(); + // 节点 + String[] servers = new String[] { + "192.168.0.1:1024", + "192.168.0.2:1024", + "192.168.0.3:1024", + "192.168.0.4:1024", + "192.168.0.5:1024" + }; + for(String server : servers) { + int hashCode = Math.abs(server.hashCode()); + treeMap.put(hashCode, server); + // 计算出每个节点的hash + System.out.println("hashCode=" + hashCode + " server=" + server); + } + + // 客户端表标识以及hashCode + String key = "client"; + int hashCode = Math.abs(key.hashCode()); + + System.out.println("hashCode=" + hashCode); + + + // 大于该hash的map + SortedMap tailMap = treeMap.tailMap(hashCode); + if(!tailMap.isEmpty()) { + // 有,则使用第一个(大于该hash的第一个) + return tailMap.get(tailMap.firstKey()); + }else { + //没则使用所有节点的第一个 + return treeMap.get(treeMap.firstKey()); + } + } +} diff --git "a/Algorithm/\345\270\203\351\232\206\350\277\207\346\273\244\347\256\227\346\263\225.py" "b/Algorithm/\345\270\203\351\232\206\350\277\207\346\273\244\347\256\227\346\263\225.py" new file mode 100644 index 00000000..849d3ff1 --- /dev/null +++ "b/Algorithm/\345\270\203\351\232\206\350\277\207\346\273\244\347\256\227\346\263\225.py" @@ -0,0 +1,30 @@ +---------------------------- +布隆过滤算法 | +---------------------------- + # 在N多数据中,判断数据是否存在 + + # 添加逻辑 + 1,存入数据,使用多个hash函数对数据进行运算 + v1 = hash3(key) + v2 = hash3(key) + v3 = hash3(key) + + 2,多个hash值取模数组长度,把得到的结果角标设置为1 + arr[v1 % arr.length] = 1; + arr[v2 % arr.length] = 1; + arr[v3 % arr.length] = 1; + + + # 判断逻辑 + 1,使用多个hash函数对数据进行运算 + v1 = hash3(key) + v2 = hash3(key) + v3 = hash3(key) + + 2,多个hash值取模数组长度,判断结果角标是否都为1,如果是则包含,任何非1则不包含 + arr[v1 % arr.length] == 1 && + arr[v2 % arr.length] == 1 && + arr[v3 % arr.length] == 1 + + # 注意 + * hash运算次数越多,数组越长,误报的几率越小 diff --git "a/Algorithm/\346\211\276\345\207\272\347\254\254\344\272\214\345\244\247\347\232\204\345\205\203\347\264\240.py" "b/Algorithm/\346\211\276\345\207\272\347\254\254\344\272\214\345\244\247\347\232\204\345\205\203\347\264\240.py" new file mode 100644 index 00000000..85628bbb --- /dev/null +++ "b/Algorithm/\346\211\276\345\207\272\347\254\254\344\272\214\345\244\247\347\232\204\345\205\203\347\264\240.py" @@ -0,0 +1,16 @@ +''' + 闈炴帓搴忛泦鍚,鎵惧嚭绗簩澶х殑鍏冪礌 +''' +def second_biggest(arr): + max = 0; + second = 0; + for i in arr: + if i > max: + second = max + max = i + elif i > second: + second = i + return second + +print(second_biggest([1,5,47,5,3,6,96])) + diff --git "a/Algorithm/\346\212\242\347\272\242\345\214\205\347\256\227\346\263\225\347\232\204\345\256\236\347\216\260.java" "b/Algorithm/\346\212\242\347\272\242\345\214\205\347\256\227\346\263\225\347\232\204\345\256\236\347\216\260.java" new file mode 100644 index 00000000..1522f33f --- /dev/null +++ "b/Algorithm/\346\212\242\347\272\242\345\214\205\347\256\227\346\263\225\347\232\204\345\256\236\347\216\260.java" @@ -0,0 +1,94 @@ +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.Random; + +class Packet { + // 总金额 + private int totalAmount; + // 总个数 + private int totalCount; + public Packet(int totalAmount, int totalCount) { + assert totalAmount > 0; + assert totalCount > 0; + this.totalAmount = totalAmount; + this.totalCount = totalCount; + } + public int getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(int totalAmount) { + this.totalAmount = totalAmount; + } + + public int getTotalCount() { + return totalCount; + } + + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } +} + +public class Main { + public static void main(String[] args) { + Random random = new Random(); + for(int x = 0 ;x < 100 ;x ++){ + test(random.nextInt(1000), random.nextInt(20) + 1); + } + } + + public static void test(int totalAmount, int totalCount){ + + BigDecimal bigDecimal = new BigDecimal(0); + + Packet packet = new Packet(totalAmount, totalCount); + + System.out.print("["); + for (int x = 0 ;x < totalCount ;x ++){ + int value = unpacking(packet); + bigDecimal = bigDecimal.add(new BigDecimal(value)); + if (x != 0){ + System.out.print(", "); + } + System.out.print(value); + } + + System.out.println("] totalAmount=" + totalAmount + ",totalCount=" + totalCount+ ", accuracy=" + (bigDecimal.intValue() == totalAmount)); + } + + // 打开一个红包 + public static int unpacking(Packet packet){ + + if (packet.getTotalCount() == 1){ + // 最后一个红包 + int totalAmount = packet.getTotalAmount(); + packet.setTotalAmount(0); + packet.setTotalCount(0); + return totalAmount; + }else if(packet.getTotalCount() == 0){ + // 红包已经领取完 + return 0; + } + + // 最大金额 = (总金额 / 总个数)(四舍五入掉小数) * 2 + BigDecimal totalAmount = new BigDecimal(packet.getTotalAmount()); + BigDecimal maxValue = totalAmount.divide(new BigDecimal(packet.getTotalCount()),0 , RoundingMode.HALF_UP).multiply(new BigDecimal(2)); + + // 0% - 100% 随机概率 + BigDecimal random = new BigDecimal(new Random().nextInt(101)).divide(new BigDecimal(100),2, RoundingMode.UNNECESSARY); + + // 随机获取金额(四舍五入掉小数位) + int randomValue = maxValue.multiply(random, MathContext.UNLIMITED).intValue(); + + // 最小可以获取到1分 + randomValue = randomValue < 1 ? 1 : randomValue; + + // 修改红包数据 + packet.setTotalAmount(packet.getTotalAmount() - randomValue); + packet.setTotalCount(packet.getTotalCount() - 1); + + return randomValue; + } +} diff --git "a/Algorithm/\346\217\222\345\205\245\346\216\222\345\272\217.py" "b/Algorithm/\346\217\222\345\205\245\346\216\222\345\272\217.py" new file mode 100644 index 00000000..667d544b --- /dev/null +++ "b/Algorithm/\346\217\222\345\205\245\346\216\222\345\272\217.py" @@ -0,0 +1,88 @@ + + +# 实现1 +def insertSort1(arr): + # 从第1个元素开始 + i = 1 + while i < len(arr): + # 当前元素 + temp = arr[i] + # 上一个元素大于当前元素 + while i >= 0 and arr[i - 1] > temp: + # 上一个元素大于当前元素 + arr[i] = arr[i - 1] + # 角标后移 + i -= 1 + arr[i] = temp + i += 1 + + +# 实现2 +def inset(arr,n): + value = arr[n] + i = n + while arr[i - 1] > value: + arr[i] = arr[i - 1] + i -= 1 + if i == 0: + break + arr[i] = value + +def insertSort2(arr): + i = 1 + while i < len(arr): + inset(arr,i) + i += 1 + + +arr = [1, 4, 7, 5, 3, 6] +insertSort1(arr) +print(arr) + + + + + +############### C + +#include +#include +void printArr(int *arr, size_t size) { + for(int x = 0 ;x < size ; x++){ + printf("%d\n", arr[x]); + } +} +void insetSort1(int *arr, size_t size) { + for (int i = 1; i < size; i++) { + int temp = arr[i]; + while (i >= 0 && arr[i - 1] > temp) { + arr[i] = arr[i - 1]; + i--; + } + arr[i] = temp; + } +} +void inset(int *arr,int n){ + int value = arr[n]; + int i = n; + while(arr[i - 1] > value){ + arr[i] = arr[i - 1]; + i--; + if (i == 0){ + break; + } + } + arr[i] = value; +} +void insetSort2(int *arr,size_t size){ + for (int i = 1; i < size; i++) { + inset(arr, i); + } +} +int main(int argc, char **argv) { + int arr[] = {1,2,6,4,7,1,5,9}; + size_t size = sizeof(arr) / sizeof(arr[0]); + insetSort2(arr,size); + printArr(arr, size); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git "a/Algorithm/\346\226\220\346\263\242\351\202\243\345\245\221.py" "b/Algorithm/\346\226\220\346\263\242\351\202\243\345\245\221.py" new file mode 100644 index 00000000..0441ba18 --- /dev/null +++ "b/Algorithm/\346\226\220\346\263\242\351\202\243\345\245\221.py" @@ -0,0 +1,16 @@ + +# 斐波那契数列,从第3项开始,每一项都等于前两项之和 +# 1,1,2,3,5,8,13,21,34,55..... +# 可以使用递归 + +def fib(num): + # 如果是前2项 + if num <= 2: + return 1 + else: + return fib(num - 2) + fib(num - 1) + + +for i in range(1,11): + print(fib(i),end=',') + diff --git "a/Algorithm/\346\227\266\351\227\264\350\275\256\347\256\227\346\263\225.java" "b/Algorithm/\346\227\266\351\227\264\350\275\256\347\256\227\346\263\225.java" new file mode 100644 index 00000000..bc609771 --- /dev/null +++ "b/Algorithm/\346\227\266\351\227\264\350\275\256\347\256\227\346\263\225.java" @@ -0,0 +1,305 @@ +# 时间轮算法 + * 定时器大概有两种,一种是开阻塞线程,另一种是开一个任务队列然后定期扫描 + * 显而易见这两种方式的弊端很明显,前者对线程消耗过大,后者对时间消耗过大(很多未到时间的任务会被多次重复扫描消耗性能) + +# 参考 + https://github.com/wolaiye1010/zdc-java-script + +# 时间轮算法是基于循环链表数据结构 + +# 几个关键的概念 + ticksPerWheel + * 一轮的tick数 + + tickDuration + * 一个tick的持续时间 + * timeUnit + + + + +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class TimingWheel { + // 一个tick的持续时间 + private final long tickDuration; + // tick数量 + private final int ticksPerWheel; + // 当前tick的指针 + private volatile int currentTickIndex = 0; + + // 过期的任务 + private final CopyOnWriteArrayList> expirationListeners = new CopyOnWriteArrayList>(); + + // 刻度插槽 + private final ArrayList> wheel; + + // 指示符 + private final Map> indicator = new ConcurrentHashMap>(); + + // 是否shutdown + private final AtomicBoolean shutdown = new AtomicBoolean(false); + + // 锁 + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + // 工作线程 + private Thread workerThread; + + /** + * @param tickDuration 每一刻度毫秒 + * @param ticksPerWheel 刻度多少 + * @param timeUnit 单位 + */ + public TimingWheel(int tickDuration, int ticksPerWheel, TimeUnit timeUnit) { + if (timeUnit == null) { + throw new NullPointerException("unit"); + } + if (tickDuration <= 0) { + throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration); + } + if (ticksPerWheel <= 0) { + throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel); + } + + // 实例化插槽 + this.wheel = new ArrayList>(); + this.tickDuration = TimeUnit.MILLISECONDS.convert(tickDuration, timeUnit); + this.ticksPerWheel = ticksPerWheel + 1; + + for (int i = 0; i < this.ticksPerWheel; i++) { + // 添加插槽里面的对象 + wheel.add(new Slot(i)); + } + + wheel.trimToSize(); + + // 实例化工作线程 + workerThread = new Thread(new TickWorker(), "Timing-Wheel"); + } + + public void start() { + if (shutdown.get()) { + throw new IllegalStateException("Cannot be started once stopped"); + } + + if (!workerThread.isAlive()) { + // 启动工作线程 + workerThread.start(); + } + } + + public boolean stop() { + if (!shutdown.compareAndSet(false, true)) { + return false; + } + + boolean interrupted = false; + while (workerThread.isAlive()) { + workerThread.interrupt(); + try { + workerThread.join(100); + } catch (InterruptedException e) { + interrupted = true; + } + } + if (interrupted) { + Thread.currentThread().interrupt(); + } + + return true; + } + + public void addExpirationListener(ExpirationListener listener) { + expirationListeners.add(listener); + } + + public void removeExpirationListener(ExpirationListener listener) { + expirationListeners.remove(listener); + } + + public long add(E e) { + synchronized (e) { + checkAdd(e); + int previousTickIndex = getPreviousTickIndex(); + Slot slot = wheel.get(previousTickIndex); + slot.add(e); + indicator.put(e, slot); + return (ticksPerWheel - 1) * tickDuration; + } + } + + private void checkAdd(E e) { + Slot slot = indicator.get(e); + if (slot != null) { + slot.remove(e); + } + } + + private int getPreviousTickIndex() { + lock.readLock().lock(); + try { + int cti = currentTickIndex; + if (cti == 0) { + return ticksPerWheel - 1; + } + + return cti - 1; + } finally { + lock.readLock().unlock(); + } + } + + public boolean remove(E e) { + synchronized (e) { + Slot slot = indicator.get(e); + if (slot == null) { + return false; + } + + indicator.remove(e); + return slot.remove(e) != null; + } + } + + private void notifyExpired(int idx) { + + // 获取当前插槽的Slot + Slot slot = wheel.get(idx); + + // 获取当期Slot中的任务Set + Set elements = slot.elements(); + + for (E e : elements) { + // 从Slot中移除任务 + slot.remove(e); + synchronized (e) { + // 从指示符获取到任务对象 + Slot latestSlot = indicator.get(e); + if (latestSlot.equals(slot)) { + indicator.remove(e); + } + } + for (ExpirationListener listener : expirationListeners) { + // 触发过期事件 + listener.expired(e); + } + } + } + + private class TickWorker implements Runnable { + + private long startTime; + + private long tick; + + @Override + public void run() { + // 时间轮开始时间 + startTime = System.currentTimeMillis(); + tick = 1; + for (int i = 0; !shutdown.get(); i++) { + if (i == wheel.size()) { + // 重置指针 + i = 0; + } + lock.writeLock().lock(); + try { + // 同步设置指针位置 + currentTickIndex = i; + } finally { + lock.writeLock().unlock(); + } + + // 唤醒到期任务 + notifyExpired(currentTickIndex); + + waitForNextTick(); + } + } + + // 移动到下一个刻度 + private void waitForNextTick() { + for (;;) { + long currentTime = System.currentTimeMillis(); + // 线程暂停时间 = 每个刻度耗时 * 刻度 - (开始时间 - 现在时间) + long sleepTime = tickDuration * tick - (currentTime - startTime); + + if (sleepTime <= 0) { + break; + } + + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + return; + } + } + + tick++; + } + } + + private static class Slot { + + private int id; + private Map elements = new ConcurrentHashMap(); + + public Slot(int id) { + this.id = id; + } + + public void add(E e) { + elements.put(e, e); + } + + public E remove(E e) { + return elements.remove(e); + } + + public Set elements() { + return elements.keySet(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + @SuppressWarnings("rawtypes") + Slot other = (Slot) obj; + if (id != other.id) + return false; + return true; + } + + @Override + public String toString() { + return "Slot [id=" + id + ", elements=" + elements + "]"; + } + } +} + +public interface ExpirationListener { + + void expired(E expiredObject); +} diff --git "a/Algorithm/\346\260\264\344\273\231\350\212\261\346\225\260.py" "b/Algorithm/\346\260\264\344\273\231\350\212\261\346\225\260.py" new file mode 100644 index 00000000..b22ce685 --- /dev/null +++ "b/Algorithm/\346\260\264\344\273\231\350\212\261\346\225\260.py" @@ -0,0 +1,24 @@ +# 所谓水仙花数就是一个三位数,它各位的立方之和加起来的数值等于本身, +# 153就是一个水仙花数 + 153 = 1 + 125 + 27 + + 1 * 1 * 1 = 1 + 5 * 5 * 5 = 125 + 3 * 3 * 3 = 27 + + + + +def flowers(): + x = 0 + y = 0 + z = 0 + for i in range(100, 1000): + # 第一位 + x = i // 100 + # 第二位 + y = (i % 100) // 10 + # 第三位 + z = (i % 100) % 10 + if (x*x*x + y*y*y + z*z*z) == i: + print(i) \ No newline at end of file diff --git "a/Algorithm/\350\200\201\351\274\240\350\257\225\346\257\222\347\232\204\351\227\256\351\242\230.py" "b/Algorithm/\350\200\201\351\274\240\350\257\225\346\257\222\347\232\204\351\227\256\351\242\230.py" new file mode 100644 index 00000000..2a18f363 --- /dev/null +++ "b/Algorithm/\350\200\201\351\274\240\350\257\225\346\257\222\347\232\204\351\227\256\351\242\230.py" @@ -0,0 +1,15 @@ +''' +七只老鼠,一百瓶药水,其中有一瓶是毒药,毒发时间为一天,使用一天时间检测出毒药 +思路: + 对100瓶毒药进行二进制编码,0000001,0000010,...,1100100 + 老鼠分别为A,B,C,D,E,F,G + A老鼠喝编码格式为1xxxxxx的药水 + B老鼠喝编码格式为x1xxxxx的药水 + C老鼠喝编码格式为xx1xxxx的药水 + D老鼠喝编码格式为xxx1xxx的药水 + E老鼠喝编码格式为xxxx1xx的药水 + F老鼠喝编码格式为xxxxx1x的药水 + G老鼠喝编码格式为xxxxxx1的药水 + + 最后查看老鼠死亡情况,假如E和F死亡,说明 0000110 为毒药。 +''' diff --git "a/Algorithm/\350\256\241\347\256\227\344\270\252\346\225\260\345\255\227\344\270\272\345\207\240\344\275\215\346\225\260.c" "b/Algorithm/\350\256\241\347\256\227\344\270\252\346\225\260\345\255\227\344\270\272\345\207\240\344\275\215\346\225\260.c" new file mode 100644 index 00000000..f53ed3c4 --- /dev/null +++ "b/Algorithm/\350\256\241\347\256\227\344\270\252\346\225\260\345\255\227\344\270\272\345\207\240\344\275\215\346\225\260.c" @@ -0,0 +1,13 @@ +#include +#include + +int main(int argc, char **argv) { + int x = 1000; + int count = 0; + while(x > 0){ + x /= 10; + count ++; + } + printf("%d",count); + return EXIT_SUCCESS; +} diff --git "a/Algorithm/\350\256\241\347\256\227\346\225\260\347\273\204\344\270\255\346\214\207\345\256\232\345\214\272\345\237\237\347\232\204\345\222\214.py" "b/Algorithm/\350\256\241\347\256\227\346\225\260\347\273\204\344\270\255\346\214\207\345\256\232\345\214\272\345\237\237\347\232\204\345\222\214.py" new file mode 100644 index 00000000..df0c319c --- /dev/null +++ "b/Algorithm/\350\256\241\347\256\227\346\225\260\347\273\204\344\270\255\346\214\207\345\256\232\345\214\272\345\237\237\347\232\204\345\222\214.py" @@ -0,0 +1,13 @@ +class NumArray(object): + + def __init__(self, arr): + # 新建一个数组,来存储和 + self.sum = [0 for i in range(0, len(arr) + 1)] + for i in range(1, len(arr) + 1): + self.sum[i] = self.sum[i - 1] + arr[i - 1] + + def sumRange(self, start, end): + return self.sum[end + 1] - self.sum[start] + + +# 也可以使用线段树这种数据结构来解决 \ No newline at end of file diff --git "a/Algorithm/\351\200\211\346\213\251\346\216\222\345\272\217.c" "b/Algorithm/\351\200\211\346\213\251\346\216\222\345\272\217.c" new file mode 100644 index 00000000..88254817 --- /dev/null +++ "b/Algorithm/\351\200\211\346\213\251\346\216\222\345\272\217.c" @@ -0,0 +1,77 @@ +#include +#include + +void printArr(int[], size_t); +int getMinPosition(int[], int, size_t); +void selectSorted(int[], size_t ); + +int main(int argc, char **argv) { + int arr[5] = {5,4,7,1,2}; + selectSorted(arr,5); + printArr(arr,5); + return EXIT_SUCCESS; +} +void printArr(int arr[],size_t size){ + for(int x = 0 ; x < size ; x++){ + printf("%d\n", arr[x]); + } +} + +int getMinPosition(int arr[], int start, size_t end) { + int position = start; + int value = arr[start]; + start++; + for (; start < end; start++) { + if (arr[start] < value) { + position = start; + value = arr[start]; + } + } + return position; +} + +void selectSorted(int arr[], size_t size) { + for (int i = 0; i < size; i++) { + int index = getMinPosition(arr, i, size); + int temp = arr[index]; + arr[index] = arr[i]; + arr[i] = temp; + + } +} + + +//python + + + +def getMinIndex(arr, position): + + length = len(arr) + + min = arr[position] + minIndex = position + + position += 1 + + while(position < length): + if arr[position] < min: + min = arr[position] + minIndex = position + position += 1 + return minIndex + + +def selectSort(arr): + length = len(arr) + for i, v in enumerate(arr): + minIndex = getMinIndex(arr, i) + temp = arr[i] + arr[i] = arr[minIndex] + arr[minIndex] = temp + + +arr = [4, 5, 7, 8, 9, 6, 1] +selectSort(arr) +print(arr) + diff --git "a/Algorithm/\351\242\240\345\200\222\346\225\260\347\273\204\351\241\272\345\272\217.py" "b/Algorithm/\351\242\240\345\200\222\346\225\260\347\273\204\351\241\272\345\272\217.py" new file mode 100644 index 00000000..6db275db --- /dev/null +++ "b/Algorithm/\351\242\240\345\200\222\346\225\260\347\273\204\351\241\272\345\272\217.py" @@ -0,0 +1,12 @@ + +arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +length = len(arr) + +for i in range(0,length // 2): + temp = arr[i] + arr[i] = arr[length - 1 - i] + arr[length - i - 1] = temp + +print(arr) + diff --git "a/Arthas/arthas-\346\214\207\344\273\244-dashboard.java" "b/Arthas/arthas-\346\214\207\344\273\244-dashboard.java" new file mode 100644 index 00000000..0aef9cfc --- /dev/null +++ "b/Arthas/arthas-\346\214\207\344\273\244-dashboard.java" @@ -0,0 +1,56 @@ +---------------------------- +dashboard | +---------------------------- + # 文档 + https://alibaba.github.io/arthas/dashboard.html + + # 当前系统的实时数据面板 + * 按 ctrl+c 退出 + + # 面板 + ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON + 21 Timer-for-arthas-dashboard-61567f15-5bbc-4f8e system 10 RUNNABLE 100 0:0 false true + 10 AsyncAppender-Worker-arthas-cache.result.Asyn system 9 WAITING 0 0:0 false true + 8 Attach Listener system 9 RUNNABLE 0 0:0 false true + 3 Finalizer system 8 WAITING 0 0:0 false true + 2 Reference Handler system 10 WAITING 0 0:0 false true + 4 Signal Dispatcher system 9 RUNNABLE 0 0:0 false true + 20 as-command-execute-daemon system 10 TIMED_WAITING 0 0:0 false true + 12 job-timeout system 9 TIMED_WAITING 0 0:0 false true + 1 main main 5 TIMED_WAITING 0 0:0 false false + 13 nioEventLoopGroup-2-1 system 10 RUNNABLE 0 0:0 false false + 17 nioEventLoopGroup-2-2 system 10 RUNNABLE 0 0:0 false false + 14 nioEventLoopGroup-3-1 system 10 RUNNABLE 0 0:0 false false + 15 pool-1-thread-1 system 5 TIMED_WAITING 0 0:0 false false + 16 pool-2-thread-1 system 5 WAITING 0 0:0 false false + + + Memory used total max usage GC + heap 17M 35M 224M 8.00% gc.copy.count 25 + eden_space 1M 9M 61M 1.67% gc.copy.time(ms) 129 + survivor_space 1M 1M 7M 15.45% gc.marksweepcompact.count 3 + tenured_gen 15M 24M 154M 10.16% gc.marksweepcompact.time(ms) 103 + nonheap 19M 20M -1 95.69% + code_cache 4M 4M 240M 1.69% + metaspace 13M 14M -1 95.74% + compressed_class_space 1M 1M 1024M 0.16% + direct 0K 0K - Infinity% + mapped 0K 0K - NaN% + + + Runtime + os.name Linux + os.version 3.10.0-957.1.3.el7.x86_64 + java.version 1.8.0_211 + java.home /usr/local/java/jdk1.8.0_211/jre + systemload.average 0.00 + processors 1 + uptime 385s + + * 线程的ID, 名称, 线程组, 优先级(越大表示优先级越高), 状态, CPU占用百分比(将所有线程在这100ms内的cpu使用量求和, 再算出每个线程的cpu使用占比), 线程的执行时间, 是否中断, 是否是daemon线程 + + * 内存区域, 已使用, 总大小, 最大, 使用率, GC信息 + gc的拷贝次数,拷贝花费的时间, 标记整理次数, 标记整理花费时间 + + * 运行时信息 + diff --git "a/Arthas/arthas-\346\214\207\344\273\244-thread.java" "b/Arthas/arthas-\346\214\207\344\273\244-thread.java" new file mode 100644 index 00000000..b94b110c --- /dev/null +++ "b/Arthas/arthas-\346\214\207\344\273\244-thread.java" @@ -0,0 +1,43 @@ +-------------------------- +thread | +-------------------------- + # 查看当前线程信息, 查看线程的堆栈 + + # 面板 + Threads Total: 13, NEW: 0, RUNNABLE: 6, BLOCKED: 0, WAITING: 4, TIMED_WAITING: 3, TERMINATED: 0 + ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON + 22 as-command-execute-daemon system 10 RUNNABLE 100 0:0 false true + 10 AsyncAppender-Worker-arthas-cache.result.Asyn system 9 WAITING 0 0:0 false true + 8 Attach Listener system 9 RUNNABLE 0 0:0 false true + 3 Finalizer system 8 WAITING 0 0:0 false true + 2 Reference Handler system 10 WAITING 0 0:0 false true + 4 Signal Dispatcher system 9 RUNNABLE 0 0:0 false true + 12 job-timeout system 9 TIMED_WAITING 0 0:0 false true + 1 main main 5 TIMED_WAITING 0 0:0 false false + 13 nioEventLoopGroup-2-1 system 10 RUNNABLE 0 0:0 false false + 17 nioEventLoopGroup-2-2 system 10 RUNNABLE 0 0:0 false false + 14 nioEventLoopGroup-3-1 system 10 RUNNABLE 0 0:0 false false + 15 pool-1-thread-1 system 5 TIMED_WAITING 0 0:0 false false + 16 pool-2-thread-1 system 5 WAITING 0 0:0 false false + + + # 参数 + id + * 查看指定id线程的堆栈 + thread 16 + -n + * 指定最忙的前N个线程, 并打印堆栈 + thread -n 3 + -b + * 找出当前阻塞其他线程的线程 + + * 有时候发现应用卡住了, 通常是由于某个线程拿住了某个锁, 并且其他线程都在等待这把锁造成的 + thread -b + * 该参数可以一键找出阻塞了其他线程的线程 + + -i + * 指定cpu占比统计的采样间隔, 单位为毫秒 + thread -n 3 -i 1000 + + + \ No newline at end of file diff --git "a/Arthas/arthas-\350\277\234\347\250\213.java" "b/Arthas/arthas-\350\277\234\347\250\213.java" new file mode 100644 index 00000000..b8d6221a --- /dev/null +++ "b/Arthas/arthas-\350\277\234\347\250\213.java" @@ -0,0 +1,10 @@ +----------------------------- +远程监控 | +----------------------------- + + + +------------------------------------------ +Cloud Toolkit插件使用Arthas诊断远程服务器 | +------------------------------------------ + diff --git a/Arthas/arthas.java b/Arthas/arthas.java new file mode 100644 index 00000000..d7bdf570 --- /dev/null +++ b/Arthas/arthas.java @@ -0,0 +1,36 @@ +----------------------- +arthas | +----------------------- + # 阿里开源的java检测工具 + # 网站 + https://github.com/alibaba/arthas + https://alibaba.github.io/arthas/ + + # 安装 + wget https://alibaba.github.io/arthas/arthas-boot.jar + + * 下载该java包就行了 + * 就是用这java库去执行一些指令, 来监控java进程 + + + # 命名格式 + arthas-boot [-h] [--target-ip ] [--telnet-port ] + [--http-port ] [--session-timeout ] [--arthas-home ] + [--use-version ] [--repo-mirror ] [--versions] [--use-http] + [--attach-only] [-c ] [-f ] [--height ] [--width + ] [-v] [pid] + + # 启动监控的方式 + 1, 直接执行 java -jar arthas-boot.jar + * 会列出来所有的java进程 + * 输入进程的序号, 开始检测指定的Java程序 + + 2, 启动时直接指定进程的pid + * java -jar arthas-boot.jar [pid] + + +----------------------- + 命令参数 | +----------------------- + -h + * 打印帮助信息 \ No newline at end of file diff --git a/Beetl/beetl-springboot.java b/Beetl/beetl-springboot.java index b3e89c6e..54a6c290 100644 --- a/Beetl/beetl-springboot.java +++ b/Beetl/beetl-springboot.java @@ -144,4 +144,77 @@ public BeetlSpringViewResolver getBeetlSpringViewResolver(@Qualifier("beetlConfi ModelAndView INDEX = new ModelAndView("index/index"); * template - @ include('/common/common_header.html'){} \ No newline at end of file + @ include('/common/common_header.html'){} + +------------------------------- +beetl-2.7.26集成方式 | +------------------------------- + + /** + * 模板根目录 + */ + @Value("${beetl.templatesPath}") + private String templatesPath; + + @Bean(initMethod = "init", name = "beetlConfig") + public BeetlGroupUtilConfiguration getBeetlGroupUtilConfiguration() { + BeetlGroupUtilConfiguration beetlGroupUtilConfiguration = new BeetlGroupUtilConfiguration(); + //获取Spring Boot 的ClassLoader + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if(classLoader == null){ + classLoader = BeetlConfiguration.class.getClassLoader(); + } + //beetlGroupUtilConfiguration.setConfigProperties(extProperties); + ClasspathResourceLoader classpathResourceLoader = new ClasspathResourceLoader(classLoader, templatesPath); + beetlGroupUtilConfiguration.setResourceLoader(classpathResourceLoader); + + Map funcMap = new HashMap<>(); + + beetlGroupUtilConfiguration.setFunctions(funcMap); + beetlGroupUtilConfiguration.setConfigFileResource(new ClassPathResource("/beetl.properties")); + beetlGroupUtilConfiguration.init(); + //如果使用了优化编译器,涉及到字节码操作,需要添加ClassLoader + beetlGroupUtilConfiguration.getGroupTemplate().setClassLoader(classLoader); + return beetlGroupUtilConfiguration; + } + + @Bean(name = "beetlViewResolver") + public BeetlSpringViewResolver getBeetlSpringViewResolver(@Qualifier("beetlConfig") BeetlGroupUtilConfiguration beetlGroupUtilConfiguration) { + BeetlSpringViewResolver beetlSpringViewResolver = new BeetlSpringViewResolver(); + beetlSpringViewResolver.setContentType("text/html;charset=UTF-8"); + beetlSpringViewResolver.setOrder(0); + //beetlSpringViewResolver.setPrefix("/"); + beetlSpringViewResolver.setSuffix(".html"); + beetlSpringViewResolver.setConfig(beetlGroupUtilConfiguration); + return beetlSpringViewResolver; + } + +------------------------------- +Spring-boot IDEA下模版的热更新 | +------------------------------- + 1,添加依赖 + + org.springframework.boot + spring-boot-devtools + + + 2,插件 + + + org.springframework.boot + spring-boot-maven-plugin + + true + true //目前我不知道这个fork有啥用? + + + + + 3,配置 + # devtools,指定模版的目录 + spring.devtools.restart.exclude=templates/** */ + + 4,快捷键重新加载 + ctrl + shift + F9 + + \ No newline at end of file diff --git "a/GIT/GIT-\345\270\270\347\224\250\346\223\215\344\275\234.java" b/C/c-assert.c similarity index 100% rename from "GIT/GIT-\345\270\270\347\224\250\346\223\215\344\275\234.java" rename to C/c-assert.c diff --git "a/C/c-io\345\207\275\346\225\260\346\200\273\347\273\223.c" "b/C/c-io\345\207\275\346\225\260\346\200\273\347\273\223.c" new file mode 100644 index 00000000..209d5473 --- /dev/null +++ "b/C/c-io\345\207\275\346\225\260\346\200\273\347\273\223.c" @@ -0,0 +1,24 @@ +int printf (const char *, ...); +int scanf (const char *, ...); + +int fprintf (FILE *, const char *, ...); +int fscanf (FILE *, const char *, ...); + +int sprintf (char *, const char *, ...); +int sscanf (const char *, const char *, ...); + +int putchar (int); +int getchar (void); + +int getc (FILE *); +int putc (int, FILE *); + +int fgetc (FILE *); +int fputc (int, FILE *); + +char * fgets (char *, int, FILE *); +int fputs (const char *, FILE *); + +char * gets (char *); +int puts (const char *); + diff --git "a/C/c-\345\205\245\351\227\250.c" "b/C/c-\345\205\245\351\227\250.c" new file mode 100644 index 00000000..4e4afd76 --- /dev/null +++ "b/C/c-\345\205\245\351\227\250.c" @@ -0,0 +1,144 @@ +-------------------------------- +C语言-环境搭建 | +-------------------------------- + + +-------------------------------- +C语言-main函数定义 | +-------------------------------- + int main() + { + return 0; + } + +-------------------------------- +C语言的构成 | +-------------------------------- + # 关键字 + auto extern short while + break float signed _Alignas + case for sizeof _Alignof + char goto static _Atomic + const if struct _Bool + continue inline switch _Complex + default int typedef _Generic + double register void _Static_assert + enum return volatile _Thread_local + + + # 控制语句(9) + if else + for + while + do while + continue + break + switch + goto + return + + # 运算(34) + typedef + + do{}while(); + while(){} + for(;;){} + + + +-------------------------------- +C语言-编译 | +-------------------------------- + # 语法 + gcc hello.cpp + + * 在windows平台默认生成一个 a.exe 文件 + * 在Linux平台默认生成一个 a.out 文件 + + # 参数 + -o + * 指定名称,参数与值之间使用空格 + + -std + * 指定编译的C标准,枚举值参数与值之间使用=号 + c99 使用c99标准 + c1x 使用c11以前的标准 + c11 使用c11标准 + -E + * 只做预处理 + * 文件后缀: .i + -S + * 只做预处理和编译 + * 文件后缀: .s + -o + * 只做预处理和编译还有汇编 + * 文件后缀: .o + + -w + * 忽略警告 + + + # GCC编译过程 + 1,预处理 + * 宏定义展开,头文件展开,条件编译,注释删除 + 2,编译 + * 语法检查,将预处理后文件编程成汇编文件 + 3,汇编 + * 将汇编文件生成目标文件(二进制文件) + 4,链接 + * C语言写的程序需要依赖各种库 + * 编译之后还需要把库链接到最终可执行程序中去 + * 在 linux 里面使用命令: 'ldd [可执行文件]' ,可以列出来该程序运行需要的动态库 + * 在 windows 下可以使用 Dependency Walker 去列出可执行文件运行需要的动态库 + + + + +-------------------------------- +C语言-printf 转换符 | +-------------------------------- + %a 浮点数,十六进制数和P计数法 + %A 浮点数,十六进制数和P计数法 + %c 单个字符 + %d 有符号十进制整数 + %e 浮点数,e计数法 + %E 浮点数,e计数法 + %f 浮点数,十进制计数法 + %g 根据值的不同,自动选择 %f 或者 %e,%e格式用于指数小余-4或者大于等于精度时 + %G 根据值的不同,自动选择 %f 或者 %e,%e格式用于指数小余-4或者大于等于精度时 + %i 有符号的十进制整数(与%d相同) + %o 无符号八进制整数 + %p 指针 + %s 字符串 + %u 无符号十进制整数 + %x 无符号十六进制整数,使用十六进制数0f + %X 无符号十六进制整数,使用十六进制数0F + %% 打印一个百分号 + +-------------------------------- +C语言-printf 转换说明修饰符 | +-------------------------------- + # 在%和转换字符之间插入修饰符,可以修饰基本的转换说明 + # 标记 + - + * 打印项左对齐 + + + 空格 + # + 0 + + # 数字 + # .数字 + # 转换 + h + * 转换为短整型 short + hh + j + l + ll + * 转换为 long long 类型 + L + t + z + + diff --git "a/C/c-\345\205\261\347\224\250\344\275\223.c" "b/C/c-\345\205\261\347\224\250\344\275\223.c" new file mode 100644 index 00000000..df511a0b --- /dev/null +++ "b/C/c-\345\205\261\347\224\250\344\275\223.c" @@ -0,0 +1,30 @@ +------------------------------------ +共同体 | +------------------------------------ + # 共用体是一种特殊的数据类型,允许在'相同的内存位置存储不同的数据类型' + # 可以定义一个带有多成员的共用体,但是'任何时候只能有一个成员带有值' + # 共用体提供了一种使用相同的内存位置的有效方式 + union Data { + int id; + char name[20]; + } data; + + data.id = 1; + printf("%d\n",data.id); //1 + + strcpy(data.name,"KevinBlandy"); + printf("%d %s\n",data.id,data.name); //1769366859 KevinBlandy (因为公用体重复赋值,所以id变量的值被擦除) + + # 公用体的合法声明跟结构体一样 + + # 共用体占用的内存应足够存储共用体中最大的成员 + union Data { + int i; + float f; + char str[20]; + } data; + + printf("%d\n", sizeof(data)); //20 + + * 换句话说,公用体的最大内存,取决于里面定义的最大内存变量 + \ No newline at end of file diff --git "a/C/c-\345\206\205\345\255\230\347\256\241\347\220\206.c" "b/C/c-\345\206\205\345\255\230\347\256\241\347\220\206.c" new file mode 100644 index 00000000..2ecb7aa3 --- /dev/null +++ "b/C/c-\345\206\205\345\255\230\347\256\241\347\220\206.c" @@ -0,0 +1,233 @@ +---------------------- +内存分区 | +---------------------- + # GCC编译过程 + 1,预处理 + * 宏定义展开,头文件展开,条件编译,注释删除 + 2,编译 + * 语法检查,将预处理后文件编程成汇编文件 + 3,汇编 + * 将汇编文件生成目标文件(二进制文件) + 4,链接 + * C语言写的程序需要依赖各种库 + * 编译之后还需要把库链接到最终可执行程序中去 + * 在 linux 里面使用命令: 'ldd [可执行文件]' ,可以列出来该程序运行需要的动态库 + * 在 windows 下可以使用 Dependency Walker 去列出可执行文件运行需要的动态库 + + # 在Linux下查看编译后文件的内存分区 + size Demo.out + + text data bss dec hex filename + 1224 548 4 1776 6f0 Demo.out + + text + * 代码区,只读,函数 + * 加载的可执行代码段,所有可执行代码都加载到代码区,这块内存是只读的,不能在运行期间修改 + + data + * 初始化的数据:全局变量,静态变量,文字常量区(只读) + * 加载的是可执行文件数据段 + * 存储于数据段(全局初始化,静态初始化数据,文字常量(只读))的数据的生命周期为整个程序运行过程 + + bss + * 没有初始化的数据:全局变量,静态变量 + * 加的是可执行文件BSS段,位置可以分开也可以紧靠数据段 + * 存储于数据段的数据(全局未初始化,静态未初始化数据)的生命周期为整个程序过程 + + * 在程序执行之前,有几个内存分区已经确定,但是没有加载 + * text,data,bss + + * 运行程序,加载内存,首先会根据前面确定的内存分区先加载 + * text,data,bss + * 还会额外加载两个区:一个栈,一个堆 + + * 栈区(stack) + * 普通局部变量,自动管理内存 + * 先进后出 + + * 堆区(heap) + * 手动申请空间,需要手动释放,整个程序结束,系统也会自动的回收 + * 位于BSS区和栈区之间 + + + # 栈越界 + * 递归,耗光了栈内存的空间的 + * 查看系统(linux)栈空间的大小 + ulimit -a + + core file size (blocks, -c) 0 + data seg size (kbytes, -d) unlimited + scheduling priority (-e) 0 + file size (blocks, -f) unlimited + pending signals (-i) 15088 + max locked memory (kbytes, -l) 64 + max memory size (kbytes, -m) unlimited + open files (-n) 65535 + * 最多只能打开 65535个文件 + pipe size (512 bytes, -p) 8 + POSIX message queues (bytes, -q) 819200 + real-time priority (-r) 0 + stack size (kbytes, -s) 8192 + * 栈的大小是 8192 字节,就是 1024 KB ,也就是 1 MB + cpu time (seconds, -t) unlimited + max user processes (-u) 15088 + * 一个用户最大的进程数量 + virtual memory (kbytes, -v) unlimited + file locks (-x) unlimited + + * 栈的生长方向以及存放方向 + int a; + int b; + int arr[2] = { }; + printf("%p\n", &a); //0028FF3C + printf("%p\n", &b); //0028FF38 + printf("%p\n", &arr[0]); //0028FF30 + printf("%p\n", &arr[1]); //0028FF34 + + /* 栈内存 + ----------- + a 0028FF3C + ----------- + b 0028FF38 + ----------- + arr[1] 0028FF34 + ----------- + arr[0] 0028FF30 + ----------- + */ + + +------------------------------- +操作内的函数 | +------------------------------- + # 这些函数都是在 库中 + + void *memset (void *p, int v, size_t s) + * 将p所指向的某一块内存中的'每个字节'的内容全部设置为v指定的ASCII值,块的大小由第三个参数s指定 + * 参数 + - p 操作的内存首地址 + - c 填充的数据看起来是整形,其实是当作 ascii 码值,unsigned int,可以是 0 - 255 + * 其实它的值只有是0 才有意义,或者说p是一个数组 + - n 填充的数据大小(以p开始填充多少个字节) + * 返回 p 的首地址地址 + * demo + int a; + memset(&a,0,sizeof(a)); //其实是四个字节每个字节都写入了 97 + printf("%d\n",a); //0 + + memset(&a,97,sizeof(a)); //其实是四个字节每个字节都写入了 97 + printf("%c\n",a); //a(%c仅仅读取一个字节) + + int arr[10]; + memset(arr,97,sizeof(arr)); //40个字节,每个字节都写入了0 + printf("%c\n",arr[0]); //a + + * 这个函数多用来清空数组 + int arr[] = {1,2,3,4,5}; + memset(arr,0,sizeof(arr) * 5); + + + void *memcpy (void *dst, const void *src, size_t size); + * 把src中的size个字节数据copy到dst中 + * 使用该函数,最好不要出现内存重叠(拷贝源和目的都是一个) + * demo + int src[] = {1,2,3}; + int dst[3]; + //把src的数据拷贝到dst中,拷贝dst大小个字节 + memcpy(dst,src,sizeof(dst)); + for(int x = 0 ;x < 3 ;x++){ + printf("%d\n",dst[x]); //1 2 3 + } + + void *memmove (void *dst, const void *src, size_t szie); + * 同上,从src拷贝szie字节到dest,它的使用场景是'内存重叠cpy'的时候 + * 它能够保证'src在被覆盖之前将重叠区域的字节拷贝到目标区域中',但复制完成后src内容会被更改 + * 当目标区域与源区域没有重叠则和memcpy函数功能相同 + + int memcmp (const void src*, const void dst*, size_t size) + * 用来比较俩内存块儿是否相等 + * 比较src和dst内存块开始的size个字节数据是否相同 + * 如果相同返回 0,如果 dst > src 返回 1,如果 dst 小于 src 返回 -1 + + +------------------------------- +堆区内存的操作 | +------------------------------- + # 申请一个堆空间 + void *malloc (size_t) + * 参数表示申请的空间是多少(就算是0也能申请成功,但是操作该内存会越界) + * 如果申请成功,返回的数据就是申请的堆空间的首元素地址(指针),申请失败,返回 NULL + * 申请的堆空间,如果程序没有结束,那么不会释放,需要程序手动的释放 + * demo + int *p = (int *) malloc(sizeof(int)); + *p = 15; + printf("%d",p[0]); //15 + + void *calloc(int num, int size); + * 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0 + * 所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0 + * 与 malloc() 除了参数不同,它会出申请的内存进行初始化(设置为0)操作,malloc不会,在申请后可能内存存在遗留数据 + + # 释放堆空间内存 + void free (void *); + * 释放堆空间的内存,交还给操作系统 + * 同一块儿的堆内存,只能执行一次释放操作 + * 释放掉内存后,执行该内存的指针就是野指针了 + * 标准的释放代码,要释放内存,并且 + if(p != NULL){ + free(p); + p = NULL; + } + + + # 重新分配内存 + void *realloc(void *address, int newsize); + * 该函数重新分配内存,把内存扩展到 newsize + * demo + char *p = (char *) calloc(1, 1); + *p = 'a'; + + char *p1 = (char *)realloc(p,2); + printf("%p %p\n", p, p1); //009F2128 009F2128 + + *(p1 + 1) = 'b'; + printf("%c %c", *p, *(p + 1)); //a b + + + # 堆空间的越界 + * 编译器不会检查堆空间的越界,开发的时候需要注意 + + char *p = (char *)malloc(0); + *p = 'a'; + printf("%c",*p); //a + + + # 函数返回堆区的指针 + void *foo(int size){ + return malloc(size); + } + + int main() { + int *p = (int *)foo(4); + *p = 4; + printf("%d",*p); //4 + return EXIT_SUCCESS; + } + * 允许函数返回堆内存的指针,只要是没有被回收,该内存都可以被操作 + + + + + + + + + + + + + + + + + diff --git "a/C/c-\345\206\205\347\275\256\345\207\275\346\225\260.c" "b/C/c-\345\206\205\347\275\256\345\207\275\346\225\260.c" new file mode 100644 index 00000000..98577f1c --- /dev/null +++ "b/C/c-\345\206\205\347\275\256\345\207\275\346\225\260.c" @@ -0,0 +1,12 @@ +----------------------------- +C 内置函数 | +----------------------------- +sizeof() + * 以字节为单位,返回对象的大小 + printf("int 的大小是 %d",sizeof(int)); //4 + * 返回类型是 size_t + * 如果参数是关键字,必须添加括号,如果是变量,则不需要 + int num = 4; + printf("size = %d",sizeof num); + + diff --git "a/C/c-\345\207\275\346\225\260.c" "b/C/c-\345\207\275\346\225\260.c" new file mode 100644 index 00000000..6f3f7ffc --- /dev/null +++ "b/C/c-\345\207\275\346\225\260.c" @@ -0,0 +1,143 @@ +---------------------------------------- +函数 | +---------------------------------------- + # 天下函数都差不多 + void func(int var){} + + # 函数声明 + * 函数声明会告诉编译器函数名称及如何调用函数,函数的实际主体可以单独定义 + return_type function_name( parameter list ); + * demo + + int max(int num1, int num2){} => int max(int num1, int num2); + + //函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面也是有效的声明 + int max(int, int); + + * 函数声明的作用就是告诉编译器,这个函数我是有定义的,但是放在了别的地方 + * 一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的,在这种情况下,应该在调用函数的文件顶部声明函数 + * 在main函数中调用其他的函数,只会往前面找已经定义/声明的函数,如果没找到的话,C编译会给警告,C++会给错误 + * 一个函数,可以重复的声明多次 + * 声明中的形参变量名不仅仅可以省略,还可以不一样 + + + # 函数返回局部变量的指针 + * 返回值是指针类型的函数 + * 不能返回局部变量的地址,因函数执行完毕后,局部遍历内存释放,指针指向的内存地址被回收,此时返回的指针,就成了野指针 + int *foo(){ + int x = 4; + return &x; //function returns address of local variable + } + * 在一些环境中(Linux测试ok),就算你返回了局部变量的指针,系统也会给你返回该指针的变量值 + + * 可以使用全局变量,因为全局变量是在程序结束后才释放内存 + + + + + +---------------------------------------- +函数参数 | +---------------------------------------- + # 调用函数时,有两种向函数传递参数的方式 + * 传值调用 + - 该方法把参数的实际值复制给函数的形式参数,在这种情况下,修改函数内的形式参数不会影响实际参数 + - 默认情况下,C 使用传值调用来传递参数,一般来说,这意味着函数内的代码不能改变用于调用函数的实际参数 + + * 引用调用 + - 通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作 + +---------------------------------------- +可变参数 | +---------------------------------------- + # 需要使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏 + * 定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数 + * 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的 + * 使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。 + * 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。 + * 使用宏 va_end 来清理赋予 va_list 变量的内存。 + + # 几个方法 + va_list 声明一个变量 + va_copy() 复制一个副本出来 + va_start() 初始化变量,该参数的第二个参数表示返回的数据类型(也就是可变参数的类型,可变参数允许类型不同) + va_arg(); 开始获取变量 + va_end() 释放保存数据的内存 + + # Demo + void foo(char *p, int num, ...) { + //定义变长变量本质是一个集合 + va_list valist; + + //初始化该值,num参数表示变长参数的数量 + va_start(valist, num); + + printf("%s:", p); + + for (int x = 0; x < num; x++) { + //每次调用该方法都会获取下一个变长变量 + int value = va_arg(valist, int); + printf("[%d]", value); + } + + //清空集合保存的内存 + va_end(valist); + } + + int main(int argc, char **argv) { + foo("Hello",4,1,2,3,4); //Hello:[1][2][3][4] + } + +---------------------------------------- +函数的常量 | +---------------------------------------- + + # 函数的名称和行数 + * 函数的名称:__FUNCTION__ + * 当前的行号:__LINE__ + + #include + #include + + void function(){ + printf("%s-%d",__FUNCTION__,__LINE__); //function-5 + } + + +---------------------------------------- +main函数的参数 | +---------------------------------------- + # main函数的参数定义 + int main(int argc,char *argv[]) { + return EXIT_SUCCESS; + } + int main(int argc,char **argv) { + return EXIT_SUCCESS; + } + + * argc是第二个数组的元素个数 + * char *rgv[],表示参数是一个数组,数组的每个元素都是 char 的指针 + * **argv,表示参数是一个2级指针 + + # 获取main函数的参数 + int main(int argc,char **argv) { + printf("%d\n",argc); + for(int x = 0 ;x < argc; x++){ + //*(argv + x) + printf("%s\n",argv[x]); + } + return EXIT_SUCCESS; + } + + * '第一个参数永远都是当前指向文件的名称,也就是说main函数无论如何都有一个参数' + * 参数的传递用","号分割 + demo.exe 1 2 3 + + # 使用双引号把多个单词合并为一个字符串 + //Demo.exe "Hello KevinBlandy" C + int main(int argc, char **argv) { + puts(argv[1]); //Hello KevinBlandy + puts(argv[2]); //C + return EXIT_SUCCESS; + } + diff --git "a/C/c-\345\217\230\351\207\217.c" "b/C/c-\345\217\230\351\207\217.c" new file mode 100644 index 00000000..1330a220 --- /dev/null +++ "b/C/c-\345\217\230\351\207\217.c" @@ -0,0 +1,327 @@ + + +---------------------------------------- +变量 | +---------------------------------------- + # 变量的类型与内存大小 + char + * 字符型,使用单引号声明,用于输出一个单一的字符 + * 1个字节 + * 它并不是把字符存储到内存单元,而是把字符对应的 ascii 编码存放到存储单元 + * 本质上就是一个字节的整形,下面两种声明方式都是正确的 + char c = 97; + char c = 'a'; + * 花里胡哨 + printf("%d\n",'\023'); //\0开头表示八进制 19 + printf("%d\n",'\x15'); //\x开头表示十六进制 21 + printf("%d\n",'\0'); //\0还是0 + + short + * 短整型,2字节 + int + * 整形,4字节 + long + * 长整型 + * windows4字节 + * linux4字节(32位),8字节(64位) + long long + * 长长整型 + * 8字节 + + float + * 单精度浮点型 + * 4字节,7位有效数字 + * 1位符号,8位指数,23位小数 + + + double + * 双精度浮点型 + * 8字节,15 - 16 位有效数字 + * 双精度浮点值,双精度是1位符号,11位指数,52位小数 + + long double + * 16字节 + + + + unsigned + signed + + void + * 不可以定义 void 类型的普通变量,编译器不知道它是什么类型,无法分配内存空间 + * 但是可以定义 void 指针,因为指针的大小是可以确定的(传说中的万能指针) + + _Bool + _Complex + _Imaginary + + * 整形数据在内存中占的字节与所选择的OS有关 + * C标准并没有明确规定整形类型数据的长度,但 long 的长度不能低于 int, int 不能低于 short + * 占位小的数据赋值给占位大的数据,没有问题,系统会有变量提示 + * 占位大的数据赋值给占位小的数据,会发生高位截断从而引发精度丢失的问题 + + * 浮点类型变量是由有限的存储单元组成,因此只能提供有限的有效数字 + * 不在有效范围的数字将会被舍去,这样可能会产生一些误差 + + + # 常量默认类型 + * 数字通常以 int 类型表示,如果超出了 int 类型,编译器会先视其为 long int 类型 + * 如果超出了 long 可表示的最大值,编译器则视其为 unsigned long 类型 + * 如果还不不够大,编译器则将其视为 long long 或者 unsigned long long 类型 + * 前提是编译器能够识别这些类型 + + * 八进制,十六进制也被视为 int 类型 + * 如果值太大,编译器会尝试使用 unsigned int ,long ,unsigned long ,long long ,unsigned long 类型 + + + # 指定常量数据类型 + * 在值后面添加L/l表示该常量是 long 数据类型 + 15L; + + * 在职后天添加LL/ll表示该常量是 long long 数据类型 + 15LL + + * 添加f/F后缀,是 float 的常量声明 + 0.23F + + * 添加 u/U后缀 是 unsgined 常量类型声明 + 5u + 5UL + 5ULL + + * 添加0b前缀,是二进制数据类型 + 0B0101101 + + +---------------------------------------- +变量修饰符 | +---------------------------------------- + const + * 常量 + + volatile + * 防止编译器优化代码 + + restrict + * 允许编译器优化某部分代码,以更好的支持运算,它只能用于指针 + * 表名该指针是访问数据对象唯一且初始化的方式 + + extern + * 声明一个变量,extern 生的变量没有建立存储空间 + extern int x; + * 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候 + + register + * 定义寄存器变量,提高效率,register 是建议类型的指令,而不是命令类型的指令 + * 如果 CPU 有空闲的寄存器,那么 register 生效,反之 register 无效 + * 这种类型的变量,不能使用取地址符 & ,因为无法获取,该变量在CPU寄存器 + + + auto + * 局部变量修饰 + * 在函数内部的变量 int a = 10; 编译器会有 int a = 10 之前会加上auto的关键字 + * auto的出现意味着,当前变量的作用域为当前函数或代码段的局部变量,意味着当前变量会在内存栈上进行分配 + + static + * 静态变量修饰 + + +---------------------------------------- +类型转换 | +---------------------------------------- + # 显式转换 + * 大转小 + int num = 255; + short s = (short)num; + * 使用括号来完成强制转换,可能引发精度丢失的问题 + + + # 隐式转换 + * 小转大 + double a; + int b = 0; + a = b; //编译器自动把b的值转换为double,在给a赋值 + +---------------------------------------- +bool 类型 | +---------------------------------------- + # C语言中使用 0 表示 false,非 0 表示 true + # C99提供了 #include 头文件,使C增加了 bool 关键字和 true / false 关键字 + bool flag = true; + flag = false; + +---------------------------------------- +常量声明 | +---------------------------------------- + # 编译时替换常量 + #define name value + + * 在编译程序的时候,程序中的所有 name 都会被替换为 value + * 这个过程称为编译时替换,在运行程序的时候,所有替换都已经完成 + * 这样定义的常量也被称为明示常量 + + * 末尾不需要添加;,而且名称要大写 + + # const + const int X = 5; + + * X 这个变量只读,不能修改 + + +---------------------------------------- +typedef 机制 | +---------------------------------------- + # typedef 机制允许开发者为现有的数据类型创建别名 + typedef char byte; + /* + 1111 1111 源码 + 1000 0000 反码 + 1000 0001 补码 + */ + byte x = 255; + printf("%d",x); //-1 + + +---------------------------------------- +可移植类型 stdint.h 和 inttypes.h | +---------------------------------------- + //TODO + + + + + +---------------------------------------- +局部变量 | +---------------------------------------- + # 普通局部变量 + * 声明在代码块{}内部的变量,只能在{}内部使用,在{}外部,不能访问{}内部中的局部变量 + * 局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值 + * 局部变量也叫 auto 变量(auto 可以省略不不写编译器会自动的添加,它只能出现在{}内部) + * 如果没有初始化赋值,那么默认值为随机(垃圾数据) + * 只有执行到该变量的定义语句,才会分配内存空间,{}代码块结束后,内存释放 + + # static 局部变量 + * 在{}中使用 static 修饰的局部变量 + * 静态的局部变量,在编译阶段就已经分配了空间,函数在调用前,就已经初始化,且仅仅只会执行这一次初始化(但是可以重复的赋值多次) + * 静态局部变量不会释放,只有程序结束,所有静态变量才会自动释放 + * 在{}之外,不能访问{}中的静态变量 + * 如果静态变量未初始化,那么它的默认值为:0 + + * 静态变量,存在于 data 区,在程序未启动,就已经初始化了值 + * 只能用常量初始化,不能使用变量(变量还未初始化,常量就已经初始化了) + int y = 0; + static int x = y; //error + +---------------------------------------- +全局变量 | +---------------------------------------- + # 普通全局变量(外部链接) + * 定义在函数外部的变量,称之为全局变量 + * 在任何地方,都能使用这个全局变量 + * 全局变量,在编译时,就已经分配了内存,只有在整个程序结束,才会释放 + * 如果全局变量定义在了执行函数的后面,那么需要先使用 extern 声明,如果只有 extern 声明,没有定义,那么会异常 + #include + #include + //初始化局部变量 + int y = 5; + int main(int argc,char **argv) { + //声明局部变量 + extern int x; + //使用局部变量,变量y在执行函数的前面,不需要声明 + printf("%d %d\n",x,y); + return EXIT_SUCCESS; + } + //初始化全局变量 + int x = 10; + + * 在函数内部使用全局变量,都建议先声明再使用 + + * 全局变量未初始化,默认赋值为:0 + * 全局变量只能定义一次,可以声明多次 + #include + #include + int main(int argc,char **argv) { + extern int x; + extern int x; + extern int x; //多次声明 + return EXIT_SUCCESS; + } + int x = 10; + extern int a; + extern int a; + extern int a; //多次声明 + + * 全局变量的缺陷,如果定义一个全局变量,没有初始化的时候,无法确定是定义,还是声明 + * 声明,可以有多次,定义只有一次,'只要是赋值,就是定义,其他的都是声明' + int x;int x;int x; //x不知道哪个是定义,哪个是声明,这种语法在C语言是合法的,在C++是非法的 + int x = 10; //有赋值,肯定是定义,其他地方只能是声明 + + * 定义全局变量,建议初始化 + * 声明全局变量,建议添加:extern 关键字 + #include + #include + int main(int argc,char **argv) { + //声明全局变量 + extern int a; + printf("%d",a); //10 + return EXIT_SUCCESS; + } + //定义全局变量 + int a = 15; + //声明全局变量 + extern int a; + + # extern 还可以用于函数声明(函数声明的 extern 可以省略不写) + extern void func(int,char *); + + + # 全局变量只能使用常量初始化 + int x = 5; + size_t size = sizeof(x); + int y = x * 5; // error: initializer element is not constant + int main(int argc, char **argv) { + return EXIT_SUCCESS; + } + + * 只要不是变成数组, sizeof 表达式可以被视为常量 + + # 全局变量分文件 + * 不同文件,普通全局变量只能定义一次,可以声明多次 + + # static 全局变量(内部链接) + * 和普通全局变量的文件作用域不一样 + * 普通的全局变量,在多个文件中,只能被定义一次,所有文件都能使用(需要先声明) + * static 全局变量,可以在多个文件中被重复定义,因为,它只能当前文件使用,其他的文件无法使用 + + * extern 只能用于普通的全局变量 + +---------------------------------------- +普通函数与 static 函数 | +---------------------------------------- + # 普通函数和 static 函数的区别与 普通全局变量和 static 全局变量的区别是一样的 + * 普通函数,所有文件只能定义一次,所有文都可以使用 + * static 函数,只能在当前文件使用,所以多个文件,可以重复的定义相同的函数 + +--------------------------------------- +总结 | +--------------------------------------- +—————————————————————————————————————————————————————————————————————————— +类型 作用域 生命周期 存储位置 | +—————————————————————————————————————————————————————————————————————————— +auto 变量 {}内 当前{} 栈 +—————————————————————————————————————————————————————————————————————————— +static 局部变量 {}内 整个程序运行期 初始化在data,未初始化在bss +—————————————————————————————————————————————————————————————————————————— +extern 变量 整个程序 整个程序运行期 初始化在data,未初始化在bss +—————————————————————————————————————————————————————————————————————————— +static 全局变量 当前文件 整个程序运行期 初始化在data,未初始化在bss +—————————————————————————————————————————————————————————————————————————— +extern 函数 整个程序 整个程序运行期 代码区 +—————————————————————————————————————————————————————————————————————————— +static 函数 当前文件 整个程序运行期 代码区 +—————————————————————————————————————————————————————————————————————————— +register 变量 {}内 当前{} 运行时存储在CPU寄存器 +—————————————————————————————————————————————————————————————————————————— +字符串常量 当前文件 整个程序运行期 data +—————————————————————————————————————————————————————————————————————————— diff --git "a/C/c-\345\255\227\347\254\246\344\270\262.c" "b/C/c-\345\255\227\347\254\246\344\270\262.c" new file mode 100644 index 00000000..f46c031a --- /dev/null +++ "b/C/c-\345\255\227\347\254\246\344\270\262.c" @@ -0,0 +1,407 @@ +------------------------ +字符串常量 | +------------------------ + # C語言中没有字符串,使用的是字符数组 + # 字符串是内存中一段连续的char空间,以'\0'(数字0)结尾 + # 字符串一定是字符数组,字符数组不一定是字符串 + //字符数组 + char name[5] = {'K','e','v','i','n'}; + //字符串 + char name[10] = {'K','e','v','i','n','\0'}; + //字符串 + char name[10] = {'K','e','v','i','n',0}; + //字符串 + char a1[10] = {'a','b','c'};(因为后面的角标默认值为0,0就是结束符) + + * 字符数组以'\0'结尾,那么这个字符数组就是字符串 + + # '%s'遇到结束符便结束 + char a1[] = {'a','b','c','\0','d','e','f'}; + printf("%s",a1); //abc + + # 最常用的初始化 + char string[] = "你好啊"; + + * 字符串的结尾,编译器会自动添加结束符 + + char string[] = "你好"; + printf("%s size=%d",string,sizeof(string)); + //你好 size=7 + //UTF8编码一个汉字占3个字节,最后还有一个隐藏的0,一个字节表示结束符 + + * 就算是自己主动添加结束符,编译器还是会帮你添加一个 + const char names[] = "Hello\0"; + printf("string = %s,size = %d",names,sizeof(names)); + //string = Hello,size = 7 + + * 遇到 \0 便结束 + char string[] = "Hello\0c"; + printf("%s size=%d",string,sizeof(string)); + //Hello size=8 + //遇到\0结束,所以只打印了Hello,最后自动添加了\0结束符,所以也会占一个字节 + + * \0后别跟数字,可能刚好组成一个转义字符 + char string[] = "\0123Hello"; + printf("%s size=%d",string,sizeof(string)); + // + //3Hello size=8 + // '\012' 被转义成了一个换行符(012是一个八进制,在ascii中表示换行) + + * 如果限定字符长度,一定要给结束符留一个位置 + char string[6] = "Hello"; + printf("%s size=%d",string,sizeof(string)); + //Hello size=5 + //如果[]小于6,会有乱码,因为没有留给结束符位置 + + # 获取字符串的长度(字节大小) + * 使用 strlen(const char *s) 函数 + * 计算并返回字符串s的长度,不包含结束符 '\0',size_t 类型 + * 仅仅计算第一个文件分隔符以前的字符串长度 + + # 字符串的copy + * 使用 strcpy(char *dst,char *src) 函数 + * 如果src的内容长度大于dst的,那么会产生数据溢出 + * 只会拷贝第一个'\0'前的数据 + char name[11] = "KevinBlandy"; + char cp_name[11]; + + strcpy(cp_name,name); + printf("%s\n",cp_name); //KevinBlandy + + * strncpy(char *dst,char *src,int size) + * 把 src 的数据拷贝到 dst,size参数可以限制copy的长度 + * 建议只复制 sizeof(dst) - 1 个元素到目标,因为要留一个位置给'\0' + * 如果 size 超过了 sizeof(dst) - 1,那么会发生溢出 + char str[] = "123456"; + char dst[5]; + strncpy(dst,str,sizeof(dst) - 1); + printf("%s",dst); //123 + + * 如果 '\0' 在拷贝的范围之内,那么'\0'以后的数据全部会被丢弃 + char str[] = "Hello\0Java"; + char dst[100]; + + strncpy(dst,str,sizeof(str)); + + printf("dst = %s\n",dst); //Hello + printf("dst = %s\n",dst + strlen("Hello") + 1); // + + # 字符串的比较 + * 字符串的比较,strcmp(s1,s2) + * 如果 s1 和 s2 是相同的,则返回 0 + * 如果 s1 s2 则返回大于 0 + char s1[11] = "Hello"; + char s2[11] = "Hi"; + char s3[11] = "Hello"; + printf("%d %d %d\n",strcmp(s1,s3),strcmp(s1,s2),strcmp(s2,s1)); //0 -1 1 + * 其实是比较字符的ascii码,谁大就是谁大 + + * 可以指定比较的字符串长度使用 strncmp(s1,s2,length); + * 仅仅比较 s1,s2的前length个字符 + char src[] = "HelloC"; + char dst[] = "HelloJava"; + int result = strncmp(src,dst,5); + printf("result = %d\n",result); //result = 0 + + + # 字符串的追加 + * strcat(s1, s2)连接字符串 s2 到字符串 s1 的末尾 + char s1[11] = "Hello"; + char s2[11] = " C"; + strcat(s1,s2); + printf("%s\n",s1); //HelloC + + * 仅仅追加指定的长度可以使用 strncat(s1,s2,length); + * 把s2的前length个字符追加到s1 + char src[] = "Hello"; + char dst[] = "clang"; + strncat(src,dst,1); + printf("result = %s\n",src); //result = Helloc + + + # 获取格式化后的字符串(序列化) + * sprintf(char dst*, const char *, ...) + * 把 字符串格式化后,写入到dst中 + int x = 10; + char y = 'H'; + char str[] = "Java"; + char dst[100]; + sprintf(dst,"x = %d,y = %c,buf = %s",x,y,str); + printf("dst = %s\n",dst); //dst = x = 10,y = H,buf = Java + + # 预定义从键盘的输入的格式化字符串(反序列化) + * sscanf (const char *, const char *, ...) + * 把从屏幕读取到的字符串格式化后,写入到dst中 + //定义一个"输入的字符串" + char dst[] = "1 2 3"; + //定义变量 + int a,b,c; + //使用 cccanf 把 输入的字符,赋值给变量 + sscanf(dst,"%d %d %d",&a,&b,&c); + printf("a=%d,b=%d,c=%d\n",a,b,c); //a=1,b=2,c=3 + + * 从字符串中提取整形变量是最方便的 + char inputs[] = "a=10,b=20"; + int a , b; + sscanf(inputs,"a=%d,b=%d",&a,&b); + printf("a=%d,b=%d\n",a,b); //a=10,b=20 + + * 提取字符串,默认以空格分割 + char temp[] = "abc def 123"; + char str1[4],str2[4],str3[4]; + sscanf(temp,"%s %s %s",str1,str2,str3); + printf("str1=%s,str2=%s,str3=%s",str1,str2,str3);//str1=abc,str2=def,str3=123 + + # 字符串的查询 + * 检索字符第一次出现的位置(内存地址) strchr(temp,'x'); + * 如果查询失败,返回 NULL + + * 检索字符串第一次出现的位置(内存地址) strstr(temp,"def"); + * 如果查询失败,返回 NULL + + char temp[] = "abcdefg"; + + char *result = strchr(temp,'a'); + printf("result = %p,char = %c \n",result,*result); //result = 0061FF24,char = a + + result = strstr(temp,"def"); + printf("result = %p,char = %c \n",result,*result); //result = 0061FF27,char = d + + + # 字符串切割 + * strtok(char *str,const char *delmi); + * 以 delmi 来切割字符串str + * 返回值为切割后的字符串 + * 匹配切割字符串的地方,换成结束符 + * 可以通过多次调用该方法来获取到字符串中所有被切割的字段 + * 除了第一次调用,余下的N次调用第一个参数都必须是NULL,当最后一次匹配不出结果返回 NULL + char temp[] = "ab-cd-ef"; + //第一次匹配 + char *p = strtok(temp,"-"); + printf("p = %s\n",p); //ab + + //第二次匹配 + p = strtok(NULL,"-"); + printf("p = %s\n",p); //cd + + //第三次匹配 + p = strtok(NULL,"-"); + printf("p = %s\n",p); //ef + + //第四次匹配 + p = strtok(NULL,"-"); + printf("p = %s\n",p); //NULL 第四次匹配,没有了返回NULL + + printf("原字符串:%s\n",temp); //被切割的字符串会因为切割操作而被修改(插入了\0) + * 读取出所有的分割段 + char temp[] = "ab-cd-ef-gh-ij"; + char delmi[] = "-"; + char *p = strtok(temp,delmi); + + while(p != NULL){ + printf("find:%s \n",p); + p = strtok(NULL,delmi); + } + /* + find:ab + find:cd + find:ef + find:gh + find:ij + */ + + # 把字符串转换为数字类型 + * 转换为整形: atoi(const char *nptr); + * 会跳过前面的空格,直到遇到数字或者正负号才开始转换,遇到非数字字符串或者\0就结束转换,并且返回转换结果 + + * 转换为浮点数: stof(); + * 转换为long: atol(); + + + # 字符串常量 + * 每个字符串都是一个首地址,这个地址是指字符串首元素地址 + printf("%s\n","Hello" + 1); //ello +1,首地址向后偏移1 + printf("%c\n",*("Hello")); //H 取出了首地址的字符 + + char *p = "123456789"; //字符串可以直接赋值给指针,因为字符串本身就是个指针 + + * 字符串常量放在内存的data区,文字常量区 + void func(char *p){ + printf("%p",p); + } + int main() { + printf("%p\n","Hello"); + func("Hello"); + /* + 00405067 + 00405067 + */ + return EXIT_SUCCESS; + } + + * 字符串常量,文字常量区的字符串,只读,不能修改 + char *p = "123456"; + *p = 'z'; + printf("%s\n",p); //error + + char *p = "123456"; + strcpy(p, "abc"); //error + + * 文字常量区的生命周期跟程序的生命周期一样,程序结束才释放内存 + + # 字符串常量初始化的问题 + //p 指针,保存了常量 "KevinBlandy" 的地址 + //常量不能修改 + char *p = "KevinBlandy"; + *p = "123"; //error + + //把"KevinBlandy"保存在buf数组中,内存位置非常量池,数组它是可以修改的 + char buf[] = "KevinBlandy"; + buf[0] = 'z'; //ok + +------------------------ +字符的输入/输出 | +------------------------ + # scanf 和 printf + char ch; + scanf("%c",&ch); + printf("%c",ch); + + char name[100]; + char buf[100] = {0}; + scanf("%s",buf); + printf("你是输入的是:%s\n",buf); + + * 默认以空格为分割,仅仅读取空格符前面的数据,后面的字符会被添加到缓冲区 + * 'scanf() 这种方式输入不会有越界检查,不安全' + * scanf 会自动对输入的字符添加 '\0' + + # getchar() / putchar() + * 专门用于处理单个字符的函数 + * 一次性的读取/输出单个字符,效率比较高,因为专门处理的就是字符 + char ch; + scanf("%c",&ch) + + char ch = '1'; + printf("%d",ch) + + + # 使用 gets / fgets 读取字符串 + gets(char *s) + * 已经废弃了,从标准输入读取字符,并保存到s指定的内存空间直到出现换行符或者文件结尾为止 + * 可能会发生数组越界的情况,因为不知道用户输入的内容大小,不建议使用 + + fgets(char *s,int size,FILE stream) + * 参数 + s: 字符串 + size:指定读取最大字符串的长度(默认 -1,不限制) + stram:文件指针,'如果读取键盘输入的字符串,固定为stdin' + * 从stream指定的文件内读入字符,保存到s指定的内存空间,直到出现换行字符,读取到文件结尾,或是已经读取了size - 1 个字符为止 + * 会自动在最后加上 '\0' 标识,会把换行符也一起读取进去 + * s最多只能装 length - 1个字符,因为必须要留一个给字符串结束符 '\0',如果输入内容大于了 size 或者 sizeof(s) 那么超出部分会被截断 + * 读取成功返回读取到的字符串,读取到文件末尾或者出错,返回 NULL + * 读取键盘输入demo + char buf[100]; + fgets(buf,sizeof(buf),stdin); + printf("你输入的是:%s\n",buf); + + fgetc( FILE * fp ); + * fgetc() 函数从 fp 所指向的输入文件中读取一个字符,返回值是读取的字符 + * 如果发生错误则返回 EOF,(-1) + + # 使用 puts / fputs 来输出字符串 + puts(const char *s) + * 标准设备输出s字符串,会自动添加换行符 \n + * 成功返回非负数,失败返回 -1 + + fputs(const char *str,FILE *stream) + * 把 str 字符写入到stream指定的文件中,字符串结束符 '\0' 不写入文件 + * 成功返回 0,失败返回 -1 + * 可以把stream替换为 stdout,使str被输出到屏幕 + + fputc( int c, FILE *stream ); + * 用于把单个字符写入stream指定的文件中 + * 果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF + + # 打印流 + fprintf(FILE *fp,const char *format, ...) + * 可以把格式化的内容,输出到指定的流 + * printf("Hello %s","Java") == fprintf(stdout,"Hello %s","Java") + + + # 从文件读取一行字符 + int fscanf(FILE *fp, const char *format, ...) + * 函数来从文件中读取字符串,但是在遇到第一个空格/换行字符时,它会停止读取 + * 可以从指定的流读取数据,填充模版,序列化到指定的缓冲区 + FILE *file = fopen("E:\\c-lang.txt","r"); + char buf[1024]; + fscanf(file,"%s",buf); //读取到第一行,存入buf + + + +---------------------------- +常用的字符串函数 | +---------------------------- + # 去除字符串两边的空格 + void trim(char *p,char *dst){ + char *start = p; + char *end = p + (strlen(p) - 1); + while((*start == ' ' || *start == ' ' || *start == '\n') && start != '\0'){ + start++; + } + while((*end == ' ' || *end == ' ' || *end == '\n') && start != end){ + end --; + } + *(end + 1) = '\0'; + strcpy(dst, start); + //strcpy(p,start); 可以在原字符串上修改,但是原字符串不能是字符串常量 + } + + void trim(char *str){ + char *start = str; + char *end = str + (strlen(str) - 1); + while((*start == ' ' || *start == '\n' || *start == ' ') && start < end){ + start ++; + } + while((*end == ' ' || *end == '\n' || *end == ' ') && end > start){ + end --; + } + *(end + 1) = '\0'; + strcpy(str, start); + } + + # 反转字符串 + void reversal(char *p) { + int start = 0; + int end = strlen(p) - 1; + while (start < end) { + p[start] = p[start] ^ p[end]; + p[end] = p[start] ^ p[end]; + p[start] = p[start] ^ p[end]; + + start++; + end--; + } + } + # 检索字符串中,子串出现的次数 + int count(const char *p,const char *s){ + char *temp = strstr(p,s); + int count = 0; + while(temp != NULL){ + p = temp + strlen(s); + temp = strstr(p,s); + count ++; + } + return count; + } + + int count(char *p, char *sub) { + int count = 0; + char *index = strstr(p, sub); + int position = strlen(sub); + while (index != NULL) { + index = strstr(index + position, sub); + count++; + } + return count; + } \ No newline at end of file diff --git "a/C/c-\345\257\204\345\255\230\345\231\250.c" "b/C/c-\345\257\204\345\255\230\345\231\250.c" new file mode 100644 index 00000000..ff1a510c --- /dev/null +++ "b/C/c-\345\257\204\345\255\230\345\231\250.c" @@ -0,0 +1,11 @@ +---------------------------- +c 寄存器 | +---------------------------- + # 寄存器是CPU内部的基本存储单元 + * 32/64 位系统,表示的就是CPU寄存器的大小(bit) + + # CPU缓存 + * 是寄存器的缓存 + * 它介于 寄存器 和 内存之间 + + diff --git "a/C/c-\345\265\214\345\245\227\346\261\207\347\274\226.c" "b/C/c-\345\265\214\345\245\227\346\261\207\347\274\226.c" new file mode 100644 index 00000000..9da5b0b7 --- /dev/null +++ "b/C/c-\345\265\214\345\245\227\346\261\207\347\274\226.c" @@ -0,0 +1,23 @@ +----------------------------- +汇编 | +----------------------------- + + #include + #include + + int main(void){ + + int a; + int b; + int c; + + __asm { + mov a,3; //3的值放在a对应内存的位置 + mov b,4; //4的值放在b对应内存的位置 + mov eax,a; //把a内存的值放在eax寄存器 + add eax,b; //把eax和b相加,结果放在eax + mov c,eax; //把eax的值放在c中 + } + printf("%d\n",c); + return EXIT_SUCCESS; + } diff --git "a/C/c-\345\276\201\351\200\224.c" "b/C/c-\345\276\201\351\200\224.c" new file mode 100644 index 00000000..ce36e45d --- /dev/null +++ "b/C/c-\345\276\201\351\200\224.c" @@ -0,0 +1,3 @@ +----------------------------- +c-征途 | +----------------------------- diff --git "a/C/c-\346\214\207\351\222\210.c" "b/C/c-\346\214\207\351\222\210.c" new file mode 100644 index 00000000..4925d3a9 --- /dev/null +++ "b/C/c-\346\214\207\351\222\210.c" @@ -0,0 +1,257 @@ +-------------------------------- +指针 | +-------------------------------- + # 指针是一个变量,其值为另一个变量的地址 + # 指针变量的声明 + [合法的C语言类型] *变量名称 + + int *ip; /* 一个整型的指针 */ + double *dp; /* 一个 double 型的指针 */ + float *fp; /* 一个浮点型的指针 */ + char *ch; /* 一个字符型的指针 */ + + # 所有指针的值的实际数据类型,不管是整型,浮点型,字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数 + * 不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同 + * 32位编译器,用32位大小保存地址(4字节) + * 64位编译器,用64位大小保存地址(8字节) + + printf("%d",sizeof(int *)); //4 + + + # 指针的简单使用 + int number = 10; + int *pointer = &number; + printf("number的内存地址是:%p\n",pointer); + printf("通过指针访问值是:%d\n",*pointer); + + # 野指针 + * 值栈保存了一个毫无意义的地址(非法的地址) + int *p; + p = 0x00000FFF; + printf("%p",p); //00000FFF + + * 只有定义了变量后,此变量的地址才是合法地址 + * 操作野指针本身不会有任何问题 + * 操作野指针指向的内存,这种直接操作系统未授权的内存,是非法的,就会有问题 + + # NULL 指针 + * 赋为 NULL 值的指针被称为空指针 + * 它可以尽量的避免野指针 + * NULL 指针是一个定义在标准库中的值为零的常量: #define NULL((void *)0) + int *pointer = NULL; + printf("pointer 的地址是 %p\n", pointer); //00000000 + + * 在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的 + * 然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置,但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西 + if(pointer) /* 如果 p 非空,则完成 */ + if(!pointer) /* 如果 p 为空,则完成 */ + + # 还可以通过[]操作指针 + * 下标只能是 0,越界的话会出现不可预料的错误 + int x = 10; + int *p = &x; + + //p[0] 等同于 *p 等同于 *p(p + 0) + printf("%d\n",p[0]); //10 + + //p[0] 操作的是指针p指向的内存 + p[0] = 250; + printf("%d\n",*p); //250 + + * 如果是 p[1],其实就是等于 *(p + 1),属于指针运算了 + + # 万能指针 + * 也就是 void 指针 + * void *p; 可以指向任何类型的变量 + * 在使用指针指向的内存时,最好强制转换为其当前的指针类型 + * 因为 void 指针指向的数据类型不确定,在使用时,通过强制转换来确定最终的数据类型从而确定内存大小 + void *p = NULL; + int x = 10; + p = &x; + + //强制转换后读取内存 + printf("%d\n",*((int *)p)); //10 + + //强制转换后写入内存 + * ((int *)p) = 20; + printf("%d\n",x); //20 + * 按照 ANSI(American National Standards Institute) 标准,不能对 void 指针进行算法操作( + , -) + + # 指针步长 + * 其实就是指针的运算,可以通过运算符( +,- )来操作指针 + * 指针在执行运算的步长,由指针指向的数据类型决定 + int x = 0; + int *p = &x; + printf("p = %p\n",p); //p = 0061FF04 + + p = p + 1; //+1操作,当前类型是int,所以内存地址会 +4 + printf("p = %p\n",p); //p = 0061FF08 + + * 通过指针来排序数组 + void sort(int *p,int len){ + for(int x = 0 ;x < len ; x++){ + for(int y = x ; y < len ; y++){ + int *_x = p + x; + int *_y = p + y; + if(*_x < *_y){ + *_x = *_x ^ *_y; + *_y = *_x ^ *_y; + *_x = *_x ^ *_y; + } + } + } + } + + # const 修饰的指针 + * const 修饰 * ,只能对指向的内存进行读操作(const 在*号前面) + int x = 0; + const int *p; + p = &x; + *p = 100; // :assignment of read-only location + + * const 修饰指针变量,代表指针变量值为制度(const 在*号后面) + int x = 0; + int * const p = &x; + p = 0xFFFF; //assignment of read-only variable + + * const 修饰指针变量 以及 *,啥都改不了(俩 const 在 * 前后) + int x = 0; + int const * const p = &x; + p = 0xFFFF; //assignment of read-only variable + *p = 15; //assignment of read-only variable + + # 只要是合法的内存地址,都可以使用强制转换 + int x = 1; + printf("p = %p\n",&x); //获取变量x的地址:0061FF2C + * ((int *)0x0061FF2C) = 9; //强制把地址数据0061FF2C,转换为地址,并且操作该地址的内存 + printf("x = %d\n",x); + + # 字符串指针 + //打印数组 + char buf[] = "Hello Java"; + printf("%s %p %p %c\n",buf,buf,&buf[0],*buf); //Hello Java 0028FF2D 0028FF2D H + + //以指针的方式打印 + int i = 0; + while(buf[i] != '\0'){ + putchar(buf[i]); //Hello Java + i ++; + } + + printf("\n"); + + //以指针方式打印2 + int x = 0; + while(*(buf + x) != '\0'){ + putchar(*(buf + x)); //Hello Java + x++; + } + + +-------------------------------- +多级指针 | +-------------------------------- + # 指向指针的指针 + int x = 15; + int *p1 = &x; //p1 -> x + int **p2 = &p1; //p2 -> p1 + int ***p3 = &p2; //p3 -> p2 + printf("x=%d",***p3); //15 + + # 多级指针与指针数组 + int *arr[10]; + int **p = arr; + + - int *arr[10],表示是一个 int 指针的数组 + - 首元素其实就是个二级指针 ,首元素本身是个指针,指向数组第一个元素,而数组的第一个元素也是指针,指向了 int 变量 + + # 多级指针在函数中的使用 + //**p 表示参数是一个2级指针,因为数组传递进来是一个指针,而第一个元素也是指针,所以就成了指针指向的指针 + void func(char **p,int len){ + for(int x = 0 ;x < len ;x++){ + //p[x] 返回的也是一个指针 + printf("%s\n",p[x]); + } + } + + //另一种声明方式,表 *p 是一个指针数组。其实本质上也是二级指针 + void func(char *p[],int len){ + for(int x = 0 ;x < len ;x++){ + //p[x] 返回的也是一个指针 + printf("%s\n",p[x]); + } + } + + int main(int argc,char *argv[]) { + //p是一个指针,p里面的元素也是一个指针,也就是2级指针 + char *p[] = {"a","b","c"}; + func(p,3); + return EXIT_SUCCESS; + } + + + +-------------------------------- +指针数组 | +-------------------------------- + # 专门保存指针的数组 + int arr[5] = {1,2,3,4,5}; + int *ps[5]; + for(int x = 0 ; x < 5;x++ ){ + ps[x] = &arr[x]; + } + for(int x = 0 ; x < 5;x++ ){ + printf("%d",*ps[x]); //12345 + } + + +-------------------------------- +指针函数 | +-------------------------------- + # 指向函数的指针 + #include + #include + + //定义函数 + int max(int a,int b){ + return a > b ? a : b; + } + + int main(void){ + + //m 就是函数 max 的指针 + int (* m)(int,int) = &max; + + int a,b,c; + + scanf("%d %d %d",&a,&b,&c); + + //等同于 max(max(a,b),c) + int r = m(m(a,b),c); + + printf("%d\n",r); + } + + * 定义方法 + [返回值类型] (* 指针变量)(形参类型) + int (* pointer)(void) + int (* pointer)(int,int) + void (* pointer)(void) + + + # 回调函数 + * 函数指针作为某个函数的参数 + * 函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数 + + #include + #include + + void foo(int value,int (*printf)(const char *, ...)){ + printf("%d\n",value); + } + + int main(int argc, char **argv) { + foo(15,&printf); //15 + return EXIT_SUCCESS; + } + \ No newline at end of file diff --git "a/C/c-\346\225\260\347\273\204.c" "b/C/c-\346\225\260\347\273\204.c" new file mode 100644 index 00000000..c3dad84e --- /dev/null +++ "b/C/c-\346\225\260\347\273\204.c" @@ -0,0 +1,292 @@ +-------------------------------- +数组 | +-------------------------------- + # 存储一系列相同的值 + * 数组在内存中是连续存储的,所有成员在内存中的地址是连续的 + + # 数组属于构造数据类型 + * 一个数组可以分解为多个数组,这些数组元素可以是基本数据类型或者构造类型 + * 按照数据元素的不同,数组可以分为:数值数组,字符数组,指针数组,结构数组等类别 + + # 使用下标来访问数据的元素,下标从0开始 + * 使用数组最好不要越界 + * 编译器不会去检查数组下标是否正确 + # 数组定义 + int nums[10]; + + * 如果使用变量来定义数组长度,那么尽量使用常量(部分编译器差异会导致编译失败) + int len = 10; + int arr[len]; //在不同的编译环境可能会编译失败 + * 使用 #define 或者 const 都可以 + + # 可以使用表达式,常量,变量来初始化数组成员 + int arr[10]; + arr[0] = 1; + arr[1] = 1 + 1; + int num = 4; + arr[2] = num; + + # 数组的初始化 + * 直接初始化所有值 + int arr[5] = {1,2,3,4,5}; + + * 部分初始化,未初始化部分为0 + int arr[5] = {1,2,3} //角标3,4为0 + + * 数组全部初始化为0(可以省略括号中的0) + int arr[5] = {0} + + * 如果直接初始化所有值,那么可以不用声明数组长度 + int arr[] = {1,2,3,4,5} + + * 指定初始化,在{},可以使用[index]来初始化指定角标的值 + int arr[3] = { [0]=0, 1, [2]=2 }; + + int arr[] = {1,[99]=10}; + * 这个数组会有100元素,编译器会把数组的大小初始化为足够装下初始化的值 + + * 局部未初始化的数组,默认值是随机数 + * 直接初始化时,如果成员数量与数组长度不一致会异常 + + # 数组名,就是首元素的地址 + //数组 + int arr[10] = {1,2,3,4,5,6,7,8,9,10}; + //指向首元素的指针 + int *p = NULL; + //首元素地址 + p = arr; //p = &arr[0]; + for(int x = 0 ;x < 10 ;x++){ + //arr[x] = *(p + x) = *(arr + x) + printf("%d %d %d\n",arr[x],*(p + x),*(arr + x)); + } + + + # 数组名称是常量,不能修改 + int arr[4]; + arr = 10; //error + + * 有点类似于 : int const * p; + + + # 计算数组长度 + //5个长度的int类型数组 + int arr[5] = {1,2,3,4,5}; + //计算出int的字节大小 + size_t int_size = sizeof(int); + //计算出数组的字节大小 + size_t arr_size = sizeof(arr); + //总大小 / 元素的大小,获得数组的长度 + int arr_length = arr_size / int_size; + + printf("int大小=%d,数组大小=%lu,数组长度=%d\n",int_size,arr_size,arr_length);//int大小=4,数组大小=20,数组长度=5 + + printf("数组长度=%d\n",sizeof(arr) / sizeof(arr[0])); //数组长度=5 + + # 数组与指针的区别 + * 数组的 sizeof 运算返回的是数组占用内存的大小,指针的运算返回的是指针的大小 + * 数组的[x]操作相当于指针 *(p + x) 操作 + * 通过不断改变指针指向来遍历数据,比直接修改数组索引遍历满 + * 因为不断改变指针的值,需要指针重新运算,不断的修改内存 + + * 指针可以进行 + / - 运算,数组不能 + * 数组是常量,指针是变量(除了被 const 修饰的) + + # arr 和 &arr + int arr[] = { 1, 2, 3, 4, 5 }; //sizeof(arr) = 20 + printf("%p %p\n", arr, &arr); //0x0028FF2C 0x0028FF2C + printf("%p\n", arr + 1); // + 4 = 0x0028FF30 + printf("%p\n", &arr + 1); // + 20 = 0x0028FF40 + //arr[x] = *(arr + x) + + * &arr 表示了一个数组的指针,它的+-操作都会以数组的大小为单位进行 + * arr 表示执行了数组头元素的指针,它的 +- 操作都会以数组元素的大小为单位进行 + +-------------------------------- +二维数组 | +-------------------------------- + # 二维数组的定义 + + int arr[2][4]; //2个元素的数组,里面每个元素都是4个长度的数组 + + * 内存是没有多维的这种概念,多维数组其实就是特殊的一维数组 + * 特殊的一维数组里面的元素,又是数组元素 + + # 二维数组的初始化 + * 直接初始化 + int arr[2][3] = {{1,2,3},{4,5,6}}; + + * 没有被初始化的元素为0 + int arr[][2] = {{1}} //只有arr[0][0] = 1,其它的都是 0 + + + * 匹配模式的初始化 + int arr[2][3] = {1,2,3,4,5,6} + + * 先把123放入了第0个元素的0,1,2角标,再把456放入了第1个元素的0,1,2角标 + * 反正就是挨着放,结果跟 {{1,2,3},{4,5,6}} 是一样的,就是省略了里面的大括号而已 + + * 初始化所有元素 + int arr[2][3] = {0} + + * 所有元素都初始化为0 + + * 如果是直接初始化的方式(上面两张方式),那么第一个[](一维数组)可以不用手动的声明元素个数 + int arr[][3] = {1,2,3,4,5,6} + + * 一共有6个元素,每个2维数组的长度是3 , 6 / 3 = 2,所以就计算出了一维数组的长度 + + * 没有初始化的元素为0 + int arr[][3] = {1,2,3,4,5}; + [0][0]=1 + [0][1]=2 + [0][2]=3 + [1][0]=4 + [1][1]=5 + [1][2]=0 //没有初始化的元素为0 + + + # 数组是常量,不能修改 + int arr[5][10]; + arr = 10; //error + + # 二维数组的长度计算 + * 原理分析 + int arr[5][10]; + sizeof(arr) //10 * 5 * 4 = 200 + sizeof(arr[0]); //10 * 4 = 40; + sizeof(arr[0][0]); //4 + + * 计算代码 + int arr[][5] = {{1,2,3},{4,5,6}}; + + //1,先计算出单个元素的字节大小 + int item_size = sizeof(arr[0][0]); + //2,计算出二维数组的大小 + int two_dimensional_size = sizeof(arr[0]); + //3,计算出一维数组的大小 + int one_dimensional_size = sizeof(arr); + + //二维数组的长度 = 二维数组的大小 / 单个元素的大小 + int two_dimensional_length = two_dimensional_size / item_size; + //一维数组的长度 = 一维数组的大小 / 二维数组的大小 + int one_dimensional_length = one_dimensional_size / two_dimensional_size; + + printf("一维数组长度:%d,二维数组长度:%d",one_dimensional_length,two_dimensional_length); + + return EXIT_SUCCESS; + + # 数组名,就是首元素[0][0]的地址 + int arr[][5] = {{1,2,3},{4,5,6}}; + printf("%p,%p",arr,&arr[0][0]); //0061FF08,0061FF08 + + + # 二维数组与指针 + int (*p)[2]; + * p是一个 int 指针,指向了一个具有2个 int 元素的数组 -> int arr[1][2] -> [[1,2]] + + int (*p)[2][2] + * p是一个 int 指针,执行了一个具有2个 int[2] 元素的数组 -> int arr[1][2][2] -> [[[1,2],[3,4]]] + + int *p[2]; + * p是一个数组,里面有俩元素,都是 int 类型的指针 + +-------------------------------- +传递数据给函数 | +-------------------------------- + # 想要在函数中传递一个一维数组作为参数,必须以下面三种方式来声明函数形式参数 + # 这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针 + # 同样地,也可以传递一个多维数组作为形式参数 + + # 方式 1 + * 形式参数是一个指针 + void func(int *param){ + } + + # 方式 2 + * 形式参数是一个已定义大小的数组 + void myFunction(int param[10]){ + } + + # 方式 3 + * 形式参数是一个未定义大小的数组: + + void myFunction(int param[]){ + } + +-------------------------------- +匿名函数 | +-------------------------------- + # 也叫做复合字面量 + # 字面量 + (int[6]) { 1, 2, 3, 4, 5, 6 } + + * 6可以省略,编译器会自动推算 + + # 字面量可以赋值给指针 + int *p; + p = (int[6]) { 1, 2, 3, 4, 5, 6 }; + + + # 一维数组 + void print_arr(int *, size_t); + + int main(int argc, char **argv) { + print_arr((int[6] ) { 1, 2, 3, 4, 5, 6 } , 6); + return EXIT_SUCCESS; + } + + void print_arr(int *p, size_t size) { + while (size > 0) { + printf("%d\n", p[--size]); + } + } + + # 二维数组 + void print_arr(int arr[][2], size_t); + + int main(int argc, char **argv) { + int (*p)[2]; + + p = (int[][2] ) { { 1, 2 }, { 3, 4 } }; //多维数组的字面量,需要声明第二纬数组的长度,因为实质上它是一个指针,需要二维长度来计算指针的运算 + + print_arr(p, 2); + + return EXIT_SUCCESS; + } + + void print_arr(int arr[][2], size_t size) { + for (int x = 0; x < size; x++) { + for (int y = 0; y < 2; y++) { + printf("%d\n", arr[x][y]); + } + } + } + +-------------------------------- +通过 typedef 定义数组 | +-------------------------------- + + # 定义一个数组 + typedef int (Array)[5]; //定义一个数组,长度为5 + + Array in; + in[0] = 1; + printf("%d\n", in[0]); //1 + + # 定义数组指针 + typedef int (*IntArray)[2]; //执行一个数组的指针 + + int arr[][2] = { { 1, 2 } }; + + IntArray intArray = arr; + + intArray[0][1] = 15; + printf("%d\n", arr[0][1]); //15 + + + +-------------------------------- +vla变长数组 | +-------------------------------- + ... 玄学的东西,很多编译器不支持 + diff --git "a/C/c-\346\226\207\344\273\266\350\257\273\345\206\231.c" "b/C/c-\346\226\207\344\273\266\350\257\273\345\206\231.c" new file mode 100644 index 00000000..d58b0e1d --- /dev/null +++ "b/C/c-\346\226\207\344\273\266\350\257\273\345\206\231.c" @@ -0,0 +1,437 @@ +---------------------------- +文件编程 | +---------------------------- + # 文件的类型 + * 设备文件,设备也可以当做文件,具备io + * 磁盘文件 + + # 打开文件 + FILE *fopen( const char * filename, const char * mode ); + + * 打开一个文件,name指定文件地址,model指定打开的类型 + * model的枚举字符串 + r 打开一个已有的文本文件,允许读取文件 + + w 打开一个文本文件,允许写入文件,如果文件不存在,则会创建一个新文件 + 在这里,程序会从文件的开头写入内容,如果文件存在,则该会被截断为零长度,重新写入 + + a 打开一个文本文件,以追加模式写入文件,如果文件不存在,则会创建一个新文件 + 在这里,程序会在已有的文件内容中追加内容 + + r+ 打开一个文本文件,允许读写文件 + + w+ 打开一个文本文件,允许读写文件,如果文件已存在,则文件会被截断为零长度 + 如果文件不存在,则会创建一个新文件 + + a+ 打开一个文本文件,允许读写文件,如果文件不存在,则会创建一个新文件,读取会从文件的开头开始,写入则只能是追加模式 + + * 处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式: + "rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b" + + * 如果打开失败,返回 NULL + FILE *file = fopen("d.txt","r"); + if (file == NULL){ + perror("fopen"); //fopen: No such file or directory + return EXIT_FAILURE; + } + * 注意,该结构体是在堆空间分配的,使用完成后要释放 + * 文件的打开,可以是相对路径,也可以是绝对路径 + fopen("./d.txt","r"); + + + # 关闭文件 + int fclose (FILE *); + * 关闭文件会刷出缓冲区 + * 成功返回 0,失败返回 -1 + + # 刷出缓冲区 + int fflush (FILE *); + * 成功返回 0,失败返回 -1 + + # 重定向流到文件 + FILE * freopen (const char *filename, const char *mode, FILE *stream); + * filename 需要重定向到的文件名或文件路径 + * mode 代表文件访问权限的字符串,例如,"r"表示"只读访问","w"表示"只写访问","a"表示"追加写入" + * stream 需要被重定向的文件流 + * 如果成功,则返回该指向该输出流的文件指针,否则返回为NULL + if(freopen("D:\\output.txt", "w", stdout) == NULL){ + fprintf(stderr,"error redirecting stdout\n"); + } + /* this output will go to a file */ + printf("This will go into a file.\n"); + /*close the standard output stream*/ + fclose(stdout); + + # 设置文件的缓冲区 + int setvbuf (FILE *file, char *buf, int model, size_t size); + * 设置文件的缓冲区,设置成功返回0 + * file 文件 + * buf 缓冲区指针 + * model 枚举值,缓冲器的模式 + _IOFBF 0x0000 /* 完全缓冲,在缓冲区满的时候刷出 */ + _IOLBF 0x0040 /* 行缓冲,在缓冲区满或者写入一个换行符时刷出 */ + _IONBF 0x0004 /* 无缓冲 */ + * size 缓冲区的大小 + + + # FILE 结构体的属性 + typedef struct _iobuf + { + char *_ptr; + int _cnt; + char *_base; + int _flag; + int _file; + int _charbuf; + int _bufsiz; + char *_tmpfname; + } FILE; + + typedef struct + { + short level; //缓冲区状态,满或者空 + unsigned flags; //文件状态标识 + char fd; //文件描述符 + unsigned char hold; //如无缓冲区不读取字符 + short bsize; //缓冲区大小 + unsigned char *buffer; //缓冲区数据位置 + unsigned ar; //指针,当前的指向 + unsigned istemp; //临时文件,指示器 + short token; //用于有效性的检查 + } FILE; + + # 把指定的字符放回流中 + int ungetc (int, FILE *) + * 感觉没啥用... + ch = getchar(); + ungetc(cg,stdin); + + # 异常判断 + int ferror (FILE *); + * 如果文件IO发生了异常,该函数返回非0,否则返回0 + + # 文件结尾判断 + * 文本文件的末尾会有一个隐藏的-1(EOF),表示文件已经结束 + * 二进制文件末尾没有-1标识,因为-1可能是文件中的数据,同过 -1 来判断不靠谱 + * 可以通过 feof() 来判断文件是否读取到了末尾,不论是二进制文件还是文本文件 + * feof(); 返回 bool + FILE *file = fopen("c.txt","r"); + + bool end = feof(file); + printf("%d",end); //0 + + * 它其实是判断你读取后的数据,是不是末尾标识,也就是说要先读取了末尾标识,所以,一般先读取,再判断 + fgetc(file); + if(feof(file)){ + break; + } + .... + +---------------------------- +文本文件的读写 | +---------------------------- + # 读取/写入单个字符 + fgetc( FILE * fp ); + * fgetc() 函数从 fp 所指向的输入文件中读取一个字符,返回值是读取的字符 + * 如果发生错误则返回 EOF,(-1) + + fputc( int c, FILE *stream ); + * 用于把单个字符写入stream指定的文件中 + * 果写入成功,它会返回写入的字符,如果发生错误,或者读取到了末尾,则会返回 EOF(-1) + + # 读取/写入字符串 + fgets(char *s,int size,FILE stream) + * 参数 + s: 字符串 + size:指定读取最大字符串的长度(默认 -1,不限制) + stram:文件指针,'如果读取键盘输入的字符串,固定为stdin' + * 从stream指定的文件内读入字符,保存到s指定的内存空间,直到出现换行字符,读取到文件结尾,或是已经读取了size - 1 个字符为止 + * 会自动在最后加上 '\0' 标识,会把换行符也一起读取进去 + * s最多只能装 length - 1个字符,因为必须要留一个给字符串结束符 '\0',如果输入内容大于了 size 或者 sizeof(s) 那么超出部分会被截断 + * 读取成功返回读取到的字符串,读取到文件末尾或者出错,返回 NULL + * 读取文件 + FILE *file = fopen("E:\\c-lang.txt","r"); + char buf[1024]; + char *r = fgets(buf,sizeof(buf) - 1,file); + while(r != NULL){ + printf("%s",buf); + r = fgets(buf,sizeof(buf) - 1,file); + } + + fputs(const char *str,FILE *stream) + * 把 str 字符写入到stream指定的文件中,字符串结束符 '\0' 不写入文件 + * 成功返回 0,失败返回 EOF(-1) + * 可以把stream替换为 stdout,使str被输出到屏幕 + + + + # 格式化流输出 fprintf(FILE *fp,const char *format, ...) + * 源是变量,目标是文件 + + FILE *file = fopen("E:\\c-lang.txt","w+"); + fprintf(file,"Hello KevinBlany ,Im %s","Java"); + fclose(file); + + + # 格式化流输入 int fscanf(FILE *fp, const char *format, ...) + * 源是文件,目标是变量 + * 函数来从文件中读取字符串,但是在遇到第一个空格字符时,它会停止读取 + + FILE *file = fopen("E:\\c-lang.txt","r"); + int x,y,z; + fscanf(file,"%d %d %d",&z,&y,&z); + printf("z=%d,y=%d,z=%d",z,y,z); //z=36,y=12,z=36 + fclose(file); + + + +---------------------------- +随机io | +---------------------------- + int fseek (FILE *file, long offset, int whence); + * fseek 设置当前读写点到 offset 处 + * 如果操作成功返回0,操作失败返回-1(移动的范围超过了文件大小) + * whence 可以是 SEEK_SET,SEEK_CUR,SEEK_END 这些值决定是从文件头,当前点,文件尾 计算偏移量 offset + * 相对当前位置往后移动一个字节:fseek(fp,1,SEEK_CUR); + * 往前移动一个字节.直接改为负值就可以:fseek(fp,-1,SEEK_CUR) + + FILE *file = fopen("E:\\c-lang.txt","r+"); + //指针移动到第10个字节 + fseek(file,10,SEEK_SET); + //把第10个字节替换为'A' + if(fputc('A',file) == EOF){ + printf("异常"); + } + fclose(file); + + * 只有用 r+ 模式打开文件才能插入内容,w 或 w+ 模式都会清空掉原来文件的内容再来写 + * a 或 a+ 模式即总会在文件最尾添加内容,哪怕用 fseek() 移动了文件指针位置 + + long ftell(FILE *file); + * 获取文件光标的位置 + * 如果失败返回 -1 + + void rewind(FILE *file); + * 把文件光标移动到文件开头(重置指针位置) + + # 通过随机io获取文件的大小 + //打开文件 + FILE *file = fopen("D:\\springboot.sql","r+"); + //移动指针到末尾 + fseek(file,0,SEEK_END); + //获取文件的光标位置,其实就是大小(KB) + long size = ftell(file); + printf("size=%ld",size); + + # fgetpos(); 和 fsetpos(); + int fgetpos (FILE *, fpos_t *); + * 获取文件指针位置,注意,这里需要给变量的指针,是通过指针赋值的 + int fsetpos (FILE *, const fpos_t *); + * 设置文件指针位置,注意,这里需要给变量的指针,是通过指针读取变量的 + + * fseek() 和 fetll() 的问题是,他们都使用 long 这个数据类型,最多表示20亿个字节 + * 但是现在的文件越来越大,可能超过了 20 亿 + * 新的自定义数据类型: fpos_t == typedef long long fpos_t; + * 如果执行成功返回0,失败返回-1 + +---------------------------- +二进制文件的读写 | +---------------------------- + + # 读取二进制文件 + * size_t fread(void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file); + * ptr 存放读取数据的内存空间 + * size_of_elements 指定读取文件内容的块数据大小(unsigned int) + * number_of_elements 读取文件的块数,读取文件数据总大小为: size_of_elements * number_of_elements + * a_file 已经打开的文件指针 + * 成功返回,number_of_elements(读取到的文件块数),如果该值比number_of_elements小,但是大于0,则表示读取到了文件末尾 + * 如果用户指定读取的块儿大小,大于了文件可读的大小,返回可能为0 + * 读取1块儿(一块儿设置为了10个字节) + * 但是文件只有5个字节了。只能读取0.5块儿。返回0.5,因为结果是int。被类型转换最终为 0 + * '建议块大小永远设置为1,则不会出现该问题' + * 失败,返回0 + + # 写入二进制数据 + * size_t fwrite(const void *ptr, size_t size_of_elements,size_t number_of_elements, FILE *a_file); + * ptr 准备写入文件的数据 + * size_of_elements 指定写入文件内容的块数据大小(unsigned int) + * number_of_elements 写入文件的块数,写入文件的总大小 = size_of_elements * number_of_elements + * a_file 已经打开的文件指针 + * 成功返回,number_of_elements(成功写入文件数据的块数目),失败返回 0 + + + + + # 这两个函数都是用于存储块的读写 (写入和读取的通常是数组或结构体) + + # 简单的copy + FILE *source = fopen("D:\\20181009153347.jpg","rb"); + FILE *target = fopen("D:\\cp.jpg","wb"); + + char buf[1024]; + + int result = fread(buf,1,1024,source); + while(result > 0){ + printf("读取到了:%d\n",result); + fwrite(buf,1,result,target); + fflush(target); + result = fread(buf,1,1024,source); + } + + fclose(target); + fclose(source); + + # struct 的读写 + struct Lang { + unsigned int id; + char name[10]; + }; + struct Lang lang = {2,"Python"}; + + FILE *file = fopen("D:\\lang.d","wb"); + fwrite(&lang,sizeof(struct Lang),1,file); + fflush(file); + fclose(file); + + file = fopen("D:\\lang.d","rb"); + fread(&lang,sizeof(struct Lang),1,file); + printf("lang=%d,name=%s\n",lang.id,lang.name); + + # struct 数组的读写 + struct Lang { + unsigned int id; + char name[11]; + }; + + struct Lang langs[] = { + {1,"Java"}, + {2,"Python"}, + {3,"C"}, + {4,"Javascript"} + }; + + FILE *file = fopen("D:\\langs.d","wb"); + fwrite(langs,sizeof(langs),1,file); + fflush(file); + fclose(file); + + file = fopen("D:\\langs.d","rb"); + fread(langs,sizeof(langs),1,file); + for(int x = 0 ;x < 4 ; x++){ + printf("id=%d,name=%s\n",langs[x].id,langs[x].name); + } + + +---------------------------- +Linux与Window下的文件区别 | +---------------------------- + # fopen("c.txt","rb") 第二个参数中的 "b" + * "b"表示以2进制模式打开文件,其实在Linux系统下,使用该函数不用加"b",也是可以的 + fopen("c.txt","r"); //在Linux下同样是以二进制的形式打开文件 + * 为了系统兼容,一般还是加一个"r"比较好 + + # Unix和Linux下的所有文本文件都是以:"\n"结尾,而Windows下的文本文件以:"\r\n"结尾 + # 在Windows平台下,读取文本文件的时候,系统会把所有的"\r\n"转换为"\n" + # 在Windows平台下,写入文本文件的时候,系统会把所有的"\n"转换为"\r\n" + # 判断文件是Linux文件还是Windows文件 + +---------------------------- +删除与重命名 | +---------------------------- + # 删除文件 + int remove (const char file*); + * 成功返回0,失败返回-1 + + # 重命名 + int rename (const char *old, const char *new_); + * 成功返回0,失败返回-1 + +---------------------------- +获取文件的属性 | +---------------------------- + # 通过 下的 int stat (const char *path, struct stat *); 函数 + * 获取文件的属性,成功返回0,失败返回-1 + * path 文件的路径,stat 保存状态的结构体 + * 结构体的定义 + { _dev_t st_dev; /* Equivalent to drive number 0=A 1=B ... */ + _ino_t st_ino; /* Always zero ? */ + _mode_t st_mode; /* See above constants */ + short st_nlink; /* Number of links. */ + short st_uid; /* User: Maybe significant on NT ? */ + short st_gid; /* Group: Ditto */ + _dev_t st_rdev; /* Seems useless (not even filled in) */ + __st_off_t st_size; /* File size in bytes */ + __st_time_t st_atime; /* Access time (always 00:00 on FAT) */ + __st_time_t st_mtime; /* Modified time */ + __st_time_t st_ctime; /* Creation time */ + } + + # 通过该方法获取文件的大小 + #include + #include + #include + + int main(int argc, char **argv) { + struct stat st; + int result = stat("D:\\springboot.sql",&st); + printf("size=%d\n",st.st_size); + return EXIT_SUCCESS; + } + + # 通过 'st_mode' 属性可以获取到更多关于文件的信息 + * 以下判断函数都是 宏定义的,用于判断,返回 bool + S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) //是否是目录 + S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) + S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) + S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) + S_ISREG(m) (((m) & S_IFMT) == S_IFREG) //是否是普通文件 + + * demo + S_ISDIR(fileStat.st_mode); //是否是一个目录 + S_ISREG(fileStat.st_mode) //是否是一个普通文件 + +----------------------------- +操作目录(文件夹) | +---------------------------- +# + # 打开一个目录,返回一个目录指针 typedef struct __dirstream_t DIR; + DIR * opendir (const char *__dirname) + + # 读取目录信息,参数是一个目录指针,返回 struct dirent + struct dirent* readdir (DIR *__dir) + struct dirent + { + long d_ino; /* Always zero. */ + unsigned short d_reclen; /* Always sizeof struct dirent. */ + unsigned short d_namlen; /* 文件/文件夹名称的字符长度 */ + unsigned d_type; /* File attributes */ + char d_name[FILENAME_MAX]; /* 文件/文件夹名称 */ + }; + + * 它应该循环调用 + * 因为每次调用,它会逐渐遍历出目录指针下的所有文件/目录信息,如果遍历到末尾,返回 NULL + * 遍历某个目录下的所有文件 + DIR *dir = opendir("E:\\letsencrypt"); + struct dirent *dirent = NULL; + + while ((dirent = readdir(dir)) != NULL) { + printf("%s\n", dirent->d_name); + } + +# + # 创建一个文件夹 + int mkdir (const char *); + * 创建成功返回0,否则返回-1(目录已经存在) + + # 获取当前执行程序所在的目录 + char *getcwd (char *buf, int size); + + * 把当前路径写入buf,最大长度为size + char buf[1024] = { 0 }; + getcwd(buf, 1024); + printf("%s", buf); //D:\workspace\clang-practice + +---------------------------- +关于文件缓冲区 | +---------------------------- + # ANSI C标准采用"缓冲区文件系统" diff --git "a/C/c-\346\236\232\344\270\276.c" "b/C/c-\346\236\232\344\270\276.c" new file mode 100644 index 00000000..5f36e2cd --- /dev/null +++ "b/C/c-\346\236\232\344\270\276.c" @@ -0,0 +1,87 @@ +----------------------------- +枚举 | +----------------------------- + # 枚举类型语法定义格式为 + enum DAY + { + MON=1, TUE, WED, THU, FRI, SAT, SUN + }; + + * 第一个枚举成员的'默认值为整型的 0',后续枚举成员的值在前一个成员上加 1 + * 在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推 + + # 可以在定义枚举类型时改变枚举元素的值 + enum season {spring, summer=3, autumn, winter}; + + * 有指定值的枚举元素,其值为前一元素加 1,也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5 + + # 枚举变量的定义 + * 前面只是声明了枚举类型,通过以下三种方式来定义枚举变量 + + 1,先定义枚举类型,再定义枚举变量 + enum DAY + { + MON=1, TUE, WED, THU, FRI, SAT, SUN + }; + enum DAY day; + + 2,定义枚举类型的同时定义枚举变量 + enum DAY + { + MON=1, TUE, WED, THU, FRI, SAT, SUN + } day; + + 3,省略枚举名称,直接定义枚举变量 + enum + { + MON=1, TUE, WED, THU, FRI, SAT, SUN + } day; + + # 枚举的遍历 + * 在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的,所以按照 C 语言规范是没有办法遍历枚举类型的 + * 不过在一些特殊的情况下,枚举类型必须连续是可以实现有条件的遍历 + #include + enum DAY + { + MON=1, TUE, WED, THU, FRI, SAT, SUN + } day; + int main() + { + // 遍历枚举元素 + for (day = MON; day <= SUN; day++) { + printf("枚举元素:%d \n", day); + } + } + * 枚举类型不连续,这种枚举无法遍历 + + # 枚举在 switch 中的使用 + enum color { red=1, green, blue }; + + enum color favorite_color; + + printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): "); + scanf("%d", &favorite_color); + + switch (favorite_color){ + case red: + printf("你喜欢的颜色是红色"); + break; + case green: + printf("你喜欢的颜色是绿色"); + break; + case blue: + printf("你喜欢的颜色是蓝色"); + break; + default: + printf("你没有选择你喜欢的颜色"); + } + + # 将整数转换为枚举 + enum Color { + RED, + BLUE, + YELLOW, + BLACK + }; + int a = 0; + enum Color color = (enum Color)a; \ No newline at end of file diff --git "a/C/c-\347\250\213\345\272\217\347\232\204\351\200\200\345\207\272.c" "b/C/c-\347\250\213\345\272\217\347\232\204\351\200\200\345\207\272.c" new file mode 100644 index 00000000..17f5a103 --- /dev/null +++ "b/C/c-\347\250\213\345\272\217\347\232\204\351\200\200\345\207\272.c" @@ -0,0 +1,35 @@ + +------------------- +exit | +------------------- + void exit (int) + * 退出程序 + * 它会执行 atexit() 添加的所有回调函数 + * 刷新所有输出流,关闭所有打开的流,关闭 tempfile() 创建的临时文件,把控制权返回主机环境,向主机环境报告终止状态 + +------------------- +atexit | +------------------- + int atexit (void (*)(void)); + + * 该函数参数是一个方法指针 + * 该方法可以多次调用,最少可以放32个回调函数 + * 在程序退出的时候,会调用该方法(最后添加的先执行) + + void f1(){ + printf("第一个执行\n"); + } + void f2(){ + printf("第二个执行\n"); + } + + int main(int argc, char **argv) { + atexit(&f1); + atexit(&f2); + + /* + 第二个执行 + 第一个执行 + */ + return EXIT_SUCCESS; + } diff --git "a/C/c-\347\273\223\346\236\204\344\275\223.c" "b/C/c-\347\273\223\346\236\204\344\275\223.c" new file mode 100644 index 00000000..15e7a935 --- /dev/null +++ "b/C/c-\347\273\223\346\236\204\344\275\223.c" @@ -0,0 +1,378 @@ +---------------------------- +结构体 | +---------------------------- + # 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许存储不同类型的数据项 + # 结构体的变量,不能在初始化的时候赋值 + * 结构体只是一个类型,没有定义变量前没有分配空间,没空间就不能赋值 + # 结构体的声明与定义和值的初始化 + struct Book { + int id; + char name[50]; + char *author; + double price; + }; + + struct Book java,python; + + java.id = 1; + strcpy(java.name,"Java入门到精通"); //字符串数组可以使用copy + java.author = "KevinBlandy"; //字符指针,可以直接赋值 + java.price = 15.6; + + printf("id=%d,name=%s,author=%s,price=%f",java.id,java.name,java.author,java.price); + //id=1,name=Java入门到精通,author=KevinBlandy,price=15.600000 + + # 使用大括号进行初始化也是可以的 + struct User { + int id; + char *name; + char hobby[20]; + }; + struct User user = {1,"KevinBlandy"}; + printf("id=%d,name=%s,hobby=%s",user.id,user.name,"Java & Python",user.hobby); //id=1,name=KevinBlandy,hobby=Java & Python + + * 使用大括号进行初始化的时候,可以直接对数组类型赋值,底层通过copy完成 + + # c99和c11新的初始化方式 + struct Lang { + int id; + char name[10]; + }; + struct Lang java = { //通过 .属性名 = 值 来初始化 + .id = 1, + .name = "Java" + }; + printf("id=%d,name=%s", java.id, java.name); //id=1,name=Java + + # 结构作为函数参数 + * 值传递,这种方式跟普通的变量值传递是一样的 + struct Book { + int id; + char name[50]; + char *author; + double price; + }; + void func(struct Book book){ + book.id = 15; + printf("id=%d,name=%s,author=%s,price=%f\n",book.id,book.name,book.author,book.price); + } + int main(){ + struct Book java; + java.id = 1; + strcpy(java.name,"Java入门到精通"); + java.author = "KevinBlandy"; + java.price = 15.6; + + //把结构体的值复制给函数的形参使用的,所以这种方式在函数的内部没法修改结构体的数据 + func(java); //id=1,name=Java入门到精通,author=KevinBlandy,price=15.600000 + printf("%d",java.id); //1 + + //引用传递,这种方式,在函数内部可以修改结构体的数据 + func(&java); + return EXIT_SUCCESS; + } + + * 指针传递的函数也可以这样定义 + void func(struct Book *p){ + *p->id = 15; + } + + * 指针传递这种方式也适用于 const 指针的规则 + * const 修饰指针变量名称,该变量只读 + * const 修饰指针,该指针指向的内存数据不能修改 + + # 结构体的浅拷贝 + * 如果结构体有指针,下面的这种方式就会发生浅拷贝 + *to = *from; + + * 结构体可以直接通过变量赋值,但是不要使用这种方式 + * 要给结构体中的成员一个一个拷贝 + + # 指向结构的指针 + struct Book { + int id; + }; + int main(){ + //初始化结构体 + struct Book java; + //初始化属性值 + java.id = 15; + + //定义一个指向结构体的指针 + struct Book *p; + //获取结构体的地址 + p = &java; + + //通过 -> 操作符来通过结构体指针访问结构体属性 + printf("id=%d\n",p -> id); + + //也可以通过 -> 操作符来赋值 + p -> id = 255; + + //也可以通过取地址来操作结构体的变量 + (*p).id = 15; + printf("id=%d\n",(*p).id); //15 + + return EXIT_SUCCESS; + } + + * 结构体的指针与首元素的位置相同 + struct User { + int id; + char *name; + char hobby[20]; + }; + struct User user = {1,"KevinBlandy"}; + printf("%p %p",&user,&user.id); //0061FF14 0061FF14 + + # 结构体内存分配原则 + * 原则一:结构体中元素按照定义顺序存放到内存中,但并'不是紧密排列' + * 从结构体存储的首地址开始 ,每一个元素存入内存中时,它都会认为内存是以自己的宽度来划分空间的 + * 因此'元素存放的位置一定会在自己大小的整数倍上开始' + + + * 原则二:在原则一的基础上,检查计算出的'存储单元是否为所有元素中最宽的元素长度的整数倍' + * 若是,则结束:否则,将其'补齐'为它的整数倍 + + * 计算demo + struct Data{ + char a; //1 -> 1个字节 + int b; //8 -> 以为所有元素都是int,前面四个字节已经有人用了,所以从4字节后开辟内存 + char c; //9 -> 1个字节 + double d; //24 -> 以为所有元素都是double,前面9个字节都被人用了,所以从18字节后开辟内存 + short f; //26 -> 2字节 + } data; + + /* + 26 % 8 != 0 + (26 + 6) % 8 == 0; + 26 + 6 = 32 + */ + printf("d1 = %d\n",sizeof(data)); //32 + + + # 结构体数组 + struct User { + int id; + char *name; + }; + + struct User users[5] = { + {1,"KevinBlandy"}, + {2,"Litch"}, + {3,"Rooco"}, + }; + + int size = sizeof(users) / sizeof(users[0]); + + printf("size=%d\n",size); //5 + + //通过.操作 + users[0].id = 1; + users[0].name = "KevinBlandy"; + printf("id=%d,name=%s\n",users[0].id,users[0].name); //id=1,name=KevinBlandy + + //通过->操作 + (users+1)->id = 2; + (users+1)->name = "Litch"; + printf("id=%d,name=%s\n",(users+1)->id,(users+1)->name); //id=2,name=Litch + + //也可以这样操作 + (*(users + 2)).id = 3; + (*(users + 2)).name = "Rocco"; + printf("id=%d,name=%s\n",(*(users + 2)).id,(*(users + 2)).name); //id=3,name=Rocco + + # 结构体的嵌套 + * 结构体里面的成员还是一个结构体 + struct Role { + int id; + char *name; + }; + struct User { + int id; + char *name; + struct Role role; + }; + + //直接初始化结构体成员 + struct User user = {1,"Kevin",{1,"admin"}}; + printf("id=%d,role=%s",user.id,user.role.name); //id=1,role=admin + + //先定义在初始化结构体成员 + struct User user = {1,"Kevin"}; + struct Role role = {1,"ADMIN"}; + user.role = role; + printf("%s 的角色是 %s",user.name,user.role.name);//Kevin 的角色是 ADMIN + + + # 指针指向堆空间 + struct User { + int id; + char *name; + }; + struct User *p = (struct User *)malloc(sizeof(struct User)); + p -> id = 1; + p -> name = "KevinBlandy"; + printf("id=%d,name=%s",p[0].id,(*p).name); //id=1,name=KevinBlandy + free(p); //使用完成之后,记得释放 + + # 成员指针指向堆区空间 + struct User { + int id; + char *name; + }; + struct User user; + user.name = (char *)malloc(strlen("KevinBlandy") + 1); + strcpy(user.name, "KevinBlandy"); + printf("%s",user.name); + free(user.name); + + # 如果结构体与结构体成员都指向了堆内存,那么释放的时候要先小后大,由里到外 + struct User { + char *name; + }; + //申请堆内存,存放结构体 + struct User *user = (struct User *)malloc(sizeof(struct User)); + //申请堆内存,存放结构体中的name属性值 + user -> name = malloc(sizeof("KevinBlandy") + 1); + strcpy(user -> name,"KevinBlandy"); + printf("name=%s",user[0].name); //name=KevinBlandy + //先释放属性的堆内存 + free(user -> name); + //最后释放结构体的堆内存 + free(user); + + * 如果先释放掉了结构体,那么 user->name,就找不到地址了,没法儿释放属性值占用的堆内存,导致内存泄漏 + + # 结构体字面量 + struct Lang { + int id; + char name[10]; + }; + struct Lang lang; + lang = (struct Lang ) { 1, "Java" }; //字面量,相当于一个匿名的结构体 + printf("id=%d,name=%s", lang.id, lang.name); //id=1,name=Java + return EXIT_SUCCESS; + + # 伸缩形数组成员 + * c99特性,利用这个特性,结构体的最后一个数组成员具备一些特性 + * 这个特性的意图不是让你声明一个结构体变量,而是声明一个指针,通过 malloc()来 分配足够的空间 + * 伸缩形数组成员的规则 + - 伸缩形数组成员必须是结构体的最后一个成员 + - 结构体中至少有一个其他的成员 + - 伸缩数组类似于普通数组,只是不声明长度 + * 合法的声明 + struct Lang { + int id; + char name[]; //伸缩型数组成员 + }; + + struct Lang * lang; + lang = (struct Lang *) calloc(1, sizeof(struct Lang) + 5 * sizeof(char));//为伸缩数组开辟5字节内存 + + //复制四个字符串到该数组 + strncpy(lang->name, "Java", 4); + + printf("id=%d,name=%s", lang->id, lang->name); //id=0,name=Java + +---------------------------- +合法的结构体定义与声明 | +---------------------------- + # 同时定义结构体以及变量 + struct Book { + int id; + char name[50]; + char author[50]; + double price; + } java,python; + + + # 匿名结构体 + struct { + int id; + char name[50]; + char author[50]; + double price; + } java,python = {1,"KevinBlandy","Litch"}; + + # 仅仅定义结构体 + struct Book { + int id; + char name[50]; + char author[50]; + double price; + }; + + * 声明该结构体的变量 + struct Book t1, t2[20],*t3; + + # 用typedef创建新类型 + typedef struct { + int id; + char name[50]; + char author[50]; + double price; + } Book; + + * 现在可以用Book作为类型声明新的结构体变量 + Book t1, t2[20],*t3; + + + +---------------------------- +位域 | +---------------------------- + # 有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位 + # 例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可 + # 为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段" + # 定义与赋值 + struct Bits { + int a :8; //a属性占了8bit + unsigned int b :2; //b属性占了2bit + int c :6; //c属性占了6bit + } bits,*p; + + bits.a = 2; //初始化属性值 + + p = &bits; //获取地址 + + //通过对象/指针访问值 + printf("%d %d\n",bits.a,p -> a); //2 2 + + + # 一个位域必须存储在同一个字节中,不能跨两个字节 + * 如'一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域',也可以有意使某位域从下一单元开始 + struct Bits { + unsigned a :4; //一个数据存储用了4bit + unsigned :4; //剩下的4bit空域 + unsigned b :4; //从下一个单元开始存放 + unsigned c :4; + }; + + # 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位 + * 如果最大长度大于计算机的整数字长,一些编译器可能会允许域的内存重叠 + * 另外一些编译器可能会把大于一个域的部分存储在下一个字中 + + # 位域可以是无名位域,这时它只用来作填充或调整位置,无名的位域是不能使用的 + struct k{ + int a:1; + int :2; /* 该 2 位不能使用 */ + int b:3; + int c:2; + }; + + + + # bit位数必须小于等于声明的类型 + struct Bits{ + int a:1; //1小于等于int的位数,这个声明是合法的 + }; + + + # 赋值如果超出位数,可能会丢失经度 + struct { + unsigned int age :3; + } Age; + + Age.age = 8; // 二进制表示为 1000 有四4位,超出 + printf("Age.age : %d\n", Age.age); //0 \ No newline at end of file diff --git "a/C/c-\350\277\220\347\256\227\350\241\250\350\276\276\345\274\217.c" "b/C/c-\350\277\220\347\256\227\350\241\250\350\276\276\345\274\217.c" new file mode 100644 index 00000000..ffac403f --- /dev/null +++ "b/C/c-\350\277\220\347\256\227\350\241\250\350\276\276\345\274\217.c" @@ -0,0 +1,87 @@ +------------------------ +基本运算 | +------------------------ + + + - + * + / + + # 两数相除的问题 + * 两数相除,想要得到小数部分,分子分母必须有一个是小数,否则结果只会取整 + printf("%f", (double)(5 / 2)); //2.000000 + printf("%f", (double)(5.0 / 2)); //2.500000 + + # 整数与小数相乘/除结果为小数 + + + +------------------------ +循环 | +------------------------ + do{}while(); + while(){} + for(;;){} + +------------------------ +判断 | +------------------------ + && + || + ! + + * 带短路 + +------------------------ +支持三元运算 | +------------------------ + int x = (5 > 4) ? 5 : 2; + +------------------------ +switch | +------------------------ + int num = 4; + switch(num){ + case 4:{ + printf("4"); + break; + } + case 5:{ + printf("5"); + break; + } + default:{ + printf("没"); + break; + } + } + + # 数据类型只能是整形和字符型变量 + +------------------------ +goto | +------------------------ + # 没卵用 + ----------- + loop:for(;;){ + goto loop; + } + + ----------- + goto GO_TO; + + printf("执行了1\n"); + printf("执行了2\n"); + + GO_TO:{ + printf("执行了GOTO"); //上面两句打印会被跳过,不会执行 + } + +------------------------ +其他 | +------------------------ + & + - 返回变量的地址 + - &a, 将给出变量的实际地址 + * + - 指向一个变量 + - *a; 将指向一个变量 \ No newline at end of file diff --git "a/C/c-\351\224\231\350\257\257\345\244\204\347\220\206.c" "b/C/c-\351\224\231\350\257\257\345\244\204\347\220\206.c" new file mode 100644 index 00000000..af0d871b --- /dev/null +++ "b/C/c-\351\224\231\350\257\257\345\244\204\347\220\206.c" @@ -0,0 +1,51 @@ +------------------------ +错误处理 | +------------------------ + # C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许您访问底层数据 + # 在发生错误时,大多数的 C 或 UNIX 函数调用返回 1 或 NULL,同时'会设置一个错误代码 errno,该错误代码是全局变量,表示在函数调用期间发生了错误' + # 可以在 errno.h 头文件中找到各种各样的错误代码 + # 所以,C 程序员可以通过检查返回值,然后根据返回值决定采取哪种适当的动作 + # 开发人员应该在程序初始化时,把 errno 设置为 0,这是一种良好的编程习惯,0 值表示程序中没有错误 + + #include + #include + #include + + //声明全局变量 + extern int errno; + + int main() { + FILE * file; + int errnum; + file = fopen("unexist.txt", "rb"); + if (file == NULL) { + + errnum = errno; + fprintf(stderr, "错误号: %d\n", errno); + perror("通过 perror 输出错误"); + fprintf(stderr, "打开文件错误: %s\n", strerror(errnum)); + } else { + fclose(file); + } + return 0; + } + + + + # 根据异常代码获取其文字描述 + char *strerror (int) + * + * 根据int(异常码)返回其文字描述 + + + + # 打印错误信息 + void perror (const char *); + * 标注的错误打印流,会在参数字符串后面添异常/正常的提示 + perror("hi"); //hi: No error + + * 它可以打印'库函数'调用失败的原因 + fclose(stdout); + printf("Hello"); + perror("hi"); //hi: Bad file descriptor(坏的文件描述符,因为已经关闭了) + diff --git "a/C/c-\351\232\217\346\234\272\346\225\260.c" "b/C/c-\351\232\217\346\234\272\346\225\260.c" new file mode 100644 index 00000000..c77627e7 --- /dev/null +++ "b/C/c-\351\232\217\346\234\272\346\225\260.c" @@ -0,0 +1,19 @@ +-------------------------- +随机数 | +-------------------------- + # 获取随机数 + #include + #include + #include + + int main(void) { + //设置种子,使用当前时间,保证每次调用获取的随机数都不一致 + srand((unsigned int)time(NULL)); + //产生随机数 + for(int i = 0 ; i < 10 ; i++){ + int rand_number = rand(); + printf("获取随机数:%d\n",rand_number); + } + return EXIT_SUCCESS; + } + diff --git "a/C/c-\351\242\204\345\244\204\347\220\206.c" "b/C/c-\351\242\204\345\244\204\347\220\206.c" new file mode 100644 index 00000000..e01cc912 --- /dev/null +++ "b/C/c-\351\242\204\345\244\204\347\220\206.c" @@ -0,0 +1,236 @@ +-------------------------------- +预处理指令 | +-------------------------------- + #include + * 包含一个源代码文件 + + #define + * 定义宏 + #undef + * 取消已定义的宏 + #ifdef + * 如果宏已经定义,则返回真 + #ifndef + * 如果宏没有定义,则返回真 + + #if + * 如果给定条件为真,则编译下面代码 + #else + * #if 的替代方案 + #elif + * 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 + #endif + * 结束一个 #if……#else 条件编译块 + #error + * 当遇到标准错误时,输出错误消息 + #pragma + * 使用标准化方法,向编译器发布特殊的命令到编译器中 + + +-------------------------------- +#include | +-------------------------------- + # 头文件的本质其实就是把指定的文件'copy'到当前文件的include处 + # 头文件的导入 + #include + + # 系统标准的头文件 + Windows + C:\MinGW\include + + Linux + /usr/include + + # #include 和 #include "xxx.h" 的区别 + * <> 用的是编译器的类库路径里面的头文件 + * 一般是引用自带的一些头文件 + + * "" 引用的是程序目录的相对路径中的头文件 + * 一般是用来引用自己写的一些头文件 + * 它会先在你项目的当前目录查找是否有对应头文件,如果没有,它还是会去path(编译)环境中寻找 + + # 多个文件(#include)中,函数名称不能重复(静态函数除外,静态函数仅在当前文件有效) + + # 防止头文件重复包含,有两种方式 + 1,头文件添加 #pragma once + * 你希望哪个头文件进制被其他文件重复包含,添加到哪个头文件顶部 + * 顶部添加了该代码的文件,在被重复包含时,也仅仅只有一次生效 + + 2,条件编译(老版本) + #ifndef _NAME_.H + #define _NAME_.H + //函数声明 + #endif + + * 以上是 name.h 这个头文件的条件编译 + + # 有条件的引用 + + * 有时需要从多个不同的头文件中选择一个引用到程序中 + * 需要指定在不同的操作系统上使用的配置参数,可以通过一系列条件来实现这点 + + #if SYSTEM_1 + # include "system_1.h" + #elif SYSTEM_2 + # include "system_2.h" + #elif SYSTEM_3 + ... + #endif + + * 但是如果头文件比较多的时候,这么做是很不妥当的,预处理器使用宏来定义头文件的名称 + * 这就是所谓的有条件引用,它不是用头文件的名称作为 #include 的直接参数,只需要使用宏名称代替即可 + + #define SYSTEM_H "system_1.h" + ... + #include SYSTEM_H + + * SYSTEM_H 会扩展,预处理器会查找 system_1.h,就像 #include 最初编写的那样 + * SYSTEM_H 可通过 -D 选项被 Makefile 定义 + +-------------------------------- +#define | +-------------------------------- + # 编译时替换常量 + #define name value + + * 在编译程序的时候,程序中的所有 name 都会被替换为 value + * 这个过程称为编译时替换,在运行程序的时候,所有替换都已经完成 + * 这样定义的常量也被称为明示常量 + + * 末尾不需要添加;,而且名称要大写(非强制约束) +-------------------------------- +#undef | +-------------------------------- + # 取消已经定义的宏 + #define MAX 15 + #undef MAX //取消已经定义的宏 + #define MAX 25 //重新定义该宏 + +-------------------------------- +#pragma | +--------------------------------- + # 比较复杂的一个指令,使用标准化方法,向编译器发布特殊的命令到编译器中 + # 保证头文件只被编译一次(用的比较多) + #pragma once + +-------------------------------- +#ifdef | +-------------------------------- + # 如果宏已经定义,则返回真 + #ifdef DEBUG + /* Your debugging statements here */ + #endif + + * 这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句 + * 在编译时,如果向 gcc 编译器传递了 -DDEBUG 开关量,这个指令就非常有用,它定义了 DEBUG,您可以在编译期间随时开启或关闭调试 + gcc -DDEBUG ..... + # demo + #ifdef DEBUG + #define MESSAGE "DEBUG" + #endif + + #ifndef MESSAGE + #define MESSAGE "NOT DEBUG" + #endif + + int main(int argc, char **argv) { + printf("%s",MESSAGE); + return EXIT_SUCCESS; + } +-------------------------------- +#ifndef | +-------------------------------- + # 如果宏没有定义,则返回真 + #ifndef MESSAGE //如果未定义 MESSAGE 宏 + #define MESSAGE "You wish!" //则定义 + #endif //结束 + +-------------------------------- +#if | +-------------------------------- + # 如果给定条件为真,则编译下面代码 + +-------------------------------- +#else | +-------------------------------- + # #if 的替代方案 + +-------------------------------- +#elif | +-------------------------------- + # 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 + +-------------------------------- +#endif | +-------------------------------- + # 结束一个 #if……#else 条件编译块 + +-------------------------------- +#error | +-------------------------------- + # 当遇到标准错误时,输出错误消息 + + +-------------------------------- +预处理器运算符 | +-------------------------------- + # 宏延续运算符(\) + * 一个宏通常写在一个单行上,但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\) + #define message_for(a, b) \ + printf(#a " and " #b ": We love you!\n") + + # 字符串常量化运算符(#) + * 在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#) + * 在宏中使用的该运算符有一个特定的参数或参数列表 + + #include + #define message_for(a, b) \ + printf(#a " and " #b ": We love you!\n") + + int main(void){ + message_for(Carole, Debra); //Carole and Debra: We love you! + return 0; + } + + # 标记粘贴运算符(##) + * 宏定义内的标记粘贴运算符(##)会合并两个参数 + * 它允许在宏定义中两个独立的标记被合并为一个标记 + #include + + #define tokenpaster(n) \ + printf ("token" #n " = %d", token##n) //token##n就是把token和n粘贴到一起了,实际上会被编译为:printf ("token34 = %d", token34); + + int main(void){ + int token34 = 40; + tokenpaster(34); //token34 = 40 + return 0; + } + + # defined() 运算符 + * 预处理器 defined 运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define 定义过 + * 如果指定的标识符已定义则值为真(非零),如果指定的标识符未定义,则值为假(零) + + #include + #if !defined (MESSAGE) //如果未定义 MESSAGE 宏 + #define MESSAGE "You wish!" //则定义 + #endif + + int main(void) { + printf("Here is the message: %s\n", MESSAGE); + return 0; + } + +-------------------------------- +带参数化的宏 | +-------------------------------- + # 在使用带有参数的宏之前,必须使用 #define 指令定义 + # 参数列表是括在圆括号内,且必须紧跟在宏名称的后边 + # 宏名称和左圆括号之间不允许有空格 + + #include + #define MAX(x,y) ((x) > (y) ? (x) : (y)) + + int main(void) { + printf("Max between 20 and 10 is %d\n", MAX(10, 20)); //Max between 20 and 10 is 20 + return 0; + } \ No newline at end of file diff --git "a/C/c-\351\242\204\345\256\232\344\271\211\345\256\217.c" "b/C/c-\351\242\204\345\256\232\344\271\211\345\256\217.c" new file mode 100644 index 00000000..6e800325 --- /dev/null +++ "b/C/c-\351\242\204\345\256\232\344\271\211\345\256\217.c" @@ -0,0 +1,28 @@ +---------------------------- +预定义宏 | +---------------------------- + # ANSI C 定义了许多宏,在编程中可以使用这些宏,但是不能直接修改这些预定义的宏 + + __DATE__ 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量 + __TIME__ 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量 + __FILE__ 这会包含当前文件名,一个字符串常量 + __LINE__ 这会包含当前行号,一个十进制常量 + __STDC__ 当编译器以 ANSI 标准编译时,则定义为 1 + + + #include + + main() + { + printf("File :%s\n", __FILE__ ); File :test.c + printf("Date :%s\n", __DATE__ ); Date :Jun 2 2012 + printf("Time :%s\n", __TIME__ ); Time :03:36:24 + printf("Line :%d\n", __LINE__ ); Line :8 + printf("ANSI :%d\n", __STDC__ ); ANSI :1 + } + + + + + + diff --git a/C/lib/c-lib-assert.h b/C/lib/c-lib-assert.h new file mode 100644 index 00000000..7c3203d5 --- /dev/null +++ b/C/lib/c-lib-assert.h @@ -0,0 +1,10 @@ +-------------- +assert | +-------------- + # assert 断言库 + +assert(int ) + * 如果参数表达式非0(假),assert就会标准的错误流中写入错误信息 + * 并且会调用 abort() 函数(stdlin)停止程序 + + assert(1 == 0); //Assertion failed: 1 == 0, file Demo.c, line 6 \ No newline at end of file diff --git a/C/lib/c-lib-conio.h b/C/lib/c-lib-conio.h new file mode 100644 index 00000000..0e417a46 --- /dev/null +++ b/C/lib/c-lib-conio.h @@ -0,0 +1,26 @@ + +------------------------- +conio | +------------------------- + # 支持无缓冲输入的一些函数 + + int _getch(); + * 该函数从标准的io流中返回一个 char + * 当用户输入一个字符,就会立即返回,不会根据空格/换行等切割 + * demo + char ch = _getch(); + printf("%c",ch); + + + int getche() + * 用于回显无缓冲 + * 会回显输入的字符 + * 回显意味着用户的输入,会直接出现在屏幕上 + + int getch() + * 用无回显无缓冲 + * 不会回显输入的字符 + * 不回显意味着用户的输入,不会直接出现在屏幕上 + + + \ No newline at end of file diff --git a/C/lib/c-lib-ctype.h b/C/lib/c-lib-ctype.h new file mode 100644 index 00000000..7ac59cd3 --- /dev/null +++ b/C/lib/c-lib-ctype.h @@ -0,0 +1,53 @@ +------------------------ +ctype.h | +------------------------ + # 包含了一系列对字符的判断函数,这些函数都使用字符作为参数,如果满足某些条件就返回 true,反之返回 false + +------------------------ +判断 | +------------------------ + isalpha(int c) + * 如果该字符是一个字母,返回 true + + isalnum(int c) + * 字母或者数字 + + isblank(int c) + * 标准空白字符(空格,制表符,换行符),或者任何额其他本地化指定为空白的字符 + + iscntrl(int c) + * 控制字符,如: Ctrl + B + + isdigit(int c) + * 是否是十进制数字 + + isgraph(int c) + * 除了空格以外的任意可打印字符 + * 该函数检查所传的字符是否有图形表示法 + + islower(int c) + * 小写字母 + + isprint(int c) + * 可打印字符 + + ispunct(int c) + * 标点符号(除了空格或做字母数字字符以外的任何可打印字符) + + isspace(int c) + * 空白字符(空格,换行符,换页符,回车符,垂直制表符,水平制表符,或者其他本地化定义的字符) + + isupper(int c) + * 是否是大写字母 + + isxdigit(int c) + * 十六进制字符 + +------------------------ +行为 | +------------------------ + tolower(int c) + * 如果参数是大写,该函数返回其小写,否则返回原始参数 + + toupper(int c) + * 同上,大写转换 \ No newline at end of file diff --git a/C/lib/c-lib-dirent.h b/C/lib/c-lib-dirent.h new file mode 100644 index 00000000..a239823e --- /dev/null +++ b/C/lib/c-lib-dirent.h @@ -0,0 +1,32 @@ +------------------------ +dirent | +------------------------ + # 操作目录的库 + + DIR * opendir (const char *__dirname) + * 打开一个目录,返回一个目录指针 typedef struct __dirstream_t DIR; + * 如果目录不存在,返回 NULL + + dirent* readdir (DIR *__dir) + * 读取目录信息,参数是一个目录指针 + * 返回 struct dirent + struct dirent + { + long d_ino; /* Always zero. */ + unsigned short d_reclen; /* Always sizeof struct dirent. */ + unsigned short d_namlen; /* 文件/文件夹名称的字符长度 */ + unsigned d_type; /* File attributes */ + char d_name[FILENAME_MAX]; /* 文件/文件夹名称 */ + }; + + * 它应该循环调用 + * 因为每次调用,它会逐渐遍历出目录指针下的所有文件/目录信息,如果遍历到末尾,返回 NULL + + * 遍历某个目录下的所有文件 + DIR *dir = opendir("E:\\letsencrypt"); + struct dirent *dirent = NULL; + while ((dirent = readdir(dir)) != NULL) { + printf("%s\n", dirent->d_name); + } + + diff --git a/C/lib/c-lib-errno.h b/C/lib/c-lib-errno.h new file mode 100644 index 00000000..07b81894 --- /dev/null +++ b/C/lib/c-lib-errno.h @@ -0,0 +1,59 @@ +------------------------ +errno | +------------------------ + # 预定义了N多异常宏,在发生异常的时候可以获取全局的 error 变量,通过变量值在宏定义中确定异常 + + #define EPERM 1 /* Operation not permitted */ + #define ENOFILE 2 /* No such file or directory */ + #define ENOENT 2 + #define ESRCH 3 /* No such process */ + #define EINTR 4 /* Interrupted function call */ + #define EIO 5 /* Input/output error */ + #define ENXIO 6 /* No such device or address */ + #define E2BIG 7 /* Arg list too long */ + #define ENOEXEC 8 /* Exec format error */ + #define EBADF 9 /* Bad file descriptor */ + #define ECHILD 10 /* No child processes */ + #define EAGAIN 11 /* Resource temporarily unavailable */ + #define ENOMEM 12 /* Not enough space */ + #define EACCES 13 /* Permission denied */ + #define EFAULT 14 /* Bad address */ + /* 15 - Unknown Error */ + #define EBUSY 16 /* strerror reports "Resource device" */ + #define EEXIST 17 /* File exists */ + #define EXDEV 18 /* Improper link (cross-device link?) */ + #define ENODEV 19 /* No such device */ + #define ENOTDIR 20 /* Not a directory */ + #define EISDIR 21 /* Is a directory */ + #define EINVAL 22 /* Invalid argument */ + #define ENFILE 23 /* Too many open files in system */ + #define EMFILE 24 /* Too many open files */ + #define ENOTTY 25 /* Inappropriate I/O control operation */ + /* 26 - Unknown Error */ + #define EFBIG 27 /* File too large */ + #define ENOSPC 28 /* No space left on device */ + #define ESPIPE 29 /* Invalid seek (seek on a pipe?) */ + #define EROFS 30 /* Read-only file system */ + #define EMLINK 31 /* Too many links */ + #define EPIPE 32 /* Broken pipe */ + #define EDOM 33 /* Domain error (math functions) */ + #define ERANGE 34 /* Result too large (possibly too small) */ + /* 35 - Unknown Error */ + #define EDEADLOCK 36 /* Resource deadlock avoided (non-Cyg) */ + #define EDEADLK 36 + /* 37 - Unknown Error */ + #define ENAMETOOLONG 38 /* Filename too long (91 in Cyg?) */ + #define ENOLCK 39 /* No locks available (46 in Cyg?) */ + #define ENOSYS 40 /* Function not implemented (88 in Cyg?) */ + #define ENOTEMPTY 41 /* Directory not empty (90 in Cyg?) */ + #define EILSEQ 42 /* Illegal byte sequence */ + + # 全局变量 errno + * 在发生异常的时候,该error值为异常信息 + + # ERANGE + * 表示一个范围错误,它在输入参数超出数学函数定义的范围时发生,errno 被设置为 ERANGE + + # DOM + * 表示一个域错误,它在输入参数超出数学函数定义的域时发生,errno 被设置为 EDOM + diff --git a/C/lib/c-lib-float.h b/C/lib/c-lib-float.h new file mode 100644 index 00000000..eb502925 --- /dev/null +++ b/C/lib/c-lib-float.h @@ -0,0 +1,28 @@ + +----------------------------- +flot.h | +----------------------------- + # 定义了系统float数据类型的限制常量 + +FLT_MANT_DIG + * float 类型的尾数位数 + +FLT_DIG + * float 类型的最少有效数字位数(10进制) + +FLT_MIN_10_EXP + * 带全部有效数字 float 类型的最小负指数(以10为底) + +FLT_MAX_10_EXP + * float 类型的最大正指数(以10为底) + +FLT_MIN + * 保留全部精度的 float 类型最小正数 + +FLT_MAX + * float 类型的最大正数 + +FLT_EPSILON + * 1.00 和比 1.00 大的最小 float 类型值之间的差值 + + diff --git a/Java/aio/java-aio.java b/C/lib/c-lib-inttypes.h similarity index 100% rename from Java/aio/java-aio.java rename to C/lib/c-lib-inttypes.h diff --git a/C/lib/c-lib-io.h b/C/lib/c-lib-io.h new file mode 100644 index 00000000..3741cb3b --- /dev/null +++ b/C/lib/c-lib-io.h @@ -0,0 +1,16 @@ +------------------------ +io.h | +------------------------ + # 系统io相关 + # 在Linux环境中,这个库的位置在:/usr/include/sys/io.h + + int mkdir (const char *); + * 创建一个文件夹 + * 创建成功返回0,否则返回-1(目录已经存在) + + char *getcwd (char *buf, int size); + * 获取当前执行程序所在的目录 + * 把当前路径写入buf,最大长度为size + char buf[1024] = { 0 }; + getcwd(buf, 1024); + printf("%s", buf); //D:\workspace\clang-practice diff --git a/C/lib/c-lib-iso646.h b/C/lib/c-lib-iso646.h new file mode 100644 index 00000000..bb672d9b --- /dev/null +++ b/C/lib/c-lib-iso646.h @@ -0,0 +1,25 @@ +------------------------- +iso646 | +------------------------- + # 该头文件提供了一些几个指令,可以代替判断关键字或者位运算关键字 + # 例如 + and,or,not 代替 &&, ||, ! + # 源码 + #ifndef _ISO646_H + #define _ISO646_H + + #ifndef __cplusplus + #define and && + #define and_eq &= + #define bitand & + #define bitor | + #define compl ~ + #define not ! + #define not_eq != + #define or || + #define or_eq |= + #define xor ^ + #define xor_eq ^= + #endif + + #endif diff --git a/C/lib/c-lib-limits.h b/C/lib/c-lib-limits.h new file mode 100644 index 00000000..e6cc07da --- /dev/null +++ b/C/lib/c-lib-limits.h @@ -0,0 +1,47 @@ + +----------------------------- +limits.h | +----------------------------- + # 定义了系统int数据类型的限制常量 + +CHAR_BIT +CHAR_MAX +CHAR_MIN + * char类型的位数,最大值,最小值 + +SCHAR_MAX +SCHAR_MIN + * signed char 类型最大值,最小值 + +UCHAR_MAX + * unsigned char 类型最大值 + +SHRT_MAX +SHRT_MIN + * short 类型最大值,最小值 + +USHRT_MAX + * unsigned short 最大值 + +INT_MAX +INT_MIN + * int类型最大值,最小值 + +UINT_MAX + * unsigned int 最大值 + +LONG_MAX +LONG_MIN + * long 数据类型最大值,最小值 + +ULONG_MAX + * unsigned long 类型最大值 + +LLong_MAX +LLong_MIN + * long long 类型最大值,最小值 + +ULLONG_MAX + * unsigned long long 最大值 + + diff --git "a/RocketMQ/RocketMQ-\346\231\256\351\200\232\346\266\210\346\201\257java.java" b/C/lib/c-lib-stdarg.h similarity index 100% rename from "RocketMQ/RocketMQ-\346\231\256\351\200\232\346\266\210\346\201\257java.java" rename to C/lib/c-lib-stdarg.h diff --git a/C/lib/c-lib-stdbool.h b/C/lib/c-lib-stdbool.h new file mode 100644 index 00000000..8b681b3e --- /dev/null +++ b/C/lib/c-lib-stdbool.h @@ -0,0 +1,32 @@ +---------------------------- +stdbool | +---------------------------- + # C99,该库使C支持了 bool 语法,和 true false 关键字 + # 源码 + #ifndef _STDBOOL_H + #define _STDBOOL_H + + #ifndef __cplusplus + + #define bool _Bool + #define true 1 + #define false 0 + + #else /* __cplusplus */ + + /* Supporting _Bool in C++ is a GCC extension. */ + #define _Bool bool + + #if __cplusplus < 201103L + /* Defining these macros in C++98 is a GCC extension. */ + #define bool bool + #define false false + #define true true + #endif + + #endif /* __cplusplus */ + + /* Signal that all the definitions are present. */ + #define __bool_true_false_are_defined 1 + + #endif /* stdbool.h */ \ No newline at end of file diff --git a/C/lib/c-lib-stdint.h b/C/lib/c-lib-stdint.h new file mode 100644 index 00000000..a66a7c90 --- /dev/null +++ b/C/lib/c-lib-stdint.h @@ -0,0 +1,9 @@ +------------------------ +stdint.h | +------------------------ + # 可移植的数据类型 + + + int32_t + * int 的别名 + * 表示32位有符号整数类型 diff --git a/C/lib/c-lib-stdio.h b/C/lib/c-lib-stdio.h new file mode 100644 index 00000000..004c69d9 --- /dev/null +++ b/C/lib/c-lib-stdio.h @@ -0,0 +1,175 @@ +-------------------------------- +stdio | +-------------------------------- + +EOF + * -1,表示文件结尾 + +BUFSIZ + * 缓冲区大小 + +stdin + * 标准输入流指针 +stdout + * 标准输出流指针 +stderr + * 标准错误流指针 + + +int printf() + * 用于标注输出(打印) + * 支持字符串占位符,必须严格按照占位符的数据类型传递参数 + printf("Hello %d,%d",5,6); + * 返回打印的字节数量,如果发生异常返回 -1 + +void perror (const char *); + * 标注的错误打印流,会在参数字符串后面添异常/正常的提示 + perror("hi"); //hi: No error + + * 它可以打印'库函数'调用失败的原因 + fclose(stdout); + printf("Hello"); + perror("hi"); //hi: Bad file descriptor(坏的文件描述符,因为已经关闭了) + +sprintf(char dst*, const char *, ...) + * 把 字符串格式化后,写入到dst中 + int x = 10; + char y = 'H'; + char str[] = "Java"; + char dst[100]; + sprintf(dst,"x = %d,y = %c,buf = %s",x,y,str); + printf("dst = %s\n",dst); //dst = x = 10,y = H,buf = Java + +puts() + * 函数只用来输出字符串,不能使用占位符,自动添加换行 + * 里面的参数可以直接是字符串或者是存放字符串的字符数组名 + * 作用与 printf("%s\n",s);的作用形同 + +scanf() + * 获取屏幕输入,跟 printf 一样,也可以使用格式字符串和参数列表 + * scanf 在读取的时候,会跳过所有非空白符前面的空白符 + * printf函数使用便利,常量和表达式,scanf函数使用变量的指针 + int var1; + int var2; + scanf("%d %d",&var1,&var2); + printf("%d %d",var1,var2); + + * 当用户通过scanf输入字符时,编译器默认先把内容放在缓冲区 + * scanf自动在缓冲区读取内容 + +getchar() + * 读取下一个字符串输入,并且返回,它只处理字符 + * 等同于 + char ch; + scanf("%c",&ch); + +putchar() + * 该函数的作用就是,打印该函数的参数,它只处理字符 + * 等同于 + char ch = '1'; + printf("%d",ch) + +gets(char *s) + * 从标准输入读取字符,并保存到s指定的内存空间直到出现换行符或者文件结尾为止 + * 返回读取成功的字符串,如果读取失败返回 NULL + * 它与scanf的区别,它允许输入的数据有空格 + * 它与scanf都无法知道输入字符串的大小,必须遇到换行符或者读取到文件的结尾才接收输入因此容易导致数组越界(缓冲区溢出) + * 该api已经废弃,不建议使用( warning: the `gets' function is dangerous and should not be used.) + +fgets(char *s,int size,FILE *stream) + * 参数 + s: 字符串 + size:指定读取最大字符串的长度(默认 -1,不限制) + stram:文件指针,'如果读取键盘输入的字符串,固定为stdin' + * 从stream指定的文件内读入字符,保存到s指定的内存空间,直到出现换行字符,读取到文件结尾,或是已经读取了size - 1 个字符为止 + * 会自动在最后加上 '\0' 标识,会把换行符也一起读取进去 + * s最多只能装 length - 1个字符,因为必须要留一个给字符串结束符 '\0',如果输入内容大于了 size 或者 sizeof(s) 那么超出部分会被截断 + * 读取成功返回读取到的字符串,读取到文件末尾或者出错,返回 NULL + * 无法读取中文??? + * 读取键盘输入demo + char buf[100]; + fgets(buf,sizeof(buf),stdin); + printf("你输入的是:%s\n",buf); + +puts(const char *s) + * 标准设备输出s字符串,会自动添加换行符 \n + * 成功返回非负数,失败返回 -1 + + +fputs(const char *str,FILE *stream) + * 把 str 字符写入到stream指定的文件中,字符串结束符 '\0' 不写入文件 + * 无法输出中文??? + +fprintf(FILE *fp,const char *format, ...) + * '源是多个变量,目标是文件' + * 可以把格式化的内容,输出到指定的流 + FILE *file = fopen("E:\\c-lang.txt","w"); + fprintf(file,"Hello %s","Java"); + fclose(file); + * printf("Hello %s","Java") == fprintf(stdout,"Hello %s","Java") + +fscanf(FILE *fp, const char *format, ...) + * '源是文件,目标是多个变量' + * 函数来从文件中读取字符串,但是在遇到第一个空格/换行字符时,它会停止读取 + FILE *file = fopen("E:\\c-lang.txt","r"); + int x,y,z; + fscanf(file,"%d %d %d",&z,&y,&z); + printf("z=%d,y=%d,z=%d",z,y,z); //z=36,y=12,z=36 + fclose(file); + +sprintf(char dst*, const char *, ...) + * '源是多个变量,目标是缓冲区' + * 把 字符串格式化后,写入到dst中 + +sscanf (const char *, const char *temp, ...) + * '源是缓冲区,目标是多个变量' + * 把从dst读取到的字符串,填充到temp模版 + //定义一个"输入的字符串" + char dst[] = "1 2 3"; + //定义变量 + int a,b,c; + //使用 cccanf 把 输入的字符,赋值给变量 + sscanf(dst,"%d %d %d",&a,&b,&c); + printf("a=%d,b=%d,c=%d\n",a,b,c); //a=1,b=2,c=3 + + * 从字符串中提取整形变量是最方便的 + char inputs[] = "a=10,b=20"; + int a , b; + sscanf(inputs,"a=%d,b=%d",&a,&b); + printf("a=%d,b=%d\n",a,b); //a=10,b=20 + + * 提取字符串,默认以空格分割 + char temp[] = "abc def 123"; + char str1[4],str2[4],str3[4]; + sscanf(temp,"%s %s %s",str1,str2,str3); + printf("str1=%s,str2=%s,str3=%s",str1,str2,str3);//str1=abc,str2=def,str3=123 + + +FILE * fopen (const char *name, const char *model); + * 打开一个文件,name指定文件地址,model指定打开的类型 + * model的枚举字符串 + r 打开一个已有的文本文件,允许读取文件 + + w 打开一个文本文件,允许写入文件,如果文件不存在,则会创建一个新文件 + 在这里,程序会从文件的开头写入内容,如果文件存在,则该会被截断为零长度,重新写入 + + a 打开一个文本文件,以追加模式写入文件,如果文件不存在,则会创建一个新文件 + 在这里,程序会在已有的文件内容中追加内容 + + r+ 打开一个文本文件,允许读写文件 + + w+ 打开一个文本文件,允许读写文件,如果文件已存在,则文件会被截断为零长度 + 如果文件不存在,则会创建一个新文件 + + a+ 打开一个文本文件,允许读写文件,如果文件不存在,则会创建一个新文件,读取会从文件的开头开始,写入则只能是追加模式 + + * 处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式: + + "rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b" + +int fclose (FILE *); + * 关闭文件 + +int fseek (FILE *file, long offset, int whence); + * 随机io + diff --git a/C/lib/c-lib-stdlib.h b/C/lib/c-lib-stdlib.h new file mode 100644 index 00000000..5d8908fa --- /dev/null +++ b/C/lib/c-lib-stdlib.h @@ -0,0 +1,168 @@ +-------------------------------- +stdlib | +-------------------------------- + +FILE + +NULL + * null + +EXIT_SUCCESS = 0 + * 成功 + +EXIT_FAILURE = 1 + * 失败 + +exit() + * 程序退出,参数为 int 值,表示程序的退出状态 + +system() + * 执行系统命令 + * 返回值 int,该值就是程序的返回值,在不同的平台不一样 + +char *getenv (const char *); + * 获取系统环境变量,如果不存在返回NULL + char *p = getenv("JAVA_HOME"); + printf("%s",p); //C:\Program Files\Java\jdk1.8.0_171 + +srand(unsigned int) + * 设置随机数的种子 + +rand() + * 该函数返回一个随机数 int + * 如果种子一样,那么多次调用的结果也一样 + +atoi(const char *nptr); + * 把字符串转换为整形,然后返回 + * 会跳过前面的空格,直到遇到数字或者正负号才开始转换,遇到非数字字符串或者\0就结束转换,并且返回转换结果 + +stof(const char *nptr); + * 同上 + * 把字符串转换为float + +atol(const char *nptr); + * 同上 + * 把字符串转换为long + +void *malloc (size_t) + * 参数表示申请的空间是多少 + * 如果申请成功,返回的数据就是申请的堆空间的首元素地址(指针),申请失败,返回 NULL + * 申请的堆空间,如果程序没有结束,那么不会释放,需要程序手动的释放 + * demo + int *p = (int *) malloc(sizeof(int)); + *p = 15; + printf("%d",p[0]); //15 + +void free (void *); + * 释放堆空间的内存,交还给操作系统 + * 同一块儿的堆内存,只能执行一次释放操作 + * 释放掉内存后,执行该内存的指针就是野指针了 + +fclose() +fopen() +freopen() +fflush() +atexit (void (*)(void)); + * 该函数参数是一个方法指针 + * 该方法可以多次调用,最少可以放32个回调函数 + * 在程序退出的时候,会调用该方法(最后添加的先执行) + + void f1(){ + printf("第一个执行\n"); + } + void f2(){ + printf("第二个执行\n"); + } + + int main(int argc, char **argv) { + atexit(&f1); + atexit(&f2); + + /* + 第二个执行 + 第一个执行 + */ + return EXIT_SUCCESS; + } + +void qsort(void *arr, size_t length, size_t size, int (*)(const void *, const void *)); + * 快速排序的实现 + * arr 待排序数组的首元素 + * lenngth 数组的长度 + * size 数组元素的大小 + * int (*)(const void *v1, const void *v2) 排序方法的指针 + - 如果v1 > v2 返回整数 + - 如果v1 < v2 返回负数 + - 如果v1 = v2 返回0 + * 排序 + int compareTo(const void *v1, const void *v2) { + //从大到小排序 + return *((int *) v1) < *((int *) v2); + } + + int main(int argc, char **argv) { + + int arr[] = { 1, 5, 4, 7, 8, 2, 3, 9, 6 }; + + size_t arrLength = sizeof(arr) / sizeof(arr[0]); + size_t itemSize = sizeof(arr[0]); + + qsort(arr, arrLength, itemSize, &compareTo); + + for (int x = 0; x < arrLength; x++) { + printf("%d\n", arr[x]); + } + + return EXIT_SUCCESS; + } + +void *bsearch(const void *key, const void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *)) + * 二分查找的实现,如果找到元素,则返回该元素的指针,否则返回空指针 + * key 查找元素的指针 + * base 数组 + * nitems 数组元素个数 + * size 数组元素的字节大小 + * compar 指针函数,用于对比元素 + int comparable(const void *arg1, const void *arg2) { + int v1 = *(int *) arg1; + int v2 = *(int *) arg2; + if(v1 > v2){ + return 1; + }else if(v1 < v2){ + return -1; + }else{ + return 0; + } + } + * demo + int comparable(const void *arg1, const void *arg2) { + int v1 = *(int *) arg1; + int v2 = *(int *) arg2; + if(v1 > v2){ + return 1; + }else if(v1 < v2){ + return -1; + }else{ + return 0; + } + } + + int main(int argc, char **argv) { + int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + size_t itemSize = sizeof(arr[0]); + size_t arrLeng = sizeof(arr) / itemSize; + int number = 6; + int * index = (int *) bsearch(&number, arr, arrLeng, itemSize, &comparable); + printf("index = %p\n", index); //index = 0028FF24 + return EXIT_SUCCESS; + } + + * 结构体的指针数组 + int compareTo(const void *v1, const void *v2) { + //该指针实际上是一个二级指针,因为指针数组里面的元素每个都是指针 + struct User **user1 = (struct User **) v1; + struct User **user2 = (struct User **) v2; + int height1 = (*user1)->height; + int height2 = (*user2)->height; + return height1 > height2; + } \ No newline at end of file diff --git a/C/lib/c-lib-string.h b/C/lib/c-lib-string.h new file mode 100644 index 00000000..38b26525 --- /dev/null +++ b/C/lib/c-lib-string.h @@ -0,0 +1,176 @@ +-------------------------------- +string | +-------------------------------- + +strcpy(char *dst,char *src); + * 把 src 的数据拷贝到 dst + * 如果src的内容长度大于dst的,那么会产生数据溢出 + * 只会拷贝第一个'\0'前的数据,并且会把 '\0' 拷贝到目标数组 + char name[11] = "KevinBlandy"; + char cp_name[11]; + + strcpy(cp_name,name); + printf("%s\n",cp_name); //KevinBlandy + + strncpy(char *dst,char *src,int size) + * 把 src 的数据拷贝到 dst,size参数可以限制copy的长度 + * 建议只复制 sizeof(dst) - 1 个元素到目标,因为要留一个位置给'\0' + * 如果 size 超过了 sizeof(dst) - 1,那么会发生溢出 + char str[] = "123456"; + char dst[5]; + strncpy(dst,str,sizeof(dst) - 1); + printf("%s",dst); //123 + + * 如果 '\0' 在拷贝的范围之内,那么'\0'以后的数据全部会被丢弃 + char str[] = "Hello\0Java"; + char dst[100]; + + strncpy(dst,str,sizeof(str)); + + printf("dst = %s\n",dst); //Hello + printf("dst = %s\n",dst + strlen("Hello") + 1); // + +strcat(s1, s2) + * 连接字符串 s2 到字符串 s1 的末尾 + char s1[11] = "Hello"; + char s2[11] = " C"; + strcat(s1,s2); + printf("%s\n",s1); //HelloC + +strncat(s1,s2,length) + * 同上,可以限制追加字符串的长度 + * 把s2的前length个字符追加到s1 + char src[] = "Hello"; + char dst[] = "clang"; + strncat(src,dst,1); + printf("result = %s\n",src); //result = Helloc + +strlen(s1) + * 返回字符串的长度(字节大小),不包含结束符,仅仅计算到第一个分割符 + * 返回 size_t 类型 + char s1[11] = "Hello"; + char s2[11] = "He\0llo"; + printf("%d %d\n",strlen(s1),strlen(s2)); //5 2 + +strcmp(s1,s2) + * 字符串的比较 + * 如果 s1 和 s2 是相同的,则返回 0 + * 如果 s1 s2 则返回大于 0 + char s1[11] = "Hello"; + char s2[11] = "Hi"; + char s3[11] = "Hello"; + printf("%d %d %d\n",strcmp(s1,s3),strcmp(s1,s2),strcmp(s2,s1)); //0 -1 1 + +strncmp(s1,s2,length) + * 同上,也是字符串的比较,该方法可以指定比较的字符串长度 + char src[] = "HelloC"; + char dst[] = "HelloJava"; + int result = strncmp(src,dst,5); + printf("result = %d\n",result); //result = 0 + +strchr(s1, ch); + * 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置 + * 如果没找到返回0 + char s1[11] = "Hello"; + printf("%p\n",strchr(s1,'H')); //28FF35 + +strstr(s1, s2); + * 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置 + * 如果没找到返回0 + char s1[11] = "Hello"; + char s2[11] = "ll"; + printf("%p",strstr(s1,s2)); //0028FF37 + + +strtok(char *str,const char *delmi); + * 以 delmi 来切割字符串str + * 返回值为切割后的字符串 + * 匹配切割字符串的地方,换成结束符 + * 被切割的字符串会因为切割操作而被修改(插入了\0) + * 可以通过多次调用该方法来获取到字符串中所有被切割的字段 + * 除了第一次调用,余下的N次调用第一个参数都必须是NULL,当最后一次匹配不出结果返回 NULL + char temp[] = "ab-cd-ef"; + //第一次匹配 + char *p = strtok(temp,"-"); + printf("p = %s\n",p); //ab + + //第二次匹配 + p = strtok(NULL,"-"); + printf("p = %s\n",p); //cd + + //第三次匹配 + p = strtok(NULL,"-"); + printf("p = %s\n",p); //ef + + //第四次匹配 + p = strtok(NULL,"-"); + printf("p = %s\n",p); //NULL 第四次匹配,没有了返回NULL + + printf("原字符串:%s\n",temp); //被切割的字符串会因为切割操作而被修改(插入了\0) + * 读取出所有的分割段 + char temp[] = "ab-cd-ef-gh-ij"; + char delmi[] = "-"; + char *p = strtok(temp,delmi); + + while(p != NULL){ + printf("find:%s \n",p); + p = strtok(NULL,delmi); + } + /* + find:ab + find:cd + find:ef + find:gh + find:ij + */ + +void *memset (void *p, int v, size_t s) + * 将p所指向的某一块内存中的'每个字节'的内容全部设置为v指定的ASCII值,块的大小由第三个参数s指定 + * 参数 + - p 操作的内存首地址 + - c 填充的数据看起来是整形,其实是当作 ascii 码值,unsigned int,可以是 0 - 255 + * 其实它的值只有是0 才有意义,或者说p是一个数组 + - n 填充的数据大小(以p开始填充多少个字节) + * 返回 p 的首地址地址 + * demo + int a; + memset(&a,0,sizeof(a)); //其实是四个字节每个字节都写入了 97 + printf("%d\n",a); //0 + + memset(&a,97,sizeof(a)); //其实是四个字节每个字节都写入了 97 + printf("%c\n",a); //a(%c仅仅读取一个字节) + + int arr[10]; + memset(arr,97,sizeof(arr)); //40个字节,每个字节都写入了0 + printf("%c\n",arr[0]); //a + + * 这个函数多用来清空数组 + int arr[] = {1,2,3,4,5}; + memset(arr,0,sizeof(arr) * 5); + + +void *memcpy (void *dst, const void *src, size_t size); + * 把src中的size个字节数据copy到dst中 + * 使用该函数,最好不要出现内存重叠(拷贝源和目的都是一个) + * demo + int src[] = {1,2,3}; + int dst[3]; + //把src的数据拷贝到dst中,拷贝dst大小个字节 + memcpy(dst,src,sizeof(dst)); + for(int x = 0 ;x < 3 ;x++){ + printf("%d\n",dst[x]); //1 2 3 + } + +void *memmove (void *dst, const void *src, size_t szie); + * 同上,从src拷贝szie字节到dest,它的使用场景是'内存重叠cpy'的时候 + * 它能够保证'src在被覆盖之前将重叠区域的字节拷贝到目标区域中',但复制完成后src内容会被更改 + * 当目标区域与源区域没有重叠则和memcpy函数功能相同 + +int memcmp (const void src*, const void dst*, size_t size) + * 用来比较俩内存块儿是否相等 + * 比较src和dst内存块开始的size个字节数据是否相同 + * 如果相同返回 0,如果 dst > src 返回 1,如果 dst 小于 src 返回 -1 + + +char *strerror (int) + * 根据int(异常码)返回其文字描述 \ No newline at end of file diff --git a/C/lib/c-lib-time.h b/C/lib/c-lib-time.h new file mode 100644 index 00000000..c82444a6 --- /dev/null +++ b/C/lib/c-lib-time.h @@ -0,0 +1,29 @@ +------------------------ +time | +------------------------ + +CLK_TCK + * 机器时钟每秒所走的时钟打点数(一秒有多少个clock_t) + * printf("%d",CLK_TCK); //1000 + +time() + * 返回当前系统时间(秒数),四个字节 + +clock_t clock (void); + * 返回程序从开始执行到执行该函数的耗费时间 + * 这个时间单位是: clock tick 即 "时钟打点" + * 可以理解为返回的是毫秒 + * Demo + //记录开始和结束 + clock_t start,stop; + //运行时间,单位为秒 + double duration; + + int main(int argc, char **argv) { + start = clock(); + //TODO 执行目标函数函数 + stop = clock(); + duration = ((double)(stop - start)) / CLK_TCK; + printf("耗费:%f秒\n",duration); + return EXIT_SUCCESS; + } diff --git a/C/lib/c-lib-windows.h b/C/lib/c-lib-windows.h new file mode 100644 index 00000000..9cab631b --- /dev/null +++ b/C/lib/c-lib-windows.h @@ -0,0 +1,6 @@ +---------------------------- +windows | +---------------------------- +Sleep() + * 在阻塞多久后系统继续执行,参数是 int,毫秒 + diff --git a/C/lib/sys/stat.h b/C/lib/sys/stat.h new file mode 100644 index 00000000..521a534d --- /dev/null +++ b/C/lib/sys/stat.h @@ -0,0 +1,32 @@ +------------------- +stat | +------------------ + # 指定路径文件的信息 + + int stat (const char *path, struct stat *); + * 获取文件的属性,成功返回0,失败返回-1 + * path 文件的路径,stat 保存状态的结构体 + * 结构体的定义 + { _dev_t st_dev; /* Equivalent to drive number 0=A 1=B ... */ + _ino_t st_ino; /* Always zero ? */ + _mode_t st_mode; /* See above constants */ + short st_nlink; /* Number of links. */ + short st_uid; /* User: Maybe significant on NT ? */ + short st_gid; /* Group: Ditto */ + _dev_t st_rdev; /* Seems useless (not even filled in) */ + __st_off_t st_size; /* File size in bytes */ + __st_time_t st_atime; /* Access time (always 00:00 on FAT) */ + __st_time_t st_mtime; /* Modified time */ + __st_time_t st_ctime; /* Creation time */ + } + + + S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) + * 判断文件是否是一个目录 + S_ISDIR(fileStat.st_mode); + + S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) + S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) + S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) + S_ISREG(m) (((m) & S_IFMT) == S_IFREG) + * 判断是否一个普通文件 \ No newline at end of file diff --git "a/C/practice/Leetcode\347\273\203\344\271\240\351\242\230-804.c" "b/C/practice/Leetcode\347\273\203\344\271\240\351\242\230-804.c" new file mode 100644 index 00000000..4b541e30 --- /dev/null +++ "b/C/practice/Leetcode\347\273\203\344\271\240\351\242\230-804.c" @@ -0,0 +1,120 @@ +/* +https://leetcode-cn.com/problems/unique-morse-code-words/submissions/ + 国际摩尔斯密码定义一种标准编码方式,将每个字母对应于一个由一系列点和短线组成的字符串, 比如: "a" 对应 ".-", "b" 对应 "-...", "c" 对应 "-.-.", 等等。 + + 为了方便,所有26个英文字母对应摩尔斯密码表如下: + + [".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."] + 给定一个单词列表,每个单词可以写成每个字母对应摩尔斯密码的组合。例如,"cab" 可以写成 "-.-..--...",(即 "-.-." + "-..." + ".-"字符串的结合)。我们将这样一个连接过程称作单词翻译。 + + 返回我们可以获得所有词不同单词翻译的数量。 + + 例如: + 输入: words = ["gin", "zen", "gig", "msg"] + 输出: 2 + 解释: + 各单词翻译如下: + "gin" -> "--...-." + "zen" -> "--...-." + "gig" -> "--...--." + "msg" -> "--...--." + + 共有 2 种不同翻译, "--...-." 和 "--...--.". + + + 注意: + + 单词列表words 的长度不会超过 100。 + 每个单词 words[i]的长度范围为 [1, 12]。 + 每个单词 words[i]只包含小写字母。 + + */ +#include +#include +#include +#include + +char * morse[] = { ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", + "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", + "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.." }; + +struct Node { + char *value; + struct Node * next; +}; + +struct List { + int size; + struct Node * head; +}; + +bool contains(struct List * list, char *value) { + struct Node * node = list->head; + while (node != NULL) { + if (strcmp(node->value, value) == 0) { + return true; + } + node = node->next; + } + return false; +} + +void add(struct List * list, char *value) { + if (list->head == NULL) { + list->head = (struct Node *) calloc(sizeof(struct Node), 1); + list->head->value = value; + list->head->next = NULL; + list->size++; + } else { + if (!contains(list, value)) { + struct Node * node = list->head; + while (node->next != NULL) { + node = node->next; + } + node->next = (struct Node *) calloc(sizeof(struct Node), 1); + node->next->value = value; + node->next->next = NULL; + list->size++; + } + } +} + +void clear(struct List * list) { + if (list != NULL) { + if (list->head != NULL) { + struct Node * node = list->head; + while (node != NULL) { + struct Node * freeNode = node; + node = node->next; + free(freeNode->value); + free(freeNode); + } + } + } +} + +int uniqueMorseRepresentations(char** words, int wordsSize) { + struct List list = { 0, NULL }; + for (int i = 0; i < wordsSize; i++) { + char *word = words[i]; + //摩斯码最长4个字符,一个单词最多12个字符 + char * buf = (char *) calloc(sizeof(char), (4 * 12) + 1); + for (int x = 0; x < strlen(word); x++) { + char ch = word[x]; + //每个字符对应的摩斯码 + char * key = morse[ch - 'a']; + strcat(buf,key); + } + add(&list, buf); + } + int count = list.size; + clear(&list); + return count; +} +int main(int argc, char **argv) { + char * arr[] = { "gin", "zen", "gig", "msg" }; + int result = uniqueMorseRepresentations(arr, 4); + printf("%d\n", result); + return EXIT_SUCCESS; +} + diff --git a/C/practice/guess_number_game.c b/C/practice/guess_number_game.c new file mode 100644 index 00000000..95de1619 --- /dev/null +++ b/C/practice/guess_number_game.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include +//猜数字游戏 +int main(void) { + + //设置种子为时间戳 + srand((unsigned int )time(NULL)); + + //随机四个数 + int random_numbers[4]; + + //输入的数字 + int input_number; + + //输入四个数 + int intput_numbers[4]; + + for(int x = 0 ; x < 4 ; x++){ + random_numbers[x] = rand() % 10; + } + + printf("已经生成了四个随机数\n"); + + while(true){ + printf("请输入随机四位数\n"); + + scanf("%d",&input_number); + + intput_numbers[0] = (input_number / 1000) % 10; + intput_numbers[1] = (input_number / 100) % 10; + intput_numbers[2] = (input_number / 10) % 10; + intput_numbers[3] = input_number % 10; + + int flag = 0; + + for(int x = 0 ;x < 4 ; x++){ + if(intput_numbers[x] > random_numbers[x]){ + printf("%d 位大了\n", x + 1); + }else if(intput_numbers[x] < random_numbers[x]){ + printf("%d 位小了\n", x + 1); + }else{ + printf("%d 位猜对了\n", x + 1); + flag ++; + } + } + + if(flag == 4){ + printf("恭喜你,全部猜对了。"); + Sleep(2000); + break; + } + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/C/practice/max_second.c b/C/practice/max_second.c new file mode 100644 index 00000000..360d771f --- /dev/null +++ b/C/practice/max_second.c @@ -0,0 +1,21 @@ +//获取倒数第二大的数据 + +int max_second(int *p,size_t len){ + + int max = 0; + int second = 0; + + for(int x = 0 ;x < len; x++){ + int v = *(p + x); + if(v > max){ + second = max; + //第二大的值,为上一次的最大值 + max = v; + }else{ + if(v > second){ + second = v; + } + } + } + return second; +} \ No newline at end of file diff --git a/C/practice/my_strcopy.c b/C/practice/my_strcopy.c new file mode 100644 index 00000000..0d83a61b --- /dev/null +++ b/C/practice/my_strcopy.c @@ -0,0 +1,12 @@ +//自己实现字符串的copy + +void strcpy(const char *src,char *dst) { + char c = *src; + while(c != '\0'){ + *dst = c; + dst ++; + c = *(++ src); + } + *dst = '\0'; + return; +} \ No newline at end of file diff --git a/C/practice/pointer_sort.c b/C/practice/pointer_sort.c new file mode 100644 index 00000000..4153ad16 --- /dev/null +++ b/C/practice/pointer_sort.c @@ -0,0 +1,41 @@ +#include +#include + +#define SIZE 10 + +void sort(int *arr,size_t size){ + for(int x = 0 ;x < size ; x++){ + for(int i = x ; i < size ; i++){ + if (*(arr + x) < *(arr + i)){ + int temp = *(arr + x); + *(arr + x) = *(arr + i); + *(arr + i) = temp; + } + } + } +} +int main(void){ + + int arr[SIZE] = {1,9,8,4,7,3,5,2,0,6,}; + + sort(arr,SIZE); + + for(int x = 0 ;x < SIZE ; x++){ + printf("%d\n",arr[x]); + } +} + +//一样的排序方法 +void sort_1(int *p,int len){ + for(int x = 0 ;x < len ; x++){ + for(int y = x ; y < len ; y++){ + int *_x = p + x; + int *_y = p + y; + if(*_x < *_y){ + *_x = *_x ^ *_y; + *_y = *_x ^ *_y; + *_x = *_x ^ *_y; + } + } + } +} \ No newline at end of file diff --git a/C/practice/random_number.c b/C/practice/random_number.c new file mode 100644 index 00000000..579c6538 --- /dev/null +++ b/C/practice/random_number.c @@ -0,0 +1,24 @@ +#include +#include +#include + +//获取随机数 +int random_number(){ + return rand(); +} + +//填充数组 +void fill_random_number(int *arr,size_t size,int (* r)(void)){ + for(int x = 0 ;x < size ; x++){ + *(arr + x) = r(); + } +} +int main(void){ + srand(time(NULL)); + int length = 10; + int arr[length]; + fill_random_number(arr,length,&random_number); + for(int x = 0 ;x < length; x++){ + printf("%d\n",arr[x]); + } +} diff --git a/C/practice/str_practice.c b/C/practice/str_practice.c new file mode 100644 index 00000000..79a12cf8 --- /dev/null +++ b/C/practice/str_practice.c @@ -0,0 +1,54 @@ +#include +#include +#include + +void reverse(char *); +int count(char *,char *); +void trim(char *); + +/** + * + * 字符串练习 + * + */ +int main(int argc, char **argv) { + char buf[] = " \n aaaa \n"; + trim(buf); + printf("%s",buf); + return EXIT_SUCCESS; +} + +void trim(char *p){ + char *start = p; + char *end = p + (strlen(p)) - 1; + while((*start == ' ' || *start == ' ' || *start == '\n') && *start != '\0'){ + start ++; + } + while((*end == ' ' || *end == ' ' || *end == '\n') && end != start){ + end --; + } + *(end + 1) = '\0'; + strcpy(p,start); +} + +int count(char *p,char *sub){ + int count = 0; + char *temp = strstr(p,sub); + while(temp != NULL){ + temp = strstr(temp + strlen(sub),sub); + count ++; + } + return count; +} + +void reverse(char *p){ + int start = 0; + int end = strlen(p) - 1; + while(start < end){ + p[start] = p[start] ^ p[end]; + p[end] = p[start] ^ p[end]; + p[start] = p[start] ^ p[end]; + start ++; + end --; + } +} diff --git "a/C/practice/\343\200\212C Primer Plus\343\200\213\347\273\203\344\271\240\351\242\230.c" "b/C/practice/\343\200\212C Primer Plus\343\200\213\347\273\203\344\271\240\351\242\230.c" new file mode 100644 index 00000000..2a92037a --- /dev/null +++ "b/C/practice/\343\200\212C Primer Plus\343\200\213\347\273\203\344\271\240\351\242\230.c" @@ -0,0 +1,81 @@ +#include +#include +#include + +#define TSIZE 45 + +struct film { + char title[TSIZE]; + int rating; + struct film * next; +}; + +char * s_gets(char *, int); + +int main(int argc, char **argv) { + + struct film * head = NULL; + struct film * prev = NULL; + struct film * current = NULL; + + char input[TSIZE]; + + puts("输入第一个电影的标题"); + + while (s_gets(input, TSIZE) != NULL && input[0] != '\0') { + current = (struct film *) calloc(1, sizeof(struct film)); + if (head == NULL) { + head = current; + } else { + prev->next = current; + } + current->next = NULL; + strcpy(current->title, input); + puts("输入评级<0-10>"); + scanf("%d", ¤t->rating); + while (getchar() != '\n') { + continue; + } + puts("输入下一个电影信息(输入空行停止)"); + prev = current; + } + + if (head == NULL) { + printf("没有输入任何电影信息"); + } else { + printf("输入的电影信息列表:\n"); + } + + current = head; + while (current != NULL) { + printf("电影:%s,评级:%d\n", current->title, current->rating); + current = current->next; + } + + current = head; + while (current != NULL) { + head = current->next; + free(current); + current = head; + } + puts("Bye!"); + return EXIT_SUCCESS; +} + +char * s_gets(char *st, int n) { + char * ret_val; + char * find; + ret_val = fgets(st, n, stdin); + if (ret_val) { + find = strchr(st, '\n'); + if (find) { + *find = '\0'; + } else { + while (getchar() != '\n') { + continue; + } + } + } + return ret_val; +} + diff --git "a/C/practice/\344\272\214\345\210\206\346\220\234\347\264\242\346\240\221.c" "b/C/practice/\344\272\214\345\210\206\346\220\234\347\264\242\346\240\221.c" new file mode 100644 index 00000000..0351ce28 --- /dev/null +++ "b/C/practice/\344\272\214\345\210\206\346\220\234\347\264\242\346\240\221.c" @@ -0,0 +1,128 @@ +#include +#include +#include + +struct Node { + void *value; + void *left; + void *right; +}; + +struct BinarySearchTree { + struct Node * root; + int size; +}; + +typedef struct BinarySearchTree * tree; + +extern tree newTree(); +extern void add(tree, void *, int (*)(const void *, const void *)); +extern bool contains(tree, void *, int (*)(const void *, const void *)); +extern void forEach(tree, void (*)(const void *)); + +tree newTree() {tree tree = (struct BinarySearchTree *) calloc(sizeof(struct BinarySearchTree), 1); + tree->size = 0; + tree->root = NULL; + return tree; +} + +static struct Node * newNode(void *value) { + struct Node * node = (struct Node *) calloc(sizeof(struct Node), 1); + node->value = value; + node->left = NULL; + node->right = NULL; + return node; +} + +static int addNode(struct Node * node, void * value, + int (*compareTo)(const void *, const void *)) { + int result = compareTo(value, node->value); + if (result < 0) { + if (node->left == NULL) { + node->left = newNode(value); + return 1; + } + return addNode(node->left, value, compareTo); + } else if (result > 0) { + if (node->right == NULL) { + node->right = newNode(value); + return 1; + } + return addNode(node->right, value, compareTo); + } + return 0; +} + +void add(tree tree, void * value, int (*compareTo)(const void *, const void *)) { + if (tree->root == NULL) { + tree->root = newNode(value); + tree->size++; + } else { + tree->size += addNode(tree->root, value, compareTo); + } +} + +static bool containsNode(struct Node * node, void * value,int (*compareTo)(const void *, const void *)) { + if (node == NULL) { + return false; + } + int result = compareTo(value, node->value); + if (result < 0) { + return containsNode(node->left, value, compareTo); + } else if (result > 0) { + return containsNode(node->right, value, compareTo); + } + return true; +} + +bool contains(tree tree, void * value, + int (*compareTo)(const void *, const void *)) { + return containsNode(tree->root, value, compareTo); +} + +static void forEachNode(struct Node * node, void (*consumer)(const void *)) { + if (node == NULL) { + return; + } + forEachNode(node->left, consumer); + consumer(node->value); + forEachNode(node->right, consumer); +} + +void forEach(tree tree, void (*consumer)(const void *)) { + forEachNode(tree->root, consumer); +} + +static int compareTo(const void * v1, const void * v2) { + int i1 = *((int *) v1); + int i2 = *((int *) v2); + if (i1 < i2) { + return -1; + } else if (i1 > i2) { + return 1; + } + return 0; +} + +static void consumer(const void *v) { + printf("%d\n", *((int *) v)); +} + +int main(int argc, char **argv) { + tree tree = newTree(); + + int arr[] = { 9, 4, 7, 8, 5, 1, 6, 3, 2, 0 }; + + for (int x = 0; x < 10; x++) { + add(tree, &arr[x], &compareTo); + } + for (int x = 0; x < 10; x++) { + add(tree, &arr[x], &compareTo); + } + + printf("size = %d\n", tree->size); + int x = -1; + printf("%d constains = %d\n", x, contains(tree, &x, &compareTo)); + forEach(tree, &consumer); + return EXIT_SUCCESS; +} diff --git "a/C/practice/\345\215\225\345\220\221\351\223\276\350\241\250.c" "b/C/practice/\345\215\225\345\220\221\351\223\276\350\241\250.c" new file mode 100644 index 00000000..628a656e --- /dev/null +++ "b/C/practice/\345\215\225\345\220\221\351\223\276\350\241\250.c" @@ -0,0 +1,150 @@ +#include +#include +#include +#include + +#define NAME_SIZE 20 +#define MAX_LIST_SIZE 100 + +struct Item { + int id; + char name[NAME_SIZE]; +}; +struct Node { + struct Item * item; + struct Node * next; +}; +struct List { + struct Node * head; + int size; +}; + +extern struct Node * createNode(struct Item *); +extern struct Item * createItem(int, char *); +extern bool addItem(struct List *, struct Item *); +extern struct Item * getItem(struct List *, int); +extern struct Node * getNode(struct List *, int); + +extern struct Item * removeItem(struct List *, int); + +extern void consumer(struct List *, void (*)(void *)); +extern void console(void *); + +int main(int argc, char **argv) { + //创建了一个链表 + struct List list = { .head = NULL, .size = 0 }; + + addItem(&list, createItem(1, "a")); + addItem(&list, createItem(2, "b")); + addItem(&list, createItem(3, "c")); + addItem(&list, createItem(4, "d")); + addItem(&list, createItem(5, "e")); + addItem(&list, createItem(6, "f")); + addItem(&list, createItem(7, "g")); + addItem(&list, createItem(8, "h")); + addItem(&list, createItem(9, "i")); + + consumer(&list, &console); + return EXIT_SUCCESS; +} + +//添加数据到链表 +bool addItem(struct List *list, struct Item *item) { + //创建首节点 + if (list->head == NULL) { + struct Node * node = createNode(item); + list->head = node; + list->size = 1; + return true; + } else if (list->size >= MAX_LIST_SIZE) { + //超出最大长度 + return false; + } + //创建子链节点 + struct Node * node = createNode(item); + + struct Node * next = list->head; + while (next->next != NULL) { + next = next->next; + } + next->next = node; + list->size += 1; + return true; +} +//创建节点 +struct Node * createNode(struct Item *item) { + struct Node * node = calloc(1, sizeof(struct Node)); + node->item = item; + node->next = NULL; + return node; +} +//创建item +struct Item * createItem(int id, char *name) { + struct Item * item = calloc(1, sizeof(struct Item)); + item->id = id; + strncpy(item->name, name, NAME_SIZE - 1); + return item; +} +//根据下标获取node +struct Node * getNode(struct List *list, int index) { + if (list->size == 0) { + return NULL; + } else if (list->size <= index) { + return NULL; + } + + struct Node * node = list->head; + while ((index--) >= 1) { + node = node->next; + } + return node; +} +//根据下标获取node +struct Item * getItem(struct List *list, int index) { + struct Node * node = getNode(list, index); + return node == NULL ? NULL : node->item; +} +//删除指定下标的节点 +struct Item * removeItem(struct List * list, int index) { + if (list->size == 0) { + return NULL; + } else if (list->size <= index) { + return NULL; + } + struct Item * ret_val = NULL; + if (index == 0) { + struct Node * node = list->head; + ret_val = node->item; + list->head = node->next; + free(node); + } else { + struct Node * preNode = getNode(list, index - 1); + struct Node * node = getNode(list, index); + + ret_val = node->item; + if (node->next != NULL) { + preNode->next = node->next; + } else { + preNode->next = NULL; + } + free(node); + + } + list->size -= 1; + return ret_val; +} + + +//对所有节点进行处理 +void consumer(struct List *list, void (*consumer)(void *)) { + struct Node * node = list->head; + while (node != NULL) { + consumer(node->item); + node = node->next; + } +} +//打印一个item +void console(void *valule) { + struct Item * item = ((struct Item *) valule); + printf("id=%d,name=%s\n", item->id, item->name); +} diff --git "a/C/practice/\345\237\272\344\272\216\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\345\270\246\345\260\276\346\214\207\351\222\210\351\230\237\345\210\227.c" "b/C/practice/\345\237\272\344\272\216\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\345\270\246\345\260\276\346\214\207\351\222\210\351\230\237\345\210\227.c" new file mode 100644 index 00000000..2bc4fd98 --- /dev/null +++ "b/C/practice/\345\237\272\344\272\216\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\345\270\246\345\260\276\346\214\207\351\222\210\351\230\237\345\210\227.c" @@ -0,0 +1,94 @@ +#include +#include +#include + +struct Node { + void *value; + struct Node * next; +}; + +struct LinkedList { + int size; + struct Node * head; + struct Node * tail; +}; + +typedef struct LinkedList * queue; + +extern queue newQueue(); /** 实例化一个队列 **/ +int equeue(queue, void *); /**添加一个元素到首**/ +void * dequeue(queue); /**从队列尾获取一个元素**/ + +queue newQueue() { + queue ret = (queue) calloc(sizeof(struct LinkedList), 1); + ret->size = 0; + ret->head = NULL; + ret->tail = NULL; + return ret; +} + +int equeue(queue queue, void *value) { + if (queue == NULL || value == NULL) { + return 1; + } + struct Node * node = calloc(sizeof(struct Node), 1); + node->value = value; + node->next = NULL; + if (queue->head == NULL) { + queue->tail = node; + queue->head = node; + } else { + queue->tail->next = node; + queue->tail = queue->tail->next; + } + queue->size++; + return 0; +} + +void * dequeue(queue queue) { + if (queue == NULL || queue->head == NULL) { + return NULL; + } + struct Node * node = queue->head; + queue->head = node->next; + if (queue->head == NULL) { + queue->tail = NULL; + } + void * ret = node->value; + free(node); + queue->size--; + return ret; +} + +static void consumer(queue queue, void (*accept)(int index, void *)) { + if (queue == NULL || queue->head == NULL || accept == NULL) { + return; + } + struct Node * node = queue->head; + int index = 0; + while (node != NULL) { + accept(index++, node->value); + node = node->next; + } +} + +static void console(int index, void *value) { + int * p = (int *) value; + printf("%d %d\n", index, *p); +} + +int main(int argc, char **argv) { + int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + queue queue = newQueue(); + for (int x = 0; x < 10; x++) { + equeue(queue, &arr[x]); + } + consumer(queue, &console); + printf("size = %d\n", queue->size); + for (int x = 0; x < 10; x++) { + int * value = (int *)dequeue(queue); + printf("dequeue=%d size=%d\n",*value,queue->size); + } + return EXIT_SUCCESS; +} + diff --git "a/C/practice/\345\237\272\344\272\216\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\346\240\210.c" "b/C/practice/\345\237\272\344\272\216\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\346\240\210.c" new file mode 100644 index 00000000..27b78759 --- /dev/null +++ "b/C/practice/\345\237\272\344\272\216\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\346\240\210.c" @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +struct Node { + void *value; + struct Node * next; +}; + +struct LinkedList { + int size; + struct Node * head; +}; + +typedef struct LinkedList * stack; + +int push(stack stack, void *value) { + if (stack == NULL) { + return 1; + } + struct Node * node = (struct Node *) calloc(sizeof(struct Node), 1); + node->value = value; + if (stack->head == NULL) { + stack->head = node; + node->next = NULL; + } else { + node->next = stack->head; + stack->head = node; + } + stack->size++; + return 0; +} +void * pop(stack stack) { + if (stack == NULL || stack->head == NULL) { + return NULL; + } + struct Node * head = stack->head; + stack->head = head->next; + void *value = head->value; + free(head); + stack->size--; + return value; +} + +int main(int argc, char **argv) { + struct LinkedList list = { 0, NULL }; + stack stack = &list; + int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + for (int x = 0; x < 10; x++) { + push(stack, &arr[x]); + } + printf("size = %d\n", stack->size); + for (int x = 0; x < 10; x++) { + int *value = (int *) pop(stack); + printf("pop = %d size = %d\n", *value, stack->size); + } + return EXIT_SUCCESS; +} + diff --git "a/C/practice/\345\270\246\346\214\207\351\222\210\347\273\223\346\236\204\344\275\223\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.c" "b/C/practice/\345\270\246\346\214\207\351\222\210\347\273\223\346\236\204\344\275\223\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.c" new file mode 100644 index 00000000..fa962ec4 --- /dev/null +++ "b/C/practice/\345\270\246\346\214\207\351\222\210\347\273\223\346\236\204\344\275\223\347\232\204\345\272\217\345\210\227\345\214\226\344\270\216\345\217\215\345\272\217\345\210\227\345\214\226.c" @@ -0,0 +1,58 @@ +#include +#include +#include + +struct User { + unsigned int id; + char *name; + size_t name_size; +}; + +void write() { + + char name[] = "KevinBlandy"; + + size_t size = strlen(name); + + struct User user = { 1, NULL }; + + user.name = (char *) malloc(size); + user.name_size = size; + + strcpy(user.name, name); + + FILE *file = fopen("user.data","wb"); + fwrite(&user,sizeof(struct User),1,file); + fflush(file); + + fwrite(user.name,user.name_size,1,file); + fflush(file); + fclose(file); + + printf("wirte:id=%d,name=%s,name_size=%d",user.id,user.name,user.name_size); + free(user.name); +} + +void read(){ + + struct User user; + + FILE *file = fopen("user.data","rb"); + + fread(&user,sizeof(struct User),1,file); + + char *p = malloc(user.name_size + 1); + fread(p,user.name_size,1,file); + + user.name = p; + + printf("read:id=%d,name=%s,name_size=%d",user.id,user.name,user.name_size); + free(user.name); +} + +int main(int argc, char **argv) { + //write(); + read(); + return EXIT_SUCCESS; +} + diff --git "a/C/practice/\346\211\223\345\255\227\345\260\217\346\270\270\346\210\217.c" "b/C/practice/\346\211\223\345\255\227\345\260\217\346\270\270\346\210\217.c" new file mode 100644 index 00000000..aad9b6d0 --- /dev/null +++ "b/C/practice/\346\211\223\345\255\227\345\260\217\346\270\270\346\210\217.c" @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +#define RANDOM_SIZE 40 + +int main() { + + char chars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'z', 'y', 'z' }; + + int len = sizeof(chars) / sizeof(chars[0]); + + srand((signed int) time(NULL)); + + char randomChars[RANDOM_SIZE]; + + for (int x = 0; x < RANDOM_SIZE; x++) { + int randomNumber = rand() % len; + randomChars[x] = chars[randomNumber]; + } + + for (int x = 0; x < RANDOM_SIZE; x++) { + printf("%c", randomChars[x]); + } + printf("\n"); + + for (int x = 0; x < RANDOM_SIZE; x++) { + char ch = _getch(); + if (ch == randomChars[x]) { + printf("%c", ch); + } else { + printf("-"); + } + } + return EXIT_SUCCESS; +} + diff --git "a/C/practice/\346\226\207\344\273\266io\347\273\203\344\271\240.c" "b/C/practice/\346\226\207\344\273\266io\347\273\203\344\271\240.c" new file mode 100644 index 00000000..5a1f8c20 --- /dev/null +++ "b/C/practice/\346\226\207\344\273\266io\347\273\203\344\271\240.c" @@ -0,0 +1,62 @@ +#include +#include +#include + +char * const FILE_NAME = "demo.txt"; +char OPERATOR[] = "+-*/"; + +int main(int argc, char **argv) { + + srand((unsigned int)time(NULL)); + + FILE *file = fopen(FILE_NAME,"w"); + + for(int x = 0 ;x < 10 ;x++ ){ + + int v1 = rand() % 100; + int v2 = rand() % 100; + + char template[100]; + sprintf(template,"%d%c%d=\n",v1,OPERATOR[rand() % 4],v2); + + fputs(template,file); + } + + fclose(file); + + file = fopen(FILE_NAME,"r+"); + + char buf[100]; + + char *line = fgets(buf,sizeof(buf) - 1,file); + + while(line){ + + line = fgets(buf,sizeof(buf) - 1,file); + + int v1; + int v2; + char operator; + + sscanf(buf,"%d%c%d=\n",&v1,&operator,&v2); + + int result; + if (operator == '+'){ + result = v1 + v2; + }else if(operator == '-'){ + result = v1 - v2; + }else if(operator == '*'){ + result = v1 * v2; + }else if(operator == '/'){ + result = v1 / v2; + } + + printf("%d %c %d = %d\n",v1,operator,v2,result); + } + + return EXIT_SUCCESS; +} + + + + diff --git "a/C/practice/\346\250\241\346\213\237cat\345\221\275\344\273\244.c" "b/C/practice/\346\250\241\346\213\237cat\345\221\275\344\273\244.c" new file mode 100644 index 00000000..5f74644c --- /dev/null +++ "b/C/practice/\346\250\241\346\213\237cat\345\221\275\344\273\244.c" @@ -0,0 +1,17 @@ +void cat(char *file_name){ + printf("read file:%s ------------\n",file_name); + FILE *file = fopen(file_name,"r"); + char ch = fgetc(file); + while(!feof(file)){ + printf("%c",ch); + ch = fgetc(file); + } + fclose(file); + free(file); +} + +int main(int argc,char **argv) { + char *file_name = argv[1]; + cat(file_name); + return EXIT_SUCCESS; +} diff --git "a/C/practice/\347\256\200\345\215\225\347\232\204\351\223\276\350\241\250\346\225\260\346\215\256\347\273\223\346\236\204.c" "b/C/practice/\347\256\200\345\215\225\347\232\204\351\223\276\350\241\250\346\225\260\346\215\256\347\273\223\346\236\204.c" new file mode 100644 index 00000000..d971b8e1 --- /dev/null +++ "b/C/practice/\347\256\200\345\215\225\347\232\204\351\223\276\350\241\250\346\225\260\346\215\256\347\273\223\346\236\204.c" @@ -0,0 +1,49 @@ +#include +#include +#include + +struct List { + void *value; + struct List *next; +}; + +void listForEach(struct List **lists,size_t size){ + for(int x = 0 ;x < size ; x++){ + if(x == (size - 1)){ + printf("value = %s ,next = NULL\n",lists[x] -> value); + }else{ + printf("value = %s ,next = %s\n",lists[x] -> value,lists[x] -> next -> value); + } + } +} + +int main(int argc,char **argv) { + + struct List *lists[10]; + + for(int x = 0 ;x < 10 ;x++){ + + //从堆内存获取构造空间 + struct List *list = (struct List *)malloc(sizeof(struct List)); + + //设置value值 + char *value = (char *)malloc(4); + sprintf(value,"[%d]",x); + list -> value = value; + + //链表节点关系 + if (x > 0){ + lists[x - 1] -> next = list; + } + lists[x] = list; + } + + listForEach(lists,10); + + //内存释放 + for(int x = 0 ;x < 10 ;x++ ){ + free(lists[x] -> value); + free(lists[x]); + } + return EXIT_SUCCESS; +} diff --git "a/C/practice/\347\273\203\344\271\240\351\242\230.c" "b/C/practice/\347\273\203\344\271\240\351\242\230.c" new file mode 100644 index 00000000..ad741ecd --- /dev/null +++ "b/C/practice/\347\273\203\344\271\240\351\242\230.c" @@ -0,0 +1,61 @@ +#include +#include +#include + +#define MAX_NAME_SIZE 10 + +struct User { + int id; + char *name; + int height; +}; + +extern int compareTo(const void *, const void *); + +int main(int argc, char **argv) { + + printf("多少人有\n"); + + int size; + scanf("%d", &size); + + //TODO 计算分组数组长度 + + struct User * users1[size]; + struct User * users2[size]; + struct User * users3[size]; + + for (int x = 0; x < size; x++) { + + struct User * user = (struct User *) calloc(sizeof(struct User), 1); + user->id = x; + + printf("请输入第[%d]个人的名字和身高(空格隔开)\n", x + 1); + user->name = (char *) calloc(sizeof(char), MAX_NAME_SIZE); + fscanf(stdin, "%s %d", user->name, &user->height); + + if (x % 3 == 0) { + users1[x] = user; + } else if (x % 3 == 1) { + users2[x] = user; + } else if (x % 3 == 2) { + users3[x] = user; + } + } + + printf("开始排序\n"); + qsort(users1, size, sizeof(struct User *), &compareTo); + qsort(users2, size, sizeof(struct User *), &compareTo); + qsort(users3, size, sizeof(struct User *), &compareTo); + + return EXIT_SUCCESS; +} + +int compareTo(const void *v1, const void *v2) { + struct User **user1 = (struct User **) v1; + struct User **user2 = (struct User **) v2; + int height1 = (*user1)->height; + int height2 = (*user2)->height; + return height1 > height2; +} + diff --git "a/C/practice/\350\207\252\345\212\250\346\211\251\345\256\271\347\232\204\346\225\260\347\273\204.c" "b/C/practice/\350\207\252\345\212\250\346\211\251\345\256\271\347\232\204\346\225\260\347\273\204.c" new file mode 100644 index 00000000..55c7e18a --- /dev/null +++ "b/C/practice/\350\207\252\345\212\250\346\211\251\345\256\271\347\232\204\346\225\260\347\273\204.c" @@ -0,0 +1,114 @@ +#include + +struct IntArray { + int size; //数据长度 + int capacity; //容器长度 + int * arr; //首元素指针 +}; + +int init(struct IntArray *, int); /** 初始化 **/ +int addFirst(struct IntArray *,int); /** 添加元素到开头 **/ +int addLast(struct IntArray *,int); /** 添加元素到末尾 **/ +int add(struct IntArray *, int, int); /** 添加元素到指定的位置 **/ +int set(struct IntArray *, int, int); /** 设置元素到指定的位置 **/ +void forEach(struct IntArray *); /** 遍历,打印元素 **/ +int indexOf(struct IntArray *,int); /** 获取指定元素第一次出现的下标 **/ +int removeIndex(struct IntArray *, int);/** 根据下标删除元素 **/ +void removeItem(struct IntArray *, int);/** 删除第一个指定值的元素 **/ +int removeFirst(struct IntArray *); /** 删除第一个元素 **/ +int removeLast(struct IntArray *); /** 删除最后一个元素 **/ + + +int init(struct IntArray *arr, int capacity) { + if(capacity <= 0){ + return -1; + } + arr->capacity = capacity; + arr->size = 0; + arr->arr = (int *) calloc(sizeof(int), arr->capacity); + return 0; +} + +int addFirst(struct IntArray *arr,int value){ + return add(arr, 0, value); +} + +int addLast(struct IntArray *arr,int value){ + return add(arr, arr->size, value); +} + +int add(struct IntArray *arr, int index, int value) { + if (index < 0 || index > arr->size) { + return -1; + } + if (arr->size == arr->capacity) { + arr->capacity = arr->capacity * 2; + realloc(arr->arr, arr->capacity); + } + + for (int i = arr->size - 1; i >= index; i--) { + arr->arr[i + 1] = arr->arr[i]; + } + arr->arr[index] = value; + arr->size++; + return 0; +} + +int set(struct IntArray *arr, int index, int value) { + if (index < 0 || index >= arr->size) { + return 1; + } + arr->arr[index] = value; + return 0; +} + +int indexOf(struct IntArray *arr,int value){ + for (int i = 0; i < arr->size; i++) { + if(arr->arr[i] == value){ + return i; + } + } + return -1; +} + +int removeIndex(struct IntArray *arr, int index) { + if (index < 0 || index >= arr->size) { + return -1; + } + int value = arr->arr[index]; + for (int i = index + 1; i < arr->size; i++) { + arr->arr[i - 1] = arr->arr[i]; + } + arr->size--; + return value; +} + +int removeFirst(struct IntArray *arr){ + return removeIndex(arr, 0); +} +int removeLast(struct IntArray *arr){ + return removeIndex(arr, arr->size - 1); +} + +void removeItem(struct IntArray *arr, int value) { + int index = indexOf(arr, value); + if(index != -1){ + removeIndex(arr, index); + } +} + +void forEach(struct IntArray *arr) { + printf("size=%d capacity=%d [", arr->size, arr->capacity); + for (int i = 0; i < arr->size; i++) { + printf("%d", arr->arr[i]); + if (i != (arr->size - 1)) { + printf(","); + } + } + printf("]\n"); +} + + + + + diff --git "a/C/practice/\350\257\273\345\217\226\346\216\247\345\210\266\345\217\260\350\276\223\345\205\245.c" "b/C/practice/\350\257\273\345\217\226\346\216\247\345\210\266\345\217\260\350\276\223\345\205\245.c" new file mode 100644 index 00000000..f80f2f60 --- /dev/null +++ "b/C/practice/\350\257\273\345\217\226\346\216\247\345\210\266\345\217\260\350\276\223\345\205\245.c" @@ -0,0 +1,16 @@ +char * s_gets(char *st, int n) { + char * ret_val; + char * find; + ret_val = fgets(st, n, stdin); + if (ret_val) { + find = strchr(st, '\n'); + if (find) { + *find = '\0'; + } else { + while (getchar() != '\n') { + continue; + } + } + } + return ret_val; +} diff --git "a/C/practice/\351\200\206\345\272\217\350\276\223\345\207\272\346\226\207\346\234\254\346\226\207\344\273\266.c" "b/C/practice/\351\200\206\345\272\217\350\276\223\345\207\272\346\226\207\346\234\254\346\226\207\344\273\266.c" new file mode 100644 index 00000000..4c1f01ba --- /dev/null +++ "b/C/practice/\351\200\206\345\272\217\350\276\223\345\207\272\346\226\207\346\234\254\346\226\207\344\273\266.c" @@ -0,0 +1,20 @@ +#include +#include + +int main(int argc, char **argv) { + + FILE *file = fopen("Demo.c", "r"); + + fseek(file, 0L, SEEK_END); + + long size = ftell(file); + + for (long x = 1L; x <= size; x++) { + + fseek(file, -x, SEEK_END); + + printf("%c", fgetc(file)); + } + + return EXIT_SUCCESS; +} diff --git "a/C/practice/\351\200\222\345\275\222\350\256\241\347\256\227\346\225\260\345\200\274\347\232\204\344\272\214\350\277\233\345\210\266.c" "b/C/practice/\351\200\222\345\275\222\350\256\241\347\256\227\346\225\260\345\200\274\347\232\204\344\272\214\350\277\233\345\210\266.c" new file mode 100644 index 00000000..5675bb9a --- /dev/null +++ "b/C/practice/\351\200\222\345\275\222\350\256\241\347\256\227\346\225\260\345\200\274\347\232\204\344\272\214\350\277\233\345\210\266.c" @@ -0,0 +1,8 @@ +void toBinary(unsigned long number) { + int bin; + bin = number % 2; + if (number >= 2) { + toBinary(number / 2); + } + putchar(bin == 0 ? '0' : '1'); +} diff --git "a/C/practice/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204Map.c" "b/C/practice/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204Map.c" new file mode 100644 index 00000000..aff898ba --- /dev/null +++ "b/C/practice/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204Map.c" @@ -0,0 +1,145 @@ +#include +#include +#include +#include + +struct Node { + void *key; + void *value; + struct Node * next; +}; + +struct LinkedList { + int size; + struct Node * head; + bool (*equals)(void *, void *); +}; + +typedef struct LinkedList * map; + +map newMap(bool (*)(void *, void *)); /** 创建一个新的map **/ +extern bool contains(map, void *); /** 是否包含指定的key **/ +void * get(map, void *); /** 根据key获取value **/ +void add(map, void *, void *);/** 添加元素到map **/ +void * set(map map, void *, void *);/** 修改指定的key **/ +void * del(map, void *);/** 删除指定的节点 **/ + +static struct Node * getNode(map map, void *key) { + struct Node * current = map->head; + while (current != NULL) { + if (map->equals(current->key, key)) { + return current; + } + current = current->next; + } + return NULL; +} + +static bool equals(void *v1, void *v2) { + char *c1 = (char*) v1; + char *c2 = (char*) v2; + return strcmp(c1, c2) == 0; +} + +map newMap(bool (*equals)(void *, void *)) { + map newMap = (map) calloc(sizeof(struct LinkedList), 1); + if (newMap != NULL) { + newMap->size = 0; + newMap->head = NULL; + newMap->equals = equals; + } + return newMap; +} + +bool contains(map map, void *key) { + return getNode(map, key) != NULL; +} + +void * get(map map, void *key) { + struct Node * node = getNode(map, key); + return node == NULL ? NULL : node->value; +} + +void add(map map, void *key, void *value) { + struct Node * node = getNode(map, key); + if (node != NULL) { //覆盖 + node->value = value; + } else { //新增 + node = (struct Node *) calloc(sizeof(struct Node), 1); + node->key = key; + node->value = value; + + node->next = map->head; + map->head = node; + map->size++; + } +} + +void * set(map map, void *key, void *value) { + struct Node * node = getNode(map, key); + if (node != NULL) { + void * ret = node->value; + node->value = value; + return ret; + } + return NULL; +} + +void * del(map map, void *key) { + + struct Node dummyHead = { NULL, NULL, map->head }; + + struct Node * pre = &dummyHead; + + struct Node * delNode = NULL; + + //删除节点的父节点 + + while (pre->next != NULL) { + if (map->equals(pre->next->key, key)) { + delNode = pre->next; + pre->next = delNode->next; + map->size--; + map->head = dummyHead.next; + break; + } + pre = pre->next; + } + return delNode == NULL ? NULL : delNode->value; //遍历到了链表尾还是没找到要删除的节点 +} + +static void forEach(map map) { + struct Node * node = map->head; + while (node != NULL) { + printf("%s = %s\n", (char *) node->key, (char *) node->value); + node = node->next; + } +} + +int main(int argc, char **argv) { + + map map = newMap(&equals); + + char * keyArr[] = { "1", "2", "3", "4", "5", "6" }; + char * valueArr[] = { "a", "b", "c", "d", "e", "f" }; + + for (int x = 0; x < 6; x++) { + add(map, keyArr[x], valueArr[x]); + } + + printf("size = %d\n", map->size); + forEach(map); + printf("==============\n"); + for (int x = 0; x < 6; x++) { + char * value = (char *) del(map, keyArr[x]); + printf("del %s\n", value); + } + + forEach(map); + + printf("==============\n"); + + printf("size = %d\n", map->size); + return EXIT_SUCCESS; +} + diff --git "a/Cache/Hash\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.java" "b/Cache/Hash\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.java" new file mode 100644 index 00000000..2d6da737 --- /dev/null +++ "b/Cache/Hash\344\270\200\350\207\264\346\200\247\347\256\227\346\263\225.java" @@ -0,0 +1,6 @@ +----------------------------------------- +Hash一致性算法 | +----------------------------------------- + # 缓存分布式存储的时候, 避免节点新增/删除导致大量的key失效 + 略, 简单 + diff --git "a/Cache/\347\274\223\345\255\230\345\217\214\345\206\231\344\270\200\350\207\264\346\200\247\347\232\204\351\227\256\351\242\230.java" "b/Cache/\347\274\223\345\255\230\345\217\214\345\206\231\344\270\200\350\207\264\346\200\247\347\232\204\351\227\256\351\242\230.java" new file mode 100644 index 00000000..b1c5e94e --- /dev/null +++ "b/Cache/\347\274\223\345\255\230\345\217\214\345\206\231\344\270\200\350\207\264\346\200\247\347\232\204\351\227\256\351\242\230.java" @@ -0,0 +1,106 @@ +------------------------ +几种更新策略 | +------------------------ + # 先更新数据库, 再更新缓存 + + # 先删除缓存, 再更新数据库 + + # 先更新数据库, 再删除缓存 + + # 先更新缓存, 再更新数据库 + * 绝不能用 + * 缓存更新成功, 但是DB更新失败数据回滚了, 缓存中就是脏数据 + * 没并发都容易导致脏数据的模式 + + +------------------------ +先更新数据库, 再更新缓存| +------------------------ + # 一般没人用, 存在2个问题 + 1. 线程的安全问题 + * 同时有请求A和请求B进行更新操作, 那么会出现 + (1)线程A先更新了数据库 + (2)线程B又新了数据库 + (3)线程B先更新了缓存 + (4)线程A又更新了缓存 + + * 请求A更新缓存应该比请求B更新缓存早才对, 但是因为网络等原因,B却比A更早更新了缓存(导致缓存被旧数据覆盖) + * 这就导致了脏数据, 因此不考虑 + + 2. 写多读少的场景问题 + * 写数据库场景比较多, 而读数据场景比较少的业务场景, 采用这种方案就会导致:数据没怎么被读取, 缓存就被频繁的更新, 浪费性能 + * 如果写入缓存的数据, 并不是简单的读取存入, 而是要经过计算获取的情况下, 因为写多读少, 每次更新都去执行一次计算, 更是非常的浪费性能 + + * 做不到延迟加载的效果, 只有第一次读取的时候才计算缓存才是合理的, 而不是每次更新都去计算 + + # 线程安全问题的解决方案: + * CAS算法, 可以给数据添加一个版本号, 每次执行更新 +1 + * 执行更新缓存的时候, 如果缓存的版本号比记录的版本号大, 则不执行更新 + +------------------------ +先删除缓存, 再更新数据库| +------------------------ + # 该方案会导致不一致的原因: + (1)请求A进行写操作,删除缓存 + (2)请求B查询发现缓存不存在 + (3)请求B去数据库查询得到旧值 + (4)请求B将旧值写入缓存 + (5)请求A将新值写入数据库 + + * 因为A的数据库更新请求在写入缓存后执行了, 该值是一个脏数据, 如果缓存没过期时间的话, 这更为可怕 + + # 解决1: + * 先删除缓存, 再更新数据库, 再回写缓存(出现策略一的情况) + + # 解决2: + * 先删除缓存, 再更新数据库, 再删缓存(双删, 第二次删可异步) + + # 解决3: + * 设置缓存的过期时间 + * 缓存有效时间短, 容易发生一段时间内缓存大量失效, 此时的数据库压力突然剧增, 引发缓存雪崩现象 + * 缓存有效时间为随机值减少发生缓存雪崩的可能性 + +------------------------ +先更新数据库, 再删除缓存| +------------------------ + # 老外提出了一个缓存更新套路,名为: Cache-Aside pattern (缓存加持策略??) + * 失效:应用程序先从cache取数据, 没有得到, 则从数据库中取数据, 成功后, 放到缓存中 + * 命中:应用程序从cache中取数据, 取到后返回 + * 更新:先把数据存到数据库中, 成功后, 再让缓存失效 + + * 据说facebook也是这个策略? + + + # 并发问题: + (1)缓存不存在(刚好失效) + (2)请求A查询数据库, 得一个旧值 + (3)请求B执行修改,将新数据写入数据库 + (4)请求B执行删除缓存(缓存此刻还不存在) + (5)请求A将查到的旧值写入缓存 + + * 因为请求A的写入缓存操作(5),在B更新后的删除操作之后(4), 导入了写入缓存的数据是脏数据 + * 根本的原因就是因为: 读比写耗时导致的 + * 实际上数据库的读操作的速度远快于写操作的,不然做读写分离干嘛, 所以这一情形很难出现 + + # 并发导致脏数据的解决方案:设置缓存的过期时间 + + # 缓存删除失败的问题: + * 数据库更新成功, 但是缓存删除失败, 导致了缓存里面是脏数据 + + # 缓存删除失败的问题的解决方案: 删除队列 + 1. 尝试执行缓存的删除 + 2. 如果删除失败, 则添加到队列 + 3. 轮询队列, 尝试删除 + 4. 还是删除失败, 则重复执行步骤 2 + + * 缺点 + * 引入中间件(Queue/MQ), 增加复杂度 + * 对业务线代码造成大量的侵入 + + # 缓存删除失败的问题的解决方案: 订阅DB的 binlog + * 启动一个订阅程序(应用)去订阅数据库的binlog, 获得需要操作的数据 + * 在应用中进行删除操作 + + * mysql中有现成的中间件叫canal, 可以完成订阅binlog日志的功能 + + \ No newline at end of file diff --git "a/Cache/\347\274\223\345\255\230\347\232\204\346\267\230\346\261\260\347\255\226\347\225\245.java" "b/Cache/\347\274\223\345\255\230\347\232\204\346\267\230\346\261\260\347\255\226\347\225\245.java" new file mode 100644 index 00000000..4f978974 --- /dev/null +++ "b/Cache/\347\274\223\345\255\230\347\232\204\346\267\230\346\261\260\347\255\226\347\225\245.java" @@ -0,0 +1,35 @@ +---------------------------- +缓存淘汰策略 | +---------------------------- + + # LFU + * Least Frequency Used, 淘汰使用频率最低的 + * 为每个entry维护一个计数器, 每命中一次+1, 淘汰时找最小的 + + # LRU + * Least Recently Used, 淘汰最近命中时间最早 的entry, 即最久没有被使用过的 + + * 直接的实现需要为每个entry维护最后一次命中的时刻 + + * 也有一个更好的实现方法, 维护一个队列 + * 每次命中将entry移到队列头部,淘汰时找队尾即可, 该元素即最久没有使用过的元素 + + * 该算法既考虑到了时效性, 又容易实现, 是用的最多的evict策略 + + +---------------------------- +LFU | +---------------------------- + #使用JDK提供的实现 + public class LRUCache extends LinkedHashMap { + private static final int MAX_ENTRIES = 3; + + public LRUCache2(){ + super(MAX_ENTRIES+1, .75F,true); // 最后一个参数为true, 表示维护的是access order 而非 insertion order + } + + // 再每次执行插入后, 判断该方法, 如果返回 true, 执行删除链表最后的元素 + protected boolean removeEldestEntry(Map.Entry eldest){ + return this.size() > MAX_ENTRIES; + } + } \ No newline at end of file diff --git "a/CentOS/Centos7/centos7-ssh\350\266\205\346\227\266\346\226\255\345\274\200java.java" "b/CentOS/Centos7/centos7-ssh\350\266\205\346\227\266\346\226\255\345\274\200java.java" new file mode 100644 index 00000000..8549f32d --- /dev/null +++ "b/CentOS/Centos7/centos7-ssh\350\266\205\346\227\266\346\226\255\345\274\200java.java" @@ -0,0 +1,22 @@ + +------------------------------------- +centos7长时间不操作导致连接超时中断 | +------------------------------------- + +1.编辑sshd配置文件 + + # vi /etc/ssh/sshd_config + + 找到 + + #ClientAliveInterval 0 + #ClientAliveCountMax 3 + + 修改为 + + ClientAliveInterval 60 + ClientAliveCountMax 3 + +2.重启sshd服务 + + # systemctl restart sshd diff --git "a/CentOS/Centos7/centos7-\344\277\235\345\255\230\345\220\257\345\212\250\347\250\213\345\272\217\347\232\204pid.java" "b/CentOS/Centos7/centos7-\344\277\235\345\255\230\345\220\257\345\212\250\347\250\213\345\272\217\347\232\204pid.java" new file mode 100644 index 00000000..011c30e6 --- /dev/null +++ "b/CentOS/Centos7/centos7-\344\277\235\345\255\230\345\220\257\345\212\250\347\250\213\345\272\217\347\232\204pid.java" @@ -0,0 +1,10 @@ +-------------------- +保存启动程序的pid | +-------------------- + # nohup java [command] & echo $! > [file].pid + + command 执行启动的命令 + file 保存的pid文件 + + # demo + nohup java -jar socket.jar > socket.log 2 >&1 & echo $! > socket.pid \ No newline at end of file diff --git "a/CentOS/Centos7/centos7-\345\221\275\344\273\244-tomcat.java" "b/CentOS/Centos7/centos7-\345\221\275\344\273\244-tomcat.java" new file mode 100644 index 00000000..a7b26960 --- /dev/null +++ "b/CentOS/Centos7/centos7-\345\221\275\344\273\244-tomcat.java" @@ -0,0 +1,11 @@ +--------------- +Tomcat | +--------------- + # Centos7涓,Tomcat8.5.x鍚姩鍗′綇瑙e喅鏂规 + # 淇敼 $JAVA_HOME/jre/lib/security/java.security 鏂囦欢鐨剆ecurerandom.source閰嶇疆 + * securerandom.source=file:/dev/./urandom + * 绗117琛 + + # 濡傛灉鏄 jar 褰㈠紡鍚姩鐨勮瘽 + java -Djava.security.egd=file:/dev/./urandom -jar app.jar + \ No newline at end of file diff --git "a/CentOS/Centos7/centos7-\350\275\257\344\273\266\345\256\211\350\243\205-mysql.java" "b/CentOS/Centos7/centos7-\350\275\257\344\273\266\345\256\211\350\243\205-mysql.java" index 6f43a313..9f9bf395 100644 --- "a/CentOS/Centos7/centos7-\350\275\257\344\273\266\345\256\211\350\243\205-mysql.java" +++ "b/CentOS/Centos7/centos7-\350\275\257\344\273\266\345\256\211\350\243\205-mysql.java" @@ -32,7 +32,7 @@ -DWITH_PARTITION_STORAGE_ENGINE=1 \ -DWITH_READLINE=1 \ [MySQL的readline library] - -DMYSQL_UNIX_ADDR=/var/run/mysql/mysql.sock \ [MySQL的通讯目录] + -DMYSQL_UNIX_ADDR=/run/mysql/mysql.sock \ [MySQL的通讯目录] -DMYSQL_TCP_PORT=1124 \ [MySQL的监听端口] -DENABLED_LOCAL_INFILE=1 \ [启用加载本地数据] -DENABLE_DOWNLOADS=1 \ [编译时允许自主下载相关文件] @@ -59,7 +59,7 @@ -DWITH_MEMORY_STORAGE_ENGINE=1 \ -DWITH_PARTITION_STORAGE_ENGINE=1 \ -DWITH_READLINE=1 \ --DMYSQL_UNIX_ADDR=/var/run/mysql/mysql.sock \ +-DMYSQL_UNIX_ADDR=/run/mysql/mysql.sock \ -DMYSQL_TCP_PORT=1124 \ -DENABLED_LOCAL_INFILE=1 \ -DENABLE_DOWNLOADS=1 \ @@ -72,7 +72,7 @@ -DWITH_ZLIB:STRING=bundled \ -DDOWNLOAD_BOOST=1 \ -DWITH_BOOST=/usr/local/mysql/boost_1_59_0 - + 4,配置OK后,执行编译安装 make && make install @@ -109,7 +109,7 @@ datadir = /usr/local/mysql/data port = 1124 server_id = 1 - socket = /var/run/mysql/mysql.sock + socket = /run/mysql/mysql.sock 9,执行授权操作 chown mysql /usr/local/mysql -R @@ -140,3 +140,167 @@ 14,开启访问端口 firewall-cmd --add-port=1124/tcp --permanent firewall-cmd --reload + + + +c++: internal compiler error: Killed (program cc1plus) +Please submit a full bug report, +with preprocessed source if appropriate. +See for instructions. +make[2]: *** [unittest/gunit/CMakeFiles/merge_small_tests-t.dir/merge_small_tests.cc.o] Error 4 +make[1]: *** [unittest/gunit/CMakeFiles/merge_small_tests-t.dir/all] Error 2 +make: *** [all] Error 2 + +------------------------ yum安装 +http://blog.csdn.net/xyang81/article/details/51759200 + +1,下载 + https://dev.mysql.com/downloads/repo/yum/ + Red Hat Enterprise Linux 7 / Oracle Linux 7 (Architecture Independent), RPM Package 25.1K + (mysql57-community-release-el7-11.noarch.rpm) + + * mysql 8 + https://repo.mysql.com//mysql80-community-release-el7-1.noarch.rpm + +2,安装yum源 + yum localinstall mysql57-community-release-el7-11.noarch.rpm + + * 查看yum源是否安装成功/etc/yum.repos.d/mysql-community.repo + yum repolist enabled | grep "mysql.*-community.*" + + mysql-connectors-community/x86_64 MySQL Connectors Community 45 + mysql-tools-community/x86_64 MySQL Tools Community 59 + mysql57-community/x86_64 MySQL 5.7 Community Server 247 + + * 修改默认安装的mysql版本 + vim /etc/yum.repos.d/mysql-community.repo + + # Enable to use MySQL 5.5 + [mysql55-community] + enabled=0 # 不安装5.5 + + # Enable to use MySQL 5.6 + [mysql56-community] + enabled=0 # 不安装5.6 + + # Enable to use MySQL 5.7 + [mysql57-community] + enabled=1 # 安装5.7 + + enabled=1 //表示安装 + enabled=0 //表示不安装 + +3,安装mysql服务器 + yum install mysql-community-server + +4,启动mysql服务 + systemctl start mysqld + + * 查看mysql服务状态 + systemctl status mysqld + + * 设置开机启动 + systemctl enable mysqld + systemctl daemon-reload + + +5,修改root本地登录密码 + * 查看临时密码 + less /var/log/mysqld.log | grep 'temporary password' + + * 使用临时密码进行登录 + mysql -uroot -p + + * 执行修改密码方式1 + ALTER USER 'root'@'localhost' IDENTIFIED BY 'new pass'; + + * 执行修改密码方式2 + set password for 'root'@'localhost' = password('new pass'); + + * 注意 + mysql5.7默认安装了密码安全检查插件(validate_password),默认密码检查策略要求密码必须包含:大小写字母,数字和特殊符号,并且长度不能少于8位 + 否则会提示ERROR 1819 (HY000):Your password does not satisfy the current policy requirements + +6,授权用户在远程登录 + GRANT ALL PRIVILEGES ON *.* TO 'KevinBlandy'@'%' IDENTIFIED BY 'pass' WITH GRANT OPTION; + + *.* 任意数据库下的任意数据表 + KevinBlandy 用户名 + % 任意ip + pass 密码 + + flush privileges; + + # mysql 8的需要通过ROLE机制来进行授权 + 1,创建1个或者多个角色 + CREATE ROLE 'app_developer', 'app_read', 'app_write'; + + 2,对创建的角色进行授权 + * 语法 + GRANT [权限] ON [db].[tb] TO [角色] + + * 权限是枚举,可以有多个,或者直接用 ALL 代替,表示所有权限 + SELECT,INSERT, UPDATE, DELETE + + * demo + GRANT ALL ON app_db.* TO 'app_developer'; + GRANT SELECT ON app_db.* TO 'app_read'; + GRANT INSERT, UPDATE, DELETE ON app_db.* TO 'app_write'; + + 3,创建用户 + CREATE USER 'KevinBlandy'@'%' IDENTIFIED BY '123456'; + + 4,授权角色到用户 + * 语法 + GRANT [角色] TO [用户名]@[ip] + + * demo + GRANT 'app_developer' TO 'dev1'@'localhost'; + * 授权用户在指定的ip可以使用一个角色 + GRANT 'app_read' TO 'read_user1'@'localhost', 'read_user2'@'localhost'; + * 授权用户在不同的IP可以使用不同的角色 + GRANT 'app_read', 'app_write' TO 'rw_user1'@'localhost'; + * 授权用户在一个ip可以使用多个角色 + + # 通用 + CREATE ROLE 'app_developer'; + GRANT ALL ON *.* TO 'app_developer'; + CREATE USER 'KevinBlandy'@'%' IDENTIFIED BY '123456'; + GRANT 'app_developer' TO 'KevinBlandy'@'%'; + + + +7,设置默认编码 + * 编辑:vim /etc/my.cnf,在 [mysqld] 配置项中添加配置 + character_set_server=utf8 + init_connect='SET NAMES utf8' + + * 重启mysql服务 + systemctl restart mysqld + + * 登录,查看编码 + show variables like '%character%'; + + +8,默认配置文件路径 + 配置文件: /etc/my.cnf + 日志文件: /var/log//var/log/mysqld.log + 服务启动脚本: /usr/lib/systemd/system/mysqld.service + socket文件: /var/run/mysqld/mysqld.pid + +9,维护命令 + * 启动服务 + systemctl start mysqld + + * 停止服务 + systemctl stop mysqld + + * 重启服务 + systemctl restart mysqld + + * 查看mysql服务状态 + systemctl status mysqld + + * 设置开机启动 + systemctl enable mysqld + systemctl daemon-reload \ No newline at end of file diff --git "a/CentOS/Centos7/centos7-\350\275\257\344\273\266\345\256\211\350\243\205-nodejs.java" "b/CentOS/Centos7/centos7-\350\275\257\344\273\266\345\256\211\350\243\205-nodejs.java" new file mode 100644 index 00000000..6dd8fd08 --- /dev/null +++ "b/CentOS/Centos7/centos7-\350\275\257\344\273\266\345\256\211\350\243\205-nodejs.java" @@ -0,0 +1,10 @@ + +1,下载地址 + https://nodejs.org/en/download/ + +2,解压 + tar -xvf node-v8.11.3-linux-x64.tar.xz + +3,添加到环境变量 + ln -s /usr/local/node-v8.11.3-linux-x64/bin/node /usr/local/bin/ + ln -s /usr/local/node-v8.11.3-linux-x64/bin/npm /usr/local/bin/ \ No newline at end of file diff --git a/CentOS/Linux-JDK.java b/CentOS/Linux-JDK.java index ea250d04..cccbb6ba 100644 --- a/CentOS/Linux-JDK.java +++ b/CentOS/Linux-JDK.java @@ -9,10 +9,10 @@ 4,删除原文件:rm jdk-8u101-linux-x64.tar.gz 5,修改/etc/profile文件(注意版本) #JDK - export JAVA_HOME=/usr/local/java/jdk1.8.0_131 - export JRE_HOME=/usr/local/java/jdk1.8.0_131/jre - export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools/jar:$JRE_HOME/lib:$CLASSPATH - export PATH=$JAVA_HOME/bin/:$PATH +export JAVA_HOME=/usr/local/java/jdk1.8.0_131 +export JRE_HOME=/usr/local/java/jdk1.8.0_131/jre +export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools/jar:$JRE_HOME/lib:$CLASSPATH +export PATH=$JAVA_HOME/bin/:$PATH diff --git a/CentOS/Linux-Nginx.java b/CentOS/Linux-Nginx.java index ab0ce05f..9e870264 100644 --- a/CentOS/Linux-Nginx.java +++ b/CentOS/Linux-Nginx.java @@ -26,14 +26,14 @@ the HTTP rewrite module requires the PCRE library * 缺少正则表达[PCRE]的库 * 解决方案,直接安装依赖就是了 - yum install pcre - yum install pcre-devel + yum -y install pcre + yum -y install pcre-devel the HTTP gzip module requires the zlib library. * 缺少:zlib依赖 * 解决方案,直接安装就是了 - yum install zlib - yum install zlib-devel + yum -y install zlib + yum -y install zlib-devel # rpm安装方式我就不说了,那东西简单.而且.可以用service 命令来进行维护 \ No newline at end of file diff --git a/CentOS/Linux-Redis.java b/CentOS/Linux-Redis.java index 1ab6ca85..ecbcb118 100644 --- a/CentOS/Linux-Redis.java +++ b/CentOS/Linux-Redis.java @@ -37,4 +37,20 @@ * 开启服务 * 直接双击redis-server.exe 启动服务 * 加载配置文件启动服务:redis-server.exe redis.conf - * 双击redis-cli 启动客户端 \ No newline at end of file + * 双击redis-cli 启动客户端 + +----------------------- +Redis-4.0.x安装 | +----------------------- + 1,下载源码解压 + 2,安装依赖 + yum install -y gcc gcc-c++ + + 3,进入源码目录执行编译安装 + make PREFIX=/usr/local/redis install + + PREFIX,指定安装目录 + + 4,复制配置文件到安装目录 + cp *.conf /usr/local/redis/conf/ + diff --git "a/CentOS/Linux-\345\205\245\351\227\250\344\272\206\350\247\243.java" "b/CentOS/Linux-\345\205\245\351\227\250\344\272\206\350\247\243.java" index 77a5fd64..3f47a308 100644 --- "a/CentOS/Linux-\345\205\245\351\227\250\344\272\206\350\247\243.java" +++ "b/CentOS/Linux-\345\205\245\351\227\250\344\272\206\350\247\243.java" @@ -153,6 +153,7 @@ /etc/hosts --> 本地HOST文件 /etc/sysconfig/iptables --> 防火墙 /etc/sysconfig/network --> 主机名 + /etc/motd --> 登录欢迎语 ----------------------- Linux-常规日志文件地址 | diff --git a/CentOS/lrzsz.java b/CentOS/lrzsz.java new file mode 100644 index 00000000..01afb10d --- /dev/null +++ b/CentOS/lrzsz.java @@ -0,0 +1,13 @@ +----------------------------- +lrzsz | +----------------------------- + # 在linux中rz 和 sz 命令允许开发者与主机通过串口进行传递文件 + # 安装 + yum install lrzsz + + # 操作 + sz [file] + * 把远程服务器的文件拉取到本地,存放文件夹取决于ssh客户端的设置 + rz + * 运行该命令会弹出一个文件选择窗口,从本地选择文件上传到Linux服务器 + diff --git "a/Chrome\346\217\222\344\273\266\345\274\200\345\217\221/chrome.js" "b/Chrome\346\217\222\344\273\266\345\274\200\345\217\221/chrome.js" new file mode 100644 index 00000000..e97f0ebd --- /dev/null +++ "b/Chrome\346\217\222\344\273\266\345\274\200\345\217\221/chrome.js" @@ -0,0 +1,5 @@ +------------------------------ +chrome插件开发 | +------------------------------ + # 参考 + https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html \ No newline at end of file diff --git a/Codings/AESUtils.java b/Codings/AESUtils.java new file mode 100644 index 00000000..da604cce --- /dev/null +++ b/Codings/AESUtils.java @@ -0,0 +1,40 @@ +package io.springboot.utils; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; +/** + * + * AES + * @author KevinBlandy + * + */ +public class AESUtils { + + private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding"; + + //鑾峰彇 cipher + private static Cipher getCipher(byte[] key,int model) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException { + SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance(AES_ALGORITHM); + cipher.init(model, secretKeySpec); + return cipher; + } + + //AES鍔犲瘑 + public static byte[] encrypt(byte[] data,byte[] key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { + Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE); + return cipher.doFinal(data); + } + + //AES瑙e瘑 + public static byte[] decrypt(byte[] data,byte[] key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{ + Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE); + return cipher.doFinal(data); + } +} diff --git a/Codings/AccessLogFilter.java b/Codings/AccessLogFilter.java new file mode 100644 index 00000000..042dae0f --- /dev/null +++ b/Codings/AccessLogFilter.java @@ -0,0 +1,112 @@ +package io.javaweb.paste.web.filter; + +import java.io.IOException; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.servlet.DispatcherType; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; +/** + * + * + * @author KevinBlandy + * + */ +@WebFilter(displayName = "accessLogFilter", urlPatterns = "/*", dispatcherTypes = { DispatcherType.REQUEST }) +public class AccessLogFilter extends HttpFilter { + + /** + * + */ + private static final long serialVersionUID = -2829826365107767069L; + + private static final Logger LOGGER = LoggerFactory.getLogger(AccessLogFilter.class); + + @Override + protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) + throws IOException, ServletException { + + String uri = req.getRequestURI(); + String method = req.getMethod(); + String remoteAddr = req.getRemoteAddr(); + + LOGGER.info("鈹 [client] 鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹"); + LOGGER.info("鈹 uri: {}", uri); + LOGGER.info("鈹 method: {}", method); + LOGGER.info("鈹 remoteAddr: {}", remoteAddr); + LOGGER.info("鈹 鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹"); + + // headers + Enumeration headerEnumeration = req.getHeaderNames(); + LOGGER.info("鈹 [Headers] 鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣"); + while (headerEnumeration.hasMoreElements()) { + String headerName = headerEnumeration.nextElement(); + String headerValue = req.getHeader(headerName); + LOGGER.info("鈹 {}: {}", headerName, headerValue); + } + LOGGER.info("鈹 鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹"); + + // body + String contentType = req.getHeader(HttpHeaders.CONTENT_TYPE); + LOGGER.info("鈹 [body] 鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣"); + LOGGER.info("鈹 Content-Type: {}", contentType); + LOGGER.info("鈹 "); + if (!StringUtils.isEmpty(contentType)) { + MediaType mediaType = MediaType.parseMediaType(contentType); + if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || MediaType.TEXT_PLAIN.isCompatibleWith(mediaType)) { // json/text + + LOGGER.debug("鈹 {}", "Unreadable!!!"); // TODO 娴侀噸鐢 + + } else if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) { // form + Map parameterMap = req.getParameterMap(); + parameterMap.entrySet().forEach(entry -> { + LOGGER.debug("鈹 {}={}", entry.getKey(), Stream.of(entry.getValue()).collect(Collectors.joining(",", "", ""))); + }); + } else if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType)) { // multipart + Collection parts = req.getParts(); + parts.forEach(part -> { + + String name = part.getName(); + String submittedFileName = part.getSubmittedFileName(); + Collection partHeaderNames = part.getHeaderNames(); + + LOGGER.debug("鈹 鈹 {} 鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹", name); + if(!StringUtils.isEmpty(submittedFileName)) { + LOGGER.debug("鈹 鈹 FileName: {} ", submittedFileName); + } + try { + LOGGER.debug("鈹 鈹 size: {}", part.getInputStream().available()); + } catch (IOException e) { + e.printStackTrace(); + LOGGER.debug("鈹 鈹 size: {}", "Unknown"); + } + + partHeaderNames.forEach(header -> { + LOGGER.debug("鈹 鈹 {}: {}",header, part.getHeaders(header).stream().collect(Collectors.joining(",", "", ""))); + }); + + LOGGER.debug("鈹 鈹 鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹鈹"); + }); + } + } else { + LOGGER.info("鈹 Unsupported Content-Type Or Request Body Is None !!!"); + } + LOGGER.info("鈹 鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹佲攣鈹"); + super.doFilter(req, res, chain); + } +} diff --git a/Codings/AvatarUtils.java b/Codings/AvatarUtils.java new file mode 100644 index 00000000..165e902e --- /dev/null +++ b/Codings/AvatarUtils.java @@ -0,0 +1,74 @@ +package io.springcloud.utils; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Random; + +import javax.imageio.ImageIO; + +public class AvatarUtils { + + /** + * 鏍规嵁id鐢熸垚鐢ㄦ埛澶村儚 + * @param id + * @return + * @throws IOException + */ + public static byte[] create(int id) throws IOException { + // 姣忎釜灏忓潡鍧楃殑澶у皬 + int width = 20; + // 5 x 5 + int grid = 5; + // 鍥涜竟濉厖 + int padding = width / 2; + int size = width * grid + width; + BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB); + Graphics2D _2d = img.createGraphics(); + _2d.setColor(new Color(240, 240, 240)); + _2d.fillRect(0, 0, size, size); + _2d.setColor(randomColor(80, 200)); + char[] idchars = createIdent(id); + int i = idchars.length; + for (int x = 0; x < Math.ceil(grid / 2.0); x++) { + for (int y = 0; y < grid; y++) { + if (idchars[--i] < 53) { + _2d.fillRect((padding + x * width), (padding + y * width), width, width); + if (x < Math.floor(grid / 2)) { + _2d.fillRect((padding + ((grid - 1) - x) * width), (padding + y * width), width, width); + } + } + } + } + _2d.dispose(); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ImageIO.write(img, "png", byteArrayOutputStream); + return byteArrayOutputStream.toByteArray(); + } + + private static Color randomColor(int fc, int bc) { + Random random = new Random(); + if (fc > 255) { + fc = 255; + } + if (bc > 255) { + bc = 255; + } + int r = fc + random.nextInt(Math.abs(bc - fc)); + int g = fc + random.nextInt(Math.abs(bc - fc)); + int b = fc + random.nextInt(Math.abs(bc - fc)); + return new Color(r, g, b); + } + + private static char[] createIdent(int id) { + BigInteger bi_content = new BigInteger((id + "").getBytes()); + BigInteger bi = new BigInteger(id + "identicon" + id, 36); + bi = bi.xor(bi_content); + return bi.toString(10).toCharArray(); + } +} + + diff --git a/Codings/EncryptionUtils.java b/Codings/EncryptionUtils.java new file mode 100644 index 00000000..d175f748 --- /dev/null +++ b/Codings/EncryptionUtils.java @@ -0,0 +1,245 @@ +package io.springboot.utils; + +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; +/** + * + * @author KevinBlandy + * + */ +public class EncryptionUtils { + + /***************** 闈炲绉板瘑閽ョ畻娉 **********************/ + + //rsa绠楁硶 + public static final String RSA_ALGORITHM = "RSA"; + + /** + * 瀵嗛挜闀垮害,DH绠楁硶鐨勯粯璁ゅ瘑閽ラ暱搴︽槸1024 + * 瀵嗛挜闀垮害蹇呴』鏄64鐨勫嶆暟,鍦512鍒65536浣嶄箣闂 + */ + private static final int KEY_SIZE = 1024; + + //鏁板瓧绛惧悕绠楁硶 + private static final String SIGNATURE_ALGORITHM = "MD5withRSA"; + + //鍏挜key + public static final String PUBLIC_KEY = "RSAPublicKey"; + + //绉侀挜key + public static final String PRIVATE_KEY = "RSAPrivateKey"; + + /** + * 鐢熸垚鍏挜鍜屽瘑閽 + * @return + * @throws Exception + */ + public static Map initKey() throws NoSuchAlgorithmException { + //瀹炰緥鍖栧瘑閽ョ敓鎴愬櫒 + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM); + //鍒濆鍖栧瘑閽ョ敓鎴愬櫒 + keyPairGenerator.initialize(KEY_SIZE); + //鐢熸垚瀵嗛挜瀵 + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + //鍏挜 + RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); + //绉侀挜 + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); + //灏嗗瘑閽ュ瓨鍌ㄥ湪map涓 + Map keyMap = new HashMap(); + keyMap.put(PUBLIC_KEY, rsaPublicKey); + keyMap.put(PRIVATE_KEY, rsaPrivateKey); + return keyMap; + } + + /** + * 绉侀挜鍔犲瘑 + * @param source + * @param key + * @return + * @throws Exception + */ + public static byte[] encryptByPrivateKey(byte[] source, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + //鍙栧緱绉侀挜 + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(key); + //瀹炰緥鍖栧瘑閽ュ伐鍘 + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + //鐢熸垚绉侀挜 + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + //鏁版嵁鍔犲瘑 + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + return cipher.doFinal(source); + } + + /** + * 鍏挜鍔犲瘑 + * @param source + * @param key + * @return + * @throws Exception + */ + public static byte[] encryptByPublicKey(byte[] source, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + //瀹炰緥鍖栧瘑閽ュ伐鍘 + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + //瀵嗛挜鏉愭枡杞崲 + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(key); + //浜х敓鍏挜 + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); + //鏁版嵁鍔犲瘑 + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + return cipher.doFinal(source); + } + + + /** + * 绉侀挜瑙e瘑 + * @param source + * @param key + * @return + * @throws Exception + */ + public static byte[] decryptByPrivateKey(byte[] source, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + //鍙栧緱绉侀挜 + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(key); + //瀹炰緥鍖栧瘑閽ュ伐鍘 + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + //鐢熸垚绉侀挜 + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + //鏁版嵁瑙e瘑 + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + return cipher.doFinal(source); + } + + /** + * 鍏挜瑙e瘑 + * @param source + * @param key + * @return byte + */ + public static byte[] decryptByPublicKey(byte[] source, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException { + //瀹炰緥鍖栧瘑閽ュ伐鍘 + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + //瀵嗛挜鏉愭枡杞崲 + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(key); + //浜х敓鍏挜 + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); + //鏁版嵁瑙e瘑 + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, publicKey); + return cipher.doFinal(source); + } + + /** + * 浣跨敤绉侀挜瀵逛俊鎭敓鎴愭暟瀛楃鍚 + * @param source + * @param privateKey + * @return + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws InvalidKeyException + * @throws SignatureException + */ + public static byte[] signature(byte[] source, byte[] privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { + // 鏋勯燩KCS8EncodedKeySpec瀵硅薄 + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey); + // rsa鍔犲瘑绠楁硶 + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + // 鐢熸垚绉侀挜瀵硅薄 + PrivateKey _privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + // 鐢ㄧ閽ュ淇℃伅鐢熸垚鏁板瓧绛惧悕 + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initSign(_privateKey); + signature.update(source); + return signature.sign(); + } + + /** + * 鏍¢獙鏁板瓧绛惧悕 + * @param source + * @param publicKey + * @param sign + * @return + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws InvalidKeyException + * @throws SignatureException + * @throws Exception + */ + public static boolean signatureVerify(byte[] source, byte[] publicKey, byte[] sign) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException{ + // 鏋勯燲509EncodedKeySpec瀵硅薄 + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey); + // KEY_ALGORITHM 鎸囧畾鐨勫姞瀵嗙畻娉 + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + // 鍙栧叕閽ュ寵瀵硅薄 + PublicKey _pubKey = keyFactory.generatePublic(x509EncodedKeySpec); + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initVerify(_pubKey); + signature.update(source); + // 楠岃瘉绛惧悕鏄惁姝e父 + return signature.verify(sign); + } + + /***************** 瀵圭О鍔犲瘑 **********************/ + //aes绠楁硶 + private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding"; + + /** + * AES鍔犲瘑 + * @param source + * @param key + * @return + * @throws InvalidKeyException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + public static byte[] encryptByAES(byte[] source,byte[] key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { + SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance(AES_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + return cipher.doFinal(source); + } + + /** + * AES瑙e瘑 + * @param source + * @param key + * @return + * @throws InvalidKeyException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws IllegalBlockSizeException + * @throws BadPaddingException + */ + public static byte[] decryptByAES(byte[] source,byte[] key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{ + SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance(AES_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + return cipher.doFinal(source); + } +} diff --git a/Codings/ExportCert.java b/Codings/ExportCert.java new file mode 100644 index 00000000..37a39a20 --- /dev/null +++ b/Codings/ExportCert.java @@ -0,0 +1,93 @@ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.security.Key; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.util.Base64; +import java.util.Base64.Encoder; + +/** + * + * 从keystore导出证书,私钥,公钥 + * + */ +public class ExportCert { + + // 导出证书 base64格式 + public static void exportCert(KeyStore keystore, String alias, String exportFile) throws Exception { + Certificate cert = keystore.getCertificate(alias); + Encoder encoder = Base64.getEncoder(); + String encoded = new String(encoder.encode(cert.getEncoded())); + FileWriter fw = new FileWriter(exportFile); + fw.write("-----BEGIN CERTIFICATE-----\r\n"); // 非必须 + fw.write(encoded); + fw.write("\r\n-----END CERTIFICATE-----"); // 非必须 + fw.close(); + } + + // 得到KeyPair + public static KeyPair getKeyPair(KeyStore keystore, String alias, char[] password) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException { + Key key = keystore.getKey(alias, password); + if (key instanceof PrivateKey) { + Certificate cert = keystore.getCertificate(alias); + PublicKey publicKey = cert.getPublicKey(); + return new KeyPair(publicKey, (PrivateKey) key); + } + return null; + } + + // 导出私钥 + public static void exportPrivateKey(PrivateKey privateKey, String exportFile) throws Exception { + Encoder encoder = Base64.getEncoder(); + String encoded = new String(encoder.encode(privateKey.getEncoded())); + FileWriter fw = new FileWriter(exportFile); + fw.write("—–BEGIN PRIVATE KEY—–\r\n"); // 非必须 + fw.write(encoded); + fw.write("\r\n—–END PRIVATE KEY—–"); // 非必须 + fw.close(); + } + + // 导出公钥 + public static void exportPublicKey(PublicKey publicKey, String exportFile) throws Exception { + Encoder encoder = Base64.getEncoder(); + String encoded = new String(encoder.encode(publicKey.getEncoded())); + FileWriter fw = new FileWriter(exportFile); + fw.write("—–BEGIN PUBLIC KEY—–\r\n"); // 非必须 + fw.write(encoded); + fw.write("\r\n—–END PUBLIC KEY—–"); // 非必须 + fw.close(); + } + + public static void main(String args[]) throws Exception { + + // keysotre文件和密码 + String keysotreFile = "C:\\Users\\Administrator\\Desktop\\ssl\\server\\server.keystore"; + String password = "123456"; + + // 加载keystore文件 + KeyStore keystore = KeyStore.getInstance("JKS"); + keystore.load(new FileInputStream(new File(keysotreFile)), password.toCharArray()); + + // 证书的别名 + String alias = "server"; + String exportCertFile = "C:\\Users\\Administrator\\Desktop\\ssl\\server.cer"; + String exportPrivateFile = "C:\\Users\\Administrator\\Desktop\\ssl\\serverPrivateKey.txt"; + String exportPublicFile = "C:\\Users\\Administrator\\Desktop\\ssl\\serverPublicKey.txt"; + + // 导出证书到指定目录 + ExportCert.exportCert(keystore, alias, exportCertFile); + + // 导出公钥和私钥到指定目录 + KeyPair keyPair = ExportCert.getKeyPair(keystore, alias, password.toCharArray()); + ExportCert.exportPrivateKey(keyPair.getPrivate(), exportPrivateFile); + ExportCert.exportPublicKey(keyPair.getPublic(), exportPublicFile); + } +} \ No newline at end of file diff --git a/Codings/RSAUtils.java b/Codings/RSAUtils.java new file mode 100644 index 00000000..5c3517c9 --- /dev/null +++ b/Codings/RSAUtils.java @@ -0,0 +1,226 @@ +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +/** + * + * RSA + * + * @author KevinBlandy + * + */ +public class RSAUtils { + + // rsa绠楁硶 + public static final String RSA_ALGORITHM = "RSA"; + + // 瀵嗛挜闀垮害 + private static final int KEY_SIZE = 2048; + + // 鏁板瓧绛惧悕绠楁硶 + private static final String SIGNATURE_ALGORITHM = "MD5withRSA"; + + // 鍏挜key + public static final String PUBLIC_KEY = "RSAPublicKey"; + + // 绉侀挜key + public static final String PRIVATE_KEY = "RSAPrivateKey"; + + // 鏈澶х殑鍔犲瘑鏄庢枃闀垮害 + public static final int MAX_ENCRYPT_BLOCK = 245; + + // 鏈澶х殑瑙e瘑瀵嗘枃闀垮害 + public static final int MAX_DECRYPT_BLOCK = 256; + + // 鍒濆鍖栧叕閽ュ拰瀵嗛挜 + public static Map initKey() throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM); + keyPairGenerator.initialize(KEY_SIZE); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); + Map keyMap = new HashMap<>(); + keyMap.put(PUBLIC_KEY, rsaPublicKey); + keyMap.put(PRIVATE_KEY, rsaPrivateKey); + return keyMap; + } + + // 鏍规嵁绉侀挜鑾峰彇cipher + private static Cipher getCipherByPrivateKey(byte[] privateKey, int mode)throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey); + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + PrivateKey _privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(mode, _privateKey); + return cipher; + } + + // 鏍规嵁鍏挜鑾峰彇cipher + private static Cipher getCipherByPublicKey(byte[] publicKey, int mode) + throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException { + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey); + PublicKey _publicKey = keyFactory.generatePublic(x509EncodedKeySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(mode, _publicKey); + return cipher; + } + + // 浣跨敤绉侀挜鍔犲瘑鏁版嵁 + public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey) + throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, + IllegalBlockSizeException, BadPaddingException { + Cipher cipher = getCipherByPrivateKey(privateKey, Cipher.ENCRYPT_MODE); + return cipher.doFinal(data); + } + + // 浣跨敤绉侀挜瑙e瘑鏁版嵁 + public static byte[] decryptByPrivateKey(byte[] data, byte[] privateKey)throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,BadPaddingException, IllegalBlockSizeException { + Cipher cipher = getCipherByPrivateKey(privateKey, Cipher.DECRYPT_MODE); + return cipher.doFinal(data); + } + + // 浣跨敤鍏挜鍔犲瘑鏁版嵁 + public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey)throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,BadPaddingException, IllegalBlockSizeException { + Cipher cipher = getCipherByPublicKey(publicKey, Cipher.ENCRYPT_MODE); + return cipher.doFinal(data); + } + + // 浣跨敤鍏挜瑙e瘑鏁版嵁 + public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey) + throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, BadPaddingException, + IllegalBlockSizeException, NoSuchPaddingException { + Cipher cipher = getCipherByPublicKey(publicKey, Cipher.DECRYPT_MODE); + return cipher.doFinal(data); + } + + // 鏍规嵁绉侀挜鐢熸垚鏁板瓧绛惧悕 + public static byte[] signature(byte[] data, byte[] privateKey) + throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey); + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + PrivateKey _privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initSign(_privateKey); + signature.update(data); + return signature.sign(); + } + + // 鏍规嵁鍏瘬鏍¢獙鏁板瓧绛惧悕 + public static boolean signatureVerify(byte[] data, byte[] publicKey, byte[] signature) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey); + KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); + PublicKey _pubKey = keyFactory.generatePublic(x509EncodedKeySpec); + Signature _signature = Signature.getInstance(SIGNATURE_ALGORITHM); + _signature.initVerify(_pubKey); + _signature.update(data); + return _signature.verify(signature); + } + + + // 鍒嗘鐨勫姞瀵嗚В瀵 + + private static byte[] segmentDoFinal(InputStream inputStream,Cipher cipher,int maxBlock) throws IOException, IllegalBlockSizeException, BadPaddingException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] bytes = new byte[maxBlock]; + int length; + while ((length = inputStream.read(bytes)) != -1) { + byte[] result = cipher.doFinal(bytes, 0, length); + byteArrayOutputStream.write(result); + } + return byteArrayOutputStream.toByteArray(); + } + + // 绉侀挜鍔犲瘑 + public static byte[] encryptByPrivateKeySegment(InputStream inputStream, byte[] privateKey) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, IOException { + Cipher cipher = getCipherByPrivateKey(privateKey, Cipher.ENCRYPT_MODE); + return segmentDoFinal(inputStream, cipher, MAX_ENCRYPT_BLOCK); + } + + // 绉侀挜瑙e瘑 + public static byte[] decryptByPrivateKeySegment(InputStream inputStream, byte[] privateKey) throws IllegalBlockSizeException, BadPaddingException, IOException, InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException { + Cipher cipher = getCipherByPrivateKey(privateKey, Cipher.DECRYPT_MODE); + return segmentDoFinal(inputStream, cipher, MAX_DECRYPT_BLOCK); + } + + // 鍏挜鍔犲瘑 + public static byte[] encryptByPublicKeySegment(InputStream inputStream, byte[] publicKey)throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,BadPaddingException, IllegalBlockSizeException, IOException { + Cipher cipher = getCipherByPublicKey(publicKey, Cipher.ENCRYPT_MODE); + return segmentDoFinal(inputStream, cipher, MAX_ENCRYPT_BLOCK); + } + + // 鍏挜瑙e瘑 + public static byte[] decryptByPublicKeySegment(InputStream inputStream, byte[] publicKey)throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, BadPaddingException,IllegalBlockSizeException, NoSuchPaddingException, IOException { + Cipher cipher = getCipherByPublicKey(publicKey, Cipher.DECRYPT_MODE); + return segmentDoFinal(inputStream, cipher, MAX_DECRYPT_BLOCK); + } + + private static void segmentDoFinal(InputStream inputStream,Cipher cipher,int maxBlock,OutputStream outputStream) throws IllegalBlockSizeException, BadPaddingException, IOException { + byte[] bytes = new byte[maxBlock]; + int length; + while ((length = inputStream.read(bytes)) != -1) { + byte[] result = cipher.doFinal(bytes, 0, length); + outputStream.write(result); + } + } + + // 绉侀挜鍔犲瘑 + public static void encryptByPrivateKeySegment(InputStream inputStream, byte[] privateKey,OutputStream outputStream) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, IOException { + Cipher cipher = getCipherByPrivateKey(privateKey, Cipher.ENCRYPT_MODE); + segmentDoFinal(inputStream, cipher, MAX_ENCRYPT_BLOCK,outputStream); + } + + // 绉侀挜瑙e瘑 + public static void decryptByPrivateKeySegment(InputStream inputStream, byte[] privateKey,OutputStream outputStream) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, IOException { + Cipher cipher = getCipherByPrivateKey(privateKey, Cipher.DECRYPT_MODE); + segmentDoFinal(inputStream, cipher, MAX_DECRYPT_BLOCK,outputStream); + } + + // 鍏挜鍔犲瘑 + public static void encryptByPublicKeySegment(InputStream inputStream, byte[] publicKey,OutputStream outputStream)throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,BadPaddingException, IllegalBlockSizeException, IOException { + Cipher cipher = getCipherByPublicKey(publicKey, Cipher.ENCRYPT_MODE); + segmentDoFinal(inputStream, cipher, MAX_ENCRYPT_BLOCK,outputStream); + } + + // 鍏挜瑙e瘑 + public static void decryptByPublicKeySegment(InputStream inputStream, byte[] publicKey,OutputStream outputStream)throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, BadPaddingException,IllegalBlockSizeException, NoSuchPaddingException, IOException { + Cipher cipher = getCipherByPublicKey(publicKey, Cipher.DECRYPT_MODE); + segmentDoFinal(inputStream, cipher, MAX_DECRYPT_BLOCK,outputStream); + } +} + + + + + + + + + + + + + diff --git a/Codings/ThumbnailatorHelper.java b/Codings/ThumbnailatorHelper.java new file mode 100644 index 00000000..768cfc9d --- /dev/null +++ b/Codings/ThumbnailatorHelper.java @@ -0,0 +1,29 @@ +--------------------- +Thumbnailator类库 | +--------------------- + + + + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import net.coobird.thumbnailator.Thumbnails; + +public class ThumbnailatorHelper { + + /** + * 压缩图片 + * @param inputStream + * @param quality + * @return + * @throws IOException + */ + public byte[] compress(InputStream inputStream, float quality) throws IOException { + try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + Thumbnails.of(inputStream).scale(1f).outputQuality(quality).toOutputStream(byteArrayOutputStream); + return byteArrayOutputStream.toByteArray(); + } + } +} diff --git a/Codings/TreeFormatJson.java b/Codings/TreeFormatJson.java new file mode 100644 index 00000000..126dce4f --- /dev/null +++ b/Codings/TreeFormatJson.java @@ -0,0 +1,113 @@ +// 把树形结构, 序列化为JSON + +/** +CREATE TABLE `foo` ( + `id` INT ( 10 ) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR ( 255 ) COLLATE utf8mb4_croatian_ci DEFAULT NULL, + `parent_id` INT ( 10 ) UNSIGNED DEFAULT NULL, +PRIMARY KEY ( `id` ) +) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_croatian_ci; + + */ +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; + +import org.sql2o.Connection; +import org.sql2o.Sql2o; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; + +class Foo { + private Integer id; + private String name; + private Integer parentId; + private List childs; + + public Foo(int id, String name, Integer parentId) { + super(); + this.id = id; + this.name = name; + this.parentId = parentId; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public List getChilds() { + return childs; + } + + public void setChilds(List childs) { + this.childs = childs; + } + + @Override + public String toString() { + return "Foo [id=" + id + ", name=" + name + ", parentId=" + parentId + "]"; + } +} + +public class Main { + public static void main(String[] args) throws SQLException { + System.out.println(JSON.toJSONString(getFoos(), SerializerFeature.PrettyFormat)); + + } + + public static List getFoos() { + Sql2o sql2o = new Sql2o( + "jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=GMT%2b8", + "root", "root"); + + Connection connection = sql2o.beginTransaction(); + + // 检索顶级记录 + List foos = connection.createQuery("SELECT * FROM `foo` WHERE `parent_id` = 0;") + .addColumnMapping("parent_id", "parentId").executeAndFetch(Foo.class); + + LinkedList queue = new LinkedList(); + for (Foo foo : foos) { + queue.addLast(foo); + } + + // 遍历所有子记录 + while (!queue.isEmpty()) { + + Foo foo = queue.pollLast(); + + List subFoos = connection.createQuery("SELECT * FROM `foo` WHERE `parent_id` = :id;") + .addParameter("id", foo.getId()).addColumnMapping("parent_id", "parentId") + .executeAndFetch(Foo.class); + + if (!subFoos.isEmpty()) { + for (Foo subFoo : subFoos) { + queue.addLast(subFoo); + } + foo.setChilds(subFoos); + } + } + return foos; + } +} diff --git a/Discourse/discourse-app.yml b/Discourse/discourse-app.yml new file mode 100644 index 00000000..cf89f10f --- /dev/null +++ b/Discourse/discourse-app.yml @@ -0,0 +1,108 @@ +## this is the all-in-one, standalone Discourse Docker container template +## +## After making changes to this file, you MUST rebuild +## /var/discourse/launcher rebuild app +## +## BE *VERY* CAREFUL WHEN EDITING! +## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT! +## visit http://www.yamllint.com/ to validate this file as needed + +templates: + - "templates/postgres.template.yml" + - "templates/redis.template.yml" + - "templates/web.template.yml" + - "templates/web.ratelimited.template.yml" +## Uncomment these two lines if you wish to add Lets Encrypt (https) + - "templates/web.ssl.template.yml" + - "templates/web.letsencrypt.ssl.template.yml" + +## which TCP/IP ports should this container expose? +## If you want Discourse to share a port with another webserver like Apache or nginx, +## see https://meta.discourse.org/t/17247 for details +expose: + - "80:80" # http + - "443:443" # https + +params: + db_default_text_search_config: "pg_catalog.english" + + ## Set db_shared_buffers to a max of 25% of the total memory. + ## will be set automatically by bootstrap based on detected RAM, or you can override + db_shared_buffers: "768MB" + + ## can improve sorting performance, but adds memory usage per-connection + #db_work_mem: "40MB" + + ## Which Git revision should this container use? (default: tests-passed) + #version: tests-passed + +env: + LANG: en_US.UTF-8 + # DISCOURSE_DEFAULT_LOCALE: en + + ## How many concurrent web requests are supported? Depends on memory and CPU cores. + ## will be set automatically by bootstrap based on detected CPUs, or you can override + UNICORN_WORKERS: 2 + + ## TODO: The domain name this Discourse instance will respond to + ## Required. Discourse will not work with a bare IP number. + + # 社区地址 + DISCOURSE_HOSTNAME: springboot.io + + ## Uncomment if you want the container to be started with the same + ## hostname (-h option) as specified above (default "$hostname-$config") + #DOCKER_USE_HOSTNAME: true + + ## TODO: List of comma delimited emails that will be made admin and developer + ## on initial signup example 'user1@example.com,user2@example.com' + # 开发者邮箱 + DISCOURSE_DEVELOPER_EMAILS: '747692844@qq.com' + + ## TODO: The SMTP mail server used to validate new accounts and send notifications + # SMTP ADDRESS, username, and password are required + # WARNING the char '#' in SMTP password can cause problems! + + # 企业邮件配置 + DISCOURSE_SMTP_ADDRESS: smtp.exmail.qq.com + DISCOURSE_SMTP_PORT: 587 + DISCOURSE_SMTP_USER_NAME: + DISCOURSE_SMTP_PASSWORD: "" + DISCOURSE_SMTP_ENABLE_START_TLS: true # (optional, default true) + DISCOURSE_SMTP_AUTHENTICATION: login + DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: none + + + ## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate + LETSENCRYPT_ACCOUNT_EMAIL: 747692844@qq.com # LETSENCRYPT 邮箱 + + ## The CDN address for this Discourse instance (configured to pull) + ## see https://meta.discourse.org/t/14857 for details + #DISCOURSE_CDN_URL: //discourse-cdn.example.com + +## The Docker container is stateless; all data is stored in /shared +volumes: + - volume: + host: /var/discourse/shared/standalone + guest: /shared + - volume: + host: /var/discourse/shared/standalone/log/var-log + guest: /var/log + +## Plugins go here +## see https://meta.discourse.org/t/19157 for details +hooks: + after_code: + - exec: + cd: $home/plugins + cmd: + - git clone https://github.com/discourse/docker_manager.git + +## Any custom commands to run after building +run: + - exec: echo "Beginning of custom commands" + ## If you want to set the 'From' email address for your first registration, uncomment and change: + ## After getting the first signup email, re-comment the line. It only needs to run once. + - exec: rails r "SiteSetting.notification_email='no-reply@springboot.io'" + - exec: echo "End of custom commands" +(END) \ No newline at end of file diff --git "a/Discourse/discourse-\347\233\256\345\275\225\346\230\240\345\260\204.java" "b/Discourse/discourse-\347\233\256\345\275\225\346\230\240\345\260\204.java" new file mode 100644 index 00000000..ebe5d3f1 --- /dev/null +++ "b/Discourse/discourse-\347\233\256\345\275\225\346\230\240\345\260\204.java" @@ -0,0 +1,19 @@ + +/cids + 包含当前运行的Docker容器的容器ID。cids是Docker的“等效”pids。每个容器都有一个像hash这样的独特git。 + +/containers + 此目录用于各种Discourse容器的容器定义。你负责这个目录,它是空的。 + +/samples + 可用于引导环境的示例容器定义。您可以将模板从此处复制到容器目录中。 + +/shared + 共享卷的占位符点与各种Discourse容器。您可以选择在容器外部存储某些持久性信息,在我们的例子中,我们保留各种日志文件并将目录上传到外部。这使您可以轻松地重建容器而不会丢失重要信息。将上传保留在容器之外允许您在多个Web实例之间共享它们。 + +/templates + 幼崽管理的模板,您可以用来引导您的环境。 + +/image + Dockerfiles for Discourse; 有关详细信息,请参阅自述文件。 + diff --git a/Discourse/discourse.java b/Discourse/discourse.java new file mode 100644 index 00000000..81e1b417 --- /dev/null +++ b/Discourse/discourse.java @@ -0,0 +1,177 @@ +--------------------- +discourse | +--------------------- + # github + https://github.com/discourse/discourse + + # 安装 + https://github.com/discourse/discourse_docker + https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md + + + # Docker安装 + * 移除旧版本 +yum remove docker \ +docker-client \ +docker-client-latest \ +docker-common \ +docker-latest \ +docker-latest-logrotate \ +docker-logrotate \ +docker-selinux \ +docker-engine-selinux \ +docker-engine + * 安装系统所需要的工具 + yum install -y yum-utils device-mapper-persistent-data lvm2 + + * 添加软件源信息 + yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo + + * 更新 yum 缓存 + yum makecache fast + + * 安装 Docker-ce + yum -y install docker-ce + + * 启动 Docker 后台服务 + systemctl start docker + + * 测试运行 hello-world + docker run hello-world + + * 删除docket ce + yum remove docker-ce + rm -rf /var/lib/docker + + +--------------------- +launcher 维护 | +--------------------- + 语法: launcher COMMAND CONFIG [--skip-prereqs] [--docker-args STRING] + COMMAND: + start: Start/initialize a container + * 初始化一个container + stop: Stop a running container + * 停止一个container + restart: Restart a container + * 重启一个container + destroy: Stop and remove a container + enter: Use nsenter to get a shell into a container + logs: View the Docker logs for a container + bootstrap: Bootstrap a container for the config based on a template + rebuild: Rebuild a container (destroy old, bootstrap, start new) + * 重新构建 + cleanup: Remove all containers that have stopped for > 24 hours + + Options: + --skip-prereqs Don't check launcher prerequisites + --docker-args Extra arguments to pass when running docker + + +--------------------- +杂七杂八 | +--------------------- + # 阿里云部署时配置邮件要注意 + + * 添加如下配置 + +DISCOURSE_SMTP_AUTHENTICATION: login +DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: none +DISCOURSE_SMTP_ENABLE_START_TLS: true + + * 此外在配置文件最后的 run: 那一块中找到 + run: + - exec: echo "Beginning of custom commands" + + ## If you want to set the 'From' email address for your first registration, uncomment and change: + #- exec: rails r "SiteSetting.notification_email='info@unconfigured.discourse.org'" + ## After getting the first signup email, re-comment the line. It only needs to run once. + + * 删除掉 #- exec: rails r "SiteSetting.notification_email='info@unconfigured.discourse.org'"* 这一行开头的 # 井号 + * 再把 info@unconfigured.discourse.org 改成你的发件邮箱地址 + * 编辑文件的时候不要删除每一行前的空格符,保持语句块上下是对齐的,不要删除没说明的引号 + +--------------------- +杂七杂八 | +--------------------- + Staff + * 系统的模块,里面有固定不能删除的帖子 + 常见问题 + 服务条款 + 隐私 + + +--------------------- +杂七杂八 | +--------------------- + + # 设置顶部全局提示信息 + 设置 -> 未分类 > global notice + + # 开启帖子标签功能 + 设置 -> 标签 + min trust to create tag 创建标签所需的最小信任等级 + min trust level to tag topics 给主题加标签的最小信任等级 + + # 设置分类页面的样式(/categories) + 设置 -> 基本设置 -> desktop category page style + + # 设置主页的菜单(帖子布局) + 设置 -> 基本设置 -> top menu + categories 分类 + latest 最新 + top 热门 + posted 我的帖子 + new + red 已读 + unred 未读 + bookmarks 收藏 + + # 帖子审核机制 + 设置 -> 发帖 -> approve post count + 设置 -> 发帖 -> approve unless trust level + 设置 -> 发帖 -> approve new topics unless trust level + + # 询问是否允许通知 + 设置 -> 基本设置 -> push notifications prompt + + # 设置通知图标 + 设置 -> 基本设置 -> push notifications icon + + # Github登录 + 设置 -> 登录 -> github + + * github的回调地址是 + {site}/auth/github/callback + + + # 自动备份 + 设置 -> 备份 -> backup frequency + * 多少天备份一次 + + 设置 -> 备份 -> allow restore + * 允许导入备份的数据,这会替换全站的数据 + + # 固定主页板块分类 + 设置 -> 基本设置 -> fixed category positions + + # 新用户欢迎私信设置 + + # 修改模板引擎代码 + 定制 -> 主题 -> 自定义 CSS/HTML + + # 删除未分类模块 + 设置 -> 发帖 -> allow uncategorized topics + + # 不允许使用外部系统的头像 + 设置 -> 文件 -> external system avatars enabled + * 如果使用的话,可能导致用户的头像访问失败 + + + # 发送摘要邮件的频率 + 设置 -> 用户设置 -> default email digest frequency + + # 显示回复到回复的标识 + 设置 -> 发帖 -> suppress reply directly above + + diff --git a/Disruptor/disruptor.java b/Disruptor/disruptor.java new file mode 100644 index 00000000..6fc9ef72 --- /dev/null +++ b/Disruptor/disruptor.java @@ -0,0 +1,12 @@ +-------------------------- +Disruptor | +-------------------------- + # Disruptor是一个高性能的异步处理框架, 或者可以认为是线程间通信的高效低延时的内存消息组件 + * 它最大特点是高性能, 其LMAX架构可以获得每秒6百万订单, 用1微秒的延迟获得吞吐量为100K+ + + # 网址 + https://github.com/LMAX-Exchange/disruptor + + + # TODO + \ No newline at end of file diff --git a/Docker/compose/compose-yml.java b/Docker/compose/compose-yml.java new file mode 100644 index 00000000..543230b5 --- /dev/null +++ b/Docker/compose/compose-yml.java @@ -0,0 +1,40 @@ + +# 配置文件参考 + https://github.com/docker/docker.github.io/blob/master/compose/compose-file/index.md + + +# 常用 + +version: "[版本号]" +# 表示的是配置文件的版本号 +services: + [应用名称]: + image:[image] + container_name: [容器名称] + build: [指定Dockerfile目录,会新构建一个镜像] + ports: + - "[宿主机端口]:[容器端口]" + volumes: + - [宿主机目录]:[容器目录] + networks: + - [网络] + environment: + # 环境变量 + NAME: root + PASS: ROOT + depends_on: + # 该应用,依赖的应用 + - nginx + - mysql + working_dir: /opt/app + # 工作目录 + deploy: + # 部署选项 + replicas: [集群数量] + update_config: + parallelism: 2 + delay: 10s + restart_policy: + # 重启策略 + condition: on-failure + # 重启条件,枚举值 \ No newline at end of file diff --git "a/Docker/compose/compose-\345\221\275\344\273\244.java" "b/Docker/compose/compose-\345\221\275\344\273\244.java" new file mode 100644 index 00000000..43045d7e --- /dev/null +++ "b/Docker/compose/compose-\345\221\275\344\273\244.java" @@ -0,0 +1,7 @@ +docker-compose up +docker-compose start +docker-compose stop +docker-compose logs +docker-compose kill +docker-compose rm +docker-comopse ps diff --git a/Docker/compose/compose.java b/Docker/compose/compose.java new file mode 100644 index 00000000..637cd697 --- /dev/null +++ b/Docker/compose/compose.java @@ -0,0 +1,12 @@ +------------------------------ +compose | +------------------------------ + # python写的 + # github + https://github.com/docker/compose + +------------------------------ +compose - 安装 | +------------------------------ + pip install docker-compose + diff --git a/Docker/docker-Dockerfile.java b/Docker/docker-Dockerfile.java new file mode 100644 index 00000000..1b85a549 --- /dev/null +++ b/Docker/docker-Dockerfile.java @@ -0,0 +1,195 @@ +------------------------ +Dockerfile | +------------------------ + # Dockerfile基于DSL(Domain Specifile Language)语法的指令来构建一个Docker镜像 + # Docker大体上按照如下流程执行Dockerfile中的命令 + * Docker 从基础镜像运行一个人容器 + * 执行一条指令,对容器做出修改 + * Docker再基于刚才提交的镜像运行一个新容器 + * 执行Dockerfile中的下一条指令直到所有指令都执行完毕 + + # 如果Dockerfile由于一些原因没有正常结束,还是可以得到一个可以使用的镜像 + * 可以基于该镜像生成一个容器,进入交互式环境查看排查原因 + + # 以 # 开头表示注释 + # 第一条之类必须是: FROM + * 表示基础镜像,它必须指定一个已经存在的镜像 + + # 参数可以通过 $ 引用 + ENV DIR /opt/app + WORKDIR $DIR + +------------------------ +Dockerfile | +------------------------ + FROM + * 基本的镜像 + FROM centos:7 + * FROM 其实可以出现多次,表示多个阶段的构建 + * 可以为某个阶段起别名 + FROM golang:1.9-alpine as builder + docker build --target builder -t username/imagename:tag . + + + + MAINTAINER + * 作者信息 + MAINTAINER KevinBlandy "747692844@qq.com" + ENV + * 环境变量 + ENV JAVA_HOME /usr/local/java + * 也可以一次性设置多个 + ENV JAVA_HOM=/home/java PYTHON_HOME=/home/python + * 也可以在其他的指令中引用该变量 + ENV DIR /opt/app + WORKDIR $DIR + * 这些环节变量会持久保存到镜像创建的容器,可以用过 env 指令查看 + + RUN + * 构建时候,执行的shell命令 + RUN yum -y install git + + EXPOSE + * 对外公开的端口 + EXPOSE 80 + + CMD + * 容器启动时运行的命令 + COM ["/bin/true","-l"] + * 跟 docker run 指令后指定的命名一样的,而且该指令指定的命名会覆盖它 + * 整个文件中只能定义一个CMD指令,如果定义了多个,那么只有最后一个生效 + + ENTRYPOINT + * 类似于 CMD 指令,也是用来执行命令的 + ENTRYPOINT ["/usr/sbin/nginx"] + * 它不会被 docker run 指令的命令覆盖 + * docker run 指令的的命令参数会作为值,传递给这个命令 + ENTRYPOINT ["/usr/sbin/nginx"] + + + docker run ... nginx-image -g "daemon off" + = + /usr/sbin/nginx -g "daemon off" + + * 实在需要覆盖该命令,可以通过 --entrypoint 来覆盖 + * 可以配合CMD使用生成一条指令 + ENTRYPOINT ["/usr/sbin/nginx"] + + + CMD ["-h"] + = + /usr/sbin/nginx -h + + + WORKDIR + * 在从镜像创建一个新的容器时,会在容器内部内置一个工作目录 + WORKDIR /usr/local/webapp + * CMD 和 ENTRYPOINT 指令都会在这个目录下执行 + * 该命令可以出现多次,表示目录切换 + WORKDIR /usr/local/db + RUN mysql install + WORKDIR /usr/local/webapp + ENTRYPOINT ["setup"] + * 可以在docker run时通过 -w 参数来覆盖该配置 + docer run ... -w /var/log ... + + + USER + * 指镜像会以哪个用户去执行 + USER root + * 用户必须先建立好,可以使用 RUN 指令先创建用户 + * 可以指定用户名,uid,用户组,gid,以及各种组合 + USER user + USER user:group + + * 如果未指定,默认为 root + + VOLUME + * 基于镜像创建的容器,添加卷 + * 一个卷可以存在一个或者多个容器内特定的目录,这个目录可以绕过联合文件系统,并且提供如下共享数据或者对数据进行持久化的功能 + * 卷可以在容器之间共享和重用 + * 对卷的修改是立即生效的 + * 对卷的修改,不会对更新镜像产生影响 + * 卷会一直存在,直到没有容器使用它 + * 可以一致性指定一个或者多个卷 + VOLUME ["/opt/project","/data"] + + ADD + * 把构建环境下的文件和目录复制到镜像中 + * 需要指定源文件和目标文件的位置 + ADD software.lic /opt/application/software.lic + * 源文件也可以是一个url,但是不能对构建目录外的文件进行操作 + * 通过结尾来判断是文件还是文件夹,如果结尾以 / 字符结尾,表示为一个文件夹,反之则为一个文件 + * 如果源文件是压缩文件(zip),而目标是一个目录,则会主动解压 + ADD a.zip /usrl/local/a/ + + * 该指令会让构建缓存失效,如果通过ADD指令向镜添加一个文件或者目录,那么Df文件中的后续指令不能继续使用之前的缓存 + + COPY + * 跟ADD一样,唯一不同的时候,如果cp的时候压缩文件,不会进行解压操作 + * 支持通配符,规则要满足 Go 的 filepath.Match 规则 + COPY hom* /mydir/ + COPY hom?.txt /mydir/ + * 不仅仅是从文件目录,还可以从其他的镜像复制文件 + COPY --from=imgae:tag /source /target + COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf + + * 也可以从指定的构建阶段复制 + COPY --from=buildName /source /target + + + LABEL + * 为Docker镜像添加元数据信息,k=v形式展示 + LABEL version="10.1" + LABEL location="中国 - 重庆" type="DLK" role="ADMIN" + * 可以通过 docker inspect 来查看 Docker 镜像中的标签信息 + + STOPSIGNAL + * 停止容器的时候,发送指定的系统信号给容器 + * 信号必须是内核系统调用中的合法数(数字或者名称) + STOPSIGNAL 9 + STOPSIGNAL SIGKILL + + ARG + * 可以在docker build命令运行时候传递构建运行时的变量 + * 它的作用就是定义一了一堆变量,这些变量可以在df文件中被引用 + * 但是它们的值,是在执行构建的时候才通过命名来初始化 + * 在构建时使用 --build-arg,用户只能在构建时指定Df文件中定义过的参数 + ARG build + docker build ... --build-arg build=1234 .... + + * 也可以有默认值,如果build时没指定,则使用默认值 + ARG webapp_usr=user + + * Docker预定义了N多ARG变量,可以直接赋值使用 + HTTP_PROXY + + ONBUILD + * 为镜像添加触发器,当该镜像被其他镜像当作基础镜像的时候,该镜像中的触发器会被执行 + * 触发器会在构建过程中插入新指令,这些指令是跟在FROM之后执行的(FROM 哪个镜像,哪个镜像的触发器就会执行) + * 触发器可以是任何构建之类 + ONBUILD ADD . /app/src/ + ONBUILD RUN cd /app/src && make && make install + * 触发器会按照父级镜像中的指定顺序执行,并且只能被继承一次(只能在子镜像中运行,不能在孙子镜像中运行) + + + + HEALTHCHECK + * 设置检查容器健康状况的命令,告诉 Docker 应该如何进行判断容器的状态是否正常 + HEALTHCHECK [options] CMD [命令] + * 如果基础镜像有健康检查指令,设置为NULL可以屏蔽掉其健康检查指令 + HEALTHCHECK NONE + * 一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy + * 支持下列选项: + --interval=<间隔> + 两次健康检查的间隔,默认为 30 秒 + --timeout=<时长> + 健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒 + --retries=<次数> + 当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次 + * 该指令只能出现一次,如果出现多次的话,只有最后一个生效 + + + +------------------------------ +scratch | +------------------------------ + # 元镜像 diff --git a/Docker/docker-image.java b/Docker/docker-image.java new file mode 100644 index 00000000..f271a53b --- /dev/null +++ b/Docker/docker-image.java @@ -0,0 +1,76 @@ +------------------------- +image | +------------------------- + # 查询镜像 + docker search [name] + name + * 查询指定的镜像 + ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + NAME DESCRIPTION STARS OFFICIAL AUTOMATED + mysql MySQL is a widely used, open-source relation… 7649 [OK] [ok] + ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + NAME 名称 + DESCRIPTION 描述 + STARS 星星数量(受到欢迎的程度) + OFFICIAL 是否是官方管理的 + AUTOMATED 是否DockerHub自动构建的 + + + # 查看本地的镜像(列出镜像) + docker images [name] + name + * 仅仅列出本地的镜像 + -a + * 显示所有的镜像 + * 中间层的镜像也会显示出来 + + --digest + * 显示摘要信息 + + --no-trunc + * 显示完整信息 + + -------------------------------------------------------------------------------------- + REPOSITORY TAG IMAGE ID CREATED SIZE + hello-world latest fce289e99eb9 6 days ago 1.84kB + nginx latest 7042885a156a 9 days ago 109MB + -------------------------------------------------------------------------------------- + + # 镜像拉取 + docker pull [name]:[tag] + name + * 镜像名称 + tag + * 标签(版本号) + -a + * 下载指定镜像的所有tag(版本)到本地 + + # 删除镜像 + docker rmi [name] + * 仅仅只会删除 last 版本 + * 也可以把name换成指定的image id + * 删除所有的镜像 + docker rmi `docker images -q` + + + # 导出镜像 + docker save [name]:[tag] > /[path].image + + # 导入镜像 + docker load < /[path].image + + + +------------------- +虚悬镜像 | +------------------- + # 关于 + 这个镜像原本是有镜像名和标签的,原来为 mongo:3.2,随着官方镜像维护,发布了新版本后,重新 docker pull mongo:3.2 时,mongo:3.2 这个镜像名被转移到了新下载的镜像身上 + 而旧的镜像上的这个名称则被取消,从而成为了 + 除了 docker pull 可能导致这种情况,docker build 也同样可以导致这种现象,由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名,标签均为 的镜像 + 这类无标签镜像也被称为 虚悬镜像(dangling image) ,可以用下面的命令专门显示这类镜像 + + docker image ls -f dangling=true + + # 删除虚悬镜像 + docker image prune \ No newline at end of file diff --git "a/Docker/docker-image\346\236\204\345\273\272.java" "b/Docker/docker-image\346\236\204\345\273\272.java" new file mode 100644 index 00000000..16ed70c4 --- /dev/null +++ "b/Docker/docker-image\346\236\204\345\273\272.java" @@ -0,0 +1,140 @@ +------------------------ +镜像构建 | +------------------------ + # 注册 + https://hub.docker.com/ + + # 登录 & 退出登录 + docker login + docker logout + + + +------------------------ +Commit | +------------------------ + # 其实是更新了本地的镜像,类似于版本控制器的操作 + * 先根据镜像创建容器 + * 修改了容器信息(安装了软件配置啥的) + * 再把这个修改过的容器作为新的镜像提交到本地 + + + docker commit [id] [name]:[tag] + id + * 容器id + name + * 镜像名称 + tag + * 标签 + -m + * 备注信息 + -m "这是备注信息" + -a + * 指定作者信息 + -a "KevinBlandy" + + + # commoit 提交的仅仅修改的部分,所以更新操作非常的轻量 + + +------------------------ +Build | +------------------------ + # 根据Dockerfile构建 + + docker build -t [name]:[tag] . + -t + * 指定镜像名称,标签 + -t foo:1 + -t /res/foo:1 + + * 如果未指定标签,则默认会添加一个 last 的标签 + + -f + * 如果当前目录下没有:Dockerfile文件,那么就要通过该参数指定Dockerfile的路径 + * 而且,如果是通过该参数指定的文件,非必须命名为:Dockerfile + + --target + * 只构建某一阶段的镜像 + * 该选项值为 FROM 的别名 + FROM golang:1.9-alpine as builder + docker build --target builder -t username/imagename:tag . + + + --build-arg + * 为Df文件中声明的配置项(ARG)赋值 + --build-arg name=root + + --no-cache + * 禁止docker的构建缓存 + + # .dockerignore 文件 + * 如果构建的上下文环境中存在该文件的话 + * 那么该文件中列出的文件不会被当作构建上下文的一部分 + * 防止它们被上传到Docker守护进程中去 + * 跟 .gitignore 文件一样 + * 匹配模式采用了Go语言中的 filepath(???咋又是GO的东西) + + + # Docker 构建缓存 + * Docker非常的聪明其实,它的构建过程存在缓存机制 + * 它会把每一步的执行都构建成一个镜像,缓存起来 + * 会把之前构建的镜像,当作缓存,并且基于它去执行新的构建 + * 假设执行:1,2,3步都成功了,在执行第4步的时候失败 + * 修改第4步的配置信息,重新构建时,仅仅只会从第4步开始构建 + + + + # 查看构建的过程(执行栈) + docker history [id] + id + * image id + ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + IMAGE CREATED CREATED BY SIZE COMMENT + 860466ea6f08 19 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B + 46cfd6b742e2 19 minutes ago /bin/sh -c echo 'vim & git &install success!… 0B + 7c8f32c5a997 22 minutes ago /bin/sh -c yum -y install git 40.2MB + 881201610d17 22 minutes ago /bin/sh -c yum -y install vim 126MB + 83c083061e4e 23 minutes ago /bin/sh -c #(nop) MAINTAINER KevinBlandy "7… 0B + 1e1148e4cc2c 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B + 4 weeks ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B + 4 weeks ago /bin/sh -c #(nop) ADD file:6f877549795f4798a… 202MB + ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— + + * 也可以查看某个镜像的修改记录 + + + + + +------------------------ +多阶段构建 | +------------------------ + # 关于多阶段构建 + * 多个 FROM 指令并不是为了生成多根的层关系 + * '最后生成的镜像,仍以最后一条 FROM 为准',之前的 FROM 会被抛弃,那么之前的FROM 又有什么意义呢 + * 每一条 FROM 指令都是一个构建阶段,多条 FROM 就是多阶段构建,虽然最后生成的镜像只能是最后一个阶段的结果,但是,'能够将前置阶段中的文件拷贝到后边的阶段中' + * 这就是多阶段构建的最大意义 + + # 可以出现多个FROM,表示多个阶段 + FROM image:1 + FROM image:2 + + * 可以通过 as 设置阶段的别名 + FROM image:1 as build1 + + # 复制操作,不仅仅是从文件目录,还可以从其他的镜像/构建阶段复制文件 + * 从镜像复制 + COPY --from=imgae:tag /source /target + COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf + + * 也可以从指定的构建阶段复制 + COPY --from=0 /source /target + + * 0 表示第一个阶段,也可以使用别名 + + # 构建的时候,可以使用 --target 指定要构建的阶段 + docker build --target builder .... + + + diff --git "a/Docker/docker-\344\273\223\345\272\223.java" "b/Docker/docker-\344\273\223\345\272\223.java" new file mode 100644 index 00000000..5f23b991 --- /dev/null +++ "b/Docker/docker-\344\273\223\345\272\223.java" @@ -0,0 +1,43 @@ +------------------------ +仓库 | +------------------------ + # Docker 仓库地址 & 结构 + https://hub.docker.com/ + |-repoisoty1 + |-image1 tag1 + |-image1 tag2 + |-image2 + |-repoisoty2 + + + +------------------------ +各种仓库 | +------------------------ + https://hub.docker.com/ + +------------------------ +修改为国内的仓库 | +------------------------ + # 编辑文件 + vim /etc/docker/deamon.json + * 不存在,直接添加该文件 + + # 内容 + { + "registry-mirrors":["https://docker.mirrors.ustc.edu.cn"] + } + + # 重启服务 + systemctl daemon-reload + systemctl restart docker + + +------------------------ +私有仓库 | +------------------------ + docker run -p5000:5000 registry + + docker push xx.com:8080/kevinblandy/app:1 + docker pull xx.com:8080/kevinblandy/app:1 + docker run xx.com:8080/kevinblandy/app:1 \ No newline at end of file diff --git "a/Docker/docker-\345\220\204\347\247\215\351\227\256\351\242\230.java" "b/Docker/docker-\345\220\204\347\247\215\351\227\256\351\242\230.java" new file mode 100644 index 00000000..f81615ab --- /dev/null +++ "b/Docker/docker-\345\220\204\347\247\215\351\227\256\351\242\230.java" @@ -0,0 +1,15 @@ +------------------------ +时区问题 | +------------------------ + # 构建的时候可以在 Dockerfile 里面设置 + ENV TZ=Asia/Shanghai + RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + + # 已经构建好的容器,可以进入内部去执行两个修改 + * 在 /etc/profile 下添加变量 + export TZ=Asia/Shanghai + + * 执行命名 + ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + + * 重启容器 \ No newline at end of file diff --git "a/Docker/docker-\345\221\275\344\273\244.java" "b/Docker/docker-\345\221\275\344\273\244.java" new file mode 100644 index 00000000..b815c49b --- /dev/null +++ "b/Docker/docker-\345\221\275\344\273\244.java" @@ -0,0 +1,69 @@ +---------------------------- +docker 命令 | +---------------------------- + docker help + * 查看所有的命令帮助 + + docker info + * 查看docker的基本配置信息 + +---------------------------- +其他 | +---------------------------- + # 命令可以参数化 + * 使用 $(cmd) + docker rmi $(docker images -q) + + * ``也可以 + docker rm `docker ps -aq` + + + + + + + + + + + + + +docker build -t friendlyname .# 使用此目录的 Dockerfile 创建镜像 +docker run -p 4000:80 friendlyname # 运行端口 4000 到 90 的“友好名称”映射 +docker run -d -p 4000:80 friendlyname # 内容相同,但在分离模式下 +docker ps # 查看所有正在运行的容器的列表 +docker stop # 平稳地停止指定的容器 +docker ps -a # 查看所有容器的列表,甚至包含未运行的容器 +docker kill # 强制关闭指定的容器 +docker rm # 从此机器中删除指定的容器 +docker rm $(docker ps -a -q) # 从此机器中删除所有容器 +docker images -a # 显示此机器上的所有镜像 +docker rmi # 从此机器中删除指定的镜像 +docker rmi $(docker images -q) # 从此机器中删除所有镜像 +docker login # 使用您的 Docker 凭证登录此 CLI 会话 +docker tag username/repository:tag # 标记 以上传到镜像库 +docker push username/repository:tag # 将已标记的镜像上传到镜像库 +docker run username/repository:tag # 运行镜像库中的镜像 + + +docker stack ls # 列出此 Docker 主机上所有正在运行的应用 +docker stack deploy -c # 运行指定的 Compose 文件 +docker stack services # 列出与应用关联的服务 +docker stack ps # 列出与应用关联的正在运行的容器 +docker stack rm # 清除应用 + +docker-machine create --driver virtualbox myvm1 # 创建 VM(Mac、Win7、Linux) +docker-machine create -d hyperv --hyperv-virtual-switch "myswitch" myvm1 # Win10 +docker-machine env myvm1 # 查看有关节点的基本信息 +docker-machine ssh myvm1 "docker node ls" # 列出 swarm 中的节点 +docker-machine ssh myvm1 "docker node inspect " # 检查节点 +docker-machine ssh myvm1 "docker swarm join-token -q worker" # 查看加入令牌 +docker-machine ssh myvm1 # 打开与 VM 的 SSH 会话;输入“exit”以结束会话 +docker-machine ssh myvm2 "docker swarm leave" # 使工作节点退出 swarm +docker-machine ssh myvm1 "docker swarm leave -f" # 使主节点退出,终止 swarm +docker-machine start myvm1 # 启动当前未运行的 VM +docker-machine stop $(docker-machine ls -q) # 停止所有正在运行的 VM +docker-machine rm $(docker-machine ls -q) # 删除所有 VM 及其磁盘镜像 +docker-machine scp docker-compose.yml myvm1:~ # 将文件复制到节点的主目录 +docker-machine ssh myvm1 "docker stack deploy -c " # 部署应用 diff --git "a/Docker/docker-\345\256\271\345\231\250\347\256\241\347\220\206.java" "b/Docker/docker-\345\256\271\345\231\250\347\256\241\347\220\206.java" new file mode 100644 index 00000000..e92d4c70 --- /dev/null +++ "b/Docker/docker-\345\256\271\345\231\250\347\256\241\347\220\206.java" @@ -0,0 +1,285 @@ +---------------------------- +容器管理 | +---------------------------- + # 容器所在的本地目录 + /var/lib/docker/containers + + # 容器在的运行,必须要有一个前台程序在运行,不然就会自动退出 + + +---------------------------- +容器管理 | +---------------------------- + # 新启动容器 + docker run [image] [cmd] + -d + * 以守护进程的形式启动容器 + * docker 必须运行一个前台程序,不然也会自动的退出 + + -p + * 端口映射 + -p [宿主机端口]:[容器端口] + + * 可以有多个-p参数,表示绑定多个端口 + + * 如果存在多块网卡(多个ip),也可以指定 + -p 127.0.0.1:8081:80 + + * 如果只有一个参数,则表示容器的端口,而会随机选择一个宿主机的端口进行绑定(建议这种方式同时启动多个容器) + -p 80 + + * 可以在后面添加 /udp 表示映射的是udp端口 + -p 1024:1024/udp + + -P + * 对外公开 Dockerfile 中 EXPOSE 定义的所有端口 + * 每个对外公开的端口,都会随机选择一个宿主机端口进行绑定 + + -it + * 以交互形式启动容器 + * -i 开启了容器的stdin + * -t 开启一个tty的伪终端 + -e + * 指定环境变量 + -e "WEB_PORT=8080" + -v + * 设置共享卷 -v [宿主机目录]:[虚拟机目录] + -v /usr/local/website:/var/www/html/website + * 如果目录不存在,docker会自动的创建 + * 还可以添加权限来控制是否只读:ro(readOnly) + -v /usr/local/website:/var/www/html/website:ro + * 可以有多个 -v 参数,来表示要设置多个共享卷 + + -h + * 指定容器的主机名,默认主机名使用的是容器id + + --rm + * 该参数表示一次性容器,当容器关闭后就会被删除 + + --volumes-from + * 把指定容器设置的所有卷都加入进创建的容器里面 + --volumes-from app1 + * 复用指定容器的卷 + * 该命令可以执行多次,表示从多个容器复用卷 + --volumes-from app1 --volumes-from app2 + + --name + * 自定义名称(多个运行的容器名称不能重复) + * --name [name] + * '在docker很多命令中,都可以通过该名称来操作容器' + + --restart + * 指定容器的重启策略 + --restart=always + * 枚举值 + always + 只要容器被关闭就会立即重启,不论任何原因导致的关闭 + on-failure + 只有容器的退出代码非0时才会重启(非正常关闭,则重启) + 它还接收一个最大重启次数: --restart=on-failure:5 + + --entrypoint + * 覆写的是Dockerfile里面的 ENTRYPOINT 指令 + * 会把 [cmd] 当作参数传递给该命令执行 + + --privileged + * 启动Dokcer的特权模式 + --privileged=true + * 这种模式下允许虚拟机以,宿主机具有(几乎)的所有能力来运行容器,包括内核特性和设备访问 + * 这是想要在Docker中运行Docker必要的魔法 + + --net/--network + * 指定容器运行的网络 + --net=mynet + * 该网络需要先创建 + + --network-alias + * 指定当前容器在network中的别名(默认:[container-name].[net-name]) + --network-alias my-service + + --cidfile + * 可以把容器的id存储到指定的文件 + --cidfile=/tmp/containerid + + --mount + * 挂载数据卷 + --mount source=[name],target=[source] [target] + + --link + * ? + + image + * 镜像 + + cmd + * 启动OK后,向容器执行的命令,覆写的是Dockerflile里面的 CMD 指令 + * 如果包含特殊符号,需要用""包含 + + + # 重新启动已经停止的容器 + docker start [id] + id + * 容器id或者name + + + # 查看运行的容器 + docker -ps + -a + * 包含未运行的容器 + -l + * 仅仅列出最后一次运行的容器 + -n + * 指定显示的数量 + * -n 15 + -q + * 仅仅查看(返回)容器的id信息 + + ------------------------------------------------------------------------------------------------------------------------------------------------------ + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + b860f33a7e02 nginx "nginx -g 'daemon of…" 8 minutes ago Up 8 minutes 80/tcp keen_galois + ------------------------------------------------------------------------------------------------------------------------------------------------------ + + * STATUS 后面的括号有一个状态值,如果该值为0,则表示是正常退出的 + * NAMES 随机的名称(贼有意思) + + + # 优雅停止容器 + docker stop [id] + id + * CONTAINER ID,或者是自己定义的名称 + + + # 强制杀死容器 + docker kill [id] + id + * CONTAINER ID,或者是自己定义的名称 + + + # 复制文件到容器 + docker cp [file] [id]:[path] + * 把file复制到名字为id的path路径下 + * id也可以是容器的CONTAINER ID + + # 复制文件到宿主机 + docker cp [id]:[file] [path] + * 把指定容器(id)的指定文件(file)复制到宿主机的path目录下 + + # 查看容器的日志 + docker logs [id] + id + * 容器的id或者name + -f + * 可以即时的看到日志刷新 + -t + * 为每条日志加上时间信息 + + + # 删除容器 + docker rm [id] + id + * 容器的hash或者name + -f + * 可以删除运行时的容器 + * 如果不添加该参数,则只能删除停止状态的容器 + + -v + * 同时移除挂载的数据卷 + + * 删除所有容器 + docker rm `docker ps -a -q` + + # 删除所有已经停止的容器 + docker container prune + + # 查看容器内的进程 + docker top [id] + id + * 容器的id或者name + + # 查看容器的状态 + docker stats [id] + id + * 容器的id或者name + + # 在容器内部运行进程 + docker exec [id] [cmd] + id + * 容器id或者name + cmd + * 启动命令 + * 如果包含特殊符号,需要用""包含 + -it + * 在交互模式下进行 + + + # 查看容器的详细信息(返回json信息) + docker inspect [id] + id + * 容器id或者name + * 在这里可以指定一个或者多个,用空格隔开 + -f + * 格式化(仅仅查看指定的json信息,json导航) + docker inspect -f "{{.NetworkSettings.SandboxID}}" name + + * 格式化的参数,需要用""包裹,因为包含了特殊的字符 + * -f 完全支持go语言的模板(不会,滚) + * 该参数也可以同时指定多个参数,表示查看多个信息值 + docker inspect -f "{{.NetworkSettings.SandboxID}} {{.name}}" name + * 其实参数就是一个字符串模板,然后可以用go的表达式去配置信息中取值来填充 + + + # 查看(返回)容器映射(与宿主机)的端口信息 + docker port [id] [port] + id + * 容器name或者id + port + * 容器的开放的端口 + * 必须在容器启动状态下才能查看 + + # 连接容器到指定的网络 + docker network connect [net] [container] + container + * 指定的container容器 + net + * 指定网络 + + # 从指定的网络中断开容器连接 + docker network connect [net] [container] + container + * 指定的container容器 + net + * 指定网络 + + # 查看容器的改变记录 + docker diff [id] + id + * 容器name或者id + + # 容器导出 + docker export [id] > xxxtar + + # 容器导入 + docker import + + * 可以通过指定 URL 或者某个目录来导入,例如 + docker import http://example.com/exampleimage.tgz example/imagerepo + + +---------------------------- +其它 | +---------------------------- + # 从前容器中退出 + exit + * 退出,如果是不是后台运行的容器(-d),那么就会终止容器 + * 如果run启动的时候没有添加 -d 参数,执行exit时,就会退出容器 + + ctrl + p + q + * 退出,但是不终止容器 + + + + + + + + \ No newline at end of file diff --git "a/Docker/docker-\346\225\260\346\215\256\345\215\267.java" "b/Docker/docker-\346\225\260\346\215\256\345\215\267.java" new file mode 100644 index 00000000..e474eb88 --- /dev/null +++ "b/Docker/docker-\346\225\260\346\215\256\345\215\267.java" @@ -0,0 +1,36 @@ +------------------------ +数据卷 | +------------------------ + # 创建数据卷 + docker volume create [name] + name + * 数据卷的名称 + + # 查看所有的数据卷 + docker volume ls + + ———————————————————————————————————————————————————————————————————————————————————— + DRIVER VOLUME NAME + local 3bb416400f99e7f7a6149c7b07e42ffcf12333d20a35d4c4c9a90a7a78f03278 + local 3cd87fa709e34934c2f9c349aa25c1618f3dcc5997ef61ae1214c40b891aa419 + local 5260bb4c37a27c5997146dd6455c986e0f3a4ad7f87d60d7471decf1f5236f01 + local a97a58710879fe9c8af75796d42e8092594a31d78b5168a66eedd18e0b1aa45e + local e2862f5087ae9394a83db36aeb7e786e2c0982da67a22bfc09ce87aa3bd38329 + ———————————————————————————————————————————————————————————————————————————————————— + + # 查看指定数据卷的详情 + docker volume inspect [name] + name + * 数据卷的名称 + + + + # 删除数据卷 + volume rm [name] + name + * 数据卷的名称 + + # 清理无主的数据卷 + docker volume prune + + diff --git "a/Docker/docker-\347\247\201\346\234\211registry.java" "b/Docker/docker-\347\247\201\346\234\211registry.java" new file mode 100644 index 00000000..edfb90ed --- /dev/null +++ "b/Docker/docker-\347\247\201\346\234\211registry.java" @@ -0,0 +1,61 @@ +----------------------------------------- +docker-registry | +----------------------------------------- + # 安装 + docker run -d -p 5000:5000 --restart=always --name registry -v /usr/local/registry:/var/lib/registry registry + + # 为本地镜像添加标签 + docker tag [image] 127.0.0.1:5000/[image]:[tag] + + # 推送到本地仓库 + docker push 127.0.0.1:5000/[image]:[tag] + + # 查看仓库中的镜像 + curl 127.0.0.1:5000/v2/_catalog + {"repositories":["ubuntu"]} + + # 删除镜像 + docker image rm 127.0.0.1:5000/[image]:[tag] + + # 让本网段的其他主机也能把镜像推送到私有仓库 + * 直接推送会失败,因为Docker 默认不允许非 HTTPS 方式推送镜像 + * 可以通过 Docker 的配置选项来取消这个限制 + * 修改配置添加局域网的ip地址:/etc/docker/daemon.json + { + "insecure-registries": ["192.168.199.100:5000" ] + } + +----------------------------------------- +阿里云 | +----------------------------------------- + 1. 登录阿里云Docker Registry + $ sudo docker login --username=747692844@qq.com registry.cn-hangzhou.aliyuncs.com + 用于登录的用户名为阿里云账号全名,密码为开通服务时设置的密码。 + + 您可以在产品控制台首页修改登录密码。 + + 2. 从Registry中拉取镜像 + $ sudo docker pull registry.cn-hangzhou.aliyuncs.com/kevinblandy/test:[镜像版本号] + + 3. 将镜像推送到Registry + $ sudo docker login --username=747692844@qq.com registry.cn-hangzhou.aliyuncs.com + $ sudo docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/kevinblandy/test:[镜像版本号] + $ sudo docker push registry.cn-hangzhou.aliyuncs.com/kevinblandy/test:[镜像版本号] + 请根据实际镜像信息替换示例中的[ImageId]和[镜像版本号]参数。 + + 4. 选择合适的镜像仓库地址 + 从ECS推送镜像时,可以选择使用镜像仓库内网地址。推送速度将得到提升并且将不会损耗您的公网流量。 + + 如果您使用的机器位于经典网络,请使用 registry-internal.cn-hangzhou.aliyuncs.com 作为Registry的域名登录,并作为镜像命名空间前缀。 + 如果您使用的机器位于VPC网络,请使用 registry-vpc.cn-hangzhou.aliyuncs.com 作为Registry的域名登录,并作为镜像命名空间前缀。 + + 5. 示例 + 使用"docker tag"命令重命名镜像,并将它通过专有网络地址推送至Registry。 + + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + registry.aliyuncs.com/acs/agent 0.7-dfb6816 37bb9c63c8b2 7 days ago 37.89 MB + $ sudo docker tag 37bb9c63c8b2 registry-vpc.cn-hangzhou.aliyuncs.com/acs/agent:0.7-dfb6816 + 使用"docker images"命令找到镜像,将该镜像名称中的域名部分变更为Registry专有网络地址。 + + $ sudo docker push registry-vpc.cn-hangzhou.aliyuncs.com/acs/agent:0.7-dfb6816 \ No newline at end of file diff --git "a/Docker/docker-\347\275\221\347\273\234.java" "b/Docker/docker-\347\275\221\347\273\234.java" new file mode 100644 index 00000000..019157a4 --- /dev/null +++ "b/Docker/docker-\347\275\221\347\273\234.java" @@ -0,0 +1,49 @@ +------------------------------ +network | +------------------------------ + # 参考文档 + https://docs.docker.com/config/containers/container-networking/ + + # 同一个网络中的容器,都维护了一个 dns列表(/etc/hosts) + + ip [container-name] + ip [container-name].[net-name] + +------------------------------ +network | +------------------------------ + # 创建一个网络 + docker network create [name] + name + * 网络名称 + -d + * 指定网络的类型,枚举值 + bridge + overlay(Swarm mode) + + # 查看所有创建的网络 + docker network ls + + # 查看网络的详情 + docker network inspect [name] + name + * 网络名称或者id + + # 连接容器到指定的网络 + docker network connect [net] [container] + container + * 指定的container容器 + net + * 指定网络 + + # 从指定的网络中断开容器连接 + docker network connect [net] [container] + container + * 指定的container容器 + net + * 指定网络 + + # 删除 + docker network rm [name] + name + * 网络名称或者id \ No newline at end of file diff --git a/Docker/docker.java b/Docker/docker.java new file mode 100644 index 00000000..f212012e --- /dev/null +++ b/Docker/docker.java @@ -0,0 +1,77 @@ +---------------------------- +docker | +---------------------------- + # 官网 + https://www.docker.com/ + http://www.docker-cn.com/ + + # 参考 + https://docker_practice.gitee.io/ + + + +---------------------------- +Centos yum 安装 | +---------------------------- + # 移除一些自带的组件 +sudo yum remove docker \ +docker-client \ +docker-client-latest \ +docker-common \ +docker-latest \ +docker-latest-logrotate \ +docker-logrotate \ +docker-selinux \ +docker-engine-selinux \ +docker-engine + + # 安装依赖 +yum install -y yum-utils \ +device-mapper-persistent-data \ +lvm2 + + # 添加仓库 +yum-config-manager \ +--add-repo \ +https://download.docker.com/linux/centos/docker-ce.repo + + # 启用仓库 + yum-config-manager --enable docker-ce-edge + + yum-config-manager --enable docker-ce-test + + * 如果需要禁用仓库 + yum-config-manager --disable docker-ce-edge + + # 安装docker + yum install -y docker-ce + + # 启动dcoker + systemctl start docker + + # HelloWorld测试 + docker run hello-world + +---------------------------- +本地安装 | +---------------------------- + + # 下载rpm文件 + https://download.docker.com/linux/centos/7/x86_64/stable/Packages/ + + # 执行安装 + yum install /opt/docker-ce-18.09.0-3.el7.x86_64.rpm + + # 启动docker + systemctl start docker + + # HelloWorld测试 + docker run hello-world + +---------------------------- +Centos Docker基本的维护 | +---------------------------- + systemctl start docker + systemctl stop docker + systemctl restart docker + systemctl status docker \ No newline at end of file diff --git "a/Druid/Druid-\345\205\245\351\227\250.java" "b/Druid/Druid-\345\205\245\351\227\250.java" index e0f77165..06e26509 100644 --- "a/Druid/Druid-\345\205\245\351\227\250.java" +++ "b/Druid/Druid-\345\205\245\351\227\250.java" @@ -120,4 +120,15 @@ ------------------------ Durid-加密 | ------------------------ \ No newline at end of file +----------------------- + 1,执行加密,得到私钥,公钥,加密后的密文 + java -cp druid-1.1.10.jar com.alibaba.druid.filter.config.ConfigTools [密码] + + 2,配置 + spring: + datasource + password: aAO2o86uTLOgw42z6xWJYb8Ta4F7OR2LzKnqcO46dYR5fccPIpuxLdvCznH1+MXRadtxsnmbCK4Masu8zE3QGg== + public-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAI5cG4UDYuGP7eljYApw4czFoORmMMCgN1JUs4GgAdcXS8130FbaUzK8Of2JvK1oT778EdmnBJaRAGEv/9vH0gMCAwEAAQ== + + filters: config + connectionProperties: config.decrypt=true;config.decrypt.key=${spring.datasource.public-key} \ No newline at end of file diff --git "a/EasyUI/easyui-\345\270\270\347\224\250\344\273\243\347\240\201.js" "b/EasyUI/easyui-\345\270\270\347\224\250\344\273\243\347\240\201.js" new file mode 100644 index 00000000..42ca983a --- /dev/null +++ "b/EasyUI/easyui-\345\270\270\347\224\250\344\273\243\347\240\201.js" @@ -0,0 +1,99 @@ +--------------------- +其他 | +--------------------- + # 右击显示自己的菜单(具备右击事件的组件都可以使用) + onContextMenu:function(e,node){ + //阻止浏览器的右击事件 + e.preventDefault(); + /* + 显示我们自定义的菜单 + */ + $('#treeMenu').menu('show',{ + //菜单的位置是在鼠标右击点击的地方 + left:e.pageX, + top:e.pageY + }); + } + +--------------------- +tree | +--------------------- + # 异步全展开 + $('#tree').tree({ + //后台数据响应成功,会调用这个方法 + onLoadSuccess:function(node,data){ + /* + 执行展开所有节点 + 而每次展开都要请求后台都会触发这个事件 + 这个事件就相当于'递归'执行了 + */ + if(data){ + //遍历后台响应的节点 + $(data).each(function(index,d){ + /* + 如果是关闭状态,则执行打开操作 + 打开操作,又会请求后台服务器.又会触发这个事件 + 后台的逻辑:如果有子节点,就是 closed 状态,如果没子节点,则是 open 状态 + tree如果是通过url进行加载的,那么在执行展开时候,会自动尝试把id值发送给服务器,来获取子节点 + */ + if(this.state == 'closed'){ + $('#tree').tree('expandAll'); + } + }); + } + } + }); + + # 勾选的时候,如果有父级菜单,全部勾选,取消勾选的时候,如果有子菜单,全部取消勾选 + onCheck:function (node, checked) { + if(checked){ + //勾选,存在父级菜单,则勾选所有父级菜单 + var parent = $(this).tree('getParent',node.target); + if(parent != null){ + $(this).tree('check',parent.target); + } + }else { + //取消勾选,存在子菜单则全部取消 + var childrens = $(this).tree('getChildren',node.target); + if(childrens != null && childrens.length != 0){ + for(var x = 0;x < childrens.length ; x++){ + $(this).tree('uncheck',childrens[x].target); + } + } + } + }, + + # 双击打开/关闭节点 + onDblClick:function (node) { + $(this).tree('toggle',node.target); + }, + +--------------------- +datagrid | +--------------------- + # 动态的隐藏toolbar按钮 + /* + 把第3个摁钮隐藏 + */ + $('div.datagrid-toolbar a').eq(2).hide(); + /* + 把第3个摁钮分隔符('|')隐藏 + */ + $('.datagrid-btn-separator').eq(2).hide(); + + * 同理的,既然可以获取到摁钮的jq对象,就可以执行linkbutton的方法,禁用掉它 + $('div.datagrid-toolbar a').eq(2).linkbutton('disable'); + +--------------------- +validatebox | +--------------------- + # 扩展easyui的validatebox,新增验证 + $.extend($.fn.validatebox.defaults.rules,{ + name:{ + validator : function(value,param){ + console.log(value); + return /^(?!\d+$)(?!_+$)\w{1,14}$/.test(value.replace(/[\u4e00-\u9fa5]/g,"aa")); + }, + message:'名称格式错误' + } + }); \ No newline at end of file diff --git "a/EasyUI/easyui-\347\273\204\344\273\266\346\211\251\345\261\225.js" "b/EasyUI/easyui-\347\273\204\344\273\266\346\211\251\345\261\225.js" new file mode 100644 index 00000000..228da287 --- /dev/null +++ "b/EasyUI/easyui-\347\273\204\344\273\266\346\211\251\345\261\225.js" @@ -0,0 +1,8 @@ +---------------------------- +组件的扩展 | +---------------------------- + # 替换或者增加组件默认的属性或者方法 + $.extend($.fn.[组件].defaults.[属性],{ + // 新增的属性,或者方法 + }); + diff --git "a/Ecache/ehcache-\344\272\213\344\273\266\347\233\221\345\220\254.java" "b/Ecache/ehcache-\344\272\213\344\273\266\347\233\221\345\220\254.java" new file mode 100644 index 00000000..b7e69483 --- /dev/null +++ "b/Ecache/ehcache-\344\272\213\344\273\266\347\233\221\345\220\254.java" @@ -0,0 +1,41 @@ + +----------------------------- +浜嬩欢鐩戝惉 | +----------------------------- + # 瀹炵幇 CacheEventListener 鎺ュ彛 + import org.ehcache.event.CacheEvent; + import org.ehcache.event.CacheEventListener; + import org.ehcache.event.EventType; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + public class Li implements CacheEventListener{ + + private static final Logger LOGGER = LoggerFactory.getLogger(Li.class); + + @Override + public void onEvent(CacheEvent event) { + Long key = event.getKey(); + String newValue = event.getNewValue(); + String oldValue = event.getOldValue(); + EventType eventType = event.getType(); + LOGGER.info("key={},newValue={},oldValue={},eventType={}",key,newValue,oldValue,eventType); + } + } + + + # xml閰嶇疆 + + + + + + com.tedi.door.utils.Li + + ASYNCHRONOUS + + UNORDERED + + CREATED + + diff --git "a/Ecache/ehcache-\345\205\245\351\227\250.java" "b/Ecache/ehcache-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..ae38ab52 --- /dev/null +++ "b/Ecache/ehcache-\345\205\245\351\227\250.java" @@ -0,0 +1,243 @@ +---------------------------- +入门 | +---------------------------- + # 官网 + http://www.ehcache.org/ + + # 特点 + 快速 + 简单 + 多种缓存策略 + 缓存数据有两级:内存和磁盘,因此无需担心容量问题 + 缓存数据会在虚拟机重启的过程中写入磁盘 + 可以通过RMI,可插入API等方式进行分布式缓存 + 具有缓存和缓存管理器的侦听接口 + 支持多缓存管理器实例,以及一个实例的多个缓存区域 + 提供Hibernate的缓存实现 + + # 集成 + * 可以单独使用.一般在第三方库中被用到的比较多(如mybatis,shiro等) + * ehcache 对分布式支持不够好,多个节点不能同步,通常和redis一块使用 + + + # 配置文件 + * 默认情况下Ehcache会自动加载classpath根目录下名为ehcache.xml文件 + * 也可以将该文件放到其他地方在使用时指定文件的位置 + + +---------------------------- +代码形式初始化 | +---------------------------- + //CacheConfiguration,用于配置Cache + CacheConfigurationBuilder cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class,String.class, ResourcePoolsBuilder.heap(10)); + + //创建cacheManagerBuilder + CacheManagerBuilder cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder(); + + //通过build()方法获取cacheManager实例对象,build()方法可以传递一个bool值,表示是否立即初始化cacheManager + CacheManager cacheManager = cacheManagerBuilder.build(); + + //初始化cacheManager实例 + cacheManager.init(); + + //通过cacheManager创建新的cache对象 + Cache myCache = cacheManager.createCache("myCache", cacheConfigurationBuilder); + + //往缓存添加一个值 + myCache.put(1L, "da one!"); + + //从缓存获取一个值 + String value = myCache.get(1L); + + System.out.println(value); + + //删除 Cache,还会将其关闭,Cache释放所有本地保存的瞬态资源(如内存),此Cache变得不可用 + cacheManager.removeCache("preConfigured"); + + //关闭cacheManager + cacheManager.close(); + +---------------------------- +配置文件形式初始化 | +---------------------------- + //配置文件地址 + URL url = Main.class.getResource("/ehcache.xml"); + + //实例化xml配置对象 + Configuration xConfiguration = new XmlConfiguration(url); + + //根据xml配置对象创建CacheManager + CacheManager cacheManager = CacheManagerBuilder.newCacheManager(xConfiguration); + + //初始化cacheManager + cacheManager.init(); + + //获取指定名称的cache + Cache cache = cacheManager.getCache("foo", String.class, String.class); + + cache.put("1", "KevinBlandy"); + System.out.println(cache.get("1")); + cache.remove("1"); + + //从cacheManager移除cache,但是该cache还可以被其他的CacheManager使用 + cacheManager.removeCache("foo"); + + //关闭cacheManager,它实现了Closeable接口 + cacheManager.close(); + +---------------------------- +存储层 | +---------------------------- + # 代码构建 + //创建 ResourcePoolsBuilder + ResourcePoolsBuilder resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder(); + + /** + 可以仅仅使用一种类型的 resourcePoolsBuilder,例如:可以使用缓存中的数据只能在offheap(堆外)中使用 + 也可以像下面一样组合 + **/ + + resourcePoolsBuilder = resourcePoolsBuilder.heap(10, EntryUnit.ENTRIES); //堆存储属性控制 + resourcePoolsBuilder = resourcePoolsBuilder.offheap(1, MemoryUnit.MB); //堆外存储属性控制 + resourcePoolsBuilder = resourcePoolsBuilder.disk(20, MemoryUnit.MB, true); //硬盘存储属性控制 + + //创建 cacheConfigurationBuilder + CacheConfigurationBuilder cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, resourcePoolsBuilder); + + //创建 CacheConfigurationBuilder + CacheManagerBuilder cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder(); + + //创建 persistentCacheManagerBuilder + CacheManagerBuilder persistentCacheManagerBuilder = cacheManagerBuilder.with(CacheManagerBuilder.persistence(new File("e:\\ehcache", "myData"))); + + //创建 PersistentCacheManager + PersistentCacheManager persistentCacheManager = persistentCacheManagerBuilder.build(true); + + //使用 cacheConfigurationBuilder 创建 cache + Cache cache = persistentCacheManager.createCache("foo", cacheConfigurationBuilder); + + cache.put(1L, "KevinBlandy"); + + System.out.println(cache.get(1L)); + + # 三层 + 1,堆内 + 2,堆外 + 3,磁盘 + 4,集群(不推荐) + + * 当缓存在堆外的数据,必须实现序列化接口 + + * 所有分层选项都可以单独使用,例如,可以使用缓存中的数据只能在offheap中使用 + CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.newResourcePoolsBuilder().offheap(2, MemoryUnit.GB)).build(); + + # 堆层 + * 每个缓存的起点,也是更快的,因为不需要序列化 + * 可以按条目或按大小调整堆层的大小 + ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10, EntryUnit.ENTRIES); //堆上只允许有10个条目 + ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10); //堆上只允许有10个条目(默认) + ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10, MemoryUnit.MB); //只允许10 MB + + # 堆外 + * 使用堆外,必须定义一个资源池,给你想要分配的内存大小 + ResourcePoolsBuilder.newResourcePoolsBuilder().offheap(10, MemoryUnit.MB); //只有10MB大小 + * 堆外数据必须被序列化和反序列化 - 因此比堆更慢 + * 倾向于堆大量的数据,这些数据堆上的垃圾收集,会有太严重的影响(几乎不会被GC) + * -XX:MaxDirectMemorySize:根据打算使用的堆外大小,不要忘记在java选项中定义该选项 + + # 磁盘层 + * 数据存储在磁盘上,磁盘越快,越专用,访问数据的速度就越快 + PersistentCacheManager persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder() + .with(CacheManagerBuilder.persistence(new File(getStoragePath(), "myData"))) + .withCache("persistent-cache", CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, + ResourcePoolsBuilder.newResourcePoolsBuilder().disk(10, MemoryUnit.MB)) + ).build(true); + persistentCacheManager.close(); + + * 设置磁盘永久存储,默认非永久 + ResourcePoolsBuilder.newResourcePoolsBuilder().disk(10, MemoryUnit.MB, true) + + * 磁盘层不能在缓存管理器之间共享,持久性目录当时专用于一个高速缓存管理器 + + * Ehcache 3仅在正常关闭的情况下提供持久性 + * 必须执行了 CacheManager的 close() api + * 如果JVM崩溃,则不存在数据完整性保证 + * 在重新启动时,Ehcache会检测到它CacheManager没有完全关闭,并在使用之前擦除磁盘存储器 + + * 磁盘存储分为多个段,提供并发访问,但也保存打开的文件指针,缺省值是16 + * 在某些情况下,可能希望通过减少段的数量来降低并发性并节省资源 + + ResourcePoolsBuilder resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder(); + resourcePoolsBuilder = resourcePoolsBuilder.disk(20, MemoryUnit.MB, true); + + CacheConfigurationBuilder cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, resourcePoolsBuilder); + //设置分段大小 + cacheConfigurationBuilder.add(new OffHeapDiskStoreConfiguration(2)); + + + # 多层设置 + * 使用多个层次,有一些限制 + 1,在多层设置中必须始终有一个堆层 + 2,您不能合并磁盘层和集群层 + 3,层级的大小应该是金字塔形的,也就是说金字塔层级越高,层级越低 + + * 它们之间的大小关系:堆内存大小 < 堆外内存大小 < 磁盘大小 + + # 资源池 + * 多层使用资源池进行配置,大多数时间使用一个ResourcePoolsBuilder + ResourcePoolsBuilder resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder(); + //堆内存的设置 + resourcePoolsBuilder = resourcePoolsBuilder.heap(10, EntryUnit.ENTRIES); + //堆外内存设置 + resourcePoolsBuilder = resourcePoolsBuilder.offheap(1, MemoryUnit.MB); + //磁盘设置 + resourcePoolsBuilder = resourcePoolsBuilder.disk(20, MemoryUnit.MB, true); + + CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, resourcePoolsBuilder); + + * 使用3层的缓存(堆,offheap,磁盘)他们创建并链接使用 ResourcePoolsBuilder 声明顺序无关紧要(例如offheap可以在堆之前声明) + + * 资源池'仅用来指定配置',它不是可以在缓存之间共享的实际池 + ResourcePools pool = ResourcePoolsBuilder.newResourcePoolsBuilder().heap(10).build(); + CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder() + .withCache("test-cache1", CacheConfigurationBuilder.newCacheConfigurationBuilder(Integer.class, String.class, pool)) + .withCache("test-cache2", CacheConfigurationBuilder.newCacheConfigurationBuilder(Integer.class, String.class, pool)) + .build(true); + + * test-cache1和test-cache2将是两个缓存 + + * 更新ResourcePools + * updateResourcePools()允许更改堆层大小,而不是池类型 + * 因此,'不能更改堆外层或磁盘层的大小' + + ResourcePools pools = ResourcePoolsBuilder.newResourcePoolsBuilder().heap(20L, EntryUnit.ENTRIES).build(); + + cache.getRuntimeConfiguration().updateResourcePools(pools); + + assertThat(cache.getRuntimeConfiguration().getResourcePools() + .getPoolForResource(ResourceType.Core.HEAP).getSize(), is(20L)); + + # 摧毁 + * 堆外和磁盘,是两个持续层,这意味着当JVM停止时,所有创建的缓存及其数据仍存在于磁盘或群集中 + * 需要完全删除它们,他们的定义 PersistentCacheManager 提供了以下方法 + destroy() + * 此方法销毁与缓存管理器相关的所有内容(当然包括缓存) + * 缓存管理器必须关闭或未初始化才能调用此方法 + * 另外,对于群集层,当前没有其他缓存管理器应该连接到相同的缓存管理器服务器实体 + + destroyCache(String cacheName) + * 此方法销毁给定的缓存,该缓存不应该被另一个缓存管理器使用 + + + + # 多层缓存操作的顺序流程 + 1,当向缓存中添加一个值时,它会直接进入最低层的授权层。 + 2,以下内容get将会在缓存层中向上推送该值。 + 3,当然,只要授权层中有值,所有更高级别的缓存层都会失效。 + 4,完全缓存未命中(该值不在任何层上)始终会一直延伸到授权层。 + + +---------------------------- +总结 | +---------------------------- + \ No newline at end of file diff --git "a/Ecache/ehcache-\345\243\260\346\230\216\345\221\250\346\234\237.java" "b/Ecache/ehcache-\345\243\260\346\230\216\345\221\250\346\234\237.java" new file mode 100644 index 00000000..0a9aad20 --- /dev/null +++ "b/Ecache/ehcache-\345\243\260\346\230\216\345\221\250\346\234\237.java" @@ -0,0 +1,53 @@ + +----------------------- +缂撳瓨鐨勮繃鏈 | +----------------------- + # 閫氳繃浠g爜閰嶇疆 + CacheConfiguration cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,ResourcePoolsBuilder.heap(100)) + //鏈夋晥鏃堕棿20绉 + .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(20))) + .build(); + + # 閫氳繃xml閰嶇疆 + + + 20 + + 100 + + + # Java鍜孹ML閮芥彁渚涘涓夌鍒版湡鏃堕棿鐨勭洿鎺ユ敮鎸 + 1,娌℃湁鍒版湡 + * 杩欐剰鍛崇潃缂撳瓨鏄犲皠姘歌繙涓嶄細杩囨湡 + + 2,鏃堕棿鍒扮幇鍦 + * 杩欐剰鍛崇潃缂撳瓨鏄犲皠灏嗗湪鍒涘缓鍚庣殑鍥哄畾鎸佺画鏃堕棿鍚庤繃鏈 + + 3,鏃惰嚦鎬犻 + * 杩欐剰鍛崇潃缂撳瓨鏄犲皠灏嗗湪涓婃璁块棶鍚庣殑鍥哄畾鎸佺画鏃堕棿鍚庤繃鏈 + + + //鑷畾涔夌殑杩囨湡璁剧疆 + //姘歌繙涓嶈繃鏈 + 20 //鎸囧畾鏃堕棿杩囨湡 + 20 //璺濈鏈鍚庝竴娆¤闂悗瓒呮椂杩囨湡 + + + * 浠ヤ笂涓夌绛栫暐,鍙兘璁剧疆涓绉 + +----------------------- +鑷畾涔夊埌鏈 | +----------------------- + # 瀹炵幇鎺ュ彛 + @Deprecated + public interface Expiry + + # xml閰嶇疆鑷畾涔夊埌鏈熷疄鐜 + + xx.x.x + + + # 浠g爜閰嶇疆 + CacheConfiguration cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,ResourcePoolsBuilder.heap(100)) + .withExpiry(new CustomExpiry()) + .build(); \ No newline at end of file diff --git "a/Ecache/ehcache-\346\234\200\344\275\263\345\256\236\350\267\265.java" "b/Ecache/ehcache-\346\234\200\344\275\263\345\256\236\350\267\265.java" new file mode 100644 index 00000000..e69de29b diff --git "a/Ecache/ehcache-\347\272\277\347\250\213\346\261\240.java" "b/Ecache/ehcache-\347\272\277\347\250\213\346\261\240.java" new file mode 100644 index 00000000..4888b281 --- /dev/null +++ "b/Ecache/ehcache-\347\272\277\347\250\213\346\261\240.java" @@ -0,0 +1,41 @@ + +# 浠g爜閰嶇疆 + 鎭跺績,澶暱,涓嶇湅 + +# 鐢╔ML閰嶇疆绾跨▼姹 + + + + + + + + + + + + + + + + + java.lang.Long + java.lang.String + + org.ehcache.docs.plugs.ListenerObject + + + + 10 + + + + + + + 10 + 10 + + + + \ No newline at end of file diff --git "a/Ecache/ehcache-\350\207\252\345\256\232\344\271\211\345\272\217\345\210\227\345\214\226.java" "b/Ecache/ehcache-\350\207\252\345\256\232\344\271\211\345\272\217\345\210\227\345\214\226.java" new file mode 100644 index 00000000..e69de29b diff --git "a/Ecache/ehcache-\351\233\206\347\276\244.java" "b/Ecache/ehcache-\351\233\206\347\276\244.java" new file mode 100644 index 00000000..e69de29b diff --git a/Ecache/ehcache.xml b/Ecache/ehcache.xml new file mode 100644 index 00000000..ccf11c5a --- /dev/null +++ b/Ecache/ehcache.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + java.lang.Long + java.lang.String + + 200 + + + + + + java.lang.Long + java.lang.String + + + + + + 20 + + + + + + + com.tedi.door.utils.Li + + ASYNCHRONOUS + + UNORDERED + + CREATED + + + + + + 100 + + 150 + + 500 + + + + + + + + java.lang.String + + + + + + + diff --git a/Elasticsearch/client/client.java b/Elasticsearch/client/client.java new file mode 100644 index 00000000..f4a7b6e7 --- /dev/null +++ b/Elasticsearch/client/client.java @@ -0,0 +1,17 @@ +------------------------------ +java客户端 | +------------------------------ + + + + org.elasticsearch.client + elasticsearch-rest-client + 7.1.1 + + + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + 7.1.1 + diff --git a/Elasticsearch/es-config-elasticsearch.yml b/Elasticsearch/es-config-elasticsearch.yml new file mode 100644 index 00000000..b9666247 --- /dev/null +++ b/Elasticsearch/es-config-elasticsearch.yml @@ -0,0 +1,51 @@ +# ---------------------------------- Cluster ----------------------------------- +# 集群相关的配置 | +# ---------------------------------- Cluster ----------------------------------- +cluster.name: elaticsearch + * 集群的名称 + +# ------------------------------------ Node ------------------------------------ +# 节点的设置 | +# ------------------------------------ Node ------------------------------------ +node.name: node-1 + * 当前节点的名称 + +node.attr.rack: r1 + * 可以为当前的节点添加自定义的属性 + +# ----------------------------------- Paths ------------------------------------ +# 存储目录的配置 | +# ----------------------------------- Paths ------------------------------------ +path.data: D:\elasticsearch\data +path.logs: D:\elasticsearch\log + * 数据存储目录和日志存储目录 + * 可以有多个目录, 使用逗号分隔 + +# ----------------------------------- Memory ----------------------------------- +# 内存配置 | +# ----------------------------------- Memory ----------------------------------- +bootstrap.memory_lock: true + +# ---------------------------------- Network ----------------------------------- +# 网络配置 | +# ---------------------------------- Network ----------------------------------- +network.host: 192.168.0.1 +http.port: 9200 + * 设置监听的网卡, 以及端口 + * 只要提供自定义设置network.host, Elasticsearch就会认为正在从开发模式转移到生产模式, 并将许多系统启动检查从警告升级到异常 + +# --------------------------------- Discovery ---------------------------------- +# 集群服务发现 | +# --------------------------------- Discovery ---------------------------------- +discovery.seed_hosts: ["host1", "host2"] +cluster.initial_master_nodes: ["node-1", "node-2"] + +# ---------------------------------- Gateway ----------------------------------- +# +# ---------------------------------- Gateway ----------------------------------- +gateway.recover_after_nodes: 3 + +# ---------------------------------- Various ----------------------------------- +# +# ---------------------------------- Various ----------------------------------- +action.destructive_requires_name: true diff --git a/Elasticsearch/es-config-jvm.options b/Elasticsearch/es-config-jvm.options new file mode 100644 index 00000000..93a426ed --- /dev/null +++ b/Elasticsearch/es-config-jvm.options @@ -0,0 +1,137 @@ +-------------------------------- +jvm.options | +-------------------------------- + # 这个是JVM相关的配置, 可以通过该文件来修改JVM的参数 + # 针对于不同的JVM版本参数, 需要通过前缀: : 来控制 + 8:-Xmx2g + * 必须是JDK8才会生效 + + 8-:-Xmx2g + * 必须是与于JDK8才会生效 + + 8-9:-Xmx2g + * 必须是JDK8 - JDK9之间才会生效 + + +-------------------------------- +jvm.options | +-------------------------------- +## JVM configuration + +################################################################ +## IMPORTANT: JVM heap size +################################################################ +## +## You should always set the min and max JVM heap +## size to the same value. For example, to set +## the heap to 4 GB, set: +## +## -Xms4g +## -Xmx4g +## +## See https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html +## for more information +## +################################################################ + +# Xms represents the initial size of total heap space +# Xmx represents the maximum size of total heap space + +-Xms1g +-Xmx1g + +################################################################ +## Expert settings +################################################################ +## +## All settings below this section are considered +## expert settings. Don't tamper with them unless +## you understand what you are doing +## +################################################################ + +## GC configuration +-XX:+UseConcMarkSweepGC +-XX:CMSInitiatingOccupancyFraction=75 +-XX:+UseCMSInitiatingOccupancyOnly + +## G1GC Configuration +# NOTE: G1GC is only supported on JDK version 10 or later. +# To use G1GC uncomment the lines below. +# 10-:-XX:-UseConcMarkSweepGC +# 10-:-XX:-UseCMSInitiatingOccupancyOnly +# 10-:-XX:+UseG1GC +# 10-:-XX:InitiatingHeapOccupancyPercent=75 + +## DNS cache policy +# cache ttl in seconds for positive DNS lookups noting that this overrides the +# JDK security property networkaddress.cache.ttl; set to -1 to cache forever +-Des.networkaddress.cache.ttl=60 +# cache ttl in seconds for negative DNS lookups noting that this overrides the +# JDK security property networkaddress.cache.negative ttl; set to -1 to cache +# forever +-Des.networkaddress.cache.negative.ttl=10 + +## optimizations + +# pre-touch memory pages used by the JVM during initialization +-XX:+AlwaysPreTouch + +## basic + +# explicitly set the stack size +-Xss1m + +# set to headless, just in case +-Djava.awt.headless=true + +# ensure UTF-8 encoding by default (e.g. filenames) +-Dfile.encoding=UTF-8 + +# use our provided JNA always versus the system one +-Djna.nosys=true + +# turn off a JDK optimization that throws away stack traces for common +# exceptions because stack traces are important for debugging +-XX:-OmitStackTraceInFastThrow + +# flags to configure Netty +-Dio.netty.noUnsafe=true +-Dio.netty.noKeySetOptimization=true +-Dio.netty.recycler.maxCapacityPerThread=0 + +# log4j 2 +-Dlog4j.shutdownHookEnabled=false +-Dlog4j2.disable.jmx=true + +-Djava.io.tmpdir=${ES_TMPDIR} + +## heap dumps + +# generate a heap dump when an allocation from the Java heap fails +# heap dumps are created in the working directory of the JVM +-XX:+HeapDumpOnOutOfMemoryError + +# specify an alternative path for heap dumps; ensure the directory exists and +# has sufficient space +-XX:HeapDumpPath=data + +# specify an alternative path for JVM fatal error logs +-XX:ErrorFile=logs/hs_err_pid%p.log + +## JDK 8 GC logging + +8:-XX:+PrintGCDetails +8:-XX:+PrintGCDateStamps +8:-XX:+PrintTenuringDistribution +8:-XX:+PrintGCApplicationStoppedTime +8:-Xloggc:logs/gc.log +8:-XX:+UseGCLogFileRotation +8:-XX:NumberOfGCLogFiles=32 +8:-XX:GCLogFileSize=64m + +# JDK 9+ GC logging +9-:-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m +# due to internationalization enhancements in JDK 9 Elasticsearch need to set the provider to COMPAT otherwise +# time/date parsing will break in an incompatible way for some date patterns and locals +9-:-Djava.locale.providers=COMPAT diff --git a/Elasticsearch/es-config-log4j2.properties b/Elasticsearch/es-config-log4j2.properties new file mode 100644 index 00000000..838df46e --- /dev/null +++ b/Elasticsearch/es-config-log4j2.properties @@ -0,0 +1,11 @@ +--------------------------------- +log4j2.properties | +--------------------------------- + # 日志相关的配置 + https://www.elastic.co/guide/en/elasticsearch/reference/current/logging.html + + +--------------------------------- +log4j2.properties | +--------------------------------- + \ No newline at end of file diff --git "a/Elasticsearch/es-core-\345\200\222\346\216\222\347\264\242\345\274\225.java" "b/Elasticsearch/es-core-\345\200\222\346\216\222\347\264\242\345\274\225.java" new file mode 100644 index 00000000..b6ec5bda --- /dev/null +++ "b/Elasticsearch/es-core-\345\200\222\346\216\222\347\264\242\345\274\225.java" @@ -0,0 +1,22 @@ + +-------------------- +倒排索取 | +-------------------- + # 倒排索引的建立 + doc-1: Hello KevinBlandy,Im Coder + doc-2: Hello Litch,You are Coder ? + + 关键字 doc-1 doc-2 + ------------------------------ + Hello * * + KevinBlandy * + Im * + Coder * * + + * 关键字在哪些索引出现过 + + # 在建立倒排索引的时候,会执行一个操作 normalization + * 对拆分出来的各个单词进行相应的处理 + * 以提升,后面搜索的时候能够搜索到关联文档的概率 + + * 我的理解其实就是对分词进行一些同义词,复数,时态,大小写...之类的转换 diff --git "a/Elasticsearch/es-core-\347\233\270\345\205\263\345\272\246\350\257\204\345\210\206\347\256\227\346\263\225.java" "b/Elasticsearch/es-core-\347\233\270\345\205\263\345\272\246\350\257\204\345\210\206\347\256\227\346\263\225.java" new file mode 100644 index 00000000..270d9ecc --- /dev/null +++ "b/Elasticsearch/es-core-\347\233\270\345\205\263\345\272\246\350\257\204\345\210\206\347\256\227\346\263\225.java" @@ -0,0 +1,17 @@ +------------------------ +相关度评分算法 | +------------------------ + # relevance score算法 + * 计算出一个索引中的文本, 与搜索关键字之间的关联匹配程度 + + # ES使用的算法(TF/IDF) + term frequency + inverse document frequency + + + # term frequency + * 在doc中出现, 搜索关键字次数越多, 就越相关 + + # inverse document frequency + * 在doc中出现, 搜索关键字次数越多, 就越不相关 + diff --git a/Elasticsearch/es-document-DSL-all.java b/Elasticsearch/es-document-DSL-all.java new file mode 100644 index 00000000..e075df84 --- /dev/null +++ b/Elasticsearch/es-document-DSL-all.java @@ -0,0 +1,54 @@ +----------------------- +queryDSL 整个结构 | +----------------------- +{ + "query":{ + "match_all":{}, + "match":{"field":"value"}, + "term":{ + "field":"value" + }, + "terms":{ + "field":["value1","value2"] + } + "match_phrase":{ + "field":"value" + } + "multi_match":{ + "query":"value", + "fields":["field1,field2"] + }, + "exists":{ + "field":"field" + }, + "bool":{ + "must":{ + "match":{ + "key":"value" + } + }, + "filter":{ + "range":{ + "field":{"gt/lt/le/ge/ne":"value"} + } + } + } + }, + "sort":[ + {"key1":"desc"}, + {"key2":"asc"} + ], + "from":1, + "size":2, + "_source":[], + "highlight":{ + "fields":{ + "field":{} + } + }, + "aggs":{ + }, + "properties":{ + + } +} \ No newline at end of file diff --git a/Elasticsearch/es-document-DSL-validate.java b/Elasticsearch/es-document-DSL-validate.java new file mode 100644 index 00000000..9754f8e1 --- /dev/null +++ b/Elasticsearch/es-document-DSL-validate.java @@ -0,0 +1,29 @@ +-------------------------------- +validate | +-------------------------------- + # 类似于SQL中的 explain, 不仅可以判断搜索是否合法 + * 在使用非常复杂的搜索DSL情况下, 可以先通过这种方式来验证是否合法 + + # 语法 + GET //_validate/query?explain + { + "query":.... + } + + { + "_shards" : { + "total" : 1, + "successful" : 1, + "failed" : 0 + }, + "valid" : true, + * 是否合法 + "explanations" : [ + { + "index" : "", + "valid" : true, + "explanation" : "ConstantScore(NormsFieldExistsQuery [field=modify_date])" + } + ] + } + diff --git "a/Elasticsearch/es-document-DSL-\350\201\232\345\220\210.java" "b/Elasticsearch/es-document-DSL-\350\201\232\345\220\210.java" new file mode 100644 index 00000000..32a7242d --- /dev/null +++ "b/Elasticsearch/es-document-DSL-\350\201\232\345\220\210.java" @@ -0,0 +1,83 @@ +------------------------------ +聚合检索 | +------------------------------ + +------------------------------ +聚合检索 | +------------------------------ + # aggs + { + "aggs":{ + "":{ + "": { + "field": "", + "order": { + "" : "" + } + } + } + } + } + + * name, 对分组起名,自定义 + * agg, 聚合的统计操作 + * field , 用于分组的属性, 可以是数组, 字段, 不能是对象,否则检索不出来数据 + + + # terms + * 统计总数量 + + # ranges + * 根据某个区间值来进行分组 + + # avg + * 求平均值 + + + + # 聚合检索可以嵌套计算 + GET /goods/_search + { + "aggs":{ + "groupByAuthorAge":{ + "terms": { + "field": "author.age" // 先根据作者的年龄来聚合检索 + }, + "aggs": { + "avgd": { + "avg": { + "field": "price" // 根据聚合检索的结果, 对价格字段再次进行平均值的计算 + } + } + } + } + } + } + * 类似于, 先group by ,然后再执行count,avg啥的计算 + + + # 使用排序 + GET /goods/_search + { + "aggs":{ + "groupByAuthorAge":{ + "terms": { + "field": "author.age", + "order":{ + "avgd":"desc" // 根据子聚合结果进行降序排序 + } + }, + "aggs": { + "avgd": { + "avg": { + "field": "price" + } + } + } + } + } + } + + # 可以通过设置: size : 0, 来限制显示的结果 + * 仅仅只查看聚合的统计结果, 不查看doc + diff --git "a/Elasticsearch/es-document-DSL-\351\253\230\344\272\256.java" "b/Elasticsearch/es-document-DSL-\351\253\230\344\272\256.java" new file mode 100644 index 00000000..cbdc60bb --- /dev/null +++ "b/Elasticsearch/es-document-DSL-\351\253\230\344\272\256.java" @@ -0,0 +1,56 @@ +------------------------------ +DSL 结果高亮 | +------------------------------ + # 文档 + https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-highlighting.html + + # 使用 highlight + { + "query" : { + "match": { "": "" } + }, + "highlight" : { + "fields" : { + "" : {} + } + } + } + + * filed 指定要高亮检索的字段,可以通过属性控制高亮的很多行为 + + # 响应 + { + "took" : 78, + "timed_out" : false, + "_shards" : { + "total" : 1, + "successful" : 1, + "skipped" : 0, + "failed" : 0 + }, + "hits" : { + "total" : { + "value" : 1, + "relation" : "eq" + }, + "max_score" : 1.2693083, + "hits" : [ + { + "_index" : "goods", + "_type" : "_doc", + "_id" : "1", + "_score" : 1.2693083, + "_source" : { + ... + }, + "highlight" : { + "" : [ + "去打球当前" + ] + } + } + ] + } + } + + * 响应的结果保函了高亮的结果highlight diff --git a/Elasticsearch/es-document-DSL.java b/Elasticsearch/es-document-DSL.java new file mode 100644 index 00000000..165801c6 --- /dev/null +++ b/Elasticsearch/es-document-DSL.java @@ -0,0 +1,250 @@ +------------------------------ +DSL | +------------------------------ + # 文档 + https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html + + # HTTP协议规定GET没有请求体,一般也不允许GET请求带有body,但GET更加适合于检索场景 + * 如果遇到不支持的场景,也可以使用POST方法 + _search 端点 + POST //_search + +------------------------------ +DSL query | +------------------------------ + # match_all + * 匹配所有 + {"query": { "match_all": {} }} + + # match + * 全文检索 + { + "query": { "match": { "": } } + } + + * 如果允许匹配多个字段, 那么 keyworlds 可以有多个, 使用逗号分隔 + { + "query":{"match": {"name":"Litch Rocck"}} //name = "Litch" or name = "Rocck" + } + * 会把检索的内容进行分词, 然后去倒排索引中检索, 只要是索引可以匹配到任何一个词, 就可以算作结果 + * 通俗理解就是, 关键字被拆分后, 任何一个文档匹配到了任何关键字, 都ok, 多个关键字与文档的关系是 or + + # match_phrase + * 跟全文检索差不多, 也是会对关键字进行分词 + * 但是必须要求doc中的关键字, 符合检索条件中的所有, 才会算作结果, 多个关键字与文档的关系是 and + { + "query":{"match_phrase": {"name":"Litch Rocck"}} // 检索name同时包含了 Litch Rocck 的记录 + } + + * 完全匹配限制非常的严格, 必须要求doc中出现, 关键字拆词儿后的所有词 + * 有个可调节因子, 可以允许少匹配N个关键字也满足条件, 使用到:slop + { + "query":{ + "match_phrase": { + "":{ + "query": "", + "slop": 1 // 只要 field 字段中, 起码出现了1个 keywords拆词后的词, 就算作返回结果 + } + } + }, + } + + + + # multi_match + * 跟 match 差不多, 可以把检索的关键字, 用于多个指定的字段 + { + "query": { + "multi_match":{ + "query":"", // 检索的关键字 + "fields":["field1", "field2"...] // 匹配的字段 + } + } + } + + * 只要是任何字段成功匹配, 都算作返回记录 + + # match_phrase_prefix + + # range + * 区间值匹配, 指定的字段大于, 等于, 小于, 大于等于, 小于等于 指定的值 + { + "query":{ + "range":{ + "":{ + "":"" + "":"" + } + } + } + } + + * operation 可以有: + gt + lt + le + ge + ne + + # term + * 精准匹配, 不对关键字进行分词器分析, 文档中指定字段必须包含整个搜索的词汇 + { + "query": { + "term": { + "":'' + } + } + } + + * 使用term要确定的是这个字段是否"被分析(analyzed)", 默认的字符串是被分析的 + * 通俗理解就是, 关键字没分词, 但是你doc却分词了, 就算doc和关键字是一摸一样的, 也没法匹配 + * 关键字没分词, doc也没分词, 内容一样, 就可以匹配 + + + # terms + * 跟term一样, 但是允许指定的字段, 匹配多个值 + { + "query":{ + "terms": { + "": ["", ""] + } + } + } + + # exists + * 指定的field必须存在, 并且不能为 null, 就符合条件 + { + "query":{ + "exists": { + "field": "" + } + } + } + + + + +------------------------------ +DSL bool | +------------------------------ + # 把多个条件使用布尔逻辑将较小的查询组成更大的查询 + # and 关系用:must + { + "query": { + "bool": { + "must": [ + { "match": { "name": "KevinBlandy" } }, + { "match": { "age": "23" } } + ] + } + } + } + * 该bool返回一个判断条件:name = "KevinBlandy" and age = "23" + + # or关系用:should + { + "query": { + "bool": { + "should": [ + { "match": { "name": "Litch" } }, + { "match": { "name": "Roccl" } } + ] + } + } + } + * 该 bool 返回一个判断条件:name = "Litch" or name = "Roccl" + + # 结果取反用:must_not + { + "query": { + "bool": { + "must_not": [ + { "match": { "name": "Litch" } }, + { "match": { "name": "Roccl" } } + ] + } + } + } + * 该 bool 返回一个判断条件:name != "Litch" and name != "Roccl" + + # 多个条件可以组合 + { + "query":{ + "bool":{ + "should": [{ + "match": { + "name": "Litch" + }} + ], + "must_not": [{ + "match": { + "age": 24 + }} + ] + } + } + } + * 该 bool 返回一个判断条件:name = "Litch" and 24 != 24 + + # bool也可以嵌套, 类似于SQL中的子查询 + + # minimum_should_match + + +------------------------------ +DSL filter | +------------------------------ + # 过滤, 使用查询来限制将与其他子句匹配的文档, 而不会更改计算分数的方式 + + # range 计算区间值 + { + "query":{ + "bool":{ + "must":{ + "match_all":{} + }, + "filter":{ + "range":{ + "":{ + "": + } + } + } + } + } + } + + + # filter 和 query 的对比区别 + * filter, 仅仅只会按照条件过滤出需要的数据 + * query, 会去计算出每个doc相对于搜索条件的相关度, 并且安装相关度进行排序 + + * 如果只是进行搜索, 需要把匹配搜索条件的数据先返回, 用query + * 如果只是要根据条件, 筛选出一部分的数据, 不关注排序, 用filter + + * filter 不需要计算相关度分数, 不需要按照相关度进行排序, 同时还会cache最常使用 + * query 需要计算相关度分数, 还要进行排序, 不能cache结果 + +------------------------------ +DSL 分页 | +------------------------------ + # 使用 from & size + {"from":0, "size":10} + +------------------------------ +DSL 排序 | +------------------------------ + # 使用 sort + "sort": [{ "": { "order": "" } }] + + # 默认的排序规则是根据元数据中的:_score 来进行排序的(相关度越高的越在前面) + +------------------------------ +DSL 限制结果字段 | +------------------------------ + # 使用 _source + { + "_source": ["", ""] + } + + * 通过 _source 指定n多个要检索的字段, 字段支持对象导航 + diff --git a/Elasticsearch/es-document-termvectors.java b/Elasticsearch/es-document-termvectors.java new file mode 100644 index 00000000..f352a8ae --- /dev/null +++ b/Elasticsearch/es-document-termvectors.java @@ -0,0 +1,6 @@ +---------------------------- +termvectors | +---------------------------- + # 文档 + https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-termvectors.html + diff --git "a/Elasticsearch/es-document-\345\210\240\351\231\244.java" "b/Elasticsearch/es-document-\345\210\240\351\231\244.java" new file mode 100644 index 00000000..cb734f3b --- /dev/null +++ "b/Elasticsearch/es-document-\345\210\240\351\231\244.java" @@ -0,0 +1,121 @@ +-------------------- +document - 删除 | +-------------------- + # document不会被立即的物理删除,只会被标记为delete,当数据越来越多的试试,在后台自动的删除 + * 尝试删除一个doc后, 再次根据这个id创建一个doc, 发现_version字段会增加 + + +-------------------- +document - 删除 | +-------------------- + # 基本的根据id删除 + DELETE //_update/?pretty + + { + "_index" : "customer", + "_type" : "_doc", + "_id" : "11", + "_version" : 3, + "result" : "deleted", + "_shards" : { + "total" : 2, + "successful" : 1, + "failed" : 0 + }, + "_seq_no" : 23, + "_primary_term" : 1 + } + + * 如果删除的数据不存在, 则"result" = "not_found" + * 如果需要删除所有的doc, 建议直接删除整个索引,这样更高效 + + +-------------------- +document - API删除 | +-------------------- + # 使用 _delete_by_query 对匹配到的文档进行删除 + POST //_delete_by_query + {"query":{...}} + + { + "took" : 0, + * 从整个操作的开始到结束的毫秒数。 + + "timed_out" : false, + * 在通过查询执行删除期间执行的请求是否有超时 + + "total" : 0, + * 匹配到的文档数量 + + "deleted" : 0, + * 成功删除的文档数量 + + "batches" : 0, + "version_conflicts" : 0, + * 版本冲突数量 + + "noops" : 0, + * 对于删除来说, 它始终是0 + "retries" : { + "bulk" : 0, + * 批量操作的重试次数 + "search" : 0 + * 搜索操作的重试次数 + }, + "throttled_millis" : 0, + "requests_per_second" : -1.0, + * 在通过查询删除期间有效执行的每秒请求数 + + "throttled_until_millis" : 0, + * 该字段在_delete_by_query响应中应始终等于零 + "failures" : [ ] + * 匹配出的, 删除失败的结果 + } + + # _delete_by_query执行期间, 顺序执行多个搜索请求以便找到要删除的所有匹配文档 + * 每次找到一批文档时, 都会执行相应的批量请求以删除所有这些文档 + * 如果搜索或批量请求被拒绝, 则_delete_by_query 依赖于默认策略来重试被拒绝的请求(最多10次), 达到最大重试次数限制会导致_delete_by_query 中止 + * 并failures在响应中返回所有失败, 已执行的删除仍然有效 + + * 也就说删除不是原子性的, 过程不会回滚, 只会中止 + * 当第一个失败导致中止时, 失败的批量请求返回的所有失败都将在 failures 元数据中 + + + + # _delete_by_query 使用internal版本控制删除它找到的内容 + * 这意味着如果文档在拍摄快照的时间和处理删除请求之间发生更改, 则会出现版本冲突 + * 当版本匹配时, 文档才会被删除 + + * 计算版本冲突, 不停止删除操作, 可以使用检索参数 + conflicts=proceed + + # 默认情况下, _delete_by_query使用1000的滚动批次来进行删除,可以使用scroll_size 参数更改批量大小 + scroll_size=5000 + + # 删除通过查询API也支持很多的功能参数 + refresh + wait_for_completion + * 如果 wait_for_completion=false, 则Elasticsearch将执行一些预检检查 + * 启动请求, 然后返回task 可与Tasks API 一起使用以取消或获取任务状态的请求 + * Elasticsearch还将创建此任务的记录作为文档 .tasks/task/${taskId} + + wait_for_active_shards + timeout + scroll + * _delete_by_query 采用滚动搜索, 还可以指定scroll参数来控制多长时间保持"搜索上下文"活着(默认5分钟) + scroll=10m + + requests_per_second + + +-------------------- +使用Task API | +-------------------- + # Task API是新功能, 目前仍视为测试版功能 + + +-------------------- +切片 | +-------------------- + + diff --git "a/Elasticsearch/es-document-\345\271\266\345\217\221\346\233\264\346\226\260.java" "b/Elasticsearch/es-document-\345\271\266\345\217\221\346\233\264\346\226\260.java" new file mode 100644 index 00000000..31a11984 --- /dev/null +++ "b/Elasticsearch/es-document-\345\271\266\345\217\221\346\233\264\346\226\260.java" @@ -0,0 +1,84 @@ +---------------------------- + 乐观锁并发控制 | +---------------------------- + # 文档 + https://www.elastic.co/guide/en/elasticsearch/reference/current/optimistic-concurrency-control.html + + + # 检索的结果中会存在两个元数据 + { + ... + "_seq_no" : 28, + "_primary_term" : 1 + } + + + # CAS更新 + PUT //_doc/?if_seq_no=<_seq_no>&if_primary_term=<_primary_term> + + POST //_doc/?if_seq_no=<_seq_no>&if_primary_term=<_primary_term> + + POST //_update/?if_seq_no=<_seq_no>&if_primary_term=<_primary_term> + + + * 通过查询参数: if_seq_no 和 if_primary_term 来控制版本号,如果更新失败, 抛出异常 + { + "error": { + "root_cause": [ + { + "type": "version_conflict_engine_exception", + "reason": "[1]: version conflict, required seqNo [8], primary term [1]. current document has seqNo [9] and primary term [1]", + "index_uuid": "NhO0l2JpRW-MwwnQpjexcA", + "shard": "0", + "index": "goods" + } + ], + "type": "version_conflict_engine_exception", + "reason": "[1]: version conflict, required seqNo [8], primary term [1]. current document has seqNo [9] and primary term [1]", + "index_uuid": "NhO0l2JpRW-MwwnQpjexcA", + "shard": "0", + "index": "goods" + }, + "status": 409 + } + + * 如果是部分更新, 那么只有在更新成功(更新的内容有修改)的时候才会去判断版本号 + +---------------------------- +版本控制 | +---------------------------- + # 每个doc都有一个 version 字段 + { + "_version" : 20, + } + + * 默认从1开始, 并在每次更新时递增, 包括删除 + + # 执行修改时, 使用版本号 + PUT //_doc/?version=<_version>&version_type= + { + "message" : "elasticsearch now has versioning support, double cool!" + } + + * CAS更新,如果使用doc内部的版本号则, 使用 if_seq_no 和 if_primary_term + * CAS更新,如果使用外部的版本号, 则使用 version + + # version_type ,枚举值 + internal + * 仅在给定版本与存储文档的版本相同时才对文档编制索引 + * 已经不支持了: internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead + + external / external_gt + external_gte + + # 可以把版本号交给外部程序控制, external version + * es提供了一个feature,可以不使用内容部的_version版本号来进行并发控制 + * 可以基于自己维护的'version版本号'来进行并发控制 + * 使用场景 + 在mysql中也存在一份数据,应用系统本身就维护了一个版本号,此时使用乐观锁控制的时候,不想使用es的version,而是想使用应用系统中的version + + * version控制语法 + ?version=<_version>&version_type=external + + * 当 version_type=external 的时候,version参数必须要大于当前的_version才能更新成功 + * 在修改成功后,并且会把document的_version修改为version参数的值 \ No newline at end of file diff --git "a/Elasticsearch/es-document-\346\211\271\345\244\204\347\220\206.java" "b/Elasticsearch/es-document-\346\211\271\345\244\204\347\220\206.java" new file mode 100644 index 00000000..c3bf80a2 --- /dev/null +++ "b/Elasticsearch/es-document-\346\211\271\345\244\204\347\220\206.java" @@ -0,0 +1,75 @@ +---------------------------- +bulk批量增删改 | +---------------------------- + # _bulk 请求对json的语法,要求相当严格 + * 每个json串儿,不能换行,只能放在同一行 + * 两个json串儿之间要换一行 + * 最后一行数据必须以换行符结尾 + * Content-Type标头应设置为application/x-ndjson + + # create,update操作,需要俩json串 + * 第一个json指定操作,以及元数据 + * 第二个json指定提交的业务数据 + + # json格式 + {"create":{...元数据}} + {...业务数据} + + {"index":{...元数据}} + {...业务数据} + + {"delet":{...元数据}} + + {"update":{...元数据}} + {"doc":{...业务数据}} + + + * create 属于强制创建 + * index 属于普通的put操作,可以是替换文档,或者创建文档 + * update 属于部分操作,而且使用CAS算法保证数据一致性, 可以使用 retry_on_conflict 来控制乐观锁的重试次数 + * 创建操作如果不在元数据中定义id那么会自动生成id + * 一个批量操作可以同时提交N多个 create,delete,update 请求,只需要符合json格式即可 + * 批量操作中的其中一个操作失败,不会影响到其他的批量操作,但是会在返回结果中提示失败的日志 + + # 不同index的批量操作 + POST /_bulk + {"create":{"_index":"user","_id":3}} + {"id":3,"name":"Batch Name","age":24} + {"delete":{"_index":"user","_id":2}} + {"update":{"_index":"user","_id":1,"retry_on_conflict":3}} + {"doc":{"name":"Batch Uapdate Name"}} + + # 相同index,的批量操作 + POST //_bulk + {"create":{"_id":3}} + {"id":3,"name":"Batch Name","age":24} + {"delete":{"_id":2}} + {"update":{"_id":1,"retry_on_conflict":3}} + {"doc":{"name":"Batch Uapdate Name"}} + + * 元数据仅仅需要声明index, 只要 id 即可,因为index已经在url中定义 + + + + # bulk 性能优化 + * bulk request 会加载到内容,如果太大的话,性能反而会下降,因此需要反复尝试一个最佳的bulk size + * 一般从 1000 - 5000条开始,尝试逐渐增加 + * 如果从大小上看的话,最好是在5 - 15MB之间 + + # bulk奇特的JSON格式与底层性能优化关系 + 1,bulk中的每个操作,都可能要转发到不同的node的shard去执行 + 2,如果采用比较良好的json数组格式 + (1),将json数组解析为 JSONArray 对象 + (2),对每个请求中的document进行路由 + (3),为路由到同一个shard上的多个请求创建一个请求数组 + (4),将这个请求数组序列化 + (5),把序列化的数据发送到对应的节点 + + * 耗费更多的内存,更多的jvm gc开销 + + 3,特殊的json请求格式 + (1),按照换行符切割json + (2),对每两个一组的json,读取meta,进行document路由 + (3),直接把对应的json发送到node上去 + + * 最大的优势在于,不需要将json数组解析为一个JSONArray对象,形成一份大数据的拷贝,浪费内存空间 \ No newline at end of file diff --git "a/Elasticsearch/es-document-\346\211\271\351\207\217\346\237\245\350\257\242.java" "b/Elasticsearch/es-document-\346\211\271\351\207\217\346\237\245\350\257\242.java" new file mode 100644 index 00000000..c538071b --- /dev/null +++ "b/Elasticsearch/es-document-\346\211\271\351\207\217\346\237\245\350\257\242.java" @@ -0,0 +1,65 @@ +---------------------------- +mget 批量查询 | +---------------------------- + # 如果需要检索100数据,使用单次请求,那么就会进行100次的网络请求,消耗严重,如果使用批量查询,那么仅需要一次网络开销即可 + # mget 相当重要 + * 当一次性要检索多条数据的时候,一定要使用mget,减少网络开销可以大幅度提升性能 + + # mget 基本语法 + GET /_mget + { + "docs":[{ + "_index":"user", //通过 _index 指定index + "_id":1 //通过 _id 指定 id + },{ + "_index":"user", + "_id":2 + }] + } + + * doc 是个数组,可以定义多个条件 + * 响应数据中的docs也是一个数组,返回的是符合条件的数据 + { + "docs": [ + { + "_index": "user", + "_id": "1", + "_version": 3, + "found": true, + "_source": { + ... + } + }, + { + "_index": "user", + "_id": "2", + "_version": 1, + "found": true, + "_source": { + ... + } + } + ] + } + + + # 如果批量查询的document,是在同一个index,可以使用另外两种语法 + GET //_mget + { + "docs":[{ + "_id":1 + }] + } + * 仅仅需要在docs中指定_id,因为url已经指定了index + + GET //_mget + { + "ids":[1,2] + } + * 如果只是根据id检索, 可以这样写, json格式更简单,只要定义ids字段即可 + + # 也可以使用部分功能的检索参数 + _source + _source_includes + _source_excludes + routing diff --git "a/Elasticsearch/es-document-\346\233\264\346\226\260.java" "b/Elasticsearch/es-document-\346\233\264\346\226\260.java" new file mode 100644 index 00000000..216e5d84 --- /dev/null +++ "b/Elasticsearch/es-document-\346\233\264\346\226\260.java" @@ -0,0 +1,125 @@ +-------------------- +document - 更新 | +-------------------- + # 更新时, Elasticsearch都会删除旧文档, 然后一次性对应用了更新的新文档编制索引 + +-------------------- +document - 更新 | +-------------------- + # 全量替换更新(PUT) + PUT //_doc/ + {...} + + { + "_index" : "customer", + "_type" : "_doc", + "_id" : "1", + "_version" : 2, + "result" : "updated", + "_shards" : { + "total" : 2, + "successful" : 1, + "failed" : 0 + }, + "_seq_no" : 1, + "_primary_term" : 1 + } + + * 相当于执行了一次覆盖 + * 如果id已经存在,那么原来的document不会被立即删除,而是会被标记为: delete + * 当es中数据越来越多的时候,es会在后台自己动的把标记为:delete 的document物理删除掉 + * _version 始终会 +1 + + # 强制更新(全部更新) + POST //_doc/ + {...} + + * 请求体需要提交所有字段,不存在的字段会被删除 + * 不管本次提交,是否有成功修改字段,result值永远为:'updated' + * 不管是有修改,_version字段必会加1 + * 可以理解为强制更新 + * 如果指定id的数据不存在(或者未指定id), 则会创建, 则 "result" = "created" + + # 非强制更新(部分更新) + POST //_update/ + {"doc":{...}} + + { + "_index" : "customer", + "_type" : "_doc", + "_id" : "1", + "_version" : 5, + "result" : "noop", + "_shards" : { + "total" : 0, + "successful" : 0, + "failed" : 0 + } + } + + + * 该种方式,提交的JSON体有所变化 + { + "doc":{ + //需要修改的字段 + } + } + + * 可以仅仅提交更新需要更新的字段 + * 如果本次提交未修改数据的话,那么result字段值为:'noop',并且没有:'_seq_no'和'_primary_term'字段, + - noop更新 + + * 只有在数据有修改的时候,version +1 + * 可以理解为非强制更新 + * partial update(部分更新) + + # 也支持使用脚本语言更新 + POST //_update/ + { + "script" : "ctx._source.age += 5" + } + + * 具体看脚本 + + +-------------------- +使用查询API更新 | +-------------------- + # 使用 _update_by_query 对匹配到的文档进行更新 + POST //_update_by_query + { + "query": { + "term": { + "": "" + } + } + } + * 把index中的所有field的值都修改为value + + # 此更新通过查询API也支持不分功能查询参数 + refresh + wait_for_completion + wait_for_active_shards + timeout + scroll + + # 更新操作, 支持的查询功能参数 + retry_on_conflict + * 在更新的get和indexing阶段之间, 另一个进程可能已经更新了同一文档 + * 默认情况下, 更新将因版本冲突异常而失败, 该retry_on_conflict 参数控制在最终抛出异常之前重试更新的次数 + + routing + timeout + wait_for_active_shards + refresh + _source + * 是否以及如何在响应中返回更新的数据 + * 默认情况下, 不会返回更新的数据 + + version + * 更新API不支持内部版本以外的版本控制 + +-------------------- +使用Task API | +-------------------- + # Task API是新功能, 目前仍视为测试版功能 diff --git "a/Elasticsearch/es-document-\346\243\200\347\264\242.java" "b/Elasticsearch/es-document-\346\243\200\347\264\242.java" new file mode 100644 index 00000000..9b4f855f --- /dev/null +++ "b/Elasticsearch/es-document-\346\243\200\347\264\242.java" @@ -0,0 +1,137 @@ +------------------- +基本的根据条件检索 | +------------------- + # 可以一次性检索多个index + * 直接检索多个index + GET /,/_search + + * 一次性检索所有的index + GET /_all/_search + + * 通过 * 来匹配多个index + GET /*,*/_search + +------------------- +基本的根据条件检索 | +------------------- + # 请求 + GET //_search?q= + + { + "took" : 2, + * 执行搜索的时间(以毫秒为单位) + "timed_out" : false, + * 搜索是否超时 + "_shards" : { + "total" : 1, + * 搜索了多少个分片 + "successful" : 1, + * 搜索成功的分片数量 + "skipped" : 0, + * 跳过的分片数量 + "failed" : 0 + * 搜索失败的分片数量 + }, + "hits" : { + "total" : { + "value" : 13, + "relation" : "eq" + }, + "max_score" : 1.0, + "hits" : [ + { + "_index" : "customer", + "_type" : "_doc", + "_id" : "TsVMQGsBor31qRgUxQmS", + "_score" : 1.0, + "_source" : { + "name" : "KevinBlandy" + } + } + ] + } + } + + +------------------- +query参数 | +------------------- + # 过滤参数:q + q=* + * 检索所有, q=* + + q= + * 任何字段, 只要包含该值就满足条件 + * 搜索的是_all field + + q=: + * 全文检索, 只要是指定字段中有关键字的都OK, :q=name:KevinBlandy + * 有多个匹配value值, 使用逗号分隔 + + q=:<+/-> + * + 表示必须包含, - 表示必须不包含: q=-author.name:Litch + + + # 排序擦数:sort + sort=: + + * 如果有多个, 使用逗号分隔:sort=age:asc,_id:desc + + # 分页参数:size & from + * size,每页显示的记录数量, 默认10 + * from,从第几条数据开始检索,默认0(表示第一条) + + * deep paging问题 + * deep paging,简单来说,就是搜索得特别深,比如1000000条数据,每页显示10条,此时需要检索最后一页的数据 + * 符合请求的数据,可能存在于多个primary shard,replica shard,于是就要把所有数据汇总到 coordinating node(协调节点) + * 由协调节点进行排序,取出最符合条件的数据,按照分页返回 + * 这个过程耗费带宽,内存,网络计算,这个就是deep paging问题,我们的开发尽量要避免这种情 + + + # _source 数据过滤参数: + _source + * 检索数据是否要携带 _source 数据, 值可以是 true/false + * 也可以通过该参数来指定要检索的字段 + GET /goods/_doc/1?_source=author.name,author.age + + _source_includes + _source_excludes + * 过滤/包含指定的 _source 数据 + + * 支持有多个值, 使用否号分割 + * 支持通配符:* + GET /goods/_doc/1?_source=*.name + + # stored_fields + //TODO + + # timeout 超时参数 + * 默认无超时 + + # df + # analyzer + # analyze_wildcard + # batched_reduce_size + # default_operator + # lenient + # explain + # track_scores + # track_total_hits + # terminate_after + # search_type + # allow_partial_search_results + # scroll + * 滚动搜索(有点像迭代器的感觉) + * 每次仅仅响应一批数据, 直到响应完毕所有 + * 第一次检索的时候, 会生成快照, 在响应完毕之前, doc的修改不可见(可重复读的感觉) + + + # filter_path + * 可以过滤整个JSON结果的字段, 包括元数据信息 + * 支持对象导航 + GET //_search?filter_path=hits.hits // 仅仅显示hits信息 + + + # error_trace + * true/false, 是否在异常的时候, 响应堆栈信息 + diff --git "a/Elasticsearch/es-document-\351\207\215\346\226\260\347\264\242\345\274\225.java" "b/Elasticsearch/es-document-\351\207\215\346\226\260\347\264\242\345\274\225.java" new file mode 100644 index 00000000..3ac7112d --- /dev/null +++ "b/Elasticsearch/es-document-\351\207\215\346\226\260\347\264\242\345\274\225.java" @@ -0,0 +1,5 @@ +-------------------------------- +重新索引 | +-------------------------------- + # 文档 + https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html diff --git a/Elasticsearch/es-document.java b/Elasticsearch/es-document.java new file mode 100644 index 00000000..798255ee --- /dev/null +++ b/Elasticsearch/es-document.java @@ -0,0 +1,242 @@ +-------------------- +document | +-------------------- + + +-------------------- +document - 创建 | +-------------------- + # 基本的创建 + PUT //_doc/?pretty + {...} + + { + "_index" : "customer", + "_type" : "_doc", + "_id" : "1", + "_version" : 4, + "result" : "updated", + "_shards" : { + "total" : 2, + "successful" : 1, + "failed" : 0 + }, + "_seq_no" : 3, + "_primary_term" : 1 + } + + _shards.total + * 执行索引操作的分片副本(主分片和副本分片)的数量 + + _shards.successful + * 操作成功的副本数量 + + _shards.failed + * 操作失败的副本数量 + + + + * 如果Index不存在, 会自动的创建 + * PUT方式, 必须手动的指定id属性 + + # 使用POST创建 + POST //_doc + {...} + + * POST方式, 如果不指定id的话, 系统自动为doc生成一个uuid(VMVNQGsBor31qRgUZwnr) + + # 强制创建 + * 如果只想新建文档,不想替换文档,那么就需要强制创建(两种方式) + + PUT //_doc/?op_type=create + + PUT //_doc//_create + * 这种方式比较常见 + + * 如果该id的document已经存在,那么不会PUT成功,会抛出异常 + + +-------------------- +document - 检索 | +-------------------- + # 基本的根据id检索 + GET //_doc/ + + { + "_index" : "customer", + "_type" : "_doc", + "_id" : "1", + "_version" : 4, + "_seq_no" : 3, + "_primary_term" : 1, + "found" : true, + "_source" : { + ... + } + } + + # 根据id判断是否存在, 使用 HEAD 请求 + HEAD //_doc/ + + 存在:200 - OK + 不在:404 - Not Found + + +------------------------------------ +document 路由 | +------------------------------------ + # document 路由到 shard + * index 数据会被分片到多少shard找那个,所以一个document只会存在一个shard + * 计算出 document 应该在存在哪个shard,其实就是路由 + + # 路由算法 + * shard = hash(routing) % number_of_primary_shards + > routing 默认为 document的 id(可以手动指定) + > hash算法 + > number_of_primary_shards : primary shad的数量 + + * 手动指定 routing + PUT //?routing=15 + GET //?routing=15 + + > 通过参数手动指定routing value很有用,可以保证,某一类document一定被路由到一个shard上 + > 那么在后续进行应用级别的负载均衡,以及提升批量读取性能的时候,是很有帮助的 + + # primary shard 数量不可变 + * 一旦发生变化了,那么在取模算法时,就会有问题,会间接导致数据丢失 + * 但是 replica shard可以随意增删 + +--------------------------- +活动分片 | +--------------------------- + # 文档 + https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html + + # 执行增删改的时候, 可以通过分片的活跃度来保证写一致性 + # ElasticSearch 5.0之后使用 wait_for_active_shards 代替了consistency 来指定写一致性级别 + POST //_doc/?wait_for_active_shards=1 + + # 有效值是all或任何正整数 + all + * 所有的primary shard和replica shard都是活跃的,才可以执行 + + 任何正整数 + * TODO + + # 如果活跃节点数量少于指定的数量,可能会导致无法执行写操作 + * 跃节点数量少于指定的数量时,默认 wait 1分钟(期望活跃的shard可以增加) + * 可以通过 timeout 参数来控制(单位默认是毫秒,可以通过添加s来设置为秒) + PUT //_doc/?timeout=60s + +--------------------------- +立即刷新 | +--------------------------- + # 写操作只能由主分片执行, 主分片写入后, 会同步到副本分配 + * 可能导致的问题就是, 主分片还未同步到副本分片, 就已经响应给客户端成功信息 + * 客户端再次检索到副本分片, 由于还未同步完成, 所以检索不到数据 + + # 可以通过参数来控制, 是否要等到副本同步完成, 才返回给客户端成功 + + # 通过query参数控制:refresh + true (空字符串也可以) + * 强制执行刷新 + * 不会阻塞客户端, 而是在客户端的操作响应后立即刷新相关的主分片和副本分片 + + wait_for + * 等待刷新的发生 + * 会阻塞客户端, 在响应之前, 等待刷新请求所做的更改, 这不会强制立即刷新, 而是等待刷新发生 + * ES 会每 index.refresh_interval(默认值为1秒)自动刷新已经更改的碎片, 这个设置是动态的 + * 调用Refresh API或在任何支持它的API上将refresh设置为true也会导致刷新, 从而导致已经运行的带有refresh=wait_for的请求响应 + + + false (默认) + * 不等待刷新, 立即返回 + + PUT //_doc/?refresh=false + + # refresh=wait_for 也可以强制刷新 + * 如果存在配置 index.max_refresh_listener(默认数量为1000) + * 请求等待刷新的情况下, 那个碎片上出现refresh=wait_for请求, 那么该请求的行为就好像它已经将refresh设置为true一样 + * 它将强制刷新, 这保证了当refresh=wait_for请求返回时, 它的更改对于搜索是可见的, 同时防止对被阻塞的请求使用未检查的资源 + + * 如果一个请求因为耗尽了监听器插槽而强制刷新, 那么它的响应将包含"forced_refresh": true + +-------------------- +_all metadata | +-------------------- + # 新建一个doc, ES把会doc的所有字段值, 都拆开, 组成一个长的字符串 + * 该字符串就是doc的 _all field 的值 + + # 对该字符串建立索引, 如果搜索的时候, 没有指定搜索的field, 那么默认就是搜索 _all field + + +-------------------- +数据结构的改变 | +-------------------- + # 一个json的doc, 会发生一下数据结构的改变 + * 会变成列式存储, 都会被转换为一列 + + # 对象的改变 + { + "name":"Java入门到入土", + "author":{ + "name":"KevinBlandy", + "age":23 + } + } + + { + "name":"Java入门到入土", + "author.name":"KevinBlandy", + "author.age":"KevinBlandy" + } + + # 数组的改变 + { + "name":"KevinBlandy", + "skill":[{ + "name":"Java", + "proficiency": 90 + },{ + "name":"Python", + "proficiency": 80 + }] + } + + { + "name":"KevinBlandy", + "skill.name":["Java", "Python"], + "skill.proficiency":[90, 80] + } + + +-------------------------------------------- +对field索引两次来解决字符串排序的问题 | +-------------------------------------------- + # 对 String field 进行排序, 往往结果不准确, 因为分词后是是多个单词, 再次进行排序的时候, 结果不是我们想要的 + # 通常的解决方案是, 把一个 String field 建立两次索引, 一个分词, 用于搜索, 一个不分词, 用于排序 + + { + "title":{ + "type":"string", + "analyzer":"english", + "fields": { + "raw": { + "type": "string", + "index": "not_analyzed", + "fielddata": true + } + } + } + } + + GET /_search + { + "query":{ + "match":{ + "title":"Hello" + } + }, + "sort": "title.raw" + } + diff --git a/Elasticsearch/es-endpoint.java b/Elasticsearch/es-endpoint.java new file mode 100644 index 00000000..8afc81d3 --- /dev/null +++ b/Elasticsearch/es-endpoint.java @@ -0,0 +1,11 @@ +------------------------ +_cluster | +------------------------ +GET /_cluster/health + +------------------------ +_cat | +------------------------ +GET /_cat/health? +GET /_cat/nodes?v +GET /_cat/indices?v diff --git a/Elasticsearch/es-foo.java b/Elasticsearch/es-foo.java new file mode 100644 index 00000000..4a442d61 --- /dev/null +++ b/Elasticsearch/es-foo.java @@ -0,0 +1,2003 @@ + + + +{"index":{"_id":"1"}} +{"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"} +{"index":{"_id":"6"}} +{"account_number":6,"balance":5686,"firstname":"Hattie","lastname":"Bond","age":36,"gender":"M","address":"671 Bristol Street","employer":"Netagy","email":"hattiebond@netagy.com","city":"Dante","state":"TN"} +{"index":{"_id":"13"}} +{"account_number":13,"balance":32838,"firstname":"Nanette","lastname":"Bates","age":28,"gender":"F","address":"789 Madison Street","employer":"Quility","email":"nanettebates@quility.com","city":"Nogal","state":"VA"} +{"index":{"_id":"18"}} +{"account_number":18,"balance":4180,"firstname":"Dale","lastname":"Adams","age":33,"gender":"M","address":"467 Hutchinson Court","employer":"Boink","email":"daleadams@boink.com","city":"Orick","state":"MD"} +{"index":{"_id":"20"}} +{"account_number":20,"balance":16418,"firstname":"Elinor","lastname":"Ratliff","age":36,"gender":"M","address":"282 Kings Place","employer":"Scentric","email":"elinorratliff@scentric.com","city":"Ribera","state":"WA"} +{"index":{"_id":"25"}} +{"account_number":25,"balance":40540,"firstname":"Virginia","lastname":"Ayala","age":39,"gender":"F","address":"171 Putnam Avenue","employer":"Filodyne","email":"virginiaayala@filodyne.com","city":"Nicholson","state":"PA"} +{"index":{"_id":"32"}} +{"account_number":32,"balance":48086,"firstname":"Dillard","lastname":"Mcpherson","age":34,"gender":"F","address":"702 Quentin Street","employer":"Quailcom","email":"dillardmcpherson@quailcom.com","city":"Veguita","state":"IN"} +{"index":{"_id":"37"}} +{"account_number":37,"balance":18612,"firstname":"Mcgee","lastname":"Mooney","age":39,"gender":"M","address":"826 Fillmore Place","employer":"Reversus","email":"mcgeemooney@reversus.com","city":"Tooleville","state":"OK"} +{"index":{"_id":"44"}} +{"account_number":44,"balance":34487,"firstname":"Aurelia","lastname":"Harding","age":37,"gender":"M","address":"502 Baycliff Terrace","employer":"Orbalix","email":"aureliaharding@orbalix.com","city":"Yardville","state":"DE"} +{"index":{"_id":"49"}} +{"account_number":49,"balance":29104,"firstname":"Fulton","lastname":"Holt","age":23,"gender":"F","address":"451 Humboldt Street","employer":"Anocha","email":"fultonholt@anocha.com","city":"Sunriver","state":"RI"} +{"index":{"_id":"51"}} +{"account_number":51,"balance":14097,"firstname":"Burton","lastname":"Meyers","age":31,"gender":"F","address":"334 River Street","employer":"Bezal","email":"burtonmeyers@bezal.com","city":"Jacksonburg","state":"MO"} +{"index":{"_id":"56"}} +{"account_number":56,"balance":14992,"firstname":"Josie","lastname":"Nelson","age":32,"gender":"M","address":"857 Tabor Court","employer":"Emtrac","email":"josienelson@emtrac.com","city":"Sunnyside","state":"UT"} +{"index":{"_id":"63"}} +{"account_number":63,"balance":6077,"firstname":"Hughes","lastname":"Owens","age":30,"gender":"F","address":"510 Sedgwick Street","employer":"Valpreal","email":"hughesowens@valpreal.com","city":"Guilford","state":"KS"} +{"index":{"_id":"68"}} +{"account_number":68,"balance":44214,"firstname":"Hall","lastname":"Key","age":25,"gender":"F","address":"927 Bay Parkway","employer":"Eventex","email":"hallkey@eventex.com","city":"Shawmut","state":"CA"} +{"index":{"_id":"70"}} +{"account_number":70,"balance":38172,"firstname":"Deidre","lastname":"Thompson","age":33,"gender":"F","address":"685 School Lane","employer":"Netplode","email":"deidrethompson@netplode.com","city":"Chestnut","state":"GA"} +{"index":{"_id":"75"}} +{"account_number":75,"balance":40500,"firstname":"Sandoval","lastname":"Kramer","age":22,"gender":"F","address":"166 Irvington Place","employer":"Overfork","email":"sandovalkramer@overfork.com","city":"Limestone","state":"NH"} +{"index":{"_id":"82"}} +{"account_number":82,"balance":41412,"firstname":"Concetta","lastname":"Barnes","age":39,"gender":"F","address":"195 Bayview Place","employer":"Fitcore","email":"concettabarnes@fitcore.com","city":"Summerfield","state":"NC"} +{"index":{"_id":"87"}} +{"account_number":87,"balance":1133,"firstname":"Hewitt","lastname":"Kidd","age":22,"gender":"M","address":"446 Halleck Street","employer":"Isologics","email":"hewittkidd@isologics.com","city":"Coalmont","state":"ME"} +{"index":{"_id":"94"}} +{"account_number":94,"balance":41060,"firstname":"Brittany","lastname":"Cabrera","age":30,"gender":"F","address":"183 Kathleen Court","employer":"Mixers","email":"brittanycabrera@mixers.com","city":"Cornucopia","state":"AZ"} +{"index":{"_id":"99"}} +{"account_number":99,"balance":47159,"firstname":"Ratliff","lastname":"Heath","age":39,"gender":"F","address":"806 Rockwell Place","employer":"Zappix","email":"ratliffheath@zappix.com","city":"Shaft","state":"ND"} +{"index":{"_id":"102"}} +{"account_number":102,"balance":29712,"firstname":"Dena","lastname":"Olson","age":27,"gender":"F","address":"759 Newkirk Avenue","employer":"Hinway","email":"denaolson@hinway.com","city":"Choctaw","state":"NJ"} +{"index":{"_id":"107"}} +{"account_number":107,"balance":48844,"firstname":"Randi","lastname":"Rich","age":28,"gender":"M","address":"694 Jefferson Street","employer":"Netplax","email":"randirich@netplax.com","city":"Bellfountain","state":"SC"} +{"index":{"_id":"114"}} +{"account_number":114,"balance":43045,"firstname":"Josephine","lastname":"Joseph","age":31,"gender":"F","address":"451 Oriental Court","employer":"Turnabout","email":"josephinejoseph@turnabout.com","city":"Sedley","state":"AL"} +{"index":{"_id":"119"}} +{"account_number":119,"balance":49222,"firstname":"Laverne","lastname":"Johnson","age":28,"gender":"F","address":"302 Howard Place","employer":"Senmei","email":"lavernejohnson@senmei.com","city":"Herlong","state":"DC"} +{"index":{"_id":"121"}} +{"account_number":121,"balance":19594,"firstname":"Acevedo","lastname":"Dorsey","age":32,"gender":"M","address":"479 Nova Court","employer":"Netropic","email":"acevedodorsey@netropic.com","city":"Islandia","state":"CT"} +{"index":{"_id":"126"}} +{"account_number":126,"balance":3607,"firstname":"Effie","lastname":"Gates","age":39,"gender":"F","address":"620 National Drive","employer":"Digitalus","email":"effiegates@digitalus.com","city":"Blodgett","state":"MD"} +{"index":{"_id":"133"}} +{"account_number":133,"balance":26135,"firstname":"Deena","lastname":"Richmond","age":36,"gender":"F","address":"646 Underhill Avenue","employer":"Sunclipse","email":"deenarichmond@sunclipse.com","city":"Austinburg","state":"SC"} +{"index":{"_id":"138"}} +{"account_number":138,"balance":9006,"firstname":"Daniel","lastname":"Arnold","age":39,"gender":"F","address":"422 Malbone Street","employer":"Ecstasia","email":"danielarnold@ecstasia.com","city":"Gardiner","state":"MO"} +{"index":{"_id":"140"}} +{"account_number":140,"balance":26696,"firstname":"Cotton","lastname":"Christensen","age":32,"gender":"M","address":"878 Schermerhorn Street","employer":"Prowaste","email":"cottonchristensen@prowaste.com","city":"Mayfair","state":"LA"} +{"index":{"_id":"145"}} +{"account_number":145,"balance":47406,"firstname":"Rowena","lastname":"Wilkinson","age":32,"gender":"M","address":"891 Elton Street","employer":"Asimiline","email":"rowenawilkinson@asimiline.com","city":"Ripley","state":"NH"} +{"index":{"_id":"152"}} +{"account_number":152,"balance":8088,"firstname":"Wolfe","lastname":"Rocha","age":21,"gender":"M","address":"457 Guernsey Street","employer":"Hivedom","email":"wolferocha@hivedom.com","city":"Adelino","state":"MS"} +{"index":{"_id":"157"}} +{"account_number":157,"balance":39868,"firstname":"Claudia","lastname":"Terry","age":20,"gender":"F","address":"132 Gunnison Court","employer":"Lumbrex","email":"claudiaterry@lumbrex.com","city":"Castleton","state":"MD"} +{"index":{"_id":"164"}} +{"account_number":164,"balance":9101,"firstname":"Cummings","lastname":"Little","age":26,"gender":"F","address":"308 Schaefer Street","employer":"Comtrak","email":"cummingslittle@comtrak.com","city":"Chaparrito","state":"WI"} +{"index":{"_id":"169"}} +{"account_number":169,"balance":45953,"firstname":"Hollie","lastname":"Osborn","age":34,"gender":"M","address":"671 Seaview Court","employer":"Musaphics","email":"hollieosborn@musaphics.com","city":"Hanover","state":"GA"} +{"index":{"_id":"171"}} +{"account_number":171,"balance":7091,"firstname":"Nelda","lastname":"Hopper","age":39,"gender":"M","address":"742 Prospect Place","employer":"Equicom","email":"neldahopper@equicom.com","city":"Finderne","state":"SC"} +{"index":{"_id":"176"}} +{"account_number":176,"balance":18607,"firstname":"Kemp","lastname":"Walters","age":28,"gender":"F","address":"906 Howard Avenue","employer":"Eyewax","email":"kempwalters@eyewax.com","city":"Why","state":"KY"} +{"index":{"_id":"183"}} +{"account_number":183,"balance":14223,"firstname":"Hudson","lastname":"English","age":26,"gender":"F","address":"823 Herkimer Place","employer":"Xinware","email":"hudsonenglish@xinware.com","city":"Robbins","state":"ND"} +{"index":{"_id":"188"}} +{"account_number":188,"balance":41504,"firstname":"Tia","lastname":"Miranda","age":24,"gender":"F","address":"583 Ainslie Street","employer":"Jasper","email":"tiamiranda@jasper.com","city":"Summerset","state":"UT"} +{"index":{"_id":"190"}} +{"account_number":190,"balance":3150,"firstname":"Blake","lastname":"Davidson","age":30,"gender":"F","address":"636 Diamond Street","employer":"Quantasis","email":"blakedavidson@quantasis.com","city":"Crumpler","state":"KY"} +{"index":{"_id":"195"}} +{"account_number":195,"balance":5025,"firstname":"Kaye","lastname":"Gibson","age":31,"gender":"M","address":"955 Hopkins Street","employer":"Zork","email":"kayegibson@zork.com","city":"Ola","state":"WY"} +{"index":{"_id":"203"}} +{"account_number":203,"balance":21890,"firstname":"Eve","lastname":"Wyatt","age":33,"gender":"M","address":"435 Furman Street","employer":"Assitia","email":"evewyatt@assitia.com","city":"Jamestown","state":"MN"} +{"index":{"_id":"208"}} +{"account_number":208,"balance":40760,"firstname":"Garcia","lastname":"Hess","age":26,"gender":"F","address":"810 Nostrand Avenue","employer":"Quiltigen","email":"garciahess@quiltigen.com","city":"Brooktrails","state":"GA"} +{"index":{"_id":"210"}} +{"account_number":210,"balance":33946,"firstname":"Cherry","lastname":"Carey","age":24,"gender":"M","address":"539 Tiffany Place","employer":"Martgo","email":"cherrycarey@martgo.com","city":"Fairacres","state":"AK"} +{"index":{"_id":"215"}} +{"account_number":215,"balance":37427,"firstname":"Copeland","lastname":"Solomon","age":20,"gender":"M","address":"741 McDonald Avenue","employer":"Recognia","email":"copelandsolomon@recognia.com","city":"Edmund","state":"ME"} +{"index":{"_id":"222"}} +{"account_number":222,"balance":14764,"firstname":"Rachelle","lastname":"Rice","age":36,"gender":"M","address":"333 Narrows Avenue","employer":"Enaut","email":"rachellerice@enaut.com","city":"Wright","state":"AZ"} +{"index":{"_id":"227"}} +{"account_number":227,"balance":19780,"firstname":"Coleman","lastname":"Berg","age":22,"gender":"M","address":"776 Little Street","employer":"Exoteric","email":"colemanberg@exoteric.com","city":"Eagleville","state":"WV"} +{"index":{"_id":"234"}} +{"account_number":234,"balance":44207,"firstname":"Betty","lastname":"Hall","age":37,"gender":"F","address":"709 Garfield Place","employer":"Miraclis","email":"bettyhall@miraclis.com","city":"Bendon","state":"NY"} +{"index":{"_id":"239"}} +{"account_number":239,"balance":25719,"firstname":"Chang","lastname":"Boyer","age":36,"gender":"M","address":"895 Brigham Street","employer":"Qaboos","email":"changboyer@qaboos.com","city":"Belgreen","state":"NH"} +{"index":{"_id":"241"}} +{"account_number":241,"balance":25379,"firstname":"Schroeder","lastname":"Harrington","age":26,"gender":"M","address":"610 Tapscott Avenue","employer":"Otherway","email":"schroederharrington@otherway.com","city":"Ebro","state":"TX"} +{"index":{"_id":"246"}} +{"account_number":246,"balance":28405,"firstname":"Katheryn","lastname":"Foster","age":21,"gender":"F","address":"259 Kane Street","employer":"Quantalia","email":"katherynfoster@quantalia.com","city":"Bath","state":"TX"} +{"index":{"_id":"253"}} +{"account_number":253,"balance":20240,"firstname":"Melissa","lastname":"Gould","age":31,"gender":"M","address":"440 Fuller Place","employer":"Buzzopia","email":"melissagould@buzzopia.com","city":"Lumberton","state":"MD"} +{"index":{"_id":"258"}} +{"account_number":258,"balance":5712,"firstname":"Lindsey","lastname":"Hawkins","age":37,"gender":"M","address":"706 Frost Street","employer":"Enormo","email":"lindseyhawkins@enormo.com","city":"Gardners","state":"AK"} +{"index":{"_id":"260"}} +{"account_number":260,"balance":2726,"firstname":"Kari","lastname":"Skinner","age":30,"gender":"F","address":"735 Losee Terrace","employer":"Singavera","email":"kariskinner@singavera.com","city":"Rushford","state":"WV"} +{"index":{"_id":"265"}} +{"account_number":265,"balance":46910,"firstname":"Marion","lastname":"Schneider","age":26,"gender":"F","address":"574 Everett Avenue","employer":"Evidends","email":"marionschneider@evidends.com","city":"Maplewood","state":"WY"} +{"index":{"_id":"272"}} +{"account_number":272,"balance":19253,"firstname":"Lilly","lastname":"Morgan","age":25,"gender":"F","address":"689 Fleet Street","employer":"Biolive","email":"lillymorgan@biolive.com","city":"Sunbury","state":"OH"} +{"index":{"_id":"277"}} +{"account_number":277,"balance":29564,"firstname":"Romero","lastname":"Lott","age":31,"gender":"M","address":"456 Danforth Street","employer":"Plasto","email":"romerolott@plasto.com","city":"Vincent","state":"VT"} +{"index":{"_id":"284"}} +{"account_number":284,"balance":22806,"firstname":"Randolph","lastname":"Banks","age":29,"gender":"M","address":"875 Hamilton Avenue","employer":"Caxt","email":"randolphbanks@caxt.com","city":"Crawfordsville","state":"WA"} +{"index":{"_id":"289"}} +{"account_number":289,"balance":7798,"firstname":"Blair","lastname":"Church","age":29,"gender":"M","address":"370 Sutton Street","employer":"Cubix","email":"blairchurch@cubix.com","city":"Nile","state":"NH"} +{"index":{"_id":"291"}} +{"account_number":291,"balance":19955,"firstname":"Lynn","lastname":"Pollard","age":40,"gender":"F","address":"685 Pierrepont Street","employer":"Slambda","email":"lynnpollard@slambda.com","city":"Mappsville","state":"ID"} +{"index":{"_id":"296"}} +{"account_number":296,"balance":24606,"firstname":"Rosa","lastname":"Oliver","age":34,"gender":"M","address":"168 Woodbine Street","employer":"Idetica","email":"rosaoliver@idetica.com","city":"Robinson","state":"WY"} +{"index":{"_id":"304"}} +{"account_number":304,"balance":28647,"firstname":"Palmer","lastname":"Clark","age":35,"gender":"M","address":"866 Boulevard Court","employer":"Maximind","email":"palmerclark@maximind.com","city":"Avalon","state":"NH"} +{"index":{"_id":"309"}} +{"account_number":309,"balance":3830,"firstname":"Rosemarie","lastname":"Nieves","age":30,"gender":"M","address":"206 Alice Court","employer":"Zounds","email":"rosemarienieves@zounds.com","city":"Ferney","state":"AR"} +{"index":{"_id":"311"}} +{"account_number":311,"balance":13388,"firstname":"Vinson","lastname":"Ballard","age":23,"gender":"F","address":"960 Glendale Court","employer":"Gynk","email":"vinsonballard@gynk.com","city":"Fairforest","state":"WY"} +{"index":{"_id":"316"}} +{"account_number":316,"balance":8214,"firstname":"Anita","lastname":"Ewing","age":32,"gender":"M","address":"396 Lombardy Street","employer":"Panzent","email":"anitaewing@panzent.com","city":"Neahkahnie","state":"WY"} +{"index":{"_id":"323"}} +{"account_number":323,"balance":42230,"firstname":"Chelsea","lastname":"Gamble","age":34,"gender":"F","address":"356 Dare Court","employer":"Isosphere","email":"chelseagamble@isosphere.com","city":"Dundee","state":"MD"} +{"index":{"_id":"328"}} +{"account_number":328,"balance":12523,"firstname":"Good","lastname":"Campbell","age":27,"gender":"F","address":"438 Hicks Street","employer":"Gracker","email":"goodcampbell@gracker.com","city":"Marion","state":"CA"} +{"index":{"_id":"330"}} +{"account_number":330,"balance":41620,"firstname":"Yvette","lastname":"Browning","age":34,"gender":"F","address":"431 Beekman Place","employer":"Marketoid","email":"yvettebrowning@marketoid.com","city":"Talpa","state":"CO"} +{"index":{"_id":"335"}} +{"account_number":335,"balance":35433,"firstname":"Vera","lastname":"Hansen","age":24,"gender":"M","address":"252 Bushwick Avenue","employer":"Zanilla","email":"verahansen@zanilla.com","city":"Manila","state":"TN"} +{"index":{"_id":"342"}} +{"account_number":342,"balance":33670,"firstname":"Vivian","lastname":"Wells","age":36,"gender":"M","address":"570 Cobek Court","employer":"Nutralab","email":"vivianwells@nutralab.com","city":"Fontanelle","state":"OK"} +{"index":{"_id":"347"}} +{"account_number":347,"balance":36038,"firstname":"Gould","lastname":"Carson","age":24,"gender":"F","address":"784 Pulaski Street","employer":"Mobildata","email":"gouldcarson@mobildata.com","city":"Goochland","state":"MI"} +{"index":{"_id":"354"}} +{"account_number":354,"balance":21294,"firstname":"Kidd","lastname":"Mclean","age":22,"gender":"M","address":"691 Saratoga Avenue","employer":"Ronbert","email":"kiddmclean@ronbert.com","city":"Tioga","state":"ME"} +{"index":{"_id":"359"}} +{"account_number":359,"balance":29927,"firstname":"Vanessa","lastname":"Harvey","age":28,"gender":"F","address":"679 Rutledge Street","employer":"Zentime","email":"vanessaharvey@zentime.com","city":"Williston","state":"IL"} +{"index":{"_id":"361"}} +{"account_number":361,"balance":23659,"firstname":"Noreen","lastname":"Shelton","age":36,"gender":"M","address":"702 Tillary Street","employer":"Medmex","email":"noreenshelton@medmex.com","city":"Derwood","state":"NH"} +{"index":{"_id":"366"}} +{"account_number":366,"balance":42368,"firstname":"Lydia","lastname":"Cooke","age":31,"gender":"M","address":"470 Coleman Street","employer":"Comstar","email":"lydiacooke@comstar.com","city":"Datil","state":"TN"} +{"index":{"_id":"373"}} +{"account_number":373,"balance":9671,"firstname":"Simpson","lastname":"Carpenter","age":21,"gender":"M","address":"837 Horace Court","employer":"Snips","email":"simpsoncarpenter@snips.com","city":"Tolu","state":"MA"} +{"index":{"_id":"378"}} +{"account_number":378,"balance":27100,"firstname":"Watson","lastname":"Simpson","age":36,"gender":"F","address":"644 Thomas Street","employer":"Wrapture","email":"watsonsimpson@wrapture.com","city":"Keller","state":"TX"} +{"index":{"_id":"380"}} +{"account_number":380,"balance":35628,"firstname":"Fernandez","lastname":"Reid","age":33,"gender":"F","address":"154 Melba Court","employer":"Cosmosis","email":"fernandezreid@cosmosis.com","city":"Boyd","state":"NE"} +{"index":{"_id":"385"}} +{"account_number":385,"balance":11022,"firstname":"Rosalinda","lastname":"Valencia","age":22,"gender":"M","address":"933 Lloyd Street","employer":"Zoarere","email":"rosalindavalencia@zoarere.com","city":"Waverly","state":"GA"} +{"index":{"_id":"392"}} +{"account_number":392,"balance":31613,"firstname":"Dotson","lastname":"Dean","age":35,"gender":"M","address":"136 Ford Street","employer":"Petigems","email":"dotsondean@petigems.com","city":"Chical","state":"SD"} +{"index":{"_id":"397"}} +{"account_number":397,"balance":37418,"firstname":"Leonard","lastname":"Gray","age":36,"gender":"F","address":"840 Morgan Avenue","employer":"Recritube","email":"leonardgray@recritube.com","city":"Edenburg","state":"AL"} +{"index":{"_id":"400"}} +{"account_number":400,"balance":20685,"firstname":"Kane","lastname":"King","age":21,"gender":"F","address":"405 Cornelia Street","employer":"Tri@Tribalog","email":"kaneking@tri@tribalog.com","city":"Gulf","state":"VT"} +{"index":{"_id":"405"}} +{"account_number":405,"balance":5679,"firstname":"Strickland","lastname":"Fuller","age":26,"gender":"M","address":"990 Concord Street","employer":"Digique","email":"stricklandfuller@digique.com","city":"Southmont","state":"NV"} +{"index":{"_id":"412"}} +{"account_number":412,"balance":27436,"firstname":"Ilene","lastname":"Abbott","age":26,"gender":"M","address":"846 Vine Street","employer":"Typhonica","email":"ileneabbott@typhonica.com","city":"Cedarville","state":"VT"} +{"index":{"_id":"417"}} +{"account_number":417,"balance":1788,"firstname":"Wheeler","lastname":"Ayers","age":35,"gender":"F","address":"677 Hope Street","employer":"Fortean","email":"wheelerayers@fortean.com","city":"Ironton","state":"PA"} +{"index":{"_id":"424"}} +{"account_number":424,"balance":36818,"firstname":"Tracie","lastname":"Gregory","age":34,"gender":"M","address":"112 Hunterfly Place","employer":"Comstruct","email":"traciegregory@comstruct.com","city":"Onton","state":"TN"} +{"index":{"_id":"429"}} +{"account_number":429,"balance":46970,"firstname":"Cantu","lastname":"Lindsey","age":31,"gender":"M","address":"404 Willoughby Avenue","employer":"Inquala","email":"cantulindsey@inquala.com","city":"Cowiche","state":"IA"} +{"index":{"_id":"431"}} +{"account_number":431,"balance":13136,"firstname":"Laurie","lastname":"Shaw","age":26,"gender":"F","address":"263 Aviation Road","employer":"Zillanet","email":"laurieshaw@zillanet.com","city":"Harmon","state":"WV"} +{"index":{"_id":"436"}} +{"account_number":436,"balance":27585,"firstname":"Alexander","lastname":"Sargent","age":23,"gender":"M","address":"363 Albemarle Road","employer":"Fangold","email":"alexandersargent@fangold.com","city":"Calpine","state":"OR"} +{"index":{"_id":"443"}} +{"account_number":443,"balance":7588,"firstname":"Huff","lastname":"Thomas","age":23,"gender":"M","address":"538 Erskine Loop","employer":"Accufarm","email":"huffthomas@accufarm.com","city":"Corinne","state":"AL"} +{"index":{"_id":"448"}} +{"account_number":448,"balance":22776,"firstname":"Adriana","lastname":"Mcfadden","age":35,"gender":"F","address":"984 Woodside Avenue","employer":"Telequiet","email":"adrianamcfadden@telequiet.com","city":"Darrtown","state":"WI"} +{"index":{"_id":"450"}} +{"account_number":450,"balance":2643,"firstname":"Bradford","lastname":"Nielsen","age":25,"gender":"M","address":"487 Keen Court","employer":"Exovent","email":"bradfordnielsen@exovent.com","city":"Hamilton","state":"DE"} +{"index":{"_id":"455"}} +{"account_number":455,"balance":39556,"firstname":"Lynn","lastname":"Tran","age":36,"gender":"M","address":"741 Richmond Street","employer":"Optyk","email":"lynntran@optyk.com","city":"Clinton","state":"WV"} +{"index":{"_id":"462"}} +{"account_number":462,"balance":10871,"firstname":"Calderon","lastname":"Day","age":27,"gender":"M","address":"810 Milford Street","employer":"Cofine","email":"calderonday@cofine.com","city":"Kula","state":"OK"} +{"index":{"_id":"467"}} +{"account_number":467,"balance":6312,"firstname":"Angelica","lastname":"May","age":32,"gender":"F","address":"384 Karweg Place","employer":"Keeg","email":"angelicamay@keeg.com","city":"Tetherow","state":"IA"} +{"index":{"_id":"474"}} +{"account_number":474,"balance":35896,"firstname":"Obrien","lastname":"Walton","age":40,"gender":"F","address":"192 Ide Court","employer":"Suremax","email":"obrienwalton@suremax.com","city":"Crucible","state":"UT"} +{"index":{"_id":"479"}} +{"account_number":479,"balance":31865,"firstname":"Cameron","lastname":"Ross","age":40,"gender":"M","address":"904 Bouck Court","employer":"Telpod","email":"cameronross@telpod.com","city":"Nord","state":"MO"} +{"index":{"_id":"481"}} +{"account_number":481,"balance":20024,"firstname":"Lina","lastname":"Stanley","age":33,"gender":"M","address":"361 Hanover Place","employer":"Strozen","email":"linastanley@strozen.com","city":"Wyoming","state":"NC"} +{"index":{"_id":"486"}} +{"account_number":486,"balance":35902,"firstname":"Dixie","lastname":"Fuentes","age":22,"gender":"F","address":"991 Applegate Court","employer":"Portico","email":"dixiefuentes@portico.com","city":"Salix","state":"VA"} +{"index":{"_id":"493"}} +{"account_number":493,"balance":5871,"firstname":"Campbell","lastname":"Best","age":24,"gender":"M","address":"297 Friel Place","employer":"Fanfare","email":"campbellbest@fanfare.com","city":"Kidder","state":"GA"} +{"index":{"_id":"498"}} +{"account_number":498,"balance":10516,"firstname":"Stella","lastname":"Hinton","age":39,"gender":"F","address":"649 Columbia Place","employer":"Flyboyz","email":"stellahinton@flyboyz.com","city":"Crenshaw","state":"SC"} +{"index":{"_id":"501"}} +{"account_number":501,"balance":16572,"firstname":"Kelley","lastname":"Ochoa","age":36,"gender":"M","address":"451 Clifton Place","employer":"Bluplanet","email":"kelleyochoa@bluplanet.com","city":"Gouglersville","state":"CT"} +{"index":{"_id":"506"}} +{"account_number":506,"balance":43440,"firstname":"Davidson","lastname":"Salas","age":28,"gender":"M","address":"731 Cleveland Street","employer":"Sequitur","email":"davidsonsalas@sequitur.com","city":"Lloyd","state":"ME"} +{"index":{"_id":"513"}} +{"account_number":513,"balance":30040,"firstname":"Maryellen","lastname":"Rose","age":37,"gender":"F","address":"428 Durland Place","employer":"Waterbaby","email":"maryellenrose@waterbaby.com","city":"Kiskimere","state":"RI"} +{"index":{"_id":"518"}} +{"account_number":518,"balance":48954,"firstname":"Finch","lastname":"Curtis","age":29,"gender":"F","address":"137 Ryder Street","employer":"Viagrand","email":"finchcurtis@viagrand.com","city":"Riverton","state":"MO"} +{"index":{"_id":"520"}} +{"account_number":520,"balance":27987,"firstname":"Brandy","lastname":"Calhoun","age":32,"gender":"M","address":"818 Harden Street","employer":"Maxemia","email":"brandycalhoun@maxemia.com","city":"Sidman","state":"OR"} +{"index":{"_id":"525"}} +{"account_number":525,"balance":23545,"firstname":"Holly","lastname":"Miles","age":25,"gender":"M","address":"746 Ludlam Place","employer":"Xurban","email":"hollymiles@xurban.com","city":"Harold","state":"AR"} +{"index":{"_id":"532"}} +{"account_number":532,"balance":17207,"firstname":"Hardin","lastname":"Kirk","age":26,"gender":"M","address":"268 Canarsie Road","employer":"Exposa","email":"hardinkirk@exposa.com","city":"Stouchsburg","state":"IL"} +{"index":{"_id":"537"}} +{"account_number":537,"balance":31069,"firstname":"Morin","lastname":"Frost","age":29,"gender":"M","address":"910 Lake Street","employer":"Primordia","email":"morinfrost@primordia.com","city":"Rivera","state":"DE"} +{"index":{"_id":"544"}} +{"account_number":544,"balance":41735,"firstname":"Short","lastname":"Dennis","age":21,"gender":"F","address":"908 Glen Street","employer":"Minga","email":"shortdennis@minga.com","city":"Dale","state":"KY"} +{"index":{"_id":"549"}} +{"account_number":549,"balance":1932,"firstname":"Jacqueline","lastname":"Maxwell","age":40,"gender":"M","address":"444 Schenck Place","employer":"Fuelworks","email":"jacquelinemaxwell@fuelworks.com","city":"Oretta","state":"OR"} +{"index":{"_id":"551"}} +{"account_number":551,"balance":21732,"firstname":"Milagros","lastname":"Travis","age":27,"gender":"F","address":"380 Murdock Court","employer":"Sloganaut","email":"milagrostravis@sloganaut.com","city":"Homeland","state":"AR"} +{"index":{"_id":"556"}} +{"account_number":556,"balance":36420,"firstname":"Collier","lastname":"Odonnell","age":35,"gender":"M","address":"591 Nolans Lane","employer":"Sultraxin","email":"collierodonnell@sultraxin.com","city":"Fulford","state":"MD"} +{"index":{"_id":"563"}} +{"account_number":563,"balance":43403,"firstname":"Morgan","lastname":"Torres","age":30,"gender":"F","address":"672 Belvidere Street","employer":"Quonata","email":"morgantorres@quonata.com","city":"Hollymead","state":"KY"} +{"index":{"_id":"568"}} +{"account_number":568,"balance":36628,"firstname":"Lesa","lastname":"Maynard","age":29,"gender":"F","address":"295 Whitty Lane","employer":"Coash","email":"lesamaynard@coash.com","city":"Broadlands","state":"VT"} +{"index":{"_id":"570"}} +{"account_number":570,"balance":26751,"firstname":"Church","lastname":"Mercado","age":24,"gender":"F","address":"892 Wyckoff Street","employer":"Xymonk","email":"churchmercado@xymonk.com","city":"Gloucester","state":"KY"} +{"index":{"_id":"575"}} +{"account_number":575,"balance":12588,"firstname":"Buchanan","lastname":"Pope","age":39,"gender":"M","address":"581 Sumner Place","employer":"Stucco","email":"buchananpope@stucco.com","city":"Ellerslie","state":"MD"} +{"index":{"_id":"582"}} +{"account_number":582,"balance":33371,"firstname":"Manning","lastname":"Guthrie","age":24,"gender":"F","address":"271 Jodie Court","employer":"Xerex","email":"manningguthrie@xerex.com","city":"Breinigsville","state":"NM"} +{"index":{"_id":"587"}} +{"account_number":587,"balance":3468,"firstname":"Carly","lastname":"Johns","age":33,"gender":"M","address":"390 Noll Street","employer":"Gallaxia","email":"carlyjohns@gallaxia.com","city":"Emison","state":"DC"} +{"index":{"_id":"594"}} +{"account_number":594,"balance":28194,"firstname":"Golden","lastname":"Donovan","age":26,"gender":"M","address":"199 Jewel Street","employer":"Organica","email":"goldendonovan@organica.com","city":"Macdona","state":"RI"} +{"index":{"_id":"599"}} +{"account_number":599,"balance":11944,"firstname":"Joanna","lastname":"Jennings","age":36,"gender":"F","address":"318 Irving Street","employer":"Extremo","email":"joannajennings@extremo.com","city":"Bartley","state":"MI"} +{"index":{"_id":"602"}} +{"account_number":602,"balance":38699,"firstname":"Mcgowan","lastname":"Mcclain","age":33,"gender":"M","address":"361 Stoddard Place","employer":"Oatfarm","email":"mcgowanmcclain@oatfarm.com","city":"Kapowsin","state":"MI"} +{"index":{"_id":"607"}} +{"account_number":607,"balance":38350,"firstname":"White","lastname":"Small","age":38,"gender":"F","address":"736 Judge Street","employer":"Immunics","email":"whitesmall@immunics.com","city":"Fairfield","state":"HI"} +{"index":{"_id":"614"}} +{"account_number":614,"balance":13157,"firstname":"Salazar","lastname":"Howard","age":35,"gender":"F","address":"847 Imlay Street","employer":"Retrack","email":"salazarhoward@retrack.com","city":"Grill","state":"FL"} +{"index":{"_id":"619"}} +{"account_number":619,"balance":48755,"firstname":"Grimes","lastname":"Reynolds","age":36,"gender":"M","address":"378 Denton Place","employer":"Frenex","email":"grimesreynolds@frenex.com","city":"Murillo","state":"LA"} +{"index":{"_id":"621"}} +{"account_number":621,"balance":35480,"firstname":"Leslie","lastname":"Sloan","age":26,"gender":"F","address":"336 Kansas Place","employer":"Dancity","email":"lesliesloan@dancity.com","city":"Corriganville","state":"AR"} +{"index":{"_id":"626"}} +{"account_number":626,"balance":19498,"firstname":"Ava","lastname":"Richardson","age":31,"gender":"F","address":"666 Nautilus Avenue","employer":"Cinaster","email":"avarichardson@cinaster.com","city":"Sutton","state":"AL"} +{"index":{"_id":"633"}} +{"account_number":633,"balance":35874,"firstname":"Conner","lastname":"Ramos","age":34,"gender":"M","address":"575 Agate Court","employer":"Insource","email":"connerramos@insource.com","city":"Madaket","state":"OK"} +{"index":{"_id":"638"}} +{"account_number":638,"balance":2658,"firstname":"Bridget","lastname":"Gallegos","age":31,"gender":"M","address":"383 Wogan Terrace","employer":"Songlines","email":"bridgetgallegos@songlines.com","city":"Linganore","state":"WA"} +{"index":{"_id":"640"}} +{"account_number":640,"balance":35596,"firstname":"Candace","lastname":"Hancock","age":25,"gender":"M","address":"574 Riverdale Avenue","employer":"Animalia","email":"candacehancock@animalia.com","city":"Blandburg","state":"KY"} +{"index":{"_id":"645"}} +{"account_number":645,"balance":29362,"firstname":"Edwina","lastname":"Hutchinson","age":26,"gender":"F","address":"892 Pacific Street","employer":"Essensia","email":"edwinahutchinson@essensia.com","city":"Dowling","state":"NE"} +{"index":{"_id":"652"}} +{"account_number":652,"balance":17363,"firstname":"Bonner","lastname":"Garner","age":26,"gender":"M","address":"219 Grafton Street","employer":"Utarian","email":"bonnergarner@utarian.com","city":"Vandiver","state":"PA"} +{"index":{"_id":"657"}} +{"account_number":657,"balance":40475,"firstname":"Kathleen","lastname":"Wilder","age":34,"gender":"F","address":"286 Sutter Avenue","employer":"Solgan","email":"kathleenwilder@solgan.com","city":"Graniteville","state":"MI"} +{"index":{"_id":"664"}} +{"account_number":664,"balance":16163,"firstname":"Hart","lastname":"Mccormick","age":40,"gender":"M","address":"144 Guider Avenue","employer":"Dyno","email":"hartmccormick@dyno.com","city":"Carbonville","state":"ID"} +{"index":{"_id":"669"}} +{"account_number":669,"balance":16934,"firstname":"Jewel","lastname":"Estrada","age":28,"gender":"M","address":"896 Meeker Avenue","employer":"Zilla","email":"jewelestrada@zilla.com","city":"Goodville","state":"PA"} +{"index":{"_id":"671"}} +{"account_number":671,"balance":29029,"firstname":"Antoinette","lastname":"Cook","age":34,"gender":"M","address":"375 Cumberland Street","employer":"Harmoney","email":"antoinettecook@harmoney.com","city":"Bergoo","state":"VT"} +{"index":{"_id":"676"}} +{"account_number":676,"balance":23842,"firstname":"Lisa","lastname":"Dudley","age":34,"gender":"M","address":"506 Vanderveer Street","employer":"Tropoli","email":"lisadudley@tropoli.com","city":"Konterra","state":"NY"} +{"index":{"_id":"683"}} +{"account_number":683,"balance":4381,"firstname":"Matilda","lastname":"Berger","age":39,"gender":"M","address":"884 Noble Street","employer":"Fibrodyne","email":"matildaberger@fibrodyne.com","city":"Shepardsville","state":"TN"} +{"index":{"_id":"688"}} +{"account_number":688,"balance":17931,"firstname":"Freeman","lastname":"Zamora","age":22,"gender":"F","address":"114 Herzl Street","employer":"Elemantra","email":"freemanzamora@elemantra.com","city":"Libertytown","state":"NM"} +{"index":{"_id":"690"}} +{"account_number":690,"balance":18127,"firstname":"Russo","lastname":"Swanson","age":35,"gender":"F","address":"256 Roebling Street","employer":"Zaj","email":"russoswanson@zaj.com","city":"Hoagland","state":"MI"} +{"index":{"_id":"695"}} +{"account_number":695,"balance":36800,"firstname":"Gonzales","lastname":"Mcfarland","age":26,"gender":"F","address":"647 Louisa Street","employer":"Songbird","email":"gonzalesmcfarland@songbird.com","city":"Crisman","state":"ID"} +{"index":{"_id":"703"}} +{"account_number":703,"balance":27443,"firstname":"Dona","lastname":"Burton","age":29,"gender":"M","address":"489 Flatlands Avenue","employer":"Cytrex","email":"donaburton@cytrex.com","city":"Reno","state":"VA"} +{"index":{"_id":"708"}} +{"account_number":708,"balance":34002,"firstname":"May","lastname":"Ortiz","age":28,"gender":"F","address":"244 Chauncey Street","employer":"Syntac","email":"mayortiz@syntac.com","city":"Munjor","state":"ID"} +{"index":{"_id":"710"}} +{"account_number":710,"balance":33650,"firstname":"Shelton","lastname":"Stark","age":37,"gender":"M","address":"404 Ovington Avenue","employer":"Kraggle","email":"sheltonstark@kraggle.com","city":"Ogema","state":"TN"} +{"index":{"_id":"715"}} +{"account_number":715,"balance":23734,"firstname":"Tammi","lastname":"Hodge","age":24,"gender":"M","address":"865 Church Lane","employer":"Netur","email":"tammihodge@netur.com","city":"Lacomb","state":"KS"} +{"index":{"_id":"722"}} +{"account_number":722,"balance":27256,"firstname":"Roberts","lastname":"Beasley","age":34,"gender":"F","address":"305 Kings Hwy","employer":"Quintity","email":"robertsbeasley@quintity.com","city":"Hayden","state":"PA"} +{"index":{"_id":"727"}} +{"account_number":727,"balance":27263,"firstname":"Natasha","lastname":"Knapp","age":36,"gender":"M","address":"723 Hubbard Street","employer":"Exostream","email":"natashaknapp@exostream.com","city":"Trexlertown","state":"LA"} +{"index":{"_id":"734"}} +{"account_number":734,"balance":20325,"firstname":"Keri","lastname":"Kinney","age":23,"gender":"M","address":"490 Balfour Place","employer":"Retrotex","email":"kerikinney@retrotex.com","city":"Salunga","state":"PA"} +{"index":{"_id":"739"}} +{"account_number":739,"balance":39063,"firstname":"Gwen","lastname":"Hardy","age":33,"gender":"F","address":"733 Stuart Street","employer":"Exozent","email":"gwenhardy@exozent.com","city":"Drytown","state":"NY"} +{"index":{"_id":"741"}} +{"account_number":741,"balance":33074,"firstname":"Nielsen","lastname":"Good","age":22,"gender":"M","address":"404 Norfolk Street","employer":"Kiggle","email":"nielsengood@kiggle.com","city":"Cumberland","state":"WA"} +{"index":{"_id":"746"}} +{"account_number":746,"balance":15970,"firstname":"Marguerite","lastname":"Wall","age":28,"gender":"F","address":"364 Crosby Avenue","employer":"Aquoavo","email":"margueritewall@aquoavo.com","city":"Jeff","state":"MI"} +{"index":{"_id":"753"}} +{"account_number":753,"balance":33340,"firstname":"Katina","lastname":"Alford","age":21,"gender":"F","address":"690 Ross Street","employer":"Intrawear","email":"katinaalford@intrawear.com","city":"Grimsley","state":"OK"} +{"index":{"_id":"758"}} +{"account_number":758,"balance":15739,"firstname":"Berta","lastname":"Short","age":28,"gender":"M","address":"149 Surf Avenue","employer":"Ozean","email":"bertashort@ozean.com","city":"Odessa","state":"UT"} +{"index":{"_id":"760"}} +{"account_number":760,"balance":40996,"firstname":"Rhea","lastname":"Blair","age":37,"gender":"F","address":"440 Hubbard Place","employer":"Bicol","email":"rheablair@bicol.com","city":"Stockwell","state":"LA"} +{"index":{"_id":"765"}} +{"account_number":765,"balance":31278,"firstname":"Knowles","lastname":"Cunningham","age":23,"gender":"M","address":"753 Macdougal Street","employer":"Thredz","email":"knowlescunningham@thredz.com","city":"Thomasville","state":"WA"} +{"index":{"_id":"772"}} +{"account_number":772,"balance":37849,"firstname":"Eloise","lastname":"Sparks","age":21,"gender":"M","address":"608 Willow Street","employer":"Satiance","email":"eloisesparks@satiance.com","city":"Richford","state":"NY"} +{"index":{"_id":"777"}} +{"account_number":777,"balance":48294,"firstname":"Adkins","lastname":"Mejia","age":32,"gender":"M","address":"186 Oxford Walk","employer":"Datagen","email":"adkinsmejia@datagen.com","city":"Faywood","state":"OK"} +{"index":{"_id":"784"}} +{"account_number":784,"balance":25291,"firstname":"Mabel","lastname":"Thornton","age":21,"gender":"M","address":"124 Louisiana Avenue","employer":"Zolavo","email":"mabelthornton@zolavo.com","city":"Lynn","state":"AL"} +{"index":{"_id":"789"}} +{"account_number":789,"balance":8760,"firstname":"Cunningham","lastname":"Kerr","age":27,"gender":"F","address":"154 Sharon Street","employer":"Polarium","email":"cunninghamkerr@polarium.com","city":"Tuskahoma","state":"MS"} +{"index":{"_id":"791"}} +{"account_number":791,"balance":48249,"firstname":"Janine","lastname":"Huber","age":38,"gender":"F","address":"348 Porter Avenue","employer":"Viocular","email":"janinehuber@viocular.com","city":"Fivepointville","state":"MA"} +{"index":{"_id":"796"}} +{"account_number":796,"balance":23503,"firstname":"Mona","lastname":"Craft","age":35,"gender":"F","address":"511 Henry Street","employer":"Opticom","email":"monacraft@opticom.com","city":"Websterville","state":"IN"} +{"index":{"_id":"804"}} +{"account_number":804,"balance":23610,"firstname":"Rojas","lastname":"Oneal","age":27,"gender":"M","address":"669 Sandford Street","employer":"Glukgluk","email":"rojasoneal@glukgluk.com","city":"Wheaton","state":"ME"} +{"index":{"_id":"809"}} +{"account_number":809,"balance":47812,"firstname":"Christie","lastname":"Strickland","age":30,"gender":"M","address":"346 Bancroft Place","employer":"Anarco","email":"christiestrickland@anarco.com","city":"Baden","state":"NV"} +{"index":{"_id":"811"}} +{"account_number":811,"balance":26007,"firstname":"Walls","lastname":"Rogers","age":28,"gender":"F","address":"352 Freeman Street","employer":"Geekmosis","email":"wallsrogers@geekmosis.com","city":"Caroleen","state":"NV"} +{"index":{"_id":"816"}} +{"account_number":816,"balance":9567,"firstname":"Cornelia","lastname":"Lane","age":20,"gender":"F","address":"384 Bainbridge Street","employer":"Sulfax","email":"cornelialane@sulfax.com","city":"Elizaville","state":"MS"} +{"index":{"_id":"823"}} +{"account_number":823,"balance":48726,"firstname":"Celia","lastname":"Bernard","age":33,"gender":"F","address":"466 Amboy Street","employer":"Mitroc","email":"celiabernard@mitroc.com","city":"Skyland","state":"GA"} +{"index":{"_id":"828"}} +{"account_number":828,"balance":44890,"firstname":"Blanche","lastname":"Holmes","age":33,"gender":"F","address":"605 Stryker Court","employer":"Motovate","email":"blancheholmes@motovate.com","city":"Loomis","state":"KS"} +{"index":{"_id":"830"}} +{"account_number":830,"balance":45210,"firstname":"Louella","lastname":"Chan","age":23,"gender":"M","address":"511 Heath Place","employer":"Conferia","email":"louellachan@conferia.com","city":"Brookfield","state":"OK"} +{"index":{"_id":"835"}} +{"account_number":835,"balance":46558,"firstname":"Glover","lastname":"Rutledge","age":25,"gender":"F","address":"641 Royce Street","employer":"Ginkogene","email":"gloverrutledge@ginkogene.com","city":"Dixonville","state":"VA"} +{"index":{"_id":"842"}} +{"account_number":842,"balance":49587,"firstname":"Meagan","lastname":"Buckner","age":23,"gender":"F","address":"833 Bushwick Court","employer":"Biospan","email":"meaganbuckner@biospan.com","city":"Craig","state":"TX"} +{"index":{"_id":"847"}} +{"account_number":847,"balance":8652,"firstname":"Antonia","lastname":"Duncan","age":23,"gender":"M","address":"644 Stryker Street","employer":"Talae","email":"antoniaduncan@talae.com","city":"Dawn","state":"MO"} +{"index":{"_id":"854"}} +{"account_number":854,"balance":49795,"firstname":"Jimenez","lastname":"Barry","age":25,"gender":"F","address":"603 Cooper Street","employer":"Verton","email":"jimenezbarry@verton.com","city":"Moscow","state":"AL"} +{"index":{"_id":"859"}} +{"account_number":859,"balance":20734,"firstname":"Beulah","lastname":"Stuart","age":24,"gender":"F","address":"651 Albemarle Terrace","employer":"Hatology","email":"beulahstuart@hatology.com","city":"Waiohinu","state":"RI"} +{"index":{"_id":"861"}} +{"account_number":861,"balance":44173,"firstname":"Jaime","lastname":"Wilson","age":35,"gender":"M","address":"680 Richardson Street","employer":"Temorak","email":"jaimewilson@temorak.com","city":"Fidelis","state":"FL"} +{"index":{"_id":"866"}} +{"account_number":866,"balance":45565,"firstname":"Araceli","lastname":"Woodward","age":28,"gender":"M","address":"326 Meadow Street","employer":"Olympix","email":"araceliwoodward@olympix.com","city":"Dana","state":"KS"} +{"index":{"_id":"873"}} +{"account_number":873,"balance":43931,"firstname":"Tisha","lastname":"Cotton","age":39,"gender":"F","address":"432 Lincoln Road","employer":"Buzzmaker","email":"tishacotton@buzzmaker.com","city":"Bluetown","state":"GA"} +{"index":{"_id":"878"}} +{"account_number":878,"balance":49159,"firstname":"Battle","lastname":"Blackburn","age":40,"gender":"F","address":"234 Hendrix Street","employer":"Zilphur","email":"battleblackburn@zilphur.com","city":"Wanamie","state":"PA"} +{"index":{"_id":"880"}} +{"account_number":880,"balance":22575,"firstname":"Christian","lastname":"Myers","age":35,"gender":"M","address":"737 Crown Street","employer":"Combogen","email":"christianmyers@combogen.com","city":"Abrams","state":"OK"} +{"index":{"_id":"885"}} +{"account_number":885,"balance":31661,"firstname":"Valdez","lastname":"Roberson","age":40,"gender":"F","address":"227 Scholes Street","employer":"Delphide","email":"valdezroberson@delphide.com","city":"Chilton","state":"MT"} +{"index":{"_id":"892"}} +{"account_number":892,"balance":44974,"firstname":"Hill","lastname":"Hayes","age":29,"gender":"M","address":"721 Dooley Street","employer":"Fuelton","email":"hillhayes@fuelton.com","city":"Orason","state":"MT"} +{"index":{"_id":"897"}} +{"account_number":897,"balance":45973,"firstname":"Alyson","lastname":"Irwin","age":25,"gender":"M","address":"731 Poplar Street","employer":"Quizka","email":"alysonirwin@quizka.com","city":"Singer","state":"VA"} +{"index":{"_id":"900"}} +{"account_number":900,"balance":6124,"firstname":"Gonzalez","lastname":"Watson","age":23,"gender":"M","address":"624 Sullivan Street","employer":"Marvane","email":"gonzalezwatson@marvane.com","city":"Wikieup","state":"IL"} +{"index":{"_id":"905"}} +{"account_number":905,"balance":29438,"firstname":"Schultz","lastname":"Moreno","age":20,"gender":"F","address":"761 Cedar Street","employer":"Paragonia","email":"schultzmoreno@paragonia.com","city":"Glenshaw","state":"SC"} +{"index":{"_id":"912"}} +{"account_number":912,"balance":13675,"firstname":"Flora","lastname":"Alvarado","age":26,"gender":"M","address":"771 Vandervoort Avenue","employer":"Boilicon","email":"floraalvarado@boilicon.com","city":"Vivian","state":"ID"} +{"index":{"_id":"917"}} +{"account_number":917,"balance":47782,"firstname":"Parks","lastname":"Hurst","age":24,"gender":"M","address":"933 Cozine Avenue","employer":"Pyramis","email":"parkshurst@pyramis.com","city":"Lindcove","state":"GA"} +{"index":{"_id":"924"}} +{"account_number":924,"balance":3811,"firstname":"Hilary","lastname":"Leonard","age":24,"gender":"M","address":"235 Hegeman Avenue","employer":"Metroz","email":"hilaryleonard@metroz.com","city":"Roosevelt","state":"ME"} +{"index":{"_id":"929"}} +{"account_number":929,"balance":34708,"firstname":"Willie","lastname":"Hickman","age":35,"gender":"M","address":"430 Devoe Street","employer":"Apextri","email":"williehickman@apextri.com","city":"Clay","state":"MS"} +{"index":{"_id":"931"}} +{"account_number":931,"balance":8244,"firstname":"Ingrid","lastname":"Garcia","age":23,"gender":"F","address":"674 Indiana Place","employer":"Balooba","email":"ingridgarcia@balooba.com","city":"Interlochen","state":"AZ"} +{"index":{"_id":"936"}} +{"account_number":936,"balance":22430,"firstname":"Beth","lastname":"Frye","age":36,"gender":"M","address":"462 Thatford Avenue","employer":"Puria","email":"bethfrye@puria.com","city":"Hiseville","state":"LA"} +{"index":{"_id":"943"}} +{"account_number":943,"balance":24187,"firstname":"Wagner","lastname":"Griffin","age":23,"gender":"M","address":"489 Ellery Street","employer":"Gazak","email":"wagnergriffin@gazak.com","city":"Lorraine","state":"HI"} +{"index":{"_id":"948"}} +{"account_number":948,"balance":37074,"firstname":"Sargent","lastname":"Powers","age":40,"gender":"M","address":"532 Fiske Place","employer":"Accuprint","email":"sargentpowers@accuprint.com","city":"Umapine","state":"AK"} +{"index":{"_id":"950"}} +{"account_number":950,"balance":30916,"firstname":"Sherrie","lastname":"Patel","age":32,"gender":"F","address":"658 Langham Street","employer":"Futurize","email":"sherriepatel@futurize.com","city":"Garfield","state":"OR"} +{"index":{"_id":"955"}} +{"account_number":955,"balance":41621,"firstname":"Klein","lastname":"Kemp","age":33,"gender":"M","address":"370 Vanderbilt Avenue","employer":"Synkgen","email":"kleinkemp@synkgen.com","city":"Bonanza","state":"FL"} +{"index":{"_id":"962"}} +{"account_number":962,"balance":32096,"firstname":"Trujillo","lastname":"Wilcox","age":21,"gender":"F","address":"914 Duffield Street","employer":"Extragene","email":"trujillowilcox@extragene.com","city":"Golconda","state":"MA"} +{"index":{"_id":"967"}} +{"account_number":967,"balance":19161,"firstname":"Carrie","lastname":"Huffman","age":36,"gender":"F","address":"240 Sands Street","employer":"Injoy","email":"carriehuffman@injoy.com","city":"Leroy","state":"CA"} +{"index":{"_id":"974"}} +{"account_number":974,"balance":38082,"firstname":"Deborah","lastname":"Yang","age":26,"gender":"F","address":"463 Goodwin Place","employer":"Entogrok","email":"deborahyang@entogrok.com","city":"Herald","state":"KY"} +{"index":{"_id":"979"}} +{"account_number":979,"balance":43130,"firstname":"Vaughn","lastname":"Pittman","age":29,"gender":"M","address":"446 Tompkins Place","employer":"Phormula","email":"vaughnpittman@phormula.com","city":"Fingerville","state":"WI"} +{"index":{"_id":"981"}} +{"account_number":981,"balance":20278,"firstname":"Nolan","lastname":"Warner","age":29,"gender":"F","address":"753 Channel Avenue","employer":"Interodeo","email":"nolanwarner@interodeo.com","city":"Layhill","state":"MT"} +{"index":{"_id":"986"}} +{"account_number":986,"balance":35086,"firstname":"Norris","lastname":"Hubbard","age":31,"gender":"M","address":"600 Celeste Court","employer":"Printspan","email":"norrishubbard@printspan.com","city":"Cassel","state":"MI"} +{"index":{"_id":"993"}} +{"account_number":993,"balance":26487,"firstname":"Campos","lastname":"Olsen","age":37,"gender":"M","address":"873 Covert Street","employer":"Isbol","email":"camposolsen@isbol.com","city":"Glendale","state":"AK"} +{"index":{"_id":"998"}} +{"account_number":998,"balance":16869,"firstname":"Letha","lastname":"Baker","age":40,"gender":"F","address":"206 Llama Court","employer":"Dognosis","email":"lethabaker@dognosis.com","city":"Dunlo","state":"WV"} +{"index":{"_id":"2"}} +{"account_number":2,"balance":28838,"firstname":"Roberta","lastname":"Bender","age":22,"gender":"F","address":"560 Kingsway Place","employer":"Chillium","email":"robertabender@chillium.com","city":"Bennett","state":"LA"} +{"index":{"_id":"7"}} +{"account_number":7,"balance":39121,"firstname":"Levy","lastname":"Richard","age":22,"gender":"M","address":"820 Logan Street","employer":"Teraprene","email":"levyrichard@teraprene.com","city":"Shrewsbury","state":"MO"} +{"index":{"_id":"14"}} +{"account_number":14,"balance":20480,"firstname":"Erma","lastname":"Kane","age":39,"gender":"F","address":"661 Vista Place","employer":"Stockpost","email":"ermakane@stockpost.com","city":"Chamizal","state":"NY"} +{"index":{"_id":"19"}} +{"account_number":19,"balance":27894,"firstname":"Schwartz","lastname":"Buchanan","age":28,"gender":"F","address":"449 Mersereau Court","employer":"Sybixtex","email":"schwartzbuchanan@sybixtex.com","city":"Greenwich","state":"KS"} +{"index":{"_id":"21"}} +{"account_number":21,"balance":7004,"firstname":"Estella","lastname":"Paul","age":38,"gender":"M","address":"859 Portal Street","employer":"Zillatide","email":"estellapaul@zillatide.com","city":"Churchill","state":"WV"} +{"index":{"_id":"26"}} +{"account_number":26,"balance":14127,"firstname":"Lorraine","lastname":"Mccullough","age":39,"gender":"F","address":"157 Dupont Street","employer":"Zosis","email":"lorrainemccullough@zosis.com","city":"Dennard","state":"NH"} +{"index":{"_id":"33"}} +{"account_number":33,"balance":35439,"firstname":"Savannah","lastname":"Kirby","age":30,"gender":"F","address":"372 Malta Street","employer":"Musanpoly","email":"savannahkirby@musanpoly.com","city":"Muse","state":"AK"} +{"index":{"_id":"38"}} +{"account_number":38,"balance":10511,"firstname":"Erna","lastname":"Fields","age":32,"gender":"M","address":"357 Maple Street","employer":"Eweville","email":"ernafields@eweville.com","city":"Twilight","state":"MS"} +{"index":{"_id":"40"}} +{"account_number":40,"balance":33882,"firstname":"Pace","lastname":"Molina","age":40,"gender":"M","address":"263 Ovington Court","employer":"Cytrak","email":"pacemolina@cytrak.com","city":"Silkworth","state":"OR"} +{"index":{"_id":"45"}} +{"account_number":45,"balance":44478,"firstname":"Geneva","lastname":"Morin","age":21,"gender":"F","address":"357 Herkimer Street","employer":"Ezent","email":"genevamorin@ezent.com","city":"Blanco","state":"AZ"} +{"index":{"_id":"52"}} +{"account_number":52,"balance":46425,"firstname":"Kayla","lastname":"Bradshaw","age":31,"gender":"M","address":"449 Barlow Drive","employer":"Magnemo","email":"kaylabradshaw@magnemo.com","city":"Wawona","state":"AZ"} +{"index":{"_id":"57"}} +{"account_number":57,"balance":8705,"firstname":"Powell","lastname":"Herring","age":21,"gender":"M","address":"263 Merit Court","employer":"Digiprint","email":"powellherring@digiprint.com","city":"Coral","state":"MT"} +{"index":{"_id":"64"}} +{"account_number":64,"balance":44036,"firstname":"Miles","lastname":"Battle","age":35,"gender":"F","address":"988 Homecrest Avenue","employer":"Koffee","email":"milesbattle@koffee.com","city":"Motley","state":"ID"} +{"index":{"_id":"69"}} +{"account_number":69,"balance":14253,"firstname":"Desiree","lastname":"Harrison","age":24,"gender":"M","address":"694 Garland Court","employer":"Barkarama","email":"desireeharrison@barkarama.com","city":"Hackneyville","state":"GA"} +{"index":{"_id":"71"}} +{"account_number":71,"balance":38201,"firstname":"Sharpe","lastname":"Hoffman","age":39,"gender":"F","address":"450 Conklin Avenue","employer":"Centree","email":"sharpehoffman@centree.com","city":"Urbana","state":"WY"} +{"index":{"_id":"76"}} +{"account_number":76,"balance":38345,"firstname":"Claudette","lastname":"Beard","age":24,"gender":"F","address":"748 Dorset Street","employer":"Repetwire","email":"claudettebeard@repetwire.com","city":"Caln","state":"TX"} +{"index":{"_id":"83"}} +{"account_number":83,"balance":35928,"firstname":"Mayo","lastname":"Cleveland","age":28,"gender":"M","address":"720 Brooklyn Road","employer":"Indexia","email":"mayocleveland@indexia.com","city":"Roberts","state":"ND"} +{"index":{"_id":"88"}} +{"account_number":88,"balance":26418,"firstname":"Adela","lastname":"Tyler","age":21,"gender":"F","address":"737 Clove Road","employer":"Surelogic","email":"adelatyler@surelogic.com","city":"Boling","state":"SD"} +{"index":{"_id":"90"}} +{"account_number":90,"balance":25332,"firstname":"Herman","lastname":"Snyder","age":22,"gender":"F","address":"737 College Place","employer":"Lunchpod","email":"hermansnyder@lunchpod.com","city":"Flintville","state":"IA"} +{"index":{"_id":"95"}} +{"account_number":95,"balance":1650,"firstname":"Dominguez","lastname":"Le","age":20,"gender":"M","address":"539 Grace Court","employer":"Portica","email":"dominguezle@portica.com","city":"Wollochet","state":"KS"} +{"index":{"_id":"103"}} +{"account_number":103,"balance":11253,"firstname":"Calhoun","lastname":"Bruce","age":33,"gender":"F","address":"731 Clarkson Avenue","employer":"Automon","email":"calhounbruce@automon.com","city":"Marienthal","state":"IL"} +{"index":{"_id":"108"}} +{"account_number":108,"balance":19015,"firstname":"Christensen","lastname":"Weaver","age":21,"gender":"M","address":"398 Dearborn Court","employer":"Quilk","email":"christensenweaver@quilk.com","city":"Belvoir","state":"TX"} +{"index":{"_id":"110"}} +{"account_number":110,"balance":4850,"firstname":"Daphne","lastname":"Byrd","age":23,"gender":"F","address":"239 Conover Street","employer":"Freakin","email":"daphnebyrd@freakin.com","city":"Taft","state":"MN"} +{"index":{"_id":"115"}} +{"account_number":115,"balance":18750,"firstname":"Nikki","lastname":"Doyle","age":31,"gender":"F","address":"537 Clara Street","employer":"Fossiel","email":"nikkidoyle@fossiel.com","city":"Caron","state":"MS"} +{"index":{"_id":"122"}} +{"account_number":122,"balance":17128,"firstname":"Aurora","lastname":"Fry","age":31,"gender":"F","address":"227 Knapp Street","employer":"Makingway","email":"aurorafry@makingway.com","city":"Maybell","state":"NE"} +{"index":{"_id":"127"}} +{"account_number":127,"balance":48734,"firstname":"Diann","lastname":"Mclaughlin","age":33,"gender":"F","address":"340 Clermont Avenue","employer":"Enomen","email":"diannmclaughlin@enomen.com","city":"Rutherford","state":"ND"} +{"index":{"_id":"134"}} +{"account_number":134,"balance":33829,"firstname":"Madelyn","lastname":"Norris","age":30,"gender":"F","address":"176 Noel Avenue","employer":"Endicil","email":"madelynnorris@endicil.com","city":"Walker","state":"NE"} +{"index":{"_id":"139"}} +{"account_number":139,"balance":18444,"firstname":"Rios","lastname":"Todd","age":35,"gender":"F","address":"281 Georgia Avenue","employer":"Uberlux","email":"riostodd@uberlux.com","city":"Hannasville","state":"PA"} +{"index":{"_id":"141"}} +{"account_number":141,"balance":20790,"firstname":"Liliana","lastname":"Caldwell","age":29,"gender":"M","address":"414 Huron Street","employer":"Rubadub","email":"lilianacaldwell@rubadub.com","city":"Hiwasse","state":"OK"} +{"index":{"_id":"146"}} +{"account_number":146,"balance":39078,"firstname":"Lang","lastname":"Kaufman","age":32,"gender":"F","address":"626 Beverley Road","employer":"Rodeomad","email":"langkaufman@rodeomad.com","city":"Mahtowa","state":"RI"} +{"index":{"_id":"153"}} +{"account_number":153,"balance":32074,"firstname":"Bird","lastname":"Cochran","age":31,"gender":"F","address":"691 Bokee Court","employer":"Supremia","email":"birdcochran@supremia.com","city":"Barrelville","state":"NE"} +{"index":{"_id":"158"}} +{"account_number":158,"balance":9380,"firstname":"Natalie","lastname":"Mcdowell","age":27,"gender":"M","address":"953 Roder Avenue","employer":"Myopium","email":"nataliemcdowell@myopium.com","city":"Savage","state":"ND"} +{"index":{"_id":"160"}} +{"account_number":160,"balance":48974,"firstname":"Hull","lastname":"Cherry","age":23,"gender":"F","address":"275 Beaumont Street","employer":"Noralex","email":"hullcherry@noralex.com","city":"Whipholt","state":"WA"} +{"index":{"_id":"165"}} +{"account_number":165,"balance":18956,"firstname":"Sims","lastname":"Mckay","age":40,"gender":"F","address":"205 Jackson Street","employer":"Comtour","email":"simsmckay@comtour.com","city":"Tilden","state":"DC"} +{"index":{"_id":"172"}} +{"account_number":172,"balance":18356,"firstname":"Marie","lastname":"Whitehead","age":20,"gender":"M","address":"704 Monaco Place","employer":"Sultrax","email":"mariewhitehead@sultrax.com","city":"Dragoon","state":"IL"} +{"index":{"_id":"177"}} +{"account_number":177,"balance":48972,"firstname":"Harris","lastname":"Gross","age":40,"gender":"F","address":"468 Suydam Street","employer":"Kidstock","email":"harrisgross@kidstock.com","city":"Yettem","state":"KY"} +{"index":{"_id":"184"}} +{"account_number":184,"balance":9157,"firstname":"Cathy","lastname":"Morrison","age":27,"gender":"M","address":"882 Pine Street","employer":"Zytrek","email":"cathymorrison@zytrek.com","city":"Fedora","state":"FL"} +{"index":{"_id":"189"}} +{"account_number":189,"balance":20167,"firstname":"Ada","lastname":"Cortez","age":38,"gender":"F","address":"700 Forest Place","employer":"Micronaut","email":"adacortez@micronaut.com","city":"Eagletown","state":"TX"} +{"index":{"_id":"191"}} +{"account_number":191,"balance":26172,"firstname":"Barr","lastname":"Sharpe","age":28,"gender":"M","address":"428 Auburn Place","employer":"Ziggles","email":"barrsharpe@ziggles.com","city":"Springdale","state":"KS"} +{"index":{"_id":"196"}} +{"account_number":196,"balance":29931,"firstname":"Caldwell","lastname":"Daniel","age":28,"gender":"F","address":"405 Oliver Street","employer":"Furnigeer","email":"caldwelldaniel@furnigeer.com","city":"Zortman","state":"NE"} +{"index":{"_id":"204"}} +{"account_number":204,"balance":27714,"firstname":"Mavis","lastname":"Deleon","age":39,"gender":"F","address":"400 Waldane Court","employer":"Lotron","email":"mavisdeleon@lotron.com","city":"Stollings","state":"LA"} +{"index":{"_id":"209"}} +{"account_number":209,"balance":31052,"firstname":"Myers","lastname":"Noel","age":30,"gender":"F","address":"691 Alton Place","employer":"Greeker","email":"myersnoel@greeker.com","city":"Hinsdale","state":"KY"} +{"index":{"_id":"211"}} +{"account_number":211,"balance":21539,"firstname":"Graciela","lastname":"Vaughan","age":22,"gender":"M","address":"558 Montauk Court","employer":"Fishland","email":"gracielavaughan@fishland.com","city":"Madrid","state":"PA"} +{"index":{"_id":"216"}} +{"account_number":216,"balance":11422,"firstname":"Price","lastname":"Haley","age":35,"gender":"M","address":"233 Portland Avenue","employer":"Zeam","email":"pricehaley@zeam.com","city":"Titanic","state":"UT"} +{"index":{"_id":"223"}} +{"account_number":223,"balance":9528,"firstname":"Newton","lastname":"Fletcher","age":26,"gender":"F","address":"654 Dewitt Avenue","employer":"Assistia","email":"newtonfletcher@assistia.com","city":"Nipinnawasee","state":"AK"} +{"index":{"_id":"228"}} +{"account_number":228,"balance":10543,"firstname":"Rosella","lastname":"Albert","age":20,"gender":"M","address":"185 Gotham Avenue","employer":"Isoplex","email":"rosellaalbert@isoplex.com","city":"Finzel","state":"NY"} +{"index":{"_id":"230"}} +{"account_number":230,"balance":10829,"firstname":"Chris","lastname":"Raymond","age":28,"gender":"F","address":"464 Remsen Street","employer":"Cogentry","email":"chrisraymond@cogentry.com","city":"Bowmansville","state":"SD"} +{"index":{"_id":"235"}} +{"account_number":235,"balance":17729,"firstname":"Mcpherson","lastname":"Mueller","age":31,"gender":"M","address":"541 Strong Place","employer":"Tingles","email":"mcphersonmueller@tingles.com","city":"Brantleyville","state":"AR"} +{"index":{"_id":"242"}} +{"account_number":242,"balance":42318,"firstname":"Berger","lastname":"Roach","age":21,"gender":"M","address":"125 Wakeman Place","employer":"Ovium","email":"bergerroach@ovium.com","city":"Hessville","state":"WI"} +{"index":{"_id":"247"}} +{"account_number":247,"balance":45123,"firstname":"Mccormick","lastname":"Moon","age":37,"gender":"M","address":"582 Brighton Avenue","employer":"Norsup","email":"mccormickmoon@norsup.com","city":"Forestburg","state":"DE"} +{"index":{"_id":"254"}} +{"account_number":254,"balance":35104,"firstname":"Yang","lastname":"Dodson","age":21,"gender":"M","address":"531 Lott Street","employer":"Mondicil","email":"yangdodson@mondicil.com","city":"Enoree","state":"UT"} +{"index":{"_id":"259"}} +{"account_number":259,"balance":41877,"firstname":"Eleanor","lastname":"Gonzalez","age":30,"gender":"M","address":"800 Sumpter Street","employer":"Futuris","email":"eleanorgonzalez@futuris.com","city":"Jenkinsville","state":"ID"} +{"index":{"_id":"261"}} +{"account_number":261,"balance":39998,"firstname":"Millicent","lastname":"Pickett","age":34,"gender":"F","address":"722 Montieth Street","employer":"Gushkool","email":"millicentpickett@gushkool.com","city":"Norwood","state":"MS"} +{"index":{"_id":"266"}} +{"account_number":266,"balance":2777,"firstname":"Monique","lastname":"Conner","age":35,"gender":"F","address":"489 Metrotech Courtr","employer":"Flotonic","email":"moniqueconner@flotonic.com","city":"Retsof","state":"MD"} +{"index":{"_id":"273"}} +{"account_number":273,"balance":11181,"firstname":"Murphy","lastname":"Chandler","age":20,"gender":"F","address":"569 Bradford Street","employer":"Zilch","email":"murphychandler@zilch.com","city":"Vicksburg","state":"FL"} +{"index":{"_id":"278"}} +{"account_number":278,"balance":22530,"firstname":"Tamra","lastname":"Navarro","age":27,"gender":"F","address":"175 Woodruff Avenue","employer":"Norsul","email":"tamranavarro@norsul.com","city":"Glasgow","state":"VT"} +{"index":{"_id":"280"}} +{"account_number":280,"balance":3380,"firstname":"Vilma","lastname":"Shields","age":26,"gender":"F","address":"133 Berriman Street","employer":"Applidec","email":"vilmashields@applidec.com","city":"Adamstown","state":"ME"} +{"index":{"_id":"285"}} +{"account_number":285,"balance":47369,"firstname":"Hilda","lastname":"Phillips","age":28,"gender":"F","address":"618 Nixon Court","employer":"Comcur","email":"hildaphillips@comcur.com","city":"Siglerville","state":"NC"} +{"index":{"_id":"292"}} +{"account_number":292,"balance":26679,"firstname":"Morrow","lastname":"Greene","age":20,"gender":"F","address":"691 Nassau Street","employer":"Columella","email":"morrowgreene@columella.com","city":"Sanborn","state":"FL"} +{"index":{"_id":"297"}} +{"account_number":297,"balance":20508,"firstname":"Tucker","lastname":"Patrick","age":35,"gender":"F","address":"978 Whitwell Place","employer":"Valreda","email":"tuckerpatrick@valreda.com","city":"Deseret","state":"CO"} +{"index":{"_id":"300"}} +{"account_number":300,"balance":25654,"firstname":"Lane","lastname":"Tate","age":26,"gender":"F","address":"632 Kay Court","employer":"Genesynk","email":"lanetate@genesynk.com","city":"Lowell","state":"MO"} +{"index":{"_id":"305"}} +{"account_number":305,"balance":11655,"firstname":"Augusta","lastname":"Winters","age":29,"gender":"F","address":"377 Paerdegat Avenue","employer":"Vendblend","email":"augustawinters@vendblend.com","city":"Gwynn","state":"MA"} +{"index":{"_id":"312"}} +{"account_number":312,"balance":8511,"firstname":"Burgess","lastname":"Gentry","age":25,"gender":"F","address":"382 Bergen Court","employer":"Orbixtar","email":"burgessgentry@orbixtar.com","city":"Conestoga","state":"WI"} +{"index":{"_id":"317"}} +{"account_number":317,"balance":31968,"firstname":"Ruiz","lastname":"Morris","age":31,"gender":"F","address":"972 Dean Street","employer":"Apex","email":"ruizmorris@apex.com","city":"Jacksonwald","state":"WV"} +{"index":{"_id":"324"}} +{"account_number":324,"balance":44976,"firstname":"Gladys","lastname":"Erickson","age":22,"gender":"M","address":"250 Battery Avenue","employer":"Eternis","email":"gladyserickson@eternis.com","city":"Marne","state":"IA"} +{"index":{"_id":"329"}} +{"account_number":329,"balance":31138,"firstname":"Nellie","lastname":"Mercer","age":25,"gender":"M","address":"967 Ebony Court","employer":"Scenty","email":"nelliemercer@scenty.com","city":"Jardine","state":"AK"} +{"index":{"_id":"331"}} +{"account_number":331,"balance":46004,"firstname":"Gibson","lastname":"Potts","age":34,"gender":"F","address":"994 Dahill Road","employer":"Zensus","email":"gibsonpotts@zensus.com","city":"Frizzleburg","state":"CO"} +{"index":{"_id":"336"}} +{"account_number":336,"balance":40891,"firstname":"Dudley","lastname":"Avery","age":25,"gender":"M","address":"405 Powers Street","employer":"Genmom","email":"dudleyavery@genmom.com","city":"Clarksburg","state":"CO"} +{"index":{"_id":"343"}} +{"account_number":343,"balance":37684,"firstname":"Robbie","lastname":"Logan","age":29,"gender":"M","address":"488 Linden Boulevard","employer":"Hydrocom","email":"robbielogan@hydrocom.com","city":"Stockdale","state":"TN"} +{"index":{"_id":"348"}} +{"account_number":348,"balance":1360,"firstname":"Karina","lastname":"Russell","age":37,"gender":"M","address":"797 Moffat Street","employer":"Limozen","email":"karinarussell@limozen.com","city":"Riegelwood","state":"RI"} +{"index":{"_id":"350"}} +{"account_number":350,"balance":4267,"firstname":"Wyatt","lastname":"Wise","age":22,"gender":"F","address":"896 Bleecker Street","employer":"Rockyard","email":"wyattwise@rockyard.com","city":"Joes","state":"MS"} +{"index":{"_id":"355"}} +{"account_number":355,"balance":40961,"firstname":"Gregory","lastname":"Delacruz","age":38,"gender":"M","address":"876 Cortelyou Road","employer":"Oulu","email":"gregorydelacruz@oulu.com","city":"Waterloo","state":"WV"} +{"index":{"_id":"362"}} +{"account_number":362,"balance":14938,"firstname":"Jimmie","lastname":"Dejesus","age":26,"gender":"M","address":"351 Navy Walk","employer":"Ecolight","email":"jimmiedejesus@ecolight.com","city":"Berlin","state":"ME"} +{"index":{"_id":"367"}} +{"account_number":367,"balance":40458,"firstname":"Elaine","lastname":"Workman","age":20,"gender":"M","address":"188 Ridge Boulevard","employer":"Colaire","email":"elaineworkman@colaire.com","city":"Herbster","state":"AK"} +{"index":{"_id":"374"}} +{"account_number":374,"balance":19521,"firstname":"Blanchard","lastname":"Stein","age":30,"gender":"M","address":"313 Bartlett Street","employer":"Cujo","email":"blanchardstein@cujo.com","city":"Cascades","state":"OR"} +{"index":{"_id":"379"}} +{"account_number":379,"balance":12962,"firstname":"Ruthie","lastname":"Lamb","age":21,"gender":"M","address":"796 Rockaway Avenue","employer":"Incubus","email":"ruthielamb@incubus.com","city":"Hickory","state":"TX"} +{"index":{"_id":"381"}} +{"account_number":381,"balance":40978,"firstname":"Sophie","lastname":"Mays","age":31,"gender":"M","address":"261 Varanda Place","employer":"Uneeq","email":"sophiemays@uneeq.com","city":"Cressey","state":"AR"} +{"index":{"_id":"386"}} +{"account_number":386,"balance":42588,"firstname":"Wallace","lastname":"Barr","age":39,"gender":"F","address":"246 Beverly Road","employer":"Concility","email":"wallacebarr@concility.com","city":"Durham","state":"IN"} +{"index":{"_id":"393"}} +{"account_number":393,"balance":43936,"firstname":"William","lastname":"Kelly","age":24,"gender":"M","address":"178 Lawrence Avenue","employer":"Techtrix","email":"williamkelly@techtrix.com","city":"Orin","state":"PA"} +{"index":{"_id":"398"}} +{"account_number":398,"balance":8543,"firstname":"Leticia","lastname":"Duran","age":35,"gender":"F","address":"305 Senator Street","employer":"Xleen","email":"leticiaduran@xleen.com","city":"Cavalero","state":"PA"} +{"index":{"_id":"401"}} +{"account_number":401,"balance":29408,"firstname":"Contreras","lastname":"Randolph","age":38,"gender":"M","address":"104 Lewis Avenue","employer":"Inrt","email":"contrerasrandolph@inrt.com","city":"Chesapeake","state":"CT"} +{"index":{"_id":"406"}} +{"account_number":406,"balance":28127,"firstname":"Mccarthy","lastname":"Dunlap","age":28,"gender":"F","address":"684 Seacoast Terrace","employer":"Canopoly","email":"mccarthydunlap@canopoly.com","city":"Elliott","state":"NC"} +{"index":{"_id":"413"}} +{"account_number":413,"balance":15631,"firstname":"Pugh","lastname":"Hamilton","age":39,"gender":"F","address":"124 Euclid Avenue","employer":"Techade","email":"pughhamilton@techade.com","city":"Beaulieu","state":"CA"} +{"index":{"_id":"418"}} +{"account_number":418,"balance":10207,"firstname":"Reed","lastname":"Goff","age":32,"gender":"M","address":"959 Everit Street","employer":"Zillan","email":"reedgoff@zillan.com","city":"Hiko","state":"WV"} +{"index":{"_id":"420"}} +{"account_number":420,"balance":44699,"firstname":"Brandie","lastname":"Hayden","age":22,"gender":"M","address":"291 Ash Street","employer":"Digifad","email":"brandiehayden@digifad.com","city":"Spelter","state":"NM"} +{"index":{"_id":"425"}} +{"account_number":425,"balance":41308,"firstname":"Queen","lastname":"Leach","age":30,"gender":"M","address":"105 Fair Street","employer":"Magneato","email":"queenleach@magneato.com","city":"Barronett","state":"NH"} +{"index":{"_id":"432"}} +{"account_number":432,"balance":28969,"firstname":"Preston","lastname":"Ferguson","age":40,"gender":"F","address":"239 Greenwood Avenue","employer":"Bitendrex","email":"prestonferguson@bitendrex.com","city":"Idledale","state":"ND"} +{"index":{"_id":"437"}} +{"account_number":437,"balance":41225,"firstname":"Rosales","lastname":"Marquez","age":29,"gender":"M","address":"873 Ryerson Street","employer":"Ronelon","email":"rosalesmarquez@ronelon.com","city":"Allendale","state":"CA"} +{"index":{"_id":"444"}} +{"account_number":444,"balance":44219,"firstname":"Dolly","lastname":"Finch","age":24,"gender":"F","address":"974 Interborough Parkway","employer":"Zytrac","email":"dollyfinch@zytrac.com","city":"Vowinckel","state":"WY"} +{"index":{"_id":"449"}} +{"account_number":449,"balance":41950,"firstname":"Barnett","lastname":"Cantrell","age":39,"gender":"F","address":"945 Bedell Lane","employer":"Zentility","email":"barnettcantrell@zentility.com","city":"Swartzville","state":"ND"} +{"index":{"_id":"451"}} +{"account_number":451,"balance":31950,"firstname":"Mason","lastname":"Mcleod","age":31,"gender":"F","address":"438 Havemeyer Street","employer":"Omatom","email":"masonmcleod@omatom.com","city":"Ryderwood","state":"NE"} +{"index":{"_id":"456"}} +{"account_number":456,"balance":21419,"firstname":"Solis","lastname":"Kline","age":33,"gender":"M","address":"818 Ashford Street","employer":"Vetron","email":"soliskline@vetron.com","city":"Ruffin","state":"NY"} +{"index":{"_id":"463"}} +{"account_number":463,"balance":36672,"firstname":"Heidi","lastname":"Acosta","age":20,"gender":"F","address":"692 Kenmore Terrace","employer":"Elpro","email":"heidiacosta@elpro.com","city":"Ezel","state":"SD"} +{"index":{"_id":"468"}} +{"account_number":468,"balance":18400,"firstname":"Foreman","lastname":"Fowler","age":40,"gender":"M","address":"443 Jackson Court","employer":"Zillactic","email":"foremanfowler@zillactic.com","city":"Wakarusa","state":"WA"} +{"index":{"_id":"470"}} +{"account_number":470,"balance":20455,"firstname":"Schneider","lastname":"Hull","age":35,"gender":"M","address":"724 Apollo Street","employer":"Exospeed","email":"schneiderhull@exospeed.com","city":"Watchtower","state":"ID"} +{"index":{"_id":"475"}} +{"account_number":475,"balance":24427,"firstname":"Morales","lastname":"Jacobs","age":22,"gender":"F","address":"225 Desmond Court","employer":"Oronoko","email":"moralesjacobs@oronoko.com","city":"Clayville","state":"CT"} +{"index":{"_id":"482"}} +{"account_number":482,"balance":14834,"firstname":"Janie","lastname":"Bass","age":39,"gender":"M","address":"781 Grattan Street","employer":"Manglo","email":"janiebass@manglo.com","city":"Kenwood","state":"IA"} +{"index":{"_id":"487"}} +{"account_number":487,"balance":30718,"firstname":"Sawyer","lastname":"Vincent","age":26,"gender":"F","address":"238 Lancaster Avenue","employer":"Brainquil","email":"sawyervincent@brainquil.com","city":"Galesville","state":"MS"} +{"index":{"_id":"494"}} +{"account_number":494,"balance":3592,"firstname":"Holden","lastname":"Bowen","age":30,"gender":"M","address":"374 Elmwood Avenue","employer":"Endipine","email":"holdenbowen@endipine.com","city":"Rosine","state":"ID"} +{"index":{"_id":"499"}} +{"account_number":499,"balance":26060,"firstname":"Lara","lastname":"Perkins","age":26,"gender":"M","address":"703 Monroe Street","employer":"Paprikut","email":"laraperkins@paprikut.com","city":"Barstow","state":"NY"} +{"index":{"_id":"502"}} +{"account_number":502,"balance":31898,"firstname":"Woodard","lastname":"Bailey","age":31,"gender":"F","address":"585 Albee Square","employer":"Imperium","email":"woodardbailey@imperium.com","city":"Matheny","state":"MT"} +{"index":{"_id":"507"}} +{"account_number":507,"balance":27675,"firstname":"Blankenship","lastname":"Ramirez","age":31,"gender":"M","address":"630 Graham Avenue","employer":"Bytrex","email":"blankenshipramirez@bytrex.com","city":"Bancroft","state":"CT"} +{"index":{"_id":"514"}} +{"account_number":514,"balance":30125,"firstname":"Solomon","lastname":"Bush","age":34,"gender":"M","address":"409 Harkness Avenue","employer":"Snacktion","email":"solomonbush@snacktion.com","city":"Grayhawk","state":"TX"} +{"index":{"_id":"519"}} +{"account_number":519,"balance":3282,"firstname":"Lorna","lastname":"Franco","age":31,"gender":"F","address":"722 Schenck Court","employer":"Zentia","email":"lornafranco@zentia.com","city":"National","state":"FL"} +{"index":{"_id":"521"}} +{"account_number":521,"balance":16348,"firstname":"Josefa","lastname":"Buckley","age":34,"gender":"F","address":"848 Taylor Street","employer":"Mazuda","email":"josefabuckley@mazuda.com","city":"Saranap","state":"NM"} +{"index":{"_id":"526"}} +{"account_number":526,"balance":35375,"firstname":"Sweeney","lastname":"Fulton","age":33,"gender":"F","address":"550 Martense Street","employer":"Cormoran","email":"sweeneyfulton@cormoran.com","city":"Chalfant","state":"IA"} +{"index":{"_id":"533"}} +{"account_number":533,"balance":13761,"firstname":"Margarita","lastname":"Diaz","age":23,"gender":"M","address":"295 Tapscott Street","employer":"Zilodyne","email":"margaritadiaz@zilodyne.com","city":"Hondah","state":"ID"} +{"index":{"_id":"538"}} +{"account_number":538,"balance":16416,"firstname":"Koch","lastname":"Barker","age":21,"gender":"M","address":"919 Gerry Street","employer":"Xplor","email":"kochbarker@xplor.com","city":"Dixie","state":"WY"} +{"index":{"_id":"540"}} +{"account_number":540,"balance":40235,"firstname":"Tammy","lastname":"Wiggins","age":32,"gender":"F","address":"186 Schenectady Avenue","employer":"Speedbolt","email":"tammywiggins@speedbolt.com","city":"Salvo","state":"LA"} +{"index":{"_id":"545"}} +{"account_number":545,"balance":27011,"firstname":"Lena","lastname":"Lucas","age":20,"gender":"M","address":"110 Lamont Court","employer":"Kindaloo","email":"lenalucas@kindaloo.com","city":"Harleigh","state":"KY"} +{"index":{"_id":"552"}} +{"account_number":552,"balance":14727,"firstname":"Kate","lastname":"Estes","age":39,"gender":"M","address":"785 Willmohr Street","employer":"Rodeocean","email":"kateestes@rodeocean.com","city":"Elfrida","state":"HI"} +{"index":{"_id":"557"}} +{"account_number":557,"balance":3119,"firstname":"Landry","lastname":"Buck","age":20,"gender":"M","address":"558 Schweikerts Walk","employer":"Protodyne","email":"landrybuck@protodyne.com","city":"Edneyville","state":"AL"} +{"index":{"_id":"564"}} +{"account_number":564,"balance":43631,"firstname":"Owens","lastname":"Bowers","age":22,"gender":"M","address":"842 Congress Street","employer":"Nspire","email":"owensbowers@nspire.com","city":"Machias","state":"VA"} +{"index":{"_id":"569"}} +{"account_number":569,"balance":40019,"firstname":"Sherri","lastname":"Rowe","age":39,"gender":"F","address":"591 Arlington Place","employer":"Netility","email":"sherrirowe@netility.com","city":"Bridgetown","state":"SC"} +{"index":{"_id":"571"}} +{"account_number":571,"balance":3014,"firstname":"Ayers","lastname":"Duffy","age":28,"gender":"F","address":"721 Wortman Avenue","employer":"Aquasseur","email":"ayersduffy@aquasseur.com","city":"Tilleda","state":"MS"} +{"index":{"_id":"576"}} +{"account_number":576,"balance":29682,"firstname":"Helena","lastname":"Robertson","age":33,"gender":"F","address":"774 Devon Avenue","employer":"Vicon","email":"helenarobertson@vicon.com","city":"Dyckesville","state":"NV"} +{"index":{"_id":"583"}} +{"account_number":583,"balance":26558,"firstname":"Castro","lastname":"West","age":34,"gender":"F","address":"814 Williams Avenue","employer":"Cipromox","email":"castrowest@cipromox.com","city":"Nescatunga","state":"IL"} +{"index":{"_id":"588"}} +{"account_number":588,"balance":43531,"firstname":"Martina","lastname":"Collins","age":31,"gender":"M","address":"301 Anna Court","employer":"Geekwagon","email":"martinacollins@geekwagon.com","city":"Oneida","state":"VA"} +{"index":{"_id":"590"}} +{"account_number":590,"balance":4652,"firstname":"Ladonna","lastname":"Tucker","age":31,"gender":"F","address":"162 Kane Place","employer":"Infotrips","email":"ladonnatucker@infotrips.com","city":"Utting","state":"IA"} +{"index":{"_id":"595"}} +{"account_number":595,"balance":12478,"firstname":"Mccall","lastname":"Britt","age":36,"gender":"F","address":"823 Hill Street","employer":"Cablam","email":"mccallbritt@cablam.com","city":"Vernon","state":"CA"} +{"index":{"_id":"603"}} +{"account_number":603,"balance":28145,"firstname":"Janette","lastname":"Guzman","age":31,"gender":"F","address":"976 Kingston Avenue","employer":"Splinx","email":"janetteguzman@splinx.com","city":"Boomer","state":"NC"} +{"index":{"_id":"608"}} +{"account_number":608,"balance":47091,"firstname":"Carey","lastname":"Whitley","age":32,"gender":"F","address":"976 Lawrence Street","employer":"Poshome","email":"careywhitley@poshome.com","city":"Weogufka","state":"NE"} +{"index":{"_id":"610"}} +{"account_number":610,"balance":40571,"firstname":"Foster","lastname":"Weber","age":24,"gender":"F","address":"323 Rochester Avenue","employer":"Firewax","email":"fosterweber@firewax.com","city":"Winston","state":"NY"} +{"index":{"_id":"615"}} +{"account_number":615,"balance":28726,"firstname":"Delgado","lastname":"Curry","age":28,"gender":"F","address":"706 Butler Street","employer":"Zoxy","email":"delgadocurry@zoxy.com","city":"Gracey","state":"SD"} +{"index":{"_id":"622"}} +{"account_number":622,"balance":9661,"firstname":"Paulette","lastname":"Hartman","age":38,"gender":"M","address":"375 Emerald Street","employer":"Locazone","email":"paulettehartman@locazone.com","city":"Canterwood","state":"OH"} +{"index":{"_id":"627"}} +{"account_number":627,"balance":47546,"firstname":"Crawford","lastname":"Sears","age":37,"gender":"F","address":"686 Eastern Parkway","employer":"Updat","email":"crawfordsears@updat.com","city":"Bison","state":"VT"} +{"index":{"_id":"634"}} +{"account_number":634,"balance":29805,"firstname":"Deloris","lastname":"Levy","age":38,"gender":"M","address":"838 Foster Avenue","employer":"Homelux","email":"delorislevy@homelux.com","city":"Kempton","state":"PA"} +{"index":{"_id":"639"}} +{"account_number":639,"balance":28875,"firstname":"Caitlin","lastname":"Clements","age":32,"gender":"F","address":"627 Aster Court","employer":"Bunga","email":"caitlinclements@bunga.com","city":"Cetronia","state":"SC"} +{"index":{"_id":"641"}} +{"account_number":641,"balance":18345,"firstname":"Sheppard","lastname":"Everett","age":39,"gender":"F","address":"791 Norwood Avenue","employer":"Roboid","email":"sheppardeverett@roboid.com","city":"Selma","state":"AK"} +{"index":{"_id":"646"}} +{"account_number":646,"balance":15559,"firstname":"Lavonne","lastname":"Reyes","age":31,"gender":"F","address":"983 Newport Street","employer":"Parcoe","email":"lavonnereyes@parcoe.com","city":"Monument","state":"LA"} +{"index":{"_id":"653"}} +{"account_number":653,"balance":7606,"firstname":"Marcia","lastname":"Bennett","age":33,"gender":"F","address":"455 Bragg Street","employer":"Opticall","email":"marciabennett@opticall.com","city":"Magnolia","state":"NC"} +{"index":{"_id":"658"}} +{"account_number":658,"balance":10210,"firstname":"Bass","lastname":"Mcconnell","age":32,"gender":"F","address":"274 Ocean Avenue","employer":"Combot","email":"bassmcconnell@combot.com","city":"Beyerville","state":"OH"} +{"index":{"_id":"660"}} +{"account_number":660,"balance":46427,"firstname":"Moon","lastname":"Wood","age":33,"gender":"F","address":"916 Amersfort Place","employer":"Olucore","email":"moonwood@olucore.com","city":"Como","state":"VA"} +{"index":{"_id":"665"}} +{"account_number":665,"balance":15215,"firstname":"Britney","lastname":"Young","age":36,"gender":"M","address":"766 Sackman Street","employer":"Geoforma","email":"britneyyoung@geoforma.com","city":"Tuttle","state":"WI"} +{"index":{"_id":"672"}} +{"account_number":672,"balance":12621,"firstname":"Camille","lastname":"Munoz","age":36,"gender":"F","address":"959 Lewis Place","employer":"Vantage","email":"camillemunoz@vantage.com","city":"Whitmer","state":"IN"} +{"index":{"_id":"677"}} +{"account_number":677,"balance":8491,"firstname":"Snider","lastname":"Benton","age":26,"gender":"M","address":"827 Evans Street","employer":"Medicroix","email":"sniderbenton@medicroix.com","city":"Kaka","state":"UT"} +{"index":{"_id":"684"}} +{"account_number":684,"balance":46091,"firstname":"Warren","lastname":"Snow","age":25,"gender":"M","address":"756 Oakland Place","employer":"Bizmatic","email":"warrensnow@bizmatic.com","city":"Hatteras","state":"NE"} +{"index":{"_id":"689"}} +{"account_number":689,"balance":14985,"firstname":"Ines","lastname":"Chaney","age":28,"gender":"M","address":"137 Dikeman Street","employer":"Zidant","email":"ineschaney@zidant.com","city":"Nettie","state":"DC"} +{"index":{"_id":"691"}} +{"account_number":691,"balance":10792,"firstname":"Mclean","lastname":"Colon","age":22,"gender":"M","address":"876 Classon Avenue","employer":"Elentrix","email":"mcleancolon@elentrix.com","city":"Unionville","state":"OK"} +{"index":{"_id":"696"}} +{"account_number":696,"balance":17568,"firstname":"Crane","lastname":"Matthews","age":32,"gender":"F","address":"721 Gerritsen Avenue","employer":"Intradisk","email":"cranematthews@intradisk.com","city":"Brewster","state":"WV"} +{"index":{"_id":"704"}} +{"account_number":704,"balance":45347,"firstname":"Peters","lastname":"Kent","age":22,"gender":"F","address":"871 Independence Avenue","employer":"Extragen","email":"peterskent@extragen.com","city":"Morriston","state":"CA"} +{"index":{"_id":"709"}} +{"account_number":709,"balance":11015,"firstname":"Abbott","lastname":"Odom","age":29,"gender":"M","address":"893 Union Street","employer":"Jimbies","email":"abbottodom@jimbies.com","city":"Leeper","state":"NJ"} +{"index":{"_id":"711"}} +{"account_number":711,"balance":26939,"firstname":"Villarreal","lastname":"Horton","age":35,"gender":"F","address":"861 Creamer Street","employer":"Lexicondo","email":"villarrealhorton@lexicondo.com","city":"Lydia","state":"MS"} +{"index":{"_id":"716"}} +{"account_number":716,"balance":19789,"firstname":"Paul","lastname":"Mason","age":34,"gender":"F","address":"618 Nichols Avenue","employer":"Slax","email":"paulmason@slax.com","city":"Snowville","state":"OK"} +{"index":{"_id":"723"}} +{"account_number":723,"balance":16421,"firstname":"Nixon","lastname":"Moran","age":27,"gender":"M","address":"569 Campus Place","employer":"Cuizine","email":"nixonmoran@cuizine.com","city":"Buxton","state":"DC"} +{"index":{"_id":"728"}} +{"account_number":728,"balance":44818,"firstname":"Conley","lastname":"Preston","age":28,"gender":"M","address":"450 Coventry Road","employer":"Obones","email":"conleypreston@obones.com","city":"Alden","state":"CO"} +{"index":{"_id":"730"}} +{"account_number":730,"balance":41299,"firstname":"Moore","lastname":"Lee","age":30,"gender":"M","address":"797 Turner Place","employer":"Orbean","email":"moorelee@orbean.com","city":"Highland","state":"DE"} +{"index":{"_id":"735"}} +{"account_number":735,"balance":3984,"firstname":"Loraine","lastname":"Willis","age":32,"gender":"F","address":"928 Grove Street","employer":"Gadtron","email":"lorainewillis@gadtron.com","city":"Lowgap","state":"NY"} +{"index":{"_id":"742"}} +{"account_number":742,"balance":24765,"firstname":"Merle","lastname":"Wooten","age":26,"gender":"M","address":"317 Pooles Lane","employer":"Tropolis","email":"merlewooten@tropolis.com","city":"Bentley","state":"ND"} +{"index":{"_id":"747"}} +{"account_number":747,"balance":16617,"firstname":"Diaz","lastname":"Austin","age":38,"gender":"M","address":"676 Harway Avenue","employer":"Irack","email":"diazaustin@irack.com","city":"Cliff","state":"HI"} +{"index":{"_id":"754"}} +{"account_number":754,"balance":10779,"firstname":"Jones","lastname":"Vega","age":25,"gender":"F","address":"795 India Street","employer":"Gluid","email":"jonesvega@gluid.com","city":"Tyhee","state":"FL"} +{"index":{"_id":"759"}} +{"account_number":759,"balance":38007,"firstname":"Rose","lastname":"Carlson","age":27,"gender":"M","address":"987 Navy Street","employer":"Aquasure","email":"rosecarlson@aquasure.com","city":"Carlton","state":"CT"} +{"index":{"_id":"761"}} +{"account_number":761,"balance":7663,"firstname":"Rae","lastname":"Juarez","age":34,"gender":"F","address":"560 Gilmore Court","employer":"Entropix","email":"raejuarez@entropix.com","city":"Northchase","state":"ID"} +{"index":{"_id":"766"}} +{"account_number":766,"balance":21957,"firstname":"Thomas","lastname":"Gillespie","age":38,"gender":"M","address":"993 Williams Place","employer":"Octocore","email":"thomasgillespie@octocore.com","city":"Defiance","state":"MS"} +{"index":{"_id":"773"}} +{"account_number":773,"balance":31126,"firstname":"Liza","lastname":"Coffey","age":36,"gender":"F","address":"540 Bulwer Place","employer":"Assurity","email":"lizacoffey@assurity.com","city":"Gilgo","state":"WV"} +{"index":{"_id":"778"}} +{"account_number":778,"balance":46007,"firstname":"Underwood","lastname":"Wheeler","age":28,"gender":"M","address":"477 Provost Street","employer":"Decratex","email":"underwoodwheeler@decratex.com","city":"Sardis","state":"ID"} +{"index":{"_id":"780"}} +{"account_number":780,"balance":4682,"firstname":"Maryanne","lastname":"Hendricks","age":26,"gender":"F","address":"709 Wolcott Street","employer":"Sarasonic","email":"maryannehendricks@sarasonic.com","city":"Santel","state":"NH"} +{"index":{"_id":"785"}} +{"account_number":785,"balance":25078,"firstname":"Fields","lastname":"Lester","age":29,"gender":"M","address":"808 Chestnut Avenue","employer":"Visualix","email":"fieldslester@visualix.com","city":"Rowe","state":"PA"} +{"index":{"_id":"792"}} +{"account_number":792,"balance":13109,"firstname":"Becky","lastname":"Jimenez","age":40,"gender":"F","address":"539 Front Street","employer":"Isologia","email":"beckyjimenez@isologia.com","city":"Summertown","state":"MI"} +{"index":{"_id":"797"}} +{"account_number":797,"balance":6854,"firstname":"Lindsay","lastname":"Mills","age":26,"gender":"F","address":"919 Quay Street","employer":"Zoinage","email":"lindsaymills@zoinage.com","city":"Elliston","state":"VA"} +{"index":{"_id":"800"}} +{"account_number":800,"balance":26217,"firstname":"Candy","lastname":"Oconnor","age":28,"gender":"M","address":"200 Newel Street","employer":"Radiantix","email":"candyoconnor@radiantix.com","city":"Sandston","state":"OH"} +{"index":{"_id":"805"}} +{"account_number":805,"balance":18426,"firstname":"Jackson","lastname":"Sampson","age":27,"gender":"F","address":"722 Kenmore Court","employer":"Daido","email":"jacksonsampson@daido.com","city":"Bellamy","state":"ME"} +{"index":{"_id":"812"}} +{"account_number":812,"balance":42593,"firstname":"Graves","lastname":"Newman","age":32,"gender":"F","address":"916 Joralemon Street","employer":"Ecrater","email":"gravesnewman@ecrater.com","city":"Crown","state":"PA"} +{"index":{"_id":"817"}} +{"account_number":817,"balance":36582,"firstname":"Padilla","lastname":"Bauer","age":36,"gender":"F","address":"310 Cadman Plaza","employer":"Exoblue","email":"padillabauer@exoblue.com","city":"Ahwahnee","state":"MN"} +{"index":{"_id":"824"}} +{"account_number":824,"balance":6053,"firstname":"Dyer","lastname":"Henson","age":33,"gender":"M","address":"650 Seaview Avenue","employer":"Nitracyr","email":"dyerhenson@nitracyr.com","city":"Gibsonia","state":"KS"} +{"index":{"_id":"829"}} +{"account_number":829,"balance":20263,"firstname":"Althea","lastname":"Bell","age":37,"gender":"M","address":"319 Cook Street","employer":"Hyplex","email":"altheabell@hyplex.com","city":"Wadsworth","state":"DC"} +{"index":{"_id":"831"}} +{"account_number":831,"balance":25375,"firstname":"Wendy","lastname":"Savage","age":37,"gender":"M","address":"421 Veranda Place","employer":"Neurocell","email":"wendysavage@neurocell.com","city":"Fresno","state":"MS"} +{"index":{"_id":"836"}} +{"account_number":836,"balance":20797,"firstname":"Lloyd","lastname":"Lindsay","age":25,"gender":"F","address":"953 Dinsmore Place","employer":"Suretech","email":"lloydlindsay@suretech.com","city":"Conway","state":"VA"} +{"index":{"_id":"843"}} +{"account_number":843,"balance":15555,"firstname":"Patricia","lastname":"Barton","age":34,"gender":"F","address":"406 Seabring Street","employer":"Providco","email":"patriciabarton@providco.com","city":"Avoca","state":"RI"} +{"index":{"_id":"848"}} +{"account_number":848,"balance":15443,"firstname":"Carmella","lastname":"Cash","age":38,"gender":"M","address":"988 Exeter Street","employer":"Bristo","email":"carmellacash@bristo.com","city":"Northridge","state":"ID"} +{"index":{"_id":"850"}} +{"account_number":850,"balance":6531,"firstname":"Carlene","lastname":"Gaines","age":37,"gender":"F","address":"753 Monroe Place","employer":"Naxdis","email":"carlenegaines@naxdis.com","city":"Genoa","state":"OR"} +{"index":{"_id":"855"}} +{"account_number":855,"balance":40170,"firstname":"Mia","lastname":"Stevens","age":31,"gender":"F","address":"326 Driggs Avenue","employer":"Aeora","email":"miastevens@aeora.com","city":"Delwood","state":"IL"} +{"index":{"_id":"862"}} +{"account_number":862,"balance":38792,"firstname":"Clayton","lastname":"Golden","age":38,"gender":"F","address":"620 Regent Place","employer":"Accusage","email":"claytongolden@accusage.com","city":"Ona","state":"NC"} +{"index":{"_id":"867"}} +{"account_number":867,"balance":45453,"firstname":"Blanca","lastname":"Ellison","age":23,"gender":"F","address":"593 McKibben Street","employer":"Koogle","email":"blancaellison@koogle.com","city":"Frystown","state":"WY"} +{"index":{"_id":"874"}} +{"account_number":874,"balance":23079,"firstname":"Lynette","lastname":"Higgins","age":22,"gender":"M","address":"377 McKinley Avenue","employer":"Menbrain","email":"lynettehiggins@menbrain.com","city":"Manitou","state":"TX"} +{"index":{"_id":"879"}} +{"account_number":879,"balance":48332,"firstname":"Sabrina","lastname":"Lancaster","age":31,"gender":"F","address":"382 Oak Street","employer":"Webiotic","email":"sabrinalancaster@webiotic.com","city":"Lindisfarne","state":"AZ"} +{"index":{"_id":"881"}} +{"account_number":881,"balance":26684,"firstname":"Barnes","lastname":"Ware","age":38,"gender":"F","address":"666 Hooper Street","employer":"Norali","email":"barnesware@norali.com","city":"Cazadero","state":"GA"} +{"index":{"_id":"886"}} +{"account_number":886,"balance":14867,"firstname":"Willa","lastname":"Leblanc","age":38,"gender":"F","address":"773 Bergen Street","employer":"Nurali","email":"willaleblanc@nurali.com","city":"Hilltop","state":"NC"} +{"index":{"_id":"893"}} +{"account_number":893,"balance":42584,"firstname":"Moses","lastname":"Campos","age":38,"gender":"F","address":"991 Bevy Court","employer":"Trollery","email":"mosescampos@trollery.com","city":"Freetown","state":"AK"} +{"index":{"_id":"898"}} +{"account_number":898,"balance":12019,"firstname":"Lori","lastname":"Stevenson","age":29,"gender":"M","address":"910 Coles Street","employer":"Honotron","email":"loristevenson@honotron.com","city":"Shindler","state":"VT"} +{"index":{"_id":"901"}} +{"account_number":901,"balance":35038,"firstname":"Irma","lastname":"Dotson","age":23,"gender":"F","address":"245 Mayfair Drive","employer":"Bleeko","email":"irmadotson@bleeko.com","city":"Lodoga","state":"UT"} +{"index":{"_id":"906"}} +{"account_number":906,"balance":24073,"firstname":"Vicki","lastname":"Suarez","age":36,"gender":"M","address":"829 Roosevelt Place","employer":"Utara","email":"vickisuarez@utara.com","city":"Albrightsville","state":"AR"} +{"index":{"_id":"913"}} +{"account_number":913,"balance":47657,"firstname":"Margery","lastname":"Monroe","age":25,"gender":"M","address":"941 Fanchon Place","employer":"Exerta","email":"margerymonroe@exerta.com","city":"Bannock","state":"MD"} +{"index":{"_id":"918"}} +{"account_number":918,"balance":36776,"firstname":"Dianna","lastname":"Hernandez","age":25,"gender":"M","address":"499 Moultrie Street","employer":"Isologica","email":"diannahernandez@isologica.com","city":"Falconaire","state":"ID"} +{"index":{"_id":"920"}} +{"account_number":920,"balance":41513,"firstname":"Jerri","lastname":"Mitchell","age":26,"gender":"M","address":"831 Kent Street","employer":"Tasmania","email":"jerrimitchell@tasmania.com","city":"Cotopaxi","state":"IA"} +{"index":{"_id":"925"}} +{"account_number":925,"balance":18295,"firstname":"Rosario","lastname":"Jackson","age":24,"gender":"M","address":"178 Leonora Court","employer":"Progenex","email":"rosariojackson@progenex.com","city":"Rivereno","state":"DE"} +{"index":{"_id":"932"}} +{"account_number":932,"balance":3111,"firstname":"Summer","lastname":"Porter","age":33,"gender":"F","address":"949 Grand Avenue","employer":"Multiflex","email":"summerporter@multiflex.com","city":"Spokane","state":"OK"} +{"index":{"_id":"937"}} +{"account_number":937,"balance":43491,"firstname":"Selma","lastname":"Anderson","age":24,"gender":"M","address":"205 Reed Street","employer":"Dadabase","email":"selmaanderson@dadabase.com","city":"Malo","state":"AL"} +{"index":{"_id":"944"}} +{"account_number":944,"balance":46478,"firstname":"Donaldson","lastname":"Woodard","age":38,"gender":"F","address":"498 Laurel Avenue","employer":"Zogak","email":"donaldsonwoodard@zogak.com","city":"Hasty","state":"ID"} +{"index":{"_id":"949"}} +{"account_number":949,"balance":48703,"firstname":"Latasha","lastname":"Mullins","age":29,"gender":"F","address":"272 Lefferts Place","employer":"Zenolux","email":"latashamullins@zenolux.com","city":"Kieler","state":"MN"} +{"index":{"_id":"951"}} +{"account_number":951,"balance":36337,"firstname":"Tran","lastname":"Burris","age":25,"gender":"F","address":"561 Rutland Road","employer":"Geoform","email":"tranburris@geoform.com","city":"Longbranch","state":"IL"} +{"index":{"_id":"956"}} +{"account_number":956,"balance":19477,"firstname":"Randall","lastname":"Lynch","age":22,"gender":"F","address":"490 Madison Place","employer":"Cosmetex","email":"randalllynch@cosmetex.com","city":"Wells","state":"SD"} +{"index":{"_id":"963"}} +{"account_number":963,"balance":30461,"firstname":"Griffin","lastname":"Sheppard","age":20,"gender":"M","address":"682 Linden Street","employer":"Zanymax","email":"griffinsheppard@zanymax.com","city":"Fannett","state":"NM"} +{"index":{"_id":"968"}} +{"account_number":968,"balance":32371,"firstname":"Luella","lastname":"Burch","age":39,"gender":"M","address":"684 Arkansas Drive","employer":"Krag","email":"luellaburch@krag.com","city":"Brambleton","state":"SD"} +{"index":{"_id":"970"}} +{"account_number":970,"balance":19648,"firstname":"Forbes","lastname":"Wallace","age":28,"gender":"M","address":"990 Mill Road","employer":"Pheast","email":"forbeswallace@pheast.com","city":"Lopezo","state":"AK"} +{"index":{"_id":"975"}} +{"account_number":975,"balance":5239,"firstname":"Delores","lastname":"Booker","age":27,"gender":"F","address":"328 Conselyea Street","employer":"Centice","email":"deloresbooker@centice.com","city":"Williams","state":"HI"} +{"index":{"_id":"982"}} +{"account_number":982,"balance":16511,"firstname":"Buck","lastname":"Robinson","age":24,"gender":"M","address":"301 Melrose Street","employer":"Calcu","email":"buckrobinson@calcu.com","city":"Welch","state":"PA"} +{"index":{"_id":"987"}} +{"account_number":987,"balance":4072,"firstname":"Brock","lastname":"Sandoval","age":20,"gender":"F","address":"977 Gem Street","employer":"Fiberox","email":"brocksandoval@fiberox.com","city":"Celeryville","state":"NY"} +{"index":{"_id":"994"}} +{"account_number":994,"balance":33298,"firstname":"Madge","lastname":"Holcomb","age":31,"gender":"M","address":"612 Hawthorne Street","employer":"Escenta","email":"madgeholcomb@escenta.com","city":"Alafaya","state":"OR"} +{"index":{"_id":"999"}} +{"account_number":999,"balance":6087,"firstname":"Dorothy","lastname":"Barron","age":22,"gender":"F","address":"499 Laurel Avenue","employer":"Xurban","email":"dorothybarron@xurban.com","city":"Belvoir","state":"CA"} +{"index":{"_id":"4"}} +{"account_number":4,"balance":27658,"firstname":"Rodriquez","lastname":"Flores","age":31,"gender":"F","address":"986 Wyckoff Avenue","employer":"Tourmania","email":"rodriquezflores@tourmania.com","city":"Eastvale","state":"HI"} +{"index":{"_id":"9"}} +{"account_number":9,"balance":24776,"firstname":"Opal","lastname":"Meadows","age":39,"gender":"M","address":"963 Neptune Avenue","employer":"Cedward","email":"opalmeadows@cedward.com","city":"Olney","state":"OH"} +{"index":{"_id":"11"}} +{"account_number":11,"balance":20203,"firstname":"Jenkins","lastname":"Haney","age":20,"gender":"M","address":"740 Ferry Place","employer":"Qimonk","email":"jenkinshaney@qimonk.com","city":"Steinhatchee","state":"GA"} +{"index":{"_id":"16"}} +{"account_number":16,"balance":35883,"firstname":"Adrian","lastname":"Pitts","age":34,"gender":"F","address":"963 Fay Court","employer":"Combogene","email":"adrianpitts@combogene.com","city":"Remington","state":"SD"} +{"index":{"_id":"23"}} +{"account_number":23,"balance":42374,"firstname":"Kirsten","lastname":"Fox","age":20,"gender":"M","address":"330 Dumont Avenue","employer":"Codax","email":"kirstenfox@codax.com","city":"Walton","state":"AK"} +{"index":{"_id":"28"}} +{"account_number":28,"balance":42112,"firstname":"Vega","lastname":"Flynn","age":20,"gender":"M","address":"647 Hyman Court","employer":"Accupharm","email":"vegaflynn@accupharm.com","city":"Masthope","state":"OH"} +{"index":{"_id":"30"}} +{"account_number":30,"balance":19087,"firstname":"Lamb","lastname":"Townsend","age":26,"gender":"M","address":"169 Lyme Avenue","employer":"Geeknet","email":"lambtownsend@geeknet.com","city":"Epworth","state":"AL"} +{"index":{"_id":"35"}} +{"account_number":35,"balance":42039,"firstname":"Darla","lastname":"Bridges","age":27,"gender":"F","address":"315 Central Avenue","employer":"Xeronk","email":"darlabridges@xeronk.com","city":"Woodlake","state":"RI"} +{"index":{"_id":"42"}} +{"account_number":42,"balance":21137,"firstname":"Harding","lastname":"Hobbs","age":26,"gender":"F","address":"474 Ridgewood Place","employer":"Xth","email":"hardinghobbs@xth.com","city":"Heil","state":"ND"} +{"index":{"_id":"47"}} +{"account_number":47,"balance":33044,"firstname":"Georgia","lastname":"Wilkerson","age":23,"gender":"M","address":"369 Herbert Street","employer":"Endipin","email":"georgiawilkerson@endipin.com","city":"Dellview","state":"WI"} +{"index":{"_id":"54"}} +{"account_number":54,"balance":23406,"firstname":"Angel","lastname":"Mann","age":22,"gender":"F","address":"229 Ferris Street","employer":"Amtas","email":"angelmann@amtas.com","city":"Calverton","state":"WA"} +{"index":{"_id":"59"}} +{"account_number":59,"balance":37728,"firstname":"Malone","lastname":"Justice","age":37,"gender":"F","address":"721 Russell Street","employer":"Emoltra","email":"malonejustice@emoltra.com","city":"Trucksville","state":"HI"} +{"index":{"_id":"61"}} +{"account_number":61,"balance":6856,"firstname":"Shawn","lastname":"Baird","age":20,"gender":"M","address":"605 Monument Walk","employer":"Moltonic","email":"shawnbaird@moltonic.com","city":"Darlington","state":"MN"} +{"index":{"_id":"66"}} +{"account_number":66,"balance":25939,"firstname":"Franks","lastname":"Salinas","age":28,"gender":"M","address":"437 Hamilton Walk","employer":"Cowtown","email":"frankssalinas@cowtown.com","city":"Chase","state":"VT"} +{"index":{"_id":"73"}} +{"account_number":73,"balance":33457,"firstname":"Irene","lastname":"Stephenson","age":32,"gender":"M","address":"684 Miller Avenue","employer":"Hawkster","email":"irenestephenson@hawkster.com","city":"Levant","state":"AR"} +{"index":{"_id":"78"}} +{"account_number":78,"balance":48656,"firstname":"Elvira","lastname":"Patterson","age":23,"gender":"F","address":"834 Amber Street","employer":"Assistix","email":"elvirapatterson@assistix.com","city":"Dunbar","state":"TN"} +{"index":{"_id":"80"}} +{"account_number":80,"balance":13445,"firstname":"Lacey","lastname":"Blanchard","age":30,"gender":"F","address":"823 Himrod Street","employer":"Comdom","email":"laceyblanchard@comdom.com","city":"Matthews","state":"MO"} +{"index":{"_id":"85"}} +{"account_number":85,"balance":48735,"firstname":"Wilcox","lastname":"Sellers","age":20,"gender":"M","address":"212 Irving Avenue","employer":"Confrenzy","email":"wilcoxsellers@confrenzy.com","city":"Kipp","state":"MT"} +{"index":{"_id":"92"}} +{"account_number":92,"balance":26753,"firstname":"Gay","lastname":"Brewer","age":34,"gender":"M","address":"369 Ditmars Street","employer":"Savvy","email":"gaybrewer@savvy.com","city":"Moquino","state":"HI"} +{"index":{"_id":"97"}} +{"account_number":97,"balance":49671,"firstname":"Karen","lastname":"Trujillo","age":40,"gender":"F","address":"512 Cumberland Walk","employer":"Tsunamia","email":"karentrujillo@tsunamia.com","city":"Fredericktown","state":"MO"} +{"index":{"_id":"100"}} +{"account_number":100,"balance":29869,"firstname":"Madden","lastname":"Woods","age":32,"gender":"F","address":"696 Ryder Avenue","employer":"Slumberia","email":"maddenwoods@slumberia.com","city":"Deercroft","state":"ME"} +{"index":{"_id":"105"}} +{"account_number":105,"balance":29654,"firstname":"Castillo","lastname":"Dickerson","age":33,"gender":"F","address":"673 Oxford Street","employer":"Tellifly","email":"castillodickerson@tellifly.com","city":"Succasunna","state":"NY"} +{"index":{"_id":"112"}} +{"account_number":112,"balance":38395,"firstname":"Frederick","lastname":"Case","age":30,"gender":"F","address":"580 Lexington Avenue","employer":"Talkalot","email":"frederickcase@talkalot.com","city":"Orovada","state":"MA"} +{"index":{"_id":"117"}} +{"account_number":117,"balance":48831,"firstname":"Robin","lastname":"Hays","age":38,"gender":"F","address":"347 Hornell Loop","employer":"Pasturia","email":"robinhays@pasturia.com","city":"Sims","state":"WY"} +{"index":{"_id":"124"}} +{"account_number":124,"balance":16425,"firstname":"Fern","lastname":"Lambert","age":20,"gender":"M","address":"511 Jay Street","employer":"Furnitech","email":"fernlambert@furnitech.com","city":"Cloverdale","state":"FL"} +{"index":{"_id":"129"}} +{"account_number":129,"balance":42409,"firstname":"Alexandria","lastname":"Sanford","age":33,"gender":"F","address":"934 Ridgecrest Terrace","employer":"Kyagoro","email":"alexandriasanford@kyagoro.com","city":"Concho","state":"UT"} +{"index":{"_id":"131"}} +{"account_number":131,"balance":28030,"firstname":"Dollie","lastname":"Koch","age":22,"gender":"F","address":"287 Manhattan Avenue","employer":"Skinserve","email":"dolliekoch@skinserve.com","city":"Shasta","state":"PA"} +{"index":{"_id":"136"}} +{"account_number":136,"balance":45801,"firstname":"Winnie","lastname":"Holland","age":38,"gender":"M","address":"198 Mill Lane","employer":"Neteria","email":"winnieholland@neteria.com","city":"Urie","state":"IL"} +{"index":{"_id":"143"}} +{"account_number":143,"balance":43093,"firstname":"Cohen","lastname":"Noble","age":39,"gender":"M","address":"454 Nelson Street","employer":"Buzzworks","email":"cohennoble@buzzworks.com","city":"Norvelt","state":"CO"} +{"index":{"_id":"148"}} +{"account_number":148,"balance":3662,"firstname":"Annmarie","lastname":"Snider","age":34,"gender":"F","address":"857 Lafayette Walk","employer":"Edecine","email":"annmariesnider@edecine.com","city":"Hollins","state":"OH"} +{"index":{"_id":"150"}} +{"account_number":150,"balance":15306,"firstname":"Ortega","lastname":"Dalton","age":20,"gender":"M","address":"237 Mermaid Avenue","employer":"Rameon","email":"ortegadalton@rameon.com","city":"Maxville","state":"NH"} +{"index":{"_id":"155"}} +{"account_number":155,"balance":27878,"firstname":"Atkinson","lastname":"Hudson","age":39,"gender":"F","address":"434 Colin Place","employer":"Qualitern","email":"atkinsonhudson@qualitern.com","city":"Hoehne","state":"OH"} +{"index":{"_id":"162"}} +{"account_number":162,"balance":6302,"firstname":"Griffith","lastname":"Calderon","age":35,"gender":"M","address":"871 Vandervoort Place","employer":"Quotezart","email":"griffithcalderon@quotezart.com","city":"Barclay","state":"FL"} +{"index":{"_id":"167"}} +{"account_number":167,"balance":42051,"firstname":"Hampton","lastname":"Ryan","age":20,"gender":"M","address":"618 Fleet Place","employer":"Zipak","email":"hamptonryan@zipak.com","city":"Irwin","state":"KS"} +{"index":{"_id":"174"}} +{"account_number":174,"balance":1464,"firstname":"Gamble","lastname":"Pierce","age":23,"gender":"F","address":"650 Eagle Street","employer":"Matrixity","email":"gamblepierce@matrixity.com","city":"Abiquiu","state":"OR"} +{"index":{"_id":"179"}} +{"account_number":179,"balance":13265,"firstname":"Elise","lastname":"Drake","age":25,"gender":"M","address":"305 Christopher Avenue","employer":"Turnling","email":"elisedrake@turnling.com","city":"Loretto","state":"LA"} +{"index":{"_id":"181"}} +{"account_number":181,"balance":27983,"firstname":"Bennett","lastname":"Hampton","age":22,"gender":"F","address":"435 Billings Place","employer":"Voipa","email":"bennetthampton@voipa.com","city":"Rodman","state":"WY"} +{"index":{"_id":"186"}} +{"account_number":186,"balance":18373,"firstname":"Kline","lastname":"Joyce","age":32,"gender":"M","address":"285 Falmouth Street","employer":"Tetratrex","email":"klinejoyce@tetratrex.com","city":"Klondike","state":"SD"} +{"index":{"_id":"193"}} +{"account_number":193,"balance":13412,"firstname":"Patty","lastname":"Petty","age":34,"gender":"F","address":"251 Vermont Street","employer":"Kinetica","email":"pattypetty@kinetica.com","city":"Grantville","state":"MS"} +{"index":{"_id":"198"}} +{"account_number":198,"balance":19686,"firstname":"Rachael","lastname":"Sharp","age":38,"gender":"F","address":"443 Vernon Avenue","employer":"Powernet","email":"rachaelsharp@powernet.com","city":"Canoochee","state":"UT"} +{"index":{"_id":"201"}} +{"account_number":201,"balance":14586,"firstname":"Ronda","lastname":"Perry","age":25,"gender":"F","address":"856 Downing Street","employer":"Artiq","email":"rondaperry@artiq.com","city":"Colton","state":"WV"} +{"index":{"_id":"206"}} +{"account_number":206,"balance":47423,"firstname":"Kelli","lastname":"Francis","age":20,"gender":"M","address":"671 George Street","employer":"Exoswitch","email":"kellifrancis@exoswitch.com","city":"Babb","state":"NJ"} +{"index":{"_id":"213"}} +{"account_number":213,"balance":34172,"firstname":"Bauer","lastname":"Summers","age":27,"gender":"M","address":"257 Boynton Place","employer":"Voratak","email":"bauersummers@voratak.com","city":"Oceola","state":"NC"} +{"index":{"_id":"218"}} +{"account_number":218,"balance":26702,"firstname":"Garrison","lastname":"Bryan","age":24,"gender":"F","address":"478 Greenpoint Avenue","employer":"Uniworld","email":"garrisonbryan@uniworld.com","city":"Comptche","state":"WI"} +{"index":{"_id":"220"}} +{"account_number":220,"balance":3086,"firstname":"Tania","lastname":"Middleton","age":22,"gender":"F","address":"541 Gunther Place","employer":"Zerology","email":"taniamiddleton@zerology.com","city":"Linwood","state":"IN"} +{"index":{"_id":"225"}} +{"account_number":225,"balance":21949,"firstname":"Maryann","lastname":"Murphy","age":24,"gender":"F","address":"894 Bridgewater Street","employer":"Cinesanct","email":"maryannmurphy@cinesanct.com","city":"Cartwright","state":"RI"} +{"index":{"_id":"232"}} +{"account_number":232,"balance":11984,"firstname":"Carr","lastname":"Jensen","age":34,"gender":"F","address":"995 Micieli Place","employer":"Biohab","email":"carrjensen@biohab.com","city":"Waikele","state":"OH"} +{"index":{"_id":"237"}} +{"account_number":237,"balance":5603,"firstname":"Kirby","lastname":"Watkins","age":27,"gender":"F","address":"348 Blake Court","employer":"Sonique","email":"kirbywatkins@sonique.com","city":"Freelandville","state":"PA"} +{"index":{"_id":"244"}} +{"account_number":244,"balance":8048,"firstname":"Judith","lastname":"Riggs","age":27,"gender":"F","address":"590 Kosciusko Street","employer":"Arctiq","email":"judithriggs@arctiq.com","city":"Gorham","state":"DC"} +{"index":{"_id":"249"}} +{"account_number":249,"balance":16822,"firstname":"Mckinney","lastname":"Gallagher","age":38,"gender":"F","address":"939 Seigel Court","employer":"Premiant","email":"mckinneygallagher@premiant.com","city":"Catharine","state":"NH"} +{"index":{"_id":"251"}} +{"account_number":251,"balance":13475,"firstname":"Marks","lastname":"Graves","age":39,"gender":"F","address":"427 Lawn Court","employer":"Dentrex","email":"marksgraves@dentrex.com","city":"Waukeenah","state":"IL"} +{"index":{"_id":"256"}} +{"account_number":256,"balance":48318,"firstname":"Simon","lastname":"Hogan","age":31,"gender":"M","address":"789 Suydam Place","employer":"Dancerity","email":"simonhogan@dancerity.com","city":"Dargan","state":"GA"} +{"index":{"_id":"263"}} +{"account_number":263,"balance":12837,"firstname":"Thornton","lastname":"Meyer","age":29,"gender":"M","address":"575 Elliott Place","employer":"Peticular","email":"thorntonmeyer@peticular.com","city":"Dotsero","state":"NH"} +{"index":{"_id":"268"}} +{"account_number":268,"balance":20925,"firstname":"Avis","lastname":"Blackwell","age":36,"gender":"M","address":"569 Jerome Avenue","employer":"Magnina","email":"avisblackwell@magnina.com","city":"Bethany","state":"MD"} +{"index":{"_id":"270"}} +{"account_number":270,"balance":43951,"firstname":"Moody","lastname":"Harmon","age":39,"gender":"F","address":"233 Vanderbilt Street","employer":"Otherside","email":"moodyharmon@otherside.com","city":"Elwood","state":"MT"} +{"index":{"_id":"275"}} +{"account_number":275,"balance":2384,"firstname":"Reynolds","lastname":"Barnett","age":31,"gender":"M","address":"394 Stockton Street","employer":"Austex","email":"reynoldsbarnett@austex.com","city":"Grandview","state":"MS"} +{"index":{"_id":"282"}} +{"account_number":282,"balance":38540,"firstname":"Gay","lastname":"Schultz","age":25,"gender":"F","address":"805 Claver Place","employer":"Handshake","email":"gayschultz@handshake.com","city":"Tampico","state":"MA"} +{"index":{"_id":"287"}} +{"account_number":287,"balance":10845,"firstname":"Valerie","lastname":"Lang","age":35,"gender":"F","address":"423 Midwood Street","employer":"Quarx","email":"valerielang@quarx.com","city":"Cannondale","state":"VT"} +{"index":{"_id":"294"}} +{"account_number":294,"balance":29582,"firstname":"Pitts","lastname":"Haynes","age":26,"gender":"M","address":"901 Broome Street","employer":"Aquazure","email":"pittshaynes@aquazure.com","city":"Turah","state":"SD"} +{"index":{"_id":"299"}} +{"account_number":299,"balance":40825,"firstname":"Angela","lastname":"Talley","age":36,"gender":"F","address":"822 Bills Place","employer":"Remold","email":"angelatalley@remold.com","city":"Bethpage","state":"DC"} +{"index":{"_id":"302"}} +{"account_number":302,"balance":11298,"firstname":"Isabella","lastname":"Hewitt","age":40,"gender":"M","address":"455 Bedford Avenue","employer":"Cincyr","email":"isabellahewitt@cincyr.com","city":"Blanford","state":"IN"} +{"index":{"_id":"307"}} +{"account_number":307,"balance":43355,"firstname":"Enid","lastname":"Ashley","age":23,"gender":"M","address":"412 Emerson Place","employer":"Avenetro","email":"enidashley@avenetro.com","city":"Catherine","state":"WI"} +{"index":{"_id":"314"}} +{"account_number":314,"balance":5848,"firstname":"Norton","lastname":"Norton","age":35,"gender":"M","address":"252 Ditmas Avenue","employer":"Talkola","email":"nortonnorton@talkola.com","city":"Veyo","state":"SC"} +{"index":{"_id":"319"}} +{"account_number":319,"balance":15430,"firstname":"Ferrell","lastname":"Mckinney","age":36,"gender":"M","address":"874 Cranberry Street","employer":"Portaline","email":"ferrellmckinney@portaline.com","city":"Rose","state":"WV"} +{"index":{"_id":"321"}} +{"account_number":321,"balance":43370,"firstname":"Marta","lastname":"Larsen","age":35,"gender":"M","address":"617 Williams Court","employer":"Manufact","email":"martalarsen@manufact.com","city":"Sisquoc","state":"MA"} +{"index":{"_id":"326"}} +{"account_number":326,"balance":9692,"firstname":"Pearl","lastname":"Reese","age":30,"gender":"F","address":"451 Colonial Court","employer":"Accruex","email":"pearlreese@accruex.com","city":"Westmoreland","state":"MD"} +{"index":{"_id":"333"}} +{"account_number":333,"balance":22778,"firstname":"Trudy","lastname":"Sweet","age":27,"gender":"F","address":"881 Kiely Place","employer":"Acumentor","email":"trudysweet@acumentor.com","city":"Kent","state":"IA"} +{"index":{"_id":"338"}} +{"account_number":338,"balance":6969,"firstname":"Pierce","lastname":"Lawrence","age":35,"gender":"M","address":"318 Gallatin Place","employer":"Lunchpad","email":"piercelawrence@lunchpad.com","city":"Iola","state":"MD"} +{"index":{"_id":"340"}} +{"account_number":340,"balance":42072,"firstname":"Juarez","lastname":"Gutierrez","age":40,"gender":"F","address":"802 Seba Avenue","employer":"Billmed","email":"juarezgutierrez@billmed.com","city":"Malott","state":"OH"} +{"index":{"_id":"345"}} +{"account_number":345,"balance":9812,"firstname":"Parker","lastname":"Hines","age":38,"gender":"M","address":"715 Mill Avenue","employer":"Baluba","email":"parkerhines@baluba.com","city":"Blackgum","state":"KY"} +{"index":{"_id":"352"}} +{"account_number":352,"balance":20290,"firstname":"Kendra","lastname":"Mcintosh","age":31,"gender":"F","address":"963 Wolf Place","employer":"Orboid","email":"kendramcintosh@orboid.com","city":"Bladensburg","state":"AK"} +{"index":{"_id":"357"}} +{"account_number":357,"balance":15102,"firstname":"Adele","lastname":"Carroll","age":39,"gender":"F","address":"381 Arion Place","employer":"Aquafire","email":"adelecarroll@aquafire.com","city":"Springville","state":"RI"} +{"index":{"_id":"364"}} +{"account_number":364,"balance":35247,"firstname":"Felicia","lastname":"Merrill","age":40,"gender":"F","address":"229 Branton Street","employer":"Prosely","email":"feliciamerrill@prosely.com","city":"Dola","state":"MA"} +{"index":{"_id":"369"}} +{"account_number":369,"balance":17047,"firstname":"Mcfadden","lastname":"Guy","age":28,"gender":"F","address":"445 Lott Avenue","employer":"Kangle","email":"mcfaddenguy@kangle.com","city":"Greenbackville","state":"DE"} +{"index":{"_id":"371"}} +{"account_number":371,"balance":19751,"firstname":"Barker","lastname":"Allen","age":32,"gender":"F","address":"295 Wallabout Street","employer":"Nexgene","email":"barkerallen@nexgene.com","city":"Nanafalia","state":"NE"} +{"index":{"_id":"376"}} +{"account_number":376,"balance":44407,"firstname":"Mcmillan","lastname":"Dunn","age":21,"gender":"F","address":"771 Dorchester Road","employer":"Eargo","email":"mcmillandunn@eargo.com","city":"Yogaville","state":"RI"} +{"index":{"_id":"383"}} +{"account_number":383,"balance":48889,"firstname":"Knox","lastname":"Larson","age":28,"gender":"F","address":"962 Bartlett Place","employer":"Bostonic","email":"knoxlarson@bostonic.com","city":"Smeltertown","state":"TX"} +{"index":{"_id":"388"}} +{"account_number":388,"balance":9606,"firstname":"Julianne","lastname":"Nicholson","age":26,"gender":"F","address":"338 Crescent Street","employer":"Viasia","email":"juliannenicholson@viasia.com","city":"Alleghenyville","state":"MO"} +{"index":{"_id":"390"}} +{"account_number":390,"balance":7464,"firstname":"Ramona","lastname":"Roy","age":32,"gender":"M","address":"135 Banner Avenue","employer":"Deminimum","email":"ramonaroy@deminimum.com","city":"Dodge","state":"ID"} +{"index":{"_id":"395"}} +{"account_number":395,"balance":18679,"firstname":"Juliet","lastname":"Whitaker","age":31,"gender":"M","address":"128 Remsen Avenue","employer":"Toyletry","email":"julietwhitaker@toyletry.com","city":"Yonah","state":"LA"} +{"index":{"_id":"403"}} +{"account_number":403,"balance":18833,"firstname":"Williamson","lastname":"Horn","age":32,"gender":"M","address":"223 Strickland Avenue","employer":"Nimon","email":"williamsonhorn@nimon.com","city":"Bawcomville","state":"NJ"} +{"index":{"_id":"408"}} +{"account_number":408,"balance":34666,"firstname":"Lidia","lastname":"Guerrero","age":30,"gender":"M","address":"254 Stratford Road","employer":"Snowpoke","email":"lidiaguerrero@snowpoke.com","city":"Fairlee","state":"LA"} +{"index":{"_id":"410"}} +{"account_number":410,"balance":31200,"firstname":"Fox","lastname":"Cardenas","age":39,"gender":"M","address":"987 Monitor Street","employer":"Corpulse","email":"foxcardenas@corpulse.com","city":"Southview","state":"NE"} +{"index":{"_id":"415"}} +{"account_number":415,"balance":19449,"firstname":"Martinez","lastname":"Benson","age":36,"gender":"M","address":"172 Berkeley Place","employer":"Enersol","email":"martinezbenson@enersol.com","city":"Chumuckla","state":"AL"} +{"index":{"_id":"422"}} +{"account_number":422,"balance":40162,"firstname":"Brigitte","lastname":"Scott","age":26,"gender":"M","address":"662 Vermont Court","employer":"Waretel","email":"brigittescott@waretel.com","city":"Elrama","state":"VA"} +{"index":{"_id":"427"}} +{"account_number":427,"balance":1463,"firstname":"Rebekah","lastname":"Garrison","age":36,"gender":"F","address":"837 Hampton Avenue","employer":"Niquent","email":"rebekahgarrison@niquent.com","city":"Zarephath","state":"NY"} +{"index":{"_id":"434"}} +{"account_number":434,"balance":11329,"firstname":"Christa","lastname":"Huff","age":25,"gender":"M","address":"454 Oriental Boulevard","employer":"Earthpure","email":"christahuff@earthpure.com","city":"Stevens","state":"DC"} +{"index":{"_id":"439"}} +{"account_number":439,"balance":22752,"firstname":"Lula","lastname":"Williams","age":35,"gender":"M","address":"630 Furman Avenue","employer":"Vinch","email":"lulawilliams@vinch.com","city":"Newcastle","state":"ME"} +{"index":{"_id":"441"}} +{"account_number":441,"balance":47947,"firstname":"Dickson","lastname":"Mcgee","age":29,"gender":"M","address":"478 Knight Court","employer":"Gogol","email":"dicksonmcgee@gogol.com","city":"Laurelton","state":"AR"} +{"index":{"_id":"446"}} +{"account_number":446,"balance":23071,"firstname":"Lolita","lastname":"Fleming","age":32,"gender":"F","address":"918 Bridge Street","employer":"Vidto","email":"lolitafleming@vidto.com","city":"Brownlee","state":"HI"} +{"index":{"_id":"453"}} +{"account_number":453,"balance":21520,"firstname":"Hood","lastname":"Powell","age":24,"gender":"F","address":"479 Brevoort Place","employer":"Vortexaco","email":"hoodpowell@vortexaco.com","city":"Alderpoint","state":"CT"} +{"index":{"_id":"458"}} +{"account_number":458,"balance":8865,"firstname":"Aida","lastname":"Wolf","age":21,"gender":"F","address":"403 Thames Street","employer":"Isis","email":"aidawolf@isis.com","city":"Bordelonville","state":"ME"} +{"index":{"_id":"460"}} +{"account_number":460,"balance":37734,"firstname":"Aguirre","lastname":"White","age":21,"gender":"F","address":"190 Crooke Avenue","employer":"Unq","email":"aguirrewhite@unq.com","city":"Albany","state":"NJ"} +{"index":{"_id":"465"}} +{"account_number":465,"balance":10681,"firstname":"Pearlie","lastname":"Holman","age":29,"gender":"M","address":"916 Evergreen Avenue","employer":"Hometown","email":"pearlieholman@hometown.com","city":"Needmore","state":"UT"} +{"index":{"_id":"472"}} +{"account_number":472,"balance":25571,"firstname":"Lee","lastname":"Long","age":32,"gender":"F","address":"288 Mill Street","employer":"Comverges","email":"leelong@comverges.com","city":"Movico","state":"MT"} +{"index":{"_id":"477"}} +{"account_number":477,"balance":25892,"firstname":"Holcomb","lastname":"Cobb","age":40,"gender":"M","address":"369 Marconi Place","employer":"Steeltab","email":"holcombcobb@steeltab.com","city":"Byrnedale","state":"CA"} +{"index":{"_id":"484"}} +{"account_number":484,"balance":3274,"firstname":"Staci","lastname":"Melendez","age":35,"gender":"F","address":"751 Otsego Street","employer":"Namebox","email":"stacimelendez@namebox.com","city":"Harborton","state":"NV"} +{"index":{"_id":"489"}} +{"account_number":489,"balance":7879,"firstname":"Garrett","lastname":"Langley","age":36,"gender":"M","address":"331 Bowne Street","employer":"Zillidium","email":"garrettlangley@zillidium.com","city":"Riviera","state":"LA"} +{"index":{"_id":"491"}} +{"account_number":491,"balance":42942,"firstname":"Teresa","lastname":"Owen","age":24,"gender":"F","address":"713 Canton Court","employer":"Plasmos","email":"teresaowen@plasmos.com","city":"Bartonsville","state":"NH"} +{"index":{"_id":"496"}} +{"account_number":496,"balance":14869,"firstname":"Alison","lastname":"Conrad","age":35,"gender":"F","address":"347 Varet Street","employer":"Perkle","email":"alisonconrad@perkle.com","city":"Cliffside","state":"OH"} +{"index":{"_id":"504"}} +{"account_number":504,"balance":49205,"firstname":"Shanna","lastname":"Chambers","age":23,"gender":"M","address":"220 Beard Street","employer":"Corporana","email":"shannachambers@corporana.com","city":"Cashtown","state":"AZ"} +{"index":{"_id":"509"}} +{"account_number":509,"balance":34754,"firstname":"Durham","lastname":"Pacheco","age":40,"gender":"M","address":"129 Plymouth Street","employer":"Datacator","email":"durhampacheco@datacator.com","city":"Loveland","state":"NC"} +{"index":{"_id":"511"}} +{"account_number":511,"balance":40908,"firstname":"Elba","lastname":"Grant","age":24,"gender":"F","address":"157 Bijou Avenue","employer":"Dognost","email":"elbagrant@dognost.com","city":"Coyote","state":"MT"} +{"index":{"_id":"516"}} +{"account_number":516,"balance":44940,"firstname":"Roy","lastname":"Smith","age":37,"gender":"M","address":"770 Cherry Street","employer":"Parleynet","email":"roysmith@parleynet.com","city":"Carrsville","state":"RI"} +{"index":{"_id":"523"}} +{"account_number":523,"balance":28729,"firstname":"Amalia","lastname":"Benjamin","age":40,"gender":"F","address":"173 Bushwick Place","employer":"Sentia","email":"amaliabenjamin@sentia.com","city":"Jacumba","state":"OK"} +{"index":{"_id":"528"}} +{"account_number":528,"balance":4071,"firstname":"Thompson","lastname":"Hoover","age":27,"gender":"F","address":"580 Garden Street","employer":"Portalis","email":"thompsonhoover@portalis.com","city":"Knowlton","state":"AL"} +{"index":{"_id":"530"}} +{"account_number":530,"balance":8840,"firstname":"Kathrine","lastname":"Evans","age":37,"gender":"M","address":"422 Division Place","employer":"Spherix","email":"kathrineevans@spherix.com","city":"Biddle","state":"CO"} +{"index":{"_id":"535"}} +{"account_number":535,"balance":8715,"firstname":"Fry","lastname":"George","age":34,"gender":"M","address":"722 Green Street","employer":"Ewaves","email":"frygeorge@ewaves.com","city":"Kenmar","state":"DE"} +{"index":{"_id":"542"}} +{"account_number":542,"balance":23285,"firstname":"Michelle","lastname":"Mayo","age":35,"gender":"M","address":"657 Caton Place","employer":"Biflex","email":"michellemayo@biflex.com","city":"Beaverdale","state":"WY"} +{"index":{"_id":"547"}} +{"account_number":547,"balance":12870,"firstname":"Eaton","lastname":"Rios","age":32,"gender":"M","address":"744 Withers Street","employer":"Podunk","email":"eatonrios@podunk.com","city":"Chelsea","state":"IA"} +{"index":{"_id":"554"}} +{"account_number":554,"balance":33163,"firstname":"Townsend","lastname":"Atkins","age":39,"gender":"M","address":"566 Ira Court","employer":"Acruex","email":"townsendatkins@acruex.com","city":"Valle","state":"IA"} +{"index":{"_id":"559"}} +{"account_number":559,"balance":11450,"firstname":"Tonia","lastname":"Schmidt","age":38,"gender":"F","address":"508 Sheffield Avenue","employer":"Extro","email":"toniaschmidt@extro.com","city":"Newry","state":"CT"} +{"index":{"_id":"561"}} +{"account_number":561,"balance":12370,"firstname":"Sellers","lastname":"Davis","age":30,"gender":"M","address":"860 Madoc Avenue","employer":"Isodrive","email":"sellersdavis@isodrive.com","city":"Trail","state":"KS"} +{"index":{"_id":"566"}} +{"account_number":566,"balance":6183,"firstname":"Cox","lastname":"Roman","age":37,"gender":"M","address":"349 Winthrop Street","employer":"Medcom","email":"coxroman@medcom.com","city":"Rosewood","state":"WY"} +{"index":{"_id":"573"}} +{"account_number":573,"balance":32171,"firstname":"Callie","lastname":"Castaneda","age":36,"gender":"M","address":"799 Scott Avenue","employer":"Earthwax","email":"calliecastaneda@earthwax.com","city":"Marshall","state":"NH"} +{"index":{"_id":"578"}} +{"account_number":578,"balance":34259,"firstname":"Holmes","lastname":"Mcknight","age":37,"gender":"M","address":"969 Metropolitan Avenue","employer":"Cubicide","email":"holmesmcknight@cubicide.com","city":"Aguila","state":"PA"} +{"index":{"_id":"580"}} +{"account_number":580,"balance":13716,"firstname":"Mcmahon","lastname":"York","age":34,"gender":"M","address":"475 Beacon Court","employer":"Zillar","email":"mcmahonyork@zillar.com","city":"Farmington","state":"MO"} +{"index":{"_id":"585"}} +{"account_number":585,"balance":26745,"firstname":"Nieves","lastname":"Nolan","age":32,"gender":"M","address":"115 Seagate Terrace","employer":"Jumpstack","email":"nievesnolan@jumpstack.com","city":"Eastmont","state":"UT"} +{"index":{"_id":"592"}} +{"account_number":592,"balance":32968,"firstname":"Head","lastname":"Webster","age":36,"gender":"F","address":"987 Lefferts Avenue","employer":"Empirica","email":"headwebster@empirica.com","city":"Rockingham","state":"TN"} +{"index":{"_id":"597"}} +{"account_number":597,"balance":11246,"firstname":"Penny","lastname":"Knowles","age":33,"gender":"M","address":"139 Forbell Street","employer":"Ersum","email":"pennyknowles@ersum.com","city":"Vallonia","state":"IA"} +{"index":{"_id":"600"}} +{"account_number":600,"balance":10336,"firstname":"Simmons","lastname":"Byers","age":37,"gender":"M","address":"250 Dictum Court","employer":"Qualitex","email":"simmonsbyers@qualitex.com","city":"Wanship","state":"OH"} +{"index":{"_id":"605"}} +{"account_number":605,"balance":38427,"firstname":"Mcclain","lastname":"Manning","age":24,"gender":"M","address":"832 Leonard Street","employer":"Qiao","email":"mcclainmanning@qiao.com","city":"Calvary","state":"TX"} +{"index":{"_id":"612"}} +{"account_number":612,"balance":11868,"firstname":"Dunn","lastname":"Cameron","age":32,"gender":"F","address":"156 Lorimer Street","employer":"Isonus","email":"dunncameron@isonus.com","city":"Virgie","state":"ND"} +{"index":{"_id":"617"}} +{"account_number":617,"balance":35445,"firstname":"Kitty","lastname":"Cooley","age":22,"gender":"M","address":"788 Seagate Avenue","employer":"Ultrimax","email":"kittycooley@ultrimax.com","city":"Clarktown","state":"MD"} +{"index":{"_id":"624"}} +{"account_number":624,"balance":27538,"firstname":"Roxanne","lastname":"Franklin","age":39,"gender":"F","address":"299 Woodrow Court","employer":"Silodyne","email":"roxannefranklin@silodyne.com","city":"Roulette","state":"VA"} +{"index":{"_id":"629"}} +{"account_number":629,"balance":32987,"firstname":"Mcclure","lastname":"Rodgers","age":26,"gender":"M","address":"806 Pierrepont Place","employer":"Elita","email":"mcclurerodgers@elita.com","city":"Brownsville","state":"MI"} +{"index":{"_id":"631"}} +{"account_number":631,"balance":21657,"firstname":"Corrine","lastname":"Barber","age":32,"gender":"F","address":"447 Hunts Lane","employer":"Quarmony","email":"corrinebarber@quarmony.com","city":"Wyano","state":"IL"} +{"index":{"_id":"636"}} +{"account_number":636,"balance":8036,"firstname":"Agnes","lastname":"Hooper","age":25,"gender":"M","address":"865 Hanson Place","employer":"Digial","email":"agneshooper@digial.com","city":"Sperryville","state":"OK"} +{"index":{"_id":"643"}} +{"account_number":643,"balance":8057,"firstname":"Hendricks","lastname":"Stokes","age":23,"gender":"F","address":"142 Barbey Street","employer":"Remotion","email":"hendricksstokes@remotion.com","city":"Lewis","state":"MA"} +{"index":{"_id":"648"}} +{"account_number":648,"balance":11506,"firstname":"Terry","lastname":"Montgomery","age":21,"gender":"F","address":"115 Franklin Avenue","employer":"Enervate","email":"terrymontgomery@enervate.com","city":"Bascom","state":"MA"} +{"index":{"_id":"650"}} +{"account_number":650,"balance":18091,"firstname":"Benton","lastname":"Knight","age":28,"gender":"F","address":"850 Aitken Place","employer":"Pholio","email":"bentonknight@pholio.com","city":"Cobbtown","state":"AL"} +{"index":{"_id":"655"}} +{"account_number":655,"balance":22912,"firstname":"Eula","lastname":"Taylor","age":30,"gender":"M","address":"520 Orient Avenue","employer":"Miracula","email":"eulataylor@miracula.com","city":"Wacissa","state":"IN"} +{"index":{"_id":"662"}} +{"account_number":662,"balance":10138,"firstname":"Daisy","lastname":"Burnett","age":33,"gender":"M","address":"114 Norman Avenue","employer":"Liquicom","email":"daisyburnett@liquicom.com","city":"Grahamtown","state":"MD"} +{"index":{"_id":"667"}} +{"account_number":667,"balance":22559,"firstname":"Juliana","lastname":"Chase","age":32,"gender":"M","address":"496 Coleridge Street","employer":"Comtract","email":"julianachase@comtract.com","city":"Wilsonia","state":"NJ"} +{"index":{"_id":"674"}} +{"account_number":674,"balance":36038,"firstname":"Watts","lastname":"Shannon","age":22,"gender":"F","address":"600 Story Street","employer":"Joviold","email":"wattsshannon@joviold.com","city":"Fairhaven","state":"ID"} +{"index":{"_id":"679"}} +{"account_number":679,"balance":20149,"firstname":"Henrietta","lastname":"Bonner","age":33,"gender":"M","address":"461 Bond Street","employer":"Geekol","email":"henriettabonner@geekol.com","city":"Richville","state":"WA"} +{"index":{"_id":"681"}} +{"account_number":681,"balance":34244,"firstname":"Velazquez","lastname":"Wolfe","age":33,"gender":"M","address":"773 Eckford Street","employer":"Zisis","email":"velazquezwolfe@zisis.com","city":"Smock","state":"ME"} +{"index":{"_id":"686"}} +{"account_number":686,"balance":10116,"firstname":"Decker","lastname":"Mcclure","age":30,"gender":"F","address":"236 Commerce Street","employer":"Everest","email":"deckermcclure@everest.com","city":"Gibbsville","state":"TN"} +{"index":{"_id":"693"}} +{"account_number":693,"balance":31233,"firstname":"Tabatha","lastname":"Zimmerman","age":30,"gender":"F","address":"284 Emmons Avenue","employer":"Pushcart","email":"tabathazimmerman@pushcart.com","city":"Esmont","state":"NC"} +{"index":{"_id":"698"}} +{"account_number":698,"balance":14965,"firstname":"Baker","lastname":"Armstrong","age":36,"gender":"F","address":"796 Tehama Street","employer":"Nurplex","email":"bakerarmstrong@nurplex.com","city":"Starks","state":"UT"} +{"index":{"_id":"701"}} +{"account_number":701,"balance":23772,"firstname":"Gardner","lastname":"Griffith","age":27,"gender":"M","address":"187 Moore Place","employer":"Vertide","email":"gardnergriffith@vertide.com","city":"Coventry","state":"NV"} +{"index":{"_id":"706"}} +{"account_number":706,"balance":5282,"firstname":"Eliza","lastname":"Potter","age":39,"gender":"M","address":"945 Dunham Place","employer":"Playce","email":"elizapotter@playce.com","city":"Woodruff","state":"AK"} +{"index":{"_id":"713"}} +{"account_number":713,"balance":20054,"firstname":"Iris","lastname":"Mcguire","age":21,"gender":"F","address":"508 Benson Avenue","employer":"Duflex","email":"irismcguire@duflex.com","city":"Hillsboro","state":"MO"} +{"index":{"_id":"718"}} +{"account_number":718,"balance":13876,"firstname":"Hickman","lastname":"Dillard","age":22,"gender":"F","address":"132 Etna Street","employer":"Genmy","email":"hickmandillard@genmy.com","city":"Curtice","state":"NV"} +{"index":{"_id":"720"}} +{"account_number":720,"balance":31356,"firstname":"Ruth","lastname":"Vance","age":32,"gender":"F","address":"229 Adams Street","employer":"Zilidium","email":"ruthvance@zilidium.com","city":"Allison","state":"IA"} +{"index":{"_id":"725"}} +{"account_number":725,"balance":14677,"firstname":"Reeves","lastname":"Tillman","age":26,"gender":"M","address":"674 Ivan Court","employer":"Cemention","email":"reevestillman@cemention.com","city":"Navarre","state":"MA"} +{"index":{"_id":"732"}} +{"account_number":732,"balance":38445,"firstname":"Delia","lastname":"Cruz","age":37,"gender":"F","address":"870 Cheever Place","employer":"Multron","email":"deliacruz@multron.com","city":"Cresaptown","state":"NH"} +{"index":{"_id":"737"}} +{"account_number":737,"balance":40431,"firstname":"Sampson","lastname":"Yates","age":23,"gender":"F","address":"214 Cox Place","employer":"Signidyne","email":"sampsonyates@signidyne.com","city":"Brazos","state":"GA"} +{"index":{"_id":"744"}} +{"account_number":744,"balance":8690,"firstname":"Bernard","lastname":"Martinez","age":21,"gender":"M","address":"148 Dunne Place","employer":"Dragbot","email":"bernardmartinez@dragbot.com","city":"Moraida","state":"MN"} +{"index":{"_id":"749"}} +{"account_number":749,"balance":1249,"firstname":"Rush","lastname":"Boyle","age":36,"gender":"M","address":"310 Argyle Road","employer":"Sportan","email":"rushboyle@sportan.com","city":"Brady","state":"WA"} +{"index":{"_id":"751"}} +{"account_number":751,"balance":49252,"firstname":"Patrick","lastname":"Osborne","age":23,"gender":"M","address":"915 Prospect Avenue","employer":"Gynko","email":"patrickosborne@gynko.com","city":"Takilma","state":"MO"} +{"index":{"_id":"756"}} +{"account_number":756,"balance":40006,"firstname":"Jasmine","lastname":"Howell","age":32,"gender":"M","address":"605 Elliott Walk","employer":"Ecratic","email":"jasminehowell@ecratic.com","city":"Harrodsburg","state":"OH"} +{"index":{"_id":"763"}} +{"account_number":763,"balance":12091,"firstname":"Liz","lastname":"Bentley","age":22,"gender":"F","address":"933 Debevoise Avenue","employer":"Nipaz","email":"lizbentley@nipaz.com","city":"Glenville","state":"NJ"} +{"index":{"_id":"768"}} +{"account_number":768,"balance":2213,"firstname":"Sondra","lastname":"Soto","age":21,"gender":"M","address":"625 Colonial Road","employer":"Navir","email":"sondrasoto@navir.com","city":"Benson","state":"VA"} +{"index":{"_id":"770"}} +{"account_number":770,"balance":39505,"firstname":"Joann","lastname":"Crane","age":26,"gender":"M","address":"798 Farragut Place","employer":"Lingoage","email":"joanncrane@lingoage.com","city":"Kirk","state":"MA"} +{"index":{"_id":"775"}} +{"account_number":775,"balance":27943,"firstname":"Wilson","lastname":"Merritt","age":33,"gender":"F","address":"288 Thornton Street","employer":"Geeky","email":"wilsonmerritt@geeky.com","city":"Holtville","state":"HI"} +{"index":{"_id":"782"}} +{"account_number":782,"balance":3960,"firstname":"Maldonado","lastname":"Craig","age":36,"gender":"F","address":"345 Myrtle Avenue","employer":"Zilencio","email":"maldonadocraig@zilencio.com","city":"Yukon","state":"ID"} +{"index":{"_id":"787"}} +{"account_number":787,"balance":11876,"firstname":"Harper","lastname":"Wynn","age":21,"gender":"F","address":"139 Oceanic Avenue","employer":"Interfind","email":"harperwynn@interfind.com","city":"Gerber","state":"ND"} +{"index":{"_id":"794"}} +{"account_number":794,"balance":16491,"firstname":"Walker","lastname":"Charles","age":32,"gender":"M","address":"215 Kenilworth Place","employer":"Orbin","email":"walkercharles@orbin.com","city":"Rivers","state":"WI"} +{"index":{"_id":"799"}} +{"account_number":799,"balance":2889,"firstname":"Myra","lastname":"Guerra","age":28,"gender":"F","address":"625 Dahlgreen Place","employer":"Digigene","email":"myraguerra@digigene.com","city":"Draper","state":"CA"} +{"index":{"_id":"802"}} +{"account_number":802,"balance":19630,"firstname":"Gracie","lastname":"Foreman","age":40,"gender":"F","address":"219 Kent Avenue","employer":"Supportal","email":"gracieforeman@supportal.com","city":"Westboro","state":"NH"} +{"index":{"_id":"807"}} +{"account_number":807,"balance":29206,"firstname":"Hatfield","lastname":"Lowe","age":23,"gender":"M","address":"499 Adler Place","employer":"Lovepad","email":"hatfieldlowe@lovepad.com","city":"Wiscon","state":"DC"} +{"index":{"_id":"814"}} +{"account_number":814,"balance":9838,"firstname":"Morse","lastname":"Mcbride","age":26,"gender":"F","address":"776 Calyer Street","employer":"Inear","email":"morsemcbride@inear.com","city":"Kingstowne","state":"ND"} +{"index":{"_id":"819"}} +{"account_number":819,"balance":3971,"firstname":"Karyn","lastname":"Medina","age":24,"gender":"F","address":"417 Utica Avenue","employer":"Qnekt","email":"karynmedina@qnekt.com","city":"Kerby","state":"WY"} +{"index":{"_id":"821"}} +{"account_number":821,"balance":33271,"firstname":"Trisha","lastname":"Blankenship","age":22,"gender":"M","address":"329 Jamaica Avenue","employer":"Chorizon","email":"trishablankenship@chorizon.com","city":"Sexton","state":"VT"} +{"index":{"_id":"826"}} +{"account_number":826,"balance":11548,"firstname":"Summers","lastname":"Vinson","age":22,"gender":"F","address":"742 Irwin Street","employer":"Globoil","email":"summersvinson@globoil.com","city":"Callaghan","state":"WY"} +{"index":{"_id":"833"}} +{"account_number":833,"balance":46154,"firstname":"Woodward","lastname":"Hood","age":22,"gender":"M","address":"398 Atkins Avenue","employer":"Zedalis","email":"woodwardhood@zedalis.com","city":"Stonybrook","state":"NE"} +{"index":{"_id":"838"}} +{"account_number":838,"balance":24629,"firstname":"Latonya","lastname":"Blake","age":37,"gender":"F","address":"531 Milton Street","employer":"Rugstars","email":"latonyablake@rugstars.com","city":"Tedrow","state":"WA"} +{"index":{"_id":"840"}} +{"account_number":840,"balance":39615,"firstname":"Boone","lastname":"Gomez","age":38,"gender":"M","address":"256 Hampton Place","employer":"Geekular","email":"boonegomez@geekular.com","city":"Westerville","state":"HI"} +{"index":{"_id":"845"}} +{"account_number":845,"balance":35422,"firstname":"Tracy","lastname":"Vaughn","age":39,"gender":"M","address":"645 Rockaway Parkway","employer":"Andryx","email":"tracyvaughn@andryx.com","city":"Wilmington","state":"ME"} +{"index":{"_id":"852"}} +{"account_number":852,"balance":6041,"firstname":"Allen","lastname":"Hammond","age":26,"gender":"M","address":"793 Essex Street","employer":"Tersanki","email":"allenhammond@tersanki.com","city":"Osmond","state":"NC"} +{"index":{"_id":"857"}} +{"account_number":857,"balance":39678,"firstname":"Alyce","lastname":"Douglas","age":23,"gender":"M","address":"326 Robert Street","employer":"Earbang","email":"alycedouglas@earbang.com","city":"Thornport","state":"GA"} +{"index":{"_id":"864"}} +{"account_number":864,"balance":21804,"firstname":"Duffy","lastname":"Anthony","age":23,"gender":"M","address":"582 Cooke Court","employer":"Schoolio","email":"duffyanthony@schoolio.com","city":"Brenton","state":"CO"} +{"index":{"_id":"869"}} +{"account_number":869,"balance":43544,"firstname":"Corinne","lastname":"Robbins","age":25,"gender":"F","address":"732 Quentin Road","employer":"Orbaxter","email":"corinnerobbins@orbaxter.com","city":"Roy","state":"TN"} +{"index":{"_id":"871"}} +{"account_number":871,"balance":35854,"firstname":"Norma","lastname":"Burt","age":32,"gender":"M","address":"934 Cyrus Avenue","employer":"Magnafone","email":"normaburt@magnafone.com","city":"Eden","state":"TN"} +{"index":{"_id":"876"}} +{"account_number":876,"balance":48568,"firstname":"Brady","lastname":"Glover","age":21,"gender":"F","address":"565 Oceanview Avenue","employer":"Comvex","email":"bradyglover@comvex.com","city":"Noblestown","state":"ID"} +{"index":{"_id":"883"}} +{"account_number":883,"balance":33679,"firstname":"Austin","lastname":"Jefferson","age":34,"gender":"M","address":"846 Lincoln Avenue","employer":"Polarax","email":"austinjefferson@polarax.com","city":"Savannah","state":"CT"} +{"index":{"_id":"888"}} +{"account_number":888,"balance":22277,"firstname":"Myrna","lastname":"Herman","age":39,"gender":"F","address":"649 Harwood Place","employer":"Enthaze","email":"myrnaherman@enthaze.com","city":"Idamay","state":"AR"} +{"index":{"_id":"890"}} +{"account_number":890,"balance":31198,"firstname":"Alvarado","lastname":"Pate","age":25,"gender":"M","address":"269 Ashland Place","employer":"Ovolo","email":"alvaradopate@ovolo.com","city":"Volta","state":"MI"} +{"index":{"_id":"895"}} +{"account_number":895,"balance":7327,"firstname":"Lara","lastname":"Mcdaniel","age":36,"gender":"M","address":"854 Willow Place","employer":"Acusage","email":"laramcdaniel@acusage.com","city":"Imperial","state":"NC"} +{"index":{"_id":"903"}} +{"account_number":903,"balance":10238,"firstname":"Wade","lastname":"Page","age":35,"gender":"F","address":"685 Waldorf Court","employer":"Eplosion","email":"wadepage@eplosion.com","city":"Welda","state":"AL"} +{"index":{"_id":"908"}} +{"account_number":908,"balance":45975,"firstname":"Mosley","lastname":"Holloway","age":31,"gender":"M","address":"929 Eldert Lane","employer":"Anivet","email":"mosleyholloway@anivet.com","city":"Biehle","state":"MS"} +{"index":{"_id":"910"}} +{"account_number":910,"balance":36831,"firstname":"Esmeralda","lastname":"James","age":23,"gender":"F","address":"535 High Street","employer":"Terrasys","email":"esmeraldajames@terrasys.com","city":"Dubois","state":"IN"} +{"index":{"_id":"915"}} +{"account_number":915,"balance":19816,"firstname":"Farrell","lastname":"French","age":35,"gender":"F","address":"126 McKibbin Street","employer":"Techmania","email":"farrellfrench@techmania.com","city":"Wescosville","state":"AL"} +{"index":{"_id":"922"}} +{"account_number":922,"balance":39347,"firstname":"Irwin","lastname":"Pugh","age":32,"gender":"M","address":"463 Shale Street","employer":"Idego","email":"irwinpugh@idego.com","city":"Ivanhoe","state":"ID"} +{"index":{"_id":"927"}} +{"account_number":927,"balance":19976,"firstname":"Jeanette","lastname":"Acevedo","age":26,"gender":"M","address":"694 Polhemus Place","employer":"Halap","email":"jeanetteacevedo@halap.com","city":"Harrison","state":"MO"} +{"index":{"_id":"934"}} +{"account_number":934,"balance":43987,"firstname":"Freida","lastname":"Daniels","age":34,"gender":"M","address":"448 Cove Lane","employer":"Vurbo","email":"freidadaniels@vurbo.com","city":"Snelling","state":"NJ"} +{"index":{"_id":"939"}} +{"account_number":939,"balance":31228,"firstname":"Hodges","lastname":"Massey","age":37,"gender":"F","address":"431 Dahl Court","employer":"Kegular","email":"hodgesmassey@kegular.com","city":"Katonah","state":"MD"} +{"index":{"_id":"941"}} +{"account_number":941,"balance":38796,"firstname":"Kim","lastname":"Moss","age":28,"gender":"F","address":"105 Onderdonk Avenue","employer":"Digirang","email":"kimmoss@digirang.com","city":"Centerville","state":"TX"} +{"index":{"_id":"946"}} +{"account_number":946,"balance":42794,"firstname":"Ina","lastname":"Obrien","age":36,"gender":"M","address":"339 Rewe Street","employer":"Eclipsent","email":"inaobrien@eclipsent.com","city":"Soham","state":"RI"} +{"index":{"_id":"953"}} +{"account_number":953,"balance":1110,"firstname":"Baxter","lastname":"Black","age":27,"gender":"M","address":"720 Stillwell Avenue","employer":"Uplinx","email":"baxterblack@uplinx.com","city":"Drummond","state":"MN"} +{"index":{"_id":"958"}} +{"account_number":958,"balance":32849,"firstname":"Brown","lastname":"Wilkins","age":40,"gender":"M","address":"686 Delmonico Place","employer":"Medesign","email":"brownwilkins@medesign.com","city":"Shelby","state":"WY"} +{"index":{"_id":"960"}} +{"account_number":960,"balance":2905,"firstname":"Curry","lastname":"Vargas","age":40,"gender":"M","address":"242 Blake Avenue","employer":"Pearlesex","email":"curryvargas@pearlesex.com","city":"Henrietta","state":"NH"} +{"index":{"_id":"965"}} +{"account_number":965,"balance":21882,"firstname":"Patrica","lastname":"Melton","age":28,"gender":"M","address":"141 Rodney Street","employer":"Flexigen","email":"patricamelton@flexigen.com","city":"Klagetoh","state":"MD"} +{"index":{"_id":"972"}} +{"account_number":972,"balance":24719,"firstname":"Leona","lastname":"Christian","age":26,"gender":"F","address":"900 Woodpoint Road","employer":"Extrawear","email":"leonachristian@extrawear.com","city":"Roderfield","state":"MA"} +{"index":{"_id":"977"}} +{"account_number":977,"balance":6744,"firstname":"Rodgers","lastname":"Mccray","age":21,"gender":"F","address":"612 Duryea Place","employer":"Papricut","email":"rodgersmccray@papricut.com","city":"Marenisco","state":"MD"} +{"index":{"_id":"984"}} +{"account_number":984,"balance":1904,"firstname":"Viola","lastname":"Crawford","age":35,"gender":"F","address":"354 Linwood Street","employer":"Ginkle","email":"violacrawford@ginkle.com","city":"Witmer","state":"AR"} +{"index":{"_id":"989"}} +{"account_number":989,"balance":48622,"firstname":"Franklin","lastname":"Frank","age":38,"gender":"M","address":"270 Carlton Avenue","employer":"Shopabout","email":"franklinfrank@shopabout.com","city":"Guthrie","state":"NC"} +{"index":{"_id":"991"}} +{"account_number":991,"balance":4239,"firstname":"Connie","lastname":"Berry","age":28,"gender":"F","address":"647 Gardner Avenue","employer":"Flumbo","email":"connieberry@flumbo.com","city":"Frierson","state":"MO"} +{"index":{"_id":"996"}} +{"account_number":996,"balance":17541,"firstname":"Andrews","lastname":"Herrera","age":30,"gender":"F","address":"570 Vandam Street","employer":"Klugger","email":"andrewsherrera@klugger.com","city":"Whitehaven","state":"MN"} +{"index":{"_id":"0"}} +{"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"} +{"index":{"_id":"5"}} +{"account_number":5,"balance":29342,"firstname":"Leola","lastname":"Stewart","age":30,"gender":"F","address":"311 Elm Place","employer":"Diginetic","email":"leolastewart@diginetic.com","city":"Fairview","state":"NJ"} +{"index":{"_id":"12"}} +{"account_number":12,"balance":37055,"firstname":"Stafford","lastname":"Brock","age":20,"gender":"F","address":"296 Wythe Avenue","employer":"Uncorp","email":"staffordbrock@uncorp.com","city":"Bend","state":"AL"} +{"index":{"_id":"17"}} +{"account_number":17,"balance":7831,"firstname":"Bessie","lastname":"Orr","age":31,"gender":"F","address":"239 Hinsdale Street","employer":"Skyplex","email":"bessieorr@skyplex.com","city":"Graball","state":"FL"} +{"index":{"_id":"24"}} +{"account_number":24,"balance":44182,"firstname":"Wood","lastname":"Dale","age":39,"gender":"M","address":"582 Gelston Avenue","employer":"Besto","email":"wooddale@besto.com","city":"Juntura","state":"MI"} +{"index":{"_id":"29"}} +{"account_number":29,"balance":27323,"firstname":"Leah","lastname":"Santiago","age":33,"gender":"M","address":"193 Schenck Avenue","employer":"Isologix","email":"leahsantiago@isologix.com","city":"Gerton","state":"ND"} +{"index":{"_id":"31"}} +{"account_number":31,"balance":30443,"firstname":"Kristen","lastname":"Santana","age":22,"gender":"F","address":"130 Middagh Street","employer":"Dogspa","email":"kristensantana@dogspa.com","city":"Vale","state":"MA"} +{"index":{"_id":"36"}} +{"account_number":36,"balance":15902,"firstname":"Alexandra","lastname":"Nguyen","age":39,"gender":"F","address":"389 Elizabeth Place","employer":"Bittor","email":"alexandranguyen@bittor.com","city":"Hemlock","state":"KY"} +{"index":{"_id":"43"}} +{"account_number":43,"balance":33474,"firstname":"Ryan","lastname":"Howe","age":25,"gender":"M","address":"660 Huntington Street","employer":"Microluxe","email":"ryanhowe@microluxe.com","city":"Clara","state":"CT"} +{"index":{"_id":"48"}} +{"account_number":48,"balance":40608,"firstname":"Peck","lastname":"Downs","age":39,"gender":"F","address":"594 Dwight Street","employer":"Ramjob","email":"peckdowns@ramjob.com","city":"Coloma","state":"WA"} +{"index":{"_id":"50"}} +{"account_number":50,"balance":43695,"firstname":"Sheena","lastname":"Kirkland","age":33,"gender":"M","address":"598 Bank Street","employer":"Zerbina","email":"sheenakirkland@zerbina.com","city":"Walland","state":"IN"} +{"index":{"_id":"55"}} +{"account_number":55,"balance":22020,"firstname":"Shelia","lastname":"Puckett","age":33,"gender":"M","address":"265 Royce Place","employer":"Izzby","email":"sheliapuckett@izzby.com","city":"Slovan","state":"HI"} +{"index":{"_id":"62"}} +{"account_number":62,"balance":43065,"firstname":"Lester","lastname":"Stanton","age":37,"gender":"M","address":"969 Doughty Street","employer":"Geekko","email":"lesterstanton@geekko.com","city":"Itmann","state":"DC"} +{"index":{"_id":"67"}} +{"account_number":67,"balance":39430,"firstname":"Isabelle","lastname":"Spence","age":39,"gender":"M","address":"718 Troy Avenue","employer":"Geeketron","email":"isabellespence@geeketron.com","city":"Camptown","state":"WA"} +{"index":{"_id":"74"}} +{"account_number":74,"balance":47167,"firstname":"Lauri","lastname":"Saunders","age":38,"gender":"F","address":"768 Lynch Street","employer":"Securia","email":"laurisaunders@securia.com","city":"Caroline","state":"TN"} +{"index":{"_id":"79"}} +{"account_number":79,"balance":28185,"firstname":"Booker","lastname":"Lowery","age":29,"gender":"M","address":"817 Campus Road","employer":"Sensate","email":"bookerlowery@sensate.com","city":"Carlos","state":"MT"} +{"index":{"_id":"81"}} +{"account_number":81,"balance":46568,"firstname":"Dennis","lastname":"Gilbert","age":40,"gender":"M","address":"619 Minna Street","employer":"Melbacor","email":"dennisgilbert@melbacor.com","city":"Kersey","state":"ND"} +{"index":{"_id":"86"}} +{"account_number":86,"balance":15428,"firstname":"Walton","lastname":"Butler","age":36,"gender":"M","address":"999 Schenck Street","employer":"Unisure","email":"waltonbutler@unisure.com","city":"Bentonville","state":"IL"} +{"index":{"_id":"93"}} +{"account_number":93,"balance":17728,"firstname":"Jeri","lastname":"Booth","age":31,"gender":"M","address":"322 Roosevelt Court","employer":"Geekology","email":"jeribooth@geekology.com","city":"Leming","state":"ND"} +{"index":{"_id":"98"}} +{"account_number":98,"balance":15085,"firstname":"Cora","lastname":"Barrett","age":24,"gender":"F","address":"555 Neptune Court","employer":"Kiosk","email":"corabarrett@kiosk.com","city":"Independence","state":"MN"} +{"index":{"_id":"101"}} +{"account_number":101,"balance":43400,"firstname":"Cecelia","lastname":"Grimes","age":31,"gender":"M","address":"972 Lincoln Place","employer":"Ecosys","email":"ceceliagrimes@ecosys.com","city":"Manchester","state":"AR"} +{"index":{"_id":"106"}} +{"account_number":106,"balance":8212,"firstname":"Josefina","lastname":"Wagner","age":36,"gender":"M","address":"418 Estate Road","employer":"Kyaguru","email":"josefinawagner@kyaguru.com","city":"Darbydale","state":"FL"} +{"index":{"_id":"113"}} +{"account_number":113,"balance":41652,"firstname":"Burt","lastname":"Moses","age":27,"gender":"M","address":"633 Berry Street","employer":"Uni","email":"burtmoses@uni.com","city":"Russellville","state":"CT"} +{"index":{"_id":"118"}} +{"account_number":118,"balance":2223,"firstname":"Ballard","lastname":"Vasquez","age":33,"gender":"F","address":"101 Bush Street","employer":"Intergeek","email":"ballardvasquez@intergeek.com","city":"Century","state":"MN"} +{"index":{"_id":"120"}} +{"account_number":120,"balance":38565,"firstname":"Browning","lastname":"Rodriquez","age":33,"gender":"M","address":"910 Moore Street","employer":"Opportech","email":"browningrodriquez@opportech.com","city":"Cutter","state":"ND"} +{"index":{"_id":"125"}} +{"account_number":125,"balance":5396,"firstname":"Tanisha","lastname":"Dixon","age":30,"gender":"M","address":"482 Hancock Street","employer":"Junipoor","email":"tanishadixon@junipoor.com","city":"Wauhillau","state":"IA"} +{"index":{"_id":"132"}} +{"account_number":132,"balance":37707,"firstname":"Horton","lastname":"Romero","age":35,"gender":"M","address":"427 Rutherford Place","employer":"Affluex","email":"hortonromero@affluex.com","city":"Hall","state":"AK"} +{"index":{"_id":"137"}} +{"account_number":137,"balance":3596,"firstname":"Frost","lastname":"Freeman","age":29,"gender":"F","address":"191 Dennett Place","employer":"Beadzza","email":"frostfreeman@beadzza.com","city":"Sabillasville","state":"HI"} +{"index":{"_id":"144"}} +{"account_number":144,"balance":43257,"firstname":"Evans","lastname":"Dyer","age":30,"gender":"F","address":"912 Post Court","employer":"Magmina","email":"evansdyer@magmina.com","city":"Gordon","state":"HI"} +{"index":{"_id":"149"}} +{"account_number":149,"balance":22994,"firstname":"Megan","lastname":"Gonzales","age":21,"gender":"M","address":"836 Tampa Court","employer":"Andershun","email":"megangonzales@andershun.com","city":"Rockhill","state":"AL"} +{"index":{"_id":"151"}} +{"account_number":151,"balance":34473,"firstname":"Kent","lastname":"Joyner","age":20,"gender":"F","address":"799 Truxton Street","employer":"Kozgene","email":"kentjoyner@kozgene.com","city":"Allamuchy","state":"DC"} +{"index":{"_id":"156"}} +{"account_number":156,"balance":40185,"firstname":"Sloan","lastname":"Pennington","age":24,"gender":"F","address":"573 Opal Court","employer":"Hopeli","email":"sloanpennington@hopeli.com","city":"Evergreen","state":"CT"} +{"index":{"_id":"163"}} +{"account_number":163,"balance":43075,"firstname":"Wilda","lastname":"Norman","age":33,"gender":"F","address":"173 Beadel Street","employer":"Kog","email":"wildanorman@kog.com","city":"Bodega","state":"ME"} +{"index":{"_id":"168"}} +{"account_number":168,"balance":49568,"firstname":"Carissa","lastname":"Simon","age":20,"gender":"M","address":"975 Flatbush Avenue","employer":"Zillacom","email":"carissasimon@zillacom.com","city":"Neibert","state":"IL"} +{"index":{"_id":"170"}} +{"account_number":170,"balance":6025,"firstname":"Mann","lastname":"Madden","age":36,"gender":"F","address":"161 Radde Place","employer":"Farmex","email":"mannmadden@farmex.com","city":"Thermal","state":"LA"} +{"index":{"_id":"175"}} +{"account_number":175,"balance":16213,"firstname":"Montoya","lastname":"Donaldson","age":28,"gender":"F","address":"481 Morton Street","employer":"Envire","email":"montoyadonaldson@envire.com","city":"Delco","state":"MA"} +{"index":{"_id":"182"}} +{"account_number":182,"balance":7803,"firstname":"Manuela","lastname":"Dillon","age":21,"gender":"M","address":"742 Garnet Street","employer":"Moreganic","email":"manueladillon@moreganic.com","city":"Ilchester","state":"TX"} +{"index":{"_id":"187"}} +{"account_number":187,"balance":26581,"firstname":"Autumn","lastname":"Hodges","age":35,"gender":"M","address":"757 Granite Street","employer":"Ezentia","email":"autumnhodges@ezentia.com","city":"Martinsville","state":"KY"} +{"index":{"_id":"194"}} +{"account_number":194,"balance":16311,"firstname":"Beck","lastname":"Rosario","age":39,"gender":"M","address":"721 Cambridge Place","employer":"Zoid","email":"beckrosario@zoid.com","city":"Efland","state":"ID"} +{"index":{"_id":"199"}} +{"account_number":199,"balance":18086,"firstname":"Branch","lastname":"Love","age":26,"gender":"M","address":"458 Commercial Street","employer":"Frolix","email":"branchlove@frolix.com","city":"Caspar","state":"NC"} +{"index":{"_id":"202"}} +{"account_number":202,"balance":26466,"firstname":"Medina","lastname":"Brown","age":31,"gender":"F","address":"519 Sunnyside Court","employer":"Bleendot","email":"medinabrown@bleendot.com","city":"Winfred","state":"MI"} +{"index":{"_id":"207"}} +{"account_number":207,"balance":45535,"firstname":"Evelyn","lastname":"Lara","age":35,"gender":"F","address":"636 Chestnut Street","employer":"Ultrasure","email":"evelynlara@ultrasure.com","city":"Logan","state":"MI"} +{"index":{"_id":"214"}} +{"account_number":214,"balance":24418,"firstname":"Luann","lastname":"Faulkner","age":37,"gender":"F","address":"697 Hazel Court","employer":"Zolar","email":"luannfaulkner@zolar.com","city":"Ticonderoga","state":"TX"} +{"index":{"_id":"219"}} +{"account_number":219,"balance":17127,"firstname":"Edwards","lastname":"Hurley","age":25,"gender":"M","address":"834 Stockholm Street","employer":"Austech","email":"edwardshurley@austech.com","city":"Bayview","state":"NV"} +{"index":{"_id":"221"}} +{"account_number":221,"balance":15803,"firstname":"Benjamin","lastname":"Barrera","age":34,"gender":"M","address":"568 Main Street","employer":"Zaphire","email":"benjaminbarrera@zaphire.com","city":"Germanton","state":"WY"} +{"index":{"_id":"226"}} +{"account_number":226,"balance":37720,"firstname":"Wilkins","lastname":"Brady","age":40,"gender":"F","address":"486 Baltic Street","employer":"Dogtown","email":"wilkinsbrady@dogtown.com","city":"Condon","state":"MT"} +{"index":{"_id":"233"}} +{"account_number":233,"balance":23020,"firstname":"Washington","lastname":"Walsh","age":27,"gender":"M","address":"366 Church Avenue","employer":"Candecor","email":"washingtonwalsh@candecor.com","city":"Westphalia","state":"MA"} +{"index":{"_id":"238"}} +{"account_number":238,"balance":21287,"firstname":"Constance","lastname":"Wong","age":28,"gender":"M","address":"496 Brown Street","employer":"Grainspot","email":"constancewong@grainspot.com","city":"Cecilia","state":"IN"} +{"index":{"_id":"240"}} +{"account_number":240,"balance":49741,"firstname":"Oconnor","lastname":"Clay","age":35,"gender":"F","address":"659 Highland Boulevard","employer":"Franscene","email":"oconnorclay@franscene.com","city":"Kilbourne","state":"NH"} +{"index":{"_id":"245"}} +{"account_number":245,"balance":22026,"firstname":"Fran","lastname":"Bolton","age":28,"gender":"F","address":"147 Jerome Street","employer":"Solaren","email":"franbolton@solaren.com","city":"Nash","state":"RI"} +{"index":{"_id":"252"}} +{"account_number":252,"balance":18831,"firstname":"Elvia","lastname":"Poole","age":22,"gender":"F","address":"836 Delevan Street","employer":"Velity","email":"elviapoole@velity.com","city":"Groveville","state":"MI"} +{"index":{"_id":"257"}} +{"account_number":257,"balance":5318,"firstname":"Olive","lastname":"Oneil","age":35,"gender":"F","address":"457 Decatur Street","employer":"Helixo","email":"oliveoneil@helixo.com","city":"Chicopee","state":"MI"} +{"index":{"_id":"264"}} +{"account_number":264,"balance":22084,"firstname":"Samantha","lastname":"Ferrell","age":35,"gender":"F","address":"488 Fulton Street","employer":"Flum","email":"samanthaferrell@flum.com","city":"Brandywine","state":"MT"} +{"index":{"_id":"269"}} +{"account_number":269,"balance":43317,"firstname":"Crosby","lastname":"Figueroa","age":34,"gender":"M","address":"910 Aurelia Court","employer":"Pyramia","email":"crosbyfigueroa@pyramia.com","city":"Leyner","state":"OH"} +{"index":{"_id":"271"}} +{"account_number":271,"balance":11864,"firstname":"Holt","lastname":"Walter","age":30,"gender":"F","address":"645 Poplar Avenue","employer":"Grupoli","email":"holtwalter@grupoli.com","city":"Mansfield","state":"OR"} +{"index":{"_id":"276"}} +{"account_number":276,"balance":11606,"firstname":"Pittman","lastname":"Mathis","age":23,"gender":"F","address":"567 Charles Place","employer":"Zuvy","email":"pittmanmathis@zuvy.com","city":"Roeville","state":"KY"} +{"index":{"_id":"283"}} +{"account_number":283,"balance":24070,"firstname":"Fuentes","lastname":"Foley","age":30,"gender":"M","address":"729 Walker Court","employer":"Knowlysis","email":"fuentesfoley@knowlysis.com","city":"Tryon","state":"TN"} +{"index":{"_id":"288"}} +{"account_number":288,"balance":27243,"firstname":"Wong","lastname":"Stone","age":39,"gender":"F","address":"440 Willoughby Street","employer":"Zentix","email":"wongstone@zentix.com","city":"Wheatfields","state":"DC"} +{"index":{"_id":"290"}} +{"account_number":290,"balance":26103,"firstname":"Neva","lastname":"Burgess","age":37,"gender":"F","address":"985 Wyona Street","employer":"Slofast","email":"nevaburgess@slofast.com","city":"Cawood","state":"DC"} +{"index":{"_id":"295"}} +{"account_number":295,"balance":37358,"firstname":"Howe","lastname":"Nash","age":20,"gender":"M","address":"833 Union Avenue","employer":"Aquacine","email":"howenash@aquacine.com","city":"Indio","state":"MN"} +{"index":{"_id":"303"}} +{"account_number":303,"balance":21976,"firstname":"Huffman","lastname":"Green","age":24,"gender":"F","address":"455 Colby Court","employer":"Comtest","email":"huffmangreen@comtest.com","city":"Weeksville","state":"UT"} +{"index":{"_id":"308"}} +{"account_number":308,"balance":33989,"firstname":"Glass","lastname":"Schroeder","age":25,"gender":"F","address":"670 Veterans Avenue","employer":"Realmo","email":"glassschroeder@realmo.com","city":"Gratton","state":"NY"} +{"index":{"_id":"310"}} +{"account_number":310,"balance":23049,"firstname":"Shannon","lastname":"Morton","age":39,"gender":"F","address":"412 Pleasant Place","employer":"Ovation","email":"shannonmorton@ovation.com","city":"Edgar","state":"AZ"} +{"index":{"_id":"315"}} +{"account_number":315,"balance":1314,"firstname":"Clare","lastname":"Morrow","age":33,"gender":"F","address":"728 Madeline Court","employer":"Gaptec","email":"claremorrow@gaptec.com","city":"Mapletown","state":"PA"} +{"index":{"_id":"322"}} +{"account_number":322,"balance":6303,"firstname":"Gilliam","lastname":"Horne","age":27,"gender":"M","address":"414 Florence Avenue","employer":"Shepard","email":"gilliamhorne@shepard.com","city":"Winesburg","state":"WY"} +{"index":{"_id":"327"}} +{"account_number":327,"balance":29294,"firstname":"Nell","lastname":"Contreras","age":27,"gender":"M","address":"694 Gold Street","employer":"Momentia","email":"nellcontreras@momentia.com","city":"Cumminsville","state":"AL"} +{"index":{"_id":"334"}} +{"account_number":334,"balance":9178,"firstname":"Cross","lastname":"Floyd","age":21,"gender":"F","address":"815 Herkimer Court","employer":"Maroptic","email":"crossfloyd@maroptic.com","city":"Kraemer","state":"AK"} +{"index":{"_id":"339"}} +{"account_number":339,"balance":3992,"firstname":"Franco","lastname":"Welch","age":38,"gender":"F","address":"776 Brightwater Court","employer":"Earthplex","email":"francowelch@earthplex.com","city":"Naomi","state":"ME"} +{"index":{"_id":"341"}} +{"account_number":341,"balance":44367,"firstname":"Alberta","lastname":"Bradford","age":30,"gender":"F","address":"670 Grant Avenue","employer":"Bugsall","email":"albertabradford@bugsall.com","city":"Romeville","state":"MT"} +{"index":{"_id":"346"}} +{"account_number":346,"balance":26594,"firstname":"Shelby","lastname":"Sanchez","age":36,"gender":"F","address":"257 Fillmore Avenue","employer":"Geekus","email":"shelbysanchez@geekus.com","city":"Seymour","state":"CO"} +{"index":{"_id":"353"}} +{"account_number":353,"balance":45182,"firstname":"Rivera","lastname":"Sherman","age":37,"gender":"M","address":"603 Garden Place","employer":"Bovis","email":"riverasherman@bovis.com","city":"Otranto","state":"CA"} +{"index":{"_id":"358"}} +{"account_number":358,"balance":44043,"firstname":"Hale","lastname":"Baldwin","age":40,"gender":"F","address":"845 Menahan Street","employer":"Kidgrease","email":"halebaldwin@kidgrease.com","city":"Day","state":"AK"} +{"index":{"_id":"360"}} +{"account_number":360,"balance":26651,"firstname":"Ward","lastname":"Hicks","age":34,"gender":"F","address":"592 Brighton Court","employer":"Biotica","email":"wardhicks@biotica.com","city":"Kanauga","state":"VT"} +{"index":{"_id":"365"}} +{"account_number":365,"balance":3176,"firstname":"Sanders","lastname":"Holder","age":31,"gender":"F","address":"453 Cypress Court","employer":"Geekola","email":"sandersholder@geekola.com","city":"Staples","state":"TN"} +{"index":{"_id":"372"}} +{"account_number":372,"balance":28566,"firstname":"Alba","lastname":"Forbes","age":24,"gender":"M","address":"814 Meserole Avenue","employer":"Isostream","email":"albaforbes@isostream.com","city":"Clarence","state":"OR"} +{"index":{"_id":"377"}} +{"account_number":377,"balance":5374,"firstname":"Margo","lastname":"Gay","age":34,"gender":"F","address":"613 Chase Court","employer":"Rotodyne","email":"margogay@rotodyne.com","city":"Waumandee","state":"KS"} +{"index":{"_id":"384"}} +{"account_number":384,"balance":48758,"firstname":"Sallie","lastname":"Houston","age":31,"gender":"F","address":"836 Polar Street","employer":"Squish","email":"salliehouston@squish.com","city":"Morningside","state":"NC"} +{"index":{"_id":"389"}} +{"account_number":389,"balance":8839,"firstname":"York","lastname":"Cummings","age":27,"gender":"M","address":"778 Centre Street","employer":"Insurity","email":"yorkcummings@insurity.com","city":"Freeburn","state":"RI"} +{"index":{"_id":"391"}} +{"account_number":391,"balance":14733,"firstname":"Holman","lastname":"Jordan","age":30,"gender":"M","address":"391 Forrest Street","employer":"Maineland","email":"holmanjordan@maineland.com","city":"Cade","state":"CT"} +{"index":{"_id":"396"}} +{"account_number":396,"balance":14613,"firstname":"Marsha","lastname":"Elliott","age":38,"gender":"F","address":"297 Liberty Avenue","employer":"Orbiflex","email":"marshaelliott@orbiflex.com","city":"Windsor","state":"TX"} +{"index":{"_id":"404"}} +{"account_number":404,"balance":34978,"firstname":"Massey","lastname":"Becker","age":26,"gender":"F","address":"930 Pitkin Avenue","employer":"Genekom","email":"masseybecker@genekom.com","city":"Blairstown","state":"OR"} +{"index":{"_id":"409"}} +{"account_number":409,"balance":36960,"firstname":"Maura","lastname":"Glenn","age":31,"gender":"M","address":"183 Poly Place","employer":"Viagreat","email":"mauraglenn@viagreat.com","city":"Foscoe","state":"DE"} +{"index":{"_id":"411"}} +{"account_number":411,"balance":1172,"firstname":"Guzman","lastname":"Whitfield","age":22,"gender":"M","address":"181 Perry Terrace","employer":"Springbee","email":"guzmanwhitfield@springbee.com","city":"Balm","state":"IN"} +{"index":{"_id":"416"}} +{"account_number":416,"balance":27169,"firstname":"Hunt","lastname":"Schwartz","age":28,"gender":"F","address":"461 Havens Place","employer":"Danja","email":"huntschwartz@danja.com","city":"Grenelefe","state":"NV"} +{"index":{"_id":"423"}} +{"account_number":423,"balance":38852,"firstname":"Hines","lastname":"Underwood","age":21,"gender":"F","address":"284 Louise Terrace","employer":"Namegen","email":"hinesunderwood@namegen.com","city":"Downsville","state":"CO"} +{"index":{"_id":"428"}} +{"account_number":428,"balance":13925,"firstname":"Stephens","lastname":"Cain","age":20,"gender":"F","address":"189 Summit Street","employer":"Rocklogic","email":"stephenscain@rocklogic.com","city":"Bourg","state":"HI"} +{"index":{"_id":"430"}} +{"account_number":430,"balance":15251,"firstname":"Alejandra","lastname":"Chavez","age":34,"gender":"M","address":"651 Butler Place","employer":"Gology","email":"alejandrachavez@gology.com","city":"Allensworth","state":"VT"} +{"index":{"_id":"435"}} +{"account_number":435,"balance":14654,"firstname":"Sue","lastname":"Lopez","age":22,"gender":"F","address":"632 Stone Avenue","employer":"Emergent","email":"suelopez@emergent.com","city":"Waterford","state":"TN"} +{"index":{"_id":"442"}} +{"account_number":442,"balance":36211,"firstname":"Lawanda","lastname":"Leon","age":27,"gender":"F","address":"126 Canal Avenue","employer":"Xixan","email":"lawandaleon@xixan.com","city":"Berwind","state":"TN"} +{"index":{"_id":"447"}} +{"account_number":447,"balance":11402,"firstname":"Lucia","lastname":"Livingston","age":35,"gender":"M","address":"773 Lake Avenue","employer":"Soprano","email":"lucialivingston@soprano.com","city":"Edgewater","state":"TN"} +{"index":{"_id":"454"}} +{"account_number":454,"balance":31687,"firstname":"Alicia","lastname":"Rollins","age":22,"gender":"F","address":"483 Verona Place","employer":"Boilcat","email":"aliciarollins@boilcat.com","city":"Lutsen","state":"MD"} +{"index":{"_id":"459"}} +{"account_number":459,"balance":18869,"firstname":"Pamela","lastname":"Henry","age":20,"gender":"F","address":"361 Locust Avenue","employer":"Imageflow","email":"pamelahenry@imageflow.com","city":"Greenfields","state":"OH"} +{"index":{"_id":"461"}} +{"account_number":461,"balance":38807,"firstname":"Mcbride","lastname":"Padilla","age":34,"gender":"F","address":"550 Borinquen Pl","employer":"Zepitope","email":"mcbridepadilla@zepitope.com","city":"Emory","state":"AZ"} +{"index":{"_id":"466"}} +{"account_number":466,"balance":25109,"firstname":"Marcie","lastname":"Mcmillan","age":30,"gender":"F","address":"947 Gain Court","employer":"Entroflex","email":"marciemcmillan@entroflex.com","city":"Ronco","state":"ND"} +{"index":{"_id":"473"}} +{"account_number":473,"balance":5391,"firstname":"Susan","lastname":"Luna","age":25,"gender":"F","address":"521 Bogart Street","employer":"Zaya","email":"susanluna@zaya.com","city":"Grazierville","state":"MI"} +{"index":{"_id":"478"}} +{"account_number":478,"balance":28044,"firstname":"Dana","lastname":"Decker","age":35,"gender":"M","address":"627 Dobbin Street","employer":"Acrodance","email":"danadecker@acrodance.com","city":"Sharon","state":"MN"} +{"index":{"_id":"480"}} +{"account_number":480,"balance":40807,"firstname":"Anastasia","lastname":"Parker","age":24,"gender":"M","address":"650 Folsom Place","employer":"Zilladyne","email":"anastasiaparker@zilladyne.com","city":"Oberlin","state":"WY"} +{"index":{"_id":"485"}} +{"account_number":485,"balance":44235,"firstname":"Albert","lastname":"Roberts","age":40,"gender":"M","address":"385 Harman Street","employer":"Stralum","email":"albertroberts@stralum.com","city":"Watrous","state":"NM"} +{"index":{"_id":"492"}} +{"account_number":492,"balance":31055,"firstname":"Burnett","lastname":"Briggs","age":35,"gender":"M","address":"987 Cass Place","employer":"Pharmex","email":"burnettbriggs@pharmex.com","city":"Cornfields","state":"TX"} +{"index":{"_id":"497"}} +{"account_number":497,"balance":13493,"firstname":"Doyle","lastname":"Jenkins","age":30,"gender":"M","address":"205 Nevins Street","employer":"Unia","email":"doylejenkins@unia.com","city":"Nicut","state":"DC"} +{"index":{"_id":"500"}} +{"account_number":500,"balance":39143,"firstname":"Pope","lastname":"Keith","age":28,"gender":"F","address":"537 Fane Court","employer":"Zboo","email":"popekeith@zboo.com","city":"Courtland","state":"AL"} +{"index":{"_id":"505"}} +{"account_number":505,"balance":45493,"firstname":"Shelley","lastname":"Webb","age":29,"gender":"M","address":"873 Crawford Avenue","employer":"Quadeebo","email":"shelleywebb@quadeebo.com","city":"Topanga","state":"IL"} +{"index":{"_id":"512"}} +{"account_number":512,"balance":47432,"firstname":"Alisha","lastname":"Morales","age":29,"gender":"M","address":"623 Batchelder Street","employer":"Terragen","email":"alishamorales@terragen.com","city":"Gilmore","state":"VA"} +{"index":{"_id":"517"}} +{"account_number":517,"balance":3022,"firstname":"Allyson","lastname":"Walls","age":38,"gender":"F","address":"334 Coffey Street","employer":"Gorganic","email":"allysonwalls@gorganic.com","city":"Dahlen","state":"GA"} +{"index":{"_id":"524"}} +{"account_number":524,"balance":49334,"firstname":"Salas","lastname":"Farley","age":30,"gender":"F","address":"499 Trucklemans Lane","employer":"Xumonk","email":"salasfarley@xumonk.com","city":"Noxen","state":"AL"} +{"index":{"_id":"529"}} +{"account_number":529,"balance":21788,"firstname":"Deann","lastname":"Fisher","age":23,"gender":"F","address":"511 Buffalo Avenue","employer":"Twiist","email":"deannfisher@twiist.com","city":"Templeton","state":"WA"} +{"index":{"_id":"531"}} +{"account_number":531,"balance":39770,"firstname":"Janet","lastname":"Pena","age":38,"gender":"M","address":"645 Livonia Avenue","employer":"Corecom","email":"janetpena@corecom.com","city":"Garberville","state":"OK"} +{"index":{"_id":"536"}} +{"account_number":536,"balance":6255,"firstname":"Emma","lastname":"Adkins","age":33,"gender":"F","address":"971 Calder Place","employer":"Ontagene","email":"emmaadkins@ontagene.com","city":"Ruckersville","state":"GA"} +{"index":{"_id":"543"}} +{"account_number":543,"balance":48022,"firstname":"Marina","lastname":"Rasmussen","age":31,"gender":"M","address":"446 Love Lane","employer":"Crustatia","email":"marinarasmussen@crustatia.com","city":"Statenville","state":"MD"} +{"index":{"_id":"548"}} +{"account_number":548,"balance":36930,"firstname":"Sandra","lastname":"Andrews","age":37,"gender":"M","address":"973 Prospect Street","employer":"Datagene","email":"sandraandrews@datagene.com","city":"Inkerman","state":"MO"} +{"index":{"_id":"550"}} +{"account_number":550,"balance":32238,"firstname":"Walsh","lastname":"Goodwin","age":22,"gender":"M","address":"953 Canda Avenue","employer":"Proflex","email":"walshgoodwin@proflex.com","city":"Ypsilanti","state":"MT"} +{"index":{"_id":"555"}} +{"account_number":555,"balance":10750,"firstname":"Fannie","lastname":"Slater","age":31,"gender":"M","address":"457 Tech Place","employer":"Kineticut","email":"fannieslater@kineticut.com","city":"Basye","state":"MO"} +{"index":{"_id":"562"}} +{"account_number":562,"balance":10737,"firstname":"Sarah","lastname":"Strong","age":39,"gender":"F","address":"177 Pioneer Street","employer":"Megall","email":"sarahstrong@megall.com","city":"Ladera","state":"WY"} +{"index":{"_id":"567"}} +{"account_number":567,"balance":6507,"firstname":"Diana","lastname":"Dominguez","age":40,"gender":"M","address":"419 Albany Avenue","employer":"Ohmnet","email":"dianadominguez@ohmnet.com","city":"Wildwood","state":"TX"} +{"index":{"_id":"574"}} +{"account_number":574,"balance":32954,"firstname":"Andrea","lastname":"Mosley","age":24,"gender":"M","address":"368 Throop Avenue","employer":"Musix","email":"andreamosley@musix.com","city":"Blende","state":"DC"} +{"index":{"_id":"579"}} +{"account_number":579,"balance":12044,"firstname":"Banks","lastname":"Sawyer","age":36,"gender":"M","address":"652 Doone Court","employer":"Rooforia","email":"bankssawyer@rooforia.com","city":"Foxworth","state":"ND"} +{"index":{"_id":"581"}} +{"account_number":581,"balance":16525,"firstname":"Fuller","lastname":"Mcintyre","age":32,"gender":"M","address":"169 Bergen Place","employer":"Applideck","email":"fullermcintyre@applideck.com","city":"Kenvil","state":"NY"} +{"index":{"_id":"586"}} +{"account_number":586,"balance":13644,"firstname":"Love","lastname":"Velasquez","age":26,"gender":"F","address":"290 Girard Street","employer":"Zomboid","email":"lovevelasquez@zomboid.com","city":"Villarreal","state":"SD"} +{"index":{"_id":"593"}} +{"account_number":593,"balance":41230,"firstname":"Muriel","lastname":"Vazquez","age":37,"gender":"M","address":"395 Montgomery Street","employer":"Sustenza","email":"murielvazquez@sustenza.com","city":"Strykersville","state":"OK"} +{"index":{"_id":"598"}} +{"account_number":598,"balance":33251,"firstname":"Morgan","lastname":"Coleman","age":33,"gender":"M","address":"324 McClancy Place","employer":"Aclima","email":"morgancoleman@aclima.com","city":"Bowden","state":"WA"} +{"index":{"_id":"601"}} +{"account_number":601,"balance":20796,"firstname":"Vickie","lastname":"Valentine","age":34,"gender":"F","address":"432 Bassett Avenue","employer":"Comvene","email":"vickievalentine@comvene.com","city":"Teasdale","state":"UT"} +{"index":{"_id":"606"}} +{"account_number":606,"balance":28770,"firstname":"Michael","lastname":"Bray","age":31,"gender":"M","address":"935 Lake Place","employer":"Telepark","email":"michaelbray@telepark.com","city":"Lemoyne","state":"CT"} +{"index":{"_id":"613"}} +{"account_number":613,"balance":39340,"firstname":"Eddie","lastname":"Mccarty","age":34,"gender":"F","address":"971 Richards Street","employer":"Bisba","email":"eddiemccarty@bisba.com","city":"Fruitdale","state":"NY"} +{"index":{"_id":"618"}} +{"account_number":618,"balance":8976,"firstname":"Cheri","lastname":"Ford","age":30,"gender":"F","address":"803 Ridgewood Avenue","employer":"Zorromop","email":"cheriford@zorromop.com","city":"Gambrills","state":"VT"} +{"index":{"_id":"620"}} +{"account_number":620,"balance":7224,"firstname":"Coleen","lastname":"Bartlett","age":38,"gender":"M","address":"761 Carroll Street","employer":"Idealis","email":"coleenbartlett@idealis.com","city":"Mathews","state":"DE"} +{"index":{"_id":"625"}} +{"account_number":625,"balance":46010,"firstname":"Cynthia","lastname":"Johnston","age":23,"gender":"M","address":"142 Box Street","employer":"Zentry","email":"cynthiajohnston@zentry.com","city":"Makena","state":"MA"} +{"index":{"_id":"632"}} +{"account_number":632,"balance":40470,"firstname":"Kay","lastname":"Warren","age":20,"gender":"F","address":"422 Alabama Avenue","employer":"Realysis","email":"kaywarren@realysis.com","city":"Homestead","state":"HI"} +{"index":{"_id":"637"}} +{"account_number":637,"balance":3169,"firstname":"Kathy","lastname":"Carter","age":27,"gender":"F","address":"410 Jamison Lane","employer":"Limage","email":"kathycarter@limage.com","city":"Ernstville","state":"WA"} +{"index":{"_id":"644"}} +{"account_number":644,"balance":44021,"firstname":"Etta","lastname":"Miller","age":21,"gender":"F","address":"376 Lawton Street","employer":"Bluegrain","email":"ettamiller@bluegrain.com","city":"Baker","state":"MD"} +{"index":{"_id":"649"}} +{"account_number":649,"balance":20275,"firstname":"Jeanine","lastname":"Malone","age":26,"gender":"F","address":"114 Dodworth Street","employer":"Nixelt","email":"jeaninemalone@nixelt.com","city":"Keyport","state":"AK"} +{"index":{"_id":"651"}} +{"account_number":651,"balance":18360,"firstname":"Young","lastname":"Reeves","age":34,"gender":"M","address":"581 Plaza Street","employer":"Krog","email":"youngreeves@krog.com","city":"Sussex","state":"WY"} +{"index":{"_id":"656"}} +{"account_number":656,"balance":21632,"firstname":"Olson","lastname":"Hunt","age":36,"gender":"M","address":"342 Jaffray Street","employer":"Volax","email":"olsonhunt@volax.com","city":"Bangor","state":"WA"} +{"index":{"_id":"663"}} +{"account_number":663,"balance":2456,"firstname":"Rollins","lastname":"Richards","age":37,"gender":"M","address":"129 Sullivan Place","employer":"Geostele","email":"rollinsrichards@geostele.com","city":"Morgandale","state":"FL"} +{"index":{"_id":"668"}} +{"account_number":668,"balance":45069,"firstname":"Potter","lastname":"Michael","age":27,"gender":"M","address":"803 Glenmore Avenue","employer":"Ontality","email":"pottermichael@ontality.com","city":"Newkirk","state":"KS"} +{"index":{"_id":"670"}} +{"account_number":670,"balance":10178,"firstname":"Ollie","lastname":"Riley","age":22,"gender":"M","address":"252 Jackson Place","employer":"Adornica","email":"ollieriley@adornica.com","city":"Brethren","state":"WI"} +{"index":{"_id":"675"}} +{"account_number":675,"balance":36102,"firstname":"Fisher","lastname":"Shepard","age":27,"gender":"F","address":"859 Varick Street","employer":"Qot","email":"fishershepard@qot.com","city":"Diaperville","state":"MD"} +{"index":{"_id":"682"}} +{"account_number":682,"balance":14168,"firstname":"Anne","lastname":"Hale","age":22,"gender":"F","address":"708 Anthony Street","employer":"Cytrek","email":"annehale@cytrek.com","city":"Beechmont","state":"WV"} +{"index":{"_id":"687"}} +{"account_number":687,"balance":48630,"firstname":"Caroline","lastname":"Cox","age":31,"gender":"M","address":"626 Hillel Place","employer":"Opticon","email":"carolinecox@opticon.com","city":"Loma","state":"ND"} +{"index":{"_id":"694"}} +{"account_number":694,"balance":33125,"firstname":"Craig","lastname":"Palmer","age":31,"gender":"F","address":"273 Montrose Avenue","employer":"Comvey","email":"craigpalmer@comvey.com","city":"Cleary","state":"OK"} +{"index":{"_id":"699"}} +{"account_number":699,"balance":4156,"firstname":"Gallagher","lastname":"Marshall","age":37,"gender":"F","address":"648 Clifford Place","employer":"Exiand","email":"gallaghermarshall@exiand.com","city":"Belfair","state":"KY"} +{"index":{"_id":"702"}} +{"account_number":702,"balance":46490,"firstname":"Meadows","lastname":"Delgado","age":26,"gender":"M","address":"612 Jardine Place","employer":"Daisu","email":"meadowsdelgado@daisu.com","city":"Venice","state":"AR"} +{"index":{"_id":"707"}} +{"account_number":707,"balance":30325,"firstname":"Sonya","lastname":"Trevino","age":30,"gender":"F","address":"181 Irving Place","employer":"Atgen","email":"sonyatrevino@atgen.com","city":"Enetai","state":"TN"} +{"index":{"_id":"714"}} +{"account_number":714,"balance":16602,"firstname":"Socorro","lastname":"Murray","age":34,"gender":"F","address":"810 Manhattan Court","employer":"Isoswitch","email":"socorromurray@isoswitch.com","city":"Jugtown","state":"AZ"} +{"index":{"_id":"719"}} +{"account_number":719,"balance":33107,"firstname":"Leanna","lastname":"Reed","age":25,"gender":"F","address":"528 Krier Place","employer":"Rodeology","email":"leannareed@rodeology.com","city":"Carrizo","state":"WI"} +{"index":{"_id":"721"}} +{"account_number":721,"balance":32958,"firstname":"Mara","lastname":"Dickson","age":26,"gender":"M","address":"810 Harrison Avenue","employer":"Comtours","email":"maradickson@comtours.com","city":"Thynedale","state":"DE"} +{"index":{"_id":"726"}} +{"account_number":726,"balance":44737,"firstname":"Rosemary","lastname":"Salazar","age":21,"gender":"M","address":"290 Croton Loop","employer":"Rockabye","email":"rosemarysalazar@rockabye.com","city":"Helen","state":"IA"} +{"index":{"_id":"733"}} +{"account_number":733,"balance":15722,"firstname":"Lakeisha","lastname":"Mccarthy","age":37,"gender":"M","address":"782 Turnbull Avenue","employer":"Exosis","email":"lakeishamccarthy@exosis.com","city":"Caberfae","state":"NM"} +{"index":{"_id":"738"}} +{"account_number":738,"balance":44936,"firstname":"Rosalind","lastname":"Hunter","age":32,"gender":"M","address":"644 Eaton Court","employer":"Zolarity","email":"rosalindhunter@zolarity.com","city":"Cataract","state":"SD"} +{"index":{"_id":"740"}} +{"account_number":740,"balance":6143,"firstname":"Chambers","lastname":"Hahn","age":22,"gender":"M","address":"937 Windsor Place","employer":"Medalert","email":"chambershahn@medalert.com","city":"Dorneyville","state":"DC"} +{"index":{"_id":"745"}} +{"account_number":745,"balance":4572,"firstname":"Jacobs","lastname":"Sweeney","age":32,"gender":"M","address":"189 Lott Place","employer":"Comtent","email":"jacobssweeney@comtent.com","city":"Advance","state":"NJ"} +{"index":{"_id":"752"}} +{"account_number":752,"balance":14039,"firstname":"Jerry","lastname":"Rush","age":31,"gender":"M","address":"632 Dank Court","employer":"Ebidco","email":"jerryrush@ebidco.com","city":"Geyserville","state":"AR"} +{"index":{"_id":"757"}} +{"account_number":757,"balance":34628,"firstname":"Mccullough","lastname":"Moore","age":30,"gender":"F","address":"304 Hastings Street","employer":"Nikuda","email":"mcculloughmoore@nikuda.com","city":"Charco","state":"DC"} +{"index":{"_id":"764"}} +{"account_number":764,"balance":3728,"firstname":"Noemi","lastname":"Gill","age":30,"gender":"M","address":"427 Chester Street","employer":"Avit","email":"noemigill@avit.com","city":"Chesterfield","state":"AL"} +{"index":{"_id":"769"}} +{"account_number":769,"balance":15362,"firstname":"Francis","lastname":"Beck","age":28,"gender":"M","address":"454 Livingston Street","employer":"Furnafix","email":"francisbeck@furnafix.com","city":"Dunnavant","state":"HI"} +{"index":{"_id":"771"}} +{"account_number":771,"balance":32784,"firstname":"Jocelyn","lastname":"Boone","age":23,"gender":"M","address":"513 Division Avenue","employer":"Collaire","email":"jocelynboone@collaire.com","city":"Lisco","state":"VT"} +{"index":{"_id":"776"}} +{"account_number":776,"balance":29177,"firstname":"Duke","lastname":"Atkinson","age":24,"gender":"M","address":"520 Doscher Street","employer":"Tripsch","email":"dukeatkinson@tripsch.com","city":"Lafferty","state":"NC"} +{"index":{"_id":"783"}} +{"account_number":783,"balance":11911,"firstname":"Faith","lastname":"Cooper","age":25,"gender":"F","address":"539 Rapelye Street","employer":"Insuron","email":"faithcooper@insuron.com","city":"Jennings","state":"MN"} +{"index":{"_id":"788"}} +{"account_number":788,"balance":12473,"firstname":"Marianne","lastname":"Aguilar","age":39,"gender":"F","address":"213 Holly Street","employer":"Marqet","email":"marianneaguilar@marqet.com","city":"Alfarata","state":"HI"} +{"index":{"_id":"790"}} +{"account_number":790,"balance":29912,"firstname":"Ellis","lastname":"Sullivan","age":39,"gender":"F","address":"877 Coyle Street","employer":"Enersave","email":"ellissullivan@enersave.com","city":"Canby","state":"MS"} +{"index":{"_id":"795"}} +{"account_number":795,"balance":31450,"firstname":"Bruce","lastname":"Avila","age":34,"gender":"M","address":"865 Newkirk Placez","employer":"Plasmosis","email":"bruceavila@plasmosis.com","city":"Ada","state":"ID"} +{"index":{"_id":"803"}} +{"account_number":803,"balance":49567,"firstname":"Marissa","lastname":"Spears","age":25,"gender":"M","address":"963 Highland Avenue","employer":"Centregy","email":"marissaspears@centregy.com","city":"Bloomington","state":"MS"} +{"index":{"_id":"808"}} +{"account_number":808,"balance":11251,"firstname":"Nola","lastname":"Quinn","age":20,"gender":"M","address":"863 Wythe Place","employer":"Iplax","email":"nolaquinn@iplax.com","city":"Cuylerville","state":"NH"} +{"index":{"_id":"810"}} +{"account_number":810,"balance":10563,"firstname":"Alyssa","lastname":"Ortega","age":40,"gender":"M","address":"977 Clymer Street","employer":"Eventage","email":"alyssaortega@eventage.com","city":"Convent","state":"SC"} +{"index":{"_id":"815"}} +{"account_number":815,"balance":19336,"firstname":"Guthrie","lastname":"Morse","age":30,"gender":"M","address":"685 Vandalia Avenue","employer":"Gronk","email":"guthriemorse@gronk.com","city":"Fowlerville","state":"OR"} +{"index":{"_id":"822"}} +{"account_number":822,"balance":13024,"firstname":"Hicks","lastname":"Farrell","age":25,"gender":"M","address":"468 Middleton Street","employer":"Zolarex","email":"hicksfarrell@zolarex.com","city":"Columbus","state":"OR"} +{"index":{"_id":"827"}} +{"account_number":827,"balance":37536,"firstname":"Naomi","lastname":"Ball","age":29,"gender":"F","address":"319 Stewart Street","employer":"Isotronic","email":"naomiball@isotronic.com","city":"Trona","state":"NM"} +{"index":{"_id":"834"}} +{"account_number":834,"balance":38049,"firstname":"Sybil","lastname":"Carrillo","age":25,"gender":"M","address":"359 Baughman Place","employer":"Phuel","email":"sybilcarrillo@phuel.com","city":"Kohatk","state":"CT"} +{"index":{"_id":"839"}} +{"account_number":839,"balance":38292,"firstname":"Langley","lastname":"Neal","age":39,"gender":"F","address":"565 Newton Street","employer":"Liquidoc","email":"langleyneal@liquidoc.com","city":"Osage","state":"AL"} +{"index":{"_id":"841"}} +{"account_number":841,"balance":28291,"firstname":"Dalton","lastname":"Waters","age":21,"gender":"M","address":"859 Grand Street","employer":"Malathion","email":"daltonwaters@malathion.com","city":"Tonopah","state":"AZ"} +{"index":{"_id":"846"}} +{"account_number":846,"balance":35099,"firstname":"Maureen","lastname":"Glass","age":22,"gender":"M","address":"140 Amherst Street","employer":"Stelaecor","email":"maureenglass@stelaecor.com","city":"Cucumber","state":"IL"} +{"index":{"_id":"853"}} +{"account_number":853,"balance":38353,"firstname":"Travis","lastname":"Parks","age":40,"gender":"M","address":"930 Bay Avenue","employer":"Pyramax","email":"travisparks@pyramax.com","city":"Gadsden","state":"ND"} +{"index":{"_id":"858"}} +{"account_number":858,"balance":23194,"firstname":"Small","lastname":"Hatfield","age":36,"gender":"M","address":"593 Tennis Court","employer":"Letpro","email":"smallhatfield@letpro.com","city":"Haena","state":"KS"} +{"index":{"_id":"860"}} +{"account_number":860,"balance":23613,"firstname":"Clark","lastname":"Boyd","age":37,"gender":"M","address":"501 Rock Street","employer":"Deepends","email":"clarkboyd@deepends.com","city":"Whitewater","state":"MA"} +{"index":{"_id":"865"}} +{"account_number":865,"balance":10574,"firstname":"Cook","lastname":"Kelley","age":28,"gender":"F","address":"865 Lincoln Terrace","employer":"Quizmo","email":"cookkelley@quizmo.com","city":"Kansas","state":"KY"} +{"index":{"_id":"872"}} +{"account_number":872,"balance":26314,"firstname":"Jane","lastname":"Greer","age":36,"gender":"F","address":"717 Hewes Street","employer":"Newcube","email":"janegreer@newcube.com","city":"Delshire","state":"DE"} +{"index":{"_id":"877"}} +{"account_number":877,"balance":42879,"firstname":"Tracey","lastname":"Ruiz","age":34,"gender":"F","address":"141 Tompkins Avenue","employer":"Waab","email":"traceyruiz@waab.com","city":"Zeba","state":"NM"} +{"index":{"_id":"884"}} +{"account_number":884,"balance":29316,"firstname":"Reva","lastname":"Rosa","age":40,"gender":"M","address":"784 Greene Avenue","employer":"Urbanshee","email":"revarosa@urbanshee.com","city":"Bakersville","state":"MS"} +{"index":{"_id":"889"}} +{"account_number":889,"balance":26464,"firstname":"Fischer","lastname":"Klein","age":38,"gender":"F","address":"948 Juliana Place","employer":"Comtext","email":"fischerklein@comtext.com","city":"Jackpot","state":"PA"} +{"index":{"_id":"891"}} +{"account_number":891,"balance":34829,"firstname":"Jacobson","lastname":"Clemons","age":24,"gender":"F","address":"507 Wilson Street","employer":"Quilm","email":"jacobsonclemons@quilm.com","city":"Muir","state":"TX"} +{"index":{"_id":"896"}} +{"account_number":896,"balance":31947,"firstname":"Buckley","lastname":"Peterson","age":26,"gender":"M","address":"217 Beayer Place","employer":"Earwax","email":"buckleypeterson@earwax.com","city":"Franklin","state":"DE"} +{"index":{"_id":"904"}} +{"account_number":904,"balance":27707,"firstname":"Mendez","lastname":"Mcneil","age":26,"gender":"M","address":"431 Halsey Street","employer":"Macronaut","email":"mendezmcneil@macronaut.com","city":"Troy","state":"OK"} +{"index":{"_id":"909"}} +{"account_number":909,"balance":18421,"firstname":"Stark","lastname":"Lewis","age":36,"gender":"M","address":"409 Tilden Avenue","employer":"Frosnex","email":"starklewis@frosnex.com","city":"Axis","state":"CA"} +{"index":{"_id":"911"}} +{"account_number":911,"balance":42655,"firstname":"Annie","lastname":"Lyons","age":21,"gender":"M","address":"518 Woods Place","employer":"Enerforce","email":"annielyons@enerforce.com","city":"Stagecoach","state":"MA"} +{"index":{"_id":"916"}} +{"account_number":916,"balance":47887,"firstname":"Jarvis","lastname":"Alexander","age":40,"gender":"M","address":"406 Bergen Avenue","employer":"Equitax","email":"jarvisalexander@equitax.com","city":"Haring","state":"KY"} +{"index":{"_id":"923"}} +{"account_number":923,"balance":48466,"firstname":"Mueller","lastname":"Mckee","age":26,"gender":"M","address":"298 Ruby Street","employer":"Luxuria","email":"muellermckee@luxuria.com","city":"Coleville","state":"TN"} +{"index":{"_id":"928"}} +{"account_number":928,"balance":19611,"firstname":"Hester","lastname":"Copeland","age":22,"gender":"F","address":"425 Cropsey Avenue","employer":"Dymi","email":"hestercopeland@dymi.com","city":"Wolcott","state":"NE"} +{"index":{"_id":"930"}} +{"account_number":930,"balance":47257,"firstname":"Kinney","lastname":"Lawson","age":39,"gender":"M","address":"501 Raleigh Place","employer":"Neptide","email":"kinneylawson@neptide.com","city":"Deltaville","state":"MD"} +{"index":{"_id":"935"}} +{"account_number":935,"balance":4959,"firstname":"Flowers","lastname":"Robles","age":30,"gender":"M","address":"201 Hull Street","employer":"Xelegyl","email":"flowersrobles@xelegyl.com","city":"Rehrersburg","state":"AL"} +{"index":{"_id":"942"}} +{"account_number":942,"balance":21299,"firstname":"Hamilton","lastname":"Clayton","age":26,"gender":"M","address":"413 Debevoise Street","employer":"Architax","email":"hamiltonclayton@architax.com","city":"Terlingua","state":"NM"} +{"index":{"_id":"947"}} +{"account_number":947,"balance":22039,"firstname":"Virgie","lastname":"Garza","age":30,"gender":"M","address":"903 Matthews Court","employer":"Plasmox","email":"virgiegarza@plasmox.com","city":"Somerset","state":"WY"} +{"index":{"_id":"954"}} +{"account_number":954,"balance":49404,"firstname":"Jenna","lastname":"Martin","age":22,"gender":"M","address":"688 Hart Street","employer":"Zinca","email":"jennamartin@zinca.com","city":"Oasis","state":"MD"} +{"index":{"_id":"959"}} +{"account_number":959,"balance":34743,"firstname":"Shaffer","lastname":"Cervantes","age":40,"gender":"M","address":"931 Varick Avenue","employer":"Oceanica","email":"shaffercervantes@oceanica.com","city":"Bowie","state":"AL"} +{"index":{"_id":"961"}} +{"account_number":961,"balance":43219,"firstname":"Betsy","lastname":"Hyde","age":27,"gender":"F","address":"183 Junius Street","employer":"Tubalum","email":"betsyhyde@tubalum.com","city":"Driftwood","state":"TX"} +{"index":{"_id":"966"}} +{"account_number":966,"balance":20619,"firstname":"Susanne","lastname":"Rodriguez","age":35,"gender":"F","address":"255 Knickerbocker Avenue","employer":"Comtrek","email":"susannerodriguez@comtrek.com","city":"Trinway","state":"TX"} +{"index":{"_id":"973"}} +{"account_number":973,"balance":45756,"firstname":"Rice","lastname":"Farmer","age":31,"gender":"M","address":"476 Nassau Avenue","employer":"Photobin","email":"ricefarmer@photobin.com","city":"Suitland","state":"ME"} +{"index":{"_id":"978"}} +{"account_number":978,"balance":21459,"firstname":"Melanie","lastname":"Rojas","age":33,"gender":"M","address":"991 Java Street","employer":"Kage","email":"melanierojas@kage.com","city":"Greenock","state":"VT"} +{"index":{"_id":"980"}} +{"account_number":980,"balance":42436,"firstname":"Cash","lastname":"Collier","age":33,"gender":"F","address":"999 Sapphire Street","employer":"Ceprene","email":"cashcollier@ceprene.com","city":"Glidden","state":"AK"} +{"index":{"_id":"985"}} +{"account_number":985,"balance":20083,"firstname":"Martin","lastname":"Gardner","age":28,"gender":"F","address":"644 Fairview Place","employer":"Golistic","email":"martingardner@golistic.com","city":"Connerton","state":"NJ"} +{"index":{"_id":"992"}} +{"account_number":992,"balance":11413,"firstname":"Kristie","lastname":"Kennedy","age":33,"gender":"F","address":"750 Hudson Avenue","employer":"Ludak","email":"kristiekennedy@ludak.com","city":"Warsaw","state":"WY"} +{"index":{"_id":"997"}} +{"account_number":997,"balance":25311,"firstname":"Combs","lastname":"Frederick","age":20,"gender":"M","address":"586 Lloyd Court","employer":"Pathways","email":"combsfrederick@pathways.com","city":"Williamson","state":"CA"} +{"index":{"_id":"3"}} +{"account_number":3,"balance":44947,"firstname":"Levine","lastname":"Burks","age":26,"gender":"F","address":"328 Wilson Avenue","employer":"Amtap","email":"levineburks@amtap.com","city":"Cochranville","state":"HI"} +{"index":{"_id":"8"}} +{"account_number":8,"balance":48868,"firstname":"Jan","lastname":"Burns","age":35,"gender":"M","address":"699 Visitation Place","employer":"Glasstep","email":"janburns@glasstep.com","city":"Wakulla","state":"AZ"} +{"index":{"_id":"10"}} +{"account_number":10,"balance":46170,"firstname":"Dominique","lastname":"Park","age":37,"gender":"F","address":"100 Gatling Place","employer":"Conjurica","email":"dominiquepark@conjurica.com","city":"Omar","state":"NJ"} +{"index":{"_id":"15"}} +{"account_number":15,"balance":43456,"firstname":"Bobbie","lastname":"Sexton","age":21,"gender":"M","address":"232 Sedgwick Place","employer":"Zytrex","email":"bobbiesexton@zytrex.com","city":"Hendersonville","state":"CA"} +{"index":{"_id":"22"}} +{"account_number":22,"balance":40283,"firstname":"Barrera","lastname":"Terrell","age":23,"gender":"F","address":"292 Orange Street","employer":"Steelfab","email":"barreraterrell@steelfab.com","city":"Bynum","state":"ME"} +{"index":{"_id":"27"}} +{"account_number":27,"balance":6176,"firstname":"Meyers","lastname":"Williamson","age":26,"gender":"F","address":"675 Henderson Walk","employer":"Plexia","email":"meyerswilliamson@plexia.com","city":"Richmond","state":"AZ"} +{"index":{"_id":"34"}} +{"account_number":34,"balance":35379,"firstname":"Ellison","lastname":"Kim","age":30,"gender":"F","address":"986 Revere Place","employer":"Signity","email":"ellisonkim@signity.com","city":"Sehili","state":"IL"} +{"index":{"_id":"39"}} +{"account_number":39,"balance":38688,"firstname":"Bowers","lastname":"Mendez","age":22,"gender":"F","address":"665 Bennet Court","employer":"Farmage","email":"bowersmendez@farmage.com","city":"Duryea","state":"PA"} +{"index":{"_id":"41"}} +{"account_number":41,"balance":36060,"firstname":"Hancock","lastname":"Holden","age":20,"gender":"M","address":"625 Gaylord Drive","employer":"Poochies","email":"hancockholden@poochies.com","city":"Alamo","state":"KS"} +{"index":{"_id":"46"}} +{"account_number":46,"balance":12351,"firstname":"Karla","lastname":"Bowman","age":23,"gender":"M","address":"554 Chapel Street","employer":"Undertap","email":"karlabowman@undertap.com","city":"Sylvanite","state":"DC"} +{"index":{"_id":"53"}} +{"account_number":53,"balance":28101,"firstname":"Kathryn","lastname":"Payne","age":29,"gender":"F","address":"467 Louis Place","employer":"Katakana","email":"kathrynpayne@katakana.com","city":"Harviell","state":"SD"} +{"index":{"_id":"58"}} +{"account_number":58,"balance":31697,"firstname":"Marva","lastname":"Cannon","age":40,"gender":"M","address":"993 Highland Place","employer":"Comcubine","email":"marvacannon@comcubine.com","city":"Orviston","state":"MO"} +{"index":{"_id":"60"}} +{"account_number":60,"balance":45955,"firstname":"Maude","lastname":"Casey","age":31,"gender":"F","address":"566 Strauss Street","employer":"Quilch","email":"maudecasey@quilch.com","city":"Enlow","state":"GA"} +{"index":{"_id":"65"}} +{"account_number":65,"balance":23282,"firstname":"Leonor","lastname":"Pruitt","age":24,"gender":"M","address":"974 Terrace Place","employer":"Velos","email":"leonorpruitt@velos.com","city":"Devon","state":"WI"} +{"index":{"_id":"72"}} +{"account_number":72,"balance":9732,"firstname":"Barlow","lastname":"Rhodes","age":25,"gender":"F","address":"891 Clinton Avenue","employer":"Zialactic","email":"barlowrhodes@zialactic.com","city":"Echo","state":"TN"} +{"index":{"_id":"77"}} +{"account_number":77,"balance":5724,"firstname":"Byrd","lastname":"Conley","age":24,"gender":"F","address":"698 Belmont Avenue","employer":"Zidox","email":"byrdconley@zidox.com","city":"Rockbridge","state":"SC"} +{"index":{"_id":"84"}} +{"account_number":84,"balance":3001,"firstname":"Hutchinson","lastname":"Newton","age":34,"gender":"F","address":"553 Locust Street","employer":"Zaggles","email":"hutchinsonnewton@zaggles.com","city":"Snyderville","state":"DC"} +{"index":{"_id":"89"}} +{"account_number":89,"balance":13263,"firstname":"Mcdowell","lastname":"Bradley","age":28,"gender":"M","address":"960 Howard Alley","employer":"Grok","email":"mcdowellbradley@grok.com","city":"Toftrees","state":"TX"} +{"index":{"_id":"91"}} +{"account_number":91,"balance":29799,"firstname":"Vonda","lastname":"Galloway","age":20,"gender":"M","address":"988 Voorhies Avenue","employer":"Illumity","email":"vondagalloway@illumity.com","city":"Holcombe","state":"HI"} +{"index":{"_id":"96"}} +{"account_number":96,"balance":15933,"firstname":"Shirley","lastname":"Edwards","age":38,"gender":"M","address":"817 Caton Avenue","employer":"Equitox","email":"shirleyedwards@equitox.com","city":"Nelson","state":"MA"} +{"index":{"_id":"104"}} +{"account_number":104,"balance":32619,"firstname":"Casey","lastname":"Roth","age":29,"gender":"M","address":"963 Railroad Avenue","employer":"Hotcakes","email":"caseyroth@hotcakes.com","city":"Davenport","state":"OH"} +{"index":{"_id":"109"}} +{"account_number":109,"balance":25812,"firstname":"Gretchen","lastname":"Dawson","age":31,"gender":"M","address":"610 Bethel Loop","employer":"Tetak","email":"gretchendawson@tetak.com","city":"Hailesboro","state":"CO"} +{"index":{"_id":"111"}} +{"account_number":111,"balance":1481,"firstname":"Traci","lastname":"Allison","age":35,"gender":"M","address":"922 Bryant Street","employer":"Enjola","email":"traciallison@enjola.com","city":"Robinette","state":"OR"} +{"index":{"_id":"116"}} +{"account_number":116,"balance":21335,"firstname":"Hobbs","lastname":"Wright","age":24,"gender":"M","address":"965 Temple Court","employer":"Netbook","email":"hobbswright@netbook.com","city":"Strong","state":"CA"} +{"index":{"_id":"123"}} +{"account_number":123,"balance":3079,"firstname":"Cleo","lastname":"Beach","age":27,"gender":"F","address":"653 Haring Street","employer":"Proxsoft","email":"cleobeach@proxsoft.com","city":"Greensburg","state":"ME"} +{"index":{"_id":"128"}} +{"account_number":128,"balance":3556,"firstname":"Mack","lastname":"Bullock","age":34,"gender":"F","address":"462 Ingraham Street","employer":"Terascape","email":"mackbullock@terascape.com","city":"Eureka","state":"PA"} +{"index":{"_id":"130"}} +{"account_number":130,"balance":24171,"firstname":"Roxie","lastname":"Cantu","age":33,"gender":"M","address":"841 Catherine Street","employer":"Skybold","email":"roxiecantu@skybold.com","city":"Deputy","state":"NE"} +{"index":{"_id":"135"}} +{"account_number":135,"balance":24885,"firstname":"Stevenson","lastname":"Crosby","age":40,"gender":"F","address":"473 Boardwalk ","employer":"Accel","email":"stevensoncrosby@accel.com","city":"Norris","state":"OK"} +{"index":{"_id":"142"}} +{"account_number":142,"balance":4544,"firstname":"Vang","lastname":"Hughes","age":27,"gender":"M","address":"357 Landis Court","employer":"Bolax","email":"vanghughes@bolax.com","city":"Emerald","state":"WY"} +{"index":{"_id":"147"}} +{"account_number":147,"balance":35921,"firstname":"Charmaine","lastname":"Whitney","age":28,"gender":"F","address":"484 Seton Place","employer":"Comveyer","email":"charmainewhitney@comveyer.com","city":"Dexter","state":"DC"} +{"index":{"_id":"154"}} +{"account_number":154,"balance":40945,"firstname":"Burns","lastname":"Solis","age":31,"gender":"M","address":"274 Lorraine Street","employer":"Rodemco","email":"burnssolis@rodemco.com","city":"Ballico","state":"WI"} +{"index":{"_id":"159"}} +{"account_number":159,"balance":1696,"firstname":"Alvarez","lastname":"Mack","age":22,"gender":"F","address":"897 Manor Court","employer":"Snorus","email":"alvarezmack@snorus.com","city":"Rosedale","state":"CA"} +{"index":{"_id":"161"}} +{"account_number":161,"balance":4659,"firstname":"Doreen","lastname":"Randall","age":37,"gender":"F","address":"178 Court Street","employer":"Calcula","email":"doreenrandall@calcula.com","city":"Belmont","state":"TX"} +{"index":{"_id":"166"}} +{"account_number":166,"balance":33847,"firstname":"Rutledge","lastname":"Rivas","age":23,"gender":"M","address":"352 Verona Street","employer":"Virxo","email":"rutledgerivas@virxo.com","city":"Brandermill","state":"NE"} +{"index":{"_id":"173"}} +{"account_number":173,"balance":5989,"firstname":"Whitley","lastname":"Blevins","age":32,"gender":"M","address":"127 Brooklyn Avenue","employer":"Pawnagra","email":"whitleyblevins@pawnagra.com","city":"Rodanthe","state":"ND"} +{"index":{"_id":"178"}} +{"account_number":178,"balance":36735,"firstname":"Clements","lastname":"Finley","age":39,"gender":"F","address":"270 Story Court","employer":"Imaginart","email":"clementsfinley@imaginart.com","city":"Lookingglass","state":"MN"} +{"index":{"_id":"180"}} +{"account_number":180,"balance":34236,"firstname":"Ursula","lastname":"Goodman","age":32,"gender":"F","address":"414 Clinton Street","employer":"Earthmark","email":"ursulagoodman@earthmark.com","city":"Rote","state":"AR"} +{"index":{"_id":"185"}} +{"account_number":185,"balance":43532,"firstname":"Laurel","lastname":"Cline","age":40,"gender":"M","address":"788 Fenimore Street","employer":"Prismatic","email":"laurelcline@prismatic.com","city":"Frank","state":"UT"} +{"index":{"_id":"192"}} +{"account_number":192,"balance":23508,"firstname":"Ramsey","lastname":"Carr","age":31,"gender":"F","address":"209 Williamsburg Street","employer":"Strezzo","email":"ramseycarr@strezzo.com","city":"Grapeview","state":"NM"} +{"index":{"_id":"197"}} +{"account_number":197,"balance":17246,"firstname":"Sweet","lastname":"Sanders","age":33,"gender":"F","address":"712 Homecrest Court","employer":"Isosure","email":"sweetsanders@isosure.com","city":"Sheatown","state":"VT"} +{"index":{"_id":"200"}} +{"account_number":200,"balance":26210,"firstname":"Teri","lastname":"Hester","age":39,"gender":"M","address":"653 Abbey Court","employer":"Electonic","email":"terihester@electonic.com","city":"Martell","state":"MD"} +{"index":{"_id":"205"}} +{"account_number":205,"balance":45493,"firstname":"Johnson","lastname":"Chang","age":28,"gender":"F","address":"331 John Street","employer":"Gleamink","email":"johnsonchang@gleamink.com","city":"Sultana","state":"KS"} +{"index":{"_id":"212"}} +{"account_number":212,"balance":10299,"firstname":"Marisol","lastname":"Fischer","age":39,"gender":"M","address":"362 Prince Street","employer":"Autograte","email":"marisolfischer@autograte.com","city":"Oley","state":"SC"} +{"index":{"_id":"217"}} +{"account_number":217,"balance":33730,"firstname":"Sally","lastname":"Mccoy","age":38,"gender":"F","address":"854 Corbin Place","employer":"Omnigog","email":"sallymccoy@omnigog.com","city":"Escondida","state":"FL"} +{"index":{"_id":"224"}} +{"account_number":224,"balance":42708,"firstname":"Billie","lastname":"Nixon","age":28,"gender":"F","address":"241 Kaufman Place","employer":"Xanide","email":"billienixon@xanide.com","city":"Chapin","state":"NY"} +{"index":{"_id":"229"}} +{"account_number":229,"balance":2740,"firstname":"Jana","lastname":"Hensley","age":30,"gender":"M","address":"176 Erasmus Street","employer":"Isotrack","email":"janahensley@isotrack.com","city":"Caledonia","state":"ME"} +{"index":{"_id":"231"}} +{"account_number":231,"balance":46180,"firstname":"Essie","lastname":"Clarke","age":34,"gender":"F","address":"308 Harbor Lane","employer":"Pharmacon","email":"essieclarke@pharmacon.com","city":"Fillmore","state":"MS"} +{"index":{"_id":"236"}} +{"account_number":236,"balance":41200,"firstname":"Suzanne","lastname":"Bird","age":39,"gender":"F","address":"219 Luquer Street","employer":"Imant","email":"suzannebird@imant.com","city":"Bainbridge","state":"NY"} +{"index":{"_id":"243"}} +{"account_number":243,"balance":29902,"firstname":"Evangelina","lastname":"Perez","age":20,"gender":"M","address":"787 Joval Court","employer":"Keengen","email":"evangelinaperez@keengen.com","city":"Mulberry","state":"SD"} +{"index":{"_id":"248"}} +{"account_number":248,"balance":49989,"firstname":"West","lastname":"England","age":36,"gender":"M","address":"717 Hendrickson Place","employer":"Obliq","email":"westengland@obliq.com","city":"Maury","state":"WA"} +{"index":{"_id":"250"}} +{"account_number":250,"balance":27893,"firstname":"Earlene","lastname":"Ellis","age":39,"gender":"F","address":"512 Bay Street","employer":"Codact","email":"earleneellis@codact.com","city":"Sunwest","state":"GA"} +{"index":{"_id":"255"}} +{"account_number":255,"balance":49339,"firstname":"Iva","lastname":"Rivers","age":38,"gender":"M","address":"470 Rost Place","employer":"Mantrix","email":"ivarivers@mantrix.com","city":"Disautel","state":"MD"} +{"index":{"_id":"262"}} +{"account_number":262,"balance":30289,"firstname":"Tameka","lastname":"Levine","age":36,"gender":"F","address":"815 Atlantic Avenue","employer":"Acium","email":"tamekalevine@acium.com","city":"Winchester","state":"SD"} +{"index":{"_id":"267"}} +{"account_number":267,"balance":42753,"firstname":"Weeks","lastname":"Castillo","age":21,"gender":"F","address":"526 Holt Court","employer":"Talendula","email":"weekscastillo@talendula.com","city":"Washington","state":"NV"} +{"index":{"_id":"274"}} +{"account_number":274,"balance":12104,"firstname":"Frieda","lastname":"House","age":33,"gender":"F","address":"171 Banker Street","employer":"Quonk","email":"friedahouse@quonk.com","city":"Aberdeen","state":"NJ"} +{"index":{"_id":"279"}} +{"account_number":279,"balance":15904,"firstname":"Chapman","lastname":"Hart","age":32,"gender":"F","address":"902 Bliss Terrace","employer":"Kongene","email":"chapmanhart@kongene.com","city":"Bradenville","state":"NJ"} +{"index":{"_id":"281"}} +{"account_number":281,"balance":39830,"firstname":"Bean","lastname":"Aguirre","age":20,"gender":"F","address":"133 Pilling Street","employer":"Amril","email":"beanaguirre@amril.com","city":"Waterview","state":"TX"} +{"index":{"_id":"286"}} +{"account_number":286,"balance":39063,"firstname":"Rosetta","lastname":"Turner","age":35,"gender":"M","address":"169 Jefferson Avenue","employer":"Spacewax","email":"rosettaturner@spacewax.com","city":"Stewart","state":"MO"} +{"index":{"_id":"293"}} +{"account_number":293,"balance":29867,"firstname":"Cruz","lastname":"Carver","age":28,"gender":"F","address":"465 Boerum Place","employer":"Vitricomp","email":"cruzcarver@vitricomp.com","city":"Crayne","state":"CO"} +{"index":{"_id":"298"}} +{"account_number":298,"balance":34334,"firstname":"Bullock","lastname":"Marsh","age":20,"gender":"M","address":"589 Virginia Place","employer":"Renovize","email":"bullockmarsh@renovize.com","city":"Coinjock","state":"UT"} +{"index":{"_id":"301"}} +{"account_number":301,"balance":16782,"firstname":"Minerva","lastname":"Graham","age":35,"gender":"M","address":"532 Harrison Place","employer":"Sureplex","email":"minervagraham@sureplex.com","city":"Belleview","state":"GA"} +{"index":{"_id":"306"}} +{"account_number":306,"balance":2171,"firstname":"Hensley","lastname":"Hardin","age":40,"gender":"M","address":"196 Maujer Street","employer":"Neocent","email":"hensleyhardin@neocent.com","city":"Reinerton","state":"HI"} +{"index":{"_id":"313"}} +{"account_number":313,"balance":34108,"firstname":"Alston","lastname":"Henderson","age":36,"gender":"F","address":"132 Prescott Place","employer":"Prosure","email":"alstonhenderson@prosure.com","city":"Worton","state":"IA"} +{"index":{"_id":"318"}} +{"account_number":318,"balance":8512,"firstname":"Nichole","lastname":"Pearson","age":34,"gender":"F","address":"656 Lacon Court","employer":"Yurture","email":"nicholepearson@yurture.com","city":"Juarez","state":"MO"} +{"index":{"_id":"320"}} +{"account_number":320,"balance":34521,"firstname":"Patti","lastname":"Brennan","age":37,"gender":"F","address":"870 Degraw Street","employer":"Cognicode","email":"pattibrennan@cognicode.com","city":"Torboy","state":"FL"} +{"index":{"_id":"325"}} +{"account_number":325,"balance":1956,"firstname":"Magdalena","lastname":"Simmons","age":25,"gender":"F","address":"681 Townsend Street","employer":"Geekosis","email":"magdalenasimmons@geekosis.com","city":"Sterling","state":"CA"} +{"index":{"_id":"332"}} +{"account_number":332,"balance":37770,"firstname":"Shepherd","lastname":"Davenport","age":28,"gender":"F","address":"586 Montague Terrace","employer":"Ecraze","email":"shepherddavenport@ecraze.com","city":"Accoville","state":"NM"} +{"index":{"_id":"337"}} +{"account_number":337,"balance":43432,"firstname":"Monroe","lastname":"Stafford","age":37,"gender":"F","address":"183 Seigel Street","employer":"Centuria","email":"monroestafford@centuria.com","city":"Camino","state":"DE"} +{"index":{"_id":"344"}} +{"account_number":344,"balance":42654,"firstname":"Sasha","lastname":"Baxter","age":35,"gender":"F","address":"700 Bedford Place","employer":"Callflex","email":"sashabaxter@callflex.com","city":"Campo","state":"MI"} +{"index":{"_id":"349"}} +{"account_number":349,"balance":24180,"firstname":"Allison","lastname":"Fitzpatrick","age":22,"gender":"F","address":"913 Arlington Avenue","employer":"Veraq","email":"allisonfitzpatrick@veraq.com","city":"Marbury","state":"TX"} +{"index":{"_id":"351"}} +{"account_number":351,"balance":47089,"firstname":"Hendrix","lastname":"Stephens","age":29,"gender":"M","address":"181 Beaver Street","employer":"Recrisys","email":"hendrixstephens@recrisys.com","city":"Denio","state":"OR"} +{"index":{"_id":"356"}} +{"account_number":356,"balance":34540,"firstname":"Lourdes","lastname":"Valdez","age":20,"gender":"F","address":"700 Anchorage Place","employer":"Interloo","email":"lourdesvaldez@interloo.com","city":"Goldfield","state":"OK"} +{"index":{"_id":"363"}} +{"account_number":363,"balance":34007,"firstname":"Peggy","lastname":"Bright","age":21,"gender":"M","address":"613 Engert Avenue","employer":"Inventure","email":"peggybright@inventure.com","city":"Chautauqua","state":"ME"} +{"index":{"_id":"368"}} +{"account_number":368,"balance":23535,"firstname":"Hooper","lastname":"Tyson","age":39,"gender":"M","address":"892 Taaffe Place","employer":"Zaggle","email":"hoopertyson@zaggle.com","city":"Nutrioso","state":"ME"} +{"index":{"_id":"370"}} +{"account_number":370,"balance":28499,"firstname":"Oneill","lastname":"Carney","age":25,"gender":"F","address":"773 Adelphi Street","employer":"Bedder","email":"oneillcarney@bedder.com","city":"Yorklyn","state":"FL"} +{"index":{"_id":"375"}} +{"account_number":375,"balance":23860,"firstname":"Phoebe","lastname":"Patton","age":25,"gender":"M","address":"564 Hale Avenue","employer":"Xoggle","email":"phoebepatton@xoggle.com","city":"Brule","state":"NM"} +{"index":{"_id":"382"}} +{"account_number":382,"balance":42061,"firstname":"Finley","lastname":"Singleton","age":37,"gender":"F","address":"407 Clay Street","employer":"Quarex","email":"finleysingleton@quarex.com","city":"Bedias","state":"LA"} +{"index":{"_id":"387"}} +{"account_number":387,"balance":35916,"firstname":"April","lastname":"Hill","age":29,"gender":"M","address":"818 Bayard Street","employer":"Kengen","email":"aprilhill@kengen.com","city":"Chloride","state":"NC"} +{"index":{"_id":"394"}} +{"account_number":394,"balance":6121,"firstname":"Lorrie","lastname":"Nunez","age":38,"gender":"M","address":"221 Ralph Avenue","employer":"Bullzone","email":"lorrienunez@bullzone.com","city":"Longoria","state":"ID"} +{"index":{"_id":"399"}} +{"account_number":399,"balance":32587,"firstname":"Carmela","lastname":"Franks","age":23,"gender":"M","address":"617 Dewey Place","employer":"Zensure","email":"carmelafranks@zensure.com","city":"Sanders","state":"DC"} +{"index":{"_id":"402"}} +{"account_number":402,"balance":1282,"firstname":"Pacheco","lastname":"Rosales","age":32,"gender":"M","address":"538 Pershing Loop","employer":"Circum","email":"pachecorosales@circum.com","city":"Elbert","state":"ID"} +{"index":{"_id":"407"}} +{"account_number":407,"balance":36417,"firstname":"Gilda","lastname":"Jacobson","age":29,"gender":"F","address":"883 Loring Avenue","employer":"Comveyor","email":"gildajacobson@comveyor.com","city":"Topaz","state":"NH"} +{"index":{"_id":"414"}} +{"account_number":414,"balance":17506,"firstname":"Conway","lastname":"Daugherty","age":37,"gender":"F","address":"643 Kermit Place","employer":"Lyria","email":"conwaydaugherty@lyria.com","city":"Vaughn","state":"NV"} +{"index":{"_id":"419"}} +{"account_number":419,"balance":34847,"firstname":"Helen","lastname":"Montoya","age":29,"gender":"F","address":"736 Kingsland Avenue","employer":"Hairport","email":"helenmontoya@hairport.com","city":"Edinburg","state":"NE"} +{"index":{"_id":"421"}} +{"account_number":421,"balance":46868,"firstname":"Tamika","lastname":"Mccall","age":27,"gender":"F","address":"764 Bragg Court","employer":"Eventix","email":"tamikamccall@eventix.com","city":"Tivoli","state":"RI"} +{"index":{"_id":"426"}} +{"account_number":426,"balance":4499,"firstname":"Julie","lastname":"Parsons","age":31,"gender":"M","address":"768 Keap Street","employer":"Goko","email":"julieparsons@goko.com","city":"Coldiron","state":"VA"} +{"index":{"_id":"433"}} +{"account_number":433,"balance":19266,"firstname":"Wilkinson","lastname":"Flowers","age":39,"gender":"M","address":"154 Douglass Street","employer":"Xsports","email":"wilkinsonflowers@xsports.com","city":"Coultervillle","state":"MN"} +{"index":{"_id":"438"}} +{"account_number":438,"balance":16367,"firstname":"Walter","lastname":"Velez","age":27,"gender":"F","address":"931 Farragut Road","employer":"Virva","email":"waltervelez@virva.com","city":"Tyro","state":"WV"} +{"index":{"_id":"440"}} +{"account_number":440,"balance":41590,"firstname":"Ray","lastname":"Wiley","age":31,"gender":"F","address":"102 Barwell Terrace","employer":"Polaria","email":"raywiley@polaria.com","city":"Hardyville","state":"IA"} +{"index":{"_id":"445"}} +{"account_number":445,"balance":41178,"firstname":"Rodriguez","lastname":"Macias","age":34,"gender":"M","address":"164 Boerum Street","employer":"Xylar","email":"rodriguezmacias@xylar.com","city":"Riner","state":"AL"} +{"index":{"_id":"452"}} +{"account_number":452,"balance":3589,"firstname":"Blackwell","lastname":"Delaney","age":39,"gender":"F","address":"443 Sackett Street","employer":"Imkan","email":"blackwelldelaney@imkan.com","city":"Gasquet","state":"DC"} +{"index":{"_id":"457"}} +{"account_number":457,"balance":14057,"firstname":"Bush","lastname":"Gordon","age":34,"gender":"M","address":"975 Dakota Place","employer":"Softmicro","email":"bushgordon@softmicro.com","city":"Chemung","state":"PA"} +{"index":{"_id":"464"}} +{"account_number":464,"balance":20504,"firstname":"Cobb","lastname":"Humphrey","age":21,"gender":"M","address":"823 Sunnyside Avenue","employer":"Apexia","email":"cobbhumphrey@apexia.com","city":"Wintersburg","state":"NY"} +{"index":{"_id":"469"}} +{"account_number":469,"balance":26509,"firstname":"Marci","lastname":"Shepherd","age":26,"gender":"M","address":"565 Hall Street","employer":"Shadease","email":"marcishepherd@shadease.com","city":"Springhill","state":"IL"} +{"index":{"_id":"471"}} +{"account_number":471,"balance":7629,"firstname":"Juana","lastname":"Silva","age":36,"gender":"M","address":"249 Amity Street","employer":"Artworlds","email":"juanasilva@artworlds.com","city":"Norfolk","state":"TX"} +{"index":{"_id":"476"}} +{"account_number":476,"balance":33386,"firstname":"Silva","lastname":"Marks","age":31,"gender":"F","address":"183 Eldert Street","employer":"Medifax","email":"silvamarks@medifax.com","city":"Hachita","state":"RI"} +{"index":{"_id":"483"}} +{"account_number":483,"balance":6344,"firstname":"Kelley","lastname":"Harper","age":29,"gender":"M","address":"758 Preston Court","employer":"Xyqag","email":"kelleyharper@xyqag.com","city":"Healy","state":"IA"} +{"index":{"_id":"488"}} +{"account_number":488,"balance":6289,"firstname":"Wilma","lastname":"Hopkins","age":38,"gender":"M","address":"428 Lee Avenue","employer":"Entality","email":"wilmahopkins@entality.com","city":"Englevale","state":"WI"} +{"index":{"_id":"490"}} +{"account_number":490,"balance":1447,"firstname":"Strong","lastname":"Hendrix","age":26,"gender":"F","address":"134 Beach Place","employer":"Duoflex","email":"stronghendrix@duoflex.com","city":"Allentown","state":"ND"} +{"index":{"_id":"495"}} +{"account_number":495,"balance":13478,"firstname":"Abigail","lastname":"Nichols","age":40,"gender":"F","address":"887 President Street","employer":"Enquility","email":"abigailnichols@enquility.com","city":"Bagtown","state":"NM"} +{"index":{"_id":"503"}} +{"account_number":503,"balance":42649,"firstname":"Leta","lastname":"Stout","age":39,"gender":"F","address":"518 Bowery Street","employer":"Pivitol","email":"letastout@pivitol.com","city":"Boonville","state":"ND"} +{"index":{"_id":"508"}} +{"account_number":508,"balance":41300,"firstname":"Lawrence","lastname":"Mathews","age":27,"gender":"F","address":"987 Rose Street","employer":"Deviltoe","email":"lawrencemathews@deviltoe.com","city":"Woodburn","state":"FL"} +{"index":{"_id":"510"}} +{"account_number":510,"balance":48504,"firstname":"Petty","lastname":"Sykes","age":28,"gender":"M","address":"566 Village Road","employer":"Nebulean","email":"pettysykes@nebulean.com","city":"Wedgewood","state":"MO"} +{"index":{"_id":"515"}} +{"account_number":515,"balance":18531,"firstname":"Lott","lastname":"Keller","age":27,"gender":"M","address":"827 Miami Court","employer":"Translink","email":"lottkeller@translink.com","city":"Gila","state":"TX"} +{"index":{"_id":"522"}} +{"account_number":522,"balance":19879,"firstname":"Faulkner","lastname":"Garrett","age":29,"gender":"F","address":"396 Grove Place","employer":"Pigzart","email":"faulknergarrett@pigzart.com","city":"Felt","state":"AR"} +{"index":{"_id":"527"}} +{"account_number":527,"balance":2028,"firstname":"Carver","lastname":"Peters","age":35,"gender":"M","address":"816 Victor Road","employer":"Housedown","email":"carverpeters@housedown.com","city":"Nadine","state":"MD"} +{"index":{"_id":"534"}} +{"account_number":534,"balance":20470,"firstname":"Cristina","lastname":"Russo","age":25,"gender":"F","address":"500 Highlawn Avenue","employer":"Cyclonica","email":"cristinarusso@cyclonica.com","city":"Gorst","state":"KS"} +{"index":{"_id":"539"}} +{"account_number":539,"balance":24560,"firstname":"Tami","lastname":"Maddox","age":23,"gender":"F","address":"741 Pineapple Street","employer":"Accidency","email":"tamimaddox@accidency.com","city":"Kennedyville","state":"OH"} +{"index":{"_id":"541"}} +{"account_number":541,"balance":42915,"firstname":"Logan","lastname":"Burke","age":32,"gender":"M","address":"904 Clarendon Road","employer":"Overplex","email":"loganburke@overplex.com","city":"Johnsonburg","state":"OH"} +{"index":{"_id":"546"}} +{"account_number":546,"balance":43242,"firstname":"Bernice","lastname":"Sims","age":33,"gender":"M","address":"382 Columbia Street","employer":"Verbus","email":"bernicesims@verbus.com","city":"Sena","state":"KY"} +{"index":{"_id":"553"}} +{"account_number":553,"balance":28390,"firstname":"Aimee","lastname":"Cohen","age":28,"gender":"M","address":"396 Lafayette Avenue","employer":"Eplode","email":"aimeecohen@eplode.com","city":"Thatcher","state":"NJ"} +{"index":{"_id":"558"}} +{"account_number":558,"balance":8922,"firstname":"Horne","lastname":"Valenzuela","age":20,"gender":"F","address":"979 Kensington Street","employer":"Isoternia","email":"hornevalenzuela@isoternia.com","city":"Greenbush","state":"NC"} +{"index":{"_id":"560"}} +{"account_number":560,"balance":24514,"firstname":"Felecia","lastname":"Oneill","age":26,"gender":"M","address":"995 Autumn Avenue","employer":"Mediot","email":"feleciaoneill@mediot.com","city":"Joppa","state":"IN"} +{"index":{"_id":"565"}} +{"account_number":565,"balance":15197,"firstname":"Taylor","lastname":"Ingram","age":37,"gender":"F","address":"113 Will Place","employer":"Lyrichord","email":"tayloringram@lyrichord.com","city":"Collins","state":"ME"} +{"index":{"_id":"572"}} +{"account_number":572,"balance":49355,"firstname":"Therese","lastname":"Espinoza","age":20,"gender":"M","address":"994 Chester Court","employer":"Gonkle","email":"thereseespinoza@gonkle.com","city":"Hayes","state":"UT"} +{"index":{"_id":"577"}} +{"account_number":577,"balance":21398,"firstname":"Gilbert","lastname":"Serrano","age":38,"gender":"F","address":"294 Troutman Street","employer":"Senmao","email":"gilbertserrano@senmao.com","city":"Greer","state":"MT"} +{"index":{"_id":"584"}} +{"account_number":584,"balance":5346,"firstname":"Pearson","lastname":"Bryant","age":40,"gender":"F","address":"971 Heyward Street","employer":"Anacho","email":"pearsonbryant@anacho.com","city":"Bluffview","state":"MN"} +{"index":{"_id":"589"}} +{"account_number":589,"balance":33260,"firstname":"Ericka","lastname":"Cote","age":39,"gender":"F","address":"425 Bath Avenue","employer":"Venoflex","email":"erickacote@venoflex.com","city":"Blue","state":"CT"} +{"index":{"_id":"591"}} +{"account_number":591,"balance":48997,"firstname":"Rivers","lastname":"Macdonald","age":34,"gender":"F","address":"919 Johnson Street","employer":"Ziore","email":"riversmacdonald@ziore.com","city":"Townsend","state":"IL"} +{"index":{"_id":"596"}} +{"account_number":596,"balance":4063,"firstname":"Letitia","lastname":"Walker","age":26,"gender":"F","address":"963 Vanderveer Place","employer":"Zizzle","email":"letitiawalker@zizzle.com","city":"Rossmore","state":"ID"} +{"index":{"_id":"604"}} +{"account_number":604,"balance":10675,"firstname":"Isabel","lastname":"Gilliam","age":23,"gender":"M","address":"854 Broadway ","employer":"Zenthall","email":"isabelgilliam@zenthall.com","city":"Ventress","state":"WI"} +{"index":{"_id":"609"}} +{"account_number":609,"balance":28586,"firstname":"Montgomery","lastname":"Washington","age":30,"gender":"M","address":"169 Schroeders Avenue","employer":"Kongle","email":"montgomerywashington@kongle.com","city":"Croom","state":"AZ"} +{"index":{"_id":"611"}} +{"account_number":611,"balance":17528,"firstname":"Katherine","lastname":"Prince","age":33,"gender":"F","address":"705 Elm Avenue","employer":"Zillacon","email":"katherineprince@zillacon.com","city":"Rew","state":"MI"} +{"index":{"_id":"616"}} +{"account_number":616,"balance":25276,"firstname":"Jessie","lastname":"Mayer","age":35,"gender":"F","address":"683 Chester Avenue","employer":"Emtrak","email":"jessiemayer@emtrak.com","city":"Marysville","state":"HI"} +{"index":{"_id":"623"}} +{"account_number":623,"balance":20514,"firstname":"Rose","lastname":"Combs","age":32,"gender":"F","address":"312 Grimes Road","employer":"Aquamate","email":"rosecombs@aquamate.com","city":"Fostoria","state":"OH"} +{"index":{"_id":"628"}} +{"account_number":628,"balance":42736,"firstname":"Buckner","lastname":"Chen","age":37,"gender":"M","address":"863 Rugby Road","employer":"Jamnation","email":"bucknerchen@jamnation.com","city":"Camas","state":"TX"} +{"index":{"_id":"630"}} +{"account_number":630,"balance":46060,"firstname":"Leanne","lastname":"Jones","age":31,"gender":"M","address":"451 Bayview Avenue","employer":"Wazzu","email":"leannejones@wazzu.com","city":"Kylertown","state":"OK"} +{"index":{"_id":"635"}} +{"account_number":635,"balance":44705,"firstname":"Norman","lastname":"Gilmore","age":33,"gender":"M","address":"330 Gates Avenue","employer":"Comfirm","email":"normangilmore@comfirm.com","city":"Riceville","state":"TN"} +{"index":{"_id":"642"}} +{"account_number":642,"balance":32852,"firstname":"Reyna","lastname":"Harris","age":35,"gender":"M","address":"305 Powell Street","employer":"Bedlam","email":"reynaharris@bedlam.com","city":"Florence","state":"KS"} +{"index":{"_id":"647"}} +{"account_number":647,"balance":10147,"firstname":"Annabelle","lastname":"Velazquez","age":30,"gender":"M","address":"299 Kensington Walk","employer":"Sealoud","email":"annabellevelazquez@sealoud.com","city":"Soudan","state":"ME"} +{"index":{"_id":"654"}} +{"account_number":654,"balance":38695,"firstname":"Armstrong","lastname":"Frazier","age":25,"gender":"M","address":"899 Seeley Street","employer":"Zensor","email":"armstrongfrazier@zensor.com","city":"Cherokee","state":"UT"} +{"index":{"_id":"659"}} +{"account_number":659,"balance":29648,"firstname":"Dorsey","lastname":"Sosa","age":40,"gender":"M","address":"270 Aberdeen Street","employer":"Daycore","email":"dorseysosa@daycore.com","city":"Chamberino","state":"SC"} +{"index":{"_id":"661"}} +{"account_number":661,"balance":3679,"firstname":"Joanne","lastname":"Spencer","age":39,"gender":"F","address":"910 Montauk Avenue","employer":"Visalia","email":"joannespencer@visalia.com","city":"Valmy","state":"NH"} +{"index":{"_id":"666"}} +{"account_number":666,"balance":13880,"firstname":"Mcguire","lastname":"Lloyd","age":40,"gender":"F","address":"658 Just Court","employer":"Centrexin","email":"mcguirelloyd@centrexin.com","city":"Warren","state":"MT"} +{"index":{"_id":"673"}} +{"account_number":673,"balance":11303,"firstname":"Mcdaniel","lastname":"Harrell","age":33,"gender":"M","address":"565 Montgomery Place","employer":"Eyeris","email":"mcdanielharrell@eyeris.com","city":"Garnet","state":"NV"} +{"index":{"_id":"678"}} +{"account_number":678,"balance":43663,"firstname":"Ruby","lastname":"Shaffer","age":28,"gender":"M","address":"350 Clark Street","employer":"Comtrail","email":"rubyshaffer@comtrail.com","city":"Aurora","state":"MA"} +{"index":{"_id":"680"}} +{"account_number":680,"balance":31561,"firstname":"Melton","lastname":"Camacho","age":32,"gender":"F","address":"771 Montana Place","employer":"Insuresys","email":"meltoncamacho@insuresys.com","city":"Sparkill","state":"IN"} +{"index":{"_id":"685"}} +{"account_number":685,"balance":22249,"firstname":"Yesenia","lastname":"Rowland","age":24,"gender":"F","address":"193 Dekalb Avenue","employer":"Coriander","email":"yeseniarowland@coriander.com","city":"Lupton","state":"NC"} +{"index":{"_id":"692"}} +{"account_number":692,"balance":10435,"firstname":"Haney","lastname":"Barlow","age":21,"gender":"F","address":"267 Lenox Road","employer":"Egypto","email":"haneybarlow@egypto.com","city":"Detroit","state":"IN"} +{"index":{"_id":"697"}} +{"account_number":697,"balance":48745,"firstname":"Mallory","lastname":"Emerson","age":24,"gender":"F","address":"318 Dunne Court","employer":"Exoplode","email":"malloryemerson@exoplode.com","city":"Montura","state":"LA"} +{"index":{"_id":"700"}} +{"account_number":700,"balance":19164,"firstname":"Patel","lastname":"Durham","age":21,"gender":"F","address":"440 King Street","employer":"Icology","email":"pateldurham@icology.com","city":"Mammoth","state":"IL"} +{"index":{"_id":"705"}} +{"account_number":705,"balance":28415,"firstname":"Krystal","lastname":"Cross","age":22,"gender":"M","address":"604 Drew Street","employer":"Tubesys","email":"krystalcross@tubesys.com","city":"Dalton","state":"MO"} +{"index":{"_id":"712"}} +{"account_number":712,"balance":12459,"firstname":"Butler","lastname":"Alston","age":37,"gender":"M","address":"486 Hemlock Street","employer":"Quordate","email":"butleralston@quordate.com","city":"Verdi","state":"MS"} +{"index":{"_id":"717"}} +{"account_number":717,"balance":29270,"firstname":"Erickson","lastname":"Mcdonald","age":31,"gender":"M","address":"873 Franklin Street","employer":"Exotechno","email":"ericksonmcdonald@exotechno.com","city":"Jessie","state":"MS"} +{"index":{"_id":"724"}} +{"account_number":724,"balance":12548,"firstname":"Hopper","lastname":"Peck","age":31,"gender":"M","address":"849 Hendrickson Street","employer":"Uxmox","email":"hopperpeck@uxmox.com","city":"Faxon","state":"UT"} +{"index":{"_id":"729"}} +{"account_number":729,"balance":41812,"firstname":"Katy","lastname":"Rivera","age":36,"gender":"F","address":"791 Olive Street","employer":"Blurrybus","email":"katyrivera@blurrybus.com","city":"Innsbrook","state":"MI"} +{"index":{"_id":"731"}} +{"account_number":731,"balance":4994,"firstname":"Lorene","lastname":"Weiss","age":35,"gender":"M","address":"990 Ocean Court","employer":"Comvoy","email":"loreneweiss@comvoy.com","city":"Lavalette","state":"WI"} +{"index":{"_id":"736"}} +{"account_number":736,"balance":28677,"firstname":"Rogers","lastname":"Mcmahon","age":21,"gender":"F","address":"423 Cameron Court","employer":"Brainclip","email":"rogersmcmahon@brainclip.com","city":"Saddlebrooke","state":"FL"} +{"index":{"_id":"743"}} +{"account_number":743,"balance":14077,"firstname":"Susana","lastname":"Moody","age":23,"gender":"M","address":"842 Fountain Avenue","employer":"Bitrex","email":"susanamoody@bitrex.com","city":"Temperanceville","state":"TN"} +{"index":{"_id":"748"}} +{"account_number":748,"balance":38060,"firstname":"Ford","lastname":"Branch","age":25,"gender":"M","address":"926 Cypress Avenue","employer":"Buzzness","email":"fordbranch@buzzness.com","city":"Beason","state":"DC"} +{"index":{"_id":"750"}} +{"account_number":750,"balance":40481,"firstname":"Cherie","lastname":"Brooks","age":20,"gender":"F","address":"601 Woodhull Street","employer":"Kaggle","email":"cheriebrooks@kaggle.com","city":"Groton","state":"MA"} +{"index":{"_id":"755"}} +{"account_number":755,"balance":43878,"firstname":"Bartlett","lastname":"Conway","age":22,"gender":"M","address":"453 Times Placez","employer":"Konnect","email":"bartlettconway@konnect.com","city":"Belva","state":"VT"} +{"index":{"_id":"762"}} +{"account_number":762,"balance":10291,"firstname":"Amanda","lastname":"Head","age":20,"gender":"F","address":"990 Ocean Parkway","employer":"Zentury","email":"amandahead@zentury.com","city":"Hegins","state":"AR"} +{"index":{"_id":"767"}} +{"account_number":767,"balance":26220,"firstname":"Anthony","lastname":"Sutton","age":27,"gender":"F","address":"179 Fayette Street","employer":"Xiix","email":"anthonysutton@xiix.com","city":"Iberia","state":"TN"} +{"index":{"_id":"774"}} +{"account_number":774,"balance":35287,"firstname":"Lynnette","lastname":"Alvarez","age":38,"gender":"F","address":"991 Brightwater Avenue","employer":"Gink","email":"lynnettealvarez@gink.com","city":"Leola","state":"NC"} +{"index":{"_id":"779"}} +{"account_number":779,"balance":40983,"firstname":"Maggie","lastname":"Pace","age":32,"gender":"F","address":"104 Harbor Court","employer":"Bulljuice","email":"maggiepace@bulljuice.com","city":"Floris","state":"MA"} +{"index":{"_id":"781"}} +{"account_number":781,"balance":29961,"firstname":"Sanford","lastname":"Mullen","age":26,"gender":"F","address":"879 Dover Street","employer":"Zanity","email":"sanfordmullen@zanity.com","city":"Martinez","state":"TX"} +{"index":{"_id":"786"}} +{"account_number":786,"balance":3024,"firstname":"Rene","lastname":"Vang","age":33,"gender":"M","address":"506 Randolph Street","employer":"Isopop","email":"renevang@isopop.com","city":"Vienna","state":"NJ"} +{"index":{"_id":"793"}} +{"account_number":793,"balance":16911,"firstname":"Alford","lastname":"Compton","age":36,"gender":"M","address":"186 Veronica Place","employer":"Zyple","email":"alfordcompton@zyple.com","city":"Sugartown","state":"AK"} +{"index":{"_id":"798"}} +{"account_number":798,"balance":3165,"firstname":"Catherine","lastname":"Ward","age":30,"gender":"F","address":"325 Burnett Street","employer":"Dreamia","email":"catherineward@dreamia.com","city":"Glenbrook","state":"SD"} +{"index":{"_id":"801"}} +{"account_number":801,"balance":14954,"firstname":"Molly","lastname":"Maldonado","age":37,"gender":"M","address":"518 Maple Avenue","employer":"Straloy","email":"mollymaldonado@straloy.com","city":"Hebron","state":"WI"} +{"index":{"_id":"806"}} +{"account_number":806,"balance":36492,"firstname":"Carson","lastname":"Riddle","age":31,"gender":"M","address":"984 Lois Avenue","employer":"Terrago","email":"carsonriddle@terrago.com","city":"Leland","state":"MN"} +{"index":{"_id":"813"}} +{"account_number":813,"balance":30833,"firstname":"Ebony","lastname":"Bishop","age":20,"gender":"M","address":"487 Ridge Court","employer":"Optique","email":"ebonybishop@optique.com","city":"Fairmount","state":"WA"} +{"index":{"_id":"818"}} +{"account_number":818,"balance":24433,"firstname":"Espinoza","lastname":"Petersen","age":26,"gender":"M","address":"641 Glenwood Road","employer":"Futurity","email":"espinozapetersen@futurity.com","city":"Floriston","state":"MD"} +{"index":{"_id":"820"}} +{"account_number":820,"balance":1011,"firstname":"Shepard","lastname":"Ramsey","age":24,"gender":"F","address":"806 Village Court","employer":"Mantro","email":"shepardramsey@mantro.com","city":"Tibbie","state":"NV"} +{"index":{"_id":"825"}} +{"account_number":825,"balance":49000,"firstname":"Terra","lastname":"Witt","age":21,"gender":"F","address":"590 Conway Street","employer":"Insectus","email":"terrawitt@insectus.com","city":"Forbestown","state":"AR"} +{"index":{"_id":"832"}} +{"account_number":832,"balance":8582,"firstname":"Laura","lastname":"Gibbs","age":39,"gender":"F","address":"511 Osborn Street","employer":"Corepan","email":"lauragibbs@corepan.com","city":"Worcester","state":"KS"} +{"index":{"_id":"837"}} +{"account_number":837,"balance":14485,"firstname":"Amy","lastname":"Villarreal","age":35,"gender":"M","address":"381 Stillwell Place","employer":"Fleetmix","email":"amyvillarreal@fleetmix.com","city":"Sanford","state":"IA"} +{"index":{"_id":"844"}} +{"account_number":844,"balance":26840,"firstname":"Jill","lastname":"David","age":31,"gender":"M","address":"346 Legion Street","employer":"Zytrax","email":"jilldavid@zytrax.com","city":"Saticoy","state":"SC"} +{"index":{"_id":"849"}} +{"account_number":849,"balance":16200,"firstname":"Barry","lastname":"Chapman","age":26,"gender":"M","address":"931 Dekoven Court","employer":"Darwinium","email":"barrychapman@darwinium.com","city":"Whitestone","state":"WY"} +{"index":{"_id":"851"}} +{"account_number":851,"balance":22026,"firstname":"Henderson","lastname":"Price","age":33,"gender":"F","address":"530 Hausman Street","employer":"Plutorque","email":"hendersonprice@plutorque.com","city":"Brutus","state":"RI"} +{"index":{"_id":"856"}} +{"account_number":856,"balance":27583,"firstname":"Alissa","lastname":"Knox","age":25,"gender":"M","address":"258 Empire Boulevard","employer":"Geologix","email":"alissaknox@geologix.com","city":"Hartsville/Hartley","state":"MN"} +{"index":{"_id":"863"}} +{"account_number":863,"balance":23165,"firstname":"Melendez","lastname":"Fernandez","age":40,"gender":"M","address":"661 Johnson Avenue","employer":"Vixo","email":"melendezfernandez@vixo.com","city":"Farmers","state":"IL"} +{"index":{"_id":"868"}} +{"account_number":868,"balance":27624,"firstname":"Polly","lastname":"Barron","age":22,"gender":"M","address":"129 Frank Court","employer":"Geofarm","email":"pollybarron@geofarm.com","city":"Loyalhanna","state":"ND"} +{"index":{"_id":"870"}} +{"account_number":870,"balance":43882,"firstname":"Goff","lastname":"Phelps","age":21,"gender":"M","address":"164 Montague Street","employer":"Digigen","email":"goffphelps@digigen.com","city":"Weedville","state":"IL"} +{"index":{"_id":"875"}} +{"account_number":875,"balance":19655,"firstname":"Mercer","lastname":"Pratt","age":24,"gender":"M","address":"608 Perry Place","employer":"Twiggery","email":"mercerpratt@twiggery.com","city":"Eggertsville","state":"MO"} +{"index":{"_id":"882"}} +{"account_number":882,"balance":10895,"firstname":"Mari","lastname":"Landry","age":39,"gender":"M","address":"963 Gerald Court","employer":"Kenegy","email":"marilandry@kenegy.com","city":"Lithium","state":"NC"} +{"index":{"_id":"887"}} +{"account_number":887,"balance":31772,"firstname":"Eunice","lastname":"Watts","age":36,"gender":"F","address":"707 Stuyvesant Avenue","employer":"Memora","email":"eunicewatts@memora.com","city":"Westwood","state":"TN"} +{"index":{"_id":"894"}} +{"account_number":894,"balance":1031,"firstname":"Tyler","lastname":"Fitzgerald","age":32,"gender":"M","address":"787 Meserole Street","employer":"Jetsilk","email":"tylerfitzgerald@jetsilk.com","city":"Woodlands","state":"WV"} +{"index":{"_id":"899"}} +{"account_number":899,"balance":32953,"firstname":"Carney","lastname":"Callahan","age":23,"gender":"M","address":"724 Kimball Street","employer":"Mangelica","email":"carneycallahan@mangelica.com","city":"Tecolotito","state":"MT"} +{"index":{"_id":"902"}} +{"account_number":902,"balance":13345,"firstname":"Hallie","lastname":"Jarvis","age":23,"gender":"F","address":"237 Duryea Court","employer":"Anixang","email":"halliejarvis@anixang.com","city":"Boykin","state":"IN"} +{"index":{"_id":"907"}} +{"account_number":907,"balance":12961,"firstname":"Ingram","lastname":"William","age":36,"gender":"M","address":"826 Overbaugh Place","employer":"Genmex","email":"ingramwilliam@genmex.com","city":"Kimmell","state":"AK"} +{"index":{"_id":"914"}} +{"account_number":914,"balance":7120,"firstname":"Esther","lastname":"Bean","age":32,"gender":"F","address":"583 Macon Street","employer":"Applica","email":"estherbean@applica.com","city":"Homeworth","state":"MN"} +{"index":{"_id":"919"}} +{"account_number":919,"balance":39655,"firstname":"Shauna","lastname":"Hanson","age":27,"gender":"M","address":"557 Hart Place","employer":"Exospace","email":"shaunahanson@exospace.com","city":"Outlook","state":"LA"} +{"index":{"_id":"921"}} +{"account_number":921,"balance":49119,"firstname":"Barbara","lastname":"Wade","age":29,"gender":"M","address":"687 Hoyts Lane","employer":"Roughies","email":"barbarawade@roughies.com","city":"Sattley","state":"CO"} +{"index":{"_id":"926"}} +{"account_number":926,"balance":49433,"firstname":"Welch","lastname":"Mcgowan","age":21,"gender":"M","address":"833 Quincy Street","employer":"Atomica","email":"welchmcgowan@atomica.com","city":"Hampstead","state":"VT"} +{"index":{"_id":"933"}} +{"account_number":933,"balance":18071,"firstname":"Tabitha","lastname":"Cole","age":21,"gender":"F","address":"916 Rogers Avenue","employer":"Eclipto","email":"tabithacole@eclipto.com","city":"Lawrence","state":"TX"} +{"index":{"_id":"938"}} +{"account_number":938,"balance":9597,"firstname":"Sharron","lastname":"Santos","age":40,"gender":"F","address":"215 Matthews Place","employer":"Zenco","email":"sharronsantos@zenco.com","city":"Wattsville","state":"VT"} +{"index":{"_id":"940"}} +{"account_number":940,"balance":23285,"firstname":"Melinda","lastname":"Mendoza","age":38,"gender":"M","address":"806 Kossuth Place","employer":"Kneedles","email":"melindamendoza@kneedles.com","city":"Coaldale","state":"OK"} +{"index":{"_id":"945"}} +{"account_number":945,"balance":23085,"firstname":"Hansen","lastname":"Hebert","age":33,"gender":"F","address":"287 Conduit Boulevard","employer":"Capscreen","email":"hansenhebert@capscreen.com","city":"Taycheedah","state":"AK"} +{"index":{"_id":"952"}} +{"account_number":952,"balance":21430,"firstname":"Angelique","lastname":"Weeks","age":33,"gender":"M","address":"659 Reeve Place","employer":"Exodoc","email":"angeliqueweeks@exodoc.com","city":"Turpin","state":"MD"} +{"index":{"_id":"957"}} +{"account_number":957,"balance":11373,"firstname":"Michael","lastname":"Giles","age":31,"gender":"M","address":"668 Court Square","employer":"Yogasm","email":"michaelgiles@yogasm.com","city":"Rosburg","state":"WV"} +{"index":{"_id":"964"}} +{"account_number":964,"balance":26154,"firstname":"Elena","lastname":"Waller","age":34,"gender":"F","address":"618 Crystal Street","employer":"Insurety","email":"elenawaller@insurety.com","city":"Gallina","state":"NY"} +{"index":{"_id":"969"}} +{"account_number":969,"balance":22214,"firstname":"Briggs","lastname":"Lynn","age":30,"gender":"M","address":"952 Lester Court","employer":"Quinex","email":"briggslynn@quinex.com","city":"Roland","state":"ID"} +{"index":{"_id":"971"}} +{"account_number":971,"balance":22772,"firstname":"Gabrielle","lastname":"Reilly","age":32,"gender":"F","address":"964 Tudor Terrace","employer":"Blanet","email":"gabriellereilly@blanet.com","city":"Falmouth","state":"AL"} +{"index":{"_id":"976"}} +{"account_number":976,"balance":31707,"firstname":"Mullen","lastname":"Tanner","age":26,"gender":"M","address":"711 Whitney Avenue","employer":"Pulze","email":"mullentanner@pulze.com","city":"Mooresburg","state":"MA"} +{"index":{"_id":"983"}} +{"account_number":983,"balance":47205,"firstname":"Mattie","lastname":"Eaton","age":24,"gender":"F","address":"418 Allen Avenue","employer":"Trasola","email":"mattieeaton@trasola.com","city":"Dupuyer","state":"NJ"} +{"index":{"_id":"988"}} +{"account_number":988,"balance":17803,"firstname":"Lucy","lastname":"Castro","age":34,"gender":"F","address":"425 Fleet Walk","employer":"Geekfarm","email":"lucycastro@geekfarm.com","city":"Mulino","state":"VA"} +{"index":{"_id":"990"}} +{"account_number":990,"balance":44456,"firstname":"Kelly","lastname":"Steele","age":35,"gender":"M","address":"809 Hoyt Street","employer":"Eschoir","email":"kellysteele@eschoir.com","city":"Stewartville","state":"ID"} +{"index":{"_id":"995"}} +{"account_number":995,"balance":21153,"firstname":"Phelps","lastname":"Parrish","age":25,"gender":"M","address":"666 Miller Place","employer":"Pearlessa","email":"phelpsparrish@pearlessa.com","city":"Brecon","state":"ME"} diff --git a/Elasticsearch/es-index-config.java b/Elasticsearch/es-index-config.java new file mode 100644 index 00000000..a4e45ff0 --- /dev/null +++ b/Elasticsearch/es-index-config.java @@ -0,0 +1,15 @@ +-------------------------- +index-config | +-------------------------- + # 创建索引的时候, 可以指定索引的一些设置 + + PUT / + + { + "settings":{ + ... + }, + "mappings":{ + + } + } \ No newline at end of file diff --git a/Elasticsearch/es-index-mapping.java b/Elasticsearch/es-index-mapping.java new file mode 100644 index 00000000..1be3d9f5 --- /dev/null +++ b/Elasticsearch/es-index-mapping.java @@ -0,0 +1,149 @@ +------------------------ +mapping | +------------------------ + # 文档 + https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html + + # 跟关系型数据库的DDL一样, 定义存储的数据类型, 索引, 使用的分词器等等 + * 不同的数据类型, 在建立索引, 分词的时候可能有所不同 + * 会影响到索引的建立和搜索行为 + + * mapping 可以在创建 index 之前创建 + * mapping 可以在创建 index 之后编辑, 编辑只能是新增, 不能是修改 + + + # 查看指定索引的mapping + GET //_mapping + + # mapping的结构 + { + "":{ + "mappings":{ + "properties":{ + "":{ + + } + } + } + } + } + + * properties 下就是每个字段对应的'配置' + - 数据类型 + - 如何分词 + - ... ... + * properties 属性下可以嵌套 properties + + + + # 建立索引的时候, 创建 mapping + PUT / + { + "mappings":{ + "properties":{ + "":{ + ... + } + } + } + } + + # 创建索引后, 修改 mapping + PUT //_mapping + { + "properties":{ + "":{ + ... + } + } + } + +------------------------ +type 数据类型 | +------------------------ + # 指定字段的数据类型, 如果不手动设置, 则自动推测 + + # long + # boolean + # double + # string + # byte + # short + # integer + # float + # text + # date + # keyword + +------------------------ +index 是否可以被索引 | +------------------------ + # 指定字段是否可以被索引 + * 默认既允许索引, 也要进行分词 + + # not_analyzed + * 可以被索引, 但是不分词 + + # no + * 不可以被索引, 也不进行分词 + +------------------------ +analyzer 指定分词器 | +------------------------ + # 默认: standard + +------------------------ +settings | +------------------------ + +------------------------ +_source | +------------------------ + +------------------------ +_all | +------------------------ + +------------------------ +dynamic | +------------------------ + +------------------------ +mapping 字段属性 | +------------------------ + { + "" : { + "mappings" : { + "properties" : { + "" : { + "properties" : { + "" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + } + } + }, + "" : { + "type" : "text", + "fields" : { + "keyword" : { + "type" : "keyword", + "ignore_above" : 256 + } + } + }, + "" : { + "type" : "date" + }, + "" : { + "type" : "long" + } + } + } + } + } diff --git a/Elasticsearch/es-index.java b/Elasticsearch/es-index.java new file mode 100644 index 00000000..a15ff541 --- /dev/null +++ b/Elasticsearch/es-index.java @@ -0,0 +1,53 @@ +----------------------- +index | +----------------------- + +----------------------- +index - 创建 | +----------------------- + # 请求 + PUT / + { + ...config + } + + { + "acknowledged" : true, + "shards_acknowledged" : true, + "index" : "" + } + +----------------------- +index - 删除 | +----------------------- + # 请求 + DELETE / ?pretty + + { + "acknowledged" : true + } + + + +---------------------------- +查看集群中索引信息 | +---------------------------- + # 请求 + GET /_cat/indices?v + + # 响应 + health status index uuid pri rep docs.count docs.deleted store.size pri.store.size + green open .kibana_task_manager cEN_d-7TTwiYBn7xE1LkpQ 1 0 2 0 12.7kb 12.7kb + green open .kibana_1 RpX3IRZEQKag3H8xyWq4eQ 1 0 4 0 17.6kb 17.6kb + yellow open customer R4mXIa-_QKiDngr7bKG-OA 1 1 0 0 230b 230b + + health + status + index + uuid + pri + rep + docs.count + docs.deleted + store.size + pri.store.size \ No newline at end of file diff --git a/Elasticsearch/es-plugin-head.java b/Elasticsearch/es-plugin-head.java new file mode 100644 index 00000000..501a6bba --- /dev/null +++ b/Elasticsearch/es-plugin-head.java @@ -0,0 +1 @@ + \ No newline at end of file diff --git "a/Elasticsearch/es-\345\210\206\350\257\215\345\231\250.java" "b/Elasticsearch/es-\345\210\206\350\257\215\345\231\250.java" new file mode 100644 index 00000000..f6527318 --- /dev/null +++ "b/Elasticsearch/es-\345\210\206\350\257\215\345\231\250.java" @@ -0,0 +1,69 @@ +--------------------- +分词器 | +--------------------- + # 分词器 + + # 切分词语 & normalization(提升recall召回率) + * 对数据进行分词, 并且对每个单词进行normalization + * 召回率:搜索的时候, 增加能够搜索到的结果数量 + + + # 分词器包含了几个部分 + character filter + * 分词之间, 进行预处理, 例如:过滤html标签 + + tokenizer + * 分词 + + token filter + * 会执行normalization的一些操作, 例如:同义词转换, 大小写转换 + + + + +--------------------- +内置的分词器 | +--------------------- + Standard analyzer(默认) + Simple analyzer + Witespace analyzer + Language analyzer + +--------------------- +执行分词指令 | +--------------------- + # 请求 + GET //_analyze + + { + "analyzer": "", + "text":"" + } + + * index 非必须, 如果指定的话, 就是使用指定index的mapping来进行 + * analyzer 指定要使用的分词器, 以及要进行分词的文本(text) + + { + "tokens" : [ + { + "token" : "hello", //拆分的一个词儿 + "start_offset" : 0, // 该词在文本中的位置(从哪儿字符串开始到哪个字符串) + "end_offset" : 5, + "type" : "", + "position" : 0 // 该词语在文本中的位置(拆出来的第几个词儿) + }, + { + "token" : "world", + "start_offset" : 6, + "end_offset" : 11, + "type" : "", + "position" : 1 + } + ] + } + +--------------------- +定制分词器 | +--------------------- + + \ No newline at end of file diff --git "a/Elasticsearch/es-\345\256\211\345\205\250\350\256\276\347\275\256.java" "b/Elasticsearch/es-\345\256\211\345\205\250\350\256\276\347\275\256.java" new file mode 100644 index 00000000..ac98c080 --- /dev/null +++ "b/Elasticsearch/es-\345\256\211\345\205\250\350\256\276\347\275\256.java" @@ -0,0 +1,7 @@ +---------------------------- +安全设置 | +---------------------------- + # 文档 + https://www.elastic.co/guide/en/elasticsearch/reference/current/secure-settings.html + https://www.elastic.co/guide/en/elasticsearch/reference/current/security-settings.html + diff --git "a/Elasticsearch/es-\345\256\211\350\243\205.java" "b/Elasticsearch/es-\345\256\211\350\243\205.java" new file mode 100644 index 00000000..a9f32484 --- /dev/null +++ "b/Elasticsearch/es-\345\256\211\350\243\205.java" @@ -0,0 +1,26 @@ +------------------------------ +安装 | +------------------------------ + +------------------------------ +Linux安装 | +------------------------------ + # 下载指定的版本 + https://www.elastic.co/cn/downloads/elasticsearch + + # 解压到目录 + + # 创建运行用户, 执行目录授权 + * ES不允许直接使用root账户进行启动, 会给出异常:can not run elasticsearch as root + + useradd -r elasticsearch + + chown [path] elasticsearch -R + + * 如果指定了其他的志数据目录, 也需要进行授权 + + + # 启动脚本 + /bin/elasticsearch + + diff --git "a/Elasticsearch/es-\351\233\206\347\276\244-\347\212\266\346\200\201\347\233\221\346\216\247.java" "b/Elasticsearch/es-\351\233\206\347\276\244-\347\212\266\346\200\201\347\233\221\346\216\247.java" new file mode 100644 index 00000000..eb77480c --- /dev/null +++ "b/Elasticsearch/es-\351\233\206\347\276\244-\347\212\266\346\200\201\347\233\221\346\216\247.java" @@ -0,0 +1,52 @@ +---------------------------- +集群状态 | +---------------------------- + # 请求 + GET /_cat/health? + + epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent + 1560149716 06:55:16 elaticsearch green 1 1 2 2 0 0 0 0 - 100.0% + + epoch + timestamp + cluster + * 集群名称 + + status + * 表示集群的状态, 使用英文的颜色词儿表示 + Green :一切都很好 + Yellow :所有数据都可用,但尚未分配一些副本 + Red :某些数据由于某种原因不可用 + + node.total + * 集群中的节点数量 + + node.data + shards + pri + relo + init + unassign + pending_tasks + max_task_wait_time + active_shards_percent + +---------------------------- +查看集群中的节点信息 | +---------------------------- + # 请求 + GET /_cat/nodes?v + + ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name + 127.0.0.1 12 51 8 mdi * KEVINBLANDY + + ip + heap.percent + ram.percent + cpu + load_1m + load_5m + load_15m + node.role + master + name \ No newline at end of file diff --git "a/Elasticsearch/es-\351\233\206\347\276\244.java" "b/Elasticsearch/es-\351\233\206\347\276\244.java" new file mode 100644 index 00000000..5b0edfe3 --- /dev/null +++ "b/Elasticsearch/es-\351\233\206\347\276\244.java" @@ -0,0 +1,41 @@ +---------------------------- +集群 | +---------------------------- + # 文档 + https://www.elastic.co/guide/en/elasticsearch/reference/current/discovery-settings.html + + # ES隐藏了分布式系统中的很多复杂特性 + * 数据分片机制, 数据会被分片为多个shard, 分布式的存储在不同的节点 + * 集群发现机制, 可以有新节点的加入和退出 + * shard负载均衡, shard 要平均的分配到不同的节点 + * shard的重新分配, 在节点增减的时候, 会自动的均衡每个节点的 shard + + # Shard + * Primary Shard, 分布式存储 + * 一个doc肯定存在于一个 Primary Shard + * Primary Shard在创建的时候就已经确定, 不能修改, 默认有5个Primary Shard + + * Replica Shard, 副本 + * 每个Primary Shard 默认有1个 Replica Shard, 可以index创建后随意的修改 + + * Master Shard负责处理读写请求, Replica Shard仅仅负责读请求 + * Master Shard和Replica Shard不能在同一个节点, 如果节点宕机, 主从都不能使用, 不能容错 + * Master Shard宕机, 某个Replica Shard会自动成为Master Shard + + # 集群节点之间的数据同步是多线程异步的 + * Replica节点在更新的时候, 使用_version乐观锁来保证数据的一致性 + + +---------------------------- +相关的端点 | +---------------------------- + GET /_cluster/health + GET /_cat/health? + GET /_cat/nodes?v + + + + + + + diff --git a/Elasticsearch/es.java b/Elasticsearch/es.java new file mode 100644 index 00000000..42f7ea7a --- /dev/null +++ b/Elasticsearch/es.java @@ -0,0 +1,116 @@ +----------------------------- +Elasticsearch | +----------------------------- + # 官网 + https://www.elastic.co + https://www.elastic.co/cn/ + https://www.elastic.co/guide/en/elasticsearch/reference/index.html + + # Github + https://github.com/elastic/elasticsearch + + # 参考 + https://blog.csdn.net/laoyang360/article/details/79293493 + https://github.com/laoyang360/deep_elasticsearch + http://www.ruanyifeng.com/blog/2017/08/elasticsearch.html + https://elasticsearch.apachecn.org/#/ + + https://elastic.blog.csdn.net/ + https://elasticsearch.cn/ + + + +----------------------------- +Elasticsearch-目录结构 | +----------------------------- + bin + |-elasticsearch + config + |-elasticsearch.yml + |-jvm.options + |-log4j2.properties + |-role_mapping.yml + |-roles.yml + |-users + |-users_roles + jdk + lib + logs + modules + plugins + +----------------------------- +Elasticsearch-启动 | +----------------------------- + # 执行脚本 + bin/elasticsearch + + # 访问:http://127.0.0.1:9200/ + { + "name": "KEVINBLANDY", + "cluster_name": "elaticsearch", + "cluster_uuid": "wCaZ0Z6rSmmFpFjQFSWjDw", + "version": { + "number": "7.1.1", + "build_flavor": "default", + "build_type": "zip", + "build_hash": "7a013de", + "build_date": "2019-05-23T14:04:00.380842Z", + "build_snapshot": false, + "lucene_version": "8.0.0", + "minimum_wire_compatibility_version": "6.8.0", + "minimum_index_compatibility_version": "6.0.0-beta1" + }, + "tagline": "You Know, for Search" + } + +----------------------------- +Elasticsearch-核心概念 | +----------------------------- + Near Realtime(NRT) + # 近实时,两个意思,从写入数据到可以搜索大约会有1s的延迟,基于es进行搜索和分析可以达到秒级 + + Cluster + # 节点,集群中的一个节点,节点会有一个名称,默认是随机分配的 + # 节点名称很重要,节点默认会去加入一个名为:elaticsearch 的集群 + # 如果启动一堆节点,那么它们会自动组成一个es集群,当然,一个节点也可以组成一个es集群 + + Index + # 索引,包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引是有一个名称的 + + Type + # 类型,每个索引里都可以有一个或者多个type,type是index中的另一个逻辑分类 + # 一个type下的document都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type + # 在7里面已经被沉底的删除了 + + Documen + # 文档,es种的最小数据单元,一个document可以是一条客户数据,一条商品分类数据 + # 通常使用JSON数据结构表示,每个index下的type中,都可以存储多个document + + Shard + # 单台机器无法存储大量数据,es可以吧一个索引数据分为多个shard,分布式在多台服务器上存储 + # 有了Shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能 + # 每个shard都是一个lucene index + + Replica + # 任何一个服务器都有可能会宕机,此时shard就会丢失,因此可以为每个shard创建n个replica副本 + # replia可以在shard故障时,提供服务,保证shard的不丢失,多个replica还可以提升搜索操作的吞吐量和性能 + # Shard -> primary shard(建立索引时一次设置,不能修改,默认5个) + # Replica -> replica shard(随时修改数量,默认1个) + # 默认每个索引有10个shard,5个primary shard,5个replica shard + # 最小的高可用peizhi,是两台服务器 + + + # 传统关系型数据库的对比 + Relational DB -> Databases -> Tables -> Rows -> Columns + Elasticsearch -> Indices -> Types -> Documents -> Fields + + + +----------------------------- +Elasticsearch-征途 | +----------------------------- +倒排索引 +打分机制 +全文检索原理 +分词原理 \ No newline at end of file diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-api-cat.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-api-cat.java" new file mode 100644 index 00000000..2acd5d8f --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-api-cat.java" @@ -0,0 +1,19 @@ + +------------------------ +cat - api | +------------------------ + # 系统提供的一套快速cat - api + # 显示额外的列头部信息 + * 在api地址后面添加参数: ?v + +/_cat/health + # 快速的查看服务健康状况 + epoch |timestamp|cluster |status |node.total|node.data|shards|pri|relo|init|unassign|pending_tasks|max_task_wait_time|active_shards_percent + 1529503744 |22:09:04 |elasticsearch |green | 1 |1 |1 |1 |0 |0 |0 | 0 | - |100.0% + + 时间戳 时间 集群名称 + +/_cat/indices + # 快速的查看索引信息 + health status index uuid pri rep docs.count docs.deleted store.size pri.store.size + green open .kibana TiiXPteWSB6eLJ0GwTbMsQ 1 0 1 0 4kb 4kb \ No newline at end of file diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-dsl\350\257\246\350\247\243.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-dsl\350\257\246\350\247\243.java" new file mode 100644 index 00000000..da514f44 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-dsl\350\257\246\350\247\243.java" @@ -0,0 +1,131 @@ +{ + "query":{ + + "match_all":{ + + }, + + "match":{ + "field":"value" + }, + + "term":{ + "field":"value" + }, + "terms":{ + "field":["value1","value2"] + } + "match_phrase":{ + "field":"value" + } + "multi_match":{ + "query":"value", + "fields":["field1,field2"] + }, + "exists":{ + "field":"field" + }, + "bool":{ + "must":{ + "match":{ + "key":"value" + } + }, + "filter":{ + "range":{ + "field":{"gt/lt/le/ge/ne":"value"} + } + } + } + }, + "sort":[ + {"key1":"desc"}, + {"key2":"asc"} + ], + "from":1, + "size":2, + "_source":[], + "highlight":{ + "fields":{ + "field":{} + } + }, + "aggs":{ + }, + "properties":{ + + } +} + +============================================================================================== +query + query.match_all + * 检索所有,该值是一个空对象{} + + query.match + * 检索符合条件的 + * 该值是一个对象,通过kv来组合条件 + * 全文检索,value可以有多个,使用空格隔开,只要key中包含了任意value关键字即满足条件 + + query.match_phrase + * 短语检索,跟全文检索相反,必须是全部符合key=value,才符合条件 + + query.multi_match + * 等于同一个值的多个字段 + query.term + * 跟 match_phrase ,是全文匹配 + query.terms + * 等一个多个值的一个字段 + * 允许匹配多个值,任意一个值件满足条即可 + + query.exists + * 通过field属性指定的属性不能为null + +============================================================================================== +query.bool + * 它是一个条件,可以由N个子条件构成 + query.bool.must + * 一个或者多个条件,必须全部满足 + query.bool.should + * 一个或者多个条件,满足一个即可 + query.bool.must_not + * 一个或者多个条件,必须全部不满足 + query.bool.filter + * 一个或者多个过滤条件 + +============================================================================================== + +from + * 从第几个数据开始检索,limit的第一个参数 +size + * 每页显示的记录数 +_source + * 该值是一个数组,指定要检索的字段,而不是检索所有 + * 通过字符串执行属性,支持.属性导航 + +============================================================================================== +highlight.fields + * 高亮设置 + + + +-------------------- +组件 | +-------------------- +match +match_all +match_phrase +multi_match +term +terms +exists + +bool +must +should +must_not +filter + +range +constant_score + diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-elasticsearch.yml" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-elasticsearch.yml" new file mode 100644 index 00000000..239bf9b1 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-elasticsearch.yml" @@ -0,0 +1,88 @@ +# ======================== Elasticsearch Configuration ========================= +# +# NOTE: Elasticsearch comes with reasonable defaults for most settings. +# Before you set out to tweak and tune the configuration, make sure you +# understand what are you trying to accomplish and the consequences. +# +# The primary way of configuring a node is via this file. This template lists +# the most important settings you may want to configure for a production cluster. +# +# Please consult the documentation for further information on configuration options: +# https://www.elastic.co/guide/en/elasticsearch/reference/index.html +# +# ---------------------------------- Cluster ----------------------------------- +# +# Use a descriptive name for your cluster: +# +#cluster.name: my-application +# +# ------------------------------------ Node ------------------------------------ +# +# Use a descriptive name for the node: +# +#node.name: node-1 +# +# Add custom attributes to the node: +# +#node.attr.rack: r1 +# +# ----------------------------------- Paths ------------------------------------ +# +# Path to directory where to store the data (separate multiple locations by comma): +# +#path.data: /path/to/data +# +# Path to log files: +# +#path.logs: /path/to/logs +# +# ----------------------------------- Memory ----------------------------------- +# +# Lock the memory on startup: +# +#bootstrap.memory_lock: true +# +# Make sure that the heap size is set to about half the memory available +# on the system and that the owner of the process is allowed to use this +# limit. +# +# Elasticsearch performs poorly when the system is swapping the memory. +# +# ---------------------------------- Network ----------------------------------- +# +# Set the bind address to a specific IP (IPv4 or IPv6): +# +#network.host: 192.168.0.1 +# +# Set a custom port for HTTP: +# +#http.port: 9200 +# +# For more information, consult the network module documentation. +# +# --------------------------------- Discovery ---------------------------------- +# +# Pass an initial list of hosts to perform discovery when this node is started: +# The default list of hosts is ["127.0.0.1", "[::1]"] +# +#discovery.seed_hosts: ["host1", "host2"] +# +# Bootstrap the cluster using an initial set of master-eligible nodes: +# +#cluster.initial_master_nodes: ["node-1", "node-2"] +# +# For more information, consult the discovery and cluster formation module documentation. +# +# ---------------------------------- Gateway ----------------------------------- +# +# Block initial recovery after a full cluster restart until N nodes are started: +# +#gateway.recover_after_nodes: 3 +# +# For more information, consult the gateway module documentation. +# +# ---------------------------------- Various ----------------------------------- +# +# Require explicit names when deleting indices: +# +#action.destructive_requires_name: true diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-kibana.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-kibana.java" new file mode 100644 index 00000000..dcdd0e53 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-kibana.java" @@ -0,0 +1,25 @@ +------------------------ +Kibana | +------------------------ + # 使用Kibana的开发界面去操作elasticsearch + + # 主页地址: + http://localhost:5601/app/kibana#/home?_g=() + + http://localhost:5601/app/kibana#/dev_tools/console + +------------------------ +Kibana-目录结构 | +------------------------ + bin + config + data + node + node_modules + optimize + plugins + src + webpackShims + package.json + + diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\205\203\346\225\260\346\215\256.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\205\203\346\225\260\346\215\256.java" new file mode 100644 index 00000000..01c30413 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\205\203\346\225\260\346\215\256.java" @@ -0,0 +1,20 @@ +---------------------------- +元数据 | +---------------------------- + # _index + 1,代表一个doucment存放在哪个index中 + 2,类似的数据放在一个索引,非类似的数据放在不同的索引 + 3,index名称必须是小写的,不能以下划线开头,不能包含逗号 + + # _type + 1,代码document属于index中的哪个类别 + 2,一个索引通常会划分为多个type + 3,type名称可以是大写,小写,不能以下划线开头,不能包含逗号 + + # _id + 1,代表document的唯一表示,与index和type一起,可以唯一标识和定位一个document + 2,可以手动指定document的id,也可以不指定,由es自动为我们创建一个id + + + + diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217.java" new file mode 100644 index 00000000..ba0fb0a7 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217.java" @@ -0,0 +1,68 @@ +---------------------------------------- +ES分布式 | +---------------------------------------- + # 对复杂分布式机制的透明透明隐藏特性 + * 分片,cluster,discovery,shard负载均衡,shard副本,请求路由,集群扩容,shard重分配 + # 垂直扩容与水平扩容 + * 垂直: 加大已有服务器硬盘,配置 + * 水平: 新买服务器,加入节点 + * 一般都是用:水平,垂直会带来瓶颈问题 + # 增减节点时,数据的 rebalance + # master节点 + * 集群中,总有一个节点是:master 节点,由集群自动选择 + * 它主要是管理ES集群的元数据(索引的创建,删除,索引维护,节点的增加,删除,维护节点的元数据) + + # 节点对等的分布式架构 + * 节点对等,每个节点都能接收所有的请求 + * 自动请求路由 + * 响应采集 + + + + +---------------------------------------- +shard & replica机制 | +---------------------------------------- + 1,index包含一个或者多个shard + 2,每个shard都是一个最小工作单元 + * 承载部分数据,lucene实例,完整的建立索引和处理请求的能力 + 3,增减节点,shard会自动在nodes中负载均衡 + 4,primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的replica shard中,不可能存于多个primary shard + 5,replica shard是primary shard的副本,负责容错,以及承担读请求的负载 + 6,primary shard的数量,在创建索引的时候就固定了,replica shard的数量可以随时修改 + 7,primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个 replica shard + 8,primary shard 不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用),但是可以和其他的primary shard和replica shard放在同一个节点上 + + + +---------------------------------------- +单Node环境下的index | +---------------------------------------- + 1,单Node环境下,创建一个Index,有3个Primary shard,3个replica shard + 2,集群的status 是 yellow + 3,只会把三个primary shard分配到仅有的一个node上去,另外3个replica shard无法分配 + 4,集群可以正常工作,但是一旦出现节点宕机,数据全部丢失,而且集群不可用,无法执行任何请求 + +---------------------------------------- +2个Node环境下的index | +---------------------------------------- + 1,2个Node环境下,三个primary shard 会部署到一个节点,三个 replica shard会部署到一个节点 + +---------------------------------------- +横向扩容 | +---------------------------------------- + 1,primary & replica自动负载均衡 + 2,每个node有更少的shard, io/cpu/memory资源给每个shard分配更多,每个shard性能更好 + + +---------------------------------------- +容错机制 | +---------------------------------------- + 1,master选举 + 2,replica容错 + 3,数据恢复 + + master node宕机,自动选举master -> red + replica 容错:新master 将replica提升为 primary shard -> yellow + 重启宕机node,master copy replica到该node,使用原有的shard并同步宕机后的修改 green + diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-bulk\346\211\271\351\207\217\345\242\236\345\210\240\346\224\271.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-bulk\346\211\271\351\207\217\345\242\236\345\210\240\346\224\271.java" new file mode 100644 index 00000000..66bf4496 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-bulk\346\211\271\351\207\217\345\242\236\345\210\240\346\224\271.java" @@ -0,0 +1,82 @@ +---------------------------- +bulk批量增删改 | +---------------------------- + # _bulk 请求对json的语法,要求相当严格 + * 每个json串儿,不能换行,只能放在同一行 + * 两个json串儿之间要换一行 + + # create,update操作,需要俩json串 + * 第一个json指定操作,以及元数据 + * 第二个json指定提交的业务数据 + + # json格式 + {"create":{...元数据}} + {...业务数据} + + {"index":{...元数据}} + {...业务数据} + + {"delet":{...元数据}} + + {"update":{...元数据}} + {...业务数据} + + + * create 属于强制创建 PUT /index/type/id/_create + * index 属于普通的put操作,可以是替换文档,或者创建文档 + * update 属于 partial update 操作,可以使用 retry_on_conflict 来控制乐观锁的重试次数 + * 创建操作如果不在元数据中定义id那么会自动生成id + * 一个批量操作可以同时提交N多个 create,delete,update 请求,只需要符合json格式即可 + * 批量操作中的其中一个操作失败,不会影响到其他的批量操作,但是会在返回结果中提示失败的日志 + + # 不同index的批量操作 + POST /_bulk + {"create":{"_index":"user","_type":"coder", "_id":3}} + {"id":3,"name":"Batch Name","age":24} + {"delete":{"_index":"user","_type":"coder","_id":2}} + {"update":{"_index":"user","_type":"coder","_id":1,"retry_on_conflict":3}} + {"doc":{"name":"Batch Uapdate Name"}} + + # 相同index,不同type的批量操作 + POST /user/_bulk + {"create":{"_type":"coder","_id":3}} + {"id":3,"name":"Batch Name","age":24} + {"delete":{"_type":"coder","_id":2}} + {"update":{"_type":"coder","_id":1,"retry_on_conflict":3}} + {"doc":{"name":"Batch Uapdate Name"}} + + * 元数据仅仅需要声明 _type 和 id 即可,因为index已经在url中定义 + + # 相同index,相同type的批量操作 + POST /user/coder/_bulk + {"create":{"_id":3}} + {"id":3,"name":"Batch Name","age":24} + {"delete":{"_id":2}} + {"update":{"_id":1,"retry_on_conflict":3}} + {"doc":{"name":"Batch Uapdate Name"}} + + * 元数据仅仅需要声明 id 即可,因为 _type 和 _index 已经在url中定义 + + + # bulk 性能优化 + * bulk request 会加载到内容,如果太大的话,性能反而会下降,因此需要反复尝试一个最佳的bulk size + * 一般从 1000 - 5000条开始,尝试逐渐增加 + * 如果从大小上看的话,最好是在5 - 15MB之间 + + # bulk奇特的JSON格式与底层性能优化关系 + 1,bulk中的每个操作,都可能要转发到不同的node的shard去执行 + 2,如果采用比较良好的json数组格式 + (1),将json数组解析为 JSONArray 对象 + (2),对每个请求中的document进行路由 + (3),为路由到同一个shard上的多个请求创建一个请求数组 + (4),将这个请求数组序列化 + (5),把序列化的数据发送到对应的节点 + + * 耗费更多的内存,更多的jvm gc开销 + + 3,特殊的json请求格式 + (1),按照换行符切割json + (2),对每两个一组的json,读取meta,进行document路由 + (3),直接把对应的json发送到node上去 + + * 最大的优势在于,不需要将json数组解析为一个JSONArray对象,形成一份大数据的拷贝,浪费内存空间 \ No newline at end of file diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-document.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-document.java" new file mode 100644 index 00000000..076d76a5 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-document.java" @@ -0,0 +1,194 @@ +-------------------- +document的更新 | +-------------------- + # 强制更新 + POST /{index_name}/{type_name}/{id} + { + "_index": "test_index", + "_type": "product", + "_id": "1", + "_version": 2, + "result": "updated", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 1, + "_primary_term": 1 + } + + * 请求体需要提交所有字段,不存在的字段会被删除 + * 不管本次提交,是否有成功修改字段,result值永远为:'updated' + * 不管是有修改,_version字段必会加1 + * 可以理解为强制更新 + + # 非强制更新 + POST /{index_name}/{type_name}/{id}/_update + * 该种方式,提交的JSON体有所变化 + { + "doc":{ + //需要修改的字段 + } + } + * 可以仅仅提交更新需要更新的字段 + * 如果本次提交未修改数据的话,那么result字段值为:'noop',并且没有:'_seq_no'和'_primary_term'字段, + * 本次提交有修改数据的是,跟强制更新的返回结果是一样的 + * 只有在数据有修改的时候,version +1 + * 可以理解为非强制更新 + * partial update(部分更新) + + # 全量替换 + PUT /{index_name}/{type_name}/{id} + + * 如果id已经存在,那么原来的document不会被立即删除,而是会被标记为: delete + * 当es中数据越来越多的时候,es会在后台自己动的把标记为:delete 的document物理删除掉 + * _version 始终会 +1 + + +-------------------- +document的强制创建 | +-------------------- + # 创建文档域全量替换的语法是一样的 + # 如果只想新建文档,不想替换文档,那么就需要强制创建(两种方式) + PUT /index/type/id?op_type=create + PUT /index/type/id/_create + * 这种方式比较常见 + + * 如果该id的document已经存在,那么不会PUT成功,会抛出异常 + +-------------------- +document的删除 | +-------------------- + # document不会被立即的物理删除,只会被标记为delete,当数据越来越多的试试,在后台自动的删除 + + DELETE /index/type/id + +---------------------------- +partial update 详解 | +---------------------------- + # partial update + + POST /index/type/id/_update + { + "doc":{ + "仅仅需要修改的数据,不需要全量的数据" + } + } + * 看起来比较方便,仅仅需要传递修改修改的参数即可 + * '不需要先读取数据,再修改,直接提交需要修改的字段即可' + + # 内部原理 + * 其实es对 partial update 的执行,其实跟全量替换几乎是一样的 + * 在执行 partial update 的时候,内部还是会偷偷的先查询出所有document的数据,然后'更新需要更新的字段' + * 更新完成后,把旧的document标记为delete,再写入新的doucment + + # 优点 + 1,所有查询,修改,回写操作,都发生在一个shard内部,避免了所有网络数据传输的开销 + * 读取开销,回写开销 + 2,减少了修改和查询中的网络间隔,可以有效减少冲突的情况 + * 当前用户在修改界面,占用时间过长,其实该document已经被其他的用户发生了修改,当前用户执行更新会发生冲突 + + +---------------------------- +partial update 乐观锁并发控制| +---------------------------- + # 默认会使用乐观锁的并发控制策略 + * partial update 提交到shard后,会先去内容读取该document的所有field,以及version + * 修改partial update提交的部分field,然后回写,在回写的时候,使用version来处理并发控制 + + # retry策略 + * 在执行修改时,发现version不对 + * 再一次读取documnet的最新版本号 + * 基于最新的版本号去更新document + * 如果失败,则重复上述俩步骤,重复的次数可以通过 retry_on_conflict 值来控制 + + POST /user/coder/1/_update?retry_on_conflict=5 + + # 也可以手动通过请求参数 _version 来控制并发,当version不一致时,会给出异常 + + +------------------------------------ +document 路由 | +------------------------------------ + # document 路由到 shard + * index 数据会被分片到多少shard找那个,所以一个document只会存在一个shard + * 计算出 document 应该在存在哪个shard,其实就是路由 + + # 路由算法 + * shard = hash(routing) % number_of_primary_shards + > routing 默认为 document的 id(可以手动指定) + > hash算法 + > number_of_primary_shards :primary shad的数量 + + * 手动指定 routing + PUT /index/type/id?routing=15 + GET /index/type/id?routing=15 + + > 通过参数手动指定routing value很有用,可以保证,某一类document一定被路由到一个shard上 + > 那么在后续进行应用级别的负载均衡,以及提升批量读取性能的时候,是很有帮助的 + + # primary shard 数量不可变 + * 一旦发生变化了,那么在取模算法时,就会有问题,会间接导致数据丢失 + * 但是 replica shard可以随意增删 + +---------------------------- +document 增删改内部实现原理 | +---------------------------- + 1,客户端选择一个node发送请求,该node就是 coordinating node(协调节点) + * 每个node,都知道document在哪个node上 + + 2,coordinating node,对document进行路由,将请求转发给对应的node(primary shard) + * 增删改,只能由primary shard处理 + + 3,实际node上的primary shard处理请求,然后把数据同步到replica node + + + 4,coordinating node,如果发现primary node和所有replicat node都完成之后,响应客户端 + + + +------------------------------- +document写一致性 consistency | +-------------------------------- + # 在执行增删改的时候,可以提交一个 consistency 参数,来指定写一致性的级别 + PUT /index/type/id?consistency=one + + # 该值是一个枚举值 + one + * 只要有一个primary shard是active活跃可用,就可以执行 + + all + * 所有的primary shard和replica shard都是活跃的,才可以执行 + + quorum(默认) + * 当 number_of_replicas > 1时该机制才会生效 + * shard中活跃的shard数量超过/等于 quorum, 才可以执行写操作 + * 计算公式: quorum = int((primary + number_of_replicas) / 2) + 1 + primary -> primary shard的数量 + number_of_replicas -> 每个primary shard有几个replica shard + + * 如果活跃节点数量少于 quorum 数量,可能会导致quorum不齐全,进而导致无法执行写操作 + * quorum不齐全时,wait 默认1分钟(期望活跃的shard可以增加),可以通过 timeout 参数来控制(单位默认是毫秒,可以通过添加s来设置为秒) + PUT /index/type/id?timeout=60s + + +------------------------------- +document 查询原理 | +-------------------------------- + 1,客户端发送请求到任意node,该node成为:coordinate node + + 2,coordinate node 对 document 进行路由,将请求结果转发到对应的node + * 会使用 round-robin 随机轮询算法,在primary shard 和 replica shard 中随机选择一个 + * 让读,负载均衡 + + 3,处理请求的node返回document到 coordinate node + + 4,coordinate node 返回document 给客户端 + + 5,特殊情况 + * document还在建立索引过程中,可能只有primary shard有,任何一个 replica shard都没有 + * 如果负载到了 relicat shard,那么可能会读取不到document + + + \ No newline at end of file diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-groovy.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-groovy.java" new file mode 100644 index 00000000..83603afc --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-groovy.java" @@ -0,0 +1,5 @@ +-------------------- +Groovy | +-------------------- + # es有个内置的脚本支持,可以基于Groovy脚本实现各样的复杂操作. + # es scripting model diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-id\347\224\237\346\210\220.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-id\347\224\237\346\210\220.java" new file mode 100644 index 00000000..47667b60 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-id\347\224\237\346\210\220.java" @@ -0,0 +1,22 @@ +-------------------- +手动生成Document id | +-------------------- + # 提交 + put /index/id + +-------------------- +自动生成Document id | +-------------------- + # 提交 + put /index + + + # 自动生成的id特点 + * 长度为20个字符 + * URL安全 + * base64编码 + * GUID + * 分布式系统并发时不可能会发生冲突 + + + \ No newline at end of file diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-mget\346\211\271\351\207\217\346\237\245\350\257\242.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-mget\346\211\271\351\207\217\346\237\245\350\257\242.java" new file mode 100644 index 00000000..560bef4d --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-mget\346\211\271\351\207\217\346\237\245\350\257\242.java" @@ -0,0 +1,73 @@ +---------------------------- +mget 批量查询 | +---------------------------- + # 如果需要检索100数据,使用单次请求,那么就会进行100次的网络请求,消耗严重,如果使用批量查询,那么仅需要一次网络开销即可 + # mget 相当重要 + * 当一次性要检索多条数据的时候,一定要使用mget,减少网络开销可以大幅度提升性能 + + # mget 基本语法 + GET /_mget + { + "docs":[{ + "_index":"user", //通过 _index 指定index + "_type":"coder", //通过 _type 指定 type + "_id":1 //通过 _id 指定 id + },{ + "_index":"user", + "_type":"coder", + "_id":2 + }] + } + + * doc 是个数组,可以定义多个条件 + * 响应数据中的docs也是一个数组,返回的是符合条件的数据 + { + "docs": [ + { + "_index": "user", + "_type": "coder", + "_id": "1", + "_version": 3, + "found": true, + "_source": { + ... + } + }, + { + "_index": "user", + "_type": "coder", + "_id": "2", + "_version": 1, + "found": true, + "_source": { + ... + } + } + ] + } + + # 如果批量查询的document,是同一个index中,但是非同一个type中的时候可以使用另一种语法 + GET /user/_mget + { + "docs":[{ + "_type":"coder", + "_id":1 + }] + } + + * 仅仅需要在docs中指定_type和_id即可,因为在url中已经指定了 index + + # 如果批量查询的document,是在同一个index,痛一个type中的花,可以使用另外两种语法 + GET /user/coder/_mget + { + "docs":[{ + "_id":1 + }] + } + * 仅仅需要在docs中指定_id,因为url已经指定了index和type + + GET /user/coder/_mget + { + "ids":[1,2] + } + * json格式更简单,只要定义ids字段即可 diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-source\345\205\203\346\225\260\346\215\256.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-source\345\205\203\346\225\260\346\215\256.java" new file mode 100644 index 00000000..e061f876 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-source\345\205\203\346\225\260\346\215\256.java" @@ -0,0 +1,67 @@ +---------------------------- +_source 元数据 | +---------------------------- + # 检索数据的时候,返回的属性前面带有 _ ,的就是元数据 + { + "_index": "user", + "_type": "coder", + "_id": "3", + "_version": 2, + "found": true, + "_source": { + "id": 3, + "name": "Rocco", + "age": 21, + "gender": "girl", + "hobby": [ + "basketball", + "Sing", + "Write the code" + ], + "skill": { + "java": { + "level": "9" + }, + "python": { + "level": "9" + }, + "javascript": { + "level": "6" + } + } + } + } + + _source + * document数据 + +---------------------------- +_source 定制返回结果 | +---------------------------- + # 指定 _source 中返回哪个field + + # 仅仅查询name属性 + GET /user/coder/3?_source=name + { + ... + "_source": { + "name": "Rocco" + } + } + + # 仅仅查询name属性和 skill 属性中的 java属性 + GET /user/coder/3?_source=name,skill.java + { + ... + "_source": { + "skill": { + "java": { + "level": "9" + } + }, + "name": "Rocco" + } + } + + * 使用逗号分隔多个field,支持属性导航 + diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\345\255\230\345\202\250.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\345\255\230\345\202\250.java" new file mode 100644 index 00000000..10003e59 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\345\255\230\345\202\250.java" @@ -0,0 +1,18 @@ +------------------------------------ +distributed document store | +------------------------------------ + # es实质上就是一个分布式的文档数据,存储系统 + * 文档数据 ==> json + * 存储系统 ==> 对json数据可以进行 crud + + * es满足了这俩功能,其实已经是一个nosql的存储系统了 + + # es适用的app + * 数据量较大,es的分布式本质,可以帮助快速的扩容,承载大量数据 + * 数据结构灵活多变,随时可能会变化,而且数据结构之间的关系,非常复杂,如果用传统数据库,那需要大量的表 + * 对数据的操作,较为简单,比如就是一些简单的增删改查 + * NoSQL数据,适用的也是类似于上面的这种场景 + + + + diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-\345\271\266\345\217\221\345\206\262\347\252\201.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-\345\271\266\345\217\221\345\206\262\347\252\201.java" new file mode 100644 index 00000000..d0744d70 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\210\206\345\270\203\345\274\217\346\226\207\346\241\243\347\263\273\347\273\237-\345\271\266\345\217\221\345\206\262\347\252\201.java" @@ -0,0 +1,100 @@ +-------------------------------- +并发冲突-悲观锁 | +-------------------------------- + # 常见于关系型数据库中,不多解释 + SELECT .. FROM ... WHERE ... FOR UPDATE; + + # 并发能力差,但是方便 + + + +-------------------------------- +并发冲突-乐观锁 | +-------------------------------- + # CAS 算法 + # 可以通过version字段来控制 + # 场景 + 1, + * 线程a读取商品,剩余100,version=1 + * 线程b读取商品,剩余100,version=1 + + 2, + * 线程a下单,把库存 -1 + * 回写的时候检查当前数据的version是是否跟es中的version一样 + * 一样则回写,不一样,则重新读取最新数据后 -1,再回写 + + # 并发能力好,但是麻烦 + # 关于_version + * 每次创建新的document的时候,它的内部 _version 内部版本号就是1 + * 以后每次对该doucment进行修改/删除的时候,该_version都会自动+1(就算是删除,也会+1) + * 删除document的时候,并不会立即的进行物理删除,还保留着它们的元信息(_version等...) + + * 先删除一个document,再重新put(创建)的时候,_version 会在delete的记录上+1(玄学) + + # es内部多线程异步并发执行修改时,是基于_version版本号进行乐观锁控制的 + * 在同步修改的时候,会比较一下当前es的_version是否与当前的_version一样 + * 不一样就丢掉,一样则更新 + + # 基于 _version 来进行乐观锁进行并发冲突的控制 + POST /{index_name}/{type_name}/{id}?version=1 + POST /{index_name}/{type_name}/{id}/_update?version=1 + PUT /{index_name}/{type_name}/{id}?version=1 + + * 说白了,就是在进行修改的时候需要带上当前版本号 + * 在uri添加查询参数 version,该值为当前document的version字段 + * 在更新的时候系统会对进行乐观锁控制,如果version一样则修改,否则返回错误信息,不修改 + + # 基于乐观锁的的demo + 1,创建数据 + PUT /user/1 + { + "name":"KevinBlandy" + } + + 2,线程1,更新 + PUT /user/1?version=1 + { + "name":"KevinBlandy1" + } + * 更新成功,_version = 2 + + 3,线程2,更新 + PUT /user/1?version=1 + { + "name":"KevinBlandy1" + } + * 异常,因为线程1已经修改成功,version已经是2了, + { + "error": { + "root_cause": [ + { + "type": "version_conflict_engine_exception", + "reason": "[coder][666]: version conflict, current version [2] is different than the one provided [1]", + "index_uuid": "hmtk8vwASdCPpQENyyLwqw", + "shard": "0", + "index": "user" + } + ], + "type": "version_conflict_engine_exception", + "reason": "[coder][666]: version conflict, current version [2] is different than the one provided [1]", + "index_uuid": "hmtk8vwASdCPpQENyyLwqw", + "shard": "0", + "index": "user" + }, + "status": 409 + } + + # external version + * es提供了一个feature,可以不使用内容部的_version版本号来进行并发控制 + * 可以基于自己维护的'version版本号'来进行并发控制 + * 使用场景 + 在mysql中也存在一份数据,应用系统本身就维护了一个版本号,此时使用乐观锁控制的时候,不想使用es的version,而是想使用应用系统中的version + + * version控制语法 + ?version=1&version_type=external + + * 当 version_type=external 的时候,version参数必须要大于当前的_version才能更新成功 + * 在修改成功后,并且会把document的_version修改为version参数的值 + + + \ No newline at end of file diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\237\272\346\234\254\346\223\215\344\275\234ava.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\237\272\346\234\254\346\223\215\344\275\234ava.java" new file mode 100644 index 00000000..8394c125 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\237\272\346\234\254\346\223\215\344\275\234ava.java" @@ -0,0 +1,129 @@ +------------------------ +基本操作 | +------------------------ + +------------------------ +索引的操作 | +------------------------ + # 新建 + PUT /{index_name} + { + "acknowledged": true, + "shards_acknowledged": true, + "index": "test_index1" + } + + # 删除 + DELETE /{index_name} + { + "acknowledged": true + } + +------------------------ +Document-新增 | +------------------------ + PUT /{index_name}/{type_name}/{id} + { + "_index": "[index_name]", //index名称 + "_type": "[type_name]", //type名称 + "_id": "[id]", //document的id + "_version": 1, //版本号 + "result": "created", //执行结果 + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 0, + "_primary_term": 1 + } + +------------------------ +Document-更新 | +------------------------ + # 更新-1 + POST /{index_name}/{type_name}/{id} + { + "_index": "test_index", + "_type": "product", + "_id": "1", + "_version": 2, + "result": "updated", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 1, + "_primary_term": 1 + } + + * 请求体直接提交需要修改的字段即可 + * 不管本次提交,是否有成功修改字段,result值永远为:'updated' + * _version字段必会加1 + * 可以理解为强制更新 + + # 更新-2 + POST /{index_name}/{type_name}/{id}/_update + * 该种方式,提价的JSON体有所变化 + { + "doc":{ + //需要修改的字段 + } + } + * 如果本次提交未修改数据的话,那么result字段值为:'noop',并且没有:'_seq_no'和'_primary_term'字段,version也不会+1 + * 可以理解为非强制更新,仅仅更新需要更新的字段 + + + # 替换 + PUT /{index_name}/{type_name}/{id} + + # 对比 + * 直接替换的话,version会被重置为1,更新的话,version会加1 + * 更新情况下,result字段值为"updated" + * 直接替换不好,替换的情况下,必须带上所有的数据 + * 更新则,仅仅需要提供更新的字段即可 + +------------------------ +Document-删除 | +------------------------ + DELETE /{index_name}/{type_name}/{id} + { + "_index": "test_index", + "_type": "product", + "_id": "1", + "_version": 24, + "result": "deleted", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 23, + "_primary_term": 1 + } + + * 如果未删除成功(Document不存在),那么result值为: not_found + +------------------------ +Document-检索 | +------------------------ + GET /{index_name}/{type_name}/{id} + { + "_index": "[index_name]", + "_type": "[type_name]", + "_id": "1", + "_version": 1, + "found": true, //是否成功检索到 + "_source": { //数据 + "id": 1, + "name": "java", + "price": 30, + "producer": "this is producer", + "tags": [ + "t1", + "t2" + ] + } + } + \ No newline at end of file diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\244\232\347\247\215\346\243\200\347\264\242\346\226\271\345\274\217.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\244\232\347\247\215\346\243\200\347\264\242\346\226\271\345\274\217.java" new file mode 100644 index 00000000..3c806cb2 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\345\244\232\347\247\215\346\243\200\347\264\242\346\226\271\345\274\217.java" @@ -0,0 +1,268 @@ +------------------------ +多种检索方式 | +------------------------ + # 一般来说有6种类 + 1,query string search + 2,query DSL + 3,query filter + 4,full-text search + 5,phrase search + 6,highlight search + +------------------------ +检索响应 | +------------------------ + # 响应体 + { + "took": 2, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": 1, + "max_score": null, + "hits": [ + { + "_index": "user", + "_type": "coder", + "_id": "3", + "_score": null, + "_source": { + "id": 3, + "name": "Rocco", + "age": 21, + "gender": "girl", + "hobby": [ + "basketball", + "Sing", + "Write the code" + ], + "skill": { + "java": { + "level": "9" + }, + "python": { + "level": "9" + }, + "javascript": { + "level": "6" + } + } + }, + "sort": [ + 21 + ] + } + ] + } + } + + took + * 耗费毫秒 + timed_out + * 是否超时 + _shards + * 数据拆分为了5个分片,搜索请求会打到所有的primary shard(或者某个replica shar) + hits + hits.total + * 总记录数据 + hits.max_score + * document对于一个search的相关度的匹配系数,越相关,就越匹配,分数也越高 + hits.hits + * document的详细数据 + +------------------------ +query string search | +------------------------ + GET /{index}/{type}/_search?q=[k]:[v]&sort=[k]:[desc/asc] + * 检索k=v的所有记录,并且按照k进行desc/asc排序 + + * /user/coder/_search?q=gender:boy&sort=age:asc + - 检索出所有gender=boy的用户,并且按照age升序排序 + + * 最简单的检索,所有的条件,参数都是通过uri参数构建 + * 一般生产环境极少的使用 + + +------------------------ +query DSL | +------------------------ + # DSL:Domain Specied Language(特定的领域语言) + + # 检索gender=boy的所有数据,并且按照age升序排序,id降序排序 + { + "query":{ + "match":{ + "gender":"boy" + } + }, + "sort":[{"age":"asc"},{"id":"desc"}] + } + + # 分页查询 + { + "query":{ + "match_all":{} + }, + "from":1, //从第几条数据开始检索 + "size":2 //检索第几条 + } + + # 仅仅检索指定的字段数据 + { + "query":{ + "match_all":{} + }, + "_source":["name","id","skill.java"] //仅仅检索指定的字段 + } + * 仅仅检索Documnet里面的name,id属性.以及skill属性(对象)里面的java属性 + + + + # 请求体的定义详解 + + { + "query":{ + "match_all":{ + + }, + "match":{ + "key":"value" + } + }, + "sort":[ + {"key1":"desc"}, + {"key2":"asc"} + ], + "from":1, + "size":2, + "_source":[""] + } + + query.match_all + * 检索所有,该值是一个空对象{} + + query.match + * 检索符合条件的 + * 该值是一个对象,通过kv来组合条件 + from + * 从第几个数据开始检索,limit的第一个参数 + size + * 每页显示的记录数 + _source + * 该值是一个数组,指定要检索的字段,而不是检索所有 + * 通过字符串执行属性,支持.属性导航 + * "_source":["name","age","skill.java"] //仅仅检索name,age和skill属性里面的java属性 + +------------------------ +query filter | +------------------------ + # 对数据进行过滤,是基于DSL进行的过滤 + + # 检索所有性别为:girl,年龄 gt/lt/le/ge/ne 23的记录 + { + "query":{ + "bool":{ + "must":{ + "match":{ + "gender":"girl" + } + }, + "filter":{ + "range":{ + "age":{"gt/lt/le/ge/ne":"23"} + } + } + } + } + } + +------------------------ +full-text search | +------------------------ + # 全文检索 + { + "query":{ + "match":{ + "key":"value1 value2" + } + } + } + + * 检索 key里面包含了 value1,value2关键字的记录 + * 包含,即满足 + +------------------------ +full-phrase search | +------------------------ + # 短语搜索 + { + "query":{ + "match_phrase":{ + "key":"value" + } + } + } + + * 与全文检索相反,要求key的value必须完全匹配,才会被检索出来 + * 必须全匹配,才满足 + + +------------------------ +full-highlight search | +------------------------ + # 高亮 + { + "query":{ + "match":{ + "key":"value" + } + }, + "highlight":{ + "fields":{ + "key":{} + } + } + } + +-------------------- +聚合 | +-------------------- + { + "aggs":{ + "group_by_tags":{ + "terms":{ + "field":"tags" + } + } + } + } + + * 根据tags字段进行聚合检索,返回每个tag下的商品数量 + + { + "aggs":{ + "group_by_tags":{ + "terms":{ + "field":"tags", + "order":{ + "avg_price":"desc" + } + }, + "aggs":{ + "avg_price":{ + "field":"price" + } + } + } + } + } + + * 根据tags字段计算分组,然后计算机每组的平均值 + * 根据 avg_price 来降序排序 + + \ No newline at end of file diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-mapping.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-mapping.java" new file mode 100644 index 00000000..6a0826ef --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-mapping.java" @@ -0,0 +1,187 @@ +-------------------- +mapping | +-------------------- + # 自动或者手动为index中的type建立的一种数据结构和相关配置,简称为mapping + # 查看es自动建立的mapping + GET /index/_mapping/type + { + "user": { + "mappings": { + "coder": { + "properties": { + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "age": { + "type": "long" + }, + "foo": { + "type": "long" + }, + ... + + # 搜索结果不一致的问题 + * ES自动建立mapping的试试,设置了不同的field不同的data type + * 不同的data type的数据类型,分词,搜索行为是不一样的,所以出现了 _all,field的搜索表现不一样 + + + # 透彻理解mapping + 1,往es里面直接插入数据,es会自动建立索引,同时建立type以及对应的mapping + 2,mapping中就自动定义了每个field的数据类型 + 3,不同的数据类型(text,data),可能有的是exact value,有的是full text + 4,exact value 在建立倒排索引,分词的时候,是将整个值一起作为一个关键字建立到倒排索引中的 + 5,full text 就会进过各种处理(分词,normalizationm....),建立在倒排索引中 + 6,exact value 和 full text 的field在被搜索的时候,query string 也会经过相同的行为处理 + * exact value 全值匹配 + * full text 先对query string进行分词,normalizationm,再进行匹配 + 7,可以用es的dynamic mapping,让其自动建立mapping(自动设置数据类型),也可以手动的提前为index创建mapping,手动对每个field设置数据类型,索引行为,分词器... + 8,mapping,就是index的type的元数据,每个type都有一个自己的mapping,决定了数据类型,建立倒排索引的行为,还有进行搜索的行为 + +-------------------- +查看mapping | +-------------------- + GET /index/_mapping/type + { + "user": { + "mappings": { + "coder": { + "properties": { + "birthday": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + } + } + } + + +-------------------- +mapping-核心数据类型| +-------------------- + # 核心数据类型 + Text + Byte,Short,Integer,Long + Float,Double + Boolean + Date + KeyWord + + # 数据类型的推测规则 + true or false --> Boolean + 123 --> Long + 123.54 --> Double + 2017-01-01 --> Date + "Hello" --> Text + + + * 6.x 以前 text 叫做 String,在6.x以后,String 被移除 + + +-------------------- +mapping-创建 | +-------------------- + # 只能创建index时手动建立mapping,或者新增mapping + # 不能修改 field mapping + PUT /user + { + "mappings":{ + "coder":{ + "properties":{ + "desc":{ + "type":"text", + "analyzer":"english" + }, + "title":{ + "type":"text" + }, + "createDate":{ + "type":"date" + }, + "id":{ + "type":"long" + }, + "userId":{ + "type":"long", + "index":"true" + } + } + } + } + } + + * coder 表示type + * properties 中 key 来指定属性,value来设置分词器以及数据类型 + - index 属性指定字段的是否建立索引,boolean 值(在6.x以前,这个值是字符串枚举:analyzed,not_analyzed,no) + - analyzer 属性指定字段建立索引时,使用的分词器 + - type 指定字段的数据类型 + - filed ... + + + # 添加一个字段 + PUT /user/_mapping/coder + { + "properties":{ + "newField":{ + "type":"text", + "analyzer":"english" + } + } + } + + * 在url中指定index和type + * 通过请求体指定新的字段和其mapping属性 + +-------------------------- +mapping-复杂的数据类型转换| +-------------------------- + # 数组 + "tags":["Java","PHP"] + + * 建立索引的时候,跟String是一样的,只是说数据类型不能混,[]里面要么全是字符串,要么全是数字 + + # 空 + null,[],[null] + + # Object + * 底层的数据结构转换 + { + "name":"Kevin", + "skill":{ + "java":90, + "python":70 + } + } + ====================== + { + "name":"Kevin", + "skill.java":90, + "skill.python":70 + } + + * 更为复杂的数据结构 + { + "authors":[ + {"age":23,"name":"Kevin"}, + {"age":25,"name":"Litch"}, + {"age":24,"name":"Rocco"} + ] + } + ====================== + { + "authors.age":[23,25,24], + "authors.name":["Kevin","Litch","Rooc"] + } + \ No newline at end of file diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\345\200\222\346\216\222\347\264\242\345\274\225\346\240\270\345\277\203\345\216\237\347\220\206.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\345\200\222\346\216\222\347\264\242\345\274\225\346\240\270\345\277\203\345\216\237\347\220\206.java" new file mode 100644 index 00000000..5deb2142 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\345\200\222\346\216\222\347\264\242\345\274\225\346\240\270\345\277\203\345\216\237\347\220\206.java" @@ -0,0 +1,23 @@ +------------------------ +倒排索引核心原理 | +------------------------ + # 倒排索引的建立 + + doc-1: Hello KevinBlandy,Im Coder + doc-2: Hello Litch,You are Coder ? + + 关键字 doc-1 doc-2 + ------------------------------ + Hello * * + KevinBlandy * + Im * + Coder * * + + * 关键字在哪些索引出现过 + + # 在建立倒排索引的时候,会执行一个操作 normalization + * 对拆分出来的各个单词进行相应的处理 + * 以提升,后面搜索的时候能够搜索到关联文档的概率 + + * 我的理解其实就是对分词进行一些同义词,复数,时态,大小写...之类的转换 + diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\345\210\206\350\257\215\345\231\250.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\345\210\206\350\257\215\345\231\250.java" new file mode 100644 index 00000000..4c52f52d --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\345\210\206\350\257\215\345\231\250.java" @@ -0,0 +1,68 @@ +----------------------------- +分词器 | +----------------------------- + # 分词器包含了几个部分 + * 在文本进行分词之前,先进行预处理 + - 过滤html标签 + - & 转换为单词 and + + * 分词,把语句拆分为单词 + + * 对分词进行一些同义词,复数,时态,大小写...之类的转换 + * 这个过程叫做normalization(提升能够搜索到的结果数量) + + + # 内置的几种分词器 + 1,Standard analyzer(默认) + 2,Simple analyzer + 3,Whitespace analyzer + 4,Language analyzer + + +----------------------------- +query String分词器 | +----------------------------- + # query tring 默认情况下,es会使用它对应的field建立倒排索引时使用的分词器去进行分词 + * 分词,normalizationm,只有这种才能正确的搜索 + * query string 也会被进行分词,分词策略就是document建立倒排索引时的分词策略 + + # 不同类型的类型(exact value,full text),不同对待 + /_search?q=name:KevinBlandy + /_all/_search?q=KevinBlandy + +----------------------------- +分词器的测试 | +----------------------------- + # 直接测试分词器 + GET /_analyze + { + "analyzer": "standard", //指定分词器 + "text":"Hello KevinBlandy" //指定词句 + } + + # 测试指定index下指定字段的分词器 + GET /index/_analyze + { + "field":"desc", //指定字段 + "text":"hhhhh" //给字段指定值 + } + + # 响应分词信息 + { + "tokens": [ + { + "token": "hello", + "start_offset": 0, + "end_offset": 5, + "type": "", + "position": 0 + }, + { + "token": "kevinblandy", + "start_offset": 6, + "end_offset": 17, + "type": "", + "position": 1 + } + ] + } \ No newline at end of file diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\345\210\235\350\257\206.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\345\210\235\350\257\206.java" new file mode 100644 index 00000000..21f938ad --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\345\210\235\350\257\206.java" @@ -0,0 +1,113 @@ +------------------------ +search 结果 | +------------------------ + { + "took": 19, + * 整个搜索请求花费了多少毫秒 + "timed_out": false, + * 是否超时 + "_shards": { + * 关于shard的一些信息 + "total": 6, + * 默认来说说,一个搜索请求,会打到该index所有的primary shard上去 + "successful": 6, + "skipped": 0, + "failed": 0 + }, + "hits": { + * 搜索结果 + "total": 6, + * 本次搜索的返回的结果 + "max_score": 1, + * 本次搜索所有结果中,最大相关度分数是多少 + * 每一跳document对于search的相关度,越相关,_score分数越大,排位越靠前 + "hits": [ + * 数据 + { + "_index": ".kibana", + "_type": "doc", + "_id": "config:6.3.0", + "_score": 1, + "_source": { + "type": "config", + "updated_at": "2018-06-20T13:41:22.461Z", + "config": { + "buildNum": 17230, + "telemetry:optIn": true + } + } + } + ] + } + } + +------------------------ +timeout机制 | +------------------------ + + # timeout机制 + * 默认情况下,没有timeout + * 该机制,指定了每个shard就只能在timeout时间范围内,将收索到的数据响应给客户端 + * 在timeout时间内,可能已经搜索出来了,也可能没搜索全,反正不会等,立即把已经搜索到的数据响应 + + # 语法 + GET /_search?timeout=10ms + GET /_search?timeout=1m + + * 使用timeout参数来指定秒,毫秒... + +------------------------ +multi-index和multi-type | +------------------------ + GET /_search + * 检索所有index,及其index下所有type的数据 + + GET /index/type/_search + * 检索index下所有type的数据 + + GET /index/type1,type2/_search + * 一个index下多个type + * 搜索index下type1,type2下的所有数据 + + GET /index1,index2/_search + * 多个index + * 检索index1,index2及其下所有type的数据 + + GET /index1,index2/type1,type2/_search + * 多个index,多个type + * 检索index1下的type1,index2下的type,的所有数据 + + GET /index_*/_search + * 支持通配符 + * 检索所有名称以 'index_' 开头的index,及其下所有type的数据 + + GET /_all/type1,type2/_search + * 检索所有index下,指定type的数据 + + +------------------------ +搜索原理 | +------------------------ + # clinet的一个请求,会分布到index的所有的primary shard上,因为它可能在任何一个primary shard上 + * 如果primary shard还有replica shard,那么还会请求到replica shard上 + + +------------------------ +分页搜索 | +------------------------ + # 分页检索 + GET /_search?size=10 + GET /_search?size=10&from=0 + GET /_search?size=10&from=20 + + * size,每页显示的记录数量 + * from,从第几条数据开始检索,0表示第一条 + + # deep paging问题 + * deep paging,简单来说,就是搜索得特别深,比如1000000条数据,每页显示10条,此时需要检索最后一页的数据 + * 符合请求的数据,可能存在于多个primary shard,replica shard,于是就要把所有数据汇总到 coordinating node(协调节点) + * 由协调节点进行排序,取出最符合条件的数据,按照分页返回 + * 这个过程耗费带宽,内存,网络计算,这个就是deep paging问题,我们的开发尽量要避免这种情况 + + + diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\346\220\234\347\264\242\350\257\255\346\263\225.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\346\220\234\347\264\242\350\257\255\346\263\225.java" new file mode 100644 index 00000000..2e7d3638 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\346\220\234\347\264\242\345\274\225\346\223\216-\346\220\234\347\264\242\350\257\255\346\263\225.java" @@ -0,0 +1,222 @@ +------------------------------------ +_all metadata的原理和作用 | +------------------------------------ + GET /_search?q=elaticsearch + * 所有的index,所有的type,所有的document,所有的field里面有elaticsearch就符合条件 + * 这个在生产环境用得最多 + * 也可以通过url去限制index,type + + # 原理 + * 在建立索引的时候,插入一条document,它包含了多个field,此时es会把多个field的值用字符串串联,变成一个很长的字符串 + { + "name":"KevinBlandy", + "age":23, + "gender":"男" + } + =============== 元数据 + "KevinBlandy 23 男" + + * 这个字符串叫做 _all 元数据 + + +------------------------------------ +Query String 基础语法 | +------------------------------------ + GET /index/type/_search?q=content:elaticsearch + * content必须包含elaticsearch关键字 + + GET /index/type/_search?q=+content:elaticsearch + * 同上 + + GET /index/type/_search?q=-content:elaticsearch + * content必须没包含elaticsearch关键字 + + GET /_search + GET /_index1,index2/type1,type2/_search + * 针对于多个index,type的检索 + + + * HTTP协议规定GET没有请求体,一般也不允许GET请求带有body,但GET更加适合于检索场景 + * 如果遇到不支持的场景,也可以使用:POST /_search + + +------------------------------------ +Query String - 分页 & 排序 | +------------------------------------ + * 分页 + GET /_search?from=0&size=10 + + * from 表示从第几条数据开始 + * size 表示取几条数据 + + * 排序 + GET /_search?sort=age:asc + + * 按照age升序排序 + + +------------------------------------ +DSL - query | +------------------------------------ + + # 检索所有 + { + "query":{ + "match_all": {} + } + } + + # 根据字段数据检索 + { + "query":{ + "match":{ + "name":"KevinBlandy" + } + } + } + * 检索 name 属性里面包含了 KevinBlandy 的记录 + * 一个match只能有一个属性,不支持多个属性 + + # 多个字段的相同值的检索 + { + "query": { + "multi_match":{ + "query":"1", + "fields": ["id","name"] + } + } + } + * 检索 id 包含了 1 或者 name包含了 1 的索引 + + # boolean 属性 + * boolean 属性是一个或者多个条件 + * 属性名称枚举固定,属性值可以为数组或者对象 + * must 一个或者多个条件,必须全部满足 + * should 一个或者多个条件,满足一个即可 + * must_not 一个或者多个条件,必须全部不满足 + * filter 过滤(大于,等于,小余) + + * must 必须 + * should 任何一个条件满足即可 + * minimm_should_match 至少要匹配到一条 + + # boolean - filter + { + "query": { + "bool": { + "filter": { + "range": { + "id": { //字段 + "gte": 10, //条件和值 + "lte": 20 + } + } + } + } + } + } + * range 也可以直接放在query里面,这样的话,会进行相关度的计算 + + # query 与 filter + * filter仅仅按照搜索条件过滤出需要的数据 + * query,会去计算每个document相对于搜索条件的相关度,并且按照相关度进行排序 + + * filter不需要计算相关度分数,不需要按照相关度分数进行排序,同时还有内置的,自动cache最常使用filter的功能 + * query相反,需要计算相关度分数,按照分数进行排序,而且无法cache结果 + + + +------------------------------------ +DSL - 定制搜索结果 | +------------------------------------ + "query":{ + "_source":[], + } + + * 该值是一个数组,指定要检索的字段,而不是检索所有 + * 通过字符串执行属性,支持.属性导航 + +------------------------------------ +DSL - 排序 | +------------------------------------ + "query":{ + "sort":[ + {"field1":"desc"}, + {"field2":"asc"} + ], + } + + * desc,倒序,asc 正序 + +------------------------------------ +DSL - 限制结果数量 | +------------------------------------ + "query":{ + "from":1, + "size":2, + } + + * 从第一条记录开始,检索出2条结果 + +------------------------------------ +代码高亮 | +------------------------------------ + "query":{ + "highlight":{ + "fields":{ + "field":{} + } + } + } + +------------------------------------ +判断你的检索是否可用 | +------------------------------------ + GET /index/type/_validate/query?explain + + { + "_shards": { + "total": 1, + "successful": 1, + "failed": 0 + }, + "valid": true, + "explanations": [ + { + "index": "user", + "valid": true, + "explanation": "+ConstantScore(id:[2 TO 9223372036854775807]) #*:*" + } + ] + } + + * 在特别复杂,庞大的检索下,可以先使用该api测试一下检索语句是否有问题 + + + +------------------------------------ +解决字符串排序的问题 | +------------------------------------ + # 把一个field索引两次来解决字符串的排序问题 + PUT /user + { + "mappings":{ + "coder":{ + "properties":{ + "title":{ + "type":"text", + "analyzer":"english", + "field":{ //再一次建立索引 + "raw":{ + "type":"string", //类型 + "index":"false", //不索引 + "fielddata":true + } + } + } + } + } + } + } + + * 没看懂是啥骚操作 diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\347\273\203\344\271\240.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\347\273\203\344\271\240.java" new file mode 100644 index 00000000..3cf39744 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\347\273\203\344\271\240.java" @@ -0,0 +1,8 @@ +-------------------------------- +电商网站商品管理 | +-------------------------------- + +-------------------------------- +Document 数据格式 | +-------------------------------- + diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\347\273\203\344\271\240\346\225\260\346\215\256.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\347\273\203\344\271\240\346\225\260\346\215\256.java" new file mode 100644 index 00000000..0dd88e9c --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\347\273\203\344\271\240\346\225\260\346\215\256.java" @@ -0,0 +1,87 @@ + +GET /_cat/indices?v + +PUT /user?pretty + +PUT /user/coder/1?pretty +{ + "id":1, + "name":"KevinBlandy", + "birthday":"2018-8-9 21:23:30", + "gender":"男", + "skill":["Java","Python","Javascript"], + "describe":"Christmas Day is on December 25th. It was originated in the western country, but today, this festival has been celebrated by the world. For the manufacturers, they are very happy to make this day as a shopping day. I enjoy the great atmosphere.", + "account":{ + "qq":"747692844", + "github":"KevinBlandy" + } +} + +PUT /user/coder/2?pretty +{ + "id":2, + "name":"Litch", + "birthday":"2017-7-9 15:15:20", + "gender":"男", + "skill":["Java","C++","Ruby"], + "describe":"I had a very special Christmas day last year. I experienced the western style festival. There was a new foreign teacher taught us the lesson. She was about 50 years old and she was very kind and we all liked her. On Christmas Day, she brought us the desserts she made early in the morning. We enjoyed the home-made cakes. What's more, she invited us to came to her house and spent the day with her. Then for the first time, I ate big turkey, which was so delicious. The turkey was filled with many stuffs and the flavor was so good. After dinner, we sang songs and danced.", + "account":{ + "qq":"547415696", + "github":"Faker" + } +} + +PUT /user/coder/3?pretty +{ + "id":3, + "name":"Rocco", + "birthday":"1994-8-9 22:15:14", + "gender":"男", + "skill":["C","Go","Groovy","Java"], + "describe":"Thanks to my foreign teacher, I experienced the American style festival. It was such funny for me. Though today many people enjoy shopping in all kinds of festivals, the meaning of these festival should be remembered. ", + "account":{ + "qq":"548755564", + "github":"Rocco" + } +} + +PUT /user/coder/4?pretty +{ + "id":4, + "name":"Lili", + "birthday":"1996-12-9 14:25:30", + "gender":"女", + "skill":["Java","Ruby","Scala"], + "describe":"She had been shopping with her Mom in Wal-Mart. She must have been 6 years old, this beautiful brown haired, freckle-faced image of innocence. It was pouring outside. The kind of rain that gushes over the top of rain gutters, so much in a hurry to hit the Earth, it has no time to flow down the spout.", + "account":{ + "qq":"654845125", + "github":"Lili_lili" + } +} + +PUT /user/coder/5?pretty +{ + "id":5, + "name":"Lucy", + "birthday":"1997-12-4 4:15:3", + "gender":"女", + "skill":["Groovy","C++","Delphi"], + "describe":"We all stood there under the awning and just inside the door of the Wal-Mart. We all waited, some patiently, others irritated, because nature messed up their hurried day. I am always mesmerized by rainfall. I get lost in the sound and sight of the heavens washing away the dirt and dust of the world. Memories of running, splashing so carefree as a child come pouring in as a welcome reprieve from the worries of my day.", + "account":{ + "qq":"948593625", + "github":"Lucy012" + } +} + +POST /user/coder/_bulk +{"index":{"_id":1}} +{"id":1,"name":"KevinBlandy","birthday":"2018-8-9 21:23:30","gender":"男","skill":["Java","Python","Javascript"],"describe":"Christmas Day is on December 25th. It was originated in the western country, but today, this festival has been celebrated by the world. For the manufacturers, they are very happy to make this day as a shopping day. I enjoy the great atmosphere.","account":{"qq":"747692844","github":"KevinBlandy"}} +{"index":{"_id":2}} +{"id":2,"name":"Litch","birthday":"2017-7-9 15:15:20","gender":"男","skill":["Java","C++","Ruby"],"describe":"I had a very special Christmas day last year. I experienced the western style festival. There was a new foreign teacher taught us the lesson. She was about 50 years old and she was very kind and we all liked her. On Christmas Day, she brought us the desserts she made early in the morning. We enjoyed the home-made cakes. What's more, she invited us to came to her house and spent the day with her. Then for the first time, I ate big turkey, which was so delicious. The turkey was filled with many stuffs and the flavor was so good. After dinner, we sang songs and danced.","account":{"qq":"547415696","github":"Faker"}} +{"index":{"_id":3}} +{"id":3,"name":"Rocco","birthday":"1994-8-9 22:15:14","gender":"男","skill":["C","Go","Groovy","Java"],"describe":"Thanks to my foreign teacher, I experienced the American style festival. It was such funny for me. Though today many people enjoy shopping in all kinds of festivals, the meaning of these festival should be remembered. ","account":{"qq":"548755564","github":"Rocco"}} +{"index":{"_id":4}} +{"id":4,"name":"Lili","birthday":"1996-12-9 14:25:30","gender":"女","skill":["Java","Ruby","Scala"],"describe":"She had been shopping with her Mom in Wal-Mart. She must have been 6 years old, this beautiful brown haired, freckle-faced image of innocence. It was pouring outside. The kind of rain that gushes over the top of rain gutters, so much in a hurry to hit the Earth, it has no time to flow down the spout.","account":{"qq":"654845125","github":"Lili_lili"}} +{"index":{"_id":5}} +{"id":5,"name":"Lucy","birthday":"1997-12-4 4:15:3","gender":"女","skill":["Groovy","C++","Delphi"],"describe":"We all stood there under the awning and just inside the door of the Wal-Mart. We all waited, some patiently, others irritated, because nature messed up their hurried day. I am always mesmerized by rainfall. I get lost in the sound and sight of the heavens washing away the dirt and dust of the world. Memories of running, splashing so carefree as a child come pouring in as a welcome reprieve from the worries of my day.","account":{"qq":"948593625","github":"Lucy012"}} + diff --git "a/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\351\273\230\350\256\244\347\232\204\346\234\215\345\212\241.java" "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\351\273\230\350\256\244\347\232\204\346\234\215\345\212\241.java" new file mode 100644 index 00000000..cb91ada4 --- /dev/null +++ "b/Elasticsearch/\346\227\247\347\254\224\350\256\260/es-\351\273\230\350\256\244\347\232\204\346\234\215\345\212\241.java" @@ -0,0 +1,36 @@ + +---------------------- +查看服务的健康状态 | +---------------------- + # /_cluster/health + { + "cluster_name": "elasticsearch", + "status": "green", + * 状态等级,以颜色划分:green,yellow,red + "timed_out": false, + "number_of_nodes": 1, + * 节点数量 + "number_of_data_nodes": 1, + "active_primary_shards": 1, + "active_shards": 1, + "relocating_shards": 0, + "initializing_shards": 0, + "unassigned_shards": 0, + "delayed_unassigned_shards": 0, + "number_of_pending_tasks": 0, + "number_of_in_flight_fetch": 0, + "task_max_waiting_in_queue_millis": 0, + "active_shards_percent_as_number": 100 + } + + # 账户状态 + green + * 每个索引的primary shard和replica shard都是active状态的 + yellow + * 每个索引的primary shard都是active状态的,但是部分replica shard不是active状态 + red + * 不是所有索引的primary shard都是acvive状态的,部分索引有可能数据丢失 + + + +/ecommerce/_mapping/{index} \ No newline at end of file diff --git a/Fastjson/fastjson-api.java b/Fastjson/fastjson-api.java index 46868f4d..05c7a83e 100644 --- a/Fastjson/fastjson-api.java +++ b/Fastjson/fastjson-api.java @@ -13,6 +13,28 @@ List parseArray(String text, Class clazz); * 把JSON字符串反序列化为List集合 + +# 构造一个排序的JSONObject 对象 + new JSONObject(true); + //源码 + public JSONObject(boolean ordered){ + this(DEFAULT_INITIAL_CAPACITY, ordered); + } + public JSONObject(int initialCapacity, boolean ordered){ + if (ordered) { + //如果是需要排序的话,创建的就是 LinkedHashMap 对象 + map = new LinkedHashMap(initialCapacity); + } else { + map = new HashMap(initialCapacity); + } + } + +----------------------------------- +反序列化泛型 | +----------------------------------- + T obj = JSON.parseObject("json str", new TypeReference() {}); + * 空实现一个 TypeReference 实例对象,该对象的泛型就是最终序列化的泛型结果 + ----------------------------------- Spring 相关 | ----------------------------------- @@ -37,20 +59,23 @@ * 输出key时是否使用双引号,默认为true SerializerFeature.UseSingleQuotes * 使用单引号而不是双引号,默认为false - SerializerFeature.WriteMapNullValue - * 是否输出值为null的字段,默认为false SerializerFeature.WriteEnumUsingToString * Enum输出name()或者original,默认为false + * 如果该属性为true,则枚举字段输出 toString() 的返回值 SerializerFeature.UseISO8601DateFormat * Date使用ISO8601格式输出,默认为false + + SerializerFeature.WriteMapNullValue + * 是否输出值为null的普通字段和object字段,默认为false SerializerFeature.WriteNullListAsEmpty * List字段如果为null,输出为[],而非null SerializerFeature.WriteNullStringAsEmpty - * 字符类型字段如果为null,输出为”“,而非null + * 字符类型字段如果为null,输出为"",而非null SerializerFeature.WriteNullNumberAsZero * 数值字段如果为null,输出为0,而非null SerializerFeature.WriteNullBooleanAsFalse * Boolean字段如果为null,输出为false,而非null + SerializerFeature.SkipTransientField * 如果是true,类中的Get方法对应的Field是transient,序列化时将会被忽略。默认为true SerializerFeature.SortField diff --git a/Fastjson/fastjson-jsonpath.java b/Fastjson/fastjson-jsonpath.java new file mode 100644 index 00000000..8ca377f5 --- /dev/null +++ b/Fastjson/fastjson-jsonpath.java @@ -0,0 +1,85 @@ +----------------- +jsonpath | +----------------- + # 类似于XPATH, 用于解析访问 json + * 如果json体非常的复杂, 嵌套很深的话, 一层层的去访问非常的难受, + * 就可以选择使用这种快速查找的表达式 + + +----------------- +JSONPath | +----------------- + # 构造方法 + public JSONPath(String path) + public JSONPath(String path, SerializeConfig serializeConfig, ParserConfig parserConfig) + + # 静态属性/方法 + public static Object eval(Object rootObject, String path) + public static Object extract(String json, String path, ParserConfig config, int features, Feature... optionFeatures) + public static Object extract(String json, String path) + * 根据path检索值 + * extract,按需计算, 性能会更好 + + public static int size(Object rootObject, String path) + * 计算Size + * Map非空元素个数, 对象非空元素个数, Collection的Size, 数组的长度 + * 其他无法求值返回-1 + + public static Set keySet(Object rootObject, String path) + * 获取, Map的KeySet, 对象非空属性的名称 + * 数组, Collection等不支持类型返回null + + public static boolean contains(Object rootObject, String path) + * 是否包含, path中是否存在对象 + + public static boolean containsValue(Object rootObject, String path, Object value) + * 是否包含, path中是否存在指定值 + * 如果是集合或者数组, 在集合中查找value是否存在 + + public static void arrayAdd(Object rootObject, String path, Object... values) + * 在数组或者集合中添加元素, 添加成功返回 true,失败返回 false + + public static boolean set(Object rootObject, String path, Object value) + * 修改制定路径的值, 如果修改成功, 返回true, 否则返回false + + public static boolean remove(Object root, String path) + * 删除指定path的元素, 删除成功返回 true,失败返回 false + + public static JSONPath compile(String path) + * 编译一个jsonpath为对象 + + public static Object read(String json, String path) + * 从一个json字符串中, 根据指定的path读取为Json对象 + + public static Map paths(Object javaObject) + public static Map paths(Object javaObject, SerializeConfig config) + * 返回指定Java对象的属性的所有json访问path + + # 实例方法 + * 实例方法跟上述的静态方法差不多, 只是不需要 path 参数 + * 因为构造的时候已经设置, 类似于 Pattern + + + +----------------- +支持语法 | +----------------- + JSONPATH 描述 + $ 根对象, 例如$.name + [num] 数组访问, 其中num是数字,可以是负数, 例如:$[0].leader.departments[-1].name + [num0,num1,num2...] 数组多个元素访问, 其中num是数字, 可以是负数, 返回数组中的多个元素,例如:$[0,3,-2,5] + [start:end] 数组范围访问, 其中start和end是开始小表和结束下标, 可以是负数, 返回数组中的多个元素, 例如:$[0:5] + [start:end :step] 数组范围访问, 其中start和end是开始小表和结束下标, 可以是负数,step是步长, 返回数组中的多个元素例如:$[0:5:2] + [?(key)] 对象属性非空过滤, 例如:$.departs[?(name)] + [key > 123] 数值类型对象属性比较过滤,例如:$.departs[id >= 123],比较操作符支持:=,!=,>,>=,<,<= + [key like 'aa%'] 字符串类型like过滤,例如:$.departs[name like 'sz*'], 通配符只支持:% 支持:not like + [key rlike 'regexpr'] 字符串类型正则匹配过滤,:例如departs[name like 'aa(.)*'], 正则语法为jdk的正则语法, 支持:not rlike + [key in ('v0', 'v1')] IN过滤, 支持字符串和数值类型,例如: $.departs[name in ('wenshao','Yako')] $.departs[id not in (101,102)] + [key between 234 and 456] BETWEEN过滤, 支持数值类型,支持not between 例如: $.departs[id between 101 and 201] $.departs[id not between 101 and 201] + length() 或者 size() 数组长度, 例如$.values.size() 支持类型java.util.Map和java.util.Collection和数组 + keySet() 获取Map的keySet或者对象的非空属性名称, 例如$.val.keySet() 支持类型:Map和普通对象,不支持:Collection和数组(返回 null) + . 属性访问, 例如$.name + ['key'] 属性访问, 例如$['name'] + ['key0','key1'] 多个属性访问, 例如$['id','name'] + .. deepScan属性访问,例如$..name + * 对象的所有属性, 例如$.leader.* diff --git "a/Fastjson/fastjson-\345\244\204\347\220\206\346\236\232\344\270\276.java" "b/Fastjson/fastjson-\345\244\204\347\220\206\346\236\232\344\270\276.java" new file mode 100644 index 00000000..a5851c61 --- /dev/null +++ "b/Fastjson/fastjson-\345\244\204\347\220\206\346\236\232\344\270\276.java" @@ -0,0 +1,155 @@ +---------------------------- +枚举处理 | +---------------------------- + # 默认的枚举处理是直接把枚举的名称序列化为字符串 + + + + +-------------------------------- +把枚举对象转换为自定义的数据类型| +-------------------------------- + # 定义Serialize和Deserialize + import java.io.IOException; + import java.lang.reflect.Type; + import com.alibaba.fastjson.serializer.JSONSerializer; + import io.javaweb.common.BaseEnum; + public class EnumsSerialize implements com.alibaba.fastjson.serializer.ObjectSerializer { + + public EnumsSerialize() { + } + + @Override + public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)throws IOException { + if(!(object instanceof BaseEnum)) { + //抽象出来一个借口,必须要有 getValue() 方法,所有的枚举都应该实现这个方法 + throw new RuntimeException(object.getClass().getName() + " 未实现 io.javaweb.common.BaseEnum 接口"); + } + serializer.out.writeInt(((BaseEnum)object).getValue()); + } + } + + import java.lang.reflect.Type; + import com.alibaba.fastjson.parser.DefaultJSONParser; + import com.alibaba.fastjson.parser.JSONToken; + import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; + + import io.javaweb.common.BaseEnum; + + + public class EnumDeserialize implements ObjectDeserializer { + + @SuppressWarnings("unchecked") + @Override + public T deserialze(DefaultJSONParser parser, Type type, java.lang.Object fieldName) { + + Integer intValue = parser.parseObject(int.class); + + String typeName = type.getTypeName(); + + try { + + Class clazz = Class.forName(typeName); + + if(!clazz.isEnum()) { + throw new IllegalArgumentException(clazz.getName() + " 非枚举"); + } + + if(!BaseEnum.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException(clazz.getName() + " 未实现 io.javaweb.common.BaseEnum 接口"); + } + + BaseEnum[] constants = (BaseEnum[])clazz.getEnumConstants(); + + for(BaseEnum constant : constants) { + if(constant.getValue().equals(intValue)) { + return (T) constant; + } + } + + throw new IllegalArgumentException("value " + intValue + ",在 " + clazz.getName() + " 中未定义"); + + } catch (ClassNotFoundException | IllegalArgumentException | SecurityException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + + } + + @Override + public int getFastMatchToken() { + return JSONToken.LITERAL_INT; + } + } + + + + + + # http 消息转换器的配置 + @Bean + public HttpMessageConverters fastJsonHttpMessageConverter() { + + FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); + + //实例化序列配置 + SerializeConfig serializeConfig = new SerializeConfig(); + //指定枚举,以及创建Serialize对象 + serializeConfig.put(BaseEntity.Status.class, new EnumsSerialize()); + + FastJsonConfig fastJsonConfig = new FastJsonConfig(); + fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); + fastJsonConfig.setCharset(StandardCharsets.UTF_8); + fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); + + //设置到fastJsonConfig + fastJsonConfig.setSerializeConfig(serializeConfig); + + fastJsonHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON_UTF8)); + fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); + + return new HttpMessageConverters(fastJsonHttpMessageConverter); + } + + # 注解式(最简单) + * 在枚举字段上声明注解 + @JSONField(serializeUsing = EnumsSerialize.class,deserializeUsing = EnumDeserialize.class) + + +-------------------------------- +序列化枚举为ordinal | +-------------------------------- + # 全局设置 + JSON.DEFAULT_GENERATE_FEATURE &= ~SerializerFeature.WriteEnumUsingName.mask; + + # 特殊设置 + // 临时计算出配置 + int serializerFeatures = JSON.DEFAULT_GENERATE_FEATURE & ~SerializerFeature.WriteEnumUsingName.mask; + String text = JSON.toJSONString(object, serializerFeatures); + +-------------------------------- +Enum当做JavaBean序列化 | +-------------------------------- + # 在枚举类标识注解 + @JSONType(serializeEnumAsJavaBean = true) + + # 代码配置 + //创建序列化配置 + SerializeConfig serializeConfig = new SerializeConfig(); + //使用该 api 来指定枚举类.class,可变参数,仅仅对这一次的序列化有效 + serializeConfig.configEnumAsJavaBean(PreservationModel.Category.class); + + // 也可以全局设置,所有默认的序列化都有效 + // SerializeConfig.globalInstance.configEnumAsJavaBean(OrderType.class); + + + //创建fastjson配置 + FastJsonConfig fastJsonConfig = new FastJsonConfig(); + fastJsonConfig.setSerializeConfig(serializeConfig); + + PreservationModel preservationModel = new PreservationModel(); + preservationModel.setCategory(PreservationModel.Category.BASIS); + preservationModel.setId(1); + + //在序列化对象时候,设置fastjsonConfig或者serializeConfig + System.out.println(JSON.toJSONString(preservationModel, serializeConfig)); \ No newline at end of file diff --git a/Fastjson/fastjson.java b/Fastjson/fastjson.java index be037d77..035ce5fd 100644 --- a/Fastjson/fastjson.java +++ b/Fastjson/fastjson.java @@ -27,16 +27,25 @@ 5,把JSON数组转换为JSON对象 JSONArray JSONArray.parse(String jsonArray) + -------------------- FastJson-注解 | -------------------- @JSONField + name + * 指定该字段的json key名称 + format * 标注在Bean的字段属性上,以字符串的形式指定格式化形式 serialize * true/false,是否序列化该字段 + ordinal + * 排序 + * 字段越小,则字段排在越前面 + + diff --git a/Flexmark/flexmark.java b/Flexmark/flexmark.java new file mode 100644 index 00000000..3ecd1686 --- /dev/null +++ b/Flexmark/flexmark.java @@ -0,0 +1,6 @@ +------------------------------ +flexmark | +------------------------------ + # markdown 转换为 html 的工具库 + # 地址 + https://github.com/vsch/flexmark-java \ No newline at end of file diff --git a/Freemarker/freemarker-servlet.java b/Freemarker/freemarker-servlet.java new file mode 100644 index 00000000..a2c079b0 --- /dev/null +++ b/Freemarker/freemarker-servlet.java @@ -0,0 +1,103 @@ +------------------- +servlet 环境 | +-------------------- + # 官方提供一个类库....花里胡哨 + + freemarker + freemarker.ext.servlet.FreemarkerServlet + + + + TemplatePath + / + + + NoCache + true + + + ContentType + text/html; charset=UTF-8 + + + + + incompatible_improvements + 2.3.22 + + + + template_exception_handler + + rethrow + + + template_update_delay + + 0 + + + default_encoding + + UTF-8 + + + locale + + en_US + + + number_format + 0.########## + + + 1 + + + + freemarker + *.ftl + + + ... + + + + + FreeMarker MVC Views + *.ftl + + + + + + + +------------------- +servlet 自定义 | +-------------------- + this.configuration = new Configuration(Configuration.VERSION_2_3_28); + + //设置模板路径 + configuration.setServletContextForTemplateLoading(super.getServletContext() , "/WEB-INF/templates"); + + //设置读取默认的编码 + configuration.setDefaultEncoding("UTF-8"); + + //设置输出编码 + configuration.setOutputEncoding("UTF-8"); + + //设置模板异常处理器,抛出异常,由程序处理 + configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + + try { + //设置模板引擎变化检测的间隔时间 + configuration.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE, "0ms"); + } catch (TemplateException e) { + e.printStackTrace(); + } \ No newline at end of file diff --git a/Freemarker/freemarker-spring.ftl.java b/Freemarker/freemarker-spring.ftl.java new file mode 100644 index 00000000..5028cb58 --- /dev/null +++ b/Freemarker/freemarker-spring.ftl.java @@ -0,0 +1,382 @@ +---------------------------- +Spring提供的宏 | +---------------------------- + # spring.ftl + org/springframework/spring-webmvc/5.0.6.RELEASE/spring-webmvc-5.0.6.RELEASE.jar!/org/springframework/web/servlet/view/freemarker/spring.ftl + + # message + * 参数只有code, 是我们国际化中最常用的 + * 它实际调用了RequestContext的getMessage(String code)方法 + + # messageArgs + * 参数有两个code和args, 国际化字符串中有占位符时可以用到 + * 它实际调用了RequestContext的getMessage(String code, @Nullable Object[] args)方法 + + +---------------------------- +Spring提供的宏 | +---------------------------- +<#ftl output_format="HTML" strip_whitespace=true> + +<#-- + * message + * + * Macro to translate a message code into a message. + --> +<#macro message code>${springMacroRequestContext.getMessage(code)?no_esc} + +<#-- + * messageText + * + * Macro to translate a message code into a message, + * using the given default text if no message found. + --> +<#macro messageText code, text>${springMacroRequestContext.getMessage(code, text)?no_esc} + +<#-- + * messageArgs + * + * Macro to translate a message code with arguments into a message. + --> +<#macro messageArgs code, args>${springMacroRequestContext.getMessage(code, args)?no_esc} + +<#-- + * messageArgsText + * + * Macro to translate a message code with arguments into a message, + * using the given default text if no message found. + --> +<#macro messageArgsText code, args, text>${springMacroRequestContext.getMessage(code, args, text)?no_esc} + +<#-- + * theme + * + * Macro to translate a theme message code into a message. + --> +<#macro theme code>${springMacroRequestContext.getThemeMessage(code)?no_esc} + +<#-- + * themeText + * + * Macro to translate a theme message code into a message, + * using the given default text if no message found. + --> +<#macro themeText code, text>${springMacroRequestContext.getThemeMessage(code, text)?no_esc} + +<#-- + * themeArgs + * + * Macro to translate a theme message code with arguments into a message. + --> +<#macro themeArgs code, args>${springMacroRequestContext.getThemeMessage(code, args)?no_esc} + +<#-- + * themeArgsText + * + * Macro to translate a theme message code with arguments into a message, + * using the given default text if no message found. + --> +<#macro themeArgsText code, args, text>${springMacroRequestContext.getThemeMessage(code, args, text)?no_esc} + +<#-- + * url + * + * Takes a relative URL and makes it absolute from the server root by + * adding the context root for the web application. + --> +<#macro url relativeUrl extra...><#if extra?? && extra?size!=0>${springMacroRequestContext.getContextUrl(relativeUrl,extra)?no_esc}<#else>${springMacroRequestContext.getContextUrl(relativeUrl)?no_esc} + +<#-- + * bind + * + * Exposes a BindStatus object for the given bind path, which can be + * a bean (e.g. "person") to get global errors, or a bean property + * (e.g. "person.name") to get field errors. Can be called multiple times + * within a form to bind to multiple command objects and/or field names. + * + * This macro will participate in the default HTML escape setting for the given + * RequestContext. This can be customized by calling "setDefaultHtmlEscape" + * on the "springMacroRequestContext" context variable, or via the + * "defaultHtmlEscape" context-param in web.xml (same as for the JSP bind tag). + * Also regards a "htmlEscape" variable in the namespace of this library. + * + * Producing no output, the following context variable will be available + * each time this macro is referenced (assuming you import this library in + * your templates with the namespace 'spring'): + * + * spring.status : a BindStatus instance holding the command object name, + * expression, value, and error messages and codes for the path supplied + * + * @param path the path (string value) of the value required to bind to. + * Spring defaults to a command name of "command" but this can be + * overridden by user configuration. + --> +<#macro bind path> + <#if htmlEscape?exists> + <#assign status = springMacroRequestContext.getBindStatus(path, htmlEscape)> + <#else> + <#assign status = springMacroRequestContext.getBindStatus(path)> + + <#-- assign a temporary value, forcing a string representation for any + kind of variable. This temp value is only used in this macro lib --> + <#if status.value?exists && status.value?is_boolean> + <#assign stringStatusValue=status.value?string> + <#else> + <#assign stringStatusValue=status.value?default("")> + + + +<#-- + * bindEscaped + * + * Similar to spring:bind, but takes an explicit HTML escape flag rather + * than relying on the default HTML escape setting. + --> +<#macro bindEscaped path, htmlEscape> + <#assign status = springMacroRequestContext.getBindStatus(path, htmlEscape)> + <#-- assign a temporary value, forcing a string representation for any + kind of variable. This temp value is only used in this macro lib --> + <#if status.value?exists && status.value?is_boolean> + <#assign stringStatusValue=status.value?string> + <#else> + <#assign stringStatusValue=status.value?default("")> + + + +<#-- + * formInput + * + * Display a form input field of type 'text' and bind it to an attribute + * of a command or bean. + * + * @param path the name of the field to bind to + * @param attributes any additional attributes for the element + * (such as class or CSS styles or size) + --> +<#macro formInput path attributes="" fieldType="text"> + <@bind path/> + ${stringStatusValue}" ${attributes?no_esc}<@closeTag/> + + +<#-- + * formPasswordInput + * + * Display a form input field of type 'password' and bind it to an attribute + * of a command or bean. No value will ever be displayed. This functionality + * can also be obtained by calling the formInput macro with a 'type' parameter + * of 'password'. + * + * @param path the name of the field to bind to + * @param attributes any additional attributes for the element + * (such as class or CSS styles or size) + --> +<#macro formPasswordInput path attributes=""> + <@formInput path, attributes, "password"/> + + +<#-- + * formHiddenInput + * + * Generate a form input field of type 'hidden' and bind it to an attribute + * of a command or bean. This functionality can also be obtained by calling + * the formInput macro with a 'type' parameter of 'hidden'. + * + * @param path the name of the field to bind to + * @param attributes any additional attributes for the element + * (such as class or CSS styles or size) + --> +<#macro formHiddenInput path attributes=""> + <@formInput path, attributes, "hidden"/> + + +<#-- + * formTextarea + * + * Display a text area and bind it to an attribute of a command or bean. + * + * @param path the name of the field to bind to + * @param attributes any additional attributes for the element + * (such as class or CSS styles or size) + --> +<#macro formTextarea path attributes=""> + <@bind path/> + + + +<#-- + * formSingleSelect + * + * Show a selectbox (dropdown) input element allowing a single value to be chosen + * from a list of options. + * + * @param path the name of the field to bind to + * @param options a map (value=label) of all the available options + * @param attributes any additional attributes for the element + * (such as class or CSS styles or size) +--> +<#macro formSingleSelect path options attributes=""> + <@bind path/> + + + +<#-- + * formMultiSelect + * + * Show a listbox of options allowing the user to make 0 or more choices from + * the list of options. + * + * @param path the name of the field to bind to + * @param options a map (value=label) of all the available options + * @param attributes any additional attributes for the element + * (such as class or CSS styles or size) +--> +<#macro formMultiSelect path options attributes=""> + <@bind path/> + + + +<#-- + * formRadioButtons + * + * Show radio buttons. + * + * @param path the name of the field to bind to + * @param options a map (value=label) of all the available options + * @param separator the HTML tag or other character list that should be used to + * separate each option (typically ' ' or '
') + * @param attributes any additional attributes for the element + * (such as class or CSS styles or size) +--> +<#macro formRadioButtons path options separator attributes=""> + <@bind path/> + <#list options?keys as value> + <#assign id="${status.expression?replace('[','')?replace(']','')}${value_index}"> + checked="checked" ${attributes?no_esc}<@closeTag/> + ${separator?no_esc} + + + +<#-- + * formCheckboxes + * + * Show checkboxes. + * + * @param path the name of the field to bind to + * @param options a map (value=label) of all the available options + * @param separator the HTML tag or other character list that should be used to + * separate each option (typically ' ' or '
') + * @param attributes any additional attributes for the element + * (such as class or CSS styles or size) +--> +<#macro formCheckboxes path options separator attributes=""> + <@bind path/> + <#list options?keys as value> + <#assign id="${status.expression?replace('[','')?replace(']','')}${value_index}"> + <#assign isSelected = contains(status.actualValue?default([""]), value)> + checked="checked" ${attributes?no_esc}<@closeTag/> + ${separator?no_esc} + + + + +<#-- + * formCheckbox + * + * Show a single checkbox. + * + * @param path the name of the field to bind to + * @param attributes any additional attributes for the element + * (such as class or CSS styles or size) +--> +<#macro formCheckbox path attributes=""> + <@bind path /> + <#assign id="${status.expression?replace('[','')?replace(']','')}"> + <#assign isSelected = status.value?? && status.value?string=="true"> + + checked="checked" ${attributes?no_esc}/> + + +<#-- + * showErrors + * + * Show validation errors for the currently bound field, with + * optional style attributes. + * + * @param separator the HTML tag or other character list that should be used to + * separate each option (typically ' ' or '
') + * @param classOrStyle either the name of a CSS class element (which is defined in + * the template or an external CSS file) or an inline style. If the value passed + * in here contains a colon (:) then a 'style=' attribute will be used, + * otherwise a 'class=' attribute will be used. +--> +<#macro showErrors separator classOrStyle=""> + <#list status.errorMessages as error> + <#if classOrStyle == ""> + ${error} + <#else> + <#if classOrStyle?index_of(":") == -1><#assign attr="class"><#else><#assign attr="style"> + ${error} + + <#if error_has_next>${separator?no_esc} + + + +<#-- + * checkSelected + * + * Check a value in a list to see if it is the currently selected value. + * If so, add the 'selected="selected"' text to the output. + * Handles values of numeric and string types. + * This function is used internally but can be accessed by user code if required. + * + * @param value the current value in a list iteration +--> +<#macro checkSelected value> + <#if stringStatusValue?is_number && stringStatusValue == value?number>selected="selected" + <#if stringStatusValue?is_string && stringStatusValue == value>selected="selected" + + +<#-- + * contains + * + * Macro to return true if the list contains the scalar, false if not. + * Surprisingly not a FreeMarker builtin. + * This function is used internally but can be accessed by user code if required. + * + * @param list the list to search for the item + * @param item the item to search for in the list + * @return true if item is found in the list, false otherwise +--> +<#function contains list item> + <#list list as nextInList> + <#if nextInList == item><#return true> + + <#return false> + + +<#-- + * closeTag + * + * Simple macro to close an HTML tag that has no body with '>' or '/>', + * depending on the value of a 'xhtmlCompliant' variable in the namespace + * of this library. +--> +<#macro closeTag> + <#if xhtmlCompliant?exists && xhtmlCompliant>/><#else>> + diff --git a/Freemarker/freemarker-springmvc.java b/Freemarker/freemarker-springmvc.java new file mode 100644 index 00000000..e24aa60a --- /dev/null +++ b/Freemarker/freemarker-springmvc.java @@ -0,0 +1,39 @@ +----------------------- +整合spring | +----------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/Freemarker/freemarker-\345\205\245\351\227\250.java" "b/Freemarker/freemarker-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..254d2aa4 --- /dev/null +++ "b/Freemarker/freemarker-\345\205\245\351\227\250.java" @@ -0,0 +1,63 @@ +-------------------- +freemarker - 入门 | +-------------------- + # 文档 + http://freemarker.foofun.cn/toc.html + + # Maven + + org.freemarker + freemarker + 2.3.28 + + + + # Demo + Configuration configuration = new Configuration(Configuration.VERSION_2_3_28); + + //设置模板路径 + configuration.setServletContextForTemplateLoading(super.getServletContext() , "/WEB-INF/templates"); + + //设置读取默认的编码 + configuration.setDefaultEncoding("UTF-8"); + + //设置输出编码 + configuration.setOutputEncoding("UTF-8"); + + //设置模板异常处理器,抛出异常,由程序处理 + configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + + try { + //设置模板引擎变化检测的间隔时间 + configuration.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE, "0ms"); + //Date的datetime默认格式化 + configuration.setSetting(Configuration.DATETIME_FORMAT_KEY_SNAKE_CASE, "yyyy-MM-dd HH-mm-ss "); + //禁用本地化模板查找 + configuration.setLocalizedLookup(false); + } catch (TemplateException e) { + e.printStackTrace(); + } + + //获模板 + Template template = this.configuration.getTemplate("index/index.ftl"); + resp.setContentType("text/html"); + resp.setCharacterEncoding("utf-8"); + Map hashMap = new HashMap<>(); + hashMap.put("x", 1); + try { + //填充对象并且渲染到目标 OutputStream + template.process(hashMap, resp.getWriter()); + } catch (TemplateException e) { + e.printStackTrace(); + } + + # 保留关键字 + true:布尔值"true" + false:布尔值"false" + gt:比较运算符"大于" + gte:比较运算符"大于或等于" + lt:比较运算符"小于" + lte:比较运算符"小于或等于" + as:由少数指令使用 + in:由少数指令使用 + using:由少数指令使用 \ No newline at end of file diff --git "a/Freemarker/freemarker-\345\221\275\345\220\215\347\251\272\351\227\264.java" "b/Freemarker/freemarker-\345\221\275\345\220\215\347\251\272\351\227\264.java" new file mode 100644 index 00000000..67f8bcb6 --- /dev/null +++ "b/Freemarker/freemarker-\345\221\275\345\220\215\347\251\272\351\227\264.java" @@ -0,0 +1,40 @@ +---------------------------- +命名空间 | +---------------------------- + # 让模板引擎有点儿模块化的意思 + * 每一次 模板的执行过程, 它都有一个私有的命名空间的集合 + * 每一次模板执行工作都是一个分离且有序的过程,它们仅仅存在一段很短的时间,同时页面用以渲染内容,然后就和所有填充过的命名空间一起消失了 + * 因此,无论何时我们说第一次调用 import,一个单一模板执行工作的内容都是这样 + + # 一个模板文件中的所有内容,其实都是变量:宏,函数,变量 + # 定义一个库 :lib.ftl + <#macro foo> +

Im Foo

+ + <#assign name="KevinBlandy"> + + # 在其他模板引擎中引入,使用 + * 通过 as 关键字取别名,通过别名引用库中的变量 + + <#import "/lib/lib.ftl" as lib> + <@lib.foo/> + ${lib.name} + + + # 修改import的模板中的变量 + + <#import "/lib/my_test.ftl" as my> + ${my.mail} + + <#assign mail="jsmith@other.com" in my> + ${my.mail} + + * #assign 指令,可以通过 in 指定别名,来设置要修改的命名空间 + + # 数据模型中的变量在任何位置都是可见的 + * import 的模板中也是可以访问该变量的 ${name} + + # 命名空间的生命周期 + * 多次 import,那么只会为第一次 import 引用创建命名空间并执行模板 + * 多次 import 的都是同一个模板,任何修改,都会影响 + diff --git "a/Freemarker/freemarker-\345\234\250\346\250\241\346\235\277\344\270\255\345\256\232\344\271\211\345\217\230\351\207\217.java" "b/Freemarker/freemarker-\345\234\250\346\250\241\346\235\277\344\270\255\345\256\232\344\271\211\345\217\230\351\207\217.java" new file mode 100644 index 00000000..170c3472 --- /dev/null +++ "b/Freemarker/freemarker-\345\234\250\346\250\241\346\235\277\344\270\255\345\256\232\344\271\211\345\217\230\351\207\217.java" @@ -0,0 +1,40 @@ +---------------------------- +freemarker | +---------------------------- + # 模板本身也可以定义变量来使用。 + # 这些临时变量可以使用FTL指令来创建和替换 + # 模板级别定义的变量,在模板完成渲染后随即释放,并且存在同名属性时,渲染时优先使用模板变量 + # 如果变量名称包含特殊字符,可以用""包裹 + + # 简单的变量 + * 它能从模板中的任何位置来访问,或者从使用 include 指令引入的模板访问(include 子模板也可以访问) + * 可以使用 assign 指令来创建或覆盖这些变量, + * 宏和方法也是变量,那么 macro 指令 和 function 指令 也可以用来设置变量,就像 assign 那样 + <#assign x = 1> + <#assign x = x + 1> + + # 局部变量 + * 它们只能被设置在 宏定义体内, 而且只在宏内可见 + * 一个局部变量的生命周期只是宏的调用过程 + * 可以使用 local指令 在宏定义体内创建或替换局部变量 + <#macro foo> + <#local y = 15> + + + # 循环变量 + * 循环变量是由如 list 指令自动创建的,而且它们只在指令的开始和结束标记内有效 + * 宏的参数是局部变量而不是循环变量 + + # 全局变量 + * 这是一个高级话题了,并且这种变量最好别用 + * 即便它们属于不同的命名空间, 全局变量也被所有模板共享,因为它们是被 import进来的 + * 不同于 include 进来的,那么它们的可见度就像数据模型那样 + * 全局变量通过 global指令来定义 + + + + + # 越过局部变量,直接访问model变量,使用特殊变量 .globals + <#assign user = "Joe Hider"> + ${user} Joe Hider + ${.globals.user} KevinBlandy \ No newline at end of file diff --git "a/Freemarker/freemarker-\345\257\271\350\261\241\345\214\205\350\243\205.java" "b/Freemarker/freemarker-\345\257\271\350\261\241\345\214\205\350\243\205.java" new file mode 100644 index 00000000..e69de29b diff --git "a/Freemarker/freemarker-\346\225\260\346\215\256\346\250\241\345\236\213.java" "b/Freemarker/freemarker-\346\225\260\346\215\256\346\250\241\345\236\213.java" new file mode 100644 index 00000000..b78738ca --- /dev/null +++ "b/Freemarker/freemarker-\346\225\260\346\215\256\346\250\241\345\236\213.java" @@ -0,0 +1,10 @@ + + +TemplateHashModel + * hash + +TemplateSequenceModel + * 数组 + +TemplateCollectionModel + * 集合 \ No newline at end of file diff --git "a/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\345\205\245\351\227\250.java" "b/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..e69de29b diff --git "a/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\345\206\205\347\275\256\345\207\275\346\225\260.java" "b/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\345\206\205\347\275\256\345\207\275\346\225\260.java" new file mode 100644 index 00000000..23e9aca3 --- /dev/null +++ "b/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\345\206\205\347\275\256\345\207\275\346\225\260.java" @@ -0,0 +1,348 @@ +--------------------- +内置函数 | +--------------------- + # 内置函数的调用都是通过 ?func 来调用的 + + +--------------------- +字符串相关 | +--------------------- + boolean + * 转换字符串为 boolean 值 + * 字符串必须是 true 或 false (大小写敏感) + * 也可以通过 boolean_format 设置的特定格式 + + html + * 把html字符转义输出 + + cap_first + * 首字母设置大写 + + uncap_first + * 设置字符串中所有单词的首字母小写 + + capitalize + * 字符串中所有单词的首字母都大写 + + chop_linebreak + * 如果字符串末尾没换行,则添加换行符 + + contains + * 判读字符串中是否包含指定的子串,返回 boolean + + trim + * 去掉首尾空格 + + date, time, datetime + * 对Date类型数据的格式化 + * 参数可以是格式化的格式 + * 也可以设置默认的格式化方法 + + ends_with + * 判断字符串是否以指定的子串儿结尾 + + ensure_ends_with + * 如果字符串没有以指定的字符串结尾 + * 则会把指定的字符串添加到结尾 + + ensure_starts_with + * 同上,字符串是否以指定的字符串开头 + + * 如果它指定了两个参数,那么第一个就会被解析为'正则表达式' + * 如果它不匹配字符串的开头,那么第二个参数指定的字符串就会添加到字符串开头 + * 该方法也接受第三个标志位参数,(也就是正则表达式模式) + + length + * 字符串的数量 + + index_of + * 返回字字符串第一次出现的位置,没找到返回-1 + + left_pad + * 接收一个参数n,如果字符串不足n长度的话,会自动在前面添加空格 + ${" a"?left_pad(5)} //" a" + + * 如果存在第二参数,也是一个字符, 那么这个字符串可以替换默认的空格填充策略 + ${" a"?left_pad(5,"-")} //"--- a" + + * 第二个参数也可以是个长度比1大的字符串,那么这个字符串会周期性的插入 + ${" a"?left_pad(5,"abcd")} //"abc a" + + right_pad + * 同上,不过是在后面填充 + + lower_case + * 转换为小写模式 + + upper_case + * 转换为大写模式 + + url + * 对数据进行url编码 + * 它会转义所有保留的URL字符 (/,=, &,等...) + Click here... + + * 设置URI编码的字符集 + <#setting url_escaping_charset="UTF-8"> + + * 也可以在参数中指定 + ${x?url('UTF-8')} + + url_path + * 同上,但是它不转义斜杠(/)字符 + * 也可以指定字符集 + url_path('utf-8') + + number + * 转换为数字 + + replace + * 使用参数2,替换参数1的子串儿 + + remove_beginning + * 移除指定开头的字符串,如果开头的字符串不是指定的,则返回原串儿 + + remove_ending + * 同上,移除尾部分 + + split + * 把字符串按照指定的子串儿分割为序列 + + starts_with + * 如果以指定的字符串开始,返回 true + + +--------------------- +数值相关 | +--------------------- + abs + * 绝对值 + c + * 返回计算机能看懂的数据 + + int + * 转换为int数据 + + long + * 转换为long属性 + + string + * 转换为字符串 + * 可以通过 number_format 和 locale 设置的默认格式 + * 四种预定义的数字格式 + computer + currency + number + percent + + <#assign x=42> + ${x} + ${x?string} <#-- 无任格式化策略,输出和 ${x} 一样 --> + ${x?string.number} + ${x?string.currency} + ${x?string.percent} + ${x?string.computer} + + * 还可以使用 Java 数字格式语法(使用[]设置表达式) 中的任意数字格式(DecimalFormat) + <#assign x = 1.234> + ${x?string["0"]} + ${x?string["0.#"]} + ${x?string["0.##"]} + ${x?string["0.###"]} + ${x?string["0.####"]} + + ${1?string["000.00"]} + ${12.1?string["000.00"]} + ${123.456?string["000.00"]} + + ${1.2?string["0"]} + ${1.8?string["0"]} + ${1.5?string["0"]} <-- 1.5, rounded towards even neighbor --> + ${2.5?string["0"]} <-- 2.5, rounded towards even neighbor --> + + ${12345?string["0.##E0"]} + +--------------------- +日期相关 | +--------------------- + date + * 仅日期部分,没有一天当中的时间部分。 + time + * 仅一天当中的时间部分,没有日期部分。 + datetime + * 日期和时间都在 + + # 以上三个函数都支持通过参数来设置日志的格式化 + # 也可以设置全局的格式化方式 + date_format,time_format,datetime_format + + string + * 也可以用于格式化日期,可以用参数指定格式化 + * 尽量少用这种格式化方法,标识过期 + ${time?string('yyyy-MM-dd HH:mm:ss')} + + number_to_date + number_to_time + number_to_datetime + * 以上函数都适用于把数值(Timestamp)转换为日期 + +--------------------- +boolean | +--------------------- + c + * 输出计算机能看懂的"true"/"false" + * 如果要渲染为javascript的代码,可以用它 + + then + * 格式化bool的输出 + ${true?then('true','false')} + +--------------------- +序列 | +--------------------- + chunk + * 将序列分隔为多个子序列,每个子序列的长度为第一个参数给定的值 (arr?chunk(3)) + <#assign arr = [0,1,2,3,4,5,6,7,8,9]> + <#list arr?chunk(3) as subArr> + <#list subArr as item> + ${item} +
+ + 0 1 2 + 3 4 5 + 6 7 8 + 9 + * 也可以指定第二个参数,来设置,当序列长度不足时以什么补充? + <#assign arr = [0,1,2,3,4,5,6,7,8,9]> + <#list arr?chunk(3,'-') as subArr> + <#list subArr as item> + ${item} +
+ + 0 1 2 + 3 4 5 + 6 7 8 + 9 - - + + first + * 返回第一个元素 + last + * 返回最后一个元素 + + join + * 使用指定的字符串链接序列里面的所有元素,形成一个新的字符串 + ${[0,1,2,3,4,5,6,7,8,9]?join('-')} + 0-1-2-3-4-5-6-7-8-9 + + * 它可以有三个参数 + 1. 分隔符,是必须的:插入到每一项中间的字符串 + 2. 空值,默认是 "" (空字符串),如果序列为空([]),使用该值 + 3. 列表结尾,默认是 "" (空字符串): 如果列表序列不为空,该值在最后一个值后面输出 + + reverse + * 反转 + + seq_contains + * 判断序列中是否存在指定的值,返回 boolean + * 使用 == 操作符 + ${[1,2,3,4,6]?seq_contains(5)?then('包含','不包含')} + + seq_index_of + * 返回指定元素第一次出现的角标,如果未找到,返回 -1 + * 搜索开始的索引值可以由第二个可选参数来确定 + + seq_last_index_of + * 返回指定元素最后一次出现的角标,如果未找到返回 -1 + * 搜索开始的索引值可以由第二个可选参数来确定 + + size + * 返回序列的长度 + + sort + * 以升序排列序列,仅仅在元素是:数值,字符串,boolean,Date 时有效 + * 要使用降序排列时,使用它之后使用 reverse 内建函数 + <#assign ls = ["whale", "Barbara", "zeppelin", "aardvark", "beetroot"]?sort> + <#list ls as i>${i} + + sort_by + * 以升序排列序列,仅仅元素是对象 + * 通过参数来指定用于排序的字段 + <#assign ls = [ + {"name":"whale", "weight":2000}, + {"name":"Barbara", "weight":53}, + {"name":"zeppelin", "weight":-200}, + {"name":"aardvark", "weight":30}, + {"name":"beetroot", "weight":0.3} + ]> + + <#list ls?sort_by("name") as i> + + * 用来排序的子变量的层次很深 (也就是说,它是子变量的子变量的子变量,以此类推), 那么可以使用序列来作为参数,它指定了子变量的名字 + <#assign members = [ + {"name": {"first": "Joe", "last": "Smith"}, "age": 40}, + {"name": {"first": "Fred", "last": "Crooger"}, "age": 35}, + {"name": {"first": "Amanda", "last": "Fox"}, "age": 25}]> + <#list members?sort_by(['name', 'last']) as m> + +--------------------- +对象/Hash | +--------------------- + keys + * 返回所有的key,是一个序列 + + values + * 返回所有的value + +--------------------- + 其他 | +--------------------- + switch + * 基本内联:matchedValue?switch(case1, result1, case2, result2, ... caseN, resultN, defaultResult) + <#list ['r', 'w', 'x', 's'] as flag> + ${flag?switch('r', 'readable', 'w' 'writable', 'x', 'executable', 'unknown flag: ' + flag)} + + readable + writable + executable + unknown flag: s + + * 最后一个参数是没找到的默认值 + * result 不一定是常量,也可以是变量或者其他复杂的表达式 + + has_content + * 如果变量(不是Java的 null) 存在而且不是"空"就返回 true,否则返回 false + * 空值 + 长度为0的字符串 + 没有子变量的序列或哈希表 + 一个已经超过最后一项元素的集合 + + ${""?has_content?c} //false + ${[]?has_content?c} //false + + +--------------------- +数据类型判断 | +--------------------- + + is_string 字符串 + is_sequence 序列 + is_enumerable 序列或集合 + is_collection 集合 (包含扩展的集合) + is_collection_ex 扩展的集合 (支持 ?size) + is_number 数字 + is_boolean 布尔值 + is_hash 哈希表 (包含扩展的哈希表) + is_hash_ex 扩展的哈希表 (支持 ?keys 和 ?values) + is_indexable 序列 + + is_date 不要使用它!使用 is_date_like 来代替, 它们是相同的。往后也许会修改它的含义为 date_only。 + is_date_like 日期,也就似乎日期,时间或者日期-时间, 亦或者是未知精确类型的日期(从 FreeMarker 2.3.21 版本开始) + is_date_only 日期 (没有时间部分) (从 FreeMarker 2.3.21 版本开始) + is_time 时间 (没有年-月-日部分) (从 FreeMarker 2.3.21 版本开始) + is_datetime 日期-时间 (包含年-月-日和时间两者) + is_unknown_date_like 不清楚是日期或时间或日期-时间的日期 + is_method 方法 + is_transform 变换 + is_macro 宏或函数(当然,由于历史问题,也对函数有效) + is_directive 指令类型 (例如宏 或 TemplateDirectiveModel, TemplateTransformModel, 等...), 或者函数 (由于历史问题) + is_node 节点 \ No newline at end of file diff --git "a/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\346\214\207\344\273\244.java" "b/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\346\214\207\344\273\244.java" new file mode 100644 index 00000000..6181167f --- /dev/null +++ "b/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\346\214\207\344\273\244.java" @@ -0,0 +1,263 @@ +----------- +指令 | +----------- + # 使用 FTL标签来调用 指令 + # 如果标签没有嵌套内容(在开始标签和结束标签之间的内容),那么可以忽略关闭标签 + # 存在两种指令 + * 自定义指令,使用@ + <@my parameters > + * 系统指令,使用# + <#if parameters > + + # FreeMarker会忽略FTL标签中多余的空白标记 + + + + +------------ +assign | +------------ + # 创建一个新的变量, 或者替换一个已经存在的变量 + # 注意仅仅顶级变量可以被创建/替换 (也就是说你不能创建/替换 some_hash.subvar, 除了 some_hash) + <#assign seq = ["foo", "bar", "baz"]> + <#assign x++> + <#assign + seq = ["foo", "bar", "baz"] + x++ + > + # 名称可以有双引号 + # 保存输出 + +------------ +compress | +------------ + # 移除多余的空白是很有用的,它捕捉在指令体中生成的内容,然后缩小所有不间断的空白序列到一个单独的空白字符 + # 如果被替代的序列包含换行符或是一段空间,那么被插入的字符也会是一个 换行符, 开头和结尾的空白符会被移除 + # 有点儿压缩内容的意思 + # 属性 + single_line + * bool值,如果该值为 true,则会连换行都一起移除 + +------------ +escape | +------------ + # 会对该指令标签下${}输出的内容进行指定的编码 + # 这是一个避免编写相似表达式的很方便的方法 + * 它不会影响在字符串形式的插值(比如在 <#assign x = "Hello ${user}!">) + * 而且,它也不会影响数值插值 (#{...}) + + # Demo + <#escape x as x?html> + First name: ${firstName} + Last name: ${lastName} + Maiden name: ${maidenName} + + + * x 表示${}输出的内容 + * x?html 对输出内容的处理,表示用html编码去处理,也可以换成自己定义的函数啥的 + * 甚至可以让x作为map的key + <#escape x as skills[x]>.... + + * 其实等于 + First name: ${firstName?html} + Last name: ${lastName?html} + Maiden name: ${maidenName?html} + + + # 关闭转义,可以在escape中嵌套#noescape指令 + <#escape x as x?html> + First name: ${firstName} + <#noescape>Message: ${message} + + + * message 不会被转义 + + # 转义可以嵌套多个 + <#escape x as x?html> + Customer Name: ${customerName} + Items to ship: + <#escape x as itemCodeToNameMap[x]> + ${itemCode1} + ${itemCode2} + ${itemCode3} + ${itemCode4} + + + + * 上代码等于 + Customer Name: ${customerName?html} + Items to ship: + ${itemCodeToNameMap[itemCode1]?html} + ${itemCodeToNameMap[itemCode2]?html} + ${itemCodeToNameMap[itemCode3]?html} + ${itemCodeToNameMap[itemCode4]?html} + + * 嵌入的转义区块内使用非转义指令时,它仅仅不处理一个单独层级的转义 + * 因此,为了在两级深的转义区块内完全关闭转义,你需要使用两个嵌套的非转义指令 + + +------------ +ftl | +------------ + # 对于模板引擎的一些设置 + <#ftl param1=value1 param2=value2 ... paramN=valueN> + + * 参数名称固定 + * 参数值是常量,不能使用变量 + + # 属性 + encoding + * 编码 + + strip_whitespace + * 这将开启/关闭 空白剥离 + * 合法的值是布尔值常量 true 和 false (为了向下兼容,字符串 "yes","no", "true","false" 也是可以的) + * 默认值是 true。 + + strip_text + * 当开启它时,当模板被解析时模板中所有顶级文本被移除 + * 这个不会影响宏,指令,或插值中的文本 + * 合法值是布尔值常量 true 和 false (为了向下兼容,字符串 "yes","no", "true","false" 也是可以的) + * 默认值(也就是当你不使用这个参数时)是 false + + + strict_syntax + * 这会开启/关闭"严格的语法" + * 合法值是布尔值常量 true 和 false (为了向下兼容,字符串 "yes","no", "true","false" 也是可以的) + * 默认值(也就是当你不使用这个参数时)是依赖于程序员对 FreeMarker 的配置, 但是对新的项目还应该是 true。 + + + attributes + * 这是关联模板任意属性(名-值对)的哈希表, 属性的值可以是任意类型(字符串,数字,序列等)。 + * reeMarker 不会尝试去理解属性的含义,它是由封装 FreeMarker(比如Web应用框架)的应用程序决定的 + * 因此,允许的属性的设置是它们依赖的应用(Web应用框架)的语义 + * 可以通过关联 Template 对象的 getCustomAttributeNames 和 getCustomAttribute 方法 (从 freemarker.core.Configurable 继承而来)获得属性 + * 如当模板被解析时,关联 Template 对象的模板属性, 属性可以在任意时间被读取,而模板不需要被执行。 上面提到的方法返回未包装的属性值,也就是说, 使用 FreeMarker 独立的类型,如 java.util.List。 + +------------ +global | +------------ + # 设置全局变量,在所有命名空间中都可见 + # 语法 + <#global name=value> + <#global name1=value1 name2=value2 ... nameN=valueN> + # 如果变量名称包含特殊字符,可以用""包裹 + +-------------------- +if, else, elseif | +-------------------- + # 太简单了,不说 + # 判断尽量使用括号 + <#if (x > 0)> + + + * 不然的话 ><会被解析为指令的结束符 + + # 最终的结果必须是一个bool类型,不然异常 + +-------------------- +import | +-------------------- + # 用于导入一个库 + <#import "/libs/mylib.ftl" as my> + <@my.foo/> + ${my.func("123")} + + +-------------------- +include | +-------------------- + # 包含 + # 语法 + <#include path> + <#include path options> + + * path表示路径 + * ptions 一个或多个这样的选项 + encoding 编码 + parse 是否当作ftl解析,如果为false则把内容当作纯字符串 + ignore_missing 是否忽略异常,如果目标目标异常,则不会有任何输出 + + # 本地化模板查找 + + * 假设模板使用本地化 en_US 来加载, 就是美国英语,当包含其它模板时 + + <#include "footer.ftl"> + + //引擎实际上就会按照这个顺序寻找模板 + + footer_en_US.ftl, + footer_en.ftl + footer.ftl + + * 通过程序关闭本地检索机制 + //禁用本地化模板查找 + configuration.setLocalizedLookup(false); + + * 也可以控制这个查找的过程 + configuration.setTemplateLookupStrategy(TemplateLookupStrategy); + +-------------------- +noparse | +-------------------- + # 会忽略该指令中间的所有ftl表达式,会把它们直接当作字符串原样输出 + # 模板 + <#noparse> + <#list animals as animal> + ${animal.name}${animal.price} Euros + + + # 输出 + Example: + -------- + + <#list animals as animal> + ${animal.name}${animal.price} Euros + + +-------------------- +setting | +-------------------- + # 设置是影响 FreeMarker 行为的值 + <#setting name=value> + + # 可以设置的属性 + locale + number_format + boolean_format + date_format + time_format + datetime_format + time_zone + sql_date_and_time_time_zone + url_escaping_charset + output_encoding + classic_compatible + +-------------------- +#switch | +-------------------- + # 官方说不建议用,建议用if elseif + <#switch value> + <#case refValue1> + ... + <#break> + <#case refValue2> + ... + <#break> + <#case refValueN> + ... + <#break> + <#default> + ... + + +------------------------ +t, lt, rt | +------------------------ + t + * 忽略本行中首和尾的所有空白。 + lt + * 忽略本行中首部所有的空白。 + rt + * 忽略本行中尾部所有的空白 \ No newline at end of file diff --git "a/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\347\211\271\346\256\212\345\217\230\351\207\217.java" "b/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\347\211\271\346\256\212\345\217\230\351\207\217.java" new file mode 100644 index 00000000..3b716b7f --- /dev/null +++ "b/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\347\211\271\346\256\212\345\217\230\351\207\217.java" @@ -0,0 +1,25 @@ +------------------------ +特殊变量 | +------------------------ + # 特殊变量的调用是通过 . 来完成的 + + current_template_name + * 返回当前模板的名称(相对路径) + + data_model + * 可以用来直接访问数据模型的哈希表 + + error + * 存储的错误信息 + + globals + * 访问全局变量 + + locale + * 返回本地化设置 + + now + * 返回当前时间 + * ${.now.datetime} + + \ No newline at end of file diff --git "a/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\350\241\250\350\276\276\345\274\217.java" "b/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\350\241\250\350\276\276\345\274\217.java" new file mode 100644 index 00000000..e8194e5d --- /dev/null +++ "b/Freemarker/freemarker-\346\250\241\346\235\277\350\257\255\350\250\200-\350\241\250\350\276\276\345\274\217.java" @@ -0,0 +1,301 @@ +-------------------- +取值表达式 | +-------------------- + # 取值表达式的最终结果是"字符串" + # 直接取值 + * model属性 + ${name} + ${user.name} ${user["mame"]} + ${user.author.name} ${user["author"].name} ${user["author"]["name"]} + + * 如果属性名称里面有特殊的属性,需要使用转义字符 + 表达式为 data\-id,因为 data-id 将被解释成 "data 减去 id" + 这些转义仅在标识符中起作用,而不是字符串中。 + + * 常量 + ${"这是一个字符串"} + + * 字符串支持转义 + \" 引号 (u0022) + \' 单引号(又称为撇号) (u0027) + \{ 起始花括号:{ + \\ 反斜杠 (u005C) + \n 换行符 (u000A) + \r 回车 (u000D) + \t 水平制表符(又称为tab) (u0009) + \b 退格 (u0008) + \f 换页 (u000C) + \l 小于号:< + \g 大于号:> + \a &符:& + \xCode 字符的16进制 Unicode 码 (UCS 码) + + + # 可以在文本表达式中使用 + <#include "/footer/${company}.html"> + + * <#if ${big}>...,语法上的错误 + + # 日期 + * 渲染Date必须要使用到内置的日期函数 + * '必须要'通过这些函数来告诉模板引擎,要渲染Date里面的哪部分数据 + date + time + datetime + + ${now?datetime} + + * 设置全局的格式化 + configuration.setSetting(Configuration.DATETIME_FORMAT_KEY_SNAKE_CASE, "yyyy-MM-dd HH-mm-ss "); //仅仅设置了 datetime + ${now?datetime} + + * 可以使用 ?string 临时把date转换为指定的日期类型 + ${now?string("yyyy年MM月dd日 HH时mm分ss秒")} + + + # boolean 值 + * 直接输出boolean值会异常,可以使用内建函数 ?then 来将布尔值转换为字符串形 + ${married?then("yes", "no")} + + * 也可以通过 ?c 直接输出'计算机'可以看懂的 true/false + ${someBoolean?c} + + * 可以使用设置参数 boolean_format 来为 FreeMarker 配置默认的布尔值格式,这样就可以直接输出 boolean 类型的变量:${married} + + * string("true","false") 也可以用于格式化bool变量,但是已经废弃了。 + + + +-------------------- +值域 | +-------------------- + # 用于表示出一个区间的循环 + start..end + * 指定开始和结尾角标 + * 包含结尾的值域,比如 1..4 就是 [1, 2, 3, 4], 而 4..1 就是 [4, 3, 2, 1] + * 注意, 包含结尾的值域不能是一个空序列,如果为空的话 0..length-1 就是错误的,因为当长度(length)为 0 了,序列就成了 [0, -1] + + start.. //x = Kevin + ${s} //Hello Kevin + + <#assign s = "Hello " + user + "!"> + ${s}//Hello Kevin + + + # 根据索引获取字符 + ${user[0]} //获取字符串的第一个字符 + ${user[4]} //获取字符串的第三个字符 + + # 字符串切割 + <#assign s = "ABCDEF"> + ${s[2..3]} CD //从指定的角标到指定的角标 + ${s[2..<4]} CD //从指定角标到指定角标一下 + ${s[2..*3]} CDE + ${s[2..*100]} CDEF + ${s[2..]} CDEF //从指定角标以后 + + + * 操作字符串的内置函数 + remove_beginning + remove_ending + keep_before + keep_after + keep_before_last + keep_after_last + + +-------------------- +序列的操作 | +-------------------- + # 序列取值 + ${user.skills[0].id} + + # 序列的连接 + <#list ["Joe", "Fred"] + ["Julia", "Kate"] as user> + - ${user} + + + # 序列切分 + <#assert seq = ["A", "B", "C", "D", "E"]> + <#list seq[1..3] as i> + ${i} //输出BCD + + + * seq[1..3] 表示仅仅变量序列1-3角标的元素 + * 切分后序列中的项会和值域的顺序相同,那么上面的示例中,如果值域是 3..1 将会输出 'DCB' + * 值域中的数字必须是序列可使用的合法索引 + - seq[-1..0] + - seq[-1] + - seq[1..5] 异常,因为5超出了最大角标4 + - seq[100..<100] 合法 + - seq[100..*0] 合法 + +-------------------- +Hash | +-------------------- + # 连接 + <#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}> + - Joe is ${ages.Joe} + - Fred is ${ages.Fred} + - Julia is ${ages.Julia} + + * 右侧的会覆盖前面的同名属性 + +-------------------- +运算 | +-------------------- + # 支持 +-*/%等运算 + # 字符串与数值相加,会自动转换数值为字符串 + # 字符串与数值执行除了相加以外的所有操作,都会异常 + # 比较运算 + <#if user == "Big Joe"> + It is Big Joe + + <#if user != "Big Joe"> + It is not Big Joe + + + * = 或 != 两边的表达式的结果都必须是标量,而且两个标量都必须是相同类型 (也就是说字符串只能和字符串来比较,数字只能和数字来比较等) + + * 对数字和日期类型的比较,也可以使用 <, <=,>= 和 > + + # 逻辑操作 || ,&&,! + + <#if x < 12 && color == "green"> + We have less than 12 things, and they are green. + + <#if !hot> <#-- here hot must be a boolean --> + It's not hot. + + +-------------------- +内建函数 | +-------------------- + # 内置函数的调用 + ${变量?执行方法} + ${变量?执行方法(方法参数)} + ${变量?执行方法1(方法参数)?执行方法2} + + ${testString?upper_case} + ${testString?html} + ${testString?upper_case?html} + + ${testSequence?size} + ${testSequence?join(", ")} + + # 内建函数的左侧可以是任意的表达式,而不仅仅是变量名 + ${testSeqence[1]?cap_first} + ${"horse"?cap_first} + ${(testString + " & Duck")?html} + +-------------------- +方法调用 | +-------------------- + # 自定义的方法调用 + ${repeat("Foo", 3)} + + # 方法调用也是普通表达式,和其它都是一样的 + ${repeat(repeat("x", 2), 3) + repeat("Foo", 4)?upper_case} + +-------------------- +不存在的值处理 | +-------------------- + + # 默认值操作符 + unsafe_expr!default_expr ${name!"默认值"} + unsafe_expr! or (unsafe_expr)!default_expr ${name!} ${(name)!"默认值"} + (unsafe_expr)! ${(name)!} + + # 默认值可以是任何类型的表达式,也可以不必是字符串 + hits!0 + colors!["red", "green", "blue"] + cargo.weight!(item.weight * itemCount + 10) + + + # 空序列或空哈希表,如果想让默认值为 0 或 false,则不能省略默认值 + + # 于非顶层变量时,默认值操作符可以有两种使用方式 + ${product.color!"red"} + * 如果 color 属性不存在,默认返回 red + * 如果 product 属性不存在,模板异常 + + ${(product.color)!"red"} + * 如果 product 属性不存在或者 color 属性不存在,都会返回 red + + + * 如果没有括号,仅仅允许表达式最后的一个属性可以未被定义 + + + # 不存在值检测操作符:unsafe_expr?? 或 (unsafe_expr)?? + <#if mouse??> + Mouse found + <#else> + No mouse found + + Creating mouse... + <#assign mouse = "Jerry"> + <#if mouse??> + Mouse found + <#else> + No mouse found + + + # 默认值操作也可以作用于序列子变量 + <#assign seq = ['a', 'b']> + ${seq[0]!'-'} + ${seq[1]!'-'} + ${seq[2]!'-'} + ${seq[3]!'-'} + + a + b + - //角标不存在,输出-- + - + + * 如果序列索引是负数(比如 seq[-1]!'-') 也会发生错误,不能使用该运算符或者其它运算符去压制它 + + +-------------------- +赋值操作符 | +-------------------- + # 并不是表达式,只是复制指令语法的一部分,比如 assign, local 和 global + <#assign x += y> 是 <#assign x = x + y> 的简写 + <#assign x *= y> 是 <#assign x = x * y> 的简写 + <#assign x--> 是 <#assign x -= 1> 的简写 + + <#assign x++> 和 <#assign x += 1>不同,它只做算术加法运算 (如果变量不是数字的话就会失败) + +-------------------- +括号 | +-------------------- + //TODO + +-------------------- +表达式中的空格 | +-------------------- + # FTL 忽略表达式中的多余的 空格。下面的表示是相同的: + +-------------------- +特殊变量 | +-------------------- \ No newline at end of file diff --git "a/Freemarker/freemarker-\346\270\262\346\237\223-foreach.java" "b/Freemarker/freemarker-\346\270\262\346\237\223-foreach.java" new file mode 100644 index 00000000..86635e7d --- /dev/null +++ "b/Freemarker/freemarker-\346\270\262\346\237\223-foreach.java" @@ -0,0 +1,78 @@ +--------------------------- +处理渲染中的循环 | +--------------------------- + # 简单的渲染 + <#list arr as item> + ... + <#else> + ... arr 为[] + + + # 在循环开始和结尾处添加输出 + <#list sequence> + //在循环之前执行一次 + <#items as item> + // 开始循环每一个元素 + + //在循环之后执行一次 + <#else> + //空集合 + + + # 在第一项之厚,最后一项之前添加东西 + <#list users as user> + ${user}<#sep>, + + + * 使用<#sep>指令,该指令的字符会被添加到每个遍历项后面 + * 但是会忽略最后一个遍历项的后面 + * 非常方便 + + # 中断循环 + <#list 1..10 as x> + ${x} + <#if x == 3> + <#break> + + + + # 访问迭代状态 + * 通过内置函数操作迭代项 + <#list ['a','b'] as item> + ${item}-${item?index}-${item?has_next?c}
+ + + * 可以使用的函数 + counter + * 返会从1开始的索引 + has_next + * 是否还有下一个 + index + * 返回从0开始的索引 + is_even_item + * 是否是奇数个 + is_first + * 是否是第一个 + is_last + * 是否是最后一个 + is_odd_item + * 是否为偶数个 + item_cycle + * 以指定何值来代替 "odd" 和 "even", 它也允许多余两个值来循环 + * 取模算法 + <#list ['a', 'b', 'c', 'd', 'e', 'f', 'g'] as i> + ${i}-${i?item_cycle('row1', 'row2', 'row3')}
+ + * 第一个值为:row1 + * 第四个值为:row1 + + + item_parity + * 返回当前是奇数还是偶数 + * 返回字符串值 "odd" 或 "even" + + item_parity_cap + * 同上,返回的是大写开头 + * 返回字符串值 "Odd" 或 "Even" + + diff --git "a/Freemarker/freemarker-\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244.java" "b/Freemarker/freemarker-\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244.java" new file mode 100644 index 00000000..fd3a0405 --- /dev/null +++ "b/Freemarker/freemarker-\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244.java" @@ -0,0 +1,239 @@ +------------------------ +以使用 macro 指令来定义 | +------------------------ + # 基本定义 + <#macro greet> + Hello Joe! + + + * 指令名称 greet + * 值定义指令是用:@ + * 使用:<@greet> 或者 <@greet/> + + # 变量会在模板开始时被创建 + * 也就是说,可以在调用指令后创建指令(其实指令在调用之前会先初始化) + + # 指令之间的东西是模板片段,可以使用任意的ftl语法 + * 可以嵌套其他的自定义指令 + + # 参数的定义与使用 + <#macro foo var1 var2> + ${var1} - ${var2} + + + <@foo users[0].name "KevinBlandy"/> + * 如果不指定名称,则参数按照顺序传递 + + <@foo var1=users[0].name var2="KevinBlandy"/> + * 通过指定标签属性来设置指定的参数 + + * 如果定义了参数,则必须都要传递,少传递参数会发生异常 + * 也不能多传递参数,或者传递指令未定义的参数,不然也会发生异常 + + * 指令可以带默认值,这样的话,使用的时候,可以省略这个参数 + * 没有默认值的参数必须在有默认值参数 (paramName=defaultValue) 之前 + <#macro foo var1 var2="default"> + ${var1} - ${var2} + + @foo var1=users[0].name/> + + * 自定义指令里面使用的参数,都是局部变量 + + # 可变参数 + * 最后一个参数,可能会有三个点(...), 这就意味着宏接受可变数量的参数 + * 不匹配其它参数的参数可以作为最后一个参数 (也被称作笼统参数) + * 当宏被命名参数调用,paramN 将会是包含宏的所有未声明的键/值对的哈希表 + * 当宏被位置参数调用,paramN 将是额外参数的序列 (在宏内部,要查找参数,可以使用 myCatchAllParam?is_sequence) + + * 可变参数,可以一个map,也可以是一个集合 + <#macro foo v1 v2 v3...> + <#if v3?is_sequence> + //是集合 + <#list v3 as i> + ${i}
+ + + //是map + <#else> + + <#list v3?keys as key> + ${v3[key]}
+ + + + + + <@foo v1="1" v2="2" aa="3" bb="4"> + * 这种调用方式可变参数是map + + <@foo "1" "2" "3" "4"> + * 这种调用方式可变参数是集合 + + + # 指令体的插入 + <#macro foo> + Hello<#nested> + + + <@foo> + Java + + + HelloJava + + * <#nested> 替换为使用指令时的标签体 + + * <#nested> 指令也可以多次被调用 + <#macro foo> + Hello<#nested> + <#nested> + <#nested> + + + # 在嵌套的内容中,宏的局部变量是不可见的 + <#macro repeat count> + <#local y = "test"> //局部变量 + <#list 1..count as x> + ${y} ${count}/${x}: <#nested> + + + + <@repeat count=3> + //不能访问到指令内部定义的变量 + ${y!"?"} ${x!"?"} ${count!"?"}
+ + + test 3/1: ? ? ? + test 3/2: ? ? ? + test 3/3: ? ? ? + + # 循环变量 + * <#nested />可以设置一个循环变量 + * 使用标签的时候,可以渲染该变量 + <#macro foo> + <#nested 1/> + <#nested 2/> + <#nested 3/> + + <@foo ;x> + name ${x}
+ + name 1 + name 2 + name 3 + + * 环变量的名称是在自定义指令的开始标记(<@...>) 的参数后面通过分号确定的 + * 一个宏可以使用多个循环变量(变量的顺序是很重要的) + <#macro foo count=10> + <#list 1..count as item> + <#nested item,item==1,item==count/> + + + + <@foo ;index,first, last,a> + ${index} ${first?string("第一个","不是第一个")} ${last?string("最后一个","不是最后一个")}
+ + + # 中止指令 #return + <#macro test> + Test text + <#return> + Will not be printed. //会被忽略 + +------------------------ +通过编码来完成自定义 | +------------------------ + # 实现接口:TemplateDirectiveModel + import java.io.IOException; + import java.util.Map; + + import freemarker.core.Environment; + import freemarker.template.TemplateDirectiveBody; + import freemarker.template.TemplateDirectiveModel; + import freemarker.template.TemplateException; + import freemarker.template.TemplateModel; + + public class FooDirective implements TemplateDirectiveModel{ + + /** + * @param env 运行环境 + * @param params 参数 + * @param loopVars 循环参数 + * @param body 指令体 + */ + @SuppressWarnings("rawtypes") + @Override + public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)throws TemplateException, IOException { + //渲染 + body.render(env.getOut()); + } + } + + # 添加指令到模板变量 + * configuration.put("foo", new FooDirective()); + * 可以设置为共享变量,以单例形式存在 + + # 在模板引擎中使用 + <@foo> + + + +------------------------ +demo | +------------------------ + # 一个 upper 指令,让该指令之间的输出都变成大写 + import java.io.IOException; + import java.io.Writer; + import java.util.Map; + + import freemarker.core.Environment; + import freemarker.template.TemplateDirectiveBody; + import freemarker.template.TemplateDirectiveModel; + import freemarker.template.TemplateException; + import freemarker.template.TemplateModel; + import freemarker.template.TemplateModelException; + + public class UpperDirective implements TemplateDirectiveModel { + + @SuppressWarnings("rawtypes") + public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) + throws TemplateException, IOException { + if (!params.isEmpty()) { + throw new TemplateModelException("该指令不允许有标签"); + } + if (loopVars.length != 0) { + throw new TemplateModelException("该指令不允许有循环变量"); + } + + if (body != null) { + body.render(new UpperCaseFilterWriter(env.getOut())); + } else { + throw new RuntimeException("未定义标签体"); + } + } + + private static class UpperCaseFilterWriter extends Writer { + + private final Writer out; + + UpperCaseFilterWriter(Writer out) { + this.out = out; + } + + public void write(char[] cbuf, int off, int len) throws IOException { + char[] transformedCbuf = new char[len]; + for (int i = 0; i < len; i++) { + transformedCbuf[i] = Character.toUpperCase(cbuf[i + off]); + } + out.write(transformedCbuf); + } + + public void flush() throws IOException { + out.flush(); + } + + public void close() throws IOException { + out.close(); + } + } + } \ No newline at end of file diff --git "a/Freemarker/freemarker-\350\207\252\345\256\232\344\271\211\346\226\271\346\263\225.java" "b/Freemarker/freemarker-\350\207\252\345\256\232\344\271\211\346\226\271\346\263\225.java" new file mode 100644 index 00000000..11709934 --- /dev/null +++ "b/Freemarker/freemarker-\350\207\252\345\256\232\344\271\211\346\226\271\346\263\225.java" @@ -0,0 +1,148 @@ +---------------------------- +标签实现 | +---------------------------- + # 语法 + <#function name param1 param2 ... paramN> + ... + <#return returnValue> + ... + + + * name 方法名称 + * 可以定义多个参数 + * paramN,最后一个参数, 可以可选的包含一个尾部省略(...)这就意味着宏接受可变的参数数量,局部变量 paramN 将是额外参数的序列(变成参数) + * 参数可以有默认值,但是必须排在普通参数的后面 + * return 指令是必须的 + * 方法内的所有输出都会被忽略 + + # Demo + <#function foo v1 v2 v3...> + <#local x = v1 + v2> + <#list v3 as i> + <#local x = i + x> + + <#return x> + + + ${foo(1,2,3,4,5)} //15 + + # 参数类型的转换 + * 参数类型都是:TemplateModel 子类 + + * 字符串参数 func('123'); + SimpleScalar simpleScalar = ((SimpleScalar)arguments.get(0)); + simpleScalar.getAsString() //simpleScalar.toString(); + + * 数值参数 func(123); + SimpleNumber simpleNumber = ((SimpleNumber)arguments.get(0)); + simpleNumber.getAsNumber().intValue(); + simpleNumber.getAsNumber().longValue(); + + * 序列参数 func([1,2,3]); + SimpleSequence simpleSequence = ((SimpleSequence)arguments.get(0)); + TemplateModel value = param.get(0); //这个value,可能是任意的常量类型(取决于你数组中元素的类型) + int size = param.size(); //数组长度 + param.add(new Object()); //添加新的元素 + + * Hash常量 func({'v1':'51515','v2':[1,2,3,4]}) + TemplateHashModelEx2 templateHashModelEx2 = (TemplateHashModelEx2)arguments.get(0); + TemplateModel templateModel = templateHashModelEx2.get("Key"); //获取key + templateHashModelEx2.size(); //Hash长度 + //迭代 + KeyValuePairIterator keyValuePairIterator = templateHashModelEx2.keyValuePairIterator(); + while(keyValuePairIterator.hasNext()) { + KeyValuePair keyValuePair = keyValuePairIterator.next(); + TemplateModel key = keyValuePair.getKey(); + TemplateModel value = keyValuePair.getValue(); + } + + * Map 变量 + * 参数是 HashMap + + SimpleHash simpleHash = (SimpleHash)arguments.get(0); + TemplateModel templateModel = simpleHash.get("Key"); //获取key + simpleHash.size(); //Hash长度 + simpleHash.containsKey("Key"); //判读key是否存在 + simpleHash.put("age", 23); //添加元素到map + simpleHash.isEmpty(); + //迭代 + KeyValuePairIterator keyValuePairIterator = simpleHash.keyValuePairIterator(); + while(keyValuePairIterator.hasNext()) { + KeyValuePair keyValuePair = keyValuePairIterator.next(); + TemplateModel key = keyValuePair.getKey(); + TemplateModel value = keyValuePair.getValue(); + } + + * 对象变量 + * 参数是Java对象 + StringModel stringModel = (StringModel)arguments.get(0); + TemplateModel templateModel = stringModel.get(""); + TemplateModel templateModel2 = stringModel.getAPI(); + String string = stringModel.getAsString(); + TemplateCollectionModel templateCollectionModel = stringModel.keys(); + int size = stringModel.size(); + + * Date 参数 func(.now); + SimpleDate simpleDate = (SimpleDate)arguments.get(0); + Date date = simpleDate.getAsDate(); //获取日期 + int simpleDate.getDateType(); //获取日期的类型 + + * dateType 常量 + TemplateDateModel.UNKNOWN; + TemplateDateModel.TIME; + TemplateDateModel.DATE; + TemplateDateModel.DATETIME; + + + * 这设计感觉咋那么瓜皮??? + * 所有的参数被封装为Freemarker定义的类型 BeanModel 后,都有2个方法 (BeanModel implements TemplateModel ) + + Object getAdaptedObject(Class hint) + Object getWrappedObject() + + * 通过这俩方法,可以获取到调用时传递的原生对象 + + ${localDateTimeFormatter(paste.createDate)} + + StringModel stringModel = (StringModel) arguments.get(0); + LocalDateTime localDateTime = (LocalDateTime) stringModel.getAdaptedObject(LocalDateTime.class); + + +---------------------------- +编码实现 | +---------------------------- + # 实现接口: TemplateMethodModelEx + import java.util.List; + + import freemarker.core.Environment; + import freemarker.template.TemplateMethodModelEx; + import freemarker.template.TemplateModelException; + + public class FooFunction implements TemplateMethodModelEx{ + + public FooFunction() { + System.out.println("new"); + } + + //arguments 就是传递进来的参数 + @SuppressWarnings("rawtypes") + @Override + public Object exec(List arguments) throws TemplateModelException { + //获取当前运行环境 + Environment environment = Environment.getCurrentEnvironment(); + System.out.println(environment); + return "result"; + } + } + + + # 把该实例给模版引擎,作为一个变量存在 + * configuration.setSharedVariable("fooFunc", new FooFunction()); + * 可以设置为共享变量,以单例形式存在 + + # 在模板引擎中使用 + ${fooFunc("123456")} + + + + diff --git "a/Freemarker/freemarker-\350\256\277\351\227\256\351\235\231\346\200\201\345\261\236\346\200\247.java" "b/Freemarker/freemarker-\350\256\277\351\227\256\351\235\231\346\200\201\345\261\236\346\200\247.java" new file mode 100644 index 00000000..36fb8a1d --- /dev/null +++ "b/Freemarker/freemarker-\350\256\277\351\227\256\351\235\231\346\200\201\345\261\236\346\200\247.java" @@ -0,0 +1,18 @@ +---------------------------------- +使用BeansWrapper访问静态属性 | +---------------------------------- + # 代码配置 + BeansWrapper wrapper = new BeansWrapper(freemarker.template.Configuration.VERSION_2_3_28); + TemplateHashModel templateHashModel = wrapper.getStaticModels(); + + this.configuration.setSharedVariable("static", templateHashModel); + + * wrapper.getStaticModels() 会返回一个 TemplateHashModel + * 它会利用反射公开所有的静态方法和静态成员变量, 包括 final 和 非final 的 + + * 将这个 HashModel 添加到全局共享变量 + + # 在视图中调用静态方法 + ${static['org.springframework.context.i18n.LocaleContextHolder'].getLocale()} + + * 使用访问hash的方法去访问到类, 然后再调用它的静态属性/方法 diff --git "a/Freemarker/freemarker-\351\205\215\347\275\256.java" "b/Freemarker/freemarker-\351\205\215\347\275\256.java" new file mode 100644 index 00000000..239370a2 --- /dev/null +++ "b/Freemarker/freemarker-\351\205\215\347\275\256.java" @@ -0,0 +1,163 @@ +----------------------- +freemarker-配置 | +----------------------- + # 实例化 + Configuration configuration = new Configuration(Configuration.VERSION_2_3_28); + + * 它应该是一个单例对象 + + + # 共享变量 + * 是为所有模板定义的变量,可以使用 setSharedVariable 方法向配置中添加共享变量 + Configuration cfg = new Configuration(Configuration.VERSION_2_3_22); + cfg.setSharedVariable("warp", new WarpDirective()); + cfg.setSharedVariable("company", "Foo Inc."); + + * 用户自定义的同名变量会覆盖全局变量 + * 在多线程环境中使用,不要使用 TemplateModel 实现类来作为共享变量, 因为它是不是线程安全的 + + * 出于向后兼容的特性,共享变量的集合初始化时 (就是对于新的 Configuration 实例来说)不能为空, 它包含下列用户自定义指令(用户自定义指令使用时需要用 @ 来代替#) + + capture_output freemarker.template.utility.CaptureOutput + compress freemarker.template.utility.StandardCompress + html_escape freemarker.template.utility.HtmlEscape + normalize_newlines freemarker.template.utility.NormalizeNewlines + xml_escape freemarker.template.utility.XmlEscape + + # 配置信息的设置 + * 配置信息可以被想象成3层(Configuration, Template,Environment), 级别由低到高,高级别会覆盖低级别的配置 + * Configuration 的配置 + * 原则上设置配置信息时使用 Configuration 对象的setter方法 + * 在真正使用 Configuration 对象 (通常在初始化应用程序时)之前来配置它,后面必须将其视为只读的对象 + + * Template 的配置 + + * Environment 的配置 + * 使用Java API:使用 Environment 对象的setter方法 + * 当然想要在模板执行之前来做,然后当调用 myTemplate.process(...) 时会遇到问题, 因为在内部创建 Environment 对象后立即就执行模板了, 导致没有机会来进行设置 + * 这个问题的解决可以用下面两个步骤进行 + Environment env = myTemplate.createProcessingEnvironment(root, out); //创建env + env.setLocale(java.util.Locale.ITALY); //设置属性 + env.setNumberFormat("0.####"); + env.process(); // 渲染模板 + + * 在模板中(通常这被认为是不好的做法)直接使用 setting 指令 + <#setting locale="it_IT"> + <#setting number_format="0.####"> + + + # 模板加载 + * 从文件路径加载 + void setDirectoryForTemplateLoading(File dir); + + * 从classpath路径加载 + void setClassForTemplateLoading(Class cl, String prefix); + + * 传入的class参数会被 Class.getResource() 用来调用方法来找到模板 + * prefix 是给模板的名称来加前缀的 + * 类加载机制是首选用来加载模板的方法,通常情况下,从类路径下加载文件的这种机制,要比从文件系统的特定目录位置加载安全而且简单 + * 在最终的应用程序中,所有代码都使用 .jar 文件打包也是不错的, 这样用户就可以直接执行包含所有资源的 .jar 文件了 + * 如果 prefix 以"/"开头,则从classpath根路径开始搜索,否则以当前类的路径开始搜索 + + * 最佳实践 + //配置 + configuration.setClassForTemplateLoading(this.getClass(), "templates/"); + //模板目录 + {this.getClass()..getName()}/templates/ + //获取模板 + Template template = this.configuration.getTemplate("foo.ftl"); + + + //配置 + configuration.setClassForTemplateLoading(this.getClass(), "/templates/"); + //模板目录 + {classpath}/templates/ + //获取模板 + Template template = this.configuration.getTemplate("foo.ftl"); + + + + * 相对于WEB应用的'/WEB-INF目录的上级目录'(项目路径)的路径加载 + void setServletContextForTemplateLoading(Object servletContext, String path); + + * 需要Web应用的上下文和一个基路径作为参数, 这个基路径是Web应用根路径(WEB-INF目录的上级目录)的相对路径 + * 该加载方法对非war包的程序没用,因为它使用了 ServletContext.getResource() 方法来访问模板 + * 如果忽略了第二个参数(或使用了""),那么就可以混合存储静态文件(.html.jpg等) 和 .ftl 文件,只是只有 .ftl 文件会被模板渲染 + * 当然必须在 WEB-INF/web.xml 中配置一个Servlet来处理URI格式为 *.ftl 的用户请求,否则客户端无法获取到模板 + + * 最佳实践 + //配置 + configuration.setServletContextForTemplateLoading(super.getServletContext() , "/WEB-INF/templates"); + //模板目录 + /WEB-INF/templates + //获取模板 + Template template = this.configuration.getTemplate("foo.ftl"); + + + * 从多个位置加载模板 + * TODO + + * 从URL加载模板 + * TODO + + # 模板缓存 + * 会缓存模板的(假设使用 Configuration 对象的方法来创建 Template 对象) + * 这就是说当调用 getTemplate方法时,FreeMarker不但返回了 Template 对象,而且还会将它存储在缓存中 + * 当下一次再以相同(或相等)路径调用 getTemplate 方法时, 那么它只返回缓存的 Template 实例, 而不会再次加载和解析模板文件了 + + * 如果更改了模板文件,当下次调用模板时,FreeMarker 将会自动重新载入和解析模板 + * 然而,要检查模板文件是否改变内容了是需要时间,可以配置这个时间 其默认值是5秒,如果想要看到模板立即更新的效果,那么就要把它设置为0 + configuration.setSetting(Configuration.TEMPLATE_UPDATE_DELAY_KEY_SNAKE_CASE, "0ms"); + + * 某些模板加载器也许在模板更新时可能会有问题, 例如,典型的例子就是在基于类加载器的模板加载器就不会注意到模板文件内容的改变 + + + * 将一个缓存模板清除的实际应用策略是由配置的属性 cache_storage 来确定的 + //TODO configuration.setCacheStorage(new freemarker.cache.MruCacheStorage(20, 250)) ?二级缓存 + + * 还可以使用 Configuration 对象的 clearTemplateCache 方法手动清空缓存 + configuration.clearTemplateCache(); + + + # 错误控制 + * FreeMarker 发生的异常,可以分为如下几类 + + * 初始化异常 + * 加载解析模板异常 + * 调用了 Configuration.getTemplate(...) 方法,FreeMarker就要把模板文件加载到内存中然后来解析它 (除非模板已经在 Configuration 对象中被 缓存 了)。 + * 在这期间,有两种异常可能发生: + - 因模板文件没有找到而发生的 IOException 异常,或在读取文件时发生其他的I/O问题,比如没有读取文件的权限,或者是磁盘错误 + - 根据FTL语言的规则,模板文件发生语法错误时会导致 freemarker.core.ParseException 异常 + * 渲染模板异常 + * 是当调用了 Template.process(...) 方法时会发生的两种异常 + - 当试图写入输出对象时发生错误而导致的 IOException 异常 + - 当执行模板时发生的其它问题而导致的 freemarker.template.TemplatException 异常 + * 比如,一个频繁发生的错误,就是当模板引用一个不存在的变量 + + * 默认情况下,当 TemplatException 异常发生时,FreeMarker会用普通文本格式在输出中打印出FTL的错误信息和堆栈跟踪信息, 然后再次抛出 TemplatException 异常而中止模板的执行 + * 可以捕捉到 Template.process(...) 方法抛出的异常,而这种行为是可以定制的,FreeMarker也会经常写 TemplatException 异常的 日志 + + * FreeMarker 本身带有这些预先编写的错误控制器 + + TemplateExceptionHandler.DEBUG_HANDLER + * 打印堆栈跟踪信息(包括FTL错误信息和FTL堆栈跟踪信息)和重新抛出的异常 + * 这是默认的异常控制器(也就是说,在所有新的 Configuration 对象中,它是初始化好的) + * '打印页面' + + TemplateExceptionHandler.HTML_DEBUG_HANDLER + * 和 DEBUG_HANDLER 相同,但是它可以格式化堆栈跟踪信息, 那么就可以在Web浏览器中来阅读错误信息 + * 当你在制作HTML页面时,建议使用它而不是 DEBUG_HANDLER。 + * '打印html页面' + + TemplateExceptionHandler.IGNORE_HANDLER + * 简单地压制所有异常(但是要记住,FreeMarker 仍然会写日志)。 它对处理异常没有任何作用,也不会重新抛出异常。 + * '不处理,不抛出' + + TemplateExceptionHandler.RETHROW_HANDLER + * 简单重新抛出所有异常而不会做其它的事情, 这个控制器对Web应用程序(假设你在发生异常之后不想继续执行模板)来说非常好, 因为它在生成的页面发生错误的情况下,给了你很多对Web应用程序的控制权 (因为FreeMarker不向输出中打印任何关于该错误的信息) + * '再次抛出异常,可以被调用者捕获而自定义逻辑' + + +----------------------- +freemarker-配置项 | +----------------------- \ No newline at end of file diff --git "a/GIT/GIT-\345\210\206\346\224\257java.java" "b/GIT/GIT-\345\210\206\346\224\257java.java" deleted file mode 100644 index da9c1481..00000000 --- "a/GIT/GIT-\345\210\206\346\224\257java.java" +++ /dev/null @@ -1,16 +0,0 @@ ------------------------- -GIT-分支 | ------------------------- - git branch - # 查看所有的分支 - git branch [name] - # 创建分支 - git checkout [name] - # 切换分支 - git checkout -b [name] - # 创建+切换分支 - git merge [name] - # 合并某分支到当前分支 - - git branch -d [name] - # 删除分支 \ No newline at end of file diff --git "a/GIT/GIT-\345\221\275\344\273\244.java" "b/GIT/GIT-\345\221\275\344\273\244.java" index 20c83994..7dc50f37 100644 --- "a/GIT/GIT-\345\221\275\344\273\244.java" +++ "b/GIT/GIT-\345\221\275\344\273\244.java" @@ -1,8 +1,190 @@ ------------------------ -Git-命令 | +差异查看 | ------------------------ - # 哈哈哈,Linux之父折腾出来的神器 - # 对于学过Linux的少年来说,懂命令行...学GIT命令跟玩儿似得 + git diff + * 查看的是工作区修改的内容和暂存区的差异 + * 修改之后还没有暂存起来的变化内容 + + git diff --staged + * 查看已暂存的,将要添加到下次提交里的内容 + + git difftool + * 以图形化的形式去对比 + + +------------------------ +撤销操作 | +------------------------ + # 撤销文件的修改/删除 + git checkout -- [文件] + * 文件修改/删除后还没有被放到暂存区,撤销就回到和版本库一模一样的状态 + * 文件已经添加到暂存区后,又作了修改/删除,撤销就回到添加到暂存区时的状态 + * 总之,就是让这个文件回到最近一次 git commit 或 git add时的状态 + * -- 符号很重要,不然这个命令就会变成切换分支的命令 + * 场景:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令 + + # 取消暂存的文件 + git reset HEAD [文件] + * 其实就是 add 文件到了暂存区后,从暂存区删除文件 + * HEAD 参数表示最新的版本 + * 场景:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改 + 第一步用命令git reset HEAD file,先从暂存区删除add的文件信息 + 第二步用命令git checkout -- file,把文件还原到修改之前(与版本库保持一直)的样子 + + # 版本回退 + git reset --hard HEAD^ + * 在Git中,用HEAD表示当前版本,也就是最新的提交 3628164...882e1e0(注意我的提交ID和你的肯定不一样) + * 上一个版本就是HEAD^,上上一个版本就是HEAD^^ + * 当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100 + + + git reset --hard 4e7e8757dc1bb577df3eab1de1572f7ab7f665ef + * 切换到指定的版本 + * 版本号没必要写全,前几位就可以了,Git会自动去找 + + +------------------------ +远程仓库 | +------------------------ + git remote -v + * 查看远程列表的push/pull列表 + + git remote show [alias] + * 查看指定远程仓库的详细信息 + + git remote add [alias] [remote] + * 添加一个远程仓库 + alias 指定别名 + remote 指定地址 + + git remote rename [old-alias] [new-alias] + * 重命名远程仓库的名称 + + git remote rm [alias] + * 删除指定别名的远程仓库 + + git clone [remote] + * clone远程仓库,并且自动关联master分支 + +------------------------ +GIT-分支 | +------------------------ + # 本地分支 + git branch + * 查看所有的分支 + -a + * 查看所有的分支,包括远程仓库的分支信息 + + git branch [name] + * 创建分支 + + git checkout [name] + * 切换分支 + + git checkout -b [name] + * 创建+切换分支 + + git merge [name] + * 合并某分支到当前分支 + + --no-ff + * 使用该参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并 + * 因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去 + git merge --no-ff -m "注释" [name] + + * fast forward模式的合并就看不出来曾经做过合并(默认) + + git branch -d [name] + * 删除分支 + -D + * 大写的-D表示强制删除 + + git branch --merged + * 查看哪些分支已经合并到了当前分支 + + git branch --no-merged + * 查看哪些分支还未合并到当前的分支 + + # 远程分支 + git checkout --track [alias]/[branch] + * 从远程仓库拉取分支,并且在本地创建与之关联的分支 + * 可以通过 -b 参数来设置本地的分支的名称(不与远程分支一样) + + git branch --set-upstream-to=[alias]/[branch] [branch] + * 设置已有的本地分支(当前分支)跟踪一个刚刚拉取下来的远程分支(或者想要修改正在跟踪的上游分支) + + + git push [alias] --delete [branch] + * 删除远程分支 + + git push [alias] [branch] + * 推送本地分支到远程分支 + + * 第一次执行可以通过 -u 参数来与远程分支创建关联(如果远程分支不存在,会先创建) + * 关联成功后可以直接在当前分支执行:git push + + git fetch [alias] [branch] + * 拉取远程仓库代码,不合并 + + git pull [alias] [branch] + * 拉取远程仓库代码,并且合并到当前分支 + +------------------------ +GIT-标签 | +------------------------ + # tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起 + * 标签总是和某个commit挂钩,如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签 + * 创建的标签都只存储在本地,不会自动推送到远程 + + git tag + * 查看标签 + * 标签不是按时间顺序列出,而是按字母排序的 + + git tag [name] [commit] + * 在当前分支打一个标签 + name + * 标签名称 + + commit + * 可以指定commitid,通过下面的命令查看历史commitid + git log --pretty=oneline --abbrev-commit + * 如果不指定该参数,默认标签是打在最新提交的commit上的 + + -a + * 该选项用来设置标签的名称 + -m + * 该选项用来设置标签的说明 + * 一般跟 -a 一起使用,用于打标签,并且设置说明 + git tag -a "v1" -m "我的第一个标签" + + git show [name] + * 查看指定标签的commit详情 + + git push [alias] [name] + * 推送当前分支的指定的标签到远程仓库关联的分支 + --tags + * 一次性推送全部尚未推送到远程的本地标签 + git push origin --tags + * 不用手动指定标签名 name + + git tag -d [name] + * 删除本地指定名称的标签 + + git push origin --delete [name] + * 删除远程仓库的标签(先删除本地标签) + * 标签的删除也是 push + + + + + + + + + + + + ------------------------ diff --git "a/GIT/GIT-\350\277\234\347\250\213\344\273\223\345\272\223.java" "b/GIT/GIT-\350\277\234\347\250\213\344\273\223\345\272\223.java" deleted file mode 100644 index 415e2c69..00000000 --- "a/GIT/GIT-\350\277\234\347\250\213\344\273\223\345\272\223.java" +++ /dev/null @@ -1,31 +0,0 @@ ------------------------- -GIT-远程仓库 | ------------------------- - git remote add origin git@github.com:XXXXXXXX/XXXXXX.git - # 让本地仓库与远程仓库关联 - # 就可以同步下来远程仓库上的代码 - # 如果需要推送,那么就必须要注意:"ssh"公钥 - - ssh-keygen -t rsa -C "youremail@example.com" - # 创建shh-key - # 会在~/.ssh 生成一个带有pub的文件,里面就是公钥 - - - git clone [仓库地址] [本地目录] - * 克隆远程仓库中的数据到本地的目录 - * 会在本地目录中生成一个:.git 的隐藏文件 - - git push origin master - # 提交到中央仓库 - # 你这个仓库是从哪里Clone的?就会提交到哪个仓库 - # 如果是第一次推送:git push -u origin master - # 加-u参数:Git不但会把本地的master分支内容推送的远程新的master分支, - # 还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。 - - git pull - # 从中央仓库更新本地仓库的数据 - - - 要关联一个远程库,使用命令git remote add origin git@github.com:KevinBlandy/spring-boot-dynamicDatasource.git - 关联后,使用命令git push -u origin master第一次推送master分支的所有内容; - 此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改; \ No newline at end of file diff --git "a/GIT/git\346\223\215\344\275\234.java" "b/GIT/git-\346\223\215\344\275\234.java" similarity index 85% rename from "GIT/git\346\223\215\344\275\234.java" rename to "GIT/git-\346\223\215\344\275\234.java" index 656abd9f..f50338ab 100644 --- "a/GIT/git\346\223\215\344\275\234.java" +++ "b/GIT/git-\346\223\215\344\275\234.java" @@ -1,16 +1,22 @@ --------------------------------- 鏈湴浠撳簱 | --------------------------------- +# 鍙傝冨湴鍧 + http://tv1314.com/post-571.html + + 鍒濆鍖栦粨搴 git init + * 榛樿鍒濆鍖栧綋鍓嶇洰褰,鑰屼笖褰撳墠闈㈠繀椤绘槸绌虹殑 + * 濡傛灉鎸囧畾浜嗙洰褰曞弬鏁,鍒欎細鍦ㄥ綋鍓嶇洰褰曟柊寤轰竴涓洰褰曡繘琛屽垵濮嬪寲 -璁剧疆鐢ㄦ埛淇℃伅 - git config user.name "KevinBlandy" - git config user.email "747692844@qq.com" - * 璁剧疆鍏ㄥ眬,娣诲姞 --global 鍙傛暟 - * 琛ㄧず鏈哄櫒涓婃墍鏈夌殑Git浠撳簱閮戒細浣跨敤杩欎釜閰嶇疆,褰撶劧涔熷彲浠ュ鏌愪釜浠撳簱鎸囧畾涓嶅悓鐨勭敤鎴峰悕鍜孍mail鍦板潃 +鍏ㄥ眬鐨勮缃(璁剧疆鍏ㄥ眬,娣诲姞 --global 鍙傛暟) + git config --global user.name "KevinBlandy" + git config --global user.email "747692844@qq.com" + * 琛ㄧず鏈哄櫒涓婃墍鏈夌殑Git浠撳簱閮戒細浣跨敤杩欎釜閰嶇疆,褰撶劧涔熷彲浠ュ鏌愪釜浠撳簱鎸囧畾涓嶅悓鐨勭敤鎴峰悕鍜孍mail鍦板潃 - + git config --global color.ui true + * 寮鍚僵鑹茬殑UI(浜や簰鏂囧瓧鏈夐鑹) 娣诲姞鏂囦欢鍒版殏瀛樺尯 git add [鏂囦欢鍚峕 @@ -101,6 +107,17 @@ 6eb36af HEAD@{10}: commit: 2 333e9a8 HEAD@{11}: commit (initial): first +涓枃澶勭悊 + git config --global core.quotepath false + * 瑙e喅鍦 git bash 涓,涓枃浠ョ紪鐮佸舰寮忓嚭鐜扮殑闂 + + git config --global gui.encoding utf-8 + * 璁剧疆 gui 鐨勭紪鐮 + + git config --global i18n.commitencoding utf-8 + * 璁剧疆鎻愪氦鏃剁敤鐨勭紪鐮,蹇呴』涓庢湇鍔″櫒淇濇寔涓鑷 + + --------------------------------- 杩滅▼浠撳簱 | --------------------------------- @@ -140,6 +157,14 @@ * 鍏跺疄灏辨槸杩斿洖杩滅▼浠撳簱鐨勫悕绉 origin * 鍙互娣诲姞鍙傛暟 -v 鏉ユ樉绀烘洿瀹屾暣鐨勪俊鎭 +# 鍒涘缓涓庤繙绋嬪垎鏀叧鑱旂殑鏈湴鍒嗘敮 + git checkout -b origin/[杩滅▼鍒嗘敮鍚嶇О] [鏈湴鍒嗘敮鍚嶇О] + +# 璁剧疆鏈湴鍒嗘敮涓庤繙绋嬪垎鏀叧鑱 + git branch --set-upstream-to origin/[杩滅▼鍒嗘敮鍚嶇О] [鏈湴鍒嗘敮鍚嶇О] + + * 鏈湴鍒嗛渶瑕佸厛鍒涘缓 + --------------------------------- 鍒嗘敮绠$悊 | --------------------------------- @@ -167,6 +192,20 @@ 鍒犻櫎鍒嗘敮 git branch -d [name] + +# 鍐荤粨鐜板満(淇濆瓨杩涘害) + git stash + +# 鏌ョ湅鍐荤粨鐨勫垪琛 + git stash list + +# 瑙i櫎鐜板満鍐荤粨 + git stash apply stash@{0} + +# 鍒犻櫎鍐荤粨 + git stash drop + git stash pop(瑙i櫎骞朵笖鍒犻櫎鏈鍚庝竴涓喕缁) + --------------------------------- 鍒嗘敮绛栫暐绠$悊 | --------------------------------- diff --git "a/GIT/git-\351\205\215\347\275\256.java" "b/GIT/git-\351\205\215\347\275\256.java" new file mode 100644 index 00000000..33645d3a --- /dev/null +++ "b/GIT/git-\351\205\215\347\275\256.java" @@ -0,0 +1,35 @@ + +# 查看所有的配置 +git config --list + core.symlinks=false + core.autocrlf=true + core.fscache=true + color.diff=auto + color.status=auto + color.branch=auto + color.interactive=true + help.format=html + rebase.autosquash=true + http.sslcainfo=C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt + http.sslbackend=openssl + diff.astextplain.textconv=astextplain + filter.lfs.clean=git-lfs clean -- %f + filter.lfs.smudge=git-lfs smudge -- %f + filter.lfs.process=git-lfs filter-process + filter.lfs.required=true + credential.helper=manager + user.email=747692844@qq.com + user.name=KevinBlandy + * 用户的邮件和名称 + core.quotepath=false + color.ui=true + * 交互界面的文字带颜色 + +# 设置配置 + git config --global [配置项] [配置值] + + --global + * 表示全局,对所有仓库生效 + * 不添加的话,仅仅对当前仓库生效 + + \ No newline at end of file diff --git "a/Gradle/gradle-\345\205\245\351\227\250.java" "b/Gradle/gradle-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..4c6a6e1f --- /dev/null +++ "b/Gradle/gradle-\345\205\245\351\227\250.java" @@ -0,0 +1,22 @@ +-------------------------------- +gradle-鍏ラ棬 | +-------------------------------- + # 瀹樼綉 + https://gradle.org/ + + # 涓嬭浇鍦板潃 + https://gradle.org/releases/ + +-------------------------------- +gradle-windows瀹夎 | +-------------------------------- + 1,涓嬭浇 + 2,瑙e帇 + 3,閰嶇疆path + D:\gradle-4.6\bin + +-------------------------------- +gradle-Linux瀹夎 | +-------------------------------- + + \ No newline at end of file diff --git a/Guava/guava-Hashing.java b/Guava/guava-Hashing.java new file mode 100644 index 00000000..6136aae1 --- /dev/null +++ b/Guava/guava-Hashing.java @@ -0,0 +1,111 @@ +-------------------------------- +Hashing | +-------------------------------- + # Guava提供的hash算法类库(https://docs.google.com/spreadsheets/d/1_q2EVcxA2HjcrlVMbaqXwMj31h9M5-Bqj_m8vITOwwk/edit#gid=0) + +---------------------------------------------------------------------------------------------- + | Hash 算法 长度(Bit) 时间 推荐 备注 + +---------------------------------------------------------------------------------------------- + | Hashing#adler32 32 1.00 No 仅限校验和(交易速度的可靠性) + +---------------------------------------------------------------------------------------------- + | Hashing#crc32 32 1.52 No + +---------------------------------------------------------------------------------------------- + | Hashing#goodFastHash(32) 32 2.73 Yes VM运行之间不稳定 + +---------------------------------------------------------------------------------------------- + | Hashing#murmur3_32 32 2.75 Yes + +---------------------------------------------------------------------------------------------- + | Hashing#goodFastHash(64) 64 5.25 Yes VM运行之间不稳定 + +---------------------------------------------------------------------------------------------- + | Hashing#murmur3_128 128 5.26 Yes + +---------------------------------------------------------------------------------------------- + | Hashing#goodFastHash(128) 128 5.41 Yes VM运行之间不稳定 + +---------------------------------------------------------------------------------------------- + | Hashing#md5 128 6.03 No 不具有加密安全性或抗冲突性 + +---------------------------------------------------------------------------------------------- + | Hashing#sha1 160 9.78 No 不加密安全 + +---------------------------------------------------------------------------------------------- + | Hashing#goodFastHash(256) 256 10.41 Yes VM运行之间不稳定 + +---------------------------------------------------------------------------------------------- + | Hashing#sha256 256 17.58 No 可能是加密安全的 + +---------------------------------------------------------------------------------------------- + | Hashing#sha512 512 43.78 Yes + +---------------------------------------------------------------------------------------------- + | Hashing#goodFastHash(int bits) N n/a Yes 不稳定,用户配置了N位HashCodes + +---------------------------------------------------------------------------------------------- + + + + # 它提供了N多的静态方法,用于创建不同的Hash算法(以及不同版本,位数)实例 + + HashFunction crc32() + + HashFunction crc32c() + + HashFunction goodFastHash(int minimumBits) + + HashFunction farmHashFingerprint64() + + HashFunction hmacMd5(byte[] key) + + + HashFunction murmur3_128() + HashFunction murmur3_128(int seed) + HashFunction murmur3_32() + HashFunction murmur3_32(int seed) + + HashFunction sha256() + HashFunction sha384() + HashFunction sipHash24() + +-------------------------------- +HashFunction | +-------------------------------- + # 它是一个接口,由不同的hash算法实现 + # 接口 方法 + Hasher newHasher(); + + Hasher newHasher(int expectedInputSize); + + HashCode hashInt(int input); + + HashCode hashLong(long input); + + HashCode hashBytes(byte[] input); + * 对字节数据进行hash计算 + + HashCode hashBytes(byte[] input, int off, int len); + + HashCode hashBytes(ByteBuffer input); + + HashCode hashUnencodedChars(CharSequence input); + + HashCode hashString(CharSequence input, Charset charset); + * 对字符串进行hash运算 + + HashCode hashObject(T instance, Funnel funnel); + + int bits(); +-------------------------------- +Hashing | +-------------------------------- + # 是hash计算结果的抽象接口 + # 静态方法 + HashCode fromInt(int hash) + + # 实例烦恼发 + int asInt(); + * 返回hash的int值 + + long asLong(); + * 返回hash的long值 + + long padToLong(); + byte[] asBytes(); + int bits(); + +-------------------------------- +各种Hash算法的实现 | +-------------------------------- + # murmur3 128位hash算法 + HashFunction function = Hashing.murmur3_128(); + HashCode hascode = function.hashString("murmur3_128", StandardCharsets.UTF_8); + System.out.println(hascode.asInt()); \ No newline at end of file diff --git "a/Guava/guava-\344\273\244\347\211\214\346\241\266\347\256\227\346\263\225.java" "b/Guava/guava-\344\273\244\347\211\214\346\241\266\347\256\227\346\263\225.java" new file mode 100644 index 00000000..61af9317 --- /dev/null +++ "b/Guava/guava-\344\273\244\347\211\214\346\241\266\347\256\227\346\263\225.java" @@ -0,0 +1,57 @@ +---------------------------- +RateLimiter | +---------------------------- + # 工厂函数 + RateLimiter create(double permitsPerSecond) + RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) + + * permitsPerSecond 表示一秒产生多少个令牌 + * warmupPeriod + * unit表示warmupPeriod的时间单位 + + +---------------------------- +RateLimiter | +---------------------------- + # 令牌桶算法实现 + //令牌桶每秒产生2.5个令牌 + RateLimiter rateLimiter = RateLimiter.create(2.5,1,TimeUnit.SECONDS); + //消费一个令牌 + System.out.println(rateLimiter.acquire()); + //手动设置,需要消费多少个令牌,如果桶里面没有令牌,则当前线程阻塞,返回值为阻塞了多久才得到的令牌 + System.out.println(rateLimiter.acquire(5)); + //如果一次性拿走的令牌,超过了剩余的令牌,那么下一次获取是要还的(也就是,取的速率可以超过令牌产生的速率,但是下一次再次去取的时候,需要阻塞等待) + System.out.println(rateLimiter.acquire(1)); + + + //非阻塞的获取令牌,实时返回结果。是否有剩余的令牌 + boolean result = rateLimiter.tryAcquire(); + System.out.println(result); + + //非阻塞的获取令牌,实时返回结果。是否有剩余的令牌。通过参数设置超时时间,如果超时了执行返回 false + result = rateLimiter.tryAcquire(2,TimeUnit.SECONDS); + System.out.println(result); + + # 令牌可以超额获取,但是下一个获取令牌的人是要还的 + //令牌桶每秒产生1个令牌 + RateLimiter rateLimiter = RateLimiter.create(1,1,TimeUnit.SECONDS); + //直接获取5个令牌,因为只有1个令牌,透支了4个 + System.out.println(rateLimiter.acquire(5)); //0.0 + //获取1一个令牌,会被多阻塞4s,因为需要补上,上一个透支的令牌数量 + System.out.println(rateLimiter.acquire(1)); //5.498552 + + # Controller 里面使用 + //每1s产生0.5个令牌,也就是说该接口2s只允许调用1次 + private RateLimiter rateLimiter = RateLimiter.create(0.5,1,TimeUnit.SECONDS); + + @GetMapping(produces = "text/plain;charset=utf-8") + public String foo() throws InterruptedException { + //尝试获取1一个令牌 + if(rateLimiter.tryAcquire()) { + //获取到令牌,进行逻辑处理 + return "处理成功"; + }else { + //未获取到令牌 + return "请求频繁"; + } + } \ No newline at end of file diff --git "a/Guava/guava-\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250.java" "b/Guava/guava-\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250.java" new file mode 100644 index 00000000..12dd00d8 --- /dev/null +++ "b/Guava/guava-\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250.java" @@ -0,0 +1,32 @@ +---------------------------- +BloomFilter | +---------------------------- + # 布隆过滤的实现 + + # Demo + /** + * 创建一个插入对象为一亿,误报率为0.01%的布隆过滤器 + */ + BloomFilter bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf-8")),100000000, 0.0001); + + //可以添加一亿个元素 + bloomFilter.put("123456"); + bloomFilter.put("123456"); + bloomFilter.put("123456"); + + //是否可能包含 + boolean result = bloomFilter.mightContain("121"); + System.out.println(result); + + + // 返回添加的元素个数 + long approximateElementCount(); + + # 注意 + * 元素可以是其他的数据类型:BloomFilter bloomFilter = BloomFilter.create(Funnels.longFunnel(), 100000000, 0.0001); + Funnels.longFunnel() //表示元素类型是long + + * 误报率必须是正数,且小于 1.0 + + * 提高数组长度以及 hash 计算次数可以降低误报率,但相应的 CPU,内存的消耗就会提高 + \ No newline at end of file diff --git "a/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\345\233\272\345\256\232\347\232\204\345\272\225\346\240\217.html" "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\345\233\272\345\256\232\347\232\204\345\272\225\346\240\217.html" new file mode 100644 index 00000000..dbaaf6f3 --- /dev/null +++ "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\345\233\272\345\256\232\347\232\204\345\272\225\346\240\217.html" @@ -0,0 +1,32 @@ +# 椤甸潰鍐呭澶皯,鏃犳硶鍗犳弧涓灞忕殑楂樺害,搴曟爮灏变細鎶珮鍒伴〉闈㈢殑涓棿 +# 杩欐椂鍙互閲囩敤Flex甯冨眬,璁╁簳鏍忔绘槸鍑虹幇鍦ㄩ〉闈㈢殑搴曢儴 + + + + + + + + Index + + + +
+ header +
+
body
+
鍥哄畾鐨勫簳閮
+ + \ No newline at end of file diff --git "a/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\345\234\243\346\235\257\345\270\203\345\261\200.html" "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\345\234\243\346\235\257\345\270\203\345\261\200.html" new file mode 100644 index 00000000..681f22c1 --- /dev/null +++ "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\345\234\243\346\235\257\345\270\203\345\261\200.html" @@ -0,0 +1,67 @@ +# 涓绉嶆渶甯歌鐨勭綉绔欏竷灞 +# 椤甸潰浠庝笂鍒颁笅,鍒嗘垚涓変釜閮ㄥ垎 + 澶撮儴(header) + 韬共(body) + 灏鹃儴(footer) + +# 鍏朵腑韬共鍙堟按骞冲垎鎴愪笁鏍,浠庡乏鍒板彸涓 + 瀵艰埅 + 涓绘爮 + 鍓爮 + + + + + + Index + + + +
澶撮儴
+
+
瀵艰埅
+
涓绘爮
+
鍓爮
+
+
灏鹃儴
+ + + +# 濡傛灉鏄皬灞忓箷,韬共鐨勪笁鏍忚嚜鍔ㄥ彉涓哄瀭鐩村彔鍔 + + @media (max-width: 768px) { + .HolyGrail-body { + flex-direction: column; + flex: 1; + } + .HolyGrail-nav, + .HolyGrail-ads, + .HolyGrail-content { + flex: auto; + } + } \ No newline at end of file diff --git "a/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\345\237\272\346\234\254\347\232\204\347\275\221\346\240\274\345\270\203\345\261\200.html" "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\345\237\272\346\234\254\347\232\204\347\275\221\346\240\274\345\270\203\345\261\200.html" new file mode 100644 index 00000000..8b7c77de --- /dev/null +++ "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\345\237\272\346\234\254\347\232\204\347\275\221\346\240\274\345\270\203\345\261\200.html" @@ -0,0 +1,42 @@ + +# 骞冲潎鍒嗗竷,鍦ㄥ鍣ㄩ噷闈㈠钩鍧囧垎閰嶇┖闂,浣嗘槸闇瑕佽缃」鐩殑鑷姩缂╂斁 + + + + + + Index + + + +
+
1
+
2
+
+
+
1
+
2
+
3
+
+
+
1
+
2
+
3
+
4
+
+ + + diff --git "a/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\346\265\201\345\274\217\345\270\203\345\261\200.html" "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\346\265\201\345\274\217\345\270\203\345\261\200.html" new file mode 100644 index 00000000..efc85c60 --- /dev/null +++ "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\346\265\201\345\274\217\345\270\203\345\261\200.html" @@ -0,0 +1,40 @@ +# 每行的项目数固定,会自动分行 + + + + + + + Index + + + +
+
+
+
+
+
+
+ + \ No newline at end of file diff --git "a/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\347\231\276\345\210\206\346\257\224\345\270\203\345\261\200.html" "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\347\231\276\345\210\206\346\257\224\345\270\203\345\261\200.html" new file mode 100644 index 00000000..b83e9542 --- /dev/null +++ "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\347\231\276\345\210\206\346\257\224\345\270\203\345\261\200.html" @@ -0,0 +1,41 @@ +# 鏌愪釜缃戞牸鐨勫搴︿负鍥哄畾鐨勭櫨鍒嗘瘮,鍏朵綑缃戞牸骞冲潎鍒嗛厤鍓╀綑鐨勭┖闂 + + + + + + Index + + + +
+
25%
+
鍓╀綑鐨勫叏鍗
+
33.3333%
+
+ + \ No newline at end of file diff --git "a/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\350\276\223\345\205\245\346\241\206\347\232\204\345\270\203\345\261\200.html" "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\350\276\223\345\205\245\346\241\206\347\232\204\345\270\203\345\261\200.html" new file mode 100644 index 00000000..b512e76e --- /dev/null +++ "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\350\276\223\345\205\245\346\241\206\347\232\204\345\270\203\345\261\200.html" @@ -0,0 +1,25 @@ +# 鍦ㄨ緭鍏ユ鐨勫墠鏂规坊鍔犳彁绀,鍚庢柟娣诲姞鎸夐挳 + + + + + + Index + + + +
+ 鍓嶉潰鐨勬彁绀 + + +
+ + \ No newline at end of file diff --git "a/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\351\252\260\345\255\220.html" "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\351\252\260\345\255\220.html" new file mode 100644 index 00000000..765f3c88 --- /dev/null +++ "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200-\351\252\260\345\255\220.html" @@ -0,0 +1,162 @@ + + + + + + + + + +
+ +
+
+ + +
+
+ + + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +
+
+ + +
+
+
+
+ + + +
+
+ + + +
+
\ No newline at end of file diff --git "a/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200.html" "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200.html" new file mode 100644 index 00000000..024281a7 --- /dev/null +++ "b/HTML/flex\345\270\203\345\261\200/flex\345\270\203\345\261\200.html" @@ -0,0 +1,155 @@ +---------------------------- +Flex 甯冨眬鏄粈涔 | +---------------------------- +# Flex 鏄 Flexible Box 鐨勭缉鍐,鎰忎负"寮规у竷灞",鐢ㄦ潵涓虹洅鐘舵ā鍨嬫彁渚涙渶澶х殑鐏垫椿鎬 + +# 浠讳綍涓涓鍣ㄩ兘鍙互鎸囧畾涓 Flex 甯冨眬 + .box{ + display: flex; + } + + * 琛屽唴鍏冪礌涔熷彲浠ヤ娇鐢 Flex 甯冨眬 + .box{ + display: inline-flex; + } + +# Webkit 鍐呮牳鐨勬祻瑙堝櫒,蹇呴』鍔犱笂-webkit鍓嶇紑 + .box{ + display: -webkit-flex; /* Safari */ + display: flex; + } + +# 璁句负 Flex 甯冨眬浠ュ悗,瀛愬厓绱犵殑float,clear鍜寁ertical-align灞炴у皢澶辨晥 + +---------------------------- +鍩烘湰鐨勬蹇 | +---------------------------- + # 閲囩敤 Flex 甯冨眬鐨勫厓绱,绉颁负 Flex 瀹瑰櫒(flex container) + # 瀹冪殑鎵鏈夊瓙鍏冪礌鑷姩鎴愪负瀹瑰櫒鎴愬憳,绉颁负 Flex 椤圭洰(flex item) + + main axis + |-main start(x杞村ご) + |-main end(x杞村熬) + |-main size(鍏冪礌瀹藉害) + + cross axis + |-cross start(y杞村ご) + |-cross end(y杞村熬) + |-cross size(鍏冪礌楂樺害) + + + +---------------------------- +瀹瑰櫒鐨勫睘鎬 | +---------------------------- + # flex-direction + * 鎬у喅瀹氫富杞寸殑鏂瑰悜(鍗抽」鐩殑鎺掑垪鏂瑰悜) + * 鏋氫妇鍊 + row(榛樿鍊):涓昏酱涓烘按骞虫柟鍚,璧风偣鍦ㄥ乏绔 (浠庡乏鍒板彸,鑷笂鑰屼笅) + row-reverse:涓昏酱涓烘按骞虫柟鍚,璧风偣鍦ㄥ彸绔 (浠庡彸鍒板乏,鑷笂鑰屼笅) + + column:涓昏酱涓哄瀭鐩存柟鍚,璧风偣鍦ㄤ笂娌 (浠庝笂鑰屼笅,浠庡乏鑷冲彸) + column-reverse:涓昏酱涓哄瀭鐩存柟鍚,璧风偣鍦ㄤ笅娌 (浠庝笅鑰屼笂,浠庡乏鑷冲彸) + + # flex-wrap + * 榛樿鎯呭喌涓,椤圭洰閮芥帓鍦ㄤ竴鏉$嚎涓,璇ュ睘鎬у畾涔,濡傛灉涓鏉¤酱绾挎帓涓嶄笅,濡備綍鎹㈣ + * 鏋氫妇鍊 + nowrap(榛樿):涓嶆崲琛 + wrap:鎹㈣,绗竴琛屽湪涓婃柟 + wrap-reverse:鎹㈣,绗竴琛屽湪涓嬫柟 + + + # flex-flow + * flex-direction 灞炴у拰 flex-wrap 灞炴х殑绠鍐欏舰寮,榛樿鍊间负:row nowrap + .box { + flex-flow: || ; + } + + # justify-content + * 椤圭洰鍦ㄤ富杞翠笂鐨勫榻愭柟寮 + * 瀹冨彲鑳藉彇5涓,鍏蜂綋瀵归綈鏂瑰紡涓庤酱鐨勬柟鍚戞湁鍏(鍋囪涓昏酱涓轰粠宸﹀埌鍙) + flex-start(榛樿鍊) + * 宸﹀榻 + flex-end + * 鍙冲榻 + center + * 灞呬腑 + space-between + * 涓ょ瀵归綈,椤圭洰涔嬮棿鐨勯棿闅旈兘鐩哥瓑銆 + space-around + * 姣忎釜椤圭洰涓や晶鐨勯棿闅旂浉绛 + * 鎵浠,椤圭洰涔嬮棿鐨勯棿闅旀瘮椤圭洰涓庤竟妗嗙殑闂撮殧澶т竴鍊(宸﹀彸杈硅窛 = 椤圭洰涔嬮棿鐨勮竟璺) + + # align-items + * 椤圭洰鍦ㄤ氦鍙夎酱涓婂浣曞榻 + * 鏋氫妇鍊 + flex-start + * 浜ゅ弶杞寸殑璧风偣瀵归綈 + flex-end + * 浜ゅ弶杞寸殑缁堢偣瀵归綈 + center + * 浜ゅ弶杞寸殑涓偣瀵归綈 + * 涓婁笅灞呬腑 + baseline + * 椤圭洰鐨勭涓琛屾枃瀛楃殑鍩虹嚎瀵归綈銆 + stretch(榛樿鍊) + * 濡傛灉椤圭洰鏈缃珮搴︽垨璁句负auto,灏嗗崰婊℃暣涓鍣ㄧ殑楂樺害 + + # align-content + * 澶氭牴杞寸嚎鐨勫榻愭柟寮,濡傛灉椤圭洰鍙湁涓鏍硅酱绾,璇ュ睘鎬т笉璧蜂綔鐢 + * 鏋氫妇鍊 + flex-start + * 涓庝氦鍙夎酱鐨勮捣鐐瑰榻 + flex-end + * 涓庝氦鍙夎酱鐨勭粓鐐瑰榻 + center + * 涓庝氦鍙夎酱鐨勪腑鐐瑰榻 + space-between + * 涓庝氦鍙夎酱涓ょ瀵归綈,杞寸嚎涔嬮棿鐨勯棿闅斿钩鍧囧垎甯 + * 涓婁笅灞呬腑,涓ょ瀵归綈,涓棿闂撮殧鑷姩 + + space-around + * 姣忔牴杞寸嚎涓や晶鐨勯棿闅旈兘鐩哥瓑 + * 鎵浠,杞寸嚎涔嬮棿鐨勯棿闅旀瘮杞寸嚎涓庤竟妗嗙殑闂撮殧澶т竴鍊 + + stretch(榛樿鍊) + * 杞寸嚎鍗犳弧鏁翠釜浜ゅ弶杞 + + +---------------------------- +瀹瑰櫒鐨勫睘鎬 | +---------------------------- + # order + * 瀹氫箟椤圭洰鐨勬帓鍒楅『搴,鏁板艰秺灏,鎺掑垪瓒婇潬鍓,榛樿涓0 + + # flex-grow + * 瀹氫箟椤圭洰鐨勬斁澶ф瘮渚,榛樿涓0,鍗冲鏋滃瓨鍦ㄥ墿浣欑┖闂,涔熶笉鏀惧ぇ + * 濡傛灉鎵鏈夐」鐩殑flex-grow灞炴ч兘涓1,鍒欏畠浠皢绛夊垎鍓╀綑绌洪棿(濡傛灉鏈夌殑璇) + * 濡傛灉涓涓」鐩殑flex-grow灞炴т负2,鍏朵粬椤圭洰閮戒负1,鍒欏墠鑰呭崰鎹殑鍓╀綑绌洪棿灏嗘瘮鍏朵粬椤瑰涓鍊 + + # flex-shrink + * 椤圭洰鐨勭缉灏忔瘮渚,榛樿涓1,鍗冲鏋滅┖闂翠笉瓒,璇ラ」鐩皢缂╁皬 + * 濡傛灉鎵鏈夐」鐩殑flex-shrink灞炴ч兘涓1,褰撶┖闂翠笉瓒虫椂,閮藉皢绛夋瘮渚嬬缉灏 + * 濡傛灉涓涓」鐩殑flex-shrink灞炴т负0,鍏朵粬椤圭洰閮戒负1,鍒欑┖闂翠笉瓒虫椂,鍓嶈呬笉缂╁皬 + * 璐熷煎璇ュ睘鎬ф棤鏁 + + # flex-basis + * 瀹氫箟浜嗗湪鍒嗛厤澶氫綑绌洪棿涔嬪墠,椤圭洰鍗犳嵁鐨勪富杞寸┖闂(main size) + * 娴忚鍣ㄦ牴鎹繖涓睘鎬,璁$畻涓昏酱鏄惁鏈夊浣欑┖闂,瀹冪殑榛樿鍊间负auto,鍗抽」鐩殑鏈潵澶у皬 + * 瀹冨彲浠ヨ涓鸿窡width鎴杊eight灞炴т竴鏍风殑鍊(姣斿350px),鍒欓」鐩皢鍗犳嵁鍥哄畾绌洪棿 + + # flex + * flex-grow, flex-shrink 鍜 flex-basis鐨勭畝鍐,榛樿鍊间负0 1 auto + * 鍚庝袱涓睘鎬у彲閫 + .item { + flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] + } + * 璇ュ睘鎬ф湁涓や釜蹇嵎鍊:auto (1 1 auto) 鍜 none (0 0 auto) + * 寤鸿浼樺厛浣跨敤杩欎釜灞炴,鑰屼笉鏄崟鐙啓涓変釜鍒嗙鐨勫睘鎬,鍥犱负娴忚鍣ㄤ細鎺ㄧ畻鐩稿叧鍊 + + # align-self + * align-self灞炴у厑璁稿崟涓」鐩湁涓庡叾浠栭」鐩笉涓鏍风殑瀵归綈鏂瑰紡,鍙鐩 align-items 灞炴 + * 榛樿鍊间负 auto,琛ㄧず缁ф壙鐖跺厓绱犵殑 align-items 灞炴,濡傛灉娌℃湁鐖跺厓绱,鍒欑瓑鍚屼簬:stretch + * 璇ュ睘鎬у彲鑳藉彇6涓,闄や簡auto,鍏朵粬閮戒笌 align-items 灞炴у畬鍏ㄤ竴鑷 + + \ No newline at end of file diff --git "a/Idea\344\275\277\347\224\250\346\211\213\345\206\214.java" "b/Idea\344\275\277\347\224\250\346\211\213\345\206\214.java" index ad1fb237..5c064eae 100644 --- "a/Idea\344\275\277\347\224\250\346\211\213\345\206\214.java" +++ "b/Idea\344\275\277\347\224\250\346\211\213\345\206\214.java" @@ -33,3 +33,7 @@ file->setting->Editor->Filr and Code Templates->Includes->File Header +-------------------- +IDEA-设置代码粗体 | +-------------------- + Editor -> Color Scheme -> General -> Text -> Default Text -> Bold[√] \ No newline at end of file diff --git "a/JAVAEE/JAVAWEB\347\233\221\345\220\254\345\231\250.txt" "b/JAVAEE/JAVAWEB\347\233\221\345\220\254\345\231\250.txt" index fc9d203c..7c2a3ef6 100644 --- "a/JAVAEE/JAVAWEB\347\233\221\345\220\254\345\231\250.txt" +++ "b/JAVAEE/JAVAWEB\347\233\221\345\220\254\345\231\250.txt" @@ -117,4 +117,25 @@ session javaBean实现监听钝化/活化的的接口 HttpSessionActivationListener public void sessionDidActivate(HttpSessionEvent arg0); - public void sessionWillPassivate(HttpSessionEvent arg0); \ No newline at end of file + public void sessionWillPassivate(HttpSessionEvent arg0); + + +-------SESSIONID变化监听器 +HttpSessionIdListener + void sessionIdChanged(HttpSessionEvent event, String oldSessionId); + +—————————————————————————— +总结 | +—————————————————————————— +ServletContextListener +ServletContextAttributeLitener + +HttpSessionListener +HttpSessionAttributeLitener + +ServletRequestListener +SerlvetRequestAttributeLitener + +HttpSessionBindingListener +HttpSessionActivationListener +HttpSessionIdListener diff --git a/JAVAEE/JavaMail/JavaMail.java b/JAVAEE/JavaMail/JavaMail.java index e73068af..7910730e 100644 --- a/JAVAEE/JavaMail/JavaMail.java +++ b/JAVAEE/JavaMail/JavaMail.java @@ -82,6 +82,13 @@ protected PasswordAuthentication getPasswordAuthentication() MimeMessage msg = new MimeMessage(session); try { + /** + 如果地址信息包含中文,需要进行编码 + String addr = javax.mail.internet.MimeUtility.encodeText("springboot中文社区"); + new InternetAddress(addr); + + **/ + //设置发件人信息 msg.setFrom(new InternetAddress("747692844@qq.com")); //设置收件人信息 diff --git a/JAVAEE/Servlet/Servlet3.0/ServletContainerInitializer.java b/JAVAEE/Servlet/Servlet3.0/ServletContainerInitializer.java new file mode 100644 index 00000000..31c2d6ff --- /dev/null +++ b/JAVAEE/Servlet/Servlet3.0/ServletContainerInitializer.java @@ -0,0 +1,43 @@ +----------------------------------- +ServletContainerInitializer | +----------------------------------- + + # 主要用于在容器启动阶段通过编程风格注册Filter, Servlet以及Listener,以取代通过web.xml配置注册 + + # Servlet容器启动会扫描,当前应用里面每一个'jar'包的 ServletContainerInitializer 的实现 + * 注意,是jar + + # 提供 ServletContainerInitializer 的实现类特殊限制 + * 必须创建文件:META-INF/services/javax.servlet.ServletContainerInitializer + * 文件的内容就是 ServletContainerInitializer实现类的全类名 + + # 可以给ServletContainerInitializer 的实现类添加 @HandlesTypes 注解 + * 在其onStartup 方法上便可以得到我们感兴趣的类 + * 容器会将当前应用中所有这一类型(继承或者实现)的类放在 ServletContainerInitializer 接口的集合参数c中传递进 + * 果不定义处理类型,或者应用中不存在相应的实现类,则集合参数c为空 + + # 它优先于 Listenner 执行 + + # Demo + import java.util.Set; + + import javax.servlet.ServletContainerInitializer; + import javax.servlet.ServletContext; + import javax.servlet.ServletException; + import javax.servlet.annotation.HandlesTypes; + + @HandlesTypes(Foo.class) + public class ApplicationServletContainerInitializer implements ServletContainerInitializer{ + + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException { + //c 里面就是 foo所有的子类 + } + } + + # SpringBoot 支持war的原理就是如此 + + +-------------------- +ServletContext | +-------------------- diff --git "a/JAVAEE/Servlet/Servlet3.0/\346\226\260\347\211\271\346\200\247-\345\274\202\346\255\245\345\244\204\347\220\206.java" "b/JAVAEE/Servlet/Servlet3.0/\346\226\260\347\211\271\346\200\247-\345\274\202\346\255\245\345\244\204\347\220\206.java" new file mode 100644 index 00000000..c80e1f3b --- /dev/null +++ "b/JAVAEE/Servlet/Servlet3.0/\346\226\260\347\211\271\346\200\247-\345\274\202\346\255\245\345\244\204\347\220\206.java" @@ -0,0 +1,117 @@ +异步处理 +一,回忆异步处理 + 1,什么是异步处理,还记得?原来在服务器还没结束响应之前,客户端浏览器看不到响应的内容,只有响应结束的时候浏览器才能显示结果 + 2,异步处理的作用,在服务器开始响应后,浏览器就可以看到响应内容,不用等待服务器响应结束 + +二,实现异步的步骤 +1,得到AsyncContext,它叫做异步上下文 + AsyncContext ac = request.startAsync(request,response); +2,给上下文一个Runnable对象(别说不认识),启动它 + final AsyncContext ac = request.startAsync(request,response); + ac.start(new Runnable()//匿名内部类 + { + public void run() + { + //异步执行的代码,其实也就是多线程代码 + } + ac.complete(); + }); + * ac.complete();方法详解 + > 这个方法就是告诉服务器,我们的异步交互已经结束了,你可以关闭跟客户端的连接了 + > 因为这个线程是衍生出来的,Tomca主线程根本没办法知道你是不是已经运行完毕了,只有一直等等等,一直到连接超时才断开, + > 这也是为什么,服务器已经响应完毕,而我们的浏览器还在请求状态,还在孤独的旋转! + * 注意,如果不设置响应编码,经常有可能会导致异步失败 + > response.setContentType("text/html;charset=utf-8");//如果不设置编码,经常导致异步失败 + > !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + > 给你一排感叹号,就是提醒你,这东西不设置真的会失败,我已经试过了... ... + > 其实这个重要的不是编码,而是要告诉他text/html + * 关于异步交互,IE有个毛病 + > 当你的输出的数据小余512KB的时候,它就不给你显示,给你存着,等你发完了,就给你一堆弄出来 + > 很多人就以为IE异步不起来,其实我们只要在程序内部,先给它512KB的垃圾数据给它玩儿就好了,照样还是能够异步的 + > 这东西吧,只会影响测试.实际项目不会有问题,都特么用上异步了,这512KB还没啊? + +3,记得给Servlet类头上添加一个注解 + @WebServlet(asyncSupported=true) + > 此注解值==true,那么该页面才能使用这个异步处理 + + @WebFilter(asyncSupported=true) + > 如果使用了Filter,也要添加该注解属性 + +---------------------- +AsyncContext | +---------------------- + # 接口方法 + ServletRequest getRequest(); + ServletResponse getResponse(); + boolean hasOriginalRequestAndResponse(); + + void addListener(AsyncListener listener); + void addListener(AsyncListener listener,ServletRequest servletRequest,ServletResponse servletResponse); + * 添加监听器 + + T createListener(Class clazz); + + + void complete(); + * 完成输出 + + + void dispatch(); + void dispatch(String path); + void dispatch(ServletContext context, String path); + + long getTimeout(); + + void setTimeout(long timeout); + * 设置和获取超时时间 + + void start(Runnable run); + +---------------------- +AsyncEvent | +---------------------- + # 事件 + AsyncContext getAsyncContext() + ServletRequest getSuppliedRequest() + ServletResponse getSuppliedResponse() + Throwable getThrowable() + + +---------------------- +Demo | +---------------------- + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setContentType("text/html;charset=utf-8");//如果不设置编码,经常导致异步失败 + AsyncContext asyncContext = req.startAsync(req, resp); + asyncContext.addListener(new AsyncListener() { + @Override + public void onTimeout(AsyncEvent event) throws IOException { + System.out.println("超时:" + event); + } + @Override + public void onStartAsync(AsyncEvent event) throws IOException { + System.out.println("开始异步输出:" + event); + } + @Override + public void onError(AsyncEvent event) throws IOException { + System.out.println("异常:" + event); + } + @Override + public void onComplete(AsyncEvent event) throws IOException { + System.out.println("执行完毕:" + event); + } + }); + asyncContext.start(() -> { + for(int x = 0 ;x < 10 ; x++) { + try { + asyncContext.getResponse().getWriter().write(x + "
"); + asyncContext.getResponse().getWriter().flush(); + Thread.sleep(1000); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + asyncContext.complete(); + }); + } \ No newline at end of file diff --git "a/JAVAEE/Servlet/Servlet3.0/\346\226\260\347\211\271\346\200\247-\345\274\202\346\255\245\345\244\204\347\220\206.txt" "b/JAVAEE/Servlet/Servlet3.0/\346\226\260\347\211\271\346\200\247-\345\274\202\346\255\245\345\244\204\347\220\206.txt" deleted file mode 100644 index 273aca59..00000000 --- "a/JAVAEE/Servlet/Servlet3.0/\346\226\260\347\211\271\346\200\247-\345\274\202\346\255\245\345\244\204\347\220\206.txt" +++ /dev/null @@ -1,40 +0,0 @@ -异步处理 -一,回忆异步处理 - 1,什么是异步处理,还记得?原来在服务器还没结束响应之前,客户端浏览器看不到响应的内容,只有响应结束的时候浏览器才能显示结果 - 2,异步处理的作用,在服务器开始响应后,浏览器就可以看到响应内容,不用等待服务器响应结束 - -二,实现异步的步骤 -1,得到AsyncContext,它叫做异步上下文 - AsyncContext ac = request.startAsync(request,response); -2,给上下文一个Runnable对象(别说不认识),启动它 - final AsyncContext ac = request.startAsync(request,response); - ac.start(new Runnable()//匿名内部类 - { - public void run() - { - //异步执行的代码,其实也就是多线程代码 - } - ac.complete(); - }); - * ac.complete();方法详解 - > 这个方法就是告诉服务器,我们的异步交互已经结束了,你可以关闭跟客户端的连接了 - > 因为这个线程是衍生出来的,Tomca主线程根本没办法知道你是不是已经运行完毕了,只有一直等等等,一直到连接超时才断开, - > 这也是为什么,服务器已经响应完毕,而我们的浏览器还在请求状态,还在孤独的旋转! - * 注意,如果不设置响应编码,经常有可能会导致异步失败 - > response.setContentType("text/html;charset=utf-8");//如果不设置编码,经常导致异步失败 - > !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - > 给你一排感叹号,就是提醒你,这东西不设置真的会失败,我已经试过了... ... - > 其实这个重要的不是编码,而是要告诉他text/html - * 关于异步交互,IE有个毛病 - > 当你的输出的数据小余512KB的时候,它就不给你显示,给你存着,等你发完了,就给你一堆弄出来 - > 很多人就以为IE异步不起来,其实我们只要在程序内部,先给它512KB的垃圾数据给它玩儿就好了,照样还是能够异步的 - > 这东西吧,只会影响测试.实际项目不会有问题,都特么用上异步了,这512KB还没啊? - -3,记得给Servlet类头上添加一个注解 - @WebServlet(asyncSupported=true) - > 此注解值==true,那么该页面才能使用这个异步处理 - - - - - \ No newline at end of file diff --git "a/JAVAEE/Servlet/Servlet3.0/\346\226\260\347\211\271\346\200\247-\346\226\207\344\273\266\344\270\212\344\274\240\346\224\257\346\214\201.txt" "b/JAVAEE/Servlet/Servlet3.0/\346\226\260\347\211\271\346\200\247-\346\226\207\344\273\266\344\270\212\344\274\240\346\224\257\346\214\201.txt" index 7ce5644f..cc376ff5 100644 --- "a/JAVAEE/Servlet/Servlet3.0/\346\226\260\347\211\271\346\200\247-\346\226\207\344\273\266\344\270\212\344\274\240\346\224\257\346\214\201.txt" +++ "b/JAVAEE/Servlet/Servlet3.0/\346\226\260\347\211\271\346\200\247-\346\226\207\344\273\266\344\270\212\344\274\240\346\224\257\346\214\201.txt" @@ -42,6 +42,14 @@ Part * 获取上传文件的大小 > write(String filename); * 保存文件到指定的路径 + + > getSubmittedFileName() + * 获取文件的名称,就是文件在磁盘系统中的名称 + * servlet3.1新特性 + + > delete() + * 删除文件,包括删除缓存在磁盘的文件 + 四,得到Part Part p = request.getPart("name属性"); @@ -56,10 +64,16 @@ Collection c = request.getParts(); > getName();//获取表单项名称 > write(Strin path);//写入指定路径 > ... ...自己看上面 + 2,默认Servlet是不是支持使用上传组键的,我们需要给Servlet添加一个注解 - > @MultipartConfig + @MultipartConfig + * 不带任何参数,直接复制上去就好 + * 该注解也有一些属性,可以控制上传的文件的大小,本地序列化的目录,请求体的总大小等等 + 六,这东西没有提供获取上传文件名称的方法 * 这个需要我们自己从Content-Disposition头中获取出来,再截取 + * 3.1 版本以后带有了这个API + getSubmittedFileName() \ No newline at end of file diff --git a/JAVAEE/Servlet/Servlet4/HttpServletMapping.java b/JAVAEE/Servlet/Servlet4/HttpServletMapping.java new file mode 100644 index 00000000..2a780f93 --- /dev/null +++ b/JAVAEE/Servlet/Servlet4/HttpServletMapping.java @@ -0,0 +1,20 @@ +------------------- +HttpServletMapping | +------------------- + # 对象获取 + HttpServletMapping httpServletMapping = request.getHttpServletMapping(); + # 接口方法 + public String getMatchValue(); + + public String getPattern(); + + public String getServletName(); + + public MappingMatch getMappingMatch(); + + # MappingMatch 枚举 + CONTEXT_ROOT + DEFAULT + EXACT + EXTENSION + PATH \ No newline at end of file diff --git a/JAVAEE/Servlet/Servlet4/PushBuilder.java b/JAVAEE/Servlet/Servlet4/PushBuilder.java new file mode 100644 index 00000000..a63600ec --- /dev/null +++ b/JAVAEE/Servlet/Servlet4/PushBuilder.java @@ -0,0 +1,48 @@ +------------------- +PushBuilder | +------------------- + # 创建 + PushBuilder pushBuilder = request.newPushBuilder(); + + * 如果服务器推送不可用,newPushBuilder() 将返回 null + * 如果客户端没有使用安全连接,服务器推送也不会起作用 + + # demo + // push 当前页面需要的静态资源 + PushBuilder pushBuilder = req.newPushBuilder(); + if (pushBuilder != null) { + pushBuilder.path("static/images/index.jpg").push(); + pushBuilder.path("static/css/index.css").push(); + pushBuilder.path("static/js/index.js").push(); + } + + # 接口方法 + public PushBuilder method(String method); + + public PushBuilder queryString(String queryString); + + public PushBuilder sessionId(String sessionId); + + public PushBuilder setHeader(String name, String value); + + public PushBuilder addHeader(String name, String value); + + public PushBuilder removeHeader(String name); + + public PushBuilder path(String path); + + public void push(); + * push() 方法是非阻塞的,立即返回 + + public String getMethod(); + + public String getQueryString(); + + public String getSessionId(); + + public Set getHeaderNames(); + + public String getHeader(String name); + + public String getPath(); + diff --git "a/JAVAEE/Servlet/Servlet4/servlet4\345\205\266\344\273\226\346\226\260\347\211\271\346\200\247.java" "b/JAVAEE/Servlet/Servlet4/servlet4\345\205\266\344\273\226\346\226\260\347\211\271\346\200\247.java" new file mode 100644 index 00000000..1dcabe97 --- /dev/null +++ "b/JAVAEE/Servlet/Servlet4/servlet4\345\205\266\344\273\226\346\226\260\347\211\271\346\200\247.java" @@ -0,0 +1,17 @@ + +# 添加了 GenericFilter 和 HttpFilter 抽象类 + * 这些抽象类通过最低限度地实现生命周期方法 init() 和 destroy() + * 简化了编写过滤器 + +# ServletContext 接口采用了一些新方法 + + ServletRegistration.Dynamic addJspFile(String servletName, String jspFile); + * 可将带有给定 JSP 文件的 servlet 添加到 servlet 上下文中 + + int getSessionTimeout(); + void setSessionTimeout(int sessionTimeout); + * 设置和读取session超时时间 + + String getRequestCharacterEncoding(); + void setRequestCharacterEncoding(String encoding); + * 设置和读取默认的request的编码 \ No newline at end of file diff --git a/JAVAEE/Tomcat/tomcat-apr.java b/JAVAEE/Tomcat/tomcat-apr.java new file mode 100644 index 00000000..ae3ebfb1 --- /dev/null +++ b/JAVAEE/Tomcat/tomcat-apr.java @@ -0,0 +1,42 @@ +-------------------- +apr | +-------------------- + # 这个东西可以大大提升Tomcat对静态文件的处理性能,同时如果你使用了HTTPS方式传输的话,也可以提升SSL的处理性能 + + + # 下载依赖 + * 下载地址:http://apr.apache.org/download.cgi + apr-1.6.5.tar.gz + apr-util-1.6.1.tar.gz + apr-iconv-1.2.2.tar.gz + tomcat-native.tar.gz + * 在 ${TOMCAT_HOME}/lib 目录下,不需要下载 + + # 解压安装 apr-1.6.5.tar.gz + tar -xvf apr-1.6.5.tar.gz +    cd apr-1.5.2 + ./configure + make && make install + + # 解压安装 tomcat-native.tar.gz + tar -xvf tomcat-native.tar.gz + cd tomcat-native-1.2.17-src/native + ./configure --with-apr=/usr/local/apr --with-java-home=/usr/local/java/jdk1.8.0_181 + make && make install + + * --with-apr 是安装apr自动生成的安装目录 + * --with-java-home 是自己环境的Java目录 + + # 环境变量 + vim /etc/profile + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/apr/lib + source /etc/profile + + # 修改Tomcat配置 + * 修改server.xml配置文件 + * Connector 节点的 protocol 属性 + protocol="org.apache.coyote.http11.Http11AprProtocol" + +-------------------- +apr-windows | +-------------------- \ No newline at end of file diff --git "a/JAVAEE/Tomcat/\346\220\255\345\273\272HTTPS\346\234\215\345\212\241\345\231\250.java" "b/JAVAEE/Tomcat/\346\220\255\345\273\272HTTPS\346\234\215\345\212\241\345\231\250.java" index e4acfb8e..42a997ec 100644 --- "a/JAVAEE/Tomcat/\346\220\255\345\273\272HTTPS\346\234\215\345\212\241\345\231\250.java" +++ "b/JAVAEE/Tomcat/\346\220\255\345\273\272HTTPS\346\234\215\345\212\241\345\231\250.java" @@ -47,6 +47,12 @@ * OK,在~目录下,会生成 .keystore 一个证书文件 * 至此,证书创建成功 + + * JKS 密钥库使用专用格式,迁移到行业标准格式 PKCS12 + keytool -importkeystore -srckeystore [name].keystore -destkeystore [name].keystore -deststoretype pkcs12 + +输入源密钥库口令: +已成功导入别名 localhost 的条目。 2,配置服务器 * 把 .keystore 文件复制到 $TOMCAT_HOME/conf 目录下 @@ -65,10 +71,32 @@ - + 3, 导出公钥 + keytool -export -alias [alias] -file [name].cer -keystore [name].keystore -storepass [密码] + alias :生成证书时使用的别名 + [name].cer :导出的公钥文件(包含了公钥和证书) + [name].keystore :key.keystore + + 4,导入合作方公钥 + * 通讯双方假设为A和B,A发布了自己的证书并公开了公钥,B所有经过A的公钥加密的报文发送给A后,A可以正确解密,如果A给B发送报文,A用私钥加密,B可以用公钥解密 + * 但这里有一个问题就是公钥是公开的,A发送给B的报文,任何有公钥的人都可以解密,不能保证A向B发送信息的安全性 + * 所以B也需要制作自己的证书,并对A公开自己的公钥,这样A向B发送信息里用B的公钥加密,这样B就可以用私钥解密,而其他截获信息的人因为没有私钥也不能解密 + * A需要将B的公钥导入自己的证书库 + + keytool -import -file [name].cer -keystore [name].keystore -storepass [密码] + * [name].cer 合作方的公钥 + * [name].keystore 本地的keystore + * [密码] 本地keystore的密码 + + * 提示是否信任这个认证,y + * 回车后即可导入然后查看证书库中的证书条目 + keytool -list -v -keystore [name].keystore -storepass [密码] + + 5,删除合作方的公钥 + keytool -delete -alias [公钥] -keystore [keystore文件] + ---------------------------- Tomcat-阿里云 | ---------------------------- - \ No newline at end of file diff --git a/JAVAEE/javaee.java b/JAVAEE/javaee.java new file mode 100644 index 00000000..5e21ff10 --- /dev/null +++ b/JAVAEE/javaee.java @@ -0,0 +1,6 @@ +--------------------- +javaee | +--------------------- + https://javaee.github.io/ + https://projects.eclipse.org/projects/ee4j + https://github.com/eclipse-ee4j diff --git "a/JAVAEE/\344\270\212\344\274\240\344\270\213\350\275\275\344\270\223\351\242\230/\346\230\276\347\244\272\350\277\233\345\272\246\346\235\241\347\232\204\344\270\212\344\274\240.java" "b/JAVAEE/\344\270\212\344\274\240\344\270\213\350\275\275\344\270\223\351\242\230/\346\230\276\347\244\272\350\277\233\345\272\246\346\235\241\347\232\204\344\270\212\344\274\240.java" index efb0ad07..e2178c3c 100644 --- "a/JAVAEE/\344\270\212\344\274\240\344\270\213\350\275\275\344\270\223\351\242\230/\346\230\276\347\244\272\350\277\233\345\272\246\346\235\241\347\232\204\344\270\212\344\274\240.java" +++ "b/JAVAEE/\344\270\212\344\274\240\344\270\213\350\275\275\344\270\223\351\242\230/\346\230\276\347\244\272\350\277\233\345\272\246\346\235\241\347\232\204\344\270\212\344\274\240.java" @@ -20,7 +20,7 @@ if(myXhr.upload){ //监听上传属性的上传事件,每次上传事件都会执行 progressHandlingFunction myXhr.upload.addEventListener('progress',progressHandlingFunction, false); - //myXhr.upload.progress = function(){} 也可以 + //myXhr.upload.onprogress = function(){} 也可以 } //返回给 $.ajax 使用 return myXhr; @@ -64,7 +64,7 @@ function progressHandlingFunction(event) { if(xhr.upload){ //监听上传属性的上传事件,每次上传事件都会执行 progressHandlingFunction xhr.upload.addEventListener('progress',progressHandlingFunction, false); - //xhr.upload.progress = function(){} 也可以 + //xhr.upload.onprogress = function(){} 也可以 } //执行上传 xhr.send(formData); diff --git "a/JAVAEE/\345\206\215\350\260\210\345\210\206\351\241\265.txt" "b/JAVAEE/\345\206\215\350\260\210\345\210\206\351\241\265.java" similarity index 86% rename from "JAVAEE/\345\206\215\350\260\210\345\210\206\351\241\265.txt" rename to "JAVAEE/\345\206\215\350\260\210\345\210\206\351\241\265.java" index 2d1389bf..365bca48 100644 --- "a/JAVAEE/\345\206\215\350\260\210\345\210\206\351\241\265.txt" +++ "b/JAVAEE/\345\206\215\350\260\210\345\210\206\351\241\265.java" @@ -105,3 +105,46 @@ 尾页 + +======================== js算法 + +//预定义开始 +var begin = 0; + +//预定义结束 +var end = 0; + +if(pageInfo.totalPage <= 10){ + begin = 1; + end = pageInfo.totalPage; +}else{ + begin = pageInfo.page - 5; + end = pageInfo.page + 4; + if(begin < 1){ + begin = 1; + end = 10; + } + if(end > pageInfo.totalPage){ + begin = pageInfo.totalPage - 9; + end = pageInfo.totalPage; + } +} + +if(begin > 1){ + //生成首页摁钮 +} +if(pageInfo.page > 1){ + //生成上一页摁钮 +} + +for(let i = begin ; i <= end; i++){ + //生成页码摁钮 +} + +if(pageInfo.totalPage > pageInfo.page){ + //生成下一页摁钮 +} + +if(pageInfo.totalPage > end ){ + //生成尾页摁钮 +} \ No newline at end of file diff --git "a/JAVAEE/\345\233\275\351\231\205\345\214\226.txt" "b/JAVAEE/\345\233\275\351\231\205\345\214\226.txt" index 49857380..1692dd24 100644 --- "a/JAVAEE/\345\233\275\351\231\205\345\214\226.txt" +++ "b/JAVAEE/\345\233\275\351\231\205\345\214\226.txt" @@ -39,4 +39,13 @@ Locale locale = request.getLocale();// //根据客户端的语言环境来创建加载文件的对象 ResourceBundle rb = ResourceBundle.getBundle("res",locale); //这就是获取资源文件中userName的资源!因为Locale是中文,所以加载出来的东西就是中文.如果Locale是英文,那么加载出来就是的英文 -System.out.println(rb.getString("userName"));//打印出来的就是:用户名 \ No newline at end of file +System.out.println(rb.getString("userName"));//打印出来的就是:用户名 + + +获取所有支持的国家地区 + Locale[] localeList = Locale.getAvailableLocales(); + // 遍历数组的每个元素,依次获取所支持的国家和语言 + for (int i = 0; i < localeList.length; i++) { + // 打印出所支持的国家和语言 + System.out.println(localeList[i].getDisplayCountry() + "=" + localeList[i].getCountry() + " " + localeList[i].getDisplayLanguage() + "=" + localeList[i].getLanguage()); + } \ No newline at end of file diff --git "a/JAVASE/JAVA-\345\212\250\346\200\201\347\274\226\350\257\221.java" "b/JAVASE/JAVA-\345\212\250\346\200\201\347\274\226\350\257\221.java" index ff3ecdaf..f953a256 100644 --- "a/JAVASE/JAVA-\345\212\250\346\200\201\347\274\226\350\257\221.java" +++ "b/JAVASE/JAVA-\345\212\250\346\200\201\347\274\226\350\257\221.java" @@ -11,26 +11,26 @@ Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec("javac -cp -D:/MyJava/ HelloWorld.java"); 2,通过JavaCompiler动态的进行编译(6.0以后引入的新功能) - public static int compileFile(String sourceFile){ - //模拟JAVA源文件地址 - sourceFile = "E:/MyJava/HelloWorld.java"; - //获取编译器对象 - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - /* 执行编译操作 - 第一个参数inputStream :为JAVA编译器提供参数 - 第二个参数outputStream :得到JAVA编译器的输出信息 - 第三个参数OutputStream :接收编译器的错误信息 - 第四个参数:可变参数(String[]),能传入一个或者多个JAVA源文件的路径 - 返回值:0表示成功编译,非0表示编译失败 - */ - int result = compiler.run(null, System.out, System.out, sourceFile); - if(result == 0){ - System.out.println("编译成功"); - }else{ - System.out.println("编译失败"); - } - return result; - } +public static int compileFile(String sourceFile){ + //模拟JAVA源文件地址 + sourceFile = "E:/MyJava/HelloWorld.java"; + //获取编译器对象 + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + /* 执行编译操作 + 第一个参数inputStream :为JAVA编译器提供参数 + 第二个参数outputStream :得到JAVA编译器的输出信息 + 第三个参数OutputStream :接收编译器的错误信息 + 第四个参数:可变参数(String[]),能传入一个或者多个JAVA源文件的路径 + 返回值:0表示成功编译,非0表示编译失败 + */ + int result = compiler.run(null, System.out, System.out, sourceFile); + if(result == 0){ + System.out.println("编译成功"); + }else{ + System.out.println("编译失败"); + } + return result; +} -------------------- JAVA-动态运行 | diff --git "a/JAVASE/JAVA-\350\204\232\346\234\254\345\274\225\346\223\216.java" "b/JAVASE/JAVA-\350\204\232\346\234\254\345\274\225\346\223\216.java" deleted file mode 100644 index b6cb7b1f..00000000 --- "a/JAVASE/JAVA-\350\204\232\346\234\254\345\274\225\346\223\216.java" +++ /dev/null @@ -1,46 +0,0 @@ --------------------- -JAVA-脚本引擎 | --------------------- - # 其实就是在JAVA代码中执行JavaScript的代码 - # JAVA只是提供了这个接口,但是没提供实现,由其他的脚本厂商自己编写实现. - # 案例 - 1,客户端传递了一个字符串 - "3 * 4 / 2 + 6 -5" - 2,该字符串应该用于计算最后的结果 - 3,如果是使用JAVA来完成,就比较的麻烦 - 4,如果是JavaScript那就简单 eval();函数就搞定 - # 脚本引擎是JDK6.0以后添加的新功能 - * 其实就是,JAVA应用程序可以通过一套固定的接口与'各种脚本引擎'交互 - * 从而达到在Java平台上调用各种脚本语言的目的 - * Java脚本API是连通Java平台好脚本语言的桥梁 - * 可以把一些复杂异变的业务逻辑交给脚本语言处理,这也大大提高了开发效率 - * 其实就是可以在java程序中执行其他的脚本语言 - * 6.0后javascript-->Rhino被添加到了JDK - # 获取脚本引擎对象 - ScriptEngineManager sem = new ScriptEngineManager(); - # Java脚本API为开发者提供了N多功能 - * 获取脚本程序输入,通过脚本引擎运行脚本并返回运行结果. - * 核心的接口 : ScriptEngineFactory - 1,注意:是接口,JAVA可以使用各种不同的实现,从而调用js,groovy,python等脚本 - 2,JavasScript --> RhinoEngineFactory - 3,Rhino 是一种使用java语言编写的javascript代码 - * Rhino <>,这本书封面就是Rhino. - # Demo - public static void demo() throws ScriptException{ - ScriptEngineManager sem = new ScriptEngineManager(); - //获取JavaScript的脚本引擎 - ScriptEngine engine = sem.getEngineByName("javascript"); - /* - 定义一个变量,存储到引擎上下文中 - Java可以获取到,引擎也可以获取到 - */ - engine.put("key", "value"); - /* - 定义一串JavaScript脚本语言 - */ - String str = "var user = {name:'KevinBlandy',age:22,gender:'男'};"; - engine.eval(str); - } - - - # 还可以去执行或者加载,URL上的,本地的JS代码文件 diff --git a/Java/annotation/AbstractProcessor.java b/Java/annotation/AbstractProcessor.java new file mode 100644 index 00000000..b6a2238f --- /dev/null +++ b/Java/annotation/AbstractProcessor.java @@ -0,0 +1,8 @@ +------------------------ +AbstractProcessor | +------------------------ + # JDK6提供的一个抽象类 + * 有一个抽象方法 + public abstract boolean process(Set annotations, RoundEnvironment roundEnv); + + * 可以在编译的时候, 执行自定义的代码 diff --git a/Java/concurrent/Java-juc-ThreadLocalRandom.java b/Java/concurrent/Java-juc-ThreadLocalRandom.java new file mode 100644 index 00000000..1731b066 --- /dev/null +++ b/Java/concurrent/Java-juc-ThreadLocalRandom.java @@ -0,0 +1,12 @@ +------------------------------------- +ThreadLocalRandom | +------------------------------------- + # ThreadLocalRandom类是JDK7在JUC包下新增的随机数生成器 + # 它解决了Random类在多线程下多个线程竞争内部唯一的原子性种子变量而导致大量线程自旋重试的不足 + + # 创建实例 + ThreadLocalRandom random = ThreadLocalRandom.current(); + + + # 实例方法 + int nextInt(int bound); diff --git a/Java/concurrent/java-juc-AtomicInteger.java b/Java/concurrent/atomic/AtomicInteger.java similarity index 100% rename from Java/concurrent/java-juc-AtomicInteger.java rename to Java/concurrent/atomic/AtomicInteger.java diff --git a/Java/concurrent/atomic/AtomicIntegerFieldUpdater.java b/Java/concurrent/atomic/AtomicIntegerFieldUpdater.java new file mode 100644 index 00000000..d7d3d6dd --- /dev/null +++ b/Java/concurrent/atomic/AtomicIntegerFieldUpdater.java @@ -0,0 +1,86 @@ +-------------------------------------- +AtomicIntegerFieldUpdater | +-------------------------------------- + # Integer 字段的原子修改器 + * 它是一个抽象类 + * 主要就是通过该修改器去修改指定对象的指定 int 字段值(使用反射) + * 在多线程环境下,这种修改是原子性的(CAS算法) + + # 静态的工厂方法 + AtomicIntegerFieldUpdater newUpdater(Class tclass, String fieldName) + * tclass 指定类 + * fieldName 字段的属性必须是 int,不能是 Integer,而且不能是 static 的 + * 注意,该字段必须使用 protected 及其以上的权限修饰符,并且必须添加:volatile 修饰符 + + # 实例方法 + int accumulateAndGet(T obj, int x, IntBinaryOperator accumulatorFunction) + int addAndGet(T obj, int delta) + + boolean compareAndSet(T obj, int expect, int update) + * obj 对象 + * expect 原始值 + * update 新的值 + * 如果修改成功,返回 true + + int decrementAndGet(T obj) + + int get(T obj) + int getAndAccumulate(T obj, int x,IntBinaryOperator accumulatorFunction) + int getAndAdd(T obj, int delta) + int getAndDecrement(T obj) + int getAndIncrement(T obj) + int getAndSet(T obj, int newValue) + int getAndUpdate(T obj, IntUnaryOperator updateFunction) + + int incrementAndGet(T obj) + void lazySet(T obj, int newValue) + void set(T obj, int newValue) + int updateAndGet(T obj, IntUnaryOperator updateFunction) + boolean weakCompareAndSet(T obj, int expect, int update) + + + # demo + class Foo { + protected volatile int value = 0; + public int getValue() { + return value; + } + public void setValue(int value) { + this.value = value; + } + } + + public static void casUpdate(Foo foo) { + AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(Foo.class, "value"); + for (int x = 0 ;x < 100 ;x ++) { + new Thread(() -> { + while(true) { + // 原始值 + int value = foo.getValue(); + // 新值 + int newValue = value + 1; + // 原子更新 + boolean result = updater.compareAndSet(foo, value, newValue); + if(result) { + break; // 更新失败,进行自旋 + } + } + }) .start(); + } + } + + +-------------------------------------- +还提供的其他修改器 | +-------------------------------------- + # 几乎都是相同的aip,主要是针对的字段类型不同 + + AtomicReferenceFieldUpdater + AtomicReferenceFieldUpdater newUpdater(Class tclass, Class vclass,String fieldName) + + * 对象引用字段的原子修改器 + + AtomicLongFieldUpdater + AtomicLongFieldUpdater newUpdater(Class tclass,String fieldName) + + * long 属性字段的原子修改器 diff --git a/Java/concurrent/atomic/AtomicReference.java b/Java/concurrent/atomic/AtomicReference.java new file mode 100644 index 00000000..65eeb451 --- /dev/null +++ b/Java/concurrent/atomic/AtomicReference.java @@ -0,0 +1,6 @@ +------------------------ +AtomicReference | +------------------------ + # 可以把指定对象的所有操作, 都转换为原子操作 + + diff --git a/Java/concurrent/atomic/AtomicStampedReference.java b/Java/concurrent/atomic/AtomicStampedReference.java new file mode 100644 index 00000000..caa9a353 --- /dev/null +++ b/Java/concurrent/atomic/AtomicStampedReference.java @@ -0,0 +1,22 @@ +------------------------ +AtomicStampedReference | +------------------------ + # jdk1.5后的新CAS类,解决了ABA的问题 + # 构造函数 + AtomicStampedReference(V initialRef, int initialStamp) + + + # 实例方法 + + boolean attemptStamp(V expectedReference, int newStamp) + boolean compareAndSet(V expectedReference, V newReference,int expectedStamp, int newStamp) + * 首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志 + * 如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值 + * 解决了CAS中'ABA'的问题 + + V get(int[] stampHolder) + V getReference() + int getStamp() + void set(V newReference, int newStamp) + boolean weakCompareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) + diff --git a/Java/concurrent/atomic/LongAdder.java b/Java/concurrent/atomic/LongAdder.java new file mode 100644 index 00000000..c111cc19 --- /dev/null +++ b/Java/concurrent/atomic/LongAdder.java @@ -0,0 +1,33 @@ +------------------------- +LongAdder | +------------------------- + # 这东西JDK8才有的东西, 用于替代:AtomicLong + * 内部的实现有点类似 ConcurrentHashMap 的分段锁 + * 最好的情况下, 每个线程都有独立的计数器, 这样可以大量减少并发操作 + + # AtomicLong 的问题 + * 为了实现正确的累加操作, 如果并发量很大的话,cpu会花费大量的时间在试错上面 + * 相当于一个spin(自旋)的操作, 如果并发量小的情况, 这些消耗可以忽略不计 + + + # 构造函数 + LongAdder() + + # 实例方法 + void add(long x) + void decrement() + double doubleValue() + float floatValue() + void increment() + int intValue() + longValue() + reset() + long sum() + long sumThenReset() + + # 总结 + * 单线程环境, AtomicLong 性能比 LongAdder 好 + * 一些高并发的场景, 比如限流计数器, 建议使用 LongAdder 替换 AtomicLong, 性能可以提升不少 + + # JDK还提供了一些其他类型的原子自增/自减 + DoubleAdder \ No newline at end of file diff --git a/Java/concurrent/atomic/atomic.java b/Java/concurrent/atomic/atomic.java new file mode 100644 index 00000000..4433845a --- /dev/null +++ b/Java/concurrent/atomic/atomic.java @@ -0,0 +1,40 @@ +---------------------------- +Atomic | +---------------------------- + # java.util.concurrent.atomic 包下提供的一些原子类 + + # 使用原子的方式更新基本类型 + AtomicInteger + * 整型原子类 + AtomicLong + * 长整型原子类 + AtomicBoolean + * 布尔型原子类 + + # 数组类型 + * 使用原子的方式更新数组里的某个元素 + + AtomicIntegerArray + * 整型数组原子类 + AtomicLongArray + * 长整型数组原子类 + AtomicReferenceArray + * 引用类型数组原子类 + + # 引用类型 + AtomicReference + * 引用类型原子类 + + AtomicMarkableReference + * 原子更新带有标记位的引用类型 + + # 对象的属性修改类型 + + AtomicIntegerFieldUpdater + * 原子更新整型字段的更新器 + AtomicLongFieldUpdater + * 原子更新长整型字段的更新器 + AtomicStampedReference + * 原子更新带有版本号的引用类型 + * 该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 + diff --git a/Java/concurrent/java-juc-ConcurrentHashMap.java b/Java/concurrent/java-juc-ConcurrentHashMap.java index b09df968..b995e90a 100644 --- a/Java/concurrent/java-juc-ConcurrentHashMap.java +++ b/Java/concurrent/java-juc-ConcurrentHashMap.java @@ -1,14 +1,118 @@ ---------------------------- ConcurrentHashMap | ---------------------------- - # 锁分段机制 + # JDK1.8以前使用 Segment 分段锁 + * Segment 继承于 ReentrantLock,不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理 + * 理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发,每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment * 默认有 16 个段(Segment) * 每个段中有 16 个元素 + * 但是仍然存在,查询遍历链表效率太低的问题 - # JDK1.8后,ConcurrentHashMap 底层也采用了 CAS 算法 - # 几个 Map 之间的比较 - * Hashtable 线程安全,但是锁了整张表,效率低 - * HashMap 线程不安全,效率高 - * ConcurrentHashMap 线程安全,采用锁分段机制,效率较高 + # JDK1.8后,其中抛弃了原有的 Segment 分段锁,而采用了 CAS(乐观锁) + synchronized 来保证并发安全性 + * Hash冲突的链表在满足一定条件后会转换为红黑树 + * 取消了 ReentrantLock 改为了 synchronized(可以看出在新版的 JDK 中对 synchronized 优化是很到位的) + + + # PUT 过程 + final V putVal(K key, V value, boolean onlyIfAbsent) { + if (key == null || value == null) throw new NullPointerException(); + int hash = spread(key.hashCode()); + int binCount = 0; + for (Node[] tab = table;;) { // 1 + Node f; int n, i, fh; + if (tab == null || (n = tab.length) == 0)// 2 + tab = initTable(); + else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// 3 + if (casTabAt(tab, i, null, + new Node(hash, key, value, null))) + break; // no lock when adding to empty bin + } + else if ((fh = f.hash) == MOVED)// 4 + tab = helpTransfer(tab, f); + else { + V oldVal = null; + synchronized (f) {// 5 + if (tabAt(tab, i) == f) { + if (fh >= 0) { + binCount = 1; + for (Node e = f;; ++binCount) { + K ek; + if (e.hash == hash && + ((ek = e.key) == key || + (ek != null && key.equals(ek)))) { + oldVal = e.val; + if (!onlyIfAbsent) + e.val = value; + break; + } + Node pred = e; + if ((e = e.next) == null) { + pred.next = new Node(hash, key, + value, null); + break; + } + } + } + else if (f instanceof TreeBin) { + Node p; + binCount = 2; + if ((p = ((TreeBin)f).putTreeVal(hash, key, + value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) + p.val = value; + } + } + } + } + if (binCount != 0) { + if (binCount >= TREEIFY_THRESHOLD) // 6 + treeifyBin(tab, i); + if (oldVal != null) + return oldVal; + break; + } + } + } + addCount(1L, binCount); + return null; + } + + 1 根据 key 计算出 hashcode + 2 判断是否需要进行初始化 + 3 f 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功 + 4 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容 + 5 如果都不满足,则利用 synchronized 锁写入数据 + 6 如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树 + + # GET过程 + public V get(Object key) { + Node[] tab; Node e, p; int n, eh; K ek; + int h = spread(key.hashCode()); // 1 + if ((tab = table) != null && (n = tab.length) > 0 && + (e = tabAt(tab, (n - 1) & h)) != null) { + if ((eh = e.hash) == h) { + if ((ek = e.key) == key || (ek != null && key.equals(ek))) + return e.val; // 2 + } + else if (eh < 0) + return (p = e.find(h, key)) != null ? p.val : null; + while ((e = e.next) != null) { + if (e.hash == h && + ((ek = e.key) == key || (ek != null && key.equals(ek)))) + return e.val; //3 + } + } + return null; + } + + 1 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值 + 2 如果是红黑树那就按照树的方式获取值 + 3 就不满足那就按照链表的方式遍历获取值 + + # 静态方法 + KeySetView newKeySet() + KeySetView newKeySet(int initialCapacity) + \ No newline at end of file diff --git a/Java/concurrent/java-juc-CopyOnWriteArrayList.java b/Java/concurrent/java-juc-CopyOnWriteArrayList.java index e5f24ae7..af07aeb6 100644 --- a/Java/concurrent/java-juc-CopyOnWriteArrayList.java +++ b/Java/concurrent/java-juc-CopyOnWriteArrayList.java @@ -5,4 +5,16 @@ * 添加操作多的时候,效率低 # 写入并复制 - * 每次写入数据的时候,都会在低层复制出新的一个列表,再进行添加 \ No newline at end of file + * 每次写入数据的时候,都会在低层复制出新的一个列表,再进行添加 + + # 它要处理的问题 + * Java的list在遍历时,若中途有别的线程对list容器进行修改,则会抛出 ConcurrentModificationException 异常 + * 而 CopyOnWriteArrayList 由于其"读写分离"的思想,遍历和修改操作分别作用在不同的list容器 + * 所以在使用迭代器进行遍历时候,也就不会抛出 ConcurrentModificationException 异常了 + + * 一句话,它就是为了解决在高并发下,执行修改不会抛出异常 + + # 它带来的问题 + * 一是内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁GC + * 二是无法保证实时性,Vector对于读写操作均加锁同步,可以保证读和写的强一致性 + * 而 CopyOnWriteArrayList 由于其实现策略的原因,写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据 diff --git a/Java/concurrent/java-juc-CountDownLatch.java b/Java/concurrent/java-juc-CountDownLatch.java index 2b11c604..9f817c7a 100644 --- a/Java/concurrent/java-juc-CountDownLatch.java +++ b/Java/concurrent/java-juc-CountDownLatch.java @@ -2,14 +2,38 @@ CountDownLatch | ---------------------------- # 在完成某些运算的时候,只有其他的运算全部完成,当前运算才会执行 + # 和 CyclicBarrier 的区别 + CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行 + * 重点是一个线程(多个线程_等待,而其他的N个线程在完成某件事情之后,可以终止,也可以等待 + + CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行 + * 重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待 + # 创建 CountDownLatch countDownLatch = new CountDownLatch(5); * 创建的时候,就指定一个基数 # 方法 - countDownLatch.countDown(); + countDown(); * 在基数上减1,当值为0的时候,countDownLatch await(); - + * 阻塞,等待其他线程执行完,直到基数 == 0 + * 其实本身就是个循环检测 + + boolean await(long timeout, TimeUnit unit) + * 设置超时时间 + + long getCount() + * 返回count + + # demo + + CountDownLatch countDownLatch = new CountDownLatch(5); + + //启动多线程执行,当有线程执行完毕后,执行该api + countDownLatch.countDown(); - + + + //主线程阻塞,直到 countDownLatch 基数 == 0 + countDownLatch.await(); \ No newline at end of file diff --git a/Java/concurrent/java-juc-CyclicBarrier.java b/Java/concurrent/java-juc-CyclicBarrier.java new file mode 100644 index 00000000..d8639083 --- /dev/null +++ b/Java/concurrent/java-juc-CyclicBarrier.java @@ -0,0 +1,105 @@ +------------------------ +CyclicBarrier | +------------------------ + # (循环栅栏)和 CountDownLatch 非常类似,它也可以实现线程间的技术等待 + # 但是它的功能比 CountDownLatch 更加复杂和强大,主要应用场景和 CountDownLatch 类似 + * 可以用于多线程计算数据,最后合并计算结果的应用场景 + + # 和 CountDownLatch 的区别 + CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行 + * 重点是一个线程(多个线程_等待,而其他的N个线程在完成某件事情之后,可以终止,也可以等待 + * 不能重复使用 + + CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行 + * 重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待 + * 可以重复使用 + + # 构建方法 + CyclicBarrier(int parties) + CyclicBarrier(int parties, Runnable barrierAction) + + * 需要同步的线程数量:parties + * barrierAction 线程到达屏障(parties)时,优先执行barrierAction(插队的,而且不会计算parties) + + # 实例方法 + int await() + * 当前线程等待 + * 当有 parties 个线程都处于await() 状态时,大家一起唤醒 + + int await(long timeout, TimeUnit unit) + + int getNumberWaiting() + * 获取正在 await() 的线程数量 + + int getParties() + + boolean isBroken() + + void reset() + * 重置 + + +------------------------ +Demo | +------------------------ +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class Main { + public static void main(String[] args) { + CyclicBarrier cyclicBarrier = new CyclicBarrier(5,() -> { + System.out.println("====================紧急执行===================="); + }) ; + + ExecutorService threadPool = Executors.newFixedThreadPool(10); + + for(int x = 0 ; x < 10 ; x++) { + + final int numer = x; + + try { + Thread.sleep(1000); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + + threadPool.execute(() -> { + try { + System.out.println("线程:" + numer + " await"); + cyclicBarrier.await(); + System.out.println("线程:" + numer + " notify"); + } catch (InterruptedException | BrokenBarrierException e) { + e.printStackTrace(); + } + }); + } + + threadPool.shutdown(); + } +} +/** +线程:0 await +线程:1 await +线程:2 await +线程:3 await +线程:4 await +====================紧急执行==================== +线程:4 notify +线程:0 notify +线程:2 notify +线程:1 notify +线程:3 notify +线程:5 await +线程:6 await +线程:7 await +线程:8 await +线程:9 await +====================紧急执行==================== +线程:9 notify +线程:5 notify +线程:7 notify +线程:6 notify +线程:8 notify +**/ \ No newline at end of file diff --git a/Java/concurrent/java-juc-forkjoin.java b/Java/concurrent/java-juc-ForkJoinPool.java similarity index 50% rename from Java/concurrent/java-juc-forkjoin.java rename to Java/concurrent/java-juc-ForkJoinPool.java index 86393849..3c58f69a 100644 --- a/Java/concurrent/java-juc-forkjoin.java +++ b/Java/concurrent/java-juc-ForkJoinPool.java @@ -6,7 +6,12 @@ # 采用工作窃取模式 + + # 使用ForkJoin框架,必须先创建一个ForkJoin任务 + * 它提供在任务中执行fork()和join的操作机制,通常不直接继承ForkjoinTask类,只需要直接继承其子类 + # 结构体系 + ForkJoinPool ForkJoinTask * 接口 |-RecursiveAction @@ -16,6 +21,14 @@ |-RecursiveTask * 带返回值的抽象类 protected abstract V compute(); - + - +------------------------------- +ForkJoinPool | +------------------------------- +------------------------------- +ForkJoinTask | +------------------------------- + ForkJoinTask fork() + V join() + V invoke() \ No newline at end of file diff --git a/Java/concurrent/java-juc-Semaphore.java b/Java/concurrent/java-juc-Semaphore.java new file mode 100644 index 00000000..37ceb22a --- /dev/null +++ b/Java/concurrent/java-juc-Semaphore.java @@ -0,0 +1,36 @@ +---------------------------- +Semaphore | +---------------------------- + # 主要的作用就是控制访问资源的线程数量 + + # 构造函数 + Semaphore(int permits, boolean fair) + Semaphore(int permits) + + * permits 资源同时访问线程的最大数量 + * fair 是否是公平锁 + + # 函数 + void acquire() + * 获取一个令牌,如果不足,则阻塞 + void acquire(int permits) + * 尝试获取N个令牌,如果不足,则阻塞 + void acquireUninterruptibly() + * 尝试获取一个令牌 + void acquireUninterruptibly(int permits) + * 尝试获取N个令牌,可能会抛出中断异常 + + int availablePermits() + int drainPermits() + int getQueueLength() + + boolean hasQueuedThreads() + boolean isFair() + + void release() + void release(int permits) + + boolean tryAcquire() + boolean tryAcquire(int permits) + boolean tryAcquire(int permits, long timeout, TimeUnit unit) + boolean tryAcquire(long timeout, TimeUnit unit) \ No newline at end of file diff --git a/Java/concurrent/java-juc-executor-Executors.java b/Java/concurrent/java-juc-executor-Executors.java new file mode 100644 index 00000000..210f4fc6 --- /dev/null +++ b/Java/concurrent/java-juc-executor-Executors.java @@ -0,0 +1,47 @@ +-------------------- +Executors | +-------------------- + # 创建各种线程池的工厂类 + ExecutorService newFixedThreadPool(int nThreads); + * 创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小 + * 线程池的大小一旦达到最大值就会保持不变 + * 如果某个线程因为执行异常而结束,那么线程池会补充一个新线程 + * 的面使用的 LinkedBlockingQueue 是一个无边界队列,如果不断的往里加任务,最终会导致内存的不可控 + + ExecutorService newCachedThreadPool(); + * 创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程 + * 当任务数增加时,此线程池又添加新线程来处理任务 + + ExecutorService newSingleThreadExecutor() + * 一个单线程的线程池,这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务 + * 如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它 + * 此线程池保证所有任务的执行顺序按照任务的提交顺序执行 + + ScheduledExecutorService newScheduledThreadPool(int corePoolSize) + * 创建调度线程池 + * 创建固定大小的线程池,可以延时/重复的执行任务调度 + + ExecutorService newWorkStealingPool() + * jdk1.8新提供的 + * 会根据所需的并行层次来动态创建和关闭线程,通过使用多个队列减少竞争 + * 底层用的 ForkJoinPool 来实现的 + * ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个"小任务" + * 把多个"小任务"放到多个处理器核心上并行执行,当多个"小任务"执行完成之后,再将这些执行结果合并起来即可 + + + # 五种线程池的使用场景 + newSingleThreadExecutor + 一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行 + + newFixedThreadPool + 一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制 + + newCachedThreadPool + 一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务 + + newScheduledThreadPool + 可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景 + + newWorkStealingPool + 一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行 + diff --git a/Java/concurrent/java-juc-executor-ScheduledThreadPoolExecutor.java b/Java/concurrent/java-juc-executor-ScheduledThreadPoolExecutor.java new file mode 100644 index 00000000..58ffff64 --- /dev/null +++ b/Java/concurrent/java-juc-executor-ScheduledThreadPoolExecutor.java @@ -0,0 +1,33 @@ +-------------------------------- +ScheduledThreadPoolExecutor | +-------------------------------- + # 处理定时任务的线程池,继承:ThreadPoolExecutor 实现:ScheduledExecutorService + # 构造方法 + ScheduledThreadPoolExecutor(int corePoolSize) + ScheduledThreadPoolExecutor(int corePoolSize,RejectedExecutionHandler handler) + ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory) + ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory,RejectedExecutionHandler handler) + + # 实例方法 + void execute(Runnable command) + boolean getContinueExistingPeriodicTasksAfterShutdownPolicy() + boolean getExecuteExistingDelayedTasksAfterShutdownPolicy() + BlockingQueue getQueue() + boolean getRemoveOnCancelPolicy() + + ScheduledFuture schedule(Runnable command,long delay,TimeUnit unit) + ScheduledFuture schedule(Callable callable,long delay, TimeUnit unit) + * * 在指定延迟后,执行command/callable + + ScheduledFuture scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit) + ScheduledFuture scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit) + + void setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean value) + void setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean value) + void setRemoveOnCancelPolicy(boolean value) + + void shutdown() + List shutdownNow() + Future submit(Runnable task) + Future submit(Runnable task, T result) + Future submit(Callable task) diff --git a/Java/concurrent/java-juc-executor-ThreadFactory.java b/Java/concurrent/java-juc-executor-ThreadFactory.java new file mode 100644 index 00000000..93268358 --- /dev/null +++ b/Java/concurrent/java-juc-executor-ThreadFactory.java @@ -0,0 +1,6 @@ +---------------------------------------- +ThreadFactory | +---------------------------------------- + # 线程工厂类接口 + # 抽象方法 + Thread newThread(Runnable r); diff --git a/Java/concurrent/java-juc-executor-ThreadPoolExecutor.java b/Java/concurrent/java-juc-executor-ThreadPoolExecutor.java new file mode 100644 index 00000000..a82e83c6 --- /dev/null +++ b/Java/concurrent/java-juc-executor-ThreadPoolExecutor.java @@ -0,0 +1,156 @@ +--------------------------- +ThreadPoolExecutor | +--------------------------- + # 线程池的实现 + # 构造方法 + ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue) + ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler) + ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory) + ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) + + * corePoolSize + * 线程池基本的线程数量 + + * maximumPoolSize + * 线程池最大线程数量 + + * keepAliveTime + * 超出 corePoolSize 后创建的线程, 空闲后的存活时间 + * 超过时间后就会被回收 + + * unit + * 存活时间的单位 + + * workQueue + * 存放任务的阻塞队列 + * 如果 workQueue 是无界的, 那么永远不会触发 maximumPoolSize, 自然keepAliveTime也就没有了意义 + + * threadFactory + * 线程池工厂类 + + * handler + * 在线程池无法处理新任务时的处理handler + + # 实例方法 + void allowCoreThreadTimeOut(boolean value) + boolean allowsCoreThreadTimeOut() + int getCorePoolSize() + boolean awaitTermination(long timeout, TimeUnit unit) + + + int getActiveCount() + long getCompletedTaskCount() + long getKeepAliveTime(TimeUnit unit) + int getLargestPoolSize() + int getMaximumPoolSize() + int getPoolSize() + BlockingQueue getQueue() + RejectedExecutionHandler getRejectedExecutionHandler() + long getTaskCount() + ThreadFactory getThreadFactory() + boolean isTerminated() + * 如果队列中的所有任务都处理完毕后返回 true + + boolean isTerminating() + int prestartAllCoreThreads() + boolean prestartCoreThread() + void purge() + boolean remove(Runnable task) + void setCorePoolSize(int corePoolSize) + void setKeepAliveTime(long time, TimeUnit unit) + void setMaximumPoolSize(int maximumPoolSize) + void setRejectedExecutionHandler(RejectedExecutionHandler handler) + void setThreadFactory(ThreadFactory threadFactory) + + boolean isShutdown() + void shutdown() + * 会等到所有任务完成才会关闭 + + List shutdownNow() + * 立即关闭线程池 + * 对正在执行的任务全部发出interrupt(),停止执行 + * 对还未开始执行的任务全部取消,并且返回还没开始的任务列表 + + void execute(Runnable command) + Future submit(Runnable task) + * 执行任务,submit可以获取到执行结果的返回值和抛出的异常 + + Future submit(Runnable task, T result) + Future submit(Callable task) + T invokeAny(Collection> tasks,long timeout, TimeUnit unit) + T invokeAny(Collection> tasks) + List> invokeAll(Collection> tasks,long timeout, TimeUnit unit) + List> invokeAll(Collection> tasks) + + # 内部类 + * 他们都是 ThreadPoolExecutor 的实现类,负责处理线程池无法执行新任务时的情况 + + AbortPolicy + * 无法处理时抛出异常 + * 源码 + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { + throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); + } + + CallerRunsPolicy + * 直接运行新任务 + * 源码 + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { + if (!e.isShutdown()) { + r.run(); + } + } + + DiscardOldestPolicy + * 丢弃队列中最老的任务 + * 源码 + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { + if (!e.isShutdown()) { + e.getQueue().poll(); + e.execute(r); + } + } + + DiscardPolicy + * 丢弃新任务 + * 源码 + public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { + } + +--------------------------- +RejectedExecutionHandler | +--------------------------- + # ThreadPoolExecutor 无法处理新任务时的处理Handler 接口 + # 抽象方法 + void rejectedExecution(Runnable r, ThreadPoolExecutor executor); + + +--------------------------- +线程池参数的详解 | +--------------------------- + corePoolSize + * 如果运行的线程少于 corePoolSize, 则创建新线程来处理任务, 即使线程池中的其他线程是空闲的 + * 如果线程池中的线程数量大于等于 corePoolSize 且小于 maximumPoolSize , 则只有当 workQueue 满时才创建新的线程去处理任务 + + * 如果设置的 corePoolSize 和 maximumPoolSize 相同, 则创建的线程池的大小是固定的, + * 这时如果有新任务提交, 若workQueue未满, 则将请求放入workQueue中, 等待有空闲的线程去从workQueue中取任务并处理 + + * 如果运行的线程数量大于等于 maximumPoolSize, 这时如果workQueue已经满了, 则通过handler所指定的策略来处理任务; + + maximumPoolSize + * 最大线程数, 当线程数 >= corePoolSize 的时候, 会把runnable放入workQueue中 + + workQueue + * 等待队列, 当任务提交时, 如果线程池中的线程数量大于等于 corePoolSize 的时候, 把该任务封装成一个 Worker 对象放入等待队列 + + 1, 当线程数小于 corePoolSize 时, 创建线程执行任务 + + 2, 当线程数大于等于 corePoolSize 并且 workQueue 没有满时, 放入 workQueue 中 + + 3, 线程数大于等于 corePoolSize 并且当 workQueue 满时, 新任务新建线程运行, 线程总数要小于 maximumPoolSize + * 超出了 corePoolSize 而创建的线程, 如果空闲时间超过了:keepAliveTime 就会被回收 + + 4, 当线程总数等于 maximumPoolSize 并且 workQueue 满了的时候执行 handler 的 rejectedExecution 也就是拒绝策略 + + + \ No newline at end of file diff --git a/Java/concurrent/java-juc-executor.java b/Java/concurrent/java-juc-executor.java new file mode 100644 index 00000000..f41cfcc3 --- /dev/null +++ b/Java/concurrent/java-juc-executor.java @@ -0,0 +1,12 @@ +---------------------------- +线程池工具 | +---------------------------- + # 体系 + Executor(接口) + |-ExecutorService(接口) + |-AbstractExecutorService(抽象类) + |-ThreadPoolExecutor(实现) + |-ScheduledExecutorService(接口,负责线程调度) + |-ScheduledThreadPoolExecutor(继承了ThreadPoolExecutor又实现了ScheduledExecutorService) + Executors(工具/工厂类) + \ No newline at end of file diff --git a/Java/concurrent/java-juc-future-CompletableFuture.java b/Java/concurrent/java-juc-future-CompletableFuture.java new file mode 100644 index 00000000..bb2585e5 --- /dev/null +++ b/Java/concurrent/java-juc-future-CompletableFuture.java @@ -0,0 +1,126 @@ +------------------------------- +CompletableFuture | +------------------------------- + # jdk 1.8 提供的对于 Future 的实现 + CompletableFuture implements Future, CompletionStage + + # 支持以回调的形式去处理执行结果,而不用需要通过阻塞当前线程来获取执行结果 + + # 构造函数 + CompletableFuture() + + # 静态工厂函数 + CompletableFuture allOf(CompletableFuture... cfs) + CompletableFuture anyOf(CompletableFuture... cfs) + CompletableFuture completedFuture(U value) + + CompletableFuture runAsync(Runnable runnable) + CompletableFuture runAsync(Runnable runnable,Executor executor) + + CompletableFuture supplyAsync(Supplier supplier) + CompletableFuture supplyAsync(Supplier supplier,Executor executor) + + # 实例方法 + CompletableFuture acceptEither(CompletionStage other, Consumer action) + CompletableFuture acceptEitherAsync(CompletionStage other, Consumer action) + CompletableFuture acceptEitherAsync(CompletionStage other, Consumer action,Executor executor) + CompletableFuture runAfterEither(CompletionStage other,Runnable action) + CompletableFuture applyToEither(CompletionStage other, Function fn) + CompletableFuture applyToEitherAsync(CompletionStage other, Function fn) + CompletableFuture applyToEitherAsync(CompletionStage other, Function fn,Executor executor) + + boolean cancel(boolean mayInterruptIfRunning) + boolean complete(T value) + boolean completeExceptionally(Throwable ex) + CompletableFuture exceptionally(Function fn) + T get() + T get(long timeout, TimeUnit unit) + T getNow(T valueIfAbsent) + int getNumberOfDependents() + CompletableFuture handle(BiFunction fn) + CompletableFuture handleAsync(BiFunction fn) + CompletableFuture handleAsync(BiFunction fn, Executor executor) + + boolean isCancelled() + boolean isCompletedExceptionally() + + boolean isDone() + T join() + void obtrudeException(Throwable ex) + void obtrudeValue(T value) + + CompletableFuture runAfterBoth(CompletionStage other,Runnable action) + CompletableFuture runAfterBothAsync(CompletionStage other,Runnable action) + CompletableFuture runAfterBothAsync(CompletionStage other, Runnable action,Executor executor) + CompletableFuture runAfterEither(CompletionStage other, Runnable action) + CompletableFuture runAfterEitherAsync(CompletionStage other,Runnable action) + CompletableFuture runAfterEitherAsync(CompletionStage other,Runnable action, Executor executor) + + CompletableFuture thenAcceptBoth(CompletionStage other,BiConsumer action) + CompletableFuture thenAcceptBothAsync(CompletionStage other,BiConsumer action) + CompletableFuture thenAcceptBothAsync(CompletionStage other,BiConsumer action, Executor executor) + CompletableFuture thenApply(Function fn) + CompletableFuture thenApplyAsync(Function fn) + CompletableFuture thenApplyAsync(Function fn, Executor executor) + CompletableFuture thenCombine(CompletionStage other,BiFunction fn) + CompletableFuture thenCombineAsync(CompletionStage other,BiFunction fn) + CompletableFuture thenCombineAsync(CompletionStage other,BiFunction fn, Executor executor) + CompletableFuture thenCompose(Function> fn) + CompletableFuture thenComposeAsync(Function> fn) + CompletableFuture thenComposeAsync(Function> fn,Executor executor) + + CompletableFuture thenRun(Runnable action) + CompletableFuture thenRunAsync(Runnable action) + CompletableFuture thenRunAsync(Runnable action,Executor executor) + + CompletableFuture thenAccept(Consumer action) + CompletableFuture thenAcceptAsync(Consumer action) + CompletableFuture thenAcceptAsync(Consumer action,Executor executor) + + CompletableFuture thenApply(Function fn) + CompletableFuture thenApplyAsync(Function fn) + CompletableFuture thenApplyAsync(Function fn, Executor executor) + + + CompletableFuture toCompletableFuture() + + CompletableFuture whenComplete(BiConsumer action) + CompletableFuture whenCompleteAsync(BiConsumer action) + CompletableFuture whenCompleteAsync(BiConsumer action, Executor executor) + + + + * 没有指定 Executor 的方法会使用 ForkJoinPool.commonPool() 作为它的线程池执行异步代码 + * 如果指定线程池,则使用指定的线程池运行 + + * runAsync方法不支持返回值 + * supplyAsync可以支持返回值 + + * whenComplete, 是执行当前任务的线程执行继续执行 whenComplete 的任务 + * whenCompleteAsync, 是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行 + + * 当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化 + + * handle 是执行任务完成时对结果的处理 + * handle 方法和 thenApply 方法处理方式基本一样 + * 不同的是 handle 是在任务完成后再执行, 还可以处理异常的任务,thenApply 只可以执行正常的任务,任务出现异常则不执行 thenApply 方法 + + * thenCombine 会把 两个 CompletionStage 的任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理 + * 当两个CompletionStage都执行完成后, 把结果一块交给thenAcceptBoth来进行消耗 + + * 两个CompletionStage, 谁执行返回的结果快, 就用那个CompletionStage的结果进行下一步的转化操作 + + + + +------------------------------- +CompletableFuture | +------------------------------- + + CompletableFuture.supplyAsync(() -> 1) // 第一个任务,线程返回 1 + .thenApply(i -> i + 1) // 第二个任务,使用第一个任务的返回值作为参数 + .thenApply(i -> i * 2) // 第三个任务,使用第二个任务的返回值作为参数 + .whenComplete((r, e) -> { + System.out.println(r); // 最后都执行完毕了,获取到执行的结果 + }); + diff --git a/Java/concurrent/java-juc-future-Future.java b/Java/concurrent/java-juc-future-Future.java new file mode 100644 index 00000000..726816d2 --- /dev/null +++ b/Java/concurrent/java-juc-future-Future.java @@ -0,0 +1,22 @@ +------------------------------- +Future | +------------------------------- + # 异步执行结果的接口 + # 方法 + boolean cancel(boolean mayInterruptIfRunning); + * 取消执行 + * mayInterruptIfRunning 设置是否要抛出线程中断异常 + + boolean isCancelled(); + * 是否取消执行 + + boolean isDone(); + * 是否执行完毕 + + V get() throws InterruptedException, ExecutionException; + * 获取到执行的结果,会阻塞当前的线程 + + V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException; + * 获取到执行的结果,会阻塞当前的线程 + * 可以设置一个超时时间,超时后抛出 TimeoutException + diff --git a/Java/concurrent/lock/java-AbstractQueuedSynchronizer.java b/Java/concurrent/lock/java-AbstractQueuedSynchronizer.java new file mode 100644 index 00000000..004a5668 --- /dev/null +++ b/Java/concurrent/lock/java-AbstractQueuedSynchronizer.java @@ -0,0 +1,70 @@ +---------------------------- +AbstractQueuedSynchronizer | +---------------------------- + # 抽象队列同步器, 传说中的 AQS, 抽象类 + AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable + + # 构造函数 + protected AbstractQueuedSynchronizer() { } + +---------------------------- + CLH队列(线程同步队列) | +---------------------------- + # 队列的Node + static final class Node { + static final Node SHARED = new Node(); + static final Node EXCLUSIVE = null; + + static final int CANCELLED = 1; + static final int SIGNAL = -1; + static final int CONDITION = -2; + static final int PROPAGATE = -3; + + volatile int waitStatus; + * 表示节点的状态 + CANCELLED: 值为 1, 表示当前的线程被取消 + SIGNAL: 值为-1, 表示当前节点的后继节点包含的线程需要运行, 也就是unpark + CONDITION: 值为-2, 表示当前节点在等待condition, 也就是在condition队列中 + PROPAGATE: 值为-3, 表示当前场景下后续的acquireShared能够得以执行 + 0(初始值): 表示当前节点在sync队列中, 等待着获取锁 + + volatile Node prev; + volatile Node next; + * 双向链表的前后节点 + + volatile Thread thread; + * 入队列时的线程 + + Node nextWaiter; + * 存储condition队列中的后继节点 + + //是否为共享模式 + final boolean isShared() { + return nextWaiter == SHARED; + } + + // 返回前一个节点 + final Node predecessor() throws NullPointerException { + Node p = prev; + if (p == null) + throw new NullPointerException(); + else + return p; + } + //用于建立初始头或SHARED标记 + Node() {} + // 使用在addWaiter方法中 + Node(Thread thread, Node mode) { + this.nextWaiter = mode; + this.thread = thread; + } + // 使用在Condition条件中 + Node(Thread thread, int waitStatus) { + this.waitStatus = waitStatus; + this.thread = thread; + } + } + + + * Node 可以包含两个队列: next/prev 双向链表, 和 nextWaiter 单向链表 + diff --git a/Java/concurrent/lock/java-Condition.java b/Java/concurrent/lock/java-Condition.java new file mode 100644 index 00000000..bf6d06b8 --- /dev/null +++ b/Java/concurrent/lock/java-Condition.java @@ -0,0 +1,17 @@ + +------------------------ +Condition | +------------------------ + # Condition 将 Object 监视器方法(wait,notify 和 notifyAll)分解成截然不同的对象 + * 以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set) + + # 接口方法 + void await() + * wait() 阻塞睡眠,等待唤醒 + boolean await(long time, TimeUnit unit) + long awaitNanos(long nanosTimeout) + void awaitUninterruptibly() + boolean awaitUntil(Date deadline) + void signal() + * notify ,唤醒 + void signalAll() diff --git a/Java/concurrent/java-juc-Lock.java b/Java/concurrent/lock/java-Lock.java similarity index 66% rename from Java/concurrent/java-juc-Lock.java rename to Java/concurrent/lock/java-Lock.java index d6f8704b..46c5e6fe 100644 --- a/Java/concurrent/java-juc-Lock.java +++ b/Java/concurrent/lock/java-Lock.java @@ -15,9 +15,15 @@ ---------------------------- Lock-方法 | ---------------------------- - lock(); + void lock(); * 上锁 + + void lockInterruptibly() throws InterruptedException; + + boolean tryLock(); + boolean tryLock(long time, TimeUnit unit) throws InterruptedException; unlock(); * 锁释放 - + + Condition newCondition(); diff --git a/Java/concurrent/lock/java-lock-ReentrantLock.java b/Java/concurrent/lock/java-lock-ReentrantLock.java new file mode 100644 index 00000000..2cc353d3 --- /dev/null +++ b/Java/concurrent/lock/java-lock-ReentrantLock.java @@ -0,0 +1,57 @@ +------------------------ +ReentrantLock | +------------------------ + # 可重入锁 + * 实现了 Lock 接口 + ReentrantLock implements Lock, java.io.Serializable + + # 构造方法 + ReentrantLock() + ReentrantLock(boolean fair) + + # 公平锁 + * 一般意义上的锁是不公平的,不一定先来的线程能先得到锁,后来的线程就后得到锁。 + * 不公平的锁可能会产生饥饿现象 + * 公平锁的意思就是,这个锁能保证线程是先来的先得到锁,虽然公平锁不会产生饥饿现象,但是公平锁的性能会比非公平锁差很多 + ReentrantLock fairLock = new ReentrantLock(true); + + # 实例方法 + lock(); + * 尝试获取锁,线程阻塞 + + lockInterruptibly(); + * 当前线程会阻塞获取锁 + * 如果该线程被执行了 interrupt() 方法,并且该线程处于阻塞状态的话,那么该线程的此方法就会抛出异常 + * '详情可以看 Thread的中断机制' + * 与local()的区别 + * lock() 会一直阻塞,直到获取锁 + * lockInterruptibly() 也会一直阻塞,直到获取锁 + * 但是,如果调用了该线程的 interrupt() 的方法,那么 lockInterruptibly() 就会抛出中断异常,必须进行处理 + * 该方法可以在线程处于阻塞状态下,由其他线程促使该方法抛出异常,从而中断线程,可以防止死锁之类的问题产生 + + + newCondition(); + * 返回一个Condition实例 + + unlock(); + * 释放锁 + + getHoldCount(); + getQueueLength(); + getWaitQueueLength(null); + + hasQueuedThread(null); + hasWaiters(null); + hasQueuedThreads(); + + isFair(); + * 是否是公平锁 + + isHeldByCurrentThread(); + isLocked(); + + boolean tryLock(); + * 尝试获取锁,不会阻塞,立即返回是否可以获取 + + boolean tryLock(long timeout, TimeUnit unit); + * 尝试获取锁,如果超时,返回 false \ No newline at end of file diff --git a/Java/concurrent/lock/java-lock-ReentrantReadWriteLock.java b/Java/concurrent/lock/java-lock-ReentrantReadWriteLock.java new file mode 100644 index 00000000..265e7284 --- /dev/null +++ b/Java/concurrent/lock/java-lock-ReentrantReadWriteLock.java @@ -0,0 +1,20 @@ +----------------------------- +读写锁 | +----------------------------- + # 读写锁 + ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { + + # 构造方法 + ReentrantReadWriteLock(boolean fair) + * 构造公平锁 + ReentrantReadWriteLock() + * 构造非公平锁 + + + # 实例方法 + ReentrantReadWriteLock.ReadLock readLock() + * 返回读锁 + + ReentrantReadWriteLock.WriteLock writeLock() + * 返回写锁 + diff --git a/Java/concurrent/lock/java-lock-StampedLock.java b/Java/concurrent/lock/java-lock-StampedLock.java new file mode 100644 index 00000000..f0ae00e9 --- /dev/null +++ b/Java/concurrent/lock/java-lock-StampedLock.java @@ -0,0 +1,7 @@ +----------------------------- +StampedLock | +----------------------------- + # JDK1.8的新锁, 并不是 Lock 的实现 + StampedLock implements java.io.Serializable + + \ No newline at end of file diff --git a/Java/concurrent/queue/queue-ArrayBlockingQueue.java b/Java/concurrent/queue/queue-ArrayBlockingQueue.java new file mode 100644 index 00000000..53cd6888 --- /dev/null +++ b/Java/concurrent/queue/queue-ArrayBlockingQueue.java @@ -0,0 +1,26 @@ +---------------------------------- +ArrayBlockingQueue | +---------------------------------- + # 底层使用数组的有界队列 + ArrayBlockingQueue extends AbstractQueue implements BlockingQueue, java.io.Serializable + + # 通过一把锁和两个Condition完成阻塞,通知 + final ReentrantLock lock; + private final Condition notEmpty; + private final Condition notFull; + + # 构造函数 + ArrayBlockingQueue(int capacity) + ArrayBlockingQueue(int capacity, boolean fair) + ArrayBlockingQueue(int capacity, boolean fair, Collection c) + + capacity + * 构造指定大小的有界队列 + fair + * 指定为公平或非公平锁 , 默认 false + c + * 初始化的数据集合 + + + # 实例函数 + \ No newline at end of file diff --git a/Java/concurrent/queue/queue-ConcurrentLinkedQueue.java b/Java/concurrent/queue/queue-ConcurrentLinkedQueue.java new file mode 100644 index 00000000..bfae02b2 --- /dev/null +++ b/Java/concurrent/queue/queue-ConcurrentLinkedQueue.java @@ -0,0 +1,31 @@ +-------------------------------- +ConcurrentLinkedQueue | +-------------------------------- + # 使用链表作为其数据的结构的队列 + + # 无界非阻塞队列, 并且是线程安全的 + * 使用CAS算法保证线程安全 + + * 入队出队函数都是操作volatile变量:head,tail, 所以要保证队列线程安全只需要保证对这两个Node操作的可见性和原子性 + * 由于volatile本身保证可见性, 所以只需要在多线程下保证对着两个变量操作的原子性 + + * 对于offer操作是在tail后面添加元素, 也就是调用tail.casNext方法, 而这个方法是使用的CAS操作, + * 只有一个线程会成功, 然后失败的线程会循环一下, 重新获取tail,然后执行casNext方法 + * 对于poll也是这样的 + + + # 构造函数 + public ConcurrentLinkedQueue() + public ConcurrentLinkedQueue(Collection c) + + # 实例函数 + + # 注意 + * 调用size()方法的时候, 会遍历一遍链表, 对性能损害较大, 执行很慢, 因此应该尽量的减少使用这个方法, 如果判断是否为空, 最好用isEmpty()方法 + + * CAS没有加锁所以从调用size函数到返回结果期间有可能增删元素, 导致统计的元素个数不精确 + + * 无界的, 一定要注意内存溢出的问题 + + + diff --git a/Java/concurrent/queue/queue-LinkedBlockingQueue.java b/Java/concurrent/queue/queue-LinkedBlockingQueue.java new file mode 100644 index 00000000..7cbc2a3a --- /dev/null +++ b/Java/concurrent/queue/queue-LinkedBlockingQueue.java @@ -0,0 +1,26 @@ +---------------------------------- +LinkedBlockingQueue | +---------------------------------- + # 底层使用单链表的形式实现Queue + + # 采用两把锁的锁分离技术实现入队出队互不阻塞 + private final ReentrantLock takeLock = new ReentrantLock(); + private final Condition notEmpty = takeLock.newCondition(); + + private final ReentrantLock putLock = new ReentrantLock(); + private final Condition notFull = putLock.newCondition(); + + # 构造函数 + public LinkedBlockingQueue() + public LinkedBlockingQueue(int capacity) + LinkedBlockingQueue(Collection c) + + capacity + * queue的容量大小, 默认:Integer.MAX_VALUE + + c + * 初始化的内容 + + + # 实例函数 + \ No newline at end of file diff --git a/Java/concurrent/queue/queue.java b/Java/concurrent/queue/queue.java new file mode 100644 index 00000000..f8bc61de --- /dev/null +++ b/Java/concurrent/queue/queue.java @@ -0,0 +1,63 @@ +------------------------------------ +Queue | +------------------------------------ + # 体系 + Queue(java.util) + |-Deque + |-ArrayDeque + |-ConcurrentLinkedDeque + |-BlockingQueue + |-AbstractQueue + |-PriorityQueue + |-DelayQueue + |-SynchronousQueue + |-PriorityBlockingQueue + |-LinkedTransferQueue + |-LinkedBlockingDeque + |-ArrayBlockingQueue + * 才用数组结构实现 + * 入队出队采用一把锁,导致入队出队相互阻塞,效率低下 + + |-LinkedBlockingQueue + * 采用单链表的形式实现 + * 采用两把锁的锁分离技术实现入队出队互不阻塞 + * 是有界队列,不传入容量时默认为最大int值 + + + # 常用的一些Queue + +----------------------+----------------+-----------+-----------------+ + |队列 | 加锁方式 | 是否有界| 数据结构 + +----------------------+----------------+-----------+-----------------+ + |ArrayBlockingQueue |加锁 |有界 |ArrayList | + +----------------------+----------------+-----------+-----------------+ + |LinkedBlockingQueue |加锁 |无界 |LinkedList | + +----------------------+----------------+-----------+-----------------+ + |ConcurrentLinkedQueue |CAS |无界 |LinkedList | + +----------------------+----------------+-----------+-----------------+ + |ConcurrentLinkedDeque |CAS |无界 | | + +----------------------+----------------+-----------+-----------------+ + |LinkedTransferQueue |CAS |无界 |LinkedList | + +----------------------+----------------+-----------+-----------------+ + |PriorityBlockingQueue | + +----------------------+----------------+-----------+-----------------+ + |DelayQueue | + +----------------------+----------------+-----------+-----------------+ + |SynchronousQueue | + +----------------------+----------------+-----------+-----------------+ +------------------------------------ +Queue | +------------------------------------ + # Queue 接口, 抽象出了基本的操作 + public interface Queue extends Collection { + boolean add(E e); + + boolean offer(E e); + + E remove(); + + E poll(); + + E element(); + + E peek(); + } diff --git a/Java/io/java-io-Closeable.java b/Java/io/java-io-Closeable.java new file mode 100644 index 00000000..45730bf7 --- /dev/null +++ b/Java/io/java-io-Closeable.java @@ -0,0 +1,9 @@ +-------------------- +Closeable | +-------------------- + * 标记接口,实现该接口的类,可以在 try() 中进行实例化 + * 系统会自动的进行资源关闭 + * 唯一抽象方法 + public void close() throws IOException; + + diff --git a/Java/io/java-io-Console.java b/Java/io/java-io-Console.java new file mode 100644 index 00000000..53e81a59 --- /dev/null +++ b/Java/io/java-io-Console.java @@ -0,0 +1,17 @@ +---------------------------- +Console | +---------------------------- + * jdk1.6新增类库,用于读取屏幕输入 + * 构造 + Console console = System.console(); + + +---------------------------- +Console-实例方法/属性 | +---------------------------- + String readLine(String fmt, Object ... args) + * 读取输入 + + char[] readPassword(String fmt, Object ... args) + * 读取密码输入,屏幕上是 * + diff --git a/Java/io/java-io-Externalizable.java b/Java/io/java-io-Externalizable.java new file mode 100644 index 00000000..07e4c669 --- /dev/null +++ b/Java/io/java-io-Externalizable.java @@ -0,0 +1,8 @@ +------------------------------ +Externalizable | +------------------------------ + # 继承了:Serializable 接口 + # 提供了俩抽象方法,让开发者实现手动的序列化/反序列化对象 + + void writeExternal(ObjectOutput out) throws IOException; + void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; \ No newline at end of file diff --git a/Java/io/java-io-PipedInputStream.java b/Java/io/java-io-PipedInputStream.java index 735e2f34..e69de29b 100644 --- a/Java/io/java-io-PipedInputStream.java +++ b/Java/io/java-io-PipedInputStream.java @@ -1,7 +0,0 @@ -class -{ - public static void main(String[] args) - { - System.out.println("Hello World!"); - } -} diff --git a/Java/io/java-io-PipedOutputStream.java b/Java/io/java-io-PipedOutputStream.java index 735e2f34..e69de29b 100644 --- a/Java/io/java-io-PipedOutputStream.java +++ b/Java/io/java-io-PipedOutputStream.java @@ -1,7 +0,0 @@ -class -{ - public static void main(String[] args) - { - System.out.println("Hello World!"); - } -} diff --git a/Java/io/java-io-PushbackInputStream.java b/Java/io/java-io-PushbackInputStream.java new file mode 100644 index 00000000..ec1e10d2 --- /dev/null +++ b/Java/io/java-io-PushbackInputStream.java @@ -0,0 +1,23 @@ +---------------------------- +PushbackInputStream | +---------------------------- + # 本身继承:FilterInputStream + # 可回退流(给了用户第二次读的机会) + PushbackInputStream(InputStream in) + PushbackInputStream(InputStream in, int size) + + * in 指定的 InputStream + * size 可以重读的buf大小 + + + # 特殊的实例方法 + unread(byte[] b) + * 重新把字节数组里面的数据放回到流里面 + + void unread(byte[] b, int off, int len) + * 重新把字节数组里面的数据放回到流里面 + + void unread(int b) + * 重新把一个字节放回到流里面 + + diff --git a/Java/io/java-io-RandomAccessFile.java b/Java/io/java-io-RandomAccessFile.java index 735e2f34..42547861 100644 --- a/Java/io/java-io-RandomAccessFile.java +++ b/Java/io/java-io-RandomAccessFile.java @@ -1,7 +1,76 @@ -class -{ - public static void main(String[] args) - { - System.out.println("Hello World!"); - } -} +------------------------ +RandomAccessFile | +------------------------ + # 随机IO + # 构造方法 + RandomAccessFile(File file, String mode) + RandomAccessFile(String name, String mode) + + * file/name 目标文件 + * mode io模式:"r","w"组合 + + # 实例方法 + FileChannel getChannel() + * 获取关联的channel + + FileDescriptor getFD() + + long getFilePointer() + * 返回文件记录指针的当前位置 + + void seek(long pos) + * 将文件记录指针定位到pos的位置 + + long length() + * 获取文件的长度 + + int read() + * 读取一个字节的数据 + + int read(byte b[]) + * 读取数据到 byte[] + + readFully(byte b[]) + + int read(byte b[], int off, int len) + * 读取数据到 byte[] + * 从byte[]的off开始写入,写入len长度 + + readLine(); + * 从指针位置读取当前行,只会读指针这一行后面的数据 + + + # 读取Demo + RandomAccessFile randomAccessFile = new RandomAccessFile(PATH,"rw"); + randomAccessFile.seek(60); //设置指针位置 + int len = 0; + byte[] buf = new byte[1024]; + while ((len = randomAccessFile.read(buf)) != -1){ + System.out.println(new String(buf,0,len,"GBK")); + } + randomAccessFile.close(); + + # 写入Demo + RandomAccessFile randomAccessFile = new RandomAccessFile(PATH,"rw"); + randomAccessFile.seek(randomAccessFile.length()); //移到指针的最后 + randomAccessFile.write("//哈哈哈,这个是追加的哟".getBytes("GBK")); //写入数据 + + # 监听文件的变化 + // 不能打开写权限("w"),不然其他程序没法写入数据到该文件 + try(RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\log.log", "r")){ + // 最后一次指针的位置,默认从头开始 + long lastPointer = 0; + // 每一行读取到的数据 + String line = null; + // 持续监听文件 + while(true) { + // 从最后一次读取到的数据开始读取 + randomAccessFile.seek(lastPointer); + // 读取一行,遇到换行符停止,不包含换行符 + while((line = randomAccessFile.readLine()) != null) { + System.out.print(line + "\n"); + } + // 读取完毕后,记录最后一次读取的指针位置 + lastPointer = randomAccessFile.getFilePointer(); + } + } \ No newline at end of file diff --git a/Java/io/java-io-StringWriter.java b/Java/io/java-io-StringWriter.java new file mode 100644 index 00000000..636f7268 --- /dev/null +++ b/Java/io/java-io-StringWriter.java @@ -0,0 +1,8 @@ +------------------------- +StringWriter | +------------------------- + # Writer 的实现,专门用于字符串的处理 + # 用于写入各种字符串,字符,最后一般都会 通过 toString() 获取到写入的所有数据 + # 内部封装了一个 StringBuffer + + diff --git a/Java/io/java-io.java b/Java/io/java-io.java index 5f0ffdec..8271b124 100644 --- a/Java/io/java-io.java +++ b/Java/io/java-io.java @@ -120,6 +120,11 @@ # length,可以设置缓冲区的大小 reader.readLine(); * 读取一行数据(并不包含回车符),当读到末尾。返回 null. + + LineNumberReader + * BufferedReader 的子类,带有可以读写行号的类 + getLineNumber()//可以获取读取的行号 + setLineNumber(num)//设置初始的行号,读取的第一行从num+1开始标记 ---------------------------- FileWriter | @@ -180,7 +185,33 @@ 打印流 | ---------------------------- PrintWriter + PrintWriter(File file) + PrintWriter(File file, String csn) + PrintWriter(OutputStream out) + PrintWriter(OutputStream out, boolean autoFlush) + PrintWriter(Writer out) + PrintWriter(Writer out, boolean autoFlush) + PrintWriter(String fileName) + PrintWriter(String fileName, String csn) + PrintStream + PrintStream(File file) + PrintStream(File file, String csn) + PrintStream(OutputStream out) + PrintStream(OutputStream out, boolean autoFlush) + PrintStream(OutputStream out, boolean autoFlush, String encoding) + PrintStream(String fileName) + PrintStream(String fileName, String csn) + + + autoFlush: 是否自动刷新 + + + + * 此流不负责数据源,只负责数据目的 + * 为其他输出流添加了功能 + * 永远不会抛出IOException,但是可能抛出其他异常 + * 两个打印流方法完全一致 ---------------------------- 管道流 | @@ -188,6 +219,9 @@ PipedInputStream PipedOutputStream + * 结合线程使用,输入输出直接进行连接,不建议使用单线程,因为读取流先开启,那么流中一旦没有数据,读取流的阻塞式方法就会一直处于等待状态-死锁 + + ---------------------------- 序列化流 | ---------------------------- diff --git a/Java/java-annotation.java b/Java/java-annotation.java index bf862b8c..625bb2fa 100644 --- a/Java/java-annotation.java +++ b/Java/java-annotation.java @@ -101,3 +101,30 @@ 新的 +------------------------------ +Inherited | +------------------------------ + # 允许子类继承父类的注解 + + import java.lang.annotation.*; + @Inherited + @Target(value = {ElementType.TYPE}) + @Retention(value = RetentionPolicy.RUNTIME) + public @interface Foo { + + } + + @Foo + public class Foo1 { + } + + public class Foo2 extends Foo1 { + } + + public class Main { + public static void main(String[] args) { + // true + boolean result = Foo2.class.isAnnotationPresent(Foo.class); + System.out.println(result); + } + } \ No newline at end of file diff --git a/Java/java-clone.java b/Java/java-clone.java new file mode 100644 index 00000000..27329abe --- /dev/null +++ b/Java/java-clone.java @@ -0,0 +1,66 @@ +---------------------------- +Java对象的深浅Clone | +---------------------------- + * 浅clone,只是简单的吧对象的属性copy到clone对象上,这样两个对象的相同属性都指向了同一个地址 + * 深clone,clone对象的属性,是新创建的属性,由源对象的属性复制过来 + * 先谈谈clone()方法 + * 这是定义在 Object 里面的方法,Java里面任何对象都能调用,但是它是被protected修饰 + protected native Object clone() throws CloneNotSupportedException; + * 也就是说,我们自己创建的对象没法直接调用该方法,因为自己创建类的没法跟Object在同一个包。 + * 必须要覆写掉该方法,在覆写方法中调用Object的方法来完成clone,clone细节不用关心,因为它是一个native本地方法。 + * 调用clone方法的类,必须要实现Cloneable接口,该接口跟Serializable一样,只是一个标记接口,无任何抽象方法 + +---------------------------- +浅clone | +---------------------------- + import java.util.Date; + class Foo implements Cloneable { + public Date date; + Foo(Date date){ + this.date = date; + } + @Override + protected Object clone() throws CloneNotSupportedException { + //调用父类的本地方法,获得浅clone的对象 + return super.clone(); + } + } + public class Main { + public static void main(String[] args) throws CloneNotSupportedException { + Foo foo = new Foo(new Date()); + Foo cloneFoo = (Foo) foo.clone(); + System.out.println(foo == cloneFoo); //false + System.out.println(foo.date == cloneFoo.date); //true(浅clone,俩对象的date其实都是同一个) + } + } +---------------------------- +深clone | +---------------------------- + import java.util.Date; + class Foo implements Cloneable { + public Date date; + Foo(Date date){ + this.date = date; + } + @Override + protected Object clone() throws CloneNotSupportedException { + //浅clone Foo对象 + Foo cloneFoo = (Foo) super.clone(); + //浅clone Date(属性)对象 + Date cloneDate = (Date) date.clone(); + //深clone完成 + cloneFoo.date = cloneDate; + return cloneFoo; + } + } + public class Main { + public static void main(String[] args) throws CloneNotSupportedException { + Foo foo = new Foo(new Date()); + Foo cloneFoo = (Foo) foo.clone(); + System.out.println(foo == cloneFoo); //false + System.out.println(foo.date == cloneFoo.date); //false + } + } + +在设计模式原型模型中clone用得多 +其实就是把一个对象进行复制,复制出来的对象,就拥有了原对象的所有属性和方法,并且可以基于该对象进行增强,修改,而且不会影响原对象 \ No newline at end of file diff --git "a/Java/java-jdk7-\346\226\271\346\263\225\347\232\204\345\212\250\346\200\201\350\260\203\347\224\250.java" "b/Java/java-jdk7-\346\226\271\346\263\225\347\232\204\345\212\250\346\200\201\350\260\203\347\224\250.java" new file mode 100644 index 00000000..ec634033 --- /dev/null +++ "b/Java/java-jdk7-\346\226\271\346\263\225\347\232\204\345\212\250\346\200\201\350\260\203\347\224\250.java" @@ -0,0 +1,28 @@ +------------------------ +方法动态调用 | +------------------------ + + import java.lang.invoke.MethodHandle; + import java.lang.invoke.MethodHandles; + import java.lang.invoke.MethodHandles.Lookup; + import java.lang.invoke.MethodType; + + public class Main { + + public void println(Object value) { + System.out.println("value=" + value); + } + + public static MethodHandle getMethodHandle(Object receiver) throws NoSuchMethodException, IllegalAccessException { + Lookup lookup = MethodHandles.lookup(); + //方法的返回类型和参数类型 + MethodType methodType = MethodType.methodType(void.class,Object.class); + //获取指定对象的方法名称,以及方法类型,绑定到该对象 + return lookup.findVirtual(receiver.getClass(), "println", methodType).bindTo(receiver); + } + + public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, Throwable { + getMethodHandle(System.out).invoke("我是System.out"); //我是System.out + getMethodHandle(new Main()).invoke("我是Main"); //value=我是Main + } + } \ No newline at end of file diff --git a/Java/java-socket.java b/Java/java-socket.java index 836614a2..7dd7f929 100644 --- a/Java/java-socket.java +++ b/Java/java-socket.java @@ -1,8 +1,3 @@ ---------------------------- -Netty网络编程基础 | ---------------------------- - - --------------------------- Netty-UDP发送 | --------------------------- diff --git "a/Java/java-spi\346\234\272\345\210\266.java" "b/Java/java-spi\346\234\272\345\210\266.java" new file mode 100644 index 00000000..b9414867 --- /dev/null +++ "b/Java/java-spi\346\234\272\345\210\266.java" @@ -0,0 +1,81 @@ +------------------------ +spi机制 | +------------------------ + # Service Provider Interface + * 我感觉就是动态的加载实现类 + + # 工作流程 + -------------------- [实现A] + | + [调用] -> [接口] -> [本地服务发现加载] + | + -------------------- [实现B] + + # 实现 + * 在jar的目录创建文件:META-INF/services/<接口全限定名> + * 文件内容就是接口实现类的全路径 + * 例如 mysql-jdbc 的实现 + /META-INF/services/java.sql.Driver + com.mysql.cj.jdbc.Driver + + + + # 通过 java.util.ServiceLoder 动态装载实现模块 + * 它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名, 把类加载到JVM4 + * SPI的实现类必须携带一个不带参数的构造方法(反射创建) + + + // 通过参数指定SPI的接口 + ServiceLoader serviceLoader = ServiceLoader.load(Driver.class); + // 返回SPI接口的实现,迭代器 + Iterator driverIterator = serviceLoader.iterator(); + while (driverIterator.hasNext()){ + // 遍历每一个实现 + Driver driver = driverIterator.next(); + System.out.println(driver); + } + + /* + org.postgresql.Driver@5479e3f + com.alibaba.druid.proxy.DruidDriver@7bfcd12c + com.alibaba.druid.mock.MockDriver@42f30e0a + com.mysql.cj.jdbc.Driver@46f5f779 + */ + + # 缺点 + * 虽然ServiceLoader也算是使用的延迟加载, 但是基本只能通过遍历全部获取, 也就是接口的实现类全部加载并实例化一遍 + * 如果并不想用某些实现类, 它也被加载并实例化了, 这就造成了浪费 + + * 获取某个实现类的方式不够灵活, 只能通过Iterator形式获取, 不能根据某个参数来获取对应的实现类 + * 多个并发多线程使用ServiceLoader类的实例是不安全的 + + # 查看 JDBC 的加载过程 + DriverManager.setLogWriter(new PrintWriter(System.out)); + DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true", "root", "root"); + /* + DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true") + trying org.postgresql.Driver + trying com.alibaba.druid.proxy.DruidDriver + trying com.alibaba.druid.mock.MockDriver + trying com.mysql.cj.jdbc.Driver + getConnection returning com.mysql.cj.jdbc.Driver + */ + +------------------------ +ServiceLoader | +------------------------ + # JDK6的东西 + final class ServiceLoader implements Iterable + + # 静态工厂函数 + ServiceLoader load(Class service) + ServiceLoader load(Class service, ClassLoader loader) + ServiceLoader loadInstalled(Class service) + + # 实例方法 + Iterator iterator(); + forEach(Consumer action) + void reload(); + Spliterator spliterator() + + diff --git "a/Java/java-\345\205\263\351\224\256\345\255\227.java" "b/Java/java-\345\205\263\351\224\256\345\255\227.java" new file mode 100644 index 00000000..437b65b0 --- /dev/null +++ "b/Java/java-\345\205\263\351\224\256\345\255\227.java" @@ -0,0 +1,5 @@ +-------------------- +有点意思的关键字 | +-------------------- + strictfp + * 标识在函数上,表示该函数中所有浮点运算,都是精准的 \ No newline at end of file diff --git "a/Java/java-\345\233\275\351\231\205\345\214\226.java" "b/Java/java-\345\233\275\351\231\205\345\214\226.java" new file mode 100644 index 00000000..02a0e0e6 --- /dev/null +++ "b/Java/java-\345\233\275\351\231\205\345\214\226.java" @@ -0,0 +1,24 @@ +--------------------------- +国际化 | +--------------------------- + Locale locale = Locale.CHINA; + //多个语言配置文件 + //res_zh_CN.properties + //res_en_US.properties + ResourceBundle rb = ResourceBundle.getBundle("res", locale); + //获取配置里面的属性 + System.out.println(rb.getString("name")); + +--------------------------- +获取所有支持的国家地区 | +--------------------------- + //获取所支持的国家和语言 + Locale[] localeList = Locale.getAvailableLocales(); + for (int i = 0; i < localeList.length; i++) { + // 打印出所支持的国家和语言 + System.out.println(localeList[i].getDisplayCountry() + "=" + localeList[i].getCountry() + " " + localeList[i].getDisplayLanguage() + "=" + localeList[i].getLanguage()); + } + /* + 阿拉伯联合酋长国=AE 阿拉伯文=ar + 约旦=JO 阿拉伯文=ar + */ \ No newline at end of file diff --git "a/Java/java-\346\225\260\346\215\256\347\261\273\345\236\213.java" "b/Java/java-\346\225\260\346\215\256\347\261\273\345\236\213.java" new file mode 100644 index 00000000..41499d3e --- /dev/null +++ "b/Java/java-\346\225\260\346\215\256\347\261\273\345\236\213.java" @@ -0,0 +1,19 @@ +-------------------- +变量 | +-------------------- + # java中所有数值变量取值范围都是有符号的(例如:byte -127 - 128) + +-------------------- +变量声明 | +-------------------- + int x = 0xFFFF + # 16进制表示 + + int x = 0755 + # 8进制表示 + + int x = 0b01010101 + # 2进制表示 + + int x = 1000__222 + # 数值类型,可以添加下划线(为了易读),编译器会擦除该下划线 \ No newline at end of file diff --git "a/Java/javax/javax-\345\212\250\346\200\201\347\274\226\350\257\221.java" "b/Java/javax/javax-\345\212\250\346\200\201\347\274\226\350\257\221.java" new file mode 100644 index 00000000..ff3ecdaf --- /dev/null +++ "b/Java/javax/javax-\345\212\250\346\200\201\347\274\226\350\257\221.java" @@ -0,0 +1,67 @@ +-------------------- +JAVA-动态编译 | +-------------------- + # 传统的运行JAVA程序的方式 + 1,编写源代码 + 2,编译源代码 + 3,调用main函数开始执行程序 + # 实际上,JAVA提供了API用于动态编译的功能. + # 动态编译的两种做法 + 1,通过Runtime调用JAVA,启动新的进程去操作 + Runtime runtime = Runtime.getRuntime(); + Process process = runtime.exec("javac -cp -D:/MyJava/ HelloWorld.java"); + 2,通过JavaCompiler动态的进行编译(6.0以后引入的新功能) + public static int compileFile(String sourceFile){ + //模拟JAVA源文件地址 + sourceFile = "E:/MyJava/HelloWorld.java"; + //获取编译器对象 + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + /* 执行编译操作 + 第一个参数inputStream :为JAVA编译器提供参数 + 第二个参数outputStream :得到JAVA编译器的输出信息 + 第三个参数OutputStream :接收编译器的错误信息 + 第四个参数:可变参数(String[]),能传入一个或者多个JAVA源文件的路径 + 返回值:0表示成功编译,非0表示编译失败 + */ + int result = compiler.run(null, System.out, System.out, sourceFile); + if(result == 0){ + System.out.println("编译成功"); + }else{ + System.out.println("编译失败"); + } + return result; + } + +-------------------- +JAVA-动态运行 | +-------------------- + # 还是那个概念,通过JAVA程序去运行JAVA的class文件 + # 两种办法 + 1,通过RuntimeAPI启用新的线程去执行 + Runtime runtime = Runtime.getRuntime(); + Process process = runtime.exec("java -cp -D:/MyJava/ HelloWorld.java"); + 2,通过反射,运行编译好的类 + public static void run(String dir,String classFile)throws Exception{ + //目标目录 + dir = "E:/MyJava/"; + //目标class文件 + classFile = "HelloWorld"; + //创建目录url类 + URL[] urls = new URL[]{new URL("file:/"+dir)}; + //获取url类加载器 + URLClassLoader classLoader = new URLClassLoader(urls); + //加载指定的class文件 + Class clazz = classLoader.loadClass(classFile); + //获取其main方法 + Method mainMethod = clazz.getMethod("main", String[].class); + /* + 执行main方法,传递字空符数组参数 + 因为main方法是static方法,所以可以不用传递对象 + # 强制转换的原因 + * 可变参数是jdk5之后的东西 + * 如果说不把这个String[]转换为Object的话 + * String[]里面的每个参数,都会被当作一个新的String[]被main方法加载 + * 而main方法的参数只有一个 + */ + mainMethod.invoke(null, (Object)new String[]{}); + } \ No newline at end of file diff --git "a/Java/javax/javax-\350\204\232\346\234\254\345\274\225\346\223\216.java" "b/Java/javax/javax-\350\204\232\346\234\254\345\274\225\346\223\216.java" new file mode 100644 index 00000000..c266adfb --- /dev/null +++ "b/Java/javax/javax-\350\204\232\346\234\254\345\274\225\346\223\216.java" @@ -0,0 +1,57 @@ +-------------------- +JAVA-脚本引擎 | +-------------------- + # 其实就是在JAVA代码中执行JavaScript的代码 + # JAVA只是提供了这个接口,但是没提供实现,由其他的脚本厂商自己编写实现. + # 案例 + 1,客户端传递了一个字符串 + "3 * 4 / 2 + 6 -5" + 2,该字符串应该用于计算最后的结果 + 3,如果是使用JAVA来完成,就比较的麻烦 + 4,如果是JavaScript那就简单 eval();函数就搞定 + + # 脚本引擎是JDK6.0以后添加的新功能 + * 其实就是,JAVA应用程序可以通过一套固定的接口与'各种脚本引擎'交互 + * 从而达到在Java平台上调用各种脚本语言的目的 + * Java脚本API是连通Java平台好脚本语言的桥梁 + * 可以把一些复杂异变的业务逻辑交给脚本语言处理,这也大大提高了开发效率 + * 其实就是可以在java程序中执行其他的脚本语言 + * 6.0后javascript-->Rhino被添加到了JDK + + # Java脚本API为开发者提供了N多功能 + * 获取脚本程序输入,通过脚本引擎运行脚本并返回运行结果. + * 核心的接口 : ScriptEngineFactory + 1,注意:是接口,JAVA可以使用各种不同的实现,从而调用js,groovy,python等脚本 + 2,JavasScript --> RhinoEngineFactory + 3,Rhino 是一种使用java语言编写的javascript代码 (jdk.nashorn.api.scripting.NashornScriptEngine) + * Rhino <>,这本书封面就是Rhino + + # 获取脚本引擎Manager对象 + ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); + + # 查看支持的脚本引擎 + ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); + for (ScriptEngineFactory scriptEngineFactory : scriptEngineManager.getEngineFactories()) { + System.out.println(scriptEngineFactory.getEngineName() + ":" + scriptEngineFactory.getEngineVersion()); // 执行引擎名称和版本号 + ScriptEngine scriptEngine = scriptEngineFactory.getScriptEngine(); // 获取到执行引擎 + } + + # 获取指定名称的脚本引擎 + ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); + ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("javascript"); + + # 还可以根据mineType,扩展名等api去获取一个执行引擎 + scriptEngineManager.getEngineByMimeType("text/javascript"); + scriptEngineManager.getEngineByExtension("js"); + + + # 简单的使用 + ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); + + ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("javascript"); + + scriptEngine.put("name", "KevinBlandy"); + + Object result = scriptEngine.eval("\"Hello \" + name"); + + System.out.println(result); // Hello KevinBlandy diff --git a/Java/jdbc/java-jdbc.java b/Java/jdbc/java-jdbc.java index d5894316..81f78376 100644 --- a/Java/jdbc/java-jdbc.java +++ b/Java/jdbc/java-jdbc.java @@ -27,6 +27,9 @@ * 创建一个Statement对象 PreparedStatement prepareStatement(String sql); * 根据SQL出创建一个PreparedStatement对象 + prepareStatement(String sql, int autoGeneratedKeys) + * 同上,多了一个设置,可以返回自动增加的key + CallableStatement prepareCall(String sql); * 创建一个CallableStatement对象来调用数据库存储过程 setAutoCommit(boolean transaction); @@ -46,6 +49,17 @@ int executeUpdate(String sql) * 执行给定 SQL 语句,该语句可能为 INSERT、UPDATE 或 DELETE 语句,或者不返回任何内容的 SQL 语句(如 SQL DDL 语句)。 * 返回受影响的行数 + void addBatch(String sql); + * 添加一条批处理sql + int[] executeBatch() + * 执行添加的批处理语句 + void clearBatch() + * 清空批处理 + + # 静态变量 + int RETURN_GENERATED_KEYS = 1; + * 返回自动增长的key + |-PreparedStatement # 预编译SQL的,statement,可以防止SQL注入 # 实例方法 @@ -53,12 +67,30 @@ int executeUpdate(String sql) * 给第一个问号赋值 * 有N多重载(setXxx),可以赋值不同的数据类型 * 注意'?'号的角标是从1开始,而不是0 + void setCharacterStream(int parameterIndex,java.io.Reader reader, int length); + * 设置字符流 + void setBinaryStream(int parameterIndex, java.io.InputStream x,long length + * 设置字节流 boolean execute() * 执行SQL语句该语句可以是任何种类的 SQL 语句。 ResultSet executeQuery(); * 执行检索,并返回该查询生成的 ResultSet 对象。 int executeUpdate() * 执行插入或者修改语句 + ResultSet getGeneratedKeys() throws SQLException; + * 在执行 insert 后,可以执行该方法获取自增id + if (preparedStatement.executeUpdate() > 0) { + resultSet = preparedStatement.getGeneratedKeys(); + if (resultSet.next()) { + int id = resultSet.getInt(1); + System.out.println(id); + } + } + * 创建 PreparedStatement 的时候需要有个额外的设置:cnnection.prepareStatement("..",Statement.RETURN_GENERATED_KEYS); 否则抛出异常 + + void addBatch() + * 添加当前编译后的语句到批处理 + * 执行该方法后,会把当前编译的语句添加到批处理缓冲区。并且重置参数指针 |-CallableStatement # 调用存储过程的 statement @@ -79,6 +111,10 @@ int executeUpdate() * 获取指定字段的String类型值 getString(int columnIndex); * 获取指定索引的String类型值 + InputStream getBinaryStream(String columnLabel) + * 获取二进制流 + Reader getCharacterStream(String columnLabel); + * 获取字符流 * 有大量的 getXxx();存在,包含了JAVA各种基本数据类型的返回值 Blob @@ -163,4 +199,187 @@ public static void get()throws Exception bufr.flush(); } } - } \ No newline at end of file + } + + +------------------------------ +JDBC-批处理 | +------------------------------ + # Statement执行批处理 + * 优点: + 可以向数据库发送不同的SQL语句 + * 缺点 + SQL没有预编译 + 仅参数不同的SQL,需要重复写多条SQL + + Connection connection = null; + + Statement statement = connection.createStatement(); + String sql1 = "UPDATE users SET name='zhongfucheng' WHERE id='3'"; + String sql2 = "INSERT INTO users (id, name, password, email, birthday) VALUES('5','nihao','123','ss@qq.com','1995-12-1')"; + + //将sql添加到批处理 + statement.addBatch(sql1); + statement.addBatch(sql2); + + //执行批处理 + statement.executeBatch(); + + //清空批处理的sql + statement.clearBatch(); + + + + # PreparedStatement批处理 + * 优点: + SQL语句预编译了 + 对于同一种类型的SQL语句,不用编写很多条 + + * 缺点: + 不能发送不同类型的SQL语句 + + Connection connection = UtilsDemo.getConnection(); + String sql = "INSERT INTO test(id,name) VALUES (?,?)"; + PreparedStatement preparedStatement = connection.prepareStatement(sql); + + for (int i = 1; i <= 205; i++) { + preparedStatement.setInt(1, i); + preparedStatement.setString(2, (i + "zhongfucheng")); + + //添加到批处理中 + preparedStatement.addBatch(); + + if (i %2 ==100) { + + //执行批处理(200条记录) + preparedStatement.executeBatch(); + + //清空批处理【如果数据量太大,所有数据存入批处理,内存肯定溢出】 + preparedStatement.clearBatch(); + } + + } + //不是所有的%2==100,剩下的再执行一次批处理(5条记录) + preparedStatement.executeBatch(); + + //再清空 + preparedStatement.clearBatch(); + + UtilsDemo.release(connection, preparedStatement, null); + + +------------------------------ +JDBC-大文本的读写 | +------------------------------ + # 插入大文本数据 + Connection connection = null; + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + + String sql = "INSERT INTO test2 (bigTest) VALUES(?) "; + preparedStatement = connection.prepareStatement(sql); + + // 获取到文件的路径 + String path = Main.class.getClassLoader().getResource("BigTest").getPath(); + File file = new File(path); + FileReader fileReader = new FileReader(file); + + // 第三个参数,由于测试的Mysql版本过低,所以只能用int类型的。高版本的不需要进行强转 + preparedStatement.setCharacterStream(1, fileReader, (int) file.length()); + + if (preparedStatement.executeUpdate() > 0) { + System.out.println("插入成功"); + } + + # 读取大文本数据 + Connection connection = null; + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + + String sql = "SELECT * FROM test2"; + preparedStatement = connection.prepareStatement(sql); + resultSet = preparedStatement.executeQuery(); + + if (resultSet.next()) { + //从结果集获取reader + Reader reader = resultSet.getCharacterStream("bigTest"); + //目的文件 + FileWriter fileWriter = new FileWriter("d:\\abc.txt"); + //缓冲区 + char[] chars = new char[1024]; + int len = 0; + while ((len = reader.read(chars)) != -1) { + fileWriter.write(chars, 0, len); + fileWriter.flush(); + } + fileWriter.close(); + reader.close(); + } + + +------------------------------ +JDBC-大的二进制数据读写 | +------------------------------ + # 插入大的二进制数据 + Connection connection = null; + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + + + String sql = "INSERT INTO test3 (blobtest) VALUES(?)"; + preparedStatement = connection.prepareStatement(sql); + + //获取文件的路径和文件对象 + String path = Main.class.getClassLoader().getResource("1.wmv").getPath(); + File file = new File(path); + + //调用方法 + preparedStatement.setBinaryStream(1, new FileInputStream(path), (int)file.length()); + + if (preparedStatement.executeUpdate() > 0) { + + System.out.println("添加成功"); + } + + # 读取大的二进制数据 + Connection connection = null; + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + + + String sql = "SELECT * FROM test3"; + preparedStatement = connection.prepareStatement(sql); + + resultSet = preparedStatement.executeQuery(); + + + //如果读取到数据,就把数据写到磁盘下 + if (resultSet.next()) { + InputStream inputStream = resultSet.getBinaryStream("blobtest"); + FileOutputStream fileOutputStream = new FileOutputStream("d:\\aa.jpg"); + int len = 0; + byte[] bytes = new byte[1024]; + while ((len = inputStream.read(bytes)) > 0) { + + fileOutputStream.write(bytes, 0, len); + + } + fileOutputStream.close(); + inputStream.close(); + + } + +------------------------------ +获取到自增长的字段 | +------------------------------ + Class.forName("com.mysql.jdbc.Driver"); + Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo", "root", "root"); + PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `test`(`name`) VALUES(?)",Statement.RETURN_GENERATED_KEYS); + preparedStatement.setString(1, "name"); + if (preparedStatement.executeUpdate() > 0) { + ResultSet resultSet = preparedStatement.getGeneratedKeys(); + if (resultSet.next()) { + int id = resultSet.getInt(1); + System.out.println("自增id="+id); + } + } \ No newline at end of file diff --git a/Java/lang/java-lang-Class.java b/Java/lang/java-lang-Class.java new file mode 100644 index 00000000..04636a71 --- /dev/null +++ b/Java/lang/java-lang-Class.java @@ -0,0 +1,15 @@ +------------------------ +Class | +------------------------ + # 类对象 + + # 静态方法 + Class forName(String className) + Class forName(String name, boolean initialize, ClassLoader loader) + * 加载类 + className/name 类名称 + initialize 是否要初始化, 默认 true + loader 使用自定义的类加载器执行 + + + \ No newline at end of file diff --git a/Java/lang/java-lang-ClassLoader.java b/Java/lang/java-lang-ClassLoader.java new file mode 100644 index 00000000..4e5a1cc6 --- /dev/null +++ b/Java/lang/java-lang-ClassLoader.java @@ -0,0 +1,65 @@ +------------------------ +ClassLoader | +------------------------ + # 类加载器, 是一个抽象类 + + # 构造函数 + protected ClassLoader() + protected ClassLoader(ClassLoader parent) + + # 静态方法 + protected static boolean registerAsParallelCapable() + protected final void resolveClass(Class c) + + public static ClassLoader getSystemClassLoader() + public static URL getSystemResource(String name) + public static InputStream getSystemResourceAsStream(String name) + public static Enumeration getSystemResources(String name) + + # 实例函数 + + @Deprecated + protected final Class defineClass(byte[] b, int off, int len) + protected final Class defineClass(String name, byte[] b, int off, int len) + protected final Class defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain) + protected final Class defineClass(String name, java.nio.ByteBuffer b, ProtectionDomain protectionDomain) + * 根据字节数据创建一个类 + + protected Class findClass(String name) + * 空的方法, 自己实现的加载器建议覆写该方法 + + protected Class loadClass(String name, boolean resolve) + public Class loadClass(String name) + * 加载方法 + name 类方法名称 + resolve 是否解析初始化类 + + + protected Package definePackage(String name, String specTitle,String specVersion, String specVendor, String implTitle, String implVersion,String implVendor, URL sealBase) + protected String findLibrary(String libname) + protected final Class findLoadedClass(String name) + protected URL findResource(String name) + protected Enumeration findResources(String name) + protected final Class findSystemClass(String name) + protected Object getClassLoadingLock(String className + protected Package getPackage(String name) + protected Package[] getPackages() + + protected final void setSigners(Class c, Object[] signers) + + + public void clearAssertionStatus() + public final ClassLoader getParent() + public URL getResource(String name) + public InputStream getResourceAsStream(String name) + public Enumeration getResources(String name) + + public void setClassAssertionStatus(String className, boolean enabled) + public void setDefaultAssertionStatus(boolean enabled) + public void setPackageAssertionStatus(String packageName, boolean enabled) + + + + + + diff --git a/Java/lang/java-lang-Double.java b/Java/lang/java-lang-Double.java new file mode 100644 index 00000000..d257421f --- /dev/null +++ b/Java/lang/java-lang-Double.java @@ -0,0 +1,21 @@ +------------------------ +Double | +------------------------ + * double 的包装类 + +------------------------ +Double-类方法/属性 | +------------------------ + Double.POSITIVE_INFINITY //正无穷大 + Double.NEGATIVE_INFINITY //负无穷大 + Double.NaN //非数字 + + + boolean isNaN(double d) + * ??判断是不是数字?? + + + +------------------------ +Double | +------------------------ \ No newline at end of file diff --git a/Java/lang/java-lang-ProcessBuilder.java b/Java/lang/java-lang-ProcessBuilder.java new file mode 100644 index 00000000..24cb633d --- /dev/null +++ b/Java/lang/java-lang-ProcessBuilder.java @@ -0,0 +1,25 @@ +----------------------------- +ProcessBuilder | +----------------------------- + # 子进程任务构建工厂类 + # 构造函数 + ProcessBuilder builder = new ProcessBuilder(); + * 创建 builder + + ProcessBuilder builder = new ProcessBuilder(String... command); + * 创建 builder ,并且设置命令 + + +----------------------------- +ProcessBuilder | +----------------------------- + +----------------------------- +ProcessBuilder - 实例方法 | +----------------------------- + ProcessBuilder command(String... command) + * 设置执行命令 + * 返回 this + + Process start() throws Exception + * 执行 \ No newline at end of file diff --git a/Java/lang/java-lang-Runtime.java b/Java/lang/java-lang-Runtime.java index b9a5458b..98595efa 100644 --- a/Java/lang/java-lang-Runtime.java +++ b/Java/lang/java-lang-Runtime.java @@ -17,6 +17,13 @@ long totalMemory() int availableProcessors(); * 返回到虚拟机的最大可用的处理器数量,不会小余一个 + + void addShutdownHook(Thread hook) + * 添加一个关闭回调,收到系统退出指令的时候,会执行该线程 + * 可以添加多个,但是没法保证执行顺序 + * 如果JVM崩溃,可能不会执行 + * 不能在回调中调用 : System.exit(),否则会卡住 + # Process void prop.destroy(); diff --git a/Java/lang/java-lang-String.java b/Java/lang/java-lang-String.java index d4f3935b..4c5f0f28 100644 --- a/Java/lang/java-lang-String.java +++ b/Java/lang/java-lang-String.java @@ -13,4 +13,36 @@ String format(String format, Object... args) ------------------------ 实例方法 | ------------------------ - \ No newline at end of file + native String intern(); + * 返回常量池中的某字符串,如果常量池中已经存在该字符串,则返回常量池中该对象的引用 + * 否则 + * JDK6及其以前是, 先复制到字符串池, 然后返回字符串池中的字符串引用 + * JDK7及其以后是, 只在字符串池中记录该字符串对象第一次出现的引用, 然后返回(jdk1.7之后, 常量池不仅仅可以存储对象, 还可以存储对象的引用, 会直接将字符串的地址存储在常量池) + + + // 1.1在堆中创建"1"字符串对象 + // 1.2字符串常量池引用"1"字符串对象 + // 1.3s引用指向堆中"1"字符串对象 + String s = new String("1"); + + // 2. 发现字符串常量池中已经存在"1"字符串对象,直接返回字符串常量池中对堆的引用(但没有接收)-->s引用还是指向着堆中的对象 + s.intern(); + + // 3. 发现字符串常量池已经保存了该对象的引用了,直接返回字符串常量池对堆中字符串的引用 + String s2 = "1"; + + // 4. s指向的是堆中对象的引用,s2指向的是在字符串常量池对堆中对象的引用 + System.out.println(s == s2);// false + + --------------------------------------------- + // 1. 在堆中首先创建了两个“1”对象 + // 1.1 +号运算符解析成stringBuilder,最后toString(),最终在堆中创建出"11"对象 + // 1.2 注意:此时"11"对象并没有在字符串常量池中保存引用 + String s3 = new String("1") + new String("1"); + + // 2. 发现"11"对象并没有在字符串常量池中存在,于是将"11"对象在字符串常量池中保存当前字符串的引用,并返回当前字符串的引用 + s3.intern(); + + // 3. 发现字符串常量池已经存在引用了,直接返回(拿到的也是与s3相同指向的引用) + String s4 = "11"; + System.out.println(s3 == s4); // true \ No newline at end of file diff --git a/Java/lang/java-lang-StringBuilder.java b/Java/lang/java-lang-StringBuilder.java new file mode 100644 index 00000000..e1f6b586 --- /dev/null +++ b/Java/lang/java-lang-StringBuilder.java @@ -0,0 +1,22 @@ +-------------------- +StringBuilder | +-------------------- + + +--------------------------- +StringBuilder-实例方法/属性| +--------------------------- + int length() + + AbstractStringBuilder append(String str) + + AbstractStringBuilder insert(int i,String str) + + StringBuilder delete(int start, int end) + + String toString() + + + + + \ No newline at end of file diff --git a/Java/lang/java-lang-System.java b/Java/lang/java-lang-System.java index 9711e7e3..4849c48b 100644 --- a/Java/lang/java-lang-System.java +++ b/Java/lang/java-lang-System.java @@ -19,3 +19,9 @@ void setProperties(Properties props) * 从系统环境变量中读取数据 * 如果不存在,则使用默认值 + +------------------------ +System-格式化输出 | +------------------------ + System.out.printf(str,Object... fmt) + * System.out.printf("你好,我是 %s 今年 %s 岁","KevinBlandy",15);//你好,我是 KevinBlandy 今年 15 岁 diff --git a/Java/lang/java-lang-Thread.java b/Java/lang/java-lang-Thread.java index 21feea12..e08d3168 100644 --- a/Java/lang/java-lang-Thread.java +++ b/Java/lang/java-lang-Thread.java @@ -21,8 +21,54 @@ * 返回当前的线程对象 sleep(long l); * 当前线程停止 l 毫秒 + + Map getAllStackTraces() + * 获取到JVM中所有线程的执行栈 # 实例方法 getName(); - * 返回线程名称 \ No newline at end of file + * 返回线程名称 + + void setPriority(int newPriority) + * 设置线程的优先级 + * Thread 类提供了N多的静态变量值 + + int getPriority() + * 获取线程的优先级 + + void interrupt() + * 中断线程 + + boolean isInterrupted() + * 线程是否被中断 + + void join(); + * 调用该方法的线程会一直阻塞,直到该线程(join 方法的 Thread 线程)执行完毕后才往下执行 + + void setDaemon(boolean on) + * 设置为当前线程的守护线程 + * 必须在调用 start() 方法之前设置 + + void stop(); + * 暴力停止该线程 + + void setContextClassLoader(ClassLoader cl) + ClassLoader getContextClassLoader() + * 设置/获取当前线程程序中使用的 classloader + +--------------------------- +Thread 的中断机制 | +--------------------------- + # 每个线程都有一个 "中断" 标志,这里分两种情况 + + # 线程在sleep或wait(阻塞),join .... + * 此时如果别的进程调用此进程(Thread 对象)的 interrupt()方法,此线程会被唤醒并被要求处理 InterruptedException + * (thread在做IO操作时也可能有类似行为,见java thread api) + * 异常发生后,会重置这个标识位为 false + + # 此线程在运行中 + * 此时如果别的进程调用此进程(Thread 对象)的 interrupt()方法,不会收到提醒,但是此线程的 "中断" 会被设置为 true + * 可以通过 isInterrupted() 查看并作出处理 + + diff --git "a/GIT/GIT-\346\267\261\345\205\245\347\220\206\350\247\243\346\232\202\345\255\230\345\214\272.java" b/Java/lang/java-lang-ThreadGroup.java similarity index 72% rename from "GIT/GIT-\346\267\261\345\205\245\347\220\206\350\247\243\346\232\202\345\255\230\345\214\272.java" rename to Java/lang/java-lang-ThreadGroup.java index b5880330..9294df2a 100644 --- "a/GIT/GIT-\346\267\261\345\205\245\347\220\206\350\247\243\346\232\202\345\255\230\345\214\272.java" +++ b/Java/lang/java-lang-ThreadGroup.java @@ -1,4 +1,4 @@ ------------------------ -GIT-鏆傚瓨鍖 | +ThreadGroup | ------------------------ \ No newline at end of file diff --git a/Java/math/java-math-BigDecimal.java b/Java/math/java-math-BigDecimal.java new file mode 100644 index 00000000..eadc545b --- /dev/null +++ b/Java/math/java-math-BigDecimal.java @@ -0,0 +1,79 @@ +------------------------ +BigDecimal | +------------------------ + # 静态变量 + public final static int ROUND_UP = 0; + public final static int ROUND_DOWN = 1; + public final static int ROUND_CEILING = 2; + public final static int ROUND_FLOOR = 3; + public final static int ROUND_HALF_UP = 4; + public final static int ROUND_HALF_DOWN = 5; + public final static int ROUND_HALF_EVEN = 6; + public final static int ROUND_UNNECESSARY = 7; + + * 各种舍入模式 + # 构造函数 + BigDecimal(char[] in) + BigDecimal(char[] in, int offset, int len) + BigDecimal(char[] in, int offset, int len, MathContext mc) + BigDecimal(char[] in, MathContext mc) + BigDecimal(double val) + BigDecimal(double val, MathContext mc) + BigDecimal(int val) + BigDecimal(int val, MathContext mc) + BigDecimal(String val) + BigDecimal(String val, MathContext mc) + BigDecimal(BigInteger val) + BigDecimal(BigInteger unscaledVal, int scale) + BigDecimal(BigInteger unscaledVal, int scale, MathContext mc) + BigDecimal(BigInteger val, MathContext mc) + BigDecimal(long val) + BigDecimal(long val, MathContext mc) + + +------------------------ +BigDecimal-静态方法 | +------------------------ + BigDecimal valueOf(double val) + BigDecimal valueOf(long val) + BigDecimal valueOf(long unscaledVal, int scale) + + +------------------------ +BigDecimal-实例方法 | +------------------------ + BigDecimal add(BigDecimal augend) + BigDecimal add(BigDecimal augend, MathContext mc) + + BigDecimal subtract(BigDecimal subtrahend) + BigDecimal subtract(BigDecimal subtrahend, MathContext mc) + + BigDecimal multiply(BigDecimal multiplicand) + BigDecimal multiply(BigDecimal multiplicand, MathContext mc) + + BigDecimal divide(BigDecimal divisor) + BigDecimal divide(BigDecimal divisor, int roundingMode) + BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) + BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) + BigDecimal divide(BigDecimal divisor, MathContext mc) + BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode) + * 加减乘除 + newScale 表示保留的小数位数 + roundingMode 指定舍入模式(可以是int或者枚举) + mc 通过指定的 MathContext 处理精度和舍入 + + BigDecimal setScale(int newScale, int roundingMode) + * 格式化小数 + newScale 表示保留的小数位数 + roundingMode 舍入模式 + + + String toString(); + * 有必要时使用科学计数法 + + String toPlainString(); + * 不使用任何指数 + + String toEngineeringString(); + * 有必要时使用工程计数法 + * 工程记数法是一种工程计算中经常使用的记录数字的方法, 与科学技术法类似. 但要求10的幂必须是3的倍数 diff --git a/Java/math/java-math-BigInteger.java b/Java/math/java-math-BigInteger.java new file mode 100644 index 00000000..0c850ad7 --- /dev/null +++ b/Java/math/java-math-BigInteger.java @@ -0,0 +1,62 @@ +--------------------------- +BigInteger | +--------------------------- + # 静态方法 + BigInteger probablePrime(int bitLength, Random rnd) + BigInteger valueOf(long val) + + # 构造函数 + BigInteger(byte[] val) + BigInteger(int signum, byte[] magnitude) + BigInteger(int bitLength, int certainty, Random rnd) + BigInteger(int numBits, Random rnd) + BigInteger(String val) + BigInteger(String val, int radix) + + # 实例方法 + BigInteger abs() + + BigInteger add(BigInteger val) + BigInteger subtract(BigInteger val) + BigInteger multiply(BigInteger val) + BigInteger divide(BigInteger val) + * 加减乘除 + + BigInteger and(BigInteger val) + BigInteger andNot(BigInteger val) + int bitCount() + int bitLength() + byte byteValueExact() + BigInteger clearBit(int n) + BigInteger[] divideAndRemainder(BigInteger val) + double doubleValue() + float floatValue() + BigInteger flipBit(int n) + BigInteger gcd(BigInteger val) + int getLowestSetBit() + int intValue() + int intValueExact() + boolean isProbablePrime(int certainty) + long longValue() + long longValueExact() + + BigInteger max(BigInteger val) + BigInteger min(BigInteger val) + BigInteger mod(BigInteger m) + BigInteger modInverse(BigInteger m) + BigInteger modPow(BigInteger exponent, BigInteger m) + BigInteger negate() + BigInteger nextProbablePrime() + BigInteger not() + BigInteger or(BigInteger val) + BigInteger pow(int exponent) + BigInteger remainder(BigInteger val) + BigInteger setBit(int n) + BigInteger shiftLeft(int n) + BigInteger shiftRight(int n) + short shortValueExact() + int signum() + boolean testBit(int n) + byte[] toByteArray() + String toString(int radix) + BigInteger xor(BigInteger val) \ No newline at end of file diff --git a/Java/math/java-math-Math.java b/Java/math/java-math-Math.java new file mode 100644 index 00000000..1af29d3e --- /dev/null +++ b/Java/math/java-math-Math.java @@ -0,0 +1,53 @@ +-------------------- +Math | +-------------------- + # 包含了大量的静态方法,用于数学计算 + + + double abs(double a); + * 返回绝对值 + + double max(double a,double b); + double min(double a,double b); + * 返回较大/小的一个 + + double sin(double theta); + * 正余弦函数 + + double cos(double theta); + * 余弦函数 + + double tan(double threa); + * 正切函数 + + double asin(double theta); + double acos(double theta); + double atan(double threa); + + double exp(double a); + * 指数函数 + + double log(double a); + * 自然对数函数 + + double pow(double a,double b); + * a的b次方 + + double random(); + * 0 - 1 的随机数 + + double sqrt(double a); + * a的平方根 + + int round(float a) + * 四舍五入 + + double floor(double a) + * 向下取整 + + # 静态属性 + E + * 常数e + PI + * 常数π + \ No newline at end of file diff --git a/Java/math/java-math-MathContext.java b/Java/math/java-math-MathContext.java new file mode 100644 index 00000000..0bf9fed1 --- /dev/null +++ b/Java/math/java-math-MathContext.java @@ -0,0 +1,28 @@ +------------------------ +MathContext | +------------------------ + # 主要的作用是处理运算过程中的精度问题 + # 静态变量 + MathContext UNLIMITED = new MathContext(0, RoundingMode.HALF_UP) + * 不限制长度, 使用四舍五入的方式来舍入小数 + + MathContext DECIMAL32 = new MathContext(7, RoundingMode.HALF_EVEN) + MathContext DECIMAL64 = new MathContext(16, RoundingMode.HALF_EVEN) + MathContext DECIMAL128 = new MathContext(34, RoundingMode.HALF_EVEN) + + # 构造方法 + MathContext(int setPrecision) + MathContext(int setPrecision,RoundingMode setRoundingMode) + MathContext(String val) + + setPrecision + * 精度,一共最长最少长度(包含小数) + + setRoundingMode + * 舍入模式 + + # 实例方法 + + int getPrecision() + RoundingMode getRoundingMode() + \ No newline at end of file diff --git a/Java/math/java-math-RoundingMode.java b/Java/math/java-math-RoundingMode.java new file mode 100644 index 00000000..7a7e51c3 --- /dev/null +++ b/Java/math/java-math-RoundingMode.java @@ -0,0 +1,104 @@ + +------------------------- +RoundingMode | +------------------------- + # 舍入模式 + # 枚举实例 + UP(BigDecimal.ROUND_UP) + 5.5 6 + 2.5 3 + 1.6 2 + 1.1 2 + 1.0 1 + -1.0 -1 + -1.1 -2 + -1.6 -2 + -2.5 -3 + -5.5 -6 + + DOWN(BigDecimal.ROUND_DOWN) + 5.5 5 + 2.5 2 + 1.6 1 + 1.1 1 + 1.0 1 + -1.0 -1 + -1.1 -1 + -1.6 -1 + -2.5 -2 + -5.5 -5 + + CEILING(BigDecimal.ROUND_CEILING) + 5.5 6 + 2.5 3 + 1.6 2 + 1.1 2 + 1.0 1 + -1.0 -1 + -1.1 -1 + -1.6 -1 + -2.5 -2 + -5.5 -5 + + FLOOR(BigDecimal.ROUND_FLOOR) + 5.5 5 + 2.5 2 + 1.6 1 + 1.1 1 + 1.0 1 + -1.0 -1 + -1.1 -2 + -1.6 -2 + -2.5 -3 + -5.5 -6 + + HALF_UP(BigDecimal.ROUND_HALF_UP) (常用,就是传说中的四舍五入) + 5.5 6 + 2.5 3 + 1.6 2 + 1.1 1 + 1.0 1 + -1.0 -1 + -1.1 -1 + -1.6 -2 + -2.5 -3 + -5.5 -6 + + HALF_DOWN(BigDecimal.ROUND_HALF_DOWN) + 5.5 5 + 2.5 2 + 1.6 2 + 1.1 1 + 1.0 1 + -1.0 -1 + -1.1 -1 + -1.6 -2 + -2.5 -2 + -5.5 -5 + + HALF_EVEN(BigDecimal.ROUND_HALF_EVEN) + 5.5 6 + 2.5 2 + 1.6 2 + 1.1 1 + 1.0 1 + -1.0 -1 + -1.1 -1 + -1.6 -2 + -2.5 -2 + -5.5 -6 + + UNNECESSARY(BigDecimal.ROUND_UNNECESSARY) + 5.5 throw ArithmeticException + 2.5 throw ArithmeticException + 1.6 throw ArithmeticException + 1.1 throw ArithmeticException + 1.0 1 + -1.0 -1 + -1.1 throw ArithmeticException + -1.6 throw ArithmeticException + -2.5 throw ArithmeticException + -5.5 throw ArithmeticException + + # 静态方法 + RoundingMode valueOf(int rm) \ No newline at end of file diff --git a/Java/net/java-net-URLClassLoader.java b/Java/net/java-net-URLClassLoader.java new file mode 100644 index 00000000..75131f59 --- /dev/null +++ b/Java/net/java-net-URLClassLoader.java @@ -0,0 +1,10 @@ +---------------------- +URLClassLoader | +---------------------- + # 自定义类加载器一般都实现该接口 + + # 构造函数 + public URLClassLoader(URL[] urls) + public URLClassLoader(URL[] urls, ClassLoader parent) + public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) + diff --git a/Java/net/java-net-ssl.java b/Java/net/java-net-ssl.java new file mode 100644 index 00000000..393e45f5 --- /dev/null +++ b/Java/net/java-net-ssl.java @@ -0,0 +1,167 @@ +------------------------ +ssl | +------------------------ + # SSL + * netscape 公司提出,Server Socket Layer + + # Java的安全套接字扩展 JSSE(Java Secure Socket Extension) + * 为基于SSL和TLS协议的Java网络应用提供了Java API和参考实现 + * 支持数据加密,服务端身份严重,数据完整性,以及可选的客户端身份严重 + + # TLS/SSL + * 目前来说TSL1.0和SSL3.0差别非常小 + + + + # 类库 + javax.net + javax.net.ssl + java.security.cert + + KeyStore + * 密钥证书存储设施 + * 这个对象用于存放安全证书,安全证书一般以文件形式存放 + * KeyStore 负责将证书加载到内存 + + KeyManagerFactory + KeyManager + * 密钥管理器 + * 责选择用于证实自己身份的安全证书,发给通信另一方 + + TrustManagerFactory + TrustManager + * 信任管理器 + * 负责判断决定是否信任对方的安全证书 + + |-X509TrustManager + |-X509Certificate + + + SSLContext + * 对整个SSL/TLS协议的封装,表示了安全套接字协议的实现 + * 主要负责设置安全通信过程中的各种信息,例如跟证书相关的信息 + * 并且负责构建 SSLSocketFactory,SSLServerSocketFactory 和 SSLEngine 等工厂类 + void init(KeyManager[] keyManagers, TrustManager[] trustManagers, SecureRandom secureRandom); + + keyManagers + * 使用证明自身证书,如果为null,系统会创建默认的KeyManager对象,以及关联的KeyStore对象 + * KeyStore对象从系统属性:javax.net.ssl.keyStore 中获取安全证书,如果不存在该属性,那么KeyStore对象的内容为空 + + trustManagers + * 信任的证书,如果为null,系统会创建默认的TrustManager对象,以及关联的KeyStore对象 + * KeyStore对象会从如下步骤去获取安全证书,如果如下步骤为都失败,则KeyStore对象的内容为空 + 1, 尝试从系统属性: javax.net.ssl.trustStore 属性中获取 + 2, 尝试把 ${JAVA_HOME}/jre/lib/security/jssecacerts 文件作为安全证书 + ... + + secureRandom + * 安全的随机数,如果设置为null,则使用默认的 + + + SSLServerSocketFactory + SSLServerSocket(ServerSocket子类) + + SSLSocketFactory + SSLSocket(Socket子类) + + SSLEngine + * SSL非阻塞引擎 + * NIO通信,使用这个类,它让通过过程支持非阻塞的安全通信 + void setUseClientMode(true); + * 当前是客户端模式还是服务端模式 + + void setNeedClientAuth(false); + * 是否要校验客户端的证书 + + void setWantClientAuth(true); + * 希望对方供安全证书,如果对方不提供,连接不会中断,通信继续进行 + + void setNeedClientAuth(false); + * 要求对方必须提供安全证书,如果对方不提供,连接中断,通信无法进行 + + SSLSession + * SSL会话 + * 安全通信握手过程需要一个会话,为了提高通信的效率 + * SSL协议允许多个SSLSocket共享同一个SSL会话,在同一个会话中,只有第一个打开的 SSLSocket 需要进行SSL握手,负责生成密钥及交换密钥,其余SSLSocket都共享密钥信息 + + + + + SecureRandom + + CertificateFactory + +------------------------ +ssl | +------------------------ + +// JKS的证书密码 +char[] password = "123456".toCharArray(); + +// 加载证书文件 +KeyStore keyStore = KeyStore.getInstance("JKS"); +keyStore.load(Files.newInputStream(Paths.get("")),password); + +// KeyManager 选择用于证明自身身份的证书,并请发送给对方 +KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); +keyManagerFactory.init(keyStore,password); +KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); + +// TrustManager 决定是否信任对方的证书 +TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); +trustManagerFactory.init(keyStore); +TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + +SSLContext sslContext = SSLContext.getInstance("TLS"); + +// 设置当前socket使用的证书,以及信任的证书,还有随机源 +sslContext.init(keyManagers,trustManagers,null); + +// 用于nio +SSLEngine sslEngine = sslContext.createSSLEngine(); + +// 用于bio +SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory(); + +------------------------ +ssl | +------------------------ +/** + * + * @param keyPath 当前Socket使用的证书 + * @param keyPassword 密码 + * @param trustPath 信任证书 + * @param trustPassword 密码 + * @return + * @throws Exception + */ +public SSLContext sslContext(InputStream keyPath, String keyPassword, InputStream trustPath, String trustPassword) throws Exception { + + // 加载socket证书 + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(keyPath,keyPassword.toCharArray()); + + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); + keyManagerFactory.init(keyStore,keyPassword.toCharArray()); + KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); + + + // 加载Trust信任证书 + TrustManager[] trustManagers = null; + if(trustPath != null && trustPassword != null){ + + KeyStore trustKeystore = KeyStore.getInstance("JKS"); + trustKeystore.load(trustPath,trustPassword.toCharArray()); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); + trustManagerFactory.init(trustKeystore); + trustManagers = trustManagerFactory.getTrustManagers(); + } + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagers,trustManagers,new SecureRandom()); + + return sslContext; +} + + diff --git a/Netty/NIO-Pipe.java b/Java/nio/java-Pipe.java similarity index 100% rename from Netty/NIO-Pipe.java rename to Java/nio/java-Pipe.java diff --git a/Java/nio/java-buffer-MappedByteBuffer.java b/Java/nio/java-buffer-MappedByteBuffer.java index 8dddadc6..53d35f61 100644 --- a/Java/nio/java-buffer-MappedByteBuffer.java +++ b/Java/nio/java-buffer-MappedByteBuffer.java @@ -1,6 +1,30 @@ ------------------------------- MappedByteBuffer | ------------------------------- - # 用于映射问信息到内存 - # 通过 Channel 获取 - MappedByteBuffer map(MapMode mode,long position, long size); \ No newline at end of file + # 将文件的某一部分或全部映射到内存中, 映射内存缓冲区是个直接缓冲区 + # 它继承:ByteBuffer + # 通过 FileChannel 的 api 获取 + MappedByteBuffer map(MapMode mode,long position, long size); + + MapMode + READ_ONLY + * 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException + READ_WRITE + * 对得到的缓冲区的更改最终将传播到文件 + * 该更改对映射到同一文件的其他程序不一定是可见的 + PRIVATE + * 对得到的缓冲区的更改不会传播到文件, 并且该更改对映射到同一文件的其他程序也不是可见的 + * 会创建缓冲区已修改部分的专用副本 + + + # 方法 + MappedByteBuffer force() + * 缓冲区是READ_WRITE模式下, 此方法对缓冲区内容的修改强行写入文件 + + boolean isLoaded() + * 如果缓冲区的内容在物理内存中, 则返回真, 否则返回假 + + MappedByteBuffer load() + * load()将缓冲区的内容载入内存, 并返回该缓冲区的引用 + + diff --git a/Java/nio/java-channel-Pipe.java b/Java/nio/java-channel-Pipe.java new file mode 100644 index 00000000..63f10a8c --- /dev/null +++ b/Java/nio/java-channel-Pipe.java @@ -0,0 +1,67 @@ +-------------------------------- +Pipe | +-------------------------------- + # jdk4以前管道流需要通过 PipedInputStream, PipedOutputStream 操作流来完成通信 + # jdk4以后, 提供了 Pipe, 通过操作缓冲区来通信 + * Pipe具备一个sourcechannel和一个sinkchannel + * sinkchannel负责往管道写入数据,sourcechannel负责从管道读取数据 + + * 千万不能同一个线程既读又写,因为读取操作会阻塞线程。同一个线程操作管道流会造成线程死锁 + + # 构建 + //打开一个管道 + Pipe pipe = Pipe.open(); + //从管道获取写入数据的channel + SinkChannel sinkChannel = pipe.sink(); + //从管道获取读取数据的channel + SourceChannel sourceChannel = pipe.source(); + + # 演示demo + import java.io.IOException; + import java.nio.ByteBuffer; + import java.nio.channels.Pipe; + import java.nio.channels.Pipe.SinkChannel; + import java.nio.channels.Pipe.SourceChannel; + import java.nio.charset.StandardCharsets; + import java.util.Scanner; + public class Main { + public static void main(String[] args) throws Exception { + //打开一个管道 + Pipe pipe = Pipe.open(); + //从管道获取写入数据的channel + SinkChannel sinkChannel = pipe.sink(); + //从管道获取读取数据的channel + SourceChannel sourceChannel = pipe.source(); + //新启动线程模拟读 + new Thread(() -> { + try { + ByteBuffer byteBuffer = ByteBuffer.allocate(1024); + while(true) { + //返回int,表示读取到了多少字节的数据,如果管道没有数据,该方法会阻塞 + sourceChannel.read(byteBuffer); + //读取完毕后复位,由写改为读 + byteBuffer.flip(); + byte bytes[] = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + System.out.println(Thread.currentThread().getName() + "-收到消息:" + new String(bytes,StandardCharsets.UTF_8)); + //清空缓冲区,由写改为读 + byteBuffer.clear(); + } + } catch (IOException e) { + e.printStackTrace(); + } + }) .start(); + //当前线程模拟写 + try(Scanner scanner = new Scanner(System.in)){ + ByteBuffer byteBuffer = ByteBuffer.allocate(1024); + while(true) { + String line = scanner.nextLine(); + System.out.println(Thread.currentThread().getName() + "-发送消息:" + line); + byteBuffer.put(line.getBytes(StandardCharsets.UTF_8)); + byteBuffer.flip(); + sinkChannel.write(byteBuffer); + byteBuffer.clear(); + } + } + } + } \ No newline at end of file diff --git a/Java/nio/java-file-Options.java b/Java/nio/java-file-Options.java index 13cc4dc7..189e5593 100644 --- a/Java/nio/java-file-Options.java +++ b/Java/nio/java-file-Options.java @@ -25,8 +25,8 @@ WRITE, //打开写权限 APPEND, //在末尾添加,不覆盖以前的数据 TRUNCATE_EXISTING, //如果文件已经存在,并且打开进行WRITE 访问,则其长度将被截断为0。 - CREATE, //如果不存在,则创建新的 - CREATE_NEW, //创建一个新的文件,如果该文件已经存在失败。 + CREATE, //如果不存在,则创建新的,如果存在,则从头开始写入内容 + CREATE_NEW, //创建一个新的文件,如果该文件已经存在,则抛出异常。 DELETE_ON_CLOSE, //在jvm关闭的时候删除文件 SPARSE, //稀疏文件 SYNC, //要求将文件内容或元数据的每次更新都同步写入底层存储设备。 diff --git "a/Java/nio/java-nio-\351\233\266\346\213\267\350\264\235.java" "b/Java/nio/java-nio-\351\233\266\346\213\267\350\264\235.java" new file mode 100644 index 00000000..1eae84ec --- /dev/null +++ "b/Java/nio/java-nio-\351\233\266\346\213\267\350\264\235.java" @@ -0,0 +1,52 @@ +------------------------------ +零拷贝 | +------------------------------ + # 非零拷贝的SocketIO步骤 + 1. read 执行, 导致用户态到内核态发生第一次变化, DMA引擎从磁盘读取(复制)文件到内核缓冲区 + + 2. 把内核缓冲区的数据, 拷贝到用户缓冲区, 内核态到用户态的上下文切换 + + 3. 执行 write 的时候, 把用户缓冲区的数据拷贝到 Socket 缓冲区, 用户态到内核态的切换 + + 4. 数据异步的从 Socket 缓冲区使用 DMA 引擎拷贝到网络协议引擎 , 仍然是内核状态, 不需要切换 + + 5. write 方法返回, 再次从内核状态切换到用户态 + + + + + # mmap 优化 + * mmap 通过内存映射, 把文件映射到内核缓冲区, 用户空间可以共享内核空间的数据 + * 在网络传输的时候, 可以减少拷贝次数 + + * 只需要把内核缓冲区的数据拷贝的 Socket 缓冲区, 减少了一次内存的拷贝 + * 但是没减少上下文的切换 + + * MappedByteBuffer 就是 mmap + + # sendFile + * inux 2.1 版本 提供了 sendFile 函数 + * 数据不经过用户态, 直接从内核缓冲区进入到 Socket Buffer + * 同时, 由于和用户态完全无关, 就减少了一次上下文切换 + + * 只需要2次拷贝 + 1. 使用DMA引擎把文件从磁盘拷贝到内核缓冲区 + 2. 从内核缓冲区把数据拷贝到网络协议栈 + + + + * FileChannel 的 transferTo 和 transferFrom 方法,即对应 Linux 的 sendFile + + + # 总结 + * mmap 和 sendFile 的区别 + * mmap 适合小数据量读写, sendFile 适合大文件传输 + * mmap 需要 4 次上下文切换, 3 次数据拷贝, sendFile 需要 3 次上下文切换, 最少 2 次数据拷贝 + * sendFile 可以利用 DMA 方式, 减少 CPU 拷贝, mmap 则不能(必须从内核拷贝到 Socket 缓冲区) + + + * 需要优化网络传输的性能, 或者文件读写的速度, 尽量使用零拷贝 + * 不仅能较少复制拷贝次数, 还能较少上下文切换, 缓存行污染 + + + diff --git a/Java/reflect/java-reflect-Class.java b/Java/reflect/java-reflect-Class.java index 1014f344..9d71e328 100644 --- a/Java/reflect/java-reflect-Class.java +++ b/Java/reflect/java-reflect-Class.java @@ -33,7 +33,7 @@ Constructor getConstructor(Class... parameterTypes) Field[] getFields(); * 返回该类所有 public 修饰的字段对象 - Field[] stsgetDeclaredFields() + Field[] getDeclaredFields() * 返回该类所有的字段对象 Method getMethod(String name, Class... parameterTypes); @@ -53,6 +53,14 @@ Field[] stsgetDeclaredFields() boolean isArray(); * 是否是数组 + + boolean isPrimitive(); + * 判断是否是原始数据类型类对象 + int.class.isPrimitive() => true + Integer.class.isPrimitive() => false + + boolean isInstance(Object obj); + * 判断指定的对象,是否是当前class的类型,或者子类 Class[] getInterfaces() * 获取当前类实现的所有接口 @@ -68,7 +76,28 @@ Class[] getInterfaces() T[] getEnumConstants() * 如果此 Class 对象不表示枚举类型,则返回枚举类的元素或 null。 * 以声明顺序返回一个数组,该数组包含构成此 Class 对象所表示的枚举类的值,或者在此 Class 对象不表示枚举类型时返回 null + + + int getModifiers() + * 返回权限修饰的表示数值(public,private,native,final....) + Class getSuperclass(); + * 获取直接父类类实例 + + Type getGenericSuperclass() + * 返回父类,如果是泛型的话,返回:ParameterizedType + * 可以获取到泛型列表 + Class clazz = Sub.class; + Type type = clazz.getGenericSuperclass(); + if(type instanceof ParameterizedType){ // 泛型父类 + ParameterizedType parameterizedType = (ParameterizedType) type; + // 获取到父类的泛型列表 + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + Stream.of(actualTypeArguments).forEach(System.out::println); + } + + + --------------------- 静态方法 | diff --git a/Java/reflect/java-reflect-Field.java b/Java/reflect/java-reflect-Field.java index 6b2374eb..a021e2aa 100644 --- a/Java/reflect/java-reflect-Field.java +++ b/Java/reflect/java-reflect-Field.java @@ -23,4 +23,10 @@ getAnnotation(Class annotationClass) * 获取该字段上所有的注解 boolean isAnnotationPresent(Class annotationClass); - * 判断是否有指定类型的注解标识在该属性 \ No newline at end of file + * 判断是否有指定类型的注解标识在该属性 + + Class getType() + * 返回数量的类型 + + int getModifiers() + * 返回权限修饰的表示数值(public,private,native,final....) \ No newline at end of file diff --git a/Java/reflect/java-reflect-Method.java b/Java/reflect/java-reflect-Method.java index f1811a5b..f42d8b2a 100644 --- a/Java/reflect/java-reflect-Method.java +++ b/Java/reflect/java-reflect-Method.java @@ -16,4 +16,36 @@ getAnnotation(Class annotationClass) * 获取该方法所有的注解 boolean isAnnotationPresent(Class annotationClass); - * 判断是否有指定类型的注解标识在该方法 \ No newline at end of file + * 判断是否有指定类型的注解标识在该方法 + + int getModifiers() + * 返回权限修饰的表示数值(public,private,native,final....) + + Class getReturnType() + * 返回 return 的Class类类型 + + boolean isBridge() + * 是否是泛型桥接方法 + * 桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容 + * 由编译器自动生成的方法 + interface Parent { + void foo(T t); + } + + class Sub implements Parent { + @Override + public void foo(String s) { + System.out.println(s); + } + // JVM编译器生成的桥接方法 + // public void foo(Object s) { + // this.foo((String) s); + // } + } + + Method bridgeMethod = Sub.class.getMethod("foo",Object.class); + System.out.println(bridgeMethod.isBridge()); // true + + Method method = Sub.class.getMethod("foo",String.class); + System.out.println(method.isBridge()); // false + diff --git a/Java/reflect/java-reflect-Modifier.java b/Java/reflect/java-reflect-Modifier.java new file mode 100644 index 00000000..c77374af --- /dev/null +++ b/Java/reflect/java-reflect-Modifier.java @@ -0,0 +1,30 @@ +------------------------ +Modifier | +------------------------ + * 一个工具类,可以检查类的字段,方法,构造函数的访问级别 + * Method Field Class Constructor 都几乎具备方法来获取访问级别 + int getModifiers() + + +------------------------ +Modifier-静态方法 | +------------------------ + boolean isAbstract(int mode) + * 是否是抽象的 + + boolean isFinal(int model) + * 是否被 final 修饰 + + boolean isNative(int model) + * 是否是本地方法 + + boolean isInterface(int model) + * 是否是接口 + + + + ... + String toString(int model) + * 把指定的model,以String形式返回 + + diff --git a/Java/reflect/java-reflect-Parameter.java b/Java/reflect/java-reflect-Parameter.java new file mode 100644 index 00000000..b875a148 --- /dev/null +++ b/Java/reflect/java-reflect-Parameter.java @@ -0,0 +1,29 @@ +------------------------ +Parameter | +------------------------ + # jdk8鐨勬柊api,琛ㄧず鏂规硶鐨勫弬鏁板璞 + # 涓鑸槸閫氳繃 Method 瀵硅薄鐨刟pi鑾峰彇 + Parameter[] parameters = method.getParameters(); + + # 淇濈暀鍙傛暟鍚嶈繖涓閫夐」鐢辩紪璇戝紑鍏 javac -parameters鎵撳紑,榛樿鏄叧闂.... + +------------------------ +Parameter-灞炴 | +------------------------ + +------------------------ +Parameter-鏂规硶 | +------------------------ + + String getName() + * 鑾峰彇鍙傛暟鐨勫悕绉 + + + Class getType() + * 鑾峰彇鍙傛暟鐨勭被鍨 + + T getAnnotation(Class annotationClass) + * 鑾峰彇鍙傛暟涓婄殑娉ㄨВ + + + diff --git a/Java/reflect/java-reflect-ParameterizedType.java b/Java/reflect/java-reflect-ParameterizedType.java new file mode 100644 index 00000000..9c66a72a --- /dev/null +++ b/Java/reflect/java-reflect-ParameterizedType.java @@ -0,0 +1,41 @@ +---------------------------- +ParameterizedType | +---------------------------- + # 接口,基础自 Type + # 抽象方法 + Type[] getActualTypeArguments(); + * 返回泛型列表 + + Type getRawType(); + Type getOwnerType(); + + +---------------------------- +获取泛型列表 | +---------------------------- + abstract class Parent { + abstract void foo(T t,R r); + } + + class Sub extends Parent { + @Override + public void foo(String s,Integer i) { + System.out.println(s + ":" + i); + } + } + + Class clazz = Sub.class; + + Type type = clazz.getGenericSuperclass(); + // 泛型父类 + if(type instanceof ParameterizedType){ + + ParameterizedType parameterizedType = (ParameterizedType) type; + + // 获取到父类的泛型列表 + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + + Stream.of(actualTypeArguments).forEach(System.out::println); + //class java.lang.String + //class java.lang.Integer + } \ No newline at end of file diff --git a/Java/sun/Unsafe.java b/Java/sun/Unsafe.java new file mode 100644 index 00000000..bc570263 --- /dev/null +++ b/Java/sun/Unsafe.java @@ -0,0 +1,7 @@ +---------------------------- +创建Unsafe类 | +---------------------------- + # 反射方法创建 + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + Unsafe unsafe = (Unsafe) field.get(null); \ No newline at end of file diff --git a/Java/text/java-text-DecimalFormat.java b/Java/text/java-text-DecimalFormat.java index 7e5a140d..e8696f89 100644 --- a/Java/text/java-text-DecimalFormat.java +++ b/Java/text/java-text-DecimalFormat.java @@ -1,15 +1,38 @@ ------------------------ DecimalFormat | ------------------------ - # 强大的数字格式化工具 + # 强大的数字格式化工具, 继承:NumberFormat # DecimalFormat 类主要靠 # 和 0 两种占位符号来指定数字长度。 # 0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置。 + # 常用 + // 可以无参数构造,有参数构造 参数有语法,详见底部。参考"0.0","0.0¤","0.0%"。 + DecimalFormat df = new DecimalFormat(); + // 设置国家货币符号 参数为ISO 4217标准,如果构造参数添加‘¤’符号,参考--> 5211314¥ + df.setCurrency(Currency.getInstance("CNY")); + // 设置最多保留几位.参考--> 5211314.00 + df.setMaximumFractionDigits(2); + // 设置分组大小.参考--> 5,211,314 + df.setGroupingSize(3); + // 设置乘以的倍数.参考--> 521131400 + df.setMultiplier(100); + // 设置正数前缀,参考--> @5211314 + df.setPositivePrefix("@"); + // 设置正数后缀 但是替换掉 已有字符 参考--> 5211314@ + df.setPositiveSuffix("@"); + // 设置负数前缀,只对负数有效 参考-->@-1 + df.setNegativePrefix("@"); + // 设置负数后缀 但是替换掉 已有字符 只对负数有效 参考--> -1@ + df.setNegativeSuffix("@"); + // 设置四舍五入的模式 详见 RoundingMode 类 写的 非常详细 + df.setRoundingMode(RoundingMode.DOWN); + // 格式化 成 想要的结果 + df.format(5211314); ------------------------ demo | ------------------------ - doublepi=3.1415927; //圆周率 + double pi=3.1415927; //圆周率     //取一位整数     System.out.println(new DecimalFormat("0").format(pi));   //3 @@ -25,7 +48,7 @@     //以百分比方式计数,并取两位小数     System.out.println(new DecimalFormat("#.##%").format(pi)); //314.16%    -    longc=299792458;  //光速 + long c = 299792458;  //光速     //显示为科学计数法,并取五位小数     System.out.println(new DecimalFormat("#.#####E0").format(c)); //2.99792E8 @@ -40,4 +63,6 @@     System.out.println(new DecimalFormat("光速大小为每秒,###米。").format(c)); # DecimalFormat 类主要靠 # 和 0 两种占位符号来指定数字长度。 - # 0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置。 \ No newline at end of file + # 0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置。 + + \ No newline at end of file diff --git a/Java/text/java-text-NumberFormat.java b/Java/text/java-text-NumberFormat.java new file mode 100644 index 00000000..141b89c9 --- /dev/null +++ b/Java/text/java-text-NumberFormat.java @@ -0,0 +1,11 @@ +------------------------- +NumberFormat | +------------------------- + # 数字的格式化处理, 抽象类 + + # 构造/静态函数(工厂) + NumberFormat getCurrencyInstance() + NumberFormat getCurrencyInstance(Locale inLocale) + + Locale[] getAvailableLocales() + \ No newline at end of file diff --git a/Java/time/java-time-DateTimeFormatter.java b/Java/time/java-time-DateTimeFormatter.java index 434f3dd8..4f197bbe 100644 --- a/Java/time/java-time-DateTimeFormatter.java +++ b/Java/time/java-time-DateTimeFormatter.java @@ -3,7 +3,7 @@ --------------------------- # 时间格式化工具类 # 构造 - DateTimeFormatter DateTimeFormatter.ofPatterm("yyy-MM-dd"); + DateTimeFormatter DateTimeFormatter.ofPattern("yyy-MM-dd"); * 根据指定的时间格式,返回 DateTimeFormatter 对象 # 系统预定义了大量的格式化对象 diff --git a/Java/time/java-time-LocalDateTime.java b/Java/time/java-time-LocalDateTime.java index 7c7e19b1..0fdbef88 100644 --- a/Java/time/java-time-LocalDateTime.java +++ b/Java/time/java-time-LocalDateTime.java @@ -12,6 +12,12 @@ LocalDateTime parse(CharSequence text, DateTimeFormatter formatter); * 根据 formatter 来格式出 LocalDateTime 对象 + + LocalDateTime of(LocalDate date, LocalTime time) + * 根据日期和时间构造 + * 可以构造一个指定日期(今天)的最小时间和最大时间 + LocalDateTime.of(LocalDate.now(),LocalTime.MAX); 2019-04-18 23:59:59.999999999 + LocalDateTime.of(LocalDate.now(),LocalTime.MIN); 2019-04-18 00:00 ------------------------- LocalDateTime-api | @@ -29,6 +35,11 @@ * 返回分钟 int getSecond(); * 返回秒 + + int get(TemporalField field) + * 根据参数获取指定的数据 + * 可以使用枚举:ChronoField + # 设置相关 LocalDateTime withDayOfMonth(int num); * 设置月中的天数 @@ -45,6 +56,10 @@ LocalDateTime plusMonths(int month); * 添加多少个月 * '还有很多添加时分秒的API,都一样' + + LocalDateTime minusWeeks(long weeks) + * 返回N周之前的现在时间 + * '还有很多添加时分秒的API,都一样' # 格式化相关 String formart(DateTimeFormatter matter); diff --git a/Java/time/java-time-Preiod.java b/Java/time/java-time-Period.java similarity index 91% rename from Java/time/java-time-Preiod.java rename to Java/time/java-time-Period.java index 5475d602..9ced829f 100644 --- a/Java/time/java-time-Preiod.java +++ b/Java/time/java-time-Period.java @@ -1,5 +1,5 @@ ------------------------------ -Preiod | +Period | ------------------------------ # 计算两个日期之间的间隔 # 构造(使用Preiod静态方法) @@ -8,7 +8,7 @@ ------------------------------ -Preiod-API | +Period-API | ------------------------------ getYears(); * 获取年份 diff --git a/Java/time/java-time.java b/Java/time/java-time.java index 5e94a368..c4452341 100644 --- a/Java/time/java-time.java +++ b/Java/time/java-time.java @@ -8,12 +8,41 @@ |-LocalTime //本地时间 |-LocalDateTime //本地日期时间 |-Instant //时间戳 + |-DayOfWeek + |-Year + |-Month java.time.chrono java.time.format |-DateTimeFormatter |-DateTimeFormatterBuilder |-DecimalStyle java.time.temporal + |-Temporal + |-ChronoField + |-ChronoUnit + |-TemporalAdjuster java.time.zone - \ No newline at end of file + +------------------------ +java.time demos | +------------------------ + +// 当天开始,结束 +LocalDateTime minDay = LocalDateTime.of(LocalDate.now(),LocalTime.MIN); +LocalDateTime maxDay = LocalDateTime.of(LocalDate.now(),LocalTime.MAX); + +// 本周开始,结束 +LocalDateTime minWeak = LocalDateTime.of(LocalDate.now().with(DayOfWeek.MONDAY),LocalTime.MIN); +LocalDateTime maxWeak = LocalDateTime.of(LocalDate.now().with(DayOfWeek.SUNDAY),LocalTime.MAX); + +// 本月开始,结束 +LocalDateTime minMonth = LocalDateTime.of(LocalDate.now().withDayOfMonth(1),LocalTime.MIN); +LocalDateTime maxMonth = LocalDateTime.of(LocalDate.now().withDayOfMonth(LocalDate.now().lengthOfMonth()),LocalTime.MAX); + +// 本年开始,结束 +LocalDateTime minYear = LocalDateTime.of(LocalDate.now().withDayOfYear(1),LocalTime.MIN); +LocalDateTime maxYear = LocalDateTime.of(LocalDate.now().withDayOfYear(LocalDate.now().lengthOfYear()),LocalTime.MAX); + + + diff --git a/Java/util/java-util-Arrays.java b/Java/util/java-util-Arrays.java index 0f42af5a..e079e292 100644 --- a/Java/util/java-util-Arrays.java +++ b/Java/util/java-util-Arrays.java @@ -14,3 +14,8 @@ * 把可变长数组转换为LIST集合 Stream stream(T...t); * 转换为流 + + T[] copyOf(T[] original, int newLength) + * 扩充原数组,original ,newLength指定新的长度 + * 返回新的数组,并且会把旧数组的元素复制到新数组,空出来的位置填充默认值 + \ No newline at end of file diff --git a/Java/util/java-util-CRC32.java b/Java/util/java-util-CRC32.java new file mode 100644 index 00000000..7c9ae1dc --- /dev/null +++ b/Java/util/java-util-CRC32.java @@ -0,0 +1,30 @@ +---------------------------- +CRC32 | +---------------------------- + # 循环冗余检查 + * 在数据存储和数据通讯领域, 为了保证数据的正确, 不得不采用检错的算法 + + + # 构造函数 + public CRC32() + + + # 实例函数 + void update(int b) + void update(byte[] b, int off, int len) + void update(byte[] b) + void update(ByteBuffer buffer) + * 添加校验数据 + + void reset() + * 重置校验 + + long getValue() + * 获取计算出的crc32值 + + # demo + CRC32 crc32 = new CRC32(); + crc32.update(new byte[]{0,0,0,0,0,0}); + long value = crc32.getValue(); + System.out.println(value); + diff --git a/Java/util/java-util-Calendar.java b/Java/util/java-util-Calendar.java index 4732dded..6db5d7ad 100644 --- a/Java/util/java-util-Calendar.java +++ b/Java/util/java-util-Calendar.java @@ -26,4 +26,9 @@ |--把日历类转换成 Date 类。返回的是 Date 对象。 long getTimeInMillis() |-转换为毫秒值 - \ No newline at end of file + + getActualMaximum(int field) + |-获取指定'数据的'的最大值 + |-数据可以是月份,(获取指定月最多只有多少天) + getActualMinimum(in field) + |--获取指定'数据的'的最小值 \ No newline at end of file diff --git a/Java/util/java-util-Collections.java b/Java/util/java-util-Collections.java index b014fcfd..06180258 100644 --- a/Java/util/java-util-Collections.java +++ b/Java/util/java-util-Collections.java @@ -8,4 +8,11 @@ Collections-静态字段/方法| ------------------------- boolean addAll(Collection c, T... elements) - * 把变长数据都存入c \ No newline at end of file + * 把变长数据都存入c + + void shuffle(List list); + * 随机打乱集合数据 + + void shuffle(List list, Random rnd) + * 同上,指定的 Random + diff --git a/Java/util/java-util-Formattable.java b/Java/util/java-util-Formattable.java new file mode 100644 index 00000000..5c52e84d --- /dev/null +++ b/Java/util/java-util-Formattable.java @@ -0,0 +1,8 @@ +---------------------------- +Formattable | +---------------------------- + * 是一个接口,只有一个抽象方法 + void formatTo(Formatter formatter, int flags, int width, int precision); + + * 如果类实现了该接口,那么在使用字符串(%s) format 实例的时候会调用该方法(否则会调用 toString()) + diff --git a/Java/util/java-util-LinkedHashMap.java b/Java/util/java-util-LinkedHashMap.java new file mode 100644 index 00000000..edaf31e5 --- /dev/null +++ b/Java/util/java-util-LinkedHashMap.java @@ -0,0 +1,30 @@ +--------------------------- +LinkedHashMap | +--------------------------- + # 继承自 hashMap,实现了 Map 接口 + # 双向链表(头指针,尾指针)实现 + + # 构造 + LinkedHashMap(int initialCapacity) + LinkedHashMap(int initialCapacity, float loadFactor) + LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) + LinkedHashMap(Map m) + + initialCapacity + * 初始容量 + + loadFactor + * 负载因子 + + accessOrder + * 是否安装访问时间进行排序 + * 最近访问的排在链表前面 + * 对元素的访问,会使元素移动到链表顶部 + + # 方法 + protected boolean removeEldestEntry(Map.Entry eldest); + * LinkedHashMap自带的判断是否删除最老的元素方法,默认返回false,即不删除老数据 + * 子类重写这个方法,当满足一定条件时删除老数据(链表尾) + + + diff --git a/Java/util/java-util-Logger.java b/Java/util/java-util-Logger.java new file mode 100644 index 00000000..a93999c4 --- /dev/null +++ b/Java/util/java-util-Logger.java @@ -0,0 +1,117 @@ +------------------------ +Logger | +------------------------ + * jdk级别的logger + * 关系图谱(跟python差不多) + logger 日志记录器 + |-handler 处理器 + |formatter 格式化器 + |filter 过滤器 + + * 涉及组件 + Logger + * 日志记录器 + LogRecord + * 一个日志对象,包含了消息,时间戳,类.等等日志的信息 + Filter + * 日志过滤器,控制哪些日志要输出 + Formatter + * 日志格式化,输出自己想要的日志信息 + Handler + * 日志控制器,用于控制日志的输出 + +------------------------ +Logger-静态方法/属性 | +------------------------ + GLOBAL_LOGGER_NAME = "global" + + Logger getGlobal() + * 获取全局的一个日志记录器对象 + + Logger getAnonymousLogger() + Logger getAnonymousLogger(String resourceBundleName) + + Logger getLogger(String name) + * 获取指定名称的日志记录器 + + Logger getLogger(String name, String resourceBundleName) + * 获取指定名称的日志记录器,并且指定资源包(国际化)的名称 + +------------------------ +Logger-实例方法/属性 | +------------------------ + void setLevel(Level newLevel) + * 设置日记级别,参数就是 Level 实例 + * 包含一下日志级别 + + Level.SEVERE; + Level.WARNING; + Level.INFO; + Level.CONFIG; + Level.FINE; + Level.FINER; + Level.FINEST; + Level.ALL; + Level.OFF; + + * 也可以使用 Level 的静态 parse(String name) api来获取指定名称的日志级别实例 + + void fine(""); + void finer(""); + void finest(""); + void config(""); + void info(""); + void severe(""); + void warning(""); + * 以上api表示不同级别的日志输出 + + void log(Level level, String msg) + * 以指定的日志级别输出日志 + + void log(LogRecord record) + * 以日志记录器对象的形式记录一个日志 + * LogRecord 属性/方法 + record.getLevel(); //获取日志级别 + record.getMessage(); //获取日志正文 + record.getMillis(); //获取时间戳 + record.getClass(); + record.getSequenceNumber(); //获取日志的唯一序列号 + record.getParameters(); + record.getLoggerName(); + record.getSourceClassName(); + + throwing(String sourceClass, String sourceMethod, Throwable thrown) + * 一般用于在 catch 内部,发生异常的时候进行日志记录 + + void setUseParentHandlers(boolean useParentHandlers) + * 是否使用父级logger的hanlder处理器 + + void addHandler(Handler handler) + * 添加一个hanlder到logger + * handler 默认添加一个 ConsoleHandler ,也就数以 System.err 的形式输出到屏幕 + * 可以通过扩展 Handler/StreamHandler 类来自定义处理器 + * 系统预定义的handler + FileHandler + * new FileHandler("c:\\log.xml") + * 保存日志到指定文件的handler,以xml形式保存 + * FileHandler 实例的方法/属性 + void setEncoding(String encoding) + * 设置字符集 + void setFormatter(Formatter newFormatter) + * 设置一个日志格式化formatter,来生成不格式的日志 + * 也可以自己扩展 Formatter 类 + * Formatter 只用实现一个方法,把一个日志记录对象,转换为 String + @Override + public String format(LogRecord record) { + return null; + } + + void publish(LogRecord record) + * 添加一个日志记录对象 + + void setFilter(Filter newFilter) + * 设置一个过滤器 + * Filter 是一个接口,只有一个抽象方法,用于判断是否要记录日志 + public boolean isLoggable(LogRecord record); + + diff --git a/Java/util/java-util-PriorityQueue.java b/Java/util/java-util-PriorityQueue.java new file mode 100644 index 00000000..96a4276a --- /dev/null +++ b/Java/util/java-util-PriorityQueue.java @@ -0,0 +1,33 @@ +------------------------ +PriorityQueue | +------------------------ + # 优先队列 + # 内部默认是最小堆 + * 最小权重值,会被先出队 + + # 构造方法 + public PriorityQueue(int initialCapacity) ; + public PriorityQueue(Comparator comparator); + + # 实例方法 + + poll(); + * 出队 + E remove() + * 出队 + peek(); + * 看一下队首元素 + offer(E e); + * 入队 + add(E e); + * 入队 + addAll(Collection c); + * 入队所有 + size(); + * 长度 + isEmpty(); + * 是否为空 + contains(Object o); + * 是否包含 + + \ No newline at end of file diff --git a/Java/util/java-util-Scanner.java b/Java/util/java-util-Scanner.java index 9fa1cfd3..e70bece1 100644 --- a/Java/util/java-util-Scanner.java +++ b/Java/util/java-util-Scanner.java @@ -3,10 +3,26 @@ -------------------- # 扫描器 # 我用得最多的就是读取屏幕的输入(会阻塞线程) - # Demo - Scanner scanner = new Scanner(System.in); - - scanner.nextInt(); - scanner.next(); - scanner.nextLine(); + * 还可以用于读取文件,字符等等.... + # 构造方法 + Scanner(InputStream in); + Scanner(Readable source); + Scanner(InputStream source, String charsetName) + Scanner(File source) + # jdk1.5以前读取屏幕输入 + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); + bufferedReader.readLine(); + +-------------------- +Scanner - 方法 | +-------------------- + int nextInt(); + String next(); + String line nextLine(); + boolean hasNext() + useDelimiter(String pattern); + * 使用指定的正则去分割 + useLocale(Locale locale) + * 使用指定的语言环境 + \ No newline at end of file diff --git a/Java/util/java-util-StringJoiner.java b/Java/util/java-util-StringJoiner.java new file mode 100644 index 00000000..4b9b5fb0 --- /dev/null +++ b/Java/util/java-util-StringJoiner.java @@ -0,0 +1,12 @@ +------------------------ +StringJoiner | +------------------------ + # JDK9的新类库 + # 几行代码看明白 + StringJoiner stringJoiner = new StringJoiner(",","[","]"); + + for(int x = 0 ;x < 10 ; x++) { + stringJoiner.add(x + ""); + } + + System.out.println(stringJoiner.toString()); // [0,1,2,3,4,5,6,7,8,9] \ No newline at end of file diff --git a/Java/util/java-util-WeakHashMap.java b/Java/util/java-util-WeakHashMap.java new file mode 100644 index 00000000..30482f47 --- /dev/null +++ b/Java/util/java-util-WeakHashMap.java @@ -0,0 +1,11 @@ +-------------------------------- +WeakHashMap | +-------------------------------- + # 实现了 Map 接口 + * 而且键和值都可以是null + + # 特点 + * 在 WeakHashMap 中,当某个键不再正常使用时,会被从 WeakHashMap 中被自动移除 + * 更精确地说, 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收 + * 某个键被终止时, 它对应的键值对也就从映射中有效地移除了 + diff --git a/Java/util/java-util-map-HashMap.java b/Java/util/java-util-map-HashMap.java new file mode 100644 index 00000000..fc2247d2 --- /dev/null +++ b/Java/util/java-util-map-HashMap.java @@ -0,0 +1,56 @@ +-------------------- +HashMap | +-------------------- + # JDK 1.7及以前使用 Hash 表 + 链表实现 + # JDK 1.8及以后使用 Hash 表 + 红黑树实现(优化了查询效率) + * 当触发了一定的条件后,会把链表转换为红黑树 + + # Hash表的尺寸和容量非常的重要 + * 一般来说,Hash表这个容器当有数据要插入时,都会检查容量有没有超过设定的thredhold,如果超过,需要增大hash表的尺寸 + * 但是这样一来,整个hash表里的无素都需要被重算一遍,这叫rehash,这个成本相当的大 + + # 核心的成员变量 + static final int TREEIFY_THRESHOLD = 8; + * 用于判断是否需要将链表转换为红黑树的阈值 + * 如果链表的长度大于了该值,大于了该值就会转换为红黑树 + + int size; + * 存储的数据数量 + + int modCount; + * 修改的次数 + + float loadFactor; + * 负载因子,决定了什么时候会触发扩容 + 容器大小 x 负载因子 = 触发扩容的大小 + + int threshold; + * 调整Map大小的下一个值 + + Node[] table; + * 存放数据的数组 + + Set> entrySet; + + # HashMap 在并发场景下使用时容易出现问题 + * 多线程put操作后,get操作导致死循环(据说jdk8修复了这个问题) + HashMap map = new HashMap(); + for (int i = 0; i < 1000; i++) { + new Thread(new Runnable() { + @Override + public void run() { + map.put(UUID.randomUUID().toString(), ""); + } + }).start(); + } + + * HashMap 扩容的时候会调用 resize() 方法 + * 这里的并发操作容易在一个桶上形成环形链表 + * 当获取一个不存在的 key 时,计算出的 index 正好是环形链表的下标就会出现死循环 + + + * 多线程put非null元素后,get操作得到null值(导致元素丢失,这个问题jdk8没有修复) + + + + diff --git "a/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/Queue.java" "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/Queue.java" new file mode 100644 index 00000000..b6f337fb --- /dev/null +++ "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/Queue.java" @@ -0,0 +1,28 @@ +---------------- +Queue | +---------------- + # java5.0增加的两类安全队列 + 1,Queue + + 2,BlockingQueue + +---------------- +Queue | +---------------- + # 是一个队列接口 + # 用来保存临时数据 + # 提供了很多实现 + ConcurrentLinkedQueue + * 传统的先进先出的队列 + LinkedList + PriorityQueue + * 优先队列 + + # Queue 的操作都不会阻塞队列 + # 如果元素为空,那么获取操作返回null + +---------------- +BlockingQueue | +---------------- + # 扩展了 Queue,提供可阻塞的插入和获取操作 + # 在生产者和消费者模型中,这个很重要 \ No newline at end of file diff --git "a/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/juc-AtomicReference.java" "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/juc-AtomicReference.java" new file mode 100644 index 00000000..692035f2 --- /dev/null +++ "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/juc-AtomicReference.java" @@ -0,0 +1,3 @@ + +AtomicReference + diff --git "a/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/juc-BlockingQueue.java" "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/juc-BlockingQueue.java" new file mode 100644 index 00000000..cdfc6caf --- /dev/null +++ "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/juc-BlockingQueue.java" @@ -0,0 +1,4 @@ +---------------------------- +BlockingQueue | +---------------------------- + # 扩展了 Queue,提供可阻塞的插入和获取操作 \ No newline at end of file diff --git "a/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/volatile.java" "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/volatile.java" new file mode 100644 index 00000000..e8fd5bae --- /dev/null +++ "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/volatile.java" @@ -0,0 +1,16 @@ +------------------------ +volatile | +------------------------ + # 操作 volatile 变量不会加锁,所以不会阻塞 + # 是一种比 synchronized 关键字更轻量级的同步机制 + + # volatile 变量通常用作某个操作:完成,发生中断或者状态变化的标志 + # 满足如下条件的时候,才应该使用 volatile 变量 + + 1,对变量的写入操作不依赖变量的当前值,或者可以保证只有单个线程能更新变量的值 + * 例如: i++,这个变量的自增就依赖于当前值 + 2,该变量不会与其他状态变量一起纳入不变性条件中 + 3,在访问变量时不需要加锁 + + + diff --git "a/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\345\205\245\351\227\250.java" "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\345\205\245\351\227\250.java" new file mode 100644 index 00000000..8b3c133b --- /dev/null +++ "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\345\205\245\351\227\250.java" @@ -0,0 +1,105 @@ + +# 重入 + * Foo线程已经获取了a锁,Foo再次获取a锁,是OK可以成功获取到的,这个就是重入 + class Bar{ + public synchronized void test() { + } + } + public class Foo extends Bar{ + public synchronized void test() { + /* + 当前线程已经持有了this这把锁,访问父类的方法,如果没有重入机制,那么就会一直阻塞.因为this锁已经被持有了 + + */ + super.test(); + } + } + + * 重入的实现 + 1,为这个锁设置了一个计数器,当有一个线程持有了这个锁的时候,计数器 +1 + 2,当线程退出同步代码块的时候,计数器 -1,当计数器为0的时候,这个锁将会被释放 + + +# 非原子性的64位操作 + * jvm规定,变量的声明赋值,是一个原子操作,但是存在一个例外 + * 非 volatile 类型的64位数值变量(double & long),jvm允许把64位的读和写操作分为两个32位的读和写 + * 当读取一个非 volatile 的 long 时,读写不在一个线程,那么很可能读到某个值的高32位或者低32位 + * 在多线程环境下,使用共享且可变的 long,double 都是有安全问题的,应该加锁或者使用 volatile 关键字声明 + + +# 同步容器类要遵守同步策略 + * 支持客户端加锁 + * 问题 + public static Object getLast(Vector vector) { + int index = vector.size(); + return vector.get(index - 1); + } + public static Object removeLast(Vector vector) { + int index = vector.size(); + return vector.remove(index - 1); + } + A -> size = 10 -> removeLast() + b -> size = 10 -> 失去执行权 -> getLast(); //异常数组越界 + * 解决,客户端加锁 + public static Object getLast(Vector vector) { + synchronized (vector){ + int index = vector.size(); + return vector.get(index - 1); + } + + } + public static Object removeLast(Vector vector) { + synchronized (vector){ + int index = vector.size(); + return vector.remove(index - 1); + } + } + + +# 如果不希望在迭代期间对数据加锁,那么一种替代方法就是克隆容器,并在副本上进行迭代 + * 由于副本封闭在线程内,因此其他线程不会在迭代期间对其进行修改 + * 克隆的过程也是需要加锁的 + +# 容器类的hashCode和equals,toString(),contains....等等也会间接性的执行迭代操作 + * 这些间接的迭代操作都有可能会抛出 ConcurrentModificationException + +# 一些类库 + Queue + BlockingQueue + LinkedBlockingDeque + ArrayBlockingQueue + PriorityBlockingQueue + SynchronousQueue + ConcurrentLinkedQueue + LinkedList + PriorityQueue + + Deque + BlockingDeque + LinkedBlockingDeque + ArrayDeque + + Map + ConcurrentHashMap + ConcurrentSkipListMap + + Set + ConcurrentSkipListSet + CopyOnWriteArraySet + + List + CopyOnWriteArrayList + + AtomicReference + Semaphore + CountDownLatch + FutureTask + CyclicBarrier + CompletionService + ExecutorCompletionService + Executor + ExecutorService + ThreadPoolExecutor + ScheduledExecutorService + ScheduledThreadPoolExecutor + \ No newline at end of file diff --git "a/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\345\220\257\345\212\250\346\226\260\347\272\277\347\250\213.java" "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\345\220\257\345\212\250\346\226\260\347\272\277\347\250\213.java" new file mode 100644 index 00000000..72a871f3 --- /dev/null +++ "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\345\220\257\345\212\250\346\226\260\347\272\277\347\250\213.java" @@ -0,0 +1,48 @@ + +1,缁ф壙 Thread + + public class Main extends Thread { + @Override + public void run() { + //Thread-0 + System.out.print(Thread.currentThread().getName()); + } + public static void main(String[] args) { + new Main().start(); + } + } + +2,瀹炵幇 Runnable + + public class Main implements Runnable { + @Override + public void run() { + //Thread-0 + System.out.print(Thread.currentThread().getName()); + } + public static void main(String[] args) { + new Thread(new Main()).start(); + } + } + +3,瀹炵幇 Callable + * 鑳藉鑾峰彇鍒扮嚎绋嬬殑杩斿洖鍊 + + import java.util.concurrent.Callable; + import java.util.concurrent.ExecutionException; + import java.util.concurrent.FutureTask; + public class Main implements Callable { + @Override + public String call() throws Exception { + return Thread.currentThread().getName(); + } + public static void main(String[] args) throws InterruptedException, ExecutionException { + //閫氳繃 Callable 瀹炰緥,鏋勯 FutureTask 瀹炰緥 + FutureTask futureTask = new FutureTask<>(new Main()); + //閫氳繃 FutureTask 瀹炰緥,鏋勯 Thread瀹炰緥,骞朵笖鍚姩绾跨▼ + new Thread(futureTask).start(); + //FutureTask 瀹炰緥鐨刧et(),浼氫竴鐩撮樆濉炲綋鍓嶇嚎绋,鐩村埌FutureTask鐨勭嚎绋嬭繑鍥炵粨鏋 + String result = futureTask.get(); + System.out.println(result); + } + } \ No newline at end of file diff --git "a/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\346\226\260\347\211\210\346\234\254\347\232\204\345\220\214\346\255\245\345\267\245\345\205\267\347\261\273.java" "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\346\226\260\347\211\210\346\234\254\347\232\204\345\220\214\346\255\245\345\267\245\345\205\267\347\261\273.java" new file mode 100644 index 00000000..1f7d983e --- /dev/null +++ "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\346\226\260\347\211\210\346\234\254\347\232\204\345\220\214\346\255\245\345\267\245\345\205\267\347\261\273.java" @@ -0,0 +1,35 @@ + +# ConcurrentHashMap + * 用于替代散列同步的 Map + * 只有应用程序需要加锁对 Map 进行独占访问时,才应该放弃使用 ConcurrentHashMap + * 额外的原子操作API + V putIfAbsent(K key, V value) + * 当 key ,不存在则插入 value + + boolean remove(Object key, Object value) + * 当key映射到了value才执行移除 + + boolean replace(K key, V oldValue, V newValue) + * 当key映射到了oldValue,才执行替换 + + V replace(K key, V value) { + * 当key已经存在的时候,才执行替换 + + + +# ConcurrentSkipListMap + * 代替 SortedMap + +# ConcurrentSkipListSet + * 代替 SortedSet + +# CopyOnWriteArrayList + * 代替同步的 List + * 它提供了更好的性能,在迭代期间不需要对容器进行加锁或者克隆、 + * 写入时复制返回的容器,返回的迭代器不会抛出 ConcurrentModificationException,并且返回的元素,与创建时的元素完全一致,而不必考虑之后修改操作所带来的影响 + * 每当修改容器,都会复制底层数组,这需要很打的开销 + * 当迭代操作多于修改操作时,才应该使用写入时复制容器(例如:通知系统) + +# CopyOnWriteArraySet + * 代替同步的 Set + * 它提供了更好的性能,在迭代期间不需要对容器进行加锁或者克隆 \ No newline at end of file diff --git a/Java/concurrent/java-juc-ExecutorService.java "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\347\272\277\347\250\213\346\261\240.java" similarity index 90% rename from Java/concurrent/java-juc-ExecutorService.java rename to "Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\347\272\277\347\250\213\346\261\240.java" index be866bd2..acfda6ea 100644 --- a/Java/concurrent/java-juc-ExecutorService.java +++ "b/Java/\345\271\266\345\217\221\347\274\226\347\250\213\344\270\223\351\242\230/\347\272\277\347\250\213\346\261\240.java" @@ -7,7 +7,7 @@ |-ExecutorService(接口) |-ThreadPoolExecutor(实现) |-ScheduledExecutorService(接口,负责线程调度) - |-ScheduledThreadPoolExecutor(继承了ThreadPoolExecutor又实现了ScheduledExecutorService) + |-ScheduledThreadPoolExecutor(继承了 ThreadPoolExecutor 又实现了 ScheduledExecutorService) # 创建 ExecutorService pool = Executors.newFixedThreadPool(2); * 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。 @@ -45,14 +45,19 @@ ScheduledFuture schedule(Runnable command,long delay, TimeUnit unit); * 在指定延迟后,执行command + ScheduledFuture schedule(Callable callable,long delay, TimeUnit unit); * 在指定延迟后,执行callable - # 关闭线程池 + # api + boolean isShutdown() void shutdown(); * 会等到所有任务完成才会关闭 List shutdownNow(); * 立即关闭线程池 + + boolean isTerminated(); + * 如果队列中的所有任务都处理完毕后返回 true \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/InetAddress.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/InetAddress.java" new file mode 100644 index 00000000..18029690 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/InetAddress.java" @@ -0,0 +1,35 @@ +------------------------ +InetAddress | +------------------------ + # Java涓敤浜庢弿杩癐P鍦板潃鐨勭被 + # 娌″緱public鐨勬瀯閫犲嚱鏁,鍙兘閫氳繃闈欐佹柟娉曟潵鍒涘缓 + # 瀛愮被 + Inet4Address + Inet6Address + + +------------------------ +瀹炰緥鏂规硶 | +------------------------ + String getHostAddress(); + * 杩斿洖ip淇℃伅 + + String getHostName() + * 杩斿洖涓绘満鍚 + +------------------------ +闈欐佹柟娉 | +------------------------ + InetAddress getLocalHost() + * 寰楀埌鎻忚堪鏈満IP鐨処netAddress瀵硅薄 + + InetAddress getByName(String host) + * 閫氳繃鎸囧畾鍩熷悕浠嶥NS涓緱鍒扮浉搴旂殑IP鍦板潃 + + InetAddress[] getAllByName(String host) + + InetAddress getByAddress(byte[] addr) + + InetAddress getByAddress(String host, byte[] addr) + + InetAddress getLoopbackAddress() \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio-AsynchronousChannelGroup.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio-AsynchronousChannelGroup.java" new file mode 100644 index 00000000..c195e264 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio-AsynchronousChannelGroup.java" @@ -0,0 +1,8 @@ +---------------------------------- +AsynchronousChannelGroup | +----------------------------------- + # AsynchronousChannelGroup 是一个抽象类,通过静态方法获取实例对象 + + AsynchronousChannelGroup withThreadPool(ExecutorService executor); + AsynchronousChannelGroup withCachedThreadPool(ExecutorService executor,int initialSize); + AsynchronousChannelGroup withFixedThreadPool(int nThreads,ThreadFactory threadFactory); \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio-AsynchronousServerSocketChannel.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio-AsynchronousServerSocketChannel.java" new file mode 100644 index 00000000..0d8011b4 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio-AsynchronousServerSocketChannel.java" @@ -0,0 +1,93 @@ + + +----------------------------------- +AsynchronousServerSocketChannel | +----------------------------------- + # 异步,非阻塞服务器端Socket通道 + # AIO + # AIO提供了两种异步操作的监听机制。 + * 第一种通过返回一个 Future 对象来实现,调用其get();会阻塞到操作完成。 + Future + boolean cancel(boolean mayInterruptIfRunning); + boolean isCancelled(); + boolean isDone(); + V get() throws InterruptedException, ExecutionException; + V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException; + + * 第二种类似于回调函数。在进行异步操作时,传递一个 CompletionHandler 接口实例 当异步操作结束时,会调用 实例的 complete 方法 + * 不会阻塞,OS完成IO后回调 + CompletionHandler + void completed(V result, A attachment); + * 任务完成的时候执行 + void failed(Throwable exc, A attachment); + * 任务异常的时候执行 + +----------------------------------- +AsynchronousServerSocketChannel-API| +----------------------------------- + # 静态方法 + AsynchronousServerSocketChannel open(); + * 打开一个异步的Socket通道 + AsynchronousServerSocketChannel open(AsynchronousChannelGroup group); + * 使用 AsynchronousChannelGroup 来打开一个新的通道 + + # 实例方法 + //===================== 监听 + AsynchronousServerSocketChannel bind(SocketAddress local); + * 监听指定的端口,返回 this + * 如果该参数是 null,则自动会在当前机器寻找空闲的端口进行绑定 + * demo + AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8888)); + AsynchronousServerSocketChannel bind(SocketAddress local, int backlog) + * 同上,并且设置最大连接数量 + + //===================== 阻塞 + Future accept(); + * 该方法会阻塞,直到收到请求 + + void accept(A attachment,CompletionHandler handler); + * 传递一个附件 attachment + * 该在 CompletionHandler 接口的 completed 方法 中可以获取到这个附件 + + + //===================== 配置 + AsynchronousServerSocketChannel setOption(SocketOption name, T value); + * 设置配置 + T getOption(SocketOption name); + * 获取配置 + Set> supportedOptions(); + * 获取支持的配置 + + //===================== 其他 + void close(); + * 关闭 + + SocketAddress getLocalAddress(); + * 获取当前的地址 + + AsynchronousChannelProvider provider(); + * 未知 + + + +---------------------------------------- +AsynchronousServerSocketChannel-api | +---------------------------------------- + static AsynchronousServerSocketChannel open() + static AsynchronousServerSocketChannel open(AsynchronousChannelGroup group) + + Future accept(); + void accept(A attachment,CompletionHandler handler); + + AsynchronousServerSocketChannel bind(SocketAddress local) + AsynchronousServerSocketChannel bind(SocketAddress local, int backlog) + + Set> supportedOptions(); + AsynchronousServerSocketChannel setOption(SocketOption name, T value) + T getOption(SocketOption name) + + void close() + boolean isOpen(); + SocketAddress getLocalAddress() + AsynchronousChannelProvider provider() + diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio-AsynchronousSocketChannel.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio-AsynchronousSocketChannel.java" new file mode 100644 index 00000000..ce32a1d4 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio-AsynchronousSocketChannel.java" @@ -0,0 +1,103 @@ +----------------------------------- +AsynchronousServerSocketChannel | +----------------------------------- + # 异步,非阻塞客户端Socket通道 + + +----------------------------------- +AsynchronousServerSocketChannel-API| +----------------------------------- + # 静态方法 + AsynchronousSocketChannel open(AsynchronousChannelGroup group); + * 打开一个异步通道,使用指定的 ChannelGroup + AsynchronousSocketChannel open(); + * 打开一个异步通道 + + # 实例方法 + //==========================绑定 + AsynchronousSocketChannel bind(SocketAddress local); + * 绑定本机地址 + + + //==========================连接 + Future connect(SocketAddress remote); + * 连接远程服务器 + * demo + asynchronousSocketChannel.connect(new InetSocketAddress("localhost",55)); + + void connect(SocketAddress remote,A attachment,CompletionHandler handler); + * 连接远程主机 + + //==========================读取 + Future read(ByteBuffer dst); + void read(ByteBuffer[] dsts, int offset, int length, long timeout, TimeUnit unit, A attachment, CompletionHandler handler); + void read(ByteBuffer dst, A attachment, CompletionHandler handler) + void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) + + //==========================写入 + Future write(ByteBuffer src) + void write(ByteBuffer[] srcs, int offset, int length, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) + void write(ByteBuffer src, A attachment, CompletionHandler handler) + void write(ByteBuffer src, long timeout, TimeUnit unit, A attachment, CompletionHandler handler)\ + + //=========================配置 + AsynchronousSocketChannel setOption(SocketOption name, T value); + * 设置配置 + + T getOption(SocketOption name); + * 获取配置 + + Set> supportedOptions(); + * 获取支持的配置 + + //=========================关闭 + AsynchronousSocketChannel shutdownInput() + * 关闭读,并没有关闭连接 + AsynchronousSocketChannel shutdownOutput() + * 关闭写,并没有关闭连接 + void close(); + * 关闭连接 + + //==========================其他 + boolean isOpen(); + * 连接是否打开 + SocketAddress getLocalAddress(); + * 本地地址 + SocketAddress getRemoteAddress() + * 远程地址 + AsynchronousChannelProvider provider() + * 未知 + +-------------------------------- +AsynchronousSocketChannel-api | +-------------------------------- + static AsynchronousSocketChannel open() + static AsynchronousSocketChannel open(AsynchronousChannelGroup group) + + Future read(ByteBuffer dst); + void read(ByteBuffer dst,A attachment,CompletionHandler handler) + void read(ByteBuffer dst,long timeout,TimeUnit unit, A attachment,CompletionHandler handler) + void read(ByteBuffer[] dsts,int offset,int length,long timeout,TimeUnit unit,A attachment,CompletionHandler handler) + + Future write(ByteBuffer src) + void write(ByteBuffer src,A attachment,CompletionHandler handler) + void write(ByteBuffer src,long timeout,TimeUnit unit,A attachment,CompletionHandler handler) + void write(ByteBuffer[] srcs,int offset,int length,long timeout,TimeUnit unit,A attachment,CompletionHandler handler) + + Set> supportedOptions() + AsynchronousSocketChannel setOption(SocketOption name, T value) + T getOption(SocketOption name) + + SocketAddress getLocalAddress() + SocketAddress getRemoteAddress() + + AsynchronousSocketChannel shutdownInput() + AsynchronousSocketChannel shutdownOutput() + + Future connect(SocketAddress remote) + void connect(SocketAddress remote,A attachment,CompletionHandler handler) + + void close() + AsynchronousSocketChannel bind(SocketAddress local) + AsynchronousChannelProvider provider() + boolean isOpen() diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio-CompletionHandler.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio-CompletionHandler.java" new file mode 100644 index 00000000..e69de29b diff --git a/Netty/AIO.java "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio.java" similarity index 98% rename from Netty/AIO.java rename to "Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio.java" index 34d148b4..25d6e004 100644 --- a/Netty/AIO.java +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/aio/aio.java" @@ -24,7 +24,10 @@ public final void read(ByteBuffer dst,A attachment,CompletionHandler ServerSocketChannel setOption(SocketOption name, T value) + SocketChannel accept() + SelectionKey register(Selector sel, int ops) + SelectionKey register(Selector sel, int ops,Object att) + ServerSocketChannel bind(SocketAddress local) + ServerSocketChannel bind(SocketAddress local, int backlog) + SocketAddress getLocalAddress() + int validOps() + * 返回 ServerSocketChannel 能产生的事件 + * 固定返回:SelectionKey.OP_ACCEPT;(连接就绪事件) + + Object blockingLock() + void close() + T getOption(SocketOption name) + boolean isBlocking() + boolean isOpen() + boolean isRegistered() + SelectionKey keyFor(Selector sel) + Set> supportedOptions() + SelectorProvider provider() + SelectableChannel configureBlocking(boolean block) + + + + + diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-Channel-SocketChannle-api.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-Channel-SocketChannle-api.java" new file mode 100644 index 00000000..95bb1a27 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-Channel-SocketChannle-api.java" @@ -0,0 +1,101 @@ +---------------------------- +SocketChannel | +---------------------------- + * SocketChannel 是 Socket 的替代类,但是它比 Socket 具有更多的功能 + * 它继承 SelectableChannel 实现了 ByteChannel + * 没有公共的构造函数,需要通过静态的工厂方法来创建实例对象 + +---------------------------- +SocketChannel 静态方法 | +---------------------------- + static SocketChannel open() + SocketChannel open(SocketAddress remote) + + * open静态方法返回的实例是阻塞模式,需要手动的设置为非阻塞模式 + +---------------------------- +SocketChannel 实例方法 | +---------------------------- + SocketChannel bind(SocketAddress local) + boolean connect(SocketAddress remote) + * 建立远程连接 + * 如果 SocketChannel 处于非阻塞模式,如果立即连接成功,该方法返回 true + 如果不能立即连接成功,返回 false,程序过会儿必须调用 finishConnection() 方法来完成连接 + * 如果 SocketChannel 处于阻塞模式,如果立即连接成功,该方法返回 true + 如果不能连接成功,会进入阻塞状态,直到连接成功,或者出现 IO 异常 + + + boolean finishConnect() + SocketAddress getRemoteAddress() + boolean isConnected() + * 底层的 Socket 是否已经建立了远程连接 + + boolean isConnectionPending() + * 判断是否正在进行远程连接 + * 当远程连接操作已经开始,但是还没完成,返回 true,否则返回 false + * 也就是说,底层 Socket 还没有开始连接,或者已经连接成功都会返回 false + + SocketChannel open() + SocketChannel open(SocketAddress remote) + + int read(ByteBuffer dst) + * 把 Channel 中的数据读入到 dst Buffer 中 + * 下文 r 字节表示 Buffer 的 remaining() 值 + * 在阻塞模式下,read() 方法会争取读取到 r 个字节,如果流中的数据不足r个字节,就会进入阻塞状态 + 直到读取了r个字节,或者读到了数据流的末尾,或者出现了io异常 + * 在非阻塞模式下,read 方法奉行等读到多少数据,就读多少数据的原则 + read 方法读取当前通道中的可读数据,有可能不足 r 个字节,或者为0个字节, + read 放总是返回,而不会等到读取了 r 个字节后再返回 + * read 方法返回实际上可读入的字节数,有可能为0,如果返回 -1 就表示读到了流末尾 + + long read(ByteBuffer[] dsts) + long read(ByteBuffer[] dsts, int offset, int length) + + SocketChannel setOption(SocketOption name, T value) + SocketChannel shutdownInput() + SocketChannel shutdownOutput() + Socket socket() + * 返回 SocketChannel 关联的 Socket 对象 + + int validOps() + * 返回 SocketChannel 支持的事件 + * 返回 SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE + + int write(ByteBuffer src) + * 把 src 中的数据写入到 Channel + * 在阻塞模式下,write 方法会争取输出r字节,如果底层网络缓冲区不能容纳r个字节,就会处于阻塞状态 + 直到输出了r个自己,或者出现了io异常 + * 在非阻塞模式下,write() 方法奉行能够输出多少数据,就输出多少数据的原则 + 有可能不足r个字节,或者为0个字节,write 方法总是立即返回,而不会等到输出r个字节后再返回 + * wirte 方法实际上输出的字节数,可能为0 + + long write(ByteBuffer[] srcs) + long write(ByteBuffer[] srcs, int offset, int length) + + Object blockingLock() + SelectableChannel configureBlocking(boolean block) + * 设置IO模式,默认为 true,表示阻塞模式 + + boolean isBlocking() + boolean isRegistered() + * 是否已经注册 + + SelectionKey keyFor(Selector sel) + * 从指定的Selector获取到key + + SelectorProvider provider() + SelectionKey register(Selector sel, int ops, Object att) + SelectionKey register(Selector sel,int ops) + void close() + boolean isOpen() + SocketAddress getLocalAddress() + T getOption(SocketOption name) + Set> supportedOptions() + + + + + + + + diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-Channel.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-Channel.java" new file mode 100644 index 00000000..775f4759 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-Channel.java" @@ -0,0 +1,71 @@ +------------------------ +Channel | +------------------------ + # Channel 接口就只声明了两个方法 + void close(); + boolean isOpen(); + + # Channel 最重要的两个子接口是 + * ReadableByteChannel + * 声明了 read(ByteBuffer dst) 方法 + * 该方法把数据源的数据读入参数指定的 ByteBuffer 缓冲区中 + + * WritableByteChannel + * 声明了 writr(ByteBuffer src) + * 该方法把参数 ByteBuffer 缓冲区中的数据写到数据汇中 + + * Channel 与 Buffer 关系 + |<---------------- ReadableByteChannel ----------------| + ByteBuffer 数据源 + |----------------- WritableByteChannel --------------->| + + * ByteChannel 是一个便利接口,扩展了 ReadableByteChannel 和 WritableByteChannel,所以同时支持读写操作 + +----------------------------------------------- +ScatteringByteChannel 和 GatheringByteChannel | +----------------------------------------------- + # ScatteringByteChannel + * 该接口扩展了 ReadableByteChannel 接口,允许分散地读取数据 + 分散读取数据是指单个读取操作,能填充多个缓冲区, + * 该接口声明了 read(ByteBuffer[] dsts)方法,该方法把从数据源读取到的数据依次填充到参数指定的 ByteBuffer 数组的各个 Buffer 中 + + # GatheringByteChannel + * 该接口扩展了 WritableByteChannel 接口,允许集中的写入数据 + 集中写入数据是指单个写操作能把多个缓冲区的数据写到一起(数据汇) + * 该接口声明了 write(ByteBuffer[] srcs)方法,该方法依次把srcs中的每个 Buffer 中的数据写入数据汇 + + # 分散读取和集中写数据,能够进一步提高输入和输出操作的速度 + +----------------------------------------------- +FileChannel | +----------------------------------------------- + # FileChannel 实现了接口 Channel,代表一个与文件相连的通道 + # 该类还实现了 ByteChannel,ScatteringByteChannel,GatheringByteChannel + # 它支持读,写,分散读,集中写等操作 + # 它没有提供 public 的构造方法,不能 new + # 在 FileInputStream,FileOutputStream,RandomAccessFile 类中,都有提供 getChannel() 方法,返回对应的 FileChannel 实例 + +----------------------------------------------- +SelectableChannel | +----------------------------------------------- + # 也是一种通道,不仅支持阻塞的IO操作,还支持非阻塞的IO操作 + # 它有两个子类 + ServerSocketChannel + SocketChannel + # 它还实现了 ByteChannel 接口,具有 read(ByteBuffer b); 和 write(ByteBuffer src); 方法 + # 在非阻塞模式下,读写数据不会阻塞,并且它还可以向 Selector 注册读写就绪等事件 + # Selector 负责监控这些事件,在事件触发的时候,例如:触发了读事件,那么该接口就可以进行读操作了 + # 主要的方法 + SelectableChannel configureBlocking(boolean block) + * 如果 block 为 true,则表示设置为阻塞模式,否则为非阻塞模式 + * 默认 true(阻塞) + + SelectionKey register(Selector sel, int ops, Object att) + SelectionKey register(Selector sel, int ops) + * 向 Selector 注册事件 + * 返回的 SelectionKey,用来跟踪被注册的事件 + * 第二个api还支持携带一个附件,在处理该事件的时候,可以从 SelectionKey 中获得这个附件 + * SelectionKey 还支持主动的去关联一个附件, attach(Object att) + + + diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-CharsetDecoder.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-CharsetDecoder.java" new file mode 100644 index 00000000..9c633cd1 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-CharsetDecoder.java" @@ -0,0 +1,15 @@ +---------------------------- +CharsetDecoder | +---------------------------- + # 字符编码器 + + + # 对 ByteBuffer 编码 + + ByteBuffer attachment = ByteBuffer.allocate(1024); + attachment.flip(); + CharBuffer charBuffer = CharBuffer.allocate(1024); + CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); + decoder.decode(attachment,charBuffer,false); + charBuffer.flip(); + String data = new String(charBuffer.array(),0, charBuffer.limit()); \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-CharsetEncoder.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-CharsetEncoder.java" new file mode 100644 index 00000000..e69de29b diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-SelectionKey.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-SelectionKey.java" new file mode 100644 index 00000000..3c1c40a7 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-SelectionKey.java" @@ -0,0 +1,81 @@ +---------------------------- +SelectionKey | +---------------------------- + * ServerSocketChannel 或者 SocketChannel 通过 register() 方法向 Selector 注册事件的时候 + register() 方法会创建一个 SelectionKey 对象,该对象是用来跟踪注册事件的句柄 + + * 在 SelectionKey 有效期, Selector 会一直监控与它相关的事件 + 如果事件发生,就会把 SelectionKey 对象加入到: selected-keys 集合中 + + * 在以下情况中,SelectionKey 对象会失效,也就说说 Selector 再也不会监控与它相关的事件了 + 1,程序调用了 SelectionKey 的 cancel() 方法 + 2,关闭与 SelectionKey 关联的 Channel + 3,与 SelectionKey 关联的 Selector 被关闭 + +---------------------------- +SelectionKey 事件常量 | +---------------------------- + SelectionKey.OP_ACCEPT; + * 标记连接就绪,表示至少有一个客户端连接,服务器可以接收这个连接 + * 16 + + SelectionKey.OP_CONNECT; + * 连接就绪事件,表示客户与服务器的连接已经建立成功了 + * 8 + + SelectionKey.OP_READ; + * 读就绪事件,表示输入流中已经有可读数据,可以执行读操作了 + * 1 + + SelectionKey.OP_WRITE; + * 写就绪事件,表示已经可以向输出流写数据了 + * 4 + +---------------------------- +SelectionKey-api | +---------------------------- + void cancel() + * 使 SelectionKey 对象失效 + * 该方法把 SelectionKey 对象加入到与它关联的 Selector 对象的 cancelled=keys 集合中 + 程序下一次执行 Selector 的 select() 方法时,该方法会把 SelectionKey 从 Selector 对象的 all-keys,selected-keys.cancelled-keys 这仨集合中删除 + + Object attach(Object ob) + * 使 SelectionKey 关联一个附件对象 + + Object attachment() + * 返回 SelectionKey 关联的对象 + + SelectableChannel channel() + * 返回与之关联的 SelectableChannel 对象 + + int interestOps() + * 返回所有感兴趣的事件 + + SelectionKey interestOps(int ops) + * 添加一个感兴趣的事件 + + boolean isAcceptable() + * 接收连接事件是否就绪 + boolean isConnectable() + * 连接就绪事件是否已经发生 + boolean isReadable() + * 读事件 + boolean isWritable() + * 写事件 + + boolean isValid() + * 判断当前 SelectionKey 是否有效 + * 当 SelectionKey 创建后,它就一直处于有效状态 + * 如果调用了 cancel() ,或者关闭了关联的 Channel 或者 Selector 关闭的时候,它就失效 + + + int readyOps() + * 返回所有已经发生的事件 + * 假如返回值:SelectionKey.OP_WRITE | SelectionKey.OP_READ + 意味着,读就绪和写就绪事件发生了,与之关联的 SocketChannel 对象可以进行读操作和写操作 + + Selector selector() + * 返回关联的 Selector 对象 + + + diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-Selector.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-Selector.java" new file mode 100644 index 00000000..9c4cecbf --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-Selector.java" @@ -0,0 +1,72 @@ +------------------------ +Selector | +------------------------ + * 只要 ServerSocketChannel 或者 SocketChannel 向 Selector 注册了特定的事件 + Selector 就会监控这些事件是否发生,SelectableChannel 的 register() 方法负责注册事件 + 该方法返回一个 SelectionKey 对象,该对象是用于跟踪这些被注册事件的句柄 + + * 一个 Selector 会包含3种类型的 SelectionKey 集合 + all-keys + * 当前所有向 Selector 注册 SelectionKey 集合 + * Selector 的 keys() 方法返回该集合 + + slected-keys + * 相关事件已经被 Selector 捕获,的 SelectionKey 集合 + * Selector 的 selectedKeys() 方法返回该集合 + + cancelled-keys + * 已经被取消的 SelectionKey 的集合 + * Selector 没有提供方法这种集合的方法 + + * 当 SelectableChannel 执行 register() 方法时,该方法会新建一个 SelectionKey ,并把它加入到 Selector 的 all-keys 集合中 + + * 如果关闭了 SelectionKey 对象关联的 Channel 对象,或者调用了 SelectionKey 对象的 cancel() 方法 + 这个 SelectionKey 对象就会被加入到 cancelled-key 集合中,表示这个 SelectionKey 已经被取消 + 程序执行下一次 Selector 的 select() 方法时,被取消的 SelectionKey 对象将从所有集合中删除 + + * 在 Selector 执行 select() 方法的时候,如果与 SelectionKey 相关的事件发生了,这个 SelectionKey 就会被加入到:selected-keys 集合中 + 程序直接调用 selected-keys 集合的 remove() 方法,或者调用它的 Iterator 的remove() 方法都可以从 selected-keys 集合中删除一个 SelectionKey 对象 + 程序不允许,直接通过集合集合的 remove() 方法,删除 all-keys 集合中的 SelectionKey,如果程序视图这么做,会抛出异常:UnsupportedOperationException + +------------------------ +Selector-api | +------------------------ + public static Selector open() + + void close() + boolean isOpen() + * 判断 Selector 是否处于打开状态 + * 创建了 Selector 实例后,就是打开状态,调用了 close() 方法,就进入了关闭状态 + + Set keys()  + * 返回 Selector 的 all-keys 集合,包含了 Selector 关联的 SelectionKey 丢下 + + SelectorProvider provider() + int select() + int select(long timeout) + * select() 方法才用阻塞的工作方式,返回相关事件已经发生的 SelectionKey 对象数目 + * 如果一个都没有,则会进入阻塞,直到出现以下情况之一,才用 select() 方法中返回 + 1,至少有一个 SelectionKey 的相关事件已经发生 + 2,其他线程调用了 Selector 的 wakeup() 方法,导致 select() 方法的线程,立即从 select() 方法返回 + 3,当前执行 select()方法的线程,被其他线程中断 + 4,超出了等待时间,单位是毫秒,如果等待超时,就正常返回,不会抛出异常 + 如果调用了是没有超时参数的select(),该方法的线程就会进入阻塞状态,永远不会因为超时而中断 + + Set selectedKeys() + int selectNow() + * 返回相关事件已经发送的 SelectionKey 对象数目 + * 该方法采用非阻塞的工作方式,返回当前相关事件已经发生的 SelectionKey 对象数目 + * 如果没有,立即返回0 + + + Selector wakeup() + * 唤醒执行 Selector 的 select() 方法(同样适用于 select(long timeOut))的线程 + * 线程A执行了 Selector 对象的 wakeup() 方法,如果线程B正在执行同一个 Selector 对象的 select() 方法时 + 或者B线程过一会儿执行这个 Selector 对象的 select() 方法,那么B线程在执行select() 方法时,会立即从 select() 方法中返回 + * wakeup() 只能唤醒执行select()方法的线程B一次,如果线程B在执行select()方法时被唤醒,以后再执行select()还会被阻塞 + 除非线程A再次执行 Selector 对象的 wakeup() 方法 + + + + + \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-StandardSocketOptions.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-StandardSocketOptions.java" new file mode 100644 index 00000000..77f0b4e7 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-StandardSocketOptions.java" @@ -0,0 +1,26 @@ +------------------------ +StandardSocketOptions | +------------------------ + # nio包下系统预定义的socket选项 StandardSocketOptions + + IP_MULTICAST_IF + IP_MULTICAST_LOOP + IP_MULTICAST_TTL + IP_TOS + SO_BROADCAST + SO_KEEPALIVE + * 表示对于长时间处于空闲状态的Socket是否要自动把它关闭 + SO_LINGER + * 表示当执行Socket的close()方法时,是否立即关闭底层的socket + SO_RCVBUF + * 表示接收数据的缓冲区大小 + SO_REUSEADDR + * 是否允许重用socket所绑定的本地地址 + SO_SNDBUF + * 表示发送数据的缓冲区大小 + TCP_NODELAY + * 是否立即发送数据 + + # 设置 + ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); + serverSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-\345\205\245\351\227\250.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..d88b9ea3 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-\345\205\245\351\227\250.java" @@ -0,0 +1,51 @@ +---------------------------- +java-nio | +---------------------------- + * 参考资料 + http://ifeve.com/overview/ + + + + +---------------------------- +关键组件 | +---------------------------- + ServerSocketChannel + * ServerSocket 代替类,支持阻塞通信与非阻塞通信 + + SocketChannel + * Socket 代替类,支持阻塞通信与非阻塞通信 + + Selector + * 为 ServerSocketChannel 监控接收连接就绪事件 + * 为 SocketChannel 监控连接就绪,读就绪,写就绪事件 + + SelectionKey + * 代表 ServerSocketChannel 和 SocketChannel 向 Selector 注册事件的句柄 + * 当一个 SelectionKey 对象位于 Selector 对象的 selected-keys 集合中时,就表示与这个 SelectionKey 对象相关的事件发生了 + + +---------------------------- +Channel 体系图 | +---------------------------- + |----------Channel-------------| + SelectableChannel (interface)ByteChannel + | |----------------------| +ServerSocketChannel SocketChannel + + +---------------------------- +SelectionKey 事件常量 | +---------------------------- + SelectionKey.OP_ACCEPT; + * 标记连接就绪,表示至少有一个客户端连接,服务器可以接收这个连接 + + SelectionKey.OP_CONNECT; + * 连接就绪事件,表示客户与服务器的连接已经建立成功了 + + SelectionKey.OP_READ; + * 读就绪事件,表示输入流中已经有可读数据,可以执行读操作了 + + SelectionKey.OP_WRITE; + * 写就绪事件,表示已经可以向输出流写数据了 + diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-\351\233\266\346\213\267\350\264\235.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-\351\233\266\346\213\267\350\264\235.java" new file mode 100644 index 00000000..f334cb3b --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/nio/nio-\351\233\266\346\213\267\350\264\235.java" @@ -0,0 +1,36 @@ + +---------------------------- +零拷贝 | +---------------------------- + # 在操作系统支持的情况下,通过该方法传输数据并不需要将源数据从内核态拷贝到用户态,再从用户态拷贝到目标通道的内核态 + # 同时也避免了两次用户态和内核态间的上下文切换,也即使用了"零拷贝" + +---------------------------- +文件与网络的零拷贝 | +---------------------------- +import java.io.IOException; +import java.io.RandomAccessFile; +import java.net.InetSocketAddress; +import java.nio.channels.FileChannel; +import java.nio.channels.SocketChannel; + +public class NIOClient { + public static void main(String[] args) throws IOException, InterruptedException { + + SocketChannel socketChannel = SocketChannel.open(); + socketChannel.connect(new InetSocketAddress("192.168.0.2",1234)); + + // 随机io + RandomAccessFile randomAccessFile = new RandomAccessFile(NIOClient.class.getClassLoader().getResource("test.txt").getFile(),"rw"); + + // 文件channel + FileChannel fileChannel = randomAccessFile.getChannel(); + // 把文件channle直接输出到网络channle + fileChannel.transferTo(0, fileChannel.size(), socketChannel); + + // 资源关闭 + fileChannel.close(); + randomAccessFile.close(); + socketChannel.close(); + } +} \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/demo-\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213\347\232\204\344\274\240\350\276\223.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/demo-\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213\347\232\204\344\274\240\350\276\223.java" new file mode 100644 index 00000000..bfd88e8d --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/demo-\345\237\272\346\234\254\346\225\260\346\215\256\347\261\273\345\236\213\347\232\204\344\274\240\350\276\223.java" @@ -0,0 +1,108 @@ + +-------------------------------- +基本数据类型的传输 | +-------------------------------- + # 其实就是借助了 ByteBuffer 的api完成基本数据类型和java基础数据类型的转换 + + +-------------------------------- +Server | +-------------------------------- + +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.ByteBuffer; + +public class Server { + + public static void main(String[] args) throws Exception { + + try (ServerSocket serverSocket = new ServerSocket()) { + serverSocket.bind(new InetSocketAddress("0.0.0.0", 1024)); + serverSocket.setReuseAddress(true); + + for (;;) { + try (Socket socket = serverSocket.accept()) { + + InputStream inputStream = socket.getInputStream(); + + byte[] bytes = new byte[1024]; + + int length = inputStream.read(bytes); + + if (length != -1) { + + // 使用 ByteBuffer的api完成基本数据类型和字节数据的转换 + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, 0, length); + + byte byteValue = byteBuffer.get(); + + char charValue = byteBuffer.getChar(); + + int intValue = byteBuffer.getInt(); + + boolean booleanValue = byteBuffer.get() == 1; + + long longValue = byteBuffer.getLong(); + + float floatValue = byteBuffer.getFloat(); + + double doubleValue = byteBuffer.getDouble(); + + // 读取到的角标 + int position = byteBuffer.position(); + // 下一个角标开始,直到最后一个字节,都是字符串数据 + String stringValue = new String(bytes, position, (length - position) - 1); + + System.out.println("收到客户端的数据:" + + "byte="+byteValue + "\n" + + "char="+charValue + "\n" + + "int="+intValue + "\n" + + "boolean="+booleanValue + "\n" + + "long="+longValue + "\n" + + "floatValue="+floatValue + "\n" + + "double="+doubleValue + "\n" + + "String="+stringValue + "\n" + ); + } + } + } + } + } +} + + +-------------------------------- +Client | +-------------------------------- +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; + +public class Client { + + public static void main(String[] args) throws Exception { + + try (Socket socket = new Socket()) { + socket.connect(new InetSocketAddress("127.0.0.1", 1024)); // 发起连接 + + OutputStream outputStream = socket.getOutputStream(); + + // 使用 ByteBuffer的api完成基本数据类型和字节数据的转换 + ByteBuffer byteBuffer = ByteBuffer.allocate(1024); + byteBuffer.put((byte)1); + byteBuffer.putChar('C'); + byteBuffer.putInt(3); + byteBuffer.put((byte)1); + byteBuffer.putLong((long)5); + byteBuffer.putFloat((float)6.1); + byteBuffer.putDouble(6.2); + byteBuffer.put("KevinBlandy".getBytes()); + + outputStream.write(byteBuffer.array(),0,byteBuffer.position() + 1); + } + } +} diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/serversocket-api.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/serversocket-api.java" new file mode 100644 index 00000000..940daedb --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/serversocket-api.java" @@ -0,0 +1,36 @@ +----------------------- +ServerSocket - 构造函数| +----------------------- + ServerSocket() throws IOException + ServerSocket(int port) + ServerSocket(int port, int backlog) + ServerSocket(int port, int backlog, InetAddress bindAddr) + +----------------------- +ServerSocket - api | +----------------------- + static synchronized void setSocketFactory(SocketImplFactory fac) + + Socket accept() + void bind(SocketAddress endpoint) + void bind(SocketAddress endpoint, int backlog) + * 绑定到地址,backlog指定最大等待链接的数量,超过该数量的客户端连接会抛出异常 + + void close() + ServerSocketChannel getChannel() + InetAddress getInetAddress() + int getLocalPort() + SocketAddress getLocalSocketAddress() + int getReceiveBufferSize() + boolean getReuseAddress() + int getSoTimeout() + boolean isBound() + boolean isClosed() + + void setPerformancePreferences(int connectionTime,int latency,int bandwidth) + void setReceiveBufferSize (int size) + void setReuseAddress(boolean on) + void setSoTimeout(int timeout) + + + \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/serversocket-\350\257\246\350\247\243.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/serversocket-\350\257\246\350\247\243.java" new file mode 100644 index 00000000..19754409 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/serversocket-\350\257\246\350\247\243.java" @@ -0,0 +1,92 @@ +------------------------ +ServerSocket | +------------------------ + # 端口绑定 + * 如果是绑定 1 - 1023端口,必须使用管理器权限执行程序,否则可能抛出异常 + * 如果构造参数为0,那么系统随机选择端口进行绑定 + ServerSocket(0) + + # 设置客户端请求队列的长度,默认为50 + * 构造设置 + ServerSocket(1024,50) + * 如果链接数据量超过,新的客户端连接就会抛出异常 + * ServerSocket 构造函数中的 backlog 参数来限制队列长度,会覆盖系统限定的队列最大长度 + * 在几个情况下,仍然会采用操作系统限定的队列最大长度 + * backlog 参数的值大于操作系统限定的队列的最大长度 + * backlog 参数的值小于或等于0 + * ServerSocket 构造方法中没有设置 backlog 参数 + + # 绑定主机地址 + * 一般主机只有一个地址,默认就会绑定它 + * 如果主机具备多个网卡,一个网卡连接局域网网(192.168.3.4),一个网卡连接互联网(59.110.167.11) + 如果你的程序,只想给局域网用户访问,则需要绑定ip到局域网 + new ServerSocket(1024,50,InetAddress.getByName("192.168.3.4")); + * 如果你绑定的地址是:0.0.0.0,则系统会随机绑定到一个ip地址 + new ServerSocket(1024,50,InetAddress.getByName("0.0.0.0")); + + # 无参的构造方法 + * 有一个无參的构造方法 + ServerSocket(); + * 使用这种方式创建了实例对象,那么还需要通过 bind() api 来完成绑定 + void bind(SocketAddress endpoint) + void bind(SocketAddress endpoint, int backlog) + * 这个无參构造的作用是,允许服务器在绑定到指定端口之前,进行一些 Socket 的选项设置 + 因为有些设置,一旦服务器与端口绑定了,就不能再更改了 + + * 设置端口重用demo + ServerSocket serverSocket = new ServerSocket(); + serverSocket.setReuseAddress(true); + serverSocket.bind(new InetSocketAddress("0.0.0.0",1024)); + + + # 接收客户端的连接 + * 通过api accept() 来获取新的连接,该api会一直阻塞,直到新的连接 + * 如果客户端与服务器断开了连接,服务器端会抛出一个:SocketException,这个异常应该被捕获,不能影响服务端继续为其他客户端提供服务 + + # 关闭 ServerSocket + * 服务器一般不会关闭,24H * 7 都对外提供服务 + * 在一些情况下,希望及时释放服务器的端口,以便让其他的程序占用,可以执行 close() api + * isBound() api仅仅判断是否已经绑定了端口,即时 ServerSocket 已经被关闭 + * isClosed() api仅仅判断是否关闭,只有在执行了 close() api 后,该方法返回 true. + * 确定一个 ServerSocket 已经绑定了端口,并且没有被关闭 + boolean isService = serverSocket.isBound() && !serverSocket.isClose(); + + # 获取 ServerSocket 的信息 + * 获取服务器绑定的ip地址/端口 + InetAddress getInetAddress() + int getLocalPort() + + # ServerSocket 选项 + * ServerSocket 具备三个选项 + 1,SO_TIMEOUT + * 表示等待客户端链接的超时时间 + 2,SO_REUSEADDR + * 表示是否允许地址重复 + 3,SO_RCVBUF + * 表示接收数据的缓冲区大小 + * SO_TIMEOUT + * accept()等待客户端连接的超时时间,单位是毫秒 + * api + void setSoTimeout(int timeout) + int getSoTimeout() + * 如果该值为:0,则永远不是超时,这也是默认值 + + * SO_REUSEADDR + * 不多解释 + + * SO_RCVBUF + * api + void setReceiveBufferSize (int size) + int getReceiveBufferSize() + * 设置缓冲区大小,必须在绑定端口之前进行设置才有效 + * setReceiveBufferSize() 方法,相当于对所有由 accpet()方法返回的 Socket 设置接收数据的缓冲区大小 + + # 设置连接时间,延迟,带宽的相对重要性 + * api + void setPerformancePreferences(int connectionTime,int latency,int bandwidth) + * 不多解释 + + + + + \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/socket-api.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/socket-api.java" new file mode 100644 index 00000000..7484c85a --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/socket-api.java" @@ -0,0 +1,62 @@ +----------------------------- +构造函数 | +----------------------------- + public Socket() + Socket(Proxy proxy) + Socket(String host, int port)throws UnknownHostException, IOException + Socket(String host, int port, InetAddress localAddr,int localPort) throws IOException + Socket(InetAddress address, int port) throws IOException + Socket(InetAddress address, int port, InetAddress localAddr,int localPort) throws IOException + Socket(Proxy proxy) + +----------------------------- +api | +----------------------------- + void bind(SocketAddress bindpoint) + void close() + void connect(SocketAddress endpoint) + void connect(SocketAddress endpoint, int timeout) + + SocketChannel getChannel() + InetAddress getInetAddress() + InputStream getInputStream() + boolean getKeepAlive() + InetAddress getLocalAddress() + int getLocalPort() + SocketAddress getLocalSocketAddress() + boolean getOOBInline() + OutputStream getOutputStream() + int getPort() + int getReceiveBufferSize() + SocketAddress getRemoteSocketAddress() + boolean getReuseAddress() + int getSendBufferSize() + int getSoLinger() + int getSoTimeout() + boolean getTcpNoDelay() + int getTrafficClass() + + boolean isBound() + boolean isClosed() + boolean isConnected() + boolean isInputShutdown() + boolean isOutputShutdown() + + void sendUrgentData (int data) + void setKeepAlive(boolean on) + void setOOBInline(boolean on) + void setPerformancePreferences(int connectionTime,int latency,int bandwidth) + void setReceiveBufferSize(int size) + void setReuseAddress(boolean on) throws SocketException + void setSendBufferSize(int size) + void setSoLinger(boolean on, int linger) + void setSoTimeout(int timeout) + void setTcpNoDelay(boolean on) + void setTrafficClass(int tc) + void shutdownInput() + void shutdownOutput() + + + + + diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/socket-\344\273\243\347\220\206.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/socket-\344\273\243\347\220\206.java" new file mode 100644 index 00000000..e69de29b diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/socket-\351\200\211\351\241\271.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/socket-\351\200\211\351\241\271.java" new file mode 100644 index 00000000..f6cc2643 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/socket-\351\200\211\351\241\271.java" @@ -0,0 +1,159 @@ +---------------------------- +socket选项 | +---------------------------- + SocketOptions 常量 + TCP_NODELAY + * 是否立即发送数据 + SO_RESUSEADDR + * 是否允许重用socket所绑定的本地地址 + SO_TIMEOUT + * 表示接收数据时的,等待超时时间 + SO_LINGER + * 表示当执行Socket的close()方法时,是否立即关闭底层的socket + SO_RCVBUF + * 表示接收数据的缓冲区大小 + SO_SNDBUF + * 表示发送数据的缓冲区大小 + SO_KEEPALIVE + * 表示对于长时间处于空闲状态的Socket是否要自动把它关闭 + OOBINLINE + * 表示是否支持发送一个字节的TCP紧急数据 + + StandardSocketOptions 常量 + * 对于 nio 的定义 + + +---------------------------- +socket选项-详解 | +---------------------------- + SO_RESUSEADDR + * 默认值为 false,表示不允许地址重用 + * socket api + void setReuseAddress(boolean on) + boolean getReuseAddress() + + * 当 Socket 执行 close()关闭的时候,如果网络上还有发送到这个Socket的数据,那么底层的Socket不会立即释放本地端口,而是会等待一段时间 + 确保收到了网络上发过来的延迟数据,然后再释放端口,Socket 收到这些延迟数据后,不会做任何处理,目的是:确保这些数据不会被其他碰巧绑定到同样端口的新进程接收到 + * 为了确保一个进程关闭socket后,即使它还没释放端口,同一个主机上的其他进程还可以即刻重用该端口,可以调用api设置 true + * 需要注意的是,setReuseAddress(true),必须在Socket还没有绑定到一个本地端口之前调用,否则无效 + * 公用同一个端口的进程都必须调用:setReuseAddress(true) + + SO_TIMEOUT + * 读取超时时间,单位是毫秒 + * socket api + void setSoTimeout(int timeout) + int getSoTimeout() + + * 在 InputStream 执行 read() 数据的时候,如果没有数据就会等待 + * 如果超过 SO_TIMEOUT 时间还没有数据,就抛出异常 + * 该api必须在读取数据之前设置才有效,当抛出了:SocketTimeoutException,连接并没有断开,可以尝试再次的读取数据 + * 包括 accept() Api,超过该时间限制没有新的连接时也会异常 + + SO_RCVBUF + * 控制输入数据的缓冲区大小 + * socket api + void setReceiveBufferSize(int size) + int getReceiveBufferSize() + * 如果传输大的连续的数据块(基于http或者ftp协议的通信)可以使用较大的缓冲区,这可以减少传输数据的次数,提高传输效率 + * 如果是交互频繁,且单次传输数据量比较小的通信方式(Telnet和网络游戏),则应该才用小的缓冲区,确保小批量的数据能够及时发送给对方 + * 这种设置缓冲区大小的原则,也同样适用于Socket的SO_SNDBUF选项 + * 如果底层不支持SO_RCVBUF/SO_SNDBUF选项,那么会抛出异常 + + TCP_NODELAY + * 默认值为 false,表示采用用Negale算法 + * socket api + void setTcpNoDelay(boolean on) + boolean getTcpNoDelay() + + * 默认情况下,发送数据采用Negale算法,该算法指的是:发送方发送的数据 不会立即发出,而是先放在缓冲区内 + 缓冲区满了再发出,发送完一批数据后,会等待接收方对这批数据回应,然后再发送下一批数据 + * 这种算法通过减少传输数据的次数来提高通信效率,适用于:发送方需要发送大批量的数据,并且接收方会及时做出回应的场合 + * 如果发送方持续地发送小批量数据,并且接收方不一定会立即响应数据,那么Negale算法会使发送方运行慢(游戏,鼠标的移动都会发送数据到服务器,就不要才用Negale算法) + * 如果socket底层不支持 TCP_NODELAY 会抛出 SocketException + + + SO_LINGER + * 控制socket的关闭行为 + * socket api + void setSoLinger(boolean on, int linger) + void setSoLinger(boolean on, int linger) + * 默认情况下,socket执行close()时,该方法会立即 return,但底层socket实际上并不会立即关闭 + 它会延迟一段时间,直到发送完毕所有剩余数据,才会真正关闭socket,断开连接 + * 如果执行以下方法 + + //注意,超时的单位是秒,而不是毫秒 + socket.setSoLinger(true, 3600); + + 此时socket执行close(),该方法不会立即返回,而是进入阻塞状态 + 同时底层的socket会尝试发送剩余数据,只有满足两个条件之一,close()方法才会 return + 1,底层socket已经发送完毕所有的数据 + 2,尽管底层socket并没有发送完毕剩余的数据,但是超时,剩余未发送的数据会被丢弃 + + SO_SNDBUF + * 控制输出数据的缓冲区的大小 + * socket api + void setSendBufferSize(int size); + int getSendBufferSize(); + * 同上 + + SO_KEEPALIVE + * 是否保持连接,默认为false + * socket api + void setKeepAlive(boolean on) + boolean getKeepAlive() + * 如果该属性为true,则底层的tcp会监视该连接是否有效,如果连接处于空闲状态(两端都没有传递数据)超过了2小时 + 本地的tcp实现会发送一个数据包给远程的socket,如果远程的tcp没有回应,tcp实现就会持续尝试11分钟,直到收到响应为止 + 如果在12分钟内未收到响应,tcp实现就会自动关闭本地socket,断开连接 + * 在不同的网络平台上,tcp实现尝试与远程socket对话的时限会有所差别 + * 默认值为:false,表示tcp不会监视连接是否有效,不活动的客户端可能会永久存活小区,而不会注意到服务器已经崩溃 + + OOBINLINE + * 表示是否支持发送一个字节的紧急数据 + * socket api + void setOOBInline(boolean on) + boolean getOOBInline() + * 当该值为 true 的时候,表示支持发送一个字节的 tcp 紧急数据, + 通过 Socket 类的 sendUrgentData (int data) 来发送一个字节的紧急数据 + * 该值默认为 false,在这种情况下,接收方收到紧急数据时不做任何处理,直接丢弃 + * 如果该值为 true,接收方会把收到的紧急数据与普通数据放在同样的队列中 + 值得注意的是,除非使用一些更高层的协议,否则接收方处理紧急数据的能力非常有限 + 当紧急数据到来时,接收方不会收到任何通知,因此很难区分普通数据与紧急数据,只好按照相同的方式去处理它们 + +---------------------------- +socket服务类型选项 | +---------------------------- + * internet上传输数据分为不同的服务类型,它们有不同的'定价' + * IP规定了4种服务类型,用来定型的描述服务的质量 + 1,低成本 0x02(二进制倒数第2位=1) 发送成本低 + 2,高可靠 0x04(二进制倒数第3位=1) 保证把数据可靠的发送到目的地 + 3,高吞吐 0x08(二进制倒数第4位=1) 一次可以接收/发送大批量的数据 + 4,最小延迟 0x10(二进制倒数第5位=1) 传输数据最快,把数据快速的送到目的地 + * 以上四种服务类型还可以进行组合,以获取不同的效果 + * 他们在 SocketOptions 都以常量的形式有定义 + * socket api + void setTrafficClass(int tc) + int getTrafficClass() + * demo + socket.setTrafficClass(0x02 | 0x10); //低成本 + 最小延迟 模式 + + +---------------------------- +socket-连接时间/延迟/带宽 | +---------------------------- + * 设置连接时间,延迟,带宽之间的相对重要性 + * socket api + void setPerformancePreferences(int connectionTime,int latency,int bandwidth) + * 参数详解 + connectionTime 表示用最少的时间建立连接 + latency 表示最小延迟 + bandwidth 表示最高带宽 + * 该api用来设置3项指标之间相对重要性,可以为这些参数赋予任意整数 + 这些整数之间的相对大小就决定了相应参数的相对重要性 + 例:参数 *2,1,3就表示带宽最重要,其次是最少连接时间,最后是最小延迟 + + + + + + + \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/tcp-\345\237\272\346\234\254\346\224\266\345\217\221.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/tcp-\345\237\272\346\234\254\346\224\266\345\217\221.java" new file mode 100644 index 00000000..4fee077a --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/tcp-\345\237\272\346\234\254\346\224\266\345\217\221.java" @@ -0,0 +1,116 @@ +---------------------------- +Server | +---------------------------- + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class Server { + + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + private static class Handler implements Runnable { + private Socket socket; + public Handler(Socket socket) { + this.socket = socket; + } + @Override + public void run() { + try { + PrintStream printStream = new PrintStream(this.socket.getOutputStream()); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(this.socket.getInputStream(),DEFAULT_CHARSET)); + while(true) { + String line = bufferedReader.readLine(); + System.out.println("recv:" + line); + + printStream.println(line.toUpperCase()); + + if(line.equalsIgnoreCase("bye")) { + break; + } + } + }catch (Exception e){ + e.printStackTrace(); + }finally { + try { + this.socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public static void main(String[] args) throws Exception{ + + try(ServerSocket serverSocket = new ServerSocket()){ + + serverSocket.bind(new InetSocketAddress("0.0.0.0",1024)); + serverSocket.setReuseAddress(true); + + while(true) { + + Socket socket = serverSocket.accept(); + new Thread(new Handler(socket)).start(); + } + } + } +} + +---------------------------- +Client | +---------------------------- +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Scanner; + +public class Client { + + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + public static void main(String[] args) throws Exception { + + try (Socket socket = new Socket()) { + + socket.setSoTimeout(3000); // 超时时间 + socket.connect(new InetSocketAddress("127.0.0.1", 1024), 3000); // 发起连接,设置超时时间 + + PrintStream printStream = new PrintStream(socket.getOutputStream()); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(),DEFAULT_CHARSET)); + + try(Scanner scanner = new Scanner(System.in)){ + + while (true) { + + // 读取标准输入 + String line = scanner.nextLine(); + + System.out.println("send:" + line); + + printStream.println(line); + + // 读取服务端响应 + String response = bufferedReader.readLine(); + + System.out.println("recv:" + response); + + if("bye".equalsIgnoreCase(response)) { + break; + } + } + } + } + } +} diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/tcp.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/tcp.java" new file mode 100644 index 00000000..5b697a58 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/tcp/tcp.java" @@ -0,0 +1,33 @@ +------------------------- +tcp | +------------------------- + # 传输层控制协议 RFC 793 + * 面向连接 + * 可靠 + * 基于字节流 + +------------------------- +tcp 机制 | +------------------------- + # 三次握手,四次挥手 + 客户端A 服务器B + |---------> SYN=1,seq=x | + |SYN-SENT | + | SYN=1,ACK=1,seq=y,ack=x+1 <---------| + | SYN=RCVD| + |---------> ACK=1,seq=x+1,ack=y+1 | + |ESTAB-LISHED <-数据传递-> ESTAB-LISHED| + + + 客户端进程先创建'传输控制模块TCB',然后向B发出连接请求报文段 + 此时,首部中的同步位:syn=1,同时选择一个初始序号seq=x,tcp规定,syn报文段(syn=1的报文段)不能携带数据,但是要消耗掉一个序号 + 此时tcp客户端进程进入SYN-SENT同步一发送状态 + + # 连接可靠性 + + # 传输可靠性 + * 失败重发 + * 排序,顺序发送,顺序组装 + * 丢弃,超时 + * 重发机制,定时器 + diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-api.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-api.java" new file mode 100644 index 00000000..7fcb1673 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-api.java" @@ -0,0 +1,49 @@ +—————————————————————— +DatagramSocket | +—————————————————————— + # 接收和发送UDP数据,既是服务又是客户端 + # 构造函数 + DatagramSocket() + * 随机端口 + DatagramSocket(int port) + * 指定端口 + DatagramSocket(int port, InetAddress laddr) + DatagramSocket(SocketAddress bindaddr) + * 指定ip和端口 + + # 方法 + setReuseAddress(true); + * 是否允许端口重用 + + bind(new InetSocketAddress(1024)); + * 绑定端口 + + receive(DatagramPacket p) + * 接收一个udp报文 + + send(DatagramPacket p) + * 发送一个udp报文 + + setSoTimeout(int timeout) + * 设置超时时间 + + close() + * 关闭释放资源 + +—————————————————————— +DatagramPacket | +—————————————————————— + # UDP数据包实体 + # 构造函数 + DatagramPacket(byte buf[], int length) + DatagramPacket(byte buf[], int offset, int length) + DatagramPacket(byte buf[], int offset, int length,InetAddress address, int port) + DatagramPacket(byte buf[], int offset, int length, SocketAddress address) + DatagramPacket(byte buf[], int length,InetAddress address, int port) + DatagramPacket(byte buf[], int length, SocketAddress address) + + # 方法 + ..不多,都很简单 + + + \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-demo-\345\271\277\346\222\255\345\217\221\347\216\260\345\261\200\345\237\237\347\275\221\350\256\276\345\244\207.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-demo-\345\271\277\346\222\255\345\217\221\347\216\260\345\261\200\345\237\237\347\275\221\350\256\276\345\244\207.java" new file mode 100644 index 00000000..2bdc5130 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-demo-\345\271\277\346\222\255\345\217\221\347\216\260\345\261\200\345\237\237\347\275\221\350\256\276\345\244\207.java" @@ -0,0 +1,163 @@ + +------------------------------ +局域网的设备发现 | +------------------------------ + # 要求所有设备都监听一个UDP端口,监听广播信息 + # 收到广播消息后,从消息里面获取到服务器的ip,端口等信息 + # 再往服务器发送自身的设备id,ip,提供的服务(协议类型),端口等等信息 + +------------------------------ +扫描 | +------------------------------ + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +public class Scanner { + + // 节点描述 + private static class Endpoint { + private String id; + private int port; + private String ip; + public Endpoint(String id, int port, String ip) { + super(); + this.id = id; + this.port = port; + this.ip = ip; + } + @Override + public String toString() { + return "Endpoint [id=" + id + ", port=" + port + ", ip=" + ip + "]"; + } + } + + // 监听服务 + private static class Listenner implements Runnable { + private int port; + private CountDownLatch countDownLatch; + private volatile boolean done; + private List endpoints; + public Listenner(int port,CountDownLatch countDownLatch) { + this.port = port; + this.countDownLatch = countDownLatch; + this.done = false; + this.endpoints = new LinkedList<>(); + } + + @Override + public void run() { + DatagramSocket datagramSocket = null; + try { + + this.countDownLatch.countDown(); + + datagramSocket = new DatagramSocket(null); + datagramSocket.setReuseAddress(true); + datagramSocket.bind(new InetSocketAddress("0.0.0.0", this.port)); + + while(!this.done) { + byte[] data = new byte[1024]; + DatagramPacket datagramPacket = new DatagramPacket(data,data.length); + datagramSocket.receive(datagramPacket); + String[] response = new String(datagramPacket.getData(),0,datagramPacket.getLength()).split(":"); + this.endpoints.add(new Endpoint(response[0], Integer.parseInt(response[1]),datagramPacket.getAddress().getHostAddress())); + } + }catch (Exception e) { + e.printStackTrace(); + }finally { + if(datagramSocket != null) { + datagramSocket.close(); + } + } + } + + public List endpoints(){ + this.done = true; + return this.endpoints; + } + } + + public static void broadcast(int port,byte[] data) { + + DatagramSocket datagramSocket = null; + try { + + datagramSocket = new DatagramSocket(); + + DatagramPacket datagramPacket = new DatagramPacket(data, data.length); + datagramPacket.setAddress(InetAddress.getByName("255.255.255.255")); + datagramPacket.setPort(port); + + datagramSocket.send(datagramPacket); + + }catch (Exception e) { + e.printStackTrace(); + }finally { + if(datagramSocket != null) { + datagramSocket.close(); + } + } + } + + public static void main(String[] args) throws Exception{ + + // 监听服务 + int port = 10086; + CountDownLatch countDownLatch = new CountDownLatch(1); + + Listenner listenner = new Listenner(port, countDownLatch); + Thread thread = new Thread(listenner); + thread.setDaemon(true); // 守护线程 + thread.start(); + + countDownLatch.await(); + + // 广播消息 + byte[] data = ("port=" + port).getBytes(); + broadcast(1024, data); + + System.in.read(); + + // 读取广播后的响应 + List endpoints = listenner.endpoints(); + for(Endpoint endpoint : endpoints) { + System.out.println(endpoint); + } + } +} + + + + + +------------------------------ +服务端 | +------------------------------ +import socket + +id = '0xFF' +port = 1024 + +server = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) +server.bind(('0.0.0.0', port)) + +while True: + data, client = server.recvfrom(1024) + + print('收到数据:%s %s'%(data,client)) + + # 从数据中获取到报告服务的端口 + serverPort = int(data.decode().split('=')[1]) + response = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) + # 向服务端报告自己的id和提供服务的端口 + response.sendto((id + ':' + str(port)).encode(),(client[0],serverPort)) + + + + diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-nio-NioClient.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-nio-NioClient.java" new file mode 100644 index 00000000..4a9a4b61 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-nio-NioClient.java" @@ -0,0 +1,26 @@ + +import java.net.InetSocketAddress; +import java.net.StandardSocketOptions; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.nio.charset.StandardCharsets; + +public class NioClient { + public static void main(String[] args) throws Exception{ + + DatagramChannel datagramChannel = DatagramChannel.open(); + datagramChannel.setOption(StandardSocketOptions.SO_REUSEADDR, Boolean.TRUE); + // 绑定发送端口 + datagramChannel.bind(new InetSocketAddress(7725)); + + + ByteBuffer byteBuffer = ByteBuffer.allocate(65507); + byteBuffer.put("Nio UDP".getBytes(StandardCharsets.UTF_8)); + byteBuffer.flip(); + + // 返回已经成功发送的字节数据, 可能一次性没发送完毕 + int count = datagramChannel.send(byteBuffer, new InetSocketAddress("127.0.0.1", 1024)); + + System.out.println(count); + } +} diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-nio-NioUdpServer.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-nio-NioUdpServer.java" new file mode 100644 index 00000000..7de62cd5 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-nio-NioUdpServer.java" @@ -0,0 +1,118 @@ +---------------------------- +非 selector | +---------------------------- +import java.net.InetSocketAddress; +import java.net.StandardSocketOptions; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.nio.charset.StandardCharsets; + +public class NioUdpServer { + + public static void main(String[] args) throws Exception { + + try(DatagramChannel datagramChannel = DatagramChannel.open()){ + + datagramChannel.bind(new InetSocketAddress(1024)); + // 一些socket的选项 + datagramChannel.setOption(StandardSocketOptions.SO_REUSEADDR, Boolean.TRUE); + + // 预定义缓冲区 + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(65507); + + // 阻塞接收数据 + while (true){ + // 客户端地址信息 + InetSocketAddress inetSocketAddress = (InetSocketAddress) datagramChannel.receive(byteBuffer); + String hostName = inetSocketAddress.getHostName(); + int port = inetSocketAddress.getPort(); + String hostString = inetSocketAddress.getHostString(); + System.out.println(hostName + "[" + hostString +"]:" + port); + + // 数据信息 + byteBuffer.flip(); + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + byteBuffer.clear(); + System.out.println(new String(bytes, StandardCharsets.UTF_8)); + } + } + } +} + + +---------------------------- +selector | +---------------------------- + # udp的selector只有一个作用, 就是用它来轮询n个客户端/服务端 + * udp没有连接, 不需要去监听状态, 也不存在阻塞等待数据的情况 + * 如果只是一个客户端/服务端, 那么使用传统的:DatagramSocket 没区别 + +import java.net.InetSocketAddress; +import java.net.StandardSocketOptions; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; + +public class NioUdpServer { + + public static void main(String[] args) throws Exception { + + Selector selector = Selector.open(); + DatagramChannel datagramChannel = DatagramChannel.open(); + + try{ + datagramChannel.bind(new InetSocketAddress(1024)); + + // 非阻塞模式 + datagramChannel.configureBlocking(false); + // 一些socket的选项 + datagramChannel.setOption(StandardSocketOptions.SO_REUSEADDR, Boolean.TRUE); + + datagramChannel.register(selector, SelectionKey.OP_READ); + + // 预定义缓冲区 + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(65507); + + while (selector.select() > 0){ + Iterator selectionKeyIterator = selector.selectedKeys().iterator(); + while (selectionKeyIterator.hasNext()){// 读事件 + SelectionKey selectionKey = selectionKeyIterator.next(); + try{ + if(selectionKey.isReadable()){ + + DatagramChannel channel = (DatagramChannel) selectionKey.channel(); + + // 客户端地址信息 + InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.receive(byteBuffer); + String hostName = inetSocketAddress.getHostName(); + int port = inetSocketAddress.getPort(); + String hostString = inetSocketAddress.getHostString(); + System.out.println(hostName + "[" + hostString +"]:" + port); + + // 数据信息 + byteBuffer.flip(); + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + byteBuffer.clear(); + System.out.println(new String(bytes, StandardCharsets.UTF_8)); + + }else if(selectionKey.isWritable()){ + }else if(selectionKey.isAcceptable()){ + }else if(selectionKey.isConnectable()){ + }else{ } + }finally { + selectionKeyIterator.remove(); + } + } + } + + }finally { + selector.close(); + datagramChannel.close(); + } + } +} diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\215\225\346\222\255.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\215\225\346\222\255.java" new file mode 100644 index 00000000..d30e2a9a --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\215\225\346\222\255.java" @@ -0,0 +1,69 @@ +------------------------ +udp 单播 | +------------------------ + # 点对点 + * 客户端发送数据的端口,其实就是接受服务端响应的端口 + + + +------------------------ +Server | +------------------------ + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; + +public class Server { + public static void main(String[] args) throws Exception { + + try (DatagramSocket datagramSocket = new DatagramSocket(1024)) { + + while (true) { + + // 读取客户端数据 + byte[] buffer = new byte[1024]; + DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length); + datagramSocket.receive(datagramPacket); + + // 处理收到的客户端数据 + String ip = datagramPacket.getAddress().getHostAddress(); + String data = new String(datagramPacket.getData(), 0, datagramPacket.getLength()); + int port = datagramPacket.getPort(); + + System.out.println("recv: ip=" + ip + ",port=" + port + ",data=" + data); + + // 响应客户端 + buffer = data.toUpperCase().getBytes(); + datagramSocket.send(new DatagramPacket(buffer, 0, buffer.length, new InetSocketAddress(ip, port))); + } + } + } +} + +------------------------ +client | +------------------------ + + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; + +public class Client { + public static void main(String[] args) throws Exception { + + try(DatagramSocket datagramSocket = new DatagramSocket()){ + + // 往服务端发送数据 + byte[] data = "Hello".getBytes(); + datagramSocket.send(new DatagramPacket(data, 0, data.length, new InetSocketAddress("127.0.0.1", 1024))); + + // 读取服务端响应的数据 + byte[] buffer = new byte[1024]; + DatagramPacket datagramPacket = new DatagramPacket(buffer,buffer.length); + datagramSocket.receive(datagramPacket); + System.out.println("recv:" + new String(datagramPacket.getData(),0,datagramPacket.getLength())); + } + } +} diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\237\272\346\234\254\346\224\266\345\217\221.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\237\272\346\234\254\346\224\266\345\217\221.java" new file mode 100644 index 00000000..c90ee79f --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\237\272\346\234\254\346\224\266\345\217\221.java" @@ -0,0 +1,41 @@ +--------------------------- +UDP发送 | +--------------------------- + /* + 发送udp + */ + try(DatagramSocket datagramSocket = new DatagramSocket()){ + byte[] data = "Hello".getBytes(); + DatagramPacket datagramPacket = new DatagramPacket(data, 0, data.length, new InetSocketAddress("127.0.0.1", 10086)); + datagramSocket.send(datagramPacket); + } + + /* + 从指定端口发送数据,哪怕该端口已经被监听(端口重用) + */ + try(DatagramSocket datagramSocket = new DatagramSocket(null)){ + datagramSocket.setReuseAddress(true); //允许端口重用 + datagramSocket.bind(new InetSocketAddress(1024)); //绑定要从本地的哪个端口发送 + byte[] data = "Hello".getBytes(); + // 设置数据,目的端口,目的ip + DatagramPacket datagramPacket = new DatagramPacket(data, 0, data.length, new InetSocketAddress("127.0.0.1", 10086)); + datagramSocket.send(datagramPacket); + } + +--------------------------- +UDP接收 | +--------------------------- + //创建udpsocket服务.建立端点,监听端口 + try(DatagramSocket datagramSocket = new DatagramSocket(10000)){ + while(true){ + //定义数据包,用于存储数据 + byte[] buffer = new byte[1024];//缓冲区 + DatagramPacket datagramPacket = new DatagramPacket(buffer,buffer.length); + //通过socket服务receive方法将收到的数据存入数据包中(阻塞方法) + datagramSocket.receive(datagramPacket); + //通过数据包的方法获取其中的数据 + String ip = datagramPacket.getAddress().getHostAddress(); //获取IP。 + String data = new String(datagramPacket.getData(),0,datagramPacket.getLength());//获取数据。 + int port = datagramPacket.getPort(); //获取端口。 + } + } \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\244\232\346\222\255.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\244\232\346\222\255.java" new file mode 100644 index 00000000..611a0d13 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\244\232\346\222\255.java" @@ -0,0 +1,8 @@ +------------------------ +udp 多播 | +------------------------ + # 点对N(组播),把目标分成组,一次性给一组发送消息 + # 专门用于多播的ip地址段(D类地址) + 224-239 + + diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\271\277\346\222\255.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\271\277\346\222\255.java" new file mode 100644 index 00000000..6add5ac5 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp-\345\271\277\346\222\255.java" @@ -0,0 +1,43 @@ +------------------------ +udp 广播 | +------------------------ + # 往所有节点发送消息 + * 理论来说会往所有的设备发送广播数据 + * 但是会被路由器拦截,最终只能对局域网里面的设备进行广播 + + + # 受限广播地址: 255.255.255.255 + # C网广播地址一般是:xxx.xxx.xxx.255 (192.168.1.255) + + # 广播地址的运算 + ip 192.168.127.7 + + 子网掩码 255.255.255.0 + + 网络地址 192.168.124.0 + + 广播地址 192.168.124.255 + + +------------------------ +udp 广播 | +------------------------ +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; + +public class Broadcast { + public static void main(String[] args) throws Exception { + + try(DatagramSocket datagramSocket = new DatagramSocket()){ + byte[] buffer = "Hello".getBytes(); + DatagramPacket datagramPacket = new DatagramPacket(buffer, 0,buffer.length); + // 设置广播地址 + datagramPacket.setAddress(InetAddress.getByName("255.255.255.255")); + // 设置端口 + datagramPacket.setPort(1024); + // 发送广播消息 + datagramSocket.send(datagramPacket); + } + } +} \ No newline at end of file diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp.java" new file mode 100644 index 00000000..e0804b03 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/udp/udp.java" @@ -0,0 +1,27 @@ +------------------------ +ddp | +------------------------ + # 用户数据报文协议 RFC 768 + # 不可靠 + * 数据发送后,不会保留数据(用于备份重发) + * 在ip数据报头部仅仅加入了复用和数据校验字段 + * 发送到生产数据,接收端从网络中抓取数据 + * 结构简单,无校验,速度快,容易丢包,可广播 + + + +------------------------ +ddp 包最大的长度 | +------------------------ + # UDP理想的一个数据包的数据大小最大是 6507 字节 + * 16 bit(2字节),存储长度信息 + * 2 ^ 16 - 1 = 64k - 1 = 65536 - 1 = 65535 + * 自身协议占用:32 + 32 = 64(bit) = 8 字节 + * 65535(UDP本身协议的报文长度) - 8(udp包头) - 20(ip包头) = 65507 字节 + + # 只看第上面的因素有点理想化了 + * 因为 UDP 属于不可靠协议,应该尽量避免在传输过程中,数据包被分割 + * 所以这里有一个非常重要的概念 MTU -- 也就是最大传输单元 + * 在 Internet 下 MTU 的值为 576 字节 + * 所以在 internet 下使用 UDP 协议,每个数据报最大的字节数为: 576 - 20 - 8 = 548 字节 + diff --git "a/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/\347\275\221\347\273\234\347\274\226\347\250\213\345\276\201\351\200\224.java" "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/\347\275\221\347\273\234\347\274\226\347\250\213\345\276\201\351\200\224.java" new file mode 100644 index 00000000..b94d8e76 --- /dev/null +++ "b/Java/\347\275\221\347\273\234\347\274\226\347\250\213\344\270\223\351\242\230/\347\275\221\347\273\234\347\274\226\347\250\213\345\276\201\351\200\224.java" @@ -0,0 +1,78 @@ + +---------------------------- +缃戠粶缂栫▼ | +---------------------------- + # 瀛︿範鍦板潃 + http://www.52im.net/forum.php?mod=collection&op=all + http://www.52im.net/topic-tcpipvol1.html + + # socket + RFC 797(IP鍦板潃鍜岀鍙g殑缁撳悎鍗忚) + +---------------------------- +osc | +---------------------------- + # 涓冨眰 + Application + * 搴旂敤灞 FTP HTTP + Presentation + * 琛ㄧ幇灞 + Session + * 浼氳瘽灞 + Transport + * 浼犺緭灞 TCP UDP + Network + * 缃戠粶灞 IP[IGMP ICMP ARP] + Data Link + * 鏁版嵁閾捐矾灞 + Physical + * 鐗╃悊灞 + + # 浜斿眰 + +---------------------------- +鎶ユ枃娈 | +---------------------------- + # 鎶ユ枃鍦ㄤ紶杈撹繃绋嬩腑,浼氫笉鏂殑灏佽涓虹粍,鍖,甯ф潵浼犺緭 + # 灏佽鐨勬柟寮忓氨鏄坊鍔犱竴浜涙帶鍒朵俊鎭粍鎴愮殑棣栭儴,灏辨槸鎶ユ枃澶 + +---------------------------- +ip鍦板潃 | +---------------------------- + # 浜斿ぇ绫: A B C D E + # E 绫诲睘浜庣壒娈婁繚鐣欏湴鍧 + + 1.1.1.1 + * 鐩存帴骞挎挱鍦板潃 + * 鐞嗚涓婃潵璇,浼氬線鎵鏈夌殑浜掕仈缃戝湴鍧閮藉箍鎾暟鎹寘(浣嗘槸浼氳闃茬伀澧欐嫤鎴) + + 255.255.255.255 + * 鍙楅檺骞挎挱鍦板潃,浼氬線灞鍩熺綉骞挎挱鏁版嵁鍖 + + # ipv4 鍙互杞崲涓 ipv6,鍙嶄箣鑲畾涓嶈 + + + +---------------------------- +tcp/udp | +---------------------------- + TCP + * 闈㈠悜杩炴帴 + * 閫氳繃涓夋鎻℃墜寤虹珛杩炴帴,閫氫俊瀹屾垚瑕佹柇寮杩炴帴 + * 绔埌绔殑閫氫俊 + + UDP + * 闈㈠悜鏃犺繛鎺 + * UDP鏁版嵁涓寘鍚簡鐩殑鍦扮鍙e彿,鍜岃嚜宸辩鍙e彿 + * 鍙互骞挎挱 + +---------------------------- +缃戠粶缂栫▼闂 | +---------------------------- + 鏁版嵁瀹屾暣鎬 + 绮樺寘 鎷嗗寘 鍗婂寘 + 闀胯繛鎺 + 蹇冭烦 + 鏂嚎閲嶈繛 + 鏂囦欢鍒嗘浼犺緭 + \ No newline at end of file diff --git a/JavaSript/BOM/javascript-bom-window.js b/JavaSript/BOM/javascript-bom-window.js index 325e7d17..8c58694d 100644 --- a/JavaSript/BOM/javascript-bom-window.js +++ b/JavaSript/BOM/javascript-bom-window.js @@ -60,8 +60,9 @@ javascript- clearInterval(id); * 清除指定的定时器,传递定时器的id - setTimeOut(fun,10); + setTimeout(fun,10,param); * 在10毫秒后,去执行指定的函数,也可以直接是JS代码,仅仅会执行一次,也会返回一个id + * 最后一个参数(param),是执行fun时传递的参数,可以省略 clearTimeOut(id); * 清除指定的单次定时器,传递定时器的id @@ -86,7 +87,15 @@ javascript- scrollTo(x,y); * scrollTo() 方法可把内容滚动到指定的坐标。 + + btoa(v) + * 把数据转换为base64编码,好像不支持中文 + * 对于中文可以先URI编码 + + atob(b) + * 把base64编码转换为原始数据 + -------------------------- javascript-事件 | -------------------------- diff --git a/JavaSript/DOM/javascript-dom-attribute.js b/JavaSript/DOM/javascript-dom-attribute.js index 5dcc2b32..b21346d1 100644 --- a/JavaSript/DOM/javascript-dom-attribute.js +++ b/JavaSript/DOM/javascript-dom-attribute.js @@ -7,6 +7,13 @@ * 包含了代表一个元素的特性的Attr对象,仅用于Element节点 * 返回 NamedNodeMap 对象 + dataset + * 返回的是一个 map,里面包含了节点上的所有 data- 属性 + let p = document.querySelector('p'); + console.log(p.dataset); //DOMStringMap {a: "1", b: "2"} + console.log(p.dataset.a); //1 + + createAttribute(); * 创建一个指定名称的属性节点对象 * 返回的是一个属性节点对象 @@ -30,7 +37,6 @@ hasAttribute(); * 判断节点是否具备指定的属性 - ---------------------- 节点属性-属性 | ---------------------- diff --git a/JavaSript/DOM/javascript-dom-document.js b/JavaSript/DOM/javascript-dom-document.js index d66a2d9b..9e985781 100644 --- a/JavaSript/DOM/javascript-dom-document.js +++ b/JavaSript/DOM/javascript-dom-document.js @@ -53,6 +53,12 @@ document | * 返回当前 hover 的td * 根据事件检索 + * 根据标签名称和指定的k,v + + document.querySelector('meta[name="csrf-token"]'); + document.querySelector('meta[name]'); + + querySelector(); * 跟进表达式检索一个节点 diff --git a/JavaSript/ES6/es6-ArrayBuffer.js b/JavaSript/ES6/es6-ArrayBuffer.js new file mode 100644 index 00000000..62b1ced7 --- /dev/null +++ b/JavaSript/ES6/es6-ArrayBuffer.js @@ -0,0 +1,420 @@ +-------------------------------- +ArrayBuffer | +-------------------------------- + 1,ArrayBuffer 瀵硅薄 + 2,TypedArray 瑙嗗浘 + 3,澶嶅悎瑙嗗浘 + 4,DataView 瑙嗗浘 + 5,浜岃繘鍒舵暟缁勭殑搴旂敤 + 6,SharedArrayBuffer + 7,Atomics 瀵硅薄 + +-------------------------------- +绠浠 | +-------------------------------- + # ArrayBuffer瀵硅薄,TypedArray瑙嗗浘鍜孌ataView瑙嗗浘鏄 JavaScript 鎿嶄綔浜岃繘鍒舵暟鎹殑涓涓帴鍙 + # 浜岃繘鍒舵暟缁勭敱涓夌被瀵硅薄缁勬垚 + 1,ArrayBuffer + * 浠h〃鍐呭瓨涔嬩腑鐨勪竴娈典簩杩涘埗鏁版嵁,鍙互閫氳繃"瑙嗗浘"杩涜鎿嶄綔 + * "瑙嗗浘"閮ㄧ讲浜嗘暟缁勬帴鍙,杩欐剰鍛崇潃,鍙互鐢ㄦ暟缁勭殑鏂规硶鎿嶄綔鍐呭瓨 + + 2,TypedArray + * 瑙嗗浘,鍏卞寘鎷 9 绉嶇被鍨嬬殑瑙嗗浘,姣斿Uint8Array(鏃犵鍙 8 浣嶆暣鏁)鏁扮粍瑙嗗浘, Int16Array(16 浣嶆暣鏁)鏁扮粍瑙嗗浘, Float32Array(32 浣嶆诞鐐规暟)鏁扮粍瑙嗗浘绛夌瓑 + + 3,DataView + * 瑙嗗浘,鍙互鑷畾涔夊鍚堟牸寮忕殑瑙嗗浘,姣斿绗竴涓瓧鑺傛槸 Uint8(鏃犵鍙 8 浣嶆暣鏁),绗簩銆佷笁涓瓧鑺傛槸 Int16(16 浣嶆暣鏁),绗洓涓瓧鑺傚紑濮嬫槸 Float32(32 浣嶆诞鐐规暟)绛夌瓑,姝ゅ杩樺彲浠ヨ嚜瀹氫箟瀛楄妭搴 + + + # ArrayBuffer瀵硅薄浠h〃鍘熷鐨勪簩杩涘埗鏁版嵁 + # TypedArray 瑙嗗浘鐢ㄦ潵璇诲啓绠鍗曠被鍨嬬殑浜岃繘鍒舵暟鎹 + # DataView瑙嗗浘鐢ㄦ潵璇诲啓澶嶆潅绫诲瀷鐨勪簩杩涘埗鏁版嵁 + + # TypedArray 瑙嗗浘鏀寔鐨勬暟鎹被鍨嬩竴鍏辨湁 9 绉(DataView瑙嗗浘鏀寔闄int8C浠ュ鐨勫叾浠 8 绉) + 鏁版嵁绫诲瀷 瀛楄妭闀垮害 鍚箟 瀵瑰簲鐨 C 璇█绫诲瀷 + Int8 1 8 浣嶅甫绗﹀彿鏁存暟 signed char + Uint8 1 8 浣嶄笉甯︾鍙锋暣鏁 unsigned char + Uint8C 1 8 浣嶄笉甯︾鍙锋暣鏁(鑷姩杩囨护婧㈠嚭) unsigned char + Int16 2 16 浣嶅甫绗﹀彿鏁存暟 short + Uint16 2 16 浣嶄笉甯︾鍙锋暣鏁 unsigned short + Int32 4 32 浣嶅甫绗﹀彿鏁存暟 int + Uint32 4 32 浣嶄笉甯︾鍙风殑鏁存暟 unsigned int + Float32 4 32 浣嶆诞鐐规暟 float + Float64 8 64 浣嶆诞鐐规暟 double + + # 浜岃繘鍒舵暟缁勫苟涓嶆槸鐪熸鐨勬暟缁,鑰屾槸绫讳技鏁扮粍鐨勫璞 + + # 寰堝娴忚鍣ㄦ搷浣滅殑 API,鐢ㄥ埌浜嗕簩杩涘埗鏁扮粍鎿嶄綔浜岃繘鍒舵暟鎹,涓嬮潰鏄叾涓殑鍑犱釜 + File API + XMLHttpRequest + Fetch API + Canvas + WebSockets + +-------------------------------- +ArrayBuffer | +-------------------------------- + # ArrayBuffer瀵硅薄浠h〃鍌ㄥ瓨浜岃繘鍒舵暟鎹殑涓娈靛唴瀛,瀹冧笉鑳界洿鎺ヨ鍐 + * 鍙兘閫氳繃瑙嗗浘(TypedArray瑙嗗浘鍜孌ataView瑙嗗浘)鏉ヨ鍐 + * 瑙嗗浘鐨勪綔鐢ㄦ槸浠ユ寚瀹氭牸寮忚В璇讳簩杩涘埗鏁版嵁 + + # ArrayBuffer涔熸槸涓涓瀯閫犲嚱鏁,鍙互鍒嗛厤涓娈靛彲浠ュ瓨鏀炬暟鎹殑杩炵画鍐呭瓨鍖哄煙 + const buffer = new ArrayBuffer(32); + * 鐢熸垚浜嗕竴娈 32 瀛楄妭鐨勫唴瀛樺尯鍩,姣忎釜瀛楄妭鐨勫奸粯璁ら兘鏄 0 + * ArrayBuffer鏋勯犲嚱鏁扮殑鍙傛暟鏄墍闇瑕佺殑鍐呭瓨澶у皬(鍗曚綅瀛楄妭) + + # 璇诲啓ArrayBuffer鍐呭,闇瑕佷负瀹冩寚瀹氳鍥 + + * DataView瑙嗗浘鐨勫垱寤,闇瑕佹彁渚汚rrayBuffer瀵硅薄瀹炰緥浣滀负鍙傛暟 + + const buf = new ArrayBuffer(32); + const dataView = new DataView(buf); + let data = dataView.getUint8(0) + console.log(data); //0 + + * 32 瀛楄妭鐨勫唴瀛,寤虹珛DataView瑙嗗浘,鐒跺悗浠ヤ笉甯︾鍙风殑 8 浣嶆暣鏁版牸寮,浠庡ご璇诲彇 8 浣嶄簩杩涘埗鏁版嵁,缁撴灉寰楀埌 0, + * 鍘熷鍐呭瓨鐨凙rrayBuffer瀵硅薄,榛樿鎵鏈変綅閮芥槸 0 + + # TypedArray 瑙嗗浘,涓嶥ataView瑙嗗浘鐨勪竴涓尯鍒槸,瀹冧笉鏄竴涓瀯閫犲嚱鏁,鑰屾槸涓缁勬瀯閫犲嚱鏁,浠h〃涓嶅悓鐨勬暟鎹牸寮 + const x1 = new Int32Array(buffer); + const x2 = new Uint8Array(buffer); + + * 32 浣嶅甫绗﹀彿鏁存暟(Int32Array鏋勯犲嚱鏁)鍜 8 浣嶄笉甯︾鍙锋暣鏁(Uint8Array鏋勯犲嚱鏁) + * 鐢变簬涓や釜瑙嗗浘瀵瑰簲鐨勬槸鍚屼竴娈靛唴瀛,涓涓鍥句慨鏀瑰簳灞傚唴瀛,浼氬奖鍝嶅埌鍙︿竴涓鍥 + + * TypedArray 瑙嗗浘鐨勬瀯閫犲嚱鏁,闄や簡鎺ュ彈ArrayBuffer瀹炰緥浣滀负鍙傛暟 + * 杩樺彲浠ユ帴鍙楁櫘閫氭暟缁勪綔涓哄弬鏁,鐩存帴鍒嗛厤鍐呭瓨鐢熸垚搴曞眰鐨凙rrayBuffer瀹炰緥,骞跺悓鏃跺畬鎴愬杩欐鍐呭瓨鐨勮祴鍊 + const typedArray = new Uint8Array([0,1,2]); + + typedArray.length // 3 + typedArray[0] = 5; + typedArray // [5, 1, 2] + + * TypedArray 瑙嗗浘鐨刄int8Array鏋勯犲嚱鏁,鏂板缓涓涓笉甯︾鍙风殑 8 浣嶆暣鏁拌鍥 + * Uint8Array鐩存帴浣跨敤鏅氭暟缁勪綔涓哄弬鏁,瀵瑰簳灞傚唴瀛樼殑璧嬪煎悓鏃跺畬鎴 + + # ArrayBuffer.prototype.byteLength() + * ArrayBuffer瀹炰緥鐨刡yteLength灞炴,杩斿洖鎵鍒嗛厤鐨勫唴瀛樺尯鍩熺殑瀛楄妭闀垮害 + const buffer = new ArrayBuffer(32); + buffer.byteLength + // 32 + * 濡傛灉瑕佸垎閰嶇殑鍐呭瓨鍖哄煙寰堝ぇ,鏈夊彲鑳藉垎閰嶅け璐(鍥犱负娌℃湁閭d箞澶氱殑杩炵画绌轰綑鍐呭瓨)鎵浠ユ湁蹇呰妫鏌ユ槸鍚﹀垎閰嶆垚鍔 + if (buffer.byteLength === [鍒嗛厤鐨勫唴瀛樺ぇ灏廬) { + // 鎴愬姛 + } else { + // 澶辫触 + } + + # ArrayBuffer.prototype.slice() + * slice鏂规硶,鍏佽灏嗗唴瀛樺尯鍩熺殑涓閮ㄥ垎,鎷疯礉鐢熸垚涓涓柊鐨凙rrayBuffer瀵硅薄 + const buffer = new ArrayBuffer(8); + const newBuffer = buffer.slice(0, 3); + + * 鎷疯礉buffer瀵硅薄鐨勫墠 3 涓瓧鑺(浠 0 寮濮,鍒扮 3 涓瓧鑺傚墠闈㈢粨鏉),鐢熸垚涓涓柊鐨凙rrayBuffer瀵硅薄 + * slice鏂规硶鍏跺疄鍖呭惈涓ゆ,绗竴姝ユ槸鍏堝垎閰嶄竴娈垫柊鍐呭瓨,绗簩姝ユ槸灏嗗師鏉ラ偅涓狝rrayBuffer瀵硅薄鎷疯礉杩囧幓 + + * slice鏂规硶鎺ュ彈涓や釜鍙傛暟,绗竴涓弬鏁拌〃绀烘嫹璐濆紑濮嬬殑瀛楄妭搴忓彿(鍚瀛楄妭),绗簩涓弬鏁拌〃绀烘嫹璐濇埅姝㈢殑瀛楄妭搴忓彿(涓嶅惈璇ュ瓧鑺) + * 濡傛灉鐪佺暐绗簩涓弬鏁,鍒欓粯璁ゅ埌鍘烝rrayBuffer瀵硅薄鐨勭粨灏 + * slice鏂规硶,ArrayBuffer瀵硅薄涓嶆彁渚涗换浣曠洿鎺ヨ鍐欏唴瀛樼殑鏂规硶,鍙厑璁稿湪鍏朵笂鏂瑰缓绔嬭鍥,鐒跺悗閫氳繃瑙嗗浘璇诲啓 + + # ArrayBuffer.isView() + * 闈欐佹柟娉昳sView,杩斿洖涓涓竷灏斿,琛ㄧず鍙傛暟鏄惁涓篈rrayBuffer鐨勮鍥惧疄渚 + * 杩欎釜鏂规硶澶ц嚧鐩稿綋浜庡垽鏂弬鏁,鏄惁涓 TypedArray 瀹炰緥鎴朌ataView瀹炰緥 + const buffer = new ArrayBuffer(8); + ArrayBuffer.isView(buffer) // false + + const v = new Int32Array(buffer); + ArrayBuffer.isView(v) // true + +-------------------------------- +TypedArray 瑙嗗浘 | +-------------------------------- + # TypedArray 瑙嗗浘涓鍏卞寘鎷 9 绉嶇被鍨,姣忎竴绉嶈鍥鹃兘鏄竴绉嶆瀯閫犲嚱鏁 + Int8Array 8 浣嶆湁绗﹀彿鏁存暟,闀垮害 1 涓瓧鑺 + Uint8Array 8 浣嶆棤绗﹀彿鏁存暟,闀垮害 1 涓瓧鑺 + Uint8ClampedArray 8 浣嶆棤绗﹀彿鏁存暟,闀垮害 1 涓瓧鑺,婧㈠嚭澶勭悊涓嶅悓 + Int16Array 16 浣嶆湁绗﹀彿鏁存暟,闀垮害 2 涓瓧鑺 + Uint16Array 16 浣嶆棤绗﹀彿鏁存暟,闀垮害 2 涓瓧鑺 + Int32Array 32 浣嶆湁绗﹀彿鏁存暟,闀垮害 4 涓瓧鑺 + Uint32Array 32 浣嶆棤绗﹀彿鏁存暟,闀垮害 4 涓瓧鑺 + Float32Array 32 浣嶆诞鐐规暟,闀垮害 4 涓瓧鑺 + Float64Array 64 浣嶆诞鐐规暟,闀垮害 8 涓瓧鑺 + + # 9 涓瀯閫犲嚱鏁扮敓鎴愮殑鏁扮粍,缁熺О涓 TypedArray 瑙嗗浘 + * 瀹冧滑寰堝儚鏅氭暟缁,閮芥湁length灞炴,閮借兘鐢ㄦ柟鎷彿杩愮畻绗([])鑾峰彇鍗曚釜鍏冪礌 + * 鎵鏈夋暟缁勭殑鏂规硶,鍦ㄥ畠浠笂闈㈤兘鑳戒娇鐢 + * 鏅氭暟缁勪笌 TypedArray 鏁扮粍鐨勫樊寮備富瑕佸湪浠ヤ笅鏂归潰 + TypedArray 鏁扮粍鐨勬墍鏈夋垚鍛,閮芥槸鍚屼竴绉嶇被鍨 + TypedArray 鏁扮粍鐨勬垚鍛樻槸杩炵画鐨,涓嶄細鏈夌┖浣 + TypedArray 鏁扮粍鎴愬憳鐨勯粯璁ゅ间负 0 + * 姣斿,new Array(10)杩斿洖涓涓櫘閫氭暟缁,閲岄潰娌℃湁浠讳綍鎴愬憳,鍙槸 10 涓┖浣 + * new Uint8Array(10)杩斿洖涓涓 TypedArray 鏁扮粍,閲岄潰 10 涓垚鍛橀兘鏄 0 + TypedArray 鏁扮粍鍙槸涓灞傝鍥,鏈韩涓嶅偍瀛樻暟鎹,瀹冪殑鏁版嵁閮藉偍瀛樺湪搴曞眰鐨凙rrayBuffer瀵硅薄涔嬩腑 + * 瑕佽幏鍙栧簳灞傚璞″繀椤讳娇鐢╞uffer灞炴 + + # 鏋勯犲嚱鏁 + * TypedArray 鏁扮粍鎻愪緵 9 绉嶆瀯閫犲嚱鏁,鐢ㄦ潵鐢熸垚鐩稿簲绫诲瀷鐨勬暟缁勫疄渚 + * 鏋勯犲嚱鏁版湁澶氱鐢ㄦ硶 + 1,TypedArray(buffer, byteOffset=0, length) + * 鍚屼竴涓狝rrayBuffer瀵硅薄涔嬩笂,鍙互鏍规嵁涓嶅悓鐨勬暟鎹被鍨,寤虹珛澶氫釜瑙嗗浘 + // 鍒涘缓涓涓8瀛楄妭鐨凙rrayBuffer + const b = new ArrayBuffer(8); + // 鍒涘缓涓涓寚鍚慴鐨処nt32瑙嗗浘,寮濮嬩簬瀛楄妭0,鐩村埌缂撳啿鍖虹殑鏈熬 + const v1 = new Int32Array(b); + // 鍒涘缓涓涓寚鍚慴鐨刄int8瑙嗗浘,寮濮嬩簬瀛楄妭2,鐩村埌缂撳啿鍖虹殑鏈熬 + const v2 = new Uint8Array(b, 2); + // 鍒涘缓涓涓寚鍚慴鐨処nt16瑙嗗浘,寮濮嬩簬瀛楄妭2,闀垮害涓2 + const v3 = new Int16Array(b, 2, 2); + + * 鍦ㄤ竴娈甸暱搴︿负 8 涓瓧鑺傜殑鍐呭瓨(b)涔嬩笂,鐢熸垚浜嗕笁涓鍥:v1銆乿2鍜寁3 + * 瑙嗗浘鐨勬瀯閫犲嚱鏁板彲浠ユ帴鍙椾笁涓弬鏁 + 绗竴涓弬鏁(蹇呴渶):瑙嗗浘瀵瑰簲鐨勫簳灞侫rrayBuffer瀵硅薄 + 绗簩涓弬鏁(鍙):瑙嗗浘寮濮嬬殑瀛楄妭搴忓彿,榛樿浠 0 寮濮 + 绗笁涓弬鏁(鍙):瑙嗗浘鍖呭惈鐨勬暟鎹釜鏁,榛樿鐩村埌鏈鍐呭瓨鍖哄煙缁撴潫 + + * byteOffset蹇呴』涓庢墍瑕佸缓绔嬬殑鏁版嵁绫诲瀷涓鑷,鍚﹀垯浼氭姤閿 + const buffer = new ArrayBuffer(8); + const i16 = new Int16Array(buffer, 1); //Uncaught RangeError: start offset of Int16Array should be a multiple of 2 + * 甯︾鍙风殑 16 浣嶆暣鏁伴渶瑕佷袱涓瓧鑺,鎵浠yteOffset鍙傛暟蹇呴』鑳藉琚 2 鏁撮櫎 + + * 濡傛灉鎯充粠浠绘剰瀛楄妭寮濮嬭В璇籄rrayBuffer瀵硅薄,蹇呴』浣跨敤DataView瑙嗗浘,鍥犱负 TypedArray 瑙嗗浘鍙彁渚 9 绉嶅浐瀹氱殑瑙h鏍煎紡 + + 2,TypedArray(length) + * 涓嶉氳繃ArrayBuffer瀵硅薄,鐩存帴鍒嗛厤鍐呭瓨鑰岀敓鎴 + const f64a = new Float64Array(8); + f64a[0] = 10; + f64a[1] = 20; + f64a[2] = f64a[0] + f64a[1]; + + * 鐢熸垚涓涓 8 涓垚鍛樼殑Float64Array鏁扮粍(鍏 64 瀛楄妭),鐒跺悗渚濇瀵规瘡涓垚鍛樿祴鍊 + * 杩欐椂,瑙嗗浘鏋勯犲嚱鏁扮殑鍙傛暟灏辨槸鎴愬憳鐨勪釜鏁,鍙互鐪嬪埌,瑙嗗浘鏁扮粍鐨勮祴鍊兼搷浣滀笌鏅氭暟缁勭殑鎿嶄綔姣棤涓ゆ牱 + * 瑙嗗浘鏁扮粍鐨勮祴鍊兼搷浣滀笌鏅氭暟缁勭殑鎿嶄綔姣棤涓ゆ牱 + + 3,TypedArray(typedArray) + * 鍙互鎺ュ彈鍙︿竴涓 TypedArray 瀹炰緥浣滀负鍙傛暟 + const typedArray = new Int8Array(new Uint8Array(4)); + * Int8Array鏋勯犲嚱鏁版帴鍙椾竴涓猆int8Array瀹炰緥浣滀负鍙傛暟 + + * 鐢熸垚鐨勬柊鏁扮粍,鍙槸澶嶅埗浜嗗弬鏁版暟缁勭殑鍊,瀵瑰簲鐨勫簳灞傚唴瀛樻槸涓嶄竴鏍风殑 + * 鏂版暟缁勪細寮杈熶竴娈垫柊鐨勫唴瀛樺偍瀛樻暟鎹,涓嶄細鍦ㄥ師鏁扮粍鐨勫唴瀛樹箣涓婂缓绔嬭鍥 + const x = new Int8Array([1, 1]); + const y = new Int8Array(x); + x[0] // 1 + y[0] // 1 + + x[0] = 2; + y[0] // 1 + //鏁扮粍y鏄互鏁扮粍x涓烘ā鏉胯岀敓鎴愮殑,褰搙鍙樺姩鐨勬椂鍊,y骞舵病鏈夊彉鍔 + + * 鏋滄兂鍩轰簬鍚屼竴娈靛唴瀛,鏋勯犱笉鍚岀殑瑙嗗浘,鍙互閲囩敤涓嬮潰鐨勫啓娉 + const x = new Int8Array([1, 1]); + const y = new Int8Array(x.buffer); //鏍规嵁搴曞眰鐨刡uffer + x[0] // 1 + y[0] // 1 + + x[0] = 2; + y[0] // 2 + + 4,TypedArray(arrayLikeObject) + * 鏋勯犲嚱鏁扮殑鍙傛暟涔熷彲浠ユ槸涓涓櫘閫氭暟缁,鐒跺悗鐩存帴鐢熸垚 TypedArray 瀹炰緥 + * 娉ㄦ剰,杩欐椂 TypedArray 瑙嗗浘浼氶噸鏂板紑杈熷唴瀛,涓嶄細鍦ㄥ師鏁扮粍鐨勫唴瀛樹笂寤虹珛瑙嗗浘 + const typedArray = new Uint8Array([1, 2, 3, 4]); + * 涓婇潰浠g爜浠庝竴涓櫘閫氱殑鏁扮粍,鐢熸垚涓涓 8 浣嶆棤绗﹀彿鏁存暟鐨 TypedArray 瀹炰緥 + + * TypedArray 鏁扮粍涔熷彲浠ヨ浆鎹㈠洖鏅氭暟缁 + const normalArray = [...typedArray]; + // or + const normalArray = Array.from(typedArray); + // or + const normalArray = Array.prototype.slice.call(typedArray); + + # 鏅氭暟缁勭殑鎿嶄綔鏂规硶鍜屽睘鎬,瀵 TypedArray 鏁扮粍瀹屽叏閫傜敤 + * 娉ㄦ剰,TypedArray 鏁扮粍娌℃湁concat鏂规硶 + * 濡傛灉鎯宠鍚堝苟澶氫釜 TypedArray 鏁扮粍,鍙互鐢ㄤ笅闈㈣繖涓嚱鏁 + function concatenate(resultConstructor, ...arrays) { + let totalLength = 0; + for (let arr of arrays) { + totalLength += arr.length; + } + let result = new resultConstructor(totalLength); + let offset = 0; + for (let arr of arrays) { + result.set(arr, offset); + offset += arr.length; + } + return result; + } + + concatenate(Uint8Array, Uint8Array.of(1, 2), Uint8Array.of(3, 4)) + // Uint8Array [1, 2, 3, 4] + + # TypedArray 鏁扮粍涓庢櫘閫氭暟缁勪竴鏍,閮ㄧ讲浜 Iterator 鎺ュ彛,鎵浠ュ彲浠ヨ閬嶅巻 + let ui8 = Uint8Array.of(0, 1, 2); + for (let byte of ui8) { + console.log(byte); + } + // 0 + // 1 + // 2 + + # 瀛楄妭搴 + * 鐪嬩笉鎳 + + # BYTES_PER_ELEMENT 灞炴 + * 姣忎竴绉嶈鍥剧殑鏋勯犲嚱鏁,閮芥湁涓涓狟YTES_PER_ELEMENT灞炴,琛ㄧず杩欑鏁版嵁绫诲瀷鍗犳嵁鐨勫瓧鑺傛暟 + + Int8Array.BYTES_PER_ELEMENT // 1 + Uint8Array.BYTES_PER_ELEMENT // 1 + Int16Array.BYTES_PER_ELEMENT // 2 + Uint16Array.BYTES_PER_ELEMENT // 2 + Int32Array.BYTES_PER_ELEMENT // 4 + Uint32Array.BYTES_PER_ELEMENT // 4 + Float32Array.BYTES_PER_ELEMENT // 4 + Float64Array.BYTES_PER_ELEMENT // 8 + + * 杩欎釜灞炴у湪 TypedArray 瀹炰緥涓婁篃鑳借幏鍙,鍗虫湁TypedArray.prototype.BYTES_PER_ELEMENT + + # ArrayBuffer 涓庡瓧绗︿覆鐨勪簰鐩歌浆鎹 + * ArrayBuffer杞负瀛楃涓,鎴栬呭瓧绗︿覆杞负ArrayBuffer + * 鏈変竴涓墠鎻,鍗冲瓧绗︿覆鐨勭紪鐮佹柟娉曟槸纭畾鐨,鍋囧畾瀛楃涓查噰鐢 UTF-16 缂栫爜(JavaScript 鐨勫唴閮ㄧ紪鐮佹柟寮),鍙互鑷繁缂栧啓杞崲鍑芥暟 + // ArrayBuffer 杞负瀛楃涓,鍙傛暟涓 ArrayBuffer 瀵硅薄 + function ab2str(buf) { + // 娉ㄦ剰,濡傛灉鏄ぇ鍨嬩簩杩涘埗鏁扮粍,涓轰簡閬垮厤婧㈠嚭, + // 蹇呴』涓涓竴涓瓧绗﹀湴杞 + if (buf && buf.byteLength < 1024) { + return String.fromCharCode.apply(null, new Uint16Array(buf)); + } + + const bufView = new Uint16Array(buf); + const len = bufView.length; + const bstr = new Array(len); + for (let i = 0; i < len; i++) { + bstr[i] = String.fromCharCode.call(null, bufView[i]); + } + return bstr.join(''); + } + + // 瀛楃涓茶浆涓 ArrayBuffer 瀵硅薄,鍙傛暟涓哄瓧绗︿覆 + function str2ab(str) { + const buf = new ArrayBuffer(str.length * 2); // 姣忎釜瀛楃鍗犵敤2涓瓧鑺 + const bufView = new Uint16Array(buf); + for (let i = 0, strLen = str.length; i < strLen; i++) { + bufView[i] = str.charCodeAt(i); + } + return buf; + } + + # 婧㈠嚭 + * 鐪嬩笉鎳 + + # TypedArray.prototype.buffer + * TypedArray 瀹炰緥鐨刡uffer灞炴,杩斿洖鏁存鍐呭瓨鍖哄煙瀵瑰簲鐨凙rrayBuffer瀵硅薄 + * 璇ュ睘鎬т负鍙灞炴 + + const a = new Float32Array(64); + const b = new Uint8Array(a.buffer); + + * 涓婇潰浠g爜鐨刟瑙嗗浘瀵硅薄鍜宐瑙嗗浘瀵硅薄,瀵瑰簲鍚屼竴涓狝rrayBuffer瀵硅薄,鍗冲悓涓娈靛唴瀛 + + # TypedArray.prototype.byteLength + # TypedArray.prototype.byteOffset + * byteLength灞炴ц繑鍥 TypedArray 鏁扮粍鍗犳嵁鐨勫唴瀛橀暱搴,鍗曚綅涓哄瓧鑺 + * byteOffset灞炴ц繑鍥 TypedArray 鏁扮粍浠庡簳灞侫rrayBuffer瀵硅薄鐨勫摢涓瓧鑺傚紑濮 + * 杩欎袱涓睘鎬ч兘鏄彧璇诲睘鎬 + + const b = new ArrayBuffer(8); + + const v1 = new Int32Array(b); + const v2 = new Uint8Array(b, 2); + const v3 = new Int16Array(b, 2, 2); + + v1.byteLength // 8 + v2.byteLength // 6 + v3.byteLength // 4 + + v1.byteOffset // 0 + v2.byteOffset // 2 + v3.byteOffset // 2 + + # TypedArray.prototype.length + * length灞炴ц〃绀 TypedArray 鏁扮粍鍚湁澶氬皯涓垚鍛 + * 娉ㄦ剰灏哹yteLength灞炴у拰length灞炴у尯鍒,鍓嶈呮槸瀛楄妭闀垮害,鍚庤呮槸鎴愬憳闀垮害 + const a = new Int16Array(8); + a.length // 8 + a.byteLength // 16 + + # TypedArray.prototype.set() + * TypedArray 鏁扮粍鐨剆et鏂规硶鐢ㄤ簬澶嶅埗鏁扮粍(鏅氭暟缁勬垨 TypedArray 鏁扮粍),涔熷氨鏄皢涓娈靛唴瀹瑰畬鍏ㄥ鍒跺埌鍙︿竴娈靛唴瀛 + const a = new Uint8Array(8); + const b = new Uint8Array(8); + b.set(a); + + * 涓婇潰浠g爜澶嶅埗a鏁扮粍鐨勫唴瀹瑰埌b鏁扮粍,瀹冩槸鏁存鍐呭瓨鐨勫鍒,姣斾竴涓釜鎷疯礉鎴愬憳鐨勯偅绉嶅鍒跺揩寰楀 + + * set鏂规硶杩樺彲浠ユ帴鍙楃浜屼釜鍙傛暟,琛ㄧず浠巄瀵硅薄鐨勫摢涓涓垚鍛樺紑濮嬪鍒禷瀵硅薄 + const a = new Uint16Array(8); + const b = new Uint16Array(10); + b.set(a, 2) + + * 涓婇潰浠g爜鐨刡鏁扮粍姣攁鏁扮粍澶氫袱涓垚鍛,鎵浠ヤ粠b[2]寮濮嬪鍒 + # TypedArray.prototype.subarray() + * subarray鏂规硶鏄浜 TypedArray 鏁扮粍鐨勪竴閮ㄥ垎,鍐嶅缓绔嬩竴涓柊鐨勮鍥 + const a = new Uint16Array(8); + const b = a.subarray(2,3); + + a.byteLength // 16 + b.byteLength // 2 + + * subarray鏂规硶鐨勭涓涓弬鏁版槸璧峰鐨勬垚鍛樺簭鍙,绗簩涓弬鏁版槸缁撴潫鐨勬垚鍛樺簭鍙(涓嶅惈璇ユ垚鍛),濡傛灉鐪佺暐鍒欏寘鍚墿浣欑殑鍏ㄩ儴鎴愬憳 + * 涓婇潰浠g爜鐨刟.subarray(2,3),鎰忓懗鐫 b 鍙寘鍚玜[2]涓涓垚鍛,瀛楄妭闀垮害涓 2 + + # TypedArray.prototype.slice() + * TypeArray 瀹炰緥鐨剆lice鏂规硶,鍙互杩斿洖涓涓寚瀹氫綅缃殑鏂扮殑 TypedArray 瀹炰緥 + let ui8 = Uint8Array.of(0, 1, 2); + ui8.slice(-1) + // Uint8Array [ 2 ] + + * ui8鏄 8 浣嶆棤绗﹀彿鏁存暟鏁扮粍瑙嗗浘鐨勪竴涓疄渚 + * 瀹冪殑slice鏂规硶鍙互浠庡綋鍓嶈鍥句箣涓,杩斿洖涓涓柊鐨勮鍥惧疄渚 + + * slice鏂规硶鐨勫弬鏁,琛ㄧず鍘熸暟缁勭殑鍏蜂綋浣嶇疆,寮濮嬬敓鎴愭柊鏁扮粍 + * 璐熷艰〃绀洪嗗悜鐨勪綅缃,鍗-1 涓哄掓暟绗竴涓綅缃,-2 琛ㄧず鍊掓暟绗簩涓綅缃,浠ユ绫绘帹 + + # TypedArray.of() + * TypedArray 鏁扮粍鐨勬墍鏈夋瀯閫犲嚱鏁,閮芥湁涓涓潤鎬佹柟娉昽f,鐢ㄤ簬灏嗗弬鏁拌浆涓轰竴涓 TypedArray 瀹炰緥 + Float32Array.of(0.151, -8, 3.7) + //Float32Array [ 0.151, -8, 3.7 ] + + * 涓嬮潰涓夌鏂规硶閮戒細鐢熸垚鍚屾牱涓涓 TypedArray 鏁扮粍 + // 鏂规硶涓 + let tarr = new Uint8Array([1,2,3]); + // 鏂规硶浜 + let tarr = Uint8Array.of(1,2,3); + // 鏂规硶涓 + let tarr = new Uint8Array(3); + tarr[0] = 1; + tarr[1] = 2; + tarr[2] = 3; + + # TypedArray.from() + * 闈欐佹柟娉昮rom鎺ュ彈涓涓彲閬嶅巻鐨勬暟鎹粨鏋(姣斿鏁扮粍)浣滀负鍙傛暟,杩斿洖涓涓熀浜庤繖涓粨鏋勭殑 TypedArray 瀹炰緥 + Uint16Array.from([0, 1, 2]) + // Uint16Array [ 0, 1, 2 ] + + * 杩欎釜鏂规硶杩樺彲浠ュ皢涓绉 TypedArray 瀹炰緥,杞负鍙︿竴绉 + const ui16 = Uint16Array.from(Uint8Array.of(0, 1, 2)); + ui16 instanceof Uint16Array // true + + * from鏂规硶杩樺彲浠ユ帴鍙椾竴涓嚱鏁,浣滀负绗簩涓弬鏁,鐢ㄦ潵瀵规瘡涓厓绱犺繘琛岄亶鍘,鍔熻兘绫讳技map鏂规硶 + Int8Array.of(127, 126, 125).map(x => 2 * x) + // Int8Array [ -2, -4, -6 ] + + Int16Array.from(Int8Array.of(127, 126, 125), x => 2 * x) + // Int16Array [ 254, 252, 250 ] + * from鏂规硶娌℃湁鍙戠敓婧㈠嚭,杩欒鏄庨亶鍘嗕笉鏄拡瀵瑰師鏉ョ殑 8 浣嶆暣鏁版暟缁 + * 涔熷氨鏄,from浼氬皢绗竴涓弬鏁版寚瀹氱殑 TypedArray 鏁扮粍,鎷疯礉鍒板彟涓娈靛唴瀛樹箣涓,澶勭悊涔嬪悗鍐嶅皢缁撴灉杞垚鎸囧畾鐨勬暟缁勬牸寮 + + +-------------------------------- +澶嶅悎瑙嗗浘 | +-------------------------------- + \ No newline at end of file diff --git "a/JavaSript/ES6/es6-Class\345\237\272\346\234\254\350\257\255\346\263\225.js" "b/JavaSript/ES6/es6-Class\345\237\272\346\234\254\350\257\255\346\263\225.js" new file mode 100644 index 00000000..5f851369 --- /dev/null +++ "b/JavaSript/ES6/es6-Class\345\237\272\346\234\254\350\257\255\346\263\225.js" @@ -0,0 +1,651 @@ +-------------------------------- +class 鍩烘湰璇硶 | +-------------------------------- + 1,绠浠 + 2,涓ユ牸妯″紡 + 3,constructor 鏂规硶 + 4,绫荤殑瀹炰緥瀵硅薄 + 5,Class 琛ㄨ揪寮 + 6,涓嶅瓨鍦ㄥ彉閲忔彁鍗 + 7,绉佹湁鏂规硶鍜岀鏈夊睘鎬 + 8,this 鐨勬寚鍚 + 9,name 灞炴 + 10,Class 鐨勫彇鍊煎嚱鏁帮紙getter锛夊拰瀛樺煎嚱鏁帮紙setter锛 + 11,Class 鐨 Generator 鏂规硶 + 12,Class 鐨勯潤鎬佹柟娉 + 13,Class 鐨勯潤鎬佸睘鎬у拰瀹炰緥灞炴 + 14,new.target 灞炴 + +-------------------------------- +class 绠浠 | +-------------------------------- + # 鍩烘湰涓,ES6 鐨刢lass鍙互鐪嬩綔鍙槸涓涓娉曠硸,瀹冪殑缁濆ぇ閮ㄥ垎鍔熻兘,ES5 閮藉彲浠ュ仛鍒 + * 鏂扮殑class鍐欐硶鍙槸璁╁璞″師鍨嬬殑鍐欐硶鏇村姞娓呮櫚,鏇村儚闈㈠悜瀵硅薄缂栫▼鐨勮娉曡屽凡 + + function Point(x, y) { + this.x = x; + this.y = y; + } + + Point.prototype.toString = function () { + return '(' + this.x + ', ' + this.y + ')'; + }; + + var p = new Point(1, 2); + + //瀹氫箟绫 + class Point { + constructor(x, y) { + this.x = x; + this.y = y; + } + + toString() { + return '(' + this.x + ', ' + this.y + ')'; + } + } + * constructor鏂规硶,杩欏氨鏄瀯閫犳柟娉,鑰宼his鍏抽敭瀛楀垯浠h〃瀹炰緥瀵硅薄 + * 瀹氫箟"绫"鐨勬柟娉曠殑鏃跺,鍓嶉潰涓嶉渶瑕佸姞涓奻unction杩欎釜鍏抽敭瀛,鐩存帴鎶婂嚱鏁板畾涔夋斁杩涘幓浜嗗氨鍙互浜 + * 鏂规硶涔嬮棿涓嶉渶瑕侀楀彿鍒嗛殧,鍔犱簡浼氭姤閿 + * 绫荤殑鏁版嵁绫诲瀷灏辨槸鍑芥暟,绫绘湰韬氨鎸囧悜鏋勯犲嚱鏁 + class Point { + // ... + } + + typeof Point // "function" + Point === Point.prototype.constructor // true + + # 鏋勯犲嚱鏁扮殑prototype灞炴,鍦 ES6 鐨"绫"涓婇潰缁х画瀛樺湪 + * '浜嬪疄涓,绫荤殑鎵鏈夋柟娉曢兘瀹氫箟鍦ㄧ被鐨刾rototype灞炴т笂闈' + class Point { + constructor() { + // ... + } + toString() { + // ... + } + toValue() { + // ... + } + } + + // 绛夊悓浜 + + Point.prototype = { + constructor() {}, + toString() {}, + toValue() {}, + }; + * '鍦ㄧ被鐨勫疄渚嬩笂闈㈣皟鐢ㄦ柟娉,鍏跺疄灏辨槸璋冪敤鍘熷瀷涓婄殑鏂规硶' + class B {} + let b = new B(); + + b.constructor === B.prototype.constructor // true b鏄疊绫荤殑瀹炰緥,瀹冪殑constructor鏂规硶灏辨槸B绫诲師鍨嬬殑constructor鏂规硶 + + * 绫荤殑鏂规硶閮藉畾涔夊湪prototype瀵硅薄涓婇潰,鎵浠ョ被鐨勬柊鏂规硶鍙互娣诲姞鍦╬rototype瀵硅薄涓婇潰,Object.assign鏂规硶鍙互寰堟柟渚垮湴涓娆″悜绫绘坊鍔犲涓柟娉 + class Point { + constructor(){ + // ... + } + } + + Object.assign(Point.prototype, { + toString(){}, + toValue(){} + }); + * prototype瀵硅薄鐨刢onstructor灞炴,鐩存帴鎸囧悜"绫"鐨勬湰韬,杩欎笌 ES5 鐨勮涓烘槸涓鑷寸殑 + Point.prototype.constructor === Point // true + + * '绫荤殑鍐呴儴鎵鏈夊畾涔夌殑鏂规硶,閮芥槸涓嶅彲鏋氫妇鐨,杩欎竴鐐逛笌 ES5 鐨勮涓轰笉涓鑷' + * 绫荤殑灞炴у悕,鍙互閲囩敤琛ㄨ揪寮 + let methodName = 'getArea'; + class Square { + constructor(length) { + // ... + } + + [methodName]() { + // ... + } + } + +-------------------------------- +constructor 鏂规硶 | +-------------------------------- + # constructor鏂规硶鏄被鐨勯粯璁ゆ柟娉,閫氳繃new鍛戒护鐢熸垚瀵硅薄瀹炰緥鏃,鑷姩璋冪敤璇ユ柟娉 + # 涓涓被蹇呴』鏈塩onstructor鏂规硶,濡傛灉娌℃湁鏄惧紡瀹氫箟,涓涓┖鐨刢onstructor鏂规硶浼氳榛樿娣诲姞 + # onstructor鏂规硶榛樿杩斿洖瀹炰緥瀵硅薄(鍗硉his),瀹屽叏鍙互鎸囧畾杩斿洖鍙﹀涓涓璞 + class Foo { + constructor() { + return Object.create(null); + } + } + + new Foo() instanceof Foo + // false + + # 绫诲繀椤讳娇鐢╪ew璋冪敤,鍚﹀垯浼氭姤閿,杩欐槸瀹冭窡鏅氭瀯閫犲嚱鏁扮殑涓涓富瑕佸尯鍒,鍚庤呬笉鐢╪ew涔熷彲浠ユ墽琛 + +-------------------------------- +绫荤殑瀹炰緥瀵硅薄 | +-------------------------------- + # 瀹炰緥鐨勫睘鎬ч櫎闈炴樉寮忓畾涔夊湪鍏舵湰韬(鍗冲畾涔夊湪this瀵硅薄涓),鍚﹀垯閮芥槸瀹氫箟鍦ㄥ師鍨嬩笂(鍗冲畾涔夊湪class涓) + * '鍙涓嶆槸閫氳繃this鎸囦护璁剧疆鐨勫睘鎬,閮芥槸璁剧疆鍦ㄥ師鍨嬪璞′笂鐨' + //瀹氫箟绫 + class Point { + + constructor(x, y) { + this.x = x; + this.y = y; + } + + toString() { + return '(' + this.x + ', ' + this.y + ')'; + } + + } + + var point = new Point(2, 3); + + point.toString() // (2, 3) + + point.hasOwnProperty('x') // true + point.hasOwnProperty('y') // true + point.hasOwnProperty('toString') // false + point.__proto__.hasOwnProperty('toString') // true + + # 涓 ES5 涓鏍,绫荤殑鎵鏈夊疄渚嬪叡浜竴涓師鍨嬪璞 + var p1 = new Point(2,3); + var p2 = new Point(3,2); + + p1.__proto__ === p2.__proto__ + //true + + * 杩欎篃鎰忓懗鐫,鍙互閫氳繃瀹炰緥鐨刜_proto__灞炴т负"绫"娣诲姞鏂规硶 + +-------------------------------- +Class 琛ㄨ揪寮 | +-------------------------------- + # 涓庡嚱鏁颁竴鏍,绫讳篃鍙互浣跨敤琛ㄨ揪寮忕殑褰㈠紡瀹氫箟 + const MyClass = class Me { + getClassName() { + return Me.name; + } + }; + * 闇瑕佹敞鎰忕殑鏄,杩欎釜绫荤殑鍚嶅瓧鏄疢yClass鑰屼笉鏄疢e,Me鍙湪 Class 鐨勫唴閮ㄤ唬鐮佸彲鐢,鎸囦唬褰撳墠绫 + let inst = new MyClass(); + inst.getClassName() // Me + Me.name // ReferenceError: Me is not defined + + * 濡傛灉绫荤殑鍐呴儴娌$敤鍒扮殑璇,鍙互鐪佺暐Me,涔熷氨鏄彲浠ュ啓鎴愪笅闈㈢殑褰㈠紡 + const MyClass = class { /* ... */ }; + + # 閲囩敤 Class 琛ㄨ揪寮,鍙互鍐欏嚭绔嬪嵆鎵ц鐨 Class + let person = new class { + constructor(name) { + this.name = name; + } + + sayName() { + console.log(this.name); + } + }('寮犱笁'); + + person.sayName(); // "寮犱笁" + +-------------------------------- +涓嶅瓨鍦ㄥ彉閲忔彁鍗 | +-------------------------------- + # 绫讳笉瀛樺湪鍙橀噺鎻愬崌(hoist),杩欎竴鐐逛笌 ES5 瀹屽叏涓嶅悓 + new Foo(); // ReferenceError + class Foo {} + + * 杩欑瑙勫畾鐨勫師鍥犱笌缁ф壙鏈夊叧,蹇呴』淇濊瘉瀛愮被鍦ㄧ埗绫讳箣鍚庡畾涔 + +-------------------------------- +绉佹湁鏂规硶鍜岀鏈夊睘鎬 | +-------------------------------- + # 鐜版湁鐨勬柟娉 + * 绉佹湁鏂规硶鏄父瑙侀渶姹,浣 ES6 涓嶆彁渚,鍙兘閫氳繃鍙橀氭柟娉曟ā鎷熷疄鐜 + + 1,涓绉嶅仛娉曟槸鍦ㄥ懡鍚嶄笂鍔犱互鍖哄埆 + class Widget { + // 鍏湁鏂规硶 + foo (baz) { + this._bar(baz); + } + + // 绉佹湁鏂规硶 + _bar(baz) { + return this.snaf = baz; + } + // ... + } + * _bar鏂规硶鍓嶉潰鐨勪笅鍒掔嚎,琛ㄧず杩欐槸涓涓彧闄愪簬鍐呴儴浣跨敤鐨勭鏈夋柟娉 + * 浣嗘槸,杩欑鍛藉悕鏄笉淇濋櫓鐨,鍦ㄧ被鐨勫閮,杩樻槸鍙互璋冪敤鍒拌繖涓柟娉 + + 2,鍙︿竴绉嶆柟娉曞氨鏄储鎬у皢绉佹湁鏂规硶绉诲嚭妯″潡,鍥犱负妯″潡鍐呴儴鐨勬墍鏈夋柟娉曢兘鏄澶栧彲瑙佺殑 + class Widget { + foo (baz) { + bar.call(this, baz); + } + // ... + } + + function bar(baz) { + return this.snaf = baz; + } + * foo鏄叕鏈夋柟娉,鍐呴儴璋冪敤浜哹ar.call(this, baz) + * 杩欎娇寰梑ar瀹為檯涓婃垚涓轰簡褰撳墠妯″潡鐨勭鏈夋柟娉 + + 3,杩樻湁涓绉嶆柟娉曟槸鍒╃敤Symbol鍊肩殑鍞竴,灏嗙鏈夋柟娉曠殑鍚嶅瓧鍛藉悕涓轰竴涓猄ymbol鍊 + const bar = Symbol('bar'); + const snaf = Symbol('snaf'); + export default class myClass{ + // 鍏湁鏂规硶 + foo(baz) { + this[bar](baz); + } + // 绉佹湁鏂规硶 + [bar](baz) { + return this[snaf] = baz; + } + // ... + }; + * bar鍜宻naf閮芥槸Symbol鍊,瀵艰嚧绗笁鏂规棤娉曡幏鍙栧埌瀹冧滑,鍥犳杈惧埌浜嗙鏈夋柟娉曞拰绉佹湁灞炴х殑鏁堟灉 + + # 绉佹湁灞炴х殑鎻愭 + * 涓庣鏈夋柟娉曚竴鏍,ES6 涓嶆敮鎸佺鏈夊睘鎬 + * 鐩墠锛屾湁涓涓彁妗,涓篶lass鍔犱簡绉佹湁灞炴,鏂规硶鏄湪灞炴у悕涔嬪墠,浣跨敤#琛ㄧず + class Point { + #x; + constructor(x = 0) { + #x = +x; // 鍐欐垚 this.#x 浜﹀彲 + } + + get x() { return #x } + set x(value) { #x = +value } + } + * #x灏辫〃绀虹鏈夊睘鎬,鍦≒oint绫讳箣澶栨槸璇诲彇涓嶅埌杩欎釜灞炴х殑 + * 杩樺彲浠ョ湅鍒,绉佹湁灞炴т笌瀹炰緥鐨勫睘鎬ф槸鍙互鍚屽悕鐨(姣斿,#x涓巊et x()) + + * 涔嬫墍浠ヨ寮曞叆涓涓柊鐨勫墠缂#琛ㄧず绉佹湁灞炴,鑰屾病鏈夐噰鐢╬rivate鍏抽敭瀛,鏄洜涓 JavaScript 鏄竴闂ㄥ姩鎬佽瑷,浣跨敤鐙珛鐨勭鍙蜂技涔庢槸鍞竴鐨勫彲闈犳柟娉 + * 鑳藉鍑嗙‘鍦板尯鍒嗕竴绉嶅睘鎬ф槸鍚︿负绉佹湁灞炴,鍙﹀,Ruby 璇█浣跨敤@琛ㄧず绉佹湁灞炴,ES6 娌℃湁鐢ㄨ繖涓鍙疯屼娇鐢#,鏄洜涓篅宸茬粡琚暀缁欎簡 Decorator + * 璇ユ彁妗堝彧瑙勫畾浜嗙鏈夊睘鎬х殑鍐欐硶,浣嗘槸,寰堣嚜鐒跺湴,瀹冧篃鍙互鐢ㄦ潵鍐欑鏈夋柟娉 + class Foo { + #a; + #b; + #sum() { return #a + #b; } + printSum() { console.log(#sum()); } + constructor(a, b) { #a = a; #b = b; } + } + * #sum()灏辨槸涓涓鏈夋柟娉 + + * 绉佹湁灞炴т篃鍙互璁剧疆 getter 鍜 setter 鏂规硶 + class Counter { + #xValue = 0; + + get #x() { return #xValue; } + set #x(value) { + this.#xValue = value; + } + + constructor() { + super(); + // ... + } + } + +-------------------------------- +this 鐨勬寚鍚 | +-------------------------------- + # 绫荤殑鏂规硶鍐呴儴濡傛灉鍚湁this,瀹冮粯璁ゆ寚鍚戠被鐨勫疄渚,浣嗘槸,蹇呴』闈炲父灏忓績,涓鏃﹀崟鐙娇鐢ㄨ鏂规硶,寰堝彲鑳芥姤閿 + class Logger { + printName(name = 'there') { + this.print(`Hello ${name}`); + } + + print(text) { + console.log(text); + } + } + //鍒涘缓logger瀵硅薄 + const logger = new Logger(); + //閫氳繃瑙f瀯璧嬪,鎶妉ogger鐨刾rintName鍑芥暟璧嬪肩粰鍙橀噺printName + const { printName } = logger; + //鎵ц璇ュ彉閲,寮傚父 + printName(); // TypeError: Cannot read property 'print' of undefined + + * printName鏂规硶涓殑this,榛樿鎸囧悜Logger绫荤殑瀹炰緥 + * 濡傛灉灏嗚繖涓柟娉曟彁鍙栧嚭鏉ュ崟鐙娇鐢,this浼氭寚鍚戣鏂规硶杩愯鏃舵墍鍦ㄧ殑鐜,鍥犱负鎵句笉鍒皃rint鏂规硶鑰屽鑷存姤閿 + + * 涓涓瘮杈冪畝鍗曠殑瑙e喅鏂规硶鏄,鍦ㄦ瀯閫犳柟娉曚腑缁戝畾this,杩欐牱灏变笉浼氭壘涓嶅埌print鏂规硶浜 + class Logger { + constructor() { + this.printName = this.printName.bind(this); + } + + // ... + } + + * 鍙︿竴绉嶈В鍐虫柟娉曟槸浣跨敤绠ご鍑芥暟(绠ご鍑芥暟涓殑this,鎸囧悜瀹氫箟鏃剁殑鐜,鑰屼笉鏄繍琛屾椂) + class Logger { + constructor() { + this.printName = (name = 'there') => { + this.print(`Hello ${name}`); + }; + } + // ... + } + + * 杩樻湁涓绉嶈В鍐虫柟娉曟槸浣跨敤Proxy,鑾峰彇鏂规硶鐨勬椂鍊,鑷姩缁戝畾this + function selfish (target) { + const cache = new WeakMap(); + const handler = { + get (target, key) { + const value = Reflect.get(target, key); + if (typeof value !== 'function') { + return value; + } + if (!cache.has(value)) { + cache.set(value, value.bind(target)); + } + return cache.get(value); + } + }; + const proxy = new Proxy(target, handler); + return proxy; + } + + const logger = selfish(new Logger()); + +-------------------------------- +name 灞炴 | +-------------------------------- + # 鐢变簬鏈川涓,ES6 鐨勭被鍙槸 ES5 鐨勬瀯閫犲嚱鏁扮殑涓灞傚寘瑁,鎵浠ュ嚱鏁扮殑璁稿鐗规ч兘琚獵lass缁ф壙,鍖呮嫭name灞炴 + class Point {} + Point.name // "Point" + + * name灞炴ф绘槸杩斿洖绱ц窡鍦╟lass鍏抽敭瀛楀悗闈㈢殑绫诲悕 + +-------------------------------- +鍙栧煎嚱鏁(getter)鍜屽瓨鍊煎嚱鏁(setter)| +-------------------------------- + # 涓 ES5 涓鏍凤紝鍦"绫"鐨勫唴閮ㄥ彲浠ヤ娇鐢╣et鍜宻et鍏抽敭瀛,瀵规煇涓睘鎬ц缃瓨鍊煎嚱鏁板拰鍙栧煎嚱鏁,鎷︽埅璇ュ睘鎬х殑瀛樺彇琛屼负 + class MyClass { + constructor() { + // ... + } + get prop() { + return 'getter'; + } + set prop(value) { + console.log('setter: '+value); + } + } + + let inst = new MyClass(); + + inst.prop = 123; + // setter: 123 + + inst.prop + // 'getter' + + # 瀛樺煎嚱鏁板拰鍙栧煎嚱鏁版槸璁剧疆鍦ㄥ睘鎬х殑 Descriptor 瀵硅薄涓婄殑 + class CustomHTMLElement { + constructor(element) { + this.element = element; + } + + get html() { + return this.element.innerHTML; + } + + set html(value) { + this.element.innerHTML = value; + } + } + + var descriptor = Object.getOwnPropertyDescriptor( + CustomHTMLElement.prototype, "html" + ); + + "get" in descriptor // true + "set" in descriptor // true + + * 鍊煎嚱鏁板拰鍙栧煎嚱鏁版槸瀹氫箟鍦╤tml灞炴х殑鎻忚堪瀵硅薄涓婇潰,杩欎笌 ES5 瀹屽叏涓鑷 + +-------------------------------- +Class 鐨 Generator 鏂规硶 | +-------------------------------- + # 濡傛灉鏌愪釜鏂规硶涔嬪墠鍔犱笂鏄熷彿(*),灏辫〃绀鸿鏂规硶鏄竴涓 Generator 鍑芥暟 + class Foo { + constructor(...args) { + this.args = args; + } + * [Symbol.iterator]() { + for (let arg of this.args) { + yield arg; + } + } + } + + for (let x of new Foo('hello', 'world')) { + console.log(x); + } + // hello + // world + + # Foo绫荤殑Symbol.iterator鏂规硶鍓嶆湁涓涓槦鍙,琛ㄧず璇ユ柟娉曟槸涓涓 Generator 鍑芥暟 + # Symbol.iterator鏂规硶杩斿洖涓涓狥oo绫荤殑榛樿閬嶅巻鍣,for...of寰幆浼氳嚜鍔ㄨ皟鐢ㄨ繖涓亶鍘嗗櫒 + + +-------------------------------- +Class 鐨勯潤鎬佹柟娉 | +-------------------------------- + # 绫荤浉褰撲簬瀹炰緥鐨勫師鍨,鎵鏈夊湪绫讳腑瀹氫箟鐨勬柟娉,閮戒細琚疄渚嬬户鎵 + # 濡傛灉鍦ㄤ竴涓柟娉曞墠,鍔犱笂static鍏抽敭瀛,灏辫〃绀鸿鏂规硶涓嶄細琚疄渚嬬户鎵,鑰屾槸鐩存帴閫氳繃绫绘潵璋冪敤,杩欏氨绉颁负"闈欐佹柟娉" + class Foo { + static classMethod() { + return 'hello'; + } + } + + Foo.classMethod() // 'hello' + + var foo = new Foo(); + foo.classMethod() + // TypeError: foo.classMethod is not a function + + # 濡傛灉闈欐佹柟娉曞寘鍚玹his鍏抽敭瀛,杩欎釜this鎸囩殑鏄被,鑰屼笉鏄疄渚 + class Foo { + static bar () { + this.baz(); + } + static baz () { + console.log('hello'); + } + baz () { + console.log('world'); + } + } + + Foo.bar() // hello + * 闈欐佹柟娉曞彲浠ヤ笌闈為潤鎬佹柟娉曢噸鍚 + + # 鐖剁被鐨勯潤鎬佹柟娉,鍙互琚瓙绫荤户鎵 + class Foo { + static classMethod() { + return 'hello'; + } + } + + class Bar extends Foo { + } + + Bar.classMethod() // 'hello' + + # 闈欐佹柟娉曚篃鏄彲浠ヤ粠super瀵硅薄涓婅皟鐢ㄧ殑 + class Foo { + static classMethod() { + return 'hello'; + } + } + + class Bar extends Foo { + static classMethod() { + return super.classMethod() + ', too'; + } + } + + Bar.classMethod() // "hello, too" + +-------------------------------- +Class 鐨勯潤鎬佸睘鎬у拰瀹炰緥灞炴 | +-------------------------------- + # 闈欐佸睘鎬ф寚鐨勬槸 Class 鏈韩鐨勫睘鎬,鍗矯lass.propName,鑰屼笉鏄畾涔夊湪瀹炰緥瀵硅薄(this)涓婄殑灞炴 + class Foo { + } + + Foo.prop = 1; + Foo.prop // 1 + + # 鐩墠,鍙湁杩欑鍐欐硶鍙,鍥犱负 ES6 鏄庣‘瑙勫畾,Class 鍐呴儴鍙湁闈欐佹柟娉,娌℃湁闈欐佸睘鎬 + # 鐩墠鏈変竴涓潤鎬佸睘鎬х殑鎻愭,瀵瑰疄渚嬪睘鎬у拰闈欐佸睘鎬ч兘瑙勫畾浜嗘柊鐨勫啓娉 + # 瀹炰緥鐨勭被鍨嬪睘鎬 + class MyClass { + myProp = 42; + + constructor() { + console.log(this.myProp); // 42 + } + } + * myProp灏辨槸MyClass鐨勫疄渚嬪睘鎬,鍦∕yClass鐨勫疄渚嬩笂,鍙互璇诲彇杩欎釜灞炴 + * 浠ュ墠,鎴戜滑瀹氫箟瀹炰緥灞炴,鍙兘鍐欏湪绫荤殑constructor鏂规硶閲岄潰 + class ReactCounter extends React.Component { + constructor(props) { + super(props); + this.state = { + count: 0 + }; + } + } + * 鏋勯犳柟娉昪onstructor閲岄潰,瀹氫箟浜唗his.state灞炴 + + * 鏈変簡鏂扮殑鍐欐硶浠ュ悗,鍙互涓嶅湪constructor鏂规硶閲岄潰瀹氫箟 + class ReactCounter extends React.Component { + state = { + count: 0 + }; + } + * 杩欑鍐欐硶姣斾互鍓嶆洿娓呮櫚 + + * 涓轰簡鍙鎬х殑鐩殑,瀵逛簬閭d簺鍦╟onstructor閲岄潰宸茬粡瀹氫箟鐨勫疄渚嬪睘鎬,鏂板啓娉曞厑璁哥洿鎺ュ垪鍑 + class ReactCounter extends React.Component { + state; + constructor(props) { + super(props); + this.state = { + count: 0 + }; + } + } + + # 绫荤殑闈欐佸睘鎬 + * 绫荤殑闈欐佸睘鎬у彧瑕佸湪涓婇潰鐨勫疄渚嬪睘鎬у啓娉曞墠闈,鍔犱笂static鍏抽敭瀛楀氨鍙互浜 + class MyClass { + static myStaticProp = 42; + + constructor() { + console.log(MyClass.myStaticProp); // 42 + } + } + * 鍚屾牱鐨,杩欎釜鏂板啓娉曞ぇ澶ф柟渚夸簡闈欐佸睘鎬х殑琛ㄨ揪 + // 鑰佸啓娉 + class Foo { + // ... + } + Foo.prop = 1; + + // 鏂板啓娉 + class Foo { + static prop = 1; + } + +-------------------------------- +new.target 灞炴 | +-------------------------------- + # new鏄粠鏋勯犲嚱鏁扮敓鎴愬疄渚嬪璞$殑鍛戒护 + # ES6 涓簄ew鍛戒护寮曞叆浜嗕竴涓猲ew.target灞炴,'璇ュ睘鎬т竴鑸敤鍦ㄦ瀯閫犲嚱鏁颁箣涓,杩斿洖new鍛戒护浣滅敤浜庣殑閭d釜鏋勯犲嚱鏁' + # 濡傛灉鏋勯犲嚱鏁颁笉鏄氳繃new鍛戒护璋冪敤鐨刵ew.target浼氳繑鍥瀠ndefined,鍥犳杩欎釜灞炴у彲浠ョ敤鏉ョ‘瀹氭瀯閫犲嚱鏁版槸鎬庝箞璋冪敤鐨 + function Person(name) { + if (new.target !== undefined) { + this.name = name; + } else { + throw new Error('蹇呴』浣跨敤 new 鍛戒护鐢熸垚瀹炰緥'); + } + } + + // 鍙︿竴绉嶅啓娉 + function Person(name) { + if (new.target === Person) { + this.name = name; + } else { + throw new Error('蹇呴』浣跨敤 new 鍛戒护鐢熸垚瀹炰緥'); + } + } + + var person = new Person('寮犱笁'); // 姝g‘ + var notAPerson = Person.call(person, '寮犱笁'); // 鎶ラ敊 + + * 涓婇潰浠g爜纭繚鏋勯犲嚱鏁板彧鑳介氳繃new鍛戒护璋冪敤 + + # Class 鍐呴儴璋冪敤new.target,杩斿洖褰撳墠 Class + class Rectangle { + constructor(length, width) { + console.log(new.target === Rectangle); + this.length = length; + this.width = width; + } + } + + var obj = new Rectangle(3, 4); // 杈撳嚭 true + + # 闇瑕佹敞鎰忕殑鏄,瀛愮被缁ф壙鐖剁被鏃,new.target浼氳繑鍥炲瓙绫 + class Rectangle { + constructor(length, width) { + console.log(new.target === Rectangle); //new.target浼氳繑鍥炲瓙绫 + // ... + } + } + + class Square extends Rectangle { + constructor(length) { + super(length, length); + } + } + + var obj = new Square(3); // 杈撳嚭 false + + * 鍒╃敤杩欎釜鐗圭偣,鍙互鍐欏嚭涓嶈兘鐙珛浣跨敤,蹇呴』缁ф壙鍚庢墠鑳戒娇鐢ㄧ殑绫 + class Shape { + constructor() { + if (new.target === Shape) { + throw new Error('鏈被涓嶈兘瀹炰緥鍖'); + } + } + } + + class Rectangle extends Shape { + constructor(length, width) { + super(); + // ... + } + } + + var x = new Shape(); // 鎶ラ敊 + + * Shape绫讳笉鑳借瀹炰緥鍖,鍙兘鐢ㄤ簬缁ф壙 + + # 娉ㄦ剰,鍦ㄥ嚱鏁板閮,浣跨敤new.target浼氭姤閿 diff --git "a/JavaSript/ES6/es6-Class\347\273\247\346\211\277.js" "b/JavaSript/ES6/es6-Class\347\273\247\346\211\277.js" new file mode 100644 index 00000000..2512e046 --- /dev/null +++ "b/JavaSript/ES6/es6-Class\347\273\247\346\211\277.js" @@ -0,0 +1,425 @@ +-------------------------------- +class 缁ф壙 | +-------------------------------- + 1,绠浠 + 2,Object.getPrototypeOf() + 3,super 鍏抽敭瀛 + 4,绫荤殑 prototype 灞炴у拰__proto__灞炴 + 5,鍘熺敓鏋勯犲嚱鏁扮殑缁ф壙 + 6,Mixin 妯″紡鐨勫疄鐜 + +-------------------------------- +class 绠浠 | +-------------------------------- + # Class 鍙互閫氳繃extends鍏抽敭瀛楀疄鐜扮户鎵,杩欐瘮 ES5 鐨勯氳繃淇敼鍘熷瀷閾惧疄鐜扮户鎵,瑕佹竻鏅板拰鏂逛究寰堝 + class Point { + } + class ColorPoint extends Point { + } + + # 瀛愮被蹇呴』鍦╟onstructor鏂规硶涓皟鐢╯uper鏂规硶,鍚﹀垯鏂板缓瀹炰緥鏃朵細鎶ラ敊 + * 杩欐槸鍥犱负瀛愮被娌℃湁鑷繁鐨則his瀵硅薄,鑰屾槸缁ф壙鐖剁被鐨則his瀵硅薄,鐒跺悗瀵瑰叾杩涜鍔犲伐 + * 濡傛灉涓嶈皟鐢╯uper鏂规硶,瀛愮被灏卞緱涓嶅埌this瀵硅薄 + class Point {} + class ColorPoint extends Point { + constructor() { + + } + } + let cp = new ColorPoint(); // ReferenceError + + # ES5 鐨勭户鎵,瀹炶川鏄厛鍒涢犲瓙绫荤殑瀹炰緥瀵硅薄this,鐒跺悗鍐嶅皢鐖剁被鐨勬柟娉曟坊鍔犲埌this涓婇潰(Parent.apply(this)) + * ES6 鐨勭户鎵挎満鍒跺畬鍏ㄤ笉鍚,瀹炶川鏄厛鍒涢犵埗绫荤殑瀹炰緥瀵硅薄this(鎵浠ュ繀椤诲厛璋冪敤super鏂规硶),鐒跺悗鍐嶇敤瀛愮被鐨勬瀯閫犲嚱鏁颁慨鏀箃his + * 濡傛灉瀛愮被娌℃湁瀹氫箟constructor鏂规硶,杩欎釜鏂规硶浼氳榛樿娣诲姞,浠g爜濡備笅 + class ColorPoint extends Point { + } + // 绛夊悓浜 + class ColorPoint extends Point { + constructor(...args) { + super(...args); + } + } + * 涓嶇鏈夋病鏈夋樉寮忓畾涔,浠讳綍涓涓瓙绫婚兘鏈塩onstructor鏂规硶 + + # 鍦ㄥ瓙绫荤殑鏋勯犲嚱鏁颁腑,鍙湁璋冪敤super涔嬪悗,鎵嶅彲浠ヤ娇鐢╰his鍏抽敭瀛,鍚﹀垯浼氭姤閿 + * 杩欐槸鍥犱负瀛愮被瀹炰緥鐨勬瀯寤,鏄熀浜庡鐖剁被瀹炰緥鍔犲伐,鍙湁super鏂规硶鎵嶈兘杩斿洖鐖剁被瀹炰緥 + class Parent{ + constructor(){ + + } + } + class Sub extends Parent{ + constructor(){ + this.name = 'KevinBlandy'; + super(); //super 蹇呴』鍦ㄤ娇鐢 this 涔嬪墠璋冪敤 + } + } + new Sub(); + //Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor + + # instanceof 杩愮畻 + class Parent{} + class Sub extends Parent{} + let sub = new Sub(); + console.log(sub instanceof Parent); //true + console.log(sub instanceof Sub); //true + + # 鐖剁被鐨勯潤鎬佹柟娉,涔熶細琚瓙绫荤户鎵 + +-------------------------------- +Object.getPrototypeOf() | +-------------------------------- + # Object.getPrototypeOf鏂规硶鍙互鐢ㄦ潵浠庡瓙绫讳笂鑾峰彇鐖剁被 + + Object.getPrototypeOf(ColorPoint) === Point + // true + * 鍥犳,鍙互浣跨敤杩欎釜鏂规硶鍒ゆ柇,涓涓被鏄惁缁ф壙浜嗗彟涓涓被 + + # Object.getPrototypeOf(),鑾峰彇鐨勬槸鎸囧畾瀵硅薄鐨勭洿鎺ョ埗绫 + class Parent{} + class Sub extends Parent{} + class Ssub extends Sub{} + + let ssub = Object.getPrototypeOf(new Ssub()); + console.log(ssub);//Sub聽{constructor: 茠} + +-------------------------------- +super 鍏抽敭瀛 | +-------------------------------- + # super杩欎釜鍏抽敭瀛,鏃㈠彲浠ュ綋浣滃嚱鏁颁娇鐢,涔熷彲浠ュ綋浣滃璞′娇鐢 + # 鍦ㄨ繖涓ょ鎯呭喌涓,瀹冪殑鐢ㄦ硶瀹屽叏涓嶅悓 + 1,绗竴绉嶆儏鍐,super浣滀负鍑芥暟璋冪敤鏃 + * 浠h〃鐖剁被鐨勬瀯閫犲嚱鏁,ES6 瑕佹眰,瀛愮被鐨勬瀯閫犲嚱鏁板繀椤绘墽琛屼竴娆uper鍑芥暟 + * 鏄繀椤荤殑,鍚﹀垯 JavaScript 寮曟搸浼氭姤閿 + * super铏界劧浠h〃浜嗙埗绫绘瀯閫犲嚱鏁,浣嗘槸杩斿洖鐨勬槸瀛愮被B鐨勫疄渚,鍗硈uper鍐呴儴鐨則his鎸囩殑鏄瓙绫 + * 鍥犳super()鍦ㄨ繖閲岀浉褰撲簬:鐖剁被.prototype.constructor.call(this) + class A { + constructor() { + console.log(new.target.name); + } + } + class B extends A { + constructor() { + super(); + } + } + new A() // A + new B() // B + + * 浣滀负鍑芥暟鏃,super()鍙兘鐢ㄥ湪瀛愮被鐨勬瀯閫犲嚱鏁颁箣涓,鐢ㄥ湪鍏朵粬鍦版柟灏变細鎶ラ敊 + class A {} + class B extends A { + m() { + super(); // 鎶ラ敊 + } + } + + 2,super浣滀负瀵硅薄 + * 鍦ㄦ櫘閫氭柟娉曚腑,鎸囧悜鐖剁被鐨勫師鍨嬪璞 + * 鍦ㄩ潤鎬佹柟娉曚腑,鎸囧悜鐖剁被 + * super鎸囧悜鐖剁被鐨勫師鍨嬪璞,鎵浠ュ畾涔夊湪鐖剁被瀹炰緥涓婄殑鏂规硶鎴栧睘鎬,鏄棤娉曢氳繃super璋冪敤鐨 + class A { + constructor() { + this.p = 2; + } + } + + class B extends A { + get m() { + return super.p; + } + } + + let b = new B(); + console.log(b.m); // undefined + + * 鍦ㄥ瓙绫绘櫘閫氭柟娉曚腑閫氳繃super璋冪敤鐖剁被鐨勬柟娉曟椂,鏂规硶鍐呴儴鐨則his鎸囧悜褰撳墠鐨勫瓙绫诲疄渚 + * this鎸囧悜瀛愮被瀹炰緥,鎵浠ュ鏋滈氳繃super瀵规煇涓睘鎬ц祴鍊,杩欐椂super灏辨槸this,璧嬪肩殑灞炴т細鍙樻垚瀛愮被瀹炰緥鐨勫睘鎬 + class Parent{ + } + class Sub extends Parent{ + foo(){ + //瀵箂uper璁剧疆灞炴,鍏跺疄灏辨槸瀵箃his璁剧疆灞炴 + super.name = '123456'; + } + } + + let sub = new Sub(); + sub.foo(); + console.log(sub.name); + + * 濡傛灉super浣滀负瀵硅薄,鐢ㄥ湪闈欐佹柟娉曚箣涓,杩欐椂super灏嗘寚鍚戠埗绫,鑰屼笉鏄埗绫荤殑鍘熷瀷瀵硅薄 + class Parent { + static myMethod(msg) { + console.log('static', msg); + } + myMethod(msg) { + console.log('instance', msg); + } + } + + class Child extends Parent { + static myMethod(msg) { + super.myMethod(msg); + } + myMethod(msg) { + super.myMethod(msg); + } + } + Child.myMethod(1); // static 1 鎵ц闈欐佺殑myMethod + let child = new Child(); + child.myMethod(2); // instance 2 鎵ц瀹炰緥鐨刴yMethod + + * super鍦ㄩ潤鎬佹柟娉曚箣涓寚鍚戠埗绫,鍦ㄦ櫘閫氭柟娉曚箣涓寚鍚戠埗绫荤殑鍘熷瀷瀵硅薄 + + * 鍦ㄥ瓙绫荤殑闈欐佹柟娉曚腑閫氳繃super璋冪敤鐖剁被鐨勬柟娉曟椂,鏂规硶鍐呴儴鐨則his鎸囧悜褰撳墠鐨勫瓙绫,鑰屼笉鏄瓙绫荤殑瀹炰緥 + * 浣跨敤super鐨勬椂鍊,蹇呴』鏄惧紡鎸囧畾鏄綔涓哄嚱鏁,杩樻槸浣滀负瀵硅薄浣跨敤,鍚﹀垯浼氭姤閿 + console.log(super); // 鎶ラ敊 + + # 鐢变簬瀵硅薄鎬绘槸缁ф壙鍏朵粬瀵硅薄鐨,鎵浠ュ彲浠ュ湪浠绘剰涓涓璞′腑,浣跨敤super鍏抽敭瀛 + var obj = { + toString() { + return "MyObject: " + super.toString(); + } + }; + + obj.toString(); // MyObject: [object Object] + +------------------------------------ +绫荤殑 prototype 灞炴у拰__proto__灞炴 | +------------------------------------ + # 澶у鏁版祻瑙堝櫒鐨 ES5 瀹炵幇涔嬩腑,姣忎竴涓璞¢兘鏈塤_proto__灞炴,鎸囧悜瀵瑰簲鐨勬瀯閫犲嚱鏁扮殑prototype灞炴 + # Class 浣滀负鏋勯犲嚱鏁扮殑璇硶绯,鍚屾椂鏈塸rototype灞炴у拰__proto__灞炴,鍥犳鍚屾椂瀛樺湪涓ゆ潯缁ф壙閾 + 1,瀛愮被鐨刜_proto__灞炴,琛ㄧず鏋勯犲嚱鏁扮殑缁ф壙锛屾绘槸鎸囧悜鐖剁被 + 2,瀛愮被prototype灞炴х殑__proto__灞炴,琛ㄧず鏂规硶鐨勭户鎵,鎬绘槸鎸囧悜鐖剁被鐨刾rototype灞炴 + class A {} + class B extends A {} + B.__proto__ === A // true 瀛愮被B鐨刜_proto__灞炴ф寚鍚戠埗绫籄 + B.prototype.__proto__ === A.prototype // true 瀛愮被B鐨刾rototype灞炴х殑__proto__灞炴ф寚鍚戠埗绫籄鐨刾rototype灞炴 + + # 绫荤殑缁ф壙鏄寜鐓т笅闈㈢殑妯″紡瀹炵幇鐨 + class A {} + class B {} + + // B 鐨勫疄渚嬬户鎵 A 鐨勫疄渚 + Object.setPrototypeOf(B.prototype, A.prototype); + // B 缁ф壙 A 鐨勯潤鎬佸睘鎬 + Object.setPrototypeOf(B, A); + const b = new B(); + + # 杩欎袱鏉$户鎵块摼,鍙互杩欐牱鐞嗚В + * 浣滀负涓涓璞,瀛愮被(B)鐨勫師鍨(__proto__灞炴)鏄埗绫(A) + * 浣滀负涓涓瀯閫犲嚱鏁,瀛愮被(B)鐨勫師鍨嬪璞(prototype灞炴)鏄埗绫荤殑鍘熷瀷瀵硅薄(prototype灞炴)鐨勫疄渚 + Object.create(A.prototype); + // 绛夊悓浜 + B.prototype.__proto__ = A.prototype; + + # extends 鐨勭户鎵跨洰鏍 + * extends鍏抽敭瀛楀悗闈㈠彲浠ヨ窡澶氱绫诲瀷鐨勫 + class B extends A { + } + + * 鍙鏄竴涓湁prototype灞炴х殑鍑芥暟,灏辫兘琚獴缁ф壙 + * 鐢变簬鍑芥暟閮芥湁prototype灞炴(闄や簡Function.prototype鍑芥暟),鍥犳A鍙互鏄换鎰忓嚱鏁 + + * 涓夌鐗规畩鎯呭喌 + 1,瀛愮被缁ф壙Object绫 + class A extends Object { + } + + A.__proto__ === Object // true + A.prototype.__proto__ === Object.prototype // true + + * 杩欑鎯呭喌涓,A鍏跺疄灏辨槸鏋勯犲嚱鏁癘bject鐨勫鍒,A鐨勫疄渚嬪氨鏄疧bject鐨勫疄渚 + + 2,涓嶅瓨鍦ㄤ换浣曠户鎵 + class A { + } + + A.__proto__ === Function.prototype // true + A.prototype.__proto__ === Object.prototype // true + + * A浣滀负涓涓熀绫(鍗充笉瀛樺湪浠讳綍缁ф壙),灏辨槸涓涓櫘閫氬嚱鏁,鎵浠ョ洿鎺ョ户鎵縁unction.prototype + * 浣嗘槸,A璋冪敤鍚庤繑鍥炰竴涓┖瀵硅薄(鍗砄bject瀹炰緥),鎵浠.prototype.__proto__鎸囧悜鏋勯犲嚱鏁(Object)鐨刾rototype灞炴 + + 3,瀛愮被缁ф壙null + class A extends null { + } + + A.__proto__ === Function.prototype // true + A.prototype.__proto__ === undefined // true + + * 杩欑鎯呭喌涓庣浜岀鎯呭喌闈炲父鍍 + * A涔熸槸涓涓櫘閫氬嚱鏁,鎵浠ョ洿鎺ョ户鎵縁unction.prototype + * 浣嗘槸,A璋冪敤鍚庤繑鍥炵殑瀵硅薄涓嶇户鎵夸换浣曟柟娉,鎵浠ュ畠鐨刜_proto__鎸囧悜Function.prototype,鍗冲疄璐ㄤ笂鎵ц浜嗕笅闈㈢殑浠g爜 + class C extends null { + constructor() { return Object.create(null); } + } + + # 瀹炰緥鐨 __proto__ 灞炴 + * 瀛愮被瀹炰緥鐨刜_proto__灞炴х殑__proto__灞炴,鎸囧悜鐖剁被瀹炰緥鐨刜_proto__灞炴 + * 涔熷氨鏄,瀛愮被鐨勫師鍨嬬殑鍘熷瀷,鏄埗绫荤殑鍘熷瀷 + var p1 = new Point(2, 3); + var p2 = new ColorPoint(2, 3, 'red'); + + p2.__proto__ === p1.__proto__ // false + p2.__proto__.__proto__ === p1.__proto__ // true + + * ColorPoint缁ф壙浜哖oint,瀵艰嚧鍓嶈呭師鍨嬬殑鍘熷瀷鏄悗鑰呯殑鍘熷瀷 + * 鍥犳閫氳繃瀛愮被瀹炰緥鐨刜_proto__.__proto__灞炴,鍙互淇敼鐖剁被瀹炰緥鐨勮涓 + p2.__proto__.__proto__.printName = function () { + console.log('Ha'); + }; + p1.printName() // "Ha" + + * ColorPoint 鐨勫疄渚媝2涓婂悜Point绫绘坊鍔犳柟娉,缁撴灉褰卞搷鍒颁簡Point鐨勫疄渚媝1 + + +---------------------------- +鍘熺敓鏋勯犲嚱鏁扮殑缁ф壙 | +---------------------------- + # 鍘熺敓鏋勯犲嚱鏁版槸鎸囪瑷鍐呯疆鐨勬瀯閫犲嚱鏁,閫氬父鐢ㄦ潵鐢熸垚鏁版嵁缁撴瀯 + # ECMAScript 鐨勫師鐢熸瀯閫犲嚱鏁板ぇ鑷存湁涓嬮潰杩欎簺 + Boolean() + Number() + String() + Array() + Date() + Function() + RegExp() + Error() + Object() + + # 浠ュ墠,杩欎簺鍘熺敓鏋勯犲嚱鏁版槸鏃犳硶缁ф壙鐨,姣斿,涓嶈兘鑷繁瀹氫箟涓涓狝rray鐨勫瓙绫 + # ES6 鍏佽缁ф壙鍘熺敓鏋勯犲嚱鏁板畾涔夊瓙绫,鍥犱负 ES6 鏄厛鏂板缓鐖剁被鐨勫疄渚嬪璞his,鐒跺悗鍐嶇敤瀛愮被鐨勬瀯閫犲嚱鏁颁慨楗皌his,浣垮緱鐖剁被鐨勬墍鏈夎涓洪兘鍙互缁ф壙 + class MyArray extends Array { + constructor(...args) { + super(...args); + } + } + + var arr = new MyArray(); + arr[0] = 12; + arr.length // 1 + + arr.length = 0; + arr[0] // undefined + + # extends鍏抽敭瀛椾笉浠呭彲浠ョ敤鏉ョ户鎵跨被,杩樺彲浠ョ敤鏉ョ户鎵垮師鐢熺殑鏋勯犲嚱鏁,鍥犳鍙互鍦ㄥ師鐢熸暟鎹粨鏋勭殑鍩虹涓,瀹氫箟鑷繁鐨勬暟鎹粨鏋 + * 瀹氫箟浜嗕竴涓甫鐗堟湰鍔熻兘鐨勬暟缁 + class VersionedArray extends Array { + constructor() { + super(); + //鍒涘缓 history 浜岀淮鏁扮粍 + this.history = [[]]; + } + commit() { + //璁板綍褰撳墠[]涓殑鎵鏈夋暟鎹埌histrory + this.history.push(this.slice()); + } + revert() { + //鎶婂綋鍓峢istory涓殑鎵鏈夋暟鎹啓鍏ュ埌褰撳墠[] + //绉婚櫎 浠0 鍒 length,鐒跺悗鐢╤istory鐨勬渶鍚庝竴涓猍]涓殑鍏冪礌濉厖鍒板綋鍓峓] + this.splice(0, this.length, ...this.history[this.history.length - 1]); + } + } + + var x = new VersionedArray(); + + x.push(1); + x.push(2); + x // [1, 2] + x.history // [[]] + + x.commit(); + x.history // [[], [1, 2]] + + x.push(3); + x // [1, 2, 3] + x.history // [[], [1, 2]] + + x.revert(); + x // [1, 2] + + * 鑷畾涔塃rror瀛愮被鐨勪緥瀛,鍙互鐢ㄦ潵瀹氬埗鎶ラ敊鏃剁殑琛屼负 + class ExtendableError extends Error { + constructor(message) { + super(); + this.message = message; + this.stack = (new Error()).stack; + this.name = this.constructor.name; + } + } + + class MyError extends ExtendableError { + constructor(m) { + super(m); + } + } + + var myerror = new MyError('ll'); + myerror.message // "ll" + myerror instanceof Error // true + myerror.name // "MyError" + myerror.stack + + // Error + // at MyError.ExtendableError + // ... + + # 娉ㄦ剰,缁ф壙Object鐨勫瓙绫,鏈変竴涓涓哄樊寮 + class NewObj extends Object{ + constructor(){ + super(...arguments); + } + } + var o = new NewObj({attr: true}); + o.attr === true // false + + * NewObj缁ф壙浜哋bject,浣嗘槸鏃犳硶閫氳繃super鏂规硶鍚戠埗绫籓bject浼犲弬 + * 鍥犱负 ES6 鏀瑰彉浜哋bject鏋勯犲嚱鏁扮殑琛屼负,涓鏃﹀彂鐜癘bject鏂规硶涓嶆槸閫氳繃new Object()杩欑褰㈠紡璋冪敤,ES6 瑙勫畾Object鏋勯犲嚱鏁颁細蹇界暐鍙傛暟 + +---------------------------- +Mixin 妯″紡鐨勫疄鐜 | +---------------------------- + # Mixin 鎸囩殑鏄涓璞″悎鎴愪竴涓柊鐨勫璞,鏂板璞″叿鏈夊悇涓粍鎴愭垚鍛樼殑鎺ュ彛 + const a = { + a: 'a' + }; + const b = { + b: 'b' + }; + const c = {...a, ...b}; // {a: 'a', b: 'b'} + + * c瀵硅薄鏄痑瀵硅薄鍜宐瀵硅薄鐨勫悎鎴,鍏锋湁涓よ呯殑鎺ュ彛 + + # 鏇村畬澶囩殑瀹炵幇,灏嗗涓被鐨勬帴鍙"娣峰叆"(mix in)鍙︿竴涓被 + function mix(...mixins) { + class Mix {} + + for (let mixin of mixins) { + copyProperties(Mix, mixin); // 鎷疯礉瀹炰緥灞炴 + copyProperties(Mix.prototype, mixin.prototype); // 鎷疯礉鍘熷瀷灞炴 + } + + return Mix; + } + + function copyProperties(target, source) { + for (let key of Reflect.ownKeys(source)) { + if ( key !== "constructor" + && key !== "prototype" + && key !== "name" + ) { + let desc = Object.getOwnPropertyDescriptor(source, key); + Object.defineProperty(target, key, desc); + } + } + } + + * 涓婇潰浠g爜鐨刴ix鍑芥暟,鍙互灏嗗涓璞″悎鎴愪负涓涓被 + * 浣跨敤鐨勬椂鍊,鍙缁ф壙杩欎釜绫诲嵆鍙 + + class DistributedEdit extends mix(Loggable, Serializable) { + // ... + } \ No newline at end of file diff --git a/JavaSript/ES6/es6-Decorator.js b/JavaSript/ES6/es6-Decorator.js new file mode 100644 index 00000000..53a0edd4 --- /dev/null +++ b/JavaSript/ES6/es6-Decorator.js @@ -0,0 +1,16 @@ +-------------------------------- +淇グ鍣 | +-------------------------------- + 1,绫荤殑淇グ + 2,鏂规硶鐨勪慨楗 + 3,涓轰粈涔堜慨楗板櫒涓嶈兘鐢ㄤ簬鍑芥暟 + 4,core-decorators.js + 5,浣跨敤淇グ鍣ㄥ疄鐜拌嚜鍔ㄥ彂甯冧簨浠 + 6,Mixin + 7,Trait + 8,Babel 杞爜鍣ㄧ殑鏀寔 + + + + * 璺 python 閲屼竴鏍 + * 鎻愭,鏈疄鐜 \ No newline at end of file diff --git "a/JavaSript/ES6/es6-Generator\345\207\275\346\225\260\345\222\214\350\257\255\346\263\225.js" "b/JavaSript/ES6/es6-Generator\345\207\275\346\225\260\345\222\214\350\257\255\346\263\225.js" new file mode 100644 index 00000000..1468cc69 --- /dev/null +++ "b/JavaSript/ES6/es6-Generator\345\207\275\346\225\260\345\222\214\350\257\255\346\263\225.js" @@ -0,0 +1,857 @@ +------------------------------------ +Generator | +------------------------------------ + 1,绠浠 + 2,next 鏂规硶鐨勫弬鏁 + 3,for...of 寰幆 + 4,Generator.prototype.throw() + 5,Generator.prototype.return() + 6,next(),throw(),return() 鐨勫叡鍚岀偣 + 7,yield* 琛ㄨ揪寮 + 8,浣滀负瀵硅薄灞炴х殑 Generator 鍑芥暟 + 9,Generator 鍑芥暟鐨則his + 10,鍚箟 + 11,搴旂敤 + +------------------------------------ +Generator-绠浠 | +------------------------------------ + # 璺烶ython鐨勭敓鎴愬櫒,杩唬鍣ㄤ竴鏍风殑 + # 姝g‘鐨勫畾涔夊Э鍔(*鍙峰湪function 涓庡嚱鏁板悕涓棿浠绘剰鍦版柟閮藉彲浠) + function * foo(x, y) { 路路路 } + function *foo(x, y) { 路路路 } + function* foo(x, y) { 路路路 } + function*foo(x, y) { 路路路 } + + # 浣跨敤next()鍑芥暟鏉ユ搷浣滅敓鎴愬櫒鐨勭粨鏋 + + function* foo(){ + yield 5; + yield 6; + return 7; + } + + it = foo(); + + console.log(it.next()); //{value: 5, done: false} + console.log(it.next()); //{value: 6, done: false} + console.log(it.next()); //{value: 7, done: true} + console.log(it.next()); //{value: undefined, done: true} + + # 閬嶅巻鍣ㄥ璞$殑next鏂规硶鐨勮繍琛岄昏緫濡備笅 + 1,閬囧埌yield琛ㄨ揪寮忥紝灏辨殏鍋滄墽琛屽悗闈㈢殑鎿嶄綔,骞跺皢绱ц窡鍦▂ield鍚庨潰鐨勯偅涓〃杈惧紡鐨勫,浣滀负杩斿洖鐨勫璞$殑value灞炴у + 2,涓嬩竴娆¤皟鐢╪ext鏂规硶鏃,鍐嶇户缁線涓嬫墽琛,鐩村埌閬囧埌涓嬩竴涓獃ield琛ㄨ揪寮 + 3,濡傛灉娌℃湁鍐嶉亣鍒版柊鐨剏ield琛ㄨ揪寮,灏变竴鐩磋繍琛屽埌鍑芥暟缁撴潫,鐩村埌return璇彞涓烘,骞跺皢return璇彞鍚庨潰鐨勮〃杈惧紡鐨勫,浣滀负杩斿洖鐨勫璞$殑value灞炴у + 4,濡傛灉璇ュ嚱鏁版病鏈塺eturn璇彞,鍒欒繑鍥炵殑瀵硅薄鐨剉alue灞炴у间负undefined + + * 闇瑕佹敞鎰忕殑鏄,yield琛ㄨ揪寮忓悗闈㈢殑琛ㄨ揪寮,鍙湁褰撹皟鐢╪ext鏂规硶,鍐呴儴鎸囬拡鎸囧悜璇ヨ鍙ユ椂鎵嶄細鎵ц,鍥犳绛変簬涓 JavaScript 鎻愪緵浜嗘墜鍔ㄧ殑"鎯版ф眰鍊"(Lazy Evaluation)鐨勮娉曞姛鑳 + function* gen() { + yield 123 + 456; + } + * yield鍚庨潰鐨勮〃杈惧紡123 + 456,涓嶄細绔嬪嵆姹傚,鍙細鍦╪ext鏂规硶灏嗘寚閽堢Щ鍒拌繖涓鍙ユ椂,鎵嶄細姹傚 + + + # return 涓 yield + * 鐢熸垚鍣ㄩ噷闈 return 鍙兘鎵ц涓娆,渚夸細缁撴潫鎺,鑰寉ield鍙互鎵ц澶氭 + + # Generator 鍑芥暟鍙互涓嶇敤yield琛ㄨ揪寮,杩欐椂灏卞彉鎴愪簡涓涓崟绾殑鏆傜紦鎵ц鍑芥暟 + function* f() { + console.log('鎵ц浜嗭紒') + } + + var generator = f(); //涓嶄細鎵ц鍑芥暟 + generator.next(); //璋冪敤next()鏂规硶鎵嶄細鎵ц鍑芥暟 + + # yield琛ㄨ揪寮忓彧鑳界敤鍦 Generator 鍑芥暟閲岄潰,鐢ㄥ湪鍏朵粬鍦版柟閮戒細鎶ラ敊 + # yield琛ㄨ揪寮忓鏋滅敤鍦ㄥ彟涓涓〃杈惧紡涔嬩腑,蹇呴』鏀惧湪鍦嗘嫭鍙烽噷闈 + function* demo() { + console.log('Hello' + yield); // SyntaxError + console.log('Hello' + yield 123); // SyntaxError + console.log('Hello' + (yield)); // OK + console.log('Hello' + (yield 123)); // OK + } + + * yield琛ㄨ揪寮'鐢ㄤ綔鍑芥暟鍙傛暟'鎴'鏀惧湪璧嬪艰〃杈惧紡鐨勫彸杈',鍙互涓嶅姞鎷彿 + function* demo() { + foo(yield 'a', yield 'b'); // OK + let input = yield; // OK + } + + # 涓 Iterator 鎺ュ彛鐨勫叧绯 + * 浠绘剰涓涓璞$殑Symbol.iterator鏂规硶,绛変簬'璇ュ璞$殑閬嶅巻鍣ㄧ敓鎴愬嚱鏁',璋冪敤璇ュ嚱鏁颁細杩斿洖璇ュ璞$殑涓涓亶鍘嗗櫒瀵硅薄 + 鐢变簬 'Generator 鍑芥暟灏辨槸閬嶅巻鍣ㄧ敓鎴愬嚱鏁',鍥犳鍙互鎶 Generator 璧嬪肩粰瀵硅薄鐨凷ymbol.iterator灞炴,浠庤屼娇寰楄瀵硅薄鍏锋湁 Iterator 鎺ュ彛 + + let obj = { + name:'KevinBlandy', + [Symbol.iterator]:function* (){ + for(let key of Reflect.ownKeys(this)){ + yield {[key]:Reflect.get(this,key)}; + } + } + } + + for(let item of obj){ + console.log(item); + } + + * Generator 鍑芥暟鎵ц鍚,杩斿洖涓涓亶鍘嗗櫒瀵硅薄,璇ュ璞℃湰韬篃鍏锋湁Symbol.iterator灞炴,鎵ц鍚庤繑鍥炶嚜韬 + function* foo(){ + yield 5; + } + //鎵ц鐢熸垚鍣ㄥ嚱鏁,杩斿洖涓涓亶鍘嗗櫒瀵硅薄 + it = foo(); + + //璇ュ璞¤嚜宸变篃鏈塖ymbol.iterator灞炴,鎵ц鍚,杩斿洖 this + console.log(it === it[Symbol.iterator]()); //true + +------------------------------------ +next 鏂规硶鍙傛暟 | +------------------------------------ + # yield琛ㄨ揪寮忔湰韬病鏈夎繑鍥炲,鎴栬呰鎬绘槸杩斿洖undefined + # next鏂规硶鍙互甯︿竴涓弬鏁,璇ュ弬鏁板氨浼氳褰撲綔涓婁竴涓獃ield琛ㄨ揪寮忕殑杩斿洖鍊 + # 璺 Python 鐢熸垚鍣ㄧ殑 send() 鏂规硶涓鏍 + function* foo(num){ + for(let x = 0 ;x < num ; x++){ + let param = yield x; + console.log(`鏈杩唬鍙傛暟${param}`); + } + } + + it = foo(3); + + console.log(it.next('鎴戞槸鍙傛暟1')); //0 绗竴娆¤凯浠g殑鍙傛暟,鑾峰彇涓嶅埌 + console.log(it.next('鎴戞槸鍙傛暟2')); //1 鏈杩唬鍙傛暟鎴戞槸鍙傛暟2 + console.log(it.next('鎴戞槸鍙傛暟3')); //2 鏈杩唬鍙傛暟鎴戞槸鍙傛暟3 + console.log(it.next('鎴戞槸鍙傛暟4')); //undefined 鏈杩唬鍙傛暟鎴戞槸鍙傛暟3 + + # 鍙互閫氳繃杩欑鏂瑰紡,淇敼鐢熸垚鍣ㄧ殑鎵ц鐘舵 + +------------------------------------ +next 鏂规硶鍙傛暟 | +------------------------------------ + # for...of寰幆鍙互鑷姩閬嶅巻 Generator 鍑芥暟鏃剁敓鎴愮殑Iterator瀵硅薄,涓旀鏃朵笉鍐嶉渶瑕佽皟鐢╪ext鏂规硶 + function* foo (num){ + while(num > 0){ + yield num; + num -= 1; + } + } + for(let item of foo(5)){ + console.log(item); + } + //5,4,3,2,1 + + # 鍒╃敤 Generator 鍑芥暟鍜宖or...of寰幆,瀹炵幇鏂愭尝閭e鏁板垪 + function* fibonacci() { + let [prev, curr] = [0, 1]; + for (;;) { + [prev, curr] = [curr, prev + curr]; + yield curr; + } + } + + for (let n of fibonacci()) { + if (n > 1000) { + break; + } + console.log(n); + } + + # 鍒╃敤for...of寰幆,鍙互鍐欏嚭閬嶅巻浠绘剰瀵硅薄(object)鐨勬柟娉,鍘熺敓鐨 JavaScript 瀵硅薄娌℃湁閬嶅巻鎺ュ彛,鏃犳硶浣跨敤for...of寰幆 + 閫氳繃 Generator 鍑芥暟涓哄畠鍔犱笂杩欎釜鎺ュ彛,灏卞彲浠ョ敤浜 + //璁㎡bject鐨勫瓙瀵硅薄閮芥敮鎸佽凯浠 + Object.prototype[Symbol.iterator] = function* (){ + let keys = Reflect.ownKeys(this); + for(let key of keys){ + yield [key,Reflect.get(this,key)]; + } + } + + function Obj(){ + this.name = "KevinBlandy"; + this.age = 22; + } + + for(let [key,value] of new Obj()){ + console.log(`${key} = ${value}`); + } + //name = KevinBlandy + //age = 22 + + # 闄や簡for...of寰幆浠ュ,鎵╁睍杩愮畻绗(...),瑙f瀯璧嬪煎拰Array.from鏂规硶鍐呴儴璋冪敤鐨,閮芥槸閬嶅巻鍣ㄦ帴鍙 + * 杩欐剰鍛崇潃,瀹冧滑閮藉彲浠ュ皢 Generator 鍑芥暟杩斿洖鐨 Iterator 瀵硅薄,浣滀负鍙傛暟 + function* numbers () { + yield 1 + yield 2 + return 3 + yield 4 + } + + // 鎵╁睍杩愮畻绗 + [...numbers()] // [1, 2] + + // Array.from 鏂规硶 + Array.from(numbers()) // [1, 2] + + // 瑙f瀯璧嬪 + let [x, y] = numbers(); + x // 1 + y // 2 + + // for...of 寰幆 + for (let n of numbers()) { + console.log(n) + } + // 1 + // 2 + + +------------------------------------ +Generator.prototype.throw() | +------------------------------------ + # Generator 鍑芥暟杩斿洖鐨勯亶鍘嗗櫒瀵硅薄,閮芥湁涓涓猼hrow鏂规硶,鍙互鍦ㄥ嚱鏁颁綋澶栨姏鍑洪敊璇,鐒跺悗鍦 Generator 鍑芥暟浣撳唴鎹曡幏 + var g = function* () { + try { + yield; + } catch (e) { + console.log('鍐呴儴鎹曡幏', e); + } + }; + + var i = g(); + i.next(); + + try { + i.throw('a'); + i.throw('b'); + } catch (e) { + console.log('澶栭儴鎹曡幏', e); + } + // 鍐呴儴鎹曡幏 a + // 澶栭儴鎹曡幏 b + + * i杩炵画鎶涘嚭涓や釜閿欒,绗竴涓敊璇 Generator 鍑芥暟浣撳唴鐨刢atch璇彞鎹曡幏 + * i绗簩娆℃姏鍑洪敊璇,鐢变簬 Generator 鍑芥暟鍐呴儴鐨刢atch璇彞宸茬粡鎵ц杩囦簡,涓嶄細鍐嶆崟鎹夊埌杩欎釜閿欒浜,鎵浠ヨ繖涓敊璇氨琚姏鍑轰簡 Generator 鍑芥暟浣,琚嚱鏁颁綋澶栫殑catch璇彞鎹曡幏 + + # throw鏂规硶鍙互鎺ュ彈涓涓弬鏁,璇ュ弬鏁颁細琚玞atch璇彞鎺ユ敹,寤鸿鎶涘嚭Error瀵硅薄鐨勫疄渚 + var g = function* () { + try { + yield; + } catch (e) { + console.log(e); + } + }; + + var i = g(); + i.next(); + i.throw(new Error('鍑洪敊浜嗭紒')); + // Error: 鍑洪敊浜嗭紒 + + # 濡傛灉 Generator 鍑芥暟鍐呴儴娌℃湁閮ㄧ讲try...catch浠g爜鍧,閭d箞throw鏂规硶鎶涘嚭鐨勯敊璇,灏嗚澶栭儴try...catch浠g爜鍧楁崟鑾 + var g = function* () { + yield; + }; + + var i = g(); + i.next(); + i.throw('寮傚父浜'); //Uncaught 寮傚父浜 + + # throw鏂规硶琚崟鑾蜂互鍚,浼氶檮甯︽墽琛屼笅涓鏉ield琛ㄨ揪寮,涔熷氨鏄,浼氶檮甯︽墽琛屼竴娆ext鏂规硶 + var gen = function* gen(){ + try { + yield console.log('a'); + } catch (e) { + // ... + } + yield console.log('b'); + yield console.log('c'); + } + + var g = gen(); + g.next() // a + g.throw() // b 鎵ц浜唗hrow,琚凯浠e櫒閲岄潰鐨刢atch浜,杩樻槸浼氶檮甯︽墽琛屼笅涓娆 yield + g.next() // c + + * 鍙 Generator 鍑芥暟鍐呴儴閮ㄧ讲浜唗ry...catch浠g爜鍧,閭d箞閬嶅巻鍣ㄧ殑throw鏂规硶鎶涘嚭鐨勯敊璇,涓嶅奖鍝嶄笅涓娆¢亶鍘 + * 'throw鍛戒护涓巘hrow鏂规硶鏄棤鍏崇殑,涓よ呬簰涓嶅奖鍝' + + # Generator 鍑芥暟浣撳鎶涘嚭鐨勯敊璇,鍙互鍦ㄥ嚱鏁颁綋鍐呮崟鑾,鍙嶈繃鏉,Generator 鍑芥暟浣撳唴鎶涘嚭鐨勯敊璇,涔熷彲浠ヨ鍑芥暟浣撳鐨刢atch鎹曡幏 + function* foo() { + var x = yield 3; + var y = x.toUpperCase(); + yield y; + } + var it = foo(); + it.next(); // { value:3, done:false } + try { + it.next(42); + } catch (err) { + console.log(err); //TypeError: x.toUpperCase is not a function + } + + # 涓鏃 Generator 鎵ц杩囩▼涓姏鍑洪敊璇,涓旀病鏈夎鍐呴儴鎹曡幏,灏变笉浼氬啀鎵ц涓嬪幓浜 + * 濡傛灉姝ゅ悗杩樿皟鐢╪ext鏂规硶,灏嗚繑鍥炰竴涓獀alue灞炴х瓑浜巙ndefined,done灞炴х瓑浜巘rue鐨勫璞,鍗 JavaScript 寮曟搸璁や负杩欎釜 Generator 宸茬粡杩愯缁撴潫浜 + function* foo(){ + yield 5; + throw e; + yield 6; + } + it = foo(); + console.log(it.next()); + try{ + console.log(it.next()); + }catch(e){ + //skip + } + console.log(it.next()); + //{value: 5, done: false} + //{value: undefined, done: true} + +------------------------------------ +Generator.prototype.return() | +------------------------------------ + # Generator 鍑芥暟杩斿洖鐨勯亶鍘嗗櫒瀵硅薄,杩樻湁涓涓猺eturn鏂规硶,鍙互杩斿洖缁欏畾鐨勫,骞朵笖缁堢粨閬嶅巻 Generator 鍑芥暟 + function* gen() { + yield 1; + yield 2; + yield 3; + } + + var g = gen(); + + g.next() // { value: 1, done: false } + g.return('foo') // { value: "foo", done: true } + g.next() // { value: undefined, done: true } + + * 濡傛灉return鏂规硶璋冪敤鏃,涓嶆彁渚涘弬鏁,鍒欒繑鍥炲肩殑value灞炴т负undefined + + # 濡傛灉 Generator 鍑芥暟鍐呴儴鏈塼ry...finally浠g爜鍧,閭d箞return鏂规硶浼氭帹杩熷埌finally浠g爜鍧楁墽琛屽畬鍐嶆墽琛 + function* gen() { + try{ + yield 1; + yield 2; + yield 3; + }finally{ + console.log('鏈缁堟墽琛'); + } + } + + var g = gen(); + + console.log(g.next()) //{value: 1, done: false} + //鎵цreturn,浼氬厛鍘绘墽琛 finally鐨勪唬鐮 + console.log(g.return()) //鏈缁堟墽琛 { value: undefined, done: true } + console.log(g.next()) // {value: undefined, done: true} + +------------------------------------ +next(),throw(),return() 鐨勫叡鍚岀偣 | +------------------------------------ + # next(),throw(),return()杩欎笁涓柟娉曟湰璐ㄤ笂鏄悓涓浠朵簨,鍙互鏀惧湪涓璧风悊瑙 + # 瀹冧滑鐨勪綔鐢ㄩ兘鏄 Generator 鍑芥暟鎭㈠鎵ц,骞朵笖浣跨敤涓嶅悓鐨勮鍙ユ浛鎹ield琛ㄨ揪寮 + +------------------------------------ +yield* 琛ㄨ揪寮 | +------------------------------------ + # 浠庤娉曡搴︾湅,濡傛灉yield琛ㄨ揪寮忓悗闈㈣窡鐨勬槸涓涓亶鍘嗗櫒瀵硅薄,闇瑕佸湪yield琛ㄨ揪寮忓悗闈㈠姞涓婃槦鍙,琛ㄦ槑瀹冭繑鍥炵殑鏄竴涓亶鍘嗗櫒瀵硅薄,杩欒绉颁负yield*琛ㄨ揪寮 + # 濡傛灉鍦 Generator 鍑芥暟鍐呴儴,璋冪敤鍙︿竴涓 Generator 鍑芥暟,榛樿鎯呭喌涓嬫槸娌℃湁鏁堟灉鐨 + function* foo() { + yield 'a'; + yield 'b'; + } + + function* bar() { + yield 'x'; + foo(); + yield 'y'; + } + + for (let v of bar()){ + console.log(v); + } + // "x" + // "y" + * foo鍜宐ar閮芥槸 Generator 鍑芥暟,鍦╞ar閲岄潰璋冪敤foo,鏄笉浼氭湁鏁堟灉鐨 + * 杩欎釜灏遍渶瑕佺敤鍒皔ield*琛ㄨ揪寮,鐢ㄦ潵鍦ㄤ竴涓 Generator 鍑芥暟閲岄潰鎵ц鍙︿竴涓 Generator 鍑芥暟 + + # 鍦ㄤ竴涓 Generator 鍑芥暟閲岄潰鎵ц鍙︿竴涓 Generator 鍑芥暟 + function* foo() { + yield 'a'; + yield 'b'; + } + + function* bar() { + yield 'x'; + yield* foo(); + yield 'y'; + } + + // 绛夊悓浜 + function* bar() { + yield 'x'; + yield 'a'; + yield 'b'; + yield 'y'; + } + + // 绛夊悓浜 + function* bar() { + yield 'x'; + for (let v of foo()) { + yield v; + } + yield 'y'; + } + + for (let v of bar()){ + console.log(v); + } + // "x" + // "a" + // "b" + // "y" + + # 濡傛灉yield*鍚庨潰璺熺潃涓涓暟缁,鐢变簬鏁扮粍鍘熺敓鏀寔閬嶅巻鍣,鍥犳灏变細閬嶅巻鏁扮粍鎴愬憳 + let arr = [1,2,3]; + let arr = [1,2,3]; + function* foo(){ + yield '鏁扮粍鍏冪礌涔嬪墠'; + yield *arr; + yield '鏁扮粍鍏冪礌涔嬪悗'; + } + + for(let item of foo()){ + console.log(item); + } 鏁扮粍鍏冪礌涔嬪墠 + //1 + //2 + //3 + //鏁扮粍鍏冪礌涔嬪悗 + + # yield*鍚庨潰鐨 Generator 鍑芥暟(娌℃湁return璇彞鏃)绛夊悓浜庡湪 Generator 鍑芥暟鍐呴儴,閮ㄧ讲涓涓猣or...of寰幆 + function* concat(iter1, iter2) { + yield* iter1; + yield* iter2; + } + // 绛夊悓浜 + + function* concat(iter1, iter2) { + for (var value of iter1) { + yield value; + } + for (var value of iter2) { + yield value; + } + } + + * yield*鍚庨潰鐨 Generator 鍑芥暟(娌℃湁return璇彞鏃,涓嶈繃鏄痜or...of鐨勪竴绉嶇畝鍐欏舰寮,瀹屽叏鍙互鐢ㄥ悗鑰呮浛浠e墠鑰 + * 鍙嶄箣,鍦ㄦ湁return璇彞鏃,鍒欓渶瑕佺敤var value = yield* iterator鐨勫舰寮忚幏鍙杛eturn璇彞鐨勫 + + # 闄呬笂,浠讳綍鏁版嵁缁撴瀯鍙鏈 Iterator 鎺ュ彛,灏卞彲浠ヨyield*閬嶅巻 + # 濡傛灉琚唬鐞嗙殑 Generator 鍑芥暟鏈塺eturn璇彞,閭d箞灏卞彲浠ュ悜浠g悊瀹冪殑 Generator 鍑芥暟杩斿洖鏁版嵁 + function* foo() { + yield 2; + yield 3; + return "foo"; + } + + function* bar() { + yield 1; + var v = yield* foo(); + console.log("v: " + v); + yield 4; + } + + var it = bar(); + + it.next() + // {value: 1, done: false} + it.next() + // {value: 2, done: false} + it.next() + // {value: 3, done: false} + it.next(); + // "v: foo" //鑾峰彇鍒颁簡琚唬鐞嗙殑鐢熸垚鍣ㄨ繑鍥炲 + // {value: 4, done: false} + it.next() + // {value: undefined, done: true} + -------------------------------- + function* genFuncWithReturn() { + yield 'a'; + yield 'b'; + return 'The result'; + } + function* logReturned(genObj) { + let result = yield* genObj; + console.log(result); + } + [...logReturned(genFuncWithReturn())] + // The result + // 鍊间负 [ 'a', 'b' ] + + # yield*鍛戒护鍙互寰堟柟渚垮湴鍙栧嚭宓屽鏁扮粍鐨勬墍鏈夋垚鍛 + function* iteratorTree(tree) { + if (Array.isArray(tree)) { + for(let i=0; i < tree.length; i++) { + //閬嶅巻姣忎釜鍏冪礌,濡傛灉鏄暟缁勫厓绱,鍒欓掑綊鐨剏ield + yield* iteratorTree(tree[i]); + } + } else { + //褰撳墠鍏冪礌闈炴暟缁勫厓绱,yield杩斿洖 + yield tree; + } + } + + const tree = [ 'a', ['b', 'c'], ['d', 'e'] ]; + + for(let x of iteratorTree(tree)) { + console.log(x); + } + // a + // b + // c + // d + // e + + # 绋嶅井澶嶆潅鐨勪緥瀛,浣跨敤yield*璇彞閬嶅巻瀹屽叏浜屽弶鏍 + // 涓嬮潰鏄簩鍙夋爲鐨勬瀯閫犲嚱鏁帮紝 + // 涓変釜鍙傛暟鍒嗗埆鏄乏鏍戙佸綋鍓嶈妭鐐瑰拰鍙虫爲 + function Tree(left, label, right) { + this.left = left; + this.label = label; + this.right = right; + } + + // 涓嬮潰鏄腑搴忥紙inorder锛夐亶鍘嗗嚱鏁般 + // 鐢变簬杩斿洖鐨勬槸涓涓亶鍘嗗櫒锛屾墍浠ヨ鐢╣enerator鍑芥暟銆 + // 鍑芥暟浣撳唴閲囩敤閫掑綊绠楁硶锛屾墍浠ュ乏鏍戝拰鍙虫爲瑕佺敤yield*閬嶅巻 + function* inorder(t) { + if (t) { + yield* inorder(t.left); + yield t.label; + yield* inorder(t.right); + } + } + + // 涓嬮潰鐢熸垚浜屽弶鏍 + function make(array) { + // 鍒ゆ柇鏄惁涓哄彾鑺傜偣 + if (array.length == 1) { + return new Tree(null, array[0], null); + } + return new Tree(make(array[0]), array[1], make(array[2])); + } + + let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]); + + // 閬嶅巻浜屽弶鏍 + var result = []; + + for (let node of inorder(tree)) { + result.push(node); + } + + result + // ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + +------------------------------------ +浣滀负瀵硅薄灞炴х殑 Generator 鍑芥暟 | +------------------------------------ + # 濡傛灉涓涓璞$殑灞炴ф槸 Generator 鍑芥暟,鍙互绠鍐欐垚涓嬮潰鐨勫舰寮 + let obj = { + * myGeneratorMethod() { + //路路路 + } + }; + + * 灞炴у墠闈㈡湁涓涓槦鍙,琛ㄧず杩欎釜灞炴ф槸涓涓 Generator 鍑芥暟 + * 瀹冪殑瀹屾暣褰㈠紡濡備笅,涓庝笂闈㈢殑鍐欐硶鏄瓑浠风殑 + + let obj = { + myGeneratorMethod: function* () { + // 路路路 + } + }; + +------------------------------------ +Generator 鍑芥暟鐨則his | +------------------------------------ + # Generator 鍑芥暟鎬绘槸杩斿洖涓涓亶鍘嗗櫒,ES6 瑙勫畾杩欎釜閬嶅巻鍣ㄦ槸 Generator 鍑芥暟鐨勫疄渚(瀵硅薄),涔熺户鎵夸簡 Generator 鍑芥暟鐨刾rototype瀵硅薄涓婄殑鏂规硶 + function* g() {} + + g.prototype.hello = function () { + return 'hi!'; + }; + + let obj = g(); + + obj instanceof g // true + obj.hello() // 'hi!' + + * Generator 鍑芥暟g杩斿洖鐨勯亶鍘嗗櫒obj,鏄痝鐨勫疄渚,鑰屼笖缁ф壙浜唃.prototype + * 濡傛灉鎶奼褰撲綔鏅氱殑鏋勯犲嚱鏁,骞朵笉浼氱敓鏁,鍥犱负g杩斿洖鐨勬绘槸閬嶅巻鍣ㄥ璞,鑰屼笉鏄痶his瀵硅薄 + function* g() { + this.a = 11; + } + + let obj = g(); + obj.next(); + obj.a // undefined + * Generator 鍑芥暟g鍦╰his瀵硅薄涓婇潰娣诲姞浜嗕竴涓睘鎬浣嗘槸obj瀵硅薄鎷夸笉鍒拌繖涓睘鎬 + + * Generator 鍑芥暟涔熶笉鑳借窡new鍛戒护涓璧风敤,浼氭姤閿 + function* F() { + yield this.x = 2; + yield this.y = 3; + } + + new F() + // TypeError: F is not a constructor + + # 璁 Generator 鍑芥暟杩斿洖涓涓甯哥殑瀵硅薄瀹炰緥,鏃㈠彲浠ョ敤next鏂规硶,鍙堝彲浠ヨ幏寰楁甯哥殑this + function* F() { + this.a = 1; + yield this.b = 2; + yield this.c = 3; + } + + var obj = {}; + //浣跨敤call,鏀瑰彉鐢熸垚鍣ㄧ殑context + var f = F.call(obj); + + f.next(); // Object {value: 2, done: false} + f.next(); // Object {value: 3, done: false} + f.next(); // Object {value: undefined, done: true} + + obj.a // 1 + obj.b // 2 + obj.c // 3 + + * 鎵ц鐨勬槸閬嶅巻鍣ㄥ璞,浣嗘槸鐢熸垚鐨勫璞″疄渚嬫槸obj,鏈夋病鏈夊姙娉曞皢杩欎袱涓璞$粺涓鍛? + * 涓涓姙娉曞氨鏄皢obj鎹㈡垚F.prototype + * 鍐嶅皢F鏀规垚鏋勯犲嚱鏁,灏卞彲浠ュ瀹冩墽琛宯ew鍛戒护浜 + function* F() { + this.a = 1; + yield this.b = 2; + yield this.c = 3; + } + var f = F.call(F.prototype); + + f.next(); // Object {value: 2, done: false} + f.next(); // Object {value: 3, done: false} + f.next(); // Object {value: undefined, done: true} + + f.a // 1 + f.b // 2 + --------------------------------------------------- + function* gen() { + this.a = 1; + yield this.b = 2; + yield this.c = 3; + } + + function F() { + return gen.call(gen.prototype); + } + + var f = new F(); + + f.next(); // Object {value: 2, done: false} + f.next(); // Object {value: 3, done: false} + f.next(); // Object {value: undefined, done: true} + + f.a // 1 + f.b // 2 + f.c // 3 + +------------------------------------ +鍚箟 | +------------------------------------ + # Generator 涓庣姸鎬佹満 + * Generator 鏄疄鐜扮姸鎬佹満鐨勬渶浣崇粨鏋 + //鏅氬嚱鏁板疄鐜 + var ticking = true; + var clock = function() { + if (ticking) { + console.log('Tick!'); + } else { + console.log('Tock!'); + } + ticking = !ticking; + } + + //鐢熸垚鍣ㄥ疄鐜 + var clock = function* () { + while (true) { + console.log('Tick!'); + yield; + console.log('Tock!'); + yield; + } + }; + * 灏戜簡鐢ㄦ潵淇濆瓨鐘舵佺殑澶栭儴鍙橀噺ticking,杩欐牱灏辨洿绠娲,鏇村畨鍏(鐘舵佷笉浼氳闈炴硶绡℃敼),鏇寸鍚堝嚱鏁板紡缂栫▼鐨勬濇兂,鍦ㄥ啓娉曚笂涔熸洿浼橀泤 + * Generator 涔嬫墍浠ュ彲浠ヤ笉鐢ㄥ閮ㄥ彉閲忎繚瀛樼姸鎬,鏄洜涓哄畠鏈韩灏卞寘鍚簡涓涓姸鎬佷俊鎭,鍗崇洰鍓嶆槸鍚﹀浜庢殏鍋滄 + + # Generator 涓庡崗绋 + * Emmmmmmmmmmmm,Python閲岄潰鐨勫崗绋 + * 閬囧埌io鎿嶄綔鍒欏湪褰撳墠绾跨▼鐨勫涓猧o浠诲姟涔嬮棿鍒囨崲....浣唈s娌″緱io浠诲姟鍟 + + # Generator 涓庝笂涓嬫枃 + ... + +------------------------------------ +搴旂敤 | +------------------------------------ + 1,寮傛鎿嶄綔鐨勫悓姝ュ寲琛ㄨ揪 + * 鍙互鎶婂紓姝ユ搷浣滃啓鍦▂ield琛ㄨ揪寮忛噷闈,绛夊埌璋冪敤next鏂规硶鏃跺啀寰鍚庢墽琛 + * 杩欏疄闄呬笂绛夊悓浜庝笉闇瑕佸啓鍥炶皟鍑芥暟浜,鍥犱负寮傛鎿嶄綔鐨勫悗缁搷浣滃彲浠ユ斁鍦▂ield琛ㄨ揪寮忎笅闈,鍙嶆瑕佺瓑鍒拌皟鐢╪ext鏂规硶鏃跺啀鎵ц + * 鎵浠,Generator 鍑芥暟鐨勪竴涓噸瑕佸疄闄呮剰涔夊氨鏄敤鏉ュ鐞嗗紓姝ユ搷浣,鏀瑰啓鍥炶皟鍑芥暟 + function* loadUI() { + showLoadingScreen(); + yield loadUIDataAsynchronously(); + hideLoadingScreen(); + } + + var loader = loadUI(); + + // 鍔犺浇UI + loader.next() + + // 鍗歌浇UI + loader.next() + * 绗竴娆¤皟鐢╨oadUI鍑芥暟鏃,璇ュ嚱鏁颁笉浼氭墽琛,浠呰繑鍥炰竴涓亶鍘嗗櫒 + * 涓嬩竴娆″璇ラ亶鍘嗗櫒璋冪敤next鏂规硶,鍒欎細鏄剧ずLoading鐣岄潰(showLoadingScreen),骞朵笖寮傛鍔犺浇鏁版嵁(loadUIDataAsynchronously) + * 绛夊埌鏁版嵁鍔犺浇瀹屾垚,鍐嶄竴娆′娇鐢╪ext鏂规硶,鍒欎細闅愯棌Loading鐣岄潰,鍙互鐪嬪埌,杩欑鍐欐硶鐨勫ソ澶勬槸鎵鏈塋oading鐣岄潰鐨勯昏緫,閮借灏佽鍦ㄤ竴涓嚱鏁,鎸夐儴灏辩彮闈炲父娓呮櫚 + + * Ajax 鏄吀鍨嬬殑寮傛鎿嶄綔,閫氳繃 Generator 鍑芥暟閮ㄧ讲 Ajax 鎿嶄綔,鍙互鐢ㄥ悓姝ョ殑鏂瑰紡琛ㄨ揪 + function* main() { + var result = yield request("http://some.url"); + var resp = JSON.parse(result); + console.log(resp.value); + } + + function request(url) { + //makeAjaxCall鍑芥暟涓殑next鏂规硶,蹇呴』鍔犱笂response鍙傛暟,鍥犱负yield琛ㄨ揪寮,鏈韩鏄病鏈夊肩殑,鎬绘槸绛変簬undefined + makeAjaxCall(url, function(response){ + it.next(response); + }); + } + + var it = main(); + + it.next(); + + * 娌$湅鎳傝繖涓獨鎿嶄綔 + + * 閫氳繃 Generator 鍑芥暟閫愯璇诲彇鏂囨湰鏂囦欢銆 + function* numbers() { + let file = new FileReader("numbers.txt"); + try { + while(!file.eof) { + yield parseInt(file.readLine(), 10); + } + } finally { + file.close(); + } + } + + 2,鎺у埗娴佺鐞 + * 濡傛灉鏈変竴涓姝ユ搷浣滈潪甯歌楁椂,閲囩敤鍥炶皟鍑芥暟,鍙兘浼氬啓鎴愪笅闈㈣繖鏍 + step1(function (value1) { + step2(value1, function(value2) { + step3(value2, function(value3) { + step4(value3, function(value4) { + // Do something with value4 + }); + }); + }); + }); + + * 閲囩敤 Promise 鏀瑰啓涓婇潰鐨勪唬鐮 + Promise.resolve(step1) + .then(step2) + .then(step3) + .then(step4) + .then(function (value4) { + // Do something with value4 + }, function (error) { + // Handle any error from step1 through step4 + }) + .done(); + * 涓婇潰浠g爜宸茬粡鎶婂洖璋冨嚱鏁,鏀规垚浜嗙洿绾挎墽琛岀殑褰㈠紡,浣嗘槸鍔犲叆浜嗗ぇ閲 Promise 鐨勮娉 + * Generator 鍑芥暟鍙互杩涗竴姝ユ敼鍠勪唬鐮佽繍琛屾祦绋 + function* longRunningTask(value1) { + try { + var value2 = yield step1(value1); + var value3 = yield step2(value2); + var value4 = yield step3(value3); + var value5 = yield step4(value4); + // Do something with value4 + } catch (e) { + // Handle any error from step1 through step4 + } + } + * 娉ㄦ剰,涓婇潰杩欑鍋氭硶,鍙傚悎鍚屾鎿嶄綔,鍗虫墍鏈夌殑task閮藉繀椤绘槸鍚屾鐨,涓嶈兘鏈夊紓姝ユ搷浣 + * 鍥犱负杩欓噷鐨勪唬鐮佷竴寰楀埌杩斿洖鍊,灏辩户缁線涓嬫墽琛,娌℃湁鍒ゆ柇寮傛鎿嶄綔浣曟椂瀹屾垚 + + * 鍒╃敤for...of寰幆浼氳嚜鍔ㄤ緷娆℃墽琛寉ield鍛戒护鐨勭壒鎬,鎻愪緵涓绉嶆洿涓鑸殑鎺у埗娴佺鐞嗙殑鏂规硶 + let steps = [step1Func, step2Func, step3Func]; + function* iterateSteps(steps){ + for (var i=0; i< steps.length; i++){ + var step = steps[i]; + yield step(); + } + } + * 鏁扮粍steps灏佽浜嗕竴涓换鍔$殑澶氫釜姝ラ,Generator 鍑芥暟iterateSteps鍒欐槸渚濇涓鸿繖浜涙楠ゅ姞涓妝ield鍛戒护 + * 灏嗕换鍔″垎瑙f垚姝ラ涔嬪悗,杩樺彲浠ュ皢椤圭洰鍒嗚В鎴愬涓緷娆℃墽琛岀殑浠诲姟 + let jobs = [job1, job2, job3]; + + function* iterateJobs(jobs){ + for (var i=0; i< jobs.length; i++){ + var job = jobs[i]; + yield* iterateSteps(job.steps); + } + } + * 鏁扮粍jobs灏佽浜嗕竴涓」鐩殑澶氫釜浠诲姟,Generator 鍑芥暟iterateJobs鍒欐槸渚濇涓鸿繖浜涗换鍔″姞涓妝ield*鍛戒护 + * 鏈鍚,灏卞彲浠ョ敤for...of寰幆涓娆℃т緷娆℃墽琛屾墍鏈変换鍔$殑鎵鏈夋楠 + for (var step of iterateJobs(jobs)){ + console.log(step.id); + } + + * 涓婇潰鐨勫仛娉曞彧鑳界敤浜庢墍鏈夋楠ら兘鏄悓姝ユ搷浣滅殑鎯呭喌锛屼笉鑳芥湁寮傛鎿嶄綔鐨勬楠 + * for...of鐨勬湰璐ㄦ槸涓涓獁hile寰幆,鎵浠ヤ笂闈㈢殑浠g爜瀹炶川涓婃墽琛岀殑鏄笅闈㈢殑閫昏緫 + var it = iterateJobs(jobs); + var res = it.next(); + + while (!res.done){ + var result = res.value; + // ... + res = it.next(); + } + + 3,閮ㄧ讲 Iterator 鎺ュ彛 + * 鍒╃敤 Generator 鍑芥暟,鍙互鍦ㄤ换鎰忓璞′笂閮ㄧ讲 Iterator 鎺ュ彛 + * 绠鍗,涓嶆兂鍐欎簡,灏辨槸鍐欎釜鍑芥暟,鍙互瀵逛换浣曞璞¤繘琛岃凯浠 + + 4,浣滀负鏁版嵁缁撴瀯 + * Generator 鍙互鐪嬩綔鏄暟鎹粨鏋,鏇寸‘鍒囧湴璇,鍙互鐪嬩綔鏄竴涓暟缁勭粨鏋,鍥犱负 Generator 鍑芥暟鍙互杩斿洖涓绯诲垪鐨勫,杩欐剰鍛崇潃瀹冨彲浠ュ浠绘剰琛ㄨ揪寮,鎻愪緵绫讳技鏁扮粍鐨勬帴鍙 + function* doStuff() { + yield fs.readFile.bind(null, 'hello.txt'); + yield fs.readFile.bind(null, 'world.txt'); + yield fs.readFile.bind(null, 'and-such.txt'); + } + * 涓婇潰浠g爜灏辨槸渚濇杩斿洖涓変釜鍑芥暟,浣嗘槸鐢变簬浣跨敤浜 Generator 鍑芥暟,瀵艰嚧鍙互鍍忓鐞嗘暟缁勯偅鏍,澶勭悊杩欎笁涓繑鍥炵殑鍑芥暟 + for (task of doStuff()) { + // task鏄竴涓嚱鏁帮紝鍙互鍍忓洖璋冨嚱鏁伴偅鏍蜂娇鐢ㄥ畠 + } + + * 濡傛灉鐢 ES5 琛ㄨ揪,瀹屽叏鍙互鐢ㄦ暟缁勬ā鎷 Generator 鐨勮繖绉嶇敤娉 + function doStuff() { + return [ + fs.readFile.bind(null, 'hello.txt'), + fs.readFile.bind(null, 'world.txt'), + fs.readFile.bind(null, 'and-such.txt') + ]; + } + + + diff --git "a/JavaSript/ES6/es6-Generator\345\207\275\346\225\260\347\232\204\345\274\202\346\255\245\345\272\224\347\224\250.js" "b/JavaSript/ES6/es6-Generator\345\207\275\346\225\260\347\232\204\345\274\202\346\255\245\345\272\224\347\224\250.js" new file mode 100644 index 00000000..e4593950 --- /dev/null +++ "b/JavaSript/ES6/es6-Generator\345\207\275\346\225\260\347\232\204\345\274\202\346\255\245\345\272\224\347\224\250.js" @@ -0,0 +1,12 @@ +----------------------------- +Generator 鍑芥暟鐨勫紓姝ュ簲鐢 | +----------------------------- + 1,浼犵粺鏂规硶 + 2,鍩烘湰姒傚康 + 3,Generator 鍑芥暟 + 4,Thunk 鍑芥暟 + 5,co 妯″潡 + + + # 璺 python 鐨 async,await 涓鏍 + * js鐢辩郴缁熻嚜鍔ㄧ殑鍦∟涓狿romise涔嬮棿鍒囨崲 diff --git "a/JavaSript/ES6/es6-Iterator\345\222\214for...of\345\276\252\347\216\257.js" "b/JavaSript/ES6/es6-Iterator\345\222\214for...of\345\276\252\347\216\257.js" new file mode 100644 index 00000000..4bf5adf5 --- /dev/null +++ "b/JavaSript/ES6/es6-Iterator\345\222\214for...of\345\276\252\347\216\257.js" @@ -0,0 +1,421 @@ +---------------------------- +Iterator | +---------------------------- + 1,Iterator(閬嶅巻鍣)鐨勬蹇 + 2,榛樿 Iterator 鎺ュ彛 + 3,璋冪敤 Iterator 鎺ュ彛鐨勫満鍚 + 4,瀛楃涓茬殑 Iterator 鎺ュ彛 + 5,Iterator 鎺ュ彛涓 Generator 鍑芥暟 + 6,閬嶅巻鍣ㄥ璞$殑 return(),throw() + 7,for...of 寰幆 + +---------------------------- +Iterator(閬嶅巻鍣)鐨勬蹇 | +---------------------------- + # Iterator 鐨勪綔鐢ㄦ湁涓変釜 + * 涓鏄负鍚勭鏁版嵁缁撴瀯,鎻愪緵涓涓粺涓鐨,绠渚跨殑璁块棶鎺ュ彛 + * 浜屾槸浣垮緱鏁版嵁缁撴瀯鐨勬垚鍛樿兘澶熸寜鏌愮娆″簭鎺掑垪 + * 涓夋槸 ES6 鍒涢犱簡涓绉嶆柊鐨勯亶鍘嗗懡浠or...of寰幆锛孖terator 鎺ュ彛涓昏渚沠or...of娑堣垂 + #Iterator 鐨勯亶鍘嗚繃绋嬫槸杩欐牱鐨 + 1,鍒涘缓涓涓寚閽堝璞★紝鎸囧悜褰撳墠鏁版嵁缁撴瀯鐨勮捣濮嬩綅缃備篃灏辨槸璇达紝閬嶅巻鍣ㄥ璞℃湰璐ㄤ笂,灏辨槸涓涓寚閽堝璞 + 2,绗竴娆¤皟鐢ㄦ寚閽堝璞$殑next鏂规硶锛屽彲浠ュ皢鎸囬拡鎸囧悜鏁版嵁缁撴瀯鐨勭涓涓垚鍛 + 3,绗簩娆¤皟鐢ㄦ寚閽堝璞$殑next鏂规硶锛屾寚閽堝氨鎸囧悜鏁版嵁缁撴瀯鐨勭浜屼釜鎴愬憳 + 4,涓嶆柇璋冪敤鎸囬拡瀵硅薄鐨刵ext鏂规硶锛岀洿鍒板畠鎸囧悜鏁版嵁缁撴瀯鐨勭粨鏉熶綅缃 + # 姣忎竴娆¤皟鐢╪ext鏂规硶,閮戒細杩斿洖鏁版嵁缁撴瀯鐨勫綋鍓嶆垚鍛樼殑淇℃伅 + # 鍏蜂綋鏉ヨ,灏辨槸杩斿洖涓涓寘鍚玽alue鍜宒one涓や釜灞炴х殑瀵硅薄 + * 鍏朵腑,value灞炴ф槸褰撳墠鎴愬憳鐨勫 + * done灞炴ф槸涓涓竷灏斿,琛ㄧず閬嶅巻鏄惁缁撴潫 + + # 妯℃嫙next鏂规硶杩斿洖鍊肩殑渚嬪瓙 + function makeIterator(array) { + var nextIndex = 0; + return { + next: function() { + if(nextIndex < array.length){ + return {value: array[nextIndex++], done: false}; + }else{ + return {value: undefined, done: true}; + } + } + }; + } + + var it = makeIterator(['a', 'b']); + + console.log(it.next()); // { value: "a", done: false } + console.log(it.next()); // { value: "b", done: false } + console.log(it.next()); // { value: undefined, done: true } + + * 瀵逛簬閬嶅巻鍣ㄥ璞℃潵璇,done: false鍜寁alue: undefined灞炴ч兘鏄彲浠ョ渷鐣ョ殑 + * 鍥犳涓婇潰鐨刴akeIterator鍑芥暟鍙互绠鍐欐垚涓嬮潰鐨勫舰寮 + + function makeIterator(array) { + var nextIndex = 0; + return { + next: function() { + if(nextIndex < array.length ){ + //鐪佺暐done,琛ㄧず杩樻湭瀹屾垚 + return {value: array[nextIndex++]} + }else{ + //鐪佺暐value,琛ㄧず鏈鍚庝竴涓厓绱 + return {done: true}; + } + } + }; + } + + # 鏃犻檺杩愯鐨勯亶鍘嗗櫒瀵硅薄鐨勪緥瀛 + function idMaker(){ + let id = 1; + return { + next:function(){ + return { + value:id++, + done:false + } + } + } + } + let it = idMaker(); + + console.log(it.next()); + console.log(it.next()); + console.log(it.next()); + console.log(it.next()); + console.log(it.next()); + + +---------------------------- +榛樿 Iterator 鎺ュ彛 | +---------------------------- + # 褰撲娇鐢╢or...of寰幆閬嶅巻鏌愮鏁版嵁缁撴瀯鏃,璇ュ惊鐜細鑷姩鍘诲鎵 Iterator 鎺ュ彛 + # 涓绉嶆暟鎹粨鏋勫彧瑕侀儴缃蹭簡 Iterator 鎺ュ彛,灏辩О杩欑鏁版嵁缁撴瀯鏄"鍙亶鍘嗙殑"(iterable) + # 榛樿鐨 Iterator 鎺ュ彛閮ㄧ讲鍦ㄦ暟鎹粨鏋勭殑Symbol.iterator灞炴, + * 鎴栬呰,涓涓暟鎹粨鏋勫彧瑕佸叿鏈塖ymbol.iterator灞炴,灏卞彲浠ヨ涓烘槸"鍙亶鍘" + * Symbol.iterator灞炴ф湰韬槸涓涓嚱鏁帮紝灏辨槸褰撳墠鏁版嵁缁撴瀯榛樿鐨勯亶鍘嗗櫒鐢熸垚鍑芥暟,鎵ц杩欎釜鍑芥暟,灏变細杩斿洖涓涓亶鍘嗗櫒 + # 鍘熺敓鍏峰 Iterator 鎺ュ彛鐨勬暟鎹粨鏋勫涓 + Array + Map + Set + String + TypedArray + 鍑芥暟鐨 arguments 瀵硅薄 + NodeList 瀵硅薄 + + * Array 鐨 iterator + let arr = [1,2]; + it = arr[Symbol.iterator]() + + console.log(it.next()) //{value: 1, done: false} + console.log(it.next()) //{value: 2, done: false} + console.log(it.next()) //{value: undefined, done: true} + + # 鍏朵粬鏁版嵁缁撴瀯, Iterator 鎺ュ彛锛岄兘闇瑕佽嚜宸卞湪Symbol.iterator灞炴т笂闈㈤儴缃,杩欐牱鎵嶄細琚玣or...of寰幆閬嶅巻 + * 鍘熷瀷閾句笂鐨勫璞″叿鏈夎鏂规硶涔熷彲 + let me = { + name:'KevinBlandy', + age:23, + skill:['java','Python','Javascript'], + [Symbol.iterator](){ + let i = -1; + let keys = Reflect.ownKeys(this); + let _obj = this; + return { + next:function(){ + i += 1; + if(i < keys.length){ + return {value:{[keys[i]]:Reflect.get(_obj,keys[i])}} + }else{ + return {done:true} + } + } + } + } + } + for(let x of me){ + console.log(x) + } + //{name: "KevinBlandy"} + //{age: 23} + //{skill: Array(3)} + //{Symbol(Symbol.iterator): 茠} + + # 閮ㄧ讲 Iterator 鎺ュ彛,鏈変竴涓畝渚挎柟娉,灏辨槸Symbol.iterator鏂规硶鐩存帴寮曠敤鏁扮粍鐨 Iterator 鎺ュ彛 + * 绫讳技鏁扮粍鐨勫璞¤皟鐢ㄦ暟缁勭殑Symbol.iterator鏂规硶 + let iterable = { + 0: 'a', + 1: 'b', + 2: 'c', + length: 3, + [Symbol.iterator]: Array.prototype[Symbol.iterator] + }; + for (let item of iterable) { + console.log(item); // 'a', 'b', 'c' + } + + * 鏅氬璞¢儴缃叉暟缁勭殑Symbol.iterator鏂规硶,骞舵棤鏁堟灉 + let iterable = { + a: 'a', + b: 'b', + c: 'c', + length: 3, + [Symbol.iterator]: Array.prototype[Symbol.iterator] + }; + for (let item of iterable) { + console.log(item); // undefined, undefined, undefined + } + + # 濡傛灉Symbol.iterator鏂规硶瀵瑰簲鐨勪笉鏄亶鍘嗗櫒鐢熸垚鍑芥暟(鍗充細杩斿洖涓涓亶鍘嗗櫒瀵硅薄),瑙i噴寮曟搸灏嗕細鎶ラ敊 + var obj = {}; + obj[Symbol.iterator] = () => 1; + [...obj] // TypeError: [] is not a function + + # 閬嶅巻鍣ㄦ帴鍙,鏁版嵁缁撴瀯灏卞彲浠ョ敤for...of寰幆閬嶅巻,涔熷彲浠ヤ娇鐢╳hile寰幆閬嶅巻 + var $iterator = ITERABLE[Symbol.iterator](); + var $result = $iterator.next(); + while (!$result.done) { + var x = $result.value; + // ... + $result = $iterator.next(); + } + * ITERABLE浠h〃鏌愮鍙亶鍘嗙殑鏁版嵁缁撴瀯,$iterator鏄畠鐨勯亶鍘嗗櫒瀵硅薄 + * 閬嶅巻鍣ㄥ璞℃瘡娆$Щ鍔ㄦ寚閽(next鏂规硶),閮芥鏌ヤ竴涓嬭繑鍥炲肩殑done灞炴,濡傛灉閬嶅巻杩樻病缁撴潫,灏辩Щ鍔ㄩ亶鍘嗗櫒瀵硅薄鐨勬寚閽堝埌涓嬩竴姝(next鏂规硶),涓嶆柇寰幆 + +---------------------------- +璋冪敤 Iterator 鎺ュ彛鐨勫満鍚 | +---------------------------- + 1,瑙f瀯璧嬪 + * 瀵规暟缁勫拰 Set 缁撴瀯杩涜瑙f瀯璧嬪兼椂,浼氶粯璁よ皟鐢⊿ymbol.iterator鏂规硶 + let set = new Set().add('a').add('b').add('c'); + let [x,y] = set; + // x='a'; y='b' + let [first, ...rest] = set; + // first='a'; rest=['b','c']; + + 2,鎵╁睍杩愮畻绗 + * 鎵╁睍杩愮畻绗(...)涔熶細璋冪敤榛樿鐨 Iterator 鎺ュ彛 + // 渚嬩竴 + var str = 'hello'; + [...str] // ['h','e','l','l','o'] + + // 渚嬩簩 + let arr = ['b', 'c']; + ['a', ...arr, 'd'] + // ['a', 'b', 'c', 'd'] + + * 瀹為檯涓,杩欐彁渚涗簡涓绉嶇畝渚挎満鍒,鍙互灏嗕换浣曢儴缃蹭簡 Iterator 鎺ュ彛鐨勬暟鎹粨鏋,杞负鏁扮粍 + * 涔熷氨鏄,鍙鏌愪釜鏁版嵁缁撴瀯閮ㄧ讲浜 Iterator 鎺ュ彛,灏卞彲浠ュ瀹冧娇鐢ㄦ墿灞曡繍绠楃,灏嗗叾杞负鏁扮粍 + let arr = [...iterable]; + + 3,yield* + * yield*鍚庨潰璺熺殑鏄竴涓彲閬嶅巻鐨勭粨鏋,瀹冧細璋冪敤璇ョ粨鏋勭殑閬嶅巻鍣ㄦ帴鍙 + + let generator = function* () { + yield 1; + yield* [2,3,4]; + yield 5; + }; + + var iterator = generator(); + + iterator.next() // { value: 1, done: false } + iterator.next() // { value: 2, done: false } + iterator.next() // { value: 3, done: false } + iterator.next() // { value: 4, done: false } + iterator.next() // { value: 5, done: false } + iterator.next() // { value: undefined, done: true } + + 4,鍏朵粬鍦哄悎 + * 鐢变簬鏁扮粍鐨勯亶鍘嗕細璋冪敤閬嶅巻鍣ㄦ帴鍙,鎵浠ヤ换浣曟帴鍙楁暟缁勪綔涓哄弬鏁扮殑鍦哄悎,鍏跺疄閮借皟鐢ㄤ簡閬嶅巻鍣ㄦ帴鍙 + for...of + Array.from() + Map(), Set(), WeakMap(), WeakSet() + (姣斿new Map([['a',1],['b',2]])) + Promise.all() + Promise.race() + +---------------------------- +瀛楃涓茬殑 Iterator 鎺ュ彛 | +---------------------------- + # 瀛楃涓叉槸涓涓被浼兼暟缁勭殑瀵硅薄,涔熷師鐢熷叿鏈 Iterator 鎺ュ彛 + var someString = "hi"; + + typeof someString[Symbol.iterator] // "function" + + var iterator = someString[Symbol.iterator](); + + iterator.next() // { value: "h", done: false } + iterator.next() // { value: "i", done: false } + iterator.next() // { value: undefined, done: true } + + # 鍙互瑕嗙洊鍘熺敓鐨凷ymbol.iterator鏂规硶,杈惧埌淇敼閬嶅巻鍣ㄨ涓虹殑鐩殑 + var str = new String("hi"); + [...str] // ["h", "i"] + str[Symbol.iterator] = function() { + return { + next: function() { + if (this._first) { + this._first = false; + return { value: "bye", done: false }; + } else { + return { done: true }; + } + }, + _first: true + }; + }; + [...str] // ["bye"] + str // "hi" + +-------------------------------- +Iterator 鎺ュ彛涓 Generator 鍑芥暟 | +-------------------------------- + # Symbol.iterator鏂规硶鐨勬渶绠鍗曞疄鐜,鏄娇鐢 Generator 鍑芥暟 + let myIterable = { + [Symbol.iterator]: function* () { + yield 1; + yield 2; + yield 3; + } + } + [...myIterable] // [1, 2, 3] + + // 鎴栬呴噰鐢ㄤ笅闈㈢殑绠娲佸啓娉 + let obj = { + * [Symbol.iterator]() { + yield 'hello'; + yield 'world'; + } + }; + + for (let x of obj) { + console.log(x); + } + // "hello" + // "world" + + +-------------------------------- +閬嶅巻鍣ㄥ璞$殑 return(),throw() | +-------------------------------- + # 閬嶅巻鍣ㄥ璞¢櫎浜嗗叿鏈塶ext鏂规硶,杩樺彲浠ュ叿鏈塺eturn鏂规硶鍜宼hrow鏂规硶(鍙) + # return鏂规硶鐨勪娇鐢ㄥ満鍚堟槸,濡傛灉for...of寰幆鎻愬墠閫鍑(閫氬父鏄洜涓哄嚭閿,鎴栬呮湁break璇彞鎴朿ontinue璇彞,灏变細璋冪敤return鏂规硶 + * 濡傛灉涓涓璞″湪瀹屾垚閬嶅巻鍓,闇瑕佹竻鐞嗘垨閲婃斁璧勬簮,灏卞彲浠ラ儴缃瞨eturn鏂规硶 + + function readLinesSync(file) { + return { + [Symbol.iterator]() { + return { + next() { + return { done: false }; + }, + return() { + file.close(); + return { done: true }; + } + }; + }, + }; + } + + * 鍑芥暟readLinesSync鎺ュ彈涓涓枃浠跺璞′綔涓哄弬鏁,杩斿洖涓涓亶鍘嗗櫒瀵硅薄,鍏朵腑闄や簡next鏂规硶,杩橀儴缃蹭簡return鏂规硶 + * 涓嬮潰鐨勪笁绉嶆儏鍐,閮戒細瑙﹀彂鎵цreturn鏂规硶 + + // 鎯呭喌涓 杈撳嚭鏂囦欢鐨勭涓琛屼互鍚,灏变細鎵цreturn鏂规硶,鍏抽棴杩欎釜鏂囦欢 + for (let line of readLinesSync(fileName)) { + console.log(line); + break; + } + + // 鎯呭喌浜 鍑烘墍鏈夎浠ュ悗,鎵цreturn鏂规硶,鍏抽棴璇ユ枃浠 + for (let line of readLinesSync(fileName)) { + console.log(line); + continue; + } + + // 鎯呭喌涓 浼氬湪鎵цreturn鏂规硶鍏抽棴鏂囦欢涔嬪悗,鍐嶆姏鍑洪敊璇 + for (let line of readLinesSync(fileName)) { + console.log(line); + throw new Error(); + } + + * throw鏂规硶涓昏鏄厤鍚 Generator 鍑芥暟浣跨敤,涓鑸殑閬嶅巻鍣ㄥ璞$敤涓嶅埌杩欎釜鏂规硶 + +-------------------------------- +for...of 寰幆 | +-------------------------------- + # 涓涓暟鎹粨鏋勫彧瑕侀儴缃蹭簡Symbol.iterator灞炴,灏辫瑙嗕负鍏锋湁 iterator 鎺ュ彛,灏卞彲浠ョ敤for...of寰幆閬嶅巻瀹冪殑鎴愬憳 + # 涔熷氨鏄,for...of寰幆鍐呴儴璋冪敤鐨勬槸鏁版嵁缁撴瀯鐨凷ymbol.iterator鏂规硶銆 + # for...of寰幆鍙互浣跨敤鐨勮寖鍥村寘鎷暟缁,Set 鍜 Map 缁撴瀯,鏌愪簺绫讳技鏁扮粍鐨勫璞(姣斿arguments瀵硅薄DOM NodeList 瀵硅薄),鍚庢枃鐨 Generator 瀵硅薄,浠ュ強瀛楃涓 + + # 鏁扮粍 + * 鏁扮粍鍘熺敓鍏峰iterator鎺ュ彛(鍗抽粯璁ら儴缃蹭簡Symbol.iterator灞炴),for...of寰幆鏈川涓婂氨鏄皟鐢ㄨ繖涓帴鍙d骇鐢熺殑閬嶅巻鍣 + const arr = ['red', 'green', 'blue']; + for(let v of arr) { + console.log(v); // red green blue + } + const obj = {}; + obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr); + for(let v of obj) { + console.log(v); // red green blue + } + + # Set 鍜 Map 缁撴瀯 + * Set 鍜 Map 缁撴瀯涔熷師鐢熷叿鏈 Iterator 鎺ュ彛,鍙互鐩存帴浣跨敤for...of寰幆 + var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]); + for (var e of engines) { + console.log(e); + } + // Gecko + // Trident + // Webkit + + var es6 = new Map(); + es6.set("edition", 6); + es6.set("committee", "TC39"); + es6.set("standard", "ECMA-262"); + for (var [name, value] of es6) { + console.log(name + ": " + value); + } + // edition: 6 + // committee: TC39 + // standard: ECMA-262 + + # 璁$畻鐢熸垚鐨勬暟鎹粨鏋 + # 绫讳技鏁扮粍鐨勫璞 + * 绫讳技鏁扮粍鐨勫璞″寘鎷ソ鍑犵被 + * 骞朵笉鏄墍鏈夌被浼兼暟缁勭殑瀵硅薄閮藉叿鏈 Iterator 鎺ュ彛,涓涓畝渚跨殑瑙e喅鏂规硶,灏辨槸浣跨敤Array.from鏂规硶灏嗗叾杞负鏁扮粍 + let arrayLike = { length: 2, 0: 'a', 1: 'b' }; + // 鎶ラ敊 + for (let x of arrayLike) { + console.log(x); + } + + // 姝g‘ + for (let x of Array.from(arrayLike)) { + console.log(x); + } + # 瀵硅薄 + * 瀵逛簬鏅氱殑瀵硅薄锛宖or...of缁撴瀯涓嶈兘鐩存帴浣跨敤,浼氭姤閿,蹇呴』閮ㄧ讲浜 Iterator 鎺ュ彛鍚庢墠鑳戒娇鐢 + * 涓绉嶈В鍐虫柟娉曟槸,浣跨敤Object.keys鏂规硶灏嗗璞$殑閿悕鐢熸垚涓涓暟缁,鐒跺悗閬嶅巻杩欎釜鏁扮粍 + for (var key of Object.keys(someObject)) { + console.log(key + ': ' + someObject[key]); + } + * 鍙︿竴涓柟娉曟槸浣跨敤 Generator 鍑芥暟灏嗗璞¢噸鏂板寘瑁呬竴涓 + function* entries(obj) { + for (let key of Object.keys(obj)) { + yield [key, obj[key]]; + } + } + + for (let [key, value] of entries(obj)) { + console.log(key, '->', value); + } + // a -> 1 + // b -> 2 + +-------------------------------- +涓巉or in 姣旇緝 | +-------------------------------- + # for ..in 鏄敤鏉ラ亶鍘嗗璞″睘鎬 + # for of 渚濊禆浜 Symbol.iterator,鐢ㄦ潵閬嶅巻鎴愬憳 \ No newline at end of file diff --git "a/JavaSript/ES6/es6-Module\347\232\204\345\212\240\350\275\275\345\256\236\347\216\260.js" "b/JavaSript/ES6/es6-Module\347\232\204\345\212\240\350\275\275\345\256\236\347\216\260.js" new file mode 100644 index 00000000..28fc85c0 --- /dev/null +++ "b/JavaSript/ES6/es6-Module\347\232\204\345\212\240\350\275\275\345\256\236\347\216\260.js" @@ -0,0 +1,71 @@ +-------------------------------- +Module 鐨勫姞杞藉疄鐜 | +-------------------------------- + 1,娴忚鍣ㄥ姞杞 + 2,ES6 妯″潡涓 CommonJS 妯″潡鐨勫樊寮 + 3,Node 鍔犺浇 + 4,寰幆鍔犺浇 + 5,ES6 妯″潡鐨勮浆鐮 + 6,涓婁竴绔犱粙缁嶄簡妯″潡鐨勮娉,鏈珷浠嬬粛濡備綍鍦ㄦ祻瑙堝櫒鍜 Node 涔嬩腑鍔犺浇 ES6 妯″潡,浠ュ強瀹為檯寮鍙戜腑缁忓父閬囧埌鐨勪竴浜涢棶棰(姣斿寰幆鍔犺浇) + +-------------------------------- +娴忚鍣ㄥ姞杞 | +-------------------------------- + # HTML 缃戦〉涓,娴忚鍣ㄩ氳繃 + + + + + * 娴忚鍣ㄦ槸鍚屾鍔犺浇 JavaScript 鑴氭湰,鍗虫覆鏌撳紩鎿庨亣鍒 + + + * defer瑕佺瓑鍒版暣涓〉闈㈠湪鍐呭瓨涓甯告覆鏌撶粨鏉(DOM 缁撴瀯瀹屽叏鐢熸垚,浠ュ強鍏朵粬鑴氭湰鎵ц瀹屾垚),鎵嶄細鎵ц + 'defer鏄覆鏌撳畬鍐嶆墽琛' + * async涓鏃︿笅杞藉畬,娓叉煋寮曟搸灏变細涓柇娓叉煋,鎵ц杩欎釜鑴氭湰浠ュ悗,鍐嶇户缁覆鏌 + 'async鏄笅杞藉畬灏辨墽琛' + + * 濡傛灉鏈夊涓猟efer鑴氭湰,浼氭寜鐓у畠浠湪椤甸潰鍑虹幇鐨勯『搴忓姞杞,鑰屽涓猘sync鑴氭湰鏄笉鑳戒繚璇佸姞杞介『搴忕殑 + + + # 鍔犺浇瑙勫垯 + * 瑙堝櫒鍔犺浇 ES6 妯″潡,涔熶娇鐢 + + * foo.js 鏈夊嚑鐐归渶瑕佹敞鎰 + + 1,浠g爜鏄湪妯″潡浣滅敤鍩熶箣涓繍琛,鑰屼笉鏄湪鍏ㄥ眬浣滅敤鍩熻繍琛,妯″潡鍐呴儴鐨勯《灞傚彉閲,澶栭儴涓嶅彲瑙 + 2,妯″潡鑴氭湰鑷姩閲囩敤涓ユ牸妯″紡,涓嶇鏈夋病鏈夊0鏄巙se strict + 3,妯″潡涔嬩腑,鍙互浣跨敤import鍛戒护鍔犺浇鍏朵粬妯″潡(.js鍚庣紑涓嶅彲鐪佺暐,闇瑕佹彁渚涚粷瀵 URL 鎴栫浉瀵 URL),涔熷彲浠ヤ娇鐢╡xport鍛戒护杈撳嚭瀵瑰鎺ュ彛 + 4,妯″潡涔嬩腑,椤跺眰鐨則his鍏抽敭瀛楄繑鍥瀠ndefined,鑰屼笉鏄寚鍚憌indow,涔熷氨鏄,鍦ㄦā鍧楅《灞備娇鐢╰his鍏抽敭瀛,鏄棤鎰忎箟鐨 + * 鍒╃敤椤跺眰鐨則his绛変簬undefined杩欎釜璇硶鐐,鍙互渚︽祴褰撳墠浠g爜鏄惁鍦 ES6 妯″潡涔嬩腑 + const isNotModuleScript = this !== undefined; + 5,鍚屼竴涓ā鍧楀鏋滃姞杞藉娆,灏嗗彧鎵ц涓娆 + + * 娴忚鍣ㄥ浜庡甫鏈塼ype="module"鐨 + + + * 濡傛灉缃戦〉鏈夊涓 + + * 涓鏃︿娇鐢ㄤ簡async灞炴, + diff --git "a/JavaSript/ES6/es6-Module\350\257\255\346\263\225.js" "b/JavaSript/ES6/es6-Module\350\257\255\346\263\225.js" new file mode 100644 index 00000000..3ebb2229 --- /dev/null +++ "b/JavaSript/ES6/es6-Module\350\257\255\346\263\225.js" @@ -0,0 +1,559 @@ +---------------------------- +Module 鐨勮娉 | +---------------------------- + 1,姒傝堪 + 2,涓ユ牸妯″紡 + 3,export 鍛戒护 + 4,import 鍛戒护 + 5,妯″潡鐨勬暣浣撳姞杞 + 6,export default 鍛戒护 + 7,export 涓 import 鐨勫鍚堝啓娉 + 8,妯″潡鐨勭户鎵 + 9,璺ㄦā鍧楀父閲 + 10,import() + +---------------------------- +姒傝堪 | +---------------------------- + # 鍦 ES6 涔嬪墠,绀惧尯鍒跺畾浜嗕竴浜涙ā鍧楀姞杞芥柟妗,鏈涓昏鐨勬湁 CommonJS 鍜 AMD 涓ょ + * 鍓嶈呯敤浜庢湇鍔″櫒,鍚庤呯敤浜庢祻瑙堝櫒 + + # ES6 鍦ㄨ瑷鏍囧噯鐨勫眰闈笂,瀹炵幇浜嗘ā鍧楀姛鑳,鑰屼笖瀹炵幇寰楃浉褰撶畝鍗,瀹屽叏鍙互鍙栦唬 CommonJS 鍜 AMD 瑙勮寖,鎴愪负娴忚鍣ㄥ拰鏈嶅姟鍣ㄩ氱敤鐨勬ā鍧楄В鍐虫柟妗 + + # ES6 妯″潡鐨勮璁℃濇兂鏄敖閲忕殑闈欐佸寲,浣垮緱缂栬瘧鏃跺氨鑳界‘瀹氭ā鍧楃殑渚濊禆鍏崇郴,浠ュ強杈撳叆鍜岃緭鍑虹殑鍙橀噺 + * CommonJS 鍜 AMD 妯″潡,閮藉彧鑳藉湪杩愯鏃剁‘瀹氳繖浜涗笢瑗 + * 姣斿,CommonJS 妯″潡灏辨槸瀵硅薄,杈撳叆鏃跺繀椤绘煡鎵惧璞″睘鎬 + // CommonJS妯″潡 + let { stat, exists, readFile } = require('fs'); + + // 绛夊悓浜 + let _fs = require('fs'); + let stat = _fs.stat; + let exists = _fs.exists; + let readfile = _fs.readfile; + + * 鏁翠綋鍔犺浇fs妯″潡(鍗冲姞杞絝s鐨勬墍鏈夋柟娉),鐢熸垚涓涓璞(_fs) + * 鐒跺悗鍐嶄粠杩欎釜瀵硅薄涓婇潰璇诲彇 3 涓柟娉,杩欑鍔犺浇绉颁负"杩愯鏃跺姞杞",鍥犱负鍙湁杩愯鏃舵墠鑳藉緱鍒拌繖涓璞,瀵艰嚧瀹屽叏娌″姙娉曞湪缂栬瘧鏃跺仛"闈欐佷紭鍖" + + # ES6 妯″潡涓嶆槸瀵硅薄,鑰屾槸閫氳繃 export 鍛戒护鏄惧紡鎸囧畾杈撳嚭鐨勪唬鐮,鍐嶉氳繃 impor t鍛戒护杈撳叆 + // ES6妯″潡 + import { stat, exists, readFile } from 'fs'; + + * 浠巉s妯″潡鍔犺浇 3 涓柟娉,鍏朵粬鏂规硶涓嶅姞杞 + * 杩欑鍔犺浇绉颁负"缂栬瘧鏃跺姞杞"鎴栬呴潤鎬佸姞杞,鍗 ES6 鍙互鍦ㄧ紪璇戞椂灏卞畬鎴愭ā鍧楀姞杞,鏁堢巼瑕佹瘮 CommonJS 妯″潡鐨勫姞杞芥柟寮忛珮 + * 褰撶劧,杩欎篃瀵艰嚧浜嗘病娉曞紩鐢 ES6 妯″潡鏈韩,鍥犱负瀹冧笉鏄璞 + + # 闄や簡闈欐佸姞杞藉甫鏉ョ殑鍚勭濂藉,ES6 妯″潡杩樻湁浠ヤ笅濂藉 + * 涓嶅啀闇瑕乁MD妯″潡鏍煎紡浜,灏嗘潵鏈嶅姟鍣ㄥ拰娴忚鍣ㄩ兘浼氭敮鎸 ES6 妯″潡鏍煎紡(鐩墠,閫氳繃鍚勭宸ュ叿搴,鍏跺疄宸茬粡鍋氬埌浜嗚繖涓鐐) + * 灏嗘潵娴忚鍣ㄧ殑鏂 API 灏辫兘鐢ㄦā鍧楁牸寮忔彁渚,涓嶅啀蹇呴』鍋氭垚鍏ㄥ眬鍙橀噺鎴栬卬avigator瀵硅薄鐨勫睘鎬 + * 涓嶅啀闇瑕佸璞′綔涓哄懡鍚嶇┖闂(姣斿Math瀵硅薄),鏈潵杩欎簺鍔熻兘鍙互閫氳繃妯″潡鎻愪緵 + +---------------------------- +涓ユ牸妯″紡 | +---------------------------- + # ES6 鐨勬ā鍧楄嚜鍔ㄩ噰鐢ㄤ弗鏍兼ā寮,涓嶇浣犳湁娌℃湁鍦ㄦā鍧楀ご閮ㄥ姞涓"use strict" + # 涓ユ牸妯″紡涓昏鏈変互涓嬮檺鍒 + * 鍙橀噺蹇呴』澹版槑鍚庡啀浣跨敤 + * 鍑芥暟鐨勫弬鏁颁笉鑳芥湁鍚屽悕灞炴,鍚﹀垯鎶ラ敊 + * 涓嶈兘浣跨敤with璇彞 + * 涓嶈兘瀵瑰彧璇诲睘鎬ц祴鍊,鍚﹀垯鎶ラ敊 + * 涓嶈兘浣跨敤鍓嶇紑 0 琛ㄧず鍏繘鍒舵暟,鍚﹀垯鎶ラ敊 + * 涓嶈兘鍒犻櫎涓嶅彲鍒犻櫎鐨勫睘鎬,鍚﹀垯鎶ラ敊 + * 涓嶈兘鍒犻櫎鍙橀噺delete prop,浼氭姤閿,鍙兘鍒犻櫎灞炴elete global[prop] + * eval涓嶄細鍦ㄥ畠鐨勫灞備綔鐢ㄥ煙寮曞叆鍙橀噺 + * eval鍜宎rguments涓嶈兘琚噸鏂拌祴鍊 + * arguments涓嶄細鑷姩鍙嶆槧鍑芥暟鍙傛暟鐨勫彉鍖 + * 涓嶈兘浣跨敤arguments.callee + * 涓嶈兘浣跨敤arguments.caller + * 绂佹this鎸囧悜鍏ㄥ眬瀵硅薄 + * 涓嶈兘浣跨敤fn.caller鍜宖n.arguments鑾峰彇鍑芥暟璋冪敤鐨勫爢鏍 + * 澧炲姞浜嗕繚鐣欏瓧(姣斿protected銆乻tatic鍜宨nterface) + + # 灏ゅ叾闇瑕佹敞鎰弔his鐨勯檺鍒,ES6 妯″潡涔嬩腑,椤跺眰鐨則his鎸囧悜undefined,鍗充笉搴旇鍦ㄩ《灞備唬鐮佷娇鐢╰his + +---------------------------- +export 鍛戒护 | +---------------------------- + # 妯″潡鍔熻兘涓昏鐢变袱涓懡浠ゆ瀯鎴,export鍜宨mport + * export鍛戒护鐢ㄤ簬瑙勫畾妯″潡鐨勫澶栨帴鍙 + * import鍛戒护鐢ㄤ簬杈撳叆鍏朵粬妯″潡鎻愪緵鐨勫姛鑳 + + # 涓涓ā鍧楀氨鏄竴涓嫭绔嬬殑鏂囦欢,璇ユ枃浠跺唴閮ㄧ殑鎵鏈夊彉閲,澶栭儴鏃犳硶鑾峰彇,濡傛灉甯屾湜澶栭儴鑳藉璇诲彇妯″潡鍐呴儴鐨勬煇涓彉閲忓氨蹇呴』浣跨敤export鍏抽敭瀛楄緭鍑鸿鍙橀噺 + * 浣跨敤export鍛戒护杈撳嚭鍙橀噺 + // profile.js + export var firstName = 'Michael'; + export var lastName = 'Jackson'; + export var year = 1958; + + * 杩樻湁鍙﹀涓绉 + // profile.js + var firstName = 'Michael'; + var lastName = 'Jackson'; + var year = 1958; + + export {firstName, lastName, year}; + + * 浣跨敤澶ф嫭鍙锋寚瀹氭墍瑕佽緭鍑虹殑涓缁勫彉閲,瀹冧笌鍓嶄竴绉嶅啓娉(鐩存帴鏀剧疆鍦╲ar璇彞鍓)鏄瓑浠风殑 + * 浣嗘槸搴旇浼樺厛鑰冭檻浣跨敤杩欑鍐欐硶,鍥犱负杩欐牱灏卞彲浠ュ湪鑴氭湰灏鹃儴,涓鐪肩湅娓呮杈撳嚭浜嗗摢浜涘彉閲 + + * export鍛戒护闄や簡杈撳嚭鍙橀噺,杩樺彲浠ヨ緭鍑哄嚱鏁版垨绫(class) + export function multiply(x, y) { //瀵瑰杈撳嚭涓涓嚱鏁癿ultiply + return x * y; + }; + + * export杈撳嚭鐨勫彉閲忓氨鏄湰鏉ョ殑鍚嶅瓧,浣嗘槸鍙互浣跨敤as鍏抽敭瀛楅噸鍛藉悕 + function v1() { ... } + function v2() { ... } + + export { + v1 as streamV1, + v2 as streamV2, //浣跨敤as鍏抽敭瀛,閲嶅懡鍚嶄簡鍑芥暟v1鍜寁2鐨勫澶栨帴鍙,閲嶅懡鍚嶅悗,v2鍙互鐢ㄤ笉鍚岀殑鍚嶅瓧杈撳嚭涓ゆ + v2 as streamLatestVersion + }; + + * export鍛戒护瑙勫畾鐨勬槸瀵瑰鐨勬帴鍙,蹇呴』涓庢ā鍧楀唴閮ㄧ殑鍙橀噺寤虹珛涓涓瀵瑰簲鍏崇郴 + // 鎶ラ敊 + export 1; + + // 鎶ラ敊 + var m = 1; + export m; + + * 涓ょ鍐欐硶閮戒細鎶ラ敊,鍥犱负娌℃湁鎻愪緵瀵瑰鐨勬帴鍙 + * 绗竴绉嶅啓娉曠洿鎺ヨ緭鍑 1 + * 绗簩绉嶅啓娉曢氳繃鍙橀噺m,杩樻槸鐩存帴杈撳嚭 1,1鍙槸涓涓,涓嶆槸鎺ュ彛 + + * function鍜宑lass鐨勮緭鍑,涔熷繀椤婚伒瀹堣繖鏍风殑鍐欐硶 + // 鎶ラ敊 + function f() {} + export f; + + // 姝g‘ + export function f() {}; + + // 姝g‘ + function f() {} + export {f}; + + * export璇彞杈撳嚭鐨勬帴鍙,涓庡叾瀵瑰簲鐨勫兼槸鍔ㄦ佺粦瀹氬叧绯,鍗抽氳繃璇ユ帴鍙,鍙互鍙栧埌妯″潡鍐呴儴瀹炴椂鐨勫 + export var foo = 'bar'; + setTimeout(() => foo = 'baz', 500); //杈撳嚭鍙橀噺foo,鍊间负bar,500 姣涔嬪悗鍙樻垚baz + + * export鍛戒护鍙互鍑虹幇鍦ㄦā鍧楃殑浠讳綍浣嶇疆,鍙澶勪簬妯″潡椤跺眰灏卞彲浠,濡傛灉澶勪簬鍧楃骇浣滅敤鍩熷唴,灏变細鎶ラ敊 + * 涓嬩竴鑺傜殑import鍛戒护涔熸槸濡傛 + * 杩欐槸鍥犱负澶勪簬鏉′欢浠g爜鍧椾箣涓,灏辨病娉曞仛闈欐佷紭鍖栦簡,杩濊儗浜 ES6 妯″潡鐨勮璁″垵琛 + function foo() { + export default 'bar' // 浠g爜鍧 SyntaxError + } + foo() + +---------------------------- +import 鍛戒护 | +---------------------------- + # 浣跨敤export鍛戒护瀹氫箟浜嗘ā鍧楃殑瀵瑰鎺ュ彛浠ュ悗,鍏朵粬 JS 鏂囦欢灏卞彲浠ラ氳繃import鍛戒护鍔犺浇杩欎釜妯″潡 + // main.js + import {firstName, lastName, year} from './profile.js'; + + function setName(element) { + element.textContent = firstName + ' ' + lastName; + } + + * import鍛戒护,鐢ㄤ簬鍔犺浇profile.js鏂囦欢,骞朵粠涓緭鍏ュ彉閲 + * import鍛戒护鎺ュ彈涓瀵瑰ぇ鎷彿,閲岄潰鎸囧畾瑕佷粠鍏朵粬妯″潡瀵煎叆鐨勫彉閲忓悕 + * 澶ф嫭鍙烽噷闈㈢殑鍙橀噺鍚,蹇呴』涓庤瀵煎叆妯″潡(profile.js)瀵瑰鎺ュ彛鐨勫悕绉扮浉鍚 + + # 涓鸿緭鍏ョ殑鍙橀噺閲嶆柊鍙栦竴涓悕瀛,import鍛戒护瑕佷娇鐢╝s鍏抽敭瀛,灏嗚緭鍏ョ殑鍙橀噺閲嶅懡鍚 + import { lastName as surname } from './profile.js'; + + # import鍛戒护杈撳叆鐨勫彉閲忛兘鏄彧璇荤殑,鍥犱负瀹冪殑鏈川鏄緭鍏ユ帴鍙 + * 涔熷氨鏄,涓嶅厑璁稿湪鍔犺浇妯″潡鐨勮剼鏈噷闈,鏀瑰啓鎺ュ彛 + import {a} from './xxx.js' + + a = {}; // Syntax Error : 'a' is read-only; + //濡傛灉a鏄竴涓璞,鏀瑰啓a鐨勫睘鎬ф槸鍏佽鐨 + + * a鐨勫睘鎬у彲浠ユ垚鍔熸敼鍐,骞朵笖鍏朵粬妯″潡涔熷彲浠ヨ鍒版敼鍐欏悗鐨勫 + + # import鍚庨潰鐨刦rom鎸囧畾妯″潡鏂囦欢鐨勪綅缃,鍙互鏄浉瀵硅矾寰,涔熷彲浠ユ槸缁濆璺緞 + * .js鍚庣紑鍙互鐪佺暐,濡傛灉鍙槸妯″潡鍚,涓嶅甫鏈夎矾寰,閭d箞蹇呴』鏈夐厤缃枃浠,鍛婅瘔 JavaScript 寮曟搸璇ユā鍧楃殑浣嶇疆 + import {myMethod} from 'util'; + //util鏄ā鍧楁枃浠跺悕,鐢变簬涓嶅甫鏈夎矾寰,蹇呴』閫氳繃閰嶇疆,鍛婅瘔寮曟搸鎬庝箞鍙栧埌杩欎釜妯″潡 + + # import鍛戒护鍏锋湁鎻愬崌鏁堟灉,浼氭彁鍗囧埌鏁翠釜妯″潡鐨勫ご閮,棣栧厛鎵ц + foo(); + + import { foo } from 'my_module'; + + * 涓嶄細鎶ラ敊,鍥犱负import鐨勬墽琛屾棭浜巉oo鐨勮皟鐢 + * 杩欑琛屼负鐨勬湰璐ㄦ槸,import鍛戒护鏄紪璇戦樁娈垫墽琛岀殑,鍦ㄤ唬鐮佽繍琛屼箣鍓 + + # 鐢变簬import鏄潤鎬佹墽琛,鎵浠ヤ笉鑳戒娇鐢ㄨ〃杈惧紡鍜屽彉閲,杩欎簺鍙湁鍦ㄨ繍琛屾椂鎵嶈兘寰楀埌缁撴灉鐨勮娉曠粨鏋 + // 鎶ラ敊 + import { 'f' + 'oo' } from 'my_module'; + + // 鎶ラ敊 + let module = 'my_module'; + import { foo } from module; + + // 鎶ラ敊 + if (x === 1) { + import { foo } from 'module1'; + } else { + import { foo } from 'module2'; + } + + * 涓夌鍐欐硶閮戒細鎶ラ敊,鍥犱负瀹冧滑鐢ㄥ埌浜嗚〃杈惧紡,鍙橀噺鍜宨f缁撴瀯 + * 鍦ㄩ潤鎬佸垎鏋愰樁娈,杩欎簺璇硶閮芥槸娌℃硶寰楀埌鍊肩殑 + + # import璇彞浼氭墽琛屾墍鍔犺浇鐨勬ā鍧,鍥犳鍙互鏈変笅闈㈢殑鍐欐硶 + import 'lodash'; + //涓婇潰浠g爜浠呬粎鎵цlodash妯″潡,浣嗘槸涓嶈緭鍏ヤ换浣曞 + + * 濡傛灉澶氭閲嶅鎵ц鍚屼竴鍙mport璇彞,閭d箞鍙細鎵ц涓娆,鑰屼笉浼氭墽琛屽娆 + import 'lodash'; + import 'lodash'; + //浠g爜鍔犺浇浜嗕袱娆odash,浣嗘槸鍙細鎵ц涓娆 + ---------------------------------- + import { foo } from 'my_module'; + import { bar } from 'my_module'; + + // 绛夊悓浜 + import { foo, bar } from 'my_module'; + +---------------------------- +妯″潡鐨勬暣浣撳姞杞 | +---------------------------- + # 闄や簡鎸囧畾鍔犺浇鏌愪釜杈撳嚭鍊,杩樺彲浠ヤ娇鐢ㄦ暣浣撳姞杞,鍗崇敤鏄熷彿(*)鎸囧畾涓涓璞,鎵鏈夎緭鍑哄奸兘鍔犺浇鍦ㄨ繖涓璞′笂闈 + // circle.js + export function area(radius) { + return Math.PI * radius * radius; + } + export function circumference(radius) { + return 2 * Math.PI * radius; + } + + // main.js + import * as circle from './circle'; + console.log('鍦嗛潰绉細' + circle.area(4)); + console.log('鍦嗗懆闀匡細' + circle.circumference(14)); + + * 妯″潡鏁翠綋鍔犺浇鎵鍦ㄧ殑閭d釜瀵硅薄(涓婁緥鏄痗ircle),搴旇鏄彲浠ラ潤鎬佸垎鏋愮殑,鎵浠ヤ笉鍏佽杩愯鏃舵敼鍙,涓嬮潰鐨勫啓娉曢兘鏄笉鍏佽鐨 + + import * as circle from './circle'; + + // 涓嬮潰涓よ閮芥槸涓嶅厑璁哥殑 + circle.foo = 'hello'; + circle.area = function () {}; + +---------------------------- +export default 鍛戒护 | +---------------------------- + # 浣跨敤import鍛戒护鐨勬椂鍊,鐢ㄦ埛闇瑕佺煡閬撴墍瑕佸姞杞界殑鍙橀噺鍚嶆垨鍑芥暟鍚,鍚﹀垯鏃犳硶鍔犺浇 + # 甯屾湜蹇熶笂鎵,鏈繀鎰挎剰闃呰鏂囨。,鍘讳簡瑙fā鍧楁湁鍝簺灞炴у拰鏂规硶,灏辫鐢ㄥ埌export default鍛戒护,涓烘ā鍧楁寚瀹氶粯璁よ緭鍑 + // export-default.js + export default function () { + console.log('foo'); + } + + * 涓涓ā鍧楁枃浠秂xport-default.js,瀹冪殑榛樿杈撳嚭鏄竴涓嚱鏁 + * 鍏朵粬妯″潡鍔犺浇璇ユā鍧楁椂,import鍛戒护鍙互涓鸿鍖垮悕鍑芥暟鎸囧畾浠绘剰鍚嶅瓧 + + // import-default.js + import customName from './export-default'; + customName(); // 'foo' + + * import鍛戒护,鍙互鐢ㄤ换鎰忓悕绉版寚鍚慹xport-default.js杈撳嚭鐨勬柟娉,杩欐椂灏变笉闇瑕佺煡閬撳師妯″潡杈撳嚭鐨勫嚱鏁板悕 + * 闇瑕佹敞鎰忕殑鏄,'杩欐椂import鍛戒护鍚庨潰,涓嶄娇鐢ㄥぇ鎷彿' + + # export default鍛戒护鐢ㄥ湪闈炲尶鍚嶅嚱鏁板墠,涔熸槸鍙互鐨 + // export-default.js + export default function foo() { + console.log('foo'); + } + + // 鎴栬呭啓鎴 + + function foo() { + console.log('foo'); + } + //foo鍑芥暟鐨勫嚱鏁板悕foo,鍦ㄦā鍧楀閮ㄦ槸鏃犳晥鐨,鍔犺浇鐨勬椂鍊,瑙嗗悓鍖垮悕鍑芥暟鍔犺浇 + export default foo; + + # 涓涓ā鍧楀彧鑳芥湁涓涓粯璁よ緭鍑,鍥犳export default鍛戒护鍙兘浣跨敤涓娆,鎵浠,import鍛戒护鍚庨潰鎵嶄笉鐢ㄥ姞澶ф嫭鍙 + # 鏈川涓,export default灏辨槸'杈撳嚭涓涓彨鍋歞efault鐨勫彉閲忔垨鏂规硶',鐒跺悗绯荤粺鍏佽浣犱负瀹冨彇浠绘剰鍚嶅瓧 + * 涓嬮潰鐨勫啓娉曟槸鏈夋晥鐨 + // modules.js + function add(x, y) { + return x * y; + } + export {add as default}; + // 绛夊悓浜 + // export default add; + + // app.js + import { default as foo } from 'modules'; + // 绛夊悓浜 + // import foo from 'modules'; + + * 鍥犱负export default鍛戒护鍏跺疄鍙槸杈撳嚭涓涓彨鍋歞efault鐨勫彉閲,鎵浠ュ畠鍚庨潰'涓嶈兘璺熷彉閲忓0鏄庤鍙' + // 姝g‘ + export var a = 1; + + // 姝g‘ + var a = 1; + export default a; + + // 閿欒 + export default var a = 1; + + * export default鍛戒护鐨勬湰璐ㄦ槸灏嗗悗闈㈢殑鍊,璧嬬粰default鍙橀噺,鎵浠'鍙互鐩存帴灏嗕竴涓煎啓鍦╡xport default涔嬪悗' + // 姝g‘ + export default 42; + + // 鎶ラ敊 + export 42; + + + # 鎯冲湪涓鏉mport璇彞涓,鍚屾椂杈撳叆榛樿鏂规硶鍜屽叾浠栨帴鍙 + export default function (obj) { + // 路路路 + } + + export function each(obj, iterator, context) { + // 路路路 + } + + export { each as forEach }; + + import _, { each, each as forEach } from 'lodash'; //forEach鍜宔ach鎸囧悜鍚屼竴涓柟娉 + + # export default涔熷彲浠ョ敤鏉ヨ緭鍑虹被 + // MyClass.js + export default class { ... } + + // main.js + import MyClass from 'MyClass'; + let o = new MyClass(); + +---------------------------- +export 涓 import 鐨勫鍚堝啓娉 | +---------------------------- + # 鍦ㄤ竴涓ā鍧椾箣涓,鍏堣緭鍏ュ悗杈撳嚭鍚屼竴涓ā鍧,import璇彞鍙互涓巈xport璇彞鍐欏湪涓璧 + export { foo, bar } from 'my_module'; //鍏坕mport,鍐峞xport + + // 鍙互绠鍗曠悊瑙d负 + import { foo, bar } from 'my_module'; + export { foo, bar }; + + * export鍜宨mport璇彞鍙互缁撳悎鍦ㄤ竴璧,鍐欐垚涓琛 + * 浣嗛渶瑕佹敞鎰忕殑鏄,鍐欐垚涓琛屼互鍚,'foo鍜宐ar瀹為檯涓婂苟娌℃湁琚鍏ュ綋鍓嶆ā鍧,鍙槸鐩稿綋浜庡澶栬浆鍙戜簡杩欎袱涓帴鍙,瀵艰嚧褰撳墠妯″潡涓嶈兘鐩存帴浣跨敤foo鍜宐ar' + + # 妯″潡鐨勬帴鍙f敼鍚嶅拰鏁翠綋杈撳嚭,涔熷彲浠ラ噰鐢ㄨ繖绉嶅啓娉 + // 鎺ュ彛鏀瑰悕 + export { foo as myFoo } from 'my_module'; + + // 鏁翠綋杈撳嚭 + export * from 'my_module'; + + + # 榛樿鎺ュ彛鐨勫啓娉曞涓 + export { default } from 'foo'; + + # 鍏峰悕鎺ュ彛鏀逛负榛樿鎺ュ彛鐨勫啓娉曞涓 + export { es6 as default } from './someModule'; + // 绛夊悓浜 + import { es6 } from './someModule'; + export default es6; + + # 鍚屾牱鍦,榛樿鎺ュ彛涔熷彲浠ユ敼鍚嶄负鍏峰悕鎺ュ彛 + export { default as es6 } from './someModule'; + + # 涓嬮潰涓夌import璇彞,娌℃湁瀵瑰簲鐨勫鍚堝啓娉 + import * as someIdentifier from "someModule"; + import someIdentifier from "someModule"; + import someIdentifier, { namedIdentifier } from "someModule"; + + * 涓轰簡鍋氬埌褰㈠紡鐨勫绉,鐜板湪鏈夋彁妗,鎻愬嚭琛ヤ笂杩欎笁绉嶅鍚堝啓娉 + export * as someIdentifier from "someModule"; + export someIdentifier from "someModule"; + export someIdentifier, { namedIdentifier } from "someModule"; + +---------------------------- +妯″潡鐨勭户鎵 | +---------------------------- + # 妯″潡涔嬮棿涔熷彲浠ョ户鎵 + * 鍋囪鏈変竴涓猚ircleplus妯″潡,缁ф壙浜哻ircle妯″潡 + // circleplus.js + export * from 'circle'; //export *,琛ㄧず鍐嶈緭鍑篶ircle妯″潡鐨勬墍鏈夊睘鎬у拰鏂规硶,娉ㄦ剰,export *鍛戒护浼氬拷鐣ircle妯″潡鐨刣efault鏂规硶 + export var e = 2.71828182846; + export default function(x) { //鍙堣緭鍑轰簡鑷畾涔夌殑e鍙橀噺鍜岄粯璁ゆ柟娉 + return Math.exp(x); + } + + * 涔熷彲浠ュ皢circle鐨勫睘鎬ф垨鏂规硶,鏀瑰悕鍚庡啀杈撳嚭 + // circleplus.js + export { area as circleArea } from 'circle'; //鍙緭鍑篶ircle妯″潡鐨刟rea鏂规硶,涓斿皢鍏舵敼鍚嶄负circleArea + + * 鍔犺浇涓婇潰妯″潡鐨勫啓娉曞涓 + // main.js + import * as math from 'circleplus'; + import exp from 'circleplus'; //import exp琛ㄧず,灏哻ircleplus妯″潡鐨勯粯璁ゆ柟娉曞姞杞戒负exp鏂规硶 + console.log(exp(math.e)); + +---------------------------- +璺ㄦā鍧楀父閲 | +---------------------------- + # const澹版槑鐨勫父閲忓彧鍦ㄥ綋鍓嶄唬鐮佸潡鏈夋晥 + # 濡傛灉鎯宠缃法妯″潡鐨勫父閲(鍗宠法澶氫釜鏂囦欢),鎴栬呰涓涓艰琚涓ā鍧楀叡浜,鍙互閲囩敤涓嬮潰鐨勫啓娉 + // constants.js 妯″潡 + export const A = 1; + export const B = 3; + export const C = 4; + + // test1.js 妯″潡 + import * as constants from './constants'; + console.log(constants.A); // 1 + console.log(constants.B); // 3 + + // test2.js 妯″潡 + import {A, B} from './constants'; + console.log(A); // 1 + console.log(B); // 3 + + # 濡傛灉瑕佷娇鐢ㄧ殑甯搁噺闈炲父澶,鍙互寤轰竴涓笓闂ㄧ殑constants鐩綍,灏嗗悇绉嶅父閲忓啓鍦ㄤ笉鍚岀殑鏂囦欢閲岄潰,淇濆瓨鍦ㄨ鐩綍涓 + // constants/db.js + export const db = { + url: 'http://my.couchdbserver.local:5984', + admin_username: 'admin', + admin_password: 'admin password' + }; + + // constants/user.js + export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator']; + + * 灏嗚繖浜涙枃浠惰緭鍑虹殑甯搁噺,鍚堝苟鍦╥ndex.js閲岄潰 + // constants/index.js + export {db} from './db'; + export {users} from './users'; + + * 浣跨敤鐨勬椂鍊,鐩存帴鍔犺浇index.js灏卞彲浠ヤ簡 + // script.js + import {db, users} from './index'; + +---------------------------- +import() | +---------------------------- + # import鍛戒护浼氳 JavaScript 寮曟搸闈欐佸垎鏋愬厛浜庢ā鍧楀唴鐨勫叾浠栬鍙ユ墽琛 + # (import鍛戒护鍙仛"杩炴帴" binding 鍏跺疄鏇村悎閫),鎵浠,涓嬮潰鐨勪唬鐮佷細鎶ラ敊 + // 鎶ラ敊 + if (x === 2) { + import MyModual from './myModual'; + } + + * 寮曟搸澶勭悊import璇彞鏄湪缂栬瘧鏃,杩欐椂涓嶄細鍘诲垎鏋愭垨鎵цif璇彞 + * 鎵浠mport璇彞鏀惧湪if浠g爜鍧椾箣涓鏃犳剰涔,鍥犳浼氭姤鍙ユ硶閿欒,鑰屼笉鏄墽琛屾椂閿欒 + * 涔熷氨鏄,import鍜宔xport鍛戒护鍙兘鍦ㄦā鍧楃殑椤跺眰,涓嶈兘鍦ㄤ唬鐮佸潡涔嬩腑(姣斿,鍦╥f浠g爜鍧椾箣涓,鎴栧湪鍑芥暟涔嬩腑) + + * 杩欐牱鐨勮璁,鍥虹劧鏈夊埄浜庣紪璇戝櫒鎻愰珮鏁堢巼,浣嗕篃瀵艰嚧鏃犳硶鍦ㄨ繍琛屾椂鍔犺浇妯″潡 + * 鍦ㄨ娉曚笂,鏉′欢鍔犺浇灏变笉鍙兘瀹炵幇,濡傛灉import鍛戒护瑕佸彇浠 Node 鐨剅equire鏂规硶,杩欏氨褰㈡垚浜嗕竴涓殰纰 + * 鍥犱负require鏄繍琛屾椂鍔犺浇妯″潡,import鍛戒护鏃犳硶鍙栦唬require鐨勫姩鎬佸姞杞藉姛鑳 + const path = './' + fileName; + const myModual = require(path); + + * 涓婇潰鐨勮鍙ュ氨鏄姩鎬佸姞杞,require鍒板簳鍔犺浇鍝竴涓ā鍧,鍙湁杩愯鏃舵墠鐭ラ亾,import鍛戒护鍋氫笉鍒拌繖涓鐐 + + # 鍥犳鏈変竴涓彁妗,寤鸿寮曞叆import()鍑芥暟,瀹屾垚鍔ㄦ佸姞杞 + import(specifier) + + * import鍑芥暟鐨勫弬鏁皊pecifier,鎸囧畾鎵瑕佸姞杞界殑妯″潡鐨勪綅缃 + * import鍛戒护鑳藉鎺ュ彈浠涔堝弬鏁,import()鍑芥暟灏辫兘鎺ュ彈浠涔堝弬鏁,涓よ呭尯鍒富瑕佹槸鍚庤呬负鍔ㄦ佸姞杞 + + * import()杩斿洖涓涓 Promise 瀵硅薄 + + const main = document.querySelector('main'); + import(`./section-modules/${someVariable}.js`) + .then(module => { + module.loadPageInto(main); + }) + .catch(err => { + main.textContent = err.message; + }); + + * import()鍑芥暟鍙互鐢ㄥ湪浠讳綍鍦版柟,涓嶄粎浠呮槸妯″潡,闈炴ā鍧楃殑鑴氭湰涔熷彲浠ヤ娇鐢 + * 瀹冩槸杩愯鏃舵墽琛,涔熷氨鏄,浠涔堟椂鍊欒繍琛屽埌杩欎竴鍙,灏变細鍔犺浇鎸囧畾鐨勬ā鍧 + * 鍙﹀,import()鍑芥暟涓庢墍鍔犺浇鐨勬ā鍧楁病鏈夐潤鎬佽繛鎺ュ叧绯,杩欑偣涔熸槸涓巌mport璇彞涓嶇浉鍚 + * import()绫讳技浜 Node 鐨剅equire鏂规硶,鍖哄埆涓昏鏄墠鑰呮槸寮傛鍔犺浇,鍚庤呮槸鍚屾鍔犺浇 + + # 閫傜敤鍦哄悎 + 1,鎸夐渶鍔犺浇 + * import()鍙互鍦ㄩ渶瑕佺殑鏃跺,鍐嶅姞杞芥煇涓ā鍧 + button.addEventListener('click', event => { + import('./dialogBox.js') + .then(dialogBox => { + dialogBox.open(); + }) + .catch(error => { + /* Error handling */ + }) + }); + + * import()鏂规硶鏀惧湪click浜嬩欢鐨勭洃鍚嚱鏁颁箣涓,鍙湁鐢ㄦ埛鐐瑰嚮浜嗘寜閽,鎵嶄細鍔犺浇杩欎釜妯″潡 + + 2,鏉′欢鍔犺浇 + * import()鍙互鏀惧湪if浠g爜鍧,鏍规嵁涓嶅悓鐨勬儏鍐,鍔犺浇涓嶅悓鐨勬ā鍧 + if (condition) { + import('moduleA').then(...); + } else { + import('moduleB').then(...); + } + + * 濡傛灉婊¤冻鏉′欢,灏卞姞杞芥ā鍧 A,鍚﹀垯鍔犺浇妯″潡 B + + 3,鍔ㄦ佺殑妯″潡璺緞 + * import()鍏佽妯″潡璺緞鍔ㄦ佺敓鎴 + + import(f()).then(...); //鏍规嵁鍑芥暟f鐨勮繑鍥炵粨鏋,鍔犺浇涓嶅悓鐨勬ā鍧 + + + # 娉ㄦ剰鐐 + * import()鍔犺浇妯″潡鎴愬姛浠ュ悗,杩欎釜妯″潡浼氫綔涓轰竴涓璞,褰撲綔then鏂规硶鐨勫弬鏁 + * 鍥犳,鍙互浣跨敤瀵硅薄瑙f瀯璧嬪肩殑璇硶,鑾峰彇杈撳嚭鎺ュ彛 + import('./myModule.js') + .then(({export1, export2}) => { + // ...路 + }); + //export1鍜宔xport2閮芥槸myModule.js鐨勮緭鍑烘帴鍙o紝鍙互瑙f瀯鑾峰緱 + + * 濡傛灉妯″潡鏈塪efault杈撳嚭鎺ュ彛,鍙互鐢ㄥ弬鏁扮洿鎺ヨ幏寰 + import('./myModule.js') + .then(myModule => { + console.log(myModule.default); + }); + + * 涔熷彲浠ヤ娇鐢ㄥ叿鍚嶈緭鍏ョ殑褰㈠紡 + import('./myModule.js') + .then(({default: theDefault}) => { + console.log(theDefault); + }); + + * 濡傛灉鎯冲悓鏃跺姞杞藉涓ā鍧,鍙互閲囩敤涓嬮潰鐨勫啓娉 + Promise.all([ + import('./module1.js'), + import('./module2.js'), + import('./module3.js'), + ]) + .then(([module1, module2, module3]) => { + 路路路 + }); + + * import()涔熷彲浠ョ敤鍦 async 鍑芥暟涔嬩腑 + async function main() { + const myModule = await import('./myModule.js'); + const {export1, export2} = await import('./myModule.js'); + const [module1, module2, module3] = await Promise.all([ + import('./module1.js'), + import('./module2.js'), + import('./module3.js'), + ]); + } + main(); diff --git a/JavaSript/ES6/es6-Promise.js b/JavaSript/ES6/es6-Promise.js new file mode 100644 index 00000000..6d0d85e0 --- /dev/null +++ b/JavaSript/ES6/es6-Promise.js @@ -0,0 +1,466 @@ +---------------------------- +Promise | +---------------------------- + 1,Promise 鐨勫惈涔 + 2,鍩烘湰鐢ㄦ硶 + 3,Promise.prototype.then() + 4,Promise.prototype.catch() + 5,Promise.prototype.finally() + 6,Promise.all() + 7,Promise.race() + 8,Promise.resolve() + 9,Promise.reject() + 10,搴旂敤 + 11,Promise.try() + +---------------------------- +Promise 鐨勫惈涔 | +---------------------------- + # Promise 鏄紓姝ョ紪绋嬬殑涓绉嶈В鍐虫柟妗,姣斾紶缁熺殑瑙e喅鏂规鈥斺斿洖璋冨嚱鏁板拰浜嬩欢鈥斺旀洿鍚堢悊鍜屾洿寮哄ぇ + # 瀹冪敱绀惧尯鏈鏃╂彁鍑哄拰瀹炵幇,ES6 灏嗗叾鍐欒繘浜嗚瑷鏍囧噯,缁熶竴浜嗙敤娉,鍘熺敓鎻愪緵浜哖romise瀵硅薄 + # 鎵璋揚romise,绠鍗曡灏辨槸涓涓鍣,閲岄潰淇濆瓨鐫鏌愪釜鏈潵鎵嶄細缁撴潫鐨勪簨浠(閫氬父鏄竴涓紓姝ユ搷浣)鐨勭粨鏋 + # 浠庤娉曚笂璇,Promise 鏄竴涓璞,浠庡畠鍙互鑾峰彇寮傛鎿嶄綔鐨勬秷鎭 + # Promise 鎻愪緵缁熶竴鐨 API,鍚勭寮傛鎿嶄綔閮藉彲浠ョ敤鍚屾牱鐨勬柟娉曡繘琛屽鐞 + # 'Promise 鏂板缓鍚庣珛鍗虫墽琛' + # 涓夌鐘舵 + pending(杩涜涓) + fulfilled(宸叉垚鍔) + rejected(宸插け璐) + +---------------------------- +Promise 鍏ラ棬 | +---------------------------- + let promise = new Promise(function(resolve,reject){ + window.setTimeout(function(){ + resolve('Hello World'); + },3000); + //reject鏂规硶鐨勪綔鐢,绛夊悓浜庢姏鍑洪敊璇 + }); + + promise.then(value => { + //鍥炶皟澶勭悊 3s鍚庤緭鍑:Hello World + console.log(value); + },error => { + //寮傚父澶勭悊 + console.log(error); + }); + + console.log('js浠g爜鎵ц瀹屾瘯'); + +---------------------------- +Promise.prototype.then() | +---------------------------- + # Promise 瀹炰緥鍏锋湁then鏂规硶,涔熷氨鏄,then鏂规硶鏄畾涔夊湪鍘熷瀷瀵硅薄Promise.prototype涓婄殑 + # then鏂规硶鐨勭涓涓弬鏁版槸resolved鐘舵佺殑鍥炶皟鍑芥暟,绗簩涓弬鏁(鍙)鏄痳ejected鐘舵佺殑鍥炶皟鍑芥暟 + # 'then鏂规硶杩斿洖鐨勬槸涓涓柊鐨凱romise瀹炰緥(娉ㄦ剰,涓嶆槸鍘熸潵閭d釜Promise瀹炰緥)' Promise聽{} + +---------------------------- +Promise.prototype.catch | +---------------------------- + # Promise.prototype.catch鏂规硶鏄.then(null, rejection)鐨勫埆鍚,鐢ㄤ簬鎸囧畾鍙戠敓閿欒鏃剁殑鍥炶皟鍑芥暟 + # then鏂规硶鎸囧畾鐨勫洖璋冨嚱鏁,濡傛灉杩愯涓姏鍑洪敊璇,涔熶細琚玞atch鏂规硶鎹曡幏 + let promise = new Promise(function(resolve,reject){ + resolve('Hello'); + }); + + promise.then(function(r){ + throw 'then 鎶涘嚭鐨勫紓甯'; + }).catch(function(e){ + console.log('鎹曡幏鍒颁簡寮傚父:' + e) + }); + //鎹曡幏鍒颁簡寮傚父:then 鎶涘嚭鐨勫紓甯 + + # 濡傛灉 Promise 鐘舵佸凡缁忓彉鎴恟esolved,鍐嶆姏鍑洪敊璇槸鏃犳晥鐨 + const promise = new Promise(function(resolve, reject) { + resolve('ok'); + throw new Error('test'); + }); + promise + .then(function(value) { console.log(value) }) + .catch(function(error) { console.log(error) }); + // ok + + # Promise 瀵硅薄鐨勯敊璇叿鏈"鍐掓场"鎬ц川,浼氫竴鐩村悜鍚庝紶閫,鐩村埌琚崟鑾蜂负姝 + * 涔熷氨鏄,閿欒鎬绘槸浼氳涓嬩竴涓猚atch璇彞鎹曡幏 + getJSON('/post/1.json').then(function(post) { + return getJSON(post.commentURL); + }).then(function(comments) { + // some code + }).catch(function(error) { + // 澶勭悊鍓嶉潰涓変釜Promise浜х敓鐨勯敊璇 + }); + + # 涓鑸潵璇,涓嶈鍦╰hen鏂规硶閲岄潰瀹氫箟 Reject 鐘舵佺殑鍥炶皟鍑芥暟(鍗硉hen鐨勭浜屼釜鍙傛暟)鎬绘槸浣跨敤catch鏂规硶 + * 鐞嗙敱鏄浜岀鍐欐硶鍙互鎹曡幏鍓嶉潰then鏂规硶鎵ц涓殑閿欒,涔熸洿鎺ヨ繎鍚屾鐨勫啓娉(try/catch) + * 鍥犳,寤鸿鎬绘槸浣跨敤catch鏂规硶,鑰屼笉浣跨敤then鏂规硶鐨勭浜屼釜鍙傛暟 + + # 璺熶紶缁熺殑try/catch浠g爜鍧椾笉鍚岀殑鏄,濡傛灉娌℃湁浣跨敤catch鏂规硶鎸囧畾閿欒澶勭悊鐨勫洖璋冨嚱鏁,Promise 瀵硅薄鎶涘嚭鐨勯敊璇笉浼氫紶閫掑埌澶栧眰浠g爜,鍗充笉浼氭湁浠讳綍鍙嶅簲 + * romise 鍐呴儴鐨勯敊璇笉浼氬奖鍝嶅埌 Promise 澶栭儴鐨勪唬鐮,閫氫織鐨勮娉曞氨鏄"Promise 浼氬悆鎺夐敊璇" + + # Node 鏈変竴涓猽nhandledRejection浜嬩欢,涓撻棬鐩戝惉鏈崟鑾风殑reject閿欒 + process.on('unhandledRejection', function (err, p) { + throw err; + }); + + # 涓鑸绘槸寤鸿,Promise 瀵硅薄鍚庨潰瑕佽窡catch鏂规硶,杩欐牱鍙互澶勭悊 Promise 鍐呴儴鍙戠敓鐨勯敊璇 + * catch鏂规硶杩斿洖鐨勮繕鏄竴涓 Promise 瀵硅薄,鍥犳鍚庨潰杩樺彲浠ユ帴鐫璋冪敤then鏂规硶 + const someAsyncThing = function() { + const someAsyncThing = function() { + return new Promise(function(resolve, reject) { + // 涓嬮潰涓琛屼細鎶ラ敊锛屽洜涓簒娌℃湁澹版槑 + resolve(x + 2); + }); + }; + + someAsyncThing().catch(function(error) { + console.log('oh no', error); + }).then(function() { + console.log('carry on'); + }); + // oh no [ReferenceError: x is not defined] + // carry on + + * 浠g爜杩愯瀹宑atch鏂规硶鎸囧畾鐨勫洖璋冨嚱鏁,浼氭帴鐫杩愯鍚庨潰閭d釜then鏂规硶鎸囧畾鐨勫洖璋冨嚱鏁,濡傛灉娌℃湁鎶ラ敊,鍒欎細璺宠繃catch鏂规硶 + + # catch鏂规硶涔嬩腑,杩樿兘鍐嶆姏鍑洪敊璇 + someAsyncThing().then(function() { + return someOtherAsyncThing(); + }).catch(function(error) { + console.log('oh no', error); + y + 2; // 涓嬮潰涓琛屼細鎶ラ敊锛屽洜涓 y 娌℃湁澹版槑 + }).then(function() { + console.log('carry on'); //涓嶄細杈撳嚭,鍥犱负涓婂眰catch鎶涘嚭浜嗗紓甯 + }); + // oh no [ReferenceError: x is not defined] + + * catch鏂规硶鎶涘嚭涓涓敊璇,鍥犱负鍚庨潰娌℃湁鍒殑catch鏂规硶浜,瀵艰嚧杩欎釜閿欒涓嶄細琚崟鑾,涔熶笉浼氫紶閫掑埌澶栧眰 + * 鏀瑰啓涓涓,缁撴灉灏变笉涓鏍蜂簡 + + const someAsyncThing = function() { + return new Promise(function(resolve, reject) { + // 涓嬮潰涓琛屼細鎶ラ敊锛屽洜涓簒娌℃湁澹版槑 + resolve(x + 2); + }); + }; + + someAsyncThing().then(function() { + return someOtherAsyncThing(); + }).catch(function(error) { + console.log('oh no', error); + // 涓嬮潰涓琛屼細鎶ラ敊锛屽洜涓簓娌℃湁澹版槑 + y + 2; + }).catch(function(error) { + //鎹曡幏浜嗕笂灞俢atch鎶涘嚭鐨勫紓甯 + console.log('carry on', error); + }); + // oh no [ReferenceError: x is not defined] + // carry on [ReferenceError: y is not defined] + + +---------------------------- +Promise.prototype.finally() | +---------------------------- + # finally鏂规硶鐢ㄤ簬鎸囧畾涓嶇 Promise 瀵硅薄鏈鍚庣姸鎬佸浣,閮戒細鎵ц鐨勬搷浣 + * 璇ユ柟娉曟槸 ES2018 寮曞叆鏍囧噯鐨 + + # finally鏂规硶鐨勫洖璋冨嚱鏁颁笉鎺ュ彈浠讳綍鍙傛暟,杩欐剰鍛崇潃娌℃湁鍔炴硶鐭ラ亾,鍓嶉潰鐨 Promise 鐘舵佸埌搴曟槸fulfilled杩樻槸rejected + * 杩欒〃鏄,finally鏂规硶閲岄潰鐨勬搷浣,搴旇鏄笌鐘舵佹棤鍏崇殑,涓嶄緷璧栦簬 Promise 鐨勬墽琛岀粨鏋 + + # finally鏈川涓婃槸then鏂规硶鐨勭壒渚 + promise.finally(() => { + // 鎬绘槸浼氭墽琛岀殑璇彞 + }); + + // 绛夊悓浜 + promise.then( + result => { + // 鎬绘槸浼氭墽琛岀殑璇彞 + return result; + }, + error => { + // 鎬绘槸浼氭墽琛岀殑璇彞 + throw error; + } + ); + + + + # 瀹冪殑瀹炵幇涔熷緢绠鍗 + Promise.prototype.finally = function (callback) { + let P = this.constructor; + return this.then( + value => P.resolve(callback()).then(() => value), + reason => P.resolve(callback()).then(() => { throw reason }) + ); + }; + + * 涓嶇鍓嶉潰鐨 Promise 鏄痜ulfilled杩樻槸rejected,閮戒細鎵ц鍥炶皟鍑芥暟callback + * finally鏂规硶鎬绘槸浼氳繑鍥炲師鏉ョ殑鍊 + // resolve 鐨勫兼槸 undefined + Promise.resolve(2).then(() => {}, () => {}) + + // resolve 鐨勫兼槸 2 + Promise.resolve(2).finally(() => {}) + + // reject 鐨勫兼槸 undefined + Promise.reject(3).then(() => {}, () => {}) + + // reject 鐨勫兼槸 3 + Promise.reject(3).finally(() => {}) + + + + + + +---------------------------- +Promise.all | +---------------------------- + # Promise.all鏂规硶鐢ㄤ簬灏嗗涓 Promise 瀹炰緥,鍖呰鎴愪竴涓柊鐨 Promise 瀹炰緥 + * 鎵鏈夊疄渚嬮兘瀹屾垚,鎵嶅畬鎴,鏈缁堢粨鏋滀互[]褰㈠紡浼犻掔粰褰㈠弬 + * 浠讳綍瀹炰緥寮傚父,閮戒細瑙﹀彂 Promise.all 瀹炰緥鐨 rejected,姝ゆ椂绗竴涓reject鐨勫疄渚嬬殑杩斿洖鍊,浼氫紶閫掔粰Promise.all鐨勫洖璋冨嚱鏁 + + // 鐢熸垚涓涓狿romise瀵硅薄鐨勬暟缁 + const promises = [2, 3, 5, 7, 11, 13].map(function (id) { + return getJSON('/post/' + id + ".json"); + }); + + Promise.all(promises).then(function (posts) { + // ... + }).catch(function(reason){ + // ... + }); + + const p = Promise.all([p1, p2, p3]); + + * Promise.all鏂规硶鐨勫弬鏁板彲浠ヤ笉鏄暟缁,浣嗗繀椤诲叿鏈 Iterator 鎺ュ彛,涓旇繑鍥炵殑姣忎釜鎴愬憳閮芥槸 Promise 瀹炰緥 + * 鍙湁p1,p2,p3鐨勭姸鎬侀兘鍙樻垚 fulfilled,p鐨勭姸鎬佹墠浼氬彉鎴恌ulfilled,姝ゆ椂p1,p2,p3鐨勮繑鍥炲肩粍鎴愪竴涓暟缁,浼犻掔粰p鐨勫洖璋冨嚱鏁 + * 鍙p1,p2,p3涔嬩腑鏈変竴涓 rejected,p鐨勭姸鎬佸氨鍙樻垚rejected,姝ゆ椂绗竴涓reject鐨勫疄渚嬬殑杩斿洖鍊,浼氫紶閫掔粰p鐨勫洖璋冨嚱鏁 + + # 濡傛灉浣滀负鍙傛暟鐨 Promise 瀹炰緥,鑷繁瀹氫箟浜哻atch鏂规硶,閭d箞瀹冧竴鏃﹁rejected,骞朵笉浼氳Е鍙慞romise.all()鐨刢atch鏂规硶 + +---------------------------- +Promise.race | +---------------------------- + # Promise.race鏂规硶鍚屾牱鏄皢澶氫釜 Promise 瀹炰緥,鍖呰鎴愪竴涓柊鐨 Promise 瀹炰緥 + * 浠讳綍瀹炰緥鍏堝畬鎴,灏变細鍥炶皟 Promise.race 瀹炰緥鐨 then + * 浠讳綍瀹炰緥寮傚父,閮戒細瑙﹀彂 Promise.all 瀹炰緥鐨 rejected,姝ゆ椂绗竴涓reject鐨勫疄渚嬬殑杩斿洖鍊,浼氫紶閫掔粰Promise.all鐨勫洖璋冨嚱鏁 + + const p = Promise.race([p1, p2, p3]); + + * 鍙p1,p2,p3涔嬩腑鏈変竴涓疄渚嬬巼鍏堟敼鍙樼姸鎬,p鐨勭姸鎬佸氨璺熺潃鏀瑰彉 + * 鍝釜鐜囧厛鏀瑰彉鐨 Promise 瀹炰緥鐨勮繑鍥炲,灏变紶閫掔粰p鐨勫洖璋冨嚱鏁 + * Promise.race鏂规硶鐨勫弬鏁颁笌Promise.all鏂规硶涓鏍,濡傛灉涓嶆槸 Promise 瀹炰緥,灏变細鍏堣皟鐢ㄤ笅闈㈣鍒扮殑Promise.resolve鏂规硶,灏嗗弬鏁拌浆涓 Promise 瀹炰緥鍐嶈繘涓姝ュ鐞 + + # 涓涓皬Demo + const p = Promise.race([ + fetch('/resource-that-may-take-a-while'), + new Promise(function (resolve, reject) { + setTimeout(() => reject(new Error('request timeout')), 5000) + }) + ]); + + p.then(console.log).catch(console.error); + + * 濡傛灉 5 绉掍箣鍐協etch鏂规硶鏃犳硶杩斿洖缁撴灉,鍙橀噺p鐨勭姸鎬佸氨浼氬彉涓簉ejected,浠庤岃Е鍙慶atch鏂规硶鎸囧畾鐨勫洖璋冨嚱鏁 + +---------------------------- +Promise.resolve() | +---------------------------- + # 鏈夋椂闇瑕佸皢鐜版湁瀵硅薄杞负 Promise 瀵硅薄,Promise.resolve鏂规硶灏辫捣鍒拌繖涓綔鐢 + + const jsPromise = Promise.resolve($.ajax('/user.json')); + * 涓婇潰浠g爜灏 jQuery 鐢熸垚鐨刣eferred瀵硅薄,杞负涓涓柊鐨 Promise 瀵硅薄 + + # Promise.resolve绛変环浜庝笅闈㈢殑鍐欐硶 + let promise = Promise.resolve('foo') + // 绛変环浜 + let promise = new Promise(function(resolve){ + resolve("foo"); + }); + + # Promise.resolve鏂规硶鐨勫弬鏁板垎鎴愬洓绉嶆儏鍐 + 1,鍙傛暟鏄竴涓 Promise 瀹炰緥 + * 鍙傛暟鏄 Promise 瀹炰緥,閭d箞Promise.resolve灏嗕笉鍋氫换浣曚慨鏀,鍘熷皝涓嶅姩鍦拌繑鍥炶繖涓疄渚 + + 2,鍙傛暟鏄竴涓猼henable瀵硅薄 + * thenable瀵硅薄鎸囩殑鏄叿鏈塼hen鏂规硶鐨勫璞,姣斿涓嬮潰杩欎釜瀵硅薄 + * Promise.resolve鏂规硶浼氬皢杩欎釜瀵硅薄杞负 Promise 瀵硅薄,鐒跺悗灏辩珛鍗虫墽琛宼henable瀵硅薄鐨則hen鏂规硶 + let thenable = { + then: function(resolve, reject) { + resolve(42); + } + }; + + let p1 = Promise.resolve(thenable); + p1.then(function(value) { + console.log(value); // 42 + }); + * thenable 瀵硅薄鐨 then 鏂规硶鎵ц鍚,瀵硅薄p1鐨勭姸鎬佸氨鍙樹负 resolved, 浠庤岀珛鍗虫墽琛屾渶鍚庨偅涓 then 鏂规硶鎸囧畾鐨勫洖璋冨嚱鏁,杈撳嚭 42 + + 3,鍙傛暟涓嶆槸鍏锋湁then鏂规硶鐨勫璞,鎴栨牴鏈氨涓嶆槸瀵硅薄 + * 濡傛灉鍙傛暟鏄竴涓師濮嬪,鎴栬呮槸涓涓笉鍏锋湁then鏂规硶鐨勫璞,鍒橮romise.resolve鏂规硶杩斿洖涓涓柊鐨 Promise 瀵硅薄,鐘舵佷负resolved + const p = Promise.resolve('Hello'); + p.then(function (s){ + console.log(s) + }); + //Hello + + * 涓婇潰浠g爜鐢熸垚涓涓柊鐨 Promise 瀵硅薄鐨勫疄渚媝 + * 鐢变簬瀛楃涓睭ello涓嶅睘浜庡紓姝ユ搷浣(鍒ゆ柇鏂规硶鏄瓧绗︿覆瀵硅薄涓嶅叿鏈 then 鏂规硶),杩斿洖 Promise 瀹炰緥鐨勭姸鎬佷粠涓鐢熸垚灏辨槸resolved,鎵浠ュ洖璋冨嚱鏁颁細绔嬪嵆鎵ц + * Promise.resolve鏂规硶鐨勫弬鏁,浼氬悓鏃朵紶缁欏洖璋冨嚱鏁 + + 4,涓嶅甫鏈変换浣曞弬鏁 + * Promise.resolve鏂规硶鍏佽璋冪敤鏃朵笉甯﹀弬鏁,鐩存帴杩斿洖涓涓猺esolved鐘舵佺殑 Promise 瀵硅薄. + * 鎵浠,濡傛灉甯屾湜寰楀埌涓涓 Promise 瀵硅薄,姣旇緝鏂逛究鐨勬柟娉曞氨鏄洿鎺ヨ皟鐢≒romise.resolve鏂规硶 + const p = Promise.resolve(); + p.then(function () { + console.log('涓嶅甫浠讳綍鍙傛暟鐨 resolve'); + }); + + * 绔嬪嵆resolve鐨 Promise 瀵硅薄,鏄湪鏈疆"浜嬩欢寰幆"(event loop)鐨勭粨鏉熸椂,鑰屼笉鏄湪涓嬩竴杞"浜嬩欢寰幆"鐨勫紑濮嬫椂 + //0s鍚庢墦鍗皌hree + setTimeout(function () { + console.log('three'); + }, 0); + + //鍗硆esolve鐨 Promise 瀵硅薄 + Promise.resolve().then(function () { + console.log('two'); + }); + + //浠g爜鎵ц鍒拌繖閲屾墦鍗 + console.log('one'); + + // one + // two + // three + + * setTimeout(fn, 0)鍦ㄤ笅涓杞"浜嬩欢寰幆"寮濮嬫椂鎵ц. + * Promise.resolve()鍦ㄦ湰杞"浜嬩欢寰幆"缁撴潫鏃舵墽琛,console.log('one')鍒欐槸绔嬪嵆鎵ц,鍥犳鏈鍏堣緭鍑 + +---------------------------- +Promise.reject() | +---------------------------- + # Promise.reject(reason)鏂规硶杩斿洖涓涓柊鐨 Promise 瀵硅薄,瀹炰緥鐨勭姸鎬佷负rejected + const p = Promise.reject('鍑洪敊浜'); + + // 绛夊悓浜 + const p = new Promise((resolve, reject) => reject('鍑洪敊浜')) + + p.then(null, function (s) { + console.log(s) + }); + // 鍑洪敊浜 + + * 鐢熸垚涓涓 Promise 瀵硅薄鐨勫疄渚媝,鐘舵佷负rejected,鍥炶皟鍑芥暟浼氱珛鍗虫墽琛 + + + # Promise.reject()鏂规硶鐨勫弬鏁,浼氬師灏佷笉鍔ㄥ湴浣滀负reject鐨勭悊鐢,鍙樻垚鍚庣画鏂规硶鐨勫弬鏁 + * 杩欎竴鐐逛笌Promise.resolve鏂规硶涓嶄竴鑷 + + let promise = Promise.reject("reject鐨勫弬鏁"); + + const thenable = { + then(resolve, reject) { + //绔嬪嵆鎵ц reject + reject('鍑洪敊浜'); + } + }; + Promise.reject(thenable) + .catch(e => { + //璇ュ弬鏁,灏辨槸thenable瀵硅薄,鑰屼笉鏄"鍑洪敊浜" + console.log(e === thenable) + }) + // true + + * Promise.reject鏂规硶鐨勫弬鏁版槸涓涓猼henable瀵硅薄 + * '鎵ц浠ュ悗,鍚庨潰catch鏂规硶鐨勫弬鏁颁笉鏄痳eject鎶涘嚭鐨"鍑洪敊浜"杩欎釜瀛楃涓,鑰屾槸thenable瀵硅薄' + +---------------------------- +Promise.try() | +---------------------------- + # 杩樺彧鏄竴涓彁妗,鏈疄鐜 + # 浜嬪疄涓,Promise.try灏辨槸妯℃嫙try浠g爜鍧,灏卞儚promise.catch妯℃嫙鐨勬槸catch浠g爜鍧 + +---------------------------- +搴旂敤 | +---------------------------- + 1,鍔犺浇鍥剧墖 + * 鍙互鎶婂浘鐗囧姞杞藉啓涓轰竴涓 Promise,涓鏃﹀姞杞藉畬鎴,Promise鐨勭姸鎬佸氨浼氬彂鐢熸敼鍙 + const preloadImage = function (path) { + return new Promise(function (resolve, reject) { + const image = new Image(); + //鍔犺浇ok,鎵ц resolve + image.onload = resolve; + //鍔犺浇澶辫触,鎵ц reject + image.onerror = reject; + image.src = path; + }); + }; + + 2,Generator 鍑芥暟涓 Promise 鐨勭粨鍚 + * 浣跨敤 Generator 鍑芥暟绠$悊娴佺▼,閬囧埌寮傛鎿嶄綔鐨勬椂鍊,閫氬父杩斿洖涓涓狿romise瀵硅薄 + function getFoo () { + return new Promise(function (resolve, reject){ + resolve('foo'); + }); + } + const g = function* () { + try { + const foo = yield getFoo(); + console.log(foo); + } catch (e) { + console.log(e); + } + }; + function run (generator) { + const it = generator(); + + function go(result) { + if (result.done) return result.value; + return result.value.then(function (value) { + return go(it.next(value)); + }, function (error) { + return go(it.throw(error)); + }); + } + go(it.next()); + } + run(g); + + * Generator 鍑芥暟g涔嬩腑,鏈変竴涓紓姝ユ搷浣済etFoo,瀹冭繑鍥炵殑灏辨槸涓涓狿romise瀵硅薄 + * 鍑芥暟run鐢ㄦ潵澶勭悊杩欎釜Promise瀵硅薄,骞惰皟鐢ㄤ笅涓涓猲ext鏂规硶 + + 3,Promise.try() + * 瀹為檯寮鍙戜腑,缁忓父閬囧埌涓绉嶆儏鍐:涓嶇煡閬撴垨鑰呬笉鎯冲尯鍒,鍑芥暟f鏄悓姝ュ嚱鏁拌繕鏄紓姝ユ搷浣,浣嗘槸鎯崇敤 Promise 鏉ュ鐞嗗畠 + * 鍥犱负杩欐牱灏卞彲浠ヤ笉绠鏄惁鍖呭惈寮傛鎿嶄綔,閮界敤then鏂规硶鎸囧畾涓嬩竴姝ユ祦绋,鐢╟atch鏂规硶澶勭悊f鎶涘嚭鐨勯敊璇,涓鑸氨浼氶噰鐢ㄤ笅闈㈢殑鍐欐硶 + ... + * demo + function getUsername(userId) { + return database.users.get({id: userId}) + .then(function(user) { + return user.name; + }); + } + + * 涓婇潰浠g爜涓紝database.users.get()杩斿洖涓涓 Promise 瀵硅薄,濡傛灉鎶涘嚭寮傛閿欒,鍙互鐢╟atch鏂规硶鎹曡幏,灏卞儚涓嬮潰杩欐牱鍐 + + database.users.get({id: userId}) + .then(...) + .catch(...) + + * 浣嗘槸database.users.get()鍙兘杩樹細鎶涘嚭鍚屾閿欒(姣斿鏁版嵁搴撹繛鎺ラ敊璇,鍏蜂綋瑕佺湅瀹炵幇鏂规硶)杩欐椂浣犲氨涓嶅緱涓嶇敤try...catch鍘绘崟鑾 + + try { + database.users.get({id: userId}) + .then(...) + .catch(...) + } catch (e) { + // ... + } + + * 涓婇潰杩欐牱鐨勫啓娉曞氨寰堢鎷欎簡,杩欐椂灏卞彲浠ョ粺涓鐢╬romise.catch()鎹曡幏鎵鏈夊悓姝ュ拰寮傛鐨勯敊璇 + Promise.try(database.users.get({id: userId})) + .then(...) + .catch(...) \ No newline at end of file diff --git a/JavaSript/ES6/es6-Proxy.js b/JavaSript/ES6/es6-Proxy.js new file mode 100644 index 00000000..29d63270 --- /dev/null +++ b/JavaSript/ES6/es6-Proxy.js @@ -0,0 +1,201 @@ +---------------------------- +Proxy | +---------------------------- + 1,概述 + 2,Proxy 实例的方法 + 3,Proxy.revocable() + 4,this 问题 + 5,实例:Web 服务的客户端 + + +---------------------------- +概述 | +---------------------------- + # 跟Java的Proxy一样,AOP编程 + # 创建Proxy实例对象 + + new Proxy(target,handler) + + * target,目标对象(被增强对象) + * handler,代理控制器(一个对象) + + # Hello World + let obj = {name:'Kevin',age:23}; + let proxyObj = new Proxy(obj,{ + //读取属性时拦截 + get:function(target,key,receiver){ + console.log(`获取属性:${key}`); + return Reflect.get(target, key, receiver); + }, + //设置属性时拦截 + set:function(target, key, value, receiver){ + console.log(`设置属性:${key},${value}`); + return Reflect.set(target, key, value, receiver); + } + }); + + proxyObj.name; //获取属性:name + proxyObj.age = 25; //设置属性:age,25 + + + # 如果handler没有设置任何拦截,那就等同于直接通向原对象 + let obj = {name:'Kevin',age:23}; + let proxyObj = new Proxy(obj,{}); + * handler是一个空对象,没有任何拦截效果,访问proxyObj就等同于访问obj + +-------------------------------- +Proxy.revocable | +-------------------------------- + # Proxy.revocable方法返回一个可取消的 Proxy 实例 + * Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数 + let target = {}; + let handler = {}; + + let {proxy, revoke} = Proxy.revocable(target, handler); + + proxy.foo = 123; + proxy.foo // 123 + + revoke(); + proxy.foo // TypeError: Revoked + //上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误 + +-------------------------------- +this | +-------------------------------- + # 目标对象内部的this关键字会指向 Proxy 代理 + const target = { + m: function () { + console.log(this === proxy); + } + }; + + const handler = {}; + const proxy = new Proxy(target, handler); + target.m() // false + proxy.m() // true + + # 有些原生对象的内部属性,只有通过正确的this才能拿到,所以 Proxy 也无法代理这些原生对象的属性 + const target = new Date(); + + const handler = {}; + + const proxy = new Proxy(target, handler); + + console.log(target.getDate()); //29(日) + console.log(proxy.getDate()); // TypeError: this is not a Date object. + + * getDate方法只能在Date对象实例上面拿到,如果this不是Date对象实例就会报错 + * 这时,this绑定原始对象,就可以解决这个问题 + const target = new Date('2015-01-01'); + const handler = { + get(target, prop) { + if (prop === 'getDate') { + //如果发现是执行 getDate 方法,则绑定这个函数的上下文为原始对象 + return target.getDate.bind(target); + } + return Reflect.get(target, prop); + } + }; + const proxy = new Proxy(target, handler); + + proxy.getDate() // 1 + +-------------------------------- +Proxy 支持的拦截操作一览(13 种) | +-------------------------------- + # 其实就是 hanlder 的属性 + + get(target, propKey, receiver) + * 拦截对象属性的读取,比如proxy.foo和proxy['foo'] + * 'get方法的第三个参数receiver,总是为当前的 Proxy 实例' + const proxy = new Proxy({}, { + get: function(target, property, receiver) { + return receiver; + } + }); + proxy.getReceiver === proxy // true + * 如果一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过 Proxy 对象访问该属性会报错 + + set(target, propKey, value, receiver) + * 拦截对象属性的设置,比如proxy.foo = v 或 proxy['foo'] = v + * 如果目标对象自身的某个属性,不可写或不可配置,那么set方法将不起作用 + * 返回布尔值 + + has(target, propKey) + * 拦截propKey in proxy的操作 + * 这个方法会生效,典型的操作就是in运算符 + * 原对象不可配置或者禁止扩展,这时has拦截会报错 + * has方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has方法不判断一个属性是对象自身的属性,还是继承的属性 + * 虽然for...in循环也用到了in运算符,但是has拦截对for...in循环不生效 + * 返回布尔值 + + deleteProperty(target, propKey) + * 拦截delete proxy[propKey]的操作 + * 如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除 + * 目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错 + * 返回一个布尔值 + + ownKeys(target) + * 拦截Object.getOwnPropertyNames(proxy),Object.getOwnPropertySymbols(proxy),Object.keys(proxy) + * 返回一个数组,该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性 + * ownKeys方法返回的数组成员,只能是字符串或 Symbol 值,如果有其他类型的值,或者返回的根本不是数组,就会报错 + * 如果目标对象自身包含不可配置的属性,则该属性必须被ownKeys方法返回,否则报错 + * 具体来说,拦截以下操作 + Object.getOwnPropertyNames() + Object.getOwnPropertySymbols() + Object.keys() + * 使用Object.keys方法时,有三类属性会被ownKeys方法自动过滤,不会返回 + 目标对象上不存在的属性 + 属性名为 Symbol 值 + 不可遍历(enumerable)的属性 + + getOwnPropertyDescriptor(target, propKey) + * 拦截Object.getOwnPropertyDescriptor(proxy, propKey) + * 返回属性的描述对象 + + defineProperty(target, propKey, propDesc) + * 拦截Object.defineProperty(proxy, propKey, propDesc),Object.defineProperties(proxy, propDescs) + * defineProperty方法返回false,会导致添加新属性会抛出错误 + * 如果目标对象不可扩展(extensible),则defineProperty不能增加目标对象上不存在的属性,否则会报错 + * 如果目标对象的某个属性不可写(writable)或不可配置(configurable),则defineProperty方法不得改变这两个设置 + * 返回布尔值 + + preventExtensions(target) + * 拦截Object.preventExtensions(proxy) + * 该方法必须返回一个布尔值,否则会被自动转为布尔值 + * 返回布尔值 + + getPrototypeOf(target) + * 拦截Object.getPrototypeOf(proxy) + * 具体来说,拦截下面这些操作 + Object.prototype.__proto__ + Object.prototype.isPrototypeOf() + Object.getPrototypeOf() + Reflect.getPrototypeOf() + instanceof + * getPrototypeOf方法的返回值必须是对象或者null,否则报错 + * 如果目标对象不可扩展(extensible), getPrototypeOf方法必须返回目标对象的原型对象 + * 返回一个对象 + + isExtensible(target) + * 拦截Object.isExtensible(proxy) + * 该方法只能返回布尔值,否则返回值会被自动转为布尔值 + * 这个方法有一个强限制,它的返回值必须与目标对象的isExtensible属性保持一致,否则就会抛出错误 + * 返个布尔值 + + setPrototypeOf(target, proto) + * 拦截Object.setPrototypeOf(proxy, proto) + * 该方法只能返回布尔值,否则会被自动转为布尔值 + * 如果目标对象不可扩展(extensible),setPrototypeOf方法不得改变目标对象的原型 + * 返回布尔值,如果目标对象是函数,那么还有两种额外操作可以拦截 + + apply(target, object, args) + * 拦截 Proxy 实例作为函数调用的操作,比如proxy(...args),proxy.call(object, ...args),proxy.apply(...) + * 三个参数,分别是目标对象,目标对象的上下文对象(this),目标对象的参数数组 + * 直接调用Reflect.apply方法,也会被拦截 + + construct(target, args) + * 拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args) + * 接受两个参数,target:目标对象,args:构建函数的参数对象 + * construct方法返回的必须是一个对象,否则会报错 diff --git a/JavaSript/ES6/es6-Reflect.js b/JavaSript/ES6/es6-Reflect.js new file mode 100644 index 00000000..5acda6c7 --- /dev/null +++ b/JavaSript/ES6/es6-Reflect.js @@ -0,0 +1,92 @@ +---------------------------- +Reflect | +---------------------------- + # 椤惧悕鎬濅箟,鍙嶅皠鐩稿叧鐨勪竴涓潤鎬佺被 + # Reflect瀵硅薄涓嶱roxy瀵硅薄涓鏍,涔熸槸 ES6 涓轰簡鎿嶄綔瀵硅薄鑰屾彁渚涚殑鏂 API,Reflect瀵硅薄鐨勮璁$洰鐨勬湁杩欐牱鍑犱釜 + 1,灏哋bject瀵硅薄鐨勪竴浜涙槑鏄惧睘浜庤瑷鍐呴儴鐨勬柟娉(姣斿Object.defineProperty),鏀惧埌Reflect瀵硅薄涓 + * 鐜伴樁娈,鏌愪簺鏂规硶鍚屾椂鍦∣bject鍜孯eflect瀵硅薄涓婇儴缃,鏈潵鐨勬柊鏂规硶灏嗗彧閮ㄧ讲鍦≧eflect瀵硅薄涓 + * 涔熷氨鏄,浠嶳eflect瀵硅薄涓婂彲浠ユ嬁鍒拌瑷鍐呴儴鐨勬柟娉 + + 2,淇敼鏌愪簺Object鏂规硶鐨勮繑鍥炵粨鏋,璁╁叾鍙樺緱鏇村悎鐞 + * Object.defineProperty(obj, name, desc)鍦ㄦ棤娉曞畾涔夊睘鎬ф椂,浼氭姏鍑轰竴涓敊璇 + * Reflect.defineProperty(obj, name, desc)鍒欎細杩斿洖false + + 3, 璁㎡bject鎿嶄綔閮藉彉鎴愬嚱鏁拌涓 + * 鏌愪簺Object鎿嶄綔鏄懡浠ゅ紡,姣斿name in obj鍜宒elete obj[name] + * 鑰孯eflect.has(obj, name)鍜孯eflect.deleteProperty(obj, name)璁╁畠浠彉鎴愪簡鍑芥暟琛屼负 + + 4,Reflect瀵硅薄鐨勬柟娉曚笌Proxy瀵硅薄鐨勬柟娉曚竴涓瀵瑰簲,鍙鏄疨roxy瀵硅薄鐨勬柟娉,灏辫兘鍦≧eflect瀵硅薄涓婃壘鍒板搴旂殑鏂规硶 + * 杩欏氨璁㏄roxy瀵硅薄鍙互鏂逛究鍦拌皟鐢ㄥ搴旂殑Reflect鏂规硶,瀹屾垚榛樿琛屼负,浣滀负淇敼琛屼负鐨勫熀纭 + * 涔熷氨鏄,涓嶇Proxy鎬庝箞淇敼榛樿琛屼负,浣犳诲彲浠ュ湪Reflect涓婅幏鍙栭粯璁よ涓 + +---------------------------- +Reflect - 鏂规硶 | +---------------------------- + # 涓鍏13涓潤鎬佹柟娉('涓嶱roxy瀵硅薄鐨勬柟娉曚竴涓瀵瑰簲') + # 澶ч儴鍒嗕笌Object.xxx 鐩稿悓鐨勬柟娉曢兘鏈変竴涓笉鍚岀偣 + * Object.xxx 绗竴涓弬鏁颁笉鏄璞,涓嶄細鎶ラ敊 + * Reflect.xxx 绗竴涓弬鏁颁笉鏄璞,浼氭姤閿 + + apply(target, thisArg, args) + * 绛夊悓浜嶧unction.prototype.apply.call(func, thisArg, args),鐢ㄤ簬缁戝畾this瀵硅薄鍚庢墽琛岀粰瀹氬嚱鏁 + * 涓鑸潵璇,濡傛灉瑕佺粦瀹氫竴涓嚱鏁扮殑this瀵硅薄,鍙互杩欐牱鍐檉n.apply(obj, args) + * 浣嗘槸濡傛灉鍑芥暟瀹氫箟浜嗚嚜宸辩殑apply鏂规硶,灏卞彧鑳藉啓鎴怓unction.prototype.apply.call(fn, obj, args) + * 閲囩敤Reflect瀵硅薄鍙互绠鍖栬繖绉嶆搷浣 + + construct(target, args) + * 鏂规硶绛夊悓浜巒ew target(...args),杩欐彁渚涗簡涓绉嶄笉浣跨敤new,鏉ヨ皟鐢ㄦ瀯閫犲嚱鏁扮殑鏂规硶 + function Greeting(name) { + this.name = name; + } + // new 鐨勫啓娉 + const instance = new Greeting('寮犱笁'); + // Reflect.construct 鐨勫啓娉 + const instance = Reflect.construct(Greeting, ['寮犱笁']); + + get(target, name, receiver) + * 鏂规硶鏌ユ壘骞惰繑鍥瀟arget瀵硅薄鐨刵ame灞炴,濡傛灉娌℃湁璇ュ睘鎬,鍒欒繑鍥瀠ndefined + * 濡傛灉name灞炴ч儴缃蹭簡璇诲彇鍑芥暟(getter),鍒欒鍙栧嚱鏁扮殑this缁戝畾receive + * 濡傛灉绗竴涓弬鏁颁笉鏄璞,Reflect.get鏂规硶浼氭姤閿 + + set(target, name, value, receiver) + * 璁剧疆target瀵硅薄鐨刵ame灞炴х瓑浜巚alue + * 濡傛灉name灞炴ц缃簡璧嬪煎嚱鏁,鍒欒祴鍊煎嚱鏁扮殑this缁戝畾receiver + * 濡傛灉 Proxy 瀵硅薄鍜 Reflect 瀵硅薄鑱斿悎浣跨敤,鍓嶈呮嫤鎴祴鍊兼搷浣,鍚庤呭畬鎴愯祴鍊肩殑榛樿琛屼负,鑰屼笖浼犲叆浜 receiver,閭d箞Reflect.set浼氳Е鍙慞roxy.defineProperty鎷︽埅 + + defineProperty(target, name, desc) + * 鏂规硶鍩烘湰绛夊悓浜嶰bject.defineProperty,鐢ㄦ潵涓哄璞″畾涔夊睘鎬 + * 鏈潵,鍚庤呬細琚愭笎搴熼櫎,璇蜂粠鐜板湪寮濮嬪氨浣跨敤Reflect.defineProperty浠f浛瀹 + * 濡傛灉Reflect.defineProperty鐨勭涓涓弬鏁颁笉鏄璞,灏变細鎶涘嚭閿欒 + + + deleteProperty(target, name) + * 绛夊悓浜巇elete obj[name],鐢ㄤ簬鍒犻櫎瀵硅薄鐨勫睘鎬 + * 濡傛灉鍒犻櫎鎴愬姛,鎴栬呰鍒犻櫎鐨勫睘鎬т笉瀛樺湪,杩斿洖true + * 鍒犻櫎澶辫触,琚垹闄ょ殑灞炴т緷鐒跺瓨鍦,杩斿洖false + + has(target, name) + * 瀵瑰簲name in obj閲岄潰鐨刬n杩愮畻绗 + + ownKeys(target) + * 杩斿洖瀵硅薄鐨勬墍鏈夊睘鎬,鍩烘湰绛夊悓浜嶰bject.getOwnPropertyNames涓嶰bject.getOwnPropertySymbols涔嬪拰 + + isExtensible(target) + * 瀵瑰簲Object.isExtensible,杩斿洖涓涓竷灏斿,琛ㄧず褰撳墠瀵硅薄鏄惁鍙墿灞 + + preventExtensions(target) + * 瀵瑰簲Object.preventExtensions 鏂规硶,鐢ㄤ簬璁╀竴涓璞″彉涓轰笉鍙墿灞 + * 瀹冭繑鍥炰竴涓竷灏斿,琛ㄧず鏄惁鎿嶄綔鎴愬姛 + + getOwnPropertyDescriptor(target, name) + * 鍩烘湰绛夊悓浜嶰bject.getOwnPropertyDescriptor,鐢ㄤ簬寰楀埌鎸囧畾灞炴х殑鎻忚堪瀵硅薄,灏嗘潵浼氭浛浠f帀鍚庤 + + getPrototypeOf(target) + * 鐢ㄤ簬璇诲彇瀵硅薄鐨刜_proto__灞炴,瀵瑰簲Object.getPrototypeOf(obj) + * Reflect.getPrototypeOf鍜孫bject.getPrototypeOf鐨勪竴涓尯鍒槸,濡傛灉鍙傛暟涓嶆槸瀵硅薄,Object.getPrototypeOf浼氬皢杩欎釜鍙傛暟杞负瀵硅薄,鐒跺悗鍐嶈繍琛,鑰孯eflect.getPrototypeOf浼氭姤閿 + + setPrototypeOf(target, prototype) + * 鐢ㄤ簬璁剧疆瀵硅薄鐨刜_proto__灞炴,杩斿洖绗竴涓弬鏁板璞,瀵瑰簲Object.setPrototypeOf(obj, newProto) + * 濡傛灉绗竴涓弬鏁颁笉鏄璞,Object.setPrototypeOf浼氳繑鍥炵涓涓弬鏁版湰韬,鑰孯eflect.setPrototypeOf浼氭姤閿 + * 濡傛灉绗竴涓弬鏁版槸undefined鎴杗ull,Object.setPrototypeOf鍜孯eflect.setPrototypeOf閮戒細鎶ラ敊 + + \ No newline at end of file diff --git "a/JavaSript/ES6/es6-Set\345\222\214Map.js" "b/JavaSript/ES6/es6-Set\345\222\214Map.js" new file mode 100644 index 00000000..697a542e --- /dev/null +++ "b/JavaSript/ES6/es6-Set\345\222\214Map.js" @@ -0,0 +1,537 @@ +-------------------------------- +Set 鍜 Map 鏁版嵁缁撴瀯 | +-------------------------------- + 1,Set + 2,WeakSet + 3,Map + 4,WeakMap + +-------------------------------- +Set | +-------------------------------- + # ES6 鎻愪緵浜嗘柊鐨勬暟鎹粨鏋 Set + * 瀹冪被浼间簬鏁扮粍,浣嗘槸鎴愬憳鐨勫奸兘鏄敮涓鐨,娌℃湁閲嶅鐨勫 + * 璺焜ava鐨剆et宸笉澶氭槸涓涓痉琛 + + # Set 鏈韩鏄竴涓瀯閫犲嚱鏁,鐢ㄦ潵鐢熸垚 Set 鏁版嵁缁撴瀯 + const s = new Set(); + [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x)); + for (let i of s) { + console.log(i); + } + // 2 3 5 4 娌℃湁閲嶅鐨 + + # Set 鍑芥暟鍙互鎺ュ彈涓涓暟缁(鎴栬呭叿鏈 iterable 鎺ュ彛鐨勫叾浠栨暟鎹粨鏋)浣滀负鍙傛暟,鐢ㄦ潵鍒濆鍖 + // 渚嬩竴 + const set = new Set([1, 2, 3, 4, 4]); + [...set] //杩樺彲浠ュ幓闄ゆ暟缁勭殑閲嶅鎴愬憳 + // [1, 2, 3, 4] + + // 渚嬩簩 + const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]); + items.size // 5 + + // 渚嬩笁 + const set = new Set(document.querySelectorAll('div')); + set.size // 56 + + // 绫讳技浜 + const set = new Set(); + document.querySelectorAll('div').forEach(div => set.add(div)); + + set.size // 56 + + * 渚嬩竴鍜屼緥浜岄兘鏄疭et鍑芥暟鎺ュ彈鏁扮粍浣滀负鍙傛暟,渚嬩笁鏄帴鍙楃被浼兼暟缁勭殑瀵硅薄浣滀负鍙傛暟 + + # 鍚 Set 鍔犲叆鍊肩殑鏃跺,涓嶄細鍙戠敓绫诲瀷杞崲,鎵浠5鍜"5"鏄袱涓笉鍚岀殑鍊 + * Set 鍐呴儴鍒ゆ柇涓や釜鍊兼槸鍚︿笉鍚,浣跨敤鐨勭畻娉曞彨鍋"Same-value-zero equality" + * 瀹冪被浼间簬绮剧‘鐩哥瓑杩愮畻绗(===),涓昏鐨勫尯鍒槸NaN绛変簬鑷韩,鑰岀簿纭浉绛夎繍绠楃璁や负NaN涓嶇瓑浜庤嚜韬 + + let set = new Set(); + let a = NaN; + let b = NaN; + set.add(a); + set.add(b); + console.log(set); // Set(1)聽{NaN} + + * Set 瀹炰緥娣诲姞浜嗕袱涓狽aN,浣嗘槸鍙兘鍔犲叆涓涓,杩欒〃鏄,鍦 Set 鍐呴儴,涓や釜NaN鏄浉绛 + +-------------------------------- +Set 瀹炰緥鐨勫睘鎬у拰鏂规硶 | +-------------------------------- + # 鎿嶄綔鏁版嵁绯诲垪鐨勬柟娉 + + Set.prototype.constructor + * 鏋勯犲嚱鏁,榛樿灏辨槸Set鍑芥暟 + Set.prototype.size + * 杩斿洖Set瀹炰緥鐨勬垚鍛樻绘暟 + + + add(value) + * 娣诲姞鏌愪釜鍊,杩斿洖 Set 缁撴瀯鏈韩 + delete(value) + * 鍒犻櫎鏌愪釜鍊,杩斿洖涓涓竷灏斿,琛ㄧず鍒犻櫎鏄惁鎴愬姛 + has(value) + * 杩斿洖涓涓竷灏斿,琛ㄧず璇ュ兼槸鍚︿负Set鐨勬垚鍛 + clear() + * 娓呴櫎鎵鏈夋垚鍛,娌℃湁杩斿洖鍊 + + # 閬嶅巻鏁版嵁鐨勬柟娉 + keys() + * 杩斿洖閿悕鐨勯亶鍘嗗櫒 + values() + * 杩斿洖閿肩殑閬嶅巻鍣 + entries() + * 杩斿洖閿煎鐨勯亶鍘嗗櫒 + forEach() + * 浣跨敤鍥炶皟鍑芥暟閬嶅巻姣忎釜鎴愬憳 + + * 浠ヤ笂鍓嶄花鏂规硶閮芥槸杩斿洖杩唬鍣 + * 鐢变簬 Set 缁撴瀯娌℃湁閿悕,鍙湁閿(鎴栬呰閿悕鍜岄敭鍊兼槸鍚屼竴涓),鎵浠eys鏂规硶鍜寁alues鏂规硶鐨勮涓哄畬鍏ㄤ竴鑷 + * entries鏂规硶杩斿洖鐨勯亶鍘嗗櫒,鍚屾椂鍖呮嫭閿悕鍜岄敭鍊,鎵浠ユ瘡娆¤緭鍑轰竴涓暟缁,瀹冪殑涓や釜鎴愬憳瀹屽叏鐩哥瓑 + + # Set鐨勯亶鍘嗛『搴忓氨鏄彃鍏ラ『搴,杩欎釜鐗规ф湁鏃堕潪甯告湁鐢,姣斿浣跨敤 Set 淇濆瓨涓涓洖璋冨嚱鏁板垪琛,璋冪敤鏃跺氨鑳戒繚璇佹寜鐓ф坊鍔犻『搴忚皟鐢 + # Set 缁撴瀯鐨勫疄渚嬮粯璁ゅ彲閬嶅巻,瀹冪殑榛樿閬嶅巻鍣ㄧ敓鎴愬嚱鏁板氨鏄畠鐨剉alues鏂规硶 + Set.prototype[Symbol.iterator] === Set.prototype.values + // true + * 杩欐剰鍛崇潃,鍙互鐪佺暐values鏂规硶,鐩存帴鐢╢or...of寰幆閬嶅巻 Set + + # Set 缁撴瀯鐨勫疄渚嬩笌鏁扮粍涓鏍,涔熸嫢鏈塮orEach鏂规硶,鐢ㄤ簬瀵规瘡涓垚鍛樻墽琛屾煇绉嶆搷浣,娌℃湁杩斿洖鍊 + set = new Set([1, 4, 9]); + set.forEach((value, key) => console.log(key + ' : ' + value)) + // 1 : 1 + // 4 : 4 + // 9 : 9 + + # 閬嶅巻搴旂敤 + * 鎵╁睍杩愮畻绗(...)鍐呴儴浣跨敤for...of寰幆,鎵浠ヤ篃鍙互鐢ㄤ簬 Set 缁撴瀯 + let set = new Set(['red', 'green', 'blue']); + let arr = [...set]; + // ['red', 'green', 'blue'] + + * 鎵╁睍杩愮畻绗﹀拰 Set 缁撴瀯鐩哥粨鍚,灏卞彲浠ュ幓闄ゆ暟缁勭殑閲嶅鎴愬憳 + let arr = [3, 5, 2, 2, 5, 5]; + let unique = [...new Set(arr)]; + // [3, 5, 2] + + * 鏁扮粍鐨刴ap鍜宖ilter鏂规硶涔熷彲浠ラ棿鎺ョ敤浜 Set 浜 + let set = new Set([1, 2, 3]); + set = new Set([...set].map(x => x * 2)); + // 杩斿洖Set缁撴瀯锛歿2, 4, 6} + + let set = new Set([1, 2, 3, 4, 5]); + set = new Set([...set].filter(x => (x % 2) == 0)); + // 杩斿洖Set缁撴瀯锛歿2, 4} + + * 鐢 Set 鍙互寰堝鏄撳湴瀹炵幇骞堕泦(Union),浜ら泦(Intersect)鍜屽樊闆(Difference) + let a = new Set([1, 2, 3]); + let b = new Set([4, 3, 2]); + + // 骞堕泦 + let union = new Set([...a, ...b]); + // Set {1, 2, 3, 4} + + // 浜ら泦 + let intersect = new Set([...a].filter(x => b.has(x))); + // set {2, 3} + + // 宸泦 + let difference = new Set([...a].filter(x => !b.has(x))); + // Set {1} + + * 濡傛灉鎯冲湪閬嶅巻鎿嶄綔涓,鍚屾鏀瑰彉鍘熸潵鐨 Set 缁撴瀯,鐩墠娌℃湁鐩存帴鐨勬柟娉,浣嗘槸鏈変袱绉嶅彉閫氭柟娉 + * 涓绉嶆槸鍒╃敤鍘 Set 缁撴瀯鏄犲皠鍑轰竴涓柊鐨勭粨鏋,鐒跺悗璧嬪肩粰鍘熸潵鐨 Set 缁撴瀯 + * 鍙︿竴绉嶆槸鍒╃敤Array.from鏂规硶 + // 鏂规硶涓 + let set = new Set([1, 2, 3]); + set = new Set([...set].map(val => val * 2)); + // set鐨勫兼槸2, 4, 6 + + // 鏂规硶浜 + let set = new Set([1, 2, 3]); + set = new Set(Array.from(set, val => val * 2)); + // set鐨勫兼槸2, 4, 6 + + +-------------------------------- +WeakSet | +-------------------------------- + # WeakSet 缁撴瀯涓 Set 绫讳技,涔熸槸涓嶉噸澶嶇殑鍊肩殑闆嗗悎,浣嗘槸,瀹冧笌 Set 鏈変袱涓尯鍒 + 1,WeakSet 鐨勬垚鍛樺彧鑳芥槸瀵硅薄,鑰屼笉鑳芥槸鍏朵粬绫诲瀷鐨勫 + const ws = new WeakSet(); + ws.add(1) + // TypeError: Invalid value used in weak set + ws.add(Symbol()) + // TypeError: invalid value used in weak set + + 2,WeakSet 涓殑瀵硅薄閮芥槸寮卞紩鐢 + * 鍗冲瀮鍦惧洖鏀舵満鍒朵笉鑰冭檻 WeakSet 瀵硅瀵硅薄鐨勫紩鐢 + * 涔熷氨鏄,濡傛灉鍏朵粬瀵硅薄閮戒笉鍐嶅紩鐢ㄨ瀵硅薄,閭d箞鍨冨溇鍥炴敹鏈哄埗浼氳嚜鍔ㄥ洖鏀惰瀵硅薄鎵鍗犵敤鐨勫唴瀛,涓嶈冭檻璇ュ璞¤繕瀛樺湪浜 WeakSet 涔嬩腑 + + # WeakSet 鐨勬垚鍛樻槸涓嶉傚悎寮曠敤鐨 + * 鍥犱负瀹冧細闅忔椂娑堝け,鍙﹀,鐢变簬 WeakSet 鍐呴儴鏈夊灏戜釜鎴愬憳,鍙栧喅浜庡瀮鍦惧洖鏀舵満鍒舵湁娌℃湁杩愯,杩愯鍓嶅悗寰堝彲鑳芥垚鍛樹釜鏁版槸涓嶄竴鏍风殑 + * 鑰屽瀮鍦惧洖鏀舵満鍒朵綍鏃惰繍琛屾槸涓嶅彲棰勬祴鐨,鍥犳 ES6 瑙勫畾 WeakSet 涓嶅彲閬嶅巻 + + # 璇硶 + * WeakSet 鏄竴涓瀯閫犲嚱鏁,鍙互浣跨敤new鍛戒护,鍒涘缓 WeakSet 鏁版嵁缁撴瀯 + const ws = new WeakSet(); + + * WeakSet 鍙互鎺ュ彈涓涓暟缁勬垨绫讳技鏁扮粍鐨勫璞′綔涓哄弬鏁 + * 瀹為檯涓,浠讳綍鍏锋湁 Iterable 鎺ュ彛鐨勫璞,閮藉彲浠ヤ綔涓 WeakSet 鐨勫弬鏁 + * 璇ユ暟缁勭殑鎵鏈夋垚鍛,閮戒細鑷姩鎴愪负 WeakSet 瀹炰緥瀵硅薄鐨勬垚鍛 + const a = [[1, 2], [3, 4]]; + const ws = new WeakSet(a); + // WeakSet {[1, 2], [3, 4]} + + * '鏄暟缁勭殑鎴愬憳鎴愪负 WeakSet 鐨勬垚鍛,鑰屼笉鏄暟缁勬湰韬,杩欐剰鍛崇潃,鏁扮粍鐨勬垚鍛樺彧鑳芥槸瀵硅薄' + const b = [3, 4]; //鏁版嵁鎴愬憳闈炲璞,鎶ラ敊 + const ws = new WeakSet(b); + // Uncaught TypeError: Invalid value used in weak set(鈥) + + * WeakSet 缁撴瀯鏈変互涓嬩笁涓柟娉 + WeakSet.prototype.add(value) + * 鍚 WeakSet 瀹炰緥娣诲姞涓涓柊鎴愬憳 + + WeakSet.prototype.delete(value) + * 娓呴櫎 WeakSet 瀹炰緥鐨勬寚瀹氭垚鍛 + + WeakSet.prototype.has(value) + * 杩斿洖涓涓竷灏斿,琛ㄧず鏌愪釜鍊兼槸鍚﹀湪 WeakSet 瀹炰緥涔嬩腑 + + * WeakSet 娌℃湁size灞炴,娌℃湁鍔炴硶閬嶅巻瀹冪殑鎴愬憳 + + + # WeakSet 鐨勪竴涓敤澶,鏄偍瀛 DOM 鑺傜偣,鑰屼笉鐢ㄦ媴蹇冭繖浜涜妭鐐逛粠鏂囨。绉婚櫎鏃,浼氬紩鍙戝唴瀛樻硠婕 + # demo + const foos = new WeakSet() + + class Foo { + constructor() { + foos.add(this) + } + method () { + if (!foos.has(this)) { + throw new TypeError('Foo.prototype.method 鍙兘鍦‵oo鐨勫疄渚嬩笂璋冪敤锛'); + } + } + } + + * 涓婇潰浠g爜淇濊瘉浜咶oo鐨勫疄渚嬫柟娉,鍙兘鍦‵oo鐨勫疄渚嬩笂璋冪敤 + * 杩欓噷浣跨敤 WeakSet 鐨勫ソ澶勬槸,foos瀵瑰疄渚嬬殑寮曠敤,涓嶄細琚鍏ュ唴瀛樺洖鏀舵満鍒,鎵浠ュ垹闄ゅ疄渚嬬殑鏃跺,涓嶇敤鑰冭檻foos,涔熶笉浼氬嚭鐜板唴瀛樻硠婕 + + +-------------------------------- +Map | +-------------------------------- + # JavaScript 鐨勫璞(Object),鏈川涓婃槸閿煎鐨勯泦鍚(Hash 缁撴瀯),浣嗘槸浼犵粺涓婂彧鑳界敤瀛楃涓插綋浣滈敭,杩欑粰瀹冪殑浣跨敤甯︽潵浜嗗緢澶х殑闄愬埗 + const data = {}; + const element = document.getElementById('myDiv'); + + //鏈剰鏄娇鐢 dom 鑺傜偣瀵硅薄浣滀负閿 + data[element] = 'metadata'; + //瀹為檯涓婁細琚浆鎹负瀛楃涓 + data['[object HTMLDivElement]'] // "metadata" + + # ES6 鎻愪緵浜 Map 鏁版嵁缁撴瀯 + * 瀹冪被浼间簬瀵硅薄,涔熸槸閿煎鐨勯泦鍚 + * "閿"鐨勮寖鍥翠笉闄愪簬瀛楃涓,鍚勭绫诲瀷鐨勫(鍖呮嫭瀵硅薄)閮藉彲浠ュ綋浣滈敭 + * Object 缁撴瀯鎻愪緵浜"瀛楃涓测斿"鐨勫搴,Map 缁撴瀯鎻愪緵浜"鍊尖斿"鐨勫搴,鏄竴绉嶆洿瀹屽杽鐨 Hash 缁撴瀯瀹炵幇 + * 濡傛灉闇瑕"閿煎"鐨勬暟鎹粨鏋,Map 姣 Object 鏇村悎閫 + * 濡傛灉瀵瑰悓涓涓敭澶氭璧嬪,鍚庨潰鐨勫煎皢瑕嗙洊鍓嶉潰鐨勫 + * 濡傛灉璇诲彇涓涓笉瀛樺湪鐨勯敭,杩斿洖undefined + * Map 鐨勯敭瀹為檯涓婃槸璺熷唴瀛樺湴鍧缁戝畾鐨,鍙鍐呭瓨鍦板潃涓嶄竴鏍,灏辫涓轰袱涓敭 + * 濡傛灉 Map 鐨勯敭鏄竴涓畝鍗曠被鍨嬬殑鍊(鏁板瓧,瀛楃涓,甯冨皵鍊),鍒欏彧瑕佷袱涓间弗鏍肩浉绛,Map 灏嗗叾瑙嗕负涓涓敭 + * 0鍜-0灏辨槸涓涓敭 + * 甯冨皵鍊紅rue鍜屽瓧绗︿覆true鍒欐槸涓や釜涓嶅悓鐨勯敭 + * undefined鍜宯ull涔熸槸涓や釜涓嶅悓鐨勯敭,铏界劧NaN涓嶄弗鏍肩浉绛変簬鑷韩,浣 Map 灏嗗叾瑙嗕负鍚屼竴涓敭(null鍜寀ndefined涔熷彲浠ヤ綔涓洪敭) + + const m = new Map(); + const o = {p: 'Hello World'}; + m.set(o, 'content'); + m.get(o); // "content" + m.has(o); // true + m.delete(o); // true + m.has(o); // false + + # Map鐨勬瀯閫,鏀寔key-value鐨勬暟鎹牸寮 + const map = new Map([ + ['name', '寮犱笁'], + ['title', 'Author'] + ]); + + map.size // 2 + map.has('name') // true + map.get('name') // "寮犱笁" + map.has('title') // true + map.get('title') // "Author" + + * 瀹為檯涓婃墽琛岀殑鏄笅闈㈢殑绠楁硶 + + [['name', '寮犱笁'],['title', 'Author']].forEach(i => void map.set(i[0],i[1])); + + + # 浜嬪疄涓,涓嶄粎浠呮槸鏁扮粍,浠讳綍鍏锋湁 Iterator 鎺ュ彛,涓旀瘡涓垚鍛橀兘鏄竴涓弻鍏冪礌鐨勬暟缁勭殑鏁版嵁缁撴瀯(璇﹁銆奍terator銆嬩竴绔)閮藉彲浠ュ綋浣淢ap鏋勯犲嚱鏁扮殑鍙傛暟 + * 杩欏氨鏄,Set鍜孧ap閮藉彲浠ョ敤鏉ョ敓鎴愭柊鐨 Map + const set = new Set([ + ['foo', 1], + ['bar', 2] + ]); + //浣跨敤鍊间负kv缁撴瀯鐨剆et鏉ユ瀯寤簃ap + const m1 = new Map(set); + m1.get('foo') // 1 + + //浣跨敤宸茬粡瀛樺湪鐨刴ap鏉ユ瀯寤簃ap + const m2 = new Map([['baz', 3]]); + const m3 = new Map(m2); + m3.get('baz') // 3 + + # 瀹炰緥鐨勫睘鎬у拰鎿嶄綔鏂规硶 + size + * 杩斿洖鎴愬憳鏁伴噺 + + set(k,v) + * 娣诲姞涓涓敭鍊 + * 璇ユ柟娉曡繑鍥 this,鎵浠ュ彲浠ヤ娇鐢ㄩ摼寮忚皟鐢 + let map = new Map() + .set(1, 'a') + .set(2, 'b') + .set(3, 'c'); + + get(k) + * get鏂规硶璇诲彇key瀵瑰簲鐨勯敭鍊,濡傛灉鎵句笉鍒発ey,杩斿洖undefined + + has(k) + * 鍒ゆ柇鏄惁鏈塳ey + + delete(k) + * 鍒犻櫎鎸囧畾鐨凨ey,鍒犻櫎鎴愬姛杩斿洖 true,鍒犻櫎澶辫触杩斿洖 false + + clear() + * 娓呯┖ + + keys() + * 杩斿洖閿悕鐨勯亶鍘嗗櫒 + values() + * 杩斿洖閿肩殑閬嶅巻鍣 + entries() + * 杩斿洖鎵鏈夋垚鍛樼殑閬嶅巻鍣 + for (let item of map.entries()) { + console.log(item[0], item[1]); + } + * Map 缁撴瀯鐨勯粯璁ら亶鍘嗗櫒鎺ュ彛(Symbol.iterator灞炴)灏辨槸entries鏂规硶 + for (let [key, value] of map) { + console.log(key, value); + } + forEach() + * 閬嶅巻 Map 鐨勬墍鏈夋垚鍛 + * Map 鐨勯亶鍘嗛『搴忓氨鏄彃鍏ラ『搴 + map.forEach(function(value, key, map) { + console.log("Key: %s, Value: %s", key, value); + }); + + + # Map 缁撴瀯杞负鏁扮粍缁撴瀯,姣旇緝蹇熺殑鏂规硶鏄娇鐢ㄦ墿灞曡繍绠楃(...) + const map = new Map([ + [1, 'one'], + [2, 'two'], + [3, 'three'], + ]); + [...map.keys()] + // [1, 2, 3] + [...map.values()] + // ['one', 'two', 'three'] + [...map.entries()] + // [[1,'one'], [2, 'two'], [3, 'three']] + [...map] + // [[1,'one'], [2, 'two'], [3, 'three']] + + # 缁撳悎鏁扮粍鐨刴ap鏂规硶,filter鏂规硶,鍙互瀹炵幇 Map 鐨勯亶鍘嗗拰杩囨护(Map 鏈韩娌℃湁map鍜宖ilter鏂规硶) + const map0 = new Map() + .set(1, 'a') + .set(2, 'b') + .set(3, 'c'); + + const map1 = new Map( + [...map0].filter(([k, v]) => k < 3) + ); + // 浜х敓 Map 缁撴瀯 {1 => 'a', 2 => 'b'} + + const map2 = new Map( + [...map0].map(([k, v]) => [k * 2, '_' + v]) + ); + // 浜х敓 Map 缁撴瀯 {2 => '_a', 4 => '_b', 6 => '_c'} + + # 涓庡叾浠栨暟鎹粨鏋勭殑浜掔浉杞崲 + * Map 杞负鏁扮粍,鎵╁睍杩愮畻绗 + const myMap = new Map() + .set(true, 7) + .set({foo: 3}, ['abc']); + [...myMap] + // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] + + * 鏁扮粍 杞负 Map + new Map([ + [true, 7], + [{foo: 3}, ['abc']] + ]) + + * Map 杞负瀵硅薄 + * 濡傛灉鎵鏈 Map 鐨勯敭閮芥槸瀛楃涓,瀹冨彲浠ユ棤鎹熷湴杞负瀵硅薄 + function strMapToObj(strMap) { + let obj = Object.create(null); + for (let [k,v] of strMap) { + obj[k] = v; + } + return obj; + } + const myMap = new Map() + .set('yes', true) + .set('no', false); + strMapToObj(myMap) + // { yes: true, no: false } + + * 瀵硅薄杞负map + function objToStrMap(obj) { + let strMap = new Map(); + for (let k of Object.keys(obj)) { + strMap.set(k, obj[k]); + } + return strMap; + } + objToStrMap({yes: true, no: false}) + // Map {"yes" => true, "no" => false} + + * Map 杞负 JSON + * Map 杞负 JSON 瑕佸尯鍒嗕袱绉嶆儏鍐 + * 涓绉嶆儏鍐垫槸,Map 鐨勯敭鍚嶉兘鏄瓧绗︿覆,杩欐椂鍙互閫夋嫨杞负瀵硅薄 JSON + function strMapToJson(strMap) { + return JSON.stringify(strMapToObj(strMap)); + } + let myMap = new Map().set('yes', true).set('no', false); + strMapToJson(myMap) + // '{"yes":true,"no":false}' + * 鍙︿竴绉嶆儏鍐垫槸,Map 鐨勯敭鍚嶆湁闈炲瓧绗︿覆,杩欐椂鍙互閫夋嫨杞负鏁扮粍 JSON + function mapToArrayJson(map) { + return JSON.stringify([...map]); + } + let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']); + mapToArrayJson(myMap) + // '[[true,7],[{"foo":3},["abc"]]]' + + * JSON 杞负 Map + * JSON 杞负 Map,姝e父鎯呭喌涓,鎵鏈夐敭鍚嶉兘鏄瓧绗︿覆 + function jsonToStrMap(jsonStr) { + return objToStrMap(JSON.parse(jsonStr)); + } + jsonToStrMap('{"yes": true, "no": false}') + // Map {'yes' => true, 'no' => false} + + * 鏈変竴绉嶇壒娈婃儏鍐,鏁翠釜 JSON 灏辨槸涓涓暟缁,涓旀瘡涓暟缁勬垚鍛樻湰韬,鍙堟槸涓涓湁涓や釜鎴愬憳鐨勬暟缁 + * 杩欐椂,瀹冨彲浠ヤ竴涓瀵瑰簲鍦拌浆涓 Map,杩欏線寰鏄 Map 杞负鏁扮粍 JSON 鐨勯嗘搷浣 + function jsonToMap(jsonStr) { + return new Map(JSON.parse(jsonStr)); + } + jsonToMap('[[true,7],[{"foo":3},["abc"]]]') + // Map {true => 7, Object {foo: 3} => ['abc']} + +------------------------------------ +WeakMap | +------------------------------------ + # WeakMap缁撴瀯涓嶮ap缁撴瀯绫讳技,涔熸槸鐢ㄤ簬鐢熸垚閿煎鐨勯泦鍚 + # WeakMap涓嶮ap鐨勫尯鍒湁涓ょ偣 + 1,WeakMap鍙帴鍙楀璞′綔涓洪敭鍚(null闄ゅ),涓嶆帴鍙楀叾浠栫被鍨嬬殑鍊间綔涓洪敭鍚 + const map = new WeakMap(); + map.set(1, 2) + // TypeError: 1 is not an object! 浣跨敤鏁板瓧,鎶ラ敊 + map.set(Symbol(), 2) + // TypeError: Invalid value used as weak map key 浣跨敤 Symbol, 鎶ラ敊 + map.set(null, 2) + // TypeError: Invalid value used as weak map key 浣跨敤null,鎶ラ敊 + + 2,WeakMap鐨勯敭鍚嶆墍鎸囧悜鐨勫璞,涓嶈鍏ュ瀮鍦惧洖鏀舵満鍒 + * 濡傛灉浣犺寰瀵硅薄涓婃坊鍔犳暟鎹,鍙堜笉鎯冲共鎵板瀮鍦惧洖鏀舵満鍒,灏卞彲浠ヤ娇鐢 WeakMap + * 涓涓吀鍨嬪簲鐢ㄥ満鏅槸,鍦ㄧ綉椤电殑 DOM 鍏冪礌涓婃坊鍔犳暟鎹,灏卞彲浠ヤ娇鐢╓eakMap缁撴瀯,褰撹 DOM 鍏冪礌琚竻闄,鍏舵墍瀵瑰簲鐨刉eakMap璁板綍灏变細鑷姩琚Щ闄 + const wm = new WeakMap(); + const element = document.getElementById('example'); + wm.set(element, 'some information'); + wm.get(element) // "some information" + + + # WeakMap鐨勪笓鐢ㄥ満鍚堝氨鏄,瀹冪殑閿墍瀵瑰簲鐨勫璞鍙兘浼氬湪灏嗘潵娑堝け,WeakMap缁撴瀯鏈夊姪浜庨槻姝㈠唴瀛樻硠婕 + # 娉ㄦ剰,WeakMap 寮卞紩鐢ㄧ殑鍙槸閿悕,鑰屼笉鏄敭鍊,閿间緷鐒舵槸姝e父寮曠敤 + const wm = new WeakMap(); + let key = {}; + let obj = {foo: 1}; + + wm.set(key, obj); + obj = null; + wm.get(key) + // Object {foo: 1} + //閿紀bj鏄甯稿紩鐢,鎵浠,鍗充娇鍦 WeakMap 澶栭儴娑堥櫎浜唎bj鐨勫紩鐢,WeakMap 鍐呴儴鐨勫紩鐢ㄤ緷鐒跺瓨鍦 + + + # WeakMap 鐨勮娉 + * WeakMap 涓 Map 鍦 API 涓婄殑鍖哄埆涓昏鏄袱涓 + * 涓鏄病鏈夐亶鍘嗘搷浣(鍗虫病鏈塳ey(),values()鍜宔ntries()鏂规硶),涔熸病鏈塻ize灞炴 + * 浜屾槸鏃犳硶娓呯┖,鍗充笉鏀寔clear鏂规硶 + + * 鍥犳,WeakMap鍙湁鍥涗釜鏂规硶鍙敤:get(),set(),has(),delete() + + # WeakMap 鐨勭ず渚 + .. + + # 鐢ㄩ + * WeakMap 搴旂敤鐨勫吀鍨嬪満鍚堝氨鏄 DOM 鑺傜偣浣滀负閿悕 + + let myElement = document.getElementById('logo'); + + let myWeakmap = new WeakMap(); + + myWeakmap.set(myElement, {timesClicked: 0}); + + myElement.addEventListener('click', function() { + let logoData = myWeakmap.get(myElement); + logoData.timesClicked++; + }, false); + + /** + myElement鏄竴涓 DOM 鑺傜偣,姣忓綋鍙戠敓click浜嬩欢,灏辨洿鏂颁竴涓嬬姸鎬 + 鎴戜滑灏嗚繖涓姸鎬佷綔涓洪敭鍊兼斁鍦 WeakMap 閲,瀵瑰簲鐨勯敭鍚嶅氨鏄痬yElement,涓鏃﹁繖涓 DOM 鑺傜偣鍒犻櫎,璇ョ姸鎬佸氨浼氳嚜鍔ㄦ秷澶,涓嶅瓨鍦ㄥ唴瀛樻硠婕忛闄 + **/ + + * WeakMap 鐨勫彟涓涓敤澶勬槸閮ㄧ讲绉佹湁灞炴 + const _counter = new WeakMap(); + const _action = new WeakMap(); + + class Countdown { + constructor(counter, action) { + _counter.set(this, counter); + _action.set(this, action); + } + dec() { + let counter = _counter.get(this); + if (counter < 1) return; + counter--; + _counter.set(this, counter); + if (counter === 0) { + _action.get(this)(); + } + } + } + + const c = new Countdown(2, () => console.log('DONE')); + + c.dec() + c.dec() + // DONE + + //Countdown绫荤殑涓や釜鍐呴儴灞炴counter鍜宊action,鏄疄渚嬬殑寮卞紩鐢,鎵浠ュ鏋滃垹闄ゅ疄渚,瀹冧滑涔熷氨闅忎箣娑堝け,涓嶄細閫犳垚鍐呭瓨娉勬紡銆 + + + + + + + + + diff --git a/JavaSript/ES6/es6-Symbol.js b/JavaSript/ES6/es6-Symbol.js new file mode 100644 index 00000000..a66c5faf --- /dev/null +++ b/JavaSript/ES6/es6-Symbol.js @@ -0,0 +1,616 @@ +------------------------ +Symbol | +------------------------ + 1,姒傝堪 + 2,浣滀负灞炴у悕鐨 Symbol + 3,瀹炰緥:娑堥櫎榄旀湳瀛楃涓 + 4,灞炴у悕鐨勯亶鍘 + 5,Symbol.for()锛孲ymbol.keyFor() + 6,瀹炰緥锛氭ā鍧楃殑 Singleton 妯″紡 + 7,鍐呯疆鐨 Symbol 鍊 + +------------------------ +姒傝堪 | +------------------------ + # ES5 鐨勫璞″睘鎬у悕閮芥槸瀛楃涓,杩欏鏄撻犳垚灞炴у悕鐨勫啿绐 + * 浣犱娇鐢ㄤ簡涓涓粬浜烘彁渚涚殑瀵硅薄,浣嗗張鎯充负杩欎釜瀵硅薄娣诲姞鏂扮殑鏂规硶(mixin 妯″紡),鏂版柟娉曠殑鍚嶅瓧灏辨湁鍙兘涓庣幇鏈夋柟娉曚骇鐢熷啿绐 + * 濡傛湁涓绉嶆満鍒,淇濊瘉姣忎釜灞炴х殑鍚嶅瓧閮芥槸鐙竴鏃犱簩鐨勫氨濂戒簡,杩欐牱灏变粠鏍规湰涓婇槻姝㈠睘鎬у悕鐨勫啿绐 + * 杩欏氨鏄 ES6 寮曞叆Symbol鐨勫師鍥 + + # ES6 寮曞叆浜嗕竴绉嶆柊鐨勫師濮嬫暟鎹被鍨婼ymbol,琛ㄧず鐙竴鏃犱簩鐨勫 + * 瀹冩槸 JavaScript 璇█鐨勭涓冪鏁版嵁绫诲瀷 + undefined,null,Boolean,String,Number,Object + + # Symbol 鍊奸氳繃Symbol鍑芥暟鐢熸垚,杩欏氨鏄,瀵硅薄鐨勫睘鎬у悕鐜板湪鍙互鏈変袱绉嶇被鍨 + * 涓绉嶆槸鍘熸潵灏辨湁鐨勫瓧绗︿覆 + * 涓绉嶅氨鏄柊澧炵殑 Symbol 绫诲瀷 + * 鍑℃槸灞炴у悕灞炰簬 Symbol 绫诲瀷,灏遍兘鏄嫭涓鏃犱簩鐨,鍙互淇濊瘉涓嶄細涓庡叾浠栧睘鎬у悕浜х敓鍐茬獊 + + let s = Symbol(); + typeof s // "symbol" + + * 涓婇潰浠g爜涓,鍙橀噺s灏辨槸涓涓嫭涓鏃犱簩鐨勫 + * typeof杩愮畻绗︾殑缁撴灉,琛ㄦ槑鍙橀噺s鏄 Symbol 鏁版嵁绫诲瀷 + * 鑰屼笉鏄瓧绗︿覆涔嬬被鐨勫叾浠栫被鍨 + + # Symbol鍑芥暟鍓嶄笉鑳戒娇鐢╪ew鍛戒护,鍚﹀垯浼氭姤閿 + * 鏄洜涓虹敓鎴愮殑 Symbol 鏄竴涓師濮嬬被鍨嬬殑鍊,涓嶆槸瀵硅薄 + * 涔熷氨鏄,鐢变簬 Symbol 鍊间笉鏄璞,鎵浠ヤ笉鑳芥坊鍔犲睘鎬 + * 鍩烘湰涓,瀹冩槸涓绉嶇被浼间簬瀛楃涓茬殑鏁版嵁绫诲瀷 + + # Symbol鍑芥暟鍙互鎺ュ彈涓涓瓧绗︿覆浣滀负鍙傛暟 + * 琛ㄧず瀵 Symbol 瀹炰緥鐨勬弿杩 + * 涓昏鏄负浜嗗湪鎺у埗鍙版樉绀,鎴栬呰浆涓哄瓧绗︿覆鏃,姣旇緝瀹规槗鍖哄垎 + let s1 = Symbol('foo'); + let s2 = Symbol('bar'); + + s1 // Symbol(foo) + s2 // Symbol(bar) + + s1.toString() // "Symbol(foo)" + s2.toString() // "Symbol(bar)" + + * 濡傛灉涓嶅姞鍙傛暟,瀹冧滑鍦ㄦ帶鍒跺彴鐨勮緭鍑洪兘鏄疭ymbol(),涓嶅埄浜庡尯鍒 + + # 濡傛灉 Symbol 鐨勫弬鏁版槸涓涓璞,灏变細璋冪敤璇ュ璞$殑toString鏂规硶,灏嗗叾杞负瀛楃涓,鐒跺悗鎵嶇敓鎴愪竴涓 Symbol 鍊 + const obj = { + toString() { + return 'abc'; + } + }; + const sym = Symbol(obj); + + console.log(sym); // Symbol(abc) + + # Symbol鍑芥暟鐨勫弬鏁板彧鏄〃绀哄褰撳墠 Symbol 鍊肩殑鎻忚堪 + * 鍥犳鐩稿悓鍙傛暟鐨凷ymbol鍑芥暟鐨勮繑鍥炲兼槸涓嶇浉绛夌殑 + // 娌℃湁鍙傛暟鐨勬儏鍐 + let s1 = Symbol(); + let s2 = Symbol(); + console.log(s1 === s2); // false + // 鏈夊弬鏁扮殑鎯呭喌 + let s1 = Symbol('foo'); + let s2 = Symbol('foo'); + console.log(s1 === s2); // false + + # Symbol 鍊间笉鑳戒笌鍏朵粬绫诲瀷鐨勫艰繘琛岃繍绠,浼氭姤閿 + let sym = Symbol('My symbol'); + "your symbol is " + sym + // TypeError: can't convert symbol to string + `your symbol is ${sym}` + // TypeError: can't convert symbol to string + + * Symbol 鍊煎彲浠ユ樉寮忚浆涓哄瓧绗︿覆,涓嶈兘鐩存帴杞崲涓烘暟鍊 + let sym = Symbol('My symbol'); + String(sym) // 'Symbol(My symbol)' + sym.toString() // 'Symbol(My symbol)' + + * 涔熷彲浠ヨ浆鎹负 bool鍊 + let sym = Symbol(); + Boolean(sym); // true + !sym // false + if (sym) { + // ... + } + Number(sym); // TypeError + sym + 2; // TypeError + +------------------------ +浣滀负灞炴у悕鐨 Symbol | +------------------------ + # 鐢变簬姣忎竴涓 Symbol 鍊奸兘鏄笉鐩哥瓑鐨,杩欐剰鍛崇潃 Symbol 鍊煎彲浠ヤ綔涓烘爣璇嗙锛岀敤浜庡璞$殑灞炴у悕,灏辫兘淇濊瘉涓嶄細鍑虹幇鍚屽悕鐨勫睘鎬 + * 杩欏浜庝竴涓璞$敱澶氫釜妯″潡鏋勬垚鐨勬儏鍐甸潪甯告湁鐢,鑳介槻姝㈡煇涓涓敭琚笉灏忓績鏀瑰啓鎴栬鐩 + let mySymbol = Symbol(); + // 绗竴绉嶅啓娉 + let a = {}; + a[mySymbol] = 'Hello!'; + // 绗簩绉嶅啓娉 + let a = { + [mySymbol]: 'Hello!' + }; + // 绗笁绉嶅啓娉 + let a = {}; + Object.defineProperty(a, mySymbol, { value: 'Hello!' }); + // 浠ヤ笂鍐欐硶閮藉緱鍒板悓鏍风粨鏋 + a[mySymbol] // "Hello!" + + # Symbol 鍊间綔涓哄璞″睘鎬у悕鏃,涓嶈兘鐢ㄧ偣杩愮畻绗 + const mySymbol = Symbol(); + const a = {}; + + a.mySymbol = 'Hello!'; + a[mySymbol] // undefined + a['mySymbol'] // "Hello!" + + * 鐐硅繍绠楃鍚庨潰鎬绘槸瀛楃涓,鎵浠ヤ笉浼氳鍙杕ySymbol浣滀负鏍囪瘑鍚嶆墍鎸囦唬鐨勯偅涓,瀵艰嚧a鐨勫睘鎬у悕瀹為檯涓婃槸涓涓瓧绗︿覆,鑰屼笉鏄竴涓 Symbol 鍊 + * 鍚岀悊,鍦ㄥ璞$殑鍐呴儴,浣跨敤 Symbol 鍊煎畾涔夊睘鎬ф椂,Symbol 鍊煎繀椤绘斁鍦ㄦ柟鎷彿涔嬩腑 + let s = Symbol(); + let obj = { + [s]: function (arg) { ... } + }; + obj[s](123); + * 閲囩敤澧炲己鐨勫璞″啓娉,涓婇潰浠g爜鐨刼bj瀵硅薄鍙互鍐欏緱鏇寸畝娲佷竴浜 + let obj = { + [s](arg) { ... } + }; + + + # Symbol 绫诲瀷杩樺彲浠ョ敤浜庡畾涔変竴缁勫父閲,淇濊瘉杩欑粍甯搁噺鐨勫奸兘鏄笉鐩哥瓑鐨 + log.levels = { + DEBUG: Symbol('debug'), + INFO: Symbol('info'), + WARN: Symbol('warn') + }; + log(log.levels.DEBUG, 'debug message'); + log(log.levels.INFO, 'info message'); + -------------------------------------- + const COLOR_RED = Symbol(); + const COLOR_GREEN = Symbol(); + + function getComplement(color) { + switch (color) { + case COLOR_RED: + return COLOR_GREEN; + case COLOR_GREEN: + return COLOR_RED; + default: + throw new Error('Undefined color'); + } + } + + * 甯搁噺浣跨敤 Symbol 鍊兼渶澶х殑濂藉,灏辨槸鍏朵粬浠讳綍鍊奸兘涓嶅彲鑳芥湁鐩稿悓鐨勫间簡,鍥犳鍙互淇濊瘉涓婇潰鐨剆witch璇彞浼氭寜璁捐鐨勬柟寮忓伐浣 + + # 杩樻湁涓鐐归渶瑕佹敞鎰,Symbol 鍊间綔涓哄睘鎬у悕鏃,璇ュ睘鎬ц繕鏄叕寮灞炴,涓嶆槸绉佹湁灞炴 + +------------------------ +娑堥櫎榄旀湳瀛楃涓 | +------------------------ + # 榄旀湳瀛楃涓叉寚鐨勬槸,鍦ㄤ唬鐮佷箣涓娆″嚭鐜,涓庝唬鐮佸舰鎴愬己鑰﹀悎鐨勬煇涓涓叿浣撶殑瀛楃涓叉垨鑰呮暟鍊 + * 椋庢牸鑹ソ鐨勪唬鐮,搴旇灏介噺娑堥櫎榄旀湳瀛楃涓,鏀圭敱鍚箟娓呮櫚鐨勫彉閲忎唬鏇 + + # 灏辨槸鎶 Symbol 浣滀负鍞竴鐨勫父閲忎娇鐢 + +------------------------ +灞炴у悕閬嶅巻 | +------------------------ + # Symbol 浣滀负灞炴у悕,璇ュ睘鎬т笉浼氬嚭鐜板湪for...in,for...of寰幆涓,涔熶笉浼氳Object.keys(),Object.getOwnPropertyNames(),JSON.stringify()杩斿洖 + # 浣嗘槸,瀹冧篃涓嶆槸绉佹湁灞炴,鏈変竴涓 Object.getOwnPropertySymbols 鏂规硶,鍙互鑾峰彇鎸囧畾瀵硅薄鐨勬墍鏈 Symbol 灞炴у悕 + const name = Symbol("name"); + let obj = { + [name]:'KevinBlandy' + } + let symbols = Object.getOwnPropertySymbols(obj); + for (let symbol of symbols){ + console.log(symbol); //Symbol(name) + console.log(obj[symbol]); //KevinBlandy + } + + # 浣跨敤Object.getOwnPropertyNames鏂规硶寰椾笉鍒癝ymbol灞炴у悕,闇瑕佷娇鐢∣bject.getOwnPropertySymbols鏂规硶 + # Reflect.ownKeys鏂规硶鍙互杩斿洖鎵鏈夌被鍨嬬殑閿悕(鏁扮粍),鍖呮嫭甯歌閿悕鍜 Symbol 閿悕 + const name = Symbol("name"); + let obj = { + [name]:'KevinBlandy', + name:'Litch' + } + let keys = Reflect.ownKeys(obj); + for(let key of keys){ + console.log(key); + console.log(obj[key]); + } + + # 鐢变簬浠 Symbol 鍊间綔涓哄悕绉扮殑灞炴,涓嶄細琚父瑙勬柟娉曢亶鍘嗗緱鍒,鎴戜滑鍙互鍒╃敤杩欎釜鐗规,涓哄璞″畾涔変竴浜涢潪绉佹湁鐨,浣嗗張甯屾湜鍙敤浜庡唴閮ㄧ殑鏂规硶 + ...鐪嬪畬浜 class 浠ュ悗鍐嶆潵鐪 + + +------------------------------------ +Symbol.for(),Symbol.keyFor() | +------------------------------------ + # 鏈夋椂,鎴戜滑甯屾湜閲嶆柊浣跨敤鍚屼竴涓 Symbol 鍊,Symbol.for鏂规硶鍙互鍋氬埌杩欎竴鐐 + * 瀹冩帴鍙椾竴涓瓧绗︿覆浣滀负鍙傛暟,鐒跺悗鎼滅储鏈夋病鏈変互璇ュ弬鏁颁綔涓哄悕绉扮殑 Symbol 鍊 + * 濡傛灉鏈,灏辫繑鍥炶繖涓 Symbol 鍊,鍚﹀垯灏辨柊寤哄苟杩斿洖涓涓互璇ュ瓧绗︿覆涓哄悕绉扮殑 Symbol 鍊 + let s1 = Symbol.for('foo'); + let s2 = Symbol.for('foo'); + console.log(s1 === s2); // true + + * Symbol.for()涓嶴ymbol()杩欎袱绉嶅啓娉,閮戒細鐢熸垚鏂扮殑 Symbol + * 瀹冧滑鐨勫尯鍒槸,鍓嶈呬細琚櫥璁板湪鍏ㄥ眬鐜涓緵鎼滅储,鍚庤呬笉浼 + * Symbol.for()涓嶄細姣忔璋冪敤灏辫繑鍥炰竴涓柊鐨 Symbol 绫诲瀷鐨勫,鑰屾槸浼氬厛妫鏌ョ粰瀹氱殑key鏄惁宸茬粡瀛樺湪,濡傛灉涓嶅瓨鍦ㄦ墠浼氭柊寤轰竴涓 + * 璋冪敤Symbol("cat")30 娆,浼氳繑鍥 30 涓笉鍚岀殑 Symbol 鍊,璋冪敤30娆 Symbol.for(),杩斿洖鍚屼竴涓璞 + + + # Symbol.keyFor鏂规硶杩斿洖涓涓凡鐧昏鐨 Symbol 绫诲瀷鍊肩殑key + let s1 = Symbol.for("foo"); //鐧昏Symbol(涓嶅瓨鍦ㄥ垯鍒涘缓) + console.log(Symbol.keyFor(s1)) // "foo" //鑾峰彇宸茬粡鐧昏鐨 + + let s2 = Symbol("foo"); + console.log(Symbol.keyFor(s2)) // undefined //鏈櫥璁 + + * 鐧昏,鍏跺疄灏辨槸鍦 Symbol.for 涓繘琛屾敞鍐 + + # Symbol.for涓 Symbol 鍊肩櫥璁扮殑鍚嶅瓧,鏄叏灞鐜鐨,鍙互鍦ㄤ笉鍚岀殑 iframe 鎴 service worker 涓彇鍒板悓涓涓 + iframe = document.createElement('iframe'); + iframe.src = String(window.location); + document.body.appendChild(iframe); + iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo') + // true + + +------------------------------------ +妯″潡鐨 Singleton 妯″紡 | +------------------------------------ + # Singleton 妯″紡鎸囩殑鏄皟鐢ㄤ竴涓被,浠讳綍鏃跺欒繑鍥炵殑閮芥槸鍚屼竴涓疄渚 + ... + +------------------------------------ +鍐呯疆鐨 Symbol 鍊 | +------------------------------------ + # 闄や簡瀹氫箟鑷繁浣跨敤鐨 Symbol 鍊间互澶,ES6 杩樻彁渚涗簡 11 涓唴缃殑 Symbol 鍊,鎸囧悜璇█鍐呴儴浣跨敤鐨勬柟娉 + + # Symbol.hasInstance + * 瀵硅薄鐨 Symbol.hasInstance 灞炴,鎸囧悜涓涓唴閮ㄦ柟娉,褰撳叾浠栧璞′娇鐢╥nstanceof杩愮畻绗,鍒ゆ柇鏄惁涓鸿瀵硅薄鐨勫疄渚嬫椂,浼氳皟鐢ㄨ繖涓柟娉 + * foo instanceof Foo 鍦ㄨ瑷鍐呴儴,瀹為檯璋冪敤鐨勬槸 Foo[Symbol.hasInstance](foo) + + class MyClass { + [Symbol.hasInstance](foo) { + console.log(foo) //[1, 2, 3] instalce 浼犻掕繘鏉ユ潵杩涜姣旇緝鐨勫 + return foo instanceof Array; + } + } + let isArray = [1, 2, 3] instanceof new MyClass() ; + console.log(isArray) // true + ----------------------- + class Even { + static [Symbol.hasInstance](obj) { + return Number(obj) % 2 === 0; + } + } + + // 绛夊悓浜 + const Even = { + [Symbol.hasInstance](obj) { + return Number(obj) % 2 === 0; + } + }; + + 1 instanceof Even // false + 2 instanceof Even // true + 12345 instanceof Even // false + ---------------------------- + let obj = { + [Symbol.hasInstance]:function(foo){ + return foo == 'Hello'; + } + } + + console.log('Hello' instanceof obj);//true + console.log('World' instanceof obj);//false + + # Symbol.isConcatSpreadable + * 瀵硅薄鐨凷ymbol.isConcatSpreadable灞炴х瓑浜庝竴涓竷灏斿,琛ㄧず璇ュ璞$敤浜嶢rray.prototype.concat()鏃,鏄惁鍙互灞曞紑 + let arr1 = ['c', 'd']; + ['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e'] + arr1[Symbol.isConcatSpreadable] // undefined + + * '鏁扮粍鐨勯粯璁よ涓烘槸鍙互灞曞紑' + * Symbol.isConcatSpreadable 榛樿绛 浜巙ndefined,璇ュ睘鎬х瓑浜巘rue鏃讹紝涔熸湁灞曞紑鐨勬晥鏋 + + let arr2 = ['c', 'd']; + arr2[Symbol.isConcatSpreadable] = false; + ['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e'] + + * arr2璁剧疆涓嶅厑璁稿睍寮,灏变笉浼氬睍寮,鑰屾槸褰撲綔涓涓暣浣 + + * '绫讳技鏁扮粍鐨勫璞℃濂界浉鍙,榛樿涓嶅睍寮',瀹冪殑Symbol.isConcatSpreadable灞炴ц涓簍rue,鎵嶅彲浠ュ睍寮 + let obj = { + length: 2, + 0: 'c', + 1: 'd' + }; + ['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e'] + * 鍦╟oncat閲岄潰涓嶄細灞曞紑,鑰屾槸浣滀负涓涓暣浣揙bject鍔犲叆鍒版暟缁勯噷闈 + + obj[Symbol.isConcatSpreadable] = true; + ['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e'] + * 璁剧疆灞曞紑鍚,浼氭妸鑷韩灞炴у间綔涓烘柊鐨勬暟缁勫厓绱犳尐涓姞鍏ュ埌鏁扮粍 + + * Symbol.isConcatSpreadable灞炴т篃鍙互瀹氫箟鍦ㄧ被閲岄潰 + class A1 extends Array { + constructor(args) { + super(args); + //鍦ㄦ瀯閫犲嚱鏁伴噷闈㈠垵濮嬪寲 isConcatSpreadable 鍊 + this[Symbol.isConcatSpreadable] = true; + } + } + class A2 extends Array { + constructor(args) { + super(args); + } + //鎻愪緵 Symbol.isConcatSpreadable 鐨勮鍙栧櫒 + get [Symbol.isConcatSpreadable] () { + return false; + } + } + let a1 = new A1(); + a1[0] = 3; + a1[1] = 4; + + let a2 = new A2(); + a2[0] = 5; + a2[1] = 6; + + [1, 2].concat(a1).concat(a2) + // [1, 2, 3, 4, [5, 6]] a2涓嶅厑璁稿睍寮 + + * 娉ㄦ剰,Symbol.isConcatSpreadable鐨勪綅缃樊寮,A1鏄畾涔夊湪瀹炰緥涓,A2鏄畾涔夊湪绫绘湰韬,鏁堟灉鐩稿悓 + + + # Symbol.species + * 瀵硅薄鐨凷ymbol.species灞炴,鎸囧悜涓涓瀯閫犲嚱鏁,鍒涘缓琛嶇敓瀵硅薄鏃,浼氫娇鐢ㄨ灞炴 + class MyArray extends Array { + } + + const a = new MyArray(1, 2, 3); + + const b = a.map(x => x); + + const c = a.filter(x => x > 1); + + b instanceof MyArray // true + c instanceof MyArray // true + + * 瀛愮被MyArray缁ф壙浜嗙埗绫籄rray + * a鏄疢yArray鐨勫疄渚,b鍜宑鏄痑鐨勮鐢熷璞,浣犲彲鑳戒細璁や负,b鍜宑閮芥槸璋冪敤鏁扮粍鏂规硶鐢熸垚鐨,鎵浠ュ簲璇ユ槸鏁扮粍(Array鐨勫疄渚) + * '浣嗗疄闄呬笂瀹冧滑涔熸槸MyArray鐨勫疄渚' + + * Symbol.species灞炴у氨鏄负浜嗚В鍐冲涓婇棶棰樿屾彁渚涚殑,鐜板湪,鎴戜滑鍙互涓篗yArray璁剧疆Symbol.species灞炴 + class MyArray extends Array { + static get [Symbol.species]() { + return Array; //鍒涘缓琛嶇敓瀵硅薄鐨勬椂鍊,浣跨敤return鐨勫嚱鏁颁綔涓烘瀯閫犲嚱鏁 + } + } + const a = new MyArray(); + const b = a.map(x => x); + + b instanceof MyArray // false + b instanceof Array // true + + * 鐢变簬瀹氫箟浜哠ymbol.species灞炴,'鍒涘缓琛嶇敓瀵硅薄鏃跺氨浼氫娇鐢ㄨ繖涓睘鎬ц繑鍥炵殑鍑芥暟,浣滀负鏋勯犲嚱鏁' + * 杩欎釜渚嬪瓙涔熻鏄,'瀹氫箟Symbol.species灞炴ц閲囩敤get鍙栧煎櫒' + * 榛樿鐨凷ymbol.species灞炴х瓑鍚屼簬涓嬮潰鐨勫啓娉 + static get [Symbol.species]() { + return this; + } + + * 灏廳emo + class T1 extends Promise { + } + class T2 extends Promise { + static get [Symbol.species]() { + return Promise; + } + } + new T1(r => r()).then(v => v) instanceof T1 // true + new T2(r => r()).then(v => v) instanceof T2 // false + + + * Symbol.species鐨勪綔鐢ㄥ湪浜,瀹炰緥瀵硅薄鍦ㄨ繍琛岃繃绋嬩腑,闇瑕佸啀娆¤皟鐢ㄨ嚜韬殑鏋勯犲嚱鏁版椂,浼氳皟鐢ㄨ灞炴ф寚瀹氱殑鏋勯犲嚱鏁 + * 瀹冧富瑕佺殑鐢ㄩ旀槸,鏈変簺绫诲簱鏄湪鍩虹被鐨勫熀纭涓婁慨鏀圭殑,閭d箞瀛愮被浣跨敤缁ф壙鐨勬柟娉曟椂,浣滆呭彲鑳藉笇鏈涜繑鍥炲熀绫荤殑瀹炰緥,鑰屼笉鏄瓙绫荤殑瀹炰緥 + + # Symbol.match + * 瀵硅薄鐨凷ymbol.match灞炴,鎸囧悜涓涓嚱鏁,褰撴墽琛宻tr.match(myObject)鏃,濡傛灉璇ュ睘鎬у瓨鍦,浼氳皟鐢ㄥ畠,杩斿洖璇ユ柟娉曠殑杩斿洖鍊 + * 琚 str 鐨刴atch鎵ц鏃,鎵ц璇ュ嚱鏁 + + String.prototype.match(regexp); + + // 绛夊悓浜 + regexp[Symbol.match](this); + + class MyMatcher { + [Symbol.match](string) { + return 'hello world'.indexOf(string); + } + } + + 'e'.match(new MyMatcher()) // 1 + + # Symbol.replace + * 瀵硅薄鐨凷ymbol.replace灞炴,鎸囧悜涓涓柟娉,褰撹瀵硅薄琚玈tring.prototype.replace鏂规硶璋冪敤鏃,浼氳繑鍥炶鏂规硶鐨勮繑鍥炲 + String.prototype.replace(searchValue, replaceValue) + // 绛夊悓浜 + searchValue[Symbol.replace](this, replaceValue) + ---------------------------- + const x = {}; + x[Symbol.replace] = (...s) => console.log(s); + 'Hello'.replace(x, 'World') // ["Hello", "World"] + + * Symbol.replace鏂规硶浼氭敹鍒颁袱涓弬鏁,绗竴涓弬鏁版槸replace鏂规硶姝e湪浣滅敤鐨勫璞 + * 涓婇潰渚嬪瓙鏄疕ello,绗簩涓弬鏁版槸鏇挎崲鍚庣殑鍊,涓婇潰渚嬪瓙鏄疻orld + + # Symbol.search + * 瀵硅薄鐨凷ymbol.search灞炴,鎸囧悜涓涓柟娉,褰撹瀵硅薄琚玈tring.prototype.search鏂规硶璋冪敤鏃,浼氳繑鍥炶鏂规硶鐨勮繑鍥炲 + String.prototype.search(regexp) + // 绛夊悓浜 + regexp[Symbol.search](this) + + class MySearch { + constructor(value) { + this.value = value; + } + [Symbol.search](string) { + return string.indexOf(this.value); + } + } + 'foobar'.search(new MySearch('foo')) // 0 + + # Symbol.split + * 瀵硅薄鐨凷ymbol.split灞炴,鎸囧悜涓涓柟娉,褰撹瀵硅薄琚玈tring.prototype.split鏂规硶璋冪敤鏃,浼氳繑鍥炶鏂规硶鐨勮繑鍥炲 + String.prototype.split(separator, limit) + // 绛夊悓浜 + separator[Symbol.split](this, limit) + ------------------------- + class MySplitter { + constructor(value) { + this.value = value; + } + [Symbol.split](string) { + let index = string.indexOf(this.value); + if (index === -1) { + return string; + } + return [ + string.substr(0, index), + string.substr(index + this.value.length) + ]; + } + } + 'foobar'.split(new MySplitter('foo')) + // ['', 'bar'] + 'foobar'.split(new MySplitter('bar')) + // ['foo', ''] + 'foobar'.split(new MySplitter('baz')) + // 'foobar' + + * 浣跨敤 split 閲嶆柊瀹氫箟瀛楃涓茬殑 split 琛屼负 + + # Symbol.iterator + * 瀵硅薄鐨凷ymbol.iterator灞炴,鎸囧悜璇ュ璞$殑榛樿閬嶅巻鍣ㄦ柟娉 + const myIterable = {}; + myIterable[Symbol.iterator] = function* () { + yield 1; + yield 2; + yield 3; + }; + [...myIterable] //瀵硅瀵硅薄杩涜凯浠 [1, 2, 3] + + * 瀵硅薄杩涜for...of寰幆鏃,浼氳皟鐢⊿ymbol.iterator鏂规硶,杩斿洖璇ュ璞$殑榛樿閬嶅巻 + class Collection { + *[Symbol.iterator]() { + let i = 0; + while(this[i] !== undefined) { + yield this[i]; + ++i; + } + } + } + + let myCollection = new Collection(); + myCollection[0] = 1; + myCollection[1] = 2; + + for(let value of myCollection) { + console.log(value); + } + // 1 + // 2 + + # Symbol.toPrimitive + * 瀵硅薄鐨凷ymbol.toPrimitive灞炴,鎸囧悜涓涓柟娉 + * 璇ュ璞¤杞负鍘熷绫诲瀷鐨勫兼椂,浼氳皟鐢ㄨ繖涓柟娉,杩斿洖璇ュ璞″搴旂殑鍘熷绫诲瀷鍊 + * Symbol.toPrimitive琚皟鐢ㄦ椂,浼氭帴鍙椾竴涓瓧绗︿覆鍙傛暟,琛ㄧず褰撳墠杩愮畻鐨勬ā寮,涓鍏辨湁涓夌妯″紡 + Number :璇ュ満鍚堥渶瑕佽浆鎴愭暟鍊 + String :璇ュ満鍚堥渶瑕佽浆鎴愬瓧绗︿覆 + Default :璇ュ満鍚堝彲浠ヨ浆鎴愭暟鍊,涔熷彲浠ヨ浆鎴愬瓧绗︿覆 + + let obj = { + [Symbol.toPrimitive](hint) { + switch (hint) { + case 'number': + return 123; + case 'string': + return 'str'; + case 'default': + return 'default'; + default: + throw new Error(); + } + } + }; + + 2 * obj // 246 + 3 + obj // '3default' + obj == 'default' // true + String(obj) // 'str' + + # Symbol.toStringTag + * 瀵硅薄鐨凷ymbol.toStringTag灞炴.鎸囧悜涓涓柟娉 + * 鍦ㄨ瀵硅薄涓婇潰璋冪敤Object.prototype.toString鏂规硶鏃,濡傛灉杩欎釜灞炴у瓨鍦,瀹冪殑杩斿洖鍊间細鍑虹幇鍦╰oString鏂规硶杩斿洖鐨勫瓧绗︿覆涔嬩腑 + * 琛ㄧず瀵硅薄鐨勭被鍨,涔熷氨鏄,杩欎釜灞炴у彲浠ョ敤鏉ュ畾鍒禰object Object]鎴朳object Array]涓璷bject鍚庨潰鐨勯偅涓瓧绗︿覆 + // 渚嬩竴 + ({[Symbol.toStringTag]: 'Foo'}.toString()) + // "[object Foo]" + + // 渚嬩簩 + class Collection { + get [Symbol.toStringTag]() { + return 'xxx'; + } + } + let x = new Collection(); + Object.prototype.toString.call(x) // "[object xxx]" + + * ES6 鏂板鍐呯疆瀵硅薄鐨凷ymbol.toStringTag灞炴у煎涓 + JSON[Symbol.toStringTag]锛'JSON' + Math[Symbol.toStringTag]锛'Math' + Module 瀵硅薄M[Symbol.toStringTag]锛'Module' + ArrayBuffer.prototype[Symbol.toStringTag]锛'ArrayBuffer' + DataView.prototype[Symbol.toStringTag]锛'DataView' + Map.prototype[Symbol.toStringTag]锛'Map' + Promise.prototype[Symbol.toStringTag]锛'Promise' + Set.prototype[Symbol.toStringTag]锛'Set' + %TypedArray%.prototype[Symbol.toStringTag]锛'Uint8Array' + WeakMap.prototype[Symbol.toStringTag]锛'WeakMap' + WeakSet.prototype[Symbol.toStringTag]锛'WeakSet' + %MapIteratorPrototype%[Symbol.toStringTag]锛'Map Iterator' + %SetIteratorPrototype%[Symbol.toStringTag]锛'Set Iterator' + %StringIteratorPrototype%[Symbol.toStringTag]锛'String Iterator' + Symbol.prototype[Symbol.toStringTag]锛'Symbol' + Generator.prototype[Symbol.toStringTag]锛'Generator' + GeneratorFunction.prototype[Symbol.toStringTag]锛'GeneratorFunction' + + # Symbol.unscopables + * 瀵硅薄鐨凷ymbol.unscopables灞炴,鎸囧悜涓涓璞 + * 璇ュ璞℃寚瀹氫簡浣跨敤with鍏抽敭瀛楁椂,鍝簺灞炴т細琚玾ith鐜鎺掗櫎 + Array.prototype[Symbol.unscopables] + // { + // copyWithin: true, + // entries: true, + // fill: true, + // find: true, + // findIndex: true, + // includes: true, + // keys: true + // } + + Object.keys(Array.prototype[Symbol.unscopables]) + // ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'includes', 'keys'] + + * 涓婇潰浠g爜璇存槑,鏁扮粍鏈 7 涓睘鎬,浼氳with鍛戒护鎺掗櫎 + -------------------------------------------- + // 娌℃湁 unscopables 鏃 + class MyClass { + foo() { return 1; } + } + + var foo = function () { return 2; }; + + with (MyClass.prototype) { + foo(); // 1 璇oo鏉ヨ嚜浜 MyClass.prototype 鐨勪笂涓嬫枃 + } + + // 鏈 unscopables 鏃 + class MyClass { + foo() { return 1; } + get [Symbol.unscopables]() { + //璁剧疆浜唚ith鎺掗櫎 foo + return { foo: true }; + } + } + + var foo = function () { return 2; }; + + with (MyClass.prototype) { + //璇oo鏉ヨ嚜浜庝笂灞備綔鐢ㄥ煙(鍏ㄥ眬) + foo(); // 2 + } + + * 閫氳繃鎸囧畾Symbol.unscopables灞炴,浣垮緱with璇硶鍧椾笉浼氬湪褰撳墠浣滅敤鍩熷鎵緁oo灞炴,鍗砯oo灏嗘寚鍚戝灞備綔鐢ㄥ煙鐨勫彉閲 \ No newline at end of file diff --git "a/JavaSript/ES6/es6-async\345\207\275\346\225\260.js" "b/JavaSript/ES6/es6-async\345\207\275\346\225\260.js" new file mode 100644 index 00000000..bca50c11 --- /dev/null +++ "b/JavaSript/ES6/es6-async\345\207\275\346\225\260.js" @@ -0,0 +1,70 @@ +------------------------------------ +async 鍑芥暟 | +------------------------------------ + 1,鍚箟 + 2,鍩烘湰鐢ㄦ硶 + 3,璇硶 + 4,sync 鍑芥暟鐨勫疄鐜板師鐞 + 5,涓庡叾浠栧紓姝ュ鐞嗘柟娉曠殑姣旇緝 + 6,瀹炰緥锛氭寜椤哄簭瀹屾垚寮傛鎿嶄綔 + 7,寮傛閬嶅巻鍣 + +------------------------------------ +鍚箟 | +------------------------------------ + # ES2017 鏍囧噯寮曞叆浜 async 鍑芥暟,浣垮緱寮傛鎿嶄綔鍙樺緱鏇村姞鏂逛究 + # async 鍑芥暟鏄粈涔? 涓鍙ヨ瘽,瀹冨氨鏄 Generator 鍑芥暟鐨勮娉曠硸 + # async鍑芥暟灏辨槸灏 Generator 鍑芥暟鐨勬槦鍙(*)鏇挎崲鎴恆sync,灏唝ield鏇挎崲鎴恆wait,浠呮鑰屽凡 + + # async鍑芥暟瀵 Generator 鍑芥暟鐨勬敼杩,浣撶幇鍦ㄤ互涓嬪洓鐐 + 1,鍐呯疆鎵ц鍣ㄣ + * Generator 鍑芥暟鐨勬墽琛屽繀椤婚潬鎵ц鍣,鎵浠ユ墠鏈変簡co妯″潡,鑰宎sync鍑芥暟鑷甫鎵ц鍣 + * 涔熷氨鏄,async鍑芥暟鐨勬墽琛,涓庢櫘閫氬嚱鏁颁竴妯′竴鏍,鍙涓琛 + + 2,鏇村ソ鐨勮涔 + * async鍜宎wait,姣旇捣鏄熷彿鍜寉ield,璇箟鏇存竻妤氫簡 + * async琛ㄧず鍑芥暟閲屾湁寮傛鎿嶄綔,await琛ㄧず绱ц窡鍦ㄥ悗闈㈢殑琛ㄨ揪寮忛渶瑕佺瓑寰呯粨鏋 + + 3,鏇村箍鐨勯傜敤鎬 + * co妯″潡绾﹀畾,yield鍛戒护鍚庨潰鍙兘鏄 Thunk 鍑芥暟鎴 Promise 瀵硅薄,鑰宎sync鍑芥暟鐨刟wait鍛戒护鍚庨潰,鍙互鏄 Promise 瀵硅薄鍜屽師濮嬬被鍨嬬殑鍊(鏁板,瀛楃涓插拰甯冨皵鍊,浣嗚繖鏃剁瓑鍚屼簬鍚屾鎿嶄綔 + + 4,杩斿洖鍊兼槸 Promise + * async鍑芥暟鐨勮繑鍥炲兼槸 Promise 瀵硅薄,杩欐瘮 Generator 鍑芥暟鐨勮繑鍥炲兼槸 Iterator 瀵硅薄鏂逛究澶氫簡,浣犲彲浠ョ敤then鏂规硶鎸囧畾涓嬩竴姝ョ殑鎿嶄綔 + + + * 杩涗竴姝ヨ,async鍑芥暟瀹屽叏鍙互鐪嬩綔澶氫釜寮傛鎿嶄綔,鍖呰鎴愮殑涓涓 Promise 瀵硅薄,鑰宎wait鍛戒护灏辨槸鍐呴儴then鍛戒护鐨勮娉曠硸 + +------------------------------------ +鍩烘湰鐢ㄦ硶 | +------------------------------------ + # async鍑芥暟杩斿洖涓涓 Promise 瀵硅薄,鍙互浣跨敤then鏂规硶娣诲姞鍥炶皟鍑芥暟,褰撳嚱鏁版墽琛岀殑鏃跺,涓鏃﹂亣鍒癮wait灏变細鍏堣繑鍥,绛夊埌寮傛鎿嶄綔瀹屾垚,鍐嶆帴鐫鎵ц鍑芥暟浣撳唴鍚庨潰鐨勮鍙 + + # async鍑芥暟鏈夊绉嶅0鏄庢柟寮 + // 鍑芥暟澹版槑 + async function foo() {} + + // 鍑芥暟琛ㄨ揪寮 + const foo = async function () {}; + + // 瀵硅薄鐨勬柟娉 + let obj = { async foo() {} }; + obj.foo().then(...) + + // Class 鐨勬柟娉 + class Storage { + constructor() { + this.cachePromise = caches.open('avatars'); + } + + async getAvatar(name) { + const cache = await this.cachePromise; + return cache.match(`/avatars/${name}.jpg`); + } + } + + const storage = new Storage(); + storage.getAvatar('jake').then(鈥); + + // 绠ご鍑芥暟 + const foo = async () => {}; + diff --git a/JavaSript/ES6/es6-class.js b/JavaSript/ES6/es6-class.js deleted file mode 100644 index 646b5e0a..00000000 --- a/JavaSript/ES6/es6-class.js +++ /dev/null @@ -1,7 +0,0 @@ --------------------- -class | --------------------- - # 很牛逼, class,JS也有类了 - # 其实就是个语法糖 - - \ No newline at end of file diff --git "a/JavaSript/ES6/es6-\345\205\245\351\227\250.js" "b/JavaSript/ES6/es6-\345\205\245\351\227\250.js" new file mode 100644 index 00000000..d9f410a7 --- /dev/null +++ "b/JavaSript/ES6/es6-\345\205\245\351\227\250.js" @@ -0,0 +1,19 @@ +----------------------- +阮一峰 | +------------------------ + http://es6.ruanyifeng.com/ + +----------------------- +es6变化/征途 | +------------------------ + 1,新的变量声明关键字 + let + const + 2,解构赋值 + 3,字符串的扩展 + 4,正则扩展 + 5,数值扩展 + 6,函数扩展 + 7,数组扩展 + 8,对象扩展 + diff --git "a/JavaSript/ES6/es6-\345\217\230\351\207\217-const.js" "b/JavaSript/ES6/es6-\345\217\230\351\207\217-const.js" deleted file mode 100644 index 4ded2b3f..00000000 --- "a/JavaSript/ES6/es6-\345\217\230\351\207\217-const.js" +++ /dev/null @@ -1,15 +0,0 @@ --------------------- -const | --------------------- - # 有点像java中的 final,值不允许被改变,如果进行改变,会抛出异常 - # const 一旦声明变量,就必须立即初始化,不能留到以后赋值。 - # const 的作用域与let命令相同:只在声明所在的块级作用域内有效。 - # const 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。 - # const 声明的常量,也与let一样不可重复声明。 - # ES6 声明变量的六种方法 - var - function - let - const - import - class diff --git "a/JavaSript/ES6/es6-\345\217\230\351\207\217-let.js" "b/JavaSript/ES6/es6-\345\217\230\351\207\217-let.js" deleted file mode 100644 index 49cb301f..00000000 --- "a/JavaSript/ES6/es6-\345\217\230\351\207\217-let.js" +++ /dev/null @@ -1,89 +0,0 @@ ----------------------------- -let | ----------------------------- - # 块儿级变量声明 - for(var x = 0;x < 5 ; x++){} - out(x); //5 - - for(let x = 0;x < 5 ; x++){} - out(x); //Uncaught ReferenceError: x is not defined - - for (let i = 0; i < 3; i++) { - //循环体中的i和判断体中的i在不同的作用域 - let i = 'abc'; - console.log(i); //输出三次abc - } - # 对于函数中的变量提升 - * 不存在变量提升 - * 当函数体内部存在与外部同名的变量,则外部变量没有效(与外部变量脱离关系) - * 必须在函数最顶部进行声明 - * let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。 - function test(){ - out(x); - var x = 8; - } - test(); //undefined,因为 x 被变量提升到了顶部声明,只是没有赋值 - - function test(){ - out(x); - let x = 8; //x 并没有被提升到顶部,所以未声明异常 - } - test(); //Uncaught ReferenceError: x is not defined - - # 暂时性死区 - * 只要块级作用域内存在let命令,它所声明的变量就"绑定"(binding)这个区域,不再受外部的影响。 - * "暂时性死区",也意味着 typeof 不再是一个百分之百安全的操作。 - * 如果一个变量根本没有被声明,使用 typeof 反而不会报错 - * 在let变量声明之前使用 typeof 会抛出异常 - * 暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。 - - # 不允许重复声明 - * let不允许在相同作用域内,重复声明同一个变量。 - - # ES6 的块级作用域 - * 可以允许无限重叠 - {{{{ - let x = 5; - }}}} - - # 可以替代匿名自调用函数 - * 匿名自调用函数 - (function(){ - var temp = 15; - })(); - * let - { - let temp; - } - - # ES6 的块级作用域 - * ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。 - if (true) { - function f() {} //非法函数定义 - } - - try { - function f() {} //非法函数定义 - } catch(e) { - } - * 但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。 - * ES6 允许函数在块级作用域中声明 - * ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用. - - # do 表达式 - * 本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。 - { - let t = f(); - t = t * t + 1; - } - //在块级作用域以外,没有办法得到t的值,因为块级作用域不返回值,除非t是全局变量。 - * 现在有一个提案,使得块级作用域可以变为表达式,也就是说可以返回值,办法就是在块级作用域之前加上do,使它变为do表达式。 - let x = do { - let t = f(); - t * t + 1; - }; - //上面代码中,变量x会得到整个块级作用域的返回值。 - - # 顶层对象的属性 - * 传统的 var 声明,其实就是 window 对象的一个属性 - * 而现在 let 的声明,不再是 window 对象的属性 \ No newline at end of file diff --git "a/JavaSript/ES6/es6-\345\217\230\351\207\217-\345\243\260\346\230\216.js" "b/JavaSript/ES6/es6-\345\217\230\351\207\217-\345\243\260\346\230\216.js" new file mode 100644 index 00000000..ab0605a6 --- /dev/null +++ "b/JavaSript/ES6/es6-\345\217\230\351\207\217-\345\243\260\346\230\216.js" @@ -0,0 +1,279 @@ +------------------- +es6变量声明 | +------------------- + # ES5 只有两种声明变量的方法: var 命令和 function 命令 + # ES6 除了添加 let 和 const 命令,还有 import 命令和 class 命令,所以ES6 一共有 6 种声明变量的方法 + +------------------- +let | +------------------- + # 该关键字声明的变量,仅仅在当前代码块有效 + //代码 + { + var y = 5; + let x = 5; + } + console.log(y); + console.log(x); + //执行结果 + 5 //变量y可以被访问 + Uncaught ReferenceError: x is not defined //变量x未定义 + + * for循环中的变量,就非常适合使用 let,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量 + * 每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值 + 这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算 + + # for循环中的作用域 + for(let x = 0 ;x < 10 ;x ++){ + let x = 'KevinBlandy'; + console.log(x); + } + * 执行结果是打印了10次 'KevinBlandy' + * 说明 for 循环中的 x 与 循环体中声明的 x 在不同的作用域 + + # 不会存在变量提升 + * 在变量声明之前,使用 var 声明的变量,它的值为 undefined + console.log(x); //undefined + var x = 5; + + * let 声明的变量,必须要先声明在使用 + console.log(x); + let x = 5; + + Uncaught ReferenceError: x is not defined + + # 暂时性死区 + * 只要块级作用域内存在let命令,它所声明的变量就"绑定"这个区域,不再受外部的影响 + var temp = 5; + { + temp = 6; //Uncaught ReferenceError: temp is not defined + let temp; + } + + * 存在全局变量temp,但是块级作用域内let又声明了一个局部变量temp,导致后者绑定这个块级作用域 + * 所以在let声明变量前,对temp赋值会报错 + + * ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域,'凡是在声明之前就使用这些变量,就会报错' + * 总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的. + * 这在语法上,称为"暂时性死区"(temporal dead zone,简称 TDZ) + + * "暂时性死区"也意味着typeof不再是一个百分之百安全的操作 + typeof x //Uncaught ReferenceError: x is not defined + let x = 5; + console.log(typeof xxx) //undefined + + * 因为 x 被let声明,所以之前都是属于 'x' 的死区,在声明之前使用都会抛出异常 + * xxx 是一个根本没有被声明过的变量,使用 typeof ,结果返回,undefined + * '在没有let之前,typeof运算符是百分之百安全的,永远不会报错,现在这一点不成立了' + + * 一些不容易被发现的'死区' + ------------------------------------------------ + function bar(x = y, y = 2) { + return [x, y]; + } + bar(); // 报错Uncaught ReferenceError: y is not defined + + * x = y的时候,y还没有被声明,y属于死区 + * 如果 (x = 2,y = x),就ok,因为x已经被声明,可以被赋值给后面的y + ------------------------------------------------ + var x = x; //ok + let x = x; // ReferenceError: x is not defined + + * let 与 var不同,let声明会报错,也是因为死区的问题,x等于的那个x还没有被声明,就用来赋值 + + * 总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取 + * 只有等到声明变量的那一行代码出现,才可以获取和使用该变量.(先声明,后使用) + + # 不允许重复声明 + * let不允许在相同作用域内,重复声明同一个变量 + { + let x = 4; + let x = 5; //Uncaught SyntaxError: Identifier 'x' has already been declared + } + * 那么在方法中,也就不允许对形参进行 let 声明 + function test(x) { + let x = 4; + } + test(5); //Uncaught SyntaxError: Identifier 'x' has already been declared + +------------------- +块级作用域 | +------------------- + # 为啥要有块级作用域 + * ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景 + var tmp = new Date(); + function f() { + //在变量声明之前使用,该值为 undefined + console.log(tmp); + if (false) { + //函数块里面声明了变量 + var tmp = 'hello world'; + } + } + f(); // undefined + + * if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量(变量提升) + * 但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量 + ------------------------------------------------ + var s = 'hello'; + for (var i = 0; i < s.length; i++) { + console.log(s[i]); + } + console.log(i); // 5 + * 变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量 + + # ES6中的块级作用域 + * let 实际上为js新增了块级作用域 + * es6允许块级作用域的任意嵌套 + * 父级作用域不能访问子作用域的数据,子作用域可以访问父级作用域的变量 + * 内层作用域可以定义外层作用域的同名变量 + * 块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了 + // IIFE 写法 + (function () { + var tmp = ...; + ... + }()); + + // 块级作用域写法 + { + let tmp = ...; + ... + } + + + # 块级作用域与函数声明 + * 函数是否可以在块级作用域中声明,这是一个令人混淆的问题 + * ES5,规定,函数只能在顶层作用域中和函数作用域中声明,不能在块级作用域中声明 + // 情况一 + if (true) { + function f() {} + } + + // 情况二 + try { + function f() {} + } catch(e) { + // ... + } + + * 上述两种声明方式,在es5中都是非法的 + * 但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错 + + * ES6 引入了块级作用域,明确允许在块级作用域之中声明函数 + * ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用 + + * 浏览器特不一样的地方 + function f() { console.log('I am outside!'); } + (function () { + if (false) { + // 重复声明一次函数f + function f() { console.log('I am inside!'); } + } + f(); + }()); + + * 该代码在es5环境下会输出:I am inside! + * 因为ES5'变量提升'的问题,实际上代码是 + // ES5 环境 + function f() { console.log('I am outside!'); } + (function () { + //函数声明被提升到顶部 + function f() { console.log('I am inside!'); } + if (false) { + } + f(); + }()); + + * 如果在es6模式下运行该代码会抛出异常,因为: + 如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响. + 为了减轻因此产生的不兼容问题,ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式 + + * 浏览器自己的行为方式 + 1,'允许在块级作用域内声明函数' + 2,'函数声明类似于var,即会提升到全局作用域或函数作用域的头部' + 3,'同时,函数声明还会提升到所在的块级作用域的头部' + + * 这三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理 + + * 上面的代码在符合 ES6 的浏览器中,会报错,因为实际运行的是下面的代码 + // 浏览器的 ES6 环境 + function f() { console.log('I am outside!'); } + (function () { + var f = undefined; + if (false) { + function f() { console.log('I am inside!'); } + } + + f(); + }()); + // Uncaught TypeError: f is not a function + + + * 考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数,如果确实需要,也应该写成函数表达式,而不是函数声明语句 + // 函数声明语句 + { + let a = 'secret'; + function f() { + return a; + } + } + + // 函数表达式 + { + let a = 'secret'; + let f = function () { + return a; + }; + } + + + * 另外,还有一个需要注意的地方.ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错 + // 不报错 + 'use strict'; + if (true) { + function f() {} + } + + // 报错 + 'use strict'; + if (true) + function f() {} + +------------------- +const | +------------------- + # const声明一个只读的常量,一旦声明,常量的值就不能改变 + # const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化 + # const的作用域与let命令相同,只在声明所在的块级作用域内有效 + # const命令声明的常量也是'不提升',同样存在暂时性死区,只能在声明的位置后面使用 + # const声明的常量,也与let一样不可重复声明 + var message = "Hello!"; + let age = 25; + + // 以下两行都会报错 + const message = "Goodbye!"; + const age = 30; + + # const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动 + # 如果非要冻结一个对象的所有属性,可以使用 Object.freeze + + +--------------------- +顶层对象的属性 | +--------------------- + # 在浏览器环境中顶层对象即 window,在Node指的是 global 对象 + # ES5之中,顶层对象的属性与全局变量是等价的 + window.a = 1; + console.log(a) // 1 + + a = 2; + console.log(window.a) // 2 + + # ES6 为了改变这一点,为了保持兼容性, var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性 + # 另一方面规定,let命令,const命令,class命令声明的全局变量,不属于顶层对象的属性 + # 也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩 + +--------------------- +global对象 | +--------------------- + ... diff --git "a/JavaSript/ES6/es6-\345\217\230\351\207\217-\350\247\243\346\236\204\350\265\213\345\200\274.js" "b/JavaSript/ES6/es6-\345\217\230\351\207\217-\350\247\243\346\236\204\350\265\213\345\200\274.js" index 6186746f..0ca1d2ba 100644 --- "a/JavaSript/ES6/es6-\345\217\230\351\207\217-\350\247\243\346\236\204\350\265\213\345\200\274.js" +++ "b/JavaSript/ES6/es6-\345\217\230\351\207\217-\350\247\243\346\236\204\350\265\213\345\200\274.js" @@ -1,35 +1,66 @@ ------------------------------------ -解构赋值 | +瑙f瀯璧嬪 | ------------------------------------ - # 一种赋值的语法而已 + 1,鏁扮粍鐨勮В鏋勮祴鍊 + 2,瀵硅薄鐨勮В鏋勮祴鍊 + 3,瀛楃涓茬殑瑙f瀯璧嬪 + 4,鏁板煎拰甯冨皵鍊肩殑瑙f瀯璧嬪 + 5,鍑芥暟鍙傛暟鐨勮В鏋勮祴鍊 + 6,鍦嗘嫭鍙烽棶棰 + 7,鐢ㄩ + +------------------------------------ +瑙f瀯璧嬪 | +------------------------------------ + # 涓绉嶈祴鍊肩殑璇硶鑰屽凡 let [a,b,c] = [1,2,3]; - # 如果等号的右边不是数组(或者严格地说,不是可遍历的结构,参见《Iterator》一章),那么将会报错。 - * 以下都会报错 + + #銆濡傛灉绛夊彿鐨勫彸杈逛笉鏄暟缁(鎴栬呬弗鏍煎湴璇,涓嶆槸鍙亶鍘嗙殑缁撴瀯,鍙傝銆奍terator銆嬩竴绔),閭d箞灏嗕細鎶ラ敊 + * 浠ヤ笅閮戒細鎶ラ敊 let [foo] = 1; let [foo] = false; let [foo] = NaN; let [foo] = undefined; let [foo] = null; let [foo] = {}; - # 这种写法属于 "模式匹配",只要等号两边的模式相同,左边的变量就会被赋予对应的值。 + + # 瀵逛簬 Set 缁撴瀯,涔熷彲浠ヤ娇鐢ㄦ暟缁勭殑瑙f瀯璧嬪 + let [x, y, z] = new Set(['a', 'b', 'c']); + x // "a" + + # 鍙鏌愮鏁版嵁缁撴瀯鍏锋湁 Iterator 鎺ュ彛,閮藉彲浠ラ噰鐢ㄦ暟缁勫舰寮忕殑瑙f瀯璧嬪 + function* fibs() { + let a = 0; + let b = 1; + while (true) { + yield a; + [a, b] = [b, a + b]; + } + } + + let [first, second, third, fourth, fifth, sixth] = fibs(); + console.log(sixth) // 5 - # 复杂的案例 - * 多维数组嵌套 + # 杩欑鍐欐硶灞炰簬 "妯″紡鍖归厤"锛屽彧瑕佺瓑鍙蜂袱杈圭殑妯″紡鐩稿悓锛屽乏杈圭殑鍙橀噺灏变細琚祴浜堝搴旂殑鍊笺 + + # 澶嶆潅鐨勬渚 + * 澶氱淮鏁扮粍宓屽 let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3 - * 使用空元素占位 + * 浣跨敤绌哄厓绱犲崰浣 let [ , , third] = ["foo", "bar", "baz"]; third // "baz" let [x, , y] = [1, 2, 3]; x // 1 y // 3 - * 对数组进行解构赋值 - * 数组解构失败,会是空数组 - * 却数组只能在最后出现,前面的都匹配完毕后,剩余的所有数据都会被匹配进数组 + + * 瀵规暟缁勮繘琛岃В鏋勮祴鍊 + * 鏁扮粍瑙f瀯澶辫触,浼氭槸绌烘暟缁 + * 鏁扮粍鍙兘鍦ㄦ渶鍚庡嚭鐜(浣跨敤涓変釜鐐(...)鏍囪瘑),鍓嶉潰鐨勯兘鍖归厤瀹屾瘯鍚,鍓╀綑鐨勬墍鏈夋暟鎹兘浼氳鍖归厤杩涙暟缁 let [head, ...tail] = [1, 2, 3, 4]; head // 1 tail // [2, 3, 4] @@ -39,7 +70,456 @@ y // undefined z // [] - # 如果解构不成功,变量的值就等于undefined。 - * 以下两种情况都属于解构不成功,foo的值都会等于undefined。 + # 濡傛灉瑙f瀯涓嶆垚鍔,鍙橀噺鐨勫煎氨绛変簬undefined銆 + * 浠ヤ笅涓ょ鎯呭喌閮藉睘浜庤В鏋勪笉鎴愬姛,foo鐨勫奸兘浼氱瓑浜巙ndefined銆 let [foo] = []; - let [bar, foo] = [1]; \ No newline at end of file + let [bar, foo] = [1]; + + # 鍙︿竴绉嶆儏鍐垫槸涓嶅畬鍏ㄨВ鏋,鍗崇瓑鍙峰乏杈圭殑妯″紡,鍙尮閰嶄竴閮ㄥ垎鐨勭瓑鍙峰彸杈圭殑鏁扮粍 + * 杩欑鎯呭喌涓,瑙f瀯渚濈劧鍙互鎴愬姛 + let [x, y] = [1, 2, 3]; + x // 1 + y // 2 + + let [a, [b], d] = [1, [2, 3], 4]; + a // 1 + b // 2 + d // 4 + + # 榛樿鍊 + * 鍏佽浣跨敤 = 鏉ヨ缃粯璁ゅ + let [foo = true] = []; + foo // true + + let [x, y = 'b'] = ['a']; // x='a', y='b' + let [x, y = 'b'] = ['a', undefined]; // x='a', y='b' + + * ES6 鍐呴儴浣跨敤涓ユ牸鐩哥瓑杩愮畻绗(===),鍒ゆ柇涓涓綅缃槸鍚︽湁鍊 + * 鎵浠,鍙湁褰撲竴涓暟缁勬垚鍛樹弗鏍肩瓑浜巙ndefined,榛樿鍊兼墠浼氱敓鏁 + let [x = 1] = [undefined]; + x // 1 + + let [x = 1] = [null]; + x // null + + * 濡傛灉榛樿鍊兼槸涓涓〃杈惧紡,閭d箞杩欎釜琛ㄨ揪寮忔槸鎯版ф眰鍊肩殑,鍗冲彧鏈夊湪鐢ㄥ埌鐨勬椂鍊,鎵嶄細姹傚 + let func = function(){ + console.log(9); + } + //x 鍙互鎴愬姛鐨勮鏋愭瀯璧嬪,鎵浠 func() 鏍规湰灏变笉浼氭墽琛 + let [x = func()] = [1] + console.log(x); //1 + + + * 榛樿鍊煎彲浠ュ紩鐢ㄨВ鏋勮祴鍊肩殑鍏朵粬鍙橀噺,浣嗚鍙橀噺蹇呴』宸茬粡澹版槑 + let [x = 1, y = x] = []; // x=1; y=1 + let [x = 1, y = x] = [2]; // x=2; y=2 + let [x = 1, y = x] = [1, 2]; // x=1; y=2 + let [x = y, y = 1] = []; // ReferenceError: y is not defined + + * 鏈鍚庝竴涓細寮傚父,鍥犱负 x = y鐨勬椂鍊,y杩樻病鏈夊0鏄 + +------------------------------------ +瀵硅薄鐨勮В鏋勮祴鍊 | +------------------------------------ + # 瀵硅薄鐨勮В鏋勮祴鍊(鎶婁竴涓璞$殑灞炴у,璧嬪煎埌鍙橀噺) + let {foo,bar,none} = {foo:"aaa",bar:"bbb"}; + foor //aaa + bar //bbb + none //undefined + + * 瀵硅薄鐨勮В鏋勪笌鏁扮粍鏈変竴涓噸瑕佺殑涓嶅悓,鏁扮粍鐨勫厓绱犳槸鎸夋搴忔帓鍒楃殑,鍙橀噺鐨勫彇鍊肩敱瀹冪殑浣嶇疆鍐冲畾 + * 鑰屽璞$殑灞炴ф病鏈夋搴,鍙橀噺蹇呴』涓庡睘鎬у悓鍚,鎵嶈兘鍙栧埌姝g‘鐨勫 + * 鍙橀噺娌℃湁瀵瑰簲鐨勫悓鍚嶅睘鎬(瑙f瀯澶辫触),绛変簬undefined + + # 濡傛灉鍙橀噺鍚嶄笌灞炴у悕涓嶄竴鑷,蹇呴』鍐欐垚涓嬮潰杩欐牱 + //鎵句竴涓悕绉板彨鍋 foo鐨勫彉閲,鎶婂畠鐨勫艰祴缁檅az + let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; + baz // "aaa" + + let obj = { first: 'hello', last: 'world' }; + + let { first: f, last: l } = obj; + f // 'hello' + l // 'world' + + * 涔熷氨鏄,瀵硅薄鐨勮В鏋勮祴鍊肩殑鍐呴儴鏈哄埗,鏄厛鎵惧埌鍚屽悕灞炴,鐒跺悗鍐嶈祴缁欏搴旂殑鍙橀噺 + * 鐪熸琚祴鍊肩殑鏄悗鑰,鑰屼笉鏄墠鑰 + let { foo: baz } = { foo: "aaa", bar: "bbb" }; + baz // "aaa" + foo // error: foo is not defined + + * 涓婇潰浠g爜涓紝foo鏄尮閰嶇殑妯″紡,baz鎵嶆槸鍙橀噺銆傜湡姝h璧嬪肩殑鏄彉閲廱az,鑰屼笉鏄ā寮廸oo + + * 閭d箞瀵硅薄瑙f瀯璧嬪,濡傛灉鍖归厤妯″紡鍜屽彉閲忓悕绉颁竴鏍,鍙互绠鍐 + let {foo:foo,bar:bar,none:none} = {foo:"aaa",bar:"bbb"}; + 鈫(绠鍐) + let {foo,bar,none} = {foo:"aaa",bar:"bbb"}; + + # 涓庢暟缁勪竴鏍,瑙f瀯璧嬪间篃鍙互鐢ㄤ簬宓屽鐨勫璞 + let obj = { + p: [ + 'Hello', + { y: 'World' } + ] + }; + + let { p: [x, { y }] } = obj; + x // "Hello" + y // "World" + + * 娉ㄦ剰,杩欓噷鐨刾,鏄竴涓ā寮,涓嶆槸鍙橀噺,濡傛灉p涔熻浣滀负鍙橀噺璧嬪 + let obj = { + p: [ + 'Hello', + { y: 'World' } + ] + }; + + let { p, p: [x, { y }] } = obj; + x // "Hello" + y // "World" + p // ["Hello", {y: "World"}] + + # 鍐嶆潵涓涓祵濂椾緥瀛 + let obj = {}; + let arr = []; + ({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true }); + console.log(obj); //{prop: 123} + console.log(arr); //[true] + + # 瀵硅薄鐨勮В鏋勪篃鍙互鎸囧畾榛樿鍊 + var {x = 3} = {}; + x // 3 + + var {x, y = 5} = {x: 1}; + x // 1 + y // 5 + + var {x: y = 3} = {}; + y // 3 + + var {x: y = 3} = {x: 5}; + y // 5 + + var { message: msg = 'Something went wrong' } = {}; + msg // "Something went wrong" + + * 榛樿鍊肩敓鏁堢殑鏉′欢鏄,瀵硅薄鐨勫睘鎬у间弗鏍肩瓑浜巙ndefined(===) + + var {x = 3} = {x: undefined}; + x // 3 + + var {x = 3} = {x: null}; + x // null + + # 濡傛灉瑙f瀯妯″紡鏄祵濂楃殑瀵硅薄,鑰屼笖瀛愬璞℃墍鍦ㄧ殑鐖跺睘鎬т笉瀛樺湪,閭d箞灏嗕細鎶ラ敊 + let {foo: {bar}} = {baz: 'baz'}; //alarm.html:173 Uncaught TypeError: Cannot destructure property `bar` of 'undefined' or 'null'. + + * 浠巤baz:'ba'}涓幏鍙栦竴涓彨鍋歠oo鐨勫睘鎬у璞,鍐嶄粠璇ュ璞¢噷闈㈣幏鍙 bar灞炴ц祴鍊肩粰bar + * 浣 foor 灞炴ф牴鏈笉瀛樺湪,鍊兼槸 undefined,浠巙ndefined閲岄潰鎵惧嚭bar灞炴,灏卞紓甯镐簡 + + # 濡傛灉瑕佸皢涓涓凡缁忓0鏄庣殑鍙橀噺鐢ㄤ簬瑙f瀯璧嬪,蹇呴』闈炲父灏忓績 + // 閿欒鐨勫啓娉 + let x; + {x} = {x: 1}; + // SyntaxError: syntax error + + * JavaScript 寮曟搸浼氬皢{x}鐞嗚В鎴愪竴涓唬鐮佸潡,浠庤屽彂鐢熻娉曢敊璇 + * 鍙湁涓嶅皢澶ф嫭鍙峰啓鍦ㄨ棣,閬垮厤 JavaScript 灏嗗叾瑙i噴涓轰唬鐮佸潡,鎵嶈兘瑙e喅杩欎釜闂 + // 姝g‘鐨勫啓娉 + let x; + ({x} = {x: 1}); + + # 鍏充簬鍦嗘嫭鍙蜂笌瑙f瀯璧嬪肩殑鍏崇郴 + ({} = [true, false]); + ({} = 'abc'); + ({} = []); + + * 涓婇潰鐨勮娉曠█濂囧彜鎬,姣棤鎰忎箟,浣嗘槸鍗存槸鍚堟硶鐨 + + # 瀵硅薄鐨勮В鏋勮祴鍊,鍙互寰堟柟渚垮湴灏嗙幇鏈夊璞$殑鏂规硶,璧嬪煎埌鏌愪釜鍙橀噺 + let { log, sin, cos } = Math; + + # 鐢变簬鏁扮粍鏈川鏄壒娈婄殑瀵硅薄,鍥犳鍙互瀵规暟缁勮繘琛屽璞″睘鎬х殑瑙f瀯銆 + let arr = [1, 2, 3]; + //鎶婄0涓崟浣,璧嬪肩粰first + //鎶婃渶鍚庝竴涓崟浣,璧嬪肩粰last + let {0 : first, [arr.length - 1] : last} = arr; + first // 1 + last // 3 + + * 涓婇潰浠g爜瀵规暟缁勮繘琛屽璞¤В鏋,鏁扮粍arr鐨0閿搴旂殑鍊兼槸1,[arr.length - 1]灏辨槸2閿,瀵瑰簲鐨勫兼槸3 + * 鏂规嫭鍙疯繖绉嶅啓娉,灞炰簬"灞炴у悕琛ㄨ揪寮"(鍙傝銆婂璞$殑鎵╁睍銆嬩竴绔) ??? + +------------------------------------ +瀛楃涓茬殑瑙f瀯璧嬪 | +------------------------------------ + # 瀛楃涓蹭篃鍙互瑙f瀯璧嬪,鍥犱负瀛楃涓茶杞崲鎴愪簡涓涓被浼兼暟缁勭殑瀵硅薄 + const [a, b, c, d, e] = 'hello'; + a // "h" + b // "e" + c // "l" + d // "l" + e // "o" + + # 绫讳技鏁扮粍鐨勫璞¢兘鏈変竴涓猯ength灞炴,鍥犳杩樺彲浠ュ杩欎釜灞炴цВ鏋勮祴鍊笺 + let {length : len} = 'hello'; + len // 5 + +------------------------------------ +鏁板煎拰甯冨皵鍊肩殑瑙f瀯璧嬪 | +------------------------------------ + # 瑙f瀯璧嬪兼椂,濡傛灉绛夊彿鍙宠竟鏄暟鍊煎拰甯冨皵鍊,鍒欎細鍏堣浆涓哄璞 + let {toString: s} = 123; + s === Number.prototype.toString // true + + let {toString: s} = true; + s === Boolean.prototype.toString // true + + # 瑙f瀯璧嬪肩殑瑙勫垯鏄,鍙绛夊彿鍙宠竟鐨勫间笉鏄璞℃垨鏁扮粍,灏卞厛灏嗗叾杞负瀵硅薄 + 鐢变簬undefined鍜宯ull鏃犳硶杞负瀵硅薄,鎵浠ュ瀹冧滑杩涜瑙f瀯璧嬪,閮戒細鎶ラ敊 + let { prop: x } = undefined; // TypeError + let { prop: y } = null; // TypeError + + +------------------------------------ +鍑芥暟鍙傛暟鐨勮В鏋勮祴鍊 | +------------------------------------ + # 鍑芥暟鐨勫弬鏁颁篃鍙互浣跨敤瑙f瀯璧嬪 + function add([x, y]){ + return x + y; + } + + add([1, 2]); // 3 + + * 涓婇潰浠g爜涓,鍑芥暟add鐨勫弬鏁拌〃闈笂鏄竴涓暟缁勪絾鍦ㄤ紶鍏ュ弬鏁扮殑閭d竴鍒,鏁扮粍鍙傛暟灏辫瑙f瀯鎴愬彉閲弜鍜寉 + * 瀵逛簬鍑芥暟鍐呴儴鐨勪唬鐮佹潵璇,瀹冧滑鑳芥劅鍙楀埌鐨勫弬鏁板氨鏄痻鍜寉 + + # 鍙︿竴涓緥瀛,涓鐪嬪氨鎳 + let result = [[1, 2], [3, 4]].map(function([x,y]){return x + y}); + console.log(result); //聽[3, 7] + + # 鍑芥暟鍙傛暟鐨勮В鏋勪篃鍙互浣跨敤榛樿鍊 + function move({x = 0, y = 0} = {}) { + return [x, y]; + } + + move({x: 3, y: 8}); // [3, 8] + move({x: 3}); // [3, 0] + move({}); // [0, 0] + move(); // [0, 0] + + * move鐨勫弬鏁版槸涓涓璞,閫氳繃瀵硅繖涓璞¤繘琛岃В鏋,寰楀埌鍙橀噺x鍜寉鐨勫 + * 濡傛灉瑙f瀯澶辫触,x鍜寉绛変簬榛樿鍊 + * 涓嬮潰鐨勫啓娉曚細寰楀埌涓嶄竴鏍风殑缁撴灉 + function move({x, y} = { x: 0, y: 0 }) { + return [x, y]; + } + + move({x: 3, y: 8}); // [3, 8] + move({x: 3}); // [3, undefined] + move({}); // [undefined, undefined] + move(); // [0, 0] + + * move鐨勫弬鏁版寚瀹氶粯璁ゅ,鑰屼笉鏄负鍙橀噺x鍜寉鎸囧畾榛樿鍊 + * 鎵浠ヤ細寰楀埌涓庡墠涓绉嶅啓娉曚笉鍚岀殑缁撴灉 + + # undefined灏变細瑙﹀彂鍑芥暟鍙傛暟鐨勯粯璁ゅ + [1, undefined, 3].map((x = 'yes') => x); + // [ 1, 'yes', 3 ] + + # 鎴戠殑鐞嗚В + * 鍏跺疄灏辨槸python閲岄潰鐨 **浼犲弬 + + --------------------------------------------- + function test({name,age}={name:'Kevin',age:23}){ + console.log(name); //Kevin + console.log(age); //23 + } + test() + * 寰堢畝鍗 + --------------------------------------------- + function test({name,age}){ + console.log(name); //undefined + console.log(age); //undefined + } + test({}) + * 濡傛灉涓嶄紶鍊奸偅涔堝氨绛変簬 {name,age} = undefined,灏变細鎶涘嚭寮傚父 + * 濡傛灉浼犱簡鍊间箣鍚, + --------------------------------------------- + function test({name,age} = {name:'Kevin',age:23}){ + console.log(name); //Litch + console.log(age); //undefined + } + test({name:'Litch'}) + * test()鎵ц鐨勬椂鍊欎紶閫掔殑鍙傛暟,浼氭浛浠 {name:'Kevin',age:23} 瀹 + * 鐢变簬test()鎵ц浼犻掔殑鍙傛暟娌℃湁age灞炴,鎵浠ge涓 undefined + --------------------------------------------- + function test({name:name,age:age = 27} = {name:'Kevin',age:23}){ + console.log(name); //Litch + console.log(age); //27 + } + test({name:'Litch'}) + * 鍚屼笂,涓嶈繃褰㈠弬閲岄潰鐨刟ge,宸茬粡鍏峰浜嗛粯璁ゅ间簡:27,鎵浠ュ湪undefined鐨勬椂鍊,浼氫娇鐢ㄩ粯璁ゅ + --------------------------------------------- + + +------------------------------------ +鍦嗘嫭鍙风殑闂 | +------------------------------------ + # 瑙f瀯璧嬪艰櫧鐒跺緢鏂逛究,浣嗘槸瑙f瀽璧锋潵骞朵笉瀹规槗,瀵逛簬缂栬瘧鍣ㄦ潵璇,涓涓紡瀛愬埌搴曟槸妯″紡,杩樻槸琛ㄨ揪寮, + 娌℃湁鍔炴硶浠庝竴寮濮嬪氨鐭ラ亾,蹇呴』瑙f瀽鍒(鎴栬В鏋愪笉鍒)绛夊彿鎵嶈兘鐭ラ亾 + + # 鐢辨甯︽潵鐨勯棶棰樻槸,濡傛灉妯″紡涓嚭鐜板渾鎷彿鎬庝箞澶勭悊,ES6 鐨勮鍒欐槸,鍙鏈夊彲鑳藉鑷磋В鏋勭殑姝т箟,灏变笉寰椾娇鐢ㄥ渾鎷彿 + + # 浣嗘槸,杩欐潯瑙勫垯瀹為檯涓婁笉閭d箞瀹规槗杈ㄥ埆,澶勭悊璧锋潵鐩稿綋楹荤儲,鍥犳,寤鸿鍙鏈夊彲鑳,灏变笉瑕佸湪妯″紡涓斁缃渾鎷彿 + + # 涓嶈兘浣跨敤鍦嗘嫭鍙风殑鎯呭喌 + 1,鍙橀噺澹版槑璇彞 + let [(a)] = [1]; + + let {x: (c)} = {}; + let ({x: c}) = {}; + let {(x: c)} = {}; + let {(x): c} = {}; + + let { o: ({ p: p }) } = { o: { p: 2 } }; + + * 涓婇潰 6 涓鍙ラ兘浼氭姤閿,鍥犱负瀹冧滑閮芥槸鍙橀噺澹版槑璇彞,妯″紡涓嶈兘浣跨敤鍦嗘嫭鍙 + + 2,鍙傛暟鍑芥暟 + // 鎶ラ敊 + function f([(z)]) { return z; } + // 鎶ラ敊 + function f([z,(x)]) { return x; } + + * 鍑芥暟鍙傛暟涔熷睘浜庡彉閲忓0鏄,鍥犳涓嶈兘甯︽湁鍦嗘嫭鍙 + + 3,璧嬪艰鍙ユā寮 + // 鍏ㄩ儴鎶ラ敊 + ({ p: a }) = { p: 42 }; + ([a]) = [5]; + + * 灏嗘暣涓ā寮忔斁鍦ㄥ渾鎷彿涔嬩腑锛屽鑷存姤閿 + + // 鎶ラ敊 + [({ p: a }), { x: c }] = [{}, {}]; + * 灏嗕竴閮ㄥ垎妯″紡鏀惧湪鍦嗘嫭鍙蜂箣涓,瀵艰嚧鎶ラ敊 + + # 鍙互浣跨敤鍦嗘嫭鍙风殑鎯呭喌 + + * 鍙互浣跨敤鍦嗘嫭鍙风殑鎯呭喌鍙湁涓绉:璧嬪艰鍙ョ殑闈炴ā寮忛儴鍒,鍙互浣跨敤鍦嗘嫭鍙 + + [(b)] = [3]; // 姝g‘ + ({ p: (d) } = {}); // 姝g‘ + [(parseInt.prop)] = [3]; // 姝g‘ + + * 涓婇潰涓夎璇彞閮藉彲浠ユ纭墽琛,鍥犱负棣栧厛瀹冧滑閮芥槸璧嬪艰鍙,鑰屼笉鏄0鏄庤鍙 + * 鍏舵瀹冧滑鐨勫渾鎷彿閮戒笉灞炰簬妯″紡鐨勪竴閮ㄥ垎 + 绗竴琛岃鍙ヤ腑,妯″紡鏄彇鏁扮粍鐨勭涓涓垚鍛,璺熷渾鎷彿鏃犲叧 + 绗簩琛岃鍙ヤ腑,妯″紡鏄痯,鑰屼笉鏄痙 + 绗笁琛岃鍙ヤ笌绗竴琛岃鍙ョ殑鎬ц川涓鑷 + + +------------------------------------ +鐢ㄩ | +------------------------------------ + 1,鍙橀噺鍊煎揩閫熶氦鎹 + let x = 1; + let y = 1; + + [x,y] = [y,x] + + 2,浠庡嚱鏁拌繑鍥炲涓 + function example() { + return [1, 2, 3]; + } + let [a, b, c] = example(); + + // 杩斿洖涓涓璞 + function example() { + return { + foo: 1, + bar: 2 + }; + } + let { foo, bar } = example(); + + * 鍑芥暟鍙兘杩斿洖涓涓,濡傛灉瑕佽繑鍥炲涓,鍙兘灏嗗畠浠斁鍦ㄦ暟缁勬垨瀵硅薄閲岃繑鍥 + * 鏈変簡瑙f瀯璧嬪,鍙栧嚭杩欎簺鍊煎氨闈炲父鏂逛究 + + 3,鍑芥暟鍙傛暟鐨勫畾涔 + // 鍙傛暟鏄竴缁勬湁娆″簭鐨勫 + function f([x, y, z]) { ... } + f([1, 2, 3]); + + // 鍙傛暟鏄竴缁勬棤娆″簭鐨勫 + function f({x, y, z}) { ... } + f({z: 3, y: 2, x: 1}); + + * 瑙f瀯璧嬪煎彲浠ユ柟渚垮湴灏嗕竴缁勫弬鏁颁笌鍙橀噺鍚嶅搴旇捣鏉 + + 4,鎻愬彇json鏁版嵁 + + let jsonData = { + id: 42, + status: "OK", + data: [867, 5309] + }; + + let { id, status, data: number } = jsonData; + console.log(id, status, number); // 42, "OK", [867, 5309] + + * 瑙f瀯璧嬪煎鎻愬彇 JSON 瀵硅薄涓殑鏁版嵁,灏ゅ叾鏈夌敤 + + 5,鍑芥暟鍙傛暟鐨勯粯璁ゅ + jQuery.ajax = function (url, { + async = true, + beforeSend = function () {}, + cache = true, + complete = function () {}, + crossDomain = false, + global = true, + // ... more config + }) { + // ... do stuff + }; + + * 棰勫畾涔夊嚱鏁扮殑涓浜涢粯璁ゅ + * 鎸囧畾鍙傛暟鐨勯粯璁ゅ,灏遍伩鍏嶄簡鍦ㄥ嚱鏁颁綋鍐呴儴鍐嶅啓var foo = config.foo || 'default foo';杩欐牱鐨勮鍙 + + 6,閬嶅巻Map缁撴瀯 + const map = new Map(); + map.set('first', 'hello'); + map.set('second', 'world'); + for (let entry of map) { + console.log(entry); + } + // ["first", "hello"] + // ["second", "world"] + for (let [key, value] of map) { + console.log(key + " is " + value); + } + //first is hello + //index.html:20 second is world + + * 浠讳綍閮ㄧ讲浜 Iterator 鎺ュ彛鐨勫璞,閮藉彲浠ョ敤for...of寰幆閬嶅巻 + * Map 缁撴瀯鍘熺敓鏀寔 Iterator 鎺ュ彛,閰嶅悎鍙橀噺鐨勮В鏋勮祴鍊,鑾峰彇閿悕鍜岄敭鍊煎氨闈炲父鏂逛究 + * 濡傛灉鍙兂鑾峰彇閿悕,鎴栬呭彧鎯宠幏鍙栭敭鍊,鍙互鍐欐垚涓嬮潰杩欐牱 + // 鑾峰彇閿悕 + for (let [key] of map) { + // ... + } + + // 鑾峰彇閿 + for (let [,value] of map) { + // ... + } + + 7,杈撳叆妯″潡鐨勬寚瀹氭柟娉 + + const { SourceMapConsumer, SourceNode } = require("source-map"); + + * 鍔犺浇妯″潡鏃,寰寰闇瑕佹寚瀹氳緭鍏ュ摢浜涙柟娉 + * 瑙f瀯璧嬪间娇寰楄緭鍏ヨ鍙ラ潪甯告竻鏅 diff --git "a/JavaSript/ES6/es6-\346\200\273\347\273\223.js" "b/JavaSript/ES6/es6-\346\200\273\347\273\223.js" new file mode 100644 index 00000000..9015cdc0 --- /dev/null +++ "b/JavaSript/ES6/es6-\346\200\273\347\273\223.js" @@ -0,0 +1,4 @@ +-------------------------------- +鍙橀噺澹版槑 | +-------------------------------- + \ No newline at end of file diff --git "a/JavaSript/ES6/es6-\346\211\251\345\261\225-lambda.js" "b/JavaSript/ES6/es6-\346\211\251\345\261\225-lambda.js" deleted file mode 100644 index 8b2b680a..00000000 --- "a/JavaSript/ES6/es6-\346\211\251\345\261\225-lambda.js" +++ /dev/null @@ -1,10 +0,0 @@ --------------------------- -lambda | --------------------------- - - var func = (x) => { - console.log(x); - } - - func("Lambda") - diff --git "a/JavaSript/ES6/es6-\346\211\251\345\261\225-string-\346\250\241\346\235\277.js" "b/JavaSript/ES6/es6-\346\211\251\345\261\225-string-\346\250\241\346\235\277.js" new file mode 100644 index 00000000..2a585a08 --- /dev/null +++ "b/JavaSript/ES6/es6-\346\211\251\345\261\225-string-\346\250\241\346\235\277.js" @@ -0,0 +1,345 @@ +------------------------ +字符串模板 | +------------------------ + # 传统的 JavaScript 语言输出模板通常是这样写的 + $('#result').append( + 'There are ' + basket.count + ' ' + + 'items in your basket, ' + + '' + basket.onSale + + ' are on sale!' + ); + + # 上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题 + $('#result').append( + ` + There are ${basket.count} items + in your basket, ${basket.onSale} + are on sale! + ` + ); + + # 模板字符串 + * 模板字符串(template string)是增强版的字符串,用反引号(`)标识 + * '它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量' + + // 普通字符串 + `In JavaScript '\n' is a line-feed.` + + // 多行字符串 + `In JavaScript this is + not legal.` + + console.log(`string text line 1 + string text line 2`); + + // 字符串中嵌入变量 + let name = "Bob", time = "today"; + `Hello ${name}, how are you ${time}?` + + * 如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。 + let greeting = `\`Yo\` World!`; + + * 如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中 + let str = ` + 前面的空格换行都被保留了 + `; + console.log(str); + + * 如果你不想要这个换行,空格可以使用trim方法消除它 + console.log(str.trim()); + + * 模板字符串中嵌入变量,需要将变量名写在${}之中 + function authorize(user, action) { + if (!user.hasPrivilege(action)) { + throw new Error( + // 传统写法为 + // 'User ' + // + user.name + // + ' is not authorized to do ' + // + action + // + '.' + `User ${user.name} is not authorized to do ${action}.`); + } + } + + * ${}内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性 + let x = 1; + let y = 2; + + `${x} + ${y} = ${x + y}` + // "1 + 2 = 3" + + `${x} + ${y * 2} = ${x + y * 2}` + // "1 + 4 = 5" + + let obj = {x: 1, y: 2}; + `${obj.x + obj.y}` + // "3" + + * 模板字符串之中还能调用函数。 + function fn() { + return "Hello World"; + } + + `foo ${fn()} bar` + // foo Hello World bar + + * 如果大括号中的值不是字符串,将按照一般的规则转为字符串比如,大括号中是一个对象,将默认调用对象的toString方法 + + * 如果模板字符串中的变量没有声明,将报错 + + // 变量place没有声明,报错 + let msg = `Hello, ${place}`; + + * 由于模板字符串的大括号内部,就是执行 JavaScript 代码,因此如果大括号内部是一个字符串将会原样输出 + `Hello ${'World'}` + // "Hello World" + + * 模板字符串甚至还能嵌套。 + const tmpl = addrs => + ` + + ${addrs.map(addr => + ` + + + ` + ).join('')} +
${addr.first}
${addr.last}
+ `; + + * 就是在 `你好啊 ${getNameById(`747692844`)}` + + * 如果需要引用模板字符串本身,在需要时执行,可以像下面这样写 + // 写法一 + let str = 'return ' + '`Hello ${name}!`'; + let func = new Function('name', str); + func('Jack') // "Hello Jack!" + + // 写法二 + let str = '(name) => `Hello ${name}!`'; + let func = eval.call(null, str); + func('Jack') // "Hello Jack!" + +------------------------ +模板编译 | +------------------------ + # 一个通过模板字符串,生成正式模板的实例 + + 1,定义模板 + let template = ` +
    + <%for (let i of users){%> +
  • <%=i.name%>,<%=i.age%>
  • + <%}%> +
+ `; + + * 上面代码在模板字符串之中,放置了一个常规模板 + * 该模板使用<%...%>放置 JavaScript 代码,使用<%= ... %>输出 JavaScript 表达式 + + 2,编译模版方法 + function compile(template){ + const evalExpr = /<%=(.+?)%>/g; + const expr = /<%([\s\S]+?)%>/g; + + template = template + .replace(evalExpr, '`); \n echo( $1 ); \n echo(`') + .replace(expr, '`); \n $1 \n echo(`'); + + template = 'echo(`' + template + '`);'; + + let script = + ` (function parse(data){ + let output = ""; + + function echo(html){ + output += html; + } + + ${ template } + + return output; + }) + `; + + return script; + } + + 3,执行编译和渲染 + + //获取编译后的模版 + let parse = eval(compile(template)); + + //定义填充数据 + var users = [{name:'Kevin',age:23},{name:'Litch',age:25}]; + + //执行模版渲染,返回渲染结果 + console.log(parse(users)); + + +------------------------ +模板标签 | +------------------------ + # 模板字符串,可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串,这被称为"标签模板"功能(tagged template) + alert`Hello` + + # 标签模板其实不是模板,而是函数调用的一种特殊形式,"标签"指的就是函数,紧跟在后面的模板字符串就是它的参数 + + # 但是,如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数 + let a = 5; + let b = 10; + + tag`Hello ${ a + b } world ${ a * b }`; + + // 等同于 ===== 根据 ${} 把字符串切分开来,把每段儿都放在一个数组里传递给第一个参数,然后把${}里面的值,挨个写入后面的参数 + // + tag(['Hello ', ' world ', ''], 15, 50); + + * 函数tag依次会接收到多个参数 + function tag(stringArr, value1, value2){ + // ... + } + // 等同于 + function tag(stringArr, ...values){ + // ... + } + * tag函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分 + * 也就是说,变量替换只发生在数组的第一个成员与第二个成员之间,第二个成员与第三个成员之间,以此类推 + * 也就是说,tag函数实际上以下面的形式调用(第一个数组参数最后一定有一个空字符串('')) + tag(['Hello ', ' world ', ''], 15, 50) + + * 我们可以按照需要编写tag函数的代码 + let a = 5; + let b = 10; + + function tag(s, v1, v2) { + console.log(s[0]); // "Hello " + console.log(s[1]); // " world " + console.log(s[2]); // "" + console.log(v1); // 15 + console.log(v2); // 50 + return "OK"; + } + tag`Hello ${ a + b } world ${ a * b}`; + + * 更复杂的例子 + function passthru(literals) { + let result = ''; + let i = 0; + while (i < literals.length) { + result += literals[i++]; + if (i < arguments.length) { //通过arguments来获取传递进来的模版参数 + result += arguments[i]; + } + } + return result; + } + + * rest 参数的函数调用 + function passthru(literals, ...values) { + let output = ""; + let index; + for (index = 0; index < values.length; index++) { //遍历模版参数 + output += literals[index] + values[index]; + } + output += literals[index] + return output; + } + + * "标签模板"的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容 + function SaferHTML(templateData) { + let s = templateData[0]; + for (let i = 1; i < arguments.length; i++) { + let arg = String(arguments[i]); + // Escape special characters in the substitution. + s += arg.replace(/&/g, "&") + .replace(//g, ">"); + + // Don't escape special characters in the template. + s += templateData[i]; + } + return s; + } + + let message = SaferHTML`

${sender} has sent you a message.

`; + + * ender变量往往是用户提供的,经过 SaferHTML 函数处理,里面的特殊字符都会被转义 + + * 标签模板的另一个应用,就是多语言转换(国际化处理) + i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!` + // "欢迎访问xxx,您是第xxxx位访问者!" + + * 模板处理函数的第一个参数(模板字符串数组),还有一个raw属性 + console.log`123` + // ["123", raw: Array[1]] + + * console.log接受的参数,实际上是一个数组,该数组有一个raw属性 + + * tag函数的第一个参数,有一个raw属性,也指向一个数组,该数组的成员与第一个参数完全一致 + tag`First line\nSecond line` + function tag(strings) { + console.log(strings.raw[0]); + // strings.raw[0] 为 "First line\\nSecond line" + // 打印输出 "First line\nSecond line" + } + * 比如 strings 数组是 ["First line\nSecond line"] + * 那么 strings.raw 数组就是 ["First line\\nSecond line"] + * 两者唯一的区别,就是字符串里面的斜杠都被转义了,\ + * 比如,strings.raw 数组会将\n视为\\和n两个字符,而不是换行符 + 这是为了方便取得转义之前的原始模板而设计的 + + * String.raw() 方法 + * 就是把 `` 转换为正规的字符串,然后把``里面看不见的那些换行啊之类的符号,都添加字符串的转义符号 + * ES6 还为原生的 String 对象,提供了一个raw方法 + * String.raw方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串对应于替换变量后的模板字符串 + String.raw`Hi\n${2+3}!`; + // 返回 "Hi\\n5!" + + String.raw`Hi\u000A!`; + // 返回 "Hi\\u000A!" + + * 如果原字符串的斜杠已经转义,那么String.raw会进行再次转义 + String.raw`Hi\\n` + // 返回 "Hi\\\\n" + + * String.raw方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,'方便下一步作为字符串来使用' + * String.raw方法也可以作为正常的函数使用,这时,它的第一个参数,应该是一个具有raw属性的对象,'且raw属性的值应该是一个数组' + + String.raw({ raw: 'test' }, 0, 1, 2); + // 't0e1s2t' + + // 等同于 + String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2); + + * 作为函数,String.raw的代码实现基本如下 + String.raw = function (strings, ...values) { + let output = ''; + let index; + + for (index = 0; index < values.length; index++) { + output += strings.raw[index] + values[index]; + } + + output += strings.raw[index] + return output; + } + +------------------------ +字符串模版的限制 | +------------------------ + * 标签模板里面,可以内嵌其他语言,但是,模板字符串默认会将字符串转义,导致无法嵌入其他语言 + * 为了解决这个问题,ES2018 放松了对标签模板里面的字符串转义的限制 + * 如果遇到不合法的字符串转义,就返回undefined,而不是报错,并且从raw属性上面可以得到原始字符串 + function tag(strs) { + strs[0] === undefined //是不合法的字符串转义 + strs.raw[0] === "\\unicode and \\u{55}"; //可以获取其原始字符串 + } + tag`\unicode and \u{55}` + + * 上面代码中,模板字符串原本是应该报错的,但是由于放松了对字符串转义的限制,所以不报错了 + * JavaScript 引擎将第一个字符设置为undefined,但是raw属性依然可以得到原始字符串,因此tag函数还是可以对原字符串进行处理 + + * 注意,这种对字符串转义的放松,只在标签模板解析字符串时生效,不是标签模板的场合,依然会报错 + let bad = `bad escape sequence: \unicode`; // 报错 \ No newline at end of file diff --git "a/JavaSript/ES6/es6-\346\211\251\345\261\225-string.js" "b/JavaSript/ES6/es6-\346\211\251\345\261\225-string.js" new file mode 100644 index 00000000..fb0fea55 --- /dev/null +++ "b/JavaSript/ES6/es6-\346\211\251\345\261\225-string.js" @@ -0,0 +1,155 @@ +---------------------------- +string | +---------------------------- + 1,瀛楃鐨 Unicode 琛ㄧず娉 + 2,codePointAt() + 3,String.fromCodePoint() + 4,瀛楃涓茬殑閬嶅巻鍣ㄦ帴鍙 + 5,at() + 6,normalize() + 7,includes(), startsWith(), endsWith() + 8,repeat() + 9,padStart()锛宲adEnd() + 10,matchAll() + 11,妯℃澘瀛楃涓 + 12,瀹炰緥:妯℃澘缂栬瘧 + 13,鏍囩妯℃澘 + 14,String.raw() + 15,妯℃澘瀛楃涓茬殑闄愬埗 + +---------------------------- +瀛楃涓蹭笌 Unicode | +---------------------------- + codePointAt(); + codePointAt鏂规硶鏄祴璇曚竴涓瓧绗︾敱涓や釜瀛楄妭杩樻槸鐢卞洓涓瓧鑺傜粍鎴愮殑鏈绠鍗曟柟娉曘 + + function is32Bit(c) { + return c.codePointAt(0) > 0xFFFF; + } + + is32Bit("馉") // true + is32Bit("a") // false + + String.fromCharCode() + +---------------------------- +瀛楃涓茬殑閬嶅巻鎺ュ彛 | +---------------------------- + # ES6 涓哄瓧绗︿覆娣诲姞浜嗛亶鍘嗗櫒鎺ュ彛(璇﹁銆奍terator銆嬩竴绔),浣垮緱瀛楃涓插彲浠ヨfor...of寰幆閬嶅巻 + for (let codePoint of 'foo') { + console.log(codePoint) + } + // "f" + // "o" + // "o" + + # 闄や簡閬嶅巻瀛楃涓,杩欎釜閬嶅巻鍣ㄦ渶澶х殑浼樼偣鏄彲浠ヨ瘑鍒ぇ浜0xFFFF鐨勭爜鐐,浼犵粺鐨刦or寰幆鏃犳硶璇嗗埆杩欐牱鐨勭爜鐐 + let text = String.fromCodePoint(0x20BB7); + for (let i = 0; i < text.length; i++) { + console.log(text[i]); + } + // " " + // " " + + for (let i of text) { + console.log(i); + } + // "馉" + +---------------------------- +at() | +---------------------------- + # ES5 瀵瑰瓧绗︿覆瀵硅薄鎻愪緵charAt鏂规硶,杩斿洖瀛楃涓茬粰瀹氫綅缃殑瀛楃,璇ユ柟娉曚笉鑳借瘑鍒爜鐐瑰ぇ浜0xFFFF鐨勫瓧绗 + 'abc'.charAt(0) // "a" + '馉'.charAt(0) // "\uD842" + + * 涓婇潰浠g爜涓殑绗簩鏉¤鍙,charAt鏂规硶鏈熸湜杩斿洖鐨勬槸鐢2涓瓧鑺傝〃绀虹殑瀛楃 + * 浣嗘眽瀛"馉"鍗犵敤浜4涓瓧鑺,charAt(0)琛ㄧず鑾峰彇杩4涓瓧鑺備腑鐨勫墠2涓瓧鑺,寰堟樉鐒,杩欐槸鏃犳硶姝e父鏄剧ず鐨 + + # 绗︿覆瀹炰緥鐨刟t鏂规硶,鍙互璇嗗埆 Unicode 缂栧彿澶т簬0xFFFF鐨勫瓧绗,杩斿洖姝g‘鐨勫瓧绗 + 'abc'.at(0) // "a" + '馉'.at(0) // "馉" + +---------------------------- +normalize() | +---------------------------- + 璁稿娆ф床璇█鏈夎璋冪鍙峰拰閲嶉煶绗﹀彿,涓轰簡琛ㄧず瀹冧滑.......... + 涓嶈〃浜... + +------------------------------------ +includes(), startsWith(), endsWith()| +------------------------------------- + includes() + * 杩斿洖甯冨皵鍊,琛ㄧず鏄惁鎵惧埌浜嗗弬鏁板瓧绗︿覆 + startsWith() + * 杩斿洖甯冨皵鍊,琛ㄧず鍙傛暟瀛楃涓叉槸鍚﹀湪鍘熷瓧绗︿覆鐨勫ご閮 + endsWith() + * 杩斿洖甯冨皵鍊,琛ㄧず鍙傛暟瀛楃涓叉槸鍚﹀湪鍘熷瓧绗︿覆鐨勫熬閮 + + * 杩欎笁涓柟娉曢兘鏀寔绗簩涓弬鏁,琛ㄧず寮濮嬫悳绱㈢殑浣嶇疆 + * 浣跨敤绗簩涓弬鏁版椂,endsWith鐨勮涓轰笌鍏朵粬涓や釜鏂规硶鏈夋墍涓嶅悓,瀹冮拡瀵瑰墠n涓瓧绗 + 鑰屽叾浠栦袱涓柟娉曢拡瀵逛粠绗琻涓綅缃洿鍒板瓧绗︿覆缁撴潫 + +---------------------------- +repeat() | +---------------------------- + # repeat鏂规硶杩斿洖涓涓柊瀛楃涓,琛ㄧず灏嗗師瀛楃涓查噸澶峮娆 + 'x'.repeat(3) // "xxx" + 'hello'.repeat(2) // "hellohello" + 'na'.repeat(0) // "" + + # 鍙傛暟濡傛灉鏄皬鏁颁細琚彇鏁 + 'na'.repeat(2.9) // "nana" + + # 濡傛灉repeat鐨勫弬鏁版槸璐熸暟鎴栬匢nfinity,浼氭姤閿 + 'na'.repeat(Infinity) + // RangeError + 'na'.repeat(-1) + // RangeError + + # 濡傛灉鍙傛暟鏄 0 鍒-1 涔嬮棿鐨勫皬鏁,鍒欑瓑鍚屼簬 0 + * 杩欐槸鍥犱负浼氬厛杩涜鍙栨暣杩愮畻,0 鍒-1 涔嬮棿鐨勫皬鏁,鍙栨暣浠ュ悗绛変簬-0,repeat瑙嗗悓涓 0 + 'na'.repeat(-0.9) // "" + * 鍙傛暟NaN绛夊悓浜 0 + 'na'.repeat(NaN) // "" + + # 濡傛灉repeat鐨勫弬鏁版槸瀛楃涓,鍒欎細鍏堣浆鎹㈡垚鏁板瓧 + 'na'.repeat('na') // "" + 'na'.repeat('3') // "nanana" + +---------------------------- +padStart(),padEnd() | +---------------------------- + # 棣栬ˉ榻,鍜屽熬琛ラ綈 + 'x'.padStart(5, 'ab') // 'ababx' + 'x'.padStart(4, 'ab') // 'abax' + + 'x'.padEnd(5, 'ab') // 'xabab' + 'x'.padEnd(4, 'ab') // 'xaba' + + # 濡傛灉鍘熷瓧绗︿覆鐨勯暱搴,绛変簬鎴栧ぇ浜庢寚瀹氱殑鏈灏忛暱搴,鍒欒繑鍥炲師瀛楃涓 + 'xxx'.padStart(2, 'ab') // 'xxx' + 'xxx'.padEnd(2, 'ab') // 'xxx' + + # 濡傛灉鐢ㄦ潵琛ュ叏鐨勫瓧绗︿覆涓庡師瀛楃涓,涓よ呯殑闀垮害涔嬪拰瓒呰繃浜嗘寚瀹氱殑鏈灏忛暱搴,鍒欎細鎴幓瓒呭嚭浣嶆暟鐨勮ˉ鍏ㄥ瓧绗︿覆 + 'abc'.padStart(10, '0123456789') + // '0123456abc' + + # 濡傛灉鐪佺暐绗簩涓弬鏁,榛樿浣跨敤绌烘牸琛ュ叏闀垮害 + 'x'.padStart(4) // ' x' + 'x'.padEnd(4) // 'x ' + + # padStart鐨勫父瑙佺敤閫旀槸涓烘暟鍊艰ˉ鍏ㄦ寚瀹氫綅鏁 + '1'.padStart(10, '0') // "0000000001" + '12'.padStart(10, '0') // "0000000012" + '123456'.padStart(10, '0') // "0000123456" + + # 鍙︿竴涓敤閫旀槸鎻愮ず瀛楃涓叉牸寮 + '12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12" + '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12" + +---------------------------- +matchAll() | +---------------------------- + * 杩斿洖涓涓鍒欒〃杈惧紡鍦ㄥ綋鍓嶅瓧绗︿覆鐨勬墍鏈夊尮閰,璇﹁銆婃鍒欑殑鎵╁睍銆嬬殑涓绔 + diff --git "a/JavaSript/ES6/es6-\346\211\251\345\261\225-\345\207\275\346\225\260.js" "b/JavaSript/ES6/es6-\346\211\251\345\261\225-\345\207\275\346\225\260.js" new file mode 100644 index 00000000..8adf9980 --- /dev/null +++ "b/JavaSript/ES6/es6-\346\211\251\345\261\225-\345\207\275\346\225\260.js" @@ -0,0 +1,479 @@ +---------------------------- +鍑芥暟鐨勬墿灞 | +---------------------------- + 1,鍑芥暟鍙傛暟鐨勯粯璁ゅ + 2,rest 鍙傛暟 + 3,涓ユ牸妯″紡 + 4,name 灞炴 + 5,绠ご鍑芥暟 + 6,鍙屽啋鍙疯繍绠楃 + 7,灏捐皟鐢ㄤ紭鍖 + 8,鍑芥暟鍙傛暟鐨勫熬閫楀彿 + +---------------------------- +鍑芥暟鍙傛暟鐨勯粯璁ゅ | +---------------------------- + # ES6 鍏佽涓哄嚱鏁扮殑鍙傛暟璁剧疆榛樿鍊,鍗崇洿鎺ュ啓鍦ㄥ弬鏁板畾涔夌殑鍚庨潰 + let work = function(id='F8575532'){ + console.log(id) + } + work(null) //null + work(undefined) //F8575532 + + * 鍙鍙傛暟 === undefined 鎵嶄細浣跨敤榛樿鍊 + + # 鍙傛暟鍙橀噺鏄粯璁ゅ0鏄庣殑,鎵浠ヤ笉鑳界敤let鎴朿onst鍐嶆澹版槑 + function foo(x = 5) { + let x = 1; // error + const x = 2; // error + } + + # 浣跨敤鍙傛暟榛樿鍊兼椂,鍑芥暟涓嶈兘鏈夊悓鍚嶅弬鏁 + // 涓嶆姤閿 + function foo(x, x, y) { + // ... + } + + // 鎶ラ敊 + function foo(x, x, y = 1) { + // ... + } + // SyntaxError: Duplicate parameter name not allowed in this context + + # 鍙傛暟榛樿鍊间笉鏄紶鍊肩殑,鑰屾槸姣忔閮介噸鏂拌绠楅粯璁ゅ艰〃杈惧紡鐨勫,涔熷氨鏄'鍙傛暟榛樿鍊兼槸鎯版ф眰鍊肩殑' + let x = 99; + function foo(p = x + 1) { + console.log(p); + } + foo() // 100 + x = 100; + foo() // 101 + + * 鍙傛暟p鐨勯粯璁ゅ兼槸x + 1,杩欐椂,姣忔璋冪敤鍑芥暟foo,閮戒細閲嶆柊璁$畻x + 1,鑰屼笉鏄粯璁绛変簬 100 + + # 涓庤В鏋勮祴鍊奸粯璁ゅ肩粨鍚堜娇鐢 + function foo({x, y = 5}) { + console.log(x, y); + } + + foo({}) // undefined 5 + foo({x: 1}) // 1 5 + foo({x: 1, y: 2}) // 1 2 + foo() // TypeError: Cannot read property 'x' of undefined + + + * 褰撳舰鍙傚叿澶囬粯璁ょ殑瑙f瀯璧嬪煎璞℃椂,鐢氳嚦鍙互涓嶇敤浼犻掑弬鏁 + function foo({method,header} = {method:'GET',header:{'ContentType':'application/json'}}){ + console.log(method); + console.log(header); + } + foo() //GET {ContentType: "application/json"} + foo({method:'POST'}) //POST undefined + + # 鍙傛暟榛樿鍊肩殑浣嶇疆 + * 閫氬父鎯呭喌涓,瀹氫箟浜嗛粯璁ゅ肩殑鍙傛暟,搴旇鏄嚱鏁扮殑灏惧弬鏁癿,鍥犱负杩欐牱姣旇緝瀹规槗鐪嬪嚭鏉,鍒板簳鐪佺暐浜嗗摢浜涘弬鏁 + * 濡傛灉闈炲熬閮ㄧ殑鍙傛暟璁剧疆榛樿鍊,瀹為檯涓婅繖涓弬鏁版槸娌℃硶鐪佺暐鐨 + function foo(v1,{name,age} = {name:'name',age:12},v2){ + console.log(v1); + console.log(name); + console.log(age); + console.log(v2); + } + foo(1,3) //1,undefined,undefined,undefined + foo(1,undefined,3) //1,name,12,3 + + * 蹇呴』浣跨敤 undefined 鏉ュ崰浣,浠庤岃Е鍙戦粯璁ゅ + + + # 鍑芥暟鐨 length 灞炴 + * 鎸囧畾浜嗛粯璁ゅ间互鍚,鍑芥暟鐨刲ength灞炴,灏嗚繑鍥炴病鏈夋寚瀹氶粯璁ゅ肩殑鍙傛暟涓暟 + * 涔熷氨鏄,鎸囧畾浜嗛粯璁ゅ煎悗,length灞炴у皢澶辩湡 + + # 浣滅敤鍩 + var x = 1; + function f(x, y = x) { + console.log(y); + } + f(2) // 2 + + * 鍙傛暟y鐨勯粯璁ゅ肩瓑浜庡彉閲弜璋冪敤鍑芥暟f鏃,鍙傛暟褰㈡垚涓涓崟鐙殑浣滅敤鍩,鍦ㄨ繖涓綔鐢ㄥ煙閲岄潰,榛樿鍊煎彉閲弜鎸囧悜绗竴涓弬鏁皒,鑰屼笉鏄叏灞鍙橀噺x,鎵浠ヨ緭鍑烘槸2 + + let x = 1; + function f(y = x) { + let x = 2; + console.log(y); + } + f() // 1 + + * 鍑芥暟f璋冪敤鏃,鍙傛暟y = x褰㈡垚涓涓崟鐙殑浣滅敤鍩,杩欎釜浣滅敤鍩熼噷闈,鍙橀噺x鏈韩娌℃湁瀹氫箟,鎵浠ユ寚鍚戝灞傜殑鍏ㄥ眬鍙橀噺x + * 鍑芥暟璋冪敤鏃,鍑芥暟浣撳唴閮ㄧ殑灞閮ㄥ彉閲弜褰卞搷涓嶅埌榛樿鍊煎彉閲弜 + * 濡傛灉姝ゆ椂,鍏ㄥ眬鍙橀噺x涓嶅瓨鍦,灏变細鎶ラ敊 + + * '鍙傛暟,鏄竴涓崟鐙殑浣滅敤鍩' + + # 搴旂敤 + * 鍒╃敤鍙傛暟榛樿鍊,鍙互鎸囧畾鏌愪竴涓弬鏁颁笉寰楃渷鐣,濡傛灉鐪佺暐灏辨姏鍑轰竴涓敊 + function throwIfMissing() { + throw new Error('Missing parameter'); + } + + function foo(mustBeProvided = throwIfMissing()) { + return mustBeProvided; + } + + foo() + + * foo鍑芥暟锛屽鏋滆皟鐢ㄧ殑鏃跺欐病鏈夊弬鏁,灏变細璋冪敤榛樿鍊紅hrowIfMissing鍑芥暟,浠庤屾姏鍑轰竴涓敊璇 + + * 鍙互灏嗗弬鏁伴粯璁ゅ艰涓簎ndefined,琛ㄦ槑杩欎釜鍙傛暟鏄彲浠ョ渷鐣ョ殑 + function foo(optional = undefined) { 路路路 } + +---------------------------- +rest鍙傛暟 | +---------------------------- + # 鍙橀暱鍙傛暟,鍙兘鍑虹幇鍦ㄥ弬鏁板垪琛ㄧ殑鏈鍚庝竴浣,瀹冧互鏁扮粍鐨勫舰寮忓嚭鐜 + function add(...values) { + let sum = 0; + for (var val of values) { + sum += val; + } + return sum; + } + add(2, 5, 3) // 10 + + * 鏌愮绋嬪害涓婅,瀹冨彲浠ユ浛浠rguments + // arguments鍙橀噺鐨勫啓娉 + function sortNumbers() { + + return Array.prototype.slice.call(arguments).sort(); + } + + // rest鍙傛暟鐨勫啓娉 + const sortNumbers = (...numbers) => numbers.sort(); + + * arguments骞朵笉鏄竴涓暟缁,鑰屾槸涓涓被浼兼暟缁勭殑瀵硅薄,鎵浠ュ厛浣跨敤 Array.prototype.slice() 鎶婂畠杞崲涓烘暟缁,鍐嶈繘琛屾帓搴 + * 鑰 ...numbers 鏄竴涓湡姝g殑鏁版嵁,涓嶉渶瑕佽繘琛岃浆鎹 + + * 鍑芥暟鐨刲ength灞炴,涓嶅寘鎷 rest 鍙傛暟 + +---------------------------- +涓ユ牸妯″紡 | +---------------------------- + # ES5 寮濮,鍑芥暟鍐呴儴鍙互璁惧畾涓轰弗鏍兼ā寮忋 + function doSomething(a, b) { + 'use strict'; + // code + } + + # ES6瑙勫畾鍙鍑芥暟鍙傛暟浣跨敤浜嗛粯璁ゅ,瑙f瀯璧嬪,鎴栬呮墿灞曡繍绠楃,閭d箞鍑芥暟鍐呴儴灏变笉鑳芥樉寮忚瀹氫负涓ユ牸妯″紡鍚﹀垯浼氭姤閿 + + # 杩欐牱瑙勫畾鐨勫師鍥 + * 鍑芥暟鍐呴儴鐨勪弗鏍兼ā寮,鍚屾椂閫傜敤浜庡嚱鏁颁綋鍜屽嚱鏁板弬鏁 + * 鍑芥暟鎵ц鐨勬椂鍊,鍏堟墽琛屽嚱鏁板弬鏁,鐒跺悗鍐嶆墽琛屽嚱鏁颁綋 + * 杩欐牱灏辨湁涓涓笉鍚堢悊鐨勫湴鏂,鍙湁浠庡嚱鏁颁綋涔嬩腑,鎵嶈兘鐭ラ亾鍙傛暟鏄惁搴旇浠ヤ弗鏍兼ā寮忔墽琛,浣嗘槸鍙傛暟鍗村簲璇ュ厛浜庡嚱鏁颁綋鎵ц + + # 涓ょ鏂规硶鍙互瑙勯伩杩欑闄愬埗 + * 绗竴绉嶆槸璁惧畾鍏ㄥ眬鎬х殑涓ユ牸妯″紡,杩欐槸鍚堟硶鐨 + 'use strict'; + function doSomething(a, b = a) { + // code + } + + * 绗簩绉嶆槸鎶婂嚱鏁板寘鍦ㄤ竴涓棤鍙傛暟鐨勭珛鍗虫墽琛屽嚱鏁伴噷闈 + const doSomething = (function () { + 'use strict'; + return function(value = 42) { + return value; + }; + }()); + +---------------------------- +name灞炴 | +---------------------------- + # name灞炴,杩斿洖璇ュ嚱鏁扮殑鍑芥暟鍚, + * 娴忚鍣ㄦ棭灏辨敮鎸佽繖涓睘鎬т簡,浣嗗湪ES6鎵嶈鍐欏叆瑙勮寖 + + # 濡傛灉灏嗕竴涓尶鍚嶅嚱鏁拌祴鍊肩粰涓涓彉閲,ES5 鐨刵ame灞炴,浼氳繑鍥炵┖瀛楃涓.鑰 ES6 鐨刵ame灞炴т細杩斿洖瀹為檯鐨勫嚱鏁板悕 + let foo = function(){} + console.log(foo.name); //foo + + # 灏嗕竴涓叿鍚嶅嚱鏁拌祴鍊肩粰涓涓彉閲,鍒 ES5 鍜 ES6 鐨刵ame灞炴ч兘杩斿洖杩欎釜鍏峰悕鍑芥暟鍘熸湰鐨勫悕瀛 + + const bar = function baz() {}; + + // ES5 + bar.name // "baz" + + // ES6 + bar.name // "baz" + + # Function鏋勯犲嚱鏁拌繑鍥炵殑鍑芥暟瀹炰緥,name灞炴х殑鍊间负anonymous + + (new Function).name // "anonymous" + + # bind杩斿洖鐨勫嚱鏁,name灞炴у间細鍔犱笂bound鍓嶇紑 + + function foo() {}; + + foo.bind({}).name // "bound foo" + (function(){}).bind({}).name // "bound " + +---------------------------- +绠ご鍑芥暟 | +---------------------------- + # ES6鐨勭澶村嚱鏁,鏈夌偣鍍廽ava涓殑 lambda 琛ㄨ揪寮 + let foo = x => x; + let foo = function(x){return x} + console.log(foo(1)) + + # 濡傛灉鏈夊涓弬鏁,浣跨敤()鍖呭惈 + let foo = (x,y) => x + y; + console.log(foo(1,2)) + + # 濡傛灉绠ご鍑芥暟鐨勪唬鐮佹湁鍑犲彞,灏辫浣跨敤澶ф嫭鍙峰皢瀹冧滑鎷捣鏉,骞朵笖鐮佸潡閮ㄥ垎澶氫簬涓鏉¤浣跨敤return璇彞杩斿洖 + let foo = (x,y) => { + let r = x + y; + return r; + }; + console.log(foo(1,2)) + + # 濡傛灉绠ご鍑芥暟鐩存帴杩斿洖鐨勬暟鎹槸涓璞,閭d箞蹇呴』浣跨敤()杩涜鍖呰9 + let foo = () => ({name:'Kevin'}); + console.log(foo()) + + # 濡傛灉绠ご鍑芥暟鍙湁涓琛岃鍙,涓斾笉闇瑕佽繑鍥炲,鍙互鐪佺暐澶ф嫭鍙 + + let fn = () => void doesNotReturn(); + let foo = () => void console.log('娌℃湁杩斿洖鍊,浠呬粎鐢ㄤ簬鎵ц涓琛屼唬鐮'); + + # 绠ご鍑芥暟涓庤В鏋勮祴鍊 + const full = ({ first, last }) => first + ' ' + last; + + // 绛夊悓浜 + function full(person) { + return person.first + ' ' + person.last; + } + + # rest 鍙傛暟涓庣澶村嚱鏁扮粨鍚 + + const numbers = (...nums) => nums; + + numbers(1, 2, 3, 4, 5) + // [1,2,3,4,5] + + const headAndTail = (head, ...tail) => [head, tail]; + + headAndTail(1, 2, 3, 4, 5) + // [1,[2,3,4,5]] + + # 绠ご鍑芥暟闇瑕佹敞鎰忕殑闂 + 1,鍑芥暟浣撳唴鐨則his瀵硅薄,灏辨槸瀹氫箟鏃舵墍鍦ㄧ殑瀵硅薄,鑰屼笉鏄娇鐢ㄦ椂鎵鍦ㄧ殑瀵硅薄('绠ご鍑芥暟閲岄潰鏍规湰娌℃湁鑷繁鐨則his,鑰屾槸寮曠敤澶栧眰鐨則his') + * 鐢变簬绠ご鍑芥暟娌℃湁鑷繁鐨則his,鎵浠ュ綋鐒朵篃灏变笉鑳界敤call(),apply(),bind()杩欎簺鏂规硶鍘绘敼鍙榯his鐨勬寚鍚 + + + 2,涓嶅彲浠ュ綋浣滄瀯閫犲嚱鏁,涔熷氨鏄,涓嶅彲浠ヤ娇鐢╪ew鍛戒护,鍚﹀垯浼氭姏鍑轰竴涓敊璇 + 3,涓嶅彲浠ヤ娇鐢╝rguments瀵硅薄,璇ュ璞″湪鍑芥暟浣撳唴涓嶅瓨鍦,濡傛灉瑕佺敤,鍙互鐢 rest 鍙傛暟浠f浛 + 4,涓嶅彲浠ヤ娇鐢▂ield鍛戒护,鍥犳绠ご鍑芥暟涓嶈兘鐢ㄤ綔 Generator 鍑芥暟 + + + * 绗竴鐐瑰挨鍏跺煎緱娉ㄦ剰,this 瀵硅薄鐨勬寚鍚戞槸鍙彉鐨,浣嗘槸鍦ㄧ澶村嚱鏁颁腑瀹冩槸鍥哄畾鐨 + var id = 21; + + function foo() { + window.setTimeout(() => { + console.log('id:', this.id); + }, 100); + } + + * setTimeout鐨勫弬鏁版槸涓涓澶村嚱鏁,杩欎釜绠ご鍑芥暟鐨勫畾涔夌敓鏁堟槸鍦╢oo鍑芥暟鐢熸垚鏃 + * 鑰屽畠鐨勭湡姝f墽琛岃绛夊埌 100 姣鍚,濡傛灉鏄櫘閫氬嚱鏁,鎵ц鏃秚his搴旇鎸囧悜鍏ㄥ眬瀵硅薄window,杩欐椂搴旇杈撳嚭21 + * 浣嗘槸,绠ご鍑芥暟瀵艰嚧this鎬绘槸鎸囧悜鍑芥暟瀹氫箟鐢熸晥鏃舵墍鍦ㄧ殑瀵硅薄(鏈緥鏄瘂id: 42}),鎵浠ヨ緭鍑虹殑鏄42 + + * 绠ご鍑芥暟鍙互璁﹕etTimeout閲岄潰鐨則his,缁戝畾瀹氫箟鏃舵墍鍦ㄧ殑浣滅敤鍩,鑰屼笉鏄寚鍚戣繍琛屾椂鎵鍦ㄧ殑浣滅敤鍩 + function Timer() { + this.s1 = 0; + this.s2 = 0; + // 绠ご鍑芥暟,this鎸囧悜瀹氫箟鏃舵墍鍦ㄧ殑浣滅敤,涔熷氨鏄 Timer + setInterval(() => this.s1++, 1000); + // 鏅氬嚱鏁 + setInterval(function () { + //鎸囧悜杩愯鏃剁殑浣滅敤鍩,setInterval 鏄痺indows鐨勫睘鎬,瀹冩槸鐢眞indow璋冪敤,鎵浠,杩欎釜this鎸囧悜windows + this.s2++; + }, 1000); + } + + var timer = new Timer(); + + setTimeout(() => console.log('s1: ', timer.s1), 3100); + setTimeout(() => console.log('s2: ', timer.s2), 3100); + + // s1: 3 + // s2: 0 + // id: 42 + + * Timer鍑芥暟鍐呴儴璁剧疆浜嗕袱涓畾鏃跺櫒,鍒嗗埆浣跨敤浜嗙澶村嚱鏁板拰鏅氬嚱鏁 + * 鍓嶈呯殑this缁戝畾瀹氫箟鏃舵墍鍦ㄧ殑浣滅敤鍩(鍗砊imer鍑芥暟),鍚庤呯殑this鎸囧悜杩愯鏃舵墍鍦ㄧ殑浣滅敤鍩(鍗冲叏灞瀵硅薄) + * 鎵浠,3100 姣涔嬪悗,timer.s1琚洿鏂颁簡 3 娆★紝鑰宼imer.s2涓娆¢兘娌℃洿鏂 + + + * 绠ご鍑芥暟鍙互璁 this 鎸囧悜鍥哄畾鍖,杩欑鐗规у緢鏈夊埄浜庡皝瑁呭洖璋冨嚱鏁 + var handler = { + id: '123456', + init: function() { + //濡傛灉涓嶄娇鐢ㄧ澶村嚱鏁,閭d箞绠ご鍑芥暟閲岄潰鐨則his,鍦ㄤ簨浠朵腑鎸囧悜鍙戠敓浜嬩欢鐨刣ocument瀵硅薄 + document.addEventListener('click',event => this.doSomething(event.type), false); + }, + + doSomething: function(type) { + console.log('Handling ' + type + ' for ' + this.id); + } + }; + + * init鏂规硶涓,浣跨敤浜嗙澶村嚱鏁,杩欏鑷磋繖涓澶村嚱鏁伴噷闈㈢殑this,鎬绘槸鎸囧悜handler瀵硅薄,鍚﹀垯,鍥炶皟鍑芥暟杩愯鏃,this.doSomething 杩欎竴琛屼細鎶ラ敊,鍥犱负姝ゆ椂this鎸囧悜document瀵硅薄 + * this鎸囧悜鐨勫浐瀹氬寲,涓嶆槸鍥犱负绠ご鍑芥暟鍐呴儴鏈夌粦瀹歵his鐨勬満鍒,瀹為檯鍘熷洜鏄澶村嚱鏁版牴鏈病鏈夎嚜宸辩殑this,瀵艰嚧鍐呴儴鐨則his灏辨槸澶栧眰浠g爜鍧楃殑this,姝f槸鍥犱负瀹冩病鏈塼his,鎵浠ヤ篃灏变笉鑳界敤浣滄瀯閫犲嚱鏁 + + * 绠ご鍑芥暟杞崲涓篍S5浠g爜 + // ES6 + function foo() { + setTimeout(() => { + console.log('id:', this.id); + }, 100); + } + + // ES5 + function foo() { + var _this = this; + + setTimeout(function () { + console.log('id:', _this.id); //绠ご鍑芥暟鐨則his,灏辨槸涓婄骇浣滅敤鍩熺殑this + }, 100); + } + + # 宓屽鐨勭澶村嚱鏁 + * 绠ご鍑芥暟鍐呴儴,杩樺彲浠ュ啀浣跨敤绠ご鍑芥暟 + +-------------------- +鍙屽啋鍙疯繍绠楃 | +--------------------- + # '鍑芥暟缁戝畾'(function bind)杩愮畻绗,鐢ㄦ潵鍙栦唬call,apply,bind璋冪敤 + * 鍑芥暟缁戝畾杩愮畻绗︽槸骞舵帓鐨勪袱涓啋鍙(::) + * 鍙屽啋鍙峰乏杈规槸涓涓璞,鍙宠竟鏄竴涓嚱鏁 + * 璇ヨ繍绠楃浼氳嚜鍔ㄥ皢宸﹁竟鐨勫璞,浣滀负涓婁笅鏂囩幆澧(鍗硉his瀵硅薄),缁戝畾鍒板彸杈圭殑鍑芥暟涓婇潰 + + + # 濡傛灉鍙屽啋鍙峰乏杈逛负绌,鍙宠竟鏄竴涓璞$殑鏂规硶,鍒欑瓑浜庡皢璇ユ柟娉曠粦瀹氬湪璇ュ璞′笂闈 + //鎶 鍙宠竟obj瀵硅薄鐨刦oo鏂规硶缁戝畾鍦ㄥ乏杈筼bj瀵硅薄涓 + var method = obj::obj.foo; + // 绛夊悓浜 + var method = ::obj.foo; + + //鎶婂彸杈,log鏂规硶缁戝畾鐨勫湪console鏂规硶涓 + let log = ::console.log; + + // 绛夊悓浜 + var log = console.log.bind(console); + + # 鍙屽啋鍙疯繍绠,娴忚鍣ㄦ姤閿,搴旇鏄鎻愭杩樻湭瀹炵幇 + +-------------------- +灏捐皟鐢ㄤ紭鍖 | +--------------------- + # 灏捐皟鐢(Tail Call)鏄嚱鏁板紡缂栫▼鐨勪竴涓噸瑕佹蹇,鏈韩闈炲父绠鍗,涓鍙ヨ瘽灏辫兘璇存竻妤,灏辨槸鎸囨煇涓嚱鏁扮殑鏈鍚庝竴姝ユ槸璋冪敤鍙︿竴涓嚱鏁 + function f(x){ + return g(x); + } + + * '鍑芥暟鎵цreturn鐨勬椂鍊,璋冪敤浜嗗叾浠栧嚱鏁,浠庤岃繑鍥炵殑鏄叾浠栧嚱鏁扮殑杩斿洖鍊' + * '灏捐皟鐢ㄤ笉涓瀹氬嚭鐜板湪鍑芥暟灏鹃儴锛屽彧瑕佹槸鏈鍚庝竴姝ユ搷浣滃嵆鍙' + function f(x) { + if (x > 0) { + return m(x) + } + return n(x); + } + + # 浠ヤ笅涓夌鎯呭喌,閮戒笉灞炰簬灏捐皟鐢 + // 鎯呭喌涓 + function f(x){ + let y = g(x); + return y; + } + + // 鎯呭喌浜 + function f(x){ + return g(x) + 1; + } + + // 鎯呭喌涓 + function f(x){ + g(x); + } + + # 閫氫織鐞嗚В + * 杩欎釜灞炰簬鎴戜滑涓嶉渶瑕佸叧蹇冪殑搴曞眰浼樺寲,鑰屼笖鎴戜滑涔熸病鍔炴硶鍘诲共棰 + * 鍙湁涓嶅啀鐢ㄥ埌澶栧眰鍑芥暟鐨勫唴閮ㄥ彉閲,鍐呭眰鍑芥暟鐨勮皟鐢ㄥ抚鎵嶄細鍙栦唬澶栧眰鍑芥暟鐨勮皟鐢ㄥ抚,鍚﹀垯灏辨棤娉曡繘琛"灏捐皟鐢ㄤ紭鍖" + function addOne(a){ + var one = 1; + function inner(b){ + return b + one; //鐢ㄥ埌浜嗗閮ㄥ嚱鏁扮殑鍙橀噺,涓嶄細鍙戠敓浼樺寲 + } + return inner(a); + } + * 'ES6 鐨勫熬璋冪敤浼樺寲鍙湪涓ユ牸妯″紡涓嬪紑鍚,姝e父妯″紡鏄棤鏁堢殑' + * 鍥犱负鍦ㄦ甯告ā寮忎笅,鍑芥暟鍐呴儴鏈変袱涓彉閲,鍙互璺熻釜鍑芥暟鐨勮皟鐢ㄦ爤 + func.arguments 杩斿洖璋冪敤鏃跺嚱鏁扮殑鍙傛暟 + func.caller 杩斿洖璋冪敤褰撳墠鍑芥暟鐨勯偅涓嚱鏁 + * 灏捐皟鐢ㄤ紭鍖栧彂鐢熸椂,鍑芥暟鐨勮皟鐢ㄦ爤浼氭敼鍐,鍥犳涓婇潰涓や釜鍙橀噺灏变細澶辩湡,涓ユ牸妯″紡绂佺敤杩欎袱涓彉閲,鎵浠ュ熬璋冪敤妯″紡浠呭湪涓ユ牸妯″紡涓嬬敓鏁 + function restricted() { + 'use strict'; + restricted.caller; // 鎶ラ敊 + restricted.arguments; // 鎶ラ敊 + } + restricted(); + + * 璇存槑 + function f(){ + let x = 4; + return k(); + } + + ES5 + * 褰撴墽琛 k() 鐨勬椂鍊,杩樹細鍦ㄧ郴缁熶腑淇濆瓨鐫f鍑芥暟鐨 x 鍙橀噺 + + ES6 + * 褰撴墽琛 k() 鍒嗘椂鍊,灏卞彲浠ユ妸f鍑芥暟涓殑鎵鏈夊彉閲忛兘鍒犻櫎浜,鍥犱负鐢ㄤ笉鐫鏉 + +-------------------- +灏鹃掑綊鐨勪紭鍖 | +--------------------- + # 鍑芥暟璋冪敤鑷韩,绉颁负閫掑綊,濡傛灉灏捐皟鐢ㄨ嚜韬,灏辩О涓哄熬閫掑綊 + # 閫掑綊闈炲父鑰楄垂鍐呭瓨,鍥犱负闇瑕佸悓鏃朵繚瀛樻垚鍗冧笂鐧句釜璋冪敤甯,寰堝鏄撳彂鐢"鏍堟孩鍑"閿欒(stack overflow)浣嗗浜庡熬閫掑綊鏉ヨ,鐢变簬鍙瓨鍦ㄤ竴涓皟鐢ㄥ抚,鎵浠ユ案杩滀笉浼氬彂鐢"鏍堟孩鍑"閿欒 + # 閫氫織鐞嗚В + * 灏鹃掑綊,涓瀹氭槸鍦ㄥ熬璋冪敤鐨勬儏鍐典笅 + * Demo + function factorial(n, total) { + if (n === 1) return total; + return factorial(n - 1, n * total); + } + ES5 + * 姣忔鎵ц灏鹃儴鐨 factorial ,閮戒細淇濈暀涓婄骇鍑芥暟涓殑鍙橀噺 + ES6 + * 姣忔鎵ц灏鹃儴鐨 factorial ,閮戒笉浼氫繚鐣欎笂绾у嚱鏁颁腑鐨勫彉閲 + + * 杩欎釜閫掑綊璋冪敤,灏辨湁鐐瑰儚鏄"寰幆璋冪敤"浜,涓嶄細鍙戠敓鏍堟孩鍑 + * ES6 涓彧瑕佷娇鐢ㄥ熬閫掑綊,灏变笉浼氬彂鐢熸爤婧㈠嚭,鐩稿鑺傜渷鍐呭瓨 + + # 灏鹃掑綊浼樺寲鐨勫疄鐜 + * 灏鹃掑綊浼樺寲鍙湪涓ユ牸妯″紡涓嬬敓鏁,鍦ㄦ甯告ā寮忔垨鑰呴偅浜涗笉鏀寔璇ュ姛鑳界殑鐜涓,涔熸湁鍔炴硶浣跨敤灏鹃掑綊浼 + * 绠椾簡...鎴戝喅瀹氭垜杩欒緢瀛愰兘涓嶄細鍐欒繖绉嶄唬鐮佺殑(闇瑕佹椂,鍙傝) + http://es6.ruanyifeng.com/#docs/function#%E5%B0%BE%E9%80%92%E5%BD%92%E4%BC%98%E5%8C%96%E7%9A%84%E5%AE%9E%E7%8E%B0 + +--------------------- +鍑芥暟鍙傛暟鐨勫熬閫楀彿 | +--------------------- + # ES2017 鍏佽鍑芥暟鐨勬渶鍚庝竴涓弬鏁版湁灏鹃楀彿(trailing comma) + # 姝ゅ墠,鍑芥暟瀹氫箟鍜岃皟鐢ㄦ椂,閮戒笉鍏佽鏈鍚庝竴涓弬鏁板悗闈㈠嚭鐜伴楀彿 + # 涓鍙ヨ瘽 + * 鍦ㄥ畾涔夋垨鑰呰皟鐢ㄥ嚱鏁扮殑鏃跺,鏈鍚庝竴涓弬鏁板悗闈㈡坊鍔犱竴涓楀彿',',涓嶄細鎶涘嚭寮傚父 + function clownsEverywhere(param1,param2,) { } + clownsEverywhere('foo','bar',); \ No newline at end of file diff --git "a/JavaSript/ES6/es6-\346\211\251\345\261\225-\345\255\227\347\254\246\344\270\262.js" "b/JavaSript/ES6/es6-\346\211\251\345\261\225-\345\255\227\347\254\246\344\270\262.js" deleted file mode 100644 index a28ab0e4..00000000 --- "a/JavaSript/ES6/es6-\346\211\251\345\261\225-\345\255\227\347\254\246\344\270\262.js" +++ /dev/null @@ -1,125 +0,0 @@ ---------------------- -瀛楃涓叉墿灞 | ---------------------- - 1,瀛楃鐨 Unicode 琛ㄧず娉 - 2,codePointAt() - 3,String.fromCodePoint() - 4,瀛楃涓茬殑閬嶅巻鍣ㄦ帴鍙 - 5,at() - 6,normalize() - 7,includes(), startsWith(), endsWith() - 8,repeat() - 9,padStart()锛宲adEnd() - 10,妯℃澘瀛楃涓 - 11,瀹炰緥锛氭ā鏉跨紪璇 - 12,鏍囩妯℃澘 - 13,String.raw() - 14,妯℃澘瀛楃涓茬殑闄愬埗 - ---------------------- -Unicode | ---------------------- - # JavaScript 鍏佽閲囩敤\uxxxx褰㈠紡琛ㄧず涓涓瓧绗︼紝鍏朵腑xxxx琛ㄧず瀛楃鐨 Unicode 鐮佺偣銆 - let x = "\u0061"; - - # 杩欑琛ㄧず娉曞彧闄愪簬鐮佺偣鍦╘u0000~\uFFFF涔嬮棿鐨勫瓧绗︺傝秴鍑鸿繖涓寖鍥寸殑瀛楃锛 - # 瓒呭嚭杩欎釜鑼冨洿鐨勫瓧绗︼紝蹇呴』鐢ㄤ袱涓弻瀛楄妭鐨勫舰寮忚〃绀恒 - let x = "\uD842\uDFB7" // "馉" - let y = "\u20BB7" // " 7" - * 濡傛灉鐩存帴鍦╘u鍚庨潰璺熶笂瓒呰繃0xFFFF鐨勬暟鍊硷紙姣斿\u20BB7锛夛紝JavaScript浼氱悊瑙f垚\u20BB+7銆 - * 鐢变簬\u20BB鏄竴涓笉鍙墦鍗板瓧绗︼紝鎵浠ュ彧浼氭樉绀轰竴涓┖鏍硷紝鍚庨潰璺熺潃涓涓7銆 - - # 'ES6 瀵硅繖涓鐐瑰仛鍑轰簡鏀硅繘锛屽彧瑕佸皢鐮佺偣鏀惧叆澶ф嫭鍙凤紝灏辫兘姝g‘瑙h璇ュ瓧绗︺' - \u{UNICODE鐮亇 - "\u{20BB7}" // "馉" - "\u{41}\u{42}\u{43}" // "ABC" - let hello = 123; hell\u{6F} // 123 - '\u{1F680}' === '\uD83D\uDE80' // true - - # 鏈変簡杩欑琛ㄧず娉曚箣鍚庯紝JavaScript 鍏辨湁6绉嶆柟娉曞彲浠ヨ〃绀轰竴涓瓧绗︺ - '\z' === 'z' // true - '\172' === 'z' // true - '\x7A' === 'z' // true - '\u007A' === 'z' // true - '\u{7A}' === 'z' // true - - # 鐩稿叧AIP - codePointAt(); - String.fromCodePoint(); - String.fromCharCode(); - ---------------------- -瀛楃涓查亶鍘嗘帴鍙 | ---------------------- - # ES6涓哄瓧绗︿覆瀹炵幇浜嗛亶鍘嗘帴鍙,鍙互琚 for in 閬嶅巻 - for (let codePoint of 'foo') { - console.log(codePoint) - } - # 杩欎釜閬嶅巻鍣ㄥ湪瀛楃涓蹭腑鏈澶х殑浼樼偣鏄彲浠ヨ瘑鍒ぇ浜0xFFFF鐨勭爜鐐癸紝浼犵粺鐨刦or寰幆鏃犳硶璇嗗埆杩欐牱鐨勭爜鐐广 - ---------------------- -at | ---------------------- - # 杩斿洖鎸囧畾浣嶇疆鐨勫瓧绗 - # ES5瀵瑰瓧绗︿覆瀵硅薄鎻愪緵 charAt鏂规硶锛岃繑鍥炲瓧绗︿覆缁欏畾浣嶇疆鐨勫瓧绗︺傝鏂规硶涓嶈兘璇嗗埆鐮佺偣澶т簬0xFFFF鐨勫瓧绗︺ - lex x = "1234".at(2); //3 - ---------------------- -normalize | ---------------------- - # 璺熼煶绗︾浉鍏崇殑API - ---------------------- -鍏朵粬鏂扮殑API | ---------------------- - - includes(); - * 杩斿洖 boolean,鏄惁鍖呭惈鎸囧畾鐨勫瓧绗︿覆 - - startsWith(); - * 鏄惁浠ユ寚瀹氱殑瀛楃涓插紑澶 - - endsWith(); - * 鏄惁浠ユ寚瀹氱殑瀛楃涓茬粨灏 - - # 浠ヤ笂涓嬩花鏂规硶閮芥敮鎸佺浜屼釜鍙傛暟,琛ㄧず鎸囧畾涓嬫爣,浠庡摢涓綅缃捣寮濮嬪垽鏂 - * 榛樿灏辨槸浠0寮濮 - * endsWith鐨勮涓轰笌鍏朵粬涓や釜鏂规硶鏈夋墍涓嶅悓銆傚畠閽堝鍓峮涓瓧绗︼紝鑰屽叾浠栦袱涓柟娉曢拡瀵逛粠绗琻涓綅缃洿鍒板瓧绗︿覆缁撴潫銆 - - repeat(); - * 琛ㄧず鎶婂瓧绗︿覆閲嶅鍑犳鍚庤繑鍥 - * repeat鐨勫弬鏁版槸璐熸暟鎴栬匢nfinity锛屼細鎶ラ敊銆 - * 濡傛灉鍙傛暟鏄0鍒-1涔嬮棿鐨勫皬鏁帮紝鍒欑瓑鍚屼簬0锛岃繖鏄洜涓轰細鍏堣繘琛屽彇鏁磋繍绠椼0鍒-1涔嬮棿鐨勫皬鏁帮紝鍙栨暣浠ュ悗绛変簬-0锛宺epeat瑙嗗悓涓0銆 - * 鍙傛暟NaN绛夊悓浜0銆傚瓧绗︿覆涔熶細琚浆鎹负鏁板瓧 - - padStart(); - * 澶磋ˉ鍏 - * 淇╁弬鏁,绗竴涓槸鎸囧畾闀垮害,绗簩涓弬鏁板氨鏄敤浜庤ˉ鍏ㄧ殑瀛楃涓 - * 濡傛灉鍘熷瓧绗︿覆鐨勯暱搴︼紝绛変簬鎴栧ぇ浜庢寚瀹氱殑鏈灏忛暱搴︼紝鍒欒繑鍥炲師瀛楃涓层 - * 濡傛灉鐢ㄦ潵琛ュ叏鐨勫瓧绗︿覆涓庡師瀛楃涓诧紝涓よ呯殑闀垮害涔嬪拰瓒呰繃浜嗘寚瀹氱殑鏈灏忛暱搴︼紝鍒欎細鎴幓瓒呭嚭浣嶆暟鐨勮ˉ鍏ㄥ瓧绗︿覆銆 - 'abc'.padStart(10, '0123456789'); // '0123456abc' - * 濡傛灉鐪佺暐绗簩涓弬鏁帮紝榛樿浣跨敤绌烘牸琛ュ叏闀垮害銆 - 'x'.padStart(4) // ' x' - * 甯歌鐢ㄩ旀槸涓烘暟鍊艰ˉ鍏ㄦ寚瀹氫綅鏁 - var day = new Date().getDatofMonth(); - - padEnd(); - * 灏捐ˉ鍏 - * 淇╁弬鏁,绗竴涓槸鎸囧畾闀垮害,绗簩涓弬鏁板氨鏄敤浜庤ˉ鍏ㄧ殑瀛楃涓 - * 鍚屼笂 - * 濡傛灉鐪佺暐绗簩涓弬鏁帮紝榛樿浣跨敤绌烘牸琛ュ叏闀垮害銆 - 'x'.padEnd(4) // 'x - ---------------------- -杞箟瀛楃涓 | ---------------------- - * 浼犵粺鍐欐硶 - var x = "\"Hello\"" - - * 鏂扮増鍐欐硶 - var x = `"Hello"` - ---------------------- -妯$増瀛楃涓 | ---------------------- - \ No newline at end of file diff --git "a/JavaSript/ES6/es6-\346\211\251\345\261\225-\345\257\271\350\261\241.js" "b/JavaSript/ES6/es6-\346\211\251\345\261\225-\345\257\271\350\261\241.js" new file mode 100644 index 00000000..e6dc9c34 --- /dev/null +++ "b/JavaSript/ES6/es6-\346\211\251\345\261\225-\345\257\271\350\261\241.js" @@ -0,0 +1,797 @@ +---------------------------- +ES6-瀵硅薄 | +---------------------------- + 1,灞炴х殑绠娲佽〃绀烘硶 + 2,灞炴у悕琛ㄨ揪寮 + 3,鏂规硶鐨 name 灞炴 + 4,Object.is() + 5,Object.assign() + 6,灞炴х殑鍙灇涓炬у拰閬嶅巻 + 7,Object.getOwnPropertyDescriptors() + 8,__proto__灞炴,Object.setPrototypeOf(),Object.getPrototypeOf() + 9,super 鍏抽敭瀛 + 10,Object.keys(),Object.values()锛孫bject.entries() + 11,瀵硅薄鐨勬墿灞曡繍绠楃 + +---------------------------- +灞炴х殑绠娲佽〃绀烘硶 | +---------------------------- + # S6 鍏佽鐩存帴鍐欏叆鍙橀噺鍜屽嚱鏁,浣滀负瀵硅薄鐨勫睘鎬у拰鏂规硶 + const foo = 'bar'; + const baz = {foo}; + console.log(baz); // {foo: "bar"} + + // 绛夊悓浜 + const baz = {foo: foo}; + + # ES6 鍏佽鍦ㄥ璞′箣涓,鐩存帴鍐欏彉閲 + * 杩欐椂,灞炴у悕涓哄彉閲忓悕, 灞炴у间负鍙橀噺鐨勫 + function f(x, y) { + return {x, y}; + } + // 绛夊悓浜 + function f(x, y) { + return {x: x, y: y}; + } + f(1, 2) // Object {x: 1, y: 2} + + # 闄や簡灞炴,鏂规硶涔熷彲浠ョ畝鍐 + const o = { + method() { + return "Hello!"; + } + }; + // 绛夊悓浜 + const o = { + method: function() { + return "Hello!"; + } + }; + * demo + let birth = '2000/01/01'; + const Person = { + name: 'KevinBlandy', + //绛夊悓浜巄irth: birth + birth, + // 绛夊悓浜巋ello: function ()... + hello() { console.log('鎴戠殑鍚嶅瓧鏄', this.name); } + }; + + + //鐢ㄤ簬鍑芥暟鐨勮繑鍥炲 + function foo(){ + let name = "Kevin"; + let age = 23; + return {name,age}; + } + console.log(foo()); //{name: "Kevin", age: 23} + + # CommonJS 妯″潡杈撳嚭涓缁勫彉閲,灏遍潪甯稿悎閫備娇鐢ㄧ畝娲佸啓娉 + let ms = {}; + function getItem (key) { + return key in ms ? ms[key] : null; + }; + function setItem (key, value) { + ms[key] = value; + }; + function clear () { + ms = {}; + }; + + module.exports = { getItem, setItem, clear }; + // 绛夊悓浜 + module.exports = { + getItem: getItem, + setItem: setItem, + clear: clear + }; + + # 灞炴х殑璧嬪煎櫒(setter)鍜屽彇鍊煎櫒(getter),浜嬪疄涓婁篃鏄噰鐢ㄨ繖绉嶅啓娉 + const cart = { + _wheels: 4, + + get wheels () { + return this._wheels; + }, + + set wheels (value) { + if (value < this._wheels) { + throw new Error('鏁板煎お灏忎簡锛'); + } + this._wheels = value; + } + } + + # 绠娲佸啓娉曠殑灞炴у悕鎬绘槸瀛楃涓,杩欎細瀵艰嚧涓浜涚湅涓婂幓姣旇緝濂囨殑缁撴灉 + const obj = { + //鍦ㄨ繖閲宑lass鏄瓧绗︿覆,鎵浠ヤ笉浼氬洜涓哄畠灞炰簬鍏抽敭瀛,鑰屽鑷磋娉曡В鏋愭姤閿 + class () {} + }; + // 绛夊悓浜 + var obj = { + 'class': function() {} + }; + + # 濡傛灉鏌愪釜鏂规硶鐨勫兼槸涓涓 Generator 鍑芥暟,鍓嶉潰闇瑕佸姞涓婃槦鍙 + const obj = { + * m() { + yield 'hello world'; + } + }; + +---------------------------- +灞炴у悕琛ㄨ揪寮 | +---------------------------- + # JavaScript 涓ょ瀹氫箟瀵硅薄灞炴х殑鏂规硶 + // 鏂规硶涓 鐩存帴鐢ㄦ爣璇嗙浣滀负灞炴у悕 + obj.foo = true; + + // 鏂规硶浜 鐢ㄨ〃杈惧紡浣滀负灞炴у悕,杩欐椂瑕佸皢琛ㄨ揪寮忔斁鍦ㄦ柟鎷彿涔嬪唴 + obj['a' + 'bc'] = 123; + + # S5浣跨敤瀛楅潰閲忔柟寮忓畾涔夊璞(浣跨敤澶ф嫭鍙)鍦 ES5 涓彧鑳戒娇鐢ㄦ柟娉曚竴(鏍囪瘑绗)瀹氫箟灞炴 + var obj = { + foo: true, + abc: 123 + }; + + # ES6 鍏佽瀛楅潰閲忓畾涔夊璞℃椂,鐢ㄦ柟娉曚簩(琛ㄨ揪寮)浣滀负瀵硅薄鐨勫睘鎬у悕,鍗虫妸琛ㄨ揪寮忔斁鍦ㄦ柟鎷彿鍐 + let propKey = 'foo'; + let obj = { + [propKey]: true, //foo:true + ['a' + 'bc']: 123 //abc:123 + }; + console.log(obj); //{foo: true, abc: 123} + ---------------------------------------------------- + let lastWord = 'last word'; + const a = { + 'first word': 'hello', + [lastWord]: 'world' + }; + a['first word'] // "hello" + a[lastWord] // "world" + a['last word'] // "world" + console.log(a); //{first word: "hello", last word: "world"} + + # 琛ㄨ揪寮忚繕鍙互鐢ㄤ簬瀹氫箟鏂规硶鍚 + let test = 'test'; + let obj = { + ['h' + 'ello']() { //閫氳繃 []鏉ヨ绠,鍑芥暟鍚 + console.log('hi'); + }, + [test](){ + console.log("test"); + } + }; + obj.hello() // hi + obj.test(); //test + + # 灞炴у悕琛ㄨ揪寮忎笌绠娲佽〃绀烘硶,涓嶈兘鍚屾椂浣跨敤,浼氭姤閿 + // 鎶ラ敊 + const foo = 'bar'; + const bar = 'abc'; + const baz = { [foo] }; + + // 姝g‘ + const foo = 'bar'; + const baz = { [foo]: 'abc'}; + + # 灞炴у悕琛ㄨ揪寮忓鏋滄槸涓涓璞,榛樿鎯呭喌涓嬩細鑷姩灏嗗璞¤浆涓哄瓧绗︿覆'[object Object]' + const keyA = {a: 1}; + const keyB = {b: 2}; + + const myObject = { + [keyA]: 'valueA', + [keyB]: 'valueB' + }; + + console.log(myObject); // Object {[object Object]: "valueB"} + +----------------------------------- +鏂规硶鐨刵ame灞炴 | +----------------------------------- + # 鍑芥暟鐨刵ame灞炴,杩斿洖鍑芥暟鍚,瀵硅薄鏂规硶涔熸槸鍑芥暟,鍥犳涔熸湁name灞炴 + const person = { + sayName() { + console.log('hello!'); + }, + }; + person.sayName.name; // "sayName" + + # 濡傛灉瀵硅薄鐨勬柟娉曚娇鐢ㄤ簡鍙栧煎嚱鏁(getter)鍜屽瓨鍊煎嚱鏁(setter) + * name灞炴т笉鏄湪璇ユ柟娉曚笂闈,鑰屾槸'璇ユ柟娉曠殑灞炴х殑鎻忚堪瀵硅薄'鐨刧et鍜宻et灞炴т笂闈,杩斿洖鍊兼槸鏂规硶鍚嶅墠鍔犱笂get鍜宻et + const obj = { + get foo() {}, + set foo(x) {} + }; + + obj.foo.name + TypeError: Cannot read property 'name' of undefined + + const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo'); + console.log(descriptor.get.name); // "get foo" + console.log(descriptor.set.name); // "set foo" + + # 鏈変袱绉嶇壒娈婃儏鍐 + * bind鏂规硶鍒涢犵殑鍑芥暟,name灞炴ц繑鍥瀊ound鍔犱笂鍘熷嚱鏁扮殑鍚嶅瓧 + var doSomething = function() { + // ... + }; + doSomething.bind().name // "bound doSomething" + + * Function鏋勯犲嚱鏁板垱閫犵殑鍑芥暟,name灞炴ц繑鍥瀉nonymous + (new Function()).name // "anonymous" + + # 濡傛灉瀵硅薄鐨勬柟娉曟槸涓涓 Symbol 鍊,閭d箞name灞炴ц繑鍥炵殑鏄繖涓 Symbol 鍊肩殑鎻忚堪 + const key1 = Symbol('description'); + const key2 = Symbol(); + let obj = { + [key1]() {}, + [key2]() {}, + }; + console.log(obj[key1].name); // "[description]" + console.log(obj[key2].name); // "" + //key1瀵瑰簲鐨 Symbol 鍊兼湁鎻忚堪,key2娌℃湁 + +------------------------------------ +Object.is() | +------------------------------------ + # ES5 姣旇緝涓や釜鍊兼槸鍚︾浉绛,鍙湁涓や釜杩愮畻绗 + * 鐩哥瓑杩愮畻绗(==)鍜屼弗鏍肩浉绛夎繍绠楃(===) + * 瀹冧滑閮芥湁缂虹偣,鍓嶈呬細鑷姩杞崲鏁版嵁绫诲瀷,鍚庤呯殑NaN涓嶇瓑浜庤嚜韬,浠ュ強+0绛変簬-0 + + # javaScript 缂轰箯涓绉嶈繍绠,鍦ㄦ墍鏈夌幆澧冧腑,鍙涓や釜鍊兼槸涓鏍风殑,瀹冧滑灏卞簲璇ョ浉绛 + * ES6 鎻愬嚭"Same-value equality"(鍚屽肩浉绛)绠楁硶,鐢ㄦ潵瑙e喅杩欎釜闂 + * Object.is灏辨槸閮ㄧ讲杩欎釜绠楁硶鐨勬柊鏂规硶,瀹冪敤鏉ユ瘮杈冧袱涓兼槸鍚︿弗鏍肩浉绛,'涓庝弗鏍兼瘮杈冭繍绠楃(===)鐨勮涓哄熀鏈竴鑷' + + Object.is('foo', 'foo') + // true + Object.is({}, {}) + // false + + * 涓嶅悓涔嬪鍙湁涓や釜:涓鏄+0涓嶇瓑浜-0,浜屾槸NaN绛変簬鑷韩 + +0 === -0 //true + NaN === NaN // false + + Object.is(+0, -0) // false + Object.is(NaN, NaN) // true + + # ES5 鍙互閫氳繃涓嬮潰鐨勪唬鐮,閮ㄧ讲Object.is + Object.defineProperty(Object, 'is', { + value: function(x, y) { + if (x === y) { + // 閽堝+0 涓嶇瓑浜 -0鐨勬儏鍐 + return x !== 0 || 1 / x === 1 / y; + } + // 閽堝NaN鐨勬儏鍐 + return x !== x && y !== y; + }, + configurable: true, + enumerable: false, + writable: true + }); + +------------------------------------ +Object.assign | +------------------------------------ + # Object.assign 鏂规硶鐢ㄤ簬瀵硅薄鐨勫悎骞,灏嗘簮瀵硅薄(source)鐨勬墍鏈夊彲鏋氫妇灞炴,澶嶅埗鍒扮洰鏍囧璞(target) + * Object.assign鏂规硶鐨勭涓涓弬鏁版槸鐩爣瀵硅薄,鍚庨潰鐨勫弬鏁(涓涓垨鑰呭涓)閮芥槸婧愬璞 + const target = { a: 1 }; + const source1 = { b: 2 }; + const source2 = { c: 3 }; + + Object.assign(target, source1, source2); + console.log(target); // {a:1, b:2, c:3} + + * '濡傛灉鐩爣瀵硅薄涓庢簮瀵硅薄鏈夊悓鍚嶅睘鎬,鎴栧涓簮瀵硅薄鏈夊悓鍚嶅睘鎬,鍒欏悗闈㈢殑灞炴т細瑕嗙洊鍓嶉潰鐨勫睘鎬' + * '濡傛灉鍙湁涓涓弬鏁,Object.assign浼氱洿鎺ヨ繑鍥炶鍙傛暟' + * 濡傛灉璇ュ弬鏁颁笉鏄璞,鍒欎細鍏堣浆鎴愬璞,鐒跺悗杩斿洖 + typeof Object.assign(2) // "object" + + * 鐢变簬undefined鍜宯ull鏃犳硶杞垚瀵硅薄,鎵浠ュ鏋滃畠浠綔涓哄弬鏁,灏变細鎶ラ敊 + Object.assign(undefined) // 鎶ラ敊 + Object.assign(null) // 鎶ラ敊 + + * 杩欎簺鍙傛暟閮戒細杞垚瀵硅薄,濡傛灉鏃犳硶杞垚瀵硅薄,灏变細璺宠繃,杩欐剰鍛崇潃,濡傛灉undefined鍜宯ull涓嶅湪棣栧弬鏁,灏变笉浼氭姤閿 + let obj = {a: 1}; + Object.assign(obj, undefined) === obj // true + Object.assign(obj, null) === obj // true + + * 鍏朵粬绫诲瀷鐨勫(鍗虫暟鍊,瀛楃涓插拰甯冨皵鍊)涓嶅湪棣栧弬鏁,涔熶笉浼氭姤閿,浣嗘槸,闄や簡瀛楃涓蹭細浠ユ暟缁勫舰寮,鎷疯礉鍏ョ洰鏍囧璞,鍏朵粬鍊奸兘涓嶄細浜х敓鏁堟灉 + const v1 = 'abc'; + const v2 = true; + const v3 = 10; + + const obj = Object.assign({}, v1, v2, v3); + console.log(obj); // { "0": "a", "1": "b", "2": "c" } + + * 涓婇潰浠g爜涓,甯冨皵鍊,鏁板,瀛楃涓插垎鍒浆鎴愬搴旂殑鍖呰瀵硅薄 + * 鍙互鐪嬪埌瀹冧滑鐨勫師濮嬪奸兘鍦ㄥ寘瑁呭璞$殑鍐呴儴灞炴[PrimitiveValue]]涓婇潰,杩欎釜灞炴ф槸涓嶄細琚玂bject.assign鎷疯礉鐨 + * 鍙湁瀛楃涓茬殑鍖呰瀵硅薄,浼氫骇鐢熷彲鏋氫妇鐨勫疄涔夊睘鎬,閭d簺灞炴у垯浼氳鎷疯礉 + + * Object.assign鎷疯礉鐨勫睘鎬ф槸鏈夐檺鍒剁殑,鍙嫹璐濇簮瀵硅薄鐨勮嚜韬睘鎬(涓嶆嫹璐濈户鎵垮睘鎬),涔'涓嶆嫹璐濅笉鍙灇涓剧殑灞炴(enumerable: false)' + + * 灞炴у悕涓 Symbol 鍊肩殑灞炴,涔熶細琚玂bject.assign鎷疯礉 + Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }) + // { a: 'b', Symbol(c): 'd' } + + # 娉ㄦ剰鐐 + 1,娴呮嫹璐 + * Object.assign鏂规硶瀹炶鐨勬槸娴呮嫹璐,鑰屼笉鏄繁鎷疯礉 + * 涔熷氨鏄,濡傛灉婧愬璞℃煇涓睘鎬х殑鍊兼槸瀵硅薄,閭d箞鐩爣瀵硅薄鎷疯礉寰楀埌鐨勬槸杩欎釜瀵硅薄鐨勫紩鐢 + + 2,鍚屽悕灞炴х殑鏇挎崲 + * 瀵逛簬宓屽鐨勫璞,涓鏃﹂亣鍒板悓鍚嶅睘鎬,Object.assign鐨勫鐞嗘柟娉曟槸鏇挎崲,鑰屼笉鏄坊鍔 + const target = { a: { b: 'c', d: 'e' } }; + const source = { a: { b: 'hello' } }; + Object.assign(target, source); + console.log(target);// { a: { b: 'hello' } } + + * target瀵硅薄鐨刟灞炴цsource瀵硅薄鐨刟灞炴ф暣涓浛鎹㈡帀浜 + + 3,鏁扮粍鐨勫鐞 + * Object.assign鍙互鐢ㄦ潵澶勭悊鏁扮粍,浣嗘槸浼氭妸鏁扮粍瑙嗕负瀵硅薄 + Object.assign([1, 2, 3], [4, 5]); + // [4, 5, 3] + + * object.assign鎶婃暟缁勮涓哄睘鎬у悕涓 0,1,2 鐨勫璞 + * 鍥犳婧愭暟缁勭殑 0 鍙峰睘鎬4瑕嗙洊浜嗙洰鏍囨暟缁勭殑 0 鍙峰睘鎬1 + + 4,鍙栧煎嚱鏁扮殑澶勭悊 + * Object.assign鍙兘杩涜鍊肩殑澶嶅埗,濡傛灉瑕佸鍒剁殑鍊兼槸涓涓彇鍊煎嚱鏁,閭d箞灏嗘眰鍊煎悗鍐嶅鍒 + const source = { + get foo() { return 1 } + }; + const target = {}; + + Object.assign(target, source)// { foo: 1 } + * source瀵硅薄鐨刦oo灞炴ф槸涓涓彇鍊煎嚱鏁,Object.assign涓嶄細澶嶅埗杩欎釜鍙栧煎嚱鏁,鍙細鎷垮埌鍊间互鍚,灏嗚繖涓煎鍒惰繃鍘 + + # 甯歌鐢ㄩ + 1,涓哄璞℃坊鍔犲睘鎬 + class Point { + constructor(x, y) { + Object.assign(this, {x, y}); + } + } + + * 閫氳繃Object.assign鏂规硶,灏唜灞炴у拰y灞炴ф坊鍔犲埌Point绫荤殑瀵硅薄瀹炰緥 + + 2,涓哄璞℃坊鍔犳柟娉 + * 缁 SomeClass 绫荤殑鍘熷瀷瀵硅薄娣诲姞淇╂柟娉 + Object.assign(SomeClass.prototype, { + someMethod(arg1, arg2) { + }, + anotherMethod() { + } + }); + + // 绛夊悓浜庝笅闈㈢殑鍐欐硶 + SomeClass.prototype.someMethod = function (arg1, arg2) { + }; + SomeClass.prototype.anotherMethod = function () { + }; + + 3,鍏嬮殕瀵硅薄 + * 灏嗗師濮嬪璞℃嫹璐濆埌涓涓┖瀵硅薄,灏卞緱鍒颁簡鍘熷瀵硅薄鐨勫厠闅,浣嗘槸涓嶄細鎶婃簮瀵硅薄鐨勫師鍨嬪璞$殑灞炴ф坊鍔犺繃鍘 + function clone(origin) { + return Object.assign({}, origin); + } + + * 濡傛灉鎯宠淇濇寔缁ф壙閾,鍙互閲囩敤涓嬮潰鐨勪唬鐮 + function clone(origin) { + //鑾峰彇鍘熷瀷瀵硅薄 + let originProto = Object.getPrototypeOf(origin); + //浣跨敤Object.create鏉ュ垱寤哄璞,鍏堢户鎵胯嚜鍏跺師鍨嬪璞,鐒跺悗鍐嶆妸婧愬璞¤嚜宸辩殑灞炴х户鎵胯繃鏉 + return Object.assign(Object.create(originProto), origin); + } + + 4,鍚堝苟澶氫釜瀵硅薄 + const merge = (target, ...sources) => Object.assign(target, ...sources); + const merge = (...sources) => Object.assign({}, ...sources); + + 5,涓哄睘鎬ф寚瀹氶粯璁ゅ + const DEFAULTS = { + logLevel: 0, + outputFormat: 'html' + }; + function processContent(options) { + //涓 options 杩欎釜瀵硅薄,娣诲姞 DEFAULTS瀵硅薄鐨勬墍鏈夊睘鎬у悗,鎶婂璞¤祴鍊肩粰 options + options = Object.assign({}, DEFAULTS, options); + console.log(options); + } + +------------------------------------ +灞炴х殑鍙灇涓炬у拰閬嶅巻 | +------------------------------------ + # 鍙灇涓 + * 瀵硅薄鐨勬瘡涓睘鎬ч兘鏈変竴涓弿杩板璞(Descriptor),鐢ㄦ潵鎺у埗璇ュ睘鎬х殑琛屼负 + * Object.getOwnPropertyDescriptor 鏂规硶鍙互鑾峰彇璇ュ睘鎬х殑鎻忚堪瀵硅薄 + * 鎻忚堪瀵硅薄鐨別numerable灞炴,绉颁负"鍙灇涓炬",濡傛灉璇ュ睘鎬т负false,灏辫〃绀烘煇浜涙搷浣滀細蹇界暐褰撳墠灞炴 + for...in寰幆 :鍙亶鍘嗗璞¤嚜韬殑鍜岀户鎵跨殑鍙灇涓剧殑灞炴 + Object.keys() :杩斿洖瀵硅薄鑷韩鐨勬墍鏈夊彲鏋氫妇鐨勫睘鎬х殑閿悕 + JSON.stringify():鍙覆琛屽寲瀵硅薄鑷韩鐨勫彲鏋氫妇鐨勫睘鎬 + Object.assign() :蹇界暐enumerable涓篺alse鐨勫睘鎬э紝鍙嫹璐濆璞¤嚜韬殑鍙灇涓 + + Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable + // false + + Object.getOwnPropertyDescriptor([], 'length').enumerable + // false + + * 鍙﹀,ES6 瑙勫畾,鎵鏈 Class 鐨勫師鍨嬬殑鏂规硶閮芥槸涓嶅彲鏋氫妇鐨 + Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable + // false + + * 鎬荤殑鏉ヨ,鎿嶄綔涓紩鍏ョ户鎵跨殑灞炴т細璁╅棶棰樺鏉傚寲,澶у鏁版椂鍊,鎴戜滑鍙叧蹇冨璞¤嚜韬殑灞炴 + 鎵浠,灏介噺涓嶈鐢╢or...in寰幆,鑰岀敤Object.keys()浠f浛 + + + # 灞炴х殑閬嶅巻,ES6 涓鍏辨湁 5 绉嶆柟娉曞彲浠ラ亶鍘嗗璞$殑灞炴 + 1,for...in + * for...in寰幆閬嶅巻瀵硅薄鑷韩鐨勫拰缁ф壙鐨勫彲鏋氫妇灞炴(涓嶅惈 Symbol 灞炴) + + 2,Object.keys(obj) + * Object.keys杩斿洖涓涓暟缁,鍖呮嫭瀵硅薄鑷韩鐨(涓嶅惈缁ф壙鐨)鎵鏈夊彲鏋氫妇灞炴(涓嶅惈 Symbol 灞炴)鐨勯敭鍚 + + 3,Object.getOwnPropertyNames(obj) + * Object.getOwnPropertyNames杩斿洖涓涓暟缁,鍖呭惈瀵硅薄鑷韩鐨勬墍鏈夊睘鎬(涓嶅惈 Symbol 灞炴,浣嗘槸鍖呮嫭涓嶅彲鏋氫妇灞炴)鐨勯敭鍚 + + 4,Object.getOwnPropertySymbols(obj) + * Object.getOwnPropertySymbols杩斿洖涓涓暟缁,鍖呭惈瀵硅薄鑷韩鐨勬墍鏈 Symbol 灞炴х殑閿悕 + + 5,Reflect.ownKeys(obj) + * Reflect.ownKeys杩斿洖涓涓暟缁,鍖呭惈瀵硅薄鑷韩鐨勬墍鏈夐敭鍚,涓嶇閿悕鏄 Symbol 鎴栧瓧绗︿覆,涔熶笉绠℃槸鍚﹀彲鏋氫妇 + + * 浠ヤ笂鐨 5 绉嶆柟娉曢亶鍘嗗璞$殑閿悕,閮介伒瀹堝悓鏍风殑灞炴ч亶鍘嗙殑娆″簭瑙勫垯 + 棣栧厛閬嶅巻鎵鏈夋暟鍊奸敭,鎸夌収鏁板煎崌搴忔帓鍒 + 鍏舵閬嶅巻鎵鏈夊瓧绗︿覆閿,鎸夌収鍔犲叆鏃堕棿鍗囧簭鎺掑垪 + 鏈鍚庨亶鍘嗘墍鏈 Symbol 閿紝鎸夌収鍔犲叆鏃堕棿鍗囧簭鎺掑垪 + + Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 }) + // ['2', '10', 'b', 'a', Symbol()] + + * Reflect.ownKeys鏂规硶杩斿洖涓涓暟缁,鍖呭惈浜嗗弬鏁板璞$殑鎵鏈夊睘鎬 + * 杩欎釜鏁扮粍鐨勫睘鎬ф搴忔槸杩欐牱鐨,棣栧厛鏄暟鍊煎睘鎬2鍜10,鍏舵鏄瓧绗︿覆灞炴鍜宎,鏈鍚庢槸 Symbol 灞炴 + +------------------------------------ +Object.getOwnPropertyDescriptors() | +------------------------------------ + # Object.getOwnPropertyDescriptors鏂规硶,杩斿洖鎸囧畾瀵硅薄鎵鏈夎嚜韬睘鎬(闈炵户鎵垮睘鎬)鐨勬弿杩板璞 + # 璇ユ柟娉曠殑瀹炵幇闈炲父瀹规槗 + function getOwnPropertyDescriptors(obj) { + const result = {}; + for (let key of Reflect.ownKeys(obj)) { + result[key] = Object.getOwnPropertyDescriptor(obj, key); + } + return result; + } + + # 璇ユ柟娉曠殑寮曞叆鐩殑,涓昏鏄负浜嗚В鍐砄bject.assign()鏃犳硶姝g‘鎷疯礉get灞炴у拰set灞炴х殑闂 + const source = { + set foo(value) { + console.log(value); + } + }; + const target1 = {}; + Object.assign(target1, source); + console.log(Object.getOwnPropertyDescriptor(target1, 'foo')); + //{value: undefined, writable: true, enumerable: true, configurable: true} + + # Object.getOwnPropertyDescriptors 鏂规硶閰嶅悎Object.defineProperties 鏂规硶,灏卞彲浠ュ疄鐜版纭嫹璐 + const source = { + set foo(value) { + console.log(value); + } + }; + const target2 = {}; + //缁 target2 瀵硅薄鍒涘缓N澶氬睘鎬,鏉ヨ嚜浜 source 瀵硅薄鐨凬澶氬睘鎬 + Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source)); + //鍙互姝g‘鐨勮幏鍙栧埌get/set灞炴 + Object.getOwnPropertyDescriptor(target2, 'foo'); + + //鍚堝苟鎴愪竴鍙ヨ瘽 + const shallowMerge = (target, source) => Object.defineProperties(target,Object.getOwnPropertyDescriptors(source)); + + # Object.getOwnPropertyDescriptors鏂规硶鐨勫彟涓涓敤澶,鏄厤鍚圤bject.create鏂规硶,灏嗗璞″睘鎬у厠闅嗗埌涓涓柊瀵硅薄 + //瀵筼bj瀵硅薄杩涜clone + function clone(obj){ + return Object.create(Object.getPrototypeOf(obj),Object.getOwnPropertyDescriptors(obj)); + } + + // 鎴栬 + const shallowClone = (obj) => Object.create(Object.getPrototypeOf(obj),Object.getOwnPropertyDescriptors(obj)); + + # Object.getOwnPropertyDescriptors鏂规硶鍙互瀹炵幇涓涓璞$户鎵垮彟涓涓璞 + const obj = Object.create( + prot, //鍘熷瀷瀵硅薄 + Object.getOwnPropertyDescriptors({ //浠庢煇涓璞$户鎵垮睘鎬 + foo: 123, + }) + ); + + # Object.getOwnPropertyDescriptors涔熷彲浠ョ敤鏉ュ疄鐜 Mixin(娣峰叆)妯″紡 + .... + +---------------------------------------------------------------- +__proto__灞炴,Object.setPrototypeOf(),Object.getPrototypeOf() | +---------------------------------------------------------------- + # __proto__灞炴 + * 鐢ㄦ潵璇诲彇鎴栬缃綋鍓嶅璞$殑prototype瀵硅薄,鐩墠,鎵鏈夋祻瑙堝櫒(鍖呮嫭 IE11)閮介儴缃蹭簡杩欎釜灞炴 + * '灏介噺涓嶈浣跨敤杩欎釜灞炴,涓嶆槸涓涓寮忕殑瀵瑰鐨 API,鍙槸鐢变簬娴忚鍣ㄥ箍娉涙敮鎸,鎵嶈鍔犲叆浜 ES6,鏍囧噯鏄庣‘瑙勫畾,鍙湁娴忚鍣ㄥ繀椤婚儴缃茶繖涓睘鎬,鍏朵粬杩愯鐜涓嶄竴瀹氶渶瑕侀儴缃' + * 浣跨敤api鏉ヨ缃,璇诲彇,鍒涘缓 + Object.setPrototypeOf() + * 璁剧疆鍘熷瀷瀵硅薄 + Object.getPrototypeOf() + * 鑾峰彇鍘熷瀷瀵硅薄 + Object.create() + * 鍒涘缓瀵硅薄 + + # Object.setPrototypeOf() + * 浣滅敤涓巁_proto__鐩稿悓,鐢ㄦ潵璁剧疆涓涓璞$殑鍘熷瀷瀵硅薄 + * 杩斿洖鍙傛暟瀵硅薄鏈韩,瀹冩槸 ES6 姝e紡鎺ㄨ崘鐨勮缃師鍨嬪璞$殑鏂规硶 + * 鐩稿綋浜 + function (obj, proto) { + obj.__proto__ = proto; + return obj; + } + + * 濡傛灉绗竴涓弬鏁颁笉鏄璞,浼氳嚜鍔ㄨ浆涓哄璞,浣嗘槸鐢变簬杩斿洖鐨勮繕鏄涓涓弬鏁,鎵浠ヨ繖涓搷浣滀笉浼氫骇鐢熶换浣曟晥鏋 + * 鐢变簬undefined鍜宯ull鏃犳硶杞负瀵硅薄,鎵浠ュ鏋滅涓涓弬鏁版槸undefined鎴杗ull,灏变細鎶ラ敊 + + + # Object.getPrototypeOf() + * 璇诲彇涓涓璞$殑鍘熷瀷瀵硅薄 + * 濡傛灉鍙傛暟涓嶆槸瀵硅薄,浼氳鑷姩杞负瀵硅薄,濡傛灉鍙傛暟鏄痷ndefined鎴杗ull,瀹冧滑鏃犳硶杞负瀵硅薄,鎵浠ヤ細鎶ラ敊 + +-------------------------------- +super 鍏抽敭瀛 | +-------------------------------- + # this鍏抽敭瀛楁绘槸鎸囧悜鍑芥暟鎵鍦ㄧ殑褰撳墠瀵硅薄 + # ES6 鍙堟柊澧炰簡鍙︿竴涓被浼肩殑鍏抽敭瀛 super,鎸囧悜褰撳墠瀵硅薄鐨勫師鍨嬪璞 + const proto = { + foo: 'hello' + }; + + const obj = { + foo: 'world', + find() { + return super.foo; + } + }; + + Object.setPrototypeOf(obj, proto); + obj.find() // "hello" + * 瀵硅薄obj鐨刦ind鏂规硶涔嬩腑,閫氳繃super.foo寮曠敤浜嗗師鍨嬪璞roto鐨刦oo灞炴 + + # 'super鍏抽敭瀛楄〃绀哄師鍨嬪璞℃椂,鍙兘鐢ㄥ湪瀵硅薄鐨勬柟娉曚箣涓,鐢ㄥ湪鍏朵粬鍦版柟閮戒細鎶ラ敊' + // 鎶ラ敊 + const obj = { + foo: super.foo + } + + // 鎶ラ敊 + const obj = { + foo: () => super.foo + } + + // 鎶ラ敊 + const obj = { + foo: function () { + return super.foo + } + } + * 涓婇潰涓夌super鐨勭敤娉曢兘浼氭姤閿,鍥犱负瀵逛簬 JavaScript 寮曟搸鏉ヨ,杩欓噷鐨剆uper閮芥病鏈夌敤鍦ㄥ璞$殑鏂规硶涔嬩腑 + * 绗竴绉嶅啓娉曟槸super鐢ㄥ湪灞炴ч噷闈 + * 绗簩绉嶅拰绗笁绉嶅啓娉曟槸super鐢ㄥ湪涓涓嚱鏁伴噷闈,鐒跺悗'璧嬪肩粰foo灞炴'(鍏跺疄杩樻槸灞炴,涓嶈繃杩欎釜灞炴ф槸涓柟娉) + * '鐩墠,鍙湁瀵硅薄鏂规硶鐨勭畝鍐欐硶鍙互璁 JavaScript 寮曟搸纭,瀹氫箟鐨勬槸瀵硅薄鐨勬柟娉' + + # JavaScript 寮曟搸鍐呴儴,super 鏄繖鏍风殑 + * Object.getPrototypeOf(this).foo (灞炴) + * Object.getPrototypeOf(this).foo.call(this) (鏂规硶) + + +---------------------------------------------- +Object.keys(),Object.values(),Object.entries()| +---------------------------------------------- + # Object.keys() + * 杩斿洖涓涓暟缁,鎴愬憳鏄弬鏁板璞¤嚜韬殑(涓嶅惈缁ф壙鐨)鎵鏈夊彲閬嶅巻(enumerable)灞炴х殑閿悕 + + # Object.values() + * 杩斿洖涓涓暟缁,鎴愬憳鏄弬鏁板璞¤嚜韬殑(涓嶅惈缁ф壙鐨)鎵鏈夊彲閬嶅巻(enumerable)灞炴х殑閿 + * 杩斿洖鏁扮粍鐨勬垚鍛橀『搴,涓庢湰绗旇鐨勭殑銆婂睘鎬х殑閬嶅巻銆嬮儴鍒嗕粙缁嶇殑鎺掑垪瑙勫垯涓鑷 + * Object.values 浼氳繃婊ゅ睘鎬у悕涓 Symbol 鍊肩殑灞炴 + * 濡傛灉Object.values鏂规硶鐨勫弬鏁版槸涓涓瓧绗︿覆,浼氳繑鍥炲悇涓瓧绗︾粍鎴愮殑涓涓暟缁 + * 濡傛灉鍙傛暟涓嶆槸瀵硅薄,Object.values浼氬厛灏嗗叾杞负瀵硅薄,鐢变簬鏁板煎拰甯冨皵鍊肩殑鍖呰瀵硅薄,閮戒笉浼氫负瀹炰緥娣诲姞闈炵户鎵跨殑灞炴,鎵浠,Object.values浼氳繑鍥炵┖鏁扮粍 + + # Object.entries() + * 娉曡繑鍥炰竴涓暟缁,鎴愬憳鏄弬鏁板璞¤嚜韬殑(涓嶅惈缁ф壙鐨)鎵鏈夊彲閬嶅巻(enumerable)灞炴х殑閿煎鏁扮粍 + * 闄や簡杩斿洖鍊间笉涓鏍,璇ユ柟娉曠殑琛屼负涓嶰bject.values鍩烘湰涓鑷 + * 濡傛灉鍘熷璞$殑灞炴у悕鏄竴涓 Symbol 鍊,璇ュ睘鎬т細琚拷鐣 + * Object.entries鐨勫熀鏈敤閫旀槸閬嶅巻瀵硅薄鐨勫睘鎬 + let obj = { one: 1, two: 2 }; + for (let [k, v] of Object.entries(obj)) { + console.log( + `${JSON.stringify(k)}: ${JSON.stringify(v)}` + ); + } + * Object.entries鏂规硶鐨勫彟涓涓敤澶勬槸,灏嗗璞¤浆涓虹湡姝g殑Map缁撴瀯 + const obj = { foo: 'bar', baz: 42 }; + const map = new Map(Object.entries(obj)); + log(map); // Map { foo: "bar", baz: 42 } + * 鑷繁瀹炵幇Object.entries鏂规硶 + // Generator鍑芥暟鐨勭増鏈 + function* entries(obj) { + for (let key of Object.keys(obj)) { + yield [key, obj[key]]; + } + } + + // 闈濭enerator鍑芥暟鐨勭増鏈 + function entries(obj) { + let arr = []; + for (let key of Object.keys(obj)) { + arr.push([key, obj[key]]); + } + return arr; + } + +-------------------------------- +瀵硅薄鐨勬墿灞曡繍绠楃 | +-------------------------------- + # 瑙f瀯璧嬪 + * 瀵硅薄鐨勮В鏋勮祴鍊肩敤浜庝粠涓涓璞″彇鍊,鐩稿綋浜庡皢鐩爣瀵硅薄鑷韩鐨勬墍鏈夊彲閬嶅巻鐨(enumerable)浣嗗皻鏈璇诲彇鐨勫睘鎬,鍒嗛厤鍒版寚瀹氱殑瀵硅薄涓婇潰,鎵鏈夌殑閿拰瀹冧滑鐨勫,閮戒細鎷疯礉鍒版柊瀵硅薄涓婇潰 + let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; + console.log(x); // 1 + console.log(y) // 2 + console.log(z) // { a: 3, b: 4 } + + + * x鍜寉閮借兘鎴愬姛鐨勮璧嬪 + * 褰搝娌℃湁鐨勪笌涔嬪尮閰嶇殑鏁版嵁椤圭殑鏃跺,浼氭妸鎵鏈夌殑鏁版嵁閮藉尮閰嶅埌z閲岄潰,褰㈡垚涓涓璞(瑙f瀯璧嬪) + + * 瑙f瀯璧嬪艰姹傜瓑鍙峰彸杈规槸涓涓璞,鎵浠ュ鏋滅瓑鍙峰彸杈规槸undefined鎴杗ull,灏变細鎶ラ敊,鍥犱负瀹冧滑鏃犳硶杞负瀵硅薄 + * 瑙f瀯璧嬪煎繀椤绘槸鏈鍚庝竴涓弬鏁,鍚﹀垯浼氭姤閿 + * 瑙f瀯璧嬪肩殑鎷疯礉鏄祬鎷疯礉,鍗冲鏋滀竴涓敭鐨勫兼槸澶嶅悎绫诲瀷鐨勫(鏁扮粍,瀵硅薄,鍑芥暟),a閭d箞瑙f瀯璧嬪兼嫹璐濈殑鏄繖涓肩殑寮曠敤,鑰屼笉鏄繖涓肩殑鍓湰 + * 鎵╁睍杩愮畻绗︾殑瑙f瀯璧嬪,涓嶈兘澶嶅埗缁ф壙鑷師鍨嬪璞$殑灞炴('鍘熷瀷瀵硅薄,涓嶈兘琚В鏋勮祴鍊') + let o1 = { a: 1 }; + let o2 = { b: 2 }; + //璁剧疆o2鐨勫師鍨嬪璞′负o1 + o2.__proto__ = o1; + //鎶妎2瑙f瀯璧嬪煎埌o3 + let { ...o3 } = o2; + //鎵撳嵃o3,娌℃湁o1(鍘熷瀷瀵硅薄)鐨勫睘鎬 + log(o3) // { b: 2 } + //鎵撳嵃o3鍘熷瀷瀵硅薄閲岄潰鐨刟,涓嶅瓨鍦 + log(o3.a) + -------------------------------------- + //鍒涘缓涓涓璞,骞朵笖璁剧疆鍘熷瀷瀵硅薄 + const o = Object.create({ x: 1, y: 2 }); + //璁剧疆瀵硅薄鐨剒灞炴 + o.z = 3; + //鎶婂璞¤В鏋勮祴鍊 + let { x, ...{ y, z } } = o; + + x // 1 + y // undefined 鍘熷瀷瀵硅薄鐨勬暟鎹湭瑙f瀯璧嬪兼垚鍔 + z // 3 + + '娴忚鍣ㄦ姏鍑哄紓甯 Uncaught SyntaxError: `...` must be followed by an identifier in declaration contexts' + + * 瑙f瀯璧嬪肩殑涓涓敤澶,鏄墿灞曟煇涓嚱鏁扮殑鍙傛暟,寮曞叆鍏朵粬鎿嶄綔 + function baseFunction({ a, b }) { + console.log(a); + console.log(b); + } + function wrapperFunction({ x, y, ...restConfig }) { + // 浣跨敤 x 鍜 y 鍙傛暟杩涜鎿嶄綔 + // 鍏朵綑鍙傛暟浼犵粰鍘熷鍑芥暟 + return baseFunction(restConfig); + } + + # 鎵╁睍杩愮畻绗 + * 瀵硅薄鐨勬墿灞曡繍绠楃(...)鐢ㄤ簬鍙栧嚭鍙傛暟瀵硅薄鐨勬墍鏈夊彲閬嶅巻灞炴,鎷疯礉鍒板綋鍓嶅璞′箣涓 + let z = { a: 3, b: 4 }; + let n = { ...z }; //鎶妟瀵硅薄,瑙f瀯璧嬪艰В鏋愪负涓涓釜鐨勫睘鎬,璧嬪肩粰浜唍 + n // { a: 3, b: 4 } + + * 杩欑瓑鍚屼簬浣跨敤Object.assign鏂规硶 + let aClone = { ...a }; + // 绛夊悓浜 + let aClone = Object.assign({}, a); + + * 涓婇潰鐨勪緥瀛愬彧鏄嫹璐濅簡瀵硅薄瀹炰緥鐨勫睘鎬,濡傛灉鎯冲畬鏁村厠闅嗕竴涓璞,杩樻嫹璐濆璞″師鍨嬬殑灞炴,鍙互閲囩敤涓嬮潰鐨勫啓娉 + // 鍐欐硶涓 + const clone1 = { + //璁剧疆褰撳墠瀵硅薄鐨刾rototype涓簅bj鐨刾rototype + __proto__: Object.getPrototypeOf(obj), + ...obj + }; + + // 鍐欐硶浜 + const clone2 = Object.assign( + //鍒涘缓涓涓璞,璇ュ璞$殑鍘熷瀷瀵硅薄灏辨槸obj鐨刾rototype + Object.create(Object.getPrototypeOf(obj)), + obj + ); + + // 鍐欐硶涓 + const clone3 = Object.create( + //鍒涘缓涓涓璞,鍘熷瀷瀵硅薄缁ф壙鑷猳bj鐨勫師鍨嬪璞,鐒跺悗灞炴ф潵鑷簬obj + Object.getPrototypeOf(obj), + Object.getOwnPropertyDescriptors(obj) + ) + + * 涓婇潰浠g爜涓,鍐欐硶涓鐨刜_proto__灞炴у湪闈炴祻瑙堝櫒鐨勭幆澧冧笉涓瀹氶儴缃,鍥犳'鎺ㄨ崘浣跨敤鍐欐硶浜屽拰鍐欐硶涓' + + * 鎵╁睍杩愮畻绗﹀彲浠ョ敤浜庡悎骞朵袱涓璞 + let a = {name:'Kevin'} + let b = {age:23} + + let ab = { ...a, ...b }; + let ab = Object.assign({}, a, b); + //{name: "Kevin", age: 23} + + * 濡傛灉鐢ㄦ埛鑷畾涔夌殑灞炴,鏀惧湪鎵╁睍杩愮畻绗﹀悗闈,鍒欐墿灞曡繍绠楃鍐呴儴鐨勫悓鍚嶅睘鎬т細琚鐩栨帀 + let a = {x:'x鐨勫'} + let aWithOverrides1 = { ...a, x: 1, y: 2 }; //濡傛灉a瀵硅薄閲屾湁x/y鐨勫睘鎬у,閭d箞浼氳鍚庨潰鐨剎/y瑕嗙洊 + // 绛夊悓浜 + let aWithOverrides2 = { ...a, ...{ x: 1, y: 2 } }; + // 绛夊悓浜 + let x = 1, y = 2, aWithOverrides3 = { ...a, x, y }; + // 绛夊悓浜 + let aWithOverrides4 = Object.assign({}, a, { x: 1, y: 2 }); + //a瀵硅薄鐨剎灞炴у拰y灞炴,鎷疯礉鍒版柊瀵硅薄鍚庝細琚鐩栨帀 + console.log(aWithOverrides1); //{x: 1, y: 2} + + * 鐢ㄦ潵淇敼鐜版湁瀵硅薄閮ㄥ垎鐨勫睘鎬у氨寰堟柟渚夸簡 + let newVersion = { + //鏃у璞 + ...previousVersion, + //瑕嗙洊鏃у璞¢噷闈㈢殑name灞炴 + name: 'New Name' // Override the name property + }; + + * 濡傛灉鎶婅嚜瀹氫箟灞炴ф斁鍦ㄦ墿灞曡繍绠楃鍓嶉潰,灏卞彉鎴愪簡璁剧疆鏂板璞$殑榛樿灞炴у('鍙嶆鍚庨潰鐨勫睘鎬,浼氳鐩栧墠闈㈢殑灞炴') + let a = {x:6} + let aWithDefaults1 = { x: 1, y: 2, ...a }; //a瀵硅薄閲岄潰鐨剎灞炴т細瑕嗙洊鍓嶉潰鐨剎灞炴 + // 绛夊悓even if property keys don鈥檛 clash, because objects record insertion order: + + let aWithDefaults2 = Object.assign({}, { x: 1, y: 2 }, a); + // 绛夊悓浜 + let aWithDefaults3 = Object.assign({ x: 1, y: 2 }, a); + + console.log(aWithDefaults1); + console.log(aWithDefaults2); + console.log(aWithDefaults3); + + * 涓庢暟缁勭殑鎵╁睍杩愮畻绗︿竴鏍,瀵硅薄鐨勬墿灞曡繍绠楃鍚庨潰鍙互璺熻〃杈惧紡 + const obj = { + ...(x > 1 ? {a: 1} : {}), + b: 2, + }; + + * 濡傛灉鎵╁睍杩愮畻绗﹀悗闈㈡槸涓涓┖瀵硅薄,鍒欐病鏈変换浣曟晥鏋 + let {...x} = {...{}, a: 1} + console.log(x); //{a: 1} + + * 濡傛灉鎵╁睍杩愮畻绗︾殑鍙傛暟鏄痭ull鎴杣ndefined,杩欎袱涓间細琚拷鐣,涓嶄細鎶ラ敊 + let emptyObject = { ...null, ...undefined }; // 涓嶆姤閿 + + * 鎵╁睍杩愮畻绗︾殑鍙傛暟瀵硅薄涔嬩腑,濡傛灉鏈夊彇鍊煎嚱鏁癵et,杩欎釜鍑芥暟鏄細鎵ц鐨 + // 骞朵笉浼氭姏鍑洪敊璇紝鍥犱负 x 灞炴у彧鏄瀹氫箟锛屼絾娌℃墽琛 + let aWithXGetter = { + ...a, + get x() { + throw new Error('not throw yet'); + } + }; + // 浼氭姏鍑洪敊璇紝鍥犱负 x 灞炴ц鎵ц浜 + let runtimeError = { + ...a, + ...{ + get x() { + throw new Error('throw now'); + } + } + }; \ No newline at end of file diff --git "a/JavaSript/ES6/es6-\346\211\251\345\261\225-\346\225\260\345\200\274.js" "b/JavaSript/ES6/es6-\346\211\251\345\261\225-\346\225\260\345\200\274.js" new file mode 100644 index 00000000..0805df63 --- /dev/null +++ "b/JavaSript/ES6/es6-\346\211\251\345\261\225-\346\225\260\345\200\274.js" @@ -0,0 +1,132 @@ +------------------------ +鏁板 | +------------------------ + 1,浜岃繘鍒跺拰鍏繘鍒惰〃绀烘硶 + 2,Number.isFinite(), Number.isNaN() + 3,Number.parseInt(), Number.parseFloat() + 4,Number.isInteger() + 5,Number.EPSILON + 6,瀹夊叏鏁存暟鍜 Number.isSafeInteger() + 7,Math 瀵硅薄鐨勬墿灞 + 8,鎸囨暟杩愮畻绗 + +------------------------ +浜岃繘鍒跺拰鍏繘鍒 | +------------------------ + # 浜岃繘鍒,鍏繘鍒,鍗佸叚杩涘埗琛ㄧず + let x = 0o000227 + let y = 0b010101 + let z = 0xF88481 + + * 鎶婂畠浠浆鎹负10杩涘埗 + Number('0b111') // 7 + Number('0o10') // 8 + +----------------------------------- +鏂板鍑芥暟/甯搁噺 | +------------------------------------ + Number.isFinite() + * 鐢ㄦ潵妫鏌ヤ竴涓暟鍊兼槸鍚︿负鏈夐檺鐨(finite),鍗充笉鏄疘nfinity(姝g殑鏃犵┓澶х殑鏁板)銆 + Number.isFinite(15); // true + Number.isFinite(0.8); // true + Number.isFinite(NaN); // false + Number.isFinite(Infinity); // false + Number.isFinite(-Infinity); // false + Number.isFinite('foo'); // false + Number.isFinite('15'); // false + Number.isFinite(true); // false + + * 濡傛灉鍙傛暟绫诲瀷涓嶆槸鏁板硷紝Number.isFinite涓寰嬭繑鍥瀎alse + + + Number.isNaN() + * 鍒ゆ柇鏁版嵁鏄惁鏄竴涓潪鏁板瓧 + + Number.parseInt() + Number.parseFloat() + * 鎶婃寚瀹氭暟鎹浆鎹负Int绫诲瀷鐨勬暟鎹 + * 鍏跺疄灏辨槸閭d釜鍏ㄥ眬鍑芥暟 + Number.parseInt === parseInt // true + Number.parseFloat === parseFloat // true + + Number.isInteger() + * 鍒ゆ柇涓涓暟鏄惁涓烘暣鏁 + * avaScript 鍐呴儴,鏁存暟鍜屾诞鐐规暟閲囩敤鐨勬槸鍚屾牱鐨勫偍瀛樻柟娉,鎵浠 25 鍜 25.0 琚涓哄悓涓涓 + umber.isInteger(25) // true + Number.isInteger(25.0) // false + + * 濡傛灉鍙傛暟涓嶆槸鏁板硷紝Number.isInteger杩斿洖false銆 + * '瀵规暟鎹簿搴︾殑瑕佹眰杈冮珮,涓嶅缓璁娇鐢∟umber.isInteger()鍒ゆ柇涓涓暟鍊兼槸鍚︿负鏁存暟' + + Number.isSafeInteger() + * 鏄惁鏄竴涓畨鍏ㄧ殑鏁存暟,涔熷氨鏄鍊兼槸鍚﹀湪 MAX_SAFE_INTEGER 鍜 MIN_SAFE_INTEGER 涔嬮棿 + + + Number.EPSILON + * 瀹冭〃绀 1 涓庡ぇ浜 1 鐨勬渶灏忔诞鐐规暟涔嬮棿鐨勫樊 + * 瀵逛簬 64 浣嶆诞鐐规暟鏉ヨ,澶т簬 1 鐨勬渶灏忔诞鐐规暟鐩稿綋浜庝簩杩涘埗鐨1.00..001,灏忔暟鐐瑰悗闈㈡湁杩炵画 51 涓浂,杩欎釜鍊煎噺鍘 1 涔嬪悗,灏辩瓑浜 2 鐨 -52 娆℃柟 + + Number.MAX_SAFE_INTEGER + * JavaScript 鑳藉鍑嗙‘琛ㄧず鐨勬渶澶ф暣鏁 + + Number.MIN_SAFE_INTEGER + * JavaScript 鑳藉鍑嗙‘琛ㄧず鐨勬渶灏忔暣鏁 + + +----------------------------------- +Math-鎵╁睍 | +------------------------------------ + Math.trunc() + * 鍘婚櫎涓涓暟鐨勫皬鏁伴儴鍒,杩斿洖鏁存暟閮ㄥ垎 + * 瀵逛簬闈炴暟鍊,Math.trunc鍐呴儴浣跨敤Number鏂规硶灏嗗叾鍏堣浆涓烘暟鍊 + * 瀵逛簬绌哄煎拰鏃犳硶鎴彇鏁存暟鐨勫,杩斿洖NaN + Math.trunc(4.9) // 4 + Math.trunc(-4.1) // -4 + + Math.sign() + * 鍒ゆ柇涓涓暟鍒板簳鏄鏁,璐熸暟,杩樻槸闆,瀵逛簬闈炴暟鍊,浼氬厛灏嗗叾杞崲涓烘暟鍊 + * 瀹冧細杩斿洖浜旂鍊 + 鍙傛暟涓烘鏁 杩斿洖+1 + 鍙傛暟涓鸿礋鏁 杩斿洖-1 + 鍙傛暟涓0 杩斿洖锛 + 鍙傛暟涓-0 杩斿洖-0 + 鍏朵粬鍊 杩斿洖NaN + + Math.cbrt() + * 璁$畻涓涓暟鐨勭珛鏂规牴 + + Math.clz32() + * 杩斿洖涓涓暟鐨 32 浣嶆棤绗﹀彿鏁存暟褰㈠紡鏈夊灏戜釜鍓嶅 0 + + Math.imul() + * 杩斿洖涓や釜鏁颁互 32 浣嶅甫绗﹀彿鏁存暟褰㈠紡鐩镐箻鐨勭粨鏋滐紝杩斿洖鐨勪篃鏄竴涓 32 浣嶇殑甯︾鍙锋暣鏁 + + Math.fround() + * 杩斿洖涓涓暟鐨32浣嶅崟绮惧害娴偣鏁板舰寮 + + Math.hypot() + * 杩斿洖鎵鏈夊弬鏁扮殑骞虫柟鍜岀殑骞虫柟鏍 + + ... + +----------------------------------- +鏂扮殑杩愮畻绗 | +------------------------------------ + * S2016 鏂板浜嗕竴涓寚鏁拌繍绠楃 ** + 2 ** 2 // 4 + 2 ** 3 // 8 + + + * 鎸囨暟杩愮畻绗﹀彲浠ヤ笌绛夊彿缁撳悎锛屽舰鎴愪竴涓柊鐨勮祴鍊艰繍绠楃锛**=锛夈 + + let a = 1.5; + a **= 2; + // 绛夊悓浜 a = a * a; + + let b = 4; + b **= 3; + // 绛夊悓浜 b = b * b * b; + + + + diff --git "a/JavaSript/ES6/es6-\346\211\251\345\261\225-\346\225\260\347\273\204.js" "b/JavaSript/ES6/es6-\346\211\251\345\261\225-\346\225\260\347\273\204.js" new file mode 100644 index 00000000..a00085b4 --- /dev/null +++ "b/JavaSript/ES6/es6-\346\211\251\345\261\225-\346\225\260\347\273\204.js" @@ -0,0 +1,539 @@ +---------------------------- +鏁扮粍鐨勬墿灞 | +---------------------------- + 1,鎵╁睍杩愮畻绗 + 2,Array.from() + 3,Array.of() + 4,鏁扮粍瀹炰緥鐨 copyWithin() + 5,鏁扮粍瀹炰緥鐨 find() 鍜 findIndex() + 6,鏁扮粍瀹炰緥鐨 fill() + 7,鏁扮粍瀹炰緥鐨 entries(),keys() 鍜 values() + 8,鏁扮粍瀹炰緥鐨 includes() + 9,鏁扮粍鐨勭┖浣 + +---------------------------- +鎵╁睍杩愮畻绗 | +---------------------------- + # 鎵╁睍杩愮畻绗︽槸涓変釜. (...),鍍弐est鍙傛暟鐨勯嗚繍绠 + console.log(1,...[2,3]) //1 2 3 + * 灏辨槸鎶婁竴涓暟缁,瑙f瀽涓轰竴涓釜鐨勫弬鏁 + + # 澶ч兘鏄敤浜庡嚱鏁板弬鏁 + function foo(...vs){ + let sum = 0; + for(let x of vs){ + sum += x; + } + return sum; + } + console.log(foo(1,...[2,3])) //6 + + # 鎵╁睍杩愮畻绗﹀悗闈㈣繕鍙互鏀剧疆琛ㄨ揪寮 + let x = true; + const ARR = [...(x > 0 ? ['a'] : []), 'b',]; + log(ARR) //聽["a", "b"] + + * 濡傛灉鎵╁睍杩愮畻绗﹀悗闈㈡槸涓涓┖鏁扮粍锛屽垯涓嶄骇鐢熶换浣曟晥鏋溿 + [...[], 1] + // [1] + + # 鏇夸唬鍑芥暟鐨 apply 鏂规硶 + * 鐢变簬鎵╁睍杩愮畻绗﹀彲浠ュ睍寮鏁扮粍,鎵浠ヤ笉鍐嶉渶瑕乤pply鏂规硶,灏嗘暟缁勮浆涓哄嚱鏁扮殑鍙傛暟浜 + // ES5 鐨勫啓娉 + function f(x, y, z) { + // ... + } + var args = [0, 1, 2]; + //apply鐨勭涓涓弬鏁颁负 null 鎴栬卽ndifined,閭d箞鎵ц鐜灏辨槸window/global + //apply鐨勭浜屼釜鍙傛暟灏辨槸灏辨槸鍙傛暟鏁扮粍,杩欐牱灏遍『鐞嗘垚绔犵殑鎶婂弬鏁颁綔涓轰竴涓紶閫掕繃鍘讳簡,骞朵笖鎶婃暟缁勯噷闈㈢殑姣忎釜閫夐」瑙f瀽鍒板嚱鏁扮殑姣忎釜褰㈠弬 + f.apply(null, args); + + // ES6鐨勫啓娉 + function f(x, y, z) { + // ... + } + let args = [0, 1, 2]; + f(...args); + + * demo + // ES5 鐨勫啓娉 + Math.max.apply(null, [14, 3, 77]) + // ES6 鐨勫啓娉 + Math.max(...[14, 3, 77])// 绛夊悓浜嶮ath.max(14, 3, 77); + + let arr = [1,2,3] + arr.push(...'6555') //瀛楃涓蹭篃鏄彲浠ョ殑 + console.log(arr); //[1, 2, 3, "6", "5", "5", "5"] + + // ES5 + new (Date.bind.apply(Date, [null, 2015, 1, 1])) + // ES6 + new Date(...[2015, 1, 1]); + + # 鎵╁睍杩愮畻杩樻湁鍙互鐢ㄥ湪瀛楃涓 + [...'hello'] + // [ "h", "e", "l", "l", "o" ] + + * 杩樿兘姝g‘鐨勮瘑鍒玌nicode瀛楃 + 'x\uD83D\uDE80y'.length // 4 + [...'x\uD83D\uDE80y'].length // 3 + * 姝g‘杩斿洖瀛楃涓查暱搴︾殑鍑芥暟 + function length(str) { + return [...str].length; + } + + length('x\uD83D\uDE80y') // 3 + * 鍑℃槸娑夊強鍒版搷浣滃洓涓瓧鑺傜殑 Unicode 瀛楃鐨勫嚱鏁,閮芥湁杩欎釜闂,鍥犳,鏈濂介兘鐢ㄦ墿灞曡繍绠楃鏀瑰啓 + let str = 'x\uD83D\uDE80y'; + + str.split('').reverse().join('') + // 'y\uDE80\uD83Dx' + + [...str].reverse().join('') + // 'y\uD83D\uDE80x' + * 濡傛灉涓嶇敤鎵╁睍杩愮畻绗,瀛楃涓茬殑reverse鎿嶄綔灏变笉姝g‘ + + # 瀹炵幇浜 Iterator 鎺ュ彛,閮藉彲浠ョ敤鎵╁睍杩愮畻绗﹁浆涓虹湡姝g殑鏁扮粍 + let nodeList = document.querySelectorAll('div'); + let array = [...nodeList]; + * uerySelectorAll鏂规硶杩斿洖鐨勬槸涓涓猲odeList瀵硅薄,瀹冧笉鏄暟缁,鑰屾槸涓涓被浼兼暟缁勭殑瀵硅薄 + + # Map 鍜 Set 缁撴瀯,Generator 鍑芥暟 + * 鎵╁睍杩愮畻绗﹀唴閮ㄨ皟鐢ㄧ殑鏁版嵁瑙f瀯閮芥槸 Iterator 鎺ュ彛 + * 鍥犳鍙鏄疄鐜颁簡 Iterator 鎺ュ彛鐨勫璞,閮藉彲浠ヤ娇鐢ㄦ墿灞曡繍绠楃 + * Map + let map = new Map([ + [1, 'one'], + [2, 'two'], + [3, 'three'], + ]); + + let arr = [...map.keys()]; // [1, 2, 3] + + * Generator 鍑芥暟杩愯鍚庯紝杩斿洖涓涓亶鍘嗗櫒瀵硅薄锛屽洜姝や篃鍙互浣跨敤鎵╁睍杩愮畻绗︺ + const go = function*(){ + yield 1; + yield 2; + yield 3; + }; + [...go()] // [1, 2, 3] + + + + # 搴旂敤 + * 澶嶅埗鏁扮粍 + const a1 = [1, 2]; + // 鍐欐硶涓 + const a2 = [...a1]; + * 瀹氫箟浜嗕竴涓猘2鐨勫彉閲,鐒跺悗[]鎶婅В鏋勫悗鐨勬暟鎹寘瑁硅捣鏉,灏辨槸涓涓暟缁勪簡 + + // 鍐欐硶浜(杩欎釜鍐欐硶鏈夌偣鎰忔濅簡) + const [...a2] = a1; + * 瀹氫箟浜嗕竴涓猘2鐨勫彉閲,鏄竴涓В鏋勪綋 + * 鐒跺悗鎶奱1鐨勬瘡涓暟鎹,閮借В鏋勮祴鍊煎埌杩欎釜绌虹殑瑙f瀯浣 + * ...a2鍙鍑虹幇鍑虹幇鍦ㄥ乏杈,閭d箞瀹冩渶缁堢殑缁撴灉鑲畾灏辨槸涓涓暟缁 + + + * 鍚堝苟鏁扮粍 + // ES5 + [1, 2].concat(more) + // ES6 + [1, 2, ...more] + + var arr1 = ['a', 'b']; + var arr2 = ['c']; + var arr3 = ['d', 'e']; + + // ES5鍚堝苟鏁扮粍 + arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] + + // ES6鍚堝苟鏁扮粍 + [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ] + + * 涓庤В鏋勮祴鍊肩粨鍚 + // ES5 + a = list[0], //鎶婄涓涓弬鏁拌祴鍊肩粰a + rest = list.slice(1) //浠庣浜屼釜鍙傛暟寮濮嬬殑鎵鏈夊弬鏁,璧嬪肩粰rest + + // ES6 + [a, ...rest] = list + + * 濡傛灉灏嗘墿灞曡繍绠楃鐢ㄤ簬鏁扮粍璧嬪,鍙兘鏀惧湪鍙傛暟鐨勬渶鍚庝竴浣,鍚﹀垯浼氭姤閿 + const [...butLast, last] = [1, 2, 3, 4, 5]; + // 鎶ラ敊 + + const [first, ...middle, last] = [1, 2, 3, 4, 5]; + // 鎶ラ敊 + + +-------------------------------- +Array.from() | +-------------------------------- + # Array.from鏂规硶鐢ㄤ簬灏嗕袱绫诲璞¤浆涓虹湡姝g殑鏁扮粍 + * 绫讳技鏁扮粍鐨勫璞(array-like object) + * 鎵璋撶殑绫绘暟缁勫璞,灏辨槸鏈 length 灞炴х殑瀵硅薄,濡傛灉娌℃湁璇ュ睘鎬,閭d箞杞崲瀹屾瘯鍚庡氨鏄 [] + * 鍙亶鍘(iterable)鐨勫璞(鍖呮嫭 ES6 鏂板鐨勬暟鎹粨鏋 Set 鍜 Map) + + let arrayLike = { + '0': 'a', + '1': 'b', + '2': 'c', + length: 3 + }; + + // ES5鐨勫啓娉 + //[]鐨剆ice()鏂规硶(鎴彇),鍦╝rraLike鐨勪笂涓嬫枃涓墽琛 + var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] + + // ES6鐨勫啓娉 + let arr2 = Array.from(arrayLike); // ['a', 'b', 'c'] + + # 瑙佺殑绫讳技鏁扮粍鐨勫璞℃槸 DOM 鎿嶄綔杩斿洖鐨 NodeList 闆嗗悎,浠ュ強鍑芥暟鍐呴儴鐨刟rguments瀵硅薄,Array.from閮藉彲浠ュ皢瀹冧滑杞负鐪熸鐨勬暟缁 + // NodeList瀵硅薄 + let ps = document.querySelectorAll('p'); + Array.from(ps).filter(p => { + //杞崲涓烘暟缁勫璞″悗鎵ц鍏秄ilter鏂规硶 + return p.textContent.length > 100; + }); + + // 鎶奱rguments瀵硅薄杞崲涓烘暟缁 + function foo() { + var args = Array.from(arguments); + // ... + } + + # 鍙鏄儴缃蹭簡 Iterator 鎺ュ彛鐨勬暟鎹粨鏋,Array.from閮借兘灏嗗叾杞负鏁扮粍 + Array.from('hello') + // ['h', 'e', 'l', 'l', 'o'] + + let namesSet = new Set(['a', 'b']) + Array.from(namesSet) // ['a', 'b'] + * 瀛楃涓插拰 Set 缁撴瀯閮藉叿鏈 Iterator 鎺ュ彛,鍥犳鍙互琚獳rray.from杞负鐪熸鐨勬暟缁 + + # 濡傛灉鍙傛暟鏄竴涓湡姝g殑鏁扮粍锛孉rray.from浼氳繑鍥炰竴涓竴妯′竴鏍风殑鏂版暟缁勩 + Array.from([1, 2, 3]) // [1, 2, 3] + + # 鎵╁睍杩愮畻绗 ...,涔熷彲浠ユ妸鏌愪簺鏁版嵁瑙f瀯杞崲涓烘暟缁 + // arguments瀵硅薄 + function foo() { + const args = [...arguments]; + } + + // NodeList瀵硅薄 + [...document.querySelectorAll('div')] + + * 鎵╁睍杩愮畻绗﹁儗鍚庤皟鐢ㄧ殑鏄亶鍘嗗櫒鎺ュ彛(Symbol.iterator) + * 濡傛灉涓涓璞℃病鏈夐儴缃茶繖涓帴鍙,灏辨棤娉曡浆鎹 + + # Array.from鏂规硶杩樻敮鎸佺被浼兼暟缁勭殑瀵硅薄 + * 鎵璋撶被浼兼暟缁勭殑瀵硅薄,鏈川鐗瑰緛鍙湁涓鐐,鍗冲繀椤绘湁length灞炴 + * 浠讳綍鏈塴ength灞炴х殑瀵硅薄,閮藉彲浠ラ氳繃Array.from鏂规硶杞负鏁扮粍,鑰屾鏃舵墿灞曡繍绠楃灏辨棤娉曡浆鎹 + + Array.from({ length: 3 }); + // [ undefined, undefined, undefined ] + + * 濡傛灉娴忚鍣ㄤ笉鏀寔璇ユ柟娉 Array.from,鍙互浣跨敤 Array.prototype.slice 鏂规硶浠f浛 + function toArray(obj){ + return [].slice.call(obj); + } + + * 婕旂ず,鎻愬彇鍑轰竴缁刣om鑺傜偣鐨勬枃鏈唴瀹 + let spans = document.querySelectorAll('span.name'); + // map() + let names1 = Array.prototype.map.call(spans, s => s.textContent); + // Array.from() + let names2 = Array.from(spans, s => s.textContent) + + + + # Array.from()鍙互灏嗗悇绉嶅艰浆涓虹湡姝g殑鏁扮粍,骞朵笖杩樻彁渚沵ap鍔熻兘 + * Array.from 鐨勭浜屼釜鍙傛暟杩樻敮鎸佹槸涓涓柟娉,浼氭妸鐩爣鐨勬瘡涓垚鍛橀兘鍦ㄨ鏂规硶杩涜涓娆℃秷璐瑰悗杩斿洖 + Array.from([1, , 2, , 3], (n) => n || 0 ) //鎶 bool鍊间负 false鐨勬垚鍛樿浆鎹负0 + // [1, 0, 2, 0, 3] + + function typesOf () { + //璇ユ柟娉,杩斿洖姣忕鏁版嵁鐨 type + return Array.from(arguments, value => typeof value) + } + typesOf(null, [], NaN) + // ['object', 'object', 'number'] + + * 濡傛灉map(娑堣垂)鍑芥暟閲岄潰鐢ㄥ埌浜唗his鍏抽敭瀛,杩樺彲浠ヤ紶鍏rray.from鐨勭涓変釜鍙傛暟,鐢ㄦ潵缁戝畾this + + + # Array.from()鐨勫彟涓涓簲鐢ㄦ槸,灏嗗瓧绗︿覆杞负鏁扮粍,鐒跺悗杩斿洖瀛楃涓茬殑闀垮害 + * 鍥犱负瀹冭兘姝g‘澶勭悊鍚勭 Unicode 瀛楃,鍙互閬垮厤 JavaScript 灏嗗ぇ浜嶾uFFFF鐨 Unicode 瀛楃,绠椾綔涓や釜瀛楃鐨 bug + function countSymbols(string) { + return Array.from(string).length; + } + +---------------------------- +Array.of | +---------------------------- + # Array.of鏂规硶鐢ㄤ簬灏嗕竴缁勫,杞崲涓烘暟缁 + Array.of(3, 11, 8) // [3,11,8] + Array.of(3) // [3] + Array.of(3).length // 1 + + # 瀹冨瓨鍦ㄧ殑鎰忎箟鏄负浜嗗讥琛ユ瀯閫犲嚱鏁癆rray()鐨勪笉瓒 + * 鍥犱负鍙傛暟涓暟鐨勪笉鍚,浼氬鑷碅rray()鐨勮涓烘湁宸紓 + Array() // [] + Array(3) // [, , ,] + Array(3, 11, 8) // [3, 11, 8] + + * 鍙湁褰撳弬鏁颁釜鏁颁笉灏戜簬 2 涓椂,Array()鎵嶄細杩斿洖鐢卞弬鏁扮粍鎴愮殑鏂版暟缁 + * 鍙傛暟涓暟鍙湁涓涓椂,瀹為檯涓婅鍊兼槸鎸囧畾鏁扮粍鐨勯暱搴 + + # Array.of鍩烘湰涓婂彲浠ョ敤鏉ユ浛浠rray()鎴杗ew Array(),骞朵笖涓嶅瓨鍦ㄧ敱浜庡弬鏁颁笉鍚岃屽鑷寸殑閲嶈浇,瀹冪殑琛屼负闈炲父缁熶竴 + # Array.of鎬绘槸杩斿洖鍙傛暟鍊肩粍鎴愮殑鏁扮粍,濡傛灉娌℃湁鍙傛暟锛屽氨杩斿洖涓涓┖鏁扮粍 + # Array.of鏂规硶鍙互鐢ㄤ笅闈㈢殑浠g爜妯℃嫙瀹炵幇 + function ArrayOf(){ + return [].slice.call(arguments); + } + + function ArrayOf(..args){ + return args; + } + +---------------------------- +鏁扮粍瀹炰緥鏂规硶-copyWithin() | +---------------------------- + # 鍦ㄥ綋鍓嶆暟缁勫唴閮,灏嗘寚瀹氫綅缃殑鎴愬憳澶嶅埗鍒板叾浠栦綅缃(浼氳鐩栧師鏈夋垚鍛),鐒跺悗杩斿洖褰撳墠鏁扮粍 + * 浣跨敤杩欎釜鏂规硶,浼氫慨鏀瑰綋鍓嶆暟缁 + + # 鎺ュ彈浠ㄥ弬鏁 + Array.prototype.copyWithin(target, start = 0, end = this.length) + target(蹇呴渶) 浠庤浣嶇疆寮濮嬫浛鎹㈡暟鎹,濡傛灉涓鸿礋鍊,琛ㄧず鍊掓暟 + start(鍙) 浠庤浣嶇疆寮濮嬭鍙栨暟鎹,榛樿涓 0,濡傛灉涓鸿礋鍊,琛ㄧず鍊掓暟(涓嶅寘鍚涓嬫爣) + end(鍙) 鍒拌浣嶇疆鍓嶅仠姝㈣鍙栨暟鎹,榛樿绛変簬鏁扮粍闀垮害,濡傛灉涓鸿礋鍊,琛ㄧず鍊掓暟 + + * 杩欎笁涓弬鏁伴兘搴旇鏄暟鍊,濡傛灉涓嶆槸,浼氳嚜鍔ㄨ浆涓烘暟鍊 + + [1, 2, 3, 4, 5].copyWithin(0, 3) + //浠庝笅鏍3寮濮嬪鍒朵竴鐩村埌鏈鍚 + //鐒跺悗鎶婂鍒剁殑鍐呭,浠庝笅鏍0寮濮嬫尐涓矘璐磋鐩 + + + # demos + // 灏3鍙蜂綅澶嶅埗鍒0鍙蜂綅 + [1, 2, 3, 4, 5].copyWithin(0, 3, 4) + // [4, 2, 3, 4, 5] + + // -2鐩稿綋浜3鍙蜂綅锛-1鐩稿綋浜4鍙蜂綅 + [1, 2, 3, 4, 5].copyWithin(0, -2, -1) + // [4, 2, 3, 4, 5] + + // 灏3鍙蜂綅澶嶅埗鍒0鍙蜂綅 + [].copyWithin.call({length: 5, 3: 1}, 0, 3) + // {0: 1, 3: 1, length: 5} + + // 灏2鍙蜂綅鍒版暟缁勭粨鏉燂紝澶嶅埗鍒0鍙蜂綅 + let i32a = new Int32Array([1, 2, 3, 4, 5]); + i32a.copyWithin(0, 2); + // Int32Array [3, 4, 5, 4, 5] + + // 瀵逛簬娌℃湁閮ㄧ讲 TypedArray 鐨 copyWithin 鏂规硶鐨勫钩鍙 + // 闇瑕侀噰鐢ㄤ笅闈㈢殑鍐欐硶 + [].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4); + // Int32Array [4, 2, 3, 4, 5] + + +-------------------------------- +瀹炰緥鐨 find() 鍜 findIndex() | +-------------------------------- + # find鏂规硶宸茬粡鏄叿澶囩殑,鎵惧埌杩斿洖,鎵句笉鍒拌繑鍥 undefined + + # findIndex涓巉ind鐩镐技,杩斿洖绗竴涓鍚堟潯浠剁殑鏁扮粍鎴愬憳鐨勪綅缃,濡傛灉鎵鏈夋垚鍛橀兘涓嶇鍚堟潯浠,鍒欒繑鍥-1 + * 鍙傛暟涔熸槸璺 find() 涓鏍 + + # 杩欎袱涓柟娉曢兘鍙互鎺ュ彈绗簩涓弬鏁,鐢ㄦ潵缁戝畾鍥炶皟鍑芥暟鐨則his瀵硅薄 + function f(v){ + return v > this.age; + } + + let person = {name: 'John', age: 20}; + + [10, 12, 26, 15].find(f, person); // 26 + + # 杩欎袱涓柟娉曢兘鍙互鍙戠幇NaN,寮ヨˉ浜嗘暟缁勭殑indexOf鏂规硶鐨勪笉瓒 + [NaN].indexOf(NaN) + // -1 + + [NaN].findIndex(y => Object.is(NaN, y)) + // 0 + +-------------------------------- +瀹炰緥鐨 fill() | +-------------------------------- + # fill鏂规硶浣跨敤缁欏畾鍊,濉厖婊″綋鍓嶆暟缁 + ['a', 'b', 'c'].fill(7) + // [7, 7, 7] + + new Array(3).fill(7) + // [7, 7, 7] + + # 璇ユ柟娉曡繕鎺ユ敹淇╁弬鏁,鍒嗗埆琛ㄧず濉厖鐨勮捣濮嬩綅缃拰缁撴潫浣嶇疆 + # 濉厖绫诲瀷涓哄璞,閭d箞澶嶅埗鐨勫叾瀹炴槸鍚屼竴涓唴瀛樺湴鍧,闈炴繁鎷疯礉 + new Array(5).fill({name:'Kevin'}) + * 5涓厓绱犳寚鍚戠殑鍐呭瓨鍦板潃閮芥槸涓鏍风殑 + + +-------------------------------------------------------- +鏁扮粍瀹炰緥鐨 entries(),keys() 鍜 values() | +-------------------------------------------------------- + # entries(),keys()鍜寁alues()閮芥槸鐢ㄤ簬閬嶅巻鏁扮粍 + # 瀹冧滑閮借繑鍥炰竴涓亶鍘嗗櫒瀵硅薄,鍙互鐢╢or...of寰幆杩涜閬嶅巻 + # 鏁板簭Map鏁版嵁瑙f瀯鐨勫氨搴旇涓嶉檶鐢 + # keys()鏄閿悕鐨勯亶鍘,values()鏄閿肩殑閬嶅巻,entries()鏄閿煎鐨勯亶鍘 + for (let index of ['a', 'b'].keys()) { + console.log(index); + } + // 0 + // 1 + + for (let elem of ['a', 'b'].values()) { + console.log(elem); + } + // 'a' + // 'b' + + for (let [index, elem] of ['a', 'b'].entries()) { + //瑙f瀯璧嬪煎憿 + console.log(index, elem); + } + // 0 "a" + // 1 "b" + + # 涔熷彲浠ヤ娇鐢ㄨ凯浠e櫒鐨 next() 杩涜閬嶅巻 + let letter = ['a', 'b', 'c']; + + let entries = letter.entries(); + + console.log(entries.next().value); // [0, 'a'] + console.log(entries.next().value); // [1, 'b'] + console.log(entries.next().value); // [2, 'c'] + +------------------------------ +鏁扮粍瀹炰緥鐨 includes() | +------------------------------ + # Array.prototype.includes鏂规硶杩斿洖涓涓竷灏斿,琛ㄧず鏌愪釜鏁扮粍鏄惁鍖呭惈缁欏畾鐨勫 + # 涓庡瓧绗︿覆鐨刬ncludes鏂规硶绫讳技 + [1, 2, 3].includes(2) // true + [1, 2, 3].includes(4) // false + [1, 2, NaN].includes(NaN) // true + + # 鐨勭浜屼釜鍙傛暟琛ㄧず鎼滅储鐨勮捣濮嬩綅缃,榛樿涓0 + * 濡傛灉绗簩涓弬鏁颁负璐熸暟,鍒欒〃绀哄掓暟鐨勪綅缃 + * 濡傛灉杩欐椂瀹冨ぇ浜庢暟缁勯暱搴(姣斿绗簩涓弬鏁颁负-4,浣嗘暟缁勯暱搴︿负3),鍒欎細閲嶇疆涓轰粠0寮濮 + + [1, 2, 3].includes(3, 3); // false + [1, 2, 3].includes(3, -1); // true + + # 娌℃湁璇ユ柟娉曚箣鍓,鎴戜滑閫氬父浣跨敤鏁扮粍鐨刬ndexOf鏂规硶,妫鏌ユ槸鍚﹀寘鍚煇涓 + if (arr.indexOf(el) !== -1) { + // ... + } + * 杩欐柟娉曟湁淇╃己鐐 + 1,涓嶅璇箟鍖,瀹冪殑鍚箟鏄壘鍒板弬鏁板肩殑绗竴涓嚭鐜颁綅缃墍浠ヨ鍘绘瘮杈冩槸鍚︿笉绛変簬-1,琛ㄨ揪璧锋潵涓嶅鐩磋 + 2,瀹冨唴閮ㄤ娇鐢ㄤ弗鏍肩浉绛夎繍绠楃(===)杩涜鍒ゆ柇,杩欎細瀵艰嚧瀵筃aN鐨勮鍒 + + [NaN].indexOf(NaN) + // -1 + + * includes浣跨敤鐨勬槸涓嶄竴鏍风殑鍒ゆ柇绠楁硶,灏辨病鏈夎繖涓棶棰 + [NaN].includes(NaN) + // true + + # 鍙﹀,Map 鍜 Set 鏁版嵁缁撴瀯鏈変竴涓猦as鏂规硶,闇瑕佹敞鎰忎笌includes鍖哄垎 + * Map 缁撴瀯鐨刪as鏂规硶,鏄敤鏉ユ煡鎵鹃敭鍚嶇殑 + Map.prototype.has(key)銆乄eakMap.prototype.has(key) + Reflect.has(target, propertyKey) + * Set 缁撴瀯鐨刪as鏂规硶,鏄敤鏉ユ煡鎵惧肩殑 + Set.prototype.has(value) + WeakSet.prototype.has(value) + +------------------------ +鏁扮粍绌轰綅 | +------------------------ + # 鏁扮粍鐨勭┖浣嶆寚,鏁扮粍鐨勬煇涓涓綅缃病鏈変换浣曞 + * Array鏋勯犲嚱鏁拌繑鍥炵殑鏁扮粍閮芥槸绌轰綅 + Array(3) // [, , ,] + + # 绌轰綅涓嶆槸undefined,涓涓綅缃殑鍊肩瓑浜巙ndefined,渚濈劧鏄湁鍊肩殑,绌轰綅鏄病鏈変换浣曞,in杩愮畻绗﹀彲浠ヨ鏄庤繖涓鐐 + 0 in [undefined, undefined, undefined] // true + 0 in [, , ,] // false + + # ES5 瀵圭┖浣嶇殑澶勭悊,宸茬粡寰堜笉涓鑷翠簡,澶у鏁版儏鍐典笅浼氬拷鐣ョ┖浣 + forEach(),filter(), reduce(), every() 鍜宻ome()閮戒細璺宠繃绌轰綅 + map()浼氳烦杩囩┖浣,浣嗕細淇濈暀杩欎釜鍊 + join()鍜宼oString()浼氬皢绌轰綅瑙嗕负undefined鑰寀ndefined鍜宯ull浼氳澶勭悊鎴愮┖瀛楃涓 + + // forEach鏂规硶 + [,'a'].forEach((x,i) => console.log(i)); // 1 + + // filter鏂规硶 + ['a',,'b'].filter(x => true) // ['a','b'] + + // every鏂规硶 + [,'a'].every(x => x==='a') // true + + // reduce鏂规硶 + [1,,2].reduce((x,y) => return x+y) // 3 + + // some鏂规硶 + [,'a'].some(x => x !== 'a') // false + + // map鏂规硶 + [,'a'].map(x => 1) // [,1] + + // join鏂规硶 + [,'a',undefined,null].join('#') // "#a##" + + // toString鏂规硶 + [,'a',undefined,null].toString() // ",a,," + + + # ES6 鍒欐槸鏄庣‘灏嗙┖浣嶈浆涓簎ndefined + * Array.from鏂规硶浼氬皢鏁扮粍鐨勭┖浣,杞负undefined,涔熷氨鏄,杩欎釜鏂规硶涓嶄細蹇界暐绌轰綅 + Array.from(['a',,'b']) + // [ "a", undefined, "b" ] + + * 鎵╁睍杩愮畻绗(...)涔熶細灏嗙┖浣嶈浆涓簎ndefined + [...['a',,'b']] + // [ "a", undefined, "b" ] + + * copyWithin()浼氳繛绌轰綅涓璧锋嫹璐 + [,'a','b',,].copyWithin(2,0) // [,"a",,"a"] + + * fill()浼氬皢绌轰綅瑙嗕负姝e父鐨勬暟缁勪綅缃 + new Array(3).fill('a') // ["a","a","a"] + + * for...of寰幆涔熶細閬嶅巻绌轰綅 + let arr = [, ,]; + for (let i of arr) { + console.log(1); + } + // 1 + // 1 + + * 鏁扮粍arr鏈変袱涓┖浣,for...of骞舵病鏈夊拷鐣ュ畠浠 + * 濡傛灉鏀规垚map鏂规硶閬嶅巻,绌轰綅鏄細璺宠繃鐨 + + * entries(),keys(),values(),find()鍜宖indIndex()浼氬皢绌轰綅澶勭悊鎴恥ndefined + // entries() + [...[,'a'].entries()] // [[0,undefined], [1,"a"]] + + // keys() + [...[,'a'].keys()] // [0,1] + + // values() + [...[,'a'].values()] // [undefined,"a"] + + // find() + [,'a'].find(x => true) // undefined + + // findIndex() + [,'a'].findIndex(x => true) // 0 + + + # '鐢变簬绌轰綅鐨勫鐞嗚鍒欓潪甯镐笉缁熶竴,鎵浠ュ缓璁伩鍏嶅嚭鐜扮┖浣' diff --git "a/JavaSript/ES6/es6-\346\211\251\345\261\225-\346\255\243\345\210\231.js" "b/JavaSript/ES6/es6-\346\211\251\345\261\225-\346\255\243\345\210\231.js" new file mode 100644 index 00000000..2813e65d --- /dev/null +++ "b/JavaSript/ES6/es6-\346\211\251\345\261\225-\346\255\243\345\210\231.js" @@ -0,0 +1,165 @@ +---------------------------- +姝e垯 | +---------------------------- + 1,RegExp 鏋勯犲嚱鏁 + 2,瀛楃涓茬殑姝e垯鏂规硶 + 3,u 淇グ绗 + 5,y 淇グ绗 + 6,sticky 灞炴 + 7,flags 灞炴 + 8,s 淇グ绗︼細dotAll 妯″紡 + 9,鍚庤鏂█ + 10,Unicode 灞炴х被 + 11,鍏峰悕缁勫尮閰 + 12,String.prototype.matchAll + + +---------------------------- +RegExp 鏋勯犲嚱鏁 | +---------------------------- + # ES5 涓,RegExp鏋勯犲嚱鏁扮殑鍙傛暟鏈変袱绉嶆儏鍐点 + * 绗竴绉嶆儏鍐垫槸鍙傛暟鏄瓧绗︿覆,杩欐椂绗簩涓弬鏁拌〃绀烘鍒欒〃杈惧紡鐨勪慨楗扮(flag) + var regex = new RegExp('xyz', 'i'); + //绛変环浜 + var regex = /xyz/i; + + # 绗簩绉嶆儏鍐垫槸,鍙傛暟鏄竴涓鍒欒〃绀哄紡,杩欐椂浼氳繑鍥炰竴涓師鏈夋鍒欒〃杈惧紡鐨勬嫹璐 + var regex = new RegExp(/xyz/i); + // 绛変环浜 + var regex = /xyz/i; + + # ES5 涓嶅厑璁告鏃朵娇鐢ㄧ浜屼釜鍙傛暟娣诲姞淇グ绗,鍚﹀垯浼氭姤閿 + var regex = new RegExp(/xyz/, 'i'); + // Uncaught TypeError: Cannot supply flags when constructing one RegExp from another + + # ES6 鏀瑰彉浜嗚繖绉嶈涓,濡傛灉RegExp鏋勯犲嚱鏁扮涓涓弬鏁版槸涓涓鍒欏璞,閭d箞鍙互浣跨敤绗簩涓弬鏁版寚瀹氫慨楗扮 + 鑰屼笖,杩斿洖鐨勬鍒欒〃杈惧紡浼氬拷鐣ュ師鏈夌殑姝e垯琛ㄨ揪寮忕殑淇グ绗,鍙娇鐢ㄦ柊鎸囧畾鐨勪慨楗扮 + new RegExp(/abc/ig, 'i').flags + // "i" + + * 鍘熸湁姝e垯瀵硅薄鐨勪慨楗扮鏄痠g,瀹冧細琚浜屼釜鍙傛暟i瑕嗙洊 + +---------------------------- +瀛楃涓茬殑姝e垯鏂规硶 | +---------------------------- + # 瀛楃涓插璞″叡鏈 4 涓柟娉,鍙互浣跨敤姝e垯琛ㄨ揪寮 + match() + replace() + search() + split() + + # ES6 灏嗚繖 4 涓柟娉,鍦ㄨ瑷鍐呴儴鍏ㄩ儴璋冪敤RegExp鐨勫疄渚嬫柟娉,浠庤屽仛鍒版墍鏈変笌姝e垯鐩稿叧鐨勬柟娉,鍏ㄩ兘瀹氫箟鍦≧egExp瀵硅薄涓 + + String.prototype.match 璋冪敤 RegExp.prototype[Symbol.match] + String.prototype.replace 璋冪敤 RegExp.prototype[Symbol.replace] + String.prototype.search 璋冪敤 RegExp.prototype[Symbol.search] + String.prototype.split 璋冪敤 RegExp.prototype[Symbol.split] + +---------------------------- +u淇グ绗 | +---------------------------- + # ES6 瀵规鍒欒〃杈惧紡娣诲姞浜唘淇グ绗,鍚箟涓"Unicode 妯″紡",鐢ㄦ潵姝g‘澶勭悊澶т簬\uFFFF鐨 Unicode 瀛楃 + # 涔熷氨鏄,浼氭纭鐞嗗洓涓瓧鑺傜殑 UTF-16 缂栫爜 + /^\uD83D/u.test('\uD83D\uDC2A') // false + /^\uD83D/.test('\uD83D\uDC2A') // true + + +---------------------------- +鍏峰悕缁勫尮閰 | +---------------------------- + # 姝e垯琛ㄨ揪寮忛噷闈㈡湁涓夌粍鍦嗘嫭鍙,浣跨敤exec鏂规硶,灏卞彲浠ュ皢杩欎笁缁勫尮閰嶇粨鏋滄彁鍙栧嚭鏉 + const RE_DATE = new RegExp('^(\\d{4})-(\\d{2})-(\\d{2})$') + + const matcher = RE_DATE.exec('1999-12-31'); + + //["1999-12-31", "1999", "12", "31", index: 0, input: "1999-12-31"] + + # ES2018 寮曞叆浜嗗叿鍚嶇粍鍖归厤(Named Capture Groups),鍏佽涓烘瘡涓涓粍鍖归厤鎸囧畾涓涓悕瀛.鏃究浜庨槄璇讳唬鐮,鍙堜究浜庡紩鐢 + + const RE_DATE = new RegExp('^(?\\d{4})-(?\\d{2})-(?\\d{2})$') + const matcher = RE_DATE.exec('1999-12-31'); + + console.log(matcher); //["1999-12-31", "1999", "12", "31", index: 0, input: "1999-12-31", groups: {鈥] + console.log(matcher.groups.year); //1999 + console.log(matcher.groups.month); //12 + console.log(matcher.groups.day); //31 + + * 濡傛灉鍏峰悕缁勬病鏈夊尮閰,閭d箞瀵瑰簲鐨刧roups瀵硅薄灞炴т細鏄痷ndefined + +---------------------------- +瑙f瀯璧嬪煎拰鏇挎崲 | +---------------------------- + # 鏈変簡鍏峰悕缁勫尮閰嶄互鍚,鍙互浣跨敤瑙f瀯璧嬪肩洿鎺ヤ粠鍖归厤缁撴灉涓婁负鍙橀噺璧嬪 + const RE_DATE = new RegExp('^(?\\d{4})-(?\\d{2})-(?\\d{2})$') + const matcher = RE_DATE.exec('1999-12-31'); + let {groups:{year:y,month:m,day:d}} = matcher + console.log(y) //1999 + console.log(m) //12 + console.log(d) //31 + + # 瀛楃涓叉浛鎹㈡椂,浣跨敤$<缁勫悕>寮曠敤鍏峰悕 + let re = /(?\d{4})-(?\d{2})-(?\d{2})/u; + + '2015-01-02'.replace(re, '$/$/$') + + * replace鏂规硶鐨勭浜屼釜鍙傛暟鏄竴涓瓧绗︿覆,鑰屼笉鏄鍒欒〃杈惧紡 + * replace鏂规硶鐨勭浜屼釜鍙傛暟涔熷彲浠ユ槸鍑芥暟,璇ュ嚱鏁扮殑鍙傛暟搴忓垪濡備笅 + '2015-01-02'.replace(re, ( + matched, // 鏁翠釜鍖归厤缁撴灉 2015-01-02 + capture1, // 绗竴涓粍鍖归厤 2015 + capture2, // 绗簩涓粍鍖归厤 01 + capture3, // 绗笁涓粍鍖归厤 02 + position, // 鍖归厤寮濮嬬殑浣嶇疆 0 + S, // 鍘熷瓧绗︿覆 2015-01-02 + groups // 鍏峰悕缁勬瀯鎴愮殑涓涓璞 {year, month, day} + ) => { + let {day, month, year} = args[args.length - 1]; + return `${day}/${month}/${year}`; + }); + + * 鍏峰悕缁勫尮閰嶅湪鍘熸潵鐨勫熀纭涓,鏂板浜嗘渶鍚庝竴涓嚱鏁板弬鏁:鍏峰悕缁勬瀯鎴愮殑涓涓璞,鍑芥暟鍐呴儴鍙互鐩存帴瀵硅繖涓璞¤繘琛岃В鏋勮祴鍊 + + +---------------------------- +String.prototype.matchAll | +---------------------------- + # 濡傛灉涓涓鍒欒〃杈惧紡鍦ㄥ瓧绗︿覆閲岄潰鏈夊涓尮閰,鐜板湪涓鑸娇鐢╣淇グ绗︽垨y淇グ绗,鍦ㄥ惊鐜噷闈㈤愪竴鍙栧嚭 + var regex = /t(e)(st(\d?))/g; + var string = 'test1test2test3'; + + var matches = []; + var match; + while (match = regex.exec(string)) { + matches.push(match); + } + + matches + // [ + // ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"], + // ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"], + // ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"] + // ] + + # 鐩墠鏈変竴涓彁妗,澧炲姞浜哠tring.prototype.matchAll鏂规硶 + # 鍙互涓娆℃у彇鍑烘墍鏈夊尮閰,涓嶈繃,瀹冭繑鍥炵殑鏄竴涓亶鍘嗗櫒(Iterator),鑰屼笉鏄暟缁 + + const string = 'test1test2test3'; + + // g 淇グ绗﹀姞涓嶅姞閮藉彲浠 + const regex = /t(e)(st(\d?))/g; + + for (const match of string.matchAll(regex)) { + console.log(match); + } + // ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"] + // ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"] + // ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"] + + * 杩唬鍣ㄦ瘮杈冪渷璧勬簮 + * 閬嶅巻鍣ㄨ浆涓烘暟缁勬槸闈炲父绠鍗曠殑,浣跨敤...杩愮畻绗﹀拰Array.from鏂规硶灏卞彲浠ヤ簡銆 + + // 杞负鏁扮粍鏂规硶涓 + [...string.matchAll(regex)] + + // 杞负鏁扮粍鏂规硶浜 + Array.from(string.matchAll(regex)); \ No newline at end of file diff --git a/JavaSript/ES6/es6.js b/JavaSript/ES6/es6.js deleted file mode 100644 index 0c562a0d..00000000 --- a/JavaSript/ES6/es6.js +++ /dev/null @@ -1,5 +0,0 @@ -1,新增关键字 let -2,新增关键字 const -3,解构赋值 -4,字符串的扩展 - diff --git a/JavaSript/javascript-ServiceWorker.js b/JavaSript/javascript-ServiceWorker.js new file mode 100644 index 00000000..36910e1f --- /dev/null +++ b/JavaSript/javascript-ServiceWorker.js @@ -0,0 +1 @@ +//立个flag,以后学 \ No newline at end of file diff --git a/JavaSript/javascript-Worker.js b/JavaSript/javascript-Worker.js new file mode 100644 index 00000000..5fbdc29f --- /dev/null +++ b/JavaSript/javascript-Worker.js @@ -0,0 +1,50 @@ +-------------------------------- +Worker | +-------------------------------- + # Hello World + let worker = new Worker('./foo.js'); + worker.onmessage = function(event){ + let data = event.data; + console.log(data); + } + + ---- foo.js + let x = 0; + + for(let i = 0 ;i < 10000; i++){ + x +=i ; + } + + postMessage(x); + + # Worker绾跨▼涓厑璁镐娇鐢ㄧ殑API + * 閮ㄥ垎api涓嶅厑璁稿湪worker绾跨▼涓娇鐢,鍖呮嫭alert绛夌瓑... + + +-------------------------------- +Worker 瀹炰緥鏂规硶 | +-------------------------------- + void postMessage(JSObject message); + * 鍙戦佹墽琛屽畬姣曠殑鏁版嵁,浠绘剰鍊 + + void terminate(); + * 鍋滄worker瀵硅薄 + +-------------------------------- +Worker 瀹炰緥瀵硅薄 | +-------------------------------- + + onmessage + * 涓涓簨浠剁洃鍚嚱鏁,姣忓綋鎷ユ湁 message 灞炴х殑 MessageEvent 浠 worker 涓啋娉″嚭鏉ユ椂灏变細鎵ц璇ュ嚱鏁,浜嬩欢鐨 data 灞炴у瓨鏈夋秷鎭唴瀹 + + onerror + * 涓涓簨浠剁洃鍚嚱鏁,姣忓綋绫诲瀷涓 error 鐨 ErrorEvent 浠 worker 涓啋娉″嚭鏉ユ椂灏变細鎵ц璇ュ嚱鏁 + + * Event瀵硅薄鍖呭惈涓夊睘鎬 + data + * 绾跨▼鎵ц鐨勭粨鏋 + message + * 涓涓彲璇绘ц壇濂界殑閿欒淇℃伅 + filename + * 浜х敓閿欒鐨勮剼鏈枃浠跺悕 + lineno \ No newline at end of file diff --git a/JavaSript/javascript-fetch.js b/JavaSript/javascript-fetch.js new file mode 100644 index 00000000..e7c902b8 --- /dev/null +++ b/JavaSript/javascript-fetch.js @@ -0,0 +1,277 @@ +-------------------- +fetch | +-------------------- + # 璇硶 + fetch(url,options) + + # options + credentials + * 榛樿浠ㄥ + "omit", //涓嶅彂閫 + "same-origin" //鍚屽煙鍙戦 + "include" //鎬绘槸鍙戦 + * Fetch 璇锋眰榛樿鏄笉甯 cookie 鐨,闇瑕佽缃 credentials: 'include' + * fetch(url, {credentials: 'include'}) + + body + * 璇锋眰浣撴暟鎹,鍙互鏄疐ormData,URLSearchParams鎴栬呭瓧绗︿覆 + * 濡傛灉鏄疓ET璇锋眰,涓嶅厑璁告湁璇ュ弬鏁 + + method + * 浠ュ瓧绗︿覆褰㈠紡鎸囧畾璇锋眰鏂规硶 + + headers + * 瀵硅薄,璇锋眰澶 + + mode + * 鍏充簬璺ㄥ煙鐨勪竴浜涢厤缃 + "same-origin" + * 鍙湁鏉ヨ嚜鍚屽煙鐨勮姹傛墠鑳芥垚鍔,鍏跺畠鐨勫潎灏嗚鎷掔粷 + "cors" + * 鍏佽涓嶅悓鍩熺殑璇锋眰,浣嗚姹傛湁姝g‘鐨 CORS 澶翠俊鎭 + "cors-with-forced-preflight" + * 鍦ㄦ墽琛岀湡姝g殑璋冪敤鍓嶅厛鎵цpreflight check + "no-cors" + * 鐩墠杩欑妯″紡鏄棤娉曟墽琛岀殑銆 + + cache + * 琛ㄧず澶勭悊缂撳瓨鐨勭瓥鐣 + + redirect + * 琛ㄧず鍙戠敓閲嶅畾鍚戞椂,鏈変笁涓夐」 + follow 璺熼殢 + error: 鍙戠敓閿欒 + manual 闇瑕佺敤鎴锋墜鍔ㄨ窡闅 + + integrity + * 鍖呭惈涓涓敤浜庨獙璇佽祫璧勬簮瀹屾暣鎬х殑瀛楃涓 + + # 杩斿洖瀵硅薄鏄 Promise 瀵硅薄 + fetch('/login') instanceof Promise + + # 鏈嶅姟鍣ㄨ繑鍥 400,500 閿欒鐮佹椂骞朵笉浼 reject,鍙湁缃戠粶閿欒杩欎簺瀵艰嚧璇锋眰涓嶈兘瀹屾垚鏃,fetch 鎵嶄細琚 reject + +-------------------- +Headers | +-------------------- + # Headers鎺ュ彛鏄竴涓畝鍗曠殑澶氭槧灏勭殑鍚-鍊艰〃 + let reqHeaders = new Headers(); + reqHeaders.append("Content-Type", "text/plain"); + reqHeaders.append("X-Custom-Header", "ProcessThisImmediately"); + + # 涔熷彲浠ラ氳繃鏋勯犲嚱鏁颁紶涓涓缁存暟缁勬垨鑰卝son鏉ュ垱寤 + reqHeaders = new Headers({ + "Content-Type": "text/plain", + "Content-Length": content.length.toString(), + "X-Custom-Header": "ProcessThisImmediately", + }); + + # Headers瀵硅薄鏈変竴涓壒娈婄殑 guard 灞炴 + * 杩欎釜灞炴ф病鏈夋毚闇茬粰Web,浣嗘槸瀹冨奖鍝嶅埌鍝簺鍐呭鍙互鍦℉eaders瀵硅薄涓鏀瑰彉 + "none" 榛樿鐨 + "request" 浠嶳equest涓幏寰楃殑Headers鍙 + "request-no-cors" 浠庝笉鍚屽煙鐨凴equest涓幏寰楃殑Headers鍙 + "response" 浠嶳esponse鑾峰緱鐨凥eaders鍙 + "immutable" 鍦⊿erviceWorkers涓渶甯哥敤鐨,m鎵鏈夌殑Headers閮藉彧璇 + + + # Headers瀹炰緥鐨刟pi + has(key) + * 鍒ゆ柇鎸囧畾鐨勮姹傚ご鏄惁瀛樺湪 + + set(key,value) + * 璁剧疆鏂扮殑璇锋眰澶,浼氳鐩栨帀鍚屽悕鐨 + + append(key,value) + * 璁剧疆鏂扮殑璇锋眰澶,濡傛灉瀛樺湪鍚屽悕,涓嶄細瑕嗙洊,浼氭坊鍔 + + get(key) + * 鑾峰彇鎸囧畾鍚嶇О璇锋眰澶寸殑鍊 + + getAll(key) + * 鑾峰彇鎸囧畾鍚嶇О璇锋眰澶寸殑鎵鏈夊,杩斿洖[] + + delete(key) + * 鍒犻櫎鎸囧畾鐨勮姹傚ご + +-------------------- +Request | +-------------------- + # Request鎺ュ彛瀹氫箟浜嗛氳繃HTTP璇锋眰璧勬簮鐨剅equest鏍煎紡 + * 璇硶 + Request(url,options) + * 鍙傛暟闇瑕 + URL + method + headers + + * 鍚屾椂Request涔熸帴鍙椾竴涓壒瀹氱殑 body,mode,credentials浠ュ強cache hints + + var req = new Request("/index.html"); + console.log(req.method); // "GET" + console.log(req.url); // "http://example.com/index.html" + + # 涔熷彲浠ュ皢涓涓缓濂界殑Request瀵硅薄浼犵粰鏋勯犲嚱鏁,杩欐牱灏嗗鍒跺嚭涓涓柊鐨凴equest + var copy = new Request(req); + console.log(copy.method); // "GET" + console.log(copy.url); // "http://example.com/index.html" + + # URL浠ュ鐨勫叾浠栧睘鎬х殑鍒濆鍊艰兘澶熼氳繃绗簩涓弬鏁颁紶缁橰equest鏋勯犲嚱鏁 + var uploadReq = new Request("/uploadImage", { + method: "POST", + headers: { + "Content-Type": "image/png", + }, + body: "image data" + }); + + # options 鍙傛暟(璺焒etch鐨刼ptions涓鏍) + credentials + * 榛樿浠ㄥ + "omit", + "same-origin" + "include" + * Fetch 璇锋眰榛樿鏄笉甯 cookie 鐨,闇瑕佽缃 credentials: 'include' + * fetch(url, {credentials: 'include'}) + method + * 璇锋眰鏂规硶 + headers + * 璇锋眰澶,瀵硅薄 + body + * 璇锋眰浣,鍙互鏄瓧绗︿覆,formData,URLSearchParams + mode + * 鍏充簬璺ㄥ煙鐨勪竴浜涢厤缃 + "same-origin" + * 鍙湁鏉ヨ嚜鍚屽煙鐨勮姹傛墠鑳芥垚鍔,鍏跺畠鐨勫潎灏嗚鎷掔粷 + "cors" + * 鍏佽涓嶅悓鍩熺殑璇锋眰,浣嗚姹傛湁姝g‘鐨 CORS 澶翠俊鎭 + "cors-with-forced-preflight" + * 鍦ㄦ墽琛岀湡姝g殑璋冪敤鍓嶅厛鎵цpreflight check + "no-cors" + * 鐩墠杩欑妯″紡鏄棤娉曟墽琛岀殑銆 + + # 灞炴 + method + url + + # 鏋勯犲嚱鏁版帴鍙楃殑鍙傛暟涓巉etch鏂规硶涓鑷达紝杩欓噷灏变笉灞曞紑浠嬬粛浜 + # 鍙互杩欎箞鐞嗚В锛屼簨瀹炰笂fetch鏂规硶鍦ㄨ皟鐢ㄦ椂锛屼細灏嗕紶鍏ョ殑鍙傛暟鏋勯犲嚭涓涓 Request 瀵硅薄骞舵墽琛屻 +-------------------- +Response | +-------------------- + # Response鏈変釜鎺ユ敹涓や釜鍙夊弬鏁扮殑鏋勯犲櫒,绗竴涓弬鏁版槸杩斿洖鐨刡ody,绗簩涓弬鏁版槸涓涓猨son,璁剧疆status銆乻tatusText浠ュ強headers + # 闈欐佹柟娉昍esponse.error()绠鍗曡繑鍥炰竴涓敊璇殑璇锋眰,绫讳技鐨勶紝Response.redirect(url, status)杩斿洖涓涓烦杞琔RL鐨勮姹 + # 灞炴 + ok + * 鏄惁鎴愬姛,鏍规嵁http鐘舵佺爜鍐冲畾 + bodyUsed + * 鏄惁宸茬粡浣跨敤杩 + headers + * 杩斿洖 Headers 瀹炰緥瀵硅薄 + redirected + status + statusText + type + * 绫诲瀷,鏋氫妇瀛楃涓插 + "basic" 姝e父鐨,鍚屽煙鐨勮姹,鍖呭惈鎵鏈夌殑headers闄ゅ紑"Set-Cookie"鍜"Set-Cookie2" + "cors" Response浠庝竴涓悎娉曠殑璺ㄥ煙璇锋眰鑾峰緱,涓閮ㄥ垎header鍜宐ody鍙銆 + "error" 缃戠粶閿欒:Response鐨剆tatus鏄0,Headers鏄┖鐨勫苟涓斾笉鍙啓,褰揜esponse鏄粠Response.error()涓緱鍒版椂,灏辨槸杩欑绫诲瀷 + "opaque" Response浠"no-cors"璇锋眰浜嗚法鍩熻祫婧,渚濋潬Server绔潵鍋氶檺鍒躲 + "error" 绫诲瀷浼氬鑷磃etch()鍑芥暟鐨凱romise琚玶eject骞跺洖璋冨嚭涓涓猅ypeError + url + + + # 鏂规硶 + arrayBuffer() + blob() + json() + text() + formData() + + * 瀵瑰搷搴旂粨鏋滅殑澶勭悊,浠ヤ笂閮借繑鍥 Promise 瀵硅薄 + +-------------------- +URLSearchParams | +-------------------- + # 琛ㄥ崟鍙傛暟鏋勯犲櫒 + let u = new URLSearchParams(); + u.append('method', 'flickr.interestingness.getList'); + u.append('api_key', ''); + u.append('format', 'json'); + u.append('nojsoncallback', '1'); + + u.toString(); + //"method=flickr.interestingness.getList&api_key=%3Cinsert+api+key+here%3E&format=json&nojsoncallback=1" + + # api + append(k,v) + * 娣诲姞kv,涓嶄細瑕嗙洊,宸茬粡瀛樺湪,鍒欏娣诲姞涓涓敭鍊煎 + + set(k,v) + * 娣诲姞kv,鍚屽悕灞炴т細鍙戠敓瑕嗙洊 + + toString() + * 杩斿洖瀛楃涓,灏辨槸缁忚繃浜嗙紪鐮佸悗鐨勮〃鍗曚綋 + + # 瀹炰緥瀵硅薄鍙互鐩存帴浣滀负 fetch鐨刼ptions鐨刡ody灞炴,鎴栬匯equest瀹炰緥鐨刼ptions鐨刡ody灞炴 + * 浼氳嚜鍔ㄦ坊鍔 ContentType澶 + +-------------------- +娴佸拰鍏嬮殕 | +-------------------- + # Request鍜孯esponse鐨刡ody鍙兘琚鍙栦竴娆,瀹冧滑鏈変竴涓睘鎬у彨bodyUsed璇诲彇涓娆′箣鍚庤缃负true,灏变笉鑳藉啀璇诲彇浜 + var res = new Response("one time use"); + + console.log(res.bodyUsed); // false + + res.text().then(function(v) { + console.log(res.bodyUsed); // true + }); + + console.log(res.bodyUsed); // true + + res.text().catch(function(e) { + console.log("Tried to read already consumed Response"); + }); + + # 璁゜ody鑳界粡寰楄捣澶氭璇诲彇,API鎻愪緵浜嗕竴涓猚lone()鏂规硶 + * 璋冪敤杩欎釜鏂规硶鍙互寰楀埌涓涓厠闅嗗璞 + * 涓嶈繃瑕佽寰,clone()蹇呴』瑕佸湪璇诲彇涔嬪墠璋冪敤,涔熷氨鏄厛clone()鍐嶈鍙 + + addEventListener('fetch', function(evt) { + var sheep = new Response("Dolly"); + console.log(sheep.bodyUsed); // false + var clone = sheep.clone(); + console.log(clone.bodyUsed); // false + + clone.text(); + console.log(sheep.bodyUsed); // false + console.log(clone.bodyUsed); // true + + evt.respondWith(cache.add(sheep.clone()).then(function(e) { + return sheep; + }); + }); + +-------------------- +Demo | +-------------------- + let requestBody = new URLSearchParams(); + requestBody.set('account','KevinBlandy'); + requestBody.set('password','123456'); + + fetch(new Request('/login',{ + method:'POST', + headers:new Headers({ + 'X-Requested-With':'XMLHttpRequest' + }), + body:requestBody + })).then(function(response){ + if(response.ok){ + response.json().then(function(rep){ + console.log(rep); + }); + } + }).catch(function(error){ + console.log(error); + }); \ No newline at end of file diff --git a/JavaSript/javascript-indexedDB.js b/JavaSript/javascript-indexedDB.js new file mode 100644 index 00000000..91b359ce --- /dev/null +++ b/JavaSript/javascript-indexedDB.js @@ -0,0 +1,142 @@ + + +------------------------ +indexedDB | +------------------------ + # 创建db + window.indexedDB.open(dbName,version); + + # 创建index + let objectStore = db.createObjectStore(objectStoreName,options); + + # 创建事务 + let transaction = db.transaction(objectStoreName,enum); + let objectStore = transaction.objectStore(objectStoreName); + + # CRUD + objectStore.add(item); + objectStore.get(id); + objectStore.put(row); + objectStore.delete(id); + + # 遍历 + objectStore.openCursor(); + + # range + ... + +------------------------ +indexedDB-demo | +------------------------ + //db连接 + let db = null; + //db名称 + let dbName = 'database'; + //index名称 + let objectStoreName = 'users'; + + //打开链接 + let opener = window.indexedDB.open(dbName,1); + opener.onsuccess = function(event){ + db = event.target.result; + } + + //在第一次创建db或者是版本号有修改的时候会执行 + opener.onupgradeneeded = function(event){ + let db = event.target.result; + + //创建数据库存储对象 + let objectStore = db.createObjectStore(objectStoreName,{ + keyPath:'id', + autoIncrement:true + }); + + //定义存储对象的数据项 + objectStore.createIndex('id','id',{ + //唯一约束 + unique:true + }); + objectStore.createIndex('name','name'); + objectStore.createIndex('age','age'); + } + + function create(db,item){ + //创建事务 + let transaction = db.transaction(objectStoreName,'readwrite');//readonly readwrite versionchange + //通过事务,打开存储对象 + let objectStore = transaction.objectStore(objectStoreName); + //插入记录 + objectStore.add(item); + } + + function update(db,id){ + let transaction = db.transaction(objectStoreName,'readwrite'); + let objectStore = transaction.objectStore(objectStoreName); + //根据id检索记录,返回游标 + let cursor = objectStore.get(id); + cursor.onsuccess = function(event){ + //成功检索结果集,如果检索失败,返回 undefined + let row = event.target.result; + //修改记录值 + row.name = 'new name'; + //使put更新记录 + objectStore.put(row); + } + } + + function remove(db,id){ + let transaction = db.transaction(objectStoreName,'readwrite'); + let objectStore = transaction.objectStore(objectStoreName); + //通过id删除记录 + objectStore.delete(id); + } + + function foreach(db){ + let transaction = db.transaction(objectStoreName,'readwrite'); + let objectStore = transaction.objectStore(objectStoreName); + let cursor = objectStore.openCursor(); + cursor.onsuccess = function(event){ + let cursor = event.target.result; + if(cursor){ + let value = cursor.value; + //遍历到的值 + console.log(value); + cursor.continue();//继续遍历 + }else{ + //遍历完毕 + } + } + } + + + function rangeEach(db,start,end){ + let transaction = db.transaction(objectStoreName,'readwrite'); + let objectStore = transaction.objectStore(objectStoreName); + /* + bound(start,end,lowerOpen,upperOpen); + id范围内,后面俩bool参数可以设置是否包含开始和结束(默认都为true) + only(id); + 仅仅是指定的id + lowerBound(id); + 小余参数的id + upperBound(id); + 大于参数的id + */ + //创建范api,表示仅仅检索id start - end 之间的数据 + let range = IDBKeyRange.bound(start,end); + //判断range检索的范围,是否包含了id = 5的记录,返回 bool + let include = range.includes(5); + //通过范围api创建游标 + let cursor = objectStore.openCursor(range); + cursor.onsuccess = function(event){ + let cursor = event.target.result; + if(cursor){ + let value = cursor.value; + //遍历到的值 + console.log(value); + cursor.continue();//继续遍历 + }else{ + //遍历完毕 + } + } + } \ No newline at end of file diff --git a/JavaSript/javascript-obj-Notification.js b/JavaSript/javascript-obj-Notification.js new file mode 100644 index 00000000..3d44307c --- /dev/null +++ b/JavaSript/javascript-obj-Notification.js @@ -0,0 +1,126 @@ +---------------------------- +Notification | +---------------------------- + # 娴忚鍣ㄧ殑妗岄潰閫氱煡瀵硅薄 + # Notification 瀵硅薄缁ф壙鑷 EventTarget 鎺ュ彛 + # 杩欎笢瑗垮繀椤诲湪https鍗忚涓嬫墠鑳藉脊鍑 + # window 瀵硅薄 + + # 澶ц嚧姝ラ + 1,鍒ゆ柇娴忚鍣ㄦ槸鍚︽敮鎸 + Boolean(window.Notification) + + 2,鍒ゆ柇鏄惁宸茬粡鎺堟潈 + if (Notification.permission==='granted') { + //宸茬粡鎺堟潈,鎵ц浠g爜 + } else { + Notification.requestPermission(); //鍚﹀垯寮瑰嚭寮鍚潈闄愭彁绀烘 + } + + 3,璁剧疆鎻愮ず鍐呭 + var notify = new Notification('title' , config ); + * config鏄竴涓璞★紝鏈5涓睘鐩革紝鍙互璁剧疆鏄剧ず鍐呭 + var config = { + body :'姝f枃鍐呭', + dir : '鏂囨湰鏄剧ず鏂瑰悜锛宎uto, ltr, rtl', + lang : '鏂囨湰璇█', + tag : '涓洪氱煡鏍忓垎閰嶄竴涓猧d锛屾柟渚垮鎻愮ず鎿嶄綔锛屾绱紝鏇挎崲锛岀Щ闄ょ瓑', + icon : '鍥剧墖url' + } + + 4,浜嬩欢 + notify.onclick //鐐瑰嚮鎻愮ず妗嗘椂瑙﹀彂 + notify.onshow //鏄剧ず鎻愮ず妗嗘椂瑙﹀彂 + notify.onerror //鏄剧ず鎻愮ず妗嗗嚭閿欐椂瑙﹀彂 + notify.onclose //鍏抽棴鎻愮ず妗嗘椂瑙﹀彂 + + 5,鍏抽棴 + notify.close() + +---------------------------- +Notification-灞炴 | +---------------------------- + # 绫诲睘鎬 + permission + * 杩斿洖褰撳墠鐢ㄦ埛瀵瑰脊绐楅氱煡鐨勭殑鎺堟潈鐘舵 + granted //宸茬粡鎺堟潈 + denied //鎷掔粷 + default //鍏跺疄灏辨槸鎷掔粷 + + # 瀹炰緥灞炴 + title + * 鏍囬 + lang + * 閫氱煡璇█ + * en-US + dir + * 鏂囨湰鐨勬樉绀烘柟鍚 + auto: 缁ф壙娴忚鍣ㄧ殑璇█璁剧疆閲屽埗瀹氱殑鏂瑰悜 (榛樿) + ltr : 浠庡乏鍒板彸 + rtl : 浠庡彸鍒板乏 + + body + * 姝f枃鍐呭 + tag + * 閫氱煡鐨刬d + * 璧嬩簣閫氱煡涓涓狪D,浠ヤ究鍦ㄥ繀瑕佺殑鏃跺欏閫氱煡杩涜鍒锋柊,鏇挎崲鎴栫Щ闄 + + icon + * 鍥剧墖鍦板潃 + +---------------------------- +Notification-鏂规硶 | +---------------------------- + # 鏋勯犳柟娉 + Notification(title,options) + * title,鏍囬 + * options,閰嶇疆瀵硅薄 + # 绫绘柟娉 + requestPermission(call) + * 寮瑰嚭鎻愮ず妗,寰佹眰鐢ㄦ埛鏄惁鍏佽寮瑰嚭妗岄潰閫氱煡 + * call鏂规硶鐨勭涓涓弬鏁,灏辨槸鐢ㄦ埛鐨勬剰瑙佺粨鏋 + * 杩斿洖 Promise() 瀵硅薄 + + + + # 瀹炰緥鏂规硶 + close() + * 鍏抽棴閫氱煡 + + + # 瀹炰緥浜嬩欢 + onclick + * 澶勭悊 click 浜嬩欢鐨勫鐞 + onshow + * 澶勭悊 show 浜嬩欢鐨勫鐞 + onerror + * 澶勭悊 error 浜嬩欢鐨勫鐞 + onclose + * 澶勭悊 close 浜嬩欢鐨勫鐞 + +---------------------------- +Notification-demo | +---------------------------- + if(!window.Notification){ + //涓嶆敮鎸佸脊绐 + } + if(window.Notification.permission != 'granted'){ + //寰佹眰鎺堟潈 + window.Notification.requestPermission(function(permission){ + if(permission === 'granted'){ + //鍚屾剰,寮逛釜绐楀帇鍘嬫儕 + let notification = new window.Notification('浣犲ソ',{ + body:'Hello', + lang:'zh-ch', + tag:'1', + icon:'http://static.javaweb.io/e/1/7/5/0F8B0280ED034E65B602C2F923187F2B.gif' + }); + notification.onshow = function(event){ + } + notification.onclick = function(event){ + } + }else{ + //鎷掔粷,閭e氨绠椾簡 + } + }); + } \ No newline at end of file diff --git a/JavaSript/javascript-obj-WebSocket.js b/JavaSript/javascript-obj-WebSocket.js index 866cf6e3..d2676cf7 100644 --- a/JavaSript/javascript-obj-WebSocket.js +++ b/JavaSript/javascript-obj-WebSocket.js @@ -94,3 +94,25 @@ WebSocket-WebSocket | ------------------------ WebSocket-WebSocket | ------------------------ +状态码 名称 描述 +0–999 - 保留段, 未使用。 +1000 CLOSE_NORMAL 正常关闭; 无论为何目的而创建, 该链接都已成功完成任务。 +1001 CLOSE_GOING_AWAY 终端离开, 可能因为服务端错误, 也可能因为浏览器正从打开连接的页面跳转离开。 +1002 CLOSE_PROTOCOL_ERROR 由于协议错误而中断连接。 +1003 CLOSE_UNSUPPORTED 由于接收到不允许的数据类型而断开连接 (如仅接收文本数据的终端接收到了二进制数据)。 +1004 - 保留。 其意义可能会在未来定义。 +1005 CLOSE_NO_STATUS 保留。 表示没有收到预期的状态码。 +1006 CLOSE_ABNORMAL 保留。 用于期望收到状态码时连接非正常关闭 (也就是说, 没有发送关闭帧)。 +1007 Unsupported Data 由于收到了格式不符的数据而断开连接 (如文本消息中包含了非 UTF-8 数据)。 +1008 Policy Violation 由于收到不符合约定的数据而断开连接。 这是一个通用状态码, 用于不适合使用 1003 和 1009 状态码的场景。 +1009 CLOSE_TOO_LARGE 由于收到过大的数据帧而断开连接。 +1010 Missing Extension 客户端期望服务器商定一个或多个拓展, 但服务器没有处理, 因此客户端断开连接。 +1011 Internal Error 客户端由于遇到没有预料的情况阻止其完成请求, 因此服务端断开连接。 +1012 Service Restart 服务器由于重启而断开连接。 [Ref] +1013 Try Again Later 服务器由于临时原因断开连接, 如服务器过载因此断开一部分客户端连接。 [Ref] +1014 - 由 WebSocket 标准保留以便未来使用。 +1015 TLS Handshake 保留。 表示连接由于无法完成 TLS 握手而关闭 (例如无法验证服务器证书)。 +1016–1999 - 由 WebSocket 标准保留以便未来使用。 +2000–2999 - 由 WebSocket 拓展保留使用。 +3000–3999 - 可以由库或框架使用。 不应由应用使用。 可以在 IANA 注册, 先到先得。 +4000–4999 - 可以由应用使用。 \ No newline at end of file diff --git a/JavaSript/javascript-obj-XMLHttpRequest.js b/JavaSript/javascript-obj-XMLHttpRequest.js index 2baf404c..1eb3f25e 100644 --- a/JavaSript/javascript-obj-XMLHttpRequest.js +++ b/JavaSript/javascript-obj-XMLHttpRequest.js @@ -59,7 +59,6 @@ XMLHttpRequest | * 可选值 arraybuffer, blog, - responseXML * 返回服务器响应的XML数据 @@ -69,19 +68,50 @@ XMLHttpRequest | withCredentials * 在跨域请求的时候,是否把Cookie值也发送到跨域服务器 + * 该属性应该在 open() 方法执行之前设置 upload * 当提交的表单是文件表单的时候,该属性会存在 * 该属性可以监听一个上传事件:progress + if(xhr.upload){ + //监听上传属性的上传事件,每次上传事件都会执行 progressHandlingFunction + xhr.upload.addEventListener('progress',progressHandlingFunction, false); + //xhr.upload.onprogress = function(){} 也可以 + } * Event属性 total; //获取上传文件的总(所有)大小 loaded; //获取已经上传的文件大小 + + function progressHandlingFunction(event) { + event.total; //获取上传文件的总大小 + event.loaded; //获取已经上传的文件大小 + //获取进度的百分比值 + var percent = (event.loaded / event.total) * 100; + //四舍五入保留两位小数 + percent = percent.toFixed(2); + } + statusText + * http状态的描述文字 + + onload + * 就是readyState = 4 时会执行的回调,它可以代替这种判断 + if(xmlHttp.readyState == 4 && xmlHttp.status == 200){} + + * 使用 onload + let xhr = new XMLHttpRequest(); + xhr.onload = function () { + // 得到客户端的响应 + let json = JSON.parse(xhr.responseText); + }; + xhr.open('GET', '/foo', true); + xhr.send(null); + # 事件 onreadyStatechange * 当异步对象的状态发生改变的时候调用 * 当使用 async=false 时,不用编写 onreadystatechange 函数,把代码放到 send() 语句后面即可 - + --------------------------- XMLHttpRequest-GET | diff --git "a/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Array.js" "b/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Array.js" index b7a24c4d..1336a8fe 100644 --- "a/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Array.js" +++ "b/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Array.js" @@ -102,9 +102,17 @@ Array | isArray(); * 判断指定的对象是否是数组类型 + from() + * 把指定的数据转换为[],数据可以是类数组对象,也可以是 Ierator 子类 + + of() + * 相当于是构造方法,可以把一个或者多个参数,组成一个数组返回 + + # 实例方法 concat() - * 连接一个或更多的数组,返回新的数组。 + * 连接一个或更多的数组,/返回新的数组 + * 如果没有传递参数的话,相当于是浅复制了该数组,返回当前数组的所有成员组成的新数组 * demo var a1 = [1,2,3]; var a2 = ["4","5","6"]; @@ -326,13 +334,15 @@ Array | * 反转数组的元素顺序,不返回新的数组,直接就把当前数组反转 slice() - * 选取数组的的一部分,并返回一个新数组。 + * 选取数组的的一部分,并返回一个新数组 * 包含头,不包含尾 * 参数(从哪个角标开始?到哪个角标结束?) start - > 必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。 + - 必需,规定从何处开始选取 + - 如果是负数,那么它规定从数组尾部开始算起的位置,也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推 end - > 可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。 + > 可选,规定从何处结束选取,该参数是数组片断结束处的数组下标,如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素 + - 如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素 * Demo var arr = [1,2,3]; var result = arr.slice(0,2); //12 @@ -407,8 +417,17 @@ Array | * Demo var fruits = ["Banana", "Orange", "Apple", "Mango"]; var v = fruits.valueOf(); //fruits.valueOf()与 fruits返回值一样,v输出结果为:Banana,Orange,Apple,Mango + + entries() + keys() + values() + * 这仨方法返回的都是迭代器,一看就懂 + includes() + * 返回一个布尔值,表示某个数组是否包含给定的值 + * 的第二个参数表示搜索的起始位置,默认为0 + -------------------- Array-注意的地方 | -------------------- diff --git "a/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Number.js" "b/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Number.js" index b7425e89..81cb4931 100644 --- "a/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Number.js" +++ "b/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Number.js" @@ -13,15 +13,56 @@ Number | # 静态属性 MAX_VALUE - * 可表示的最大的数。 + * 可表示的最大的数 + MIN_VALUE - * 可表示的最小的数。 + * 可表示的最小的数 + NEGATIVE_INFINITY - * 负无穷大,溢出时返回该值。 - NaN - * 非数字值。 + * 负无穷大,溢出时返回该值 + POSITIVE_INFINITY - * 正无穷大,溢出时返回该值。 + * 正无穷大,溢出时返回该值 + + NaN + * 非数字值 + + Number.EPSILON + * 它表示 1 与大于 1 的最小浮点数之间的差 + + + Number.MAX_SAFE_INTEGER + * JavaScript 能够准确表示的最大整数 + + Number.MIN_SAFE_INTEGER + * JavaScript 能够准确表示的最小整数 + + + + # 静态方法 + isFinite() + * 判断数据是不是一个非无穷大的数据 + * !Infinity + * 如果参数类型不是数值,Number.isFinite一律返回false + + isNaN() + * 判断数据是否是一个非数字 + * NaN + + parseInt() + parseFloat() + * 把指定数据转换为Int类型的数据 + * 其实就是那个全局函数 + Number.parseInt === parseInt // true + Number.parseFloat === parseFloat // true + + isInteger() + * 判断一个数是否为整数 + * '对数据精度的要求较高,不建议使用Number.isInteger()判断一个数值是否为整数' + + isSafeInteger() + * 是否是一个安全的整数,也就是说值是否在 MAX_SAFE_INTEGER 和 MIN_SAFE_INTEGER 之间 + # 实例方法 toExponential(x) diff --git "a/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Object.js" "b/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Object.js" index dd876c32..526330e0 100644 --- "a/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Object.js" +++ "b/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-Object.js" @@ -42,7 +42,7 @@ Object | * 第二个参数,用于对对象属性进行更详细的描述(可选) writable :是否可任意写 configurable :是否能够删除,是否能够被修改 - enumerable :是否能用 for in 枚举 + enumerable :是否可枚举,默认为 false value :值,'当设置后不能设置get和set' get :访问属性的时候触发,记住千万不能使用 this.当前属性名称 set :设置属性的时候触发,记住千万不能使用 this.当前属性名称 @@ -73,14 +73,23 @@ Object | * 自身的属性就OK,不论是否可以枚举 * 返回的属性不包含原型对象的属性 + getOwnPropertySymbols(obj); + * 返回一个数组,包含对象自身的所有 Symbol 属性的键名 + + defineProperty(); * 给指定的对象设置属性 * 三个参数 1,要设置属性的对象 2,设置什么属性(以字符串表示属性的名称) 3,Options配置项({}) - configurable 属性能否被删除或者重新定义 + configurable 属性能否被删除或者重新定义,默认 false enumerable 遍历对象的时候属性是否可见 + * 目前,有四个操作会忽略enumerable为false的属性 + for...in循环 :只遍历对象自身的和继承的可枚举的属性 + Object.keys() :返回对象自身的所有可枚举的属性的键名 + JSON.stringify():只串行化对象自身的可枚举的属性 + Object.assign() :忽略enumerable为false的属性,只拷贝对象自身的可枚举 value 属性值,'当设置后不能设置get和set' writable 属性能否改变 get 当获取属性的时候触发,记住千万不能使用 this.当前属性名称 @@ -98,13 +107,78 @@ Object | value:2 } }); - + getOwnPropertyDescriptor(obj,prop); * 获取指定对象,指定属性的...属性 * 就是参数1这个对象的参数2的属性的一些属性(只读...等等) * 不能检测从原型继承的属性 * Object.getOwnPropertyDescriptor(user,"name") + getOwnPropertyDescriptors(obj) + * 返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象 + * 自己实现 + function getOwnPropertyDescriptors(obj) { + const result = {}; + for (let key of Reflect.ownKeys(obj)) { + result[key] = Object.getOwnPropertyDescriptor(obj, key); + } + return result; + } + + freeze(obj); + * 冻结一个对象,冻结了之后,该对象的属性值不能进行修改 + 'use strict'; + const foo = Object.freeze({name:'Kevin'}); + foo.name = 'Litch' //Uncaught TypeError: Cannot assign to read only property 'name' of object '#' + foo.age = 23; //Uncaught TypeError: Cannot add property age, object is not extensible + * 但是对象的属性对象是允许被修改的 + const foo = Object.freeze({user:{name:'Kevin'}}); + foo.user.name = 'Litch'; + + * 冻结对象的所有属性 + var constantize = (obj) => { + Object.freeze(obj); + Object.keys(obj).forEach( (key, i) => { + if ( typeof obj[key] === 'object' ) { + constantize( obj[key] ); + } + }); + }; + + is(t,o); + * 其实就是 ==== 判断 + * 与 === 不同之处只有两个:一是+0不等于-0,二是NaN等于自身 + +0 === -0 //true + NaN === NaN // false + + Object.is(+0, -0) // false + Object.is(NaN, NaN) // true + + assign(target,...source) + * 将源对象(source)的所有可枚举属性,复制到目标对象(target) + + setPrototypeOf(obj,prototype) + * 设置指定对象的原型对象 + + getPrototypeOf(obj) + * 获取指定对象的原型对象 + + keys() + * 返回所有属性名的集合 + + values() + * 返回所有的值的集合 + + entries() + * 返回键值对的集合[[k,v],[k,v]] + + preventExtensions() + * 用于让一个对象变为不可扩展 + * 它返回一个布尔值,表示是否操作成功 + isExtensible() + * 返回一个布尔值,表示当前对象是否可扩展 + + diff --git "a/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-RegExp.js" "b/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-RegExp.js" index d9a820fc..a5c0356a 100644 --- "a/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-RegExp.js" +++ "b/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-RegExp.js" @@ -2,17 +2,42 @@ RegExp | ----------------------- # 正则表达式 + # 构造函数 + RegExp(regStr,flag) # 创建 - var re = new RegExp("\\w+"); - var re = /\w+/; + var re = new RegExp("\\w+",'i'); + var re = /\w+/i; * 当使用构造函数创造正则对象时,需要常规的字符转义规则(在前面加反斜杠 \)。 # 实例方法 exec(); - * 检测指定的字符串,返回符合正则的子串 + * 检测指定的字符串,返回一个匹配对象 + * 如果字符串有多个匹配对象,可以多次执行,直到返回 null test(); * 检测指定的字符串是否有一个或者多个符合正则表达,返回 true/false - \ No newline at end of file + + # 实例属性 + flags + * 返回设置的flag + + sticky + * sticky属性,表示是否设置了y修饰符 + + dotAll + * 返回一个布尔值,表示该正则表达式是否处在dotAll模式 + + + + + # flags(抄自python笔记) + + re.I 使匹配对大小写不敏感 + re.L 做本地化识别(locale-aware)匹配 + re.M 多行匹配,影响 ^ 和 $ + re.S 使 . 匹配包括换行在内的所有字符(全文匹配) + re.U 根据Unicode字符集解析字符,这个标志影响 \w, \W, \b, \B. + re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解 + * 这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释 \ No newline at end of file diff --git "a/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-String.js" "b/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-String.js" index cd41390f..edbf05fd 100644 --- "a/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-String.js" +++ "b/JavaSript/javascript-\345\205\250\345\261\200\345\257\271\350\261\241-String.js" @@ -37,7 +37,7 @@ String | split(); * 分隔为数组 - + # 创建 var s = new String("123"); var s = "123"; @@ -47,6 +47,7 @@ String | * 字符串的长度 # 实例方法 + charAt() * 返回在指定位置的字符。 @@ -128,4 +129,29 @@ String | * 去除字符串两边的空白 valueOf() - * 返回某个字符串对象的原始值。 \ No newline at end of file + * 返回某个字符串对象的原始值。 + + //es6新增 + + at() + * 返回指定位置的字符串,ES6新增,反正牛逼 + + includes() + * 返回布尔值,表示是否找到了参数字符串 + * 支持第二个参数,表示开始搜索的位置 + startsWith() + * 返回布尔值,表示参数字符串是否在原字符串的头部 + * 支持第二个参数,表示开始搜索的位置 + endsWith() + * 返回布尔值,表示参数字符串是否在原字符串的尾部 + * 支持第二个参数,表示针对前n个字符 + + repeat(n) + * 方法返回一个新字符串,表示将原字符串重复n次 + "1".repeat(2) //11 + + padStart() + padEnd() + * 首/尾补齐 + * 第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串 + diff --git "a/JavaSript/javascript-\345\207\275\346\225\260.js" "b/JavaSript/javascript-\345\207\275\346\225\260.js" index ee255849..43978593 100644 --- "a/JavaSript/javascript-\345\207\275\346\225\260.js" +++ "b/JavaSript/javascript-\345\207\275\346\225\260.js" @@ -30,6 +30,9 @@ # 属性 length * 返回函数的形参数量 + + name + * 返回函数的名称 # 可以给函数自定义属性/访问 function test(){} @@ -126,6 +129,20 @@ # 获取当前函数的名称 arguments.caler; +---------------------------- +caller | +---------------------------- + # 用来获取到,调用当前函数的函数 + function b(){ + a(); + } + function a(){ + var caller = a.caller; + console.log(caller); + } + b(); //b函数对象 + a(); //null + ---------------------------- this 对象 | ---------------------------- diff --git "a/JavaSript/javascript-\346\213\226\346\213\275\345\222\214\347\262\230\350\264\264\344\270\212\344\274\240.js" "b/JavaSript/javascript-\346\213\226\346\213\275\345\222\214\347\262\230\350\264\264\344\270\212\344\274\240.js" new file mode 100644 index 00000000..9f095227 --- /dev/null +++ "b/JavaSript/javascript-\346\213\226\346\213\275\345\222\214\347\262\230\350\264\264\344\270\212\344\274\240.js" @@ -0,0 +1,85 @@ +/** + * 鍥剧墖榛忚创涓婁紶 + */ +editorNode.addEventListener('paste',function(event){ + let clipboardData = event.clipboardData; + if(clipboardData){ + let items = clipboardData.items; + if(items){ + for(let item of items){ + //鏁版嵁绫诲瀷 + let type = item.type; + if(type.startsWith('image/')){ + //鏂囦欢瀵硅薄 + let file = item.getAsFile(); + if(!file){ + //TODO 淇敼寮圭獥鎺т欢 + alert('鏂囦欢璇诲彇澶辫触,浠呬粎鏀寔閮ㄥ垎鎴浘澶嶅埗鍚庣殑鍥剧墖绮樿创'); + return null; + } + //TODO 澶у皬闄愬埗 + let formData = new FormData(); + formData.append('editormd-image-file',file); + $.ajax({ + url:'/editor/upload', + data:formData, + type:'POST', + processData:false, + contentType:false, + success:function(response){ + options.editor.insertValue("![](" + response.url + ")"); + }, + complete:function(){ + + }, + error:function(error){ + + } + }); + } + } + } + } +}); + + +/** + * 鍥剧墖鎷栨嫿涓婁紶 + */ +editorNode.addEventListener('dragenter',function(event){ //鎷栬繘 + event.preventDefault(); +}); +editorNode.addEventListener('dragover',function(event){ //鎷栨潵鎷栧幓 + event.preventDefault(); +}); +editorNode.addEventListener('drop',function(event){ //閲婃斁榧犳爣 + event.preventDefault(); + let files = event.dataTransfer.files; + for(let file of files){ + if(file.type.startsWith('image/')){ + //蹇呴』鏄枃浠 + //TODO 澶у皬闄愬埗 + let formData = new FormData(); + formData.append('editormd-image-file',file); + $.ajax({ + url:'/editor/upload', + data:formData, + type:'POST', + processData:false, + contentType:false, + success:function(response){ + options.editor.insertValue("![](" + response.url + ")"); + }, + complete:function(){ + + }, + error:function(error){ + + } + }); + } + } +}); +editorNode.addEventListener('dragend',function(event){ //鎷栧嚭 + event.preventDefault(); +}); \ No newline at end of file diff --git "a/JavaSript/javascript-\346\272\220\347\240\201.js" "b/JavaSript/javascript-\346\272\220\347\240\201.js" index ee271c3f..915b847a 100644 --- "a/JavaSript/javascript-\346\272\220\347\240\201.js" +++ "b/JavaSript/javascript-\346\272\220\347\240\201.js" @@ -119,4 +119,143 @@ doucment- cookie[name] = value; } return cookie; - } \ No newline at end of file + } + +---------------------------------------- +监听文字复制事件,并且添加自己的数据 | +---------------------------------------- +document.addEventListener('copy', function (event) { + var clipboardData = event.clipboardData || window.clipboardData; + if (!clipboardData) { + return; + } + //复制到的文字信息 + var text = window.getSelection().toString(); + if (text) { + event.preventDefault(); + //修改原来的文字信息 + clipboardData.setData('text/plain', text + '\n\njavaweb开发者社区版权所有'); + } +}); + +---------------------------------------- +获取到粘贴的图片 | +---------------------------------------- +document.addEventListener('paste', function(event) { + let items = event.clipboardData && event.clipboardData.items; + let file = null; + if (items && items.length) { + // 检索剪切板items + for (let i = 0; i < items.length; i++) { + if (items[i].type.indexOf('image') !== -1) { + file = items[i].getAsFile(); + if(!file){ + // 文件读取失败,可能是复制了文件系统的图片 + } + break; + } + } + } + // 此时file就是剪切板中的图片文件 +}); + +---------------------------------------- +获取到拖曳的图片 | +---------------------------------------- +document.addEventListener('dragenter', function (event) { + event.preventDefault(); +}); +document.addEventListener('dragover', function (event) { + event.preventDefault(); +}); +document.addEventListener('drop', function (event) { + event.preventDefault(); + let files = event.dataTransfer.files; + if (files) { + // 获取到拖曳的图片 + console.log(files); + } +}); +document.addEventListener('dragend', function (event) { + event.preventDefault(); +}); + +---------------------------------------- +序列化表单为form字符串 | +---------------------------------------- +function serializationForm(form){ + + const urlSearchParams = new URLSearchParams(); + const formNodes = ['INPUT', 'TEXTAREA', 'SELECT'] + const queue = [...form.childNodes] + + while (queue.length > 0){ + const node = queue.shift() + if(formNodes.includes(node.nodeName)){ + let name = node.getAttribute('name'); + let value = null; + if (node.getAttribute('type') == 'checkbox' || node.getAttribute('type') == 'radio'){ + if (node.checked){ + value = node.value; + }else{ + continue; + } + }else { + value = node.value; + } + + urlSearchParams.append(name, value); + }else { + for(let subNode of node.childNodes){ + queue.push(subNode); + } + } + } + return urlSearchParams.toString(); +} + +---------------------------------------- +序列化表单为json字符串 | +---------------------------------------- +function serializationJSON(form){ + + const requestBody = {}; + const formNodes = ['INPUT', 'TEXTAREA', 'SELECT'] + const queue = [...form.childNodes] + + while (queue.length > 0){ + const node = queue.shift() + if(formNodes.includes(node.nodeName)){ + let name = node.getAttribute('name'); + let value = null; + if (node.getAttribute('type') == 'checkbox' || node.getAttribute('type') == 'radio'){ + if (node.checked){ + value = node.value; + }else{ + continue; + } + }else { + value = node.value; + } + + value = encodeURIComponent(value); + + if (name in requestBody){ + const existsValue = requestBody[name] + if (Array.isArray(existsValue)){ + existsValue.push(value) + }else { + const arrValue = [existsValue, value] + requestBody[name] = arrValue; + } + }else { + requestBody[name] = value; + } + }else { + for(let subNode of node.childNodes){ + queue.push(subNode); + } + } + } + return JSON.stringify(requestBody); +} \ No newline at end of file diff --git "a/\347\233\264\346\222\255\346\212\200\346\234\257/javacv-\345\205\245\351\227\250.java" "b/Javacv/javacv-\345\205\245\351\227\250.java" similarity index 100% rename from "\347\233\264\346\222\255\346\212\200\346\234\257/javacv-\345\205\245\351\227\250.java" rename to "Javacv/javacv-\345\205\245\351\227\250.java" diff --git "a/\347\233\264\346\222\255\346\212\200\346\234\257/\347\233\264\346\222\255\346\212\200\346\234\257.java" "b/Javacv/\347\233\264\346\222\255\346\212\200\346\234\257.java" similarity index 100% rename from "\347\233\264\346\222\255\346\212\200\346\234\257/\347\233\264\346\222\255\346\212\200\346\234\257.java" rename to "Javacv/\347\233\264\346\222\255\346\212\200\346\234\257.java" diff --git a/Javafx/javafx.java b/Javafx/javafx.java new file mode 100644 index 00000000..cbc6fdc0 --- /dev/null +++ b/Javafx/javafx.java @@ -0,0 +1,28 @@ +----------------------------- +Javafx | +----------------------------- + # 参考地址 + www.javafxchina.net/main/ + + # JavaFX架构和生态系统的高阶视图 + * 场景图(Scene Graph) + * JavaFX功能的公开API(Java Public APIs for JavaFX Features) + * 图形系统(Graphics System) + * Glass窗体工具包(Glass Windowing Toolkit) + * 多媒体和图像(Media and Images) + * Web组件(Web Component) + * CSS + * UI控件(UI Controls) + * 布局(Layout) + * 2-D和3-D转换(2-D and 3-D Transformations) + * 视觉特效(Visual Effects) + + # JavaFX应用程序基本结构 + * JavaFX应用程序的主类需要继承自 application.Application 类,覆写的start()方法是所有JavaFX应用程序的入口 + * JavaFX应用程序将UI容器定义为舞台(Stage)与场景(Scene),Stage类是JavaFX顶级容器,Scene类是所有内容的容器 + + * 在JavaFX中,Scene中的内容会以由图形节点(Node)构成的分层场景图(Scene Graph)来展现 + + * 当JavaFX应用程序是通过JavaFX Packager工具打包时,main()方法就不是必需的的了,因为JavaFX Package工具会将JavaFX Launcher嵌入到JAR文件中 + * 但是保留main()方法还是很有用的,这样可以运行不带有JavaFX Launcher的JAR文件,例如在使用某些没有将JavaFX工具完全集成进去的IDE时,另外嵌入了JavaFX代码的Swing应用程序仍需要main()方法 + \ No newline at end of file diff --git "a/Jdk9\346\226\260\347\211\271\346\200\247/jdk9-jdk\344\270\216jre\347\232\204\346\224\271\345\217\230.java" "b/Jdk9\346\226\260\347\211\271\346\200\247/jdk9-jdk\344\270\216jre\347\232\204\346\224\271\345\217\230.java" new file mode 100644 index 00000000..e69de29b diff --git "a/Jdk9\346\226\260\347\211\271\346\200\247/jdk9.java" "b/Jdk9\346\226\260\347\211\271\346\200\247/jdk9.java" new file mode 100644 index 00000000..410c7d22 --- /dev/null +++ "b/Jdk9\346\226\260\347\211\271\346\200\247/jdk9.java" @@ -0,0 +1,28 @@ +---------------------------- +java9 | +---------------------------- + # 2017-9 月发布 + # 比较重要的新特性 + * 模块化系统 + * jshell + * 多版本兼容jar + * 接口的私有方法 + * 钻石操作符的使用升级 + * 语法改进:try 语句 + * 下划线使用限制 + * String 存储结构变更 + * 便利的集合特性:of + * 增强的Stream API() + * 多分辨率图形API + * 全新的 HTTP 客户端 API + * Deprecated 相关API + * 智能Java编译工具 + * 统一jvm日志系统 + * javadoc的HTML5支持 + * javascript 引擎升级: Nashorn + * Java的动态编译 + + # 参考资料 + https://www.ibm.com/developerworks/cn/java/the-new-features-of-Java-9/index.html + + diff --git "a/Jmeter/jmeter-\350\204\232\346\234\254\345\275\225\345\210\266.java" "b/Jmeter/jmeter-\350\204\232\346\234\254\345\275\225\345\210\266.java" new file mode 100644 index 00000000..c183eccc --- /dev/null +++ "b/Jmeter/jmeter-\350\204\232\346\234\254\345\275\225\345\210\266.java" @@ -0,0 +1,15 @@ +-------------------- +脚本录制 | +-------------------- + + +-------------------- +badboy进行录制 | +-------------------- + +-------------------- +使用代理进行录制 | +-------------------- + # Jemeter代理浏览器的请求, 录制执行步骤 + + diff --git a/Jmeter/jmeter.java b/Jmeter/jmeter.java new file mode 100644 index 00000000..64126d11 --- /dev/null +++ b/Jmeter/jmeter.java @@ -0,0 +1,46 @@ +-------------------------- +Jmeter | +-------------------------- + # 参考地址 + https://jmeter.apache.org/ + http://jmeter.apache.org/usermanual/index.html + https://github.com/apache/jmeter + + https://www.cnblogs.com/imyalost/p/7062784.html + +-------------------------- +目录 | +-------------------------- + bin + |-examples + |-report-template + |-templates + |-ApacheJMeter.jar + |-jmeter.bat + * windows下启动脚本 + |-jmeter.sh + * Linux下启动脚本 + + docs + extras + lib + licenses + printable_docs + LICENSE + NOTICE + README.md + + +-------------------------- +核心概念 | +-------------------------- + # 取样器 + * 脚本逻辑控制 + + # 线程组 + * 场景设置, 模拟用户的行为 + + # 监视器 + * 监控脚本运行, 取得性能指标(TPS/QPS) + + diff --git a/Jvm/jvm-TLAB.java b/Jvm/jvm-TLAB.java new file mode 100644 index 00000000..a685e0ad --- /dev/null +++ b/Jvm/jvm-TLAB.java @@ -0,0 +1,21 @@ +-------------------------- +TLAB | +-------------------------- + # TLAB (ThreadLocalAllocBuffer) + + # 创建对象时,需要在堆上申请指定大小的内存, 如果同时有大量线程申请内存的话, 可以通过锁机制或者指针碰撞的方式确保不会申请到同一块内存 + * 在JVM运行中, 内存分配是一个极其频繁的动作, 这种方式势必会降低性能 + + # 在Hotspot 1.6的实现中引入了TLAB技术 + * TLAB是线程的一块私有内存, 如果设置了虚拟机参数 -XX:UseTLAB + * 在线程初始化时, 同时也会申请一块指定大小的内存, 只给当前线程使用, 这样每个线程都单独拥有一个 Buffer + * 如果需要分配内存, 就在自己的Buffer上分配, 这样就不存在竞争的情况, 可以大大提升分配效率 + * 当Buffer容量不够的时候, 再重新从Eden区域申请一块继续使用 + * 申请动作还是需要原子操作的 + + # TLAB只是让每个线程有私有的分配指针, 但底下存对象的内存空间还是给所有线程访问的, 只是其它线程无法在这个区域分配而已 + * 当一个TLAB用满就新申请一个TLAB + * 在老TLAB里的对象还留在原地什么都不用管——它们无法感知自己是否是曾经从TLAB分配出来的, 而只关心自己是在eden里分配的 + + + diff --git "a/Jvm/jvm-gc-G1\346\224\266\351\233\206\345\231\250.java" "b/Jvm/jvm-gc-G1\346\224\266\351\233\206\345\231\250.java" new file mode 100644 index 00000000..3106356d --- /dev/null +++ "b/Jvm/jvm-gc-G1\346\224\266\351\233\206\345\231\250.java" @@ -0,0 +1,146 @@ +-------------------- +G1 收集器 | +-------------------- + # 在JK9已经被设置为默认的收集器,面向大内存(Heap区数G到数10G), 多核系统的收集器,能够实现软停顿目标收集并且具有高吞吐量, 具有更可预测的停顿时间 + * Copying算法收集的分代的增量式收集器 + * 避免长暂停时间, 可以考虑将堆分成多个部分, 一次收集其中一部分, 这样的方式又叫做增量收集(incremental collection), 分代收集也可以看成一种特殊的增量收集 + + + # 它是目前收集器技术发展的前沿成果之一, 是一个面向服务端的GC收集器 + * 它的使命就是替换掉JDK5的CMS收集器 + + # 特点 + 1. 并行和并发 + * 并行收集, 充分发挥多核的优势, 缩短 STW 时间 + * 其他收集器需要暂停业务线程来完成收集工作, G1仍然可以通过并发让业务线程继续执行 + + 2. 分代收集 + * G1中仍然保留分代的概念, + * 不需要其他的收集器配合, 自己就能管理好整个堆, 它采取不同的处理方式去管理不同的对象, 以达到更好的收集效果 + - 新创建的对象 + - 熬过了多次GC, 仍然存活的对象 + + 3. 空间整合 + * 与CMS的标记-清理算法不同, G1从整体来看是标记-整理算法, 从局部来看是基于复制算法实现的 + * 这两种算法都不回产生空间碎片, GC后能提供规整的可用内存, 非常利于程序的长时间运行, 分配大对象时不会因为找不到连续内存而提前触发下一次GC + + 4. 可预测的停顿 + * 这是G1相对于CMS的又一个优势, 降低停顿时间是G1和CMS共同的关注点 + * G1除了追求低停顿后, 还能建立可预测的停顿时间模型, + * 能够让使用者明确指定在一个长度M毫秒的时间段内, GC收集时间不能超过N毫秒 + * 这几乎已经是 Java(RTSJ) 实时垃圾收集器的特征 + + # 参考 + https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html#garbage_first_garbage_collection + https://liuzhengyang.github.io/2017/06/07/garbage-first-collector/ + +-------------------- +G1 内存模型 | +-------------------- + # 分区 Region + * 堆内存划分为一系列大小相等的Region区域 + * Region大小在1MB到32MB在启动时确定 + * G1同样也使用分代收集策略, 将堆分为Eden, Survivior, Old等, 只不过是按照逻辑划分的, 可以不连续, 一个 Old Region 经过GC后可能成为新的 Eden + * 当申请的对象大于Region大小的一半时, 会被放入一个Humongous Region(巨型区域)中 + * 当一个Region中是空的时, 称为可用Region或新Region + + # 卡片 Card + * 512bytes表示一个card, cardtable就是一个byte数组 + + +-------------------- +G1 跨区域引用的问题 | +-------------------- + # G1之可以建立, 能预测的停顿时间模型, 是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集 + * 跟踪每一个 Region 里面的垃圾堆的价值大小(回收所获得的空间大小, 和回收所需的时间经验值) + * 在后台维护了一个'优先列表', 每次根据允许收集的时间, 优先回收价值最大的 Region(Garbage-First名称就是这么来的) + * 使用 Region 划分内存空间, 以及带优先级区域回收的方式, 保证了G1在有限时间内, 获取尽可能高的收集效率 + + # 把内存化整为零的思想, 理解起来很容器, 但是实现的细节确比较难 + * 这东西从 2004 年提Sun实验室发表第一批G1论文到今天, 用了近10年的时间才鼓捣出来, 你说容易不容易 + + # 跨 Region 引用的问题 + * 把堆分成了N个 Region, 垃圾收集的单位也不能完全的以 Region 进行 + * 因为一样对象, 分配在一个 Region 中, 它不仅可以被同一个 Region 的对象引用, 还可能被其他 Region 的对象引用 + * 在判断对象存活的时候, 岂不是还得扫描整个 Java 堆才能保证准确性 + + * 这个问题并非G1才有, 只是在G1上显得更加的突出 + * 分代收集, 回收新生代中的对象, 也面临相同的问题, 可能老年代引用了新生代的对象 + * 回收新生代的时候, 也不得不扫描老年代的话, 那么 Minor GC 的效率就会下降不少 + + # G1收集器里面, Region 之间的对象引用, 以及其他收集器中的新生代与老年代之间的对象引用, JVM使用:Remembered Set 来避免全堆扫描 + * G1里面, 每一个 Region 都有一个 Remembered Set + * 虚拟机在程序对 Refrence 类型的数据写进行写操作的时候, 会产生一个 Write Barrier 暂时中断写操作 + + * 检查 Refrence 引用的对象是否在不同的 Region 中 + * 如果是的话, 便通过: Card Table 把相关引用信息记录到被引用对象所属的 Region 的 Remembered Set 中 + * 当进行GC的时候, 在GC根节点的枚举范围中加入: Remembered Set , 就不用对全堆扫描, 而且也不会有遗漏 + + + * 模拟一个过程 + // 1. 在写入引用对象的时候, 中断操作 + User user = new User(); + Book book = new Book(); + book.author = user; // Write Barrier + // 2. 判断 user 是否不在 book 对象的 Region 中 + // 3. 如果不是在同一个 Region ,记录引用信息在 user 所属的 Region 的 Remembered Set 中 + // 4. 对 user 的 Region 进行回收的时候, 可以通过引用信息, 找到 book 对象的 Region + +-------------------- +G1 执行步骤 | +-------------------- + # 不计算维护 Remembered Set 的操作 + + # 初始标记(Initial Marking) + * 仅仅标记一下 GC Roots 能直接关联到的对象, 并且修改 TAMS(Next Top at Mark Start) 的值 + * 让下一个阶段用户程序并发的时候, 能在正确可用的 Region 中创建新对象 + * 这个阶段需要业务线程停顿, 但是耗时非常短, + + # 并发标记(Concurrent Marking) + * 从GC Roots开始对堆中对象进行可达性分析, 找出存活的对象 + * 耗时比较长, 但是可以跟业务线程并发执行 + + # 最终标记(Final Marking) + * 修正上阶段中(并发标记), 因为业务线程继续执行而导致标记产生了变化的一部分记录 + * JVM把这段时间对象变化记录在线程 Remembered Set Logs 中 + * 在该阶段, 需要把 Remembered Set Logs 的数据合并到 Remembered Set 中, + * 在该阶段, 需业务线程停顿, 但是可以并行执行 + + # 筛选回收(Live Data Counting And Evacuation) + * 首先对各个 Region 的回收价值和成本进行排序 + * 根据用户所期望的GC停顿时间来制定回收计划 + + * Sun透露的信息来看, 这个阶段其实也可以做到与业务线程一起并发执行的 + * 但是因为只回收一部分的 Region, 时间是用户可以控制的, 而且停顿用户线程可以大幅度提高收集效率 + + + + +-------------------- +G1 相关的配置 | +-------------------- +-Xms +-Xmx + * 堆的初始化大小和最大大小 + +-XX:+UseG1GC + * 启动G1收集器 + +-XX:MaxGCPauseMillis=200 + * GC最大暂停时间, 默认 200 毫秒 + +-XX:InitiatingHeapOccupancyPercent=45 + * 开始一个标记周期的堆占用比例阈值, 默认45% + * 注意这里是整个堆, 不同于CMS中的Old堆比例 + +XX:G1HeapRegionSize + * 设置每个Regin的大小, + * 1MB到32MB的(2的指数的大小) + + + + +-------------------- +G1 的GC日志 | +-------------------- + \ No newline at end of file diff --git "a/Jvm/jvm-gc-ZGC\346\224\266\351\233\206\345\231\250.java" "b/Jvm/jvm-gc-ZGC\346\224\266\351\233\206\345\231\250.java" new file mode 100644 index 00000000..2b250a03 --- /dev/null +++ "b/Jvm/jvm-gc-ZGC\346\224\266\351\233\206\345\231\250.java" @@ -0,0 +1,36 @@ +----------------- +ZGC | +----------------- + # Z Garbage Collector, JDK11的新收集器 + * 停顿时间不会超过10ms + * 停顿时间不会随着堆的增大而增大(不管多大的堆都能保持在10ms以下) + * 可支持几百M, 甚至几T的堆大小(最大支持4T) + + # ZGC目前只在Linux/x64上可用, 而且还处于试验阶段 + + +----------------- +ZGC - 参数 | +----------------- + +-Xms +-Xmx + * 堆的初始化大小和最大大小 + +-XX:+UseZGC + * 使用ZGC收集器 + +-XX:ConcGCThread=4 + * 并发执行的GC线程数, 不宜过大, 因为应用线程还在执行 + * 默认根据CPU的核数计算出一个合理的数量, 默认是核数的 12.5% + +-XX:ParallelGCThreads=20 + * 对GC Roots进行标记和移动时, 需要进行STW + * 这个过程会使用 ParallelGCThreads 个GC线程进行并行执行 + * 默认为CPU核数的 60%, 因为应用线程已经停止了, 可以让出更多的线程来来执行GC任务, 这样才能让STW更短暂 + + + + + + diff --git "a/Jvm/jvm-gc-\345\217\202\346\225\260.java" "b/Jvm/jvm-gc-\345\217\202\346\225\260.java" new file mode 100644 index 00000000..51ef12ab --- /dev/null +++ "b/Jvm/jvm-gc-\345\217\202\346\225\260.java" @@ -0,0 +1,80 @@ +------------------------ +收集器的参数总结 | +------------------------ + -XX:+UseSerialGC + * Client 模式的默认值, 开启该模式后使用:Serial + Serial Old 组合 + * 新生代串行, 老年串行 + + -XX:+UseParNewGC + * 打开后, 使用:ParNew + Serial Old 组合 + * 新生代并行, 老年代串行 + + -XX:+UseConcMarkSweepGC + * 使用:ParNew + CMS + Serial Old 组合 + * 当CMS出现:Concurrent Mode Failure的时候, 会临时使用 Serial Old 收集器来重新对老年代进行垃圾收集 + * 新生代并行, 老年代并行(并行失败,临时转换为串行) + * 可以跟业务线程并行 + + -XX:+UseParallelGC + * 在Server模式下的默认值, 使用:Parallel Scavenge + Serial Old 组合(PS MarkSweep)组合 + + -XX:+UseParallelOldGC + * 使用:Parallel Scavenge + Parallel Old + + -XX:SurvivorRatio + * 新生代Eden区域和Survivor区域的容量比值, 默认为:8 (也就是说Eden=80%) + + -XX:PretenureSizeThreshold + * 晋升到老年代的对象大小, 设置该参数后, 体积大于该参数的对象, 直接在老年代分配 + * 默认值是0, 意思是不管多大都是先在Eden中分配内存 + * 仅仅只对:Serial 和 ParNew 两个收集器有效 + + -XX:MaxTenuringThreshold + * 晋升到老年代的对象年龄, 每个对象在坚持过一次:Minor GC 后, 年龄就会 +1 + * 当超过该值的时候, 就会进入老年代, 默认为 15 + + + -XX:+UseAdaptiveSizePolicy + * 打开自适应 GC 策略, 在这种模式下, 其他的一些属性不需要自己去设置, 参数会被自动调整, 以达到在堆大小, 吞吐量和停顿时间之间的平衡点 + -Xmn(新生代大小) + -XX:+SuivivorRatio(Eden和Survivor区的比例) + -XX:+PretenureSizeThreshold(晋升老年代对象年龄) + * 使用自适应GC策略, 只需要把基本的内存数据设置好,例如堆内存大小值 + * 然后仅仅关注/设置最大停顿时间:-XX:MaxGCPauseMillis + * 或者给JVM设置一个最大吞吐量 -XX:GCTimeRatio 的优化目标, 具体的工作细节就由jvm完成 + + -XX:HandlePromotionFailure + * 是否允许分配担保失败, 即老年代的剩余空间不足以应付整个新生代所有对象都存活的极端情况 + * 担保失败后会执行Full GC + + -XX:ParallelGCThreads + * 设置 ParNew 收集器的收集线程数量 + + -XX:GCTimeRatio + * 设置吞吐量大小, 它的值是一个大于 0 小于 100 之间的整数 + * 可以理解为: 垃圾收集时间占总时间的比例 + * 默认 GCTimeRatio 的值为 99, 那么系统将花费不超过 1 / (1 + 99) = 1% 的时间用于垃圾收集 + + -XX:MaxGCPauseMillis + * 置最大垃圾收集停顿时间, 它的值是一个大于 0 的整数 + * 收集器在工作时, 会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMillis 以内 + * 停顿时间的缩短, 是牺牲了吞吐量(以前10s一次100ms的GC, 现在5s一次70ms的GC)和新生代空间(对体积小的内存收集比较快)换来的, 这也导致GC发生得更加的频繁 + * 过小的话, GC停顿时间确实下降了, 但是吞吐量也下降了 + + -XX:CMSInitiatingOccupancyFraction + * 设置CMS收集器在老年代空间被使用多少后出发垃圾收集,默认68% + * 如果中老年代增长不是很快, 可以适当的调高参数(0 - 100 百分比), 降低GC次数 + * 如果CMS运行期间,预留的内存不足以业务线程的使用, 就会出现一次:Concurrent Mode Failure 失败 + * 这是JVM会启动预案, 临时启动:Serial Old 收集器来重新对老年代进行垃圾收集 + * 也就是说设置太高, 可能会导致大量的Concurrent Mode Failure 失败, 性能反而降低 + + -XX:+UseCMSCompactAtFullCollection + * 用于在CMS顶不住要进行Full GC的时候, 开启内存碎片的合并整理过程 + * 内存整理的国产没法并发, 空间随便问题解决了, 但是业务线程停顿时间不得不变长了 + + -XX:CMSFullGCsBeforeCompaction + * 设置执行多少次不压缩的FullGC后, 跟着来一次带压缩的 + * 默认为0, 表示每次进入Full GC时都进行碎片整理 + + -XX:+UseG1GC + * 使用G1收集器 \ No newline at end of file diff --git "a/Jvm/jvm-gc-\346\224\266\351\233\206\345\231\250.java" "b/Jvm/jvm-gc-\346\224\266\351\233\206\345\231\250.java" new file mode 100644 index 00000000..8950760e --- /dev/null +++ "b/Jvm/jvm-gc-\346\224\266\351\233\206\345\231\250.java" @@ -0,0 +1,249 @@ +------------------------ +Gc收集器 | +------------------------ + # 收集算法是内存回收的方法论, 收集器则是实现了 + # jvm规范对收集器怎么去实现, 没有任何规定, 所以不同厂家, 不同版本的可能不一样 + + # GC收集器目前主要的有 + * 新生代收集器 + Serial + ParNew + Parallel Scavenge + + * 老年代收集器 + Concurrent Mark Sweep(CMS) + Parallel Old + Serial Old(MSC) + + * 全堆收集器 + Garbage First(G1) + + * 不同的收集器可以共存, 组合使用 + * 它们之间没有绝对的最完美的收集器,(如果有, 也不用实现那么多出来) + + + +------------------------ +Gc收集器关系图 | +------------------------ + +-------------------------年轻代回收------------------------------------+ + |[Serial] [ParNew] [Parallel Scavenge] | + |--------------------------老年代回收-------------------------------[G1]| + |[Concurrent Mark Sweep(CMS)] [Serial Old(MSC)] [Parallel Old] | + +-----------------------------------------------------------------------+ + + # 可以组合的GC收集器 + [Serial] + Concurrent Mark Sweep(CMS)] + + [Serial] + [Serial Old(MSC)] + + [ParNew] + [Concurrent Mark Sweep(CMS)] + + [ParNew] + [Serial Old(MSC)] + + [Parallel Scavenge] + [Serial Old(MSC)] + + [Parallel Scavenge] + [Parallel Old] + + [Serial Old(MSC)] + [Concurrent Mark Sweep(CMS)] + + +------------------------ +Serial | +------------------------ + # 最基本的, 历史最悠久的收集器, 在JDK1.3.1之前是虚拟机新生代的唯一选择 + # 这个收集器是一个单线程的收集器 + # 它在执行GC的时候, 会暂停所有的工作线程, 直到它收集结束 + + # 它在Client模式下的虚拟机来说, 是一个很好的选择 + * Client模式(桌面环境), 一般分配给jvm管理的内存不是很大 + * GC导致的停顿时间, 完全可以控制在几十毫秒 - 100毫秒以内, 这是可以接收的 + * 单线程, 免去了多线程的切换, 可以专注的进行收集工作, 效率更高 + + # 工作模式(采用复制算法) + 1. (第一阶段标记)暂停业务线程, 单线程收集(新生代采用复制算法) + 2. 唤醒业务线程 + 3. (第二阶段标记)暂停业务线程, 单线程收集(老年代采用标记整理算法) + +------------------------ +ParNew | +------------------------ + # 它其实就是 Serial 的多线程版本 + # 还可以通过一系列的JVM参数对它进行控制 + -XX:SurvivorRatio + -XX:PretenureSizeThreshold + -XX:HandlePromotionFailure + + # 他的工作流程 + 1. (第一阶段标记)暂停业务线程, 多线程收集(新生代采用复制算法) + 2. 唤醒业务线程 + 3. (第二阶段标记)暂停业务线程, 单线程收集(老年代采用标记整理算法) + + + # 它相对于 Serial 并没太多的创新之处, 甚至连部分代码都是共用的, 但它却是很多在运行Server模式的JVM的'新生代'首选收集器 + * 因为一个与性能无关的原因: 除了 Serial 以外, 只有它能与Concurrent Mark Sweep(CMS)收集器配合工作 + + # 在单核的CPU环境中, 它的效果不一定比 Serial 好 + * 甚至由于多线程交互的开销, 可能不如 Serial + * 这个收集器, 在通过超线程技术实现的两个CPU核心环境中, 都不能100%的 保证超越 Serial + + # 随着CPU核心数量的增加, 它对于GC时系统资源的有效利用还是很有好处的 + * 默认开启的收集线程数量与CPU的核心数量相同 + * 在核心数量非常多的情况下, 可以通过参数来限制垃圾收集的线程的数量 + -XX:ParallelGCThreads=10 + + + # 指定使用ParNew收集器 + -XX:+UseConMarkSweepGC + * 使用Concurrent Mark Sweep(CMS)作为老年代收集器 + * 如果使用该参数, 默认就会使用: ParNew 作为新生代的收集器 + + -XX:+UseParNewGC + * 强制系统使用 ParNew 作为新生代的收集器 + +------------------------ +Parallel Scavenge | +------------------------ + # 新生代收集器, 使用复制算法, 也是可以并行收集的 + + # 看似与 ParNew 一样, 但是它的目的则是达到一个可控制的吞吐量 + 吞吐量 = CPU用于业务线程的时间 / (CPU用于业务线程的时间 + CPU用于垃圾收集的时间) + + * 虚拟机运行了 100分钟, 垃圾回收花费了1 分钟, 则吞吐量就是: 99% + * 因为与吞吐量相关,也被称为:吞吐量优先的收集器 + + # 提供了参数用于精准的控制吞吐量 + -XX:MaxGCPauseMillis + * 置最大垃圾收集停顿时间, 它的值是一个大于 0 的整数 + * 收集器在工作时, 会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMillis 以内 + * 停顿时间的缩短, 是牺牲了吞吐量(以前10s一次100ms的GC, 现在5s一次70ms的GC)和新生代空间(对体积小的内存收集比较快)换来的, 这也导致GC发生得更加的频繁 + * 过小的话, GC停顿时间确实下降了, 但是吞吐量也下降了 + + + -XX:GCTimeRatio + * 设置吞吐量大小, 它的值是一个大于 0 小于 100 之间的整数 + * 可以理解为: 垃圾收集时间占总时间的比例 + * 默认 GCTimeRatio 的值为 99, 那么系统将花费不超过 1 / (1 + 99) = 1% 的时间用于垃圾收集 + + -XX:+UseAdaptiveSizePolicy + * 打开自适应 GC 策略, 在这种模式下, 其他的一些属性不需要自己去设置, 参数会被自动调整, 以达到在堆大小, 吞吐量和停顿时间之间的平衡点 + -Xmn(新生代大小) + -XX:+SuivivorRatio(Eden和Survivor区的比例) + -XX:+PretenureSizeThreshold(晋升老年代对象年龄) + * 使用自适应GC策略, 只需要把基本的内存数据设置好,例如堆内存大小值 + * 然后仅仅关注/设置最大停顿时间:-XX:MaxGCPauseMillis + * 或者给JVM设置一个最大吞吐量 -XX:GCTimeRatio 的优化目标, 具体的工作细节就由jvm完成 + +------------------------ +Serial Old | +------------------------ + # 它是 Serial 收集器的老年代版本, 也是一个单线程的收集器 + # 它存在的意义, 也是给 Client 模式的JVM使用 + # 如果在Server模式下使用, 它有两个用途 + * 在JDK1.5之前与 Parallel Scavenge 搭配使用 + + * 作为 Concurrent Mark Sweep(CMS) 收集器的后备预案, 在并发收集发生 Concurrent Mode Failure 时使用 + * 出现此现象的原因主要有两个:一个是在年老代被用完之前不能完成对无引用对象的回收 , 一个是当新空间分配请求在年老代的剩余空间中得到满足 + + # Serial 配合 Serial Old工作模式 + 1. (第一阶段标记)暂停业务线程, 单线程收集(新生代采用复制算法) + 2. 唤醒业务线程 + 3. (第二阶段标记)暂停业务线程, 单线程收集(老年代采用标记-整理算法) + +------------------------ +Parallel Old | +------------------------ + # 它其实是 Parallel Scavenge 收集器的老年代版本, 使用标记清理算法, 在JDK1.6以后才提供 + + # 在此之前, 新生代的Parallel Scavenge收集器存在一个尴尬的问题 + * 如果选择了Parallel Scavenge收集器, 那么老年代除了Serial Old收集器外别无选择 + * 由于老年代收集器Serial Old在服务端性能上的拖累, 就算使用 Parallel Scavenge 也未必能获得最大的吞吐量效果 + * 这种组合的吞吐量, 不一定比使用 ParNew + Serial Old 更好 + + # Parallel Old 收集器出现后, 就有了和Parallel Scavenge收集器的一个优良组合 + * 在注重吞吐量和CPU资源敏感的场合, 都可以优先考虑 Parallel Scavenge + Parallel Old + + + # Parallel Scavenge 配合 Parallel Old 工作原理 + 1. (第一阶段标记)暂停业务线程, 多线程收集 + 2. 唤醒业务线程 + 3. (第二阶段标记)暂停业务线程, 多线程收集 + +-------------------------- +Concurrent Mark Sweep(CMS)| +-------------------------- + # 以获取最短停顿时间为目标的收集器, 基于标记-清除的算法实现 + # 目前很大一部分Java都是应用在B/S环境下的, 尤其重视响应速度, 希望系统停顿时间短, CMS就非常适合 + # 它的执行过程稍微要复杂一点 + 1. 初始标记(Initial Mark) + * 停止业务线程 + * 标记一下GC Roots能直接关联的对象, 速度很快 + + 2. 并发标记(Concurrent Mark) + 3. 重新标记(Remark) + * 停止业务线程 + * 修正并发标记期间, 因为程序逻辑导致标记产生变动的对象记录 + + 4. 并发清除(Concurrent Sweeo) + + * 整个过程中, 最耗时的并发标记和并发清除过程不会停止业务线程 + + # 它是优秀的收集器, 并发收集, 低停顿, 但是也有一些缺点 + 1, 对CPU资源敏感 + * 事实上, 面向并发设计的程序都对CPU资源敏感 + * 并发阶段, 虽然不会停止业务线程, 但是因为占用了一部分CPU资源, 从而会导致应用程序变慢, 导致总吞吐量变低 + + * CMS默认启动的回收线程数:(CPU数量 + 3) / 4, 也就是说在CPU核心数 >= 4的时候, 并发回收时, 占用 25% d CPU资源 + * 如果CPU核心数不足 4, 业务线程受到的影响就比较大了, 因为需要付出一半的算力去执行GC + + * 为了解决这个问题, JVM提供了一种"增量式并发收集器" , 没用,已经被标识为过时 + + + 2, 无法处理浮动垃圾, 可能出现:Concurrent Mode Failure 失败而导致另一次Full GC的产生 + * 浮动垃圾就是, 在执行并发清理的时候, 因为是并发,业务线程不会停止, 在此期间产生的垃圾, 只能在下一次GC处理 + * 也就是说要留足内存空间给业务线程使用, 因此CMS不能跟其他的收集器一样要等到老年代几乎被完全填满了后再进行收集,因为需要预留空间给并发业务线程使用 + + * JDK1.5环境下,默认老年代内存使用了 68%后, 就会触发该收集器, 这个设置比较保守 + * 如果中老年代增长不是很快, 可以适当的调高参数(0 - 100 百分比), 降低GC次数 + -XX:CMSInitiatingOccupancyFraction + + * 如果CMS运行期间,预留的内存不足以业务线程的使用, 就会出现一次:Concurrent Mode Failure 失败 + * 这是JVM会启动预案, 临时启动:Serial Old 收集器来重新对老年代进行垃圾收集 + * 也就是说-XX:CMSInitiatingOccupancyFraction设置太高, 可能会导致大量的Concurrent Mode Failure 失败, 性能反而降低 + + 3, 因为使用标记清除算法,导致大量的内存碎片 + * 碎片过多, 这会给内存分配带来很大的麻烦 + * 往往老年代还有足够的内存, 但是因为找不到连续的空间不得不触发一次 Full GC + + * 为了解决这个问题,CMS提供了一个参数(默认已经开启) + -XX:+UseCMSCompactAtFullCollection + + * 用于在CMS顶不住要进行Full GC的时候, 开启内存碎片的合并整理过程 + * 内存整理的国产没法并发, 空间随便问题解决了, 但是业务线程停顿时间不得不变长了 + + * 设置执行多少次不压缩的FullGC后, 跟着来一次带压缩的 + -XX:CMSFullGCsBeforeCompaction + + * 默认为0, 表示每次进入Full GC时都进行碎片整理 + + + + +-------------------------- +G1收集器 | +-------------------------- + # Garbage-First, 收集器是当前收集器技术发展的最前沿成果之一 + * 从JDK7开始 + * 设计的目标就是为了替代掉:Concurrent Mark Sweep(CMS) 收集器 + + # 它是全堆收集器, 老年代, 新生代都行 + + # 因为是全堆收集器, 所以使用G1的时候, 内存布局就有点儿与其他的收集器不同 + * 它把整个堆划分为多个相等大小的独立区域(Region) + * 还是保留了新生代, 老年代的概念, 但是新生代和老年代不再是物理隔离了, 它们都是一部分 Region 的集合 + + * 引入了分区的概念, 弱到了分代的概念 + + # 比较重要, 单独写 + + diff --git "a/Jvm/jvm-gc-\346\227\245\345\277\227.java" "b/Jvm/jvm-gc-\346\227\245\345\277\227.java" new file mode 100644 index 00000000..5bb5cee9 --- /dev/null +++ "b/Jvm/jvm-gc-\346\227\245\345\277\227.java" @@ -0,0 +1,49 @@ +---------------------------------- +GC日志 | +---------------------------------- + # 每个收集器的日志形式可能不一样 + # 虚拟机为了方便阅读, 把每个收集器的日志都维持了一定的共性 + +0.513: [GC (System.gc()) [PSYoungGen: 5012K->1258K(38400K)] 5012K->1266K(125952K), 0.0014624 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] +0.196: [Full GC (System.gc()) [PSYoungGen: 1258K->0K(38400K)] [ParOldGen: 8K->1156K(87552K)] 1266K->1156K(125952K), [Metaspace: 3473K->3473K(1056768K)], 0.0054130 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] + + * 最前面的数字表示JVM的运行时间, 秒数 + + * GC/Full GC 表示GC的类型, 而不是区分哪个年代的 + * 如果是Full GC, 肯定是发生了 stop the world 的 + + * 收集器的类型,例如:ParNew + * (System.gc()), 表示调用 System.gc() 方法触发的收集 + + * 表示GC发生的区域, 这个区域名称和收集器的名称有密切的关系 + DefNew(Serial 收集器) + ParNew(ParNew 收集器) + PSYoungGen(Parallel Scavenge 收集器) + Tenured + Perm + + * 5012K->1258K(38400K) + * GC前的内存已使用量 -> GC后内存已使用量(该区域的内存总容量) + + * 5012K->1266K(125952K) + * Java堆已使用的容量 -> GC后堆已使用的容量(Java堆总容量) + + * 0.0014624 secs + * 该区域GC所占用的时间 + + * [Times: user=0.00 sys=0.00, real=0.00 secs] + * 更为详细的时间数据, user,sys,real 跟Linux的time命名输出的时间含义一样 + user:用户态消耗CPU的时间 + sys:.. + radl.. + + + Heap + PSYoungGen total 38400K, used 333K [0x00000000d5a00000, 0x00000000d8480000, 0x0000000100000000) + eden space 33280K, 1% used [0x00000000d5a00000,0x00000000d5a534a8,0x00000000d7a80000) + from space 5120K, 0% used [0x00000000d7a80000,0x00000000d7a80000,0x00000000d7f80000) + to space 5120K, 0% used [0x00000000d7f80000,0x00000000d7f80000,0x00000000d8480000) + ParOldGen total 87552K, used 1156K [0x0000000080e00000, 0x0000000086380000, 0x00000000d5a00000) + object space 87552K, 1% used [0x0000000080e00000,0x0000000080f21140,0x0000000086380000) + Metaspace used 3481K, capacity 4496K, committed 4864K, reserved 1056768K + class space used 376K, capacity 388K, committed 512K, reserved 1048576K \ No newline at end of file diff --git a/Jvm/jvm-gc.java b/Jvm/jvm-gc.java new file mode 100644 index 00000000..3b855cbb --- /dev/null +++ b/Jvm/jvm-gc.java @@ -0,0 +1,220 @@ +----------------------- +Garbage Collection | +----------------------- + # GC考虑的三个东西 + * 哪些内存需要回收? + * 什么时候回收? + * 如何回收? + +----------------------- +引用计数算法 | +----------------------- + # 给对象添加一个引用计数器 + * 添加引用, 计数器 +1 + * 引用时效, 计数器 -1 + + * 计数器 == 0, 则对象不可用 + + # Java主流的JVM没有选用这个算法来管理内存 + * 主要是这东西很难解决'循环引用'的问题 + class A { + public Object instance; + } + class B{ + public Object instance; + } + public class Main { + public static void main(String[] args) { + A a = new A(); + B b = new B(); + a.instance = b; + b.instance = a; + a = null; + b = null; + System.gc(); + } + } + +----------------------- +可达性分析算法 | +----------------------- + # 主流的语言, 都是采用这个GC算法 + - Java + - C#(辣鸡语言) + - Lisp + + # 基本的思想 + * 通过一系列的 'GC Roots' 作为起始点 + * 从起始点往下搜索的路径, 成为引用链(树状) + * 当一个对象到 'GC Roots' 没有任何引用链的时候, 就可以证明该对象不可用的 + + # 可以作为 'GC Roots' 的对象 + * 虚拟机栈用引用的对象 + * 方法区中类静态属性引用的对象 + * 本地方法栈中JNI(Native方法)引用的对象 + + +----------------------- +引用 | +----------------------- + # 强引用(Strong Refrence) + * 普遍存在的: Object instance = new Object(); + * 只要关联还在, 不会回收内存 + + # 软引用(Soft Reference) + * 描述一些有用,但非必须的对象 + * 在系统即将发生内存溢出之前, 会把这些对象列进回收范围内, 进行二次回收 + * 如果内存还是不足, 则抛出内存溢出异常 + * JDK 提供的实现类:SoftReference + + # 弱引用(Weak Reference) + * 描述非必须的对象 + * 它只能存在于下一次GC之前, 下一次GC, 不论内存是否足够, 都会回收掉那些仅被弱引用关联的对象 + * JDK 提供的实现类:WeakReference + + # 虚引用(Phantom Reference) + * 虚引用关系的存在不会影响它的生存时间, 无法根据虚引用访问到对象 + * 设置虚引用的唯一目的:在这个对象被GC回收时, 收到系统通知 + * JDK提供的实现类:PhantomReference + + + +---------------------------- +对象的回收至少会经过两次标记| +---------------------------- + # 第一次标记 + * 经过可达性分析后, 发现对象没有与 Gc Roots 关联 + * 如果覆写了 finalize 方法, 并且还没被调用过 + - 把对象放置到一个名为:F-Queue 的队列中, 稍后会通过一个虚拟机自动创建的一个低优先级线程(Finalizer)去执行finalize方法 + - 虚拟机只是会触发这个方法, 但是不会阻塞到这个方法结束, 因为如果这个方法执行缓慢, 或者发生了死循环, 可能导致: F-Queue 队列中的其他对象永远处于等待状态, 甚至导致GC系统崩溃 + + # 第二次标记 + * 如果在第一次标记的时候,如果对象通过 finalize() 发生了自救的行为(把自己赋值到其他的对象或者其他的行为, 反正可以引用到GC Roots), 那么第二次标记就会把对象从即将回收集合移除 + + # finalize() 不建议使用, 快忘记 + * 这并不是C/C++的析构函数, 而是为了骗搞C/C++的开发者上Java贼船而鼓捣的东西 + * 运行代价高昂, 不确定性大, 无法保证各个对象的调用顺序 + + +---------------------------- +关于方法区的回收 | +---------------------------- + # 蛮多人认为方法区(HopSpot中的 Metaspeace(永久代))是没有GC的 + * jvm规范也定义了不需要在方法区中实现gc + * 而且在方法区中实现gc, 性价比比较低(耗费了性能, 但是回收不到多少空间) + * 比起搜刮方法区, 不如果搜刮新生代 + + # 元空间的垃圾回收, 分为两个部分: 废弃的常量, 无用的类 + + # 废弃的常量 + * 这个跟回收Java对象非常相似, 不存在引用则回收 + + # 无用的类, 这个判断比较苛刻, 必须满足以下的所有条件 + * 该类的所有实例都已经被回收, 系统中不存在该类的实例 + * 加载该类的 ClassLoader 已经被回收 + * 该类的 Class 对象, 没有在任何地方被引用, 也就说无法在任何地方通过反射获取到该类的方法 + + * 满足条件, 可以被进行回收, 而回收不是必须的, 可以通过 jvm 参数控制 + * 关闭Class的回收 + -Xnoclassgc + + * 查看类的加载/卸载信息 + -verbose:class -XX:+TraceClassLoading -XX:+TraceClassUnloading + +---------------------------- +关于直接内存的回收 | +---------------------------- + # Java堆GC的时候, 会顺带的执行直接内存的GC + # 或者在Java中手动的执行 System.gc() 来触发 + + +------------------------------------ +垃圾收集算法-标记清除法 | +------------------------------------ + # 这个是基本的一个算法, 其他的很多算法都是基于这个算法的思路进行优化的 + # 主要就是两个阶段, 标记和清除 + * 标记的过程, 上面已经说了 + * 清除就不用多说 + + # 它有两个问题 + * 效率问题: 标记和清除的效率都不高 + * 空间问题 + - 标记清除之后会产生大量不连续的内存碎片 + - 内存随便过多后, 会占用大量的内存空间, 可能会导致内存不足而不得不提前触发下一次GC + + +------------------------------------ +垃圾收集算法-复制算法 | +------------------------------------ + # 为了解决效率问题, 把内存分为同等大小的两块儿, 每次使用其中一块儿 + * 当一块儿用完了之后, 就把该块儿上已经存活的对象移动到另一块儿, 然后释放该内存块儿 + * 这样内存分配的时候, 不用考虑内存碎片的问题, 只需要移动堆指针, 按照顺序分配内存 + * 实现简单, 运行高效, 但是内存被缩小为了原来的一半 + + # 现在商业的JVM都是这种算法 + * IMB研究表明: 98% 的新生对象都是朝生夕死, 所以并不需要按照 1:1 的比例来划分内存空间 + * 而是划分为一块较大的:Eden空间,和两块较小的:Survivor空间 + + * 每次使用Eden和其中一块Survivor(from)空间, 执行回收的时候, 把存活的对象复制到另一块Survivor(to)空间, + * 最后清理掉Eden和刚才使用的Survivor(from)空间 + + * HotSpot默认的Edent和Survivor空间大小比例是: 8:1 + [8|1|1] + + * 也就是说,新生代的容量为 90%, 仅有 10% 的内存被'浪费' + + * 也不能保证每次GC后, 存活的对象大小都不大余 10% (要移动到另一块Survivor(to)空间,它只有 10% 的内存大小) + * 当Survivor(to)不够使用的时候, 还需要依赖其他的内存进行担保分配 - 老年代(ParOldGen) + + +------------------------------------ +垃圾收集算法-标记-整理算法 | +------------------------------------ + # 复制收集算法在存活对象较多的时候, 要进行较多的复制工作, 效率会更低 + # 更关键的是, 不想浪费 50% 的空间, 就需要有额外的空间进行担保, 以应对内存中所有对象都是 100% 存活的情况 + * 所以老年代(ParOldGen)一般不能直接选用这种算法 + + # 根据老年代的特点, 一种: 标记 - 整理 的算法被人提了出来 + + * 与标记清除的过程仍然是一样的, 但是后续的步骤不是对可回收对象直接进行清理 + * 而是让所有存活对象都向一端移动, 然后直接清理掉端边界以外的内存 + + +------------------------------------ +垃圾收集算法-分代收集算法 | +------------------------------------ + # 目前商业的JVM的垃圾收集都采用这种算法 + + # 根据对象存活周期的不同, 把内存划分为几块, 根据不同年代的特点采用不同的收集算法 + + # 新生代(PSYoungGen) + * 每次垃圾回收都有大量的对象死去, 只有少量存活, + * 使用复制法, 仅仅需要复制少量存活的对象就可以完成收集 + + # 老年代(ParOldGen) + * 对象存活率高, 没有额外的空间对它进行分配担保 + * 必须使用 标记清除 或者 标记整理 算法来完成回收 + +------------------------------------ +HotSpot的算法实现 | +------------------------------------ + # 枚举根节点 + * 作为GC Roots的对象一般都是在:全局引用(常量, 类静态属性), 执行上下文中(栈帧, 本地变量表) + * 现在很多应用, 仅方法区就有好几百MB, 如果挨个找的话, 会消费非常多的时间 + + * GC 执行时, 必须要停顿所有的Java线程,Sun称之为: Stop The World + * GC过程, 如果对象引用关系还可以不断变化的话, 可达性分析算法的结果可能不准确 + + * 系统暂停后,不需要去一个不漏的检查全局引用, 系统上下文 + * 使用一个 OoMap的数据结构来完成 + + + # 安全点 + * 并非执行程序也并非在任何执行点都能停顿下来执行GC, 只有在到达安全点才能暂停 + + + # 安全区域 + * 在没有分配到CPU时间的线程, 典型的就是: sleep,wait 状态的线程, 它们没法跑到安全点 + * 安全区域就是, 在一段儿代码中, 引用关系不会发生变化, 在这个区域中的任何地方开始GC都是安全的 + + diff --git "a/Jvm/jvm-load-\347\261\273\345\212\240\350\275\275\345\231\250.java" "b/Jvm/jvm-load-\347\261\273\345\212\240\350\275\275\345\231\250.java" new file mode 100644 index 00000000..08435aea --- /dev/null +++ "b/Jvm/jvm-load-\347\261\273\345\212\240\350\275\275\345\231\250.java" @@ -0,0 +1,157 @@ +----------------- +ClassLoader | +----------------- + # 类和类加载器 + * 每个类, 都要和加载它的加载器一起, 确立在jvm中的唯一性 + + * 比较两个类是否相等, 只有在这两个类都是同一个类加载器加载的前提下才有意义 + * 否则, 一个class文件, 被一个jvm加载, 但是存在两个加载器进行加载, 那么这俩加载后的类就肯定不相等 + + * 这里的相等包括了 Class 对象的: equals(), isAssignableForm(), instance() 返回的结果 + * 也包括使用关键字: instanceof 判断结果 + + + + + # 类加载类型 + * 启动类加载器 Bootstrap ClassLoader + * 在 $JAVA_HOME\lib 目录中, 或者通过 -Xbootclasspath 参数指定 + * 使用 C++ 实现, 是JVM自身的一部分, 不能被程序代码获取到 + * 查看它的加载目录:System.getProperty("sun.boot.class.path") + + * 扩展类加载器 Extension ClassLoader + * 由: sun.misc.Launcher$ExtClassLoader 实现, 实现接口:ClassLoader + * 它负责加载 $JAVA_HOME\lib\ext 目录中的, 或者被 java.ext.dirs 系统变量指定的路径中的所有类库 + * 开发者可以直接使用扩展类加载器 + * 查看它的加载目录:System.getProperty("java.ext.dirs") + + * 应用类加载器 Application ClassLoader + * 由: sun.misc.Launcher$AppClassLoader 实现, 实现接口:ClassLoader + * 由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值, 所以也被称为系统类加载器 + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + + * 它负责加载用户类路径(CLASS_PATH)上所指定的类库 + * 如果程序中没自定义过类加载器, 一般情况下这个就是默认的类加载器 + * 查看它的加载目录:System.getProperty("java.class.path") + + Class driverClass = Class.forName("com.mysql.cj.jdbc.Driver"); + System.out.println(driverClass.getClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2 + + * 程序里面获取各个加载器 + ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); + System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2 + + ClassLoader extClassLoader = appClassLoader.getParent(); + System.out.println(extClassLoader); //sun.misc.Launcher$ExtClassLoader@cc34f4d + + ClassLoader bootstrapClassLoader = extClassLoader.getParent(); + System.out.println(bootstrapClassLoader); // null ,C++实现,用户代码无法获取 + + + # 双亲委派机制 + * 双亲委派机制, 要求除了启动类加载器以外, 都必须要有父类加载器 + * 子父关系并不是由继承实现的, 而是使用组合的方式实现的 + + * 如果一个类加载器收到了类加载请求, 它首先会把这个请求委托给父类加载器去完成 + * 每一个类加载器都是如此, 因此最后会委托到启动类加载器进行加载 + * 父级启动类加载器成功加载, 那么就直接返回加载后的结果 + * 如果父类无法加载, 就尝试自己加载, 如果加载失败, 则抛出异常: ClassNotFoundException + + * 这种机制的好处在于, 你没法使用自己的加载器,加载自己定义的那些与系统类重名的类, 安全 + * ClassLoader 的 loadClass(String name) 方法实现代码 + protected Class loadClass(String name, boolean resolve)throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + // 首先, 检查类是否被加载过 + Class c = findLoadedClass(name); + if (c == null) { + long t0 = System.nanoTime(); + try { + if (parent != null) { + c = parent.loadClass(name, false); + } else { + c = findBootstrapClassOrNull(name); + } + } catch (ClassNotFoundException e) { + // 父级加载器抛出了 ClassNotFoundException , 说明父级加载失败 + } + + if (c == null) { + // 父级加载器, 加载失败的情况下, 调用本身的 findClass() 进行加载 + + long t1 = System.nanoTime(); + c = findClass(name); + + // 定义的类加载器的记录统计数据 + sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); + sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); + sun.misc.PerfCounter.getFindClasses().increment(); + } + } + if (resolve) { + resolveClass(c); + } + return c; + } + } + + * 就算是自己定义了类加载器, 使用 defineClass() 去加载一个以 "java.lang" 开头的类, 也不会成功 + * 会得到JVM抛出的安全异常 + + # 破坏双亲委托机制 + * 双亲委派机制, 并不是强制性的, 而是推荐给开发者的加载器实现模式 + + * 双亲委派机制第一次被破坏, 是因为历史遗留问题 + * 该机制在 jdk1.2 才被引入, 但是类加载器和抽象类: ClassLoader 在 jdk1.0 就存在 + * 对于那些, 已经存在的用户自定义加载器实现, 不得不做出妥协, + + * 为了向前兼容, JDK1.2 后在 ClassLoader 添加了一个方法: protected Class findClass(String name) + * 在此之前, 用于继承 ClassLoader 方法, 唯一目的就是为了覆写: loadClass() 方法, 因为JVM在进行类加载的时候会调用私有方法:private Class loadClassInternal(String name) + * 而这个方法的唯一逻辑就是去调用自己的: loadClass() 方法 + + * JDK1.2后不建议覆写: loadClass() 方法, 而是把加载逻辑写到 findClass() 方法中 + * loadClass() 方法里面的逻辑, 当父加载器加载失败, 才会调用 findClass() 完成加载 + * 这样的规则是符合双亲委派机制的 + + + + * 双亲委派机制第二次被破坏, 是因为这个模型自身的缺陷导致的 + * 该机制很好的解决了基础类的统一加载的问题(越是基础的类,越由最上层的加载器进行加载) + + * 但是基础的类, 可能需要回调用户的代码, 例如: JNDI + * JNDI的目的是最资源进行集中管理和查找, 它需要调用由厂商实现, 并且部署在classpath下的JNDI接口提供者的代码 + * 但是启动类加载器, 不认识这些代码 + + * 为了解决这个问题, Java设计团队鼓捣了一个 线程上下文加载: Thread Context ClassLoader(其实设计得不太优雅) + * Thread 的实例有一个方法:public void setContextClassLoader(ClassLoader cl) + * 如果创建线程的时候, 该加载器还没设置, 它将会从父级线程继承一个, 如果应用程序全局范围内都没设置过的话, 那么这个类加载器默认就是应用程序类加载器 + + * JDNI服务使用这个线程上线我的加载器去加载所需要的SPI代码, 也就是父类加载器请求子类加载器去完成类加载 + * 这种行为, 实际上就是打通了双亲委派模型的层次结构来逆向使用加载器 + * 已经违背了双亲委派模型的一般性原则, 但是没则, JAVA涉及 SPI的加载动作, 都基本才用这种方式 + + + + * 双亲委派机制的第三次被破坏, 是因为对程序的动态性追求导致的 + * 动态性: 代码热替换, 模块热部署 + * 目前OSGi已经是业务事实上的Java模块标准, 而OSGi实现模块化热部署的关键就是它自定义的类加载器机制的实现 + + * 每一个程序模块都有一个自己的类加载器, 当需要更换一个 Bundle 的时候, 就把 Bundle 连加载器一起换掉, 实现代码的热替换 + * 在OSGi环境下, 类加载器不是双亲委派模型中的树结构,而是更加复杂的网状结构 + + * OSGi加载类的搜索顺序 + 1. 把 java.* 开头的类委派给父类加载器加载 + 2. 否则, 把委派列表名单内的类委派给父类加载器加载 + 3. 否则, 把 import 列表中的类委派给 Export 这个类的 Bundle 的类加载器加载 + 4. 否则, 查找当前 Bundle 的 ClassPath, 使用自己的类加载器加载 + 5. 否则, 查找类是否在自己的 Fragment Bundle 中, 如果在, 则委托给 Fragment Bundle 的类加载器加载 + 6. 否则, 查找 Dynamic import 列表的 Bundle, 委派给对应的 Bundle 的类加载器加载 + 7. 否则, 类查找失败 + + * 只有开头2点符合双亲委派规则, 其余的类查找都是在平级的类加载器中进行的 + + + + + + + \ No newline at end of file diff --git "a/Jvm/jvm-load-\350\207\252\345\256\232\344\271\211\347\261\273\345\212\240\350\275\275\345\231\250.java" "b/Jvm/jvm-load-\350\207\252\345\256\232\344\271\211\347\261\273\345\212\240\350\275\275\345\231\250.java" new file mode 100644 index 00000000..c378d52f --- /dev/null +++ "b/Jvm/jvm-load-\350\207\252\345\256\232\344\271\211\347\261\273\345\212\240\350\275\275\345\231\250.java" @@ -0,0 +1,56 @@ +----------------- +ClassLoader | +----------------- + # 类加载器的几个关键方法 + protected final Class defineClass(String name, byte[] b, int off, int len) + protected final Class defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain) + protected final Class defineClass(String name, java.nio.ByteBuffer b, ProtectionDomain protectionDomain) + * 用于把字节互数据解析为JVM能够识别的Class对象 + * 该方法只是会生成对象, 但是还没 resolve + + protected Class findClass(String name) + * 自定义加载器, 一般覆写父类的 findClass 方法来完成类的加载 + + + protected Class loadClass(String name, boolean resolve) + public Class loadClass(String name) + + protected final void resolveClass(Class c) + * 解析初始化类, 就是链接 + + # 自定义类加载器 + class MyClassLoader extends ClassLoader { + @Override + protected Class findClass(String name) throws ClassNotFoundException { + // 尝试委托加载 + Class clazz = super.loadClass(name); + if (clazz != null){ + return clazz; + } + /** + * TODO 自己尝试加载, 如果不存在, 抛出异常:ClassNotFoundException + * TODO 如果存在则获取到字节数据, 转换为类对象 + */ + + byte[] data = new byte[1024]; + clazz = defineClass("", data, 0, data.length); + return clazz; + } + } + + * 最好不要重写 loadClass 方法, 因为这样容易破坏双亲委托模式 + + # 一般都还可以考虑继承:URLClassLoader + * 因为它帮我们完成了大部分的加载工作 + +--------------------------------------------- +应用场景-加载自定义路径下的class文件 | +--------------------------------------------- + +--------------------------------------------- +应用场景-需要对自己加载的类做特殊处理 | +--------------------------------------------- + +--------------------------------------------- +应用场景-热部署 | +--------------------------------------------- \ No newline at end of file diff --git "a/Jvm/jvm-load-\350\277\207\347\250\213.java" "b/Jvm/jvm-load-\350\277\207\347\250\213.java" new file mode 100644 index 00000000..5d12825f --- /dev/null +++ "b/Jvm/jvm-load-\350\277\207\347\250\213.java" @@ -0,0 +1,97 @@ +------------------------ +类加载过程 | +------------------------ + # 加载 + * 类的加载阶段需要做3件事情 + + 1. 通过一个类的全限定名来获取定义该类的字节数据 + * 不仅仅是磁盘的class文件 + * 可以从ZIP包读取, (jar/war,ear) + * 从可以网络读取,Applet + * 运行时计算生成, Proxy 代理,就是使用: ProxyGenerator.generateProxyClass 来为特定的接口生成形式为"*$Proxy"的代理类字节数据 + * 可以由其他的文件生成, JSP, 通过JSP生成类 + * 可以从数据库读取 + + 2. 把这个字节数据代表的静态存储结构转换为方法区的运行时数据结构 + + 3. 在内存中生成一个代表该类的 Class 对象, 作为方法区访问这个类的各种数据的入口 + * Class 对象比较特殊, 虽然它是对象, 但是它是在方法区中的 + + + + * 对于数组而言, 加载有所不同, 数组类本身不需要通过类加载器创建 + * 它是由jvm直接创建的, 但是数组类和类加载器还是有密切的关系, 因为数组的元素类型, 最终是要靠类加载器去创建 + * 一个数组的创建过程需要遵循的规则 + - 如果数组的类型(去掉一个维度后的类型), 是引用类型就采取上述的加载过程 + + - 如果数组的类型不是引用类型(int[]), JVM会把数组标记为与引导加载器关联 + + + # 验证 + * 连接阶段的第一步 + * 验证的目的是为了确保 Class 文件的字节流中包含的信息符合当前JVM虚拟机的要求, 并且不会危害JVM的安全 + + * 验证阶段大致会完成4个阶段的校验动作 + 1. 文件格式验证 + * 是否以魔输:0xCAFEBABE 开头 + * 主,次版本号是否在当前JVM虚拟机的处理范围内 + * 常量池的常量是否头不被支持的常量类型(检查常量 tag 标志) + * CONSTANT_Utf8_info, 型的常量中是否有不符合 utf8 编码的数据 + * Class 文件中各个部分及文件本身是否有被删除, 或者附加的其他信息 + * ... 更多 + + 2. 元数据验证 + * 是否有父类(除了 Object 外, 都应该有父类) + * 是否继承了不允许被继承的类(final 修饰的类) + * 如果类不是抽象类, 是否实现了其父类接口中的所有抽象方法 + * 类中的字段, 方法是否与父类产生矛盾(覆盖了父类的 final 字段, 或者出现了不符合规则的重载) + + 3. 字节码验证 + + 4. 符合引用验证 + * 符号引用中通过字符串描述的全限定名, 是否能找到对应的类 + * 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段 + * 符号引用中的类, 字段, 方法的访问属性:private,protected,public,default 是否可以被当前类访问 + + + # 准备 + * 为变量分配内存, 并且设置类变量(static)初始值的阶段 + * 这些变量所使用的内存都会在方法区中进行分配 + + public static int value = 123; + + * 在准备阶段, value的初始化值是0, 因为还没开始执行任何Java方法 + * 把value赋值为 123 的putstatic指令是在程序被编译后, 存放于类构造器 () 方法中 + + * 如果是 final 修饰的属性, 在准备阶段, 变量就会被初始化 + public static final int value = 123; + + * 准备阶段, jvm会把value初始化为 123 + + # 解析 + + # 初始化 + * 类加载的最后一步, 直到该阶段, 才真正开始执行类中定义的 Java 程序代码 + + * () 方法是由编译器自动收集类中的所有类变量的赋值动作,和静态语句块中的语句合并产生的 + * 编译器的收集顺序, 是按照源代码文件中出现的顺序所决定的 + + * static 代码块, 只能访问到定义在静态代码块之前的变量, 定义在之后的变量, 可以赋值, 但是不能访问 + class Foo { + static { + value = 10; // 赋值正常 + System.out.println(value);// 访问异常:非法向前引用 + } + public static int value = 0; + } + + + * JVM保证子类的 () 执行之前, 父类的() 已经执行完毕 + * 因此, 父类的 static 会先执行 + * 因此在JVM中第一个执行 () 肯定是 Object + + * () 对于接口来说并不是必须的, 如果一个类没有 static 代码块, 也没有对变量的赋值操作, 那么是可以不需要 () 的 + + * () 的初始化过程是线程安全的, 类加载的过程是线程安全的 + + diff --git a/Jvm/jvm-load.java b/Jvm/jvm-load.java new file mode 100644 index 00000000..83219a90 --- /dev/null +++ b/Jvm/jvm-load.java @@ -0,0 +1,128 @@ +-------------------------- +类加载机制 | +-------------------------- + # 与编译时需要进行连接工作的语言不同 + * Java语言, 类型的加载, 连接, 和初始化过程都是运行时完成的 + * 这种策略会让类的加载增加性能开销, 但是可以为Java应用程序提供很高的灵活性 + + * Java是天生可以动态扩展的语言, 特性就是依赖运行期间的动态加载和动态连接这个特点实现的 + + # 类的生命周期 + 1.加载(Loading) + 2.验证(Verification) + 3.准备(Preparation) + 4.解析(Resolution) + 5.初始化(Initialization) + 6.使用(Using) + 7.卸载(Unloading) + + * 2,3,4 步骤就是连接步骤 + * 通常 1,2,3,5,7 是确定的, 类的加载顺序不会改变 + * 解析阶段不一样, 它在某些情况下可以在初始化后解析, 这是为了支持Java语言的动态绑定 + + # JVM规范规定了只有5种情况必须对类进行初始化 + * 加载, 验证, 准备 肯定是需要先执行的 + * 除了以下五种情况外, 所有引用类的方式都不会触发初始化 + + 1. new ,getstatic, putstatic, invokestatic + * 通俗理解就是创建对象, 读取, 写入 static 属性, 执行 static 方法 + * 注意, 被 final 修饰属性的除外, final 修饰的属性已经在编译时候就把结果放入了常量池 + + 2. 使用 java.lang.reflect 包的api对象类进行反射调用的时候, 如果类没初始化, 必须先初始化 + + 3. 初始化一个类的时候, 如果其父类还没初始化, 就会先初始化其父类 + + 4. 当JVM启动的时候, 用户需要指定一个执行的主类, main 函数类, JVM会先初始化这个类 + + 5. 对使用JDK7的动态语言支持的时候, + * 如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果是: REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄 + * 并且这个方法句柄所对应的类还没有初始化, 就会执行初始化 + + # 打印JVM的类加载/卸载 + -XX:+TraceClassLoading + * 打印类加载信息 + + -XX:+TraceClassUnLoading + * 打印类卸载信息 + + # 接口的加载过程和类的加过程有点不同 + * 接口也有初始化过程, 跟类一致 + * 接口不能使用 static {} 语句块, + * 编译器仍然会为接口生成 () 类构造器, 用于初始化接口中所定义的成员变量 + + * 接口在初始化的时候, 并不要求其父接口都完成了初始化, 只有用到父接口的时候, 才会初始化(如:引用接口中定义的变量) + * 这点跟JVM规范中的第3点相悖 + +------------------------------- +类被引用, 但是不会初始化场景 | +------------------------------- + # 对于静态字段, 只有直接定义这个字段的类才会被初始化 + * 通过子类触发父类的静态字段, 只有父类会被初始化, 子类不会初始化 + + public class SuperClass { + public static int value = 1; + static{ + System.out.println("SuperClass Load..."); + } + } + public class SubClass extends SuperClass { + static { + System.out.println("SubClass Load..."); + } + } + + // main函数执行 + System.out.println(SubClass.value); + + //打印结果 + SuperClass Load... + 1 + + # 通过数组来使用引用类, 不会触发类的初始化 + package io.javaweb.jvm; + public class Foo { + static { + System.out.println("Foo load..."); + } + } + Foo[] foos = new Foo[10]; + // 没有任何输出 + + * 但是该改代码触发了另外一个名为: [Lio.javaweb.jvm.Foo 的类初始化阶段 + * 这是一个由JVM自动生成, 直接继承 java.lang.Object 的子类, 创建动作由字节码指令 newarray 触发 + * 这个类表示了一个元素类型为:io.javaweb.jvm.Foo的一维数组, 数组中应用的属性和方法都实现在该类 + * 可以直接使用的, 被 public 修饰的属性和方法 + length 属性 + clone() 方法 + * java比C/C++相对安全是因为这个类封装了数组元素的访问方法, 而C/C++直接翻译为指针的移动 + * Java检查到数组越界就会给出异常: ArrayIndexOutOfBoundsException + + + # 访问 final 修饰的属性不会触发类的初始化 + * 常量在编译阶段就会存入调用类的常量池中, 本质上并没有引用到定义常量的类, 因为不会触发定义常量的类的初始化 + + class Foo { + public static final int value = 0; + static { + System.out.println("Foo load..."); + } + } + + public class Main { + public static void main(String[] args) { + System.out.println(Foo.value); // 0 + } + } + + + + * 访问类的 final 修饰方法, 会触发初始化 + * 在编译阶段通过常量的传播优化, 已经把常量的值: 1 ,存储到了 Main 的常量池中 + * 以后 Main 类对常量:Foo.value的引用, 实际上都被转换为 Main 类对自身常量池的引用 + + * 实际上Main的class文件之中并没 Foo 类的符合引用入口, 这两个类在编译成 class 后就没得任何联系了 + + + + + \ No newline at end of file diff --git "a/Jvm/jvm-\345\206\205\345\255\230-\345\210\206\351\205\215\347\255\226\347\225\245.java" "b/Jvm/jvm-\345\206\205\345\255\230-\345\210\206\351\205\215\347\255\226\347\225\245.java" new file mode 100644 index 00000000..6cc0055c --- /dev/null +++ "b/Jvm/jvm-\345\206\205\345\255\230-\345\210\206\351\205\215\347\255\226\347\225\245.java" @@ -0,0 +1,51 @@ +---------------------------- +内存的分配策略 | +---------------------------- + # 对象优先在 Eden 分配 + * 当 Eden 空间不足的时候, 会发起一次: Minor GC + + # 大对象, 直接进入老年代 + * 大对象, 指的是需要大量连续内存空间的对象(很长的字符串, 数组) + * 如果经常出现大对象的话, 可能会导致内存还有不少空间, 但是不得不提前触发GC来获取足够的连续空间 + * 可控制参数: -XX:PretenureSizeThreshold + + # 长期存活的对象, 进入老年代 + * JVM给每个对象定义了一个年龄的计数器 + * 进过一次GC, 还存活的对象, 年龄+1 + * 年龄达到了一定的程度, 就会进入老年代, 默认 15 岁 + * 可控制参数: -XX:MaxTenuringThreshold + + # 动态对象年龄判定 + * JVM并不是需要对象的年龄必须到达了 MaxTenuringThreshold 后才能晋升到老年代 + * 在 Survivor 空间中, 相同年龄的所有对象的大小总和, 大于了 Survivor 的一半, 那么年龄大于或者等于该年龄的对象都直接进入老年代 + + * 通俗的理解就是, 几个对象, 年龄相等, 而且 Survivor 内存已经耗了一半多了, 此时就会把这个年龄段的对象, 和大于该年龄的对象, 都放入老年代 + + + # 空间分配担保 + * 在发生 Minor GC 之前, JVM要检查老年代可用的最大连续空间是否大于新生代所有对象的总空间 + '我这空的地儿, 能不能装下你的所有对象' + + * 如果条件成立(能装下), 那么 Minor GC 是安全的, 直接执行就OK + * 如果条件不成立(空闲的内存存不下所有的对象), 那么会判断一个值:-XX:HandlePromotionFailure + * +HandlePromotionFailure + * 检查老年代可用的最大连续空间, 是否大于历次晋升到老年代对象(总大小)的平均大小 + * 大于 + * 尝试进行一次: Minor GC , 尽管这是有风险的 + * 取平均值是一种动态概率手段, 如果某次GC存活后的对象剧增, 远远高于平均值的话, 会导致担保失败 + * 在担保失败后, 会发起一次 Full GC + + * 小于 + * 执行 Full GC + + * -HandlePromotionFailure + * 执行 Full GC + + + * JDK6之后, HandlePromotionFailure 参数就没用了 + * 只要老年代连续的内存空间大于新生代所有对象总内存大小空间, 或者大于历次晋升到老年代对象(总大小)的平均大小, 就执行:Minor GC + * 否则, 就执行 Full GC + + + + diff --git "a/Jvm/jvm-\345\206\205\345\255\230-\345\240\206.java" "b/Jvm/jvm-\345\206\205\345\255\230-\345\240\206.java" new file mode 100644 index 00000000..114c2fca --- /dev/null +++ "b/Jvm/jvm-\345\206\205\345\255\230-\345\240\206.java" @@ -0,0 +1,43 @@ +------------------------------------ +堆(Heap) | +------------------------------------ + # 每个线程共享的内存区域, GC主要管理的区域 + # 堆是物理上不连续的内存空间,逻辑上是连续的 + # 一般是固定大小,也可以通过启动参数来修改空间的大小 + # 如果堆没有空间进行扩展会抛出异常:OutOfMemoryError + + # 根据内存回收(基本都采用分代收算法)的角度, 看堆内存分区 + * 堆区域 + - 新生代(PSYoungGen) + * Eden 空间 + * From Survivor 空间 + * To Survivior 空间 + - 老年代(ParOldGen) + + + * 非堆区 + - 元空间(Metaspace/PermGen) + + + + + + + # 根据内存分配的角度, 看堆内存分区 + 线程私有的分配缓冲区 TLAB(Thread Local Allocation Buffer) + + + # 不管堆怎么分区, 存放的内容都是对象, 各种分区的目的是为了更快, 更好的回收内存 + + # 对象的访问定位方式 + * 句柄 + - 堆空间会划分一个句柄池, 句柄存储了对象真正的地址 + - refrence 存储的就是对象句柄的地址 + - 对象修改了地址(垃圾回收时可能会移动对象), 只用修改句柄, 而不用修改 refrence + + * 直接指针(HotSpot默认) + - refrence 直接存储对象的地址 + + + + diff --git "a/Jvm/jvm-\345\206\205\345\255\230.java" "b/Jvm/jvm-\345\206\205\345\255\230.java" new file mode 100644 index 00000000..104c312c --- /dev/null +++ "b/Jvm/jvm-\345\206\205\345\255\230.java" @@ -0,0 +1,120 @@ +---------------------------- +内存区域 | +---------------------------- + # 运行时数据区 + + -- 线程共享 + 堆(Heap) + 方法区(Method Area) + + + + -- 线程私有 + 虚拟机栈(VM Stack) + 本地方法栈(Native Method Stack) + 程序计数器(Program Counter Register) + +------------------------------------ +方法区(Method Area) | +------------------------------------ + # 每个线程共享的内存区域 + # 它存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据 + - 类信息(类名, 访问修饰符, 字段描述, 方法描述...) + - 常量池 + - ... + + # Jvm规范把它描述为堆的一个逻辑部分,所以也被成为: Non Heap(非堆),目的是为了跟堆区分开来 + + # 蛮多人也称它为永久代(Permanent Generation) + * 因为HotSpot虚拟机的设计团队,把GC分代收集器扩展到了方法区(就是使用了永久代来实现方法区) + * 好处就是能够省去专门为方法区编译内存管理代码的工作 + * 怎么去实现方法区,不受虚拟机规范的约束 + + # 如果方法区无法满足内存分配需求的时候会抛出异常:OutOfMemoryError + + # 运行时常量池也是方法区的一部分 + * class 文件中除了了有类的版本,字段,方法,接口等描述信息外 + * 还存放了编译时期生成的各种字面量和符号引用,这部分内容将在类加载后存放进方法区的常量池 + + * 它作为方法区的一部分,也受限于方法区,如果常量池无法申请到内存,就会抛出异常:OutOfMemoryError + + # 在jdk1.8以后,HotSpot虚拟机已经移除了永久代,该用 Native Memory (Metaspeace)来实现方法区 + * 不论是jdk8的元空间还是以前版本的永久代, 都是JVM方法区规范的实现 + * 唯一的不同就是, 元空间并不在虚拟机中, 而是使用本地内存 + +------------------------------------ +虚拟机栈(VM Stack) | +------------------------------------ + # 线程私有的,它的生命周期跟线程的生命周期一样 + # 执行每个Java方法时,就会创建一个栈帧,在栈帧里面会存储一些东西 + * 存储局部变量表 + - 编译之间可预知的一些数据类型:boolean,byte,char,short,int,long,float,double,对象引用,rerurnAddress(指向一条字节码指定的地址) + - 64位长度的 long 和 double 会占用两个局部变量空间(slot),其余的数据类型只会占用1个 + - 局部变量表所需要的内存空间,在编译的时候就已经确定了,不能修改 + + * 操作数栈 + * 动态链接 + * 方法出口等信息 + + # 每一个方法从调用到结束,就对应着一个栈帧在jvm中入栈到出栈 + + # 这个区域可能会抛出两种异常 + StackOverflowError + * 不当的递归或者其他操作,导致了栈的深度大于了虚拟机允许的深度 + + OutOfMemoryError + * 不能申请到足够的内存来开辟新的栈帧 + +------------------------------------ +本地方法栈(Native Method Stack) | +------------------------------------ + # 它与虚拟机栈其实是一样的,服务的对象不同 + # 虚拟机栈为Java的方法提供运行空间,本地方法栈为本地方法提供了运行空间 + * 虚拟机规范对于本地方法栈使用的语言, 数据结构, 使用方式都没有强制约束 + * HotSpot虚拟机, 甚至直接就把本地方法栈和虚拟机栈合二为一 + + # 它也也可能会抛出异常:StackOverflowError,OutOfMemoryError + + +------------------------------------ +程序计数器(Program Counter Register)| +------------------------------------ + # 一块儿较小的内存 + + # 可以当看做是当前线程所执行的字节码行号指示器 + * 字节码解释器工作的时候,就是通过修改这个计数器的值来选取下一条要执行的字节码指令 + * if while break continue goto ... 线程的恢复等等功能都需要依赖这个计数器 + + # 如果正在执行一个Java方法,则该计数器的值是虚拟机字节码指令的地址 + + # 如果正在执行一个Native方法,责该计数器的值是:undefined + + # 它是一个线程私有的内存区域,在每个线程中独立存在,互不影响 + + # 它是唯一一个在JVM虚拟机规范中不存在:OutOfMemoryError 情况的区域 + + # 它只是一个概念模型,不同的JVM可能会通过其他更为高效的方式去实现 + + +------------------------------------ +直接内存 | +------------------------------------ + # 它不是Jvm规范的一部分,但是却可以被频繁的使用 + + # NIO中的 Channel/Buffer,可以使用 Natice 函数直接分配堆外内存 + * DirectByteBuffer + * 可以带来显著的行性能提升,因为避免了Java堆和Natice堆中来回的复制数据 + + # 直接内存的分配只会受到物理内存,处理器寻址空间的限制 + + # 可能用到直接内存的地方 + DirectByteBuffer + Socket + * 有俩缓冲区, recv/send 大约占 37KB/25KB 内存 + * 如果无法分配, 可能会抛出异常:IOException: Too many open files + JNI代码 + * 如果调用本地库, 本地库使用的内存也不在堆中 + 虚拟机和GC + * 虚拟机和GC代码的执行, 也是要消耗一部部分的内存 + + \ No newline at end of file diff --git "a/Jvm/jvm-\345\217\202\346\225\260.java" "b/Jvm/jvm-\345\217\202\346\225\260.java" new file mode 100644 index 00000000..91f78cf6 --- /dev/null +++ "b/Jvm/jvm-\345\217\202\346\225\260.java" @@ -0,0 +1,157 @@ +-------------------------- +jvm参数 | +-------------------------- + +-------------------------- +jvm参数 统计 | +-------------------------- + +-verbose:gc -Xms20m -Xmx20m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError +-verbose:class -XX:+TraceClassLoading -XX:+TraceClassUnloading + +-Xmx + * 堆内存最大值 + +-Xms + * 堆初始化大小 + * 此值可以设置与-Xmx相同, 以避免每次垃圾回收完成后JVM重新分配内存 + +-Xmn + * 新生代内存大小 + +-Xss + * 每个线程的栈的大小 + +-Xnoclassgc + * 关闭Class的回收 + +-verbose:gc +-verbose:class + +-XX:PermSize +-XX:MetaspaceSize +-XX:MaxPermSize +-XX:MaxMetaspaceSize +-XX:MaxDirectMemorySize + * 设置最大的堆外内存大小 + +-XX:MinMetaspaceFreeRatio +-XX:MaxMetaspaceFreeRatio + +-XX:+DisableExplicitGC + * 禁止显示的调用GC(System.gc()) + +-Xloggc + * 指定gc日志文件的地址, 而不是输出到标准输出流 + -Xloggc:../logs/gc.log + +-XX:+HeapDumpOnOutOfMemoryError + * 在内存溢出的时候, 生成内存快照 + +-XX:HeapDumpPath + * 在内存溢出的时候, 存储内存快照的文件夹 + -XX:HeapDumpPath=C:\Users\KevinBlandy\Desktop + + * 文件名称格式: java_pid[pid].hprof + java_pid7560.hprof + + +-XX:+PrintGC + * 打印GC +-XX:+PrintHeapAtGC + * 在GC前后都打印日志信息 + +-XX:+PrintGCDetails + * 打印GC日志详情 + * 并且在JVM退出的时候, 打印各个内存区域的使用情况 + +-XX:+PrintGCTimeStamps + * 打印GC时间,JVM启动时间 + +-XX:+PrintGCDateStamps + * 输出GC的时间戳(以日期的形式, 如 2013-05-04T21:53:59.234+0800) + +-Xloggc: + * 保存GC信息到指定的文件 + -Xloggc:C:\Users\KevinBlandy\Desktop\gc.log + * 如果文件已经存在, 会被覆写 + +-XX:+TraceClassLoading + * 打印类加载信息 + +-XX:+TraceClassUnLoading + * 打印类卸载信息 + +-XX:+PrintFlagsFinal + * 打印出JVM所有的最终配置信息 + * 它包含了所有的配置信息, 包括手动设置的, 以及系统默认的 + + + + + + + + + + + + + +-XX:ParallelGCThreads + * 设置 ParNew 收集器的收集线程数量 + +-XX:MaxGCPauseMillis + * 置最大垃圾收集停顿时间, 它的值是一个大于 0 的整数 + * 收集器在工作时, 会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMillis 以内 + * 停顿时间的缩短, 是牺牲了吞吐量(以前10s一次100ms的GC, 现在5s一次70ms的GC)和新生代空间(对体积小的内存收集比较快)换来的, 这也导致GC发生得更加的频繁 + * 过小的话, GC停顿时间确实下降了, 但是吞吐量也下降了 + +-XX:GCTimeRatio + * 设置吞吐量大小, 它的值是一个大于 0 小于 100 之间的整数 + * 可以理解为: 垃圾收集时间占总时间的比例 + * 默认 GCTimeRatio 的值为 99, 那么系统将花费不超过 1 / (1 + 99) = 1% 的时间用于垃圾收集 + +-XX:NewRatio + * 设置新生代和老年代的内存比例 + * 设置为4 -XX:NewRatio=4, 则年轻代与年老代所占比值为 1 : 4, 年轻代占整个堆栈的1/5 + +-XX:SurvivorRatio + * 设置新生代内存中 Eden 和 Survivor的比例值 + * 默认 -XX:SurvivorRatio=8 , 也就是说 Eden 占 80%内存 + +-XX:PretenureSizeThreshold + * 晋升到老年代的对象大小, 设置该参数后, 体积大于该参数的对象, 直接在老年代分配 + * 默认值是0, 意思是不管多大都是先在Eden中分配内存 + * 仅仅只对:Serial 和 ParNew 两个收集器有效 + +-XX:MaxTenuringThreshold + * 晋升到老年代的对象年龄, 每个对象在坚持过一次:Minor GC 后, 年龄就会 +1 + * 当超过该值的时候, 就会进入老年代 + * 当超过该值的时候, 就会进入老年代, 默认为 15 + +-XX:+HandlePromotionFailure + * 是否允许分配担保失败, 即老年代的剩余空间不足以应付整个新生代所有对象都存活的极端情况 + * 担保失败后会执行Full GC + +-XX:+UseConMarkSweepGC + * 使用Concurrent Mark Sweep(CMS)作为老年代收集器 + * 如果使用该参数, 默认就会使用: ParNew 作为新生代的收集器 + +-XX:+UseParNewGC + * 强制系统使用 ParNew 作为新生代的收集器 + +-XX:+UseAdaptiveSizePolicy + * 打开自适应 GC 策略, 在这种模式下, 其他的一些属性不需要自己去设置, 参数会被自动调整, 以达到在堆大小, 吞吐量和停顿时间之间的平衡点 + -Xmn(新生代大小) + -XX:+SuivivorRatio(Eden和Survivor区的比例) + -XX:+PretenureSizeThreshold(晋升老年代对象年龄) + * 使用自适应GC策略, 只需要把基本的内存数据设置好,例如堆内存大小值 + * 然后仅仅关注/设置最大停顿时间:-XX:MaxGCPauseMillis + * 或者给JVM设置一个最大吞吐量 -XX:GCTimeRatio 的优化目标, 具体的工作细节就由jvm完成 + + +-XX:+SerialGC +-XX:+UseParallelGC +-XX:+UseParallelOldGC +-XX:+UseG1GC \ No newline at end of file diff --git "a/Jvm/jvm-\345\267\245\345\205\267-hsdis.java" "b/Jvm/jvm-\345\267\245\345\205\267-hsdis.java" new file mode 100644 index 00000000..87f81157 --- /dev/null +++ "b/Jvm/jvm-\345\267\245\345\205\267-hsdis.java" @@ -0,0 +1,17 @@ +-------------------- +hsdis | +-------------------- + # Sun 官方推荐的 HotSpot 虚拟机 JIT 编译代码的反汇编插件 + * 它包含在HotSpot的源码中, 但是没有提供编译后的恒旭 + * 它的作用是让 HotSpot 的 -XX:PrintAssembly 指令调用它把动态生成的本地代码还原文件汇编代码输出 + * 还提供了大量有价值的注释 + + # 需要根据自己的操作系统,CPU类型,从 Project Kenai 网站上下载插件 + * 下载好,复制到目录: + JDK_HOME/jre/bin/client + JDK_HOME/jre/bin/server + + * 如果找不到的话, 需要自己根据源代码编译一下 + + + diff --git "a/Jvm/jvm-\345\267\245\345\205\267-jconsole.java" "b/Jvm/jvm-\345\267\245\345\205\267-jconsole.java" new file mode 100644 index 00000000..e0ac585a --- /dev/null +++ "b/Jvm/jvm-\345\267\245\345\205\267-jconsole.java" @@ -0,0 +1,11 @@ +------------------------ +jsoncole | +------------------------ + # jconsole, 是基于JMX的可视化监视, 管理工具 + * 它的部分管理功能是针对 JMX MBean 进行管理 + * 由 MBean 可以使用代码, 中间件服务器的管理控制或者所有符合JXM规范的软件进行访问 + + + # 概览 + + \ No newline at end of file diff --git "a/Jvm/jvm-\345\267\245\345\205\267-jhat.java" "b/Jvm/jvm-\345\267\245\345\205\267-jhat.java" new file mode 100644 index 00000000..678f7b8a --- /dev/null +++ "b/Jvm/jvm-\345\267\245\345\205\267-jhat.java" @@ -0,0 +1,34 @@ +--------------------- +jhat | +--------------------- + # JVM Heap Dump Browser + + # 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器, 可以直接在浏览器上查看分析结果 + * 除非真的是没工具查看 dump 文件的解析结果了, 否则不建议使用它 + * 因为一般不会在部署应用的服务器上去解析dump文件, 因为解析工作非常的消费硬件资源, 耗时 + * 它的分析功能也比较的简陋, 一般不会用 + + # 建议使用的分析器 + Eclipse Memory Analyzer + IMB HeapAnalyzer + + # 命令 + jhat [options] [dump] + + dump: 指定dump文件 + + * 默认在 7000 端口提供 http 服务 + Reading from heapdump... + Dump file created Mon May 27 13:39:33 GMT+08:00 2019 + Snapshot read, resolving... + Resolving 279684 objects... + Chasing references, expect 55 dots....................................................... + Eliminating duplicate references....................................................... + Snapshot resolved. + Started HTTP server on port 7000 + Server is ready. + + # 参数 + -port + * 指定端口号 + diff --git "a/Jvm/jvm-\345\267\245\345\205\267-jinfo.java" "b/Jvm/jvm-\345\267\245\345\205\267-jinfo.java" new file mode 100644 index 00000000..37c84ff9 --- /dev/null +++ "b/Jvm/jvm-\345\267\245\345\205\267-jinfo.java" @@ -0,0 +1,33 @@ +------------------------ +jinfo | +------------------------ + # Configuration Info For Java + # 显示虚拟机的配置信息 + * 实时的查看和调整JVM的参数 + + # 语法 + jinfo [options] pid + + * 如果不添加参数(options), 则打印出该进程相关的所有信息(JVM参数, System.properties 参数) + + # 参数 + -flag [name] + * 查看指定jvm选项的值 + * 例: 查看 -XX:CICompilerCount 的值 + jinfo -flag CICompilerCount 3900 + + -flags + * 查看指定JVM选项的所有值(如果没设置, 就是默认值了) + + -sysprops + * 查看 System.properties 的数据 + * 返回 key=value + + # 动态的启动/禁用某些JVM参数 + jinfo -flag [+/-] [name] + + # 动态的修改JVM参数 + jinfo -flag [name]=[value] + + + diff --git "a/Jvm/jvm-\345\267\245\345\205\267-jmap.java" "b/Jvm/jvm-\345\267\245\345\205\267-jmap.java" new file mode 100644 index 00000000..b2aff637 --- /dev/null +++ "b/Jvm/jvm-\345\267\245\345\205\267-jmap.java" @@ -0,0 +1,48 @@ +-------------------- +jmap | +-------------------- + # Memory Map For Java + # 生成虚拟机的内存转储快照文件(heapdump) + + # 生成Java的堆转储快照, 可以通过一些手段 + * 使用:-XX:+HeapDumpOnOutOfMemoryError 在内存溢出的时候, 生成内存快照 + * 在Linux下使用: kill -3 命令'吓唬JVM', 也能拿到快照 + + # 命令格式 + jmap [options] vmid + + + # 参数 + -dump + * 生成Java堆转储快照 + * 该参数有几个子参数, 在'-dump:'后添加, 多子参数, 使用逗号分隔 + -dump:live,format=b,file=D:\\heapdump + + live 该参数表示,仅仅dump出存活的对象 + format 指定格式化?? + file 指定文件的路径 + + * 生成快照示例 + jmap -dump:format=b,file=C:\Users\KevinBlandy\Desktop\heapdump 3900 + + * 如果快照文件已经存在, 则会创建失败 + + -finalizerinfo + * 显示在:F-Queue 中等待 Finalier 线程执行 finalize 方法的对象 + + -heap + * 显示Java堆详细信息, 如:使用的回器, 参数配置, 分代状态 + + -histo + * 显示堆中的对象统计信息, 包括类, 实例数量, 合计容量 + * 该参数有一个子参数 + live 仅仅统计存活的对象 + + -clstats + * 以 ClassLoader 为统计口径, 显示内存状态 + + -F + * 当JVM进程对 -dump 选项没有响应的时候, 可以使用该选项强制生成 dump 快照 + * 只在 Linux/Solaris平台有效 + + diff --git "a/Jvm/jvm-\345\267\245\345\205\267-jps.java" "b/Jvm/jvm-\345\267\245\345\205\267-jps.java" new file mode 100644 index 00000000..cee483f2 --- /dev/null +++ "b/Jvm/jvm-\345\267\245\345\205\267-jps.java" @@ -0,0 +1,23 @@ +-------------------- +jps | +-------------------- + # JVM Process Status Tool + * 显示系统中所有 HotSpot 虚拟机进程 + + # 命令格式 + jps [options] [hostid] + + * hostid, 表示RMI注册表中注册的主机名(可以通过RMI协议查询远程JVM的进程状态) + + # 常用参数 + -q + * 只输出 LVMID, 省略 Main 类名称 + -m + * 输出JVM启动的时候, 传递给 main 函数的参数 + -l + * 输出主类的全名, 如果是 jar 包, 则输出 jar 路径 + -v + * 输出虚拟机启动时的JVM参数 + + + \ No newline at end of file diff --git "a/Jvm/jvm-\345\267\245\345\205\267-jstack.java" "b/Jvm/jvm-\345\267\245\345\205\267-jstack.java" new file mode 100644 index 00000000..0e4ecefc --- /dev/null +++ "b/Jvm/jvm-\345\267\245\345\205\267-jstack.java" @@ -0,0 +1,42 @@ +---------------------------- +jstack | +---------------------------- + # Stack Trace for Java + + # 生成JVM当前时刻的线程快照 + * 一般成为:theaddump/javacore 文件 + + # 线程快照就是当前JVM每一条线程正在执行方法堆栈的集合 + * 生成线程快照的主要目的就是为了定位线程出现长时间停顿的原因 + * 如:线程间死锁, 死循环, 请求外部资源导致常见等待 + + # 命令 + jstack [options] [vmid] + + # 参数 + -F + * 当正常输出的请求没响应的时候, 使用该参数强制输出线程堆栈 + + -l + * 除堆栈外, 还显示关于锁的信息 + + -m + * 如果调用到本地方法的话, 可以显示: C/C++ 的堆栈 + + + # Thread 有个静态方法, 可以完成该工具的大部分功能 + Map getAllStackTraces() + + * 获取到当前jvm的所有线程的执行栈 + +---------------------------- +jstack - 线程状态 | +---------------------------- + RUNNABLE, 线程处于执行中 + * 可能是已经获取到了锁, 正在执行 + + BLOCKED, 线程被阻塞 + * 等待获取到锁 + + WAITING, 线程正在等待 + * sleep/wait 等操作, 阻塞了线程 \ No newline at end of file diff --git "a/Jvm/jvm-\345\267\245\345\205\267-jstat.java" "b/Jvm/jvm-\345\267\245\345\205\267-jstat.java" new file mode 100644 index 00000000..37b78294 --- /dev/null +++ "b/Jvm/jvm-\345\267\245\345\205\267-jstat.java" @@ -0,0 +1,114 @@ +------------------------- +jstat | +------------------------- + # JVM Statistics Tool + * 收集 HotSpot 虚拟机各方面的运行数据 + + # 监控JVM运行状态的命令行工具 + * 可以显示本地或者远程虚拟机中的类装载, 内存, 垃圾收集, JIT编译等运行参数 + + # 在Windows是GUI图形界面,在Linux是文本统计行, 类似于 top 命令的结果 + + # 命令格式 + jstat [option vmid [interval[s|ms] [count]] ] + + vmid + * 如果是本地的JVM, 则就是进程ID, 如果是远程的: [protocal:][//]lvmid[@hostname:[:port]/servername] + + interval + * 表示查询间隔, 时间单位是毫秒 + + count + * 表示查询总次数 + * 默认只会查询一次 + * 如果设置为 : -1, 则无限输出 + + + # 可选参数(option) + -class + * 监视类装载, 卸载数量, 总空间以及所耗费的时间 + * 输出内容 + Loaded Bytes Unloaded Bytes Time + 1913 3906.6 8 6.7 4.74 + 1913 3906.6 8 6.7 4.74 + + -gc + * 监视堆状况, 包括各个分区的容量, 已经使用的空间, GC时间合计等信息 + * 输出内容 + S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT + 1408.0 1408.0 0.0 1408.0 11840.0 10459.3 29264.0 21233.5 13056.0 12766.8 1536.0 1407.4 9885 12.705 65 1.641 14.346 + + -gccapacity + * 监视内容与 -gc 一样, 但是输出主要关注 Java 堆各个区域使用到的最大, 最小空间 + * 输出内容 + NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC + 10240.0 156992.0 14656.0 1408.0 1408.0 11840.0 20480.0 314048.0 29264.0 29264.0 0.0 1060864.0 13056.0 0.0 1048576.0 1536.0 9885 65 + + -gcutil + * 监视内容与 -gc 一样, 但主要输出关注已使用空间占总空间的百分比 + * 输出内容 + S0 S1 E O M CCS YGC YGCT FGC FGCT GCT + 0.00 100.00 88.34 72.56 97.79 91.62 9885 12.705 65 1.641 14.346 + + S0:Survivor0 是空的 + S1:Survivor1 是装满的 + E: Eden 已经使用了 88.34% 的空间 + O: Old, 老年代, 已经使用了 72.56% 的空间 + M: Metaspace, 元空间, 已经使用了 97.79% 的空间 + P: JDK8以前的永久区 + CCS: + YGC: 程序运行期间, 发生MinorGC 9885 次 + YGCT:程序运行期间, 发生MinorGC 共耗时: 12.705 秒 + FGC:程序运行期间, 发生FullGC 65 次 + FGCT:程序运行期间, 发生FullGC 共耗时:1.641 秒 + GCT:程序运行期间, 所有GC总耗时: 14.346 秒 + + + + -gccause + * 监视内容与 -gcutil 一样, 但是会额外输出导致上一次GC的原因 + * 输出内容 + S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC + 0.00 100.00 88.34 72.56 97.79 91.62 9885 12.705 65 1.641 14.346 Allocation Failure No GC + + -gcnew + * 监视新生代GC状态 + * 输出内容 + S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT + 1408.0 1408.0 0.0 1408.0 1 15 704.0 11840.0 10459.3 9885 12.705 + + -gcnewcapacity + * 监视内容和 -gcnew 相同, 输出主要关注使用到的最大, 最小空间 + * 输出内容 + NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC + 10240.0 156992.0 14656.0 15680.0 1408.0 15680.0 1408.0 125632.0 11840.0 9885 65 + + -gcold + * 监视老年代GC状态 + * 输出内容 + MC MU CCSC CCSU OC OU YGC FGC FGCT GCT + 13056.0 12766.8 1536.0 1407.4 29264.0 21233.5 9885 65 1.641 14.346 + + -gcoldcapacity + * 监视内容可以 -gcold 相同, 输出主要关注使用到的最大, 最小空间 + * 输出内容 + OGCMN OGCMX OGC OC YGC FGC FGCT GCT + 20480.0 314048.0 29264.0 29264.0 9885 65 1.641 14.346 + + -gcpermcapacity + * 输出永久代使用到的最大, 最小空间 + * JDK8后, 没有永久代了, 使用元空间 metaspace 代替 + + -compiler + * 输出JIT 编译器编译过的方法, 耗时等信息 + * 输出内容 + Compiled Failed Invalid Time FailedType FailedMethod + 2234 0 0 9.15 0 + + -printcompilation + * 输出已经被JIT编译的方法 + * 输出内容 + Compiled Size Type Method + 2234 17 1 io/netty/channel/AbstractChannel$AbstractUnsafe voidPromise + 2234 17 1 io/netty/channel/AbstractChannel$AbstractUnsafe voidPromise + diff --git "a/Jvm/jvm-\345\267\245\345\205\267-visualvm.java" "b/Jvm/jvm-\345\267\245\345\205\267-visualvm.java" new file mode 100644 index 00000000..5144d816 --- /dev/null +++ "b/Jvm/jvm-\345\267\245\345\205\267-visualvm.java" @@ -0,0 +1,40 @@ +--------------------- +visualvm | +--------------------- + # 它是在 1.6Update中首次发布, 现在是Sun(Oracle)主力推动的多合一故障处理工具 + # 已经从JDK分离出来, 成为独立的项目 + https://visualvm.github.io/ + + # 它对应用程序的实际性能影响很小, 所以可以直接应用在生产环境中 + + +--------------------- +开启 jstatd 远程监控 | +--------------------- + # 在服务端JDK的bin目录下新建 jstatd.all.policy 文件 + * 其实目录可以自己选择 + * 输入代码用于启动JSTATD: + +   grant codebase "file:${java.home}/../lib/tools.jar" { +     permission java.security.AllPermission; +   }; + + # 服务端启动 jstatd + jstatd -J-Djava.security.policy=jstatd.all.policy -p 1024 & + + # 选项 + -nr + * 当没有找到现有的RMI注册表时, 不会尝试在jstatd进程中创建内部RMI注册表 + + -p [port] + * 希望找到RMI注册表的端口号, 或者如果未找到, 则在未指定-nr的情况下创建RMI注册表 + + -n [rminame] + * 远程RMI对象在RMI注册表中绑定的名称 + * 默认名称是JStatRemoteHost + * 如果多个jstatd服务器在同一主机上启动,通过指定该选项,可以使每个服务器导出的RMI对象的名称唯一, 但是,这样做将需要保证在监视客户端的hostid和vmid中存在唯一的服务器名称 + + -J [option] + * 将选项传递给java程序 + * 例如:-J-Xms48m将启动内存设置为48兆字节, -J通过是一个通用的约定执行用Java编写的应用程序的底层VM选项 + diff --git "a/Jvm/jvm-\345\267\245\345\205\267.java" "b/Jvm/jvm-\345\267\245\345\205\267.java" new file mode 100644 index 00000000..847990b1 --- /dev/null +++ "b/Jvm/jvm-\345\267\245\345\205\267.java" @@ -0,0 +1,33 @@ +------------------------- +JDK的工具 | +------------------------- + # 在JDK的bin目录下 + + jps + * JVM Process Status Tool + * 显示系统中所有 HotSpot 虚拟机进程 + + jstat + * JVM Statistics Tool + * 收集 HotSpot 虚拟机各方面的运行数据 + + jinfo + * Configuration Info For Java + * 显示虚拟机的配置信息 + + jmap + * Memory Map For Java + * 生成虚拟机的内存转储快照文件(heapdump) + + jhat + * JVM Heap Dump Browser + * 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器, 可以直接在浏览器上查看分析结果 + + jstack + * Stack Trace for Java + * 显示虚拟机的线程快照 + + + * 这些exe主要的功能都是在 tools.jar 中实现的 + * 在 Linux 环境下, 还有直接通过 shell 脚本实现的 + diff --git "a/Jvm/jvm-\345\271\266\345\217\221.java" "b/Jvm/jvm-\345\271\266\345\217\221.java" new file mode 100644 index 00000000..4cd10870 --- /dev/null +++ "b/Jvm/jvm-\345\271\266\345\217\221.java" @@ -0,0 +1,3 @@ +------------------------ +jvm并发 | +------------------------ diff --git "a/Jvm/jvm-\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.java" "b/Jvm/jvm-\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.java" new file mode 100644 index 00000000..e69de29b diff --git a/Jvm/jvm.java b/Jvm/jvm.java new file mode 100644 index 00000000..41546051 --- /dev/null +++ b/Jvm/jvm.java @@ -0,0 +1,105 @@ +------------------------ +jvm | +------------------------ + # 参考 + https://blog.csdn.net/antony9118/article/details/51375662 + https://blog.csdn.net/coderlius/article/details/79272773 + + # HotSpot VM + + # 堆内存的切分 + * 新生代 1/3 + - Eden 80% + * 新生对象, 存放内存 + * 如果对象太大, 会直接放入老年代 + * 如果内存不足, 会触发一次: Minor GC + + - From Survivor 10% + * 上一次GC的幸存对象, 作为这一GC的被扫描者 + + - TO Survivor 10% + * 保留了一次 Minor GC 中的幸存者 + + + * 老年代 2/3 + + +------------------------ +GC | +------------------------ + # Minor GC + * 从年轻代空间(包括 Eden 和 Survivor 区域)回收内存 + * Minor GC 都会触发 stop-the-world, 采用复制算法 + * GC过程 + 1. 把存活对象从 Eden/From Survivor 复制到 To Survivor , 对象年龄 +1, + * 如果有对象年龄到达了上限, 移动到老年代 + * 如果 To Survivor 内存不足, 就放到老年代 + + 2. 清空 Eden/From Survivor 中的对象 + 3. From Survivor 和 To Survivor 互换 + * 原来的 To Survivor 成为下一次 GC 的 From Survivor + + + # Full GC/Major GC + * Major GC 指的是, 清理老年代, 一般都会伴随一次 Minor GC(非绝对的), 也就形成了 Full GC + * 速度很慢, 比 Minor GC 慢10倍以上, 采用标记清除算法 + * 它会扫描整个堆, 标记存活对象, 回收没被标记的对象, 会产生内存碎片 + * 为了减少内存消耗, 还需要进行合并 + +------------------------ +可能涉及到的名词解释 | +------------------------ + # 并行 + * 多个GC收集器线程在同时的工作, 但是应用线程处于终止状态 + + # 并发 + * 应用线程和GC收集线程同时(因为CPU核心数的问题,可能会交替执行)执行 + + # promotion failed + * Minor GC时, survivor space放不下, 对象只能放入老年代, 而此时老年代也放不下, 会触发 Full GC + + # Concurrent Mode Failure + * Concurrent Mode Failure 并发执行收集的时候, 不能腾出内存给正在运行的业务线程 + * 此时会临时启动:Serial Old 收集器来重新对老年代进行垃圾收集 + +------------------------ +触发Full GC的可能 | +------------------------ + # 出现Full GC的时候经常伴随至少一次的Minor GC,但非绝对的 + * Major GC的速度一般会比Minor GC慢10 倍以上 + + # 执行 System.gc() + * 执行该方法是建议jvm进行 Full GC, 但是JVM不一定会真的执行 Full GC, 但是大部分都是会执行Full GC的 + + # 老年代代空间不足 + * 老年代空间只有在新生代对象转入及创建为大对象, 大数组时才会出现不足的现象 + * 当执行Full GC后空间仍然不足, 则抛出异常:java.lang.OutOfMemoryError: Java heap space + + + # CMS GC时出现 promotion failed 和concurrent mode failure + * promotion failed 是在进行Minor GC时, survivor space放不下, 对象只能放入老年代, 而此时老年代也放不下造成的 + * concurrent mode failure 是在执行CMS GC的过程中同时有对象要放入老年代, 而此时老年代空间不足造成的(浮动垃圾) + + # 担保失败 + * Minor GC 之前, JVM要检查老年代可用的最大连续空间是否大于新生代所有对象的总空间(为了应付新生代的所有对象都存活的情况) + * 要老年代连续的内存空间小余新生代所有对象总内存大小空间, 或者小于历次晋升到老年代对象(总大小)的平均大小, 就执行 Full GC + + + + +------------------------ +一般线程分析步骤 | +------------------------ + 1. JSP 确定Java的进程ID: 18392 + + 2. 使用jstack导出java的线程列表 + jstack -l 18392 > 18392.stack + + 3. 使用top -Hp 18392 -d1 命令查看java进程里的子线程的实际占用 + * 记录id后和导出的stack文件比对, 就能知道具体的是哪里占用 + * 在线程列表里面,线程的的id是以16进制表示的, 名称叫做:nid + nid=0x47d8 + + + # 可以使用专业的在线线程dump分析平台 + https://fastthread.io/ diff --git a/Kafka/api/common-Deserializer.java b/Kafka/api/common-Deserializer.java new file mode 100644 index 00000000..275aeacc --- /dev/null +++ b/Kafka/api/common-Deserializer.java @@ -0,0 +1,30 @@ +-------------------- +Deserializer | +-------------------- + # 消费者的解码接口 + # 抽象方法 + void configure(Map configs, boolean isKey); + * 配置,在创建消费者实例的时候调用 + + T deserialize(String topic, byte[] data); + * 解码消息 + + default T deserialize(String topic, Headers headers, byte[] data) { + return deserialize(topic, data); + } + * 可以获取到消息头的解码方法 + + @Override + void close(); + + # 提供的实现类 + ByteArrayDeserializer + ByteBufferDeserializer + BytesDeserializer + DoubleDeserializer + FloatDeserializer + IntegerDeserializer + LongDeserializer + ShortDeserializer + StringDeserializer + UUIDDeserializer diff --git a/Kafka/api/common-OffsetAndMetadata.java b/Kafka/api/common-OffsetAndMetadata.java new file mode 100644 index 00000000..519b8399 --- /dev/null +++ b/Kafka/api/common-OffsetAndMetadata.java @@ -0,0 +1,78 @@ +------------------------------ +OffsetAndMetadata | +------------------------------ + # 用于描述分区信息 + # 源码 + package org.apache.kafka.clients.consumer; + + import org.apache.kafka.common.requests.OffsetFetchResponse; + + import java.io.Serializable; + import java.util.Objects; + import java.util.Optional; + public class OffsetAndMetadata implements Serializable { + private static final long serialVersionUID = 2019555404968089681L; + + private final long offset; // 消费位移 + private final String metadata; + + private final Integer leaderEpoch; + + public OffsetAndMetadata(long offset, Optional leaderEpoch, String metadata) { + if (offset < 0) + throw new IllegalArgumentException("Invalid negative offset"); + + this.offset = offset; + this.leaderEpoch = leaderEpoch.orElse(null); + + if (metadata == null) + this.metadata = OffsetFetchResponse.NO_METADATA; + else + this.metadata = metadata; + } + + public OffsetAndMetadata(long offset, String metadata) { + this(offset, Optional.empty(), metadata); + } + + public OffsetAndMetadata(long offset) { + this(offset, ""); + } + + public long offset() { + return offset; + } + + public String metadata() { + return metadata; + } + + public Optional leaderEpoch() { + return Optional.ofNullable(leaderEpoch); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OffsetAndMetadata that = (OffsetAndMetadata) o; + return offset == that.offset && + Objects.equals(metadata, that.metadata) && + Objects.equals(leaderEpoch, that.leaderEpoch); + } + + @Override + public int hashCode() { + return Objects.hash(offset, metadata, leaderEpoch); + } + + @Override + public String toString() { + return "OffsetAndMetadata{" + + "offset=" + offset + + ", leaderEpoch=" + leaderEpoch + + ", metadata='" + metadata + '\'' + + '}'; + } + + } diff --git a/Kafka/api/common-Serializer.java b/Kafka/api/common-Serializer.java new file mode 100644 index 00000000..290fa4f2 --- /dev/null +++ b/Kafka/api/common-Serializer.java @@ -0,0 +1,45 @@ +------------------- +Serializer | +------------------- + # 消息的序列化接口,把对象序列化为字节数组(编码器) + # 抽象方法 + void configure(Map configs, boolean isKey); + * 配置当前类,在KafkaProducer实例创建的时候调用 + + byte[] serialize(String topic, T data); + * 执行序列化操作 + + default byte[] serialize(String topic, Headers headers, T data) { + return serialize(topic, data); + } + + @Override + void close(); + * 执行关闭,一般来说都是空实现 + * 如果要自己实现的话,必须确保该方法的幕等性 + * 因为close()可能会被KafkaProducer调用多次 + + # 提供了N多序列化实现类 + ByteArraySerializer + * byte[] + + ByteBufferSerializer + * ByteBuffer + + BytesSerializer + * Bytes + + DoubleSerializer + FloatSerializer + IntegerSerializer + LongSerializer + ShortSerializer + * 基本数据类型(包装类)的编码器 + + StringSerializer + * String,默认编码 UTF-8 + + UUIDSerializer + * uuid + * 其实本质上也是把 UUID 对象 toStirng()后 getBytes(),默认使用UTF-8编码 + diff --git a/Kafka/api/common-TopicConfig.java b/Kafka/api/common-TopicConfig.java new file mode 100644 index 00000000..20404324 --- /dev/null +++ b/Kafka/api/common-TopicConfig.java @@ -0,0 +1,52 @@ +----------------------- +TopicConfig | +----------------------- + # 主题的配置,在broker端都有默认的,如果主题的参数没有设置,那么就会使用broker默认的 + + # 主题的配置 + segment.bytes + segment.ms + segment.jitter.ms + segment.index.bytes + flush.messages + * 收集到了多少消息,才强制刷入磁盘 + * 默认值:Long.MAX_VALUE,也就是让操作系统决定 + + flush.ms + * 需要等待多久才会将消息强制刷新到磁盘 + * 默认值为 Long.MAX_VALUE ,即让操作系统来决定 + + retention.bytes + retention.ms + max.message.bytes + index.interval.bytes + file.delete.delay.ms + * 清理文件之前可以等待多少时间,默认为:60000ms 也就是 1分钟 + + delete.retention.ms + * 标识了删除的数据保留多久后物理删除 + * 默认86400000ms 也就是1 天 + + min.compaction.lag.ms + min.cleanable.dirty.ratio + cleanup.policy + * 日志清除策略 + delete(默认) + compcat + + unclean.leader.election.enable + min.insync.replicas + compression.type + * 如果设置 cleanup.policy=compcat + * 那么可以通过该参数来设置,消息的压缩算法 + uncompressed + snappy + lz4 + gzip + + preallocate + message.format.version + message.timestamp.type + message.timestamp.difference.max.ms + message.downconversion.enable + diff --git a/Kafka/api/common-TopicPartition.java b/Kafka/api/common-TopicPartition.java new file mode 100644 index 00000000..a9f8ace1 --- /dev/null +++ b/Kafka/api/common-TopicPartition.java @@ -0,0 +1,61 @@ +-------------------------- +TopicPartition | +-------------------------- + # 用于描述一个主题和分区对象 + # 源码 + package org.apache.kafka.common; + + import java.io.Serializable; + import java.util.Objects; + public final class TopicPartition implements Serializable { + private static final long serialVersionUID = -613627415771699627L; + + private int hash = 0; + private final int partition; //分区号 + private final String topic; // 主题 + + public TopicPartition(String topic, int partition) { + this.partition = partition; + this.topic = topic; + } + + public int partition() { + return partition; + } + + public String topic() { + return topic; + } + + @Override + public int hashCode() { + if (hash != 0) + return hash; + final int prime = 31; + int result = 1; + result = prime * result + partition; + result = prime * result + Objects.hashCode(topic); + this.hash = result; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TopicPartition other = (TopicPartition) obj; + return partition == other.partition && Objects.equals(topic, other.topic); + } + + @Override + public String toString() { + return topic + "-" + partition; + } + } + + + \ No newline at end of file diff --git a/Kafka/api/consumer-ConsumerConfig.java b/Kafka/api/consumer-ConsumerConfig.java new file mode 100644 index 00000000..e3e1d8d4 --- /dev/null +++ b/Kafka/api/consumer-ConsumerConfig.java @@ -0,0 +1,199 @@ +----------------------------- +ConsumerConfig | +----------------------------- + # 消息消费者配置 + # 配置项 + bootstrap.servers + * kafka集群的节点 + * ip:port + * 连接多个使用逗号分隔 + + client.dns.lookup + group.id + * 设置消费组,默认值为空字符串 + + session.timeout.ms + * 组管理协议中用来检测消费者是否失效的时间 + * 默认:1000 + + heartbeat.interval.ms + * 使用Kafka分组管理功能的时候 + * 消费者与协调器之间的心跳间隔时间,默认值为:3000ms + * 通过心跳感知新的消费者加入/离开组,从而重新进行分区的分配(负载均衡) + + * 该值必须比: session.timeout.ms 小(通常不高于它的 1/3) + + partition.assignment.strategy + * 消费者与topic之间的分区分配策略实现类 + * 接口:PartitionAssignor + * 默认:org.apache.kafka.clients.consumer.RangeAssignor + + metadata.max.age.ms + * 更新元数据的间隔,默认为:300000ms,5分钟 + * 如果超过这个时间元数据没有被更新,那么就会主动的进行更新 + + enable.auto.commit + * 是否开启自动提交消费位移,默认值为:true + + auto.commit.interval.ms + * * 设置两次位移记录提交的间隔,默认: 50000(ms) 也就是5秒 + + client.id + * 当前消费者的id + * 如果不设置,会自动的生成一个非空字符串(consumer-[编号]) + consumer-1....consumer-x + + max.partition.fetch.bytes + * 配置从每个分区返回给消费者的最大数据量 + * 默认为:1MB,该参数与:fetch.max.bytes 相似 + * 不过该参数限制的是一次拉取中整体消息的大小 + + * 同样的,它也不是一个强硬的限制 + * 如果这个参数比消息大小要小,消息还是可以被消费的 + + send.buffer.bytes + * 用来设置 Socket 发送消息缓冲区(SO_SNDBUF)的大小,默认值为 131072B,也就是128KB + * 如果设置为 -l,则使用操作系统的默认值 + + receive.buffer.bytes + * 用来设置 Socket 接收消息缓冲区(SO_RECBUF )的大小 + * 默认值为 32768B,也就是32 kb + * 如果设置为 -l,则使用操作系统的默认值 + * 如果 Producer 与 Kafka 处于不同的机房 ,则可以适地调大这个参数值 + + fetch.min.bytes + * 消费者一次poll()能从一个分区可以拉取的最小数据值,默认为 1Byte + * 如果消息不足该值,那么就会阻塞,直到大于等于该值,才会返回 + * 适当的调大这个参数,可以带来吞吐量,但是也会造成额外的延迟 + * 对于延迟敏感的应用就不适合 + + fetch.max.bytes + * 消费者一次能从一个分区中拉取的最大数据值,默认为 50MB + + * 该参数设置的值,不是绝对的最大值 + * 如果在第一个非空的分区中拉取的第一条消息大于该值,那么消息仍然会返回 + * 以确保消费者继续工作 + + * 它不是一个强硬的限制 + * 就算这个参数比消息大小要小,消息还是可以被消费的 + * Kafak中所能接受的消息最大值,是通过服务端的参数:message.max.bytes 来设置 + + fetch.max.wait.ms + * 在数据不足 fetch.min.bytes 的时候,kafka会阻塞 + * 该参数指定kafka的最大等待时间,默认为 500ms + * 超时后会自动的返回 + * 如果应用对延迟敏感,那可以适当的调小该参数 + + reconnect.backoff.ms + * 配置尝试重新连接指定主机之前的等待时间 + * 避免频繁地连接主机,默认值为: 50ms + * 这种机制适用于消费者向broker发送的所有请求 + + reconnect.backoff.max.ms + retry.backoff.ms + * 配置尝试重新发送失败的请求到指定的主题分区之前的等待时间 + * 避免在某些故障情况下频繁地发送,默认值为:100 ms + + auto.offset.reset + * 当消费者找不到消费偏移量记录的时候,从哪里开始进行消费 + * 枚举值: + earliest 重置为最早的偏移量,从头开始消费 + latest 将偏移重置为最新偏移量,通俗的说就是不消费以前的消息了,从下条消息开始消费(默认) + none 如果没有找到偏移量记录,抛出异常 + + * 如果设置偏移量越界了,也会通过该配置的策略来重置消费偏移量 + + + check.crcs + metrics.sample.window.ms + metrics.num.samples + metrics.recording.level + metric.reporters + key.deserializer + value.deserializer + * 设置value和key的解码器 + + request.timeout.ms + * 默认的网络请求超时时间 + * 消费者等待broker的响应时间 + + default.api.timeout.ms + connections.max.idle.ms + * 设置在多久之后关闭限制的连接,默认值是:540000 ms = 9分钟 + + interceptor.classes + * 配置一个或者多个消费者拦截器 + * 如果是多个使用逗号分隔 + + max.poll.records + * 一次最多可以拉取多少条消息 + * 默认值为 500 条,如果消息的体积都比较小的话,可以调大该值 + + max.poll.interval.ms + * 通过消费者管理消费者的时候,该配置指定拉取消息线程的最大空闲时间 + * 如果超过这个时间还没发起poll()操作,则消费组认为该消费者已经离开了消费组 + * 将会再次触发负载均衡(分区计算) + * 默认值:300000 + + exclude.internal.topics + * Kafka中有两个内部主题:_consumer_offsets 和 __transaction_state + * 该参数指定这俩内部主题是否对消费者公开,默认值为:true + + * 如果设置为true,那么消费者只能使用:subsribe(Collection<> ) 来订阅到内部Topic + + internal.leave.group.on.close + isolation.level + * 配置事务的隔离级别 + * 枚举值: + READ_UNCOMMITTED(默认) + * 可以读取到HW(High Watermark)处的位置 + * 可以读取到消费者未commit的事务消息 + + READ_COMMITTED + * 读事务已经提交的消息 + * 只能消费到 LSO(Last Stable Offset)的位置 + + security.protocol + ssl.protocol + ssl.provider + ssl.cipher.suites + ssl.enabled.protocols + ssl.keystore.type + ssl.keystore.location + ssl.keystore.password + ssl.key.password + ssl.truststore.type + ssl.truststore.location + ssl.truststore.password + ssl.keymanager.algorithm + ssl.trustmanager.algorithm + ssl.endpoint.identification.algorithm + ssl.secure.random.implementation + sasl.kerberos.service.name + sasl.kerberos.kinit.cmd + sasl.kerberos.ticket.renew.window.factor + sasl.kerberos.ticket.renew.jitter + sasl.kerberos.min.time.before.relogin + sasl.login.refresh.window.factor + sasl.login.refresh.window.jitter + sasl.login.refresh.min.period.seconds + sasl.login.refresh.buffer.seconds + sasl.mechanism + sasl.jaas.config + sasl.client.callback.handler.class + sasl.login.callback.handler.class + sasl.login.class + + # 构造函数 + ProducerConfig(Properties props) + ProducerConfig(Map props) + + # 静态方法 + Map addSerializerToConfig(Map configs,Serializer keySerializer, Serializer valueSerializer) + Properties addSerializerToConfig(Properties properties,Serializer keySerializer,Serializer valueSerializer) + Set configNames() + * 返回可配置的key + + void main(String[] args) + * main函数,以html格式打印配置和说明 + \ No newline at end of file diff --git a/Kafka/api/consumer-ConsumerRecord.java b/Kafka/api/consumer-ConsumerRecord.java new file mode 100644 index 00000000..1b79ca9b --- /dev/null +++ b/Kafka/api/consumer-ConsumerRecord.java @@ -0,0 +1,54 @@ +------------------------ +ConsumerRecord | +------------------------ + # 消费者消费的消息对象 + # 静态属性 + public static final long NO_TIMESTAMP = RecordBatch.NO_TIMESTAMP; + public static final int NULL_SIZE = -1; + public static final int NULL_CHECKSUM = -1; + + # 构造函数 + ConsumerRecord(String topic,int partition,long offset,K key,V value) + ConsumerRecord(String topic,int partition,long offset,long timestamp,TimestampType timestampType,Long checksum,int serializedKeySize,int serializedValueSize,K key,V value,Headers headers) + ConsumerRecord(String topic,int partition,long offset,long timestamp,TimestampType timestampType,Long checksum,int serializedKeySize,int serializedValueSize,K key,V value,Headers headers,Optional leaderEpoch) + ConsumerRecord(String topic,int partition,long offset,long timestamp,TimestampType timestampType,long checksum,int serializedKeySize,int serializedValueSize,K key,V value) + + timestampType + * 时间戳的类型,枚举值 + NO_TIMESTAMP_TYPE(-1, "NoTimestampType") // 没时间戳 + CREATE_TIME(0, "CreateTime") // 消息创建时间 + LOG_APPEND_TIME(1, "LogAppendTime"); // 追加到日志文件的时间 + + headers + * 消息头,可以用于传输业务数据以外的其他信息 + * Headers是一个接口,实现类:RecordHeaders + + # 实例属性 + private final String topic; // 主题 + private final int partition; // 分区号 + private final long offset; // 消费偏移量 + private final long timestamp; // 时间戳 + private final TimestampType timestampType; // 时间戳的类型,创建时间,和追加时间 + private final int serializedKeySize; // key的大小 + private final int serializedValueSize; // value的大小 + private final Headers headers; // 消息头 + private final K key; // 键 + private final V value; // 值 + private final Optional leaderEpoch; + private volatile Long checksum; // CRC32校验值 + + # 实例方法 + Headers headers() + K key() + Optional leaderEpoch() + long offset() + int partition() + + int serializedKeySize() + int serializedValueSize() + * 返回key/value的大小,如果为 null,返回 -1 + + long timestamp() + TimestampType timestampType() + String topic() + V value() diff --git a/Kafka/api/consumer-ConsumerRecords.java b/Kafka/api/consumer-ConsumerRecords.java new file mode 100644 index 00000000..32f0629f --- /dev/null +++ b/Kafka/api/consumer-ConsumerRecords.java @@ -0,0 +1,32 @@ +-------------------------------- +ConsumerRecords | +-------------------------------- + # 消费者消费的消息对象的集合 + # 它实现了迭代器:Iterable,表示多个ConsumerRecord + # 静态属性 + ConsumerRecords EMPTY = new ConsumerRecords<>(Collections.EMPTY_MAP); + + # 静态方法 + ConsumerRecords empty() + + # 实例方法 + int count(); + * 消息总数量 + + boolean isEmpty(); + * 是否为空 + + Iterator> iterator(); + * 返回包含了所有消息的迭代器 + + Set partitions(); + * 返回当前主题消息所在的所有主题以及分区 + + Iterable> records(String topic); + * 返回包含了指定主题消息的迭代器 + * 当前消费者可能订阅了多个主题 + + List> records(TopicPartition partition); + * 返回包含了指定主题,指定分区消息的集合 + * 当前消费者可能订阅了多个主题,或者多个分区 + diff --git a/Kafka/api/consumer-KafkaConsumer.java b/Kafka/api/consumer-KafkaConsumer.java new file mode 100644 index 00000000..d5e029bd --- /dev/null +++ b/Kafka/api/consumer-KafkaConsumer.java @@ -0,0 +1,139 @@ +--------------------------- +KafkaConsumer | +--------------------------- + # 消息消费者 + # 构造函数 + KafkaConsumer(Map configs) + KafkaConsumer(Map configs,Deserializer keyDeserializer,Deserializer valueDeserializer) + KafkaConsumer(Properties properties) + KafkaConsumer(Properties properties,Deserializer keyDeserializer,Deserializer valueDeserializer) + + # 实例方法 + void close() + void close(Duration timeout) + * 关闭客户端,释放资源 + * timeout 设置超时时间,默认是 30s + + void commitAsync() + void commitAsync(final Map offsets, OffsetCommitCallback callback) + void commitAsync(OffsetCommitCallback callback) + * 异步提交消费位移 + * offsets 可以设置指定主题,指定分区的位移值 + * OffsetCommitCallback 设置回调 + void onComplete(Map offsets, Exception exception); + + void commitSync() + void commitSync(Duration timeout) + void commitSync(final Map offsets) + void commitSync(final Map offsets, final Duration timeout) + * offsets 可以设置指定主题,指定分区的位移值 + * 同步提交消费位移 + + OffsetAndMetadata committed(TopicPartition partition) + OffsetAndMetadata committed(TopicPartition partition, final Duration timeout) + * 获取已经提交过的消费位移 + * OffsetAndMetadata + private final long offset; + private final String metadata; + private final Integer leaderEpoch; + + Map beginningOffsets(Collection partitions) + Map beginningOffsets(Collection partitions, Duration timeout) + * 获取指定分区的开始消费偏移量 + * 这个偏移量不一定是0,因为kafka日志清理动作可能会清理旧的日志 + + Map endOffsets(Collection partitions) + Map endOffsets(Collection partitions, Duration timeout) + * 获取指定分区的末尾消费偏移量(就是下次待写入消息的位置) + * timeout指定超时时间,如果不指定,使用:request.timeout.ms 配置 + + Map offsetsForTimes(Map timestampsToSearch) + Map offsetsForTimes(Map timestampsToSearch, Duration timeout) + * 获取指定时间的指定分区的消费偏移量 + * Map 里面的 value 字段就表示时间戳值 + * OffsetAndTimestamp + private final long timestamp; // 时间戳 + private final long offset; // 消费偏移量 + private final Optional leaderEpoch; + + Map> listTopics() + Map> listTopics(Duration timeout) + + Map metrics() + + + List partitionsFor(String topic) + List partitionsFor(String topic, Duration timeout) + * 返回指定主题的所有分区信息,timeout 指定等待返回元数据的超时时间 + * PartitionInfo 描述一个分区信息 + private final String topic; // 主题 + private final int partition; // 当前节点编号 + private final Node leader; // leader节点 + private final Node[] replicas; // ar列表 + private final Node[] inSyncReplicas; // isr列表 + private final Node[] offlineReplicas; // osr列表 + * Node 描述节点信息 + private final int id; + private final String idString; + private final String host; //主机名 + private final int port; // 端口 + private final String rack; + + + Set paused() + void pause(Collection partitions) + void resume(Collection partitions) + * 暂停/恢复对某些主题,分区的消费 + + ConsumerRecords poll(final Duration timeout) + * 从broker拉取消息 + * timeout控制阻塞时间 + + long position(TopicPartition partition) + long position(TopicPartition partition, final Duration timeout) + * 获取到下一条需要拉取的消息位置 + * timeout控制阻塞时间,因为要获取元数据,可能会阻塞 + + void seek(TopicPartition partition, long offset) + void seekToBeginning(Collection partitions) + void seekToEnd(Collection partitions) + * 移动指定topic的指定partition的消费偏移量 + + Set subscription() + void subscribe(Collection topics) + void subscribe(Collection topics, ConsumerRebalanceListener listener) + void subscribe(Pattern pattern) + void subscribe(Pattern pattern, ConsumerRebalanceListener listener) + * 订阅主题,如果方法被重复的调用,那么以最后一次调用的为准 + * 如果使用正则表达式的方法订阅了主题,就算是主题不存在也可以,在主题被创建后,符合条件的主题会被自动的订阅 + * 负载均衡监听器:ConsumerRebalanceListener + void onPartitionsRevoked(Collection partitions); + void onPartitionsAssigned(Collection partitions); + + * 使用这种方式进行订阅消息具有自动负载均衡的功能 + * 在多个消费者的情况下,可以根据分区分配策略来自动分配各个消费者与分区的关系 + * 在消费组内消费者的增加/减少,分区分配关系会自动的跳转,以及实现故障的自动转移 + + + void assign(Collection partitions) + * 还可以直接订阅指定主题的指定分区 + * TopicPartition 对象用于描述分区和主题 + private int hash = 0; //hash值 + private final int partition; // 分区编号 + private final String topic; // 主题信息 + TopicPartition(String topic, int partition) + * 这种方式订阅,不具备自动的负载均衡功能 + + Set assignment() + * 获取订阅的主题和分配的partition + + void unsubscribe() + * 取消订阅主题 + * 也可以通过 subscribe 去订阅一个空的主题集合来达到取消订阅的效果 + subscribe(new ArrayList()); + + void wakeup() + * 该方法是唯一可以从其他线程里面调用的安全的方法 + * 该方法被调用后,可以退出 poll() 逻辑,并且抛出:WakeupException + * 并不需要处理该异常,它只是退出 poll() 的一种机制 + * 跳出循环以后一定要显式地执行关闭动作以释放运行过程中占用的各种系统资源 diff --git a/Kafka/api/consumer-PartitionAssignor.java b/Kafka/api/consumer-PartitionAssignor.java new file mode 100644 index 00000000..14a8f839 --- /dev/null +++ b/Kafka/api/consumer-PartitionAssignor.java @@ -0,0 +1,70 @@ +------------------------------ +PartitionAssignor | +------------------------------ + # 消费者的分区分配策略 + # 抽象方法 + Subscription subscription(Set topics); + + Map assign(Cluster metadata, Map subscriptions); + + void onAssignment(Assignment assignment); + + String name(); + + class Subscription { + private final List topics; + private final ByteBuffer userData; + + public Subscription(List topics, ByteBuffer userData) { + this.topics = topics; + this.userData = userData; + } + + public Subscription(List topics) { + this(topics, ByteBuffer.wrap(new byte[0])); + } + + public List topics() { + return topics; + } + + public ByteBuffer userData() { + return userData; + } + + @Override + public String toString() { + return "Subscription(" + + "topics=" + topics + + ')'; + } + } + + class Assignment { + private final List partitions; + private final ByteBuffer userData; + + public Assignment(List partitions, ByteBuffer userData) { + this.partitions = partitions; + this.userData = userData; + } + + public Assignment(List partitions) { + this(partitions, ByteBuffer.wrap(new byte[0])); + } + + public List partitions() { + return partitions; + } + + public ByteBuffer userData() { + return userData; + } + + @Override + public String toString() { + return "Assignment(" + + "partitions=" + partitions + + ')'; + } + } diff --git a/Kafka/api/consumer-config.html b/Kafka/api/consumer-config.html new file mode 100644 index 00000000..715bfcc2 --- /dev/null +++ b/Kafka/api/consumer-config.html @@ -0,0 +1,1168 @@ + + + + +Document + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionTypeDefaultValid ValuesImportance
key.deserializerDeserializer class for key that implements the org.apache.kafka.common.serialization.Deserializer + interface. + classhigh
value.deserializerDeserializer class for value that implements the org.apache.kafka.common.serialization.Deserializer + interface. + classhigh
bootstrap.serversA list of host/port pairs to use for establishing the + initial connection to the Kafka cluster. The client will make use + of all servers irrespective of which servers are specified here for + bootstrapping—this list only impacts the initial hosts used + to discover the full set of servers. This list should be in the + form host1:port1,host2:port2,.... Since these servers + are just used for the initial connection to discover the full + cluster membership (which may change dynamically), this list need + not contain the full set of servers (you may want more than one, + though, in case a server is down). + list""non-null stringhigh
fetch.min.bytesThe minimum amount of data the server should return for a + fetch request. If insufficient data is available the request will + wait for that much data to accumulate before answering the request. + The default setting of 1 byte means that fetch requests are + answered as soon as a single byte of data is available or the fetch + request times out waiting for data to arrive. Setting this to + something greater than 1 will cause the server to wait for larger + amounts of data to accumulate which can improve server throughput a + bit at the cost of some additional latency.int1[0,...]high
group.idA unique string that identifies the consumer group this + consumer belongs to. This property is required if the consumer uses + either the group management functionality by using subscribe(topic) + or the Kafka-based offset management strategy. + string""high
heartbeat.interval.msThe expected time between heartbeats to the consumer + coordinator when using Kafka's group management facilities. + Heartbeats are used to ensure that the consumer's session stays + active and to facilitate rebalancing when new consumers join or + leave the group. The value must be set lower than session.timeout.ms, + but typically should be set no higher than 1/3 of that value. It + can be adjusted even lower to control the expected time for normal + rebalances. + int3000high
max.partition.fetch.bytesThe maximum amount of data per-partition the server will + return. Records are fetched in batches by the consumer. If the + first record batch in the first non-empty partition of the fetch is + larger than this limit, the batch will still be returned to ensure + that the consumer can make progress. The maximum record batch size + accepted by the broker is defined via message.max.bytes + (broker config) or max.message.bytes (topic config). + See fetch.max.bytes for limiting the consumer request size. + int1048576[0,...]high
session.timeout.msThe timeout used to detect consumer failures when using + Kafka's group management facility. The consumer sends periodic + heartbeats to indicate its liveness to the broker. If no heartbeats + are received by the broker before the expiration of this session + timeout, then the broker will remove this consumer from the group + and initiate a rebalance. Note that the value must be in the + allowable range as configured in the broker configuration by group.min.session.timeout.ms + and group.max.session.timeout.ms. + int10000high
ssl.key.passwordThe password of the private key in the key store file. This + is optional for client.passwordnullhigh
ssl.keystore.locationThe location of the key store file. This is optional for + client and can be used for two-way authentication for client.stringnullhigh
ssl.keystore.passwordThe store password for the key store file. This is optional + for client and only needed if ssl.keystore.location is configured. + passwordnullhigh
ssl.truststore.locationThe location of the trust store file.stringnullhigh
ssl.truststore.passwordThe password for the trust store file. If a password is not + set access to the truststore is still available, but integrity + checking is disabled.passwordnullhigh
auto.offset.resetWhat to do when there is no initial offset in Kafka or if + the current offset does not exist any more on the server (e.g. + because that data has been deleted): +
    +
  • earliest: automatically reset the offset to the earliest + offset +
  • latest: automatically reset the offset to the latest + offset
  • +
  • none: throw exception to the consumer if no previous + offset is found for the consumer's group
  • +
  • anything else: throw exception to the consumer.
  • +
+
stringlatest[latest, earliest, none]medium
client.dns.lookup

Controls how the client uses DNS lookups.

+

+ If set to + use_all_dns_ips + then, when the lookup returns multiple IP addresses for a + hostname, they will all be attempted to connect to before failing + the connection. Applies to both bootstrap and advertised servers. +

+

+ If the value is + resolve_canonical_bootstrap_servers_only + each entry will be resolved and expanded into a list of canonical + names. +

stringdefault[default, use_all_dns_ips, + resolve_canonical_bootstrap_servers_only]medium
connections.max.idle.msClose idle connections after the number of milliseconds + specified by this config.long540000medium
default.api.timeout.msSpecifies the timeout (in milliseconds) for consumer APIs + that could block. This configuration is used as the default timeout + for all consumer operations that do not explicitly accept a timeout + parameter. + int60000[0,...]medium
enable.auto.commitIf true the consumer's offset will be periodically + committed in the background.booleantruemedium
exclude.internal.topicsWhether records from internal topics (such as offsets) + should be exposed to the consumer. If set to true the + only way to receive records from an internal topic is subscribing + to it. + booleantruemedium
fetch.max.bytesThe maximum amount of data the server should return for a + fetch request. Records are fetched in batches by the consumer, and + if the first record batch in the first non-empty partition of the + fetch is larger than this value, the record batch will still be + returned to ensure that the consumer can make progress. As such, + this is not a absolute maximum. The maximum record batch size + accepted by the broker is defined via message.max.bytes + (broker config) or max.message.bytes (topic config). + Note that the consumer performs multiple fetches in parallel. + int52428800[0,...]medium
isolation.level

+ Controls how to read messages written transactionally. If set to + read_committed + , consumer.poll() will only return transactional messages which + have been committed. If set to + read_uncommitted + ' (the default), consumer.poll() will return all messages, even + transactional messages which have been aborted. Non-transactional + messages will be returned unconditionally in either mode. +

+

+ Messages will always be returned in offset order. Hence, in + read_committed + mode, consumer.poll() will only return messages up to the last + stable offset (LSO), which is the one less than the offset of the + first open transaction. In particular any messages appearing after + messages belonging to ongoing transactions will be withheld until + the relevant transaction has been completed. As a result, + read_committed + consumers will not be able to read up to the high watermark when + there are in flight transactions. +

+

+ Further, when in + read_committed + the seekToEnd method will return the LSO

stringread_uncommitted[read_committed, read_uncommitted]medium
max.poll.interval.msThe maximum delay between invocations of poll() when using + consumer group management. This places an upper bound on the amount + of time that the consumer can be idle before fetching more records. + If poll() is not called before expiration of this timeout, then the + consumer is considered failed and the group will rebalance in order + to reassign the partitions to another member.int300000[1,...]medium
max.poll.recordsThe maximum number of records returned in a single call to + poll().int500[1,...]medium
partition.assignment.strategyThe class name of the partition assignment strategy that + the client will use to distribute partition ownership amongst + consumer instances when group management is usedlistclass org.apache.kafka.clients.consumer.RangeAssignornon-null stringmedium
receive.buffer.bytesThe size of the TCP receive buffer (SO_RCVBUF) to use when + reading data. If the value is -1, the OS default will be used.int65536[-1,...]medium
request.timeout.msThe configuration controls the maximum amount of time the + client will wait for the response of a request. If the response is + not received before the timeout elapses the client will resend the + request if necessary or fail the request if retries are exhausted.int30000[0,...]medium
sasl.client.callback.handler.classThe fully qualified name of a SASL client callback handler + class that implements the AuthenticateCallbackHandler interface.classnullmedium
sasl.jaas.configJAAS login context parameters for SASL connections in the + format used by JAAS configuration files. JAAS configuration file + format is described here. + The format for the value is: 'loginModuleClass + controlFlag (optionName=optionValue)*;'. For brokers, the config + must be prefixed with listener prefix and SASL mechanism name in + lower-case. For example, + listener.name.sasl_ssl.scram-sha-256.sasl.jaas.config=com.example.ScramLoginModule + required; + passwordnullmedium
sasl.kerberos.service.nameThe Kerberos principal name that Kafka runs as. This can be + defined either in Kafka's JAAS config or in Kafka's config.stringnullmedium
sasl.login.callback.handler.classThe fully qualified name of a SASL login callback handler + class that implements the AuthenticateCallbackHandler interface. + For brokers, login callback handler config must be prefixed with + listener prefix and SASL mechanism name in lower-case. For example, + listener.name.sasl_ssl.scram-sha-256.sasl.login.callback.handler.class=com.example.CustomScramLoginCallbackHandlerclassnullmedium
sasl.login.classThe fully qualified name of a class that implements the + Login interface. For brokers, login config must be prefixed with + listener prefix and SASL mechanism name in lower-case. For example, + listener.name.sasl_ssl.scram-sha-256.sasl.login.class=com.example.CustomScramLoginclassnullmedium
sasl.mechanismSASL mechanism used for client connections. This may be any + mechanism for which a security provider is available. GSSAPI is the + default mechanism.stringGSSAPImedium
security.protocolProtocol used to communicate with brokers. Valid values + are: PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL.stringPLAINTEXTmedium
send.buffer.bytesThe size of the TCP send buffer (SO_SNDBUF) to use when + sending data. If the value is -1, the OS default will be used.int131072[-1,...]medium
ssl.enabled.protocolsThe list of protocols enabled for SSL connections.listTLSv1.2,TLSv1.1,TLSv1medium
ssl.keystore.typeThe file format of the key store file. This is optional for + client.stringJKSmedium
ssl.protocolThe SSL protocol used to generate the SSLContext. Default + setting is TLS, which is fine for most cases. Allowed values in + recent JVMs are TLS, TLSv1.1 and TLSv1.2. SSL, SSLv2 and SSLv3 may + be supported in older JVMs, but their usage is discouraged due to + known security vulnerabilities.stringTLSmedium
ssl.providerThe name of the security provider used for SSL connections. + Default value is the default security provider of the JVM.stringnullmedium
ssl.truststore.typeThe file format of the trust store file.stringJKSmedium
auto.commit.interval.msThe frequency in milliseconds that the consumer offsets are + auto-committed to Kafka if enable.auto.commit is set + to true. + int5000[0,...]low
check.crcsAutomatically check the CRC32 of the records consumed. This + ensures no on-the-wire or on-disk corruption to the messages + occurred. This check adds some overhead, so it may be disabled in + cases seeking extreme performance.booleantruelow
client.idAn id string to pass to the server when making requests. + The purpose of this is to be able to track the source of requests + beyond just ip/port by allowing a logical application name to be + included in server-side request logging.string""low
fetch.max.wait.msThe maximum amount of time the server will block before + answering the fetch request if there isn't sufficient data to + immediately satisfy the requirement given by fetch.min.bytes.int500[0,...]low
interceptor.classesA list of classes to use as interceptors. Implementing the + org.apache.kafka.clients.consumer.ConsumerInterceptor + interface allows you to intercept (and possibly mutate) records + received by the consumer. By default, there are no interceptors. + list""non-null stringlow
metadata.max.age.msThe period of time in milliseconds after which we force a + refresh of metadata even if we haven't seen any partition + leadership changes to proactively discover any new brokers or + partitions.long300000[0,...]low
metric.reportersA list of classes to use as metrics reporters. Implementing + the org.apache.kafka.common.metrics.MetricsReporter + interface allows plugging in classes that will be notified of new + metric creation. The JmxReporter is always included to register JMX + statistics. + list""non-null stringlow
metrics.num.samplesThe number of samples maintained to compute metrics.int2[1,...]low
metrics.recording.levelThe highest recording level for metrics.stringINFO[INFO, DEBUG]low
metrics.sample.window.msThe window of time a metrics sample is computed over.long30000[0,...]low
reconnect.backoff.max.msThe maximum amount of time in milliseconds to wait when + reconnecting to a broker that has repeatedly failed to connect. If + provided, the backoff per host will increase exponentially for each + consecutive connection failure, up to this maximum. After + calculating the backoff increase, 20% random jitter is added to + avoid connection storms.long1000[0,...]low
reconnect.backoff.msThe base amount of time to wait before attempting to + reconnect to a given host. This avoids repeatedly connecting to a + host in a tight loop. This backoff applies to all connection + attempts by the client to a broker.long50[0,...]low
retry.backoff.msThe amount of time to wait before attempting to retry a + failed request to a given topic partition. This avoids repeatedly + sending requests in a tight loop under some failure scenarios.long100[0,...]low
sasl.kerberos.kinit.cmdKerberos kinit command path.string/usr/bin/kinitlow
sasl.kerberos.min.time.before.reloginLogin thread sleep time between refresh attempts.long60000low
sasl.kerberos.ticket.renew.jitterPercentage of random jitter added to the renewal time.double0.05low
sasl.kerberos.ticket.renew.window.factorLogin thread will sleep until the specified window factor + of time from last refresh to ticket's expiry has been reached, at + which time it will try to renew the ticket.double0.8low
sasl.login.refresh.buffer.secondsThe amount of buffer time before credential expiration to + maintain when refreshing a credential, in seconds. If a refresh + would otherwise occur closer to expiration than the number of + buffer seconds then the refresh will be moved up to maintain as + much of the buffer time as possible. Legal values are between 0 and + 3600 (1 hour); a default value of 300 (5 minutes) is used if no + value is specified. This value and + sasl.login.refresh.min.period.seconds are both ignored if their sum + exceeds the remaining lifetime of a credential. Currently applies + only to OAUTHBEARER.short300[0,...,3600]low
sasl.login.refresh.min.period.secondsThe desired minimum time for the login refresh thread to + wait before refreshing a credential, in seconds. Legal values are + between 0 and 900 (15 minutes); a default value of 60 (1 minute) is + used if no value is specified. This value and + sasl.login.refresh.buffer.seconds are both ignored if their sum + exceeds the remaining lifetime of a credential. Currently applies + only to OAUTHBEARER.short60[0,...,900]low
sasl.login.refresh.window.factorLogin refresh thread will sleep until the specified window + factor relative to the credential's lifetime has been reached, at + which time it will try to refresh the credential. Legal values are + between 0.5 (50%) and 1.0 (100%) inclusive; a default value of 0.8 + (80%) is used if no value is specified. Currently applies only to + OAUTHBEARER.double0.8[0.5,...,1.0]low
sasl.login.refresh.window.jitterThe maximum amount of random jitter relative to the + credential's lifetime that is added to the login refresh thread's + sleep time. Legal values are between 0 and 0.25 (25%) inclusive; a + default value of 0.05 (5%) is used if no value is specified. + Currently applies only to OAUTHBEARER.double0.05[0.0,...,0.25]low
ssl.cipher.suitesA list of cipher suites. This is a named combination of + authentication, encryption, MAC and key exchange algorithm used to + negotiate the security settings for a network connection using TLS + or SSL network protocol. By default all the available cipher suites + are supported.listnulllow
ssl.endpoint.identification.algorithmThe endpoint identification algorithm to validate server + hostname using server certificate.stringhttpslow
ssl.keymanager.algorithmThe algorithm used by key manager factory for SSL + connections. Default value is the key manager factory algorithm + configured for the Java Virtual Machine.stringSunX509low
ssl.secure.random.implementationThe SecureRandom PRNG implementation to use for SSL + cryptography operations.stringnulllow
ssl.trustmanager.algorithmThe algorithm used by trust manager factory for SSL + connections. Default value is the trust manager factory algorithm + configured for the Java Virtual Machine.stringPKIXlow
+ + + diff --git a/Kafka/api/producer-KafkaProducer.java b/Kafka/api/producer-KafkaProducer.java new file mode 100644 index 00000000..523054ec --- /dev/null +++ b/Kafka/api/producer-KafkaProducer.java @@ -0,0 +1,58 @@ +------------------------ +KafkaProducer | +------------------------ + # 消息生产者 + * 它是一个线程安全的对象 + + # 构造函数 + KafkaProducer(final Map configs) + KafkaProducer(Map configs, Serializer keySerializer, Serializer valueSerializer) + KafkaProducer(Properties properties) + KafkaProducer(Properties properties, Serializer keySerializer, Serializer valueSerializer) + + # 实例方法 + void close() + void close(long timeout, TimeUnit timeUnit) + * 关闭,释放资源 + * 如果不指定超时时间,那么会阻塞,直到所有的消息都发送完毕 + * 如果设置了超时时间,一旦超时就会强行执行退出 + + void flush() + + Map metrics() + List partitionsFor(String topic) + Future send(ProducerRecord record) + * send 方法是异步的,可以通过 Future 来获取到执行的结果(RecordMetadata),或者阻塞线程直到成功 + + Future send(ProducerRecord record, Callback callback) + * 同上,可以添加一个回调 Callback 接口实现:void onCompletion(RecordMetadata metadata, Exception exception); + * 如果异常则metadata为null,exception不为null,反之metadata不为null,exception为null + * 对于同一个分区而言,回调函数(Callback)的调用可以保证分区有序,先执行调用的 Callback 肯定会先执行 + + void initTransactions() + void abortTransaction() + void beginTransaction() + void commitTransaction() + void sendOffsetsToTransaction(Map offsets,String consumerGroupId) + +------------------------ +RecordMetadata | +------------------------ + # 记录的元数据 + # 静态属性 + int UNKNOWN_PARTITION = -1; + + # 构造方法 + RecordMetadata(TopicPartition topicPartition, long baseOffset, long relativeOffset, long timestamp,Long checksum, int serializedKeySize, int serializedValueSize) + + # 实例属性 + private final long offset; + private final long timestamp; + private final int serializedKeySize; + private final int serializedValueSize; + private final TopicPartition topicPartition; + + # 实例方法 + boolean hasOffset() + boolean hasTimestamp() + String topic() diff --git a/Kafka/api/producer-Partitioner.java b/Kafka/api/producer-Partitioner.java new file mode 100644 index 00000000..e5bfd919 --- /dev/null +++ b/Kafka/api/producer-Partitioner.java @@ -0,0 +1,15 @@ +---------------------- +Partitioner | +---------------------- + # 分区器接口 + # 抽象方法 + void configure(Map configs) + * 获取配置信息以及初始化数据 + + int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster); + * 定义分区的分配逻辑 + + void close(); + + # 提供的实现类 + DefaultPartitioner \ No newline at end of file diff --git a/Kafka/api/producer-ProducerConfig.java b/Kafka/api/producer-ProducerConfig.java new file mode 100644 index 00000000..faffcce5 --- /dev/null +++ b/Kafka/api/producer-ProducerConfig.java @@ -0,0 +1,168 @@ +------------------------ +ProducerConfig | +------------------------ + # 消息生产者配置 + # 提供了N多的配置项(它们都静态类变量存在,大写,.转换为下划线,并且以 CONFIG 结尾) + bootstrap.servers (BOOTSTRAP_SERVERS_CONFIG) + * Kafka集群的列表,多个使用逗号分隔 + * ip:port + + client.dns.lookup + buffer.memory + * 消息累加器(RecordAccumulator)的缓存大小 + * 默认值为:33554432kb = 32MB + + + + acks + * 指定分区中必须要有多少个副本接收了消息,才返回成功 + * 它涉及消息的可靠性和吞吐量之间的平衡 + * 枚举值 + 0 + * 消息发生就认为是成功了,不管分区是否写入成功(吞吐量最大,消息可能会丢失) + 1 + * 只要消息写入了分区的leader副本就算成功(折中,一般用这个) + -1 + * 值也可以是:all + * 必须是分区的所有副本(ISR)都写入了,才算成功 + * 但这并不意味着消息就一定可靠,因为 ISR 中可能只有 leader 副本,这样就退化成了 acks= l 的情况 + * 要获得更高的消息可靠性需要配合 min.insync.replicas 等参数的联动 + + compression.type + * 设置消息的压缩方式,默认为:none 也就是不压缩 + * 支持的算法(值) + gzip + snappy + lz4 + + batch.size + * 在 RecordAccumulator 中 BufferPool 会缓存的 ByteBuffer 大小 + * BufferPool 只针对特定大小的 ByteBuffer 进行管理,而其他大小的 ByteBuffer 不会缓存进 BufferPool 中 + * 默认:16384B,即 16KB + + linger.ms + * 设定生产者发送 ProducerBatch 之前等待更多消息(ProducerRecord)加入ProducerBatch 的时间 + * 默认值为 0(只有有消息加入了ProducerBatch就发送,不等待) + * 生产者客户端会在 ProducerBatch 被填满或等待时间超过 linger.ms 值时发迭出去 + * 增大这个参数的值会增加消息的延迟,但是同时能提升一定的吞吐量 + * 这个参数与 TCP 协议中的 Nagle 算法有异 曲同工之妙 + + delivery.timeout.ms + client.id + * 设置客户端的id + + send.buffer.bytes + * 用来设置 Socket 发送消息缓冲区(SO_SNDBUF)的大小,默认值为 131072B,也就是128KB + * 如果设置为 -l,则使用操作系统的默认值 + + receive.buffer.bytes + * 用来设置 Socket 接收消息缓冲区(SO_RECBUF )的大小 + * 默认值为 32768B,也就是32 kb + * 如果设置为 -l,则使用操作系统的默认值 + * 如果 Producer 与 Kafka 处于不同的机房 ,则可以适地调大这个参数值 + + max.request.size + * 限制消息生产者能够发送的消息最大体积默认为:1048576kb = 1MB + * 一般不建议去修改 + * 也要注意,broker也是可以设置接受的消息最大体积 + + reconnect.backoff.ms + reconnect.backoff.max.ms + retries + * 对于可重试的异常,如果配置了 retries 参数,那么只要在规定的重试次数内自行恢复了,就不会抛出异常 + * 默认值为 0,也就是说不会重试,立即抛出异常 + + retry.backoff.ms + * 对于可重试异常发生后,两次重复调用的时间间隔 + * 默认为:100(毫秒) + + max.block.ms + * 在send()api被阻塞的时候(缓冲区满了,没有空间存放消息,一般发生在消息生产速度,大于消息的发送速度) + * 最多阻塞多少时间(毫秒),超过该时间就会抛出异常 + * 默认为:60000(60s) + + request.timeout.ms + * Producer 等待请求响应的最长时间,默认值为 30000 (ms) + * 请求超时之后可以选择进行重试 + * 注意这个参数需要 比 broker 端参数 replica.lag.time.max.ms 的值要大 + * 这样可以减少因客户端重试而引起的消息重复的概率 + + metadata.max.age.ms + * 超过该时间未更新元数据,就会选择负载最小的broker,发送MetadataRequest来更新元数据信息 + * 默认300000(ms) = 5分钟 + + metrics.sample.window.ms + metrics.num.samples + metrics.recording.level + metric.reporters + max.in.flight.requests.per.connection + * 消息从RecordAccumulator发送到broker的请求,会被缓存到 InFlightRequests 中,直到响应 + * 该配置限制每个连接(也就是客户端与 Node 之间的连接)最多缓存的请求数 + * 该请求就是,把消息已经从缓存中发出去了,但是还没收到响应的请求 + * 默认值为5,也就是说,最多缓存5个未响应的请求,一旦超过该值,就不能往这个连接发送更多的请求了 + * 除非缓存中的请求,收到了响应 + + key.serializer + value.serializer + * 设置key/value的编码器 + * 值是编码器的类路径 + + connections.max.idle.ms + * 指定在多久之后关闭限制的连接,默认值是 540000(ms)即 9 分钟 + + partitioner.class + * 设置分区器的实现类 + + interceptor.classes + * 设置拦截器的实现类 + + security.protocol + ssl.protocol + ssl.provider + ssl.cipher.suites + ssl.enabled.protocols + ssl.keystore.type + ssl.keystore.location + ssl.keystore.password + ssl.key.password + ssl.truststore.type + ssl.truststore.location + ssl.truststore.password + ssl.keymanager.algorithm + ssl.trustmanager.algorithm + ssl.endpoint.identification.algorithm + ssl.secure.random.implementation + sasl.kerberos.service.name + sasl.kerberos.kinit.cmd + sasl.kerberos.ticket.renew.window.factor + sasl.kerberos.ticket.renew.jitter + sasl.kerberos.min.time.before.relogin + sasl.login.refresh.window.factor + sasl.login.refresh.window.jitter + sasl.login.refresh.min.period.seconds + sasl.login.refresh.buffer.seconds + sasl.mechanism + sasl.jaas.config + sasl.client.callback.handler.class + sasl.login.callback.handler.class + sasl.login.class + enable.idempotence + * 是否开启幕等性功能 + + transaction.timeout.ms + transactional.id + * 设置事务的id,必须唯一 + + + # 构造函数 + ProducerConfig(Properties props) + ProducerConfig(Map props) + + # 静态方法 + Map addSerializerToConfig(Map configs,Serializer keySerializer, Serializer valueSerializer) + Properties addSerializerToConfig(Properties properties,Serializer keySerializer,Serializer valueSerializer) + Set configNames() + * 返回可配置的key + + void main(String[] args) + * main函数,以html格式打印配置和说明 \ No newline at end of file diff --git a/Kafka/api/producer-ProducerInterceptor.java b/Kafka/api/producer-ProducerInterceptor.java new file mode 100644 index 00000000..e645387b --- /dev/null +++ b/Kafka/api/producer-ProducerInterceptor.java @@ -0,0 +1,13 @@ +---------------------------- +ProducerInterceptor | +---------------------------- + # 消息拦截器接口 + # 抽象方法 + void configure(Map configs); + public ProducerRecord onSend(ProducerRecord record); + * 消息发送之前执行 + + public void onAcknowledgement(RecordMetadata metadata, Exception exception); + * 消息发送完毕,得到响应之后发送api返回之前执行 + + public void close(); \ No newline at end of file diff --git a/Kafka/api/producer-ProducerRecord.java b/Kafka/api/producer-ProducerRecord.java new file mode 100644 index 00000000..401a94ca --- /dev/null +++ b/Kafka/api/producer-ProducerRecord.java @@ -0,0 +1,20 @@ +------------------------------ +ProducerRecord | +------------------------------ + # 生产者生产的消息对象 + # 构造函数 + ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value) + ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value, Iterable
headers) + ProducerRecord(String topic, Integer partition, K key, V value) + ProducerRecord(String topic, Integer partition, K key, V value, Iterable
headers) + ProducerRecord(String topic, K key, V value) + ProducerRecord(String topic, V value) + + + # 实例属性 + private final String topic; // 主题 + private final Integer partition; // 分区 + private final Headers headers; // 消息头 + private final K key; // k + private final V value; // v + private final Long timestamp; // 时间戳 diff --git a/Kafka/api/producer-config.html b/Kafka/api/producer-config.html new file mode 100644 index 00000000..ee99656e --- /dev/null +++ b/Kafka/api/producer-config.html @@ -0,0 +1,1164 @@ + + + + +Document + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionTypeDefaultValid ValuesImportance
key.serializerSerializer class for key that implements the org.apache.kafka.common.serialization.Serializer + interface. + classhigh
value.serializerSerializer class for value that implements the org.apache.kafka.common.serialization.Serializer + interface. + classhigh
acksThe number of acknowledgments the producer requires the + leader to have received before considering a request complete. This + controls the durability of records that are sent. The following + settings are allowed: +
    +
  • acks=0 If set to zero then the producer will + not wait for any acknowledgment from the server at all. The + record will be immediately added to the socket buffer and + considered sent. No guarantee can be made that the server has + received the record in this case, and the retries + configuration will not take effect (as the client won't generally + know of any failures). The offset given back for each record will + always be set to -1. +
  • acks=1 This will mean the leader will write + the record to its local log but will respond without awaiting + full acknowledgement from all followers. In this case should the + leader fail immediately after acknowledging the record but before + the followers have replicated it then the record will be lost. +
  • acks=all This means the leader will wait for + the full set of in-sync replicas to acknowledge the record. This + guarantees that the record will not be lost as long as at least + one in-sync replica remains alive. This is the strongest + available guarantee. This is equivalent to the acks=-1 setting. +
string1[all, -1, 0, 1]high
bootstrap.serversA list of host/port pairs to use for establishing the + initial connection to the Kafka cluster. The client will make use + of all servers irrespective of which servers are specified here for + bootstrapping—this list only impacts the initial hosts used + to discover the full set of servers. This list should be in the + form host1:port1,host2:port2,.... Since these servers + are just used for the initial connection to discover the full + cluster membership (which may change dynamically), this list need + not contain the full set of servers (you may want more than one, + though, in case a server is down). + list""non-null stringhigh
buffer.memoryThe total bytes of memory the producer can use to buffer + records waiting to be sent to the server. If records are sent + faster than they can be delivered to the server the producer will + block for max.block.ms after which it will throw an + exception. +

This setting should correspond roughly to the total memory + the producer will use, but is not a hard bound since not all + memory the producer uses is used for buffering. Some additional + memory will be used for compression (if compression is enabled) as + well as for maintaining in-flight requests. +

long33554432[0,...]high
compression.typeThe compression type for all data generated by the + producer. The default is none (i.e. no compression). Valid values + are none, gzip, snappy, lz4, + or zstd. Compression is of full batches of data, so + the efficacy of batching will also impact the compression ratio + (more batching means better compression). + stringnonehigh
retriesSetting a value greater than zero will cause the client to + resend any record whose send fails with a potentially transient + error. Note that this retry is no different than if the client + resent the record upon receiving the error. Allowing retries + without setting max.in.flight.requests.per.connection + to 1 will potentially change the ordering of records because if two + batches are sent to a single partition, and the first fails and is + retried but the second succeeds, then the records in the second + batch may appear first. Note additionall that produce requests will + be failed before the number of retries has been exhausted if the + timeout configured by delivery.timeout.ms expires + first before successful acknowledgement. Users should generally + prefer to leave this config unset and instead use delivery.timeout.ms + to control retry behavior. + int2147483647[0,...,2147483647]high
ssl.key.passwordThe password of the private key in the key store file. This + is optional for client.passwordnullhigh
ssl.keystore.locationThe location of the key store file. This is optional for + client and can be used for two-way authentication for client.stringnullhigh
ssl.keystore.passwordThe store password for the key store file. This is optional + for client and only needed if ssl.keystore.location is configured. + passwordnullhigh
ssl.truststore.locationThe location of the trust store file.stringnullhigh
ssl.truststore.passwordThe password for the trust store file. If a password is not + set access to the truststore is still available, but integrity + checking is disabled.passwordnullhigh
batch.sizeThe producer will attempt to batch records together into + fewer requests whenever multiple records are being sent to the same + partition. This helps performance on both the client and the + server. This configuration controls the default batch size in + bytes. +

No attempt will be made to batch records larger than this + size. +

Requests sent to brokers will contain multiple batches, one + for each partition with data available to be sent. +

A small batch size will make batching less common and may + reduce throughput (a batch size of zero will disable batching + entirely). A very large batch size may use memory a bit more + wastefully as we will always allocate a buffer of the specified + batch size in anticipation of additional records. +

int16384[0,...]medium
client.dns.lookup

Controls how the client uses DNS lookups.

+

+ If set to + use_all_dns_ips + then, when the lookup returns multiple IP addresses for a + hostname, they will all be attempted to connect to before failing + the connection. Applies to both bootstrap and advertised servers. +

+

+ If the value is + resolve_canonical_bootstrap_servers_only + each entry will be resolved and expanded into a list of canonical + names. +

stringdefault[default, use_all_dns_ips, + resolve_canonical_bootstrap_servers_only]medium
client.idAn id string to pass to the server when making requests. + The purpose of this is to be able to track the source of requests + beyond just ip/port by allowing a logical application name to be + included in server-side request logging.string""medium
connections.max.idle.msClose idle connections after the number of milliseconds + specified by this config.long540000medium
delivery.timeout.msAn upper bound on the time to report success or failure + after a call to send() returns. This limits the total + time that a record will be delayed prior to sending, the time to + await acknowledgement from the broker (if expected), and the time + allowed for retriable send failures. The producer may report + failure to send a record earlier than this config if either an + unrecoverable error is encountered, the retries have been + exhausted, or the record is added to a batch which reached an + earlier delivery expiration deadline. The value of this config + should be greater than or equal to the sum of request.timeout.ms + and linger.ms. + int120000[0,...]medium
linger.msThe producer groups together any records that arrive in + between request transmissions into a single batched request. + Normally this occurs only under load when records arrive faster + than they can be sent out. However in some circumstances the client + may want to reduce the number of requests even under moderate load. + This setting accomplishes this by adding a small amount of + artificial delay—that is, rather than immediately sending out + a record the producer will wait for up to the given delay to allow + other records to be sent so that the sends can be batched together. + This can be thought of as analogous to Nagle's algorithm in TCP. + This setting gives the upper bound on the delay for batching: once + we get batch.size worth of records for a partition it + will be sent immediately regardless of this setting, however if we + have fewer than this many bytes accumulated for this partition we + will 'linger' for the specified time waiting for more records to + show up. This setting defaults to 0 (i.e. no delay). Setting linger.ms=5, + for example, would have the effect of reducing the number of + requests sent but would add up to 5ms of latency to records sent in + the absence of load. + int0[0,...]medium
max.block.msThe configuration controls how long KafkaProducer.send() + and KafkaProducer.partitionsFor() will block.These + methods can be blocked either because the buffer is full or + metadata unavailable.Blocking in the user-supplied serializers or + partitioner will not be counted against this timeout. + long60000[0,...]medium
max.request.sizeThe maximum size of a request in bytes. This setting will + limit the number of record batches the producer will send in a + single request to avoid sending huge requests. This is also + effectively a cap on the maximum record batch size. Note that the + server has its own cap on record batch size which may be different + from this.int1048576[0,...]medium
partitioner.classPartitioner class that implements the org.apache.kafka.clients.producer.Partitioner + interface. + classorg.apache.kafka.clients.producer.internals.DefaultPartitionermedium
receive.buffer.bytesThe size of the TCP receive buffer (SO_RCVBUF) to use when + reading data. If the value is -1, the OS default will be used.int32768[-1,...]medium
request.timeout.msThe configuration controls the maximum amount of time the + client will wait for the response of a request. If the response is + not received before the timeout elapses the client will resend the + request if necessary or fail the request if retries are exhausted. + This should be larger than replica.lag.time.max.ms (a + broker configuration) to reduce the possibility of message + duplication due to unnecessary producer retries. + int30000[0,...]medium
sasl.client.callback.handler.classThe fully qualified name of a SASL client callback handler + class that implements the AuthenticateCallbackHandler interface.classnullmedium
sasl.jaas.configJAAS login context parameters for SASL connections in the + format used by JAAS configuration files. JAAS configuration file + format is described here. + The format for the value is: 'loginModuleClass + controlFlag (optionName=optionValue)*;'. For brokers, the config + must be prefixed with listener prefix and SASL mechanism name in + lower-case. For example, + listener.name.sasl_ssl.scram-sha-256.sasl.jaas.config=com.example.ScramLoginModule + required; + passwordnullmedium
sasl.kerberos.service.nameThe Kerberos principal name that Kafka runs as. This can be + defined either in Kafka's JAAS config or in Kafka's config.stringnullmedium
sasl.login.callback.handler.classThe fully qualified name of a SASL login callback handler + class that implements the AuthenticateCallbackHandler interface. + For brokers, login callback handler config must be prefixed with + listener prefix and SASL mechanism name in lower-case. For example, + listener.name.sasl_ssl.scram-sha-256.sasl.login.callback.handler.class=com.example.CustomScramLoginCallbackHandlerclassnullmedium
sasl.login.classThe fully qualified name of a class that implements the + Login interface. For brokers, login config must be prefixed with + listener prefix and SASL mechanism name in lower-case. For example, + listener.name.sasl_ssl.scram-sha-256.sasl.login.class=com.example.CustomScramLoginclassnullmedium
sasl.mechanismSASL mechanism used for client connections. This may be any + mechanism for which a security provider is available. GSSAPI is the + default mechanism.stringGSSAPImedium
security.protocolProtocol used to communicate with brokers. Valid values + are: PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL.stringPLAINTEXTmedium
send.buffer.bytesThe size of the TCP send buffer (SO_SNDBUF) to use when + sending data. If the value is -1, the OS default will be used.int131072[-1,...]medium
ssl.enabled.protocolsThe list of protocols enabled for SSL connections.listTLSv1.2,TLSv1.1,TLSv1medium
ssl.keystore.typeThe file format of the key store file. This is optional for + client.stringJKSmedium
ssl.protocolThe SSL protocol used to generate the SSLContext. Default + setting is TLS, which is fine for most cases. Allowed values in + recent JVMs are TLS, TLSv1.1 and TLSv1.2. SSL, SSLv2 and SSLv3 may + be supported in older JVMs, but their usage is discouraged due to + known security vulnerabilities.stringTLSmedium
ssl.providerThe name of the security provider used for SSL connections. + Default value is the default security provider of the JVM.stringnullmedium
ssl.truststore.typeThe file format of the trust store file.stringJKSmedium
enable.idempotenceWhen set to 'true', the producer will ensure that exactly + one copy of each message is written in the stream. If 'false', + producer retries due to broker failures, etc., may write duplicates + of the retried message in the stream. Note that enabling + idempotence requires max.in.flight.requests.per.connection + to be less than or equal to 5, retries to be greater + than 0 and acks must be 'all'. If these values are not + explicitly set by the user, suitable values will be chosen. If + incompatible values are set, a ConfigException will be + thrown. + booleanfalselow
interceptor.classesA list of classes to use as interceptors. Implementing the + org.apache.kafka.clients.producer.ProducerInterceptor + interface allows you to intercept (and possibly mutate) the records + received by the producer before they are published to the Kafka + cluster. By default, there are no interceptors. + list""non-null stringlow
max.in.flight.requests.per.connectionThe maximum number of unacknowledged requests the client + will send on a single connection before blocking. Note that if this + setting is set to be greater than 1 and there are failed sends, + there is a risk of message re-ordering due to retries (i.e., if + retries are enabled).int5[1,...]low
metadata.max.age.msThe period of time in milliseconds after which we force a + refresh of metadata even if we haven't seen any partition + leadership changes to proactively discover any new brokers or + partitions.long300000[0,...]low
metric.reportersA list of classes to use as metrics reporters. Implementing + the org.apache.kafka.common.metrics.MetricsReporter + interface allows plugging in classes that will be notified of new + metric creation. The JmxReporter is always included to register JMX + statistics. + list""non-null stringlow
metrics.num.samplesThe number of samples maintained to compute metrics.int2[1,...]low
metrics.recording.levelThe highest recording level for metrics.stringINFO[INFO, DEBUG]low
metrics.sample.window.msThe window of time a metrics sample is computed over.long30000[0,...]low
reconnect.backoff.max.msThe maximum amount of time in milliseconds to wait when + reconnecting to a broker that has repeatedly failed to connect. If + provided, the backoff per host will increase exponentially for each + consecutive connection failure, up to this maximum. After + calculating the backoff increase, 20% random jitter is added to + avoid connection storms.long1000[0,...]low
reconnect.backoff.msThe base amount of time to wait before attempting to + reconnect to a given host. This avoids repeatedly connecting to a + host in a tight loop. This backoff applies to all connection + attempts by the client to a broker.long50[0,...]low
retry.backoff.msThe amount of time to wait before attempting to retry a + failed request to a given topic partition. This avoids repeatedly + sending requests in a tight loop under some failure scenarios.long100[0,...]low
sasl.kerberos.kinit.cmdKerberos kinit command path.string/usr/bin/kinitlow
sasl.kerberos.min.time.before.reloginLogin thread sleep time between refresh attempts.long60000low
sasl.kerberos.ticket.renew.jitterPercentage of random jitter added to the renewal time.double0.05low
sasl.kerberos.ticket.renew.window.factorLogin thread will sleep until the specified window factor + of time from last refresh to ticket's expiry has been reached, at + which time it will try to renew the ticket.double0.8low
sasl.login.refresh.buffer.secondsThe amount of buffer time before credential expiration to + maintain when refreshing a credential, in seconds. If a refresh + would otherwise occur closer to expiration than the number of + buffer seconds then the refresh will be moved up to maintain as + much of the buffer time as possible. Legal values are between 0 and + 3600 (1 hour); a default value of 300 (5 minutes) is used if no + value is specified. This value and + sasl.login.refresh.min.period.seconds are both ignored if their sum + exceeds the remaining lifetime of a credential. Currently applies + only to OAUTHBEARER.short300[0,...,3600]low
sasl.login.refresh.min.period.secondsThe desired minimum time for the login refresh thread to + wait before refreshing a credential, in seconds. Legal values are + between 0 and 900 (15 minutes); a default value of 60 (1 minute) is + used if no value is specified. This value and + sasl.login.refresh.buffer.seconds are both ignored if their sum + exceeds the remaining lifetime of a credential. Currently applies + only to OAUTHBEARER.short60[0,...,900]low
sasl.login.refresh.window.factorLogin refresh thread will sleep until the specified window + factor relative to the credential's lifetime has been reached, at + which time it will try to refresh the credential. Legal values are + between 0.5 (50%) and 1.0 (100%) inclusive; a default value of 0.8 + (80%) is used if no value is specified. Currently applies only to + OAUTHBEARER.double0.8[0.5,...,1.0]low
sasl.login.refresh.window.jitterThe maximum amount of random jitter relative to the + credential's lifetime that is added to the login refresh thread's + sleep time. Legal values are between 0 and 0.25 (25%) inclusive; a + default value of 0.05 (5%) is used if no value is specified. + Currently applies only to OAUTHBEARER.double0.05[0.0,...,0.25]low
ssl.cipher.suitesA list of cipher suites. This is a named combination of + authentication, encryption, MAC and key exchange algorithm used to + negotiate the security settings for a network connection using TLS + or SSL network protocol. By default all the available cipher suites + are supported.listnulllow
ssl.endpoint.identification.algorithmThe endpoint identification algorithm to validate server + hostname using server certificate.stringhttpslow
ssl.keymanager.algorithmThe algorithm used by key manager factory for SSL + connections. Default value is the key manager factory algorithm + configured for the Java Virtual Machine.stringSunX509low
ssl.secure.random.implementationThe SecureRandom PRNG implementation to use for SSL + cryptography operations.stringnulllow
ssl.trustmanager.algorithmThe algorithm used by trust manager factory for SSL + connections. Default value is the trust manager factory algorithm + configured for the Java Virtual Machine.stringPKIXlow
transaction.timeout.msThe maximum amount of time in ms that the transaction + coordinator will wait for a transaction status update from the + producer before proactively aborting the ongoing transaction.If + this value is larger than the transaction.max.timeout.ms setting in + the broker, the request will fail with a InvalidTransactionTimeout + error. + int60000low
transactional.idThe TransactionalId to use for transactional delivery. This + enables reliability semantics which span multiple producer sessions + since it allows the client to guarantee that transactions using the + same TransactionalId have been completed prior to starting any new + transactions. If no TransactionalId is provided, then the producer + is limited to idempotent delivery. Note that enable.idempotence + must be enabled if a TransactionalId is configured. The default is + null, which means transactions cannot be used. Note + that, by default, transactions require a cluster of at least three + brokers which is the recommended setting for production; for + development you can change this, by adjusting broker setting transaction.state.log.replication.factor. + stringnullnon-empty stringlow
+ + + diff --git a/Kafka/kafka-admin-KafkaAdminClient.java b/Kafka/kafka-admin-KafkaAdminClient.java new file mode 100644 index 00000000..885e2d3a --- /dev/null +++ b/Kafka/kafka-admin-KafkaAdminClient.java @@ -0,0 +1,183 @@ +------------------------- +KafkaAdminClient | +------------------------- + # 继承自:AdminClient + # 实例的创建 + KafkaAdminClient KafkaAdminClient.create(Map config); + KafkaAdminClient KafkaAdminClient.create(Properties properties) + + # 方法 + AlterConfigsResult alterConfigs(Map configs, final AlterConfigsOptions options) + * 修好主题的配置 + + AlterReplicaLogDirsResult alterReplicaLogDirs(Map replicaAssignment, final AlterReplicaLogDirsOptions options) + + void close(long duration, TimeUnit unit); + + CreateAclsResult createAcls(Collection acls, CreateAclsOptions options) + CreateDelegationTokenResult createDelegationToken(final CreateDelegationTokenOptions options) + CreatePartitionsResult createPartitions(Map newPartitions,final CreatePartitionsOptions options) + * 修改parition数量和副本的数量 + + CreateTopicsResult createTopics(final Collection newTopics,final CreateTopicsOptions options) + * 创建topic + + DeleteAclsResult deleteAcls(Collection filters, DeleteAclsOptions options) + DeleteConsumerGroupsResult deleteConsumerGroups(Collection groupIds, DeleteConsumerGroupsOptions options) + DeleteRecordsResult deleteRecords(final Map recordsToDelete, final DeleteRecordsOptions options) + DeleteTopicsResult deleteTopics(Collection topicNames, DeleteTopicsOptions options) + * 删除topic + + DescribeAclsResult describeAcls(final AclBindingFilter filter, DescribeAclsOptions options) + DescribeClusterResult describeCluster(DescribeClusterOptions options) + DescribeConfigsResult describeConfigs(Collection configResources, final DescribeConfigsOptions options) + DescribeConsumerGroupsResult describeConsumerGroups(final Collection groupIds, final DescribeConsumerGroupsOptions options) + DescribeDelegationTokenResult describeDelegationToken(final DescribeDelegationTokenOptions options) + DescribeLogDirsResult describeLogDirs(Collection brokers, DescribeLogDirsOptions options) + DescribeReplicaLogDirsResult describeReplicaLogDirs(Collection replicas, DescribeReplicaLogDirsOptions options) + DescribeTopicsResult describeTopics(final Collection topicNames, DescribeTopicsOptions options) + * 获取指定topic的相信信息 + * 包括分区,isr,leader等信息 + + ExpireDelegationTokenResult expireDelegationToken(final byte[] hmac, final ExpireDelegationTokenOptions options) + ListConsumerGroupOffsetsResult listConsumerGroupOffsets(final String groupId, final ListConsumerGroupOffsetsOptions options) + ListConsumerGroupsResult listConsumerGroups(ListConsumerGroupsOptions options) + ListTopicsResult listTopics(final ListTopicsOptions options) + * 获取所有的topic数量 + + Map metrics() + RenewDelegationTokenResult renewDelegationToken(final byte[] hmac, final RenewDelegationTokenOptions options) + + # 基本的一些操作 + public static void addPartition() throws InterruptedException, ExecutionException { + Properties properties = new Properties(); + properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + KafkaAdminClient kafkaAdminClient = (KafkaAdminClient) KafkaAdminClient.create(properties); + + /** + * 把指定的partition数量增加到5个 + */ + // NewPartitions newPartitions = NewPartitions.increaseTo(5); // 参数表示修改后的数量 + + /** + * 把指定的parition数量增加到6个 + * 并且重新指定其副本的分配方案 + */ + List> newAssignments = new ArrayList<>(); + newAssignments.add(Arrays.asList(1,2)); // 第1个分区,有两个副本,在broker 1 和 2上 leader节点在broker2上 + newAssignments.add(Arrays.asList(2,3)); // 第2个分区,有两个副本,在broker 2 和 3上 leader节点在broker3上 + newAssignments.add(Arrays.asList(3,1)); // 第3个分区,有两个副本,在broker 3 和 1上 leader节点在broker1上 + NewPartitions newPartitions = NewPartitions.increaseTo(6,newAssignments); + + // 执行修改操作 + CreatePartitionsResult createPartitionsResult = kafkaAdminClient.createPartitions(Collections.singletonMap("topic_1", newPartitions)); + createPartitionsResult.all().get(); + } + + public static void alterConfig() throws InterruptedException, ExecutionException { + Properties properties = new Properties(); + properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + KafkaAdminClient kafkaAdminClient = (KafkaAdminClient) KafkaAdminClient.create(properties); + // ConfigEntry 表示一个配置项 + ConfigEntry configEntry = new ConfigEntry("cleanup.policy","compact"); + // 多个配置项构造成 Config + Config config = new Config(Arrays.asList(configEntry)); + // 根据 topic/broker 和Config 构建map + Map configMap = Collections.singletonMap(new ConfigResource(ConfigResource.Type.TOPIC, "topic_1"), config); + // 执行修改 + AlterConfigsResult alterConfigsResult = kafkaAdminClient.alterConfigs(configMap); + alterConfigsResult.all().get(); // 阻塞,直到所有的修改都完成 + } + public static void describe() throws InterruptedException, ExecutionException { + Properties properties = new Properties(); + properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + KafkaAdminClient kafkaAdminClient = (KafkaAdminClient) KafkaAdminClient.create(properties); + DescribeConfigsResult describeConfigsResult = kafkaAdminClient.describeConfigs(Arrays.asList(new ConfigResource(ConfigResource.Type.TOPIC,"topic_1"))); + Map configMap = describeConfigsResult.all().get(); + for(Map.Entry entry : configMap.entrySet()) { + // 类型和名称 + System.out.println("type=" + entry.getKey().type() + " name=" + entry.getKey().name()); + // 配置详情 + System.out.println(entry.getValue()); + } + } + + public static void createTopic() throws InterruptedException, ExecutionException { + Properties properties = new Properties(); + properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + KafkaAdminClient kafkaAdminClient = (KafkaAdminClient) KafkaAdminClient.create(properties); + NewTopic newTopic = new NewTopic("topic_1", 1, (short)1); + CreateTopicsResult createTopicsResult = kafkaAdminClient.createTopics(Arrays.asList(newTopic)); + createTopicsResult.all().get();// 阻塞线程,直到所有主题创建成功 + } + + public static void deleteTopic() throws InterruptedException, ExecutionException { + Properties properties = new Properties(); + properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + KafkaAdminClient kafkaAdminClient = (KafkaAdminClient) KafkaAdminClient.create(properties); + DeleteTopicsResult deleteTopicsResult = kafkaAdminClient.deleteTopics(Arrays.asList("demo1","demo2","demo3","test")); + deleteTopicsResult.all().get(); // 阻塞线程,直到所有主题删除成功 + } + + public static void listTopics() throws InterruptedException, ExecutionException { + Properties properties = new Properties(); + properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + KafkaAdminClient kafkaAdminClient = (KafkaAdminClient) KafkaAdminClient.create(properties); + ListTopicsResult listTopicsResult = kafkaAdminClient.listTopics(); + Collection topicListings = listTopicsResult.listings().get(); + for(TopicListing topicListing : topicListings) { + System.out.println("主题名称:" + topicListing.name() + " 是否是内部的:" + topicListing.isInternal()); + } + } + +------------------------- +AdminClientConfig | +------------------------- + # 管理客户端的配置项 + bootstrap.servers + * broker节点的地址 + client.id + metadata.max.age.ms + send.buffer.bytes + receive.buffer.bytes + reconnect.backoff.ms + reconnect.backoff.max.ms + retry.backoff.ms + request.timeout.ms + connections.max.idle.ms + retries + metrics.sample.window.ms + metrics.num.samples + metric.reporters + metrics.recording.level + client.dns.lookup + security.protocol + ssl.protocol + ssl.provider + ssl.cipher.suites + ssl.enabled.protocols + ssl.keystore.type + ssl.keystore.location + ssl.keystore.password + ssl.key.password + ssl.truststore.type + ssl.truststore.location + ssl.truststore.password + ssl.keymanager.algorithm + ssl.trustmanager.algorithm + ssl.endpoint.identification.algorithm + ssl.secure.random.implementation + sasl.kerberos.service.name + sasl.kerberos.kinit.cmd + sasl.kerberos.ticket.renew.window.factor + sasl.kerberos.ticket.renew.jitter + sasl.kerberos.min.time.before.relogin + sasl.login.refresh.window.factor + sasl.login.refresh.window.jitter + sasl.login.refresh.min.period.seconds + sasl.login.refresh.buffer.seconds + sasl.mechanism + sasl.jaas.config + sasl.client.callback.handler.class + sasl.login.callback.handler.class + sasl.login.class diff --git a/Kafka/kafka-admin.java b/Kafka/kafka-admin.java new file mode 100644 index 00000000..cd8103c9 --- /dev/null +++ b/Kafka/kafka-admin.java @@ -0,0 +1,30 @@ +----------------------------- +admin | +----------------------------- + # Maven + + org.apache.kafka + kafka_2.12 + 2.1.1 + + + # 包含了 shell 脚本执行命令时实际执行的类 + # 可以直接执行这些类的main方法,用于对kafka的维护 + String commands[] = new String[] { + "--zookeeper","192.168.2.102:2181", + "--create", + "--partitions","1", + "--replication-factor","1", + "--topic","topic-demo" + }; + TopicCommand.main(commands); + + # 相关的类库 + AclCommand + * kafka-acls.sh 脚本使用的类 + + TopicCommand + * kafka-topics.sh 脚本使用的类 + + KafkaAdminClient + * 一个超级管理的类,可以用于管理broker,topic,acl diff --git "a/Kafka/kafka-client-consumer-\345\210\206\345\214\272\345\210\206\351\205\215\347\255\226\347\225\245.java" "b/Kafka/kafka-client-consumer-\345\210\206\345\214\272\345\210\206\351\205\215\347\255\226\347\225\245.java" new file mode 100644 index 00000000..d251c4c8 --- /dev/null +++ "b/Kafka/kafka-client-consumer-\345\210\206\345\214\272\345\210\206\351\205\215\347\255\226\347\225\245.java" @@ -0,0 +1,38 @@ +--------------------- +分区分配策略 | +--------------------- + # 分区分配策略配置:partition.assignment.strategy + * 参数值为:PartitionAssignor 接口的实现类全路径 + * 系统提供的默认实现 + org.apache.kafka.clients.consumer.RangeAssignor(默认) + org.apache.kafka.clients.consumer.RoundRobinAssignor + org.apache.kafka.clients.consumer.StickyAssignor + + # PartitionAssignor 接口抽象方法 + Subscription subscription(Set topics); + Map assign(Cluster metadata, Map subscriptions); + void onAssignment(Assignment assignment); + String name(); + + + # RangeAssignor + * 按照消费者总数和分区总数进行整除运算来获得一个跨度,然后将分区按照跨度进行平均分配,以保证分区尽可能均匀地分配给所有的消费者 + + + # RoundRobinAssignor + * 将消费组内所有消费者及消费者订阅的所有主题的分区按照字典序排序 + * 然后通过轮询方式逐个将分区依次分配给每个消费者 + * 如果同一个消费组内所有的消费者的订阅信息都是相同的,那么 RoundRobinAssignor 分配策略的分区分配会是均匀的 + + # StickyAssignor + * 它主要有两个目的 + 分区的分配要尽可能均匀 + 分区的分配尽可能与上次分配的保持相同 + + * 当两者发生冲突时,第一个目标优先于第二个目标 + + + # 按照 Kafka 默认的消费逻辑设定, 一个分区 只能被同一个消费组( ConsumerGroup ) 内的一个消费者消费 + * 但这一设定不是绝对的,可以通过自定义分区分配策略使一个分区可以分配给多个消费者消费 + * 种实现方式会有一个严重的问题,默认的消费位移的提交会失效 + * 所有的消费者都会提交它自身的消费位移到 consumer_offsets 中,后提交的消费位移会覆盖前面提交的消费位移 diff --git "a/Kafka/kafka-client-consumer-\345\271\266\345\217\221\346\266\210\350\264\271.java" "b/Kafka/kafka-client-consumer-\345\271\266\345\217\221\346\266\210\350\264\271.java" new file mode 100644 index 00000000..fe003cfa --- /dev/null +++ "b/Kafka/kafka-client-consumer-\345\271\266\345\217\221\346\266\210\350\264\271.java" @@ -0,0 +1,3 @@ +--------------------- +并发消费 | +--------------------- \ No newline at end of file diff --git "a/Kafka/kafka-client-consumer-\346\266\210\350\264\271\344\275\215\347\247\273.java" "b/Kafka/kafka-client-consumer-\346\266\210\350\264\271\344\275\215\347\247\273.java" new file mode 100644 index 00000000..51cabc14 --- /dev/null +++ "b/Kafka/kafka-client-consumer-\346\266\210\350\264\271\344\275\215\347\247\273.java" @@ -0,0 +1,220 @@ +------------------------ +位移提交 | +------------------------ + # 每次poll返回的是还没有被消费过的消息 + * 因为这一点,所以需要记录消费者上一次消费的位移,而且必须持久化保存 + * 如果不持久化保存,可能会丢失,而且新加入进来的消费者也不知道从哪里开始消费 + + * 旧版本的消费者客户端,消费位移存放在 zookeeper中 + * 新版本的消费者客户端,消费位移存放在Kafka内部的主题 _consumer_offsets 中 + + * 把消息持久化的动作称之为提交,消费者在消费完消息之后,需要执行消费位移的提交 + + # 模拟图 + 0 1 2 3 4 5 6 7 + +-----+-----+-----+-----+-----+-----+-----+-----+ + | A | B | C | D | E | F | G | x | + +-----+-----+-----+-----+-----+-----+-----+-----+ + + 已经消费的消息(消费到D) + ————————————————————————> ^ 消费偏移量为 4 + + + # 获取到消费位移值 + long position(TopicPartition partition) + long position(TopicPartition partition, final Duration timeout) + * 获取到下一条需要拉取的消息位置 + * 其实就是自己消费的最后一条记录值 + 1 + + OffsetAndMetadata committed(TopicPartition partition) + OffsetAndMetadata committed(TopicPartition partition, final Duration timeout) + * 获取已经提交过的消费位移 + * 就是自己消费的最后一条记录值 + + * 这些API都需要先进行 poll()操作,成功的获得了分区后才能执行成功 + + # Kafka默认的消费位移提交方式为自动提交 + * 可以通过参数:enable.auto.commit 设置,默认值为 true + properties.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"true"); + + * 设置两次位移记录提交的间隔:auto.commit.interval.ms,默认值为: 50000 (ms ) + properties.setProperty(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,"50000"); + + * 默认的方式下,消费者每隔5s就会把拉取到的每个分区中的最大消费位移进行提交 + * 这个操作是在poll()方法的逻辑里面完成的,每次向服务器发起拉取请求之前都会检查是否可以进行位移提交 + * 如果可以,那么就会提交上一次轮询的位移 + + * 自动提交的方式非常简单,省心,但是可能会带来重复消费的问题(消费位移未提交时,系统宕了) + * 可以适当的减少auto.commit.interval.ms值,来避免重复消费的问题,但是不能完全避免,而且还会使消费位移的提交更加的频繁 + + * 消息的丢失可能会发生在多线程消费的情况下 + 1. A线程从topic拉取消息缓存到本地的队列: BlockingQueue,并且自动提交消费位移 + 2. 线程B从本地队列消费消息 + 3. 系统宕机,线程B还没消费完毕本地队列中的消息,但是线程A已经提交了消费位移 + + + # 关闭自动提交后,可以使用手动提交消费位移的API + void commitAsync() + void commitAsync(final Map offsets, OffsetCommitCallback callback) + void commitAsync(OffsetCommitCallback callback) + * 异步提交 + * OffsetCommitCallback 在提交成功后的回调 + void onComplete(Map offsets, Exception exception); + + void commitSync() + void commitSync(Duration timeout) + * 同步提交 + * 只要没有发生不可恢复的错误,它就会阻塞消费者线程,直到提交成功 + * 对于不可恢复的错误,我们需要捕获并且处理 + + void commitSync(final Map offsets) + void commitSync(final Map offsets, final Duration timeout) + * 提供了更加细粒度的消费位移 + * 可以精准的设置指定主题的指定分区,的消费位移值 + + * 无參提交,会根据poll()拉取的最新位移进行提交 + * 无參提交,它提交消费位移的频率和拉取批次消息,处理批次消息的频率是一样的 + + * offsets 提供了更加细粒度的消费位移控制 + * TopicPartition 表示topic和分区信息 + private int hash = 0; + private final int partition; + private final String topic; + + * OffsetAndMetadata 表示位移信息 + private final long offset; + private final String metadata; + private final Integer leaderEpoch; + + + # 异步提交也可能导致重复消费的问题 + * 如果异步提交失败,一般会采取重试的方法 + * 如果在多线程环境下,也可能会导致重复消费的问题 + 1. A线程异步提交失败,进行重试 + 2. B线程进行异步提交,成功 + 3. A线程重试成功,覆盖了B线程的消费位移提交,导致重复消费 + + * 可以设置一个递增的序列号(AtomicLong)来维护异步提交的顺序 + * 每次位移提交之后就递增序列号,并且记录 + * 如果位移提交失败,需要重试,就检查当前序列号是否与上一次提交后记录的值一样 + * 如果当前值大于上一次提交后的值了,说明有更大的位置消费提交了,当前不需要重试 + * 如果相等,说明还可以进行重试提交 + + # 位移提交失败的情况一般极少发生 + * 不进行重试也没关系,后面的提交,也会有成功的 + * 如果重试,会增加编码难度,不重试又会增加重复消费的可能 + + * 如果消费者异常退出,那么重复消费的问题就不可避免 + * 如果消费者正常退出或发生再均衡的情况,那么可以在退出或再均衡执行之前使用同步提交的方式做最后的把关 + try{ + while(true) { + // 拉取消息消费后执行异步的提交 + kafkaConsumer.commitAsync(); + } + }finally { + try { + // 稳妥的再同步提交一次 + kafkaConsumer.commitSync(); + }finally { + kafkaConsumer.close(); + } + } + + + # 每次消费一条记录就提交一次消费位移(十分消耗性能) + // 消息的主题 + String topic = consumerRecord.topic(); + // 消息的分区 + int partition = consumerRecord.partition(); + kafkaConsumer.commitSync(Collections.singletonMap(new TopicPartition(topic, partition), new OffsetAndMetadata(consumerRecord.offset() + 1))); + + # 按照分区粒度同步提交消费位移(消费完毕一个分区的消息后才提交一次) + ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofMillis(1000)); + // 遍历消息的所有主题分区 + for(TopicPartition topicPartition : consumerRecords.partitions()) { + // 获取分区下的消息集合 + List> topicPartitionConsumerRecords = consumerRecords.records(topicPartition); + // 遍历消费消息 + for(ConsumerRecord consumerRecord : topicPartitionConsumerRecords) { + // TODO 消费消息 + } + // 获取到最后一条消息的消费位移 + long offset = topicPartitionConsumerRecords.get(topicPartitionConsumerRecords.size() - 1).offset(); + // 同步的提交当前分区的消费位移 + kafkaConsumer.commitSync(Collections.singletonMap(topicPartition, new OffsetAndMetadata(offset + 1))); + } + + +------------------------ +指定位移消费 | +------------------------ + # 有了消费位移的持久化,才使消费者在关闭,崩溃或者在遇到再均衡的时候,可以让接替的消费者能够根据存储的消费位移继续进行消费 + + # 每当消费者查找不到所记录的消费位移时,就会根据消费者客户端参数 auto.offset.reset 的配置来决定从何处开始进行消费 + auto.offset.reset + * 当消费者找不到消费偏移量记录的时候,从哪里开始进行消费 + * 枚举值: + earliest 重置为最早的偏移量,从头开始消费 + latest 将偏移重置为最新偏移量,通俗的说就是不消费以前的消息了,从下条消息开始消费(默认) + none 如果没有找到偏移量记录,抛出异常 + + properties.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "none"); + + # 通过程序来控制消费偏移量 + void seek(TopicPartition partition, long offset) + void seekToBeginning(Collection partitions) + void seekToEnd(Collection partitions) + + * 在执行 seek 系列的方法之前需要先执行一次 poll()方法 + * 确保分配到分区之后才可以重置消费位置 + try(KafkaConsumer kafkaConsumer = new KafkaConsumer<>(properties)){ + // 监听主题 + kafkaConsumer.subscribe(Arrays.asList("test")); + + // 必须确定消费者已经分配了分区信息 + while(kafkaConsumer.assignment().size() == 0) { + // 执行poll() 获取到分区信息 + kafkaConsumer.poll(Duration.ofMillis(1000L)); + } + + // 遍历分区,并且设置消费偏移量 + for (TopicPartition topicPartition : kafkaConsumer.assignment()) { + kafkaConsumer.seek(topicPartition, 0); + } + + // 开始消费 + while(true) { + ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofMillis(Long.MAX_VALUE)); + for(ConsumerRecord consumerRecord : consumerRecords ) { + System.out.println(consumerRecord); + } + } + } + + * 如果设置偏移量越界了(设置了一不存在的值),那么会根据 auto.offset.reset 策略来重置消费偏移量 + + # 获取偏移量信息 + Map beginningOffsets(Collection partitions) + Map beginningOffsets(Collection partitions, Duration timeout) + * 获取指定分区的开始消费偏移量 + * 这个偏移量不一定是0,因为kafka日志清理动作可能会清理旧的日志 + + Map endOffsets(Collection partitions) + Map endOffsets(Collection partitions, Duration timeout) + * 获取指定分区的末尾消费偏移量(就是下次待写入消息的位置) + * timeout指定超时时间,如果不指定,使用:request.timeout.ms 配置 + + Map offsetsForTimes(Map timestampsToSearch) + Map offsetsForTimes(Map timestampsToSearch, Duration timeout) + * 获取指定时间的指定分区的消费偏移量 + * Map 里面的 value 字段就表示时间戳值 + * OffsetAndTimestamp + private final long timestamp; // 时间戳 + private final long offset; // 消费偏移量 + private final Optional leaderEpoch; + + long position(TopicPartition partition) + long position(TopicPartition partition, final Duration timeout) + * 获取自己在指定分区的消费偏移量 + * 其实就是自己消费的最后一条记录值 + 1 + diff --git a/Kafka/kafka-client-consumer.java b/Kafka/kafka-client-consumer.java new file mode 100644 index 00000000..d4ff79fc --- /dev/null +++ b/Kafka/kafka-client-consumer.java @@ -0,0 +1,304 @@ +------------------------ +consumer | +------------------------ + # 每个消费者都有一个消费组,一个消费组有N多个消费者 + + # 一个消费组可以消费多个分区的消息 + + # 每一个分区的消息,只能被一个消费组中的一个消费者消费 + + # 如果消费组中的消费者数量大于了分区的数量,这样的会导致多出去的消费者一直处于空闲状态 + + # 通俗的理解 + * 一个topic可以被多个消费组消费,并且这些消费组消费的都是相同的数据 + * topic对于消费组来说,是广播,一条消息会发送到多个消费者组 + + * 消费组消费的是topic + * 消费topic的时候,topic里面的所有分区,都会均匀的分布到消费组里面的消费者身上 + * 一条来自于分区的消息,只能被一个消费者消费 + * 消息对于消费组来说,是单播,一条消息只能被消费组中的一个消费者消费 + + # Maven + + org.apache.kafka + kafka-clients + 2.1.1 + + + # 消费逻辑 + 1. 配置消费者客户端的参数 + 2. 根据参数创建消费者实例对象 + 3. 订阅主题 + 4. 拉取消息进行消费 + 5. 提交消费位移 + 6. 关闭消费者实例 + + # 基本的消费 + Properties properties = new Properties(); + properties.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + properties.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + properties.setProperty("bootstrap.servers", "localhost:9092"); + properties.setProperty("group.id", "group.demo"); + + try (KafkaConsumer kafkaConsumer = new KafkaConsumer<>(properties)) { + kafkaConsumer.subscribe(Arrays.asList("test")); + while (true) { + ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofMillis(1000)); + for (ConsumerRecord consumerRecord : consumerRecords) { + String value = consumerRecord.value(); + System.out.println(value); + } + } + } + + # 消费者客户端是非线程安全的 + + # 消费者必须属于一个消费者 + * 通过 group.id 属性设置 + properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "group.demo"); + * 如果不设置,默认为空字符串 + * 一般而言这个值的设置要有一定的业务意义 + + # 主题订阅 + * 相关api + Set subscription() + void subscribe(Collection topics) + void subscribe(Collection topics, ConsumerRebalanceListener listener) + void subscribe(Pattern pattern) + void subscribe(Pattern pattern, ConsumerRebalanceListener listener) + + * 订阅主题,如果方法被重复的调用,那么以最后一次调用的为准 + * 如果使用正则表达式的方法(Pattern)订阅了主题,就算是主题不存在也可以,在主题被创建后,符合条件的主题会被自动的订阅 + * 负载均衡监听器:ConsumerRebalanceListener + void onPartitionsRevoked(Collection partitions); + void onPartitionsAssigned(Collection partitions); + + * 使用这种方式进行订阅消息具有自动负载均衡的功能 + * 在多个消费者的情况下,可以根据分区分配策略来自动分配各个消费者与分区的关系 + * 在消费组内消费者的增加/减少,分区分配关系会自动的跳转,以及实现故障的自动转移 + + * 监听了主题后,需要调用 poll() 后才能获取到分区的分配信息 + * 可以通过:Set assignment() 获取到订阅的主题,已经分配的分区信息 + + # 分区订阅 + * 相关api + void assign(Collection partitions) + + * TopicPartition 对象用于描述分区和主题 + private int hash = 0; //hash值 + private final int partition; // 分区编号 + private final String topic; // 主题信息 + TopicPartition(String topic, int partition) + + * 这种方式订阅,不具备自动的负载均衡功能 + + # 主题订阅模式的互斥性 + * 集合的订阅方式:AUTO_TOPICS + subscribe(Collection topics) + * 正则表达式的订阅方式:AUTO_PATTERN + subscribe(Pattern pattern) + * 指定分区的订阅方法:AUTO_ASSIGNED + assign(Collection partitions) + + * 这三种订阅方法不能同时存在,只能选择一种,不然会抛出异常 + + # 消息的解码器 + * 通过 key.deserializer/value.deserializer 设置key/value的解码器 + properties.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,""); + properties.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,""); + + * 解码器接口:Deserializer + void configure(Map configs, boolean isKey); + * 配置 + T deserialize(String topic, byte[] data); + * 解码消息 + default T deserialize(String topic, Headers headers, byte[] data) { + return deserialize(topic, data); + } + * 可以获取到消息头的解码方法 + + @Override + void close(); + + * 如果有多个,使用逗号分隔 + + # 消息消费 + * 消息消费模式为主动从topic拉取消息,这是一个不断轮询的过程 + * 相关api + ConsumerRecords poll(final Duration timeout) + * 该值的设置取决于程序对响应速度的要求 + * 如果设置为0,poll会立即返回而不管是否拉取到了数据 + * 如果线程的工作仅仅是为了拉取数据,那么该值可以设置为 Long.MAX_VALUE + + # 控制消费 + * 有时候需要主动的暂停消费某些分区,在一定的时机又恢复对这些分区的消费 + * 可以达到控制消费速度的行为 + Set paused() + * 返回被暂停的分区集合 + void pause(Collection partitions) + * 暂停 + void resume(Collection partitions) + * 恢复 + # 关闭消费者 + * 一般使用一个 while 循环来包裹住 poll()方法及相应的消费逻 + * 退出循环,有一种方式是调用 KafkaConsumer 的 wakeup()方法 + void wakeup() + + * 该方法是唯一可以从其他线程里面调用的安全的方法 + * 该方法被调用后,可以退出 poll() 逻辑,并且抛出:WakeupException + * 并不需要处理该异常,它只是退出 poll() 的一种机制 + + * 跳出循环以后一定要显式地执行关闭动作以释放运行过程中占用的各种系统资源 + * 客户端提供了一个 close() api来对占用的系统资源进行释放 + * 释放的过程会比价缓慢,可能涉及到跟broker通信,提交消费位移等信息 + void close() + void close(Duration timeout) + + * timeout 设置超时时间,默认是 30s + + * 一个完整的优雅关闭 + boolean run = true; // 确定服务是否还要继续运行 + KafkaConsumer kafkaConsumer = new KafkaConsumer<>(properties); + kafkaConsumer.subscribe(Arrays.asList("test")); + try{ + while(run) { + ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofMillis(1000L)); + for(ConsumerRecord consumerRecord : consumerRecords) { + // TODO 消费消息 + System.out.println(consumerRecord); + } + } + }catch (WakeupException e) { + e.printStackTrace(); // 被主动唤醒,忽略该异常 + }catch (Exception e) { // 处理该异常 + e.printStackTrace(); + }finally { + if(kafkaConsumer != null) { + kafkaConsumer.close(); // 关闭资源 + } + } + + # 负载均衡 + * 当消费组中有消费者加入/宕机,或者是topic有partition新增/删除 + * 都可能触发消费组中,消费者的负载均衡 + + * 在执行负载均衡计算的时候,消费组会变得不可用 + * 可能会发生重复消费的情况 + 1. 消费者A消费了partition-1,还没来得及提交消费偏移度 + 2. 发生了负载均衡计算,partition-1 被分配给了消费者B + 3. 因为消费者A没有提交消费偏移,所以消费者B会重复消费A已经消费过的记录 + + * 可以在订阅topic的时候,监听负载均衡的发生 + void subscribe(Collection topics, ConsumerRebalanceListener listener) + void subscribe(Pattern pattern, ConsumerRebalanceListener listener) + + * ConsumerRebalanceListener 接口具有两个抽象方法 + void onPartitionsRevoked(Collection partitions); + * 该方法会在负载均衡被计算之前,消费停止poll()消息之后执行 + * 参数表示原始的分区 + * 可以通过它来处理消费位移的提交,尽可能的避免重复消费的问题 + + void onPartitionsAssigned(Collection partitions); + * 该方法会在负载均衡计算之后(重新分配分区),消费者开始读取消息之前执行 + * 参数表示重新分配的分区 + + * demo + try (KafkaConsumer kafkaConsumer = new KafkaConsumer<>(properties)) { + // 记录实时的消费偏移度 + Map offsets = new HashMap<>(); + // 监听主题 + kafkaConsumer.subscribe(Arrays.asList("demo3"), new ConsumerRebalanceListener() { + @Override + public void onPartitionsRevoked(Collection partitions) { + // 在重新计计算分区之前,同步提交消费偏移度,尽可能的保证不会出现重复消费的情况 + kafkaConsumer.commitSync(offsets); + } + @Override + public void onPartitionsAssigned(Collection partitions) { + // 经过计计算,分配到的分区 + } + }); + // 开始消费 + while (true) { + ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofMillis(Long.MAX_VALUE)); + + for (ConsumerRecord consumerRecord : consumerRecords) { + + // 消费每一条消息后,实时的记录下该分区的消费偏移度 + String topic = consumerRecord.topic(); // 当前消息的topic + int partition = consumerRecord.partition(); // 当前消息的partition + long offset = consumerRecord.offset(); // 当前消息的偏移度 + offsets.put(new TopicPartition(topic, partition), new OffsetAndMetadata(offset + 1)); + } + + // 每次消费完毕数据,异步的提交消费偏移度 + kafkaConsumer.commitAsync(offsets, null); + } + } + + # 消费者拦截器 + * 主要作用是在读取到消息,以及提交消费位移时进行一些定制化的操作 + * 实现接口:ConsumerInterceptor + ConsumerRecords onConsume(ConsumerRecords records); + * 在消费消息(poll() 方法返回之前)前调用 + * 如果它发生异常,会被捕获记录到日志,但是不会向上传递,会跳过当前这个发生了异常的拦截器,把上一次正常的消息提交给下个拦截去处理(如果存在) + * 参数就是新的消息 + + void onCommit(Map offsets); + * 在提交消费位移之后调用 + * 参数就是要提交的消费位移信息 + + * 可以使用该方法来记录,跟踪所提交的位移信息 + * 比如,消费者调用了:commitAsync() 方法,我们就没法直接获取到提交的消费偏移信息 + * 但是通过这种拦截器的方式,就可以获取到 + + void close(); + * 一般没啥用,空实现 + + * 配置拦截器使用属性:interceptor.classes + properties.setProperty(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, LogConsumerInterceptor.class.getName()); + + # 消费者对于并发操作的限制 + * 消费者实例对象不是线程安全的,这个跟消息生产者不一样 + * 如果有其他线程操作消费者对象,会抛出异常:ConcurrentModificationException + * 消费者实例内部提供了一个方法(private) + + private static final long NO_CURRENT_THREAD = -1L; + private final AtomicLong currentThread = new AtomicLong(NO_CURRENT_THREAD); + private final AtomicInteger refcount = new AtomicInteger(0); + + private void acquire() { + long threadId = Thread.currentThread().getId(); + if (threadId != currentThread.get() && !currentThread.compareAndSet(NO_CURRENT_THREAD, threadId)) + throw new ConcurrentModificationException("KafkaConsumer is not safe for multi-threaded access"); + refcount.incrementAndGet(); + } + + private void release() { + if (refcount.decrementAndGet() == 0) + currentThread.set(NO_CURRENT_THREAD); + } + + * 在每次执行动作之前,都会调用这个方法(acquire) + * 它是一个轻量级的锁,用线程操作计数的方法来检测是否发生了并发操作,以此保证只有一个线程在使用客户端 + + * release() 方法的存在是为了释放锁 + + # 使用多线程消费 + * 使用多线程的目的,是为了提高消费的速度 + * 多线程的实现有很多种 + + * 线程封闭,也就是一个线程实例化一个消费者客户端 + * 所有的消费者线程,都属于同一个消费组,一个分区创建一个线程(分区的数量可以提前知道) + * 优点就是,每个线程可以按照顺序去消费各个分区里面的数据 + * 缺点就是,同一台机器要开启多个tcp连接,系统资源消耗大 + + * 线程池的模式 + * 一般而言,消费的瓶颈在于消费,而不是拉取(poll()) + * 所以可以使用线程池来消费 poll() 拉取下来的消息 + * 缺点就是对于顺序消息的处理会比较的麻烦 + + * 这个名堂还是有点儿多,单独列了个笔记 + + + \ No newline at end of file diff --git "a/Kafka/kafka-client-producer-\344\272\213\345\212\241\345\222\214\345\271\225\347\255\211.java" "b/Kafka/kafka-client-producer-\344\272\213\345\212\241\345\222\214\345\271\225\347\255\211.java" new file mode 100644 index 00000000..a5273af3 --- /dev/null +++ "b/Kafka/kafka-client-producer-\344\272\213\345\212\241\345\222\214\345\271\225\347\255\211.java" @@ -0,0 +1,156 @@ +---------------------------- +事务和幕等 | +---------------------------- + # 消息中间件的消息传输保障有 3 个层级 + * at most once,至多一次,消息可能会丢失,但绝对不会重复传输 + + * at least once,最少一次,消息绝不会丢失,但可能会重复传输(Kafka默认) + + * exactly once,恰好一次,每条消息肯定会被传输一次且仅传输一次 + +---------------------------- +幕等 | +---------------------------- + # 生产者在进行重试的时候有可能会重复写入消息 + * 消息发送后遇到了网络问题而造成通信中断,那么生产者就无法判断该消息是否己经提交 + * 生产者进行多次重试来确保消息已经写入Kafka,这个重试的过程中有可能会造成消息的重复写入 + + # 使用 Kafka 的幕等性功能之后就可以避免这种情况 + # 开启幕等使用配置项:enable.idempotence + properties.setProperty(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true"); + + # 确保幕等功能正常,还需要以下配置的正确性 + retries + acks= + max.in.flight.requests.per.connection + + * 实际上在使用 幕等性功能的时候,完全可以不用配置(也不建议配置)这几个参数 + * 如果强行配置了,可能会因为配置不当导致幕等消息功能异常 + + # 原理 + * 每个新的生产者实例在初始化的时候都会被分配一个 PID , 这个 PID 对用户而言是完全透明的 + * 对于每个 PID,消息发送到的每一个分区都有对应的序列号 + * 这些序列号从 0 开始单调递增,生产者每发送一条消息就会将 对应的序列号的值加 l + + * broker对所有的 都维护了一个序列号 + * 接收到消息时候,只有它的序列号值 == ( + 1),broker才会接受 + + * 如果消息序列号 < ( + 1),说明消息被重复的写入,broker会丢弃这条消息 + * 如果消费序列号 > ( + 1),说明中途有消息丢失未写入到broker,出现了乱序,此时就会抛出非常严重的异常:OutOfOrderSequenceException + + # Kafka的幕等只能保证单个生产者会话(session )中单分区的幕等等 + +---------------------------- +幕等 | +---------------------------- + # 幕等性并不能跨多个分区运作,而事务可以弥补这个缺陷,事务可以保证对多个分区写入操作的原子性 + # 开启事务:transactional.id + properties.setProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "1"); + + * 事务要求必须开启幕等:enable.idempotence=true + * transactionalld值由程序提供 + * transactionalld与 PID一一对应,不同的是 transactionalld 由用户显式设置 ,而PID 是由 Kafka 内 部分配的 + + # 如果两个消息生产者都有相同的 transactionalld ,先启动的那个会抛出异常:ProducerFencedExcept + * 每个生产者通过 transactionalld 获取 PID 的 同时,还会获取一个单调递增的producer epoch + * 先启动的那个拥有相同 transactionalld 的生产者实例将不再工作 + + # 事务相关的方法 + + void initTransactions() + * 初始化事务,前提是必须配置了transactionalld,否则抛出异常 + void beginTransaction() + * 开启事务 + void sendOffsetsToTransaction(Map offsets,String consumerGroupId) + * 为消费者提供在事务内的位移提交的操作 + void abortTransaction() + * 回滚事务 + void commitTransaction() + * 提交事务 + + # 一个事务的操作 + Properties properties = new Properties(); + properties.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + properties.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + properties.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + // 开启幕等 + properties.setProperty(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true"); + // 设置事务id + properties.setProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "1"); + + try(KafkaProducer kafkaProducer = new KafkaProducer<>(properties)){ + // 初始化事务 + kafkaProducer.initTransactions(); + // 开始事务 + kafkaProducer.beginTransaction(); + try { + ProducerRecord producerRecord1 = new ProducerRecord<>("topic_1", "Message-1"); + ProducerRecord producerRecord2 = new ProducerRecord<>("topic_1", "Message-2"); + kafkaProducer.send(producerRecord1); + kafkaProducer.send(producerRecord2); + + //TODO 其他业务操作 + + // 提交事务 + kafkaProducer.commitTransaction(); + }catch (Exception e) { + // 回滚事务 + kafkaProducer.abortTransaction(); + } + } + + # sendOffsetsToTransaction ??? + public class Transactional { + public static Properties getConsumerConfig() { + Properties properties = new Properties(); + properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + properties.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); + properties.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); + properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "group_1"); + properties.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); + return properties; + } + + public static Properties getProducerConfig() { + Properties properties = new Properties(); + properties.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); + properties.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + properties.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + properties.setProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "1"); + return properties; + } + + public static void main(String[] args) { + KafkaConsumer kafkaConsumer = new KafkaConsumer<>(getConsumerConfig()); + kafkaConsumer.subscribe(Collections.singletonList("topic_1")); + + KafkaProducer kafkaProducer = new KafkaProducer<>(getProducerConfig()); + kafkaProducer.initTransactions(); + while (true) { + ConsumerRecords consumerRecords = kafkaConsumer.poll(Duration.ofMillis(1000)); + if (!consumerRecords.isEmpty()) { + Map offsets = new HashMap<>(); + kafkaProducer.beginTransaction(); + try { + for(TopicPartition topicPartition : consumerRecords.partitions()) { + List> consumerRecordList = consumerRecords.records(topicPartition); + for(ConsumerRecord consumerRecord : consumerRecordList) { + // 消费消息 + System.out.println(consumerRecord.value()); + + // 生产消息 + ProducerRecord producerRecord = new ProducerRecord("topic_1",consumerRecord.value()); + kafkaProducer.send(producerRecord); + } + long lastConsumedOffset = consumerRecordList.get(consumerRecordList.size() - 1).offset(); + offsets.put(topicPartition,new OffsetAndMetadata(lastConsumedOffset + 1)); + } + kafkaProducer.sendOffsetsToTransaction(offsets, "group_1"); + kafkaProducer.commitTransaction(); + }catch (Exception e) { + kafkaProducer.abortTransaction(); + } + } + } + } + } diff --git a/Kafka/kafka-client-producer.java b/Kafka/kafka-client-producer.java new file mode 100644 index 00000000..07587ce8 --- /dev/null +++ b/Kafka/kafka-client-producer.java @@ -0,0 +1,194 @@ +------------------------ +producer | +------------------------ + # Maven + + org.apache.kafka + kafka-clients + 2.1.1 + + + # 消息生产者的逻辑 + 1. 配置客户端参数,以及根据参数创建相应的消息生产者实例 + 2. 构建消息 + 4. 发送消息 + 5. 关闭客户端(生产者实例) + + # 基本的发送演示 + Properties properties = new Properties(); + properties.setProperty("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + properties.setProperty("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); + properties.setProperty("bootstrap.servers", "localhost:9092"); + properties.setProperty("client.id", "producer.client.id.demo"); + + try(KafkaProducer kafkaProducer = new KafkaProducer<>(properties)){ + ProducerRecord producerRecord = new ProducerRecord("test", "你好啊"); + kafkaProducer.send(producerRecord); + } + + # 生产者客户端是线程安全的 + + # 异常的可重试机制 + * KafkaProducer 中 一般会发生两种类型的异常 : 可重试的异常和不可重试的异常 + * 常见的可重试异常 + NetworkException(网络异常,这个有可能是由于网络瞬时故障而导致的异常,可以通过重试解决) + LeaderNotAvailableException(分区的 leader 副本不可用,这个异常通常发生在 leader 副本下线而新的 leader 副本选举完成之前,重试之后可以重新恢复) + UnknownTopicOrPartitionException + NotEnoughReplicasException + NotCoordinatorException + + * 常见的不可重试异常 + RecordTooLargeException(发送的消息太大,不会执行重试,直接抛出) + + * 如果配置了 retries 参数,那么只要在规定的重试次数内自行恢复了,就不会抛出异常(retries 参数的默认值为 0) + props.put(ProducerConfig.RETRIES_CONFIG, "10"); + + * 对于可重试异常发生后,可以设置两次重复调用的时间间隔,retry.backoff.ms(默认100毫秒) + props.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, "100"); + + * 在配置 retries 和 retry.backoff.ms 之前,最好先估算一下可能的异常恢复时间 + * 这样可以设定总的重试时间大于这个异常恢复时间,以此来避免生产者过早地放弃重试 + + # 消息的序列化(编码器) + * 需要用到序列化,把消息(key和value)序列化为byte[] + * 最顶层的序列化接口:Serializer + byte[] serialize(String topic, T data); + * 设置消息编码器(必须设置的属性) + properties.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); + properties.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); + + + # 分区器 + * 消息send()到broker时可能会经过拦截器,序列化器(编码器),分区器之后才会被发送到broker + * 如果消息对象(ProducerRecord)指定了partition属性值,那么就不需要分区器了,因为已经指定好了 + * 如果未指定partition属性值,那么就要依赖于分区器,根据key字段来计算出partition值 + * 分区器接口:Partitioner + void configure(Map configs) + int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster); + void close(); + + * Kafka提供默认的分区器实现 + DefaultPartitioner + * 如果key不为null,那么会对key进行hash(采用MurmurHash2算法,具备高运算性能和低碰撞率)计算 + * 根据最终的hash值来计算分区号 + * 也就是说相同的key会被发送到同一个分区(如果扩展了partition的数量那么就不能保证了) + * 计算得到的分区号会是所有分区中的任意一个,可能会选择到不可用的分区 + + * 如果Key为null,那么消息会被以轮询的方式发送到每个可用的分区 + * 计算得到的分区号仅为可用分区中的任意一个,不会选择到不可用的分区 + + * 使用自定义的分区器 + properties.setProperty(ProducerConfig.PARTITIONER_CLASS_CONFIG, "org.apache.kafka.clients.producer.internals.DefaultPartitioner"); + + + # 拦截器 + * 拦截器可以在消息被发送之前做一些其他的工作,例如:过滤,数据的修改 + * 也可以用来在发送回调逻辑前做一些定制化的需求,例如:统计 + * 拦截器接口:ProducerInterceptor + void configure(Map configs); + ProducerRecord onSend(ProducerRecord record); + void onAcknowledgement(RecordMetadata metadata, Exception exception); + void close(); + + * Kafka会在消息编码,分区计算之前调用拦截器的onSend方法 + + * 一般来说最好不要修改消息的:topic,partition,key等信息 + * 除非确保有准确的判断,否则可能会出现于预想结果出现偏差的可能 + * 不仅可能会影响分区的计算,还可能印象broker端日志的压缩功能 + + * Kafka会在消息被应答(send api返回)之前或者消息发送失败时调用拦截器的onAcknowledgement方法 + * 一样的,需要判断 exception 是否为null,从而确定消息是否发送成功 + * 它会优先于 Callback 执行 + * 这个方法运行在Producer的I/O线程中,所以越简单越好,否则会影响消息的发送速度 + + * close()方法主要用在关闭拦截器时执行一些资源的清理工作 + + * 拦截器中几个方法,在执行中抛出的异常,都会被捕获并记录到日志 + * 但是不会向上传递(不会抛出) + + * 拦截器可以配置多,形成一个责任链(责任链不多解释) + * 如果拦截链中某一个拦截器抛出了异常,它会被跳过 + * 下一个拦截器会接着上一个执行成功的拦截器开始执行(略过执行异常的拦截器) + * 多个拦截器使用逗号分隔 + + * 配置拦截器 + properties.setProperty(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, "io.javaweb.kafka.interceptor.SimpleInterceptor"); + + * 配置拦截器链 + properties.setProperty(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, "SimpleInterceptor1,SimpleInterceptor1"); + + # 执行过程 + * 生产者客户端由两个线程组成:主线程,发送线程 + * 主线程创建消息 -> 拦截器 —> 编码器 -> 分区计算器 -> 消息累加器(RecordAccumulator) + * 发送线程负责从消息累加器中获取消息,发送到broker + + # 消息的累加器(缓存) + * 主线程调用了 send() api并不会立即执行消息的发送,会先把消息添加到累加器 (RecordAccumulator) + * RecordAccumulator 的作用就是缓存消息 + * 以便sender线程批量的进行发送,提高效率(减少网络传输) + * 默认缓存的大小为:33554432kb = 32MB + * 缓存的大小可以设置 + properties.setProperty(ProducerConfig.BUFFER_MEMORY_CONFIG, String.valueOf(1024 * 1024 * 50)); + + * 如果生产者的消息发送速度超过了发送消息到服务器的速度 + * 从而占满了缓冲区,在这种情况下,send() api 要么阻塞直到缓冲区有新的空间,要么抛出异常 + * 可以通过配置来设置send()操作,最长阻塞时间(ms),超过时间就会抛出异常 + properties.setProperty(ProducerConfig.MAX_BLOCK_MS_CONFIG, String.valueOf(60000)); + + * 主线程发送的消息都会被追击到 RecordAccumulator 的某个双端队列(Deque)中 + * RecordAccumulator 为每个分区都维护了一个双端队列,队列的内容就是:ProducerBatch(Deque) + ConcurrentMap> batches; + * 一个ProducerBatch起码包含了一个消息记录(ProducerRecord) + * 消息写入缓存的时候,追加到双端队列的尾部,sender线程读取消息的时候,从双端队列的头部读取 + + * 在 RecordAccumulator 的内部还有一个 BufferPool + * 它主要用来实现 ByteBuffer 的复用,以实现缓存的高效利用 + * 不过 BufferPool 只针对特定大小的 ByteBuffer 进行管理,而其他大小的 ByteBuffer 不会缓存进 BufferPool 中 + * 这个特定的大小由 batch.size 参数来指定,默认值为 16384B,即 16KB + properties.setProperty(ProducerConfig.BATCH_SIZE_CONFIG, String.valueOf(16384)); + + * 当一条消息send()到RecordAccumulator的时候,会先找到其对应的分区队列(Deque),如果不存在,则先创建 + * 再从队列尾部获取一个ProducerBatch,如果不存在则新建 + * 判断ProducerBatch是否有足够的空间写入当前的消息,如果可以则直接写入 + * 如果不可以,则创建一个新的ProducerBatch,在新建 ProducerBatch 时评估这条消息的大小是否超过 batch.size 参数的大小 + * 如果不超过,那么就以 batch.size 参数的大小来创建 ProducerBatch,这样在使用完这段内存区域之后,可以通过 BufferPool 的管理来进行复用 + * 如果超过,那么就以评估的大小来创建 ProducerBatch,这段内存区域不会被复用 + + # 请求响应的缓存 + * 消息从RecordAccumulator发送到broker的请求,会被缓存到 InFlightRequests 中,直到响应 + * 它的缓存格式为 -> broker节点id:Deque + Map> requests = new HashMap<>(); + + * 可以通过配置限制每个连接(也就是客户端与 Node 之间的连接)最多缓存的请求数 + * max.in.flight.requests.per.connection: 默认值为5,也就是说,最多缓存5个未响应的请求,一旦超过该值,就不能往这个连接发送更多的请求了 + * 除非缓存中的请求,收到了响应 + properties.setProperty(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, String.valueOf(5)); + + * 可以通过 Deque.size() 和 max.in.flight.requests.per.connection 进行比较 + * 从而判读是否堆积了N多已经发送但是未响应的消息,如果真的存在很多,那么该broker能网络负载比较大,或者连接有问题 + + + # 元数据的更新 + * 元数据,其实就是除了我们业务数据以外的一些其他数据 + * 对于我们而言是透明的,但是又是不可缺少的,例如:kafka集群中的主题数量,每个主题的分区数量,分区的副本分配等等信息 + * 当客户端中没有需要使用的元数据信息时,比如没有指定的主题信息 + * 或者超过 metadata.max.age.ms 时间没有更新元数据都会引起元数据的更新操作,metadata.max.age.ms 的默认值为 300000,即 5 分钟 + properties.setProperty(ProducerConfig.METADATA_MAX_AGE_CONFIG, String.valueOf(300000)); + + * 更新的操作对于客户端来说是透明的 + + * 当需要更新元数据的时候,会先挑选出:leastLoadedNode + * leastLoadedNode其实就是所有Node中负载最小的Node(判断InFlightRequest中的未响应的请求数量值,数量越少,节点的负载越小) + * 然后向这个 Node 发送 MetadataRequest 请求来获取具体的元数据信息,这个更新操作是由 Sender 线程发起 的 + * 在创建完 MetadataRequest 之后 同样会存入 InFlightRequest 之后的步骤就和发送消息时的类似 + * 元数据虽然由 Sender 线程负责更新,但是主线程也需要读取这些信息,这里的数据同步通过 synchronized 和 final 关键字来保障 + + # 消息的有序性问题 + * Kafka只能保证一个分区中的消息是有序性的 + + * 如果将 acks 参数配置为非零值,并且 max.in.flight.requests.per.connection 参数配置为大于 l 的值,那么就会出现错序的现象 + * 如果第一批次消息写入失败,而第二批次消息写入成功,那么生产者会重试发送第一批次的消息 + * 此时如果第一批次的消息写入成功,那么这两个批次的消息就出现了错序 + + * 一般而言,在需要保证消息顺序的场合建议把参数 max.in.flight.requests.per.connection 配置为 1,而不是把 acks 配置为 0 + * 这样会影响整体的吞吐 \ No newline at end of file diff --git a/Kafka/kafka-config-server.properties b/Kafka/kafka-config-server.properties new file mode 100644 index 00000000..56ffcf68 --- /dev/null +++ b/Kafka/kafka-config-server.properties @@ -0,0 +1,139 @@ +# 配置类:kafka.server.KafkaConfig +#---------------------- +# 服务器基础配置 +#---------------------- +broker.id=0 + * 当前broker的id,在集群中唯一,非负整数 + * 如果没有设置(默认值-1),那么 Kafka会自动生成一个 +#---------------------- +# 服务器Socket配置 +#---------------------- +listeners=PLAINTEXT://:9092 + * broker监听客户端连接的地址列表 + * 配置格式:protocol(l)://hostname(l):port(l),protocol(2)://hostname(2):port(2) + * 支持的协议: + PLAINTEXT(明文),SSL,SASL_SSL + * hostname,设置主机名,如果不设置会绑定到默认的网卡,多张网卡情况下,可能会绑定到 127.0.0.1,这样无法对外部提供服务 + * hostname可以设置为 0.0.0.0,表示绑定所有的网卡 + +advertised.listeners=PLAINTEXT://your.host.name:9092 + * 跟上面的配置一样,默认值为 null + * 主要用于IaaS (Infrastructure as a Service)环境 + * 比如公有云上的机器通常配备有多块网卡,即包含私网网卡和公网网卡 + * 对于这种情况而言,可以设置 advertised.listeners 参数绑定公网 IP 供外部客户端使用 + * 而配置 listeners 参数来绑定私网 IP 地址供 broker 间通信使用 + +listener.security.protocol.map=PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL +num.network.threads=3 +num.io.threads=8 +socket.send.buffer.bytes=102400 +socket.receive.buffer.bytes=102400 +socket.request.max.bytes=104857600 + +#---------------------- +# 日志配置 +#---------------------- +log.dirs=/tmp/kafka-logs + * 数据持久化的目录,如果有多个,可以使用逗号分隔 + +num.partitions=1 + * 默认topic的partition数量 + +num.recovery.threads.per.data.dir=1 + +#---------------------- +# 内部的Topic配置 +#---------------------- +offsets.topic.replication.factor=1 +transaction.state.log.replication.factor=1 +transaction.state.log.min.isr=1 + +#---------------------- +# 日志刷出策略 +#---------------------- +log.flush.interval.messages=10000 +log.flush.interval.ms=1000 + +#---------------------- +# 日志保留策略 +#---------------------- +log.retention.hours=168 +log.retention.bytes=1073741824 +log.segment.bytes=1073741824 +log.retention.check.interval.ms=300000 + +#---------------------- +# zookeeper配置 +#---------------------- +zookeeper.connect=localhost:2181 + * 设置连接的地址,如果是多个节点(集群),可以使用逗号分隔 + * 还可以指定kafka使用的目录(路径),如果不指定,则默认使用根路径 + localhost1:2181,localhost2:2181,localhost3:2181/kafka + * 可以通过设置路径的方法,可以让多个kafka集群使用同一个zk集群,以达到节省硬件资源的目的 + +zookeeper.connection.timeout.ms=6000 + +#---------------------- +# 组协调器设置 +#---------------------- +group.initial.rebalance.delay.ms=0 + +#---------------------- +# 其他配置 +#---------------------- +message.max.bytes +max.request.size +max.message.bytes + +auto.create.topics.enable=true + * 如果消息生产者往不存在的topic发送消息,或者消费者消费了一个不存在的topic时 + * 是否自动创建该不存在的主题 + * 默认 true + +default.replication.factor=1 + * 默认patition的副本数量 + +broker.rack + * 配置机房所在区 + broker.rack=RACK1 + * 当进行replica排序时候,不会仅仅按照broker顺序进行排序,而是会保证机房错开 + +delete.topic.enable=true + * 是否允许删除主题 + +create.topic.policy.class.name=null + * 严重主题创建的合法性 + * 参数为:org.apache.kafka.server.policy.CreateTopicPolicy 接口的实现类全路径 + void validate(RequestMetadata requestMetadata) throws PolicyViolationException; + * 在实现类中,可以获取到创建主题的元数据,如果校验不符合规范,那么可以跑出异常:PolicyViolationException 来组织主题的创建 + +auto.leader.rebalance.enable=true + * 是否开启分区自动平衡的功能,默认:true + +leader.imbalance.per.broker.percentage + * broker中的不平衡率,默认 10% + +leader.imbalance.check.interval.seconds + * 优先副本的选举动作周期,默认 300s + +follower.replication.throttled.rate + * 设置Follower副本的复制速速 + * 单位是 byte/s + +leader.replication.throttled.rate + * 设置Leader副本的传输速度 + * 单位是 byte/s + +log.index.interval.bytes=4096 + * 默认 4kb + +log.segment.bytes=1073741824 + * 一个segment的文件存储消息的最大体积 + * 默认1GB + +log.index.size.max.bytes=10485760 + * 默认 10MB + +log.cleanup.policy + + diff --git a/Kafka/kafka-core-partition.java b/Kafka/kafka-core-partition.java new file mode 100644 index 00000000..33e01561 --- /dev/null +++ b/Kafka/kafka-core-partition.java @@ -0,0 +1,56 @@ +---------------------- +Partition | +---------------------- + # controller负责执行partition的分配,还有leader的选举 + + # 存在多副本的情况下,会尽量把多个副本,分配到不同的broker上 + + # kafka会为partition选出一个leader,之后所有该partition的请求,实际操作的都是leader,然后再同步到其他的follower + + # 如果broker宕机,所有leader在该broker上的partition都会重新选举,选出一个leader + * 不像分布式文件存储系统那样会自动进行复制保持副本数 + + # Controller怎么分配partition + * 将所有Broker(假设共n个Broker)和待分配的Partition排序 + * 将第i个Partition分配到第(i mod n)个Broker上(这个就是leader) + * 将第i个Partition的第j个Replica分配到第((i + j) mode n)个Broker上 + + # Controller怎么选leader + * controller会在Zookeeper的'/brokers/ids'节点上注册Watch,一旦有broker宕机,它就能知道 + * roker宕机后,controller就会给受到影响的partition选出新leader + * controller从zk的 '/brokers/topics/[topic]/partitions/[partition]/state'中,读取对应partition的ISR(in-sync replica已同步的副本)列表 + * 从ISR列表中选一个出来做leader + * 选出leader后,更新zk,然后发送'LeaderAndISRRequest'给受影响的broker,让它们改变知道这事 + * 不是使用zk通知,而是直接给broker发送rpc请求(也许是因为性能问题 ) + * 如果 ISR列表是空,那么会根据配置,随便选一个replica做leader,或者干脆这个partition就是宕机(不救了) + * 如果ISR列表的有机器,但是也宕机了,那么还可以等ISR的机器活过来 + * 原来宕机的leader恢复后,controller会重新把它设置为leader????? + + # 多个副本的同步 + * 服务端处理是follower从leader批量拉取数据来同步 + * 但是具体的可靠性,是由生产者来决定的 + * 生产者生产消息的时候,通过'request.required.acks'参数来设置数据的可靠性 + 0 + * 发过去就完事了,不关心broker是否处理成功,可能丢数据 + 1 + * 写Leader成功后就返回 + * 其他的replica都是通过fetcher去同步的,所以kafka是异步写,主备切换可能丢数据 + -1 + * 要等到isr里所有机器同步成功,才能返回成功 + * 延时取决于最慢的机器,强一致,不会丢数据 + * 如果ISR少于'min.insync.replicas'指定的数目,那么就会返回不可用 + + * ISR列表中的机器是会变化的,根据配置'replica.lag.time.max.ms',多久没同步就会从ISR列表中剔除 + + * 从ISA中选出leader后,follower会从把自己日志中上一个高水位后面的记录去掉,然后去和leader拿新的数据 + * 新的leader选出来后,follower上面的数据,可能比新leader多,所以要截取 + * 高水位的意思,对于partition和leader,'就是所有ISR中都有的最新一条记录',消费者最多只能读到高水位 + + * 从leader的角度来说高水位的更新会延迟一轮 + * 例如写入了一条新消息,ISR中的broker都fetch到了,但是ISR中的broker只有在下一轮的fetch中才能告诉leader + * 正是由于这个高水位延迟一轮,在一些情况下,kafka会出现丢数据和主备数据不一致的情况 + * '0.11开始,使用leader epoch来代替高水位' + + + + diff --git a/Kafka/kafka-core-topic.java b/Kafka/kafka-core-topic.java new file mode 100644 index 00000000..04ff6cc0 --- /dev/null +++ b/Kafka/kafka-core-topic.java @@ -0,0 +1,198 @@ +---------------------------- +Topic | +---------------------------- + # 主题的创建 + * 主题相关的配置,其实在broker都有默认的配置项和值 + * 如果主题没有进行特殊的设置,那么就会使用broker默认的 + + # 主题的删除 + * 主题被删除后,不会立即的从磁盘删除,只是被标记为删除 + * 如果broker配置的参数: delete.topic.enable=false 那么执行删除不会有任何的效果 + * 如果要删除的主题是 Kafka 的内部主题,那么删除时就会报错 + + * 使用 kafka-topics.sh 脚本删除主题的行为本质上只是在 ZooKeeper 中的 /admin/delete_topics 路径下创建一个与待删除主题同名的节点 + * 以此标记该主题为待删除的状态 + * 与创建主题相同的是,真正删除主题的动作也是由 Kafka 的控制器负责完成的 + * 说白了,可以通过操作zookeeper的节点来完成主题的删除操作 + + * 还可以手动的方式来删除主题,主题的元数据存储在zookeeper的路径: + /brokers/topics + /config/topics + * 主题中的消息数据存储在磁盘 log.dir 或 log.dirs 配置的路径下,只需要手动删除这些地方的内容即可 + 1. 先删除zk中的元数据 + rmr /config/topics/[主题名称] + delete /brokers/topics/[主题名称] + delete /config/topics/[主题名称] + 2. 再删除日志文件 + rm - rf /tmp/kafka-logs/[主题名称]* + + * 删除主题是一个不可逆的操作 + +---------------------------- +Topic 优先副本的选举 | +---------------------------- + # partition集群中只有leader节点可以对外提供io服务 + * follower节点只负责同步leader节点的数据 + * 如果leader节点宕机,那么该parition就会变得不可用 + + * 此时就会从follower节点中选择一个节点,成为新的leader节点 + * 当原来的leader节点恢复以后,加入集群中时,它只是成为了一个新的follower节点 + + # 优先副本(Preferred Replica) + * 优先副本是指在AR集合列表中的第一个副本 + * 理想的情况下,优先副本就是该parition的leader副本 + + * 优先副本的选举就是指通过一定的方式,促使优先副本选举为Leader副本 + * 以此来促进集群的负载均衡,这个行为也叫做分区平衡 + + # 分区自动平衡 + * broker端参数:auto.leader.rebalance.enable(默认值为 true) + * 在开启的情况下,Kafka的控制器会启动一个定时任务 + * 该任务会轮询所有的broker节点,计算每个broker节点的分区不平衡率 + + * broker中的不平衡率 = 非优先副本的leader个数 / 分区(parition)总数 + * broker中的不平衡率是否超过了指定参数的比值 + leader.imbalance.per.broker.percentage(默认值: 10%) + * 如果超过了,就会自动执行优先副本的选举动作,以求分区平衡 + * 执行周期由指定的参数控制 + leader.imbalance.check.interval.seconds(默认 300s) + + # 生产环境不建议开启:auto.leader.rebalance.enable + * 这可能引起负面的性能问题,也可能引发客户端一定时间的阻塞 + * 因为它的执行时间无法自主掌控 + + * 如果在关键时期,执行关键任务的时候,执行了优先副本的选举操作 + * 此时会阻塞业务,频繁超时 + + * 所以,这个可以手动的去控制 + + # 手动的对分区leader副本进行重新平衡 + * 使用脚本:kafka-preferred-replica-election.sh + * 优先副本的选举是一个安全的过程,Kafka客户端可以自动感知到分区leader副本的变更 + + * 手动通过脚本执行leader副本重新平衡 + kafka-preferred-replica-election.sh --zookeeper localhost:2181 + + --path-to-json-file + * 可以通过该参数指定一个JSON文件 + * 该JSON文件保存需要执行优先副本选举的parition清单(而不是执行所有分区) + { + "paritions":[{ + "parition":0, + "topic":"topic-paritions" + },{ + "parition":1, + "topic":"topic-paritions" + }] + } + + + * 这种方式默认会把集群中的所有分区都执行一遍优先副本的选举操作 + * leader副本转移是一项高成本的工作,如果要执行的分区数很多,那么对客户端会造成一定的影响 + + * 如果集群包含了大量的分区,那么上面的这种使用方式可能会失效 + * 在优先副本的选举过程中,具体的元数据会存入zk节点:/admin/preferred_replica_election + * 受限于zk单个节点的数据大小限制(默认1mb),那么可能会选举失败 + + + * 生产环境一般都是使用 path-to-json-file来分批,手动的执行优先副本的选举操作 + * 而且要注意时间段,尽量的避开业务高峰 + + # 通俗的解释 + * topic的每个paritio会分布在不同的broker上 + * 一般每个broker上的leader数目相同是最好的,因为只有leade负责io,大家都一样,就负载均衡了 + + * 如果某个leader宕机了,那么它的follwer会成为新的leader,就可能导致,某个broker上有俩leader节点,导致负载不均衡 + + * 可以开启分区的自动平衡机制,程序定时扫描整个集群,自动完成leader的重新选举,来维护集群的负载均衡 + * 执行自动平衡(优先副本选举)的这个过程比较耗时,而且还会阻塞客户端,不可控(因为不知道啥它啥时候就会执行分区的自动平衡) + + * 可以手动的去执行脚本,来完成分区的自动平衡,手动的方式自己可以控制执行的时间,以及需要平衡的分区(针对性的执行,而不是一次性的执行所有) + + * 该机制保证的是leader在集群中分布均匀 + + +---------------------------- +Topic 分区的重新分配 | +---------------------------- + # 两个问题 + * 如果broker宕机/下线,上面的分区副本就处于不可用状态 + * 可能会影响到整个集群的可用性和负载 + + * 如果新增一台broker到集群,原来的分区不会均衡到新的Broker上 + * 只有新创建的主题,才会把分区分配到这个新的broker上 + + * 为了解决上面的问题,于是就需要使用:分区重新分配 + + # 使用脚本完成分区重新分配:kafka-reassign-partitions.sh + * 它可以在集群扩容,broker节点宕机的情况下对分区进行迁移 + * 该脚本的使用分为三个步骤 + + 1 创建包含主题清单的JSON文件 + { + "topics":[{ + "topic":"topic-name" + }], + "version":1 + } + + 2 根据主题清单文件和broker节点清单生成一个重新分配方案 + bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --generate --topics-to-move-json-file reassign.json --broker-list 0,2 + --generate + * 是当前脚本的指令,用来生成一个重新分配的方案 + + --topics-to-move-json-file + * 指定主题清单JSON文件 + + --broker-list + * 指定要分配的broker节点列表(broker.id) + + * 执行完毕后会在控制台输出两个内容 + Current parition replica assignment + * 当前分区的分配情况 + * 最好保存到文件中,如果可以用来回滚 + + Proposed parition reassignment configuration + * 生成的重新分配方案 + + + 3 根据生成的方案执行重新分配动作 + bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --execute --reassignment-json-file project.json + --execute + * 是当前脚本的指令,用来执行一个重新分配的方案 + + --reassignment-json-file + * 指定分配方案的JSON文件 + + --throttle + * 限制同步数据的网络传输速度: byte/s + + * 原理就是为每个分区先添加新的副本,新的副本从leader复制数据 + * 复制的过程是通过网络复制的,所以需要花一点儿时间 + * 在复制完成后,就会删除旧的副本(恢复原来的副本数量) + + # 复制限流 + * 执行分区重分配的时候,复制的量太大可能会影响整体的性能(尤其是处于业务高峰期的时候) + * 可以通过broker参数来限制同步速度(限流),从而保证数据在同步期间整体的服务不会受到太大的影响 + follower.replication.throttled.rate + * 设置Follower副本的复制速速 + * 单位是 byte/s + + leader.replication.throttled.rate + * 设置Leader副本的传输速度 + * 单位是 byte/s + + * 在执行分区重分配脚本的时候,也可以通过参数来限制速度: byte/s + --throttle + + # 通俗理解 + * 该机制保证的是集群中新增/减少broker的时候,使各个broker上的分区平衡 + +---------------------------- +修改副本因子 | +---------------------------- + # 就是修改parition分区的副本数量 + # 也是通过脚本:kafka-reassign-partitions.sh 来实现 + * 用到的时候再查吧 + + # 分区的副本数量是可以减少的(与分区的数量不同,分区数只能增加) \ No newline at end of file diff --git "a/Kafka/kafka-core-\346\227\245\345\277\227\345\255\230\345\202\250.java" "b/Kafka/kafka-core-\346\227\245\345\277\227\345\255\230\345\202\250.java" new file mode 100644 index 00000000..30f6d61d --- /dev/null +++ "b/Kafka/kafka-core-\346\227\245\345\277\227\345\255\230\345\202\250.java" @@ -0,0 +1,105 @@ +------------------------ +日志存储 | +------------------------ + +------------------------ +文件目录的布局 | +------------------------ + # 主题逻辑布局 + Topic + |-Parition-1 + |-Log + |-LogSgement + |-.log 日志文件 + |-.index 偏移量索引文件 + |-.timeindex 时间戳索引文件 + |-.txnindex 事务索引文件 + |-.delete + |-.cleaned + |-.swap + |-.snapshot + |-leader-epoch-checkpoint + |-other 其他文件 + |-Parition-x + + * log以文件夹的形式存在于磁盘上 + * 每个 LogSgement 对应磁盘上的一个日志文件(log)和两个索引文件(index,timeindex) + + # 主题物理布局 + Kafka-Log-Dir + topic-log-0 + 00000000000000000000.index + 00000000000000000000.log + 00000000000000000000.timeindex + + 00000000000000001111.index + 00000000000000001111.log + 00000000000000001111.timeindex + + 00000000000000002222.index + 00000000000000002222.log + 00000000000000002222.timeindex + + * 文件夹命名方式为:<主题名称>-<分区号> + topic-log-0 ,表示 topic-log 主题的 第一个分区 0 + + * 具有相同名称的,一个日志文件(log)和两个索引文件(index,timeindex)组成了一个LogSgement + * 往LogSgement中写入数据,到达了一定量后,就会创建新LogSgement + * 文件名称的数字表示当前LogSgement的基准偏移量(从哪里开始的),由20个数字长度组成,不足前面补充0 + 00000000000000000000 第一个Segemnt全是0,表示当前Segement是从0开始写入数据的 + 00000000000000000133 第二个Sgement以133结尾,表示当前Segment是从133开始写入数据的,也就是说上一个Segement中有 133 条消息(0 - 132) + + + # 日志文件目录 + * broker的配置项 log.dirs 可以配置一个或者多个日志目录 + * 每个日志目录下都有几个文件 + log-start-offset-checkpoint + recovery-point-offset-checkpoint + replication-offset-checkpoint + cleaner-offset-checkpoint + meta.propertiesrecovery-point-offset-checkpoint + + * 当创建主题的时候,系统会选择分区最少的那个目录来创建 + +------------------------ +日志索引 | +------------------------ + +------------------------ +日志清理 | +------------------------ + # 为了控制消息对磁盘的空间占用,需要做清理操作 + # 两种日志清理策略 + * 日志删除,按照一定的策略直接删除不符合条件的日志分段 + + * 日志压缩,针对每个消息的key进行整合,对于相同key不同value的消息,只保留最后一个版本 + + # 设置日志清理策略,broker配置 + log.cleanup.policy + + delete(默认) + * 使用日志删除策略 + compact + * 使用压缩策略 + + * 清理策略还可以配置多个,表示同时支持删除和压缩 + * 必须要开启配置:log.cleaner.enable=true + + # 日志的清理策略粗粒度可以控制到主题 + * 主题的配置选项 + cleanup.policy + + + # 配置broker删除任务的检测周期 + log.retention.check.interval.ms=300000 + + * 默认 5 分钟检测一次 + + # + + + + + + + \ No newline at end of file diff --git a/Kafka/kafka-manager.java b/Kafka/kafka-manager.java new file mode 100644 index 00000000..6324a98e --- /dev/null +++ b/Kafka/kafka-manager.java @@ -0,0 +1,48 @@ +------------------------ +kafka-manager | +------------------------ + # github + https://github.com/yahoo/kafka-manager + + # 下载 + git clone git@github.com:yahoo/kafka-manager.git + + # 编译 + ./sbt clean dist + + * 会在~路径下创建一个 .sbt目录,编译后的文件存放在该目录相应子目录里 + * 如果提示sbt-launch.jar 下载失败,那么需要自己手动的去下载该依赖,并且上传到指定的目录下 + ~/.sbt/launchers/0.13.9/ + + # 解压文件:kafka-manager-1.3.3.22.zip + unzip /kafka-manager/target/universal/kafka-manager-1.3.3.22.zip + + + # 配置:conf/application.conf + kafka-manager.zkhosts="my.zookeeper.host.com:2181" + + # 进入bin目录启动 + nohup ./bin/kafka-manager -Dconfig.file=./conf/application.conf & + + + # 功能的启用和禁用 + application.features=["KMClusterManagerFeature","KMTopicManagerFeature","KMPreferredReplicaElectionFeature","KMReassignPartitionsFeature"] + + KMClusterManagerFeature + * 允许从Kafka Manager添加,更新,删除集群 + + KMTopicManagerFeature + * 允许从Kafka群集添加,更新,删除主题 + + KMPreferredReplicaElectionFeature + * 允许为Kafka群集运行首选副本选择 + + KMReassignPartitionsFeature + * 允许生成分区分配和重新分配分区 + + # 访问端口默认:9000 + * 可以启动时修改 + -Dhttp.port=1024 + + # 关闭 + * 直接kill掉进程,jps进程名称:ProdServerStart diff --git a/Kafka/kafka-zookeeper.java b/Kafka/kafka-zookeeper.java new file mode 100644 index 00000000..bd89eb02 --- /dev/null +++ b/Kafka/kafka-zookeeper.java @@ -0,0 +1,29 @@ +------------------------ +zookeeper 创建的节点信息| +------------------------ + cluster + controller_epoch + controller + brokers + |-ids(集群每个节点的id会存在于该目录下) + |-topics + |-[主题名称]({"version":1,"partitions":{"2":[1,2]}}) + |-partitions + |-[分区号] + |-state({"controller_epoch":6,"leader":2,"version":1,"leader_epoch":0,"isr":[2]}) + |-seqid + zookeeper + admin + |-delete_topics + |-[主题名称] + isr_change_notification + consumers + log_dir_event_notification + latest_producer_id_block + config + |-changes + |-config_change_<序列号> + |-topics + |-[主题名]({"version":1,"config":{"max.message.bytes":"10000","cleanup.poliy":"compact"}}) + + diff --git "a/Kafka/kafka-\345\256\211\350\243\205-\351\233\206\347\276\244.java" "b/Kafka/kafka-\345\256\211\350\243\205-\351\233\206\347\276\244.java" new file mode 100644 index 00000000..968a1bfe --- /dev/null +++ "b/Kafka/kafka-\345\256\211\350\243\205-\351\233\206\347\276\244.java" @@ -0,0 +1,15 @@ +-------------------------------- +kafka 集群的安装 | +-------------------------------- + # 设置每个节点的id值(非负,不能重复) + broker.id=0 + + # 连接到同一个zookeeper中 + * 如果zookeeper也是集群,可以在server.properties中配置多个地址 + + zookeeper.connect=host:2181,host:2181,host:2181 + + # 如果是伪集群,需要注意端口号和日志目录不能重复 + listeners=PLAINTEXT://:9092 + log.dirs=/tmp/kafka-logs + diff --git "a/Kafka/kafka-\345\256\211\350\243\205.java" "b/Kafka/kafka-\345\256\211\350\243\205.java" new file mode 100644 index 00000000..5ba882d7 --- /dev/null +++ "b/Kafka/kafka-\345\256\211\350\243\205.java" @@ -0,0 +1,29 @@ + +-------------------------------- +kafka 单机的安装 | +-------------------------------- + # 下载,解压 + # 启动ZKServer + * kafka自带了一个Zookeeper + lib + |-zookeeper-3.4.13.jar + config + |-zookeeper.properties + + zookeeper-server-start.bat ../../config/zookeeper.properties + + # 启动Kafka + kafka-server-start.bat ../../config/server.properties + + +-------------------------------- +kafka 安装问题 | +-------------------------------- + # 错误: 找不到或无法加载主类 Files\Java\jdk1.8.0_171\lib\dt.jar;C:\Program + * 原因是因为当前的Java环境使用的是jdk而不是jre(网上说的...) + * 修改文件:kafka-run-class.bat(Linux的话修改 sh文件) + * 修改 COMMAND 变量值(使用rem注释原来的,然后添加下面的,就是把 %CLASSPATH% 添加了一个双引号) + + rem set COMMAND=%JAVA% %KAFKA_HEAP_OPTS% %KAFKA_JVM_PERFORMANCE_OPTS% %KAFKA_JMX_OPTS% %KAFKA_LOG4J_OPTS% -cp %CLASSPATH% %KAFKA_OPTS% %* + set COMMAND=%JAVA% %KAFKA_HEAP_OPTS% %KAFKA_JVM_PERFORMANCE_OPTS% %KAFKA_JMX_OPTS% %KAFKA_LOG4J_OPTS% -cp "%CLASSPATH%" %KAFKA_OPTS% %* + diff --git "a/Kafka/kafka-\346\200\247\350\203\275\346\265\213\350\257\225.java" "b/Kafka/kafka-\346\200\247\350\203\275\346\265\213\350\257\225.java" new file mode 100644 index 00000000..664c497c --- /dev/null +++ "b/Kafka/kafka-\346\200\247\350\203\275\346\265\213\350\257\225.java" @@ -0,0 +1,4 @@ +----------------------------- +性能测试 | +----------------------------- + # 使用脚本:kafka-producer-perf-test.sh \ No newline at end of file diff --git "a/Kafka/kafka-\346\200\273\347\273\223.java" "b/Kafka/kafka-\346\200\273\347\273\223.java" new file mode 100644 index 00000000..c5665257 --- /dev/null +++ "b/Kafka/kafka-\346\200\273\347\273\223.java" @@ -0,0 +1,4 @@ +------------------------ +总结 | +------------------------ + diff --git "a/Kafka/kafka-\346\240\270\345\277\203\346\246\202\345\277\265.java" "b/Kafka/kafka-\346\240\270\345\277\203\346\246\202\345\277\265.java" new file mode 100644 index 00000000..9ff72b5c --- /dev/null +++ "b/Kafka/kafka-\346\240\270\345\277\203\346\246\202\345\277\265.java" @@ -0,0 +1,154 @@ +-------------------------- +Kafka核心概念 | +-------------------------- + # Broker + * 一台Kafka服务器,负责消息的读,写,存 + * 一个集群由多个Broker组成,一个Broker可以容纳多个Topic + * Broker与Broker之间不存在M/S(主从)的关系 + + # Topic + * 同一个Topic的消息可以分布在一个或者多个节点上 + * 一个Topic包含一个或者多个 Partition + * 每条消息都属于且仅属于一个Topic + * Producer发布消息必须要指定发布到哪一个Topic + * Consumer消费消息,也必须要指定订阅哪个Topic的消息 + * 可以把它理解为一个 Queue + * topic被删除后,默认还会存储一个礼拜,在此期间还可以进行消费 + + # Partition + * 一个Topic分为多个Partition(创建的时候指定) + * 一个Partition只分布在一个Broker上 + * 一个Partition物理上对应一个文件夹 + * 一个Partition包含多个Segment,一个Segment对应一个文件 + * Segment是由一个个不可变记录组成 + * 记录只会被append到Segement中,不会被单独的删除或者修改 + * 清除过期日志,直接删除一个或者多个Segment + * 内部消息强有序,且都有一个递增的序号 + + + # Producer + * 消息生产者,负责往broker推送消息 + * 它可以决定往哪个Partition写消息,可以是轮询,或者hash + * 推送消息的时候如果没有设置key,那么随机选择一个Partition + * 如果推送消息设置了key,那么会根据hash选择一个Partition + + # Consumer + * 消息消费者,负责从broker拉取消息消费 + * 一个Consumer一次只能从一个Partition中消费数据,并且维护它的offset(使用zookeeper) + * 每个Consumer都必须属于一个组,如果不指定,那么系统默认生成一个组 + + # Consumer Group(CG) + * 消费者群组,一个群组由一个或者多个Consumer组成 + * 这是kafka实现广播和单播的一个手段 + * 一个Topic可以有多个CG,topic的消息会复制(不是真的复制,是概念上的)到所有的CG + * 但每个CG只会把消息发给该CG中的一个Consumer + * 实现广播 + * 所有的消费者都独立的成为一个CG + + * 实现单播 + * 所有的消费者都在一个CG + + * 实质上一个CG,才是一个消费者,可以把这个'消费者'理解为一个集群,集群中的每个Consumer都是一个节点 + + * 一个CG中会包含多个Consumer,这样不仅可以提高Topic中消息的并发消费能力,而且还能提高"故障容错"性 + * 如果CG中的某个Consumer失效,那么其消费的Partitions将会有其他Consumer自动接管 + * Kafka的设计原理决定,对于一个Topic,同一个Group中不能有多于Partitions个数的Consumer同时消费 + * 否则将意味着某些Consumer将无法得到消息(处于闲置状态) + + * 一个partition,只能被消费组里的一个消费者消费 + + # Partition + * 为了实现扩展性,一个非常大的Topic可以分布到多个Broker(即服务器)上 + * 一个Topic可以分为多个Partition,每个Partition是一个有序的队列 + * Partition中的每条消息都会被分配一个有序的id(offset) + * kafka只保证按一个Partition中的顺序将消息发给Consumer,不保证一个Topic的整体(多个Partition间)的顺序 + + # Offset + * kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找 + * 例如想找位于2049的位置,只要找到2048.kafka的文件即可 + * 当然 the first offset就是00000000000.kafka + + # 数据过期机制 + * Kakfa在消息被消费完毕之后不会删除 + * 它根据时间策略来删除,默认是存储一周 + +-------------------------- +Producer负载均衡和HA机制 | +-------------------------- + * producer根据用户指定的算法,将消息发送到指定的partition + * 存在多个partiiton,每个partition有自己的replica,每个replica分布在不同的Broker节点上 + * 多个partition需要选取出lead partition,lead partition负责读写,并由zookeeper负责fail over(故障切换) + * 通过zookeeper管理broker与consumer的动态加入与离开。 + +-------------------------- +Consumer的pull机制 | +-------------------------- + * 由于kafka broker会持久化数据,broker没有cahce压力,因此,consumer比较适合采取pull的方式消费数据 + * 简化kafka设计,降低了难度 + * Consumer根据消费能力自主控制消息拉取速度 + * consumer根据自身情况自主选择消费模式,例如批量,重复消费,从制定partition或位置(offset)开始消费等 + +-------------------------- +kafak系统扩展性 | +-------------------------- + * kafka使用zookeeper来实现动态的集群扩展,不需要更改客户端(producer和consumer)的配置 + * broker会在zookeeper注册并保持相关的元数据(topic,partition信息等)更新 + * 而客户端会在zookeeper上注册相关的watcher,一旦zookeeper发生变化,客户端能及时感知并作出相应调整 + * 这样就保证了添加或去除broker时,各broker间仍能自动实现负载均衡 + +-------------------------- +partition 的分配 | +-------------------------- + * 将所有Broker(假设共n个Broker)和待分配的Partition排序 + * 将第i个Partition分配到第(i % n)个Broker上,这个就是leader + * 将第i个Partition的第j个Replica分配到第((i + j) % n)个Broker上 + + +-------------------------- +Kafka-Replica | +-------------------------- + # broker之间replica机制 + * replication策略是基于partition,而不是topic + * kafka将每个partition数据复制到多个server上,任何一个partition有一个leader和多个follower(可以没有) + * 备份的个数可以通过broker配置文件来设定. + * eader处理所有的read-write请求,follower需要和leader保持同步 + * Follower就像一个"consumer"消费消息并保存在本地日志中 + * leader负责跟踪所有的follower状态,如果follower"落后"太多或者失效,leader将会把它从replicas同步列表中删除 + + * 当所有的follower都将一条消息保存成功,此消息才被认为是"committed",那么此时consumer才能消费它, + * 这种同步策略,就要求follower和leader之间必须具有良好的网络环境(同步刷盘) + + * 即使只有一个replicas实例存活,仍然可以保证消息的正常发送和接收,只要zookeeper集群存活即可.(不同于其他分布式存储,比如hbase需要"多数派"存活才行) + + # 判定一个follower存活与否的条件有2个 + * follower需要和zookeeper保持良好的链接 + * 它必须能够及时的跟进leader,不能落后太多 + + * 如果同时满足上述2个条件,那么leader就认为此follower是"活跃的" + * 如果一个follower失效(server失效)或者落后太多, + * leader将会把它从同步列表中移除[备注:如果此replicas落后太多,它将会继续从leader中fetch数据,直到足够"up-to-date",然后再次加入到同步列表中] + * kafka不会更换replicas宿主,因为"同步列表"中replicas需要足够快,这样才能保证producer发布消息时接受到ACK的延迟较小 + + # 当leader失效时,需在followers中选取出新的leader + * 可能此时follower落后于leader,因此需要选择一个"up-to-date"的follower + * kafka中leader选举并没有采用"投票多数派"的算法 + * 因为这种算法对于"网络稳定性"/"投票参与者数量"等条件有较高的要求,而且kafka集群的设计,还需要容忍N-1个replicas失效 + * 对于kafka而言,每个partition中所有的replicas信息都可以在zookeeper中获得,那么选举leader将是一件非常简单的事情 + + * 选择follower时需要兼顾一个问题,就是新leader server上所已经承载的partition leader的个数 + * 如果一个server上有过多的partition leader,意味着此server将承受着更多的IO压力 + * 在选举新leader,需要考虑到"负载均衡",partition leader较少的broker将会更有可能成为新的leader + + * 在整几个集群中,只要有一个replicas存活,那么此partition都可以继续接受读写操作 + +-------------------------- +MQ的消息模型 | +-------------------------- + # 点对点 + * 一条消息,只会被订阅了该Topic的消费者的其中一个消费者消费 + * 点对点 + + # 发布订阅 + * 一条消息,只要是订阅了该Topic的消费者都会消费 + * 广播 + diff --git "a/Kafka/kafka-\347\273\264\346\212\244.java" "b/Kafka/kafka-\347\273\264\346\212\244.java" new file mode 100644 index 00000000..7d649e7b --- /dev/null +++ "b/Kafka/kafka-\347\273\264\346\212\244.java" @@ -0,0 +1,82 @@ +-------------------------------- +kafka 维护 | +-------------------------------- + # 启动Kafka + kafka-server-start.bat ../../config/server.properties + + # 关闭 + kafka-server-stop.sh + + # 创建主题 + bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test + --create + * 创建指令 + --zookeeper + * 指定zk的地址 + --partitions + * 创建多少个partition + --replication-factor + * 每个partition多少个备份 + --topic + * 设置topic的名称 + --replica-assignment + * 可以自己控制分区的分配 + * 这种方式根据分区号的数值大小按照从小到大的顺序进行排列,分区与分区之间用逗号","隔开 + * 分区内多个副本用冒号":"隔开 + * 并且在使用该参数创建主题时不需要原本必备的 partitions 和 replication-factor 这两个参数 + * 同一个分区内的副本不能有重复,比如指定了 0:0,1:1 这种,就会报出异常 + + --replica-assignment 2:0,0:1,1:2,2:1 + + 2:0 表示第 0 个分区,有两个副本,在broker.id 为 2 和 0 的节点上 + 0:1 表示第 1 个分区,有两个副本,在broker.id 为 0 和 1 的节点上 + 1:2 表示第 2 个分区,有两个副本,在broker.id 为 1 和 2 的节点上 + 2:1 表示第 3 个分区,有两个副本,在broker.id 为 2 和 1 的节点上 + + --config + * 自定义配置,覆盖主题的默认配置 + * 该配置项可以存在多个,表示覆盖多个值 + --config kek=value + --config cleanup.policy=compact --config max.message.bytes=l000 + + --if-not-exists + * 如果主题已经存在,不会抛出异常,也不会创建成功 + + + # 查看创建的主题 + bin/kafka-topics.sh --zookeeper localhost:2181 --list + + # 查看主题的详情 + bin/kafka-topics.sh --zookeeper localhost:2181 --describe --topic test + + Topic:test PartitionCount:1 ReplicationFactor:1 Configs: + Topic: test Partition: 0 Leader: 2 Replicas: 2 Isr: 2 + + # 删除主题 + bin/kafka-topics.sh --zookeeper localhost:2181 --delete --topic test + + + # 打开基于控制台的消息生产者 + bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test + --broker-list + * 指定集群中任意节点的地址信息 + --topic + * 指定要往哪个topic写入数据 + + + # 打开基于控制台的消息消费者 + bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning + --bootstrap-server + * 指定集群中节点的信息 + --topic + * 知道要消费哪个节点 + --from-beginning + * 该参数表要从头开始消费 + + + # 查看topic的消费进度 + bin/kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list localhost:9092 --topic test --time -1 + + --time + * -1 表示查询当前topic各个分区前最大的消息offset(非consumer的offset,而是消息在每个分区的offset) + * -2 表示获取当前各个分区的最小位移 diff --git a/Kafka/kafka.java b/Kafka/kafka.java new file mode 100644 index 00000000..00690511 --- /dev/null +++ b/Kafka/kafka.java @@ -0,0 +1,73 @@ +--------------------- +kafka-入门 | +--------------------- + # 官网 + http://kafka.apache.org + + # 中文文档 + http://kafka.apachecn.org/intro.html + + # 参考 + https://blog.csdn.net/lizhitao/article/details/39499283 + http://www.jasongj.com/tags/Kafka/ + https://www.jianshu.com/p/d3e963ff8b70 + + + # 特点 + * 吞吐量高,在廉价的机器上,单机可以支持100w/s消息的读写 + * 消息持久化,所有消息都会被持久化到硬盘,无消息丢失,支持消息重放 + * 完全分布式:Producer,Broker,Consumer都支持水平的扩展 + * 同时满足适应在线流处理和离线批处理 + + +--------------------- +kafka-目录结构 | +--------------------- + bin + windows + kafka-run-class.sh + kafka-server-start.sh + kafka-server-stop.sh + logs(运行时创建的文件夹) + controller.log + * KafkaController 运行时日志,默认 TRACE + kafka-authorizer.log + * 权限认证相关操作日志,默认 WARN + kafka-requests.log + * 网络请求日志,默认 WARN + kafkaServer-gc.log + * 运行过程,GC的日志,默认 INFO + log-cleaner.log + * 日志清理操作相关统计信息,默认 INFO + server.log + * KafkaServer 运行日志,默认 INFO + state-change.log + * 分区角色切换等状态转换日志,默认 TRACE + + config + connect-console-sink.properties + connect-console-source.properties + connect-distributed.properties + connect-file-sink.properties + connect-file-source.properties + connect-log4j.properties + connect-standalone.properties + consumer.properties + log4j.properties + producer.properties + server.properties + tools-log4j.properties + trogdor.conf + zookeeper.properties + libs + site-docs + kafka_2.12-2.1.1-site-docs.tgz + + +--------------------- +kafka 消息协议 | +--------------------- + +|offset|length|CRC32|Magic|timestamp|attributes|key length|key|value length|value| + +|4字节偏移量|4字节长度|4字节CRC32|1字节魔术变量|8字节时间戳|1字节attributes(枚举)|4字节keylength|key|4字节valuelength|value| diff --git a/Kafka/script/kafka-configs.bat b/Kafka/script/kafka-configs.bat new file mode 100644 index 00000000..8a7ddc55 --- /dev/null +++ b/Kafka/script/kafka-configs.bat @@ -0,0 +1,20 @@ +------------------------- +kafka-configs.bat | +------------------------- + # kafka-configs.bat 脚本是专门用来对配置进行操作的 + * 这里的操作是指在运行状态下修改原有的配置,如此可以达到动态变更的目的 + * 脚本包含变更配置 alter 和查看配置 describe 这两种指令类型 + * 脚本变更配置的原则一样,增 ,删,改的行为都可以看作变更操作 + * 脚本不仅可以支持操作主题相关的配置,还可 以支持操作 broker,用户,和客户端这 3 个类型的配置 + + bin/kafka-configs.bat --zookeeper localhost:2181 --describe --entity-type topics --entity-name topic-config + + --entity-type + * 指定要修改的目标 + topics 修改主题名称 + brokers 指定brokerld值,即 broker 中 broker.id 参数配置的值 + clients 指定 clientld 值,即 KatkaProducer 或 KatkaConsumer 的 client.id 参数配置的值 + users 指定用户名 + + --entity-name + * 指定目标的值 \ No newline at end of file diff --git a/Kafka/script/kafka-console-consumer.bat b/Kafka/script/kafka-console-consumer.bat new file mode 100644 index 00000000..4c982e05 --- /dev/null +++ b/Kafka/script/kafka-console-consumer.bat @@ -0,0 +1,4 @@ +-------------------------------- +kafka-console-consumer.bat | +-------------------------------- + # 使用控制台(打印)处理消息的消息消费者 diff --git a/Kafka/script/kafka-console-producer.bat b/Kafka/script/kafka-console-producer.bat new file mode 100644 index 00000000..c6bd702e --- /dev/null +++ b/Kafka/script/kafka-console-producer.bat @@ -0,0 +1,6 @@ +-------------------------------- +kafka-console-producer.bat | +-------------------------------- + # 使用控制台(读取)发送消息的消息生产者 + + \ No newline at end of file diff --git a/Kafka/script/kafka-kafka-preferred-replica-election.bat b/Kafka/script/kafka-kafka-preferred-replica-election.bat new file mode 100644 index 00000000..a1e47fd2 --- /dev/null +++ b/Kafka/script/kafka-kafka-preferred-replica-election.bat @@ -0,0 +1,19 @@ +------------------------------------ +kafka-preferred-replica-election.sh | +------------------------------------ + # 手动执行优先副本的leader选举脚本 + + kafka-preferred-replica-election.sh --zookeeper localhost:2181 + + --path-to-json-file + * 可以通过该参数指定一个JSON文件 + * 该JSON文件保存需要执行优先副本选举的parition清单(而不是执行所有分区) + { + "paritions":[{ + "parition":0, + "topic":"topic-paritions" + },{ + "parition":1, + "topic":"topic-paritions" + }] + } \ No newline at end of file diff --git a/Kafka/script/kafka-producer-perf-test.bat b/Kafka/script/kafka-producer-perf-test.bat new file mode 100644 index 00000000..3487d583 --- /dev/null +++ b/Kafka/script/kafka-producer-perf-test.bat @@ -0,0 +1,4 @@ +------------------------------- +kafka-producer-perf-test.sh | +------------------------------- + # 性能测试工具 diff --git a/Kafka/script/kafka-reassign-partitions.bat b/Kafka/script/kafka-reassign-partitions.bat new file mode 100644 index 00000000..78b8089d --- /dev/null +++ b/Kafka/script/kafka-reassign-partitions.bat @@ -0,0 +1,34 @@ +---------------------------- +kafka-reassign-partitions.sh| +---------------------------- + # 负责处理分区的重新分配 + bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --generate --topics-to-move-json-file reassign.json --broker-list 0,2 + * 生成分区的分配方案 + + bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --execute --reassignment-json-file project.json + * 执行分区的分配方案 + + bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --verify --reassignment-json-file project.json + * 查看指定方案的分配进度 + + --generate + * 是当前脚本的指令,用来生成一个重新分配的方案 + + --topics-to-move-json-file + * 指定主题清单JSON文件 + + --broker-list + * 指定要分配的broker节点列表(broker.id) + + --execute + * 是当前脚本的指令,用来执行一个重新分配的方案 + + --reassignment-json-file + * 指定分配方案的JSON文件 + + --verify + * 查看指定方案的分配进度 + + --throttle + * 在执行分区分配的时候,限制副本的数据传输速度 + * byte/s \ No newline at end of file diff --git a/Kafka/script/kafka-topics.bat b/Kafka/script/kafka-topics.bat new file mode 100644 index 00000000..ace01321 --- /dev/null +++ b/Kafka/script/kafka-topics.bat @@ -0,0 +1,151 @@ +-------------------------------- +kafka-topics.bat | +-------------------------------- + # 创建主题 + bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test + --zookeeper + * 指定zk + --partitions + * 分区数量 + --replication-factor + * 每个分区的副本数量 + --topic + * 主题名称,一般不要用下划线开头,因为kafka内部的主题使用下划线开头 + --replica-assignment + * 可以自己控制分区的分配 + * 这种方式根据分区号的数值大小按照从小到大的顺序进行排列,分区与分区之间用逗号","隔开 + * 分区内多个副本用冒号":"隔开 + * 并且在使用该参数创建主题时不需要原本必备的 partitions 和 replication-factor 这两个参数 + * 同一个分区内的副本不能有重复,比如指定了 0:0,1:1 这种,就会报出异常 + + --replica-assignment 2:0,0:1,1:2,2:1 + + 2:0 表示第 0 个分区,有两个副本,在broker.id 为 2 和 0 的节点上 + 0:1 表示第 1 个分区,有两个副本,在broker.id 为 0 和 1 的节点上 + 1:2 表示第 2 个分区,有两个副本,在broker.id 为 1 和 2 的节点上 + 2:1 表示第 3 个分区,有两个副本,在broker.id 为 2 和 1 的节点上 + + --config + * 自定义配置,覆盖主题的默认配置 + * 该配置项可以存在多个,表示覆盖多个值 + --config kek=value + --config cleanup.policy=compact --config max.message.bytes=l000 + * 可以在zookeeper的节点下查看这些数据: + get /config/topics/[topic-name] + + + --if-not-exists + * 如果主题已经存在,不会抛出异常,也不会创建成功 + + # 可以通过 ZooKeeper 客户端来获取 broker分区副本的分配情况 + get /brokers/topics/[主题名] + + {"version":1,"partitions":{"2":[1,2]}} + + partitions: + * 表示当前主题的分区"2",有两个副本,分配在了border.id 等于 1 和 2 的节点上 + * json对象的key表示主题的分区编号,value数组表示该分区的副本都分配在哪些broker节点上 + + + + # 查看主题详情 + bin/kafka-topics.sh --zookeeper localhost:2181 --describe --topic test + --zookeeper + * 指定zk + --describe + * 查看详情指令 + --topic + * 主题名称,如果不指定,则显示出所有的主题详情 + * 也可以同时指定多个主题名称,使用逗号分隔 + --topics-with-overrides + * 找出所有包含覆盖配置的主题,它只会列出包含了与集群不一样配置的主题 + * 注意使用该参数时只显示原本只使用 describe 指令的第一行信息 + --under-replicated-partitions + * 找出所有包含失效副本的分区 + * 包含失效副本的分区可能正在进行同步操作,也有可能同步发生异常 + --unavailable-partitions + * 查看主题中没有 leader 副本的分区,这些分区己经处于离线状态 + * 对于外界的生产者和消费者来说处于不可用的状态 + + Topic(主题名):test PartitionCount(分区数量):1 ReplicationFactor(每个分区副本数):1 Configs(主题的配置信息): + Topic: test Partition(分区号): 0 Leader(当前分区Lader副本所在节点的broker.id): 2 Replicas(当前分区所有副本所在节点的broker.id - AR): 2 Isr(当前分区的ISR集合 - ISR): 2 + + Replicas + * 当前这个分区都在哪些节点上 + + # 查看创建的所有主题信息 + bin/kafka-topics.sh --zookeeper localhost:2181 --list + + # 查看创建主题时设置的参数(--config) + get /config/topics/[主题名] + + { + "version":1, + "config":{ + "max.message.bytes":"10000", + "cleanup.poliy":"compact" + } + } + + * config 表示设置的一个或者多个配置项 + + # replica分配算法考虑机房(0.10.x) + * 可以配置一个参数broker.rack说明当前broker在哪个机房 + * 算了,用到的时候再去查吧 + + # 分区副本的分配 + * 如果创建topic时,指定了--replica-assignment参数时,就根据指定的配置去创建分配分区 + + * 未指定 --replica-assignment 参数,则使用内部逻辑去分配分区 + + * 使用内部逻辑分配时 + * 如果设置了broker.rack,那么就使用指定机房的分配策略 + * 如果所有节点都没设置 broker.rack 或者使用 disabale-rack-awre 参数来创建主题,那么采用的就是未指定机房的分配策略 + + + * 未指定机房的分配策略实现类 + AdminUtilities.assignReplicasToBrokersRackUnaware(已经被弃用了,使用AdminZkClient代替) + + + # 修改主题 + bin/kafka-topics.sh --zookeeper localhost:2181 --alter --topic [topic-name] --partitions 3 + bin/kafka-topics.sh --zookeeper localhost:2181 --alter --topic [topic-name] --config max.message.bytes=20000 + bin/kafka-topics.sh --zookeeper localhost:2181 --alter --topic [topic-name] --delete-config max.message.bytes + + --zookeeper + * 指定zk + --alter + * 修改主题的指令 + --topic + * 指定要修改的主题名称 + --if-exists + * 如果执行修改的主题不存在,那么不会抛出异常 + + --partitions + * 修改的参数之一(该参数修改partition数量) + * 可以有多个参数 + + --config + * 修改的主题配置信息 + * 通过该参数指定一个或者多个配置 + + --delete-config + * 删除之前覆盖的配置,使其恢复原有的默认值 + * 通过该参数指定要取消覆盖的配置名称 + + * 目前Kafka只支持增加分区数而不支持减少分区数 + * 而且分区数的修改,可能会导致那些有key的消息,投递的分区号被重置(hash不变,但是partition数量改变了) + + * alter 指令其实已经过时了,而且以后会删除,建议使用:kafka-configs.sh 脚本来实现相关功能 + + # 删除主题 + bin/kafka-topics.sh --zookeeper localhost:2181 --delete --topic [主题名称] + + * 主题被删除后,不会立即的从磁盘删除,只是被标记为删除 + * 如果broker配置的参数: delete.topic.enable=false 那么该命名不会有任何效果 + * 如果要删除的主题是 Kafka 的内部主题,那么删除时就会报错 + + * 使用 kafka-topics.sh 脚本删除主题的行为本质上只是在 ZooKeeper 中的/admin/delete_topics 路径下创建一个与待删除主题同名的节点 + * 以此标记该主题为待删除的状 + * 与创建主题相同的是,真正删除主题的动作也是由 Kafka 的控制器负责完成的 + * 说白了,可以通过操作zookeeper来完成主题的删除操作 \ No newline at end of file diff --git a/Kafka/script/kafka.bat b/Kafka/script/kafka.bat new file mode 100644 index 00000000..416f664c --- /dev/null +++ b/Kafka/script/kafka.bat @@ -0,0 +1,17 @@ + +--------------------- +脚本 | +--------------------- + kafka-configs.sh 用于配置管理 + kafka-console-consumer.sh 用于消费消息 + kafka-console-producer.sh 用于生产消息 + kafka-consumer-perf-test.sh 用于测试消费性能 + kafka-topics.sh 用于管理主题 + kafka-dump-log.sh 用于查看日志内容 + kafka-server-stop.sh 用于关闭 Kafka 服务 + kafka-preferred-replica-election.sh 用于优先副本的选举 + kafka-server-start.sh 用于启动 Kafka 服务 + kafka-producer-perf-test.sh 用于测试生产性能 + kafka-reassign-partitions.sh 用于分区重分配 + kafka-consumer-groups.sh 用于管理消费组 + \ No newline at end of file diff --git a/Kotlin/kotlin-core-String.java b/Kotlin/kotlin-core-String.java new file mode 100644 index 00000000..0839e30c --- /dev/null +++ b/Kotlin/kotlin-core-String.java @@ -0,0 +1,101 @@ +------------------------- +String | +------------------------- + # 字符串模板 + * 类似于 ES6 的模版 + var name = "KevinBlandy"; + var age = 25; + println("$name's age is $age") + + * 使用 $ 访问变量,如果变量不存在,无法访问,会抛出异常 + * 如果需要输出 $ 符号, 那么需要使用转义字符: "\$" + + * 字符串的模板,允许使用简单的表达式: ${表达式} + var age = 25; + println("${if (age > 25) "老男人" else "年轻人"}"); + + * 在表达式中, 允许合理的嵌套双引号的字符串表达式 + + * 支持对象的属性导航,需要使用 {} 包括 + var user = User("KevinBlandy", 25); + println("${user.name},${user.age}"); + + + * 编译后的代码,其实是创建了一个 StringBuilder 对象 + + # 三引号字符串 + * 类似于 js 里面的 `` 字符串, python里面的 ''' '''' + * 里面的字符不需要转义,包括反斜线,还可以包含任何字符, 包括换行 + val name : String = """ + Hello + World + " + """ + println(name) + + * 特别适合用于正则表达式 + * 可以用它来画一些ascii图 + * 可以使用字符串的模板, 因为不支持转义字符,所以需要输出: $ 符号的话,需要使用: ${'$'} + val name : String = """我就是为了输出:${'$'}""" + +------------------------- +String - 实例方法 | +------------------------- + trimMargin(marginPrefix: String = "|"): String + # 删除字符串前面的空格,以及指定的前缀 + # 参数 + marginPrefix + * 需要和空格一起删除的前缀,默认为 "|" (为啥默认值是这个?) + + + + + substringBefore(delimiter: String, missingDelimiterValue: String = this): String + substringBefore(delimiter: Char, missingDelimiterValue: String = this): String + substringBeforeLast(delimiter: String, missingDelimiterValue: String = this): String + substringBeforeLast(delimiter: Char, missingDelimiterValue: String = this): String + # 截取指定字符/字符串之前的字符串, 返回的字符串不包含指定的字符/字符串 + * substringBefore 截取到第一次出现的位置 + * substringBeforeLast 截取到最后一次出现的位置 + + # 参数 + missingDelimiterValue + * 如果指定的字符串, 字符不存在,返回的数据 + * 默认值为 this , 也就说,如果指定的字符, 字符串不存在,默认返回原始字符串 + + substringAfter(delimiter: String, missingDelimiterValue: String = this): String + substringAfter(delimiter: Char, missingDelimiterValue: String = this): String + substringAfterLast(delimiter: String, missingDelimiterValue: String = this): String + substringAfterLast(delimiter: Char, missingDelimiterValue: String = this): String + # 同上, 截取的是,指定字符串之后的字符串 + + + split(vararg delimiters: Char, ignoreCase: Boolean = false, limit: Int = 0): List + split(delimiter: String, ignoreCase: Boolean, limit: Int): List + split(regex: Regex, limit: Int = 0): List + # 按照字符串, 或者正则切割字符串 + # 参数: delimiters + * 可以设置多个分割字符 + + val name : String = "1_2-3_4.5" + val result = name.split("_", "-", ".") + println(result) // [1, 2, 3, 4, 5] + + + toRegex(): Rege + toRegex(option: RegexOption): Regex + # 转换当前字符串为 Regex 对象 + # 参数 RegexOption(枚举) + * 可选的一些正则属性 + + IGNORE_CASE + MULTILINE + LITERAL + UNIX_LINES + COMMENTS + DOT_MATCHES_ALL + CANON_EQ + + + + \ No newline at end of file diff --git a/Kotlin/kotlin-core-object.java b/Kotlin/kotlin-core-object.java new file mode 100644 index 00000000..e69de29b diff --git "a/Kotlin/kotlin-core-\345\207\275\346\225\260.java" "b/Kotlin/kotlin-core-\345\207\275\346\225\260.java" new file mode 100644 index 00000000..1a9260f0 --- /dev/null +++ "b/Kotlin/kotlin-core-\345\207\275\346\225\260.java" @@ -0,0 +1,287 @@ +---------------------- +函数 | +---------------------- + # 基本的函数定义 + fun max(a:Int, b:Int): Int{ + return if (a > b) a else b; + } + + fund [函数名]([变量名]:[类型]):[返回值类型]{ + // 函数体 + } + + + # 表达式函数体 + * 在返回值类型后添加 = ,紧跟函数体 + fun min(a:Int, b:Int):Int = if (a > b) a else b; + + * 甚至可以省略返回值表达式 + fun min(a:Int, b:Int) = if(a > b) a else b; + + * 编译会推导出返回值的类型,所以,这种表达式函数不需要声明返回值类型,以及 return 语句 + * 而非表达式函数,如果存在返回值,必须要声明返回值类型,以及 return 语句 + + + # 函数的调用,可以使用命名参数 + fun foo(var1:String, var2:String, var3:String ){ + println("var1=$var1, var2=$var2, var3=$var3") + } + + fun main(args:Array) { + foo(var3="1", var2="2", var1="3") // var1=3, var2=2, var3=1 + } + + * 因为调用的时候使用命名参数,那么传递参数的时候,可以不按照顺序传递 + * 一个参数使用命名参数,必须的参数都使用命名参数 + + * 调用 java 的方法,不能使用命名参数(java8以后才出现了参数名称保留机制,Kotlin为了兼容jdk6) + + # 参数可以有默认值 + * 在类型后面使用 = 定义默认值 + fun foo(var1:String, var2:String = "default" ){ + println("var1=$var1, var2=$var2") + } + + * 使用常规的调用语法时, 必须按照函数声明中定义的参数顺序来给定参数, 可以省略的只有排在末尾的参数 + * 如果使用命名参数, 可以省略中间的一些参数, 也可以以想要的任意顺序只给定你需要的参数 + * 参数的默认值是被编码到被调用的函数中,而不是调用的地方 + + * 从 Java 中调用 Kotlin 函数的时候,必须显式地指定所有参数值 + * 如果需要从 Java 代码中做频繁的调用,而且希望它能对 Java 的调用者更简便,可以用 @JvmOverloads 注解它 + * 这个注解指示编译器生成 Java 重载函数, 从最后一个开始省略每个参数 + fun foo(var1:String, var2:String,var3:String){ + } + fun foo(var1:String, var2:String){ + } + fun foo(var1:String){ + } + + + # 可变参数 + * 在参数上使用关键字: vararg + fun foo(var1:String, vararg values: String, var2:String){ + for ((index, value) in values.withIndex()){ + /* + value=0, value=2 + value=1, value=3 + */ + println("value=$index, value=$value") + } + } + + fun main(args:Array){ + foo("1",* args,var2 = "") + } + * vararg 可以放置在任意位置 + * 如果它不是最后一个参数的话, 那么它后面的参数, 在调用的时候需要手动的通过命名参数来指定 + + + * 可变参数与Java还有一个不同, 可变参数其实就是一个参数数组 + * 调用Java的可变参数, 传递一个数组的话,这个数组参数就是可变参数 + * 调用Kotlin的可变参数, 传递一个数组的话, 需要自己展开数组, 不然的话, 整个数组参数只会作为可变参数的一个元素(使用 *结构数组 - 这个PY又TM一样) + + # 中缀调用 + * 调用函数的方法 + 1.to("one") 一般调用 + 1 to "one" 中缀调用 + + * 所谓的中缀调用就是: [对象] [方法] [参数] + * 在中缀调用中, 没有额外的分隔符, 函数名称是直接放在目标对象名称和参数之间的 + + * 允许中缀函数调用的函数, 必须使用关键字: infix 修饰 + * 并且中缀函数, 只能有一个参数 + class Foo(){ + infix fun foo(value:String):String{ + return value.plus(value) + } + } + + fun main(args:Array){ + var foo = Foo(); + + var result = foo.foo("Hello") + println(result) + + result = foo foo "Hello"; + println(result) + } + + * 可以使用扩展方法, 来定义一个所有对象都可以使用的中缀调用的方法(其实系统已经定义过了) + infix fun Any.to(value:Any) = Pair(this,value) + fun main(args:Array){ + var pair = "name" to "KevinBlandy" + println(pair) + } + + # 局部函数 + * 方法内部可以再次声明一个函数调用,跟js/py一样 + fun outer(){ + var number = 15 + fun inner (){ + println("number=$number") + } + inner() + } + * 内部函数,可以访问外部函数的局部变量 + + +---------------------- +扩展函数 | +---------------------- + # 就是可以给已有的类,添加新的方法 + + # 尝试给 java.lang.String 类,添加一个 lastChar() 方法,返回字符串的最后一个字符 + fun String.lastChar(): Char = this[this.length - 1] + var lastChar = "KevinBlandy".lastChar() + println(lastChar) // y + + # 尝试给 ArrayList 添加一个 foo 方法 + fun ArrayList.foo() = println("自己添加的方法") + arrayListOf(1).foo(); + + # 语法 + fun [目标类].[方法名称]([方法参数]) = [方法体] + + fun [目标类].[方法名称]([方法参数]): [返回值类型] { + [方法体] + } + + * 在扩展方法体中,可以自由的使用 this ,或者不使用 来访问到内部的属性/方法 + * 但是,扩展方法不能访问到 private / protected 属性/方法 + + + # 扩展函数,需要导入才会生效(其实所谓的导入,就是执行到了那句扩展函数的设置代码) + package io.kevinblandy.common + fun String.lastChar(): Char = if (this.isEmpty()) ' ' else this[this.length - 1] + + import io.kevinblandy.common.lastChar as foo; + var lastChar = "KevinBlandy".foo(); + println(lastChar) + + + # 实质上,扩展函数是静态函数 + * 这个函数把第一个参数,设置为了当前调用的对象 + * 扩展函数不会生成代理对象,不会有额外的性能消耗 + + # 从 Java 中调用扩展函数 + * 知道了扩展函数的本质,就是一个静态函数 + * 所以这个其实非常的简单,把扩展函数当成一个静态函数,然后把调用该函数的对象,传递给扩展函数的第一个参数 + + @file:JvmName("CommonUtils") + package io.kevinblandy.common + fun String.foo(value:String) = this + value; + + import io.kevinblandy.common.CommonUtils; + public class Main { + public static void main(String[] args) { + String value = CommonUtils.foo("KevinBlandy","123456"); + System.out.println(value); // KevinBlandy123456 + } + } + + + # 扩展函数是一个非常高效的语法糖,甚至还可以通过泛型,来限制扩展的对象 + import java.lang.StringBuilder + fun Collection.join( + separator:String, + prefix:String, + suffix:String) : String { + var stringBuilder = StringBuilder(prefix) + for((index, value) in this.withIndex()){ + if (index > 0){ + stringBuilder.append(separator) + + } + stringBuilder.append(value) + } + stringBuilder.append(suffix) + return stringBuilder.toString() + } + + fun main(){ + var result = arrayListOf("1","2","3","4","5").join(", ", "[", "]") + println(result) + } + + # 扩展函数不可以被重写 + * 扩展函数不存在重写,因为扩展函数会被编译成一个静态函数,调用该函数的对象作为第一个形参 + * 扩展函数并不是类的一部分,它是声明在类之外的 + + open class View { + open fun click () = println("View Click") + } + open class Button : View (){ + override fun click() = println("Button click") + } + + // 两个类都扩展方法 + fun View.showOff() = println("Im a View") + fun Button.showOff() = println("Im a Button") + + fun main(){ + var view: View = Button() + // 普通函数, 执行时被重写 + view.click() // Button click + + // 扩展函数, 执行时没有被重写 + view.showOff() // Im a View + } + + * 如果一个类的成员函数和扩展函数有相同的签名(名称一样, 形参一样),那么成员函数会优先使用 + +---------------------- +扩展属性 | +---------------------- + # 扩展属性有必要和扩展函数一起了解 + # 扩展属性没有任何状态, 因为没地方存储, 因为不能给现有的Java对象添加额外的字段 + # 扩展属性本质上就是添加了一个: getter/setter + + # 定义语法 + [var/val] [类].[属性名称] : [返回值] get() = [方法体] + + # 给 String 添加一个 lastChar 的 getter 属性 + val String.lastChar get() = this[this.length - 1] + var lastChar = "KevinBlandy".lastChar; + println(lastChar) // y + + # 给 StringBuilder 添加 getter/setter 属性(StringBuilder 允许修改) + var StringBuilder.last : Char + get() { + return this[this.length - 1] + } + set(value: Char) { + this[this.length - 1] = value + } + val stringBuilder = StringBuilder("KevinBlandy") + stringBuilder.last = 'I'; + println(stringBuilder.last) // I + + # getter 扩展属性使用 val 声明, 因为只能读, setter 扩展属性必须用 var 声明, 因为可以写 + + # 从Java中调用扩展属性,需要显示的调用getter函数(根据属性名称驼峰命名的get函数) + @file:JvmName("CommonUtils") + package io.kevinblandy.common + val String.last get() = this[this.length - 1] + + import io.kevinblandy.common.CommonUtils; + public class Main { + public static void main(String[] args) { + // getLast + Character character = CommonUtils.getLast("KevinBlandy"); + System.out.println(character); + } + } + + + # 属性的扩展,不能在方法内部执行 + val String.lastChar get() = this[this.length - 1]; // 方法外部扩展属性, 正常 + + fun main(){ + // val String.lastChar get() = this[this.length - 1]; 方法内部进行扩展属性, 编译异常 + println("123".lastChar) + } + + + + + diff --git "a/Kotlin/kotlin-core-\345\274\202\345\270\270.java" "b/Kotlin/kotlin-core-\345\274\202\345\270\270.java" new file mode 100644 index 00000000..0d55aa71 --- /dev/null +++ "b/Kotlin/kotlin-core-\345\274\202\345\270\270.java" @@ -0,0 +1,41 @@ +---------------------------- +异常处理 | +---------------------------- + # 抛出异常,跟Java一样 + throw Exception("exception....") + + # 分支结构基本一样 + try { + + }catch (e : Exception){ + + }finally { + + } + + # Kotlin 并不区分受检异常和未受检异常 + * 不用指定函数抛出的异常 , 而且可以处理也可以不处理异常 + + # try catch 也是可以有返回值的 + * 代码块的最后一个表达式作为返回值 + * 这啥设计思想啊? + + var result = try { + 5 + }catch (e: Exception){ + 4 + } + + fun mustFour(value:Int) = try { + if (value == 4){ + 4 + }else{ + throw Exception() + } + }catch (e:Exception){ // 异常了,则返回 1 + 1 + }finally { + -1 // 如果catch代码块还是异常,则返回 -1 + } + + diff --git "a/Kotlin/kotlin-core-\346\225\260\346\215\256\347\261\273\345\236\213.java" "b/Kotlin/kotlin-core-\346\225\260\346\215\256\347\261\273\345\236\213.java" new file mode 100644 index 00000000..1e4db7c7 --- /dev/null +++ "b/Kotlin/kotlin-core-\346\225\260\346\215\256\347\261\273\345\236\213.java" @@ -0,0 +1,119 @@ +---------------------- +变量 | +---------------------- + # 变量的声明关键字:var + var name = "KevinBlandy"; + + * 自动的推导出数据类型,不需要手动声明数据类型 + * 可以修改变量的值,但是不能修改变量的数据类型,如果尝试重新赋一个不同数据类型的值,则异常 + + + # 手动的声明数据类型 + var name: String = "KevinBlandy"; + var age: Int = 25; + + * 如果变量只是声明,并没有初始化,那么必须要声明类型 + var name:String ; + name = "KevinBlandy" ; + + * 变量必须要初始化后才能使用,不然编译异常 + + # 常量的声明关键字: val + * 跟 java 的 final 一样,初始化后就不能修改,允许先声明,再初始化 + + val name:String; + name = "KevinBlandy"; + + * 该变量的引用值不能修改,但是引用的对象,是可以任意修改自身属性值的 + + +---------------------- +基本数据类型 | +---------------------- + # 根Java不一样, Kotlin 不区分基本类型和包装类型 + + # 整数类型 + Byte,Short,Int,Long + + # 浮点数类型 + Float,Double + + # 字符类型 + Char + + # 布尔类型 + Boolean + + + # 每一种基本的数据类型都包含了一系列的装函数 + toChar() + toInt() + toLong() + .. + + * 可以进行不同数据类型的转换 + +---------------------- +Any | +---------------------- + # Any 其实就是 java.lang.Object + * 它是Kotlin一些对象的根对象 + * Kotlin用Any的时候, 最后它会被编译为 Object + + # 跟Java一样, 把基本数据类型赋值给它, 会自动的装箱 + var n:Any = 2 + + # Any 默认是非空值, 不能赋值为 null, 可以使用 Any? 表示允许 null 值 + + # Any 不能调用 Object 的 wait() / notify() 等方法 + * 但是可以手动的把值转换为 Object 后调用 + + +---------------------- +Unit | +---------------------- + # 这个就是 Java 中的 void, 表示不需要返回值 + fun foo():Unit {} + fun foo() {} + + * Unit 类型的返回值, 可以省略, 代码块里面不需要 return 语句 + + # Java 的 Void 先进 + * Java 的方法, 定义了一个泛型返回方法 + * 子类覆写的时候, 使用 Void, 也必须要手动的 return 一个数据 + interface Foo { + T process(); + } + class FooImpl implements Foo { + @Override + public Void process() { + return null; // 必须要使用 return 关键字 + } + } + + * 但是Kotlin 不需要 + interface Foo { + fun process(): T + } + class FoolImp : Foo { // 泛型指定为 Unit + override fun process() { + // 不需要 return 语句 + } + } + +---------------------- +Noting | +---------------------- + # 该返回值类型表示, 这个方法永远不会返回 + * 用在会抛出异常的方法 + fun foo(): Nothing{ + throw Exception("") + } + * 用在死循环的方法 + fun bar (): Nothing { + while (true){} + } + + # 它没任何值, 只能当做函数的返回值用, 或者当做泛型才有意义 + + diff --git "a/Kotlin/kotlin-core-\350\241\250\350\276\276\345\274\217.java" "b/Kotlin/kotlin-core-\350\241\250\350\276\276\345\274\217.java" new file mode 100644 index 00000000..cb8212bb --- /dev/null +++ "b/Kotlin/kotlin-core-\350\241\250\350\276\276\345\274\217.java" @@ -0,0 +1,219 @@ +---------------------------- +表达式 | +---------------------------- + # Kotlin的大多数表达式都是有返回值的 + * 一般分支代码块的最后一个表达式结果,会被当作返回值 + + # if + * if 是有返回值的表达式 + * 如果只有一行代码,可以省略大括号 + + # while 与 do while 还是跟Java和大多数语言一样 + + # as + * 用于把一个数据类转换为另一个数据类型(cast 强制转换) + * 如果不能转换,则抛出异常 + interface Parent + class Sub:Parent + + var x = Sub(); + var y = x as Parent; + + # == 和 === + * Kotlin 会把 == 编译为 equals 调用 + * ==== 才是比较的内存地址 + + + + +---------------------------- +区间迭代 | +---------------------------- + # 类似于 python 的 for i in range().... + # 区间迭代本质上就是在迭代:kotlin.ranges.IntRange + var rannge = 1..10; + for (i in rannge){ + println(i) + } + + # 区间的表达式,留头又留尾 + + # 带步长的迭代(递减的迭代) + for (i in 100 downTo -5 step 1){ + println(i) + } + + * 从 100 开始, 到 -5 结束 + * 迭代的步长为 5 + + # 使用 until 可以不包含结尾 + for (i in 0 until 10){ + println(i) + } + + * 仅仅迭代 0 - 9 + * 相当于 + for (i in 0..(10 - 1)){ + println(i) + } + + # 对于 Map 的迭代,可以使用两个变量,同时迭代 key 和 value + var map = HashMap(); + map["key1"] = "value1" + map["key2"] = "value2" + map["key3"] = "value3" + + for((key,value) in map){ + println("key=$key, value=$value") + } + + # 可以使用两个变量迭代带下标的集合 + var arr = arrayOf("1","2","3","4","5"); + for ((index,value) in arr.withIndex()){ + println("index=$index, value=$value") + } + + * 关键点在于,本质上迭代的是集合的 withIndex() 返回的带下标的迭代器: kotlin.collections.IndexingIterable + +---------------------------- +is 表达式 | +---------------------------- + # 使用 is 判断指定的变量,是否是某种数据类型 + var x = 15; + var isInt = x is Int; + println(isInt); + + # is 可以自动的转换数据类型 + * 与 Java的 instanceof 相似,但是它可以自动的强制转换数据类型 + + class Sub1(): Parent + class Sub2(): Parent + + fun foo(value: Parent){ + if (value is Sub1){ + println(value) // 自动的cast为Sub1 + }else if(value is Sub2){ + println(value) // 自动的cast为Sub2 + } + } + +---------------------------- +in 表达式 | +---------------------------- + # 使用 in 或者 !in 来判断值是否存在于某个区间/集合 + + fun isLetter(char:Char) = char in 'a'..'z' || char in 'A'..'Z' + + var exists = "Java" in setOf("Java","Groovy","Kotlin","Scala") + +---------------------- +解构赋值,与[]展开 | +---------------------- + # 跟js/py差不多 + # [] 展开, 使用 * 运算符 + fun foo(var1:String, vararg values: String, var2:String){ + for ((index, value) in values.withIndex()){ + println("value=$index, value=$value") + } + } + + fun main(args:Array){ + foo("1",* args,var2 = "") + } + + # 解构赋值 + var (key, value) = Pair("name", "KevinBlandy") + println("key=$key, value=$value") + + +---------------------------- +when 表达式 | +---------------------------- + # 可以说是 case 语句的升级版 + # 表达式用来匹配的变量,可以是任何对象 + # 该表达式可以返回一个结果 + fun bar (number: Int):String { + return when (number){ + 1 -> { + "奇数"; + } + 2 -> { + "偶数" + } + else -> { + "啥也不是"; + } + } + } + + * 代码块可以省略 + + # 支持在函数中使用表达式 + fun getLang(lang :Lang) = when (lang){ + Lang.JAVA -> "java" + Lang.C -> "c" + Lang.JAVASCRIPT -> "javascript" + Lang.PYTHON -> "python" + } + + # 支持相同结果的多个匹配值 + fun foo(number:Int) = when(number){ + 1,3,5,7,9 -> "奇数" + 2,4,6,8,10 -> "偶数" + else -> "啥也不是" + } + + # 当 when 的变量非枚举时,必须要指定 else + + # 支持除了 == 匹配模式以外的区间匹配模式 + * 要求匹配变量的数据类型,可以参与表达式的计算 + * 并且结果是 boolean 即可 + + fun foo(number:Int) = when(number){ + in 0..9 -> "在0-9范围内" + is Int -> "是Int" + else -> "不在0-9范围内" + } + + # 也可以用来取代 if-else if链 + * 如果不提供参数,所有的分支条件都是简单的布尔表达式 + * 当一个分支的条件为真时则执行该分支的代码块 + + var x = 5; + when { + x > 5 -> println("大于5"); + + x < 5 -> println("小于5"); + + else -> println("等于5"); + } + + + # when 中的条件使用 ==(equals) 进行判断 + fun foo(v1:Int, v2:Int) :String { + return when(setOf(v1,v2)) { + setOf(1, 2) -> "1和2" + setOf(3, 4) -> "3和4" + else -> throw Exception("啥也不是") + } + } + + fun main(args:Array){ + println(foo(2,1)) // 1和2 + var v1 = setOf(1, 2); + var v2 = setOf(1, 2); + println(v1 === v2) // false + } + +---------------------------- +位移运算 | +---------------------------- + # 没有Java提供的那些符号 + shl 左移 << + shr 右移 >> + + ushr 无符号右移 >>> + and 与 & + xor 异或 | + inv 取反 ^ + diff --git a/Kotlin/kotlin-lambda.java b/Kotlin/kotlin-lambda.java new file mode 100644 index 00000000..12cd6cab --- /dev/null +++ b/Kotlin/kotlin-lambda.java @@ -0,0 +1,291 @@ +------------------------- +lambda | +------------------------- + # 基本的语法 + * 使用大括号包裹整个表达式: {[形参]:[类型] -> 方法体 } + + var func = {x:Int -> x * x} + println(func(5)) + + * lambda表达式可以被赋值给一个变量, 最终当作一个函数来执行(本身就是一个函数) + + * lambda表达式,支持直接调用,有点像是js的语法: (function(){})() + { println("KevinBlandy") }() + + { value:String -> println(value) }("KevinBlandy") + + * lambda 并不局限于单行代码, 可以有多行代码的 + * 最后一行代码的结果, 就是lambda表达式的结果 + var func = {x:String -> { + var x = 15 + println("Hello World") + x + }} + + + # 如果 lambda 表达式是函数的最后一个实参, 那么可以写在括号外面 + rrayOf("").maxBy() { i:String -> i.length } + + * 这语法怪怪的 + + + # 如果 lambda 表达式是函数的唯一实参, 那么连括号都可以不要了 + arrayOf("").maxBy { i:String -> i.length } + + + # 如果参数类型是可以推导的, 那么连参数类型都是可以忽略的 + arrayOf("").maxBy { i:String -> i.length } + arrayOf("").maxBy { i -> i.length } + + * arraOf(""), 返回的是一个 Array, 可以推导出来, 所有的数据类型都是字符串, 所以这个 lambda 可以省略参数的数据类型 + * 也有可能推导不出来参数类型, 反正一个规则: 先被推导, 咋简单咋写, 编译报错了, 再改(书上教的) + + # 使用默认的参数名称 + * 这个是简化到了最高级的地步了 + * 如果lambda只有一个参数, 并且可以推导出数据类型, 那么省略lambda参数的声明, 使用默认的变量名称代替 + * 使用默认的参数名称: it 来表示参数的引用, it 这个变量名称打死不变 + + arrayOf("").maxBy { it.length } + + + # 在作用域中访问变量 + * 跟Java不同 ,Kotlin的 lambda 表达式不进可以访问外部的 final 变量, 甚至还可以修改它们 + + fun foo(list:Collection,prefix:String):Int{ + var count = 0 + list.forEach { + println("$prefix$it") + count ++ // 在lambda修改外面的局部变量 + } + return count + } + fun main(){ + var result = foo(arrayListOf("1","2","3"), "-") + println(result) + } + + * 当访问了一个外部的 final 变量时, 它的值会被拷贝下来, 跟 lambda 表达式存储在一起(Java也是这个原理) + + * 当访问了一个外部的非 final 变量的时候, 它的值会被作为 Raf 实例对象的一个属性保存下来, Raf 是 final 修饰的, 可以被轻易的捕捉 + * 然后 Raf 存储的值, 就可以在 lambda 执行时进行修改 + + # Kotlin 的lambda 表达式里面 一般是不支持 this 的 + + +------------------------- +成员引用 | +------------------------- + # 跟 Java一样, 可以把某些已经定义好的方法, 作为一个 lambda + + # 成员引用的代码: [类]::[成员] + var length = String::length + arrayOf("").maxBy(length) + + arrayOf("").maxBy(String::length) + + # 还可以引用到顶层函数: ::[方法] + fun foo(){ + println("Hello") + } + fun main(){ + var f = ::foo // 获取到顶层函数的引用 + run(f) + } + + * 因为没有类, 所以不需要前面的类声明 + + + # 构造方法的引用: ::[类] + data class User(val name:String) + + // 获取到指定类的构造引用 + var userCreate = ::User + fun main(){ + + // 执行 + var result = userCreate("12345") + println(result) // User(name=12345) + } + + + # Kotlin 1.1 以前, 需要手动的指定运行函数引用的实例对象 + * 有点儿类似于反射的意思, 我获取到执行函数, 但是该以哪个对象的身份来执行这个函数, 需要手动的指定 + data class User(val name:String) { + fun foo(value:String) { + println("$name,$value") + } + } + fun main(){ + // 获取指定类的 foo, + var foo = User::foo + // 执行该引用的时候, 第一个参数为该引用的上下文对象(this) + foo(User("KevinBlandy"),"Hello") + } + + * 注意点就是, 这个方法引用, 是通过类来获取到的 + + # 也可以引用到扩展方法 + fun String.foo() = println("Hello") + fun main() { + // 根据对象, 获取到扩展方法 + var foo = ""::foo + foo() + } + + # Kotlin 1.1 以后, 支持从实例对象获取到方法引用 + * 从指定的实例获取到的方法引用, 是名花有主的,所以可以直接运行, 而不用手动的去指定运行时的对象 + + data class User(val name:String) { + fun foo(value:String) { + println("$name,$value") + } + } + fun main(){ + var user = User("KevinBlandy") + // 获取指定对象的方法引用 + var foo = user::foo + foo("Hello") + } + + * 注意点就是, 这个方法引用, 是通过对象来获取到的 + + + + + + + # Kotlin 的 lambda 可以无缝的和 Java api 对接 + * 把一个 Lmabda 传给 Java 方法 + + public class Main { + public static void foo(Runnable runnable){ + new Thread(runnable).start(); + } + } + + fun main() { + Main.foo {println("Hello")} // 编译器会自动的吧这个lambda转换为 Runnable 的实现 + } + + + * 也可以使用 object 这种字面量对象 + * 但是每次调用, 都会创建一个新的字面量对象 + Main.foo(object : Runnable { + override fun run() { + println("Hello World") + } + }) + + + # SAM构造方法,把 lambda 转换为 接口 + * 啥玩意儿啊这 + fun callDone() : Runnable { + return Runnable { println("done") } + } + fun main() { + var runnable = callDone() + runnable.run() + } + + * 我的理解就是把 lambda 这个表达式, 作为一个实现存在,可以赋值给变量 + + * 语法: [类型] [lambda] + var runnable = Runnable { println("Hello World") } + + +------------------------- +with / apply | +------------------------- + # 带接收者的lambda + * 非常好理解, 这TM不就是 Javascript 里面函数的 call/apply 么? + * 可以指定 lambda 运行时的 this 指向(上下文) + + fun alphabet () : String { + val stingBuilder = StringBuilder() + for(letter in 'A'..'Z'){ + stingBuilder.append(letter) + } + return stingBuilder.toString() + } + * 需要多次去使用 stingBuilder 变量名称, 去执行这个对象的方法 + + fun alphabetWith () : String { + val stingBuilder = StringBuilder() + with(stingBuilder, { + for(letter in 'A'..'Z'){ + this.append(letter) + } + return toString() // 省略 this, 执行访问到帮到对象的上下文方法 + }) + } + * 使用 with 绑定了 this 指向的对象, 那么在 lambda 里面可以痛快的使用 this + * 也可以省略 this 关键字, 因为上下文已经确定了, 可以直接访问方法 + + * 语法: with ([this对象] , [代码块( lambda 表达式 )]) + + + # 其实扩展函数,就是一种带了接收者(执行上下文)的lambda + * 编译后, 是一个静态方法, 第一个参数就是执行的对象, 就是上下文 + + + # With 运算,也是有结果的 + * 也就是说,这个表达式可以返回一个值 + * 代码块的最后一行结果, 作为返回值 + + var letters = with(StringBuilder(), { + for(letter in 'A'..'Z'){ + this.append(letter) + } + toString() + }) + + + # 方法名冲突的问题 + class Bar { + fun foo (){ + println("Im Foo In Bar") + } + } + + class Foo { + fun foo (){ + println("Im Foo In Foo") + } + fun bar (){ + with (Bar()){ + foo() // Im Foo In Bar + this.foo() // Im Foo In Bar + this@Foo.foo() // Im Foo In Foo + } + } + } + + fun main() { + var foo = Foo() + foo.bar() + } + + * 就是一个 with 的lambda 表达式,在一个类方法中, 使用 this 或者不使用, 调用的方法名, 在当前类中也存在 + * 如果需要在当前的with中调用类中的同名方法,需要使用: this@[类].[方法] + + + # apply + * apply 跟 with 的作用都是一样的, 都是为了修改运行时环境 + * 它被设计为一个扩展方法 , 也就是说很多对象都可以执行这个方法 + val stringBuilder = StringBuilder().apply { + for(letter in 'A'..'Z'){ + this.append(letter) + } + } + + fun main() { + println(stringBuilder.toString()) + } + + * apply,需要对象主动的去执行 + * apply, 始终会返回接收者对象(上下文) + + * 可以用这种方式, 在创建完毕对象后设置一些初始化属性 + + + diff --git a/Kotlin/kotlin-oop-object.java b/Kotlin/kotlin-oop-object.java new file mode 100644 index 00000000..6b58c6f7 --- /dev/null +++ b/Kotlin/kotlin-oop-object.java @@ -0,0 +1,137 @@ +---------------------------- +object | +---------------------------- + # object 在 Kotlin 里面是一个 关键字, 可以用它定义字面量形式的对象 + * 类似于, Javascript 定义一个 json 对象一样 + * 可以自由的定义属性, 方法, 而不需要定义类 + + object User { + var name = "KevinBlandy" + fun say(){ + println(this.name) + } + } + + User.say() + + + * 它甚至还可以包含 init 代码块, 但是不能包含构造函数(不需要构造) + * init 代码块只会执行一次, 就是字面量对象代码载入的时候 + + + # 天生单例, 不用多说 + + + # 支持实现接口, 继承类 + abstract class Super { + abstract fun foo() + } + + object User : Comparator, Super() { + override fun foo() { + println("impl...") + } + + override fun compare(o1: String, o2: String): Int { + return o1.compareTo(o2) + } + } + + + # 可以假装静态成员(伴生对象) + * Kotlin的类不存在静态成员这个说法, 但是可以在类内部创建这种字面量对象 + * 在类的内部,这种对象可以访问到所有的成员 + * 该对象没法被子类重写, 而且类对象,没法访问到其类的伴生对象 + * 直接通过类名就可以调用, 达到静态成员的效果 + class Foo { + object Object { + fun func(){ + println("Hello") + } + } + } + + Foo.Object.func() + + * 可以利用它实现工厂方法 + + data class Foo private constructor(var name:String){ + object InnerObject { + fun getInstance (name:String):Foo { + return Foo(name) + } + } + } + var single = Foo.InnerObject.getInstance("KevinBlandy") + + * 使用: companion 直达方法,更快捷 + * 在class中, 使用 companion 关键字修饰 object, 可以忽略object的名称,直接调用到方法 + class Foo { + companion object InnerObject { + fun foo() = println("Hello") + } + } + Foo.foo() // 调用路径, 直接忽略掉 InnerObject + + + * 在Java中访问类的伴生对象 + * 伴生对象,也会被编译为普通的对象,作为类的一个静态字段存在, 名称打死不改:INSTANCE + @file:JvmName("CommonUtils") + package io.kevinblandy.funcs + + class Foo { + object Ruby { + var name = "KevinBlandy" + } + } + + import io.kevinblandy.funcs.Foo; + public class Demo { + public static void main(String[] args) { + String name = Foo.Ruby.INSTANCE.getName(); + System.out.println(name); + } + } + + + # 用于匿名实现 + interface Function { + fun accept(value:T ) + } + + fun foo(function: Function) { + function.accept("Hello") + } + + fun main(args:Array) { + var function = object : Function { //创建一个字面量对象, 实现function接口 + override fun accept(value: String) { // 实现接口抽象方法 + println(value) + } + } + + foo(function) + } + + * 没错, 这玩意儿还可以使用一个变量接收, 非常的js(舒服) + + + + # 在Java中使用这种对象 + * 这种对象, 被编译成了通过静态字段来单一持有的对象 + * 字段的名称打死都是: INSTANCE + + @file:JvmName("CommonUtils") + package io.kevinblandy.funcs + object Foo { + var name = "KevinBlandy" + } + + import io.kevinblandy.funcs.Foo; + public class Demo { + + public static void main(String[] args) { + String name = Foo.INSTANCE.getName(); + } + } + diff --git "a/Kotlin/kotlin-oop-\346\216\245\345\217\243.java" "b/Kotlin/kotlin-oop-\346\216\245\345\217\243.java" new file mode 100644 index 00000000..47c60fe9 --- /dev/null +++ "b/Kotlin/kotlin-oop-\346\216\245\345\217\243.java" @@ -0,0 +1,101 @@ +---------------------------- +接口 | +---------------------------- + # 使用 interface 声明一个接口 + interface Parent { + fun foo() // 声明的抽象方法 + fun bar() = println("默认的实现") + } + + * 可以使用默认的实现方法, 不用声明 default 关键字, 直接写方法体就完事儿了 + + # 如果实现了多个具有相同签名默认方法的接口, 那么必须要求实现类重写该方法(Java对于这种情况咋处理的???) + interface Parent1 { + fun bar() = println("Parent2") + } + interface Parent2 { + fun bar() = println("Parent1") + } + class Sub : Parent1, Parent2 { + override fun bar() { + // 必须要重写 bar 方法, 因为这个方法在俩接口中都有默认的实现 + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + }; + + * 可以在子类中, 使用 super 关键字 + 尖括号, 类似于泛型的格式, 去指定父要调用哪个父级接口的方法 + class Sub : Parent1, Parent2 { + override fun bar() { + return super.bar() + } + }; + + # Kotlin 是兼容Java6的,而Java的接口默认方法在Java8才有 + * Kotlin会把接口的默认方法,编译成一个抽象函数, 然后再用默认实现的方法体, 生成一个静态函数 + * Java 实现Kotlin 的接口, 必须要覆写所的方法, 包括默认方法 + + # 接口, 可以声明抽象属性, 子类必须实现该属性 + interface Super { + var name : String // 抽象属性 + } + + class Sub(override var name: String) : Super ; + * 直接在构造函数参数, 声明 override 关键字 + + class Sub1(var args: String) : Super{ + override var name : String + get (){ + return args + } + set (value:String){ + args = value + } + } + * 自己实现getter + * 还需要实现 setter, 因为接口中使用 var, 而不是 val 定义 + + class Sub2 : Super{ + override var name = foo(); + private fun foo () = "" + } + * 自己初始化属性 + + + # 接口允许有 getter 和 setter 属性 + * 可以被子类继承 + + interface Super { + var name : String + get(){ + return "" + } + set(value:String) { + } + } + + + +---------------------------- +接口的实现 | +---------------------------- + # 实现语法 + import java.io.Serializable + interface Parent { + fun foo() + } + class Sub : Parent, Serializable { + override fun foo() { + println("我是实现") + } + } + fun main(args:Array) { + var obj :Parent = Sub() + obj.foo() + } + + * 使用 override 关键字修饰方法,表示覆写, 并且它是强制添加的 + * 使用冒号 : 来代替 implements 关键字, 可以通过逗号分隔n个接口, 表示多实现 + * 冒号 : 也可以代替 extends 关键字, 但是只能有一个抽象类/父类 + + + diff --git "a/Kotlin/kotlin-oop-\346\236\232\344\270\276.java" "b/Kotlin/kotlin-oop-\346\236\232\344\270\276.java" new file mode 100644 index 00000000..b9df5646 --- /dev/null +++ "b/Kotlin/kotlin-oop-\346\236\232\344\270\276.java" @@ -0,0 +1,59 @@ +---------------------------- +枚举 | +---------------------------- + # 枚举的声明 + enum class Lang { + JAVA, C, JAVASCRIPT, PYTHON + } + + * 按照kotlin的尿性,极简主义,应该只用声明一个 enum 就好了, 为啥还要声明一个 class ? + * 为了不占用 enum 关键字,只有 enum 后面跟着 class 关键字的时候,enum 才是关键字,其他的时候,可以当做变量名称使用(哦) + + # 声明带属性/方法的枚举 + enum class Lang (var desc :String ,var order :Int ){ + // 创建实例的时候初始化 + JAVA("世界上最好的语言",1), + C("世界上最牛逼的语言",2), + JAVASCRIPT("全栈语言",3), + PYTHON("最流行的语言",4); + + fun say(){ + println("name=${this},desc=${this.desc},order=${this.order}"); + } + } + + * 如果最后一个枚举后还有内容(方法),那么最后一个枚举后面一定要使用分号: ; (这是Kotlin中唯一一个强制要求使用分号结束的地方) + + # 可以使用 when 语句操作枚举 + fun getLang(lang :Lang) = when (lang){ + Lang.JAVA -> "java" + Lang.C -> "c" + Lang.JAVASCRIPT -> "javascript" + Lang.PYTHON -> "python" + } + + * 如果需要匹配多个结果,则使用逗号分隔多个值 + fun getLang(lang :Lang) = when (lang){ + Lang.JAVA, Lang.PYTHON, Lang.JAVASCRIPT -> "最喜欢的" + Lang.C -> "最不擅长的" + } + + # 可以使用 * 一次性导入所有的枚举实例,从而可以直接访问实例,而不需要通过枚举类导航 + import io.kevinblandy.funcs.Lang.* + fun main(args:Array){ + println(PYTHON) + } + + + # 枚举可以继承/实现,并且覆写方法 + interface Foo { + fun foo() + } + + enum class Lang : Foo{ + JAVA { + override fun foo() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + } + } \ No newline at end of file diff --git "a/Kotlin/kotlin-oop-\347\261\273.java" "b/Kotlin/kotlin-oop-\347\261\273.java" new file mode 100644 index 00000000..92ecc062 --- /dev/null +++ "b/Kotlin/kotlin-oop-\347\261\273.java" @@ -0,0 +1,367 @@ +------------------------ +类 | +------------------------ + # 简单的类的定义 + class User( + val name:String, + val age:Int + ) + + * 语法: class [类名称] (val [属性名]:[属性类型]); + + * 属性的访问权限默认为: public + + + # Kotlin 中 不存在 static (静态)成员 + + # Kotlin的类/方法默认都是 final,也就是说不允许被继承/覆写的(跟Java不一样), + * 如果允许被子类继承, 类需要使用 open 修饰 + * 如果允许被子类覆写, 方法需要使用 open 修饰 + + // 允许继承 Parent类 + open class Parent { + // 允许子类覆写 bar 方法 + open fun bar() = println("Im bar In Parent") + // 不允许子类覆写 foo 方法 + fun foo() = println("Im Foo In Param") + } + + * 子类对 open 方法进行覆写后(添加 override 关键字), 该覆写方法默认就是 open 的,子类可以对该方法进行覆写 + * 如果需要禁止子类覆写, 可以主动添加 final 修饰符 + open class Sub : Parent() { + final override fun bar() = println("Im bar In Sub") + } + * 通俗理解就是: 没有标注 final 的 override 方法, 就是 open 的,可以被覆写 + +------------------------ +getter/setter | +------------------------ + # 定义 getter/setter + * 跟 js 一个德行,getter 的值是计算得来的 + class User { + var name : String + get() { + return "这个是读取的name属性" + } + set(value: String ){ + println("这个是设置的name属性:$name") + } + } + println(user.name); + + * 语法: + val [属性名称] : [数据类型] get(){ + [计算代码] + return [计算后的返回值] + } + + * 如果仅仅定义 getter, 那么属性的声明必须是: val + * 如果需要定义 setter, 那么属性的声明必须是: var + * 也可以省略 {} ,使用简单的表达式 + val old : Boolean get() = this.age > 25; + + val old get() = this.age > 25; + + * 同理,甚至可以省略数据类型,编译会根据表达式的返回值自动推导出数据类型 + + # getter/setter 也可以使用 private 等权限修饰符来限制访问 + class Foo { + var name : String = "Name Value" + private set + + fun changeName(){ + this.name = "new Name" + } + } + + * name 属性的setter, 被设置为了 private, 外部不能执行 obj.name = "" 来修改数据 + * 可以调用用公共函数来完成数据的修改 + + # 在 setter 方法中,可以使用 field 关键字来访问初始化的值 + class Foo { + var name : String = "747692844" + set (value : String) { + // 设置为初始化的值为新值, 并且执行修改 + field = value + "Hello" + } + } + + +------------------------ +类构造方法 | +------------------------ + # Kotlin 定义了主构造方法 + class User(val nme:String) + + * 这个括号就是主构造方法, 定义了构造函数的参数, 以及使用这些参数初始化实例的属性 + * 可以使用 constructor 关键字, 主动的声明一个主构造方法 + + * 使用 init 关键字, 来引入一个初始化语句块, 对象创建的时候, 会调用 init 语句块的代码 + * init 代码块可以定义多个 + class User1 constructor(name:String){ + val name:String + init { + this.name = name + } + } + * 手动声明 constructor 方法, 定义成员变量 + * 并且使用 init 初始化类成员变量 + + class User2 (name: String){ + val name:String = name + } + * 省略 constructor关键字和init代码块, 直接定义初始化成员变量,然后初始化 + + class User3(val name:String) + * 直接连初始化的代码, 都省略 + * 注意要使用关键字: var / val + + # Kotlin 定义从构造方法 + * 使用 constructor 关键字定义 + + open class Super(val name: String) + class Foo : Super { + // 在从构造方法, 调用父类的构造方法 + constructor(name: String) : super(name){ + } + // 构造方法重载 + constructor() : this("Unknown") + } + + # 构造函数, 也可以使用默认值 + class User3(val name:String ,val gender:String = "男") + + * 如果所有的构造函数参数都有默认值的话, 编译器会生成一个不带参数的构造方法, 来使用所有的默认值 + * 这机制会让Kotlin使用库的时变得简单, 因为可以通过无参构造器来实例化对象 + + # 子类, 需要手动的调用父类构造函数完成初始化 + * 继承类的语法, 就是一个调用(接口不需要括号, 是因为接口不存在构造方法) + open class Super + class Sub: Super() // 父类构造函数, 没有参数, 所以子类也不用任何参数 + + * 调用带有参数的父类构造器 + open class Super(var name:String) + class Sub (name:String): Super(name) + + # 私有化构造函数, 可以阻止外面创建类实例 + class Foo private constructor() {} + + # 我最喜欢的写法, 这种表达要清楚点还是 + open class Super { + private val name:String + constructor(name: String){ + this.name = name + } + } + class User : Super{ + private val name:String + private constructor(name:String): super(name) { + this.name = name + } + constructor(): this("Unknown") + } + +------------------------ +抽象类 | +------------------------ + # 抽象类的定义, 需要在类上添加修饰符 abstract , 类不能 直接实例化 + # 抽象方法, 也需要添加 abstract 关键字 + abstract class Foo { + abstract fun bar() + } + + # 抽象类和抽象方法, 不能用 final 修饰, 必须允许被继承, 被覆写 , 默认就有 open 修饰 + # 抽象类的普通方法, 默认是 final , 不能被覆写, 需要主动声明 open, 才能被覆写 + +------------------------ +内部类 | +------------------------ + # Kotlint的外部类, 不能访问到内部类的 private 属性(与Java不一样) + class Outer { + + class Inner (private val name:String){} + + fun foo (){ + var inner = Inner("KevinBlandy") + // Error:(6, 26) Kotlin: Cannot access 'name': it is private in 'Inner' + var name = inner.name; + } + } + + # 嵌套类, 内部类在Java和Kotlin中的区别 + +-------------------------------+---------------------+---------------------------------------------+ + |类 A 在类 B 中声明 | Java | Kotlin | + +-------------------------------+---------------------+---------------------------------------------+ + |嵌套类(不需要存储外部类的引用) | static class A | class A | + +-------------------------------+---------------------+---------------------------------------------+ + |内部类(需要存储外部类的引用) | class A | inner class A | + +-------------------------------+---------------------+---------------------------------------------+ + + * 通俗理解就是不加 inner 的内部类, 是属于类的,可以通过类名访问到(前提是有足够的访问权限) + * 加了inner 的内部类, 是属于对象的, 需要通过类实例访问到 + class Outer { + class Inner1 // 属性类 + inner class Inner2 // 属于对象 + } + fun main(args:Array) { + var obj1 = Outer.Inner1() //通过类访问 + var obj2 = Outer().Inner2() //通过对象访问 + + } + + * 可以在内部类中获取到外部类的对象引用, 使用: this@[外部类] + class Outer { + inner class Inner { + // 获取到父对象 + fun getOuter() = this@Outer + } + } + fun main(args:Array) { + var outer = Outer() + var inner = outer.Inner() + println(inner.getOuter() === outer) // true + } + +------------------------ +密封类 | +------------------------ + # 使用 sealed 修饰的类, 限制其子类, 必须都嵌套在父类中 + + sealed class Parent() { + class Sub1(): Parent() + class Sub2(): Parent() + } + + * Kotlin 1.1 以前, 子类都必须嵌套定义在父类中 + * Kotlin 1.1 以后修改了限制, 可以在当前文件的任意位置定义 sealed 的子类, 而不是仅仅局限于 sealed 类中 + + # when 表达式, 在对一个 sealed 类实例进行 is 匹配的时候,不需要 else 分支 + sealed class Parent() { + class Sub1(): Parent() + class Sub2(): Parent() + } + fun main(args:Array) { + var obj:Parent = Parent.Sub1() + var result = when (obj){ + is Parent.Sub1 -> "sb1" + is Parent.Sub2 -> "sb2" // 使用is的时候, 必须定义 sealed 类 中的所有实例匹配结果 + } + } + + * 对于 sealed 类实例进行 is 匹配, when 里面的分支, 必须定义其所有的实例 + * 如果没有定义到任何一个, 则会抛出编译异常 + +------------------------ +数据类 | +------------------------ + # 在 class 类上添加: data 关键字, 表示该类是有一个数据类 + + data class Foo (var name: String,val email: String) + + * 数据类, 必须要有属性(没属性,哪里来的数据), 不然编译异常 + + # 所谓的数据类, 就是帮你自动重写了 :equals,hashCode,toString 的一个类 + + hashCode + * 会根据所有的属性,生成一个hash值 + * 没有添加到主构造函数的属性, 不会参与hash计算 + + equals + * 会检测所遇的属性值是否相等 + * 没有添加到主构造函数的属性, 不会参与比较 + + toString + * 按照类声明的顺序, 把所有的属性序列化为字符串 + Foo(name=KevinBlandy, email=747692844@qq.com) + +------------------------ +类委托 | +------------------------ + # 类委托, 就是根据接口, 你指定一个实现, 编译器自动的生成实现调用方法 + # 装饰者设计模式, 就需要把方法处理请求, 委托给一个实现类, 使用委托机制, 可以很方便的实现 + # Kotlin 可以自己完成, 使用 by 关键字 + class MyCollection ( + private val innerList : Collection = ArrayList()) + :Collection by innerList { + } + + * 可以选择性的重写那些需要实现特殊功能的方法 + + # 语法 + class [类] ([实例化接口实现对象]) : [继承接口] by [接口实现对象] + + + # 使用委托, 实现一个带add计数器的Set + class CountSet ( + private val innerSet : MutableCollection = HashSet()) + :MutableCollection by innerSet { + + var count : Int = 0 + private set + + override fun add(element: String): Boolean { + this.count ++ + return this.innerSet.add(element) + } + + override fun addAll(elements: Collection): Boolean { + this.count += elements.size + return this.innerSet.addAll(elements) + } + } + fun main(args:Array) { + var set = CountSet () + set.add("1") + set.add("1") + set.add("1") + println(set.count) + } + +------------------------ +权限修饰符 | +------------------------ + # 类的访问修饰符 + +--------+-------------------------------+------------------------------------------------------------------+ + |修饰符 | | | + +--------+-------------------------------+------------------------------------------------------------------+ + |final |不能被重写 |类中成员默认使用 | + +--------+-------------------------------+------------------------------------------------------------------+ + |oepn |可以被重写 |需要明确的声明 | + +--------+-------------------------------+------------------------------------------------------------------+ + |abstract|必须被重写 |只能在抽象类中使用, 抽象成员不能有实现 | + +--------+-------------------------------+------------------------------------------------------------------+ + |override|重写父类/接口中的方法 |如果没有用 final 修饰, 重写的成员默认是 open 的 | + +--------+-------------------------------+------------------------------------------------------------------+ + + # 可见性修饰符 + * 与Java相似, 可以使用: public, protected, private 修饰符 + + * Kotlin 默认的访问修饰符是: public, 哪里都访问 + * Java默认的访问修饰符是: protected, 同一个包下的类可以访问, 子类可以访问 + + * 但是Kotlin只把包作为命名空间里组织代码的一种方式, 并没用作权限控制 + * Kotlin 使用一个新的关键字: internal ,来限制只能在当前'模块' 中使用 + + * 一个模块, 就是一组一起编译的Kotlin文件 + * 通俗理解就是, 跟我一起编译的代码, 才能访问 internal 修饰的变量 + + +------------+----------------------------------+-------------------------------------------------------------------+ + |修饰符 |类成员 |顶层声明 | + +------------+----------------------------------+-------------------------------------------------------------------+ + |public(默认)|所有地方都可见 |所有地方都可见 | + +------------+----------------------------------+-------------------------------------------------------------------+ + |internal |模块中可见 |模块中可见 | + +------------+----------------------------------+-------------------------------------------------------------------+ + |protected |子类中可见(仅仅子类, 同包都不行) |不能使用 protected 关键字 | + +------------+----------------------------------+-------------------------------------------------------------------+ + |private |类中可见 |当前文件中可见 | + +------------+----------------------------------+-------------------------------------------------------------------+ + + * 高访问权限元素, 不能暴露低权限元素 + internal class Foo() + // 编译异常, 因为 public 的方法, 暴露了 internal 的类 + fun function ():Foo { + return Foo() + } + + \ No newline at end of file diff --git "a/Kotlin/kotlin-\345\256\211\350\243\205.java" "b/Kotlin/kotlin-\345\256\211\350\243\205.java" new file mode 100644 index 00000000..e69de29b diff --git "a/Kotlin/kotlin-\345\257\271\347\251\272\347\261\273\345\236\213\347\232\204\345\244\204\347\220\206.java" "b/Kotlin/kotlin-\345\257\271\347\251\272\347\261\273\345\236\213\347\232\204\345\244\204\347\220\206.java" new file mode 100644 index 00000000..61f6af98 --- /dev/null +++ "b/Kotlin/kotlin-\345\257\271\347\251\272\347\261\273\345\236\213\347\232\204\345\244\204\347\220\206.java" @@ -0,0 +1,222 @@ +-------------------- +null 的处理 | +-------------------- + # 我的理解就是 Kotlin 通过一些手段, 把空指针问题, 从运行阶段, 提升到了编译阶段 + + # Kotlin 中, 默认的所有数据都不能为 null , 赋值null, 或者未赋值都会给出异常 + var x:String = null; // 异常 + + # 需要手动的声明, 该变量可以为 Null + var x:String? = null; + + * dei, 就是在类型后面添加一个问号, 表示该变量可以为 null + + # 如果变量允许 null 值, 那么这会带来一些限制 + * 不能执行调用该变量的方法 + fun foo(value:String?){ + value.length // 直接调用方法, 异常 + } + + * 不能赋值给非空变量 + var x:String? = null; + var y: String = x // 异常 + + fun foo(value:String){} + fun main() { + var x:String? = "" + foo(x) // 异常, 因为方法的形参是非空的 + } + + + * 突破这些限制需要自己通过手动的去判断 null + * 一但进行过判断, 编译器就会记住, 它就会认为是安全的, 就可以执行 + fun foo(value:String?){ + if (value != null){ + value.length + } + } + + var x:String? = null; + if(x != null){ + var y: String = x + } + + fun foo(value:String){} + fun main() { + var x:String? = "" + if (x != null) { + foo(x) + } + } + + + # 安全调用运算符: ?. + * 这个其实就是简化了两个步骤, 1: 判读是否非空, 2: 执行操作 + var value : String? = "" + var result = value?.trim() + println(result) + + * 如果是null,的话那么 ?. 整个表达式的结果为 null + + * 不仅可以安全的调用方法, 还支持安全的访问属性 + class User(val name:String?) + fun main() { + var user = User(null) + var result = user.name?.trim() + println(result) + } + + * 必要时,还可以链接多个安全调用 + class Account(val email:String?) + class User(val account:Account?) + fun main() { + var user = User(null) + // 链式的安全调用 + var result = user.account?.email?.trim() + println(result) + } + + # Eivis运算符: ?: + * 这东西, 会帮你判断是否为null, 如果左边表达式的结果为 null, 那么就赋值为 右边的表达式结果 + var value:String? = null + var result = value ?: "空的" + println(result) // 空的 + + * 可以跟安全运算符配合使用 + * 安全运算符可以在变量为null的时候, 安全的返回 null, Eivis 运算符可以把 null 值设置为默认值 + fun length(value:String?):Int{ + return value?.length ?: 0 + } + + * 也可以利用它,合理的抛出异常, 类似于js: if xx && throw ... + fun length(value:String?):Int{ + return value?.length ?: throw Exception("不能为空") + } + + + # 安全的转换运算符: as ? + * as 其实就是使用了 cast 强制转换, 如果转换失败, 会抛出异常 + * 可以使用安全的 as? 转换运算符, 在不能进行转换的时候, 安全的返回 null + class Account + class User + + fun main() { + // 转换失败, 返回null + var foo: Account? = User() as? Account + println(foo) + } + + * 因为可能会返回null, 所以接收变量也得是可以为 null 的 + + * 可以配合Eivis等运算符, 在 equals 时带来方便 + class User (private val id:Int){ + override fun equals(other: Any?): Boolean { + // 转换失败返回null, null遇到eivis晕算符, 直接返回 false + var otherUser = other as? User ?: return false + return otherUser.id == this.id + } + } + + + # 非空断言: !! + * 作用就是判断变量是否为null, 如果是 null 就抛出异常 + * 这样做的目的是,在你定义断言的时候抛出, 而不是执行的时候抛出, 更好找到异常点 + fun length(value:String?):Int { + // 非空断言, 如果是 null 就抛此处抛出异常, 而不是到下面访问它的属性时才抛出 + var notNullString :String = value!! + return notNullString.length + } + + # let 函数 + * 就是对自己进行判断, 如果是 null, 则返回, 不执行 + * 如果非 null, 则执行 lambda 表达式的代码 + + fun length(value:String):Int { + return value.length + } + fun main() { + var value : String? = null + //?. 安全的访问 value, + var result = value?.let { value -> length(value)} + } + + + # lateinit 延迟初始化 + * lateinit 修饰的变量, 表示可以为null, 需要延迟初始化 + * 如果没初始化访问, 会异常:UninitializedPropertyAccessException + * 这个比空指针好理解 + * 应该可以看出必须使用 var,而不是使用 val + class User { + public lateinit var name : String + } + fun main() { + println(User().name) // UninitializedPropertyAccessException + } + + + # 可空类型的扩展方法 + * 看个方法 + var value:String? = null + // null 对象调用: isNullOrEmpty 居然没异常? + var result = value.isNullOrEmpty() + println(result) // true + + * 其实就是定义扩展属性的时候, 设置了允许调用的对象可以为null + * 但是方法体里面就必须要进行判断了, 因为真的可能为空 + fun String?.foo() : String { + return (this ?: "null") + "foo" + } + + fun main() { + var value:String? = "131" + var result = value.foo() + println(result) + } + + * 在 java 里面 this 永远不会为空 + * 在 Kotlin 里面,扩展方法中的 this 可以为null + + * 扩展方法被编译为静态方法后, this 就是第一个参数而已, 所以可以为null + + # 泛型参数是可空的 + * 方法参数如果不带 ? 表示不能为 null, 如果直接传递 null, 在编译的时候就会给异常 + * 但是泛型方法例外, 泛型参数不带 ?, 调用的时候传递 null, 不会给编译异常 + fun foo( t : T){ + println(t) + } + fun bar (value:String){ + println(value) + } + fun main() { + foo(null) + bar(null) // 编译异常 + } + + * 要使泛型可以在编译时期就判断 null 值的话, 需要给它指定泛型的上限 + + # 和 Java 打交道中的空值处理 + * Kotlin 可以识别到一些 Java 定义的注解: javax.annotation + * 并且自动的转换为 Kotlin 对 null 的表达 + @Nullable + type = type? + + @NotNull + type = type + + + * 在Kotlint中访问 Java 的对象要注意 null 判断 + + * Kotlin 中的可空类型, 不能用 Java 的基本数据类型表示 + * 意味着所有使用了基本数据类型的可空版本, 它就会被编译为对应的包装类型 + + * 覆写 Java 方法的时候, 可以重新定义方法参数是否允非空 + * 覆写 Kotlin 方法的时候, 必须跟父类定义的一样, 父类方法参数允许空, 子类必须允许空 + + + + + # 集合与空 + * 表示集合可以存储空元素 + ArrayList + + * 表示集合可以为null, 而且还可以存储 null 元素 + ArrayList? + diff --git "a/Kotlin/kotlin-\346\240\207\345\207\206\345\207\275\346\225\260\345\272\223.java" "b/Kotlin/kotlin-\346\240\207\345\207\206\345\207\275\346\225\260\345\272\223.java" new file mode 100644 index 00000000..7d2ad0c7 --- /dev/null +++ "b/Kotlin/kotlin-\346\240\207\345\207\206\345\207\275\346\225\260\345\272\223.java" @@ -0,0 +1,18 @@ +---------------------------- +标准函数库 | +---------------------------- + buildString() + * 快速的建立, StringBuilder, 它接收一个 lambda 表达式 + * 它使用 apply, 已经绑定了一个上下文: StringBuilder + var result = buildString { + for (i in 0..9){ + append(i) // 在表达式中可以调用 StringBuilder 的所有方法 + } + } + + println(result) + + * 最后返回 StringBuilder.toString() + + + \ No newline at end of file diff --git "a/Kotlin/kotlin-\346\263\250\350\247\243.java" "b/Kotlin/kotlin-\346\263\250\350\247\243.java" new file mode 100644 index 00000000..f5f21e5d --- /dev/null +++ "b/Kotlin/kotlin-\346\263\250\350\247\243.java" @@ -0,0 +1,5 @@ + +@JvmOverloads + + +@file:JvmName \ No newline at end of file diff --git "a/Kotlin/kotlin-\347\233\256\345\275\225\344\270\216\345\214\205\347\273\223\346\236\204.java" "b/Kotlin/kotlin-\347\233\256\345\275\225\344\270\216\345\214\205\347\273\223\346\236\204.java" new file mode 100644 index 00000000..dc3599b2 --- /dev/null +++ "b/Kotlin/kotlin-\347\233\256\345\275\225\344\270\216\345\214\205\347\273\223\346\236\204.java" @@ -0,0 +1,77 @@ +------------------------- +目录与包结构 | +------------------------- + # 包的声明,跟java一样,不多说 + + # 导入类库 + * Kotlin 对变量的啥的管理单位是 package,也就是说 kt 文件的名称是啥,显得不那么重要 + * Kotlin一个 kt 文件里面可能定义了N多的类,函数,需要使用什么,就导入什么 + + package io.kevinblandy.funcs + fun max(a:Int, b:Int) = if (a > b) a else b; + + import io.kevinblandy.funcs.max + fun main(args:Array){ + println(max(1,2)); + } + + * 相同 package 下的所有 kt 文件中,不能重复定义相同名称的变量,否则异常 + + * 可以使用: * 来导入所有 + + * 当导入的变量与当前包环境的变量名称冲突的时候 + + * 如果是使用 * 导入的变量冲突,则当前包的变量优先级大,反之,则 import 的变量优先级大 + + import io.kevinblandy.funcs.x; + import io.kevinblandy.funcs.*; + var x = "inner"; + var y = "inner"; + fun main(args:Array){ + print("x=$x, y=$y") // x=outer, y=inner + } + + * 可以使用 as 来设置类库/属性的 别名,python一个德行 + + + # 顶层函数与属性 + * Kotiin 编译生成的类的名称, 对应于包含函数的文件的名称, 这个文件中的所有顶层函数编译为这个类的静态函数 + + * 要改变包含 Kotlin 顶层函数的生成的类的名称, 需要为这个文件添加 @file:JvmName 的注解, 将其放到这个文件的开头, 位于包名的前面 + + * 顶层属性也是一样,也是作为类成员变量 + var 声明的属性,会生成 getter/setter 方法: getXxx/setXxx + val 声明的属性,只会生成 getter 方法 方法: getXxx + const val 声明的属性,生成 public static final .... 属性,可以直接访问,不需要方法 + + @file:JvmName("CommonUtils") + package io.kevinblandy.funcs + + var var1 = "var1"; + val var2 = "var2"; + const val VAR3 = "var3"; + + fun foo(){ + println("Hello") + } + + import io.kevinblandy.funcs.CommonUtils; + public class Demo { + + public static void main(String[] args) { + + CommonUtils.foo(); + + // getter/setter + String var1 = CommonUtils.getVar1(); + CommonUtils.setVar1("new value"); + + // getter + String var2 = CommonUtils.getVar2(); + + // 相当于 public static final String VAR3 = "var3" + String var3 = CommonUtils.VAR3; + } + } + + diff --git "a/Kotlin/kotlin-\347\274\226\350\257\221\345\231\250.java" "b/Kotlin/kotlin-\347\274\226\350\257\221\345\231\250.java" new file mode 100644 index 00000000..a8905f96 --- /dev/null +++ "b/Kotlin/kotlin-\347\274\226\350\257\221\345\231\250.java" @@ -0,0 +1,19 @@ +---------------------- +编译器 | +---------------------- + # 安装教程 + https://kotlinlang.org/docs/tutorials/command-line.html + + + +---------------------- +编译器使用 | +---------------------- + kotlinc [file] -include-runtime -d [name] + + file + * 源代码文件或者目录 + + name + * 编译后的目标文件,一般是 .jar 文件 + diff --git "a/Kotlin/kotlin-\351\207\215\350\275\275\350\277\220\347\256\227\347\254\246.java" "b/Kotlin/kotlin-\351\207\215\350\275\275\350\277\220\347\256\227\347\254\246.java" new file mode 100644 index 00000000..8ace384a --- /dev/null +++ "b/Kotlin/kotlin-\351\207\215\350\275\275\350\277\220\347\256\227\347\254\246.java" @@ -0,0 +1,45 @@ +-------------------- +Kotlin 重载运算符 | +-------------------- + # 跟py其实一样 + * 实现了某些固定名称的方法后, 就可以使用特定的运算符号直接运算 + * 这些类型的方法需要添加关键字:operator + * 而且使用该关键字后, 方法的命名一定要符合规范 + * 该方法发返回值就是最终计算的结果 + + # Demo + data class Foo (val num:Int){ + operator fun plus(other:Foo) : Foo{ + return Foo(this.num + other.num) + } + operator fun plus(other:Int) : Int{ + return this.num + other + } + } + + fun main() { + var result = Foo(1) + Foo(2) + println(result) // Foo(num=3) + println(Foo(1) + 5) //6 + } + + # 还可以使用扩展函数来定义 + operator fun Foo.plus(other:Foo) : Foo { + return Foo(this.num + other.num) + } + + # 不支持运算两边的交换, 需要重新定义函数 + * 为 Foo 定义方法 + operator fun plus(other:Int) => Foo(5) + 5 + + * 为Int定义方法 + operator fun plus(other:Foo) => 5 + Foo(5) + + + + # 可重载的函数 + a * b times + a / b div + a % b mod + a + b plus + a - b minus \ No newline at end of file diff --git "a/Kotlin/kotlin-\351\233\206\345\220\210.java" "b/Kotlin/kotlin-\351\233\206\345\220\210.java" new file mode 100644 index 00000000..528bf2e5 --- /dev/null +++ "b/Kotlin/kotlin-\351\233\206\345\220\210.java" @@ -0,0 +1,217 @@ +----------------- +集合 | +----------------- + # 创建集合的函数 + var list = listOf(1,2,3,4) java.util.Arrays$ArrayList + * Arrasys.asList() + * 只读 + + var list = arrayListOf(1,2,3,4); ArrayList + + var set = setOf() + * Collections.singleton() java.util.Collections$SingletonSet + * 只读 + + var set = hashSetOf(1,2,3,4); HashSet + + mapOf(pairs:Pair) LinkedHashMap + * 只读 + + var map = hashMapOf(1 to "1", 2 to "2", 3 to "3") HashMap + + # list/set/map 支持的一些操作(扩展函数) + last(); + min(); + max(); + + withIndex(): Iterable> + * list 数据结构的一个api + * 返回一个迭代器, 迭代对象包含了下标和值 + public data class IndexedValue(public val index: Int, public val value: T) + + filter() + * 过滤, 返回 true 的将会被留下 + arrayListOf(1,2,3,4,5).filter {it % 2 == 0} // [2, 4] + * 如果执行对象是 map, 那么参数就是一个: entry + mapOf("1" to "2").filter { it -> it.key == it.value } + mapOf("1" to "2").filter { (k,v) -> k == v} + + filterValues() + filterKeys() + * map的key 和 value 过滤器 + + filterNotNull() + * 过滤掉所有空null元素 + * 返回的列表不存在 null 元素 + + mapValues() + mapKeys() + * map 结构的消费函数 + mapOf("1" to "2").mapKeys { println("${it.key},${it.value}") } + mapOf("1" to "2").mapValues { println("${it.key},${it.value}") } + + map() + * 一个消费函数 ,py/java都有 + arrayListOf(1,2,3,4,5).map {it * 2} // [2, 4, 6, 8, 10] + + + all() + anly() + * 判断函数, 返回 boolean + * 如果所有都符合条件/或者是任何一个符合条件, 返沪 true + + + find() + * 返回匹配成功的第一个元素 + listOf(1,4,3,4).find {it % 2 ==0} // 4 + + count() + * 统计 ,它也支持过滤, 返回符合条件的数量 + listOf(1,2,3,4).count {it % 2 ==0} // 2 + + groupBy() + * 聚合,返回的结果是一个 map>, 跟 java 的stream一样 + * 把处理结果一样的数据放到一个集合,处理的结果作为key + var result = listOf("a","bb","ccc","d","ee","fff").groupBy { it.length } + println(result) // {1=[a, d], 2=[bb, ee], 3=[ccc, fff]} + + flatMap() + * 把结果合并为一个流 + * 先把每个元素做变换, 然后再合并为一个流 + arrayOf( + Book("Java编程思想", arrayListOf("Kevin","Litch")), + Book("Python编程思想", arrayListOf("Ruby","xjp")), + Book("Javascript编程思想", arrayListOf("Zy","Litch")), + Book("C编程思想", arrayListOf("Kevin","Rocco"))) + + .flatMap { it.authors }.forEach {println(it)} // 把所有的作者信息, 都组合成了一个流 + + + forEachIndex() + * 带下标的遍历, 参数是一个 lambda, 有两个参数: index, element + intArray.forEachIndexed { index, element -> println("$index, $element") } + + + * 这些函数之间可以链式调用, 跟 Java8的 Stream 一样 + + # 更为高效的 asSequence + * 一个有问题的代码 + arrayOf("1","2").map { it.length }.filter { it >= 1 } + + * 执行到 map 的时候, 会创建一个数组 + * 执行到 filer 的时候, 会创建一个数组 + + * 问题在于, 每一次执行都会创建新的数组,如果操作过多, 那么严重影响性能 + + * 可以先把需要操作的元素序列化, 使用方法: asSequence() ,在最后收集结果 + arrayOf("1","2").asSequence().map { it.length }.filter { it >= 1 }.toList() + + * 在中间不会创建任何的集合, 只有在最后收集的时候才会创建 + * 这个就跟Java的Stream一摸一样的, 如果最后没有执行收集操作, 那么中间的流处理也不会执行 + + + * asSequence 就是 Java 的 .stream() + * 这是一个集合类的扩展函数, 这种东西也被成为惰性求值 + + # 创建 sequence + * 之前的 sequence 都是通过集合的 asSequence 来获取, + * 也可以自己去创建, 类似于py的生成器 + var sequence = generateSequence(0) {it + 1} + var result = sequence.takeWhile { it <= 100 }.sum() + println(result) // 5050 + + * generateSequence() 给定一个开始元素, 以及对元素的操作lamdba + + + + + # 其他的一些相关函数 + var pari = Pair(v1,v2) + * 返回一个 pair 对象,一般用于构造 Map 的一个映射 + * 这个对象就俩属性(都是泛型) + public val first: A + public val second: B + + * 该对象可以被解构赋值 + var (key, value) = Pair("name", "KevinBlandy") + println("key=$key, value=$value") + + # Kotlin 和 Java 的集合关系 + Iterable MutableIterable : Iterable + Collection MutableCollection : Collection, MutableIterable + List MutableList : List, MutableCollection + Set MutableSet : Set, MutableCollection + + ArrayList + HashSet + + * 使用 Mutable... 开头的表示可以修改数据的的接口 + * Java类 ArrayList,HashSet 都继承了 Kotlin的可变接口 + + # 集合创建函数 + +---------------------------------------------------------------------------------------------- + |集合类型 不可变 可变 + |List listOf mutableListOf(),arrayListOf() + |Set setOf mutableSetOf(),hashSetOf(),linkedSetOf(),sortedSetOf() + |Map mapOf mutableMapOf(),hashMapOf(),linkedMapOf(),sortedMapOf() + +---------------------------------------------------------------------------------------------- + + +--------------------- +可变集合和只读集合 | +--------------------- + # 只读集合接口 + kotlin.collections.Collection + + # 可变集合接口(可以添加修改) + kotlin.collections.MutableCollection + + * 它继承自接口:kotlin.collections.Collection + * 添加了 add / remove 等方法 + + # 把集合转换为数组 + toTypedArray(): Array + + + # 基本数据类型的集合 + * 为了表示基本数据类型的数组, Kotlin 提供了N多个独立的类 + * 每种数据类型都对应一个 + IntArray + BooleanArray + ... + + * 这些都会被编译为: int[], char[].... + + * 直接创建, 一般在参数指定数组的长度 + * 因为是基本数据类型, 所以有默认值 + IntArray(size: Int) + + * 构造函数还可以添加一个 lambda 参数, 用于初始化数组成员 + * 会把每个角标, 传递给诶 lambda, 并且把返回值当作数组的元素 + var arr = IntArray(10){i -> i + 1} + println(arr.joinToString(",")) // 1,2,3,4,5,6,7,8,9,10 + + + * 使用工厂函数创建 + intArrayOf(1,2,3,4,6) + + + * 包装类型的数据集合, 可以通过 toXxxArray(), 转换为基本数据类型的集合 + var array:ArrayList = arrayListOf(1,2,3) + var intArray = array.toIntArray() + +--------------------- +数组 | +--------------------- + # Kotlin的 Array 就是 数组 + * 支持使用下标来访问元元素 + var list:List = arrayListOf("1","3") + var value = list[1] + + * 越界会有异常 + + + # Array 的构造方法, 可以接收一个数组的大小, 和 lambda 表达式 + * 数组会使用 lmabda 初始化这个数组, 参数就是当前的角标 + var letters = Array(10) { i:Int -> (i + 1).toString()} + println(letters.joinToString(",")) // 1,2,3,4,5,6,7,8,9,10 diff --git a/Kotlin/kotlin.java b/Kotlin/kotlin.java new file mode 100644 index 00000000..a08e6f55 --- /dev/null +++ b/Kotlin/kotlin.java @@ -0,0 +1,34 @@ +------------------------ +Kotlin | +------------------------ + # 相关网站 + https://kotlinlang.org/ + https://kotlinlang.org/docs/reference/ + https://www.kotlincn.net/ + + + # 一个德行 + * 局部函数跟 js/py 一样 + * 解构赋值,[]展开跟 py 一样 + * getter/setter 跟js差不多 + * raw 字符串, 跟py一样 + * object, 跟js的字面量对象差不多 + * with/apply 跟 js 一样 + * 重载运算符, 跟py 一样 + * 协程,Py有,js也有 Promise + * 高阶函数,其实就是js/py一样, 方法也是一个变量 + * use 函数跟 py 的 with ... as .. 一样 +------------------------ +Kotlin - 征途 | +------------------------ + + # 为什么在方法内部扩展属性会编译异常, 但是在方法内部扩展方法就没事儿 + val String.lastChar get() = this[this.length - 1]; // 方法外部扩展属性, 正常 + fun main(){ + // val String.lastChar get() = this[this.length - 1]; // 方法内部扩展属性, 异常 + println("123".lastChar) + + fun String.foo(value: String) = this + "_" + value // 方法内部扩展方法, 正常 + println("123".foo("321")) + } + diff --git a/Kotlin/practice/SocketServer.java b/Kotlin/practice/SocketServer.java new file mode 100644 index 00000000..38f2c544 --- /dev/null +++ b/Kotlin/practice/SocketServer.java @@ -0,0 +1,142 @@ +import java.io.Serializable +import java.lang.Exception +import java.net.InetSocketAddress +import java.net.StandardSocketOptions +import java.nio.ByteBuffer +import java.nio.channels.SelectionKey +import java.nio.channels.Selector +import java.nio.channels.ServerSocketChannel +import java.nio.channels.SocketChannel +import java.nio.charset.StandardCharsets + +class Message : Serializable { + val header : ByteBuffer = ByteBuffer.allocate(4) + private var content : ByteBuffer? = null + constructor(){} + fun getContent():ByteBuffer?{ + return this.content + } + fun setContent(content: ByteBuffer?) { + this.content = content + } +} + +fun messageEncode (data:String):ByteBuffer { + var byteBuffer = ByteBuffer.allocate(4 + data.length) + byteBuffer.putInt(data.length) + byteBuffer.put(data.toByteArray(StandardCharsets.UTF_8)) + byteBuffer.flip() + return byteBuffer +} + +fun main(){ + + val selector = Selector.open() + + val serverSocketChannel = ServerSocketChannel.open() + serverSocketChannel.bind(InetSocketAddress("0.0.0.0", 1024)) + serverSocketChannel.configureBlocking(false) + serverSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true) + + serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT) + + while (selector.select() > 0){ + + val selectionKeyIterator = selector.selectedKeys().iterator() + + while(selectionKeyIterator.hasNext()){ + + val selectionKey = selectionKeyIterator.next() + + try{ + when { + selectionKey.isAcceptable -> { + val socketChannel = (selectionKey.channel() as ServerSocketChannel).accept() + socketChannel.configureBlocking(false) + socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true) + + socketChannel.register(selector, SelectionKey.OP_READ or SelectionKey.OP_WRITE, Message()) + } + selectionKey.isReadable -> { + + val socketChannel = selectionKey.channel() as SocketChannel + + var message = selectionKey.attachment() as Message; + + if (message.getContent() === null){ + var result = socketChannel.read(message.header) + if (result == -1){ + println("连接断开:address=${socketChannel.remoteAddress}") + //TODO 广播退出,会话移除 + } + if (!message.header.hasRemaining()){ + var length = message.header.getInt(0) + if (length > Integer.MAX_VALUE){ + socketChannel.write(messageEncode("消息大小不能超过:${Integer.MAX_VALUE}")) + socketChannel.close() + // TODO 广播退出,会话移除 + } + message.setContent(ByteBuffer.allocate(length)) + } + }else{ + var result = socketChannel.read(message.getContent()) + if (result == -1){ + println("连接断开:address=${socketChannel.remoteAddress}") + //TODO 广播退出,会话移除 + } + if(!message.getContent()!!.hasRemaining()){ + var content = String(message.getContent()!!.array(),StandardCharsets.UTF_8) + message.setContent(null) + message.header.clear() + // TODO 广播消息 + println("收到消息:$content") + } + } + } + selectionKey.isWritable -> { + + } + selectionKey.isConnectable -> { + + } + } + }catch (e : Exception){ + e.printStackTrace() + } + finally { + selectionKeyIterator.remove() + } + } + } +} + + + + +import java.io.BufferedOutputStream +import java.net.InetSocketAddress +import java.net.Socket +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets + +class Reading : Runnable { + private var socket : Socket + constructor(socket:Socket){ + this.socket = socket + } + override fun run() { + var inputStream = this.socket.getInputStream() + } +} + +fun main() { + var socket = Socket() + socket.connect(InetSocketAddress("127.0.0.1", 1024)) + var bufferedOutputStream = BufferedOutputStream(socket.getOutputStream()) + var data = "卧槽。".toByteArray(StandardCharsets.UTF_8) + var byteBuffer = ByteBuffer.allocate(4 + data.size) + byteBuffer.putInt(data.size) + byteBuffer.put(data) + bufferedOutputStream.write(byteBuffer.array()) + bufferedOutputStream.flush() +} \ No newline at end of file diff --git "a/Kotlin/practice/when\347\232\204\347\273\203\344\271\240.java" "b/Kotlin/practice/when\347\232\204\347\273\203\344\271\240.java" new file mode 100644 index 00000000..30e33f3e --- /dev/null +++ "b/Kotlin/practice/when\347\232\204\347\273\203\344\271\240.java" @@ -0,0 +1,36 @@ + +// 使用is的自动转换类型,配合 when 计算和 + +import java.lang.Exception + +interface Expires + +class Num(val number:Int):Expires +class Sum(val left:Num, val right:Num):Expires + +fun eval(expires: Expires): Int { + return when (expires) { + is Num -> expires.number + is Sum -> eval(expires.left) + eval(expires.right) + else -> throw Exception(""); + } +} + +fun main(args:Array){ + var value = eval(Sum(Num(1), Num(eval(Sum(Num(1),Num(5)))))); + println(value) +} + + +// 编译异常的表达式 +fun foo(expires: Expires) = when (expires) { + is Num -> expires.number + // foo(expires.left) 异常 + /* + 为啥? 是因为递归的时候, 无法确定上层栈返回的数据类型么??? + */ + is Sum -> foo(expires.left) + foo(expires.right) + else -> throw Exception("") + } +} + diff --git a/Log4j/logback-config.xml b/Log4j/logback-config.xml new file mode 100644 index 00000000..bf6f9803 --- /dev/null +++ b/Log4j/logback-config.xml @@ -0,0 +1,33 @@ + + + + + + + ${PATTERN} + + + + + ${LOG_FILE} + + + 100MB + + %d{yyyy-MM-dd}-%i.zip + + 60 + + 20GB + + + ${PATTERN} + + + + + + + + + \ No newline at end of file diff --git a/Log4j/logback.java b/Log4j/logback.java index 7b215a31..8093e95e 100644 --- a/Log4j/logback.java +++ b/Log4j/logback.java @@ -4,14 +4,33 @@ # log4j作者的又一个力作 # logback当前分成三个模块: logback-core - logback- classic + logback-classic logback-access - * logback-core是其它两个模块的基础模块。 - * logback-classic是log4j的一个 改良版本。 - * logback-classic完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。 - * logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。 + * logback-core 是其它两个模块的基础模块。 + * logback-classic 完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。是log4j的一个 改良版本。 + * logback-access 访问模块与Servlet容器集成提供通过Http来访问日志的功能。 - # maven 依赖 + # 普通项目 + * 依赖 + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-access + + * classpath下添加配置文件: logback.xml + + # spring 项目 diff --git "a/Maven/maven-Linux\346\220\255\345\273\272.java" "b/Maven/maven-Linux\346\220\255\345\273\272.java" index 5e6f3b3e..0cdfcd1e 100644 --- "a/Maven/maven-Linux\346\220\255\345\273\272.java" +++ "b/Maven/maven-Linux\346\220\255\345\273\272.java" @@ -10,8 +10,8 @@ 3,编辑环境变量文件 vim /etc/profile - export MAVEN_HOME=/usr/local/maven/apache-maven-3.3.9 - export PATH=$PATH:$MAVEN_HOME/bin +export MAVEN_HOME=/usr/local/maven/apache-maven-3.5.4 +export PATH=$PATH:$MAVEN_HOME/bin 4,source profile source /etc/profile \ No newline at end of file diff --git "a/Maven/maven-\344\270\212\344\274\240\344\276\235\350\265\226\345\210\260\344\270\255\345\244\256\344\273\223\345\272\223.java" "b/Maven/maven-\344\270\212\344\274\240\344\276\235\350\265\226\345\210\260\344\270\255\345\244\256\344\273\223\345\272\223.java" new file mode 100644 index 00000000..eff42e05 --- /dev/null +++ "b/Maven/maven-\344\270\212\344\274\240\344\276\235\350\265\226\345\210\260\344\270\255\345\244\256\344\273\223\345\272\223.java" @@ -0,0 +1,21 @@ +------------------------- +上传依赖到maven中央仓库 | +------------------------- + # 参考 + https://www.sojson.com/blog/250.html + + # 关键地址 + 1,后台登录 + https://issues.sonatype.org/secure/Dashboard.jspa + + * 申请发布构件资格 + + 2,构件发布 + https://oss.sonatype.org/#welcome + + * 构件仓库,把jar包上传到这里,Release 之后就会同步到maven中央仓库 + + 3,中央仓库 + http://search.maven.org/ + + * 构件发布成功后,就可以在这里检索到 \ No newline at end of file diff --git "a/Maven/maven-\344\276\235\350\265\226.java" "b/Maven/maven-\344\276\235\350\265\226.java" index 76764e33..7cf60d59 100644 --- "a/Maven/maven-\344\276\235\350\265\226.java" +++ "b/Maven/maven-\344\276\235\350\265\226.java" @@ -113,4 +113,67 @@ -排除依赖 \ No newline at end of file +------------------------ +2,本地依赖 | +------------------------ + 1,在项目路径下新建 lib 目录(用于存放第三方依赖) + application + lib + src/main/... + src/test/... + + 2,添加依赖 + + icbcClient + icbcClient + 1.0 + system + ${basedir}/lib/icbcClient-1.0.jar + + + 3,添加资源打包配置 + * system 默认不会打包到jar + + + ${basedir}/lib + BOOT-INF/lib/ + + **/*.jar **/ + + + + src/main/resources + + **/* **/ + + + + * springboot项目打包本地资源配置 + + org.springframework.boot + spring-boot-maven-plugin + + + true + + + + + 4,war打包方式的本地lib + + org.apache.maven.plugins + maven-war-plugin + 3.0.0 + + + + ${project.basedir}/src/main/resources/lib/net/pusuo + WEB-INF/lib + false + + **/*.jar + + + + + diff --git "a/Maven/maven-\345\233\275\345\206\205\351\225\234\345\203\217.java" "b/Maven/maven-\345\233\275\345\206\205\351\225\234\345\203\217.java" new file mode 100644 index 00000000..e95f660d --- /dev/null +++ "b/Maven/maven-\345\233\275\345\206\205\351\225\234\345\203\217.java" @@ -0,0 +1,7 @@ + +# 华为云 + + huaweicloud + * + https://mirrors.huaweicloud.com/repository/maven/ + \ No newline at end of file diff --git "a/Maven/maven-\346\211\223\345\214\205\345\217\257\346\211\247\350\241\214jar.java" "b/Maven/maven-\346\211\223\345\214\205\345\217\257\346\211\247\350\241\214jar.java" new file mode 100644 index 00000000..668b511d --- /dev/null +++ "b/Maven/maven-\346\211\223\345\214\205\345\217\257\346\211\247\350\241\214jar.java" @@ -0,0 +1,55 @@ + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + target/classes/ + + + + io.javaweb.cas.Main + + false + + true + + lib/ + + + + . + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.1 + + + copy-dependencies + package + + + copy-dependencies + + + + jar + jar + + + ${project.build.directory}/lib + + + + + + + \ No newline at end of file diff --git "a/Maven/maven-\346\217\222\344\273\266-assembly.java" "b/Maven/maven-\346\217\222\344\273\266-assembly.java" new file mode 100644 index 00000000..e3ab959b --- /dev/null +++ "b/Maven/maven-\346\217\222\344\273\266-assembly.java" @@ -0,0 +1,31 @@ +-------------------- +打包可执行jar | +-------------------- + * 这种方式会把依赖的类。。。复制出来,复制到jar里面复制和自己的类一起 + * 打包后,会在目录下生成:xxxx-1.0.0-SNAPSHOT-jar-with-dependencies.jar 文件 + + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.0 + + + jar-with-dependencies + + + + + io.javaweb.excute.Main + + + + + + make-assembly + package + + single + + + + \ No newline at end of file diff --git "a/Maven/maven-\346\217\222\344\273\266-jetty.java" "b/Maven/maven-\346\217\222\344\273\266-jetty.java" new file mode 100644 index 00000000..11b1b86f --- /dev/null +++ "b/Maven/maven-\346\217\222\344\273\266-jetty.java" @@ -0,0 +1,18 @@ +-------------------------------- +Jetty 插件 | +-------------------------------- + + org.eclipse.jetty + jetty-maven-plugin + 9.4.14.v20181114 + + + + 80 + + + + /foo + + + \ No newline at end of file diff --git "a/Maven/maven-\346\217\222\344\273\266.java" "b/Maven/maven-\346\217\222\344\273\266.java" index b89d5f65..9a675d68 100644 --- "a/Maven/maven-\346\217\222\344\273\266.java" +++ "b/Maven/maven-\346\217\222\344\273\266.java" @@ -6,12 +6,43 @@ maven-compiler-plugin 3.7.0 - 1.8/source> + + + -parameters + + 1.8 1.8 UTF-8 +------------------------------- +资源处理插件 | +------------------------------- + + org.apache.maven.plugins + maven-resources-plugin + 3.1.0 + + +# 使用maven中的变量,替换掉配置文件的变量 + * 在打包时,使用pom里面的变量来替换配置文件中的变量 + * pom配置 + Kevin + ... + + target/classes + false + + $ + + + + * yum配置 + name: $foo.name$ + + * 只要是pom里面能使用的变量,都可以替换 + ------------------------------- 远程部署插件 | ------------------------------- @@ -111,3 +142,62 @@ The following web applications were stopped (reloaded, undeployed), but their # 可以删除ROOT项目 + + + +------------------------------- +自动添加版权声明 | +------------------------------- + + com.mycila.maven-license-plugin + maven-license-plugin + ${maven-license-plugin.version} + + ${basedir} +
src/main/resources/etc/header.txt
+ false + true + true + true + + **/src/*/java/**/*.java + **/src/*/webapp/js/*.js + **/src/*/webapp/css/*.css + **/src/*/webapp/scss/*.scss + **/src/*/resources/*.properties + **/src/*/webapp/WEB-INF/*.xml + **/src/*/webapp/*.xml + gulpfile.js + + + **/src/main/java/**/package-info.java + **/src/main/java/**/Pangu.java + **/src/*/webapp/js/lib/*.js + **/src/*/webapp/js/*.min.js + **/src/*/webapp/css/*.css + + + true + + SLASHSTAR_STYLE + SLASHSTAR_STYLE + + true + + 2012-2018 + b3log.org & hacpai.com + + UTF-8 +
+ + + +
\ No newline at end of file diff --git "a/Maven/maven-\350\201\232\345\220\210&\347\273\247\346\211\277.java" "b/Maven/maven-\350\201\232\345\220\210&\347\273\247\346\211\277.java" index 7c59d394..4df2f9cc 100644 --- "a/Maven/maven-\350\201\232\345\220\210&\347\273\247\346\211\277.java" +++ "b/Maven/maven-\350\201\232\345\220\210&\347\273\247\346\211\277.java" @@ -50,4 +50,28 @@ * 继承和聚合,可以放一起!超级整合 -* 既是做聚合的,也是做继承的 \ No newline at end of file +* 既是做聚合的,也是做继承的 + + +------------------------ +3,多继承 | +------------------------ + + + + + com.cbm.stu + maven-parent-a + 1.0.0 + pom + import + + + com.cbm.stu + maven-parent-b + 1.0.0 + pom + import + + + \ No newline at end of file diff --git "a/Maven/maven-\351\205\215\347\275\256\344\270\255\347\232\204\351\242\204\345\256\232\344\271\211\345\261\236\346\200\247.java" "b/Maven/maven-\351\205\215\347\275\256\344\270\255\347\232\204\351\242\204\345\256\232\344\271\211\345\261\236\346\200\247.java" new file mode 100644 index 00000000..4d120863 --- /dev/null +++ "b/Maven/maven-\351\205\215\347\275\256\344\270\255\347\232\204\351\242\204\345\256\232\344\271\211\345\261\236\346\200\247.java" @@ -0,0 +1,6 @@ + +# 读取环境变量的属性 + ${env.JAVA_HOME} + + * 通过 env. 前缀来读取系统环境变量的属性 + diff --git "a/Mongodb/mongodb-core-\346\225\260\346\215\256\345\272\223.java" "b/Mongodb/mongodb-core-\346\225\260\346\215\256\345\272\223.java" new file mode 100644 index 00000000..0bff401c --- /dev/null +++ "b/Mongodb/mongodb-core-\346\225\260\346\215\256\345\272\223.java" @@ -0,0 +1,49 @@ +------------------------- +数据库 | +------------------------- + # 一个mongodb中可以建立多个数据库 + + # MongoDB的默认数据库为"db",该数据库存储在data目录中 + + # MongoDB的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中 + + # 数据库名称的约束(UTF-8字符串) + * 不能是空字符串("") + * 不得含有' '(空格)、.、$、/、\和\0 (空字符) + * 应全部小写 + * 最多64字节 + + # 有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库 + admin + * 从权限的角度来看,这是"root"数据库 + * 要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限 + * 一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器 + + local + * 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合 + + config + * 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息 + + # 默认的数据库为 test,如果没有创建新的数据库,集合将存放在 test 数据库中 + +------------------------- +命令 | +------------------------- + show dbs + * 显示所有数据的列表 + * 如果数据库没有数据(空的),不会显示 + + db + * 显示当前数据库对象或集合 + + use + * 切换到指定的数据库 + * 如果数据库不存在,则创建数据库 + + db.dropDatabase() + * 删除当前数据库 + + + + diff --git "a/Mongodb/mongodb-core-\346\225\260\346\215\256\347\261\273\345\236\213.java" "b/Mongodb/mongodb-core-\346\225\260\346\215\256\347\261\273\345\236\213.java" new file mode 100644 index 00000000..efb53acf --- /dev/null +++ "b/Mongodb/mongodb-core-\346\225\260\346\215\256\347\261\273\345\236\213.java" @@ -0,0 +1,42 @@ +------------------------------------ +数据类型 | +------------------------------------ + # Document 其实就就是json + + # JSON本身就包含了6种数据类型 + "" + 1 + [] + {} + null + boolean + + # Mongo基于JSON扩展了一些数据类型 + * 日期(以时间戳存储) + {"date":new Date()} + + * 正则 + {"reg":/foobar/i} + + * 对象id(ObjectId) + {"_id":ObjectId()} + + * 不同的机器都能生成唯一的id(UUID??),在分布式环境中不会修改 + * ObjectId 使用12个字节的存储空间,使用24个字符长度的16进制字符串表示 + 4 个字节:时间戳(单位为秒) + 3 个字节: 机器id(通常是主机名的hash值) + 2 个字节: pid(进程的标识符id) + 3 个字节: 计数器(自动递增的计数器,确保同一个进程同时不会产生id冲突) + + + * 二进制数据,不能直接在shell中使用 + + * 代码 + { + "func":function(){ + /* */ + } + } + + + diff --git "a/Mongodb/mongodb-core-\346\226\207\346\241\243.java" "b/Mongodb/mongodb-core-\346\226\207\346\241\243.java" new file mode 100644 index 00000000..d9fd0677 --- /dev/null +++ "b/Mongodb/mongodb-core-\346\226\207\346\241\243.java" @@ -0,0 +1,310 @@ +-------------------------------- +文档 | +-------------------------------- + # 文档的key都是字符串 + + # 文档中不能重复名称的 key + + # 文档必须要有一个 _id 属性 + * 该属性值可以是任何数据类型,如果没有设置,那么系统会自动插入,默认值是 ObjectId 对象 + * 每个集合中,文档的 _id 不能重复 + + # 单个文档的最大值为: 54122 kb + * 可以通过Object的bsonsize方法查看db的文档大小约束 + + Object.bsonsize(db.doc); + +-------------------------------- +基本命令 | +-------------------------------- + db.[colelction].insert([document]) + * 往指定的colelction插入一条或者多条记录 + * 如果是多条记录,那么参数应该是一个包含了多个document的数组 + * 如果该集合不在该数据库中, MongoDB 会自动创建该集合并插入文档 + + db.[colelction].insertOne([document]) + * 插入一条记录,参数是一个document + + db.[colelction].insertMany([document...]) + * 插入多条记录,参数是一个document数组 + db.collection.insertMany([{"b": 3}, {'c': 4}]) + + * 如果其中的一个文档插入失败(_id重复之类的异常),那么从该异常文档,及其以后的文档都会插入失败 + * 批量插入的最大消息体积默认为 48MB,如果超过该值,则会切割为多个 48MB的消息的批量插入 + + + + db.[colelction].save([document]) + * 如果不指定 _id 字段 save() 方法类似于 insert() 方法 + * 如果指定 _id 字段,则会更新该 _id 的数据而不是插入 + + + db.[colelction].find() + * 查看指定collection中已插入文档 + + db.[colelction].findOne() + * 查看指定collection中已插入文档 + * 仅仅检索一个结果 + + db.[colelction].remove([document]); + * 删除记录,使用一个文档参数作为条件,仅仅删除符合条件的记录 + * 如果给一个空对象({}),则删除所有的记录 + * 返回删除的数量 + { "nRemoved" : 2 } + * 删除操作不可逆,千万要慎重 + * 该api其实已经过时了 + * remove() 执行删除后,并不会释放空间,需要 执行 db.repairDatabase() 来回收磁盘空间 + + db.[colelction].deleteOne() + * 删除符合条件的第一个文档 + + db.[colelction].deleteMany() + * 删除符合条件的所有文档 + + + +-------------------------------- +更新 | +-------------------------------- + + # 更新的函数,需要两个必须的参数,以及一个非必须的 config 配置项 + * 一个是负责过滤的条件,如果它是一个空对象,表示匹配所有文档 + * 一个是要更新的操作,会彻底替换掉符合条件的记录(并不是更新非空的字段) + + db.[colelction].update([query], [update], { + upsert: true, + multi: false, + writeConcern: + }) + + query + * update的查询条件,类似sql update查询内where后面的 + + update + * update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内后面的SET + + * config的配置选项 + upsert + * 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true 为插入 + * 该操作而且是原子性的,不会存在数据不一致的问题 + * 默认是 false 不插入 + + + multi + * 仅仅更新符合条件的第一个,还是更新所有 + * 它如果是 true,只能用在修改器 $ 修改操作上 + + writeConcern + * 抛出异常的级别 + + * 更新操作是一个原子操作,并发执行更新的时候,谁的请求先到达,谁就先执行,后面的更新会覆盖前面的更新 + + + # 修改器,modifter + * 通常文档只有一部分会更新(并不是更新所有的字段) + * 修改器的其实就是一系列的指令,使用 $ 开头 + * 指令的表达式,大都可以使用对象导航的方式来判断,或者操作 + * 对象导航 + {'sub':{'name':'KevinBlandy'}} sub.name + + * 数组导航(使用下标) + {'sub':[{'name':'KevinBlandy'}]} sub.0.name + + $set + * 用来设置一个字段的值,如果该字段不存在,则创建 + * 它还可以修改value的数据类型,原始的类型是字符串,可以被修改为[],{}..... + db.users.update({'id':'1'},{ + '$set':{ + 'name':'new New', // 设置name属性 + 'foo':'new field' // 设置foo属性 + } + },{ + multi:true // 修改多行 + }); + + * 它支持使用对象导航的形式去修改记录 + db.users.insert({ + 'id':1, + 'name':'KevinBlandy', + 'skill':{ + 'foo':'foo', + 'bar':'bar' + } + }); + + db.users.update({'id':1},{ + '$set':{ + 'skill.foo': 'New Foo Value' // 修改文档的 skill属性值的foo属性值 + } + }); + + $unset + * 它用来删除文档的field + db.users.update({'id':'1'},{ + '$unset':{ + 'foo': 1 // 删除所有文档的 foo 属性 + } + },{ + multi:true + }); + + + $inc + * 对数值属性进行加减,如果记录不存在,则会创建(值就会设置为要修改的值) + * 它只能用在 数值 类型的字段上,如果是其他的数据类型会操作失败 + * 该指令的值也只能是数值,不能是其他的 + db.users.insert({ + 'id':2, + 'number':1, + }); + + db.users.update({'id':2,},{ + '$inc':{ + 'number': 1 // 对文档的number属性 + 1 + } + }); + + $push + * 数组修改器,可以往数组里面 push 元素 + * 如果数组field不存在,就新创建,再push + db.users.insert({ + 'id':3, + 'skills':['Java','Python'], + }); + + db.users.update({'id':3,},{ + '$push':{ + 'skills': 'Javascript' + } + }); + * 该指令一次性,只能push一条记录,如果你的value是数组,则会把整个数组当作一个value push到文档的目标属性值数组里面 + * 如果需要一次性插入多个元素,可以配合 $each 指令 + db.users.update({'id':3,},{ + '$push':{ + 'skills':{ + '$each':['Javascript', 'Ruby', 'C++'] // 使用 each 指令,往 skills里面push多个元素 + } + } + }); + + * 可以使用 $slice 限制文档数组字段的长度 + * $slice 必须是一个负整数,表示仅仅保留最后的几个数据 + db.users.update({'id':3,},{ + '$push':{ + 'skills':{ + '$each':['Javascript', 'Ruby', 'C++'], // 插入3个元素 + '$slice': -2 // 仅仅保留最后两个,所以, skills 字段最后的结果就是 ['Ruby', 'C++'] + } + } + }); + + * 可以使用 $ort 完成排序,配合 $slice指令,就可以对数组先排序,再限制长度 + db.users.update({'id':3,},{ + '$push':{ + 'skills':{ + '$each':['Javascript', 'Ruby', 'C++'], + '$slice': -1, // 仅仅保留最后的一个元素 + '$sort':{'rating': -1} // 先按照排序策略进行排序 + } + } + }); + + + $ne + * 该指令可以判断指定的元素,是否在指定的数组中存在 + * 它适合放在update指令的过滤条件对象中 + db.users.insertMany([{ + 'skills':['Java','Python'], + },{ + 'skills':['Java','Ruby'], + }]); + + db.users.update({'skills':{ + '$ne':'Ruby' //过滤条件: Ruby不存在于skill字段中的文档 + }},{ + '$push':{ + 'skills':'Ruby' // push Ruby 到 skills字段中 + } + }); + + $addToSet + * 把数组[]当作Set使用的指令 + db.users.update({},{ + '$addToSet':{ + 'skills':'Ruby' //如果文档的skills里面不存在Ruby,就push,如果存在,不做任何处理 + } + }); + + * 配合 $each 指令,可以 addSet 多个值 + db.users.update({},{ + '$addToSet':{ + 'skills':{ + '$each':['Java','C++','Javascript'] // skills字段里面如果不存在,就会添加,存在就啥也不作 + } + } + },{ + multi:true + }); + + + $pop + * 该指令用于从数组中弹出一个元素,可以是从头,也可以从尾 + db.users.update({},{ + '$pop':{ + 'skills': -1 // -1 表示从头开始删除(0), 1 表示从尾部开始删除(length - 1) + } + },{ + multi:true + }); + + $pull + * 该指令用于删除数组中的指定元素 + * 它会把匹配到的数组元素都从数组里面删除 + db.users.update({},{ + '$pull':{ + 'skills': 'Ruby' // 删除skills字段中的Ruby元素 + } + },{ + multi:true + }); + + + + + $ + * 定位操作符,可以通过该指令找到数组元素中匹配的元素 + db.users.insertMany([{ + '_id':1, + 'name':'KevinBlandy', + 'skills':[{ + 'lang':'Java', + 'proficiency':10 + },{ + 'lang':'Python', + 'proficiency':8 + },{ + 'lang':'Javascript', + 'proficiency':8 + },{ + 'lang':'C', + 'proficiency':5 + }], + }]); + + db.users.update({'skills.lang':'C'},{ // (1) + '$inc':{ + 'skills.$.proficiency': 1 // (2) + } + },{ + multi:true + }); + + (1) 从 'skills' 的数组里面,匹配出 'lang' 属性值为 'C' 的元素,并且记录它的下标为 '$' + (2) 'skills.$.proficiency', 对'skills'数组里面指定元素的 'proficiency' 值进行加1操作, '$' 就是条件中检索到的下标 + + + $setOnInsert + +-------------------------------- +FindAndModify | +-------------------------------- diff --git "a/Mongodb/mongodb-core-\351\233\206\345\220\210.java" "b/Mongodb/mongodb-core-\351\233\206\345\220\210.java" new file mode 100644 index 00000000..d1c2d60e --- /dev/null +++ "b/Mongodb/mongodb-core-\351\233\206\345\220\210.java" @@ -0,0 +1,56 @@ +---------------------------- +集合 | +---------------------------- + # 合法的集合名称 + * 集合名不能是空字符串""。 + * 集合名不能含有\0字符(空字符),这个字符表示集合名的结尾 + * 集合名不能以"system."开头,这是为系统集合保留的前缀 + + * 用户创建的集合名字不能含有保留字符 + * 有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符,除非你要访问这种系统创建的集合,否则千万不要在名字里出现:$ + + # 可以在集合名称上使用.来设置命名空间 + blog.posts 博客的帖子集合 + blog.user 博客的用户集合 + + + # 同一个集合中的文档结构,可以不同 + * 但是还是建议把有相同结构的document放置在同一个集合中 + + # 集合只有在内容插入后才会创建 + * 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建 + +---------------------------- +命令 | +---------------------------- + db.createCollection("[name]", [config]) + * 在当前db上创建一个指定名称的集合 + * config 表示一个JSON配置项 + capped:true + * 如果为 true,则创建固定集合,固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档 + * 当该值为 true 时,必须指定 size 参数 + + size:100000 + * 为固定集合指定一个最大值(以字节计) + * 如果 capped 为 true 也需要指定该字段 + + autoIndexId + * 如为 true,自动在 _id 字段创建索引,默认为 false + + max + * 指定固定集合中包含文档的最大数量 + + * 在插入文档时,MongoDB 首先检查固定集合的 size 字段,然后检查 max 字段 + + show collections + * 查看当前db中的所有集合 + + db.[collection].drop() + * 删除当前DB中指定名称的集合 + * 成功返回 true, 失败返回 false + + db.[collection].count(); + * 返回集合中的文档数量 + + + diff --git a/Mongodb/mongodb-shell.java b/Mongodb/mongodb-shell.java new file mode 100644 index 00000000..33c9f77f --- /dev/null +++ b/Mongodb/mongodb-shell.java @@ -0,0 +1,160 @@ +------------------------------ +Shell | +------------------------------ + # 启动shell并且连接到远程的服务器 + mongo [host]:[port]/[db] + + * 如果 db 不存在,会创建 + + --nodb + * 启动时,不连接任何的mongodb + --norc + * 启动时不执行 .mongorc.js 脚本 + + # 直接'执行函数'名称,可以看到函数的实现源码 + + db.createCollection; + + function (name, opt) { + ... + } + + # 直接执行外部的js脚本 + mango [file0.js] [file1.js] [file2.js] + + * shell会依次执行js然后退出 + * 可以使用的指令 + --quiet [host]:[port]/[db] + + * 在指定的服务器上执行js脚本 + + # 在脚本里面可以访问 db 变量,和其他的全局变量,但是Shell的一些辅助函数,在js文件里面不能直接使用,但是可以用函数来代替 + // 切换DB,记得要用db变量重新接收赋值 + db.getSisterDB(db); // use db + db = db.getSisterDB(db); + + // 查看DBS + db.getMongo().getDBs(); // show dbs + { + "databases" : [{ + "name" : "admin", + "sizeOnDisk" : 32768, + "empty" : false + } + }], + "totalSize" : 241664, + "ok" : 1 + } + + // 查看collections + db.getCollectionNames(); // show collections + [ "c1", "c2", "c3" ] + + +------------------------------ +Shell 全局指令/函数 | +------------------------------ + help + * 查看帮助 + cls + * 清空面板 + exit + * 退出shell + print() + * 打印函数 + load(file) + * 加载指定的js脚本,并且执行 + * 默认加载当前目录的脚本,也可以使用绝对路径加载其他文件夹下的脚本 + run(shell) + * 执行shell命令 + * 执行成功返回 0 + +------------------------------ +Shell 内置对象 | +------------------------------ + Mongo + # 表示一个服务端连接对象 + // 创建与服务端的连接 + let connection = new Mongo('127.0.0.1:27017'); + + // 从连接获取db实例 + let db = connection.getDB('test'); + + + // 切换DB + db.getSitsterDB(db); // use db + + // 查看DBS + db.getMongo().getDBs(); // show dbs + + // 查看collections + db.getCollectionNames(); // show collections + + db.getLastError(); + + db.runCommand(); + * 执行指令 + + + DB + # db对象 + + DBCollection + # 集合对象 + + Object + + +------------------------------ +.mongorc.js | +------------------------------ + # 该js文件在用户的home目录下: ~/.mongorc.js + # 在启动 mongo 客户端的时候,会自动的加载执行 + mongo + + # 可以利用它来完成一些初始化功能(设置全局变量啥的) + + # 重写危险的函数,防止手误触发 + function no(args){ + print('危险函数,不能执行'); + } + + // 禁止删除数据库 + db.dropDatabase = DB.prototype.dropDatabase = no; + + // 禁止删除集合 + DBCollection.prototype.drop = no; + + // 禁止删除索引 + DBCollection.prototype.dropIndex = no; + +------------------------------ +定置 Shell 提示 | +------------------------------ + # 设置 prompt 变量为一个字符串,或者一个函数(返回字符串),就可以重置默认的Shell提示 + * prompt 就是命令行的前缀 + + + # 设置为当前的时间 + var prompt = function(){ + return new Date().getTime() + '>'; + } + + + # 设置为当前的db + var prompt = function(){ + if (typeof db == 'undefined'){ + return '(no db)>'; + } + try{ + db.runCommand({getLastError:1}); + }catch (e) { + print(e); + } + return db + '>'; + } + +------------------------------ +编辑复合变量 | +------------------------------ + TODO diff --git "a/Mongodb/mongodb-\345\256\211\350\243\205.java" "b/Mongodb/mongodb-\345\256\211\350\243\205.java" new file mode 100644 index 00000000..f9579426 --- /dev/null +++ "b/Mongodb/mongodb-\345\256\211\350\243\205.java" @@ -0,0 +1,11 @@ +-------------------------- +windows安装 | +-------------------------- + # 下载 + https://www.mongodb.com/download-center/community + + # 一路下一步就完事儿 + + * 下一步安装 "install mongoDB compass" 不勾选,否则可能要很长时间都一直在执行安装 + * MongoDB Compass 是一个图形界面管理工具,可以在后面自己到官网下载安装 + * https://www.mongodb.com/download-center/compass \ No newline at end of file diff --git "a/Mongodb/mongodb-\346\243\200\347\264\242.java" "b/Mongodb/mongodb-\346\243\200\347\264\242.java" new file mode 100644 index 00000000..2a818e19 --- /dev/null +++ "b/Mongodb/mongodb-\346\243\200\347\264\242.java" @@ -0,0 +1,300 @@ +----------------- +find 检索 | +----------------- + # 检索集合中的子集 + * 第一个参数是一个文档对象,表示检索复合条件的记录,如果参数是空,或者空对象,那么则是检索所有 + db.[collection].find({}) = db.[collection].find(); + + * 第二个参数,表示需要检索的文档字段 + + db.[collection].find({},{'id':1}); // 仅仅检索文档中的id字段,默认情况下 '_id' 字段总是返回的 + + * {'id':1} ,表示要检索id字段,如果它的值是0,则表示不检索id字段 + + + +----------------- +find 查询条件 | +----------------- + $lt (<) + $lte (<=) + $gt (>) + $gte (>=) + $ne (!= ) + $eq (==) + * 基本的判断 + db.[collection].find({ + 'id':{ //// 仅仅检索id大于1,小于10的记录 + '$lt':10, + '$gt':1 + } + }); + + * 可以用在日期上,new Date() + + + $in + * 匹配多个值 + db.users.find({ + 'id':{ + '$in':[1,3] // 检索id为 1 和 3 的记录 + } + }) + + * $in 的值也可以是不同的数据类型: '$in':[13,'Helo'] ,只要是匹配的都被会检索出来 + + $nin + * 跟 $in 相反,它匹配出所有不存在的急 + db.users.find({ + 'id':{ + '$nin':[1,3] // 检索id不为 1 和 3 的记录 + } + }) + + $or + * 表示多个条件为或的逻辑 + db.users.find({ + '$or':[{ + 'id':5 //检索id = 5 + },{ // 或者 + 'id':{ + '$in':[1,3] // id = 1 或者 3的文档 + } + }] + }); + + $mod + * 对指定的字段进行取模计算 + * 它的值是一个数组,第0个元素是取模的数值,第1个元素是取模的结果 + db.users.find({ + 'id':{ + '$mod':[3,1] // 检索 id % 3 == 1 的所有记录 + } + }); + + $not + * 表示对结果取反 + db.users.find({ + 'id':{ + '$not':{ + '$in':[1,3] // 检索id 不等于 1,3 的文档记录 + } + } + }); + + + + + $exists + * 使用它来处理 null 只 + db.users.find({ + 'foo':null // 检索 foo 字段为 null的记录 + }); + + * 如果文档不存在 foo 字段,也会被认为是符合条件 + + * 使用 $exists 判断指定的字段是否存在 + db.users.find({ + 'foo':{ + '$eq':null, // 检索 foo 字段为 null的记录 + '$exists':true // 并且该字段是存在于文档中的 + } + }); + + $type + * 可以根据数据字段的类型来过滤 + * 字段的类型,使用int值表示 + Double 1 + String 2 + Object 3 + Array 4 + + .... + + db.users.find({'id':{ + '$type':1 // 检索id字段的数据类型为数值的记录 + }}); + + + # 正则检索 + * 条件可以是合法的正则表达式 + * 使用JS的正则语法 + db.users.find({ + 'name':/\d{1}/ // 检索name字段是数字,并且只有一位的记录 + }); + + + # 对数组的查询 + * 数组的过滤条件默认跟in其实是一样的 + db.users.insertMany([{ + 'id':1, + 'hobby':['Java','Python','Javascript'] + },{ + 'id':2, + 'hobby':['Java','Ruby','Cplusplus'] + }]); + + db.users.find({ + 'hobby':'Java' // 检索 hoby 数组中,存在 Java 元素的文档 + }); + + db.users.find({ + 'hobby':['Java','Ruby','Cplusplus'] // 检索 hoby 数组中,必须存在 Java Ruby Cplusplus 元素的文档,一个不能多,一个不能少 + }); + + * 而且检索条件中元素的声明顺序和文档的顺序必须一致 + + + $all + * 必须匹配列表中的元帅 + db.users.find({ + 'hobby':{ + '$all':['Java','Ruby','Cplusplus'] // // 检索 hoby 数组中,必须存在 Java Ruby Cplusplus 元素的文档(如果有多的,也会被检索出来) + } + }); + * 与顺序无关 $all 声明的顺序跟文档[]中元素的顺序可以不一样 + + * 使用下标匹配 + db.users.find({ + 'hobby.0':'Java' // 匹配出 hobby 数组第一个元素是 Java 的结果 + }); + + $size + * 用于匹配指定长度的数组 + db.users.find({ + 'hobby':{ + '$size':3 // 匹配出 hobby 数组长度 = 3的记录 + } + }); + + * $size 并不支持 $lt/$ne 之类的操作,可以考虑在文档中维护一个字段(size),该字段(size)的值就是数组字段的长度,通过对size字段进行判断,达到过滤的效果 + + + $slice + * 它应该存在于 find() 的第二个函数,用于限制返回文档中数组元素的个数 + db.users.find({},{'hobby':{ + '$slice':-1 // 仅仅返回的文档中 hobby 数组的最后一个元素 + }}); + + * 正数,表示头几个 + * 负数,表示后几个 + + * 还支持使用区间,使用[] 表示开始的角标以及个数 + db.users.find({},{'hobby':{ + '$slice':[2,3] // 仅仅返回的文档中 hobby 数组中,从2角标开始一共3个元素 + }}) + + $elemMatch + + + + + $where + * 这个是利用了js代码执行检索判断 + * 应该严格限制这种检索方式 + db.users.find({ + '$where':function(){ + + } + }); + + * 如果该函数 返回 true,则表示该文档符合规则,返回 false 则表示不符合 + + * 贼慢,不建议使用 + + + + + + + + +----------------- +find 游标 | +----------------- + # 使用一个变量接收 find() 的检索结果集 + * 执行 find() 时并不会去检索数据,而是当开始迭代的时候,才会去检索记录 + + # 返回值本质上就是一个迭代器 + var cursor = db.users.find({}); + + while (cursor.hasNext()){ + var document = cursor.next(); + print(document.id); + } + + hasNext() + * 判断是否还有下一个 + next() + * 获取下一个 + + # 也可以使用 forEach 去遍历 + var cursor = db.users.find({}); + cursor.forEach(function(document){ + print(document.id); + }); + + # 支持一系列的方法链 + sort(); + * 排序 + * 参数是一个对象,key就是排序的字段,value是数值 + * 正数是升序排序,负数是逆序排序,可以同时存在多个排序策略 + sort({'name':1,'age':-1}); // 按照名称升序,按照年龄逆序 + + * 如果排序的字段值是不同的,那么mongdb本身定义了一个默认的排序规则(用到的时候再查) + + skip(); + * 丢弃前面的几个记录 + * 如果数据量很大的话,不建议使用 skip() 来略过数据,因为这个处理比较慢 + + limit(); + * 限制结果数量上限 + + pretty() + * 对JSON结果进行格式化 + + explain(); + * 显示mongo检索的详细信息 + + + + var cursor = db.users.find({}).sort({'id':1}).limit(10).skip(1); + + + # 翻页 + * skip的算法还是一样: (当前页 - 1) * 每页显示的数量 + + db.users.find({}).skip(0).limit(10); // 第一页 + db.users.find({}).skip(10).limit(10); // 第二页 + + # 随机获取文档 + // 计算出文档的总记录数量 + var count = db.users.count(); + // 计算出随机数 + var random = Math.floor(Math.random() * count) + // 使用skip和limit选择一条记录 + db.users.find({}).skip(random).limit(1); + + + # 高级查询 + * 高级的查询选项,通过 _addSpecial({}); 函数定义 + db.users.find()._addSpecial({}); + + $maxscan: int + * 限制扫描文档数的上限 + * 可能会导致某些符合条件的文档没有被扫描到 + + $min: json + $max: json + $showDiskLoc: boolean + * 在检索结果中添加一个 '$diskLoc' 字段,显示该记录存在于磁盘的位置信息 + + ... + + # 快照查询 + // TODO + + + # 游标的生命周期 + // TODO + diff --git "a/Mongodb/mongodb-\347\264\242\345\274\225.java" "b/Mongodb/mongodb-\347\264\242\345\274\225.java" new file mode 100644 index 00000000..3748ab13 --- /dev/null +++ "b/Mongodb/mongodb-\347\264\242\345\274\225.java" @@ -0,0 +1,86 @@ +---------------------------- +索引 | +---------------------------- + # 通过 explain() 来查看检索效率 + db.users.find().explain(); + { + "queryPlanner" : { + "plannerVersion" : 1, + "namespace" : "test.users", + "indexFilterSet" : false, + "parsedQuery" : { + + }, + "winningPlan" : { + "stage" : "COLLSCAN", + "direction" : "forward" + }, + "rejectedPlans" : [ ] + }, + "serverInfo" : { + "host" : "KevinBlandy", + "port" : 27017, + "version" : "4.0.9", + "gitVersion" : "fc525e2d9b0e4bceff5c2201457e564362909765" + }, + "ok" : 1 + } + + # 创建索引 + db.users.createIndex({'name':1}); + * 在集合 users 上的 'name' 字段建立索引 + { + "createdCollectionAutomatically" : false, + "numIndexesBefore" : 1, + "numIndexesAfter" : 2, + "ok" : 1 + } + + * 1 表示正序排序的索引,使用 -1 表示为逆序排序的索引 + + * mongobd一个集合最多允许 64 个索引 + * 索引会提高检索效率,但是在执行修改,插入的时候会去执行修改索引.都将会耗费更多的时间 + * 也可以定义多个字段,为复合索引 + db.users.createIndex({'name':1,'age': 1}); + + * 创建索引还支持第三个 options 参数(JSON) + background Boolean + * 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 + * 默认值为false。 + unique Boolean + * 建立的索引是否唯一,指定为true创建唯一索引,默认值为false + name string + * 索引的名称,如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称 + dropDups Boolean + * 3.0+版本已废弃,在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引,默认值为 false + + sparse Boolean + * 对文档中不存在的字段数据不启用索引 + * 这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档,默认值为 false + expireAfter Seconds integer + * 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间 + v + * index version 索引的版本号 + * 默认的索引版本取决于mongod创建索引时运行的版本 + weights document + * 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重 + default_language string + * 对于文本索引,该参数决定了停用词及词干和词器的规则的列表,默认为英语 + language_override string + * 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language + + + # 查看集合创建的索引 + db.[collection].getIndexes() + + # 查看集合索引的大小 + db.[collection].totalIndexSize() + + # 删除集合的索引 + db.[collection].dropIndex(name) + * 删除指定名称的索引 + + db.[collection].dropIndexes() + * 删除所有的索引 + + diff --git "a/Mongodb/mongodb-\350\201\232\345\220\210.java" "b/Mongodb/mongodb-\350\201\232\345\220\210.java" new file mode 100644 index 00000000..ce81664c --- /dev/null +++ "b/Mongodb/mongodb-\350\201\232\345\220\210.java" @@ -0,0 +1,7 @@ +-------------------------- +聚合检索 | +-------------------------- + # 聚合检索的语法 + db.[collection].aggregate(options); + + \ No newline at end of file diff --git a/Mongodb/mongodb.java b/Mongodb/mongodb.java new file mode 100644 index 00000000..bd4883a4 --- /dev/null +++ b/Mongodb/mongodb.java @@ -0,0 +1,60 @@ +------------------------------ +mongodb | +------------------------------ + # 地址 + https://mongodb.com/ + https://docs.mongodb.com/ + https://github.com/mongodb/mongo + +------------------------------ +mongodb - 目录结构 | +------------------------------ + bin + |-mongo.exe + * 一个js的Shell的客户端,可以使用js的语法去执行客户端的操作 + * 它还具备了js的标准库,不包含 dom 和 bom + + * 可以使用多行命令,它会检测你的js语法是否是完整的,没写完的情况下,你可以通过回车,继续在下一行写 + * 连续按三次回车,可以取消没有输入完成的命令 + + |-mongod.exe + * mongo服务端脚本 + + log + |-mongod.log + +------------------------------ +mongodb - 服务启动 | +------------------------------ + # 执行 mongod 脚本 + + # 没有参数,默认会使用 /data/db 为数据目录 + * Windows 使用 C:\data\db + * 需要先创建,否则会启动失败 + + # Mongo启动成功会在 27017 端口提供服务 + + # 启动参数 + --noscripting + * 禁止服务端执行js代码 + + + + + + +------------------------------ +mongodb - 客户端的使用 | +------------------------------ + # 执行 mongo 脚本 + # 启动的时候,它会连接到默认的 test 数据库 + # 并且把数据库的连接赋值给全局变量: db + + db + * 查看当前所在的数据库 + + user db + * 切换到指定的db,不存在会创建 + + # 通过这个js shell 可以完成数据库的所有操作 + diff --git a/MySQL/MySQL-Emoji.java b/MySQL/MySQL-Emoji.java new file mode 100644 index 00000000..cb14c371 --- /dev/null +++ b/MySQL/MySQL-Emoji.java @@ -0,0 +1,23 @@ +错误提示: + +java.sql.SQLException: Incorrect string value: '\xF0\x9F\x90\x82' for column 'content' at row 1 + +修改 my.cnf或者mysql.ini,重启服务 + +[client] +default-character-set = utf8mb4 + +[mysql] +default-character-set = utf8mb4 + +[mysqld] +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci + +数据库编码也必须为utf8mb4 + +查看编码 + +SHOW VARIABLES WHERE Variable_name LIKE 'character%' OR Variable_name LIKE 'collation%'; + +* 除了:character_set_system | utf8 ,以外。都应该是 utf8mb4 \ No newline at end of file diff --git "a/MySQL/MySQL-Windos\345\256\211\350\243\205.java" "b/MySQL/MySQL-Windos\345\256\211\350\243\205.java" new file mode 100644 index 00000000..9db0c1b6 --- /dev/null +++ "b/MySQL/MySQL-Windos\345\256\211\350\243\205.java" @@ -0,0 +1,54 @@ +-------------------------------- +Winddows 安装MySQL | +-------------------------------- + # 下载windows压缩包,解压 + https://dev.mysql.com/downloads/mysql/ + + * 可以点击: Looking for previous GA versions? 来下载以前的版本(5.7.x) + + # 添加环境变量 + MYSQL_HOME=D:\mysql-5.7.26-winx64 + + # 添加PATH变量 + %MYSQL_HOME%\bin; + + # 创建ini配置文件 + * 在mysql的解压根目录创建文件:my.ini + +[mysql] +default-character-set=utf8mb4 + +[mysqld] +port = 3306 +basedir=D:\mysql\mysql-5.7.26-winx64 +datadir=D:\mysql\mysql-5.7.26-winx64\data +max_connections=200 +character-set-server=utf8mb4 +collation-server=utf8mb4_unicode_ci + +[client] +default-character-set=utf8mb4 + + + + # 初始化 + mysqld --initialize-insecure + + * 初始化无密码的root用户 + * 登录时输入密码直接回车 + * 登录成功后修改密码 + set password for 'root'@'%' = password('root'); + + # 安装到系统服务 + mysqld --install + + * 使用系统命令维护 + net start mysql + net stop mysql + + * 出错可以卸载重新安装 + mysqld --remove mysql + + + + diff --git a/MySQL/MySQL-core-InnoDBjava.java b/MySQL/MySQL-core-InnoDBjava.java new file mode 100644 index 00000000..deaeea51 --- /dev/null +++ b/MySQL/MySQL-core-InnoDBjava.java @@ -0,0 +1,81 @@ +-------------------------------- +InnoDB | +-------------------------------- + # InnoDB将数据划分为若干个页, 以页作为磁盘和内存之间交互的基本单位 + * InnoDB中页的大小一般为 16 KB + * 也就是在一般情况下, 一次最少从磁盘中读取16KB的内容到内存中, 一次最少把内存中的16KB内容刷新到磁盘中 + + # InnoDB存储引擎会为每条记录都添加 transaction_id 和 roll_pointer 这两个列 + * row_id 是可选的, 在没有自定义主键以及非null的Unique键的情况下才会添加该列 + + + +-------------------------------- +InnoDB行格式 | +-------------------------------- + # 一般是以记录为单位来向表中插入数据的, 这些记录在磁盘上的存放方式也被称为行格式或者记录格式 + # InnoDB存储引擎到现在为止设计了4种不同类型的行格式 + Compact + Redundant + Dynamic + Compressed + + # 指定/修改行格式的语法, 使用关键字:ROW_FORMAT + CREATE TABLE 表名 ... ROW_FORMAT=行格式名称 + + ALTER TABLE 表名 ROW_FORMAT=行格式名称 + + # Compact + +----------------------------------+-------------------------------+ + | 记录的额外信息 | 记录的真实数据 | + +----------------------------------+-------+-------+-------+-------+ + |变长字段列表|NULL值列表|记录头信息|列1的值|列2的值|... ...|列n的值| + +----------------------------------+-------+-------+-------+-------+ + + # 变长字段列表 + * MySQL支持一些变长的数据类型, 比如VARCHAR(M), VARBINARY(M), 各种TEXT类型, 各种BLOB类型 + * 这些数据类型的列称为变长字段, 存储的数据大小不是固定的, 在存储这些数据的时候, 它们实际占用的大小, 也需要被存储起来 + + * 在该格式中, 把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位 + * 从而形成一个变长字段长度列表, 各变长字段数据占用的字节数'按照列的顺序逆序存放' + * 也就是说, 列中最后一个变长字段的占用长度数据, 存储在第一个 + + + * 使用多少个字节来表示变长字段的数据大小?是可以有公式进行计算的 + W 一个字符的最大体积(根据编码不同,可能有所不同) + M 列数据类型,可以最多存储的数据大小 + L 数据实际占用的空间大小 + + + if (M * W <= 255){ + 使用一个字节存储长度 + } else if(M * W > 255){ + if(L <= 127){ + 使用一个字节存储长度 + }else if(L > 127){ + 使用2个字节存储长度 + } + } + + * 如果某个字段长度大于了16KB, 那么如果该记录在单个页面中无法存储时, InnoDB会把一部分数据存放到所谓的溢出页中 + * 变长字段长度列表处只存储留在本页面中的长度, 所以使用两个字节也可以存放下来 + + + # NULL值列表 + * 表中的某些列可能存储NULL值, 如果把这些NULL值都放到记录的真实数据中存储会很占地方, 所以Compact行格式把这些值为NULL的列统一管理起来, 存储到NULL值列表中 + + * 首先统计表中允许存储NULL的列有哪些, NOT NULL 修饰的列都是不可以存储NULL值的, 所以在统计的时候不会把这些列算进去 + * 将每个允许存储NULL的列对应一个二进制位(Bit), 二进制位按照列的顺序逆序排列 + * 二进制位的值为1时, 代表该列的值为NULL + * 二进制位的值为0时, 代表该列的值不为NULL + + * 如果表中没有允许存储 NULL 的列, 则 NULL值列表 也不存在了 + + + + + + + + + \ No newline at end of file diff --git a/MySQL/MySQL-core-explain .java b/MySQL/MySQL-core-explain .java new file mode 100644 index 00000000..164758bd --- /dev/null +++ b/MySQL/MySQL-core-explain .java @@ -0,0 +1,151 @@ + +---------------- +Explain | +--------------- + # 一条查询语句在经过MySQL查询优化器的各种基于成本和规则的优化会后生成一个所谓的执行计划 + * 这个执行计划展示了接下来具体执行查询的方式, 比如多表连接的顺序是什么, 对于每个表采用什么访问方法来具体执行查询等等 + + + # 语法: EXPLAIN [检索语句] + * DELETE, INSERT, REPLACE, UPDATE语句前边都可以使用它来分析执行计划 + + +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ + | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | + +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ + | 1 | SIMPLE | user | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL | + +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ + + + id + * 在一个大的查询语句中每个SELECT关键字都对应一个唯一的id + * 每个表都会对应一条记录,但是这些记录的id值都是相同的 + + * 查询优化器可能对涉及子查询的查询语句进行重写, 从而转换为连接查询, 那么俩 SELECT 的id都一样 + + * UNION 检索的 id 是 NULL + * UNION 把多个查询的结果集合并起来并对结果集中的记录进行去重 + * MySQL使用的是内部的临时表,为了把查询的结果集合并起来并去重,所以在内部创建了一个名为的临时表 + * id为NULL表明这个临时表是为了合并两个查询的结果集而创建的 + + * UNION ALL, 不需要为最终的结果集进行去重, 它只是单纯的把多个查询的结果集中的记录合并成一个并返回给用户 + * 所以也就不需要使用临时表, 也就不存在 id 为 NULL 的记录 + + + select_type + * SELECT关键字对应的那个查询的类型 + * 枚举值 + SIMPLE + * 不包含UNION或者子查询的查询都算作是SIMPLE + * 连接查询也算是 SIMPLE + + PRIMARY + * 对于包含UNION, UNION ALL或者子查询的大查询来说, 它是由几个小查询组成的 + * 其中最左边的那个查询的 select_type 值就是PRIMARY + + UNION + * 对于包含UNION, UNION ALL的大查询来说, 它是由几个小查询组成的 + * 其中除了最左边的那个小查询以外, 其余的小查询的select_type值就是UNION + + UNION RESULT + * MySQL选择使用临时表来完成UNION查询的去重工作 + * 针对该临时表的查询的select_type就是UNION RESULT + + SUBQUERY + * 如果包含子查询的查询语句不能够转为对应的semi-join的形式, + * 并且该子查询是不相关子查询并且查询优化器决定采用将该子查询物化的方案来执行该子查询时 + * 该子查询的第一个SELECT关键字代表的那个查询的select_type就是SUBQUERY + + DEPENDENT SUBQUERY + * 如果包含子查询的查询语句不能够转为对应的semi-join的形式,并且该子查询是相关子查询 + * 则该子查询的第一个SELECT关键字代表的那个查询的select_type就是DEPENDENT SUBQUERY + + DEPENDENT UNION + * 在包含UNION或者UNION ALL的大查询中, 如果各个小查询都依赖于外层查询的话, 那除了最左边的那个小查询之外, 其余的小查询的select_type的值就是DEPENDENT UNION + + DERIVED + * 对于采用物化的方式执行的包含派生表的查询, 该派生表对应的子查询的select_type就是DERIVED + + MATERIALIZED + * 当查询优化器在执行包含子查询的语句时, 选择将子查询物化之后与外层查询进行连接查询时, 该子查询对应的select_type属性就是MATERIALIZED + + UNCACHEABLE + UNCACHEABLE UNION + + table + * 检索中使用到的表, 如果使用了别名, 则显示别名, 出现在前边的表表示驱动表, 出现在后边的表表示被驱动表 + + partitions + * 匹配的分区信息 + + type + * 针对单表的访问方法 + * 枚举值 + system + * 当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的 + * 比如MyISAM, Memory, 那么对该表的访问方法就是system + + const + * 根据主键或者唯一二级索引列与常数进行等值匹配时, 对单表的访问方法就是const + + eq_ref + * 在连接查询时, 如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话, 所有的索引列都必须进行等值比较) + * 则对该被驱动表的访问方法就是eq_ref + + ref + * 当通过普通的二级索引列与常量进行等值匹配时来查询某个表, 那么对该表的访问方法就可能是ref + + fulltext + * 全文索引 + + ref_or_null + * 当对普通二级索引进行等值匹配查询, 该索引列的值也可以是NULL值时, 那么对该表的访问方法就可能是ref_or_null + + index_merge + + unique_subquery + * unique_subquery是针对在一些包含IN子查询的查询语句中, + * 如果查询优化器决定将IN子查询转换为EXISTS子查询,而且子查询可以使用到主键进行等值匹配的话, 那么该子查询执行计划的type列的值就是unique_subquery + + index_subquery + * index_subquery与unique_subquery类似, 只不过访问子查询中的表时使用的是普通的索引 + range + * 使用索引获取某些范围区间的记录, 那么就可能使用到range访问方法 + index + * 以使用索引覆盖, 但需要扫描全部的索引记录时, 该表的访问方法就是index + + ALL + * 全表扫描 + + + + possible_keys + * 表示在某个查询语句中, 对某个表执行单表查询时可能用到的索引有哪些 + * possible_keys列中的值并不是越多越好, 可能使用的索引越多, 查询优化器计算查询成本时就得花费更长时间, 所以如果可以的话, 尽量删除那些用不到的索引 + + key + * 表示实际用到的索引有哪些 + + key_len + * 表示当优化器决定使用某个索引执行查询时, 该索引记录的最大长度 + + ref + * 当使用索引列等值查询时, 与索引列进行等值匹配的对象信息 + + rows + * 预计需要扫描的行数 + + filtered + * 某个表经过搜索条件过滤后剩余记录条数的百分比 + + Extra + * 一些额外的信息 + + + + # Json格式的执行计划 + EXPLAIN FORMAT=JSON [select] + + * 以JSON的形式显示结果 + + # 使用EXPLAIN语句查看了某个查询的执行计划后, 紧接着还可以使用SHOW WARNINGS语句查看与这个查询的执行计划有关的一些扩展信息 + SHOW WARNINGS; \ No newline at end of file diff --git "a/MySQL/MySQL-core-\344\272\213\345\212\241java" "b/MySQL/MySQL-core-\344\272\213\345\212\241java" new file mode 100644 index 00000000..dc56f2e8 --- /dev/null +++ "b/MySQL/MySQL-core-\344\272\213\345\212\241java" @@ -0,0 +1,376 @@ +----------------------- +MySQL-事务 | +----------------------- + # 讲事务这东西,都是拿银行转账来说事儿. + # 事务跟存储引擎相关.有些引擎是不支持事务的 + # 参考 + https://mp.weixin.qq.com/s/XwC9n2Ik_DrfY-ERd99wqA + https://www.imooc.com/article/17290 + +----------------------- +MySQL-CMD操作事务 | +----------------------- + # 开启事务方法1 + BEGIN; + + update... + delete... + insert... + + COMMIT; + ROLLBACK; + + # 开启事务方法2 + START TRANSACTION; + * 开启了之后,就会把操作记录到事务日志 + * 还有一种方式设置. + set autocommit=1; + * 该方式,直接针对'当前连接'进行设置,以后的每次增删改操作,默认都是需要手动提交 + * 值也可以是:ON 也是代表开启 + + update... + delete... + insert... + + COMMIT; + ROLLBACK; + + * 本质上跟 BEGIN 其实是一样的, 但是 START TRANSACTION 后面可以跟一些其他的指令 + READ ONLY + * 只读事务, 不能执行修改等操作 + + READ WRITE + * 读写事务, 即可读也可写(默认) + + WITH CONSISTENT SNAPSHOT + * 启动一致性读 + + * 如果存在多个指令的话, 可以使用逗号分隔 + + +----------------------- +MySQL-事务回滚点 | +----------------------- + # 其实我理解就是传说中的保存点. + # 在某一个成功的操作完成之后,后续的操作有可能成功.有可能失败. + # 在当前的位置,设置一个保存点,让后面的失败操作,返回到该位置 + # 语法 + savepint [回滚点名字]; 创建回滚点 + rolback [回滚点名字]; 回滚到指定的回滚点 + RELEASE SAVEPOINT [回滚点名字]; 删除指定的回滚点 + # 往往是在事务操作比较多的时候,会用到 + # 操作步骤 + 1,开启事务 + ... 逻辑操作 + ... 设置回滚点1 + ... 设置回滚点2 + ... + 2,回滚到指定的点 + ... 继续逻辑操作 + 3,提交/commit + ... + + # 事务一旦回滚,就会清空事务日志.设置了点之后,就仅仅清空点之后的事务日志 + + +----------------------- +自动事务 | +----------------------- + # MySql中默认都是自动的事务处理,用户的每个操作.都会自己开启,提交事务 + # 系统通过: autocommit 变量俩进行控制 + # show variables like 'autocommit'; + * ON :表示是自动事务 + * OFF :表示非自动事务 + # 关闭自动提交 + set autocommit = off; + * 值为0也可以,因为0代表 false + # 关闭之后,每次操作都需要执行commit命令 + # 通常都会使用自动事务,当需要使用到事务的时候,我们都是采取临时的事务 + # 该操作,仅仅是对当前连接/线程有效 + # 如果需要彻底修改,那么需要去修改配置文件 + +----------------------- +事务的隐式提交 | +----------------------- + # 事务除了手动的调用 commit 以外, 在其他的一些情况下会手动的提交 + + # 定义或修改数据库对象的数据定义语言(DDL) + * 使用CREATE, ALTER, DROP等语句去修改这些所谓的数据库对象(数据库, 表, 视图, 存储过程等等这些东西)时, 就会自动提交前面的事务 + + # 隐式使用或修改mysql数据库中的表 + * 使用ALTER USER, CREATE USER, DROP USER, GRANT, RENAME USER, REVOKE, SET PASSWORD等语句时也会隐式的提交前边语句所属于的事务 + + # 事务控制或关于锁定的语句 + * 一个事务还没提交或者回滚时就又使用START TRANSACTION或者BEGIN语句开启了另一个事务时, 会隐式的提交上一个事务 + * 使用LOCK TABLES, UNLOCK TABLES等关于锁定的语句也会隐式的提交前边语句所属的事务 + + # 加载数据的语句 + * 使用LOAD DATA语句来批量往数据库中导入数据时, 也会隐式的提交前边语句所属的事务 + + # MySQL复制的一些语句 + * 使用ANALYZE TABLE, CACHE INDEX, CHECK TABLE, FLUSH, LOAD INDEX INTO CACHE, OPTIMIZE TABLE, REPAIR TABLE, RESET等语句也会隐式的提交前边语句所属的事务 + + +----------------------- +事务特性 | +----------------------- + 事务的四大特效: + # A-Atomic 原子性 + * 一组事务,要么全部提交成功,要么全部失败 + * Undo log 保证, Undo Log 会保存每次变更之前的记录, 从而在发生错误时进行回滚 + + # C-Con... 一致性 + * 事务操作的前后,数据表中的数据.没有变化 + + # I-Isol.. 隔离性 + * 事务的操作是相互隔离,不受影响的 + * 隔离性由 MVCC 和 Lock 保证 + + # D-D... 持久性 + * 事务一旦提交,就必须要保存到数据库,永久的改变数据表 + * 持久性由 Redo Log 保证,每次真正修改数据之前, 都会将记录写到 Redo Log 中, 只有 Redo Log 写入成功, 才会真正的写入到 B+ 树中 + * 如果提交之前断电, 就可以通过 Redo Log 恢复记录 + +----------------------- +锁机制 | +----------------------- + # 因为事务的'隔离性质',所以在多线程的时候,MYSQL会有一个锁机制来保证数据的安全 + # INNODB引擎默认的锁机制是:行级锁 + # 但是如果在事务操作的过程中,没有使用到索引(就是说WHERE条件没有使用到主键).那么系统会自动全表检索数据,那么就会自动升级为表锁 + # 行锁:只有当前行被锁定,别的线程不能进行操作 + * 一个线程开启了事务,进行了事务操作之后(使用到了索引,就会单单锁定该行记录) + * 另外的线程对该条记录进行增/删/该.会被线程阻塞.直到当前事务提交或者回滚 + + # 表锁:只有当前表被锁定,别的线程不能进行操作 + * 一个线程开启了事务,进行了事务操作之后(如果操作没有使用到索引,直接锁整张表), + * 另外的线程对该表进行增/删/改.会被线程阻塞.直到当前事务提交或者回滚 + +----------------------- +事务的隔离级别 | +----------------------- + # 并发读问题(严重性由低到高) + * 脏写 :因为另一个事务的回滚, 覆盖了当前事务的提交 + * 脏写这个问题太严重了, 不论是哪种隔离级别, 都不允许脏写的情况发生 + * InnoDB使用锁来保证不会有脏写情况的发生, 也就是在第一个事务更新了某条记录后, 就会给这条记录加锁, 另一个事务再次更新时就需要等待第一个事务提交了, 把锁释放之后才可以继续更新 + + * 脏读 :读到另一个事务未提交的数据, 主要体现是在修改数据 + * 不可重复读 :两次读取不一致 + * 幻读(虚读) :按照某个相同条件多次读取记录时, 后读取时读到了之前没有读到的记录, 主要体现是其他事务插入了数据 + + + # 四大隔离级别 + * 是用来防止上面三种读问题 + * 4个等级的事物隔离级别,在相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同的事务隔离级别能够解决数据并发问题的能力是不同的. + 1,Serializable(串行化)(坚决不用) + * 不会出现任何问题,因为它对同一数据的访问是串行的,并非并发访问的! + * 性能最差(能处理任何问题) + 2,Repeatable read(可重复读取)(MYSQL默认) + * 防止脏读和不可重复读,不能处理幻读问题 + * 性能比 Serializable 好 + 3,Read Committed(读已提交数据)(Oracle默认) + * 防止脏读和不可重复读,没有处理不可重复读,也没处理幻读 + * 性能别 Serializable 好 + 4,Read Uncommitted 读未提交数据(坚决不用) + * 可以出现任何事务并发问题,没有处理任何问题.隔离级别最烂 + * 性能最好 + + # 查看数据库的隔离级别 + select @@tx_isolation + * MYSQL的默认隔离级别(一般没有必要去修改) + +-----------------+ + | @@tx_isolation | + +-----------------+ + | REPEATABLE-READ | + +-----------------+ + + # 修改数据库的隔离级别 + settransaction isolationlevel [四个隔离级别选一个] + + # JDBC设置隔离级别 + Connection conn + conn.setTransactionlsolation(int leve); + 参数可选值如下 + Connection.TRANSACTION_READ_UNCOMMITTED + Connection.TRANSACTION_READ_COMMITTED + ... ... 静态变量,字段参阅API + +----------------------- +JDBC操作事务 | +----------------------- + try{ + conn.setAutoCommit(false);//开启事物 + ... + ... + ... + conn.commit();//try语句的最后提交事务 + }catch(Exception e){ + conn.rollback();//回滚事务 + } + +------------------------ +MVCC原理 | +------------------------ + # 一个非常易懂的介绍 + https://juejin.im/book/5bffcbc9f265da614b11b731/section/5c923cfcf265da60f00ecaa9 + + # MVCC(Multi-Version Concurrency Control) + * 多版本并发控制 + + # InnoDB存储引擎的表来说, 它的聚簇索引记录中都包含两个必要的隐藏列 + trx_id + * 每次一个事务对某条聚簇索引记录进行改动时, 都会把该事务的事务id赋值给trx_id隐藏列 + + roll_pointer + * 每次对某条聚簇索引记录进行改动时, 都会把旧的版本写入到undo日志中 + * 这个隐藏列就相当于一个指针, 可以通过它来找到该记录修改前的信息 + + + * 每次对记录进行改动, 都会将旧值记录到一条undo日志, 每条undo日志也都有一个 roll_pointer 属性 + * 这些undo日志都会连起来, 串成一个链表, 这个链表称之为版本链, 版本链的头节点就是当前记录最新的值 + * 每个版本中还包含生成该版本时对应的事务id + + + + # ReadView + * 使用READ UNCOMMITTED隔离级别的事务来说, 由于可以读到未提交事务修改过的记录, 所以直接读取记录的最新版本就好了 + * 使用SERIALIZABLE隔离级别的事务来说, 使用加锁的方式来访问记录 + + * 使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说, 都必须保证读到已经提交了的事务修改过的记录 + * 也就是说假如另一个事务已经修改了记录但是尚未提交, 是不能直接读取最新版本的记录的 + * 问题就是:需要判断一下版本链中的哪个版本是当前事务可见的 + + * InnoDB的提出了一个ReadView的概念 + * 这个ReadView中主要包含4个比较重要的内容: + m_ids + * 在生成ReadView时当前系统中活跃的读写事务的事务id列表 + + min_trx_id + * 表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id + * 也就是m_ids中的最小值 + + max_trx_id + * 表示生成ReadView时系统中应该分配给下一个事务的id值 + * max_trx_id并不是m_ids中的最大值, 事务id是递增分配的 + + * 比方说现在有id为1, 2, 3这三个事务, 之后id为3的事务提交了 + * 那么一个新的读事务在生成ReadView时, m_ids就包括1和2, min_trx_id的值就是1, max_trx_id的值就是4 + + creator_trx_id + * 表示生成该ReadView的事务的事务id + * 只有第一次在对表中的记录做改动时(执行INSERT, DELETE, UPDATE这些语句时)才会为事务分配事务id + * 否则在一个只读事务中的事务id值都默认为0 + + * 有了ReadView, 在访问某条记录时, 只需要按照下边的步骤判断记录的某个版本是否可见 + 1. 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同 + * 意味着当前事务在访问它自己修改过的记录, 所以该版本可以被当前事务访问 + + 2. 如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值 + * 表明生成该版本的事务在当前事务生成ReadView前已经提交, 所以该版本可以被当前事务访问 + + 3. 如果被访问版本的trx_id属性值大于ReadView中的max_trx_id值 + * 表明生成该版本的事务在当前事务生成ReadView后才开启, 所以该版本不可以被当前事务访问 + + 4. 如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间 + * 那就需要判断一下trx_id属性值是不是在m_ids列表中 + * 如果在, 说明创建ReadView时生成该版本的事务还是活跃的, 该版本不可以被访问 + * 如果不在, 说明创建ReadView时生成该版本的事务已经被提交, 该版本可以被访问 + + + + * 如果某个版本的数据对当前事务不可见的话, 那就顺着版本链找到下一个版本的数据 + * 继续按照上边的步骤判断可见性, 依此类推, 直到版本链中的最后一个版本 + * 如果最后一个版本也不可见的话, 那么就意味着该条记录对该事务完全不可见, 查询结果就不包含该记录 + + + + # READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同 + READ COMMITTED 每次读取数据前都生成一个ReadView + REPEATABLE READ 在第一次读取数据时生成一个ReadView, 之后的查询就不会重复生成了 + + # insert undo在事务提交之后就可以被释放掉 + + # 关于purge + * insert undo在事务提交之后就可以被释放掉了, 而update undo由于还需要支持MVCC, 不能立即删除掉 + * DELETE语句或者更新主键的UPDATE语句并不会立即把对应的记录完全从页面中删除, 而是执行一个所谓的delete mark操作 + * 相当于只是对记录打上了一个删除标志位, 这主要就是为MVCC服务的 + + * 随着系统的运行, 在确定系统中包含最早产生的那个ReadView的事务不会再访问某些update undo日志以及被打了删除标记的记录后 + * 有一个后台运行的purge线程会把它们真正的删除掉 + + # 所谓的MVCC只是在我们进行普通的SEELCT查询时才生效 + + +------------------------------------------------- +Read Committed, 是否能彻底解决幻读的问题 | +------------------------------------------------- + # MYSQL 的 Read Committed, 其实是可以解决幻读的问题 + +---------------------------+-------------------------------------------+ + | SESSION1 | SESSION2 | + +-----------------------------------------------------------------------+ + |start transaction; | start transaction; | + +-----------------------------------------------------------------------+ + |select * from `user`; | | + +-----------------------------------------------------------------------+ + | | insert into `user` values(2, 0, 0); | + +-----------------------------------------------------------------------+ + | | commit; | + +-----------------------------------------------------------------------+ + |select * from `user`; | | + +-----------------------------------------------------------------------+ + |commit; | | + +-----------------------------------------------------------------------+ + + * SESSION1 先执行 select 发现读取不到数据 + * SESSION2 执行 insert 插入了记录 + * SESSION1 再次 select语句, 不能检索到session2的新插入的记录 + * 如果SESSION2 执行删除提交, 那么SESSION1 未提交之前还是可以读取到这条已经被删除的记录 + + + # MYSQL 的 Read Committed, 不一定可以彻底解决幻读的问题 + * 数据信息 + +----+---------+---------+ + | id | balance | version | + +----+---------+---------+ + | 1 | 0 | 1 | + | 2 | 0 | 1 | + +----+---------+---------+ + + * 检索 + +-------------------------------------------------------+-------------------------------------------+ + |SESSION1 |SESSION2 | + +-------------------------------------------------------+-------------------------------------------+ + |start transaction; |start transaction; | + +-------------------------------------------------------+-------------------------------------------+ + |select * from `user` where `version` = 2; | | + +-------------------------------------------------------+-------------------------------------------+ + | |insert into `user` values(3, 0, 2); | + +-------------------------------------------------------+-------------------------------------------+ + | |commit; | + +-------------------------------------------------------+-------------------------------------------+ + |select * from `user` where `version` = 2; | | + +-------------------------------------------------------+-------------------------------------------+ + |update `user` set `balance` = 1 where `version` = 2; | | + +-------------------------------------------------------+-------------------------------------------+ + |select * from `user` where `version` = 2; | | + +-------------------------------------------------------+-------------------------------------------+ + |commit; | | + +-------------------------------------------------------+-------------------------------------------+ + + * SESSION1 第一次检索, DB记录不存在 + * SESSION1 第二次检索, 检索不到SESSION2新插入的记录, 防止了幻读 + Empty set (0.00 sec) + + * 但是SESSION1 尝试去对 SESSION2 新插入的记录执行 UPDATE, 可以修改成功 + * 修改成功后, 再次执行 SELECT, 可以检索出SESSION2新插入的记录 + +----+---------+---------+ + | id | balance | version | + +----+---------+---------+ + | 3 | 1 | 2 | + +----+---------+---------+ + + * 原因 + - 在REPEATABLE READ隔离级别下, T1第一次执行普通的SELECT语句时生成了一个ReadView, 之后T2向hero表中新插入了一条记录便提交了 + - ReadView并不能阻止T1执行UPDATE或者DELETE语句来对改动这个新插入的记录, 因为T2已经提交, 所以改动该记录并不会造成阻塞 + - 但是这样一来这条新记录的 trx_id 隐藏列就变成了T1的事务id, 之后T1中再使用普通的SELECT语句去查询这条记录时就可以看到这条记录了 + - 也就把这条记录返回给客户端了, 因为这个特殊现象的存在, 也可以认为InnoDB中的MVCC并不能完完全全的禁止幻读 + diff --git "a/MySQL/MySQL-core-\344\274\230\345\214\226.java" "b/MySQL/MySQL-core-\344\274\230\345\214\226.java" new file mode 100644 index 00000000..59e624f8 --- /dev/null +++ "b/MySQL/MySQL-core-\344\274\230\345\214\226.java" @@ -0,0 +1,4 @@ +--------------------- +子查询语法 | +--------------------- + \ No newline at end of file diff --git "a/MySQL/MySQL-core-\347\264\242\345\274\225.java" "b/MySQL/MySQL-core-\347\264\242\345\274\225.java" new file mode 100644 index 00000000..df5568ab --- /dev/null +++ "b/MySQL/MySQL-core-\347\264\242\345\274\225.java" @@ -0,0 +1,349 @@ +---------------------------- +索引 | +---------------------------- + # 正确的创建合适的数据库索引, 是提升查询效率的基础 + + # MySQL的存储和查找 + * MySql 将数据按照页来存储, 默认一页为 16kb, 一页里面可以有N多条数据记录 + * 在查询时, 不会只加载某一条数据, 而是将这个数据所在的页都加载到 pageCache 中, 这个其实和 OS 的就近访问原理类似 + * 在一个页里面查找记录的两种情况 + 1. 以主键为搜索条件 + * 中使用二分法快速定位到对应的槽, 然后再遍历该槽对应分组中的记录即可快速找到指定的记录 + + 2. 以其他列作为搜索条件 + * 遍历 + + * 大部分情况下表中存放的记录都是非常多的, 需要好多的数据页来存储这些记录 + * 在很多页中查找记录的话可以分为两个步骤 + 1. 定位到记录所在的页 + 2. 从所在的页内中查找相应的记录(上面的方法) + + # 优点 + * 天生排序 + * 快速查找 + + # 缺点 + * 占用空间 + * 降低更新表的速度, 增删改都系要对索引进行维护 + + # 小表使用全表扫描更快,中大表才使用索引,超级大表索引基本无效 + + # 索引分类 + * 从数据结构上来说分为 + 1. BTree 索引(B+Tree) + 2. Hash 索引 + 3. Full Index 索引 + 4. RTree 索引 + + * 实现上说,分成 2 种(根据数据的物理顺序与键值的逻辑(索引)顺序关系 + 1. 聚集索引 + + 2. 辅助索引(也叫二级索引或者非聚集索引) + + + * 功能(应用)上说上说,分成 6 种 + + 1. 普通索引 + * 最基本的索引,没有任何约束 + + 2. 唯一索引 + * 与普通索引类似,但具有唯一性约束 + * 唯一索引可以为空,但是空值只能有一个 + + 3. 主键索引 + * 特殊的唯一索引,不允许有空值 + + 4. 复合索引 + * 将多个列组合在一起创建索引,可以覆盖多个列, 专门用于组合搜索, 其效率大于索引合并 + + 5. 外键索引 + * 只有InnoDB类型的表才可以使用外键索引,保证数据的一致性,完整性和实现级联操作 + + 6. 全文索引 + * MySQL 自带的全文索引只能用于 InnoDB、MyISAM ,并且只能对英文进行全文检索 + * 全文检索一般使用全文索引引擎(ES, Solr)来完成 + + # B+树索引 + * B树,是一个多路平衡查找树,相较于普通的二叉树,不会发生极度不平衡的状况,同时也是多路的 + * 他会将数据也保存在非页子节点 + * B+ 树的高度在 2-4 层, 由 MySql 在运行时, 根节点是常驻内存的, 因此每次查找只需要大约 2 -3 次 IO + * 可以说, B+ 树的设计, 就是根据机械磁盘的特性来进行设计的 + + * 如果B+树只有1层, 也就是只有1个用于存放用户记录的节点, 最多能存放1000条记录 + * 如果B+树有2层, 最多能存放1000×1000=1000000条记录 + * 如果B+树有3层, 最多能存放1000×1000×1000=1000000000条记录 + * 如果B+树有4层, 最多能存放1000×1000×1000×1000=1000000000000条记录 + * 表里需要存放1000000000000条记录么?所以一般情况下, 用到的B+树都不会超过4层 + * 通过主键去查找某条记录最多只需要做4个页面内的查找, 又因为在每个页面内有所谓的Page Directory, 所以在页面内也可以通过二分法实现快速定位记录 + + * B+树中每层节点(数据页)都是按照索引列值从小到大的顺序排序而组成了双向链表 + * 每个页内的记录(不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序而形成了一个单链表 + + * MySql 的主键不能太大, 如果使用 UUID 这种,将会浪费 B+ 树的非叶子节点 + * MySql 的主键最好是自增的, 如果使用 UUID 这种, 每次插入都会调整 B+树, 从而导致页分裂, 严重影响性能 + + + + + # 为什么不使用红黑树(平衡二叉树) + * 搜索性能不足, 在树结构中数据的深度决定着它搜索时的io次数 + * 节点数据内容太少, 每一个磁盘块(节点/页), 保存的关键字数量太少了 + * 没有很好的利用操作系统和磁盘的数据交换特性和磁盘预读能力(空间局部性原理) + * 磁盘和操作系统的一次io交互, 一次是一个页(单位是页), 一页是4kb + + * 红黑树每个节点只能村存储一个数据, 需要一次性加载到内存进行比较. 通俗理解就是, 一次与磁盘的交互, 只能进行一次比较 + * 而B+Tree, 一次与磁盘的交互, 可以进行N次比较, 因为BTree一个节点, 有N多数据, 可以一次性加载到内存, 在内存中进行N次比较效率比多次IO磁盘后进行比较要快 + + + # 不同引擎的索引文件存储 + * MyIsam + .frm 表约束 + .MYD 数据 + .MYI 索引 + + * 索引和数据分开 + * 索引的数据, 就是表数据的硬盘地址指针 + + * InnoDB + .frm 表约束 + .ibd 索引 + + * 数据即索引 + + + # 聚集索引 + * 聚集索引, 就是数据和索引存储在一起 + * Innodb表, 只会有一个聚集索引 + * 默认拿主键id作为索引 + * 如果主键id不存在, 那么会使用非空的唯一索引作为聚集索引 + * 如果没主键索引, 也没非空的唯一索引, 那么会自己维护一个id来作为聚集索引(隐式的 6 字节的索引) + + * 页内的记录是按照主键的大小顺序排成一个单向链表 + * 各个存放用户记录的页也是根据页中记录的主键大小顺序排成一个双向链表 + * 各个存放目录项的页也是根据页中记录的主键大小顺序排成一个双向链表 + + * B+树的叶子节点存储的是完整的用户记录 + + + + # 非聚集索引/二级索引/辅助索引 + * 它的任务就是指向聚集索引 + * 它存储的数据是聚集索引的索引值(主键值) + + * 获取到最终的数据, 仍然需要到聚簇索引中再查一遍,这个过程也被称为回表 + + + # 联合索引 + * 每条索引记录, 都是由联合索引的列值 + * 它存储的数据是聚集索引的索引值(主键值) + * 还是需要回表, 才能检索到真正的数据 + + * 存在多个列的联合主键, 从左往右依次比较大小, 直到第一个比出结果 + + + +------------------------ +B+树索引适用的条件 | +------------------------ + # SQL + CREATE TABLE person_info( + name VARCHAR(100) NOT NULL, + birthday DATE NOT NULL, + phone_number CHAR(11) NOT NULL, + country varchar(100) NOT NULL, + KEY idx_name_age_birthday (name, birthday, phone_number) + ); + + # 全值匹配 + * 搜索条件中的列的顺序必须和索引列的定义顺序一致 + * 如果搜索条件中的列的顺序和索引列的顺序不一致, 由于B+树中的数据页和记录是先按第一列的值进行排序的, 不能直接使用二分法快速定位记录, 所以只能扫描所有的记录页 + SELECT * FROM person_info WHERE name = 'Ashburn' AND birthday = '1990-09-27' AND phone_number = '15123983239'; + + # 匹配左边的列 + * 在我们的搜索语句中也可以不用包含全部联合索引中的列, 只包含最左边的就行 + SELECT * FROM person_info WHERE name = 'Ashburn'; + + * 或者包含多个最左边的列 + SELECT * FROM person_info WHERE name = 'Ashburn' AND birthday = '1990-09-27'; + + * 如果第一个条件不是索引列的第一个, 那么不会走索引 + SELECT * FROM person_info WHERE birthday = '1990-09-27'; + + # 匹配列前缀 + * 对于字符串类型的索引列来说, 没必要对该列的值进行精确匹配, 只匹配它的前缀也是可以的, 因为前缀本身就已经是排好序的 + SELECT * FROM person_info WHERE name LIKE 'As%'; + + * 如果只给出后缀或者中间的某个字符串, 就无法通过二分法来快速定位记录位置了, 因为字符串中间有指定的字符串并没有排好序, 所以只能全表扫描了 + SELECT * FROM person_info WHERE name LIKE '%As%'; + SELECT * FROM person_info WHERE name LIKE 'As%'; + + # 匹配范围值 + * 所有记录都是按照索引列的值从小到大的顺序排好序的, 所以这极大的方便我们查找索引列的值在某个范围内的记录 + SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow'; + + * 如果对多个列同时进行范围查找的话, 只有对索引最左边的那个列进行范围查找的时候才能用到B+树索引 + SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow' AND birthday > '1980-01-01'; + + - 只有name值相同的前提下才能用birthday列的值进行排序, 但是name不一定是相同的 + - 就算是name相同, 但是通过name进行范围查找的结果并不是按照birthday列进行排序的, 所以在搜索条件中继续以birthday列进行查找时是用不到B+树索引的 + - 所以 birthday 不走索引 + + # 精确匹配某一列并范围匹配另外一列 + * 同一个联合索引来说, 虽然对多个列都进行范围查找时只能用到最左边那个索引列, 但是如果左边的列是精确查找, 则右边的列可以进行范围查找 + SELECT * FROM person_info WHERE name = 'Ashburn' AND birthday > '1980-01-01' AND birthday < '2000-12-31' AND phone_number > '15100000000'; + + - name是精确查找, 肯定是可以走索引 + - 条件查找后得到的结果的name值都是相同的, 它们本身是先按照 name 排序, 再按照 birthday 排序, 所以 name 相同的记录, birthday 是有序的, 它的检索可以走索引 + - birthday 条件后得到的记录结果可能不同, 所以后面个字段 phone_number 不能走索引 + + # 用于排序 + * 查询语句需要对查询出来的记录按照某种规则进行排序, + * 对于不适用B+树索引进行排序的情况只能把记录都加载到内存中, 再用一些排序算法, 比如快速排序, 归并排序等等在内存中对这些记录进行排序, 然后再把排好序的结果集返回到客户端 + + * 如果ORDER BY子句里使用到了索引列, 就有可能省去在内存中排序的步骤 + SELECT * FROM person_info ORDER BY name, birthday, phone_number LIMIT 10; + + - 排序的字段, 跟联合索引定义的一摸一样, 索引本身是定义好顺序的, 所以这个直接提取数据就完事儿 + + * 同样的, 匹配索引左边的列的形式可以使用部分的B+树索引 + SELECT * FROM person_info ORDER BY name, phone_number LIMIT 10; + + - name 会使用到索引, 在内存只用对 phone_number 进行排序就好 + + # 不可以使用索引进行排序的几种情况 + * ASC, DESC混用 + - 对于使用联合索引进行排序的场景, 要求各个排序列的排序顺序是一致的, 也就是要么各个列都是ASC规则排序, 要么都是DESC规则排序 + SELECT * FROM person_info ORDER BY name, birthday DESC LIMIT 10; + + - 先从索引的最左边确定name列最小的值, 然后找到name列等于该值的所有记录, 然后从name列等于该值的最右边的那条记录开始往左找10条记录 + - 如果name列等于最小的值的记录不足10条, 再继续往右找name值第二小的记录, 重复上边过程, 直到找到10条记录为止 + + * WHERE子句中出现非排序使用到的索引列 + - WHERE子句中出现了非排序使用到的索引列, 那么排序依然是使用不到索引 + SELECT * FROM person_info WHERE country = 'China' ORDER BY name LIMIT 10; + + - 先根据 country 进行过滤, 这个是没法走索引的 + + * 排序列包含非同一个索引的列 + - 排序的多个列不是一个索引里的, 这种情况也不能使用索引进行排序 + SELECT * FROM person_info ORDER BY name, country LIMIT 10; + + - name和country并不属于一个联合索引中的列, 所以无法使用索引进行排序 + + * 排序列使用了复杂的表达式 + - 要想使用索引进行排序操作, 必须保证索引列是以单独列的形式出现, 而不是修饰过的形式 + SELECT * FROM person_info ORDER BY UPPER(name) LIMIT 10; + + - 使用了UPPER函数修饰过的列就不是单独的列, 这样就无法使用索引进行排序 + + + # 用于分组 + * 方便统计表中的一些信息, 会把表中的记录按照某些列进行分组 + SELECT name, birthday, phone_number, COUNT(*) FROM person_info GROUP BY name, birthday, phone_number + + - 如果有索引的话, 恰巧这个分组顺序又和的B+树中的索引列的顺序是一致的 + - 而B+树索引又是按照索引列排好序的, 这正好, 所以可以直接使用B+树索引进行分组 + + * 果没有索引的话, 这个分组过程全部需要在内存里实现 + * 和使用B+树索引进行排序是一个道理, 分组列的顺序也需要和索引列的顺序一致, 也可以只使用索引列中左边的列进行分组 + + +------------------------ +如何挑选索引 | +------------------------ + # 只为用于搜索, 排序或分组的列创建索引 + * 只为出现在WHERE子句中的列, 连接子句中的连接列, 或者出现在ORDER BY或GROUP BY子句中的列创建索引 + * 普通查询列表中的列就不要创建 + + # 考虑列的基数 + * 列的基数指的是某一列中不重复数据的个数 + * 在记录行数一定的情况下, 列的基数越大, 该列中的值越分散, 列的基数越小, 该列中的值越集中 + * 列的基数指标非常重要, 直接影响是否能有效的利用索引 + - 假设某个列的基数为1, 也就是所有记录在该列中的值都一样 + - 那为该列建立索引是没有用的, 因为所有值都一样就无法排序, 无法进行二分法查找了 + + * 最好为那些列的基数大的列建立索引, 为基数太小列的建立索引效果可能不好 + + # 索引列的类型尽量小 + * 定义表结构的时候要显式的指定列的类型 + * 数据类型越小, 在查询时进行的比较操作越快 + * 数据类型越小, 索引占用的存储空间就越少, 在一个数据页内就可以放下更多的记录, 从而减少磁盘I/O带来的性能损耗, + * 也就意味着可以把更多的数据页缓存在内存中, 从而加快读写效率 + + * 对于表的主键来说更加适用, 因为不仅是聚簇索引中会存储主键值, 其他所有的二级索引的叶子节点处都会存储一份记录的主键值 + * 如果主键适用更小的数据类型, 也就意味着节省更多的存储空间 + + # 索引字符串值的前缀 + * 一个字符串其实是由若干个字符组成, 如果在MySQL中适用utf8字符集去存储字符串的话, 编码一个字符需要占用1~3个字节 + * 假设字符串很长, 那存储一个字符串就需要占用很大的存储空间, 在需要为这个字符串列建立索引时, 那就意味着在对应的B+树中有这么两个问题 + 1. B+树索引中的记录需要把该列的完整字符串存储起来, 而且字符串越长, 在索引中占用的存储空间越大 + 2. 如果B+树索引中索引列存储的字符串很长, 那在做字符串比较时会占用更多的时间 + + * 索引的设计者提出了个方案 --- 只对字符串的前几个字符进行索引 + - 通过字符串的前几个字符已经能大概排序字符串了, 剩下不能排序的可以通过遍历进行查找 + - 这样只在B+树中存储字符串的前几个字符的编码, 既节约空间, 又减少了字符串的比较时间, 还大概能解决排序的问题 + + * 在建表语句中只对某列的前10个字符进行索引可以这么写: + CREATE TABLE person_info( + name VARCHAR(100) NOT NULL, + birthday DATE NOT NULL, + phone_number CHAR(11) NOT NULL, + country varchar(100) NOT NULL, + // name(10), 仅仅对前10个字符进行索引 + KEY idx_name_age_birthday (name(10), birthday, phone_number) + ); + + * 如果使用了索引列前缀, 那么在使用该字段进行排序的时候, 无法走索引 + SELECT * FROM person_info ORDER BY name LIMIT 10; + + * 二级索引中不包含完整的name列信息, 所以无法对前十个字符相同, 后边的字符不同的记录进行排序 + * 也就是使用索引列前缀的方式无法支持使用索引排序 + + + # 尽量使用联合索引 + * 搜索条件中有多个列的话, 最好为这些列建立一个联合索引, 而不是分别为每个列建立一个索引(因为每建一个索引都会维护一棵B+树) + + # 让索引列在比较表达式中单独出现 + * 如果索引列在比较表达式中不是以单独列的形式出现, 而是以某个表达式, 或者函数调用形式出现的话, 不会走索取 + * 例如:my_col 是一个索引列 + WHERE my_col * 2 < 4 + * my_col列并不是以单独列的形式出现的, 而是以my_col * 2这样的表达式的形式出现的 + * 存储引擎会依次遍历所有的记录, 计算这个表达式的值是不是小于4 + + WHERE my_col < 4/2 + * my_col列并是以单独列的形式出现的, 这样的情况可以直接使用B+树索引 + + # 主键插入顺序 + * InnoDB存储引擎的表来说, 在没有显式的创建索引时, 表中的数据实际上都是存储在聚簇索引的叶子节点的 + * 而记录又是存储在数据页中的, 数据页和记录又是按照记录主键值从小到大的顺序进行排序, + * 如果中途插入一个不大不小的主键值, 就会产生页面分裂和记录移位(树的子平衡) + + * 意味着:性能损耗, 所以如果想尽量避免这样无谓的性能损耗, 最好让插入的记录的主键值依次递增, 这样就不会发生这样的性能损耗了 + * 所以建议:让主键具有AUTO_INCREMENT, 让存储引擎自己为表生成主键, 而不是手动插入 + + # 冗余和重复索引 + * 有时候会有意或者无意的就对同一个列创建了多个索引 + * 维护这个索引只会增加维护的成本, 并不会对搜索有什么好处 + + # 覆盖索引 + * 最好在查询列表(结果集字段)里只包含索引列 + SELECT * FROM person_info WHERE name = 'Ashburn'; + + - 因为是检索所有的记录, 所以联合检索出结果后, 还得去执行回表, 检索其他的数据记录 + + * 如果仅仅检索联合查询的结果, 那么就不必执行回表操作了 + * 省去了回表操作带来的性能损耗, 把这种只需要用到索引的查询方式称为索引覆盖 + + * 如果业务需要查询出索引以外的列, 那还是以保证业务需求为重 + * 但是很不鼓励用 * 号作为查询列表, 最好把我们需要查询的列依次标明 + + + + + + + + + + + diff --git "a/MySQL/MySQL-core-\351\224\201.java" "b/MySQL/MySQL-core-\351\224\201.java" new file mode 100644 index 00000000..560f1075 --- /dev/null +++ "b/MySQL/MySQL-core-\351\224\201.java" @@ -0,0 +1,383 @@ +---------------------------- +锁 | +---------------------------- + # 参考 + https://mp.weixin.qq.com/s/V9nuZ-TNSI_avMVbCd1T7Q + + # 并发事务访问相同记录的情况大致可以划分为3种 + 读 - 读 + * 读不会有啥问题 + + 写 - 写 + * 在这种情况下会发生脏写的问题, 任何一种隔离级别都不允许这种问题的发生 + * 多个未提交事务相继对一条记录做改动时, 需要让它们排队执行 + * 这个排队的过程其实是通过锁来实现的 + + 读-写/写-读 + * 这种情况下可能发生脏读, 不可重复读, 幻读的问题 + * 解决这些问题有两种办法 + 1, 读操作使用MVCC,写操作加锁(事务笔记已经说了),读写不冲突, 性能比较高 + 2, 读写, 都加锁, 读写都是需要排队, 性能比较差 + * 一个事务在写记录的时候就给这条记录加锁, 那么其他事务就无法继续读取该记录了, 所以也就不会有脏读问题的产生了 + * 一个事务读取记录时就给该记录加锁, 那么另一个事务就无法修改该记录, 自然也不会发生不可重复读了 + * 当前事务在第一次读取记录时那些幻影记录并不存在, 所以读取的时候加锁就有点尴尬 --—— 因为你并不知道给谁加锁(但是可以解决) + + # 锁 + * 锁其实是一个内存中的结构, 在事务执行前本来是没有锁的, 也就是说一开始是没有锁结构和记录进行关联的 + * 当一个事务想对这条记录做改动时, 首先会看看内存中有没有与这条记录关联的锁结构, 当没有的时候就会在内存中生成一个锁结构与之关联 + * 一个锁里面有很多的信息 + trx信息 + * 代表这个锁结构是哪个事务生成的 + is_waiting + * 代表当前事务是否在等待 + * is_waiting属性如果是false,我们把这个场景就称之为获取锁成功, 或者加锁成功, 然后就可以继续执行操作了 + + * 一个锁的过程(update) + 1. A事务操作数据, 发现该记录还没锁的信息, 于是设置了一个关联:is_waiting=false, 获取到了锁, 开始执行 + + 2. B事务操作数据, 发现存在了锁信息, 于是也设置了一个关联:is_waiting=true,当前事务需要等待(获取锁失败, 或者加锁失败, 或者没有成功的获取到锁 + + 3. A线程事务提交之前, 会把生成的锁结构释放掉, 然后看看还有没有别的事务在等待获取锁, + * 发现了事务B还在等待获取锁, 所以把事务B对应的锁结构的is_waiting属性设置为false + * 然后把该事务对应的线程唤醒, 让它继续执行, 此时事务B就算获取到锁了 + +---------------------------- +一致性读(Consistent Reads) | +---------------------------- + # 事务利用MVCC进行的读取操作称之为一致性读, 或者一致性无锁读, 有的地方也称之为快照读 + # 所有普通的SELECT语句(plain SELECT)在READ COMMITTED, REPEATABLE READ隔离级别下都算是一致性读 + # 一致性读并不会对表中的任何记录做加锁操作, 其他事务可以自由的对表中的记录做改动 + +---------------------------- +锁定读 (Locking Reads) 和写| +---------------------------- + # 共享锁, 英文名: Shared Locks, 简称S锁 + * 在事务要读取一条记录时, 需要先获取该记录的S锁 + * 共享锁语句 + SELECT ... LOCK IN SHARE MODE; + + * 当前事务执行了该语句, 那么它会为读取到的记录加S锁, 这样允许别的事务继续获取这些记录的S锁 + * 比方说别的事务也使用SELECT ... LOCK IN SHARE MODE语句来读取这些记录 + + * 但是不能获取这些记录的X锁(比方说使用SELECT ... FOR UPDATE语句来读取这些记录, 或者直接修改这些记录) + * 如果别的事务想要获取这些记录的X锁, 那么它们会阻塞, 直到当前事务提交之后将这些记录上的S锁释放掉 + + # 独占锁, 英文名:Exclusive Locks, 简称X锁(也称为排他锁) + * 在事务要改动一条记录时, 需要先获取该记录的X锁 + * 独占锁语句 + SELECT ... FOR UPDATE; + + * 如果当前事务执行了该语句, 那么它会为读取到的记录加X锁, 这样既不允许别的事务获取这些记录的S锁 + * 比方说别的事务使用SELECT ... LOCK IN SHARE MODE语句来读取这些记录, 更不允许获取这些记录的X锁 + * 如果别的事务想要获取这些记录的S锁或者X锁, 那么它们会阻塞, 直到当前事务提交之后将这些记录上的X锁释放掉 + + + # 锁的兼容 + 兼容性 X S + X 不兼容 不兼容 + S 不兼容 兼容 + + * 只有 s s 是兼容的 + + # 写操作 + DELETE + * DELETE操作的过程其实是先在B+树中定位到这条记录的位置, 然后获取一下这条记录的X锁 + * 然后再执行delete mark操作, 我们也可以把这个定位待删除记录在B+树中位置的过程看成是一个获取X锁的锁定读 + + UPDATE + * 分为三种情况 + 1. 如果'未修改该记录的键值', 并且'被更新的列占用的存储空间在修改前后未发生变化', + * 则先在B+树中定位到这条记录的位置, 然后再获取一下记录的X锁, 最后在原记录的位置进行修改操作 + * 其实也可以把这个定位待修改记录在B+树中位置的过程看成是一个获取X锁的锁定读 + + 2. 如果'未修改该记录的键值'并且'至少有一个被更新的列占用的存储空间在修改前后发生变化', + * 则先在B+树中定位到这条记录的位置, 然后获取一下记录的X锁, 将该记录彻底删除掉(就是把记录彻底移入垃圾链表) + * 最后再插入一条新记录, 这个定位待修改记录在B+树中位置的过程看成是一个获取X锁的锁定读 + * 新插入的记录由INSERT操作提供的隐式锁进行保护 + + 3. '如果修改了该记录的键值' + * 则相当于在原记录上做DELETE操作之后再来一次INSERT操作 + * 加锁操作就需要按照DELETE和INSERT的规则进行了 + + INSERT + * 新插入一条记录的操作一般并不加锁,但在一些特殊情况下INSERT操作也是会获取锁的 + * InnoDB的通过一种称之为隐式锁的东东来保护这条新插入的记录在本事务提交前不被别的事务访问 + +------------------------ +多粒度锁 | +------------------------ + # 事务也可以在表级别进行加锁 + * 自然就被称之为表级锁或者表锁 + * 对一个表加锁影响整个表中的记录, 这个锁的粒度比较粗 + + # 事务也可以在行级别进行加锁 + * 对一条记录加锁影响的也只是这条记录而已, 我们就说这个锁的粒度比较细 + + # 给表加S锁 + 别的事务可以继续获得该表的S锁 + 别的事务可以继续获得该表中的某些记录的S锁 + 别的事务不可以继续获得该表的X锁 + 别的事务不可以继续获得该表中的某些记录的X锁 + + # 给表加X锁 + 别的事务不可以继续获得该表的S锁 + 别的事务不可以继续获得该表中的某些记录的S锁 + 别的事务不可以继续获得该表的X锁 + 别的事务不可以继续获得该表中的某些记录的X锁 + + + # 意向锁(Intention Locks) + * 我们在对表上锁时,需要确定表中的行是否被上锁, 如果使用遍历, 效率很低 + * 于是设计了意向锁 + + * 意向共享锁,英文名:Intention Shared Lock, 简称IS锁 + * 当事务准备在某条记录上加S锁时, 需要先在表级别加一个IS锁 + + * 意向独占锁,英文名: Intention Exclusive Lock, 简称IX锁 + * 当事务准备在某条记录上加X锁时, 需要先在表级别加一个IX锁 + + * IS, IX锁是表级锁, 它们的提出仅仅为了在之后加表级别的S锁和X锁时可以快速判断表中的记录是否被上锁, 以避免用遍历的方式来查看表中有没有上锁的记录 + * 也就是说其实IS锁和IX锁是兼容的, IX锁和IX锁是兼容的 + + # 兼容性 + 兼容性 X IX S IS + X 不兼容 不兼容 不兼容 不兼容 + IX 不兼容 兼容 不兼容 兼容 + S 不兼容 不兼容 兼容 兼容 + IS 不兼容 兼容 兼容 兼容 + +------------------------ +表锁 | +------------------------ + # 不同存储引擎对锁的支持也是不一样的, 这里仅仅说 InnoDB的锁 + + # InnoDB存储引擎既支持表锁, 也支持行锁 + * 表锁实现简单, 占用资源较少, 不过粒度很粗, 性能比较差 + * 行锁粒度更细, 可以实现更精准的并发控制 + + # 表级别的 S锁, X锁 + * 在对某个表执行SELECT, INSERT, DELETE, UPDATE语句时, InnoDB存储引擎是不会为这个表添加表级别的S锁或者X锁的。 + + * 在对某个表执行一些诸如ALTER, TABLE, DROP TABLE这类的DDL语句时, 其他事务对这个表并发执行诸如SELECT, INSERT, DELETE, UPDATE的语句会发生阻塞 + * 同理, 某个事务中对某个表执行SELECT, INSERT, DELETE, UPDATE语句时, 在其他会话中对这个表执行DDL语句也会发生阻塞 + + * 这个过程其实是通过在server层使用一种称之为元数据锁(英文名:Metadata Locks, 简称MDL)来实现的 + * 一般情况下也不会使用InnoDB存储引擎自己提供的表级别的S锁和X锁 + + * 手动获取InnoDB存储引擎提供的表的S锁或者X锁可以这么写 + LOCK TABLES [表名] READ InnoDB存储引擎会对表t加表级别的S锁 + LOCK TABLES [表名] WRITE InnoDB存储引擎会对表t加表级别的X锁 + + * 尽量避免在使用InnoDB存储引擎的表上使用LOCK TABLES这样的手动锁表语句, 它们并不会提供什么额外的保护, 只是会降低并发能力而已 + * 关于表级别的S锁和X锁了解一下就罢了 + + # 表级别的 IS锁, IX锁 + * 对使用InnoDB存储引擎的表的某些记录加S锁之前, 那就需要先在表级别加一个IS锁 + * 对使用InnoDB存储引擎的表的某些记录加X锁之前, 那就需要先在表级别加一个IX锁 + + * IS锁和IX锁的使命只是为了后续在加表级别的S锁和X锁时判断表中是否有已经被加锁的记录, 以避免用遍历的方式来查看表中有没有上锁的记录 + + # 表级别的AUTO-INC锁 + * 可以为表的某个列添加AUTO_INCREMENT属性, 之后在插入记录时, 可以不指定该列的值, 系统会自动为它赋上递增的值 + * 实现这种自动给AUTO_INCREMENT修饰的列递增赋值的原理主要是两个 + 1. 采用AUTO-INC锁 + * 也就是在执行插入语句时就在表级别加一个AUTO-INC锁 + * 然后为每条待插入记录的AUTO_INCREMENT修饰的列分配递增的值, 在该语句执行结束后, 再把AUTO-INC锁释放掉 + * 这样一个事务在持有AUTO-INC锁的过程中, 其他事务的插入语句都要被阻塞, 可以保证一个语句中分配的递增值是连续的 + + * AUTO-INC锁的作用范围只是单个插入语句, 插入语句执行完成后, 这个锁就被释放了, 跟其他锁在事务结束时释放是不一样的 + + * 锁住整个过程, 获取自增值, 插入 + + + 2. 采用轻量级的锁 + * 在为插入语句生成AUTO_INCREMENT修饰的列的值时获取一下这个轻量级锁 + * 然后生成本次插入语句需要用到的AUTO_INCREMENT列的值之后, 就把该轻量级锁释放掉, 并不需要等到整个插入语句执行完才释放锁 + + * 不会锁住整个的插入过程, 只是锁住获取自增值的过程 + + + + * 如果插入语句在执行前不可以确定具体要插入多少条记录(无法预计即将插入记录的数量) + * 比方说使用INSERT ... SELECT、REPLACE ... SELECT或者LOAD DATA这种插入语句 + * 一般是使用AUTO-INC锁为AUTO_INCREMENT修饰的列生成对应的值 + + * 如果我们的插入语句在执行前就可以确定具体要插入多少条记录, 那么就可以采用轻量级锁 + + * 使用参数控制使用哪个自增锁 + innodb_autoinc_lock_mode + + 0 一律采用AUTO-INC锁 + 1 两种方式混合使用(也就是在插入记录数量确定时采用轻量级锁, 不确定时使用AUTO-INC锁) + 2 一律采用轻量级锁 + * 可能会造成不同事务中的插入语句为AUTO_INCREMENT修饰的列生成的值是交叉的 + * 在有主从复制的场景中是不安全的 + + +------------------------ +行锁 | +------------------------ + # 行锁, 也称为记录锁, 顾名思义就是在记录上加的锁 + * 但是行锁是玩儿出花儿来的, 有很多名堂 + + # Record Locks + * 正经记录锁, LOCK_REC_NOT_GAP + * 正经记录锁是有S锁和X锁之分的, 让分别称之为'S型正经记录锁'和'X型正经记录锁' + + * 当一个事务获取了一条记录的S型正经记录锁后, 其他事务也可以继续获取该记录的S型正经记录锁, 但不可以继续获取X型正经记录锁 + * 当一个事务获取了一条记录的X型正经记录锁后, 其他事务既不可以继续获取该记录的S型正经记录锁, 也不可以继续获取X型正经记录锁 + + + # Gap Locks + * 间隙锁, LOCK_GAP + * REPEATABLE READ隔离级别下是可以解决幻读问题的, 可以使用MVCC方案解决, 也可以采用加锁方案解决 + + * 问题在于事务在第一次执行读取操作时, 幻影记录尚不存在, 无法给这些幻影记录加上正经记录锁 + * 间隙锁的存在, 就可以解决这些问题 + + * 演示表 + +--+-----+ + |id|name | + +--+-----+ + |1 |Kevin| + +--+-----+ + |5 |Litch| + +--+-----+ + + * 如果为id=5的这行记录添加间隙锁, 意味着不允许别的事务在id值为5的记录前边的间隙插入新记录 + * 其实就是id列的值(1, 5)这个区间的新记录, 是不允许立即插入的, 会阻塞直到当前事务提交, Gap Locks 释放 + + + * gap锁只是不允许其他事务往这条记录前边的间隙插入新记录, 那对于最后一条记录之后的间隙是允许的 + * 2,3,4 无法插入, 但是 6, 7, 8..... 是可以插入成功 + + * 数据页存在两条伪记录 + Infimum记录, 表示该页面中最小的记录 + Supremum记录, 表示该页面中最大的记录 + + * 为了防止其他事务插入区间 (5, 无穷大)的新纪录, 可以给索引中的最后一条记录(本例为:5),所数据页的Supremum记录加上一个gap锁 + + + * 'gap锁的提出仅仅是为了防止插入幻影记录而提出的' + * 虽然有共享gap锁和独占gap锁这样的说法, 但是它们起到的作用都是相同的 + * 对一条记录加了gap锁(不论是共享gap锁还是独占gap锁), 并不会限制其他事务对这条记录加正经记录锁或者继续加gap锁 + + # Next-Key Locks + * next-key锁, LOCK_ORDINARY + * next-key锁的本质就是一个正经记录锁和一个gap锁的合体, 它既能保护该条记录, 又能阻止别的事务将新记录插入被保护记录前边的间隙 + + # Insert Intention Locks + * 插入意向锁, LOCK_INSERT_INTENTION + * 个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了所谓的gap锁(next-key锁也包含gap锁) + * 如果有的话,插入操作需要等待, 直到拥有gap锁的那个事务提交 + + * InnoDB规定事务在等待的时候也需要在内存中生成一个锁结构, 表明有事务想在某个间隙中插入新记录 + * 这就是插入意向锁 + + * 执行的过程 + 1. 事务1, 对id=5开启了 Gap 锁 + 2. 事务2, 插入 id=4, 未能获取到插入意向锁, 阻塞 + 3. 事务3, 插入 id=3, 未能获取到插入意向锁,阻塞 + 4. 事务1, 提交, 释放 Gap 锁 + 5. 事务2,3 获取到插入意向锁(is_waiting=false), 执行插入, 它们不会互相阻塞 + + * 插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁 + + + # 隐式锁 + * 一个事务在执行INSERT操作时, 如果即将插入的间隙已经被其他事务加了gap锁, 那么本次INSERT操作会阻塞 + * 并且当前事务会在该间隙上加一个插入意向锁, 否则一般情况下INSERT操作是不加锁的 + + * 问题: 一个事务首先插入了一条记录(此时并没有与该记录关联的锁结构) + 情况1 + * 然后另一个事务执行立即使用SELECT ... LOCK IN SHARE MODE语句读取这条事务, 也就是在要获取这条记录的S锁 + * 或者使用SELECT ... FOR UPDATE语句读取这条事务或者直接修改这条记录, 也就是要获取这条记录的X锁 + * 如果允许这种情况的发生,那么可能产生脏读问题 + + 情况2 + * 另一个事务立即修改这条记录, 也就是要获取这条记录的X锁 + * 如果允许这种情况的发生, 那么可能产生脏写问题 + + * 解决 + 情况1: + * 对于聚簇索引记录来说, 有一个trx_id隐藏列, 该隐藏列记录着最后改动该记录的事务id + * 如果在当前事务中新插入一条聚簇索引记录后, 该记录的trx_id隐藏列代表的的就是当前事务的事务id + * 如果其他事务此时想对该记录添加S锁或者X锁时, 首先会看一下该记录的trx_id隐藏列代表的事务是否是当前的活跃事务(未提交的/回滚的) + + * 如果是的话, 那么就帮助当前事务创建一个X锁(也就是为当前事务创建一个锁结构,is_waiting=false) + * 然后自己进入等待状态(也就是为自己也创建一个锁结构, is_waiting=true) + + + 情况2: + * 二级索引记录, 本身并没有trx_id隐藏列, 但是在二级索引页面的Page Header部分有一个PAGE_MAX_TRX_ID属性 + * 该属性代表对该页面做改动的最大的事务id, 如果PAGE_MAX_TRX_ID属性值小于当前最小的活跃事务id + * 那么说明对该页面做修改的事务都已经提交了, 否则就需要在页面中定位到对应的二级索引记录, 然后回表找到它对应的聚簇索引记录 + * 然后再重复情况1的做法 + + + + * 一个事务对新插入的记录可以不显式的加锁(生成一个锁结构) + * 但是由于事务id的存在, 相当于加了一个隐式锁 + + * 别的事务在对这条记录加S锁或者X锁时, 由于隐式锁的存在, 会先帮助当前事务生成一个锁结构, 然后自己再生成一个锁结构后进入等待状态 + + # 行锁的实现原理就是锁住聚集索引, 如果查询的时候, 没有正确地命中索引, MySql 优化器将会抛弃行锁, 使用表锁 + + # 总结 + Record Locks + * 锁定自己 + + Gap Locks + * 锁定间隙, 不包含自己 + + Next-Key Locks + * 锁定间隙, 以及自己 + + Insert Intention Locks + * 在gap锁阻塞的试试, 添加的意向锁 + + 隐式锁 + * 使用事务id的锁, 防止新插入的数据, 被其他事务加锁, 而导致脏读/脏写 + + +------------------------ +锁的内存结 | +------------------------ + # 一条记录加锁的本质就是在内存中创建一个锁结构与之关联 + # 一个事务对多条记录加锁, 如果需要对每条记录都创建一个锁, 是非常的可怕, 浪费资源的 + # 怎么把多个记录的锁, 放在同一个锁结构里面? 需要符合几个条件 + 1, 在同一个事务中进行加锁操作 + 2, 被加锁的记录在同一个页面中 + 3, 加锁的类型是一样的 + 4, 等待状态是一样的 + + # 一个锁结构的结构 + 锁事务信息 + 索引信息 + 表锁/行锁信息 + 表锁: + 表信息, + 其他信息 + 行锁: + Space ID + Page Number + n_bit + * 一条记录就对应着一个比特位, 一个页面中包含很多记录 + * 用不同的比特位来区分到底是哪一条记录加了锁,为此在行锁结构的末尾放置了一堆比特位, + * 这个n_bits属性代表使用了多少比特 + type_mode + * 锁的模式(lock_mode), 占用低4位, 可选的值如下 + LOCK_IS(十进制的0): 表示共享意向锁,也就是IS锁。 + LOCK_IX(十进制的1): 表示独占意向锁,也就是IX锁。 + LOCK_S(十进制的2): 表示共享锁,也就是S锁。 + LOCK_X(十进制的3): 表示独占锁,也就是X锁。 + LOCK_AUTO_INC(十进制的4): 表示AUTO-INC锁。 + + * 锁的类型(lock_type), 占用第5~8位, 不过现阶段只有第5位和第6位被使用 + LOCK_TABLE(十进制的16), 也就是当第5个比特位置为1时, 表示表级锁 + LOCK_REC(十进制的32), 也就是当第6个比特位置为1时, 表示行级锁 + + ... + + 其他信息 + 一堆比特位 \ No newline at end of file diff --git "a/MySQL/MySQL-\344\272\213\345\212\241\344\270\216\345\271\266\345\217\221.java" "b/MySQL/MySQL-\344\272\213\345\212\241\344\270\216\345\271\266\345\217\221.java" deleted file mode 100644 index 93c3390f..00000000 --- "a/MySQL/MySQL-\344\272\213\345\212\241\344\270\216\345\271\266\345\217\221.java" +++ /dev/null @@ -1,169 +0,0 @@ ------------------------ -MySQL-事务 | ------------------------ - # 讲事务这东西,都是拿银行转账来说事儿. - # 事务跟存储引擎相关.有些引擎是不支持事务的 - ------------------------ -MySQL-CMD操作事务 | ------------------------ - # 开启事务 - START TRANSACTION; - # 开启了之后,就会把操作记录到事务日志 - # 还有一种方式设置. - set autocommit=1; - * 该方式,直接针对'当前连接'进行设置,以后的每次增删改操作,默认都是需要手动提交 - * 值也可以是:ON 也是代表开启 - - # 执行操作 - update... - delete... - insert... - - # 提交/回滚事务 - COMMIT; - ROLLBACK; - ------------------------ -MySQL-事务回滚点 | ------------------------ - # 其实我理解就是传说中的保存点. - # 在某一个成功的操作完成之后,后续的操作有可能成功.有可能失败. - # 在当前的位置,设置一个保存点,让后面的失败操作,返回到该位置 - # savepint [回滚点名字]; - # rolback [回滚点名字]; - # 往往是在事务操作比较多的时候,会用到 - # 操作步骤 - 1,开启事务 - ... 逻辑操作 - ... 设置回滚点1 - ... 设置回滚点2 - ... - 2,回滚到指定的点 - ... 继续逻辑操作 - 3,提交/commit - ... - - # 事务一旦回滚,就会清空事务日志.设置了点之后,就仅仅清空点之后的事务日志 - ------------------------ -MySQL-事务原理 | ------------------------ - # 事务开启之后,所有的操作都会临时保存到事务日志中 - # 而事务日志只有在得到commit命令,才会同步到数据表,其他的任何情况都会清空 - ------------------------ -自动事务 | ------------------------ - # MySql中默认都是自动的事务处理,用户的每个操作.都会自己开启,提交事务 - # 系统通过: autocommit 变量俩进行控制 - # show variables like 'autocommit'; - * ON :表示是自动事务 - * OFF :表示非自动事务 - # 关闭自动提交 - set autocommit = off; - * 值为0也可以,因为0代表 false - # 关闭之后,每次操作都需要执行commit命令 - # 通常都会使用自动事务,当需要使用到事务的时候,我们都是采取临时的事务 - # 该操作,仅仅是对当前连接/线程有效 - # 如果需要彻底修改,那么需要去修改配置文件 - ------------------------ -事务特性 | ------------------------ - 事务的四大特效: - # A-Atomic 原子性 - * 一组事务,要么全部提交成功,要么全部失败 - - # C-Con... 一致性 - * 事务操作的前后,数据表中的数据.没有变化 - - # I-Isol.. 隔离性 - * 事务的操作是相互隔离,不受影响的 - - #D-D... 持久性 - * 事务一旦提交,就必须要保存到数据库,永久的改变数据表 - ------------------------ -锁机制 | ------------------------ - # 因为事务的'隔离性质',所以在多线程的时候,MYSQL会有一个锁机制来保证数据的安全 - # INNODB引擎默认的锁机制是:行级锁 - # 但是如果在事务操作的过程中,没有使用到索引(就是说WHERE条件没有使用到主键).那么系统会自动全表检索数据,那么就会自动升级为表锁 - # 行锁:只有当前行被锁定,别的线程不能进行操作 - * 一个线程开启了事务,进行了事务操作之后(使用到了索引,就会单单锁定该行记录) - * 另外的线程对该条记录进行增/删/该.会被线程阻塞.直到当前事务提交或者回滚 - - # 表锁:只有当前表被锁定,别的线程不能进行操作 - * 一个线程开启了事务,进行了事务操作之后(如果操作没有使用到索引,直接锁整张表), - * 另外的线程对该表进行增/删/改.会被线程阻塞.直到当前事务提交或者回滚 - ------------------------ -事务的隔离级别 | ------------------------ - # 并发读问题 - * 脏读 :读到另一个事务未提交的数据 - * 不可重复读 :两次读取不一致 - * 幻读(虚读) :读取到另一个事务已经提交的数据 - - - # 四大隔离级别 - * 是用来防止上面三种读问题 - * 4个等级的事物隔离级别,在相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同的事务隔离级别能够解决数据并发问题的能力是不同的. - 1,Serializable(串行化)(坚决不用) - * 不会出现任何问题,因为它对同一数据的访问是串行的,并非并发访问的! - * 性能最差(能处理任何问题) - 2,Repeatable read(可重复读取)(MYSQL默认) - * 防止脏读和不可重复读,不能处理幻读问题 - * 性能比 Serializable 好 - 3,Read Committed(读已提交数据)(Oracle默认) - * 防止脏读和不可重复读,没有处理不可重复读,也没处理幻读 - * 性能别 Serializable 好 - 4,Read Uncommitted 读未提交数据(坚决不用) - * 可以出现任何事务并发问题,没有处理任何问题.隔离级别最烂 - * 性能最好 - - # 并发更新问题 - - # 查看数据库的隔离级别 - select @@tx_isolation - * MYSQL的默认隔离级别(一般没有必要去修改) - +-----------------+ - | @@tx_isolation | - +-----------------+ - | REPEATABLE-READ | - +-----------------+ - - # 修改数据库的隔离级别 - settransaction isolationlevel [四个隔离级别选一个] - - # JDBC设置隔离级别 - Connection conn - conn.setTransactionlsolation(int leve); - 参数可选值如下 - Connection.TRANSACTION_READ_UNCOMMITTED - Connection.TRANSACTION_READ_COMMITTED - ... ... 静态变量,字段参阅API - ------------------------ -JDBC操作事务 | ------------------------ - try{ - conn.setAutoCommit(false);//开启事物 - ... - ... - ... - conn.commit();//try语句的最后提交事务 - }catch(Exception e){ - conn.rollback();//回滚事务 - } - - - - - - - - - - diff --git "a/MySQL/MySQL-\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253.java" "b/MySQL/MySQL-\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253.java" new file mode 100644 index 00000000..f59053f3 --- /dev/null +++ "b/MySQL/MySQL-\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253.java" @@ -0,0 +1,95 @@ +1,璇绘湭鎻愪氦 Read uncommitted + * 璇诲彇鍒板叾浠栦簨鍔℃湭鎻愪氦鐨勬暟鎹 + +2,璇诲凡鎻愪氦 Read committed(Oracle,SqlServer榛樿) + * 鍙兘璇诲彇鍒板叾浠栦簨鍔″凡缁忔彁浜ょ殑鏁版嵁 + * 鑳藉瑙e喅鑴忚闂 + * 瀛樺湪涓嶅彲閲嶅璇婚棶棰 + * A 寮濮嬭鍙栦簨鍔 ,璇诲彇 = 10 + * B 寮濮婾PDATE浜嬪姟,淇敼 = 15 鎻愪氦 + * A 鍐嶆璇诲彇 = 15 + * A 鍦ㄥ悓涓浜嬪姟涓殑涓ゆ璇诲彇鐨勫间笉涓鏍 + * 瀛樺湪骞昏闂 + +3,鍙噸澶嶈 Repeatable read(mysql 榛樿) + * 鍚屼竴璇诲彇浜嬪姟涓,澶氭璇诲彇,璇ュ煎缁堜竴鏍,涓嶄細鍥犱负鍏朵粬浜嬪姟杩涜浜嗕慨鏀规彁浜よ屽凡鏀瑰彉 + * 鍙互瑙e喅涓嶅彲閲嶅璇婚棶棰 + * 涓嶅彲閲嶅璇诲搴旂殑鏄慨鏀,鍗砋PDATE鎿嶄綔 + * 瀛樺湪骞昏闂 + * 骞昏闂瀵瑰簲鐨勬槸鎻掑叆INSERT鎿嶄綔,鑰屼笉鏄疷PDATE鎿嶄綔 + +4,搴忓垪鍖 Serializable + * 鑳藉鐞嗕换浣曢棶棰 + * 鎬ц兘鏈宸 + + +------------ +鑴忚 + * 璇诲彇鍒板叾浠栦簨鍔℃湭鎻愪氦鐨勯棶棰 + +涓嶅彲閲嶅璇 + * 鍚屼竴浜嬪姟鍐,涓ゆ璇诲彇鏁版嵁涓嶄竴鑷 + +骞昏 + * 璇诲彇鍒板叾浠栦簨鍔℃彁浜ょ殑鏁版嵁 + * A 寮濮嬩簡鏇存柊浜嬪姟,瀵硅〃涓殑鎵鏈夋暟鎹繘琛屼慨鏀 + * B 寮濮嬩簡浜嬪姟,'鎻掑叆'浜嗕竴鏉℃柊鐨勬暟鎹,鎻愪氦 + * A 鎻愪氦浜嬪姟鍚,鍙戠幇鏈変竴鏁版嵁杩樻湭淇敼('B浜嬪姟鏂版彃鍏ョ殑'),灏卞儚鏄骇鐢熺殑骞昏涓鏍 + --------------- + * A 寮鍚鍙栦簨鍔,妫绱㈢鍚堟潯浠剁殑璁板綍 + * B 寮濮嬩簡浜嬪姟,'鎻掑叆'浜嗕竴鏉℃柊鐨勬暟鎹,鎻愪氦 + * A 鍐嶆妫绱,澶氬嚭浜嗕竴鏉$鍚堟潯浠剁殑璁板綍('B浜嬪姟鏂版彃鍏ョ殑') + +------------ +1銆丼QL瑙勮寖鎵瑙勫畾鐨勬爣鍑嗭紝涓嶅悓鐨勬暟鎹簱鍏蜂綋鐨勫疄鐜板彲鑳戒細鏈変簺宸紓 +2銆乵ysql涓粯璁や簨鍔¢殧绂荤骇鍒槸鍙噸澶嶈鏃跺苟涓嶄細閿佷綇璇诲彇鍒扮殑琛 +3銆佷簨鍔¢殧绂荤骇鍒负璇绘彁浜ゆ椂锛屽啓鏁版嵁鍙細閿佷綇鐩稿簲鐨勮 +4銆佷簨鍔¢殧绂荤骇鍒负鍙噸澶嶈鏃讹紝濡傛灉鏈夌储寮曪紙鍖呮嫭涓婚敭绱㈠紩锛夌殑鏃跺欙紝浠ョ储寮曞垪涓烘潯浠舵洿鏂版暟鎹紝浼氬瓨鍦ㄩ棿闅欓攣闂撮殭閿併佽閿併佷笅涓閿攣鐨勯棶棰橈紝浠庤岄攣浣忎竴浜涜锛涘鏋滄病鏈夌储寮曪紝鏇存柊鏁版嵁鏃朵細閿佷綇鏁村紶琛ㄣ +5銆佷簨鍔¢殧绂荤骇鍒负涓茶鍖栨椂锛岃鍐欐暟鎹兘浼氶攣浣忔暣寮犺〃 +6銆侀殧绂荤骇鍒秺楂橈紝瓒婅兘淇濊瘉鏁版嵁鐨勫畬鏁存у拰涓鑷存э紝浣嗘槸瀵瑰苟鍙戞ц兘鐨勫奖鍝嶄篃瓒婂ぇ锛岄奔鍜岀唺鎺屼笉鍙吋寰楀晩銆傚浜庡鏁板簲鐢ㄧ▼搴忥紝鍙互浼樺厛鑰冭檻鎶婃暟鎹簱绯荤粺鐨勯殧绂荤骇鍒涓篟ead Committed锛屽畠鑳藉閬垮厤鑴忚鍙栵紝鑰屼笖鍏锋湁杈冨ソ鐨勫苟鍙戞ц兘銆傚敖绠″畠浼氬鑷翠笉鍙噸澶嶈銆佸够璇昏繖浜涘苟鍙戦棶棰橈紝鍦ㄥ彲鑳藉嚭鐜拌繖绫婚棶棰樼殑涓埆鍦哄悎锛屽彲浠ョ敱搴旂敤绋嬪簭閲囩敤鎮茶閿佹垨涔愯閿佹潵鎺у埗銆 + + + +lock in share mode + * 閫傜敤浜庝袱寮犺〃瀛樺湪涓氬姟鍏崇郴鏃剁殑涓鑷存ц姹 + * 鍦烘櫙,涓ゅ紶琛 + parent + paren_id + child_id + + child + child_id + + * 鐩存帴 insert 涓鏉 child_id=100 璁板綍鍒癱hild琛ㄦ槸瀛樺湪椋庨櫓鐨 + * 鍥犱负鍒 insert 鐨勬椂鍊欏彲鑳藉湪 parent 琛ㄩ噷鍒犻櫎浜嗚繖鏉 child_id=100 鐨勮褰,閭d箞涓氬姟鏁版嵁灏卞瓨鍦ㄤ笉涓鑷寸殑椋庨櫓 + * 姝g‘鐨勬墦寮鏂瑰紡 + + select * from parent where child_id = 100 lock in share mode; + insert into child(child_id) values(100); + +for update + * 閫傜敤浜庢搷浣滃悓涓寮犺〃鏃剁殑涓鑷存ц姹 + * for update鐨勫姞閿佹柟寮忔棤闈炴槸姣攍ock in share mode鐨勬柟寮忓闃诲浜唖elect...lock in share mode鐨勬煡璇㈡柟寮,骞朵笉浼氶樆濉炲揩鐓ц + + +鎵撲釜姣旀柟锛屼綘鏈変釜璐︽埛銆傞噷闈㈠瓨鐫浣犵殑閽便備綘鐜板湪鏈100鍧楅挶銆 +鏈変袱涓汉鍚屾椂寰浣犺处鎴锋墦閽 + +1锛岀敤鎴 A 寮鍚簡涓涓簡浜嬪姟 + select money from account where name = yang; + money = 100 + + 鐢ㄦ埛A缁欎綘杞50鍧 + update money = money + 50 where name = yang; + +2锛岀敤鎴 B 寮鍚簡涓涓簨鍔 + select money from account where name = yang; + money = 100 + + 鐢ㄦ埛B缁欎綘杞100鍧楅挶 + update money = money + 100 where name = yang; + +3锛岀敤鎴稡鎻愪氦浜嬪姟 + money = +4锛岀敤鎴稟鎻愪氦浜嬪姟 + diff --git "a/MySQL/MySQL-\345\207\275\346\225\260.java" "b/MySQL/MySQL-\345\207\275\346\225\260.java" index 62b07f69..159e795c 100644 --- "a/MySQL/MySQL-\345\207\275\346\225\260.java" +++ "b/MySQL/MySQL-\345\207\275\346\225\260.java" @@ -39,6 +39,8 @@ * 获取当前日期,格式:2016-06-12 now(); * 获取当前时间,格式:2016-06-12 22:53:30 + unix_timestamp() + * 返回unix时间戳 /** 字符串相关 **/ concat('1','2'); @@ -66,6 +68,10 @@ * 比较俩字符串大小..根据字典排序咯.A > B * 如果a > b 返回 1,反之返回 -1 相等返回 0 + find_in_set(str,list); + * 如果 str 出现在 list 中,返回 true + SELECT find_in_set(123,'123,456,788') as result; // 1 + //加密相关 md5("被加密的字符"); * MD5加密函数,不可逆.平时用的 diff --git "a/MySQL/MySQL-\346\211\271\351\207\217\346\223\215\344\275\234.java" "b/MySQL/MySQL-\346\211\271\351\207\217\346\223\215\344\275\234.java" new file mode 100644 index 00000000..8b05ad18 --- /dev/null +++ "b/MySQL/MySQL-\346\211\271\351\207\217\346\223\215\344\275\234.java" @@ -0,0 +1,14 @@ +------------------------ +批量更新操作 | +------------------------ + UPDATE `test` + SET `name` = + CASE + `id` + WHEN 1 THEN + '@1' + WHEN 2 THEN + '@2' + END + WHERE + `id` IN ( 1, 2 ) \ No newline at end of file diff --git "a/MySQL/MySQL-\351\205\215\347\275\256\346\226\207\344\273\266.java" "b/MySQL/MySQL-\351\205\215\347\275\256\346\226\207\344\273\266.java" index e69de29b..c3d63cb6 100644 --- "a/MySQL/MySQL-\351\205\215\347\275\256\346\226\207\344\273\266.java" +++ "b/MySQL/MySQL-\351\205\215\347\275\256\346\226\207\344\273\266.java" @@ -0,0 +1,23 @@ +[server] +# 选项将作用于所有的服务器程序 +character_set_server=utf8 +collation_server=utf8_chinese_ci + +# 查询缓冲区的大小,单位是字节, 默认 128mb +innodb_buffer_pool_size = 268435456 + +[mysqld] + +[mysqld_safe] + +[client] +# 选项将作用于所有的客户端程序 +character_set_client +character_set_connection +character_set_results + + + +[mysql] + +[mysqladmin] diff --git "a/MySQL/MySQL-\351\251\261\345\212\2508.x\347\211\210\346\234\254\347\232\204\346\227\266\345\214\272\351\227\256\351\242\230.java" "b/MySQL/MySQL-\351\251\261\345\212\2508.x\347\211\210\346\234\254\347\232\204\346\227\266\345\214\272\351\227\256\351\242\230.java" new file mode 100644 index 00000000..7c867252 --- /dev/null +++ "b/MySQL/MySQL-\351\251\261\345\212\2508.x\347\211\210\346\234\254\347\232\204\346\227\266\345\214\272\351\227\256\351\242\230.java" @@ -0,0 +1,7 @@ + +# 添加链接参数 + serverTimezone=GMT+8 + + serverTimezone=GMT%2b8 + * %2b 是 + 的转义字符 + diff --git "a/Mybatis/MyBatis-\345\205\245\351\227\250.java" "b/Mybatis/MyBatis-\345\205\245\351\227\250.java" index 25d8e770..b36fe0fc 100644 --- "a/Mybatis/MyBatis-\345\205\245\351\227\250.java" +++ "b/Mybatis/MyBatis-\345\205\245\351\227\250.java" @@ -38,6 +38,29 @@ //关闭资源 session.close(); + + // sql session有多个API可以获取到 sqlSession实例 + + SqlSession openSession(boolean autoCommit); + * 是否自动提交事务 + SqlSession openSession(Connection connection); + * 从指定的Connection创建session + SqlSession openSession(TransactionIsolationLevel level); + * 事务隔离级别 + SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level) + * 执行类型,和事务隔离级别 + SqlSession openSession(ExecutorType execType); + * 执行类型 + SqlSession openSession(ExecutorType execType, boolean autoCommit); + * 执行类型 + SqlSession openSession(ExecutorType execType, Connection connection); + * 执行类型 + + ExecutorType 执行类型,是一个枚举 + SIMPLE, + REUSE, + BATCH(获取一个批处理的Session) + ———————————————————————— 4,MyBatis XML配置约束 | ———————————————————————— diff --git "a/Mybatis/MyBatis-\345\210\206\351\241\265\346\217\222\344\273\266(2).java" "b/Mybatis/MyBatis-\345\210\206\351\241\265\346\217\222\344\273\266(2).java" index f92f6d28..27bb2f4d 100644 --- "a/Mybatis/MyBatis-\345\210\206\351\241\265\346\217\222\344\273\266(2).java" +++ "b/Mybatis/MyBatis-\345\210\206\351\241\265\346\217\222\344\273\266(2).java" @@ -1,83 +1,49 @@ ----------------------- MyBatis-分页插件 | ----------------------- - # maven仓库,一共有俩依赖 + # maven com.github.pagehelper pagehelper - ${pagehelper.version} - - - com.github.jsqlparser - jsqlparser - ${jsqlparser.version} - ------------------------ -MyBatis-集成 | ------------------------ - # 在mybatis全局配置中,添加插件 - - - - - + +------------------------- +MyBatis-PageRowBounds分页| +------------------------- + # mybatis配置 + + + + + + + + + + + - # 最详细的配置信息 - - - - - - - - - - - - - - - - - - - - - # 与spring集成 - - - - - classpath:mapper/*.xml */ - - - - - - - - - dialect=hsqldb - reasonable=true - - - - - - + # 代码 ------------------------ -MyBatis-使用 | ------------------------ - # 日了狗了分页工具 - PageHelper.startPage(1,3); - List list = mapper.queryAll(); - * 第几页,要多少条数据 + // SQL参数过滤对象 + UserEntity userEntity = new UserEntity(); + + // 页码和每页显示数量 + PageRowBounds pageRowBounds = new PageRowBounds(2, 1); + + // 是否要检索总记录数 + pageRowBounds.setCount(true); + + List users = this.userMapper.pageTest(userEntity ,pageRowBounds); + - PageInfo pageInfo = new PageInfo(list); - System.out.println(pageInfo.getTotal()); //总记录数 - System.out.println(pageInfo.getPages()); //总页数 \ No newline at end of file + * pageRowBounds 的属性 + { + "count":true, // 是否检索总记录数量 + "limit":1, // 每页显示数量 + "offset":2, // 当前页码 + "total":3 // 总记录数量 + } + + \ No newline at end of file diff --git "a/Mybatis/MyBatis-\345\245\207\346\267\253\345\267\247\346\212\200.java" "b/Mybatis/MyBatis-\345\245\207\346\267\253\345\267\247\346\212\200.java" new file mode 100644 index 00000000..cbe0544d --- /dev/null +++ "b/Mybatis/MyBatis-\345\245\207\346\267\253\345\267\247\346\212\200.java" @@ -0,0 +1,5 @@ +# 读取枚举值 + ${@com.zfx.aibaise.domain.entity.LikeEntity$Type@COMMENT.value} + +# 读取静态类属性/方法 + ${@ommon.config.Global@getSysCode()} \ No newline at end of file diff --git "a/Mybatis/MyBatis-\346\236\232\344\270\276\347\261\273\345\244\204\347\220\206.java" "b/Mybatis/MyBatis-\346\236\232\344\270\276\347\261\273\345\244\204\347\220\206.java" index 8ad7744a..c887a071 100644 --- "a/Mybatis/MyBatis-\346\236\232\344\270\276\347\261\273\345\244\204\347\220\206.java" +++ "b/Mybatis/MyBatis-\346\236\232\344\270\276\347\261\273\345\244\204\347\220\206.java" @@ -11,6 +11,11 @@ * 默认的 * 是按照枚举的名字来存取的,对应数据库的设计为 直接使用枚举名。 + * 修改默认的枚举处理器(mybatis-cofig.xml), 实用属性:defaultEnumTypeHandler + + + + * 如果有特殊需求,可以考虑自定义消息类型处理器来完成 diff --git "a/Mybatis/MyBatis-\346\240\207\347\255\276-resultMap.xml" "b/Mybatis/MyBatis-\346\240\207\347\255\276-resultMap.xml" index 703da2c3..34f49388 100644 --- "a/Mybatis/MyBatis-\346\240\207\347\255\276-resultMap.xml" +++ "b/Mybatis/MyBatis-\346\240\207\347\255\276-resultMap.xml" @@ -68,6 +68,8 @@ resultMap- * DB字段的 前缀 resultMap * 结果集映射 + select + * 可以指定SQL语句或者namespace的检索 # 一对多的构造 @@ -76,6 +78,8 @@ resultMap- * DB字段的 前缀 resultMap * 结果集映射 + select + * 可以指定SQL语句或者namespace的检索 # 根据指定字段的值来决定使用哪个 resultType / resultMap 中属性进行映射 diff --git "a/Mybatis/MyBatis-\346\263\250\350\247\243.java" "b/Mybatis/MyBatis-\346\263\250\350\247\243.java" index d39f2a4d..7f86361e 100644 --- "a/Mybatis/MyBatis-\346\263\250\350\247\243.java" +++ "b/Mybatis/MyBatis-\346\263\250\350\247\243.java" @@ -22,3 +22,72 @@ public User queryByNameAndPass( @Result @Results + +---------------------------- +MyBatis-demo | +---------------------------- +@Repository +public interface FooMapper extends BaseMapper{ + + class FooSqlProvider extends BaseSqlProvider { + + } + + //=================================== + //获取自增主键 + @Options(useGeneratedKeys = true,keyProperty = "fooId",keyColumn = "foo_id") + @InsertProvider(type = FooSqlProvider.class,method = "create") + //也可以获取自增主键 +// @SelectKey(before = false,keyColumn = "foo_id",keyProperty = "fooId",resultType = Integer.class,statement = "SELECT LAST_INSERT_ID();") + int create(FooEntity foo); + + + //=================================== + @SelectProvider(type = FooSqlProvider.class,method = "queryByPrimaryKey") + @ResultMap(value = "BASE_RESULT_MAP") + FooEntity queryByPrimaryKey(Serializable primaryKey); + + @SelectProvider(type = FooSqlProvider.class,method = "queryByParamSelective") + @ResultMap(value = "BASE_RESULT_MAP") + FooEntity queryByParamSelectiveUnique(FooEntity fooEntity); + + @SelectProvider(type = FooSqlProvider.class,method = "queryByParamSelective") + @Results(id = "BASE_RESULT_MAP",value = { + @Result(id = true,column = "foo_id",property = "fooId"), + @Result(column = "create_date",property = "createDate"), + @Result(column = "modify_date",property = "modifyDate"), + @Result(column = "create_user",property = "createUser"), + @Result(column = "status",property = "status"), + @Result(column = "sorted",property = "sorted"), + @Result(column = "remark",property = "remark"), + }) + List queryByParamSelective(FooEntity foo); + + //=================================== + @UpdateProvider(type = FooSqlProvider.class,method = "updateByPrimaryKeySelective") + int updateByPrimaryKeySelective(FooEntity foo); + + @UpdateProvider(type = FooSqlProvider.class,method = "updateByPrimaryKey") + int updateByPrimaryKey(FooEntity foo); + + //=================================== + @DeleteProvider(type = FooSqlProvider.class,method = "deleteByPrimaryKey") + int deleteByPrimaryKey(Serializable deleteByPrimaryKey); + + @DeleteProvider(type = FooSqlProvider.class,method = "deleteByParamSelective") + @ResultMap(value = "BASE_RESULT_MAP") + int deleteByParamSelective(FooEntity fooEntity); + + @SelectProvider(type = FooSqlProvider.class,method = "queryByParamSelective") + @ResultMap(value = "BASE_RESULT_MAP") + PageList queryByPage(FooEntity fooEntity,PageBounds pageBounds); +} + + + + +# Provider的接口支持一个参数 + ProviderContext + private final Class mapperType; // mapper接口 + private final Method mapperMethod; // mapper方法 + diff --git "a/Mybatis/MyBatis-\346\263\250\350\247\243\345\274\200\345\217\221-\351\200\232\347\224\250.java" "b/Mybatis/MyBatis-\346\263\250\350\247\243\345\274\200\345\217\221-\351\200\232\347\224\250.java" new file mode 100644 index 00000000..c873df79 --- /dev/null +++ "b/Mybatis/MyBatis-\346\263\250\350\247\243\345\274\200\345\217\221-\351\200\232\347\224\250.java" @@ -0,0 +1,344 @@ +/** + * Created by KevinBlandy on 2018/3/28 13:49 + */ +@Repository +public interface UserMapper extends BaseMapper { + + class UserSqlProvider extends BaseSqlProvider { + + } + + @SelectProvider(type = UserSqlProvider.class,method = "queryByPrimaryKey") + //@SelectKey(before = false,keyColumn = "user_id",keyProperty = "userId",resultType = Integer.class,statement = "SELECT LAST_INSERT_ID();") + @Results(id = "BASE_RESULT_MAP",value = { + @Result(id = true,column = "user_id",property = "userId"), + @Result(column = "name",property = "name"), + @Result(column = "email",property = "email"), + @Result(column = "password",property = "password"), + @Result(column = "phone",property = "phone"), + @Result(column = "email_verify",property = "emailVerify"), + @Result(column = "phone_verify",property = "phoneVerify"), + @Result(column = "gender",property = "gender"), + @Result(column = "avatar",property = "avatar"), + @Result(column = "age",property = "age"), + @Result(column = "location",property = "location"), + @Result(column = "company",property = "company"), + @Result(column = "url",property = "url"), + @Result(column = "github",property = "github"), + @Result(column = "profile",property = "profile"), + @Result(column = "rank",property = "rank"), + @Result(column = "role",property = "role"), + @Result(column = "create_user",property = "createUser"), + @Result(column = "create_date",property = "createDate"), + @Result(column = "modify_date",property = "modifyDate"), + @Result(column = "status",property = "status"), + @Result(column = "sorted",property = "sorted"), + @Result(column = "remark",property = "remark"), + }) + @Override + UserEntity queryByPrimaryKey(Serializable primaryKey)throws Exception; + + @SelectProvider(type = UserSqlProvider.class,method = "queryByParamSelective") + @ResultMap("BASE_RESULT_MAP") + @Override + UserEntity queryByParamSelectiveUnique(UserEntity entity)throws Exception; + + @SelectProvider(type = UserSqlProvider.class,method = "queryByParamSelective") + @ResultMap("BASE_RESULT_MAP") + @Override + List queryByParamSelective(UserEntity userEntity)throws Exception; + + @SelectProvider(type = UserSqlProvider.class,method = "queryByParamSelective") + @ResultMap("BASE_RESULT_MAP") + @Override + PageList queryByPage(UserEntity userEntity, PageBounds pageBounds)throws Exception; + + @Options(useGeneratedKeys = true,keyColumn = "user_id",keyProperty = "userId") + @InsertProvider(type = UserSqlProvider.class,method = "create") + @Override + int create(UserEntity userEntity)throws Exception; + + @UpdateProvider(type = UserSqlProvider.class,method = "updateByPrimaryKeySelective") + @Override + int updateByPrimaryKeySelective(UserEntity userEntity)throws Exception; + + @UpdateProvider(type = UserSqlProvider.class,method = "updateByPrimaryKey") + @Override + int updateByPrimaryKey(UserEntity userEntity)throws Exception; + + @DeleteProvider(type = UserSqlProvider.class,method = "deleteByPrimaryKey") + @Override + int deleteByPrimaryKey(Serializable primaryKey)throws Exception; + + @DeleteProvider(type = UserSqlProvider.class,method = "deleteByParamSelective") + @Override + int deleteByParamSelective(UserEntity userEntity)throws Exception; +} + +=============================================================== +/** + * @author KevinBlandy + */ +public abstract class BaseSqlProvider { + + //瀛愮被娉涘瀷 + private Class clazz; + + //琛ㄥ悕绉 + private String tableName; + + //... + private String nickTableName; + + //鎵鏈夊瓧娈 + private List allFields = new ArrayList<>(); + + //pk瀛楁 + private List idFields = new ArrayList<>(); + + //鏅氬瓧娈 + private List ordinaryFields = new ArrayList<>(); + + + public BaseSqlProvider() { + this.clazz = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + Entity entity = this.clazz.getAnnotation(Entity.class); + if(entity == null) { + throw new RuntimeException(this.clazz.getName() + ",鏈爣璇咢Entity娉ㄨВ"); + }else { + this.tableName = entity.value(); + } + this.nickTableName = "`" + this.tableName + "`"; + Class tempClass = clazz; + while(tempClass != null) { + //鑾峰彇绫绘墍鏈夊瓧娈典俊鎭 + this.allFields.addAll(Arrays.asList(tempClass.getDeclaredFields())); + tempClass = tempClass.getSuperclass(); + } + Iterator fieldIterator = this.allFields.iterator(); + while(fieldIterator.hasNext()) { + Field field = fieldIterator.next(); + if(Modifier.isStatic(field.getModifiers()) || field.isAnnotationPresent(Ignore.class)){ + //闈欐佸瓧娈 & @Ignore + fieldIterator.remove(); + }else { + field.setAccessible(true); + if(field.isAnnotationPresent(Id.class)) { + this.idFields.add(field); + }else { + this.ordinaryFields.add(field); + } + } + } + } + + /** + * 鍒涘缓涓鏉¤褰 + * @return + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public String create() throws IllegalArgumentException, IllegalAccessException{ + SQL sql = new SQL(); + sql.INSERT_INTO(nickTableName); + for(Field field : this.allFields) { + sql.INTO_COLUMNS(underlineStyle(field.getName())); + } + for(Field field : this.allFields) { + sql.INTO_VALUES("#{"+field.getName()+"}"); + } + return sql.toString(); + } + + /** + * 鏍规嵁pk妫绱㈠嚭涓鏉¤褰 + * @return + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public String queryByPrimaryKey() throws IllegalArgumentException, IllegalAccessException { + SQL sql = new SQL(); + sql.SELECT("*").FROM(this.nickTableName); + for(Field field : this.idFields) { + String fieldName = field.getName(); + sql.WHERE("`"+underlineStyle(fieldName)+"` = #{" + fieldName + "}"); + } + return sql.toString(); + } + + /** + * 鏍规嵁闈炵┖鍙傛暟,妫绱㈠嚭涓鏉¤褰 + * @param param + * @return + * @throws IllegalArgumentException + * @throws IllegalAccessException + + public String queryByParamSelectiveUnique(T param) throws IllegalArgumentException, IllegalAccessException { + return this.queryByParamSelective(param); + }*/ + + /** + * 鏍规嵁闈炵┖鍙傛暟妫绱㈣褰 + * @param param + * @return + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public String queryByParamSelective(T param) throws IllegalArgumentException, IllegalAccessException { + SQL sql = new SQL(); + sql.SELECT("*").FROM(this.nickTableName); + if(param != null) { + this.ifParamNotNull(this.allFields, param, fieldName -> { + sql.WHERE("`"+underlineStyle(fieldName)+"` = #{" + fieldName + "}"); + }); + } + return sql.toString(); + } + + /** + * 鏍规嵁涓婚敭鏇存柊璁板綍,浠呬粎鏇存柊闈炵┖瀛楁 + * @param param + * @return + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public String updateByPrimaryKeySelective(T param) throws IllegalArgumentException, IllegalAccessException{ + SQL sql = new SQL(); + sql.UPDATE(this.nickTableName); + this.ifParamNotNull(this.ordinaryFields, param, fieldName -> { + sql.SET("`"+underlineStyle(fieldName)+"` = #{" + fieldName + "}"); + }); + for(Field field : this.idFields){ + String fieldName = field.getName(); + sql.WHERE("`"+underlineStyle(fieldName)+"` = #{" + fieldName + "}"); + } + return sql.toString(); + } + + /** + * 鏍规嵁涓绘満鏇存柊璁板綍,鏇存柊鎵鏈夊瓧娈 + * @return + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public String updateByPrimaryKey() throws IllegalArgumentException, IllegalAccessException{ + SQL sql = new SQL(); + sql.UPDATE(this.nickTableName); + for(Field field : this.ordinaryFields) { + String fieldName = field.getName(); + sql.SET("`"+underlineStyle(fieldName)+"` = #{" + fieldName + "}"); + } + for(Field field : this.idFields) { + String fieldName = field.getName(); + sql.WHERE("`"+underlineStyle(fieldName)+"` = #{" + fieldName + "}"); + } + return sql.toString(); + } + + /** + * 鏍规嵁涓婚敭,鍒犻櫎璁板綍 + * @return + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public String deleteByPrimaryKey() throws IllegalArgumentException, IllegalAccessException { + SQL sql = new SQL(); + sql.DELETE_FROM(this.nickTableName); + for(Field field : this.idFields) { + String fieldName = field.getName(); + sql.WHERE("`"+underlineStyle(fieldName)+"` = #{" + fieldName + "}"); + } + return sql.toString(); + } + + /** + * 鏍规嵁闈炵┖鍙傛暟鍒犻櫎鏁版嵁 + * !!!濡傛灉瀵硅薄鎵鏈夊睘鎬т负null,鍒欎細鐗╃悊鍒犻櫎鏁村紶琛 + * @param param + * @return + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + + public String deleteByParamSelective(T param) throws IllegalArgumentException, IllegalAccessException{ + SQL sql = new SQL(); + sql.DELETE_FROM(this.nickTableName); + this.ifParamNotNull(this.allFields, param, fieldName -> { + sql.WHERE("`"+underlineStyle(fieldName)+"` = #{" + fieldName + "}"); + }); + return sql.toString(); + } + + + + /** + * sql render + * @param fields + * @param param + * @param consumer + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + private void ifParamNotNull(List fields,T param,Consumer consumer) throws IllegalArgumentException, IllegalAccessException { + for(Field field : fields) { + if(field.get(param) != null) { + consumer.accept(field.getName()); + } + } + } + + /** + * 椹煎嘲杞笅鍒掔嚎 + * @param string + * @return + */ + private static String underlineStyle(String string) { + StringBuilder sb = new StringBuilder(); + char[] chars = string.toCharArray(); + for(int x = 0;x < chars.length; x++) { + if(Character.isUpperCase(chars[x])) { + sb.append("_"); + } + sb.append(chars[x]); + } + return sb.toString().toLowerCase(); + } +} + +=============================================================== +@Retention(RUNTIME) +@Target(ElementType.TYPE) +public @interface Entity { + + //琛ㄥ悕绉 + String value(); +} + +@Retention(RUNTIME) +@Target(FIELD) +public @interface Id { + +} + +@Retention(RUNTIME) +@Target(METHOD) +public @interface Ignore { + +} + + + + + + + + + + + + + + + + + diff --git "a/Mybatis/MyBatis-\346\263\250\350\247\243\345\274\200\345\217\221.java" "b/Mybatis/MyBatis-\346\263\250\350\247\243\345\274\200\345\217\221.java" index 9850e188..c8a9d54e 100644 --- "a/Mybatis/MyBatis-\346\263\250\350\247\243\345\274\200\345\217\221.java" +++ "b/Mybatis/MyBatis-\346\263\250\350\247\243\345\274\200\345\217\221.java" @@ -58,4 +58,16 @@ public interface UserMapper UserMapper userMapper = session.getMapper(UserMapper.class); 4,直接执行返回接口引用对象的抽象方法,就能自动执行接口抽象方法上定义的SQL语句 - User u = userMapper.findById(user); \ No newline at end of file + User u = userMapper.findById(user); + +———————————————————————— +3,注解开发 - 关系映射 | +———————————————————————— + @Results(id = "BASE_RESULT_MAP",value = { + @Result(column = "",property = "",one = @One(select = "")), + @Result(column = "",property = "",many = @Many(select = "")) + }) + + * @One 和 @Many 中的 select 可以是 mapper检索的命名空间,也可以直接是SQL语句 + + \ No newline at end of file diff --git "a/Nacos/nacos-\345\205\245\351\227\250.java" "b/Nacos/nacos-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..b0b80cd5 --- /dev/null +++ "b/Nacos/nacos-\345\205\245\351\227\250.java" @@ -0,0 +1,5 @@ +-------------------------------- +nacos | +-------------------------------- + # 官网 + https://nacos.io/zh-cn/ diff --git a/Netty/NIO-Buffer.java b/Netty/NIO-Buffer.java deleted file mode 100644 index 68793000..00000000 --- a/Netty/NIO-Buffer.java +++ /dev/null @@ -1,166 +0,0 @@ -------------------------------- -Netty-Buffer | -------------------------------- - # Buffer 是一个对象,它包含一些要写入或者读取的数据,在NIO类库中加入 Buffer 对象,提现了新库与原IO的一个重要区别 - # 在所有流的IO中可以把数据直接写入到 Stream 对象,'在NIO库中,所有的数据都是用缓冲区处理读写的' - # 缓冲区实质上是一个数组,通常它是一个字节数组,(ByteBuffer),也可以使用其他类型的数组,这个数组为缓冲区提供了数据的访问读写等操作.如果:位置,容量,上限等概念(看API) - # Buffer 类型,我们最常用的就是 ByteBuffer ,实际上每种JAVA基本类型都有一种缓冲区(除了 Boolean 类型) - # 一些列的类 - ByteBuffer - CharBuffer - ShortBuffer - IntBuffer - LongBuffer - FloatBuffer - DoubleBuffer - - MappedByteBuffer //它比较特别 用于表示内存映射文件5 - - -------------------------------- -Netty-Buffer 属性 | -------------------------------- - # capacity,position,limit - # 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。 - # 为了理解Buffer的工作原理,需要熟悉它的三个属性: - capacity - position - limit - - # capacity - * 作为一个内存块,Buffer有一个固定的大小值,也叫"capacity".你只能往里写capacity个byte、long,char等类型。 - * 一旦Buffer满了,需要将其清空('通过读数据或者清除数据')才能继续写数据往里写数据。 - - # position - * 当你写数据到Buffer中时,position表示当前的位置。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。 - * position最大可为capacity – 1. - - # limit - * 在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。 - * 当切换Buffer到读模式时,limit会被设置成写模式下的position值。也就是说,也就是说可以读取到多少数据 - - -------------------------------- -Netty-Buffer 分配 | -------------------------------- - # 要想获得一个Buffer对象首先要进行分配。 每一个Buffer类都有一个allocate方法。 - # Demo - ByteBuffer buf = ByteBuffer.allocate(48); //分配48字节capacity的ByteBuffer。 - CharBuffer buf = CharBuffer.allocate(1024); //分配1024字节capacity的CharBuffer。 - -------------------------------- -Netty-Buffer 写入数据 | -------------------------------- - # 写数据到Buffer有两种方式 - # 1,从Channel写到Buffer。 - int bytesRead = inChannel.read(buf); //read into buffer. - - # 2,通过Buffer的put()方法写到Buffer里。 - buf.put(127); - * put方法有很多版本,允许你以不同的方式把数据写入到Buffer中。 - * 例如,写到一个指定的位置,或者把一个字节数组写入到Buffer。 - * 更多Buffer实现的细节参考JavaDoc。 - -------------------------------- -Netty-Buffer 读取数据 | -------------------------------- - # 从Buffer中读取数据有两种方式: - # 1,从Buffer读取数据到Channel。 - int bytesWritten = inChannel.write(buf); - - # 2,使用get()方法从Buffer中读取数据。 - byte aByte = buf.get(); - * get方法有很多版本,允许你以不同的方式从Buffer中读取数据。 - * 例如,从指定position读取,或者从Buffer中读取数据到字节数组。 - * 更多Buffer实现的细节参考JavaDoc。 - -------------------------------- -Netty-Buffer 方法 | -------------------------------- - 静态方法: - allocate(int size); - * 创建指定大小的缓冲区 - - wrap(byte[] arr); - * 新的缓冲区将由给定的 arr 数组支持 - * 缓冲区修改将导致数组修改,反之亦然 - * 新缓冲区的容量和界限将为 arr.length,不管arr中有没有数据,buffer的置(position)始终为零 - - wrap(arr,num1,num2); - * 同上 - * 新缓冲区的 position = num1,limit = (num1 + num2),capacity = arr.length - * 简单说:把arr放入缓冲区,从arr的 num1 角标开始取值,取num2 个数据 - - 实例方法: - - get(); - * 获取到缓冲区中的数据,返回的是一单位(ByteBuffer 返回的就是 byte) - * 每次执行 get(),都会让 position 向前移动一位 - * 有N多重载方法 - - get(index); - * 获取指定下标的值 - * position 不会发生变化 - - remaining(); - * 返回当前位置与限制之间的元素数。 - - void flip(); - * 复位,也就是从写的状态转换为读的状态. - * 将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。 - - void clear(); - * clear 并没有清理数据,而是设置了postion和limit的值,让过去的数据被遗忘 - * position将被设回0,limit被设置成 capacity的值。 - * 换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。 - - void compact(); - * 清空缓冲区,只会清除已经读过的数据。 - * 任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。 - * 将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。 - * limit属性依然 跟 clear()方法一样,设置成capacity。 - * 现在Buffer准备好写数据了,但是不会覆盖未读的数据。 - - put(byte bt); - * 向缓冲区中添加一个元素 - * posistion 会 + 1 操作 - - put(byte[] data); - * 向缓冲区中写入数据 - * 有N多重载.例如可以指定:写入到哪个位置?或者是直接把一个字节数组写入 - - put(int po,byte b); - * 在指定位置写入数据, position 位置不会发生变化 - * 会覆盖原来的值 - - rewind(); - * 将position设回0,所以你可以重读Buffer中的所有数据。 - * limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)。 - - mark(); - * 通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。 - * 之后可以通过调用Buffer.reset()方法恢复到这个position。 - - reset(); - * 通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。 - * 之后可以通过调用Buffer.reset()方法恢复到这个position。 - * demo - buffer.mark(); - buffer.reset(); - - equals(); - * 当满足下列条件时,表示两个Buffer相等: - * 有相同的类型(byte、char、int等)。 - * Buffer中剩余的byte、char等的个数相等。 - * Buffer中所有剩余的byte、char等都相同。 - * 如你所见,equals只是比较Buffer的一部分,不是每一个在它里面的元素都比较。实际上,它只比较Buffer中的剩余元素。 - - compareTo(); - * compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer: - 1,第一个不相等的元素小于另一个Buffer中对应的元素 。 - 2,所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少) - - hasRemaining(); - * 判断Buffer中是否还有操作完毕的数据 - * 源码: return position < limit; - \ No newline at end of file diff --git a/Netty/NIO-Channel.java b/Netty/NIO-Channel.java deleted file mode 100644 index 302d83de..00000000 --- a/Netty/NIO-Channel.java +++ /dev/null @@ -1,19 +0,0 @@ -------------------------------- -Netty-Channel | -------------------------------- - # 通道,跟自来水管道没啥区别.网络数据通过 Channel 读取/写入,'与传统流不同的是,它双向的',而流要么是读流,要么是写流 - # 最叼的是,这个通道可以同时的进行读/写 - # 最关键的是,可以和多路复用器结合起来,有多种状态位,方便多路复用器去识别. - # 事实上,通道分为两大类 - 1,SelectableChannel 网络读写 - * DatagramChannel //从UDP中读写数据 - * SocketChannel //从TCP中读写数据 - * ServerSocketChannel //可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。 - - 2,FileChannel //从文件中读写数据 - - - - - - diff --git a/Netty/NIO-DatagramChannel.java b/Netty/NIO-DatagramChannel.java deleted file mode 100644 index a1909044..00000000 --- a/Netty/NIO-DatagramChannel.java +++ /dev/null @@ -1,55 +0,0 @@ ----------------------------- -DatagramChannel | ----------------------------- - # DatagramChannel是一个能收发UDP包的通道。 - # 因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。 - # 打开 DatagramChannel - DatagramChannel channel = DatagramChannel.open(); - channel.socket().bind(new InetSocketAddress(9999)); //打开的 DatagramChannel可以在UDP端口9999上接收数据包。 - - ----------------------------- -DatagramChannel-接收数据 | ----------------------------- - DatagramChannel channel = DatagramChannel.open(); - channel.socket().bind(new InetSocketAddress(9999)); - ByteBuffer buf = ByteBuffer.allocate(48); - buf.clear(); - channel.receive(buf); - - * receive()方法会将接收到的数据包内容复制到指定的Buffer. - * 如果Buffer容不下收到的数据,多出的数据将被丢弃。 - - ----------------------------- -DatagramChannel-发送数据 | ----------------------------- - DatagramChannel channel = DatagramChannel.open(); - channel.socket().bind(new InetSocketAddress(9999)); - String newData = "New String to write to file..." + System.currentTimeMillis(); - ByteBuffer buf = ByteBuffer.allocate(48); //构建Buffere - buf.clear(); //position = 0 - buf.put(newData.getBytes()); //填充数据,position = newData.length; - buf.flip(); //复位,因为要从里面读取数据 position = 0 - int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80)); - ----------------------------- -连接到特定的地址 | ----------------------------- - # 可以将DatagramChannel'连接'到网络中的特定地址的。 - # 由于UDP是无连接的,连接到特定地址并不会像TCP通道那样创建一个真正的连接。而 - # 只锁住DatagramChannel ,让其只能从特定地址收发数据。 - DatagramChannel channel = DatagramChannel.open(); - /** - 把 Channel 绑定在某个通道上 - 也可以使用read()和write()方法,就像在用传统的通道一样。 - */ - channel.socket().bind(new InetSocketAddress(9999)); - int bytesRead = channel.read(buf); - int bytesWritten = channel.write(but); - - - ----------------------------- -DatagramChannel-API | ----------------------------- \ No newline at end of file diff --git a/Netty/NIO-FileChannel.java b/Netty/NIO-FileChannel.java deleted file mode 100644 index 13a9ad81..00000000 --- a/Netty/NIO-FileChannel.java +++ /dev/null @@ -1,192 +0,0 @@ --------------------------- -FileChannel | --------------------------- - # Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。 - # FileChannel无法设置为非阻塞模式,它总是运行在'阻塞模式下' - # 获取 FileChannel - * FileChannel 静态方法 - FileChannel.open(Path path, OpenOption... options); - FileChannel.open(Path path, Set options,FileAttribute... attrs) - * 流对象的方法 - * InputStream,OutputStream,RandomAccessFile - * getChannel(); 方法,总是存在这些流里面 - - # 在使用 FileChannel 之前,必须先打开它。需要通过使用一个 InputStream 、 OutputStream 或 RandomAccessFile 来获取一个FileChannel实例 - * getChannel(); 方法,总是存在这些流里面 - RandomAccessFile file = new RandomAccessFile("E:\\Main.java", "rw"); - FileChannel fromChanne = file.getChannel(); - - # 从 FileChannel 中读取数据 - ByteBuffer buf = ByteBuffer.allocate(48); //准备一个48字节的buffer - int bytesRead = inChannel.read(buf); //把通道中的数据读到buffer中 - * read()方法返回的int值表示了有多少字节被读到了Buffer中。如果返回-1,表示到了文件末尾。 - - # 向 FileChannel 写数据 - String newData = "New String to write to file..." + System.currentTimeMillis(); - ByteBuffer buf = ByteBuffer.allocate(48); - buf.clear(); //重置buffer,指针移动到0 - buf.put(newData.getBytes()); //填充数据 - buf.flip(); //修改读/写模式,指针移到0 - while(buf.hasRemaining()) { - channel.write(buf); - } - * 使用FileChannel.write()方法向FileChannel写数据,该方法的参数是一个Buffer - * 注意FileChannel.write()是在while循环中调用的。 - * 因为无法保证write()方法一次能向FileChannel写入多少字节,因此需要重复调用write()方法,直到Buffer中已经没有尚未写入通道的字节。 - - # 关闭 FileChannel - channel.close(); - * 用完FileChannel后必须将其关闭 - - # 演示一个从文件中获取 Channel 的实例 - RandomAccessFile file = new RandomAccessFile("E:\\Main.java","rw"); - FileChannel fileChannel = file.getChannel(); //从文件获取流 - ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //构建1MB的缓冲区 - int len = fileChannel.read(byteBuffer); //通过管道把数据写入缓冲区 - while (len != -1){ - byteBuffer.flip(); //复位,开始读取数据 - while (byteBuffer.hasRemaining()){ - System.out.print((char) byteBuffer.get()); - } - byteBuffer.clear(); //数据读取完毕,复位该缓冲区准备写入 - len = fileChannel.read(byteBuffer); //通过管道把数据写入缓冲区 - } - file.close(); //关闭 RandomAccessFile 会关闭对应的 FileChannel - -------------------------------- -scatter | -------------------------------- - # Scattering Reads是指数据从一个channel读取到多个buffer中。如下图描述: - |-> Buffer - Channel -> |-> Buffer - |-> Buffer - - # read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel紧接着向另一个buffer中写。 - # Scattering Reads'在移动下一个buffer前,必须填满当前的buffer',这也意味着它不适用于动态消息(译者注:消息大小不固定)。 - # 换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工作。 - - # 代码Demo - ByteBuffer header = ByteBuffer.allocate(128); //一个Buffere - ByteBuffer body = ByteBuffer.allocate(1024); //又一个Buffere - ByteBuffer[] bufferArray = { header, body }; //把所有Buffer合并为一个Buffer数组 - channel.read(bufferArray); //从Channle中读取数据到这些Buffer - -------------------------------- -gather | -------------------------------- - # Gathering Writes是指数据从多个buffer写入到同一个channel。如下图描述: - <-| Buffer - Channel <- <-| Buffer - <-| Buffer - - # write()方法会按照buffer在数组中的顺序,将数据写入到channel,注意只有position和limit之间的数据才会被写入。 - # 如果一个buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到channel中。 - # 因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。 - - # 代码Demo - ByteBuffer header = ByteBuffer.allocate(128); //一个Buffer - ByteBuffer body = ByteBuffer.allocate(1024); //又一个Buffer - ByteBuffer[] bufferArray = { header, body }; //把所有Buffer合并为一个数组 - channel.write(bufferArray); //把数据从Buffre中写入到channel - -------------------------------- - 通道之间的数据传输 | -------------------------------- - # 如果两个通道中有一个是 FileChannel ,那你可以直接将数据从一个channel,传输到另外一个channel。 - - # 把目标 Channel 的数据读取到 this Channel 中 - * Demo - RandomAccessFile fromFile = new RandomAccessFile("E:\\Main.java", "rw"); - FileChannel fromChannel = fromFile.getChannel(); //源Chnnel - RandomAccessFile toFile = new RandomAccessFile("E:\\Main1.java", "rw"); - FileChannel toChannel = toFile.getChannel(); //目标Chnnel - long position = 0; //指针为0 - long count = fromChannel.size(); //获取的是源channel的总大小的数据量 - toChannel.transferFrom(fromChannel, position, count); //执行属性传输 - - * 果源通道的剩余空间小于 count 个字节,则所传输的字节数要小于请求的字节数。 - * 在SoketChannel的实现中,'SocketChannel只会传输此刻准备好的数据(可能不足count字节)'。 //SocketChannel是网络 - * 因此,SocketChannel 可能不会将请求的所有数据(count个字节) 全部传输到FileChannel中。 - - # 把 this Channel 中的数据写入到 目标 Channel 中 - * Demo - RandomAccessFile fromFile = new RandomAccessFile("E:\\Main.java", "rw"); - FileChannel fromChanne = fromFile.getChannel(); - RandomAccessFile toFile = new RandomAccessFile("E:\\ooooo.java", "rw"); - FileChannel toChannel = toFile.getChannel(); - long position = 0; - long count = fromChanne.size(); - fromChanne.transferTo(position, count, toChannel); - * 除了调用方法的FileChannel对象不一样外,其他的都一样。 - * 关于SocketChannel的问题在transferTo()方法中同样存在。 - * 'SocketChannel会一直传输数据直到目标buffer被填满'。 - -------------------------------- -FileChannel-内存文件映射 | -------------------------------- - # - -------------------------------- -FileChannel-API | -------------------------------- - int read(ByteBuffer buffer); - * 将数据从 FileChannel 读取到Buffer中。 - * read()方法返回的int值表示了有多少字节被读到了Buffer中。如果返回-1,表示到了文件末尾。 - - long transferFrom(ReadableByteChannel src,long position, long count); - * 把 src 通道中的数据写入到 this 通道,从 position 开始写,写 count 长度 - * 返回 long,表示写入了多少数据 - - long transferTo(ReadableByteChannel src,long position, long count); - * 把 this 通道中的数据写入到 src 通道,从 position 开始写,写 count 长度 - * 返回 long,表示写入了多少数据 - - long position(); - * 获取FileChannel的当前位置 - * 如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1 —— 文件结束标志。 - - void position(long position); - * 设置FileChannel的位置 - - long size(); - * 返回关联文件的大小 - - FileChannel truncate(long size); - * 截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除 - * size(字节)多大,就截取多大,后面的都不要 - - void force(boolean flag); - * 将通道里尚未写入磁盘的数据强制写到磁盘上 - * 出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。 - * boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。 - - channel.close(); - * 关闭 Channel - - - FileLock fileChannel.tryLock(); - * 该调用不会阻塞,直接获取锁,如果锁不存在,返回null - * 获取文件锁 - * demo - FileLock lock = fileChanne.lock(); //获取文件锁 - lock.release(); //释放锁 - FileLock fileChannel.lock(); - * 该调用会阻塞,直到获取到锁 - - MappedByteBuffer map(MapMode mode,long position, long size) - * 把当前Channel映射到内存 - * mode 指定方式(只读,可写....),position 指定文件管道的开始位置,size 指定结束位置 - * MappedByteBuffer 是 ByteBuffer 的子类 - -------------------------------- -FileChannel-内部静态类 | -------------------------------- - MapMode - READ_ONLY - * 只读,如果写操作会异常 - - READ_WRITE - * 可写,任何修改都会在某个时间被写入文件系统 - - PRIVATE - * 可写,但是这个不会写入到文件系统 diff --git a/Netty/NIO-Selector.java b/Netty/NIO-Selector.java deleted file mode 100644 index 5ffc9900..00000000 --- a/Netty/NIO-Selector.java +++ /dev/null @@ -1,122 +0,0 @@ ----------------------------- -Selector | ----------------------------- - # 多路复用器,它是NIO编程的基础,非常重要.多路复用器,提供'选择已经就绪的任务的能力' - # 简单说,Selector 会不断的轮询,注册在其上的通道 Channel,如果某个通道发生了读写操作.这个通道就处于就绪状态.会被 Selector 轮询出来,然后通过 SelectionKey 可以 取得就绪的 Channel 集合,从而进行后续的操作 - # 一个多路复用器,可以负责成千上万的 Channel 通道,没有上限.这也是JDK使用 epoll代替了传统 Select实现,获取连接句柄没有限制,意味着我们只需要一个线程负责 Selector 的轮询.就可以接入成千上万的客户端,这就是JDK NIO库的巨大进步 - # Selector 线程就类似于一个管理者(Master),管理成千万的通道,然后轮询出哪个通道已经准备好.通知CPU执行I/O操作 - # Selector 模式:当IO事件(管道)注册到选择器后,Selector 会分配给每个管道一个 key 值,标签.Selector 轮询注册的管道,当管道就绪后, select就会识别,通过key值来找到对应的管道.进行相关数据处理操作.(写入,或者读到缓冲区) - # 每个管道都会对选择器注册不同的事件状态,以便选择器查找 - - SelectionKey.OP_ACCEPT //用于套接字接受操作的操作集位。 - SelectionKey.OP_CONNECT //用于套接字连接操作的操作集位。 - SelectionKey.OP_READ //用于读取操作的操作集位。 - SelectionKey.OP_WRITE //用于写入操作的操作集位。 - - # Selector允许单线程处理多个 Channel。 - # 如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。 - # 例如,在一个聊天服务器中。这是在一个单线程中使用一个Selector处理3个Channel的图示: - Thread - | - Selector - |----|----| - Channel Channel Channel - ----------------------------- -Selector-创建 | ----------------------------- - Selector selector = Selector.open(); - ----------------------------- -Selector-注册通道(Channel) | ----------------------------- - # 为了将Channel和Selector配合使用,必须将channel注册到selector上。 - # 与Selector一起使用时,Channel必须处于非阻塞模式下。 - # 这意味着'不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以'。 - # 通过SelectableChannel.register()方法来实现 - - Selector selector = Selector.open(); - channel.configureBlocking(false); //设置非阻塞模式 - SelectionKey key = channel.register(selector,Selectionkey.OP_READ); - - * 第一个参数,就是 channel 要注册的 selector - * 第二个参数,是监听的事件,枚举值,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件: - SelectionKey.OP_ACCEPT - SelectionKey.OP_CONNECT - SelectionKey.OP_READ - SelectionKey.OP_WRITE - - # 通道触发了一个事件意思是该事件已经就绪。 - * 某个channel成功连接到另一个服务器称为"连接就绪"。 - * 一个server socket channel准备好接收新进入的连接称为"接收就绪"。 - * 一个有数据可读的通道可以说是"读就绪"。 - * 等待写数据的通道可以说是"写就绪"。 - - # 如果需要注册多个事件,就用位移操作运算多个事件 - int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; - - ----------------------------- -Selector-SelectionKey | ----------------------------- - # 当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些属性 - interest集合 - ready集合 - Channel - Selector - 附加的对象(可选) - - # interest集合 - interest集合是你所选择的感兴趣的事件集合。可以通过 SelectionKey 读写interest集合 - int interestSet = selectionKey.interestOps(); - boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT; - boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; - boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; - boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE; - - * 可以看到,用"位与"操作interest 集合和给定的SelectionKey常量,可以确定某个确定的事件是否在interest 集合中。 - - # ready集合 - # ready 集合是通道已经准备就绪的操作的集合。 - # 在一次选择(Selection)之后,你会首先访问这个ready set。Selection将在下一小节进行解释。可以这样访问ready集合 - int readySet = selectionKey.readyOps(); - # 可以用像检测interest集合那样的方法,来检测channel中什么事件或操作已经就绪。但是,也可以使用以下四个方法,它们都会返回一个布尔类型 - selectionKey.isAcceptable(); //是否可以接收客户端的请求 - selectionKey.isConnectable(); //是否有客户端连接 - selectionKey.isReadable(); //是否可读 - selectionKey.isWritable(); //是否可写 - - # 访问 Channel 和 Selector - Channel channel = selectionKey.channel(); - Selector selector = selectionKey.selector(); - - - # 附加的对象 - * 可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。 - * 例如,可以附加与通道一起使用的Buffer,或是包含聚集数据的某个对象。使用方法如下: - selectionKey.attach(theObject); //设置附加对象 - Object attachedObj = selectionKey.attachment(); //获取附加对象 - * 也可以在通道注册的时候设置附加对象('最后一个参数') - SelectionKey key = channel.register(selector,Selectionkey.OP_READm,attachedObj); - - # API - ----------------------------- -Selector-选择通道 | ----------------------------- - # 一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。 - # 这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。 - # 换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。 - # select 方法 - int select(); - * 阻塞到至少有一个通道在你注册的事件上就绪了。 - int select(long timeout); - * 和select()一样,除了最长会阻塞timeout毫秒(参数)。 - int selectNow(); - * 不会阻塞,不管什么通道就绪都立刻返回 - * 此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。 - ----------------------------- -Selector-API | ----------------------------- - \ No newline at end of file diff --git a/Netty/NIO-ServerSocketChannel.java b/Netty/NIO-ServerSocketChannel.java deleted file mode 100644 index 8f737a41..00000000 --- a/Netty/NIO-ServerSocketChannel.java +++ /dev/null @@ -1,36 +0,0 @@ ---------------------------------- -ServerSocketChannel | ---------------------------------- - # ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, - # 就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。 - # Demo - ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); - serverSocketChannel.socket().bind(new InetSocketAddress(9999)); //绑定端口 - while(true){ - SocketChannel socketChannel = serverSocketChannel.accept(); - //do something with socketChannel... - } - # accept(); 会一直阻塞线程,直到有新的客户端连接进来 - - ---------------------------------- -ServerSocketChannel-非阻塞模式 | ---------------------------------- - # ServerSocketChannel可以设置成非阻塞模式。 - # 在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是null。 因此,需要检查返回的SocketChannel是否是null. - # Demo - ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); - serverSocketChannel.socket().bind(new InetSocketAddress(9999)); - serverSocketChannel.configureBlocking(false); //设置为非阻塞模式 - while(true){ - SocketChannel socketChannel = serverSocketChannel.accept(); - if(socketChannel != null){ - //do something with socketChannel... - } - } - - ---------------------------------- -ServerSocketChannel-API | ---------------------------------- - \ No newline at end of file diff --git a/Netty/NIO-SocketChannel.java b/Netty/NIO-SocketChannel.java deleted file mode 100644 index 310cafe6..00000000 --- a/Netty/NIO-SocketChannel.java +++ /dev/null @@ -1,73 +0,0 @@ -------------------------------- -SocketChannel | -------------------------------- - # SocketChannel是一个连接到TCP网络套接字的通道。 - # 可以通过以下2种方式创建SocketChannel: - 1,打开一个SocketChannel并连接到互联网上的某台服务器。 - 2,一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。 - # 打开 SocketChannel - SocketChannel socketChannel = SocketChannel.open(); - socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80)); - - # 关闭 SocketChannel - socketChannel.close(); - - # 从 SocketChannel 中读取数据 - - ByteBuffer byteBuffer = ByteBuffer.allocate(1024); - int bytesRead = socketChannel.read(byteBuffer); - - * 分配一个Buffer。从SocketChannel读取到的数据将会放到这个Buffer中。 - * 然后,调用SocketChannel.read()。该方法将数据从SocketChannel 读到Buffer中。 - * read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了流的末尾(连接关闭了)。 - - # 写入数据到 SocketChannel - String newData = "New String to write to file..." + System.currentTimeMillis(); - ByteBuffer buf = ByteBuffer.allocate(1024); - buf.clear(); //清空缓冲区 - buf.put(newData.getBytes()); //填充数据 - buf.flip(); //复位,limit = position,position = 0 - while(buf.hasRemaining()) { //确定还有数据,进行写入 - channel.write(buf); - } - * 注意SocketChannel.write()方法的调用是在一个while循环中的。 - * Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。 - - - # 非阻塞模式与选择器 - * 非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道已经准备好了读取,写入等。 - * Selector与SocketChannel的搭配使用会在后面详讲。 - - -------------------------------- -SocketChannel-读 | -------------------------------- - -------------------------------- -SocketChannel-写 | -------------------------------- - -------------------------------- -SocketChannel-非阻塞模式 | -------------------------------- - # 可以设置 SocketChannel 为非阻塞模式(non-blocking mode).设置之后,就可以在异步模式下调用connect(), read() 和write()了。 - SocketChannel socketChannel = SocketChannel.open(); - socketChannel.configureBlocking(false); - socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80)); - - # write(); - * 非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。 - * 所以需要在循环中调用write()。前面已经有例子了,这里就不赘述了。 - - # read(); - * 非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。 - * 所以需要关注它的int返回值,它会告诉你读取了多少字节。 - - -------------------------------- -SocketChannel-Selector | -------------------------------- - -------------------------------- -SocketChannel-API | -------------------------------- \ No newline at end of file diff --git a/Netty/NIO.java b/Netty/NIO.java deleted file mode 100644 index ede9eddb..00000000 --- a/Netty/NIO.java +++ /dev/null @@ -1,98 +0,0 @@ -------------------------------- -Netty-IO的区别 | -------------------------------- - # JDK1.4的的东西 - - # BIO 和 NIO的区别 - 阻塞 :程序在获取网络数据的时候,如果网络数据传输慢,那么程序就会一直等待下去.直到传输完毕 - 非阻塞 :程序可以直接获取已经准备就绪的数据,无需等待 - 'BIO为同步阻塞,NIO为同步非阻塞,NIO并没有实现异步',JDK1.7以后,升级了NIO包,支持异步非阻塞通信模型-NIO2.0(AIO) - 同步和异步 :同步和异步一般是面向操作系统与程序对于IO操作的层面上来区别的 - 同步:程序直接参数IO读写,并且程序会阻塞到某方法,直到数据准备就绪,或者采用轮询的策略实时检查数据的就绪状态,如果数据准备就绪则读取数据、 - 异步:所有的IO交给操作系统去处理,与程序没关系.程序不用关心读写.操作系统完成了IO后,会通知程序,程序只需要拿走数据就OK - - 同步说的是Servlet服务器端的执行方式 - 阻塞说的是具体的技术,接收数据的方法(IO,NIO) - - - # 阻塞/非阻塞 - * 阻塞,意思是程序,收数据的时候.是要一直等着.啥也做不了,直到数据传完 - * 非阻塞,意思是.有个缓冲区.收的时候,我不用一直等着.可以做点其他有意思的事情,缓冲区里面有数据了?我就处理一点 - - # 异步/同步 - * 跟操作系统有关,异步就是.收数据这活儿操作系统给我干了.我不管,就有时间搞其他的.操作系统弄完了.通知我OK - * 同步就是,直接我亲自上. - - # NIO原理 - * 事件的轮询,多路复用技术(Select模式),轮询交给一个单独的线程来完成.当是数据准备OK后通知读写线程来进行读写操作.那么在数据未准备OK之前,读写线程可以完成其他的事情 - * 传统BIO中,读写线程需要自己等着数据准备OK,进行读写 - - # 学习网站 - * http://ifeve.com/overview/ - * http://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html - * http://watchmen.cn/portal.php?mod=view&aid=509 - * http://www.iteye.com/magazines/132-Java-NIO - * http://www.importnew.com/19816.html - - - BIO - 同步阻塞 - NIO - 同步非阻塞(JDK1.4) - AIO - 异步非阻塞(JDK1.7) - -------------------------------- -Netty-NIO | -------------------------------- - # NIO 有人叫做 New IO 或者 Non-block IO (非阻塞),最好还是以后者的理解比较恰当 - - # 学习NIO需要明确几个概念 - Buffer - * 缓冲区 - * NIO 中核心的概念,传统是直接IO数据,要么写要么读.NIO提供了一个缓冲区的概念 - - Channel - * 管道/通道 - - Selector - * 选择器/多路复用器 - -------------------------------- -Netty-Channel | -------------------------------- - # 通道,跟自来水管道没啥区别.网络数据通过 Channel 读取/写入,'与传统流不同的是,它双向的',而流要么是读流,要么是写流 - # 最叼的是,这个通道可以同时的进行读/写 - # 最关键的是,可以和多路复用器结合起来,有多种状态位,方便多路复用器去识别. - # 事实上,通道分为两大类 - 1,SelectableChannel 网络读写 - * DatagramChannel //UDP - * SocketChannel //TCP - * ServerSocketChannel //服务器端对象 - - 2,FileChannel 文件操作 - - -------------------------------- -Netty-Selector | -------------------------------- - # 多路复用器,它是NIO编程的基础,非常重要.多路复用器,提供'选择已经就绪的任务的能力' - # 简单说,Selector 会不断的轮询,注册在其上的通道 Channel,如果某个通道发生了读写操作.这个通道就处于就绪状态.会被 Selector 轮询出来,然后通过 SelectionKey 可以 取得就绪的 Channel 集合,从而进行后续的操作 - # 一个多路复用器,可以负责成千上万的 Channel 通道,没有上限.这也是JDK使用 epoll代替了传统 Select实现,获取连接句柄没有限制,意味着我们只需要一个线程负责 Selector 的轮询.就可以接入成千上万的客户端,这就是JDK NIO库的巨大进步 - - # Selector 线程就类似于一个管理者(Master),管理成千万的通道,然后轮询出哪个通道已经准备好.通知CPU执行I/O操作 - # Selector 模式:当IO事件(管道)注册到选择器后,Selector 会分配给每个管道一个 key 值,标签.Selector 轮询注册的管道,当管道就绪后, select就会识别,通过key值来找到对应的管道.进行相关数据处理操作.(写入,或者读到缓冲区) - # 每个管道都会对选择器注册不同的事件状态,以便选择器查找 - SelectionKey.OP_ACCEPT //用于套接字接受操作的操作集位。 - SelectionKey.OP_CONNECT //用于套接字连接操作的操作集位。 - SelectionKey.OP_READ //用于读取操作的操作集位。 - SelectionKey.OP_WRITE //用于写入操作的操作集位。 - - -------------------------------- -Netty-总结 | -------------------------------- - # Buffer - * 新建总是要执行 clear(); 比较好 - * 如果是从 Buffer 中读取数据,要先记得复位,然后再进行读取. - * 如果是从 Buffer 中读取数据那么要用 while(buf.hasRemaining()) 循环,有可能数据一次性没读取完 - - # Channel - * 如果是读取 Channel 中的数据,非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的 int 返回值 \ No newline at end of file diff --git a/Netty/Netty-API.java b/Netty/Netty-API.java deleted file mode 100644 index 4435c6cf..00000000 --- a/Netty/Netty-API.java +++ /dev/null @@ -1,48 +0,0 @@ -NioEventLoopGroup - # 是用来处理I/O操作的多线程事件循环器 - # Netty 提供了许多不同的 EventLoopGroup的实现用来处理不同的传输。 - -AbstractBootstrap - # io.netty.bootstrap.AbstractBootstrap - -ChannelHandlerAdapter - # io.netty.channel.ChannelHandlerAdapter - -ChannelHandler - # io.netty.channel.ChannelHandler - # handler 中最顶层的接口 - - - -SimpleChannelInboundHandler - # io.netty.channel.SimpleChannelInboundHandler - # Client端Handler继承 - # 接收到数据后会自动release掉数据占用的Bytebuffer资源(自动调用Bytebuffer.release())。 - -ChannelInboundHandlerAdapter - # io.netty.channel.ChannelInboundHandlerAdapter - # Server端Handler继承 - # 这个类实现了 ChannelInboundHandler 接口 - # 现在仅仅只需要继承 ChannelInboundHandlerAdapter 类而不是你自己去实现接口方法 - - -ChannelInboundHandler - # io.netty.channel.ChannelInboundHandler - # ChannelInboundHandler 接口提供了许多事件处理的接口方法,然后你可以覆盖这些方法。 - - -ByteToMessageDecoder - # ChannelInboundHandler 的一个实现类,他可以在处理数据拆分的问题上变得很简单 - -ReferenceCountUtil - # io.netty.util.ReferenceCountUtil - # 静态方法 - public static boolean release(Object msg); - public static boolean release(Object msg, int decrement); - -ServerBootstrap - # 方法 - option(ChannelOption.SO_BACKLOG,128); - * 系统内核,TCP维护的俩队列的长度的和 - - \ No newline at end of file diff --git "a/Netty/Netty-\347\275\221\347\273\234\347\274\226\347\250\213\345\237\272\347\241\200.java" "b/Netty/Netty-\347\275\221\347\273\234\347\274\226\347\250\213\345\237\272\347\241\200.java" deleted file mode 100644 index f059607c..00000000 --- "a/Netty/Netty-\347\275\221\347\273\234\347\274\226\347\250\213\345\237\272\347\241\200.java" +++ /dev/null @@ -1,57 +0,0 @@ ---------------------------- -Netty网络编程基础 | ---------------------------- - - ---------------------------- -Netty-UDP发送 | ---------------------------- - - //创建udp服务,通过 DatagramSocet对象,绑定本机端口 - DatagramSocket ds = new DatagramSocket(8888); - //确定数据,并封装成数据包 - byte[] buf = "KevinBlandy".getBytes();//String.getBytes();把字符串转换成字节数组。 - //构建数据包,指定数据,长度,地址,端口 - DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.132.245"),10000); - //通过socket服务将已有的数据包通过 send方法。发送出去。 - ds.send(dp);//阻塞式方法。如果没数据就会一直等(线程机制)。 - //关闭资源 - ds.close(); - ---------------------------- -Netty-UDP接收 | ---------------------------- - //创建udpsocket服务.建立端点,监听端口 - DatagramSocket ds = new DatagramSocket(10000); - while(true){ - //定义数据包,用于存储数据 - byte[] buf = new byte[1024];//缓冲区 - DatagramPacket dp = new DatagramPacket(buf,buf.length); - //通过socket服务receive方法将收到的数据存入数据包中(阻塞方法) - ds.receive(dp); - //通过数据包的方法获取其中的数据 - String ip = dp.getAddress().getHostAddress(); //获取IP。 - String data = new String(dp.getData(),0,dp.getLength());//获取数据。 - int port = dp.getPort(); //获取端口。 - System.out.println(ip+"::"+data+"::"+port); - //ds.close(); 关闭资源 - } - - ---------------------------- -Netty-TCP发送 | ---------------------------- - Socket s = new Socket("120.76.182.243",8080); - OutputStream os = s.getOutputStream(); //此输出流是 OutputStream();是抽象的。 - InputStream is = s.getInputStream(); //此输入流是 InputStream();是抽象的。 - ---------------------------- -Netty-TCP接收 | ---------------------------- - ServerSocket ss = new ServerSocket(8888); - while(true){ - Socket s = ss.accept(); //线程阻塞 - InputStream is = s.getInputStream(); //获取 InputStream - OutputStream os = s.getOutputStream(); //获取 OutputStream - } - \ No newline at end of file diff --git a/Netty/api/netty-api-ByteBuf.java b/Netty/api/netty-api-ByteBuf.java new file mode 100644 index 00000000..df57b3ae --- /dev/null +++ b/Netty/api/netty-api-ByteBuf.java @@ -0,0 +1,108 @@ +----------------------------- +ByteBuf | +----------------------------- + # abstract class ByteBuf implements ReferenceCounted, Comparable + +----------------------------- +方法 | +----------------------------- + // 角标的操作 + int readerIndex(); + * 返回读角标 + + ByteBuf readerIndex(int readerIndex); + * 设置新的读角标 + + int writerIndex(); + * 返回写角标 + + ByteBuf writerIndex(int writerIndex) + * 设置新的写角标 + + ByteBuf setIndex(int readerIndex, int writerIndex); + * 同时设置读写角标 + + int arrayOffset(); + * 返回底层数组存储数据的偏移量(一般都是0) + + // 容器的属性操作 + int capacity(); + * 返回容器长度 + + boolean isReadable(); + * 是否还有起码1个字节的可读空间 + * wi > ri + + boolean isReadable(int size); + * 是否还有指定长度的可读空间 + * wi - ri >= size + + boolean isWritable(); + * 是否起码还有1个字节的可写空间 + + boolean isWritable(int size); + * 是否起码还有指定个字节的可写空间 + + int writableBytes(); + * 返回可写的空间大小 + + ByteBuf clear(); + * 设置读写标识都为1,但是未清空内容 + + int readableBytes(); + * 返回可读字节数(写索引 - 读索引) + + + // 数据读取 + long readUnsignedInt() + ByteBuf retainedDuplicate() + short readUnsignedByte() + CharSequence getCharSequence(int index, int length, Charset charset); + * 从指定的角标开始,读取指定长度的数据,使用指定编码后返回 + + // 数据丢弃 + ByteBuf discardReadBytes(); + * 清空 ByteBuf 中已读取的数据,未读数据往前移动,从而使 ByteBuf 有多余的空间容纳新的数据 + * 可能会涉及内存复制,因为它需要移动 ByteBuf 中可读的字节到开始位置,这样的操作会影响性能 + * 一般在需要马上释放内存的时候使用收益会比较大 + + ByteBuf discardSomeReadBytes(); + + // 标记操作 + ByteBuf markReaderIndex() + * 标记读角标 + + ByteBuf resetReaderIndex() + * 重置读角标为标记角标 + + // 复制和重用 + ByteBuf duplicate(); + * 复制一个新的缓冲区,所有的数据,包括index都是一样的 + * 共享内存,数据变化会互相影响 + + ByteBuf slice(); + * 复制新的缓冲区,有独立的index + capacity = '原来buffer的可读长度' + rindex = 0 + windex = '原buffer的windex' + * 共享内存,数据变化会互相影响 + * 把未读的数据复制出来 + + ByteBuf slice(int index, int length); + + ByteBuf order(ByteOrder endianness); + + + // 其他 + boolean hasArray() + * 是否支持访问数组(可能,非堆的buf) + * 访问非堆缓冲区 ByteBuf 的数组,会抛出异常 UnsupportedOperationException + + byte[] array(); + * 获取关联的数组 + + int indexOf(int fromIndex, int toIndex, byte value); + * 判断value知否存在与buffer,从from开始,到to结束 + + int bytesBefore(byte value); + \ No newline at end of file diff --git a/Netty/api/netty-api-ChannelFuture.java b/Netty/api/netty-api-ChannelFuture.java new file mode 100644 index 00000000..9f603268 --- /dev/null +++ b/Netty/api/netty-api-ChannelFuture.java @@ -0,0 +1,38 @@ +-------------------------------- +ChannelFuture | +-------------------------------- + # interface ChannelFuture extends Future + # 事件的回调 + + +-------------------------------- +方法 | +-------------------------------- + Channel channel(); + * 返回关联的channel + + @Override + ChannelFuture addListener(GenericFutureListener> listener); + * 添加一个监听 + + @Override + ChannelFuture addListeners(GenericFutureListener>... listeners); + * 添加多个监听 + + @Override + ChannelFuture removeListener(GenericFutureListener> listener); + * 移除一个监听 + + @Override + ChannelFuture removeListeners(GenericFutureListener>... listeners); + * 移除多个监听 + + ChannelFuture sync() + * 同步,线程阻塞,直到任务完成 + + Throwable cause() + * 返回异常信息 + + boolean isSuccess(); + * 是否操作成功 + diff --git a/Netty/api/netty-api-ChannelFutureListener.java b/Netty/api/netty-api-ChannelFutureListener.java new file mode 100644 index 00000000..53696a09 --- /dev/null +++ b/Netty/api/netty-api-ChannelFutureListener.java @@ -0,0 +1,17 @@ +-------------------------------- +ChannelFutureListener | +-------------------------------- + # interface ChannelFutureListener extends GenericFutureListener + # 回调事件监听器 + # 预定义了N多的事件回调的快捷实现 + CLOSE + * 完成后关闭连接 + CLOSE_ON_FAILURE + * 如果抛出了异常,关闭连接 + FIRE_EXCEPTION_ON_FAILURE + +-------------------------------- +方法 | +-------------------------------- + public void operationComplete(ChannelFuture future) + * 覆写完成操作的事件 \ No newline at end of file diff --git a/Netty/api/netty-api-ChannelHandlerContext.java b/Netty/api/netty-api-ChannelHandlerContext.java new file mode 100644 index 00000000..5590b72c --- /dev/null +++ b/Netty/api/netty-api-ChannelHandlerContext.java @@ -0,0 +1,45 @@ +-------------------------------- +ChannelHandlerContext | +-------------------------------- + # interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker + # 表示客户端的连接 + +-------------------------------- +接口方法 | +-------------------------------- + + ChannelFuture write(Object msg); + * 写入数据到通道,但是会被缓存到缓冲区 + + ChannelFuture write(Object msg, ChannelPromise promise); + + ChannelFuture writeAndFlush(Object msg); + * 写入并且刷出缓冲区 + + ChannelFuture writeAndFlush(Object msg, ChannelPromise promise); + * 返回 futrue + + ChannelOutboundInvoker flush(); + * 把缓冲区中的数据刷出 + + ByteBufAllocator alloc(); + * 得到一个当前的ByteBufAllocator,从而构建一个Buffer + ByteBuf intBuf = ctx.alloc().buffer(4); + + ChannelHandlerContext fireChannelRead(Object msg) + * 触发下一个 ChannelInboundHandler 的 channelRead() 方法,并且给定msg参数 + + ChannelHandlerContext fireUserEventTriggered(Object evt); + * 主动触发用户自定义的事件 + + String name(); + * 获取的是当前 handler 的name(添加到 pipeline 时设置的name属性) + + ChannelHandler handler(); + * 返回的就是当前的handler实例对象 + + ChannelProgressivePromise newProgressivePromise(); + ChannelFuture newFailedFuture(Throwable cause); + ChannelFuture newSucceededFuture(); + ChannelPromise newPromise(); + ChannelPromise voidPromise(); \ No newline at end of file diff --git a/Netty/api/netty-api-ChannelInboundHandlerAdapter.java b/Netty/api/netty-api-ChannelInboundHandlerAdapter.java new file mode 100644 index 00000000..da16abab --- /dev/null +++ b/Netty/api/netty-api-ChannelInboundHandlerAdapter.java @@ -0,0 +1,42 @@ +-------------------------------- +ChannelInboundHandlerAdapter | +-------------------------------- + # class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler + # 服务端实现的读取事件处理类 + +-------------------------------- +方法 | +-------------------------------- + public void channelActive(ChannelHandlerContext ctx) + * 连接被建立并且准备进行通信时被调用 + + public void channelRead(ChannelHandlerContext ctx, Object msg) + * 读取事件 + + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) + * 异常时调用 + + public void handlerAdded(ChannelHandlerContext ctx) + * handler被添加时调用 + + public void handlerRemoved(ChannelHandlerContext ctx) + * handler被移除时调用 + + void channelInactive(ChannelHandlerContext ctx) + * 非活跃状态时调用 + + void channelReadComplete(ChannelHandlerContext ctx) + * 在读取完成后调用 + + void handlerAdded(ChannelHandlerContext ctx) + * 在添加到 ChannelPipeline 调用 + + void handlerRemoved(ChannelHandlerContext ctx) + * 从 ChannelPipeline 移除时调用 + + void userEventTriggered(ChannelHandlerContext ctx, Object evt) + * 主动触发用户自定义的事时,调用 + + void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception; + * 在channel变为可写状态的时候触发 + * 可以使用 ctx.channel().isWritable(); 来判断 \ No newline at end of file diff --git a/Netty/api/netty-api-ChannelInitializer.java b/Netty/api/netty-api-ChannelInitializer.java new file mode 100644 index 00000000..e69de29b diff --git a/Netty/api/netty-api-ChannelOutboundHandlerAdapter.java b/Netty/api/netty-api-ChannelOutboundHandlerAdapter.java new file mode 100644 index 00000000..d36928d3 --- /dev/null +++ b/Netty/api/netty-api-ChannelOutboundHandlerAdapter.java @@ -0,0 +1,4 @@ +-------------------------------- +ChannelOutboundHandlerAdapter | +-------------------------------- + # class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler \ No newline at end of file diff --git a/Netty/api/netty-api-ChannelPipeline.java b/Netty/api/netty-api-ChannelPipeline.java new file mode 100644 index 00000000..521e2c2d --- /dev/null +++ b/Netty/api/netty-api-ChannelPipeline.java @@ -0,0 +1,4 @@ +-------------------------------- +ChannelPipeline | +-------------------------------- + # ChannelPipeline 负责安排Handler的顺序及其执行 diff --git a/Netty/api/netty-api-ReferenceCountUtil.java b/Netty/api/netty-api-ReferenceCountUtil.java new file mode 100644 index 00000000..bce424ba --- /dev/null +++ b/Netty/api/netty-api-ReferenceCountUtil.java @@ -0,0 +1,6 @@ +------------------------ +ReferenceCountUtil | +------------------------ + boolean release(Object msg) + * 释放资源 + \ No newline at end of file diff --git a/Netty/api/netty-api-ServerBootstrap.java b/Netty/api/netty-api-ServerBootstrap.java new file mode 100644 index 00000000..98b5e1bb --- /dev/null +++ b/Netty/api/netty-api-ServerBootstrap.java @@ -0,0 +1,44 @@ +------------------------- +ServerBootstrap | +------------------------- + # ServerBootstrap extends AbstractBootstrap + # 构造函数 + public ServerBootstrap() + + +------------------------- +方法 | +------------------------- + ServerBootstrap handler(ChannelHandler handler) + * 添加服务端的handler + + ServerBootstrap childHandler(ChannelHandler childHandler) + * 设置一个或者多个客户端处理器 + * 一般可以设置 ChannelInitializer 实例,添加一个handler处理链 + serverBootstrap.childHandler(new ChannelInitializer() { + @Override // 唯一的抽象方法 + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast(new TimeServerHandler()); // 在此添加一个或者多个Handler处理 + } + }); + + ServerBootstrap childOption(ChannelOption childOption, T value) + * 设置客户端连接的配置 + + ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) + * parentGroup处理连接事件 + * childGroup 处理IO事件 + + ChannelFuture bind() + ChannelFuture bind(int inetPort) + ChannelFuture bind(String inetHost, int inetPort) + ChannelFuture bind(InetAddress inetHost, int inetPort) + ChannelFuture bind(SocketAddress localAddress) + * 绑定操作 + + B channel(Class channelClass) + * 设置io类型 + + B option(ChannelOption option, T value) + * 设置服务器端的属性 + diff --git a/Netty/api/netty-api-channel.java b/Netty/api/netty-api-channel.java new file mode 100644 index 00000000..714f9fc8 --- /dev/null +++ b/Netty/api/netty-api-channel.java @@ -0,0 +1,84 @@ +----------------------------- +Channel | +----------------------------- + # 实现的接口 + Channel extends AttributeMap, ChannelOutboundInvoker, Comparable + |-LineBasedFrameDecoder + |-LengthFieldBasedFrameDecoder + |-DelimiterBasedFrameDecoder + |-FixedLengthFrameDecoder + |-SslHandler + * 实现了 ChannelOutboundHandler + |-MessageToMessageDecoder + |-StringDecoder + |-ChannelInitializer + |-SimpleChannelInboundHandler + * 实现类型的强制转换,并且自动释放buf + + |-ChannelOutboundHandler + * 写处理接口 + + |-ChannelOutboundHandlerAdapter + |-MessageToByteEncoder + |-MessageToMessageEncoder + |-LengthFieldPrepender + |-StringEncoder + + |-ChannelDuplexHandler + * 实现了读写接口 + * 继承:ChannelInboundHandlerAdapter,实现:ChannelOutboundHandler + + |-ChunkedWriteHandler + |-CombinedChannelDuplexHandler + + +-------------------- +buf体系 | +-------------------- +ReferenceCounted + |-ByteBuf + |-CompositeByteBuf + |-ByteBufHolder + |-FileRegion + |-DefaultFileRegion +ByteBufProcessor +ByteBufAllocator +Unpooled +ByteBufUtil + + +-------------------- +其他 | +-------------------- +ChunkedInput + |-ChunkedFile + |-ChunkedNioFile + |-ChunkedStream + |-ChunkedNioStream diff --git a/Netty/http/netty-http.java b/Netty/http/netty-http.java new file mode 100644 index 00000000..f7716072 --- /dev/null +++ b/Netty/http/netty-http.java @@ -0,0 +1,18 @@ +------------------------------------ +http | +------------------------------------ + # 类库 + HttpServerExpectContinueHandler + HttpObjectDecoder + |-HttpRequestDecoder + |-HttpResponseDecoder + |-RtspDecoder + HttpServerCodec + HttpContentCompressor + HttpUtil + HttpObjectAggregator + |-HttpServerUpgradeHandler + FullHttpRequest + |-DefaultFullHttpRequest + FullHttpResponse + |-DefaultFullHttpResponse \ No newline at end of file diff --git a/Netty/netty-HashedWheelTimer.java b/Netty/netty-HashedWheelTimer.java new file mode 100644 index 00000000..aee00736 --- /dev/null +++ b/Netty/netty-HashedWheelTimer.java @@ -0,0 +1,802 @@ +--------------------------------- +HashedWheelTimer | +--------------------------------- + # io.netty.util.HashedWheelTimer + * 时间轮算法的实现 + +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.util; + +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLong; + +import static io.netty.util.internal.StringUtil.simpleClassName; + +public class HashedWheelTimer implements Timer { + + static final InternalLogger logger = InternalLoggerFactory.getInstance(HashedWheelTimer.class); + + // 实例数量 + private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(); + // 警告 - 有太多实例 + private static final AtomicBoolean WARNED_TOO_MANY_INSTANCES = new AtomicBoolean(); + private static final int INSTANCE_COUNT_LIMIT = 64; + private static final long MILLISECOND_NANOS = TimeUnit.MILLISECONDS.toNanos(1); + private static final ResourceLeakDetector leakDetector = ResourceLeakDetectorFactory.instance().newResourceLeakDetector(HashedWheelTimer.class, 1); + + private static final AtomicIntegerFieldUpdater WORKER_STATE_UPDATER = AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimer.class, "workerState"); + + private final ResourceLeakTracker leak; + private final Worker worker = new Worker(); + private final Thread workerThread; + + public static final int WORKER_STATE_INIT = 0; + public static final int WORKER_STATE_STARTED = 1; + public static final int WORKER_STATE_SHUTDOWN = 2; + @SuppressWarnings({ "unused", "FieldMayBeFinal" }) + private volatile int workerState; // 0 - init, 1 - started, 2 - shut down + + private final long tickDuration; + private final HashedWheelBucket[] wheel; + private final int mask; + private final CountDownLatch startTimeInitialized = new CountDownLatch(1); + private final Queue timeouts = PlatformDependent.newMpscQueue(); + private final Queue cancelledTimeouts = PlatformDependent.newMpscQueue(); + private final AtomicLong pendingTimeouts = new AtomicLong(0); + private final long maxPendingTimeouts; + + private volatile long startTime; + + /** + * Creates a new timer with the default thread factory + * ({@link Executors#defaultThreadFactory()}), default tick duration, and + * default number of ticks per wheel. + */ + public HashedWheelTimer() { + this(Executors.defaultThreadFactory()); + } + + /** + * Creates a new timer with the default thread factory + * ({@link Executors#defaultThreadFactory()}) and default number of ticks + * per wheel. + * + * @param tickDuration the duration between tick + * @param unit the time unit of the {@code tickDuration} + * @throws NullPointerException if {@code unit} is {@code null} + * @throws IllegalArgumentException if {@code tickDuration} is <= 0 + */ + public HashedWheelTimer(long tickDuration, TimeUnit unit) { + this(Executors.defaultThreadFactory(), tickDuration, unit); + } + + /** + * Creates a new timer with the default thread factory + * ({@link Executors#defaultThreadFactory()}). + * + * @param tickDuration the duration between tick + * @param unit the time unit of the {@code tickDuration} + * @param ticksPerWheel the size of the wheel + * @throws NullPointerException if {@code unit} is {@code null} + * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0 + */ + public HashedWheelTimer(long tickDuration, TimeUnit unit, int ticksPerWheel) { + this(Executors.defaultThreadFactory(), tickDuration, unit, ticksPerWheel); + } + + /** + * Creates a new timer with the default tick duration and default number of + * ticks per wheel. + * + * @param threadFactory a {@link ThreadFactory} that creates a + * background {@link Thread} which is dedicated to + * {@link TimerTask} execution. + * @throws NullPointerException if {@code threadFactory} is {@code null} + */ + public HashedWheelTimer(ThreadFactory threadFactory) { + this(threadFactory, 100, TimeUnit.MILLISECONDS); + } + + /** + * Creates a new timer with the default number of ticks per wheel. + * + * @param threadFactory a {@link ThreadFactory} that creates a + * background {@link Thread} which is dedicated to + * {@link TimerTask} execution. + * @param tickDuration the duration between tick + * @param unit the time unit of the {@code tickDuration} + * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null} + * @throws IllegalArgumentException if {@code tickDuration} is <= 0 + */ + public HashedWheelTimer( + ThreadFactory threadFactory, long tickDuration, TimeUnit unit) { + this(threadFactory, tickDuration, unit, 512); + } + + /** + * Creates a new timer. + * + * @param threadFactory a {@link ThreadFactory} that creates a + * background {@link Thread} which is dedicated to + * {@link TimerTask} execution. + * @param tickDuration the duration between tick + * @param unit the time unit of the {@code tickDuration} + * @param ticksPerWheel the size of the wheel + * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null} + * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0 + */ + public HashedWheelTimer( + ThreadFactory threadFactory, + long tickDuration, TimeUnit unit, int ticksPerWheel) { + this(threadFactory, tickDuration, unit, ticksPerWheel, true); + } + + /** + * Creates a new timer. + * + * @param threadFactory a {@link ThreadFactory} that creates a + * background {@link Thread} which is dedicated to + * {@link TimerTask} execution. + * @param tickDuration the duration between tick + * @param unit the time unit of the {@code tickDuration} + * @param ticksPerWheel the size of the wheel + * @param leakDetection {@code true} if leak detection should be enabled always, + * if false it will only be enabled if the worker thread is not + * a daemon thread. + * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null} + * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0 + */ + public HashedWheelTimer( + ThreadFactory threadFactory, + long tickDuration, TimeUnit unit, int ticksPerWheel, boolean leakDetection) { + this(threadFactory, tickDuration, unit, ticksPerWheel, leakDetection, -1); + } + + /** + * Creates a new timer. + * + * @param threadFactory a {@link ThreadFactory} that creates a + * background {@link Thread} which is dedicated to + * {@link TimerTask} execution. + * @param tickDuration the duration between tick + * @param unit the time unit of the {@code tickDuration} + * @param ticksPerWheel the size of the wheel + * @param leakDetection {@code true} if leak detection should be enabled always, + * if false it will only be enabled if the worker thread is not + * a daemon thread. + * @param maxPendingTimeouts The maximum number of pending timeouts after which call to + * {@code newTimeout} will result in + * {@link java.util.concurrent.RejectedExecutionException} + * being thrown. No maximum pending timeouts limit is assumed if + * this value is 0 or negative. + * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null} + * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0 + */ + public HashedWheelTimer( + ThreadFactory threadFactory, + long tickDuration, TimeUnit unit, int ticksPerWheel, boolean leakDetection, + long maxPendingTimeouts) { + + if (threadFactory == null) { + throw new NullPointerException("threadFactory"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + if (tickDuration <= 0) { + throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration); + } + if (ticksPerWheel <= 0) { + throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel); + } + + // Normalize ticksPerWheel to power of two and initialize the wheel. + wheel = createWheel(ticksPerWheel); + mask = wheel.length - 1; + + // Convert tickDuration to nanos. + long duration = unit.toNanos(tickDuration); + + // Prevent overflow. + if (duration >= Long.MAX_VALUE / wheel.length) { + throw new IllegalArgumentException(String.format( + "tickDuration: %d (expected: 0 < tickDuration in nanos < %d", + tickDuration, Long.MAX_VALUE / wheel.length)); + } + + if (duration < MILLISECOND_NANOS) { + if (logger.isWarnEnabled()) { + logger.warn("Configured tickDuration %d smaller then %d, using 1ms.", + tickDuration, MILLISECOND_NANOS); + } + this.tickDuration = MILLISECOND_NANOS; + } else { + this.tickDuration = duration; + } + + workerThread = threadFactory.newThread(worker); + + leak = leakDetection || !workerThread.isDaemon() ? leakDetector.track(this) : null; + + this.maxPendingTimeouts = maxPendingTimeouts; + + if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT && + WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) { + reportTooManyInstances(); + } + } + + @Override + protected void finalize() throws Throwable { + try { + super.finalize(); + } finally { + // This object is going to be GCed and it is assumed the ship has sailed to do a proper shutdown. If + // we have not yet shutdown then we want to make sure we decrement the active instance count. + if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) { + INSTANCE_COUNTER.decrementAndGet(); + } + } + } + + private static HashedWheelBucket[] createWheel(int ticksPerWheel) { + if (ticksPerWheel <= 0) { + throw new IllegalArgumentException( + "ticksPerWheel must be greater than 0: " + ticksPerWheel); + } + if (ticksPerWheel > 1073741824) { + throw new IllegalArgumentException( + "ticksPerWheel may not be greater than 2^30: " + ticksPerWheel); + } + + ticksPerWheel = normalizeTicksPerWheel(ticksPerWheel); + HashedWheelBucket[] wheel = new HashedWheelBucket[ticksPerWheel]; + for (int i = 0; i < wheel.length; i ++) { + wheel[i] = new HashedWheelBucket(); + } + return wheel; + } + + private static int normalizeTicksPerWheel(int ticksPerWheel) { + int normalizedTicksPerWheel = 1; + while (normalizedTicksPerWheel < ticksPerWheel) { + normalizedTicksPerWheel <<= 1; + } + return normalizedTicksPerWheel; + } + + /** + * Starts the background thread explicitly. The background thread will + * start automatically on demand even if you did not call this method. + * + * @throws IllegalStateException if this timer has been + * {@linkplain #stop() stopped} already + */ + public void start() { + switch (WORKER_STATE_UPDATER.get(this)) { + case WORKER_STATE_INIT: + if (WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_INIT, WORKER_STATE_STARTED)) { + workerThread.start(); + } + break; + case WORKER_STATE_STARTED: + break; + case WORKER_STATE_SHUTDOWN: + throw new IllegalStateException("cannot be started once stopped"); + default: + throw new Error("Invalid WorkerState"); + } + + // Wait until the startTime is initialized by the worker. + while (startTime == 0) { + try { + startTimeInitialized.await(); + } catch (InterruptedException ignore) { + // Ignore - it will be ready very soon. + } + } + } + + @Override + public Set stop() { + if (Thread.currentThread() == workerThread) { + throw new IllegalStateException( + HashedWheelTimer.class.getSimpleName() + + ".stop() cannot be called from " + + TimerTask.class.getSimpleName()); + } + + if (!WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_STARTED, WORKER_STATE_SHUTDOWN)) { + // workerState can be 0 or 2 at this moment - let it always be 2. + if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) { + INSTANCE_COUNTER.decrementAndGet(); + if (leak != null) { + boolean closed = leak.close(this); + assert closed; + } + } + + return Collections.emptySet(); + } + + try { + boolean interrupted = false; + while (workerThread.isAlive()) { + workerThread.interrupt(); + try { + workerThread.join(100); + } catch (InterruptedException ignored) { + interrupted = true; + } + } + + if (interrupted) { + Thread.currentThread().interrupt(); + } + } finally { + INSTANCE_COUNTER.decrementAndGet(); + if (leak != null) { + boolean closed = leak.close(this); + assert closed; + } + } + return worker.unprocessedTimeouts(); + } + + @Override + public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) { + if (task == null) { + throw new NullPointerException("task"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + + long pendingTimeoutsCount = pendingTimeouts.incrementAndGet(); + + if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) { + pendingTimeouts.decrementAndGet(); + throw new RejectedExecutionException("Number of pending timeouts (" + + pendingTimeoutsCount + ") is greater than or equal to maximum allowed pending " + + "timeouts (" + maxPendingTimeouts + ")"); + } + + start(); + + // Add the timeout to the timeout queue which will be processed on the next tick. + // During processing all the queued HashedWheelTimeouts will be added to the correct HashedWheelBucket. + long deadline = System.nanoTime() + unit.toNanos(delay) - startTime; + + // Guard against overflow. + if (delay > 0 && deadline < 0) { + deadline = Long.MAX_VALUE; + } + HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline); + timeouts.add(timeout); + return timeout; + } + + /** + * Returns the number of pending timeouts of this {@link Timer}. + */ + public long pendingTimeouts() { + return pendingTimeouts.get(); + } + + private static void reportTooManyInstances() { + if (logger.isErrorEnabled()) { + String resourceType = simpleClassName(HashedWheelTimer.class); + logger.error("You are creating too many " + resourceType + " instances. " + + resourceType + " is a shared resource that must be reused across the JVM," + + "so that only a few instances are created."); + } + } + + private final class Worker implements Runnable { + private final Set unprocessedTimeouts = new HashSet(); + + private long tick; + + @Override + public void run() { + // Initialize the startTime. + startTime = System.nanoTime(); + if (startTime == 0) { + // We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized. + startTime = 1; + } + + // Notify the other threads waiting for the initialization at start(). + startTimeInitialized.countDown(); + + do { + final long deadline = waitForNextTick(); + if (deadline > 0) { + int idx = (int) (tick & mask); + processCancelledTasks(); + HashedWheelBucket bucket = + wheel[idx]; + transferTimeoutsToBuckets(); + bucket.expireTimeouts(deadline); + tick++; + } + } while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED); + + // Fill the unprocessedTimeouts so we can return them from stop() method. + for (HashedWheelBucket bucket: wheel) { + bucket.clearTimeouts(unprocessedTimeouts); + } + for (;;) { + HashedWheelTimeout timeout = timeouts.poll(); + if (timeout == null) { + break; + } + if (!timeout.isCancelled()) { + unprocessedTimeouts.add(timeout); + } + } + processCancelledTasks(); + } + + private void transferTimeoutsToBuckets() { + // transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread when it just + // adds new timeouts in a loop. + for (int i = 0; i < 100000; i++) { + HashedWheelTimeout timeout = timeouts.poll(); + if (timeout == null) { + // all processed + break; + } + if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) { + // Was cancelled in the meantime. + continue; + } + + long calculated = timeout.deadline / tickDuration; + timeout.remainingRounds = (calculated - tick) / wheel.length; + + final long ticks = Math.max(calculated, tick); // Ensure we don't schedule for past. + int stopIndex = (int) (ticks & mask); + + HashedWheelBucket bucket = wheel[stopIndex]; + bucket.addTimeout(timeout); + } + } + + private void processCancelledTasks() { + for (;;) { + HashedWheelTimeout timeout = cancelledTimeouts.poll(); + if (timeout == null) { + // all processed + break; + } + try { + timeout.remove(); + } catch (Throwable t) { + if (logger.isWarnEnabled()) { + logger.warn("An exception was thrown while process a cancellation task", t); + } + } + } + } + + /** + * calculate goal nanoTime from startTime and current tick number, + * then wait until that goal has been reached. + * @return Long.MIN_VALUE if received a shutdown request, + * current time otherwise (with Long.MIN_VALUE changed by +1) + */ + private long waitForNextTick() { + long deadline = tickDuration * (tick + 1); + + for (;;) { + final long currentTime = System.nanoTime() - startTime; + long sleepTimeMs = (deadline - currentTime + 999999) / 1000000; + + if (sleepTimeMs <= 0) { + if (currentTime == Long.MIN_VALUE) { + return -Long.MAX_VALUE; + } else { + return currentTime; + } + } + + // Check if we run on windows, as if thats the case we will need + // to round the sleepTime as workaround for a bug that only affect + // the JVM if it runs on windows. + // + // See https://github.com/netty/netty/issues/356 + if (PlatformDependent.isWindows()) { + sleepTimeMs = sleepTimeMs / 10 * 10; + } + + try { + Thread.sleep(sleepTimeMs); + } catch (InterruptedException ignored) { + if (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) { + return Long.MIN_VALUE; + } + } + } + } + + public Set unprocessedTimeouts() { + return Collections.unmodifiableSet(unprocessedTimeouts); + } + } + + private static final class HashedWheelTimeout implements Timeout { + + private static final int ST_INIT = 0; + private static final int ST_CANCELLED = 1; + private static final int ST_EXPIRED = 2; + private static final AtomicIntegerFieldUpdater STATE_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimeout.class, "state"); + + private final HashedWheelTimer timer; + private final TimerTask task; + private final long deadline; + + @SuppressWarnings({"unused", "FieldMayBeFinal", "RedundantFieldInitialization" }) + private volatile int state = ST_INIT; + + // remainingRounds will be calculated and set by Worker.transferTimeoutsToBuckets() before the + // HashedWheelTimeout will be added to the correct HashedWheelBucket. + long remainingRounds; + + // This will be used to chain timeouts in HashedWheelTimerBucket via a double-linked-list. + // As only the workerThread will act on it there is no need for synchronization / volatile. + HashedWheelTimeout next; + HashedWheelTimeout prev; + + // The bucket to which the timeout was added + HashedWheelBucket bucket; + + HashedWheelTimeout(HashedWheelTimer timer, TimerTask task, long deadline) { + this.timer = timer; + this.task = task; + this.deadline = deadline; + } + + @Override + public Timer timer() { + return timer; + } + + @Override + public TimerTask task() { + return task; + } + + @Override + public boolean cancel() { + // only update the state it will be removed from HashedWheelBucket on next tick. + if (!compareAndSetState(ST_INIT, ST_CANCELLED)) { + return false; + } + // If a task should be canceled we put this to another queue which will be processed on each tick. + // So this means that we will have a GC latency of max. 1 tick duration which is good enough. This way + // we can make again use of our MpscLinkedQueue and so minimize the locking / overhead as much as possible. + timer.cancelledTimeouts.add(this); + return true; + } + + void remove() { + HashedWheelBucket bucket = this.bucket; + if (bucket != null) { + bucket.remove(this); + } else { + timer.pendingTimeouts.decrementAndGet(); + } + } + + public boolean compareAndSetState(int expected, int state) { + return STATE_UPDATER.compareAndSet(this, expected, state); + } + + public int state() { + return state; + } + + @Override + public boolean isCancelled() { + return state() == ST_CANCELLED; + } + + @Override + public boolean isExpired() { + return state() == ST_EXPIRED; + } + + public void expire() { + if (!compareAndSetState(ST_INIT, ST_EXPIRED)) { + return; + } + + try { + task.run(this); + } catch (Throwable t) { + if (logger.isWarnEnabled()) { + logger.warn("An exception was thrown by " + TimerTask.class.getSimpleName() + '.', t); + } + } + } + + @Override + public String toString() { + final long currentTime = System.nanoTime(); + long remaining = deadline - currentTime + timer.startTime; + + StringBuilder buf = new StringBuilder(192) + .append(simpleClassName(this)) + .append('(') + .append("deadline: "); + if (remaining > 0) { + buf.append(remaining) + .append(" ns later"); + } else if (remaining < 0) { + buf.append(-remaining) + .append(" ns ago"); + } else { + buf.append("now"); + } + + if (isCancelled()) { + buf.append(", cancelled"); + } + + return buf.append(", task: ") + .append(task()) + .append(')') + .toString(); + } + } + + /** + * Bucket that stores HashedWheelTimeouts. These are stored in a linked-list like datastructure to allow easy + * removal of HashedWheelTimeouts in the middle. Also the HashedWheelTimeout act as nodes themself and so no + * extra object creation is needed. + */ + private static final class HashedWheelBucket { + // Used for the linked-list datastructure + private HashedWheelTimeout head; + private HashedWheelTimeout tail; + + /** + * Add {@link HashedWheelTimeout} to this bucket. + */ + public void addTimeout(HashedWheelTimeout timeout) { + assert timeout.bucket == null; + timeout.bucket = this; + if (head == null) { + head = tail = timeout; + } else { + tail.next = timeout; + timeout.prev = tail; + tail = timeout; + } + } + + /** + * Expire all {@link HashedWheelTimeout}s for the given {@code deadline}. + */ + public void expireTimeouts(long deadline) { + HashedWheelTimeout timeout = head; + + // process all timeouts + while (timeout != null) { + HashedWheelTimeout next = timeout.next; + if (timeout.remainingRounds <= 0) { + next = remove(timeout); + if (timeout.deadline <= deadline) { + timeout.expire(); + } else { + // The timeout was placed into a wrong slot. This should never happen. + throw new IllegalStateException(String.format( + "timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline)); + } + } else if (timeout.isCancelled()) { + next = remove(timeout); + } else { + timeout.remainingRounds --; + } + timeout = next; + } + } + + public HashedWheelTimeout remove(HashedWheelTimeout timeout) { + HashedWheelTimeout next = timeout.next; + // remove timeout that was either processed or cancelled by updating the linked-list + if (timeout.prev != null) { + timeout.prev.next = next; + } + if (timeout.next != null) { + timeout.next.prev = timeout.prev; + } + + if (timeout == head) { + // if timeout is also the tail we need to adjust the entry too + if (timeout == tail) { + tail = null; + head = null; + } else { + head = next; + } + } else if (timeout == tail) { + // if the timeout is the tail modify the tail to be the prev node. + tail = timeout.prev; + } + // null out prev, next and bucket to allow for GC. + timeout.prev = null; + timeout.next = null; + timeout.bucket = null; + timeout.timer.pendingTimeouts.decrementAndGet(); + return next; + } + + /** + * Clear this bucket and return all not expired / cancelled {@link Timeout}s. + */ + public void clearTimeouts(Set set) { + for (;;) { + HashedWheelTimeout timeout = pollTimeout(); + if (timeout == null) { + return; + } + if (timeout.isExpired() || timeout.isCancelled()) { + continue; + } + set.add(timeout); + } + } + + private HashedWheelTimeout pollTimeout() { + HashedWheelTimeout head = this.head; + if (head == null) { + return null; + } + HashedWheelTimeout next = head.next; + if (next == null) { + tail = this.head = null; + } else { + this.head = next; + next.prev = null; + } + + // null out prev and next to allow for GC. + head.next = null; + head.prev = null; + head.bucket = null; + return head; + } + } +} diff --git a/Netty/netty-boostrap.java b/Netty/netty-boostrap.java new file mode 100644 index 00000000..eebd4ed7 --- /dev/null +++ b/Netty/netty-boostrap.java @@ -0,0 +1,46 @@ +---------------------------- +Netty服务端的配置 | +---------------------------- + # 服务端的一般配置代码 + // 处理连接的线程池 + EventLoopGroup bossGroup = new NioEventLoopGroup(); + // 处理IO事件的线程池 + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + // 创建服务端对象 + ServerBootstrap serverBootstrap = new ServerBootstrap(); + // 设置线程池 + serverBootstrap.group(bossGroup, workerGroup); + // 设置io模式 + serverBootstrap.channel(NioServerSocketChannel.class); + // 监听的网卡和端口 + serverBootstrap.localAddress(new InetSocketAddress("0.0.0.0", 1024)); + // 设置日志handler + serverBootstrap.handler(new LoggingHandler(LogLevel.INFO)); + // 设置客户端handlder + serverBootstrap.childHandler(new ChannelInitializer() { + // 初始化信息设置 + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast(new TimeServerHandler()); + } + }); + // 设置服务端属性 + serverBootstrap.option(ChannelOption.SO_BACKLOG, 128); + serverBootstrap.option(ChannelOption.SO_REUSEADDR, true); + + // 设置客户端属性 + serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); + + // 绑定到设置的端口和网卡,并且同步(阻塞)的启动服务 + ChannelFuture channelFuture = serverBootstrap.bind().sync(); + channelFuture.channel().closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + bossGroup.shutdownGracefully(); + } + +---------------------------- +ChannelInitializer | +---------------------------- + # ChannelInitializer 自身也是一个ChannelHandler,在添加完其他的 handlers 之后会自动从 ChannelPipeline 中删除自己 diff --git a/Netty/netty-buffer-ByteBufAllocator.java b/Netty/netty-buffer-ByteBufAllocator.java new file mode 100644 index 00000000..0e5edd0e --- /dev/null +++ b/Netty/netty-buffer-ByteBufAllocator.java @@ -0,0 +1,43 @@ +-------------------------------- +ByteBufAllocator | +-------------------------------- + # 内存分配器 + # 类库 + ByteBufAllocator + AbstractByteBufAllocator + PooledByteBufAllocator(池化) + UnpooledByteBufAllocator(非池化) + + # 抽象方法 + ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR; + + ByteBuf buffer(); + ByteBuf buffer(int initialCapacity); + ByteBuf buffer(int initialCapacity, int maxCapacity); + + ByteBuf ioBuffer(); + ByteBuf ioBuffer(int initialCapacity); + ByteBuf ioBuffer(int initialCapacity, int maxCapacity); + * 期望返回一个直接内存buf(适合io的buf) + + ByteBuf heapBuffer(); + ByteBuf heapBuffer(int initialCapacity); + ByteBuf heapBuffer(int initialCapacity, int maxCapacity); + * 返回一个堆内存的buf + + ByteBuf directBuffer(); + ByteBuf directBuffer(int initialCapacity); + ByteBuf directBuffer(int initialCapacity, int maxCapacity); + * 返回一个直接内存buf + + CompositeByteBuf compositeBuffer(); + CompositeByteBuf compositeBuffer(int maxNumComponents); + + CompositeByteBuf compositeHeapBuffer(); + CompositeByteBuf compositeHeapBuffer(int maxNumComponents); + + CompositeByteBuf compositeDirectBuffer(); + CompositeByteBuf compositeDirectBuffer(int maxNumComponents); + + boolean isDirectBufferPooled(); + int calculateNewCapacity(int minNewCapacity, int maxCapacity); diff --git a/Netty/netty-buffer-ByteBufHolder.java b/Netty/netty-buffer-ByteBufHolder.java new file mode 100644 index 00000000..09928a31 --- /dev/null +++ b/Netty/netty-buffer-ByteBufHolder.java @@ -0,0 +1,25 @@ +---------------------------- +ByteBufHolder | +---------------------------- + # 继承了 ReferenceCounted + # 抽象方法 + ByteBuf content(); + + ByteBufHolder copy(); + + ByteBufHolder duplicate(); + + ByteBufHolder retainedDuplicate(); + + @Override + ByteBufHolder retain(); + + @Override + ByteBufHolder retain(int increment); + + @Override + ByteBufHolder touch(); + + @Override + ByteBufHolder touch(Object hint); + \ No newline at end of file diff --git a/Netty/netty-buffer-ByteBufUtil.java b/Netty/netty-buffer-ByteBufUtil.java new file mode 100644 index 00000000..8011a3e4 --- /dev/null +++ b/Netty/netty-buffer-ByteBufUtil.java @@ -0,0 +1,16 @@ +--------------------------------- +ByteBufUtil | +--------------------------------- + # 提供了很多的静态API可以操作buf + + + String hexDump(ByteBuf buffer) + * 返回buffer的16进制字符串,会根据rindex去读取 + + String hexDump(byte[] array) + * 返回 byte[] 的16进制字符串 + + byte[] decodeHexDump(CharSequence hexDump) + * 把16进制字符串转换为字节数组 + + \ No newline at end of file diff --git a/Netty/netty-buffer-CompositeByteBuf.java b/Netty/netty-buffer-CompositeByteBuf.java new file mode 100644 index 00000000..1edec0b5 --- /dev/null +++ b/Netty/netty-buffer-CompositeByteBuf.java @@ -0,0 +1,54 @@ +------------------------------ +CompositeByteBuf | +------------------------------ + # 复合Buffer,它本身也是实现了ByteBuf接口,而且它不会有内存泄漏的问题 + * 其实就是把多个Buffer柔和成一个Buffer + + # CompositeByteBuf.hasArray()总是返回 false,因为它可能包含一些直接或间接的不同类型的 ByteBuf + + # 构造函数 + CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents) + CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteBuf... buffers) + CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, Iterable buffers) + + # 方法 + CompositeByteBuf addComponent(boolean increaseWriterIndex, int cIndex, ByteBuf buffer) + CompositeByteBuf addComponent(boolean increaseWriterIndex, ByteBuf buffer) + CompositeByteBuf addComponent(int cIndex, ByteBuf buffer) + CompositeByteBuf addComponent(ByteBuf buffer) + CompositeByteBuf addComponents(boolean increaseWriterIndex, ByteBuf... buffers) + CompositeByteBuf addComponents(boolean increaseWriterIndex, Iterable buffers) + CompositeByteBuf addComponents(int cIndex, ByteBuf... buffers) + CompositeByteBuf addComponents(int cIndex, Iterable buffers) + CompositeByteBuf addComponents(ByteBuf... buffers) + CompositeByteBuf addComponents(Iterable buffers) + + + + # 简单的使用 + // 创建复合缓冲区 + CompositeByteBuf compBuf = Unpooled.compositeBuffer(); + + // 创建俩buffer + ByteBuf heapBuf = Unpooled.buffer(8); + ByteBuf directBuf = Unpooled.directBuffer(16); + + //添加 ByteBuf 到 CompositeByteBuf + compBuf.addComponents(heapBuf,directBuf); + + //删除第一个 ByteBuf + compBuf.removeComponent(0); + + // 可以迭代复合缓冲区中的每一个缓冲区 + Iterator iter = compBuf.iterator(); + while(iter.hasNext()){ + System.out.println(iter.next().toString()); + } + + //使用数组访问数据 + if(!compBuf.hasArray()){ + int len = compBuf.readableBytes(); + byte[] arr = new byte[len]; + compBuf.getBytes(0, arr); + } + diff --git a/Netty/netty-buffer-Unpooled.java b/Netty/netty-buffer-Unpooled.java new file mode 100644 index 00000000..f843d0b5 --- /dev/null +++ b/Netty/netty-buffer-Unpooled.java @@ -0,0 +1,13 @@ +---------------------------------- +Unpooled | +---------------------------------- + # 非池化的ByteBuf工厂类 + + ByteBuf copiedBuffer(CharSequence string, Charset charset) + * 把指定的string编码为ByteBuff + * 会开辟字符串大小3倍长度的一个bufer + + CompositeByteBuf compositeBuffer() + * 返回一个复合缓冲区 + + \ No newline at end of file diff --git a/Netty/netty-buffer.java b/Netty/netty-buffer.java new file mode 100644 index 00000000..d04037b6 --- /dev/null +++ b/Netty/netty-buffer.java @@ -0,0 +1,100 @@ +-------------------- +Buffer | +-------------------- + # 涉及的类库 + ByteBuf + |-AbstractByteBuf + |-CompositeByteBuf + |-UnpooledHeapByteBuf + |-UnpooledDirectByteBuf + ByteBufHolder + ByteBufProcessor + ByteBufAllocator + Unpooled + ByteBufUtil + + # Netty 缓冲 API 提供了几个优势 + * 可以自定义缓冲类型 + * 通过一个内置的复合缓冲类型实现零拷贝 + * 扩展性好,类似 StringBuilder(可以自动的扩容) + ByteBuf byteBuf = Unpooled.buffer(5); // 初始5个长度 + for(int x = 0 ;x < 10 ;x ++) { // 强行写入10个数据 + byteBuf.writeByte(x); + } + //UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 10, cap: 64) 自动扩容到64 + System.out.println(byteBuf); + * 读取和写入索引分开,不需要调用 flip() 来切换读/写模式 + * 方法链 + * 引用计数 + * Pooling(池) + + # Netty 使用 reference-counting(引用计数)的时候知道安全释放 Buf 和其他资源 + + # ByteBuf 的默认最大容量限制是 Integer.MAX_VALUE + + # Buffer的相关的库 + ByteBufAllocator + |-PooledByteBufAllocator + |-UnpooledByteBufAllocator + Unpooled + CompositeByteBuf + ByteBufUtil + ByteBufHolder + + # ByteBuf的类型 + ———————————————————————————————————————— + 池化的 非池化的 + 直接缓冲 + 堆缓冲 + 复合缓冲 + ———————————————————————————————————————— + heap buffer + * 存储在jvm堆,可以快速的创建与销毁,并且可以直接访问内部的数组 + * 每次的数据io,都有一个拷贝数据的过程(把堆中数据复制到直接缓冲区) + + direct buffer + * 在堆外直接分配空间,它不会占用堆的空间 + * 在socket进行io时性能比较好,因为省略了复制这一个步骤 + * 它不能直接访问内部的数组 + * 它的内存分配与释放比堆会比较复杂且速度会慢一些(可以通过内存池来解决这个问题) + + # 不同ByeBuf的使用场景 + * 业务消息的编解码采用HeapByteBuf + * IO通信线程在IO缓冲区时,使用DirectByteBuf(0拷贝) + +-------------------- +Buffer | +-------------------- + capacity 总大小 + readindex 读角标 + wirteindex 写角标 + +-------------------- +Heap Buffer(堆缓冲区)| +-------------------- + # 最常用的类型是 ByteBuf 将数据存储在 JVM 的堆空间 + * 提供了直接访问数组的方法,通过 ByteBuf.array()来获取 byte[]数据 + + # 访问非堆缓冲区 ByteBuf 的数组会导致 UnsupportedOperationException + * 可以使用 ByteBuf.hasArray()来检查是否支持访问数组 + +------------------------ +Direct Buffer(直接缓冲区)| +------------------------ + # 在堆之外直接分配内存,直接缓冲区不会占用堆空间容量 + # 直接缓冲区的缺点是在分配内存空间和释放内存时比堆缓冲区更复杂,而 Netty 使用内存池来解决这样的问题,这也是 Netty 使用内存池的原因之一 + # 直接缓冲区不支持数组访问数据,但是可以间接的访问数据数组 + ByteBuf directBuf = Unpooled.directBuffer(16); + // 直接缓冲区 + if(!directBuf.hasArray()){ + // 可读的数据长度 + int len = directBuf.readableBytes(); + // 创建相同长度的数组 + byte[] arr = new byte[len]; + // 把缓冲区的数据读取到数组 + directBuf.getBytes(0, arr); + } + + + + diff --git a/Netty/netty-channel-attr.java b/Netty/netty-channel-attr.java new file mode 100644 index 00000000..301b54cf --- /dev/null +++ b/Netty/netty-channel-attr.java @@ -0,0 +1,65 @@ +------------------------- +attr | +------------------------- + # 类库 + Constant + |-AbstractConstant + |-AttributeKey + ConstantPool + AttributeMap + |-DefaultAttributeMap + |-AbstractChannel + |-AbstractChannelHandlerContext + |-Channel + |-ChannelHandlerContext + + Attribute + + # ConstantPool + * 维护了一个 ConcurrentHashMap + private final ConcurrentMap constants = PlatformDependent.newConcurrentHashMap(); + + + # AttributeMap + * 一个抽象的接口, Channel 实现了 + Attribute attr(AttributeKey key); + boolean hasAttr(AttributeKey key); + + # Attribute + * 表示Value的接口 + AttributeKey key(); + T get(); + void set(T value); + T getAndSet(T value); + T setIfAbsent(T value); + boolean compareAndSet(T oldValue, T newValue); + + # ChannelHandlerContext 和 Channel 的 attr 没有区别 + Channel.attr() == ChannelHandlerContext.attr() + + * ChannelHandlerContext.attr(),已经废弃,建议使用 Channel 的 attr + + + +------------------------ +AttributeKey | +------------------------ + # 用于创建一个key对象,泛型表示该key对应的Value类型 + # AttributeKey是基于ConstantPool进行缓存的 + + public static AttributeKey valueOf(String name) + * 采用类乐观锁的方式,当 constant != null时,那么返回已经插入的 constant + * 如果 name 不存在就创建一个 + * 且多线程随先创建返回谁 + + public static AttributeKey newInstance(String name) + * 采用类乐观锁的方式,当 constant != null时,直接抛出异常 + * 如果name存在就抛出异常 + * 多线程创建,除了成功创建的那个线程外,其他线程抛出异常 + + public static boolean exists(String name) + + public static AttributeKey valueOf(Class firstNameComponent, String secondNameComponent) + * 底层还是调用 valueOf(String name), + * 在 firstNameComponent 和 secondNameComponent 之间添加了 '#' 字符串(命名空间的感觉) + diff --git a/Netty/netty-channel-config.java b/Netty/netty-channel-config.java new file mode 100644 index 00000000..ad80d4a4 --- /dev/null +++ b/Netty/netty-channel-config.java @@ -0,0 +1,76 @@ +-------------------------------- +ChannelConfig | +-------------------------------- + # 对于Channel的一些配置 + # 类库 + ChannelConfig + |-SocketChannelConfig + |-ServerSocketChannelConfig + + # 从Channel获取到Config对象 + ChannelHandlerContext ctx ... + ChannelConfig channelConfig = ctx.channel().config(); + + ChannelFuture channelFuture = ... + ChannelConfig channelConfig = channelFuture.channel().config(); + + + # 方法 + Map, Object> getOptions(); + + boolean setOptions(Map, ?> options); + + T getOption(ChannelOption option); + + boolean setOption(ChannelOption option, T value); + + int getConnectTimeoutMillis(); + + ChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis); + + @Deprecated + int getMaxMessagesPerRead(); + + @Deprecated + ChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead); + + int getWriteSpinCount(); + + ChannelConfig setWriteSpinCount(int writeSpinCount); + + ByteBufAllocator getAllocator(); + + ChannelConfig setAllocator(ByteBufAllocator allocator); + + T getRecvByteBufAllocator(); + + ChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator); + + boolean isAutoRead(); + + ChannelConfig setAutoRead(boolean autoRead); + + boolean isAutoClose(); + + ChannelConfig setAutoClose(boolean autoClose); + + int getWriteBufferHighWaterMark(); + + ChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark); + * 设置高水位 + * 当发送队列待发送的字节数组达到高水位上限时,对应的 Channel 就变为不可写状态 + * 由于高水位并不会阻止业务线程调用 write() 方法并把消息加入到待发送队列中 + * 因此,必须要在消息发送时对 Channel 的状态进行判断(当到达高水位时,Channel 的状态被设置为不可写,通过对 Channel 的可写状态进行判断来决定是否发送消息) + isWritable(); + + int getWriteBufferLowWaterMark(); + + ChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark); + + MessageSizeEstimator getMessageSizeEstimator(); + + ChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator); + + WriteBufferWaterMark getWriteBufferWaterMark(); + + ChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark); \ No newline at end of file diff --git a/Netty/netty-channel-context.java b/Netty/netty-channel-context.java new file mode 100644 index 00000000..0ee96f59 --- /dev/null +++ b/Netty/netty-channel-context.java @@ -0,0 +1,18 @@ +------------------------------------ +ChannelHandlerContext | +------------------------------------ + # 当前连接的上下文 + * 它可以主动的把消息往下传递,触发下一个handler的事件 + + # 两种响应方式 + * 从 ChannelHandlerContext 获取原始 Channel 执行 write + 处理消息从 ChannelPipeline 的尾部(最右边)开始,也是从头开始进入整个输出执行链 + + * 直接调执行 ChannelHandlerContext 的 wirte + 写入 ChannelHandlerContext 对象的消息会从 ChannelPipeline 的下一个(左边)ChannelOutboundHandler开始处理,而不是从头开始 + + + + + + \ No newline at end of file diff --git a/Netty/netty-channel-handler.java b/Netty/netty-channel-handler.java new file mode 100644 index 00000000..cc2619d2 --- /dev/null +++ b/Netty/netty-channel-handler.java @@ -0,0 +1,208 @@ +----------------------------- +ChannelHandler | +----------------------------- + # 提供基本的Handler事件 + # 类库 + ChannelHandler + |-ChannelHandlerAdapter(抽象) + |-ChannelInboundHandler(读接口) + |-ChannelOutboundHandler(写接口) + |-ChannelDuplexHandler(读写都实现) + + # Handler的生命周期(基本的事件) + handlerAdded ChannelHandler 添加到 ChannelPipeline + handlerRemoved ChannelHandler 从 ChannelPipeline 移除 + exceptionCaught ChannelPipeline 执行抛出异常 + + # 一些其他的类库 + SimpleUserEventChannelHandler + * 类似于SimpleChannelInboundHandler + * 该Handler用于处理指定类型(泛型), 的用户事件handler + public class StringEventHandler extends SimpleUserEventChannelHandler { + @Override + protected void eventReceived(ChannelHandlerContext ctx, String evt) throws Exception { + System.out.println(evt); + } + } + +----------------------------- +ChannelInboundHandler | +----------------------------- + # 读取Handler + # 类库 + |-ChannelInboundHandler + |-ChannelInboundHandlerAdapter + * 它实现了 ChannelInboundHandler 的所有方法 + * 默认的作用就是处理消息(事件)并将消息转发到 ChannelPipeline 中的下一个 ChannelHandler + + |-ByteToMessageDecoder + |-ReplayingDecoder + |-LineBasedFrameDecoder + |-LengthFieldBasedFrameDecoder + |-DelimiterBasedFrameDecoder + |-FixedLengthFrameDecoder + |-SslHandler + |-MessageToMessageDecoder + |-StringDecoder + |-ChannelInitializer + |-SimpleChannelInboundHandler + + # 事件(接口方法) + channelRegistered channel 注册到一个 EventLoop,该状态可以出现多次,可以重复的注册取消注册 + channelActive channel 变为活跃状态(连接到了远程主机),现在可以接收和发送数据了,该状态只会出现一次 + channelInactive channel 处于非活跃状态(连接已经关闭),没有连接到远程主机,该状态只会出现一次 + channelUnregistered channel 已创建但未注册到一个 EventLoop (或者从EventLoop中移除),该状态可以出现多次,可以重复的注册取消注册 + + channelReadComplete channel 读取完成 + channelRead channel 可以读取 + userEventTriggered channel 用户自定义事件 + channelWritabilityChanged channel 可写状态改变,可以使用 Channel.isWritable()检查 + exceptionCaught channel 异常事件 + + # SimpleChannelInboundHandler + * 抽象类,需要覆写抽象方法: channelRead0(ChannelHandlerContext ctx, T msg) + * 自动强制转换类型,并且可以自动的释放buf资源 + ReferenceCountUtil.release(msg); + + # 一般使用的和场景 + ChannelInboundHandlerAdapter 处理其事件或者状态改变 + SimpleChannelInboundHandler 处理消息 + +----------------------------- +SimpleChannelInboundHandler | +----------------------------- + # 继承自:ChannelInboundHandlerAdapter 的泛型抽象类 + # 构造函数 + SimpleChannelInboundHandler() + SimpleChannelInboundHandler(boolean autoRelease) + SimpleChannelInboundHandler(Class inboundMessageType) + + * autoRelease 是否自动释放资源,默认 true + * inboundMessageType 该处理器会处理的消息类型(必须是泛型或者其子类) + if (inboundMessageType.isInstance(msg)){ + // 处理 + } + + # 提供的可覆写的方法 + abstract void channelRead0(ChannelHandlerContext ctx, I msg) + * 唯一的抽象类,必须覆写 + + void channelRead(ChannelHandlerContext ctx, Object msg) + * 处理读取事件 + * 源码 + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + boolean release = true; + try { + if (acceptInboundMessage(msg)) { + @SuppressWarnings("unchecked") + I imsg = (I) msg; + channelRead0(ctx, imsg); + } else { + release = false; + ctx.fireChannelRead(msg); + } + } finally { + if (autoRelease && release) { + ReferenceCountUtil.release(msg); // 自动释放资源 + } + } + } + + boolean acceptInboundMessage(Object msg) + * 判断当前Handler是否可以处理该消息对象 + * 执行 channelRead()的时候,会调用该方法,该方法默认调用了 TypeParameterMatcher 实例的 match(); 方法 + * 如果返回 true,就进行强制类型转换,并且触抽象方法 channelRead0 + * 如果返回 false,就会触发下一个Handler的 channelRead 事件 + + + # TypeParameterMatcher + * 类型匹配器 + * 在SimpleChannelInboundHandler内部维护的一个对象(抽象类) + * 很多带泛型的编解码器(内部有维护)都是靠它来进行判断是否要进行解码/编码的 + * 提供了静态的工厂方法 + static TypeParameterMatcher find(final Object object, final Class parametrizedSuperclass, final String typeParamName); + static TypeParameterMatcher get(final Class parameterType) + + * 唯一的抽象方法 + abstract boolean match(Object msg); + + + +----------------------------- +ChannelOutboundHandler | +----------------------------- + # 写入Handler + # 类库 + |-ChannelOutboundHandler + |-ChannelOutboundHandlerAdapter + |-MessageToByteEncoder + |-MessageToMessageEncoder + |-LengthFieldPrepender + |-StringEncoder + +----------------------------- +ChannelDuplexHandler | +----------------------------- + # 读写Handler + # 它继承 ChannelInboundHandlerAdapter 实现 ChannelOutboundHandler + +----------------------------- +ChannelPromise 机制 | +----------------------------- + # ChannelPromise + * 它继承自ChannelFuture + + # 运行 + * 特殊的 ChannelFuture,允许你的 ChannelPromise 及其 操作 成功或失败 + * 所以任何时候调用例如 Channel.write(...) 一个新的 ChannelPromise 将会创建并且通过 ChannelPipeline 传递 + * 这次写操作本身将会返回 ChannelFuture, 这样只允许你得到一次操作完成的通知 + * Netty 本身使用 ChannelPromise 作为返回的 ChannelFuture 的通知,事实上在大多数时候就是 ChannelPromise 自身 + * ChannelPromise 扩展了 ChannelFuture + * 如前所述,ChannelOutboundHandlerAdapter 提供了一个实现了 ChannelOutboundHandler 所有基本方法的实现的框架。 + * 这些简单事件转发到下一个 ChannelOutboundHandler 管道通过调用 ChannelHandlerContext 相关的等效方法,可以根据需要自己实现想要的方法 + + +----------------------------- +共享的Handler | +----------------------------- + # ChannelHandler 实例如果带有 @Sharable 注解则可以被添加到多个ChannelPipeline + * 也就是说单个 ChannelHandler 实例可以有多个 ChannelHandlerContext + * 因此可以调用不同 ChannelHandlerContext 获取同一个 ChannelHandler + * 如果添加不带 @Sharable 注解的 ChannelHandler 实例到多个 ChannelPipeline 则会抛出异常 + * 使用 @Sharable 注解后的 ChannelHandler 必须在不同的线程和不同的通道上安全使用 + + # 使用 @Sharable 注解共享一个 ChannelHandler 在一些需求中还是有很好的作用的 + * 如使用一个 ChannelHandler 来统计连接数或来处理一些全局数据等等 + +----------------------------- +事件总结 | +----------------------------- + void handlerAdded(ChannelHandlerContext ctx) throws Exception; + void handlerRemoved(ChannelHandlerContext ctx) throws Exception; + void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; + + void channelRegistered(ChannelHandlerContext ctx) throws Exception; + void channelUnregistered(ChannelHandlerContext ctx) throws Exception; + + void channelActive(ChannelHandlerContext ctx) throws Exception; + void channelInactive(ChannelHandlerContext ctx) throws Exception; + + void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception; + void channelReadComplete(ChannelHandlerContext ctx) throws Exception; + void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception; + void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception; + +----------------------------- +异常的传播与最佳实践 | +----------------------------- + # 异常的传播 + * 异常发生后,会先触发当前handler exceptionCaught() 方法 + * 如果当前节点,主动触发给下一个节点处理 + * 会根据 pipeline 的添加顺序(跟Handler类型无关)触发下一个 handler 的 exceptionCaught() 方法 + + # 最佳的异常处理实践 + * 在 pipeline 的最后,添加一个终极的 ExceptionHandler + * 继承:ChannelInboundHandlerAdapter/ChannelHandlerAdapter,覆写 exceptionCaught() 方法来处理全局的异常 + * 根据不同的异常类型来做处理 + diff --git a/Netty/netty-channel-initializer.java b/Netty/netty-channel-initializer.java new file mode 100644 index 00000000..658b41d2 --- /dev/null +++ b/Netty/netty-channel-initializer.java @@ -0,0 +1,15 @@ +---------------------------- +ChannelInitializer | +---------------------------- + # ChannelInitializer extends ChannelInboundHandlerAdapter + # 抽象类,它是一个特殊的 ChannelInboundHandler + # 提供的抽象方法 + + protected abstract void initChannel(C ch) throws Exception; + + * 在连接建立的时候,通过调用这个抽象方法来设置各种handler(i/o) + * 在完成了初始化后(initChannel 方法),它会把自己从 pipeline 里面移除 + + # 它被标记为:@Sharable,可以被多个 ServerBoostrap 使用 + * 所以,它的子类实现,必须要注意线程安全的问题 + \ No newline at end of file diff --git a/Netty/netty-channel-options.java b/Netty/netty-channel-options.java new file mode 100644 index 00000000..e4d7994a --- /dev/null +++ b/Netty/netty-channel-options.java @@ -0,0 +1,84 @@ +--------------------------------- +ChannelOption | +--------------------------------- + # ChannelOption 提供的标准配置项 + # ChannelConfig 对象也提供api,可以对这些属性进行设置 + +--------------------------------- +ALLOCATOR | +--------------------------------- + * 设置Channe的buffer分配器,客户端,服务端都能设置的属性 + ByteBufAllocator byteBufAllocator = ctx.alloc(); + + * 它的参数是接口实现:ByteBufAllocator + * 系统预定义了一些默认的实现 + serverBootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + serverBootstrap.childOption(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT); + +--------------------------------- +RCVBUF_ALLOCATOR | +--------------------------------- + * 接收缓冲区的内存分配器,客户端,服务端都能设置的属性 + * 它的参数是接口的实现:RecvByteBufAllocator + * 已有的实现类 + DefaultMaxBytesRecvByteBufAllocator + DefaultMaxMessagesRecvByteBufAllocator + |-AdaptiveRecvByteBufAllocator(默认) + * 容量动态调整的接收缓冲区分配器,它会根据之前Channel接收到的数据报大小进行计算,如果连续填充满接收缓冲区的可写空间,则动态扩展容量 + * 如果连续2次接收到的数据报都小于指定值,则收缩当前的容量,以节约内存 + * 构造方法 + AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) + + minimum + * 最小化预期缓冲区大小的包含下限(默认 64) + initial + * 在未收到反馈时初始化初始缓冲区大小(默认 1024) + maximum + * 最大化预期缓冲区大小的包含上限(默认 65536) + + + |-FixedRecvByteBufAllocator + * 固定长度的接收缓冲区分配器,由它分配的ByteBuf长度都是固定大小的,并不会根据实际数据报的大小动态收缩 + * 如果容量不足,支持动态扩展,动态扩展是Netty ByteBuf的一项基本功能,与ByteBuf分配器的实现没有关系 + + + MESSAGE_SIZE_ESTIMATOR + CONNECT_TIMEOUT_MILLIS + MAX_MESSAGES_PER_READ + + WRITE_SPIN_COUNT + WRITE_BUFFER_HIGH_WATER_MARK + WRITE_BUFFER_LOW_WATER_MARK + WRITE_BUFFER_WATER_MARK + ALLOW_HALF_CLOSURE + + AUTO_READ + AUTO_CLOSE + + SO_BROADCAST + SO_KEEPALIVE + * 是否启用心跳保活机制 + * 在双方TCP套接字建立连接后(即都进入ESTABLISHED状态)并且在两个小时左右上层没有任何数据传输的情况下,这套机制才会被激活 + + SO_SNDBUF + SO_RCVBUF + SO_REUSEADDR + SO_LINGER + SO_BACKLOG + * 用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时 + * 用于临时存放已完成三次握手的请求的队列的最大长度 + * 如果未设置或所设置的值小于1,Java将使用默认值50 + + SO_TIMEOUT + + IP_TOS + IP_MULTICAST_ADDR + IP_MULTICAST_IF + IP_MULTICAST_TTL + IP_MULTICAST_LOOP_DISABLED + TCP_NODELAY + + DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION + SINGLE_EVENTEXECUTOR_PER_GROUP + + \ No newline at end of file diff --git a/Netty/netty-channel-pipeline.java b/Netty/netty-channel-pipeline.java new file mode 100644 index 00000000..3eaff116 --- /dev/null +++ b/Netty/netty-channel-pipeline.java @@ -0,0 +1,42 @@ +---------------------------- +ChannelPipeline | +--------------------------- + # 继承:ChannelInboundInvoker ChannelOutboundInvoker + # 一个 ChannelPipeline 是用来保存关联到一个 Channel 的ChannelHandler + * 可以修改 ChannelPipeline 通过动态添加和删除 ChannelHandler + * ChannelPipeline 有着丰富的API调用动作来回应入站和出站事件 + + # 内部维护了一个双向链表 + * InBound事件的传递: ---> + * OutBound事件的传递: <--- + * Exception事件的传递: ---> + + # 它也可以给客户端响应数据 + ChannelPipeline pipeline = ctx.pipeline(); + pipeline.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8)); + + * 该api处理消息从 ChannelPipeline 的尾部(右边)开始,也是从头开始进入整个输出执行链 + + # 它也可以主动的触发事件 + ChannelPipeline pipeline = ctx.pipeline(); + pipeline.fireXxxxx(); + + * 从头部(左边)开始执行触发 ChannelInboundHandler 的事件 + * 它继承了接口: + ChannelInboundInvoker 定义了触发入站事件的fireXxxx 方法 + ChannelOutboundInvoker 定义了触发出站事件的fireXxxx 方法 + + + + # 部分添加handler的api + ChannelPipeline addFirst(String name, ChannelHandler handler); + * 添加handler到链表,并且设置名称 + + ChannelPipeline addLast(ChannelHandler... handlers); + * 添加handler到链表 + * 名称默认: 类名#编号(从0开始) + ServerMessageHandler#0 + + ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers); + * 添加handler到链表,并且设置执行这些handler事件方法的线程池 + diff --git a/Netty/netty-channel-pool.java b/Netty/netty-channel-pool.java new file mode 100644 index 00000000..d98923db --- /dev/null +++ b/Netty/netty-channel-pool.java @@ -0,0 +1,81 @@ +--------------------- +pool | +--------------------- + # 给客户端的的东西,给客户端的的东西,给客户端的的东西 + # 目的是解决, 与多个服务端交互以及与单个服务端建立连接池的问题 + + # 相关类库 + ChannelPool + |-AbstractChannelPoolHandler + |-SimpleChannelPool + |-FixedChannelPool + ChannelPoolHandler + + ChannelHealthChecker + + ChannelPoolMap + |-AbstractChannelPoolMap + + # 类库 + ChannelPool + * 连接池接口 + + SimpleChannelPool + * 实现ChannelPool接口, 简单的连接池实现 + + FixedChannelPool + * 继承SimpleChannelPool, 有大小限制的连接池实现 + + ChannelPoolMap + * 管理host与连接池映射的接口 + + AbstractChannelPoolMap + * 抽象类,实现ChannelPoolMap接口 + +---------------------- +ChannelPool | +---------------------- + # 连接池的抽象接口, 实现了 Closeable 接口 + # 抽象方法 + Future acquire(); + + Future acquire(Promise promise); + + Future release(Channel channel); + + Future release(Channel channel, Promise promise); + + @Override + void close(); + + + +---------------------- +SimpleChannelPool | +---------------------- + # 简单的连接池实现 + # 构造函数 + SimpleChannelPool(Bootstrap bootstrap, final ChannelPoolHandler handler) + SimpleChannelPool(Bootstrap bootstrap, final ChannelPoolHandler handler, ChannelHealthChecker healthCheck) + SimpleChannelPool(Bootstrap bootstrap, final ChannelPoolHandler handler, ChannelHealthChecker healthCheck, boolean releaseHealthCheck) + SimpleChannelPool(Bootstrap bootstrap, final ChannelPoolHandler handler, ChannelHealthChecker healthCheck, boolean releaseHealthCheck, boolean lastRecentUsed) + + +---------------------- +FixedChannelPool | +---------------------- + # 固定数量的连接池实现, 继承 SimpleChannelPool + # 构造函数 + FixedChannelPool(Bootstrap bootstrap, ChannelPoolHandler handler, int maxConnections) + FixedChannelPool(Bootstrap bootstrap, ChannelPoolHandler handler, int maxConnections, int maxPendingAcquires) + FixedChannelPool(Bootstrap bootstrap, ChannelPoolHandler handler, ChannelHealthChecker healthCheck, AcquireTimeoutAction action, final long acquireTimeoutMillis, int maxConnections, int maxPendingAcquires) + FixedChannelPool(Bootstrap bootstrap, ChannelPoolHandler handler, ChannelHealthChecker healthCheck, AcquireTimeoutAction action, final long acquireTimeoutMillis, int maxConnections, int maxPendingAcquires, final boolean releaseHealthCheck) + FixedChannelPool(Bootstrap bootstrap, ChannelPoolHandler handler, ChannelHealthChecker healthCheck, AcquireTimeoutAction action, final long acquireTimeoutMillis, int maxConnections, int maxPendingAcquires, boolean releaseHealthCheck, boolean lastRecentUsed) + + + action + * 内部静态枚举类:FixedChannelPool.AcquireTimeoutAction + NEW + * 检测到超时时创建新连接 + FAIL + * 抛出异常:TimeoutException diff --git "a/Netty/netty-channel-\346\200\273\347\273\223.java" "b/Netty/netty-channel-\346\200\273\347\273\223.java" new file mode 100644 index 00000000..f45d39fc --- /dev/null +++ "b/Netty/netty-channel-\346\200\273\347\273\223.java" @@ -0,0 +1,31 @@ +----------------------------- +Channel 的总结 | +----------------------------- + # 处理数据用ChannelInboundHandler + # 响应数据用ChannelOutboundHandler + +----------------------------- +事件 | +----------------------------- + # 事件机制 + * ChannelInboundHandler 可以覆写N多的事件方法 + * ChannelOutboundHandler 只有基本的 handler 事件,但也是空实现 + * ChannelHandlerContext 可以主动的调用 fireXxxxx(),触发下一个(右边) ChannelInboundHandler 的事件 + * ChannelPipeline 也可以主动的调用 fireXxxxx(),触发事件,但是它是从第一个 ChannelInboundHandler 开始触发 + + # 使用 ChannelPipeline 执行响应或者触发事件,都是从头开始 + * 执行它的 write(),会从ChannelPipeline 的右边开始处理消息,也是从头开始进入整个输出执行链 + * 执行它的 fireXxxxx(),都是最先触发左边的 ChannelInboundHandler 事件 + +----------------------------- +异常 | +----------------------------- + +----------------------------- +数据响应 | +----------------------------- + + # 几种响应数据的方法和区别 + * 调用原始 Channel 的write() api,也会从ChannelPipeline 的最右边开始处理消息,也是从头开始进入整个输出执行链 + * 调用 ChannelHandlerContext 的 write() 方法,会从下一个(左边最近的一个) ChannelOutboundHandler 开始执行 + * ChannelPipeline 也可以使用 write() api,跟第一种情况一样 \ No newline at end of file diff --git a/Netty/netty-channel.java b/Netty/netty-channel.java new file mode 100644 index 00000000..1f6bfeb9 --- /dev/null +++ b/Netty/netty-channel.java @@ -0,0 +1,9 @@ +---------------------------- +channel | +--------------------------- + # Channel 生命周期 + channelRegistered channel 注册到一个 EventLoop + channelActive channel 变为活跃状态(连接到了远程主机),现在可以接收和发送数据了 + channelInactive channel 处于非活跃状态,没有连接到远程主机 + channelUnregistered channel 已创建但未注册到一个 EventLoop (或者从EventLoop中移除) + diff --git a/Netty/netty-client-bootstrap.java b/Netty/netty-client-bootstrap.java new file mode 100644 index 00000000..15593b42 --- /dev/null +++ b/Netty/netty-client-bootstrap.java @@ -0,0 +1,27 @@ +------------------------ +Bootstrap | +----------------------- + # 客户端一般配置代码 + EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); + try { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(eventLoopGroup); + bootstrap.channel(NioSocketChannel.class); + // 连接的远端地址 + bootstrap.remoteAddress(new InetSocketAddress("127.0.0.1", 1024)); + bootstrap.option(ChannelOption.SO_REUSEADDR, true); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + + } + }); + // 执行连接 + ChannelFuture channelFuture = bootstrap.connect().sync(); + channelFuture.channel().closeFuture().sync(); + } finally { + eventLoopGroup.shutdownGracefully(); + } + + * 没有childOption和childHandler配置 + * channel为:NioSocketChannel \ No newline at end of file diff --git a/Netty/netty-epoll.java b/Netty/netty-epoll.java new file mode 100644 index 00000000..d64bacdd --- /dev/null +++ b/Netty/netty-epoll.java @@ -0,0 +1,36 @@ +------------------------ +epoll | +------------------------ + # 只能在Linux系统下运行的高性能io模式 + # 参考 + https://github.com/netty/netty/wiki/Native-transports + + # 需要替换的组件 + NioEventLoopGroup → EpollEventLoopGroup + NioEventLoop → EpollEventLoop + + NioServerSocketChannel → EpollServerSocketChannel + NioSocketChannel → EpollSocketChannel + + # 需要的依赖 + + io.netty + netty-transport-native-epoll + 4.1.34.Final + + + + + kr.motd.maven + os-maven-plugin + 1.6.2 + + + + + # 可以通过静态工具类:Epoll 判断是否支持epoll + boolean isAvailable(); + * 判断Epoll是否可用 + + void ensureAvailability(); + Throwable unavailabilityCause(); \ No newline at end of file diff --git a/Netty/netty-eventloop.java b/Netty/netty-eventloop.java new file mode 100644 index 00000000..083bd989 --- /dev/null +++ b/Netty/netty-eventloop.java @@ -0,0 +1,55 @@ +------------------------ +EventLoop | +------------------------ + # 类库 + EventLoopGroup + * 继承接口:ScheduledExecutorService, Iterable + |-MultithreadEventLoopGroup + |-EpollEventLoopGroup + |-NioEventLoopGroup + + + # EpollEventLoopGroup + * 由jni驱动的epoll()和非阻塞IO + * 只能在Linux系统上使用,比NIO传输更快 + * 如果使用epoll(),那么ServerBootstrap的channel要使用:EpollServerSocketChannel + + NioEventLoopGroup → EpollEventLoopGroup + NioEventLoop → EpollEventLoop + + NioServerSocketChannel → EpollServerSocketChannel + NioSocketChannel → EpollSocketChannel + + # 总结 + * 一个EventLoopGroup当中会包含一个或者多个EventLoop + * 一个EventLoop在它的整个生命周期中都只会与唯一一个Thread进行绑定 + * 所有由EventLoop所处理的各种I/O事件都将在它所关联的那个Thread上进行处理 + * 一个Channel在它的整个生命周期中只会被注册一个EventLoop上 + * 一个EventLoop在运行过程当中,会被分配给一个或者多个Cahnnel + + * 在Netty中,Channel的实现一定是线程安全的,基于此,可以存储一个Channel的引用,并且在需要向远程端点发送数据时,通过该引用调用Cahnnel相应的方法 + * 即便当时在多个线程都在同时操作的时候,也不会出现线程安全问题,并且消息一定会按照顺序发送出去 + + * 在实际开发中,不要把执行时间长的任务放入到EventLoop的执行队列中,因为它会一直阻塞该线程所对应的所有Channel上的其他执行任务 + * 如果需要进行阻塞调用或者是耗时的操作,那可以专门准备一个EventExecutor(业务线程池) + + // 实例化线程池 + ExecutorService executorService = Executors.newCachedThreadPool(); + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + // 通过自己定义的线程池异步的去执行任务 + executorService.execute(() -> { + + }); + } + + * 借助 Netty 提供的向 ChannelPipeline ,添加 ChannelHandler时调用的addLst()方法来传递EventExecutor + + ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler); + ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers); + + * 通过这种方式,来设置指定Handler中回调方法执行使用的线程池(EventExecutorGroup) + * EventExecutorGroup 子类也太TM多了吧? + + diff --git "a/Netty/netty-io\346\250\241\345\274\217.java" "b/Netty/netty-io\346\250\241\345\274\217.java" new file mode 100644 index 00000000..729b54c9 --- /dev/null +++ "b/Netty/netty-io\346\250\241\345\274\217.java" @@ -0,0 +1,18 @@ +--------------------------------- +IO模式 | +--------------------------------- + NIO + * io.netty.channel.socket.nio + * 基于java.nio.channels的工具包,使用选择器作为基础的方法。 + OIO + * io.netty.channel.socket.oio + * 基于java.net的工具包,使用阻塞流 + + Local + * io.netty.channel.local + * 用来在虚拟机之间本地通信 + + Embedded + * io.netty.channel.embedded 嵌入传输,它允许在没有真正网络的传输中使用 ChannelHandler + * 可以非常有用的来测试ChannelHandler的实现 + diff --git "a/Netty/netty-ip\350\277\207\346\273\244.java" "b/Netty/netty-ip\350\277\207\346\273\244.java" new file mode 100644 index 00000000..35a68d60 --- /dev/null +++ "b/Netty/netty-ip\350\277\207\346\273\244.java" @@ -0,0 +1,82 @@ +-------------------------------- +ip控制 | +-------------------------------- + # 类库 + IpFilterRuleType + * 拦截的结果枚举 + ACCEPT + REJECT + + IpFilterRule + *规则描述 + |-IpSubnetFilterRule + + AbstractRemoteAddressFilter + * 不同的拦截器 + |-UniqueIpFilter + |-RuleBasedIpFilter + + # AbstractRemoteAddressFilter + * 抽象类,提供了一个抽象方法,用于过滤ip + boolean accept(ChannelHandlerContext ctx, T remoteAddress) throws Exception; + + * 如果ip处于黑名单,则会触发异常 + throw new IllegalStateException("cannot determine to accept or reject a channel: " + ctx.channel()); + + * 提供可覆写的事件方法 + void channelAccepted(ChannelHandlerContext ctx, T remoteAddress); + * 客户端验证通过时触发 + + ChannelFuture channelRejected(ChannelHandlerContext ctx, T remoteAddress); + * 客户端验证失败时触发 + * ip拦截触发(可以在里面实现要给客户端响应的数据) + * 如果不响应任何数据,返回 null + + # IpFilterRule + * ip规则的描述,是一个接口,实现类:IpSubnetFilterRule + IpSubnetFilterRule(String ipAddress, int cidrPrefix, IpFilterRuleType ruleType) + IpSubnetFilterRule(InetAddress ipAddress, int cidrPrefix, IpFilterRuleType ruleType) + + +-------------------------------- +UniqueIpFilter | +-------------------------------- + # 唯一ip连接的过滤器 + # 一个ip只能保持一个连接 + # accept() 实现 + @Override + protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception { + final InetAddress remoteIp = remoteAddress.getAddress(); + if (!connected.add(remoteIp)) { + return false; // 该ip已经存在于Set集合,阻止连接 + } else { + ctx.channel().closeFuture().addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + // 断开连接时从Set集合移除 + connected.remove(remoteIp); + } + }); + return true; + } + } + +-------------------------------- +RuleBasedIpFilter | +-------------------------------- + # 根据规则来过滤ip + # 构造方法 + RuleBasedIpFilter(IpFilterRule... rules) + # accept() 实现 + @Override + protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception { + for (IpFilterRule rule : rules) { + if (rule == null) { + break; + } + if (rule.matches(remoteAddress)) { + return rule.ruleType() == IpFilterRuleType.ACCEPT; + } + } + return true; + } diff --git "a/Netty/netty-ssl-\345\217\214\345\220\221\350\256\244\350\257\201.java" "b/Netty/netty-ssl-\345\217\214\345\220\221\350\256\244\350\257\201.java" new file mode 100644 index 00000000..a3f33cd8 --- /dev/null +++ "b/Netty/netty-ssl-\345\217\214\345\220\221\350\256\244\350\257\201.java" @@ -0,0 +1,151 @@ +---------------------------------------- +ssl双向认证 | +---------------------------------------- + # 一般Web应用都是采用SSL单向认证的 + * 原因很简单,用户数目广泛,且无需在通讯层对用户身份进行验证,一般都在应用逻辑层来保证用户的合法登入 + + # 企业应用对接,可能会要求对客户端(相对而言)做身份验证 + * 这时就需要做SSL双向认证 + * SSL双向认证,要求客户端和服务器都要有自己的证书,并且互相导入了公钥 + +---------------------------------------- +证书生成 | +---------------------------------------- + # 生成证书 + keytool -genkey -alias [公钥别名] -validity [有效期(天)] -keystore [keystore文件] -keyalg [算法(RSA)] + + # 导出公钥 + keytool -export -alias [公钥别名] -file [公钥文件] -keystore [keystore文件] -storepass [keystore密码] + + # 导入公钥 + keytool -import -alias [公钥别名] -keystore [keystore文件] -file [公钥文件] -keypass [公钥密码] -storepass [keystore密码] + + # 删除导入的公钥 + keytool -delete -alias [公钥别名] -keystore [keystore文件] + + # 查看已经导入的公钥列表 + keytool -list -v -keystore [keystore文件] -storepass [keystore密码] + + # 演示 + 1 服务端生成证书,并且导出公钥 + keytool -genkey -alias server -validity 100 -keystore server.keystore -keyalg RSA + + keytool -export -alias server -file server.cer -keystore server.keystore + + 2 客户端生成证书 + keytool -genkey -alias client -validity 100 -keystore client.keystore -keyalg RSA + + 3 客户端导入服务端公钥到证书 + keytool -import -trustcacerts -alias server -file server.cer -keystore client.keystore + + 4 客户端导出公钥 + keytool -export -alias client -file client.cer -keystore client.keystore + + 5 服务端导入客户端公钥到证书 + keytool -import -trustcacerts -alias client -file client.cer -keystore server.keystore + + * 客户端从证书删除已经导入的公钥 + keytool -delete -alias server -keystore client.keystore + + * 服务端从证书删除已经导入的公钥 + keytool -delete -alias client -keystore server.keystore + + +---------------------------------------- +SSLContextFactory | +---------------------------------------- + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +public class SSLContextFactory { + + // 协议 + private static final String PROTOCOL = "TLS"; //SSLv3 + + // keystore 类型 + private static final String KEY_KEYSTORE_TYPE = "JKS"; + + // 算法 + private static final String ALGORITHM = "SunX509"; + + public static SSLContext getSslContext(String keystore, String password) throws NoSuchAlgorithmException,UnrecoverableKeyException, KeyStoreException, CertificateException, IOException, KeyManagementException { + + SSLContext sslContext = SSLContext.getInstance(PROTOCOL); + + KeyManager[] keyManagers = getKeyManagers(Files.newInputStream(Paths.get(keystore)), password); + + TrustManager[] trustManagers = getTrustManagers(Files.newInputStream(Paths.get(keystore)), password); + + sslContext.init(keyManagers, trustManagers, null); + + return sslContext; + } + + private static KeyManager[] getKeyManagers(InputStream keystore, String password)throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException,IOException { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(ALGORITHM); + KeyStore keyStore = KeyStore.getInstance(KEY_KEYSTORE_TYPE); + keyStore.load(keystore, password.toCharArray()); + keyManagerFactory.init(keyStore, password.toCharArray()); + KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); + return keyManagers; + } + + private static TrustManager[] getTrustManagers(InputStream keystore, String password)throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(ALGORITHM); + KeyStore keyStore = KeyStore.getInstance(KEY_KEYSTORE_TYPE); + keyStore.load(keystore, password.toCharArray()); + trustManagerFactory.init(keyStore); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + return trustManagers; + } +} + + + +---------------------------------------- +服务端 | +---------------------------------------- + SSLEngine engine = SSLContextFactory.getSslContext("server.keystore","123456").createSSLEngine(); + engine.setUseClientMode(false); + engine.setNeedClientAuth(true); // 需要验证客户端 + socketChannel.pipeline().addLast(new SslHandler(engine)); + +---------------------------------------- +客户端 | +---------------------------------------- + SSLEngine engine = SSLContextFactory.getSslContext("client.keystore","123456").createSSLEngine(); + engine.setUseClientMode(true); + socketChannel.pipeline().addLast(new SslHandler(engine)); + +---------------------------------------- +监听握手是否完成 | +---------------------------------------- + # 监听通道激活事件,在里面获取到SslHandler的handshakeFuture,并且设置监听 + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + ctx.pipeline().get(SslHandler.class).handshakeFuture().addListener(new GenericFutureListener>() { + @Override + public void operationComplete(Future future) throws Exception { + if(!future.isSuccess()) { + // 握手失败 + future.cause().printStackTrace(); + } + } + }); + } \ No newline at end of file diff --git a/Netty/netty-ssl.java b/Netty/netty-ssl.java new file mode 100644 index 00000000..aca1256a --- /dev/null +++ b/Netty/netty-ssl.java @@ -0,0 +1,49 @@ +----------------------------- +ssl | +----------------------------- + # 一个使用 SslHandler 数据流程 + * 加密的入站数据被拦截,并被解密为平常数据 + * 平常数据传过 SslHandler + * SslHandler 加密数据并它传递出站 + + # 在大多数情况下,SslHandler 将成为 ChannelPipeline 中的第一个 ChannelHandler + * 这将确保所有其他 ChannelHandler 应用他们的逻辑到数据后加密后才发生,从而确保他们的变化是安全的 + + # 涉及类库 + SslHandler + + # 服务端的Handler创建 + protected void initChannel(SocketChannel ch) throws Exception { + SelfSignedCertificate ssc = new SelfSignedCertificate(); + SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); + SslHandler sslHandler = sslCtx.newHandler(ch.alloc()); + ch.pipeline().addLast(sslHandler); // 把sslHandler添加到第一个 + } + + # 客户端的Handler创建 + String host = "127.0.0.1"; // 服务器地址 + int port = 1024; // 服务器端口 + @Override + protected void initChannel(SocketChannel ch) throws Exception { + SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build(); + SslHandler sslHandler = sslContext.newHandler(ch.alloc(), host, port); + ch.pipeline().addLast(sslHandler); // 把sslHandler添加到第一个 + } + + # SslHandler 的一些常用方法 + void setHandshakeTimeout(long handshakeTimeout, TimeUnit unit) + * 设置握手超时时间,ChannelFuture 将得到通知 + + void setHandshakeTimeoutMillis(long handshakeTimeoutMillis) + * 设置握手超时时间,ChannelFuture 将得到通知 + + long getHandshakeTimeoutMillis() + * 获取握手超时时间值 + + + void setHandshakeTimeoutMillis(long handshakeTimeoutMillis) + * 设置关闭通知超时时间,若超时, ChannelFuture 会关闭失败 + + Future handshakeFuture() + * 返回完成握手后的 ChannelFuture(握手结果) + diff --git a/Netty/netty-udp.java b/Netty/netty-udp.java new file mode 100644 index 00000000..acd774f6 --- /dev/null +++ b/Netty/netty-udp.java @@ -0,0 +1,171 @@ +------------------------------- +udp | +------------------------------- + # 类库 + DatagramPacket + DatagramPacketEncoder + DatagramPacketDecoder + + # 资瓷的配置 + // 开启广播 + bootstrap.option(ChannelOption.SO_BROADCAST, true); + // 设置UDP读缓冲区为2M + bootstrap.option(ChannelOption.SO_RCVBUF, 2048 * 1024); + // 设置UDP写缓冲区为1M + bootstrap.option(ChannelOption.SO_SNDBUF, 1024 * 1024); + +------------------------------- +DatagramPacket | +------------------------------- + # 继承:DefaultAddressedEnvelope 实现 ByteBufHolder + # Netty 对udp消息的封装 + DatagramPacket(ByteBuf data, InetSocketAddress recipient) + DatagramPacket(ByteBuf data, InetSocketAddress recipient, InetSocketAddress sender) + + * data 数据 + * recipient 接收方信息 + * sender 发送方信息 + + # 方法 + DatagramPacket copy() + DatagramPacket duplicate() + DatagramPacket retainedDuplicate() + DatagramPacket replace(ByteBuf content) + DatagramPacket retain() + DatagramPacket retain(int increment) + DatagramPacket touch() + DatagramPacket touch(Object hint) + +------------------------------- +DatagramPacketDecoder | +------------------------------- + # UDP消息的解码器 + DatagramPacketDecoder(MessageToMessageDecoder decoder) + + +------------------------------- +DatagramPacketEncoder | +------------------------------- + # UDP消息的编码器 + DatagramPacketEncoder(MessageToMessageEncoder encoder) + + +------------------------------- +UDP服务器 | +------------------------------- +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.DatagramPacket; +import io.netty.channel.socket.nio.NioDatagramChannel; + +public class Server { + public static void main(String[] args) throws InterruptedException { + EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); + try { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(eventLoopGroup); + bootstrap.channel(NioDatagramChannel.class); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + + ch.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { + + // 收到的消息 + ByteBuf byteBuf = msg.content(); + + // 发送方的信息 + InetSocketAddress inetSocketAddress = msg.sender(); + + System.out.println(byteBuf.toString(StandardCharsets.UTF_8)); + System.out.println(inetSocketAddress); + + // 响应客户端 + DatagramPacket datagramPacket = new DatagramPacket(Unpooled.copiedBuffer("我已经收到了",StandardCharsets.UTF_8), inetSocketAddress); + ctx.writeAndFlush(datagramPacket); + } + }); + } + }); + + // 绑定监听 + ChannelFuture channelFuture = bootstrap.bind(new InetSocketAddress(1024)).sync(); + channelFuture.channel().closeFuture().sync(); + } finally { + eventLoopGroup.shutdownGracefully(); + } + } +} + +------------------------------- +UDP客户端 | +------------------------------- +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.DatagramPacket; +import io.netty.channel.socket.nio.NioDatagramChannel; + +public class Clien { + public static void main(String[] args) throws InterruptedException { + EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); + try { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(eventLoopGroup); + bootstrap.channel(NioDatagramChannel.class); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { + // 收到的消息 + ByteBuf byteBuf = msg.content(); + + // 发送方的信息 + InetSocketAddress inetSocketAddress = msg.sender(); + + System.out.println(byteBuf.toString(StandardCharsets.UTF_8)); + System.out.println(inetSocketAddress); + } + }); + } + }); + + // 绑定本地端口(如果设置为0则是随机端口) + ChannelFuture channelFuture = bootstrap.bind(new InetSocketAddress(0)).sync(); + + // 发送数据 + DatagramPacket datagramPacket = new DatagramPacket(Unpooled.copiedBuffer("Hello World!", StandardCharsets.UTF_8), new InetSocketAddress("127.0.0.1", 1024)); + channelFuture.channel().writeAndFlush(datagramPacket); + + channelFuture.channel().closeFuture().sync(); + }finally { + eventLoopGroup.shutdownGracefully(); + } + } +} diff --git a/Netty/netty-util-FastThreadLocal.java b/Netty/netty-util-FastThreadLocal.java new file mode 100644 index 00000000..bbce55ab --- /dev/null +++ b/Netty/netty-util-FastThreadLocal.java @@ -0,0 +1,31 @@ + +---------------------------- +FastThreadLocal | +---------------------------- + # 构造方法 + FastThreadLocal() + + + # 静态方法 + void destroy() + void removeAll() + int size() + + + # 实例方法 + V get() + V get(InternalThreadLocalMap threadLocalMap) + + boolean isSet() + boolean isSet(InternalThreadLocalMap threadLocalMap) + + void remove() + void remove(InternalThreadLocalMap threadLocalMap) + + void set(V value) + void set(InternalThreadLocalMap threadLocalMap, V value) + + + + protected V initialValue() + protected void onRemoval(@SuppressWarnings("UnusedParameters") V value) \ No newline at end of file diff --git a/Netty/netty-util-GlobalEventExecutor.java b/Netty/netty-util-GlobalEventExecutor.java new file mode 100644 index 00000000..ed691d6d --- /dev/null +++ b/Netty/netty-util-GlobalEventExecutor.java @@ -0,0 +1,8 @@ +---------------------------- +GlobalEventExecutor | +---------------------------- + # 具备任务队列的单线程事件执行器,其适合用来实行时间短,碎片化的任务 + # 实现了 AbstractScheduledEventExecutor + # + # 唯一的一个静态实例 + GlobalEventExecutor.INSTANCE \ No newline at end of file diff --git a/Netty/netty-util-Recycler.java b/Netty/netty-util-Recycler.java new file mode 100644 index 00000000..4b3dd898 --- /dev/null +++ b/Netty/netty-util-Recycler.java @@ -0,0 +1,18 @@ +------------------------- +Recycler | +------------------------- + # 抽象类 + # 构造方法 + Recycler() + Recycler(int maxCapacityPerThread) + Recycler(int maxCapacityPerThread, int maxSharedCapacityFactor) + Recycler(int maxCapacityPerThread, int maxSharedCapacityFactor,int ratio, int maxDelayedQueuesPerThread) + + + # 实例方法 + T get() + + + # 唯一抽象方法 + abstract T newObject(Handle handle); + diff --git a/Netty/netty-websocket-client.java b/Netty/netty-websocket-client.java new file mode 100644 index 00000000..b8aa1ad9 --- /dev/null +++ b/Netty/netty-websocket-client.java @@ -0,0 +1,158 @@ +------------------------- +client | +------------------------- + # 类库 + WebSocketClientHandshakerFactory + WebSocketClientHandshaker + +------------------------------------ +WebSocketClientHandshakerFactory | +------------------------------------ + # 客户端握手处理器的工厂类 + WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subprotocol,boolean allowExtensions, HttpHeaders customHeaders) + WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subprotocol,boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength) + WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subprotocol,boolean allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,boolean performMasking, boolean allowMaskMismatch) + + +------------------------- +客户端实现 | +------------------------- + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPromise; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException; +import io.netty.handler.codec.http.websocketx.WebSocketVersion; +import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler; +import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.util.CharsetUtil; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; + +class WebSocketClientHandler extends SimpleChannelInboundHandler { + + private final WebSocketClientHandshaker handshaker; + + private ChannelPromise handshakeFuture; + + public WebSocketClientHandler(WebSocketClientHandshaker handshaker) { + this.handshaker = handshaker; + } + + public ChannelFuture handshakeFuture() { + return handshakeFuture; + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + handshakeFuture = ctx.newPromise(); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + handshaker.handshake(ctx.channel()); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + /** + * 处理握手消息 + */ + if (!handshaker.isHandshakeComplete()) { // 握手消息 + try { + handshaker.finishHandshake(ctx.channel(), (FullHttpResponse) msg); // 完成握手 + handshakeFuture.setSuccess(); + } catch (WebSocketHandshakeException e) { + handshakeFuture.setFailure(e); // 握手异常 + } + return; + } + + /** + * 握手失败的响应的异常信息 + */ + if (msg instanceof FullHttpResponse) { + FullHttpResponse response = (FullHttpResponse) msg; + throw new IllegalStateException("Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')'); + } + + /** + * WebSocket消息 + */ + WebSocketFrame frame = (WebSocketFrame) msg; + if (frame instanceof TextWebSocketFrame) { // 文本消息数据 + TextWebSocketFrame textFrame = (TextWebSocketFrame) frame; + System.out.println("收到服务端数据:" + textFrame.text()); + } else { + // 其他类型的数据 + } + } +} + +public class Client { + public static void main(String[] args) throws URISyntaxException, InterruptedException, IOException { + + // 连接地址 + URI uri = new URI("ws://localhost:1024/websocket"); + + EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); + + try { + + // uri,版本,子协议,是否支持扩展,http头 + WebSocketClientHandler handler = new WebSocketClientHandler(WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders())); + + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(eventLoopGroup); + bootstrap.channel(NioSocketChannel.class); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(new HttpClientCodec()); + pipeline.addLast(new ChunkedWriteHandler()); + pipeline.addLast(new HttpObjectAggregator(8192)); + pipeline.addLast(WebSocketClientCompressionHandler.INSTANCE); + pipeline.addLast(handler); + } + }); + + Channel channel = bootstrap.connect(uri.getHost(), uri.getPort()).sync().channel(); + + // 等待握手完成 + handler.handshakeFuture().sync().addListener(new GenericFutureListener>() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { // 握手失败 + future.cause().printStackTrace(); + } + } + }); + + channel.closeFuture().sync(); + } finally { + eventLoopGroup.shutdownGracefully(); + } + } +} diff --git a/Netty/netty-websocket.java b/Netty/netty-websocket.java new file mode 100644 index 00000000..b0bf03f8 --- /dev/null +++ b/Netty/netty-websocket.java @@ -0,0 +1,227 @@ +--------------------------------- +websocket | +--------------------------------- + # 类库 + WebSocketFrame + |-BinaryWebSocketFrame + |-CloseWebSocketFrame + |-ContinuationWebSocketFrame + |-PingWebSocketFrame + |-PongWebSocketFrame + |-TextWebSocketFrame + WebSocketProtocolHandler(自动处理webSocket的ping pong等消息) + |-WebSocketServerProtocolHandler(简化开发提供的handler) + WebSocketServerCompressionHandler(WebSocket消息压缩) + WebSocketServerProtocolHandshakeHandler(WebSocket协议的握手实现) + WebSocketServerHandshaker(握手处理器) + WebSocketServerHandshakerFactory(生成握手器的工厂类) + WebSocketDecoderConfig(解码相关的配置) + WebSocketCloseStatus(WebSocket的状态码封装) + + + # WebSocketFrame + * ws消息类型的抽象类,提供了N个实现,表示不同的消息类型 + * 看子类,类名就知道是啥消息的实现了 + + # WebSocketServerCompressionHandler + * 提供了对websocket消息的压缩 + + # WebSocketDecoderConfig + * websocket解码相关的配置 + * 实用Builder模式构建 + WebSocketDecoderConfig webSocketDecoderConfig = WebSocketDecoderConfig.newBuilder() + .allowExtensions(true) // 是否允许扩展 + .allowMaskMismatch(false) // 是否要接受未实现标准规范的数据帧 + .closeOnProtocolViolation(true) // 是否在协议错误(冲突)的情况下关闭(默认true) + .expectMaskedFrames(true) // 服务端必须设置为true(如果是客户端, 必须设置为false) + .maxFramePayloadLength(1024) // 最大的消息体体积 + .build(); + + private int maxFramePayloadLength = 65536; + private boolean expectMaskedFrames = true; + private boolean allowMaskMismatch; + private boolean allowExtensions; + private boolean closeOnProtocolViolation = true; + + + # WebSocketCloseStatus + * websocket状态码的封装 + private final int statusCode; // 状态码 + private final String reasonText; // 原因 + private String text; // this.text = text = code() + " " + reasonText(); + + * 构造函数 + public WebSocketCloseStatus(int statusCode, String reasonText) + + * 静态方法 + public static WebSocketCloseStatus valueOf(int code) + * 根据code获取现有的静态实现 + * 如果不存在, 则创建: + return new WebSocketCloseStatus(code, "Close status #" + code); + + public static boolean isValidStatusCode(int code) + * 判断指定的状态码是否是一个不合法的状态码 + + + * 提供了N多的的静态实现 + public static final WebSocketCloseStatus NORMAL_CLOSURE = new WebSocketCloseStatus(1000, "Bye"); + ... + + +------------------------------------- +WebSocketServerProtocolHandler | +------------------------------------- + # 最重的一个封装,完成了N多的工作 + # 负责处理:握手,控制帧(ping/pong/close),文本消息,二进制消息.... + # 构造函数(版本迭代, 构造函数的形参也许不正确了, 但是形参列表的始终正确) + WebSocketServerProtocolHandler(String websocketPath) + WebSocketServerProtocolHandler(String websocketPath, long handshakeTimeoutMillis) + WebSocketServerProtocolHandler(String websocketPath, boolean checkStartsWith) + WebSocketServerProtocolHandler(String websocketPath, String subprotocols) + WebSocketServerProtocolHandler(String websocketPath, String subprotocols,boolean allowExtensions) + WebSocketServerProtocolHandler(String websocketPath, String subprotocols,boolean allowExtensions, int maxFrameSize) + WebSocketServerProtocolHandler(String websocketPath, String subprotocols,boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch) + WebSocketServerProtocolHandler(String websocketPath, String subprotocols,boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch, boolean checkStartsWith) + WebSocketServerProtocolHandler(String websocketPath, String subprotocols,boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch, boolean checkStartsWith, boolean dropPongFrames) + + websocketPath + * 提供服务的uri + + subprotocols + * 支持的子协议列表 + + allowExtensions + * 是否允许扩展 + + maxFrameSize + * 消息帧最大字节数 + + allowMaskMismatch + * 是否要接受未实现标准规范的数据帧 + + checkStartsWith + * 如果为true, 则使用startWith 检测 uri, 否则使用quals + * 如果为false, 则必须要求uri跟websocket一摸一样 + + dropPongFrames + * 忽略pong信息 + + handshakeTimeoutMillis + * websocket握手超时, + * 默认: private static final long DEFAULT_HANDSHAKE_TIMEOUT_MS = 10000L; + + decoderConfig + * WebSocketDecoderConfig 类 + * WebSocket解码相关的配置 + + # 它会把websocket的数据编码为 WebSocketFrame 对象,交给下一个Handler去处理 + + + + +--------------------------------- +服务端 | +--------------------------------- +import java.util.Locale; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.handler.stream.ChunkedWriteHandler; + +public class Server { + public static void main(String[] args) throws InterruptedException { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup); + serverBootstrap.channel(NioServerSocketChannel.class); + serverBootstrap.handler(new LoggingHandler(LogLevel.INFO)); + serverBootstrap.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel socketChannel) throws Exception { + ChannelPipeline pipeline = socketChannel.pipeline(); + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new ChunkedWriteHandler()); + pipeline.addLast(new HttpObjectAggregator(65536)); + pipeline.addLast(new WebSocketServerCompressionHandler()); + pipeline.addLast(new WebSocketServerProtocolHandler("/websocket", null, true)); // uri,子协议,是否支持扩展 + pipeline.addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception { + if (msg instanceof TextWebSocketFrame) { // 文本消息处理 + String request = ((TextWebSocketFrame) msg).text(); + System.out.println("服收到消息:" + request); + ctx.channel().writeAndFlush(new TextWebSocketFrame(request.toUpperCase(Locale.CHINA))); + } else { // 其他类型的消息 + String message = "不支持的消息类型: " + msg.getClass().getName(); + throw new UnsupportedOperationException(message); + } + } + // 握手结果事件通知 + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if(evt instanceof HandshakeComplete) { + HandshakeComplete handshakeComplete = (HandshakeComplete) evt; + String uri = handshakeComplete.requestUri(); + HttpHeaders httpHeaders = handshakeComplete.requestHeaders(); + String selectedSubprotocol = handshakeComplete.selectedSubprotocol(); + LOGGER.debug("HandshakeComplete uri={},headers={},selectedSubprotocol={}",uri,httpHeaders,selectedSubprotocol); + } + super.userEventTriggered(ctx, evt); + } + }); + } + }); + Channel channel = serverBootstrap.bind(1024).sync().channel(); + channel.closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} + + +--------------------------------- +在握手前处理uri参数等等 | +--------------------------------- + # 思路 + * 在 HttpObjectAggregator 之后添加一个Handler,用于获取到完整的http请求信息:FullHttpRequest + * 可以通过FullHttpRequest获取到uri,header,method,query param等信息 + * 自己可以决定是响应异常信息,还是执行下一个handler,也就是ws握手 + + # 代码 + pipeline.addLast(new HttpObjectAggregator(65536)); + pipeline.addLast(new ChannelInboundHandlerAdapter() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if(msg instanceof FullHttpRequest) { + FullHttpRequest fullHttpRequest = (FullHttpRequest) msg; + + // 获取到请求的uri,里面包含了请求路径和请求参数 + String uri = fullHttpRequest.uri(); + + fullHttpRequest.setUri(ENDPOINT); // 一定要设置uri跟websocket的端点匹配 + } + super.channelRead(ctx, msg); + } + }); + pipeline.addLast(new WebSocketServerCompressionHandler()); + pipeline.addLast(new WebSocketServerProtocolHandler(ENDPOINT, null, true)); // uri,子协议,是否支持扩展 diff --git "a/Netty/netty-\344\274\230\351\233\205\347\232\204\345\220\257\345\212\250\344\270\216\345\205\263\351\227\255.java" "b/Netty/netty-\344\274\230\351\233\205\347\232\204\345\220\257\345\212\250\344\270\216\345\205\263\351\227\255.java" new file mode 100644 index 00000000..c7de9638 --- /dev/null +++ "b/Netty/netty-\344\274\230\351\233\205\347\232\204\345\220\257\345\212\250\344\270\216\345\205\263\351\227\255.java" @@ -0,0 +1,27 @@ +------------------------- +服务器优雅的启动 | +------------------------- + EventLoopGroup bossEventLoopGroup = new NioEventLoopGroup(); + EventLoopGroup workerEventLoopGroup = new NioEventLoopGroup(); + + ServerBootstrap serverBootstrap = new ServerBootstrap(); + // 省略N多配置 + ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(1024)).sync(); + // 在关闭回调里面处理堆积消息以及释放线程池资源 + channelFuture.channel().closeFuture().addListener(new GenericFutureListener>() { + @Override + public void operationComplete(Future future) throws Exception { + System.out.println("服务器停止..."); + bossEventLoopGroup.shutdownGracefully(); + workerEventLoopGroup.shutdownGracefully(); + } + }); + + // 线程不阻塞,继续往下执行 + System.out.println("服务器启动成功...."); + + +------------------------- +服务器优雅的退出 | +------------------------- + \ No newline at end of file diff --git "a/Netty/netty-\345\206\205\345\255\230\346\263\204\346\274\217.java" "b/Netty/netty-\345\206\205\345\255\230\346\263\204\346\274\217.java" new file mode 100644 index 00000000..fa2b8c94 --- /dev/null +++ "b/Netty/netty-\345\206\205\345\255\230\346\263\204\346\274\217.java" @@ -0,0 +1,48 @@ +------------------------------- +泄漏检测 | +------------------------------- + # Netty 定义了四种泄漏检测等级(枚举) + ResourceLeakDetector.Level + DISABLED + SIMPLE + ADVANCED + PARANOID + + * 修改检测等级,可以修改 io.netty.leakDetectionLevel jvm参数 + java -Dio.netty.leakDetectionLevel=paranoid + + + # 是通过日志来判断内存泄漏的 + + 12:05:24.374 [nioEventLoop-1-1] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. + Recent access records: 2 + #2: + Hint: 'EchoServerHandler#0' will handle the message from this point. + io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:329) + io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846) + io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:133) + io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485) + io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452) + io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346) + io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794) + java.lang.Thread.run(Thread.java:744) + #1: + io.netty.buffer.AdvancedLeakAwareByteBuf.writeBytes(AdvancedLeakAwareByteBuf.java:589) + io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:208) + io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:125) + io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485) + io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452) + io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346) + io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794) + java.lang.Thread.run(Thread.java:744) + Created at: + io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55) + io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155) + io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:146) + io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:107) + io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:123) + io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485) + io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452) + io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346) + io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794) + java.lang.Thread.run(Thread.java:744) \ No newline at end of file diff --git "a/Netty/netty-\345\216\213\347\274\251.java" "b/Netty/netty-\345\216\213\347\274\251.java" new file mode 100644 index 00000000..48468084 --- /dev/null +++ "b/Netty/netty-\345\216\213\347\274\251.java" @@ -0,0 +1,21 @@ +-------------------------------- +消息压缩 | +-------------------------------- + # 设置一个zip的压缩编码和解码器 + pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP)); + pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP)); + + # 支持的压缩类型 + ZLIB + GZIP + NONE + ZLIB_OR_NONE + +-------------------------------- +客户端 | +-------------------------------- + + +-------------------------------- +服务端 | +-------------------------------- diff --git "a/Netty/netty-\345\244\247\344\275\223\347\247\257\346\225\260\346\215\256\347\232\204\344\274\240\350\276\223.java" "b/Netty/netty-\345\244\247\344\275\223\347\247\257\346\225\260\346\215\256\347\232\204\344\274\240\350\276\223.java" new file mode 100644 index 00000000..ec748201 --- /dev/null +++ "b/Netty/netty-\345\244\247\344\275\223\347\247\257\346\225\260\346\215\256\347\232\204\344\274\240\350\276\223.java" @@ -0,0 +1,127 @@ +-------------------- +零拷贝 | +-------------------- + # NIO 的"zero-copy(零拷贝)"功能,消除移动一个文件的内容从文件系统到网络堆栈的复制步骤 + # 类库 + FileRegion + DefaultFileRegion + + # 直接传输一个文件的内容,没有执行的数据应用程序的处理 + // 获取 FileInputStream + FileInputStream in = new FileInputStream(file); + + // 创建一个新的 DefaultFileRegion 用于文件的完整长度 + FileRegion region = new DefaultFileRegion(in.getChannel(), 0, file.length()); + + // 发送 DefaultFileRegion 并且注册一个 ChannelFutureListener + channel.writeAndFlush(region).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + // 处理发送失败 + if (!future.isSuccess()) { + Throwable cause = future.cause(); + } + } + }); + + + # ssl 情况下,不能使用零拷贝 + * ssl 需要对数据进行加密 , 而加密就需要进入用户空间使用CPU计算,所以无法做到零拷贝 + +-------------------- +分段传输 | +-------------------- + # 传输文件中指定的数据块 + # Handler + ChunkedWriteHandler + + # 类库 + ChunkedInput(接口) + ChunkedFile + 平台不支持 zero-copy 或者你需要转换数据,从文件中一块一块的获取数据 + + ChunkedNioFile + 与 ChunkedFile 类似,处理使用了NIOFileChannel + + ChunkedStream + 从 InputStream 中一块一块的转移内容 + + ChunkedNioStream + 从 ReadableByteChannel 中一块一块的转移内容 + + # demo + // 初始化设置 ChunkedWriteHandler 和 自己的Handler + ChannelPipeline p = ...; + p.addLast("streamer", new ChunkedWriteHandler()); + p.addLast("handler", new MyHandler()); + + // 在自己Handler里面完成 ChunkedFile 的输出 + Channel ch = ...; + ch.write(new ChunkedFile(new File("video.mkv")); + +-------------------- +传输方式的选择 | +-------------------- + @Override + public void channelRead0(ChannelHandlerContext ctx, String filePath) throws Exception { + RandomAccessFile raf = new RandomAccessFile(filePath, "r"); + if (ctx.pipeline().get(SslHandler.class) == null) { + // 未使用ssl,可以使用零拷贝 + ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length)); + } else { + // 使用了ssl,不能使用零拷贝,可以使用分段传输中的实现 + ctx.write(new ChunkedFile(raf)); + } + ctx.flush(); + } + + +---------------------- +ChunkedInput | +---------------------- + # 接口抽象方法 + + boolean isEndOfInput() throws Exception; + * 是否已经读取完毕 + + void close() throws Exception; + * 关闭 + + @Deprecated + B readChunk(ChannelHandlerContext ctx) throws Exception; + B readChunk(ByteBufAllocator allocator) throws Exception; + * 读取数据,返回泛型 + + long length(); + * 可读长度 + + long progress(); + * 目前的传输进度 + +-------------------------------------------- +传输进度的监听 | +-------------------------------------------- + # 类库 + ChannelProgressiveFutureListener + * 用于监听传输进度的监听器 + * 对 GenericProgressiveFutureListener 的空实现 + + + ChannelFuture sendFileFuture = ctx.write(new DefaultFileRegion(file.getChannel(), 0, fileLength), ctx.newProgressivePromise()); + sendFileFuture.addListener(new ChannelProgressiveFutureListener() { + // 传输进度 + @Override + public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) { + if (total < 0) { // 未知总大小 + System.err.println(future.channel() + " Transfer progress: " + progress); + } else { + System.err.println(future.channel() + " Transfer progress: " + progress + " / " + total); + } + } + + // 传输完成 + @Override + public void operationComplete(ChannelProgressiveFuture future) { + System.err.println(future.channel() + " Transfer complete."); + } + }); \ No newline at end of file diff --git "a/Netty/netty-\345\272\217\345\210\227\345\214\226.java" "b/Netty/netty-\345\272\217\345\210\227\345\214\226.java" new file mode 100644 index 00000000..e69de29b diff --git "a/Netty/netty-\345\274\202\346\255\245\346\234\272\345\210\266.java" "b/Netty/netty-\345\274\202\346\255\245\346\234\272\345\210\266.java" new file mode 100644 index 00000000..1ef97f2a --- /dev/null +++ "b/Netty/netty-\345\274\202\346\255\245\346\234\272\345\210\266.java" @@ -0,0 +1,75 @@ +---------------------------- +异步机制 | +---------------------------- + # jdk的 Future + * 只能通过手动的方式检查执行结果,并且会阻塞 + + # ChannelFuture + * 通过监听器的方式,以回调的方式来获取执行结果,不需要手动检查,不会阻塞 + * ChannelFutureListener 的回调方法 operationComplete(F future) 是由IO线程去执行的,所以不要在这里执行耗时的操作,可以使用线程池去执行 + + + # ChannelPromise + * 继承自 ChannelFuture ,Promise + * 可以由开发者去控制是否成功,还是失败 + +-------------------------------- +ChannelFuture | +-------------------------------- + # 方法 + Channel channel(); + * 返回关联的channel + + @Override + ChannelFuture addListener(GenericFutureListener> listener); + * 添加一个监听 + + @Override + ChannelFuture addListeners(GenericFutureListener>... listeners); + * 添加多个监听 + + @Override + ChannelFuture removeListener(GenericFutureListener> listener); + * 移除一个监听 + + @Override + ChannelFuture removeListeners(GenericFutureListener>... listeners); + * 移除多个监听 + + ChannelFuture sync() + * 同步,线程阻塞,直到任务完成 + + Throwable cause() + * 返回异常信息 + + boolean isSuccess(); + * 是否操作成功 + + boolean isVoid(); + + # ChannelFutureListener + * 空继承了接口:GenericFutureListener + * 预定义了N个实例 + CLOSE + * 完成后关闭连接 + CLOSE_ON_FAILURE + * 如果抛出了异常,关闭连接 + FIRE_EXCEPTION_ON_FAILURE + +-------------------------------- +ChannelPromise | +-------------------------------- + # 继承自 ChannelFuture ,Promise + # 新增加了一个机制:可写 + + @Override + ChannelPromise setSuccess(Void result); + + @Override + ChannelPromise setFailure(Throwable cause); + + ChannelPromise setSuccess(); + + boolean trySuccess(); + + ChannelPromise unvoid(); \ No newline at end of file diff --git "a/Netty/netty-\345\274\225\347\224\250\350\256\241\346\225\260.java" "b/Netty/netty-\345\274\225\347\224\250\350\256\241\346\225\260.java" new file mode 100644 index 00000000..ec5fc065 --- /dev/null +++ "b/Netty/netty-\345\274\225\347\224\250\350\256\241\346\225\260.java" @@ -0,0 +1,54 @@ +------------------------- +引用计数 | +------------------------- + # 引用计数 + * 被引用计数包含的对象,能够显示的被垃圾回收,当初始化的时候,如果计数被减少到0则对象会被显示回收 + * 再次访问被回收的这些对象将会抛出异常 + * 如果一个对象实现了ReferenceCounted,并且包含有其他对象也实现来ReferenceCounted,当这个对象计数为0被回收的时候,所包含的对象同样会通过release()释放掉 + * 一个引用对象刚创建时,初始化refCnt值就是1 + + # 接口 ReferenceCounted + # 接口方法 + int refCnt(); + * 返回引用数 + + ReferenceCounted retain(); + * 添加一个引用 + + ReferenceCounted retain(int increment); + * 添加N个引用 + + ReferenceCounted touch(); + ReferenceCounted touch(Object hint); + + boolean release(); + * 释放一个引用,如果引用等于0,则会被回收 + + boolean release(int decrement); + * 释放n个引用,如果引用等于0,则会被回收 + + +------------------------- +ReferenceCountUtil | +------------------------- + # 对于 ReferenceCounted 的操作工具类 + # 提供了N多的静态方法(几乎跟ReferenceCounted接口一样) + * 参数都是 Object,并且提供安全的释放方法 + + int refCnt(Object msg) + + boolean release(Object msg) + boolean release(Object msg, int decrement) + + T retain(T msg) + T retain(T msg, int increment) + + void safeRelease(Object msg) + void safeRelease(Object msg, int decrement) + + T touch(T msg) + T touch(T msg, Object hint) + + + + diff --git "a/Netty/netty-\346\213\206\345\214\205\347\262\230\345\214\205.java" "b/Netty/netty-\346\213\206\345\214\205\347\262\230\345\214\205.java" new file mode 100644 index 00000000..5b8b214f --- /dev/null +++ "b/Netty/netty-\346\213\206\345\214\205\347\262\230\345\214\205.java" @@ -0,0 +1,162 @@ +---------------------------- +拆包和粘包的解决方案 | +---------------------------- + # 如果确切知道消息的固定长度,可以简单的使用ByteToMessageDecoder + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { // (2) + if (in.readableBytes() < 4) { + // 消息长不足4,不能解码 + return; + } + // 可以成功解码了一个消息 + out.add(in.readBytes(4)); + } + + # 也可以使用现成提供的解码器 + LineBasedFrameDecoder + * 回车换行符作为消息结束符的TCP黏包的问题 + + LengthFieldBasedFrameDecoder + * 固定长度 + + DelimiterBasedFrameDecoder + * 以指定的符号分割消息 + + FixedLengthFrameDecoder + * 固定长度的消息头 + + # 自动添加长度头的编码器 + LengthFieldPrepender + +---------------------------- +LengthFieldBasedFrameDecoder| +---------------------------- + # 专门为固定包头提供的解码器 + public LengthFieldBasedFrameDecoder(int maxFrameLength,int lengthFieldOffset, int lengthFieldLength) + public LengthFieldBasedFrameDecoder(int maxFrameLength,int lengthFieldOffset, int lengthFieldLength,int lengthAdjustment, int initialBytesToStrip) + public LengthFieldBasedFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,int lengthAdjustment, int initialBytesToStrip, boolean failFast) + public LengthFieldBasedFrameDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,int lengthAdjustment, int initialBytesToStrip, boolean failFast) + + + + maxFrameLength + * 单个包最大的长度,这个值根据实际场景而定 + + lengthFieldOffset + * 表示数据长度字段开始的偏移量 + * 第几个字节开始,是表示数据长度的(0) + + lengthFieldLength + * 数据长度字段的所占的字节数 + + lengthAdjustment + * 添加到长度字段的补偿值(0) + * lengthAdjustment + 数据长度取值 = 数据长度字段之后剩下包的字节数 + * 对于某些协议,长度字段还包含了消息头的长度,在这种应用场景中,往往需要使用lengthAdjustment进行修正 + 由于整个消息(包含消息头)的长度往往大于消息体的长度,所以它要设置为负数(数据长度字段的长度取负) + + initialBytesToStrip + * 表示从整个包第一个字节开始,向后忽略的字节数(0) + * 可以设置该参数,来忽略掉包头信息,仅仅留下数据包体,给下一个Handler处理 + + # 各种协议 + lengthFieldOffset = 0 + lengthFieldLength = 2 // 通用的前面2字节表示数据长度 + lengthAdjustment = 0 + initialBytesToStrip = 0 + + BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes) + +--------+----------------+ +--------+----------------+ + | Length | Actual Content |----->| Length | Actual Content | + | 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" | + +--------+----------------+ +--------+----------------+ + + lengthFieldOffset = 0 + lengthFieldLength = 2 + lengthAdjustment = 0 + initialBytesToStrip = 2 // 丢弃前面2字节的包头 + + BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes) + +--------+----------------+ +----------------+ + | Length | Actual Content |----->| Actual Content | + | 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" | + +--------+----------------+ +----------------+ + + lengthFieldOffset = 0 + lengthFieldLength = 2 + lengthAdjustment = -2 // 长度头表示的长度,包含了自身头部的长度 + initialBytesToStrip = 0 + + BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes) + +--------+----------------+ +--------+----------------+ + | Length | Actual Content |----->| Length | Actual Content | + | 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" | + +--------+----------------+ +--------+----------------+ + + lengthFieldOffset = 2 // 表示消息长度的头,不在首部 + lengthFieldLength = 3 + lengthAdjustment = 0 + initialBytesToStrip = 0 + + BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes) + +----------+----------+----------------+ +----------+----------+----------------+ + | Header 1 | Length | Actual Content |----->| Header 1 | Length | Actual Content | + | 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" | + +----------+----------+----------------+ +----------+----------+----------------+ + + lengthFieldOffset = 0 + lengthFieldLength = 3 + lengthAdjustment = 2 // 整个消息体的长度,还要包含一个头部的长度,因为后面还有一个头部 + initialBytesToStrip = 0 + + BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes) + +----------+----------+----------------+ +----------+----------+----------------+ + | Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content | + | 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" | + +----------+----------+----------------+ +----------+----------+----------------+ + + lengthFieldOffset = 1 (= the length of HDR1) // 第2个字节表示数据长度 + lengthFieldLength = 2 + lengthAdjustment = 1 (= the length of HDR2) // 除此之外,还有1个字节的消息头 + initialBytesToStrip = 3 (= the length of HDR1 + LEN) // 移除前面3个字节的数据 + + BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes) + +------+--------+------+----------------+ +------+----------------+ + | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | + | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | + +------+--------+------+----------------+ +------+----------------+ + + lengthFieldOffset = 1 + lengthFieldLength = 2 + lengthAdjustment = -3 (= the length of HDR1 + LEN, negative) //长度头表示的是整个消息体的长度 + initialBytesToStrip = 3 + + BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes) + +------+--------+------+----------------+ +------+----------------+ + | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | + | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | + +------+--------+------+----------------+ +------+----------------+ + + +---------------------------- +LengthFieldPrepender | +---------------------------- + # 自动为数据包添加长度头的编码器 + + LengthFieldPrepender(int lengthFieldLength) + LengthFieldPrepender(int lengthFieldLength, boolean lengthIncludesLengthFieldLength) + LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment) + LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment, boolean lengthIncludesLengthFieldLength) + LengthFieldPrepender(ByteOrder byteOrder, int lengthFieldLength,int lengthAdjustment, boolean lengthIncludesLengthFieldLength) + + lengthFieldLength + * 长度字段的所占的字节数 + * 只能是:1, 2, 3, 4, 8 + + lengthIncludesLengthFieldLength + * 长度是否包含了消息头的长度 + + lengthAdjustment + * 添加到长度字段的补偿值 + * 可以消息中还有其他的头 + diff --git "a/Netty/netty-\346\226\255\347\272\277\351\207\215\350\277\236.java" "b/Netty/netty-\346\226\255\347\272\277\351\207\215\350\277\236.java" new file mode 100644 index 00000000..1715060a --- /dev/null +++ "b/Netty/netty-\346\226\255\347\272\277\351\207\215\350\277\236.java" @@ -0,0 +1,129 @@ +---------------------------------- +Netty断线重连 | +---------------------------------- + +---------------------------------- +Netty Client启动的时候需要重连 | +---------------------------------- + # Netty作者在stackoverflow的回答 + https://stackoverflow.com/questions/19739054/whats-the-best-way-to-reconnect-after-connection-closed-in-netty + + private void doConnect() { + Bootstrap b = ...; + b.connect().addListener((ChannelFuture f) -> { + if (!f.isSuccess()) { + long nextRetryDelay = nextRetryDelay(...); + // 连接失败,在指定延迟后重试连接 + f.channel().eventLoop().schedule(nextRetryDelay, ..., () -> { + doConnect(); + }); // 也可以放弃连接 + } + }); + } + +---------------------------------- +在程序运行中连接断掉需要重连 | +---------------------------------- + # 参考 + https://github.com/netty/netty/blob/master/example/src/main/java/io/netty/example/uptime/UptimeClientHandler.java + + + +---------------------------------- +Client | +---------------------------------- +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.GenericFutureListener; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + +public class Client { + + private final EventLoopGroup loop; + + private final InetSocketAddress inetSocketAddress; + + public Client(EventLoopGroup loop,InetSocketAddress inetSocketAddress) { + this.loop = loop; + this.inetSocketAddress = inetSocketAddress; + } + + static class ConnectionListener implements ChannelFutureListener { + private Client client; + public ConnectionListener(Client client) { + this.client = client; + } + @Override + public void operationComplete(ChannelFuture channelFuture) throws Exception { + if (!channelFuture.isSuccess()) { + // 连接失败 + final EventLoop loop = channelFuture.channel().eventLoop(); + loop.schedule(new Runnable() { + @Override + public void run() { + // 在1s后尝试重新连接 + client.createBootstrap(new Bootstrap(), loop); + } + }, 1L, TimeUnit.SECONDS); + } + } + } + + private Bootstrap createBootstrap(Bootstrap bootstrap, EventLoopGroup eventLoop) { + if (bootstrap != null) { + + bootstrap.group(this.loop); + bootstrap.channel(NioSocketChannel.class); + bootstrap.remoteAddress(this.inetSocketAddress); + bootstrap.option(ChannelOption.SO_KEEPALIVE, true); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel socketChannel) throws Exception { + + // 添加Handler处理在程序运行中连接断掉需要重连 + socketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter() { + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + final EventLoop eventLoop = ctx.channel().eventLoop(); + eventLoop.schedule(new Runnable() { + @Override + public void run() { + // 重试连接 + Client.this.createBootstrap(new Bootstrap(), eventLoop); + } + }, 1L, TimeUnit.SECONDS); + super.channelInactive(ctx); + } + }); + } + }); + + // 在连接的时候添加监听,处理启动的时候需要重连 + bootstrap.connect().addListener(new GenericFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + // 连接失败 + final EventLoop loop = future.channel().eventLoop(); + loop.schedule(new Runnable() { + @Override + public void run() { + // 在1s后尝试重新连接 + Client.this.createBootstrap(new Bootstrap(), loop); + } + }, 1L, TimeUnit.SECONDS); + } + } + }); + } + return bootstrap; + } + // 启动客户端 + public void run() { + createBootstrap(new Bootstrap(), loop); + } +} \ No newline at end of file diff --git "a/Netty/netty-\346\227\245\345\277\227.java" "b/Netty/netty-\346\227\245\345\277\227.java" new file mode 100644 index 00000000..00d6abe0 --- /dev/null +++ "b/Netty/netty-\346\227\245\345\277\227.java" @@ -0,0 +1,25 @@ +--------------------------- +日志 | +--------------------------- + # 日志Handler,它继承:ChannelDuplexHandler + LoggingHandler + + # 构造函数 + LoggingHandler(LogLevel level) + + * level 日志级别枚举:LogLevel(默认 DEBUG) + TRACE(InternalLogLevel.TRACE) + DEBUG(InternalLogLevel.DEBUG) + INFO(InternalLogLevel.INFO) + WARN(InternalLogLevel.WARN) + ERROR(InternalLogLevel.ERROR) + + + # 只需把日志Handler添加到 pipeline 中 + + + # 设置netty的loggerFactory来使用不同的日志框架 + * 使用静态方法:InternalLoggerFactory.setDefaultFactory(InternalLoggerFactory defaultFactory) + + InternalLoggerFactory.setDefaultFactory(new Log4JLoggerFactory()); + diff --git "a/Netty/netty-\346\265\201\351\207\217\347\273\237\350\256\241\345\222\214\346\216\247\345\210\266.java" "b/Netty/netty-\346\265\201\351\207\217\347\273\237\350\256\241\345\222\214\346\216\247\345\210\266.java" new file mode 100644 index 00000000..1c5fe4a4 --- /dev/null +++ "b/Netty/netty-\346\265\201\351\207\217\347\273\237\350\256\241\345\222\214\346\216\247\345\210\266.java" @@ -0,0 +1,75 @@ +---------------------------- +流量整形 | +---------------------------- + # 类库 + TrafficCounter + * 统计的数据 + |-GlobalChannelTrafficCounter + + AbstractTrafficShapingHandler + * 流量整形handler + |-ChannelTrafficShapingHandler + |-GlobalChannelTrafficShapingHandler + |-GlobalTrafficShapingHandler + + + # 流量整形的原理 + 输入 -> 流量洪峰 -> 队列(令牌桶) -> 输出 -> 平滑流量 + + # AbstractTrafficShapingHandler + * 所有handler的抽象父类 + + AbstractTrafficShapingHandler() + AbstractTrafficShapingHandler(long checkInterval) + AbstractTrafficShapingHandler(long writeLimit, long readLimit) + AbstractTrafficShapingHandler(long writeLimit, long readLimit, long checkInterval) + AbstractTrafficShapingHandler(long writeLimit, long readLimit) + AbstractTrafficShapingHandler(long writeLimit, long readLimit, long checkInterval, long maxTime) + + writeLimit + * 限制没秒最多写入多少kb数据 + * 如果设置为0,表示不限制 + + readLimit + * 限制没秒最多读取多少kb数据 + * 如果设置为0,表示不限制 + + checkInterval + * 两次性能计算之间的延迟,默认1s + * 如果设置为0,表示不统计 + + maxTime + * 当流量超过时等待的最大延迟,默认15s + + +---------------------------- +ChannelTrafficShapingHandler| +---------------------------- + # 单个 Channel 级别的IO限制 + ChannelTrafficShapingHandler(long checkInterval) + ChannelTrafficShapingHandler(long writeLimit,long readLimit) + ChannelTrafficShapingHandler(long writeLimit,long readLimit, long checkInterval) + ChannelTrafficShapingHandler(long writeLimit, long readLimit,long checkInterval, long maxTime) + + + +----------------------------------- +GlobalChannelTrafficShapingHandler | +----------------------------------- + # 针对于某个进程中的所有 Channel 的IO限制 + GlobalChannelTrafficShapingHandler(ScheduledExecutorService executor) + GlobalChannelTrafficShapingHandler(ScheduledExecutorService executor, long checkInterval) + GlobalChannelTrafficShapingHandler(ScheduledExecutorService executor, long writeGlobalLimit, long readGlobalLimit, long writeChannelLimit, long readChannelLimit) + GlobalChannelTrafficShapingHandler(ScheduledExecutorService executor,long writeGlobalLimit, long readGlobalLimit,long writeChannelLimit, long readChannelLimit, long checkInterval) + GlobalChannelTrafficShapingHandler(ScheduledExecutorService executor,long writeGlobalLimit, long readGlobalLimit,long writeChannelLimit, long readChannelLimit, long checkInterval, long maxTime) + + +----------------------------------- +GlobalTrafficShapingHandler | +----------------------------------- + # 全局级别的同时对全局,和单个 Channel 的IO限制(整合了上面俩) + GlobalTrafficShapingHandler(EventExecutor executor) + GlobalTrafficShapingHandler(ScheduledExecutorService executor, long checkInterval) + GlobalTrafficShapingHandler(ScheduledExecutorService executor, long writeLimit,long readLimit) + GlobalTrafficShapingHandler(ScheduledExecutorService executor, long writeLimit,long readLimit, long checkInterval) + GlobalTrafficShapingHandler(ScheduledExecutorService executor, long writeLimit, long readLimit,long checkInterval, long maxTime) diff --git "a/Netty/netty-\346\266\210\346\201\257\347\232\204\347\274\226\347\240\201.java" "b/Netty/netty-\346\266\210\346\201\257\347\232\204\347\274\226\347\240\201.java" new file mode 100644 index 00000000..df5e814a --- /dev/null +++ "b/Netty/netty-\346\266\210\346\201\257\347\232\204\347\274\226\347\240\201.java" @@ -0,0 +1,34 @@ +---------------------------- +编码 | +---------------------------- + # 编码就是把对象转换为网络传输的二进制数据(ByteBuf) + # 涉及类库 + |-MessageToByteEncoder + |-MessageToMessageEncoder + |-LengthFieldPrepender + |-StringEncoder + +---------------------------- +MessageToByteEncoder | +---------------------------- + # 把对象转换为ByteBuf,抽象类,需要实现抽象方法 + void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception; + + msg + * 对象 + out + * 把对象转换为字节数据后,写入到该Buf + + # 把 Integer 转换为 Byte + public class IntegerToByteEncoder extends MessageToByteEncoder { + @Override + protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception { + out.writeInt(msg); + } + } +---------------------------- +MessageToMessageEncoder | +---------------------------- + # 抽象类,把对象转换为对象,需要实现抽象方法 + void encode(ChannelHandlerContext ctx, I msg, List out) + diff --git "a/Netty/netty-\346\266\210\346\201\257\347\232\204\347\274\226\350\247\243\347\240\201.java" "b/Netty/netty-\346\266\210\346\201\257\347\232\204\347\274\226\350\247\243\347\240\201.java" new file mode 100644 index 00000000..361dc4c9 --- /dev/null +++ "b/Netty/netty-\346\266\210\346\201\257\347\232\204\347\274\226\350\247\243\347\240\201.java" @@ -0,0 +1,66 @@ +------------------------ +消息的编解码 | +------------------------ + # 结合解码器和编码器在一起(在一个类) + * 可能会牺牲可重用性 + + # 涉及类库 + |-ChannelDuplexHandler + |-CombinedChannelDuplexHandler + |-ByteToMessageCodec + |-MessageToMessageCodec + + # 系统还提供了由 byte[] 和 ByteBuf 的编解码器 + ByteArrayEncoder + ByteArrayDecoder + +------------------------ +ByteToMessageCodec | +------------------------ + # 用来处理 byte-to-message 和 message-to-byte + # 解码字节消息成 POJO 或编码 POJO 消息成字节 + # 等同于 ByteToMessageDecoder 和 MessageToByteEncoder 的组合,抽象类,其中有 2 个方法需要我们自己实现 + void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception; + * 编码方法 + + void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception; + * 解码方法 + +-------------------------- +MessageToMessageCodec| +-------------------------- + # 用于 message-to-message 的编码和解码,对象转换为对象 + # 可以看成是 MessageToMessageDecoder 和 MessageToMessageEncoder 的组合体 + # 抽象类,需要实现两个方法 + void encode(ChannelHandlerContext ctx, I msg, List out)throws Exception; + * 编码 + + void decode(ChannelHandlerContext ctx, O msg, List out)throws Exception; + * 解码 + + # 数字的编码和解码 + public class NumberCodec extends MessageToMessageCodec { + @Override + public Long decode(ChannelHandlerContext ctx, Integer msg, List out)throws Exception { + // 把Integer转换为Long + out.add(msg.longValue()); + } + + @Override + public Integer encode(ChannelHandlerContext ctx, Long msg, List out)throws Exception { + // 把Long转换为Integer + out.add(msg.intValue()); + } + } +---------------------------------- +CombinedChannelDuplexHandler | +---------------------------------- + # CombinedChannelDuplexHandler extends ChannelDuplexHandler + # 虽然这个类不是编解码器 API 的一部分,但是它经常被用来建立一个编解码器 + # 它可以绑定一个编码器和解码器,它既可以处理入站消息,也可以处理出站消息 + # 自定义的 Char 和 Byte + public class CharCodec extends CombinedChannelDuplexHandler { + public CharCodec(){ + super(new ByteToCharDecoder(), new CharToByteEncoder()); + } + } diff --git "a/Netty/netty-\346\266\210\346\201\257\347\232\204\350\247\243\347\240\201.java" "b/Netty/netty-\346\266\210\346\201\257\347\232\204\350\247\243\347\240\201.java" new file mode 100644 index 00000000..cb025e72 --- /dev/null +++ "b/Netty/netty-\346\266\210\346\201\257\347\232\204\350\247\243\347\240\201.java" @@ -0,0 +1,87 @@ + +---------------------------- +解码 | +---------------------------- + # 解码器其实就是实现了ChannelInboundHandler的handler,主要的作用就是把消息转换为自定义的对象 + # 涉及类库 + |-ByteToMessageDecoder + |-ReplayingDecoder + |-LineBasedFrameDecoder + |-LengthFieldBasedFrameDecoder + |-DelimiterBasedFrameDecoder + |-FixedLengthFrameDecoder + |-MessageToMessageDecoder + |-StringDecoder + +---------------------------- +ByteToMessageDecoder | +---------------------------- + # 把二进制数据转换为自定义的对象 + # 抽象类,核心的抽象方法需要用户手动的覆写 + void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception; + + * 通过该方法,手动的buf里面的数据转换为自定义的对象,添加到out集合里面 + * 如果buf里面的数据不够组包,直接 return,直到可以组装成一个对象时才处理 + * 在下一个handler处理器里就可以强制的把对象转换为自己解码的对象了(建议使用SimpleChannelInboundHandler) + + # 运行机制 + * 每当有新数据接收的时候,ByteToMessageDecoder 都会调用 decode() 方法来处理内部的累积缓冲 + * decode() 方法可以决定当累积缓冲里没有足够数据时可以往 out 对象里放任意数据 + * 当有更多的数据被接收了 ByteToMessageDecoder 会再一次调用 decode() 方法 + * 如果在 decode() 方法里增加了一个对象到 out 对象里,这意味着解码器解码消息成功 + * ByteToMessageDecoder 将会丢弃在累积缓冲里已经被读过的数据 + * 不需要对多条消息调用 decode(),ByteToMessageDecoder 会持续调用 decode() 直到不放任何数据到 out 里 + + # Integer 解码器 + public class IntegerDecoder extends ByteToMessageDecoder { + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + if(in.readableBytes() >= 4){ + // 必须起码够四个字节,才读取为int对象 + out.add(in.readInt()); + } + } + } + //在下一个解码器里面就可以使用了,SimpleChannelInboundHandler + +---------------------------- +ReplayingDecoder | +---------------------------- + # 继承自 ByteToMessageDecoder 抽象类 + # 跟 ByteToMessageDecoder 相比就是,它读取缓冲区的数据的时候,不需要去判断是否有足够的字节 + * 它内部使用了一个特殊的 ByteBuf 实现:ReplayingDecoderByteBuf + * 如果在decode()中执行读取的字节数不够,ByteBuf自己会抛出异常然,在异常抛出后会被 ReplayingDecoder catch住 + * 异常实例:Signal REPLAY = Signal.valueOf(ReplayingDecoder.class, "REPLAY"); + * 每次的异常抛出,都是抛出同一个实例,避免异常对象多次创建的负担 + * ReplayingDecoder catch住异常后,会把读角标重置为初始值 + * 当有更多的数据后,会再次调用 decode() 方法 + + # 源码导读 + // 如果可读的长度小于4字节,返回 + if (buf.readableBytes() < 4) { + return; + } + // 标记读索引 + buf.markReaderIndex(); + + // 读取一个int,表示数据的长度 + int length = buf.readInt(); + + if (buf.readableBytes() < length) { + // 如果可读数据,小于数据的长度,返回 + buf.resetReaderIndex(); // 并且重置读索引 + return; + } + + out.add(buf.readBytes(length)); + +---------------------------- +MessageToMessageDecoder | +---------------------------- + # 把对象转换为对象,泛型类 + # 抽象类,核心的抽象方法需要用户手动的覆写 + void decode(ChannelHandlerContext ctx, T msg, List out) throws Exception; + + * 可能在前面的handler中已经把二进制数据转换了对象 + * 但是,需要把解码后的对象再次通过handler转换为其他的对象,于是就出现了这个 + diff --git "a/Netty/netty-\347\276\244\347\273\204.java" "b/Netty/netty-\347\276\244\347\273\204.java" new file mode 100644 index 00000000..4ec626f3 --- /dev/null +++ "b/Netty/netty-\347\276\244\347\273\204.java" @@ -0,0 +1,147 @@ +------------------------------ +ChannelGroup | +------------------------------ + # 类库 + ChannelGroup + |-DefaultChannelGroup + ChannelGroupException + ChannelGroupFuture + ChannelGroupFutureListener + ChannelMatcher + ChannelMatchers + ALL_MATCHER + + + # ChannelGroup + * 是一个接口,实现了 Set 接口 + * 表示一组 Channel + + # ChannelGroupFuture + * 表示一个执行结果,该结果可能是:写入,查找,关闭等等对于群组的一个操作 + + # ChannelGroupFutureListener + * 事件监听回调的接口,继承了GenericFutureListener,空继承而已,没新增或者覆写父类的任何抽象方法 + * 但是限定了回调结果的泛型:GenericFutureListener + channelGroupFuture.addListener(new ChannelGroupFutureListener() { + @Override + public void operationComplete(ChannelGroupFuture future) throws Exception { + // TODO + } + }); + + # ChannelMatcher + * 接口,对于Channel的描述,用于在一个群组中匹配出指定的Channel + * 只有一个抽象方法 + boolean matches(Channel channel); + + # ChannelMatchers + * 提供了一些创建/操作 ChannelMatcher 实例的静态方法 + ChannelMatcher all() + * 匹配所有的Channel的 ChannelMatcher + ChannelMatcher isNot(Channel channel) + ChannelMatcher is(Channel channel) + ChannelMatcher isInstanceOf(Class clazz) + ChannelMatcher isNotInstanceOf(Class clazz) + ChannelMatcher isServerChannel() + ChannelMatcher isNonServerChannel() + ChannelMatcher invert(ChannelMatcher matcher) + * 反转给定的matcher + ChannelMatcher compose(ChannelMatcher... matchers) + +------------------------------ +DefaultChannelGroup | +------------------------------ + # ChannelGroup 接口的唯一实现 + # 构造函数 + DefaultChannelGroup(EventExecutor executor) + DefaultChannelGroup(EventExecutor executor, boolean stayClosed) + DefaultChannelGroup(String name, EventExecutor executor) + DefaultChannelGroup(String name, EventExecutor executor, boolean stayClosed) + + executor + * 执行器 + name + * 群组的名称 + stayClosed + * 如果该值为 true, 那么在当前群组被关闭的时候, 新加入的 channel 会被执行 close + if (stayClosed && closed) { + channel.close(); + } + return added; + + + # 可以使用 GlobalEventExecutor.INSTANCE 来创建 + ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + # 实例函数(它还实现了Set接口 Set) + ChannelGroupFuture close(); + ChannelGroupFuture close(ChannelMatcher matcher); + * 关闭群组,不再接收新的channel + * 还会断开群组中的所有连接 + + ChannelGroupFuture disconnect(); + ChannelGroupFuture disconnect(ChannelMatcher matcher); + * 断开群组中所有channle的连接 + + Channel find(ChannelId id); + + ChannelGroup flush(); + ChannelGroup flush(ChannelMatcher matcher); + + String name(); + + ChannelGroupFuture newCloseFuture(); + ChannelGroupFuture newCloseFuture(ChannelMatcher matcher); + * 创建一个新的关闭通知回调 + * 如果Group中有任何channel(或者符合条件的channle)触发了close()的时候,触发该回调 + + ChannelGroupFuture write(Object message); + ChannelGroupFuture write(Object message, ChannelMatcher matcher); + ChannelGroupFuture write(Object message, ChannelMatcher matcher, boolean voidPromise); + + ChannelGroupFuture writeAndFlush(Object message); + ChannelGroupFuture writeAndFlush(Object message, ChannelMatcher matcher); + ChannelGroupFuture writeAndFlush(Object message, ChannelMatcher matcher, boolean voidPromise); + + # Channel 关闭,会自动的从 Group 种移除 + @Override + public boolean add(Channel channel) { + ... + boolean added = map.putIfAbsent(channel.id(), channel) == null; + if (added) { // 如果添加成功,则设置channel的close监听,逻辑代码为把自己从group中移除 + channel.closeFuture().addListener(remover); + } + ... + } + + # 一个 Channel 可以属于多个 Group + # 如果ServerChannel和channel存在于同一个中ChannelGroup中,则对该组执行的任何请求的I / O操作ServerChannel都会先执行 + * 当一次性关闭服务器时,此规则非常有用 + + ChannelGroup allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + public static void main(String[] args) throws Exception { + ServerBootstrap b = new ServerBootstrap(..); + + // 忽略常规的配置.... + b.childHandler(new MyHandler()); + + // 启动服务器 + b.getPipeline().addLast("handler", new MyHandler()); + Channel serverChannel = b.bind(..).sync(); + allChannels.add(serverChannel); + + // 等待关闭信令 + + //会先关闭 serverChannel 然后再关闭其他的 channel + allChannels.close().awaitUninterruptibly(); + } + + public class MyHandler extends ChannelInboundHandlerAdapter { + @Override + public void channelActive(ChannelHandlerContext ctx) { + // closed on shutdown. + allChannels.add(ctx.channel()); + super.channelActive(ctx); + } + } \ No newline at end of file diff --git "a/Netty/netty-\350\266\205\346\227\266.java" "b/Netty/netty-\350\266\205\346\227\266.java" new file mode 100644 index 00000000..43458305 --- /dev/null +++ "b/Netty/netty-\350\266\205\346\227\266.java" @@ -0,0 +1,86 @@ +---------------------------- +心跳机制 | +---------------------------- + # IdleStateHandler + * 它继承自:ChannelDuplexHandler + * 可以拦截读和写 + + # 构造函数 + public IdleStateHandler(boolean observeOutput, long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit) + public IdleStateHandler(int readerIdleTimeSeconds,int writerIdleTimeSeconds,int allIdleTimeSeconds) + public IdleStateHandler(long readerIdleTime, long writerIdleTime, long allIdleTime,TimeUnit unit) + + observeOutput + * 是否考虑出站时较慢的情况,默认值是 false(不考虑) + * 这个字段就是用来对付 "客户端接收数据奇慢无比,慢到比空闲时间还多"的极端情况,所以,Netty 默认是关闭这个字段的 + + readerIdleTime + * 读空闲超时时间设定,如果channelRead()方法超过readerIdleTime时间未被调用则会触发超时事件调用userEventTrigger()方法 + * 0 则禁用事件 + + writerIdleTime + * 写空闲超时时间设定,如果write()方法超过writerIdleTime时间未被调用则会触发超时事件调用userEventTrigger()方法 + * 0 则禁用事件 + + allIdleTime + * 读或写空闲时间设定 + * 0 则禁用事件 + + unit + * 时间单位 + + + # 触发机制 + * 当超时发生的时候,该handler就会触发一个用户自定义的事件,会给定一个对象:IdleStateEvent + * 可以通过该对象判断是什么超时事件发生了 + * 该对象是一个枚举对象(非枚举类) + IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT + * 第一次发生了读超时, 就是很久没读取到客户的信息了 + + IdleStateEvent.READER_IDLE_STATE_EVENT + * 不是第一次发生了读超时,如果出现该状态,就说明channle目前处于连续读超时(起码2次) + + IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT + * 第一次发生了写超时, 就是很久没往客户端写数据了 + + IdleStateEvent.WRITER_IDLE_STATE_EVENT + IdleStateEvent.FIRST_ALL_IDLE_STATE_EVENT + * 第一次发生, 读写都超时了 + + IdleStateEvent.ALL_IDLE_STATE_EVENT + + # 总结 + * IdleStateHandler心跳检测主要是通过向线程任务队列中添加定时任务,判断channelRead()方法或write()方法是否调用空闲超时 + * 如果超时则触发超时事件执行自定义userEventTrigger()方法 + + * Netty通过IdleStateHandler实现最常见的心跳机制不是一种双向心跳的PING-PONG模式,而是客户端发送心跳数据包,服务端接收心跳但不回复 + * 因为如果服务端同时有上千个连接,心跳的回复需要消耗大量网络资源 + * 如果服务端一段时间内内有收到客户端的心跳数据包则认为客户端已经下线,将通道关闭避免资源的浪费 + * 在这种心跳模式下服务端可以感知客户端的存活情况,无论是宕机的正常下线还是网络问题的非正常下线,服务端都能感知到,而客户端不能感知到服务端的非正常下线 + + * 要想实现客户端感知服务端的存活情况,需要进行双向的心跳 + * Netty中的channelInactive()方法是通过Socket连接关闭时挥手数据包触发的,因此可以通过channelInactive()方法感知正常的下线情况,但是因为网络异常等非正常下线则无法感知 + + + # 还有两个控制超时的组件 + ReadTimeoutHandler + 在指定时间内没有接收到任何数据将抛出 ReadTimeoutException + + WriteTimeoutHandler + 在指定时间内有写入数据将抛出 WriteTimeoutException + +---------------------------- +心跳的建议 | +---------------------------- + # 客户端设置写超时,例如: 5s 内没有write事件,就会尝试往服务端发送一个心跳包 + # 通过这个心跳包的成功发送来检测服务端是否还存活 + + # 服务端不用响应这个心跳包,如果连接很多(数十万个),那么响应心跳将是一个非常繁重的工作 + + # 服务端可以设置读超时,例如: 5s 内没收到客户端的心跳,表示客户端可能宕机,就可以close()连接 + + +---------------------------- +客户端断线重连 | +---------------------------- + TODO diff --git "a/Netty/Netty-\345\205\245\351\227\250.java" b/Netty/netty.java similarity index 59% rename from "Netty/Netty-\345\205\245\351\227\250.java" rename to Netty/netty.java index dc46e098..52eec6e9 100644 --- "a/Netty/Netty-\345\205\245\351\227\250.java" +++ b/Netty/netty.java @@ -8,27 +8,53 @@ * RocketMQ * Dubbox - # Netty实现通信的步骤 - 1,创建两个NIO线程组,一个用于网络事件的处理(接受客户端的连接),另一个则进行网络通信的读写 - - 2,创建 ServerBootstrap 对象,配置Netty的一系列参数,例如传出数据的缓存大小等 - - 3,创建一个实际处理数据的类 ChannelInitializer ,进行初始化的准备工作.比如,设置接受传出数据的字符集,格式,以及实际处理数据的接口 - - 4,绑定端口,执行同步阻塞方法等待服务器成功启动 + # 官网地址 + https://netty.io + https://github.com/netty/netty/ + https://netty.io/4.1/api/index.html + https://github.com/netty/netty/tree/4.0/example/src/main/java/io/netty/example # 学习站点 + https://waylau.gitbooks.io/netty-4-user-guide/ + https://waylau.gitbooks.io/essential-netty-in-action/ + http://www.importnew.com/17647.html http://ifeve.com/netty-in-action/ http://ifeve.com/netty5-user-guide/ http://www.cnblogs.com/zou90512/p/3492287.html + + http://www.52im.net/thread-2043-1-1.html + # 为什么放弃Netty5 + https://github.com/netty/netty/issues/4466 + + * 使用ForkJoinPool极大的增加了复杂性,但是没有明显的性能提升 + + + ---------------------------- Netty-HelloWorld | ---------------------------- - # 服务端 - + + # Netty实现通信的步骤 + 1,创建两个NIO线程组,一个用于网络事件的处理(接受客户端的连接),另一个则进行网络通信的读写 - # 客户端 + 2,创建 ServerBootstrap 对象,配置Netty的一系列参数,例如传出数据的缓存大小等 + + 3,创建一个实际处理数据的类 ChannelInitializer ,进行初始化的准备工作.比如,设置接受传出数据的字符集,格式,以及实际处理数据的接口 + 4,绑定端口,执行同步阻塞方法等待服务器成功启动 + + + +---------------------------- +征途 | +---------------------------- + # attr + # 流量整形 + # buffer + # Promise + # 工具类 + * FastThreadLocal + * Recycler diff --git "a/Nginx/Nginx-rtmp\346\234\215\345\212\241.java" "b/Nginx/Nginx-rtmp\346\234\215\345\212\241.java" new file mode 100644 index 00000000..2deeb3ea --- /dev/null +++ "b/Nginx/Nginx-rtmp\346\234\215\345\212\241.java" @@ -0,0 +1,34 @@ +----------------------- +编译安装rtmp模块 | +----------------------- + # 下载stmp模块 + https://github.com/arut/nginx-rtmp-module + + # 下载Nginx源码 + ..略 + + # 编译 +./configure --prefix=/usr/local/nginx \ +--add-module=/opt/nginx-rtmp-module \ +--with-http_stub_status_module \ +--with-http_ssl_module \ +--with-file-aio \ +--with-http_realip_module \ +--with-http_v2_module \ +--with-stream \ +--with-stream_ssl_module \ +--with-http_slice_module \ +--with-mail \ +--with-mail_ssl_module + + make && make install + + * add-module 指向的是从git clone下来的目录 + + # 查看Nginx编译安装的模块 + nginx -V + +----------------------- +配置详解 | +---------------------- + \ No newline at end of file diff --git "a/Nginx/Nginx-\345\217\215\345\220\221\344\273\243\347\220\206.java" "b/Nginx/Nginx-\345\217\215\345\220\221\344\273\243\347\220\206.java" index 106c40f8..ef3884ae 100644 --- "a/Nginx/Nginx-\345\217\215\345\220\221\344\273\243\347\220\206.java" +++ "b/Nginx/Nginx-\345\217\215\345\220\221\344\273\243\347\220\206.java" @@ -15,23 +15,23 @@ //监听域名 server_name manager.kevinblandy.com; - //日志文件配置 - #access_log logs/kevinblandy.com.access.log main; - - //错误日志文件 - #error_log logs/kevinblandy.com.error.log; - - - //转发客户端请求的时候携带的域名 - proxy_set_header Host $host; - - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Server $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - //转发客户端真实IP(通过 request.getHeader("X-Requested-For")获取) - proxy_set_header X-Requested-For $remote_addr; - proxy_set_header REMOTE-HOST $remote_addr; + //日志文件配置 + #access_log logs/kevinblandy.com.access.log main; + + //错误日志文件 + #error_log logs/kevinblandy.com.error.log; + + + //转发客户端请求的时候携带的域名 + proxy_set_header Host $host; + + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + //转发客户端真实IP(通过 request.getHeader("X-Requested-For")获取) + proxy_set_header X-Requested-For $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; //映射 location / { @@ -70,7 +70,8 @@ server_name static.kevinblandy.com; location / { root /usr/local/static; - autoindex on; # 开启索引 + autoindex on; # 开启索引 + charset utf-8,gbk; # 解决文件名称中文乱码的问题 autoindex_exact_size on; # 显示文件大小 autoindex_localtime on; # 显示最后修改时间 } @@ -86,7 +87,84 @@ proxy_set_header Connection "upgrade"; ------------------------ -Nginx-变量 | +Nginx-域名重定向 | ------------------------ + # 把 springboot.io 和 www.springboot.io 重定向到 www.javaweb.io + server{ + listen 80; + server_name www.springboot.io springboot.io; + rewrite ^/(.*)$ http://www.javaweb.io/$1 permanent; + } +------------------------ +Nginx-https | +------------------------ + server { + listen 443; + server_name springboot.io www.springboot.io; + + ssl on; + ssl_certificate /usr/local/ssl/springboot/springboot.pem; + ssl_certificate_key /usr/local/ssl/springboot/springboot.key; + access_log logs/springboot.io.log main; + error_log logs/springboot.io.error.log; + + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Requested-For $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + location / { + proxy_pass http://127.0.0.1:1024; + proxy_connect_timeout 600; + proxy_read_timeout 600; + + } + } + + # http重定向到https + server { + listen 80; + server_name springboot.io www.springboot.io; + return 301 https://springboot.io$request_uri; + } + +------------------------ +Nginx-http2开启 | +------------------------ + # 需要依赖模块支持,必须开启https + # 核心的配置 + listen 443 ssl http2;; + + server{ + listen 443 ssl http2; + server_name *.c-lang.cn; + + ssl on; + ssl_certificate /etc/letsencrypt/live/c-lang.cn/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/c-lang.cn/privkey.pem; + + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Requested-For $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + + # websocket 非必须 + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + location / { + root html; + index index.html index.htm; + } + } \ No newline at end of file diff --git "a/Nginx/Nginx-\345\256\211\350\243\205.java" "b/Nginx/Nginx-\345\256\211\350\243\205.java" index bd1dedcd..7d6ea8bd 100644 --- "a/Nginx/Nginx-\345\256\211\350\243\205.java" +++ "b/Nginx/Nginx-\345\256\211\350\243\205.java" @@ -5,7 +5,7 @@ # 源码安装必须依赖的库 1,yum -y install gcc gcc-c++ autoconf automake - 2,yum -y install zlib zlib-devel openssl openssl-devel pcre-devel + 2,yum -y install zlib zlib-devel openssl openssl-devel pcre pcre-devel 1,官网下载源码包,上传至服务器 nginx-1.10.1.tar.gz @@ -13,7 +13,20 @@ tar zxvf nginx-1.10.1.tar.gz 3,进入源码包目录 * 指定安装目录,检查安装 - ./configure --prefix=/usr/local/nginx + +./configure --prefix=/usr/local/nginx \ +--with-http_stub_status_module \ +--with-http_ssl_module \ +--with-file-aio \ +--with-http_realip_module \ +--with-http_v2_module \ +--with-stream \ +--with-stream_ssl_module \ +--with-http_slice_module \ +--with-mail \ +--with-mail_ssl_module + + 4,确定无误后直接安装 make && make install @@ -40,6 +53,8 @@ * 解决方案,直接安装就是了 yum install zlib yum install zlib-devel + yum -y install openssl openssl + yum -y install openssl openssl-devel # zlib :Nginx提供gzip模块,需要zlib库的支持 diff --git a/OkHttp/okhttp-GET.java b/OkHttp/okhttp-GET.java index 201ef77c..e50d590b 100644 --- a/OkHttp/okhttp-GET.java +++ b/OkHttp/okhttp-GET.java @@ -1,9 +1,58 @@ --------------------- Get | --------------------- - OkHttpClient client = new OkHttpClient(); String run(String url) throws IOException { - Request request = new Request.Builder().url(url).build(); - Response response = client.newCall(request).execute(); - return response.body().string(); - } \ No newline at end of file + + OkHttpClient client = new OkHttpClient(); + + Request request = new Request.Builder() + .url(url) // url + .addHeader("foo","bar") // 消息头 + .build(); + + Response response = client.newCall(request).execute(); + + response.body().string(); + } + +--------------------- +带query param的GET | +--------------------- + OkHttpClient client = new OkHttpClient(); + + HttpUrl.Builder builder = HttpUrl.parse("http://localhost:1024/openApi/category").newBuilder(); + + // 添加一个或者多个检索参数 + builder.addQueryParameter("name","val"); + builder.addEncodedQueryParameter("age","val"); + + HttpUrl httpUrl = builder.build(); + + Request request = new Request.Builder() + .url(httpUrl) // url + .addHeader("foo","bar") // 消息头 + .build(); + + Response response = client.newCall(request).execute(); + + response.body().string(); + +--------------------- +文件下载 | +--------------------- + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder().url("http://localhost/1.mp4").build(); + Response response = client.newCall(request).execute(); + + // 从响应获取到InputStream + BufferedInputStream bufferedInputStream = new BufferedInputStream(response.body().byteStream()); + + // io到本地 + BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(new File("D:\\演讲.mp4"))); + byte[] data = new byte[1024]; + int len; + while((len = bufferedInputStream.read(data)) != -1) { + bufferedOutputStream.write(data,0,len); + bufferedOutputStream.flush(); + } + bufferedOutputStream.close(); \ No newline at end of file diff --git a/OkHttp/okhttp-POST.java b/OkHttp/okhttp-POST.java index cdebf810..95158376 100644 --- a/OkHttp/okhttp-POST.java +++ b/OkHttp/okhttp-POST.java @@ -1,11 +1,90 @@ --------------------- -POST | +POST JSON | --------------------- - public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { + + MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + + OkHttpClient client = new OkHttpClient(); + + // json请求体 RequestBody body = RequestBody.create(JSON, json); + Request request = new Request.Builder().url(url).post(body).build(); Response response = client.newCall(request).execute(); return response.body().string(); - } \ No newline at end of file + } + +--------------------- +POST FORM | +--------------------- + OkHttpClient client = new OkHttpClient(); + + // 表单请求体 + RequestBody body = new FormBody.Builder() + .add("name","KevinBlandy") // 添加表单项 + .addEncoded("charset","utf-8") // 添加编码后的表单项 + .build(); + + Request request = new Request.Builder().url("").post(body).build(); + + Response response = client.newCall(request).execute(); + response.body().string(); + +--------------------- +构建文件表单体 | +--------------------- + OkHttpClient client = new OkHttpClient(); + + // 文件表单体 + File file = new File(""); + RequestBody requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file",file.getName(), RequestBody.create(MediaType.parse("image/png"), file)) + .addFormDataPart("name", "KevinBlandy") + .build(); + + Request request = new Request.Builder().url("").post(requestBody).build(); + Response response = client.newCall(request).execute(); + response.body().string(); + + +--------------------- +复杂的文件表单体 | +--------------------- + OkHttpClient client = new OkHttpClient(); + // 文件表单体 + File file = new File(""); + + RequestBody requestBody = new MultipartBody.Builder() + + .setType(MultipartBody.FORM) //设置文件表单项 + + .addFormDataPart("file",file.getName(), RequestBody.create(MediaType.parse("image/png"), file)) // 从磁盘加载文件数据 + + .addFormDataPart("file","head.jpg", RequestBody.create(MediaType.parse("image/png"), new byte[]{})) // 从字节数组加载数据 + + .addFormDataPart("name", "KevinBlandy") // 普通的表单项 + + .addPart(MultipartBody.Part.create( // 自定义消息头的表单项 + // 额外的消息头 + Headers.of(Collections.singletonMap("foo","bar")), + // 消息体 + new FormBody.Builder() + .addEncoded("name","KevinBlandy") + .build())) + + .addPart(MultipartBody.Part.create( //自定义消息头的文件项 + // 额外的消息头 + Headers.of(Collections.singletonMap("foo","bar")), + // 消息体 + RequestBody.create(MediaType.parse("image/png"), new byte[]{}) + )) + .build(); + + Request request = new Request.Builder().url("").post(requestBody).build(); + + Response response = client.newCall(request).execute(); + + response.body().string(); + diff --git a/OkHttp/okhttp-ssl.java b/OkHttp/okhttp-ssl.java new file mode 100644 index 00000000..d4bcd6ca --- /dev/null +++ b/OkHttp/okhttp-ssl.java @@ -0,0 +1,103 @@ + +-------------------- +okhttp官方的demo代码| +-------------------- + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); + trustManagerFactory.init((KeyStore) null); //使用默认的证书 + + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("Unexpected trust managers:" + Arrays.toString(trustManagers)); + } + + X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] { trustManager }, null); + + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + + OkHttpClient client = new OkHttpClient.Builder() + .sslSocketFactory(sslSocketFactory, trustManager) + .build(); + + +-------------------- +okhttp自己实践的 | +-------------------- +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import javax.net.ssl.*; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.*; +import java.security.cert.CertificateException; + +public class HttpClient { + + // TLS/SSLv3 + private static final String PROTOCOL = "TLS"; //SSLv3 + + // JKS/PKCS12 + private static final String KEY_KEYSTORE_TYPE = "JKS"; + + private static final String SUN_X_509 = "SunX509"; + + private static SSLContext getSslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, CertificateException, IOException, KeyManagementException, IOException { + SSLContext sslContext = SSLContext.getInstance(PROTOCOL); + sslContext.init(keyManagers, trustManagers, new SecureRandom()); + return sslContext; + } + + private static KeyManager[] getKeyManagers(InputStream keystore, String password)throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException,IOException { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(SUN_X_509); + KeyStore keyStore = KeyStore.getInstance(KEY_KEYSTORE_TYPE); + keyStore.load(keystore, password.toCharArray()); + keyManagerFactory.init(keyStore, password.toCharArray()); + KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); + return keyManagers; + } + + private static TrustManager[] getTrustManagers(InputStream keystore, String password)throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(SUN_X_509); + KeyStore keyStore = KeyStore.getInstance(KEY_KEYSTORE_TYPE); + keyStore.load(keystore, password.toCharArray()); + trustManagerFactory.init(keyStore); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + return trustManagers; + } + + public static void main(String[] args)throws Exception { + + // 客户端证书的路径 + String keystorePath = "C:\\Users\\Administrator\\Desktop\\key\\client\\client.keystore"; + + // keystore的密码 + String keystorePassword = "123456"; + + KeyManager[] keyManagers = getKeyManagers(Files.newInputStream(Paths.get(keystorePath)),keystorePassword); + TrustManager[] trustManagers = getTrustManagers(Files.newInputStream(Paths.get(keystorePath)),keystorePassword); + SSLContext sslContext = getSslContext(keyManagers,trustManagers); + + OkHttpClient client = new OkHttpClient.Builder() + .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0]) + .hostnameVerifier((host,sslSession) -> { + // 校验证书域名 + return true; + }) + .build(); + + Request request = new Request.Builder() + .url("https://localhost:443") + .build(); + + Response response = client.newCall(request).execute(); + + System.out.println(response.body().string()); + } +} diff --git "a/OkHttp/okhttp-\345\205\245\351\227\250.java" "b/OkHttp/okhttp-\345\205\245\351\227\250.java" index c7331fc6..21d784a0 100644 --- "a/OkHttp/okhttp-\345\205\245\351\227\250.java" +++ "b/OkHttp/okhttp-\345\205\245\351\227\250.java" @@ -10,6 +10,48 @@ okhttp 3.7.0 + + + # 在线doc文档 + https://square.github.io/okhttp/3.x/okhttp/ + + + # 组件 + OkHttpClient + * 相当于浏览器 + + RequestBody + * 请求体 + + Request + * 一次请求 + + Response + * 一次响应 + + ResponseBody + * 响应体 + +--------------------------- +okhttp-client | +--------------------------- + # 相当于浏览器的配置 + OkHttpClient client = new OkHttpClient.Builder() + .followRedirects(false) //禁止OkHttp的重定向操作,自己处理重定向 + .followSslRedirects(false) //禁止ssl重定向(80 -> 443) + .cookieJar(new CookieJar() { //cookie的序列化与反序列化接口,需要自己实现 + @Override + public void saveFromResponse(HttpUrl url, List cookies) { + } + @Override + public List loadForRequest(HttpUrl url) { + return null; + } + }) + .build(); + + + * 它应该是以单例的形式存在于应用中 --------------------------- okhttp-Request | diff --git "a/OkHttp/okhttp-\345\274\202\346\255\245\346\211\247\350\241\214.java" "b/OkHttp/okhttp-\345\274\202\346\255\245\346\211\247\350\241\214.java" new file mode 100644 index 00000000..cdbb405a --- /dev/null +++ "b/OkHttp/okhttp-\345\274\202\346\255\245\346\211\247\350\241\214.java" @@ -0,0 +1,26 @@ +---------------------------- +异步执行 | +---------------------------- + OkHttpClient okHttpClient = new OkHttpClient(); + + Request request = new Request.Builder() + .url("http://www.qq.com") + .build(); + + Call call = okHttpClient.newCall(request); + + // 异步执行不会阻塞,设置回调 + call.enqueue(new Callback() { + // 处理异常 + @Override + public void onFailure(Call call, IOException e) { + System.out.println(e.getMessage()); + } + // 处理响应 + @Override + public void onResponse(Call call, Response response) throws IOException { + if (response.isSuccessful()) { + System.out.println(response.body().string()); + } + } + }); diff --git "a/Openoffice/oo-java\345\256\242\346\210\267\347\253\257.java" "b/Openoffice/oo-java\345\256\242\346\210\267\347\253\257.java" new file mode 100644 index 00000000..9da8865e --- /dev/null +++ "b/Openoffice/oo-java\345\256\242\346\210\267\347\253\257.java" @@ -0,0 +1,27 @@ +---------------------------- +maven | +---------------------------- + + com.artofsolving + jodconverter + 2.2.1 + + + +---------------------------- +一些document | +---------------------------- + + # doc + DocumentFormat docDocumentFormat = new DocumentFormat("Microsoft Word",DocumentFamily.TEXT,"application/msword", "doc"); + docDocumentFormat.setExportFilter(DocumentFamily.TEXT, "MS Word 97"); + + # docx + DocumentFormat docxDocumentFormat = new DocumentFormat("Microsoft Word 2007 XML", DocumentFamily.TEXT,"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx"); + + # pdf + DocumentFormat pdfDocumentFormat = new DocumentFormat("Portable Document Format", "application/pdf", "pdf"); + pdfDocumentFormat.setExportFilter(DocumentFamily.DRAWING, "draw_pdf_Export"); + pdfDocumentFormat.setExportFilter(DocumentFamily.PRESENTATION, "impress_pdf_Export"); + pdfDocumentFormat.setExportFilter(DocumentFamily.SPREADSHEET, "calc_pdf_Export"); + pdfDocumentFormat.setExportFilter(DocumentFamily.TEXT, "writer_pdf_Export"); \ No newline at end of file diff --git "a/Openoffice/oo-world\350\275\254\346\215\242\344\270\272pdf.java" "b/Openoffice/oo-world\350\275\254\346\215\242\344\270\272pdf.java" new file mode 100644 index 00000000..660a4e13 --- /dev/null +++ "b/Openoffice/oo-world\350\275\254\346\215\242\344\270\272pdf.java" @@ -0,0 +1,35 @@ + +public static void word2Pdf(InputStream src, OutputStream dsc) throws IOException { + + //打开与Openoffice的链接 + OpenOfficeConnection connection = new SocketOpenOfficeConnection("192.168.265", 8100); + connection.connect(); + + //源文件document docx 类型 + DocumentFormat docxDocumentFormat = new DocumentFormat("Microsoft Word 2007 XML", DocumentFamily.TEXT,"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx"); + + //源文件document doc 类型 + DocumentFormat docDocumentFormat = new DocumentFormat("Microsoft Word", + DocumentFamily.TEXT,"application/msword", "doc"); + docDocumentFormat.setExportFilter(DocumentFamily.TEXT, "MS Word 97"); + + + //目标文件document pdf类型 + DocumentFormat pdfDocumentFormat = new DocumentFormat("Portable Document Format", "application/pdf", "pdf"); + pdfDocumentFormat.setExportFilter(DocumentFamily.DRAWING, "draw_pdf_Export"); + pdfDocumentFormat.setExportFilter(DocumentFamily.PRESENTATION, "impress_pdf_Export"); + pdfDocumentFormat.setExportFilter(DocumentFamily.SPREADSHEET, "calc_pdf_Export"); + pdfDocumentFormat.setExportFilter(DocumentFamily.TEXT, "writer_pdf_Export"); + + + //本地服务器转换 + //DocumentConverter documentConverter = new OpenOfficeDocumentConverter(connection); + + //远程服务器转换 + StreamOpenOfficeDocumentConverter streamOpenOfficeDocumentConverter = new StreamOpenOfficeDocumentConverter(connection); + //convert有几个重载,可以接收File等参数 + streamOpenOfficeDocumentConverter.convert(src, docDocumentFormat, dsc, pdfDocumentFormat); + + // 关闭连接 + connection.disconnect(); +} \ No newline at end of file diff --git "a/Openoffice/oo-\345\256\211\350\243\205.java" "b/Openoffice/oo-\345\256\211\350\243\205.java" new file mode 100644 index 00000000..03757511 --- /dev/null +++ "b/Openoffice/oo-\345\256\211\350\243\205.java" @@ -0,0 +1,28 @@ +------------------------- +openoffice安装 | +------------------------- + # 准备安装环境 + yum -y install libXext.x86_64 + yum -y groupinstall "X Window System" + + # 下载地址:http://www.openoffice.org/download/index.html + wget https://jaist.dl.sourceforge.net/project/openofficeorg.mirror/4.1.5/binaries/zh-CN/Apache_OpenOffice_4.1.5_Linux_x86-64_install-rpm_zh-CN.tar.gz + + # 解压 + tar -zxvf Apache_OpenOffice_4.1.5_Linux_x86-64_install-rpm_zh-CN.tar.gz + + # 进入解压后的目录zh-CN/RPMS执行安装 + yum -y localinstall *.rpm + + # 进入解压后的目录zh-CN/RPMS/desktop-integration执行安装 + yum -y localinstall openoffice4.1.5-redhat-menus-4.1.5-9789.noarch.rpm + + # 安装成功后,程序会被安装到:/opt/openoffice4 + # 启动服务 + nohup soffice -headless -accept="socket,host=0.0.0.0,port=8100;urp;" -nofirststartwizard & + + # 查看服务是否启动:netstat -anop | grep 8100 + tcp 0 0 127.0.0.1:8100 0.0.0.0:* LISTEN 1115/soffice.bin off (0.00/0/0) + + #杀死进程:ps -ef | grep openoffice + kill -9 [pid] \ No newline at end of file diff --git a/Openssl/openssl-config.java b/Openssl/openssl-config.java new file mode 100644 index 00000000..51cd2e9a --- /dev/null +++ b/Openssl/openssl-config.java @@ -0,0 +1,8 @@ +[ CA_default ] +dir = /etc/pki/CA # 定义路径变量 +certs = $dir/certs # 已颁发证书的保存目录 +database = $dir/index.txt # 数据库索引文件 +new_certs_dir = $dir/newcerts # 新签署的证书保存目录 +certificate = $dir/cacert.pem # CA证书路径名 +serial = $dir/serial # 当前证书序列号 +private_key = $dir/private/cakey.pem # CA的私钥路径名 \ No newline at end of file diff --git "a/Openssl/openssl-\350\207\252\347\255\276CA.java" "b/Openssl/openssl-\350\207\252\347\255\276CA.java" new file mode 100644 index 00000000..1b8cc5f6 --- /dev/null +++ "b/Openssl/openssl-\350\207\252\347\255\276CA.java" @@ -0,0 +1,85 @@ + + # 参考 + https://blog.csdn.net/xu_0705/article/details/34435445 + + # 创建配置文件 + openssl.cnf + + + # 生成RSA私钥 + openssl genrsa -out ca_root.key 2048 + * -out 置顶输出文件,不指定的话,输出到屏幕 + * 2048 表示RSA密钥长度,默认 2048 + + # 生成证书请求 + openssl req -new -key ca_root.key -out ca_root.csr -md5 + * -key 指定RSA私钥文件 + * -out 生成的证书文件 + * -config 指定配置文件 + * -md5 使用的hash算法,默认使用的是sha1 + + * 需要交互输入 + Contry Name:cn + * 国别,用小写字母 + State Of Province Name:重庆 + * 省份 + Locality Name:重庆 + * 城市 + Organization Name:Javaweb + * 企业名称 + Organizational Unit Name:Javaweb + * 单位名称 + Common Name:javaweb.io + * 域名 + Email Address:747692844@qq.com + * 邮件地址 + + * 下列选项可以不用考虑,留空.回车跳过即可 + + A challenge password: + * 密码 + An Optional Company name: + * 可选的公司名称 + + * 直接回车将选择使用默认值,输入点"."将表示该信息项留空 + + + # 签发证书 + openssl req -x509 -key ca_root.key -in ca_root.csr -out ca1.crt -days 365 + * -x509 表示需要自签署证书 + * -key 指定RSA私钥 + * -in 证书请求文件 + * -out 签发的证书文件 + * -days 过期时间 + + * "-x509" 可以配合 "-new"或"-newkey"使用,这样的话,不需要指定请求证书文件(-in 选项) + * "-x509"选项后,"-new"或"-newkey"将表示创建一个证书文件,而不是一个证书请求文件 + * 它会在签署过程中自动创建请求证书文件,但是需要交互式输入申请者信息 + openssl req -new -x509 -key ca_root.key -out ca1.crt -days 365 + +------------------------ +签发下级证书 | +------------------------ + # 生成自己的私钥 + openssl genrsa -aes128 -out myprivate.key 2048 + + # 生成证书请求文件 + openssl req -new -key myprivate.key -out MyCaReq.csr -config D:\OpensslInstall\openssl.cnf + + # 根证书签发下级证书 + openssl x509 -req -in MyCaReq.csr -out MyCa.crt -signkey myprivate.key -CA CARoot.crt -CAkey rootca.key -CAcreateserial -days 990 + +------------------------ +其他操作 | +------------------------ + # 从证书提取出公钥 + openssl req -in ca_root.csr -pubkey -noout + * -in 指定证书文件 + * -pubkey 指定提取公钥 + * -noout 输出到屏幕 + * -out 指定公钥输出的文件 + + # 验证请求文件的数字签名,这样可以验证出证书请求文件是否被篡改过 + openssl req -verify -in ca_root.csr + -in 指定证书文件 + diff --git a/Openssl/openssl.java b/Openssl/openssl.java new file mode 100644 index 00000000..8c6705d6 --- /dev/null +++ b/Openssl/openssl.java @@ -0,0 +1,63 @@ +------------------------- +openssl | +------------------------- + # 官网 + https://www.openssl.org/ + + # 参考 + http://www.cnblogs.com/f-ck-need-u/p/7048359.html#blogopenssl + + +------------------------- +互联网数据安全传输的细节 | +------------------------- + 用户A + # 先对数据进行 Hash 运算,得到摘要 + [数据][摘要] + + # 使用RSA私钥A加密摘要,得到数字签名 + [数据][RSA(摘要) = 数字签名] + + # 使用对称加密,对所有数据进行加密 + [AES密文] = AES([数据][数字摘要]) + + # 使用RSA公钥B对AES密钥进行加密 + [AES密文][RSA(AES密钥)] + + 用户B + # 使用RSA私钥B解密AES密钥 + [AES密文][RSA(AES密钥) = AES密钥] + + # 使用AES密钥解密整个消息 + [数据][数字签名] + + # 使用RSA公钥A解密签名得到摘要 + [数据][RSA(数字签名) = 摘要] + + # 使用相同的Hash算法对数据进行Hash运算,得到摘要后对比 + +------------------------- +CA | +------------------------- + +申请CA的数字证书 + + # 申请者,提交自己的的公钥和个人信息(国家,姓名,单位等) + [公钥][个人信息] + + # CA对提交的信息进行Hash计算,得到摘要 + [摘要] = MD5([公钥][个人信息]) + + # CA使用自己的私钥加密摘要信息,生成了CA对申请者的数字签名 + [数字签名] = RSA([摘要]) + + # CA在添加一些其他的信息,最终得到数字证书 + CA的机构名称 + CA层次路径 + 证书的信息(例如有效期限) + + [数字签名][机构信息,有效期,证书路径....] + + + + \ No newline at end of file diff --git "a/Python/Demo\344\273\243\347\240\201/Demo.py" "b/Python/Demo\344\273\243\347\240\201/Demo.py" deleted file mode 100644 index e12120e4..00000000 --- "a/Python/Demo\344\273\243\347\240\201/Demo.py" +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: UTF-8 -*- -while True: - num = int(input("璇疯緭鍏ユ暟瀛 \n")) - if num <= 0: - print ("鏁版嵁涓嶈兘灏忎簬绛変簬0") - else: - for x in range(1,num + 1): - for y in range(1,x + 1): - print ('%s x %s = %s' %(x, y, x*y)) diff --git a/Python/practice/3.7/demo1.py b/Python/practice/3.7/demo1.py new file mode 100644 index 00000000..2b430964 --- /dev/null +++ b/Python/practice/3.7/demo1.py @@ -0,0 +1,16 @@ + +import dataclasses + +@dataclasses.dataclass +class User(): + # 澹版槑灞炴у悕绉颁互鍙婂睘鎬х殑绫诲瀷 + id: int + name: str + # 榛樿鍊 + join: bool = True + + def __str__(self): + return 'id=%d,name=%s,join=%s' % (self.id, self.name, self.join) + +print(User(1, 'KevinBlandy')) # id=1,name=KevinBlandy,join=True +print(User(1, 'KevinBlandy', False)) # id=1,name=KevinBlandy,join=False diff --git "a/Python/Demo\344\273\243\347\240\201/Calculator.py" b/Python/practice/Calculator.py similarity index 100% rename from "Python/Demo\344\273\243\347\240\201/Calculator.py" rename to Python/practice/Calculator.py diff --git a/Python/practice/SelectSort.py b/Python/practice/SelectSort.py new file mode 100644 index 00000000..bba29f43 --- /dev/null +++ b/Python/practice/SelectSort.py @@ -0,0 +1,31 @@ + + +def getMinIndex(arr, position): + + length = len(arr) + + min = arr[position] + minIndex = position + + position += 1 + + while(position < length): + if arr[position] < min: + min = arr[position] + minIndex = position + position += 1 + return minIndex + + +def selectSort(arr): + for i, v in enumerate(arr): + minIndex = getMinIndex(arr, i) + temp = arr[i] + arr[i] = arr[minIndex] + arr[minIndex] = temp + + +arr = [4, 5, 7, 8, 9, 6, 1] +selectSort(arr) +print(arr) + diff --git a/Python/practice/application.py b/Python/practice/application.py new file mode 100644 index 00000000..6a1d80f6 --- /dev/null +++ b/Python/practice/application.py @@ -0,0 +1,34 @@ +from urllib import request,parse +import json +import base64 +import time + +def instruction(): + req = request.Request('http://127.0.0.1:5236/instruction') + req.add_header('Content-Type', 'application/json') + with request.urlopen(req,data = bytes(json.dumps({'serial':'1Y1101','data':{'AcsRes':'1','ActIndex':'1','Time':'1','Event':'10','Note':'鍑哄悕','Name':'涔犺繎骞'}}),'UTF_8')) as rep: + pass + +def open(): + req = request.Request('http://192.168.0.101/cdor.cgi?open=0') + req.add_header('Authorization', 'Basic ' + base64.b64encode(bytes('admin:888888','iso_8859_1')).decode('iso_8859_1')) + print(req) + with request.urlopen(req) as rep: + pass + +def card(): + req = request.Request('http://127.0.0.1:5236/card') + form = parse.urlencode([ + ('Serial','0004A38FC4E5'), + ('Card','1234567890'), + ('type','0') + ]) + with request.urlopen(req,data = bytes(form,'UTF_8')) as rep: + print(rep) + +if __name__ == '__main__': + while True: + instruction() + time.sleep(5) + #open() + #card(); \ No newline at end of file diff --git a/Python/practice/datastructure/BinarySearchTree.py b/Python/practice/datastructure/BinarySearchTree.py new file mode 100644 index 00000000..0f0be8a1 --- /dev/null +++ b/Python/practice/datastructure/BinarySearchTree.py @@ -0,0 +1,174 @@ + + +class Node(): + + def __init__(self, value): + self.value = value; + self.left = None + self.right = None + + +class BinarySearchTree(): + + def __init__(self, comparator): + self.__root = None + self.__size = 0 + self.__comparator = comparator + + @property + def size(self): + return self.__size + + @property + def empty(self): + return self.__size == 0 + + def __add(self, node, value): + if(node == None): + self.__size += 1 + return Node(value) + result = self.__comparator(value, node.value) + if(result < 0): + node.left = self.__add(node.left, value) + elif(result > 0): + node.right = self.__add(node.right, value) + return node + + def add(self, value): + self.__root = self.__add(self.__root, value); + + def __contains(self, node, value): + if node == None: + return False + result = self.__comparator(value, node.value) + if(result < 0): + return self.__contains(node.left, value) + elif(result > 0): + return self.__contains(node.right, value) + return True + + def contains(self, value): + return self.__contains(self.__root, value) + + def foreach(self, consumer): + assert not self.empty, 'Empty Map' + stack = [] + stack.append(self.__root) + while len(stack) > 0: + node = stack.pop() + consumer(node.value) + if node.left != None: + stack.append(node.left) + if node.right != None: + stack.append(node.right) + + def forEach(self, consumer): + assert not self.empty, 'Empty Map' + queue = [] + queue.insert(0, self.__root); + while len(queue) > 0: + node = queue.pop() + consumer(node.value) + if node.left != None: + queue.insert(0, node.left) + if node.right != None: + queue.insert(0, node.right) + + def __min(self, node): + if node.left == None: + return node + return self.__min(node.left) + + def min(self): + assert not self.empty, 'Empty Map' + return self.__min(self.__root).value + + def __max(self, node): + if node.right == None: + return node + return self.__max(node.right) + + def max(self): + assert not self.empty, 'Empty Map' + return self.__max(self.__root).value + + def __removeMin(self, node): + if node.left == None: + rightNode = node.right + node.right = None + self.__size -= 1 + return rightNode + node.left = self.__removeMin(node.left) + return node + + def removeMin(self): + assert not self.empty, 'Empty Map' + minValue = self.min() + self.__root = self.__removeMin(self.__root) + return minValue + + def __removeMax(self, node): + if node.right == None: + leftNode = node.left + self.left = None + self.__size -= 1 + return leftNode + + node.right = self.__removeMax(node.right) + return node + + def removeMax(self): + assert not self.empty, 'Empty Map' + value = self.max() + self.__root = self.__removeMax(self.__root) + return value + + def __remove(self, node, value): + if node == None: + return node + result = self.__comparator(value, node.value) + if(result < 0): + node.left = self.__remove(node.left, value) + return node + elif(result > 0): + node.right = self.__remove(node.right, value) + return node + else: + if node.left == None: + rightNode = node.right + node.right = None + self.__size -= 1 + return rightNode + + if node.right == None: + leftNode = node.left + node.left = None + self.__size -= 1 + return leftNode + + rightMinNode = self.__min(node.right) + rightMinNode.right = self.__removeMin(node.right) + rightMinNode.left = node.left + + node.left = node.left = None + return rightMinNode + + def remove(self, value): + self.__root = self.__remove(self.__root, value) + + +def comparator(v1 , v2): + if v1 > v2: + return 1 + elif v1 < v2: + return -1 + return 0 + + +tree = BinarySearchTree(comparator) +for i in [5, 3, 6, 8, 4, 2]: + tree.add(i) + +tree.foreach(lambda x:print(x)) +print('size=%d' % (tree.size)) + diff --git a/Python/practice/datastructure/LinkedList.py b/Python/practice/datastructure/LinkedList.py new file mode 100644 index 00000000..96f46660 --- /dev/null +++ b/Python/practice/datastructure/LinkedList.py @@ -0,0 +1,77 @@ + + +class Node (object): + + def __init__(self, value=None, next=None,): + self.value = value + self.next = next + + +class LinkendList(object): + + def __init__(self): + self.__header = None + self.__size = 0 + + def add(self, value): + if self.__header == None: + self.__header = Node(value) + else: + node = self.__header + while node.next != None: + node = node.next + node.next = Node(value, None) + self.__size += 1 + + @property + def size(self): + return self.__size + + def get(self, index): + assert index > -1, "index must bigger than -1" + + node = self.__header + if node == None: + return None + + flag = 0 + while node != None: + if flag == index: + return node.value + node = node.next + flag += 1 + return None + + def index(self,value): + node = self.__header + if node == None: + return -1 + flag = 0 + while node != None: + if node.value == value: + return flag + node = node.next + flag += 1 + return -1 + + def __iter__(self): + if self.__header == None: + return None + node = self.__header + while node != None: + yield node.value + node = node.next + + +linkedList = LinkendList() +linkedList.add("Java") +linkedList.add("Python") +linkedList.add("C") +linkedList.add("Javascript") + +for i, v in enumerate(linkedList): + print(i, v) + +print(linkedList.size) +print(linkedList.get(2)) + diff --git a/Python/practice/datastructure/Queue.py b/Python/practice/datastructure/Queue.py new file mode 100644 index 00000000..5e5746f3 --- /dev/null +++ b/Python/practice/datastructure/Queue.py @@ -0,0 +1,64 @@ + + +class Node(object): + + def __init__(self, value): + self.value = value; + self.next = None + + +class Queue(object): + + def __init__(self): + self.size = 0 + self.head = None # 澶存寚閽 + self.tail = None # 灏炬寚閽 + + def equeue(self, value): + if self.head == None: + self.head = Node(value) + self.tail = self.head + else: + self.tail.next = Node(value) + self.tail = self.tail.next + self.size += 1 + + def dequeue(self): + assert self.size > 0 , 'Empty Queue' + retValue = self.head.value + self.head = self.head.next + if self.head == None: + self.tail = None + self.size -= 1 + return retValue + + def __str__(self, *args, **kwargs): + retValue = "head=[%s] ["%(self.head.value if self.head != None else None) + + node = self.head + size = 0 + while node != None: + retValue += "%s"%(node.value) + if (size + 1) != self.size: + retValue += "," + node = node.next + size += 1 + + retValue += "] tail=[%s] size=[%s]"%(self.tail.value if self.head != None else None,self.size) + return retValue + + +queue = Queue() + +print(queue) + +for i in range(100): + queue.equeue(i) + +print(queue) + +for i in range(queue.size): + print(queue.dequeue()) + +print(queue) + diff --git a/Python/practice/datastructure/Stack.py b/Python/practice/datastructure/Stack.py new file mode 100644 index 00000000..bd5e3f2d --- /dev/null +++ b/Python/practice/datastructure/Stack.py @@ -0,0 +1,24 @@ + + +# 鏁扮粍瀹炵幇 +class Stack(): + + def __init__(self): + self.__arr = [] + + def push(self, item): + self.__arr.append(item); + + def pop(self): + return self.__arr.pop() + + def peek(self): + return self.__arr[0] if len(self.__arr) > 0 else None + + @property + def size(self): + return len(self.__arr) + + @property + def empty(self): + return self.size == 0 \ No newline at end of file diff --git a/Python/practice/datastructure/__init__.py b/Python/practice/datastructure/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Python/practice/dazhong.py b/Python/practice/dazhong.py new file mode 100644 index 00000000..e3242029 --- /dev/null +++ b/Python/practice/dazhong.py @@ -0,0 +1,52 @@ +import queue +from concurrent.futures import ThreadPoolExecutor +import threading +from urllib import request +from bs4 import BeautifulSoup + +opener = request.build_opener() + +url = 'https://www.dianping.com/search/keyword/9/0_%E8%8A%B1%E5%BA%97' + +# 宸ヤ綔闃熷垪 +work_queue = queue.Queue(maxsize=100) + +# 鎬婚〉鐮 +total = None + +# 绾跨▼姹 +exector = ThreadPoolExecutor() + +def worker(item): + opener = request.build_opener() + with opener.open(item['href']) as rep: + html = BeautifulSoup(rep.read(),'html.parser') + print(html) + address = html.find_all('span',attr={'class':'item','itemprop':'street-address'}) + print(address) + +def start(): + while True: + item = work_queue.get() + exector.submit(worker,item) + +threading.Thread(target = start).start() + +with opener.open(url) as rep: + html = BeautifulSoup(rep.read(),'html.parser') + page_div = html.find('div',class_='page') + # 鑾峰彇鎬婚〉鐮 + total = int(page_div.find_all('a')[-2].string) + +for i in range(1,total + 1): + with opener.open(url + '/p' + str(i)) as rep: + div = html.find_all('div',class_='tit') + for i in div: + href = i.find('a').get('href') + name = i.find_all('h4')[0].string + work_queue.put({'href':href,'name':name}) + + + + + diff --git "a/Python/Demo\344\273\243\347\240\201/demo-\345\237\272\347\241\200-CC\346\224\273\345\207\273.py" "b/Python/practice/demo-\345\237\272\347\241\200-CC\346\224\273\345\207\273.py" similarity index 95% rename from "Python/Demo\344\273\243\347\240\201/demo-\345\237\272\347\241\200-CC\346\224\273\345\207\273.py" rename to "Python/practice/demo-\345\237\272\347\241\200-CC\346\224\273\345\207\273.py" index fa1c163f..1208dc57 100644 --- "a/Python/Demo\344\273\243\347\240\201/demo-\345\237\272\347\241\200-CC\346\224\273\345\207\273.py" +++ "b/Python/practice/demo-\345\237\272\347\241\200-CC\346\224\273\345\207\273.py" @@ -2,7 +2,7 @@ import sys from urllib import request -target_url = "http://www.javaweb.io" +target_url = "http://www.baidu.com" def do_request(url): while True: diff --git "a/Python/Demo\344\273\243\347\240\201/demo-\345\237\272\347\241\200-\346\212\242\347\245\250.py" "b/Python/practice/demo-\345\237\272\347\241\200-\346\212\242\347\245\250.py" similarity index 100% rename from "Python/Demo\344\273\243\347\240\201/demo-\345\237\272\347\241\200-\346\212\242\347\245\250.py" rename to "Python/practice/demo-\345\237\272\347\241\200-\346\212\242\347\245\250.py" diff --git "a/Python/Demo\344\273\243\347\240\201/demo-\345\237\272\347\241\200-\346\267\230\345\256\235IP\345\272\223.py" "b/Python/practice/demo-\345\237\272\347\241\200-\346\267\230\345\256\235IP\345\272\223.py" similarity index 100% rename from "Python/Demo\344\273\243\347\240\201/demo-\345\237\272\347\241\200-\346\267\230\345\256\235IP\345\272\223.py" rename to "Python/practice/demo-\345\237\272\347\241\200-\346\267\230\345\256\235IP\345\272\223.py" diff --git "a/Python/Demo\344\273\243\347\240\201/demo-\345\237\272\347\241\200-\347\214\234\346\213\263\345\260\217\346\270\270\346\210\217.py" "b/Python/practice/demo-\345\237\272\347\241\200-\347\214\234\346\213\263\345\260\217\346\270\270\346\210\217.py" similarity index 100% rename from "Python/Demo\344\273\243\347\240\201/demo-\345\237\272\347\241\200-\347\214\234\346\213\263\345\260\217\346\270\270\346\210\217.py" rename to "Python/practice/demo-\345\237\272\347\241\200-\347\214\234\346\213\263\345\260\217\346\270\270\346\210\217.py" diff --git "a/Python/Demo\344\273\243\347\240\201/demo-\345\237\272\347\241\200-\350\264\255\347\211\251\350\275\246\345\260\217\347\250\213\345\272\217.py" "b/Python/practice/demo-\345\237\272\347\241\200-\350\264\255\347\211\251\350\275\246\345\260\217\347\250\213\345\272\217.py" similarity index 100% rename from "Python/Demo\344\273\243\347\240\201/demo-\345\237\272\347\241\200-\350\264\255\347\211\251\350\275\246\345\260\217\347\250\213\345\272\217.py" rename to "Python/practice/demo-\345\237\272\347\241\200-\350\264\255\347\211\251\350\275\246\345\260\217\347\250\213\345\272\217.py" diff --git a/Python/practice/demo10.py b/Python/practice/demo10.py new file mode 100644 index 00000000..0a145cc2 --- /dev/null +++ b/Python/practice/demo10.py @@ -0,0 +1,3 @@ +def encode_hex(bins): + return ''.join([hex(i).replace('0x','').zfill(2).upper() for i in bins]) +print(encode_hex(bytes.fromhex('F1581856'))) \ No newline at end of file diff --git a/Python/practice/demo11.py b/Python/practice/demo11.py new file mode 100644 index 00000000..bbd62012 --- /dev/null +++ b/Python/practice/demo11.py @@ -0,0 +1,4 @@ +import hashlib +md5 = hashlib.md5() +md5.update(b'javaweb.io') +print(md5.hexdigest()) #0a473d1c44fbccb8f58c7401c5f04a3d \ No newline at end of file diff --git a/Python/practice/demo12.py b/Python/practice/demo12.py new file mode 100644 index 00000000..93cc5979 --- /dev/null +++ b/Python/practice/demo12.py @@ -0,0 +1,17 @@ +from urllib import request +from bs4 import BeautifulSoup + +url = 'http://tool.oschina.net/commons' + +with request.urlopen(url) as rep: + html = rep.read().decode('UTF_8') + soup = BeautifulSoup(html, 'html.parser') + trs = soup.select('table tr')[1:] + for tr in trs: + tds = tr.find_all('td') + + for i,v in enumerate(tds): + if i % 2 == 1: + suffix = tds[i - 1].string + contentType = v.string + print(suffix,contentType) diff --git a/Python/practice/demo13.py b/Python/practice/demo13.py new file mode 100644 index 00000000..c6ded468 --- /dev/null +++ b/Python/practice/demo13.py @@ -0,0 +1,11 @@ + + +def getBinary(number): + bin = None + bin = number % 2 + if number >= 2: + getBinary(number / 2) + print("%s" % ('1' if bin > 0 else '0'), end='') + + +getBinary(6) diff --git a/Python/practice/demo14.py b/Python/practice/demo14.py new file mode 100644 index 00000000..d7023989 --- /dev/null +++ b/Python/practice/demo14.py @@ -0,0 +1,18 @@ + +from urllib import request, parse +import json + +url = 'http://service.tiancity.com/GB/Block/QueryCso' + +req = request.Request(url) + +reqBody = parse.urlencode([ + ('n1', 'undefined'), + ('n2', 'ByGone涓舵枃瀛'), + ('ix', '1'), + ('sz', '20') +]); + +with request.urlopen(req, bytes(reqBody, 'UTF_8')) as rep: + result = json.loads(rep.read()); + print(result['ReturnObject']['Html']); diff --git a/Python/practice/demo15.py b/Python/practice/demo15.py new file mode 100644 index 00000000..436ca313 --- /dev/null +++ b/Python/practice/demo15.py @@ -0,0 +1,24 @@ + + +def indexedBinarySearch(collection, value): + low = 0 + high = len(collection) - 1 + while(low <= high): + mid = (low + high) // 2 + item = collection[mid] + if item > value: + high = mid - 1 + elif item < value: + low = mid + 1 + else: + return mid + return -(low + 1) + +def size(number): + size = 0 + while(number > 0): + number = number // 10 + size += 1 + return size + +print(indexedBinarySearch([5,8,9],7)) diff --git a/Python/practice/demo16.py b/Python/practice/demo16.py new file mode 100644 index 00000000..da9804c0 --- /dev/null +++ b/Python/practice/demo16.py @@ -0,0 +1,56 @@ + + +def test(str): + length = len(str) + if length % 2 != 0: + return False + + mid = length // 2 + + leftPoint = mid - 1 + rightPoint = mid + + left = [] + while leftPoint >= 0: + left.insert(0, str[leftPoint]) + leftPoint -= 1 + + right = [] + while rightPoint < length: + right.insert(0, str[rightPoint]) + rightPoint += 1 + + for i, v in enumerate(left): + if (v == '('): + if right[i] != ')': + return False + elif (v == '{'): + if right[i] != '}': + return False + elif (v == '['): + if right[i] != ']': + return False + else: + return False + return True + + +def test1(str): + arr = [] + for i in str: + if i == '[' or i == '(' or i == '{': + arr.insert(0, i); + else: + item = arr.pop(0) + if item == '[' and i != ']': + return False + elif item == '(' and i != ')': + return False + elif item == '{' and i != '}': + return False + return True if len(arr) == 0 else False + + +s = '{[([{[([{[([{[([{[([[]])]}])]}])]}])]}])]}' +print(test(s)) +print(test1(s)) diff --git a/Python/practice/demo17.py b/Python/practice/demo17.py new file mode 100644 index 00000000..66e10ce2 --- /dev/null +++ b/Python/practice/demo17.py @@ -0,0 +1,40 @@ +''' +涓冨彧鑰侀紶锛屼竴鐧剧摱鑽按锛屽叾涓湁涓鐡舵槸姣掕嵂锛屾瘨鍙戞椂闂翠负涓澶╋紝浣跨敤涓澶╂椂闂存娴嬪嚭姣掕嵂 +鎬濊矾: + 瀵100鐡舵瘨鑽繘琛屼簩杩涘埗缂栫爜锛0000001锛0000010锛...锛1100100 + 鑰侀紶鍒嗗埆涓篈,B,C,D,E,F,G + A鑰侀紶鍠濈紪鐮佹牸寮忎负1xxxxxx鐨勮嵂姘 + B鑰侀紶鍠濈紪鐮佹牸寮忎负x1xxxxx鐨勮嵂姘 + C鑰侀紶鍠濈紪鐮佹牸寮忎负xx1xxxx鐨勮嵂姘 + D鑰侀紶鍠濈紪鐮佹牸寮忎负xxx1xxx鐨勮嵂姘 + E鑰侀紶鍠濈紪鐮佹牸寮忎负xxxx1xx鐨勮嵂姘 + F鑰侀紶鍠濈紪鐮佹牸寮忎负xxxxx1x鐨勮嵂姘 + G鑰侀紶鍠濈紪鐮佹牸寮忎负xxxxxx1鐨勮嵂姘 + + 鏈鍚庢煡鐪嬭侀紶姝讳骸鎯呭喌锛屽亣濡侲鍜孎姝讳骸锛岃鏄 0000110 涓烘瘨鑽 +''' + +''' +鐡跺瓙: 1 2 3 4 5 6 +鑰侀紶: A B C D E F +姣掕嵂: 5 + +0 0 0 0 0 1 +0 0 0 0 1 0 +0 0 0 0 1 1 +0 0 0 1 0 0 +0 0 0 1 0 1 +0 0 0 1 1 0 +A B C D E F + +A() 0 +B() 0 +C() 0 +D(4,5,6) 1 +E(2,3,6) 0 +F(1,3,5) 1 + +000101 = 5 + + +''' diff --git a/Python/practice/demo2.py b/Python/practice/demo2.py new file mode 100644 index 00000000..05800817 --- /dev/null +++ b/Python/practice/demo2.py @@ -0,0 +1,8 @@ +n = 2 +m = 3 + +n = n ^ m; +m = n ^ m; +n = n ^ m; + +print(n,m) diff --git a/Python/practice/demo3.py b/Python/practice/demo3.py new file mode 100644 index 00000000..2c51f8ca --- /dev/null +++ b/Python/practice/demo3.py @@ -0,0 +1,117 @@ +import json +data = [ + {"Id":1,"ParentId":None,"Sort":0,"Name":"鑿滃崟1"}, + {"Id":2,"ParentId":1,"Sort":0,"Name":"鑿滃崟1-1"}, + {"Id":3,"ParentId":1,"Sort":0,"Name":"鑿滃崟1-2"}, + {"Id":4,"ParentId":2,"Sort":2,"Name":"鑿滃崟1-1-2"}, + {"Id":5,"ParentId":2,"Sort":1,"Name":"鑿滃崟1-1-1"}, + {"Id":6,"ParentId":None,"Sort":0,"Name":"鑿滃崟2"}, + {"Id":7,"ParentId":6,"Sort":0,"Name":"鑿滃崟2-1"}, + {"Id":8,"ParentId":6,"Sort":0,"Name":"鑿滃崟2-2"}, + {"Id":9,"ParentId":8,"Sort":2,"Name":"鑿滃崟2-2-2"}, + {"Id":10,"ParentId":8,"Sort":1,"Name":"鑿滃崟2-2-1"}, + {"Id":11,"ParentId":10,"Sort":0,"Name":"鑿滃崟2-2-1-1"}, + {"Id":12,"ParentId":10,"Sort":0,"Name":"鑿滃崟2-2-1-2"}, +] + +class Tree(object): + def __init__(self,node): + self.id = node["Id"] + self.parentId = node["ParentId"] + self.sort = node["Sort"] + self.name = node["Name"] + self.children = [] + + def sub_test(self,node): + if node["ParentId"] == self.id: + self.children.append(Tree(node)) + data.remove(node) + else: + if len(self.children) > 0: + for item in self.children: + item.sub_test(node) + + def format(self): + result = {"Id":self.id,"ParentId":self.parentId,"Sort":self.sort,"Name":self.name} + if len(self.children) > 0: + result["children"] = [] + for item in self.children: + result["children"].append(item.format()) + return result + + def __str__(self): + return json.dumps(self.format(),ensure_ascii = False) + +trees = [] + +for item in data: + if item["ParentId"] is None: + trees.append(Tree(item)) + data.remove(item) + +while len(data) > 0: + for item in data: + for tree in trees: + tree.sub_test(item) + +for tree in trees: + print(tree) + +'''JavaScript + const data = new Set([ + {"Id":1,"ParentId":null,"Sort":0,"Name":"鑿滃崟1"}, + {"Id":2,"ParentId":1,"Sort":0,"Name":"鑿滃崟1-1"}, + {"Id":3,"ParentId":1,"Sort":0,"Name":"鑿滃崟1-2"}, + {"Id":4,"ParentId":2,"Sort":2,"Name":"鑿滃崟1-1-2"}, + {"Id":5,"ParentId":2,"Sort":1,"Name":"鑿滃崟1-1-1"}, + {"Id":6,"ParentId":null,"Sort":0,"Name":"鑿滃崟2"}, + {"Id":7,"ParentId":6,"Sort":0,"Name":"鑿滃崟2-1"}, + {"Id":8,"ParentId":6,"Sort":0,"Name":"鑿滃崟2-2"}, + {"Id":9,"ParentId":8,"Sort":2,"Name":"鑿滃崟2-2-2"}, + {"Id":10,"ParentId":8,"Sort":1,"Name":"鑿滃崟2-2-1"}, + {"Id":11,"ParentId":10,"Sort":0,"Name":"鑿滃崟2-2-1-1"}, + {"Id":12,"ParentId":10,"Sort":0,"Name":"鑿滃崟2-2-1-2"}, + ]) + + class Tree{ + constructor(node){ + this.id = node.Id; + this.parentId = node.ParentId; + this.sort = node.Sort; + this.name = node.Name; + this.children = []; + } + subTest(node){ + if (node.ParentId == this.id){ + this.children.push(new Tree(node)); + data.delete(node); + }else{ + if (this.children.length > 0){ + for(let item of this.children){ + item.subTest(node) + } + } + } + } + } + + let trees = []; + + for (let item of data){ + if (item.ParentId == null){ + trees.push(new Tree(item)) + data.delete(item) + } + } + + while (data.size > 0){ + for(let item of data){ + for(tree of trees){ + tree.subTest(item) + } + } + } + + console.log(JSON.stringify(trees)); +''' + diff --git a/Python/practice/demo4.py b/Python/practice/demo4.py new file mode 100644 index 00000000..5368d70a --- /dev/null +++ b/Python/practice/demo4.py @@ -0,0 +1,15 @@ + +def hex_to_bytes(hex_str): + assert len(hex_str) % 2 == 0,"婊" + bins = bytearray() + for i,v in enumerate(hex_str): + if (i + 1) % 2 == 0: + bins.append(int(hex_str[i - 1] + hex_str[i],16)) + return bins + + +result = hex_to_bytes("FFFE") + +print(result) + +print(bytes.fromhex("FFFE")) \ No newline at end of file diff --git a/Python/practice/demo5.py b/Python/practice/demo5.py new file mode 100644 index 00000000..c2615c63 --- /dev/null +++ b/Python/practice/demo5.py @@ -0,0 +1,32 @@ +from functools import reduce +print("sum=%d"%(reduce(lambda v1,v2:v1 + v2, [i for i in range(1,int(input()) + 1)]))) +''' +package com.zfx.basics.test; +import java.util.Scanner; +public class Main { + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + int number = scanner.nextInt(); + scanner.close(); + int sum = 0; + for(int x = 1 ; x <= number ; x++) { + sum += x; + } + System.out.println("sum=" + sum); + } +} +''' +''' +#include +#include +int main(void){ + int number; + scanf("%d",&number); + int sum = 0; + for(int x = 1 ; x <= number ; x++){ + sum += x; + } + printf("sum=%d\n",sum); + return EXIT_SUCCESS; +} +''' \ No newline at end of file diff --git a/Python/practice/demo6.py b/Python/practice/demo6.py new file mode 100644 index 00000000..2273765d --- /dev/null +++ b/Python/practice/demo6.py @@ -0,0 +1,81 @@ +import random +import time + +random_numbers = [] +input_number = None + +for i in range(4): + random_numbers.append(random.randrange(10)) + +print("宸茬粡鐢熸垚鍥涗釜闅忔満鏁") + +while True: + + input_number = int(input("杈撳叆鍥涗綅鏁板瓧:\n")); + + inputs_numbers = [] + inputs_numbers.append((input_number // 1000) % 10) + inputs_numbers.append((input_number // 100) % 10) + inputs_numbers.append((input_number // 10) % 10) + inputs_numbers.append(input_number % 10) + + flag = 0 + + for i,v in enumerate(inputs_numbers): + if v > random_numbers[i]: + print("[%d] 澶т簡"%(i + 1)) + elif v < random_numbers[i]: + print("[%d] 灏忎簡"%(i + 1)) + else: + print("[%d] 瀵逛簡"%(i + 1)) + flag += 1 + + if flag == 4: + print("鍏ㄩ儴鐚滃浜,2s鍚庨鍑") + time.sleep(2) + break +'''c-lang +#include +#include +#include +#include +#include +#define SIZE 4 +int main(void){ + //闅忔満绉嶅瓙 + srand((unsigned int)time(NULL)); + int random_numbers[SIZE]; + int inputs_numbers[SIZE]; + int input_number; + //鑾峰彇鍥涗釜闅忔満鏁 + for(int x = 0 ;x < SIZE ;x ++){ + random_numbers[x] = rand() % 10; + } + printf("宸茬粡鐢熸垚鍥涗釜闅忔満鏁癨n"); + while(true){ + printf("杈撳叆鍥涗釜鏁板瓧:\n"); + scanf("%d",&input_number); + inputs_numbers[0] = (input_number / 1000) % 10; + inputs_numbers[1] = (input_number / 100) % 10; + inputs_numbers[2] = (input_number / 10) % 10; + inputs_numbers[3] = input_number % 10; + int flag = 0; + for(int x = 0 ;x < SIZE ; x++){ + if(inputs_numbers[x] > random_numbers[x]){ + printf("[%d] 浣嶅ぇ浜哱n",x+1); + }else if(inputs_numbers[x] < random_numbers[x]){ + printf("[%d] 浣嶅皬浜哱n",x+1); + }else{ + printf("[%d] 浣嶅浜哱n",x+1); + flag ++; + } + } + if(flag == 4){ + printf("鍏ㄩ儴鐚滃浜,2s鍚庨鍑篭n"); + Sleep(2000); + break; + } + } + return EXIT_SUCCESS; +} +''' \ No newline at end of file diff --git a/Python/practice/demo7.py b/Python/practice/demo7.py new file mode 100644 index 00000000..bb35a904 --- /dev/null +++ b/Python/practice/demo7.py @@ -0,0 +1,11 @@ +import os +import time +import shutil +path = "F:\\" +while True: + if os.path.exists(path): + print("U鐩樻彃鍏,寮濮媍opy") + shutil.copytree(os.path.abspath(path), "e:\\copy") + print("copy瀹屾瘯,绋嬪簭缁撴潫") + break + time.sleep(3) \ No newline at end of file diff --git a/Python/practice/demo8.py b/Python/practice/demo8.py new file mode 100644 index 00000000..c2a8099c --- /dev/null +++ b/Python/practice/demo8.py @@ -0,0 +1,23 @@ + +def binarySearch(arr,obj): + + start = 0; + end = len(arr); + + while start <= end : + mid = start + ((end - start) // 2) + if arr[mid] == obj: + return mid + elif obj > arr[mid]: + start = mid + 1 + else: + end = mid - 1 + + return -1 + +arr = [1,2,3,4,5,6,7,8,9] + + +index = binarySearch(arr, 4) + +print(index) \ No newline at end of file diff --git a/Python/practice/demo9.py b/Python/practice/demo9.py new file mode 100644 index 00000000..a37b53b2 --- /dev/null +++ b/Python/practice/demo9.py @@ -0,0 +1,17 @@ +from urllib import request,parse + +req = request.Request('http://www.96096kp.com/ecsite/control/updateSelfInformation') + +req.add_header('Host', 'www.96096kp.com') +req.add_header('User-Agent','Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0') +req.add_header('Accept','application/json, text/javascript, */*; q=0.01') +req.add_header('Accept-Language','zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2') +#req.add_header('Accept-Encoding','gzip, deflate') +req.add_header('Referer','http://www.96096kp.com/ecsite/control/userData') +req.add_header('Content-Type','application/x-www-form-urlencoded; charset=UTF-8') +req.add_header('X-Requested-With','XMLHttpRequest') +req.add_header('Cookie','ykx_wap_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjI4MjY0NSwic3ViIjoic3ViamVjdCIsIm5iZiI6MTUzODI5OTMxMSwiaXNzIjoiaXNzdWVyIiwidHlwZSI6IjEiLCJleHAiOjE1Mzg4MjQ5MTEsImlhdCI6MTUzODI5OTMxMSwianRpIjoiaWQifQ.pd792otyO3y5xWERb7Mxj0DfHYXuy1BzdiseDDxFPdQ; JSESSIONID=02A18788492DE66BBFEDE9594BE6B483.jvm1; UM_distinctid=1660e54e30c343-0be86433864ac1-143c7340-1fa400-1660e54e30d6f3; CNZZDATA1258728964=2030848198-1537836918-null%7C1538299291; Hm_lvt_7299c6dbd5750ffb2b56c44b9f4ad2ab=1537838737,1538299315; MEIQIA_VISIT_ID=1AgCDjL9caBO0Wkq89iE0pUkBU7; MEIQIA_EXTRA_TRACK_ID=1APQRzCyKrqP7syyIayPzzJidXE; ykx=20111181; Hm_lpvt_7299c6dbd5750ffb2b56c44b9f4ad2ab=1538299347') +req.add_header('Connection','keep-alive') + +with request.urlopen(req, 'userName=KevinBlandy&telphone=18523570974&idNumber=500222199312096610&partyId=2100299'.encode(encoding='utf_8', errors='strict')) as rep: + print(rep.read().decode('UTF_8')) \ No newline at end of file diff --git a/Python/practice/door/.gitignore b/Python/practice/door/.gitignore new file mode 100644 index 00000000..8eddac96 --- /dev/null +++ b/Python/practice/door/.gitignore @@ -0,0 +1,5 @@ +/door.log +/__pycache__/ +/.project +/.pydevproject +/.settings \ No newline at end of file diff --git a/Python/practice/door/Application.py b/Python/practice/door/Application.py new file mode 100644 index 00000000..f9da68e0 --- /dev/null +++ b/Python/practice/door/Application.py @@ -0,0 +1,33 @@ +''' + @author :KevinBlandy + @email :747692844@qq.com + @date :2017骞12鏈18鏃 18:07:13 +''' +import os +import sys + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +import threading + +import Logger +import UdpServer +import SimpleHttpServer +#import HttpServer +#import Config + +logger = Logger.getLogger() + +if __name__ == '__main__': + + ''' + 瀛愮嚎绋嬪惎鍔╱dp鏈嶅姟鍣 + ''' + udpServer = threading.Thread(target=UdpServer.start) + udpServer.setDaemon(True) + udpServer.start() + + logger.debug('http鏈嶅姟鍚姩.... ...'); + + #HttpServer.application.run(port=Config.httpServerPort,debug=False,host='0.0.0.0') + SimpleHttpServer.start() \ No newline at end of file diff --git a/Python/practice/door/CommonUtils.py b/Python/practice/door/CommonUtils.py new file mode 100644 index 00000000..9cd85b0d --- /dev/null +++ b/Python/practice/door/CommonUtils.py @@ -0,0 +1,99 @@ +import time + +def bytesToHex(bins): + ''' + 鎶婂瓧鑺傛暟缁勮浆鎹负16杩涘埗瀛楃涓,涓嶅甫0x鍓嶇紑 + :param bins: + :return: + ''' + return ''.join([hex(i).replace('0x','').zfill(2).upper() for i in bins]) + +def hexToBytes(hexStr): + ''' + 16杩涘埗瀛楃涓,杞崲涓哄瓧鑺傛暟缁 + 瀛楃涓蹭笉鑳藉寘鍚 0x 鍓嶇紑,浼氳嚜鍔ㄥ拷鐣ョ┖鏍 + :param hexStr: + :return: + ''' + return bytes.fromhex(hexStr) + + +def xorOperation(data): + ''' + 鏈byte鏁扮粍杩涜寮傛垨杩愮畻,杩斿洖鏂癰yte鏁扮粍,鏈鍚庝竴涓瓧鑺備负鏈缁堝紓鎴栬繍绠楃殑缁撴灉鍊 + 寮傛垨缁撴灉鍊 = data[0] ^ data[1] ^ [2] .... data[-1] + :param data: + :return: + ''' + tempValue = data[0] ^ data[1] + bins = [] + for i in range(len(data)): + bins.append(data[i]) + if i <= 1: + continue + tempValue = tempValue ^ data[i] + bins.append(tempValue) + return bytearray(bins) + +def getHexComplement(num): + ''' + 鑾峰彇鎸囧畾16杩涘埗鐨勫弽鐮,涓嶅寘鍚0x鍓嶇紑 + :param hex_num: + :return: + ''' + if type(num) == str: + num = int(num,16) + return hex(0xFF - num).replace('0x','').zfill(2).upper() + +def now(): + ''' + 鑾峰彇褰撳墠鏃堕棿瀛楃涓 + :return: + ''' + return time.strftime("%Y-%m-%d %H:%M:%S") + + +def getFloorHex(targets): + ''' + 鏍规嵁妤煎眰鑾峰彇鍗忚hex缂栫爜(8涓瓧鑺) + :param targets: + :return: + ''' + floors = [['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'] # 1 - 8妤 + ,['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'] + ,['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'] + ,['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'] + ,['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'] + ,['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'] + ,['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'] + ,['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0'], ['0']] # 57 - 64妤 + for i in set(targets): + floor = int(i) + if floor < 1: + floor = 1 + elif floor > 64: + floor = 64 + floors[floor - 1][0] = '1' + result = '' + floors.reverse() + for i in floors: + result += i[0] + return hex(int(result, 2)).replace('0x', '').zfill(16) + +def parseFloor(hexstr): + ''' + 鎶婄綉缁滃瓧鑺傛暟鎹浆鎹负妤煎眰,杩斿洖[],鍙互琛ㄧず澶氫釜妤煎眰 + :return: + ''' + result = [] + arr = list(str(bin(int(hexstr, 16))).replace('0b', '').zfill(64)) + arr.reverse() + for i,n in enumerate(arr): + if n == '1': + result.append(i + 1) + return result + +if __name__ == '__main__': + result = parseFloor('0000000000000200') + print(result) + diff --git a/Python/practice/door/Config.py b/Python/practice/door/Config.py new file mode 100644 index 00000000..6a50758c --- /dev/null +++ b/Python/practice/door/Config.py @@ -0,0 +1,63 @@ +''' + 閰嶇疆 +''' + +from configparser import ConfigParser + +import Logger +import sys +import os + +logger = Logger.getLogger() + +configparser = ConfigParser() + +configparser.read(sys.path[-1] + os.sep + 'application.properties',encoding='utf_8') + +logger.debug('鍔犺浇閰嶇疆淇℃伅') + +# http鏈嶅姟绔彛 +httpServerPort = int(configparser['global_config']['http.server.port']) + +# udp鏈嶅姟ip +if configparser.has_option('global_config','udp.server.ip'): + udpServerIp = configparser['global_config']['udp.server.ip'] +else: + udpServerIp = '' +# udp鏈嶅姟绔彛 +udpServerPort = int(configparser['global_config']['udp.server.port']) + +########################## 闂ㄧ鎺ュ彛 ########################## + +# 蹇冭烦閫氱煡鍦板潃 +doorHeartbeat = configparser['global_config']['door.heartbeat'] +# 鍒峰崱璁板綍涓婁紶鎺ュ彛 +doorRecord = configparser['global_config']['door.record'] +# 涓存椂瀵嗙爜鏍¢獙 +doorTempPassValidate = configparser['global_config']['door.tempPassValidate'] + +########################## 姊帶鎺ュ彛 ########################## +# 蹇冭烦 +elevatorHeartbeat = configparser['global_config']['elevator.heartbeat'] +# 鍒峰崱璁板綍涓婁紶 +elevatorRecord = configparser['global_config']['elevator.record'] +# 涓存椂瀵嗙爜鏍¢獙 +elevatorTempPassValidate = configparser['global_config']['elevator.tempPassValidate'] +# 瀵嗙爜璁板綍涓婁紶 +elevatorPassRecord = configparser['global_config']['elevator.passRecord'] + +logger.debug(' http鏈嶅姟绔彛:%s'%(httpServerPort)) +logger.debug(' udp鏈嶅姟ip:%s'%(udpServerIp)) +logger.debug(' udp鏈嶅姟绔彛:%s'%(udpServerPort)) +logger.debug(' 闂ㄧ-蹇冭烦閫氱煡鎺ュ彛:%s'%(doorHeartbeat)) +logger.debug(' 闂ㄧ-鍒峰崱璁板綍鎺ュ彛:%s'%(doorRecord)) +logger.debug(' 闂ㄧ-涓存椂瀵嗙爜鏍¢獙鎺ュ彛:%s'%(doorTempPassValidate)) +logger.debug('') +logger.debug(' 姊帶-蹇冭烦閫氱煡鎺ュ彛:%s'%(elevatorHeartbeat)) +logger.debug(' 姊帶-鍒峰崱璁板綍鎺ュ彛:%s'%(elevatorRecord)) +logger.debug(' 姊帶-涓存椂瀵嗙爜鏍¢獙鎺ュ彛:%s'%(elevatorTempPassValidate)) +logger.debug(' 姊帶-瀵嗙爜璁板綍涓婁紶鎺ュ彛:%s'%(elevatorPassRecord)) + + + + diff --git a/Python/practice/door/HttpClient.py b/Python/practice/door/HttpClient.py new file mode 100644 index 00000000..ea7acd35 --- /dev/null +++ b/Python/practice/door/HttpClient.py @@ -0,0 +1,23 @@ +''' + Http瀹㈡埛绔 +''' + +from urllib import request + +import json + +def doPost(url,requestBody): + client = request.Request(url) + client.add_header('Content-Type','application/json') + with request.urlopen(client,data=bytes(json.dumps(requestBody),encoding='UTF_8')) as response: + data = response.read() + if data: + return json.loads(data) + return '' + +def doGet(url): + with request.urlopen(url) as response: + responseBody = response.read() + if responseBody: + return json.loads(responseBody) + return '' diff --git a/Python/practice/door/Logger.py b/Python/practice/door/Logger.py new file mode 100644 index 00000000..40575ee1 --- /dev/null +++ b/Python/practice/door/Logger.py @@ -0,0 +1,34 @@ + +import logging +import sys +import os + +logger = None + +log_file = sys.path[-1] + os.sep +'door.log' + +def getLogger(): + + global logger + + if not logger : + + logger = logging.getLogger('root') + logger.setLevel(logging.DEBUG) + + streamHandler = logging.StreamHandler() + fileHandler = logging.FileHandler(log_file, encoding='UTF_8') + + formatter = logging.Formatter( + # [鏃堕棿][鏂囦欢-琛屽彿][LEVEL]:鏃ュ織姝f枃 [%(asctime)s][%(filename)s-%(lineno)s][%(levelname)s]:%(message)s + fmt="[%(asctime)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S") + + streamHandler.setFormatter(formatter) + fileHandler.setFormatter(formatter) + + logger.addHandler(streamHandler) + logger.addHandler(fileHandler) + + return logger + diff --git a/Python/practice/door/README.md b/Python/practice/door/README.md new file mode 100644 index 00000000..4bc07165 --- /dev/null +++ b/Python/practice/door/README.md @@ -0,0 +1 @@ +# door diff --git a/Python/practice/door/SimpleHttpServer.py b/Python/practice/door/SimpleHttpServer.py new file mode 100644 index 00000000..6bcdbd43 --- /dev/null +++ b/Python/practice/door/SimpleHttpServer.py @@ -0,0 +1,389 @@ + +from http.server import BaseHTTPRequestHandler +from http.server import HTTPServer +from socketserver import ThreadingMixIn +import json +import time + +import Config +import Logger +import UdpClient +import CommonUtils + +logger = Logger.getLogger() + +# 棰勫畾涔夊搷搴 +responseBody = { + 'success':{'success':True,'message':'ok'}, + 'methodNotAllowd':{'success':False,'message':'璇锋眰鏂瑰紡涓嶆敮鎸'}, + 'notFound':{'success':False,'message':'璇锋眰璺緞鏈壘鍒'}, + 'systemError':{'success':False,'message':'绯荤粺寮傚父'}, +} + +# content type +contentType = ('Content-type', 'application/json;charset=UTF-8') + +# 澶勭悊鍣ㄦ柟娉曟槧灏 +controllers = { + '/open':'open', + '/setting':'doorSetting', + '/card':'card', + '/delay':'delay', + '/setPass':'setPass', + '/restart':'restart', + '/setServerInfo':'setServerInfo', + # ------------- 姊帶api ------------- + '/setCards':'setCards', + '/elevatorControll':'elevatorControll', + '/timeAsync':'timeAsync', + '/setElevatorKey':'setElevatorKey', + '/setElevatorConfig':'setElevatorConfig', + '/setElevatorPass':'setElevatorPass' +} + + +class HttpHandler(BaseHTTPRequestHandler): + + def do_GET(self): + self.send_response(200, message=None) + self.send_header(*contentType) + self.end_headers() + if self.path not in controllers.keys(): + self.wfile.write(bytes(json.dumps(responseBody['notFound'], ensure_ascii=False), 'UTF_8')) + else: + self.wfile.write(bytes(json.dumps(responseBody['methodNotAllowd'], ensure_ascii=False), 'UTF_8')) + + def do_POST(self): + self.send_response(200, message=None) + self.send_header(*contentType) + self.end_headers() + + try: + if self.path not in controllers.keys(): + self.wfile.write(bytes(json.dumps(responseBody['notFound'], ensure_ascii=False), 'UTF_8')) + return + + request_obj = json.loads(self.rfile.read(int(self.headers["Content-length"])).decode(encoding='utf_8', errors='strict')) + + # 鑾峰彇澶勭悊鏂规硶鍚嶇О + handlerMethod = controllers[self.path] + + # 鑾峰彇澶勭悊鏂规硶骞朵笖鎵ц + result = getattr(self,handlerMethod)(request_obj) + + if result: + self.wfile.write(bytes(json.dumps(result),'UTF_8')) + + except KeyError as exception: + self.wfile.write(bytes(json.dumps({'success':False,'message':'JSON璇锋眰浣撴牸寮忓紓甯,灞炴 %s 鏈壘鍒'%(exception)}), 'UTF_8')) + except Exception as exception: + logger.error(exception) + self.wfile.write(bytes(json.dumps(responseBody['systemError']), 'UTF_8')) + + + def open(self,requestBody): + ''' + 鎵ц寮闂 + :param requestBody: + :return: + ''' + logger.debug('鎵ц寮闂:%s'%(requestBody)) + UdpClient.sendUdpData(bytes.fromhex(requestBody['message']), (requestBody['ip'], int(requestBody['port']))) + return responseBody['success'] + + def doorSetting(self,requestBody): + ''' + 鎵цudp闂ㄧ杩滅▼璁剧疆 + :param requestBody: + :return: + ''' + logger.debug('鎵ц杩滅▼璁剧疆:%s' % (requestBody)) + + ip = requestBody['ip'] # 鐩爣IP + port = int(requestBody['port']) # 鐩爣绔彛 + operation = int(requestBody['type']) # 鎿嶄綔绫诲瀷 + header = requestBody['header'].zfill(2) # 鏈哄彿 + param = requestBody['param'].zfill(2) # 璁剧疆鍙傛暟 + do = requestBody['do'].zfill(2) # 缁х數鍣ㄥ姩浣(operation) + + message = None + + if operation == 1: # 璁惧鍒濆鍖 03{0}0A + template = '03{0}0A' + message = template.format(header) + elif operation == 2: # 璁剧疆鏈哄彿 05FF71{0}{1} + template = '05FF71{0}{1}' + message = template.format(param, CommonUtils.getHexComplement(param)) + elif operation == 3: # 璁剧疆寮闂ㄥ欢杩 05{0}0E{1}{2} + template = '05{0}0E{1}{2}' + message = template.format(header, param, do) + elif operation == 4: # 璁剧疆鎵囧尯璇诲啓鎸囦护 09{0}11{1} + template = '09{0}11{1}' + message = template.format(header, param) + elif operation == 5: # 璁剧疆鎵囧尯鍖哄彿 05{0}05{1}{2} + template = ' 05{0}05{1}{2}' + message = template.format(header, param, CommonUtils.getHexComplement(param)) + + if not message: + return + + message = message.upper() + + logger.debug("鎸囦护娓叉煋缁撴灉:%s" % (message)) + + message = CommonUtils.xorOperation(bytes.fromhex(message)) + + logger.debug("鎸囦护杩愮畻缁撴灉:%s" % (CommonUtils.bytesToHex(message))) + + UdpClient.sendUdpData(message,(ip, port)) + return responseBody['success'] + + def card(self,requestBody): + ''' + 鍗$墖鍚屾鎵ц + :param requestBody: + :return: + ''' + logger.debug('鎵ц鍗$墖鍚屾:%s'%(requestBody)) + ip = requestBody['ip'] + port = int(requestBody['port']) + serial = requestBody['serial'] + cards = requestBody['cards'] + if cards: + for card in cards: + cardNum = card['num'] + operation = card['operation'] + message = serial + if operation == 'create': + message += '02' + elif operation == 'delete': + message += '03' + else: + continue + cardNum = cardNum.zfill(10) + message += cardNum + logger.debug('鍚屾鍗$墖淇℃伅(%s):%s'%('娣诲姞' if operation == 'create' else '鍒犻櫎',message)) + message = CommonUtils.xorOperation(bytes.fromhex(message)) + # 鍙戦乽dp鍖呭埌璁惧 + UdpClient.sendUdpData(message,(ip,port)) + return responseBody['success'] + + def delay(self,requestBody): + ''' + 璁剧疆寮闂ㄥ欢杩 + :param requestBody: + :return: + ''' + logger.debug('璁剧疆寮闂ㄥ欢杩:%s'%(requestBody)) + ip = requestBody['ip'] + port = int(requestBody['port']) + serial = requestBody['serial'] + delay = requestBody['delay'].zfill(2) + message = serial + '09' + delay + message = CommonUtils.xorOperation(bytes.fromhex(message)) + logger.debug('寤惰繜璁剧疆鎸囦护:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message,(ip,port)) + return responseBody['success'] + + def setPass(self,requestBody): + ''' + 璁剧疆鍥哄畾瀵嗙爜 + :param requestBody: + :return: + ''' + logger.debug('璁剧疆鍥哄畾瀵嗙爜:%s'%(requestBody)) + + ip = requestBody['ip'] + port = int(requestBody['port']) + serial = requestBody['serial'] + pass_ = requestBody['pass'] + + message = serial + '0B' + pass_ + message = CommonUtils.xorOperation(bytes.fromhex(message)) + + logger.debug('鍥哄畾瀵嗙爜璁剧疆鎸囦护:%s' % (CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message, (ip, port)) + return responseBody['success'] + + def restart(self,requestBody): + ''' + 闂ㄧ閲嶅惎 + :param requestBody: + :return: + ''' + logger.debug('閲嶅惎闂ㄧ:%s'%(requestBody)) + for door in requestBody: + serial = door['serial'] + ip = door['ip'] + port = int(door['port']) + + reverseSerial = list(serial) + reverseSerial.reverse() + + message = CommonUtils.xorOperation(bytes.fromhex(serial + '0C' + ''.join(reverseSerial))) + + logger.debug('閲嶅惎鎸囦护:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message,(ip,port)) + return responseBody['success'] + + def setServerInfo(self,requestBody): + ''' + 璁剧疆璁惧鐨勭洰鏍囨湇鍔″櫒ip鍜岀鍙 + :param requestBody: + :return: + ''' + logger.debug('璁剧疆鏈嶅姟鍣ㄤ俊鎭:%s'%(requestBody)) + + ip = requestBody['ip'] + port = int(requestBody['port']) + serverIp = requestBody['serverInfo']['ip'] + # 绔彛鎸夌収鍗忚鏉ヨ,鏈澶ч暱搴︿负4,涔熷氨鏄绔彛鍙峰彧鑳芥槸 0 - 9999 + serverPort = requestBody['serverInfo']['port'] + # 鍘婚櫎'.',ip姣忎竴娈靛繀椤绘槸3浣嶉暱搴,涓嶈冻琛0 + serverIp = ''.join(list(map(lambda x: x.strip().zfill(3), serverIp.split('.')))) + message = requestBody['mac'] + '0D' + serverIp + serverPort.zfill(4) + message = CommonUtils.xorOperation(bytes.fromhex(message)) + logger.debug('鎵ц鏈嶅姟鍣ㄤ俊鎭缃:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message, (ip, port)) + + + ################### 姊帶鎺ュ彛 ######################## + + ''' + ''' + def setCards(self,requestBody): + ''' + 鐢垫涓嬪彂/鍒犻櫎鍗″彿 + :param requestBody: + :return: + ''' + serial = requestBody['serial'] + ip = requestBody['ip'] + port = int(requestBody['port']) + cards = requestBody['cards'] + if cards: + for card in cards: + num = card['num'].zfill(10) + operation = card['operation'] + floors = card.get('floors',[]) + + message = serial + + if operation == "create": + # 涓嬪彂鍗 + message += '12' + num + CommonUtils.getFloorHex(floors) + else: + # 鍒犻櫎鍗 + message += '13' + num + message = CommonUtils.xorOperation(bytes.fromhex(message)) + logger.debug('姊帶鍗″彿涓嬪彂/鍒犻櫎:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message,(ip,port)) + + def timeAsync(self,requestBody): + ''' + 涓诲姩鏃堕棿鍚屾 + :param requestBody: + :return: + ''' + serial = requestBody['serial'] + ip = requestBody['ip'] + port = int(requestBody['port']) + + message = serial + '14' + time.strftime("%y%m%d%H%M%S") + message = CommonUtils.xorOperation(bytes.fromhex(message)) + logger.debug('姊帶鏃堕棿鍚屾:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message, (ip, port)) + + def elevatorControll(self,requestBody): + ''' + 鐢垫鎺у埗 + :return: + ''' + serial = requestBody['serial'] + ip = requestBody['ip'] + port = int(requestBody['port']) + floors = requestBody['floors'] + + message = serial + '10' + CommonUtils.getFloorHex(floors) + message = CommonUtils.xorOperation(bytes.fromhex(message)) + + logger.debug('鎵ц鐢垫鎺у埗:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message,(ip,port)) + + def setElevatorKey(self,requestBody): + ''' + 涓嬪彂瀵嗛挜 瀵嗛挜鍦ㄥ績璺虫暟鎹噷闈㈠凡缁忓寘鍚,璇ユ帴鍙d笉鍋氬疄鐜 + :param requestBody: + :return: + ''' + serial = requestBody['serial'] + ip = requestBody['ip'] + port = int(requestBody['port']) + key = requestBody['key'] + message = serial + '16' + key + message = CommonUtils.xorOperation(bytes.fromhex(message)) + logger.debug('璁剧疆姊帶瀵嗛挜:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message,(ip,port)) + + + def setElevatorCardFormat(self,requestBody): + ''' + 涓嬪彂鍗″彿杈撳嚭鏍煎紡 鍦ㄥ績璺抽噷闈㈠寘鍚湁,璇ユ帴鍙d笉鍋氬疄鐜 + :param requestBody: + :return: + ''' + serial = requestBody['serial'] + ip = requestBody['ip'] + port = int(requestBody['port']) + cardFormat = requestBody['cardFormat'] + message = serial + '18' + cardFormat + message = CommonUtils.xorOperation(bytes.fromhex(message)) + logger.debug('璁剧疆姊帶鍗″彿杈撳嚭鏍煎紡:%s' % (CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message, (ip, port)) + + def setElevatorConfig(self,requestBody): + ''' + 璁剧疆鐢垫鍙傛暟鏍煎紡 + :param requestBody: + :return: + ''' + serial = requestBody['serial'] + ip = requestBody['ip'] + port = int(requestBody['port']) + + delay = requestBody['delay'] .zfill(2) # 寮闂ㄥ欢杩 + switch = requestBody['switch'].zfill(2) # 琛岀▼寮鍏冲彿 + + message = serial + '19' + delay + switch + message = CommonUtils.xorOperation(bytes.fromhex(message)) + logger.debug('璁剧疆鐢垫鍙傛暟鏍煎紡:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message, (ip, port)) + + def setElevatorPass(self,requestBody): + ''' + 璁剧疆鐢垫鍥哄畾瀵嗙爜 + :param requestBody: + :return: + ''' + serial = requestBody['serial'] + ip = requestBody['ip'] + port = int(requestBody['port']) + _pass = requestBody['pass'] + + message = serial + '1B' + _pass + message = CommonUtils.xorOperation(bytes.fromhex(message)) + logger.debug('璁剧疆鐢垫鍥哄畾瀵嗙爜:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message,(ip,port)) + +class ThreadingHttpServer(ThreadingMixIn, HTTPServer): + pass + + +server = ThreadingHttpServer(('0.0.0.0', Config.httpServerPort), HttpHandler) + +def start(): + ''' + 鍚姩鏈嶅姟鍣 + :return: + ''' + server.serve_forever() \ No newline at end of file diff --git a/Python/practice/door/Test.py b/Python/practice/door/Test.py new file mode 100644 index 00000000..e896f2aa --- /dev/null +++ b/Python/practice/door/Test.py @@ -0,0 +1,22 @@ +import socket +#client = socket.socket(type=socket.SOCK_DGRAM) +#client.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) +#client.bind(('0.0.0.0',3025)) +#client.sendto(bytes.fromhex('515148475485 1A 4879 FF'),('127.0.0.1',3025)) + + +# client = socket.socket() +# client.connect(('127.0.0.1',1024)) +# client.send(bytes("鏈夌偣鎰忔",'UTF-8')) +# data = client.recv(1024) +# print(data) + +# testStr = ''' +# ndiqndqw鍗曚綅绛捐澶ф棗缃$1鐨勩伄濮斿眻@#^$#^4451 +# ''' +# s = re.sub(r'[\W\u4e00-\u9fa5]',lambda s:"",testStr) +# print(s) +ip = '8.8.8.8' +ip = ''.join(list(map(lambda x:x.strip().zfill(3),ip.split('.')))) +print(ip) + diff --git a/Python/practice/door/UdpClient.py b/Python/practice/door/UdpClient.py new file mode 100644 index 00000000..39638761 --- /dev/null +++ b/Python/practice/door/UdpClient.py @@ -0,0 +1,13 @@ + +import socket +import Config + +''' + 鍙戦乁DP鏁版嵁鍖 +''' +def sendUdpData(data,_client): + client = socket.socket(type=socket.SOCK_DGRAM) + client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) + # 鎸囦护鏁版嵁鍖呭繀椤讳粠udp鐩戝惉绔彛鍙戝嚭 + client.bind(('0.0.0.0', Config.udpServerPort)) + client.sendto(data,_client) \ No newline at end of file diff --git a/Python/practice/door/UdpServer.py b/Python/practice/door/UdpServer.py new file mode 100644 index 00000000..801b504d --- /dev/null +++ b/Python/practice/door/UdpServer.py @@ -0,0 +1,395 @@ + +''' + UDP鏈嶅姟 +''' + +import socketserver +import Config +import Logger +import CommonUtils +import HttpClient +import UdpClient +import time + +from urllib import request + +server = None + +logger = Logger.getLogger() + +''' + udp鏈嶅姟澶勭悊 +''' +class Handler(socketserver.BaseRequestHandler): + + def handle(self): + try: + data = self.request[0] + + client = self.client_address + + if len(data) == 6: + # 鏃х増鏈棬绂佸績璺 + self.heartbeat(data,client) + elif len(data) > 6: + + operation = data[6] + + # 蹇界暐鏈鍚庝綅鏍¢獙瀛楄妭 + data = data[0:-1] + + ############## 闂ㄧ鎸囦护寮濮 ############### + + if operation == 0x01: + self.uploadRecord(data,client) + elif operation == 0x04: + # 鏃堕棿鍚屾(蹇冭烦鍖呴噷鏈,蹇界暐) + pass + elif operation == 0x05: + self.tempPassValidate(data,client) + elif operation == 0x06: + pass + elif operation == 0x07: + self.heartbeatWithStatus(data,client) + elif operation == 0x08: + pass + elif operation == 0x09: + logger.debug('寮闂ㄥ欢杩熻缃粨鏋:%s'%(CommonUtils.bytesToHex(data))) + elif operation == 0x0A: + self.uploadPassRecord(data,client) + elif operation == 0x0B: + logger.debug('鍥哄畾瀵嗙爜璁剧疆缁撴灉:%s'%(CommonUtils.bytesToHex(data))) + elif operation == 0x0C: + logger.debug('闂ㄧ閲嶅惎缁撴灉:%s'%(CommonUtils.bytesToHex(data))) + + ############## 姊帶鎸囦护寮濮 ############### + + elif operation == 0x17: + self.elevatorHeartBeat(data,client) + elif operation == 0x10: + logger.debug('姊帶杩滅▼鎺у埗缁撴灉:%s'%(CommonUtils.bytesToHex(data))) + elif operation == 0x11: + self.elevatorCardUpload(data,client) + elif operation == 0x12: + logger.debug('姊帶涓嬪彂鐢垫鍗″彿缁撴灉:%s'%(CommonUtils.bytesToHex(data))) + elif operation == 0x13: + logger.debug('姊帶鍒犻櫎鐢垫鍗″彿缁撴灉:%s'%(CommonUtils.bytesToHex(data))) + elif operation == 0x14: + logger.debug('姊帶鍚屾鏃堕棿璁剧疆缁撴灉:%s'%(CommonUtils.bytesToHex(data))) + elif operation == 0x15: + self.elevatorTempPassValidate(data,client) + elif operation == 0x16: + logger.debug('姊帶瀵嗛挜涓嬪彂缁撴灉:%s'%(CommonUtils.bytesToHex(data))) + elif operation == 0x18: + logger.debug('姊帶璇诲崱鍣ㄥ崱鍙锋牸寮忎笅鍙戠粨鏋:%s'%(CommonUtils.bytesToHex(data))) + elif operation == 0x19: + logger.debug('姊帶鍙傛暟璁剧疆缁撴灉:%s'%(CommonUtils.bytesToHex(data))) + elif operation == 0x1A: + self.uploadElevatorPass(data,client); + elif operation == 0x1B: + logger.debug('姊帶鍥哄畾瀵嗙爜璁剧疆缁撴灉:%s'%(CommonUtils.bytesToHex(data))) + elif operation == 0x0D: + logger.debug('璁剧疆鏈嶅姟鍣ㄤ俊鎭粨鏋:%s'%(CommonUtils.bytesToHex(data))) + else: + logger.error('鏈瘑鍒寚浠:data=%s,client=%s'%(CommonUtils.bytesToHex(data),client)) + else: + logger.error('闈炴硶鏁版嵁:data=%s,client=%s'%(CommonUtils.bytesToHex(data),client)) + except Exception as exception: + logger.error('寮傚父:%s'%(exception)) + + def heartbeat(self,data,client): + ''' + 鏅氬績璺 + :param data: + :param client: + :return: + ''' + requestBody = { + 'sourceIp':client[0], # 闂ㄧip + 'sourcePort':client[1], # 闂ㄧ绔彛 + 'mac':CommonUtils.bytesToHex(data), # 搴忓垪鍙 + 'targetIp':Config.udpServerIp, # 鐩爣鏈嶅姟鍣╥p(褰撳墠鏈嶅姟鍣╥p) + 'targetPort':Config.udpServerPort, # 鐩爣鏈嶅姟鍣ㄧ鍙 + 'date':CommonUtils.now(), # 蹇冭烦鏃堕棿 + } + logger.debug('蹇冭烦娉ㄥ唽:%s'%(requestBody)) + HttpClient.doPost(Config.doorHeartbeat,requestBody) + + def uploadRecord(self,data,client): + ''' + 鍒峰崱璁板綍涓婁紶 + :param data: + :param client: + :return: + ''' + requestBody = { + 'serial':CommonUtils.bytesToHex(data[0:6]), + 'carNum':CommonUtils.bytesToHex(data[7:12]), + 'dateTime':CommonUtils.bytesToHex(data[12:]) + } + + logger.debug('涓婁紶鍒峰崱璁板綍:%s'%(requestBody)) + + response = HttpClient.doGet(Config.doorRecord + '?type=1&mac=' + requestBody['serial'] + '&cardNum=' + requestBody['carNum']) + + logger.debug('鍒峰崱璁板綍涓婁紶缁撴灉:%s'%(response)) + + data = requestBody['serial'] + '01' + requestBody['carNum'] + + if response['success']: + data += '4F4B' + else: + data += '4445' + + data = CommonUtils.xorOperation(bytes.fromhex(data)) + + logger.debug('鍝嶅簲UDP闂ㄧ:hex=%s'%(CommonUtils.bytesToHex(data))) + + UdpClient.sendUdpData(data,client) + + + def tempPassValidate(self,data,client): + ''' + 涓存椂瀵嗙爜鏍¢獙 + :param data: + :param client: + :return: + ''' + requestBody = { + 'serial':CommonUtils.bytesToHex(data[0:6]), + 'pass':CommonUtils.bytesToHex(data[7:9]), + } + + logger.debug('涓存椂瀵嗙爜鏍¢獙:%s',requestBody) + + + #response = HttpClient.doGet(Config.doorTempPassValidate + '?mac=' + requestBody['serial'] + '&code=' + requestBody['pass']) + with request.urlopen(Config.doorTempPassValidate + '?mac=' + requestBody['serial'] + '&code=' + requestBody['pass']) as response: + ''' + 鍝嶅簲闈炴爣鍑咼SON + ''' + response = str(response.read(),'UTF_8') + + logger.debug('涓存椂瀵嗙爜鏍¢獙缁撴灉:%s'%(response)) + + data = requestBody['serial'] + '05' + + if response: + data += '4F4B' + # 鏈嶅姟绔湁鍝嶅簲浠讳綍鏁版嵁,鍒欐牎楠屾垚鍔,杩涜瀵嗙爜璁板綍寮闂ㄤ笂浼 + response = HttpClient.doGet(Config.doorRecord + '?type=2&mac=' + requestBody['serial'] + '&cardNum=' + requestBody['pass']) + logger.debug('瀵嗙爜寮闂ㄨ褰曚笂浼犵粨鏋:%s' % (response)) + else: + data += '4445' + + + data = CommonUtils.xorOperation(bytes.fromhex(data)) + + logger.debug('涓存椂瀵嗙爜鏍¢獙鍝嶅簲:hex=%s'%(CommonUtils.bytesToHex(data))) + + UdpClient.sendUdpData(data,client) + + def heartbeatWithStatus(self,data,client): + ''' + 甯︽湁鐘舵佺殑蹇冭烦鍖 + :param data: + :param client: + :return: + ''' + requestBody = { + 'sourceIp':client[0], + 'sourcePort':client[1], + 'mac':CommonUtils.bytesToHex(data[0:6]), + 'targetIp':'' if not Config.udpServerIp else Config.udpServerIp, + 'targetPort':Config.udpServerPort, + 'status':CommonUtils.bytesToHex(data[7:8]), + 'date':CommonUtils.now(), + } + + logger.debug('鐘舵佸績璺:%s'%(requestBody)) + + response = HttpClient.doPost(Config.doorHeartbeat,requestBody) + + logger.debug('鐘舵佸搷搴:%s'%(response)) + + if not response['success']: + logger.error('鏈嶅姟鍣ㄥ紓甯:%s'%(response['message'])) + return + + if 'data' not in response or not response['data']: + logger.error('闂ㄧ娌℃湁瀵嗛挜鍜屽崱鐗囪緭鍑烘牸寮:serital=%s'%(requestBody['mac'])) + return + + if 'key' not in response['data']: + logger.error('闂ㄧ娌℃湁瀵嗛挜淇℃伅:serital=%s'%(requestBody['mac'])) + return + + key = response['data']['key'] + + if 'cardOutFormat' not in response['data']: + cardFormat = '04' + else: + cardFormat = response['data']['cardOutFormat'].zfill(2) + + responseHex = requestBody['mac'] + '07' + key + time.strftime("%y%m%d%H%M%S") + cardFormat + + data = CommonUtils.xorOperation(bytes.fromhex(responseHex)) + + logger.debug('鐘舵佸搷搴斿寘:hex=%s'%(CommonUtils.bytesToHex(data))) + + UdpClient.sendUdpData(data,client) + + def uploadPassRecord(self,data,client): + ''' + 瀵嗙爜寮闂ㄨ褰曚笂浼 + :param data: + :param client: + :return: + ''' + requestBody = { + 'serial': CommonUtils.bytesToHex(data[0:6]), + 'pass': CommonUtils.bytesToHex(data[7:9]), + } + + logger.debug('涓婁紶瀵嗙爜寮闂ㄨ褰:%s'%(requestBody)) + + response = HttpClient.doGet(Config.doorRecord + '?type=2&mac=' + requestBody['serial'] + '&cardNum=' + requestBody['pass']) + + logger.debug('瀵嗙爜寮闂ㄨ褰曚笂浼犵粨鏋:%s' % (response)) + + data = requestBody['serial'] + '0A4F4B' + + data = CommonUtils.xorOperation(bytes.fromhex(data)) + + logger.debug('鍝嶅簲UDP闂ㄧ:hex=%s' % (CommonUtils.bytesToHex(data))) + + UdpClient.sendUdpData(data, client) + + + ##################### 姊帶api ######################## + + def elevatorHeartBeat(self,data,client): + ''' + 姊帶蹇冭烦 + :return: + ''' + requestBody = { + 'sourceIp': client[0], + 'sourcePort': client[1], + 'mac': CommonUtils.bytesToHex(data[0:6]), + 'targetIp': '' if not Config.udpServerIp else Config.udpServerIp, + 'targetPort': Config.udpServerPort, + 'date': CommonUtils.now(), + } + logger.debug("姊帶蹇冭烦:%s"%(requestBody)) + response = HttpClient.doPost(Config.elevatorHeartbeat,requestBody) + logger.debug('姊帶蹇冭烦缁撴灉:%s'%(response)) + if response['success']: + cardFormat = response['data']['cardFormat'] + key = response['data']['key'] + if not key: + logger.error('娌℃湁瀵嗛挜') + return + if not cardFormat: + cardFormat = '04' + + message = requestBody['mac'] + '17' + key + time.strftime("%y%m%d%H%M%S") + cardFormat; + message = CommonUtils.xorOperation(bytes.fromhex(message)) + logger.debug('姊帶蹇冭烦鍝嶅簲:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message,client) + + def elevatorCardUpload(self,data,client): + ''' + 姊帶鍒峰崱璁板綍涓婁紶 + :param data: + :param client: + :return: + ''' + requestBody = { + 'mac': CommonUtils.bytesToHex(data[0:6]), + 'carNum': CommonUtils.bytesToHex(data[7:12]), + 'dateTime': CommonUtils.bytesToHex(data[12:]) + } + logger.debug('姊帶鍗″彿涓婁紶:%s'%(requestBody)) + # 鎻愪氦璁板綍鍒颁簯骞冲彴,杩斿洖璇ュ崱鍙互浣跨敤鐨勬ゼ灞 + response = HttpClient.doPost(Config.elevatorRecord, requestBody) + logger.debug('姊帶鍗″彿涓婁紶缁撴灉:%s'%(response)) + + message = None + + if response['success']: + message = requestBody['mac'] + '11' + requestBody['carNum'] + CommonUtils.getFloorHex(response['data']) + else: + message = requestBody['mac'] + '11' + requestBody['carNum'] + '4445' + + message = CommonUtils.xorOperation(bytes.fromhex(message)) + + logger.debug('姊帶鍗″彿涓婁紶鍝嶅簲:%s' % (CommonUtils.bytesToHex(message))) + + UdpClient.sendUdpData(message, client) + + + def elevatorTempPassValidate(self,data,client): + ''' + 姊帶涓存椂瀵嗙爜鏍¢獙 + :param data: + :param celint: + :return: + ''' + requestBody = { + 'serial':CommonUtils.bytesToHex(data[0:6]), + 'pass':CommonUtils.bytesToHex(data[7:9]), + } + logger.debug('姊帶涓存椂瀵嗙爜鏍¢獙:%s'%(requestBody)) + response = HttpClient.doPost(Config.elevatorTempPassValidate,requestBody) + logger.debug('姊帶涓存椂瀵嗙爜鏍¢獙缁撴灉:%s'%(response)) + if response['success']: + floors = response['data'] + message = None + if floors: + # 瀛樺湪涓涓垨鑰呭涓ゼ灞傛潈闄 + message = requestBody['serial'] + '15' + CommonUtils.getFloorHex(floors) + '4F4B' + else: + # 鏍¢獙澶辫触,涓嶅瓨鍦ㄦゼ灞傛潈闄 + message = requestBody['serial'] + '15' + '4445' + message = CommonUtils.xorOperation(bytes.fromhex(message)) + logger.debug('姊帶涓存椂瀵嗙爜鍝嶅簲:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message,client) + + def uploadElevatorPass(self,data,client): + ''' + 姊帶瀵嗙爜璁板綍涓婁紶 + :param data: + :param client: + :return: + ''' + requestBody = { + 'mac': CommonUtils.bytesToHex(data[0:6]), + 'pass': CommonUtils.bytesToHex(data[7:12]), + } + logger.debug('姊帶瀵嗙爜璁板綍涓婁紶:%s' % (requestBody)) + response = HttpClient.doPost(Config.elevatorPassRecord,requestBody) + logger.debug('姊帶瀵嗙爜璁板綍涓婁紶缁撴灉:%s'%(response)) + if response['success']: + message = requestBody['mac'] + '1A4F4B' + message = CommonUtils.xorOperation(bytes.fromhex(message)) + logger.debug('姊帶瀵嗙爜璁板綍涓婁紶鍝嶅簲:%s'%(CommonUtils.bytesToHex(message))) + UdpClient.sendUdpData(message,client) + +def start(): + ''' + 鍚姩udp鏈嶅姟 + :return: + ''' + global server + socketserver.UDPServer.allow_reuse_address = True + server = socketserver.ThreadingUDPServer(('0.0.0.0', Config.udpServerPort), Handler) + logger.debug('udp鏈嶅姟鍚姩... ...') + server.serve_forever() + +def close(): + ''' + 鍏抽棴udp鏈嶅姟 + :return: + ''' + pass \ No newline at end of file diff --git a/Python/practice/door/application.properties b/Python/practice/door/application.properties new file mode 100644 index 00000000..7bc87083 --- /dev/null +++ b/Python/practice/door/application.properties @@ -0,0 +1,32 @@ + +[global_config] + +# http鏈嶅姟绔彛 +http.server.port=5236 + +# udp鏈嶅姟ip,闈炵壒娈婃儏鍐(浠g悊),涓嶅缓璁厤缃,鐢辨湇鍔″櫒鑷繁鑾峰彇ip鍦板潃淇℃伅 +#udp.server.ip= + +# udp鏈嶅姟绔彛 +udp.server.port=3025 + + +########################## 闂ㄧ鎺ュ彛 ########################## + +# 蹇冭烦閫氱煡 +door.heartbeat=http://localhost/community/api/door/heartbeat +# 涓婁紶鍒峰崱璁板綍 +door.record=http://localhost/community/api/door/swipe +# 涓存椂瀵嗙爜鏍¢獙 +door.tempPassValidate=http://localhost/community/api/door/tempkey + +########################## 姊帶鎺ュ彛 ########################## + +# 蹇冭烦 +elevator.heartbeat=http://localhost/community/api/elevator/heartbeat +# 涓婁紶鍒峰崱璁板綍 +elevator.record=http://localhost/community/api/elevator/swipe +# 涓存椂瀵嗙爜鏍¢獙 +elevator.tempPassValidate=http://localhost/community/api/elevator/tempkey +# 瀵嗙爜璁板綍 +elevator.passRecord=http://localhost/community/api/elevator/passRecord \ No newline at end of file diff --git a/Python/practice/door/start.sh b/Python/practice/door/start.sh new file mode 100644 index 00000000..e0f85f8b --- /dev/null +++ b/Python/practice/door/start.sh @@ -0,0 +1 @@ +nohup python3 Application.py > /dev/null 2>&1 & \ No newline at end of file diff --git a/Python/practice/downloader.py b/Python/practice/downloader.py new file mode 100644 index 00000000..70e9ef81 --- /dev/null +++ b/Python/practice/downloader.py @@ -0,0 +1,51 @@ +from urllib import request +import pymysql +import os +import socket + +socket.setdefaulttimeout(20) + +dir = 'd:' + os.sep + 'video' + +if not os.path.exists(dir): + os.mkdir(dir) + +db_config = { + 'host':'localhost', + 'port':3306, + 'user':'root', + 'passwd':'root', + 'db':'7mav001', + 'charset':'utf8mb4', + 'cursorclass':pymysql.cursors.DictCursor +} + +connection = pymysql.connect(**db_config) +cursor = connection.cursor() +cursor.execute('SELECT COUNT(*) AS `count` FROM `7mav001`;') +count = cursor.fetchone()['count'] + +def down_callback(block_number,read_size,total_file_size): + per=100.0 * block_number * read_size / total_file_size + if per > 100: + per = 100 + print('宸茬粡涓嬭浇:%.2f%%' % per) + +for i in range(count): + cursor.execute('SELECT * FROM `7mav001` LIMIT %s , 1',(i,)) + result = cursor.fetchone() + # id + id = result['id'] + # 鏍囬 + title = result['title'] + '.mp4' + # 瑙嗛鍦板潃 + src = result['src'] + if not os.path.exists(dir + os.sep + title): + try: + request.urlretrieve(src, dir + os.sep + title, down_callback) + except Exception: + print("寮傚父%s"%(Exception)) + continue + + + diff --git a/Python/practice/encryption.py b/Python/practice/encryption.py new file mode 100644 index 00000000..093f98f3 --- /dev/null +++ b/Python/practice/encryption.py @@ -0,0 +1,30 @@ +import rsa + +(publicKey,privateKey) = rsa.newkeys(1024) + +rsa.key +# #rsa鍔犲瘑 +# def rsaEncrypt(str): +# #鐢熸垚鍏挜銆佺閽 +# (pubkey, privkey) = rsa.newkeys(512) +# #鏄庢枃缂栫爜鏍煎紡 +# content = str.encode('utf-8') +# #鍏挜鍔犲瘑 +# crypto = rsa.encrypt(content,pubkey) +# return (crypto,privkey) +# +# +# #rsa瑙e瘑 +# def rsaDecrypt(str,pk): +# #绉侀挜瑙e瘑 +# content = rsa.decrypt(str,pk) +# con=content.decode('utf-8') +# return con +# +# +# (a,b) = rsaEncrypt("hello") +# print('鍔犲瘑鍚庡瘑鏂囷細') +# print(a) +# content = rsaDecrypt(a,b) +# print('瑙e瘑鍚庢槑鏂囷細') +# print(content) \ No newline at end of file diff --git a/Python/practice/filter.py b/Python/practice/filter.py new file mode 100644 index 00000000..958cfcd0 --- /dev/null +++ b/Python/practice/filter.py @@ -0,0 +1,27 @@ + +class Filter(object): + # 涓嬩竴涓鎵ц鐨勫璞 + def __init__(self,name): + self.name = name + + + def set(self,next): + self.next = next + + def work(self): + if hasattr(self, "next"): + print('%s 鎵ц浜'%(self.name)) + self.next.work() + +a = Filter('A') +b = Filter('B') +c = Filter('C') +d = Filter('D') +e = Filter('E') + +a.set(b) +b.set(c) +c.set(d) +d.set(e) + +a.work() \ No newline at end of file diff --git a/Python/practice/leetcode/1.py b/Python/practice/leetcode/1.py new file mode 100644 index 00000000..263b8f5e --- /dev/null +++ b/Python/practice/leetcode/1.py @@ -0,0 +1,29 @@ +''' +https://leetcode-cn.com/problems/two-sum/description/ + +缁欏畾涓涓暣鏁版暟缁勫拰涓涓洰鏍囧硷紝鎵惧嚭鏁扮粍涓拰涓虹洰鏍囧肩殑涓や釜鏁般 + +浣犲彲浠ュ亣璁炬瘡涓緭鍏ュ彧瀵瑰簲涓绉嶇瓟妗堬紝涓斿悓鏍风殑鍏冪礌涓嶈兘琚噸澶嶅埄鐢ㄣ + +绀轰緥: + +缁欏畾 nums = [2, 7, 11, 15], target = 9 + +鍥犱负 nums[0] + nums[1] = 2 + 7 = 9 +鎵浠ヨ繑鍥 [0, 1] +''' +class Solution(object): + def twoSum(self, nums, target): + result = [] + length = len(nums) + index = 0 + current = None + while(index < length): + current = nums[index] + for i in range(index + 1,length): + if (nums[i] + current) == target: + result.append(index) + result.append(i) + index += 1 + return result +print(Solution().twoSum([2, 7, 11, 15], 9)) \ No newline at end of file diff --git a/Python/practice/leetcode/11.py b/Python/practice/leetcode/11.py new file mode 100644 index 00000000..47873e39 --- /dev/null +++ b/Python/practice/leetcode/11.py @@ -0,0 +1,72 @@ +''' +https://leetcode-cn.com/problems/container-with-most-water/description/ +''' +import math +import time +import functools + +def time_consuming(func): + @functools.wraps(func) + def wapper(*args,**kws): + start = round(time.time() * 1000) + result = func(*args,**kws) + end = round(time.time() * 1000) + print("鍑芥暟 :%s 鎵ц鑰楁椂:%s"%(func.__name__,end - start)) + return result + return wapper + +class Solution(object): + + @time_consuming + def maxArea(self, height): + max_value = 0 + for i,v in enumerate(height): + for y in range(i + 1,len(height)): + max_value = max(max_value,min(v,height[y]) * (y - i)) + return max_value + + @time_consuming + def maxArea_1(self, height): + result = [] + for i,v in enumerate(height): + for _i,_v in enumerate(height): + if _i == i: + continue; + min_value = _v if _v < v else v + step = int(math.fabs(_i - i)) + sum = min_value * step + result.append(sum) + return max(result) + + @time_consuming + def maxArea_2(self,height): + max_value = 0 + left = 0 + right = len(height) - 1 + while (left < right): + left_value = height[left] + right_value = height[right] + max_value = max(max_value,min(left_value,right_value) * (right - 1)) + if (left_value < right_value): + left += 1 + else: + right -= 1 + return max_value +arr = [28,342,418,485,719,670,878,752,662,994,654,504,929,660,424,855,922,744,600,229,728,33,371,863,561,772,271,178,455,449,426,835,143,845,321,214,867,199,967,881,193,973,386,122,633,810,330,907,906,282,136,986,315,860,849,229,632,473,759,87,922,185,922,418,382,243,632,250,795,599,131,988,924,869,463,558,680,145,465,938,427,954,925,94,814,126,323,798,599,434,885,874,620,159,292,354,755,924,956,550,876,88,890,800,309,705,358,989,850,176,280,629,130,205,724,296,331,399,94,283,186,331,157,806,490,801,512,597,725,469,499,601,909,390,754,218,447,112,560,298,640,840,279,122,397,355,418,80,755,864,363,293,195,872,451,38,673,963,635,751,432,487,352,341,229,458,912,676,923,472,326,563,312,606,686,709,313,456,789,420,321,505,713,868,377,164,258,403,128,246,154,912,733,858,606,962,317,518,990,240,990,317,803,302,275,841,363,588,650,504,9,323,9,74,191,387,239,450,790,367,48,944,279,781,802,885,743,471,755,85,711,745,402,867,399,29,708,762,970,710,267,331,33,276,405,577,15,644,379,157,363,427,453,995,208,608,232,303,79,988,388,791,733,143,658,133,524,718,247,846,429,514,529,814,143,935,743,510,931,122,668,647,901,473,642,461,81,874,764,513,214,505,304,948,0,314,433,876,384,680,75,165,547,604,980,42,891,75,552,823,198,572,822,451,46,816,913,479,42,29,992,257,886,648,557,238,962,990,115,699,22,190,864,921,794,196,315,686,272,868,861,822,792,35,273,190,851,186,670,893,568,662,150,454,311,59,693,625,49,808,324,424,350,189,697,496,737,13,534,9,881,747,831,25,782,457,216,985,643,886,879,211,900,381,18,211,441,711,837,842,871,513,618,573,702,316,69,440,329,956,801,562,703,633,587,486,90,803,471,85,41,702,649,942,436,19,505,877,730,342,71,953,856,690,526,910,6,947,702,335,903,504,249,607,489,836,445,931,992,268,368,33,971,369,327,407,388,833,636,470,527,59,423,735,749,301,646,107,249,700,794,504,556,43,111,397,232,908,328,224,177,49,609,500,418,937,259,159,770,247,629,297,306,405,33,408,706,31,867,955,83,662,460,640,57,923,37,289,832,718,865,361,119,475,213,537,412,472,48,534,71,678,183,377,83,568,137,141,599,5,449,35,19,261,675,76,184,64,366,368,134,583,81,253,58,294,143,822,118,191,356,189,221,892,919,656,460,56,798,412,61,247,447,80,508,474,509,44,538,227,765,673,810,846,278,869,493,421,43,611,613,752,153,834,644,72,491,456,128,641,868,542,888,315,974,748,141,483,144,32,710,909,705,873,108,983,94,601,757,137,564,370,241,69,556,885,141,47,694,622,688,562,516,928,230,490,28,371,326,173,403,388,82,460,613,542,796,707,495,553,197,412,275,438,481,831,676,623,231,370,597,271,284,113,200,514,955,228,238,633,753,993,374,188,806,987,730,602,695,226,507,892,638,782,330,471,965,358,94,196,728,43,468,365,508,20,879,464,600,469,449,354,463,823,542,621,811,624,223,506,850,82,750,840,216,432,312,181,791,758,730,871,802,550,236,310,570,468,126,522,289,576,228,104,751,122,725,562,747,300,420,949,382,170,790,950,603,454,132,746,564,214,969,366,764,206,29,686,26,507,208,315,435,789,420,187,911,145,101,658,446,522,608,180,692,750,131,647,556,615,393,120,181,363,839,945,921,220,983,299,727,543,614,515,332,386,54,244,884,155,254,682,29,214,862,722,964,345,721,520,960,467,993,141,182,832,438,455,52,421,754,131,317,720,646,649,107,52,245,343,208,500,25,589,714,239,311,31,585,385,903,897,852,896,391,386,728,181,841,780,955,595,264,272,315,262,921,774,315,167,117,875,19,494,464,733,86,128,116,671,513,20,920,717,916,663,103,997,845,944,129,800,891,393,72,558,656,345,333,971,512,802,846,883,649,662,617,735,790,733,406,655,105,678,372,374,342,827,371,187,123,500,339,14,246,411,573,902,108,258,225,973,60,423,856,709,85,473,444,876,559,202,531,16,233,256,390,575,83,761,114,559,614,453,573,860,216,498,114,324,756,339,649,169,762,506,230,199,979,27,427,890,581,311,259,814,567,649,389,2,763,855,561,377,660,487,589,228,337,703,905,446,42,554,615,156,60,197,707,392,576,135,282,158,446,541,324,365,543,66,367,306,921,281,35,582,768,624,810,457,327,715,903,721,270,870,229,682,68,936,74,996,423,709,154,221,250,479,586,145,545,306,451,818,587,486,400,707,462,563,164,789,630,420,862,252,290,443,935,358,732,9,355,155,718,861,729,321,692,315,466,237,973,270,56,560,108,808,619,571,723,136,712,354,556,927,958,846,370,893,557,454,255,264,610,325,477,339,998,170,6,817,759,980,87,815,892,547,976,864,470,51,0,183,405,908,462,716,754,184,961,663,639,216,927,249,894,405,940,892,927,298,709,686,630,148,854,523,696,830,387,166,881,739,701,639,647,163,707,401,348,668,417,987,237,344,588,131,101,880,375,28,178,85,67,161,233,921,684,929,103,423,448,984,162,149,975,809,665,682,562,13,703,979,352,940,676,940,423,777,172,798,158,702,883,225,863,469,498,899,750,601,322,198,937,484,348,265,293,365,299,856,378,2,187,82,294,863,374,717,993,546,516,151,248,751,728,464,220,578,363,971,179,686,169,116,522,869,381,168,234,681,376,964,35,563,46,330,427,420,47,420,318,915,923,919,667,3,383,239,581,98,562,760,136,732,228,659,953,962,827,540,995,203,856,30,118,903,712,545,675,112,317,994,27,240,913,46,243,648,638,824,746,200,936,883,284,165,894,590,127,721,130,474,276,986,504,394,241,217,292,269,329,609,263,708,202,528,107,797,176,745,974,274,945,910,157,582,427,403,172,906,124,302,380,752,640,885,499,234,454,791,503,135,752,118,843,954,646,302,104,174,47,78,800,345,988,310,927,416,713,99,322,838,753,55,942,745,940,441,979,746,584,482,881,337,952,76,643,598,379,747,124,778,825,925,123,166,235,402,934,948,853,608,138,606,663,81,704,955,874,683,701,459,518,934,148,470,363,791,421,94,891,545,872,68,822,348,234,57,102,168,358,956,129,496,914,792,929,618,748,804,654,801,263,524,736,411,994,451,554,767,545,797,313,769,866,487,117,452,545,572,973,903,528,102,751,442,246,681,413,994,485,67,148,100,591,236,863,937,687,417,705,232,567,370,353,433,857,823,237,754,395,210,9,923,664,113,717,911,794,482,257,631,549,405,83,492,641,946,782,328,715,839,912,282,561,266,715,418,89,305,525,484,515,534,759,532,999,476,443,793,959,700,776,860,106,859,705,99,805,839,428,873,678,692,155,239,958,223,9,399,528,886,235,395,773,994,927,772,823,722,566,134,423,694,346,881,554,403,332,711,242,760,584,272,805,740,863,763,315,873,515,843,759,750,238,884,745,518,657,568,240,575,54,15,269,400,248,175,804,581,887,398,693,823,671,498,915,534,262,230,759,129,425,871,879,664,755,624,534,412,544,774,987,950,790,257,351,390,784,507,971,671,905,665,847,928,163,762,463,777,345,574,906,770,445,786,786,201,762,320,965,659,95,953,609,237,562,312,979,346,819,951,370,77,616,569,357,131,331,820,909,28,747,167,799,192,953,585,745,68,906,711,727,353,16,688,942,930,1,921,628,172,224,350,601,192,919,959,324,251,131,585,279,878,752,430,423,58,16,168,126,274,231,205,979,247,893,921,177,246,194,158,419,419,508,20,963,428,331,639,31,463,224,662,341,329,93,764,739,461,285,217,735,516,422,714,764,667,987,293,914,533,451,685,304,960,57,268,740,389,907,771,852,484,433,545,165,878,310,904,339,947,121,426,463,895,492,579,914,831,873,828,365,676,865,21,988,923,289,728,312,549,851,516,385,637,61,550,515,723,806,207,670,927,985,134,174,478,713,88,309,938,269,26,615,134,48,955,409,689,684,721,590,887,589,975,524,651,877,392,726,683,599,397,962,584,883,136,414,948,577,724,239,846,102,206,332,150,161,742,192,197,815,782,85,405,758,609,408,635,353,134,671,952,883,633,889,118,122,655,419,699,731,658,897,834,216,229,336,377,323,528,927,139,311,12,896,69,973,304,56,679,790,727,631,674,361,872,792,483,528,563,534,259,221,431,445,437,12,782,167,336,310,94,827,621,458,723,42,783,379,99,462,169,826,446,843,539,670,988,374,550,551,908,162,125,691,607,914,704,389,81,392,52,527,571,673,337,294,716,473,25,167,935,194,993,733,38,885,404,378,259,306,281,168,468,406,211,76,321,915,817,754,659,869,634,230,895,323,876,611,796,253,778,84,448,123,817,838,8,573,216,268,880,849,788,348,256,999,776,929,267,594,683,278,815,669,861,710,993,89,673,789,343,803,225,791,927,43,981,287,968,549,907,848,398,47,549,654,47,325,583,666,271,619,944,439,640,805,149,633,895,175,775,590,978,0,381,257,395,714,545,364,263,804,564,661,852,113,668,899,791,603,917,414,222,861,205,863,19,355,848,914,530,623,856,860,976,237,118,371,303,15,87,566,819,652,579,671,117,247,922,260,851,839,27,425,53,232,640,72,587,489,338,469,464,194,682,440,783,800,812,86,167,899,652,986,903,231,10,21,831,284,281,682,476,308,107,529,893,748,601,832,589,939,302,53,485,336,494,268,136,658,354,303,909,358,641,813,941,651,186,772,288,467,454,764,128,914,293,21,14,246,853,603,537,507,656,22,843,502,290,331,160,996,986,70,706,628,235,647,631,421,420,919,888,226,35,368,492,328,741,506,926,947,461,463,806,118,485,2,972,127,685,133,475,672,555,181,652,790,181,635,211,953,555,451,179,942,820,24,271,913,530,197,212,992,13,19,462,850,373,434,330,58,919,805,82,474,339,734,264,872,370,475,825,277,927,4,219,99,380,842,364,911,392,577,255,405,948,69,255,321,855,585,731,775,743,814,601,434,548,866,306,270,693,131,547,620,487,119,71,868,961,436,131,705,365,738,110,313,159,366,986,14,303,717,141,398,883,743,832,432,961,138,54,654,621,954,627,109,73,698,329,386,486,812,92,851,550,554,516,709,920,502,75,576,572,217,974,455,312,807,239,273,297,646,927,919,600,906,380,25,957,61,763,443,873,207,647,423,762,163,484,34,18,559,610,942,128,937,749,440,96,341,65,393,987,993,664,587,251,396,964,208,457,727,652,330,935,651,105,697,166,589,731,536,501,694,478,981,631,580,422,727,921,839,472,908,832,137,847,84,533,811,644,991,538,296,673,825,299,779,522,466,720,606,2,221,300,833,203,283,413,977,362,334,816,834,594,1,323,441,437,209,604,81,552,142,730,225,968,29,4,842,847,77,448,850,298,100,35,853,735,448,182,97,134,351,284,728,704,959,521,493,168,125,926,720,619,8,946,939,390,302,782,237,731,582,439,30,35,474,235,770,922,770,220,408,473,856,136,529,815,9,374,984,486,300,704,106,661,2,45,51,657,179,640,388,114,80,770,149,554,358,271,829,480,843,237,953,699,726,834,515,735,208,851,222,508,555,680,521,910,77,572,919,609,213,307,723,293,430,872,199,788,495,28,268,339,618,573,38,344,407,905,431,615,756,5,475,664,685,997,574,115,921,493,724,134,152,447,779,582,671,331,370,518,359,990,857,329,563,248,673,322,153,105,289,262,462,765,926,500,114,852,615,35,345,691,522,849,490,653,432,513,984,154,31,696,145,889,25,60,489,51,383,642,508,672,904,970,789,182,470,255,34,437,643,731,128,165,581,618,170,13,483,155,519,515,203,664,756,228,77,245,631,812,239,139,836,496,110,978,678,932,233,713,722,876,796,850,41,377,821,564,742,304,719,262,171,274,278,927,502,355,524,486,519,764,625,356,612,87,334,642,20,567,707,742,444,504,944,837,881,117,401,624,774,472,238,945,746,516,225,601,224,749,87,743,513,64,99,477,152,785,120,524,705,827,266,501,331,562,338,213,32,92,189,806,916,427,103,15,295,328,968,519,78,407,263,943,471,714,421,623,852,541,147,557,368,765,58,700,680,748,265,712,192,454,870,109,233,973,476,528,302,444,48,380,851,663,323,674,729,744,650,581,637,149,490,6,267,548,58,947,297,323,11,841,129,881,950,362,206,778,890,508,222,290,240,425,305,916,100,387,660,102,320,298,251,811,656,518,711,714,817,360,37,828,202,518,61,504,880,268,283,122,128,857,765,369,283,422,637,735,809,297,189,482,947,440,645,955,959,356,669,776,69,58,957,271,576,18,127,808,638,410,931,119,268,48,840,903,470,477,990,632,126,179,114,426,971,759,381,930,467,403,59,536,813,16,159,742,386,287,550,377,49,833,496,317,881,688,572,352,165,914,336,291,445,450,69,417,561,451,699,380,206,110,917,19,478,428,761,217,715,664,594,765,497,442,434,379,130,359,83,647,625,419,290,423,221,360,192,134,163,891,514,369,2,783,740,832,564,854,49,279,518,643,396,15,437,183,746,919,894,829,566,519,600,857,294,173,217,486,307,380,378,174,101,732,309,841,916,873,695,966,505,213,961,253,229,399,788,975,318,682,157,885,554,109,742,848,283,311,335,942,691,65,468,792,149,778,985,65,3,681,31,860,894,345,466,475,744,254,803,62,289,312,299,843,421,393,43,56,704,730,999,747,147,467,891,648,597,877,714,953,558,97,813,804,442,279,632,538,534,787,953,823,99,252,18,872,998,413,929,702,496,280,802,643,747,693,292,697,570,358,650,128,807,463,285,602,95,917,140,629,704,445,804,155,50,174,27,48,939,308,750,435,588,904,431,688,598,723,385,168,433,35,649,592,850,934,194,945,203,687,926,907,132,730,414,182,256,793,582,196,102,685,983,690,589,414,378,187,489,115,356,922,150,357,867,1,291,61,946,494,100,873,753,585,955,167,119,564,312,702,760,414,387,95,457,976,510,187,516,999,303,224,274,453,581,141,806,872,554,105,718,655,978,471,592,285,990,711,849,302,765,961,69,504,57,526,833,567,713,349,918,368,573,192,174,154,333,980,378,888,85,96,895,415,919,487,701,261,550,902,915,668,216,984,172,273,862,5,192,928,706,462,296,279,655,470,785,340,451,163,580,888,611,827,304,882,314,357,143,217,611,59,237,827,395,409,100,258,767,644,186,473,107,482,105,114,305,890,806,756,406,387,996,17,214,300,900,881,9,43,450,621,454,687,448,850,96,901,460,215,897,646,689,356,480,794,470,785,36,277,893,442,16,890,460,230,542,712,111,904,107,913,525,562,600,973,764,49,226,224,264,124,222,953,480,54,99,303,192,136,580,437,578,948,679,390,178,222,102,642,478,210,555,3,772,156,328,888,557,907,464,821,31,38,775,863,444,226,166,636,362,98,74,293,46,753,683,577,327,786,219,157,996,126,160,120,634,841,360,191,748,824,13,131,862,140,994,306,718,513,295,81,611,721,726,10,826,409,587,506,547,158,663,543,284,824,15,919,17,375,462,765,199,475,896,413,967,242,72,686,755,367,119,367,440,845,729,266,606,316,772,154,474,436,697,110,612,65,29,629,440,844,746,992,319,994,405,639,236,829,677,992,548,796,711,988,993,440,255,599,108,27,105,934,815,155,44,427,220,74,408,660,918,154,652,589,148,410,580,737,239,257,729,788,405,792,776,750,584,383,702,692,763,807,626,578,962,670,358,182,96,766,195,366,273,199,308,421,609,240,158,201,850,239,341,255,31,469,6,967,853,60,659,616,867,637,546,182,660,904,364,108,671,911,475,944,111,135,717,72,375,228,273,225,819,614,833,203,84,191,170,289,251,830,905,470,819,803,652,479,708,369,588,731,280,415,27,743,902,744,816,277,324,441,855,144,56,40,347,492,583,869,781,834,51,38,656,871,841,309,350,549,678,290,632,310,705,659,406,607,756,222,237,80,15,444,576,423,484,275,915,419,145,48,253,196,86,909,67,280,570,770,181,600,60,814,263,118,473,669,725,229,243,314,662,258,758,238,682,594,514,597,13,659,998,618,207,436,880,275,716,802,45,250,403,457,64,666,575,889,335,301,471,930,967,133,188,726,723,870,672,237,820,38,248,818,656,456,606,536,83,323,339,128,925,94,937,989,112,513,230,799,166,701,729,133,186,917,211,910,788,884,499,960,922,748,130,930,556,736,819,639,411,510,119,336,604,56,677,716,569,908,515,87,961,244,221,500,513,784,410,653,20,909,613,294,9,743,225,565,480,44,204,891,906,323,580,510,732,257,578,653,517,445,741,479,41,314,979,554,98,741,208,471,2,173,765,12,917,990,577,749,386,134,992,292,809,572,154,541,830,84,195,347,529,288,178,570,602,509,477,52,250,685,523,253,858,641,265,127,631,194,876,370,680,869,14,490,441,169,383,623,253,930,971,135,218,149,705,820,659,534,225,261,219,100,514,430,741,131,909,373,678,138,95,358,7,109,200,800,630,936,424,236,866,747,371,85,248,428,257,259,963,482,521,534,583,387,316,324,871,226,49,549,364,496,259,723,958,812,875,588,748,651,176,614,398,547,51,999,328,309,258,291,143,131,825,726,871,494,403,94,72,804,995,436,301,254,511,611,418,738,199,518,390,376,133,140,275,184,139,603,845,750,246,989,233,424,67,456,918,470,550,990,627,545,778,280,152,641,891,570,379,90,89,121,818,574,262,446,110,753,401,956,855,648,945,441,424,12,249,694,835,800,684,462,697,814,742,849,807,633,772,538,75,213,660,246,787,274,44,249,379,445,205,587,445,502,380,869,867,629,563,54,429,599,868,479,765,610,328,572,595,452,463,22,665,123,620,804,749,664,54,480,110,611,67,907,466,447,129,685,77,692,739,858,644,607,337,761,569,18,686,516,470,149,538,136,624,159,292,373,175,698,853,285,310,273,545,128,720,674,165,149,718,904,8,714,863,345,476,784,715,162,300,186,311,190,674,287,701,966,660,877,665,865,514,327,138,59,807,211,733,972,360,804,876,720,518,91,418,994,875,133,508,175,319,819,717,993,458,419,312,470,296,977,336,810,656,826,870,463,389,955,435,750,759,311,470,630,754,888,624,629,374,133,156,693,304,873,39,763,292,351,233,940,680,921,751,336,100,973,799,489,280,234,239,392,545,62,22,299,302,998,280,676,483,436,722,788,309,761,903,954,464,488,894,144,410,997,480,510,970,279,351,603,513,591,995,410,5,369,61,659,367,341,688,851,129,410,991,438,523,894,744,987,382,639,131,144,988,611,6,311,242,358,914,755,301,261,517,306,630,578,317,349,919,5,552,48,415,543,838,290,789,935,277,524,926,760,668,914,371,675,225,965,33,491,72,686,752,589,992,734,167,661,436,438,667,988,838,434,884,29,725,673,964,354,197,242,115,218,156,838,893,734,804,278,225,876,964,330,466,308,416,985,969,852,776,988,841,614,775,725,995,500,750,311,854,948,553,969,166,62,808,411,796,964,41,373,840,357,703,658,17,120,996,338,324,772,327,165,738,102,242,86,954,993,397,160,293,303,130,459,365,290,222,513,606,615,886,446,972,942,457,989,414,453,327,738,577,6,904,315,460,498,401,414,491,151,575,784,454,57,595,819,699,169,332,305,784,570,103,108,512,560,449,926,365,777,665,942,135,921,610,596,419,11,362,911,514,289,47,968,698,643,787,397,164,471,54,949,42,158,57,906,70,507,833,788,636,850,730,123,123,692,71,542,56,434,805,570,75,853,539,774,848,678,171,12,150,578,313,544,736,723,802,158,230,987,946,218,837,29,693,960,721,765,855,777,199,660,348,274,865,239,400,713,917,924,78,419,502,391,315,590,114,118,748,696,105,47,266,295,428,960,255,149,725,110,279,924,123,979,550,988,218,951,702,135,875,780,907,729,523,222,319,638,340,419,686,798,466,953,93,894,913,700,396,638,163,675,914,286,654,816,274,224,119,328,359,994,460,618,75,984,841,394,974,533,166,660,331,984,613,776,879,526,477,627,516,992,654,430,278,660,599,904,884,718,233,595,65,45,214,140,29,407,887,3,940,53,16,624,37,629,752,268,508,581,895,376,573,901,807,851,561,406,756,445,124,341,393,541,386,607,682,768,14,569,123,306,622,139,282,11,121,35,280,981,616,527,357,190,429,164,393,342,922,149,788,399,842,181,940,229,140,974,997,506,543,120,812,517,612,95,881,733,130,161,714,746,40,71,288,469,588,682,164,862,183,952,613,26,485,906,607,977,880,604,483,776,76,295,645,40,742,526,125,872,39,839,971,80,263,611,901,203,293,65,417,829,369,383,855,206,289,462,183,521,418,18,649,846,314,295,887,408,173,12,633,213,204,604,645,819,567,898,374,213,316,143,42,685,526,897,244,815,711,427,689,481,446,338,327,112,633,566,520,159,931,153,724,487,109,721,306,677,619,32,242,935,175,284,973,54,181,217,869,244,996,910,725,442,249,52,906,234,971,779,393,902,932,469,389,394,190,47,423,810,431,665,97,958,949,70,12,482,639,234,726,636,144,451,430,393,855,337,628,826,116,373,80,400,843,469,794,33,868,569,195,299,234,293,258,183,715,622,17,355,856,743,991,1,546,421,394,402,110,374,228,226,748,309,979,943,130,773,976,351,695,524,2,929,817,612,465,884,235,482,239,91,226,582,92,772,4,839,526,466,213,107,45,313,768,24,256,250,149,585,601,844,109,604,774,278,216,239,162,803,73,402,895,299,984,339,424,340,178,302,159,744,409,204,57,177,228,666,428,729,251,381,574,360,985,700,990,554,291,152,357,364,906,604,16,243,944,792,583,474,94,94,570,504,298,980,33,526,646,813,608,249,195,182,961,532,882,951,438,525,103,796,889,10,752,905,253,696,49,188,523,144,635,93,0,933,425,385,812,71,551,420,320,746,602,281,630,836,584,69,361,688,217,602,50,969,860,655,18,909,195,541,405,830,986,757,764,412,143,928,835,694,348,508,792,302,141,422,490,726,491,851,766,708,805,816,30,665,823,48,927,370,941,332,201,279,90,317,43,585,245,879,279,945,387,71,247,880,845,737,606,689,940,372,749,97,540,779,115,363,179,42,86,120,374,287,400,816,956,443,401,553,674,32,498,413,455,97,294,301,186,900,990,478,625,91,575,165,871,690,881,50,732,967,523,459,606,923,627,914,718,29,467,393,413,317,158,869,766,452] + +result = Solution().maxArea(arr) +print(result) +result = Solution().maxArea_1(arr) +print(result) +result = Solution().maxArea_2(arr) +print(result) + +''' +鍑芥暟 :maxArea 鎵ц鑰楁椂:6474 +4913370 +鍑芥暟 :maxArea_1 鎵ц鑰楁椂:16609 +4913370 +鍑芥暟 :maxArea_2 鎵ц鑰楁椂:3 +4921290 + +''' \ No newline at end of file diff --git a/Python/practice/leetcode/2.py b/Python/practice/leetcode/2.py new file mode 100644 index 00000000..df9f056d --- /dev/null +++ b/Python/practice/leetcode/2.py @@ -0,0 +1,31 @@ +''' +https://leetcode-cn.com/problems/add-two-numbers/description/ + +缁欏畾涓や釜闈炵┖閾捐〃鏉ヨ〃绀轰袱涓潪璐熸暣鏁般備綅鏁版寜鐓ч嗗簭鏂瑰紡瀛樺偍锛屽畠浠殑姣忎釜鑺傜偣鍙瓨鍌ㄥ崟涓暟瀛椼傚皢涓ゆ暟鐩稿姞杩斿洖涓涓柊鐨勯摼琛ㄣ + +浣犲彲浠ュ亣璁鹃櫎浜嗘暟瀛 0 涔嬪锛岃繖涓や釜鏁板瓧閮戒笉浼氫互闆跺紑澶淬 + +绀轰緥锛 + +杈撳叆锛(2 -> 4 -> 3) + (5 -> 6 -> 4) +杈撳嚭锛7 -> 0 -> 8 +鍘熷洜锛342 + 465 = 807 +''' + +class ListNode: + def __init__(self, x): + self.val = x + self.next = None + +class Solution: + def addTwoNumbers(self, l1, l2): + """ + :type l1: ListNode + :type l2: ListNode + :rtype: ListNo + """ + + + + + diff --git a/Python/practice/leetcode/203.py b/Python/practice/leetcode/203.py new file mode 100644 index 00000000..9fe3023b --- /dev/null +++ b/Python/practice/leetcode/203.py @@ -0,0 +1,70 @@ +''' +https://leetcode-cn.com/problems/remove-linked-list-elements/ +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def removeElements(self, head, val): + """ + :type head: ListNode + :type val: int + :rtype: ListNode + """ + +''' + + +class ListNode: + + def __init__(self, x): + self.val = x + self.next = None + + +class Solution: + + # 鏅氬疄鐜 + def removeElements(self, head, val): + # 鍒犻櫎閾捐〃澶村厓绱 + while(head != None and head.val == val): + delNode = head + head = delNode.next + delNode.next = None + + if head == None: + return head + + # 鍒犻櫎閾捐〃涓殑鍏冪礌 + prev = head + while (prev.next != None): + if prev.next.val == val: + prev.next = prev.next.next + else: + prev = prev.next + + return head + + # 浣跨敤铏氭嫙澶磋妭鐐 + def removeElements1(self, head, val): + dummyHead = ListNode(-1) + dummyHead.next = head + + prev = dummyHead + + while(prev.next != None): + if(prev.next.val == val): + prev.next = prev.next.next + else: + prev = prev.next + return dummyHead.next + + # 浣跨敤閫掑綊瀹炵幇 + def removeElements2(self, head, val): + if head == None: + return None + head.next = self.removeElements2(head.next, val) + return head.next if head.val == val else head + diff --git a/Python/practice/leetcode/804.py b/Python/practice/leetcode/804.py new file mode 100644 index 00000000..25e1f940 --- /dev/null +++ b/Python/practice/leetcode/804.py @@ -0,0 +1,153 @@ +''' +鍥介檯鎽╁皵鏂瘑鐮佸畾涔変竴绉嶆爣鍑嗙紪鐮佹柟寮忥紝灏嗘瘡涓瓧姣嶅搴斾簬涓涓敱涓绯诲垪鐐瑰拰鐭嚎缁勬垚鐨勫瓧绗︿覆锛 姣斿: "a" 瀵瑰簲 ".-", "b" 瀵瑰簲 "-...", "c" 瀵瑰簲 "-.-.", 绛夌瓑銆 + +涓轰簡鏂逛究锛屾墍鏈26涓嫳鏂囧瓧姣嶅搴旀懇灏旀柉瀵嗙爜琛ㄥ涓嬶細 + +[".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."] +缁欏畾涓涓崟璇嶅垪琛紝姣忎釜鍗曡瘝鍙互鍐欐垚姣忎釜瀛楁瘝瀵瑰簲鎽╁皵鏂瘑鐮佺殑缁勫悎銆備緥濡傦紝"cab" 鍙互鍐欐垚 "-.-..--..."锛(鍗 "-.-." + "-..." + ".-"瀛楃涓茬殑缁撳悎)銆傛垜浠皢杩欐牱涓涓繛鎺ヨ繃绋嬬О浣滃崟璇嶇炕璇戙 + +杩斿洖鎴戜滑鍙互鑾峰緱鎵鏈夎瘝涓嶅悓鍗曡瘝缈昏瘧鐨勬暟閲忋 + +渚嬪: +杈撳叆: words = ["gin", "zen", "gig", "msg"] +杈撳嚭: 2 +瑙i噴: +鍚勫崟璇嶇炕璇戝涓: +"gin" -> "--...-." +"zen" -> "--...-." +"gig" -> "--...--." +"msg" -> "--...--." + +鍏辨湁 2 绉嶄笉鍚岀炕璇, "--...-." 鍜 "--...--.". + + +娉ㄦ剰: + +鍗曡瘝鍒楄〃words 鐨勯暱搴︿笉浼氳秴杩 100銆 +姣忎釜鍗曡瘝 words[i]鐨勯暱搴﹁寖鍥翠负 [1, 12]銆 +姣忎釜鍗曡瘝 words[i]鍙寘鍚皬鍐欏瓧姣嶃 +''' + +arr = [".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."] + + + +class Solution: + def uniqueMorseRepresentations(self, words): + hashSet = set(); + for i in words: + value = "" + for x in i: + key = arr[ord(x) - ord('a')] + value += key + hashSet.add(value) + return len(hashSet) + + +r = Solution().uniqueMorseRepresentations(["gin", "zen", "gig", "msg"]) +print(r) + + + + + + +'''c +#include +#include +#include +#include + +char * morse[] = { ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", + "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", + "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.." }; + +struct Node { + char *value; + struct Node * next; +}; + +struct List { + int size; + struct Node * head; +}; + +bool contains(struct List * list, char *value) { + struct Node * node = list->head; + while (node != NULL) { + if (strcmp(node->value, value) == 0) { + return true; + } + node = node->next; + } + return false; +} + +void add(struct List * list, char *value) { + if (list->head == NULL) { + list->head = (struct Node *) calloc(sizeof(struct Node), 1); + list->head->value = value; + list->head->next = NULL; + list->size++; + } else { + if (!contains(list, value)) { + struct Node * node = list->head; + while (node->next != NULL) { + node = node->next; + } + node->next = (struct Node *) calloc(sizeof(struct Node), 1); + node->next->value = value; + node->next->next = NULL; + list->size++; + } + } +} + +void clear(struct List * list) { + if (list != NULL) { + if (list->head != NULL) { + struct Node * node = list->head; + while (node != NULL) { + struct Node * freeNode = node; + node = node->next; + free(freeNode->value); + free(freeNode); + } + } + } +} + +int uniqueMorseRepresentations(char** words, int wordsSize) { + struct List list = { 0, NULL }; + for (int i = 0; i < wordsSize; i++) { + char *word = words[i]; + char * buf = (char *) calloc(sizeof(char), (4 * 12) + 1); + for (int x = 0; x < strlen(word); x++) { + char ch = word[x]; + char * key = morse[ch - 'a']; + strcat(buf,key); + } + add(&list, buf); + } + int count = list.size; + clear(&list); + return count; +} +int main(int argc, char **argv) { + char * arr[] = { "gin", "zen", "gig", "msg" }; + int result = uniqueMorseRepresentations(arr, 4); + printf("%d\n", result); + return EXIT_SUCCESS; +} + + +''' + + + + + + + + + diff --git a/Python/practice/leetcode/__init__.py b/Python/practice/leetcode/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Python/practice/main.py b/Python/practice/main.py new file mode 100644 index 00000000..85628bbb --- /dev/null +++ b/Python/practice/main.py @@ -0,0 +1,16 @@ +''' + 闈炴帓搴忛泦鍚,鎵惧嚭绗簩澶х殑鍏冪礌 +''' +def second_biggest(arr): + max = 0; + second = 0; + for i in arr: + if i > max: + second = max + max = i + elif i > second: + second = i + return second + +print(second_biggest([1,5,47,5,3,6,96])) + diff --git a/Python/practice/md5helper.py b/Python/practice/md5helper.py new file mode 100644 index 00000000..6355f895 --- /dev/null +++ b/Python/practice/md5helper.py @@ -0,0 +1,18 @@ +import hashlib +from tkinter import * + +md5digest = hashlib.md5() + +root = Tk() +root.title("md5") + +entry = Entry(root) +lable = Label(root,text='鍝堝搱') +button = Button(root,text='璁$畻') + + +lable.grid(row=0,sticky=E+W) +entry.grid(row=1) +button.grid(row=2,column=0) + +root.mainloop() \ No newline at end of file diff --git a/Python/practice/mysql8.py b/Python/practice/mysql8.py new file mode 100644 index 00000000..bcd11b86 --- /dev/null +++ b/Python/practice/mysql8.py @@ -0,0 +1,16 @@ +import pymysql + +db_host = '127.0.0.1' +db_port = 3306 +db_user = 'root' +db_pass = 'root' +db_database = 'information_schema' +db_charset = 'utf8mb4' +db_cursorclass = pymysql.cursors.DictCursor + +# 鍒涘缓杩炴帴瀵硅薄 +conn = pymysql.connect(host=db_host,port=db_port,user=db_user,passwd=db_pass,db=db_database,charset=db_charset,cursorclass=db_cursorclass) + +# 閫氳繃杩炴帴瀵硅薄,鍒涘缓娓告爣 +cursor = conn.cursor() + diff --git a/Python/practice/olddriver.py b/Python/practice/olddriver.py new file mode 100644 index 00000000..0a207353 --- /dev/null +++ b/Python/practice/olddriver.py @@ -0,0 +1,108 @@ +''' +DROP TABLE IF EXISTS `7mav001`; +CREATE TABLE `7mav001` ( + `id` int(11) NOT NULL COMMENT ' pk', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '鏍囬', + `href` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '閾炬帴鍦板潃', + `src` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '鎾斁鍦板潃', + `is_down` tinyint(1) NULL DEFAULT NULL COMMENT '鏄惁宸茬粡涓嬭浇', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +''' +from urllib import request,parse +from bs4 import BeautifulSoup +import queue +import pymysql +from concurrent.futures import ThreadPoolExecutor +import base64 + +# url +base_url = base64.b64decode(b'aHR0cDovL3d3dy43bWF2MDAxLmNvbQ==').decode('UTF_8') + +# 涓婚〉 +index = base_url + '/recent/' + +# 褰撳墠椤 +current_page = 1 + +# 鎬婚〉鏁 +total_page = None + +# 鏁版嵁搴撻厤缃 +db_config = { + 'host':'127.0.0.1', + 'port':3306, + 'user':'root', + 'passwd':'root', + 'db':'7mav001', + 'charset':'utf8mb4', + 'cursorclass':pymysql.cursors.DictCursor +} + +class Vedio(object): + def __init__(self,id,title,href,src): + self.id = int(id) + self.title = title + self.href = href + self.src = src + + def __str__(self, *args, **kwargs): + return "id=%s,title=%s,href=%s,src=%s"%(self.id,self.title,self.href,self.src) + +req = request.Request(index) + +with request.urlopen(req) as rep: + soup = BeautifulSoup(rep.read(),'html.parser') + pagination = soup.find('ul', class_='pagination').find_all('li')[-2]; + # 寰楀埌鎬婚〉鐮 + total_page = int(pagination.string) + +def create(video): + connection = pymysql.connect(**db_config) + connection.begin() + cursor = connection.cursor() + cursor.execute('SELECT `id` FROM `7mav001` WHERE `id` = %s;',(video.id,)) + if not cursor.fetchone(): + print(video) + cursor.execute('INSERT INTO `7mav001`(`id`,`title`,`href`,`src`) VALUES(%s,%s,%s,%s);',(video.id,video.title,video.href,video.src)) + connection.commit() + connection.close() + +def worker (page): + req = request.Request(index + page) + with request.urlopen(req) as rep: + soup = BeautifulSoup(rep.read(),'html.parser') + videos = soup.find('ul', class_ = 'videos').find_all('li') + # 閬嶅巻瑙嗛鍒楄〃 + for video in videos: + # 瑙嗛id + id = video.attrs['id'].split('-')[1] + link = video.find('a',class_ = 'thumbnail') + # 瑙嗛鏍囬 + title = link.attrs['title'] + # 瑙嗛椤甸潰鍦板潃 + href = base_url + '/' + id + '/' + parse.quote(title) + + with request.urlopen(href) as rep: + soup = BeautifulSoup(rep.read(),'html.parser') + video = soup.find('video') + source = video.find('source') + # 瑙嗛鎾斁鍦板潃 + src = source.attrs['src'] + # 鎸佷箙鍖 + create(Vedio(id,title,href,src)) + +def start(): + global current_page + while current_page <= total_page: + page = '' + if current_page > 1: + page = str(current_page) + worker(page) + #椤电爜鍓嶇Щ + current_page += 1 + break + +if __name__ == '__main__': + start() diff --git a/Python/practice/opencv/demo1.py b/Python/practice/opencv/demo1.py new file mode 100644 index 00000000..e69de29b diff --git a/Python/practice/sign.py b/Python/practice/sign.py new file mode 100644 index 00000000..e1ed9385 --- /dev/null +++ b/Python/practice/sign.py @@ -0,0 +1,20 @@ +from hashlib import md5 +secret_key = '123456' +param = { + "order":"123456", + "senderPhone":"18524574185", + "receiverPhone":"17455451254", + "receiverName":"鐔婁繚瀹", + "remark":"杩欎釜鏄娉", +} +message = "" +for key in sorted(param.keys()): + value = param.get(key) + if value : + message = message + key + "=" + value + "&" +message += "code=" + secret_key +md5_digest = md5() +print("璁$畻绛惧悕:%s" %(message)) +md5_digest.update(bytes(message,'UTF_8')) +sign = md5_digest.hexdigest() +print("绛惧悕缁撴灉:%s" %(sign)) \ No newline at end of file diff --git a/Python/practice/temp.py b/Python/practice/temp.py new file mode 100644 index 00000000..e69de29b diff --git a/Python/practice/test.py b/Python/practice/test.py new file mode 100644 index 00000000..a496d17f --- /dev/null +++ b/Python/practice/test.py @@ -0,0 +1,39 @@ +import threading +import urllib + +from http.server import BaseHTTPRequestHandler +from http.server import HTTPServer +from socketserver import ThreadingMixIn + + +class Http_Handler(BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200, message=None) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write('Hello Python'.encode(encoding='utf_8', errors='strict')) + + def do_POST(self): + # 鍝嶅簲璁剧疆 + self.send_response(200, message=None) + self.send_header('Content-type', 'application/json;charset=UTF-8') + self.end_headers() + + # 璇锋眰璺緞鍒ゆ柇 + if not self.path == '/open': + self.wfile.write(bytes(json.dumps(RESPONSE_BODY['not_found'],ensure_ascii=False),'UTF_8')) + return + + # 鑾峰彇POST鐨凧SON璇锋眰浣 + request_size = int(self.headers["Content-length"]) + request_body = self.rfile.read(request_size).decode(encoding='utf_8', errors='strict') + request_obj = json.loads(request_body) + + # 鍝嶅簲瀹㈡埛绔 + self.wfile.write(bytes(json.dumps(RESPONSE_BODY['success'],ensure_ascii=False),'UTF_8')) +class ThreadingHttpServer(ThreadingMixIn, HTTPServer): + pass + +server = ThreadingHttpServer(('localhost', 8080), Http_Handler) +server.serve_forever() +server.server_close() \ No newline at end of file diff --git a/Python/practice/transaction.py b/Python/practice/transaction.py new file mode 100644 index 00000000..cca4fcc1 --- /dev/null +++ b/Python/practice/transaction.py @@ -0,0 +1,50 @@ +''' + CREATE TABLE `account` ( + `id` int(11) NOT NULL COMMENT 'id', + `money` int(255) DEFAULT NULL COMMENT '浣欓', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +''' +import pymysql +import time +import threading + +db_config = { + 'host':'localhost', + 'port':3306, + 'user':'root', + 'passwd':'root', + 'database':'springcloud', + 'charset':'utf8mb4', + 'cursorclass':pymysql.cursors.DictCursor +} + + +def add(money,seconds): + + connection = pymysql.connect(**db_config) + connection.autocommit(False) + # 寮鍚簨鍔 + connection.begin() + cursor = connection.cursor() + + # 鎵цSELECT + cursor.execute('SELECT `money` FROM `account` WHERE `id` = %s;',(1,)) + + balance = cursor.fetchone()['money']; + print("%s-璇诲彇鍒颁綑棰:%s,鏈娣诲姞閲戦:%s"%(threading.current_thread().getName(),balance,money)) + # 妯℃嫙绾跨▼鍗′綇 + time.sleep(seconds) + # 鍥炲啓 + cursor.execute('UPDATE `account` SET `money` = %s WHERE `id` = %s;',(balance + money,1)) + + # 鎻愪氦浜嬪姟 + print("%s-鎻愪氦浜嬪姟"%(threading.current_thread().getName())) + connection.commit() + + +if __name__ == '__main__': + threading.Thread(target=add,args=(10,2)).start() + threading.Thread(target=add,args=(10,1)).start() + + diff --git a/Python/practice/webapp/application.py b/Python/practice/webapp/application.py new file mode 100644 index 00000000..d47d2731 --- /dev/null +++ b/Python/practice/webapp/application.py @@ -0,0 +1,13 @@ +from flask import Flask, request, render_template +import json + +app = Flask('flask-application') + + +@app.route('/') +def default(): + return (json.dumps({'name':'KevinBlandy'}),200,{'Content-Type':'application/json;charset=utf-8'}) + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/Python/practice/webapp/static/index.js b/Python/practice/webapp/static/index.js new file mode 100644 index 00000000..b2dee1da --- /dev/null +++ b/Python/practice/webapp/static/index.js @@ -0,0 +1 @@ +console.log('hh'); \ No newline at end of file diff --git a/Python/practice/webapp/templates/index.html b/Python/practice/webapp/templates/index.html new file mode 100644 index 00000000..a59b0127 --- /dev/null +++ b/Python/practice/webapp/templates/index.html @@ -0,0 +1,12 @@ + + + + index + + + + + + \ No newline at end of file diff --git a/Python/python-lib-crypto.py b/Python/python-lib-crypto.py new file mode 100644 index 00000000..03c35d66 --- /dev/null +++ b/Python/python-lib-crypto.py @@ -0,0 +1,10 @@ +------------------------------------ +Crypto | +------------------------------------ + # 常用的加密/解密模块 + # 安装 + pip install Crypto + +------------------------------------ +AES | +------------------------------------ \ No newline at end of file diff --git a/Python/python-lib-pyinstaller.py b/Python/python-lib-pyinstaller.py new file mode 100644 index 00000000..0ab97b24 --- /dev/null +++ b/Python/python-lib-pyinstaller.py @@ -0,0 +1,15 @@ +----------------------------- +pyinstaller | +----------------------------- + * 一个可以把py转换为exe的库 + * 快速安装 + pip3 install pyinstaller + + * 快速使用 + pyinstaller Demo.py + + +----------------------------- +参数详解 | +----------------------------- + \ No newline at end of file diff --git a/Python/python-lib-rsa.py b/Python/python-lib-rsa.py new file mode 100644 index 00000000..7cd0ee62 --- /dev/null +++ b/Python/python-lib-rsa.py @@ -0,0 +1,45 @@ +------------------------- +rsa模块 | +------------------------- + # 创建密钥对 + (publicKey, privateKey) rsa.newkeys(length) + * length 表示长度 + + # 把key序列化为证书格式的文本 + publicKey.save_pkcs1().decode() + privateKey.save_pkcs1().decode() + + * 其实就是序列化为字符串 + -----BEGIN RSA PUBLIC KEY----- + MIIBCgKCAQEA3TQgbv7yA2tmp6Cxp2VO9dz8ByfYSZWVhtpc1kKYwTljjaZv2U4e + ... + -----END RSA PUBLIC KEY----- + + -----BEGIN RSA PRIVATE KEY----- + MIIEqgIBAAKCAQEA3TQgbv7yA2tmp6Cxp2VO9dz8ByfYSZWVhtpc1kKYwTljjaZv + 2U4eKCc4k33z6euoWwEyn5eYYfSC+9I7AJLwJXq7ABy+o/fBVXZR/WsepuX506Ew + ... + -----END RSA PRIVATE KEY----- + + # 导入密钥 + * 导入文本格式的公钥和密钥(格式见上) + + rsa.PublicKey.load_pkcs1(publicKeyStr) + rsa.PrivateKey.load_pkcs1(privateKeyStr) + + # 私钥签名,公钥验签 + signature = rsa.sign(content, privateKey, 'SHA-1') # 使用私钥计算出前面,算法使用 SHA-1 + rsa.verify(content, signature, publicKey) # 使用公钥对签名进行验证 + + # 公钥加密,私钥解密 + import rsa + publicKey, privateKey = rsa.newkeys(512) # 初始化密钥对 + result = rsa.encrypt("你好啊".encode('UTF_8'), publicKey) # 公钥加密,计算出密文 + content = rsa.decrypt(result, privateKey); # 私钥根据密文解密出原始数据 + print(content.decode('UTF_8')) + + # 私钥加密公钥解密 + * 虽然rsa算法理论上支持对称的公钥加密私钥解密/私钥加密公钥解密,但大部分平台的rsa api都设计成只提供public key encrypt/ private key decrypt的接口 + * 这是由于私钥加密会带来私钥泄露的风险,一般私钥加密过程只用于签名sign + * 因为sign的过程是加密之前对消息进行hash,然后der,然后加密,验证的过程是逆向的,对比解密和der解码之后的hash做对比,因此不会泄露private key + diff --git a/Python/python-lib-rtmp.py b/Python/python-lib-rtmp.py new file mode 100644 index 00000000..28b5d6e6 --- /dev/null +++ b/Python/python-lib-rtmp.py @@ -0,0 +1,57 @@ +----------------------- +Linux 安装依赖 | +----------------------- + # 安装gcc和依赖包 + yum install gcc* python-devel libffi-dev* -y + + # 安装 librtmp + git clone git://git.ffmpeg.org/rtmpdump + cd rtmpdump/librtmp/ + make && make install + + # 安装 setuptools + * 在:https://pypi.org/ 搜索:setuptools + * 在下载页面,下载 zip 包 + wget -S https://files.pythonhosted.org/packages/b0/d1/8acb42f391cba52e35b131e442e80deffbb8d0676b93261d761b1f0ef8fb/setuptools-40.6.2.zip + * 解压 & 安装 + unzip setuptools-40.6.2.zip + cd setuptools-40.6.2 + python setup.py install + * 直接 pip 安装也行 + + # 安装 cffi + * 在:https://pypi.org/ 搜索:cffi + * 在下载页面,下载 tar.gz 包 + wget -S https://files.pythonhosted.org/packages/e7/a7/4cd50e57cc6f436f1cc3a7e8fa700ff9b8b4d471620629074913e3735fb2/cffi-1.11.5.tar.gz + * 解压 & 安装 + tar -zxvf cffi-1.11.5.tar.gz + cd cffi-1.11.5 + python setup.py install + * 直接pip安装也行 + + # 安装 librtmp + pip install python-librtmp + + # 尝试 import librtmp + + # 处理异常:ImportError: librtmp.so.1: cannot open shared object file: No such file or directory + * 查找到librtmp.so.1路径,复制到lib64目录下即可 + * 编译安装 librtmp 的时候,日志就有 librtmp.so.1 的路径信息 + + find / -name librtmp.so.1 + cp /usr/local/lib/librtmp.so.1 /usr/lib64/ + +----------------------- +windows 安装依赖 | +----------------------- + # 执行 + pip install python-librtmp + + # 安装异常 + error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": https://visualstudio.microsoft.com/downloads/ + + * 先在本机安装: visualcppbuildtools_full.exe + + + # 编译失败: + Can not open include file: 'librtmp/rtmp.h': No such file or directory diff --git a/Python/python-mysql/python-pymysql.py b/Python/python-mysql/python-pymysql.py index 59015832..578e36f0 100644 --- a/Python/python-mysql/python-pymysql.py +++ b/Python/python-mysql/python-pymysql.py @@ -121,6 +121,18 @@ db_charset = 'utf8mb4' db_cursorclass = pymysql.cursors.DictCursor +''' +config = { + 'host':'127.0.0.1', + 'port' : 3306, + 'user' : 'root', + 'passwd' : 'root', + 'database' : 'springcloud', + 'charset' : 'utf8mb4', + 'cursorclass' : pymysql.cursors.DictCursor +} +''' + # 创建连接对象 conn = pymysql.connect(host=db_host,port=db_port,user=db_user,passwd=db_pass,db=db_database,charset=db_charset,cursorclass=db_cursorclass) diff --git "a/Python/python-oop-\347\261\273\344\277\241\346\201\257.py" "b/Python/python-oop-\347\261\273\344\277\241\346\201\257.py" index 920e353f..beb6012e 100644 --- "a/Python/python-oop-\347\261\273\344\277\241\346\201\257.py" +++ "b/Python/python-oop-\347\261\273\344\277\241\346\201\257.py" @@ -158,7 +158,7 @@ def __new__(cls, name, bases, attrs): attrs['add'] = lambda self, value: self.append(value) return type.__new__(cls, name, bases, attrs) # return type(name,bases,attrs) 涔熶竴鏍 - # return super(ListMetaclass,self).__new__(self,name,bases,attrs) 涔熶竴鏍 + # return super(ListMetaclass,cls).__new__(cls,name,bases,attrs) 涔熶竴鏍 2,鑷畾涔夌被 * 缁ф壙鑷 list,灏辨湁浜 list 鐨勬墍鏈夋柟娉 * 浣跨敤 metaclass 鍏抽敭瀛,鎸囧畾鍏冪被 @@ -217,6 +217,11 @@ class A(): __delitem__(self,key) * 浠ヤ笂鍑犱釜,鎶婂璞″綋瀛楀吀浣跨敤鏃惰皟鐢 + __getattribute__(self,item) + __setattr__(self, key, value) + __delattr__(self, item) + * 浠ヤ笂鍑芥暟,鍦ㄥ瀵硅薄杩涜灞炴ф搷浣滅殑鏃跺欒皟鐢 + __add__(self,other) * 鎵ц淇╁璞$浉鍔犵殑鏃跺欐墽琛岀殑鏂规硶 __lt__(self,other) @@ -265,8 +270,36 @@ class A(): '澶猅M澶氫簡...鍙鏄搷浣滅,鎿嶄綔瀵硅薄,瀵硅薄閮芥湁瀵瑰簲鐨勫嚱鏁拌繘琛屽鐞' - # 瑁呴グ鍣 - * 瑁呴グ鍣ㄤ篃鍙互鏍囪瘑鍦ㄧ被涓 - * 涔熷彲浠ユ爣璇嗗湪闈欐佹柟娉曚笂 + # 绫昏楗板櫒 + * 鎶婁竴涓被浣滀负鍑芥暟鐨勫寮 + class Foo(): + # 鍦ㄥ垵濮嬪寲鐨勬椂鍊欎繚瀛樿澧炲己鍑芥暟 + def __init__(self,func): + self.func = func + + # 琚寮哄嚱鏁版墽琛岀殑鏃跺,浼氭墽琛宊_call__ + def __call__(self, *args, **kwargs): + print('绫昏楗板櫒鍓嶇疆澧炲己') + # 鎵ц琚寮哄嚱鏁 + result = self.func() + print('绫昏楗板櫒鍚庣疆澧炲己') + return result + + # test鍑芥暟鐩稿綋浜庢寚鍚戜簡鐢‵oo鍒涘缓鍑烘潵鐨勫疄渚嬪璞 + @Foo + def test(): + print('hahah') + return 'ok' + + # 褰撳湪浣跨敤test()杩涜璋冪敤鏃,灏辩浉褰撲簬璁╄繖涓璞()锛屽洜姝や細璋冪敤杩欎釜瀵硅薄鐨刜_call__鏂规硶 + print(test()) + print(test) + ''' + 绫昏楗板櫒鍓嶇疆澧炲己 + hahah + 绫昏楗板櫒鍚庣疆澧炲己 + ok + <__main__.Foo object at 0x000000000296BBE0> # 鐩存帴鎵撳嵃鍑芥暟,杩欎釜鍑芥暟灏辨槸灏辨槸Foo鐨勫疄渚嬪璞 isinstance(test,Foo) ==> True + ''' \ No newline at end of file diff --git a/Python/python-socket-aio.py b/Python/python-socket-aio.py index 21059ec5..9f267401 100644 --- a/Python/python-socket-aio.py +++ b/Python/python-socket-aio.py @@ -16,6 +16,24 @@ tasks = [hello(), hello()] loop.run_until_complete(asyncio.wait(tasks)) 4,关闭 event_loop 对象 (close()) + * demo + import asyncio + @asyncio.coroutine + def test(name): + print('%s 开始执行'%(name)) + yield from asyncio.sleep(1) + print('%s 执行完毕' % (name)) + + loop = asyncio.get_event_loop() + loop.run_until_complete(asyncio.wait([test('Kevin'),test('Litch'),test('Rocco')])) + loop.close() + + Kevin 开始执行 + Litch 开始执行 + Rocco 开始执行 + Kevin 执行完毕 + Litch 执行完毕 + Rocco 执行完毕 * 3.5 版本以后 * 用asyncio提供的 @asyncio.coroutine 可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个 coroutine 实现异步操作 @@ -26,15 +44,22 @@ 2,不使用 yield from 语句,而使用 await 关键字 * demo import asyncio + async def test(name): + print('%s 开始执行'%(name)) + await asyncio.sleep(1) + print('%s 执行完毕' % (name)) - async def demo(name): - print('[%s]1'%(name)) - r = await asyncio.sleep(2) - print('[%s]2'%(name)) - loop = asyncio.get_event_loop() - loop.run_until_complete(asyncio.wait([demo('Kevin'),demo('Litch')])) - + loop.run_until_complete(asyncio.wait([test('Kevin'),test('Litch'),test('Rocco')])) + loop.close() + + Kevin 开始执行 + Litch 开始执行 + Rocco 开始执行 + Kevin 执行完毕 + Litch 执行完毕 + Rocco 执行完毕 + * event_loop api create_task() run_until_complete() @@ -103,7 +128,6 @@ async def demo(var): # 创建 task task = asyncio.ensure_future(demo(5)) - # task添加监听,在回调函数中调用形参的 result() 获取结果信息 # task和回调里的x对象,实际上是同一个对象 task.add_done_callback(lambda x : print('result:%s'%(x.result()))) diff --git a/Python/python-socket-bio.py b/Python/python-socket-bio.py index 97fc39e3..640850e4 100644 --- a/Python/python-socket-bio.py +++ b/Python/python-socket-bio.py @@ -19,6 +19,22 @@ client = server.recvfrom(64) print(client[0]) # 鏁版嵁鍖 print(client[1]) # 瀹㈡埛绔疘P & 绔彛 + + + # udp骞挎挱 + import socket, sys + # 鐩殑鍦颁负骞挎挱,褰撳墠灞鍩熺綉涓墍鏈7788绔彛 + dest = ('', 7788) + # 鍒涘缓udp濂楁帴瀛 + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + # 瀵硅繖涓渶瑕佸彂閫佸箍鎾暟鎹殑濂楁帴瀛楄繘琛屼慨鏀硅缃紝鍚﹀垯涓嶈兘鍙戦佸箍鎾暟鎹 + s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) + # 浠ュ箍鎾殑褰㈠紡鍙戦佹暟鎹埌鏈綉缁滅殑鎵鏈夌數鑴戜腑 + s.sendto(b"Hi", dest) + print("绛夊緟瀵规柟鍥炲(鎸塩trl+c閫鍑)") + while True: + buf, address = s.recvfrom(2048) + print("Received from %s: %s" % (address, buf)) ------------------------- socket-TCP(BIO) | diff --git a/Python/python-socket-nio.py b/Python/python-socket-nio.py index 34efa123..f567e05d 100644 --- a/Python/python-socket-nio.py +++ b/Python/python-socket-nio.py @@ -1,14 +1,17 @@ -------------------------------- -NIO-四种IO模式 | +nio | -------------------------------- - 同步 阻塞 - 同步 非阻塞 + # 四种IO模式 + 同步 阻塞 + 同步 非阻塞 - 异步 阻塞 - 异步 非阻塞 - - select -> poll -> epoll + 异步 阻塞 + 异步 非阻塞 + select -> poll -> epoll + + # 相关文档 + https://docs.python.org/3/library/selectors.html -------------------------------- NIO-select-模块函数 | @@ -21,6 +24,58 @@ error : time :隔多少时间轮询一次 +-------------------------------- +NIO-select-epoll | +-------------------------------- +import socket +import select + +# 创建套接字 +server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) +# 设置可以重复使用绑定的信息 +server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) +# 绑定本机信息 +server.bind(("0.0.0.0",7788)) +# 变为被动 +server.listen(10) +# 创建一个epoll对象 +epoll = select.epoll() + +# 注册事件到epoll中 +# epoll.register(fd[, eventmask]) +# 注意,如果fd已经注册过,则会发生异常 +# 将创建的套接字添加到epoll的事件监听中 +epoll.register(server.fileno(),select.EPOLLIN|select.EPOLLET) + +connections = {} +addresses = {} + +# 循环等待客户端的到来或者对方发送数据 +while True: + # epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待 + epoll_list = epoll.poll() + # 对事件进行判断 + for fd,events in epoll_list: + # 如果是socket创建的套接字被激活 + if fd == server.fileno(): + conn,addr = server.accept() + print('有新的客户端连接%s'%str(addr)) + # 将 conn 和 addr 信息分别保存起来 + connections[conn.fileno()] = conn + addresses[conn.fileno()] = addr + # 向 epoll 中注册 连接 socket 的 可读 事件 + epoll.register(conn.fileno(), select.EPOLLIN | select.EPOLLET) + elif events == select.EPOLLIN: + # 从激活 fd 上接收 + recvData = connections[fd].recv(1024) + if len(recvData) > 0: + print('recv:%s'%recvData) + else: + # 从 epoll 中移除该 连接 fd + epoll.unregister(fd) + # server 侧主动关闭该 连接 fd + connections[fd].close() + print("%s---offline---"%str(addresses[fd])) diff --git "a/Python/python-thread-\345\215\217\347\250\213.py" "b/Python/python-thread-\345\215\217\347\250\213.py" index 7f27b2fa..a2e9f5ea 100644 --- "a/Python/python-thread-\345\215\217\347\250\213.py" +++ "b/Python/python-thread-\345\215\217\347\250\213.py" @@ -75,6 +75,7 @@ def do_get(url): ------------------------ * greenlet 鏄 gevent 涓嬬殑杞婚噺绾у崗绋嬪垏鎹㈡ā鍧 * 鍏跺疄灏辨槸閫氳繃 switch() 鏂规硶鏉ュ畬鎴愮嚎绋嬩笂浠诲姟鐨勫垏鎹 + * 浣嗘槸杩欎釜閬囧埌io瑕佸幓鎵嬪姩鐨勫垏鎹笂涓嬫枃 * demo from greenlet import greenlet def fun1(): @@ -99,3 +100,31 @@ def fun2(): 2 b ''' + +------------------------ +鍗忕▼-gevent鐨則cp鏈嶅姟鍣 | +------------------------ +import gevent + +from gevent import socket,monkey +monkey.patch_all() + +def handle_request(conn): + while True: + data = conn.recv(1024) + if not data: + conn.close() + break + print("recv:", data) + conn.send(data) + +def server(port): + s = socket.socket() + s.bind(('0.0.0.0', port)) + s.listen(5) + while True: + cli, addr = s.accept() + gevent.spawn(handle_request, cli) + +if __name__ == '__main__': + server(7788) \ No newline at end of file diff --git "a/Python/python-\345\205\245\351\227\250.py" "b/Python/python-\345\205\245\351\227\250.py" index 8cf136b6..8de0fb64 100644 --- "a/Python/python-\345\205\245\351\227\250.py" +++ "b/Python/python-\345\205\245\351\227\250.py" @@ -31,7 +31,37 @@ * 唯一注意的是,添加到环境变量(不然得手动的添加) 3,安装OK后,打开CMD,输入:python –V * 成功看到版本信息则安装成功 + +---------------------------- +1,Cetntos环境搭建 | +---------------------------- + 1,下载 + https://www.python.org/ftp/python/ + + 2,解压,创建文件夹 + tar -zxvf Python-3.7.1.tgz + mkdir /usr/local/python + + 3,安装依赖 + yum -y install zlib + yum -y install zlib-devel + yum install -y libffi-devel + yum install -y openssl-devel + + 4,进入解压目录,执行编译 + ./configure --prefix=/usr/local/python + + 5,编译ok后,执行安装 + make && make install + + 4,创建软连接 + ln -s /usr/local/python/bin/python3 /usr/bin/python3 + ln -s /usr/local/python/bin/pip3 /usr/bin/pip3 + 5,测试 + python3 -V + + ---------------------------- 2,Python-处理文件编码 | diff --git "a/Python/python-\345\205\250\345\261\200\345\207\275\346\225\260.py" "b/Python/python-\345\205\250\345\261\200\345\207\275\346\225\260.py" index 717803e0..3b0c7aed 100644 --- "a/Python/python-\345\205\250\345\261\200\345\207\275\346\225\260.py" +++ "b/Python/python-\345\205\250\345\261\200\345\207\275\346\225\260.py" @@ -3,7 +3,7 @@ ---------------------------- * 瀹樻柟鏂囨。 - http://docs.python.org/3/library/functions.html*abs + https://docs.python.org/3/library/functions.html exit() * 閫鍑虹▼搴,濡傛灉鍙傛暟鏄瓧绗︿覆,鍒欐墦鍗拌瀛楃涓 @@ -226,9 +226,8 @@ ---------------------------- 鍏ㄥ眬鍑芥暟-鏁板鐩稿叧 | ---------------------------- + sum(iter) max(iter) - * - min(iter) * diff --git "a/Python/python-\345\237\272\347\241\200-pip.py" "b/Python/python-\345\237\272\347\241\200-pip.py" index 3702da0c..45dcefa1 100644 --- "a/Python/python-\345\237\272\347\241\200-pip.py" +++ "b/Python/python-\345\237\272\347\241\200-pip.py" @@ -13,3 +13,21 @@ * 模块下载目录(sys.path 中可见) C:\Users\Kevin\AppData\Local\Programs\Python\Python36-32\lib\site-packages + +-------------------------------- +python 3.6.5后pip丢失的问题 | +-------------------------------- + * 在该版本的环境下执行: pip,提示不是命名 + * 解决方案,在控制台执行命令 + python -m ensurepip + + * 该命令执行成功后会在 script 目录生成pip3等文件 + * 使用 pip3 代替原来的pip + + +-------------------------------- +python-whl文件安装 | +-------------------------------- + * 把whl文件下载到本地 + * 使用pip去执行 + pip install D:\Download\VideoCapture-0.9.5-cp27-none-win_amd64.whl \ No newline at end of file diff --git "a/Python/python-\345\237\272\347\241\200-\346\226\207\344\273\266\346\223\215\344\275\234.py" "b/Python/python-\345\237\272\347\241\200-\346\226\207\344\273\266\346\223\215\344\275\234.py" index 4c73732f..20675401 100644 --- "a/Python/python-\345\237\272\347\241\200-\346\226\207\344\273\266\346\223\215\344\275\234.py" +++ "b/Python/python-\345\237\272\347\241\200-\346\226\207\344\273\266\346\223\215\344\275\234.py" @@ -1,7 +1,7 @@ ---------------------------- Python-文件操作 | ---------------------------- - open() + open(file, mode, buffering, encoding, errors, newline, closefd, opener) * 全局函数,用于打开一个文件,如果文件不存在,抛出异常 * 返回一个对象:/ * 第一个参数,以字符形式表示文件路径,可以是绝对路径,也可以是相对路径 @@ -68,6 +68,7 @@ str read() * 返回相对于当前指针,文件的所有文本数据 * 第一个参数,表示要读取的字节个数,如果未指定,或者该参数为负数,则默认是所有 + * 读取到了文件末尾返回 None * '指针后移' bool readable() diff --git "a/Python/python-\345\237\272\347\241\200-\346\240\207\345\207\206\346\250\241\345\235\227.py" "b/Python/python-\345\237\272\347\241\200-\346\240\207\345\207\206\346\250\241\345\235\227.py" index 924bba3d..8faca2c9 100644 --- "a/Python/python-\345\237\272\347\241\200-\346\240\207\345\207\206\346\250\241\345\235\227.py" +++ "b/Python/python-\345\237\272\347\241\200-\346\240\207\345\207\206\346\250\241\345\235\227.py" @@ -86,7 +86,7 @@ 61. os:澶氭柟闈㈢殑鎿嶄綔绯荤粺鎺ュ彛 62. io:娴佹牳蹇冨伐鍏 63. time:鏃堕棿鐨勬煡璇笌杞寲 - 64. argparser:鍛戒护琛岄夐」銆佸弬鏁板拰瀛愬懡浠ょ殑瑙f瀽鍣 + 64. argparse:鍛戒护琛岄夐」銆佸弬鏁板拰瀛愬懡浠ょ殑瑙f瀽鍣 65. optparser:鍛戒护琛岄夐」瑙f瀽鍣 66. getopt:C椋庢牸鐨勫懡浠よ閫夐」瑙f瀽鍣 67. logging:Python鏃ュ織宸ュ叿 diff --git "a/Python/python-\345\237\272\347\241\200-\346\265\213\350\257\225\345\215\225\345\205\203.py" "b/Python/python-\345\237\272\347\241\200-\346\265\213\350\257\225\345\215\225\345\205\203.py" new file mode 100644 index 00000000..ad3e4588 --- /dev/null +++ "b/Python/python-\345\237\272\347\241\200-\346\265\213\350\257\225\345\215\225\345\205\203.py" @@ -0,0 +1,20 @@ +-------------------------------- +测试单元 | +-------------------------------- + import unittest + class MyTest(unittest.TestCase): + # 执行测试之前执行 + def setUp(self): + pass + + # 执行测试的方法,必须是由 test 开头 + def testDemo(self): + pass + + # 测试执行完毕后的清理工作 + def tearDown(self): + pass + + if __name__ == '__main__': + # 执行测试 + unittest.main() diff --git "a/Python/python-\345\276\201\351\200\224.py" "b/Python/python-\345\276\201\351\200\224.py" index 40ade0ff..b31a595d 100644 --- "a/Python/python-\345\276\201\351\200\224.py" +++ "b/Python/python-\345\276\201\351\200\224.py" @@ -19,31 +19,96 @@ 92(生产环境部署) ================================ -55-03 -================================ -1,文件操作中 readlines() 方法 -2,文件操作中的 truncate() 方法 - - - - - - - - - - - - - - - - - - - - - -================================ -1, open() 打开模式详解 -2, 文本文件中,内容替换 \ No newline at end of file +解压密码: + www.snowfox.wang +│ tree.txt +│ +├─01基础 +│ │ 第1节 linux操作系统基础.zip +│ │ 第2节 python语法基础.zip +│ │ 第3节 项目-飞机大战.zip +│ │ 补充资料.zip +│ │ +│ └─第1节 linux操作系统基础 +│ └─01.Linux以及命令 +│ └─视频 +│ 04-unix、minix、Linux操作系统的发展1.flv +│ +├─02Python核心编程 +│ 第1节 python高级编程.zip +│ 第2节 linux系统编程.zip +│ 第3节 网络编程.zip +│ 第4节 web服务器案例.zip +│ 第5节 正则表达式.zip +│ +├─03数据结构与算法 +│ 第1节 数据结构和算法基础.zip +│ 第2节 顺序表.zip +│ 第3节 栈与队列.zip +│ 第4节 链表.zip +│ 第5节 排序与搜索.zip +│ 第6节 树与树算法.zip +│ 资料.zip +│ +├─04数据库 +│ 第1节 MySQL.zip +│ 第2节 MongoDB.zip +│ 第3节 Redis.zip +│ +├─05前端 +│ 第1节 HTML.zip +│ 第2节 CSS.zip +│ 第3节 PhotoShop.zip +│ 第4节 HTML5+CSS3.zip +│ 第5节 移动端页面开发.zip +│ 第6节 JavaScript.zip +│ 第7节 JQuery.zip +│ 第8节 移动端JS库.zip +│ 第9节 Bootstrap.zip +│ 资料.zip +│ +├─06django +│ 第1节 Django.zip +│ 第2节 天天生鲜项目思路.zip +│ 第3节 Git.zip +│ 资料.zip +│ +├─07爬虫 +│ 第1节 Python爬虫基础类库.zip +│ 第2节 scrapy框架.zip +│ 第3节 scrapy-redis分布式策略.zip +│ 资料.zip +│ +├─08 tornado +│ 第1节 Tornado.zip +│ 第2节 爱家租房项目.zip +│ 第3节 微信公众号.zip +│ 资料.zip +│ +├─09shell和自动化运维 +│ 第1节 运维和shell.zip +│ 第2节 shell常用工具.zip +│ 第3节 nginx.zip +│ 第4节 自动化部署.zip +│ 资料.zip +│ +├─课件和资料 +│ django.zip +│ linux基础.zip +│ Python基础.zip +│ Python核心编程.zip +│ shell.zip +│ tornado.zip +│ 前端.zip +│ 微信公众号.zip +│ 数据库.zip +│ 数据结构和算法.zip +│ 爬虫.zip +│ +└─软件 + python环境及IDE.rar + 前端教学软件.rar + 安装环境教学参考文档.rar + 开班环境讲解.wmv + 虚拟机安装包及ubuntu16.04.rar + diff --git "a/Python/python-\346\225\260\346\215\256\347\261\273\345\236\213-bytes.py" "b/Python/python-\346\225\260\346\215\256\347\261\273\345\236\213-bytes.py" index 7e58e654..50d35d31 100644 --- "a/Python/python-\346\225\260\346\215\256\347\261\273\345\236\213-bytes.py" +++ "b/Python/python-\346\225\260\346\215\256\347\261\273\345\236\213-bytes.py" @@ -1,3 +1,13 @@ +-------------------- +bytes 绫绘柟娉 | +-------------------- + decode() + * 鎶婂瓧鑺傛暟鎹浆鎹负瀛楃,鍙兘杞崲 ASCII 鐮,涓嶈兘杞崲姹夊瓧 + + formhex() + * 鎶16杩涘埗瀛楃涓茶浆鎹负瀛楄妭 + + -------------------- bytes | -------------------- diff --git "a/Python/python-\346\250\241\345\235\227-\344\272\214\350\277\233\345\210\266/python-struct.py" "b/Python/python-\346\250\241\345\235\227-\344\272\214\350\277\233\345\210\266/python-struct.py" index bb14cfdd..3c954386 100644 --- "a/Python/python-\346\250\241\345\235\227-\344\272\214\350\277\233\345\210\266/python-struct.py" +++ "b/Python/python-\346\250\241\345\235\227-\344\272\214\350\277\233\345\210\266/python-struct.py" @@ -12,12 +12,100 @@ --------------------------- --------------------------- -struct-鏂规硶 | +struct-妯″潡鏂规硶 | --------------------------- - bytes pack() + bytes pack(fmt,v1,v2,v3....) * 鎶婃寚瀹氱殑鏁版嵁,杞崲涓哄瓧鑺傛暟鎹 * 鐨勭涓涓弬鏁版槸澶勭悊鎸囦护 '>I' >琛ㄧず瀛楄妭椤哄簭鏄痓ig-endian,涔熷氨鏄綉缁滃簭 I琛ㄧず4瀛楄妭鏃犵鍙锋暣鏁 - \ No newline at end of file + + unpack(fmt,buff) + * 鏍规嵁鎵缁欑殑fmt鎻忚堪鐨勬牸寮忓皢bytes鍙嶅悜瑙f瀽鍑烘潵,杩斿洖涓涓厓缁 + + calcsize(fmt) + * 鏍规嵁鎵缁欑殑fmt鎻忚堪鐨勬牸寮忚繑鍥炶缁撴瀯鐨勫ぇ灏 + +--------------------------- +struct-Struct | +--------------------------- + * 妯″潡绫,鎻愪緵闈㈠悜瀵硅薄鐨凙PI + * demo + values = (1, 'abc', 2.7) + s = struct.Struct('I3sf') + packed_data = s.pack(*values) + unpacked_data = s.unpack(packed_data) + + +--------------------------- +struct-fmt瀵圭収琛 | +--------------------------- + Format C Type Python type Standard size Notes + x pad byte no value + c char bytes of length 1 1 + b signed char integer 1 (1),(3) + B unsigned char integer 1 (3) + ? _Bool bool 1 (1) + h short integer 2 (3) + H unsigned short integer 2 (3) + i int integer 4 (3) + I unsigned int integer 4 (3) + l long integer 4 (3) + L unsigned long integer 4 (3) + q long long integer 8 (2), (3) + Q unsigned long long integer 8 (2), (3) + n ssize_t integer (4) + N size_t integer (4) + e (7) float 2 (5) + f float float 4 (5) + d double float 8 (5) + s char[] bytes + p char[] bytes + P void * integer (6) + +--------------------------- +缃戠粶瀛楄妭搴 | +--------------------------- + @ 鏈満椤哄簭 + = 鍘熺敓椤哄簭 + < little-endian,灏忕 + > big-endian,澶х + ! 涓>鐩稿悓 + + +--------------------------- +struct-demo | +--------------------------- + # 涓枃鐨勭紪鐮 + import struct + + message = '鍗фЫ銆傘傘傝繖涓氨绠楁槸甯︿腑鏂囦篃娌¢棶棰樼殑鍝熴傘' + + bin_message = message.encode(encoding='utf_8', errors='strict') + # + format = 'II%ds'%(len(bin_message)) + + packager = struct.Struct(format) + + package = packager.pack(5,15,bin_message) + + unpackage = packager.unpack(package) + + print(unpackage) + + print(unpackage[2].decode(encoding='utf_8', errors='strict')) + + + # 缂栫爜鍜岃В鐮佸嚱鏁 + # 瀵规暟鎹繘琛岀紪鐮,娣诲姞涓涓4瀛楄妭闀垮害鐨勬秷鎭暱搴﹀ご + def encode(data): + data = bytes(data,'UTF_8') + length = len(data) + return struct.pack('I%ds'%(length),length,data) + + + # 瀵规暟鎹繘琛岃В鐮,鍒嗙鍑烘潵鏁版嵁鍜岄暱搴﹀ご + def decode(data): + length = len(data) - 4 # 娑堟伅浣撶殑闀垮害瑕佸噺鍘绘秷鎭ご鐨勯暱搴 + return struct.unpack('I%ds'%(length),data) \ No newline at end of file diff --git "a/Python/python-\346\250\241\345\235\227-\344\272\222\350\201\224\347\275\221\345\215\217\350\256\256\344\270\216\346\224\257\346\214\201/python-http.py" "b/Python/python-\346\250\241\345\235\227-\344\272\222\350\201\224\347\275\221\345\215\217\350\256\256\344\270\216\346\224\257\346\214\201/python-http.py" new file mode 100644 index 00000000..bfb198f2 --- /dev/null +++ "b/Python/python-\346\250\241\345\235\227-\344\272\222\350\201\224\347\275\221\345\215\217\350\256\256\344\270\216\346\224\257\346\214\201/python-http.py" @@ -0,0 +1,28 @@ +------------------------ +http | +------------------------ + * 该模块提供了关于http通信的的一些对象 + + * http状态码对象: http.HTTPStatus + http.HTTPStatus.OK + http.HTTPStatus.CREATED + + + + +------------------------ +http.server | +------------------------ + * 类 + HTTPServer(socketserver.TCPServer) + BaseHTTPRequestHandler(socketserver.StreamRequestHandler) + SimpleHTTPRequestHandler(BaseHTTPRequestHandler) + CGIHTTPRequestHandler(SimpleHTTPRequestHandler) + + + * 方法 + nobody_uid() + executable(path) + test(HandlerClass=BaseHTTPRequestHandler,ServerClass=HTTPServer, protocol="HTTP/1.0", port=8000, bind="") + + diff --git "a/Python/python-\346\250\241\345\235\227-\344\272\222\350\201\224\347\275\221\345\215\217\350\256\256\344\270\216\346\224\257\346\214\201/python-socketserver.py" "b/Python/python-\346\250\241\345\235\227-\344\272\222\350\201\224\347\275\221\345\215\217\350\256\256\344\270\216\346\224\257\346\214\201/python-socketserver.py" index 8fb89054..4fbdacf8 100644 --- "a/Python/python-\346\250\241\345\235\227-\344\272\222\350\201\224\347\275\221\345\215\217\350\256\256\344\270\216\346\224\257\346\214\201/python-socketserver.py" +++ "b/Python/python-\346\250\241\345\235\227-\344\272\222\350\201\224\347\275\221\345\215\217\350\256\256\344\270\216\346\224\257\346\214\201/python-socketserver.py" @@ -13,6 +13,7 @@ |-ForkingUDPServer(支持多进程的UDPServer,仅仅在unix有效)) |-UnixDatagramServer BaseRequestHandler(Handle顶层类) + ThreadingMixIn ---------------------------- BaseServer | diff --git "a/Python/python-\346\250\241\345\235\227-\344\272\222\350\201\224\347\275\221\345\215\217\350\256\256\344\270\216\346\224\257\346\214\201/python-urllib.py" "b/Python/python-\346\250\241\345\235\227-\344\272\222\350\201\224\347\275\221\345\215\217\350\256\256\344\270\216\346\224\257\346\214\201/python-urllib.py" index a5cde25c..9fd82a5d 100644 --- "a/Python/python-\346\250\241\345\235\227-\344\272\222\350\201\224\347\275\221\345\215\217\350\256\256\344\270\216\346\224\257\346\214\201/python-urllib.py" +++ "b/Python/python-\346\250\241\345\235\227-\344\272\222\350\201\224\347\275\221\345\215\217\350\256\256\344\270\216\346\224\257\346\214\201/python-urllib.py" @@ -4,6 +4,7 @@ * 瀹冩槸涓涓寘,鍏锋湁妯″潡 request parse + * 涓绯诲垪鐢ㄤ簬鎿嶄綔URL鐨勫姛鑳 * 鍙互闈炲父鏂逛究鍦版姄鍙朥RL鍐呭 @@ -20,7 +21,7 @@ ---------------------------- * 鐢ㄤ簬鍙戣捣HTTP璇锋眰鐨勬ā鍧 * 鏂规硶 - http.client.HTTPResponse request.urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,*, cafile=None, capath=None, cadefault=False, context=None) + urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,*, cafile=None, capath=None, cadefault=False, context=None) * 鎵撳紑杩炴帴,杩斿洖瀹炰緥瀵硅薄(http.client.HTTPResponse) * 鍙傛暟鏄彲浠ヤ竴涓洰鏍 URL * 鎴栬呮槸涓涓 Request 瀵硅薄 @@ -28,12 +29,253 @@ * 鍏抽敭瀛楀弬鏁: data # 璇锋眰浣,瀛楄妭绫诲瀷 timeout # 瓒呮椂鏃堕棿 - + + urlretrieve(url,path,call,data) + * 鎵撳紑url,骞朵笖鎶婂搷搴旂殑鏁版嵁淇濆瓨鍒皃ath + * 鍙互鐢ㄤ簬涓嬭浇鍥剧墖/瑙嗛鐨勮繛鎺 + * 鍙傛暟 + url 璧勬簮璺緞 + path 涓嬭浇璺緞 + call 鍥炶皟,鍙互閫氳繃璇ュ嚱鏁拌幏鍙栧埌鏂囦欢澶у皬,宸蹭笅杞藉ぇ灏忕瓑鏁版嵁 + data post鍒版湇鍔″櫒鎻愪氦鐨勬暟鎹 + + Request(url) + * 鍒涘缓涓涓猺equest瀵硅薄,鍙互璁剧疆璇锋眰澶寸瓑淇℃伅 + + OpenerDirector() + * 鍒涘缓涓涓 opener 瀵硅薄 + + install_opener(opener) + * 鐢ㄦ潵鍒涘缓(鍏ㄥ眬)榛樿opener + * 璋冪敤 urlopen 灏嗕娇鐢ㄨapi install 鐨 opener + + build_opener(*handlers) + * 鍒涘缓涓涓 绌虹殑opener + +------------------------------------ +urllib-request-opener & handler | +------------------------------------ + * opener 鍙互鏈塶澶氫釜handler,hander鐢ㄤ簬澶勭悊涓嶅悓鐨勮姹傚拰搴旂敤鍦烘櫙 + * opener + * 鍒涘缓 + opener = request.OpenerDirector() + * 閫氳繃鏋勯犲嚱鏁板垱寤,杩斿洖娌℃湁浠讳綍handler鐨勪竴涓猳pener + + opener = request.build_opener(*handler) + * request 瀵硅薄鐩存帴鍒涘缓,浼氶粯璁ゆ坊鍔犱竴浜沨andler + + * 鏂规硶 + add_handler(handler) + * 娣诲姞handler + + open(fullurl, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT) + * 鎵ц璇锋眰 + * 鍙傛暟 + fullurl + * 鍙互鏄瓧绗︿覆鐨剈rl,涔熷彲浠ユ槸 Request 瀹炰緥瀵硅薄 + + * handler + * 绯荤粺棰勫畾涔夊鐞嗗櫒(request)绫 + AbstractHTTPHandler + ProxyDigestAuthHandler + HTTPDigestAuthHandler + AbstractDigestAuthHandler + AbstractBasicAuthHandler + HTTPSHandler + * https handler + HTTPRedirectHandler + CacheFTPHandler + BaseHandler + ProxyHandler({'http':'183.17.123.152:9000'}) + * 浠g悊handler,閫氳繃浠g悊鏈嶅姟鍣ㄥ彂閫乭ttp鏈嶅姟 + * 鍙傛暟涓篸ict:(涓嶄紶閫,鎴栬呬紶閫掔┖{},琛ㄧず涓嶄唬鐞) + key 琛ㄧず浠g悊鐨勫崗璁 + value ip:port + * 鍏峰瀵嗙爜鐨勪唬鐞嗘湇鍔″櫒 + ProxyHandler({'http':'kevin:123@183.17.123.152:9000'}) + + HTTPDefaultErrorHandler + HTTPBasicAuthHandler + * tomcat鐨刴anager椤甸潰,灏遍渶瑕佽韩浠介獙璇,灏卞彲浠ョ敤杩欎釜 + * demo + # 鍒涘缓瀵嗙爜绠$悊鍣 + realm = request.HTTPPasswordMgrWithDefaultRealm() + # 璁剧疆鍩/绔欑偣ip/璐︽埛/瀵嗙爜 鍒板瘑鐮佺鐞嗗櫒 + realm.add_password(None,'192.168.21.52','root','123456') + # 鍒涘缓http楠岃瘉handler + handler = request.HTTPBasicAuthHandler(realm) + # 鍒涘缓涓涓猳pener + opener = request.build_opener(handler) + # 鎵撳紑url + with opener.open('http://192.168.21.52/admin') as response: + print(response.read()) + + ProxyBasicAuthHandler + * 浠g悊鎺堟潈楠岃瘉,浣滅敤灏辨槸鐢ㄦ潵澶勭悊闇瑕佺敤鎴峰瘑鐮侀獙璇佺殑浠g悊鏈嶅姟鍣 + * 涓鑸笉鎬庝箞寤鸿浣跨敤,鐩存帴浣跨敤 ProxyHandler 灏辫浜 + * demo + # 鍒涘缓瀵嗙爜绠$悊鍣 + realm = HTTPPasswordMgrWithDefaultRealm() + # 瀵嗙爜绠$悊鍣,璁剧疆浠g悊鏈嶅姟鍣 鍩/ip:绔彛/鐢ㄦ埛鍚/瀵嗙爜 + realm.add_password(None,'183.17.123.152:9000','kevin','123') + # 娣诲姞瀵嗙爜绠$悊鍣ㄥ埌 ProxyBasicAuthHandler + proxyAuthHandler = ProxyBasicAuthHandler(realm) + # 鍒涘缓 opener + opener = request.build_opener(proxyAuthHandler) + with opener.open('http://javaweb.io') as response: + print(response.read()) + + + HTTPHandler + * http handler + FileHandler + FTPHandler + CacheFTPHandler + DataHandler + UnknownHandler + HTTPCookieProcessor + * BaseHandler 鐨勫瓙绫,鐢ㄤ簬澶勭悊鏈嶅姟鍣ㄧ鍝嶅簲鐨刢ookie鏁版嵁 + + * 閮ㄥ垎handler閫氱敤鐨勬瀯閫犲嚱鏁板弬鏁 + debuglevel + * 濡傛灉璇ュ间负1/True,鍒欒〃绀哄紑鍚疍EBUG妯″紡,浼氭墦鍗板嚭涓浜涙棩蹇椾俊鎭 + + * Realm + |-HTTPPasswordMgr + |-HTTPPasswordMgrWithDefaultRealm + * 榛樿鐨勫瘑鐮佺鐞嗗璞,鐢ㄤ簬淇濆瓨鍜宧ttp璇锋眰鐩稿叧鐨勬巿鏉冧俊鎭 + * 涓鑸細鐢ㄥ湪涓や釜鍦版柟: + * ProxyBasicAuthHandler - 鎺堟潈浠g悊鐨勫鐞嗗櫒 + * HTTPBasicAuthHandler - 楠岃瘉web瀹㈡埛绔殑鎺堟潈澶勭悊鍣 + + * simple demo + # 鎵嬪姩鍒涘缓opener + opener = request.OpenerDirector() + # + # 鎵嬪姩娣诲姞涓涓 HTTPHandler,鍙互澶勭悊http璇锋眰 + opener.add_handler(request.HTTPHandler()) + + + # 閫氳繃request build 涓涓猳pener,浼氭湁涓浜涢粯璁ょ殑handler + opener = request.build_opener(request.HTTPHandler(debuglevel=1),request.HTTPSHandler(debuglevel=True)) + + # 鏋勫缓璇锋眰淇℃伅 + req = request.Request('http://www.baidu.com') + + # 閫氳繃 opener 鎵撳紑 + with opener.open(req) as response: + print(response.read()) + + * ip浠g悊 demo + from urllib import request + # 閫氳繃request build 涓涓猳pener,浼氭湁涓浜涢粯璁ょ殑handler + opener = request.build_opener() + # 鍒涘缓涓涓唬鐞唄andler,鎸囧畾浠g悊鐨勫崗璁,浠g悊鏈嶅姟鍣ㄧ殑ip涓庣鍙 + proxyHandler = request.ProxyHandler({'http':'112.114.93.39:8118'}) + + ''' + 濡傛灉浠g悊鏈嶅姟鍣ㄩ渶瑕佽处鎴峰悕/瀵嗙爜鎺堟潈:{'鍗忚':'鐢ㄦ埛鍚:瀵嗙爜@ip:port'} + ProxyHandler({'http':'kevin:123@183.17.123.152:9000'}) + ''' + + # 娣诲姞浠g悊handler鍒皁pener + opener.add_handler(proxyHandler) + # 鏋勫缓璇锋眰淇℃伅 + req = request.Request('http://javaweb.io') + # 閫氳繃 opener 鎵撳紑 + with opener.open(req) as response: + print(response.read()) + + * 绔欑偣韬唤楠岃瘉 demo + from urllib import request + # 鍒涘缓瀵嗙爜绠$悊鍣 + realm = request.HTTPPasswordMgrWithDefaultRealm() + # 璁剧疆鍩/绔欑偣ip/璐︽埛/瀵嗙爜 鍒板瘑鐮佺鐞嗗櫒 + realm.add_password(None,'192.168.21.52','root','123456') + # 鍒涘缓http楠岃瘉handler + handler = request.HTTPBasicAuthHandler(realm) + # 鍒涘缓涓涓猳pener + opener = request.build_opener(handler) + # 鎵撳紑url + with opener.open('http://192.168.21.52/admin') as response: + print(response.read()) + +----------------------------------------------- +cookielib & request.HTTPCookieProcessor | +------------------------------------------------ + * cookielib 鏄嫭绔嬬殑搴,闇瑕佸崟鐙亾瀵煎叆,涓昏浣滅敤灏辨槸鐢ㄦ潵淇濆瓨Cookie + * python2.7涓ā鍧楀悕: cookielib + * python3.x涓ā鍧楀悕: http.cookiejar (from http import cookiejar) + * cookielib 妯″潡涓昏鐨勫璞 + CookieJar(甯哥敤) + * 绠$悊HTTP cookie 鍊,瀛樺偍http璇锋眰鐢熸垚鐨刢ookie,鍚戝彂璧风殑http璇锋眰娣诲姞cookie瀵硅薄 + * cookie閮藉瓨鍌ㄥ湪鍐呭瓨涓,CookieJar 瀹炰緥琚瀮鍦惧洖鏀跺悗,cookie涔熶細娑堝け + + FileCookieJar(filename,delayload=None,policy=None) + * CookieJar 鐨勫瓙绫,浼氭妸cookie淇℃伅鎸佷箙鍖栧埌纭洏 + * 鏋勯犲弬鏁 + filename 瀛樺偍cookie鐨勬枃浠跺悕 + delayload bool鍊,鏄惁鏀寔寤惰繜鍔犺浇(闇瑕佺殑鏃跺欐墠鍥炲幓璇诲彇cookie鏂囦欢) + + MozillaCookieJar + * FileCookieJar 瀛愮被,鍒涘缓涓 Mozilla鍐呮牳娴忚鍣ㄥ吋瀹圭殑 FileCookieJar 瀹炰緥 + + LWPCookieJar + * FileCookieJar 瀛愮被,鍒涘缓涓 libwww-per 鏍囧噯鐨 Set-Cookie3 鍏煎鐨 FileCookieJar 瀹炰緥 + + * HTTPCookieProcessor 鏄 urllib.request 鐨勫璞(Handler绯诲垪),浣滅敤灏辨槸淇濆瓨鏈嶅姟鍣ㄥ搷搴旂殑cookie + + * cookie澶勭悊 demo + + from urllib import request + from http import cookiejar + # 鍒涘缓 cookie 瀵硅薄,鐢ㄤ簬淇濆瓨cookie淇℃伅 + cookie = cookiejar.CookieJar() + # 鍒涘缓 HTTPCookieProcessor 瀵硅薄(Cookie澶勭悊鍣ㄥ璞) + # 杩斿洖鐨勫氨鏄竴涓鐞嗗櫒瀵硅薄 + cookieHandler = request.HTTPCookieProcessor(cookie) + # 鍒涘缓opener + opener = request.build_opener() + # 娣诲姞cookie澶勭悊鍣ㄥ璞″埌opener + opener.add_handler(cookieHandler) + with opener.open('http://javaweb.io') as response: + print(cookie) + # ]> + + # 姝ゆ椂,璇 opener 宸茬粡鏈変簡璇ookie,濡傛灉璇 opener 鍐嶆鍙戣捣璇锋眰,浼氭惡甯ookie + + * 鐧诲綍 javaweb.io 瀹炴垬 + from urllib import request,parse + from http import cookiejar + cookie = cookiejar.CookieJar() + cookieHandler = request.HTTPCookieProcessor(cookie) + opener = request.build_opener() + opener.add_handler(cookieHandler) + # 鎵撳紑椤甸潰,鑾峰彇鏈嶅姟鍣ㄧ殑cookie + with opener.open('http://javaweb.io') as response: + # 浣跨敤璇ookie閲嶅畾鍚戝埌鐧诲綍椤甸潰(澶氫綑鎿嶄綔) + with opener.open('http://javaweb.io/login') as response: + # 閫氳繃璇ookie璇诲彇楠岃瘉鐮佹暟鎹 + with opener.open('http://javaweb.io/verifycode') as response: + # 杈撳叆閫氳繃鍚庡彴鏃ュ織鐪嬪埌鐨勯獙璇佺爜淇℃伅 + verifyCode = input('楠岃瘉鐮:') + req = request.Request('http://javaweb.io/login') + requestBody = parse.urlencode({ + 'name': 'root', + 'pass': 'root', + 'verifyCode': verifyCode, + }) + # 閫氳繃璇ookie鎵ц鐧诲綍 + with opener.open(req,data=bytes(requestBody,'utf_8')) as response: + # 鐧诲綍ok,鍥犱负璇ookie宸茬粡鍏峰鐧诲綍鍑瘉,鍙互杩涘叆涓婚〉 + with opener.open('http://javaweb.io') as response: + print(response.read().decode()) + ---------------------------- urllib-HTTPResponse | ---------------------------- - * Http鍝嶅簲瀵硅薄 + * Http鍝嶅簲瀵硅薄,涔熷氨鏄 request.open() 鍚庤繑鍥炵殑瀵硅薄 * 瀹炰緥灞炴 status * HTTP鐘舵佺爜 @@ -81,13 +323,20 @@ ---------------------------- * 鍙互鎶婂弬鏁拌В鏋愪负URL缂栫爜鐨勬ā鍧 * 鏂规硶 - str urlencode(query, doseq, safe, encoding, errors, quote_via) + urlencode(query, doseq, safe, encoding, errors, quote_via) * 鎶婂弬鏁拌В鏋愪负URL缂栫爜鐨勫瓧绗︿覆 * query鍙傛暟鍙互鏄痆(k,v)],涔熷彲浠ユ槸{k:v} - [(,)] parse_qs(qs, keep_blank_values, strict_parsing, encoding, errors) + parse_qs(qs, keep_blank_values, strict_parsing, encoding, errors) * 鎶奤RL缂栫爜鐨勫瓧绗﹁В鏋愪负[],鍒楄〃涓殑姣忎釜鍏冪礌閮芥槸涓涓 tuple * 灏辨槸鎶妅ey value 璇锋眰浣撹浆鎹负[(key,value)] + + quote(string, safe='/', encoding=None, errors=None) + * 鎶婁竴涓甫姹夊瓧鐨剈rl,杩涜url缂栫爜 + + unquote(string, encoding='utf-8', errors='replace') + * 鎶婁竴涓猽rl缂栫爜鐨勫瓧绗︿覆,杞崲涓烘眽瀛梪rl + * demo parse.urlencode([ ('username', 'Kevin'), @@ -98,6 +347,19 @@ ('ec', ''), ('pagerefer', 'https://passport.weibo.cn/signin/welcome?entry=mweibo&r=http%3A%2F%2Fm.weibo.cn%2F') ]) + + * 浣跨敤 parse.quote 澶勭悊涓枃url(瀵逛腑鏂囪繘琛寀rl缂栫爜) + + r = parse.quote('http://www.javaweb.io/浣犲ソ鎴戞槸KevinBlandy/') + print(r) + # http%3A//www.javaweb.io/%E4%BD%A0%E5%A5%BD%E6%88%91%E6%98%AFKevinBlandy/ + # 杩 http:// 鐨:涔熻杞箟浜,杩欎釜鏄剧劧鏄笉琛岀殑 + + r = parse.quote('http://www.javaweb.io/浣犲ソ鎴戞槸KevinBlandy/',safe='://') + print(r) + # http://www.javaweb.io/%E4%BD%A0%E5%A5%BD%E6%88%91%E6%98%AFKevinBlandy/ + + * 閫氳繃鍏抽敭瀛楀弬鏁 safe 鏉ユ寚瀹氬摢浜涘瓧绗︿覆涓嶇敤杞箟 diff --git "a/Python/python-\346\250\241\345\235\227-\345\205\266\344\273\226/py-asyncio.py" "b/Python/python-\346\250\241\345\235\227-\345\205\266\344\273\226/py-asyncio.py" new file mode 100644 index 00000000..5268334d --- /dev/null +++ "b/Python/python-\346\250\241\345\235\227-\345\205\266\344\273\226/py-asyncio.py" @@ -0,0 +1,55 @@ +--------------------------- +寮傛io妯″潡 | +--------------------------- + * 鐢╝syncio鎻愪緵鐨 @asyncio.coroutine 鍙互鎶婁竴涓猤enerator鏍囪涓篶oroutine绫诲瀷,鐒跺悗鍦╟oroutine鍐呴儴鐢▂ield from璋冪敤鍙︿竴涓 coroutine 瀹炵幇寮傛鎿嶄綔 + * 涓轰簡绠鍖栧苟鏇村ソ鍦版爣璇嗗紓姝O锛屼粠Python 3.5寮濮嬪紩鍏ヤ簡鏂扮殑璇硶async鍜宎wait,鍙互璁ヽoroutine鐨勪唬鐮佹洿绠娲佹槗璇 + * 璇锋敞鎰,async鍜宎wait鏄拡瀵筩oroutine鐨勬柊璇硶,瑕佷娇鐢ㄦ柊鐨勮娉,鍙渶瑕佸仛涓ゆ绠鍗曠殑鏇挎崲 + * 姝ラ + 1,鐢熸垚鍣ㄤ笉浣跨敤 @asyncio.coroutine 娉ㄨВ鏍囪瘑,鑰屼娇鐢 async 鍏抽敭瀛楁爣璇 + 2,涓嶄娇鐢 yield from 璇彞,鑰屼娇鐢 await 鍏抽敭瀛 + * demo + import asyncio + async def test(name): + print('%s 寮濮嬫墽琛'%(name)) + await asyncio.sleep(1) + print('%s 鎵ц瀹屾瘯' % (name)) + + loop = asyncio.get_event_loop() + loop.run_until_complete(asyncio.wait([test('Kevin'),test('Litch'),test('Rocco')])) + loop.close() + + Kevin 寮濮嬫墽琛 + Litch 寮濮嬫墽琛 + Rocco 寮濮嬫墽琛 + Kevin 鎵ц瀹屾瘯 + Litch 鎵ц瀹屾瘯 + Rocco 鎵ц瀹屾瘯 + + * event_loop api + create_task() + run_until_complete() + run_forever() + create_server() + close() + stop() + +--------------------------- +妯″潡鍑芥暟/灞炴 | +--------------------------- + Protocol + + sleep() + * 寮傛鐨勭嚎绋嬪仠姝 + + get_event_loop() + + wait() + gather() + * 璺 wait() 涓鏍,涓嶈繃瀹冧細杩斿洖涓涓猍],閲岄潰鐨勬瘡涓厓绱犳槸姣忎釜鍗忕▼鎵ц瀹屾瘯鍚庣殑缁撴灉 + + ensure_future() + as_completed() + open_connection(ip, port) + * 鎵撳紑涓涓紓姝O杩炴帴 + * 杩斿洖 (reader,writer) 鍏冪粍 + \ No newline at end of file diff --git "a/Python/python-\346\250\241\345\235\227-\345\205\266\344\273\226/py-dataclasses.py" "b/Python/python-\346\250\241\345\235\227-\345\205\266\344\273\226/py-dataclasses.py" new file mode 100644 index 00000000..d26463b0 --- /dev/null +++ "b/Python/python-\346\250\241\345\235\227-\345\205\266\344\273\226/py-dataclasses.py" @@ -0,0 +1,43 @@ +----------------------------- +dataclasses | +----------------------------- + * 3.7的新模块 + +----------------------------- +dataclasses-自动生成类属性 | +----------------------------- + * 模块函数 + dataclass(_cls=None, *, init=True, repr=True, eq=True, order=False,unsafe_hash=False, frozen=False) + + - init 生成__init__方法 + - repr 生成__repr__方法 + * 生成的repr字符串将具有类名以及每个字段的名称和repr,按照它们在类中定义的顺序 + * 标记为从repr中排除的字段不包括在内,例如: InventoryItem(name ='widget', unit_price = 3.0,quantity_on_hand = 10) + * 如果类已定义__repr__,则忽略此参数 + - eq 生成__eq__方法 + * 此方法按顺序比较类,就好像它是字段的元组一样,比较中的两个实例必须是相同的类型 + * 如果类已定义__eq__,则忽略此参数 + - order 生成__lt __,__ le __, __ gt__和__ge__方法 + * 些按顺序将类比较为它的字段元组,比较中的两个实例必须是相同的类型 + * 如果 order为true且eq为false.则引发ValueError。 + * 如果类已经定义任何的__lt__,__le__,__gt__,或__ge__,然后ValueError异常升高(???) + + - unsafe_hash + - frozen + + * demo + import dataclasses + + @dataclasses.dataclass + class User(): + # 声明属性名称以及属性的类型 + id: int + name: str + # 默认值 + join: bool = True + + def __str__(self): + return 'id=%d,name=%s,join=%s' % (self.id, self.name, self.join) + + print(User(1, 'KevinBlandy')) # id=1,name=KevinBlandy,join=True + print(User(1, 'KevinBlandy', False)) # id=1,name=KevinBlandy,join=False diff --git "a/Python/python-\346\250\241\345\235\227-\345\271\266\345\217\221/python-subprocess.py" "b/Python/python-\346\250\241\345\235\227-\345\271\266\345\217\221/python-subprocess.py" index 1227c4a2..c3945944 100644 --- "a/Python/python-\346\250\241\345\235\227-\345\271\266\345\217\221/python-subprocess.py" +++ "b/Python/python-\346\250\241\345\235\227-\345\271\266\345\217\221/python-subprocess.py" @@ -7,40 +7,133 @@ ---------------------------- subprocess-属性 | ---------------------------- - PIPE + PIPE = -1 + STDOUT = -2 + DEVNULL = -3 ---------------------------- subprocess-函数 | ---------------------------- - Popen() - * 开启一个子进程,执行一个shell命令,返回一个 Popen 对象 + Popen def __init__(self, args, bufsize=-1, executable=None, + stdin=None, stdout=None, stderr=None, + preexec_fn=None, close_fds=True, + shell=False, cwd=None, env=None, universal_newlines=None, + startupinfo=None, creationflags=0, + restore_signals=True, start_new_session=False, + pass_fds=(), *, encoding=None, errors=None, text=None) + + * 开启(创建)一个子进程,执行一个shell命令,返回一个 Popen 对象 * 参数可以是数组,第一个元素是命令,剩下的则是命令参数 Popen(["mkdir","t1"]) * 参数也可以直接就是一个命令 Popen("mkdir t2", shell=True) + * 建议使用 with 语句 + with subprocess.Popen(...) as popen: + pass + * 关键字参数 shell * bool 值 + * shell作为要执行的程序,如果shell值为True,则建议将args参数作为一个字符串传递而不要作为一个序列传递 + stdout * 枚举值,指定命令执行后输出内容的管道 * subprocess.PIPE :由子进程转到父进程 + + cwd + * 如果该值不为None,则切换到该目录执行命令 + + env + * 环境变量,如果该值为None那么子进程的环境变量将从父进程中继承 + * 如果env!=None,它的值必须是一个映射对象 + + universal_newlines + * 如果该参数值为True,则该文件对象的stdin,stdout和stderr将会作为文本流被打开 + * 否则他们将会被作为二进制流被打开 + str getoutput(commond) * 执行SHELL命令,返回执行后的结果 tuple getstatusoutput(commond) * 执行shell命令,返回元组,第一个数据是状态int值,第二个数据是执行后的结果 + + check_output() + * 父进程等待子进程完成 + * 如果执行异常会抛出:subprocess.CalledProcessError + * 返回子进程向标准输出的输出结果 + * 关键字参数 + stderr + shell + * demo + subprocess.check_output('dir',stderr=subprocess.STDOUT,shell=True) + + run(*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs) + * 执行指定的命令,等待命令执行完成后返回一个包含执行结果的 CompletedProcess 类的实例 + popenargs + * 要执行的shell命令,默认应该是一个字符串序列,如['df', '-Th']或('df', '-Th') + * 也可以是一个字符串,如'df -Th',但是此时需要把shell参数的值置为True + + check + * 如果check参数的值是True,且执行命令的进程以非0状态码退出,则会抛出一个CalledProcessError的异常,且该异常对象会包含 参数,退出状态码,以及stdout和stderr(如果它们有被捕获的话) + + ---------------------------- subprocess.Popen | ---------------------------- - * 实例函数 + # 实例属性 + stdout + stdint + stderr + * 返回标准的输入,输出,异常流 + pid + * 返回pid + + # 实例函数 stdout.read() * 读取执行结果,返回的结果是字节数据类型 - * 参数传递字符串,表示编码类型,不传默认使用系统编码 - * demo + * 标准输出 + + wait() + * 当前线程阻塞,直到子进程的内容执行完毕 + * 关键字参数 + timeout + * 指定超时的时间(秒),如果超时会抛出异常:TimeoutExpired + + poll() + * 检查子进程是否结束,如果结束了返回状态码,否则返回 None + + terminate() + * 停止该进程 + + kill() + * 杀死该进程 + + send_signal(signal) + * 给进程发送信号 + + communicate(input=None, timeout=None) + * 与进程进行交互,比如发送数据到stdin,从stdout和stderr读取数据,直到到达文件末尾 + * 该方法中的可选参数 input 应该是将被发送给子进程的数据,或者如没有数据发送给子进程,该参数应该是None + * input参数的数据类型必须是字节串,如果 universal_newlines 参数值为True,则input参数的数据类型必须是字符串 + * 该方法返回一个元组(stdout_data, stderr_data),这些数据将会是字节穿或字符串(如果universal_newlines的值为True) + * 如果在timeout指定的秒数后该进程还没有结束,将会抛出一个TimeoutExpired异常,捕获这个异常,然后重新尝试通信不会丢失任何输出的数据 + * 但是超时之后子进程并没有被杀死,为了合理的清除相应的内容,一个好的应用应该手动杀死这个子进程来结束通信 + * 要注意的是,这里读取的数据是缓冲在内存中的,所以,如果数据大小非常大或者是无限的,就不应该使用这个方法 + + + # demo import subprocess - popen = subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE) - # stdout=subprocess.PIPE 会把子进程执行的结果数据,传递给父进程.最终赋值给执行后的对象.如果没有该参数,会直接打印,而返回对象不能读取到执行结果 - result = popen.stdout.read() - print(str(result,'GBK')) \ No newline at end of file + with subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) as popen: + # stdout=subprocess.PIPE 会把子进程执行的结果数据,传递给父进程.最终赋值给执行后的对象.如果没有该参数,会直接打印,而返回对象不能读取到执行结果 + result = popen.stdout.read() # 读取输出 + error = popen.stderr.read() # 读取异常输出,如果没有异常,该值为None + print(str(result,'GBK')) + + + with subprocess.Popen("python",shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) as popen: + popen.stdin.write(b"print \"Hello\"") + (out,err) = popen.communicate() + print(out)#b'Hello\n' + print(err)#b'' \ No newline at end of file diff --git "a/Python/python-\346\250\241\345\235\227-\345\271\266\345\217\221/python-threading.py" "b/Python/python-\346\250\241\345\235\227-\345\271\266\345\217\221/python-threading.py" index f842ca87..27860cb9 100644 --- "a/Python/python-\346\250\241\345\235\227-\345\271\266\345\217\221/python-threading.py" +++ "b/Python/python-\346\250\241\345\235\227-\345\271\266\345\217\221/python-threading.py" @@ -56,7 +56,7 @@ * 鍚姩绾跨▼鎵ц join() - * 绛夊緟褰撳墠绾跨▼鎵ц瀹屾瘯鍚庢墠浼氬線涓嬫墽琛,璇ユ柟娉曚娇寰楀绾跨▼鍙樺緱鏃犳剰涔 + * 绛夊緟褰撳墠(self)绾跨▼鎵ц瀹屾瘯鍚庢墠浼(褰撳墠杩涚▼)寰涓嬫墽琛,璇ユ柟娉曚娇寰楀绾跨▼鍙樺緱鏃犳剰涔 getName() * 鑾峰彇绾跨▼鐨勫悕绉 diff --git "a/Python/python-\346\250\241\345\235\227-\346\225\260\346\215\256\347\261\273\345\236\213/python-collections.py" "b/Python/python-\346\250\241\345\235\227-\346\225\260\346\215\256\347\261\273\345\236\213/python-collections.py" index f46370a5..a81c09ca 100644 --- "a/Python/python-\346\250\241\345\235\227-\346\225\260\346\215\256\347\261\273\345\236\213/python-collections.py" +++ "b/Python/python-\346\250\241\345\235\227-\346\225\260\346\215\256\347\261\273\345\236\213/python-collections.py" @@ -71,7 +71,7 @@ ------------------------------- * 鏅 dict 鏄棤搴忕殑 * 璇 dict 鏄湁搴忕殑 dict - * OrderedDict 鐨凨ey浼氭寜鐓ф彃鍏ョ殑椤哄簭鎺掑垪,涓嶆槸Key鏈韩鎺掑簭 + * OrderedDict 鐨凨ey浼氭寜鐓ф彃鍏ョ殑椤哄簭鎺掑垪,涓嶆槸Key鏈韩鎺掑簭(缁ф壙涓嶥ict) * 鍒涘缓 collections.OrderedDict([('b', 2),('a', 1) , ('c', 3)]) @@ -83,10 +83,14 @@ print(dic) * api - tuple popitem() + tuple popitem(last = True) * 鍒犻櫎鍏冪礌,骞朵笖杩斿洖 * 鍏抽敭瀛楀弬鏁,鏄惁鏄垹闄ゆ渶鍚庝竴涓厓绱 last # 榛樿鍊:True + + None move_to_end(key, last=True) + * 绉婚櫎鍏冪礌鍒版渶鍚/鍓 ,鍓嶅悗鍙栧喅浜庡弬鏁:last + * 鍙互瀹炵幇涓涓 FIFO(鍏堣繘鍏堝嚭)鐨 dict,褰撳閲忚秴鍑洪檺鍒舵椂鍏堝垹闄ゆ渶鏃╂坊鍔犵殑Key diff --git "a/Python/python-\346\250\241\345\235\227-\346\226\207\346\234\254\347\233\270\345\205\263/python-re.py" "b/Python/python-\346\250\241\345\235\227-\346\226\207\346\234\254\347\233\270\345\205\263/python-re.py" index 6b1d6f34..71ec4bfc 100644 --- "a/Python/python-\346\250\241\345\235\227-\346\226\207\346\234\254\347\233\270\345\205\263/python-re.py" +++ "b/Python/python-\346\250\241\345\235\227-\346\226\207\346\234\254\347\233\270\345\205\263/python-re.py" @@ -38,12 +38,18 @@ list findall(pattern, string, flags) * 使用 pattern 表达式去匹配出 string 中的所有符合规则的元素 * flags,用于指定匹配模式,默认值为 0 + * flags,标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等 list split(pattern, string) * 分隔 str sub(pattern,repl,string,count=0,flags=0) - * 正则,目标字符串,替换值,替换多少次 + * 参数 + pattern 正则 + repl 是一个函数,会把匹配到的值传递给它,把计算结果替换原来的值 + string 目标字符串 + count 替换值,替换多少次(默认 0 表示替换所有的匹配) + flags ... callable_iterator finditer(pattern, string, flags) * 使用 pattern 表达式去匹配出 string 中的所有符合规则的元素,返回迭代器 @@ -57,38 +63,78 @@ _sre.SRE_Pattern compile(pattern,flags) * 把一个字符串编译成 pattern 对象用于匹配或搜索 + + + search(string) + * 将string中的正则表达式元字符如*/+/?等之前加上转义符再返回,在需要大量匹配元字符时有那么一点用 ------------------------ -_sre.SRE_Pattern 方法 | +_sre.SRE_Pattern | ------------------------ - _sre.SRE_Match match(str,start,end) - * 匹配 str 字符串,匹配成功,返回 _sre.SRE_Match 对象,匹配失败返回 None - * start,end 默认值为字符串的第一个最后一个 - - _sre.SRE_Match search(str,start,end) - * 返回 str 字符串,匹配到的第一个值,匹配失败返回 None - * start,end 默认值为字符串的第一个最后一个 - * '仅仅匹配开头,匹配成功后,string还有剩余字符,也视为匹配成功' - - list findall(str,start,end) - * 返回 str 字符串,匹配到的所有值 - * start,end 默认值为字符串的第一个最后一个 + * 方法 + _sre.SRE_Match match(str,start,end) + * 匹配 str 字符串,匹配成功,返回 _sre.SRE_Match 对象,匹配失败返回 None + * start,end 默认值为字符串的第一个最后一个 + + _sre.SRE_Match search(str,start,end) + * 返回 str 字符串,匹配到的第一个值,匹配失败返回 None + * start,end 默认值为字符串的第一个最后一个 + * '仅仅匹配开头,匹配成功后,string还有剩余字符,也视为匹配成功' + + list findall(str,start,end) + * 返回 str 字符串,匹配到的所有值 + * start,end 默认值为字符串的第一个最后一个 + + callable_iterator finditer(str,start,end) + * 返回 str 字符串,匹配到的所有值,返回的是一个迭代器 + * start,end 默认值为字符串的第一个最后一个 + +------------------------ +_sre.SRE_Match | +------------------------ + * Match对象是一次匹配的结果,包含了很多关于此次匹配的信息 + * 属性 + string 匹配时使用的文本 + re 匹配时使用的Pattern对象 + pos 文本中正则表达式开始搜索的索引,值与Pattern.match()和Pattern.seach()方法的同名参数相同 + endpos 文本中正则表达式结束搜索的索引,值与Pattern.match()和Pattern.seach()方法的同名参数相同 + lastindex 最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None。 + lastgroup 最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将为None。 - callable_iterator finditer(str,start,end) - * 返回 str 字符串,匹配到的所有值,返回的是一个迭代器 - * start,end 默认值为字符串的第一个最后一个 + * 方法 + str group(num) + * 返回 match object 中的字符串. + * 每一个 ( ) 都是一个分组,分组编号从1开始,从左往右,每遇到一个左括号,分组编号+1 + * 组 0 总是存在的,它就是整个表达式(原字符串) + * 没有参数时,num默认为0这时返回整个匹配到的字符串 + * 指定一个参数(整数)时,返回该分组匹配到的字符串 + * 指定多个参数时,返回由那几个分组匹配到的字符串组成的 tuple + + tuple groups() + * 获取到所有的匹配项 + + dict groupdict() + + start() + end() + span() + * 获取匹配成功的第一个子串在字符串中的角标,留头不留尾 + * 返回元组,其实就是:(start(group), end(group)) + expand() ------------------------ -_sre.SRE_Match 方法 | +正则表达式修饰符 | ------------------------ + * 多数正则方法的第三个参数(flags) + * 正则表达式可以包含一些可选标志修饰符来控制匹配的模式 + * 修饰符被指定为一个可选的标志,多个标志可以通过按位 OR(|) 它们来指定 + 如 re.I | re.M 被设置成 I 和 M 标志 - str group(num) - * 返回 match object 中的字符串. - * 每一个 ( ) 都是一个分组,分组编号从1开始,从左往右,每遇到一个左括号,分组编号+1 - * 组 0 总是存在的,它就是整个表达式(原字符串) - * 没有参数时,num默认为0这时返回整个匹配到的字符串 - * 指定一个参数(整数)时,返回该分组匹配到的字符串 - * 指定多个参数时,返回由那几个分组匹配到的字符串组成的 tuple - - tuple groups() - * 获取到所有的匹配项 \ No newline at end of file + * 修饰符 描述 + re.I 使匹配对大小写不敏感 + re.L 做本地化识别(locale-aware)匹配 + re.M 多行匹配,影响 ^ 和 $ + re.S 使 . 匹配包括换行在内的所有字符(全文匹配) + re.U 根据Unicode字符集解析字符,这个标志影响 \w, \W, \b, \B. + re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解 + * 这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释 diff --git "a/Python/python-\346\250\241\345\235\227-\346\227\266\351\227\264\347\233\270\345\205\263/python-time.py" "b/Python/python-\346\250\241\345\235\227-\346\227\266\351\227\264\347\233\270\345\205\263/python-time.py" index 3e4aebc8..12c3e7e2 100644 --- "a/Python/python-\346\250\241\345\235\227-\346\227\266\351\227\264\347\233\270\345\205\263/python-time.py" +++ "b/Python/python-\346\250\241\345\235\227-\346\227\266\351\227\264\347\233\270\345\205\263/python-time.py" @@ -144,4 +144,19 @@ %y 涓嶅甫涓栫邯鐨勫崄杩涘埗骞翠唤锛堝间粠0鍒99锛 %Y 甯︿笘绾儴鍒嗙殑鍗佸埗骞翠唤 %z %Z 鏃跺尯鍚嶇О锛屽鏋滀笉鑳藉緱鍒版椂鍖哄悕绉板垯杩斿洖绌哄瓧绗︺ - %% 鐧惧垎鍙 \ No newline at end of file + %% 鐧惧垎鍙 + + +------------------------ +time-甯哥敤鍑芥暟 | +------------------------ + # 鑾峰彇褰撳墠鏃堕棿 + import time + now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) + print(now) + # 2018-04-03 12:27:44 + + # 鎶婂瓧绗︿覆鏃堕棿,鎸夌収鎸囧畾鏍煎紡杞崲涓烘椂闂村璞 + import time + now = time.strptime('2018-04-03 12:27:44', '%Y-%m-%d %H:%M:%S') + print(now) diff --git "a/Python/python-\346\250\241\345\235\227-\346\240\207\347\255\276\350\257\255\350\250\200\347\233\270\345\205\263/python-xml.py" "b/Python/python-\346\250\241\345\235\227-\346\240\207\347\255\276\350\257\255\350\250\200\347\233\270\345\205\263/python-xml.py" index 7111909b..d1ef1aef 100644 --- "a/Python/python-\346\250\241\345\235\227-\346\240\207\347\255\276\350\257\255\350\250\200\347\233\270\345\205\263/python-xml.py" +++ "b/Python/python-\346\250\241\345\235\227-\346\240\207\347\255\276\350\257\255\350\250\200\347\233\270\345\205\263/python-xml.py" @@ -1,7 +1,22 @@ ------------------------ xml | ------------------------ - * 操作xml的api + * 该模块提供了大量操作xml的模块与类库 + xml.etree.ElementTree:树形XML元素API + + xml.dom:XML DOM API + xml.dom.minidom:XML DOM最小生成树 + xml.dom.pulldom:构建部分DOM树的支持 + + xml.sax:SAX2解析的支持 + xml.sax.handler:SAX处理器基类 + xml.sax.saxutils:SAX工具 + xml.sax.xmlreader:SAX解析器接口 + + xml.parsers.expat:运用Expat快速解析XML + + + * dom操作xml的api * 内置模块 xml.etree.ElementTree @@ -54,6 +69,7 @@ ------------------------ xml-xml.etree.ElementTree.ElementTree| ------------------------ + * 树形XML元素API xml.etree.ElementTree.ElementTree parse(file) * 解析xml文件,返回对象 @@ -100,14 +116,15 @@ # 自定义 解析Handler处理里 class DefaultSaxHandler(object): - # 解析到标签 + # 标签开始 def start_element(self, name, attrs): print('sax:start_element: %s, attrs: %s' % (name, str(attrs))) - # + # 标签结束 def end_element(self, name): print('sax:end_element: %s' % name) - + + # 文本信息 def char_data(self, text): print('sax:char_data: %s' % text) @@ -120,12 +137,16 @@ def char_data(self, text): handler = DefaultSaxHandler() # 创建解析器对象 parser = ParserCreate() + # 开始解析标签的事件 parser.StartElementHandler = handler.start_element + # 解析完毕标签事件 parser.EndElementHandler = handler.end_element + # 解析到标签体事件 parser.CharacterDataHandler = handler.char_data + # 开始解析 parser.Parse(xml) diff --git "a/Python/python-\346\250\241\345\235\227-\347\263\273\347\273\237\347\233\270\345\205\263/python-os.py" "b/Python/python-\346\250\241\345\235\227-\347\263\273\347\273\237\347\233\270\345\205\263/python-os.py" index 789bd407..1130b45b 100644 --- "a/Python/python-\346\250\241\345\235\227-\347\263\273\347\273\237\347\233\270\345\205\263/python-os.py" +++ "b/Python/python-\346\250\241\345\235\227-\347\263\273\347\273\237\347\233\270\345\205\263/python-os.py" @@ -122,10 +122,12 @@ * 运行shell/cmd命令直接显示 * 返回值如果是 0 则表示执行OK - # + # 获取安全的随机数 urandom(num) - * unkonw - + * num 表示随机数的长度 + * 返回的 byte[] + base64.b64encode(os.urandom(32)) + os._wrap_close popen("bash command"") * 执行命名,获取对象 * 执行该返回对象的read(),可以获取到命名执行后的结果 diff --git "a/Python/python-\346\250\241\345\235\227-\347\275\221\347\273\234\347\233\270\345\205\263/py-base64.py" "b/Python/python-\346\250\241\345\235\227-\347\275\221\347\273\234\347\233\270\345\205\263/py-base64.py" index dc6e1d8a..fd42a2b4 100644 --- "a/Python/python-\346\250\241\345\235\227-\347\275\221\347\273\234\347\233\270\345\205\263/py-base64.py" +++ "b/Python/python-\346\250\241\345\235\227-\347\275\221\347\273\234\347\233\270\345\205\263/py-base64.py" @@ -29,6 +29,17 @@ bytes urlsafe_b64decode(bytes) * 鍚屼笂,瑙g爜 urlsafe 鐨凚ase64 缂栫爜 + + +------------------------ +base64-Demo | +------------------------ + +# 鍩烘湰鐨凚ASE64缂栫爜 + + import base64 + result = base64.b64encode(bytes('admin:888888','UTF_8')).decode('UTF_8') + print(result) # YWRtaW46ODg4ODg4 diff --git "a/Python/python-\346\250\241\345\235\227-\347\275\221\347\273\234\347\233\270\345\205\263/py-json.py" "b/Python/python-\346\250\241\345\235\227-\347\275\221\347\273\234\347\233\270\345\205\263/py-json.py" index 3d70667e..8f90517e 100644 --- "a/Python/python-\346\250\241\345\235\227-\347\275\221\347\273\234\347\233\270\345\205\263/py-json.py" +++ "b/Python/python-\346\250\241\345\235\227-\347\275\221\347\273\234\347\233\270\345\205\263/py-json.py" @@ -45,15 +45,29 @@ ------------------------ json-模块函数 | ------------------------ - str dumps(obj) + str dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + default=None, sort_keys=False, **kw) * 把可JSON序列化对象转换为JSON字符串 * 关键字参数 default * 指定 转换 obj 对象时,的转换函数 * 如果参数是一个不能进行JSON转换的对象,那么就会调用 default 指定的函数来进行转换操作,返回结构就是该函数返回的字符串 * 并且会把 obj 对象传递给该函数的第一个参数 + separators + * 指定切割??,应该是格式化json的分隔符,和: + * separators=(', ', ': ') ,就是在json的 ',' 后添加一个空格,':' 后也添加了一个空格 + + indent + * 间距 + sort_keys + * 是否要对key进行排序 ensure_ascii * 默认值 True,是否把中文转换为 Unicode 编码 + + * 序列化格式化后的JSON + data = {'name':'KevinBlandy'} + json.dumps(data, sort_keys=True, indent=4, separators=(',', ':'), ensure_ascii=False) str dump(obj,f) diff --git "a/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/nio/nio-select.py" "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/nio/nio-select.py" new file mode 100644 index 00000000..e69de29b diff --git "a/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/nio/nio-selector.py" "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/nio/nio-selector.py" new file mode 100644 index 00000000..e69de29b diff --git "a/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/nio/nio.py" "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/nio/nio.py" new file mode 100644 index 00000000..b5607eed --- /dev/null +++ "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/nio/nio.py" @@ -0,0 +1,6 @@ +---------------------------- +nio | +---------------------------- + # 相关文档 + https://docs.python.org/3/library/selectors.html + diff --git "a/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-selectors.py" "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-selectors.py" new file mode 100644 index 00000000..3428a150 --- /dev/null +++ "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-selectors.py" @@ -0,0 +1,118 @@ +------------------------------ +selectors | +------------------------------ + # selectors模块是在python3.4版本中引进的,它封装了IO多路复用中的select和epoll,能够更快,更方便的实现多并发效果 + # 该模块允许高层高效的 I/O 多路复用 + https://yiyibooks.cn/xx/python_352/library/selectors.html + https://docs.python.org/3/library/selectors.html#module-selectors + # 建立在 选择 select 基础之上,鼓励使用此模块,触发想精确控制系统级别原函数的使用 + # 该模块定义了一个抽象基类以及N多的实现 + BaseSelector + SelectSelector 基于select.select()的选择器 + PollSelector 基于select.poll()的选择器 + EpollSelector 基于select.epoll()的选择器 + DevpollSelector 基于select.devpoll()的选择器 + KqueueSelector 基于select.kqueue()的选择器 + + # 跟java的nio没啥区别 + # 模块下的其他类 + class Mapping(Collection) + +------------------------------ +模块属性和方法 | +------------------------------ + EVENT_READ + EVENT_WRITE + * 读写事件标识 + DefaultSelector + * 默认选择器类使用在当前平台上可用的最有效的实现,大多数用户的默认选择 + * 默认会用epoll,如果系统中没有epoll(比如windows)则会自动使用select + +------------------------------ +模块类 SelectorKey | +------------------------------ + # 将文件对象关联到其底层文件描述符 + + fileobj + * 注册的文件对象(连接) + + fd + * 文件标识符 + + events + * 返回关注的事件 + + data + * 注册时设置的关联数据 + +------------------------------ +模块类 BaseSelector | +------------------------------ + # 基类 + # 方法 + SelectorKey register(fileobj, events, data=None) + * 注册 + + SelectorKey unregister(fileobj) + * 取消注册 + + SelectorKey modify(fileobj, events, data=None) + * 修改关心的事件或者data + + [(SelectorKey, events)] select(timeout=None) + * 开始轮询 + + None close() + * 关闭 + + SelectorKey get_key(fileobj) + * 获取指定fileobj的SelectorKey + + Mapping get_map() + + + +------------------------------ +demo | +------------------------------ + +import selectors +from selectors import SelectorKey +import socket + +# 默认的selector +selector = selectors.DefaultSelector() + +server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) +server.bind(('0.0.0.0', 1024)) +server.listen() +server.setblocking(False) # 设置为非阻塞模式 + +# 注册读事件 +selector.register(server, selectors.EVENT_READ) + + +# 处理连接事件 +def accept(key,event): + connection,address = key.fileobj.accept() # 获取到客户端连接 + connection.setblocking(False) # 设置为非阻塞模式 + selector.register(connection, selectors.EVENT_READ) # 注册读事件 + +# 处理读取事件 +def readable(key,event): + connection = key.fileobj + data = connection.recv(1024) # 读取数据 + if data: + print('收到数据:%s'%(data.decode('UTF_8'))) + else: + selector.unregister(connection) # 连接关闭,移除注册 + connection.close() + +while True: + events = selector.select() + for key,event in events: + print(key,event) + if key.fileobj == server: + accept(key,event) # 新的连接 + else: + readable(key, event) # 可读就绪 \ No newline at end of file diff --git "a/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-socket-raw.py" "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-socket-raw.py" new file mode 100644 index 00000000..bf9918e3 --- /dev/null +++ "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-socket-raw.py" @@ -0,0 +1,60 @@ +-------------------- +python-raw | +-------------------- + # 以太网 RFC894 + + # Ip Header + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |Version| IHL |Type of Service| Total Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Identification |Flags| Fragment Offset | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Time to Live | Protocol | Header Checksum | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source Address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination Address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Options | Padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Version IP协议版本号 4 bit 一般的值为:0100(IPV4) 0110(IPV6) + IHL (Ip Header Length) 4 bit 首部长度 + TOS (Type Of Service) 8 bit 服务类型 + Total Length 16 bit ip 包总长度,包括头和数据 + + Identification 16 bit + Flags 3 bit 标记 + Fragment Offset 13 bit 片偏移,表示该IP包在该组分片包中位置,接收端靠此来组装还原IP包 + + Time to Live 8 bit 生存时间,这个字段可以防止由于路由环路而导致IP包在网络中不停被转发。 + Protocol 8 bit 标识了上层所使用的协议 + Header Checksum 16 bit 16位首部校验和,IP头部的正确性检测,但不包含数据部分 + + Source Address 32 bit 源地址,除非使用NAT,否则整个传输的过程中,该地址不会改变 + + Destination Address 32 bit 目的IP地址,除非使用NAT,否则整个传输的过程中,该地址不会改变 + + Options 24 bit + Padding 8 bit + + + + # TCP Header + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source Port | Destination Port | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Sequence Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Acknowledgment Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Data | |U|A|P|R|S|F| | + | Offset| Reserved |R|C|S|S|Y|I| Window | + | | |G|K|H|T|N|N| | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Checksum | Urgent Pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Options | Padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- diff --git "a/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-socket.py" "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-socket.py" index 0737ecd8..da03c282 100644 --- "a/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-socket.py" +++ "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-socket.py" @@ -5,12 +5,12 @@ -------------------------------- socket-模块属性 | -------------------------------- - * 网络层 + * 网络层属性 socket.AF_UNIX # 本地(Unix/Linux系统才有该属性,适用于不同进程之间的通信) socket.AF_INET # IPV4 socket.AF_INET6 # IPV6 - * 传输层 + * 传输层属性 socket.SOCK_STREAM * tcp @@ -24,7 +24,20 @@ socket.SOCK_RDM * 可靠的UDP,保证交付消息,但是不保证交付顺序 - + + * 协议 + socket.IPPROTO_TCP = 6 + socket.IPPROTO_IP = 0 + socket.IPPROTO_UDP = 17 + socket.IPPROTO_ICMP = 1 + socket.IPPROTO_RAW = 255 + + + * socket选项 + socket.IP_HDRINCL + socket.SOL_SOCKET + socket.SO_BROADCAST + socket.SO_REUSEADDR -------------------------------- socket-模块方法 | @@ -39,6 +52,30 @@ str gethostname() * 获取本地主机名 + + float getdefaulttimeout() + None setdefaulttimeout() + * 读取/设置全局的连接超时时间 + + socketpair() + fromfd() + fromshare() + gethostname() + gethostbyname() + gethostbyaddr() + getservbyname() + getprotobyname() + * 根据协议名称获取协议'对象' + + ntohs() + ntohl() + htons() + htonl() + inet_aton() + inet_ntoa() + + socket create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,source_address=None) + -------------------------------- socket-socket | @@ -87,7 +124,10 @@ None setsockopt() * 设置 socket 的一些属性(例如:端口重用) * demo - setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 设置端口可以重用 + setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) + # 设置端口可以重用 + setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) + # 设置UDP为广播模式 int send(bytes) * 发送数据到目标,不一定一次性就把数据发送完毕,应该使用 sendall() @@ -157,5 +197,3 @@ * 创建一个与该套接字相关连的文件 - - \ No newline at end of file diff --git a/Python/python-socket-socketserver.py "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-socketserver.py" similarity index 97% rename from Python/python-socket-socketserver.py rename to "Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-socketserver.py" index e4bf9b5c..25d99d94 100644 --- a/Python/python-socket-socketserver.py +++ "b/Python/python-\346\250\241\345\235\227-\350\277\233\347\250\213\351\227\264\351\200\232\344\277\241/python-socketserver.py" @@ -27,6 +27,8 @@ * BaseServer 的一些属性 allow_reuse_address * 默认为 False,是否允许端口复用 + * 该属性是类属性,踩过坑.... + socketserver.UDPServer.allow_reuse_address = True --------------------------- tcp | diff --git "a/Python/python-\346\250\241\345\235\227-\351\202\256\344\273\266/email.py" "b/Python/python-\346\250\241\345\235\227-\351\202\256\344\273\266/email.py" new file mode 100644 index 00000000..21df73da --- /dev/null +++ "b/Python/python-\346\250\241\345\235\227-\351\202\256\344\273\266/email.py" @@ -0,0 +1,131 @@ +------------------------- +email | +------------------------- + # 参考 + https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432005226355aadb8d4b2f3f42f6b1d6f2c5bd8d5263000#0 + https://www.jianshu.com/p/abb2d6e91c1f + # 核心的模块 + email + smtplib + nntplib + Message + |- MIMEBase + |- MIMEMultipart + |- MIMENonMultipart + |- MIMEMessage + |- MIMEText + |- MIMEImage + # 大致步骤 + +------------------------- +发送普通邮件 | +------------------------- +from email.header import Header +from email.mime.text import MIMEText +from email.utils import parseaddr, formataddr + +import smtplib + +# 如果地址信息包含中文,需要通过Header对象进行编码。 +def format_address(address): + name, addr = parseaddr(address) + return formataddr((Header(name, 'utf-8').encode(), addr)) + + +# 构建消息(消息内容,类型,编码) +# 发送HTML消息 MIMEText('

Hello

', 'html', 'utf-8') +message = MIMEText('Hello', 'plain', 'utf-8') + +# 发送人 +message['From'] = format_address('Javaweb开发者社区 ') + +# 收件人,如果有多个地址,使用逗号分隔字符串 +message['To'] = format_address('KevinBlandy <747692844@qq.com>') + +# 主题 +message['Subject'] = Header('欢迎你啊', 'utf-8').encode() + +# 新建STMP实例(地址,端口) +server = smtplib.SMTP_SSL('smtp.exmail.qq.com', 465) +# 如果不是SSL的连接:smtplib.SMTP(host,port) + +# 设置DEBUG级别 +server.set_debuglevel(1) + +# 使用账户名密码登录 +server.login('no-reply@javaweb.io', '123456789') + +# 发送邮件,注意发送人账户必须和登录的账户名一样 +server.sendmail('no-reply@javaweb.io', '747692844@qq.com', message.as_string()) + +# 退出客户端 +server.quit() + +------------------------- +普通连接转换为SSL | +------------------------- +server = smtplib.SMTP(host,port) +server.starttls() # 立刻调用starttls()方法,就创建了安全连接 + +------------------------- +发送带附件的邮件 | +------------------------- +from email import encoders +from email.header import Header +from email.mime.text import MIMEText +from email.mime.base import MIMEBase +from email.mime.multipart import MIMEMultipart +from email.utils import parseaddr, formataddr +import smtplib + + +def format_address(address): + name, addr = parseaddr(address) + return formataddr((Header(name, 'utf-8').encode(), addr)) + + +# 创建邮件对象: +msg = MIMEMultipart() + +msg['From'] = format_address('Javaweb开发者社区 ') +msg['To'] = format_address('KevinBlandy <747692844@qq.com>') +msg['Subject'] = Header('这是一个带附件的邮件', 'utf-8').encode() + +# 邮件添加一个text/plain消息 +msg.attach(MIMEText('send with file...', 'plain', 'utf-8')) + +# 添加附件就是加上一个 MIMEBase,从本地读取一个图片: +with open('D:\\20181009153347.jpg', 'rb') as file: + filename = file.name + + # 设置附件的MIME和文件名,这里是png类型: + mime = MIMEBase('image', 'png', filename=filename) + + # 加上必要的头信息: + mime.add_header('Content-Disposition', 'attachment', filename=filename) + mime.add_header('Content-ID', '<0>') + mime.add_header('X-Attachment-Id', '0') + + # 把附件的内容读进来: + mime.set_payload(file.read()) + # 用Base64编码: + encoders.encode_base64(mime) + + # 添加到MIMEMultipart: + msg.attach(mime) + + +# 新建STMP实例(地址,端口) +server = smtplib.SMTP_SSL('smtp.exmail.qq.com', 465) + +# 设置DEBUG级别 +server.set_debuglevel(1) + +# 使用账户名密码登录 +server.login('no-reply@javaweb.io', '123456') + +# 发送邮件,注意发送人账户必须和登录的账户名一样 +server.sendmail('no-reply@javaweb.io', '747692844@qq.com', msg.as_string()) + +# 退出客户端 +server.quit() diff --git "a/Python/\346\241\206\346\236\266-Flask/flask-icon.py" "b/Python/\346\241\206\346\236\266-Flask/flask-icon.py" new file mode 100644 index 00000000..9434223e --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Flask/flask-icon.py" @@ -0,0 +1,5 @@ + +# icon 在项目目录 static 目录下 +@app.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/x-icon') \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-Flask/flask-web-config.py" "b/Python/\346\241\206\346\236\266-Flask/flask-web-config.py" index 710cb2d5..b7b3f3b8 100644 --- "a/Python/\346\241\206\346\236\266-Flask/flask-web-config.py" +++ "b/Python/\346\241\206\346\236\266-Flask/flask-web-config.py" @@ -26,7 +26,7 @@ * 灏辨槸鍦ㄧ幆澧冨彉閲忎腑娣诲姞涓涓,璇ュ兼寚鍚戜簡涓涓厤缃枃浠 * config 鍏峰鐨勬柟娉 - get_namespace() + get_namespace(prefix) * 鑾峰彇鎸囧畾鍓嶇紑鐨勬墍鏈夐厤缃」 app.config['IMAGE_STORE_TYPE'] = 'fs' app.config['IMAGE_STORE_PATH'] = '/var/app/images' diff --git "a/Python/\346\241\206\346\236\266-Flask/flask-web-flask.py" "b/Python/\346\241\206\346\236\266-Flask/flask-web-flask.py" new file mode 100644 index 00000000..d71df3fb --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Flask/flask-web-flask.py" @@ -0,0 +1,23 @@ +------------------------- +Falsk | +------------------------- + # 构造函数 + def __init__( + self, + import_name, + static_url_path=None, + static_folder='static', + static_host=None, + host_matching=False, + subdomain_matching=False, + template_folder='templates', + instance_path=None, + instance_relative_config=False, + root_path=None + ) + +------------------------- +实例方法 | +------------------------- + run(self, host=None, port=None, debug=None, load_dotenv=True, **options) + * 启动服务 diff --git "a/Python/\346\241\206\346\236\266-Flask/flask-web-request.py" "b/Python/\346\241\206\346\236\266-Flask/flask-web-request.py" index 5d0617d2..b9bb618d 100644 --- "a/Python/\346\241\206\346\236\266-Flask/flask-web-request.py" +++ "b/Python/\346\241\206\346\236\266-Flask/flask-web-request.py" @@ -83,7 +83,10 @@ def upload_file(): pic = files['poc'] # 鑾峰彇鏂囦欢鐨勫悕绉 - filename = pic.filename() + filename = pic.filename; + + # 鑾峰彇鍗曠嫭鐨勮姹備綋 + item__headers = pic.headers; # 鑾峰彇鏂囦欢鐨勪簩杩涘埗鏁版嵁(bytes) datas = pic.read() diff --git "a/Python/\346\241\206\346\236\266-Flask/flask-web-response.py" "b/Python/\346\241\206\346\236\266-Flask/flask-web-response.py" index 6b52aaf9..23710643 100644 --- "a/Python/\346\241\206\346\236\266-Flask/flask-web-response.py" +++ "b/Python/\346\241\206\346\236\266-Flask/flask-web-response.py" @@ -22,6 +22,7 @@ def index(name=None): * 杩斿洖 render_template(tempalte,**context) 瀹炰緥,鏉ュ畬鎴愭ā鐗堢殑娓叉煋鍝嶅簲 tempalte:妯$増鍚嶇О context :涓婁笅鏂 + * 鍏跺疄璇ュ嚱鏁拌繑鍥炵殑鏄 str,涔熷氨鏄覆鏌撳悗鐨勬ā鏉垮瓧绗︿覆鏁版嵁 * Flask 浼氬湪 templates 鏂囦欢澶归噷瀵绘壘妯℃澘 * 濡傛灉搴旂敤鏄釜妯″潡,杩欎釜鏂囦欢澶瑰簲璇ヤ笌妯″潡鍚岀骇 application.py @@ -33,7 +34,7 @@ def index(name=None): |-__init__.py |-templates |-hello.html - + -------------------------------- 鏋勫缓 response | -------------------------------- @@ -70,7 +71,10 @@ def hello_world(): response.set_cookie('JSESSIONID','F8575532',max_age=-1) return response - + * make_response 鍝嶅簲鐨勬槸涓涓 Response 瀵硅薄 + * 涔熷彲浠ヨ嚜宸卞垱寤轰竴涓 Response瀵硅薄 + from flask import Response + -------------------------------- 閲嶅畾鍚 | -------------------------------- diff --git "a/Python/\346\241\206\346\236\266-Flask/flask-web-ssl.py" "b/Python/\346\241\206\346\236\266-Flask/flask-web-ssl.py" new file mode 100644 index 00000000..214b3e1e --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Flask/flask-web-ssl.py" @@ -0,0 +1,20 @@ + + + +-------------------------------- +ssl | +-------------------------------- + # app.run(),方法,通过 ssl_context 关键字参数指定证书文件的地址 + from flask import Flask, request, render_template + app = Flask('flask-application') + + app.config.DEBUG = True + + + @app.route('/') + def hello_world(): + return render_template('index.html') + + + if __name__ == '__main__': + app.run(debug=True, ssl_context=('ssl/fullchain.cer','ssl/javaweb.key')) diff --git "a/Python/\346\241\206\346\236\266-Flask/flask-web-websocket.py" "b/Python/\346\241\206\346\236\266-Flask/flask-web-websocket.py" new file mode 100644 index 00000000..4178d32d --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Flask/flask-web-websocket.py" @@ -0,0 +1,6 @@ +----------------------- +websocket | +----------------------- + # 安装必须模块 + pip install flask-socketio + diff --git "a/Python/\346\241\206\346\236\266-Opencv/opencv-\345\205\245\351\227\250.py" "b/Python/\346\241\206\346\236\266-Opencv/opencv-\345\205\245\351\227\250.py" new file mode 100644 index 00000000..f911b8db --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Opencv/opencv-\345\205\245\351\227\250.py" @@ -0,0 +1,11 @@ +------------------------- +opencv | +------------------------- + # 环境安装 + yum install numpy opencv* + + # pip安装 + pip install --upgrade setuptools + pip install numpy Matplotlib + pip install opencv-python + diff --git "a/Python/\346\241\206\346\236\266-PhantomJS/fj-\345\205\245\351\227\250.py" "b/Python/\346\241\206\346\236\266-PhantomJS/fj-\345\205\245\351\227\250.py" new file mode 100644 index 00000000..173469b7 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-PhantomJS/fj-\345\205\245\351\227\250.py" @@ -0,0 +1,12 @@ +---------------------------- +入门 | +---------------------------- + * 它就是一个没有界面的浏览器,运行起来比正儿八经的浏览器要高效 + * 它可以处理Js,Cookie,Headers 以及我们真实需要做的事情 + * 它并不是 'python库',而是一个正儿八经的浏览器 + * 我们需要通过 Selenium 来调用它来完成操作,这俩结合可以完成一个超级强大的网络爬虫 + * 官网 + http://phantomjs.org + + + \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-QT/qt.py" "b/Python/\346\241\206\346\236\266-QT/qt.py" new file mode 100644 index 00000000..a5af13ef --- /dev/null +++ "b/Python/\346\241\206\346\236\266-QT/qt.py" @@ -0,0 +1,10 @@ +---------------------------- +QT | +---------------------------- + # 鍙傝冨湴鍧 + - http://www.pyside.org/ + - https://www.qt.io/cn + - https://wiki.qt.io/Qt_for_Python/zh + + # 瀹夎 + pip install PySide2 diff --git "a/Python/\346\241\206\346\236\266-Requests/requests-proxies.py" "b/Python/\346\241\206\346\236\266-Requests/requests-proxies.py" new file mode 100644 index 00000000..e69de29b diff --git "a/Python/\346\241\206\346\236\266-Requests/requests-request.py" "b/Python/\346\241\206\346\236\266-Requests/requests-request.py" new file mode 100644 index 00000000..e69de29b diff --git "a/Python/\346\241\206\346\236\266-Requests/requests-session.py" "b/Python/\346\241\206\346\236\266-Requests/requests-session.py" new file mode 100644 index 00000000..ed34cc58 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Requests/requests-session.py" @@ -0,0 +1,15 @@ + +------------------------ +session | +------------------------ + # 基本使用 + import requests + + # 创建一个会话 + request = requests.Session() + + '''执行HTTP请求''' + + # 关闭会话 + request.close() + diff --git "a/Python/\346\241\206\346\236\266-Requests/requests-ssl.py" "b/Python/\346\241\206\346\236\266-Requests/requests-ssl.py" new file mode 100644 index 00000000..e69de29b diff --git "a/Python/\346\241\206\346\236\266-Requests/requests.py" "b/Python/\346\241\206\346\236\266-Requests/requests.py" new file mode 100644 index 00000000..1caa1a04 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Requests/requests.py" @@ -0,0 +1,200 @@ +---------------------------- +requests | +---------------------------- + # http 库 + # 文档地址 + http://docs.python-requests.org/zh_CN/latest/user/quickstart.html + http://cn.python-requests.org/zh_CN/latest/ + + # 安装 + pip install requests + +---------------------------- +requests 执行请求 | +---------------------------- + # 执行各种方法的请求 + requests.get() + requests.post() + requests.delete() + requests.put() + requests.heade() + requests.options() + requests.patch() + + # 执行请求的参数 + 第一个参数 + * 就是url + params + * 是一个 dict,在get请求下,会被编码为URL编码 + * 序列化为请求参数,添加到uri后面 + * None 不会被添加到参数 + * 参数值可以是一个数组 + payload = {'key1': 'value1', 'key2': ['value2', 'value3']} + http://xxx.org/get?key1=value1&key2=value2&key2=value3 + stream + * bool 值,响应是否是一个数据流 + * 如果该值为 True,那么可以通过response的raw属性获取到服务器的原始套接字响应 + * 默认情况下,当进行网络请求后,响应体会立即被下载,可以通过设置 stream=True 改变这个行为,推迟下载响应体直到访问 response.content 属性 + * 如果stream=True,Requests 无法将连接释放回连接池,除非消耗了所有的数据,或者调用了 Response.close 这样会带来连接效率低下的问题 + * 应该考虑使用 with 语句发送请求,这样可以保证请求一定会被关闭 + with requests.get('http://xxx.org/get', stream=True) as response: + pass + + headers + * 参数是一个 dict ,表示自定义的请求头 + * header 值必须是 string,bytestring 或者 unicode,尽管传递 unicode header 也是允许的,但不建议这样做 + + data + * 请求体,在 post 的时候,会被编码为表单 + * 它一般应该是 dict ,或者 tuple(多个key),或者字符串(自己的字符串格式) + requestBody = (('key1', 'value1'), ('key1', 'value2')) + # key1=value1&key1=value2 + + json + * 把请求体编码为JSON字符串 + + files + * ContentType = multipart/form-data 时的请求体 + * 下面有单独的笔记 + + cookies + * 设置cookie + * 参数是一个 dict + + allow_redirects + * 是否允许重定向 + * bool 值,如果该值为 True,则会自动的重新请求 302/1 的结果 + + timeout + * 设置连接超时时间,单位为秒 + * 超时后会抛出异常:requests.exceptions.Timeout + * 它可以是一个 tuple,这样的话第一个参数表示连接超时时间,第二个参数表示读取数据超时时间 + * 如果该值为 None,则表示永远等待 + + verify + * 可以是一个 bool 值,表示是否验证 ssl (https) 的有效性 + * 也可以是一个 string,表示包含可信任 CA 证书文件的文件夹路径 + * 如果设为文件夹路径,文件夹必须通过 OpenSSL 提供的 c_rehash 工具处理 + + cert + * 参数是一个 tuple,表示客户端证书的地址 + * [0] = cert,[1] = key + * 本地证书的私有 key 必须是解密状态,目前,Requests 不支持使用加密的 key(例如 .keystory文件) + + proxies + * 设置代理 + + auth + * BasicAuth 验证方式 + * 参数是一个:HTTPBasicAuth 实例 + from requests.auth import HTTPBasicAuth + auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password') + +---------------------------- +requests 处理响应 | +---------------------------- + # 获取到响应 + response = request.get(....) + + # 属性 + status_code + * 状态码 + * 该模块内置了状态码的枚举 + requests.codes.ok + ... + text + * 服务器响应的字符数据 + encoding + * 服务端响应字符串数据的编码 + * 可以先修改该属性,再去获取 text 属性(访问text 都将会使用 encoding 的新值) + + content + * 返回的二进制数据(响应体) + * 服务端响应了一张图片 + image = Image.open(BytesIO(response.content)) + + json() + * 以JSON形式编码响应体 + raw + * 服务器的原始套接字响应 + * 必须确保在初始请求中设置了 stream=True + + iter_content() + * 流下载的时候,推荐使用的,它对于raw,帮我处理了很多东西 + * 把文本流保存到文件 + chunk_size = 1024 # 数据块儿的大小,可以自由的根据应用来定义 + with open(filename, 'wb') as fd: + for chunk in response.iter_content(chunk_size): + fd.write(chunk) + iter_lines() + * ? + + raise_for_status() + * 在40x,50x情况下,用于抛出异常,其他情况下该方法返回 None + + headers + * 获取到响应头,返回的是一个 dict + + cookies + * 获取响应的Cookie,返回的是一个 dict + + history + * 返回的是一个 list,每一个元素都是一个 response 对象,为了完成请求而创建了这些对象 + * 一般'重定向时候'可以通过该属性获取到执行的重定向列表 + * 这个对象列表按照从最老到最近的请求进行排序 + + url + * 返回请求的url + + close() + * 关闭与客户端的连接 + +---------------------------- +multipart/form-data 请求 | +---------------------------- + # 带文件参数的请求 + requestBody = { + # 普通参数项(值,None) + 'phone':(None,'18545478548'), + # 文件参数项 (文件名称,二进制文件对象,文件的Content-Type) + 'attachment': open('C:\\Users\\Administrator\\Desktop\\222.mp4', 'rb') + } + + # 执行请求 + response = requests.post('http://xxxx/upload', files = requestBody) + + # 服务器响应JSON + print(response.json()) + + * 自己添加header时,不要覆写 'Content-Type' 属性 + + # 文件表单项的构建可以更加的复杂,自己定义文件的名称,ContentType,额外的header + (filename, fileobj, contentype, custom_headers) + + requestBody = { + 'attachment': ('222.mp4',open('C:\\Users\\Administrator\\Desktop\\222.mp4', 'rb'),'audio/mp4', {'Expires': '0'}) + } + + # 带多个同名参数(文件)的请求 + requestBody = [ + ('name',(None,'KevinBlandy0')), + ('name',(None,'KevinBlandy1')), + ('name',(None,'KevinBlandy2')), + ('files',('file1.jpg',open('C:\\Users\\Administrator\\Desktop\\QQ图片20180703102725.jpg', 'rb'))), + ('files',('file2.jpg',open('C:\\Users\\Administrator\\Desktop\\微信图片_20180716103554.png', 'rb'))) + ] + +---------------------------- +文件下载 | +---------------------------- +import requests +# 文件下载地址 +url = 'http://xxx.down' +# 本地文件地址 +file = 'D:\\魔法师.mp4' +# 最大数据块的大小 +chunkSize = 1024 +with requests.get(url, stream=True) as response: + with open(file, 'wb') as file: + for chunk in response.iter_content(chunkSize): + file.write(chunk) diff --git "a/Python/\346\241\206\346\236\266-Scrapy/scrapy-api.py" "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-api.py" new file mode 100644 index 00000000..96658a43 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-api.py" @@ -0,0 +1,7 @@ +from scrapy.linkextractors import LinkExtractor +from scrapy.spiders import Rule +from scrapy import Request +from scrapy import FormRequest +from scrapy.spiders import Spider +from scrapy.spiders import CrawlSpider +from scrapy import Item \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-Scrapy/scrapy-spiders-CrawlSpider.py" "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-spiders-CrawlSpider.py" new file mode 100644 index 00000000..4eaf1295 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-spiders-CrawlSpider.py" @@ -0,0 +1,103 @@ +---------------------------- +CrawlSpider | +---------------------------- + * scrapy.spiders.CrawlSpider + * Spider子类,Spider类的设计原则是只爬取start_url列表中的网页 + * 而CrawlSpider类定义了一些规则(rule)来提供跟进link的方便的机制 + * 从爬取的网页中获取link并继续爬取的工作更适合 + +---------------------------- +CrawlSpider-特有的属性 | +---------------------------- + rules + * 定义 Rule 集合,用于定义提取url的规则 + +----------------------------- +LinkExtractor | +----------------------------- + * 用于从html结构体中提取出连接 + * from scrapy.linkextractors import LinkExtractor + * 构造函数 + LinkExtractor(allow=(), deny=(), allow_domains=(), deny_domains=(), restrict_xpaths=(), + tags=('a', 'area'), attrs=('href',), canonicalize=False, + unique=True, process_value=None, deny_extensions=None, restrict_css=(), + strip=True) + allow + * 表示匹配的规则(解析href连接),是一个或者多个正则表达式(元组) + * 如果该值为空,则所有的链接都会被提取 + deny + * 也是一组正则规则,该规则命中的连接,不会被提取 + allow_domains + * 会被提取的链接域 + deny_domains + * 不会被提取的连接域 + restrict_xpaths + * 使用xpath表达式,与allow共同过滤链接 + + * 属性 & 方法 + extract_links(response) + * 传入响应对象,进行link解析,返回 Link 对象集合 + * demo + def parse(self, response): + # 创建 extrator 对象(匹配规则) + extrator = LinkExtractor(allow=('start=\d+',)) + # 通过 extract_links 来解析 response 对象,获取Link对象集合 + links = extrator.extract_links(response) + for i in links: + print(i) # Link(url='http://hr.tencent.com/position.php?tid=87&start=10#a', text='2', fragment='', nofollow=False) + +----------------------------- +Rule | +----------------------------- + * 用于定义怎么去处理匹配到的连接 + * 一个 Rule 里面会包含一个匹配规则(LinkExtractor),程序里面可以包含多个 Rules + * 如果多个 Rule 匹配了相同的连接,则根据规则在集合中的定义顺序,第一个将会被使用 + * from scrapy.spiders import Rule + * 构造函数 + Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=identity) + link_extractor + * 表示一个匹配规则(LinkExtractor) + callback + * 回调函数名(str),每当从link_extractor中获取到连接的时候,都会作为参数传递给该回调函数 + * 注意,该函数名称不能与'parse'冲突 + follow + * bool 值,指定了根据该规则提取出来的连接是否要跟进(打开连接,深度提取) + * 如果 callback = None,该值默认为 True,否则该值为 False + process_links + * 以str形式,指定spider中哪个函数将会被调用,从匹配规则(LinkExtractor)中获取到链接列表 + * 就是处理提取到的连接的方法,处理完毕后返回连接列表 + + +----------------------------- +Spider-demo | +----------------------------- + + from job import items + from scrapy.linkextractors import LinkExtractor + from scrapy.spiders import Rule + from scrapy.spiders import CrawlSpider + + # 深度链接跟进 + class JobSpider(CrawlSpider): + name = 'job' + allowed_domains = ['hr.tencent.com'] + start_urls = ['http://hr.tencent.com/position.php'] + + # url提取规则 + rules = [ + # 定义了一个url规则,正则,回调函数,是否要跟进连接 + Rule(LinkExtractor(allow = r'start=\d+'),callback = 'parseLink',follow = True) + ] + + # 定义回调 + def parseLink(self,response): + trs = response.xpath("//tr[@class='even'] | //tr[@class='odd']") + for i in trs: + job = items.JobItem() + job['name'] = i.xpath("./td/a/text()").extract() + job['href'] = i.xpath("./td/a/@href").extract() + job['type_'] = i.xpath('./td[2]/text()').extract() + job['count'] = i.xpath('./td[3]/text()').extract() + job['address'] = i.xpath('./td[4]/text()').extract() + job['time'] = i.xpath('./td[5]/text()').extract() + yield job \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-Scrapy/scrapy-\345\221\275\344\273\244.py" "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\345\221\275\344\273\244.py" new file mode 100644 index 00000000..94dbbbc7 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\345\221\275\344\273\244.py" @@ -0,0 +1,70 @@ +---------------------------- +配置设置 | +---------------------------- + * Scrapy 默认在 scrapy.cfg 文件中查找配置参数(优先级别从低到高) + + 系统范围 /etc/scrapy.cfg 或 c:\scrapy\scrapy.cfg + 用户范围 ~/.config/scrapy.cfg ($XDG_CONFIG_HOME) 和 ~/.scrapy.cfg ($HOME) + 项目内范围 scrapy.cfg + + * 项目范围的设置将覆盖所有其他文件的设置,用户范围内定义的设置的覆盖系统范围内的设置 + + * Scrapy 也可以接受来自环境变量的配置。目前有 + + SCRAPY_SETTINGS_MODULE (见 Designating the settings) + SCRAPY_PROJECT + SCRAPY_PYTHON_SHELL (见 Scrapy shell) + +---------------------------- +全局命令 | +---------------------------- + scrapy startproject [name] + * 创建一个scrapy爬虫项目 + * 创建好的目录结构 + name + |-name + |-spiders + |-__init__.,py + |-__init__.,py + |-items.py + |-middlewares.py + |-pipelines.py + |-settings.py + |-scrapy.cfg + + + scrapy genspider [name] [url] + * 创建一个指定name的爬虫,目标地址是url + + scrapy settings + scrapy runspider + scrapy shell + scrapy fetch + scrapy view + scrapy version + +---------------------------- +项目命令 | +---------------------------- + scarpy crawl [name] + * 开始启动指定名称的爬虫 + * 参数 + -o + * 把spider的parse方法返回的数据序列化到本地 + * -o指定文件的名称,会生成在当前命令目录 + * 可以是 .json /.csv /.xml + + + + + + scrapy check + scrapy list + scrapy edit + scrapy parse + scrapy bench + +---------------------------- +自定义命令 | +---------------------------- + * 实验性功能 \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-cfg\346\226\207\344\273\266.py" "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-cfg\346\226\207\344\273\266.py" new file mode 100644 index 00000000..99635d73 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-cfg\346\226\207\344\273\266.py" @@ -0,0 +1,16 @@ +-------------------------------- +scrapy.cfg | +-------------------------------- + * 这是一个必须的配置文件 + + +-------------------------------- +scrapy.cfg配置项 | +-------------------------------- + +[settings] +default = webspider.settings + * 指定配置类的地址 + +[deploy] +project = webspider diff --git "a/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-items.py" "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-items.py" new file mode 100644 index 00000000..a77d12eb --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-items.py" @@ -0,0 +1,41 @@ +------------------------ +items | +------------------------ + * 其实就是定义model + * 简单的model定义 + import scrapy + class WebspiderItem(scrapy.Item): + name = scrapy.Field() # 定义一个name属性 + age = scrapy.Field() # 定义一个age属性 + gender = scrapy.Field() # 定义一个gender属性 + + +------------------------ +ItemLoader | +------------------------ + * from scrapy.loader import ItemLoader + * 提供了一种更为便捷的方式在spider中对 Item 进行填充 + * 构造函数 + ItemLoader(item=None, selector=None, response=None, parent=None, **context) + + * demo + from scrapy.loader import ItemLoader + from kevin.items import User + + def parse(self,response): + # 通过Item实例和response来实例化loader + loader = ItemLoader(item=User(),response=response) + + # 通过xpath对指定的属性进行赋值 + loader.add_xpath('name', '//div[@class="product_name"]') + loader.add_xpath('name', '//div[@class="product_title"]') + loader.add_xpath('price', '//p[@id="price"]') + + # 通过css选择器对指定的属性进行赋值 + loader.add_css('stock', 'p#stock]') + + # 直接设置值 + loader.add_value('last_updated', 'today') + + # 返回填充好值的item + return loader.load_item() \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-logging.py" "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-logging.py" new file mode 100644 index 00000000..57187ef5 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-logging.py" @@ -0,0 +1,46 @@ +---------------------------- +loging | +---------------------------- + * Scrapy鎻愪緵浜唋og鍔熻兘,鍙互閫氳繃 scrapy.log 妯″潡浣跨敤 + * log鏈嶅姟蹇呴』閫氳繃鏄剧ず璋冪敤 scrapy.log.start() 鏉ュ紑鍚紝浠ユ崟鎹夐《灞傜殑Scrapy鏃ュ織娑堟伅 + scrapy.log.start(logfile=None, loglevel=None, logstdout=None) + logfile + * 鎸囧畾鏃ュ織鏂囦欢 + loglevel + * 鎸囧畾鏃ュ織绾у埆 + logstdout + * 濡傛灉涓 True , 鎵鏈夊簲鐢ㄧ殑鏍囧噯杈撳嚭(鍖呮嫭閿欒)灏嗕細琚褰(logged instead) + * 渚嬪.璋冪敤 print ('hello')锛屽垯'hello' 浼氬湪Scrapy鐨刲og涓鏄剧ず,濡傛灉琚拷鐣,鍒 LOG_STDOUT 璁剧疆浼氳浣跨敤 + + * Scrapy鎻愪緵5灞俵ogging绾у埆 + scrapy.log.CRITICAL 涓ラ噸閿欒鐨凩og绾у埆 + + scrapy.log.ERROR 閿欒鐨凩og绾у埆 Log level for errors + + scrapy.log.WARNING 璀﹀憡鐨凩og绾у埆 Log level for warnings + + scrapy.log.INFO 璁板綍淇℃伅鐨凩og绾у埆(鐢熶骇閮ㄧ讲鏃舵帹鑽愮殑Log绾у埆) + + scrapy.log.DEBUG 璋冭瘯淇℃伅鐨凩og绾у埆(寮鍙戞椂鎺ㄨ崘鐨凩og绾у埆) + + * 浣跨敤 + from scrapy import log + log.msg("This is a warning", level=log.WARNING) + + * 鍦╯pider涓坊鍔爈og鐨勬帹鑽愭柟寮忔槸浣跨敤Spider鐨 log() 鏂规硶 + * 璇ユ柟娉曚細鑷姩鍦ㄨ皟鐢 scrapy.log.msg() 鏃惰祴鍊 spider 鍙傛暟,鍏朵粬鐨勫弬鏁板垯鐩存帴浼犻掔粰 msg() 鏂规硶 + + * '浠ヤ笂閮芥槸琚爣璇嗕负杩囨椂(搴熷純)鐨勪簡... ...' + +---------------------------- +loging | +---------------------------- + import logging + logging.warning("This is a warning") + logging.debug() + + logging.log(logging.WARNING, "This is a warning") + + * 鍦╯crapy涓娇鐢 + self.logger.warning('璀﹀憡淇℃伅') + diff --git "a/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-pipelines.py" "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-pipelines.py" new file mode 100644 index 00000000..9468cf06 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-pipelines.py" @@ -0,0 +1,71 @@ +------------------------ +pipelines | +------------------------ + * 默认预定义代码 + class WebspiderPipeline(object): + def process_item(self, item, spider): + return item + + * 该组件(模块)用来定义N多个 Pipelines + + * 配置在settings.py中 + ITEM_PIPELINES = { + 'webspider.pipelines.WebspiderPipeline': 300, + } + + * Pipelines可以直接继承自object + + +------------------------ +pipelines-方法 | +------------------------ + * 必须实现的的方法(处理数据的方法),参数固定 + def process_item(self, item, spider): + return item + + * item,传递过来的数据 + * spider,爬虫实例对象 + + * 在爬虫关闭的时候,执行的方法,非必须 + def close_spider(self,spider): + print("爬虫关闭:%s"%(spider)) + + * spider,爬虫实例对象 + + * 在爬虫开启的时候,执行的方法,非必须 + def open_spider(self, spider): + print("爬虫开启:%s"%(spider)) + + * spider,爬虫实例对象 + + * 未知 + from_crawler(self,crawler) + + + + +------------------------ +scrapy.pipelines.images | +------------------------ + * scrapy.pipelines.images.ImaagePiPeline + + IMAGES_STROE = "/" + + def get_media_requests(self,item,info): + yield scray.Request(item['url']) + + def item_completed(self,result,item,info): + image_path = [x['path'] for ok ,x in result if ok ] + return item + + +------------------------ +scrapy.pipelines.files | +------------------------ + + +------------------------ +scrapy.pipelines.media | +------------------------ + + diff --git "a/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-settings.py" "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-settings.py" new file mode 100644 index 00000000..18732846 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-settings.py" @@ -0,0 +1,92 @@ +-------------------------------- +settings | +-------------------------------- + * 配置模块 +-------------------------------- +settings-模块配置项 | +-------------------------------- + + BOT_NAME = 'webspider' + * 爬虫名称,也就是项目名称 + + LOG_FILE = 'my.log' + * 指定日志输出文件 + + LOG_LEVEL = 'WARNING' + * 指定日志输出级别 + 'CRITICAL': CRITICAL, + 'FATAL': FATAL, + 'ERROR': ERROR, + 'WARN': WARNING, + 'WARNING': WARNING, + 'INFO': INFO, + 'DEBUG': DEBUG, + + SPIDER_MODULES = ['webspider.spiders'] + + NEWSPIDER_MODULE = 'webspider.spiders' + + + USER_AGENT = 'webspider (+http://www.yourdomain.com)' + + ROBOTSTXT_OBEY = True + * 爬虫是否要遵循 robots 协议 + + CONCURRENT_REQUESTS = 32 + * 爬虫并发,允许爬虫同时发起的请求,默认:16 + + DOWNLOAD_DELAY = 3 + * 下载延迟,单位是秒 + + CONCURRENT_REQUESTS_PER_DOMAIN = 16 + CONCURRENT_REQUESTS_PER_IP = 16 + + COOKIES_ENABLED = False + * 是否启用cookie机制,默认开启 + + TELNETCONSOLE_ENABLED = False + + DEFAULT_REQUEST_HEADERS = { + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en', + } + * 默认的http请求头,可以添加:'User-Agent',来欺骗服务器 + + SPIDER_MIDDLEWARES = { + 'webspider.middlewares.WebspiderSpiderMiddleware': 543, + } + * 爬虫中间件,后面的值表示优先级,数值越小,优先级越高 + + DOWNLOADER_MIDDLEWARES = { + 'webspider.middlewares.MyCustomDownloaderMiddleware': 543, + } + * 下载中间件,后面的值表示优先级,数值越小,优先级越高 + + EXTENSIONS = { + 'scrapy.extensions.telnet.TelnetConsole': None, + } + + ITEM_PIPELINES = { + 'webspider.pipelines.WebspiderPipeline': 300, + } + * 管道文件,他们决定了下载好的数据如果做处理,后面的值表示优先级,数值越小,优先级越高 + + AUTOTHROTTLE_ENABLED = True + AUTOTHROTTLE_START_DELAY = 5 + AUTOTHROTTLE_MAX_DELAY = 60 + AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 + AUTOTHROTTLE_DEBUG = False + HTTPCACHE_ENABLED = True + HTTPCACHE_EXPIRATION_SECS = 0 + HTTPCACHE_DIR = 'httpcache' + HTTPCACHE_IGNORE_HTTP_CODES = [] + HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage' + +-------------------------------- +settings-在程序中获取配置信息 | +-------------------------------- + from scrapy.utils.project import get_project_settings + + # 通过该方法获取指定名称的配置项 + get_project_settings().get('BOT_NAME') + diff --git "a/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-shell.py" "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-shell.py" new file mode 100644 index 00000000..c84d0f90 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-shell.py" @@ -0,0 +1,4 @@ +------------------------ +shell | +------------------------ + \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-spiders.py" "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-spiders.py" new file mode 100644 index 00000000..84dc4d55 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-spiders.py" @@ -0,0 +1,143 @@ +----------------------------- +spiders | +----------------------------- + * spider 应该出现在 spiders python目录下 + * spider 必须继承自:scrapy.Spider + * spider的parse要么返回 item,要么返回 Request,意思是要么处理数据,要么继续爬 + +----------------------------- +spiders-主要的属性和方法 | +----------------------------- + name + * 定义spider名字的字符串 + * 例如,如果spider爬取 mywebsite.com ,该spider通常会被命名为 mywebsite + + allowed_domains + * 包含了spider允许爬取的域名(domain)的列表,可选 + + start_urls + * 初始URL元祖/列表,当没有制定特定的URL时,spider将从该列表中开始进行爬取 + * 也可以是 + + start_requests(self) + * 该方法必须返回一个 Request 实例的可迭代对象(iterable),该对象包含了spider用于爬取的第一个Request + * 其实源码中就是对 start_urls 中的url属性进行迭代,并以 parse 为回调函数生成 Request + + parse(self, response) + * 当请求url返回网页没有指定回调函数时,默认的Request对象回调函数 + * 用来处理网页返回的response,以及生成Item或者Request对象 + + log(self, message[, level, component]) + * 使用 scrapy.log.msg() 方法记录(log)message + * 更多数据请参见 logging + + closed(reason) + * 当spider关闭时,该函数被调用 + * 该方法提供了一个替代调用 signals.connect()来监听 spider_closed 信号的快捷方式 + +----------------------------- +基本spiders 定义 | +----------------------------- + import scrapy + class ItcastSpider(scrapy.Spider): + + # 指定爬虫的名称 + name = 'itcast' + + # 允许爬取的域 + allowed_domains = ['itcast.cn'] + + # 开始的地址 + start_urls = ['http://itcast.cn/'] + + # 爬取结果处理(self可以访问上面的所有属性,以及自定义的属性) + def parse(self, response): + response.url # 爬取时请求的url + response.body # 返回的html + response.body_as_unicode() # 返回的html unicode编码 + + * name,allowed_domains 等属性,都是当前Spider实例对象(可以通过 self 访问) + +----------------------------- +spiders 重新发起请求 | +----------------------------- + * 在爬取过程中,如果从响应中爬取出了连接,需要深度爬取,则返回 scrapy.Request() 实例对象 + + def parse(self, response): + ... + # 解析出数据,交给pipe处理 + yield item + + ... + # 解析出url,重新发送给调度器,入队列,并且指定回调函数,就是当前函数 + yield scrapy.Request('http://....com',callback=self.parse) + + * scrapy.Request(url, callback=None, method='GET', headers=None, body=None, + cookies=None, meta=None, encoding='utf-8', priority=0, + dont_filter=False, errback=None, flags=None) + * 第一个参数,就是提取出来的url + * 关键字参数 + callback + * 该连响应后,由谁进行处理(回调函数) + +----------------------------- +Selectors选择器 | +----------------------------- + * response.selector,将获取到一个 response 初始化的类 Selector 的对象 + * 通过使用 response.selector.xpath()或response.selector.css() 来对 response 进行查询 + * Scrapy也提供了一些快捷方式来进行访问 + response.xpath + response.css() + + * Scrapy Selectors 内置 XPath 和 CSS Selector 表达式机制 + + * Selector有四个基本的方法,最常用的还是xpath + + xpath() + * 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 + extract() + * 序列化该节点为Unicode字符串并返回list + css() + * 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表,语法同 BeautifulSoup4 + re() + * 根据传入的正则表达式对数据进行提取,返回Unicode字符串list列表 + extract_first() + * 从 extract() 结果集中获取第一个元素,不用担心数组越界异常,不存在返回 None + +------------------------------------ +爬虫命令参数 | +------------------------------------ + * 在运行爬虫时,可以通过 -a 选项为爬虫提供命令行参数 + scrapy crawl quotes -o quotes-humor.json -a tag=humor + + * 默认情况下,这些参数将传递给 Spider 的 __init__ 方法并成为爬虫的属性 + tag = getattr(self, 'tag', None) # 通过反射获取执行爬虫传递的参数 + + +------------------------------------ +Response | +------------------------------------ + * 由框架构建 + * 属性 + url # 爬取时请求的url + body # 返回http响应体 + + * 方法 + body_as_unicode() # 返回的html unicode编码 + urljoin() + follow() + * 返回一个 Request 实例对象 + +------------------------------------ +Request | +------------------------------------ + * 由自己创建,表示一个http请求对象 + * 构造函数 + Request(url, callback=None, method='GET', headers=None, body=None, + cookies=None, meta=None, encoding='utf-8', priority=0, + dont_filter=False, errback=None, flags=None) + + * 第一个参数,就是目标url + * 关键字参数 + callback + * 该连响应后,由谁进行处理(回调函数) \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-\345\220\257\345\212\250\350\204\232\346\234\254.py" "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-\345\220\257\345\212\250\350\204\232\346\234\254.py" new file mode 100644 index 00000000..fcf97ae8 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-Scrapy/scrapy-\347\273\204\344\273\266-\345\220\257\345\212\250\350\204\232\346\234\254.py" @@ -0,0 +1,18 @@ +---------------------------- +scrapy启动脚本 | +---------------------------- + * scrapy 的启动可以通过命令来进行启动,也可以把命令写入脚本,通过运行脚本来启动 + +---------------------------- +scrapy启动脚本 | +---------------------------- + from scrapy import cmdline + cmdline.execute("scrapy crawl itcast -o test.json".split()) + + + * 启动该脚本,其实也是调用了scrapy去执行命令 + * 同理,如下写法也可以 + + import os + os.system("scrapy crawl itcast -o test.json") + diff --git "a/Python/\346\241\206\346\236\266-beautifulsoup/bs-api-BeautifulSoup.py" "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-api-BeautifulSoup.py" new file mode 100644 index 00000000..4157187d --- /dev/null +++ "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-api-BeautifulSoup.py" @@ -0,0 +1,35 @@ + +---------------------------- +BeautifulSoup | +---------------------------- + * 大部分时候,可以把它当作 Tag 对象,它支持 遍历文档树 和 搜索文档树 中描述的大部分的方法 + +---------------------------- +BeautifulSoup-构造函数 | +---------------------------- + BeautifulSoup() + * 关键字参数 + parse_only + * 该属性值应该是:SoupStrainer类实例,表示解析仅仅解析部分文档 + + +---------------------------- +BeautifulSoup-属性 | +---------------------------- + name + * 该属性值是固定的:"[document]" + +---------------------------- +BeautifulSoup-方法 | +---------------------------- + new_tag( name, namespace=None, nsprefix=None, **attrs) + * 创建新的标签对象 + * name属性必须,指定标签的名称 + + new_string() + * 创建一个文本节点 + + + + + diff --git "a/Python/\346\241\206\346\236\266-beautifulsoup/bs-api-Tag.py" "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-api-Tag.py" new file mode 100644 index 00000000..811ef7a0 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-api-Tag.py" @@ -0,0 +1,118 @@ +-------------------------------- +beautifulsoup-Tag | +-------------------------------- + * + + +-------------------------------- +beautifulsoup-属性 | +-------------------------------- + name + * 返回标签名称 + + attrs + * 返回属性dict + + contents + * 返回list,当前标签'所有直接子元素集合',包括文本节点(bs4.element.NavigableString) + * '字符串节点没有该属性' + + children + * 返回迭代器,当前标签的'所有直接子元素' + + descendants + * 返回当前标签的'所有子元素' + * 递归 + + string + * 获取当前标签的文本标签 + + strings + * 获取当前标签下的所有文本标签,递归获取 + + stripped_strings + * 同上.空白会被忽略掉 + +-------------------------------- +beautifulsoup-方法 | +-------------------------------- + get(attr) + * 获取指定名称的属性值,属性不存在返回None + + find_all(name , attrs , recursive , string , **kwargs ) + * 获取子标签中指定名称的标签对象,返回list + * 参数可以是标签名称,也是可以是一个 正则对象(re),也可以是一个过滤器方法 + * 参数 + name + * 以查找所有名字为 name 的tag,字符串对象会被自动忽略掉. + string + * 通过 string 参数可以搜搜文档中的字符串内容,与 name 参数的可选值一样 + keyword + * 通过指定属性名和值来过滤 + * 值也可以是[]或者正则 + * 如值为 True,则表示无视值,属性存在就OK + attrs + * 属性值的 dict 表示 + * 通过limit关键字参数来限制检索的结果集长度,跟sql中的limit一样 + * 通过recursive关键字参数来控制是否要递归检索(包含多级子节点),默认为 True + + find() + * 跟 find_all 一样,它返回的只有一个结果 + + find_parents() + find_parent() + * 搜索父级节点 + + find_next_siblings() + find_next_sibling() + * 搜索兄弟节点 + + find_previous_siblings() + find_previous_sibling() + * 搜索上/下一个解析节点 + + find_all_next() + find_next() + + find_all_previous() + find_previous() + + + clear() + * 清除当前标签中的文本节点(就是标签体) + + extract() + * 移除当前节点,并且返回 + * 返回的节点是一个独立的html文档 + + decompose() + * 移除当前节点,并且从内存中销毁 + + replace_with(new_tag) + * 使用 new_tag 来代替当前tag,返回当前tag + + wrap(tag) + * 使用tag来包装当前tag + * 认个亲爸爸 + + prettify() + * 输出节点 + + get_text() + * 获取子节点下所有的文本节点信息 + * 参数可以传递一个分隔符,该分隔符会对结果进行分隔 + * 关键字参数 + strip + * 如果该值为 True,会去除多余的空格.默认 False + + + + + + + + + + + + diff --git "a/Python/\346\241\206\346\236\266-beautifulsoup/bs-\345\205\245\351\227\250.py" "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\345\205\245\351\227\250.py" new file mode 100644 index 00000000..aaf26e28 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\345\205\245\351\227\250.py" @@ -0,0 +1,46 @@ +------------------------ +beautifulsoup | +------------------------ + * 安装 + pip install beautifulsoup4 + + * 文档 + http://beautifulsoup.readthedocs.io/zh_CN/latest/ + https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/ + + +------------------------ +beautifulsoup-开始解析 | +------------------------ + from bs4 import BeautifulSoup + + # 可以传递html文件 + soup = BeautifulSoup(open("index.html"),'html.parser') + + # 可以直接构造html字符串 + soup = BeautifulSoup("data",'html.parser') + +-------------------------- +beautifulsoup-基本四大对象| +-------------------------- + tag + * 表示标签 + + NavigableString + * 表示标签体 + + BeautifulSoup + * 表示整个html文档对象 + + Comment + * 表示注释 + * 它属于 NavigableString 的子类 + + * 还有一些其他的对象 + CData + * xml中的 + ProcessingInstruction + Declaratio + Doctype + * HTML中的 + \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-beautifulsoup/bs-\345\237\272\346\234\254\345\257\271\350\261\241-BeautifulSoup.py" "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\345\237\272\346\234\254\345\257\271\350\261\241-BeautifulSoup.py" new file mode 100644 index 00000000..5d089591 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\345\237\272\346\234\254\345\257\271\350\261\241-BeautifulSoup.py" @@ -0,0 +1,49 @@ +---------------------------- +BeautifulSoup | +---------------------------- + * BeautifulSoup 对象表示的是一个文档的全部内容 + * 大部分时候,可以把它当作 Tag 对象,它支持 遍历文档树 和 搜索文档树 中描述的大部分的方法 + * BeautifulSoup 对象并不是真正的HTML或XML的tag,所以它没有name(仅有一个其实)和attribute属性. + +---------------------------- +BeautifulSoup-属性 | +---------------------------- + name + * 该属性值是固定的:"[document]" + +---------------------------- +BeautifulSoup-注释等其他元素| +---------------------------- + * 注释类型是 Comment,是一个特殊的 NavigableString + markup = "" + soup = BeautifulSoup(markup,'html.parser') + + comment = soup.b.string + print(type(comment)) + # + + * 它出现在HTML文档中时, Comment 对象会使用特殊的格式输出: + print(soup.b.prettify()) + # + # + # + + * Beautiful Soup中定义的其它类型都可能会出现在XML的文档中: + CData + ProcessingInstruction + Declaratio + Doctype + * 与 Comment 对象类似,这些类都是 NavigableString 的子类 + * 只是添加了一些额外的方法 + + * xml中的cdata + from bs4 import CData + # 创建cdata对象 + cdata = CData("A CDATA block") + # 替换原来的注释对象 + comment.replace_with(cdata) # replace_with 是父类:NavigableString 的方法 + print(soup.b.prettify()) + # + # + # + diff --git "a/Python/\346\241\206\346\236\266-beautifulsoup/bs-\345\237\272\346\234\254\345\257\271\350\261\241-Tag.py" "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\345\237\272\346\234\254\345\257\271\350\261\241-Tag.py" new file mode 100644 index 00000000..3dae1591 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\345\237\272\346\234\254\345\257\271\350\261\241-Tag.py" @@ -0,0 +1,79 @@ +-------------------------------- +beautifulsoup-Tag | +-------------------------------- + * 这个tag与原生解析器里面的tag一个意思,都是指:标签 + from bs4 import BeautifulSoup + soup = BeautifulSoup(open('index.html'),'html.parser') + print(soup.a) # 打印出从上往下解析到的第一个a标签字符串 + print(type(soup.a)) # + +-------------------------------- +beautifulsoup-属性 | +-------------------------------- + name + * 该属性值是字符串,就是当前标签的字符串名称 + * 可以修改该属性,但会影响所有通过当前beautifulsoup生成的HTML文档 + soup = BeautifulSoup(open('index.html'),'html.parser') + soup.a.name = 'b' + print(soup) # KevinBlandy2 + + attrs + * 返回 dict,是当前标签的所有属性值 + soup.div.attrs # {'name': 'name属性', 'data-options': 'data-options属性', 'id': 'id属性'} + + * 也可以直接以[]方式进行访问,如果属性不存在,抛出异常 + soup.div['class'] + soup.div['data-options'] + + * tag的属性值可以被修改 + soup.div['name'] = '新的name属性' + #
+ + * tag的属性值可以被删除 + del tag['name'] + #
+ + * 如果属性可以有多个值(根据html标准),那么返回的是一个list +
+ soup.div.attrs + # {'class': ['c1', 'c2']} / 只有一个值也是list:{'class': ['c1']} + + * 多值属性也允许增删(反正就是用操作list的方法进行操作就是了) + soup.div.attrs['class'].append('c3') + #
+ + soup.div.attrs['class'].remove('c1') + #
+ + * 如果转换的文档是XML格式,那么tag中不包含多值属性 + xml_soup = BeautifulSoup('

', 'xml') + xml_soup.p['class'] + # 'body strikeout' + + * 也可以通过 get() 方法来获取属性值,属性不存在返回None + get(attr) + +-------------------------------- +beautifulsoup-标签体 | +-------------------------------- + *
我就是标签体
+ * 通过 tag.string 来获取 + soup.div.string + # 我就是标签体 + * 会被封装为: 类 + + * tag中包含的字符串不能编辑,但是可以通过 replace_with() 被替换成其它的字符串 + soup.div.string.replace_with('新的标签体') + #
新的标签体
+ + * 其实也可以直接替换 + soup.div.string = '新的标签体' + + + + + + + + + diff --git "a/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\345\205\266\344\273\226\346\223\215\344\275\234.py" "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\345\205\266\344\273\226\346\223\215\344\275\234.py" new file mode 100644 index 00000000..b9cd173d --- /dev/null +++ "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\345\205\266\344\273\226\346\223\215\344\275\234.py" @@ -0,0 +1,90 @@ +-------------------- +指定文档解析器 | +-------------------- + + +-------------------- +编码 | +-------------------- + + * UnicodeDammit + * UnicodeDammit 是BS内置库, 主要用来猜测文档编码 + from bs4 import UnicodeDammit + dammit = UnicodeDammit("Sacr\xc3\xa9 bleu!") + print(dammit.unicode_markup) + # Sacré bleu! + dammit.original_encoding + # 'utf-8' + + * 如果Python中安装了 chardet 或 cchardet 那么编码检测功能的准确率将大大提高. + * 输入的字符越多,检测结果越精确 + * 如果事先猜测到一些可能编码, 那么可以将猜测的编码作为参数(list),这样将优先检测这些编码: + dammit = UnicodeDammit("Sacr\xe9 bleu!", ["latin-1", "iso-8859-1"]) + print(dammit.unicode_markup) + # Sacré bleu! + dammit.original_encoding + # 'latin-1' + +-------------------- +比较对象是否相同 | +-------------------- + * 两个 NavigableString 或 Tag 对象具有相同的HTML或XML结构时, Beautiful Soup就判断这两个对象相同. + markup = "

I want pizza and more pizza!

" + soup = BeautifulSoup(markup, 'html.parser') + first_b, second_b = soup.find_all('b') + print (first_b == second_b) + # True + print (first_b.previous_element == second_b.previous_element) + # False + # 2个 标签在 BS 中是相同的, 尽管他们在文档树的不同位置, 但是具有相同的表象: “pizza” + + print (first_b is second_b) + # False + * 如果想判断两个对象是否严格的指向同一个对象可以通过 is 来判断 + +------------------------ +复制Beautiful Soup对象 | +------------------------ + * copy.copy() 方法可以复制任意 Tag 或 NavigableString 对象 + + import copy + p_copy = copy.copy(soup.p) + print (p_copy) + #

I want pizza and more pizza!

+ # 复制后的对象跟与对象是相等的, 但指向不同的内存地址 + + print soup.p == p_copy + # True + + print soup.p is p_copy + # False + + * 源对象和复制对象的区别是源对象在文档树中, 而复制后的对象是独立的还没有添加到文档树中. + * 复制后对象的效果跟调用了 extract() 方法相同. + print (p_copy.parent) + # None + # 这是因为相等的对象不能同时插入相同的位置 + +------------------------ +解析部分文档 | +------------------------ + * SoupStrainer 类可以定义文档的某段内容,这样搜索文档时就不必先解析整篇文档 + * 只会解析在 SoupStrainer 中定义过的文档. + * 创建一个 SoupStrainer 对象并作为 parse_only 参数给 BeautifulSoup 的构造方法即可. + from bs4 import SoupStrainer + + # 仅仅解析a标签 + only_a_tags = SoupStrainer("a") + + # 仅仅解析id=link2的标签 + only_tags_with_id_link2 = SoupStrainer(id="link2") + + def is_short_string(string): + return len(string) < 10 + + # 仅仅解析方法返回 true 的标签 + only_short_strings = SoupStrainer(string=is_short_string) + + * SoupStrainer 类接受与典型搜索方法相同的参数:name , attrs , recursive , string , **kwargs 。 + + * BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags) \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\346\240\221\344\277\256\346\224\271.py" "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\346\240\221\344\277\256\346\224\271.py" new file mode 100644 index 00000000..487042d0 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\346\240\221\344\277\256\346\224\271.py" @@ -0,0 +1,180 @@ +---------------------------- +修改tag的名称和属性 | +---------------------------- + * 直接修改其属性和标签名称 + soup = BeautifulSoup('Extremely bold') + tag = soup.b + + # 修改标签的名称 + tag.name = "blockquote" + # 修改标签的class属性值 + tag['class'] = 'verybold' + # 修改标签的id值 + tag['id'] = 1 + # 标签已经被修改 + #
Extremely bold
+ + # 删除指定的属性值 + del tag['class'] + del tag['id'] + # 标签属性已经被删除 + #
Extremely bold
+ + * 修改 .string + soup = BeautifulSoup('I linked to example.com') + tag = soup.a + # 修改a标签中的文本节点 + tag.string = "New link text." + # 文本节点已经被修改 + # New link text. + + * 如果当前的tag包含了其它tag,那么给它的 .string 属性赋值会覆盖掉原有的所有内容包括子tag + + * 使用 append() 添加文本节点的内容 + soup = BeautifulSoup("Foo") + # 往a标签的文本节点添加内容 + soup.a.append("Bar") + + # a标签中的文本节点已经添加 + # FooBar + soup.a.contents + # [u'Foo', u'Bar'] //没想通,为啥会以[]形式返回 + + * append() 的参数也可以是 NavigableString() 或者其子类的实例对象 + soup.a.append(NavigableString("bar")) + + * 添加注释 + from bs4 import Comment + # 创建注释对象,注意第二个参数表示是一个注释 + new_comment = soup.new_string("Nice to see you.", Comment) + # 添加到节点 + tag.append(new_comment) + print(tag) + # Hello there + print(tag.contents) + # ['Hello', ' there', 'Nice to see you.'] + + * 通过 new_tag() 添加tag + soup = BeautifulSoup("") + # 获取b标签 + original_tag = soup.b + + # 创建新的a标签,并且初始化 href 属性 + new_tag = soup.new_tag("a", href="http://www.example.com") + # 添加a标签到b标签中 + original_tag.append(new_tag) + print(original_tag) + # + # 修改a标签的文本节点值 + new_tag.string = "Link text." + print(original_tag) + # Link text. + + * new_tag() 第一个参数作为tag的name,是必填,其它参数选填 + + * 通过 insert() 插入新的元素 + markup = 'KevinBlandyexample.com' + soup = BeautifulSoup(markup, "html.parser") + tag = soup.a + + # 往a标签插入一个文本节点 + tag.insert(1, "Python Developer") + print(tag) + # KevinBlandyPython Developerexample.com + print(tag.contents) + # ['KevinBlandy', 'Python Developer', example.com] + + * insert_before() 和 insert_after()在节点之前/之后插入新的元素 + soup = BeautifulSoup("stop") + # 创建i节点 + tag = soup.new_tag("i") + # 设置i节点的文本属性 + tag.string = "Don't" + # 在b节点的文本节点之前插入i节点 + soup.b.string.insert_before(tag) + print(soup.b) + # Don'tstop + + # 在b节点下的i节点之后插入一个 ever 文本节点 + soup.b.i.insert_after(soup.new_string(" ever ")) + print(soup.b) + # Don't ever stop + print(soup.b.contents) + # [Don't, u' ever ', u'stop'] + + * 使用 clear() 方法移除当前tag的文本内容 + markup = 'I linked to example.com' + soup = BeautifulSoup(markup) + tag = soup.a + + tag.clear() + print(tag) + # //a标签之中的文本内容已经被清除掉 + + * 使用 extract()方法将当前tag移除文档树,并作为方法结果返回 + + markup = 'I linked to example.com' + soup = BeautifulSoup(markup) + a_tag = soup.a + + # 移除i标签,并且返回 + i_tag = soup.i.extract() + print(a_tag) + # I linked to //i标签已经被移除 + print(i_tag) + # example.com //这是已经被移除的i标签,它已经是独立的一个文档 + print(i_tag.parent) + # None + + * 这个方法实际上产生了2个文档树: + 一个是用来解析原始文档的 BeautifulSoup 对象,、 + 一个是被移除并且返回的tag.被移除并返回的tag可以继续调用 extract 方法 + + + * 使用 decompose() 方法将当前节点移除文档树并完全销毁 + markup = 'I linked to example.com' + soup = BeautifulSoup(markup) + a_tag = soup.a + # 移除 i 标签 + soup.i.decompose() + print(a_tag) + # I linked to //i标签已经被移除 + + * 使用 replace_with()方法移除文档树中的某段内容,并用新tag或文本节点替代它 + markup = 'I linked to example.com' + soup = BeautifulSoup(markup) + a_tag = soup.a + + # 创建新的b标签 + new_tag = soup.new_tag("b") + # 创建b标签的标签体 + new_tag.string = "example.net" + # 移除i标签,并且使用b标签来代替它 + a_tag.i.replace_with(new_tag) + print(a_tag) + # I linked to example.net + + * replace_with() 方法返回被替代的tag或文本节点,可以用来浏览或添加到文档树其它地方 + + * 使用 wrap() 方法可以对指定的tag元素进行包装 ,并返回包装后的结果 + soup = BeautifulSoup("

I wish I was bold.

") + # 通过一个新建的b标签,来包装p节点的标签体(文本节点) + soup.p.string.wrap(soup.new_tag("b")) + # I wish I was bold. + + # 通过新建一个 div 节点来包装 p 节点 + soup.p.wrap(soup.new_tag("div")) + #

I wish I was bold.

+ + * 该方法在 Beautiful Soup 4.0.5 中添加 + + * 使用 unwrap()移除tag内的所有tag标签,该方法常被用来进行标记的解包 + markup = 'I linked to example.com' + soup = BeautifulSoup(markup) + a_tag = soup.a + # 移除a标签中的i标签 + a_tag.i.unwrap() + print(a_tag) + # I linked to example.com + + * 与 replace_with() 方法相同, unwrap() 方法返回被移除的tag \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\346\240\221\346\220\234\347\264\242.py" "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\346\240\221\346\220\234\347\264\242.py" new file mode 100644 index 00000000..199d063f --- /dev/null +++ "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\346\240\221\346\220\234\347\264\242.py" @@ -0,0 +1,178 @@ +---------------------------- +杩囨护鍣 | +---------------------------- + * 瀛楃涓茶繃婊 + soup.find_all('a') + * 杩斿洖鎵鍋 a 鏍囩鑺傜偣 + + * 姝e垯琛ㄨ揪寮忚繃婊 + soup.find_all(re.compile('^b')) + * 妫绱㈡墍鏈変互b寮澶寸殑鑺傜偣.鍜
閮戒細琚绱㈠埌 + + * 鍒楄〃杩囨护 + soup.find_all(['a','b']) + * 鍖归厤a鏍囩鎴栬卋鏍囩 + * 鍙鏄痆]浠绘剰涓涓尮閰嶅氨ok + + * True + * True 鍙互鍖归厤浠讳綍鍊,涓嬮潰浠g爜鏌ユ壘鍒版墍鏈夌殑tag,浣嗘槸涓嶄細杩斿洖瀛楃涓茶妭鐐 + for tag in soup.find_all(True): + print(tag.name) + + * 鏂规硶 + * 鍙互鐢ㄤ竴涓柟娉曚綔涓哄弬鏁拌繘琛岃繃婊.璇ユ柟娉曡捣鐮佷笖蹇呴』鏈変竴涓弬鏁,灏辨槸褰撳墠閬嶅巻鍒扮殑鑺傜偣 + * 閫氳繃杩斿洖 bool 鍊艰繕鍒ゆ柇鑺傜偣鏄鏄渶瑕佺殑 + for tag in soup.find_all(lambda e : e.get('id')): + print(tag.name) + # 杩斿洖鎵鏈夊叿澶噄d灞炴х殑鑺傜偣 + + * 鏍规嵁鏍囩灞炴ц繃婊 + * 鍏抽敭瀛楀弬鏁板氨鏄杩囨护鐨勫睘鎬 + soup.find_all('a',id='1') + # 妫绱d=1鐨刟鏍囩 + + * 鍏抽敭瀛楀弬鏁颁篃鍙互鏄竴涓嚱鏁版潵杩涜杩囨护 + soup.find_all('a',id=lambda id : id=='2') + # 妫绱d鍊间负2鐨刟鏍囩 + + * 涔熷彲浠ヤ娇鐢ㄦ鍒,鍒楄〃,True + + * 鏈変簺tag灞炴у湪鎼滅储涓嶈兘浣跨敤,姣斿HTML5涓殑 data-* + data_soup = BeautifulSoup('
foo!
') + data_soup.find_all(data-foo="value") + # SyntaxError: keyword can't be an expression + + 浣嗘槸鍙互閫氳繃 find_all() 鏂规硶鐨 attrs 鍙傛暟瀹氫箟涓涓瓧鍏稿弬鏁版潵鎼滅储鍖呭惈鐗规畩灞炴х殑tag: + data_soup.find_all(attrs={"data-foo": "value"}) + # [
foo!
] + + * 鏍规嵁class杩囨护 + * CSS绫诲悕鐨勫叧閿瓧 class 鍦≒ython涓槸淇濈暀瀛,浣跨敤 class 鍋氬弬鏁颁細瀵艰嚧璇硶閿欒 + * 浠嶣eautiful Soup鐨4.1.1鐗堟湰寮濮,鍙互閫氳繃 class_ 鍙傛暟鎼滅储鏈夋寚瀹欳SS绫诲悕鐨則ag + * class_ 鍙傛暟鍚屾牱鎺ュ彈涓嶅悓绫诲瀷鐨 杩囨护鍣 ,瀛楃涓,姝e垯琛ㄨ揪寮,鏂规硶鎴 True + * class 灞炴ф槸 澶氬煎睘鎬 .鎸夌収CSS绫诲悕鎼滅储tag鏃,鍙互鍒嗗埆鎼滅储tag涓殑姣忎釜CSS绫诲悕 + css_soup = BeautifulSoup('

') + css_soup.find_all("p", class_="strikeout") + # [

] + + css_soup.find_all("p", class_="body") + # [

] + + * 鎼滅储 class 灞炴ф椂涔熷彲浠ラ氳繃CSS鍊煎畬鍏ㄥ尮閰 + css_soup.find_all("p", class_="body strikeout") + + * 鏍规嵁鏂囨湰杩囨护 + soup.find_all(string="title") + # ['title'] + + * string 杩樺彲浠ヤ笌鍏跺畠鍙傛暟娣峰悎浣跨敤鏉ヨ繃婊 + soup.find_all("a", string="Elsie") + # [Elsie] + * string 鍊间篃鏀寔 bool,姝e垯,[] 鍑芥暟 + + * 闄愬埗缁撴灉闆 + soup.find_all("a",limit=2) + * 浠呬粎妫绱㈠墠淇゛鏍囩 + + * 浠呬粎妫绱㈢洿鎺ュ瓙鍏冪礌,閫氳繃 recursive 鍏抽敭瀛楁帶鍒 + soup.find_all("a",recursive=True) + + * 鍍忚皟鐢 find_all() 涓鏍疯皟鐢╰ag + * 瀵硅薄鍜 tag 瀵硅薄鍙互琚綋浣滀竴涓柟娉曟潵浣跨敤,杩欎釜鏂规硶鐨勬墽琛岀粨鏋滀笌璋冪敤杩欎釜瀵硅薄鐨 find_all() 鏂规硶鐩稿悓,涓嬮潰涓よ浠g爜鏄瓑浠风殑 + soup.find_all("a") + soup("a") + + soup.title.find_all(string=True) + soup.title(string=True) + + * 浣跨敤 find() 浠呬粎妫绱竴涓粨鏋滈泦,璺 find_all() 涓鏍,瀹冭繑鍥炵殑鍙湁涓涓粨鏋滈泦 + + * css閫夋嫨鍣 + * BeautifulSoup 瀵硅薄鐨 .select() 鏂规硶涓紶鍏ュ瓧绗︿覆鍙傛暟, 鍗冲彲浣跨敤CSS閫夋嫨鍣ㄧ殑璇硶鎵惧埌tag + * 灏辨槸璺焎ss鐨勯夋嫨鍣ㄤ竴鏍风殑閫夋嫨妯″紡 + soup.select("title") + # [The Dormouse's story] + + soup.select("p nth-of-type(3)") + # [

...

] + + * 閫氳繃tag鏍囩閫愬眰鏌ユ壘( ) + soup.select("body a") + # [Elsie, + # Lacie, + # Tillie] + + soup.select("html head title") + # [The Dormouse's story] + + * 鎵惧埌鏌愪釜tag鏍囩涓嬬殑鐩存帴瀛愭爣绛 (>) + soup.select("head > title") + # [The Dormouse's story] + + soup.select("p > a") + # [Elsie, + # Lacie, + # Tillie] + + soup.select("p > a:nth-of-type(2)") + # [Lacie] + + soup.select("p > #link1") + # [Elsie] + + soup.select("body > a") + # [] + + * 鎵惧埌鍏勫紵鑺傜偣鏍囩(~) + soup.select("#link1 ~ .sister") + # [Lacie, + # Tillie] + + soup.select("#link1 + .sister") + # [Lacie] + + + * 閫氳繃CSS鐨勭被鍚嶆煡鎵(.) + soup.select(".sister") + # [Elsie, + # Lacie, + # Tillie] + + soup.select("[class~=sister]") + # [Elsie, + # Lacie, + # Tillie] + + * 閫氳繃tag鐨刬d鏌ユ壘(#) + soup.select("#link1") + # [Elsie] + + soup.select("a#link2") + # [Lacie] + + * 鍚屾椂鐢ㄥ绉岰SS閫夋嫨鍣ㄦ煡璇㈠厓绱(,) + soup.select("#link1,#link2") + # [Elsie, + # Lacie] + + * 閫氳繃鏄惁瀛樺湪鏌愪釜灞炴ф潵鏌ユ壘([]) + soup.select('a[href]') + # [Elsie, + # Lacie, + # Tillie] + + * 閫氳繃灞炴х殑鍊兼潵鏌ユ壘([]) + soup.select('a[href="http://example.com/elsie"]') + # [Elsie] + + soup.select('a[href^="http://example.com/"]') + # [Elsie, + # Lacie, + # Tillie] + + soup.select('a[href$="tillie"]') + # [Tillie] + + soup.select('a[href*=".com/el"]') + # [Elsie] + \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\346\240\221\351\201\215\345\216\206.py" "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\346\240\221\351\201\215\345\216\206.py" new file mode 100644 index 00000000..5b0b5f74 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\346\240\221\351\201\215\345\216\206.py" @@ -0,0 +1,108 @@ +------------------------ +子节点 | +------------------------ + * 操作文档树最简单的方法就是告诉它你想获取的tag的name + soup.head.title + # 获取文档中 head 标签下的 titile 标签 + + * 通过这种方式,只能获取到指定名称的第一个标签 + + * 获取文档中的所有标签,需要使用 find_all(),返回list + soup.find_all('a') + + * 根据属性获取子节点信息 + contents + * 返回list,当前标签'所有直接子元素集合',包括文本节点(bs4.element.NavigableString) + * '字符串节点没有该属性' + + children + * 返回迭代器,当前标签的'所有直接子元素' + + descendants + * 返回当前标签的'所有子元素' + * 递归 + + * 如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点 + soup.head.title.string + + * 如果tag只有一个子标签,这个tag也可以直接通过 .string 获取到唯一子标签的标签体 + Title + soup.head.string + # Title + + * 通过 strings 获取所有的子文本信息,递归获取 + soup.strings + + * 返回的是迭代器 + * 使用 stripped_strings 可以忽略掉所有的空白信息 + + +------------------------ +父节点 | +------------------------ + * 通过 .parent 属性来获取某个元素的父节点 + title = soup.head.title + title.parent + # Title + + * 文档节点,也有父节点属性 + title = soup.head.title.string + title.parent + # Title + + * 文档的顶层节点比如的父节点是 BeautifulSoup 对象 + html_tag = soup.html + type(html_tag.parent) + # + + * BeautifulSoup 对象的 .parent 是None: + print(soup.parent) + # None + + * 通过元素的 .parents 属性可以递归得到元素的所有父辈节点 + for i in soup.a.parents: + print(i.name) + + #div + # body + # html + # [document] + +------------------------ +兄弟节点 | +------------------------ + * 使用 .next_sibling 和 .previous_sibling 属性来查询兄弟节点 + title = soup.head.title + meta = soup.head.meta + print(title.next_sibling) + print(meta.previous_sibling) + + # + # Title + + * title 没有上一个兄弟标签,meta没有下一个兄弟标签,强行获取返回 None + * 空白/换行也会被计算为节点 + + * 通过 .next_siblings 和 .previous_siblings 获取所有的兄弟节点,返回迭代器 + for i in soup.head.title.next_siblings: + print(i) + +------------------------ +回退和前进 | +------------------------ + * 通过 .next_element 和 .previous_element 来访问上/下一个被解析的节点 + * 通过 .next_elements 和 .previous_elements 来访问上/下被解析的内容,它是一个迭代器 + + * 他们有点像父子节点,实质大不同. + * -> + + + + + + + + + + + diff --git "a/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\350\276\223\345\207\272.py" "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\350\276\223\345\207\272.py" new file mode 100644 index 00000000..530aef83 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-beautifulsoup/bs-\346\226\207\346\241\243\350\276\223\345\207\272.py" @@ -0,0 +1,12 @@ +------------------------ +输出 | +------------------------ + prettify() + * 输出节点 + + get_text() + * 获取子节点下所有的文本节点信息 + * 参数可以传递一个分隔符,该分隔符会对结果进行分隔 + * 关键字参数 + strip + * 如果该值为 True,会去除多余的空格.默认 False diff --git "a/Python/\346\241\206\346\236\266-lxml/lxml-xpath-\350\257\255\346\263\225.py" "b/Python/\346\241\206\346\236\266-lxml/lxml-xpath-\350\257\255\346\263\225.py" new file mode 100644 index 00000000..e8a556e3 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-lxml/lxml-xpath-\350\257\255\346\263\225.py" @@ -0,0 +1,32 @@ +-------------------------------- +xpath 璇硶 | +-------------------------------- + nodename + * 閫夊彇姝よ妭鐐圭殑鎵鏈夊瓙鑺傜偣 + . + * 閫夊彇褰撳墠鑺傜偣銆 + .. + * 閫夊彇褰撳墠鑺傜偣鐨勭埗鑺傜偣 + + @ + * 閫夊彇灞炴 + /p + * 閫夊彇鏍瑰厓绱爌 + + //a//@href + * 鑾峰彇鎵鏈塧鏍囩涓殑href鐨勫睘鎬у + + //a[@href='http://www.layui.com/'] + * 鑾峰彇href鍊=http://www.layui.com/鐨刟鏍囩鑺傜偣 + + h3/text() + p/text() + * 鑾峰彇褰撳墠鏍囩涓嬫墍鏈夌殑h3/p瀛愭爣绛剧殑鏍囩浣撳唴瀹,杩斿洖[] + + /td[1]//text() + * 鑾峰彇褰撳墠鏍囩涓嬬殑绗1涓猼d鐨勬爣绛句綋鍐呭 + /bookstore/book[last()] + * 閫夊彇灞炰簬 bookstore 瀛愬厓绱犵殑鏈鍚庝竴涓 book 鍏冪礌 + /bookstore/book[last()-1] + * 閫夊彇灞炰簬 bookstore 瀛愬厓绱犵殑鍊掓暟绗簩涓 book 鍏冪礌 + diff --git "a/Python/\346\241\206\346\236\266-lxml/lxml-\345\205\245\351\227\250.py" "b/Python/\346\241\206\346\236\266-lxml/lxml-\345\205\245\351\227\250.py" new file mode 100644 index 00000000..f5b8ce39 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-lxml/lxml-\345\205\245\351\227\250.py" @@ -0,0 +1,161 @@ +------------------------- +lxml | +------------------------- + * 瀹冧粎浠呭彧鏄竴涓涓夋柟搴,绠椾笉涓婃鏋,鎻愪緵浜嗗己澶х殑xml鎿嶄綔api + * from lxml import etree + + +------------------------- +lxml-etree 妯″潡 鍑芥暟 | +------------------------- + HTML(text, parser=None, base_url=None) + * 閫氳繃html鏂囨湰鏋勯犱竴涓 Element 瀵硅薄 + + XML(text, parser=None, base_url=None) + * 閫氳繃xml鏂囨湰鏋勯犱竴涓 Element 瀵硅薄 + + tostring(element_or_tree, + encoding=None, method="xml", + xml_declaration=None, + pretty_print=False, + with_tail=True, + standalone=None, doctype=None, exclusive=False, with_comments=True, inclusive_ns_prefixes=None) + + * 浠ュ瓧绗︿覆褰㈠紡杈撳嚭鎸囧畾鑺傜偣瀵硅薄 + + SubElement(_parent, _tag, attrib=None, nsmap=None, **_extra) + * 娣诲姞鎸囧畾鍚嶇О鐨勫瓙鑺傜偣鍒拌妭鐐瑰璞,杩斿洖瀛愯妭鐐瑰璞 + * 鍙傛暟 + _parent 鐖剁骇鑺傜偣瀵硅薄 + _tag 瀛愯妭鐐瑰悕绉(瀛楃涓) + * 鍏抽敭瀛楀弬鏁 + attrib 鎸囧畾瀛愭爣绛剧殑灞炴у + + XPath() + * 鍒涘缓涓涓獂path瀵硅薄,鍙互閫氳繃璇ュ璞℃潵瀵规爣绛炬枃鏈繘琛屾绱㈡搷浣 + * demo + xpath = etree.XPath("//text()") + print(xpath(etree.XML('Hello'))) # ['Hello'] + + fromstring(xml_str) + * 鎶婃寚瀹氱殑xml鏂囨湰瑙f瀽涓:Element 瀵硅薄 + + parse(path) + * 璇诲彇鎸囧畾鐨勬枃浠,瑙f瀽涓 Element 瀵硅薄 + +------------------------- +lxml-etree-瀹炰緥灞炴,鏂规硶 | +------------------------- + tag + * 杩斿洖鏍囩鍚嶇О + + text + * 鏍囩浣 + + attrib + * 鏍囩鐨勫睘鎬ict + + tail + * 鑷叧闂爣绛惧悗鐨勬枃鏈 + + append(e) + * 娣诲姞涓涓狤lement瀵硅薄鍒板綋鍓嶅璞$殑瀛愯妭鐐 + + set(k,v) + * 璁剧疆鏍囩鐨勫睘鎬у + + get(k) + * 鑾峰彇鏍囩鎸囧畾鍚嶇О鐨勫睘鎬у + + items() + * 杩斿洖鏍囩鐨勫睘鎬(k,v)] + + iter() + * 杩斿洖瀛愭爣绛捐凯浠e櫒(閫掑綊) + * 涔熷彲浠ヤ紶閫掓爣绛惧悕绉颁綔涓哄弬鏁,鏉ヨ繃婊よ杩唬鐨勫瓙鏍囩 + + xpath() + * 鏍规嵁xpath琛ㄨ揪寮忔绱㈡暟鎹,杩斿洖[] + + iterfind() + * 杩斿洖婊¤冻鍖归厤鐨勮妭鐐瑰垪琛,杩斿洖杩唬鍣,鏀寔xpath琛ㄨ揪寮 + findall() + * 杩斿洖婊¤冻鍖归厤鐨勮妭鐐瑰垪琛,鏀寔xpath琛ㄨ揪寮 + find() + * 杩斿洖婊¤冻鍖归厤鐨勭涓涓,鏀寔xpath琛ㄨ揪寮 + findtext() + * 杩斿洖绗竴涓弧瓒冲尮閰嶆潯浠剁殑.text鍐呭,鏀寔xpath琛ㄨ揪寮 + + + +------------------------- +lxml-etree 鍩烘湰鎿嶄綔 | +------------------------- + * 鐢熸垚(鍒涘缓)绌簒ml鑺傜偣瀵硅薄 + root = etree.Element("root") + print(etree.tostring(root, pretty_print=True)) + + * 鐢熸垚瀛愯妭鐐 + from lxml import etree + root = etree.Element("root") + + root.append(etree.Element("child1")) # 鐩存帴閫氳繃瀹炰緥瀵硅薄鐨刟ppend鏂规硶娣诲姞涓涓狤lement瀛愭爣绛惧璞 + + child2 = etree.SubElement(root, "child2") # 閫氳繃etree妯″潡鐨凷ubElement鏉ユ坊鍔犲瓙鏍囩 + child2 = etree.SubElement(root, "child3") + print(etree.tostring(root)) + + * 甯﹀唴瀹圭殑xml鑺傜偣 + from lxml import etree + root = etree.Element("root") + root.text = "Hello World" # 閫氳繃鑺傜偣瀵硅薄鐨則ext灞炴ф潵鑾峰彇/璁剧疆鏍囩浣 + print(etree.tostring(root)) + + * 灞炴х敓鎴 + from lxml import etree + root = etree.Element("root", name = "Kevin") # 鍦ㄦ瀯閫犲嚱鏁颁紶閫掑叧閿瓧鍙傛暟鏉ヨ缃睘鎬 + root.set("hello","huhu") # 閫氳繃鑺傜偣瀵硅薄鐨 set(key,value) 鏉ヨ缃睘鎬 + root.text = "Hello World" # 璁剧疆鑺傜偣鐨勬爣绛句綋 + print(etree.tostring(root)) + + * 鑾峰彇灞炴 + from lxml import etree + root = etree.Element("root", name = "Kevin") + print(root.get('name')) # 閫氳繃get()鏂规硶鏉ヨ幏鍙栨寚瀹氳妭鐐瑰璞$殑灞炴,濡傛灉灞炴т笉瀛樺湪杩斿洖 None + + from lxml import etree + root = etree.Element("root", name = "Kevin",age="15") + print(root.attrib) # 閫氳繃 attrib 灞炴ф潵鑾峰彇鑺傜偣灞炴х殑dict + print(root.items()) # 閫氳繃 items() 鏂规硶杩斿洖鑺傜偣灞炴х殑[(key,value),(key,value)] + + + * 鐗规畩鍐呭 + from lxml import etree + html = etree.Element("html") + body = etree.Element("body") + body.text = 'Hello' + br = etree.Element("br") + br.tail = "KevinBlandy" # 鍦ㄨ嚜鍏抽棴鏍囩鍚庢坊鍔犵殑鏂囨湰 + body.append(br) + html.append(body) + print(etree.tostring(html)) + # Hello
KevinBlandy + + * 鑺傜偣閬嶅巻 + for element in root.iter(): + print(element.tag, element.text) + + for element in root.iter("child"): # 鎸囧畾鑺傜偣鍚嶇О鏉ヨ繃婊ゅ瓙鑺傜偣 + print(element.tag, element.text) + + * 鑺傜偣鏌ユ壘 + iterfind() + * 杩斿洖婊¤冻鍖归厤鐨勮妭鐐瑰垪琛,杩斿洖杩唬鍣 + findall() + * 杩斿洖婊¤冻鍖归厤鐨勮妭鐐瑰垪琛 + find() + * 杩斿洖婊¤冻鍖归厤鐨勭涓涓 + findtext() + * 杩斿洖绗竴涓弧瓒冲尮閰嶆潯浠剁殑.text鍐呭 + + * 浠栦滑閮芥敮鎸亁path琛ㄨ揪寮 \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-scrapy/scrapy-\345\205\245\351\227\250.py" "b/Python/\346\241\206\346\236\266-scrapy/scrapy-\345\205\245\351\227\250.py" new file mode 100644 index 00000000..48368dd0 --- /dev/null +++ "b/Python/\346\241\206\346\236\266-scrapy/scrapy-\345\205\245\351\227\250.py" @@ -0,0 +1,93 @@ +---------------------------- +scrapy-鍏ラ棬 | +---------------------------- + * 瀛︿範鍦板潃 + http://www.cnblogs.com/-E6-/p/7211025.html + http://scrapy-chs.readthedocs.io/zh_CN/latest/index.html# + https://docs.scrapy.org/en/latest/index.html # 瀹樻柟鏂囨。 + + * 杩欎笢瑗夸娇鐢ㄤ簡 Twistid 寮傛缃戠粶閫氫俊妗嗘灦鏉ュ鐞嗙綉缁滆繛鎺(涓昏瀵规墜灏辨槸:Tornado) + +---------------------------- +scrapy-瀹夎 | +---------------------------- + * pip 瀹夎 + pip install scrapy + + * 澶勭悊 building 'twisted.test.raiser' extension + error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools + + 1,浠庣綉绔欎笅杞絯hl鏂囦欢 + https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted + + Twisted + Twisted-17.9.0-cp36-cp36m-win_amd64.whl + * cp36 琛ㄧずpython鐗堟湰 + * win_amd64 64浣嶅鐞嗗櫒绯荤粺 + + 2,鎵ц瀹夎whl + pip install Twisted-17.9.0-cp36-cp36m-win_amd64.whl + + 3,鍐嶆鎵ц瀹夎scrapy + pip install scrapy + +---------------------------- +scrapy-瀹夎(Linux) | +---------------------------- + * 鍚勭鍧 + * http://blog.csdn.net/u012949658/article/details/55001179 + + +---------------------------- +scrapy-鏋舵瀯缁勪欢涓瑙 | +---------------------------- + Scrapy Engine(寮曟搸) + * 涓ぎ澶ц剳,璐熻矗缁勪欢涔嬮棿鐨勮皟搴 + Spider + * + Spiders Middlewares(Spider涓棿浠) + * 鏄竴涓彲浠ュ畾涔夋墿杩欐搷浣滃紩鎿庡拰Spider涓棿閫氫俊鐨勫姛鑳界粍浠 + ItemPipeline(閫氶亾) + * 瀹冭礋璐e鐞哠pider涓幏鍙栫殑Item,骞朵笖杩涜鍚庢湡澶勭悊(鍒嗘瀽,杩囨护,瀛樺偍) + Downloader Middlewares(涓嬭浇涓棿浠) + * 鍙互褰撲綔鏄竴涓彲浠ヨ嚜瀹氫箟鎵╁睍涓嬭浇鍔熻兘鐨勭粍浠 + Downloader(涓嬭浇鍣) + * 璐熻矗涓嬭浇寮曟搸鍙戦佺殑鎵鏈塕equests璇锋眰 + * 鐥呭苟涓旀妸鍝嶅簲缁撴灉浜よ繕缁欏紩鎿,鐢卞紩鎿庝氦杩樼粰Spider鏉ュ鐞 + Scheduler(璋冨害鍣) + * 璐熻矗鎺ュ彈寮曟搸鍙戦佽繃鏉ョ殑request璇锋眰,骞朵笖鎸夌収涓瀹氱殑鏂瑰紡鏁寸悊闃熷垪:鍏ラ槦 + * 褰撳紩鎿庨渶瑕佺殑鏃跺,杩樹細鎶妑equest浜よ繕缁欏紩鎿 + + * 鎵ц娴佺▼ + 1,Spiders鎶婅姹備涪缁欏紩鎿 + 2,寮曟搸鎶婅姹備涪缁橲cheduler + 3,Scheduler鎶婅姹備涪缁橠owmloader鍘绘墽琛 + 4,Dowmloader鎶婃墽琛岀殑缁撴灉杩斿洖缁橲piders + * 濡傛灉杩斿洖鐨勬槸鏁版嵁,鍒欎涪缁橧temPipeline鍘诲鐞嗘暟鎹 + * 濡傛灉杩斿洖鐨勬槸杩炴帴,鍒欓噸澶1姝ラ + +---------------------------- +scrapy-寮濮嬪洓閮ㄦ洸 | +---------------------------- + 1,鏂板缓椤圭洰 + scrapy startproject [name] + + * 鍒涘缓濂界殑鐩綍缁撴瀯 + name + |-name + |-spiders # 鐖櫕瀛樻斁鐩綍 + |-__init__.,py + |-__init__.,py + |-items.py # 鏁版嵁妯″瀷瀹氫箟 + |-middlewares.py # + |-pipelines.py # 鏁版嵁澶勭悊绠¢亾 + |-settings.py # 閰嶇疆妯″潡 + |-scrapy.cfg + + 2,鏄庣‘鐩爣(缂栧啓items.py),鏄庣‘瑕佹姄鍙栫殑鐩爣 + 3,鍒朵綔鐖櫕(spiders/xxsipder.py),鍒朵綔鐖櫕寮濮嬬埇鍙栫綉椤 + 4,瀛樺偍鍐呭(pipelines.py),璁捐绠¢亾瀛樺偍鐖彇鍐呭 + + + + diff --git "a/Python/\346\241\206\346\236\266-selenium/sm-\345\205\245\351\227\250.py" "b/Python/\346\241\206\346\236\266-selenium/sm-\345\205\245\351\227\250.py" new file mode 100644 index 00000000..48f021bc --- /dev/null +++ "b/Python/\346\241\206\346\236\266-selenium/sm-\345\205\245\351\227\250.py" @@ -0,0 +1,259 @@ +---------------------------- +入门 | +---------------------------- + * 安装 + pip install selenium + + +---------------------------- +创建对象 | +---------------------------- + # 导入webdriver + from selenium import webdriver + + # 创建driver对象,通过executable_path指定phantomjs执行文件路径 + driver = webdriver.PhantomJS(executable_path='./phantomjs') + +---------------------------- +基本的操作 | +---------------------------- + # 导入 webdriver + from selenium import webdriver + + # 想要调用键盘操作,需要引入 keys 包 + from selenium.webdriver.common.keys import Keys + + # 调用环境变量指定的 PhantomJS 浏览器创建浏览器对象 + # driver = webdriver.PhantomJS() + + # 如果没有在环境变量指定 PhantomJS 的位置(其实就是指定 phantomjs执行文件的地址) + driver = webdriver.PhantomJS(executable_path='./phantomjs') + + # ger方法会一直等到页面完全被加载,然后才会继续程序的执行 + driver.get('https://www.baidu.com') + + # 生成页面快照 + driver.save_screenshot('baidu.png') + + # 找到id为kw的元素(input),在里面写入 "Hello" + driver.find_element_by_id('kw').send_keys("Hello") + + # 找到id为id的元素,执行点击它 + driver.find_element_by_id('su').click() + + # 生成页面快照 + driver.save_screenshot('hello.png') + + # 打印网页源码 + print(driver.page_source) + + # 获取所有的 cookie + print(driver.get_cookies()) + + # 获取指定名称的cookie + #driver.get_cookie('JESSIONID') + + # ctrl + a 全选输入框内容 + driver.find_element_by_id('kw').send_keys(Keys.CONTROL,'a') + + # ctrl + x 剪切输入框内容 + driver.find_element_by_id('kw').send_keys(Keys.CONTROL,'x') + + # 输入框重新输入内容 + driver.find_element_by_id('kw').send_keys('KevinBlandy') + + # 模拟enter回车键 + driver.find_element_by_id('su').send_keys(Keys.ENTER) + + # 清除输入框内容 + driver.find_element_by_id('kw').clear() + + # 获取当前Url + print(driver.current_url) + + # 关闭当前页面,如果只有一个页面会关闭浏览器 + driver.close() + + # 关闭浏览器 + driver.quit() + +---------------------------- +元素获取 | +---------------------------- + * 基本的获取 + # 根据id检索 + driver.find_element_by_id() + # 根据名字检索 + driver.find_element_by_name() + driver.find_element_by_xpath() + driver.find_element_by_link_text() + driver.find_element_by_partial_link_text() + # 根据标签名称检索 + driver.find_element_by_tag_name() + # 根据class名称检索 + driver.find_element_by_class_name() + # 根据css选择器检索 + driver.find_element_by_css_selector() + + * 也可以优雅的通过api来获取 + from selenium.webdriver.common.by import By + # 通过 By 枚举来指定要过滤的属性,后面valu指定值 + element = driver.find_element(by=By.ID, value="coolestWidgetEvah") + + * By.CLASS_NAME + * By.TAG_NAME + * By.NAME + * By.LINK_TEXT + * By.CSS_SELECTOR + * By.XPATH + + +---------------------------- +鼠标动作链 | +---------------------------- + from selenium import webdriver + from selenium.webdriver import ActionChains + driver = webdriver.PhantomJS(executable_path='./phantomjs') + + # 获取节点对象 + photo = driver.find_element_by_id('photo') + + # 通过driver创建Action调用链对象 + action = ActionChains(driver) + + # 移动鼠标到指定的节点 + action.move_to_element(photo).perform() + + # 单击指定节点 + action.move_to_element(photo).click(photo).perform() + + # 双击指定节点 + action.move_to_element(photo).double_click(photo).perform() + + # 右击指定节点 + action.move_to_element(photo).context_click(photo).perform() + + # 左键hold住指定节点 + action.move_to_element(photo).click_and_hold(photo).perform() + + # 把photo节点拖拽到next节点 + action.drag_and_drop(photo, driver.find_element_by_id('next')).perform() + +---------------------------- +表单填充 | +---------------------------- + * 已经知道了怎样向文本框中输入文字 + * 但是有时候我们会碰到标签的下拉框,直接点击下拉框中的选项不一定可行 + * Selenium专门提供了Select类来处理下拉框 + # 导入 Select 类 + from selenium.webdriver.support.ui import Select + + # 找到 name 的选项卡 + select = Select(driver.find_element_by_name('status')) + + # 根据下拉项的位置选择 + select.select_by_index(1) + # 根据选项的值选择 + select.select_by_value("0") + # 根据option标签文本的值选择 + select.select_by_visible_text("未审核") + + # 取消选择 + select.deselect_all() + +---------------------------- +弹窗处理 | +---------------------------- + * 处理页面的alert弹窗 + alert = driver.switch_to_alert() + +---------------------------- +页面切换 | +---------------------------- + * 切面页面 + driver.switch_to_window('页面名称') + + * 也可以使用 window_handles 方法来获取每个窗口的操作对象 + for handle in driver.window_handles: + driver.switch_to_window(handle) + +---------------------------- +页面前进和后退 | +---------------------------- + driver.forward() # 前进 + driver.back() # 后退 + +---------------------------- +执行js脚本 | +---------------------------- + driver.execute_script('alert("哈哈")') + +---------------------------- +cookie | +---------------------------- + * 获取所有的 cookie + driver.get_cookies() + + * 获取指定名称的cookie + driver.get_cookie('JESSIONID') + + * 删除cookie + driver.delete_cookie('name') + + * 删除所有cookie + driver.delete_all_cookies() + + +---------------------------- +页面等待 | +---------------------------- + * 网站都几乎有才用ajax异步加载,有些元素是通过异步加载才会被载入dom内存 + * 有两种方式处理这个问题 + * .... ... + + * 显示等待,显示指定某个条件,然后设置最长等待时间,如果超时还未找到元素,抛出异常 + from selenium import webdriver + from selenium.webdriver.common.by import By + # WebDriverWait 库,负责循环等待 + from selenium.webdriver.support.ui import WebDriverWait + # expected_conditions 类负责条件触发 + from selenium.webdriver.support import expected_conditions as expected_conditions + + driver = webdriver.Chrome() + driver.get("http://www.xxxxx.com/loading") + try: + # 页面一直循环,直到 id="myDynamicElement" 出现 + element = WebDriverWait(driver, 10).until( + expected_conditions.presence_of_element_located((By.ID, "myDynamicElement")) + ) + finally: + driver.quit() + + * 程序默认会 0.5s 调用一次来查看元素是否已经生成,如果本来元素就是存在的,那么会立即返回。 + * 下面是一些内置的等待条件,可以直接调用这些条件,而不用自己写某些等待条件了 + title_is + title_contains + presence_of_element_located + visibility_of_element_located + visibility_of + presence_of_all_elements_located + text_to_be_present_in_element + text_to_be_present_in_element_value + frame_to_be_available_and_switch_to_it + invisibility_of_element_located + element_to_be_clickable – it is Displayed and Enabled. + staleness_of + element_to_be_selected + element_located_to_be_selected + element_selection_state_to_be + element_located_selection_state_to_be + alert_is_present + + * 隐式等待比较简单,就是简单地设置一个等待时间,单位为秒 + from selenium import webdriver + driver = webdriver.Chrome() + driver.implicitly_wait(10) # seconds + driver.get("http://www.xxxxx.com/loading") + myDynamicElement = driver.find_element_by_id("myDynamicElement") + + * 当然如果不设置,默认等待时间为0 \ No newline at end of file diff --git "a/Python/\346\241\206\346\236\266-yaml/yaml.py" "b/Python/\346\241\206\346\236\266-yaml/yaml.py" new file mode 100644 index 00000000..48c7793c --- /dev/null +++ "b/Python/\346\241\206\346\236\266-yaml/yaml.py" @@ -0,0 +1,9 @@ +--------------------------------- +pyyaml | +--------------------------------- + # 安装 + pip install PyYAML + + + # 文档 + https://pyyaml.org/wiki/PyYAMLDocumentation \ No newline at end of file diff --git "a/RabbitMQ/RabbitMQ-\345\256\211\350\243\205.java" "b/RabbitMQ/RabbitMQ-\345\256\211\350\243\205.java" index c0208be8..ae1a03c7 100644 --- "a/RabbitMQ/RabbitMQ-\345\256\211\350\243\205.java" +++ "b/RabbitMQ/RabbitMQ-\345\256\211\350\243\205.java" @@ -79,21 +79,28 @@ 6,设置配置文件 cd /etc/rabbitmq //进入配置文件目录 cp /usr/share/doc/rabbitmq-server-3.4.1/rabbitmq.config.example /etc/rabbitmq/ //复制指定目录下的案例配置到当前目录 - mv rabbitmq.config.example rabbitmq.config //改名 + mv rabbitmq.config.example rabbitmq.config //改名复制到 /etc 目录下的配置文件 7,开启用户远程可访问 vim /etc/rabbitmq/rabbitmq.config {loopback_users, []} * 去掉该行前面的注释符号"%%",并且删除最后面的逗号',' - 8,开启WEB界面管理工具 + 8,开启WEB界面管理工具以及基本的维护 + rabbitmq-plugins enable rabbitmq_management - service rabbitmq-server restart + + systemctl start rabbitmq-server.service + systemctl stop rabbitmq-server.service + systemctl restart rabbitmq-server.service + 9,防火墙开启15672端口 /sbin/iptables -I INPUT -p tcp --dport 15672 -j ACCEPT /etc/rc.d/init.d/iptables save + firewall-cmd --add-port=15672/tcp --permanent + # 如果yum源无法安装erlang,则需要添加yum源支持 diff --git "a/Redis/Redis-CentOS\345\256\211\350\243\205.java" "b/Redis/Redis-CentOS\345\256\211\350\243\205.java" index f5838473..0c51da6e 100644 --- "a/Redis/Redis-CentOS\345\256\211\350\243\205.java" +++ "b/Redis/Redis-CentOS\345\256\211\350\243\205.java" @@ -1,3 +1,6 @@ +------------------------ +4版本的安装 | +------------------------ 1,官网下载 https://redis.io/ 2,解压 @@ -25,6 +28,36 @@ 7,编辑配置 vim /etc/init.d/redis +------------------------ +5.x版本的安装 | +------------------------ + # 安装必须依赖 + yum install -y gcc + + # 准备目录 + mkdir /usr/local/redis + mkdir /usr/local/redis/bin + mkdir /usr/local/redis/conf + + # 下载源码,解压 + https://redis.io/ + + # 进入目录执行 + make + + # 复制程序文件到目录 + cd ./redis-5.0.3/src + + cp ./mkreleasehdr.sh /usr/local/redis/bin + cp ./redis-benchmark /usr/local/redis/bin + cp ./redis-check-aof /usr/local/redis/bin + cp ./redis-check-rdb /usr/local/redis/bin + cp ./redis-cli /usr/local/redis/bin + cp ./redis-sentinel /usr/local/redis/bin + cp ./redis-server /usr/local/redis/bin + cp ./redis-trib.rb /usr/local/redis/bin + # 复制配置文件到目录 + cp ./redis-5.0.3/redis.conf /usr/local/redis/conf - \ No newline at end of file + diff --git "a/Redis/Redis-Geospatial\347\232\204\346\223\215\344\275\234.java" "b/Redis/Redis-Geospatial\347\232\204\346\223\215\344\275\234.java" new file mode 100644 index 00000000..870059d3 --- /dev/null +++ "b/Redis/Redis-Geospatial\347\232\204\346\223\215\344\275\234.java" @@ -0,0 +1,7 @@ +----------------------------------- +geospatial | +----------------------------------- + # 将指定的地理空间位置(纬度, 经度, 名称)添加到指定的key中 + * 这些数据将会存储到sorted set + * 可以使用 GEORADIUS 或者 GEORADIUSBYMEMBER命令对数据进行半径查询等操作 + diff --git a/Redis/Redis-Pipelining.java b/Redis/Redis-Pipelining.java new file mode 100644 index 00000000..3374da48 --- /dev/null +++ b/Redis/Redis-Pipelining.java @@ -0,0 +1,13 @@ +------------------------------ +pipelining | +------------------------------ + # 传统的请求 + * 客户端向服务端发送一个查询请求,,监听Socket返回,通常是以阻塞模式,等待服务端响应 + * 服务端处理命令,并将结果返回给客户端 + + # pipelining 请求 + * 以一次发送多个命令,并按顺序执行,最后才返回结果,节省RTT(Round Trip Time) + * 一次 请求/响应 服务器能实现处理新的请求,即使旧的请求还未被响应 + + # 使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存 + * 如果需要发送大量的命令,最好是把他们按照合理数量分批次的处理,例如10K的命令,读回复,然后再发送另一个10k的命令 diff --git "a/Redis/Redis-Stream\347\232\204\346\223\215\344\275\234.java" "b/Redis/Redis-Stream\347\232\204\346\223\215\344\275\234.java" new file mode 100644 index 00000000..be507386 --- /dev/null +++ "b/Redis/Redis-Stream\347\232\204\346\223\215\344\275\234.java" @@ -0,0 +1,4 @@ +------------------------ +Stream | +------------------------ + # redis5的新特性 \ No newline at end of file diff --git "a/Redis/Redis-key\347\232\204\346\223\215\344\275\234.java" "b/Redis/Redis-key\347\232\204\346\223\215\344\275\234.java" index f8a84f23..8ab08f4d 100644 --- "a/Redis/Redis-key\347\232\204\346\223\215\344\275\234.java" +++ "b/Redis/Redis-key\347\232\204\346\223\215\344\275\234.java" @@ -58,4 +58,49 @@ * SETNX key value; //同样,如果key已经存在那么不会执行写入操作,设置失败返回0,成功返回1 * MSETNX key value key value ... * 如果有一个key是存在的,那么批量的插入都不会执行 + +----------------------- +Bitmap | +----------------------- + setbit key offset value + getbit key offset + * 对于bit位的设置和读取操作 + * value只能是 0/1 ,可以用这东西是实现布隆过滤器算法 + * 如果offset过大,则会在中间填充0(最长2^32, 512 MB) + long bits = 1L << 32; // 4294967296 + long mb = bits / 8 / 1024 / 1024; + System.out.println(bits + " bit=" + mb + " mb"); // 4294967296bit=512mb + + + bitop [operations] [result] [key1] [keyn..] + * operations 表示,执行不同字符串之间的位操作 + * result 表示计算结果的存储的key + AND + OR + XOR + NOT(该操作只能接受一个key,因为它是取反操作) + * bitop OR result Monday Tuesday Wednesday Thursday Friday Saturday Sunday + + bitcount key [start] [end] + * 返回指定key被设置为 1 的位的数量 + * 可以指定开始和结束的位置 + + + bitpos [key] [value] + * 返回第一个0或者1的位置 + + + # Redis 客户端的操作 + RedisClient redisClient = RedisClient.create("redis://localhost:6379/0"); + StatefulRedisConnection connection = redisClient.connect(); + RedisCommands syncCommands = connection.sync(); + + syncCommands.setbit("users",1,1); + syncCommands.getbit("users",1); + + syncCommands.bitopAnd("result","key1","key2"); + syncCommands.bitopOr("result","key1","key2"); + syncCommands.bitopXor("result","key1","key2"); + syncCommands.bitopNot("result","key1"); + diff --git "a/Redis/Redis-\344\272\213\345\212\241&\350\256\242\351\230\205.java" "b/Redis/Redis-\344\272\213\345\212\241&\350\256\242\351\230\205.java" index c6e8dc25..5d1b0885 100644 --- "a/Redis/Redis-\344\272\213\345\212\241&\350\256\242\351\230\205.java" +++ "b/Redis/Redis-\344\272\213\345\212\241&\350\256\242\351\230\205.java" @@ -14,7 +14,7 @@ unwatch watch ------------------- -Redis-发布订阅 | +Redis-发布订阅 | ------------------- publish channle message # 把信息(message)发送给指定的频道(channle) @@ -32,4 +32,84 @@ # linux下启动redis客户端的时候,就订阅一个指定服务器频道 # 同上 - + + +------------------- +Redis-CAS更新 | +------------------- + # watch 可以监听一个key, 在该Key发生数据变化的时候, 自动回滚当前的事务 + * 可以通过当前事务是否回滚, 来判断CAS是否更新成功 + +import io.lettuce.core.RedisClient; +import io.lettuce.core.TransactionResult; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisCommands; + +import java.util.concurrent.CountDownLatch; + +public class CasUpdate { + + private static final RedisClient redisClient; + + public static void main(String[] args) throws Exception{ + + StatefulRedisConnection connection = redisClient.connect(); + RedisCommands redisCommands = connection.sync(); + redisCommands.hset("user", "name", "KevinBlandy"); + redisCommands.hset("user", "balance", "0"); + redisCommands.hset("user", "version", "0"); + + // 100个线程执行 + 1操作 + CountDownLatch countDownLatch = new CountDownLatch(100); + for(int x = 0 ; x < 100 ;x ++){ + new Thread(() -> { + try { + casUpdate(); + countDownLatch.countDown(); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } + countDownLatch.await(); + } + + public static void notSafeUpdate ()throws Exception{ + StatefulRedisConnection connection = redisClient.connect(); + RedisCommands redisCommands = connection.sync(); + } + + public static void casUpdate() throws Exception { + StatefulRedisConnection connection = redisClient.connect(); + RedisCommands redisCommands = connection.sync(); + + while (true){ + + String watchResult = redisCommands.watch("user"); + if (!watchResult.equals("OK")){ + throw new RuntimeException(); + } + String multiResult = redisCommands.multi(); + if (!multiResult.equals("OK")){ + throw new RuntimeException(); + } + + + // 对数据进行自增 +1 操作 + redisCommands.hincrby("user", "balance", 1); + redisCommands.hincrby("user", "version", 1); + + TransactionResult transactionResult = redisCommands.exec(); + System.out.println(transactionResult); + if (transactionResult.wasDiscarded()){ + // 更新失败, 继续自旋 + continue; + } + break; + } + } + + static { + redisClient = RedisClient.create("redis://localhost:6379/0"); + } +} diff --git "a/Redis/Redis-\345\205\245\351\227\250.java" "b/Redis/Redis-\345\205\245\351\227\250.java" index f28f2799..8390153d 100644 --- "a/Redis/Redis-\345\205\245\351\227\250.java" +++ "b/Redis/Redis-\345\205\245\351\227\250.java" @@ -14,6 +14,7 @@ Hash //哈希表 Set //无序集合 Sorted //有序集合 + Stream 可以持久化,保证了数据的安全 * 缓存 数据缓存 @@ -164,4 +165,135 @@ Sorted sets (Sorted sets of binary-safe strings) - \ No newline at end of file +----------------------- +过期key的删除策略 | +----------------------- + # 定期删除 + * redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除 + * 是随机抽取,而不是遍历所有.因为如果设置了过期的key过多的话,每100s就进行一次遍历会很伤性能 + + # 惰性删除 + * 定期删除策略,可能会导致部分设置了过期时间的key,在过期后没有及时的被删除 + * 惰性删除发生在,key可能早就过期了但是没删除,于是在你进行访问的时候,会对这个过期的key进行删除 + +----------------------- +内存淘汰机制 | +----------------------- + # 内存淘汰机制 + * 配置文件中的配置选项:maxmemory-policy + * 枚举值值 + volatile-lru + * 从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 + + volatile-ttl + * 从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 + + volatile-lfu + * 最近最不经常使用算法,从设置了过期时间的键中选择某段时间之内使用频次最小的键值对进行清除 + + volatile-random + * 从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 + + allkeys-lru + * 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的) + + allkeys-lfu + * 最近最不经常使用算法,从所有的键中选择某段时间之内使用频次最少的键值对进行清除 + + allkeys-random + * 从数据集(server.db[i].dict)中任意选择数据淘汰 + + noeviction + * 禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错,这个应该没人使用吧 + + # LFU(使用频率最低的) + * 为每个entry维护一个计数器, 每命中一次+1, 淘汰时找最小的 + + # LRU(最近最少使用的) + * 每次命中将entry移到队列头部, 淘汰时找队尾即可, 元素即最久没有使用过的元素 + + +----------------------- +持久化 | +----------------------- + # RDB 持久化 + * 默认开启 + * 配置选项:save + save 900 1 # 900秒内有1个key发生了改变,就发起一次快照 + save 300 10 # 300秒内有10个key发生了改变,就发起一次快照 + save 60 10000 # 60秒内有10000个key发生了改变,就发起一次快照 + + * 数据修改的频率高,备份的频率也高 + * 数据修改的频率低,备份的频率也低 + + * 如果数据非常多(10-20GB),就不适合频繁该持久化操作 + + * 快照持久化,持久化的是文件的名字和存储的位置 + + * Redis启动后,会读取RDB文件,把数据从硬盘写入内存.据吹牛逼说.1GB数据,读取到内存需要20-30秒.当然,不同的服务器肯定是有差异的 + * RDB的快照过程 + 1,Redis使用fork函数,复制一份当前的进程(父进程)的副本(子进程) + 2,父进程继续接收客户端发来的命令,而子进程开始把把内存中的数据写如到硬盘 + 3,当子进程写完所有数据库后,会用该临时文件替换旧的RDB文件 + + * RDB文件是通过压缩的(默认开启压缩),可以通过配置 rdbcompression 参数来禁止压缩 + rdbcompression yes/no + + * 压缩消耗性能,但是降低磁盘空间 + * 不压缩较占用磁盘空间 + + # AOF持久化 + * 本质:把用户执行的每个写指令(添加,修改,删除)保存到文件中,还原的时候,就是仅仅执行了指定的语句而已 + * Redis启动的时候,会执行AOF文件,达到数据恢复的效果 + * 开启AOF持久化 + * 会清空redis内部的数据,所以安装的时候就建议开启 + * 在redis.conf配置文件中 + appendonly no //改为yes即可 + + appendfilename appendonly.aof //持久化文件的名称 + + * 配置文件被修改,需要删除旧进程,在根据配置文件重启新的进程 + + * AOF追加持久化的备份频率,配置项:appendfsync + always + * 有一个写入指令我就备份一次 + * 性能最差,但是可以保证数据 + + everysec + * 每秒备份(记录操作命令)一次(推荐,也是默认) + * 性能和数据做了折中 + + no + * 服务器心情好,就给你备份.心情不好,就等着晚点做!(其实就是根据性能来) + * 性能最好,但是数据没保证 + + * 为AOF备份文件做优化处理 + redis-cli bgrewriteaof + * 这个命令其实就是把备份文件内容进优化压缩 + * 例如:多个incr指令变成了一个set指令 + + * 重写策略的参数设置 + auto-aof-rewrite-percentage 100 + * 当AOF文件大小超过上次重写的AOF文件大小的百分之多少时会再次进行重写 + * 如果之前没有重写过.则以启动时的AOF文件大小为依据 + + auto-aof-rewrite-min-size 64mb + * 限制了允许重写的最小的AOF文件大小 + * 通常在AOF文件很小的时候即使其中有些冗余的命令也是可以忽略的 + + * 假设用户对Redis设置了如上配置选项并且启用了AOF持久化 + * 那么当AOF文件体积大于64mb并且AOF的体积比上一次重写之后的体积大了至少一倍(100%)的时候,Redis将执行BGREWRITEAOF命令 + + # 混合持久化的支持 + aof-use-rdb-preambl yes + + * 如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头 + * 这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据 + * 当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差 + + + # 总结 + * RDB持久化跟AOF持久化是一个互补关系 + * RDB持久化做大的文件备份 + + * AOF做细致的文件备份 diff --git "a/Redis/Redis-\345\271\266\345\217\221\351\227\256\351\242\230.java" "b/Redis/Redis-\345\271\266\345\217\221\351\227\256\351\242\230.java" new file mode 100644 index 00000000..ab292df4 --- /dev/null +++ "b/Redis/Redis-\345\271\266\345\217\221\351\227\256\351\242\230.java" @@ -0,0 +1,7 @@ +---------------------------- +并发问题 | +---------------------------- + # 对于一个key的并发操作,如果是在分布式环境中可以使用zk之类的分布式锁 + * redis 本身也提供分布式锁 + + diff --git "a/Redis/Redis-\347\274\223\345\255\230\344\270\200\350\207\264\346\200\247.java" "b/Redis/Redis-\347\274\223\345\255\230\344\270\200\350\207\264\346\200\247.java" new file mode 100644 index 00000000..1384c367 --- /dev/null +++ "b/Redis/Redis-\347\274\223\345\255\230\344\270\200\350\207\264\346\200\247.java" @@ -0,0 +1,37 @@ +---------------------------- +缓存一致性 | +---------------------------- + +---------------------------- +场景 一 | +---------------------------- + # 先读DB,后删除缓存,缓存删除失败,导致数据不一致 + * 更新某商品的库存,当前商品的库存是100,现在要更新为99,先更新数据库更改成99,然后删除缓存,发现删除缓存失败了 + * 这意味着数据库存的是99,而缓存是100,这导致数据库和缓存不一致 + + # 解决 + * 先删除缓存,然后在更新数据库,如果删除缓存失败,那就不要更新数据库 + * 如果删除缓存成功而更新数据库失败,那查询的时候只是从数据库里查了旧的数据而已,这样就能保持数据库与缓存的一致性 + + +---------------------------- +场景 二 | +---------------------------- + # 并发高的情况下 + * A线程先删除了缓存,然后更新数据库对库存进行 -1 操作,还未来得及提交修改 + * B线程读取数据,发现缓存不存在,于是尝试读取DB,检索到结果是 100,写入到缓存 + * 此时A线程提交了修改,DB的数据是99,但缓存的数据是100,导致了数据的不一致性 + + # 解决方案 + * 可以用队列的去解决这个问,创建几个队列,如20个,根据商品的ID去做hash值,然后对队列个数取摸,得到队列的下标 + * 把读写请求都写入到队列里面去执行,串行化,这样就解决了数据不一致的问题,但是,性能又不好了 + + * 当有数据更新请求时,先把它丢到队列里去,当更新完后在从队列里去除,如果在更新的过程中,遇到以上场景,先去缓存里看下有没有数据 + + * 如果没有,可以先去队列里看是否有相同商品ID在做更新,如果有也把查询的请求发送到队列里去,然后同步等待缓存更新完成 + + * 这里有一个优化点,如果发现队列里有一个查询请求了,那么就不要放新的查询操作进去了,用一个 while(true) 循环去查询缓存 + * 循环个200MS左右,如果缓存里还没有则直接取数据库的旧数据,一般情况下是可以取到的 + + + # 整复杂了不是 ? 我觉得redis 存在一些脏数据没啥,只要保证DB业务数据的一致性就好 \ No newline at end of file diff --git "a/Redis/Redis-\351\205\215\347\275\256\346\226\207\344\273\266.java" "b/Redis/Redis-\351\205\215\347\275\256\346\226\207\344\273\266.java" index 33814df3..b8a8591f 100644 --- "a/Redis/Redis-\351\205\215\347\275\256\346\226\207\344\273\266.java" +++ "b/Redis/Redis-\351\205\215\347\275\256\346\226\207\344\273\266.java" @@ -1,16 +1,94 @@ -------------------- -配置文件详解 | +閰嶇疆鏂囦欢璇﹁В | -------------------- * redis.conf - * 复制到Server目录 + * 澶嶅埗鍒癝erver鐩綍 + * 鍙互鍙傝 + http://download.redis.io/redis-stable/redis.conf daemonize no - * Redis服务是否后台启动,默认值为:no也就是后台启动. - * 修改为:yes 那么Redis会以后台启动的方式启动服务 + * Redis鏈嶅姟鏄惁鍚庡彴鍚姩,榛樿鍊间负:no涔熷氨鏄悗鍙板惎鍔. + * 淇敼涓:yes 閭d箞Redis浼氫互鍚庡彴鍚姩鐨勬柟寮忓惎鍔ㄦ湇鍔 + port 6379 - * 指定Redis监听的端口.默认为:6379 + * 鎸囧畾Redis鐩戝惉鐨勭鍙.榛樿涓:6379 + pidfile /var/run/redis.pid - * 当Redis在后台的运行的时候,默认会把pid文件放在:/var/run/redis.pid - * 可以配置为其他的地址,当运行多个redis服务的时候,需要指定不同管道pid,文件,和端口 - \ No newline at end of file + * 褰揜edis鍦ㄥ悗鍙扮殑杩愯鐨勬椂鍊,榛樿浼氭妸pid鏂囦欢鏀惧湪:/var/run/redis.pid + * 鍙互閰嶇疆涓哄叾浠栫殑鍦板潃,褰撹繍琛屽涓猺edis鏈嶅姟鐨勬椂鍊,闇瑕佹寚瀹氫笉鍚岀閬損id,鏂囦欢,鍜岀鍙 + + notify-keyspace-events + * 閰嶇疆浜嬩欢鐩戝惉 + * notify-keyspace-events="Ex" # 鐩戝惉key鐨勮繃鏈熶簨浠 + K 閿┖闂撮氱煡锛屾墍鏈夐氱煡浠ヂ犱互__keyspace@__涓哄墠缂 锛岄拡瀵筀ey + E 閿簨浠堕氱煡锛屾墍鏈夐氱煡浠ヂ犱互__keysevent@__涓哄墠缂锛岄拡瀵筫vent + g DEL 銆 EXPIRE 銆 RENAME 绛夌被鍨嬫棤鍏崇殑閫氱敤鍛戒护鐨勯氱煡 + $ 瀛楃涓插懡浠ょ殑閫氱煡 + l 鍒楄〃鍛戒护鐨勯氱煡 + s 闆嗗悎鍛戒护鐨勯氱煡 + h 鍝堝笇鍛戒护鐨勯氱煡 + z 鏈夊簭闆嗗悎鍛戒护鐨勯氱煡 + x 杩囨湡浜:姣忓綋鏈夎繃鏈熼敭琚垹闄ゆ椂鍙戦 + e 椹遍(evict)浜嬩欢锛氭瘡褰撴湁閿洜涓 maxmemory 鏀跨瓥鑰岃鍒犻櫎鏃跺彂閫 + A 鍙傛暟 g$lshzxe 鐨勫埆鍚嶏紝鐩稿綋浜庢槸All + bind + * 鎸囧畾 Redis 鍙帴鏀舵潵鑷簬璇 IP 鍦板潃鐨勮姹傦紝濡傛灉涓嶈繘琛岃缃紝閭d箞灏嗗鐞嗘墍鏈夎姹 + * 鍦ㄧ敓浜х幆澧冧腑鏈濂借缃椤 + + requirepass + * 璁剧疆杩炴帴瀵嗙爜 + + maxmemory + * 璁剧疆鏈澶у厑璁镐娇鐢ㄧ殑鍐呭瓨 + + maxmemory-policy + * 璁剧疆鍐呭瓨娣樻卑绛栫暐 + + save [time] [keys] + * RDB鎸佷箙鍖栬缃 + * time 琛ㄧず绉, keys 琛ㄧず鏃堕棿鍐呭彂鐢熸敼鍙樼殑key鏁伴噺 + * 濡傛灉鍦╰ime绉掑唴,鏈塳eys涓猭ey鍙戠敓浜嗕慨鏀,灏卞彂璧蜂竴娆″揩鐓 + * 榛樿鐨勯厤缃 + save 900 1 # 900绉掑唴鏈1涓猭ey鍙戠敓浜嗘敼鍙,灏卞彂璧蜂竴娆″揩鐓 + save 300 10 + save 60 10000 + * 濡傛灉闇瑕佸叧闂,鍙互娉ㄩ噴 + + rdbcompression + * 鏄惁鍘嬬缉RBD鎸佷箙鍖栧埌纭洏鐨勬暟鎹,榛樿:yes + * yes/no + + appendonly + * 鏄惁寮鍚疉OF鎸佷箙鍖,榛樿鏈紑鍚 + * yes/no + + appendfilename + * 璁剧疆AOF鎸佷箙鍖栨棩蹇楃殑鏂囦欢鍚嶇О + + appendfsync + * 璁剧疆AOF鎸佷箙鍖栫殑棰戠巼,鏋氫妇鍊 + always + * 鏈変竴涓啓鍏ユ寚浠ゆ垜灏卞浠戒竴娆 + * 鎬ц兘鏈宸,浣嗘槸鍙互淇濊瘉鏁版嵁 + + everysec + * 姣忕澶囦唤(璁板綍鎿嶄綔鍛戒护)涓娆(鎺ㄨ崘,涔熸槸榛樿) + * 鎬ц兘鍜屾暟鎹仛浜嗘姌涓 + no + * 鏈嶅姟鍣ㄥ績鎯呭ソ,灏辩粰浣犲浠.蹇冩儏涓嶅ソ,灏辩瓑鐫鏅氱偣鍋!(鍏跺疄灏辨槸鏍规嵁鎬ц兘鏉) + * 鎬ц兘鏈濂,浣嗘槸鏁版嵁娌′繚璇 + + auto-aof-rewrite-percentage 100 + * 褰揂OF鏂囦欢澶у皬瓒呰繃涓婃閲嶅啓鐨凙OF鏂囦欢澶у皬鐨勭櫨鍒嗕箣澶氬皯鏃朵細鍐嶆杩涜閲嶅啓 + * 濡傛灉涔嬪墠娌℃湁閲嶅啓杩.鍒欎互鍚姩鏃剁殑AOF鏂囦欢澶у皬涓轰緷鎹 + + auto-aof-rewrite-min-size 64mb + * 闄愬埗浜嗗厑璁搁噸鍐欑殑鏈灏忕殑AOF鏂囦欢澶у皬 + * 閫氬父鍦ˋOF鏂囦欢寰堝皬鐨勬椂鍊欏嵆浣垮叾涓湁浜涘啑浣欑殑鍛戒护涔熸槸鍙互蹇界暐鐨 + + aof-use-rdb-preambl yes + * 鏄惁寮鍚贩鍚堟寔涔呭寲 + * 濡傛灉鎶婃贩鍚堟寔涔呭寲鎵撳紑,AOF 閲嶅啓鐨勬椂鍊欏氨鐩存帴鎶 RDB 鐨勫唴瀹瑰啓鍒 AOF 鏂囦欢寮澶 + * 杩欐牱鍋氱殑濂藉鏄彲浠ョ粨鍚 RDB 鍜 AOF 鐨勪紭鐐, 蹇熷姞杞藉悓鏃堕伩鍏嶄涪澶辫繃澶氱殑鏁版嵁 + * 褰撶劧缂虹偣涔熸槸鏈夌殑, AOF 閲岄潰鐨 RDB 閮ㄥ垎鏄帇缂╂牸寮忎笉鍐嶆槸 AOF 鏍煎紡,鍙鎬ц緝宸 \ No newline at end of file diff --git "a/Redis/Redis-\351\233\252\345\264\251\344\270\216\347\251\277\351\200\217.java" "b/Redis/Redis-\351\233\252\345\264\251\344\270\216\347\251\277\351\200\217.java" new file mode 100644 index 00000000..840b7369 --- /dev/null +++ "b/Redis/Redis-\351\233\252\345\264\251\344\270\216\347\251\277\351\200\217.java" @@ -0,0 +1,34 @@ +------------------------ +缓存穿透 | +------------------------ + # 恶意的请求缓存和DB都不存在的数据,因为数据不存在,所以会一直请求DB,如果请求量大可能导致DB宕机 + + # 简单的解决办法 + * DB没检索到的数据,同样缓存起来(不管是数据不存在,还是系统故障) + * 缓存的时间不会长,例如:5 分钟 + + # 采用布隆过滤器 + * 将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉 + * 从而避免了对底层存储系统的查询压力 + +------------------------ +缓存雪崩 | +------------------------ + # 缓存挂掉,导致全部请求到了DB + + # 解决 + 事前 redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。 + 事中 本地 ehcache 缓存 + hystrix 限流&降级,避免 DB 宕机 + 事后 redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据 + + + # 可以使用多级缓存,例如 Ehcache + * 先查本地 ehcache 缓存,如果没查到再查 redis,如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中 + + + # 使用限流组件,例如,类似于令牌桶算法 + * 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过 + * 只要数据库不,就是说,对用户来说,2/5 的请求都是可以被处理的 + * 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次 + + diff --git "a/Redisson/redisson-\345\205\245\351\227\250.java" "b/Redisson/redisson-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..158fb2ab --- /dev/null +++ "b/Redisson/redisson-\345\205\245\351\227\250.java" @@ -0,0 +1,19 @@ +-------------------------------- +redisson | +-------------------------------- + # 中文文档 + https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95 + # Maven + + + org.redisson + redisson + 3.9.1 + + + + + org.redisson + redisson + 2.14.1 + \ No newline at end of file diff --git "a/Redisson/redisson-\345\210\206\345\270\203\345\274\217\351\224\201.java" "b/Redisson/redisson-\345\210\206\345\270\203\345\274\217\351\224\201.java" new file mode 100644 index 00000000..a866bdcb --- /dev/null +++ "b/Redisson/redisson-\345\210\206\345\270\203\345\274\217\351\224\201.java" @@ -0,0 +1,114 @@ + +------------------------ +redisson 分布式锁 | +------------------------ + # 中文文档 + https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8 + + # 看门狗机制 + * 如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态 + * 为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期 + * 默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改 Config.lockWatchdogTimeout 来另行指定 + +------------------------ +可重入锁(Reentrant Lock)| +------------------------ + + # 同步锁的创建 + RLock rLock = redissonClient.getLock("lock"); + + # 同步操作 + //加锁,需要手动unlock + void lock(); + + //加锁,10s后自动解锁 + void lock(10, TimeUnit.SECONDS); + + //尝试加锁,如果成功返回true + boolean rLock.tryLock(); + + //尝试加锁,最多等待100s,如果成功上锁(返回true),则10s后自动解锁 + boolean tryLock(100, 10, TimeUnit.SECONDS); + + //手动释放锁 + void unlock(); + + # 异步操作 + Future lockAsync(); + Future lockAsync(10, TimeUnit.SECONDS); + Future rLock.tryLockAsync(); + Future tryLockAsync(100, TimeUnit.SECONDS); + Future rLock.unlockAsync(); + + # 其他 + //判断是否可以获取锁 + boolean rLock.isLocked(); + +------------------------ +公平锁(Fair Lock) | +------------------------ + # 跟可重入锁一样 + * 不同的是,它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程 + + # 创建锁 + RLock rLock = redisson.getFairLock("lock"); + + # 其他几乎同上 + +------------------------ +联锁(MultiLock) | +------------------------ + # 可以将多个RLock对象关联为一个联锁 + * 每个RLock对象实例可以来自于不同的 redissonClient 实例 + + # 创建锁 + RLock lock1 = redissonClient.getLock("lock1"); + RLock lock2 = redissonClient.getLock("lock2"); + RLock lock3 = redissonClient.getLock("lock3"); + + RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3); + // 同时加锁:lock1 lock2 lock3 + // 所有的锁都上锁成功才算成功。 + lock.lock(); + lock.unlock(); + + # 其他几乎同上 + +------------------------ +红锁(RedLock) | +------------------------ + # RedissonRedLock对象实现了Redlock介绍的加锁算法,该对象也可以用来将多个RLock对象关联为一个红锁 + * 每个RLock对象实例可以来自于不同的 redissonClient 实例 + + # 创建锁 + RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3); + // 同时加锁:lock1 lock2 lock3 + // 红锁在大部分节点上加锁成功就算成功 + lock.lock(); + lock.unlock(); + +------------------------ +读写锁(ReadWriteLoc) | +------------------------ + # RReadWriteLock 对象实现了 java.util.concurrent.locks.ReadWriteLock 接口 + * 同时还支持自动过期解锁 + * 该对象允许同时有多个读取锁,但是最多只能有一个写入锁 + + # 创建锁 + RReadWriteLock rReadWriteLock = redissonClient.getReadWriteLock("lock"); + // 获取读锁并上锁 + rReadWriteLock.readLock().lock(); + // 获取写锁并上锁 + rReadWriteLock.writeLock().lock();; + +------------------------ +信号量(Semaphore) | +------------------------ + +------------------------------------------------ +可过期性信号量(PermitExpirableSemaphore) | +------------------------------------------------ + +------------------------ +闭锁(CountDownLatch) | +------------------------ diff --git a/RocketMQ/api/producer-DefaultMQProducer.java b/RocketMQ/api/producer-DefaultMQProducer.java new file mode 100644 index 00000000..a289286a --- /dev/null +++ b/RocketMQ/api/producer-DefaultMQProducer.java @@ -0,0 +1,13 @@ + +# DefaultMQProducer + * MQProducer 接口的实现 + * 默认的消息生产者 + +# 构造函数 + DefaultMQProducer() + DefaultMQProducer(final String producerGroup) + DefaultMQProducer(final String producerGroup, boolean enableMsgTrace) + DefaultMQProducer(final String producerGroup, boolean enableMsgTrace, final String customizedTraceTopic) + DefaultMQProducer(final String producerGroup, RPCHook rpcHook) + DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,final String customizedTraceTopic) + DefaultMQProducer(RPCHook rpcHook) diff --git a/RocketMQ/rocketmq-client-producer.java b/RocketMQ/rocketmq-client-producer.java new file mode 100644 index 00000000..f7b5fee1 --- /dev/null +++ b/RocketMQ/rocketmq-client-producer.java @@ -0,0 +1,2 @@ +# 消息生产者 + diff --git a/RocketMQ/rocketmq-config-broker.conf.java b/RocketMQ/rocketmq-config-broker.conf.java new file mode 100644 index 00000000..a8f04e2e --- /dev/null +++ b/RocketMQ/rocketmq-config-broker.conf.java @@ -0,0 +1,7 @@ +brokerClusterName = DefaultCluster +brokerName = broker-a +brokerId = 0 +deleteWhen = 04 +fileReservedTime = 48 +brokerRole = ASYNC_MASTER +flushDiskType = ASYNC_FLUSH diff --git "a/RocketMQ/rocketmq-\345\256\211\350\243\205.java" "b/RocketMQ/rocketmq-\345\256\211\350\243\205.java" new file mode 100644 index 00000000..7bb5a346 --- /dev/null +++ "b/RocketMQ/rocketmq-\345\256\211\350\243\205.java" @@ -0,0 +1,23 @@ + +# 单机安装 + # 下载,解压 + # 配置环境变量 + export ROCKETMQ_HOME=/usr/local/rocketmq/alibaba-rocketmq + + # 启动nameserver + bin/mqnamesrv.sh + + # 启动broker + bin/mqbroker.sh + + -n + * 参数指定绑定的网卡和端口 + -n 0.0.0.0:9876 + + + mqbroker.cmd -n localhost:9876 autoCreateTopicEnable=true + + + # 启动异常修改脚本:runbroker.cmd + + set CLASSPATH=.;%BASE_DIR%conf;"%CLASSPATH%" \ No newline at end of file diff --git a/RocketMQ/rocketmq.java b/RocketMQ/rocketmq.java new file mode 100644 index 00000000..b7f8a5da --- /dev/null +++ b/RocketMQ/rocketmq.java @@ -0,0 +1,7 @@ + +# RocketMQ + * 官方网址 + https://rocketmq.apache.org/ + https://github.com/apache/rocketmq/ + + https://help.aliyun.com/document_detail/29532.html \ No newline at end of file diff --git a/RocketMQ/RocketMQ-API.java "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-API.java" similarity index 100% rename from RocketMQ/RocketMQ-API.java rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-API.java" diff --git a/RocketMQ/RocketMQ-Filter.java "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-Filter.java" similarity index 100% rename from RocketMQ/RocketMQ-Filter.java rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-Filter.java" diff --git "a/RocketMQ/RocketMQ-\344\272\213\345\212\241\346\266\210\346\201\257.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\344\272\213\345\212\241\346\266\210\346\201\257.java" similarity index 100% rename from "RocketMQ/RocketMQ-\344\272\213\345\212\241\346\266\210\346\201\257.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\344\272\213\345\212\241\346\266\210\346\201\257.java" diff --git "a/RocketMQ/RocketMQ-\344\275\223\347\263\273\347\273\223\346\236\204.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\344\275\223\347\263\273\347\273\223\346\236\204.java" similarity index 100% rename from "RocketMQ/RocketMQ-\344\275\223\347\263\273\347\273\223\346\236\204.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\344\275\223\347\263\273\347\273\223\346\236\204.java" diff --git "a/RocketMQ/RocketMQ-\345\205\245\351\227\250.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\345\205\245\351\227\250.java" similarity index 100% rename from "RocketMQ/RocketMQ-\345\205\245\351\227\250.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\345\205\245\351\227\250.java" diff --git "a/RocketMQ/RocketMQ-\345\256\236\346\210\230\347\273\217\351\252\214.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\345\256\236\346\210\230\347\273\217\351\252\214.java" similarity index 100% rename from "RocketMQ/RocketMQ-\345\256\236\346\210\230\347\273\217\351\252\214.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\345\256\236\346\210\230\347\273\217\351\252\214.java" diff --git "a/RocketMQ/RocketMQ-\345\274\202\345\270\270.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\345\274\202\345\270\270.java" similarity index 100% rename from "RocketMQ/RocketMQ-\345\274\202\345\270\270.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\345\274\202\345\270\270.java" diff --git "a/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\346\231\256\351\200\232\346\266\210\346\201\257java.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\346\231\256\351\200\232\346\266\210\346\201\257java.java" new file mode 100644 index 00000000..e69de29b diff --git "a/RocketMQ/RocketMQ-\346\263\250\346\204\217.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\346\263\250\346\204\217.java" similarity index 100% rename from "RocketMQ/RocketMQ-\346\263\250\346\204\217.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\346\263\250\346\204\217.java" diff --git "a/RocketMQ/RocketMQ-\346\266\210\346\201\257\351\207\215\350\257\225\346\234\272\345\210\266.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\346\266\210\346\201\257\351\207\215\350\257\225\346\234\272\345\210\266.java" similarity index 100% rename from "RocketMQ/RocketMQ-\346\266\210\346\201\257\351\207\215\350\257\225\346\234\272\345\210\266.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\346\266\210\346\201\257\351\207\215\350\257\225\346\234\272\345\210\266.java" diff --git "a/RocketMQ/RocketMQ-\346\266\210\350\264\271\346\226\271\345\274\217.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\346\266\210\350\264\271\346\226\271\345\274\217.java" similarity index 100% rename from "RocketMQ/RocketMQ-\346\266\210\350\264\271\346\226\271\345\274\217.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\346\266\210\350\264\271\346\226\271\345\274\217.java" diff --git "a/RocketMQ/RocketMQ-\346\266\210\350\264\271\350\200\205\346\235\203\351\207\215.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\346\266\210\350\264\271\350\200\205\346\235\203\351\207\215.java" similarity index 100% rename from "RocketMQ/RocketMQ-\346\266\210\350\264\271\350\200\205\346\235\203\351\207\215.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\346\266\210\350\264\271\350\200\205\346\235\203\351\207\215.java" diff --git "a/RocketMQ/RocketMQ-\347\216\257\345\242\203\346\220\255\345\273\272.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\347\216\257\345\242\203\346\220\255\345\273\272.java" similarity index 100% rename from "RocketMQ/RocketMQ-\347\216\257\345\242\203\346\220\255\345\273\272.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\347\216\257\345\242\203\346\220\255\345\273\272.java" diff --git "a/RocketMQ/RocketMQ-\347\233\221\346\216\247.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\347\233\221\346\216\247.java" similarity index 100% rename from "RocketMQ/RocketMQ-\347\233\221\346\216\247.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\347\233\221\346\216\247.java" diff --git "a/RocketMQ/RocketMQ-\351\205\215\347\275\256\345\217\202\346\225\260.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\351\205\215\347\275\256\345\217\202\346\225\260.java" similarity index 100% rename from "RocketMQ/RocketMQ-\351\205\215\347\275\256\345\217\202\346\225\260.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\351\205\215\347\275\256\345\217\202\346\225\260.java" diff --git "a/RocketMQ/RocketMQ-\351\241\272\345\272\217\346\266\210\346\201\257.java" "b/RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\351\241\272\345\272\217\346\266\210\346\201\257.java" similarity index 100% rename from "RocketMQ/RocketMQ-\351\241\272\345\272\217\346\266\210\346\201\257.java" rename to "RocketMQ/\346\227\247\347\254\224\350\256\260/RocketMQ-\351\241\272\345\272\217\346\266\210\346\201\257.java" diff --git "a/Rtmp/rtmp-Nginx\344\275\234\344\270\272\346\265\201\346\234\215\345\212\241\345\231\250.java" "b/Rtmp/rtmp-Nginx\344\275\234\344\270\272\346\265\201\346\234\215\345\212\241\345\231\250.java" new file mode 100644 index 00000000..2deeb3ea --- /dev/null +++ "b/Rtmp/rtmp-Nginx\344\275\234\344\270\272\346\265\201\346\234\215\345\212\241\345\231\250.java" @@ -0,0 +1,34 @@ +----------------------- +编译安装rtmp模块 | +----------------------- + # 下载stmp模块 + https://github.com/arut/nginx-rtmp-module + + # 下载Nginx源码 + ..略 + + # 编译 +./configure --prefix=/usr/local/nginx \ +--add-module=/opt/nginx-rtmp-module \ +--with-http_stub_status_module \ +--with-http_ssl_module \ +--with-file-aio \ +--with-http_realip_module \ +--with-http_v2_module \ +--with-stream \ +--with-stream_ssl_module \ +--with-http_slice_module \ +--with-mail \ +--with-mail_ssl_module + + make && make install + + * add-module 指向的是从git clone下来的目录 + + # 查看Nginx编译安装的模块 + nginx -V + +----------------------- +配置详解 | +---------------------- + \ No newline at end of file diff --git "a/Rtmp/rtmp-\345\205\245\351\227\250.java" "b/Rtmp/rtmp-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..5046d2d7 --- /dev/null +++ "b/Rtmp/rtmp-\345\205\245\351\227\250.java" @@ -0,0 +1,26 @@ +---------------------------- +协议 | +---------------------------- + rtmp + rtsp + + +---------------------------- +rtmp | +---------------------------- + # 流媒体服务框架 - 两端加一服 + *推流端 + * 拉流端 + * 媒体服务器 + + # 推流协议 + * RTMP + + # 拉流协议 + * RTMP + * HLS + + + + # 参考资料 + https://www.cnblogs.com/cnsanshao/p/6370938.html \ No newline at end of file diff --git "a/SQL\347\274\226\347\250\213/sql-\345\207\275\346\225\260.java" "b/SQL\347\274\226\347\250\213/sql-\345\207\275\346\225\260.java" new file mode 100644 index 00000000..d6d376a7 --- /dev/null +++ "b/SQL\347\274\226\347\250\213/sql-\345\207\275\346\225\260.java" @@ -0,0 +1,97 @@ + +--------------------- +函数定义 | +--------------------- + + DELIMITER $$ + CREATE FUNCTION [函数名]([形参名称] [形参类型]) RETURNS [返回值类型] CHARACTER SET utf8mb4 + [函数修饰符] + BEGIN + RETURN [结果] + END + $$ + + + + # 如果一个函数,处理语句有多个SQL语句组成 + > 1,将语句组成语句块(begin - end 来标识) + > 2,语句块中语句需要独立的结束符 ";" + 针对于命令行,由于触发器使用分号作为语句结束符 + 那么当命令行客户端碰到分号的时候,就应该变成触发程序内的子语句结束,而不是整个语句的结束 + 可以通过修改命令行的语句结束符达到目的 + delimter $$ 跟SQL没关系,就是CMD窗口的命令.就让CMD以$$ 为结束符,那么命令语句中的多个分号就可以写了! + 操作完了记得该回去啊 + dlimter ; +--------------------- +系统函数 | +--------------------- + ifnull(列名,替代值); + * selct ifnull(phone,'未知');,如果phone字段为空,那么设置为未知 + if(condition,trueValue,falseValue); + * 根据判断返回值 + + /** 统计相关 **/ + rand() + * 返回一个0-1之间的随机数 + floor() + * 向下取整,就是直接干掉小数点.留下整数 + ceil() + * 向上取整 + count(); + * 返回某个字段的所有有效行数,不包含null + * 里面可以使用两种参数 + 1,* :代表统计记录 + 2,字段 :代表字段,如果是null,则不参与统计 + * 里面放入数值.其实跟*差不多!意义完全相同 + max(字段); + * 统计分组后中,最大值 + min(字段); + * 最小值 + avg(字段); + * 最小值 + sum(字段); + * 某个字段值的和 + * null,一律当作0来进行计算 + * 如果varchar是数字的字符串,那么会参与运算.如果是非数字的字符串.那么当作0来处理 + + /** 时间相关 **/ + curdate(); + * 获取当前日期,格式:2016-06-12 + now(); + * 获取当前时间,格式:2016-06-12 22:53:30 + date_format(date,fmt) + * 对日期进行格式化 + + /** 字符串相关 **/ + concat('1','2','3'); + * 字符串拼接 + * select concat(name,job) from 表名;[把两个字段的字符串拼接后查询出来] + * select ..from ..where name like concat('%',concat('kevin','%')); //mybatis模糊查询 + substring(str,x,y); + * 字符串截取.从指定字符串的指定下标开始,取多少长度 + * '注意:'该函数的下标是从1开始.('因为0代表 false,在这个里面是不能用的') + char_length(str); + * 获取的指定字符串的长度,'字符长度' + length(str1,str2); + * 获取的指定字符串才长度,'字节长度' + * 字符集会影响到结果. + instr(str); + * 判断字符串(str2)是否在某个字符串(str1)中存在 + * 如果存在,则返回其存在的位置(下标从1开始) + * 如果不存在,则返回0 + lpad(str,len,in); + * '左填充',把字符串按照指定的填充方式,填充到指定的长度 + * 就是,字符串太短了.你要延伸到几位,延伸出来的用啥代替 + insert(str,start,len,newStr); + * 替换,找到'目标位置''指定长度'的字符串,替换为'目标字符串' + strcmp(stra,strb); + * 比较俩字符串大小..根据字典排序咯.A > B + * 如果a > b 返回 1,反之返回 -1 相等返回 0 + + //加密相关 + md5("被加密的字符"); + * MD5加密函数,不可逆.平时用的 + password("被加密的字符"); + * 专门供MYSQL用的 + sha1("被加密的字符"); + * 这个也是一个加密函数,可以用于项目 \ No newline at end of file diff --git "a/SQL\347\274\226\347\250\213/sql-\345\217\230\351\207\217.java" "b/SQL\347\274\226\347\250\213/sql-\345\217\230\351\207\217.java" new file mode 100644 index 00000000..60c5c1aa --- /dev/null +++ "b/SQL\347\274\226\347\250\213/sql-\345\217\230\351\207\217.java" @@ -0,0 +1,80 @@ +--------------------------- +变量 | +--------------------------- + # 系统变量 + * 是系统定义好的变量,我们只需要去使用即可,大部分的时候,我们根本不用使用到 + * 是控制服务器的表现:autocommit.....等等 + * 查看系统变量:show variables like '变量名'; + * 修改系统变量:set [变量名] = [值]; + * 查看具体变量值还有一种方式 + SELECT @@变量名 + select @@version,@@xxx,@xxx...; + * 系统变量前面是加俩@符号 + + # 修改系统变量 + 1,会话级别的修改,该次修改仅仅对当前线程/会话有效 + set [变量名] = [值]; + set @@变量名 = [值]; + 2,全局级别的修改,对所有的线程/会话都有效,一次修改永久生效 + set global @@变量名 = [值]; + * 这种修改方式对于当前在线的其他用户无效,其他用户得重新登录才有效 + + + # 自定义变量 + * MYSQL中为了区分系统变量,规定用户自定义变量必须使用一个 @符号; + * set @name = 'KevinBlandy'; //定义变量 + * select @name; //获取变量 + + * 在MYSQL中的 '=',会默认的当作比较符号来进行处理,MYSQL为了区分比较和赋值,所以专门定义了一个赋值符号: := + * 颜文字,呵呵:冒号等于 + set @name := 'KevinBlandy'; + select @name; + + * MYSQL允许从数据表中获取数据,赋值给变量 + 1,select + SELECT [变量名] := [值]; + SELECT @name := '44'; + select [变量名] := (子查询); //该返回值是单行单列 + select @name := (select name from user where id = 1); + select [变量名] := [列名] from [表名]; + select @name := name from user ; //注意了,这个操作是把一列数据都给了该变量,其实这个变量一直在改变,最终值就是最后一个值 + * 得使用:=,如果直接使用等号会变成比较符号 + + 2,into + * 这种赋值会比较严格,要求赋值的结果仅仅只能是单行,不能是'多行',也就是仅仅一条记录,MYSQL没有数组这个概念 + set [变量名] := (子查询); //要求返回单行单列 + set @name := (select name from user where id = 1); + select [字段1],[字段2]... from [表名] where [条件] into @变量1,@变量2...; //可以返回多列,但是只能是一行.而且有多少个字段就必须要有多少个变量 + select name,age from user where id = 1 into @name,@age; + * 注意的是结果 + + * 所有自定义的变量都是会话级别,当前连接/用户有效.退了就没了; + * 这种变量也可以理解为全局变量,如果不加@,那么就是局部变量,在函数中会有该情况 + +--------------------------- +数据类型转换 | +--------------------------- + # 数据类型 + CHAR[(N)] 字符型 + DATE 日期型 + DATETIME 日期和时间型 + DECIMAL float型 + SIGNED int + UNSIGNED int + TIME 时间型 + + # 类型转换 + cast() + SELECT CAST('151515' AS SIGNED) + SELECT CAST(51515 AS CHAR(10)) + + convert() + SELECT CONVERT(12,CHAR(10)) + + +--------------------------- +系统预定义常量 | +--------------------------- + CURRENT_TIMESTAMP + * 当前时间戳 + \ No newline at end of file diff --git "a/SQL\347\274\226\347\250\213/sql-\350\257\255\345\217\245.java" "b/SQL\347\274\226\347\250\213/sql-\350\257\255\345\217\245.java" new file mode 100644 index 00000000..9f3efb2d --- /dev/null +++ "b/SQL\347\274\226\347\250\213/sql-\350\257\255\345\217\245.java" @@ -0,0 +1,27 @@ +------------------------ +if 语句 | +------------------------ + IF [表达式] THEN + [操作] + ELSEIF [表达式] THEN + [操作] + ELSE + [操作] + END IF + + # 可以由 =、<、<=、>、>=、!= 等条件运算符组成,并且可以使用AND、OR、NOT对多个表达式进行组合 + +------------------------ +case 语句 | +------------------------ + case [变量] + when [值] then [结果] + when [值] then [结果] + else [结果] + end + + case + when [条件] then [结果] + when [条件] then [结果] + else [结果] + end diff --git a/SSL/ssl-openssl.java b/SSL/ssl-openssl.java new file mode 100644 index 00000000..1407ccae --- /dev/null +++ b/SSL/ssl-openssl.java @@ -0,0 +1,43 @@ +----------------------------- +openssl | +----------------------------- + # 官网 + https://www.openssl.org + + + + # 把pem证书转换为 p12证书 + openssl pkcs12 -export -in [name.cer] -inkey [name.key] -out [name.p12] + -in + * 证书 + + -inkey + * 私钥 + + -out + * 生成的p12证书文件 + + # 把 pkcs12 证书转换为 pem证书 + * 提取证书文件 + openssl pkcs12 -in [name.p12] -out [name.pem] -nokeys -clcerts + + -in + * p12密钥库文件 + -out + * 输出的证书文件 + -nokeys + * 不输出密钥 + -clcerts + + * 提取私钥文件 + openssl pkcs12 -in [name.p12] -out [name.key] -nocerts -nodes + + -in + * p12密钥库文件 + -out + * 输出的密钥文件 + -nocerts + * 不输出证书 + -nodes + * 不对私钥文件进行加密 + diff --git a/SSL/ssl.java b/SSL/ssl.java new file mode 100644 index 00000000..7ebb9df2 --- /dev/null +++ b/SSL/ssl.java @@ -0,0 +1,222 @@ +----------------------------- +java ssl 证书类型 | +----------------------------- + # 证书文件类型 + .der .cer + * 文件是二进制格式,只保存证书,不保存私钥 + + .pem + * 一般是文本格式,可保存证书,可保存私钥 + * 以 -----BEGIN... 开头,以 -----END... 结尾,中间的内容是 BASE64 编码 + * 这种格式可以保存证书和私钥,有时也把PEM 格式的私钥的后缀改为 .key 以区别证书与私钥 + + .crt + * 有可能是 pem 编码格式,也有可能是 der 编码格式 + + .pfx .P12 + * 二进制格式,同时包含证书和私钥,一般有密码保护 + * pfx是浏览器用的 + + .jks + * 二进制格式,同时包含证书和私钥,一般有密码保护 + * JAVA 的专属格式(Java Key Storage) + + + # 包含了私钥的证书文件格式 + JKS + * Java支持的证书私钥格式 + * java用的存储密钥的容器,可以同时容纳n个公钥或私钥,后缀一般是.jks或者.keystore或.truststore等 + JCEKS + PKCS12 + BKS + UBER + PKCS12 + * 定义了包含私钥与公钥证书(public key certificate)的文件格式,行业标准 + * pfx 就实现了了PKCS#12 + +----------------------------- +java ssl | +----------------------------- + # keytool 指令一览 + -certreq 生成证书请求 + -changealias 更改条目的别名 + -delete 删除条目 + -exportcert 导出证书 + -genkeypair 生成密钥对 + -genseckey 生成密钥 + -gencert 根据证书请求生成证书 + -importcert 导入证书或证书链 + -importpass 导入口令 + -importkeystore 从其他密钥库导入一个或所有条目 + -keypasswd 更改条目的密钥口令 + -list 列出密钥库中的条目 + -printcert 打印证书内容 + -printcertreq 打印证书请求的内容 + -printcrl 打印 CRL 文件的内容 + -storepasswd 更改密钥库的存储口令 + + # 生成密钥对到keystore + keytool -genkey -deststoretype [pkcs12] -alias [alias] -validity [100] -keystore [server.keystore] -keyalg [RSA] -storepass [密码] + -genkey + * 生成证书的指令 + -deststoretype + * 指定证书类型,一般固定值:pkcs12 + -alias + * 指定证书在keystore中的别名 + -validity + * 有效期,单位是天 + -keystore + * 指定 keystore 名称(如果keystore不存在,会新建) + -keyalg + * 指定证书的非对称加密算法,一般固定:RSA + -keysize + * 指定加密算法的密钥长度 + -storepass + * keystore的密码 + -keypass + * 证书的密码 + * 只有JKS类型的证书才支持该选项,pkcs12 不支持,会忽略 + -dname + * 设置参数的快捷方式 + -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" + + # 从证书中导出公钥 + keytool -export -alias [alias] -file [name.cer] -keystore [name.keystore] -storepass [密码] + + -export + * 导出公钥的指令 + -alias + * keystore 中证书的别名 + -file + * 公钥证书的文件名称 + -keystore + * keystore + -storepass + * keystored的密码 + + # 导入公钥到 keystore + keytool -import -file [name.cer] -alias [alias] -keystore [name.keystore] -storepass [密码] + + -import + * 导入指令 + -file + * 公钥证书文件 + -alias + * 设置 该证书在目标keystore中的别名(默认名:mykey),不能冲突 + -keystore + * 导入到的目标 keystore(如果keystore不存在,会新建) + -storepass + * 目标 keystore的密码 + + + # 从keystore删除公钥 + keytool -delete -alias [alias] -keystore [name.keystore] -storepass [密码] + -delete + * 删除指令 + -alias + * 公钥的别名 + -keystore + * 目标keystore文件 + -storepass + * keystore的密码 + + # 查看keystore中的证书条目 + keytool -list -v -keystore [name.keystore] -storepass [密码] + + -list + * 列出指令 + -v + * 显示详情 + -keystore + * 指定的keystore + -storepass + * keystore的密码 + + # 生成证书请求 + keytool -certreq -alias [alias] -file [name.csr] -keystore [name.keystore] -storepass [密码] + + -certreq + * 生成请求指令 + -alias + * 证书在keystore中的别名 + -file + * 生成的请求文件名称 + -keystore + * 证书所在的keystore + -storepass + * keystore的密码 + + + # 根据证书请求生成证书 + keytool -gencert -alias [alias] -infile [name.csr] -outfile [name.cer] -keystore [name.keystore] -storepass [密码] + + -gencert + * 生成请求证书指令 + -alias + * 用于签发的证书的证书别名(root证书别名) + -infile + * 请求文件 + -outfile + * 生成的证书名称 + -keystore + * 用于签发的证书的证书所在的keystore(root证书) + -storepass + * 用于签发的证书的证书所在的keystore的密码(root证书) + + # 打印证书 + keytool -printcert -rfc -file [name.cer] + + -printcert + * 打印指令 + -rfc + -file + * 证书文件 + + # 从其他密钥库导入一个或所有条目 + keytool -importkeystore -v -srckeystore [name.p12] -srcstoretype [pkcs12] -srcstorepass [密码] -destkeystore [name.keystore] -deststoretype [pkcs12] -deststorepass [密码] + + -importkeystore + * 导入指令 + -v + -srckeystore + -srcstoretype + -srcstorepass + * 源密钥库文件,类型(PCKS12),密码 + -destkeystore + -deststoretype + -deststorepass + * 目标密钥库文件,类型(PCKS12),密码 + + +------------------------------------------ +keytool制作CA根证书以及颁发二级证书 | +------------------------------------------ + # CREATE CA + keytool -genkey -deststoretype pkcs12 -alias CA_ROOT -validity 3500 -keystore CA_ROOT.keystore -keyalg RSA -keysize 2048 -storepass 123456 + keytool -export -alias CA_ROOT -file CA_ROOT.cer -keystore CA_ROOT.keystore -storepass 123456 + + # CLIENT + keytool -genkey -deststoretype pkcs12 -alias client -validity 365 -keystore client.keystore -keyalg RSA -keysize 2048 -storepass 123456 + keytool -certreq -alias client -file client.csr -keystore client.keystore -storepass 123456 + + # CLIENT SIGN + keytool -gencert -alias CA_ROOT -infile client.csr -outfile client.cer -keystore CA_ROOT.keystore -storepass 123456 + + # SERVER + keytool -genkey -deststoretype pkcs12 -alias server -validity 365 -keystore server.keystore -keyalg RSA -keysize 2048 -storepass 123456 + keytool -certreq -alias server -file server.csr -keystore server.keystore -storepass 123456 + + # SERVER SIGN + keytool -gencert -alias CA_ROOT -infile server.csr -outfile server.cer -keystore CA_ROOT.keystore -storepass 123456 + + # INSTALL CLIENT + keytool -import -file CA_ROOT.cer -alias ca -keystore client.keystore -storepass 123456 + keytool -import -file ca_client.cer -alias client -keystore client.keystore -storepass 123456 + keytool -list -v -keystore client.keystore -storepass 123456 + + # INSTALL SERVER + keytool -import -file CA_ROOT.cer -alias ca -keystore server.keystore -storepass 123456 + keytool -import -file ca_server.cer -alias server -keystore server.keystore -storepass 123456 + keytool -list -v -keystore server.keystore -storepass 123456 + + diff --git a/ShadowsocksR/SSTap-beta-setup-1.1.0.1.exe.7z b/ShadowsocksR/SSTap-beta-setup-1.1.0.1.exe.7z new file mode 100644 index 00000000..60c09c6f Binary files /dev/null and b/ShadowsocksR/SSTap-beta-setup-1.1.0.1.exe.7z differ diff --git a/ShadowsocksR/ShadowsocksR-win-4.9.0.zip b/ShadowsocksR/ShadowsocksR-win-4.9.0.zip new file mode 100644 index 00000000..70119d5f Binary files /dev/null and b/ShadowsocksR/ShadowsocksR-win-4.9.0.zip differ diff --git a/ShadowsocksR/shadowsocksR.sh b/ShadowsocksR/shadowsocksR.sh new file mode 100644 index 00000000..cdc923ab --- /dev/null +++ b/ShadowsocksR/shadowsocksR.sh @@ -0,0 +1,514 @@ +#!/usr/bin/env bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +#=================================================================# +# System Required: CentOS 6,7, Debian, Ubuntu # +# Description: One click Install ShadowsocksR Server # +# Author: Teddysun # +# Thanks: @breakwa11 # +# Intro: https://shadowsocks.be/9.html # +#=================================================================# + +clear +echo +echo "#############################################################" +echo "# One click Install ShadowsocksR Server #" +echo "# Intro: https://shadowsocks.be/9.html #" +echo "# Author: Teddysun #" +echo "# Github: https://github.com/shadowsocksr/shadowsocksr #" +echo "#############################################################" +echo + +libsodium_file="libsodium-1.0.17" +libsodium_url="https://github.com/jedisct1/libsodium/releases/download/1.0.17/libsodium-1.0.17.tar.gz" +shadowsocks_r_file="shadowsocksr-3.2.2" +shadowsocks_r_url="https://github.com/shadowsocksrr/shadowsocksr/archive/3.2.2.tar.gz" + +#Current folder +cur_dir=`pwd` +# Stream Ciphers +ciphers=( +none +aes-256-cfb +aes-192-cfb +aes-128-cfb +aes-256-cfb8 +aes-192-cfb8 +aes-128-cfb8 +aes-256-ctr +aes-192-ctr +aes-128-ctr +chacha20-ietf +chacha20 +salsa20 +xchacha20 +xsalsa20 +rc4-md5 +) +# Reference URL: +# https://github.com/shadowsocksr-rm/shadowsocks-rss/blob/master/ssr.md +# https://github.com/shadowsocksrr/shadowsocksr/commit/a3cf0254508992b7126ab1151df0c2f10bf82680 +# Protocol +protocols=( +origin +verify_deflate +auth_sha1_v4 +auth_sha1_v4_compatible +auth_aes128_md5 +auth_aes128_sha1 +auth_chain_a +auth_chain_b +auth_chain_c +auth_chain_d +auth_chain_e +auth_chain_f +) +# obfs +obfs=( +plain +http_simple +http_simple_compatible +http_post +http_post_compatible +tls1.2_ticket_auth +tls1.2_ticket_auth_compatible +tls1.2_ticket_fastauth +tls1.2_ticket_fastauth_compatible +) +# Color +red='\033[0;31m' +green='\033[0;32m' +yellow='\033[0;33m' +plain='\033[0m' + +# Make sure only root can run our script +[[ $EUID -ne 0 ]] && echo -e "[${red}Error${plain}] This script must be run as root!" && exit 1 + +# Disable selinux +disable_selinux(){ + if [ -s /etc/selinux/config ] && grep 'SELINUX=enforcing' /etc/selinux/config; then + sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config + setenforce 0 + fi +} + +#Check system +check_sys(){ + local checkType=$1 + local value=$2 + + local release='' + local systemPackage='' + + if [[ -f /etc/redhat-release ]]; then + release="centos" + systemPackage="yum" + elif grep -Eqi "debian|raspbian" /etc/issue; then + release="debian" + systemPackage="apt" + elif grep -Eqi "ubuntu" /etc/issue; then + release="ubuntu" + systemPackage="apt" + elif grep -Eqi "centos|red hat|redhat" /etc/issue; then + release="centos" + systemPackage="yum" + elif grep -Eqi "debian|raspbian" /proc/version; then + release="debian" + systemPackage="apt" + elif grep -Eqi "ubuntu" /proc/version; then + release="ubuntu" + systemPackage="apt" + elif grep -Eqi "centos|red hat|redhat" /proc/version; then + release="centos" + systemPackage="yum" + fi + + if [[ "${checkType}" == "sysRelease" ]]; then + if [ "${value}" == "${release}" ]; then + return 0 + else + return 1 + fi + elif [[ "${checkType}" == "packageManager" ]]; then + if [ "${value}" == "${systemPackage}" ]; then + return 0 + else + return 1 + fi + fi +} + +# Get version +getversion(){ + if [[ -s /etc/redhat-release ]]; then + grep -oE "[0-9.]+" /etc/redhat-release + else + grep -oE "[0-9.]+" /etc/issue + fi +} + +# CentOS version +centosversion(){ + if check_sys sysRelease centos; then + local code=$1 + local version="$(getversion)" + local main_ver=${version%%.*} + if [ "$main_ver" == "$code" ]; then + return 0 + else + return 1 + fi + else + return 1 + fi +} + +# Get public IP address +get_ip(){ + local IP=$( ip addr | egrep -o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | egrep -v "^192\.168|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-2]\.|^10\.|^127\.|^255\.|^0\." | head -n 1 ) + [ -z ${IP} ] && IP=$( wget -qO- -t1 -T2 ipv4.icanhazip.com ) + [ -z ${IP} ] && IP=$( wget -qO- -t1 -T2 ipinfo.io/ip ) + [ ! -z ${IP} ] && echo ${IP} || echo +} + +get_char(){ + SAVEDSTTY=`stty -g` + stty -echo + stty cbreak + dd if=/dev/tty bs=1 count=1 2> /dev/null + stty -raw + stty echo + stty $SAVEDSTTY +} + +# Pre-installation settings +pre_install(){ + if check_sys packageManager yum || check_sys packageManager apt; then + # Not support CentOS 5 + if centosversion 5; then + echo -e "$[{red}Error${plain}] Not supported CentOS 5, please change to CentOS 6+/Debian 7+/Ubuntu 12+ and try again." + exit 1 + fi + else + echo -e "[${red}Error${plain}] Your OS is not supported. please change OS to CentOS/Debian/Ubuntu and try again." + exit 1 + fi + # Set ShadowsocksR config password + echo "Please enter password for ShadowsocksR:" + read -p "(Default password: teddysun.com):" shadowsockspwd + [ -z "${shadowsockspwd}" ] && shadowsockspwd="teddysun.com" + echo + echo "---------------------------" + echo "password = ${shadowsockspwd}" + echo "---------------------------" + echo + # Set ShadowsocksR config port + while true + do + dport=$(shuf -i 9000-19999 -n 1) + echo -e "Please enter a port for ShadowsocksR [1-65535]" + read -p "(Default port: ${dport}):" shadowsocksport + [ -z "${shadowsocksport}" ] && shadowsocksport=${dport} + expr ${shadowsocksport} + 1 &>/dev/null + if [ $? -eq 0 ]; then + if [ ${shadowsocksport} -ge 1 ] && [ ${shadowsocksport} -le 65535 ] && [ ${shadowsocksport:0:1} != 0 ]; then + echo + echo "---------------------------" + echo "port = ${shadowsocksport}" + echo "---------------------------" + echo + break + fi + fi + echo -e "[${red}Error${plain}] Please enter a correct number [1-65535]" + done + + # Set shadowsocksR config stream ciphers + while true + do + echo -e "Please select stream cipher for ShadowsocksR:" + for ((i=1;i<=${#ciphers[@]};i++ )); do + hint="${ciphers[$i-1]}" + echo -e "${green}${i}${plain}) ${hint}" + done + read -p "Which cipher you'd select(Default: ${ciphers[1]}):" pick + [ -z "$pick" ] && pick=2 + expr ${pick} + 1 &>/dev/null + if [ $? -ne 0 ]; then + echo -e "[${red}Error${plain}] Please enter a number" + continue + fi + if [[ "$pick" -lt 1 || "$pick" -gt ${#ciphers[@]} ]]; then + echo -e "[${red}Error${plain}] Please enter a number between 1 and ${#ciphers[@]}" + continue + fi + shadowsockscipher=${ciphers[$pick-1]} + echo + echo "---------------------------" + echo "cipher = ${shadowsockscipher}" + echo "---------------------------" + echo + break + done + + # Set shadowsocksR config protocol + while true + do + echo -e "Please select protocol for ShadowsocksR:" + for ((i=1;i<=${#protocols[@]};i++ )); do + hint="${protocols[$i-1]}" + echo -e "${green}${i}${plain}) ${hint}" + done + read -p "Which protocol you'd select(Default: ${protocols[0]}):" protocol + [ -z "$protocol" ] && protocol=1 + expr ${protocol} + 1 &>/dev/null + if [ $? -ne 0 ]; then + echo -e "[${red}Error${plain}] Input error, please input a number" + continue + fi + if [[ "$protocol" -lt 1 || "$protocol" -gt ${#protocols[@]} ]]; then + echo -e "[${red}Error${plain}] Input error, please input a number between 1 and ${#protocols[@]}" + continue + fi + shadowsockprotocol=${protocols[$protocol-1]} + echo + echo "---------------------------" + echo "protocol = ${shadowsockprotocol}" + echo "---------------------------" + echo + break + done + + # Set shadowsocksR config obfs + while true + do + echo -e "Please select obfs for ShadowsocksR:" + for ((i=1;i<=${#obfs[@]};i++ )); do + hint="${obfs[$i-1]}" + echo -e "${green}${i}${plain}) ${hint}" + done + read -p "Which obfs you'd select(Default: ${obfs[0]}):" r_obfs + [ -z "$r_obfs" ] && r_obfs=1 + expr ${r_obfs} + 1 &>/dev/null + if [ $? -ne 0 ]; then + echo -e "[${red}Error${plain}] Input error, please input a number" + continue + fi + if [[ "$r_obfs" -lt 1 || "$r_obfs" -gt ${#obfs[@]} ]]; then + echo -e "[${red}Error${plain}] Input error, please input a number between 1 and ${#obfs[@]}" + continue + fi + shadowsockobfs=${obfs[$r_obfs-1]} + echo + echo "---------------------------" + echo "obfs = ${shadowsockobfs}" + echo "---------------------------" + echo + break + done + + echo + echo "Press any key to start...or Press Ctrl+C to cancel" + char=`get_char` + # Install necessary dependencies + if check_sys packageManager yum; then + yum install -y python python-devel python-setuptools openssl openssl-devel curl wget unzip gcc automake autoconf make libtool + elif check_sys packageManager apt; then + apt-get -y update + apt-get -y install python python-dev python-setuptools openssl libssl-dev curl wget unzip gcc automake autoconf make libtool + fi + cd ${cur_dir} +} + +# Download files +download_files(){ + # Download libsodium file + if ! wget --no-check-certificate -O ${libsodium_file}.tar.gz ${libsodium_url}; then + echo -e "[${red}Error${plain}] Failed to download ${libsodium_file}.tar.gz!" + exit 1 + fi + # Download ShadowsocksR file + if ! wget --no-check-certificate -O ${shadowsocks_r_file}.tar.gz ${shadowsocks_r_url}; then + echo -e "[${red}Error${plain}] Failed to download ShadowsocksR file!" + exit 1 + fi + # Download ShadowsocksR init script + if check_sys packageManager yum; then + if ! wget --no-check-certificate https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocksR -O /etc/init.d/shadowsocks; then + echo -e "[${red}Error${plain}] Failed to download ShadowsocksR chkconfig file!" + exit 1 + fi + elif check_sys packageManager apt; then + if ! wget --no-check-certificate https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocksR-debian -O /etc/init.d/shadowsocks; then + echo -e "[${red}Error${plain}] Failed to download ShadowsocksR chkconfig file!" + exit 1 + fi + fi +} + +# Firewall set +firewall_set(){ + echo -e "[${green}Info${plain}] firewall set start..." + if centosversion 6; then + /etc/init.d/iptables status > /dev/null 2>&1 + if [ $? -eq 0 ]; then + iptables -L -n | grep -i ${shadowsocksport} > /dev/null 2>&1 + if [ $? -ne 0 ]; then + iptables -I INPUT -m state --state NEW -m tcp -p tcp --dport ${shadowsocksport} -j ACCEPT + iptables -I INPUT -m state --state NEW -m udp -p udp --dport ${shadowsocksport} -j ACCEPT + /etc/init.d/iptables save + /etc/init.d/iptables restart + else + echo -e "[${green}Info${plain}] port ${shadowsocksport} has been set up." + fi + else + echo -e "[${yellow}Warning${plain}] iptables looks like shutdown or not installed, please manually set it if necessary." + fi + elif centosversion 7; then + systemctl status firewalld > /dev/null 2>&1 + if [ $? -eq 0 ]; then + default_zone=$(firewall-cmd --get-default-zone) + firewall-cmd --permanent --zone=${default_zone} --add-port=${shadowsocksport}/tcp + firewall-cmd --permanent --zone=${default_zone} --add-port=${shadowsocksport}/udp + firewall-cmd --reload + else + echo -e "[${yellow}Warning${plain}] firewalld looks like not running or not installed, please enable port ${shadowsocksport} manually if necessary." + fi + fi + echo -e "[${green}Info${plain}] firewall set completed..." +} + +# Config ShadowsocksR +config_shadowsocks(){ + cat > /etc/shadowsocks.json<<-EOF +{ + "server":"0.0.0.0", + "server_ipv6":"[::]", + "server_port":${shadowsocksport}, + "local_address":"127.0.0.1", + "local_port":1080, + "password":"${shadowsockspwd}", + "timeout":120, + "method":"${shadowsockscipher}", + "protocol":"${shadowsockprotocol}", + "protocol_param":"", + "obfs":"${shadowsockobfs}", + "obfs_param":"", + "redirect":"", + "dns_ipv6":false, + "fast_open":false, + "workers":1 +} +EOF +} + +# Install ShadowsocksR +install(){ + # Install libsodium + if [ ! -f /usr/lib/libsodium.a ]; then + cd ${cur_dir} + tar zxf ${libsodium_file}.tar.gz + cd ${libsodium_file} + ./configure --prefix=/usr && make && make install + if [ $? -ne 0 ]; then + echo -e "[${red}Error${plain}] libsodium install failed!" + install_cleanup + exit 1 + fi + fi + + ldconfig + # Install ShadowsocksR + cd ${cur_dir} + tar zxf ${shadowsocks_r_file}.tar.gz + mv ${shadowsocks_r_file}/shadowsocks /usr/local/ + if [ -f /usr/local/shadowsocks/server.py ]; then + chmod +x /etc/init.d/shadowsocks + if check_sys packageManager yum; then + chkconfig --add shadowsocks + chkconfig shadowsocks on + elif check_sys packageManager apt; then + update-rc.d -f shadowsocks defaults + fi + /etc/init.d/shadowsocks start + + clear + echo + echo -e "Congratulations, ShadowsocksR server install completed!" + echo -e "Your Server IP : \033[41;37m $(get_ip) \033[0m" + echo -e "Your Server Port : \033[41;37m ${shadowsocksport} \033[0m" + echo -e "Your Password : \033[41;37m ${shadowsockspwd} \033[0m" + echo -e "Your Protocol : \033[41;37m ${shadowsockprotocol} \033[0m" + echo -e "Your obfs : \033[41;37m ${shadowsockobfs} \033[0m" + echo -e "Your Encryption Method: \033[41;37m ${shadowsockscipher} \033[0m" + echo + echo "Welcome to visit:https://shadowsocks.be/9.html" + echo "Enjoy it!" + echo + else + echo "ShadowsocksR install failed, please Email to Teddysun and contact" + install_cleanup + exit 1 + fi +} + +# Install cleanup +install_cleanup(){ + cd ${cur_dir} + rm -rf ${shadowsocks_r_file}.tar.gz ${shadowsocks_r_file} ${libsodium_file}.tar.gz ${libsodium_file} +} + + +# Uninstall ShadowsocksR +uninstall_shadowsocksr(){ + printf "Are you sure uninstall ShadowsocksR? (y/n)" + printf "\n" + read -p "(Default: n):" answer + [ -z ${answer} ] && answer="n" + if [ "${answer}" == "y" ] || [ "${answer}" == "Y" ]; then + /etc/init.d/shadowsocks status > /dev/null 2>&1 + if [ $? -eq 0 ]; then + /etc/init.d/shadowsocks stop + fi + if check_sys packageManager yum; then + chkconfig --del shadowsocks + elif check_sys packageManager apt; then + update-rc.d -f shadowsocks remove + fi + rm -f /etc/shadowsocks.json + rm -f /etc/init.d/shadowsocks + rm -f /var/log/shadowsocks.log + rm -rf /usr/local/shadowsocks + echo "ShadowsocksR uninstall success!" + else + echo + echo "uninstall cancelled, nothing to do..." + echo + fi +} + +# Install ShadowsocksR +install_shadowsocksr(){ + disable_selinux + pre_install + download_files + config_shadowsocks + if check_sys packageManager yum; then + firewall_set + fi + install + install_cleanup +} + +# Initialization step +action=$1 +[ -z $1 ] && action=install +case "$action" in + install|uninstall) + ${action}_shadowsocksr + ;; + *) + echo "Arguments error! [${action}]" + echo "Usage: `basename $0` [install|uninstall]" + ;; +esac \ No newline at end of file diff --git "a/ShadowsocksR/ssr-bbr\345\256\211\350\243\205.java" "b/ShadowsocksR/ssr-bbr\345\256\211\350\243\205.java" new file mode 100644 index 00000000..735e2f34 --- /dev/null +++ "b/ShadowsocksR/ssr-bbr\345\256\211\350\243\205.java" @@ -0,0 +1,7 @@ +class +{ + public static void main(String[] args) + { + System.out.println("Hello World!"); + } +} diff --git "a/ShadowsocksR/ssr-\345\256\211\350\243\205.java" "b/ShadowsocksR/ssr-\345\256\211\350\243\205.java" new file mode 100644 index 00000000..9c3e3a6d --- /dev/null +++ "b/ShadowsocksR/ssr-\345\256\211\350\243\205.java" @@ -0,0 +1,60 @@ +# 下载 & 执行脚本 + * wget --no-check-certificate https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocksR.sh + + * chmod +x shadowsocksR.sh + + + * ./shadowsocksR.sh 2>&1 | tee shadowsocksR.log + - 输入密码 + - 端口 + - 加密(rc4-md5) + - 协议(auth_aes128_md5) + - 混淆(http_simple) + +# 常用命令 + 启动 /etc/init.d/shadowsocks start + 停止 /etc/init.d/shadowsocks stop + 重启 /etc/init.d/shadowsocks restart + 状态 /etc/init.d/shadowsocks status + +# 文件路径 + 配置文件路径 /etc/shadowsocks.json + { + "server":"0.0.0.0", + "server_ipv6":"[::]", + "server_port":28888, + "local_address":"127.0.0.1", + "local_port":1080, + "password":"19931209", + "timeout":120, # 超时时间 + "method":"rc4-md5", # 加密方式 + "protocol":"auth_aes128_md5", # 协议插件 + "protocol_param":"", # 协议参数 + "obfs":"http_simple", # 混淆插件 + "obfs_param":"", # 混淆参数 + "redirect":"", + "dns_ipv6":false, + "fast_open":false, # 如果你的服务器 Linux 内核在3.7+,可以开启 true 以降低延迟 + "workers":1 # 默认一个工作者 + } + 日志文件路径 /var/log/shadowsocks.log + 代码安装目录 /usr/local/shadowsocks + +# 多用户配置 + * 把 shadowsocks.json 配置中的:server_port 和 password 删除 + * 然后添加 + "port_password":{ + "8989":"password1", + "8990":"password2", + "8991":"password3" + }, + + + +# windows 客户端 + * 网上找 + +# 开启UDP转发 + * 云服务器安全组开放ssr端口的udp协议 + * 防火墙开放ssr端口的udp协议 + diff --git a/ShadowsocksR/sstap.java b/ShadowsocksR/sstap.java new file mode 100644 index 00000000..a554c305 --- /dev/null +++ b/ShadowsocksR/sstap.java @@ -0,0 +1,8 @@ +------------------------------------ +SSTap | +------------------------------------ + # 下载 + https://www.sockscap64.com/zh-hant/sstap/ + + # 配置规则 + https://github.com/FQrabbit/SSTap-Rule \ No newline at end of file diff --git a/Shadowsockts.java b/Shadowsockts.java new file mode 100644 index 00000000..aa5d10a8 --- /dev/null +++ b/Shadowsockts.java @@ -0,0 +1,52 @@ + +# 瀹夎pip + yum install python-setuptools & easy_install pip + +# 瀹夎Shadowsocks 瀹㈡埛绔 + pip install shadowsocks + +# 缂栬緫閰嶇疆鏂囦欢 + * vim /etc/shadowsocks.json + + { + "server":"0.0.0.0", + "port_password":{ + "123456" : "----" # 绔彛鍜屽瘑鐮侊紝shadowsocks涓嶉渶瑕佽处鍙凤紝鐪熻璇存湁锛岃处鍙峰氨鏄綘鐨刬p浜嗐傚彲浠ラ厤缃涓 + }, + "timeout":300,聽 聽 聽 聽 聽 聽 聽 聽 聽 #聽瓒呮椂鏃堕棿 杩欎釜涓嶇敤淇敼 + "method":"rc4-md5",聽 聽 聽 #聽鍔犲瘑鏂瑰紡锛屼笉鐢ㄤ慨鏀癸紝鎯虫敼鐨勮嚜琛屼簡瑙e悇绉嶅姞瀵嗙畻娉曞惂 + "protocol" : "auth_aes128_md5", # 鍗忚鎻掍欢 + "protocol_param": "", # 鍗忚鍙傛暟 + "obfs" : "http_simple", # 娣锋穯鎻掍欢 + "obfs_param": "", # 娣锋穯鍙傛暟 + "fast_open": false,聽 聽 聽 聽 聽 聽 聽# 濡傛灉浣犵殑鏈嶅姟鍣 Linux 鍐呮牳鍦3.7+锛屽彲浠ュ紑鍚 true 浠ラ檷浣庡欢杩 + "workers": 1 #聽榛樿涓涓伐浣滆 + } + +# 鍚姩 shadowsocks + * 鍚姩 + ssserver -c /etc/shadowsocks.json -d start + + * 鍋滄 + ssserver -c /etc/shadowsocks.json -d stop + + * 閲嶆柊鍚姩 + ssserver -c /etc/shadowsocks.json -d restart + + + * 鏌ョ湅鏄惁鍚姩鎴愬姛 + ps -aux | grep ssserver + +# 璁剧疆寮鏈哄惎鍔 + vim /etc/rc.local + ssserver -c /etc/shadowsocks/config.json -d start + +# 寮鍚槻鐏绔彛 + * 鐣 + + +# windows瀹㈡埛绔笅杞 + https://github.com/shadowsocks/shadowsocks-windows/releases + + +# shadowsocks鏈嶅姟浼氳闃块噷浜戜箣绫荤殑鏈嶅姟鍟嗘娴嬪埌,寤鸿浣跨敤 shadowsocksR \ No newline at end of file diff --git "a/Shell/practice/99\344\271\230\346\263\225\350\241\250.sh" "b/Shell/practice/99\344\271\230\346\263\225\350\241\250.sh" new file mode 100644 index 00000000..fb69e927 --- /dev/null +++ "b/Shell/practice/99\344\271\230\346\263\225\350\241\250.sh" @@ -0,0 +1,10 @@ + +#!/bin/bash +val=10 +for (( i = 1 ;i < ${val} ; i++)); do + for (( y = 1; y <= ${i}; y ++ )); do + sum=$[${i} * ${y}] + echo -n "${i} x ${y} = ${sum} " + done + echo "" +done diff --git "a/Shell/practice/\345\206\222\346\263\241\346\216\222\345\272\217.sh" "b/Shell/practice/\345\206\222\346\263\241\346\216\222\345\272\217.sh" new file mode 100644 index 00000000..837ec75f --- /dev/null +++ "b/Shell/practice/\345\206\222\346\263\241\346\216\222\345\272\217.sh" @@ -0,0 +1,54 @@ +#!/bin/bash +# 直接修改原始数组 + +arr=(9 8 4 5 6 3 1 2 0 7) + +function sort { + len=${#arr[*]} + for (( i = 0 ; i < ${len} ; i ++ )) ; do + for (( x = ${i} ; x < ${len} ; x ++ )) ; do + if [ ${arr[${x}]} -gt ${arr[${i}]} ] ; then + temp=${arr[${x}]} + arr[${x}]=${arr[${i}]} + arr[${i}]=${temp} + fi + done + done +} +echo "排序前:${arr[*]}" +sort +echo "排序后:${arr[*]}" + + + + +#!/bin/bash +# 返回新的数组 + +arr=(0 4 1 5 7 8 2 9 3 6) + +function sort { + local arr=() + local index=0 + for i in $@ ; do + arr[${index}]=${i} + index=$[${index} + 1] + done + + for ((i = 0 ; i < ${index} ; i ++)) ; do + for (( x = ${i} ; x < ${index} ; x ++ )) ; do + if [ ${arr[${x}]} -gt ${arr[${i}]} ] ; then + temp=${arr[${x}]} + arr[${x}]=${arr[${i}]} + arr[${i}]=${temp} + fi + done + done + echo ${arr[*]} +} + +sortArr=`sort ${arr[*]}` + +echo "原始数组:${arr[*]}" + +echo "排序数组:${sortArr}" \ No newline at end of file diff --git "a/Shell/practice/\346\211\271\351\207\217\346\267\273\345\212\240\347\224\250\346\210\267.sh" "b/Shell/practice/\346\211\271\351\207\217\346\267\273\345\212\240\347\224\250\346\210\267.sh" new file mode 100644 index 00000000..9cdfbde2 --- /dev/null +++ "b/Shell/practice/\346\211\271\351\207\217\346\267\273\345\212\240\347\224\250\346\210\267.sh" @@ -0,0 +1,17 @@ +#!/bin/bash +# ============================================= 2019年1月22日 13:56:43 +:<> $outfile << EOF +INSERT INTO members (lname,fname,address,city,state,zip) VALUES +('$lname', '$fname', '$address', '$city', '$state', '$zip'); +EOF +done < ${1} + +:< + + * 重定向正常消息和错误消息 + &> + +---------------------------- +在脚本中重定向输出 | +---------------------------- + # 有意在脚本中生成错误消息,可以将单独的一行输出重定向到 STDERR : >&2 + + # 在脚本中临时重定向输出 + echo "异常信息" >&2 + + * 默认情况下,Linux会将 STDERR 导向 STDOUT + * 如果在运行脚本时重定向了STDERR ,脚本中所有导向 STDERR 的文本都会被重定向 + + echo "这是一个异常消息" >&2 + echo "这是一个正常消息" + + ./demo.sh 2> log # + + * STDOUT 显示的文本显示在了屏幕上,而发送给 STDERR 的 echo 语句的文本则被重定向到了输出文件 + + # 在脚本中永久重定向 + * 如果脚本中有大量数据需要重定向,那重定向每个 echo 语句就会很烦琐 + * 可以用 exec 命令告诉shell在脚本执行期间重定向某个特定文件描述符 + exec 1>file + + 1:表示STOUT + file:重定向的文件 + + * exec 命令会启动一个新shell并将 STDOUT 文件描述符重定向到文件 + * 脚本中发给 STDOUT 的所有输出会被重定向到文件 + * 可以在脚本执行过程中重定向回 STDOUT + +---------------------------- +在脚本中重定向输入 | +---------------------------- + # 使用与脚本中重定向 STDOUT 和 STDERR 相同的方法来将 STDIN 从键盘重定向到其他位置 + * exec 命令允许将 STDIN 重定向到Linux系统上的文件中 + + exec 0file # 把文件描述符3分配给了file这个文件 + echo "这是消息" >&3 # 把echo的内容由标准输出重定向到3 + + # 重定向时仅仅追加,不覆盖 + exec 3>>file + + # 重定向文件描述符 + exec 3>&1 # 把3重定向到标准输出 + exec 1>test14out # 把标准输出重定向到文件 + + + # 创建输入文件描述符 + * 可以用和重定向输出文件描述符同样的办法重定向输入文件描述符 + * 在重定向到文件之前,先将 STDIN 文件描述符保存到另外一个文件描述符 + * 然后在读取完文件之后再将 STDIN 恢复到它原来的位置 + + exec 6<&0 # 把标准输入重定向到6 + exec 0< testfile # 把文件重定向到标准输入 + + # 创建读写文件描述符 + * 用这种方法时,要特别小心 + * 对同一个文件进行数据读写,shell会维护一个内部指针,指明在文件中的当前位置 + * 任何读或写都会从文件指针上次的位置开始 + * 所以这操作,可能会导致数据被覆盖 + + exec 3<> testfile + + # 关闭文件描述符 + * 建了新的输入或输出文件描述符,shell会在脚本退出时自动关闭它们 + * 有些情况下,你需要在脚本结束前手动关闭文件描述符 + * 要关闭文件描述符,将它重定向到特殊符号 &- + exec 3>&- + + * 关闭文件描述符3,不再在脚本中使用它 + + * 如果尝试使用已经关闭的文件描述符会有异常:Bad file descriptor + * 关闭文件描述符时还要注意另一件事,如果随后在脚本中打开了同一个输出文件 + * shell会用一个新文件来替换已有文件,这意味着如果你输出数据,它就会覆盖已有文件 + +---------------------------- +列出打开的文件描述符 | +---------------------------- + # lsof 命令会列出整个Linux系统打开的所有文件描述符 + * 这是个有争议的功能 + * 因为它会向非系统管理员用户提供Linux系统的信息 + * 鉴于此,许多Linux系统隐藏了该命令,这样用户就不会一不小心就发现了 + * 很多Linux系统中(如Fedora), lsof 命令位于/usr/sbin目录,要想以普通用户账户来运行它,必须通过全路径名来引用 + /usr/sbin/lsof + + * 该命令会产生大量的输出,它会显示当前Linux系统上打开的每个文件的有关信息 + * 这包括后台运行的所有进程以及登录到系统的任何用户 + + /usr/sbin/lsof -a -p $$ -d 0,1,2 + + -a:对其他两个结果进 & 运算 + -p:指定pid($$为shell的pid) + -d:显示的文件描述符编号 + 0,1,2:默认文件描述符 + + +---------------------------- +阻止命令输出 | +---------------------------- + # 可能不想显示脚本的输出,这在将脚本作为后台进程运行时很常见 + # 可以把输出重定向到 /dev/null + ls > /dev/null + + # 快速的清空文件 + cat /dev/null > file + + +---------------------------- +创建临时文件 | +---------------------------- + # Linux使用/tmp目录来存放不需要永久保留的文件 + # 大多数Linux发行版配置了系统在启动时自动删除/tmp目录的所有文件 + + # 有个特殊命令可以用来创建临时文件, mktemp 命令可以在/tmp目录中创建一个唯一的临时文件 + * shell会创建这个文件,但不用默认的 umask 值 + + # 创建本地临时文件 + * mktemp 会在本地目录中创建一个文件 + * 要用 mktemp 命令在本地目录中创建一个临时文件,只要指定一个文件名模板就行了 + * 模板可以包含任意文本文件名,在文件名末尾加上6个 X 就行了 + mktemp testing.XXXXXX + + * mktemp 命令会用6个字符码替换这6个 X ,从而保证文件名在目录中是唯一的 + + # 在脚本中使用 mktemp 命令时,可能要将文件名保存到变量中,这样就能在后面的脚本中引用了 + tempfile=$(mktemp test19.XXXXXX) + exec 3>$tempfile + + # 在/tmp 目录创建临时文件 + * -t 选项会强制 mktemp 命令来在系统的临时目录来创建该文件 + * mktemp 命令会返回用来创建临时文件的全路径,而不是只有文件名 + + + # 创建临时目录 + * -d 选项告诉 mktemp 命令来创建一个临时目录而不是临时文件 + +---------------------------- +记录消息 | +---------------------------- + # 将输出同时发送到显示器和日志文件,这种做法有时候能够派上用场 + # 你不用将输出重定向两次,只要用特殊的 tee 命令就行 + tee file + + * 它就像是一个一个T形的管道,可以同时往STDOUT和文件输出数据 + + # tee 会重定向来自 STDIN 的数据,可以用它配合管道命令来重定向命令输出 + date | tee testfil # 会在屏幕输出,还会输出到文件 + + + # tee 命令会在每次使用时覆盖输出文件内容 + * 如果你想将数据追加到文件中,必须用 -a 选项 + date | tee -a testfile + + # demo + tempfile=/usr/local/temp + echo "This is the start of the test" | tee $tempfile + echo "This is the second line of the test" | tee -a $tempfile + echo "This is the end of the test" | tee -a $tempfile + + diff --git "a/Shell/shell-\346\225\260\346\215\256\347\261\273\345\236\213-\345\255\227\347\254\246\344\270\262.sh" "b/Shell/shell-\346\225\260\346\215\256\347\261\273\345\236\213-\345\255\227\347\254\246\344\270\262.sh" new file mode 100644 index 00000000..69f5c59f --- /dev/null +++ "b/Shell/shell-\346\225\260\346\215\256\347\261\273\345\236\213-\345\255\227\347\254\246\344\270\262.sh" @@ -0,0 +1,62 @@ +----------------- +数据类型字符串 | +----------------- + # 字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了) + # 字符串可以用单引号,也可以用双引号,也可以不用引号,单双引号的区别跟PHP类似 + + # 单引号 + str='this is a string' + + * 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的 + name="KevinBlandy" + echo 'Hello ${name}' -> Hello ${name} + + * 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行) + str='hello, \'' -> unexpected EOF while looking for matching `'' + + * 单引号可成对出现,作为字符串拼接使用 + name='v' + str='hello, '$name' !' -> hello, v ! + + # 双引号 + your_name='vin' + str="Hello, I know you are \"$your_name\"! \n" + echo -e $str + + # 字符串的拼接 + name="vin" + + greeting="hello, "$name" !" -> hello, vin ! + greeting_1="hello, ${name} !" -> hello, vin ! + + greeting_2='hello, '$name' !' -> hello, vin ! + greeting_3='hello, ${name} !' -> hello, ${name} ! + + # 获取字符串的长度 + #!/bin/bash + name="KevinBlandy" + echo ${#name} -> 11 + + S="KevinBlandy" + echo "${S} size = ${#S}" -> 11 + + # 提取子字符串 + string="KevinBlandy" + echo ${string:1:4} -> evin + + * ${:x:y} ,x表示开始的角标,y表示取多少个字符 + + # 查找子字符串角标 + #!/bin/bash + NAME="KevinBlandy" + S="B" + index=`expr index ${NAME} ${S}` + echo "${index}" # -> 6 + + * 返回的开始位置是从1开始计算的 + * 如果找不到返回0 + * 实际上使用的是shell命令 + expr index "KevinBlandy" "B" + + + diff --git "a/Shell/shell-\346\225\260\346\215\256\347\261\273\345\236\213-\346\225\260\347\273\204.sh" "b/Shell/shell-\346\225\260\346\215\256\347\261\273\345\236\213-\346\225\260\347\273\204.sh" new file mode 100644 index 00000000..fe1c6b1b --- /dev/null +++ "b/Shell/shell-\346\225\260\346\215\256\347\261\273\345\236\213-\346\225\260\347\273\204.sh" @@ -0,0 +1,54 @@ +------------------------------ +数组 | +------------------------------ + # 不支持多维数组,没有限制数组的大小,下标从0开始,并且可以是字符串或者数值 + # 越界不会报错,返回空 + # 使用括号表示数组,每个元素使用空格分开 + ARR=(1 2 3 4) + + # 也可以使用回车来定义 + ARR=( + Hello + + + + World + ) + echo ${ARR[*]} # -> Hello World + echo ${ARR[1]} # -> world + echo ${ARR[2]} # -> 空 + + * 空白的元素会被忽略,后面的元素会向上靠拢 + + # 可以先定义,再初始化 + ARR=() + ARR[0]=0 + ARR[1]="Hello" + ARR[3]="World" + echo ${ARR[*]} # -> 0 Hello World + echo ${ARR[2]} # 空 + echo ${ARR[3]} # World + + * 可以不按照顺序来定义,其中跳过的部分为空 + + # 可以通过角标来获取和设置值 + ARR=(1 2 3 4) + ARR[0]=1 + + # 可以通过 @/* 符合来获取到所有的值 + ARR=(1 2 3) + echo ${ARR} # -> 1 + echo ${ARR[@]} # -> 123 + echo ${ARR[*]} # -> 123 + + * 如果元素为空,全部获取时会被忽略 + + # 获取数组的长度 + ARR=() + ARR[0]=0 + ARR[15]="1" + SIZE=${#ARR[*]} + echo ${SIZE} # 2 + + * 这种方式不会计算数组中空元素的个数 + diff --git "a/Shell/shell-\346\255\243\345\210\231.sh" "b/Shell/shell-\346\255\243\345\210\231.sh" new file mode 100644 index 00000000..b83ebc95 --- /dev/null +++ "b/Shell/shell-\346\255\243\345\210\231.sh" @@ -0,0 +1,4 @@ +-------------------- +正则入门 | +-------------------- + \ No newline at end of file diff --git "a/Shell/shell-\347\216\257\345\242\203\345\217\230\351\207\217\345\210\227\350\241\250.sh" "b/Shell/shell-\347\216\257\345\242\203\345\217\230\351\207\217\345\210\227\350\241\250.sh" new file mode 100644 index 00000000..06168ed7 --- /dev/null +++ "b/Shell/shell-\347\216\257\345\242\203\345\217\230\351\207\217\345\210\227\350\241\250.sh" @@ -0,0 +1,193 @@ + +# 可以使用:set命名查看 + + +BASH=/bin/bash +BASHOPTS=checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:histappend:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath +BASH_ALIASES=() +BASH_ARGC=() +BASH_ARGV=() +BASH_CMDS=() +BASH_LINENO=() +BASH_SOURCE=() +BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu") +BASH_VERSION='4.2.46(2)-release' +CLASSPATH=.:/usr/local/java/jdk1.8.0_181/lib/dt.jar:/usr/local/java/jdk1.8.0_181/lib/tools/jar:/usr/local/java/jdk1.8.0_181/jre/lib: +COLUMNS=186 +DIRSTACK=() +EUID=0 +GROUPS=() +HISTCONTROL=ignoredups +HISTFILE=/root/.bash_history +HISTFILESIZE=1000 +HISTSIZE=1000 +HOME=/root +HOSTNAME=KevinBlandy +HOSTTYPE=x86_64 +ID=0 +IFS=$' \t\n' +JAVA_HOME=/usr/local/java/jdk1.8.0_181 +JRE_HOME=/usr/local/java/jdk1.8.0_181/jre +LANG=en_US.UTF-8 +LD_LIBRARY_PATH=:/usr/local/apr/lib +LESSOPEN='||/usr/bin/lesspipe.sh %s' +LINES=53 +LOGNAME=root +LS_COLORS='rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:' +MACHTYPE=x86_64-redhat-linux-gnu +MAIL=/var/spool/mail/root +MAILCHECK=60 +MAVEN_HOME=/usr/local/maven/apache-maven-3.5.4 +OLDPWD=/root +OPTERR=1 +OPTIND=1 +OSTYPE=linux-gnu +PATH=/usr/local/java/jdk1.8.0_181/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/maven/apache-maven-3.5.4/bin:/root/bin +PIPESTATUS=([0]="0") +PPID=1048 +PS1='[\u@\h \W]\$ ' +PS2='> ' +PS4='+ ' +PWD=/shell +SHELL=/bin/bash +SHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor +SHLVL=1 +SSH_CLIENT='222.182.194.236 18693 22211' +SSH_CONNECTION='222.182.194.236 18693 172.31.134.244 22211' +SSH_TTY=/dev/pts/0 +TERM=linux +UID=0 + * 当前用户的id +USER=root + * 当前用户 +XDG_RUNTIME_DIR=/run/user/0 +XDG_SESSION_ID=5706 +_=./demo.sh +colors=/root/.dircolors +[root@KevinBlandy shell]# clear +[root@KevinBlandy shell]# set +BASH=/bin/bash +BASHOPTS=checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:histappend:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath +BASH_ALIASES=() +BASH_ARGC=() +BASH_ARGV=() +BASH_CMDS=() +BASH_LINENO=() +BASH_SOURCE=() +BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu") +BASH_VERSION='4.2.46(2)-release' +CLASSPATH=.:/usr/local/java/jdk1.8.0_181/lib/dt.jar:/usr/local/java/jdk1.8.0_181/lib/tools/jar:/usr/local/java/jdk1.8.0_181/jre/lib: +COLUMNS=186 +DIRSTACK=() +EUID=0 +GROUPS=() +HISTCONTROL=ignoredups +HISTFILE=/root/.bash_history +HISTFILESIZE=1000 +HISTSIZE=1000 +HOME=/root +HOSTNAME=KevinBlandy +HOSTTYPE=x86_64 +ID=0 +IFS=$' \t\n' +JAVA_HOME=/usr/local/java/jdk1.8.0_181 +JRE_HOME=/usr/local/java/jdk1.8.0_181/jre +LANG=en_US.UTF-8 +LD_LIBRARY_PATH=:/usr/local/apr/lib +LESSOPEN='||/usr/bin/lesspipe.sh %s' +LINES=53 +LOGNAME=root +LS_COLORS='rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:' +MACHTYPE=x86_64-redhat-linux-gnu +MAIL=/var/spool/mail/root +MAILCHECK=60 +MAVEN_HOME=/usr/local/maven/apache-maven-3.5.4 +OLDPWD=/root +OPTERR=1 +OPTIND=1 +OSTYPE=linux-gnu +PATH=/usr/local/java/jdk1.8.0_181/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/maven/apache-maven-3.5.4/bin:/root/bin +PIPESTATUS=([0]="0") +PPID=1048 +PS1='[\u@\h \W]\$ ' +PS2='> ' +PS4='+ ' +PWD=/shell +SHELL=/bin/bash +SHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor +SHLVL=1 +SSH_CLIENT='222.182.194.236 18693 22211' +SSH_CONNECTION='222.182.194.236 18693 172.31.134.244 22211' +SSH_TTY=/dev/pts/0 +TERM=linux +UID=0 +USER=root +XDG_RUNTIME_DIR=/run/user/0 +XDG_SESSION_ID=5706 +_=clear +colors=/root/.dircolors +[root@KevinBlandy shell]# clear +[root@KevinBlandy shell]# +[root@KevinBlandy shell]# +[root@KevinBlandy shell]# clear +[root@KevinBlandy shell]# set +BASH=/bin/bash +BASHOPTS=checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:histappend:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath +BASH_ALIASES=() +BASH_ARGC=() +BASH_ARGV=() +BASH_CMDS=() +BASH_LINENO=() +BASH_SOURCE=() +BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu") +BASH_VERSION='4.2.46(2)-release' +CLASSPATH=.:/usr/local/java/jdk1.8.0_181/lib/dt.jar:/usr/local/java/jdk1.8.0_181/lib/tools/jar:/usr/local/java/jdk1.8.0_181/jre/lib: +COLUMNS=186 +DIRSTACK=() +EUID=0 +GROUPS=() +HISTCONTROL=ignoredups +HISTFILE=/root/.bash_history +HISTFILESIZE=1000 +HISTSIZE=1000 +HOME=/root +HOSTNAME=KevinBlandy +HOSTTYPE=x86_64 +ID=0 +IFS=$' \t\n' +JAVA_HOME=/usr/local/java/jdk1.8.0_181 +JRE_HOME=/usr/local/java/jdk1.8.0_181/jre +LANG=en_US.UTF-8 +LD_LIBRARY_PATH=:/usr/local/apr/lib +LESSOPEN='||/usr/bin/lesspipe.sh %s' +LINES=53 +LOGNAME=root +LS_COLORS='rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:' +MACHTYPE=x86_64-redhat-linux-gnu +MAIL=/var/spool/mail/root +MAILCHECK=60 +MAVEN_HOME=/usr/local/maven/apache-maven-3.5.4 +OLDPWD=/root +OPTERR=1 +OPTIND=1 +OSTYPE=linux-gnu +PATH=/usr/local/java/jdk1.8.0_181/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/maven/apache-maven-3.5.4/bin:/root/bin +PIPESTATUS=([0]="0") +PPID=1048 +PS1='[\u@\h \W]\$ ' +PS2='> ' +PS4='+ ' +PWD=/shell +SHELL=/bin/bash +SHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor +SHLVL=1 +SSH_CLIENT='222.182.194.236 18693 22211' +SSH_CONNECTION='222.182.194.236 18693 172.31.134.244 22211' +SSH_TTY=/dev/pts/0 +TERM=linux +UID=0 +USER=root +XDG_RUNTIME_DIR=/run/user/0 +XDG_SESSION_ID=5706 +_=clear +colors=/root/.dircolors \ No newline at end of file diff --git "a/Shell/shell-\347\273\223\346\236\204\350\257\255\345\217\245.sh" "b/Shell/shell-\347\273\223\346\236\204\350\257\255\345\217\245.sh" new file mode 100644 index 00000000..a3194572 --- /dev/null +++ "b/Shell/shell-\347\273\223\346\236\204\350\257\255\345\217\245.sh" @@ -0,0 +1,478 @@ +--------------------- +if then | +--------------------- + # 语法 + if cmd + then + cmds + fi + + + * cmd 其实并不是一个 bool表达式 + * 严格来说,它是一个shell命令 + * 这个命令的结束状态码如果是0,就会执行 cmds + + # 更加优雅的语法 + if cmd; then + coms + fi + + * 在cmd后放置一个分号,就可以把then写在后面了 + if pwd > /dev/null;then + echo "success" + fi + +--------------------- +if then else | +--------------------- + # 单个else语法 + if cmd; then + coms + else + coms + fi + + # 多个elseif语法 + if cmd; then + coms + elif cmd; then + coms + else + coms + fi + + + + # 小练习 + testuser="kevinblandy" + if grep ${testuser} /etc/passwd; then + echo "用户 :${testuser} 在系统中存在" + elif ls -d /home/${testuser}; then + echo "用户 :${testuser} 在系统中不存在" + echo "但是存在他的家目录" + else + echo "用户不存在,也没有他的家目录" + fi + + +--------------------- +test | +--------------------- + # if then 语句中,根据shell命令返回的状态码来确定是否要执行代码块 + # 但是if then不能判断命令状态码之外的条件,此时可以使用 test 指令 + if test condition;then + cmds + fi + + * condition 是一系列的参数和值 + * 其实condition是把计算结果的true转换为了状态码0,把false转换为了非0 + + # 如果忽略 condition,那么它就会以非0状态退出,执行else块 + if test; then + echo "success" + else + echo "error" # 执行 + fi + + # 测试变量是否有内容 + var=" " + test var # false + + * 全是空白字符串的值test运算结果为false + + + # 更加优雅的写法,使用[]来代替 test 指令 + if [ condition ]; then + echo "success" + else + echo "error" + fi + + * 注意了,第一个方括号之后,和第二个方括号之前必须添加一个空格 + + # test 命令可以判断三类条件 + * 数值的比较 + * 字符串的比较 + * 文件的比较 + + + # 不能在test命令中使用浮点数 + +---------------------------- +if-then 的高级特性 | +---------------------------- + # 用于数学表达式的双括号 + * 表达式 + (( expression )) + + * 除了test运算可以使用的运算符以为,在(())里面还可以使用的运算符 + val++ 后增 + varl-- 后减 + ++val 先增 + --val 先减 + ! 求反 + ~ 位反 + ** 幂运算 + << 左位移 + >> 右位移 + & 位移与 + | 位移或 + && 逻辑和 + || 逻辑或 + + * 双括号中的><运算不需要转义 + val=10 + if (( $val ** 2 > 90 )); then + (( val1=$val ** 2 )) # 双括号赋值 + echo "the square of ${val} is ${val1}" + fi + + * 不仅可以在if语句中用双括号命令,也可以在脚本的普通命令中使用双括号来赋值 + val=10 + (( val1=$val ** 2 )) + echo ${val1} + + * 这种特性就是,可以在条件里面使用各种表达式,不是所有的shell都支持这种双括号要注意 + + # 用于高级字符串处理功能的双方括号 + * 表达式 + [[ expression ]] + + * expression 使用 test命令中采用的标准字符串比较 + * 还提供了另一个特性:模式匹配 + reg="r*" # 正则表达式 + if [[ ${USER} == ${reg} ]] ; then + echo "Hello ${user}" + fi + + + * 这种特性就是可以在[[]]里面使用正则表达式 + * 不是所有的shell都支持双方括号 + + +---------------------------- +case | +---------------------------- + # 语法(玄学的语法) + case variable in + pattern | pattern) + command;; + pattern) + command;; + *) + default command;; + esac + + variable:表示变量 + pattern:表示一个只,可以有多个,使用|分割 + command:如果值匹配上了就执行的命令 + default command:都没匹配上时执行的语句 + + + * 代码块中,最后一行代码末尾需要添加两个分号 + * demo + val="c" + case ${val} in + "a") + echo "匹配到了" + echo "a";; + "b" | "c") + echo "匹配到了" + echo "b or c";; + *) + echo "都不是";; + esac + + +---------------------------- +for | +---------------------------- + # 语法 + for var in list + do + commands + done + + var :临时的迭代变量 + * 它会一直存在(值是最后一次迭代值)直到脚本终止(js一样) + * 当然,可以主动的修改这个值 + + list :多个迭代的数据 + * 默认情况下使用以下字符作为分隔符:空格,制表符,换行 + + commands: 每次迭代执行的代码 + + # 也可以把 do 关键字放在第一行 + for var in list; do + commands + done + + —————————————————————————————————— + for var in 1 2 3 "Hello"; do + echo "value=${var}" + done + + value=1 + value=2 + value=3 + value=Hello + —————————————————————————————————— + + # list数据中有引号的问题 + for var in I don't know if this'll work; do + echo "word:${var}" + done + + word:I + word:dont know if thisll # 两个单引号之间的内容,当作一个内容输出了 + word:work + + * shell尝试把列表中的单引号内容看做是一份单独的数据 + + * 可以给单引号添加转义字符 + for var in I don\'t know if this\'ll work; do + echo "word:${var}" + done + + * 也可以使用双引号来定义用到单引号的值 + for var in I "don't" know if "this'll" work; do + echo "word:${var}" + done + + # list数据中有空格的问题 + for var in Hello World; do + echo "word:${var}" + done + word:Hello + word:World # 因为以空格分割,所以,会被拆分为2个词儿 + + * 可以使用双引号把含有空格的内容括起来 + for var in "Hello World"; do + echo "word:${var}" + done + + # 当一个迭代项,两边都有"号时,shell并不会把"当作值的一部分(见上述案例) + * 如果要输出",请使用转义 + for var in \"Hello\" \"World\"; do + echo "word:${var}" + done + + + # 从变量读取列表 + list="KevinBlandy is a pythonista" + list="${list} !" + for var in $list; do + echo "word:${var}" + done + + # 从命令读取值 + file="demo.sh" + for var in `cat ${file}`; do + echo "word:${var}" + done + + # 更改字段的分隔符 + * 默认使用的是:换行,空格,制表符 + * 在脚本中添加代码,指定唯一的默认分隔符 + IFS=$'\n' # 指定分隔符为换行,那么空格和制表符不会起作用 + + * 如果代码量很大,需要在第一个地方修改IFS值,然后忽略这次修改 + * 让脚本的其他地方继续使用IFS的默认值,可以使用一个比较安全简单的操作 + + IFS_OLD=$IFS # 先记录原始值 + IFS=$'\n' # 修改分隔符 + IFS=$IFS_OLD # 在完成迭代操作后,还原为原始值 + + + * 使用冒号(:)分割的值(比如:/etc/passwd 文件) + IFS=: + value="H:e:l:l:o" + for var in ${value}; do + echo "word:${var}" + done + + * 使用多个IFS字符 + IFS=$'\n':;" #使用换行,冒号,分号,双引号 作为分隔符 + + # 用通配符读取目录 + for file in /usr/local/*; do + if [ -d ${file} ]; then + echo "目录:${file}" + else + echo "文件:${file}" + fi + done + + * 如果不在末尾添加 *,那么路径就会被当作一个字符串 + * 也可以添加多个路径,合并为一个list进行迭代 + for file in /usr/local/* /usr/*; do + if [ -d ${file} ]; then + echo "目录:${file}" + else + echo "文件:${file}" + fi + done + + * 就算是目录/文件不存在,for也会尝试去处理列表中的内容 + * 所以在执行操作之前,尽量先判断一下目录/文件是否存在 + +---------------------------- +C 风格的for | +---------------------------- + # 基本语法 + for (( var ; condition ; process )) + do + cmd + + done + + * 简直就是模仿C打造的(会让不会C的shell开发者懵逼) + * 变量的赋值可以有空格 + * 条件中的变量不以$开头 + * 迭代过程的算式没有用 expr 命令过程 + + for ((i = 0;i < 10 ; i++)); do # 很显然,它也可以使用优雅的写法 + echo "${i}" + done + + # 也可以使用多个变量 + for ((i = 0,x = 10 ;i < 10 ; i++, x--)); do + echo "i=${i},x=${x}" + done + + # 遍历数组 + arr=(9 8 7 6 5 4 3 2 1 0) + len=${#arr[*]} + for (( i = 0; i < ${len}; i++ )); do + echo "[${i}] = ${arr[${i}]}" + done + + +---------------------------- +while | +---------------------------- + # 语法 + while test + do + common + done + + * test 和 if 的 test 一样,可以使用shell命令或者 test 命令进行条件测试 + + val=5 + while (( $val > 0 )); do # 也可以使用这种更加优雅的语法和高级的特性 + echo "${val}" + val=$[${val} - 1] + done + + + # while 允许在 while 语句行定义多个测试命令 + * 只有最后一个测试命令的退出状态码会决定是否要退出循环 + + val=5 + while echo $val + [ $val -ge 0 ] # 第二个条件不写在下一行,会被当做 echo 的参数打印的 + do + echo "${val}" + val=$[ ${val} - 1 ] + done + ———————————————————————————— + 5 + 5 + 4 + 4 + 3 + 3 + 2 + 2 + 1 + 1 + 0 + 0 + -1 + ———————————————————————————— + +---------------------------- +until 命令 | +---------------------------- + # 和while的工作方式相反,只有条件不成立,才会执行,条件成立后退出循环 + # 语法 + until test + do + command + done + + # demo + until (( $val < 0 )) ; do # 可以使用高级语法,和优雅的表达式 + echo "${val}" + val=$[${val} - 1] + done + + # 它的条件也可以存在多个条件,但是只有最后一个命令成立时才提出循环 + + + +---------------------------- +控制循环 | +---------------------------- + # 没啥好解释的 + break continue + + # break默认跳出当前循环,也可以给它指定一个参数,跳出第几层循环 + break val + + * val 默认是1,就是跳出当前的循环仅仅 + * 可以修改该值,就可以跳出n层循环(嵌套循环) + + # continue 也跟break一样,可以指定一个参数,用于终止第几层的这一次循环 + continue val + + * val默认是1,表示终止当前层次的这一次循环 + +---------------------------- +处理循环的输出 | +---------------------------- + # 可以在done后添加一个重定向命令实现 + + val=10 + for (( i = 1 ;i < ${val} ; i++)); do + for (( y = 1; y <= ${i}; y ++ )); do + sum=$[${i} * ${y}] + echo -n "${y} x ${i} = ${sum} " + done + echo "" + done > 99.log + + * 循环内部的 echo 不会打印在屏幕,会被输出到 文件 + + # 也可以把循环的结果,通过管道给另一个命令 + val=10 + for (( i = 1 ;i < ${val} ; i++)); do + for (( y = 1; y <= ${i}; y ++ )); do + sum=$[${i} * ${y}] + echo -n "${y} x ${i} = ${sum} " + done + echo "" + done | sort + + + + + + + + + + + + + + + + + + + + + + diff --git "a/Shell/shell-\350\277\220\347\256\227\347\254\246.sh" "b/Shell/shell-\350\277\220\347\256\227\347\254\246.sh" new file mode 100644 index 00000000..baa436fa --- /dev/null +++ "b/Shell/shell-\350\277\220\347\256\227\347\254\246.sh" @@ -0,0 +1,145 @@ +-------------------------------- +数学运算符 | +-------------------------------- + + - * / % + + * 原生的bash不支持数学运算,可以通过其他的命令来实现 + awk + expr(常用) + +-------------------------------- +数值比较 | +-------------------------------- + -eq + * 是否相等,相等返回 true + [ 5 -eq 5 ] + -ne + * 是否相等,不相等返回 true + -gt + * 左 > 右 返回 true + -lt + * 左 < 右 返回 true + -ge + * 左 >= 右 返回 true + -le + * 左 <= 右 返回 true + + * 此类运算符只支持数字,不支持字符串(除非字符串是数字,会被自动的转换) + if [ 1 -eq "1" ] ;then + echo "yes" + fi +-------------------------------- +复合运算 | +-------------------------------- + ! + * 非运算 + [ ! -d "$ZOO_DATADIR" ] + -o + * 或(or)运算 | + -a + * 与(and)运算 & + + && + * 逻辑的AND + [ condition ] && [ condition ] + + if w && NotCommand; then + echo "不会执行的" + fi + + || + * 逻辑OR + [ condition ] || [ condition ] + +-------------------------------- +字符串比较 | +-------------------------------- + = + * 字符串相等,返回true + [ "1" = "1" ] + + != + * 字符串不相等,返回true + [ "1" != "2" ] + + -z + * 字符串长度为0,返回true + [ -z "" ] + + -n + * 字符串长度不为0,返回true + [ -n "1" ] + + > + < + * 判断字符串的大小,比较的是ascii码 + * 大于小于号注意要转义(不然就会被认为是重定向) + + + str + * 直接把字符串当作bool运算 + * 字符串非空,返回true + [ " " ] # false + [ "" ] # false + +-------------------------------- +文件运算符 | +-------------------------------- + -b + * 是否是设备块文件 + [ -b "/usr/local/foo.conf" ] + -c + * 是否字符串设备文件 + -d + * 是否是目录 + [ -d "/usr/local/" ] + -f + * 是否是普通文件(不是目录,不是设备) + -g + * 是否设置了SGID + -k + * 是否设置了粘着位(Sticky Bit) + -r + * 判断文件是否可读 + -p + * 检测文件是否具名管道 + -u + * 是否设置了suid位 + -w + * 是否可写 + -s + * 文件是否为空,size=0 + -e + * 检测文件/目录是否存在 + -x + * 是否存在并可执行 + -O + * .. + -G + * .. + -nt + * 文件1是否比文件2新 + [ f1 -nt f2 ] + -ot + * 文件1是否比文件2旧 + [ f1 -ot f2] + + * 如果目录名称或者文件名称包含空格(在Linux下命名合法) + * 那么就要用双引号把文件名称括起来,不然会异常 + [ -d "${file}" ] + + * 可以一次性判断多个条件 + if [ -f $FILE_NAME -o -d $FILE_NAME ] + +-------------------------------- +expr | +-------------------------------- + # 表达式计算工具 + V1=1 + V2=2 + SUM=`expr $V1 + $V2` + echo $SUM # 3 + echo `expr ${V1} - ${V2}` # -1 + echo `expr $V1 % $V2` # 1 + + diff --git a/Shell/shell.sh b/Shell/shell.sh new file mode 100644 index 00000000..53596d5f --- /dev/null +++ b/Shell/shell.sh @@ -0,0 +1,197 @@ +-------------------------------- +shell | +-------------------------------- + # 第一行指定运行的 bash + #!/bin/bash + + # 运行方式 + * 直接运行(需要设置可执行权限:chdmo 775) + ./hello.sh + + * 以指定的shell运行脚本 + * 这种方式运行的脚本,第一行指定的解释器信息没用 + /bin/bash hello.sh + + # '#'开头的行就是注释,会被解释器忽略 + + # 多行注释 + :< 30 + + * 也可以使用 $() 来代替 `` + sum=$(expr 15 + 15) + echo $sum# -> 30 + + + +-------------------------------- +重定向输入输出 | +-------------------------------- + # 重定向输出 + cmd > target + + * 会把cmd命令执行的结果写入到target文件(文件不存在会创建) + * 如果文件已经存在,那么会直接覆盖 + + # 仅仅追加,不覆盖,使用 >> + cmd >> target + + # 输入重定向 + cmd < target + + * 把target里面的数据重定向给cmd命令 + + + # 内联重定向 + .. + + +-------------------------------- +管道 | +-------------------------------- + # 把一个命令的输出,当作另一个命令的输入 + cmd1 | cmd2 + + * 把cd1的输出结果写入到cmd2 + * 这俩命令并不是先后执行的,而是同时执行的,系统会在内部把他俩连接起来 + + # 常用在more上 + rpm -qa | sort | more + + * 可以把很长的数据通过管道给more + * 从而仅仅显示一小段儿,可以通过翻页的形式来读取 + + +-------------------------------- +数学运算 - expr | +-------------------------------- + # expr 执行乘法,乘号要转义 + expr 4 * 4 # expr: syntax error + expr 4 \* 4 #16 + + * 因为*号是特殊符号,需要使用 \ 来转义 + + # 也可以使用方括号执行数学表达式 + val=$[5 + 5] + echo $val # 10 + echo $[5 + ${val}] #15 + + * 这种方式使用 * 来执行乘法操作,不用转义 + + # bash中的数学运算,仅仅支持整数的运算 + v1=100 + v2=45 + echo $[v1 / v2] # 2 + + * 运算结果仅仅保留整数 + * 另一个shell(zshell)可以实现浮点数的运算 + + # 浮点数的解决方案 + ... + + +-------------------------------- +脚本退出 | +-------------------------------- + # 查看退出的状态码 + echo $? + + * 该命令会返回最后一次shell命令执行的状态码 + + # 状态码 + 0 命令成功结束 + 1 一般性未知错误 + 2 不适合shell命令 + 126 命令不可执行 + 127 没找到命令 + 128 无效的退出参数 + 128+x 与Linux信号x相关的严重错误 + 130 通过ctrl + c终止命令 + 255 正常范围之外的退出状态码 + + # 使用exit终止脚本 + * 默认情况下,shell脚本会以脚本中最后一个命令的退出状态码退出 + * 可以修改这种默认行为,返回自己的状态码 + + exit val # val就是状态码之一 + + * 该值也可以是一个变量 + exit $val + + * 该值最大只能是255,系统会对该值进行模运算 + + + + + diff --git a/Shiro/Shiro-api.java b/Shiro/Shiro-api.java new file mode 100644 index 00000000..e56159a7 --- /dev/null +++ b/Shiro/Shiro-api.java @@ -0,0 +1,7 @@ +AuthenticationToken + |-UsernamePasswordToken + + +securityManager + authenticator + realms \ No newline at end of file diff --git a/Shiro/Shiro-authenticator.java b/Shiro/Shiro-authenticator.java new file mode 100644 index 00000000..427a852f --- /dev/null +++ b/Shiro/Shiro-authenticator.java @@ -0,0 +1,78 @@ +-------------------------------- +authenticator | +-------------------------------- + # Authenticator 的职责是验证用户帐号,是 Shiro API 中身份验证核心的入口点 + public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)throws AuthenticationException; + + * 验证成功,将返回 AuthenticationInfo 验证信息,信息中包含了身份及凭证 + * 如果验证失败将抛出相应的 AuthenticationException 实现 + + # SecurityManager 接口继承了 Authenticator + + # Authenticator 还有一个实现:ModularRealmAuthenticator + * 它会委托多个 Realm 进行验证,验证规则通过 AuthenticationStrategy 接口指定 + * 默认提供 AuthenticationStrategy 的实现: + FirstSuccessfulStrategy + * 只要有一个 Realm 验证成功即可 + * 只返回第一个 Realm 身份验证成功的认证信息,其他的忽略 + + AtLeastOneSuccessfulStrategy + * 只要有一个 Realm 验证成功即可('默认') + * 返回所有 Realm 身份验证成功的认证信息 + + AllSuccessfulStrategy + * 所有 Realm 验证成功才算成功 + * 只要有一个验证失败,抛出异常 + * 返回所有 Realm 身份验证成功的 + + + + # ini配置 Authenticator + authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator //实例化 ModularRealmAuthenticator + securityManager.authenticator=$authenticator + * 指定 securityManager 的 authenticator 实现 + + allSuccessfulStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy //实例化 AllSuccessfulStrategy + securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy + * 设置 securityManager.authenticator 的 authenticationStrategy + + myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1 + myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2 + myRealm3=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm3 + securityManager.realms=$myRealm1,$myRealm3 + * 设置n个realm + + # 代码 + //1、获取 SecurityManager 工厂,此处使用 Ini 配置文件初始化 SecurityManager + Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); + + //2、得到 SecurityManager 实例 并绑定给 SecurityUtils + org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); + SecurityUtils.setSecurityManager(securityManager); + + //3、得到 Subject 及创建用户名/密码身份验证 Token(即用户身份/凭证) + Subject subject = SecurityUtils.getSubject(); + UsernamePasswordToken token = new UsernamePasswordToken("Kevin", "123"); + subject.login(token); + + //4,得到一个身份集合,其包含了 Realm 验证成功的身份信息。校验模式根据 securityManager.authenticator 的 authenticationStrategy 来决定 + //校验失败,抛出异常 + PrincipalCollection principalCollection = subject.getPrincipals(); + + System.out.println(principalCollection); + + # 自定义:AuthenticationStrategy + public interface AuthenticationStrategy { + //在所有 Realm 验证之前调用 + AuthenticationInfo beforeAllAttempts(Collection realms, AuthenticationToken token) throws AuthenticationException; + //在每个 Realm 之前调用 + AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException; + //在每个 Realm 之后调用 + AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t)throws AuthenticationException; + //在所有 Realm 之后调用 + AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException; + } + + * 每个 AuthenticationStrategy 实例都是无状态的,所有每次都通过接口将相应的认证信 + * 息传入下一次流程,通过如上接口可以进行如合并/返回第一个验证成功的认证信息 + diff --git "a/Shiro/Shiro-ini\351\205\215\347\275\256.java" "b/Shiro/Shiro-ini\351\205\215\347\275\256.java" new file mode 100644 index 00000000..b9809c77 --- /dev/null +++ "b/Shiro/Shiro-ini\351\205\215\347\275\256.java" @@ -0,0 +1,114 @@ +---------------------------- +INI閰嶇疆 | +---------------------------- + # 浠嶪NI璧勬簮鏋勫缓SecurityManager + Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); + SecurityManager securityManager = factory.getInstance(); + SecurityUtils.setSecurityManager(securityManager); + + * 鏀寔浠庝换鎰忓崗璁潵鍔犺浇閰嶇疆鏂囦欢 + file: + classpath: + url: + + # INI閰嶇疆涔熷彲浠ラ氳繃缂栫▼鏂瑰紡鏋勫缓 + Ini ini = new Ini(); + ini.setSectionProperty("", "", ""); + + Factory factory = new IniSecurityManagerFactory(ini); + SecurityManager securityManager = factory.getInstance(); + SecurityUtils.setSecurityManager(securityManager); + + * Ini,璺 Properties 宸笉澶氫娇鐢,鏀寔閫氳繃缂栫▼鐨勬柟寮忔潵璁剧疆kv + +---------------------------- +INI閰嶇疆璇﹁В | +---------------------------- + [main] + # 璇ラ儴鍒嗘槸閰嶇疆搴旂敤绋嬪簭鐨凷ecurityManager瀹炰緥鍙婂叾浠讳綍渚濊禆鍏崇郴(濡俁ealm) + # 瀹氫箟涓涓璞 + myRealm = io.springboot.realm.MyRealm //瀹炰緥鍖栦竴涓璞 + + * 濡傛灉瀵硅薄瀹炵幇浜:org.apache.shiro.util.Nameable 鎺ュ彛 + * 閭d箞鍦ㄥ疄渚嬪寲瀹屾垚鍚,浼氭墽琛岃鎺ュ彛鐨:setName(String str) 鏂规硶,鎶婇厤缃睘鎬у悕绉(myRealm)浼犻掕繘鏉 + + # 璁剧疆瀵硅薄鐨勫睘鎬,Shiro榛樿浣跨敤Apache Commons BeanUtils鏉ヨ缃繖浜涘睘鎬 + myRealm.name = KevinBlandy //璁剧疆璇ュ璞$殑username灞炴 + myRealm.age = 23 //璁剧疆瀵硅薄鐨刟ge灞炴 + + * 璁剧疆鐨勫间笉鏄師濮嬬殑,鑰屾槸鍙︿竴涓璞,鍙互浣跨敤缇庡厓绗﹀彿 $ 鏉ュ紩鐢ㄥ厛鍓嶅畾涔夌殑瀹炰緥 + sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher + myRealm.credentialsMatcher = $sha256Matcher //璁剧疆 myRealm 鐨 credentialsMatcher 灞炴у间负 sha256Matcher 瀵硅薄 + + * 宓屽灞炴ц缃(灞炴у鑸) + securityManager.sessionManager.globalSessionTimeout = 1800000 //鐩稿綋浜,securityManager.getSessionManager().setGlobalSessionTimeout(1800000); + + * 瀛楄妭鏁扮粍鍊艰缃 + * 鍙互鎸囧畾涓築ase64缂栫爜鐨勫瓧绗︿覆(榛樿鍊) + * 涔熷彲浠ユ寚瀹氫负鍗佸叚杩涘埗缂栫爜鐨勫瓧绗︿覆 + securityManager.rememberMeManager.cipherKey = kPH+bIxk5D2deZiIxcaaaA== //base64缂栫爜 + securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008 //16杩涘埗瀛楃涓 + + * 闆嗗悎涓巑ap灞炴 + * 闆嗗悎,瀵逛簬闆嗗悎鍜屽垪琛,鍙渶鎸囧畾閫楀彿鍒嗛殧鐨勪竴缁勫兼垨瀵硅薄寮曠敤鍗冲彲 + sessionListener1 = com.company.my.SessionListenerImplementation //瀹氫箟淇╁璞 + sessionListener2 = com.company.my.other.SessionListenerImplementation + + securityManager.sessionManager.sessionListeners = $sessionListener1, $sessionListener2 //sessionListeners 鏄竴涓泦鍚,璁剧疆浜嗕笂闈㈠畾涔夌殑淇╁睘鎬 + + * Map + object1 = com.company.some.Class //瀹氫箟涓涓猳1 + object2 = com.company.another.Class //瀹氫箟涓涓猳2 + + anObject = some.class.with.a.Map.property //瀹炰緥鍖栦竴涓璞 + + anObject.mapProperty = key1:$object1, key2:$object2 //璁剧疆璇ュ璞$殑mapProperty涓轰竴涓猰ap瀵硅薄,key1 = o1,key2=o2 + + * 涔熷彲浠ヤ娇鐢ㄥ叾浠栧璞′綔涓洪敭 + anObject.map = $objectKey1:$objectValue1, $objectKey2:$objectValue2 + + # 娉ㄦ剰鐐 + * 閲嶅悕瀵硅薄浼氳鐩 + myRealm = com.company.security.MyRealm + myRealm = com.company.security.DatabaseRealm //璇ュ璞′細瑕嗙洊涓婁竴涓璞 + + * 榛樿SecurityManager + * securityManager,瀹炰緥鏄竴涓壒娈婄殑瀹炰緥,宸茬粡瀹炰緥鍖 + * 濡傛灉闇瑕佽嚜宸卞疄鐜,閭d箞鎸夌収涓婇潰瀹氫箟鐨勬柟娉,瑕嗗啓鍗冲彲 + + [users] + # 鍙瀹氫箟浜嗛潪绌虹殑[users]鎴朳urls]閮ㄥ垎,灏变細鑷姩鍒涘缓鍚嶄负('iniRealm')鐨刼rg.apache.shiro.realm.text.IniRealm 瀹炰緥 + * 鍦╗main]閰嶇疆涓嬪彲浠ヤ娇鐢: iniRealm 鏉ュ紩鐢ㄥ畠 + + # 瀹氫箟涓缁勯潤鎬佺殑鐢ㄦ埛甯愭埛 + * 閰嶇疆鏍煎紡:鐢ㄦ埛鍚 = 瀵嗙爜,瑙掕壊1,瑙掕壊2,....瑙掕壊n + admin = secret + lonestarr = vespa, goodguy, schwartz + darkhelmet = ludicrousspeed, badguy, schwartz + + * 鍔犲瘑瀵嗙爜,鍙互浣跨敤MD5,Sha1,Sha256绛夊鍏惰繘琛屽姞瀵 + [main] + sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher //瀹炰緥鍖栦竴涓 Sha256 鐨凪atcher + iniRealm.credentialsMatcher = $sha256Matcher //璁剧疆iniRealm 鐨 credentialsMatcher + + [users] + user1 = 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b //鍒涘缓涓涓敤鎴,瀹冪殑瀵嗙爜鏄粡杩噑ha256杩愮畻鐨勫瘑鏂 + + * 杩樻湁 Md5CredentialsMatcher 绛塎atcher鍙互鍙傝冧娇鐢 + * 濡傛灉鐢ㄦ埛鐨勫瘑鐮佸瓧绗︿覆鏄疊ase64缂栫爜,鑰屼笉鏄粯璁ょ殑鍗佸叚杩涘埗,闇瑕佽缃甅atcher鐨剆toredCredentialsHexEncoded灞炴 + [main] + # true = hex, false = base64: + sha256Matcher.storedCredentialsHexEncoded = false + + + [roles] + # 鏉ュ畾涔夎鑹插埌鏉冮檺鐨勯敭/鍊兼槧灏 + * 閰嶇疆鏍煎紡:瑙掕壊鍚嶇О = 鏉冮檺1,鏉冮檺2,鏉冮檺3,...鏉冮檺N + admin = * + manager = create,update,delete + + # 娌℃湁鏉冮檺鐨勮鑹 + * 瑙掕壊涓嶉渶瑕佹潈闄愬叧鑱,鍒欎笉闇瑕佸湪[roles]閮ㄥ垎鍒楀嚭 + * 鍦╗users]閮ㄥ垎涓畾涔夎鑹插悕绉板氨浼氶粯璁ゅ垱寤鸿鑹(roles涓笉瀛樺湪) + + [urls] diff --git a/Shiro/Shiro-realm.java b/Shiro/Shiro-realm.java new file mode 100644 index 00000000..e3ebd781 --- /dev/null +++ b/Shiro/Shiro-realm.java @@ -0,0 +1,61 @@ +---------------------------- +realm | +---------------------------- + # 顶层接口 + public interface Realm { + //返回唯一的 realm 名称 + String getName(); + //当前realm是否支持此token + boolean supports(AuthenticationToken token); + //根据token,获取认证信息 + AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; + } + + + # ini 配置一个或者多个realm + [main] + myRealm1=practice.shiro.realm.MyRealm1 + myRealm2=practice.shiro.realm.MyRealm2 + securityManager.realms=$myRealm1,$myRealm2 + + * securityManager 会按照 realms 指定的顺序进行身份认证 + * 如果删除"securityManager.realms=$myRealm1,$myRealm2",那么 securityManager 会按照 realm 声明的顺序进行使用(自动发现) + * 但是一旦手动的设置了:securityManager.realms 属性,那么自动发现会失效,将严格按照 securityManager.realms 声明的顺序来进行身份验证 + + + # Shiro提供的默认Realm + Realm + | + CachingRealm + | + CachingRealm + | + AuthenticatingRealm + | + AuthorizingRealm + | + |---------------|--------------|---------------| + SimpleAccountRealm JDBCRealm JndiLdapRealm AbstractLdapRealm + | | + TextConfigurationRealm ActiveDirectoryRealm + |-------------| + PropertiesRealm IniRealm + + * 一般继承 AuthorizingRealm(授权)即可 + + * IniRealm + * [users]部分指定用户名/密码及其角色 + * [roles]部分指定角色即权限信息 + + * PropertiesRealm + * user.username=password,role1,role2 指定用户名/密码及其角色 + * role.role1=permission1,permission2 指定角色及权限信息 + + * JdbcRealm + * 通过jdbc来进行验证和授权 + + +---------------------------- +JdbcRealm 的使用 | +---------------------------- + \ No newline at end of file diff --git "a/Shiro/Shiro-\345\205\245\351\227\250.java" "b/Shiro/Shiro-\345\205\245\351\227\250.java" index 0c2fb630..420c015a 100644 --- "a/Shiro/Shiro-\345\205\245\351\227\250.java" +++ "b/Shiro/Shiro-\345\205\245\351\227\250.java" @@ -1,6 +1,9 @@ ---------------------------- Shiro-入门 | ---------------------------- + # 文档 + http://shiro.apache.org/reference.html + # shiro 简介 来自于Apache的神器,是Apache的顶级项目.是一个易用强大的JAVA安全框架,提供认证,授权,加密,会话管理等功能 @@ -33,6 +36,7 @@ WebSuport --针对于WEB架构的一些功能 Caching --缓存 Cincurrency --多线程 + RunAs -- 在得到授权的情况下,允许访问其他用户的资源 RemmberMe --记住用户身份... ... # shiro 三大核心组键 diff --git "a/Shiro/Shiro-\346\216\210\346\235\203.java" "b/Shiro/Shiro-\346\216\210\346\235\203.java" new file mode 100644 index 00000000..d6445412 --- /dev/null +++ "b/Shiro/Shiro-\346\216\210\346\235\203.java" @@ -0,0 +1,84 @@ +------------------------ +授权 | +------------------------ + # 角色的校验方式 + 1,代码 + Subject subject = SecurityUtils.getSubject(); + if(subject.hasRole(“admin”)) { + //有权限 + } else { + //无权限 + } + + 2,注解式子 + @RequiresRoles("admin") + public void hello() { + //有权限 + } + + 3,JSP标签 + + + + + + # 授权 + * 在ini中配置用户的信息和授权角色 + [user] + kevin=123456,admin,manager + + * 判断用户是否有指定的角色 + boolean subject().hasRole("admin") + * 是否有指定的角色 + boolean subject().hasAllRoles(Arrays.asList("admin", "manager")) + * 是否有指定的所有角色 + boolean[] subject().hasRoles(Arrays.asList("role1", "role2", "role3")) + * 对所有角色进行校验,返回的是一个校验结果集 + + subject().checkRole("admin"); + * 断言有指定的角色,如果没有抛出异常 + subject().checkRoles("admin", "manager"); + * 断言有指定的所有角色,如果任意一个没有,抛出异常 + + + # 基于资源的访问控制(显示角色) + * ini配置 + [users] + Kevin=123,role1,role2 + Litch=123,role1 + + [roles] + role1=user:create,user:update + role2=user:create,user:delete + + * 用户:密码,角色1,角色2 + * 角色名称:权限1,权限2 (资源标识符:操作) + + * 断言 + subject().isPermitted("user:create") + * 判断是否具备指定的权限 + subject().isPermittedAll("user:update", "user:delete") + * 判断是否具备指定的所有权限 + subject().isPermitted("user:view") + * 判断是否不具备指定的权限 + + subject().checkPermission("user:create") + * 断言有权限 + subject().checkPermissions("user:delete", "user:update") + * 断言有指定的所有权限 + + + * 字符串通配符权限 + * "资源标识符:操作:对象实例ID" 即对哪个资源的哪个实例可以进行什么操作 + * 单个资源,单个权限 + subject().checkPermissions("system:user:update"); //用户拥有资源"system:user"的"update"权限 + + * 单个资源多个权限 + role=system:user:update,system:user:delete + subject().checkPermissions("system:user:update", "system:user:delete"); //用户拥有资源“system:user”的“update”和“delete”权限 + + role2="system:user:update,delete" + subject().checkPermissions("system:user:update,delete"); + + * 通过"system:user:update,delete" 验证 "system:user:update, system:user:delete"是没问题的 + * 但是反过来是规则不成立 \ No newline at end of file diff --git "a/Shiro/Shiro-\346\225\264\345\220\210Spring.java" "b/Shiro/Shiro-\346\225\264\345\220\210Spring.java" index f7987da0..f6b1afac 100644 --- "a/Shiro/Shiro-\346\225\264\345\220\210Spring.java" +++ "b/Shiro/Shiro-\346\225\264\345\220\210Spring.java" @@ -8,18 +8,21 @@ ------------------------ # 在web.xml中配置一个filter - - shiro + + shiroFilter org.springframework.web.filter.DelegatingFilterProxy + + targetFilterLifecycle + true + + - shiro - /* - REQUEST - FORWARD - INCLUDE - ERROR - */ + shiroFilter + /* */ + + + * 这个拦截器会代替Shiro的filter - # 这个拦截器会代替Shiro的filter + # diff --git "a/Snakeyaml/snakeyaml-\345\205\245\351\227\250.java" "b/Snakeyaml/snakeyaml-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..0b564c77 --- /dev/null +++ "b/Snakeyaml/snakeyaml-\345\205\245\351\227\250.java" @@ -0,0 +1,116 @@ +-------------------------------- +Snakeyaml | +-------------------------------- + # Maven + + org.yaml + snakeyaml + 1.23 + + + # 网站 + https://bitbucket.org/asomov/snakeyaml/wiki/Home + + # 入门 + Yaml yaml = new Yaml(); + InputStream inputStream = Main.class.getResourceAsStream("/application.yml"); + Object load = yaml.load(inputStream); + System.out.println(load); //{name=KevinBlandy} + System.out.println(load.getClass().getName()); //java.util.LinkedHashMap + + # Map 结构对象 + - 对象:java.util.LinkedHashMap + - 数组:java.util.ArrayList + +-------------------------------- +List | +-------------------------------- + - Kevin + - Litch + - Rocco + + + Yaml yaml = new Yaml(); + List list = yaml.load(Main.class.getResourceAsStream("/application.yml")); + System.out.println(list); //[Kevin, Litch, Rocco] + System.out.println(list.getClass().getName()); //java.util.ArrayList + + +-------------------------------- +Map | +-------------------------------- + name: KevinBlandy + age: 23 + gender: boy + + Yaml yaml = new Yaml(); + Map map = yaml.load(Main.class.getResourceAsStream("/application.yml")); + System.out.println(map); //{name=KevinBlandy, age=23, gender=boy} + System.out.println(map.getClass().getName()); //java.util.LinkedHashMap + +-------------------------------- +复杂的解析 | +-------------------------------- +user: + name: KevinBlandy + age: 23 + skills: + - Java + - Python + - C + - Javascript + + + Yaml yaml = new Yaml(); + Map map = yaml.load(Main.class.getResourceAsStream("/application.yml")); + System.out.println(map); //{user={name=KevinBlandy, age=23, skills=[Java, Python, C, Javascript]}} + System.out.println(map.getClass().getName()); //java.util.LinkedHashMap + + Map user = (Map) map.get("user"); + String name = (String) user.get("name"); + System.out.println(name); //KevinBlandy + + List skills = (List) user.get("skills"); + System.out.println(skills); //[Java, Python, C, Javascript] + +-------------------------------- +把 yml 解析对象渲染 | +-------------------------------- + Yaml yaml = new Yaml(); + String user = yaml.loadAs(Main.class.getResourceAsStream("/application.yml"),User.class); + + +-------------------------------- +把对象渲染为 yml | +-------------------------------- + Yaml yaml = new Yaml(); + Map map = new HashMap<>(); + map.put("name", "KevinBlandy"); + map.put("age", "23"); + map.put("gender", "男"); + yaml.dump(map, new PrintWriter(System.out)); //{gender: 男, name: KevinBlandy, age: '23'} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Spring-boot/actuator/actuator-config.java b/Spring-boot/actuator/actuator-config.java new file mode 100644 index 00000000..db5d5760 --- /dev/null +++ b/Spring-boot/actuator/actuator-config.java @@ -0,0 +1,35 @@ +management: + endpoints: + web: + # 访问端点的base uri + base-path: /actuator + # 配置指定端点的映射 uri + path-mapping: + health: health + # 跨域的支持(CorsEndpointProperties) + cors: + allow-credentials: true + allowed-methods: * + max-age: 1800s + # 外暴露的端点 + exposure: + exclude: + include: + - '*' + jmx: + exposure: + exclude: + include: + + # 是否启用默认的端点(默认 true),禁用的端点将从应用程序上下文中完全删除 + enabled-by-default: true + + # 每个端点的配置 + endpoint: + shutdown: + enabled: true + + beans: + enabled: true + cache: + time-to-live: 10s \ No newline at end of file diff --git "a/Spring-boot/actuator/actuator-\345\217\227\345\210\260\344\277\235\346\212\244\347\232\204\347\253\257\347\202\271.java" "b/Spring-boot/actuator/actuator-\345\217\227\345\210\260\344\277\235\346\212\244\347\232\204\347\253\257\347\202\271.java" new file mode 100644 index 00000000..e69de29b diff --git "a/Spring-boot/actuator/actuator-\350\207\252\345\256\232\344\271\211\347\253\257\347\202\271.java" "b/Spring-boot/actuator/actuator-\350\207\252\345\256\232\344\271\211\347\253\257\347\202\271.java" new file mode 100644 index 00000000..80e99a4e --- /dev/null +++ "b/Spring-boot/actuator/actuator-\350\207\252\345\256\232\344\271\211\347\253\257\347\202\271.java" @@ -0,0 +1,17 @@ +------------------------------------ +自定义端点 | +------------------------------------ + # 注解 + @Endpoint + + @ReadOperation + @WriteOperation + @DeleteOperation + + @WebEndpoint + @JmxEndpoint + + @EndpointWebExtension + @EndpointJmxExtension + + @ServletEndpoint \ No newline at end of file diff --git a/Spring-boot/actuator/actuator.java b/Spring-boot/actuator/actuator.java new file mode 100644 index 00000000..e5f42710 --- /dev/null +++ b/Spring-boot/actuator/actuator.java @@ -0,0 +1,46 @@ +---------------------------- +actuator | +---------------------------- + # 参考文档 + https://blog.csdn.net/alinyua/article/details/80009435 + https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready + + # 依赖 + + org.springframework.boot + spring-boot-starter-actuator + + + + + # 相关的配置类 + WebEndpointProperties + + # 可以访问的的端点 + ID 描述 默认启用 + auditevents 显示当前应用程序的审计事件信息 Yes + beans 显示一个应用中所有Spring Beans的完整列表 Yes + conditions 显示配置类和自动配置类(configuration and auto-configuration classes)的状态及它们被应用或未被应用的原因 Yes + configprops 显示一个所有@ConfigurationProperties的集合列表 Yes + env 显示来自Spring的 ConfigurableEnvironment的属性 Yes + flyway 显示数据库迁移路径,如果有的话 Yes + health 显示应用的健康信息(当使用一个未认证连接访问时显示一个简单的'status',使用认证连接访问则显示全部信息详情) Yes + info 显示任意的应用信息 Yes + liquibase 展示任何Liquibase数据库迁移路径,如果有的话 Yes + metrics 展示当前应用的metrics信息 Yes + mappings 显示一个所有@RequestMapping路径的集合列表 Yes + scheduledtasks 显示应用程序中的计划任务 Yes + sessions 允许从Spring会话支持的会话存储中检索和删除(retrieval and deletion)用户会话。使用Spring Session对反应性Web应用程序的支持时不可用 Yes + shutdown 允许应用以优雅的方式关闭(默认情况下不启用) No + threaddump 执行一个线程dump Yes + + heapdump 返回一个GZip压缩的hprof堆dump文件 Yes + jolokia 通过HTTP暴露JMX beans(当Jolokia在类路径上时,WebFlux不可用) Yes + logfile 返回日志文件内容(如果设置了logging.file或logging.path属性的话),支持使用HTTP Range头接收日志文件内容的部分信息 Yes + prometheus 以可以被Prometheus服务器抓取的格式显示metrics信息 Yes + + + + + + diff --git a/Spring-boot/application.yml b/Spring-boot/application.yml new file mode 100644 index 00000000..e4abcf5f --- /dev/null +++ b/Spring-boot/application.yml @@ -0,0 +1,163 @@ +#------------------------ +# 常用的配置 +#------------------------ + +# 响应压缩 +server: + compression: + enabled: true + mime-types: + - application/json + - application/xml + - application/javascript + - text/html + - text/xml + - text/plain + - text/css + - text/javascript + min-response-size: 2048 + +# logback日志 +logging: + config: classpath:logback.xml + +spring: + application: + name: Javaweb开发者社区 + profiles: + active: dev + http: + encoding: + charset: utf-8 + enabled: true + servlet: + multipart: + enabled: true + max-file-size: 100MB + max-request-size: 1000MB + location: D:\\temp + file-size-threshold: 10MB + mvc: + favicon: + enabled: true + date-format: yyyy-MM-dd HH:mm:ss + throw-exception-if-no-handler-found: true + static-path-pattern: /static/** + resources: + static-locations: + - classpath:/static/ + - file:${system.local.image-folder} + cache: + type: JCACHE + freemarker: + enabled: true + request-context-attribute: request + expose-request-attributes: true + expose-session-attributes: true + suffix: .ftl + content-type: text/html + # 开发环境模版,模板引擎不缓存 + cache: false + template-loader-path: + - classpath:/templates/ + charset: UTF-8 + template-encoding: UTF-8 + settings: + datetime_format: yyyy-MM-dd HH:mm:ss + # 模板引擎检测模板变化时间差 + # template_update_delay: 0 + + +mybatis: + config-location: classpath:mybatis/mybatis-config.xml + mapper-locations: + - classpath:mapper/**/*-mapper.xml + - classpath:mapper/**/*-mapper-ext.xml + +#------------------------ +# 不同环境配置 +#------------------------ + + +server: + port: 443 + servlet: + context-path: / + ssl: + enabled: true + key-store: classpath:ssl/localhost.keystore + key-store-type: PKCS12 + key-store-password: 123456 + http2: + enabled: true + +system: + host: localhost + session: + key: 0C1B55C2E72C11E8B06700163E084A12 + local: + image-folder: D:\\static + +aliyun: + oss: + access-key: + secret-key: + endpoint: + bucket: + prefix: https://${aliyun.oss.bucket}.${aliyun.oss.endpoint} + +tencent: + captcha: + verify-url: https://ssl.captcha.qq.com/ticket/verify + account: + app-id: + app-secret-key: + forum: + app-id: + app-secret-key: 0n36auU6HzzaGJwHRynKvWg** + email: + app-id: + app-secret-key: + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/javaweb?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=GMT%2b8 + username: root + password: root + initialSize: 2 + filters: stat + connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000 + redis: + database: 0 + host: localhost + port: 6379 + password: + timeout: 2000 + lettuce: + pool: + max-active: 8 + max-wait: -1 + max-idle: 8 + min-idle: 0 + cache: + jcache: + config: classpath:ehcache/ehcache-dev.xml + mail: + host: smtp.exmail.qq.com + username: + password: + sender: Javaweb开发者社区 + port: 587 + default-encoding: UTF-8 + protocol: smtp + properties: + mail: + smtp: + connectiontimeout: 5000 + timeout: 3000 + writetimeout: 5000 + auth: true + starttls: + enable: true + required: true diff --git a/Spring-boot/component/ApplicationContextAware.java b/Spring-boot/component/ApplicationContextAware.java new file mode 100644 index 00000000..acfabcc8 --- /dev/null +++ b/Spring-boot/component/ApplicationContextAware.java @@ -0,0 +1,9 @@ +------------------------------ +ApplicationContextAware | +------------------------------ + # Spring会调用该接口的实现,把ApplicatioContext注入进来 + + public interface ApplicationContextAware extends Aware { + void setApplicationContext(ApplicationContext applicationContext) throws BeansException; + } + diff --git a/Spring-boot/component/ApplicationListener.java b/Spring-boot/component/ApplicationListener.java new file mode 100644 index 00000000..017b6184 --- /dev/null +++ b/Spring-boot/component/ApplicationListener.java @@ -0,0 +1,103 @@ +-------------------------------- +Application的Event事件 | +-------------------------------- + # Spring 的核心是 ApplicationContext,它负责管理 beans 的完整生命周期 + + # 当加载 beans 时,ApplicationContext 发布某些类型的事件 + * 当上下文启动时,ContextStartedEvent 发布 + * 当上下文停止时,ContextStoppedEvent 发布 + + # 通过 ApplicationEvent 类和 ApplicationListener 接口来提供在 ApplicationContext 中处理事件 + # 如果一个 bean 实现 ApplicationListener,那么每次 ApplicationEvent 被发布到 ApplicationContext 上,那个 bean 会被通知 + # Spring 提供了以下的标准事件 + 1,ContextRefreshedEvent + * ApplicationContext 被初始化或刷新时,该事件被发布 + * 这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生 + + 2,ContextStartedEvent + * 当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布 + * 你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序 + + 3,ContextStoppedEvent + * 当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件 + * 你可以在接受到这个事件后做必要的清理的工作 + + 4,ContextClosedEvent + * 当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布 + * 一个已关闭的上下文到达生命周期末端;它不能被刷新或重启 + + 5,RequestHandledEvent + * 这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务 + # 步骤 + 1,自定义类实现 ApplicationListener + 2,根据泛型:ApplicationEvent来决定要监听的事件 + 3,把该类加入IOC + + # Demo + public class CStartEventHandler implements ApplicationListener{ + public void onApplicationEvent(ContextStartedEvent event) { + System.out.println("ContextStartedEvent Received"); + } + } + + # 部分系统预定义的事件 + ContextRefreshedEvent + ServletWebServerInitializedEvent + ApplicationStartedEvent + ApplicationReadyEvent + PayloadApplicationEvent + + +-------------------------------- +自定义事件,与发布 | +-------------------------------- + # 事件对象实现接口:ApplicationEvent + import org.springframework.context.ApplicationEvent; + public class MyEvent extends ApplicationEvent { + private static final long serialVersionUID = -2362115341823186646L; + + // 构造函数可以设置一个事件对象,可以在监听器中获取到这个事件对象 + public MyEvent(Object source) { + super(source); + } + } + + + # 实现监听器接口: + import org.springframework.context.ApplicationListener; + import org.springframework.stereotype.Component; + + @Component + public class SpringApplcationListenner implements ApplicationListener{ + + @Override + public void onApplicationEvent(MyEvent event) { + System.err.println(event.getSource() + ":" + event.getClass().getName()); + } + } + + * 泛型的定义,就是我们自定义的事件对象 + + # 使用 ApplicationContext 对象主动的发布事件 + ApplicationContext applicationContext = SpringApplication.run(PasteApplication.class,args); + // 通过 publishEvent(); 发布事件 + applicationContext.publishEvent(new MyEvent("你好啊,我是事件")); + + + # 事件发布后,只要是符合泛型对象的监听器(包括子类),都会捕获到事件 + import org.springframework.boot.context.event.ApplicationReadyEvent; + import org.springframework.context.ApplicationEvent; + import org.springframework.stereotype.Component; + + + // 不声明没有泛型,这样就会捕获到所有实现了 ApplicationEvent 接口的事件对象 + @Component + public class ApplicationListener implements org.springframework.context.ApplicationListener.ApplicationListener{ + + @Override + public void onApplicationEvent(ApplicationEvent event) { + System.err.println(event.getSource() + ":" + event.getClass().getName()); + } + } + + diff --git a/Spring-boot/component/BeanFactoryPostProcessor.java b/Spring-boot/component/BeanFactoryPostProcessor.java new file mode 100644 index 00000000..03478d3d --- /dev/null +++ b/Spring-boot/component/BeanFactoryPostProcessor.java @@ -0,0 +1,17 @@ +------------------------------------ +BeanFactoryPostProcessor | +------------------------------------ + # bean工厂的bean属性处理容器,可以管理bean工厂内所有的beandefinition(未实例化)数据可以随心所欲的修改属性 + + import org.springframework.beans.BeansException; + import org.springframework.beans.factory.config.BeanFactoryPostProcessor; + import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + import org.springframework.stereotype.Component; + + @Component + public class ApplicationBeanFactoryPostProcessor implements BeanFactoryPostProcessor{ + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + } + } diff --git a/Spring-boot/component/BeanPostProcessor.java b/Spring-boot/component/BeanPostProcessor.java new file mode 100644 index 00000000..1a5bd0bc --- /dev/null +++ b/Spring-boot/component/BeanPostProcessor.java @@ -0,0 +1,24 @@ +----------------------------------- +BeanPostProcessor | +----------------------------------- + # Bean 生命周期中的一个接口 + + # 具有两个方法,Spring在Bean初始化之前,初始化之后调调用该实现,来对Bean进行'处理Processor' + + import org.springframework.beans.BeansException; + import org.springframework.beans.factory.config.BeanPostProcessor; + + @Component + public class ApplicationBeanPostProcessor implements BeanPostProcessor { + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + // 在初始化之后对Bean进行处理 + return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName); + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + // 在初始化之前对Bean进行处理 + return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); + } + } diff --git a/Spring-boot/component/ClassPathBeanDefinitionScanner.java b/Spring-boot/component/ClassPathBeanDefinitionScanner.java new file mode 100644 index 00000000..503bd210 --- /dev/null +++ b/Spring-boot/component/ClassPathBeanDefinitionScanner.java @@ -0,0 +1,30 @@ +------------------------------- +ClassPathBeanDefinitionScanner | +------------------------------- + # SpringBoot项目中或者 Spring项目中配置 + + # 那么在IOC 容器初始化阶段(调用beanFactoryPostProcessor阶段)就会采用 ClassPathBeanDefinitionScanner 进行扫描包下 所有类 + + # 并将符合过滤条件的类注册到IOC 容器内 + * Mybatis 的Mapper注册器(ClassPathMapperScanner) 是同过继承 ClassPathBeanDefinitionScanner, 并且自定义了过滤器规则来实现的 + + # ClassPathBeanDefinitionScanner 作用就是将指定包下的类通过一定规则过滤后 将Class 信息包装成 BeanDefinition 的形式注册到IOC容器中 + + # 一般用于自定义扫描器 + * 把标识了自定义注解的类,添加到IOC中 + + # 类构造方法 + ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry); + ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters); + ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment); + ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment, @Nullable ResourceLoader resourceLoader); + + + * 子类继承的时候,必须指定要手动声明要调用的父类构造方法: super + + +------------------------------- +自定义扫描器 | +------------------------------- + + \ No newline at end of file diff --git a/Spring-boot/component/DisposableBean.java b/Spring-boot/component/DisposableBean.java new file mode 100644 index 00000000..29201d5f --- /dev/null +++ b/Spring-boot/component/DisposableBean.java @@ -0,0 +1,8 @@ +------------------------------------ +BeanPostProcessor | +------------------------------------ + # Bean 生命周期中的一个接口 + # 如果Bean实现了该接口,在ApplicationContext关闭的时候,会调用抽象方法 + public interface DisposableBean { + void destroy() throws Exception; + } diff --git a/Spring-boot/component/Formatter.java b/Spring-boot/component/Formatter.java new file mode 100644 index 00000000..94ea8be2 --- /dev/null +++ b/Spring-boot/component/Formatter.java @@ -0,0 +1,9 @@ +---------------------- +Formatter | +---------------------- + # 一个数据类型的转换接口 + * 可以用于请求参数的格式化 + + # 抽象接口 + String print(T object, Locale locale); + T parse(String text, Locale locale) throws ParseException; \ No newline at end of file diff --git a/Spring-boot/component/HandlerMethodArgumentResolver.java b/Spring-boot/component/HandlerMethodArgumentResolver.java new file mode 100644 index 00000000..ca43dbb2 --- /dev/null +++ b/Spring-boot/component/HandlerMethodArgumentResolver.java @@ -0,0 +1,21 @@ +------------------------------ +HandlerMethodArgumentResolver | +------------------------------ + # 实现该接口可以自定义参数解析 + public interface HandlerMethodArgumentResolver { + boolean supportsParameter(MethodParameter parameter); + + @Nullable + Object resolveArgument(MethodParameter parameter, Message message) throws Exception; + } + + # 在mvc的配置中, 设置 + @Configuration + public class WebMvcConfiguration implements WebMvcConfigurer { + @Override + public void addArgumentResolvers(List resolvers) { + // 添加自己的实现类 + } + } + + \ No newline at end of file diff --git a/Spring-boot/component/WebMvcConfigurer.java b/Spring-boot/component/WebMvcConfigurer.java new file mode 100644 index 00000000..bc4f89ba --- /dev/null +++ b/Spring-boot/component/WebMvcConfigurer.java @@ -0,0 +1,48 @@ +---------------------------- +WebMvcConfigurer | +---------------------------- + # WEBMVC的配置接口 + + public interface WebMvcConfigurer { + + default void configurePathMatch(PathMatchConfigurer configurer) {} + + default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {} + + default void configureAsyncSupport(AsyncSupportConfigurer configurer) {} + + default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {} + + default void addFormatters(FormatterRegistry registry) {} + + default void addInterceptors(InterceptorRegistry registry) {} + * 配置拦截器 + + default void addResourceHandlers(ResourceHandlerRegistry registry) {} + + default void addCorsMappings(CorsRegistry registry) {} + * cors跨域配置 + + default void addViewControllers(ViewControllerRegistry registry) {} + + default void configureViewResolvers(ViewResolverRegistry registry) {} + + default void addArgumentResolvers(List resolvers) {} + + default void addReturnValueHandlers(List handlers) {} + + default void configureMessageConverters(List> converters) {} + + default void extendMessageConverters(List> converters) {} + + default void configureHandlerExceptionResolvers(List resolvers) {} + + default void extendHandlerExceptionResolvers(List resolvers) {} + + @Nullable + default Validator getValidator() {return null;} + + @Nullable + default MessageCodesResolver getMessageCodesResolver() {return null;} + + } diff --git a/Spring-boot/jpa/jpa-properties.yml b/Spring-boot/jpa/jpa-properties.yml new file mode 100644 index 00000000..dec0de31 --- /dev/null +++ b/Spring-boot/jpa/jpa-properties.yml @@ -0,0 +1,3 @@ + +# 配置类 + JpaProperties diff --git a/Spring-boot/jpa/jpa-querydsl.java b/Spring-boot/jpa/jpa-querydsl.java new file mode 100644 index 00000000..1c2106de --- /dev/null +++ b/Spring-boot/jpa/jpa-querydsl.java @@ -0,0 +1,9 @@ +------------------------ +Querydsl | +------------------------ + # 官网 + http://www.querydsl.com/ + + + # 相关注解 + @QuerydslPredicate \ No newline at end of file diff --git a/Spring-boot/jpa/jpa-repository.java b/Spring-boot/jpa/jpa-repository.java new file mode 100644 index 00000000..7fecab06 --- /dev/null +++ b/Spring-boot/jpa/jpa-repository.java @@ -0,0 +1,83 @@ +---------------------- +CrudRepository | +---------------------- + +public interface CrudRepository extends Repository { + // 保存实体 + S save(S entity); + // 批量保存 + Iterable saveAll(Iterable entities); + /** + * 自增长的id会回写到对象 + **/ + + // 根据id检索 + Optional findById(ID id); + + // 根据id判断是否存在 + boolean existsById(ID id); + + // 检索所有 + Iterable findAll(); + // 根据多个id检索 + Iterable findAllById(Iterable ids); + + // 总记录数量 + long count(); + + + // 根据id 删除 + void deleteById(ID id); + // 删除指定的实体(会根据id删除) + void delete(T entity); + // 删除所有(会根据id删除) + void deleteAll(Iterable entities); + /*** + * 执行删除的时候,会先根据ID检索记录 + * 如果记录不存在(如果执行的是:deleteById,会抛出异常 EmptyResultDataAccessException ),先插入,再删除 + * 如果记录已经存在,直接删除 + ***/ + + + + // 清空表 + void deleteAll(); +} +---------------------- +JpaRepository | +---------------------- + public interface JpaRepository extends PagingAndSortingRepository, QueryByExampleExecutor { + + List findAll(); + + List findAll(Sort sort); + + List findAllById(Iterable ids); + + Iterable findAll(Sort sort); + + Page findAll(Pageable pageable); + + + List saveAll(Iterable entities); + + void flush(); + + S saveAndFlush(S entity); + + void deleteInBatch(Iterable entities); + + void deleteAllInBatch(); + + T getOne(ID id); + + @Override + List findAll(Example example); + @Override + List findAll(Example example, Sort sort); + + Optional findOne(Example example); + Page findAll(Example example, Pageable pageable); + long count(Example example); + boolean exists(Example example); + } diff --git "a/Spring-boot/jpa/jpa-\345\220\204\347\247\215\346\243\200\347\264\242.java" "b/Spring-boot/jpa/jpa-\345\220\204\347\247\215\346\243\200\347\264\242.java" new file mode 100644 index 00000000..53dca93b --- /dev/null +++ "b/Spring-boot/jpa/jpa-\345\220\204\347\247\215\346\243\200\347\264\242.java" @@ -0,0 +1,163 @@ +----------------------------- +各种检索 | +----------------------------- + List findAll(); + List findAll(Sort sort); + + List findAllById(Iterable ids); + T getOne(ID id); + @Override + List findAll(Example example); + @Override + List findAll(Example example, Sort sort); + + + Iterable findAll(Sort sort); + Page findAll(Pageable pageable); + + Optional findById(ID id); + boolean existsById(ID id); + Iterable findAll(); + Iterable findAllById(Iterable ids); + long count(); + + +----------------------------- +排序 | +----------------------------- + # 类库 + Sort + |-JpaSort + Sort.Order + Sort.Direction + + * 一个Sort对象包含N多个 Order 对象 + + # Sort.Order + Order(@Nullable Direction direction, String property) + Order(@Nullable Direction direction, String property, NullHandling nullHandlingHint) + + direction + * 排序策略,枚举 + ASC + DESC + property + * 排序字段名称 + nullHandlingHint + * 对于空值的排序处理,枚举 + NATIVE 让DB决定 + NULLS_FIRST 排在最前面 + NULLS_LAST 排在最后面 + + # demo + // 根据name升序排序 + Sort nameAes = Sort.by(new Sort.Order(Sort.Direction.ASC,"name")); + + // 根据age降序,createDate升序 排序 + Sort ageDesccreateDateAes = Sort.by(Sort.Order.desc("age"),Sort.Order.asc("createDate")); + + // 根据gender排序,如果gender字段是空的,就排在记录的最前面 + Sort genderAes = Sort.by(new Sort.Order(Sort.Direction.ASC,"gender",Sort.NullHandling.NULLS_FIRST)); + + // 不排序 + Sort unsorted = Sort.unsorted(); +----------------------------- +分页 | +----------------------------- + # 类库(接口) + Pageable + |-PageRequest + Page + + # PageRequest的静态方法来创建实例 + PageRequest of(int page, int size) + PageRequest of(int page, int size, Sort sort) + PageRequest of(int page, int size, Direction direction, String... properties) + + # demo + // 创建一个分页,不排序 + PageRequest.of(1,10); + + // 创建一个分页,根据name升序排序 + PageRequest.of(1,10,Sort.by(Sort.Order.asc("name"))); + + // 创建一个分页,根据name,age,gender升序排序 + PageRequest.of(1,10, Sort.Direction.ASC,"name","age","gender"); + + # 分页结果 Page(接口) + int getNumber(); + int getNumberOfElements(); + int getSize(); // 每页显示的记录数 + int getTotalPages(); // 总页数 + long getTotalElements(); // 总记录数 + Page map(Function converter); // 数据转换接口 + List getContent(); // 获取到数据 + boolean hasContent(); // 是否有数据 + Sort getSort(); // 获取排序策略 + boolean isFirst(); // 是否是第一个 + boolean isLast(); // 是否是最后一个 + boolean hasNext(); // 是否还有下一个 + boolean hasPrevious(); // 是否还有上一个 + + * json结构 + { + "content": [], //分页的数据 + "pageable": { + "sort": { + "sorted": true, + "unsorted": false, + "empty": false + }, + "offset": 10, + "pageSize": 10, + "pageNumber": 1, + "paged": true, + "unpaged": false + }, + "totalPages": 1, + "totalElements": 6, + "last": true, + "number": 1, + "size": 10, + "sort": { + "sorted": true, + "unsorted": false, + "empty": false + }, + "numberOfElements": 0, + "first": false, + "empty": true + } + +----------------------------- +条件 | +----------------------------- + # Example api的组成 + Example 由Probe和ExampleMatcher组成,用于查询。 + Probe 含有对应字段的实例对象 + ExampleMatcher ExampleMatcher携带有关如何匹配特定字段的详细信息,相当于匹配条件 + + # 限制 + * 属性不支持嵌套或者分组约束,比如这样的查询 firstname = ? 0 or (firstname = ? 1 and lastname = ? 2) + * 灵活匹配只支持字符串类型,其他类型只支持精确匹配 + + // 匹配所有非 null 字段 + ExampleMatcher.matching(); + // 同上 + ExampleMatcher.matchingAll(); + // 匹配任何非 null 字段 + ExampleMatcher.matchingAny(); + + // matcher属性名是对象的属性名称(驼峰),而不是DB列名 + ExampleMatcher exampleMatcher = ExampleMatcher.matching() + //模糊查询匹配开头,即{username}% + .withMatcher("username", ExampleMatcher.GenericPropertyMatchers.startsWith()) + //模糊查询匹配结尾,即%{username} + .withMatcher("userName", ExampleMatcher.GenericPropertyMatchers.endsWith()) + //全部模糊查询,即%{address}% + .withMatcher("address" ,ExampleMatcher.GenericPropertyMatchers.contains()) + + //忽略字段,即不管password是什么值都不加入查询条件 + .withIgnorePaths("password"); + + Example userDTOExample = Example.of(userDTO,exampleMatcher); \ No newline at end of file diff --git "a/Spring-boot/jpa/jpa-\346\240\271\346\215\256\346\226\271\346\263\225\345\220\215\346\243\200\347\264\242.java" "b/Spring-boot/jpa/jpa-\346\240\271\346\215\256\346\226\271\346\263\225\345\220\215\346\243\200\347\264\242.java" new file mode 100644 index 00000000..05b76eef --- /dev/null +++ "b/Spring-boot/jpa/jpa-\346\240\271\346\215\256\346\226\271\346\263\225\345\220\215\346\243\200\347\264\242.java" @@ -0,0 +1,3 @@ +--------------------------------- +根据方法名检索 | +--------------------------------- \ No newline at end of file diff --git "a/Spring-boot/jpa/jpa-\346\263\250\350\247\243.java" "b/Spring-boot/jpa/jpa-\346\263\250\350\247\243.java" new file mode 100644 index 00000000..88343972 --- /dev/null +++ "b/Spring-boot/jpa/jpa-\346\263\250\350\247\243.java" @@ -0,0 +1,68 @@ +-------------------- +JPA的注解 | +-------------------- + @EnableJpaRepositories + @EntityScan + @NoRepositoryBean + * 标识在 Repository 上,表示该接口不是一个Repository + * 不需要生成动态代理对象 + + @Query + * 标识在某个 repository 的方法上,用于定义HQL语句 + * 可以是 INSERT,CREATE,UPDATE,DELETE 语句 + + @Modifying + * 和 @Query 组合使用 + * 标识在某个 repository 的方法上,表示当前的 @Query 是一个UPDATE 语句 + * 该方法返回的 int 值标签受到影响的行数 + + @NamedQuery + * 标识在 Entity 上 + * name Sring类型的属性,用于指定检索名,例如 "User.findByName" + * query String类书的属性,用于HQL,例如 "FROM User WHERE name = :name" + * 在该 Entity 的接口中定义的 findByName 方法,就是通过 query 属性的HQL来进行检索的 + + +-------------------- +Entity注解 | +-------------------- + @Entity + @Table + String name() default ""; + String catalog() default ""; + + String schema() default ""; + + UniqueConstraint[] uniqueConstraints() default {}; + + Index[] indexes() default {}; + + @Column + String name() default ""; + + boolean unique() default false; + + boolean nullable() default true; + + boolean insertable() default true; + + boolean updatable() default true; + + String columnDefinition() default ""; + + String table() default ""; + + int length() default 255; + + int precision() default 0; + + int scale() default 0; + + @Id + @GeneratedValue + * 标识ID字段,并且指定其生成策略 + * strategy ,指定生成策略 + GenerationType.TABLE 使用一个特定的数据库表格来保存主键。 + GenerationType.SEQUENCE 根据底层数据库的序列来生成主键,条件是数据库支持序列 + GenerationType.IDENTITY 主键由数据库自动生成(主要是自动增长型)) + GenerationType.AUTO 主键由程序控制 \ No newline at end of file diff --git "a/Spring-boot/jpa/jpa-\350\207\252\345\256\232\344\271\211\346\243\200\347\264\242.java" "b/Spring-boot/jpa/jpa-\350\207\252\345\256\232\344\271\211\346\243\200\347\264\242.java" new file mode 100644 index 00000000..5691c2a4 --- /dev/null +++ "b/Spring-boot/jpa/jpa-\350\207\252\345\256\232\344\271\211\346\243\200\347\264\242.java" @@ -0,0 +1,16 @@ +-------------------------------- +自定义原生的SQL检索 | +-------------------------------- + # 使用 @Query 注解, 标识在检索方法 + String value() default ""; + + String countQuery() default ""; + + String countProjection() default ""; + + boolean nativeQuery() default false; + + String name() default ""; + + String countName() default ""; + diff --git a/Spring-boot/jpa/jpa.java b/Spring-boot/jpa/jpa.java new file mode 100644 index 00000000..1423658b --- /dev/null +++ b/Spring-boot/jpa/jpa.java @@ -0,0 +1,40 @@ +--------------------- +jpa | +--------------------- + # 参考 + https://ityouknow.gitbooks.io/spring-data-jpa-reference-documentation/content/ + https://docs.spring.io/spring-data/jpa/docs/current-SNAPSHOT/reference/html/#reference + + # Maven + + org.springframework.boot + spring-boot-starter-data-jpa + + + # 使用 + 1,自定义接口继承:Repository + + public interface UserRepository extends JpaRepository {} + + 2,配置扫描Repository和Entity + @EnableJpaRepositories(basePackages = {"io.springboot.jpa.repository"}) + @EntityScan(basePackages = {"io.springboot.jpa.dto"}) + + 4,Entit添加注解 + @Entity + @Table(name = "user") + @Id + @Column + + 5,在需要的地方注入 + @Autowired + private UserRepository userRepository; + + + # 核心的 Repository + Repository + |-CrudRepository + |PagingAndSortingRepository + |-QueryByExampleExecutor + |-JpaRepository + diff --git a/Spring-boot/resttemplate/resttemplate-request-GET.java b/Spring-boot/resttemplate/resttemplate-request-GET.java new file mode 100644 index 00000000..ce23258c --- /dev/null +++ b/Spring-boot/resttemplate/resttemplate-request-GET.java @@ -0,0 +1,32 @@ +--------------------------- +GET | +--------------------------- + # 两种类型的请求方式 + * 响应 ResponseEntity + * 可以获取到响应头,状态码等信息 + + ResponseEntity getForEntity(String url, Class responseType, Object... uriVariables) + ResponseEntity getForEntity(URI url, Class responseType) throws RestClientException; + ResponseEntity getForEntity(String url, Class responseType, Map uriVariables) throws RestClientException; + + + * 响应对象,直接返回编码后的对象 + T getForObject(String url, Class responseType, Object... uriVariables) throws RestClientException; + T getForObject(String url, Class responseType, Map uriVariables) throws RestClientException; + T getForObject(URI url, Class responseType) throws RestClientException; + + + # 可以使用占位符来设置查询参数 + ResponseEntity responseEntity = restTemplate.getForEntity("http://localhost?name={1}&age={2}",String.class,"KevinBlandy","23"); + + # 可以使用 Map 来设置占位符参数 + * 使用符号 "{}" 来占位 + + Map param = new HashMap<>(); + param.put("name","KevinBlandy"); + param.put("age","23"); + + restTemplate.getForEntity("http://localhost/user?name={name}&age={age}",String.class,param); + + # 可以使用 UriComponentsBuilder 来构建 URI 对象 + diff --git a/Spring-boot/resttemplate/resttemplate-request-POST.java b/Spring-boot/resttemplate/resttemplate-request-POST.java new file mode 100644 index 00000000..ccdce424 --- /dev/null +++ b/Spring-boot/resttemplate/resttemplate-request-POST.java @@ -0,0 +1,80 @@ +------------------------- +POST | +------------------------- + URI postForLocation(String url, @Nullable Object request, Object... uriVariables) throws RestClientException; + URI postForLocation(String url, @Nullable Object request, Map uriVariables)throws RestClientException; + URI postForLocation(URI url, @Nullable Object request) throws RestClientException; + + T postForObject(String url, @Nullable Object request, Class responseType,Object... uriVariables) throws RestClientException; + T postForObject(String url, @Nullable Object request, Class responseType,Map uriVariables) throws RestClientException; + T postForObject(URI url, @Nullable Object request, Class responseType) throws RestClientException; + + ResponseEntity postForEntity(String url, @Nullable Object request, Class responseType,Object... uriVariables) throws RestClientException; + ResponseEntity postForEntity(String url, @Nullable Object request, Class responseType,Map uriVariables) throws RestClientException; + ResponseEntity postForEntity(URI url, @Nullable Object request, Class responseType)throws RestClientException; + + + + # http表单体 + RestTemplate restTemplate = new RestTemplate(); + + // 构建消息头 + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.set(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_FORM_URLENCODED_VALUE); + + // 构建消息体 + MultiValueMap requestBody = new LinkedMultiValueMap<>(); + requestBody.add("name", "KevinBlandy"); // 单个消息 + requestBody.add("age", "23"); + requestBody.addAll("skills", Arrays.asList("java","python","javascript","c")); // 多个消息 + + // 完整的http消息体 + HttpEntity httpEntity = new HttpEntity<>(requestBody,httpHeaders); + + ResponseEntity responseEntity = restTemplate.postForEntity("http://localhost/user.do", httpEntity, String.class); + + # json表单体 + + RestTemplate restTemplate = new RestTemplate(); + + // 构建消息头 + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); + + // 构建JSON对象体 + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name","KevinBlandy"); + jsonObject.put("age",23); + + // 构建完整的http表单体 + HttpEntity httpEntity = new HttpEntity<>(jsonObject,httpHeaders); + // 可以直接使用json字符串 : HttpEntity httpEntity = new HttpEntity<>(jsonObject.toJSONString(),httpHeaders); + + ResponseEntity responseEntity = restTemplate.postForEntity("http://localhost:8081/user.do", httpEntity, String.class); + + + # 多部件表单体 + RestTemplate restTemplate = new RestTemplate(); + + // 消息头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); // 多部件表单体 + + MultipartBodyBuilder multipartBodyBuilder = new MultipartBodyBuilder(); + // 设置普通表单项 + multipartBodyBuilder.part("name", "KevinBlandy"); + multipartBodyBuilder.part("skill", Arrays.asList("Java","Python","Javascript","C")); + + // 设置文件表单项(表单名,文件对象,ContentType) + Resource file1 = new FileSystemResource("D:\\20181009153347.jpg"); + multipartBodyBuilder.part("file", file1,MediaType.IMAGE_JPEG).header("Bar", "Foo"); + + Resource file2 = new ClassPathResource("log-1.log"); // Resource 可以有很多种实现,用于从不同的源加载数据 + multipartBodyBuilder.part("file", file2,MediaType.TEXT_PLAIN).header("Bar", "Foo"); // 可以设置多个同表单名称的文件表单项 + + // 完整的消息体 + MultiValueMap> multipartBody = multipartBodyBuilder.build(); + + ResponseEntity responseEntity = restTemplate.postForEntity("http://localhost:8081/user.do", multipartBody, JSONObject.class); + + System.out.println(responseEntity); \ No newline at end of file diff --git a/Spring-boot/resttemplate/resttemplate-request.java b/Spring-boot/resttemplate/resttemplate-request.java new file mode 100644 index 00000000..d2469f97 --- /dev/null +++ b/Spring-boot/resttemplate/resttemplate-request.java @@ -0,0 +1,22 @@ +------------------------------ +请求执行 | +------------------------------ + # 通用的请求执行 + ResponseEntity exchange(String url, HttpMethod method, @Nullable HttpEntity requestEntity, Class responseType, Map uriVariables) + + url + * 请求的url + + method + * 请求方法 + + requestEntity + * 请求体,可以为null + + responseType + * 响应体编码类型 + + uriVariables + * uri参数 + + \ No newline at end of file diff --git a/Spring-boot/resttemplate/resttemplate-ssl.java b/Spring-boot/resttemplate/resttemplate-ssl.java new file mode 100644 index 00000000..e69de29b diff --git a/Spring-boot/resttemplate/resttemplate.java b/Spring-boot/resttemplate/resttemplate.java new file mode 100644 index 00000000..4d794b0b --- /dev/null +++ b/Spring-boot/resttemplate/resttemplate.java @@ -0,0 +1,101 @@ +----------------------- +RestTemplate | +----------------------- + # http客户端 + # 基础自: InterceptingHttpAccessor 实现: RestOperations + # 核心的组件 + private final List> messageConverters = new ArrayList<>(); + * 消息转换器 + + private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler(); + * 异常处理器 + + private final List interceptors = new ArrayList<>(); + * 请求拦截器 + + private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + * http请求工厂 + + # 构造函数 + public RestTemplate(ClientHttpRequestFactory requestFactory) + public RestTemplate() + public RestTemplate(List> messageConverters) + + + # 设置消息转换器 + * 用于序列化/反序列化对象为http消息 + * 消息转换器就是:HttpMessageConverter 接口的实现类 + + FastJsonConfig fastJsonConfig = new FastJsonConfig(); + fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); + fastJsonConfig.setCharset(StandardCharsets.UTF_8); + fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); + + FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); + fastJsonHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON_UTF8)); + fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); + + + restTemplate.setMessageConverters(Arrays.asList(fastJsonHttpMessageConverter)); + + # 设置拦截器 + * 很简单,就是一个责任链模式 + + restTemplate.setInterceptors(Arrays.asList(new ClientHttpRequestInterceptor(){ + /** + * @param request 请求对象 + * @param body 请求体 + * @param execution 执行器 + * @return + * @throws IOException + */ + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + // 执行下一个拦截器,并且获取到响应 + ClientHttpResponse clientHttpResponse = execution.execute(request,body); + // 返回响应到上一个拦截器 + return clientHttpResponse; + } + })); + + # 设置异常处理器 + * ResponseErrorHandler 实现类 + + restTemplate.setErrorHandler(new ResponseErrorHandler(){ + @Override + public boolean hasError(ClientHttpResponse response) throws IOException { + return false; + } + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + + } + + @Override + public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { + + } + }); + +------------------------- +ClientHttpRequestFactory | +------------------------- + # 连接工厂对象它是一个接口 + ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException; + + # 已经提供的实现 + SimpleClientHttpRequestFactory + OkHttp3ClientHttpRequestFactory + + + # RestTemplate 操作 ClientHttpRequestFactory 的api + ClientHttpRequestFactory getRequestFactory() + void setRequestFactory(ClientHttpRequestFactory requestFactory) + + + # 设置自定义的Factory + OkHttpClient okHttpClient = new OkHttpClient(); + OkHttp3ClientHttpRequestFactory okHttp3ClientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(okHttpClient); + + restTemplate.setRequestFactory(okHttp3ClientHttpRequestFactory); diff --git a/Spring-boot/springboot-Converter.java b/Spring-boot/springboot-Converter.java new file mode 100644 index 00000000..808eaa1c --- /dev/null +++ b/Spring-boot/springboot-Converter.java @@ -0,0 +1,55 @@ +---------------------------- +自定义参数类型的转换 | +---------------------------- + # spring mvc的东西,简单,一个Demo完事儿 + + # Date 转换 + 1,实现接口Converter + package io.springcloud.web.converter; + import java.text.ParseException; + import java.text.SimpleDateFormat; + import java.util.Date; + + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import org.springframework.core.convert.converter.Converter; + + import com.alibaba.druid.util.StringUtils; + + import io.springcloud.common.Messages; + import io.springcloud.exception.BusinessException; + + public class DateConverter implements Converter{ + + private static final Logger LOGGER = LoggerFactory.getLogger(DateConverter.class); + + public static final SimpleDateFormat[] SIMPLE_DATE_FORMATS = new SimpleDateFormat[] { + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), + new SimpleDateFormat("yyyy-MM-dd"), + new SimpleDateFormat("yyyy-MM"), + new SimpleDateFormat("yyyy") + }; + + @Override + public Date convert(String param) { + LOGGER.debug("自定义Date转换:{}",param); + if(!StringUtils.isEmpty(param)) { + for(SimpleDateFormat simpleDateFormat : SIMPLE_DATE_FORMATS) { + try { + return simpleDateFormat.parse(param); + } catch (ParseException e) { + e.printStackTrace(); + continue; + } + } + throw new RuntimeException(new BusinessException(Messages.BAD_PARAM)); + } + return null; + } + } + + 2,覆写配置类:WebMvcConfigurer的addFormatters方法 + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverter(new DateConverter()); + } \ No newline at end of file diff --git a/Spring-boot/springboot-HikariCP.java b/Spring-boot/springboot-HikariCP.java new file mode 100644 index 00000000..be9c5081 --- /dev/null +++ b/Spring-boot/springboot-HikariCP.java @@ -0,0 +1,86 @@ +------------------------ +HikariCP | +------------------------ + # 一个比Druid更快的连接池 + # Github + https://github.com/brettwooldridge/HikariCP + # SpringBoot2默认使用(不需要额外的starter) + # Maven导入 + + com.zaxxer + HikariCP + + + # 配置项 + +spring: + # 基本配置 + datasource: + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true + username: root + password: root + + # 连接池的配置属性 + hikari: + catalog: + connection-timeout: + validation-timeout: + idle-timeout: + leak-detection-threshold: + max-lifetime: + max-pool-size: + min-idle: + username: + password: + initialization-fail-timeout: + connection-init-sql: + connection-test-query: SELECT 1 + data-source-class-name: + data-source-jndi-name: + driver-class-name: + jdbc-url: + pool-name: + schema: + transaction-isolation-name: + is-auto-commit: + is-read-only: + is-isolate-internal-queries: + is-register-mbeans: + is-allow-pool-suspension: + data-source: + data-source-properties: + thread-factory: + scheduled-executor: + metrics-tracker-factory: + metric-registry: + health-check-registry: + health-check-properties: + sealed: + + +------------------------ +编码配置 | +------------------------ +@Bean +public DataSource dataSource() { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(dataSourceUrl); //数据源 + config.setUsername(user); //用户名 + config.setPassword(password); //密码 + config.addDataSourceProperty("cachePrepStmts", "true"); //是否自定义配置,为true时下面两个参数才生效 + config.addDataSourceProperty("prepStmtCacheSize", "250"); //连接池大小默认25,官方推荐250-500 + config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); //单条语句最大长度默认256,官方推荐2048 + config.addDataSourceProperty("useServerPrepStmts", "true"); //新版本MySQL支持服务器端准备,开启能够得到显著性能提升 + config.addDataSourceProperty("useLocalSessionState", "true"); + config.addDataSourceProperty("useLocalTransactionState", "true"); + config.addDataSourceProperty("rewriteBatchedStatements", "true"); + config.addDataSourceProperty("cacheResultSetMetadata", "true"); + config.addDataSourceProperty("cacheServerConfiguration", "true"); + config.addDataSourceProperty("elideSetAutoCommits", "true"); + config.addDataSourceProperty("maintainTimeStats", "false"); + + HikariDataSource ds = new HikariDataSource(config); + return ds; +} \ No newline at end of file diff --git a/Spring-boot/springboot-applicationEvent.java b/Spring-boot/springboot-applicationEvent.java deleted file mode 100644 index 6116d4cf..00000000 --- a/Spring-boot/springboot-applicationEvent.java +++ /dev/null @@ -1,27 +0,0 @@ ----------------------------- -applicationEvent | ----------------------------- - # 涓撻棬鐩戝惉applicationContent鐨勫悇绉嶄簨浠 - # 瀹炵幇鎺ュ彛 ApplicationListener - # 閫氳繃璇ユ帴鍙g殑娉涘瀷鏉ュ喅瀹氳鐩戝惉鐨勪簨浠(ApplicationEvent瀛愮被) - SpringApplicationEvent - ApplicationStartingEvent - .... - # demo - @Component - public class ApplicationStartingListener implements ApplicationListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationStartingListener.class); - - @Autowired - private ApplicationContext applicationContext; - - @Override - public void onApplicationEvent(SpringApplicationEvent event) { - LOGGER.debug("application 鍚姩ok"); - //娉ㄥ叆 root context - SpringContext.setApplicationContext(this.applicationContext); - //鍚姩UDP鏈嶅姟 - Server.start(); - } - } \ No newline at end of file diff --git a/Spring-boot/springboot-async.java b/Spring-boot/springboot-async.java new file mode 100644 index 00000000..e14cbe95 --- /dev/null +++ b/Spring-boot/springboot-async.java @@ -0,0 +1,58 @@ +-------------------- +Async | +-------------------- + # 异步的执行方法 + # 注解驱动 @EnableAsync + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Import(AsyncConfigurationSelector.class) + public @interface EnableAsync { + + Class annotation() default Annotation.class; + + boolean proxyTargetClass() default false; + + AdviceMode mode() default AdviceMode.PROXY; + + int order() default Ordered.LOWEST_PRECEDENCE; + } + + + * mode 代理类型, 枚举 + PROXY + ASPECTJ + + * order + + + + # 使用 @Async 开启异步 + * @Async 所修饰的函数不要定义为static类型, 这样异步调用不会生效 + public @interface Async { + String value() default ""; + } + + * 可以通过 value 属性来指定 ioc 中一个线程池 bean 的名称 + * 从而使用指定的线程池来执行 + + * 如果IOC中存在多个异步线程池, 那么必须要通过 value 属性明确的指出使用的线程池bean名称 + + + # 使用默认线程池 + * 需要在IOC注册一个实现:ThreadPoolTaskExecutor + * 属性方法很多, 用到的时候再说 + @Bean + public ThreadPoolTaskExecutor threadPoolTaskExecutor(){ + ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); + threadPoolTaskExecutor.setCorePoolSize(4); + threadPoolTaskExecutor.setMaxPoolSize(10); + threadPoolTaskExecutor.setThreadFactory( r -> { + Thread thread = new Thread(r); + thread.setName("ThreadPoolTaskExecutor-Thread"); + return thread; + }); + return threadPoolTaskExecutor; + } + + * 如果 @Async 未配置线程池的名称(value属性)那么 @Async 都会使用该线程池来执行 diff --git a/Spring-boot/springboot-batch.java b/Spring-boot/springboot-batch.java new file mode 100644 index 00000000..7583dea5 --- /dev/null +++ b/Spring-boot/springboot-batch.java @@ -0,0 +1,46 @@ +------------------------- +SpringBoot batch | +------------------------- + # 结构 + Step + ↓ + Tasklet + ↓ + Chunk + ↓ + read + process + write + + + # 结构 + JobRepository + * 用来注册job的容器,基础组件,用来持久化Job的元数据,默认使用内存 + JobLauncher + * 用来启动Job的接口,基础组件 + Job + * 实际执行的任务,包含一个或多个Step,是Batch操作的基础执行单元 + Step + * 表示Job的一个阶段,Job由一组Step组成 + * Step包含ItemReader、ItemProcessor和ItemWriter + Item + * 从数据源读出或者写入的一条记录 + Chunk + * 给定数量的Item集合(Item数量) + ItemReader + * 用来读取数据的接口 + ItemProcessor + * 用来处理数据的接口(Consumer) + ItemWriter + * 用来输出数据的接口(把Chunk包含的数据写入数据源) + + # 注解开启 + @EnableBatchProcessing + boolean modular() default false; + * 要实现多Job,需要把modular设置为true + * 让每个Job使用自己的ApplicationConext + + + + + \ No newline at end of file diff --git a/Spring-boot/springboot-cache.java b/Spring-boot/springboot-cache.java new file mode 100644 index 00000000..4b5b93c2 --- /dev/null +++ b/Spring-boot/springboot-cache.java @@ -0,0 +1,221 @@ +-------------------- +springboot-cache | +-------------------- + # 参考 + https://blog.csdn.net/u012373815/article/details/54564076 + + # 依赖 + + org.springframework.boot + spring-boot-starter-cache + + + # 开启 + @EnableCaching + * Spring Boot根据下面的顺序去侦测缓存提供者: + Generic + JCache (JSR-107) + EhCache 2.x + Hazelcast + Infinispan + COUCHBASE + Redis + CAFFEINE + Guava (新版本已经移除) + Simple + NONE + * 除了按顺序侦测外,也可以通过配置属性spring.cache.type 来强制指定,默认是simple类型 + + + # spring 缓存支持,抽象出来了一个 CacheManager和Cache接口 + org.springframework.cache.CacheManager + org.springframework.cache.Cache + + # spring支持的缓存框架 + SimpleCacheManager + * 直接使用了一个 Collection 来存储 + ConcurrentMapCache + * 使用 ConcurrentMap 来存储 + EhCacheCacheManager + * 使用EhCache作为缓存技术 + RedisCacheManager + * 使用Redis作为缓存技术 + + # SpringBoot的配置类 + CacheProperties + +-------------------- +声明式注解 | +-------------------- + @Cacheable + # 如果存在缓存,直接返回,不存在调用方法获取计算结果,存入缓存 + # 当标记在一个类上时则表示该类所有的方法都是支持缓存的 + # 属性 + @AliasFor("cacheNames") + String[] value() default {}; + + @AliasFor("value") + String[] cacheNames() default {}; + + String key() default ""; + * 默认key生成规则 + - 如果没有参数,则使用0作为key + - 如果只有一个参数,使用该参数作为key + * 在ehcache中,此时,key-type应该是参数类型 + - 如果又多个参数,使用包含所有参数的hashCode作为key + + * 支持使用SpringEL表达式 + @Cacheable(value = "user", key = "#user.id"),使用user的id作为参数 + public User create(User user) + + @Cacheable(cacheNames="books", key="#map['bookid'].toString()") + public Book findBook(Map map) + + String keyGenerator() default ""; + String cacheManager() default ""; + String cacheResolver() default ""; + String condition() default ""; + String unless() default ""; + * 如果方法返回null,也会被认为是一种返回值,null也会被缓存,有些时候这不是我们希望的 + * 通过该属性来控制,禁止缓存null,如果结果为null,那么就不缓存 + @Cacheable(value = "post",unless="#result == null") + + boolean sync() default false; + + @CachePut + # 不论怎么样,都会把方法返回值放入缓存中 + # CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中 + # 属性 + @AliasFor("cacheNames") + String[] value() default {}; + + @AliasFor("value") + String[] cacheNames() default {}; + + String key() default ""; + String keyGenerator() default ""; + String cacheManager() default ""; + String cacheResolver() default ""; + String condition() default ""; + String unless() default ""; + + @CacheEvict + # 标注在需要清除缓存元素的方法或类上的 + # 当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作 + # 属性 + @AliasFor("cacheNames") + String[] value() default {}; + @AliasFor("value") + String[] cacheNames() default {}; + String key() default ""; + String keyGenerator() default ""; + String cacheManager() default ""; + String cacheResolver() default ""; + String condition() default ""; + boolean allEntries() default false; + * 表示是否需要清除缓存中的所有元素,默认为false,表示不需要 + * 有的时候需要Cache一下清除所有的元素,这比一个一个清除元素更有效率 + + boolean beforeInvocation() default false; + * 清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作 + * 该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素 + + @Caching + # 组合注解,把多个注解整合到一个上 + # 属性 + Cacheable[] cacheable() default {}; + CachePut[] put() default {}; + CacheEvict[] evict() default {}; + + # 使用自定义注解 + * Spring允许我们在配置可缓存的方法时使用自定义的注解 + * 前提是自定义的注解上必须使用对应的注解进行标注 + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Cacheable(value="users") //(*^__^*) + public @interface MyCacheable { + + } + + +-------------------- +使用redis | +-------------------- + # 开启redis的start(具体看redis的笔记) + + org.springframework.boot + spring-boot-starter-data-redis + + + # 配置 + spring.cache.type=redis + + # 注解配置 + @Cacheable(value = "name") + * value 属性,指定了redis 的key名称 + + +-------------------- +Ehcache3 | +-------------------- + # 依赖 + + org.ehcache + ehcache + + + javax.cache + cache-api + + + # 配置 + spring.cache.type=jcache + spring.cache.jcache.config=classpath:ehcache/ehcache.xml + # 如果存在多个jcache的实现,需要在这里指定实现类 + spring.cache.jcache.provider= + + # 注解配置 + @Cacheable(value = "name") + * value属性,便是指定了 ehcache.xml 中的 + + # 注入使用 CacheManager + @Autowired + private CacheManager cacheManager; + + * 注意,该 CacheManager 是:javax.cache.CacheManager 接口 + * api跟 ehcache3 差不多 + * 很显然,实现使用的就是 ehcache3 + +----------------------------- +CachingConfigurerSupport | +----------------------------- + # 自定义缓存中的一些配置 + * 继承:CachingConfigurerSupport,覆写方法 + + import java.lang.reflect.Method; + import org.springframework.cache.annotation.CachingConfigurerSupport; + import org.springframework.cache.annotation.EnableCaching; + import org.springframework.cache.interceptor.KeyGenerator; + import org.springframework.context.annotation.Configuration; + /** + * + * @author KevinBlandy + * + */ + @EnableCaching + @Configuration + public class RedisCacheConfiguration extends CachingConfigurerSupport{ + + //自定义key生成策略 + @Override + public KeyGenerator keyGenerator() { + KeyGenerator generator = new KeyGenerator() { + @Override + public Object generate(Object target, Method method, Object... params) { + return target.getClass().getSimpleName() + ":" + method.getName(); + } + }; + return generator; + } + } + \ No newline at end of file diff --git a/Spring-boot/springboot-config-server.yml b/Spring-boot/springboot-config-server.yml new file mode 100644 index 00000000..8acb1c9a --- /dev/null +++ b/Spring-boot/springboot-config-server.yml @@ -0,0 +1,88 @@ +# 配置类 org.springframework.boot.autoconfigure.web.ServerProperties + +server: + # 全局配置 + port: 1024 + address: + use-forward-headers: true + server-header: + max-http-header-size: + connection-timeout: + + # 异常配置 + error: + path: + include-exception: + + + # servlet 配置 + servlet: + context-path: / + # SESSION相关的配置 + session: + # 会话超超时时间,秒 + timeout: + # 会话跟踪模式,可以有多个。枚举值:COOKIE URL SSL + tracking-modes: + - COOKIE + # 在服务器重启的时候,序列化session到磁盘 + persistent: true + # 保存session数据的目录 + store-dir: + # 保存session的目录 + session-store-directory: + # 目录 File 对象 + directory: + # session cookie的设置 + cookie: + name: PHPSESSIONID + domain: + path: + comment: + httpOnly: + secure: + maxAge: + + # SSL配置 + ssl: + enabled: true + key-store: classpath:ssl/localhost.keystore + key-store-type: PKCS12 + key-store-password: 123456 + # 是否开启客户端的验证,枚举值:NONE 不验证,WANT 需要验证但不是必须的,NEED 必须要验证客户端 + client-auth: NEED + # 数组 + ciphers: + # 数组 + enabled-protocols: + key-alias: + + # HTTP2 + http2: + enabled: true + + + # 压缩配置 + compression: + enabled: true + mime-types: + - application/json + - application/xml + - application/javascript + - text/html + - text/xml + - text/plain + - text/css + - text/javascript + min-response-size: 2048 + + # Tomcat配置 + tomcat + + # UnderTow配置 + undertow + + # Jetty配置 + jetty + + \ No newline at end of file diff --git a/Spring-boot/springboot-druid.java b/Spring-boot/springboot-druid.java index 16fa772d..e4ae2029 100644 --- a/Spring-boot/springboot-druid.java +++ b/Spring-boot/springboot-druid.java @@ -5,7 +5,7 @@ # 指定数据源类型 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driver-class-name=com.mysql.jdbc.Driver - spring.datasource.url=jdbc:mysql://120.76.182.243:1124/test + spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true spring.datasource.username=root spring.datasource.password=KevinBlandy_mysql @@ -54,3 +54,72 @@ public class DruidStatViewServlet extends StatViewServlet { initParams={@WebInitParam(name="exclusions",value="*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*")}) public class DruidStatFilter extends WebStatFilter { } + +---------------------------- +starter | +---------------------------- + # 文档 + https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter + + # 依赖 + + com.alibaba + druid-spring-boot-starter + 1.1.10 + + + # 配置:配置类:DruidStatProperties,DataSourceProperties +spring: + datasource: + # spring提供的通用(常用)配置 + driver-class-name: + url: + username: + password: + + # druid配置 + druid: + # 连接池配置(DruidDataSource实例属性) + initial-size: + max-active: + min-idle: + max-wait: + pool-prepared-statements: + max-pool-prepared-statement-per-connection-size: + max-open-prepared-statements: + validation-query: + validation-query-timeout: + test-on-borrow: + test-on-return: + test-while-idle: + time-between-eviction-runs-millis: + min-evictable-idle-time-millis: + max-evictable-idle-time-millis: + filters: + + # Spring监控AOP切入点(x.y.z.service.*)是一个数组 String[] + aop-patterns: + + # 监控Servlet + stat-view-servlet: + enabled: + url-pattern: + allow: + deny: + login-username: + login-password: + reset-enable: + + # 监控Filter + web-stat-filter: + enabled: + url-pattern: + exclusions: + session-stat-max-count: + session-stat-enable: + principal-session-name: + principal-cookie-name: + profile-enable: + + + diff --git a/Spring-boot/springboot-email.java b/Spring-boot/springboot-email.java new file mode 100644 index 00000000..84e33652 --- /dev/null +++ b/Spring-boot/springboot-email.java @@ -0,0 +1,76 @@ +------------------------------- +email | +------------------------------- + # 导入依赖 + + org.springframework.boot + spring-boot-starter-mail + + + + + +#-------------------------------------- +# email config +#------------------------------------- + +spring: + mail: + host: smtp.exmail.qq.com + username: no-reply@javaweb.io + password: 123456789 + sender: Javaweb开发者社区 + port: 587 + default-encoding: UTF-8 + protocol: smtp + properties: + mail: + smtp: + connectiontimeout: 5000 + timeout: 3000 + writetimeout: 5000 + auth: true + starttls: + enable: true + required: true + + +#-------------------------------------- +# MailService +#------------------------------------- + + +import javax.mail.MessagingException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import javax.mail.internet.MimeUtility; + +@Component +public class EmailService { + + @Autowired + private JavaMailSender javaMailSender; + + @Value("${spring.mail.username}") + private String username; + + @Value("${spring.mail.sender}") + private String sender; + + public void sendHTMLMail(String to,String title,String content) throws MessagingException, UnsupportedEncodingException { + MimeMessage message = javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setFrom(new InternetAddress(MimeUtility.encodeText(sender) + "<" + this.username + ">")); + helper.setTo(to); + helper.setSubject(title); + helper.setText(content, true); + javaMailSender.send(message); + } +} diff --git a/Spring-boot/springboot-event.java b/Spring-boot/springboot-event.java new file mode 100644 index 00000000..aa1fb788 --- /dev/null +++ b/Spring-boot/springboot-event.java @@ -0,0 +1,231 @@ +---------------------------- +Event | +---------------------------- + # Spring鏈韩鎻愪緵浜嗚繖绉嶄簨浠跺彂甯冭闃呯殑妯″瀷 + # 椤跺眰鎶借薄浜嗕竴涓簨浠跺彂甯冩帴鍙, ApplicationContext 瀹炵幇浜嗚鎺ュ彛 + @FunctionalInterface + public interface ApplicationEventPublisher { + default void publishEvent(ApplicationEvent event) { + publishEvent((Object) event); + } + void publishEvent(Object event); + } + + # 鎶借薄鐨勪簨浠舵帴鍙 + * JDK鎻愪緵鐨勪簨浠跺璞 + public class EventObject implements java.io.Serializable { + private static final long serialVersionUID = 5516075349620653480L; + protected transient Object source; + public EventObject(Object source) { + if (source == null) + throw new IllegalArgumentException("null source"); + this.source = source; + } + public Object getSource() { + return source; + } + + public String toString() { + return getClass().getName() + "[source=" + source + "]"; + } + } + + * Spring鎻愪緵鐨勯《灞備簨浠跺璞 + public abstract class ApplicationEvent extends EventObject { + private static final long serialVersionUID = 7099057708183571937L; + private final long timestamp; + public ApplicationEvent(Object source) { + super(source); + this.timestamp = System.currentTimeMillis(); + } + public final long getTimestamp() { + return this.timestamp; + } + } + + # 鎶借薄鐨勪簨浠剁洃鍚帴鍙 + public interface EventListener { + } + @FunctionalInterface + public interface ApplicationListener extends EventListener { + void onApplicationEvent(E event); + } + + # 瀹屾暣鐨勪簨浠跺彂甯冭闃呮祦绋 + 1. 鑷畾涔変簨浠跺璞, 缁ф壙绫:ApplicationEvent + 2. 鑷畾涔変簨浠剁洃鍚櫒, 瀹炵幇鎺ュ彛:ApplicationListener, 娉涘瀷鍙傛暟灏辨槸瑕佺洃鍚殑浜嬩欢瀵硅薄 + 3. 浣跨敤 ApplicationEventPublisher 瀹炵幇鍙戝竷涓涓簨浠跺璞$殑瀹炵幇 + 4. 瀵瑰簲鐨勭洃鍚櫒浼氬緱鍒版墽琛 + * 濡傛灉瀛樺湪澶氫釜鐩戝惉, 鍒欏涓洃鍚兘浼氭墽琛 + * 榛樿鎵ц鐨勭嚎绋, 灏辨槸褰撳墠鐨勫彂甯冪嚎绋(闈炲紓姝) + +---------------------------- +applicationEvent | +---------------------------- + # 涓撻棬鐩戝惉applicationContent鐨勫悇绉嶄簨浠 + # 瀹炵幇鎺ュ彛 ApplicationListener + # 閫氳繃璇ユ帴鍙g殑娉涘瀷鏉ュ喅瀹氳鐩戝惉鐨勪簨浠(ApplicationEvent瀛愮被) + # Spring 棰勫畾涔変簡N澶氱殑浜嬩欢 + SpringApplicationEvent + ApplicationStartingEvent + + .... + # demo + @Component + public class ApplicationStartingListener implements ApplicationListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationStartingListener.class); + + @Autowired + private ApplicationContext applicationContext; + + @Override + public void onApplicationEvent(ApplicationStartingEvent event) { + LOGGER.debug("application 鍚姩ok"); + SpringContext.setApplicationContext(this.applicationContext); + } + } + + +---------------------------- +@EventListener | +---------------------------- + # 鍙互浣跨敤娉ㄨВ椹卞姩, 鏉ョ洃鍚簨浠 + @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + public @interface EventListener { + + @AliasFor("classes") + Class[] value() default {}; + + @AliasFor("value") + Class[] classes() default {}; + + String condition() default ""; + + } + + # 榛樿浣跨敤鍙傛暟浣滀负鐩戝惉鐨勪簨浠 + @EventListener + public void stringListener(StringApplicationEvent event){ + System.out.println(Thread.currentThread().getName()); + System.out.println("common:" + event); + } + + * 涔熷彲浠ラ氳繃娉ㄨВ鐨勫睘鎬, 鏉ユ寚瀹氱洃鍚殑浜嬩欢 + + + # 瀹冨彲浠ュ幓鎺ュ彈鍒伴潪 ApplicationEvent 瀛愮被鐨勪簨浠 + @Component + public class ObjectListener { + + // 鎺ユ敹鍒版墍鏈夊彂甯冪殑浜嬩欢 + @EventListener + public void objectListener(Object event){ + System.out.println("object:" + event); + } + } + + + * 鍙戝竷鐨勪簨浠跺璞, 濡傛灉鏄洃鍚簨浠剁殑瀛愮被, 鍒欎細鎵ц + + # 涔熸敮鎸侀氳繃娉涘瀷瀵硅薄鏉ョ洃鍚簨浠 + @EventListener + public void orderListener(GenericEvent event) { + System.out.println("閫氱敤娉涘瀷浜嬩欢鐩戝惉, 璁㈠崟"); + } + + * 绯荤粺鍙戝竷鐨勫涓硾鍨嬩簨浠, 鍙湁涓庤鐩戝惉鏂规硶鎯冲尮閰嶇殑娉涘瀷浜嬩欢瀵硅薄鎵嶄細鎵ц + + # 鍙互閫氳繃SpEL鏉ヨ缃Е鍙戠殑鏉′欢 + * 鍦ㄤ竴浜涙椂鍊欏彲鑳藉彧瑕佹弧瓒虫煇浜涙潯浠舵墠杩涜瀵逛簨浠剁洃鍚 + * 鍙互鐢 @EventListener 鐨 condition 灞炴ф潵鎸囧畾鏉′欢, 鏉′欢鏄竴涓 SpEL 琛ㄨ揪寮 + + @EventListener(condition = "#event.order.orderStatus eq '寰呬粯娆'") + public void orderCreateEventCondition(OrderCreateEvent event) { + // 鍙湁璁㈠崟鐘舵佹槸寰呬粯娆炬墠鐩戝惉鎵嶆湁鏁 + System.out.println("璁㈠崟鍒涘缓浜嬩欢"); + } + + # 鏂颁簨浠剁户缁紶鎾 + * 鍙互鍦ㄤ簨浠舵柟娉曞唴, 涓诲姩鐨勮皟鐢ㄦ柟娉曟潵鍙戦佹柊鐨勪簨浠 + applicationContext.publishEvent(new StringApplicationEvent("Hello")); + + * 涔熷彲浠ヨ繑鍥炰竴涓簨浠跺璞:ApplicationEvent, 鏉ュ箍鎾柊鐨勪簨浠 + * 涔熷彲浠ヨ繑鍥炰竴涓椂闂村璞℃暟缁, 鏉ュ箍鎾涓 + * 鍗冧竾瑕佹敞鎰, 濡傛灉杩斿洖鐨勪簨浠跺璞, 杩樺彲浠ヨ褰撳墠鐨勪簨浠舵柟娉曟崟鑾风殑璇, 閭d箞灏变細浜х敓涓涓笉鏂彂甯, 澶勭悊鐨勬寰幆 + + +---------------------------- +寮傛浜嬩欢 | +---------------------------- + # 浜嬩欢骞挎挱鍣ㄧ殑婧愮爜:ApplicationEventMulticaster + public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster"; + protected void initApplicationEventMulticaster() { + ConfigurableListableBeanFactory beanFactory = getBeanFactory(); + // 濡傛灉ioc宸茬粡鍖呭惈浜嗕簨浠跺箍鎾櫒, 鍒欎娇鐢↖OC鐨 + if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { + this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class); + if (logger.isTraceEnabled()) { + logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); + } + } + else { + // 鏈寘鍚, 鍒欐柊鍒涘缓 + this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory); + beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster); + if (logger.isTraceEnabled()) { + logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " + + "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]"); + } + } + } + + # 鎴戜滑鍙互鍒涘缓鑷繁鐨 SimpleApplicationEventMulticaster 鏉ヨ揪鍒拌嚜瀹氫箟鐨勫紓姝ュ箍鎾晥鏋 + * 鍐呴儴瀛樺湪涓涓嚎绋嬫睜鎵ц鍣, 鍙互鑷畾涔変负鑷繁鐨勭嚎绋嬫睜瀹炵幇 + @Nullable + private Executor taskExecutor; + + * spring 鎻愪緵浜嗕竴涓墽琛屽櫒鐨勫疄鐜:SimpleAsyncTaskExecutor + SimpleAsyncTaskExecutor() + SimpleAsyncTaskExecutor(String threadNamePrefix) + SimpleAsyncTaskExecutor(ThreadFactory threadFactory) + + * 浣嗘槸涓嶅缓璁娇鐢ㄨ鎵ц鍣, 鍥犱负瀹冧細涓烘瘡涓换鍔¢兘鏂板惎鍔ㄤ竴涓嚎绋 + * 娌℃湁杈惧埌绾跨▼姹犵殑鍔熻兘, 寤鸿鑷繁瀹炵幇, 璁剧疆绾跨▼姹 + + * 閰嶇疆浠g爜 + @Configuration + public class AsyncApplicationEventMulticaster { + + + @Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME) + public ApplicationEventMulticaster simpleApplicationEventMulticaster() { + + SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster(); + + //璁剧疆浠诲姟鎵ц鍣 + simpleApplicationEventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor()); + return simpleApplicationEventMulticaster; + } + } + + * 鎵鏈夌殑浜嬩欢鎵ц鏂规硶, 閮戒細浣跨敤 taskExecutor 鍘绘墽琛 + + + # 寮傛浜嬩欢鐨勫疄鐜, 涔熷彲浠ヤ娇鐢 @Async 娉ㄨВ鏉ュ畬鎴 + * 闇瑕佹敞瑙i┍鍔ㄥ紑鍚紓姝: @EnableAsync + * 閰嶇疆 @Async 灏卞彲浠ュ緢鐏垫椿鐨勯夋嫨, 鍝簺鏂规硶闇瑕佸悓姝ュ鐞, 鍝簺鏂规硶闇瑕佸紓姝ュ鐞 + + @EventListener + @Async + public void objectListener(Object event){ + System.out.println(Thread.currentThread().getName()); + System.out.println("common:" + event); + } + + * 榛樿灏变細閲囩敤:SimpleAsyncTaskExecutor 鏉ユ墽琛, 姣忎釜浠诲姟鏂规硶, 鏂板缓涓涓嚎绋嬫墽琛 + * 鍙互閫氳繃 @Async 鐨 value 灞炴, 鍘绘寚瀹氳浣跨敤鐨勭嚎绋嬫睜 bean name + + diff --git a/Spring-boot/springboot-fastjson.java b/Spring-boot/springboot-fastjson.java index f2cfe5d0..c064360b 100644 --- a/Spring-boot/springboot-fastjson.java +++ b/Spring-boot/springboot-fastjson.java @@ -12,29 +12,25 @@ ----------------------------- Spring boot 方法一 | ----------------------------- - 1,启动类继承 WebMvcConfigurerAdapter + 1,实现 WebMvcConfigurer 2,覆盖方法 configureMessageConverters # 代码 - @SpringBootApplication - public class Application extends WebMvcConfigurerAdapter { - public static void main(String[] args){ - SpringApplication.run(Application.class,args); - } - + @Configuration + public class WebMvcConfiguration implements WebMvcConfigurer { @Override public void configureMessageConverters(List> converters) { - super.configureMessageConverters(converters); - //定义 Converter 消息转换器 - FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); - fastJsonHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON)); - //定义 消息转换器配置对象 + WebMvcConfigurer.super.configureMessageConverters(converters); + FastJsonConfig fastJsonConfig = new FastJsonConfig(); - //进行配置操作 - fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); - //配置 converter 消息转换器 + fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); + fastJsonConfig.setCharset(StandardCharsets.UTF_8); + fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); + + FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); + fastJsonHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON_UTF8)); fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); - //把converter 添加到 converters 中 + converters.add(fastJsonHttpMessageConverter); } } @@ -50,27 +46,23 @@ public void configureMessageConverters(List> converters) */ @Configuration public class HttpMessageConverterConfiguration { - /** - * FastJsonpHttpMessageConverter4 - * @return - */ + @Bean - public HttpMessageConverters httpMessageConverter(){ - FastJsonpHttpMessageConverter4 fastJsonpHttpMessageConverter4 = new FastJsonpHttpMessageConverter4(); - fastJsonpHttpMessageConverter4.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON_UTF8)); + public HttpMessageConverters fastJsonHttpMessageConverter() { + + FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); + FastJsonConfig fastJsonConfig = new FastJsonConfig(); - fastJsonConfig.setCharset(StandardCharsets.UTF_8); - fastJsonConfig.setSerializerFeatures( - SerializerFeature.PrettyFormat, //格式化 - SerializerFeature.WriteMapNullValue, //输出null字段 - SerializerFeature.QuoteFieldNames, //使用双引号 - SerializerFeature.WriteNullListAsEmpty); //把null集合/数组输出为[] fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); - fastJsonpHttpMessageConverter4.setFastJsonConfig(fastJsonConfig); - return new HttpMessageConverters(fastJsonpHttpMessageConverter4); + fastJsonConfig.setCharset(StandardCharsets.UTF_8); + fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); + + fastJsonHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON_UTF8)); + fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); + + return new HttpMessageConverters(fastJsonHttpMessageConverter); } - - /** + /** * 跨域支持 * @return */ diff --git a/Spring-boot/springboot-freemarker.java b/Spring-boot/springboot-freemarker.java new file mode 100644 index 00000000..5fe58859 --- /dev/null +++ b/Spring-boot/springboot-freemarker.java @@ -0,0 +1,98 @@ +---------------------------------- +freemarker | +---------------------------------- + # 官方依赖 + + org.springframework.boot + spring-boot-starter-freemarker + + + # 配置项:FreeMarkerAutoConfiguration +spring: + freemarker: + # 是否开启,默认:true + enabled: true + # 是否开启模版引擎缓存,默认:false + cache: true + # 默认:text/html + content-type: text/html + # 编码,默认: urf-8 + charset: utf-8 + # 指定使用模板的视图列表,数组 + view-names: + - views + # 是否检查模板引擎目录是否存在,默认:true + check-template-location: true + # 视图前缀 + prefix: /temp + # 视图后缀 + suffix: .ftl + # 使用该属性引用到request对象(实质上是:RequestContext 对象) + request-context-attribute: req + # 是否把request域的属性添加到模板引擎,默认:false + expose-request-attributes: true + # 是否把session域的属性添加到模板引擎,默认:false + expose-session-attributes: true + # request的属性是否可以覆盖controller的model的同名项。默认:false,如果发生同名属性覆盖的情况会抛出异常 + allow-request-override: true + # session的属性是否可以覆盖controller的model的同名项。默认 false,如果发生同名属性覆盖的情况会抛出异常 + allow-session-override: false + # 是否要暴露spring提供的宏,默认:true: /org/springframework/web/servlet/view/freemarker/spring.ftl + expose-spring-macro-helpers: true + # 模板引擎加载目录,默认:classpath:/templates/ + template-loader-path: + - classpath:/templates/ + # 是否优先从文件系统加载模板引擎,支持热加载。默认:true + prefer-file-system-access: true + # freemarker中Configuration设置的配置。也就是定义在:freemarker.coreConfigurable 中的那些静态变量值 + settings: + datetime_format: yyyy-MM-dd HH:mm:ss + + + # 一般设置 +spring: + freemarker: + enabled: true + cache: false + content-type: text/html + charset: utf-8 + suffix: .ftl + # 引用request + request-context-attribute: request + # 暴露request域中的属性 + expose-request-attributes: true + # 暴露session域中的属性 + expose-session-attributes: true + expose-spring-macro-helpers: true + check-template-location: true + template-loader-path: + - classpath:/templates/ + settings: + datetime_format: yyyy-MM-dd HH:mm:ss + +---------------------------------- +配置 | +---------------------------------- + import javax.annotation.PostConstruct; + import javax.servlet.ServletContext; + + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.context.annotation.Configuration; + + import freemarker.template.TemplateModelException; + + @Configuration + public class FreemarkerConfiguration { + + @Autowired + private ServletContext servletContext; + + @Autowired + private freemarker.template.Configuration configuration; + + @PostConstruct + public void configuration() throws TemplateModelException { + //自定义配置信息 + this.configuration.setSharedVariable("ctx", this.servletContext.getContextPath()); + } + } \ No newline at end of file diff --git a/Spring-boot/springboot-https.java b/Spring-boot/springboot-https.java new file mode 100644 index 00000000..387bd22e --- /dev/null +++ b/Spring-boot/springboot-https.java @@ -0,0 +1,221 @@ + +-------------------------------------- +springboot配置 https | +-------------------------------------- + # 使用jdk系统工具生成证书 + * 使用的是$JAVA_HOME/bin/keytool 工具(JAVA里面自带的工具) + + * keytool -genkey -alias tomcat -validity 36500 -keystore D:\home\tomcat.keystore -keyalg RSA + + * -genkey :表示产生密钥对 + * -alias :表示起个别名 + * -keyalg :指定密钥算法 + * -validity :密钥有效时间,默认为90天,36500.表示100年 + * -keystore :指定密钥保存的路径 + + + + * 输入 keystore 密码 + 产生的证书,系统会使用一个密钥库来保存,这里就是设置密钥库的密码 + + * 您的名字与姓氏是什么? + 这一步很重要,表示为哪个网站生成数字证书,填写域名 + + * 您的组织单位名称是什么? + * 无视 + + * 您的组织名称是什么? + * 无视 + + * 您所在的城市或者区域名称是什么? + * 无视 + + * 您所在的洲,或省份是什么? + * 无视 + + * 该单位的两字母国家代码是什么? + * 无视 + + * CN=localhost,OU=Unknow,O=Unknow,L=Unknow,ST=Unknow,C=Unknow 正确吗? + * 确定输入: y + + * 输入 的主密码(如果和 keystore 密码相同,直接回车) + * 数字证书的密钥,和密钥库的密码是否相同. + * 这项较为重要,会在tomcat配置文件中使用,建议输入与keystore的密码一致,设置其它密码也可以 + + * OK,在~目录下,会生成 .keystore 一个证书文件 + * 至此,证书创建成功 + + + # 配置 + server.ssl.enabled=true + server.ssl.key-store=classpath:ssl/springboot.io.p12 + server.ssl.key-store-type=PKCS12/JKS + server.ssl.key-store-password=[key.store的密码] + + # http转向HTTPS + * 很多时候,地址栏输入的http,但是会被转发到https + * 实现这个功能需要'服务器的特定'配置来实现,就是上面说的特定配置(不同服务器用不同的) + TomcatEmbeddedServletContainerFactory + JettyEmbeddedServletContainerFactory + + * 代码 + @Configuration + public class TomcatConfiguration { + + @Bean + public EmbeddedServletContainerFactory embeddedServletContainerFactory(){ + TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory = new TomcatEmbeddedServletContainerFactory(){ + @Override + protected void postProcessContext(Context context) { + SecurityConstraint securityConstraint = new SecurityConstraint(); + securityConstraint.setUserConstraint("CONFIDENTIAL"); + SecurityCollection securityCollection = new SecurityCollection(); + securityCollection.addPattern("/*"); + securityConstraint.addCollection(securityCollection); + context.addConstraint(securityConstraint); + } + }; + tomcatEmbeddedServletContainerFactory.addAdditionalTomcatConnectors(httpConnectot()); + return tomcatEmbeddedServletContainerFactory; + } + + @Bean + public Connector httpConnectot(){ + //NIO连接器 + Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); + connector.setScheme("http"); + connector.setPort(8080); //监听端口 + connector.setSecure(false); + connector.setRedirectPort(8443); //转发端口 + return connector; + } + } + + +-------------------------------------- +springboot配置 http2 | +-------------------------------------- + # 必须先配置https + # 而且目前好像只有 undertow 支持 + + server: + port: 443 + servlet: + context-path: / + ssl: //开启http2必须要开启https + enabled: true + key-store: classpath:dev_ssl/javaweb.io.keystore + key-store-type: PKCS12 + key-store-password: a12551255 + http2: //开启HTTP2 + enabled: true + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-undertow + + + # 通过谷歌浏览器查看http2是否开启成功 + chrome://net-internals/#http2 + + # undertow配置80端口转发443 + import org.springframework.beans.factory.annotation.Value; + import org.springframework.boot.web.embedded.undertow.UndertowBuilderCustomizer; + import org.springframework.boot.web.embedded.undertow.UndertowDeploymentInfoCustomizer; + import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; + import org.springframework.boot.web.server.WebServerFactoryCustomizer; + import org.springframework.context.annotation.Configuration; + + import io.undertow.Undertow.Builder; + import io.undertow.server.HttpServerExchange; + import io.undertow.servlet.api.ConfidentialPortManager; + import io.undertow.servlet.api.DeploymentInfo; + import io.undertow.servlet.api.SecurityInfo; + import io.undertow.servlet.api.SecurityConstraint; + import io.undertow.servlet.api.TransportGuaranteeType; + import io.undertow.servlet.api.WebResourceCollection; + + @Configuration + public class UndertowConfiguration implements WebServerFactoryCustomizer { + + @Value("${server.ssl.enabled:false}") + private boolean sslEnable; + + @Value("${server.port}") + private Integer port; + + private static final Integer HTTP_PORT = 80; + + @Override + public void customize(UndertowServletWebServerFactory factory) { + + factory.setServerHeader("Apache/2.2.21"); + + //开启了https,则监听80端口,重定向 + if(sslEnable) { + + factory.addBuilderCustomizers(new UndertowBuilderCustomizer() { + @Override + public void customize(Builder builder) { + builder.addHttpListener(HTTP_PORT, "0.0.0.0"); + builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true); // 开启http2 + builder.setServerOption(UndertowOptions.HTTP2_SETTINGS_ENABLE_PUSH,true); // 开启Server Push + } + }); + + factory.addDeploymentInfoCustomizers(new UndertowDeploymentInfoCustomizer() { + @Override + public void customize(DeploymentInfo deploymentInfo) { + + SecurityConstraint securityConstraint = new SecurityConstraint(); + + WebResourceCollection webResourceCollection = new WebResourceCollection(); + webResourceCollection.addUrlPattern("/*"); + + securityConstraint.addWebResourceCollection(webResourceCollection); + securityConstraint.setTransportGuaranteeType(TransportGuaranteeType.CONFIDENTIAL); + securityConstraint.setEmptyRoleSemantic(SecurityInfo.EmptyRoleSemantic.PERMIT); + + deploymentInfo.addSecurityConstraint(securityConstraint); + deploymentInfo.setConfidentialPortManager(new ConfidentialPortManager() { + + @Override + public int getConfidentialPort(HttpServerExchange exchange) { + return port; + } + }); + } + + }); + } + } + } + + +-------------------------------------- +springboot配置 ssl双向验证 | +-------------------------------------- + # 配置 +server: + ssl: + enabled: true + key-store: classpath:ssl/localhost.keystore + key-store-type: JKS + key-store-password: 123456 + + # 需要验证客户端 + client-auth: NEED + trust-store: classpath:ssl/localhost.keystore + trust-store-type: JKS + trust-store-password: 123456 diff --git a/Spring-boot/springboot-i18n.java b/Spring-boot/springboot-i18n.java new file mode 100644 index 00000000..23a09ad5 --- /dev/null +++ b/Spring-boot/springboot-i18n.java @@ -0,0 +1,94 @@ +--------------------- +i18n 国际化 | +--------------------- + # 类库 + LocaleResolver + AbstractLocaleResolver + AbstractLocaleContextResolver + FixedLocaleResolver + SessionLocaleResolver + LocaleContextResolver + AbstractLocaleContextResolver + FixedLocaleResolver(固定的) + SessionLocaleResolver(根据session) + CookieLocaleResolver(根据cookie) + AcceptHeaderLocaleResolver(根据Header) + + + LocaleChangeInterceptor(国际化的拦截器) + + # 创建 LocaleResolver 实例 + * 根据需要选择不同的实现(建议Cookie) + @Bean + public LocaleResolver localeResolver() { + CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver(); + cookieLocaleResolver.setDefaultLocale(Locale.CHINA); + //还可以设置 cookie 的一大堆属性 + return cookieLocaleResolver; + } + + # 创建 LocaleChangeInterceptor 实例 + * 拦截需要国际化的请求 + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() { + LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); + // 指定切换语言的请求参数 + localeChangeInterceptor.setParamName("_lang"); + return localeChangeInterceptor; + } + + # 在classpath目录下创建文件 + i18n + |-message.properties + |-message_zh_CN.properties + |-message_en_US.properties + + * message 是文件的前缀 + * _en_US 是文件的local + + # yaml配置 + spring: + messages: + # 指定资源文件夹以及文件的前缀 + basename: i18n/message + # 使用freemarker模版引擎 + freemarker: + enabled: true + content-type: text/html + charset: utf-8 + suffix: .ftl + request-context-attribute: request + expose-request-attributes: true + expose-session-attributes: true + check-template-location: true + # 一定要暴露spring提供的宏 + expose-spring-macro-helpers: true + template-loader-path: + - classpath:/templates/ + - classpath:/email/ + settings: + datetime_format: yyyy-MM-dd HH:mm:ss + + # 在视图中的使用国际化的文字 + <#import "/spring.ftl" as spring/> + + <#-- 根据local环境读取name --> + <@spring.message code='name'/> + + # 使用请求参数切换环境 + ?_lang=zh_CN + ?_lang=en_US + + +--------------------- +LocaleContextHolder | +--------------------- + # 当前语言环境的容器 ThreadLocal + # 提供了一些静态方法 + Locale getLocale() + * 获取当前环境的 Locale 对象 + * 可以用于在视图页面判断当前的语言环境 + + + + \ No newline at end of file diff --git a/Spring-boot/springboot-jsp.java b/Spring-boot/springboot-jsp.java index 511b4845..1b713920 100644 --- a/Spring-boot/springboot-jsp.java +++ b/Spring-boot/springboot-jsp.java @@ -9,10 +9,8 @@ ------------------------------ Spring-boot 方案一 | ------------------------------ - 1,maven 创建 web项目 - * 其实就是WEB项目其实是无所谓,但是要确定目录: src/main/webapp 是存在的 - 2,在pom.xml中添加依赖 + 1,在pom.xml中添加依赖 javax.servlet @@ -23,16 +21,26 @@ javax.servlet javax.servlet-api + org.apache.tomcat.embed tomcat-embed-jasper - - 3,在application.properties中添加配置来支持JSP - spring.mvc.view.prefix=/WEB-INF/views + + 3,创建目录 src/main/webapp/WEB-INF/views + * 在该目录中存放jsp文件 + + 4,视图解析配置 + spring.mvc.view.prefix=/WEB-INF/views/ spring.mvc.view.suffix=.jsp + 5,程序返回视图 + @GetMapping + public ModelAndView index() { + return new ModelAndView("index"); + } + \ No newline at end of file diff --git a/Spring-boot/springboot-mybatis.java b/Spring-boot/springboot-mybatis.java index ef598fe5..bef2751e 100644 --- a/Spring-boot/springboot-mybatis.java +++ b/Spring-boot/springboot-mybatis.java @@ -25,16 +25,32 @@ 2,在 @SpringBootApplication 类上添加注解来扫描 mapper 接口 @MapperScan("com.xx"); - * 扫描指定包下的 mapper 接口 - * value 参数是一个数组,可以扫描多个 - * annotationClass 仅仅加载添加了指定注解的类 - + * 扫描指定包下的 mapper 接口 + * value 参数是一个数组,可以扫描多个 + * annotationClass 仅仅加载添加了指定注解的类 + * sqlSessionFactoryRef + * 创建这些mapper代理对象的时候 + * 使用指定名称的IOC中SqlSessionFactory实例 + * 可以用于多数据源实现的时候 + + sqlSessionTemplateRef + * 创建这些mapper代理对象的时候 + * 使用指定名称的IOC中sqlSessionTemplate实例 + * 可以用于多数据源实现的时候 + + @MapperScans + * 是一个组合注解,value值是多个 @MapperScan + + 3,在properties中配置参数 mybatis.mapper-locations[0]=classpath*:com/tedi/**/*mapper.xml * mapper文件地址,可以有多个,支持使用通配符 mybatis.config-location=classpath:mybatis/mybatis-config.xml * mybatis配置文件地址 - + ----------------------------------------------------------- + mybatis.mapper-locations[0]=classpath:mapper/**/*-mapper.xml + mybatis.mapper-locations[1]=classpath:mapper/**/*-mapper-ext.xml + mybatis.config-location=classpath:mybatis/mybatis-config.xml @@ -45,3 +61,82 @@ # 使用原始配置spring的方式进行整合 # spring 是怎么整合的,就怎么整合 + +---------------------------- +mybatis 多数据源 | +---------------------------- + # 第一个数据源和mybatis配置 + @Configuration + // 指定包下的mapper实例,使用test1SqlSessionTemplate(bean name) + @MapperScan(basePackages = "com.neo.mapper.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate") + public class DataSource1Config { + + // 实例化第一个数据源 + @Bean(name = "test1DataSource") + @ConfigurationProperties(prefix = "spring.datasource.test1") + @Primary // 使用自动注入的时候,存在多个相同类型实例,且未指定名称,它优先注入 + public DataSource testDataSource() { + return DataSourceBuilder.create().build(); + } + + // 根据数据源实例化第一个 SqlSessionFactory + @Bean(name = "test1SqlSessionFactory") + @Primary // 使用自动注入的时候,存在多个相同类型实例,且未指定名称,它优先注入 + public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception { + SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); + bean.setDataSource(dataSource); + return bean.getObject(); + } + + // 根据数据源实例化第一个 DataSourceTransactionManager + @Bean(name = "test1TransactionManager") + @Primary // 使用自动注入的时候,存在多个相同类型实例,且未指定名称,它优先注入 + public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + // 根据SqlSessionFactory实例化第一个 SqlSessionTemplate + @Bean(name = "test1SqlSessionTemplate") + @Primary + public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { + return new SqlSessionTemplate(sqlSessionFactory); + } + + } + + # 第二个数据源和mybatis配置 + @Configuration + @MapperScan(basePackages = "com.neo.mapper.test2", sqlSessionTemplateRef = "test2SqlSessionTemplate") + public class DataSource2Config { + + @Bean(name = "test2DataSource") + @ConfigurationProperties(prefix = "spring.datasource.test2") + public DataSource testDataSource() { + return DataSourceBuilder.create().build(); + } + + @Bean(name = "test2SqlSessionFactory") + public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource) throws Exception { + SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); + bean.setDataSource(dataSource); + return bean.getObject(); + } + + @Bean(name = "test2TransactionManager") + public DataSourceTransactionManager testTransactionManager(@Qualifier("test2DataSource") DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean(name = "test2SqlSessionTemplate") + public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { + return new SqlSessionTemplate(sqlSessionFactory); + } + + } + + + # 注入使用 + @Autowired Mapper1 + + @Autowired Mapper2 + diff --git a/Spring-boot/springboot-properties.java b/Spring-boot/springboot-properties.java index 23c6bf41..0a08b713 100644 --- a/Spring-boot/springboot-properties.java +++ b/Spring-boot/springboot-properties.java @@ -5,21 +5,53 @@ server.port=80 # WEB监听端口 - server.context-path=/ + server.servlet.context-path=/ # WEB访问路径 - //日志 + server.tomcat.basedir + # 设置tomcat的临时目录 + # 在linux系统中,springboot应用服务再启动(java -jar 命令启动服务)的时候,会在操作系统的/tmp目录下生成一个tomcat*的文件目录 + # 上传的文件先要转换成临时文件保存在这个文件夹下面 + # 由于临时/tmp目录下的文件,在长时间(10天)没有使用的情况下,就会被系统机制自动删除掉,所以如果系统长时间无人问津的话,就可能导致:The temporary upload location [/tmp/tomcat.1337767218595042057.80/work/Tomcat/localhost/ROOT] is not... + + //编码处理 + server.tomcat.uri-encoding=UTF-8 + spring.http.encoding.charset=UTF-8 + spring.http.encoding.enabled=true + //日期格式处理 + spring.mvc.date-format=yyyy-MM-dd HH:mm:ss + + //日志 logging.config=classpath:community-logback.xml # logback配置文件地址 - //静态文件 + //静态文件映射 spring.mvc.static-path-pattern=/static/** */ + # 设置静态资源的访问前缀,默认情况下,静态资源目录有 + * /static + * /public + * /resources + * /META-INF/resources + # 用于指定静态文件的目录(在classpath目录下-src/main/resources),允许外界直接访问 + + spring.resources.static-locations[0]= + # 也是静态资源的映射处理,是一个数组,支持多个 + # 支持 classpath:/ ,支持 file:/ //MyBatis mybatis.mapper-locations[0]=classpath*:mapper/**/Mapper.xml # mybatis mapper文件扫描地址 mybatis.config-location=classpath:mybatis/mybatis-config.xml - # mybatis配置文件地址 \ No newline at end of file + # mybatis配置文件地址 + + //导入外部配置文件 + spring.profiles.include[0]=datasource + spring.profiles.include[1]=redis + + //上传配置 + spring.servlet.multipart.max-file-size=30MB + spring.servlet.multipart.max-request-size=30MB + spring.servlet.multipart.enabled=true \ No newline at end of file diff --git a/Spring-boot/springboot-quartz.java b/Spring-boot/springboot-quartz.java new file mode 100644 index 00000000..7417b51e --- /dev/null +++ b/Spring-boot/springboot-quartz.java @@ -0,0 +1,28 @@ +-------------------------------- +quartz 整合 | +-------------------------------- + # 导入依赖 + + org.springframework.boot + spring-boot-starter-quartz + + + # 配置:QuartzProperties(配置类) +spring: + quartz: + # 枚举:MEMORY(默认),JDBC + job-store-type: + scheduler-name: + auto-startup: + startup-delay: + wait-for-jobs-to-complete-on-shutdown: false + overwrite-existing-jobs: false + properties: + jdbc: + schema: classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql + # 枚举:EMBEDDED(默认),ALWAYS,NEVER + initialize-schema: + comment-prefix: -- + + + # 任务类继承:QuartzJobBean diff --git a/Spring-boot/springboot-redis.java b/Spring-boot/springboot-redis.java index 22035bbf..88a68513 100644 --- a/Spring-boot/springboot-redis.java +++ b/Spring-boot/springboot-redis.java @@ -1,23 +1,60 @@ ------------------------------------ Redis-整合单机版 | ------------------------------------ - 1,配置文件 - spring.redis.host=localhost - spring.redis.port=6379 + # 使用lettuce + # 依赖 + + org.springframework.boot + spring-boot-starter-data-redis + + + io.lettuce + lettuce-core + + + org.apache.commons + commons-pool2 + - 3,导入依赖 - spring-boot-starter-redis + # 配置文件(配置类:org.springframework.boot.autoconfigure.data.redis.RedisProperties) + # Redis数据库索引(默认为0) + spring.redis.database=0 + # Redis服务器地址 + spring.redis.host=192.168.0.58 + # Redis服务器连接端口 + spring.redis.port=6379 + # Redis服务器连接密码(默认为空) + spring.redis.password= + # 连接池最大连接数(使用负值表示没有限制) + spring.redis.lettuce.pool.max-active=8 + # 连接池最大阻塞等待时间(使用负值表示没有限制) + spring.redis.lettuce.pool.max-wait=-1 + # 连接池中的最大空闲连接 + spring.redis.lettuce.pool.max-idle=8 + # 连接池中的最小空闲连接 + spring.redis.lettuce.pool.min-idle=0 + # 连接超时时间(毫秒) + spring.redis.timeout=2000 - 2,入口类开启缓存支持 - @EnableCaching + # 使用 + * StringRedisTemplate + * 是RedisTemplate的子类 + * 一般大多数都是使用它 + * 针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口 + ValueOperations 简单K-V操作 + SetOperations set类型数据操作 + ZSetOperations zset类型数据操作 + HashOperations 针对map类型的数据操作 + ListOperations 针对list类型的数据操作 + * demo + @Autowired + private StringRedisTemplate stringRedisTemplate; + + stringRedisTemplate.opsForValue(); //获取操作简单k-v的api + stringRedisTemplate.opsForSet(); //获取操作set的api - 3,在需要的地方使用缓存 - @Cacheable - # 设置缓存 - # 属性 - value - * 指定缓存的key - # 表示在某个方法,当再次调用该方法的时候,会先去缓存中获取值 + # 非阻塞的客户端 + ReactiveRedisTemplate ------------------------------------ @@ -25,4 +62,108 @@ ------------------------------------ - \ No newline at end of file + +------------------------------------ +Redis- scan 代替 keys * | +------------------------------------ + public void scan(String pattern, Consumer consumer) { + this.stringRedisTemplate.execute((RedisConnection connection) -> { + try (Cursor cursor = connection.scan(ScanOptions.scanOptions().count(Long.MAX_VALUE).match(pattern).build())) { + cursor.forEachRemaining(consumer); + return null; + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + }); + } + +------------------------------------ +Redis- 过期key的监听 | +------------------------------------ + # redis必须开启配置 + notify-keyspace-events "Ex" # 监听key的过期事件 + + # configuration的配置 + @Configuration + public class RedisConfiguration { + + @Autowired + private RedisConnectionFactory redisConnectionFactory; + + @Bean + public RedisMessageListenerContainer redisMessageListenerContainer() { + //创建监听容器 + RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); + //设置连接工厂 + redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory); + return redisMessageListenerContainer; + } + + @Bean + public KeyExpiredListener keyExpiredListener() { + return new KeyExpiredListener(this.redisMessageListenerContainer()); + } + } + + # KeyExpiredListener 监听器 + import java.nio.charset.StandardCharsets; + + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import org.springframework.data.redis.connection.Message; + import org.springframework.data.redis.listener.KeyExpirationEventMessageListener; + import org.springframework.data.redis.listener.RedisMessageListenerContainer; + + public class KeyExpiredListener extends KeyExpirationEventMessageListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(KeyExpiredListener.class); + + public KeyExpiredListener(RedisMessageListenerContainer listenerContainer) { + super(listenerContainer); + } + + @Override + protected void doHandleMessage(Message message) { + String channel = new String(message.getChannel(), StandardCharsets.UTF_8); + String key = new String(message.getBody(), StandardCharsets.UTF_8); + LOGGER.info("redis key 过期:channel={},key={}", channel, key); + } + } + +------------------------------------ +Redis - 自定义Template序列化 | +------------------------------------ + # 自定义 Templete 类,实现 RedisTemplate,泛型为key和Value + # 实例化该类 + - 设置连接工厂 + - key的序列化类 + - value的序列化列 + - 其他redis数据结构的k/v序列化类 + + # 简单的fastjson序列化 + public class JsonRedisTemplate extends RedisTemplate { + } + + @Bean + public JsonRedisTemplate jsonRedisTemplate() { + JsonRedisTemplate jsonRedisTemplate = new JsonRedisTemplate(); + jsonRedisTemplate.setConnectionFactory(this.redisConnectionFactory); + jsonRedisTemplate.setKeySerializer(StringRedisSerializer.UTF_8); + jsonRedisTemplate.setValueSerializer(new RedisSerializer() { + @Override + public byte[] serialize(JSONObject t) throws SerializationException { + return t.toJSONString().getBytes(StandardCharsets.UTF_8); + } + @Override + public JSONObject deserialize(byte[] bytes) throws SerializationException { + if(bytes == null) { + return null; + } + return JSON.parseObject(new String(bytes, StandardCharsets.UTF_8)); + } + }); + // jsonRedisTemplate.setHashKeySerializer(hashKeySerializer); + // jsonRedisTemplate.setHashValueSerializer(); + return jsonRedisTemplate; + } \ No newline at end of file diff --git a/Spring-boot/springboot-task.java b/Spring-boot/springboot-task.java index ffe01151..a05ce4ec 100644 --- a/Spring-boot/springboot-task.java +++ b/Spring-boot/springboot-task.java @@ -1,8 +1,7 @@ ---------------------- task | ---------------------- - - 1,娉ㄨВ閰嶇疆 + # 娉ㄨВ閰嶇疆 * main绋嬪簭鍏ュ彛閰嶇疆:@EnableScheduling * 宸ヤ綔绫婚厤缃:@Component * 宸ヤ綔鏂规硶閰嶇疆:@Scheduled @@ -17,10 +16,14 @@ String initialDelayString() default ""; - 2,澶勭悊寮傚父 + # 澶勭悊寮傚父 No qualifying bean of type 'java.util.concurrent.ScheduledExecutorService' available * 濂藉儚鏄洜涓烘棩蹇楁墍浜х敓鐨勫紓甯 * 澶勭悊鏂瑰紡,淇敼鏃ュ織閰嶇疆鏂囦欢 - \ No newline at end of file + + + + # 榛樿浣跨敤绾跨▼:scheduling 鍘绘墽琛 + * 鍙互浣跨敤 @Async 娉ㄨВ, 浣跨敤鑷畾涔夌殑绾跨▼姹犲幓鎵ц \ No newline at end of file diff --git a/Spring-boot/springboot-test.java b/Spring-boot/springboot-test.java new file mode 100644 index 00000000..ed333e7f --- /dev/null +++ b/Spring-boot/springboot-test.java @@ -0,0 +1,53 @@ +---------------------------- +springboot的单元测试 | +---------------------------- + # 导入依赖 + + org.springframework.boot + spring-boot-starter-test + test + + + # 测试代码 + import io.springboot.jpa.JpaApplication; + import io.springboot.jpa.repository.UserRepository; + import junit.framework.TestCase; + import org.junit.Test; + import org.junit.Before; + import org.junit.After; + import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.boot.test.context.SpringBootTest; + import org.springframework.test.context.junit4.SpringRunner; + + + + @RunWith(SpringRunner.class) + // 设置@SpringBootApplication 类 + @SpringBootTest(classes = JpaApplication.class) + public class JpaTest { + + // 可以使用IOC + @Autowired + private UserRepository userRepository; + + @Before + public void testBefore(){ + System.out.println("测试前"); + } + + @After + public void testAfter(){ + System.out.println("测试后"); + } + + @Test + public void test(){ + //TODO 执行测试代码 + + // 断言非空 + TestCase.assertNotNull(); + // 断言equals + TestCase.assertEquals(); + } + } \ No newline at end of file diff --git a/Spring-boot/springboot-websocket.java b/Spring-boot/springboot-websocket.java index 801fb272..ff1e9363 100644 --- a/Spring-boot/springboot-websocket.java +++ b/Spring-boot/springboot-websocket.java @@ -37,4 +37,39 @@ public ServerEndpointExporter serverEndpointExporter (){ } # 注意 - * @OnError 要添加参数:Throwable ,不然启动异常 \ No newline at end of file + * @OnError 要添加参数:Throwable ,不然启动异常 + + # Demo + @Component + @ServerEndpoint(value = "/channel/test") + public class TestEndpoint { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestEndpoint.class); + + private Session session; + + @OnMessage(maxMessageSize = 10) + public void onMessage(byte[] message){ + //skip + } + + @OnOpen + public void onOpen(Session session, EndpointConfig endpointConfig){ + LOGGER.info("新的连接,id={}",session.getId()); + session.setMaxIdleTimeout(0); + this.session = session; + } + + @OnClose + public void onClose(CloseReason closeReason){ + LOGGER.info("连接断开,id={} reason={}",this.session.getId(),closeReason); + } + + @OnError + public void onError(Throwable throwable) throws IOException { + LOGGER.info("连接异常,id={},throwable={}",this.session.getId(),throwable); + this.session.close(); + throwable.printStackTrace(); + } + + } \ No newline at end of file diff --git "a/Spring-boot/springboot-web\351\241\271\347\233\256.java" "b/Spring-boot/springboot-web\351\241\271\347\233\256.java" index 03d6b42d..467652a9 100644 --- "a/Spring-boot/springboot-web\351\241\271\347\233\256.java" +++ "b/Spring-boot/springboot-web\351\241\271\347\233\256.java" @@ -57,28 +57,38 @@ public void addInterceptors(InterceptorRegistry registry) { * /public * /resources * /META-INF/resources - # 以上文件夹中的静态文件,直接可以映射为静态文件夹目录,来进行访问 + # 以上文件夹中的静态文件,通过配置静态资源pattern,来进行访问 + spring.mvc.static-path-pattern=/static/** */ # webjar,的静态资源映射 * webjar,就是jar中有jar. * /META-INF/resources/webjars/ 下的静态文件映射为: /web/jar/** */ - # 静态资源访问失败 + # 静态资源配置 * 解决方案一 * 自定义配置类,实现 WebMvcConfigurerAdapter ,覆写方法 - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); - super.addResourceHandlers(registry); - } - * 该方法还可以访问外部文件(使用"file:"开头)) - registry.addResourceHandler("/upload/**").addResourceLocations("file:"+ TaleUtils.getUplodFilePath()+"upload/"); + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + //static 静态资源目录 + registry.addResourceHandler("/static/**") + //src/main/resources/static + .addResourceLocations("classpath:/static/") + //本地d盘下的pic, + .addResourceLocations("file:D:/pic/"); + } + * 一个 ResourceHandler 映射了多个 ResourceLocations,如果出现相同路径,相同文件,则谁先映射谁优先 + * 上面代码:static/index.js 要优先于 pic/index.js 加载 * 解决方案二 - * 在application.properties 添加配置 + * 在application.properties 添加配置,设置静态资源的访问路径 spring.mvc.static-path-pattern=/static/** */ * '注意,后面不能有空格,这里完全是为了处理掉 /** Java的注释冲突' + + * 在application.properties 添加配置,指定静态资源的位置 + local.image.folder=d:/pic/ + spring.resources.static-locations[0]=classpath:/static/ + spring.resources.static-locations[1]=file:${local.image.folder} -------------------------------- Spring-boot 视图映射 | @@ -125,6 +135,21 @@ public void addCorsMappings(CorsRegistry registry) { .maxAge(3600); } } +-------------------------------- +Spring-boot 自定义参数类型转换器| +-------------------------------- + # 注册组件 + ConversionServiceFactoryBean + @Bean + public ConversionServiceFactoryBean enumConversionService() { + ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean(); + Set> converters = new HashSet<>(); + converters.add(new EnumConverter()); + conversionServiceFactoryBean.setConverters(converters); + return conversionServiceFactoryBean; + } + * Converter 参考springmvc知识点 + * '如果是Date转换器,记得要配置:spring.mvc.date-format' -------------------------------- Spring-boot 注册WEB三大组件 | diff --git a/Spring-boot/springboot-yml.java b/Spring-boot/springboot-yml.java new file mode 100644 index 00000000..439a95c6 --- /dev/null +++ b/Spring-boot/springboot-yml.java @@ -0,0 +1,94 @@ +---------------------------- +yml 配置文件 | +---------------------------- + # springboot默认使用 properties和yml类型的配置文件 + application.yml + application.properties + + # yml(YAML Ain t,Markup Language) + * 标记语言,比JSON,xml,properties 更适合做配置文件 + + +---------------------------- +yml 语法详解 | +---------------------------- + # 使用缩进表示层级关系 + # 缩进不允许使用Tab,只允许空格 + # 缩进空格数目不重要,但是相同层级的元素必须左侧对齐(python一样) + # 大小写敏感 + + # yml支持三种数据结构 + 1,对象 + * 键值对的集合 + frends: + name: Litch + age: 23 + + * 行业写法也是允许的 + frends: {name:Kevin,age23} + + 2,数组 + * 一组按次序排序的值,使用:短横线 空格 值 + skill: + - Java + - Python + - Javascript + + * 支持行内写法 + skill: [Java,Python,Javascript] + + 3,字面量 + * 单个的,不可再分的值 + Kevin: + name: Kevin + age: 23 + + * 双引号不会转义字符串里面的特殊字符 + name: "Kevin\n" //\n会换行 + + * 单引号,会转义特殊字符 + name: 'Kevin\n' //\n 会被转义输出 + + # 数据结构可以相互的交错 + class Dog{ + String name; + Integer age; + } + + @ConfigurationProperties(prefix = "Person") + class Person{ + String lastName; + Integer age; + Boolean boss; + Date birth; + Map maps; + List lists; + Dog dog; + } + + + Person: + lastName: Kevin + last-name: Kevin + + age: 23 + boss: false + birth: 2017/12/12 + + maps: + key1: value1 + key2: value2 + maps: {key1: value1,key2: values} + + lists: + - item1 + - item2 + lists: [item1,item2] + + dog: + name: Litch + age: 23 + + # 同样可以使用 @Value 注解 + @Value("${Person.lastName}") + diff --git "a/Spring-boot/springboot-\346\226\207\344\273\266\344\270\212\344\274\240.java" "b/Spring-boot/springboot-\344\270\212\344\274\240\344\270\213\350\275\275.java" similarity index 59% rename from "Spring-boot/springboot-\346\226\207\344\273\266\344\270\212\344\274\240.java" rename to "Spring-boot/springboot-\344\270\212\344\274\240\344\270\213\350\275\275.java" index afc44b91..59675a1b 100644 --- "a/Spring-boot/springboot-\346\226\207\344\273\266\344\270\212\344\274\240.java" +++ "b/Spring-boot/springboot-\344\270\212\344\274\240\344\270\213\350\275\275.java" @@ -27,7 +27,22 @@ public MultipartConfigElement multipartConfigElement() { @RequestParam("name") String name //正常接收其他的参数 + # 通过配置来限制上传大小 + spring.servlet.multipart.max-file-size=30MB + # 最大的单个文件大小 + spring.servlet.multipart.max-request-size=30MB + # 最大的请求大小 + spring.servlet.multipart.enabled=true + # 是否支持文件上传 + spring.servlet.multipart.location=/temp + # 磁盘临时目录 + spring.servlet.multipart.resolve-lazily=false + # 是否延迟解析,默认 false + spring.servlet.multipart.file-size-threshold=0 + # 文件大小阈值,当大于这个阈值时将写入到磁盘(临时目录),否则存在内存中.默认0 + * 配置类:MultipartProperties + --------------------------- Spring-boot 批量上传 | --------------------------- @@ -41,4 +56,19 @@ public MultipartConfigElement multipartConfigElement() { List files = ((MultipartHttpServletRequest)request).getFiles("file"); 2,第二种方式 - @RequestParam("files") MultipartFile[] file \ No newline at end of file + @RequestParam("files") MultipartFile[] file + +--------------------------- +Spring-boot 文件下载 | +--------------------------- + + # 使用 ResponseEntity 响应数据 + @GetMapping("/files/{filename:.+}") + @ResponseBody + public ResponseEntity serveFile(@PathVariable String filename) { + Resource file = storageService.loadAsResource(filename); + return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename=\"" + file.getFilename() + "\"").body(file); + } + + # 使用 StreamingResponseBody 响应数据 + \ No newline at end of file diff --git "a/Spring-boot/springboot-\344\272\213\345\212\241.java" "b/Spring-boot/springboot-\344\272\213\345\212\241.java" index 742b7da4..1517e629 100644 --- "a/Spring-boot/springboot-\344\272\213\345\212\241.java" +++ "b/Spring-boot/springboot-\344\272\213\345\212\241.java" @@ -46,4 +46,42 @@ public class transactionManagementAutoConfiguration { * spring boot专门用户配置事务的类是:org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration * 'spring boot中不需要显示开启使用'@EnableTransactionManagement 注解,'直接在哪里标注' + * 添加依赖 + + org.springframework + spring-tx + + + * 添加注解 + @EnableTransactionManagement + # 使用监听器, 监听事务的状态 + * 使用:@TransactionalEventListener 注解, 标识在监听方法 + @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @EventListener + public @interface TransactionalEventListener { + + TransactionPhase phase() default TransactionPhase.AFTER_COMMIT; + + boolean fallbackExecution() default false; + + @AliasFor(annotation = EventListener.class, attribute = "classes") + Class[] value() default {}; + + */ + @AliasFor(annotation = EventListener.class, attribute = "classes") + Class[] classes() default {}; + + String condition() default ""; + + } + + * phase 枚举, 表示感兴趣的事件 + BEFORE_COMMIT, + AFTER_COMMIT, + AFTER_ROLLBACK, + AFTER_COMPLETION + + * fallbackExecution diff --git "a/Spring-boot/springboot-\345\205\245\351\227\250.java" "b/Spring-boot/springboot-\345\205\245\351\227\250.java" index 126b8d20..c51c6fc4 100644 --- "a/Spring-boot/springboot-\345\205\245\351\227\250.java" +++ "b/Spring-boot/springboot-\345\205\245\351\227\250.java" @@ -22,20 +22,56 @@ org.springframework.boot spring-boot-starter-parent - 1.5.1.RELEASE + 1.5.8.RELEASE + - - org.springframework.boot - spring-boot-starter-web - - + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot spring-boot-maven-plugin + + true + + true + + + # 如果需要自定义 parent模块,但是又想继承springboot的依赖 + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + # 主函数,直接启动代码 import org.springframework.boot.SpringApplication; diff --git "a/Spring-boot/springboot-\345\205\250\345\261\200\345\274\202\345\270\270.java" "b/Spring-boot/springboot-\345\205\250\345\261\200\345\274\202\345\270\270.java" index 37fe506d..1b332fc5 100644 --- "a/Spring-boot/springboot-\345\205\250\345\261\200\345\274\202\345\270\270.java" +++ "b/Spring-boot/springboot-\345\205\250\345\261\200\345\274\202\345\270\270.java" @@ -115,4 +115,20 @@ public ModelAndView error(HttpServletRequest request, public String getErrorPath() { return ERROR_PATH; } - } \ No newline at end of file + } + + org.springframework.web.util.WebUtils + + static final String JAVAX_SERVLET_ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name"; + static final String JAVAX_SERVLET_ERROR_EXCEPTION = "javax.servlet.error.exception"; + static final String JAVAX_SERVLET_ERROR_REQUEST_URI = "javax.servlet.error.request_uri"; + static final String JAVAX_SERVLET_ERROR_MESSAGE = "javax.servlet.error.message"; + static final String JAVAX_SERVLET_ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type"; + static final String JAVAX_SERVLET_ERROR_STATUS_CODE = "javax.servlet.error.status_code"; + + Throwable throwable = (Throwable) request.getAttribute(JAVAX_SERVLET_ERROR_EXCEPTION); + Integer statusCode = (Integer) request.getAttribute(JAVAX_SERVLET_ERROR_STATUS_CODE); + String errorUri = (String) request.getAttribute(JAVAX_SERVLET_ERROR_REQUEST_URI); + String servletName = (String) request.getAttribute(JAVAX_SERVLET_ERROR_SERVLET_NAME); + String errorMessage = (String) request.getAttribute(JAVAX_SERVLET_ERROR_MESSAGE); + Class throwableType = (Class) request.getAttribute(JAVAX_SERVLET_ERROR_EXCEPTION_TYPE); \ No newline at end of file diff --git "a/Spring-boot/springboot-\345\212\250\346\200\201\346\225\260\346\215\256\346\272\220.java" "b/Spring-boot/springboot-\345\212\250\346\200\201\346\225\260\346\215\256\346\272\220.java" index e51bf771..f068d37f 100644 --- "a/Spring-boot/springboot-\345\212\250\346\200\201\346\225\260\346\215\256\346\272\220.java" +++ "b/Spring-boot/springboot-\345\212\250\346\200\201\346\225\260\346\215\256\346\272\220.java" @@ -11,37 +11,56 @@ ---------------------------- 1,配置文件 | ---------------------------- - jdbc.datasource.masterurl=jdbc:mysql://localhost:3306/community?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true - jdbc.datasource.masterusername=root - jdbc.datasource.masterpassword=root +daynamic: + datasources: + # master库,唯一只能有一个且名称不可修改 + master: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8 + username: KevinBlandy + password: 123456 - jdbc.datasource.slaveurl[0]=jdbc:mysql://localhost:3306/community?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true - jdbc.datasource.slaveusername[0]=root - jdbc.datasource.slavepassword[0]=root + public-key: + initialSize: 2 + maxActive: 50 + filters: stat,config + connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=${daynamic.datasources.master.public-key} - jdbc.datasource.slaveurl[1]=jdbc:mysql://localhost:3306/community?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true - jdbc.datasource.slaveusername[1]=root - jdbc.datasource.slavepassword[1]=root - - jdbc.datasource.slaveurl[2]=jdbc:mysql://localhost:3306/community?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true - jdbc.datasource.slaveusername[2]=root - jdbc.datasource.slavepassword[2]=root - - jdbc.datasource.slaveurl[3]=jdbc:mysql://localhost:3306/community?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true - jdbc.datasource.slaveusername[3]=root - jdbc.datasource.slavepassword[3]=root - - jdbc.datasource.driverclassname=com.mysql.jdbc.Driver - jdbc.datasource.maxactive=20 - jdbc.datasource.initialsize=10 + # slave库,名称唯一可以有多个 + slave_1: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8 + username: KevinBlandy + password: 123456 + + public-key: + initialSize: 2 + maxActive: 50 + filters: stat,config + connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=${daynamic.datasources.slave_1.public-key} + slave_2: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8 + username: KevinBlandy + password: 123456 + + public-key: + initialSize: 2 + maxActive: 50 + filters: stat,config + connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=${daynamic.datasources.slave_2.public-key} ---------------------------- 2,DataSourceAspect | ---------------------------- - package com.kevin.boot.web.datasource; + import java.lang.reflect.Method; + import java.lang.reflect.ParameterizedType; + import java.lang.reflect.Type; + import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; + import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,65 +68,59 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; - import java.lang.reflect.Method; - import java.lang.reflect.ParameterizedType; - import java.lang.reflect.Type; + import io.springcloud.datasource.DynamicDataSourceHolder; - /** - * 动态数据源切面,必需保证该切面最先执行 - */ - @Component + @Order(-9999) @Aspect - @Order(-999) //务必保证最先执行 - public class DataSourceAspect { - private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceAspect.class); - /** - * 在进入Service方法之前执行 - * @param point - * @throws NoSuchMethodException - */ - @Before(value = "execution(* com.kevin.boot.web.service..*.*(..))") - public void before(JoinPoint point) throws NoSuchMethodException { - //拦截的实体对象 - Object target = point.getTarget(); - //拦截的方法名称 - String methodName = point.getSignature().getName(); - //拦截的方法参数 - Object[] args = point.getArgs(); - //拦截的方法参数类型 - Class[] parameterTypes = ((MethodSignature)point.getSignature()).getMethod().getParameterTypes(); - //获取到的目标方法 + @Component + public class DaynamicDatasouceAop { + + private static final Logger LOGGER = LoggerFactory.getLogger(DaynamicDatasouceAop.class); + + @Pointcut(value = "execution(* io.springcloud.service.*.*(..))") + public void service() { + } + + @Before("service()") + public void beforService(JoinPoint joinPoint) throws NoSuchMethodException, SecurityException { + + Object target = joinPoint.getTarget(); + + String methodName = joinPoint.getSignature().getName(); + + Object[] args = joinPoint.getArgs(); + + Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes(); + Method method = null; - //通过反射获得拦截的method + method = target.getClass().getMethod(methodName, parameterTypes); - //如果是桥则要获得实际拦截的method - if(method.isBridge()){ - for(int i = 0; i < args.length; i++){ - //获得泛型类型 - Class genClazz = this.getSuperClassGenricType(target.getClass(),0); - //根据实际参数类型替换parameterType中的类型 - if(args[i].getClass().isAssignableFrom(genClazz)){ + + if (method.isBridge()) { + for (int i = 0; i < args.length; i++) { + Class genClazz = getSuperClassGenricType(target.getClass(), 0); + if (args[i].getClass().isAssignableFrom(genClazz)) { parameterTypes[i] = genClazz; } } - //获得parameterType参数类型的方法 method = target.getClass().getMethod(methodName, parameterTypes); } - LOGGER.info("当前事务方法 " + methodName); + + LOGGER.debug("当前事务方法 " + methodName); + Transactional transactional = method.getAnnotation(Transactional.class); + if(transactional != null && transactional.readOnly()){ - //读库 - LOGGER.info("动态数据源 - 读库"); + LOGGER.debug("动态数据源 - 读库"); DynamicDataSourceHolder.markSlave(); }else { - //写库 - LOGGER.info("动态数据源 - 写库"); + LOGGER.debug("动态数据源 - 写库"); DynamicDataSourceHolder.markMaster(); } } - - public Class getSuperClassGenricType(Class clazz, int index) { - Type genType = clazz.getGenericSuperclass(); // 得到泛型父类 + + public Class getSuperClassGenricType(Class clazz, int index) { + Type genType = clazz.getGenericSuperclass(); if (!(genType instanceof ParameterizedType)) { return Object.class; } @@ -115,15 +128,13 @@ public Class getSuperClassGenricType(Class clazz, int index) { if (!(params[index] instanceof Class)) { return Object.class; } - return (Class) params[index]; + return (Class) params[index]; } } ---------------------------- 3,DynamicDataSource | ---------------------------- - package com.kevin.boot.web.datasource; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; @@ -137,136 +148,86 @@ public Class getSuperClassGenricType(Class clazz, int index) { import java.util.concurrent.atomic.AtomicInteger; /** - * 动态数据源实现 + * Created by KevinBlandy on 2017/10/30 14:05 */ public class DynamicDataSource extends AbstractRoutingDataSource { + private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class); - /** - * 读库的数量 - */ - private Integer slaveCount; - - // 轮询计数,初始为-1 + private AtomicInteger counter = new AtomicInteger(-1); - - // 记录读库的key(配置在ioc中) - private List slaveDataSources = new ArrayList(0) ; - - /** - * 该方法的返回值,会作为一个Map中的key,去获取对应的数据源. - * 所以,每次请求.只要改变这个key,就可以改变当此次请求,在持久层会话中注入的数据源,实现自动的读写分离 - */ + + private List slaveDataSources = new ArrayList<>(0); + @Override protected Object determineCurrentLookupKey() { - /** - * 如果是主库,或者是从库数量为0 - */ Object key = null; - if (DynamicDataSourceHolder.isMaster() || this.slaveCount == 0) { - key = DynamicDataSourceHolder.getDataSourceKey(); - return key; + if (DynamicDataSourceHolder.isMaster() || this.slaveDataSources.isEmpty()) { + key = DynamicDataSourceHolder.MASTER; + } else { + key = this.getSlaveKey(); } - key = this.getSlaveKey(); - LOGGER.info("Datasource key={}",key); + LOGGER.debug("动态数据源 dataSourceKey = {}", key); return key; - } - /** - * 父类实现了InitializingBean,Spring会在Bean初始化完成后回调 afterPropertlesSet方法 - * 通过该方法,获取到所有的读库key,存储到集合 - */ + @SuppressWarnings("unchecked") @Override public void afterPropertiesSet() { super.afterPropertiesSet(); - // 由于父类的resolvedDataSources属性是私有的子类获取不到,需要使用反射获取 Field field = ReflectionUtils.findField(AbstractRoutingDataSource.class, "resolvedDataSources"); - field.setAccessible(true); // 设置可访问 + field.setAccessible(true); try { - //传递当前对象,反射执行该字段的get方法,获取到所有的数据源 Map resolvedDataSources = (Map) field.get(this); - /** - * 读库的数据量等于数据源总数减去写库的数量,写库数量为 1 - */ - this.slaveCount = resolvedDataSources.size() - 1; for (Map.Entry entry : resolvedDataSources.entrySet()) { - //遍历所有的key if (DynamicDataSourceHolder.MASTER.equals(entry.getKey())) { - //如果是写库,跳过之 continue; } - //添加写库的key到集合 slaveDataSources.add(entry.getKey()); } } catch (Exception e) { - e.printStackTrace(); LOGGER.error("afterPropertiesSet error! ", e); } } - - /** - * 轮询算法实现 - */ + public Object getSlaveKey() { - Integer index = counter.incrementAndGet() % slaveCount; - if (counter.get() > 9999) { // 以免超出Integer范围 - counter.set(-1); // 还原 + Integer index = counter.incrementAndGet() % this.slaveDataSources.size(); + if (counter.get() > 9999) { + counter.set(-1); } return slaveDataSources.get(index); } } + ---------------------------- 3,DynamicDataSourceHolder | ---------------------------- - package com.kevin.boot.web.datasource; - - /** - * ThreadLocal记录当前线程的DB key - */ public class DynamicDataSourceHolder { - //写库标识 - public static final String MASTER = "MASTER"; - - //读库标识 - private static final String SLAVE = "SLAVE"; - - //使用ThreadLocal记录当前线程的数据源标识 - private static final ThreadLocal holder = new ThreadLocal(); - - /** - * 设置数据源标识 - */ + + public static final String MASTER = "master"; + + private static final String SLAVE = "slave"; + + private static final ThreadLocal HOLDER = new ThreadLocal<>(); + private static void putDataSourceKey(String key) { - holder.set(key); + HOLDER.set(key); } - - /** - * 获取数据源key - */ + public static String getDataSourceKey() { - return holder.get(); + return HOLDER.get(); } - /** - * 标记写库 - */ - public static void markMaster(){ + public static void markMaster() { putDataSourceKey(MASTER); } - - /** - * 标记读库 - */ - public static void markSlave(){ + + public static void markSlave() { putDataSourceKey(SLAVE); } - - /** - * 是否是主库 - */ + public static boolean isMaster() { - return MASTER.equals(holder.get()); + return MASTER.equals(HOLDER.get()); } } @@ -274,180 +235,100 @@ public static boolean isMaster() { ---------------------------- 4,DataSourceConfig | ---------------------------- - package com.kevin.boot.web.config; + import java.util.HashMap; + import java.util.Map; - import com.alibaba.druid.pool.DruidDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; - import javax.sql.DataSource; - import java.util.ArrayList; - import java.util.List; + import com.alibaba.druid.pool.DruidDataSource; - /** - * Created by KevinBlandy on 2017/2/28 14:07 - * 数据库配置,他妈的说好的 "属性宽松的规则" 呢? - */ - @ConfigurationProperties(prefix = "jdbc.datasource",ignoreInvalidFields = false) - public class DataSourceConfig { - private String masterurl; - private String masterusername; - private String masterpassword; - - private List slaveurl = new ArrayList(); - private List slaveusername = new ArrayList(); - private List slavepassword = new ArrayList(); - - private String driverclassname; - private int maxactive; - private int initialsize; - - public String getMasterurl() { - return masterurl; - } - - public void setMasterurl(String masterurl) { - this.masterurl = masterurl; - } - - public String getMasterusername() { - return masterusername; - } - - public void setMasterusername(String masterusername) { - this.masterusername = masterusername; - } - - public String getMasterpassword() { - return masterpassword; - } - - public void setMasterpassword(String masterpassword) { - this.masterpassword = masterpassword; - } - - public List getSlaveurl() { - return slaveurl; - } - - public void setSlaveurl(List slaveurl) { - this.slaveurl = slaveurl; - } - - public List getSlaveusername() { - return slaveusername; - } - - public void setSlaveusername(List slaveusername) { - this.slaveusername = slaveusername; - } - - public List getSlavepassword() { - return slavepassword; - } - - public void setSlavepassword(List slavepassword) { - this.slavepassword = slavepassword; - } - - public String getDriverclassname() { - return driverclassname; - } - - public void setDriverclassname(String driverclassname) { - this.driverclassname = driverclassname; - } - - public int getMaxactive() { - return maxactive; - } - - public void setMaxactive(int maxactive) { - this.maxactive = maxactive; - } + + @ConfigurationProperties(prefix = "daynamic") + public class DynamicDataSourceConfig { - public int getInitialsize() { - return initialsize; - } + private Map datasources = new HashMap<>(); - public void setInitialsize(int initialsize) { - this.initialsize = initialsize; + public Map getDatasources() { + return datasources; } - - /** - * 根据配置获取Master连接池对象 - * @return - */ - public DataSource getMasterDataSource(){ - DruidDataSource masterDataSource = new DruidDataSource(); - masterDataSource.setUrl(this.getMasterurl()); - masterDataSource.setUsername(this.getMasterusername()); - masterDataSource.setPassword(this.getMasterpassword()); - masterDataSource.setMaxActive(this.getMaxactive()); - masterDataSource.setInitialSize(this.getInitialsize()); - masterDataSource.setDriverClassName(this.getDriverclassname()); - return masterDataSource; + + public void setDatasources(Map datasources) { + this.datasources = datasources; } } + ---------------------------- 5,DataSourceAutoConfiguration| ---------------------------- - package com.kevin.boot.web.configuration; + import java.sql.SQLException; + import java.util.HashMap; + import java.util.Map; - import com.alibaba.druid.pool.DruidDataSource; - import com.kevin.boot.web.config.DataSourceConfig; - import com.kevin.boot.web.datasource.DynamicDataSource; - import com.kevin.boot.web.utils.GeneralUtils; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - import javax.sql.DataSource; - import java.util.HashMap; - import java.util.Map; + import com.alibaba.druid.pool.DruidDataSource; + + import io.springcloud.datasource.DynamicDataSource; + import io.springcloud.datasource.DynamicDataSourceConfig; + import io.springcloud.datasource.DynamicDataSourceHolder; /** - * Created by KevinBlandy on 2017/2/28 14:11 + * + * 读写分离数据源 + * @author KevinBlandy + * */ + @Configuration - @EnableConfigurationProperties(DataSourceConfig.class) - public class DataSourceAutoConfiguration { + @EnableConfigurationProperties(DynamicDataSourceConfig.class) + public class DynamicDataSourceConfiguration { + + private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSourceConfiguration.class); @Autowired - private DataSourceConfig dataSourceConfig; + private DynamicDataSourceConfig dynamicDataSourceConfig; @Bean - public DataSource dataSource(){ + public DynamicDataSource dynamicDataSource() throws SQLException { + DynamicDataSource dynamicDataSource = new DynamicDataSource(); - //从配置获取一个主库 - DataSource masterDataSource = this.dataSourceConfig.getMasterDataSource(); - //默认库为主库 - dynamicDataSource.setDefaultTargetDataSource(masterDataSource); - Map dataSources = new HashMap(); - if(!GeneralUtils.isEmpty(this.dataSourceConfig.getSlaveurl()) && !GeneralUtils.isEmpty(this.dataSourceConfig.getSlaveusername()) && !GeneralUtils.isEmpty(this.dataSourceConfig.getSlavepassword())){ - int length = this.dataSourceConfig.getSlaveurl().size(); - DruidDataSource druidDataSource = null; - for (int x = 0;x < length ; x++){ - String url = this.dataSourceConfig.getSlaveurl().get(x); - String userName = this.dataSourceConfig.getSlaveusername().get(x); - String passWord = this.dataSourceConfig.getSlavepassword().get(x); - if(!GeneralUtils.isEmpty(url) && !GeneralUtils.isEmpty(userName) && !GeneralUtils.isEmpty(passWord)){ - //添加slave库 - druidDataSource = new DruidDataSource(); - druidDataSource.setUrl(url); - druidDataSource.setUsername(userName); - druidDataSource.setPassword(passWord); - druidDataSource.setMaxActive(this.dataSourceConfig.getMaxactive()); - druidDataSource.setInitialSize(this.dataSourceConfig.getInitialsize()); - dataSources.put("SLAVE"+x,druidDataSource); - } + + Map dataSources = new HashMap<>(); + + for(Map.Entry entry : dynamicDataSourceConfig.getDatasources().entrySet()) { + + String key = entry.getKey(); + + DruidDataSource dataSource = entry.getValue(); + + if(!key.equals(DynamicDataSourceHolder.MASTER)) { + dataSources.put(key, dataSource); } + + dataSource.init(); + + LOGGER.info("数据源初始化:key={}",key); } - //添加主库到集合,注意此处 MASTER 值 - dataSources.put("MASTER",masterDataSource); + + DruidDataSource masterDataSource = dynamicDataSourceConfig.getDatasources().get(DynamicDataSourceHolder.MASTER); + + if(masterDataSource == null) { + throw new IllegalArgumentException("未定义,master库(" + DynamicDataSourceHolder.MASTER + ")"); + } + + dataSources.put(DynamicDataSourceHolder.MASTER, masterDataSource); + + dynamicDataSource.setDefaultTargetDataSource(masterDataSource); dynamicDataSource.setTargetDataSources(dataSources); + return dynamicDataSource; } } + diff --git "a/Spring-boot/springboot-\345\216\237\347\220\206.java" "b/Spring-boot/springboot-\345\216\237\347\220\206.java" index 7991bb61..033793ce 100644 --- "a/Spring-boot/springboot-\345\216\237\347\220\206.java" +++ "b/Spring-boot/springboot-\345\216\237\347\220\206.java" @@ -14,13 +14,17 @@ * Negative matches: 以下为未启用的自动配置 0,@SpringBootApplication 注解 EnableAutoConfigurationImportSelector - 1,@EnableAutoConfiguration 注解 - 2,@Import(EnableAutoConfigurationImportSelector.class) 注解 + 1,@EnableAutoConfiguration 注解 与 @AutoConfigurationPackage + * @AutoConfigurationPackage 自动配置包 + + 2,@EnableAutoConfiguration 中的 @Import(EnableAutoConfigurationImportSelector.class) 注解 + * 2.0中已经修改:AutoConfigurationImportSelector 3,EnableAutoConfigurationImportSelector 类 # 使用 SpringFactoriesLoader.loadFactoryNames(Class factoryClass, ClassLoader classLoader);方法来扫描具有 META-INF/spring.factories 的jar包 * public static List loadFactoryNames(Class factoryClass, ClassLoader classLoader) * spring-boot-autoconfigure-1.5.1.RELEASE.jar 中就有 META-INF/spring.factories # META-INF/spring.factories 里面声明有一些自动配置项 + * 包含了N多的组件配置,包括自动配置类,事件监听等等.... 4,随便打开一个 XxxxAutoConfiguration 类,一般都会有以下注解,在 org.springframework.boot.autoconfigure.condition 包下 @@ -66,6 +70,21 @@ compile + * 最佳实践,直接继承:spring-boot-starters + + org.springframework.boot + spring-boot-starters + 2.0.2.RELEASE + + + + + org.springframework.boot + spring-boot-autoconfigure + compile + + + 2,创建 XxxxAutoConfiguration 类,会通过该类的一些注解来完成是否要注册一些组件 * 标识该类是一个配置类 @Configuration * 把 创建的 xxxProperties 加入到IOC EnableConfigurationProperties(xxxProperties.class) ,就可以在页面中 @Autowired 注入使用 @@ -82,7 +101,51 @@ com...x...,\ * 多个以逗号分割 + + org.springframework.context.ApplicationContextInitializer + * 配置 ApplicationContextInitializer 的实现,在初始化完成后获取到ApplicatioContext + + + + org.springframework.boot.SpringApplicationRunListener + * 配置 SpringApplicationRunListener 的实现,处理事件 + + +-------------------------- +Springboot-自动配置最佳实践| +-------------------------- + # 一般定义两个比较重要的组件 + xxx-starter + xxx-autoconfigure + # starter 只是一个空的maven工程,没有任何的代码,配置 + * 在 starter工程jar中除了maven的pom以外,还存在一个文件: META-INF/spring.providers + * 该文件只用描述依赖 + provides: xxx-autoconfigure, beetl-core + + # autoconfigure 是核心的自动装配工程 - \ No newline at end of file + +-------------------------- +元数据 | +-------------------------- + # SpringBoot的starter(jar)包含元数据文件 + * 元数据文件提供所有支持的配置属性的详细信息 + * 这些文件旨在让 IDE 开发人员使用 application.properties 或 application.yml 文件像用户一样提供上下文帮助和"代码完成" + + * 通俗理解来说,这个东西就是为了让idea可以读取到,从而在 yml/properties 编辑的时候,可以给出提示 + + # 参考文档 + https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html + + # 元数据文件的路径 + META-INF/spring-configuration-metadata.json + + * 它是一个json文件 + + + # groups 属性 + # properties 属性 + # hints 属性 + diff --git "a/Spring-boot/springboot-\345\220\257\345\212\250\347\233\221\345\220\254.java" "b/Spring-boot/springboot-\345\220\257\345\212\250\347\233\221\345\220\254.java" new file mode 100644 index 00000000..7a9c9d9b --- /dev/null +++ "b/Spring-boot/springboot-\345\220\257\345\212\250\347\233\221\345\220\254.java" @@ -0,0 +1,15 @@ +--------------------------- +启动监听 | +--------------------------- + # CommandLineRunner + * 容器启动成功后的最后一步回调 + * 覆写 run方法,参数就是 main 函数传递的 arg + void run(String... args) throws Exception; + + # ApplicationRunner + * 容器启动成功后的最后一步回调 + * 覆写run方法,跟 CommandLineRunner 不同的就是参数 + void run(ApplicationArguments args) throws Exception; + + # 可以在类上添加 @Order 注解来排序 + * value 值,越小,越先执行 \ No newline at end of file diff --git "a/Spring-boot/springboot-\345\223\215\345\272\224\345\216\213\347\274\251.java" "b/Spring-boot/springboot-\345\223\215\345\272\224\345\216\213\347\274\251.java" new file mode 100644 index 00000000..dc776747 --- /dev/null +++ "b/Spring-boot/springboot-\345\223\215\345\272\224\345\216\213\347\274\251.java" @@ -0,0 +1,34 @@ +-------------------- +响应压缩配置 | +-------------------- + # 配置类 + org.springframework.boot.context.embedded.Compression + + # 基本的配置 +server: + compression: + + # 开启压缩 + enabled: true + + # 设置支持压缩的响应数据类型,默认:text/html", "text/xml", "text/plain","text/css", "text/javascript", "application/javascript + mime-types: + - application/json + - application/xml + - application/javascript + - text/html + - text/xml + - text/plain + - text/css + - text/javascript + + # 默认情况下,仅会压缩2048字节以上的内容 + min-response-size: 2048 + + # 指定不压缩的user-agent ,默认为 null + excluded-user-agents: + - application/json + + + # 该配置开启后静态资源都会被压缩 + diff --git "a/Spring-boot/springboot-\345\274\202\345\270\270\345\244\204\347\220\206.java" "b/Spring-boot/springboot-\345\274\202\345\270\270\345\244\204\347\220\206.java" index 243edb24..4e6d598d 100644 --- "a/Spring-boot/springboot-\345\274\202\345\270\270\345\244\204\347\220\206.java" +++ "b/Spring-boot/springboot-\345\274\202\345\270\270\345\244\204\347\220\206.java" @@ -3,11 +3,11 @@ ---------------------------- # 使用 Spring Boot 提供的处理器 # 可以处理 404,500等异常 - 1,自动异常处理 Controller ,实现接口: org.springframework.boot.autoconfigure.web.ErrorController.ErrorController + 1,自动异常处理 Controller ,实现接口: org.springframework.boot.web.servlet.error.ErrorController 2,覆写方法 @Controller - public class ErrorController implements org.springframework.boot.autoconfigure.web.ErrorController { + public class ErrorController implements org.springframework.boot.web.servlet.error.ErrorController { private static final Logger LOGGER = LoggerFactory.getLogger(ErrorController.class); @@ -98,4 +98,173 @@ public ModelAndView resolveException(HttpServletRequest httpServletRequest, Http Method method = handlerMethod.getMethod(); //获取到请求异常的方法对象 return new ModelAndView("error/error"); } - } \ No newline at end of file + } + + +---------------------------- +Spring boot-我最喜欢的实践 | +---------------------------- + # 视图请求异常,响应异常的视图信息 + # api接口异常,响应异常的JSON/状态码信息 + + # 抽象基本的异常处理类 + import java.io.IOException; + + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; + + import org.springframework.http.HttpStatus; + import org.springframework.http.converter.HttpMessageNotReadableException; + import org.springframework.validation.BindException; + import org.springframework.web.HttpMediaTypeNotSupportedException; + import org.springframework.web.HttpRequestMethodNotSupportedException; + import org.springframework.web.bind.MissingServletRequestParameterException; + import org.springframework.web.bind.ServletRequestBindingException; + import org.springframework.web.bind.annotation.ExceptionHandler; + import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + import org.springframework.web.multipart.MaxUploadSizeExceededException; + import org.springframework.web.servlet.NoHandlerFoundException; + + import io.javaweb.paste.common.Message; + import io.javaweb.paste.exception.ServiceException; + + + public abstract class BaseExceptionAdvice { + + //请求路径未找到 + @ExceptionHandler(NoHandlerFoundException.class) + public Object noHandlerFoundException(HttpServletRequest request,HttpServletResponse response,NoHandlerFoundException e) throws IOException { + return this.errorHandler(request,response,Message.fail(HttpStatus.NOT_FOUND, "请求路径不存在"),e); + } + + //上传文件过大 + @ExceptionHandler(value = {MaxUploadSizeExceededException.class}) + public Object maxUploadSizeExceededException(HttpServletRequest request,HttpServletResponse response,MaxUploadSizeExceededException e) throws IOException { + return this.errorHandler(request,response,Message.fail(HttpStatus.BAD_REQUEST, "文件大小不能超过:" + e.getMaxUploadSize()),e); + } + + //请求方式不支持 + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public Object httpRequestMethodNotSupportedException(HttpServletRequest request, + HttpServletResponse response, + HttpRequestMethodNotSupportedException httpRequestMethodNotSupportedException) { + return this.errorHandler(request, response, Message.fail(HttpStatus.METHOD_NOT_ALLOWED, "请求方式不支持"),httpRequestMethodNotSupportedException); + } + + //缺少必须参数 + @ExceptionHandler(MissingServletRequestParameterException.class) + public Object missingServletRequestParameterException(HttpServletRequest request, + HttpServletResponse response, + MissingServletRequestParameterException exception) { + return this.errorHandler(request,response,Message.fail(HttpStatus.BAD_REQUEST, "缺少必须参数:" + exception.getParameterName()),exception); + } + + //业务异常 + @ExceptionHandler(ServiceException.class) + public Object businessException(HttpServletRequest request, + HttpServletResponse response, + ServiceException exception) { + return this.errorHandler(request, response, exception.message(),exception); + } + + //系统异常 + @ExceptionHandler(Exception.class) + public Object exception(HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse, + Exception exception) { + return this.errorHandler(httpServletRequest, httpServletResponse, Message.fail(HttpStatus.INTERNAL_SERVER_ERROR, "系统异常"),exception); + } + + + //非法请求 + @ExceptionHandler(value = { + HttpMessageNotReadableException.class, + IllegalArgumentException.class, + MethodArgumentTypeMismatchException.class, + BindException.class, + HttpMediaTypeNotSupportedException.class, + ServletRequestBindingException.class + }) + public Object badRequestException(HttpServletRequest request,HttpServletResponse response,Exception e) throws IOException { + e.printStackTrace(); + return this.errorHandler(request,response,Message.fail(HttpStatus.BAD_REQUEST, "非法请求"),e); + } + + + abstract public Object errorHandler(HttpServletRequest request,HttpServletResponse response,Message message,Throwable throwable); + } + + # api异常 + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; + + import org.springframework.http.ResponseEntity; + import org.springframework.web.bind.annotation.ControllerAdvice; + import org.springframework.web.bind.annotation.RestController; + + import io.javaweb.paste.common.Message; + + @ControllerAdvice(annotations = {RestController.class}) // 只处理restController + public class RestExceptionAdvice extends BaseExceptionAdvice { + + @Override + public ResponseEntity errorHandler(HttpServletRequest request, HttpServletResponse response, Message message,Throwable throwable) { + throwable.printStackTrace(); + return ResponseEntity.status(message.getStatus()).build(); + } + + } + + # 视图异常 + + import java.io.PrintWriter; + + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; + + import org.springframework.stereotype.Controller; + import org.springframework.web.bind.annotation.ControllerAdvice; + import org.springframework.web.servlet.ModelAndView; + + import io.javaweb.paste.common.Message; + import io.javaweb.paste.common.StringBuilderWriter; + + @ControllerAdvice(annotations = {Controller.class}) // 只处理Controller + public class ExceptionAdvice extends BaseExceptionAdvice { + + public static final String ERROR_PATH = "error/error"; + + @Override + public Object errorHandler(HttpServletRequest request, HttpServletResponse response, Message message,Throwable throwable) { + + ModelAndView modelAndView = new ModelAndView(ERROR_PATH); + + modelAndView.addObject("message", message); + + StringBuilderWriter stringBuilderWriter = new StringBuilderWriter(); + + PrintWriter printWrite = new PrintWriter(stringBuilderWriter); + + throwable.printStackTrace(printWrite); + + modelAndView.addObject("throwable", stringBuilderWriter.toString()); + + return modelAndView; + } + } + + + + # 也可以定义俩注解 + @View 标识在视图Controller上 + @Api 标识在api接口Controller上 + + + // 处理视图异常 + @ControllerAdvice(annotations = {View.class}) + public class VieExceptionAdvice extends BaseExceptionAdvice + + + // 处理接口异常 + @ControllerAdvice(annotations = {Api.class}) + public class ApiExceptionAdvice extends BaseExceptionAdvice diff --git "a/Spring-boot/springboot-\346\263\250\350\247\243.java" "b/Spring-boot/springboot-\346\263\250\350\247\243.java" index 10438a1c..7b3a4634 100644 --- "a/Spring-boot/springboot-\346\263\250\350\247\243.java" +++ "b/Spring-boot/springboot-\346\263\250\350\247\243.java" @@ -53,3 +53,52 @@ + @AutoConfigureAfter + # 只有两个属性 + Class[] value() default {}; + String[] name() default {}; + + # 用在自动配置类上面,表示该自动配置类需要在另外指定的自动配置类配置完之后 + # 例如 Mybatis 的自动配置类,需要在数据源自动配置类之后 + @AutoConfigureAfter(DataSourceAutoConfiguration.class) + public class MybatisAutoConfiguration { + + @AutoConfigureBefore + # 同上,通过该注解指定的配置类,应该要在当前类之后配置 + + + @ImportResource + # 属性 + String[] value() default {}; + String[] locations() default {}; + Class reader() default BeanDefinitionReader.class; + + # 通过该注解导入spring的xml配置文件 + # 支持从文件系统或者classpath下加载 + + + @PropertySource + # 加载外部配置文件 + String name() default ""; + String[] value(); + boolean ignoreResourceNotFound() default false; + String encoding() default ""; + Class factory() default PropertySourceFactory.class; + + # 跟标签加载一样 + + + # 支持从不同的源加载数据 + @PropertySource(value = { "file:c:/application.properties","classpath:app.properties"}) + + @Profile + # 只有一个属性 + # 可以标识在类,方法上,指定一个或者多个激活的配置文件名 + String[] value(); + # 关联的配置 + spring.profiles.active=dev + # 使用demo + @Profile("dev") // 只有激活了dev配置文件时,才会加载该controller + @RestController + public class ProdController + \ No newline at end of file diff --git "a/Spring-boot/springboot-\350\207\252\345\256\232\344\271\211starter.java" "b/Spring-boot/springboot-\350\207\252\345\256\232\344\271\211starter.java" new file mode 100644 index 00000000..948477c2 --- /dev/null +++ "b/Spring-boot/springboot-\350\207\252\345\256\232\344\271\211starter.java" @@ -0,0 +1,42 @@ +-------------------------------- +springboot 鑷畾涔塻tarter | +-------------------------------- + + 1,starter.jar 鍏跺疄灏辨槸涓┖鐨刯ar + * 閲岄潰閫氳繃 pom 渚濊禆浜:autoconfigure鑷姩閰嶇疆渚濊禆銆 + * 閫氳繃 META-INF/spring.provides 閰嶇疆鏂囦欢鏉ヨ鏄庝緷璧 + provides: spring-context-support,mail + + 2,autoconfigure.jar灏辨槸鑷姩閰嶇疆jar浜嗐 + +--------------------------------------------------- +spring-boot-starter-${project}.jar +--------------------------------------------------- + + org.springframework.boot + spring-boot-starters + 1.5.10.RELEASE + + + + spring-boot-starter-${project} + + + + org.springframework.boot + spring-boot-starter + + + xxxxxxxxxxxxx + spring-boot-autoconfigure-${project} + + + +--------------------------------------------------- + spring-boot-autoconfigure-${project}.jar +--------------------------------------------------- + + + org.springframework.boot + spring-boot-autoconfigure + \ No newline at end of file diff --git "a/Spring-boot/springboot-\351\205\215\347\275\256.java" "b/Spring-boot/springboot-\351\205\215\347\275\256.java" index 6c321cb3..e3d53847 100644 --- "a/Spring-boot/springboot-\351\205\215\347\275\256.java" +++ "b/Spring-boot/springboot-\351\205\215\347\275\256.java" @@ -121,8 +121,9 @@ class User{ > 配置日志文件地址 logging.file=d:/mylog/log.log > 配置日志输出级别 + logging.level.root=INFO logging.level.org.springframework.web=DEBUG - logging.level.*=INFO + * 引用外部文件的配置(推荐使用) > spring boot 会默认加载:classpath:logback-spring.xml或者classpath:logback-spring.groovy @@ -148,6 +149,83 @@ class User{ * 每个profile的格式固定 application-{name}.properties - * application.properties文件中指定要加载的文件 + * application.properties文件中激活指定的文件 spring.profiles.active={name} + + # 导入多个外部配置文件 + spring.profiles.include[0]=datasource + |-application-datasource.properties + spring.profiles.include[1]=redis + |-application-redis.properties + + # bootstrap.yml + * bootstrap.yml(bootstrap.properties)用来程序引导时执行,应用于更加早期配置信息读取 + + * 可以使用来配置application.yml中使用到参数等 + + * application.yml(application.properties) 应用程序特有配置信息,可以用来配置后续各个模块中需使用的公共参数等 + + * bootstrap.yml 先于 application.yml 加载 + + * 可以通过设置来禁用bootstrap + spring.cloud.bootstrap.enabled=false + + +------------------------ +多环境配置 | +------------------------ + # application.properties属性文件会被SpringBoot应用自动加载, 而且有一个加载顺序 + 1. 当前目录的/config子目录下 + 2. 当前目录下 + 3. classpath目录的/config子目录下 + 4. classpath目录下 + + * private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; + * 列表高的配置, 覆盖列表低的配置 + + # 通过Environment属性 spring.config.name 可以自定义applicaiton.properties文件的名称 + ConfigFileApplicationListener.CONFIG_NAME_PROPERTY = "spring.config.name"; + # 通过Environment属性 spring.config.location 自定义 applicaiton.properties 文件的位置 + ConfigFileApplicationListener.CONFIG_LOCATION_PROPERTY = "spring.config.location"; + + # 通过Environment属性 spring.config.additional-location 自定义 applicaiton.properties 文件的位置 + ConfigFileApplicationListener.CONFIG_ADDITIONAL_LOCATION_PROPERTY="spring.config.additional-location"; + + * spring.config.location 会覆盖默认的搜索路径 + * 它只是追加, 追加的位置是在默认的位置之前 + + # 这几个配置要在应用启用之前配置, 所以需要将其配置到'系统环境变量'或者'系统参数'或者'命令行参数'中优先读取 + + + # 指定配置文件的文件夹 + * 生产环境,和开发环境的配置文件不一定非要存储在项目中 + * 可以存储在本地磁盘, 使用配置设置 + ConfigFileApplicationListener.CONFIG_ADDITIONAL_LOCATION_PROPERTY="spring.config.additional-location"; + + * 可以在代码里面设置, 如果有多个值, 使用逗号分隔 + String configLocation = "file:${user.home}" + File.separator + "config" + File.separator; + System.setProperty(ConfigFileApplicationListener.CONFIG_ADDITIONAL_LOCATION_PROPERTY, configLocation); + + + System.setProperty("spring.config.additional-location", "file:${user.home}/config/,file:${user.home}/config-dev/"); + + + * 支持使用 spel 表达式 + * 建议项目工程中不要存储生产环境的相关配置, 生产环境的配置, 通过 'spring.config.additional-location' 制定存储在生产服务器的磁盘上 + + +------------------------ +Environment | +------------------------ + # org.springframework.core.env.Environment + # 该对象在IOC,表示当前spring的所有配置信息 + + @Autowired + private Environment env; + + //获取当前激活的的文件名称,也就是:spring.profiles.active 指定的文件 + String[] profiles = env.getActiveProfiles(); + + //可以从配置中获取数据 + String dbPass = env.getProperty("bbs.dbPassowrd"); \ No newline at end of file diff --git "a/Spring-boot/springboot-\351\205\215\347\275\256\347\273\221\345\256\232.java" "b/Spring-boot/springboot-\351\205\215\347\275\256\347\273\221\345\256\232.java" index 2c5f082d..44b96784 100644 --- "a/Spring-boot/springboot-\351\205\215\347\275\256\347\273\221\345\256\232.java" +++ "b/Spring-boot/springboot-\351\205\215\347\275\256\347\273\221\345\256\232.java" @@ -31,8 +31,10 @@ public class User{ # 把该类添加到IOC容器 * 可以在类上添加 @Component 注解 + * 也可以在其他类上,通过:@EnableConfigurationProperties({User.class}); 注解来添加到IOC + * @EnableConfigurationProperties 如果要配置在 @Configuration 类上 * Spring Boot使用宽松的规则用于绑定属性到 @ConfigurationProperties * 以配置的属性名和bean属性名不需要精确匹配。 - * 比如,context-path绑定到contextPath,PORT绑定port。 \ No newline at end of file + * 比如,context-path绑定到contextPath,PORT绑定port。 \ No newline at end of file diff --git "a/Spring-boot/springboot-\351\235\236web\351\241\271\347\233\256.java" "b/Spring-boot/springboot-\351\235\236web\351\241\271\347\233\256.java" new file mode 100644 index 00000000..1d86fe7f --- /dev/null +++ "b/Spring-boot/springboot-\351\235\236web\351\241\271\347\233\256.java" @@ -0,0 +1,55 @@ +------------------------------ +springboot 非 web项目 | +------------------------------ + # 在非 web 项目中使用springboot + # MAVEN + + org.springframework.boot + spring-boot-starter-parent + 2.1.3.RELEASE + + + + + org.springframework.boot + spring-boot-starter + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + true + + + + + + * 父级 pom 还是建议使用 parent + * 仅仅需要依赖一个 spring-boot-starter 就可以了 + + # Main 函数 + import java.io.IOException; + + import org.springframework.boot.SpringApplication; + import org.springframework.boot.autoconfigure.SpringBootApplication; + import org.springframework.context.ConfigurableApplicationContext; + + @SpringBootApplication + public class Main { + + public static void main(String[] args) throws IOException { + + SpringApplication springApplication = new SpringApplication(Main.class); + + ConfigurableApplicationContext configurableApplicationContext = springApplication.run(args); + + System.in.read(); + } + } + + \ No newline at end of file diff --git a/Spring-boot/springboot2/springboot2-WebMvcConfigurer.java b/Spring-boot/springboot2/springboot2-WebMvcConfigurer.java new file mode 100644 index 00000000..f87f9147 --- /dev/null +++ b/Spring-boot/springboot2/springboot2-WebMvcConfigurer.java @@ -0,0 +1,36 @@ +-------------------------------- +WebMvcConfigurer | +-------------------------------- + # 鏄竴涓帴鍙,鐢ㄤ簬瀵筗eb椤圭洰鐨勯厤缃 + + +-------------------------------- +WebMvcConfigurer-default 鏂规硶 | +-------------------------------- + void configurePathMatch(PathMatchConfigurer configurer) + void configureContentNegotiation(ContentNegotiationConfigurer configurer) + void configureAsyncSupport(AsyncSupportConfigurer configurer) + void configureServletHandling(ServletHandlerConfigurer configurer) + void addFormatters(FormatterRegistry registry) + void addInterceptors(InterceptorRegistry registry) + # 鎷︽埅鍣ㄩ厤缃 + + void addResourceHandlers(ResourceHandlerRegistry registry) + # 闈欐佽祫婧愭槧灏勯厤缃 + + void addCorsMappings(CorsRegistry registry) + # cors璺ㄥ煙閰嶇疆 + + void addViewControllers(ViewControllerRegistry registry) + void configureViewResolvers(ViewResolverRegistry registry) + void addArgumentResolvers(List resolvers) + void addReturnValueHandlers(List handlers) + void configureMessageConverters(List> converters) + # 娉ㄥ唽http娑堟伅杞崲鍣 + + void extendMessageConverters(List> converters) + void configureHandlerExceptionResolvers(List resolvers) + void extendHandlerExceptionResolvers(List resolvers) + Validator getValidator() + MessageCodesResolver getMessageCodesResolver() + diff --git a/Spring-boot/springboot2/springboot2-redis.java b/Spring-boot/springboot2/springboot2-redis.java new file mode 100644 index 00000000..db251a31 --- /dev/null +++ b/Spring-boot/springboot2/springboot2-redis.java @@ -0,0 +1,19 @@ + +# maven配置 + + org.springframework.boot + spring-boot-starter-redis + 1.4.7.RELEASE + + +# 配置 + spring.redis.host=127.0.0.1 + spring.redis.port=6379 + spring.redis.timeout=0ms + spring.redis.password= + pring.redis.database=0 + + spring.redis.jedis.pool.max-active=8 + spring.redis.jedis.pool.max-idle=8 + spring.redis.jedis.pool.max-wait=-1ms + spring.redis.jedis.pool.min-idle=0 \ No newline at end of file diff --git "a/Spring-boot/springboot2/springboot2-web\346\234\215\345\212\241\345\231\250.java" "b/Spring-boot/springboot2/springboot2-web\346\234\215\345\212\241\345\231\250.java" new file mode 100644 index 00000000..806c0f91 --- /dev/null +++ "b/Spring-boot/springboot2/springboot2-web\346\234\215\345\212\241\345\231\250.java" @@ -0,0 +1,35 @@ +---------------------------- +不同Servlet服务器的设置 | +---------------------------- + # WebServerFactory 是一个标记接口,实现类如下 + TomcatServletWebServerFactory + JettyServletWebServerFactory + UndertowServletWebServerFactory + + # 自定义类继承类:WebServerFactoryCustomizer + @FunctionalInterface + public interface WebServerFactoryCustomizer { + void customize(T factory); + } + + * 通过不同的泛型来配置不同的Servlet容器 + @Configuration + public class ServletContainerConfiguration implements WebServerFactoryCustomizer { + + @Override + public void customize(TomcatServletWebServerFactory factory) { + factory.setServerHeader("springboot.io"); + factory.addAdditionalTomcatConnectors(connector()); + factory.addContextCustomizers(new TomcatContextCustomizer() { + @Override + public void customize(Context context) { + context.setCookieProcessor(new LegacyCookieProcessor()); + } + }); + } + + private Connector connector() { + Connector connector = new Connector("org.apache.coyote.http11.Http11Nio2Protocol"); + return connector; + } + } diff --git "a/Spring-boot/springboot2/springboot2-\345\205\245\351\227\250.java" "b/Spring-boot/springboot2/springboot2-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..5186f8bc --- /dev/null +++ "b/Spring-boot/springboot2/springboot2-\345\205\245\351\227\250.java" @@ -0,0 +1,12 @@ +---------------------------------------- +鍏ラ棬 | +---------------------------------------- + # 瀹樻柟鏂囨。鍦板潃 + https://docs.spring.io/spring-boot/docs/current/reference/html/ + + +---------------------------------------- +鏀瑰姩灏忚 | +---------------------------------------- + 1,WebMvcConfigurer 鐢辨娊璞$被鍙樹负鎺ュ彛 + diff --git a/Spring-boot/util/StopWatch.java b/Spring-boot/util/StopWatch.java new file mode 100644 index 00000000..14f867ef --- /dev/null +++ b/Spring-boot/util/StopWatch.java @@ -0,0 +1,94 @@ +---------------------------- +StopWatch | +---------------------------- + # 可以用来分析任务耗时的工具 + * 线程不安全, 而且应该是测试环境使用, 而不是生产环境 + + # 构造函数 + StopWatch() + StopWatch(String id) + + id + * 每个实例可以设置一个唯一的id + + # 实例函数 + String getId() + TaskInfo getLastTaskInfo() + * 获取最后一个执行的任务 + + String getLastTaskName() + long getLastTaskTimeMillis() + int getTaskCount() + * 获取执行任务的次数 + + TaskInfo[] getTaskInfo() + long getTotalTimeMillis() + * 获取所有任务执行的总耗时, 毫秒 + + double getTotalTimeSeconds() + * 获取所有任务执行的总耗时, 秒 + + boolean isRunning() + String prettyPrint() + void setKeepTaskList(boolean keepTaskList) + * 是否保存每个执行过的任务 + + String shortSummary() + void start() + void start(String taskName) + void stop() + String toString() + + # 内部静态类:TaskInfo + + * 封装了每个任务的名称, 以及耗时信息 + + TaskInfo(String taskName, long timeMillis) + + String getTaskName() + * 获取任务的名称 + + long getTimeMillis() + double getTimeSeconds() + * 当前任务的耗费时间 + +---------------------------- +Demo | +---------------------------- + +import org.springframework.util.StopWatch; + +public class Main { + public static void main(String[] args) throws Throwable{ + StopWatch stopWatch = new StopWatch("id"); + + // 开始第一个任务 + stopWatch.start("task1"); + Thread.sleep(2000L); + stopWatch.stop(); + + // 开始第二个任务 + stopWatch.start("task2"); + Thread.sleep(3000L); + stopWatch.stop(); + + String prettyPrint = stopWatch.prettyPrint(); + + System.out.println(prettyPrint); + + System.out.println(stopWatch); + + } +} +/* + +StopWatch 'id': running time (millis) = 5000 +----------------------------------------- +ms % Task name +----------------------------------------- +02000 040% task1 +03000 060% task2 + + +StopWatch 'id': running time (millis) = 5001; [task1] took 2000 = 40%; [task2] took 3001 = 60% +*/ \ No newline at end of file diff --git a/Spring-boot/webclient/webclient.java b/Spring-boot/webclient/webclient.java new file mode 100644 index 00000000..e3323379 --- /dev/null +++ b/Spring-boot/webclient/webclient.java @@ -0,0 +1,7 @@ +----------------------------- +webclient | +----------------------------- + # 以后可能会代替 RestTemplete + # 文档 + https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-client + diff --git a/Spring-boot/webflux/webflux.java b/Spring-boot/webflux/webflux.java new file mode 100644 index 00000000..c656fcb1 --- /dev/null +++ b/Spring-boot/webflux/webflux.java @@ -0,0 +1,6 @@ +----------------- +webflux | +----------------- + # 文档 + https://docs.spring.io/spring/docs/5.1.5.RELEASE/spring-framework-reference/web-reactive.html + diff --git a/Spring-cloud/bus/bus.java b/Spring-cloud/bus/bus.java new file mode 100644 index 00000000..8184898e --- /dev/null +++ b/Spring-cloud/bus/bus.java @@ -0,0 +1,96 @@ +--------------------- +bus | +--------------------- + # 文档 + https://cloud.spring.io/spring-cloud-bus/spring-cloud-bus.html + + # 需要依赖到MQ中间件 + RabbitMQ + Kafka + + # 模型 + * 客户端添加,bus依赖,连接到 mq + * 配置内容被修改后,触发任意客户端的 /actuator/bus-refresh + * 此时,所有连接到bus的客户端都会发生更新操作 + + # 客户端 Maven,bus的实现,二选一 + + org.springframework.cloud + spring-cloud-starter-bus-amqp + + * amqp就是Rabbitmq + + + org.springframework.cloud + spring-cloud-starter-bus-kafka + + * 使用Kafka + + + org.springframework.boot + spring-boot-starter-actuator + + + * actuator 模块的作用是提供 /actuator/bus-refresh ,bus-env 端点,用于触发任意客户端的刷新,从而通知到所有连接到Bus的客户端 + * 一般而言,负责执行刷新操作的客户端一般都是 : config-server + * 其他的微服务客户端不必承担刷新配置的工作,从而简化了集群的一些维护工作 + * 其他的微服务客户端也许并不需要 actuator 依赖 + + # RabbitMQ客户端配置 +spring: + rabbitmq: + host: 58.87.75.64 + port: 5671 + username: guest + password: guest + ssl: + enabled: true + + + # Kafka客户端配置 + TODO + + # 负责触发刷新事件的节点,需要配置 actuator,提供 /bus-refresh,bus-env 端点 + management.endpoints.web.exposure.include=bus-refresh + management.endpoints.web.exposure.include=bus-env + + * /actuator/bus-refresh + * 触发配置更新(手动修改了内容),会更新bus中所有标识了 @RefreshScope 的配置 + * @RefreshScope,可以标识在方法,字段上,它们一般都是通过 @Value 注入了属性值 + * 该注解有一个属性:proxyMode,用于设置代理模式(动态更新依赖于动态代理) + ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; + DEFAULT, + NO, + INTERFACES, + TARGET_CLASS; + + + * /actuator/bus-env + * 修改配置,并且触发更新,该端点直接提供了通过HTTP修改配置项,并且广播更新的功能 + * POST请求,请求体为JSON,内容为 + { + "name": "key1", + "value": "value1" + } + + + +--------------------- +跟踪消息总线事件 | +--------------------- + # 配置开启 + spring.cloud.bus.trace.enabled=true + + # 访问端点(actuator) + /trace + + +--------------------- +客户端监听消息事件 | +--------------------- + # 继承类,并且实现 + RemoteApplicationEvent + + # 注解驱动 @RemoteApplicationEventScan + * 指定实现类的扫描包 @RemoteApplicationEventScan(basePackages = {"com.acme", "foo.bar", "fizz.buzz"}) + \ No newline at end of file diff --git a/Spring-cloud/config/config-client.java b/Spring-cloud/config/config-client.java new file mode 100644 index 00000000..dcd33d15 --- /dev/null +++ b/Spring-cloud/config/config-client.java @@ -0,0 +1,116 @@ +-------------------------------- +ConfigClient | +-------------------------------- + # Maven + + org.springframework.cloud + spring-cloud-config-client + + + # 配置 + * 注意,要配置在 : bootstrap.yml 中 + + spring: + application: + # 就是文件的前缀名 + name: springcloud + cloud: + config: + # 配置文件服务器 + uri: http://localhost:8015/ + # 激活的文件,就是文件的后缀 + profile: dev + # 分支 + label: master + + * 应用启动的时候会根据 application.name 和 config.profile 还有 config.label 去 config.uri 上去加载配置文件信息 + {label} ==> {application}-{profile}.yml + + # 在程序里面使用 + // 直接使用 @Value 注解 + @Value("${config.name}") + private String configName; + + // 使用 environment + @Autowired + private Environment environment; + environment.getProperty("config.name") + + # 失败的快速响应与重试 + * 在服务启动的时候,如果比较复杂,可能会在连接配置服务器,加载配置的过程中耗费大量的时间 + * 可以通过一个配置,来避免当ConfigServer配置有误的时候,需要多等待前置的一些加载时间 + * 实现了快速返回失败信息(抛出异常) + + spring.cloud.conifg.fail-fast=true + + * 在开启了'快速返回失败信息'的情况下,还提供了一个重试机制 + * 因为配置服务器的异常,可能只是因为网络波动造成的,这是可以恢复的一个异常 + * 在客户端添加依赖 + + org.springframework.retry + spring-retry + + + org.springframework.boot + spring-boot-starter-aop + + + * 添加了依赖后,不需要做任何的配置 + * 客户端在连接配置服务器失败的情况下,会连续尝试6次的重新连接 + * 如果都失败的情况下,才会抛出异常 + * 可以通过配置重试的次数以及间隔时间(配置类:RetryProperties) + spring: + cloud: + config: + retry: + # 初始重试的时间,默认为: 1000ms + initial-interval: 1000 + # 下一间隔的乘数,默认为: 1.1 + multiplier: 1.1 + # 最大重试次数,默认为 6 + max-attempts: 6 + # 最大间隔时间,默认为: 2000 ms + max-interval: 2000 + + +-------------------------------- +动态刷新配置 | +-------------------------------- + # 在客户端添加,acuator监控模块 + + org.springframework.boot + spring-boot-starter-actuator + + + * 需要开放 /refresh 端点权限 + management: + endpoints: + web: + base-path: /actuator + exposure: + include: + - '*' + + # 访问客户端,查看配置 + + # 尝试修改Git仓库的配置文件(commit) + + # 请求客户端的刷新路径 /refresh + http://localhost/actuator/refresh + + [ + "config.client.version", + "config.name" + ] + + * 此时当前客户端的配置信息已经被更新为最新修改的配置 + * 目前这种操作只能修改到:Environment 中的数据,不能修改到通过 @Value 注入的变量 + + + # 可以使用Git仓库的钩子程序(web hook),来触发客户端的 /refresh 接口完成自动刷新 + + # 实际的开发,并不建议使用这种方式来做配置更新,而是通过消息总线来: springcloud-bus + + + + diff --git "a/Spring-cloud/config/config-server-\351\233\206\347\276\244.java" "b/Spring-cloud/config/config-server-\351\233\206\347\276\244.java" new file mode 100644 index 00000000..b238b908 --- /dev/null +++ "b/Spring-cloud/config/config-server-\351\233\206\347\276\244.java" @@ -0,0 +1,68 @@ +---------------------------- +ConfigServer 集群 | +---------------------------- + # 配置服务器高可用 + + # 传统模式 + * 这种模式,就是把所有的配置都放在一个Git仓库 + * 然后使用多台配置服务器,在应用层负载均衡 + + # 服务模式 + * 把配置服务器作为服务,注册到Eureka + * 客户端通过Eureka获取到注册的配置服务 + +---------------------------- +ConfigServer 服务化 | +---------------------------- + # 服务端添加Eureka的客户端依赖 + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + # 服务端注册到注册中心 + eureka: + client: + # 配置服务器不需要从注册中心检索服务 + fetch-registry: false + # 需要把自己注册到注册中心 + register-with-eureka: true + service-url: + defaultZone: http://username:password@localhost:8081/eureka/ + + # 客户端添加Eureka依赖(作为微服务消费者,肯定已经有了) + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + # 客户端配置 + * 使用俩配置,代替原来的uri配置 + * 一个开启配置服务器的发现,一个设置配置服务器的id + spring.cloud.config.discovery.enabled + spring.cloud.config.discovery.service-id + + * 需要把 eureka和config的配置都一同的写在 : boostrap.yml 配置中 + spring: + application: + name: springcloud + # 如果eureka存在账户名密码保护的话 + security: + user: + name: springcloud + password: 123456 + cloud: + config: + # 开启服务发现机制 + discovery: + # 配置服务器的id + service-id: CONFIG-SERVER + enabled: true + profile: test + label: master + + eureka: + client: + service-url: + defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@localhost:8081/eureka/ + register-with-eureka: false diff --git a/Spring-cloud/config/config-server.java b/Spring-cloud/config/config-server.java new file mode 100644 index 00000000..b1c3950e --- /dev/null +++ b/Spring-cloud/config/config-server.java @@ -0,0 +1,261 @@ +-------------------------------- +ConfigServer | +-------------------------------- + # Maven + + org.springframework.cloud + spring-cloud-config-server + + + # 默认使用Git仓库作为配置仓库 + * 配置文件必须已经被版本控制器管理 + * spring.config.server.git.uri 配合的根路径,必须要有 .git 文件夹(必须配置到根目录) + * 并且配置文件需要提交到版本控制 + + # 注解驱动 + @EnableConfigServer + + # 基本的配置 + server: + port: 8015 + + spring: + application: + name: config-server + cloud: + config: + server: + git: + # git配置仓库的位置(不能以为 / 结尾),根目录下必须要有: .git 目录(说白了,必须是GIT的根目录) + uri: https://github.com/KevinBlandy/temp-config.git + # 本地缓存的目录 + basedir: D:\\temp\\config + # 配置仓库下的相对搜索路径,可以有多个 + search-paths: + - config + # 访问仓库的账户名密码 + username: KevinBlandy + password: F8575532 + + # 服务启动OK后,可以通过浏览器访问 + http://localhost:8015/config/springcloud.yml + + * 可以访问的路径 + /{application}/{profile}/{label} + + /{application}-{profile}.yml + /{label}/{application}-{profile}.yml + + /{application}-{profile}.properties + /{label}/{application}-{profile}.properties + + application + * 要以客户端的 spring.application.name 来定义 + profile + * 环境 + label + * 可以指定分支,默认为MASTER + + * 注意,要带上 search-paths 路径 + + # ConifgServer还会在本地缓存配置文件 + * 本地缓存的目录 + C:\Users\KevinBlandy\AppData\Local\Temp\config-repo-[随机数] + + * 防止Git服务器宕机而无法加载配置 + * 可以通过配置来指定特殊的缓存目录 + spring.cloud.config.server.git.basedir=D:\\temp\\config + + + + # 可以使用本地的 git 仓库作为配置仓库 + spring.config.server.git.uri=file:D:\\config-rep\\springcloud-config + + * 使用:file: 开头表示使用本地的配置仓库 + + # 使用URI占位符来区分不同的应用 + * application,profile,label 不仅仅可以标识配置文件规则 + + * 还可以用于ConfigServer对于Git仓库的Uri地址 + spring.config.server.git.uri=https://github.com/KevinBlandy/{application}-config.git + + application + * 表示应用名,当客户端发起请求的时候,Server会根据客户端的 spring.application.name 信息来填充URI + * 就可以根据不同的服务,来动态的加载不同URI下的资源 + + label + * 这个参数比较特别,如果Git分支名称包含了 '/' ,那么在label参数在http的uri中应该使用 '_' 来代替,避免改变了URI的含义 + + * 目前测试,好像占位符{application}不支持配置在 uri 属性中,如果使用 {application},在ConfigServer启动的时候会被替换为 : app,从而导致系统异常,提示不存在的目录 + 配置 -> uri: 'https://github.com/KevinBlandy/{application}-config-rep.git' + 异常 -> Cannot clone or checkout repository: https://github.com/KevinBlandy/app-config-rep.git + + * 其实也可以解决,亲测,在ConfigServer启动的时候,先在根路径创建目录: {application},application替换为 app,并且初始化为git目录,并且有commit文件 + 配置 -> uri: 'file:D:\\config-rep\\{application}-config' + 新建 -> D:\\config-rep\\app-config + + * 上述俩问题是因为健康监测导致的 + + * 占位符还可以使用在搜索路径上,以此来区分同一个仓库下的不同文件夹 + spring + cloud: + config: + server: + # Git的根目录 + uri: 'file:D:\\config-rep' + search-paths: + # 如果是yml的话,要使用双引号 + - '{application}-config' + + * 根据不用应用名,去不同的子目录下搜索 + * 这个靠谱,经过试验没啥问题 + + + + # 多仓库的配置 + spring.cloud.config.server.git.uri='file:\\default' + spring.cloud.config.server.git.repos.dev.pattern='dev/*' + spring.cloud.config.server.git.repos.dev.uri='file:D:\\dev' + + spring.cloud.config.server.git.repos.test.pattern='test/*' + spring.cloud.config.server.git.repos.test.uri='file:D:test' + + * git.uri 指定的默认的仓库,系统启动就会去加载 + + * 指定特殊名称的仓库,以及访问的pattern + git.repos..pattern + git.repos..uri + +-------------------------------- +访问权限 | +-------------------------------- + # 默认采用HTTP协议的鉴权 + spring.cloud.config.server.git.username + spring.cloud.config.server.git.password + + # 采用SSH协议的鉴权 + 需要自己在当前主机环境生成 ssh密钥对,并且把公钥配置到git的服务器 + +-------------------------------- +本地文件系统 | +-------------------------------- + # 不使用GIT/SVN,直接使用文件系统 + spring.profiles.active=native + + cloud.config.server.native.search-locations + * 指定一个或者多个配置文件的搜索路径 + + * 不建议使用本地文件系统,还是要使用GIT + +-------------------------------- +健康监测 | +-------------------------------- + # 在 gti.uri 配置中使用 {application} 占位符,会给出警告,(找不到 app 仓库)因为Server实现了健康监测器 + ConfigServerHealthIndicator + + * 在该检测器中默认构建一个 application 为 app 的仓库 + + + # 可以直接Git的仓库中新建一个配置库,让健康检测器访问 + spring + cloud: + config: + server: + health: + # 可以选择关闭健康监测 + enabled: false + # Map repositories + repositories: + check: + name: check-rep + label: master + profiles: default + + +-------------------------------- +属性覆盖 | +-------------------------------- + # 通过该属性配置的K/V都会被加载到客户端,但是客户端不能修改 + spring: + cloud: + config: + server: + # Map + overrides: + name: KevinBlandy + skill: + - Java + - Python + +-------------------------------- +安全保护 | +-------------------------------- + # 服务端添加Security依赖 + + org.springframework.boot + spring-boot-starter-security + + + # 服务端配置用户名和密码 + spring: + security: + user: + # 服务端的账户名密码 + name: config + password: 123456 + + * 服务端访问端点,也需要使用该账户名密码登录 + + # 客户端使用用户名和密码连接 + spring: + cloud: + config: + # 访问服务端的账户名密码 + username: config + password: 123456 + +-------------------------------- +配置的加密/解密 | +-------------------------------- + # 在SpringCloud Config 中加载的配置使用 {cipher} 前缀来标识,表示该内容是一个加密的配置值 + + spring.datasource.password={cipher}e10adc3949ba59abbe56e057f20f883e + + * 配置服务器进行加载的时候,会自动为带有 {cipher} 前缀的配置进行解密 + * 使用 yml 配置,需要使用 '' 包裹值 + + # 使用的前提,需要在Oracle官方下载依赖:jce + local_policy.jar + US_export_policy.jar + + * Java8的下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html + * 添加到: $JAVA_HOME/jre/lib/security 目录下 + + # 服务端添加密钥配置(对称加密),需要配置在:bootstrap.yml + encrypt: + key: 4D44331C666011E9B03100163E11BA6D + + # 可以访问的端点 + /encrypt/status 加密功能的状态 + /key 查看密钥(非对称加密) + /encrypt 对请求Body加密 + /decrypt 对请求Body解密 + + + # 可以使用非对称加密 + * 生成证书 + keytool -genkey -alias mytestkey -keyalg RSA -dname "CN=Config Server,OU=Unit,O=Organization,L=City,S=State,C=CN" -keypass changeme -keystore server.jks -storepass letmein + + * 服务器配置 + encrypt: + key-store: + # keystore文件位置 + location: classpath:ssl/server.jks + # 证书别名 + alias: mytestkey + # keystore密码 + password: letmein + # JKS类型keystore的证书密码 + secret: changeme + # 证书类型 + type: JKS \ No newline at end of file diff --git "a/Spring-cloud/config/config-\351\205\215\347\275\256.java" "b/Spring-cloud/config/config-\351\205\215\347\275\256.java" new file mode 100644 index 00000000..035312e1 --- /dev/null +++ "b/Spring-cloud/config/config-\351\205\215\347\275\256.java" @@ -0,0 +1,6 @@ + + +# 配置类 + MultipleJGitEnvironmentProperties + PropertySourceBootstrapProperties + ConfigServerHealthIndicator \ No newline at end of file diff --git a/Spring-cloud/config/config.java b/Spring-cloud/config/config.java new file mode 100644 index 00000000..04b6f6ea --- /dev/null +++ b/Spring-cloud/config/config.java @@ -0,0 +1,28 @@ +---------------------------- +config | +---------------------------- + # 文档 + https://cloud.spring.io/spring-cloud-config/spring-cloud-config.html + + # Maven + + org.springframework.cloud + spring-cloud-starter-config + + + org.springframework.cloud + spring-cloud-config-server + + + org.springframework.cloud + spring-cloud-config-client + + + # 架构 + <---------> Service1 + | + GIT <----> ConfigServer + | + <---------> Service2 + + diff --git a/Spring-cloud/eureka/eureka-EurekaClient.java b/Spring-cloud/eureka/eureka-EurekaClient.java new file mode 100644 index 00000000..c4b931dd --- /dev/null +++ b/Spring-cloud/eureka/eureka-EurekaClient.java @@ -0,0 +1,13 @@ +---------------------------------- +EurekaCient-获取服务提供者的IP端口| +---------------------------------- + + @Autowired + private EurekaClient discoverClient + /** + 获取指定名称的服务提供者的IP地址和端口 + */ + public String serviceUrl(){ + InstanceInfo instance = discoverClient.getNextServerFromEureka("SERVICE-USER",false); + return instance.getHomePageUrl(); + } \ No newline at end of file diff --git "a/Spring-cloud/eureka/eureka-Regin\344\270\216Zone.java" "b/Spring-cloud/eureka/eureka-Regin\344\270\216Zone.java" new file mode 100644 index 00000000..149c7447 --- /dev/null +++ "b/Spring-cloud/eureka/eureka-Regin\344\270\216Zone.java" @@ -0,0 +1,9 @@ +---------------------- +Regin与Zone | +---------------------- + # 一个Regin可以包含多个Zone + # 每个客户端都要注册到一个Zone中,所以每个客户端对应一个Zone一个Regin + # 服务调用的时候,优先访问同一个Zone中的服务提供方,如果访问不到就尝试访问其他的Zone + + # 为应用指定Zone + eureka.client.availability-zones \ No newline at end of file diff --git a/Spring-cloud/eureka/eureka-api.java b/Spring-cloud/eureka/eureka-api.java new file mode 100644 index 00000000..7f042a1d --- /dev/null +++ b/Spring-cloud/eureka/eureka-api.java @@ -0,0 +1 @@ +EurekaCient \ No newline at end of file diff --git "a/Spring-cloud/eureka/eureka-\346\234\215\345\212\241\346\217\220\344\276\233\350\200\205.java" "b/Spring-cloud/eureka/eureka-\346\234\215\345\212\241\346\217\220\344\276\233\350\200\205.java" new file mode 100644 index 00000000..afb0334b --- /dev/null +++ "b/Spring-cloud/eureka/eureka-\346\234\215\345\212\241\346\217\220\344\276\233\350\200\205.java" @@ -0,0 +1,131 @@ +------------------------ +服务提供者 | +------------------------ + # maven依赖 + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + + # 开启服务自动注册到注册中心 + @SpringBootApplication + @EnableEurekaClient //当前为eureka的客户端 + public class UserServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(UserServiceApplication.class, args); + } + } + * @EnableEurekaClient 表示了当前微服务是通过 eureka 框架进行服务注册的,不能通过其他的 + * 可以使用:@EnableDiscoveryClient 注解,该注解是一个接口,可以适用于所有服务治理的框架 + + # 配置项(配置类:EurekaInstanceConfigBean) + spring.application.name=example-user-service + # 当前微服务的名称,会以大写的形式出现在 eureka 的控制台 + + eureka.client.service-url.defaultZone=http://localhost:10086/eureka/ + # 注册中心的地址 + # 如果要同时注册到多个注册中心,那么多个注册中心地址以逗号隔开 + + eureka.instance.prefer-ip-address=true + # 在eurake管理控制台中,该服务连接的地址以ip的形式出现,默认为主机名 + + eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${server.port} + # 在管控台中,实例连接的名称 + +------------------------ +服务提供者-info信息 | +------------------------ + # 添加依赖 + + org.springframework.boot + spring-boot-starter-actuator + + + # pom.xml 配置 + * 可以直接配置在prarent/pom.xml 中,所有子级模块都能使用 + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-resources-plugin + + + + $ + + + + + + + # yml + info: + app.name: javaweb-community + company.name: javaweb + build.artifactId: $project.artifactId$ + build.version: $project.version$ + + * info 其实就是一个map,可以在其下自定义的kv + * 因为maven插件的配置,可以使用 $$ 来获取构建版本等信息 + + # 在管理控制台,就可以以json的形式来通过 ..../info 来获取到以上配置的info信息(但是这个演失败了... ...) + {"app":{"name":"javaweb-community"},"company":{"name":"javaweb"},"build":{"artifactId":"$project.artifactId$","version":"$project.version$"}} + + # eurek控制台中 info 的访问地址是可以修改的 + eureka.instance.statusPageUrlPath=/info + + * 如果服务提供者带有 context-path 属性的话,或者修改了健康检查的地址,就必须要配置它了 + * 该配置支持绝对路径,也就是说,服务提供者假设以以https提供服务: + eureka.instance.statusPageUrlPath=https://localhost/info +------------------------ +服务提供者-健康检查 | +------------------------ + # 默认情况下注册到eureka server的服务是通过心跳来告知自己是UP还是DOWN + # 默认的方式只能知道服务提供者是否有心跳而已,不能知道服务是否还能正常的提供服务 + + # 所以,可以修改心跳检查为健康检查的方式,通过在eureka客户端中配置 + eureka.client.healthcheck.enabled=true + + * 就可以改变eureka server对客户端健康检测的方式,改用actuator的/health端点来检测。 + * 必须导入maven依赖 + + org.springframework.boot + spring-boot-starter-actuator + + + # 修改eureka server访问端点的健康检查地址 + eureka.instance.healthCheckUrlPath=/health + + * 必须保证该地址可以访问,否则注册中心不会根据端点的健康检查来修改端点的状态 + * 当然,前提是客户端开启了健康检查:eureka.client.healthcheck.enabled=true + + * 如果服务提供者带有 context-path 属性的话,或者修改了健康检查的地址,就必须要配置它了 + * 该配置支持绝对路径,也就是说,服务提供者假设以以https提供服务: + eureka.instance.healthCheckUrlPath=https://localhost/info + + +-------------------------------- +服务提供者-服务的续约与失效控制 | +------------------------------- + # 默认服务提供者会跟注册中心保持30s一次的心跳 + # 如果超过90s都没收到心跳,那么注册中心就会任务该服务凉了 + # 以上俩属性都可以通过配置来修改 + eureka.instance.lease-renewal-interval-in-seconds + * 服务续约任务的调用时间间隔,默认30s + eureka.instance.lease-expiration-duration-in-seconds + * 服务时效的时间,默认90s,就是说多少秒没有收到心跳算是服务失效,就会把服务从列表移除 + +------------------------ +服务提供者-服务下线 | +------------------------ + # 服务正常的关闭,会发送一个rest请求给注册中心,告知注册中心当前节点下线的信息 + # 注册中心收到消息后会吧该节点下线,并且把该事件广播出去 diff --git "a/Spring-cloud/eureka/eureka-\346\234\215\345\212\241\346\266\210\350\264\271\350\200\205.java" "b/Spring-cloud/eureka/eureka-\346\234\215\345\212\241\346\266\210\350\264\271\350\200\205.java" new file mode 100644 index 00000000..490425af --- /dev/null +++ "b/Spring-cloud/eureka/eureka-\346\234\215\345\212\241\346\266\210\350\264\271\350\200\205.java" @@ -0,0 +1,57 @@ +------------------------ +服务消费者 | +------------------------ + # maven依赖 + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + + + # 注解启动 + @EnableEurekaClient + * @EnableEurekaClient 表示了微服务是通过 eureka 框架进行服务注册的,不能通过其他的 + * 可以使用:@EnableDiscoveryClient 注解,该注解是一个接口,可以适用于所有服务治理的框架 + + # 配置项 + eureka.client.register-with-eureka:false + # 服务消费者,不注册 + eureka.client.service-url.defaultZone:http://localhost10086.com:10086/eureka,http://localhost10087.com:10087/eureka,http://localhost10088.com:10088/eureka + # 注册中心的地址,如果Eureka注册中心是集群,则配置进所有的集群节点信息 + + + # 调用消费者 + + //微服务的地址(其实就是名称),要大写 + private static final String SERVICE_NAME = "http://USER-SERVICE"; + + @Autowired + private RestTemplate restTemplate; + + @GetMapping + public ResponseEntity test(User user){ + this.restTemplate.postForObject(SERVICE_NAME + "/add",user,JSONObject.class); + } + + + * 注册 RestTemplate 的时候要添加 @LoadBalanced,表示启动负载均衡 + @Bean + @LoadBalanced + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + * 而且我发现如果没有该注解,那么微服务将会调用失败 + +------------------------ +服务提供者列表缓存 | +------------------------ + # 服务消费者每30s从注册中心获取最新的服务提供者列表,并且进行缓存 + # 可以通过配置修改该时间值 + + eureka.client.registry-fetch-interval-seconds + + * 该值默认30s,每隔30s从注册中心获取一次服务提供者的列表 diff --git "a/Spring-cloud/eureka/eureka-\346\263\250\345\206\214\344\270\255\345\277\203.java" "b/Spring-cloud/eureka/eureka-\346\263\250\345\206\214\344\270\255\345\277\203.java" new file mode 100644 index 00000000..d8131a58 --- /dev/null +++ "b/Spring-cloud/eureka/eureka-\346\263\250\345\206\214\344\270\255\345\277\203.java" @@ -0,0 +1,99 @@ +------------------------ +注册中心 | +------------------------ + # maven依赖 + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-server + + + # 开启 eureka server + @SpringBootApplication + @EnableEurekaServer //通过 @EnableEurekaServer 注解开启 eureka 注册中心 + public class RegisterApplication { + public static void main(String[] args)throws Exception{ + SpringApplication.run(RegisterApplication.class,args); + } + } + + + # 配置项,配置类(EurekaInstanceConfigBean,EurekaServerConfigBean) + + eureka.instance.name=localhost + # eureka服务端的实例名称 + eureka.client.fetch-registry=false + # 当前eureka 仅仅作为注册中心(server),不会去检索服务 + eureka.client.register-with-eureka=false + # 当前eureka仅仅充当注册中心,忽略自己作为服务提供者的注册行为 + + + eureka.client.service-url.defaultZone=http://localhost:${server.port}/eureka/ + # 服务提供者进行注册的地址,它是具备默认值的 + + +------------------------ +自我保护策略 | +------------------------ + # 默认情况下,Eureka在一定时间内没有接收到某个微服务实例的心跳,Eureka会注销该实例(默认90秒) + * 但是,当网络分区故障发生时,微服务与Eureka之间无法正常通信,以上行为可能变得非常危险了 + * 因为微服务本身是健康的,'不应该注销该微服务',Eureka通过'自我保护机制',来解决这个问题 + * 当Eurake节点在短时间内丢失了过多的客户端(服务提供者)时,那么这个节点就会进入自我保护模式 + * 进入该模式后,Eurake会保护服务注册表中的信息,不再进行删除表中的数据(也就是说不会注销任何服务) + * 在网络故障恢复时,自动退出自我保护模式 + * eureka服务器,默认每隔60s检查一次微服务的实例是否down掉 + + # 在自我保护模式中,Eurake会保护注册表中的信息,不再删除任何服务实例 + * 当它收到的心跳数重新恢复到阈值以上时,Eurake Server节点就会自动退出自我保护模式 + * 它的设计哲学就是:'宁可保护错误的服务注册信息,也不盲目注销任何可能健康的服务实例' + + # 自我保护模式是一种对应'网络异常的安全保保护措施' + * 它的架构哲学是宁可同时保留所有微服务(监控的微服务,和不健康的服务都会保留) + * 使用自我保护模式,可以让Eurake集群更加健壮文档 + + # 禁用自我保护模式 + eureka.server.enable-self-preservation=false + + # 这种机制下,客户端能会获取到失效的服务,所以要求客户端必须要有容错机制(重试,熔断) + + +------------------------ +安全的注册中心 | +------------------------ + # 注册中心配置 + 1,添加security依赖 + + org.springframework.boot + spring-boot-starter-security + + + 2,配置用户名和密码 + spring.security.user.name=KevinBlandy + spring.security.user.password=123456 + + 3,在访问路径上加入用户名密码 + eureka.client.serviceUrl.defaultZone=http://${spring.security.user.name}:${spring.security.user.password}@localhost:${server.port}/eureka/ + * 注意格式: 用户名:密码@主机名:端口 + + * 控制台也需要使用该用户名和密码登录 + + # 客户端的配置 + + spring.security.user.name=KevinBlandy + spring.security.user.password=123456 + + eureka.client.serviceUrl.defaultZone=http://${spring.security.user.name}:${spring.security.user.password}@localhost:10086/eureka/ + + * 客户端不用security的依赖,只用在注册中心的地址中添加用户名和密码 + + # springboot2.0以后security默认开启了csrf,可能导致客户端的注册失败(响应客户端401状态码) + * 可以尝试在服务端关闭csrf + + @EnableWebSecurity + public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable(); + super.configure(http); + } + } \ No newline at end of file diff --git "a/Spring-cloud/eureka/eureka-\346\263\250\345\206\214\344\270\255\345\277\203\351\233\206\347\276\244.java" "b/Spring-cloud/eureka/eureka-\346\263\250\345\206\214\344\270\255\345\277\203\351\233\206\347\276\244.java" new file mode 100644 index 00000000..c8cad6bc --- /dev/null +++ "b/Spring-cloud/eureka/eureka-\346\263\250\345\206\214\344\270\255\345\277\203\351\233\206\347\276\244.java" @@ -0,0 +1,55 @@ +---------------------------- +注册中心 | +---------------------------- + # 各个注册中心节点的名称要修改 + * 尽量保证集群节点中所有成员的instance.name不一样 + * 命名方式可以带上端口等信息 + * 可以通过本地host文件来做映射 + + eureka.instance.name=localhost10086.com + eureka.instance.name=localhost10087.com + eureka.instance.name=localhost10088.com + + # 允许注册与发现,默认值就是true,其实不用手动声明 + fetch-registry: true + register-with-eureka: true + + # 注册地址的修改 + * 在 defaultZone 添加上当前节点以外的所有节点成员的注册地址 + + eureka.client.service-url.defaultZone=http://localhost10086.com:10086/eureka,http://localhost10088.com:10088/eureka + * 当前节点为:10087,所以只需要添加 86 和 88 节点 + + + # 配置详情 + server: + port: 10088 + + spring: + application: + # 名称为注册中心 + name: register + + eureka: + instance: + # 当前节点的主机名 + hostname: register3 + # 当前节点的id(在控制台中的名称) + instance-id: ${eureka.instance.hostname}:${server.port} + client: + service-url: + # 其他的注册中心地址 + defaultZone: http://register1:10086/eureka/,http://register2:10087/eureka/ + + * 重要的一点就是,每个节点的id不能相同,否则会出现只有一个节点能用的情况 + + # 服务提供者与服务消费者 + * 它们仅仅需要设置集群中的任意节点为defaultZone即可 + service-url: + defaultZone: http://register3:10088/eureka/ + + * 也可以主动的链接所有注册中心集群节点 + service-url: + defaultZone: http://register3:10088/eureka/,http://register2:10087/eureka/,http://register1:10086/eureka/ + + * 注册中心集群中的每个节点,会相互的同步数据 \ No newline at end of file diff --git "a/Spring-cloud/eureka/eureka-\347\212\266\346\200\201\347\233\221\346\216\247.java" "b/Spring-cloud/eureka/eureka-\347\212\266\346\200\201\347\233\221\346\216\247.java" new file mode 100644 index 00000000..cecb413e --- /dev/null +++ "b/Spring-cloud/eureka/eureka-\347\212\266\346\200\201\347\233\221\346\216\247.java" @@ -0,0 +1,18 @@ +------------------------ +实例监控接口 | +------------------------ + # 该接口会返回JSON格式的实例状态信息 + # 需要添加依赖 + + org.springframework.boot + spring-boot-starter-actuator + + + * 不多解释,spring-boot的一个适用于生产环境的监控组件 + + # 配置项 + eureka.instance.status-page-url=${management.context-path}/info + # 实例状态页面地址 + + eureka.instance.health-check-url=${management.context-path}/health + # 运行状况指示器地址 \ No newline at end of file diff --git "a/Spring-cloud/eureka/eureka-\351\205\215\347\275\256\351\241\271.java" "b/Spring-cloud/eureka/eureka-\351\205\215\347\275\256\351\241\271.java" new file mode 100644 index 00000000..f67456df --- /dev/null +++ "b/Spring-cloud/eureka/eureka-\351\205\215\347\275\256\351\241\271.java" @@ -0,0 +1,74 @@ +---------------------------- +Eureka的配置项 | +---------------------------- + # 端点信息的配置 + * 主要是对端点进行描述的元数据 + + # 服务中心的配置 + * Eureka服务中心的相关配置 + + # 客户端的配置 + * 端点的一些配置 + + # 配置类 + EurekaServerConfigBean + EurekaInstanceConfigBean + EurekaClientConfigBean + +---------------------------- +Eureka的配置项 | +---------------------------- + + eureka.instance.name + # 节点的名称 + eureka.instance.hostname + # 节点的主机名称 + eureka.instance.instance-id + # 节点的id,区分同一个服务中的不同节点 + * 同一个服务中节点的id不能相同,不然会出现只有一个节点能用的情况 + eureka.instance.lease-renewal-interval-in-seconds + # 服务续约任务的调用时间间隔,默认30s + eureka.instance.lease-expiration-duration-in-seconds + # 服务时效的时间,默认90s,就是说多少秒没有收到心跳算是服务失效 + eureka.instance.statusPageUrlPath + # 修改访问实例的元信息的地址路径,默认:/info + eureka.instance.healthCheckUrlPath + # 修改访问实例健康状态的地址路径,默认:/health + + eureka.client.fetch-registry + # 是否从注册中心发现服务 + eureka.client.registry-fetch-interval-seconds + # 每隔多少秒从注册中心获取一个服务提供者列表信息,默认30s + eureka.client.register-with-eureka + # 是否注册服务到注册中心 + eureka.client.service-url.defaultZone + # 注册中心的地址 + # service-url 是一个 名为 serviceUrl 的 HashMap + * 默认的 key:defaultZone ,value:http://localhost:8761/eureka/ + eureka.client.healthcheck.enabled + # 是否通过 healthcheck 来检查服务提供者的状态 + + + eureka.server.enable-self-preservation + # 是否开启自我保护模式,默认 true + +---------------------------- +对应的配置Bean | +---------------------------- + # 服务端的相关配置参考bean + org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean + + * 该Bean里面的属性都是以:eureka.server 开头的 + + + # 客户端的相关配置参考bean + org.springframework.cloud.netflix.eureka.EurekaClientConfigBean + + * 该Bean里面的属性都是以:eureka.client 开头的 + + # 端点信息的配置Bean + org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean + + * 该Bean里面的属性都是以:eureka.instance 开头的 + * 该Bean里面包含了一个属性名为: metadataMap 的 HashMap,也就是说支持,自定义的键值参数 + eureka.instance.metadataMap.name=KevinBlandy diff --git a/Spring-cloud/eureka/eureka.java b/Spring-cloud/eureka/eureka.java new file mode 100644 index 00000000..444d129f --- /dev/null +++ b/Spring-cloud/eureka/eureka.java @@ -0,0 +1,189 @@ +------------------------ +eureka | +------------------------ + # 浠嬬粛 + * 鐢 Netflix 寮鍙戠殑涓涓湇鍔″彂鐜版鏋 + * 鏈韩鏄竴涓熀浜嶳EST鐨勬湇鍔 + * 涓昏鐢ㄤ簬瀹氫綅杩愯鍦ˋWS(浜氶┈閫婁簯鏈嶅姟)鍩熶腑鐨勪腑闂村眰鏈嶅姟,浠ヨ揪鍒拌礋杞藉潎琛″拰涓棿灞傛晠闅滆浆绉荤殑鐩殑 + * spring cloud 灏嗗畠闆嗘垚鍦ㄥ瓙椤圭洰 spring-cloud-netflix 涓,浠ュ疄鐜皊pring cloud鐨勬湇鍔″彂鐜板姛鑳 + + # 浼樼偣 + * eureka 鏉ヨ嚜浜庣敓瀛樼幆澧 + * spring cloud 瀵 eureka 鏀寔鑹ソ + * 椤圭洰娲昏穬,杩唬棰戠箒,鐩墠鏈鏂扮増鏈:1.3.5 + + + # 鍘熺悊 + + + # wiki + https://github.com/Netflix/eureka/wiki + + +------------------------ +娉ㄥ唽涓績 | +------------------------ + # maven渚濊禆 + + org.springframework.cloud + spring-cloud-starter-eureka-server + 1.3.5.RELEASE + + + # 寮鍚 eureka server + @SpringBootApplication + @EnableEurekaServer //閫氳繃 @EnableEurekaServer 娉ㄨВ寮鍚 eureka 娉ㄥ唽涓績 + public class RegisterApplication { + public static void main(String[] args)throws Exception{ + SpringApplication.run(RegisterApplication.class,args); + } + } + + + # 閰嶇疆椤 + + eureka.client.fetch-registry=false + # 褰撳墠eureka 浠呬粎浣滀负娉ㄥ唽涓績(server),涓嶄細鍘绘绱㈡湇鍔 + eureka.client.register-with-eureka=false + # 褰撳墠eureka浠呬粎鍏呭綋娉ㄥ唽涓績,蹇界暐鑷繁浣滀负鏈嶅姟鎻愪緵鑰呯殑娉ㄥ唽琛屼负 + + + eureka.client.service-url.defaultZone=http://localhost:${server.port}/eureka + # 鏈嶅姟鎻愪緵鑰呰繘琛屾敞鍐岀殑鍦板潃,瀹冩槸鍏峰榛樿鍊肩殑 + +------------------------ +鏈嶅姟鎻愪緵鑰 | +------------------------ + # maven渚濊禆 + + org.springframework.cloud + spring-cloud-starter-eureka + 1.3.5.RELEASE + + + + # 寮鍚湇鍔¤嚜鍔ㄦ敞鍐屽埌娉ㄥ唽涓績 + @SpringBootApplication + @EnableEurekaClient //褰撳墠涓篹ureka鐨勫鎴风 + public class UserServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(UserServiceApplication.class, args); + } + } + * @EnableEurekaClient 琛ㄧず浜嗗綋鍓嶅井鏈嶅姟鏄氳繃 eureka 妗嗘灦杩涜鏈嶅姟娉ㄥ唽鐨,涓嶈兘閫氳繃鍏朵粬鐨 + * 鍙互浣跨敤:@EnableDiscoveryClient 娉ㄨВ,璇ユ敞瑙f槸涓涓帴鍙,鍙互閫傜敤浜庢墍鏈夋湇鍔℃不鐞嗙殑妗嗘灦 + + # 閰嶇疆椤 + spring.application.name=example-user-service + # 褰撳墠寰湇鍔$殑鍚嶇О,浼氫互澶у啓鐨勫舰寮忓嚭鐜板湪 eureka 鐨勬帶鍒跺彴 + eureka.client.service-url.defaultZone=http://localhost:10086/eureka + # 娉ㄥ唽涓績鐨勫湴鍧 + eureka.instance.prefer-ip-address=true + # 鍦╡urake绠$悊鎺у埗鍙颁腑,璇ユ湇鍔¤繛鎺ョ殑鍦板潃浠p鐨勫舰寮忓嚭鐜,榛樿涓轰富鏈哄悕 + eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${server.port} + # 鍦ㄧ鎺у彴涓,瀹炰緥杩炴帴鐨勫悕绉 +------------------------ +鏈嶅姟娑堣垂鑰 | +------------------------ + # maven渚濊禆 + + org.springframework.cloud + spring-cloud-starter-eureka + 1.3.5.RELEASE + + + # 閰嶇疆椤 + + +------------------------ +鏈嶅姟璁よ瘉 | +------------------------ + # 淇濊瘉鍙湁鍙俊浠荤殑鏈嶅姟鎻愪緵鑰,鎵嶈兘娉ㄥ唽鍒版敞鍐屼腑蹇 + # 绠鍗曠殑璁よ瘉閰嶇疆 + security.basic.enabled=true + # 寮鍚畨鍏ㄩ厤缃 + security.user.name=root + # 閰嶇疆鐢ㄦ埛鍚 + security.user.password=root + # 閰嶇疆瀵嗙爜 + eureka.client.service-url.defaultZon=http://${security.user.name}:${security.user.password}@localhost:8761/eureka + # 鏈嶅姟娉ㄥ唽鏃跺,浣跨敤閰嶇疆鐨勭敤鎴峰悕鍜屽瘑鐮佹潵杩涜娉ㄥ唽 + + * 闇瑕佹坊鍔犱緷璧 + + org.springframework.boot + spring-boot-starter-security + + * 璇ラ厤缃垚鍔熷悗,涓嶄粎浠呮槸鏈嶅姟瀹炰緥闇瑕侀氳繃璐︽埛鍚嶅瘑鐮佹潵杩涜杩炴帴 + eureka.client.service-url.defaultZon=http://root:root@localhost:8761/eureka + * 鐧诲綍eurake绠$悊鎺у埗鍙颁篃闇瑕佽璐︽埛鍚嶅拰瀵嗙爜鏉ュ畬鎴愮櫥褰 + + # 閫氳繃缁勪欢鏉ュ畬鎴愭湇鍔$殑璁よ瘉 TODO + DiscoveryClientOptionArgs + +------------------------ +瀹炰緥鐩戞帶鎺ュ彛 | +------------------------ + # 璇ユ帴鍙d細杩斿洖JSON鏍煎紡鐨勫疄渚嬬姸鎬佷俊鎭 + # 闇瑕佹坊鍔犱緷璧 + + org.springframework.boot + spring-boot-starter-actuator + + + * 涓嶅瑙i噴,spring-boot鐨勪竴涓傜敤浜庣敓浜х幆澧冪殑鐩戞帶缁勪欢 + + # 閰嶇疆椤 + eureka.instance.status-page-url=${management.context-path}/info + # 瀹炰緥鐘舵侀〉闈㈠湴鍧 + eureka.instance.health-check-url=${management.context-path}/health + # 杩愯鐘跺喌鎸囩ず鍣ㄥ湴鍧 + +------------------------ +HTTPS | +------------------------ + TODO + +------------------------ +鍋ュ悍妫鏌 | +------------------------ + # Spring Boot Actuator鎻愪緵浜/health绔偣,璇ョ鐐瑰彲灞曠ず搴旂敤绋嬪簭鐨勫仴搴蜂俊鎭 + # 鍙湁灏嗘湇鍔″疄渚嬬殑鍋ュ悍鐘舵佷紶鎾埌Eureka Server灏卞彲浠ヤ簡,瀹炵幇杩欑偣寰堢畝鍗 + # 鏈嶅姟瀹炰緥閰嶇疆椤 + eureka.client.healthcheck.enabled=true + # 寮鍚仴搴锋鏌(闇瑕乻pring-boot-starter-actuator渚濊禆) + + +------------------------ +娉ㄨВ | +------------------------ + @EnableEurekaServer + # 寮鍚 eureka 娉ㄥ唽涓績鏈嶅姟 + + @EnableEurekaClient + # 寮鍚 eureka 寰湇鍔,骞朵笖鑷姩娉ㄥ唽鍒版敞鍐屼腑蹇 + +------------------------ +涓巣ookeeper姣旇緝 | +------------------------ + # Eurake淇濊瘉AP + * eurak璁捐鏃,鍏堜繚璇佸彲鐢ㄦ,eurak鍚勪釜鑺傜偣閮芥槸骞崇瓑鐨,鍑犱釜鑺傜偣鎸傛帀,涓嶄細褰卞搷鍒版甯歌妭鐐圭殑宸ヤ綔 + * Eurake鏈嶅姟鎻愪緵鑰呭悜娉ㄥ唽涓績娉ㄥ唽鏈嶅姟鏃,濡傛灉閾炬帴澶辫触,浼氳嚜鍔ㄥ垏鎹㈠埌鍏朵粬鐨勮妭鐐 + * 鍙鏈変竴鍙癊ureka杩樺湪,灏辫兘淇濊瘉娉ㄥ唽鏈嶅姟鍙敤(淇濊瘉鍙敤鎬) + * 鍙笉杩囨煡璇㈠埌鐨勬暟鎹彲鑳戒笉鏄渶鏂扮殑(涓嶄繚璇佸己涓鑷存) + * Eurake杩樻湁涓绉嶈嚜鎴戜繚鎶ゆ満鍒,15鍒嗛挓鍐呰秴杩85鐨勮妭鐐归兘娌℃湁姝e父鐨勫績璺,閭d箞Eurake浼氳涓烘敞鍐屼腑蹇冧笌鏈嶅姟鎻愪緵鑰呭嚭鐜颁簡缃戠粶鏁呴殰 + 1,Eureka涓嶅啀浠庢敞鍐屽垪琛ㄤ腑绉婚櫎,鍥犱负闀挎椂闂存病鏈夋敹鍒板績璺宠岃繃鏈熺殑鏈嶅姟 + 2,Eureka浠嶇劧鍙互鎺ユ敹鏂扮殑鏈嶅姟娉ㄥ唽,鍜屾煡璇㈣姹,浣嗘槸涓嶄細鍚屾鍒板叾浠栬妭鐐(淇濊瘉褰撳墠鑺傜偣渚濈劧鍙敤) + 3,褰撶綉缁滅ǔ瀹氭椂,褰撳墠瀹炰緥鏂版敞鍐岀殑淇℃伅浼氳鍚屾鍒板叾浠栬妭鐐逛腑 + + * Eurake鍙互寰堝ソ鐨勫簲瀵,鍥犵綉缁滃紓甯稿鑷撮儴鍒嗚妭鐐瑰け鍘昏仈绯荤殑鎯呭喌,鑰屼笉浼氬儚zookeeper閭f牱鐩存帴鐦棯鏁翠釜鑺傜偣 + + # Zookeeper淇濊瘉CP + * 浠庤妭鐐规煡璇㈡湇鍔′俊鎭殑鏃跺,鑳藉鎺ュ彈杩斿洖鐨勫彲鑳芥槸鍑犲垎閽熷墠鐨勬敞鍐屼俊鎭,浣嗘槸涓嶈兘鎺ュ彈杩斿洖瀹曟帀鐨勬湇鍔′俊鎭 + * 涔熷氨鏄,鏈嶅姟娉ㄥ唽鍔熻兘,瀵瑰彲鐢ㄦх殑瑕佹眰瑕侀珮浜庝竴鑷存 + * 浣嗘槸zk浼氬嚭鐜颁竴绉嶆儏鍐,褰搈aster鑺傜偣涓庡叾浠栬妭鐐瑰け鍘昏仈绯,鍓╀綑鑺傜偣浼氶噸鏂拌繘琛宭eader閫変妇 + 閫変妇leader鐨勬椂闂村お闀,30s - 120s,鑰屼笖閫変妇鏈熼棿鏁翠釜闆嗙兢涓嶅彲鐢,杩欎釜灏卞鑷村湪閫変妇鏈熼棿娉ㄥ唽鏈嶅姟鐦棯 + * 鍦ㄤ簯閮ㄧ讲鐜涓,鍥犱负缃戠粶闂瀵艰嚧鐨剒k闆嗙兢澶卞幓master鑺傜偣鐨勫嚑鐜囧緢澶,铏界劧鏈嶅姟鑳藉鎭㈠,浣嗘槸婕暱鐨勯変妇鏃堕棿瀵艰嚧鏈嶅姟娉ㄥ唽闀挎湡涓嶈兘浣跨敤鏄笉鍙蹇嶇殑 + + \ No newline at end of file diff --git "a/Spring-cloud/feign/feign-hystrix\351\205\215\347\275\256.java" "b/Spring-cloud/feign/feign-hystrix\351\205\215\347\275\256.java" new file mode 100644 index 00000000..59e2bbcb --- /dev/null +++ "b/Spring-cloud/feign/feign-hystrix\351\205\215\347\275\256.java" @@ -0,0 +1,46 @@ +---------------------------- +hystrix配置 | +---------------------------- + # feign会为所有fegin客户端的方法都封装到Hystrix命令中进行服务保护 + + # 开启,关闭Hystrix + fegin.hystrix.enabled=true/false + + # 关闭熔断功能 + hystrix.command.default.execution.timeout.enabled=false + + # 全局配置 + hystrix.command.default.= + + * 在对hystrix进行配置的时候,必须保证它是启用状态 + fegin.hystrix.enabled=true + +---------------------------- +禁用hystrix | +---------------------------- + # 全局关闭 + fegin.hystrix.enabled=false + + # 针对于某个服务进行关闭 + * 需要通过 @Scope("prototype") 注解来为客户端指定 Feign.Builder 实例 + + @Configuration + public DisableHystrixConfigration{ + @Bean + @Scope("prototype") + public Feign.Builder feignBuilder(){ + return Feign.Builder(); + } + } + + * 接口配置 + @Feign(value = "USER-SERVICE",configuration = {DisableHystrixConfigration.class}) + + +---------------------------- +指定命令配置 | +---------------------------- +---------------------------- +服务降级配置 | +---------------------------- + diff --git "a/Spring-cloud/feign/feign-ribbon\351\205\215\347\275\256.java" "b/Spring-cloud/feign/feign-ribbon\351\205\215\347\275\256.java" new file mode 100644 index 00000000..a311c60a --- /dev/null +++ "b/Spring-cloud/feign/feign-ribbon\351\205\215\347\275\256.java" @@ -0,0 +1,19 @@ +-------------------------------- +ribbon配置 | +-------------------------------- + # fegin使用ribbon来完成负载均衡 + # 可以直接配置ribbon的各个参数 + + # 全局的配置方式 + ribbon.ConnectTimeout=500 + ribbon.ReadTimeout=5000 + + * 配置语法:ribbon.= + + # 针对服务的配置方式 + USER-SERVICE.ribbon.ConnectTimeout=500 + USER-SERVICE.ribbon.ReadTimeout=500 + USER-SERVICE.ribbon.MaxAutoRetriesNextServer=2 + USER-SERVICE.ribbon.MaxAutoRetires=1 + + * 配置语法:<服务名称>.ribbon.= \ No newline at end of file diff --git "a/Spring-cloud/feign/feign-\345\217\202\346\225\260\347\273\221\345\256\232.java" "b/Spring-cloud/feign/feign-\345\217\202\346\225\260\347\273\221\345\256\232.java" new file mode 100644 index 00000000..80b87e95 --- /dev/null +++ "b/Spring-cloud/feign/feign-\345\217\202\346\225\260\347\273\221\345\256\232.java" @@ -0,0 +1,28 @@ +-------------------------------- +feign参数绑定 | +-------------------------------- + # 定义Demo + @FeignClient("USER-SERVICE") + public interface UserFegin { + + @GetMapping("/user/{id}") + UserEntity m1(@PathVariable("id")Integer userId); + * rest参数 + + @GetMapping("/user") + UserEntity m2(@RequestParam("id")Integer userId); + * form 表单参数 + + @GetMapping("/user") + UserEntity m3(@RequestHeader("h")String header); + * 带请求头参数 + + @PostMapping("/user") + UserEntity m4(UserEntity userEntity); + * 请求数据封装为对象 + } + + + # feign接口必须要通过:@RequestHeader,@RequestParam,@RequestBody 来绑定请求参数 + * 而且必须通过 value 属性来设置参数的名称 + \ No newline at end of file diff --git "a/Spring-cloud/feign/feign-\351\205\215\347\275\256.java" "b/Spring-cloud/feign/feign-\351\205\215\347\275\256.java" new file mode 100644 index 00000000..e18533bd --- /dev/null +++ "b/Spring-cloud/feign/feign-\351\205\215\347\275\256.java" @@ -0,0 +1,10 @@ +----------------------- +配置 | +----------------------- + # 配置类 + FeignClientProperties + + +feign: + hystrix: + enabled: true \ No newline at end of file diff --git "a/Spring-cloud/feign/feign-\351\207\215\350\257\225\346\234\272\345\210\266.java" "b/Spring-cloud/feign/feign-\351\207\215\350\257\225\346\234\272\345\210\266.java" new file mode 100644 index 00000000..b48b30d2 --- /dev/null +++ "b/Spring-cloud/feign/feign-\351\207\215\350\257\225\346\234\272\345\210\266.java" @@ -0,0 +1,5 @@ +------------------------ +重试机制 | +------------------------ + # feigin默认实现了重试机制 + # 可以通过配置来修改重试机制的属性 diff --git a/Spring-cloud/feign/feign.java b/Spring-cloud/feign/feign.java new file mode 100644 index 00000000..55e117aa --- /dev/null +++ b/Spring-cloud/feign/feign.java @@ -0,0 +1,160 @@ +------------------------ +feign | +------------------------ + # 是是一个声明式的WebService客户端 + * 使用Feign能让编写WebService客户端更加的简单 + * 它的使用方法是定义一个借口,然后在接口上添加注解,同时也支持JAX-RS标准的注解 + * Feign也支持可热拔插式的编码和解码器 + + # SpringCloud对Feign进行了封装,使其支持了SpringMVC标准注解和HttpMessageConverters + + # Feign可以和Eureka,Ribbon组合使用,以支持负载均衡 + * Feign默认集成了Ribbon,Hystrix,利用Ribbon维护了微服务提供者的列表信息,并且通过轮询实现了客户端的负载均衡 + * 与Ribbon不同,feign只需要定义服务绑定接口且以声明式的方法,简单而又优雅的实现了服务的调用 + + # 坐标 + + org.springframework.cloud + spring-cloud-starter-openfeign + + + * 仅仅需要该依赖 + * 不需要重复的声明:ribbon,hystrix + +------------------------ +入门体验 | +------------------------ + # 注解开启 + @EnableFeignClients + @SpringBootApplicatoon + @EnableEurekaClient + + * 通过 @EnableFeignClients 来启动Feign驱动 + + + # 定义接口 + @FeignClient(value = "USER-SERVICE") + @RequestMapping("/user") + public interface UserService { + + @GetMapping(value = "/info/{userId}") + Object userInfo(@PathVariable("userId")Integer userId); + } + + + * @FeignClient 通过该注解,指定微服务的名称 + * 使用路由注解(@GetMapping)来指定调用路径,通过 @PathVariable 来绑定参数 + * 跟mybatis的mapper一样,动态生成实现载入IOC中 + + # 配置 + feign.hystrix.enabled: true + * 开启熔断器,不开启好像 fegin不起作用 + + # 使用 FeignClient 接口 + + @Autowired + private UserService userService; + + @GetMapping("/user") + public User getUser(@RequestParam("id")int id){ + //以接口的形式调用微服务 + return this.userService.userInfo(id); + } + +-------------------- +注解的详细属性 | +-------------------- + # @EnableFeignClients + String[] value() default {}; + String[] basePackages() default {}; + Class[] basePackageClasses() default {}; + Class[] defaultConfiguration() default {}; + Class[] clients() default {}; + + # @FeignClient + @AliasFor("name") + String value() default ""; + @Deprecated + String serviceId() default ""; + String contextId() default ""; + @AliasFor("value") + String name() default ""; + String qualifier() default ""; + String url() default ""; + boolean decode404() default false; + Class[] configuration() default {}; + Class fallback() default void.class; + * 指定服务降级类(就是失败的重试方法) + * 该类应该实现当前的Client接口,并且覆写接口方法,这些方法就是接口的降级方法 + + Class fallbackFactory() default void.class; + String path() default ""; + boolean primary() default true; + +------------------------ +数据压缩 | +------------------------ + # fegin支持对请求和响应进行zip压缩,减少带宽的传输 + +feign: + hystrix: + enabled: true + compression: + request: + # 开启请求压缩 + enabled: true + # 支持压缩的ContentType + mime-types: + - text/xml + - application/xml + - application/json + # 执行压缩的最小体积 + min-request-size: 2048 + response: + # 是否压缩响应 + enabled: true + +------------------------ +日志配置 | +------------------------ + # feign在为 @FeignClient 构建客户端的时候,会为每一个客户端都创建一个: feign.Logger 实例对象 + * 可以利用该日志对象的DEBUG模式来分析Feign的请求细节 + + # 开启指定客户端的日志 + 1,配置客户端的日志 + logging.level.=DBUG + + * FeignClient就是客户端的接口全路径 + + 2,创建全局 Logger.Level + @Bean + public Logger.Level feignnLoggerLevel(){ + return Logger.Level.FULL; + } + + * feign默认的 Logger.Level 对象定义为NONE,不会记录任何Feign调用过程中的信息 + + 3,针对于指定服务指定 Logger.Level + @Configuration + public class FullLogConfiguration{ + @Bean + public Logger.Level feignnLoggerLevel(){ + return Logger.Level.FULL; + } + } + + @FeignClient(value = "USER-SERVICE",configuration = {FullLogConfiguration.class}) + + # Feign的Logger主要有 四个 + NONE + * 不记录任何信息 + BASIC + * 仅仅记录请求方式,URL,响应状态码和执行时间 + HEADERS + * 除了BASIC以外还会记录请求头和响应头信息 + FULL + * 记录所偶 + + + + diff --git "a/Spring-cloud/hystrix/hystrix-\346\234\215\345\212\241\351\231\215\347\272\247.java" "b/Spring-cloud/hystrix/hystrix-\346\234\215\345\212\241\351\231\215\347\272\247.java" new file mode 100644 index 00000000..013f6c13 --- /dev/null +++ "b/Spring-cloud/hystrix/hystrix-\346\234\215\345\212\241\351\231\215\347\272\247.java" @@ -0,0 +1,31 @@ +-------------------- +服务降级 | +-------------------- + # 服务的降级处理,是在客户端完成的 + + # 自定义类实现接口:FallbackFactory + + public interface FallbackFactory { + T create(Throwable cause); + } + + * T泛型,便是需要熔断的点 + * T可以理解为是Service,那么在这里会返回一个T,返回的这个T的所有方法,都是熔断点T的熔断方法 + * 通俗理解:你给我个T,我返回一个T给你,当你给我的T发生熔断事件,则触发我返回给你的T的对应的方法 + + * 该实现类不要忘记添加: @Component 注解,反正就是要给Spring的ioc管理 + + # 在 @FeignClient 添加fallbackFactory的属性值 + @FeignClient(value = "USER",fallbackFactory = UserFallbackFactoryImpl.class) + + * fallbackFactory的属性值为,FallbackFactory的实现类 + + # 在application配置文件添加配置 + feign.hystrix.enabled=true + + + # 其实这种方式,是在客户端完成的熔断,发现服务端都没法响应了,所以进行这个响应 + # 如果客户端执行了熔断,那么该服务会被降级 + + + diff --git "a/Spring-cloud/hystrix/hystrix-\347\233\221\346\216\247.java" "b/Spring-cloud/hystrix/hystrix-\347\233\221\346\216\247.java" new file mode 100644 index 00000000..2253cae6 --- /dev/null +++ "b/Spring-cloud/hystrix/hystrix-\347\233\221\346\216\247.java" @@ -0,0 +1,24 @@ +---------------------------- +hystrix 监控 | +---------------------------- + # Hystrix 提供了实时调用监控:Hystrix Dashboard + # 可以持续的记录所有通过 Hystrix发起的请求执行信息,并且以统计报表和图形的形式展示给用户 + * 包括每秒执行多少次请求,多少次请求执行成功,多少次失败 + + # maven坐标 + + org.springframework.cloud + spring-cloud-starter-netflix-hystrix-dashboard + + + + org.springframework.boot + spring-boot-starter-actuator + + + # 添加注解 + @EnableHystrixDashboard + + # 访问 + ${ctx}/hystrix + diff --git a/Spring-cloud/hystrix/hystrix.java b/Spring-cloud/hystrix/hystrix.java new file mode 100644 index 00000000..ef7bf650 --- /dev/null +++ b/Spring-cloud/hystrix/hystrix.java @@ -0,0 +1,103 @@ +------------------------- +hystrix | +------------------------- + # 熔断器,主要处理服务提供者, 服务的熔断,服务的降级 + # 多个微服务之间的连续调用,称之为为:扇出 + * 如果扇出链中,某个微服务响应时间过长,或者不可用,那么系统的执行过程就会漫长,进而引起系统雪崩 + + # Hystrix是专门用于处理分布式系统的延迟和容错 + * Hyxstri保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障 + + + # 断路器本身是一种开关控制 + * 当某个服务单元发生故障之后,通过断路器的故障监控(类似于保险丝),向调用方返回一个符合预期,可处理的备选响应 + * 而不是长时间的等待或者抛出调用方无法处理的异常 + * 从而避免了线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩 + + + # 服务熔断 + * 在某个微服务不可用,或者响应时间过长,会进行服务降级,进而熔断该服务的调用,快速响应错误信息 + * 当检测到该节点微服务响应正常后恢复调用链路 + + # 默认5秒内,20次调用失败,就会启动熔断机制 + + # 默认的超时时间是 2s + +------------------------- +初试 | +------------------------- + # 坐标(服务消费者依赖) + + org.springframework.cloud + spring-cloud-starter-netflix-hystrix + + + # 驱动注解 + @EnableCircuitBreaker + + * 服务消费者端开启 + * 默认超时时间:2000 毫秒 (2秒) + + + # 定义熔断处理 + //在映射处理器上通过@HystrixCommand的fallbackMethod来指定熔断方法 + //响应该方法的返回值给服务调用端 + @HystrixCommand(fallbackMethod = "processHystrix_Get") + @GetMapping("/user/{id}") + public User get(@PathVariable("id")Integer id){ + + } + + * 不仅仅是执行超时,如果该方法执行过程抛出了异常,也会触发熔断方法 + + + /*-----------------------------------------------------------*/ + + //熔断方法,权限修饰符没特别的要求 + public User processHystrix_Get (@PathVariable("id")Integer id){ + + } + + * 熔断方法如果不稳定,也可能会抛出异常的话,它也是可以添加:@HystrixCommand 注解来进行熔断加持的、 + * 可以在熔断方法的形参中添加一个参数:Throwable,以此来获取到目标方法中抛出的异常 + + # @HystrixCommand + String groupKey() default ""; + String commandKey() default ""; + String threadPoolKey() default ""; + + String fallbackMethod() default ""; + * 指定熔断处理方法名称(熔断方法在当前handler类中) + + HystrixProperty[] commandProperties() default {}; + + + HystrixProperty[] threadPoolProperties() default {}; + Class[] ignoreExceptions() default {}; + * 忽略目标方法中抛出的异常,不会触发熔断方法 + + ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER; + * 控制执行方式 + * 枚举值 + LAZY 使用 observe() 模式 + EAGER 使用 toObservable() 模式 + + HystrixException[] raiseHystrixExceptions() default {}; + String defaultFallback() default ""; + +------------------------- +通用的熔断处理 | +------------------------- + # 自定义类实现接口:FallbackFactory + + public interface FallbackFactory { + T create(Throwable cause); + } + + * T泛型,便是需要熔断的点 + * T可以理解为是Service,那么在这里会返回一个T,返回的这个T的所有方法,都是熔断点T的熔断方法 + * 通俗理解:你给我个T,我返回一个T给你,当你给我的T发生熔断事件,则触发我返回给你的T的对应的方法 + + * 该实现类不要忘记添加: @Component 注解,反正就是要给Spring的ioc管理 + + \ No newline at end of file diff --git a/Spring-cloud/ribbon/ribbon-IRule.java b/Spring-cloud/ribbon/ribbon-IRule.java new file mode 100644 index 00000000..89d3ba4d --- /dev/null +++ b/Spring-cloud/ribbon/ribbon-IRule.java @@ -0,0 +1,88 @@ +-------------------- +IRule | +-------------------- + # 负载均衡的算法都需要实现 IRule 接口 + # 自定义轮询实现,一般都直接继承: AbstractLoadBalancerRule + # 默认的算法(实现) + RoundRobinRule + * 轮询,当轮询到的服务不能调用时,会抛出异常 + + RandomRule + * 随机 + + AvailabilityFilteringRule + * 会先过滤由于多次访问故障处于断路器跳闸状态的服务 + * 还有并发连接数量超过阈值的服务,然对剩余服务列表按照轮询策略进行访问 + + WeightedResponseTimeRule + * 根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大 + * 服务刚启动,统计信息不足,则使用 RoundRobinRule 策略,在统计信息足够后,会切换回此Rule + + RetryRule + * 先按照 RoundRobinRule 策略获取服务,如果获取服务失败则在指定时间内进行重试 + * 其实就跟 RoundRobinRule 一样的,不过当服务不能进行调用的时候,会重新选择一个服务 + + BestAvailableRule + * 会优先过滤掉由于多次访问故障儿处于断路器跳闸状态的服务,然后选择一个并发量最小的服务 + + ZoneAvoidanceRule + * 复合判断Server所在区域的性能和Server的可用性选择服务器 + + +-------------------- +修改负载算法 | +-------------------- + # 注册全局通用的算法实现,只需要往ioc中注册指定的算法实现类即可 + @Bean + public IRule iRule(){ + //覆盖默认的负载均衡算法 + return RandomRule(); + } + + # 针对指定的服务,定义轮询算法 + 1,定义配置类,该类装载自定义的算法实现到ioc + //不要添加@Configuration + public class MyRule(){ + @Bean + public IRule myRule(){ + return new RandomRule(); + } + } + + 2,使用 @RibbonClient 来为指定的服务设置负载算发的实现 + @RibbonClient(name = "USER",configuration = MyRule.class) + @SpringBootApplication + class Application{ + + } + + * 注意,来自于官方的警告 + * 自定义的配置类(MyRule),不能放在 @CompentScan 所扫描的包以及子包下 + * 否则自定义的这个配置类,就会被所有 Ribbon 客户端所共享,也就达不到特殊定制化的目的了 + + * 很好理解:'这个特殊的MyRule如果被扫描进了ioc,那么就跟上面的配置一样,直接就是Ribbon的默认算法实现了' + * 反正就是针对于某个服务特定的算法实现类,不能被ioc加载(直接删除 @Configuration 注解,然后这个自定义算法实现放哪里都行) + + + * @RibbonClient 源码 + public @interface RibbonClient { + String value() default ""; + String name() default ""; + Class[] configuration() default {}; + //有点想不通,为啥这里的configuration不直接是 IRule 的子类呢,非要是 Confuguration 类? + } + + # 针对一批的服务,定义不同的轮询算法 + * 使用 @RibbonClients 注解 + * 源码,一看就懂怎么操作 + public @interface RibbonClients { + //定义多个 @RibbonClient + RibbonClient[] value() default {}; + //定义默认的算法实现,如果 @RibbonClient 没有算法实现,则会从这里获取 + Class[] defaultConfiguration() default {}; + } + + + + + diff --git "a/Spring-cloud/ribbon/ribbon-\351\205\215\347\275\256.java" "b/Spring-cloud/ribbon/ribbon-\351\205\215\347\275\256.java" new file mode 100644 index 00000000..46e513b5 --- /dev/null +++ "b/Spring-cloud/ribbon/ribbon-\351\205\215\347\275\256.java" @@ -0,0 +1,23 @@ +------------------------ +ribbon配置 | +------------------------ + # 全局的配置方式 + ribbon.ConnectTimeout=500 + ribbon.ReadTimeout=5000 + + * 配置语法:ribbon.= + + # 针对服务的配置方式 + USER-SERVICE.ribbon.ConnectTimeout=500 + USER-SERVICE.ribbon.ReadTimeout=500 + + * 配置语法:<服务名称>.ribbon.= + +------------------------ +ribbon配置-项 | +------------------------ + +ribbon: + eureka: + # 是否禁用Eureka对Ribbon服务实例的实现 + enabled: true \ No newline at end of file diff --git a/Spring-cloud/ribbon/ribbon.java b/Spring-cloud/ribbon/ribbon.java new file mode 100644 index 00000000..a160b2f8 --- /dev/null +++ b/Spring-cloud/ribbon/ribbon.java @@ -0,0 +1,83 @@ +---------------------------- +ribbon | +---------------------------- + # Spring Cloud Ribbon 是基于Netflix Ribbon实现的一套'客户端,负载均衡'工具 + # Ribbon是netflix发布的开源项目,主要提供客户端的软件负载均衡算法 + # LB(Load Balance)在微服务或者分布式集群中经常用的一种应用 + # 常见的负载均衡Nginx,LVS,硬件F5 + + # 负载均衡的类型 + * 硬件LB + * F5之类的,买不起 + * 进程内LB + * 把LB逻辑集成到消费方,消费方从注册中心获取服务地址,再从地址中选择一个进行调用 + + # ribbon在工作时分为两步 + 1,先选择EurekaServer,会选择同一区域内负载较少的EurekaServer + 2,根据用户指定的策略,从Server取到服务提供者(多个)的地址信息中获取一个进行远程调用 + + # ribbon提供了多种策略:轮询,随机,根据响应时间加权 + + # 默认算法是-轮询 + + +---------------------------- +整合 | +---------------------------- + # 坐标 + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + + + # 开启负载均衡 + @Configuration + public class RestTemplateConfig{ + + @Bean + @LoadBalanced + public RestTemplate restTemplate(){ + return new RestTemplate(); + } + } + + * 给 RestTemplate 注册Ioc时,添加 @LoadBalanced 注解 + * @LoadBalanced 是Springcloud定义的接口注解 + +---------------------------- +LoadBalancerClient | +---------------------------- + # 负载均衡器 + # @LoadBalanced 用来给 RestTemplate做标记,以使用 LoadBalancerClient 来配置他 + + public interface LoadBalancerClient extends ServiceInstanceChooser { + + /* + 来自于ServiceInstanceChooser接口 + 从负载均衡器中挑一个指定名称的服务实例 + */ + ServiceInstance choose(String serviceId); + + /* + 使用从负载均衡器中挑出来的实例执行请求 + */ + T execute(String serviceId, LoadBalancerRequest request) throws IOException; + + T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException; + + + /* + 为系统构建一个合适的URI: host:port + 就是把服务名称翻译为了: host:port + */ + URI reconstructURI(ServiceInstance instance, URI original); + } + +---------------------------- +重试机制 | +---------------------------- + //TODO diff --git a/Spring-cloud/springcloud-api.java b/Spring-cloud/springcloud-api.java new file mode 100644 index 00000000..24caef7a --- /dev/null +++ b/Spring-cloud/springcloud-api.java @@ -0,0 +1 @@ +DiscoveryClient diff --git "a/Spring-cloud/springcloud-\345\205\245\351\227\250.java" "b/Spring-cloud/springcloud-\345\205\245\351\227\250.java" index fc46f1af..bc876e19 100644 --- "a/Spring-cloud/springcloud-\345\205\245\351\227\250.java" +++ "b/Spring-cloud/springcloud-\345\205\245\351\227\250.java" @@ -20,8 +20,51 @@ http://www.guan2ye.com/ # 寰侀 - 01 + Eureka 鏈嶅姟娉ㄥ唽涓庡彂鐜 + Ribbon 璐熻浇鍧囪  + Feign 璐熻浇鍧囪  + Hystrix 鏂矾鍣 + Zuul 璺敱缃戝叧 + SpringCloud-Config 鍒嗗竷寮忛厤缃腑蹇 + + # 鍒嗗竷寮忔湇鍔 + 鏈嶅姟娌荤悊 + 鏈嶅姟娉ㄥ唽 + 鏈嶅姟璋冪敤 + 鏈嶅姟璐熻浇鍧囪  + 鏈嶅姟鐩戞帶 +---------------------------- +Spring-Cloud寰湇鍔℃妧鏈爤 | +---------------------------- + 鏈嶅姟寮鍙 + * springboot,spring,springmvc + 閰嶇疆涓庣鐞 + * Archaius,Diamond + 娉ㄥ唽涓庡彂鐜 + * Eureka,Consul,Zookeeper + 璋冪敤 + * Rest,Rpc,Grpc + 鐔旀柇鍣 + * Hystrix,Envoy + 璐熻浇鍧囪  + * Ribbon,Nginx + 鎺ュ彛璋冪敤(瀹㈡埛绔皟鐢ㄦ湇鍔$殑绠鍖栧伐鍏) + * Feign + 娑堟伅闃熷垪 + * MQ绯诲垪 + 閰嶇疆涓績绠$悊 + * SpringCloudConfig,Chef + 鏈嶅姟璺敱(API缃戝叧) + * Zuul + 鍏ㄩ摼璺拷韪 + * Zipkin,Brave,Dapper + 鏈嶅姟閮ㄧ讲 + * Docker,OpenStack,Kubernetes + 鏁版嵁娴佹搷浣滃紑鍙戝寘 + * SpringCloud Stream + 浜嬩欢娑堟伅鎬荤嚎 + * SpringCloud Bus ---------------------------- Spring-寰湇鍔″師鍒 | @@ -51,3 +94,18 @@ spring-cloud-vault spring-cloud-gateway + +---------------------------- +Spring-maven | +---------------------------- + + + + org.springframework.cloud + spring-cloud-dependencies + Greenwich.RELEASE + pom + import + + + \ No newline at end of file diff --git "a/Spring-cloud/springcloud-\346\234\215\345\212\241\345\217\221\347\216\260java.java" "b/Spring-cloud/springcloud-\346\234\215\345\212\241\345\217\221\347\216\260java.java" new file mode 100644 index 00000000..878bca40 --- /dev/null +++ "b/Spring-cloud/springcloud-\346\234\215\345\212\241\345\217\221\347\216\260java.java" @@ -0,0 +1,24 @@ +------------------------ +服务发现 | +------------------------ + # 其实就是,可以通过一个接口来获取到服务的信息 + # DiscoveryClient + # 伪代码 + + @Autowired + private DiscoveryClient discoveryClient; + + //所有服务提供者 + List services = discoveryClient.getServices(); + + services.forEach(System.out::println); + + //获取指定服务的所有提供者信息 + List instances = discoveryClient.getInstances("USER-SERVICE"); + + for(ServiceInstance serviceInstance : instances){ + serviceInstance.getServiceId(); + serviceInstance.getHost(); + serviceInstance.getPort(); + serviceInstance.getUri(); + } \ No newline at end of file diff --git "a/Spring-cloud/springcloud-\346\234\215\345\212\241\346\262\273\347\220\206-consul.java" "b/Spring-cloud/springcloud-\346\234\215\345\212\241\346\262\273\347\220\206-consul.java" deleted file mode 100644 index 0f760173..00000000 --- "a/Spring-cloud/springcloud-\346\234\215\345\212\241\346\262\273\347\220\206-consul.java" +++ /dev/null @@ -1,5 +0,0 @@ - - org.springframework.cloud - spring-cloud-starter-consul-discovery - 1.2.1.RELEASE - diff --git "a/Spring-cloud/springcloud-\346\234\215\345\212\241\346\262\273\347\220\206-eureka.java" "b/Spring-cloud/springcloud-\346\234\215\345\212\241\346\262\273\347\220\206-eureka.java" deleted file mode 100644 index 7e499c51..00000000 --- "a/Spring-cloud/springcloud-\346\234\215\345\212\241\346\262\273\347\220\206-eureka.java" +++ /dev/null @@ -1,6 +0,0 @@ - - org.springframework.cloud - spring-cloud-starter-eureka-server - 1.3.5.RELEASE - - diff --git "a/Spring-cloud/springcloud-\346\234\215\345\212\241\346\262\273\347\220\206.java" "b/Spring-cloud/springcloud-\346\234\215\345\212\241\346\262\273\347\220\206.java" index e69de29b..ed1ca9ad 100644 --- "a/Spring-cloud/springcloud-\346\234\215\345\212\241\346\262\273\347\220\206.java" +++ "b/Spring-cloud/springcloud-\346\234\215\345\212\241\346\262\273\347\220\206.java" @@ -0,0 +1,13 @@ +---------------------------- +鏈嶅姟娌荤悊 | +---------------------------- + # 鏈嶅姟鐨勫彂鐜版柟寮 + * 瀹㈡埛绔彂鐜 + Eureka + Zookeeper + + * 鏈嶅姟绔彂鐜 + Consul + Nginx + + + \ No newline at end of file diff --git "a/Spring-cloud/springcloud-\346\263\250\350\247\243.java" "b/Spring-cloud/springcloud-\346\263\250\350\247\243.java" index 8b4b882f..ff09f47a 100644 --- "a/Spring-cloud/springcloud-\346\263\250\350\247\243.java" +++ "b/Spring-cloud/springcloud-\346\263\250\350\247\243.java" @@ -1,4 +1,2 @@ @EnableDiscoveryClient -@EnableZipkinServer -@EnableHystrix -@EnableHystrixDashboard \ No newline at end of file + # 开启当前微服务的自动注册,该注解是一个抽象接口,可以兼容所有的微服务治理框架 diff --git "a/Spring-cloud/springcloud-\351\205\215\347\275\256.java" "b/Spring-cloud/springcloud-\351\205\215\347\275\256.java" new file mode 100644 index 00000000..17c46d7f --- /dev/null +++ "b/Spring-cloud/springcloud-\351\205\215\347\275\256.java" @@ -0,0 +1,16 @@ +------------------------------------ +预定义配置 | +------------------------------------ + spring.cloud.client.ipAddress + # 服务提供端的IP地址 + + spring.cloud.client.hostname + # 服务提供端的host name + + +spring: + cloud: + loadbalancer: + retry: + # 是否开启客户端的异常重试机制(配置类:LoadBalancerRetryProperties) + enabled: true \ No newline at end of file diff --git "a/Spring-cloud/zuul/zuul-filter-\345\274\202\345\270\270\345\244\204\347\220\206.java" "b/Spring-cloud/zuul/zuul-filter-\345\274\202\345\270\270\345\244\204\347\220\206.java" new file mode 100644 index 00000000..ecc7fdbd --- /dev/null +++ "b/Spring-cloud/zuul/zuul-filter-\345\274\202\345\270\270\345\244\204\347\220\206.java" @@ -0,0 +1,31 @@ +------------------------ +异常处理 | +------------------------ + # 使用 SendErrorFilter 来处理 + * 它负责处理异常,但是它是否执行的条件是,上下文中存在异常,并且尚未转发到errorPath + * errorPath : @Value("${error.path:/error}") + + @Override + public boolean shouldFilter() { + RequestContext ctx = RequestContext.getCurrentContext(); + return ctx.getThrowable() != null && !ctx.getBoolean("sendErrorFilter.ran", false); + } + + * 可以在执行过程中设置异常,来触发SendErrorFilter进行异常处理 + RequestContext requestContext = RequestContext.getCurrentContext(); + requestContext.setThrowable(new Exception()); + + * 也可以添加如下的参数 + // 错误编码 + requestContext.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + // 异常对象 + requestContext.set("error.exception",new Exception()); + // 错误信息 + requestContext.set("error.message","错误信息"); + + * 可以自己实现异常的转发的http接口,返回自己定义的错误信息 + + + # 因为在请求的生命周期:pre,post,route 三个阶段中,只要有异常抛出都会进入到erro阶段的处理,所以可以自定义ErrorFilter + + \ No newline at end of file diff --git "a/Spring-cloud/zuul/zuul-filter-\351\273\230\350\256\244\347\232\204Filter.java" "b/Spring-cloud/zuul/zuul-filter-\351\273\230\350\256\244\347\232\204Filter.java" new file mode 100644 index 00000000..25fab3bf --- /dev/null +++ "b/Spring-cloud/zuul/zuul-filter-\351\273\230\350\256\244\347\232\204Filter.java" @@ -0,0 +1,111 @@ +------------------------ +系统预定义Filter | +------------------------ + ServletDetectionFilter + * 它的执行顺序为: -3; + * 它的执行时间为: pre + * 是最先执行的Filter + * 主要的作用就是检测,当前的请求是Spring的 DispatcherServlet 还是 ZuulServlet 来处理运行的 + * 它的检测结果会保存在一个上下文参数中:String IS_DISPATCHER_SERVLET_REQUEST_KEY = "isDispatcherServletRequest"; + RequestUtils.isZuulServletRequest(); + RequestUtils.isDispatcherServletRequest(); + + Servlet30WrapperFilter + * 它的执行顺序为: -2 + * 它的执行时间为: pre + * 它是第二个执行过滤器 + * 主要的作用就是把 HttpServletRequest 封装为:Servlet30RequestWrapper(装饰者设计模式) + + FormBodyWrapperFilter + * 它的执行顺序为: -1 + * 它的执行时间为: pre + * 它是第三个执行的Filter + * 它对两类请求生效: + Content-Type = application/x-www-form-urlencoded + Content-Type = multipart/form-data + * 它的目的就是为把请求体包装为:FormBodyRequestWrapper 对象 + + DebugFilter + * 它的执行顺序为: 1 + * 它的执行时间为: pre + * 它是第四个执行的Filter + * 该Filter会根据系统配置:zuul.debug.request 和请求中的debug参数来决定是否要执行过滤器操作 + * 它的具体过滤操作内容就是,把当前请求上下文中的 debugRouting 和 debugRequest 参数设置为true + * 在同一个请求的生命周期内,都可以访问到这个值,所以后续的Filer可以根据这个两个值来定义一些dbug信息 + * 对于请求参数中的debug参数名,可以通过配置来设置:zuul.debug.parameter + + + PreDecorationFilter + * 它的执行顺序为: 5 + * 它的执行时间为: pre + * 它是系统预定义的pre阶段中最后执行的Filter + * 它会判断当前请求上下文中是否存在:forward.to 和 serviceId 参数 + * 如果都存,那么它就会执行过滤操作(只有一个参数存在的话,说明当前请求已经被处理过了,因为这两个信息就是根据当前请求的路由信息加载进来的) + * 它的具体过滤操作就是,为当前请求做一些预处理,比如进行路由规则的匹配,在请求上下文中设置该请求的基本信息等等 + * 这些信息是后续过滤器进行处理的重要依据,可以通过:RequestContext.getCurrentContext() 来访问 + + * 可以在该实现中看到一些对HTTP头进行添加的逻辑: + X-Forwarded-Prefix + X-Forwarded-Host + X-Forwarded-Port + X-Forwarded-Proto + .... + + * 可以通过参数来控制,是否添加这些请求头(X-Forwarded-*):zuul.addProxyHeaders=true + + + + RibbonRoutingFilter + * 它的执行顺序为: 10 + * 它的执行时间为: route + * 它是route阶段第一个执行的过滤器 + * 该过滤器只对请求上下文中存在serviceId参数的请求进行处理(也就是只对通过serviceId配置路由规则的请求生效) + * 该过滤器的执行逻辑就是面向路由核心,它通过使用Ribbon和Hystrix来向服务实例发起请求,并将服务实例的执行结果返回 + + SimpleHostRoutingFilter + * 它的执行顺序为: 100 + * 它的执行时间为: route + * 它是route阶段第二个执行的过滤器 + * 该过滤器只对请求上下文中存在 routeHost 参数的请求进行处理(也就是通过 url 配置路由规则的请求生效) + * 它的执行逻辑就是,直接对 url 发起请求,使用的是 httpclient 包实现的,没有使用Hystrix命令进行包装(所以没有线程隔离器和断路器的保护) + + SendForwardFilter + * 它的执行顺序为: 500 + * 它的执行时间为: route + * 它是route阶段第三个执行的过滤器 + * 它只对请求上下文中存在:forward.to参数的请求进行处理,即用来处理路由规则中的forward本地跳转配置 + + SendErrorFilter + * 它的执行顺序为: 0 + * 它的执行时间为: post + * 它是post阶段第一个执行的过滤器 + * 它负责处理异常,但是它是否执行的条件是,上下文中存在异常,并且尚未转发到errorPath + * 它的逻辑就是,使用请求上下文中的错误信息来组成一个 forward 到 api 网关 /error 错误端点的请求,来产生错误响应 + + SendResponseFilter + * 它的执行顺序为: 1000 + * 它的执行时间为: post + * 它是post阶段第最后一个执行的过滤器 + * 该过滤器会检测请求上下文中是否包含请求响应相关的头信息,响应数据流或是响应体 + * 只有在包含它们其中一个的时候执行处理逻辑 + * 处理逻辑就是:请求请求上下文的响应信息来组织需要发送回客户端的响应内容 + +------------------------ +系统预定义Filter | +------------------------ + +排序 Filter 功能 +pre --------------------------------------------------------- +-3 ServletDetectionFilter 标记Servlet类型 +-2 Servlet30WrapperFilter 包装HttpServletRequest对象 +-1 FormBodyWrapperFilter 包装请求体 + 1 DebugFilter 标记调试标志 + 5 PreDecorationFilter 处理请求上下文,以供后续使用 +route ------------------------------------------------------- +10 RibbonRoutingFilter serviceId转发 +100 SimpleHostRoutingFilter url请求转发 +500 SendForwardFilter forward请求转发 +post -------------------------------------------------------- +0 SendErrorFilter 处理有错误的请求响应 +1000 SendResponseFilter 处理正常的请求响应 + diff --git a/Spring-cloud/zuul/zuul-filter.java b/Spring-cloud/zuul/zuul-filter.java new file mode 100644 index 00000000..322a1251 --- /dev/null +++ b/Spring-cloud/zuul/zuul-filter.java @@ -0,0 +1,90 @@ +------------------------ +zuul | +------------------------ + # 请求过滤,实现接口: com.netflix.zuul.ZuulFilter + @Bean //注册ioc即生效 + public class DemoFilter extends ZuulFilter { + @Override + public boolean shouldFilter(); + * 判断当前过滤器是否被执行,如果返回 true,则执行:run() 方法 + + @Override + public Object run(); + * 执行校验的方法 + * 目前框架不会去处理返回值(忽略了返回值) + + @Override + public String filterType(); + * 定义过滤器的类型,它决定了过滤器在请求的哪个生命周期中执行,枚举字符串 + pre(FilterConstants.PRE_TYPE) + * 在请求被路由之前执行 + error(FilterConstants.ERROR_TYPE) + * 在请求异常时候处理 + post(FilterConstants.POST_TYPE) + * 在最后(route和error之后)调用 + route(FilterConstants.ROUTE_TYPE) + * 在请求路由时调用 + + + @Override + public int filterOrder(); + * 当存在多个过滤器的时候,该值定义了过滤器的执行顺序 + * 数值越小,优先级越高 + } + + # 校验小Demo + @Override + public Object run() { + RequestContext requestContext = RequestContext.getCurrentContext(); + HttpServletRequest httpServletRequest = requestContext.getRequest(); + if(httpServletRequest.getHeader("auth") == null) { + //不进行路由 + requestContext.setSendZuulResponse(false); + //设置响应状态码为401 + requestContext.setResponseStatusCode(HttpServletResponse.SC_UNAUTHORIZED); + return null; + } + return null; + } + + +------------------------ +通过配置来禁用过滤器 | +------------------------ + zuul...disable=true + + * zuul.AuthFilter.pre.disable=true + + + +------------------------ +动态Filter | +------------------------ + # 对于请求过滤器的动态加载,需要借助动态的jvm语言:Groovy + +------------------------ +FilterProcessor | +------------------------ + FilterProcessor getInstance() + void setProcessor(FilterProcessor processor) + + + +------------------------ +RequestContext | +------------------------ + # 请求上下文 + # RequestContext + HttpServletResponse getResponse(); + StringBuilder getFilterExecutionSummary(); + getOriginContentLength(); + getOriginResponseHeaders(); + getZuulResponseHeaders(); + getZuulEngineRan(); + getResponseGZipped(); + getResponseDataStream(); + getRequestQueryParams(); + getResponseStatusCode(); + getRouteHost(); + getResponseBody(); + diff --git "a/Spring-cloud/zuul/zuul-\350\267\257\347\224\261\350\257\246\350\247\243.java" "b/Spring-cloud/zuul/zuul-\350\267\257\347\224\261\350\257\246\350\247\243.java" new file mode 100644 index 00000000..37cc643a --- /dev/null +++ "b/Spring-cloud/zuul/zuul-\350\267\257\347\224\261\350\257\246\350\247\243.java" @@ -0,0 +1,106 @@ +---------------------------- +访问映射规则 | +---------------------------- + # 单实例配置(传统) + zuul: + routes: + user-api: + path:/api/** **/ + url:http://springboot.io/ + + * /api/user/1 会被转发到 -> http://springboot.io/user/1 + + # 多实例配置(面向服务) + zuul: + routes: + user-api: + serviceId: USER + path:/api/** **/ + + ribbon.eureka.enabled=false + USER.ribbon.listOfServers=http://localhost:8080/,http://localhost:8081/ + + * 就是把对path的访问,转发到服务名为USER的服务列表 + * USER是个自定义的服务名称 + + + # 服务路由配置 + zuul: + ignored-service: USER + routes: + user: + serviceId: USER + path: /myuser/** **/ + + + * ignored-service 属性非必须,它的存在就是废除指定服务的原有访问地址(/{服务名}/{uri}) + * 也就是说,必须使用routes定义的新地址 + * 如果该值为: * (星号),则表示废除所有的微服务原始访问地址 + + * routes 像是一个Map + * 里面定义了: + 路由名称.serviceId: 服务名称 + 路由名称.path: 服务新的访问地址 + + * http://网关ip:网关端口/路由名称/接口地址 + + + # 设置统一公共前缀 + zuul: + predix: /api + + * 所有的微服务前面都要加: /api + +---------------------------- +自定义路由映射规则 | +---------------------------- + # 实例化:PatternServiceRouteMapper + + @Bean + public PatternServiceRouteMapper patternServiceRouteMapper(){ + return new PatternServiceRouteMapper("","${name}"); + } + + * 可以使用正则来匹配出转发的路径 + + # 构造函数 + public PatternServiceRouteMapper(String servicePattern, String routePattern) + servicePattern + * 服务名称匹配规则 + + routePattern + * 路由的匹配规则 + +---------------------------- +本地跳转 | +---------------------------- + # 本地跳转 + zuul: + ignored-services: "*" + routes: + api-user: + path: /user-api/****/ + # 转发到网关的: /local/demo 上 + url: forward:/local/demo + +---------------------------- +Cookie 与头信息 | +---------------------------- + # zuul在请求路由的时候,会过滤掉请求头信息中的一些敏感信息,防止它们被传递到下游服务器 + # 配置(下列出来的就是默认值): + zuul: + sensitive-headers: + - Cookie + - Set-Cookie + - Authorization + + * 可以自己覆写它 + + # 也可以针对服务进行设置 + zuul.routes..customSensitiveHeaders=true + * 对指定的路由开启自定义敏感头 + + zuul.routes..sensitiveHeaders= + * 把指定路由的敏感头设置为空 + + diff --git "a/Spring-cloud/zuul/zuul-\351\205\215\347\275\256.java" "b/Spring-cloud/zuul/zuul-\351\205\215\347\275\256.java" new file mode 100644 index 00000000..17e49185 --- /dev/null +++ "b/Spring-cloud/zuul/zuul-\351\205\215\347\275\256.java" @@ -0,0 +1,34 @@ +# 配置类:ZuulProperties,ZuulConstants + +zuul: + # 禁止以默认的方式访问服务 + ignored-services: "*" + # 路由表 + routes: + # 自定义的路由名称 + api-user: + # 访问的路径 + path: /user-api/***/ + # 转发到的服务 + service-id: USER-SERVICE + # 转发的URL + url: https://springboot.io + # 该路由是否允许失败重试 + retryable: false + + # 统一当前网关的前缀 + prefix: /api + # 配置网关忽略的URI + ignored-patterns: + - /**/foo/****/ + # 过滤敏感的http头 + sensitive-headers: + - Cookie + - Set-Cookie + - Authorization + # 添加客户端(消费者)的HOST到http头 + add-host-header: true + # 添加代理头到HTTP头 + add-proxy-headers: true + # 是否允许失败重试 + retryable: false \ No newline at end of file diff --git a/Spring-cloud/zuul/zuul.java b/Spring-cloud/zuul/zuul.java new file mode 100644 index 00000000..4f0a0591 --- /dev/null +++ b/Spring-cloud/zuul/zuul.java @@ -0,0 +1,103 @@ +---------------------------- +zuul | +---------------------------- + # Zuul包含了对请求的:路由,过滤,代理 ...等核心的主要功能 + + # 路由 + * 负责把外部请求转发到具体的微服务实例上 + * 是实现外部访问统一入口的基础 + + # 过滤 + * 负责对请求的处理过程进行干预 + * 是实现请求校验,服务聚合等功能的基础 + + # Zuul和Eureka整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获取其他微服务的消息 + * 以后访问微服务,都是通过Zuul跳转后获得 + + # 'Zuul服务最终还是会注册进Eureka' + + # Maven + + + org.springframework.cloud + spring-cloud-starter-netflix-zuul + + +---------------------------- +zuul-入门体验 | +---------------------------- + # 配置 + * 它也要当作一个服务,注册到Eureka中 + * 所以,它也需要服务提供者的那一套eureka配置 + + spring: + application: + name: zuul + security: + user: + name: springcloud + password: 123456 + eureka: + client: + # 需要从注册中心获取服务 + fetch-registry: true + # 需要把自己注册到注册中心 + register-with-eureka: true + service-url: + defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@localhost:8081/eureka/ + + # 驱动注解 + @EnableZuulProxy + + * 标识注解,没有任何的属性 + + # 通过路由进行访问 + * 协议:网关主机:端口:服务名称/接口 + + http://localhost:8081/user-service/user/info/1 + + # 通过Feign访问 + // ZUUL也是注册到eureka的服务 + @FeignClient(value = "ZUUL") + // 通过 /{服务名}/{uri} 调用服务 + @RequestMapping("/user-service/user") + public interface UserService { + + @GetMapping(value = "/info/{userId}") + Object userInfo(@PathVariable("userId")Integer userId); + } + + +---------------------------- +hystrix 和 Ribbon的支持 | +---------------------------- + # zuul本身就包含了hystrix和Ribbon,它天生就有线程隔离和断路器的自我保护功能,以及对服务端负载均衡调用的功能 + * 当使用 path 与 url 的映射关系来配置路由的时候,不会有熔断和负载均衡的功能 + * 所以要尽量的使用 path 和 service-id 来配置路由 + + # 配置 + hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds + * 配置服务调用的超时时间,单位是毫秒 + * 如果超时,会把该执行命令标记为TIMEOUT,并且抛出异常,响应异常JSON给调用方 + + ribbon.ConnecTimeout + * 设置路由转发请求的时候,创建连接的超时时间 + * 该值应该小于服务调用的超时时间,因为一旦出现创建链接超时,系统会尝试进行重试 + * 如果重试失败,则响应失败信息给调用方 + + ribbon.readTimeout + * 设置路由建立后,读取服务响应的超时时间 + * 该值应该小于服务调用的超时时间,因为一旦出现读取接超时,系统会尝试进行重试 + + zuul.retryable=true + * 可以通过该配置关闭/开启重试机制 + + zuul.routes..retryable=false + * 可以针对路由映射,设置是否要开启失败重试机制 + + +------------------------ +动态路由 | +------------------------ + # 路由的配置规则,几乎都是通过config来配置的 + # 结合 springcloud-config ,的动态刷新机制就可以动态的刷新路由规则 \ No newline at end of file diff --git a/Spring-mvc/springmvc-REST.java b/Spring-mvc/springmvc-REST.java index 53916c16..4fd37d35 100644 --- a/Spring-mvc/springmvc-REST.java +++ b/Spring-mvc/springmvc-REST.java @@ -135,6 +135,15 @@ public ResponseEntity create(User user{ ... } + # 文件下载 + @GetMapping("/files/{filename:.+}") + @ResponseBody + public ResponseEntity serveFile(@PathVariable String filename) { + + Resource file = storageService.loadAsResource(filename); + return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename=\"" + file.getFilename() + "\"").body(file); + } + # build 和 body什么时候用 ? ---------------------- diff --git a/Spring-mvc/springmvc-RequestContextHolder.java b/Spring-mvc/springmvc-RequestContextHolder.java new file mode 100644 index 00000000..6d6d5484 --- /dev/null +++ b/Spring-mvc/springmvc-RequestContextHolder.java @@ -0,0 +1,20 @@ +----------------------------- +RequestContextHolder | +----------------------------- + # 褰撳墠request鐨勪竴涓嚎绋嬪畨鍏ㄥ鍣 + # 鑾峰彇褰撳墠鐨勮姹傛暟鎹俊鎭 + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes() + + * RequestAttributes 鏄竴涓帴鍙 + + + # 鑾峰彇request鍜宺esponse + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = requestAttributes.getRequest(); + HttpServletResponse response = requestAttributes.getResponse(); + + //浠 SCOPE_SESSION 閲岄潰鑾峰彇瀵瑰簲鐨勫 + String myValue = (String) requestAttributes.getAttribute("my_value",RequestAttributes.SCOPE_SESSION); + + + diff --git "a/Spring-mvc/springmvc\346\225\260\346\215\256\347\261\273\345\236\213\350\275\254\346\215\242.java" "b/Spring-mvc/springmvc\346\225\260\346\215\256\347\261\273\345\236\213\350\275\254\346\215\242.java" index c11b6faa..89d2be95 100644 --- "a/Spring-mvc/springmvc\346\225\260\346\215\256\347\261\273\345\236\213\350\275\254\346\215\242.java" +++ "b/Spring-mvc/springmvc\346\225\260\346\215\256\347\261\273\345\236\213\350\275\254\346\215\242.java" @@ -108,6 +108,19 @@ public Date convert(String date) { //逻辑代码 } + + + /*** + 枚举转换 + **/ + public class EnumConverter implements Converter>{ + + @SuppressWarnings("unchecked") + @Override + public Enum convert(String source) { + return Enum.valueOf(Enum.class, source.toUpperCase()); + } + } ============================赘述 diff --git "a/Spring/(XML)\351\205\215\347\275\256Bean.xml" "b/Spring/(XML)\351\205\215\347\275\256Bean.xml" index 6521600a..e5a2fe5c 100644 --- "a/Spring/(XML)\351\205\215\347\275\256Bean.xml" +++ "b/Spring/(XML)\351\205\215\347\275\256Bean.xml" @@ -439,3 +439,10 @@ constructor +BeanFactoryPostProcessor +ApplicationListener +ElasticProcessor +ClassPathBeanDefinitionScanner +ImportBeanDefinitionRegistrar +ResourceLoaderAware +EnvironmentAware \ No newline at end of file diff --git "a/Spring/sprin4.0\346\226\260\347\211\271\346\200\247.java" "b/Spring/spring-4.0\346\226\260\347\211\271\346\200\247.java" similarity index 100% rename from "Spring/sprin4.0\346\226\260\347\211\271\346\200\247.java" rename to "Spring/spring-4.0\346\226\260\347\211\271\346\200\247.java" diff --git "a/Spring/spring\345\257\271JDBC\347\232\204\346\224\257\346\214\201.java" "b/Spring/spring-JDBC\347\232\204\346\224\257\346\214\201.java" similarity index 100% rename from "Spring/spring\345\257\271JDBC\347\232\204\346\224\257\346\214\201.java" rename to "Spring/spring-JDBC\347\232\204\346\224\257\346\214\201.java" diff --git a/Spring/spring-RestTemplate.java b/Spring/spring-RestTemplate.java deleted file mode 100644 index eb27a5c9..00000000 --- a/Spring/spring-RestTemplate.java +++ /dev/null @@ -1,7 +0,0 @@ ----------------------------- -RestTemplate | ----------------------------- - * Spring RestTemplate 鏄 Spring 鎻愪緵鐨勭敤浜庤闂 Rest 鏈嶅姟鐨勫鎴风 - * RestTemplate 鎻愪緵浜嗗绉嶄究鎹疯闂繙绋婬ttp鏈嶅姟鐨勬柟娉,鑳藉澶уぇ鎻愰珮瀹㈡埛绔殑缂栧啓鏁堢巼 - * 寰堝鎴风姣斿 Android鎴栬呯涓夋柟鏈嶅姟鍟嗛兘鏄娇鐢 RestTemplate 璇锋眰 restful 鏈嶅姟 - diff --git a/Spring/springAOP.java b/Spring/spring-aop.java similarity index 100% rename from Spring/springAOP.java rename to Spring/spring-aop.java diff --git a/Spring/spring-ApplicationEvent.java b/Spring/spring-api-ApplicationEvent.java similarity index 100% rename from Spring/spring-ApplicationEvent.java rename to Spring/spring-api-ApplicationEvent.java diff --git a/Spring/spring-api-DataFieldMaxValueIncrementer.java b/Spring/spring-api-DataFieldMaxValueIncrementer.java new file mode 100644 index 00000000..76e6e584 --- /dev/null +++ b/Spring/spring-api-DataFieldMaxValueIncrementer.java @@ -0,0 +1,35 @@ +-------------------------------- +DataFieldMaxValueIncrementer | +-------------------------------- + # 用于获取DB自增的主键 + # 不同的DB,有不同的实现 + mysql实现 + MySQLMaxValueIncrementer + # DataFieldMaxValueIncrementer 接口定义了3个获取下一个主键值的方法 +   int nextIntValue() + * 获取下一个主键值,主键数据类型为int + +   long nextLongValue() + * 获取下一个主键值,主键数据类型为long + +   String nextStringValue() + * 获取下一个主键值,主键数据类型为String + + + # SpringBoot 配置 + @Autowired + private DataSource dataSource; + + @Bean + public DataFieldMaxValueIncrementer userIdGenarater(){ + MySQLMaxValueIncrementer m = new MySQLMaxValueIncrementer(); + //名称 + m.setIncrementerName("user_id_sequence"); + //列 + m.setColumnName("id"); + //缓存大小 + m.setCacheSize(1); + //设置数据源 + m.setDataSource(dataSource); + return m; + } \ No newline at end of file diff --git a/Spring/sprin4.0-jdbctemplate.java b/Spring/spring-api-JdbcTemplate.java similarity index 100% rename from Spring/sprin4.0-jdbctemplate.java rename to Spring/spring-api-JdbcTemplate.java diff --git a/Spring/spring-api-LocalVariableTableParameterNameDiscoverer.java b/Spring/spring-api-LocalVariableTableParameterNameDiscoverer.java new file mode 100644 index 00000000..3ab4c435 --- /dev/null +++ b/Spring/spring-api-LocalVariableTableParameterNameDiscoverer.java @@ -0,0 +1,28 @@ +-------------------------------------------- +LocalVariableTableParameterNameDiscoverer | +-------------------------------------------- + # 涓涓伐鍏风被,鍙互閫氳繃鍙嶅皠,鑾峰彇鍒版柟娉曠殑鍙傛暟鍚嶇О + + import java.lang.reflect.Method; + + import org.assertj.core.util.Arrays; + import org.springframework.core.LocalVariableTableParameterNameDiscoverer; + + public class ParameterNameTest { + + public static void main(String[] args) throws NoSuchMethodException, SecurityException { + + LocalVariableTableParameterNameDiscoverer localVariableTableParameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + + //鐩爣鏂规硶瀵硅薄 + Method method = ParameterNameTest.class.getMethod("main", String[].class); + + String[] names = localVariableTableParameterNameDiscoverer.getParameterNames(method); + + //localVariableTableParameterNameDiscoverer.getParameterNames(Constructor constructor); 杩樺彲浠ヨ幏鍙栨瀯閫犲櫒鐨勫弬鏁板悕绉 + + System.out.println(Arrays.asList(names)); //[args] + } + } + + # jdk8,宸茬粡鍏峰浜嗗彲浠ョ洿鎺ヨ幏鍙栧埌鍙傛暟瀵硅薄鐨凙PI,浣嗘槸闇瑕佸湪缂栬瘧鐨勬椂鍊欐坊鍔犲弬鏁,璁╃紪璇戝櫒淇濈暀鍙傛暟鍚嶇О灞炴 diff --git a/Spring/spring-api-RestTemplate.java b/Spring/spring-api-RestTemplate.java new file mode 100644 index 00000000..a2ca3a27 --- /dev/null +++ b/Spring/spring-api-RestTemplate.java @@ -0,0 +1,94 @@ +---------------------------- +RestTemplate | +---------------------------- + # RestTemplate + * Spring RestTemplate 鏄 Spring 鎻愪緵鐨勭敤浜庤闂 Rest 鏈嶅姟鐨勫鎴风 + * RestTemplate 鎻愪緵浜嗗绉嶄究鎹疯闂繙绋婬ttp鏈嶅姟鐨勬柟娉,鑳藉澶уぇ鎻愰珮瀹㈡埛绔殑缂栧啓鏁堢巼 + * 寰堝鎴风姣斿 Android鎴栬呯涓夋柟鏈嶅姟鍟嗛兘鏄娇鐢 RestTemplate 璇锋眰 restful 鏈嶅姟 + + # 鍙傝冭祫鏂 + http://www.jianshu.com/p/c9644755dd5e + + # 涓昏鏋勬垚 + HttpMessageConverter + * 瀵硅薄杞崲鍣 + + ClientHttpRequestFactory + * HTTP璇锋眰宸ュ巶,榛樿鏄疛DK鐨凥ttpURLConnection,鍙互閫氳繃浣跨敤ClientHttpRequestFactory鎸囧畾涓嶅悓鐨凥TTP璇锋眰鏂瑰紡,璁剧疆ssl绛 + + ResponseErrorHandler + * 寮傚父閿欒澶勭悊 + + ClientHttpRequestInterceptor + * 璇锋眰鎷︽埅鍣 + +---------------------------- +RestTemplate-瀹炰緥鍒涘缓 | +---------------------------- + # 鐩存帴鍒涘缓 + new RestTemplate() + + # 宸ュ巶鍒涘缓 + new RestTemplateBuilder().build(); + + + + + +---------------------------- +RestTemplate-api | +---------------------------- + # 灞炴ц缃浉鍏 + public void setMessageConverters(List> messageConverters) + * 璁剧疆HTTP娑堟伅瑙f瀽鍣 + + public List> getMessageConverters() + * 鑾峰彇榛樿鐨凥TTP娑堟伅瑙f瀽鍣 + * 榛樿娉ㄥ唽 + org.springframework.http.converter.ByteArrayHttpMessageConverter + org.springframework.http.converter.StringHttpMessageConverter + org.springframework.http.converter.ResourceHttpMessageConverter + org.springframework.http.converter.xml.SourceHttpMessageConverter + org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter + org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter + org.springframework.http.converter.json.MappingJackson2HttpMessageConverter + + # HTTP璇锋眰鐩稿叧鐨刟pi + * RestTemplate瀹氫箟浜36涓笌REST璧勬簮浜や簰鐨勬柟娉,鍏朵腑鐨勫ぇ澶氭暟閮藉搴斾簬HTTP鐨勬柟娉 + * 鍏跺疄,杩欓噷闈㈠彧鏈11涓嫭绔嬬殑鏂规硶,鍏朵腑鏈夊崄涓湁涓夌閲嶈浇褰㈠紡,鑰岀鍗佷竴涓垯閲嶈浇浜嗗叚娆,杩欐牱涓鍏卞舰鎴愪簡36涓柟娉 + + delete() + * 鍦ㄧ壒瀹氱殑URL涓婂璧勬簮鎵цHTTP DELETE鎿嶄綔 + exchange() + * 鍦║RL涓婃墽琛岀壒瀹氱殑HTTP鏂规硶,杩斿洖鍖呭惈瀵硅薄鐨凴esponseEntity,杩欎釜瀵硅薄鏄粠鍝嶅簲浣撲腑 鏄犲皠寰楀埌鐨 + execute() + * 鍦║RL涓婃墽琛岀壒瀹氱殑HTTP鏂规硶锛岃繑鍥炰竴涓粠鍝嶅簲浣撴槧灏勫緱鍒扮殑瀵硅薄 + getForEntity() + * 鍙戦佷竴涓狧TTP GET璇锋眰,杩斿洖鐨凴esponseEntity鍖呭惈浜嗗搷搴斾綋鎵鏄犲皠鎴愮殑瀵硅薄 + getForObject() + * 鍙戦佷竴涓狧TTP GET璇锋眰,杩斿洖鐨勮姹備綋灏嗘槧灏勪负涓涓璞 + postForEntity() + * POST 鏁版嵁鍒颁竴涓猆RL,杩斿洖鍖呭惈涓涓璞$殑ResponseEntity,杩欎釜瀵硅薄鏄粠鍝嶅簲浣撲腑鏄犲皠寰 鍒扮殑 + postForObject() + * POST 鏁版嵁鍒颁竴涓猆RL,杩斿洖鏍规嵁鍝嶅簲浣撳尮閰嶅舰鎴愮殑瀵硅薄 + headForHeaders() + * 鍙戦丠TTP HEAD璇锋眰,杩斿洖鍖呭惈鐗瑰畾璧勬簮URL鐨凥TTP澶 + optionsForAllow() + * 鍙戦丠TTP OPTIONS璇锋眰,杩斿洖瀵圭壒瀹歎RL鐨凙llow澶翠俊鎭 + postForLocation() + * POST 鏁版嵁鍒颁竴涓猆RL,杩斿洖鏂板垱寤鸿祫婧愮殑URL + put() + * PUT 璧勬簮鍒扮壒瀹氱殑URL + +---------------------------- +RestTemplate-POST | +---------------------------- + RestTemplate restTemplate = new RestTemplate(); + //璇锋眰澶 + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); + //鏋勫缓璇锋眰浣 + HttpEntity httpEntity = new HttpEntity<>("{\"name\":\"KevinBlandy\"}",httpHeaders); + //鎵цREST璇锋眰,鑾峰彇缁撴灉 + ResponseEntity responseEntity = restTemplate.postForEntity("http://localhost/user", httpEntity, String.class); + \ No newline at end of file diff --git a/Spring/sprin4.0-api.java b/Spring/spring-api.java similarity index 100% rename from Spring/sprin4.0-api.java rename to Spring/spring-api.java diff --git a/Spring/sprin4.0-task.java b/Spring/spring-task.java similarity index 100% rename from Spring/sprin4.0-task.java rename to Spring/spring-task.java diff --git "a/Spring/sprin4.0-\344\270\200\344\272\233Bean.java" "b/Spring/spring-\344\270\200\344\272\233Bean.java" similarity index 100% rename from "Spring/sprin4.0-\344\270\200\344\272\233Bean.java" rename to "Spring/spring-\344\270\200\344\272\233Bean.java" diff --git "a/Spring/spring-\344\272\213\345\212\241\347\256\241\347\220\206\347\232\204\346\240\270\345\277\203.java" "b/Spring/spring-\344\272\213\345\212\241\347\256\241\347\220\206\347\232\204\346\240\270\345\277\203.java" new file mode 100644 index 00000000..c9def274 --- /dev/null +++ "b/Spring/spring-\344\272\213\345\212\241\347\256\241\347\220\206\347\232\204\346\240\270\345\277\203.java" @@ -0,0 +1,119 @@ +---------------------------- +PlatformTransactionManager | +---------------------------- + # 事务管理器 + # 一些实现类 + DataSourceTransactionManager(Jdbc/MyBatis/JdbcTmplate) + HibernateTransactionManager(hibernate) + JtaTransactionManager(jta) + |-WebLogicJtaTransactionManager + |-WebSphereUowTransactionManager + + TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; + * 该方法根据事务定义信息从事务环境返回一个已经存在的事务,或者创建一个事务 + + void commit(TransactionStatus status) throws TransactionException; + * 根据事务状态提交事务 + * 如果事务被标记为rollbackOnly,则该方法执行回滚操作 + + void rollback(TransactionStatus status) throws TransactionException; + * 回滚事务 + * 当commit方法抛出异常时,该方法将会被隐式调用 + +---------------------------- +TransactionStatus | +---------------------------- + # 继承自接口:SavepointManager + # 事务具体运行状态 + + boolean isNewTransaction(); + * 判断当前事务是否是一个新的事务,如果返回false,则表示当前事务是一个已经存在的事务,或者当前操作未运行在事务环境中 + + boolean hasSavepoint(); + void setRollbackOnly(); + boolean isRollbackOnly(); + * 不提交,仅仅执行回滚 + * 将当前事务标记为rollbackOnly,从而通知事务管理器只能回滚该事务 + + void flush(); + boolean isCompleted(); + * 判断当前事务是否已经结束(已经提交或回滚) + + Object createSavepoint() throws TransactionException; + * 创建恢复点 + + void rollbackToSavepoint(Object savepoint) throws TransactionException; + * 回滚到恢复点 + + void releaseSavepoint(Object savepoint) throws TransactionException; + * 保存恢复点 + +---------------------------- +TransactionDefinition | +---------------------------- + # 事务的定义(隔离级别,传播,超时,只读) + + int PROPAGATION_REQUIRED = 0; + int PROPAGATION_SUPPORTS = 1; + int PROPAGATION_MANDATORY = 2; + int PROPAGATION_REQUIRES_NEW = 3; + int PROPAGATION_NOT_SUPPORTED = 4; + int PROPAGATION_NEVER = 5; + int PROPAGATION_NESTED = 6; + * 事务传播属性 + + int ISOLATION_DEFAULT = -1; + * 使用数据库默认的隔离级别 + int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; + int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; + int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; + int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; + * 事务的隔离级别 + + int TIMEOUT_DEFAULT = -1; + * 默认的超时 + + int getPropagationBehavior(); + int getIsolationLevel(); + * 获取隔离级别和事务传播属性 + + int getTimeout(); + * 超时时间 + + boolean isReadOnly(); + * 是否只读 + + String getName(); + +---------------------------------- +TransactionSynchronizationManager | +---------------------------------- + # 事务的同步管理器(abstract) + * 事务是和线程紧密相关的 + * 事务同步管理器使用ThreadLocal为每个线程绑定一个数据库连接 + + * 比如在一个线程中,当一个Service的方法被执行时,如果这个方法需要一个事务 + * spring会在开启事务时,利用事务同步管理器为该线程绑定一个数据库连接 + * 之后在当前线程的这个事务内,只要需要用到数据库连接,都是从ThreadLocal获取这个之前被绑定的连接 + * 这也是为什么像JdbcTemplate这种单例的Bean能够正常工作在多线程环境中 + * 因为JdbcTemplate在执行sql时也是从事务同步管理器中拿数据库连接的 + + # 私有的静态 + ThreadLocal> resources = new NamedThreadLocal<>("Transactional resources"); + * 当前线程对应的connection或session等类型的资源 + + ThreadLocal> synchronizations = new NamedThreadLocal<>("Transaction synchronizations"); + * 存放当前线程的事务执行过程中的回调操作 + + ThreadLocal currentTransactionName = new NamedThreadLocal<>("Current transaction name"); + * 当前线程的事务名称 + + ThreadLocal currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status"); + * 当前线程的事务是否为只读 + + ThreadLocal currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level"); + * 当前线程的事务隔离级别 + + ThreadLocal actualTransactionActive = new NamedThreadLocal<>("Actual transaction active"); + * 当前线程的事务是否被激活 + diff --git "a/Spring/sprin4.0-\345\267\245\345\205\267.java" "b/Spring/spring-\345\267\245\345\205\267.java" similarity index 100% rename from "Spring/sprin4.0-\345\267\245\345\205\267.java" rename to "Spring/spring-\345\267\245\345\205\267.java" diff --git "a/Spring/sprin4.0-\346\240\207\347\255\276.java" "b/Spring/spring-\346\240\207\347\255\276.java" similarity index 100% rename from "Spring/sprin4.0-\346\240\207\347\255\276.java" rename to "Spring/spring-\346\240\207\347\255\276.java" diff --git "a/Spring/sprin4.0-\346\263\250\350\247\243\346\225\264\347\220\206.java" "b/Spring/spring-\346\263\250\350\247\243\346\225\264\347\220\206.java" similarity index 83% rename from "Spring/sprin4.0-\346\263\250\350\247\243\346\225\264\347\220\206.java" rename to "Spring/spring-\346\263\250\350\247\243\346\225\264\347\220\206.java" index cf1d1990..9905173c 100644 --- "a/Spring/sprin4.0-\346\263\250\350\247\243\346\225\264\347\220\206.java" +++ "b/Spring/spring-\346\263\250\350\247\243\346\225\264\347\220\206.java" @@ -51,6 +51,9 @@ @Profile # 属性 String[] value(); + # :指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件 + # 可以标识在类,方法上,用于指定当前环境,如果符合配置(下),则当前类生效 + * spring.profiles.active @CrossOrigin # 跨域支持 @@ -58,6 +61,8 @@ @DependsOn # 标识在 Bean 组件上,表示该组件依赖于该注解指定的组件(该组件会被优先的加载到mvc) +@Primary + # 自动装配时当出现多个Bean候选者时,被注解为 @Primary 的Bean将作为首选者,否则将抛出异常 diff --git a/Sql2o/sql2o-api-Connection.java b/Sql2o/sql2o-api-Connection.java new file mode 100644 index 00000000..47167ab4 --- /dev/null +++ b/Sql2o/sql2o-api-Connection.java @@ -0,0 +1,30 @@ +------------------------------ +Connection | +------------------------------ + void close() + + Sql2o commit() + Connection commit(boolean closeConnection) + + Sql2o rollback() + Connection rollback(boolean closeConnection) + + Query createQuery(String queryText) + Query createQuery(String queryText, boolean returnGeneratedKeys) + Query createQuery(String queryText, String ... columnNames) + Query createQueryWithParams(String queryText, Object... paramValues) + + int[] getBatchResult() + java.sql.Connection getJdbcConnection() + Object getKey() + V getKey(Class returnType) + Object[] getKeys() + List getKeys(Class returnType) + int getResult() + Sql2o getSql2o() + boolean isRollbackOnClose() + boolean isRollbackOnException() + + + Connection setRollbackOnClose(boolean rollbackOnClose) + Connection setRollbackOnException(boolean rollbackOnException) \ No newline at end of file diff --git a/Sql2o/sql2o-api-Query.java b/Sql2o/sql2o-api-Query.java new file mode 100644 index 00000000..db360c6c --- /dev/null +++ b/Sql2o/sql2o-api-Query.java @@ -0,0 +1,99 @@ +------------------------- +Query | +------------------------- + Query(Connection connection, String queryText, boolean returnGeneratedKeys) + Query(Connection connection, String queryText, String[] columnNames) + + + Query addColumnMapping(String columnName, String propertyName) + + Query addParameter(String name, final Boolean value) + Query addParameter(String name, final boolean value) + Query addParameter(String name, Class parameterClass, T value) + Query addParameter(String name, final Collection values) + Query addParameter(String name, final InputStream value) + Query addParameter(String name, final int value) + + Query addParameter(String name, final Integer value) + Query addParameter(String name, final Long value) + + Query addParameter(String name, final long value) + Query addParameter(String name, Object value) + + Query addParameter(String name, final Object ... values) + Query addParameter(String name, final String value) + Query addParameter(String name, final Time value) + Query addParameter(String name, final Timestamp value) + Query addParameter(String name, final UUID value) + Query addParameter(String name, Class parameterClass, T value) + + Query addToBatch() + List addToBatchGetKeys(Class klass) + + Query bind(final Object pojo) + void close() + + List executeAndFetch(Class returnType) + List executeAndFetch(ResultSetHandler resultSetHandler) + List executeAndFetch(ResultSetHandlerFactory factory) + + T executeAndFetchFirst(Class returnType) + T executeAndFetchFirst(ResultSetHandler resultSetHandler) + T executeAndFetchFirst(ResultSetHandlerFactory resultSetHandlerFactory) + + ResultSetIterable executeAndFetchLazy(final Class returnType) + ResultSetIterable executeAndFetchLazy(final ResultSetHandler resultSetHandler) + ResultSetIterable executeAndFetchLazy(final ResultSetHandlerFactory resultSetHandlerFactory) + + Table executeAndFetchTable() + LazyTable executeAndFetchTableLazy() + Connection executeBatch() + + Object executeScalar() + V executeScalar(Class returnType) + V executeScalar(Converter converter) + List executeScalarList(final Class returnType) + Connection executeUpdate() + Map getColumnMappings() + Query setColumnMappings(Map mappings) + Connection getConnection() + + int getCurrentBatchRecords() + int getMaxBatchRecords() + + String getName() + + Map> getParamNameToIdxMap() + + Quirks getQuirks() + + ResultSetHandlerFactoryBuilder getResultSetHandlerFactoryBuilder() + + boolean isAutoDeriveColumnNames() + + boolean isCaseSensitive() + + boolean isExplicitExecuteBatchRequired() + + boolean isThrowOnMappingFailure() + + void logExecution() + + Query setAutoDeriveColumnNames(boolean autoDeriveColumnNames) + + Query setCaseSensitive(boolean caseSensitive) + + Query setColumnMappings(Map mappings) + + Query setMaxBatchRecords(int maxBatchRecords) + + Query setName(String name) + + void setResultSetHandlerFactoryBuilder(ResultSetHandlerFactoryBuilder resultSetHandlerFactoryBuilder) + + Query throwOnMappingFailure(boolean throwOnMappingFailure) + + static Object[] toObjectArray(Object val) + + Query withParams(Object... paramValues) + diff --git a/Sql2o/sql2o-api-Sql2o.java b/Sql2o/sql2o-api-Sql2o.java new file mode 100644 index 00000000..7069951f --- /dev/null +++ b/Sql2o/sql2o-api-Sql2o.java @@ -0,0 +1,35 @@ +----------------------------- +Sql2o | +----------------------------- + Connection beginTransaction() + Connection beginTransaction(ConnectionSource connectionSource) + Connection beginTransaction(ConnectionSource connectionSource, int isolationLevel) + Connection beginTransaction(int isolationLevel) + + ConnectionSource getConnectionSource() + void setConnectionSource(ConnectionSource connectionSource) + + + Map getDefaultColumnMappings() + void setDefaultColumnMappings(Map defaultColumnMappings) + + Quirks getQuirks() + + boolean isDefaultCaseSensitive() + void setDefaultCaseSensitive(boolean defaultCaseSensitive) + + Connection open() + Connection open(ConnectionSource connectionSource) + + void runInTransaction(StatementRunnable runnable) + void runInTransaction(StatementRunnable runnable, Object argument) + void runInTransaction(StatementRunnable runnable, Object argument, int isolationLevel) + + V runInTransaction(StatementRunnableWithResult runnableWithResult) + V runInTransaction(StatementRunnableWithResult runnableWithResult, Object argument) + V runInTransaction(StatementRunnableWithResult runnableWithResult, Object argument, int isolationLevel) + + void withConnection(StatementRunnable runnable) + void withConnection(StatementRunnable runnable, Object argument) + V withConnection(StatementRunnableWithResult runnable) + V withConnection(StatementRunnableWithResult runnable, Object argument) diff --git a/Sql2o/sql2o-spring.java b/Sql2o/sql2o-spring.java new file mode 100644 index 00000000..119b40f5 --- /dev/null +++ b/Sql2o/sql2o-spring.java @@ -0,0 +1,82 @@ +---------------------------- +整合spring - xml配置 | +---------------------------- + + + com.mysql.jdbc.Driver + jdbc:mysql://localhost:3306/testDB + user + pass + + + + + + + + + + + + + + + + +---------------------------- +整合spring - 代码配置 | +---------------------------- + // 实现 TransactionManagementConfigurer 接口 + @Configuration + @EnableTransactionManagement + public class DatabaseContext implements TransactionManagementConfigurer { + + // 注册数据源 + @Bean + public DataSource dataSource() { + final BasicDataSource dataSource = new BasicDataSource(); + dataSource.setDriverClassName("com.mysql.jdbc.Driver"); + dataSource.setUrl("jdbc:mysql://localhost:3306/testDB"); + dataSource.setUsername("user"); + dataSource.setPassword("pass"); + } + + // 覆写抽象方法,注册事务管理器 + @Bean + @Override + public PlatformTransactionManager annotationDrivenTransactionManager() { + return new DataSourceTransactionManager(this.dataSource()); + } + + // 注册sql2o + @Bean + public Sql2o sql2o() { + return new Sql2o(this.dataSource()); + } + } + +---------------------------- +整合spring - 注入使用 | +---------------------------- + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.stereotype.Repository; + + @Repository + public class UserDAO { + + @Autowired + private Sql2o sql2o; + + public User getById(Integer userId){ + + String sql = "SELECT * FROM `user` WHERE `id` = :id"; + + Connection connection = sql2o.open(); + + User user = connection.createQuery(sql) + .addParameter("id",userId) + .executeAndFetchFirst(User.class); + + return user; + } + } \ No newline at end of file diff --git "a/Sql2o/sql2o-\344\272\213\345\212\241.java" "b/Sql2o/sql2o-\344\272\213\345\212\241.java" new file mode 100644 index 00000000..aa298d14 --- /dev/null +++ "b/Sql2o/sql2o-\344\272\213\345\212\241.java" @@ -0,0 +1,41 @@ +------------------------- +事务 | +------------------------- + # 事务操作 + Connection connection = sql2o.beginTransaction(); + try{ + // TODO 使用该Connection完成多个事务操作 + + }catch (Exception e) { + if(connection != null) { + connection.rollback(); + } + }finally { + if(connection != null){ + connection.close(); + } + } + + * beginTransaction() 获取一个事务连接 + * 可以在获取事务连接的时候,设置事务的隔离级别 + beginTransaction(int isolationLevel) + java.sql.Connection + int TRANSACTION_NONE = 0; + int TRANSACTION_READ_UNCOMMITTED = 1; + int TRANSACTION_READ_COMMITTED = 2; (Sql2o默认) + int TRANSACTION_REPEATABLE_READ = 4; + int TRANSACTION_SERIALIZABLE = 8; + + * 也可以从指定的链接源获取到事务连接 + beginTransaction(ConnectionSource connectionSource) + beginTransaction(ConnectionSource connectionSource, int isolationLevel) + + public interface ConnectionSource { + java.sql.Connection getConnection() throws SQLException; + } + + * 一般用于,第三方的框架管理事务 + + + * 使用 rollback(); 回滚事务,也可以使用: rollback(boolean closeConnection) 来设置,是否在回滚时关闭连接 + diff --git "a/Sql2o/sql2o-\346\233\264\346\226\260\346\217\222\345\205\245.java" "b/Sql2o/sql2o-\346\233\264\346\226\260\346\217\222\345\205\245.java" new file mode 100644 index 00000000..2fbcbd3f --- /dev/null +++ "b/Sql2o/sql2o-\346\233\264\346\226\260\346\217\222\345\205\245.java" @@ -0,0 +1,114 @@ +----------------------- +insert/update | +----------------------- + # 插入记录 + try(Connection connection = sql2o.open()){ + Connection executeUpdate = connection.createQuery("INSERT INTO `user`(`id`,`name`,`create_date`) VALUES(NULL, :name, :createDate);") + .addParameter("name", "KevinBlandy") + .addParameter("createDate", LocalDateTime.now()) + .executeUpdate(); + + // 返回收到的影响行数 + int result = executeUpdate.getResult(); + // 返回自增长的key + Object key = executeUpdate.getKey(); + Integer integerKey = executeUpdate.getKey(Integer.class); + + Object[] keys = executeUpdate.getKeys(); + List longKeys = executeUpdate.getKeys(Long.class); + + System.out.println(result); + System.out.println(key); + } + + * 通过 addParameter(String name, final String value) 添加占位符参数 + * addParameter() 有 N 多重载方法,对应不同的数据类型 + + * 使用 executeUpdate() 执行插入/修改 + + * 通过 getResult(); 获取到受影响的函数 + * 通过 getKey(); 获取到自增长字段的值,也可以使用 getKey(Class clzz); 来转换为需要的数据类型 + + * 如果存在多个自增长字段,那么可以使用:Object[] getKeys() / List getKeys(Long.class); 来获取 + + + # 修改记录 + try(Connection connection = sql2o.open()){ + Connection executeUpdate = connection.createQuery("UPDATE `user` SET `name` = :name WHERE `id` = :id") + .addParameter("id",13) + .addParameter("name", "KevinBlandy1") + .executeUpdate(); + + // 返回收到的影响行数 + int result = executeUpdate.getResult(); + System.out.println(result); + } + + * 使用 executeUpdate() 执行插入/修改 + * 通过 getResult(); 获取到受影响的函数 + + # 绑定对象完成占位符的映射关系 + try(Connection connection = sql2o.open()){ + + // 创建对象 + User user = new User(); + user.setId(null); + user.setName("SpringBoot中文社区"); + user.setCreateDate(LocalDateTime.now()); + + Connection executeUpdate = connection.createQuery("INSERT INTO `user`(`id`, `name`, `create_date`) VALUES(:id, :name, :createDate)") + // 映射对象的属性到SQL中的占位符 + .bind(user) + .executeUpdate(); + + // 返回收到的影响行数 + int result = executeUpdate.getResult(); + + // 自增长的id + Integer id = executeUpdate.getKey(Integer.class); + + System.out.println("result = " + result + ", id = " + id); + } + + * :prop 是对象的属性 + + +----------------------- +批量处理 | +----------------------- + # 批量插入 + try(Connection connection = sql2o.open()){ + + Query query = connection.createQuery("INSERT INTO `user`(`id`, `name`, `create_Date`) VALUES(:id, :name, :createDate);"); + + for(int x = 0 ;x < 100 ; x++){ + + // 执行数据绑定 + query.addParameter("id",x); + query.addParameter("name","name_" + x); + query.addParameter("createDate",LocalDateTime.now()); + + // 添加到批处理 + query.addToBatch(); + } + + // 执行批量处理 + Connection executeBatch = query.executeBatch(); + + // 获取到批量处理的结果 + int[] result = executeBatch.getBatchResult(); + + System.out.println(Arrays.toString(result)); + } + + * addToBatch() 添加到批处理 + * executeBatch() 执行批处理 + * getBatchResult() 获取到批处理结果 + + + * 优点: + SQL语句预编译了 + 对于同一种类型的SQL语句,不用编写很多条 + + * 缺点: + 不能发送不同类型的SQL语句 \ No newline at end of file diff --git "a/Sql2o/sql2o-\346\243\200\347\264\242.java" "b/Sql2o/sql2o-\346\243\200\347\264\242.java" new file mode 100644 index 00000000..dff9c189 --- /dev/null +++ "b/Sql2o/sql2o-\346\243\200\347\264\242.java" @@ -0,0 +1,161 @@ +----------------------- +select | +----------------------- + # 基本的检索 + try(Connection connection = sql2o.open()){ + User user = connection.createQuery("SELECT * FROM `user`").executeAndFetchFirst(User.class); + } + + * executeAndFetchFirst(Class clazz) ,封装为单行结果集 + * 如果存在多行数据,那么只取第一行 + + # 带参数的检索 + try(Connection connection = sql2o.open()){ + List users = connection.createQuery("SELECT * FROM `user` WHERE `create_date` >= :start") + .addParameter("start",LocalDateTime.of(2019,4,12,7,21,53)) + .executeAndFetch(User.class); + + System.out.println(JSON.toJSONString(users)); + } + + * executeAndFetch(Class clazz),封装为多行多列结果集 + * 在SQL里面使用 ':name' 占位 + * 通过 addParameter() api设置填充参数 + + # 绑定对象属性来检索 + try(Connection connection = sql2o.open()){ + + User user = new User(); + user.setId(15); + + user = connection.createQuery("SELECT * FROM `user` WHERE `id` = :id") + .addColumnMapping("create_date","createDate") + .bind(user) + .executeAndFetchFirst(User.class); + + System.out.println(user); + } + + * 使用 bind(final Object pojo) 方法来帮到一个对象 + * :prop 就是对象的属性 + + # 统计检索 + try(Connection connection = sql2o.open()){ + List count = connection.createQuery("SELECT COUNT(1) FROM `user`") + .executeScalarList(Integer.class); + System.out.println(JSON.toJSONString(count)); + } + + * 通过 executeScalarList(Class clazz),来封装统计的结果集合 + + try(Connection connection = sql2o.open()){ + Long count = connection.createQuery("SELECT COUNT(1) FROM `user`") + .executeScalar(Long.class); + System.out.println(JSON.toJSONString(count)); + } + + * 通过 executeScalar(Class clazz),来封装统计的单个结果 + + # 使用Map封装多行多列结果集 + Table table = connection.createQuery("SELECT * FROM `user`") + .executeAndFetchTable(); + + // 获取到结果集List,List每个Map元素都是一行记录,每个Map元素的内容都是多列记录 + // Map的key是列名称,value是 sql 类型的数据对象 + List> map = table.asList(); + + // 每一行 + List rows = table.rows(); + + // 每一列 + List columns = table.columns(); + + // 检索的表名称 + String name = table.getName(); + + * Row 对象表示一行记录 + // 把一个行记录,转换为Map + Map rowMap = row.asMap(); + + // 根据列名读取数据为BigDecimal + BigDecimal salary = row.getBigDecimal("salary"); + + // 根据下标读取数据为Integer,从0开始 + Integer id = row.getInteger(1); + + // 根据列名读取数据为指定的数据对象类型 + // 会使用转换器:Converter + String string = row.getObject("",String.class); + + // 读取数据为Object类型 + Object result = row.getObject(1); + + * 还有很多: getXxxx(int index)/getXxxx(String name) 的重载方法,用于转换为不同的数据类型 + + * Column 表示一列记录 + // 列所在的index,从0开始 + Integer index = column.getIndex(); + // 列名 + String name = column.getName(); + + // 列数据类型,JDBCType 枚举实例的name() + String type = column.getType(); + + # 迭代检索 + * 对于数据记录量很大,一次性的全部读取到内存可能会造成不必要的麻烦 + * 可以采取这种迭代检索的方法,分批次的检索数据,分批次的处理 + + try(Connection connection = sql2o.open()){ + + // 一次性最多处理多条数据 + final int MAXSIZE = 1; + + // 结果集容器 + List users = new ArrayList<>(MAXSIZE); + + ResultSetIterable resultSetIterable = connection.createQuery("SELECT * FROM `user`") + .executeAndFetchLazy(User.class); + + Iterator iterator = resultSetIterable.iterator(); + + while (iterator.hasNext()){ + + User user = iterator.next(); + + if(users.size() == MAXSIZE){ + //已经到达最大数据量,执行业务处理 + users.stream().forEach(System.out::println); + + // 处理完毕后,清空容器 + users.clear(); + } + // 添加元素到容器 + users.add(user); + } + } + + * 使用 executeAndFetchLazy(Class clazz) 获取结果集迭代器 + + # 列名与对象属性映射 + * 如果结果集中,存在对象中不存在的属性名,那么会抛出异常 + org.sql2o.Sql2oException: Could not map xxxx to any property. + + try(Connection connection = sql2o.open()){ + User user = connection.createQuery("SELECT * FROM `user`") + .addColumnMapping("create_date","createDate") + .executeAndFetchFirst(User.class); + System.out.println(user); + } + + * 使用 addColumnMapping(String columnName, String propertyName) 来设置列名称与对象的属性名映射关系 + * 当然,也可以自己在SQL里面使用`AS` 别名的方式来处理属性映射 + + * 可以通过 Sql2o 对象设置全局的映射关系,而不用每次检索都设置 + Map colMaps = new HashMap(); + colMaps.put("DUE_DATE", "dueDate"); + colMaps.put("DESC", "description"); + colMaps.put("E_MAIL", "email"); + colMaps.put("SHORT_DESC", "shortDescription"); + + sql2o.setDefaultColumnMappings(colMaps); + diff --git "a/Sql2o/sql2o-\350\207\252\345\256\232\344\271\211\347\261\273\345\236\213\350\275\254\346\215\242\345\231\250.java" "b/Sql2o/sql2o-\350\207\252\345\256\232\344\271\211\347\261\273\345\236\213\350\275\254\346\215\242\345\231\250.java" new file mode 100644 index 00000000..c0283bad --- /dev/null +++ "b/Sql2o/sql2o-\350\207\252\345\256\232\344\271\211\347\261\273\345\236\213\350\275\254\346\215\242\345\231\250.java" @@ -0,0 +1,85 @@ +---------------------------- +自定义类型转换器 | +---------------------------- + # 实现转换器接口 Converter + T convert(Object val) throws ConverterException; + * 把 sql 类型转换为 实体类型 + + Object toDatabaseParam(T val); + * 把实体类型转换为 sql 类型 + + # 实现 ConvertersProvider 接口 + void fill(Map,Converter> mapToFill); + * 覆写该方法,通过参数,添加多个: Converter 的实现 + + # 在 src/main/resources 目录下创建文件 + META-INF/services/org.sql2o.converters.ConvertersProvider + + # 在该文件中写入 ConvertersProvider 接口的实现全路径,使用回车结尾 + + + +---------------------------- +demo | +---------------------------- + # 实现 Converter + import org.sql2o.converters.Converter; + + import java.sql.Timestamp; + import java.time.Instant; + import java.time.LocalDateTime; + import java.time.ZoneOffset; + + public class LocalDateTimeConverter implements Converter { + @Override + public LocalDateTime convert(final Object val) { + if(val != null ){ + if(val instanceof Timestamp){ + return LocalDateTime.ofInstant(Instant.ofEpochMilli(((Timestamp)val).getTime()), ZoneOffset.UTC); + } + } + return null; + } + + @Override + public Object toDatabaseParam(final LocalDateTime val) { + if(val != null){ + return new Timestamp(val.toInstant(ZoneOffset.UTC).toEpochMilli()); + } + return null; + } + } + + # 实现 ConvertersProvider + import org.sql2o.converters.Converter; + import org.sql2o.converters.ConvertersProvider; + + import java.time.LocalDateTime; + import java.util.Map; + + public class Sql2oConvertersProvider implements ConvertersProvider { + @Override + public void fill(Map, Converter> mapToFill) { + mapToFill.put(LocalDateTime.class, new LocalDateTimeConverter()); + } + } + + # 在 META-INF/services/org.sql2o.converters.ConvertersProvider 文件中添加实现类全路径(注意最后有空格) + io.javaweb.example.sql2o.converter.Sql2oConvertersProvider + + +---------------------------- +在程序中设置类型转换器 | +---------------------------- + + // 创建类型映射Map + Map converters = new HashMap<>(); + + // 添加类型和转换器的映射 + converters.put(LocalDateTime.class,new LocalDateTimeConverter()); + + // 实例化 NoQuirks + Quirks quirks = new NoQuirks(converters); + + // 实例化sql2o + Sql2o sql2o = new Sql2o(dataSource,quirks); \ No newline at end of file diff --git a/Sql2o/sql2o.java b/Sql2o/sql2o.java new file mode 100644 index 00000000..a4b67063 --- /dev/null +++ b/Sql2o/sql2o.java @@ -0,0 +1,27 @@ +------------------------- +SQL2O | +------------------------- + # Github + https://github.com/aaberg/sql2o + + + # Maven + + org.sql2o + sql2o + 1.6.0 + + + + # 构造函数 + Sql2o(String jndiLookup) + Sql2o(String url, String user, String pass) + Sql2o(String url, String user, String pass, Quirks quirks) + Sql2o(DataSource dataSource) + Sql2o(DataSource dataSource, Quirks quirks) + + jndiLookup + * 从jndi加载数据源 + + quirks + * 特殊的一些设置 \ No newline at end of file diff --git a/Sqlite/sqlite-java.java b/Sqlite/sqlite-java.java new file mode 100644 index 00000000..2325004b --- /dev/null +++ b/Sqlite/sqlite-java.java @@ -0,0 +1,20 @@ +------------------------ +java | +------------------------ + # jdbc驱动 + + + org.xerial + sqlite-jdbc + 3.23.1 + + + + # 连接代码 + Class.forName("org.sqlite.JDBC"); + Connection connection = DriverManager.getConnection("jdbc:sqlite:test.db"); + + * 如果test.bd不存在,会在项目的根目录下生成一个 test.db 文件 + * 可以使用classpath:test.db,或者文件目录来指定db 文件 + + diff --git "a/Sqlite/sqlite-\344\272\213\345\212\241.java" "b/Sqlite/sqlite-\344\272\213\345\212\241.java" new file mode 100644 index 00000000..3a29fff2 --- /dev/null +++ "b/Sqlite/sqlite-\344\272\213\345\212\241.java" @@ -0,0 +1,15 @@ +------------------------ +事务 | +------------------------ + # 开始事务 + BEGIN; + or + BEGIN TRANSACTION; + + # 提交事务 + COMMIT; + or + END TRANSACTION; + + # 回滚事务 + ROLLBACK; \ No newline at end of file diff --git "a/Sqlite/sqlite-\345\205\245\351\227\250.java" "b/Sqlite/sqlite-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..115cb508 --- /dev/null +++ "b/Sqlite/sqlite-\345\205\245\351\227\250.java" @@ -0,0 +1,27 @@ +------------------------ +sqlite | +------------------------ + # 嵌入式数据库 + # 官网 + https://www.sqlite.org/index.html + + +------------------------ +sqlite-window安装 | +------------------------ + # 下载文件,都解压到一个文件夹中 + sqlite-tools-win32-x86-3240000.zip + sqlite-dll-win64-x64-3240000.zip + + # 最终目录文件 + sqlite3_analyzer.exe + sqlite3.exe + sqldiff.exe + sqlite3.dll + + # 可以考虑添加目录到环境变量 + +------------------------ +sqlite-Linux安装 | +------------------------ + \ No newline at end of file diff --git "a/Sqlite/sqlite-\345\221\275\344\273\244.java" "b/Sqlite/sqlite-\345\221\275\344\273\244.java" new file mode 100644 index 00000000..e8ad9341 --- /dev/null +++ "b/Sqlite/sqlite-\345\221\275\344\273\244.java" @@ -0,0 +1,43 @@ +---------------------------- +sqlite常用命令 | +---------------------------- + .backup [db] [file] + * 备份 db 数据库(默认是 "main")到 file 文件 + + .open [file] + * 打开指定的文件 + + .databases + * 列出数据库的名称及其所依附的文件 + + .dump [table] + * 以 SQL 文本格式转储数据库 + * 如果指定了 table 表,则只转储table表(table支持模糊表达式) + + .exit + * 退出 SQLite 提示符 + + .header(s) ON|OFF + * 开启或关闭头部显 + + .import [file] [tables] + * 导入来自 file 文件的数据到 tables 表中 + + .show + * 查看sqlite默认的配置 + + .timer ON|OFF + * 开启或关闭 CPU 定时器 + + .mode [mode] + * 设置输出模式,[mode] 可以是下列之一 + csv 逗号分隔的值 + column 左对齐的列 + html HTML 的 代码 + insert TABLE 表的 SQL 插入(insert)语句 + line 每行一个值 + list 由 .separator 字符串分隔的值 + tabs 由 Tab 分隔的值 + tcl TCL 列表元素 + + diff --git "a/Sqlite/sqlite-\346\223\215\344\275\234\346\225\260\346\215\256\345\272\223.java" "b/Sqlite/sqlite-\346\223\215\344\275\234\346\225\260\346\215\256\345\272\223.java" new file mode 100644 index 00000000..9a07d7e1 --- /dev/null +++ "b/Sqlite/sqlite-\346\223\215\344\275\234\346\225\260\346\215\256\345\272\223.java" @@ -0,0 +1,34 @@ +--------------------- +创建DB | +--------------------- + sqlite3.exe [dbname] + * 新建一个数据库,并且生成一个文件(dbname.db) + +--------------------- +备份/还原DB | +--------------------- + sqlite3 [dbname] .dump > [filename] + * .dump 点命令来导出完整的数据库在一个文本文件中(sql) + + sqlite3 [dbname] < [filename] + * 把filename的sql还原到dbname + +--------------------- +附加据库 | +--------------------- + attach database 'xxx.db' as 'xxx'; + * 如果DB不存在,会创建新的 + + * 切换了数据库之后,所有的操作都会基于该数据了 + * 使用 .database 可以查看到切换过的数据库 + * 'main' 和 'temp' 被保留用于主数据库和存储临时表及其他临时数据对象的数据库 + * 这两个数据库名称可用于每个数据库连接,且'不应该被用于附加',否则将得到一个警告消息 + +--------------------- +分离数据库 | +--------------------- + detach database 'xxx'; + * 来把命名数据库从一个数据库连接分离和游离出来 + * 如果同一个数据库文件'已经被附加上多个别名',DETACH 命令将只断开给定名称的连接,'而其余的仍然有效' + * 您无法分离 main 或 temp 数据库 + diff --git "a/Sqlite/sqlite-\346\223\215\344\275\234\346\225\260\346\215\256\350\241\250.java" "b/Sqlite/sqlite-\346\223\215\344\275\234\346\225\260\346\215\256\350\241\250.java" new file mode 100644 index 00000000..79450801 --- /dev/null +++ "b/Sqlite/sqlite-\346\223\215\344\275\234\346\225\260\346\215\256\350\241\250.java" @@ -0,0 +1,25 @@ +------------------------ +操作表 | +------------------------ + # 创建 + CREATE TABLE user( + id INT PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + age INT NOT NULL, + address CHAR(50), + salary REAL, + birthday INTEGER, + create_date TEXT + ); + + * 插入记录:insert into `user`(`id`,`name`,`age`,`address`,`salary`,`birthday`,`create_date`) values(1,'KevinBlandy',24,'重庆',5115415.5,1529570646967,'2018-06-21 16:44:19'); + + # 查看表概要 + .schema [tables] + + + # 查看所有表 + .tables + + # 删除表 + drop table [tablename] diff --git "a/Sqlite/sqlite-\346\225\260\346\215\256\347\261\273\345\236\213.java" "b/Sqlite/sqlite-\346\225\260\346\215\256\347\261\273\345\236\213.java" new file mode 100644 index 00000000..d2cde679 --- /dev/null +++ "b/Sqlite/sqlite-\346\225\260\346\215\256\347\261\273\345\236\213.java" @@ -0,0 +1,37 @@ +------------------------ +数据类型 | +------------------------ + NULL 值是一个 NULL 值 + INTEGER 值是一个带符号的整数,根据值的大小存储在 1,2,3,4,6 或 8 字节中 + REAL 值是一个浮点值,存储为 8 字节的 IEEE 浮点数字 + TEXT 值是一个文本字符串,使用数据库编码(UTF-8,UTF-16BE 或 UTF-16LE)存储 + BLOB 值是一个 blob 数据,完全根据它的输入存储 + +------------------------ +亲和(Affinity)类型 | +------------------------ + # SQLite支持列的亲和类型概念,任何列仍然可以存储任何类型的数据 + # 当数据插入时,该字段的数据将会优先采用亲缘类型作为该值的存储方式 + # SQLite目前的版本支持以下五种亲缘类型 + TEXT 数值型数据在被插入之前,需要先被转换为文本格式,之后再插入到目标字段中。 + NUMERIC 当文本数据被插入到亲缘性为NUMERIC的字段中时,如果转换操作不会导致数据信息丢失以及完全可逆,那么SQLite就会将该文本数据转换为INTEGER或REAL类型的数据,如果转换失败,SQLite仍会以TEXT方式存储该数据。对于NULL或BLOB类型的新数据,SQLite将不做任何转换,直接以NULL或BLOB的方式存储该数据。需要额外说明的是,对于浮点格式的常量文本,如"30000.0",如果该值可以转换为INTEGER同时又不会丢失数值信息,那么SQLite就会将其转换为INTEGER的存储方式。 + INTEGER 对于亲缘类型为INTEGER的字段,其规则等同于NUMERIC,唯一差别是在执行CAST表达式时。 + REAL 其规则基本等同于NUMERIC,唯一的差别是不会将"30000.0"这样的文本数据转换为INTEGER存储方式。 + NONE 不做任何的转换,直接以该数据所属的数据类型进行存储。   + +------------------------ +Boolean 数据类型 | +------------------------ + # SQLite 没有单独的 Boolean 存储类 + # 尔值被存储为整数 0(false)和 1(true) + + +------------------------ +Date 与 Time 数据类型 | +------------------------ + # SQLite 没有一个单独的用于存储日期和/或时间的存储类 + # 但 SQLite 能够把日期和时间存储为 TEXT、REAL 或 INTEGER 值 + + TEXT 格式为 "YYYY-MM-DD HH:MM:SS.SSS" 的日期。 + REAL 从公元前 4714 年 11 月 24 日格林尼治时间的正午开始算起的天数。 + INTEGER 从 1970-01-01 00:00:00 UTC 算起的秒数。 \ No newline at end of file diff --git a/V2ray/install.sh b/V2ray/install.sh new file mode 100644 index 00000000..52de27e2 --- /dev/null +++ b/V2ray/install.sh @@ -0,0 +1,1082 @@ +#!/bin/bash + +red='\e[91m' +green='\e[92m' +yellow='\e[93m' +magenta='\e[95m' +cyan='\e[96m' +none='\e[0m' + +# Root +[[ $(id -u) != 0 ]] && echo -e "\n 哎呀……请使用 ${red}root ${none}用户运行 ${yellow}~(^_^) ${none}\n" && exit 1 + +cmd="apt-get" + +sys_bit=$(uname -m) + +if [[ $sys_bit == "i386" || $sys_bit == "i686" ]]; then + v2ray_bit="32" +elif [[ $sys_bit == "x86_64" ]]; then + v2ray_bit="64" +else + echo -e " + 哈哈……这个 ${red}辣鸡脚本${none} 不支持你的系统。 ${yellow}(-_-) ${none} + + 备注: 仅支持 Ubuntu 16+ / Debian 8+ / CentOS 7+ 系统 + " && exit 1 +fi + +# 笨笨的检测方法 +if [[ -f /usr/bin/apt-get || -f /usr/bin/yum ]] && [[ -f /bin/systemctl ]]; then + + if [[ -f /usr/bin/yum ]]; then + + cmd="yum" + + fi + +else + + echo -e " + 哈哈……这个 ${red}辣鸡脚本${none} 不支持你的系统。 ${yellow}(-_-) ${none} + + 备注: 仅支持 Ubuntu 16+ / Debian 8+ / CentOS 7+ 系统 + " && exit 1 + +fi + +uuid=$(cat /proc/sys/kernel/random/uuid) +old_id="e55c8d17-2cf3-b21a-bcf1-eeacb011ed79" +v2ray_server_config="/etc/v2ray/config.json" +v2ray_client_config="/etc/v2ray/233blog_v2ray_config.json" +backup="/etc/v2ray/233blog_v2ray_backup.conf" +_v2ray_sh="/usr/local/sbin/v2ray" +systemd=true +# _test=true + +transport=( + TCP + TCP_HTTP + WebSocket + "WebSocket + TLS" + HTTP/2 + mKCP + mKCP_utp + mKCP_srtp + mKCP_wechat-video + mKCP_dtls + mKCP_wireguard + QUIC + QUIC_utp + QUIC_srtp + QUIC_wechat-video + QUIC_dtls + QUIC_wireguard + TCP_dynamicPort + TCP_HTTP_dynamicPort + WebSocket_dynamicPort + mKCP_dynamicPort + mKCP_utp_dynamicPort + mKCP_srtp_dynamicPort + mKCP_wechat-video_dynamicPort + mKCP_dtls_dynamicPort + mKCP_wireguard_dynamicPort + QUIC_dynamicPort + QUIC_utp_dynamicPort + QUIC_srtp_dynamicPort + QUIC_wechat-video_dynamicPort + QUIC_dtls_dynamicPort + QUIC_wireguard_dynamicPort +) + +ciphers=( + aes-128-cfb + aes-256-cfb + chacha20 + chacha20-ietf + aes-128-gcm + aes-256-gcm + chacha20-ietf-poly1305 +) + +_load() { + local _dir="/etc/v2ray/233boy/v2ray/src/" + . "${_dir}$@" +} + +v2ray_config() { + # clear + echo + while :; do + echo -e "请选择 "$yellow"V2Ray"$none" 传输协议 [${magenta}1-${#transport[*]}$none]" + echo + for ((i = 1; i <= ${#transport[*]}; i++)); do + Stream="${transport[$i - 1]}" + if [[ "$i" -le 9 ]]; then + # echo + echo -e "$yellow $i. $none${Stream}" + else + # echo + echo -e "$yellow $i. $none${Stream}" + fi + done + echo + echo "备注1: 含有 [dynamicPort] 的即启用动态端口.." + echo "备注2: [utp | srtp | wechat-video | dtls | wireguard] 分别伪装成 [BT下载 | 视频通话 | 微信视频通话 | DTLS 1.2 数据包 | WireGuard 数据包]" + echo + read -p "$(echo -e "(默认协议: ${cyan}TCP$none)"):" v2ray_transport + [ -z "$v2ray_transport" ] && v2ray_transport=1 + case $v2ray_transport in + [1-9] | [1-2][0-9] | 3[0-2]) + echo + echo + echo -e "$yellow V2Ray 传输协议 = $cyan${transport[$v2ray_transport - 1]}$none" + echo "----------------------------------------------------------------" + echo + break + ;; + *) + error + ;; + esac + done + v2ray_port_config +} +v2ray_port_config() { + case $v2ray_transport in + 4 | 5) + tls_config + ;; + *) + local random=$(shuf -i20001-65535 -n1) + while :; do + echo -e "请输入 "$yellow"V2Ray"$none" 端口 ["$magenta"1-65535"$none"]" + read -p "$(echo -e "(默认端口: ${cyan}${random}$none):")" v2ray_port + [ -z "$v2ray_port" ] && v2ray_port=$random + case $v2ray_port in + [1-9] | [1-9][0-9] | [1-9][0-9][0-9] | [1-9][0-9][0-9][0-9] | [1-5][0-9][0-9][0-9][0-9] | 6[0-4][0-9][0-9][0-9] | 65[0-4][0-9][0-9] | 655[0-3][0-5]) + echo + echo + echo -e "$yellow V2Ray 端口 = $cyan$v2ray_port$none" + echo "----------------------------------------------------------------" + echo + break + ;; + *) + error + ;; + esac + done + if [[ $v2ray_transport -ge 18 ]]; then + v2ray_dynamic_port_start + fi + ;; + esac +} + +v2ray_dynamic_port_start() { + + while :; do + echo -e "请输入 "$yellow"V2Ray 动态端口开始 "$none"范围 ["$magenta"1-65535"$none"]" + read -p "$(echo -e "(默认开始端口: ${cyan}10000$none):")" v2ray_dynamic_port_start_input + [ -z $v2ray_dynamic_port_start_input ] && v2ray_dynamic_port_start_input=10000 + case $v2ray_dynamic_port_start_input in + $v2ray_port) + echo + echo " 不能和 V2Ray 端口一毛一样...." + echo + echo -e " 当前 V2Ray 端口:${cyan}$v2ray_port${none}" + error + ;; + [1-9] | [1-9][0-9] | [1-9][0-9][0-9] | [1-9][0-9][0-9][0-9] | [1-5][0-9][0-9][0-9][0-9] | 6[0-4][0-9][0-9][0-9] | 65[0-4][0-9][0-9] | 655[0-3][0-5]) + echo + echo + echo -e "$yellow V2Ray 动态端口开始 = $cyan$v2ray_dynamic_port_start_input$none" + echo "----------------------------------------------------------------" + echo + break + ;; + *) + error + ;; + esac + + done + + if [[ $v2ray_dynamic_port_start_input -lt $v2ray_port ]]; then + lt_v2ray_port=true + fi + + v2ray_dynamic_port_end +} +v2ray_dynamic_port_end() { + + while :; do + echo -e "请输入 "$yellow"V2Ray 动态端口结束 "$none"范围 ["$magenta"1-65535"$none"]" + read -p "$(echo -e "(默认结束端口: ${cyan}20000$none):")" v2ray_dynamic_port_end_input + [ -z $v2ray_dynamic_port_end_input ] && v2ray_dynamic_port_end_input=20000 + case $v2ray_dynamic_port_end_input in + [1-9] | [1-9][0-9] | [1-9][0-9][0-9] | [1-9][0-9][0-9][0-9] | [1-5][0-9][0-9][0-9][0-9] | 6[0-4][0-9][0-9][0-9] | 65[0-4][0-9][0-9] | 655[0-3][0-5]) + + if [[ $v2ray_dynamic_port_end_input -le $v2ray_dynamic_port_start_input ]]; then + echo + echo " 不能小于或等于 V2Ray 动态端口开始范围" + echo + echo -e " 当前 V2Ray 动态端口开始:${cyan}$v2ray_dynamic_port_start_input${none}" + error + elif [ $lt_v2ray_port ] && [[ ${v2ray_dynamic_port_end_input} -ge $v2ray_port ]]; then + echo + echo " V2Ray 动态端口结束范围 不能包括 V2Ray 端口..." + echo + echo -e " 当前 V2Ray 端口:${cyan}$v2ray_port${none}" + error + else + echo + echo + echo -e "$yellow V2Ray 动态端口结束 = $cyan$v2ray_dynamic_port_end_input$none" + echo "----------------------------------------------------------------" + echo + break + fi + ;; + *) + error + ;; + esac + + done + +} + +tls_config() { + + echo + local random=$(shuf -i20001-65535 -n1) + while :; do + echo -e "请输入 "$yellow"V2Ray"$none" 端口 ["$magenta"1-65535"$none"],不能选择 "$magenta"80"$none" 或 "$magenta"443"$none" 端口" + read -p "$(echo -e "(默认端口: ${cyan}${random}$none):")" v2ray_port + [ -z "$v2ray_port" ] && v2ray_port=$random + case $v2ray_port in + 80) + echo + echo " ...都说了不能选择 80 端口了咯....." + error + ;; + 443) + echo + echo " ..都说了不能选择 443 端口了咯....." + error + ;; + [1-9] | [1-9][0-9] | [1-9][0-9][0-9] | [1-9][0-9][0-9][0-9] | [1-5][0-9][0-9][0-9][0-9] | 6[0-4][0-9][0-9][0-9] | 65[0-4][0-9][0-9] | 655[0-3][0-5]) + echo + echo + echo -e "$yellow V2Ray 端口 = $cyan$v2ray_port$none" + echo "----------------------------------------------------------------" + echo + break + ;; + *) + error + ;; + esac + done + + while :; do + echo + echo -e "请输入一个 $magenta正确的域名$none,一定一定一定要正确,不!能!出!错!" + read -p "(例如:233blog.com): " domain + [ -z "$domain" ] && error && continue + echo + echo + echo -e "$yellow 你的域名 = $cyan$domain$none" + echo "----------------------------------------------------------------" + break + done + get_ip + echo + echo + echo -e "$yellow 请将 $magenta$domain$none $yellow解析到: $cyan$ip$none" + echo + echo -e "$yellow 请将 $magenta$domain$none $yellow解析到: $cyan$ip$none" + echo + echo -e "$yellow 请将 $magenta$domain$none $yellow解析到: $cyan$ip$none" + echo "----------------------------------------------------------------" + echo + + while :; do + + read -p "$(echo -e "(是否已经正确解析: [${magenta}Y$none]):") " record + if [[ -z "$record" ]]; then + error + else + if [[ "$record" == [Yy] ]]; then + domain_check + echo + echo + echo -e "$yellow 域名解析 = ${cyan}我确定已经有解析了$none" + echo "----------------------------------------------------------------" + echo + break + else + error + fi + fi + + done + + if [[ $v2ray_transport -ne 5 ]]; then + auto_tls_config + else + caddy=true + install_caddy_info="打开" + fi + + if [[ $caddy ]]; then + path_config_ask + fi +} +auto_tls_config() { + echo -e " + + 安装 Caddy 来实现 自动配置 TLS + + 如果你已经安装 Nginx 或 Caddy + + $yellow并且..自己能搞定配置 TLS$none + + 那么就不需要 打开自动配置 TLS + " + echo "----------------------------------------------------------------" + echo + + while :; do + + read -p "$(echo -e "(是否自动配置 TLS: [${magenta}Y/N$none]):") " auto_install_caddy + if [[ -z "$auto_install_caddy" ]]; then + error + else + if [[ "$auto_install_caddy" == [Yy] ]]; then + caddy=true + install_caddy_info="打开" + echo + echo + echo -e "$yellow 自动配置 TLS = $cyan$install_caddy_info$none" + echo "----------------------------------------------------------------" + echo + break + elif [[ "$auto_install_caddy" == [Nn] ]]; then + install_caddy_info="关闭" + echo + echo + echo -e "$yellow 自动配置 TLS = $cyan$install_caddy_info$none" + echo "----------------------------------------------------------------" + echo + break + else + error + fi + fi + + done +} +path_config_ask() { + echo + while :; do + echo -e "是否开启 网站伪装 和 路径分流 [${magenta}Y/N$none]" + read -p "$(echo -e "(默认: [${cyan}N$none]):")" path_ask + [[ -z $path_ask ]] && path_ask="n" + + case $path_ask in + Y | y) + path_config + break + ;; + N | n) + echo + echo + echo -e "$yellow 网站伪装 和 路径分流 = $cyan不想配置$none" + echo "----------------------------------------------------------------" + echo + break + ;; + *) + error + ;; + esac + done +} +path_config() { + echo + while :; do + echo -e "请输入想要 ${magenta}用来分流的路径$none , 例如 /233blog , 那么只需要输入 233blog 即可" + read -p "$(echo -e "(默认: [${cyan}233blog$none]):")" path + [[ -z $path ]] && path="233blog" + + case $path in + *[/$]*) + echo + echo -e " 由于这个脚本太辣鸡了..所以分流的路径不能包含$red / $none或$red $ $none这两个符号.... " + echo + error + ;; + *) + echo + echo + echo -e "$yellow 分流的路径 = ${cyan}/${path}$none" + echo "----------------------------------------------------------------" + echo + break + ;; + esac + done + is_path=true + proxy_site_config +} +proxy_site_config() { + echo + while :; do + echo -e "请输入 ${magenta}一个正确的$none ${cyan}网址$none 用来作为 ${cyan}网站的伪装$none , 例如 https://liyafly.com" + echo -e "举例...你当前的域名是 $green$domain$none , 伪装的网址的是 https://liyafly.com" + echo -e "然后打开你的域名时候...显示出来的内容就是来自 https://liyafly.com 的内容" + echo -e "其实就是一个反代...明白就好..." + echo -e "如果不能伪装成功...可以使用 v2ray config 修改伪装的网址" + read -p "$(echo -e "(默认: [${cyan}https://liyafly.com$none]):")" proxy_site + [[ -z $proxy_site ]] && proxy_site="https://liyafly.com" + + case $proxy_site in + *[#$]*) + echo + echo -e " 由于这个脚本太辣鸡了..所以伪装的网址不能包含$red # $none或$red $ $none这两个符号.... " + echo + error + ;; + *) + echo + echo + echo -e "$yellow 伪装的网址 = ${cyan}${proxy_site}$none" + echo "----------------------------------------------------------------" + echo + break + ;; + esac + done +} + +blocked_hosts() { + echo + while :; do + echo -e "是否开启广告拦截(会影响性能) [${magenta}Y/N$none]" + read -p "$(echo -e "(默认 [${cyan}N$none]):")" blocked_ad + [[ -z $blocked_ad ]] && blocked_ad="n" + + case $blocked_ad in + Y | y) + blocked_ad_info="开启" + ban_ad=true + echo + echo + echo -e "$yellow 广告拦截 = $cyan开启$none" + echo "----------------------------------------------------------------" + echo + break + ;; + N | n) + blocked_ad_info="关闭" + echo + echo + echo -e "$yellow 广告拦截 = $cyan关闭$none" + echo "----------------------------------------------------------------" + echo + break + ;; + *) + error + ;; + esac + done +} +shadowsocks_config() { + + echo + + while :; do + echo -e "是否配置 ${yellow}Shadowsocks${none} [${magenta}Y/N$none]" + read -p "$(echo -e "(默认 [${cyan}N$none]):") " install_shadowsocks + [[ -z "$install_shadowsocks" ]] && install_shadowsocks="n" + if [[ "$install_shadowsocks" == [Yy] ]]; then + echo + shadowsocks=true + shadowsocks_port_config + break + elif [[ "$install_shadowsocks" == [Nn] ]]; then + break + else + error + fi + + done + +} + +shadowsocks_port_config() { + local random=$(shuf -i20001-65535 -n1) + while :; do + echo -e "请输入 "$yellow"Shadowsocks"$none" 端口 ["$magenta"1-65535"$none"],不能和 "$yellow"V2Ray"$none" 端口相同" + read -p "$(echo -e "(默认端口: ${cyan}${random}$none):") " ssport + [ -z "$ssport" ] && ssport=$random + case $ssport in + $v2ray_port) + echo + echo " 不能和 V2Ray 端口一毛一样...." + error + ;; + [1-9] | [1-9][0-9] | [1-9][0-9][0-9] | [1-9][0-9][0-9][0-9] | [1-5][0-9][0-9][0-9][0-9] | 6[0-4][0-9][0-9][0-9] | 65[0-4][0-9][0-9] | 655[0-3][0-5]) + if [[ $v2ray_transport == [45] ]]; then + local tls=ture + fi + if [[ $tls && $ssport == "80" ]] || [[ $tls && $ssport == "443" ]]; then + echo + echo -e "由于你已选择了 "$green"WebSocket + TLS $none或$green HTTP/2"$none" 传输协议." + echo + echo -e "所以不能选择 "$magenta"80"$none" 或 "$magenta"443"$none" 端口" + error + elif [[ $v2ray_dynamic_port_start_input == $ssport || $v2ray_dynamic_port_end_input == $ssport ]]; then + local multi_port="${v2ray_dynamic_port_start_input} - ${v2ray_dynamic_port_end_input}" + echo + echo " 抱歉,此端口和 V2Ray 动态端口 冲突,当前 V2Ray 动态端口范围为:$multi_port" + error + elif [[ $v2ray_dynamic_port_start_input -lt $ssport && $ssport -le $v2ray_dynamic_port_end_input ]]; then + local multi_port="${v2ray_dynamic_port_start_input} - ${v2ray_dynamic_port_end_input}" + echo + echo " 抱歉,此端口和 V2Ray 动态端口 冲突,当前 V2Ray 动态端口范围为:$multi_port" + error + else + echo + echo + echo -e "$yellow Shadowsocks 端口 = $cyan$ssport$none" + echo "----------------------------------------------------------------" + echo + break + fi + ;; + *) + error + ;; + esac + + done + + shadowsocks_password_config +} +shadowsocks_password_config() { + + while :; do + echo -e "请输入 "$yellow"Shadowsocks"$none" 密码" + read -p "$(echo -e "(默认密码: ${cyan}233blog.com$none)"): " sspass + [ -z "$sspass" ] && sspass="233blog.com" + case $sspass in + *[/$]*) + echo + echo -e " 由于这个脚本太辣鸡了..所以密码不能包含$red / $none或$red $ $none这两个符号.... " + echo + error + ;; + *) + echo + echo + echo -e "$yellow Shadowsocks 密码 = $cyan$sspass$none" + echo "----------------------------------------------------------------" + echo + break + ;; + esac + + done + + shadowsocks_ciphers_config +} +shadowsocks_ciphers_config() { + + while :; do + echo -e "请选择 "$yellow"Shadowsocks"$none" 加密协议 [${magenta}1-${#ciphers[*]}$none]" + for ((i = 1; i <= ${#ciphers[*]}; i++)); do + ciphers_show="${ciphers[$i - 1]}" + echo + echo -e "$yellow $i. $none${ciphers_show}" + done + echo + read -p "$(echo -e "(默认加密协议: ${cyan}${ciphers[6]}$none)"):" ssciphers_opt + [ -z "$ssciphers_opt" ] && ssciphers_opt=7 + case $ssciphers_opt in + [1-7]) + ssciphers=${ciphers[$ssciphers_opt - 1]} + echo + echo + echo -e "$yellow Shadowsocks 加密协议 = $cyan${ssciphers}$none" + echo "----------------------------------------------------------------" + echo + break + ;; + *) + error + ;; + esac + + done + pause +} + +install_info() { + clear + echo + echo " ....准备安装了咯..看看有毛有配置正确了..." + echo + echo "---------- 安装信息 -------------" + echo + echo -e "$yellow V2Ray 传输协议 = $cyan${transport[$v2ray_transport - 1]}$none" + + if [[ $v2ray_transport == [45] ]]; then + echo + echo -e "$yellow V2Ray 端口 = $cyan$v2ray_port$none" + echo + echo -e "$yellow 你的域名 = $cyan$domain$none" + echo + echo -e "$yellow 域名解析 = ${cyan}我确定已经有解析了$none" + echo + echo -e "$yellow 自动配置 TLS = $cyan$install_caddy_info$none" + + if [[ $ban_ad ]]; then + echo + echo -e "$yellow 广告拦截 = $cyan$blocked_ad_info$none" + fi + if [[ $is_path ]]; then + echo + echo -e "$yellow 路径分流 = ${cyan}/${path}$none" + fi + elif [[ $v2ray_transport -ge 18 ]]; then + echo + echo -e "$yellow V2Ray 端口 = $cyan$v2ray_port$none" + echo + echo -e "$yellow V2Ray 动态端口范围 = $cyan${v2ray_dynamic_port_start_input} - ${v2ray_dynamic_port_end_input}$none" + + if [[ $ban_ad ]]; then + echo + echo -e "$yellow 广告拦截 = $cyan$blocked_ad_info$none" + fi + else + echo + echo -e "$yellow V2Ray 端口 = $cyan$v2ray_port$none" + + if [[ $ban_ad ]]; then + echo + echo -e "$yellow 广告拦截 = $cyan$blocked_ad_info$none" + fi + fi + if [ $shadowsocks ]; then + echo + echo -e "$yellow Shadowsocks 端口 = $cyan$ssport$none" + echo + echo -e "$yellow Shadowsocks 密码 = $cyan$sspass$none" + echo + echo -e "$yellow Shadowsocks 加密协议 = $cyan${ssciphers}$none" + else + echo + echo -e "$yellow 是否配置 Shadowsocks = ${cyan}未配置${none}" + fi + echo + echo "---------- END -------------" + echo + pause + echo +} + +domain_check() { + # if [[ $cmd == "yum" ]]; then + # yum install bind-utils -y + # else + # $cmd install dnsutils -y + # fi + # test_domain=$(dig $domain +short) + test_domain=$(ping $domain -c 1 | grep -oE -m1 "([0-9]{1,3}\.){3}[0-9]{1,3}") + if [[ $test_domain != $ip ]]; then + echo + echo -e "$red 检测域名解析错误....$none" + echo + echo -e " 你的域名: $yellow$domain$none 未解析到: $cyan$ip$none" + echo + echo -e " 你的域名当前解析到: $cyan$test_domain$none" + echo + echo "备注...如果你的域名是使用 Cloudflare 解析的话..在 Status 那里点一下那图标..让它变灰" + echo + exit 1 + fi +} + +install_caddy() { + # download caddy file then install + _load download-caddy.sh + _download_caddy_file + _install_caddy_service + caddy_config + +} +caddy_config() { + # local email=$(shuf -i1-10000000000 -n1) + _load caddy-config.sh + + # systemctl restart caddy + do_service restart caddy +} + +install_v2ray() { + $cmd update -y + if [[ $cmd == "apt-get" ]]; then + $cmd install -y lrzsz git zip unzip curl wget qrencode libcap2-bin + else + # $cmd install -y lrzsz git zip unzip curl wget qrencode libcap iptables-services + $cmd install -y lrzsz git zip unzip curl wget qrencode libcap + fi + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + [ -d /etc/v2ray ] && rm -rf /etc/v2ray + date -s "$(curl -sI g.cn | grep Date | cut -d' ' -f3-6)Z" + + if [[ $local_install ]]; then + if [[ ! -d $(pwd)/config ]]; then + echo + echo -e "$red 哎呀呀...安装失败了咯...$none" + echo + echo -e " 请确保你有完整的上传 v2ray6.com 的 V2Ray 一键安装脚本 & 管理脚本到当前 ${green}$(pwd) $none目录下" + echo + exit 1 + fi + mkdir -p /etc/v2ray/233boy/v2ray + cp -rf $(pwd)/* /etc/v2ray/233boy/v2ray + else + pushd /tmp + git clone https://github.com/233boy/v2ray -b "$_gitbranch" /etc/v2ray/233boy/v2ray + popd + + fi + + if [[ ! -d /etc/v2ray/233boy/v2ray ]]; then + echo + echo -e "$red 哎呀呀...克隆脚本仓库出错了...$none" + echo + echo -e " 温馨提示..... 请尝试自行安装 Git: ${green}$cmd install -y git $none 之后再安装此脚本" + echo + exit 1 + fi + + # download v2ray file then install + _load download-v2ray.sh + _download_v2ray_file + _install_v2ray_service + _mkdir_dir +} + +open_port() { + if [[ $cmd == "apt-get" ]]; then + if [[ $1 != "multiport" ]]; then + + iptables -I INPUT -m state --state NEW -m tcp -p tcp --dport $1 -j ACCEPT + iptables -I INPUT -m state --state NEW -m udp -p udp --dport $1 -j ACCEPT + ip6tables -I INPUT -m state --state NEW -m tcp -p tcp --dport $1 -j ACCEPT + ip6tables -I INPUT -m state --state NEW -m udp -p udp --dport $1 -j ACCEPT + + # firewall-cmd --permanent --zone=public --add-port=$1/tcp + # firewall-cmd --permanent --zone=public --add-port=$1/udp + # firewall-cmd --reload + + else + + local multiport="${v2ray_dynamic_port_start_input}:${v2ray_dynamic_port_end_input}" + iptables -I INPUT -p tcp --match multiport --dports $multiport -j ACCEPT + iptables -I INPUT -p udp --match multiport --dports $multiport -j ACCEPT + ip6tables -I INPUT -p tcp --match multiport --dports $multiport -j ACCEPT + ip6tables -I INPUT -p udp --match multiport --dports $multiport -j ACCEPT + + # local multi_port="${v2ray_dynamic_port_start_input}-${v2ray_dynamic_port_end_input}" + # firewall-cmd --permanent --zone=public --add-port=$multi_port/tcp + # firewall-cmd --permanent --zone=public --add-port=$multi_port/udp + # firewall-cmd --reload + + fi + iptables-save >/etc/iptables.rules.v4 + ip6tables-save >/etc/iptables.rules.v6 + # else + # service iptables save >/dev/null 2>&1 + # service ip6tables save >/dev/null 2>&1 + fi +} +del_port() { + if [[ $cmd == "apt-get" ]]; then + if [[ $1 != "multiport" ]]; then + # if [[ $cmd == "apt-get" ]]; then + iptables -D INPUT -m state --state NEW -m tcp -p tcp --dport $1 -j ACCEPT + iptables -D INPUT -m state --state NEW -m udp -p udp --dport $1 -j ACCEPT + ip6tables -D INPUT -m state --state NEW -m tcp -p tcp --dport $1 -j ACCEPT + ip6tables -D INPUT -m state --state NEW -m udp -p udp --dport $1 -j ACCEPT + # else + # firewall-cmd --permanent --zone=public --remove-port=$1/tcp + # firewall-cmd --permanent --zone=public --remove-port=$1/udp + # fi + else + # if [[ $cmd == "apt-get" ]]; then + local ports="${v2ray_dynamicPort_start}:${v2ray_dynamicPort_end}" + iptables -D INPUT -p tcp --match multiport --dports $ports -j ACCEPT + iptables -D INPUT -p udp --match multiport --dports $ports -j ACCEPT + ip6tables -D INPUT -p tcp --match multiport --dports $ports -j ACCEPT + ip6tables -D INPUT -p udp --match multiport --dports $ports -j ACCEPT + # else + # local ports="${v2ray_dynamicPort_start}-${v2ray_dynamicPort_end}" + # firewall-cmd --permanent --zone=public --remove-port=$ports/tcp + # firewall-cmd --permanent --zone=public --remove-port=$ports/udp + # fi + fi + iptables-save >/etc/iptables.rules.v4 + ip6tables-save >/etc/iptables.rules.v6 + # else + # service iptables save >/dev/null 2>&1 + # service ip6tables save >/dev/null 2>&1 + fi + +} + +config() { + cp -f /etc/v2ray/233boy/v2ray/config/backup.conf $backup + cp -f /etc/v2ray/233boy/v2ray/v2ray.sh $_v2ray_sh + chmod +x $_v2ray_sh + + v2ray_id=$uuid + alterId=233 + ban_bt=true + if [[ $v2ray_transport -ge 18 ]]; then + v2ray_dynamicPort_start=${v2ray_dynamic_port_start_input} + v2ray_dynamicPort_end=${v2ray_dynamic_port_end_input} + fi + _load config.sh + + if [[ $cmd == "apt-get" ]]; then + cat >/etc/network/if-pre-up.d/iptables <<-EOF +#!/bin/sh +/sbin/iptables-restore < /etc/iptables.rules.v4 +/sbin/ip6tables-restore < /etc/iptables.rules.v6 + EOF + chmod +x /etc/network/if-pre-up.d/iptables + # else + # [ $(pgrep "firewall") ] && systemctl stop firewalld + # systemctl mask firewalld + # systemctl disable firewalld + # systemctl enable iptables + # systemctl enable ip6tables + # systemctl start iptables + # systemctl start ip6tables + fi + + [[ $shadowsocks ]] && open_port $ssport + if [[ $v2ray_transport == [45] ]]; then + open_port "80" + open_port "443" + open_port $v2ray_port + elif [[ $v2ray_transport -ge 18 ]]; then + open_port $v2ray_port + open_port "multiport" + else + open_port $v2ray_port + fi + # systemctl restart v2ray + do_service restart v2ray + backup_config + +} + +backup_config() { + sed -i "18s/=1/=$v2ray_transport/; 21s/=2333/=$v2ray_port/; 24s/=$old_id/=$uuid/" $backup + if [[ $v2ray_transport -ge 18 ]]; then + sed -i "30s/=10000/=$v2ray_dynamic_port_start_input/; 33s/=20000/=$v2ray_dynamic_port_end_input/" $backup + fi + if [[ $shadowsocks ]]; then + sed -i "42s/=/=true/; 45s/=6666/=$ssport/; 48s/=233blog.com/=$sspass/; 51s/=chacha20-ietf/=$ssciphers/" $backup + fi + [[ $v2ray_transport == [45] ]] && sed -i "36s/=233blog.com/=$domain/" $backup + [[ $caddy ]] && sed -i "39s/=/=true/" $backup + [[ $ban_ad ]] && sed -i "54s/=/=true/" $backup + if [[ $is_path ]]; then + sed -i "57s/=/=true/; 60s/=233blog/=$path/" $backup + sed -i "63s#=https://liyafly.com#=$proxy_site#" $backup + fi +} + +try_enable_bbr() { + if [[ $(uname -r | cut -b 1) -eq 4 ]]; then + case $(uname -r | cut -b 3-4) in + 9. | [1-9][0-9]) + sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf + sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf + echo "net.ipv4.tcp_congestion_control = bbr" >>/etc/sysctl.conf + echo "net.core.default_qdisc = fq" >>/etc/sysctl.conf + sysctl -p >/dev/null 2>&1 + ;; + esac + fi +} + +get_ip() { + ip=$(curl -s https://ipinfo.io/ip) + [[ -z $ip ]] && ip=$(curl -s https://api.ip.sb/ip) + [[ -z $ip ]] && ip=$(curl -s https://api.ipify.org) + [[ -z $ip ]] && ip=$(curl -s https://ip.seeip.org) + [[ -z $ip ]] && ip=$(curl -s https://ifconfig.co/ip) + [[ -z $ip ]] && ip=$(curl -s https://api.myip.com | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}") + [[ -z $ip ]] && ip=$(curl -s icanhazip.com) + [[ -z $ip ]] && ip=$(curl -s myip.ipip.net | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}") + [[ -z $ip ]] && echo -e "\n$red 这垃圾小鸡扔了吧!$none\n" && exit +} + +error() { + + echo -e "\n$red 输入错误!$none\n" + +} + +pause() { + + read -rsp "$(echo -e "按$green Enter 回车键 $none继续....或按$red Ctrl + C $none取消.")" -d $'\n' + echo +} +do_service() { + if [[ $systemd ]]; then + systemctl $1 $2 + else + service $2 $1 + fi +} +show_config_info() { + clear + _load v2ray-info.sh + _v2_args + _v2_info + _load ss-info.sh + +} + +install() { + if [[ -f /usr/bin/v2ray/v2ray && -f /etc/v2ray/config.json ]] && [[ -f $backup && -d /etc/v2ray/233boy/v2ray ]]; then + echo + echo " 大佬...你已经安装 V2Ray 啦...无需重新安装" + echo + echo -e " $yellow输入 ${cyan}v2ray${none} $yellow即可管理 V2Ray${none}" + echo + exit 1 + elif [[ -f /usr/bin/v2ray/v2ray && -f /etc/v2ray/config.json ]] && [[ -f /etc/v2ray/233blog_v2ray_backup.txt && -d /etc/v2ray/233boy/v2ray ]]; then + echo + echo " 如果你需要继续安装.. 请先卸载旧版本" + echo + echo -e " $yellow输入 ${cyan}v2ray uninstall${none} $yellow即可卸载${none}" + echo + exit 1 + fi + v2ray_config + blocked_hosts + shadowsocks_config + install_info + try_enable_bbr + # [[ $caddy ]] && domain_check + install_v2ray + if [[ $caddy || $v2ray_port == "80" ]]; then + if [[ $cmd == "yum" ]]; then + [[ $(pgrep "httpd") ]] && systemctl stop httpd + [[ $(command -v httpd) ]] && yum remove httpd -y + else + [[ $(pgrep "apache2") ]] && service apache2 stop + [[ $(command -v apache2) ]] && apt-get remove apache2* -y + fi + fi + [[ $caddy ]] && install_caddy + get_ip + config + show_config_info +} +uninstall() { + + if [[ -f /usr/bin/v2ray/v2ray && -f /etc/v2ray/config.json ]] && [[ -f $backup && -d /etc/v2ray/233boy/v2ray ]]; then + . $backup + if [[ $mark ]]; then + _load uninstall.sh + else + echo + echo -e " $yellow输入 ${cyan}v2ray uninstall${none} $yellow即可卸载${none}" + echo + fi + + elif [[ -f /usr/bin/v2ray/v2ray && -f /etc/v2ray/config.json ]] && [[ -f /etc/v2ray/233blog_v2ray_backup.txt && -d /etc/v2ray/233boy/v2ray ]]; then + echo + echo -e " $yellow输入 ${cyan}v2ray uninstall${none} $yellow即可卸载${none}" + echo + else + echo -e " + $red 大胸弟...你貌似毛有安装 V2Ray ....卸载个鸡鸡哦...$none + + 备注...仅支持卸载使用我 (v2ray6.com) 提供的 V2Ray 一键安装脚本 + " && exit 1 + fi + +} + +args=$1 +_gitbranch=$2 +[ -z $1 ] && args="online" +case $args in +online) + #hello world + [[ -z $_gitbranch ]] && _gitbranch="master" + ;; +local) + local_install=true + ;; +*) + echo + echo -e " 你输入的这个参数 <$red $args $none> ...这个是什么鬼啊...脚本不认识它哇" + echo + echo -e " 这个辣鸡脚本仅支持输入$green local / online $none参数" + echo + echo -e " 输入$yellow local $none即是使用本地安装" + echo + echo -e " 输入$yellow online $none即是使用在线安装 (默认)" + echo + exit 1 + ;; +esac + +clear +while :; do + echo + echo "........... V2Ray 一键安装脚本 & 管理脚本 by v2ray6.com .........." + echo + echo "帮助说明: https://v2ray6.com/post/1/" + echo + echo "搭建教程: https://v2ray6.com/post/2/" + echo + echo " 1. 安装" + echo + echo " 2. 卸载" + echo + if [[ $local_install ]]; then + echo -e "$yellow 温馨提示.. 本地安装已启用 ..$none" + echo + fi + read -p "$(echo -e "请选择 [${magenta}1-2$none]:")" choose + case $choose in + 1) + install + break + ;; + 2) + uninstall + break + ;; + *) + error + ;; + esac +done \ No newline at end of file diff --git "a/V2ray/v2ray-\345\256\211\350\243\205.java" "b/V2ray/v2ray-\345\256\211\350\243\205.java" new file mode 100644 index 00000000..35bfe521 --- /dev/null +++ "b/V2ray/v2ray-\345\256\211\350\243\205.java" @@ -0,0 +1,43 @@ +----------------- +安装 | +----------------- + bash <(curl -s -L https://git.io/v2ray.sh) + * 一路回车 + + + +----------------- +管理 | +----------------- + v2ray info + 查看 V2Ray 配置信息 + v2ray config + 修改 V2Ray 配置 + v2ray link + 生成 V2Ray 配置文件链接 + v2ray infolink + 生成 V2Ray 配置信息链接 + v2ray qr + 生成 V2Ray 配置二维码链接 + v2ray ss + 修改 Shadowsocks 配置 + v2ray ssinfo + 查看 Shadowsocks 配置信息 + v2ray ssqr + 生成 Shadowsocks 配置二维码链接 + v2ray status + 查看 V2Ray 运行状态 + v2ray start + 启动 V2Ray + v2ray stop + 停止 V2Ray + v2ray restart + 重启 V2Ray + v2ray log + 查看 V2Ray 运行日志 + v2ray update + 更新 V2Ray + v2ray update.sh + 更新 V2Ray 管理脚本 + v2ray uninstall + 卸载 V2Ray \ No newline at end of file diff --git "a/V2ray/v2ray-\351\205\215\347\275\256\346\226\207\344\273\266.java" "b/V2ray/v2ray-\351\205\215\347\275\256\346\226\207\344\273\266.java" new file mode 100644 index 00000000..6d18de0b --- /dev/null +++ "b/V2ray/v2ray-\351\205\215\347\275\256\346\226\207\344\273\266.java" @@ -0,0 +1,107 @@ +---------------------- +配置文件主体 | +---------------------- + { + "log": {}, + "api": {}, + "dns": {}, + "stats": {}, + "routing": {}, + "policy": {}, + "reverse": {}, + "inbounds": [], + "outbounds": [], + "transport": {} + } + # 的传入协议 + HTTP,SOCKS,VMess,Shadowsocks,Dokodemo-door + + # 传出协议有 + VMess,Shadowsocks,Blackhole,Freedom,SOCKS + + # 日志的配置 + * "log" 对象 + { + "loglevel": "warning", + "access": "/var/log/v2ray/access.log", + "error": "/var/log/v2ray/error.log" + } + + * 日志级别:loglevel + debug 最详细的日志信息,专用于软件调试 + info 比较详细的日志信息,可以看到 V2Ray 详细的连接信息 + warning 警告信息。轻微的问题信息,经我观察 warning 级别的信息大多是网络错误。推荐使用 warning + error 错误信息。比较严重的错误信息。当出现 error 时该问题足以影响 V2Ray 的正常运行 + none 空。不记录任何信息 + + # alterId + * 这个参数主要是为了加强防探测能力 + * 理论上 alterId 越大越好,但越大就约占内存(只针对服务器,客户端不占内存),所以折中之下设一个中间值才是最好的 + * 那么设多大才是最好的?其实这个是分场景的,我没有严格测试过这个,不过根据经验,alterId 的值设为 30 到 100 之间应该是比较合适的。 + * alterId 的大小要保证客户端的小于等于服务器的。 + +---------------------- +一般配置 | +---------------------- +# 服务器配置 +{ + "inbounds": [ + { + "port": 16823, // 服务器监听端口 + "protocol": "vmess", // 主传入协议 + "settings": { + "clients": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", // 用户 ID,客户端与服务器必须相同 + "alterId": 64 + } + ] + } + } + ], + "outbounds": [ + { + "protocol": "freedom", // 主传出协议 + "settings": {} + } + ] +} + + +# 客户端配置 +{ + "inbounds": [ + { + "port": 1080, // 监听端口 + "protocol": "socks", // 入口协议为 SOCKS 5 + "sniffing": { + "enabled": true, + "destOverride": ["http", "tls"] + }, + "settings": { + "auth": "noauth" //socks的认证设置,noauth 代表不认证,由于 socks 通常在客户端使用,所以这里不认证 + } + } + ], + "outbounds": [ + { + "protocol": "vmess", // 出口协议 + "settings": { + "vnext": [ + { + "address": "serveraddr.com", // 服务器地址,请修改为你自己的服务器 IP 或域名 + "port": 16823, // 服务器端口 + "users": [ + { + "id": "b831381d-6324-4d53-ad4f-8cda48b30811", // 用户 ID,必须与服务器端配置相同 + "alterId": 64 // 此处的值也应当与服务器相同 + } + ] + } + ] + }, + "mux": {"enabled": true} + } + ] +} + diff --git a/V2ray/v2ray.java b/V2ray/v2ray.java new file mode 100644 index 00000000..99fb7d33 --- /dev/null +++ b/V2ray/v2ray.java @@ -0,0 +1,60 @@ +---------------------------- +v2ry | +---------------------------- + # 地址 + https://www.v2ray.com + https://github.com/v2ray/v2ray-core + +---------------------------- +官方安装 | +---------------------------- + # 下载并安装 V2Ray + wget https://install.direct/go.sh + bash go.sh + + * yum 或 apt-get 可用的情况下,此脚本会自动安装 unzip 和 daemon + * 这两个组件是安装 V2Ray 的必要组件 + * 如果系统不支持 yum 或 apt-get,请自行安装 unzip 和 daemon + + * 此脚本会自动安装以下文件 + /usr/bin/v2ray/v2ray V2Ray 程序 + /usr/bin/v2ray/v2ctl V2Ray 工具 + /etc/v2ray/config.json 配置文件 + /usr/bin/v2ray/geoip.dat IP 数据文件 + /usr/bin/v2ray/geosite.dat 域名数据文件 + + * 运行脚本位于系统的以下位置 + /etc/systemd/system/v2ray.service: Systemd + /etc/init.d/v2ray: SysV + + + * 脚本运行完成后,你需要: + + # 编辑文件 + /etc/v2ray/config.json + + # 启动和维护 + systemctl start v2ray + + start + stop + status + reload + restart + force-reload // 强制重新加载 + + # 升级和更新 + * 再次下载脚本,重新安装就OK + + # 客户端的下载 + https://github.com/v2ray/v2ray-core/releases + + +---------------------------- +v2ray 命名行参数 | +---------------------------- + -version + * 只输出当前版本然后退出,不运行 V2Ray 主程序 + + + diff --git "a/Vue/Vue-\345\256\236\344\276\213.js" "b/Vue/Vue-\345\256\236\344\276\213.js" new file mode 100644 index 00000000..43d2efb5 --- /dev/null +++ "b/Vue/Vue-\345\256\236\344\276\213.js" @@ -0,0 +1,34 @@ + +# 实例 + * 有的 Vue 组件都是 Vue 实例,并且接受相同的选项对象 (一些根实例特有的选项除外) + * 只有当实例被创建时 data 中存在的属性才是响应式的 + vm.b = 'hi' //添加了一个新的属性b,对 b 的改动将不会触发任何视图的更新 + + * Object.freeze(),会阻止修改现有的属性,也意味着响应系统无法再追踪变化,而且会抛出异常 +
+

{{message}}

+
+ let obj = { + message : '旧值' + } + Object.freeze(obj); //冻结属性对象 + let vue = new Vue({ + el:'#app', + data:obj, + }); + + * 除了数据属性,Vue 实例还暴露了一些有用的实例属性与方法,它们都有前缀 $,以便与用户定义的属性区分开来 + vm.$data; + vm.$el; + vm.$watch + (更多参考API) + + * 生命周期钩子函数,这些函数里面的this指向vue实例 + created //创建的时候执行 + mounted + updated + destroyed + + * 不要在选项或者回调上使用箭头函数 + * 箭头函数的this,指向定义时的上下文,而不是运行时的 + diff --git "a/Vue/Vue-\345\276\201\351\200\224.js" "b/Vue/Vue-\345\276\201\351\200\224.js" new file mode 100644 index 00000000..3362bd86 --- /dev/null +++ "b/Vue/Vue-\345\276\201\351\200\224.js" @@ -0,0 +1,3 @@ + +# 作用域插槽 +# diff --git "a/Vue/Vue-\346\214\207\344\273\244.js" "b/Vue/Vue-\346\214\207\344\273\244.js" new file mode 100644 index 00000000..305f695f --- /dev/null +++ "b/Vue/Vue-\346\214\207\344\273\244.js" @@ -0,0 +1,33 @@ + +# 指令是带有 v- 前缀的特殊特性 + * 指令特性的值预期是单个 JavaScript 表达式 + +# 指令参数 + * 一些指令能够接收一个"参数",在指令名称之后以冒号表示 + +# 指令修饰符 + * 修饰符是以 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定 + ... + + .prevent + * 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault(): + + + +# v-if + * 如果表达式值为 true,则渲染节点 +

现在你看到我了

+ +# v-bind + * 绑定属性到html属性/模版属性 + ... + * 缩写 :id="id" + +# v-on + * 监听dom事件 + ... + * 属性: @click="id" + + +# v-for + * 循环渲染 \ No newline at end of file diff --git "a/Vue/Vue-\346\250\241\346\235\277\350\257\255\346\263\225.js" "b/Vue/Vue-\346\250\241\346\235\277\350\257\255\346\263\225.js" new file mode 100644 index 00000000..407befb7 --- /dev/null +++ "b/Vue/Vue-\346\250\241\346\235\277\350\257\255\346\263\225.js" @@ -0,0 +1,49 @@ +# 基本的文本渲染 + {{name}} + +# 通过 v-once 指令,让渲染不在响应式(仅仅渲染一次,属性修改后视图不会修改) + 这个将不会改变: {{ msg }} + + +# 使用v-html属性处理原始的html,慎重,可能有xss风险 + //{{rwaHtml}} 会把该html以字符串形式输出(转义) +

Using mustaches: {{ rawHtml }}

+ //这个 的内容将会被替换成为属性值 rawHtml +

Using v-html directive:

+ + * 直接作为 HTML——会忽略解析属性值中的数据绑定 + data:{ + //仅仅只会渲染出红色的 {{message}},而不会把message渲染为data里面的数据 + html:`{{message}}`, + message : 'Hi' + } + * 不能使用 v-html 来复合局部模板,因为 Vue 不是基于字符串的模板引擎、 + +# html特殊属性 + * {{name}},不能用作于HTML的特性上,应该使用 v-bind +
{{id}}
+ + * 布尔特性的情况下,它们的存在即暗示为 true,v-bind 工作起来略有不同 + + + * 如果 isButtonDisabled 的值是 null undefined 或 false 则 disabled 特性甚至不会被包含在渲染出来的
- - - * item后的第一个参数,就是下标,是从开始,可以在表达式中进行加1操作{{index+1}} - # 总结 - 1,可以直接迭代 vue 实例的 data 属性中的属性 - - - - * 如果目标是字符串,则会把字符串的每个属性都当作元素来迭代 - * 如果目标是JSON对象,则会迭代该对象中的value值 - * 如果目标是Date,则不会有迭代效果 - - 2,也可以迭代 methods 方法的返回值 - - - - ------------------------- -v-on | ------------------------- - # 绑定事件监听 - - ------------------------- -v-bind | ------------------------- - # 绑定 DOM 元素属性 - ------------------------- -v-model | ------------------------- - # 现表单输入和应用状态之间的双向绑定 diff --git "a/Vue/vue-\347\273\204\344\273\266.js" "b/Vue/vue-\347\273\204\344\273\266.js" deleted file mode 100644 index 4b2c5d00..00000000 --- "a/Vue/vue-\347\273\204\344\273\266.js" +++ /dev/null @@ -1,63 +0,0 @@ ----------------------------- -vue-缁勪欢 | ----------------------------- - # 涓粍浠舵湰璐ㄤ笂鏄竴涓嫢鏈夐瀹氫箟閫夐」鐨勪竴涓 Vue 瀹炰緥 - # 鍚憊ue娉ㄥ唽涓涓粍浠 - // 瀹氫箟鍚嶄负 todo-item 鐨勬柊缁勪欢 - Vue.component('todo-item', { - template: '
  • 杩欐槸涓緟鍔為」
  • ' - }) - - # 鏋勫缓妯$増缁勪欢 -
      - - -
    - - - -1,鑾峰彇绯荤粺璁㈠崟鍙 - url :order/create - method :POST - param : - carNum //杞︾墝鍙风爜 - parkId //杞﹀満ID - payType //鏀粯绫诲瀷(鏋氫妇:ALIPAY) - isno //IIS鏈嶅姟鍣ㄨ鍗曞彿 - -2,鏀粯瀹濇敮浠 - url :order/createAlipayOrder - method :POST - param : - orderNum //绯荤粺璁㈠崟鍙 - total_fee //鎬婚噾棰(鍗曚綅涓哄垎) - isno //IIS鏈嶅姟鍣ㄨ鍗曞彿 - - - -partner="2088101568358171"& -seller_id="xxx@alipay.com"& -out_trade_no="0819145412-6177"& -subject="娴嬭瘯"& -body="娴嬭瘯娴嬭瘯"& -total_fee="0.01"& -notify_url="http://notify.msp.hk/notify.htm"& -service="mobile.securitypay.pay"& -payment_type="1"& -_input_charset="utf-8"& -it_b_pay="30m"& -sign="lBBK%2F0w5LOajrMrji7DUgEqNjIhQbidR13GovA5r3TgIbNqv231yC1NksLdw%2Ba3JnfHXoXuet6XNNHtn7VE%2BeCoRO1O%2BR1KugLrQEZMtG5jmJIe2pbjm%2F3kb%2FuGkpG%2BwYQYI51%2BhA3YBbvZHVQBYveBqK%2Bh8mUyb7GM1HxWs9k4%3D"& -sign_type="RSA" - -_input_charset=utf-8& -body=浠樻& -notify_url=http://app.123667.com/park/order/callback& -out_trade_no=CCO69620170628181444& -partner=2088011271959663& -payment_type=1& -seller_id=2088011271959663& -service=alipay.wap.create.direct.pay.by.user& -sign=kslPKD9Vb+MiAXbU2OkQVVFcNNw5goFIOYWKJ4UuxOZKkfNNraf4wBNjhKXTls6G1xyif+tWAIqAYADPL0g0brZlkp1qFoIn9R+huF/xlE7l53Ckgao1ig2XDA/Loizw9myYiT46XkpebtU48jUG5SBDtcpGTsM0CSTb5UVHd+Q=& -sign_type=RSA& -subject=鍋滆溅鏀粯& -total_fee=900 \ No newline at end of file diff --git "a/Zookeeper/ZooKeeper-Watcher\346\234\272\345\210\266.java" "b/Zookeeper/ZooKeeper-Watcher\346\234\272\345\210\266.java" new file mode 100644 index 00000000..6b660803 --- /dev/null +++ "b/Zookeeper/ZooKeeper-Watcher\346\234\272\345\210\266.java" @@ -0,0 +1,87 @@ + +-------------------------------- +Watcher 机制 | +-------------------------------- + # ZK提供了一种订阅发布的功能 Watcher + * 一对多,多个订阅监听一个主题,当主题有变化,会通知到所有的订阅者 + + # ZK的事件 + 节点的创建 + 节点的修改 + 节点的删除 + 子节点变化 + + # Watcher机制的过程 + 1,客户端向服务端注册Watcher + 2,服务端事件发生触发Watcher + 3,客户端回调Watcher得到触发事件情况 + + # Wather机制特点 + * 一次性触发 + * 事件发生 触发监听,一个 watcher event 就会被送到设置监听的客户端 + * 这种效果是一次性的,后续再次发生相同的事件,不会再次触发 + + * 事件封装 + * ZK使用WatcherEvent对象来封装服务端事件并传递 + * WatcheEvent 包含了每一个事件的三个基本属性 + KeeperState 通知状态 + @Deprecated + Unknown (-1), + Disconnected (0), + @Deprecated + NoSyncConnected (1), + SyncConnected (3), + AuthFailed (4), + ConnectedReadOnly (5), + SaslAuthenticated(6), + Expired (-112); + + EventType 事件类型 + None (-1), + NodeCreated (1), + NodeDeleted (2), + NodeDataChanged (3), + NodeChildrenChanged (4); + + Path 节点路径 + + + * event异步发送 + * Watcher通知事件从服务端发送到客户端是异步的 + + * 先注册再触发 + * 客户端必须先在服务端注册了监听,才能收到服务端的事件触发通知 + + + # 通知状态和事件类型 + SyncConnected + |-None + * 客户端与服务端成功建立连接 + |-NodeCreated + * 监听的节点被创建 + |-NodeDeleted + * 监听的节点被删除 + |-NodeDataChanged + * 监听节点的数据发生修改 + |-NodeChildrenChanged + * 监听节点的子节点发生改变 + + Disconnected + |-None + * 客户端与Zookeeper服务端断开连接 + Expired + |-None + * 会话超时 + * 通常也会收到 SessionExpiredException 异常 + AuthFailed + |-None + * 一般有两种情况 + 1,使用错误的 schema 进行权限校验 + 2,SASL 权限检查失败 + * 同时也会收到 AuthFailedException + + * 连接状态事件(EventType=None,Path=null),不需要客户端主动注册 + * 客户端只要有需要,直接处理即可 + + + diff --git a/Zookeeper/ZooKeeper-client-curator.java b/Zookeeper/ZooKeeper-client-curator.java new file mode 100644 index 00000000..1caef8e7 --- /dev/null +++ b/Zookeeper/ZooKeeper-client-curator.java @@ -0,0 +1,18 @@ +------------------------------------- +curator | +------------------------------------- + # Apache Curator是Apache的开源项目,封装了zookeeper自带的客户端,使用相对简便 + + org.apache.curator + curator-framework + 4.1.0 + + + # 提供的各种demo代码 + + org.apache.curator + curator-examples + 4.1.0 + + + \ No newline at end of file diff --git a/Zookeeper/ZooKeeper-client-zookeeper.java b/Zookeeper/ZooKeeper-client-zookeeper.java new file mode 100644 index 00000000..2f4710c6 --- /dev/null +++ b/Zookeeper/ZooKeeper-client-zookeeper.java @@ -0,0 +1,173 @@ +------------------------------------- +zookeeper | +------------------------------------- + # zookeeper自带的客户端是官方提供的,比较底层 + + org.apache.zookeeper + zookeeper + 3.4.13 + + +------------------------------------- +基本的API | +------------------------------------- + Zookeeper + * 负责建立与服务端的连接,并且提供了方法进行操作 + + Watcher + * 一个标准的事件处理器接口 + + +------------------------------------- +连接的创建 | +------------------------------------- + # 基本的连接创建 + ZooKeeper(String connectString, int sessionTimeout, Watcher watcher) + ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,boolean canBeReadOnly) + ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,long sessionId, byte[] sessionPasswd) + ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,long sessionId, byte[] sessionPasswd, boolean canBeReadOnly) + + * connectString 连接的地址可以是集群中的多个或者一个 + "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" + + * 如果是多个连接的话,其中一个无法连接时,会尝试另一个连接 + * 也可以基于某个节点目录进行连接 + "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" + * sessionTimeout 超时时间单位为毫秒 + * Watcher + * 是一个事件监听,负责监听处理连接事件 + * canBeReadOnly + * 用于标识当前会话是否支持"read-only"模式 + * 默认情况下,在ZK集群中,一个机器如果和集群中的半数以及半数以上的机器失去连接,那么这个机器将不再处理客户端请求(读请求+写请求均不处理) + * 但是有时候我们希望在发生此类故障时不影响读取请求的处理,这个就是zk的 read-only 模式 + * sessionId + * sessionPasswd + * 会话id和会话密钥 + +------------------------------------- +节点的操作 | +------------------------------------- + # 创建节点 + String create(final String path, byte data[], List acl,CreateMode createMode) + void create(final String path, byte data[], List acl,CreateMode createMode, StringCallback cb, Object ctx) + + path + * 节点地址 + data + * 数据 + acl + * ACL权限,ZooDefs.Ids 有预定义一大堆的权限,其中:OPEN_ACL_UNSAFE 表示允许所有权限 + + createMode + * 数据类型,CreateMode 枚举 + + + + # 获取节点 + byte[] getData(String path, boolean watch, Stat stat) + void getData(String path, boolean watch, DataCallback cb, Object ctx) + + watch + * 是否要监听事件,如果该值为 true,事件触发,则会调用 Zookeeper 实例的Watcher实例 + * 事件只会触发一次 + + byte[] getData(final String path, Watcher watcher, Stat stat); + void getData(final String path, Watcher watcher, DataCallback cb, Object ctx) + + Watcher + * 设置path节点事件监听器,如果发生事件,则通知该 Watcher 实例,而不通知Zookeeper的Watcher实例 + * 事件触发的时候,每个监听都会执行 + zooKeeper.getData("/root", new DefaultWatcher("root0"), null); + zooKeeper.getData("/root", new DefaultWatcher("root1"), null); + zooKeeper.getData("/root", new DefaultWatcher("root2"), null); + zooKeeper.getData("/root", new DefaultWatcher("root3"), null); + zooKeeper.setData("/root", "new".getBytes(), -1); //修改事件触发,上面三个监听都会执行,但是执行顺序不一定 + + + # 修改节点 + Stat setData(final String path, byte data[], int version) + void setData(final String path, byte data[], int version,StatCallback cb, Object ctx) + + path + * 路径 + data + * 数据 + version + * 乐观锁版本号(如果值为 -1 表示由系统维护,程序不参与) + + # 删除节点 + void delete(final String path, int version) + void delete(final String path, int version, VoidCallback cb,Object ctx) + + path + * 路径 + version + * 乐观锁版本号(如果值为 -1 表示由系统维护,程序不参与) + + + # 获取子节点 + List getChildren(String path, boolean watch); + void getChildren(String path, boolean watch, Children2Callback cb,Object ctx) + void getChildren(String path, boolean watch, ChildrenCallback cb,Object ctx) + List getChildren(String path, boolean watch, Stat stat) + + List getChildren(final String path, Watcher watcher) + void getChildren(final String path, Watcher watcher,Children2Callback cb, Object ctx) + void getChildren(final String path, Watcher watcher,ChildrenCallback cb, Object ctx) + List getChildren(final String path, Watcher watcher,Stat stat) + + # 判断节点是否存在 + Stat exists(String path, boolean watch) + void exists(String path, boolean watch, StatCallback cb, Object ctx) + Stat exists(final String path, Watcher watcher) + void exists(final String path, Watcher watcher,StatCallback cb, Object ctx) + + + +------------------------------------- +权限 | +------------------------------------- + # 主要类 + Id{ + private String scheme; + private String id; + } + + * 授权类型和对象 + + ZooDefs.Perms{ + int READ = 1 << 0; + int WRITE = 1 << 1; + int CREATE = 1 << 2; + int DELETE = 1 << 3; + int ADMIN = 1 << 4; + int ALL = READ | WRITE | CREATE | DELETE | ADMIN; + } + + * 权限常量 + + ACL{ + private int perms; + private Id id; + } + + * ACL对象 + + + # 当schema为digest时ACL权限的创建 + //授权对象 + String digest = DigestAuthenticationProvider.generateDigest("poype:123456"); + //授权的类型 + Id id = new Id("digest", digest); + //创建其权限等信息 + ACL acl = new ACL(ZooDefs.Perms.READ | ZooDefs.Perms.WRITE, id); + + * 当schema为digest时,授权对象是username:Base64(Sha1(username:password))形式的字符串 + * 为了简化编程,ZooKeeper提供了对应的工具类 + * DigestAuthenticationProvider.generateDigest 这个方法生成对应的授权对象字符串 + + # 当schema为ip时ACL权限的创建 + ACL acl = new ACL(ZooDefs.Perms.READ | ZooDefs.Perms.WRITE, new Id("ip","192.168.1.110")); + + # 客户端添加身份信息 + zooKeeper.addAuthInfo("digest","poype:123456".getBytes()); diff --git a/Zookeeper/ZooKeeper-config.java b/Zookeeper/ZooKeeper-config.java new file mode 100644 index 00000000..8b90e60b --- /dev/null +++ b/Zookeeper/ZooKeeper-config.java @@ -0,0 +1,37 @@ +-------------------------------- +配置 | +-------------------------------- + # 配置文档 + http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance + + + +-------------------------------- +配置 | +-------------------------------- + +tickTime=2000 + * zookeeper服务器之间或者与客户端保持心跳的时间间隔 + +initLimit=10 + * zookeeper接受的客户端数量 + +syncLimit=5 + * Leader和Follower之间发送消息,请求,应答时间的长度 + * 最长不能超过多少个'tickTime' + +dataDir=/tmp/zookeeper + * 数据目录 + +dataLogDir + * 日志目录 + +clientPort=2181 + * 客户端端口 + +server.[id]=[host]:[heart-port]:[election-port] + * 集群配置 + * [id] 节点的id + * [host] 节点的host + * [heart-port] 节点心跳/数据交互端口 + * [election-port] 节点的选举端口 diff --git "a/Zookeeper/ZooKeeper-zab\347\256\227\346\263\225.java" "b/Zookeeper/ZooKeeper-zab\347\256\227\346\263\225.java" new file mode 100644 index 00000000..5557993a --- /dev/null +++ "b/Zookeeper/ZooKeeper-zab\347\256\227\346\263\225.java" @@ -0,0 +1,58 @@ +---------------------------- +ZAB算法 | +---------------------------- + # Zookeeper Atomic Broadcast (Zookeeper原子广播) + * 所有的写操作都必须通过Leader完成,Leader写入本地日志后再复制到所有的Follower节点 + * 一旦Leader节点无法工作,ZAB协议能够自动从Follower节点中重新选出一个合适的替代者,即新的Leader + * 该过程即为领导选举,该领导选举过程,是ZAB协议中最为重要和复杂的过程 + + # Leader进行写操作,主要分为五步 + * 客户端向Leader发起写请求 + * Leader将写请求以Proposal的形式发给所有Follower并等待ACK + * Follower收到Leader的Proposal后返回ACK + * Leader得到过半数的ACK(Leader对自己默认有一个ACK)后向所有的Follower和Observer发送Commmit + * Leader将处理结果返回给客户端 + + * Leader并不需要得到Observer的ACK,即Observer无投票权 + * Leader不需要得到所有Follower的ACK,只要收到过半的ACK即可,同时Leader本身对自己有一个ACK + * Observer虽然无投票权,但仍须同步Leader的数据从而在处理读请求时可以返回尽可能新的数据 + + # 未Commit过的消息对客户端不可见 + + # 支持的leader选举算法 + * 可通过electionAlg配置项设置ZooKeeper用于领导选举的算法 + + 0 基于UDP的LeaderElection + + 1 基于UDP的FastLeaderElection + + 2 基于UDP和认证的FastLeaderElection + + 3 基于TCP的FastLeaderElection(默认,另外三种算法已经被弃用,并且有计划在之后的版本中将它们彻底删除而不再支持) + + + # FastLeaderElection原理 + * myid + + * 每个ZooKeeper服务器,都需要在数据文件夹下创建一个名为myid的文件 + * 该文件包含整个ZooKeeper集群唯一的ID(整数) + + + * zxid + + * 类似于RDBMS中的事务ID,用于标识一次更新操作的Proposal ID + * 为了保证顺序性,该zkid必须单调递增 + * 因此ZooKeeper使用一个64位的数来表示,高32位是Leader的epoch,从1开始,每次选出新的Leader,epoch加一 + * 低32位为该epoch内的序号,每次epoch变化,都将低32位的序号重置,这样保证了zkid的全局递增性 + + + + + # 服务器状态 + LOOKING 不确定Leader状态,该状态下的服务器认为当前集群中没有Leader,会发起Leader选举 + + FOLLOWING 跟随者状态,表明当前服务器角色是Follower,并且它知道Leader是谁 + + LEADING 领导者状态,表明当前服务器角色是Leader,它会维护与Follower间的心跳 + + OBSERVING 观察者状态,表明当前服务器角色是Observer,与Folower唯一的不同在于不参与选举,也不参与集群写操作时的投票 \ No newline at end of file diff --git "a/Zookeeper/ZooKeeper-\345\205\245\351\227\250.java" "b/Zookeeper/ZooKeeper-\345\205\245\351\227\250.java" index 4653e4e3..1d2df53b 100644 --- "a/Zookeeper/ZooKeeper-\345\205\245\351\227\250.java" +++ "b/Zookeeper/ZooKeeper-\345\205\245\351\227\250.java" @@ -1,5 +1,57 @@ ------------------------- ZooKeeper-入门 | ------------------------- - * Zookeeper,翻译过来就是动物园管理组 - * 有趣的是,你会发现.很多技术的LOGO,或者图标都是动物.PHP,Hadoop等.. + # Zookeeper,翻译过来就是动物园管理组 + # 有趣的是,你会发现.很多技术的LOGO,或者图标都是动物.PHP,Hadoop等.. + # 官网 + https://zookeeper.apache.org/ + + # 分布式协调框架 + * 本质上就是一个分布式的小文件存储系统(树形目录结构) + + # 教程 + https://edu.aliyun.com/lesson_1343_11455#_11455 + +------------------------- +特性 | +------------------------- + # 全局一致性 + * Zookeeper集群中,每个server保存一份相同数据副本 + * client无论连接到哪个server,展示的数据都是一样的 + * 这是最重要的特征(CAP中CP的实现) + + # 可靠性 + * 如果消息被集群中的任何一个服务器接受,那么将会被所有服务器接受 + * 一个服务器收到消息后,同步到所有的节点 + + # 顺序性 + * 全局有序 + - 同一台服务器,A消息在B消息之前发布,则集群内所有服务器上,A消息都将在B消息前被发布 + - 处理服务器先创建了A,后创建了B,那么集群中所有的服务器都是先创建A再创建B + * 偏序 + - 一个消息B,在A消息后,被同一个发布者发布,A必将排在B的前面 + - 客户端先发送A消息服务端就先处理A + + # 数据更新原子性 + * 一次操作,要么全部成功,要么全部失败,不存在中间状态 + * 半数以上节点成功即为成功 + + # 实时性 + * 保证客户端在一个时间间隔范围内获取到服务器的更新消息,或者服务器的失效消息 + * 客户端能获取到实时的信息 + +------------------------- +目录结构 | +------------------------- + bin + conf + contrib + dist-maven + docs + lib + recipes + src + build.xml + ivy.xml + ivysettings.xml + zookeeper-3.4.13.jar \ No newline at end of file diff --git "a/Zookeeper/ZooKeeper-\345\256\242\346\210\267\347\253\257\344\275\277\347\224\250.java" "b/Zookeeper/ZooKeeper-\345\256\242\346\210\267\347\253\257\344\275\277\347\224\250.java" new file mode 100644 index 00000000..5afd1234 --- /dev/null +++ "b/Zookeeper/ZooKeeper-\345\256\242\346\210\267\347\253\257\344\275\277\347\224\250.java" @@ -0,0 +1,197 @@ +-------------------------------- +zkCli.sh | +-------------------------------- + # 连接 + -server [ip]:[port] + + * ip默认为localhost + * port默认为2181 + + # 命名 + help + * 帮助信息 + ZooKeeper -server host:port cmd args + stat path [watch] + set path data [version] + ls path [watch] + delquota [-n|-b] path + ls2 path [watch] + setAcl path acl + setquota -n|-b val path + history + redo cmdno + printwatches on|off + delete path [version] + sync path + listquota path + rmr path + get path [watch] + create [-s] [-e] path data acl + addauth scheme auth + quit + getAcl path + close + connect host:port + +-------------------------------- +基本的节点操作 | +-------------------------------- + # 节点的CRUD + create [-s] [-e] [-c] [-t ttl] path data acl + * 创建新的节点 + -s 表示顺序节点 + -e 表示临时节点 + -c 表示容器节点 + -t 表示ttl节点, ttl 表示ttl值 + path Znode + data 关联数据 + acl 权限控制 + + * 创建多级目录的时候,父级目录必须存在 + * 如果数据有特殊符号(空格),那么可以用双引号包裹整个数据项 + * 必须设置data值,如果不设置的话,节点会创建失败 + + ls path + * 查看指定目录下的所有子节点(只能查看一级) + - 查看根节点: ls / + * 参数 + -s 不仅仅列出子节点, 还显示当前父节点的信息 + + + ls2 path + * 查看指定目录下的所有子节点(只能查看一级) + * 被标识为过期,不推荐使用了, 建议使用: ls -s [node] + * 它不仅会列出子节点,还会展示根节点的信息 + [子节点1, 子节点n] + cZxid = 0x200000009 + ctime = Thu Jan 03 09:32:01 CST 2019 + mZxid = 0x200000009 + mtime = Thu Jan 03 09:32:01 CST 2019 + pZxid = 0x20000000e + cversion = 2 + dataVersion = 0 + aclVersion = 0 + ephemeralOwner = 0x0 + dataLength = 4 + numChildren = 2 + + get path + * 查看指定的节点信息 + data(我就是节点的数据) + cZxid = 0x20000000d + ctime = Thu Jan 03 09:39:15 CST 2019 + mZxid = 0x20000000d + mtime = Thu Jan 03 09:39:15 CST 2019 + pZxid = 0x20000000d + cversion = 0 + dataVersion = 0 + aclVersion = 0 + ephemeralOwner = 0x0 + dataLength = 11 + numChildren = 0 + + stat path + * 查看指定节点的信息 + * 跟 get一样,不过它少了节点的数据信息 + + set path data [version] + * 更新指定的节点 + pah 节点 + data 新的数据 + version 乐观锁(dataVersion) + + * 乐观锁参数不是必须的,如果存在乐观锁,会判断版本号,如果版本不符合,则更新失败 + + delete path [version] + * 删除指定的节点 + path 节点 + version 乐观锁(dataVersion) + * 乐观锁参数不是必须的,如果存在乐观锁,会判断版本号,如果版本不符合,则删除失败 + * 如果删除的节点存在子节点的话,那必须要先删除全部子节点 + + + rmr path + * 可以递归的删除指定节点 + * 慎重!!! + + # 节点的限制 quota + setquota -n|-b val path + * 设置节点的限制 + path 节点 + -n 子节点的最大数量(如果值为-1则无限制子节点数量) + -b 数据的最大长度(如果值为-1则表示不限制数据量大小???) + val 子节点的最大数量/数据的最大长度 值 + * 设置root节点只能有10个子节点 :setquota -n 10 /root + + listquota path + * 查看指定节点的 quota + absolute path is /zookeeper/quota/root0000000000/zookeeper_limits + Output quota for /root0000000000 count=10,bytes=-1 + * 节点的约束 + Output stat for /root0000000000 count=3,bytes=21 + * 当前节点的信息(count是包含了自己的,就是该值最少为1) + + count 子节点的最大数量 + bytes 当前节点的最大数据存储空间 + + delquota -n|-b path + * 删除指定节点的指定限制 + path 路径 + -n 删除子节数量的限制 + -b 删除节点数据的大小限制 + + * quota 的限制不是强制的 + * 例如:限制了子节点只能有2个,实际上添加3个或者多个子节点,还是会成功 + * 只是会在日志中有警告信息,需要我们手动捕获警告信息,自己处理 + + +-------------------------------- +Watcher 机制 | +-------------------------------- + stat path [watch] + * 监听指定节点的删除,创建,数据修改事件 + + * NodeCreated(节点被创建) + * 如果节点不存在,会提示:Node does not exist,不过仍然可以监听 + * NodeDeleted(节点被删除) + * NodeDataChanged(数据发生了变化) + + ls path [watch] + ls2 path [watch] + * 监听子节点增删的事件(不能监听子节点的数据变化事件) + + * NodeChildrenChanged(子节点添加/删除) + + get path [watch] + * 监听节点数据的变化事件 + * 当该节点数据发生变化,该监听会收到事件通知 + + * NodeDataChanged(数据发生了变化) + * NodeDeleted(节点被删除) + + * watch 其实就是随便输入一个字符 + + + +-------------------------------- +其他 | +-------------------------------- + # 历史命令 + history + * 在客户端执行的命令记录 + 49 - delquota -n /root0000000000 + 50 - delquota -n /root0000000000 + 51 - setquota -n 1 /root0000000000 + 52 - setquota -n 1 /root0000000000 + 53 - history + 54 - listquota /root0000000000 + 55 - setquota -n 10 /root0000000000 + 56 - setquota -n -b 4~10 /root0000000000 + 57 - setquota -n -b 10 /root0000000000 + + * 前面的数字表示命名的编号 + + redo number + * 重复执行指定编号的命令 + number 命令的编号 + \ No newline at end of file diff --git "a/Zookeeper/ZooKeeper-\346\225\260\346\215\256\346\250\241\345\236\213.java" "b/Zookeeper/ZooKeeper-\346\225\260\346\215\256\346\250\241\345\236\213.java" new file mode 100644 index 00000000..02b82ff3 --- /dev/null +++ "b/Zookeeper/ZooKeeper-\346\225\260\346\215\256\346\250\241\345\236\213.java" @@ -0,0 +1,145 @@ +------------------------ +Zookeeper数据模型 | +------------------------ + # ZK的数据模型和标准的系统非常相似 + * 拥有层次命名空间,都是采用树形结构 + + # ZK树中的每一个节点都被称为:Znode + * 和文件系统树一样,ZK树中的每个节点可以拥有子节点 + + # Znode具有文件和目录的特点 + * 像文件一样维护是着数据,元信息,ACL,时间戳等数据结构 + * 像目录一样可以作为路径标识的一部分,并可以具有子Znode + * 用户在权限允许的情况下,可以对Znode进行CRUD操作 + + # Znode具有原子性操作 + * 读操作将获取与节点相关的所有数据 + * 写操作也将替换掉节点的所有数据 + * 每个节点都拥有自己的ACL(访问控制列表),这列表规定了用户权限(指定用户指南进行指定的操作) + + # Znode 存在的数据大小有限制 + * ZK可以关联一些数据,但是它并不是数据库 + * 它用来管理调度数据,比如分布式应用中配置文件信息,状态,位置等等...这些数据都是很小的数据,通常都是KB为单位 + * 服务器和客户端都被设计为严格检查并限制每个Znode的数据大约是1MB(应该尽量小余此值) + + * 可以通过修改 jute.maxbuffer 参数来修改(需要注意的是,在修改该参数的时候,需要在zookeeper集群的所有服务端以及客户端上设置才能生效) + + * ZooKeeper集群中每一台服务器都包含全量的数据,并且这些数据都会加载到内存中 + * 如果ZNode的过大,那么读写某一个ZNode将造成不确定的延时 + * 同时ZNode过大,将过快地耗尽ZooKeeper服务器的内存(这也是为什么ZooKeeper不适合存储大量的数据的原因) + + # Znode 通过路径引用 + * 跟Unix中的文件路径一样,路径必须是绝对的,因此必须以斜杠开头: / + * 并且路径是唯一的,每个路径只有一个表示,因此这些路径不能修改 + * ZK中有一些限制字符串,例如:/zookeeper 用以保存管理信息,比如关键配额信息 + + + # 每个Znode由3部分组成 + stat + * 状态信息,描述该Znode的版本,权限等信息 + data + * 与该Znode关联的数据 + children + * 该Znode下的子节点 + + # 节点类型 + * 临时节点 + - 会话级别的节点,一点会话结束,临时节点将会被删除(会有几秒的延迟,因为系统要时间去判断客户端是真的断开了连接,还是网络异常) + - 可以手动删除临时节点 + - 临时节点不允许有子节点 + + * 永久节点 + - 该节点的只有在客户端主动执行删除的时候才会被删除 + + * 容器节点(3.5特性) + - 该节点, 会在最后一个字节点被删除的时候, 自动的删除 + * 许多组件(例如, 锁, 领导等)要求创建父节点, 参与者在该父节点下创建顺序节点, 参与者完成后, 将删除其节点 + - 当容器节点的最后一个孩子节点被删除之后, 容器节点将被标注并在一段时间后删除。 + - 由于容器节点的该特性,当在容器节点下创建一个子节点时, 应该始终准备捕获 KeeperException.NoNodeException, 如果捕获了该异常, 则需要重新创建容器节点 + + * TTL节点(3.5新特性, 还未实现???) + - 目前还没仔细看官网的资料, 猜测就是一个节点在经过多少时间没访问后, 就会被删除 + + * 节点的类型在创建的时候就已经确定,并且不能被改变 + + # 序列化特性 + * 在在创建Znode的时候开启该特性的的话,Znode的名字后面会追加一个不断增加的序列号 + * 序列号对于此父节点来说是唯一的,这样便能记录每个子节点的先后创建顺序 + * 格式为: "%10d",前面空的补0: 0000000001 + + + # 根据的几个特性,于是存在了多种组合种节点 + + EPHEMERAL + * 临时节点 + + EPHEMERAL_SEQUENTIAL + * 临时节点序列化 + + PERSISTENT + * 永久节点 + + PERSISTENT_SEQUENTIAL + * 永久节点序列化 + + PERSISTENT_WITH_TTL + * 带TTL(time-to-live,存活时间)的永久节点 + * 节点在TTL时间之内没有得到更新并且没有孩子节点, 就会被自动删除 + + PERSISTENT_SEQUENTIAL_WITH_TTL + * 带TTL(time-to-live, 存活时间)和序列化的永久节点, + * 节点在TTL时间之内没有得到更新并且没有孩子节点, 就会被自动删除 + + CONTAINER + * 容器节点, 用于Leader, Lock等特殊用途, + * 当容器节点不存在任何子节点时容器将成为服务器在将来某个时候删除的候选节点 + * '容器节点不能是临时的, 也不能是序列化的' + -c cannot be combined with -s or -e. Containers cannot be ephemeral or sequential. + +------------------------ +Zookeeper节点属性 | +------------------------ + # 每个Znode都包含了一系列的属性,可以通过命令get来查看 + + + dataVersion + * 数据版本号,每次对节点进行set操作,该值都会+1 + * 乐观锁 + + cversion + * 子节点版本号,默认是0 + * 当自节点有变化的时候,该值+1 + * 仅仅是添加,删除子节点的时候会影响该值,任意子节点本身都数据变化都不会影响它 + + aclVersion + * CAL版本号 + + cZxid + * Znode创建时的事务id + + mZxid + * Znode被修改时的事务id,也就是每次对Znode的修改都会更新mZxid + * 对于ZK来说,每次的变化(操作)都会产生一个唯一的事务id - zxid(Zookeeper Transaction Id) + * 通过zxid可以确定更新操作的先后顺序 + - 例如 zxid1 < zxid2 说明zxid1的操先于zxid2发生 + * zxid在集群中全局唯一 + + pZxid + * 子节点的最后一次事务id + * 包含子节点的创建,删除,修改 + + ctime + * 节点创建的时间戳 + + mtime + * 节点最后一次数据更新发生的时间戳 + + ephemeralOwner + * 如果节点为临时节点,那么该值表示与其绑定的sessionId + * 如果不是临时的,那么该值为0x0 + + dataLenth + * 数据的长度 + + numChildren + * 子节点数量 diff --git "a/Zookeeper/ZooKeeper-\346\234\215\345\212\241\347\253\257\347\273\264\346\212\244.java" "b/Zookeeper/ZooKeeper-\346\234\215\345\212\241\347\253\257\347\273\264\346\212\244.java" new file mode 100644 index 00000000..442b11e7 --- /dev/null +++ "b/Zookeeper/ZooKeeper-\346\234\215\345\212\241\347\253\257\347\273\264\346\212\244.java" @@ -0,0 +1,11 @@ +-------------------------------- +zkServer.sh | +-------------------------------- + # 参数 + * start + * start-foreground + * stop + * restart + * status + * upgrade + * print-cmd diff --git "a/Zookeeper/ZooKeeper-\351\200\211\344\270\276\346\234\272\345\210\266.java" "b/Zookeeper/ZooKeeper-\351\200\211\344\270\276\346\234\272\345\210\266.java" new file mode 100644 index 00000000..39295b62 --- /dev/null +++ "b/Zookeeper/ZooKeeper-\351\200\211\344\270\276\346\234\272\345\210\266.java" @@ -0,0 +1,90 @@ + +---------------------------- +选举机制 | +---------------------------- + # ZK默认的算法是: FastLeaderElection + * 采用投票数大于半数胜出的机制 + + # 服务器id + * 集群有3台服务器,编号为:1,2,3 + * 编号越大在选择算法中权重越大 + + # 选举状态 + LOOKING 竞选状态 + + FOLLOWING 随处状态,同步Leader状态,参与投票 + + OBSERVER 观察状态,同步Leader状态,不参与投票 + + LEADING 领导者状态 + + # 数据id + * 服务器中存放的最新数据Version + * 哪个节点的最新数据Version值越大,说明数据越新,在选举算中数据权重越大 + + # 逻辑时钟 + * 也叫做投票次数 + * 同一轮投票过程中的逻辑时钟值是相同的,每投完一次票,这个数据就会增加 + * 然后与接收到的其他服务器返回的投票信息中的数值相比 + * 根据不同的数值做出不同的判断 + + + +---------------------------- +全新集群的选举 | +---------------------------- + # 假设有5台服务器 + * 每台服务器都没有数据,编号为:1,2,3,4,5 + * 按照编号依次启动 + + # 选举过程如下 + 1,服务器1启动,给自己投票,然后发投票信息 + * 其他机器还没启动,所以它没收到反馈,于是一直处于 LOOKING + + 2,服务器2启动,给自己投票,同时与服务器1交换结果 + * 由于服务器2的编号大于服务器前面的服务器,所以服务器1会把自己的票给服务器2 + * 服务器2胜出,它有2票 + * 但此时投票数没有大于半数(2 < (5/2)),所以两个服务器依然是 LOOKING + + 3,服务器3启动,给自己投票,同时与服务器1,2交换信息 + * 由于服务器3的编号最大,所以服务器3胜出,得到了服务器1,和2的票 + * 服务器3胜出,它有 3 票 + * 此时投票数正好大于半数(3 > (5 / 2)),所以服务器3成为Leader,服务器1,2成为 Leader + + 4,服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息 + * 虽然服务器4最大,但是之前服务器3已经胜出,所以服务器4只能是 Follower + + 5,服务器5启动,后面的逻辑跟服务器4一样,也是 Follower + + + * 每个服务器都给自己投票 + * 投票数过半选举结束 + + + # 全新集群的重要依据就是服务器的编号,也就是myid + +---------------------------- +非全新集群的选举 | +---------------------------- + # 正常运行的ZK集群中,有节点宕掉,需要重新选举 + # 选举的过程就需要:数据id,服务器id,逻辑时钟 + + # 数据id + * 服务器的数据越新, Version值就越大 + + # 服务器id + + # 逻辑时钟 + * 这个值从0开始递增,每次选举对应一个值,在同一次选举中,这个值是一致的 + * 第一次选举值为0,服务器1参加了选举 + * 服务器1宕掉,集群再次选举,这时其他参与选举逻辑时钟值为1(递增 ) + * 服务器1重新加入集群,因为其他节点宕掉,参与选举,因为它宕机没有参加第一次的选举,所以它的逻辑时钟值还是为0,没有得到自增 + + + # 规则 + 1,逻辑时钟小的选举结果被忽略,重新投票(上次宕机或者其他原因,没参加选举) + * 状态最稳 + 2,统一逻辑时钟后,数据id大的胜出 + * 数据最新 + 3,数据id相同情况下,服务器id大的胜出 + * 服务器id最大 \ No newline at end of file diff --git "a/Zookeeper/ZooKeeper-\351\233\206\347\276\244.java" "b/Zookeeper/ZooKeeper-\351\233\206\347\276\244.java" new file mode 100644 index 00000000..3ca52cfe --- /dev/null +++ "b/Zookeeper/ZooKeeper-\351\233\206\347\276\244.java" @@ -0,0 +1,76 @@ +------------------------ +集群角色 | +------------------------ + # Leader + * 集群核心,唯一 + * 集群内部各个服务器的调度者 + * 事务请求(写操作)的唯一调度和处理者,保证集群中事务处理的顺序性 + - create,setData,delete 等涉及了写的操作,需要统一转发给Leader处理 + - Leader需要决定编号,执行操作.这个过程称之为一个事务 + + # Follower + * 处理客户端非事务请求(读),转发事务请求给Leader + * 参与集群Leader选举投票 + + # Observer + * 如果ZooKeeper集群的读取负载很高,或者客户端多到跨机房,可以设置一些observer服务器,以提高读取的吞吐量 + * Observer 和Follower比较相似 + * 其实就是针对非事务请求的横向扩展,事务性请求也会转发给Leader区别 + * Observer 不属于法定人数,不参加选举也不响应提议 + * Observer 不需要将事务持久化到磁盘,一旦observer被重启,需要从leader重新同步整个名字空间 + * 通常用于:在不影响集群事务处理能力的前提下,提升集群的非事务处理能力 + + +------------------------ +集群搭建 | +------------------------ + # 集群数量一般为奇数 + * 2n + 1 台server组成 + * 为了保证Leader选举(基于Paxos算法),能得到多数的支持 + + # 修改主机名称到ip地址映射配置(host) + + # 修改Zookeeper配置文件(server配置项,添加一个或者多个server节点配置) + server.[id]=[host]:[heart-port]:[election-port] + * [id] 节点的id + * [host] 节点的host + * [heart-port] 节点心跳/数据交互端口 + * [election-port] 节点的选举端口 + + * 如果是伪集群的话,那么系统设置端口不能相同,不然会冲突 + + # 设置myid + * 在 dataDir 目录下创建一个文件:myid + * 内容为当前节点的的id编号 + + # 启动集群 + * 挨个启动即可 + + # 需要保证集群中,服务器的时间是同步的 + + +------------------------ +OBServer | +------------------------ + # 在OBServer对应节点的配置文件下添加如配置 + peerType=observer + + # 所有节点的配置文件,必须指定哪些节点是Observer + server.1=ip1:2888:3888 + server.2=ip2:2888:3888 + server.3=ip3:2888:3888 + server.4=ip4:2888:3888:observer + +--------------------------- +如何提升ZOOKEEPER集群的性能| +--------------------------- + # 可以从两个方面去考虑:写入的性能与读取的性能 + * 由于ZooKeeper的写入首先需要通过Leader,然后这个写入的消息需要传播到半数以上的Fellower通过才能完成整个写入 + * 所以整个集群写入的性能无法通过增加服务器的数量达到目的 + * 相反,整个集群中Fellower数量越多,整个集群写入的性能越差 + + # ZooKeeper集群中的每一台服务器都可以提供数据的读取服务 + * 所以整个集群中服务器的数量越多,读取的性能就越好 + * 但是Fellower增加又会降低整个集群的写入性能 + * 为了避免这个问题,可以将ZooKeeper集群中部分服务器指定为Observer + diff --git "a/Zookeeper/Zookeeper-acl\346\235\203\351\231\220\347\256\241\347\220\206.java" "b/Zookeeper/Zookeeper-acl\346\235\203\351\231\220\347\256\241\347\220\206.java" new file mode 100644 index 00000000..c1549b08 --- /dev/null +++ "b/Zookeeper/Zookeeper-acl\346\235\203\351\231\220\347\256\241\347\220\206.java" @@ -0,0 +1,110 @@ +------------------------- +acl权限管理 | +------------------------- + # 参考 + https://www.jianshu.com/p/147ca2533aff + + # zookeeper支持的权限有5种分别是 + CREATE: 可以创建子节点 + READ: 可以获取节点数据以及当前节点的子节点列表 + WRITE: 可以为节点设置数据 + DELETE: 可以删除子节点 + ADMIN: 以为节点设置权限 + + # Zookeeper的ACL,可以从三个维度来理解,一是 scheme; 二是 user; 三是 permission + * scheme 表示权限的控制类型 + * user 表示授权的对象 + * permission 表示访问权限 + + # 通常表示为:scheme:id:permissions + + # scheme + * scheme对应于采用哪种方案来进行权限管理,zookeeper实现了一个pluggable的ACL方案 + * 可以通过扩展scheme,来扩展ACL的机制 + * zookeeper-3.4.4缺省支持下面几种scheme + + world + * 它下面只有一个id, 叫 anyone, world:anyone 代表任何人 + * zookeeper中对所有人有权限的结点就是属于world:anyone的 + + auth: + * 它不需要id, 只要是通过authentication的user都有权限(zookeeper支持通过kerberos来进行authencation, 也支持username/password形式的authentication) + * 不过这里应该用expression来表示,即(scheme:expression:permissions) + + digest: + * 它对应的id为username:BASE64(SHA1(password)),它需要先通过username:password形式的authentication + * digest:kevin:BASE64(SHA1('123456')) + + host + * 使用用户主机名作为访问控制列表的id + * 但是这里需要注意的是表达式用的是主机名的后缀即可,只要是符合后缀的多级域名都可以 + + ip + * 它对应的id为客户机的IP地址,设置的时候可以设置一个ip段 + * 比如 ip:192.168.1.0/16, 表示匹配前16个bit的IP段 + + super: + * 在这种scheme情况下,对应的id拥有超级权限,可以做任何事情(cdrwa) + +------------------------- +acl权限管理 - auth | +------------------------- + # 这种授权方式,是把一个节点授权给系统所有认证的用户 + * 一对多 + # 在执行授权之前,必须先添加用户 + addauth digest user:pass + + * user:pass 用户名和密码(明文) + + # 设置auth授权 + setAcl path auth::rwadc + + * path 节点 + * auth 授权方式 + * rwadc 表示权限:read,write,admin,delete,create + + * 该授权表示,授权的节点,允许被系统添加的用户操作 + + # 查看acl授权 + getAcl path + 'digest,'zookeeper:4lvlzsipXVaEhXMd+2qMrLc0at8= + : cdrwa + 'digest,'root:qiTlqPLK7XM2ht3HMn02qRpkKIE= + : cdrwa + + +------------------------- +acl权限管理 - digest | +------------------------- + # 这种授权方式,是把一个节点授权给系统指定的用户 + * 一对一 + # 先把添加用户到ZK + addauth digest user:pass + + * user:pass 用户名和密码(明文) + + # 设置digest授权 + setAcl path digest:user:pass:rwadc + + * path 节点 + * user:pass 用户名和密码,注意,密码是先经过 sha1运算,然后base64编码的结果 + * auth 授权方式 + * rwadc 表示权限:read,write,admin,delete,create + + + # 查看acl授权 + 'digest,'zookeeper:4lvlzsipXVaEhXMd+2qMrLc0at8= + : cdrwa + + # 这个操作其实就是把系统中已经存在的用户,授权给一个指定的节点 + * 执行 digest 授权的时候,不用明文密码,更加的安全 + * 获取用户的密码,如果不知道密码,可以通过:getAcl 去获取用户有权限的节点的权限信息中获取 + * 如果知道密码,自己可以计算出来 + + # 再把用户添加到系统 + # 先执行授权,设置的密码需要自己去经过 sha1 和 base64编码 + # 可以使用zk提供的客户端工具类来获取到加密后的密码 + String digest = DigestAuthenticationProvider.generateDigest("kevin:123456"); + //kevin:GSivD5W51c7Wm5vFWnFp1IYOVTY= + //冒号后面就是密码加密后的密文 + \ No newline at end of file diff --git "a/Zookeeper/Zookeeper-\344\277\256\346\224\271\350\212\202\347\202\271\346\225\260\346\215\256\345\244\247\345\260\217\351\231\220\345\210\266.java" "b/Zookeeper/Zookeeper-\344\277\256\346\224\271\350\212\202\347\202\271\346\225\260\346\215\256\345\244\247\345\260\217\351\231\220\345\210\266.java" new file mode 100644 index 00000000..a4bc1d1c --- /dev/null +++ "b/Zookeeper/Zookeeper-\344\277\256\346\224\271\350\212\202\347\202\271\346\225\260\346\215\256\345\244\247\345\260\217\351\231\220\345\210\266.java" @@ -0,0 +1,18 @@ +# 修改节点数据大小的限制 + * 服务器和客户端都被设计为严格检查并限制每个Znode的数据大约是1MB(应该尽量小余此值) + * 可以通过修改 jute.maxbuffer 参数来修改 + + * jute.maxbuffer 默认值1048575,单位字节,用于配置单个数据节点(ZNode)上可以存储的最大数据大小 + * 需要注意的是,在修改该参数的时候,需要在zookeeper集群的所有服务端以及客户端上设置才能生效 + + +# 服务端的修改 + * zkServer.sh 新增 -Djute.maxbuffer 配置 + + +# 客户端的修改 + * 如果是直接使用zookeeper自带的zkCli.sh读取数据,那么要在zkCli.sh中新增 -Djute.maxbuffer 配置 + + * 如果使用客户端(代码),可以在 System.properties 中设置(在初始化zookeeper客户端之前) + System.setProperty("jute.maxbuffer",String.valueOf(1024 * 10000L)); //10MB + diff --git "a/Zookeeper/Zookeeper-\345\272\224\347\224\250-\345\210\206\345\270\203\345\274\217\351\224\201.java" "b/Zookeeper/Zookeeper-\345\272\224\347\224\250-\345\210\206\345\270\203\345\274\217\351\224\201.java" new file mode 100644 index 00000000..c12c9f71 --- /dev/null +++ "b/Zookeeper/Zookeeper-\345\272\224\347\224\250-\345\210\206\345\270\203\345\274\217\351\224\201.java" @@ -0,0 +1,22 @@ +------------------------ +分布式锁 | +------------------------ + # 分布式锁,主要得益于ZK保证了数据的强一致性 + + # 排他锁 + * 所有客户端都抢占一把锁,最终只有一个客户端可以获得锁 + + * 把一个Znode看作是一把锁,通过 create znode 的方式实现 + * 所有的客户端都尝试创建该节点,但是只有一个人创建成功,创建成功的人,则拥有了这把锁 + * 锁拥有者删除了znode后,表示释放了锁,其他的客户端可以尝试去创建 + * 其他的客户端可以尝试监听这个znode是否存在,在znode删除事件发生的时候,通知所有客户端来竞争锁 + + * 创建的节点应该是一个临时节点,在连接断开后就会删除 + * 而且是非序列节点 + + # 控制时序 + * 利用序列化node的特性 + * 多个客户端创建同一个序列化节点,那么节点的序列就是客户端的序列 + + + \ No newline at end of file diff --git "a/Zookeeper/Zookeeper-\345\272\224\347\224\250-\345\221\275\345\220\215\346\234\215\345\212\241.java" "b/Zookeeper/Zookeeper-\345\272\224\347\224\250-\345\221\275\345\220\215\346\234\215\345\212\241.java" new file mode 100644 index 00000000..c95ffe12 --- /dev/null +++ "b/Zookeeper/Zookeeper-\345\272\224\347\224\250-\345\221\275\345\220\215\346\234\215\345\212\241.java" @@ -0,0 +1,4 @@ +------------------------ +命名服务 | +------------------------ + # Dubbo \ No newline at end of file diff --git "a/Zookeeper/Zookeeper-\345\272\224\347\224\250-\346\225\260\346\215\256\350\256\242\351\230\205\345\222\214\345\217\221\345\270\203.java" "b/Zookeeper/Zookeeper-\345\272\224\347\224\250-\346\225\260\346\215\256\350\256\242\351\230\205\345\222\214\345\217\221\345\270\203.java" new file mode 100644 index 00000000..d10d19ea --- /dev/null +++ "b/Zookeeper/Zookeeper-\345\272\224\347\224\250-\346\225\260\346\215\256\350\256\242\351\230\205\345\222\214\345\217\221\345\270\203.java" @@ -0,0 +1,7 @@ +---------------------------------------- +发布与订阅 | +---------------------------------------- + # 所谓的配置中心,就是把数据发布到ZK节点,让订阅者获取到最新的数据,实现配置信息的集中式管理和动态更新 + # 应用在启动的时候,就会主动的到ZK拉取配置,同时注册Watcher,在配置更新后,ZK都能通知到订阅的客户端 + # 客户端在处理了更新事件之后,需要再次设置监听(ZK的事件是一次性的) + diff --git a/Zookeeper/curator/curator-PathChildrenCache.java b/Zookeeper/curator/curator-PathChildrenCache.java new file mode 100644 index 00000000..4582e595 --- /dev/null +++ b/Zookeeper/curator/curator-PathChildrenCache.java @@ -0,0 +1,41 @@ +---------------------------------- +PathChildrenCache | +---------------------------------- + # 构造方法 + PathChildrenCache(CuratorFramework client, String path, boolean cacheData) + PathChildrenCache(CuratorFramework client, String path, boolean cacheData, boolean dataIsCompressed, final ExecutorService executorService) + PathChildrenCache(CuratorFramework client, String path, boolean cacheData, boolean dataIsCompressed, ThreadFactory threadFactory) + PathChildrenCache(CuratorFramework client, String path, boolean cacheData, boolean dataIsCompressed, final CloseableExecutorService executorService) + PathChildrenCache(CuratorFramework client, String path, boolean cacheData, ThreadFactory threadFactory) + + * client + * path + * cacheData + * dataIsCompressed + * threadFactory + * executorService + + # 实例方法 + void clear() + void clearAndRefresh() + void clearDataBytes(String fullPath) + boolean clearDataBytes(String fullPath, int ifVersion) + void close() + List getCurrentData() + ChildData getCurrentData(String fullPath) + void rebuild() + void rebuildNode(String fullPath) + void start() + void start(StartMode mode) + + # 内部类 + PathChildrenCache.State(enum) + LATENT + STARTED + CLOSED + + StartMode(enum) + NORMAL + BUILD_INITIAL_CACHE + POST_INITIALIZED_EVENT + diff --git a/Zookeeper/curator/curator-async.java b/Zookeeper/curator/curator-async.java new file mode 100644 index 00000000..fefd6862 --- /dev/null +++ b/Zookeeper/curator/curator-async.java @@ -0,0 +1,46 @@ +------------------------------------ +异步 | +------------------------------------ + # 异步创建 + //装饰原始的客户端对象 + AsyncCuratorFramework asyncCuratorFramework = AsyncCuratorFramework.wrap(client); + //执行创建,whenComplete 添加回调函数 + asyncCuratorFramework.create().forPath("/", new byte[] {}).whenComplete((name, exception) -> { + if ( exception != null ){ + exception.printStackTrace(); + }else{ + System.out.println("Created node name is: " + name); + } + }); + + # 异步的创建并且添加Watcher + AsyncCuratorFramework async = AsyncCuratorFramework.wrap(client); + async.create().forPath("/").whenComplete((name, exception) -> { + if (exception != null) { + exception.printStackTrace(); + } else { + handleWatchedStage(async.watched().checkExists().forPath("/").event()); + } + }); + + private static void handleWatchedStage(CompletionStage watchedStage) { + // async handling of Watchers is complicated because watchers can trigger multiple times + // and CompletionStage don't support this behavior + + // thenAccept() handles normal watcher triggering. + watchedStage.thenAccept(event -> { + System.out.println(event.getType()); + System.out.println(event); + // etc. + }); + + // exceptionally is called if there is a connection problem in which case + // watchers trigger to signal the connection problem. "reset()" must be called + // to reset the watched stage + watchedStage.exceptionally(exception -> { + AsyncEventException asyncEx = (AsyncEventException)exception; + asyncEx.printStackTrace(); // handle the error as needed + handleWatchedStage(asyncEx.reset()); + return null; + }); + } \ No newline at end of file diff --git a/Zookeeper/curator/curator-crud.java b/Zookeeper/curator/curator-crud.java new file mode 100644 index 00000000..8d7248f2 --- /dev/null +++ b/Zookeeper/curator/curator-crud.java @@ -0,0 +1,95 @@ + +---------------------------------------- +crud | +---------------------------------------- + # 总结 + * 基本的语法:操作().options1().option2(value)..optionN().forPath("/"); + curatorFramework.start(); + curatorFramework.create().forPath("/"); + curatorFramework.getChildren().forPath("/"); + curatorFramework.getACL().forPath("/"); + curatorFramework.setACL().withACL(null).forPath("/"); + curatorFramework.delete().forPath("/"); + curatorFramework.setData().forPath("/"); + curatorFramework.sync().forPath("/"); + curatorFramework.checkExists().forPath("/"); + + * 根据操作的不同,可以有N多个options + + # 普通创建 + client.create().forPath(String path, byte[] data); + + * 默认创建的永久,非序列化节点 + + # 创建其他模式的节点节点 + client.create().withMode(CreateMode.EPHEMERAL).forPath(String path, byte[] data); + + # 创建受保护的节点 + String client.create().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(String path, byte[] data); + + * 未知 + + # 递归的创建多级节点 + //TODO + + # 修改节点 + client.setData().forPath(String path, byte[] data); + + # 异步修改节点 + CuratorListener listener = new CuratorListener(){ + @Override + public void eventReceived(CuratorFramework client, CuratorEvent event) throws Exception { + } + }; + // 设置全局的 CuratorListener + client.getCuratorListenable().addListener(listener); + + // inBackground 表示异步设置,修改结果会通知给全局的CuratorListener + client.setData().inBackground().forPath(String path, byte[] data); + + + # 回调修改节点 + // 设置临时的回调接口 + BackgroundCallback backgroundCallback = new BackgroundCallback() { + @Override + public void processResult(CuratorFramework client, CuratorEvent event) throws Exception { + + } + }; + // inBackground 表示异步设置,修改结果会通知给临时的Callback + client.setData().inBackground(backgroundCallback).forPath(String path, byte[] data); + + # 删除节点 + client.delete().forPath(String path); + + + # 必须删除 + client.delete().guaranteed().forPath(String path); + + * 因为网络链接问题,删除操作可能失败,如果这个节点是锁相关的节点的话,可能会破坏锁机制 + * 一旦执行了必须删除,那么Curator将记录删除失败的节点,并尝试删除它们,直到成功为止 + * 删除失败时仍会出现异常,但是只要CuratorFramework实例打开就会尝试删除 + + # 获取子节点 + List client.getChildren().watched().forPath(String path); + + # 获取节点,并且添加watcher + + List client.getChildren().usingWatcher(new Watcher() { + @Override + public void process(WatchedEvent event) { + //zk的watcher + } + }).forPath(String path); + + # 读取节点信息,并且监听 + byte[] data = curatorFramework.getData().usingWatcher(new Watcher() { + @Override + public void process(WatchedEvent event) { + + } + }).forPath(String path); + + + # 递归的删除指定节点 + client.delete().deletingChildrenIfNeeded().forPath(String path); diff --git "a/Zookeeper/curator/curator-\344\272\213\345\212\241.java" "b/Zookeeper/curator/curator-\344\272\213\345\212\241.java" new file mode 100644 index 00000000..1242c82b --- /dev/null +++ "b/Zookeeper/curator/curator-\344\272\213\345\212\241.java" @@ -0,0 +1,18 @@ + +--------------------- + 事务 | + -------------------- + + // 第一个事务操作 + CuratorOp createOp = client.transactionOp().create().forPath("/a/path", "some data".getBytes()); + // 第二个事务操作 + CuratorOp setDataOp = client.transactionOp().setData().forPath("/another/path", "other data".getBytes()); + // 第三个事务操作 + CuratorOp deleteOp = client.transactionOp().delete().forPath("/yet/another/path"); + + //把多个操作集合为一个事务操作,并且执行,要么全部成功,要么全部失败,返回每个操作的执行结果 + Collection results = client.transaction().forOperations(createOp, setDataOp,deleteOp); + + for (CuratorTransactionResult result : results) { + System.out.println(result.getForPath() + " - " + result.getType()); + } \ No newline at end of file diff --git "a/Zookeeper/curator/curator-\345\205\245\351\227\250.java" "b/Zookeeper/curator/curator-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..e950b4b1 --- /dev/null +++ "b/Zookeeper/curator/curator-\345\205\245\351\227\250.java" @@ -0,0 +1,69 @@ +------------------------------------- +curator | +------------------------------------- + # Apache Curator是Apache的开源项目,封装了zookeeper自带的客户端,使用相对简便 + + org.apache.curator + curator-framework + 4.1.0 + + + # 各种Demo源码 + + org.apache.curator + curator-examples + ${curator-framework-version} + + + # 文档 + http://curator.apache.org/index.html + + + +------------------------------------- +curator 客户端的创建 | +------------------------------------- + + // 重试策略,1000毫秒后尝试重新连接,最后尝试连接3次 + RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); + // 直接创建客户端 + CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy) + + + // 使用Builder创建 + CuratorFramework curatorFramework = CuratorFrameworkFactory.builder() + // 地址 + .connectString(host) + // 重试策略 + .retryPolicy(new ExponentialBackoffRetry(1000, 3)) + .connectionTimeoutMs(3000) + .sessionTimeoutMs(3000) + // 权限 + .authorization(Arrays.asList(new AuthInfo("digest", "user:123456".getBytes()))) + .build(); + + + * Builder这种方式可以去设置权限啊,只读啊各种设置 + * 创建完毕客户端后要执行: start(); api + +------------------------------------- +CuratorFramework | +------------------------------------- + # 添加监听 + // 添加链接状态监听 + client.getConnectionStateListenable().addListener(new ConnectionStateListener() { + @Override + public void stateChanged(CuratorFramework client, ConnectionState newState) { + // 链接是否正常 + boolean isConnected = newState.isConnected(); + } + }); + + // 添加事件监听 + client.getCuratorListenable().addListener(new CuratorListener() { + @Override + public void eventReceived(CuratorFramework client, CuratorEvent event) throws Exception { + // 原生框架的event对象 + WatchedEvent watchedEvent = event.getWatchedEvent(); + } + }); diff --git "a/Zookeeper/curator/curator-\345\210\206\345\270\203\345\274\217\351\224\201.java" "b/Zookeeper/curator/curator-\345\210\206\345\270\203\345\274\217\351\224\201.java" new file mode 100644 index 00000000..90dee38a --- /dev/null +++ "b/Zookeeper/curator/curator-\345\210\206\345\270\203\345\274\217\351\224\201.java" @@ -0,0 +1,92 @@ +----------------------------- +zk锁 | +----------------------------- + # 所有的锁都是: InterProcessLock 子类 + // 尝试获取锁 - 阻塞,直到获取成功 + public void acquire() throws Exception; + // 尝试获取锁 - 阻塞,直到超时,返回 boolean 表示锁是否获取成功 + public boolean acquire(long time, TimeUnit unit) throws Exception; + // 释放锁 + public void release() throws Exception; + boolean isAcquiredInThisProcess(); + +----------------------------- +重入锁 | +----------------------------- + # 实现类 + // 创建锁 + InterProcessMutex interProcessMutex = new InterProcessMutex(client, "/node"); + // 尝试获取锁 - 阻塞,直到获取成功 + interProcessMutex.acquire(); + // 尝试获取锁 - 阻塞,直到超时,返回 boolean 表示锁是否获取成功 + boolean result = interProcessMutex.acquire(1000, TimeUnit.SECONDS); + // 释放锁 + interProcessMutex.release(); + +----------------------------- +互斥锁 | +----------------------------- + # 该锁不能重入 + # 实现类 + // 创建锁 + InterProcessSemaphoreMutex interProcessSemaphoreMutex = new InterProcessSemaphoreMutex(client,"/lock"); + // 尝试获取锁 - 阻塞,直到获取成功 + interProcessSemaphoreMutex.acquire(); + // 尝试获取锁 - 阻塞,直到超时,返回 boolean 表示锁是否获取成功 + interProcessSemaphoreMutex.acquire(1000, TimeUnit.SECONDS); + // 释放锁 + interProcessSemaphoreMutex.release(); + + +----------------------------- +读写锁 | +----------------------------- + # 该锁可以重入 + # 实现类 + InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(client, "/node"); + // 获取读锁 + InterProcessMutex readInterProcessMutex = interProcessReadWriteLock.readLock(); + // 获取写锁 + InterProcessMutex writeInterProcessMutex = interProcessReadWriteLock.writeLock(); + readInterProcessMutex.acquire(); + readInterProcessMutex.acquire(100, TimeUnit.SECONDS); + readInterProcessMutex.release(); + + +----------------------------- +信号量 | +----------------------------- + # 线程之间的同步,为了保证同一时刻只有限定数量的线程可以访问到资源 + # 实现 InterProcessSemaphoreV2 + // 一个创建100个租约(同时最多允许100个线程访问) + InterProcessSemaphoreV2 interProcessSemaphoreV2 = new InterProcessSemaphoreV2(client,"/node", 100); + + // 尝试获取一个租约,如果没有,则阻塞直到获取成功 + Lease lease = interProcessSemaphoreV2.acquire(); + + // 尝试获取一个租约,超时返回 null + Lease lease interProcessSemaphoreV2.acquire(1000, TimeUnit.SECONDS); + + // 获取指定数量的租约,如果不足,会阻塞 + Collection leases = interProcessSemaphoreV2.acquire(15); + + // 返还一个租约 + interProcessSemaphoreV2.returnLease(lease); + + // 返还一堆租约 + interProcessSemaphoreV2.returnAll(leases); + +----------------------------- +联合锁 | +----------------------------- + # 由多把锁组成的一把锁,必须当所有的锁都加速成功,则加锁成功 + # 实现类 + InterProcessMultiLock(CuratorFramework client, List paths); + * 默认根据paths新建 InterProcessMutex 锁 + + public InterProcessMultiLock(List locks); + * 使用指定的锁 + + interProcessMultiLock.acquire(); + interProcessMultiLock.acquire(100, TimeUnit.SECONDS); + interProcessMultiLock.release(); \ No newline at end of file diff --git "a/Zookeeper/curator/curator-\350\256\241\346\225\260\345\231\250.java" "b/Zookeeper/curator/curator-\350\256\241\346\225\260\345\231\250.java" new file mode 100644 index 00000000..7c3fab52 --- /dev/null +++ "b/Zookeeper/curator/curator-\350\256\241\346\225\260\345\231\250.java" @@ -0,0 +1,11 @@ +---------------------------- +共享计数器 | +---------------------------- + # 文档 + http://curator.apache.org/curator-recipes/shared-counter.html + +---------------------------- +原子计数器 | +---------------------------- + # 文档 + http://curator.apache.org/curator-recipes/distributed-atomic-long.html \ No newline at end of file diff --git "a/Zookeeper/curator/curator-\350\256\242\351\230\205\345\222\214\345\217\221\345\270\203.java" "b/Zookeeper/curator/curator-\350\256\242\351\230\205\345\222\214\345\217\221\345\270\203.java" new file mode 100644 index 00000000..b1053d88 --- /dev/null +++ "b/Zookeeper/curator/curator-\350\256\242\351\230\205\345\222\214\345\217\221\345\270\203.java" @@ -0,0 +1,3 @@ +----------------------------- +订阅和发布 | +----------------------------- \ No newline at end of file diff --git "a/cmd\345\221\275\344\273\244.java" "b/cmd\345\221\275\344\273\244.java" new file mode 100644 index 00000000..728e2026 --- /dev/null +++ "b/cmd\345\221\275\344\273\244.java" @@ -0,0 +1,19 @@ + +# 获取文件的hash值 + certutil -hashfile [文件] [hash算法] + + * hash算法可以是: md5,sha1,sha256 + +# 清除由于网络问题带来的,maven依赖下载失败遗留的 .lastupdate文件 + for /r %i in (*.lastUpdated)do del %i + + * 需要在maven的仓库目录执行 + +# 使用 pause 指令让控制台 "请按任意键继续。。。" + +# 修改cmd的字符集 + CHCP + * 查看当前的编码默认为GBK(936) + CHCP 65001 + * 设置编码为utf-8 + * 设置成功后如果不能正常显示,则尝试设置一下cmd属性里面的字体 diff --git "a/editormd-\347\254\224\350\256\260.java" "b/editormd-\347\254\224\350\256\260.java" new file mode 100644 index 00000000..b8bbc470 --- /dev/null +++ "b/editormd-\347\254\224\350\256\260.java" @@ -0,0 +1,114 @@ + +# 初始化 + //初始化编辑器 + editor = editormd({ + id:id, + path:'/static/editormd/lib/', + height:500, + width:'100%', + placeholder:'', + saveHTMLToTextarea:true, + //预览延迟,毫秒 + delay : 300, + + //支持 + tex:true, + flowChart:true, + sequenceDiagram:true, + emoji:true, + taskList:false, + tocm:false, + + autoFocus:false, + atLink:false, + watch:true, + htmlDecode:'style,script,iframe|on*', + imageUpload:true, + imageFormats: ['jpg,png,gif'], + imageUploadURL: '/upload/editor', + + //事件 + onchange:function(){ + } + }); + + let editorNode = document.querySelector('#' + id); + + /** + * 图片黏贴上传 + */ + editorNode.addEventListener('paste',function(event){ + let clipboardData = event.clipboardData; + if(clipboardData){ + let items = clipboardData.items; + if(items && items.length > 0){ + for(let item of items){ + if(item.type.startsWith("image/")){ + let file = item.getAsFile(); + if(!file){ + //TODO,文件读取失败,仅仅支持部分截图复制后的图片粘贴上传 + return; + } + //TODO,上传 + } + } + } + } + }); + + /** + * 图片拖拽上传 + */ + editorNode.addEventListener('dragenter',function(event){ + event.preventDefault(); + }); + editorNode.addEventListener('dragover',function(event){ + event.preventDefault(); + }); + editorNode.addEventListener('drop',function(event){ + event.preventDefault(); + let files = event.dataTransfer.files; + if(files){ + if(files.length > 5){ + //TODO,拖拽数量大于5 + return; + } + if(Array.from(files).some(file => !file.type.startsWith('image/'))){ + //TODO,一个或者多个文件非图片数据 + return; + } + //TODO,上传 + } + }); + editorNode.addEventListener('dragend',function(event){ + event.preventDefault(); + }); + + return editor; + } + + function post(){ + + let formData = new FormData(); + + //let htmlContent = editor.getHTML(); + let htmlContent = $('.editormd-preview')[0].innerHTML; + let markdwonContent = editor.getMarkdown(); + + formData.set('htmlContent',htmlContent); + formData.set('markdwonContent',markdwonContent); + + fetch('/temp/post/create',{ + method:'POST', + body:formData + }).then(function(response){ + if(response.ok){ + response.json().then(function(rep){ + console.log(rep); + window.location.href = '/temp/post/' + rep.data; + }); + } + }).catch(function(e){ + throw e; + }); + } \ No newline at end of file diff --git a/https.java b/https.java new file mode 100644 index 00000000..70f063f8 --- /dev/null +++ b/https.java @@ -0,0 +1,208 @@ +https://www.oschina.net/news/94188/acme-v2-and-wildcard-certificate-support-is-live +https://my.oschina.net/kimver/blog/1634575 +https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E + +## Nginx鍙嶅悜浠g悊http鐨則omcat + 1,鎵ц + curl https://get.acme.sh | sh + + + 2,鎵ц + source ~/.bashrc + + 3,鎵ц + # 闃块噷浜戝悗鍙扮殑瀵嗛挜 + export Ali_Key="1858118" + export Ali_Secret="1858118" + + # 濉啓鑷繁鐨勫煙鍚 + acme.sh --issue --dns dns_ali -d springboot.io -d *.springboot.io + + * acme.sh姣攃ertbot鐨勬柟寮忔洿鍔犺嚜鍔ㄥ寲,鐪佸幓浜嗘墜鍔ㄥ幓鍩熷悕鍚庡彴鏀笵NS璁板綍鐨勬楠,鑰屼笖涓嶇敤渚濊禆Python + * 绗竴娆℃垚鍔熶箣鍚,acme.sh浼氳褰曚笅App_Key璺烝pp_Secret,骞朵笖鐢熸垚涓涓畾鏃朵换鍔,姣忓ぉ鍑屾櫒0锛00鑷姩妫娴嬭繃鏈熷煙鍚嶅苟涓旇嚜鍔ㄧ画鏈 + * 瀵硅繖绉嶆柟寮忔湁椤捐檻鐨,璇锋厧閲,涓嶈繃涔熷彲浠ヨ嚜琛屽垹鎺夌敤鎴风骇鐨勫畾鏃朵换鍔,骞朵笖娓呯悊鎺墌/.acme.sh鏂囦欢澶瑰氨琛 + + 4,鍦ㄨ瘉涔︾敓鎴愮洰褰曟墽琛 + acme.sh --installcert -d springboot.io -d *.springboot.io \ + --keypath /usr/local/ssl/springboot/springboot.io.key \ + --fullchainpath /usr/local/ssl/springboot/springboot.io.pem + + * 浼氭妸key鍜宲em鐢熸垚鍒版寚瀹氱殑鐩綍 + + 5,閰嶇疆nginx + + server { + listen 443; + server_name springboot.io www.springboot.io; + + ssl on; + ssl_certificate /usr/local/ssl/springboot/springboot.io.pem; + ssl_certificate_key /usr/local/ssl/springboot/springboot.io.key; + + access_log logs/springboot.io.log main; + error_log logs/springboot.io.error.log; + + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Requested-For $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + location / { + proxy_pass http://127.0.0.1:1024; + proxy_connect_timeout 600; + proxy_read_timeout 600; + + } + } + + server { + listen 80; + server_name springboot.io www.springboot.io; + return 301 https://springboot.io$request_uri; + } + + 6,acme.sh 婧愮爜 + #!/usr/bin/env sh + + #https://github.com/Neilpang/get.acme.sh + + _exists() { + cmd="$1" + if [ -z "$cmd" ] ; then + echo "Usage: _exists cmd" + return 1 + fi + if type command >/dev/null 2>&1 ; then + command -v $cmd >/dev/null 2>&1 + else + type $cmd >/dev/null 2>&1 + fi + ret="$?" + return $ret + } + + if _exists curl && [ "${ACME_USE_WGET:-0}" = "0" ]; then + curl https://raw.githubusercontent.com/Neilpang/acme.sh/master/acme.sh | INSTALLONLINE=1 sh + elif _exists wget ; then + wget -O - https://raw.githubusercontent.com/Neilpang/acme.sh/master/acme.sh | INSTALLONLINE=1 sh + else + echo "Sorry, you must have curl or wget installed first." + echo "Please install either of them and try again." + fi + +## Springboot鍗曠嫭閰嶇疆 + 1,(鍦ㄨ瘉涔︾敓鎴愮洰褰)鐢熸垚keystore + * 鐢熸垚 p12 鏂囦欢(浼氳緭鍏ヤ竴娆″瘑鐮) + openssl pkcs12 -export -in fullchain.cer -inkey springboot.io.key -out springboot.io.p12 + + * 鏍规嵁p12 鏂囦欢鐢熸垚 keystore 鏂囦欢 + keytool -importkeystore -v -srckeystore springboot.io.p12 -srcstoretype pkcs12 -srcstorepass 123456 -destkeystore springboot.io.keystore -deststoretype jks -deststorepass 123456 + + * 濡傛灉鎻愮ず璀﹀憡,鍙互鑰冭檻鏍规嵁璀﹀憡鐨勫懡浠,鍐嶆墽琛屼竴娉 + keytool -importkeystore -srckeystore springboot.io.keystore -destkeystore springboot.io.keystore -deststoretype pkcs12 + + + 2,springboot閰嶇疆 + #ssl + server.ssl.enabled=true + server.ssl.key-store=classpath:ssl/springboot.io.keystore + server.ssl.key-store-type=PKCS12 + server.ssl.key-store-password=[key.store鐨勫瘑鐮乚 + + +------------------------------------ +鎵嬪姩瀹夎鍗曞煙鍚嶈瘉涔 | +------------------------------------ + +1,clone + git clone git@github.com:certbot/certbot.git + +2,鐢熸垚璇佷功 + * 鍦ㄥ綋鍓嶆湇鍔″櫒鐢熸垚(standalone)寤鸿 + ./letsencrypt-auto certonly --standalone --email [閭] -d [鍩熷悕] -d [鍩熷悕] + + * 闈炲綋鍓嶆湇鍔″櫒涓婄敓鎴(manual) + ./letsencrypt-auto certonly --manual --email [閭] -d [鍩熷悕] -d [鍩熷悕] + + * 闇瑕佸湪鏈嶅姟鍣ㄧ殑涓婂畨瑁呭彲璁块棶鐨,鑴氭湰鎻愪緵鐨勬枃鏈 + + CjhdUm0L4oQU0ZHg7F7832FtFweUPlRFJs0LxJGx_qg.-ielpqOUtyZI_Q0f9xYi8-Bj57TsuD5y4mGIMxW9GwM 鏂囨湰 + http://[鍩熷悕]/.well-known/acme-challenge/CjhdUm0L4oQU0ZHg7F7832FtFweUPlRFJs0LxJGx_qg 璁块棶鍦板潃 + + * 鎵ц鏈熼棿浼氫笌鎺у埗鍙版湁涓ゆ浜や簰,绗竴娆¤緭鍏,琛ㄧず鍚屾剰鍗忚,绗簩娆¤緭鍏ES,琛ㄧず鍏佽浠栦滑鏀堕泦浣犵殑閭 + * 鍒涘缓鏈熼棿,蹇呴』淇濊瘉80绔彛涓嶈鍗犵敤,鑴氭湰浼氬湪80绔彛鍚姩http鏈嶅姟鏉ラ獙璇佸煙鍚嶆墍灞(standalone) + * 鍒涘缓鎴愬姛鍚庝細鍦 /etc/letsencrypt/ 涓嬬敓鎴愯瘉涔︽枃浠 + * 濡傛灉閬囧埌鍥犱负 pip 甯︽潵鐨勫紓甯,灏濊瘯鍒犻櫎(寤鸿閲嶅懡鍚嶅鐞) ~/.pip/pip.conf 鏂囦欢 + * 璁板緱瀹夊叏缁/闃茬伀澧欏紑鏀80绔彛 + +3,璇佷功缁害(闇瑕佸湪鐢熸垚璇佷功鐨勬湇鍔″櫒涓婅繘琛) + ./letsencrypt-auto renew + + + Saving debug log to /var/log/letsencrypt/letsencrypt.log + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Processing /etc/letsencrypt/renewal/springcloud.io.conf(澶勭悊/etc/letsencrypt/renewal/springcloud.io.conf) + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Cert not yet due for renewal(璇佷功杩樻病鏈夊埌鏈) + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + The following certs are not due for renewal yet:(涓嬪垪璇佷功灏氭湭鍒版湡缁湡) + /etc/letsencrypt/live/springcloud.io/fullchain.pem expires on 2018-11-29 (skipped) + No renewals were attempted.(娌℃湁灏濊瘯鏇存柊) + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# nginx閰嶇疆 + ssl on; + ssl_certificate /etc/letsencrypt/live/[鍩熷悕]/fullchain.pem + ssl_certificate_key /etc/letsencrypt/live/[鍩熷悕]/privkey.pem + +# 杞崲涓 tomcat 鐨勬牸寮 + 1,杩涘叆鐩綍 /etc/letsencrypt/live/[鍩熷悕]/ 鎵ц + openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out [鍩熷悕].p12 + + * 鐢熸垚 p12 鏂囦欢(浼氳緭鍏ヤ竴娆″瘑鐮) + + 2,鏍规嵁p12 鏂囦欢鐢熸垚 keystore 鏂囦欢 + keytool -importkeystore -v -srckeystore [鍩熷悕].p12 -srcstoretype pkcs12 -srcstorepass [p12鏂囦欢鐨勫瘑鐮乚 -destkeystore [鍩熷悕].keystore -deststoretype jks -deststorepass [keytroe鐨勫瘑鐮乚 + + * 濡傛灉鎻愮ず璀﹀憡,鍙互鑰冭檻澶嶅埗璀﹀憡鐨勫懡浠,鍐嶆墽琛屼竴娉 + keytool -importkeystore -srckeystore [鍩熷悕].keystore -destkeystore [鍩熷悕].keystore -deststoretype pkcs12 + +------------------------------------ +鎵嬪姩瀹夎娉涘煙鍚嶈瘉涔 | +------------------------------------ +1,涓嬭浇 + wget https://dl.eff.org/certbot-auto + + chmod 775 certbot-auto + + * 涔熷彲閲囩敤certbot瀹樻柟 yum瀹夎鏂瑰紡 + +2,鎵ц + ./certbot-auto certonly -d *.javaweb.io -d javaweb.io --manual --email 747692844@qq.com --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory + + * 鍏跺疄灏辨槸璺熶笂闈㈢殑閭g鏂规硶涓鏍,閲囩敤鐨勬槸,闈炲綋鍓嶆湇鍔″櫒涓婄敓鎴(manual) + +3,娣诲姞TXT瑙f瀽璁板綍鍒癲ns鏈嶅姟鍣,娣诲姞瀹屾垚鍚,绛夊緟璇佷功鐢熸垚,鐢熸垚鍚 + * 鍒涘缓鎴愬姛鍚庝細鍦 /etc/letsencrypt/ 涓嬬敓鎴愯瘉涔︽枃浠 + * 璺熸墜鍔ㄥ垱寤哄崟鍩熷悕璇佷功涓鏍风殑 + +4,鏇存柊璇佷功 + ./certbot-auto renew + + * ??澶辫触浜,鍙互灏濊瘯閲嶆柊鎵ц鐢熸垚鎿嶄綔 + + +------------------------------------ +鍦ㄧ嚎璇佷功杞崲宸ュ叿 | +------------------------------------ + + https://myssl.com/ \ No newline at end of file diff --git "a/http\345\215\217\350\256\256\347\212\266\346\200\201\347\240\201.java" "b/http\345\215\217\350\256\256\347\212\266\346\200\201\347\240\201.java" new file mode 100644 index 00000000..25b23bd4 --- /dev/null +++ "b/http\345\215\217\350\256\256\347\212\266\346\200\201\347\240\201.java" @@ -0,0 +1,46 @@ + + +成功响应 + 200 OK(成功) + 201 Created(已创建) + 202 Accepted(已创建) + 203 Non-Authoritative Information(未授权信息) + 204 No Content(无内容) + 205 Reset Content(重置内容) + 206 Partial Content(部分内容) +重定向 + 300 Multiple Choice(多种选择) + 301 Moved Permanently(永久移动) + 302 Found(临时移动) + 303 See Other(查看其他位置) + 304 Not Modified(未修改) + 305 Use Proxy(使用代理) + 306 unused (未使用) + 307 Temporary Redirect(临时重定向) + 308 Permanent Redirect(永久重定向) +客户端错误 + 400 Bad Request(错误请求) + 401 Unauthorized(未授权) + 402 Payment Required(需要付款) + 403 Forbidden(禁止访问) + 404 Not Found(未找到) + 405 Method Not Allowed(不允许使用该方法) + 406 Not Acceptable(无法接受) + 407 Proxy Authentication Required(要求代理身份验证) + 408 Request Timeout(请求超时) + 409 Conflict(冲突) + 410 Gone(已失效) + 411 Length Required(需要内容长度头) + 412 Precondition Failed(预处理失败) + 413 Request Entity Too Large(请求实体过长) + 414 Request-URI Too Long(请求网址过长) + 415 Unsupported Media Type(媒体类型不支持) + 416 Requested Range Not Satisfiable(请求范围不合要求) + 417 Expectation Failed(预期结果失败) +服务器端错误 + 500 Internal Server Error(内部服务器错误) + 501 Implemented(未实现) + 502 Bad Gateway(网关错误) + 503 Service Unavailable(服务不可用) + 504 Gateway Timeout (网关超时) + 505 HTTP Version Not Supported(HTTP 版本不受支持) \ No newline at end of file diff --git a/tui-editor.java b/tui-editor.java new file mode 100644 index 00000000..8fefa414 --- /dev/null +++ b/tui-editor.java @@ -0,0 +1,178 @@ + + + + + Insert title here + + + + + + + + + +
    + + + + + + + + + + + + + + + + + +1,全屏编辑没 +2,鼠标指向统计图会有动态的提示 + * 生成动态图的时候,生成了一个id,js根据该id触发的相关事件 +3,预览窗口的开关没 +4,预览html的生成延迟太长 +5,初始化时,
    节点标签里面不支持初始化markdown内容 +6,生成的uml图不是https的 diff --git "a/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.java" "b/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.java" index f22d929e..152ab832 100644 --- "a/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.java" +++ "b/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.java" @@ -1,17 +1,89 @@ +-------------------------------- +XA 鍗忚鐨勫疄鐜 2 闃舵鎻愪氦 | +-------------------------------- + # 2PC + # 瑙掕壊 + * 涓涓崗璋冭 coordinator + * N涓簨鍔″弬鏁拌 partcipant + + # 绗竴涓樁娈(璇锋眰/琛ㄥ喅闃舵) + * coordinator 寰鎵鏈夌殑 partcipant 鍙戦侀澶勭悊璇锋眰:Prepare(鏈変簺璧勬枡涔熷彨"Vote Request") + - 灏辨槸闂竴涓嬭繖浜涘弬涓庤妭鐐"杩欎欢浜嬩綘浠兘涓嶈兘澶勭悊鎴愬姛浜" + - 姝ゆ椂杩欎簺鍙備笌鑰呰妭鐐逛竴鑸潵璇村氨浼氭墦寮鏈湴鏁版嵁搴撲簨鍔,鐒跺悗寮濮嬫墽琛屾暟鎹簱鏈湴浜嬪姟 + - 浣嗗湪鎵ц瀹屾垚鍚庡苟涓嶄細绔嬮┈鎻愪氦鏁版嵁搴撴湰鍦颁簨鍔 + + * partcipant 鍚 coordinator 鎶ュ憡:"鎴戣繖杈瑰彲浠ュ鐞嗕簡"/"鎴戣繖杈逛笉鑳藉鐞" -鍩轰簬鍙潬娑堟伅鐨勬渶缁堜竴鑷存 -TCC浜嬪姟琛ュ伩 -鏈澶у姫鍔涢氱煡 + * 濡傛灉鎵鏈夌殑 coordinator 閮藉悜鍗忚皟鑰呬綔浜"Vote Commit"鐨勫弽棣堢殑璇,閭d箞姝ゆ椂娴佺▼灏变細杩涘叆绗簩涓樁娈典簡 ----------------------------- -鍩轰簬鍙潬娑堟伅鐨勬渶缁堜竴鑷存 | ----------------------------- + # 绗簩涓樁娈(鎻愪氦/鎵ц闃舵) - 姝e父娴佺▼ + * 濡傛灉鎵鏈夊弬涓庤呰妭鐐归兘鍚戝崗璋冭呮姤鍛婅"鎴戣繖杈瑰彲浠ュ鐞",閭d箞姝ゆ椂鍗忚皟鑰呭氨浼氬悜鎵鏈夊弬涓庤呰妭鐐瑰彂閫"鍏ㄥ眬鎻愪氦纭閫氱煡"(global_commit) + * 姝ゆ椂鍙備笌鑰呰妭鐐瑰氨浼氬畬鎴愯嚜韬湰鍦版暟鎹簱浜嬪姟鐨勬彁浜,骞舵渶缁堝皢鎻愪氦缁撴灉鍥炲"ack"娑堟伅缁機oordinator + * 鐒跺悗Coordinator灏变細鍚戣皟鐢ㄦ柟杩斿洖鍒嗗竷寮忎簨鍔″鐞嗗畬鎴愮殑缁撴灉 + + # 绗簩涓樁娈(鎻愪氦/鎵ц闃舵) - 寮傚父娴佺▼ + * 浠绘剰鑺傜偣鍚戝崗璋冭呮姤鍛婅"鎴戣繖杈逛笉鑳藉鐞"(鍚戝崗璋冭呰妭鐐瑰弽棣"Vote Abort"鐨勬秷鎭) + * 姝ゆ椂鍒嗗竷寮忎簨鍔″崗璋冭呰妭鐐瑰氨浼氬悜鎵鏈夌殑鍙備笌鑰呰妭鐐瑰彂璧蜂簨鍔″洖婊氱殑娑堟伅(global_rollback) + * 姝ゆ椂鍚勪釜鍙備笌鑰呰妭鐐瑰氨浼氬洖婊氭湰鍦颁簨鍔,閲婃斁璧勬簮,骞朵笖鍚戝崗璋冭呰妭鐐瑰彂閫"ack"纭娑堟伅 + * 鍗忚皟鑰呰妭鐐瑰氨浼氬悜璋冪敤鏂硅繑鍥炲垎甯冨紡浜嬪姟澶勭悊澶辫触鐨勭粨鏋 + + # 涓ら樁娈垫彁浜ゅ崗璁腑浼氶亣鍒扮殑涓浜涢棶棰 + * 鎬ц兘闂 + * 浠庢祦绋嬩笂鎴戜滑鍙互鐪嬪緱鍑,鍏舵渶澶х己鐐瑰氨鍦ㄤ簬瀹冪殑鎵ц杩囩▼涓棿,鑺傜偣閮藉浜庨樆濉炵姸鎬 + * 鍚勪釜鎿嶄綔鏁版嵁搴撶殑鑺傜偣姝ゆ椂閮藉崰鐢ㄧ潃鏁版嵁搴撹祫婧,鍙湁褰撴墍鏈夎妭鐐瑰噯澶囧畬姣,浜嬪姟鍗忚皟鑰呮墠浼氶氱煡杩涜鍏ㄥ眬鎻愪氦,鍙備笌鑰呰繘琛屾湰鍦颁簨鍔℃彁浜ゅ悗鎵嶄細閲婃斁璧勬簮 + * 杩欐牱鐨勮繃绋嬩細姣旇緝婕暱,瀵规ц兘褰卞搷姣旇緝澶 ----------------------------- -TCC浜嬪姟琛ュ伩 | ----------------------------- ----------------------------- -鏈澶у姫鍔涢氱煡 | ----------------------------- + * 鍗忚皟鑰呭崟鐐规晠闅滈棶棰 + * 浜嬪姟鍗忚皟鑰呮槸鏁翠釜XA妯″瀷鐨勬牳蹇,涓鏃︿簨鍔″崗璋冭呰妭鐐规寕鎺,浼氬鑷村弬涓庤呮敹涓嶅埌鎻愪氦鎴栧洖婊氱殑閫氱煡,浠庤屽鑷村弬涓庤呰妭鐐瑰缁堝浜庝簨鍔℃棤娉曞畬鎴愮殑涓棿鐘舵 + + * 涓㈠け娑堟伅瀵艰嚧鐨勬暟鎹笉涓鑷撮棶棰 + * 鍦ㄧ浜屼釜闃舵,濡傛灉鍙戠敓灞閮ㄧ綉缁滈棶棰,涓閮ㄥ垎浜嬪姟鍙備笌鑰呮敹鍒颁簡鎻愪氦娑堟伅,鍙︿竴閮ㄥ垎浜嬪姟鍙備笌鑰呮病鏀跺埌鎻愪氦娑堟伅,閭d箞灏变細瀵艰嚧鑺傜偣闂存暟鎹殑涓嶄竴鑷撮棶棰 + +-------------------------------- +XA 鍗忚鐨勫疄鐜 3 闃舵鎻愪氦 | +-------------------------------- + # 3PC + * 鍏跺湪涓ら樁娈垫彁浜ょ殑鍩虹涓婂鍔犱簡CanCommit闃舵,骞跺紩鍏ヤ簡瓒呮椂鏈哄埗 + * 涓鏃︿簨鍔″弬涓庤呰繜杩熸病鏈夋敹鍒板崗璋冭呯殑Commit璇锋眰,灏变細鑷姩杩涜鏈湴commit,杩欐牱鐩稿鏈夋晥鍦拌В鍐充簡鍗忚皟鑰呭崟鐐规晠闅滅殑闂 + + # 绗竴闃舵: CanCommit + * 涓绉嶄簨鍔¤闂搷浣,浜嬪姟鐨勫崗璋冭呭悜鎵鏈夊弬涓庤呰闂"浣犱滑鏄惁鍙互瀹屾垚鏈浜嬪姟?" + * 濡傛灉鍙備笌鑰呰妭鐐硅涓鸿嚜韬彲浠ュ畬鎴愪簨鍔″氨杩斿洖"YES",鍚﹀垯"NO" + * 鑰屽湪瀹為檯鐨勫満鏅腑鍙備笌鑰呰妭鐐逛細瀵硅嚜韬昏緫杩涜浜嬪姟灏濊瘯,鍏跺疄璇寸櫧浜嗗氨鏄鏌ヤ笅鑷韩鐘舵佺殑鍋ュ悍鎬,鐪嬫湁娌℃湁鑳藉姏杩涜浜嬪姟鎿嶄綔 + + # 绗簩闃舵: PreCommit + * 鍦ㄩ樁娈典竴涓,濡傛灉鎵鏈夌殑鍙備笌鑰呴兘杩斿洖Yes鐨勮瘽,閭d箞灏变細杩涘叆PreCommit闃舵杩涜浜嬪姟棰勬彁浜 + * 姝ゆ椂鍒嗗竷寮忎簨鍔″崗璋冭呬細鍚戞墍鏈夌殑鍙備笌鑰呰妭鐐瑰彂閫丳reCommit璇锋眰,鍙備笌鑰呮敹鍒板悗寮濮嬫墽琛屼簨鍔℃搷浣,骞跺皢Undo鍜孯edo淇℃伅璁板綍鍒颁簨鍔℃棩蹇椾腑 + * 鍙備笌鑰呮墽琛屽畬浜嬪姟鎿嶄綔鍚庯紙姝ゆ椂灞炰簬鏈彁浜や簨鍔$殑鐘舵侊級,灏变細鍚戝崗璋冭呭弽棣"Ack"琛ㄧず鎴戝凡缁忓噯澶囧ソ鎻愪氦浜,骞剁瓑寰呭崗璋冭呯殑涓嬩竴姝ユ寚浠 + + * 濡傛灉闃舵涓涓湁浠讳綍涓涓弬涓庤呰妭鐐硅繑鍥炵殑缁撴灉鏄疦o鍝嶅簲 + * 鎴栬呭崗璋冭呭湪绛夊緟鍙備笌鑰呰妭鐐瑰弽棣堢殑杩囩▼涓秴鏃(2PC涓彧鏈夊崗璋冭呭彲浠ヨ秴鏃,鍙備笌鑰呮病鏈夎秴鏃舵満鍒) + * 鏁翠釜鍒嗗竷寮忎簨鍔″氨浼氫腑鏂,鍗忚皟鑰呭氨浼氬悜鎵鏈夌殑鍙備笌鑰呭彂閫"abort"璇锋眰 + + # 绗笁闃舵: DoCommit + * 鍦ㄩ樁娈典簩涓鏋滄墍鏈夌殑鍙備笌鑰呰妭鐐归兘鍙互杩涜PreCommit鎻愪氦,閭d箞鍗忚皟鑰呭氨浼氫粠"棰勬彁浜ょ姸鎬" -> "鎻愪氦鐘舵" + * 鐒跺悗鍚戞墍鏈夌殑鍙備笌鑰呰妭鐐瑰彂閫"doCommit"璇锋眰,鍙備笌鑰呰妭鐐瑰湪鏀跺埌鎻愪氦璇锋眰鍚庡氨浼氬悇鑷墽琛屼簨鍔℃彁浜ゆ搷浣,骞跺悜鍗忚皟鑰呰妭鐐瑰弽棣"Ack"娑堟伅 + * 鍗忚皟鑰呮敹鍒版墍鏈夊弬涓庤呯殑Ack娑堟伅鍚庡畬鎴愪簨鍔° + + * 濡傛灉鏈変竴涓弬涓庤呰妭鐐规湭瀹屾垚PreCommit鐨勫弽棣堟垨鑰呭弽棣堣秴鏃,閭d箞鍗忚皟鑰呴兘浼氬悜鎵鏈夌殑鍙備笌鑰呰妭鐐瑰彂閫"abort"璇锋眰,浠庤屼腑鏂簨鍔 + + # 瀵规瘮2PC + * 鐩告瘮杈2PC鑰岃█,3PC瀵逛簬鍗忚皟鑰(Coordinator)鍜屽弬涓庤咃紙Partcipant锛夐兘璁剧疆浜嗚秴鏃舵椂闂,鑰2PC鍙湁鍗忚皟鑰呮墠鎷ユ湁瓒呮椂鏈哄埗 + * 杩欎釜浼樺寲鐐,涓昏鏄伩鍏嶄簡鍙備笌鑰呭湪闀挎椂闂存棤娉曚笌鍗忚皟鑰呰妭鐐归氳(鍗忚皟鑰呮寕鎺変簡)鐨勬儏鍐典笅,鏃犳硶閲婃斁璧勬簮鐨勯棶棰 + * 鍥犱负鍙備笌鑰呰嚜韬嫢鏈夎秴鏃舵満鍒朵細鍦ㄨ秴鏃跺悗,鑷姩杩涜鏈湴commit浠庤岃繘琛岄噴鏀捐祫婧,鑰岃繖绉嶆満鍒朵篃渚ч潰闄嶄綆浜嗘暣涓簨鍔$殑闃诲鏃堕棿鍜岃寖鍥 + + * 閫氳繃CanCommit,PreCommit,DoCommit涓変釜闃舵鐨勮璁,鐩歌緝浜2PC鑰岃█,澶氳缃簡涓涓紦鍐查樁娈典繚璇佷簡鍦ㄦ渶鍚庢彁浜ら樁娈典箣鍓嶅悇鍙備笌鑺傜偣鐨勭姸鎬佹槸涓鑷寸殑 + + * 浠ヤ笂灏辨槸3PC鐩稿浜2PC鐨勪竴涓彁楂橈紙鐩稿缂撹В浜2PC涓殑鍓嶄袱涓棶棰橈級,浣嗘槸3PC渚濈劧娌℃湁瀹屽叏瑙e喅鏁版嵁涓嶄竴鑷寸殑闂 + + + +-------------------------------- +琛ュ伩浜嬪姟(TCC) | +-------------------------------- + # 闇瑕佽嚜宸辩紪鐮佸幓澶勭悊鍒嗗竷寮忎簨鍔$殑闂,瀹冧笉鏄敱DB鎻愪緵鐨勮В鍐虫柟妗 + # 鑰屾槸鍩轰簬搴旂敤灞傜殑涓绉嶈В鍐虫柟妗 + + \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\344\272\214\345\210\206\346\220\234\347\264\242\346\240\221.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\344\272\214\345\210\206\346\220\234\347\264\242\346\240\221.java" new file mode 100644 index 00000000..e63fa9c8 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\344\272\214\345\210\206\346\220\234\347\264\242\346\240\221.java" @@ -0,0 +1,296 @@ +------------------------ +二分搜索树 | +------------------------ + # 二分搜索树是二叉树 + # 二分搜索树的每个节点值 + * 都要大于左子树的所有节点值 + * 都要小余右子树的所有节点值 + * 不能重复 + + # 存储在树中的数据需要可排序 + + # 二分搜索树添加元素的非递归写法,跟链表很像 + + # 包含重复元素的树 + * 左子树小余等于节点,或者右子树大于等于节点 + * 每个节点设置一个:count属性,如果新增了一个重复元素,则设置 count++ + +------------------------ +二分搜索树的实现 | +------------------------ + +import java.util.LinkedList; +import java.util.Queue; +import java.util.Stack; +import java.util.function.Consumer; + +/** + * + * 二分搜索树 + *

    + * 二分搜索树是二叉树 + *

    + *

    + * 二分搜索树的每个节点值 都要大于左子树的所有节点值 ,都要小余右子树的所有节点值 + *

    + *

    + * 存储在树中的数据需要可排序 + *

    + * + */ +public class BinarySearchTree> { + + private class Node { + private E value; + private Node left; + private Node right; + + public Node(E value) { + this(value, null, null); + } + + public Node(E value, Node left, Node right) { + super(); + this.value = value; + this.left = left; + this.right = right; + } + } + + private Node root; + private int size; + + public BinarySearchTree() { + this.root = null; + this.size = 0; + } + + public int size() { + return this.size; + } + + public boolean isEmpty() { + return this.size == 0; + } + + public void addRecursion(E value) { + this.root = this.addRecursion(this.root, value); + } + + // 递归添加 + private Node addRecursion(Node node, E value) { + if (node == null) { + this.size++; + return new Node(value); + } + int result = value.compareTo(node.value); + if (result < 0) { + node.left = this.addRecursion(node.left, value); + } else if (result > 0) { + node.right = this.addRecursion(node.right, value); + } + return node; + } + + public boolean contains(E value) { + return this.contains(this.root, value); + } + + // 递归检索判断是否存在 + private boolean contains(Node node, E value) { + if (node == null) { + return false; + } + int result = node.value.compareTo(value); + if (result < 0) { + return this.contains(node.left, value); + } else if (result > 0) { + return this.contains(node.right, value); + } + return true; + } + + // 递归forEach + public void forEach(Consumer consumer) { + this.forEach(this.root, consumer); + } + + private void forEach(Node node, Consumer consumer) { + if (node != null) { + this.forEach(node.left, consumer); + /** + * 写中间就是中序遍历(遍历是有序的) 写前面就是前序遍历 写后面就是后序遍历 + */ + consumer.accept(node.value); + this.forEach(node.right, consumer); + } + } + + // 非递归的前序遍历,依赖栈结构(深度优先) + public void forEach() { + Stack stack = new Stack<>(); + stack.push(this.root); + while (!stack.isEmpty()) { + Node current = stack.pop(); // 栈顶元素 + System.out.println(current.value); // 处理元素 + if (current.right != null) { + stack.push(current.right); + } + if (current.left != null) { + stack.push(current.left); + } + } + } + + // 非递归遍历,依赖队列,属于层序遍历(广度优先) + public void forEach1() { + Queue queue = new LinkedList<>(); + queue.add(this.root); + while (!queue.isEmpty()) { + Node current = queue.remove(); // 队列尾元素 + System.out.println(current.value);// 处理元素 + if (current.left != null) { + queue.add(current.left); + } + if (current.right != null) { + queue.add(current.right); + } + } + } + + private Node min(Node node) { + if (node.left == null) { + return node; + } + return this.min(node.left); + } + + // 获取最小的值 + public E min() { + if (this.isEmpty()) { + throw new IllegalArgumentException("null map"); + } + return this.min(this.root).value; + } + + public Node max(Node node) { + if (node.right == null) { + return node; + } + return this.max(node.right); + } + + // 获取最大的值 + public E max() { + if (this.isEmpty()) { + throw new IllegalArgumentException("null map"); + } + return this.max(this.root).value; + } + + // 删除最小值节点,并且返回结果根节点 + private Node removeMin(Node node) { + if (node.left == null) { + Node rightNode = node.right; + node.right = null; + this.size--; + return rightNode; + } + node.left = this.removeMin(node.left); + return node; + } + + // 删除并返回最小值 + public E removeMin() { + E ret = this.min(); + this.root = this.removeMin(this.root); + return ret; + } + + // 删除最大值节点,并且返回结果根节点 + private Node removeMax(Node node) { + if (node.right == null) { + Node leftNode = node.left; + node.left = null; + this.size--; + return leftNode; + } + node.right = this.removeMax(node.right); + return node; + } + + // 删除并返回最大值 + public E removeMax() { + E ret = this.max(); + this.root = this.removeMax(this.root); + return ret; + } + + // 删除指定的树下的指定节点,返回删除节点后的根 + private Node remove(Node node, E value) { + if (node == null) { + return node; + } + int result = value.compareTo(node.value); + if (result < 0) { + node.left = this.remove(node.left, value); + return node; + } else if (result > 0) { + node.right = this.remove(node.right, value); + return node; + } else { + if (node.left == null) { + Node rightNode = node.right; + node.right = null; + this.size--; + return rightNode; + } + if (node.right == null) { + Node leftNode = node.left; + node.left = null; + this.size--; + return leftNode; + } + + Node rightMin = min(node.right); + rightMin.right = removeMin(node.right); + rightMin.left = node.left; + + node.left = node.right = null; + + return rightMin; + } + } + + // 删除任意节点 + public void remove(E e) { + this.root = this.remove(this.root, e); + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + this.generateBinarySearchTree(this.root, 0, stringBuilder); + return stringBuilder.toString(); + } + + // 生成节点描述字符串 + private void generateBinarySearchTree(Node node, int depth, StringBuilder stringBuilder) { + if (node == null) { + stringBuilder.append(this.generateDepthString(depth) + "null\n"); + return; + } + stringBuilder.append(generateDepthString(depth) + node.value + "\n"); + generateBinarySearchTree(node.left, depth + 1, stringBuilder); + generateBinarySearchTree(node.right, depth + 1, stringBuilder); + } + + // 生成层次字符串 + private String generateDepthString(int depth) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < depth; i++) { + stringBuilder.append("--"); + } + return stringBuilder.toString(); + } +} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\211\215\347\274\200\346\240\221(\345\255\227\345\205\270\346\240\221).java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\211\215\347\274\200\346\240\221(\345\255\227\345\205\270\346\240\221).java" new file mode 100644 index 00000000..24f60477 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\211\215\347\274\200\346\240\221(\345\255\227\345\205\270\346\240\221).java" @@ -0,0 +1,139 @@ +-------------------------------- +前缀树 | +-------------------------------- + # trie,也叫做字典树 + # 它不是二叉树,是一个多叉树 + # 它一般只用来处理字符串 + + # 每个节点有多个指向下个节点的指针 + * 26个英文字母(拼音) + * 如果考虑大小写的话,需要52个子节点 + + # 节点的设计 + class Node { + char c; + Map next; + } + + # 局限性 + * 浪费了太多的空间 + + # 扩展 + * 压缩字典树 + + + +-------------------------------- +Java实现 | +-------------------------------- +import java.util.Map; +import java.util.TreeMap; + +public class Trie { + + private class Node { + // 是否是单词 + private boolean isWord; + // 子单词树 + private TreeMap next; + + public Node(boolean isWord) { + this.isWord = isWord; + this.next = new TreeMap<>(); + } + + public Node() { + this(false); + } + } + + private Node root; + // 有多少个词儿 + private int size; + + public Trie() { + this.root = new Node(); + this.size = 0; + } + + public int size() { + return this.size; + } + + public boolean empty() { + return this.size == 0; + } + + public void add(String word) { + Node current = this.root; + // 遍历字符串 + for (int i = 0; i < word.length(); i++) { + Character character = word.charAt(i); + if (!current.next.containsKey(character)) { + current.next.put(character, new Node(false)); + } + current = current.next.get(character); + } + if (!current.isWord) { + current.isWord = true; + this.size++; + } + } + + // trie是否包含某个单词 + public boolean contains(String word) { + Node current = this.root; + for (int i = 0; i < word.length(); i++) { + Character character = word.charAt(i); + if (!current.next.containsKey(character)) { + return false; + } + current = current.next.get(character); + } + return current.isWord; + } + + // 是否有指定前缀的单词 + public boolean prefix(String prefix) { + Node current = this.root; + for (int i = 0; i < prefix.length(); i++) { + Character character = prefix.charAt(i); + if (!current.next.containsKey(character)) { + return false; + } + current = current.next.get(character); + } + return true; + } + + /** + * . 可以表示任意的一个字符 + * + * @param regex + * @return + */ + public boolean match(String regex) { + return this.match(this.root, regex, 0); + } + + private boolean match(Node node, String regex, int index) { + if (index == regex.length()) { + return node.isWord; // 找到单词 + } + Character character = regex.charAt(index); + if (!character.equals('.')) { + if (!node.next.containsKey(character)) { + return false; + } else { + return this.match(node.next.get(character), regex, index + 1); + } + } else { + for (Map.Entry entry : node.next.entrySet()) { + if (this.match(entry.getValue(), regex, index + 1)) { + return true; + } + } + return false; + } + } +} \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\223\210\345\244\253\346\233\274\346\240\221.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\223\210\345\244\253\346\233\274\346\240\221.java" new file mode 100644 index 00000000..e69de29b diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\223\210\345\270\214\350\241\250.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\223\210\345\270\214\350\241\250.java" new file mode 100644 index 00000000..7f7a6125 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\223\210\345\270\214\350\241\250.java" @@ -0,0 +1,30 @@ +-------------------------------- +hash函数的设计 | +-------------------------------- + # 原则 + * 一致性 + * 高效性 + * 均匀性 + + # 小整形 + * 可以直接使用,不用hash + * 例如: assic + + * 小范围负整数,进行偏移 + -100 ~ 100 | 0 - 200 + + # 大整数 + * 模一个素数 + * 根据存储数据的规模选择素数 + https://planetmath.org/goodhashtableprimes + + lwr(小) upr(大) + + # 浮点数 + + + # 字符串 + * 可以转换为大的整形再处理 + + # 符合类型 + \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\240\206.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\240\206.java" new file mode 100644 index 00000000..50980597 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\240\206.java" @@ -0,0 +1,204 @@ +------------------------- +堆 | +------------------------- + # 二叉堆,是一颗完全二叉树 + * 父结点都比子结点要小,我们称为最小堆 + * 父结点都比子结点要大,我们称为最大堆 + + # 一个堆可以用数组来表示 + + # d 叉堆 d-ary heap + + # 索引堆 + + # 二项堆 + + # 裴波那契堆 + + + + +------------------------- +最大堆 | +------------------------- + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * + * 最大堆 + * + * @author KevinBlandy + * + */ +public class MaxHeap> { + + private List data; + + public MaxHeap(E[] arr) { + this.heapify(arr); + } + + public MaxHeap() { + this.data = new ArrayList<>(); + } + + public int size() { + return this.data.size(); + } + + public boolean empty() { + return this.data.isEmpty(); + } + + /** + * 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引 + * + * @param index + * @return + */ + private int parent(int index) { + if (index == 0) { + throw new IllegalArgumentException("0 没有父级节点"); + } + return (index - 1) / 2; + } + + /** + * 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引 + * + * @param index + * @return + */ + private int leftChild(int index) { + return index * 2 + 1; + } + + /** + * 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引 + * + * @param index + * @return + */ + private int rightChild(int index) { + return index * 2 + 2; + } + + /** + * 往堆中添加元素,上浮到合适的位置 + * + * @param e + */ + public void add(E e) { + + // 添加到容器 + this.data.add(e); + // 维护堆性质 + siftUp(this.size() - 1); + } + + public E findMax() { + if (this.empty()) { + throw new IllegalArgumentException("空的堆"); + } + return this.data.get(0); + } + + /** + * 获取最大的元素 + * + * @return + */ + public E extractMax() { + E e = this.findMax(); + this.swap(0, this.size() - 1); + this.data.remove(this.size() - 1); + this.siftDown(0); + return e; + } + + /** + * 取出最大元素后,放入一个新的元素 + * 实现1:先extractMax(),再add。两次 O(logn)的操作 + * 实现2:可以直接将堆顶元素替换以后Sift + * Down。一次O(logn)的操作 + * + * @param e + * @return + */ + public E replace(E e) { + E ret = this.findMax(); + this.data.set(0, e); // 把堆顶元素替换为新的元素 + // siftDown + this.siftDown(0); + return ret; + } + + /** + * 把任意数组整理为堆(最大)的形状,并且放进data + */ + public void heapify(E[] arr) { + // data = new ArrayList<>(Arrays.asList(arr)); + this.data = new ArrayList<>(); + for (E e : arr) { + this.data.add(e); + } + for (int i = this.parent(this.data.size() - 1); i >= 0; i--) { + this.siftDown(i); + } + } + + // 下浮 + private void siftDown(int k) { + while (this.leftChild(k) < this.size()) { + int j = leftChild(k); + if ((j + 1) < this.size() && (this.data.get(j + 1).compareTo(this.data.get(j)) > 0)) { + j = this.rightChild(k); + } + if (this.data.get(k).compareTo(this.data.get(j)) >= 0) { + break; + } + + this.swap(k, j); + k = j; + } + } + + // 元素上浮 + private void siftUp(int k) { + while (k > 0 && this.data.get(this.parent(k)).compareTo(this.data.get(k)) < 0) { + this.swap(k, this.parent(k)); + k = this.parent(k); + } + } + + /** + * 交换[]两个角标的元素 + * + * @param i + * @param j + */ + private void swap(int i, int j) { + if (i < 0 || i >= this.size() || j < 0 || j >= this.size()) { + throw new IllegalArgumentException("下标不合法"); + } + E e = this.data.get(i); + this.data.set(i, this.data.get(j)); + this.data.set(j, e); + } + + /** + * 遍历数组 + * + * @param action + */ + public void forEach(Consumer action) { + this.data.forEach(action); + } +} + +------------------------- +最小堆 | +------------------------- \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\263\350\241\241\346\240\221.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\263\350\241\241\346\240\221.java" new file mode 100644 index 00000000..8d6244c8 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\263\350\241\241\346\240\221.java" @@ -0,0 +1,307 @@ +------------------------------------ +平衡树 | +------------------------------------ + # AVL + # 看呆了... + +------------------------------------ +平衡树 | +------------------------------------ +import java.util.ArrayList; +import java.util.function.BiConsumer; + +public class AVLTree, V> { + private class Node { + K key; + V value; + private Node left; + private Node right; + private int height; + + public Node(K key, V value, Node left, Node right) { + super(); + this.key = key; + this.value = value; + this.left = left; + this.right = right; + this.height = 0; + } + + public Node(K key, V value) { + this(key, value, null, null); + } + } + + private Node root; + + private int size; + + public int size() { + return this.size; + } + + public boolean empty() { + return this.size == 0; + } + + private int getHeight(Node node) { + if (node == null) { + return 0; + } + return node.height; + } + + // 计算平衡因子 + private int getBalanceFactor(Node node) { + if (node == null) { + return 0; + } + return this.getHeight(node.left) - this.getHeight(node.right); + } + + // 判断是否是二分搜索树 + public boolean isBinarySearchTree() { + ArrayList keys = new ArrayList<>(); + inOrder(this.root, keys); + for (int i = 1; i < keys.size(); i++) { + int result = keys.get(i - 1).compareTo(keys.get(i)); + if (result > 0) { + return false; + } + } + return true; + } + + // 判断是否是一颗平衡二叉树 + public boolean isBalanced() { + return this.isBalanced(this.root); + } + + private boolean isBalanced(Node node) { + if (node == null) { + return true; + } + int blanceFactor = this.getBalanceFactor(node); + if (Math.abs(blanceFactor) > 1) { + return false; + } + return isBalanced(node.left) && isBalanced(node.right); + } + + private void inOrder(Node node, ArrayList keys) { + if (node == null) { + return; + } + this.inOrder(node.left, keys); + keys.add(node.key); + this.inOrder(node.right, keys); + } + + private Node getNode(Node node, K key) { + if (node == null) { + return node; + } + int result = node.key.compareTo(key); + if (result > 0) { + return this.getNode(node.left, key); + } else if (result < 0) { + return this.getNode(node.right, key); + } else { + return node; + } + } + + public boolean contains(K key) { + return this.getNode(this.root, key) != null; + } + + public V get(K key) { + Node node = this.getNode(this.root, key); + return node == null ? null : node.value; + } + + public V set(K key, V value) { + Node node = this.getNode(this.root, key); + if (node == null) { + throw new IllegalArgumentException("not found key"); + } + V retVallue = node.value; + node.value = value; + return retVallue; + } + + // 右旋转 + private Node rightRotate(Node y) { + Node x = y.left; + Node t3 = x.right; + + x.left = y; + y.left = t3; + + y.height = Math.max(this.getHeight(y.left), this.getHeight(y.right)) + 1; + x.height = Math.max(this.getHeight(x.left), this.getHeight(x.right)) + 1; + + return x; + } + + //左旋转 + private Node leftRotate(Node y) { + Node x = y.right; + Node t2 = x.left; + + x.left = y; + y.right = t2; + + y.height = Math.max(this.getHeight(y.left), this.getHeight(y.right)) + 1; + x.height = Math.max(this.getHeight(x.left), this.getHeight(x.right)) + 1; + + return x; + } + + private Node add(Node node, K key, V value) { + if (node == null) { + this.size++; + return new Node(key, value); + } + int result = node.key.compareTo(key); + + if (result > 0) { + node.left = this.add(node.left, key, value); + } else if (result < 0) { + node.right = this.add(node.right, key, value); + } else { + node.value = value; // 覆盖 + } + + node.height = 1 + Math.max(this.getHeight(node.left), this.getHeight(node.right)); + int blanceFactor = this.getBalanceFactor(node); + if (blanceFactor > 1 && this.getBalanceFactor(node.left) >= 0) { + // 右旋转 LL + return this.rightRotate(node); + } + if (blanceFactor < -1 && this.getBalanceFactor(node.right) <= 0) { + // 左旋转 RR + return this.leftRotate(node); + } + if(blanceFactor > 1 && this.getBalanceFactor(node.left) < 0) { + // LR + node.left = this.leftRotate(node.left); + return this.rightRotate(node); + } + if(blanceFactor < -1 && this.getBalanceFactor(node.right) > 0) { + // RL + node.right = this.rightRotate(node.right); + return this.leftRotate(node.left); + } + return node; + } + + public void add(K key, V value) { + this.root = this.add(this.root, key, value); + } + + protected Node minNode(Node node) { + if (node.left == null) { + return node; + } + return this.minNode(node.left); + } + + public Node removeMin(Node node) { + if (node.left == null) { + Node rightNode = node.right; + node.right = null; + this.size--; + return rightNode; + } + node.left = removeMin(node.left); + return node; + } + + protected Node remove(Node node, K key) { + if (node == null) { + return node; + } + Node retNode = null; + int result = node.key.compareTo(key); + if (result > 0) {// left + node.left = remove(node.left, key); + retNode = node; + } else if (result < 0) { // right + node.right = remove(node.right, key); + retNode = node; + } else { + if (node.left == null) { + // 删除节点左子树为null + Node rightNode = node.right; + node.right = null; + this.size--; + retNode = rightNode; + }else if (node.right == null) { + // 删除节点右子树为null + Node leftNode = node.left; + node.left = null; + this.size--; + retNode = leftNode; + }else { + // 左右节点都不为空 + + // 获取到右边最小的值的节点 + Node successor = minNode(node.right); + successor.right = this.remove(node.right, successor.key); + successor.left = node.left; + node.left = node.right = null; + retNode = successor; + } + } + + if(retNode == null) { + return retNode; + } + + retNode.height = 1 + Math.max(this.getHeight(retNode.left), this.getHeight(retNode.right)); + int blanceFactor = this.getBalanceFactor(retNode); + if (blanceFactor > 1 && this.getBalanceFactor(retNode.left) >= 0) { + // 右旋转 LL + return this.rightRotate(retNode); + } + if (blanceFactor < -1 && this.getBalanceFactor(retNode.right) <= 0) { + // 左旋转 RR + return this.leftRotate(retNode); + } + if(blanceFactor > 1 && this.getBalanceFactor(retNode.left) < 0) { + // LR + retNode.left = this.leftRotate(retNode.left); + return this.rightRotate(retNode); + } + if(blanceFactor < -1 && this.getBalanceFactor(retNode.right) > 0) { + // RL + retNode.right = this.rightRotate(retNode.right); + return this.leftRotate(retNode.left); + } + + return retNode; + } + + public V remove(K key) { + Node node = this.getNode(this.root, key); + if (node != null) { + this.root = remove(this.root, key); + return node.value; + } + throw new IllegalArgumentException("not found key"); + } + + private void forEach(Node node, BiConsumer biConsumer) { + if (node == null) { + return; + } + this.forEach(node.left, biConsumer); + biConsumer.accept(node.key, node.value); + this.forEach(node.right, biConsumer); + } + + public void forEach(BiConsumer biConsumer) { + this.forEach(this.root, biConsumer); + } +} \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\266\346\237\245\351\233\206.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\266\346\237\245\351\233\206.java" new file mode 100644 index 00000000..c4dd9eb4 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\266\346\237\245\351\233\206.java" @@ -0,0 +1,83 @@ +------------------------- +并查集 | +------------------------- + # UnionFind + # 可以高效的处理连接问题 + * 迷宫图中,两个点是否能相连 + + # 网络中间那个节点的连接状态 + * 社交网络(用户之间形成的网络) + * A 和 B是否可以通过网络认识... + + # 数学中的集合类实现 + + # 主要支持俩动作 + union(p,q); + isConneted(p,q); + + +------------------------- +java实现 | +------------------------- + +public class UnionFind { + + private int[] parent; + + private int[] rank; //rank[i] 表示以i为根的集合中元素个数 + + public UnionFind(int size) { + super(); + this.parent = new int[size]; + this.rank = new int[size]; + for (int i = 0; i < this.parent.length; i++) { + this.parent[i] = i; + this.rank[i] = 1; + } + } + + public int size() { + return this.parent.length; + } + +// private int find(int p) { +// if(p < 0 || p >= this.parent.length) { +// throw new IllegalArgumentException("非法索引"); +// } +// while(p != this.parent[p]) { +// this.parent[p] = this.parent[this.parent[p]]; +// p = this.parent[p]; +// } +// return p; +// } + + private int find(int p) { + if(p < 0 || p >= this.parent.length) { + throw new IllegalArgumentException("非法索引"); + } + if(p != this.parent[p]) { + this.parent[p] = this.find(this.parent[p]); + } + return this.parent[p]; + } + + public boolean isConnected(int p,int q) { + return this.find(p) == this.find(q); + } + + public void unionElements(int p,int q) { + int pRoot = this.find(p); + int qRoot = this.find(q); + if(pRoot == qRoot) { + return; + } + if(this.rank[pRoot] < this.rank[qRoot]) { + this.parent[pRoot] = qRoot; + }else if(this.rank[pRoot] > this.rank[qRoot]){ + this.parent[qRoot] = pRoot; + }else{ + this.parent[qRoot] = pRoot; + this.rank[pRoot] += 1; + } + } +} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204.java" new file mode 100644 index 00000000..54472abf --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204.java" @@ -0,0 +1,26 @@ + +------------------------- +树结构 | +------------------------- + 二分搜索树 + 平衡二叉树 + * AVL + * 红黑树 + 堆 + 并查集 + 线段树 + Trie(字典树,前缀树) + + # 满二叉树 + * h 层的满二叉树有2^h - 1 个元素 + * 最后一层节点数,大致等于前面所有节点只和 + +------------------------- +二叉树 | +------------------------- + # 满的二叉树 + * 除了最底下的子节点,所有节点都具备了两个子节点 + + # 完全二叉树 + * 把元素排列成树的形状 + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\242\351\273\221\346\240\221\344\270\2162-3\346\240\221.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\242\351\273\221\346\240\221\344\270\2162-3\346\240\221.java" new file mode 100644 index 00000000..523394ca --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\242\351\273\221\346\240\221\344\270\2162-3\346\240\221.java" @@ -0,0 +1,21 @@ +------------------------ +红黑树 | +------------------------ + # 红黑树是二分搜索树,是一颗平衡二叉树 + # 特点 + 每个节点或者是红色的,或者是黑色的 + * 根节点是黑色的 + * 每一个叶子节点(最后的空节点)是黑色的 + * 如果一个节点是红色的,那么它的子节点都是黑色的 + * 从任意一个节点到叶子节点,经过的黑色节点是一样的 + + # 推荐的书籍 + 《算法4》 + + +------------------------ +2-3树 | +------------------------ + # 满足二分搜索树木的基本性质 + # 节点可以存放一个元素或者两个元素 + # 是一颗绝对平衡的树 diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\256\265\346\240\221.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\256\265\346\240\221.java" new file mode 100644 index 00000000..90412ef7 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\256\265\346\240\221.java" @@ -0,0 +1,175 @@ +-------------------- +线段树 | +-------------------- + # 也叫做区间树(Segment Tree) + + # 线段树每一个节点,都是表示了一段区间 + + # 线段树不一定是一颗满的二叉树,也不一定是一颗完全二叉树,线段树是平衡二叉树 + + + +-------------------- +实现 | +-------------------- + +public class SegmentTree { + + public static interface Merger { + E merge(E a, E b); + } + + private E[] data; + + private E[] tree; + + private Merger merger; + + @SuppressWarnings("unchecked") + public SegmentTree(E[] arr, Merger merger) { + this.data = arr; + this.tree = (E[]) new Object[4 * arr.length]; + this.merger = merger; + this.bildeSegmentTree(0, 0, this.data.length - 1); + } + + public E get(int index) { + if (index < 0 || index >= this.size()) { + throw new IllegalArgumentException("非法索引"); + } + return this.data[index]; + } + + /** + * 在treeIndex的位置创建表示区间的线段树 [l....r] + * + * @param treeIndex + * 在完全二叉树数组表示中那个的下标 + * @param l + * 左边开始的索引 + * @param r + * 右边开始的索引 + */ + private void bildeSegmentTree(int treeIndex, int l, int r) { + if (l == r) { + this.tree[treeIndex] = this.data[l]; + return; + } + + int lefTreeIndex = this.leftChild(treeIndex); + int rightTreeIndex = this.rightChild(treeIndex); + + int mid = l + (r - l) / 2; + + this.bildeSegmentTree(lefTreeIndex, l, mid); + this.bildeSegmentTree(rightTreeIndex, mid + 1, r); + + this.tree[treeIndex] = this.merger.merge(this.tree[lefTreeIndex], this.tree[rightTreeIndex]); + } + + private int size() { + return this.data.length; + } + + // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引 + private int leftChild(int index) { + return 2 * index + 1; + } + + // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引 + private int rightChild(int index) { + return 2 * index + 2; + } + + /** + * 返回区间[queryL,queryR]的值 + * + * @param queryL + * @param queryR + * @return + */ + public E query(int queryL, int queryR) { + if (queryL < 0 || queryL >= this.data.length || queryR < 0 || queryR >= this.data.length || queryL > queryR) { + throw new IllegalArgumentException("非法索引"); + } + return this.query(0, 0, this.data.length - 1, queryL, queryR); + } + + /** + * [l..r]范围内搜索区间[queryL..queryR]的值 + * + * @param treeIndex + * @param l + * @param r + * @param queryL + * @param queryR + * @return + */ + private E query(int treeIndex, int l, int r, int queryL, int queryR) { + if (l == queryL && r == queryR) { + return this.tree[treeIndex]; + } + int mid = l + (r - l) / 2; + + int leftTreeIndex = this.leftChild(treeIndex); + int rightTreeIndex = this.rightChild(treeIndex); + + if (queryL >= mid + 1) { + return this.query(rightTreeIndex, mid + 1, r, queryL, queryR); + } else if (queryR <= mid) { + return this.query(leftTreeIndex, l, mid, queryL, queryR); + } + + E leftResult = this.query(leftTreeIndex, l, mid, queryL, mid); + E rightResult = this.query(rightTreeIndex, mid + 1, r, mid + 1, queryR); + return this.merger.merge(leftResult, rightResult); + } + + // 在treeIndex为根的线段树中更新index的值为e + private void set(int treeIndex, int l, int r, int index, E e) { + if (l == r) { + this.tree[treeIndex] = e; + return; + } + + int mid = l + (r - l) / 2; + int leftTreeIndex = this.leftChild(treeIndex); + int rightTreeIndex = this.rightChild(treeIndex); + + if (index >= mid + 1) { + this.set(rightTreeIndex, mid + 1, r, index, e); + } else { + this.set(leftTreeIndex, l, mid, index, e); + } + + this.tree[treeIndex] = this.merger.merge(this.tree[leftTreeIndex], this.tree[rightTreeIndex]); + } + + /** + * 更新 + * + * @param index + * @param E + */ + public void set(int index, E e) { + if (index < 0 || index >= this.data.length) { + throw new IllegalArgumentException("非法索引"); + } + this.data[index] = e; + this.set(0, 0, this.data.length - 1, index, e); + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("["); + for (int x = 0; x < this.tree.length; x++) { + stringBuilder.append(this.tree[x]); + if (x != (this.tree.length - 1)) { + stringBuilder.append(","); + } + } + stringBuilder.append("]"); + return stringBuilder.toString(); + } +} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\344\270\211\347\247\215\345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\346\214\207\345\256\232\345\200\274\350\212\202\347\202\271\347\232\204\346\226\271\346\263\225.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\344\270\211\347\247\215\345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\346\214\207\345\256\232\345\200\274\350\212\202\347\202\271\347\232\204\346\226\271\346\263\225.java" new file mode 100644 index 00000000..f9397102 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\344\270\211\347\247\215\345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\346\214\207\345\256\232\345\200\274\350\212\202\347\202\271\347\232\204\346\226\271\346\263\225.java" @@ -0,0 +1,70 @@ +class ListNode { + int val; + ListNode next; + + ListNode(int val) { + this.val = val; + } +} + +/** + * + * 删除节点中的指定值的所有元素 + * + * @author KevinBlandy + * + */ +public class Solution01 { + + // 普通实现 + public ListNode removeElement(ListNode head, int val) { + while (head != null && head.val == val) { + // 处理头节点 + ListNode delNode = head; + head = head.next; + delNode.next = null; + } + if (head == null) { + return head; + } + ListNode prev = head; + while (prev.next != null) { + // 处理中间元素 + if (prev.next.val == val) { + prev.next = prev.next.next; + } else { + prev = prev.next; + } + } + + return head; //返回的是移除节点后的头节点 + } + + // 使用虚拟头节点,解决方式更加的简洁 + public ListNode removeElement1(ListNode head, int val) { + // 创建虚拟头节点 + ListNode dummyHead = new ListNode(-1); + dummyHead.next = head; + + ListNode prev = dummyHead; + while (prev.next != null) { + // 处理中间元素 + if (prev.next.val == val) { + prev.next = prev.next.next; + } else { + prev = prev.next; + } + } + + return dummyHead.next; //返回的是移除节点后的头节点 + } + + // 使用递归实现 + public ListNode removeElement2(ListNode head, int val) { + if (head == null) { + return null; + } + head.next = removeElement2(head.next, val); + return head.val == val ? head.next : head; + } +} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\344\274\230\345\205\210\351\230\237\345\210\227.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\344\274\230\345\205\210\351\230\237\345\210\227.java" new file mode 100644 index 00000000..b5ac6485 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\344\274\230\345\205\210\351\230\237\345\210\227.java" @@ -0,0 +1,61 @@ +---------------------------- +优先队列 | +---------------------------- + # 普通队列:先进先出,后进后出 + # 优先队列 + * 出队顺序和入队顺序无关 + * 和优先级相关 + + + # 底层可以使用堆来实现 + + +---------------------------- +基于最大堆实现 | +---------------------------- +/** + * + * 优先队列 + * @author KevinBlandy + * + * @param + */ +public class PriorityQueue> { + + private MaxHeap maxHeap; + + public PriorityQueue() { + this.maxHeap = new MaxHeap<>(); + } + + public int size() { + return this.maxHeap.size(); + } + + public boolean empty() { + return this.maxHeap.empty(); + } + + /** + * 查看队首元素 + */ + public E getFront() { + return this.maxHeap.findMax(); + } + + /** + * 入队 + * 由低层的最大堆自己维护堆性质 + */ + public void enqueue(E e) { + this.maxHeap.add(e); + } + + /** + * 出队 + * 由低层的最大堆维护自己的堆性质 + */ + public E dequeue() { + return this.maxHeap.extractMax(); + } +} \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\215\225\345\220\221\351\223\276\350\241\250.c" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\215\225\345\220\221\351\223\276\350\241\250.c" new file mode 100644 index 00000000..66dc598f --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\215\225\345\220\221\351\223\276\350\241\250.c" @@ -0,0 +1,205 @@ +/** + * + * 链表 + * + */ +#include +#include +#include + +struct Node { + void *value; + struct Node *next; +}; + +struct LinkedList { + int size; + struct Node *head; +}; + +typedef struct LinkedList * list_t; + +int init(list_t); /** 初始化链表 **/ + +int add(list_t, int, void *);/** 添加元素到指定下标 **/ +int addFirst(list_t, void *);/** 添加元素到首部 **/ +int addLast(list_t, void *);/** 添加元素到尾部 **/ + +void * get(list_t, int);/** 获取指定下标的元素 **/ +void * getFirst(list_t);/** 获取首部元素 **/ +void * getLast(list_t);/** 获取尾部元素 **/ + +int set(list_t, int, void *);/** 修改指定下标的值 **/ +bool contains(list_t, void *, bool (*)(void *, void *));/** 判断是否包含指定的值 **/ + +void * removeIndex(list_t, int);/** 删除指定下标的值 **/ +void * removeFirst(list_t);/** 删除首节点 **/ +void * removeLast(list_t);/** 删除尾节点 **/ + +void consumer(list_t, void (*)(void *));/** 消费接口 **/ + +int init(list_t list) { + struct Node * node = (struct Node *) calloc(sizeof(struct Node), 1); + if (node == NULL) { + return 1; + } + node->next = NULL; + node->value = NULL; + + list->head = node; + list->size = 0; + return 0; +} + +int add(list_t list, int index, void *value) { + if (index < 0 || index > list->size) { + return -1; + } + struct Node * pre = list->head; + for (int i = 0; i < index; i++) { + pre = pre->next; + } + struct Node * node = calloc(sizeof(struct Node), 1); + if (node == NULL) { + return -1; + } + + node->value = value; + node->next = pre->next; + + pre->next = node; + list->size++; + return 0; +} +int addFirst(list_t list, void *value) { + return add(list, 0, value); +} +int addLast(list_t list, void *value) { + return add(list, list->size, value); +} + +void * get(list_t list, int index) { + printf("执行"); + if (index < 0 || index >= list->size) { + return NULL; + } + struct Node * cur = list->head->next; + for (int x = 0; x < index; x++) { + cur = cur->next; + } + return cur->value; +} +void * getFirst(list_t list) { + return get(list, 0); +} +void * getLast(list_t list) { + return get(list, list->size - 1); +} + +int set(list_t list, int index, void *value) { + if (index < 0 || index >= list->size) { + return -1; + } + struct Node * cur = list->head->next; + for (int x = 0; x < index; x++) { + cur = cur->next; + } + cur->value = value; + return 0; +} + +bool contains(list_t list, void *value, bool (*equals)(void *, void *)) { + struct Node * cur = list->head->next; + while (cur != NULL) { + if (equals(cur->value, value)) { + return true; + } + cur = cur->next; + } + return false; +} + +void * removeIndex(list_t list, int index) { + if (index < 0 || index >= list->size) { + return NULL; + } + struct Node * pre = list->head; + for (int x = 0; x < index; x++) { + pre = pre->next; + } + + struct Node * deleteNode = pre->next; + pre->next = deleteNode->next; + + void * ret = deleteNode->value; + free(deleteNode); + list->size--; + return ret; +} +void * removeFirst(list_t list) { + return removeIndex(list, 0); +} +void * removeLast(list_t list) { + return removeIndex(list, list->size - 1); +} + +void consumer(list_t list, void (*accept)(void *)) { + struct Node * cur = list->head->next; + printf("start ==========================\n"); + printf("size=%d \n", list->size); + while (cur != NULL) { + accept(cur->value); + cur = cur->next; + } + printf("end ==========================\n"); +} + +//static +static void printValue(void *value) { + int *p = (int *) value; + printf("%d\n", *p); +} +static bool equals(void *var1, void *var2) { + if (*((int *) var1) == *((int *) var2)) { + return true; + } + return false; +} + +//main test +int main(int argc, char **argv) { + struct LinkedList linkedList; + list_t list = &linkedList; + init(list); + + int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + for (int x = 0; x < 10; x++) { + if (x % 2 == 0) { + addFirst(list, &arr[x]); + } else { + addLast(list, &arr[x]); + } + } + consumer(list, &printValue); + + printf("首元素:%d 尾元素:%d \n", *((int *) getFirst(list)), + *((int *) getLast(list))); + removeFirst(list); + removeLast(list); + consumer(list, &printValue); + + int x = 99; + printf("是否包含0:%d\n", contains(list, &x, &equals)); + set(list, 0, &x); + consumer(list, &printValue); + + removeIndex(list, 3); + consumer(list, &printValue); + + removeFirst(list); + removeLast(list); + consumer(list, &printValue); + return EXIT_SUCCESS; +} + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\257\346\211\251\345\256\271\347\232\204\346\225\260\347\273\204.c" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\257\346\211\251\345\256\271\347\232\204\346\225\260\347\273\204.c" new file mode 100644 index 00000000..55c7e18a --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\257\346\211\251\345\256\271\347\232\204\346\225\260\347\273\204.c" @@ -0,0 +1,114 @@ +#include + +struct IntArray { + int size; //数据长度 + int capacity; //容器长度 + int * arr; //首元素指针 +}; + +int init(struct IntArray *, int); /** 初始化 **/ +int addFirst(struct IntArray *,int); /** 添加元素到开头 **/ +int addLast(struct IntArray *,int); /** 添加元素到末尾 **/ +int add(struct IntArray *, int, int); /** 添加元素到指定的位置 **/ +int set(struct IntArray *, int, int); /** 设置元素到指定的位置 **/ +void forEach(struct IntArray *); /** 遍历,打印元素 **/ +int indexOf(struct IntArray *,int); /** 获取指定元素第一次出现的下标 **/ +int removeIndex(struct IntArray *, int);/** 根据下标删除元素 **/ +void removeItem(struct IntArray *, int);/** 删除第一个指定值的元素 **/ +int removeFirst(struct IntArray *); /** 删除第一个元素 **/ +int removeLast(struct IntArray *); /** 删除最后一个元素 **/ + + +int init(struct IntArray *arr, int capacity) { + if(capacity <= 0){ + return -1; + } + arr->capacity = capacity; + arr->size = 0; + arr->arr = (int *) calloc(sizeof(int), arr->capacity); + return 0; +} + +int addFirst(struct IntArray *arr,int value){ + return add(arr, 0, value); +} + +int addLast(struct IntArray *arr,int value){ + return add(arr, arr->size, value); +} + +int add(struct IntArray *arr, int index, int value) { + if (index < 0 || index > arr->size) { + return -1; + } + if (arr->size == arr->capacity) { + arr->capacity = arr->capacity * 2; + realloc(arr->arr, arr->capacity); + } + + for (int i = arr->size - 1; i >= index; i--) { + arr->arr[i + 1] = arr->arr[i]; + } + arr->arr[index] = value; + arr->size++; + return 0; +} + +int set(struct IntArray *arr, int index, int value) { + if (index < 0 || index >= arr->size) { + return 1; + } + arr->arr[index] = value; + return 0; +} + +int indexOf(struct IntArray *arr,int value){ + for (int i = 0; i < arr->size; i++) { + if(arr->arr[i] == value){ + return i; + } + } + return -1; +} + +int removeIndex(struct IntArray *arr, int index) { + if (index < 0 || index >= arr->size) { + return -1; + } + int value = arr->arr[index]; + for (int i = index + 1; i < arr->size; i++) { + arr->arr[i - 1] = arr->arr[i]; + } + arr->size--; + return value; +} + +int removeFirst(struct IntArray *arr){ + return removeIndex(arr, 0); +} +int removeLast(struct IntArray *arr){ + return removeIndex(arr, arr->size - 1); +} + +void removeItem(struct IntArray *arr, int value) { + int index = indexOf(arr, value); + if(index != -1){ + removeIndex(arr, index); + } +} + +void forEach(struct IntArray *arr) { + printf("size=%d capacity=%d [", arr->size, arr->capacity); + for (int i = 0; i < arr->size; i++) { + printf("%d", arr->arr[i]); + if (i != (arr->size - 1)) { + printf(","); + } + } + printf("]\n"); +} + + + + + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\257\346\211\251\345\256\271\347\232\204\346\225\260\347\273\204.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\257\346\211\251\345\256\271\347\232\204\346\225\260\347\273\204.java" new file mode 100644 index 00000000..5aa7127c --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\257\346\211\251\345\256\271\347\232\204\346\225\260\347\273\204.java" @@ -0,0 +1,152 @@ +package io.javaweb.datastructure; + +import java.util.Arrays; +import java.util.function.Consumer; + +public class Array { + + private E[] arr = null; + + private int size; + + public Array() { + this(10); + } + + @SuppressWarnings("unchecked") + public Array(int capacity) { + this.arr = (E[]) new Object[capacity]; + this.size = 0; + } + + public int getCapacity() { + return this.arr.length; + } + + public E get(int index) { + if (index < 0 || index >= this.size) { + throw new IllegalArgumentException("index 不能大于" + (this.size - 1) + ",不能小于0"); + } + return this.arr[index]; + } + + public boolean contains(E item) { + for (E e : this.arr) { + if (e.equals(item)) { + return true; + } + } + return false; + } + + public int indexOf(E item) { + for (int i = 0; i < this.size; i++) { + if (this.arr[i].equals(item)) { + return i; + } + } + return -1; + } + + public void set(int index, E item) { + if (index < 0 || index >= size) { + throw new IllegalArgumentException("index 不能大于" + (this.size - 1) + ",不能小于0"); + } + this.arr[index] = item; + } + + public E remove(int index) { + if (index < 0 || index >= this.size) { + throw new IllegalArgumentException("index 不能大于" + (this.size - 1) + ",不能小于0"); + } + E e = this.arr[index]; + for (int i = index + 1; i < size; i++) { + this.arr[i - 1] = this.arr[i]; + } + this.size--; + return e; + } + + public E removeFirst() { + return this.remove(0); + } + + public E removeLast() { + return this.remove(this.size - 1); + } + + public void removeElement(E item) { + int index = this.indexOf(item); + if (index != -1) { + this.remove(index); + } + } + + public int size() { + return this.size; + } + + public void addFirst(E item) { + this.add(0, item); + } + + public void addLast(E item) { + this.add(this.size, item); + } + + //添加元素到指定的位置 + public void add(int index, E item) { + if (index < 0 || index > this.size) { + throw new IllegalArgumentException("index 不能大于" + (this.size - 1) + ",不能小于0"); + } + if (this.size == this.arr.length) { + //throw new IllegalArgumentException("数组满了"); + // 扩容 + this.resize(this.arr.length * 2); + } + for (int i = size - 1; i >= index; i--) { + this.arr[i + 1] = this.arr[i]; + } + + this.arr[index] = item; + this.size++; + } + + public boolean isEmpty() { + return this.size == 0; + } + + public void consumer(Consumer consumer) { + if (this.size > 0) { + for (E e : this.arr) { + consumer.accept(e); + } + } + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(String.format("Array: size = %d , capacity = %d\n", size, this.arr.length)); + stringBuilder.append('['); + for (int i = 0; i < size; i++) { + stringBuilder.append(this.arr[i]); + if (i != size - 1) { + stringBuilder.append(", "); + } + } + stringBuilder.append(']'); + return stringBuilder.toString(); + } + + //执行扩容 + private void resize(int newCapacity) { + this.arr = Arrays.copyOf(this.arr, newCapacity); +// @SuppressWarnings("unchecked") +// E[] newArr = (E[]) new Object[newCapacity]; +// for (int i = 0; i < size; i++) { +// newArr[i] = this.arr[i]; +// } +// this.arr = newArr; + } +} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\270\246\350\231\232\346\213\237\345\244\264\350\212\202\347\202\271\347\232\204\345\215\225\345\220\221\351\223\276\350\241\250.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\270\246\350\231\232\346\213\237\345\244\264\350\212\202\347\202\271\347\232\204\345\215\225\345\220\221\351\223\276\350\241\250.java" new file mode 100644 index 00000000..33cb9bb5 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\270\246\350\231\232\346\213\237\345\244\264\350\212\202\347\202\271\347\232\204\345\215\225\345\220\221\351\223\276\350\241\250.java" @@ -0,0 +1,152 @@ + +public class LinkedList { + // Node + private class Node { + public E e; + public Node next; + + public Node(E e, Node next) { + this.e = e; + this.next = next; + } + + public Node(E e) { + this(e, null); + } + + @Override + public String toString() { + return this.e.toString(); + // return "Node [e=" + e + ", next=" + next + "]"; + } + } + + // 虚拟头节点 + private Node dummyHead; + + private int size; + + public LinkedList() { + this.dummyHead = new Node(null, null); + this.size = 0; + } + + public int size() { + return this.size; + } + + public boolean isEmpty() { + return this.size == 0; + } + + public void addFirst(E e) { + this.add(0, e); + } + + public void addLast(E e) { + this.add(this.size, e); + } + + public void add(int index, E e) { + if (index < 0 || index > this.size) { + throw new IllegalArgumentException("index 必须大于等于0,小余等于" + this.size); + } + Node prev = dummyHead; + for (int i = 0; i < index; i++) { + prev = prev.next; + } + Node node = new Node(e); + node.next = prev.next; + // prev.next = new Node(e,prev.next); + prev.next = node; + this.size++; + } + + public E get(int index) { + if (index < 0 || index >= this.size) { + throw new IllegalArgumentException("index 必须大于等于0,小余" + this.size); + } + Node cur = this.dummyHead.next; + for (int i = 0; i < index; i++) { + cur = cur.next; + } + return cur.e; + } + + public E getFirst() { + return this.get(0); + } + + public E getLast() { + return this.get(this.size - 1); + } + + public void set(int index, E e) { + if (index < 0 || index >= this.size) { + throw new IllegalArgumentException("index 必须大于等于0,小余" + this.size); + } + Node cur = this.dummyHead.next; + for (int i = 0; i < index; i++) { + cur = cur.next; + } + cur.e = e; + } + + public boolean contains(E e) { + if (this.size > 0) { + Node cur = this.dummyHead.next; + while (cur != null) { + if (cur.e.equals(e)) { + return true; + } + cur = cur.next; + } + } + return false; + } + + public E removeFirst() { + return this.remove(0); + } + + public E removeLast() { + return this.remove(this.size - 1); + } + + public E remove(int index) { + if (index < 0 || index >= this.size) { + throw new IllegalArgumentException("index 必须大于等于0,小余" + this.size); + } + Node prev = dummyHead; + for (int i = 0; i < index; i++) { + prev = prev.next; + } + Node deleteNode = prev.next; + prev.next = deleteNode.next; + deleteNode.next = null; + this.size--; + return deleteNode.e; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + Node cur = this.dummyHead.next; + while (cur != null) { + stringBuilder.append(cur + "->"); + cur = cur.next; + } + stringBuilder.append("NULL"); + return stringBuilder.toString(); + } + + public static void main(String[] args) { + LinkedList list = new LinkedList<>(); + for (int x = 0; x < 5; x++) { + list.addFirst(x); + } + list.add(2, 666); + list.remove(2); + System.out.println(list); + } +} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\277\344\271\211\351\230\237\345\210\227.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\271\277\344\271\211\351\230\237\345\210\227.java" new file mode 100644 index 00000000..e69de29b diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\345\256\236\347\216\260\345\276\252\347\216\257\351\230\237\345\210\227.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\345\256\236\347\216\260\345\276\252\347\216\257\351\230\237\345\210\227.java" new file mode 100644 index 00000000..fabe7101 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\345\256\236\347\216\260\345\276\252\347\216\257\351\230\237\345\210\227.java" @@ -0,0 +1,92 @@ + +----------------------- +循环队列 | +----------------------- + # 底层使用数组 + # 但是性能比较好,不会有数组的'位移'操作,通过两个指针交替位移来读写数据 + + +----------------------- +实现Demo | +----------------------- +@SuppressWarnings("unchecked") +public class LoopQueue { + private E[] array; + private int front; // 头指针 + private int tail; // 尾指针 + private int size; + + public LoopQueue(int capacity) { + this.array = (E[]) new Object[capacity + 1]; + this.front = 0; + this.tail = 0; + this.size = 0; + } + + public int capacity() { + return this.array.length - 1; + } + + public int size() { + return this.size; + } + + public boolean isEmpty() { + return this.front == this.tail; + } + + public void enqueue(E e) { + if ((this.tail + 1) % this.array.length == this.front) { + this.resize(this.capacity() * 2); + } + this.array[this.tail] = e; + this.tail = (this.tail + 1) % this.array.length; + this.size++; + } + + public E dequeue() { + if (this.isEmpty()) { + throw new IllegalArgumentException("empty queue."); + } + E value = this.array[front]; + this.array[front] = null; + this.front = (this.front + 1) % this.array.length; + this.size--; + if (this.size == this.capacity() / 4 && this.capacity() / 2 != 0) { + this.resize(this.capacity() / 2); + } + return value; + } + + public E front() { + if (this.isEmpty()) { + throw new IllegalArgumentException("empty queue."); + } + return this.array[this.front]; + } + + private void resize(int capacity) { + E[] newArray = (E[]) new Object[capacity + 1]; + for (int i = 0; i < this.size; i++) { + newArray[i] = this.array[(i + this.front) % this.array.length]; + } + this.array = newArray; + this.front = 0; + this.tail = this.size; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(String.format("Queue: size = %d , capacity = %d\n", size, this.capacity())); + stringBuilder.append("front ["); + for (int i = front; i != tail; i = (i + 1) % this.array.length) { + stringBuilder.append(this.array[i]); + if ((i + 1) % this.array.length != tail) { + stringBuilder.append(", "); + } + } + stringBuilder.append("] tail"); + return stringBuilder.toString(); + } +} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\351\230\237\345\210\227.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\351\230\237\345\210\227.java" new file mode 100644 index 00000000..0ee4fd36 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\351\230\237\345\210\227.java" @@ -0,0 +1,6 @@ +----------------------- +数组队列 | +----------------------- + # 底层使用数组,太简单,不写了 + # LinkedList 底层使用的是 链表 + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210.py" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210.py" new file mode 100644 index 00000000..b155309a --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210.py" @@ -0,0 +1,29 @@ + +# 先进后出,后进先出 + * Lst In First Out(LIFO) + + +# Python + + # 数组实现 + class Stack(): + + def __init__(self): + self.__arr = [] + + def push(self, item): + self.__arr.append(item); + + def pop(self): + return self.__arr.pop() + + def peek(self): + return self.__arr[0] if len(self.__arr) > 0 else None + + @property + def size(self): + return len(self.__arr) + + @property + def empty(self): + return self.size == 0 \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\345\270\246\346\234\211\345\260\276\346\214\207\351\222\210\347\232\204\351\230\237\345\210\227.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\345\270\246\346\234\211\345\260\276\346\214\207\351\222\210\347\232\204\351\230\237\345\210\227.java" new file mode 100644 index 00000000..63f9f4b0 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\345\256\236\347\216\260\347\232\204\345\270\246\346\234\211\345\260\276\346\214\207\351\222\210\347\232\204\351\230\237\345\210\227.java" @@ -0,0 +1,92 @@ +--------------------------------------------- +基于链表实现的带有尾指针的队列 | +--------------------------------------------- + # 出队列,时间复杂度减少了,不需要遍历,因为存在指针 + + +public class LinkedListQueue { + + private class Node { + public E e; + public Node next; + + public Node(E e, Node next) { + this.e = e; + this.next = next; + } + + public Node(E e) { + this(e, null); + } + + @Override + public String toString() { + return this.e.toString(); + } + } + + private Node head; //头指针 + private Node tail; //尾指针 + private int size; + + public LinkedListQueue() { + this.head = null; + this.tail = null; + this.size = 0; + } + + public int size() { + return this.size; + } + + public boolean isEmpty() { + return this.size == 0; + } + + public void equeue(E e) { + if (this.tail == null) { + this.tail = new Node(e); + this.head = this.tail; + } else { + this.tail.next = new Node(e); + this.tail = this.tail.next; + } + this.size++; + } + + public E dequeue() { + if (this.isEmpty()) { + throw new IllegalArgumentException("空的queue"); + } + Node retNode = this.head; + this.head = this.head.next; + retNode.next = null; + if (this.head == null) { + this.tail = null; + } + this.size--; + return retNode.e; + } + + public E front() { + if (this.isEmpty()) { + throw new IllegalArgumentException("空的queue"); + } + return this.head.e; + } + + @Override + public String toString() { + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append("Queue front "); + + Node cur = head; + while (cur != null) { + stringBuffer.append(cur + " -> "); + cur = cur.next; + } + + stringBuffer.append("NULL tail"); + return stringBuffer.toString(); + } +} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\345\222\214\346\230\240\345\260\204/\346\230\240\345\260\204.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\345\222\214\346\230\240\345\260\204/\346\230\240\345\260\204.java" new file mode 100644 index 00000000..25c1d8fd --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\345\222\214\346\230\240\345\260\204/\346\230\240\345\260\204.java" @@ -0,0 +1,310 @@ +-------------------- +映射 | +-------------------- + # 其实就是 Map + # 可以使用链表或者二分搜索树实现 + class Node{ + K key; + V value; + Node next; + } + class Node{ + K key; + V value; + Node left; + Node right; + } + + # 基本的方法 + add(k,v); + v remove(k); + boolean contains(k); + v get(k); + void set(k,v); + int size(); + boolean isEmpty(); + + +-------------------- +基于链表实现的Map | +-------------------- +import java.util.function.BiConsumer; + +public class LinkedMap { + private class Node { + private K key; + private V value; + private Node next; + + public Node(K key, V value, LinkedMap.Node next) { + super(); + this.key = key; + this.value = value; + this.next = next; + } + + public Node() { + this(null, null, null); + } + + @Override + public String toString() { + return "Node [key=" + key + ", value=" + value + ", next=" + next + "]"; + } + } + + private int size; + + private Node dummyHead; + + public LinkedMap() { + this.dummyHead = new Node(); + this.size = 0; + } + + public int size() { + return this.size; + } + + public boolean empty() { + return this.size == 0; + } + + private Node getNode(K key) { + Node node = this.dummyHead.next; + while (node != null) { + if (node.key.equals(key)) { + return node; + } + node = node.next; + } + return null; + } + + public boolean contains(K key) { + return this.getNode(key) != null; + } + + public V get(K key) { + Node node = this.getNode(key); + return node == null ? null : node.value; + } + + public void add(K key, V value) { + Node node = this.getNode(key); + if (node != null) { + // 覆盖 + node.value = value; + } else { + // 直接插入到头节点 + this.dummyHead.next = new Node(key, value, this.dummyHead.next); + this.size++; + } + } + + public void set(K key, V value) { + Node node = this.getNode(key); + if (node != null) { + node.value = value; + } + throw new IllegalArgumentException("key \"" + key + "\"not found"); + } + + public V remove(K key) { + Node pre = this.dummyHead; + while (pre.next != null) { + if (pre.next.key.equals(key)) {// 获取到要删除节点的上一个节点 + break; + } + pre = pre.next; + } + if (pre.next != null) { + Node delNode = pre.next; + pre.next = delNode.next; + this.size--; + return delNode.value; + } + // 遍历到最后也没找到要删除的节点 + throw new IllegalArgumentException("key \"" + key + "\"not found"); + } + + public void forEach(BiConsumer consumer) { + Node node = this.dummyHead; + while (node.next != null) { + consumer.accept(node.next.key, node.next.value); + node = node.next; + } + } +} + + +------------------------ +基于二分搜索树实现的Map | +------------------------ + + +import java.util.function.BiConsumer; + +public class BinarySearchTreeMap, V> { + private class Node { + K key; + V value; + private Node left; + private Node right; + + public Node(K key, V value, Node left, Node right) { + super(); + this.key = key; + this.value = value; + this.left = left; + this.right = right; + } + + public Node(K key, V value) { + this(key, value, null, null); + } + } + + private Node root; + + private int size; + + public int size() { + return this.size; + } + + public boolean empty() { + return this.size == 0; + } + + private Node getNode(Node node, K key) { + if (node == null) { + return node; + } + int result = node.key.compareTo(key); + if (result > 0) { + return this.getNode(node.left, key); + } else if (result < 0) { + return this.getNode(node.right, key); + } else { + return node; + } + } + + public boolean contains(K key) { + return this.getNode(this.root, key) != null; + } + + public V get(K key) { + Node node = this.getNode(this.root, key); + return node == null ? null : node.value; + } + + public V set(K key, V value) { + Node node = this.getNode(this.root, key); + if (node == null) { + throw new IllegalArgumentException("not found key"); + } + V retVallue = node.value; + node.value = value; + return retVallue; + } + + private Node add(Node node, K key, V value) { + if (node == null) { + this.size++; + return new Node(key, value); + } + int result = node.key.compareTo(key); + + if (result > 0) { + node.left = this.add(node.left, key, value); + } else if (result < 0) { + node.right = this.add(node.right, key, value); + } else { + node.value = value; // 覆盖 + } + return node; + } + + public void add(K key, V value) { + this.root = this.add(this.root, key, value); + } + + protected Node minNode(Node node) { + if (node.left == null) { + return node; + } + return this.minNode(node.left); + } + + public Node removeMin(Node node) { + if (node.left == null) { + Node rightNode = node.right; + node.right = null; + this.size--; + return rightNode; + } + node.left = removeMin(node.left); + return node; + } + + protected Node remove(Node node, K key) { + if (node == null) { + return node; + } + int result = node.key.compareTo(key); + if (result > 0) {// left + node.left = remove(node.left, key); + return node; + } else if (result < 0) { // right + node.right = remove(node.right, key); + return node; + } else { + // 删除节点左子树为null + if (node.left == null) { + Node rightNode = node.right; + node.right = null; + this.size--; + return rightNode; + } + // 删除节点右子树为null + if (node.right == null) { + Node leftNode = node.left; + node.left = null; + this.size--; + return leftNode; + } + // 左右节点都不为空 + + // 获取到右边最小的值的节点 + Node successor = minNode(node.right); + successor.right = removeMin(node); + successor.left = node.left; + node.left = node.right = null; + return successor; + } + } + + public V remove(K key) { + Node node = this.getNode(this.root, key); + if (node != null) { + this.root = remove(this.root, key); + return node.value; + } + throw new IllegalArgumentException("not found key"); + } + + private void forEach(Node node, BiConsumer biConsumer) { + if (node == null) { + return; + } + this.forEach(node.left, biConsumer); + biConsumer.accept(node.key, node.value); + this.forEach(node.right, biConsumer); + } + + public void forEach(BiConsumer biConsumer) { + this.forEach(this.root, biConsumer); + } +} diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\345\222\214\346\230\240\345\260\204/\351\233\206\345\220\210.java" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\345\222\214\346\230\240\345\260\204/\351\233\206\345\220\210.java" new file mode 100644 index 00000000..224069f1 --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\345\222\214\346\230\240\345\260\204/\351\233\206\345\220\210.java" @@ -0,0 +1,31 @@ +---------------------------- +集合 | +---------------------------- + # 元素不能重复 + # 接口方法 + add(); + remove(); + contains(); + size(); + isEmpty(); + + # 底层可以是二分搜索树或者链表实现 + * 二分搜索树作为底层实现太简单了,几乎API都不用咋修改,这里不写 + + # 链表的实现,其实就是在add方法添加判断是否已经存在的逻辑 + @Override + public boolean add(E e) { + if(this.contains(e)) { + //如果已经包含,则不添加 + return false; + } + return this.add(e); + } + + # 有序与无序 + * 基于红黑树树实现的集合是有序的 + * 基于Hash表实现的集合非有序性 + + # 多重集合 + * 允许元素重复 + * 其实也比较简单,在允许重复的二分搜索树上再次封装一层即可 diff --git "a/\346\234\211\347\224\250\347\232\204\346\226\271\346\263\225.java" "b/\346\234\211\347\224\250\347\232\204\346\226\271\346\263\225.java" index 97fe7d1d..6a6eb3a1 100644 --- "a/\346\234\211\347\224\250\347\232\204\346\226\271\346\263\225.java" +++ "b/\346\234\211\347\224\250\347\232\204\346\226\271\346\263\225.java" @@ -51,6 +51,26 @@ private byte[] hexStringToBytes(String hexString) { private byte charToByte(char cha) { return (byte) "0123456789ABCDEF".indexOf(cha); } + +------------------------------------------ +int 和字节数组的转换(用于网络传输) | +------------------------------------------ + public static int byteArrayToInt(byte[] b) { + return b[3] & 0xFF | + (b[2] & 0xFF) << 8 | + (b[1] & 0xFF) << 16 | + (b[0] & 0xFF) << 24; + } + + public static byte[] intToByteArray(int a) { + return new byte[]{ + (byte) ((a >> 24) & 0xFF), + (byte) ((a >> 16) & 0xFF), + (byte) ((a >> 8) & 0xFF), + (byte) (a & 0xFF) + }; + } + ------------------------------------------ 获取客户端IP | ------------------------------------------ @@ -175,4 +195,102 @@ private static String encodeHex(byte[] bytes) { buffer.append(Long.toString((int) bytes[i] & 0xff, 16)); } return buffer.toString(); -} \ No newline at end of file +} + + +------------------------------------------ +对字符串进行升序排序 | +------------------------------------------ + public static final Collator collator = Collator.getInstance(java.util.Locale.CHINA); + + //对字符串进行升序排序 + public static void sortedChinese(List collection) { + + collection.sort(collator::compare); + +// collection.sort((s1,s2) -> { +// return collator.compare(s1, s2); +// }); + +// collection.sort(new Comparator() { +// @Override +// public int compare(String o1, String o2) { +// return collator.compare(o1, o2); +// } +// }); + } + + + public static void main(String[] args) throws Exception { + + List list = new ArrayList<>(); + + list.add("p2"); + list.add("p1"); + list.add("method"); + list.add("pn"); + + sortedChinese(list); + + for (String i : list) { + System.out.println(i); + } + } + +------------------------------------------ +获取异常的堆栈信息 | +------------------------------------------ + + public static String getStackTrace(Throwable t) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + try { + t.printStackTrace(pw); + return sw.toString(); + } finally { + pw.close(); + } + } + +------------------------------------------ +校验用户名是否合法 | +------------------------------------------ + /** + * 校验用户名是否合法 + 只能是数字 汉字 英文 下划线 + 1 - 14长度 + 一个汉字占2个长度 + 不能为纯下划线 + 不能是纯数字 + * @param name + * @return + */ + public boolean nameValidate(String name) { + return name.replaceAll("[\\u4e00-\\u9fa5]", "aa").matches("^(?!\\d+$)(?!_+$)\\w{1,14}$"); + } + + + function checkUserName(name){ + return /^(?!\d+$)(?!_+$)\w{1,14}$/.test(name.replace(/[\u4e00-\u9fa5]/g,"aa")); + } + +------------------------------------------ +重用 InputStream 流 | +------------------------------------------ +//获取流 +InputStream inputStream = Files.newInputStream(Paths.get("E:\\404.jpg")); +//在内存定义输出流 +ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); +byte[] buffer = new byte[1024]; +int length = 0; +while((length = inputStream.read(buffer)) != -1) { + //把需要可重用的流,写入到内存输出流 + byteArrayOutputStream.write(buffer,0,length); + byteArrayOutputStream.flush(); +} +inputStream.close(); +//根据内存输出流创建N多个内存输入流,从而实现流的重用 +ByteArrayInputStream byteArrayInputStream1 = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); +ByteArrayInputStream byteArrayInputStream2 = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); +System.out.println(byteArrayInputStream1.available()); //105335 +System.out.println(byteArrayInputStream2.available()); //105335 \ No newline at end of file diff --git "a/\346\240\221\350\216\223\346\264\276/raspberry-picamera.py" "b/\346\240\221\350\216\223\346\264\276/raspberry-picamera.py" new file mode 100644 index 00000000..ca24c460 --- /dev/null +++ "b/\346\240\221\350\216\223\346\264\276/raspberry-picamera.py" @@ -0,0 +1,8 @@ +---------------------------- +picamera | +---------------------------- + # 安装 + apt-get install python-picamera python3-picamera -y + pip install picamera + + \ No newline at end of file diff --git "a/\346\240\221\350\216\223\346\264\276/raspberry-python.py" "b/\346\240\221\350\216\223\346\264\276/raspberry-python.py" new file mode 100644 index 00000000..fbd3e547 --- /dev/null +++ "b/\346\240\221\350\216\223\346\264\276/raspberry-python.py" @@ -0,0 +1,46 @@ +------------------------ +Python3的安装 | +------------------------ + # 依赖安装 + apt-get update + apt-get upgrade + apt-get dist-upgrade + apt-get install build-essential python-dev python-setuptools python-pip python-smbus -y + apt-get install build-essential libncursesw5-dev libgdbm-dev libc6-dev -y + apt-get install zlib1g-dev libsqlite3-dev tk-dev -y + apt-get install libssl-dev openssl -y + apt-get install libffi-dev -y + + # 安装 + 1,下载 + https://www.python.org/ftp/python/ + + 2,解压,创建文件夹 + tar -zxvf Python-3.7.1.tgz + mkdir /usr/local/python + + 3,安装依赖 + yum -y install zlib + yum -y install zlib-devel + yum install -y libffi-devel + yum install -y openssl-devel + + 4,进入解压目录,执行编译 + ./configure --prefix=/usr/local/python + + 5,编译ok后,执行安装 + make && make install + + 4,创建软连接 + ln -s /usr/local/python/bin/python3 /usr/bin/python3 + ln -s /usr/local/python/bin/pip3 /usr/bin/pip3 + + 5,测试 + python3 -V + + # 如果执行pip3安装依赖出现问题 + "return Command 'lsb_release -a' returned non-zero exit status 1..." + * 删除lsb_release文件可解决此问题 + * rm /usr/bin/lsb_release + + diff --git "a/\346\240\221\350\216\223\346\264\276/raspberry-wifi.py" "b/\346\240\221\350\216\223\346\264\276/raspberry-wifi.py" new file mode 100644 index 00000000..047465bb --- /dev/null +++ "b/\346\240\221\350\216\223\346\264\276/raspberry-wifi.py" @@ -0,0 +1,4 @@ +---------------------------- +3B板载wifi | +---------------------------- + # https://blog.csdn.net/messidona11/article/details/73649278?utm_source=gold_browser_extension \ No newline at end of file diff --git "a/\346\240\221\350\216\223\346\264\276/raspberry-\345\256\211\350\243\205Centos7.py" "b/\346\240\221\350\216\223\346\264\276/raspberry-\345\256\211\350\243\205Centos7.py" new file mode 100644 index 00000000..fbb3eed7 --- /dev/null +++ "b/\346\240\221\350\216\223\346\264\276/raspberry-\345\256\211\350\243\205Centos7.py" @@ -0,0 +1,54 @@ + +# 参考教程 + https://baijiahao.baidu.com/s?id=1591503879926889654&wfr=spider&for=pc + + +# 下载Centos7 + http://isoredirect.centos.org/altarch/7/isos/armhfp/ + * 可以选择华为的镜像 + https://mirrors.huaweicloud.com/centos-altarch/7.5.1804/isos/armhfp/ + + CentOS-Userland-7-armv7hl-RaspberryPI-Minimal-1804-sda.raw.xz + * 基本的核心(服务器) + CentOS-Userland-7-armv7hl-RaspberryPI-GNOME-1804-sda.raw.xz + * GNOME桌面环境 + CentOS-Userland-7-armv7hl-RaspberryPI-KDE-1804-sda.raw.xz + * KDE 桌面环境 + +# 下载 SD Memory Card Formatter 用于格式化SD卡 + * 格式化SD卡 + +# 下载 win32diskimager-1.0.0-install 用于烧录镜像 + * 烧录镜像到已经格式化的SD卡 + +# 插入SDK卡 + +# 链接网线,启动 + * 红灯常亮: 未能检测到TF卡 + * 双灯常亮: 未能检测到系统 + * 红灯常亮,绿灯闪烁: 系统运行正常 + +# 通过路由器查看地址 + +# 通过ssh链接 + * 初始root密码:centos + +# 设置时区 + * 查看 + timedatectl + Time zone: Asia/Shanghai (CST, +0800) + + * 修改时区为上海 + timedatectl set-timezone Asia/Shanghai + +# 扩展空间 + * 树莓派装完系统后,默认认没有把整个存储空间拓展到整张TF卡中,所以SD卡空间利用不充分 + * 在root用户家目录有个 README 文件,里面提供命令的方式自动扩展空间 + /user/bin/rootfs-expand + + +# 安装基本的软件 + yum -y install vim + yum -y install git + yum -y install gcc automake autoconf libtool make + yum -y install wget \ No newline at end of file diff --git "a/\346\240\221\350\216\223\346\264\276/raspberry-\345\256\211\350\243\205\345\256\230\346\226\271\347\263\273\347\273\237.py" "b/\346\240\221\350\216\223\346\264\276/raspberry-\345\256\211\350\243\205\345\256\230\346\226\271\347\263\273\347\273\237.py" new file mode 100644 index 00000000..8930d97f --- /dev/null +++ "b/\346\240\221\350\216\223\346\264\276/raspberry-\345\256\211\350\243\205\345\256\230\346\226\271\347\263\273\347\273\237.py" @@ -0,0 +1,75 @@ + + +# 官方下载 + https://www.raspberrypi.org/downloads/raspbian/ + Raspbian Stretch Lite + Minimal image based on Debian Stretch + - 不带桌面环境的系统 + +# 正常格式化,烧录TF卡 + +# 新系统ssh登录失败 + * 新系统,禁止了ssh服务导致没法登录 + * 用电脑打开tf卡,在根目录新建一个空文件:ssh + * 插入机器,启动,ssh可以连接 + +# 默认账户名密码 + pi + raspberry + +# 开启root账号 + sudo passwd root + * 修改root的密码,重复输入两次 + + sudo passwd --unlock root + * 允许root用户登录 + + su root + * 切换root用户 + + apt-get install -y vim + * 先安装,vim(也可以使用nano命令,但是我不熟悉) + + vim /etc/ssh/sshd_config + * PermitRootLogin without-password (旧) + * PermitRootLogin yes(新) + + reboot + * 重启,可以使用root身份登录了 + +# 扩展可用空间 + sudo raspi-config + Advanced Options + Expand Filesystem + + df -lh + +# 更改时区 + sudo raspi-config + Localisaion Options + Change Local + Change Timezone + + timedatectl + Time zone: Asia/Shanghai (CST, +0800) + +# 设置wifi + Localisaion Options + Change Wi-fi Country + CN China + +# 更换国内镜像源 + cp /etc/apt/sources.list /etc/apt/sources.list.bak + * 备份 + + vim /etc/apt/sources.list + * 注释所有,添加如下两行 + deb http://mirrors.aliyun.com/raspbian/raspbian/ jessie main non-free contrib rpi + deb-src http://mirrors.aliyun.com/raspbian/raspbian/ jessie main non-free contrib rpi + + + +# 更新源,升级已经安装的包,升级系统 + apt-get update + apt-get upgrade + apt-get dist-upgrade \ No newline at end of file diff --git "a/\346\240\221\350\216\223\346\264\276/raspberry-\346\221\204\345\203\217\345\244\264.py" "b/\346\240\221\350\216\223\346\264\276/raspberry-\346\221\204\345\203\217\345\244\264.py" new file mode 100644 index 00000000..8569b5df --- /dev/null +++ "b/\346\240\221\350\216\223\346\264\276/raspberry-\346\221\204\345\203\217\345\244\264.py" @@ -0,0 +1,162 @@ +------------------------ +sci摄像头 | +------------------------ + # 摄像头命令软件 + raspistill [options] + * 文档 + https://www.raspberrypi.org/app/uploads/2013/07/RaspiCam-Documentation.pdf + + # 图像参数与命令 + -?, --help : 帮助文档 + -w, --width : 设置图像宽度 <尺寸> + -h, --height : 设置图像高度 <尺寸> + -q, --quality : 设置jpeg品质 <0到100> + -r, --raw : 增加raw原始拜尔数据到JPEG元数据 + -o, --output : 输出文件名 <文件名>,如果要写到stdout,使用`-o -`,如果不特别指定,图像文件不会被保存 + -l, --latest : 链接最近的完整图像到指定文件 <文件名> + -v, --verbose : 在运行摄像头时输出详细信息 + -t, --timeout : 拍照和关闭时的延时指定,未指定时默认是5s + -th, --thumb : 设置预览图(小图)参数(包括X、Y、品质)或者不设置 + -d, --demo : 运行一个demo模式(cycle through range of camera options, no capture),括号里的我可以理解成循环测试模式吗? + -e, --encoding : 编码来输出指定格式文件 (jpg, bmp, gif, png) + -x, --exif :将可交换图像文件应用到捕获中(格式:`key=value`)或者不设置 + -tl, --timelapse : 间隔拍摄,每拍摄一张图片 + -fp, --fullpreview : 用静态捕捉分辨率运行预览(可能会减小预览帧率) + -k, --keypress : 按键触发,按`ENTER`键拍照,按`X`然后`ENTER`键退出 + -s, -signal : 信号触发,等待另一个进程信号来拍摄一张照片 + -g, -gl : 绘制预览到文本,而不是使用视频渲染组件(啥意思?) + -gc, --glcapture : 捕获GL帧buffer而不是摄像机图像 + -set, --settings : 检索摄像机设置值然后写出到stdout + -cs, --camselect : 选择摄像头设备 <数字>,默认0 + -bm, --burst : 运行burst capture mode + + # 预览参数与命令 + -p, --preview : 预览窗口大小设置 + -f, --fullscreen : 全屏幕预览模式 + -op, --opacity : 预览窗口透明度 (0-255) + -n, --nopreview : 不显示预览窗口 + + # 预览参数与命令 + -gs, -glscene : GL屏幕尺寸等参数 + -gw, -glwin : GL窗口参数 + + # 图像参数与命令2 + -sh, --sharpness : 设置图像锐度 (-100 到 100) + -co, --contrast : 设置图像对比度 (-100 到 100) + -br, --brightness : 设置图像亮度 (0 到 100) + -sa, --saturation : 设置图像饱和度 (-100 到 100) + -ISO, --ISO : 设置摄像头感光度 + -vs, --vstab : Turn on video stabilisation 开启摄像头防抖模式(是这个意思吗?) + -ev, --ev : 设置EV补偿 + -ex, --exposure : 设置曝光模式(参见提示) + -awb, --awb : 设置AWB模式(参见提示) + -ifx, --imxfx : 设置图像效应(参见提示) + -cfx, --colfx : 设置色彩效应(参见提示) + -mm, --metering : 设置测光模式(参见提示) + -rot, --rotation : 设置图像旋转 (0到359) + -hf, --hflip : 设置水平翻转 horizontal flip + -vf, --vflip : 设置垂直翻转 vertical flip + -roi, --roi : 设置interest区域 (啥叫interset?) + --ss, --shutter : 设置快门速度,微秒 + --awbg, --awbgains : 设置AWB阈值, AWB模式必须关闭 + --drc, --drc : 设置DRC水平 + + # 提示 + 曝光模式选项 + auto : 自动曝光模式 + night : 夜间拍摄模式 + nightpreview : 夜间预览拍摄模式 + backlight : 逆光拍摄模式 + spotlight : 聚光灯拍摄模式 + sports : 运动拍摄模式 + snow : 雪景拍摄模式 + beach : 海滩拍摄模式 + verylong : 长时间曝光拍摄模式 + fixedfps : 帧约束拍摄模式 + antishake : 防抖模式 + fireworks : 烟火拍摄模式 + + 自动白平衡选项 + off : 关闭白平衡测算 + auto : 自动模式(默认) + sun : 日光模式 + cloud : 多云模式 + shade : 阴影模式 + tungsten : 钨灯模式 + fluorescent : 荧光灯模式 + incandescent : 白炽灯模式 + flash : 闪光模式 + horizon : 地平线模式 + + 图像特效选项 + none : 无特效(默认) + negative : 反色图像 + solarise : 曝光过度图像 + posterize : 色调图像 + whiteboard : 白板特效 + blackboard : 黑板特效 + sketch : 素描风格特效 + denoise : 降噪图像 + emboss : 浮雕图像 + oilpaint : 油画风格特效 + hatch : 草图特效 + gpen : 马克笔特效 + pastel : 柔化风格特效 + watercolour : 水彩风格特效 + film : 胶片颗粒风格特效 + blur : 模糊图像 + saturation : 色彩饱和图像 + colourswap : 暂未可用 + washedout : 暂未可用 + posterise : 暂未可用 + colourpoint : 暂未可用 + colourbalance : 暂未可用 + cartoon : 暂未可用 + + 测光模式选项 + average : 全画面平衡测光 + spot : 点测光 + backlit : 模拟背光图像 + matrix : 阵列测光 + + # 常用命令: + 1 常用命令: + 2 # 两秒钟(时间单位为毫秒)延迟后拍摄一张照片,并保存为 image.jpg + 3 raspistill -t 2000 -o image.jpg + 4 + 5 # 拍摄一张自定义大小的照片。 + 6 raspistill -t 2000 -o image.jpg -w 640 -h 480 + 7 + 8 # 降低图像质量,减小文件尺寸 + 9 raspistill -t 2000 -o image.jpg -q 5 + 10 + 11 # 强制使预览窗口出现在坐标为 100,100 的位置,并且尺寸为宽 300 和高 200 像素。 + 12 raspistill -t 2000 -o image.jpg -p 100,100,300,200 + 13 + 14 # 禁用预览窗口 + 15 raspistill -t 2000 -o image.jpg -n + 16 + 17 # 将图像保存为 PNG 文件(无损压缩格式,但是要比 JPEG 速度慢)。注意,当选择图像编码时,文件扩展名将被忽略。 + 18 raspistill -t 2000 -o image.png –e png + 19 + 20 # 向 JPEG 文件中添加一些 EXIF 信息。该命令将会把作者名称标签设置为 Dreamcolor,GPS 海拔高度为 123.5米。 + 21 raspistill -t 2000 -o image.jpg -x IFD0.Artist=Dreamcolor -x GPS.GPSAltitude=1235/10 + 22 + 23 # 设置浮雕风格图像特效 + 24 raspistill -t 2000 -o image.jpg -ifx emboss + 25 + 26 # 设置 YUV 图像的 U 和 V 通道为指定的值(128:128 为黑白图像) + 27 raspistill -t 2000 -o image.jpg -cfx 128:128 + 28 + 29 # 仅显示两秒钟预览图像,而不对图像进行保存。 + 30 raspistill -t 2000 + 31 + 32 # 间隔获取图片,在 10 分钟(10 分钟 = 600000 毫秒)的时间里,每 10 秒获取一张,并且命名为 image_number_1_today.jpg,image_number_2_today.jpg... 的形式。 + 33 raspistill -t 600000 -tl 10000 -o image_num_%d_today.jpg + 34 + 35 # 获取一张照片并发送至标准输出设备 + 36 raspistill -t 2000 -o - + 37 + 38 # 获取一张照片并保存为一个文件 + 39 raspistill -t 2000 -o - > my_file.jpg + diff --git "a/\346\255\243\345\210\231/\346\255\243\345\210\231-\345\205\245\351\227\250.java" "b/\346\255\243\345\210\231/\346\255\243\345\210\231-\345\205\245\351\227\250.java" new file mode 100644 index 00000000..ec215f06 --- /dev/null +++ "b/\346\255\243\345\210\231/\346\255\243\345\210\231-\345\205\245\351\227\250.java" @@ -0,0 +1,257 @@ +---------------- +鍏ラ棬 | +---------------- + * 鍙傝冨涔(璐糡M濂藉叆闂ㄧ殑姝e垯鏁欑▼,楦h阿浣滆) + http://deerchao.net/tutorials/regex/regex.htm + + * 鑰佺敺瀛﹑ython鏁欑▼绗18澶 + +-------------------- +鍏冨瓧绗 | +-------------------- + \b + * 浠h〃鐫璇嶇殑寮澶存垨缁撳熬,涔熷氨鏄瘝鐨勫垎鐣屽 + * 閫氬父璇嶆槸鐢辩┖鏍,鏍囩偣绗﹀彿鎴栬呮崲琛屾潵鍒嗛殧 + * 浣嗘槸\b骞朵笉鍖归厤杩欎簺鍗曡瘝鍒嗛殧瀛楃涓殑浠讳綍涓涓,瀹冨彧鍖归厤涓涓綅缃 + * 鐗规畩瀛楃鐨勪竴涓竟鐣 + + \d + * 鍖归厤涓涓暟瀛 + + \s + * 浠绘剰鐨勭┖鐧界,鍖呮嫭绌烘牸,鍒惰〃绗(Tab),鎹㈣绗,涓枃鍏ㄨ绌烘牸 + + \w + * 鍖归厤瀛楁瘝鎴栨暟瀛楁垨涓嬪垝绾挎垨姹夊瓧 + + . + * 闄や簡鎹㈣绗︿互澶栫殑浠绘剰瀛楃 + + * + * 浠h〃鏁伴噺 + * 鍓嶈竟鐨勫唴瀹瑰彲浠ヨ繛缁噸澶嶄娇鐢ㄤ换鎰忔,鍙互鏄0姝 + + + + * 浠h〃鏁伴噺 + * 鍓嶈竟鐨勫唴瀹瑰彲浠ヨ繛缁噸澶嶄娇鐢ㄤ换鎰忔,璧风爜鏄1姝 + + + ? + * 浠h〃鏁伴噺 + * 閲嶅闆舵鎴栦竴娆 + + + ^ + * 鍖归厤瀛楃涓插紑濮 + + $ + * 鍖归厤瀛楃涓茬粨鏉 + + ( + * + + ) + * + +-------------------- +鍗曚釜閲嶅 | +-------------------- + * + * 鍓嶈竟鐨勫唴瀹瑰彲浠ヨ繛缁噸澶嶄娇鐢ㄤ换鎰忔,鍙互鏄0姝 + + + + * 鍓嶈竟鐨勫唴瀹瑰彲浠ヨ繛缁噸澶嶄娇鐢ㄤ换鎰忔,璧风爜鏄1姝 + + ? + * 鍓嶈竟鐨勫唴瀹瑰彲浠ラ噸澶嶉浂娆℃垨涓娆 + + {n} + * 閲嶅n娆 + + {n,} + * 閲嶅n鍒版棤闄愭 + + {n,m} + * 閲嶅n-m娆 + +-------------------- +杞箟 | +-------------------- + * 濡傛灉鏌ユ壘鍏冨瓧绗︽湰韬殑璇,姣斿 ., 鎴栬 * ,灏卞嚭鐜颁簡闂,娌″姙娉曟寚瀹氬畠浠,鍥犱负瀹冧滑浼氳瑙i噴鎴愬埆鐨勬剰鎬 + * 杩欐椂灏卞緱浣跨敤\鏉ュ彇娑堣繖浜涘瓧绗︾殑鐗规畩鎰忎箟 + * 浣跨敤 \. 鍜 \* 褰撶劧锛岃鏌ユ壘\鏈韩,浣犱篃寰楃敤 \\ + * demo + deerchao\.net 鍖归厤deerchao.net + C:\\Windows 鍖归厤C:\Windows + +------------------------ +瀛楃绫 | +------------------------ + * 鎯冲尮閰嶆病鏈夐瀹氫箟鍏冨瓧绗︾殑瀛楃闆嗗悎(姣斿鍏冮煶瀛楁瘝a,e,i,o,u) + * 鍙渶瑕佸湪鏂规嫭鍙烽噷鍒楀嚭瀹冧滑灏辫浜 + * [aeiou] 鍖归厤浠讳綍涓涓嫳鏂囧厓闊冲瓧姣 + * [.?!]鍖归厤鏍囩偣绗﹀彿(.鎴?鎴!) + + * [] 杩樺彲浠ュ彇娑堝厓瀛楃鐨勭壒娈婃剰涔(鍦╗]閲岄潰鐨勫厓瀛楃,鍙槸鏅氬瓧绗) + [w,*] 鍙互鍖归厤鍒 'wdwqdq*' 瀛楃涓蹭腑鐨 w 鍜 * + + * - \ ^ 渚嬪,浠栦滑鍦╗]閲岄潰杩樻槸鍏峰鐗规畩鐨勫姛鑳 + * 濡傛灉闇瑕佽浆涔,浠嶇劧闇瑕佹坊鍔 \ 鏉ヨ繘琛岃浆涔 + [\\,\-,\^] + + + +------------------------ +鍒嗘敮鏉′欢 | +------------------------ + * 姝e垯琛ㄨ揪寮忛噷鐨勫垎鏋濇潯浠舵寚鐨勬槸鏈夊嚑绉嶈鍒,濡傛灉婊¤冻鍏朵腑浠绘剰涓绉嶈鍒欓兘搴旇褰撴垚鍖归厤 + * 鍏蜂綋鏂规硶鏄敤 | 鎶婁笉鍚岀殑瑙勫垯鍒嗛殧寮 + * 鍖归厤鍒嗘灊鏉′欢鏃,灏嗕細浠庡乏鍒板彸鍦版祴璇曟瘡涓潯浠,濡傛灉婊¤冻浜嗘煇涓垎鏋濈殑璇,灏变笉浼氬幓鍐嶇鍏跺畠鐨勬潯浠朵簡 + +------------------------ +鍒嗙粍 | +------------------------ + * 鍙互鐢ㄥ皬鎷彿鏉ユ寚瀹氬瓙琛ㄨ揪寮(涔熷彨鍋氬垎缁) + * 鍙互鎸囧畾杩欎釜瀛愯〃杈惧紡鐨勯噸澶嶆鏁颁簡,涔熷彲浠ュ瀛愯〃杈惧紡杩涜鍏跺畠涓浜涙搷浣 + +------------------------ +鍙嶄箟 | +------------------------ + * 闇瑕佹煡鎵句笉灞炰簬鏌愪釜鑳界畝鍗曞畾涔夌殑瀛楃绫荤殑瀛楃 + * 鎯虫煡鎵鹃櫎浜嗘暟瀛椾互澶,鍏跺畠浠绘剰瀛楃閮借鐨勬儏鍐,杩欐椂闇瑕佺敤鍒板弽涔 + + \W + * 浠绘剰涓嶆槸瀛楁瘝,鏁板瓧,涓嬪垝绾,姹夊瓧鐨勫瓧绗 + \S + * 鍖归厤浠绘剰涓嶆槸绌虹櫧绗︾殑瀛楃 + \D + * 鍖归厤浠绘剰闈炴暟瀛楃殑瀛楃 + \B + * 鍖归厤涓嶆槸鍗曡瘝寮澶存垨缁撴潫鐨勪綅缃 + [^x] + * 鍖归厤闄や簡x浠ュ鐨勪换鎰忓瓧绗 + [^aeiou] + * 鍖归厤闄や簡aeiou杩欏嚑涓瓧姣嶄互澶栫殑浠绘剰瀛楃 + + + \S+ + * 鍖归厤涓嶅寘鍚┖鐧界鐨勫瓧绗︿覆 + ]+> + * 鍖归厤鐢ㄥ皷鎷彿鎷捣鏉ョ殑浠寮澶寸殑瀛楃涓 + * [^>]+,鍖归厤浜嗘墍鏈夌殑 鐨勫瓧绗,鏈鍚庝互 > 缁撳熬 + +------------------------ +鍚庡悜寮曠敤 | +------------------------ + * 浣跨敤灏忔嫭鍙锋寚瀹氫竴涓瓙琛ㄨ揪寮忓悗,鍖归厤杩欎釜瀛愯〃杈惧紡鐨勬枃鏈(涔熷氨鏄鍒嗙粍鎹曡幏鐨勫唴瀹)鍙互鍦ㄨ〃杈惧紡鎴栧叾瀹冪▼搴忎腑浣滆繘涓姝ョ殑澶勭悊 + (琛ㄨ揪寮) + * 榛樿鎯呭喌涓,姣忎釜鍒嗙粍浼氳嚜鍔ㄦ嫢鏈変竴涓粍鍙,瑙勫垯鏄:浠庡乏鍚戝彸,浠ュ垎缁勭殑宸︽嫭鍙蜂负鏍囧織,绗竴涓嚭鐜扮殑鍒嗙粍鐨勭粍鍙蜂负1,绗簩涓负2,浠ユ绫绘帹 + * 鍒嗙粍0瀵瑰簲鏁翠釜姝e垯琛ㄨ揪寮 + * 瀹為檯涓婄粍鍙峰垎閰嶈繃绋嬫槸瑕佷粠宸﹀悜鍙虫壂鎻忎袱閬嶇殑 + 绗竴閬嶅彧缁欐湭鍛藉悕缁勫垎閰(123...) + 绗簩閬嶅彧缁欏懡鍚嶇粍鍒嗛厤(456...)(鍥犳鎵鏈夊懡鍚嶇粍鐨勭粍鍙烽兘澶т簬鏈懡鍚嶇殑缁勫彿) + * 浣犲彲浠ヤ娇鐢(?:exp)杩欐牱鐨勮娉曟潵鍓ュず涓涓垎缁勫缁勫彿鍒嗛厤鐨勫弬涓庢潈 + + * 瀹氫箟缁勫悕绉 + (?<缁勫悕绉>琛ㄨ揪寮) + + * 涓嶄細琚崟鑾峰埌鏌愪釜缁勯噷闈,涔熶笉浼氭嫢鏈夌粍鍙 + (?:琛ㄨ揪寮) + + * 娉ㄩ噴 + (?#娉ㄩ噴鍐呭) + + * demo + \b(\w+)\b\s+\1\b + + + +------------------------ +闆跺鏂█ | +------------------------ + * 鏌ユ壘鍦ㄦ煇浜涘唴瀹(浣嗗苟涓嶅寘鎷繖浜涘唴瀹)涔嬪墠鎴栦箣鍚庣殑涓滆タ + * 涔熷氨鏄鍍廫b,^,$閭f牱鐢ㄤ簬鎸囧畾涓涓綅缃,杩欎釜浣嶇疆搴旇婊¤冻涓瀹氱殑鏉′欢(鍗虫柇瑷),鍥犳瀹冧滑涔熻绉颁负闆跺鏂█ + + * 闆跺搴︽棰勬祴鍏堣鏂█ + (?=姝e垯) + + \b\w+(?=ing\b) 鍖归厤浠ng缁撳熬鐨勫崟璇嶇殑'鍓嶉潰閮ㄥ垎'(闄や簡ing浠ュ鐨勯儴鍒) + 'm singing while you're dancing. 鍖归厤 sing鍜宒anc銆 + + * 闆跺搴︽鍥為【鍚庡彂鏂█ + (?<=姝e垯) + + (?<=\bre)\w+\b 鍖归厤浠e寮澶寸殑鍗曡瘝鐨'鍚庡崐閮ㄥ垎'(闄や簡re浠ュ鐨勯儴鍒) + reading a book 瀹冨尮閰 ading + +------------------------ +璐熷悜闆跺鏂█ | +------------------------ + * 鍙槸鎯宠纭繚鏌愪釜瀛楃娌℃湁鍑虹幇,浣嗗苟涓嶆兂鍘诲尮閰嶅畠鏃 + + \b\w*q[^u]\w*\b + * 鍖归厤鍖呭惈鍚庨潰涓嶆槸瀛楁瘝u鐨勫瓧姣峲鐨勫崟璇 + * 濡傛灉q鍑虹幇鍦ㄥ崟璇嶇殑缁撳熬鐨勮瘽,鍍廔raq,Benq,杩欎釜琛ㄨ揪寮忓氨浼氬嚭閿 + * 杩欐槸鍥犱负[^u]鎬昏鍖归厤涓涓瓧绗,鎵浠ュ鏋渜鏄崟璇嶇殑鏈鍚庝竴涓瓧绗︾殑璇,鍚庨潰鐨刐^u]灏嗕細鍖归厤q鍚庨潰鐨勫崟璇嶅垎闅旂(鍙兘鏄┖鏍,鎴栬呮槸鍙ュ彿鎴栧叾瀹冪殑浠涔) + * 鍚庨潰鐨刓w*\b灏嗕細鍖归厤涓嬩竴涓崟璇,浜庢槸\b\w*q[^u]\w*\b灏辫兘鍖归厤鏁翠釜Iraq fighting + * 璐熷悜闆跺鏂█鑳借В鍐宠繖鏍风殑闂,鍥犱负瀹冨彧鍖归厤涓涓綅缃,骞朵笉娑堣垂浠讳綍瀛楃 + + * 鏂█姝や綅缃殑鍚庨潰涓嶈兘鍖归厤琛ㄨ揪寮廵xp(闆跺搴﹁礋棰勬祴鍏堣鏂█) + (?!exp) + + * 鏂█姝や綅缃殑鍓嶉潰涓嶈兘鍖归厤琛ㄨ揪寮廵xp(闆跺搴﹁礋鍥為【鍚庡彂鏂█) + (?).*(?=<\/\1>) 鍖归厤涓嶅寘鍚睘鎬х殑绠鍗旽TML鏍囩鍐呴噷鐨勫唴瀹 + + * 璇疯缁嗗垎鏋愯〃杈惧紡(?<=<(\w+)>).*(?=<\/\1>),杩欎釜琛ㄨ揪寮忔渶鑳借〃鐜伴浂瀹芥柇瑷鐨勭湡姝g敤閫 + + * 鎳垫嚨鐨 + +------------------------ +娉ㄩ噴 | +------------------------ + * 灏忔嫭鍙风殑鍙︿竴绉嶇敤閫旀槸閫氳繃璇硶(?#comment)鏉ュ寘鍚敞閲 + 2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)銆 + * 瑕佸寘鍚敞閲婄殑璇,鏈濂芥槸鍚敤"蹇界暐妯″紡閲岀殑绌虹櫧绗"閫夐」,杩欐牱鍦ㄧ紪鍐欒〃杈惧紡鏃惰兘浠绘剰鐨勬坊鍔犵┖鏍,Tab,鎹㈣,鑰屽疄闄呬娇鐢ㄦ椂杩欎簺閮藉皢琚拷鐣ャ傚惎鐢ㄨ繖涓夐」鍚庯紝鍦#鍚庨潰鍒拌繖涓琛岀粨鏉熺殑鎵鏈夋枃鏈兘灏嗚褰撴垚娉ㄩ噴蹇界暐鎺 + * 鎴戜滑鍙互鍓嶉潰鐨勪竴涓〃杈惧紡鍐欐垚杩欐牱 + (?<= # 鏂█瑕佸尮閰嶇殑鏂囨湰鐨勫墠缂 + <(\w+)> # 鏌ユ壘灏栨嫭鍙锋嫭璧锋潵鐨勫瓧姣嶆垨鏁板瓧(鍗矵TML/XML鏍囩) + ) # 鍓嶇紑缁撴潫 + .* # 鍖归厤浠绘剰鏂囨湰 + (?= # 鏂█瑕佸尮閰嶇殑鏂囨湰鐨勫悗缂 + <\/\1> # 鏌ユ壘灏栨嫭鍙锋嫭璧锋潵鐨勫唴瀹癸細鍓嶉潰鏄竴涓"/"锛屽悗闈㈡槸鍏堝墠鎹曡幏鐨勬爣绛 + ) # 鍚庣紑缁撴潫 + +------------------------ +璐┆涓庢噿鎯 | +------------------------ + * 姝e垯琛ㄨ揪寮忎腑鍖呭惈鑳芥帴鍙楅噸澶嶇殑闄愬畾绗︽椂,閫氬父鐨勮涓烘槸(鍦ㄤ娇鏁翠釜琛ㄨ揪寮忚兘寰楀埌鍖归厤鐨勫墠鎻愪笅)鍖归厤灏藉彲鑳藉鐨勫瓧绗 + * 璐┆鍖归厤 + a.*b + * 瀹冨皢浼氬尮閰嶆渶闀跨殑浠寮濮,浠缁撴潫鐨勫瓧绗︿覆 + * 濡傛灉鐢ㄥ畠鏉ユ悳绱abab鐨勮瘽,瀹冧細鍖归厤鏁翠釜瀛楃涓 aabab,杩欒绉颁负璐┆鍖归厤 + + * 鎳掓儼鍖归厤 + * 鏈夋椂,鎴戜滑鏇撮渶瑕佹噿鎯板尮閰,涔熷氨鏄尮閰嶅敖鍙兘灏戠殑瀛楃,鍙鍦ㄨ〃杈惧紡鍚庨潰鍔犱笂涓涓棶鍙? + a.*?b + * 瀹冨皢浼氬尮閰嶆渶闀跨殑浠寮濮,浠缁撴潫鐨勫瓧绗︿覆 + * 濡傛灉鐢ㄥ畠鏉ユ悳绱abab鐨勮瘽,瀹冧細鍖归厤 aab(绗竴鍒扮涓変釜瀛楃)鍜 ab(绗洓鍒扮浜斾釜瀛楃) + + .*? 灏辨剰鍛崇潃鍖归厤浠绘剰鏁伴噺鐨勯噸澶,浣嗘槸鍦ㄨ兘浣挎暣涓尮閰嶆垚鍔熺殑鍓嶆彁涓嬩娇鐢ㄦ渶灏戠殑閲嶅 + + * 鎳掓儼闄愬畾绗 + + *? 閲嶅浠绘剰娆,浣嗗敖鍙兘灏戦噸澶 + +? 閲嶅1娆℃垨鏇村娆,浣嗗敖鍙兘灏戦噸澶 + ?? 閲嶅0娆℃垨1娆,浣嗗敖鍙兘灏戦噸澶 + {n,m}? 閲嶅n鍒癿娆,浣嗗敖鍙兘灏戦噸澶 + {n,}? 閲嶅n娆′互涓,浣嗗敖鍙兘灏戦噸澶 + +------------------------ +骞宠 缁/閫掑綊鍖归厤 | +------------------------ + * + diff --git "a/\346\255\243\345\210\231/\346\255\243\345\210\231-\345\270\270\347\224\250.java" "b/\346\255\243\345\210\231/\346\255\243\345\210\231-\345\270\270\347\224\250.java" new file mode 100644 index 00000000..8fd54b07 --- /dev/null +++ "b/\346\255\243\345\210\231/\346\255\243\345\210\231-\345\270\270\347\224\250.java" @@ -0,0 +1,18 @@ + +# ipv4 + ^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$ + +# 绉诲姩鐢佃瘽鍙风爜 + ^1[3-9]\d{9}$ + + +# 鐢ㄦ埛鍚 + /^(?!\d+$)(?!_+$)\w{1,14}$/.test(name.replace(/[\u4e00-\u9fa5]/g,"aa")); + name.replaceAll("[\\u4e00-\\u9fa5]", "aa").matches("^(?!\\d+$)(?!_+$)\\w{1,14}$"); + +# B绔欐荤粨鐨勫瀮鍦句俊鎭繃婊ゆ鍒(鑷存暚B绔欑殑寮婧愮簿绁) + regexpTaobao = regexp.MustCompile(`锟([\w\s]+)锟) + regexpURL = regexp.MustCompile(`(?:http|https|www)(?:[\s\.:\/\/]{1,})([\w%+:\s\/\.?=]{1,})`) + regexpWhitelist = regexp.MustCompile(`((acg|im9|bili|gov).*(com|html|cn|tv)|(av\d{8,}|AV\d{8,}))`) + regexpQQ = regexp.MustCompile(`(?:[鍔爍Q浼侀箙鍙风爜\s]{2,}|[缇ゅ彿]{1,})(?:[\x{4e00}-\x{9eff}]*)(?:[:锛岋細]?)([\d\s]{6,})`) + regexpWechat = regexp.MustCompile(`(?:[鍔+寰紜+鉃曡枃锛焩V濞佸崼鏄熲櫏鉂ゅxX淇{2,}|weixin|weix)(?:[锛屸潳锔.\s]?)(?:[\x{4e00}-\x{9eff}]?)(?:[:锛岋細]?)([\w\s]{6,})`) diff --git "a/\346\257\224\347\211\271\345\270\201.java" "b/\346\257\224\347\211\271\345\270\201.java" new file mode 100644 index 00000000..3ed667db --- /dev/null +++ "b/\346\257\224\347\211\271\345\270\201.java" @@ -0,0 +1,70 @@ + +# 一个区块大约 1024KB(1MB), 大约存储 4000 条左右的交易记录 + +---------+------------------ + |区块头 | 信息 + +---------+------------------ + +# 为什么要记账? + * 记账, 是有手续费的, A 转账给 B, 负责打包信息的人可以获取到一定的手续费, 手续费是 A 负责给 + * 10 分钟可以打包一次 + * 打包的人, 有打包奖励(中本聪的设计, 每4年, 奖励减半) + - 最开始打包的人, 一次打包可以获取到 50 个比特币的奖励 + - 4年以后, 一次打包可以获取到 25 个比特币 + - 4年以后, 一次打包可以获取到 12.5 个比特币 + + * 根据这个设计比特币一共只有 2100 W个 + +# 解决账单问题, 以谁为准? + * 打包只能是一个人打包, 具体是谁打包, 根据中本聪的设计, 使用:工作量证明方法 + + * 每个参与的用户都要去计算一道非常难的数学题:挖矿 + * 如果你做出来了, 那么你就可以有权利打包, 从而获取到手续费, 打包奖励 + + +# 挖矿, 到底是啥数学题? + * 哈希函数(SHA256), 正向计算比较容易, 逆向运算非常困难 + * 根据hash逆向出结果, 只能一个个去试 + + let data = 前一块的头部 + 账单的信息 + 时间戳 + 随机数 + let result = SHA256(SHA256(data)) + + * 要求计算结果的前n位都是0(bit) + * n 越大,难度越高,n越小,难度越低, 是怎么确定这个n的? + * 中本聪的设计, 每10分钟出一个块儿, 打包几千条信息, 就是通过调整这个 n 来保证的 + + + * 如果ok, 这个结果就是新的一个区块, 链接到上一个区块之后 + + * 整个过程只能不断的去修改随机数,其他的数据不能修改 + * 随机数从 0 开始递增 + +# 记录防伪? + * A 付款10比特币给 B + + 1. A打包数据-> 付款信息 -> (SHA256) -> RSA私钥 = 密码 + 密码 = RSA私钥加密(SHA256(付款信息)) + + 2. 全网广播(付款信息 + RSA公钥 + 密码) + + 3. 付款信息 -> (SHA256) -> RSA公钥 = 密码 + 密码 = RSA公钥解密(SHA256(付款信息)) + + 4. 对比步骤 2 和 3 的密码是否相同, 如果相同, 则是合法的 + + +# 防止双重支付? + * 区块链把很多交易信息,打包形成一个区块的链条 + * 防止支付余额不足的方法, 就是: 追溯 + + * 从区块中找到你收获的一个比特币开始, 到现在计算你目前账户应该有多少余额 + * 从而判断当前的这次交易是否可以被确认, 如果可以就可以打包为新的区块 + + +# 伪造记录? + * 比特币有个最长链的原则, 区块链最长的记录, 被全网接受 + + * 理论上来说, 你伪造的记录链长度, 必须要超过全网的区块链长度, 就可以认为是伪造成功了 + + * 除非你拥有的计算能力, 超过了全网 + +# 保密? \ No newline at end of file diff --git "a/\346\265\217\350\247\210\345\231\250\347\232\204CSP\345\256\211\345\205\250\346\234\272\345\210\266.java" "b/\346\265\217\350\247\210\345\231\250\347\232\204CSP\345\256\211\345\205\250\346\234\272\345\210\266.java" new file mode 100644 index 00000000..0d1d7484 --- /dev/null +++ "b/\346\265\217\350\247\210\345\231\250\347\232\204CSP\345\256\211\345\205\250\346\234\272\345\210\266.java" @@ -0,0 +1,198 @@ +------------------------ +CSP机制 | +----------------------- + # 参考地址 + http://www.ruanyifeng.com/blog/2016/09/csp.html + https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP + + # 还可以通过 meta 标签来限制 + + + # 通过HttpHeader来限制 + Content-Security-Policy + + # 可限制的资源列表 + script-src 外部脚本 + style-src 样式表 + img-src 图像 + media-src 媒体文件(音频和视频) + font-src 字体文件 + object-src 插件(比如 Flash) + child-src 框架 + frame-ancestors 嵌入的外部资源(比如、
    {{index}}{{item.name}}
    {{item.name}}
    {{item.name}}