-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.json
1 lines (1 loc) · 203 KB
/
index.json
1
[{"categories":null,"content":"计数排序 计数排序是桶排序的一种特殊情况,用于解决大量重复元素的排序,是一种非比较排序 它的核心思想是: 利用桶数组的下标映射待排序数组元素值,重复元素就把桶数组元素累加来记录个数。 ","date":"2021-02-17","objectID":"/%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F/:1:0","tags":null,"title":"计数排序","uri":"/%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"countSort 计数排序的核心是构造桶数组,用桶数组的下标映射待排序数组的元素。 特别要注意的是:计数排序的待排序数组如果指定排序区间,将会比从 0 开始的排序数组复杂 构造排序函数的步骤: 构造结果数组 result 构造桶数组 count 遍历 arr 数组,把其中元素值当作桶数组的下标。为了使桶数组 count 做到最节省空间,则使用 arr[i]-start 对齐。 桶数组累加。 按顺序输出给结果数组。 import java.util.Arrays; /** * @param arr 待排序数组 * @param start 数组元素区间最小值 * @param end 数组元素区间最大值 */ public static void countSort(int[] arr, int start, int end) { int[] result = new int[arr.length]; int[] count = new int[end - start]; for (int i = 0; i \u003c arr.length; i++) { //count数组下标代表待排序数组中的元素值,count中的元素代表数组下标在待排序数组中出现的次数 count[arr[i] - start]++; } //此时把count变为累加数组,元素内容-1为下标元素在原数组中对应的位置,也在结果数组中确定了位置,直接放置即可 for (int i = 1; i \u003c count.length; i++) { count[i] = count[i] + count[i - 1]; } // for (int i = 0, j = 0; i \u003c count.length; i++) { // while (count[i]-- \u003e 0) { // result[j++] = i + start; // } // } for (int i = arr.length - 1; i \u003e= 0; i--) { result[--count[arr[i] - start]] = arr[i]; } for (int i = 0; i \u003c result.length; i++) { arr[i] = result[i]; } } ","date":"2021-02-17","objectID":"/%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F/:1:1","tags":null,"title":"计数排序","uri":"/%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"归并排序 归并排序利用了分治和递归的思想,把一些元素分成若干份并把它们分别排序,有序后再合并。 归并排序基于的思想 递归 分治 ","date":"2021-02-16","objectID":"/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/:1:0","tags":null,"title":"归并排序","uri":"/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"归并排序的核心 通常来说归并排序被分为两个模块: 排序模块 sort() 归并模块 merge() ","date":"2021-02-16","objectID":"/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/:1:1","tags":null,"title":"归并排序","uri":"/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"merge 此模块是为了解决合并问题而设计的。 它的主要思想是将前后两个有序数组合并成一个有序数组。 核心思想: i,j,k 三个指针,用来表示数组下标。i 指向前一个有序数组的第一个元素,j 指向后一个有序数组的第一个元素,k 指向临时数组 temp 的第一个元素。 前后有序数组的元素逐个比较,较小的放入临时数组 temp,直到其中一个数组完全放入临时数组。 将有剩余元素未加入临时数组 temp 的数组按顺序加入临时数组 temp。 将临时数组 temp 中的元素写回 arr 中。 public static void merge(int[] arr, int leftPtr, int rightPtr, int bound) { int i = leftPtr; int j = rightPtr; int k = 0; int mid = rightPtr - 1; int[] temp = new int[bound - leftPtr + 1]; while (i \u003c= mid \u0026\u0026 j \u003c= bound) { temp[k++] = arr[i] \u003c= arr[j] ? arr[i++] : arr[j++]; } while (i \u003c= mid) { temp[k++] = arr[i++]; } while (j \u003c= bound) { temp[k++] = arr[j++]; } for (int l = 0; l \u003c temp.length; l++) { arr[leftPtr + l] = temp[l]; } } ","date":"2021-02-16","objectID":"/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/:1:2","tags":null,"title":"归并排序","uri":"/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"sort 此模块的核心思想是递归,递归条件是 left \u003c right。 如果 left \u003e right 就报错,left==right 就直接返回。 将待排序数组分成两半分别排序。 排序后将两半的有序数组合并,这就用到了刚才的merge()。 mid 变量由于 left、right 是 int 类型,防止+运算溢出就使用-,同时使用位运算除以 2,提升执行效率。 public static void sort(int[] arr, int left, int right) { if (left \u003e right) { throw new IllegalArgumentException(\"left必须小于等于right\"); } if (left == right) return; //先分成两半 int mid = left + ((right - left) \u003e\u003e 1); //左边排序 sort(arr, left, mid); //右遍排序 sort(arr, mid + 1, right); //合并 merge(arr, left, mid + 1, right); } ","date":"2021-02-16","objectID":"/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/:1:3","tags":null,"title":"归并排序","uri":"/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"Spring 循环依赖 什么是循环依赖问题? 说白了就是死循环问题,A 对象创建依赖 B,B 对象创建依赖 A new A(new B(new A(new B(……)))) //有参 创建对象时做了两件事: 在堆中开辟一块空间 给对象进行赋值 解决循环依赖需要人为的把实例化和初始化的过程给分开 new A(new B()) //无参 Spring 给属性进行赋值操作的时候产生问题 S 构造器:无法解决循环依赖 setter 方法:可以解决循环依赖问题 三级缓存 -\u003e 提前暴露对象 bean 的生命周期 实例化:在堆中开辟空间,对象中的属性值都是默认值 初始化: 填充属性 -\u003e populateBean:给默认属性(字段)赋值 调用 Aware 接口中的方法 执行 before 方法 -\u003e BeanPostProcessor 执行 init 方法 执行 after 方法 -\u003e BeanPostProcessor 获取完整对象 销毁流程 对象的创建包含实例化和初始化两部分 AB 循环依赖: 创建 A 对象 -\u003e 实例化 A 对象 -\u003e 填充 A 对象的 b 属性 -\u003e 从容器中查找 B 对象 -\u003e 如果找到 B 直接赋值,如果没有找到就创建 B 对象 -\u003e 实例化 B 对象 -\u003e 填充 B 对象的 a 属性 -\u003e 从容器中查找 A 对象 -\u003e 找到 A 直接赋值,没找到 A 又要重新创造 A 对象 在循环过程中,对象存在两种状态: 完成实例化但是未完成初始化(半成品) 只要持有了该对象的引用,能否在后续过程中进行赋值操作 完成实例化和初始化(成品) 把 AB 循环依赖中的从容器中查找改成从缓存中查找 map 结构 key-value 模型 实例化对象后把对象放入 map 中,从而解决循环创建对象问题 结论:在容器中时需要缓存对象的,使用 map 的存储结构对象存在两种状态,要把半成品对象和成品对象分开存储,避免直接获取到半成品对象,此时可以给出一个描述,一级缓存(singletonObjects)和二级缓存(earlySingletonObjects),可以做如下人为规定:一级缓存存放成品,二级缓存存放半成品 DEBUG 整个对象的创建过程,一二三级缓存如何存储 创建对象的流程 为什么要移除二三级缓存? 在三级缓存中,我们在查找对象的时候,先找一级,再找二级,再找三级,如果一级之中已经有对象,不会去二级缓存中查找,以此类推,二三级都要销毁掉。 如果只有一级缓存,能否解决循环依赖问题? 不能 在一级缓存中存放的是成品对象,二级缓存中存放的是半成品对象,如果只有一级的话,那么成品和半成品回放在一起,那么就有可能获取到半成品对象,此时会有空指针问题,所以必须分开存储。 如果只有二级缓存,能否解决循环依赖问题?为什么非要有三级缓存 (singletonFactories) 改源码验证:哪些地方用到了三级缓存? 创建完对象之后 addSingletonFactory.getSingleton() 直接使用二级缓存也可以解决循环依赖问题,那么为什么非要用三级缓存? 为什么要使用三级缓存? 三级缓存存在的本质目的是为了解决 aop 过程中存在的动态代理的时候如何处理。 如果一个对象需要被代理,那么是否需要创建当前对象的普通对象(直接通过反射创建出来的对象)? 当添加了三级缓存之后,多了一个什么样的处理过程? 在 getEarlyBeanReferenc() 方法中可能会把原来的普通对象给替换成代理对象。 为什么加了三级缓存之后就解决了这个问题? 不管是普通对象,还是代理对象,对应的 bean 名称是否一致? 一样 在整个容器中可能存在同名不同对象吗? 不会,因为是 Singleton 在程序运行过程中,能确认代理对象什么时候被使用吗? 不能 怎么保证普通对象和代理对象只对外暴露一个? 通过回调机制来保证,lambda 传递到三级缓存之后并不是直接存储对象,而是在需要对象的时候,通过回调方法返回一个唯一的对象。 同过 lambda 表达式的回调确认了一件事,不能将普通对象和代理对象直接暴露出去,而只能选择一个对外暴露,也就是说只有一个最终版本,不会存在多个版本对外暴露 ","date":"2021-01-04","objectID":"/%E8%A7%A3%E5%86%B3spring%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96%E5%92%8C%E4%B8%89%E7%BA%A7%E7%BC%93%E5%AD%98%E9%97%AE%E9%A2%98/:0:0","tags":null,"title":"解决Spring循环依赖和三级缓存问题","uri":"/%E8%A7%A3%E5%86%B3spring%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96%E5%92%8C%E4%B8%89%E7%BA%A7%E7%BC%93%E5%AD%98%E9%97%AE%E9%A2%98/"},{"categories":null,"content":"背景 当你用终端操作 git,可能会出现 commit 的 message 乱码或者写错的情况,但是如果你已经将代码 push 到了 github 中,那么修改 message 就是一个非常麻烦的操作。 需要用到的命令 $ git log $ git rebase -i HEAD~5 $ git commit --amend $ git rebase --continue $ git push -f 注: 在修复历史 commit message 的时候,请确保当前分支是最新代码, 且已经提交了所有本地修改。 修改步骤 使用 git log 查看日志。 如图,提交记录会根据时间倒序展示。 使用git rebase -i HEAD~2 ,这里 1 可以换成别的数字,意思是显示最近 2 个提交记录。 pick 1d316b0 1 pick f429786 2 pick 880cfbc 3 pick c55cf56 4 pick d10fd07 5 最左侧是命令(command) 包括: p, pick = use commit r, reword = use commit, but edit the commit message e, edit = use commit, but stop for amending s, squash = use commit, but meld into previous commit f, fixup = like “squash”, but discard this commit’s log message x, exec = run command (the rest of the line) using shell d, drop = remove commit 中间是 commit id 最右一列是 commit message 假设我们要修改最后一条 message,就要把相应的 pick 改为 edit,保存并退出。 edit 1d316b0 1 pick f429786 2 pick 880cfbc 3 pick c55cf56 4 pick d10fd07 5 随后轮流使用 git commit --amend 和 git rebase --continue 修改每个 edit 的 commit。 保存完了之后,git 的分支就会发生改变, 从原来的 master 改成了我们第一个 edit 的 commit id。 下面我们在这个 commit id 所示的分支上,执行git commit --amend,此时可以使用 vim 或者其他文本编辑器修改 message。 保存后使用 git rebase --continue,若修改多个 message,只需重复几次。 使用 git push 强制更新远程服务器 git push -f 强制修改 ","date":"2020-12-04","objectID":"/git%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BF%AE%E6%94%B9%E5%B7%B2%E7%BB%8Fpush%E7%9A%84commit%E7%9A%84message/:0:0","tags":null,"title":"Git中如何修改已经push的commit的message","uri":"/git%E4%B8%AD%E5%A6%82%E4%BD%95%E4%BF%AE%E6%94%B9%E5%B7%B2%E7%BB%8Fpush%E7%9A%84commit%E7%9A%84message/"},{"categories":null,"content":"Collection Collection 是最基本的集合接口。JDK 提供的类都是继承自 Collection 的子接口,例如 List 和 Set。 Collection 是集合类的根接口,是高度抽象的集合,包含了集合的基本操作:添加、删除、清空、遍历、是否为空、获取大小等。 Collection 继承了Iterable\u003cE\u003e,所以 Collection 的体系都支持iterator()方法,该方法返回一个迭代器,使用该迭代器可以逐一访问 Collection 的每一个元素。 Collection 体系提供的常⽤⽅法: R: size()/isEmpty()/contains()/for()/stream() C/U: add()/addAll()/retainAll() 这里特别要说一下,retainAll() 非常容易踩坑,如果a.retainAll(b) 这相当于求集合 a、b 的交集,然后把结果集放在集合 a 中,最终可能会导致集合 a 的size()减少。 D: clear()/remove()/removeAll() ","date":"2020-11-23","objectID":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/:0:0","tags":null,"title":"Java的Collection体系","uri":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/"},{"categories":null,"content":"Collection 的继承体系 Collection |-----List 有序(存储顺序和取出顺序一致),可重复 |----ArrayList 线程不安全,底层使用数组实现,查询快,增删慢,效率高。 |----LinkedList 线程不安全,底层使用链表实现,查询慢,增删快,效率高。 |----Vector 线程安全,底层使用数组实现,查询快,增删慢,效率低。每次容量不足时,默认自增长度的一倍(如果不指定增量的话)。 |-----Set 元素唯一一个不包含重复元素的 Collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素。 |--HashSet 底层是由HashMap实现的,通过对象的hashCode方法与equals方法来保证插入元素的唯一性,无序(存储顺序和取出顺序不一致),。 |--LinkedHashSet 底层数据结构由哈希表和链表组成。哈希表保证元素的唯一性,链表保证元素有序。(存储和取出是一致) |--TreeSet 基于 TreeMap 的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。 元素唯一。 ","date":"2020-11-23","objectID":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/:1:0","tags":null,"title":"Java的Collection体系","uri":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/"},{"categories":null,"content":"List 和 Set 约定 集合约定了 List 是有序的,而 Set 是无序的,有没有序是根据存储顺序和取出顺序是否一致来约定。 Collection 子接口 ","date":"2020-11-23","objectID":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/:2:0","tags":null,"title":"Java的Collection体系","uri":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/"},{"categories":null,"content":"List List 是一个有序的队列,每一个元素都有它的索引,第一个元素的索引值是 0。 ArrayList List 中最常用的就是 ArrayList,它是一个动态数组,它可以实现动态扩容,每次创建原数组容量1.5 倍的新数组,把原数组的元素拷贝到新数组。ArrayList 允许所有元素包括 null,同时 ArrayList 也不是线程安全的。 LinkedList LinkedList 实现了 List 接口,允许元素为空,LinkedList 提供了额外的 get,remove,insert 方法,这些操作可以使 LinkedList 被用作堆栈、队列或双向队列。LinkedList 并不是线程安全的,如果多个线程同时访问 LinkedList,则必须自己实现访问同步,或者另外一种解决方法是在创建 List 时构造一个同步的 List。 ","date":"2020-11-23","objectID":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/:3:0","tags":null,"title":"Java的Collection体系","uri":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/"},{"categories":null,"content":"重要约定:HashCode HashCode 在 java 中是 int 类型。 同⼀个对象必须始终返回相同的 hashCode 。 两个对象的 equals 返回 true,必须返回相同的 hashCode。 两个对象不等,也可能返回相同的 hashCode,int 最大 42 亿,而对象是无限的。 ","date":"2020-11-23","objectID":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/:4:0","tags":null,"title":"Java的Collection体系","uri":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/"},{"categories":null,"content":"Set Set 是一个不允许有重复元素的集合。 判断重复:equals() Set 中还引入了 Hash 桶的概念,这是一个抽象的容器,不同的 HashCode 就像是一个个的桶,而 Set 每加入新的对象就会依次对比它们的 HashCode,如果与 Hash 桶相匹配就丢入桶内。将一个个对象映射成 HashCode 的方法是 HashCode。 HashSet HashSet 是最高效的 Set 实现,线程不安全。 HashSet 依赖于 HashMap,底层是通过 HashMap 实现的,HashMap 的 Key 就是一个 HashSet,所以 HashSet 能实现的 HashMap 都能实现,HashSet 的构造器中 new 了一个 HashMap。 HashMap 在 1.7 之前使用的是数组+链表实现,在 1.8+使用的数组+链表+红黑树实现。链表长度大于 8 则转为红黑树 LinkedHashSet 继承了 HashSet。实现了一个有序的 HashSet。 TreeSet 同样,TreeSet 依赖于 TreeMap,底层通过 TreeMap 来实现的。 TreeSet 不允许 null 值。 TreeSet 线程不安全。 TreeSet 实现了 SortedSet 接口,从而保证了添加的元素按照元素的自然顺序(递增或其他顺序)在集合中进行存储。 ","date":"2020-11-23","objectID":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/:5:0","tags":null,"title":"Java的Collection体系","uri":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/"},{"categories":null,"content":"Queue(FIFO) 队列数据结构,先进先出。 Deque 双端队列,继承自 Queue,代替了栈(Stack)。 PriorityQueue 优先级队列,本质上是堆。是 Queue 的实现类 AbstractQueue 的子类,实现了 Serializable 接口。 ","date":"2020-11-23","objectID":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/:6:0","tags":null,"title":"Java的Collection体系","uri":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/"},{"categories":null,"content":"Vector 类似与 ArrayList 的动态数组,线程安全,访问较慢。 Stack 栈(LIFO),继承自 Vector,先进后出,被双端队列替代。 集合的另一个基本接口 Map 在《java Core 卷一 第九版》一书中的 569 页,13.3 节 集合框架 的倒数第 16 行原文:“集合有两个基本的接口:Collection 和 Map。可以使用下列方法向集合中插入元素……” Map 接口中键和值一一映射。可以通过键来获取值。 方法: C/U: put()/putAll() R: get()/size() containsKey()/containsValue() keySet()/values()/entrySet() D: remove()/clear() HashMap HashMap 是 Map 的实现类,是最高效的 Map 实现。 HashMap 的 key 和 value 都允许为 null,HashMap 遇到 key 为 null 的时候,调用 putForNullKey 方法进行处理,而对 value 没有处理。 HashMap 是动态的,它的扩容类似与 ArrayList,区别是 HashMap 是申请一个当前容量2 倍的桶数组,再将 HashMap 的 key/value 映射逐个添加进去。 HashMap 线程不安全。原因是 HashMap 在多线程扩容时可能会变成死循环。解决方案是使用 ConcurrentHashMap。 put 时导致多线程数据不一致。有线程 A、B,假设 HashCode 相同,A 找到了与之对应的 Hash 桶坐标准备插入数据,但是此时 A 的时间片用完了,A 进入阻塞状态,此时 B 找到 Hash 桶成功插入数据,但是和之前 A 要插入数据的位置相同,而后 A 又被调度,就会把 B 的数据覆盖,最后导致数据不一致问题。 死循环,get() -\u003e resize() 导致的循环链表。 Hashtable 继承自 Dictionary 类。Dictionary 是任何可将键映射到相应值的类的抽象父类。 Hashtable 的 key 和 value 都不允许为 null,Hashtable 遇到 key 为 null,直接返回 NullPointerException。 Hashtable 线程安全,而 HashMap 则不是。Hashtable 几乎所有的 public 的方法都是synchronized的,而有些方法也是在内部通过synchronized代码块来实现。所以多线程使用 Hashtable。 Collections 工具类 ","date":"2020-11-23","objectID":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/:7:0","tags":null,"title":"Java的Collection体系","uri":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/"},{"categories":null,"content":"注意:工具类一般都是在实现类的名字后面加一个 s,例如 Guava 中的 Lists/Sets/Maps。 emptySet(): 等返回⼀个⽅便的空集合。 synchronizedCollection: 将⼀个集合变成线程安全的。 unmodifiableCollection: 将⼀个集合变成不可变的(也可以 使⽤ Guava 的 Immutable)。 Guava ","date":"2020-11-23","objectID":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/:8:0","tags":null,"title":"Java的Collection体系","uri":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/"},{"categories":null,"content":"Google Guava 是 Java 的一组开源通用库,主要由 Google 工程师开发。 不要重复发明轮⼦!尽量使⽤经过实战检验的类库 Guava Github Repo Guava 中文教程 ","date":"2020-11-23","objectID":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/:9:0","tags":null,"title":"Java的Collection体系","uri":"/java%E7%9A%84collection%E4%BD%93%E7%B3%BB/"},{"categories":null,"content":"Ajax AJAX 即“Asynchronous JavaScript and XML”(异步的 JavaScript 与 XML 技术),指的是一套综合了多项技术的浏览器端网页开发技术。 异步:点击这里了解 ","date":"2020-11-10","objectID":"/ajax/:0:0","tags":null,"title":"Ajax","uri":"/ajax/"},{"categories":null,"content":"为什么需要 Ajax 传统的 Web 应用允许用户端填写表单(form),当提交表单时就向网页服务器发送一个请求。服务器接收并处理传来的表单,然后送回一个新的网页,但这个做法浪费了许多带宽,因为在前后两个页面中的大部分 HTML 码往往是相同的。由于每次应用的沟通都需要向服务器发送请求,应用的回应时间依赖于服务器的回应时间。这导致了用户界面的回应比本机应用慢得多。 与此不同,AJAX 应用可以仅向服务器发送并取回必须的数据,并在客户端采用 JavaScript 处理来自服务器的回应。因为在服务器和浏览器之间交换的数据大量减少,服务器回应更快了。同时,很多的处理工作可以在发出请求的客户端机器上完成,因此 Web 服务器的负荷也减少了。 ","date":"2020-11-10","objectID":"/ajax/:1:0","tags":null,"title":"Ajax","uri":"/ajax/"},{"categories":null,"content":"使用 Ajax 可以做什么 注册时,输入用户名自动检测用户是否已经存在。 登陆时,提示用户名密码错误 删除数据行时,将行 ID 发送到后台,后台在数据库中删除,数据库删除成功后,在页面 DOM 中将数据行也删除。 等等 ","date":"2020-11-10","objectID":"/ajax/:2:0","tags":null,"title":"Ajax","uri":"/ajax/"},{"categories":null,"content":"原生 Ajax ","date":"2020-11-10","objectID":"/ajax/:3:0","tags":null,"title":"Ajax","uri":"/ajax/"},{"categories":null,"content":"XMLHttpRequest 对象 浏览器内建对象,用于在后台与服务器通信(交换数据) ,由此我们便可实现对网页的部分更新,而不是刷新整个页面。 所有现代浏览器(IE7+、Firefox、Chrome、Safari 以及 Opera)均内建 XMLHttpRequest 对象。 var xhr = new XMLHttpRequest(); 如果需要将请求发送到服务器,需要使用 XMLHttpRequest 对象的 open() 和 send() 方法。 var xhr = new XMLHttpRequest(); xhr.open(\"GET\", \"ajax_info.json\", true); xhr.send(); 方法 描述 open(method,url,async) 规定请求的类型、URL 以及是否异步处理请求。 method:请求的类型;GET 或 POST url:文件在服务器上的位置 async:true(异步)或 false(同步) send(string) 将请求发送到服务器。string:仅用于 POST 请求 XMLHttpRequest 对象的相关属性和事件 属性 说明 status 200: “OK” responseText 获得字符串形式的响应数据。 responseXML 获得 XML 形式的响应数据。 readyState 存有 XMLHttpRequest 的状态。请求发送到后台后,状态会从 0 到 4 发生变化。 0: 请求未初始化 1: 服务器连接已建立 2: 请求已接收 3: 请求处理中 4: 请求已完成,且响应已就绪 onreadystatechange 每当 readyState 属性改变时,就会调用该函数。 可以通过监听 XMLHttpRequest 对象的 onreadystatechange 事件,在事件的回调函数中判断 readyState 的状态,可以帮助我们进行对象请求结果的判断处理。 ","date":"2020-11-10","objectID":"/ajax/:3:1","tags":null,"title":"Ajax","uri":"/ajax/"},{"categories":null,"content":"GET 请求 get 请求参数需要放在 url 地址的参数中。并通过 urlencode (百分号编码)的方式传参,也就是?隔开 url 和参数,然后多个参数用\u0026连接,参数格式为:key=value,get 请求对数据长度有限制,url 的最大长度是 2048 个字符。 // get请求 var xhr = new XMLHttpRequest(); xhr.open(\"GET\", \"/com/ajax?a=1\u0026b=2\", true); xhr.send(); xhr.onreadystatechange = function (e) { if (xhr.readyState == 4 \u0026\u0026 xhr.status == 200) { console.log(xhr.responseText); } }; ","date":"2020-11-10","objectID":"/ajax/:3:2","tags":null,"title":"Ajax","uri":"/ajax/"},{"categories":null,"content":"POST 请求 post 请求需要添加一个请求头,让后台知道我们请求的参数的格式,这样后台才能解析我们的数据。post 请求相对与 get 请求更加安全,发送的数据被放在请求体中,而不会出现在 url 中,而且对数据的长度没有限制。另外,传输的数据需要格式化到 send 方法中。 // post请求 var xhr = new XMLHttpRequest(); xhr.open(\"POST\", \"/com/ajax\", true) xhr.setRequestHeader(\"Content-type\", \"application/json\"); xhr.send(\"{ \"face\": \"😂\" }\"); xhr.onreadystatechange = function (e) { if (xhr.readyState == 4 \u0026\u0026 xhr.status == 200) { console.log(xhr.responseText); } }; ","date":"2020-11-10","objectID":"/ajax/:3:3","tags":null,"title":"Ajax","uri":"/ajax/"},{"categories":null,"content":"封装原生 Ajax 请求 GET /** * @param {String} url 请求后台的地址。 * @param {Function} callback 请求成之后,返回数据成功,并且调用此方法,这个方法接受一个参数就是后台返回的数据。 */ function ajaxGet(url, callback) { var xhr = new XMLHttpRequest(); xhr.open(\"GET\", url, true); xhr.send(); xhr.onreadystatechange = function () { if (xhr.readyState == 4 \u0026\u0026 xhr.status == 200) { callback(xhr.responseText); } }; } // 调用 ajaxGet(\"/api/users\", function (data) { console.log(data); }); POST /** * @param {String} url 请求后台的地址。 * @param {String} data 发送请求的数据,在请求体中。 * @param {Function} callback 请求成之后,返回数据成功,并且调用此方法,这个方法接受一个参数就是后台返回的数据。 */ function ajaxPost(url, data, callback) { var xhr = new XMLHttpRequest(); xhr.open(\"POST\", url, true); xhr.setRequestHeader(\"Content-type\", \"application/json\"); xhr.send(data); xhr.onreadystatechange = function () { if (xhr.readyState == 4 \u0026\u0026 xhr.status == 200) { callback(xhr.responseText); } }; } // 调用 ajaxPost(\"/api/users\", \"{\"id\":\"1\",\"name\":\"john\",\"age\":\"1\"}\", function (data) { var user = JSON.parse(data); console.log(user.id); console.log(user.name); console.log(user.age); }); ","date":"2020-11-10","objectID":"/ajax/:3:4","tags":null,"title":"Ajax","uri":"/ajax/"},{"categories":null,"content":"JQuery 的 Ajax jQuery 提供多个与 AJAX 有关的方法。 通过 jQuery AJAX 方法,您能够使用 HTTP Get 和 HTTP Post 从远程服务器上请求文本、HTML、XML 或 JSON – 同时您能够把这些外部数据直接载入网页的被选元素中。 jQuery Ajax 本质就是 XMLHttpRequest,对他进行了封装,方便调用! jQuery.ajax(...) 部分参数: url:请求地址 type:请求方式,GET、POST(1.9.0之后用method) headers:请求头 data:要发送的数据 contentType:即将发送信息至服务器的内容编码类型(默认: \"application/x-www-form-urlencoded; charset=UTF-8\") async:是否异步 timeout:设置请求超时时间(毫秒) beforeSend:发送请求前执行的函数(全局) complete:完成之后执行的回调函数(全局) success:成功之后执行的回调函数(全局) error:失败之后执行的回调函数(全局) accepts:通过请求头发送给服务器,告诉服务器当前客户端可接受的数据类型 dataType:将服务器端返回的数据转换成指定类型 \"xml\": 将服务器端返回的内容转换成xml格式 \"text\": 将服务器端返回的内容转换成普通文本格式 \"html\": 将服务器端返回的内容转换成普通文本格式,在插入DOM中时,如果包含JavaScript标签,则会尝试去执行。 \"script\": 尝试将返回值当作JavaScript去执行,然后再将服务器端返回的内容转换成普通文本格式 \"json\": 将服务器端返回的内容转换成相应的JavaScript对象 \"jsonp\": JSONP 格式使用 JSONP 形式调用函数时,如 \"myurl?callback=?\" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数 ","date":"2020-11-10","objectID":"/ajax/:4:0","tags":null,"title":"Ajax","uri":"/ajax/"},{"categories":null,"content":"Ajax 实战 Jquery 发送 ajax 请求的方法有很多,其中最基本的是 $.ajax方法,在其之上封装的方法有 $.get, $.post, $.put, $.ajaxForm, $.fileUpload 等。 $.get 请求功能以取代复杂 $.ajax 。请求成功时可调用回调函数。如果需要在出错时执行函数,请使用 $.ajax。 jQuery.get() $(selector).get(url,data,success(response,status,xhr),dataType) url 必需。规定将请求发送的哪个 URL。 data 可选。规定连同请求发送到服务器的数据。 success(response,status,xhr) 可选。规定当请求成功时运行的函数。 额外的参数: response - 包含来自请求的结果数据 status - 包含请求的状态 xhr - 包含 XMLHttpRequest 对象 dataType 可选。规定预计的服务器响应的数据类型。 默认地,jQuery 将智能判断。 可能的类型: \"xml\" \"html\" \"text\" \"script\" \"json\" \"jsonp” $.ajax({ type: \"GET\", url: url, data: data, success: success, dataType: dataType, }); $.get( \"${pageContext.request.contextPath}/yourRequestMapping\", { id: \"1\", name: \"zhangsan\", }, function (data, status) { //这里显示从服务器返回的数据 alert(data); //这里显示返回的状态 alert(status); }, \"json\" ); jQuery.post() post() 方法通过 HTTP POST 请求从服务器载入数据 ${selector}.post(url,data,success(data, textStatus, jqXHR),dataType) url 必需。规定把请求发送到哪个 URL。 data 可选。映射或字符串值。规定连同请求发送到服务器的数据。 success(data, textStatus, jqXHR) 可选。请求成功时执行的回调函数。 dataType 可选。规定预期的服务器响应的数据类型。默认执行智能判断(xml、json、script 或 html)。 $.ajax({ type: \"POST\", url: url, data: data, success: success, dataType: dataType, }); $.post( \"${pageContext.request.contextPath}/yourRequestMapping\", { id: \"1\", name: \"zhangsan\", }, function (data, status) { //这里显示从服务器返回的数据 alert(data); //这里显示返回的状态 alert(status); }, \"json\" ); ","date":"2020-11-10","objectID":"/ajax/:4:1","tags":null,"title":"Ajax","uri":"/ajax/"},{"categories":null,"content":"Java 多线程 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:0:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"什么是进程 什么是线程 《操作系统》一书中给出了一个定义:进程是程序关于某数据集合的一次执行,是系统进行资源分配和调度的基本单位。 《Java 核心技术 卷 I》14 章 给出了一个定义:一个程序同时执行多个任务。通常,每一个任务称为一个线程 (thread),它是线程控制的简称。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:1:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"什么是多线程 可以同时运行一个以上线程的程序称为多线程程序 (multithreaded), ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:2:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"多线程与多进程有哪些区别 本质的区别在于每个进程拥有自己的一整套变量,而线程则共享数据。因为这一性质,才导致多线程中共享的变量不是“安全的”。操作系统中,线程与进程相比较,线程更加轻量级,创建和撤销一个线程的开销要远小于线程。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:3:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"为什么需要多线程 CPU 和内存、内存和硬盘读写速度极度不匹配,CPU 的执行速度要远远快于其他设备。 现代 CPU 都是多核的,比如一个八核 CPU,当运行单线程的时候,另外 7 核都处于空闲状态,多线程可以提高 CPU 的利用率。 Java 的执行模型是 同步/阻塞 模型。在默认情况下,java 就是这种传统的 IO 模型: 单线程处理问题按部就班,按照代码顺序执行。 性能差劲。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:4:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"如何开启新的线程 Thread 类 Java 提供了一个 Thread 线程类,它实现了 Runnable 接口。而 Runnable 接口中仅有一个抽象方法run(),它是一个函数式接口,可以传递给 Runnable 一个 lambda 表达式。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:5:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"下面是在一个单独线程中执行一个任务的简单流程: 将任务代码移到实现了 Runnable 接口的类的run()方法中。 public interface Runnable { void run(); } 由于 Runnable 接口是一个函数式接口,可以用 lambda 表达式建立一个实例。 Runnable r = () -\u003e {/** task code **/}; 由 Runnable 创建一个 Thread 对象: Thread thread = new Thread(r); 启动线程 thread.start(); ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:5:1","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"特别要注意 只有start()方法才能并发执行。 每多开一个线程,就会多一个执行流。 每个线程都有自己独立的方法栈,每运行start(),就会给一个新线程开辟独立的方法栈,方法栈是线程私有的。 静态变量/类变量是被所有线程所共享的。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:5:2","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"线程状态 线程可以有如下 6 种状态: New 新创建 Runnable 可运行 Block 被阻塞 Waiting 等待 Timed Waiting 计时等待 Terminated 被终止 新创建线程 当使用 new 操作符创建一个线程,如 new Thread() ,该线程还没有开始运行。这意味着线程的状态是 New。 可运行的线程 一旦调用start()方法,线程处于 Runnable 状态。一个可运行的线程可能正在运行也可能没有在运行,这取决于操作系统给线程提供运行的时间。一旦一个线程开始运行,它不必始终保持运行状态。 被阻塞线程和等待线程 当线程处于被阻塞或等待状态时,它暂时不活动。它不运行任何代码而且消耗最少的资源。直到线程调度器重新激活该线程。 当一个线程试图获取一个内部对象锁,该线程进入阻塞状态。当所有其他线程释放该锁,并且线程调度器允许本线程持有它的时候,该线程将变成非阻塞状态。 当线程等待另一个线程通知调度器的一个条件时,自己进入等待状态。 有几个方法有一个超时参数。调用它们导致线程进入计时等待状态。这一状态将一直保持到超时期满。 被终止的线程 因为run()方法正常退出而自然死亡。 因为一个没有捕获的异常终止了run()方法而意外死亡。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:6:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"多线程执行的本质 多线程的本质就是,一段相同的代码被不同的线程以不可预知的顺序和速度执行。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:7:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"多线程带来的性能提升 对于 IO 密集型的应用特别有用 网络 IO (通常包括数据库) 文件 IO 对于 CPU 密集型的应用稍有折扣 性能提升的上限:理论上 CPU 占用可以达到 100% 单核 CPU:100% N 核 CPU:N × 100% ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:8:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"线程非常昂贵 能不能使用线程达到无穷无尽的提升 不能 线程的昂贵性在于 CPU 切换上下文很慢 线程需要占用内存等系统资源 如果你的应用一天只有几个用户 使用 new Thread().start() 如果你的应用负载很高,有很多用户访问 使用 JUC 包 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:9:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"线程不安全的表现 数据错误 i++ 使用多线程对变量 i 从 0 开始累加 1000 次,最后得到的结果不是 1000,问题在于对变量累加不是原子操作: i++;被做了如下处理: (1) 取i值,将i加载到寄存器。 (2) 自增i。 (3) 将结果写回i的内存位置。 这里就会出现问题: 我们假设i的初值为0, 第一个线程1执行了步骤1、2。 但是CPU给线程1执行的时间片完了,就会调度第二个线程2执行, 但是此时i值为0并没有改变(线程1没有执行变量写回内存操作), 线程2继续执行完了步骤1、2、3,把此时的i值为1写回了内存, 线程1又被调度获得了CPU,就继续之前没有进行的步骤3操作, 线程1将之前执行步骤1、2的i值为1又写回了内存, 覆盖了线程2写入的i值为1,此时的i值还是为1。就导致了数据错误。 if-then-do 类似于 i++的错误,也是因为多线程操作不是原子操作。 死锁 《操作系统》定义了死锁:如果一组进程中的每一个进程都在等待仅有该组进程中的其他进程才能引发的事件,那么成这组进程是死锁的。 著名的 HashMap 的死循环问题。 预防死锁产生的原则: 所有线程都按照相同的顺序来获取锁。 死锁问题的排查 jps/jstack 多线程经典问题:哲学家用餐问题。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:10:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"线程安全 线程的默认实现几乎都是线程不安全的,而且线程的操作不是原子性操作,导致共享变量的数据可能出错。 实现线程安全的基本手段: 不可变的类:数据出错的根本原因就是并发的修改数据。 Integer/String synchronized 同步块 1. public synchronized method(){/**Concurrent code**/} 2. public static synchronized method(){/**Concurrent code**/} 3. synchronized((Object) Lock){/**Concurrent code**/} 同步块同步了什么东西? synchronized(Object) 把这个对象当成锁 static synchronized 方法 把 Class 对象当成锁 实例的 synchronized 方法 把该实例当成锁 普通 Collection 转 ConcurrentCollection Collections.synchronized JUC Atomic 类 ConcurrentHashMap 任何 HashMap 有线程不安全的地方都使用 ConcurrentHashMap 或者无脑使用 ReentrantLock 可重入锁 语法: ReentrantLock lock = new ReentrantLock(); lock.lock();//加锁 /**Concurrent code**/ lock.unlock();//解锁 条件对象 Condition newCondtion = Lock.newCondition(); newCondition.await();//不满足条件,阻塞,放弃锁 newCondition.signal();//随机唤醒其他线程 读写锁 ReentrantReadWriteLock 注意 synchronized 也是可重入的 可重入锁也叫递归锁,意思是当一个线程中的某个对象持有锁的时候可以再次持有锁。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:11:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"Object 类里的线程方法 Java 从一开始就把线程作为语言特性,提供了语言级别的支持。 为什么 Java 中的所有对象都能成为锁 Object 中有 wait()/notify()/notifyAll()方法 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:12:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"线程池 构建一个新的线程是要付出一定代价的,因为涉及与操作系统的交互。如果程序中创建了大量的生命周期很短的线程,就应该使用线程池(thread pool);一个线程池中包含许多准备运行的空闲线程。将 Runnable 对象交给线程池,就会有一个线程调用 run 方法。当 run 方法退出,线程不会死亡,而是在线程池中准备下一次运行。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:13:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"执行器(Executor) 执行器类中有许多静态工场方法构建线程池。 newFixedThreadPool 该池包含固定数量的线程,空闲线程会被一直保留 newCachedThreadPool 必要时创建新线程,空闲线程会被保留60秒 newSingleThreadPool 只有一个线程的线程池,该线程顺序执行每一个提交的任务(类似于Swing事件分配线程) newScheduledThreadPool 用于预定执行而构建的固定线程池,替代java.util.Timer newSingleThreadScheduledPool 用于预定执行而构建的单线程池 下面总结了在使用连接池时应该做的事情: 调用 Executors 类中的静态方法 newCacheThreadPool 或 newFixedThreadPool。 调用 submit 提交 Runnable 或者 Callable 对象。 如果想要取消一个任务,或如果提交 Callable 对象,就要保存好返回的 Future 对象。 当不再提交任何任务时,调用 shotdown()。该方法启动该线程池的关闭序列。被关闭的执行器不再接受新的任务。当所有任务完成以后,线程池中的线程死亡。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:13:1","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"ThreadLocal ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:14:0","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"概念 ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。也就是说 ThreadLocal 可以为每个线程创建一个单独的变量副本,相当于线程的 private static 类型变量。 ThreadLocal 的作用和同步机制有些相反:同步机制是为了保证多线程环境下数据的一致性;而 ThreadLocal 是保证了多线程环境下数据的独立性。 对于 ThreadLocal 类型的变量,在一个线程中设置值,不影响其在其它线程中的值。也就是说 ThreadLocal 类型的变量的值在每个线程中是独立的。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:14:1","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"ThreadLocal 实现 ThreadLocal 是构造函数只是一个简单的无参构造函数,并且没有任何实现。 Set(T value) public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } set(T value) 方法中,首先获取当前线程,然后在获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则将 value 保存到 ThreadLocalMap 中,并用当前 ThreadLocal 作为 key;否则创建一个 ThreadLocalMap 并给到当前线程,然后保存 value。 ThreadLocalMap 相当于一个 HashMap,是真正保存值的地方。 get() public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings(\"unchecked\") T result = (T)e.value; return result; } } return setInitialValue(); } 同样的,在 get() 方法中也会获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则把获取 key 为当前 ThreadLocal 的值;否则调用 setInitialValue() 方法返回初始值,并保存到新创建的 ThreadLocalMap 中。 initialValue() private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } initialValue() 是 ThreadLocal 的初始值,默认返回 null,子类可以重写改方法,用于设置 ThreadLocal 的初始值。 remove() public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } ThreadLocal 还有一个 remove() 方法,用来移除当前 ThreadLocal 对应的值。同样也是通过当前线程的 ThreadLocalMap 来移除相应的值。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:14:2","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"当前线程的 ThreadLocalMap 在 set,get,initialValue 和 remove 方法中都会获取到当前线程,然后通过当前线程获取到 ThreadLocalMap,如果 ThreadLocalMap 为 null,则会创建一个 ThreadLocalMap,并给到当前线程。 \u003e 每一个线程都会持有有一个 ThreadLocalMap,用来维护线程本地的值。 在使用 ThreadLocal 类型变量进行相关操作时,都会通过当前线程获取到 ThreadLocalMap 来完成操作。每个线程的 ThreadLocalMap 是属于线程自己的,ThreadLocalMap 中维护的值也是属于线程自己的。这就保证了 ThreadLocal 类型的变量在每个线程中是独立的,在多线程环境下不会相互影响。 ","date":"2020-11-07","objectID":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/:14:3","tags":null,"title":"Java多线程","uri":"/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"categories":null,"content":"Docker ","date":"2020-10-19","objectID":"/docker/:0:0","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"什么是 docker Docker 是一个开放源代码软件,是一个开放平台,用于开发应用、交付(shipping)应用、运行应用。 Docker 允许用户将基础设施(Infrastructure)中的应用单独分割出来,形成更小的颗粒(容器),从而提高交付软件的速度。 Docker 容器与虚拟机类似,但二者在原理上不同。容器是将操作系统层虚拟化,虚拟机则是虚拟化硬件,因此容器更具有便携性、高效地利用服务器。 容器更多的用于表示 软件的一个标准化单元。由于容器的标准化,因此它可以无视基础设施(Infrastructure)的差异,部署到任何一个地方。另外,Docker 也为容器提供更强的业界的隔离兼容。 Docker 利用 Linux 核心中的资源分离机制,例如 cgroups,以及 Linux 核心名字空间(namespaces),来创建独立的容器(containers)。 ","date":"2020-10-19","objectID":"/docker/:1:0","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"Docker 引擎 Docker 引擎(Docker Engine)是一个服务端-客户端结构的应用,主要有这些部分:Docker 守护进程、Docker Engine AP 页面存档备份,存于互联网档案馆、Docker 客户端。 Docker 守护进程(Docker daemons),也叫 dockerd ,是一个持久化的进程,用户管理容器。守护进程会监听 Docker Engine API 页面存档备份,存于互联网档案馆 的请求。 Docker Engine API 页面存档备份,存于互联网档案馆是用于与 Docker 守护进程交互用的的 API。它是一个 RESTful API,因此它不仅可以被 Docker 客户端调用,也可以被 wget 和 curl 等命令调用。 Docker 客户端,也叫 docker,是大部分用户与 Docker 交互的主要方式。用户通过客户端将命令发送给守护进程。命令会遵循 Docker Engine API 页面存档备份,存于互联网档案馆。 ","date":"2020-10-19","objectID":"/docker/:2:0","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"Docker 对象 Docker 的对象是指 Images、Containers、Networks、Volumes、Plugins 等等。 容器(Containers)是镜像的可运行的实例。容器可通过 API 或 CLI(命令行)进行操控。 镜像(Images)是一个只读模板,用于指示创建容器。 镜像分层(layers)构建的,而定义这些层次的文件叫 Dockerfile。 服务(Services)允许用户跨越不同的 Docker 守护进程(Docker daemons)的情况下增加容器,并将这些容器分为管理者(managers)和工作者(workers),让他们为 swarm 共同工作。 ","date":"2020-10-19","objectID":"/docker/:3:0","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"Docker 能做什么 保证开发、测试、交付、部署的环境完全一致 抱枕资源的隔离 启动临时的、用完及弃的环境,例如测试 秒级的超大规模部署和扩容 ","date":"2020-10-19","objectID":"/docker/:4:0","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"Docker 的基本概念 镜像 images 一个预先定义好的模板文件,Docker 引擎可以按照这个模板文件启动无数个一模一样互不干扰的容器。 容器 container 是一台虚拟的计算机,它拥有独立的 网络 文件系统 进程 它默认和宿主机不发生任何交互,这意味着数据是没有持久化的。 ","date":"2020-10-19","objectID":"/docker/:5:0","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"docker 对比 VM ","date":"2020-10-19","objectID":"/docker/:5:1","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"docker pull/images docker pull是下载一个指定的镜像,方便随时启动。例如:docker pull mysql:6.7.28 下载一个 mysql 版本为 5.7.28。 docker images产看本地已经有的镜像。 ","date":"2020-10-19","objectID":"/docker/:5:2","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"docker run/ps docker run装载镜像成为一个容器。 就好像从蛋糕模子做出来一个蛋糕 在这个容器看来,自己就是一台独立的计算机 每个容器有一个 ID,支持缩写,例如容器 id 是 234sdhfsakjhf,就可以简写为 234s docker run -it \u003c镜像名\u003e \u003c镜像中要运行的命令和参数\u003e 交互式命令行,当前 shell 中运行,CTRL+c 退出 docker run -d \u003c镜像名\u003e \u003c镜像中要运行的命令和参数\u003e daemon 模式,在后台运行容器 docker run --name为容器指定一个名字 --restart=always遇到错误自动重启 -v \u003c本地文件\u003e:\u003c容器文件\u003e -p \u003c本地端口\u003e:\u003c容器端口\u003e -e NAME=VALUE ","date":"2020-10-19","objectID":"/docker/:5:3","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"docker start/stop 启动/停止一个容器 可以想象为开关机 ","date":"2020-10-19","objectID":"/docker/:5:4","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"docker rm 删除一个容器 想象成将电脑丢掉 docker rm -f \u003c容器名或id\u003e 强制删除某个容器 ","date":"2020-10-19","objectID":"/docker/:5:5","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"docker exec 指定目标容器,进入容器执行命令 docker run -it \u003c目标容器ID\u003e \u003c目标命令(通常为bash)\u003e 调试、解决问题必备命令 ","date":"2020-10-19","objectID":"/docker/:5:6","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"docker logs docker logs\u003c容器ID或容器名\u003e 查看目标容器的输出 docker logs -f\u003c容器ID或容器名\u003e ","date":"2020-10-19","objectID":"/docker/:5:7","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"docker inspect 高级命令:查看容器的详细状态(json 格式) ","date":"2020-10-19","objectID":"/docker/:5:8","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"Docker 分层的镜像 分层的镜像实现了 docker 容器的复用。 ","date":"2020-10-19","objectID":"/docker/:6:0","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"Dockerfile 指定镜像如何生成 编写一个 Dockerfile 在 Docker 中创建镜像最常用的方式,就是使用 Dockerfile。Dockerfile 是一个 Docker 镜像的描述文件,我们可以理解成火箭发射的 A、B、C、D…的步骤。Dockerfile 其内部包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。 #基于centos镜像 FROM centos #维护人的信息 MAINTAINER The CentOS Project \[email protected]\u003e #安装httpd软件包 RUN yum -y update RUN yum -y install httpd #开启80端口 EXPOSE 80 #复制网站首页文件至镜像中web站点下 ADD index.html /var/www/html/index.html #复制该脚本至镜像中,并修改其权限 ADD run.sh /run.sh RUN chmod 775 /run.sh #当启动容器时执行的脚本文件 CMD [\"/run.sh\"] docker build . 以当前目录建立一个镜像 image 每个镜像都会有一个唯一的 id ","date":"2020-10-19","objectID":"/docker/:6:1","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"Docker 的镜像仓库与 tag 可以任意对镜像进⾏ tag 操作 决定了未来这个镜像会被 push 到哪⾥ 决定了未来从哪⾥下载镜像 可以⽅便的创建镜像仓库的私服 --registry-mirror --insecure-registry ","date":"2020-10-19","objectID":"/docker/:6:2","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"Docker 与 Kubernetes(k8s) 业界有一个形象的比喻 传统服务被比作宠物 pets,意思是当对外的服务出了问题,宠物意味着你要尽心尽力的维护它,直到它健康没有任何问题。 当基于 docker 的 k8s 出现后,服务就被比作 cattle,意思就是牲畜,当牲畜出现了问题,就把他杀掉再来一只顶替(杀掉容器\u0026\u0026重新启动容器),而且成本很低。 ","date":"2020-10-19","objectID":"/docker/:7:0","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"k8s kubernetes master 主节点 用于管理 Node Node 子节点,里面存放 kubelet 当前节点的管理员 Kube-proxy 提供网络服务 Pod 就可以理解为上面所说的 cattle,里面运行一个或多个 docker 容器,当他们其中有个别出了问题,就杀掉重启。 k8s 提供自动滚动更新(rolling update) 服务,可以保证你的服务在更新的过程中不被更新中断 ","date":"2020-10-19","objectID":"/docker/:7:1","tags":null,"title":"Docker","uri":"/docker/"},{"categories":null,"content":"正则表达式 ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:0:0","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"为什么要学习正则表达式 可以在支持正则表达式的文本编辑器中提高工作效率 用很少的代码完成复杂的文本提取工作 ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:1:0","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"什么是正则表达式 正则表达式用于描述文本或字符串的一组规则。 ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:2:0","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"正则表达式的作用 处理文本 提取信息 ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:3:0","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"Java 中的转义字符 为什么需要转义字符 因为字符不可见,无法用一个键来表示。 常用的转义字符 \\n 换行:使光标到行首 \\r 回车:使光标从当前位置下移一行 \\t 制表符:一般是四个空格 \\ \\” \\’ ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:4:0","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"正则表达式中的字符 用到以下字符原来的意思的时候需要转义 ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:5:0","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"常用元字符 符号 含义 ^ 起始位置 $ 结束位置 . 单个任意字符(不一定包含换行符) \\w 单个字符(字母数字下划线) \\s 单个空白字符(\\n\\r\\t) \\d 单个数字字符 \\b 单词的开始或结束:表示所在位置的一侧为单词字符,另一侧为非单词字符、字符串的开始或结束位置 ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:5:1","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"重复 符号 含义 * 0 次或多次 + 一次或多次 ? 0 次或 1 次 {n} n 次 {n,} \u003e=n 次 {n,m} n 到 m 次 ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:5:2","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"选择 符号 含义 [aeiou] 单个 aeiou 中的字符之一:从五个字母里选择一个 [0-9] 单个数字字符 [A-Z] 单个大写字母 [A-Z0-9] 大写字母或者数字或者下划线 Hi|hi 等价于 [Hh]i Hi 或者 hi ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:5:3","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"反义 符号 含义 [^aeiou] 单个除 aeiou 之外的字符 [^a] 单个除了 A 之外的字符 \\W 单个非\\w(字母数字下划线汉字)之外的字符 \\S 单个非\\s(空白) \\D 单个非\\d(数字字符) \\B 非开头和结束位置 ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:5:4","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"一些实例 ^\\d{5,12}$ 5 到 12 位的数字 ^(0|[1-9][0-9]*)$ 0 或者⾮零开头的数字 ^(-?\\d+)(.\\d+)?$ ⼩数 \\d{3}-\\d{8}|\\d{4}-\\d{7,8} 国内的电话号码 ^\\d{4}-\\d{1,2}-\\d{1,2} yyyy-MM-dd ⽇期格式 \\n\\s*\\r 空⽩⾏ ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:6:0","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"Java 中的正则表达式 String split() replaceAll()/replaceFirst() matches() 尽量少用或者少编译因为效率低下。 Java 中正则表达式是非常昂贵的,正则表达式编译的源码非常复杂。 正则表达式需要解析。 匹配过程是靠\"回溯\"来匹配的,所以效率低下。 ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:7:0","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"实战 电话号码匹配 public class PhoneNumberMatcher { // 请编写一个函数,判断一个字符串是不是合法的固定电话号码 // 合法的固定电话号码为:区号-号码 // 123-45678901 区号必须以0开头 // 021-1234567 三位区号后面只能跟八位电话号码 public static boolean isPhoneNumber(String str) { Pattern telNumberPattern = Pattern.compile(\"[0]\\\\d{2}-[^0]\\\\d{7}|[0]\\\\d{3}-[^0]\\\\d{6,7}\"); return telNumberPattern.matcher(str).find(); } } ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:7:1","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"分组与捕获 想要将所有符合正则表达式的⽂本抓出来处理。 使用括号的来指定一个被捕获的分组。 分组的编号从 1 开始,group(0)表示的是完整匹配正则表达式的字符串,group(1)表示的是匹配正则的第一个分组。 分组的编号只看左括号。 (?:)不捕获和分配编号,括号只⽤于分组或标记优先级。 ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:8:0","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"在 Java 中处理捕获 Pattern matcher() Matcher find() group() ","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:8:1","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"使用正则表达式处理 GClog public class GCLogAnalyzer { // 在本项目的根目录下有一个gc.log文件,是JVM的GC日志 // 请从中提取GC活动的信息,每行提取出一个GCActivity对象 // 2019-08-21T07:48:17.401+0200: 2.924: [GC (Allocation Failure) [PSYoungGen: // 393216K-\u003e6384K(458752K)] 416282K-\u003e29459K(1507328K), 0.0051622 secs] [Times: user=0.02 // sys=0.00, real=0.01 secs] // 例如,对于上面这行GC日志, // [PSYoungGen: 393216K-\u003e6384K(458752K)] 代表JVM的年轻代总内存为458752,经过GC后已用内存从393216下降到了6384 // 416282K-\u003e29459K(1507328K) 代表JVM总堆内存1507328,经过GC后已用内存从416282下降到了29459 // user=0.02 sys=0.00, real=0.01 分别代表用户态消耗的时间、系统调用消耗的时间和物理世界真实流逝的时间 // 请将这些信息解析成一个GCActivity类的实例 // 如果某行中不包含这些数据,请直接忽略该行 public static List\u003cGCActivity\u003e parse(File gcLog) throws IOException { List\u003cGCActivity\u003e list = new ArrayList\u003c\u003e(); String youngGenRegex = \"\\\\[PSYoungGen:[ ](\\\\d+)K-\u003e(\\\\d+)K\\\\((\\\\d+)K\\\\)\\\\]\"; String jvmRegex = \"[ ](\\\\d+)K-\u003e(\\\\d+)K\\\\((\\\\d+)K\\\\),\"; String timeRegex = \"\\\\[Times:[ ]user=(-?\\\\d+\\\\.\\\\d+?)[ ]sys=(-?\\\\d+\\\\.\\\\d+?),[ ]real=(-?\\\\d+\\\\.\\\\d+?)[ ]secs\\\\]\"; Pattern youngGenGC = Pattern.compile(youngGenRegex); Pattern jvmGC = Pattern.compile(jvmRegex); Pattern times = Pattern.compile(timeRegex); BufferedReader bufferedReader = new BufferedReader(new FileReader(gcLog)); String str = null; while ((str = bufferedReader.readLine()) != null) { Matcher matcherYoungGen = youngGenGC.matcher(str); Matcher matcherJvm = jvmGC.matcher(str); Matcher matcherTimes = times.matcher(str); while (matcherYoungGen.find() \u0026\u0026 matcherJvm.find() \u0026\u0026 matcherTimes.find()) { list.add(new GCActivity( Integer.parseInt(matcherYoungGen.group(1)), Integer.parseInt(matcherYoungGen.group(2)), Integer.parseInt(matcherYoungGen.group(3)), Integer.parseInt(matcherJvm.group(1)), Integer.parseInt(matcherJvm.group(2)), Integer.parseInt(matcherJvm.group(3)), Double.parseDouble(matcherTimes.group(1)), Double.parseDouble(matcherTimes.group(2)), Double.parseDouble(matcherTimes.group(3)) )); } } return list; } public static void main(String[] args) throws IOException { List\u003cGCActivity\u003e activities = parse(new File(\"gc.log\")); activities.forEach(System.out::println); } public static class GCActivity { // 年轻代GC前内存占用,单位K int youngGenBefore; // 年轻代GC后内存占用,单位K int youngGenAfter; // 年轻代总内存,单位K int youngGenTotal; // JVM堆GC前内存占用,单位K int heapBefore; // JVM堆GC后内存占用,单位K int heapAfter; // JVM堆总内存,单位K int heapTotal; // 用户态时间 double user; // 系统调用消耗时间 double sys; // 物理世界流逝的时间 double real; public GCActivity( int youngGenBefore, int youngGenAfter, int youngGenTotal, int heapBefore, int heapAfter, int heapTotal, double user, double sys, double real) { this.youngGenBefore = youngGenBefore; this.youngGenAfter = youngGenAfter; this.youngGenTotal = youngGenTotal; this.heapBefore = heapBefore; this.heapAfter = heapAfter; this.heapTotal = heapTotal; this.user = user; this.sys = sys; this.real = real; } @Override public String toString() { return \"GCActivity{\" + \"youngGenBefore=\" + youngGenBefore + \", youngGenAfter=\" + youngGenAfter + \", youngGenTotal=\" + youngGenTotal + \", heapBefore=\" + heapBefore + \", heapAfter=\" + heapAfter + \", heapTotal=\" + heapTotal + \", user=\" + user + \", sys=\" + sys + \", real=\" + real + '}'; } public int getYoungGenBefore() { return youngGenBefore; } public int getYoungGenAfter() { return youngGenAfter; } public int getYoungGenTotal() { return youngGenTotal; } public int getHeapBefore() { return heapBefore; } public int getHeapAfter() { return heapAfter; } public int getHeapTotal() { return heapTotal; } public double getUser() { return user; } public double getSys() { return sys; } public double getReal() { return real; } } } gc.log(省略版) Java HotSpot(TM) 64-Bit Server VM (25.181-b13) for linux-amd64 JRE(1.8.0_181-b13), built on Jul 7 2018 00:56:38 by \"java_re\" with gcc 4.3.020080428 (Red Hat 4.3.0-8) Memory: 4k page, physical 32707224k(7763808k free),swap 16777212k(16772860k free) CommandLine flags: -XX:InitialHeapSize=1610612736-XX:MaxHeapSize=1610612736 -XX:+PrintGC -XX:+PrintGCDateStamps-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers-XX:+UseCompressed","date":"2020-09-24","objectID":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/:8:2","tags":null,"title":"正则表达式","uri":"/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"categories":null,"content":"基本概念 ","date":"2020-07-18","objectID":"/git%E5%92%8Cgithub/:0:0","tags":null,"title":"Git和Github","uri":"/git%E5%92%8Cgithub/"},{"categories":null,"content":"什么是Git git是一个分布式版本控制程序,最初由林纳斯·托瓦兹创作,于2005年以GPL发布。最初目的是为更好地管理Linux内核开发而设计。 git是用于Linux内核开发的版本控制工具。与CVS、Subversion一类的集中式版本控制工具不同,它采用了分布式版本库的作法,不需要服务器端软件,就可以运作版本控制,使得源代码的发布和交流极其方便。 git的速度很快,这对于诸如Linux内核这样的大项目来说自然很重要。git最为出色的是它的合并追踪(merge tracing)能力。 ","date":"2020-07-18","objectID":"/git%E5%92%8Cgithub/:1:0","tags":null,"title":"Git和Github","uri":"/git%E5%92%8Cgithub/"},{"categories":null,"content":"Git实现原理 git和其他版本控制系统(如CVS)有不小的差别,git本身关心文件的整体性是否有改变,但多数的版本控制系统如CVS或Subversion系统则在乎文件内容的差异。git拒绝保持每个文件的版本修订关系。因此查看一个文件的历史需要遍历各个history快照;git隐式处理文件更名,即同名文件默认为其前身,如果没有同名文件则在前一个版本中搜索具有类似内容的文件。 git更像一个文件系统,直接在本机上获取数据,不必连线到主机端获取数据。 每个开发者都可有全部开发历史的本地副本,changes从这种本地repository复制给其他开发者。这些changes作为新增的开发分支被导入,可以与本地开发分支合并。 git是用C语言开发的,以追求最高的性能。 ","date":"2020-07-18","objectID":"/git%E5%92%8Cgithub/:2:0","tags":null,"title":"Git和Github","uri":"/git%E5%92%8Cgithub/"},{"categories":null,"content":"什么是Github GitHub是通过Git进行版本控制的软件源代码托管服务平台。 GitHub同时提供付费账户和免费账户。这两种账户都可以创建公开或私有的代码仓库,但付费用户支持更多功能。 Github除了允许个人和组织创建和访问保管中的代码以外,它也提供了一些方便社会化共同软件开发的功能,即一般人口中的社群功能,包括允许用户追踪其他用户、组织、软件库的动态,对软件代码的改动和bug提出评论等。GitHub也提供了图表功能,用于概观显示开发者们怎样在代码库上工作以及软件的开发活跃程度。 应用Git ","date":"2020-07-18","objectID":"/git%E5%92%8Cgithub/:3:0","tags":null,"title":"Git和Github","uri":"/git%E5%92%8Cgithub/"},{"categories":null,"content":"初始化一个本地仓库 在任意一个终端程序(gitbash\\powershell等)中,cd到一个你要用git管理的目录,然后键入命令 git init ,就会在当前目录下创建一个本地git仓库(.git),但是要注意的是这个仓库是以“.”开头的默认隐藏文件,需要 ls -a 才能查看到。 ","date":"2020-07-18","objectID":"/git%E5%92%8Cgithub/:4:0","tags":null,"title":"Git和Github","uri":"/git%E5%92%8Cgithub/"},{"categories":null,"content":"进行本地提交 git add 命令用于将变化的文件,从工作区提交到暂存区。它的作用就是告诉 Git,下一次哪些变化需要保存到仓库区。用户可以使用git status命令查看目前的暂存区放置了哪些文件。 通常使用 git add . 意思是将当前目录下所有变化的文件都放入暂存区。 # 将指定文件放入暂存区 $ git add \u003cfile\u003e # 将指定目录下所有变化的文件,放入暂存区 $ git add \u003cdirectory\u003e # 将当前目录下所有变化的文件,放入暂存区 $ git add . git commit 命令用于将暂存区中的变化提交到仓库区。 # -m参数用于指定 commit 信息,是必需的。如果省略-m参数,git commit会自动打开文本编辑器,要求输入。 $ git commit -m \"message\" -m参数用于添加提交说明。 # -v参数可以显示所有的改动信息,也是在文本编辑器首行输入文字信息。 $ git commit -v \"verbose\" 提交时显示所有改动信息。 # -a参数用于先将所有工作区的变动文件,提交到暂存区,再运行git commit。用了-a参数,就不用执行 git add . 命令了。 $ git commit -am \"message\" 直接提交所有变化了的文件。 将Github与Git相关联 当你在Github新建了一个仓库的时候 默认页面中会出现以下两行: $ git remote add origin 你仓库的ssh链接 $ git push -u origin master 你要做的是: 切换到你的项目目录,键入 git remote add origin 你仓库的ssh链接命令,成功后你的本地git仓库就和你的github的新建仓库相关联了。 把本地仓库的内容提交之后,再键入 git push -u origin master 这时你的提交内容就成功上传到你github仓库的主分支了。 学习GitHub的资料 点我购买 ","date":"2020-07-18","objectID":"/git%E5%92%8Cgithub/:5:0","tags":null,"title":"Git和Github","uri":"/git%E5%92%8Cgithub/"},{"categories":null,"content":"hexo 和 hugo 都是现在比较火的搭建博客的第三方框架 ","date":"2020-07-16","objectID":"/hexo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:1:0","tags":null,"title":"Hexo搭建个人博客","uri":"/hexo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"前期准备 git nodejs 两者直接安装最新版本或者推荐版本即可。 ","date":"2020-07-16","objectID":"/hexo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:1:1","tags":null,"title":"Hexo搭建个人博客","uri":"/hexo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"开始搭建 因为 hexo 是基于 nodejs 框架的,所以搭建前必须要安装 nodejs。 如果已经安装完毕 ,win+R 中输入cmd测试命令 node -v是否成功。 安装成功后要在命令行输入如下命令npm -v 测试 npm(node package manager )版本。 测试成功后说明 nodejs 的步骤已经搞定了。然后继续在命令行输入npm install -g cnpm --registry=https://registry.npm.taobao.org这是使用淘宝团队的 cnpm 镜像,完全可以代替 npm 使用。使用时需要将命令中的npm全部改成cnpm即可。 cnpm 安装完毕后要用命令cnpm -v来检验是否安装成功。 在进行 hexo 框架的安装,命令cnpm install -g hexo-cli -g global全局安装 -cli Command Line Interface命令行界面 。 安装完 hexo 使用hexo -v验证安装成功与否。 使用命令mkdir blog,在默认路径下建立一个文件夹 blog,博主的默认路径是:C:\\Users\\cyunt\u003e 注意:我们在这里建立了一个 blog 文件,也就是我们的博客文件,这之后的所有操作都是在这个文件上进行的,所以如果之后的操作出现的什么问题,就可以删除这个文件再重新做,目录下 C:\\Users\\cyunt\u003edel blog这是删除这个文件的命令。 根据你的命令行的路径,可以在 GUI 界面找到你刚才建立的 blog 文件夹,(如果之后除了什么问题就 del 掉 blog 文件夹)windows 用户之后可以用鼠标来操作文件。 用命令行继续操作,cd blog进入 blog 文件,C:\\Users\\cyunt\\blog\u003e此时是位于这个目录当中。 在 blog 路径下使用命令hexo init来初始化一个博客,hexo 会自动生成一个博客。 初始化完成会提示INFO Start blogging with Hexo! 可以继续使用命令dir查看生成的文件。 继续启动博客使用命令hexo s 启动成功后会出现如下提示,可以使用http://localhost:4000来查看本地 4000 端口号的博客样式,按ctrl+c断开本地服务,即退出。 INFO Start processing INFO Hexo is running at http://localhost:4000 . Press Ctrl+C to stop. 查看本地博客以后,如果你想要新建一篇博客。 就可以在命令行输入hexo n \"hellogithub\" 之后出现提示INFO Created: ~\\blog\\source\\_posts\\hellogithub.md 之后在source\\_posts\\路径下就会生成一个hellogithub.md的 markdown 文件,windows 用户可以用 GUI 找到这个文件用 markdown 软件去编辑你的博客啦~!**PS:**不会使用 markdown 的朋友可以去找教程看看 编辑完成后使用命令hexo clean运行完提示如下。 INFO Deleted database. INFO Deleted public folder. 作用是清除缓存文件 (db.json) 和已生成的静态文件 (public)。 在某些情况(尤其是更换主题后),如果发现您对站点的更改无论如何也不生效,您可能需要运行该命令。 然后继续使用。 再继续使用命令hexo g重新生成博客,然后再hexo s启动本地在http://localhost:4000可以查看博客内容。 之前的操作就搞定了本地的博客部署,下一步就是将我们的博客给部署到互联网上,使它能够被访问。 登录 github,新建一个代码仓库,然后 Repository name*填 你的昵称.github.io,点击Create repository,这样就建立了一个代码仓库。 建立仓库之后再回到命令行,在 blog 目录下输入命令cnmp install --save hexo-deployer-git 这是下载一个 git 部署插件,以便于我们之后将博客部署到 github 上。 找到之前的C:\\Users\\cyunt\\blog路径下面有一个_config.yml文件,我们用记事本或者其他文本编辑工具(notepad++ or sublime text)打开,找到文档最下面的 #Deployment ##Docs: https://hexo.io/docs/deployment.html 修改代码如下: deploy: type: 'git' repo: https://github.com/0x522/0x522.github.io.git branch: master 注意:type,repo,branch后面需要加空格,repo就是你的代码仓库对应的 https 地址,建立仓库后会自动生成。 再将博客部署到 github 上,能够提供互联网访问,命令hexo d 如果这一步失败,就先用 git bash 设置你的 github 的用户名和 email 设置步骤: 1.打开 git bash 2.写入两个命令 git config -global user.name \"你的用户名\" git config -global user.email \"你的email\" 注意 name 和 email 后面加空格,这是将你的 github 与 git 绑定 然后再运行hexo d 如果提示让你输入用户名和 email 来确认你的身份。 就继续在 cmd 命令行内输入这两个命令,然后再用命令hexo d git config -global user.name \"你的用户名\" git config -global user.email \"你的email\" github 部署就完成了,赶快登录你的https://你的github用户名.github.io来访问你的博客吧~ 关于 npm 当一个网站依赖的代码越来越多,程序员发现这是一件很麻烦的事情: 去 jQuery 官网下载 jQuery 去 BootStrap 官网下载 BootStrap 去 Underscore 官网下载 Underscore …… 有些程序员就受不鸟了,一个拥有三大美德的程序员 Isaac Z. Schlueter(以下简称 Isaaz)给出一个解决方案:用一个工具把这些代码集中到一起来管理吧! 这个工具就是他用 JavaScript (运行在 Node.js 上)写的 npm,全称是 Node Package Manager 具体步骤: NPM 的思路大概是这样的: 买个服务器作为代码仓库(registry),在里面放所有需要被共享的代码 发邮件通知 jQuery、Bootstrap、Underscore 作者使用 npm publish 把代码提交到 registry 上,分别取名 jquery、bootstrap 和 underscore(注意大小写) 社区里的其他人如果想使用这些代码,就把 jquery、bootstrap 和 underscore 写到 package.json 里,然后运行 npm install ,npm 就会帮他们下载代码 下载完的代码出现在 node_modules 目录里,可以随意使用了。 这些可以被使用的代码被叫做「包」(package),这就是 NPM 名字的由来:Node Package(包) Manager(管理器)。 关于 cnpm 因为 npm 安装插件是从国外服务器下载,受网络影响大,可能出现异常,所以我们乐于分享的淘宝团队干了这事。来自官网:”这是一个完整 npmjs.org 镜像,你可以用此代替官方版本(只读),同步频率目前为 10 分钟 一次以保证尽量与官方服务同步。” ","date":"2020-07-16","objectID":"/hexo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:1:2","tags":null,"title":"Hexo搭建个人博客","uri":"/hexo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"使用 Hugo 搭建个人博客 ","date":"2020-07-16","objectID":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:0:0","tags":null,"title":"Hugo搭建个人博客","uri":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"前期准备工作 下载 hugo,查看官方文档根据步骤下载。 测试 hugo 能否运行,在 gitbash 或者 cmd 中写入命令 hugo version,出现 Hugo Static Site Generator v0.74.1-15163266 类似的字样说明你安装成功。 ","date":"2020-07-16","objectID":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:1:0","tags":null,"title":"Hugo搭建个人博客","uri":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"创建你的项目 Hugo 提供了一个 new 命令来创建一个新的网站:你应当在一个安全的目录下运行以下命令。 hugo new site my_website cd my_website ","date":"2020-07-16","objectID":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:2:0","tags":null,"title":"Hugo搭建个人博客","uri":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"初始化一个本地 git 仓库 git init ","date":"2020-07-16","objectID":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:3:0","tags":null,"title":"Hugo搭建个人博客","uri":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"为博客选择一个主题 git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke 利用上面一行命令下载一个主题,如果有需要请访问hugo 主题页面令行下载其他主题。 echo 'theme = \"ananke\"' \u003e\u003e config.toml 添加主题到配置文件。 ","date":"2020-07-16","objectID":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:4:0","tags":null,"title":"Hugo搭建个人博客","uri":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"添加一些内容 hugo new posts/my-first-post.md,这个操作是在 content\\posts 目录下建立一个你自己的 markdown 文件,默认都是在 posts 下添加.md 文件,hugo 就是利用这些.md 文件来作为博客静态页面的内容。 --- title: \"My First Post\" date: 2019-03-26T08:47:11+01:00 draft: true --- ## 里面的内容可以按照格式修改。 ## draft 参数表示是否被部署 true则不会被直接部署。 ","date":"2020-07-16","objectID":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:5:0","tags":null,"title":"Hugo搭建个人博客","uri":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"启动 hugo 本地服务器 hugo server -D 或者 hugo server | EN +------------------+----+ Pages | 10 Paginator pages | 0 Non-page files | 0 Static files | 3 Processed images | 0 Aliases | 1 Sitemaps | 1 Cleaned | 0 Total in 11 ms Watching for changes in /Users/bep/quickstart/{content,data,layouts,static,themes} Watching for config changes in /Users/bep/quickstart/config.toml Environment: \"development\" Serving pages from memory Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender Web Server is available at http://localhost:1313/ (bind address 127.0.0.1) Press Ctrl+C to stop ","date":"2020-07-16","objectID":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:6:0","tags":null,"title":"Hugo搭建个人博客","uri":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"自定义主题 您的新站点看起来已经不错了,但是在将其发布给公众之前,您需要对其进行一些调整,只需要配置 config.toml 文件。 ","date":"2020-07-16","objectID":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:7:0","tags":null,"title":"Hugo搭建个人博客","uri":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"建立静态页面 hugo -D或者hugo 将会在你的站点目录下输出一个 public 目录,这个就是利用 hugo 生成的静态页面。 之后会在 public 重新建立一个本地 git 仓库,与 github 上面一个对应的仓库关联。 每新写一篇博客就利用hugo命令重新生成一下 public 目录,也可以说是更新 public 目录。 ","date":"2020-07-16","objectID":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:8:0","tags":null,"title":"Hugo搭建个人博客","uri":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"连接到 github 首先你需要在 github 建立一个仓库,名字为(0x522.github.io),0x522 是作者的用户名,这里需要该为你自己的。 运行命令 git remote add origin 你仓库的ssh或https链接 这样就把 public 的本地仓库和你的 github 中的 repo 关联。 在 public 目录下 git add . git commit -m 你要备注的内容 git push -u origin master 这是第一次 push 的写法,以后都直接 git push 即可。 每次更新博客时,重复以上三条操作,把更新推送到 GitHub。 ","date":"2020-07-16","objectID":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:9:0","tags":null,"title":"Hugo搭建个人博客","uri":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"搭建完成,快去丰富你的博客吧! 通过访问仓库的域名就可以直接访问你的博客啦! ","date":"2020-07-16","objectID":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/:10:0","tags":null,"title":"Hugo搭建个人博客","uri":"/hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"categories":null,"content":"什么是Maven Maven 是一个项目管理工具,可以对 Java 项目进行构建、依赖管理。 Maven 也可被用于构建和管理各种项目,例如 java,Ruby,Scala 和其他语言编写的项目。Maven 曾是 Jakarta 项目的子项目,现为由 Apache 软件基金会主持的独立 Apache 项目。 包的概念 在java的概念中,包就是一些被用于某些功能的类的集合,把各种类打包压缩在一个文件中,表现为jar包war包等形式。 JVM的工作被设计地相当简单 执行一个类的字节码 假如执行过程中碰到了该类引用的其他类,就找到它并加载它。 ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:0:0","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"去哪里加载包中的类 类路径Classpath 答案是通过-classpath/-cp可以找到JVM加载运行的每一个类,它们在classpath中以 : 分隔。 如何确定一个类 利用类的全限定类名(目录层级)可以唯一确定一个类。 什么是包管理 包管理的本质就是告诉JVM去哪里寻找第三方类,然后如何解决其中的版本冲突问题。 ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:1:0","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"Maven 一个跨时代的包管理程序 Maven的包按照约定为所有的包编号,方便检索 扩展:语义化版本 在软件管理的领域里存在着被称作“依赖地狱”的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。 版本语义化就是依赖地狱的解决方案之一,例如 语义化版本 x.y.z 主版本号.次版本号.修订号,遵循版本号递增规则 x是主版本号:当你做了不兼容的 API 修改,y是次版本号:当你做了向下兼容的功能性新增,z是修订号:当你做了向下兼容的问题修正。 ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:2:0","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"Maven的坐标 关于坐标,我们最早的认知应该是平面几何中的二维坐标系(x,y),这样可以通过一个坐标唯一确定一个点。Maven也是利用坐标唯一确定一个包,坐标是Maven依赖管理的基础,Maven的中央仓库里拥有无数的jar或war包,通过项目中的pom.xml文件引入Maven依赖,Maven的坐标元素常用的有\u003cgroupId\u003e\u003cartifactId\u003e\u003cversion\u003e\u003cpackaging\u003e等等,利用这些坐标可以轻松的确定一个包,以前需要什么包就去什么网站寻找的日子一去不复返了。 在我们开发项目的时候,我们要严格写入坐标,这样Maven才可以正确的注入依赖。 ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:3:0","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"Maven传递性依赖的解决 ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:4:0","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"何为传递性依赖 简单来说就是某个项目添加的第三方依赖中又依赖了其他的第三方依赖。 ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:4:1","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"传递性依赖的⾃动管理 原则上绝对不允许最终的classpath中出现同名不同版本的jar包。 ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:4:2","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"依赖冲突的解决 原则:最近的胜出 举个例子: 图中C的jar有两个版本,由于0.2版本离项目比较近,所以优先使用C的0.2版本。 假如你一定要使用C的0.1版本,那么需要做以下操作之一: 在pom.xml中重新添加依赖,添加到依赖树 mvn dependency:tree 离你的项目相对更近的位置。 在pom.xml中D的依赖标签中加入\u003cexclusions\u003e标签声明排除C的0.2版本。 ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:4:3","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"依赖的scope Maven的依赖范围在xml文件中用\u003cscope\u003e表示,参数有以下: compile :编译依赖。如果没有手动指定,那么就默认使用该参数,对于编译、测试、运行的三种classpath都有效。 test :测试依赖。只对于测试classpath的范围有效,但是在编译代码或者运行项目时无效。 provide :已提供依赖。别的容器(Web Container)会提供,对于编译、测试的classpath有效,对运行无效。 runtime :运行时依赖。对于测试、运行时的classpath有效,但在编译代码时无效。 system :系统依赖。从参与度来说,也provided相同,不过被依赖项不会从maven仓库抓,而是从本地文件系统拿,一定需要配合systemPath属性使用。 import :导入依赖。略 ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:4:4","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"当你看到如下异常,大概率是发生了包冲突 AbstractMethodError NoClassDefFoundError ClassNotFoundException LinkageError ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:4:5","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"值得一提的是Maven还是优秀的自动化构建工具 ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:5:0","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"Maven的生命周期 阶段 处理 描述 验证 validate 验证项目 验证项目是否正确且所有必须信息是可用的 编译 compile 执行编译 源代码编译在此阶段完成 测试 Test 测试 使用适当的单元测试框架(例如JUnit)运行测试。 包装 package 打包 创建JAR/WAR包如在 pom.xml 中定义提及的包 检查 verify 检查 对集成测试的结果进行检查,以保证质量达标 安装 install 安装 安装打包的项目到本地仓库,以供其他项目使用 部署 deploy 部署 拷贝最终的工程包到远程仓库中,以共享给其他开发人员和工程 参考书 Maven实战.pdf ","date":"2020-07-15","objectID":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/:6:0","tags":null,"title":"Maven和java的包管理","uri":"/maven%E5%92%8Cjava%E7%9A%84%E5%8C%85%E7%AE%A1%E7%90%86/"},{"categories":null,"content":"Vim ","date":"2020-06-20","objectID":"/vim/:0:0","tags":null,"title":"Vim","uri":"/vim/"},{"categories":null,"content":"什么是vim? Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。 简单的来说, vi 是老式的字处理器,不过功能已经很齐全了,但是还是有可以进步的地方。 vim 则可以说是程序开发者的一项很好用的工具。 连 vim 的官方网站 (http://www.vim.org) 自己也说 vim 是一个程序开发工具而不是文字处理软件。 ","date":"2020-06-20","objectID":"/vim/:1:0","tags":null,"title":"Vim","uri":"/vim/"},{"categories":null,"content":"vi/vim的使用 基本上 vi/vim 共分为三种模式,分别是命令模式(Command mode),输入模式(Insert mode)和底线命令模式(Last line mode)。 这三种模式的作用分别是: ","date":"2020-06-20","objectID":"/vim/:2:0","tags":null,"title":"Vim","uri":"/vim/"},{"categories":null,"content":"命令模式: 用户刚刚启动 vi/vim,便进入了命令模式。 此状态下敲击键盘动作会被Vim识别为命令,而非输入字符。比如我们此时按下i,并不会输入一个字符,i被当作了一个命令。 以下是常用的几个命令: i 切换到输入模式,以输入字符。 x 删除当前光标所在处的字符。 : 切换到底线命令模式,以在最底一行输入命令。 若想要编辑文本:启动Vim,进入了命令模式,按下i,切换到输入模式。 命令模式只有一些最基本的命令,因此仍要依靠底线命令模式输入更多命令。 ","date":"2020-06-20","objectID":"/vim/:2:1","tags":null,"title":"Vim","uri":"/vim/"},{"categories":null,"content":"输入模式 在命令模式下按下i就进入了输入模式。 在输入模式中,可以使用以下按键: 字符按键以及Shift组合,输入字符 ENTER,回车键,换行 BACK SPACE,退格键,删除光标前一个字符 DEL,删除键,删除光标后一个字符 方向键,在文本中移动光标 HOME/END,移动光标到行首/行尾 Page Up/Page Down,上/下翻页 Insert,切换光标为输入/替换模式,光标将变成竖线/下划线 ESC,退出输入模式,切换到命令模式 ","date":"2020-06-20","objectID":"/vim/:2:2","tags":null,"title":"Vim","uri":"/vim/"},{"categories":null,"content":"底线命令模式 在命令模式下按下:(英文冒号)就进入了底线命令模式。 底线命令模式可以输入单个或多个字符的命令,可用的命令非常多。 在底线命令模式中,基本的命令有(已经省略了冒号): q 退出程序 w 保存文件 按ESC键可随时退出底线命令模式。 ","date":"2020-06-20","objectID":"/vim/:2:3","tags":null,"title":"Vim","uri":"/vim/"},{"categories":null,"content":"vi/vim按键说明 除了上面简易范例的 i, Esc, :wq 之外,其实 vim 还有非常多的按键可以使用。 ","date":"2020-06-20","objectID":"/vim/:2:4","tags":null,"title":"Vim","uri":"/vim/"},{"categories":null,"content":"第一部分:一般模式可用的光标移动、复制粘贴、搜索替换等 移动光标的方法 h 或 向左箭头键(←) 光标向左移动一个字符 j 或 向下箭头键(↓) 光标向下移动一个字符 k 或 向上箭头键(↑) 光标向上移动一个字符 l 或 向右箭头键(→) 光标向右移动一个字符 如果你将右手放在键盘上的话,你会发现 hjkl 是排列在一起的,因此可以使用这四个按钮来移动光标。 如果想要进行多次移动的话,例如向下移动 30 行,可以使用 “30j” 或 “30↓” 的组合按键, 亦即加上想要进行的次数(数字)后,按下动作即可! [Ctrl] + [f] 屏幕『向下』移动一页,相当于 [Page Down]按键 (常用) [Ctrl] + [b] 屏幕『向上』移动一页,相当于 [Page Up] 按键 (常用) [Ctrl] + [d] 屏幕『向下』移动半页 [Ctrl] + [u] 屏幕『向上』移动半页 + 光标移动到非空格符的下一行 - 光标移动到非空格符的上一行 n 那个 n 表示『数字』,例如 20 。按下数字后再按空格键,光标会向右移动这一行的 n 个字符。例如 20 则光标会向后面移动 20 个字符距离。 0 或功能键[Home] 这是数字『 0 』:移动到这一行的最前面字符处 (常用) $ 或功能键[End] 移动到这一行的最后面字符处(常用) H 光标移动到这个屏幕的最上方那一行的第一个字符 M 光标移动到这个屏幕的中央那一行的第一个字符 L 光标移动到这个屏幕的最下方那一行的第一个字符 G 移动到这个档案的最后一行(常用) nG n 为数字。移动到这个档案的第 n 行。例如 20G 则会移动到这个档案的第 20 行(可配合 :set nu) gg 移动到这个档案的第一行,相当于 1G 啊! (常用) n n 为数字。光标向下移动 n 行(常用) 搜索替换 /word 向光标之下寻找一个名称为 word 的字符串。例如要在档案内搜寻 vbird 这个字符串,就输入 /vbird 即可! (常用) ?word 向光标之上寻找一个字符串名称为 word 的字符串。 n 这个 n 是英文按键。代表重复前一个搜寻的动作。举例来说, 如果刚刚我们执行 /vbird 去向下搜寻 vbird 这个字符串,则按下 n 后,会向下继续搜寻下一个名称为 vbird 的字符串。如果是执行 ?vbird 的话,那么按下 n 则会向上继续搜寻名称为 vbird 的字符串! N 这个 N 是英文按键。与 n 刚好相反,为『反向』进行前一个搜寻动作。 例如 /vbird 后,按下 N 则表示『向上』搜寻 vbird 。 使用 /word 配合 n 及 N 是非常有帮助的!可以让你重复的找到一些你搜寻的关键词! :n1,n2s/word1/word2/g n1 与 n2 为数字。在第 n1 与 n2 行之间寻找 word1 这个字符串,并将该字符串取代为 word2 !举例来说,在 100 到 200 行之间搜寻 vbird 并取代为 VBIRD 则: 『:100,200s/vbird/VBIRD/g』。(常用) :1,$s/word1/word2/g 或 :%s/word1/word2/g 从第一行到最后一行寻找 word1 字符串,并将该字符串取代为 word2 !(常用) :1,$s/word1/word2/gc 或 :%s/word1/word2/gc 从第一行到最后一行寻找 word1 字符串,并将该字符串取代为 word2 !且在取代前显示提示字符给用户确认 (confirm) 是否需要取代!(常用) 删除、复制与贴上 x, X 在一行字当中,x 为向后删除一个字符 (相当于 [del] 按键), X 为向前删除一个字符(相当于 [backspace] 亦即是退格键) (常用) nx n 为数字,连续向后删除 n 个字符。举例来说,我要连续删除 10 个字符, 『10x』。 dd 删除游标所在的那一整行(常用) ndd n 为数字。删除光标所在的向下 n 行,例如 20dd 则是删除 20 行 (常用) d1G 删除光标所在到第一行的所有数据 dG 删除光标所在到最后一行的所有数据 d$ 删除游标所在处,到该行的最后一个字符 d0 那个是数字的 0 ,删除游标所在处,到该行的最前面一个字符 yy 复制游标所在的那一行(常用) nyy n 为数字。复制光标所在的向下 n 行,例如 20yy 则是复制 20 行(常用) y1G 复制游标所在行到第一行的所有数据 yG 复制游标所在行到最后一行的所有数据 y0 复制光标所在的那个字符到该行行首的所有数据 y$ 复制光标所在的那个字符到该行行尾的所有数据 p, P p 为将已复制的数据在光标下一行贴上,P 则为贴在游标上一行! 举例来说,我目前光标在第 20 行,且已经复制了 10 行数据。则按下 p 后, 那 10 行数据会贴在原本的 20 行之后,亦即由 21 行开始贴。但如果是按下 P 呢? 那么原本的第 20 行会被推到变成 30 行。 (常用) J 将光标所在行与下一行的数据结合成同一行 c 重复删除多个数据,例如向下删除 10 行,[ 10cj ] u 复原前一个动作。(常用) [Ctrl]+r 重做上一个动作。(常用) 这个 u 与 [Ctrl]+r 是很常用的指令!一个是复原,另一个则是重做一次~ 利用这两个功能按键,你的编辑,嘿嘿!很快乐的啦! . 不要怀疑!这就是小数点!意思是重复前一个动作的意思。 如果你想要重复删除、重复贴上等等动作,按下小数点『.』就好了! (常用) ","date":"2020-06-20","objectID":"/vim/:2:5","tags":null,"title":"Vim","uri":"/vim/"},{"categories":null,"content":"第二部分:一般模式切换到编辑模式的可用的按钮说明 进入输入或取代的编辑模式 i, I 进入输入模式(Insert mode): i 为『从目前光标所在处输入』, I 为『在目前所在行的第一个非空格符处开始输入』。 (常用) a, A 进入输入模式(Insert mode): a 为『从目前光标所在的下一个字符处开始输入』, A 为『从光标所在行的最后一个字符处开始输入』。(常用) o, O 进入输入模式(Insert mode): 这是英文字母 o 的大小写。o 为『在目前光标所在的下一行处输入新的一行』; O 为在目前光标所在处的上一行输入新的一行!(常用) r, R 进入取代模式(Replace mode): r 只会取代光标所在的那一个字符一次;R会一直取代光标所在的文字,直到按下 ESC 为止;(常用) 上面这些按键中,在 vi 画面的左下角处会出现『–INSERT–』或『–REPLACE–』的字样。 由名称就知道该动作了吧!!特别注意的是,我们上面也提过了,你想要在档案里面输入字符时, 一定要在左下角处看到 INSERT 或 REPLACE 才能输入喔! [Esc] 退出编辑模式,回到一般模式中(常用) ","date":"2020-06-20","objectID":"/vim/:2:6","tags":null,"title":"Vim","uri":"/vim/"},{"categories":null,"content":"第三部分:一般模式切换到指令行模式的可用的按钮说明 指令行的储存、离开等指令 :w 将编辑的数据写入硬盘档案中(常用) :w! 若文件属性为『只读』时,强制写入该档案。不过,到底能不能写入, 还是跟你对该档案的档案权限有关啊! :q 离开 vi (常用) :q! 若曾修改过档案,又不想储存,使用 ! 为强制离开不储存档案。 注意一下啊,那个惊叹号 (!) 在 vi 当中,常常具有『强制』的意思~ :wq 储存后离开,若为 :wq! 则为强制储存后离开 (常用) ZZ 这是大写的 Z 喔!若档案没有更动,则不储存离开,若档案已经被更动过,则储存后离开! :w [filename] 将编辑的数据储存成另一个档案(类似另存新档) :r [filename] 在编辑的数据中,读入另一个档案的数据。亦即将 『filename』 这个档案内容加到游标所在行后面 :n1,n2 w [filename] 将 n1 到 n2 的内容储存成 filename 这个档案。 :! command 暂时离开 vi 到指令行模式下执行 command 的显示结果!例如 『:! ls /home』即可在 vi 当中察看 /home 底下以 ls 输出的档案信息! vim 环境的变更 :set nu 显示行号,设定之后,会在每一行的前缀显示该行的行号 :set nonu 与 set nu 相反,为取消行号! 特别注意,在 vi/vim 中,数字是很有意义的!数字通常代表重复做几次的意思! 也有可能是代表去到第几个什么什么的意思。 举例来说,要删除 50 行,则是用 『50dd』 对吧! 数字加在动作之前,如我要向下移动 20 行呢?那就是『20j』或者是『20↓』即可。 ","date":"2020-06-20","objectID":"/vim/:2:7","tags":null,"title":"Vim","uri":"/vim/"},{"categories":null,"content":"URL 包含哪几部分 URL 包括: 协议 域名/ip 地址 路径 查询参数 锚点 以https://www.baidu.com/s?wd=hello\u0026rsv_spt=1#5 为例 https 代表超文本传输协议 www.baidu.com 代表域名 s?wd=hello\u0026rsv_spt=1 代表查询参数 #5 代表锚点,可以用来网页定位 ","date":"2020-05-23","objectID":"/url/:1:0","tags":null,"title":"URL","uri":"/url/"},{"categories":null,"content":"DNS 域名系统(英语:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS 使用 TCP 和 UDP 端口 53。当前,对于每一级域名长度的限制是 63 个字符,域名总长度则不能超过 253 个字符。 ","date":"2020-05-23","objectID":"/url/:2:0","tags":null,"title":"URL","uri":"/url/"},{"categories":null,"content":"nslookup nslookup 是一种网络管理 命令行工具,可在许多计算机操作系统中使用,用于查询域名系统(DNS)以获得域名或 IP 地址映射或其他 DNS 记录。名称“ nslookup ”表示“ 名称服务器查找 ”。 简单来说,nslookup 用来查找域名对应的 ip 地址。许多大企业的域名对应多个 ip 地址,可以实现不同地区的流量分流,叫做负载均衡。 ","date":"2020-05-23","objectID":"/url/:3:0","tags":null,"title":"URL","uri":"/url/"},{"categories":null,"content":"IP 引言 互联网协议套件(英语:Internet Protocol Suite,缩写 IPS)是一个网络通信模型,以及一整个网络传输协议家族,为网际网络的基础通信架构。它常被通称为 TCP/IP 协议族(英语:TCP/IP Protocol Suite,或 TCP/IP Protocols),简称 TCP/IP。因为该协议家族的两个核心协议:TCP(传输控制协议)和 IP(网际协议),为该家族中最早通过的标准。由于在网络通讯协议普遍采用分层的结构,当多个层次的协议共同工作时,类似计算机科学中的堆栈,因此又被称为 TCP/IP 协议栈(英语:TCP/IP Protocol Stack)。 **IP 协议就是 网际协议(英语:Internet Protocol,缩写:IP;也称互联网协议)是用于分组交换数据网络的一种协议。** ","date":"2020-05-23","objectID":"/url/:4:0","tags":null,"title":"URL","uri":"/url/"},{"categories":null,"content":"ping 命令 引言 ICMP 协议是“Internet Control Message Ptotocol”(因特网控制消息协议)的缩写。它是 TCP/IP 协议族的一个子协议,用于在 IP 主机、路由器之间传递控制消息。 ping (Packet Internet Groper),因特网包探索器,用于测试网络连接量的程序。Ping 发送一个 ICMP;回声请求消息给目的地并报告是否收到所希望的 ICMP echo (ICMP 回声应答)。它是用来检查网络是否通畅或者网络连接速度的命令。 ping 命令通常用来作为网络可用性的检查。ping 命令可以对一个网络地址发送测试数据包,看该网络地址是否有响应并统计响应时间,以此测试网络。 **ping 和 ICMP 的关系:ping 命令发送数据使用的是 ICMP 协议。** ping 的原理: 向指定的网络地址发送一定长度的数据包,按照约定,若指定网络地址存在的话,会返回同样大小的数据包,当然,若在特定时间内没有返回,就是“超时”,会被认为指定的网络地址不存在。 ICMP 协议通过 IP 协议发送的,IP 协议是一种无连接的,不可靠的数据包协议。在 Unix/Linux,序号从 0 开始计数,依次递增。而 Windows ping 程序的 ICMP 序列号是没有规律。 ","date":"2020-05-23","objectID":"/url/:5:0","tags":null,"title":"URL","uri":"/url/"},{"categories":null,"content":"域名是什么 网域名称(英语:Domain Name,简称:Domain),简称域名、网域,是由一串用点分隔的字符组成的互联网上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位。域名可以说是一个 IP 地址的代称,目的是为了便于记忆后者。 ","date":"2020-05-23","objectID":"/url/:6:0","tags":null,"title":"URL","uri":"/url/"},{"categories":null,"content":"分别有哪几类域名 顶级域名(TLD) 域名由两个或两个以上的词构成,中间由点号分隔开,最右边的那个词称为顶级域名 (TLD, top-level domain),如 .com .cn 等。 顶级域名又分为三类:一是国家和地区顶级域名(country code top-level domain,简称 ccTLD),目前 200 多个国家都按照 ISO3166 国家代码分配了顶级域名,例如中国是 cn,日本是 jp 等。二是通用顶级域名(generictop-level domain,简称 gTLD),例如表示工商企业的 .com,表示网络提供商的 .net,表示非盈利组织的 .org 等。三是新顶级域(New gTLD)如.xinss 通用顶级域名(gTLD) 通用顶级域名英文全称为 Generic top-level domain,简称为 gTLD。通用顶级域名顾名思义其没有国家和地域限制,为互联网数字分配机构 IANA(Internet Assigned Numbers Authority)管理维护。通用顶级域名又按是否为赞助又可分为两类,一类是非赞助顶级域名(unsponsored top-level domains),其为人们所常见,使用范围较广,包括 .com .net .org .biz s.info .name .pro。另一类为赞助顶级域名(sponsored top-level domains(sTLD)),其包含 .gov .edu .mil .mobi .tel .cat 等。 国家和地区顶级域名(ccTLD) 国别顶级域名英文全称为 Country code top-level domain,简称为 ccTLD,为某个国家或地区所专属域名后缀,基本均为国家代码,所以我们只要看到某个域名后缀为两字符,都是国别域名,另外部分国别顶级域名必需提供该国的一些证明材料方可进行注册。比如我们较为常见的 .cn(中国) .co(哥伦比亚共和国) .cc(Cocos 岛国) .hk(香港) .de(德国) .ru(俄罗斯) .us(美国)等,但有些国别域名后缀因其具有输入快捷、具有特定含义或行业属性等优势,也逐渐变得较为通用起来,比如有 .tv(电视、直播) .co(公司、商业),还有最近比较流行的 .vc(风投)等。 新顶级域名(New gTLD) 新顶级域名全称 New Generic Top-level Domain,简称 new gTLD 或 ngTLD。其实新顶级域名后缀本属于通用顶级域名(gTLD),只是因为其推出时间较晚(2014 年),数量众多、影响非常深远,故将其单独划分为一类。 由于在 .com 精品资源日益枯竭的情况下,ICANN 为满足广大初创企业、机构团体和个人等对优质域名的迫切需求,2011 年 6 月 20 日 ICANN 于新加坡会议上正式通过新顶级域(New gTLD)批案,任何公司、机构都有权向 ICANN 申请新的顶级域名。新顶级域名的推出,切实有效地降低了初创公司及新推品牌的域名成本,同时优化了传统的搜索方式,另外也有利于国际化大公司进一步巩固和强化自身品牌,从而真正地激发了全球经济活力。 新顶级域名按功能属性,大致可划分为通用型、行业型、地域型、公司品牌型四大类: 通用型的域名后缀为没有很强的行业属性和限定含义的域名后缀,比较有代表性的有:.club .xyz .xin .top .web .app .wang 等。 行业型有:.game .car .bank .club .news .shop .store .cloud .music .fund .pub .bid .pet .tour .men .商标等,为新顶级域名主力军,数量众多,基本上涵盖各行各业。 地域型主要以城市后缀为主,有:.nyc .berlin .paris .london 等。 公司品牌型主要为世界五百强企业申请,有:.alibaba .taobao .baidu .google .apple .sky 等。 国际化域名(IDN) 域名按语言种类可划分为两大类。一类是英文域名,由 26 个英文字母、数字和中划线(-)构成;另一种即为国际化域名,即 IDN (InternationalizedDomain Names)也称多语种域名,是指非英语国家为推广本国语言的域名系统的一个总称。含有中文的域名为中文域名,比如中文顶级域名有.中国、.商店、.广东、.世界等,含有日文的为日文域名,如日文域名コム .com,含有阿拉伯文的为阿拉伯域名,含有韩文的为韩文域名等等。顶级域名、二级域名、三级域名等均可以为 IDN。 ","date":"2020-05-23","objectID":"/url/:6:1","tags":null,"title":"URL","uri":"/url/"},{"categories":null,"content":"HTML 常用标签 ","date":"2020-05-09","objectID":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/:0:0","tags":null,"title":"HTML常用标签","uri":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/"},{"categories":null,"content":"a 标签 属性 href target _blank _top _parent _self target 跳转到外部页面 跳转到内部锚点 跳转到邮箱或者电话 download 不是打开页面,而是下载页面 但是不是所有浏览器都支持 download,手机浏览器可能不支持 rel = noopener ","date":"2020-05-09","objectID":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/:1:0","tags":null,"title":"HTML常用标签","uri":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/"},{"categories":null,"content":"iframe 标签 内嵌窗口,已经很少使用。 在当前页面内嵌一个新窗口。 ","date":"2020-05-09","objectID":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/:2:0","tags":null,"title":"HTML常用标签","uri":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/"},{"categories":null,"content":"table 标签 ","date":"2020-05-09","objectID":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/:3:0","tags":null,"title":"HTML常用标签","uri":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/"},{"categories":null,"content":"表格 常用标签 table thead : table head tbody : table body tfoot : table foot tr : table row 表示表格的一行 th : table head 表示一行的表头 td : table data 表示表格内的数据 相关的样式 table-layout : table-layout CSS 属性定义了用于布局表格单元格,行和列的算法,比如设置表格的宽度等。 border-collapse :可以使 table border 之间没有间隙。 border-spacing : 设置 table border 的间隔大小。 ","date":"2020-05-09","objectID":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/:3:1","tags":null,"title":"HTML常用标签","uri":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/"},{"categories":null,"content":"img 标签 作用 发出 get 请求,展示一张图片。 属性 alt/height/width/src 事件 onload/onerror 响应式 max-width:100% ","date":"2020-05-09","objectID":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/:4:0","tags":null,"title":"HTML常用标签","uri":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/"},{"categories":null,"content":"form 标签 作用 发出 get 或者 post 请求,然后刷新页面 属性 action/autocomplete/method/target 事件 onsubmit ","date":"2020-05-09","objectID":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/:5:0","tags":null,"title":"HTML常用标签","uri":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/"},{"categories":null,"content":"input 标签 作用 让用户输入内容 属性 类型 type:button/checkbox/email/file/hidden/number/password/radio/search/submit/tel/text/ 其他 name/autofocus/checked/disabled/maxlength/pattern/value/placeholder 事件 onchange/onfocus/onblur 验证器 H5 新增功能 require : input 不填没办法提交 其他输入标签 select+option textarea label 注意事项 一般不监听 input 的 click 事件 form 里面的 input 要有 name form 里面要放一个 type=submit 才能触发 submit 事件。 ","date":"2020-05-09","objectID":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/:6:0","tags":null,"title":"HTML常用标签","uri":"/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/"},{"categories":null,"content":"Session 概念:服务器端会话技术,在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中。HttpSession HttpSession 对象: Object getAttribute(String name) void setAttribute(String name, object value) void removeAttribute(String name) 获取 session 对象 HttpSession session = request.getSession(); Session 原理 Session 的实现是依赖于 cookie 的,当第一次请求时,会创建一个 session 对象,并给他一个 JsessionID,然后再次请求时 getSession 方法会寻找这个 ID 返回给 session 对象。所以在一次会话的多次请求中,使用的 session 是同一个。 细节 当客户端关闭后,服务端不关闭,两次获取 session 是否为同一个? 默认情况下,不是。 可以手动设置使客户端关闭后再次访问服务器获取的 session 是同一个 创建一个 cookie, Cookie cookie = new Cookie(\"JSession\",session.getId()); 设置 cookie 对象的最大存活时间 1 小时 cookie.setMaxAge(60*60) 客户端不关闭,服务器关闭后,两次获取的 session 是同一个吗? 不是同一个,但是要确保数据不丢失。tomcat 自动完成以下工作: session 的钝化: 在服务器正常关闭之前,将 session 对象序列化到硬盘上。 session 的活化: 在服务器启动后,将 session 文件转化为内存中的 session 对象即可。 session 的失效时间?什么时候被销毁? 服务器关闭 session 对象调用invalidate() session 默认失效时间 30 分钟 如何修改失效时间 找到 web.xml 里面的\u003csession-config\u003e标签修改session-timeout。 ","date":"2020-04-29","objectID":"/session/:1:0","tags":null,"title":"Session","uri":"/session/"},{"categories":null,"content":"会话技术 会话:一次会话中包含多次请求和响应。 一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止,算一次会话。 功能:在一次会话的范围内的多次请求间,共享数据。 方式: 客户端会话技术:Cookie 服务器端会话技术:Session ","date":"2020-04-19","objectID":"/cookie/:0:1","tags":null,"title":"Cookie","uri":"/cookie/"},{"categories":null,"content":"Cookie 概念:客户端会话技术,将数据保存到客户端 使用步骤: 创建 Cookie 对象,绑定数据 new Cookie(String name,String value) 发送 Cookie 对象 response.addCookie(Cookie cookie) 获取 Cookie 对象,拿到数据 Cookie[] request.getCookies() 原理: 基于响应头 set-cookie 和请求头 cookie 实现 在一次会话中,当客户端请求服务器端的资源,服务器端会将设置好的 cookie 对象响应给客户端一个响应头set-cookie:a = b,浏览器根据 http 协议会在客户端的浏览器中键值 a = b 对并保存,然后再下一次请求服务器资源的时候,会携带一个请求头cookie:a = b,在服务器中就可以获取请求头中的数据进行操作 细节 一次可不可以发送多个 cookie? 可以 可以创建多个 cookie 对象,使用 response 调用多次 addCookie 方法发送 cookie 即可 cookie 在浏览器中保存多长时间? 默认情况下,当浏览器关闭后,cookie 数据被销毁,cookie 保存在浏览器内存中 持久化存储: setMaxAge(int seconds) 参数 正数:将 cookie 数据写到硬盘的文件中。持久化存储,cookie 存活的时间。 负数:默认值一次会话后销毁 cookie 零:删除 cookie 信息 cookie 能不能存中文? 在 tomcat 8 之前不能存中文,需要将中文数据转码—一般采用 URL 编码(%E3) cookie 获取范围多大? 假设在一个 tomcat 服务器中,部署了多个 web 项目,那么在这些项目中 cookie 能不能共享 默认情况下 cookie 不能共享 cookie对象.setPath(String path):设置 cookie 的获取范围。默认情况下,设置当前的虚拟目录,共享就设置为/,是最大共享范围 不同的 tomcat 服务器间 cookie 共享问题 setDomain(String path):如果设置一级域名相同,那么多个服务器间 cookie 可以共享 例如 setDomain(\".baidu.com”),那么 tieba.baidu.com 和 news.baidu.com 中的 cookie 可以共享 ","date":"2020-04-19","objectID":"/cookie/:1:0","tags":null,"title":"Cookie","uri":"/cookie/"},{"categories":null,"content":"堆排序 O (nlgn) 堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。 堆中定义以下几种操作: 最大堆调整:将堆的末端子节点作调整,使得子节点永远小于父节点 创建最大堆:将堆中的所有数据重新排序 堆排序:移除位在第一个数据的根节点,并做最大堆调整的递归运算 Java实现: import java.util.Arrays; /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class HeapSort { public static void main(String[] args) { //使用数组定义一棵完全二叉树 int[] arr = new int[]{1,2,5,4,6}; } //构造部分大顶堆函数,参数size是节点个数,index是节点位置 public static void MaxHeap(int[] arr, int size, int index) { //左子节点 int leftNode = 2 * index + 1; //右子节点 int rightNode = 2 * index + 2; //三个节点相比取最大且保存 int max = index; if (leftNode \u003c size \u0026\u0026 arr[leftNode] \u003e arr[max]) { max = leftNode; } if (rightNode \u003c size \u0026\u0026 arr[rightNode] \u003e arr[max]) { max = rightNode; } //如果max里面的默认值index没有改变,说明index已经是最大,否则交换 if (max != index) { int temp = arr[index]; arr[index] = arr[max]; arr[max] = temp; //交换位置以后跟可能会破坏之前排好的堆,所以要重新调整,相当于是拿三个节点中的最大值节点与他们的子节点进行比较,如此递归下去 //max表示调整之后的位置,这个操作会把权值较小的元素放置在合适的位置上 MaxHeap(arr, size, max); } } //堆排序 public static void heapSort(int[] arr) { //start是最后一个叶子节点的父节点 int start = (arr.length - 1) / 2; //从最后一个节点的父节点依次向前遍历,直到根节点 for (int i = start; i \u003e= 0; i--) { //循环加构造部分大顶堆函数构造成了一个完整的大顶堆 MaxHeap(arr, arr.length, i); }//此时大顶堆已经成型 //进行排序,排序基本操作是每次有一个元素有序,因为大顶堆的最大元素在根,所以拿根与数组最后一个元素交换,每换一次再重新构造一次大顶堆,使得每一次将要排序的最大元素都在根节点 for (int i = arr.length - 1; i \u003e 0; i--) { int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; //再将有序元素外的其他元素构造成大顶堆 MaxHeap(arr, i, 0); } } } ","date":"2020-04-01","objectID":"/%E5%A0%86%E6%8E%92%E5%BA%8F/:1:0","tags":null,"title":"堆排序","uri":"/%E5%A0%86%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"顺序存储的二叉树 顺序存储的二叉树通常情况 只考虑完全二叉树。 常用基本性质: 第n个元素的左子节点是2n+1,它的右子节点是2n+2 第n个元素的父节点是(n-1)/2 ","date":"2020-03-31","objectID":"/%E9%A1%BA%E5%BA%8F%E5%AD%98%E5%82%A8%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91/:1:0","tags":null,"title":"顺序存储的二叉树","uri":"/%E9%A1%BA%E5%BA%8F%E5%AD%98%E5%82%A8%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91/"},{"categories":null,"content":"遍历 数组二叉树使用的遍历方法与正常二叉树的遍历方法相同,分为前中后序遍历。 构造一棵数组二叉树: /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class ArrayBinaryTree { int[] data; public ArrayBinaryTree(int[] data) { //构造函数,参数是一个数组,使用这个数组构造一棵完全二叉树 this.data = data; } //前序遍历,参数是从第几个元素开始遍历 public void preorder(int index) { //数组为空或者数组长度为0,直接返回 if (data == null || data.length == 0) { return; } else { //遍历当前节点, System.out.println(data[index]); //判断当前节点的左子节点是否在数组长度内,可能没有左子节点 //判断完成后前序递归查找节点 if (index * 2 + 1 \u003c data.length) { preorder(2 * index + 1); } if (index * 2 + 2 \u003c data.length) { preorder(2 * index + 2); } } } //中序遍历 public void inorder(int index) { if (data.length == 0 || data == null) { return; } else { if (2 * index + 1 \u003c data.length) { inorder(2 * index + 1); } System.out.println(data[index]); if (2 * index + 2 \u003c data.length) { inorder(2 * index + 2); } } } //后序遍历 public void postorder(int index) { if (data == null || data.length == 0) { return; } else { if (2 * index + 1 \u003c data.length) { postorder(2 * index + 1); } if (2 * index + 2 \u003c data.length) { postorder(2 * index + 2); } System.out.println(data[index]); } } } 测试类: /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class ArrayBinaryTreeTest { public static void main(String[] args) { //使用数组构造一棵完全二叉树 int[] arr = new int[]{1, 2, 3, 4, 5, 6, 7}; ArrayBinaryTree atree = new ArrayBinaryTree(arr); System.out.println(\"-------------\"); atree.preorder(0); System.out.println(\"-------------\"); atree.inorder(0); System.out.println(\"-------------\"); atree.postorder(0); } } ","date":"2020-03-31","objectID":"/%E9%A1%BA%E5%BA%8F%E5%AD%98%E5%82%A8%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91/:1:1","tags":null,"title":"顺序存储的二叉树","uri":"/%E9%A1%BA%E5%BA%8F%E5%AD%98%E5%82%A8%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91/"},{"categories":null,"content":"构造二叉树 /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class BinaryTree { //二叉树里只需要包含根节点,通过方法调用来使用其他子节点 TreeNode root; //空树构造函数 public BinaryTree() { } public void setRoot(TreeNode root) { this.root = root; } public void preorder() { if (root != null) { root.preorder(); } } public void inorder() { if (root != null) { root.inorder(); } } public void postorder() { if (root != null) { root.postorder(); } } public TreeNode preorderSearch(int value) { return root.preorderSearch(value); } public TreeNode inorderSearch(int value) { return root.inorderSearch(value); } public TreeNode postorderSearch(int value) { return root.postorderSearch(value); } } ","date":"2020-03-21","objectID":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/:1:0","tags":null,"title":"二叉树的前中后序遍历","uri":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/"},{"categories":null,"content":"构造树节点 import java.time.temporal.Temporal; /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class TreeNode { int value; TreeNode leftNode; TreeNode rightNode; public TreeNode(int value) { this.value = value; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } public TreeNode getLeftNode() { return leftNode; } public void setLeftNode(TreeNode leftNode) { this.leftNode = leftNode; } public TreeNode getRightNode() { return rightNode; } public void setRightNode(TreeNode rightNode) { this.rightNode = rightNode; } //前序遍历 public void preorder() { System.out.println(this.value); if (this.leftNode != null) { this.leftNode.preorder(); } if (this.rightNode != null) { this.rightNode.preorder(); } } //中序遍历 public void inorder() { if (this.leftNode != null) { this.leftNode.inorder(); } System.out.println(this.value); if (this.rightNode != null) { this.rightNode.inorder(); } } //后序遍历 public void postorder() { if (this.leftNode != null) { this.leftNode.postorder(); } if (this.rightNode != null) { this.rightNode.postorder(); } System.out.println(this.value); } //使用前序遍历查找节点 public TreeNode preorderSearch(int value) { TreeNode target = null; //如果当前节点的值等于目标值,返回当前节点 if (this.value == value) { return this; } //从左子树中查找目标值 if (this.leftNode != null) { target = this.leftNode.preorderSearch(value); } //判断右子树中书否查找到了目标值,如果找到则返回target if (this.rightNode != null) { target = this.rightNode.preorderSearch(value); } //如果不存在该目标值,就直接返回target,此时target值为null return target; } //使用中序遍历查找目标值 public TreeNode inorderSearch(int value) { TreeNode target = null; if (this.leftNode != null) { target = this.leftNode.inorderSearch(value); } if (this.value == value) { return this; } if (this.rightNode != null) { target = this.rightNode.inorderSearch(value); } return target; } public TreeNode postorderSearch(int value) { TreeNode target = null; if (this.leftNode != null) { target = this.leftNode.postorderSearch(value); } if (this.rightNode != null) { target = this.rightNode.postorderSearch(value); } if (this.value == value) { return this; } return target; } } ","date":"2020-03-21","objectID":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/:2:0","tags":null,"title":"二叉树的前中后序遍历","uri":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/"},{"categories":null,"content":"测试类 /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class BinaryTreeTest { public static void main(String[] args) { //建立一个空树 BinaryTree tree = new BinaryTree(); //建立一个根节点 TreeNode root = new TreeNode(1); //把根节点赋值给新创建的空树 tree.setRoot(root); //创建左右节点 TreeNode rootleft = new TreeNode(2); TreeNode rootright = new TreeNode(3); //把左右节点分别赋值给根节点的左右节点 root.setLeftNode(rootleft); root.setRightNode(rootright); //为根节点的左子节点创建左右子节点 rootleft.setRightNode(new TreeNode(5)); rootleft.setLeftNode(new TreeNode(4)); //为根节点的右子节点创建左右子节点 rootright.setLeftNode(new TreeNode(6)); rootright.setRightNode(new TreeNode(7)); //调用前序遍历 tree.preorder(); System.out.println(\"-----\"); //调用中序遍历 tree.inorder(); System.out.println(\"-----\"); //调用后序遍历 tree.postorder(); //调用查找方法 TreeNode result = tree.preorderSearch(3); //已经知道value是3的节点对象是rootright,判断两者是否是同一对象,结果返回true System.out.println(result == rootright); } } ","date":"2020-03-21","objectID":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/:2:1","tags":null,"title":"二叉树的前中后序遍历","uri":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/"},{"categories":null,"content":"二叉树的前中后序遍历都是运用了递归的思想。 ","date":"2020-03-21","objectID":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/:2:2","tags":null,"title":"二叉树的前中后序遍历","uri":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/"},{"categories":null,"content":"前序遍历 前序遍历的顺序:根节点-左孩子-右孩子 //前序遍历 public void preorder() { System.out.println(this.value); if (this.leftNode != null) { this.leftNode.preorder(); } if (this.rightNode != null) { this.rightNode.preorder(); } } //使用前序遍历查找节点 public TreeNode preorderSearch(int value) { TreeNode target = null; //如果当前节点的值等于目标值,返回当前节点 if (this.value == value) { return this; } //从左子树中查找目标值 if (this.leftNode != null) { target = this.leftNode.preorderSearch(value); } //判断右子树中书否查找到了目标值,如果找到则返回target if (this.rightNode != null) { target = this.rightNode.preorderSearch(value); } //如果不存在该目标值,就直接返回target,此时target值为null return target; } ","date":"2020-03-21","objectID":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/:3:0","tags":null,"title":"二叉树的前中后序遍历","uri":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/"},{"categories":null,"content":"中序遍历 中序遍历的顺序:左孩子-根节点-右孩子 //中序遍历 public void inorder() { if (this.leftNode != null) { this.leftNode.inorder(); } System.out.println(this.value); if (this.rightNode != null) { this.rightNode.inorder(); } } //使用中序遍历查找目标值 public TreeNode inorderSearch(int value) { TreeNode target = null; if (this.leftNode != null) { target = this.leftNode.inorderSearch(value); } if (this.value == value) { return this; } if (this.rightNode != null) { target = this.rightNode.inorderSearch(value); } return target; } ","date":"2020-03-21","objectID":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/:4:0","tags":null,"title":"二叉树的前中后序遍历","uri":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/"},{"categories":null,"content":"后序遍历 后序遍历的顺序:左孩子-右孩子-根节点 //后序遍历 public void postorder() { if (this.leftNode != null) { this.leftNode.postorder(); } if (this.rightNode != null) { this.rightNode.postorder(); } System.out.println(this.value); } public TreeNode postorderSearch(int value) { TreeNode target = null; if (this.leftNode != null) { target = this.leftNode.postorderSearch(value); } if (this.rightNode != null) { target = this.rightNode.postorderSearch(value); } if (this.value == value) { return this; } return target; } ","date":"2020-03-21","objectID":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/:5:0","tags":null,"title":"二叉树的前中后序遍历","uri":"/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E4%B8%AD%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86/"},{"categories":null,"content":"希尔排序 O(nlog2n) import java.util.Arrays; /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class ShellSort { public static void main(String[] args) { int[] arr = new int[]{2, 3, 7, 3, 0, 8, 1, 4}; shellsort(arr); System.out.println(Arrays.toString(arr)); } public static void shellsort(int[] arr) { //这里的i是步长,每多少个步长分组,每次循环步长变为原来的一半,直到步长为0 for (int i = arr.length / 2; i \u003e 0; i = i / 2) { //遍历每一个元素 for (int j = i; j \u003c arr.length; j++) { //从每组的第一个元素开始遍历 for (int k = j - i; k \u003e= 0; k = k - i) { if (arr[k] \u003e arr[k + i]) { //按照顺序交换 int temp = arr[k]; arr[k] = arr[k + 1]; arr[k + 1] = temp; } } } } } } ","date":"2020-03-20","objectID":"/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F/:1:0","tags":null,"title":"希尔排序","uri":"/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"选择排序 O(n²) 选择排序(Selection sort)是一种简单直观的排序算法。 它的工作原理是:第一次从待排序的数据元素数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。 import java.util.Arrays; /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class SelectSort { public static void main(String[] args) { int[] arr = new int[]{2, 3, 5, 7, 1, 0, 10, 3, 1, 9}; selectsort(arr); System.out.println(Arrays.toString(arr)); } public static void selectsort(int[] arr) { for (int i = 0; i \u003c arr.length; i++) { //假设第一个元素为最小元素,记录它的位置 int min = i; //从头遍历整个数组 for (int j = i + 1; j \u003c arr.length; j++) { //如果某元素比记录的元素小,就把下标指向比记录元素小的元素位置 if (arr[min] \u003e arr[j]) { min = j; } } //如果第一个min的指向变化了,就说明记录的元素已经不是最小的元素,所以要做交换 if (min != i) { int temp = arr[i]; arr[i] = arr[min]; arr[min] = temp; } } } } ","date":"2020-03-19","objectID":"/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F/:1:0","tags":null,"title":"选择排序","uri":"/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"汉诺塔问题 汉诺塔问题是递归的一个具体案例。 汉诺游戏规则如下: 1、有三根相邻的柱子,标号为 A(起始柱子),B(中间柱子),C(目标柱子)。 2、A 柱子上从下到上按金字塔状叠放着 n 个不同大小的圆盘。 3、现在把所有盘子一个一个移动到柱子 C 上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方。 汉诺塔的解题思想 不考虑全局,不展开递归,人脑的思维内能力有限,所以我们需要把问题简化,只考虑当前应该怎么做即可。 将汉诺塔问题看作两种情况:1.只有一个圆盘时 2.只有两个圆盘时(最下面的最大的盘子和上面的其他所有的盘子)。 当递归调用开始,每一次递归都是对之前递归的重复,直到条件或断言终止。 汉诺塔问题不是单一的递归问题,当圆盘个数大于 1 时,里面包含了 3 个步骤:(上面代表除最下面以外的所有盘子,下面代表最下面的盘子) 移动上面的所有盘子到中间柱子 移动下面的盘子到目标柱子 再把移动过的上面的所有盘子从中间柱子移动到目标柱子 递归联想:假如有十个农民工,我们姑且叫他们老 1,老 2 …..老 10 村里承包项目,盖十层楼 村里把项目交给老 10 来做,老 10 只会盖第十层楼,没办法,没有前面 9 层楼他也没法盖啊,然后就把前 9 层的项目外包给了老 9 很巧,老 9 只会盖第九层楼,老 9 又把前 8 层项目外包给了老 8 老 8 也是一样,只会盖第 8 层,又把前 7 层外包给了老 7 老 7 只会盖第 7 层,然后又外包了下去 老 6….. 老 5…. 老 4… 老 3.. 老 2.. 最终老 1 拿到了这个盖第 1 层楼的活,没法再外包了(条件终止),然后老 1 就盖了起来,然后老 1 盖好 1 层了老 2 盖好 2 层……老 7 盖好 7 层,老 8 盖好 8 层,直到老 10 盖好第十层,这个项目就算完工了 这个故事就是纯正的递归思想,包括思路应该很清楚了 测试代码: /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class Hanoi { //全局变量count统计方法调用的次数 static int count; public static void main(String[] args) { move(8, 'a', 'b', 'c'); System.out.println(count); } //A表示起始位置,B表示中间位置,C表示目标位置 public static void move(int n, char A, char B, char C) { if (n == 1) { //如果只有一个盘子,那么就从起始位置直接移到目标位置 System.out.println(1 + \" From \" + A + \" to \" + C); } else { //这里运用了递归思想,无论有多少个盘子,我们都认为只有两个,上面所有的和最下面的单独的一个盘子 //移动上面的所有圆盘到中间位置 move(n - 1, A, C, B); //移动最下面的圆盘到目标位置 System.out.println(n + \" From \" + A + \" to \" + C); //把中间位置的圆盘移到目标位置 move(n - 1, B, A, C); } //每调用一次move,count计数一次 count++; } } ","date":"2020-03-17","objectID":"/%E6%B1%89%E8%AF%BA%E5%A1%94%E9%97%AE%E9%A2%98/:1:0","tags":null,"title":"汉诺塔","uri":"/%E6%B1%89%E8%AF%BA%E5%A1%94%E9%97%AE%E9%A2%98/"},{"categories":null,"content":"插入排序 O(n²) 插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外(让数组多一个空间才有插入的位置),而第二部分就只包含这一个元素(即待插入元素)。在第一部分排序完成后,再将这个最后元素插入到已排好序的第一部分中。 插入排序的基本思想是:每步将一个待排序的记录,按其临时值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。 import java.util.Arrays; /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class InsertSort { public static void main(String[] args) { int[] arr = new int[]{1, 6, 3, 7, 9, 2, 0}; insertSort(arr); System.out.println(Arrays.toString(arr)); } public static void insertSort(int[] arr) { int j = 0; int temp; //默认认为只有一个元素时,数组有序,从数组的第二个元素开始遍历,每一趟排序就会有一个元素有序 for (int i = 1; i \u003c arr.length; i++) { //如果当前数字比前一个数字小,就要把它向前放 if (arr[i] \u003c arr[i - 1]) { //取出当前的数作为临时数,保存并即将做插入操作 temp = arr[i]; //从当前数字前面一个元素开始遍历,因为是向前遍历,所以条件是j\u003e=0和临时数字小于前面的数字才做操作,否则目前就是顺序排列 for (j = i - 1; j \u003e= 0 \u0026\u0026 temp \u003c arr[j]; j--) { //每次都把当前数给后一个位置,为临时值的插入腾出空间 arr[j + 1] = arr[j]; } //temp放在合适的位置上 arr[j + 1] = temp; } } } } ","date":"2020-03-17","objectID":"/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F/:1:0","tags":null,"title":"插入排序","uri":"/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"快速排序 O(nlogn) 快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 快速排序运用了递归的思想,快速排序的结束条件是排序队列内只剩下一个元素,此时不需要再排序。 快速排序算法通过多次比较和交换来实现排序,其排序流程如下: 首先设定一个分界标准值,通常这个标准值用每次排序的第一个元素,通过该分界值将数组分成左右两部分。 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。 import java.util.Arrays; /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class QuickSort { public static void main(String[] args) { int[] arr = {1, 23, 54, 21, 2, 4, 5, 8, 10}; quickSort(arr, 0, arr.length - 1); System.out.println(Arrays.toString(arr)); } public static void quickSort(int arr[], int start, int end) { //把数组的起始位置和最后一个位置标记 int low = start; int high = end; //标记标准数为要排序数组的第一个数 int standard = arr[start]; if (start \u003c end) { //当前后指针未重合,重合表示排序结束 while (low \u003c high) { //当指针没有重合,而且标准数小于等于后指针指向的数,后指针前移 while (low \u003c high \u0026\u0026 standard \u003c= arr[high]) { high--; } //如果后指针指向的数小于标准数,那么就用当前数替换前指针指向的数 arr[low] = arr[high]; //如果前指针指向的数小于标准数,就让前指针后移 while (low \u003c high \u0026\u0026 standard \u003e= arr[low]) { low++; } //如果前指针指向的数大于标准数,就用当前数替换后指针指向的数 arr[high] = arr[low]; } //此时前指针和后指针重合,去low和high代表同一个位置,把标准数放置在重合位置,因为前后指针来回替换,总有一个多出来的重复的数字,现在用标准数将它替换掉 arr[low] = standard; //递归排序,将数组分为前后两部分,前一部分全部是小于标准数的,后一部分全部是大于的,分隔线就是low和high重合的位置,取low或high都可 //对前一部分快速排序 quickSort(arr, start, low); //对后一部分快速排序 quickSort(arr, low + 1, end); } } } ","date":"2020-03-16","objectID":"/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F/:1:0","tags":null,"title":"快速排序","uri":"/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"冒泡排序 O(n²) 冒泡排序的思想是对一组元素进行数次遍历,判断前后元素的大小,做交换,将大的数向后排,每一次遍历都将此次排序的最大数移到队列的末尾,所以每整体遍历一次就会有一个元素有序。 冒泡排序算法的原理如下: 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 针对所有的元素重复以上的步骤,除了最后一个。 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 import java.util.Arrays; /** * @author: YunTaoChen * @description: * @Date: Create in * @Modified by: */ public class BubbleSort { public static void main(String[] args) { int[] arr = {1, 3, 6, 8, 10, 2}; bubblesort(arr); System.out.println(Arrays.toString(arr)); } public static void bubblesort(int[] arr) { int temp = 0; for (int i = 0; i \u003c arr.length - 1; i++) { for (int j = 0; j \u003c arr.length - i - 1; j++) { if (arr[j] \u003e arr[j + 1]) { temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } } ","date":"2020-03-12","objectID":"/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F/:1:0","tags":null,"title":"冒泡排序","uri":"/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F/"},{"categories":null,"content":"删除二叉树的子树 //二叉树类BinaryTree.java中的删除子树方法 public void delete(int value) { //判断根节点是否是要删除的节点 if(root.value == value){ root = null; }else{ //调用TreeNode中的delete()方法 root.delete(value); } } //二叉树节点类TreeNode.java中的删除子树方法 public void delete(int value) { TreeNode parent = this; //判断左儿子,这里的parent.leftNode != null必须写在\u0026\u0026之前,避免空指针异常 if (parent.leftNode != null \u0026\u0026 parent.leftNode.value == value) { parent.leftNode = null; return; } //判断右儿子,同左儿子 if (parent.rightNode != null \u0026\u0026 parent.rightNode.value == value) { parent.rightNode = null; return; } //左子树递归查找 //把当前节点的左节点作为根节点继续查找它的左右子节点,直到为空 parent = this.leftNode; if (parent != null) { parent.delete(value); } //右子树递归查找 parent = this.rightNode; if (parent != null) { parent.delete(value); } } //测试类BinaryTreeTest.java //删除权值为7的节点 tree.delete(7); //使用前序遍历来查看删除后的结果 tree.preorder(); //删除权值为1的节点,即根节点 tree.delete(1); tree.preorder(); ","date":"2020-03-11","objectID":"/%E5%88%A0%E9%99%A4%E4%BA%8C%E5%8F%89%E6%A0%91%E5%AD%90%E6%A0%91/:1:0","tags":null,"title":"删除二叉树子树","uri":"/%E5%88%A0%E9%99%A4%E4%BA%8C%E5%8F%89%E6%A0%91%E5%AD%90%E6%A0%91/"},{"categories":null,"content":"数据结构单链表 链表 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。 实现类: /** * *@author: YuntaoChen *@description: *@Date: Create in ${Time} ${Date} *@Modified by: * */ public class Node { private int data; private Node next; public Node(int data) { this.data = data; } //取出当前节点的下一个节点 public Node getNext() { return this.next; } //追加节点 public Node append(Node node) { /*this.next = node;*/ Node currentNode = this; while (true) { Node nextNode = currentNode.next; //判断是否是最后一个节点 if (nextNode == null) { currentNode.next = node; break; } else currentNode = nextNode; } return node; } //删除下一个节点,先找到当前节点的下下个节点,保存一下,然后将保存的节点赋值给当前节点的下一个节点 public void removeNext() { Node Nextnext = this.next.next; this.next = Nextnext; } //插入节点,在当前节点的后面插入一个节点 public void insert(Node node) { Node Nextnext = this.next; this.next = node; node.next = Nextnext; } //获取当前节点的data值 public int getData() { return this.data; } //判断链表是否是空 public boolean isEmpty() { //只需要判断头节点是否为空 return this.next == null; } //判断当前节点是否是最后一个节点 public boolean isLast() { return this.next == null; } //显示所有节点的信息 public void show() { Node currentNode = this; while (true) { System.out.print(currentNode.data+\" \"); //一直取下一个节点 currentNode = currentNode.next; //如果currentNode是空节点,结束循环 if(currentNode == null){ break; } } } } 测试类: public class NodeTest { public static void main(String[] args) { Node n1 = new Node(1); Node n2 = new Node(2); Node n3 = new Node(3); Node n4 = new Node(4); Node n5 = new Node(5); n1.append(n2); n1.append(n3); n1.append(n4); n2.insert(n5); n5.removeNext(); System.out.println(n1.getNext().getData()); System.out.println(n2.getNext().getData()); System.out.println(n1.getNext().getNext().isLast()); n1.show(); } } ","date":"2020-03-10","objectID":"/%E5%8D%95%E9%93%BE%E8%A1%A8/:1:0","tags":null,"title":"单链表","uri":"/%E5%8D%95%E9%93%BE%E8%A1%A8/"},{"categories":null,"content":"双向循环链表 双向循环链表是在循环链表的基本思想下,新增加了一个头指针,指向前一个元素。 特别要注意的是创建每一个独立的双向节点时,它的前后指针都指向自己。 如果做插入元素操作,从当前元素的后一个开始插入,首先保存当前元素的下一个元素(如果当前元素后没有其他元素,那么当前元素的尾指针总是指向第一个元素) 实现类: import java.util.zip.DeflaterOutputStream; /** * @author: YunTaoChen * @description: * @Date: Create in 2020/3/10 * @Modified by: */ public class DoubleNode { private int data; private DoubleNode pre = this; private DoubleNode next = this; public DoubleNode(int data) { this.data = data; } public DoubleNode getNext() { return this.next; } public DoubleNode getPre() { return this.pre; } public int getData() { return this.data; } //尾插:创建每一个独立的双向节点时,它的前后指针都指向自己,从当前元素的后一个开始插入,首先保存当前元素的下一个元素(如果当前元素后没有其他元素,那么当前元素的尾指针总是指向第一个元素) public void insert(DoubleNode node) { DoubleNode Nextnext = this.next; this.next = node; node.pre = this; node.next = Nextnext; Nextnext.pre = node; } //删除当前节点,并返回当前节点 public DoubleNode removeNode() { DoubleNode currentNode = this.next; this.pre.next = currentNode; currentNode.pre = this.pre; return this; } } 测试类: /** * @author: YunTaoChen * @description: * @Date: * @Modified by: */ public class DoubleNodeTest { public static void main(String[] args) { DoubleNode n1 = new DoubleNode(1); DoubleNode n2 = new DoubleNode(2); DoubleNode n3 = new DoubleNode(3); n1.insert(n2); n2.insert(n3); System.out.println(n1.getNext().getData()); System.out.println(n2.getNext().getData()); System.out.println(n3.getNext().getData()); System.out.println(n3.getPre().getData()); } } ","date":"2020-03-10","objectID":"/%E5%8F%8C%E5%90%91%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8/:1:0","tags":null,"title":"双向循环链表","uri":"/%E5%8F%8C%E5%90%91%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8/"},{"categories":null,"content":"ECMAScript,DOM 和 BOM 我们都知道, javascript 有三部分构成,ECMAScript,DOM 和 BOM,根据宿主(浏览器)的不同,具体的表现形式也不尽相同,ie 和其他的浏览器风格迥异。 ","date":"2020-03-10","objectID":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/:0:0","tags":null,"title":"BOM和DOM","uri":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/"},{"categories":null,"content":"BOM BOM 是 Browser Object Model,浏览器对象模型。 BOM 将浏览器的各个组成部分封装成对象。 BOM 包括: Navigator 浏览器对象 window 窗口对象 Location 地址栏对象 History 历史记录对象 Screen 显示器屏幕对象 ","date":"2020-03-10","objectID":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/:1:0","tags":null,"title":"BOM和DOM","uri":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/"},{"categories":null,"content":"Window 对象 特点: Window 对象不需要创建可以直接使用,使用方法:window.方法名(); window 引用可以省略。 常用方法: 1.与弹出有关的方法 alert() 显示带有一段消息和一个确认按钮的警告框。 confirm() 显示带有一段消息以及确认按钮和取消按钮的对话框,返回值是 boolean。 如果用户点击确定按钮,则方法返回 true 如果用户点击取消按钮,则方法返回 false prompt() 显示可提示用户输入的对话框,返回值是获取用户输入的值。 2.与打开关闭有关的方法 open() 打开一个新窗口,参数可以传 url 字符串,返回一个新的 window 对象。 close() 关闭一个窗口,由哪个对象调用的 close 方法就关闭哪个对象。 3.与定时器有关的方法 setTimeout(参数 1,参数 2) 一次性定时器,参数 1:可以传一个 js 代码片段或者函数对象,参数 2:延迟的毫秒数。返回值返回一个唯一编号。 clearTimeout() 取消定时器,参数是 setTimeout()的返回值,可以根据每个一次性定时器的唯一返回值来取消某一个一次性定时器。 setInterval(参数 1,参数 2) 循环定时器,在 setTimeout()的基础上增加了循环功能,每(参数 2)毫秒执行(参数 1)。返回值返回一个唯一编号。 clearInterval() 取消定时器,参数是 setInterval()的返回值,可以根据每个循环定时器的唯一返回值来取消某一个一次性定时器。 属性: 1.获取其他 BOM 对象 history location screen Navigator 2.获取 DOM 对象 document ","date":"2020-03-10","objectID":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/:1:1","tags":null,"title":"BOM和DOM","uri":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/"},{"categories":null,"content":"Location 对象 地址栏对象 创建: window.location location(省略 window.写法) 常用方法 reload() 重新加载当前的文档、刷新 常用属性 href 属性是一个可读可写的字符串,可设置或返回当前显示的文档的完整 URL 可以使用location.href = \"www.baidu.com\";来给 href 属性设置值。 ","date":"2020-03-10","objectID":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/:1:2","tags":null,"title":"BOM和DOM","uri":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/"},{"categories":null,"content":"History 对象 历史记录对象 创建: window.history history 常用方法: back() 加载 history 列表中的前一个 URL。 forward() 加载 history 列表中的后一个 URL。 go(参数) 加载 history 列表中的某个具体页面。 参数如果是正数,则前进参数个历史记录页面 参数如果是负数,则后退参数个历史记录页面 常用属性: length 返回当前窗口列表中的 URL 数量。 ","date":"2020-03-10","objectID":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/:1:3","tags":null,"title":"BOM和DOM","uri":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/"},{"categories":null,"content":"DOM DOM 是 Document Object Model,文档对象模型 DOM 将标记语言文档的各个组成部分,封装成对象。可以使用这些对象,对标记语言文档进行 CRUD 的动态操作。 W3C DOM 标准被分为 3 个不同的部分: 核心 DOM 针对任何结构化文档的标准模型 核心 DOM 包括: Document:文档对象 Element:元素对象 Attribute:属性对象 Text:文本对象 Comment:注释对象 Node:节点对象,是其他 5 个对象的父对象 XML DOM 针对任何 XML 文档的标准模型 HTML DOM 针对任何 HTML 文档的标准模型 ","date":"2020-03-10","objectID":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/:2:0","tags":null,"title":"BOM和DOM","uri":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/"},{"categories":null,"content":"核心 DOM 模型: ","date":"2020-03-10","objectID":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/:2:1","tags":null,"title":"BOM和DOM","uri":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/"},{"categories":null,"content":"Document:文档对象 创建: window.document document 方法: 获取 Element 对象 getElementById() 根据 id 属性值获取元素对象。id 属性值一般来说唯一。 getElementsByTagName() 根据元素名称获取元素对象,返回值是一个数组。 getElementsByClassName() 根据 class 的属性值获取元素对象。返回值是一个数组。 getElementsByName() 根据 name 属性值获取元素对象。返回值是一个数组。 创建其他 DOM 对象 createAttribute(name) createComment() createElement() 常用 var table = document.createElement(\"table\");//创建了一个table对象 createTextNode() ","date":"2020-03-10","objectID":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/:2:2","tags":null,"title":"BOM和DOM","uri":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/"},{"categories":null,"content":"Element:元素对象 创建: 使用 document 来获取和创建 常用方法: removeAttribute(属性参数) 删除指定的属性。 setAttribute(参数 1,参数 2) 给目标对象设置一个属性。参数 1 是要设置的属性,参数 2 是属性参数。 ","date":"2020-03-10","objectID":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/:2:3","tags":null,"title":"BOM和DOM","uri":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/"},{"categories":null,"content":"Node:节点对象 Node 对象是整个 DOM 的主要数据类型。 节点对象代表文档树中的一个单独的节点。 特点:所有 DOM 对象都可以被认为是一个节点 DOM 树: 常用方法: CRUD dom 树 appendChild() 向节点的子节点列表的结尾添加新的子节点。 removeChild() 删除并返回当前节点的指定子节点。 replaceChild() 用新节点替换一个子节点。 属性: parentNode 返回当前节点的父节点。 ","date":"2020-03-10","objectID":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/:2:4","tags":null,"title":"BOM和DOM","uri":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/"},{"categories":null,"content":"HTML DOM 标签体的设置和获取:使用 innerHTML table.innerHTML += \"\u003ctr\u003e\\n\" + \" \u003ctd\u003e\" + id + \"\u003c/td\u003e\\n\" + \" \u003ctd\u003e\" + name + \"\u003c/td\u003e\\n\" + \" \u003ctd\u003e\" + gender + \"\u003c/td\u003e\\n\" + ' \u003ctd\u003e\u003ca href=\"javascript:void(0);\" onclick=\"delTr(this);\" \u003e删除\u003c/a\u003e\u003c/td\u003e\\n' + \" \u003c/tr\u003e\"; //向table表格对象中添加一些html内容 使用 html 元素对象的属性 控制样式 使用 style 属性来设置点击后的样式。 \u003cscript\u003e var div1 = document.getElementById(\"div1\"); div1.onclick = function () { div1.style.width = \"100px\"; div1.style.border = \"1px solid red\"; div1.style.fontSize = \"20px\"; }; \u003c/script\u003e 提前定义好 css 样式,通过元素的 className 属性拉力设置点击后的样式。 \u003cstyle\u003e .d1 { border: blue 1px solid; width:200px; height:100px; } \u003c/style\u003e \u003cscript\u003e var div2 = document.getElementById(\"div2\"); div2.onclick = function () { div2.className = \"d1\"; }; \u003c/script\u003e ","date":"2020-03-10","objectID":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/:2:5","tags":null,"title":"BOM和DOM","uri":"/%E6%96%87%E6%A1%A3%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B%E5%92%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B/"},{"categories":null,"content":"循环链表 循环链表的实现和单链表基本相同,只是它的结构改变了,无头尾节点,整个链表是一个循环的结构。 循环链表的构造:每创建一个新的节点就让他的下一个元素指向它自己,这样来构成循环。 因为没有头尾节点,所以循环链表的追加元素方法变成了插入元素的方法(尾插法)。 实现类: public class LoopNode { private int data; //每一个独立的节点的next都指向它自己 private LoopNode next = this; public LoopNode(int data) { this.data = data; } //取出当前节点的下一个节点 public LoopNode getNext() { return this.next; } //删除下一个节点,先找到当前节点的下下个节点,保存一下,然后将保存的节点赋值给当前节点的下一个节点 public void RemoveNext() { LoopNode Nextnext = this.next.next; this.next = Nextnext; } //循环链表没有头和尾,所以只有插入节点,在当前节点的后面插入一个节点 //插入节点的思想是:先取到当前节点的下一个节点保存,然后将要插入的节点赋值给当前节点的下一个节点,最后将保存的节点赋值给插入节点的下一个节点 public void insert(LoopNode node) { LoopNode Nextnext = this.next; this.next = node; node.next = Nextnext; } //获取当前节点的data值 public int getData() { return this.data; } } 测试类: public class LoopNodeTest { public static void main(String[] args) { LoopNode n1 = new LoopNode(1); LoopNode n2 = new LoopNode(2); LoopNode n3 = new LoopNode(3); n1.insert(n2); n2.insert(n3); System.out.println(n1.getNext().getData()); System.out.println(n3.getNext().getData()); } } ","date":"2020-03-10","objectID":"/%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8/:1:0","tags":null,"title":"循环链表","uri":"/%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8/"},{"categories":null,"content":"Request 和 Response 假设一个场景:我们使用客户端的浏览器输入 url 去访问服务器(tomcat),叫做一个请求,请求包含请求消息,服务器会最终会给我们一个响应,这里面的响应消息是人为设置的,然而这里面的具体操作如下: tomcat 服务器会根据请求的 url 中的资源路径(这里是虚拟目录,人为配置的资源路径),创建对应的 Servlet 实现类对象 tomcat 服务器,会创建 request 和 response 对象,request 对象中封装请求消息数据 tomcat 将 request 和 response 两个对象传递给service方法,并且调用service方法,进行具体的操作 我们可以通过 request 对象获取请求消息数据,通过 response 对象设置响应消息数据 服务器在给浏览器做出响应之前,会从 response 对象中拿已经设置好的响应消息数据 request 和 response 对象的原理 request 和 response 对象是由服务器创建的 request 对象是来获取请求消息,response 对象是来设置响应消息 request 对象继承的体系结构 ServletRequest 接口 HttpServletRequest 接口是 ServletRequest 的子接口,由 tomcat 服务器来实现 具体是在 tomcat 源码下的java.org.apache.catalina.connector.RequestFacade 类实现了 HttpServletRequest request 功能 获取请求消息数据 获取请求行的数据 例:GET(请求方式) /tomcat/demo2?name=zhangsan HTTP/1.1 方法: 获取请求方式:获取 GET、POST 或者其他请求方式 String getMethod() 获取虚拟目录,这里是/tomcat: String getContextPath() 获取 Servlet 路径,这里是/demo2: String getServletPath() 获取get 方式的请求参数,这里是name=zhangsan: String getQueryString() 获取请求的 URI/URL(URL 是 URI 的子集,URL 可以是 URL,URI 不一定是 URL,URL 可以实现某个资源的具体 locate) String getRequestURI() ,这里是 /tomcat/demo2 StringBuffer getRequestURL(),这里是 https://localhost:80/tomcat/demo2 获取协议以及版本 HTTP/1.1 String getProtocol() 获取客户机的 ip 地址 String getRemoteAddr() 获取请求头的数据 方法: String getHeader(String name) 通过请求头的名称获取请求头的值,参数值不区分大小写 常用请求头: user-agent:使用的浏览器版本 referer:发出请求的 URL Enumeration\u003cString\u003e getHeaderNames() 获取所有的请求头名称 获取请求体的数据 请求体:只有 Post 请求方式,才有请求体,在请求体中封装了 Post 请求的请求参数 获取方法: 获取流对象 BufferedReader getReader() 获取字符输入流,只能操作字符数据 ServletInputStream getInputStream() 获取字节输入流,可以操作所有类型的数据 再从流对象中拿数据 其他功能 获取请求参数通用的方法,兼容 get 和 post 两种请求方式 String getParameter(String name) 根据参数名称获取参数值 String[] getParameterValues(String name) 根据参数名称获取参数值的数组 Enumeration\u003cString\u003e getParameterNames() 获取所有请求参数的名称 Map\u003cString,String[]\u003e getParameterMap() 获取所有参数的 Map 集合 使用keySet()可以把 Map 里的key封装成Set,再通过get()来获取每一个key对应的value[] 使用entrySet() 中文乱码问题 在 doPost 方法中的获取请求参数之前使用request.setCharacterEncoding(\"utf-8\");可以避免乱码 请求转发:一种在服务器内部的资源跳转方式 步骤: 通过 Request 对象获取请求转发器对象:RequestDispatcher getRequestDispatcher (String path) 通过 RequestDispatcher 对象来进行转发:forward(ServletRequest request,Servlet Response response) 特点: 浏览器地址栏路径不发生变化 只能转发到当前服务器内部资源中 转发是一次请求 共享数据: 域对象:一个有作用范围的对象,可以在范围内共享数据 request 域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据 方法: setAttribute(String name,Object obj):存储数据 Object getAttribute(String name):通过键获取值 removeAttribute(String name):通过键移除键值对 获取 ServletContext : request.getServletContext() ","date":"2020-02-29","objectID":"/request%E5%92%8Cresponse%E5%8E%9F%E7%90%86/:1:0","tags":null,"title":"请求响应原理原理","uri":"/request%E5%92%8Cresponse%E5%8E%9F%E7%90%86/"},{"categories":null,"content":"Http http 概念:hyper text transfer protocol 超文本传输协议 传输协议:定义了客户端和服务器端通信时发送数据的格式 特点: 基于 tcp/ip 的高级协议 默认端口号:80 例如 https:域名:80(默认不写) 基于 request/response 的模型:一次请求对应一次响应 无状态的:每次请求之间相互独立,不能交互数据 历史版本: ","date":"2020-02-29","objectID":"/http/:1:0","tags":null,"title":"HTTP","uri":"/http/"},{"categories":null,"content":"0.9 0.9 协议是适用于各种数据信息的简洁快速协议,但是远不能满足日益发展的各种应用的需要。0.9 协议就是一个交换信息的无序协议,仅仅限于文字。由于无法进行内容的协商,在双发的握手和协议中,并有规定双发的内容是什么,也就是图片是无法显示和处理的。 ","date":"2020-02-29","objectID":"/http/:1:1","tags":null,"title":"HTTP","uri":"/http/"},{"categories":null,"content":"1.0 到了 1.0 协议阶段,也就是在 1982 年,TimBerners-Lee 提出了 HTTP/1.0。在此后的不断丰富和发展中,HTTP/1.0 成为最重要的面向事务的应用层协议。该协议对每一次请求/响应建立并拆除一次连接。其特点是简单、易于管理,所以它符合了大家的需要,得到了广泛的应用。 ","date":"2020-02-29","objectID":"/http/:1:2","tags":null,"title":"HTTP","uri":"/http/"},{"categories":null,"content":"1.1 在 1.0 协议中,双方规定了连接方式和连接类型,这已经极大扩展了 HTTP 的领域,但对于互联网最重要的速度和效率,并没有太多的考虑。毕竟,作为协议的制定者,当时也没有想到 HTTP 协议会有那么快的普及速度。 ","date":"2020-02-29","objectID":"/http/:1:3","tags":null,"title":"HTTP","uri":"/http/"},{"categories":null,"content":"2.0 HTTP2.0 的前世是 HTTP1.0 和 HTTP1.1。虽然之前仅仅只有两个版本,但这两个版本所包含的协议规范之庞大,足以让任何一个有经验的工程师为之头疼。网络协议新版本并不会马上取代旧版本。实际上,1.0 和 1.1 在之后很长的一段时间内一直并存,这是由于网络基础设施更新缓慢所决定的。 请求消息的格式 请求行 请求行的格式:(http 有 7 种请求方式,常用 get 和 post 请求方式) 请求方式 请求 url 请求协议/版本 例如:GET /login.html HTTP/1.0 请求方式的区别 http 协议中的的 GET 和 POST:参数位置不同 GET: 请求参数在请求行中,在 url 后面,无请求体 请求的 url 长度有限制 安全性低 POST: 请求参数在请求体中 请求的 url 长度没有限制 安全性高 请求头:客户端浏览器告诉服务器一些信息 常见 User-Agent:浏览器告诉服务器,访问你使用的浏览器版本信息 可以在服务器端获取该头的信息,解决浏览器的兼容性的问题 Referer:告诉服务器,我从哪里来 作用 防盗链 统计工作 请求空行 空行 请求体(正文) 封装 POST 请求消息的请求参数的 ","date":"2020-02-29","objectID":"/http/:1:4","tags":null,"title":"HTTP","uri":"/http/"},{"categories":null,"content":"Servlet 基本概念:运行在服务器端的小程序 Servlet 就是一个接口,定义了 java 类被浏览器访问到的规则(被服务器识别的规则) 用户需要自定义一个类,实现 Servlet 接口,重写方法 第一个 Servlet 创建 JavaEE 项目 定义一个类,实现 Servlet 接口 public class servletImp implements Servlet 实现接口中的抽象方法 配置 Servlet web.xml 里面加入 servlet 标签 \u003cservlet\u003e \u003cservlet-name\u003edemo1\u003c/servlet-name\u003e \u003cservlet-class\u003ecom.servlet.servletImp\u003c/servlet-class\u003e \u003c/servlet\u003e \u003cservlet-mapping\u003e \u003cservlet-name\u003edemo1\u003c/servlet-name\u003e \u003curl-pattern\u003e/demo1\u003c/url-pattern\u003e \u003c/servlet-mapping\u003e Servlet 执行原理 当服务器接收到客户端浏览器的请求后,会解析请求 url 路径,获取访问的 Servlet 的资源路径 查找 web.xml 文件,是否有对应的标签体内容 如果有,则在找到对应的里面的全类名 tomcat 会将字节码文件加载进内存,并创建其对象 调用 Servlet 的方法 Servlet 中的方法 /** * 初始化方法,在servlet创建时会执行一次 * @param servletConfig * @throws ServletException */ public void init(ServletConfig servletConfig) throws ServletException { } /** * 每一次servlet被访问时,执行,执行多次 * @param servletRequest * @param servletResponse * @throws ServletException * @throws IOException */ public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { } /** * 销毁方法,服务器正常关闭时,执行,执行一次 */ public void destroy() { } /** * serclet配置对象,需要实现 * @return */ public ServletConfig getServletConfig() { return null; } /** * 获取servlet信息的方法,需要实现 * @return */ public String getServletInfo() { return null; } Servlet 生命周期 被创建:执行 init 方法,只执行一次 Servlet 的 init 方法,只执行一次,说明一个 Servlet 在内存中只存在一个对象,Servlet 是单例的 多个用户同时访问时,可能会出现安全问题 解决方案:尽量不在 Servlet 中定义成员变量,应该在方法中定义局部变量,如果已经定义了成员变量,不要对变量修改值 Servlet 什么时候被创建? 默认情况下,第一次被访问时,Servlet 被创建 可以配置执行 Servlet 的创建时机,在 web.xml 中的标签中指定标签的内容来指定 Servlet 的创建时机 第一次被访问时被创建:中的值为负数,默认值是-1 在服务器启动时被创建:中的值为大于 1 的整数 提供服务:执行 service 方法,执行多次 每次访问 Servlet 时,Service 方法都会被调用一次 被销毁:执行 destroy 方法,执行一次 Servlet 被销毁时执行(销毁之前),服务器关闭时,Servlet 被销毁 只有服务器正常关闭时,才会执行 destroy 方法 Servlet3.0 支持注解配置 Servlet3.0,可以支持注解配置,使用注解配置来代替繁琐的 xml 文档配置 定义一个类,实现 Servlet 接口 重写方法 在类上使用@WebServlet 注解,进行配置 @WebServlet(“资源路径”) Servlet 体系结构 Servlet 有两个抽象的实现类:GenericServlet 和 HttpServlet GenericServlet 将 Servlet 接口中除了 service 方法以外的其他方法重写并没有做具体实现,只有 service 方法被定义成了抽象方法,所以只要在自定义的类中重写 service 方法即可 HttpServlet 是 GenericServlet 的子类,是对 http 协议的一种封装,用来简化操作 定义类集成 HttpServlet 重现 doGet 和 doPost 方法 Servlet 相关配置 url-pattern:Servlet 访问路径 一个 Servlet 可以定义多个访问路径:@WebServlet({\"/d2\",\"/de2\",\"/demo2\"}) 路径的定义规则: /xxx /xxx/xcomxx 多层路径:可以使用*通配符:/*或者/user/*访问优先级最低 *.a(后缀名随意设置) 带.a 后缀名的路径:任意路径加.a 后缀来访问 注意:/ *.a 报错 ","date":"2020-02-28","objectID":"/servlet/:1:0","tags":null,"title":"Servlet","uri":"/servlet/"},{"categories":null,"content":"XML XML **概念:**Extensible Markup language 可扩展标记语言 可扩展:标签都是自定义的 功能: 存储数据 配置文件 在网络中传输数据 XML和HTML的区别 xml标签都是自定义的,html标签都是预定义的 xml的语法严格,html的语法松散 xml是存储数据的,html是展示数据的 语法 xml文档的后缀名是.xml xml文档的第一行必须写文档声明 xml文档中有且仅有一个根标签 属性值必须使用引号引起来 标签必须正确关闭 xml标签名称区分大小写 XML组成部分 文档声明: 格式: 属性列表: version 版本号 encoding 编码方式:告知解析引擎当前文档使用的字符集 standalone 是否独立: 取值:yes:依赖其他文件 no:不依赖其他文件 指令:结合css来控制xml中标签的样式 标签:标签名称自定义,遵循万维网指定的规则 属性:id值唯一 文本: CDATA区:在该区域中的数据会被原样展示 格式: 约束:规定xml文档的书写规则 作为框架的使用者 能够在xml中引入约束文档 能够读懂约束文档 分类 DTD:一种简单的约束技术 引入dtd文档到xml文档中 内部dtd:将约束规则定义在xml文档中 外部dtd:将约束的规则定义在外部的dtd文件中 本地: 网络: Schema;一种复杂的约束技术,可以限定标签的内容 后缀名.xsd 引入Schema文档到xml 填写xml文档的根元素 引入xsi前缀 xmlns:xsi=\"\" 引入xsd文件命名空间 xsi:schemaLocation=\"xsd文档的URL名 XXX.xsd\" 为每一个xsd约束声明一个前缀作为标识 xmlns=\"xsd文档的URL名\" 解析XML文档 操作xml文档 解析(读取):将文档中的数据读取到内存中 写入:将内存中的数据保存到xml文档中,持久化的存储 解析xml的方式 DOM:将标记语言文档一次性加载进内存,生成一棵DOM树 优点:操作方便,可以对文档进行CRUD的所有操作 缺点:占内存多 SAX:逐行读取,基于事件驱动的 优点:不占内存 缺点:只能读取,读取完立即释放,不能CRUD xml常见的解析器 JAXP:sun公司提供的解析器,支持dom和sax两种思想 DOM4J:一款非常优秀的解析器 Jsoup;Jsoup是一款java的HTML解析器,也可以用来解析XML。它提供了一套非常省力的API,可通过DOM,css以及类似于JQuery的操作方法来取出和操作数据 PULL:安卓操作系统内置的解析器,sax方式的 Jsoup的使用 导入jar包 获取Document对象 获取对应的标签Element对象 获取数据 public class JsoupDemo { public static void main(String[] args) throws IOException { //获取document对象,根据xml文档获取 //获取student.xml的path System.out.println(); String path = JsoupDemo.class.getClassLoader().getResource(\"student.xml\").getPath(); Jsoup.parse(path); //解析xml文档,加载文档进内存,获取dom树 Document document = Jsoup.parse(new File(path), \"utf-8\"); //获取元素对象 Elements names = document.getElementsByTag(\"name\"); Element name = names.get(0); String ele = name.text(); System.out.println(ele); } } student.xml: \u003c?xml version=\"1.0\" encoding=\"utf-8\" ?\u003e \u003cstudent\u003e \u003cstudent number=\"0001\"\u003e \u003cname\u003ezhangsan\u003c/name\u003e \u003cage\u003e18\u003c/age\u003e \u003csex\u003emale\u003c/sex\u003e \u003c/student\u003e \u003cstudent number=\"0002\"\u003e \u003cname\u003elisi\u003c/name\u003e \u003cage\u003e20\u003c/age\u003e \u003csex\u003efemale\u003c/sex\u003e \u003c/student\u003e \u003c/student\u003e 对象的使用 Jsoup:工具类,可以解析html或xml文档,返回Document parse:解析html或xml文档,返回Document parse(File in, String charsetName) 解析xml或html文件 parse(String html) 解析xml或html字符串 parse(URL url, int timeoutMillis) 通过网络路径解析 Document:文档对象。代表内存中的dom树 获取Element对象 getElementById(String id) 根据id属性值获取唯一的element对象 getElementByTag(String tagName) 根据标签名称获取元素对象集合 getElementByAttribute(String key) 根据属性名称获取元素对象的集合 getElementByAttributeValue(String key,String value) 根据对应的属性名和属性值获取元素对象集合 Elements:元素Element对象的集合,可以当作ArrayList来使用 Element:元素对象 获取element对象(获取的是子标签对象) getElementById(String id) 根据id属性值获取唯一的element对象 getElementByTag(String tagName) 根据标签名称获取元素对象集合 getElementByAttribute(String key) 根据属性名称获取元素对象的集合 getElementByAttributeValue(String key,String value) 根据对应的属性名和属性值获取元素对象集合 获取属性值 String attr(String key) 根据属性名称获取属性值,属性名不区分大小写 获取文本内容 String text() 获取文本内容,所有纯文本内容 String html() 获取标签体所有内容,包括子标签的标签和文本内容 Node:节点对象 document和element的父类 快捷的查询方式 selector:选择器 使用方法:Elements select(String cssQuery) document.select(“标签名称/#id名称/.类名称”); Xpath:是xml路径语言,它是一种用来确定XML文档中某部分位置的语言 使用Jsoup的Xpath需要额外导入jar包 根据document对象,创建JXDocument对象,来自JsoupXpath包 语法详情见w3school文档 ","date":"2020-02-22","objectID":"/xml/:0:0","tags":null,"title":"XML","uri":"/xml/"},{"categories":null,"content":"javascript:void(0);含义 我们经常会使用到 javascript:void(0) 这样的代码,那么在 JavaScript 中 javascript:void(0) 代表的是什么意思呢? javascript:void(0) 中最关键的是 void 关键字, void 是 JavaScript 中非常重要的关键字,该操作符指定要计算一个表达式但是不返回值。 \u003ca\u003e\u003c/a\u003e超链接的功能: 1.可以被点击,有一个下划线样式 2.点击后跳转到 href 指定的 url 如果要保留 1 功能,去掉 2 功能,需要将 href=\"javascript:void(0);” javascript 是伪协议,表示这个 href 的值要使用 js 代码,而 void 是 JavaScript 中非常重要的关键字,该操作符指定要计算一个表达式但是不返回值。 javascript:void(0);仅仅表示一个死链接。 ","date":"2020-02-16","objectID":"/href-javascript-void/:1:0","tags":null,"title":"href=\"javascript:void(0);","uri":"/href-javascript-void/"},{"categories":null,"content":"href=”#“与 href=\"javascript:void(0);“的区别 URL 超链接的 URL。 可能的值: 绝对 URL - 指向另一个站点(比如 href=\"www.google.com”) 相对 URL - 指向站点内的某个文件(href=\"index.htm”) 锚 URL - 指向页面中的锚(href=”#top”) \u003ca href=\"\" \u003e 中 href 为空,会怎样? 答:点击会刷新页面,相当于访问当前URL。 # 包含了一个位置信息,默认的锚是**#top** 也就是网页的上端。 而 javascript:void(0); 仅仅表示一个死链接。 在页面很长的时候会使用 # 来定位页面的具体位置,格式为:# + id。 如果你要定义一个死链接请使用 javascript:void(0); 。 ","date":"2020-02-16","objectID":"/href-javascript-void/:2:0","tags":null,"title":"href=\"javascript:void(0);","uri":"/href-javascript-void/"},{"categories":null,"content":"js 关于锚(a)的几种调用方法 1、a href=\"javascript:js_method();\" 这是常用的方法,但是这种方法在传递this等参数的时候很容易出问题,而且javascript:协议作为a的href属性的时候不仅会导致不必要的触发window.onbeforeunload事件,在IE里面更会使gif动画图片停止播放。W3C标准不推荐在href里面执行javascript语句 2、a href=\"javascript:void(0);\" οnclick=\"js_method()\" 这种方法是很多网站最常用的方法,也是最周全的方法,onclick方法负责执行js函数,而void是一个操作符,void(0)返回undefined,地址不发生跳转。而且这种方法不会像第一种方法一样直接将js方法暴露在浏览器的状态栏。 3、a href=\"javascript:;\" οnclick=\"js_method()\" 这种方法跟跟2种类似,区别只是执行了一条空的js代码。 4、a href=\"#\" οnclick=\"js_method()\" 这种方法也是网上很常见的代码,#是标签内置的一个方法,代表top的作用。所以用这种方法点击后网页后返回到页面的最顶端。 5、a href=\"#\" οnclick=\"js_method();return false;\" 这种方法点击执行了js函数后return false,页面不发生跳转,执行后还是在页面的当前位置。 综合上述,在 a 中调用 js 函数最适当的方法推荐使用: \u003ca href=\"javascript:void(0);\" οnclick=\"js_method()\"\u003e\u003c/a\u003e \u003ca href=\"javascript:;\" οnclick=\"js_method()\"\u003e\u003c/a\u003e \u003ca href=\"#\" οnclick=\"js_method();return false;\"\u003e\u003c/a\u003e ","date":"2020-02-16","objectID":"/href-javascript-void/:3:0","tags":null,"title":"href=\"javascript:void(0);","uri":"/href-javascript-void/"},{"categories":null,"content":"栈和队列 许多基础数据类型都和对象的集合有关。具体来说,数据类型的值就是一组对象的集合,所有操作都是关于添加、删除或者是访问集合中的对象。这里主要讨论的是栈(stack)和队列(queue)的几种实现。 ","date":"2020-02-16","objectID":"/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/:1:0","tags":null,"title":"栈和队列","uri":"/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/"},{"categories":null,"content":"链表 **定义:**链表是一种递归的数据结构,它或者为空(null),或者是指向一个结点(node)的引用,该结点含有一个泛型元素和一个指向另一条链表的引用。 **注意:**之所以在这里定义,是因为后续我们的栈和队列的实现都要使用链表来构造。在结构化存储数据集时,链表是数组的一种重要的代替方式。 //定义Node,且将Node声明为私有嵌套类之后,我们可以将Node的方法和实例变量的访问范围限制在包含它的类中 private class Node { Item item; Node next; } 数据结构 优点 缺点 数组 通过索引可以直接访问任意元素 在初始化时就需要知道元素的数量 链表 使用的空间大小和元素数量成正比 需要通过引用访问任意元素 ","date":"2020-02-16","objectID":"/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/:1:1","tags":null,"title":"栈和队列","uri":"/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/"},{"categories":null,"content":"栈 下压栈或简称栈,是一种基于后进先出(LIFO)策略的集合类型。 下压栈(后进先出,LIFO) public class Stack\u003cItem\u003e implements Iterable\u003cItem\u003e Stack() //创建一个空栈 void push() //添加一个元素 Item pop() //删除最近添加的元素 boolean isEmpty() //栈是否为空 int size() //栈中元素的数量 下压(LIFO)栈(能够动态的调整数组大小) import java.util.Iterator; import java.util.Objects; public class ResizingArrayStack\u003cItem\u003e implements Iterable\u003cItem\u003e { private Item[] a = (Item[]) new Objects[1];//栈元素 private int N = 0;//元素数量 public boolean isEmpty() { return N == 0; } public int size() { return N; } private void resize(int max) { //将栈移动到一个大小为max的新数组 Item[] temp = (Item[]) new Objects[max]; for (int i = 0; i \u003c N; i++) { temp[i] = a[i]; } a = temp; } public void push(Item item) { //将元素添加到栈顶 if (N == a.length) resize(2 * a.length); a[N++] = item; } public Item pop() { //从栈顶删除元素 Item item = a[--N]; a[N] = null;//避免对象游离 if (N \u003e 0 \u0026\u0026 N == a.length / 4) resize(a.length / 2); return item; } @Override public Iterator\u003cItem\u003e iterator() { return (Iterator\u003cItem\u003e) new ReverseArrayIterator(); } private class ReverseArrayIterator implements Iterable\u003cItem\u003e { //支持后进先出的迭代 private int i = N; public boolean hasnext() { return i \u003e 0; } public Item next() { return a[--i]; } public void remove() { } @Override public Iterator\u003cItem\u003e iterator() { return null; } } } 这份泛型的可迭代的Stack API的实现是所有集合类抽象数据类型实现的模板。它将所有元素保存在数组中,并动态调整数组的大小以保持数组大小和栈大小之比小于一个常数。 特别要注意的是 push()操作中,检查数组是否太小。具体来说,我们会通过检查栈大小 N 和数组大小a.length是否相等来检查数组是否能够容纳新的元素。如果没有多余的空间,我们会将数组的长度加倍。然后就可以像以前一样使用a[N++] = item插入新元素了。 类似,pop()操作中,首先删除栈顶元素,然后如果数组太大我们就将它的长度减半。只要稍加思考,你就明白正确的检测条件是栈的大小是否小于数组的四分之一。在数组长度被减半之后,它的状态大约为半满,在下次需要改变数组大小之前 仍然能够进行多次push()和pop()操作。 在以上实现中,栈永远不会溢出,使用率也永远不会低于四分之一(除非栈为空,那种情况下数组的大小为 1)。 应用 算数表达式求值 将操作数压入操作数栈; 将运算符压入运算符栈; 忽略左括号; 在遇到右括号时,弹出一个运算符,弹出所需数量的操作数,并将运算符和操作数的运算结果压入操作数栈。 Dijkstra 的双栈算数表达式求值算法 public class Evaluate { public static void main(String[] args) { Stack\u003cString\u003e ops = new Stack\u003cString\u003e(); Stack\u003cDouble\u003e vals = new Stack\u003cDouble\u003e(); while (!StdIn.isEmpty()) {//读取字符,如果是运算符则压入栈 String s = StdIn.readString(); if (s.equals(\"(\")) ; else if (s.equals(\"+\")) ops.push(s); else if (s.equals(\"-\")) ops.push(s); else if (s.equals(\"*\")) ops.push(s); else if (s.equals(\"/\")) ops.push(s); else if (s.equals(\"sqrt\")) ops.push(s); else if (s.equals(\")\")) {//如果字符是“)”则弹出运算符和操作数,计算结果并压入栈 String op = ops.pop(); double v = vals.pop(); if (op.equals(\"+\")) v = vals.pop() + v; else if (op.equals(\"-\")) v = vals.pop() - v; else if (op.equals(\"*\")) v = vals.pop() * v; else if (op.equals(\"/\")) v = vals.pop() / v; else if (op.equals(\"sqrt\")) v = Math.sqrt(v); vals.push(v); }//如果字符既不是运算符也不是运算数,将它作为double压入栈 else vals.push(Double.parseDouble(s)); } StdOut.println(vals.pop()); } } 下压堆栈(链表实现) public class Stack\u003cItem\u003e implements Iterable\u003cItem\u003e{ private Node first;//栈顶,最近添加的元素 private int N;//元素的数量 private class Node{ //定义了结点的嵌套 Item item; Node next; } public boolean isEmpty(){return first == null;}//或N == 0 public int size(){return N;} public void push(Item item){ Node oldfirst = first;//将栈顶元素变成第二个最近添加的元素 first = new Node();//申请一个新的结点 first.item = item;//新结点的item赋值 first.next = oldfirst;//连接,此时新结点变成栈顶的元素 N++; } public Item pop(){ Item item = first.item;//保存栈顶元素的item值 first = first.next;//修改栈顶元素,first的后一个元素变为栈顶元素 N--; return item;//返回栈顶元素 } } ","date":"2020-02-16","objectID":"/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/:1:2","tags":null,"title":"栈和队列","uri":"/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/"},{"categories":null,"content":"队列 先进先出队列或简称队列,是一种基于先进先出(FIFO)策略的集合类型。 先进先出(FIFO)队列 public class Queue\u003cItem\u003e implements Iterable\u003cItem\u003e Queue() //创建空队列 void enqueue() //添加一个元素 Item dequeue() //删除最早添加的元素 boolean isEmpty() //判断队列是否为空 int size() //队列中元素的数量` 先进先出队列(链表实现) public class Queue\u003cItem\u003e implements Iterable\u003cItem\u003e{ private Node first;//指向最早添加的结点链接 private Node last;//指向最近添加的结点链接 private int N;//队列中的元素数量 private class Node{ Item item; Node next; } public boolean isEmpty(){return N == 0;}//或者first == null public int size(){return N;} public void enqueue(){ //向表尾添加元素 Node oldlast = last; last = new Node(); last.item = item; last.next = null; if(isEmpty()){//判断队列是否之前是空队列,如果是那么此时队列里只有刚刚添加的一个元素 first = last; } else oldlast.next = last; N++; } public Item dequeue(){ //从表头删除元素 Item item = first.item; first = first.next; if(isEmpty()){ last = null; } N--; return item; } } ","date":"2020-02-16","objectID":"/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/:1:3","tags":null,"title":"栈和队列","uri":"/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/"},{"categories":null,"content":"VPN是什么 VPN,全称:Virtual Private Network,中文翻译:虛拟私人网络。作用:提供安全可靠的通信渠道,一般而言企业使用较多。延伸作用:科学上网。说明:VPN的出现并不是为了“科学上网”,二是在公网上建立加密的通信渠道。例如,公司员工出差或者在寝室,想要登录公司内网邮箱怎么办?这时VPN就派上用场了,可以通过第三方连接工具进行远程连接,比如思科就有相应的工具。 ","date":"2020-02-10","objectID":"/ss-ssr/:1:0","tags":null,"title":"ss/ssr","uri":"/ss-ssr/"},{"categories":null,"content":"什么是SS SS全称shadowsocks,一开始为个人独立开发并用作“科学上网”,后被大家所熟知和广泛使用。再后来,据说作者被请去“喝茶”,停止了该项目。 ","date":"2020-02-10","objectID":"/ss-ssr/:2:0","tags":null,"title":"ss/ssr","uri":"/ss-ssr/"},{"categories":null,"content":"什么是SSR SSR全称shadowsocks-R。SSR作者声称SS不够隐匿,容易被防火墙检测到,SSR在改进了混淆和协议,更难被防火墙检测到。简单地说,SSR是SS的改进版。 ","date":"2020-02-10","objectID":"/ss-ssr/:3:0","tags":null,"title":"ss/ssr","uri":"/ss-ssr/"},{"categories":null,"content":"Socks5代理 采用socks协议的代理服务器就是SOCKS服务器。 它是一种通用的代理服务器。Socks是个电路级的底层网关,是DavidKoblas在1990年开发的,此后就一直作为Internet RFC标准的开放标准。Socks 不要求应用程序遵循特定的操作系统平台,Socks 代理与应用层代理、 HTTP 层代理不同,Socks 代理只是简单地传递数据包,而不必关心是何种应用协议(比如FTP、HTTP和NNTP请求)。所以,比其他应用层代理要快得多。 ","date":"2020-02-10","objectID":"/ss-ssr/:4:0","tags":null,"title":"ss/ssr","uri":"/ss-ssr/"},{"categories":null,"content":"VPN与SSR、SS的区别 SS和SSR两者原理相同,都是基于socks5代理。客户端与服务端没有建立专有通道,客户端和实际要访问的服务端之间通过代理服务器进行通信,客户端发送请求和接受服务端返回的数据都要通过代理服务器。SSR目的是为了能让流量通过防火墙。客户端请求服务端数据流程(SSR): (1)浏览器发送请求(基于socks5协议), 通过ssr客户端将sock5协议通过协议插件和混淆插件进行转换加密,使得来自客户端的流量和基于HTTP协议的流量无差别; (2)SSR服务端(代理服务器)收到请求后,通过混淆插件、协议插件将数据解密并还原协议,最后转发到目标服务器。 服务端返回数据到客户端同理。 ","date":"2020-02-10","objectID":"/ss-ssr/:5:0","tags":null,"title":"ss/ssr","uri":"/ss-ssr/"},{"categories":null,"content":"VPN与SSR、SS的区别 目的(作用)不同,VPN是为了保证通信的安全性、私密性,不是专门为“科学上网”制定的技术;而SS/SSR则是为了转发客户端流量,绕过防火墙的检测,从而达到“科学上网”的真实意图,但是没有保证数据传输的安全性。 ","date":"2020-02-10","objectID":"/ss-ssr/:6:0","tags":null,"title":"ss/ssr","uri":"/ss-ssr/"},{"categories":null,"content":"什么是 JDBC JDBC 规范定义接口,具体的实现由各大数据库厂商来实现。 JDBC 是 Java 访问数据库的标准规范,真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。 每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用 JDBC 接口中的方法即可, 数据库驱动由数据库厂商提供。 使用 JDBC 的好处: 程序员如果要开发访问数据库的程序, 只需要会调用 JDBC 接口中的方法即可, 不用关注类是如何实现的。 使用同一套 Java 代码,进行少量的修改就可以访问其他 JDBC 支持的数据库 会使用到的包 说明 java.sql 所有与 JDBC 访问数据库相关的接口和类 javax.sql 数据库扩展包,提供数据库额外的功能。如:连接池 数据库的驱动 由各大数据库厂商提供,需要额外去下载,是对 JDBC 接口实现的类 接口或类 作用 DriverManager 类 1) 管理和注册数据库驱动 2) 得到数据库连接对象 Connection 接口 一个连接对象,可用于创建 Statement 和 PreparedStatement 对象 Statement 接口 一个 SQL 语句对象,用于将 SQL 语句发送给数据库服务器。 PreparedStatemen 接口 一个 SQL 语句对象,是 Statement 的子接口 ResultSet 接口 用于封装数据库查询的结果集,返回给客户端 Java 程序 ","date":"2020-02-09","objectID":"/jdbc/:1:0","tags":null,"title":"JDBC","uri":"/jdbc/"},{"categories":null,"content":"IDEA中JDBC具体实现 import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class JdbCDemo { public static void main(String[] args) { Statement statement = null; Connection conn = null; try { //1.注册驱动 Class.forName(\"com.mysql.jdbc.Driver\"); //2.定义sql语句 String sql = \"insert into account values(null,'王五',4000)\"; //3.获取Connection对象 conn = DriverManager.getConnection(\"jdbc:mysql://localhost:3306/db1\", \"root\", \"root\"); //Connection connection = DriverManager.getConnection(\"jdbc:mysql://127.0.0.1:3306/db1\", \"root\", \"root\"); //4.获取执行sql的Statement对象 statement = conn.createStatement(); //5.执行sql int count = statement.executeUpdate(sql); //count 表示影响的行数 if (count \u003e 0) { System.out.println(\"successful!\"); } else System.out.println(\"failed!\"); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } finally { //判断空指针异常 if (statement != null) { try { //关闭资源 statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } } ","date":"2020-02-09","objectID":"/jdbc/:2:0","tags":null,"title":"JDBC","uri":"/jdbc/"},{"categories":null,"content":"使用数据库连接池c3p0和Druid 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。 import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; //JDBCutils是一个工具类,里面包含了静态代码块(用来加载配置文件)/getConnection()和close方法,以下代码可能有未调用的演示类 public class JDBCutils { private static DataSource ds; //加载配置文件 static { try { Properties pro = new Properties(); ClassLoader classLoader = JdbcUtils.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream(\"druidconfig.properties\"); pro.load(is); ds = DruidDataSourceFactory.createDataSource(pro); } catch (Exception e) { e.printStackTrace(); } } //获取数据库连接 public static Connection getConnection() throws SQLException { return ds.getConnection(); } //释放资源 public static void close(Statement statement, Connection connection) { if(statement != null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if(connection != null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static void close(ResultSet resultSet, Statement statement, Connection connection) { if(resultSet != null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } JDBCutils.close(null,statement,connection); /*if(statement != null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if(connection != null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } }*/ } public static DataSource getDataSource(){ return ds; } } //c3p0: public class c3p0Demo1 {//演示类 public static void main(String[] args) throws SQLException { Connection connection = null; //1.创建数据库连接池对象 DataSource ds = new ComboPooledDataSource(); //2.获取连接对象 connection = ds.getConnection(); System.out.println(connection); } } public class c3p0Demo2 { public static void main(String[] args) throws SQLException { //指定配置文件中的配置对象为otherc3p0,建立连接池对象 DataSource ds = new ComboPooledDataSource(\"otherc3p0\"); for (int i = 0; i \u003c 12; i++) { //otherc3p0的配置文件中有\u003cproperty name = \"maxPoolSize\"\u003e8\u003c/property\u003e,最大数据接连接池对象为8个,此处循环12次,观察能不能输出连接对象 Connection connection = ds.getConnection(); System.out.println(i+1+\":\"+connection); if(i == 5){//判断归还连接对象给数据库连接池 connection.close(); } } } } //Druid: public class DruidDemo1 { public static void main(String[] args) throws Exception { ClassLoader classLoader = DruidDemo1.class.getClassLoader(); URL resource = classLoader.getResource(\"druidconfig.properties\"); String path = resource.getPath(); /* InputStream inputStream = classLoader.getResourceAsStream(\"druidconfig.properties\");*/ Properties pro = new Properties(); pro.load(new FileReader(path)); DataSource ds = DruidDataSourceFactory.createDataSource(pro); Connection conn = ds.getConnection(); System.out.println(conn); } } public class DruidDemo2 { public static void main(String[] args) { Connection connection = null; PreparedStatement preparedStatement = null; try { //调用JDBCutils中的getConnection的方法 connection = JDBCutils.getConnection(); String sql = \"insert into account values(null,?,?)\"; //这里使用PreperStatement,是一个预编译的执行sql类,可以用?代替预设置的值,可以避免sql注入问题,一定要在执行sql语句之前为PrepareStatement类的对象设置?的值 preparedStatement = connection.prepareStatement(sql); //参数1表示?的位置,参数2是给它的值 preparedStatement.setString(1, \"zhaoliu\"); preparedStatement.setDouble(2,2000); preparedStatement.executeUpdate(); } catch (Exception e) { e.printStackTrace(); }finally { //调用JDBCutils工具类中的close方法,吧资源归还给数据库连接池 JDBCutils.close(preparedStatement,connection); } } } ","date":"2020-02-09","objectID":"/jdbc/:3:0","tags":null,"title":"JDBC","uri":"/jdbc/"},{"categories":null,"content":"Tomcat 的配置 部署项目方式的 直接将项目放到 webapps 目录下即可 按照文件路径来访问 简化部署:将项目打成一个.war 的包,再将 war 包放置到 webapps 目录下,tomcat 会自动生成一个相应的文件夹,删除时直接删除 war 包也会自动删除相应的文件夹 配置 conf/server.xml 文件 在标签体中配置 在 conf\\Catalina\\localhost 创建任意名称的 xml 文件,在 xml 文件中编写内容 此时的虚拟目录就是 xml 的文件名 静态项目和动态项目 目录结构 java 动态项目 项目根目录 WEB-INF 目录 web.xml:web 项目的核心配置文件 classes:放置字节码文件的目录 lib:放置依赖的 jar 包 将 Tomcat 集成到 idea 中 run-\u003econfigurations-\u003eTemplates 里面选择 tomcatserver-\u003elocal 或者 remote 另外在 Deployment 里面设置 Applicant context 为/,这样就可以不加路径直接访问项目 设置更新自动重新部署项目(热部署) run-\u003eedit configurations-\u003etomcats-\u003eServer On Update action:选择Update Resource On frame deactivation:选择Update Resource ","date":"2019-12-27","objectID":"/tomcat%E9%85%8D%E7%BD%AE/:1:0","tags":null,"title":"Tomcat的配置","uri":"/tomcat%E9%85%8D%E7%BD%AE/"},{"categories":null,"content":"IDEA 与 tomcat 的相关配置 idea 会为每一个 tomcat 部署的项目单独建立一份配置文件 查看控制台log:Using CATALINA_BASE: \"C:\\Users\\cyunt\\.IntelliJIdea2019.1\\system\\tomcat\\_tomcatDemo\" 工作空间项目和 tomcat 部署的 web 项目 tomcat 真正访问的是tomcat 部署的 web 项目,tmocat 部署的 web 项目对应着工作空间项目的 web 目录下的所有资源 WEB-INF 目录下的资源不能被浏览器直接访问 ","date":"2019-12-27","objectID":"/tomcat%E9%85%8D%E7%BD%AE/:1:1","tags":null,"title":"Tomcat的配置","uri":"/tomcat%E9%85%8D%E7%BD%AE/"}]