-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
84 lines (84 loc) · 48.5 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[SpringBoot跨域]]></title>
<url>%2Fguojing.github.io%2F2020%2F02%2F07%2FSpringBoot%E8%B7%A8%E5%9F%9F%2F</url>
<content type="text"><![CDATA[为什么会出现跨域问题出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port) 什么是跨域当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域 当前页面url 被请求页面url 是否跨域 原因 http://www.test.com/ http://www.test.com/index.html 否 同源(协议、域名、端口号相同) http://www.test.com/ https://www.test.com/index.html 跨域 协议不同(http/https) http://www.test.com/ http://www.baidu.com/ 跨域 主域名不同(test/baidu) http://www.test.com/ http://blog.test.com/ 跨域 子域名不同(www/blog) http://www.test.com:8080/ http://www.test.com:7001/ 跨域 端口号不同(8080/7001) 非同源限制 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB 无法接触非同源网页的 DOM 无法向非同源地址发送 AJAX 请求 SprintBoot2.x解决方法12345678910111213141516@Configurationpublic class WebConfig implements WebMvcConfigurer{ @Override public void addCorsMappings(CorsRegistry registry) { //配置允许跨域访问路径 registry.addMapping("/**") //配置允许跨域域名 .allowedOrigins("*") //配置允许跨域的方法 .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") //配置预检请求的响应时间 .maxAge(3600) //配置允许发送跨域请求 .allowCredentials(true); }}]]></content>
</entry>
<entry>
<title><![CDATA[深入理解原码,补码,反码]]></title>
<url>%2Fguojing.github.io%2F2020%2F01%2F10%2F%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E5%8E%9F%E7%A0%81-%E8%A1%A5%E7%A0%81-%E5%8F%8D%E7%A0%81%2F</url>
<content type="text"><![CDATA[原码 原码:原码(true form)是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小。 以下带符号的四位二进制为例: 1001:最高位为1,表示负数,其余位001,表示1,完整的十进制为-10110:最高位为0,表示整数,其余位110,表示6,完整的十进制为6 有了以上的表示,那么我们就可以进行运算了 1230001+0010=0011 (1+2=3)完全正确1000+0000=1011 ((-0)+0=0)问题不大0001+1010=1011 (1+(-2)=-3)见鬼,问题很大好不好😱 到目前为止,针对正数的原码计算没有问题,负数的原码计算则会计算错误 补码反码]]></content>
</entry>
<entry>
<title><![CDATA[SpringBoot整合log4j2]]></title>
<url>%2Fguojing.github.io%2F2019%2F12%2F26%2FSpringBoot%E6%95%B4%E5%90%88log4j2%2F</url>
<content type="text"><![CDATA[配置文件模板1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798<?xml version="1.0" encoding="UTF-8"?><!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--><!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--><configuration monitorInterval="5"> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--变量配置--> <Properties> <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符--> <!-- %logger{36} 表示 Logger 名字最长36个字符 --> <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" /> <!-- 定义日志存储的路径,不要配置相对路径 --> <property name="FILE_PATH" value="更换为你的日志路径" /> <property name="FILE_NAME" value="更换为你的项目名" /> </Properties> <appenders> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式--> <PatternLayout pattern="${LOG_PATTERN}"/> <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> </console> <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用--> <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false"> <PatternLayout pattern="${LOG_PATTERN}"/> </File> <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> </appenders> <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。--> <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <logger name="org.mybatis" level="info" additivity="false"> <AppenderRef ref="Console"/> </logger> <!--监控系统信息--> <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。--> <Logger name="org.springframework" level="info" additivity="false"> <AppenderRef ref="Console"/> </Logger> <root level="info"> <appender-ref ref="Console"/> <appender-ref ref="Filelog"/> <appender-ref ref="RollingFileInfo"/> <appender-ref ref="RollingFileWarn"/> <appender-ref ref="RollingFileError"/> </root> </loggers></configuration>]]></content>
</entry>
<entry>
<title><![CDATA[HashMap源码分析]]></title>
<url>%2Fguojing.github.io%2F2019%2F10%2F29%2FHashMap%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%2F</url>
<content type="text"><![CDATA[背景HashMap在jdk1.8中,HashMap是由数组,链表,树实现的 常用知识在开始分析源码之前,我们先来复习几个概念: 哈希算法将任意长度的二进制值串映射为固定长度的二进制值串,这个映射的规则就是哈希算法,而通过原始数据映射之后得到的二进制值串就是哈希值(散列值).一个优秀的哈希算法需要满足: 从哈希值不能反向推导出原始数据(所以哈希算法也叫单向哈希算法); 对输入数据非常敏感,哪怕原始数据只修改了一个 Bit,最后得到的哈希值也大不相同; 散列冲突的概率要很小,对于不同的原始数据,哈希值相同的概率非常小; 哈希算法的执行效率要尽量高效,针对较长的文本,也能快速地计算出哈希值。 装填因子=(哈希表中的记录数)/(哈希表的长度),装填因子是哈希表装满程度的标记因子。值越大,填入表中的数据元素越多,产生冲突的可能性越大。 红黑树在学习红黑树之前,先学习二叉查找树(BST),先来看一下二叉查找树的特点 左子树上所有的节点的值均小于等于它的根节点的值 右子树上所有的节点的值均大于等于它的根节点的值 左右子树也一定分别为二叉排序树 在某些情况下,比如根节点足够大,就会造成左子树或者右子树特别长,查找性能大打折扣,几乎等同于线性查找所以就需要特定的操作保持二叉树的平衡,红黑树就是一种特殊的平衡二叉树,有如下特点: 节点是红色或者黑色 根节点是黑色 每个红色节点的两个子节点都是黑色(从每个叶子节点到跟的路径上不能有两个连续的红色节点) 从任一节点到其每个叶子的节点的路径都包含相同数目的黑色节点 源码分析构造函数先来看一下构造函数 1234567891011121314151617181920212223//默认构造函数,负载因子为0.75,默认容量为16public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR;}public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR);}public HashMap(int initialCapacity, float loadFactor) { //初始容量不能为复数 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //初始容量不能大于2^30 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //负载因子不能为负 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; //计算下一次扩容的阈值 this.threshold = tableSizeFor(initialCapacity);} tableSizeFor方法tableSizeFor方法会计算出一个大于输入参数且最近的2的整数次幂的值 123456789static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;} 我们来详细分析一下tableSizeFor方法,对于任意整数,例如:123456789,其二进制为:0000 0111 0101 1011 1100 1101 0001 0101 执行操作 结果 cap-1 0000 0111 0101 1011 1100 1101 0001 0100 n >>> 1 0000 0011 1010 1101 1110 0110 1000 1010 n |= n >>> 1 0000 0111 1010 1111 1110 1111 1000 1110 n >>> 2 0000 0001 1110 1011 1111 1011 1110 0011 n |= n >>> 2 0000 0111 1110 1111 1111 1111 1110 1111 n >>> 4 0000 0000 0111 1110 1111 1111 1111 1110 n |= n >>> 4 0000 0111 1111 1111 1111 1111 1111 1111 n >>> 8 0000 0000 0000 0111 1111 1111 1111 1111 n |= n >>> 8 0000 0111 1111 1111 1111 1111 1111 1111 n >>> 16 0000 0000 0000 0000 0000 0000 0000 0111 n |= n >>> 16 0000 0111 1111 1111 1111 1111 1111 1111 n + 1 0000 1000 0000 0000 0000 0000 0000 0000 计算结果为:134217728=2^27,在JVM中,整形占4个字节,所以通过位移,或操作,从最高位开始,按照从低到高的顺序,逐次置为1,非常精彩的操作,最有+1时,最高位进一,其余变为0 hash计算put方法扩容get方法HashMap默认以数组的形式存储,数组的每一个元素称为桶,每个桶中的元素默认以链表的形式存储,但是当桶中的元素超过一定数量时会转换为红黑树,当桶中的元素小于一定数量时,会重新转换为链表先看一下HashMap中的常量含义 123456789101112//默认容量为16static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//最大容量为2^30static final int MAXIMUM_CAPACITY = 1 << 30;//默认装在因子为0.75static final float DEFAULT_LOAD_FACTOR = 0.75f;//链表转为红黑树的阈值static final int TREEIFY_THRESHOLD = 8;//红黑树转为链表的阈值static final int UNTREEIFY_THRESHOLD = 6;//数组转为红黑树的阈值static final int MIN_TREEIFY_CAPACITY = 64; 内部类定义: 12345678910111213static class Node<K,V> implements Map.Entry<K,V> { final int hash; //所属桶的hash值 final K key; //唯一key V value; Node<K,V> next; //指向桶中的下一个元素 Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; }} Node实现了Map.Entry接口,本质上是一个K-V映射,前面说过,哈希算法把一连串的二进制转换为固定长度的二进制,只要哈希表中的记录数大于哈希表中的长度,那么必然出现两个不同的记录会有相同的哈希值(哈希冲突),HashMap的处理策略是链式存储相同的哈希值,在JDK1.8中又新增了红黑树: 12345678910111213141516public HashMap(int initialCapacity, float loadFactor) { //数组初始容量不能小于0 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //数组最大容量不能大于2^30 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //装载因子不能小于0 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; //当hashmap的存储容量等于 this.threshold = tableSizeFor(initialCapacity);}]]></content>
</entry>
<entry>
<title><![CDATA[三种代理方式]]></title>
<url>%2Fguojing.github.io%2F2019%2F10%2F12%2F%E4%B8%89%E7%A7%8D%E4%BB%A3%E7%90%86%E6%96%B9%E5%BC%8F%2F</url>
<content type="text"><![CDATA[代理模式代理模式(Proxy)是一种设计模式,提供了访问对象的另一种方式,即通过代理对象访问目标对象,这样做的好处是可以在目标对象的基础上,进行额外的功能操作,扩展目标对象的功能 静态代理静态代理要求代理对象与目标对象一起实现相同的接口或者继承相同的父类,使用时通过代理对象调用目标对象的相同方法达到代理的目的,举个例子:首先定义一个接口: 123public interface UserDao{ boolean addUser();} 定义目标对象,实现UserDao接口: 1234567public class UserDaoImpl implements UserDao{ @Override public boolean addUser(){ System.out.println("add user"); return true; }} 定义代理对象,与目标对象实现相同的接口: 1234567891011121314public class UserDaoProxy Implements UserDao{ /** * 目标对象 */ private UserDao target; public UserDaoProxy(UserDao target){ this.target = target; } @Override public boolean addUser(){ System.out.println("static proxy"); return this.target.addUser(); }} 使用方法: 123456789public class StaticProxyTest{ public static void main(String[] args){ //创建目标对象 UserDao target = new UserDaoImpl(); //创建代理对象 UserDao userDao = new UserDaoProxy(target); userDao.addUser(); }} 运行结果: 优点是可以在不修改目标对象的情况下对目标功能扩展 缺点是代理对象与目标对象必须徐实现一样的接口,会额外增加很多代理类,在修改接口时,需要同时维护目标对象与代理对象动态代理动态代理使用JDK的API,动态的在内存中构建代理对象,需要目标对象实现接口使用方法:1234567891011public class DynamicProxyTest{ public static void main(String[] args) { UserDao target = new UserDaoImpl(); UserDao userDao = (UserDao) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),(proxy,method,argument)->{ System.out.println("dynamic proxy"); Object result = method.invoke(target,argument); return result; }); userDao.addUser(); }} 运行结果如下: 不需要创建代理类,但是目标类必须实现接口Cglib代理静态代理和动态代理都要求目标对象必须实现接口,但是有时候目标类只是单纯的类,没有实现任何接口,对于此种情况,我们可以使用Cglib代理,也叫字节增强码代理,子类代理。Cglib包是一个强大高性能的代码生成包,他可以在运行期扩展java类与实现java接口,Cglib底层使用ASM(一个短小而精悍的字节码操作框架)来操作字节码生成新的类,不鼓励直接使用ASM,因为它要求必须对JVM内部结构包括class文件的字节码非常熟悉.举个例子:1234567891011121314151617public class CglibProxyTest{ public static void main(String[] args){ UserDao target = new UserDaoImpl(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("Cglib proxy"); Object obj = method.invoke(target,objects); return obj; } }); UserDao userDao = (UserDao) enhancer.create(); userDao.addUser(); }} 执行结果如下: 优点时Cglib不要求目标对象必须实现接口 缺点是Cglib的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常,对于final类型的class和method不能代理]]></content>
</entry>
<entry>
<title><![CDATA[String,StringBuilder,StringBuffer的区别]]></title>
<url>%2Fguojing.github.io%2F2019%2F09%2F02%2FString-StringBuilder-StringBuffer%E7%9A%84%E5%8C%BA%E5%88%AB%2F</url>
<content type="text"><![CDATA[区别与联系: String底层采用不可变数组存储StringBuilder底层采用可变数组存储,不支持并发StringBuffer底层采用可变数组,支持并发 继承关系图 String先来看一下String部分源码: 1private final char value[] 可以看到String底层采用不可变char数组存储 StringBuilderStringBuilder部分源码: 123456char[] value@Overridepublic StringBuilder append(String str) { super.append(str); return this;} 底层采用char数组,支持扩容,不支持多线程 StringBufferStringBuffer部分源码: 1234567char[] value@Overridepublic synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this;} 底层同样采用char数组,支持扩容,所有的添加,删除,修改操作都增加了synchronized关键字,支持多线程]]></content>
</entry>
<entry>
<title><![CDATA[排序算法总结]]></title>
<url>%2Fguojing.github.io%2F2019%2F07%2F23%2F%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93%2F</url>
<content type="text"><![CDATA[特点 排序算法 平均时间复杂度 最好情况时间复杂度 最坏情况时间复杂度 空间复杂度 稳定性 冒泡排序 o(n^2) o(n) o(n^2) o(1) 稳定 选择排序 o(n^2) o(n^2) o(n^2) o(1) 稳定 冒泡排序冒泡排序是一种简单的排序算法,重复的遍历所有元素,一次比较2个元素大小,如果顺序错误交换两个元素,直到没有顺序错误为止 算法描述 比较相邻元素,如果第一个比第二个大,则交换他们 从第0个元素开始,对每一对元素执行同样操作,一次循环可以把最大的元素放在末尾 重复上述所有操作直到所有的元素都有序代码实现12345678910111213141516public void bubbleSort(int[] array){ if(array == null) return; //循环次数,直到所有的元素都被比较过 for(int i=0; i<array.length;i++){ //一次循环之后,最大的数据被交换到数组末尾 for(int j=0; j< array.length - i - 1;j++){ //如果第一个元素比第二个元素大,则交换他们 if(array[j] > array[j+1]){ int temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; } } }} 优化一 假设现在排序array[]={1,2,3,4,5,6,7,8,10,9},经过一次排序之后,按照上述算法,第一趟排序交换10和9之后,序列已经有序,接下来的8趟比较什么都没做,比较多余。所以在交换的时候增加一个标记,如果本趟排序没有交换,说明数组基本有序,不用继续排序; 代码如下: 1234567891011121314151617181920212223public void bubbleSort1(int[] array){ if(array == null) return; //交换标识 boolean exchange=false; //循环次数,直到所有的元素都被比较过 for(int i=0; i<array.length;i++){ //一次循环之后,最大的数据被交换到数组末尾 for(int j=0; j< array.length - 1 - i;j++){ //如果第一个元素比第二个元素大,则交换他们 if(array[j] > array[j+1]){ int temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; exchange = true; } } //本趟排序没有发生交换,说明数组有序,不需要继续比较 if(!exchange){ break; } }} 优化二 优化一仅仅适用连片有序而整体无序的情况数据(例如:1,2,3,4,7,6,5)。但是对于前面大部分有序而后半部分无序的数据(例如:1,2,7,4,3,6,9,8,10)排序效率也不可观,对于这种情况,我们依然可以优化,我们可以记录最后一次交换的位置,后边没有交换,那必然是有序的,然后下一次排序从第一个记录比较到上次的记录位置即可。 代码如下: 12345678910111213141516171819202122232425262728public void bubbleSort2(int[] array){ if(array == null) return; //交换标识 boolean exchange=false; //记录上一次交换的结束位置 int lastPos=0; int k = array.length-1; //循环次数,直到所有的元素都被比较过 for(int i=0; i<array.length;i++){ //一次循环之后,最大的数据被交换到数组末尾 for(int j=0; j< k;j++){ //如果第一个元素比第二个元素大,则交换他们 if(array[j] > array[j+1]){ int temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; lastPos=j; exchange = true; } } //本趟排序没有发生交换,说明数组有序,不需要继续比较 if(!exchange){ break; } k=lastPos; }} 优化三 优化二已经大大提高效率,还有一种提交效率的方式,鸡尾酒排序(Cocktail Sort)(又名:双向冒泡排序 (Bidirectional Bubble Sort)、波浪排序 (Ripple Sort)、摇曳排序 (Shuffle Sort)、飞梭排序 (Shuttle Sort) 和欢乐时光排序 (Happy Hour Sort)),该排序以双向进行排序,排序时进行一次正向排序,再进行一次反向排序。 代码如下: 1234567891011121314151617181920212223242526272829303132333435363738394041public void bubbleSort3(int[] array){ if (array==null) return; //交换标识 boolean exchange = false; //记录上一次交换的结束位置 int lastPos = 0; //记录上一次交换的起始位置 int prevPos = 0; int k = array.length-1; //循环次数,直到所有的元素都被比较过 for (int i=0;i<array.length;i++){ //正向排序 for (int j=prevPos;j<k;j++) { if (array[j] > array[j + 1]) { int temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; exchange = true; lastPos = j; } } //本趟排序没有发生交换,说明数组有序,不需要继续比较 if (!exchange) break; k=lastPos; //反向排序 for (int j=k;j>1;j--) { if (array[j-1] > array[j]) { int temp = array[j - 1]; array[j - 1] = array[j]; array[j] = temp; exchange = true; prevPos = j; } } //本趟排序没有发生交换,说明数组有序,不需要继续比较 if (!exchange) break; }} 选择排序选择排序是最稳定的排序算法之一,因为无论什么样的数据,复杂度都是o(n^2),在使用的时候应该是数据规模越小越好,]]></content>
</entry>
<entry>
<title><![CDATA[AOP总结]]></title>
<url>%2Fguojing.github.io%2F2019%2F07%2F05%2FAOP%E6%80%BB%E7%BB%93%2F</url>
<content type="text"><![CDATA[Spring AOP基本术语 Aspect(切面): PonitCut(切点): Joint Point(连接点): Advice(增强) Target(目标对象) Weaving(织入)]]></content>
</entry>
<entry>
<title><![CDATA[字符编码引发的惨案]]></title>
<url>%2Fguojing.github.io%2F2019%2F07%2F02%2F%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81%E5%BC%95%E5%8F%91%E7%9A%84%E6%83%A8%E6%A1%88%2F</url>
<content type="text"><![CDATA[在收到byte数组时,需要转换为String字符串,我们都知道,String类提供了构造函数可以直接转换,但是调试模式下转换的结果与发布出来的jar包转换的结果不同,排查问题浪费了不少时间,我们先来看一下构造函数够干了些什么,以下是String的部分代码: 12345678910111213141516171819202122public String(byte bytes[]) { this(bytes, 0, bytes.length);}public String(byte bytes[], int offset, int length) { checkBounds(bytes, offset, length); this.value = StringCoding.decode(bytes, offset, length);}static char[] decode(byte[] ba, int off, int len) { String csn = Charset.defaultCharset().name(); try { return decode(csn, ba, off, len); } catch (UnsupportedEncodingException x) { warnUnsupportedCharset(csn); } try { return decode("ISO-8859-1", ba, off, len); } catch (UnsupportedEncodingException x) { MessageUtils.err("ISO-8859-1 charset not available: "+ x.toString()); System.exit(1); return null; }} 我们可以看到关键的方法在于Charset.defaultCharset().name()方法,通过该方法找到编码格式,来看一下该方法的实现: 1234567891011121314public static Charset defaultCharset() { if (defaultCharset == null) { synchronized (Charset.class) { String csn = AccessController.doPrivileged( new GetPropertyAction("file.encoding")); Charset cs = lookup(csn); if (cs != null) defaultCharset = cs; else defaultCharset = forName("UTF-8"); } } return defaultCharset;} 可以看到默认的编码格式是通过文件的编码格式确定的,由于我的工作空间配置的是UTF-8,所以在调试模式下没有问题,但是到了生产环境出现问题。所以今后在做关于字符的操作时一定要指定编码格式]]></content>
</entry>
<entry>
<title><![CDATA[ArrayList源码详细分析]]></title>
<url>%2Fguojing.github.io%2F2019%2F06%2F26%2FArrayList%E6%BA%90%E7%A0%81%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90%2F</url>
<content type="text"><![CDATA[先看一下接口实现: 123//接口实现表明ArrayList支持克隆,随机访问,序列化public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 再看构造函数: 1234567891011121314151617181920212223242526272829303132333435363738private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};private static final Object[] EMPTY_ELEMENTDATA = {};//默认初始容量为10private static final int DEFAULT_CAPACITY = 10;//记录集合的元素个数,调用size()方法返回该值private int size;//底层采用Object数组存储transient Object[] elementData;//默认构造函数public ArrayList() { //创建空数组 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}//构造包含collection元素的数组,这些元素使用迭代器的方式返回public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { //如果collection中的元素个数为0,创建空数组 this.elementData = EMPTY_ELEMENTDATA; }}//用户指定初始容量public ArrayList(int initialCapacity) { if (initialCapacity > 0) { //创建initialCapacity大小的数组 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //初始容量为0,创建空数组 this.elementData = EMPTY_ELEMENTDATA; } else { //如果初始容量小于0,则抛出异常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } 继续看add()方法 1234567public boolean add(E e) { //在添加元素之前先调用ensureCapacityInternal()方法 ensureCapacityInternal(size + 1); //可以看到相当于对数组赋值 elementData[size++] = e; return true;} 继续分析ensureCapacityInternal()方法 123456789101112131415161718private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}//用于计算最小的数组大小private static int calculateCapacity(Object[] elementData, int minCapacity) { //这里看到当使用默认构造函数时,初始化容量为10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity;}private void ensureExplicitCapacity(int minCapacity) { //用于标记修改次数,通常在多线程抛出ConcurrentModificationException异常时就是通过modeCount来判断 modCount++; if (minCapacity - elementData.length > 0) //如果最小容量比当前数组的容量大,则调用grow()方法扩容 grow(minCapacity);} 继续分析grow()方法: 12345678910111213private void grow(int minCapacity) { //把当前的数组容量赋值给oldCapacity int oldCapacity = elementData.length; //oldCapacity左移1位,相当于做oldCapacity/2,新的容量为1.5*oldCapacity int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) //当没有初始容量或者初始容量为1 newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //把旧的数组内容拷贝到新的数组中 elementData = Arrays.copyOf(elementData, newCapacity);} 可以看到,在grow()方法中完成扩容,这是add方法的过程,再来看在指定位置增加元素过程: 1234567891011121314public void add(int index, E element) { //检查边界,判断index是否在0~size之间 rangeCheckForAdd(index); ensureCapacityInternal(size + 1); //在拷贝的时候只把index~size之间的内容拷贝到新的数组中,前面的内容不变,相当于index之后的元素全部后移一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++;}private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));} 其他的add操作类似,不再详述。再看一下remove方法: 12345678910111213141516171819public E remove(int index) { //边界检查,由于对象都是连续存放,只要判断index是否大于list的size即可 rangeCheck(index); modCount++; E oldValue = elementData(index); //ArrayList删除元素需要移动数组,确保 int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); //GC elementData[--size] = null; return oldValue;}private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}]]></content>
</entry>
<entry>
<title><![CDATA[SpringBoot获取前端请求参数]]></title>
<url>%2Fguojing.github.io%2F2019%2F06%2F13%2FSpringBoot%E8%8E%B7%E5%8F%96%E5%89%8D%E7%AB%AF%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0%2F</url>
<content type="text"><![CDATA[SpringBoot获取前端请求参数SpringBoot提供了三个注解用于获取前端请求的参数,分别是@PathVariable,@RequestParam,@RequestBody,下面介绍三个参数的用法以及区别 注解 支持类型 支持的请求类型 支持的Content-Type 请求示例 @PathVariable URL Get all /orders/{id} @RequestParam URL Get all /orders?id=1 @RequestParam Body Post/Put/Delete/Patch form-data,x-www.form-urlencoded /orders?id=1 @RequestBody Body Post/Put/Delete/Patch json {“id”:1,”name”:”aa”} @PathVariable示例例如有一个接口获得User信息:前端请求路由:localhost:8080/getUserInfo/1后端@Controller层代码: 12345@GetMapping("/getUserInfo/{id}")public User getUserInfo(@PathVariable int id){ int userId = id; return new User(userId);} User代码: 12345678910111213141516public class User{ private String name; private int age; public String getName(){ return name; } public void setName(String name){ this.name = name; } public int getAge(){ return age; } public void setAge(int age){ this.age = age; }} @RequestParam示例依然是获得User信息前端请求路由:localhost:8080/getUserInfo/?id=1后端@Controller层代码: 12345@GetMapping("/getUserInfo")public User getUserInfo(@RequestParam int id){ int userId = id; return new User(userId);} @RequestBody示例前端请求路由:localhost:8080/getUserInfo请求参数:body={"id":0}后端@Controller层代码 12345@GetMapping("/getUserInfo")public User getUserInfo(@RequestBody Body body{ int userId = body.getId(); return new User(userId);} Json对象代码: 123456789public class Body{ private int id; public int getId(){ return id; } public int setId(int id){ this.id = id; } } 前端在传递Json对象时,Java方面必须有对应的类去接收该Json对象,其中类属性可以比Json对象中的多,但是不可以少]]></content>
<tags>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Bean的生命周期]]></title>
<url>%2Fguojing.github.io%2F2019%2F06%2F08%2FBean%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%2F</url>
<content type="text"><![CDATA[Bean的生命周期Spring Ioc对Bean的生命周期管理如下 通过构造函数或者工厂方法创建Bean 为Bean注入属性与其他Bean的引用 执行初始化方法 Bean可以使用 容器在关闭时执行销毁方法 在xml中配置init-method属性执行初始化方法,配置destroy-method执行销毁方法,例如: 123456789101112131415161718192021222324252627public class User{ private int name; private Date birthday; public User(){ System.out.println("执行无参构造函数"); } public void setName(String name){ System.out.println("设置name属性"); this.name = name; } public String getName(){ return name; } public void SetBirthday(Date birthday){ System.out.println("设置birthday属性"); this.birtyday = birthday; } public Date getBirtyday(){ return birthday; } public void init(){ System.out.println("执行初始化方法"); } public void destory(){ System.out.println("执行销毁方法"); }} bean.xml内容如下 12345<bean id="user" class="main.demo.User" init-method="init" destroy-method="destory"> <property name="name" value="demo"> <property name="birthday"> <bean class="java.util.Date"></bean></bean> 测试程序如下: 123456789public class Test{ public static void main(String[] args){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); User user = (User)context.getBean("user"); System.out.println(user.getName()); //调用close()时Spring容器才执行销毁方法 context.close(); }} 执行结果如下: Bean的后置处理器Bean的后置处理器允许调用初始化方法前后对Bean进行额外处理,Bean的后置处理器对Bean的所有实例逐一统一处理,而非某一个实例,典型应用就是AOP。在使用过程中需要实现BeanPostProcessor接口,该接口有两个方法: 方法 说明 postProcessBeforeInitialization 在调用初始化方法之前调用 postProcessAfterInitialization 在调用初始化方法之后调用 具体实例如下: 12345678910111213public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println(beanName + " 执行postProcessBeforeInitialization方法"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println(beanName + " 执行postProcessAfterInitialization方法"); return bean; }} bean.xml内容如下: 1234567<bean id="user" class="main.entity.User" init-method="init" destroy-method="destory"> <property name="name" value="demo"></property> <property name="birthday"> <bean class="java.util.Date"></bean> </property></bean><bean class="main.entity.MyBeanPostProcessor"></bean> 执行结果如下: 可以看到后置处理器对xml中的Date和user做了处理,并且是在初始化方法前后分别处理]]></content>
</entry>
<entry>
<title><![CDATA[Bean的种类以及作用域]]></title>
<url>%2Fguojing.github.io%2F2019%2F06%2F08%2FBean%E7%9A%84%E7%A7%8D%E7%B1%BB%E4%BB%A5%E5%8F%8A%E4%BD%9C%E7%94%A8%E5%9F%9F%2F</url>
<content type="text"><![CDATA[Bean 的种类普通BeanPerson类 1234567891011121314151617181920212223242526public class Person{ private String name; private int age; public Person(){ System.out.println("默认构造函数"); } public Person(String name){ this.name = name; } public Person(String name,int age){ this.name = name; this.age = age; } public vois setName(String name){ this.name=name; } public String getName( return name; ) public void setAge(int age){ this.age = age; } public int getAge(){ return age; }} bean.xml 1<bean id="person" class="com.demo.Person"></bean> 直接创建Bean的实例并返回 FactoryBean是一个特殊的Bean,具有工厂生产Bean的能力,只能生成特定的对象,必须实现FactoryBean接口,此接口提供的getObject()用于返回特定的bean。 123456789101112131415161718//必须实现FactoryBean接口public class MyFactoryBean implements FactoryBean { //返回bean的实例 @Override public Object getObject() throws Exception { return new Person; } /*返回getObject()的方法创建的bean的类型,如果不知道类型,则返回null,这样可以在不返回bean的情况下检查bean的类型,使用@autowire时会忽略返回null的bean*/ @Override public Class<?> getObjectType() { return Person.class; } /*如果为true,则表明getObject()方法返回的bean是单例并且放入Spring的单例缓冲池,否则是多例*/ @Override public boolean isSingleton() { return false; }} Bean的作用域 类型 说明 singleton 在Spring Ioc容器中仅存在一个实例,Bean以单例的形式存在,默认值 prototype 每次从容器中返回bean时,都会返回一个新的bean实例 request 每次HTTP请求都会创建一个新的bean,该作用域仅限于WebApplicationContext session 每次HTTP Session共享一个bean,不同session使用不同bean,仅限于WebApplicationContext globalSeesion 一般作用于Portlet应用环境,仅限于WebApplicationContext bean.xml 1234<!--每次调用person都会返回相同的实例--><bean id="person" class="com.demo.Person"></bean><!--每次调用person1都会返回新的实例--><bean id="person1" class="com.demo.Person" scopt="prototype"></bean>]]></content>
</entry>
<entry>
<title><![CDATA[Bean实例化方式]]></title>
<url>%2Fguojing.github.io%2F2019%2F06%2F07%2FBean%E5%AE%9E%E4%BE%8B%E5%8C%96%E6%96%B9%E5%BC%8F%2F</url>
<content type="text"><![CDATA[SpringBean实例化的方式主要介绍三种实例化Bean的方式 构造函数 静态工厂 实例工厂 构造函数最常用的实例化方式,Bean必须提供默认的构造函数 1234567891011121314151617181920212223242526public class Person{ private String name; private int age; public Person(){ System.out.println("默认构造函数"); } public Person(String name){ this.name = name; } public Person(String name,int age){ this.name = name; this.age = age; } public vois setName(String name){ this.name=name; } public String getName( return name; ) public void setAge(int age){ this.age = age; } public int getAge(){ return age; }} bean.xml内容 12345678<bean id="person" class="com.demo.Person"></bean><bean id="person1" class="com.demo.Person"> <constructor-arg name="name" value="张三"/></bean><bean id="person2" class="com.demo.Person"> <constructor-arg name="name" value="张三"/> <constructor-arg name="age" value="18"/></bean> 静态工厂当采用静态工厂的方式创建Bean时,除了要指定class属性之外,还需要指定factory-method属性,并且工厂方法必须是静态的 123456789public class StaticFactory{ //方法必须是静态的 public static Person createPerson(){ return new Person(); } public static Person createPerson(String name){ return new Person(name); }} bean.xml内容: 1234<bean id="person" class="com.demo.StaticFactory" factory-mathod="createPerson"></bean><bean id="person1" class="com.demo.StaticFactory" factory-mathod="createPerson"> <constructor-arg name="name" value="张三"/></bean> 实例工厂与静态工厂方式相比,采用实例工厂创建Bean时,class属性必须为空,factory-bean属性必须指定为当前类或者父类中包含工厂方法的名称,而该工厂Bean工厂方法必须通过factory-mathod属性指定。必须先有工厂实例,通过实例对象创建实例提供的所有方法都是非静态的 123456789//实例工厂,所有方法非静态public class Factory{ public Person createPerson(){ return new Person() } public Person createPerson(String name){ return new Person(name); }} bean.xml内容 12345<bean id="factory" class="com.demo.Factory"></bean><bean id="person" factory-bean="factory" factory-mathod="createPerson"></bean><bean id="person1" factory-bean="factory" factory-mathod="createPerson"> <constructor-arg name="name" value="张三"/></bean>]]></content>
</entry>
<entry>
<title><![CDATA[集合总结]]></title>
<url>%2Fguojing.github.io%2F2019%2F05%2F31%2F%E9%9B%86%E5%90%88%E6%80%BB%E7%BB%93%2F</url>
<content type="text"><![CDATA[背景 通常,我们的程序需要根据程序运行时才知道要创建多少个对象。但除非程序运行,否则我们是不知道程序创建多少个对象。这就要求我们的程序具备一种在任何时间,任何地点创建任意多个对象的。但是这些对象怎么存储呢?首先想到的就是数组,但是数组有一个缺点,即只能存放统一类型数据,而且长度是固定的,故此诞生了集合的概念 什么是集合 Java的集合类存放在java.util包下,是一个用来存放对象的容器注意:只能存放对象,如果存放基本数据类型元素,会自动转换为其包装类型集合存放的是多个对象的引用,对象本身还在堆内存中集合可以存放不同类型,不限数量的对象。 集合框架图 可以看到,除了Map,其他的都实现了Iterable接口,这是一个用于遍历集合中的元素的接口,主要的方法有iterator(),方法,用来返回一个迭代器,在JDK1.8又新增了两个默认实现方法,spliterator()和forEach(Consumer<? super T> action)方法,前者会用来返回一个可分割的迭代器,目的是为了并行遍历元素,后者按照Iterable对每个元素执行操作,直到处理完所有的元素或者抛出异常,除非实现类另有规定,否则按照迭代器顺序执行。 List,Map,Set是集合的三大主要接口: List 有序且允许重复 ArrayList的底层采用数组实现,查找效率高,增加或者删除效率低,非线程安全的 LinkedList的底层采用链表实现,每一个Node除了保存元素之外,还会保存前驱节点和后继节点,查找效率低,增加或者删除效率高,非线程安全的 Vector的底层采用数组实现,在一些方法上使用synchronized关键字加锁,查找效率高,增加或者删除效率低,线程安全 CopyOnWriteArrayList底层采用数组实现,在添加对象时会加锁,读取对象时不加锁 Map Map保存的是键值对,其中key不允许重复,允许为null,value可以重复 HashMap的底层数组实现,hash码相同的key采用链式存储,每一个Node都会保存后继节点 LinkedHashMap的底层采用链表实现,每一个Entry除了保存key和value之外,还保存前驱节点和后继节点 TreeMap底层采用树形结构实现,每一个Entry都会保存左子节点,右子节点,父节点 HashTable的实现基本与HashMap相同,key不允许为null,且部分方法使用synchronized修饰,保证数据的一致性 ConcurrentHashMap底层采用数组实现,key,value都不允许为null Set 无序不允许重复 HashSet底层采用HashMap存储,利用Map的key不允许重复的特性,允许为null LinkedHashSet TreeSet底层采用二叉树排序]]></content>
<tags>
<tag>Collections</tag>
</tags>
</entry>
</search>