-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
624 lines (624 loc) · 218 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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Java基础-反射]]></title>
<url>%2F2019%2F12%2F25%2FJava%E5%9F%BA%E7%A1%80-%E5%8F%8D%E5%B0%84%2F</url>
<content type="text"><![CDATA[对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。 除了int等基本类型外,其他类型基本都是class,包括interface 123public final class Class { private Class() {}} 这个Class实例是JVM内部创建的,如果我们查看JDK源码,可以发现Class类的构造方法是private,只有JVM能创建Class实例,我们自己的Java程序是无法创建Class实例的。 Class类当JVM加载String类时,读取String.class到内存,然后为String创建一个Class类型的实例与之关联。 1Class clz = new Class(String); 一个Class实例包含了该class的所有完整信息: Class Instance String name “java.lang.String” package “java.lang” super “java.lang.Object” interface CharSequence… field value[],hash… method indexof()… 由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。 这种通过Class实例获取class信息的方法称为反射(Reflection)。 获取Class实例的三种方式通过class的静态变量获取1Class clz = String.class; 通过实例的getClass()方法获取12String str = "bbdog";Class clz = str.getClass(); 通过Class.forName()方法获取1Class clz = Class.forName("java.lang.String"); 必须是类的完成类名。 因为每种class只会在JVM中存在唯一Class实例,所以以上三种方式获取的都是同一个Class实例。 123456789Class clz = String.class;String str = "bbdog";Class cla = str.getClass();Class cls = Class.forName("java.lang.String");System.out.println(clz == cla);//trueSystem.out.println(clz == cls);//true instanceof和==的区别还在于,instanceof体现了多态的思想,不仅可以匹配他的类型,还可以匹配它的子类型。 123String str = "bbdog";System.out.println(str instanceof String);//trueSystem.out.println(str instanceof Object);//true 通过Class创建对应实例如果获取到了一个Class实例,我们就可以通过该Class实例来创建对应类型的实例: 1234// 获取String的Class实例:Class cls = String.class;// 创建一个String实例:String s = (String) cls.newInstance(); 上述代码相当于new String()。通过Class.newInstance()可以创建类实例,它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。 动态加载JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。例如: 123456789101112// Main.javapublic class Main { public static void main(String[] args) { if (args.length > 0) { create(args[0]); } } static void create(String name) { Person p = new Person(name); }} 当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。 这就是JVM动态加载class的特性。 访问字段访问字段对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。 Field getField(name):根据字段名获取某个public的field(包括父类) Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类) Field[] getFields():获取所有public的field(包括父类) Field[] getDeclaredFields():获取当前类的所有field(不包括父类) 1234567891011121314public class Hello{ @NotNull public String name; private int age;}//定义字段时候用到的属性就是Field的属性。修饰符、类型、字段名、注解Field field = Hello.class.getDeclaredField("name");System.out.println(field.getModifiers());//1System.out.println(field.getType());//class java.lang.StringSystem.out.println(field.getName());//namefor (Annotation a : field.getAnnotations()){ System.out.println(a);}//@javax.validation.constraints.NotNull(message={javax.validation.constraints.NotNull.message}, groups=[], payload=[]) 获取字段的值123456Hello hello = new Hello("456");Class c = hello.getClass();Field f = c.getDeclaredField("name");// f.setAccessible(true);System.out.println(f.get(hello));//456 如果字段权限是private可以通过Field.setAccessible(true)实现对私有字段的取值。这么做感觉有点破坏了封装的思想。 反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。 此外,setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。 设置字段不仅仅可以获取到字段,还可以设置字段值。 123456Hello hello = new Hello("456");Class c = hello.getClass();Field f = c.getDeclaredField("str");f.setAccessible(true);f.set(hello,"789");System.out.println(f.get(hello));//789 方法获取方法Class类提供了以下几个方法来获取Method: Method getMethod(name, Class...):获取某个public的Method(包括父类) Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类) Method[] getMethods():获取所有public的Method(包括父类) Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类) 123456789101112131415161718192021222324252627282930313233343536public class Hello{ private String str; public Hello(String str){ this.str = str; } public Hello(){ } public int addNumber(int a, int b){ return a + b; } private String getStr(){ return str; } public static String printHello(){ return "Hello"; }}Hello hello = new Hello("456");Class c = hello.getClass();Method method = c.getMethod("addNumber", int.class, int.class);System.out.println(method);//public int com.bbdog.demo.Hello.addNumber(int,int)Method method1 = c.getDeclaredMethod("getStr");System.out.println(method1);//private java.lang.String com.bbdog.demo.Hello.getStr()System.out.println(method.getModifiers());//1System.out.println(method.getReturnType());//intSystem.out.println(method.getName());//addNumberfor (Parameter p:method.getParameters()){ System.out.println(p.getParameterizedType() + " " + p.getName());}//int a//int b 调用方法调用public方法1234Hello hello = new Hello();Class c = hello.getClass();Method method = c.getMethod("addNumber", int.class, int.class);System.out.println(method.invoke(hello,1,2));//3 调用静态方法1234Hello hello = new Hello();Class c = hello.getClass();Method method = c.getMethod("printHello");System.out.println(method.invoke(null));//Hello 区别于调用public方法,invoke时不需要传对象的实例。所以实例对象的位置传null,pringHello()方法没有传入参数,不需要填写其他参数。 调用非public方法12345Hello hello = new Hello("bbdog");Class c = hello.getClass();Method method = c.getDeclaredMethod("getStr");method.setAccessible(true);System.out.println(method.invoke(hello));//bbdog 与通过反射获取字段相同,使用Method.setAccessible(true)可以调用非Public方法。同时如果JVM运行期间存在SecurityManager,也还是会出错的。 多态setAccessible(true)的存在无视了封装的思想。那多态呢。 父类有getStr()方法,子类重写了getStr()方法,那么使用反射的方式调用getStr()实际上调用的是哪个呢? 123456789101112131415161718public class Main { public static void main(String[] args) throws Exception { Method method = Hello.class.getDeclaredMethod("getStr"); method.setAccessible(true); method.invoke(new Hello());//Hello:getStr }}class Lang { public getStr() { System.out.println("Hello:getStr"); }}class Hello extends Lang { public getStr() { System.out.println("Lang:getStr"); }} 可见,还是保留了封装思想的。 构造方法1Hello hello = Hello.class.newInstance(); 这是反射获取对象实例的方法,但这种方法具有局限性,就是只能调用对象的非public、无参构造函数。 但是反射还给我们提供了一个Constructor对象,可以通过Constructor获得对象实例; 写法和获取普通方法类似,但是因为构造方法名只能和类名相同,所以它不需要传入方法名。 1234567891011121314151617181920212223public class Hello{ private String str; public Hello(String str){ this.str = str; } public Hello(){ } public int addNumber(int a, int b){ return a + b; } private String getStr(){ return str; } }//-----------------------------------------------Constructor cons = Hello.class.getConstructor(String.class);Hello hello = (Hello) cons.newInstance("hello");hello.getStr();//hello 调用非public方法时,与method调用时相同,使用Constructor.setAccessible(true)即可。 继承获取父类123456Class hello = Hello.class;System.out.println(hello.getName());//com.bbdog.demo.HelloClass lang = hello.getSuperclass();System.out.println(lang.getName());//com.bbdog.demo.LangClass object = lang.getSuperclass();System.out.println(object.getName());//java.lang.Object 获取接口123456Class s = Integer.class;Class[] is = s.getInterfaces();for (Class i : is) { System.out.println(i);}//interface java.lang.Comparable 判断继承关系判断继承关系我们常用instanceof, 12345Object n = Integer.valueOf(123);boolean isDouble = n instanceof Double; // falseboolean isInteger = n instanceof Integer; // trueboolean isNumber = n instanceof Number; // trueboolean isSerializable = n instanceof java.io.Serializable; // true 如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom():12345678// Integer i = ?Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer// Number n = ?Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number// Object o = ?Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object// Integer i = ?Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer 动态代理动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。 我们先定义了一个接口,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。 1234567891011121314151617181920212223public class Main { public static void main(String[] args) { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method); if (method.getName().equals("morning")) { System.out.println("Good morning, " + args[0]); } return null; } }; Hello hello = (Hello) Proxy.newProxyInstance( Hello.class.getClassLoader(), // 传入ClassLoader new Class[] { Hello.class }, // 传入要实现的接口 handler); // 传入处理调用方法的InvocationHandler hello.morning("Bob"); }}interface Hello { void morning(String name);} 定义一个InvocationHandler实例,它负责实现接口的方法调用; 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数: 使用的ClassLoader,通常就是接口类的ClassLoader; 需要实现的接口数组,至少需要传入一个接口进去; 用来处理接口方法调用的InvocationHandler实例。 将返回的Object强制转型为接口。 动态代理实际上是JDK在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样: 123456789101112public class HelloDynamicProxy implements Hello { InvocationHandler handler; public HelloDynamicProxy(InvocationHandler handler) { this.handler = handler; } public void morning(String name) { handler.invoke( this, Hello.class.getMethod("morning"), new Object[] { name }); }} 其实就是JDK帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>Java</tag>
<tag>反射</tag>
</tags>
</entry>
<entry>
<title><![CDATA[中间件-MQTT协议]]></title>
<url>%2F2019%2F12%2F23%2F%E4%B8%AD%E9%97%B4%E4%BB%B6-MQTT%E5%8D%8F%E8%AE%AE%2F</url>
<content type="text"><![CDATA[MQTT协议是 对TCP/IP协议的封装和重构。 最小代码占用,最小报文开支,适用于处理能力不强,网络带宽不高的设备,如各类传感器。 保证消息顺序性 支持连续的会话控制 支持服务质量管理 支持动态创建主题 消息支持一对一,一对多。支持集群模式,支持广播模式。 名词解释网络连接客户端连接到服务器之后,提供双向,有序,无损的发送方式。 消息MQTT在网络上传输的消息,由主题名(topic),服务质量(Qos)、负载消息(payload)等组成。 TopicMQTT通过主题对消息进行分类,通过增加反斜杠对消息进行分级。主题不需要提前配置,直接使用。 还可以使用通配符匹配主题,+代表一级,#表示本机及下面所有级。 例: beijing/library表示北京市的图书馆 beijing/hospital/jishuitan表示北京市的积水潭医院 +/library表示所有地区的图书馆 beijing/#北京的所有场所(library和hospital) 服务质量MQTT提供了三种消息传输质量 0:你收没收到我不管,反正我发了 1:怎么着都会收到一次,有可能会收到重复消息。 2:只会收到一次消息。 质量0适合消息丢失几次影响也不大的场景,如温度传感器每隔一段时间发送一次当前温度,如果间隔够小,丢失一两次影响不是很大 质量2,这种精准的方式牺牲的是来回多次的确认消息而增加网络开销。 质量1,既不会丢失消息,也没有那么大的网络开销,只有一个确认重发机制的开销。适合于日志处理。 有效负载二进制数组,实际业务场景可以通过JSON方法包装数据再转为二进制数组。 客户端 与服务器建立和断开连接 发布主题消息 订阅主题消息 转发主题消息 服务器 与客户端建立和断开连接 满足订阅和取消订阅的要求 完成基于订阅主题的消息分发 MQTTOverWebSocket参考文献MQTT-v5官方文档 数据公会-MQTT入门篇]]></content>
<categories>
<category>中间件</category>
</categories>
<tags>
<tag>MQTT</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java正则]]></title>
<url>%2F2019%2F12%2F18%2FJava%E5%9F%BA%E7%A1%80-Java%E6%AD%A3%E5%88%99%2F</url>
<content type="text"><![CDATA[我们经常会用到判断一个字符串是否符合某一规则,像是否是一个有效手机号,是否是一个有效身份证号等等。 如果使用程序逻辑运算符判断,那可能是一个繁杂的任务,但是有了正则表达式,我们可以通过定一个一个简短的规则起到强大的约束作用。 精准匹配精准匹配使用String.equals()就能实现,这并不是他真正的用武之地。 匹配规则正则表达式的验证规则是从左到右按规则匹配。 \d是一个完整匹配单元,java中如果想要完整表达\d,字符串中应当输入\\d 单个匹配规则字符.匹配一个任意字符 12345System.out.println("&".matches("."));//trueSystem.out.println(".".matches("."));//trueSystem.out.println("".matches("."));//falseSystem.out.println("aa".matches("."));//false 数字\d匹配一个数字字符 \D匹配范围相当于!d \d是一个完整匹配单元,java中如果想要完整表达\d,字符串中应当输入\\d 1234System.out.println("a1".matches("a\\d"));//trueSystem.out.println("aa".matches("a\\d"));//falseSystem.out.println("a11".matches("a\\d"));//fasle 常用字符(字母、数字、下划线)\w匹配一个字母、数字、下划线字符 \W匹配范围相当于!w 123456System.out.println("A".matches("\\w"));//trueSystem.out.println("1".matches("\\w"));//trueSystem.out.println("_".matches("\\w"));//trueSystem.out.println("&".matches("\\w"));//falseSystem.out.println("\u548c".matches("\\w"));//false 空字符\s匹配一个空格字符,不是空格,还包括tab \S匹配范围相当于!s 12345678System.out.println(" ".matches("\\s"));//trueSystem.out.println("\t".matches("\\s"));//trueSystem.out.println("\n".matches("\\s"));//tureSystem.out.println("\f".matches("\\s"));//trueSystem.out.println("\t".matches("\\s"));//trueSystem.out.println("\b".matches("\\s"));//falseSystem.out.println("\'".matches("\\s"));//false 多个匹配规则这几个匹配符不可以单独使用 12System.out.println("a".matches("*"));//编辑器报错 "*" Dangling metacharacter *匹配0~∞个任意字符 123System.out.println("".matches("\\d*"));//trueSystem.out.println("1".matches("\\d*"));//trueSystem.out.println("11".matches("\\d*"));//true +匹配1~∞个任意字符 1234System.out.println("1".matches("\\d*"));//trueSystem.out.println("11".matches("\\d*"));//trueSystem.out.println("".matches("\\d*"));//false ?匹配0~1个任意字符1234System.out.println("".matches("\\d*"));//trueSystem.out.println("1".matches("\\d*"));//trueSystem.out.println("11".matches("\\d*"));//false {m}匹配M个字符 1234System.out.println("1".matches("\\d{1}"));//trueSystem.out.println("".matches("\\d{1}"));//falseSystem.out.println("11".matches("\\d{1}"));//false {m,n}匹配M~N个字符 12345System.out.println("1".matches("\\d{1,3}"));//trueSystem.out.println("111".matches("\\d{1,3}"));//trueSystem.out.println("".matches("\\d{1,3}"));//falseSystem.out.println("1111".matches("\\d{1,3}"));//false {m,}匹配m~∞个字符 1234System.out.println("1".matches("\\d{1,}"));//trueSystem.out.println("1111111111111".matches("\\d{1,}"));//trueSystem.out.println("".matches("\\d{1,}"));//false 复杂匹配规则匹配开头和结尾多行匹配时^表示开头,$表示结尾。 匹配指定范围[...]匹配范围,如: [0123456789],可以匹配0~9,也可以写成[0-9] 类似的还有: [0-9a-fA-F]表示不限制大小写的十六进制 [a-zA-Z]表示不限制大小写的字母 拓展: 而\可以通过[A-z],推断应该应该是ASCII表中的位序来判断的。以下结果全为true 1234567System.out.println("[".matches("[A-z]"));//91System.out.println("\\".matches("[A-z]"));//92System.out.println("]".matches("[A-z]"));//93System.out.println("^".matches("[A-z]"));//94System.out.println("_".matches("[A-z]"));//95System.out.println("`".matches("[A-z]"));//96System.out.println("_".matches("[^-`]")); 如果表达式写成z-A,编译器报错 Illegal character range (to < from),更是印证了我们猜想。 反向匹配范围 [^...]表示![...] 1System.out.println("@".matches("[^A-z]")); 或|来连接两个正则表达式 bc和 BC可以用bc|BC来匹配。 括号类似于提取公因子 3×2+3×1=>3×(2+1) Abc和 ABC可以用A(bc|BC)来匹配。]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>正则表达式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[中间件-Rocket中间件]]></title>
<url>%2F2019%2F12%2F18%2F%E4%B8%AD%E9%97%B4%E4%BB%B6-Rocket%E4%B8%AD%E9%97%B4%E4%BB%B6%2F</url>
<content type="text"><![CDATA[abstract 1Class class =]]></content>
<categories>
<category>中间件</category>
</categories>
<tags>
<tag>RocketMQ</tag>
<tag>消息中间件</tag>
</tags>
</entry>
<entry>
<title><![CDATA[枚举类]]></title>
<url>%2F2019%2F12%2F17%2FJava%E5%9F%BA%E7%A1%80-%E6%9E%9A%E4%B8%BE%E7%B1%BB%2F</url>
<content type="text"><![CDATA[abstract 在数据交互时,常量往往和显示到页面的并不一样,如表示性别时,男(0),女(1)。代码里记 0、1 页面表示用男女。 1234int gender = xxx;if(gender == 1){ //TODO} 在类型判断的时候直接使用0、1来 == 或equals并不利于代码的强壮性。 可以有两种解决方案: 自定义常量类或常量接口 枚举类 常量类可以用类或接口专门存放常量值 IWeekdayConstants.java 1234567891011121314151617181920212223242526272829303132333435public interface IWeekdayConstants{ public static final int SUN = 0; public static final String SUN_CAPTION = "星期一"; public static final int MON = 1; public static final String MON_CAPTION = "星期二"; public static final int TUE = 2; public static final String TUE_CAPTION = "星期三"; public static final int WED = 3; public static final String WED_CAPTION = "星期四"; public static final int THU = 4; public static final String THU_CAPTION = "星期五"; public static final int FRI = 5; public static final String FRI_CAPTION = "星期六"; public static final int SAT = 6; public static final String SAT_CAPTION = "星期天"; public static final Map<Integer, String> WEEKDAY_MAP = new LinkedHashMap<Integer, String>(){ private static final long serialVersionUID = 123L; { this.put(SUN,SUN_CAPTION); this.put(MON,MON_CAPTION); this.put(TUE,TUE_CAPTION); this.put(WED,WED_CAPTION); this.put(THU,THU_CAPTION); this.put(FRI,FRI_CAPTION); this.put(SAT,SAT_CAPTION); } };} Test.java 12345678910111213141516171819202122232425262728293031323334353637383940public class Test { public static void main(String[] args) throws Exception{ int day = xxx; //比较 if(IWeekdayConstants.SUN == day || IWeekdayConstants.SAT == day){ System.out.println("睡到自然醒"); } //遍历 for(Map.Entry<Integer, String> entry:IWeekdayConstants.WEEKDAY_MAP.entrySet){ System.out.println(entry.getKey() +":" + entry.getValue()); } //switch switch(day){ case IWeekdayConstants.SUN: //TODO break; case IWeekdayConstants.MON: //TODO break; case IWeekdayConstants.TUE: //TODO break; case IWeekdayConstants.WED: //TODO break; case IWeekdayConstants.THU: //TODO break; case IWeekdayConstants.FRI: //TODO break; case IWeekdayConstants.SAT: //TODO break; default : throw new RunTimeException("一周七天够用了"); } }} LindedHashMap中必须用到serialVersionUID,如果没有serialVersionUID字段,则对类的任何更改都将使以前序列化的版本不可读。[注1] 这种方式实现枚举有些问题,就是无法验证需要对比的变量是否合理。如:int day = 7。 或者仍可以作为其他用途,如: 1234int man = 1;if(man == IWeekday.MON){ //TODO} 编译通过,但用枚举用途就混乱了。 枚举类可以通过枚举类型实现上述所有功能,而且功能专用。 123public enum Weekday { SUN,MON,TUE,WED,THU,FRI,SAT;} 简单的一行代码实现了之前50的功能,能验证合理性,也用途专一,因为类型不同。 Weekday.MON的类型是Weekday. 特性enum定义的类型就是class,enum类编辑的class类似于这样: 12345678910public final class Weekday extends Enum { public static final Weekday SUN = new Weekday(); public static final Weekday MON = new Weekday(); public static final Weekday TUE = new Weekday(); public static final Weekday WED = new Weekday(); public static final Weekday THU = new Weekday(); public static final Weekday FRI = new Weekday(); public static final Weekday SAT = new Weekday(); private Weekday() {}} 隐藏构造方法,使得每个实例都是唯一的。 定义的enum类型总是继承自java.lang.Enum,且无法被继承; 只能定义出enum的实例,而无法通过new操作符创建enum的实例; 可以将enum类型用于switch语句。 枚举类是一种引用类型,引用类型需要使用equals来进行比较,如果使用==则比较的是两个对象是否相等。 但枚举类是一个特殊的引用类型,因为在虚拟机内存中只存在唯一实例,所以使用==比较也是可以的。 方法name() 1String name = Weekday.MON.name();//"MON" ordinal() 1int num = Weekday.MON.ordinal();//1 这个顺序根据常量在类中的顺序,类似于LinkedMap; 如果使用这个方法做逻辑,之后类中常量的顺序又改变了,倒是程序出问题,强壮性很低。 扩展枚举类但可以稍微改造下枚举类 123456789public enum Weekday { MON(1),TUE(2),WED(3),THU(4),FRI(5),SAT(6),SUN(0); public final int daySort; private Weekday(int daySort){ this.daySort = daySort; };} 既然可以加逻辑顺序,那岂不是还可以加别的信息呢? 1234567891011public enum Weekday { MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日"); public final int daySort; public final String caption; private Weekday(int daySort,String caption){ this.daySort = daySort; this.caption = caption; };} 默认的toString()和name()返回结果是一样的,但是添加逻辑属性之后你就可以通过重写toString()来返回想要的值。 1234567891011121314151617181920212223public enum Weekday { MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日"); private final int daySort; private final String caption; private Weekday(int daySort,String caption){ this.daySort = daySort; this.caption = caption; }; public int getDaySort(){ return daySort; } public String getCaption(){ return caption; } @OverWrite public String toString(){ return this.caption; }; pub} switch switch的用法和上面接口的实现方式一样,最后需要加上default放值漏写某一枚举常量; Test.java 12345678910111213141516171819202122232425262728293031323334353637383940414243444546public class Test { public static void main(String[] args) throws Exception{ Weekday day = Weekday.MON; //比较 if(Weekday.SUN == day || Weekday.SAT == day){ System.out.println("睡到自然醒"); } //遍历 //通过枚举类的静态方法 for (Weekday day:Weekday.values()){ System.out.println(day.getDaySort() + ":" + day.toString()); } //通过反射 Class<Weekday> clz = Weekday.class; for (Weekday day:clz.getEnumConstants()){ System.out.println(day.getDaySort() + ":" + day.toString()); } //switch switch(day){ case Weekday.SUN: //TODO break; case Weekday.MON: //TODO break; case Weekday.TUE: //TODO break; case Weekday.WED: //TODO break; case Weekday.THU: //TODO break; case Weekday.FRI: //TODO break; case Weekday.SAT: //TODO break; default : throw new RunTimeException("一周七天够用了"); } }}]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>枚举类</tag>
<tag>enum</tag>
</tags>
</entry>
<entry>
<title><![CDATA[内部类]]></title>
<url>%2F2019%2F12%2F10%2FJava%E5%9F%BA%E7%A1%80-%E5%86%85%E9%83%A8%E7%B1%BB%2F</url>
<content type="text"><![CDATA[abstract]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>内部类</tag>
</tags>
</entry>
<entry>
<title><![CDATA[多态]]></title>
<url>%2F2019%2F12%2F09%2FJava%E5%9F%BA%E7%A1%80-%E5%A4%9A%E6%80%81%2F</url>
<content type="text"><![CDATA[abstract]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>多态</tag>
</tags>
</entry>
<entry>
<title><![CDATA[继承]]></title>
<url>%2F2019%2F12%2F09%2FJava%E5%9F%BA%E7%A1%80-%E7%BB%A7%E6%89%BF%2F</url>
<content type="text"><![CDATA[abstract]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>继承</tag>
</tags>
</entry>
<entry>
<title><![CDATA[封装]]></title>
<url>%2F2019%2F12%2F09%2FJava%E5%9F%BA%E7%A1%80-%E5%B0%81%E8%A3%85%2F</url>
<content type="text"><![CDATA[封装是一种思想。 隐藏内部细节 提供使用方法 package 包是可以理解为命名空间,他能有效解决类重名问题。 为了方便组织和管理,多用倒置域名来标识。 权限修饰符一个.java文件只能有一个被public修饰的类,并且类名必须与文件名相同。如果没有public关键字,则对类的命名没有要求。 修饰类 默认访问权限(包访问权限):用来修饰类的话,表示该类只对同一个包中的其他类可见。 public:用来修饰类的话,表示该类对其他所有的类都可见。 如果你想实现其他任何人都不能访问该类,可以通过将类的所有构造器都指定为private。 1234567891011121314151617package com.qigou.b2cex.test;class People { private String name = null; public People(String name) { this.name = name; } public String getName(){ return name; } public void setName(String name){ this.name = name; }} 1234567package com.qigou.b2cex.test;public class TestExcel{ public static void main(String[] args) throws Exception { System.out.println(new People("BBDog").getName()); }} 输出结果: 1BBDog 将People的包名改为test1后 修饰方法和变量 作用域 当前类 同包下 子孙 所有类 public √ √ √ √ protected √ √ √ × 默认 √ √ × × private √ × × × 1234567891011121314151617package com.qigou.b2cex.test;public class People { private String name = null; public People(String name) { this.name = name; } public String getName(){ return name; } public void setName(String name){ this.name = name; }} 12345678package com.qigou.b2cex.test;public class TestExcel{ public static void main(String[] args) throws Exception { People people = new People("BBDog"); System.out.println(people.getName()); }} 变更getName()的权限,每次初始化包名。 public 正常输出: BBDog 不同包下输出:BBDog 默认 同包下正常输出: BBDog 不同包下: protected 同包下正常输出: BBDog 不同包下: 创建Kids类继承People 12345678910111213package com.qigou.b2cex.test;import com.qigou.b2cex.test1.People;public class Kids extends People { public Kids(String name){ super(name); } public String toString() { return getName(); }} 子类可以使用protected修饰的方法 private getting&settinggetting和setting是封装思想的核心。 修改属性可见性限制对属性的访问 增加赋值取值方法,方法可见。 在赋值取值方法中增加控制条件。 123456789101112131415pubilc class People{ private int age; public int getAge(){ return age; } public void setAge(int age){ if(age > 0){ this.age = age; }else{ System.out.prinln("年龄不能为负数"); } }} 构造方法没有新增构造方法的时候,每个类都有个默认的构造方法; 通过getting&setting,增加了对数据的统一控制,维护性强。但是如果每次赋值都单独调用一个setting方法会很麻烦。 可以通过重写构造方法来实现对类对象的赋值。 1234567891011121314151617181920212223pubilc class People{ private int age; private String lang; public People(int age,String lang){ if(age > 0){ this.age = age; } this.lang = lang; } public int getAge(){ return age; } public void setAge(int age){ if(age > 0){ this.age = age; }else{ System.out.prinln("年龄不能为负数"); } }} 12345public class TestExcel{ public static void main(String[] args){ People people = new People(100,"Chinese"); }} 内部类内部类( Inner Class )就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类。 内部类和普通类基本没有区别,只是在内部类调用外部类属性时有特殊之处。 内部类的主要作用如下: 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类 内部类的方法可以直接访问外部类的所有数据,包括私有的数据 内部类可以分为: 成员内部类 静态内部类 方法内部类 匿名内部类 成员内部类 内部类对象不能直接new 出来,内部类 对象名 = 外部类对象.new 内部类(); 编辑java文件后,产生两个.class文件:外部类.class,外部类$内部类.class 外部类不能直接使用内部类的属性和方法(需要创建内部类对象,通过对象调用) 内部类可以直接使用内部类的所有属性和方法(不受权限控制符影响)(同名时需要在属性或者方法前面加外部类.this.) 1234567891011121314151617181920212223public class Hello{ private String str = "123"; class World { String str = "456"; public void test() { System.out.println("内部类属性:" + str); System.out.println("外部类属性:" + Hello.this.str); Hello.this.test() } } private void test(){ System.out.println("外部类方法被内部调用"); } public static void main(String[] args) { Hello h = new Hello(); World w = h.new World(); w.test(); }} 静态内部类静态内部类和普通内部类相同,区别在于, 只能调用外部类的静态属性和方法外部类.属性,外部类.方法() 想要调用外部类非静态属性和方法需要使用 new 外部类().属性/方法() 内部类对象可以通过直接new 获得 方法内部类方法内部类只能供方法使用,对外不可见。 12345678910111213141516171819202122public class Hello{ private String str = "123"; private void testOUT(){ System.out.println("外部类方法被内部调用"); class World { String str = "456"; void test() { System.out.println("内部类属性:" + str); System.out.println("外部类属性:" + new Hello().str); } } World w = new World(); w.test(); } public static void main(String[] args) { Hello h = new Hello(); h.testOUT(); }} 匿名内部类]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>Java基础</tag>
<tag>封装</tag>
<tag>package</tag>
<tag>getting</tag>
<tag>setting</tag>
<tag>构造方法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[设计模式-单例模式和多例模式]]></title>
<url>%2F2019%2F12%2F03%2F%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%2F</url>
<content type="text"><![CDATA[abstract 保证对象只能被实例化一次的设计模式称为单例模式。 单例模式饿汉模式最简单的实现方法就是,一开始就初始化这个对象,并且将构造方法私有化。 123456789public class Singleton{ private static Singleton singleton = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return singleton; }} 一开始就创建实例的设计方法也成为饿汉模式。 懒汉模式还可以在调用的时候再初始化,称为懒汉模式。 123456789101112public class Singleton{ private static Singleton singleton = null; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); } return singleton; }} 懒,就要付出代价。 基础懒汉模式并不是线程安全的,因为高并发下一个线程判断singleton为空时,会创建一个对象,但是创建对象是需要时间的,如果在创建期间另一个线程执行if判断时,条件成立,也会创建一个对象。对象的属性不变的情况下影响不大,但实例化的时候属性不同时,则有较大影响。 处理办法: 方法加同步锁。 1234567891011121314151617public class Singleton { private static Singleton singleton = null; private Singleton(){} public synchronized static Singleton getInstance() { try { if(singleton == null){//懒汉式 singleton = new Singleton(); } } catch (InterruptedException e) { e.printStackTrace(); } return singleton; } } 可能出现不安全的直接代码使用同步代码块包起来。 12345678910111213141516171819public class Singleton { private static Singleton singleton = null; private Singleton(){} public static Singleton getInstance() { try { if(singleton == null){//懒汉式 synchronized (Singleton.class) { singleton = new Singleton(); } } } catch (InterruptedException e) { e.printStackTrace(); } return singleton; } } 使用volatile关键字使线程共用,并进行二次判断。 123456789101112131415161718192021public class Singleton { volatile private static Singleton singleton = null; private Singleton(){} public static Singleton getInstance() { try { if(singleton == null){//懒汉式 synchronized (Singleton.class) { if(singleton == null){//二次检查 singleton = new Singleton(); } } } } catch (InterruptedException e) { e.printStackTrace(); } return singleton; } } 静态内部类项目启动的时候会优先加载静态资源。 123456789101112public class Singleton{ private static class Handler{ private static singleton = new Singleton(); } private Singleton(){} public Singleton getInstance(){ return Handler.singleton; }} 感觉和饿汉模式异曲同工。 静态代码块静态代码块会被先执行,所以可以使用静态代码块的方式实现单例 123456789101112131415161718public class Singleton{ private static Singleton singleton = null; private Singleton(){} static{ singleton = new Singleton(); //init(); } /** 也可以根据需求绕个远 private static void init(){ singleton = new Singleton(); } */ public Singleton getInstance(){ return singleton; }} 多例模式]]></content>
<categories>
<category>设计模式</category>
</categories>
<tags>
<tag>设计模式</tag>
<tag>单例模式</tag>
<tag>多例模式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[异常]]></title>
<url>%2F2019%2F11%2F27%2FJava%E5%9F%BA%E7%A1%80-%E5%BC%82%E5%B8%B8%2F</url>
<content type="text"><![CDATA[abstract Java异常(Throwable)有两个子类Error和Exception. Error主要是机器或环境异常。如虚拟机异常、内存溢出、线程死锁。 Exception主要是指项目运行过程中可能出现的异常。运行时异常(RuntimeException)、IO异常、SQL异常等。 Error表示代码运行时 JVM(Java 虚拟机)出现的问题。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。 Exception异常主要分为运行时异常和非运行时异常。 运行时异常:RuntimeException及其子类属于运行时异常,指在程序运行过程中,因为逻辑问题而产生的异常,像空指针异常、数组下标越界等等。这些异常都是在编译阶段无法检查的,所以也成为非检查异常。 非运行时异常:除了RuntimeException及其子类称为非运行时异常,对于程序而言是必须处理的异常,不处理,可能编译就不会通过。对于这些异常是必须捕获处理或者抛出的异常。 捕获处理异常try-catch-finally关于try-catch-finally的几个关键点 try必须存在,catch可以是〇个或多个,finally只能是〇个或一个。 使用finally后,无论是否有异常,finally块都会运行,常用来关闭流。 如果try或catch块中有return,虽然执行完他们之后还会执行finally,但是他们要return的东西是已经定好了,finally中修改不生效。(查看字节码文件可知要return的表达式结果已用新变量承接)⭐ finally中有return的话try-catch中的return会被截胡。 抛出异常类使用Throws关键字指定要抛给谁来处理异常。 Q&AQ:如何判断什么时候捕获异常,什么时候抛出异常? A:知道如何处理的异常,就捕获处理。不知道如果和处理的就抛出。]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>try</tag>
<tag>catch</tag>
<tag>finally</tag>
<tag>error</tag>
<tag>throws</tag>
<tag>异常</tag>
</tags>
</entry>
<entry>
<title><![CDATA[ZooKeeper]]></title>
<url>%2F2019%2F10%2F07%2Fzookeeper%2F</url>
<content type="text"><![CDATA[又一个社会关系在计算机逻辑设计中的体现。 什么是zookeeper?Zookeeper是一个分布式协调服务,为分布式系统系统提供共享配置,协调资源,提供命名服务等; 那什么是分布式系统?** 合起伙来办大事; 一件事情一个人做需要几个小时,几个人一起做可能几分钟就能完成。这几个人就是“集群”,集群中的每个独立个体就是“节点”。 集体的优点: 集群中几个节点思想出了问题,对整个集群影响不是很大。 集群可以通过补充节点不断壮大。 节点可能代表整个集群的形象。 集群的问题: 共享资源存在竞争。 死锁,几个节点相互等待对方的结果才能进行下一步。 数据不一致。 Zookeeper的数据模型: Zookeeper数据模型是树形结构,不同于树,Zookeeper使用路径引用,比如猫就是 “ /动物/猫” Znode数据模型: data: Znode存储的数据信息。 ACL: 记录Znode的访问权限,即哪些人或哪些IP可以访问本节点。 stat: 包含Znode的各种元数据,比如事务ID、版本号、时间戳、大小等等。 child: 当前节点的子节点引用,类似于二叉树的左孩子右孩子。 Zookeeper是为了应对读多写少的场景的,所以节点信息不易频繁改变,并且节点数据最大不能超过1M。 基本操作和时间通知API:cteate、delete、exists、getDate、setDate、getChildren watch?** 客户端访问Znode时,会维护一张watchTable,key为Znode路径,value为watcher; 当Znode改变时,异步通知watcher,并删除watchTable中对应的key-value zookeeper Service 服务器:分为follow和leader,follow只读,leader读写; 客户端:向follow服务器请求; 常规数据更新步骤: 客户端发起写入请求给follower; follower把请求转发给leader; 二次提交:leader告诉所有follower,半数以上follower写入成功后,通知客户端写入成功; leader再发送commit广播给follower; 崩溃恢复:leader这么重要,要是leader崩溃了会怎么样?先了解下节点状态 节点状态:looking、leading、following; 最新事务ID:ZXID 崩溃恢复的步骤 选举阶段: 状态都置为looking,向其他节点发起投票,投票信息包含自己的节点ID和最新事务ID(ZXID) 发现ZXID比自己大时重新发起投票,投票给当前最大ZXID的节点; 节点会统计投票数量,某个节点半数以上投票时,这个节点状态置为leading,其他节点置为following; 发现阶段: 从所有节点中寻找最新事务,接收follow发过来的epoch,+1,再发回follow; follower接收到之后返回ACK,带上XZXID和事务日志,leader选出最大ZXID,并更新自身日志; 同步阶段: 将最新事务日志同步给所有follower,半数以上follower同步成功才成为正式leader 用途: 分布式锁 服务注册和发现 Dobbo 共享配置和状态信息: Redis的分布式解决方案Codis; Kafka、HBase、Hadoop]]></content>
<categories>
<category>分布式</category>
</categories>
<tags>
<tag>Zookeeper</tag>
<tag>分布式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[集合小结]]></title>
<url>%2F2019%2F09%2F25%2FJava-%E9%9B%86%E5%90%88%2F</url>
<content type="text"><![CDATA[集合类总结 ListIterator(迭代器):读取删除、删除操作,支持fail-fast机制; Enumeration(枚举类):只能读取,不支持fail-fast机制; fail-fast机制:读取数据时判断当前集合对象是否被别的线程修改过; ArrayList 动态数组,默认初始值:10,支持序列化, 扩容方式:newCapacity = oldCapacity + (oldCapacity >> 1);再与最小值最大值比较; 线程不安全,方法未使用synchronized关键字修饰; 支持Iterator,存在fail-fast机制,不支持Enumeration; 应用环境:频繁随机访问;避免中间插入、删除操作;用于单线程操作; 遍历:下标遍历最快 LinkedList 双向循环链表,初始值:0,支持序列化; 扩容方式:随用随长; 线程不安全,fail-fast机制,方法未使用synchronized关键字修饰; 支持Iterator,存在fail-fast机制,不支持Enumeration; 应用环境:频繁中间插入、删除操作;用于单线程操作;可以作为FIFO的队列,可以作为FILO的栈; 遍历: Vector 动态数组,默认初始值:10,不支持序列化; 扩容方式:newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity),capacityIncrement为构造方法中指定的增长因子; 线程安全,方法使用synchronized关键字修饰; 支持Iterator,存在fail-fast机制,支持Enumeration; 应用环境:多线程操作; 遍历:for(Object obj:Object[])方式最快 Stack 继承Vector,数组实现,先进后出(FILO, First In Last Out)队列,不支持序列化。 push():数组末尾增加追加元素;pop():数组末尾删除元素;peek():返回数组末尾元素; 继承Vector,方法使用synchronized关键字修饰; 支持Iterator,存在fail-fast机制,支持Enumeration; Map内存地址值: hashCode()`: equals(): HashMapTreeMapHashTableWeakHashMapLinkedHashMapSetHashSetTreeSetLindHashSet]]></content>
<categories>
<category>JAVA集合框架</category>
</categories>
<tags>
<tag>集合</tag>
<tag>Java</tag>
<tag>总结</tag>
</tags>
</entry>
<entry>
<title><![CDATA[盘山十二时辰]]></title>
<url>%2F2019%2F09%2F13%2FPanShan%2F</url>
<content type="text"><![CDATA[盘山十二时辰 赛前准备早知有盘山,何必下江南。报名之后就开始紧锣密鼓的在为盘山准备着,迫不及待。从网上的攻略中搜寻 较为清晰的线路图爬升图,从户外软件上下载路线在google earth上面研究,制定计划,现在看来,所有的计划在高温面前不值一提。 行程从北京出发,因为周天早上出发,所以买了周六早上的票,先去住下,也正好膜拜一下100km大神起跑的英姿。提前定了一个农家院,盘山景区周边的农家院发展规模很大,景区正门西边有着数不清的农家院,住宿条件对标快捷酒店,更关键的是这里的人儿热情好客,和他们聊聊天。尝尝他们自己做的菜,尤其是大锅鱼,给我留下了不可磨灭印象,哧溜。收拾妥当两点半来到牌楼下,虽然温度灼人,但选手们的热情更为高涨。刚刚完成八百流沙的百日侠-林海激动的介绍着这场准备了两年才与大家见面的100公里。 没过多久,视频直播平台就传来了他们赛道上的身影,恰巧同行的3个100大神都有上镜,状态不错,而周天正好也是彦博的生日,他希望用盘山首百来让今年的生日更有意义一些。这次的计时的服务由东软赛克提供,他可以订阅选手,当他们sp点打卡的时候你也会同步接受到推送,晚上一边和同行伙伴准备明天的装备,一边关注着100的伙伴的赛况,三个人越野经验都特别丰富,看着他们的实时排名一次次向前就踏实了很多。 寅初 3点,狗在外面吵架,五点鸡开始叫,七点起床。一脸疲惫的吃了些院里大哥准备的早饭就前往广场。值得一提的是,我们住的不算近,但走路也就五六分钟就到了景区广场。广场上人山人海35组10亲子组同时出发。盘山组委会,百日侠在台上热情洋溢的坐着动员。所有的人都已迫不及待的过程 起点~CP1⭐ 2公里盘山公路 + 4公里的景区台阶路 + 999台阶 + 500米下山台阶。 这4公里的台阶路累计爬升有500多,跟着同行的瑶神跑了3公里就再也没见过瑶神。到了999台阶的时候不要抬头,因为抬头你也看不到顶。补给点稍作补给,确认打卡成功就迫不及待的的去追赶瑶神。CP1~CP2⭐ 4公里下行盘山公路 + 4公里技术路段 4公里左右的下行盘山公路可以撒欢的跑了,但有很多景区接驳车,跑的时候需要注意路况。盘山公里结束就进入了真正的技术赛道,这也是第一个让我感到,上山比下山舒服的赛道,路线图上感觉像是在馒头上跑,实际上我们是在锯齿上跑,而且一山更比一山高。三公里技术路段以后是一公里的下坡,直达cp2,因为是沙土路,膝盖力量不行,很本不敢跑太快。Cp2吃了根雪糕,和外国小伙聊了会天,我用蹩脚英语给他说,他用流利中文回答我。让我至今“怀恨在心”的是我先出了补给点,他在后面喊,我一会超过你,当时我就在想,前面14公里都没赶上我还好意思说超我。然后,然后我就然后他比我提前一个小时完赛。Cp2~cp10 ⭐⭐⭐ 2公里乡村道路 + 3公里山脊 + 1公里下山路 + 2公里乡村公路 这一段我觉得是整个35公里最有技术的一段,村里跑了两公里就来到山下,之字形的赛道上到第一个小高峰,从此不知树林是何物,35度的太阳,光秃秃的山脊,这一段降温用水比喝水多。三公里的山脊最后,有个50米左右,70度的坡作为最后的磨炼,手脚并用爬上去。 开始下坡,仍然是水平不够跑不起来的技术路段,头这么大的石头散落一路,而且都是活动的。其中有一段像是冰川活动的痕迹,山谷里有一条石头流。 石头流结束可算是能跑起来了,这里的路面零星散落鸡蛋大小的石块,落脚要十分小心。跑了一公里,一路的高温,内心十分的渴望补给点,这个补给点有肉吃。遇到一个摄影师,说还有一公里,于是开始用6分左右的配速冲向补给点,这一冲,就是接近3公里。你家这1公里有点长啊,这一段匀速跑身体温度直线上升。到了补给点先物理降温,再喝了点藿香正气液预防一下。也许是东西太好吃,也许是志愿者们的服务太好,这一歇就是30分钟。懊悔不已。 CP10~CP11⭐ 6公里缓坡 + 4公里公路 和一个多跑了五公里的小姐姐一起出的补给点。小姐姐说我上山慢是膝盖不行,我下山快是因为我不怕死。接下来这10公里我是真的见识到这句话真实的样子。这一段路相对平缓上下交错,但这时大腿已经酸痛,下山已经完全跑不起来,和一个大哥从起点一直相伴到这里,路上交流着关于跑步的观点,跑步不能只图快,还要跑得长久,二三十岁能跑,五六十岁也能跑。两个补给点之间增加了一个临时补给点,这个补给点以后是3 4公里的上行盘山公路,顶着太阳,咸鱼般的向少林寺快走着。 CP11~终点⭐⭐⭐ 因为少一个补给点耽搁太久,在这最后一个补给点,我放弃了我向往的的羊汤,喷了些云南白药,看着另外两个同行伙伴惬意的坐在收容车上发来的问候,没敢停,继续走着,因为体力基本耗尽,全靠毅力在走着,听着旁边大哥报着还有多少米的爬升。终于看到了下山的路,依然是向下挪着,每有一个人超过我,我就数着我的实时名次,想追,但真的追不动。直到赛道和出发时赛道重合后才逐渐迈开腿,最后的力气跑了两公里到达终点收容车小队已经在终点等着,让我最吃惊的是彦博竟然退赛了,出发前看到他的名次还是比较靠前的,很多原因导致他没能到达终点,真的觉得很惋惜,不便多说,只得心里一句:“生日快乐,明年再来”。 让康复人员放松了下肌肉,处理了伤口,水泡。接着买了晚上最后一班回北京的门票。到家已经是十二点半了,第二天七点半起床,收拾,上班。不是我想起,是酸的睡不着。 总结第一次盘山越野可以用完美来形容,一切恰到好处,无论是赛事安排和赛道设置都是有水准的,怪不得说,早知有盘山,何必去柴古。]]></content>
<categories>
<category>越野赛</category>
</categories>
<tags>
<tag>盘山</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JavaIO-基础]]></title>
<url>%2F2019%2F08%2F26%2FJavaIO-%E5%9F%BA%E7%A1%80%2F</url>
<content type="text"><![CDATA[初步理解JavaIO]]></content>
<categories>
<category>JavaIO</category>
</categories>
<tags>
<tag>java</tag>
<tag>IO</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Markdown-markdown不常用小技巧]]></title>
<url>%2F2019%2F08%2F20%2FMarkdown-markdown%E4%B8%8D%E5%B8%B8%E7%94%A8%E5%B0%8F%E6%8A%80%E5%B7%A7%2F</url>
<content type="text"><![CDATA[一些不常用但有时又能起到画龙点睛效果的小技巧 Markdown与H5不得不说的秘密Markdown的页内跳转必把大象塞进冰箱更为简单 step1:增加锚点,其实就是给标签加一个id, 12> <span id="jump">Hello World</span>> step2:跳转 12> [XXXX](#jump)> 试了一下用class标记,好像是不行。检查页面元素发现markdown转为H5之后使用的herf 1<a href=".jump">XXXX</a>]]></content>
<categories>
<category>Markdown</category>
</categories>
<tags>
<tag>Markdown</tag>
<tag>技巧</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据库连接池]]></title>
<url>%2F2019%2F07%2F16%2FJava-%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0%2F</url>
<content type="text"><![CDATA[abstract 之前有写过文章介绍了java连接数据库的流程,该流程是一个线程请求访问数据库时必要的步骤,但是网站并发量高时,每个线程都要连接数据库,操作数据库,断开数据库连接。有时操作数据库的时间占比非常小,时间花销都用在数据库的连接与断开上。导致了并发访问瓶颈。 这个时候数据库与应用线程就是一个生产者消费者的关系,解决生产消费者关系的方案之一就是引入资源池. 生产者生产的东西一直朝池子中放,消费者从池子中获取自己想要的资源。数据库与线程也是,线程需要连接,那就把连接都初始化好,放在池子里,线程直接获取连接,操作数据库,用完之后归还连接。这样每个线程就不需要和自己和数据库建立断开连接,连接已经由连接池创建好,只需要拿过来用即可。]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>java</tag>
<tag>连接池</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java-JDBC连接数据库步骤]]></title>
<url>%2F2019%2F07%2F15%2FJava-JDBC%E8%BF%9E%E6%8E%A5%E6%95%B0%E6%8D%AE%E5%BA%93%E6%AD%A5%E9%AA%A4%2F</url>
<content type="text"><![CDATA[abstract 加载JDBC驱动类(以mysql驱动为例)JDBC定义了一套接口,数据库产品的提供商会实现这些接口来提供自己的数据库驱动程序,这是个很好的面向接口编程的实例,想要替换数据库的时候只需要替换驱动程序就可以了(这里暂不考虑不同数据库之间的数据类型和SQL语法的差异) 1234567try { // 加载MySql的驱动类 Class.forName("com.mysql.jdbc.Driver"); } catch(ClassNotFoundException e) { System.out.println("找不到驱动程序类 ,加载驱动失败!"); e.printStackTrace(); } 加载驱动类的时候具体执行了哪些操作呢? Class.froName()方法源码 123456@CallerSensitivepublic static Class<?> forName(String className) throws ClassNotFoundException { Class<?> caller = Reflection.getCallerClass(); return forName0(className, true, ClassLoader.getClassLoader(caller), caller);} Class.forName0()方法源码 12345/** Called after security check for system loader access checks have been made. */private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) throws ClassNotFoundException; java7官方文档中对该方法的描述 Returns the Class object associated with the class or interface with the given string name, using the given class loader. Given the fully qualified name for a class or interface (in the same format returned by getName) this method attempts to locate, load, and link the class or interface. The specified class loader is used to load the class or interface. If the parameter loader is null, the class is loaded through the bootstrap class loader. The class is initialized only if the initialize parameter is true and if it has not been initialized earlier. 返回一个给定类或者接口的一个 Class 对象,如果没有给定类加载器, 那么会使用根类加载器。如果 initalize 这个参数传了 true,那么给定的类如果之前没有被初始化过,那么会被初始化。 再回过头来看看com.mysql.jdbc.Driver类 123456789101112public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } }} 刚才说到类会被初始化,类初始化的时候静态方法会被执行。即DriverManager.registerDriver(new Driver());,也就是说一开始我们加载驱动的时候执行DriverManager.registerDriver(new Driver());也是一样的。 JDBC4.0以后就不用再手动加载数据库驱动了,在DriverManager.loadInitialFrivers()方法中有这么一段代码 12345678910String drivers;try { drivers = AccessController.doPrivileged(new PrivilegedAction<String>() { public String run() { return System.getProperty("jdbc.drivers"); } });} catch (Exception ex) { drivers = null;} 他会检查系统配置中名为jdbc.drivers的配置参数,并找到对应的数据库驱动。 拼接JDBC连接URL连接URL格式如下 1jdbc:mysql://[host:port],[host:port].../[database][?参数名1][=参数值1][&参数名2][=参数值2]... 例如: 12345jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=gbk;# useUnicode=true:表示使用Unicode字符集。# 如果characterEncoding设置为gb2312或GBK,本参数必须设置为true。# characterEncoding=gbk:字符编码方式。 创建数据库连接1234567891011121314要连接数据库,需要向java.sql.DriverManager请求并获得Connection对象该对象就代表着一个数据库的连接使用DriverManager的getConnectin(String url, String username, String password)方法传入指定的连接的数据库的路径、数据库的用户名和密码来获得。// 连接MySql数据库,用户名和密码都是root String url = "jdbc:mysql://localhost:3306/test"; String username = "root"; String password = "root"; try { Connection con = DriverManager.getConnection(url , username , password); } catch(SQLException se) { System.out.println("数据库连接失败!"); se.printStackTrace(); } 创建Statement实例12345678要执行SQL语句,必须获得java.sql.Statement实例,Statement实例分为以下3种类型: 1、执行静态SQL语句。通常通过Statement实例实现。 2、执行动态SQL语句。通常通过PreparedStatement实例实现。 3、执行数据库存储过程。通常通过CallableStatement实例实现。 具体的实现方式: Statement stmt = con.createStatement(); PreparedStatement pstmt = con.prepareStatement(sql); CallableStatement cstmt = con.prepareCall("{CALL demoSp(? , ?)}"); 执行SQL语句…… 操作完数据库后关闭连接12345678910111213141516171819202122232425操作完成以后要把所有使用的JDBC对象全都关闭,以释放JDBC资源,连接操作如果是入栈顺序的话,关闭连接的顺序那就是出栈顺序: 1、关闭记录集 2、关闭声明 3、关闭连接对象 if (rs != null) { // 关闭记录集 try { rs.close(); } catch(SQLException e) { e.printStackTrace(); } } if (stmt != null) { // 关闭声明 try { stmt.close(); } catch(SQLException e) { e.printStackTrace(); } } if (conn != null) { // 关闭连接对象 try { conn.close(); } catch(SQLException e) { e.printStackTrace(); } }]]></content>
</entry>
<entry>
<title><![CDATA[数据库-连接数相关命令]]></title>
<url>%2F2019%2F07%2F15%2F%E6%95%B0%E6%8D%AE%E5%BA%93-%E8%BF%9E%E6%8E%A5%E6%95%B0%E7%9B%B8%E5%85%B3%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[abstract 连接池 连接 最大连接数 查看mysql最大连接数1SHOW VARIABLES LIKE 'max_connections'; 设置mysql最大连接数如果有统计连接池使用情况的日志最好,通过日志统计出平均值,峰值。然后基于平均值再高一些设置初始连接数,基于峰值再高一些设置最大连接数。 设置mysql最大链接数的方式有两种,一种是配置文件设置,一种是数据库设置。 配置文件设置修改mysql的配置文件中max_connections项 1max_connections=1000 修改设置需要重启数据库才能生效。 命令设置命令行修改最大连接数 1set global max_connections = 1000 常见数据库状态命令 查看线程 123456# 查看当前数据库的连接状态show status like 'Threads%';# threads_cached:缓存的连接# threads_connected:建立的连接数量# threads_created:创建过的线程数量# threads_running:激活的连接数量 | Variable | Value | | —————– | ———- | | Threads_cached | 0 | | Threads_connected | 86 | | Threads_created | 2226660253 | | Threads_running | 4 | 查看当前连接状态 12#root用户可以查看所有正在连接的信息show processlist; 查看数据库运行状态 12# 查看数据库运行状态show status 返回结果相当丰富。 参数详解见官方文档。]]></content>
<categories>
<category>数据库</category>
</categories>
<tags>
<tag>数据库</tag>
<tag>连接数</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Redis-基础]]></title>
<url>%2F2019%2F07%2F11%2FRedis-%E5%9F%BA%E7%A1%80%2F</url>
<content type="text"><![CDATA[你在别的地方也能看到的Redis学习笔记。 Redis老生常谈 Redis是由C语言编写,支持网络交互,基于内存也可以持久化的Key-Value(非关系型数据库)。 其实我就知道啥叫C语言,其他两个描述我搜了一下。 redis服务器与客户端交互方式redi处理客户端的流程,通过TCP请求或者Unix socket【①】建立一个socket链接,然后检查最大连接数,再进行内部处理。 先建立链接,再告诉超过最大连接数,再返回错误,断开连接。 服务端处理多个客户端命令的顺序 socket号的大小 kernal报告事件的先后顺序 ①:UNIX Domain SOCKET 是在Socket架构上发展起来的用于同一台主机的进程间通讯(IPC)。它不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序列号应答等。只是将应用层数据从一个进程拷贝到另一个进程。 最大连接数输出缓冲区限制理解为港口运出商品,正常情况风调雨顺,政策可爱。 第一种情况,马上要提升关税了,港口疯狂向外运送商品,一个简单的命令返回的数据太大。 第二种情况是,海上天气不好,来往的船只少或者航速缓慢,但是有很多商品等待运出,在港口堆积,服务器朝客户端发送速率无法及时发送输出缓冲区的数据。 这两种情况都会导致占用内存过高,系统崩溃。redis采取的机制是限制输出缓冲区大小,和打黑除恶。 限制大小,当某一个客户端的缓冲区超过设定的最大值时,直接断开与该客户端的连接,贸易逆差,不和你玩。 打黑除恶,当某一个客户端长时间占用大空间也会断开连接。 实际情况 对普通客户端来说,限制为0,也就是不限制,因为普通客户端通常采用阻塞式的消息应答模式,如:发送请求,等待返回,再发请求,再等待返回。这种模式通常不会导致输出缓冲区的堆积膨胀。 对于 Pub/Sub 客户端来说,大小限制是32m,当输出缓冲区超过32m时,会关闭连接。持续性限制是,当客户端缓冲区大小持续60秒超过8m,也会导致连接关闭。 而对于 Slave 客户端来说,大小限制是256m,持续性限制是当客户端缓冲区大小持续60秒超过64m时,关闭连接。 输入缓冲区限制比较暴力,当客户端传输的请求大小超过1G时,服务端会直接关闭连接。 这种方式可以有效防止一些客户端或服务端 bug 导致的输入缓冲区过大的问题。 Client超时对当前的 Redis 版本来说,服务端默认是不会关闭长期空闲的客户端的。但是你可以修改默认配置来设置你希望的超时时间。比如客户端超过多长时间无交互,就直接关闭。同理,这也可以通过 CONFIG SET 命令或者修改 redis.conf 文件来配置。 值得注意的是,超时时间的设置,只对普通客户端起作用,对 Pub/Sub 客户端来说,长期空闲状态是正常的。 另外,实际的超时时间可能不会像设定的那样精确,这是因为 Redis 并不会采用计时器或者轮训遍历的方法来检测客户端超时,而是通过一种渐近式的方式来完成,每次检查一部分。所以导致的结果就是,可能你设置的超时时间是10s,但是真实执行的时间是超时12s后客户端才被关闭。 client list id=2 addr=10.117.146.21:55330 fd=6 name= age=1759348 idle=0 flags=S db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=replconf 解释为 addr: 客户端的TCP地址,包括IP和端口fd: 客户端连接 socket 对应的文件描述符句柄号name: 连接的名字,默认为空,可以通过 CLIENT SETNAME 设置age: 客户端存活的秒数idle: 客户端空闲的秒数flags: 客户端的类型 (N 表示普通客户端,更多类型见 http://redis.io/commands/client-list)omem: 输出缓冲区的大小cmd: 最后执行的命令名称 redis的优势 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) 支持丰富数据类型,支持string,list,set,sorted set,hash 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除 redis的应用场景缓存 (1) 对于一些要返回给前端数据的缓存,当有大量数据库sql操作时候,为了避免每次接口请求都要去查询数据库,可以把一些数据缓存到redis中,这样是直接从内存中获取数据,速度回增快很多。 (2) web端用户,用于登陆缓存session数据,登陆的一些信息存到session中,缓存到redis中 队列 redis中提供了list接口,这个list提供了lpush和rpop,这两个方法具有原子性,可以插入队列元素和弹出队列元素。 数据存储 redis是非关系型数据库,可以把redis直接用于数据存储,提供了增删改查等操作,因为redis有良好的硬盘持久化机制,redis数据就可以定期持久化到硬盘中,保证了redis数据的完整性和安全性。 redis锁实现防刷机制 redis锁可以处理并发问题,redis数据类型中有一个set类型,set类型在存储数据的时候是无序的,而且每个值是不一样的,不能重复,这样就可以快速的查找元素中某个值是否存在,精确的进行增加删除操作。]]></content>
</entry>
<entry>
<title><![CDATA[注解-常用注解]]></title>
<url>%2F2019%2F07%2F11%2F%E6%B3%A8%E8%A7%A3-%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3%2F</url>
<content type="text"><![CDATA[Java注解,做个笔记。 前两天学习Spring Boot整合Redis时候写的测试类中,地址栏输入请求后页面找不到。然后就查了一下,也开始了Java注解的学习之路。 123456789101112@Controller@RequestMapping(value = "/test")public class TestDBController { @Autowired private RedisUtils redisUtils; @RequestMapping(value = "/getRedis") public String getRedis(){ redisUtils.set("a",1); return redisUtils.get("a").toString(); }} 这要从@RequestMapping说起 @RequestMapping这个注解会将 HTTP 请求映射到Control的处理方法上. 123456789101112131415@RestController@RequestMapping("/home")public class IndexController{ @RequestMapping("/") public String get(){ return "methodName IndexController.get()" } @RequestMapping("/index") public String index(){ return "methodName IndexController.index()" } } /home请求由get()方法处理,返回methodName IndexController.get() /home/index请求由index()方法处理,返回methodName IndexController.index() 基本上是我接触到的第一个注解了。 还可以指定一个多个请求指向一个处理方法 12345678910@RestController@RequestMapping("/home")public class IndexController{ @RequestMapping(value = {"","/page","all"}) public String get(){ return "methodName IndexController.get()" }} /home,/home/page./home/all都由get()方法处理。 @RequestMapping还可以指定请求方式 123456789101112131415@RestController@RequestMapping("/home")public class IndexController{ @RequestMapping() public String get(){ return "methodName IndexController.get()" } @RequestMapping(method = RequestMethod.POST) public String post(){ return "methodName IndexController.post()" } } 默认的请求都是get,指定请求方式时跳转对应的处理方法处理方法,使用post请求/home时返回methodName IndexController.post(). 常用功能先到这里吧,但是我不禁陷入了沉思,还有很多用法,在现在的项目里用不到,我到底要不要整理,不整理,就感觉学的不透彻(虽然整理了还是会忘掉)。可是整理吧,就是个蚂蚁窝,可能把整个草原都翻一遍。时间太长,用不到就是在做无用功。希望有过这种经历,并有想法的可以通过右下角告诉我。 @RequsetParam123456789101112131415@RestController@RequestMapping("/home")public class IndexController{ @RequestMapping("/getParamByName") public String getParamByName(@RequestParam(value="name",defaultValue="2") String id){ return id; } @RequestMapping("/getParamById") public String getParamById(@RequestParam() String id){ return id; }} @RequestParam将请求参数绑定到处理方法的形参上。 /home/getParamByName?name=1,页面返回1,如果请求的时候没带参数将使用默认值2。 /home/getParamById?id=2,页面返回2.如果请求参数和形参同名情况下可以省略注解内的名称。其实,同名情况可以将@RequestParam都省略掉。 @ResponseBody@Responsebody 注解表示该方法的返回的结果直接写入 HTTP 响应正文(ResponseBody)中,一般在异步获取数据时使用,通常是在使用 @RequestMapping 后,返回值通常解析为跳转路径,加上@Responsebody后返回结果不会被解析为跳转路径,而是直接写入HTTP 响应正文中。 这就到了一开始说到了为什么类用了@Controller注释再用@RequestMapping返回字符串显示找不到页面的情况了。 可以给方法加上@ResponseBody或者将@Controller换成@RestController。 原来返回指定页面的也会因为使用了@RestController而显示路径名。 说道ResponseBody不说RequestBody有点对不住他 @RequestBodyGet请求没有请求体,要使用@RequestBody可以用post提交请求。 一个请求请求只有一个@RequestBody,一个请求可以有多个@RequestParam 123456@RequestMapping("/testRequestParam")public void requestParam(User user ,@RequestParam Integer userId, @RequestParam String userName,@RequestParam String password,@RequestParam String phone){ System.out.println("userId=" + userId + "userName=" + userName + "password=" + password + "phone="+ phone); System.out.println(user.toString());} userId=1userName=2password=3phone=4User(userId=1, userName=2, password=3, phone=4) GET使用x-www-form-urlencoded会报错 { “timestamp”: “2019-07-11T10:42:03.750+0000”, “status”: 400, “error”: “Bad Request”, “message”: “Required Integer parameter ‘userId’ is not present”, “path”: “/test/testRequestBody”} POST请求下@RequestParam获取使用x-www-form-urlencoded返回结果与GET请求使用form-data相同 但是如果想批量传参,就需要用到RequestBody 123456@RequestMapping("/testRequestBody") public void requestBody(@RequestBody List<User> userList){ for (User user :userList){ System.out.println(user.toString()); } } console: User(userId=1, userName=2, password=3, phone=4)User(userId=5, userName=6, password=7, phone=8) @Resource和@Autowiredspring扫描自动命名机制-将类的首字母小写当做name。 bean有两个重要属性,名称(id)和类型(class ) @Resource和@Autowired都是用来注入bean的。但两者有一些不同。 @Resource@Resource并不是Spring的默认注解,需要导入javax.annotation.Resource包使用。 @Resource有两个属性:name和type,可以注解属性和setter方法。 123456789101112131415public Class UserManager{ @Resource(name="userDao") private UserDao userDao; }public Class UserManager{ private UserDao userDao; @Resource(name="userDao") public void setUserDao(UserDap userDao){ this.UserDao = userDao; }} @Resource装配顺序: 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。 如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。 @Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。 @Autowired@Autowired只会按照type进行注入。 123456789101112131415public class UserManager { @Autowired private UserDao userDao;}public class UserManager { private UserDao userDao; @Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; }} @Autowired注入,默认要求被注入的bean是存在的,如果允许为null,则可以将它的required属性置为false 如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。 12345public class UserManager { @Autowired @Qualifier("userDao") private UserDao userDao; } @Autowired默认先按byType进行匹配,如果发现找到多个bean,则又按照byName方式进行匹配,如果还有多个,则报出异常。]]></content>
<categories>
<category>注解</category>
</categories>
<tags>
<tag>Java</tag>
<tag>注解</tag>
</tags>
</entry>
<entry>
<title><![CDATA[springBoot-Redis]]></title>
<url>%2F2019%2F07%2F10%2FspringBoot-Redis%2F</url>
<content type="text"><![CDATA[abstract 本文主要为了学习Redis相关知识,使用springBoot整合单例Redis来操作Redis数据库。 Redis安装Redis安装详见Redis官网。 因为懒,现在在Windows上把Redis装成服务,.msi傻瓜包下载地址。 如果需要修改启动配置,可以到安装路径下的redis.windows-service.conf中修改。 Redis可视化工具的话推荐RedisClient,jar包点击运行,该有的功能都有,界面如下。 SpringBoot整合Redis、Jedismaven依赖 123456789<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version></dependency> resources下合适位置新建配置文件redis.properties,等会引入自定义文件时要用到这个路径。 123456789101112131415161718redis.host=127.0.0.1redis.port=6379redis.password=nullredis.timeout=30000# 连接池最大连接数(使用负值表示没有限制)redis.maxTotal=30# 连接池中的最大空闲连接redis.maxIdle=10redis.numTestsPerEvictionRun=1024redis.timeBetweenEvictionRunsMillis=30000redis.minEvictableIdleTimeMillis=1800000redis.softMinEvictableIdleTimeMillis=10000# 连接池最大阻塞等待时间(使用负值表示没有限制)redis.maxWaitMillis=1500redis.testOnBorrow=trueredis.testWhileIdle=trueredis.blockWhenExhausted=false 使用自定义配置的方式加载redis.properties中的配置信息 这里要说一下jedis与redisTemplate的区别。 Jedis是Redis官方推荐的面向Java的操作Redis的客户端。 RedisTemplate是SpringDataRedis中对JedisApi的高度封装。 SpringDataRedis相对于Jedis来说可以方便地更换Redis的Java客户端,比Jedis多了自动管理连接池的特性,方便与其他Spring框架进行搭配使用如:SpringCache。 下文使用redisTemplate作为redis客户端 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108package com.bbdog.demo.properties;import com.bbdog.demo.utils.RedisUtils;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.PropertySource;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import org.springframework.stereotype.Component;import redis.clients.jedis.JedisPool;import redis.clients.jedis.JedisPoolConfig;@Component@Data@PropertySource(value = "classpath:config/redis.properties")@ConfigurationProperties(prefix = "redis")public class RedisConfig { private String host; private Integer port; private String password; private Integer timeout; private Integer maxTotal; private Integer maxIdle; private Integer numTestsPerEvictionRun; private Integer timeBetweenEvictionRunsMillis; private Integer minEvictableIdleTimeMillis; private Integer softMinEvictableIdleTimeMillis; private Integer maxWaitMillis; private Boolean testOnBorrow; private Boolean testWhileIdle; private Boolean blockWhenExhausted; /** * JedisPoolConfig 连接池 * @return */ @Bean public JedisPoolConfig jedisPoolConfig() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); // 最大空闲数 jedisPoolConfig.setMaxIdle(maxIdle); // 连接池的最大数据库连接数 jedisPoolConfig.setMaxTotal(maxTotal); // 最大建立连接等待时间 jedisPoolConfig.setMaxWaitMillis(maxWaitMillis); // 逐出连接的最小空闲时间 默认1800000毫秒(30分钟) jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); // 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3 jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun); // 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); // 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 jedisPoolConfig.setTestOnBorrow(testOnBorrow); // 在空闲时检查有效性, 默认false jedisPoolConfig.setTestWhileIdle(testWhileIdle); return jedisPoolConfig; } @Bean public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig){ RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName(host); redisStandaloneConfiguration.setPort(port); JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcf = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder(); jpcf.poolConfig(jedisPoolConfig); JedisClientConfiguration jedisClientConfiguration = jpcf.build(); return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration); } /** * 实例化 RedisTemplate 对象 * */ @Bean public RedisTemplate<String, Object> functionDomainRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); initDomainRedisTemplate(redisTemplate, redisConnectionFactory); return redisTemplate; } /** * 设置数据存入 redis 的序列化方式,并开启事务 * * @param redisTemplate * @param factory */ private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) { //如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String! redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 开启事务 redisTemplate.setEnableTransactionSupport(true); redisTemplate.setConnectionFactory(factory); } /** * 注入封装RedisTemplate */ @Bean(name = "redisUtils") public RedisUtils redisUtil(RedisTemplate<String, Object> redisTemplate) { RedisUtils redisUtils = new RedisUtils(); redisUtils.setRedisTemplate(redisTemplate); return redisUtils; }} RedisUtils.java 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527package com.bbdog.demo.utils;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.util.CollectionUtils;import java.util.List;import java.util.Map;import java.util.Set;import java.util.concurrent.TimeUnit;public class RedisUtils { private RedisTemplate<String, Object> redisTemplate; public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } //=============================common============================ /** * 指定缓存失效时间 * @param key 键 * @param time 时间(秒) * @return */ public boolean expire(String key,long time){ try { if(time>0){ redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key){ return redisTemplate.getExpire(key,TimeUnit.SECONDS); } /** * 判断key是否存在 * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key){ try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String ... key){ if(key!=null&&key.length>0){ if(key.length==1){ redisTemplate.delete(key[0]); }else{ redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } //============================String============================= /** * 普通缓存获取 * @param key 键 * @return 值 */ public Object get(String key){ return key==null?null:redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key,Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key,Object value,long time){ try { if(time>0){ redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); }else{ set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 递增 * @param key 键 * @param by 要增加几(大于0) * @return */ public long incr(String key, long delta){ if(delta<0){ throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * @param key 键 * @param by 要减少几(小于0) * @return */ public long decr(String key, long delta){ if(delta<0){ throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } //================================Map================================= /** * HashGet * @param key 键 不能为null * @param item 项 不能为null * @return 值 */ public Object hget(String key,String item){ return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * @param key 键 * @return 对应的多个键值 */ public Map<Object,Object> hmget(String key){ return redisTemplate.opsForHash().entries(key); } /** * HashSet * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map<String,Object> map){ try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashSet 并设置时间 * @param key 键 * @param map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map<String,Object> map, long time){ try { redisTemplate.opsForHash().putAll(key, map); if(time>0){ expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * @param key 键 * @param item 项 * @param value 值 * @return true 成功 false失败 */ public boolean hset(String key,String item,Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * @param key 键 * @param item 项 * @param value 值 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key,String item,Object value,long time) { try { redisTemplate.opsForHash().put(key, item, value); if(time>0){ expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除hash表中的值 * @param key 键 不能为null * @param item 项 可以使多个 不能为null */ public void hdel(String key, Object... item){ redisTemplate.opsForHash().delete(key,item); } /** * 判断hash表中是否有该项的值 * @param key 键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item){ return redisTemplate.opsForHash().hasKey(key, item); } /** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * @param key 键 * @param item 项 * @param by 要增加几(大于0) * @return */ public double hincr(String key, String item,double by){ return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * @param key 键 * @param item 项 * @param by 要减少记(小于0) * @return */ public double hdecr(String key, String item,double by){ return redisTemplate.opsForHash().increment(key, item,-by); } //============================set============================= /** * 根据key获取Set中的所有值 * @param key 键 * @return */ public Set<Object> sGet(String key){ try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根据value从一个set中查询,是否存在 * @param key 键 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key,Object value){ try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将数据放入set缓存 * @param key 键 * @param values 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object...values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 将set数据放入缓存 * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key,long time,Object...values) { try { Long count = redisTemplate.opsForSet().add(key, values); if(time>0) expire(key, time); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 获取set缓存的长度 * @param key 键 * @return */ public long sGetSetSize(String key){ try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 移除值为value的 * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object ...values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } //===============================list================================= /** * 获取list缓存的内容 * @param key 键 * @param start 开始 * @param end 结束 0 到 -1代表所有值 * @return */ public List<Object> lGet(String key, long start, long end){ try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取list缓存的长度 * @param key 键 * @return */ public long lGetListSize(String key){ try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 通过索引 获取list中的值 * @param key 键 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 * @return */ public Object lGetIndex(String key,long index){ try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据索引修改list中的某条数据 * @param key 键 * @param index 索引 * @param value 值 * @return */ public boolean lUpdateIndex(String key, long index,Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 移除N个值为value * @param key 键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */ public long lRemove(String key,long count,Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } }} testController.java 12345678910111213141516171819import com.bbdog.demo.utils.RedisUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.*;@RestController@RequestMapping(value = "/test")public class TestDBController { @Autowired private RedisUtils redisUtils; @RequestMapping(value = "/getRedis") public String getRedis(){ redisUtils.set("a",1); return redisUtils.get("a").toString(); }} RedisUtils:封装的Redis工具类;RedisUtils底层实际上是由redisTemplate对象操作redis; redisTemplate由RedisConfig.functionDomainRedisTemplate()实例化,并对数据存入进行序列化。 到这里为止Redis学习环境就大功告成了。]]></content>
<categories>
<category>springBoot</category>
</categories>
<tags>
<tag>springBoot</tag>
<tag>redis</tag>
<tag>Jedis</tag>
</tags>
</entry>
<entry>
<title><![CDATA[网络-计算机网络基础]]></title>
<url>%2F2019%2F07%2F08%2F%E7%BD%91%E7%BB%9C-%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80%2F</url>
<content type="text"><![CDATA[abstract 计算机网络基础之前上学时候感觉网络学的挺透彻,毕业一年细节就忘得差不多,只能想起些专有名词。所以从网络上,教材上搜罗些网络基础来加深记忆。 网络层次划分国际标准化组织(ISO)制定了OSI七层网络模型。从下到上分别是:{(物理层)、(数据链路层)、(网络层)、(传输层)、(会话层、表示层、应用层)} 每一层接受下层服务并为上层提供服务,上三层面向用户。在七层模型基础上又有TCP/IP四、五层模型。 物理层 主要功能:利用传输介质为数据链路层提供物理连接,实现比特流的透明传输。 设备:调制调解器、集线器 数据链路层 主要功能:数据成帧,链路接入,流量控制,差错检测等 如何将数据组合成数据块,在数据链路层中称这种数据块为帧(frame),帧是数据链路层的传送单位; 如何控制帧在物理信道上的传输,包括如何处理传输差错,如何调节发送速率以使与接收方相匹配; 以及在两个网络实体之间提供数据链路通路的建立、维持和释放的管理。 数据链路层在不可靠的物理介质上提供可靠的传输。 设备:网路适配器(网卡)、交换机 网络层 数据链路层是解决同一网络内节点之间的通信,而网络层主要解决不同子网间的通信。 主要功能:路径选择、路由及逻辑寻址; 主要设备:路由器 主要协议: ARP协议(Address Resolution Protocol,地址解析协议); IP协议(Internet Protocol,因特网互联协议); ICMP协议(Internet Control Message Protocol,因特网控制报文协议); RARP协议(Reverse Address Resolution Protocol,逆地址解析协议)。 传输层 根据通信子网的特性,最佳的利用网络资源,为两个端系统中主机到主机之间,提供建立、维护和取消传输连接的功能,负责端到端的可靠数据传输。在这一层,信息传送的协议数据单元称为段或报文。 主要功能:传输层负责将上层数据分段并提供端到端的、可靠的或不可靠的传输以及端到端的差错控制和流量控制问题; 设备:网关 主要协议: TCP协议(Transmission Control Protocol,传输控制协议) UDP协议(User Datagram Protocol,用户数据报协议) 应用层HTTP协议(Hyper Text Transfer Protocol) 会话层管理主机之间的会话进程,即负责建立、管理、终止进程之间的会话。 表示层对上层数据或信息进行变换以保证一个主机应用层信息可以被另一个主机的应用程序理解。表示层的数据转换包括数据的加密、压缩、格式转换等。 为操作系统或网络应用程序提供访问网络服务的接口。 主要协议: FTP(文件传送协议) Telnet(远程登录协议) DNS(域名解析协议) SMTP(邮件传送协议) POP3协议(邮局协议) HTTP协议(Hyper Text Transfer Protocol) 模型中的协议: 传输内容: IP地址IP地址由网络号和主机号组成。分为A、B、C、D、E五类地址;具体划分如下: 除了这五类地址还有一些特殊地址: 网络地址:主机号全0表示当前网络 广播地址:主机号全1表示当前网络广播地址(也称直接广播地址) 0.0.0.0: 255.255.255.255:该IP地址指的是受限的广播地址。受限广播地址与一般广播地址(直接广播地址)的区别在于,受限广播地址只能用于本地网络,路由器不会转发以受限广播地址为目的地址的分组;一般广播地址既可在本地广播,也可跨网段广播。例如:主机192.168.1.1/30上的直接广播数据包后,另外一个网段192.168.1.5/30也能收到该数据报;若发送受限广播数据报,则不能收到。 注:一般的广播地址(直接广播地址)能够通过某些路由器(当然不是所有的路由器),而受限的广播地址不能通过路由器。 回环地址:127.0.0.0/8被用作回环地址,回环地址表示本机的地址,常用于对本机的测试,用的最多的是127.0.0.1。 私有地址: A类私有地址:10.0.0.0/8,范围是:10.0.0.0~10.255.255.255 B类私有地址:172.16.0.0/12,范围是:172.16.0.0~172.31.255.255 C类私有地址:192.168.0.0/16,范围是:192.168.0.0~192.168.255.255 子网划分IP/ICMP协议IP协议IP协议是将多个包交换网络连接起来,它在源地址和目的地址之间传送一种称之为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包大小的要求。 IP不提供可靠的传输服务,它不提供端到端的或(路由)结点到(路由)结点的确认,对数据没有差错控制,它只使用报头的校验码,它不提供重发和流量控制。如果出错可以通过ICMP报告,ICMP在IP模块中实现。 ICMP协议ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。 常用形式有两种:查询报文、控制报文。 查询报文 查询端获取请求,并获取对方相应。如ping命令 ping命令相当于在原生ICMP报文中增加时间和序号,根据返回相应的顺序和时间来增加准确性。 控制报文 源抑制报文 发送端发送大量数据时,可能会导致网络( 路由器 )过载,此时过载处可以向发送端发送源抑制的消息,让他降低发送速度。 终点不可到达网络信息不能到达终点,就会给发送端发送一个目的不可到达的信息。告诉发送端可能是设备故障而引起关闭(情况之一)。然后这种又可以再次细分:A、网络不可达 — 代码为 0,B、主机不可达 — 代码为 1.C、协议不可达 — 代码为 2.D、端口不可达 — 代码为 3.E、需要分段 - 代码为 4.( 必须把数据分段才能去到终点 ) 超时网络包超过设置的在网络中的生存时间,还没有达到。 路由重定向 定义数据包的路由股则。因为大部分的时候,路由规则是通过相关协议算法生成的,有些时候重新定义过之后,会让这个数据包绕的更远。 相见本文traceroute命令(windows系统为tracert命令) ARP/RARP协议ARP协议流程地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。 首先,每个主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址之间的对应关系。 当源主机要发送数据时,首先检查ARP列表中是否有对应IP地址的目的主机的MAC地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送ARP数据包,该数据包包括的内容有:源主机 IP地址,源主机MAC地址,目的主机的IP 地址 当本网络的所有主机收到该ARP数据包时,首先检查数据包中的IP地址是否是自己的IP地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的IP和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址。 源主机收到ARP响应包后。将目的主机的IP和MAC地址写入ARP列表,并利用此信息发送数据。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。 注意:广播(255.255.255.255)发送ARP请求,单播发送ARP响应。 RARP协议流程RARP协议是根据mac地址获取IP地址的一个协议 发送端发送一个本地的RARP广播包,在此广播包中声明自己的MAC地址,并且请求任何收到此请求的RARP服务器分配一个IP地址。 本地网段上的RARP服务器收到此请求后,检查其RARP列表,查找该MAC地址对应的IP地址。如果存在,RARP服务器就给源主机发送一个响应数据包,并将此IP地址提供给对方主机使用;如果不存在,RARP服务器对此不做任何响应。 源端在收到从RARP服务器来的响应信息后,利用得到的IP地址进行通信;如果一直没有收到RARP服务器的响应信息,则表示初始化失败。 路由选择协议上学时学到的路由选择协议有RIP协议、OSPF协议 RIP协议: OSPF协议: TCP/IP协议TCP/IP协议是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。 歪,你在吗(甲方不知道乙方在不在) 嗯嗯,我在(乙方知道甲方在找自己,乙方不知道甲方能不能接收到回应)【甲方确认乙方】 在那我就开始说了(甲方知道乙方在,并且告诉乙方收到了他的回应)【乙方确认甲方】 双方确认的基础上进行可靠连接。 我听到一个消息,巴拉 嗯嗯,你继续说 巴拉巴拉 嗯嗯,你继续说 巴拉巴拉巴拉 ??? 怎么不回了?巴拉巴拉巴拉 连接意外断开情况 为了防止漏掉信息,结束对话更是谨慎 甲:我没有要说的了(停止发送数据) 已: 嗯嗯(我收了你不发信息的通知了)【已方知道甲方不发消息了】 已: 我也没有要说的了(停止发送数据) 甲: 拜拜【甲方也知道乙方不再发消息了】 由此可见,TCP协议是建立在双方完全确认的情况下进行的计算机程序之间的端到端的连接。 UDP协议ip协议是不可靠的,邮局到邮局的传输,UDP则像是对IP协议的接口话,实现了,邮筒到信箱之间的不可靠传输。 ping命令TTL : 数据包在网络中生存时间,也就是通过数据被路由器转发的次数,没转发一次就减一。知道为 0 的时候就抛弃。 windowns系统只会请求4次,linux系统会一直请求。 因为是基础,暂不研究详细参数。 traceroute命令traceoute 有点像是在不断试错的意思,windows系统为tracert命令。是用来侦测主机到目的主机之间所经路由情况的重要工具。Ping 我们知道是可以知道这条大路通不通的,traceoute 通过设置 TTL 知道到底是哪个路由器不通。 traceroute 的原理:它收到目的主机的 IP 后,首先给目的主机发送一个 TTL=1 的 UDP 数据包,而经过的第一个路由器收到这个数据包以后,就自动把 TTL 减 1,而 TTL 变为 0 以后,路由器就把这个包给抛弃了,并同时产生 一个主机不可达的 ICMP 数据报给主机。如此循环就可以知道所有的路由 IP 了。( ICMP 出错了就会回错误包 )通常 Traceoute 的目的端口设置的是一个大于 30000 的值( 一般的应用端口号远小于这个数 )。所以如果回复的是 “端口不可达”,那就说明到达终点,否则这个信息就会超时。以此确保 消息是否到达终点。 traceoute 还有一个有意思的功能,就是确定 MTU(数据最大传输单元),traceoute 通常对数据不分段,就直接发送,如果如果遇到过程中某个路由转发,出现返回 ICMP 需要分段的错误,就把数据进行拆分,直到最后到达终点。就验证出 MTU。]]></content>
<categories>
<category>网络</category>
</categories>
<tags>
<tag>网络</tag>
<tag>协议</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Cookie及Session]]></title>
<url>%2F2019%2F04%2F05%2FCookie%E5%8F%8ASession%2F</url>
<content type="text"><![CDATA[用户访问服务器,如果要区分不同用户的操作,行为等,比如在登陆是勾选记住我,下次就可以不需要登陆直接访问。区分不同的用户,现在客户端用到最多的就是Cookies和Session.Cookie和Session同样可以用来存储用户标识,但他们的存储地点,生存周期,等有很大的区别,常将两者连用。这里会把基础的特征详细的讲一下,扩展讲一下他们的安全性。为下一篇的单点登陆做铺垫。 Cookie由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。 Cookie是服务器发送给浏览器的特殊信息(需要浏览器支持Cookie),以文本方式存储在本地,一般是存储在[系统盘]:\Documents and Settings[用户名]\Cookies目录. 当浏览器向服务器发送请求时,会在请求头中携带Cookie信息。服务器接收到请求后对Cookie中的信息进行识别,操作。最后和返回信息一起返回来。 Cookie的不可跨域性每个网站都会颁发自己的Cookie,而每个网站只希望浏览器请求的时候带上的是自己颁发的Cookie.Cookie规范中的不可跨域性就保证了每个网站只能接受自己颁发的Cookies,Google接收不到alibaba的Cookie,Alibaba接收不到baidu的Cookie。 编码规则如果是Unicode的字符,需要指定编码,一般使用UTF-8,如果是二进制数据,应该编码应指定为base64,英文默认使用两字节的ASCII编码 Cookie的属性 String name: 该Cookie的名称。Cookie一旦创建,名称便不可更改. Object value:该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码。 int maxAge:该Cookie失效的时间,单位秒。如果为正数,则该Cookie在>maxAge秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为–1。(这里有很多坑) boolean secure:该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。 String path:该Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”。 String domain:可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”。 String comment:该Cookie的用处说明。浏览器显示Cookie信息的时候显示该说明。 int version:该Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范。 Cookie的有效期Cookie的maxAge决定着Cookie的有效期,单位为秒(Second)。Cookie中通过getMaxAge()方法与setMaxAge(int maxAge)方法来读写maxAge属性。 如果为正数,则表示该Cookie会在maxAge秒之后自动失效,并会进行持久化,关闭浏览器仍在有效期内。还可以这样设置为永久有效。 123Cookie cookie = new Cookie("username","bbdogUser1"); // 新建Cookiecookie.setMaxAge(Integer.MAX_VALUE); // 设置生命周期为MAX_VALUEresponse.addCookie(cookie); // 输出到客户端 如果maxAge为负数,则表示该Cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该Cookie即失效。maxAge为负数的Cookie,为临时性Cookie,不会被持久化,不会被写到Cookie文件中。Cookie信息保存在浏览器内存中,因此关闭浏览器该Cookie就消失了。Cookie默认的maxAge值为–1。 如果maxAge为0,则表示删除该Cookie。Cookie机制没有提供删除Cookie的方法,因此通过设置该Cookie即时失效实现删除Cookie的效果。失效的Cookie会被浏览器从Cookie文件或者内存中删除: 123Cookie cookie = new Cookie("username","bbdogUser1"); // 新建Cookiecookie.setMaxAge(0); // 设置生命周期为0,不能为负数response.addCookie(cookie); // 必须执行这一句 注意:从客户端读取Cookie时,包括maxAge在内的其他属性都是不可读的,也不会被提交。浏览器提交Cookie时只会提交name与value属性。maxAge属性只被浏览器用来判断Cookie是否过期。 Cookie的添加和删除Cookie并不提供修改、删除操作。如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response中覆盖原来的Cookie。如果要删除某个Cookie,只需要新建一个同名的Cookie,并将maxAge设置为0,并添加到response中覆盖原来的Cookie。注意是0而不是负数。负数代表其他的意义。读者可以通过上例的程序进行验证,设置不同的属性。 注意:修改、删除Cookie时,新建的Cookie除value、maxAge之外的所有属性,例如name、path、domain等,都要与原Cookie完全一样。否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。 Cookie的域名Cookie是不可跨域名的。域名www.google.com颁发的Cookie不会被提交到域名www.baidu.com去。这是由Cookie的隐私安全机制决定的。隐私安全机制能够禁止网站非法获取其他网站的Cookie。 正常情况下,同一个一级域名下的两个二级域名如www.bbdog.com和images.bbdog.com也不能交互使用Cookie,因为二者的域名并不严格相同。如果想所有bbdog.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数,例如:12345Cookie cookie = new Cookie("time","20000000"); // 新建Cookiecookie.setDomain(".bbdog.com"); // 设置域名cookie.setPath("/"); // 设置路径cookie.setMaxAge(Integer.MAX_VALUE); // 设置有效期response.addCookie(cookie); // 输出到客户端 读者可以修改本机C:\WINDOWS\system32\drivers\etc下的hosts文件来配置多个临时域名,然后使用setCookie.jsp程序来设置跨域名Cookie验证domain属性。 注意:domain参数必须以点(“.”)开始。另外,name相同但domain不同的两个Cookie是两个不同的Cookie。如果想要两个域名完全不同的网站共有Cookie,可以生成两个Cookie,domain属性分别为两个域名,输出到客户端。 Cookie的路径Cookie的路径是值浏览器访问服务器资源的路径. 那么Cookie的path是干什么的呢?假设你的浏览器当前已经有了两个Cookie: Cookie1:name=id; value=itcast; path=/day07_03/; Cookie2:name=name; value=qdmmy6; path=/day07_03/servlet/。 当访问http://localhost/day07_03/时,请求头中会包含c1,而不会包含c2。 当访问http://localhost/day07_03/servlet/时,请求头中会包含c1和c2。 注意: 如果服务器相应的时候没有特意设置路径,则会默认返回。例如在请求http://localhost/day07_03/AServlet时,服务器响应了一个Cookie,那么这个Cookie的默认路径就是/day07_03/。 Cookie的安全性HTTP协议不仅是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure属性为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。下面的代码设置secure属性为true: 123Cookie cookie = new Cookie("time", "20000000"); // 新建Cookiecookie.setSecure(true); // 设置安全属性response.addCookie(cookie); // 输出到客户端 提示:secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。 SessionSession可以理解为会话的意思,会话可以理解为客户端与服务器的信息交流.Cookie是将客户信息储存在客户端本地,而Session则是将客户信息存储在服务器。通过Cookie识别了用户身份,再通过Cookie中记录的SessionId来获取用户的身份明细。Session保存在服务器端,因为使用频繁,早期常将直接保存在内存中,但用户量过大时,存储压力增大,还有可能造成内存溢出。现在常将其保存在高读写的数据库中,如redis,并根据实际情况进行分布式处理。 Session生命周期Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。如果尚未生成Session,也可以使用request.getSession(true)强制生成Session。 Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session“活跃(active)”了一次。 Session的有效期由于会有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。 Session的超时时间为maxInactiveInterval属性,可以通过对应的getMaxInactiveInterval()获取,通过setMaxInactiveInterval(longinterval)修改。 Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以使Session失效。 Session的常用方法Session中包括各种方法,使用起来要比Cookie方便得多。Session的常用方法如下所示。 void setAttribute(String attribute, Object value):设置Session属性。value参数可以为任何Java Object。通常为Java Bean。value信息不宜过大 String getAttribute(String attribute):返回Session属性 Enumeration getAttributeNames():返回Session中存在的属性名 void removeAttribute(String attribute):移除Session属性 String getId():返回Session的ID。该ID由服务器自动创建,不会重复 long getCreationTime():返回Session的创建日期。返回类型为long,常被转化为Date类型,例如:Date createTime = new Date(session.get >CreationTime()) long getLastAccessedTime():返回Session的最后活跃时间。返回类型为long int getMaxInactiveInterval():返回Session的超时时间。单位为秒。超过该时间没有访问,服务器认为该Session失效 void setMaxInactiveInterval(int second):设置Session的超时时间。单位为秒。 void putValue(String attribute, Object value):不推荐的方法。已经被setAttribute(String attribute, Object Value)替代 Object getValue(String attribute):不被推荐的方法。已经被getAttribute(String attr)替代 boolean isNew():返回该Session是否是新创建的 void invalidate():使该Session失效 Session生存时间设置Tomcat中设置的默认时间时20分钟,可以在web.xml中修改。单位为分钟123<session-config> <session-timeout>60</session-timeout> <!-- 单位:分钟 --></session-config> 也可以通过setMaxInactiveInterval(int seconds)方法修改。单位为秒 session对浏览器的支持支持cookie因为sessionId记在cookie中,所以使用的时候相对简单。但是需要注意的是,新打开tab页签的时候,共用的当前浏览器窗口的session。但是新打开浏览器窗口的时候,就会新建session. 不支持Cookie如果浏览器不支持cookie,就要使用其他方法来解决。常见的方法有URL重写,和页面重定向。HttpServletResponse类提供了encodeURL(Stringurl)实现URL地址重写。1<a href="<%=response.encodeURL("index.jsp?c=1&wd=Java") %>">Homepage</a> 该方法会自动判断客户端是否支持Cookie。如果客户端支持Cookie,会将URL原封不动地输出来。如果客户端不支持Cookie,则会将用户Session的id重写到URL中。重写后的输出可能是这样的: 1<a href="index.jsp;jsessionid=0CCD096E7F8D97B0BE608AFDC3E1931E?c=1&wd=Java">Homepage</a> 即在文件名的后面,在URL参数的前面添加了字符串“;jsessionid=XXX”。其中XXX为Session的id。分析一下可以知道,增添的jsessionid字符串既不会影响请求的文件名,也不会影响提交的地址栏参数。用户单击这个链接的时候会把Session的id通过URL提交到服务器上,服务器通过解析URL地址获得Session的id。页面重定向可通过如下实现:1234if(“administrator”.equals(userName)) { response.sendRedirect(response.encodeRedirectURL(“administrator.jsp”)); return;} 与encodeURL方法类似,他也会先进行cookie支持检测。 注意:TOMCAT判断客户端浏览器是否支持Cookie的依据是请求中是否含有Cookie。尽管客户端可能会支持Cookie,但是由于第一次请求时不会携带任何Cookie(因为并无任何Cookie可以携带),URL地址重写后的地址中仍然会带有jsessionid。当第二次访问时服务器已经在浏览器中写入Cookie了,因此URL地址重写后的地址中就不会带有jsessionid了。]]></content>
<categories>
<category>Web</category>
</categories>
<tags>
<tag>Cookie</tag>
<tag>Session</tag>
<tag>会话</tag>
</tags>
</entry>
<entry>
<title><![CDATA[maven-使用Nexus搭建私服]]></title>
<url>%2F2019%2F03%2F08%2Fmaven-%E4%BD%BF%E7%94%A8Nexus%E6%90%AD%E5%BB%BA%E7%A7%81%E6%9C%8D%2F</url>
<content type="text"><![CDATA[abstract 下载官方下载地址 安装选择合适的位置解压压缩包 这就是Nexus的主要安装目录 当然也可以安装成服务 安装失败,归其原因是因为命令行权限不够,应该使用管理员角色启动命令行 配置主要的几个配置文件路径 nexus-3.15.2-01-win64\nexus-3.15.2-01\bin\nexus.vmoptions nexus-3.15.2-01-win64\nexus-3.15.2-01\etc\nexus-default.properties 例如修改nexus-default.properties中的启动端口1application-port=8686 ##启动 浏览器输入你配置的地址查看Nexus首页]]></content>
<categories>
<category>maven</category>
</categories>
<tags>
<tag>maven</tag>
<tag>Nexus</tag>
<tag>私服</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Mybatis-缓存]]></title>
<url>%2F2019%2F03%2F01%2FMybatis-%E7%BC%93%E5%AD%98%2F</url>
<content type="text"><![CDATA[abstract 缓存的作用对于变动不频繁的数据,我们通常使用缓存的方式来将数据存起来,从而减少程序对数据库的访问次数,提升查找效率。 Mybatis一级缓存一级缓存是Sqlsession级别的。默认开启。一个Sqlsession对象代表一次数据库会话。一次会话中有可能频繁执行相同查询语句,往往结果短时间内不会变动,对于这种不会变动的数据。Sqlsession会把每次查询的结果放在本地缓存(local cache),当发现相同查询时,直接从本地缓存中取。减少了对数据库的io和数据库的查询。 Mybatis一级缓存相关接口类SqlSession将它的工作交给了Executor执行器这个角色来完成,负责完成对数据库的各种操作。缓存信息也由Executor执行器维护。 MyBatis将缓存和对缓存相关的操作封装成了Cache接口中。SqlSession、Executor、Cache之间的关系如下列类图所示: BaseExecutor类实现了Executor接口 PerpetualCache类实现了Cache接口 BaseExecutor类的queryFromDatabase等方法可以操纵PerpetualCache类。从而实现了对缓存的操控。 PerpetualCache实现原理其实很简单,其内部就是通过一个简单的HashMap<k,v> 来实现的,没有其他的任何限制。如下是PerpetualCache的实现代码: MyBatis一级缓存工作流程 对于某个查询,根据statementId,params,rowBounds等来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果; 判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中; 如果命中,则直接将缓存结果返回; 如果没命中: 去数据库中查询数据,得到查询结果; 将key和查询到的结果分别作为key,value对存储到Cache中; 将查询结果返回; 如何判断两次查询是相同的呢结论 判断[statementId + rowBounds + 传递给JDBC的SQL + 传递给JDBC的参数值]组成的CacheKey是否相同。 判断依据 传入的statementId,对于MyBatis而言,你要使用它,必须需要一个statementId,它代表着你将执行什么样的Sql; MyBatis自身提供的分页功能是通过RowBounds来实现的,它通过rowBounds.offset和rowBounds.limit来过滤查询出来的结果集,这种分页功能是基于查询结果的再过滤,而不是进行数据库的物理分页; 由于MyBatis底层还是依赖于JDBC实现的,那么,对于两次完全一模一样的查询,MyBatis要保证对于底层JDBC而言,也是完全一致的查询才行。而对于JDBC而言,两次查询,只要传入给JDBC的SQL语句完全一致,传入的参数也完全一致,就认为是两次查询是完全一致的。 上述的第3个条件正是要求保证传递给JDBC的SQL语句完全一致;第4条则是保证传递给JDBC的参数也完全一致;MyBatis会将上述的SQL中的#{} 转化成?,通过JDBC的PreparedStatement的参数值进行替换,如果参数值相同,则满足自四条Mybatis一级缓存的生命周期生成 随Sqlsession的创建而创建 消亡 SqlSession的close()方法会释放缓存,缓存不可用 SqlSession的clearCache()方法会清空缓存中的内容 SqlSession执行update()、delete()、insert()方法时,会清空缓存中的内容 Mybatis一级缓存注意事项对于数据更新频繁的SQL,应该注意Sqlsession的生存时间,或是再mapper文件中禁用一级缓存 Mybatis一级缓存扩展PerpetualCache类 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081/* * Copyright 2009-2012 The MyBatis Team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.apache.ibatis.cache.impl;import java.util.HashMap;import java.util.Map;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;import org.apache.ibatis.cache.Cache;import org.apache.ibatis.cache.CacheException;public class PerpetualCache implements Cache { private String id; private Map<Object, Object> cache = new HashMap<Object, Object>(); private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public PerpetualCache(String id) { this.id = id; } public String getId() { return id; } public int getSize() { return cache.size(); } public void putObject(Object key, Object value) { cache.put(key, value); } public Object getObject(Object key) { return cache.get(key); } public Object removeObject(Object key) { return cache.remove(key); } public void clear() { cache.clear(); } public ReadWriteLock getReadWriteLock() { return readWriteLock; } public boolean equals(Object o) { if (getId() == null) throw new CacheException("Cache instances require an ID."); if (this == o) return true; if (!(o instanceof Cache)) return false; Cache otherCache = (Cache) o; return getId().equals(otherCache.getId()); } public int hashCode() { if (getId() == null) throw new CacheException("Cache instances require an ID."); return getId().hashCode(); }} BaseExecutor类的queryFromDatabase方法 1234567891011121314private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list;} Mybatis二级缓存二级缓存是mapper级别的。]]></content>
<categories>
<category>Mybatis</category>
</categories>
<tags>
<tag>Mybatis</tag>
<tag>缓存</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据库-多表关联]]></title>
<url>%2F2019%2F02%2F20%2F%E6%95%B0%E6%8D%AE%E5%BA%93-%E5%A4%9A%E8%A1%A8%E5%85%B3%E8%81%94%2F</url>
<content type="text"><![CDATA[abstract]]></content>
</entry>
<entry>
<title><![CDATA[Git-常用操作]]></title>
<url>%2F2019%2F01%2F28%2FGit-%E5%B8%B8%E7%94%A8%E6%93%8D%E4%BD%9C%2F</url>
<content type="text"><![CDATA[abstract 设置用户信息设置用户名、邮箱全局通用用户信息,本机上所有的Git仓库都是使用这个账号。123git config --global user.name "userName"git config --global user.email "userEmail" 也可以针对某个仓库指定用户名和邮箱 仓库相关初始化仓库使用初始化仓库命令可以在一个打算作为仓库的目录中生成一个.git目录,这个目录就是用作版本控制的文件目录。 1git init 将文件从工作区添加到暂存区1git add 文件名 查看文件提交状态1git status 这个命令会比对仓库与暂存区文件差异,并将差异展现出来。 查看差异1git diff 文件名 将会展现Unix中diff差异格式。 将文件从暂存区提交到仓库1git commit -m "更新说明" 1git commit -a -m "更新说明" 加上-a参数会执行 add + commit 命令 查看版本1git log 默认按更新日期从近到远展示三次提交记录。展示信息如下: commit 最近一次版本号 Author: 用户名<邮箱> Date: 日期 更新说明 commit 上上次版本号 Author: 用户名<邮箱> Date: 日期 更新说明 也可以展示简略信息1git log –-pretty=oneline 展示信息如下: 版本号 更新说明 版本号 更新说明 版本号 更新说明 跳转到某一个版本1234567git reset --hard HEAD^``` 跳转到上一次,`HEAD^^`,挑战到上上次。`HEAD^^^`跳转到上上上次。那么问题来了,上一百次怎么复制粘贴最合理?但其实并不需要```bashgit reset --hard HEAD~100 回退之后后悔了怎么办。git log 也看不到版本号了鸭,这个时候操作1git reflog]]></content>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JavaIO-File中常用方法]]></title>
<url>%2F2019%2F01%2F25%2FJavaIO-File%E4%B8%AD%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95%2F</url>
<content type="text"><![CDATA[abstract ## 示例123456789101112131415161718192021222324252627282930313233343536373839404142public static void main(String[] args){ String fileName = "D:" + File.separator + "Files"; File file = new File(fileName); // 判断路径指向的文件/文件夹是否存在、是否目录 if (file.exists() && file.isDirectory()){ System.out.println("file是一个文件夹\n"); File[] files = file.listFiles(); // 获取目录下的所有文件/文件夹(仅该层路径下) System.out.print("路径下有文件:"); for (File f : files) { System.out.print(f + "\t"); } //信息 System.out.println("files[0]的文件名:" + files[0].getName()); // 获取文件名、文件夹名 System.out.println("files[0]的文件路径:" + files[0].getPath()); // 获取文件、文件夹路径 System.out.println("files[0]的绝对路径:" + files[0].getAbsolutePath()); // 获取文件、文件夹绝对路径 System.out.println("files[0]的父文件夹名:" + files[0].getParent()); // 获取文件父目录路径 System.out.println("files[0]的最后修改时间:" + files[0].lastModified()); // 获取文件、文件夹上一次修改时间 System.out.println("files[0]的大小:" + files[0].length() + " Bytes"); // 获取文件的字节数,如果是一个文件夹则这个值为0 System.out.println("files[0]的路径转换为URI:" + files[0].toURI()); // 获取文件路径URI后的路径名 //状态 System.out.println(files[0].exists() ? "files[0]的存在" : "files[0]的不存在"); // 判断文件、文件夹是否存在 System.out.println(files[0].canWrite() ? "files[0]的可写" : "files[0]的不可写"); // 判断文件是否可写 System.out.println(files[0].canRead() ? "files[0]的可读" : "files[0]的不可读"); // 判断文件是否可读 System.out.println(files[0].canExecute() ? "file[0]可执行" : "file[0]不可执行"); // 判断文件是否可执行 System.out.println(files[0].isDirectory() ? "files[0]的是目录" : "files[0]的不是目录"); // 判断文件、文件夹是不是目录 System.out.println(files[0].isFile() ? "files[0]的是文件" : "files[0]的不是文件"); // 判断拿文件、文件夹是不是标准文件 System.out.println(files[0].isAbsolute() ? "files[0]的路径名是绝对路径" : "files[0]的路径名不是绝对路径"); // 判断路径名是不是绝对路径 //操作 if (files[0].exists()) files[0].delete(); // 删除指定的文件、文件夹 if (files[1].exists()) files[1].deleteOnExit(); // 当虚拟机终止时删除指定的文件、文件夹 }} 输出结果: file是一个文件夹 路径下有文件:D:\Files\BBDog.txt files[0]的文件名:BBDog.txt files[0]的文件路径:D:\Files\BBDog.txt files[0]的绝对路径:D:\Files\BBDog.txt files[0]的父文件夹名:D:\Files files[0]的存在 files[0]的可写 files[0]的可读 file[0]可执行 files[0]的不是目录 files[0]的是文件 files[0]的路径名是绝对路径 files[0]的最后修改时间:1548408240577 files[0]的大小:6 Bytes files[0]的路径转换为URI:file:/D:/Files/BBDog.txt 说明指定文件名时推荐使用File.separator,来实现跨平台目录分隔符-程序会判断系统类型从而展现不同的分隔符。 删除操作必须目录下没有目录或文件]]></content>
<categories>
<category>Java IO</category>
</categories>
<tags>
<tag>File</tag>
<tag>文件流</tag>
</tags>
</entry>
<entry>
<title><![CDATA[尝试-尝试实现扫二维码登陆]]></title>
<url>%2F2019%2F01%2F24%2F%E5%B0%9D%E8%AF%95-%E5%B0%9D%E8%AF%95%E5%AE%9E%E7%8E%B0%E6%89%AB%E4%BA%8C%E7%BB%B4%E7%A0%81%E7%99%BB%E9%99%86%2F</url>
<content type="text"><![CDATA[TODO 方式一生成二维码要想二维码唯一,用于生成二维码的 长地址转短地址123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899import java.util.LinkedList;import java.util.List;import org.apache.http.NameValuePair;import org.apache.http.client.entity.UrlEncodedFormEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpPost;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.message.BasicNameValuePair;import org.apache.http.util.EntityUtils;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;/*** * 短链接转换工具类 * * @author Administrator * */public class ShortUrlHelper { public static CloseableHttpClient httpClient; static { httpClient = HttpClients.createDefault(); } /** * 将长链接转为短链接(调用的新浪的短网址API) * * @param url * 需要转换的长链接url * @return 返回转换后的短链接 */ public static String convertSinaShortUrl(String url) { try { // 调用新浪API HttpPost post = new HttpPost("http://api.t.sina.com.cn/short_url/shorten.json"); List<NameValuePair> params = new LinkedList<NameValuePair>(); // 必要的url长链接参数 params.add(new BasicNameValuePair("url_long", url)); // 必要的新浪key params.add(new BasicNameValuePair("source", "3271760578")); post.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); CloseableHttpResponse response = httpClient.execute(post); // 得到调用新浪API后成功返回的json字符串 // url_short : 短链接地址 type:类型 url_long:原始长链接地址 String json = EntityUtils.toString(response.getEntity(), "utf-8"); JSONArray jsonArray = JSONArray.parseArray(json); JSONObject object = (JSONObject) jsonArray.get(0); return object.getString("url_short"); } catch (Exception e) { e.printStackTrace(); return ""; } } /** * 将长链接转为短链接(调用的百度短网址API) * * @param url * 需要转换的长链接url * @return 返回转换后的短链接 */ public static String convertBaiDuShortUrl(String url) { try { // 调用百度API HttpPost post = new HttpPost("http://www.dwz.cn/create.php"); List<NameValuePair> params = new LinkedList<NameValuePair>(); // 必要的url长链接参数 params.add(new BasicNameValuePair("url", url)); post.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); CloseableHttpResponse response = httpClient.execute(post); // 得到调用百度API后成功返回的json字符串 // tinyurl : 短链接地址 status:0 表示转换成功 非0表示转换失败 longurl:原始长链接地址 err_msg:错误信息 String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8"); JSONObject object = JSON.parseObject(jsonStr); return object.getString("tinyurl"); } catch (Exception e) { e.printStackTrace(); return ""; } } /** * 测试 * @param args */ public static void main(String []args){ String tinyurl = convertBaiDuShortUrl("http://news.sina.com.cn/gov/xlxw/2018-09-05/doc-ihiixyeu3395739.shtml"); System.out.println(tinyurl); }} 轮询判断二维码状态(服务器请求压力) 未扫描 扫描成功 过时刷新 扫码uid绑定用户(额外库)稍后轮询会返回扫描成功 点击登陆方式二生成二维码扫码通知后台进行登陆 推送 未扫码,不推送 扫码成功,用户信息页 点击登陆]]></content>
<categories>
<category>尝试</category>
</categories>
<tags>
<tag>二维码</tag>
<tag>登陆</tag>
<tag>短地址</tag>
</tags>
</entry>
<entry>
<title><![CDATA[规范-特殊注释]]></title>
<url>%2F2019%2F01%2F23%2F%E8%A7%84%E8%8C%83-%E7%89%B9%E6%AE%8A%E6%B3%A8%E9%87%8A%2F</url>
<content type="text"><![CDATA[1TODO:这里将要放一个华丽又不失内涵的简介 特殊注释 TODO: 标识该处有功能代码待编写,待实现的功能在说明中会简略说明。 FIXME: 标识该处代码需要修正,甚至代码是错误的,不能工作,需要修复,如何修正会在说明中简略说明。 XXX: 标识该处代码虽然实现了功能,但是实现的方法有待商榷,希望将来能改进,要改进的地方会在说明中简略说明。]]></content>
<categories>
<category>规范</category>
</categories>
<tags>
<tag>TODO</tag>
<tag>XXX</tag>
<tag>FIXME</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java常用方法-集合工具类常用方法]]></title>
<url>%2F2019%2F01%2F23%2FJava%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95-%E9%9B%86%E5%90%88%E5%B7%A5%E5%85%B7%E7%B1%BB%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95%2F</url>
<content type="text"><![CDATA[真香~ 对象List按属性排序1234567891011121314151617181920212223242526272829303132333435363738394041class Animal{ private String kind; private int quantity; public Animal(String k,int q){ this.kind = k; this.quantity = q; }}public class Main{ public static void main(String[] args) { Animal dog = new Animal("狗",10000); Animal cat = new Animal("猫",5000); Animal pig = new Animal("猪",2000); List<Animal> animalList = new ArrayList<Animal>(); animalList.add(dog); animalList.add(cat); animalList.add(pig); Collections.sort(animalList, new Comparator<TestA>() { @Override public int compare(TestA o1, TestA o2) { //升序 return o1.getAge().compareTo(o2.getAge()); } }); Collections.sort(animalList, new Comparator<TestA>() { @Override public int compare(TestA o1, TestA o2) { //降序 return o1.getAge().compareTo(o2.getAge()); } }); }} List初始化时进行赋值1List<Long> ids = Arrays.asList(98765432109L,12345678901L);]]></content>
<categories>
<category>Java常用方法</category>
</categories>
<tags>
<tag>排序</tag>
<tag>List</tag>
<tag>对象</tag>
<tag>属性</tag>
<tag>初始化</tag>
<tag>赋值</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java常用关键字]]></title>
<url>%2F2019%2F01%2F16%2FJava%E5%9F%BA%E7%A1%80-Java%E5%B8%B8%E7%94%A8%E5%85%B3%E9%94%AE%E5%AD%97%2F</url>
<content type="text"><![CDATA[abstract final修饰类被final修饰的类不能被继承 修饰方法 “使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。” 由此可见,被final修饰的方法最主要的目的就是防止方法被重写 修饰变量对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。 对象的引用无法改变,但是对象的属性值可不可以改变呢? 因为payStatus是int类型,初始化时候会给定默认值:0 此时输出结果 1 10 从结果来看,final”锁定”的是对象的引用,并没有”锁定”属性的值; 注意事项final与static的区别,static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变。 示例:123456789101112131415final class Father{ public final Double i = Math.random(); public static Double j = Math.random();}public static void main(String[] args){ Father f = new Father(); Father ff = new Father(); System.out.println(f.i); System.out.println(f.j); System.out.println(); System.out.println(ff.i); System.out.println(ff.j);} 输出结果: 0.41791403026319096 0.8147697161273327 0.9571817275395188 0.8147697161273327 参考资料博客园-海子-浅析Java中的final关键字 博客园-五月的仓颉-谈谈final的作用 static 静态方法内部不能调用非静态方法,非静态方法没有此限制。 被static修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要new出一个类来 被static修饰的方法属于类方法,可以通过类名.方法名直接引用,而不需要new出一个类来 静态资源是类初始化的时候加载的,类实例之间共享,一处变,处处变。 并且被static修饰的方法是没有this的,他不依附于任何对象。但是非静态方法是依赖于具体对象而调用的。这就是为什么静态方法内部不能调用非静态方法。 既然静态资源不依赖于任何对象,那为什么还要放在不同对象里呢。是不是可以把所有静态资源全都放到一起? 避免重名,通过不同对象使同名静态资源区分开。 资源分类清晰,功能明确。 放到一起,那存放静态资源的这个类会特别大。 静态资源(变量/方法/代码块)static变量也称作静态变量,静态变量和非静态变量的区别是: 静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。 很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。 注意事项静态资源真的没有this吗? 1234567891011121314151617final class Father{ static int wg = 31; public void pringtWg(){ int wg = 13; System.out.println(this.wg); }}public class TestExcel { public static void main(String[] args) throws Exception { new Father().pringtWg(); }} 输出: 31 this是指当前对象,printWg中的wg是局部变量,无法与this内联。 常见面试题一1234567891011121314151617181920212223242526public class Son extends Father{ static{ System.out.println("son static"); } public Test(){ System.out.println("son constructor"); } public static void main(String[] args) { System.out.println("main()"); new Son(); }}class Father{ static{ System.out.println("father static"); } public Base(){ System.out.println("father constructor"); }} 输出: father static son static main() father constructor son constructor 程序执行顺序: main()作为程序入口; 需要加载Son,发现Son继承Father,加载Father; Father中有static块,初始化加载这个static块; 返回Son,Son中有static块,初始化这个static块; 打印输出字符串”main()” 执行new Son(); Son继承Father,Father有构造函数,执行构造函数; 返回Son,Son有构造函数,执行构造函数; 第2~4步是对象的加载过程,第5~7步是对象的执行过程;输出结果与执行顺序的对应关系 输出行 对应执行顺序 father static 3 son static 4 father constructor 7 son constructor 8 不难发现即使mian()中并没有语句,但仍然会有输出。就像之前所说,jvm会把所有不在方法内部的的静态资源初始化。 参考资料博客园-海子-浅析Java中的final关键字 博客园-五月的仓颉-谈谈final的作用 包和访问权限控制关键字package 包是可以理解为命名空间,他能有效解决类重名问题。 为了方便组织和管理,多用倒置域名来标识。 默认访问权限(包访问权限)、public、private和protected一个.java文件只能有一个被public修饰的类,并且类名必须与文件名相同。如果没有public关键字,则对类的命名没有要求。 修饰类 默认访问权限(包访问权限):用来修饰类的话,表示该类只对同一个包中的其他类可见。 public:用来修饰类的话,表示该类对其他所有的类都可见。 如果你想实现其他任何人都不能访问该类,可以通过将类的所有构造器都指定为private。1234567891011121314151617package com.qigou.b2cex.test;class People { private String name = null; public People(String name) { this.name = name; } public String getName(){ return name; } public void setName(String name){ this.name = name; }} 1234567package com.qigou.b2cex.test;public class TestExcel{ public static void main(String[] args) throws Exception { System.out.println(new People("BBDog").getName()); }} 输出结果: BBDog 将People的包名改为test1后 修饰方法和变量 作用域 当前类 同包下 子孙 所有类 public √ √ √ √ protected √ √ √ × 默认 √ √ × × private √ × × × 1234567891011121314151617package com.qigou.b2cex.test;public class People { private String name = null; public People(String name) { this.name = name; } public String getName(){ return name; } public void setName(String name){ this.name = name; }} 12345678package com.qigou.b2cex.test;public class TestExcel{ public static void main(String[] args) throws Exception { People people = new People("BBDog"); System.out.println(people.getName()); }} 变更getName()的权限,每次初始化包名。 public 正常输出: BBDog 默认 同包下正常输出: BBDog 修改People包名为test1 protected 同包下正常输出: BBDog 修改People包名为test1 创建Kids类继承People 12345678910111213package com.qigou.b2cex.test;import com.qigou.b2cex.test1.People;public class Kids extends People { public Kids(String name){ super(name); } public String toString() { return getName(); }} 子类拥有权限,但是调用子类仍然不被允许 private 参考资料 博客园-海子-浅析Java中的final关键字]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>package</tag>
<tag>关键字</tag>
<tag>final</tag>
<tag>static</tag>
<tag>访问控制</tag>
<tag>public</tag>
<tag>private</tag>
<tag>protected</tag>
<tag>default</tag>
</tags>
</entry>
<entry>
<title><![CDATA[ElasticSearch单机集群搭建与ElasticHD]]></title>
<url>%2F2019%2F01%2F11%2F%E6%A1%86%E6%9E%B6-ElasticSearch%E5%8D%95%E6%9C%BA%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EElasticHD%2F</url>
<content type="text"><![CDATA[abstract 本机环境操作系统及位数 下载ES及分词器选择用户为什么要把选择用户单独列一项,因为ElasticSearch无法在ROOT下启动。所以,安装、配置、创建所需目录也都提前到选择用户之后。 安装##]]></content>
<categories>
<category>框架</category>
</categories>
<tags>
<tag>ElasticSearch</tag>
<tag>ES</tag>
<tag>ElasticHD</tag>
</tags>
</entry>
<entry>
<title><![CDATA[equals()和hashCode()]]></title>
<url>%2F2019%2F01%2F07%2FJava%E5%9F%BA%E7%A1%80-equals%E5%92%8ChashCode%2F</url>
<content type="text"><![CDATA[原生equals()底层源码通过==来实现,比较的是两个对象(两个对象的引用地址)。 hashCode()是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值. 两个对象的equals()相等(原生),hashCode()一定相等; 两个对象hashCode()相等,epuals()并不一定相等。 equals() 原生equals()底层源码通过==来实现,比较的是两个对象(两个对象的引用地址)。 重写equals()用来比较两个对象的内容是否相等。 常用覆盖逻辑12345678910111213141516171819202122/** * @desc 覆盖equals方法 */ public boolean equals(Object obj){ if(obj == null){ return false; } //如果是同一个对象返回true,反之返回false if(this == obj){ return true; } //判断是否类型相同 if(this.getClass() != obj.getClass()){ return false; } //比较对象属性值是否相等 //例如 Person中有两个属性 name,age Person person = (Person)obj; return name.equals(person.name) && age==person.age; } hashCode()hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值.对象的地址或者字符串或者数字就是keys,通过一个函数算出hash. hashCode=F(key); hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置; 两个对象的equals()相等(原生),hashCode()一定相等; 两个对象hashCode()相等,epuals()并不一定相等。 我们应该根据对象的特点,重写hashCode方法,尽量避免(3)提到的哈希冲突情况。 HashSet判定一个对象是否重复: HashSet判定为重复对象。 未重写equals()和hashCode()时: code: 123456789Person p1 = new Person("aaa",1);Person p2 = p1;Person p3 = new Person("bbb",1);Set<Person> PS = Sets.newHashSet();PS.add(p1);PS.add(p2);PS.add(p3);System.out.println(p1.equals(p2));System.out.printf(PS+"====》"+"p1.hashCode(%s)==>p2.hashCode(%s)==>p3.hashCode(%s)",p1.hashCode(),p2.hashCode(),p3.hashCode()); console: 12true[aaa - 1, bbb - 1]====》p1.hashCode(1047503754)==>p2.hashCode(1047503754)==>p3.hashCode(1722023916) 我们知道原生equals()相等时,hashCode一定相等; code: 123456789Person p1 = new Person("aaa",1);Person p2 = new Person("aaa",1);Person p3 = new Person("bbb",1);Set<Person> PS = Sets.newHashSet();PS.add(p1);PS.add(p2);PS.add(p3);System.out.println(p1.equals(p2));System.out.printf(PS+"====》"+"p1.hashCode(%s)==>p2.hashCode(%s)==>p3.hashCode(%s)",p1.hashCode(),p2.hashCode(),p3.hashCode()); console: 12false[bbb - 1, aaa - 1, aaa - 1]====》p1.hashCode(1047503754)==>p2.hashCode(1722023916)==>p3.hashCode(2009787198) 只重写hashCode()时: code: 123456789101112131415161718private static class Person { @Override public int hashCode(){ int nameHash = name.toUpperCase().hashCode(); return nameHash ^ age; }}public static void main(String[] args){ Person p1 = new Person("aaa",1); Person p2 = new Person("aaa",1); Person p3 = new Person("bbb",1); Set<Person> PS = Sets.newHashSet(); PS.add(p1); PS.add(p2); PS.add(p3); System.out.println(p1.equals(p2)); System.out.printf(PS+"====》"+"p1.hashCode(%s)==>p2.hashCode(%s)==>p3.hashCode(%s)",p1.hashCode(),p2.hashCode(),p3.hashCode()); } console: 12false[aaa - 1, aaa - 1, bbb - 1]====》p1.hashCode(64544)==>p2.hashCode(64544)==>p3.hashCode(65539) 只重写equals()时: code: 123456789101112131415161718192021222324252627282930313233private static class Person { @Override public boolean equals(Object obj){ if(obj == null){ return false; } //如果是同一个对象返回true,反之返回false if(this == obj){ return true; } //判断是否类型相同 if(this.getClass() != obj.getClass()){ return false; } Person person = (Person)obj; return name.equals(person.name) && age==person.age; }}public static void main(String[] args){ Person p1 = new Person("aaa",1); Person p2 = new Person("aaa",1); Person p3 = new Person("bbb",1); Set<Person> PS = Sets.newHashSet(); PS.add(p1); PS.add(p2); PS.add(p3); System.out.println(p1.equals(p2)); System.out.printf(PS+"====》"+"p1.hashCode(%s)==>p2.hashCode(%s)==>p3.hashCode(%s)",p1.hashCode(),p2.hashCode(),p3.hashCode()); } console: 12true[bbb - 1, aaa - 1, aaa - 1]====》p1.hashCode(1047503754)==>p2.hashCode(1722023916)==>p3.hashCode(2009787198) 同时重写equals()和hashCode()时: code: 1234567891011public static void main(String[] args){ Person p1 = new Person("aaa",1); Person p2 = new Person("aaa",1); Person p3 = new Person("bbb",1); Set<Person> PS = Sets.newHashSet(); PS.add(p1); PS.add(p2); PS.add(p3); System.out.println(p1.equals(p2)); System.out.printf(PS+"====》"+"p1.hashCode(%s)==>p2.hashCode(%s)==>p3.hashCode(%s)",p1.hashCode(),p2.hashCode(),p3.hashCode()); } console: 12true[aaa - 1, bbb - 1]====》p1.hashCode(64544)==>p2.hashCode(64544)==>p3.hashCode(65539) 至此我们推断HashSet的判重逻辑是,hashCode()与equals()同时满足]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>java</tag>
<tag>equals</tag>
<tag>hashCode</tag>
</tags>
</entry>
<entry>
<title><![CDATA[内存模型]]></title>
<url>%2F2019%2F01%2F05%2FJava%E8%99%9A%E6%8B%9F%E6%9C%BA-%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B%2F</url>
<content type="text"><![CDATA[Xmind Zen 挺好用的 学习了《深入理解Java虚拟机》–周志明之后的一点点个人总结,如有错误还请指正。 文字版如下 运行时内存线程私有虚拟机栈 描述 java方法执行的内存模型 存储局部变量表、操作数栈、动态链接、方法出口等信息 方法开始执行时,创建栈帧 从方法执行到执行完成对应(一个栈帧)入栈出栈的过程 功能 局部变量表 存放该方法调用者所传入的参数,及在该方法的方法体中创建的局部变量。 对象引用(reference类型) 可能是指向对象起始地址的引用指针 也可能是指向一个代表对象的句柄 returnAddress类型 指向一条字节码指令的地址 多线程 生命周期与线程一致 抛出异常 线程请求深度大于虚拟机栈允许最大深度时 StackOverflowError 虚拟机栈可以动态扩展,无法申请到足够内存 OutOfMemoryError 本地方法栈 描述 与虚拟机栈十分相似 只不过针对Native方法服务 功能 多线程 生命周期与线程一致 抛出异常 同虚拟机栈异常 程序计数器 描述 当前线程所执行字节码的行号指示器 功能 字节码解释器改变计数器的值 如果执行的是native方法,则计数器值为空 多线程 每个线程的计数器互不影响,相互独立 jvm多线程的理解 jvm通过轮流分配处理器执行时间,实现多线程 共享区域java堆 描述 虚拟机启动时创建 功能 存放对象的实例 jit编译器与逃逸分析技术的发展,栈上分配、标量替换使这一条不那么绝对 存放数组 为了更好的分配和回收会进行细分、功能不变 新生代和老年代 Eden空间、From Survivor空间、To Survivor空间 线程私有的分配缓冲区 TLAB GC 内存回收的主要区域 抛出异常 堆中没有内存可以完成实力分配并且没法扩展时 OutOfMemoryError 参数 -Xmx -Xms 方法区 主区域 描述 堆的逻辑区域 功能 存储已被虚拟机加载的类信息 类的版本、字段、方法、接口等描述信息 常量池 常量 静态变量 即时编辑器编译后的代码 特点 不需要连续的内存空间 可固定可扩展 可以不实现垃圾回收(垃圾回收在此区相对较少) 参数 有些虚拟机把分代收集扩展到方法区称为永久代 永久代大小 -XX:MaxPermSize 运行时常量池 描述 用于存放编译期生成的各种字面变量和符号引用 功能 还可以保存翻译出来的直接引用 特点 相对于Class文件常量池具备动态性 运行期间也可以讲新的常量放入池中 String.intern() 抛出异常 方法区无法满足内存分配需求时 OutOfMemoryError 直接内存 描述 NIO类可以操作Native函数库直接分配堆外内存 通过存储在堆中的DirectByteBuffer对象作为这块内存引用操作 抛出异常 OutOfMemoryError 参数 主动设置 -XX:maxDirectMemorySize 默认 -Xms 对象对象的创建 类加载 先通过New指令的参数去常量池中定位到一个类的符号引用 编译时并不知道一个类的直接内存地址,只能使用(符号)来标识类的地址 如果没有,执行相应的类加载过程 分配内存空间 类加载完成后所需的大小可以完全确定 将所需的内存区域从Java堆中划分出来 内存分配方式 规整的内存(已用和未用有一个区分界限) 零散的区域 需要一个空闲列表来统计空闲区域 内存分配时并发性问题 描述 正在给A对象分配,指针还没来得及移动,要开始分配B对象,使用了A对象内存区域 解决方案 动作原子性控制 CAS配上失败重试 作用域控制 为每个线程在堆中分配TLAB,每个线程在自己的区域中划分内存 如果原来的缓冲区域用完了,需要新的缓冲区来划分内存,则需要同步锁来保证完整性 启用TLAB -XX:+/-UseTLAB 翻台 将分配的内存区域置零 不包括对象头 如果使用TLAB,这一步在TLAB分配时完成 设置 根据对象头 对象是那个类的实例 如何才能找到类的元数据信息 对象的哈希码 对象的GC分代年龄等 至此一个对象创建完成,但是对于Java程序来讲对象的创建才刚刚开始,接下来执行init方法 对象的内存布局 对象头包含两部分信息 运行时数据、信息 位数固定 如果储存的信息太多时会复用位数空间 类型指针 即指向它类元数据的指针 虚拟机通过这个指针来确定对象是哪个类的实例 真正储存的有效信息 各类型字段(包括父类) 存储顺序 虚拟机分配策略参数 相同宽度的字段总是被分配到一起 父类定义的变量会在子类之前 字段在Java源码中的定义顺序 对齐填充 保证地址空间时8字节的整数倍 对象的访问定位 通过栈上的reference数据来操作对象 指向对象的引用 访问方式 句柄 Java堆中划分出一块句柄池 reference存储句柄地址 句柄包含对象实例数据与类型数据各自的具体信息 直接指针 Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息 优缺点 直接指针访问速度快,对象移动时需要改变栈中reference 句柄每次访问查一遍句柄池,但是对象移动(垃圾收集时)不需要修改栈中reference]]></content>
<categories>
<category>Java虚拟机</category>
</categories>
<tags>
<tag>java</tag>
<tag>JVM</tag>
<tag>虚拟机</tag>
</tags>
</entry>
<entry>
<title><![CDATA[学习JVM前的准备]]></title>
<url>%2F2019%2F01%2F05%2FJava%E8%99%9A%E6%8B%9F%E6%9C%BA-%E5%AD%A6%E4%B9%A0JVM%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87%2F</url>
<content type="text"><![CDATA[一个java程序的分娩过程 一个java程序的分娩过程大致可以分为一下四步 生成.java文件此处不必多说,但需要注意语法 生成.class文件使用Javac命令可将.java文件编译成.class文件 使用java -v [.class文件名]查看字节码文件使用java -v [.class文件名] > [输出文件路径]将字节码文件输出到指定路径的文件中java的跨平台性就体现在无论以何种方式生成字节码文件,只要字节码文件符合规范。jvm都能运行此处推荐一个.class文件查看器github:jclasslib 类加载器加载.class文件 类加载器会通过CLASSPATH找到需要执行的.class文件 读取字节码文件流,储存在方法区中3.执行引擎找到main()作为入口图片来自百度百科]]></content>
<categories>
<category>Java虚拟机</category>
</categories>
<tags>
<tag>java</tag>
<tag>JVM</tag>
<tag>字节码</tag>
</tags>
</entry>
<entry>
<title><![CDATA[mybatis-generator]]></title>
<url>%2F2018%2F11%2F23%2F%E5%B7%A5%E5%85%B7-mybatis-generator%2F</url>
<content type="text"><![CDATA[abstract 添加依赖1234567891011<!-- mybatis generator 自动生成代码插件 --><plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configuration> <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile> <overwrite>true</overwrite> <verbose>true</verbose> </configuration> </plugin> 编辑配置文件在<configurationFile>指向的位置添加generatorConfig.xml文件当前目录123456789101112131415161718192021222324252627282930313233343536<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"><generatorConfiguration> <!-- 数据库驱动:选择你的本地硬盘上面的数据库驱动包--> <classPathEntry location="D:\bbgog\bbdog-maven\maven-repository\org\mariadb\jdbc\mariadb-java-client\2.3.0\mariadb-java-client-2.3.0.jar"/> <context id="DB2Tables" targetRuntime="MyBatis3"> <commentGenerator> <property name="suppressDate" value="true"/> <!-- 是否去除自动生成的注释 true:是 : false:否 --> <property name="suppressAllComments" value="true"/> </commentGenerator> <!--数据库链接URL,用户名、密码 --> <jdbcConnection driverClass="org.mariadb.jdbc.Driver" connectionURL="jdbc:mariadb://localhost:3306/test" userId="root" password="123456"> </jdbcConnection> <javaTypeResolver> <property name="forceBigDecimals" value="false"/> </javaTypeResolver> <!-- 生成模型的包名和位置--> <javaModelGenerator targetPackage="com.demo.entity" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> <property name="trimStrings" value="true"/> </javaModelGenerator> <!-- 生成映射文件的包名和位置--> <sqlMapGenerator targetPackage="mapping" targetProject="src/main/resources"> <property name="enableSubPackages" value="true"/> </sqlMapGenerator> <!-- 生成mapper的包名和位置--> <javaClientGenerator type="XMLMAPPER" targetPackage="com.demo.mapper" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <!-- 要生成的表 tableName是数据库中的表名或视图名 domainObjectName是实体类名--> <table tableName="user" domainObjectName="User" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table> </context></generatorConfiguration> 启动generator在指定位置新建需求表123456CREATE TABLE USER ( user_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, user_name VARCHAR ( 255 ) NOT NULL, PASSWORD VARCHAR ( 255 ) NOT NULL, phone VARCHAR ( 255 ) NOT NULL) ENGINE = INNODB AUTO_INCREMENT = 1000 DEFAULT CHARSET = utf8; 双击启动generator 生成后项目目录 生成对应POJO类 生成对应mapper 生成对应mapper.xml 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><mapper namespace="com.demo.mapper.UserMapper" > <resultMap id="BaseResultMap" type="com.demo.entity.User" > <id column="user_id" property="userId" jdbcType="INTEGER" /> <result column="user_name" property="userName" jdbcType="VARCHAR" /> <result column="password" property="password" jdbcType="VARCHAR" /> <result column="phone" property="phone" jdbcType="VARCHAR" /> </resultMap> <sql id="Base_Column_List" > user_id, user_name, password, phone </sql> <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" > select <include refid="Base_Column_List" /> from user where user_id = #{userId,jdbcType=INTEGER} </select> <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" > delete from user where user_id = #{userId,jdbcType=INTEGER} </delete> <insert id="insert" parameterType="com.demo.entity.User" > insert into user (user_id, user_name, password, phone) values (#{userId,jdbcType=INTEGER}, #{userName,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR}) </insert> <insert id="insertSelective" parameterType="com.demo.entity.User" > insert into user <trim prefix="(" suffix=")" suffixOverrides="," > <if test="userId != null" > user_id, </if> <if test="userName != null" > user_name, </if> <if test="password != null" > password, </if> <if test="phone != null" > phone, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides="," > <if test="userId != null" > #{userId,jdbcType=INTEGER}, </if> <if test="userName != null" > #{userName,jdbcType=VARCHAR}, </if> <if test="password != null" > #{password,jdbcType=VARCHAR}, </if> <if test="phone != null" > #{phone,jdbcType=VARCHAR}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="com.demo.entity.User" > update user <set > <if test="userName != null" > user_name = #{userName,jdbcType=VARCHAR}, </if> <if test="password != null" > password = #{password,jdbcType=VARCHAR}, </if> <if test="phone != null" > phone = #{phone,jdbcType=VARCHAR}, </if> </set> where user_id = #{userId,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="com.demo.entity.User" > update user set user_name = #{userName,jdbcType=VARCHAR}, password = #{password,jdbcType=VARCHAR}, phone = #{phone,jdbcType=VARCHAR} where user_id = #{userId,jdbcType=INTEGER} </update></mapper>]]></content>
<categories>
<category>工具</category>
</categories>
<tags>
<tag>mybatis</tag>
<tag>generator</tag>
<tag>逆向生成文件</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Log4j补充]]></title>
<url>%2F2018%2F11%2F23%2F%E6%A1%86%E6%9E%B6-Log4j%E8%A1%A5%E5%85%85%2F</url>
<content type="text"><![CDATA[Log4j补充 LoggerConfig LevelEvent Level和LoggerConfig Level对照表 Event Level LoggerConfig Level LoggerConfig Level LoggerConfig Level LoggerConfig Level LoggerConfig Level LoggerConfig Level LoggerConfig Level Event Level TRACE DEBUG INFO WARN ERROR FATAL OFF ALL YES YES YES YES YES YES NO TRACE YES NO NO NO NO NO NO DEBUG YES YES NO NO NO NO NO INFO YES YES YES NO NO NO NO WARN YES YES YES YES NO NO NO ERROR YES YES YES YES YES NO NO FATAL YES YES YES YES YES YES NO OFF NO NO NO NO NO NO NO 感谢大佬翻译了APILog4j输出格式控制–log4j的PatternLayout参数含义以及详细配置 官方API 没把格式化看懂,但是意外发现了一个markdown编辑神器,可以把excel表格转为Markdown表格。详见文章【利器】 参数 说明 例子 %c 列出logger名字空间的全称,如果加上{<层数>}表示列出从最内层算起的指定层数的名字空间 log4j配置文件参数举例 输出显示媒介 假设当前logger名字空间是”a.b.c” %c a.b.c %c{2} b.c %20c (若名字空间长度小于20,则左边用空格填充) %-20c (若名字空间长度小于20,则右边用空格填充) %.30c (若名字空间长度超过30,截去多余字符) %20.30c (若名字空间长度小于20,则左边用空格填充;若名字空间长度超过30,截去多余字符) %-20.30c (若名字空间长度小于20,则右边用空格填充;若名字空间长度超过30,截去多余字符) %C 列出调用logger的类的全名(包含包路径) 假设当前类是”org.apache.xyz.SomeClass” %C org.apache.xyz.SomeClass %C{1} SomeClass %d 显示日志记录时间,{<日期格式>}使用ISO8601定义的日期格式 %d{yyyy/MM/dd HH:mm:ss,SSS} 2005/10/12 22:23:30,117 %d{ABSOLUTE} 22:23:30,117 %d{DATE} 12 Oct 2005 22:23:30,117 %d{ISO8601} 2005-10-12 22:23:30,117 %F 显示调用logger的源文件名 %F MyClass.java %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数 %l MyClass.main(MyClass.java:129) %L 显示调用logger的代码行 %L 129 %m 显示输出消息 %m This is a message for debug. %M 显示调用logger的方法名 %M main %n 当前平台下的换行符 %n Windows平台下表示rn UNIX平台下表示n %p 显示该条日志的优先级 %p INFO %r 显示从程序启动时到记录该条日志时已经经过的毫秒数 %r 1215 %t 输出产生该日志事件的线程名 %t MyClass %x 按NDC(Nested Diagnostic Context,线程堆栈)顺序输出日志 假设某程序调用顺序是MyApp调用com.foo.Bar %c %x - %m%n MyApp - Call com.foo.Bar. com.foo.Bar - Log in BarMyApp - Return to MyApp. %X 按MDC(Mapped Diagnostic Context,线程映射表)输出日志。通常用于多个客户端连接同一台服务器,方便服务器区分是那个客户端访问留下来的日志。 %X{5} (记录代号为5的客户端的日志) %% 显示一个百分号 %% %]]></content>
<categories>
<category>框架</category>
</categories>
<tags>
<tag>log4j</tag>
<tag>补充</tag>
<tag>log4j格式化</tag>
</tags>
</entry>
<entry>
<title><![CDATA[利器]]></title>
<url>%2F2018%2F11%2F23%2F%E5%B7%A5%E5%85%B7-%E5%88%A9%E5%99%A8%2F</url>
<content type="text"><![CDATA[这是你的金斧子吗?这是你的银斧子吗? 编辑器markdownExcel表格转Markdown表格官网高大上,功能很强大,未深喑其中功能但觉得安装包不小。Typora官网]]></content>
<categories>
<category>工具</category>
</categories>
<tags>
<tag>工具</tag>
<tag>tools</tag>
</tags>
</entry>
<entry>
<title><![CDATA[框架-Log4j]]></title>
<url>%2F2018%2F11%2F23%2F%E6%A1%86%E6%9E%B6-Log4j%2F</url>
<content type="text"><![CDATA[abstract 添加依赖在pom.xml文件中添加如下依赖12345678910111213141516171819<!-- spring boot start --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <!-- 排除自带的logback依赖 --> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- springboot-log4j --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j</artifactId> <version>1.3.8.RELEASE</version> </dependency> 编辑配置文件rootloggerrootlogger主要定义log4j支持的日志级别及输出目的地,其语法为:1log4j.rootLogger = [ level ] , appenderName, appenderName, … level 是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定义的级别。 appenderName指定日志信息输出到哪个地方,可同时指定多个输出目的地。 例如: 1log4j.rootLogger=info, stdout 有两个疑惑: Level 代号 FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 后面Threshold有限制输出范围,如果这里设置了ERROR,Threshold设置INfO,日志会输出WARN,和INFO级别的日志信息吗? 中间丢失的1、2、5对应什么级别,他们是被弃用了吗?我这里有酒,希望有了解的可以讲出他们的故事; appenderappender附加器主要定义日志信息输出位置,输出格式等。主要语法为: 123log4j.appender.appenderName = classInfolog4j.appender.appenderName.option1 = value1log4j.appender.appenderName.optionN = valueN 这里的appenderName与rootlogger中的appenderName对应 appender的classInfo有如下选项: org.apache.log4j.ConsoleAppender(控制台) org.apache.log4j.FileAppender(文件) org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件) org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件) org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方) 例如: 1log4j.appender.stdout=org.apache.log4j.ConsoleAppender appender.option–FileFile是日志输出的目的地。 例如:1log4j.appender.stdout.File=logs/log.log appender.option–Threshold输出等级限制,包含本身及以上。 例如:12## 输出DEBUG级别以上的日志log4j.appender.stdout.Threshold=DEBUG appender.option–Append日志信息的追加方式。true意味着,默认为truefales意味着, 12345678910111213141516171819package com.qigou.b2cex.test;import com.b2bex.goods.service.EsOrderIndexManager;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.ArrayList;public class TestExcel{ private static final Logger logger = LoggerFactory.getLogger("BBDOG"); public static void main(String[] args) throws Exception { logger.info("logger----Info"); logger.warn("logger----warn"); logger.debug("logger----debug"); logger.error("logger----error"); logger.info("-----------------------------分割线-----------------------------"); }} 使用默认配置,配置文件中有如下信息: 再次执行,信息如下; 配置中增加 1log4j.appender.bbdog.Append=fales 再次执行后,本想顺理成章的像其他博文一样展示只有一份信息。但学东西真的这么顺利过吗? 实际上控制台给我报错: 1log4j:WARN Failed to set property [append] to value "fales". 然后发现呵呵呵呵!false写成了fales.修改后文件中确实只有最新的日志信息了: appender.option–Encoding日志信息的编码格式; 12## 输出DEBUG级别以上的日志log4j.appender.stdout.Encoding=UTF-8 appender.option–DatePattern在DailyRollingFileAppender中可以指定monthly(每月)、 weekly(每周)、daily(每天)、half-daily(每半天)、hourly(每小时)和minutely(每分钟)六个日志生成频度,这是通过为 DatePattern选项赋予不同的值来完成的。DatePattern选项的有效值为: ‘.’yyyy-MM,对应monthly(每月) ‘.’yyyy-ww,对应weekly(每周) ‘.’yyyy-MM-dd,对应daily(每天) ‘.’yyyy-MM-dd-a,对应half-daily(每半天) ‘.’yyyy-MM-dd-HH,对应hourly(每小时) ‘.’yyyy-MM-dd-HH-mm,对应minutely(每分钟) DatePattern中不用处理的文字要放到单引号(‘)中,如上面的(.)。如果您对此有疑问可以查阅SimpleDateFormat的文档。DailyRollingFileAppender中使用这个类来处理DatePattern。 DatePattern格式化之后的文本作为文件名字的后缀。DailyRollingFileAppender不支持格式化之后的文本作为文件名字的前缀。 修改系统时间可以看到效果,当天的文件名为bbdog.log,之前的文件名会加上频度日期 appender.option–LayoutLayout 负责格式化Appender的输出。 Log4j提供的layout有以下几种: org.apache.log4j.HTMLLayout(以HTML表格形式布局), org.apache.log4j.PatternLayout(可以灵活地指定布局模式), org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串) org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息) 例如: 12log4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n 其中ConversionPattern有如下解释 参数 含义 %m 输出代码中指定的消息 %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL %r 输出自应用启动到输出该log信息耗费的毫秒数 %c 输出所属的类目,通常就是所在类的全名 %t 输出产生该日志事件的线程名 %n 输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n” %d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss , SSS},输出类似:2002年10月18日 22 : 10 : 28 , 921 %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java: 10 ) 这里只演示了DailyRollingFileAppender这种方式下的一些参数. 其他输出类型会有额外的参数,如RollingFileAppender下会有MaxFileSize和MaxBackupIndex,单个文件大小和备份数量 食用方法参考资料博客园-盖世圣猪-log4j配置详解(非常详细) CSDN-谁动了我的bug-Log4j Append属性指定是否追加内容 ConversionPattern中的格式化参数详见Log4j补充]]></content>
<categories>
<category>框架</category>
</categories>
<tags>
<tag>Apache</tag>
<tag>Log4j</tag>
</tags>
</entry>
<entry>
<title><![CDATA[web.xml]]></title>
<url>%2F2018%2F10%2F25%2F%E8%A7%84%E8%8C%83-web-xml%2F</url>
<content type="text"><![CDATA[什么是web.xml?在一个web项目中,往往需要一些初始化配置信息,如Welcome页面、servlet、servlet-mapping、filter、listener、启动加载级别等。web.xml文件就是用来初始化这些配置信息。但不是所有的web项目都需要web.xml文件,如果配置不是很负责,可以将他们放到Application中。 ##web.xmlweb.xml也遵循Schema配置的规则,以<web-app>为根标签。文件配置信息为: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106<?xml version="1.0" encoding="UTF-8"?><web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> #指定Web应用的大图标和小图标 <icon> <small-icon>/images/app_small.gif</small-icon> <large-icon>/images/app_large.gif</large-icon> </icon> #Web应用的名称 <display-name>Tomcat Example</display-name> #给出于此相关的说明性文本 <disciption>Tomcat Example servlets and JSP pages.</disciption> <context-param> <param-name>ContextParameter</para-name> <param-value>test</param-value> <description>It is a test parameter.</description> </context-param> <!-- 过滤器配置 --> <filter> <filter-name>setCharacterEncoding</filter-name> <filter-class>com.myTest.setCharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>setCharacterEncoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 监听器 --> <listener> <listerner-class>com.listener.SessionListener</listener-class> </listener> <!-- servlet --> <servlet> <servlet-name>servlet名称</servlet-name> <servlet-class>servlet类全路径</servlet-class> <init-param> <param-name>参数名</param-name> <param-value>参数值</param-value> </init-param> <run-as> <description>匿名访问的角色</description> <role-name>tomcat</role-name> </run-as> </servlet> <servlet-mapping> <servlet-name>servelet名称</servlet-name> <url-pattern>映射路径</url-pattern> </servlet-mapping> <!-- session超时时间(单位:分钟)--> <session-config> <session-timeout>120</session-timeout> </session-config> <!-- mime --> <mime-mapping> <extension>htm</extension> <mime-type>text/html</mime-type> </mime-mapping> <!-- 欢迎页 --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> </welcome-file-list> <!-- 错误代码跳转页 --> <error-page> <error-code>404</error-code> <location>/NotFound.jsp</location> </error-page> <error-page> <exception-type>java.lang.NullException</exception-type> <location>/error.jsp</location> </error-page> <jsp-config> <taglib> <taglib-uri>Taglib</taglib-uri> <taglib-location>/WEB-INF/tlds/MyTaglib.tld</taglib-location> </taglib> <jsp-property-group> <description>Special property group for JSP Configuration JSP example.</description> <display-name>JSPConfiguration</display-name> <url-pattern>/jsp/* </url-pattern> <el-ignored>true</el-ignored> <page-encoding>GB2312</page-encoding> <scripting-invalid>true</scripting-invalid> <include-prelude>/include/prelude.jspf</include-prelude> <include-coda>/include/coda.jspf</include-coda> </jsp-property-group> </jsp-config></web-app>]]></content>
<categories>
<category>规范</category>
</categories>
<tags>
<tag>web.xml</tag>
<tag>配置</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Red-Black Tree]]></title>
<url>%2F2018%2F10%2F23%2F%E7%AE%97%E6%B3%95-Red-Black-Tree%2F</url>
<content type="text"><![CDATA[你必须非常努力,才能看起来毫不费力 ———TreeMap 查找查找几乎是现在每时每刻都在用的东西,查找的速度决定了发展的速度。常用查找如: 顺序查找 二分查找 插值查找 斐波那契查找 树查找 分块查找 哈希查找 二叉树查找算法思想:为了查找的方便和快捷,先把待查找的数据生成一棵二叉排序树,利用排序树进行查找 二叉排序树有几个性值: 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 任意节点的左、右子树也分别为二叉查找树。 二叉查找树性质:对二叉查找树进行中序遍历(左根右),即可得到有序的数列。 时间复杂度分析: 最优的情况是一个我们构建出一个完全二叉树,时间复杂度与二分查找相同,即树的高度,为O(logn)。 最坏情况是构造出一个单支树,时间复杂度为O(n); 所以,想享受O(logn)的时间复杂度的代价就是,花大功夫构造出一个便于查找的二叉树树。 2-3查找树2-3节点有下列三种可能: 节点为空节点 节点为2节点,2节点中有一个key,有两个2-3节点。左子所有key比2节点的key都小,右子所有key比2节点的key都大。 节点为3节点,3节点中有两个key,有三个2-3节点,左子所有key比3节点小的key都小,中子所有key都介于3节点两个key之间,右子所有key比3节点大的key都大。 2-3查找树特性: 在一个完全平衡的2-3查找树中,根节点到每一个为空节点的距离都相同。 时间复杂度分析: 最坏情况是所有节点都是2节点,回归二叉树,而且二叉树最优情况完全二叉树,时间复杂度为O(log2n); 最好情况是所有节点都是3节点,时间复杂度就是O(log3n),约等于O(0.631log2n)。 红黑树红黑树是2-3查找树最简单的一种实现。红黑树特性: 每个节点或者是黑色,或者是红色。根节点是黑色。 每个叶子节点(为空的)都是黑色。 红色节点的子节点都是黑色 一个节点到每个子孙节点(叶子节点),经过的黑色节点数都是相同的。 为什么说红黑树是2-3树的简单实现呢。把每个节点看作是2节点。规定红色节点与左子的链接为红色链接。红色链接连接的两个节点看做是一个3节点,就转化成了2-3查找树 TreeMap源码解析(基于jdk1.7)TreeMap是红黑树的实现,首先来看一下TreeMap的Entry 123456K key;V value;Entry<K,V> left = null;Entry<K,V> right = null;Entry<K,V> parent;boolean color = BLACK; 与树的Entry相比多了一个颜色标志位 color。 TreeMap的常用操作 get() put() remove() get()因为TreeMap是已经平衡过的树(因为每次对树的解构进行改动的时候都会重新调整一遍树的机构使其每次使用时都保持最佳状态,映照开头第一句话),所以get()操作就是对排序树的遍历查找,与根节点比较,从而判断下一步走向。如此循环,直至找到对应Entry,或者没有找到而结束。 get()方法调用getEntry方法实现遍历查找12345678910111213141516171819final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; } Offload comparator-based version for sake of performance(为了性能,卸载基于比较器的版本) put()put()方法则是先进行一遍get操作,通过K比较,将新Entry放在合适位置,如果是普通二叉树,插入操作到这里就结束了,但是这是红黑树。需要对数的结构进行负责。所以在找到合适位置,插入新Entry之后又对红黑树进行了整体调整。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } Entry<K,V> e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; } 12345678910111213141516171819202122232425262728293031323334353637383940private void fixAfterInsertion(Entry<K,V> x) { x.color = RED; while (x != null && x != root && x.parent.color == RED) { if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { Entry<K,V> y = rightOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == rightOf(parentOf(x))) { x = parentOf(x); rotateLeft(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } else { Entry<K,V> y = leftOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == leftOf(parentOf(x))) { x = parentOf(x); rotateRight(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateLeft(parentOf(parentOf(x))); } } } root.color = BLACK; } remove()remove()方法同样是需要先调用getEntry找到要删除的元素,然后调用deleteEntry删除该元素,删除元素之后同样需要再次对红黑树进行解构调整。 123456789public V remove(Object key) { Entry<K,V> p = getEntry(key); if (p == null) return null; V oldValue = p.value; deleteEntry(p); return oldValue; } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748private void deleteEntry(Entry<K,V> p) { modCount++; size--; // If strictly internal, copy successor's element to p and then make p // point to successor. if (p.left != null && p.right != null) { Entry<K,V> s = successor(p); p.key = s.key; p.value = s.value; p = s; } // p has 2 children // Start fixup at replacement node, if it exists. Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { // Link replacement to parent replacement.parent = p.parent; if (p.parent == null) root = replacement; else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement; // Null out links so they are OK to use by fixAfterDeletion. p.left = p.right = p.parent = null; // Fix replacement if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null) { // return if we are the only node. root = null; } else { // No children. Use self as phantom replacement and unlink. if (p.color == BLACK) fixAfterDeletion(p); if (p.parent != null) { if (p == p.parent.left) p.parent.left = null; else if (p == p.parent.right) p.parent.right = null; p.parent = null; } } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061private void fixAfterDeletion(Entry<K,V> x) { while (x != root && colorOf(x) == BLACK) { if (x == leftOf(parentOf(x))) { Entry<K,V> sib = rightOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); } if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } else { // symmetric Entry<K,V> sib = leftOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateRight(parentOf(x)); sib = leftOf(parentOf(x)); } if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(leftOf(sib)) == BLACK) { setColor(rightOf(sib), BLACK); setColor(sib, RED); rotateLeft(sib); sib = leftOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(leftOf(sib), BLACK); rotateRight(parentOf(x)); x = root; } } } setColor(x, BLACK); } 总结一下:]]></content>
<categories>
<category>算法</category>
</categories>
<tags>
<tag>查找</tag>
<tag>二叉树</tag>
<tag>算法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[String、StringBuffer和StringBuilder]]></title>
<url>%2F2018%2F10%2F16%2FJava%E5%9F%BA%E7%A1%80-String-StringBuffer-StringBuilder%2F</url>
<content type="text"><![CDATA[整理一下近期学习的String、StringBuffer和StringBuilder之间的区别 执行速度:String < StringBuffer < Stringbuilder String与StringBuffer和StringBuilder之间的区别 String:字符串常量 StringBuffer:字符串变量 StringBuilder:字符串变量 我们知道String是常量,常量是不可以修改的。123456789101112131415public static void main(String[] args){ String str = "123"; change(str); System.out.println(str);}public static void change(String s){ System.out.println(s); s = s + "abc"; system.out.println(s);}/*console:123123abc123*/ 可以看到change方法并没有起到效果。 调用change方法时,str和s都指向”123” 当执行s = s + "abc"时s指向了”123abc” ,str还是指向”123” 123String s = "123";s = s + "abc";System.out.print(s); //result : 123abc 既然是常量,那为什么这里还可以对常量进行操作呢?JVM会创建一个新的字符串常量,原来的字符串常量成为垃圾被GC回收掉。StringBuffer和StringBuilder是字符串变量,对他们的操作是在原对象上进行的操作。 所以执行速度:String < StringBuffer < Stringbuilder 特例12String Str = "123" + "abc" + "123abc";StringBuffer Strb = new StringBuffer("123").append("abc").append("123abc"); 上面两个字符串生成的速度并不像预期的那样,StringBuffer 快于 String。为什么?因为JVM在创建字符串常量时123String Str = "123" + "abc" + "123abc";//就等于String Str = "123abc123abc"; 所以直接被创建出来,速度很快。但是如果分步执行,就会按照JVM对常规字符串常量的操作,创建新常量,回收旧常量的方式进行操作。]]></content>
<categories>
<category>Java基础</category>
</categories>
<tags>
<tag>String</tag>
<tag>StringBuffer</tag>
<tag>StringBuilder</tag>
</tags>
</entry>
<entry>
<title><![CDATA[maven]]></title>
<url>%2F2018%2F10%2F12%2F%E6%A1%86%E6%9E%B6-maven%2F</url>
<content type="text"><![CDATA[abstract pom.XMLmaven坐标Maven坐标元素包括 groupId、artifactId、version、packaging、classifier.groupId:定义当前Maven项目隶属的实际项目。artifactId: 定义实际项目中的一个Maven模块。version: 定义Maven项目当前所处的版本。packaging: 定义Maven项目的打包方式。classifier: 帮助定义构件输出的一些附属构件。 传递性依赖首先我们要了解什么叫直接依赖,A依赖于B,B就是A的直接依赖。A->B,B->(C,D)。C、D都是A的传递性依赖。 我们在使用A的时候只需要指出他的直接依赖,maven会自动帮我们解析出我们所需要的间接依赖,而不会引入多余的包。 如果间接依赖引用了同一个项目,但版本不同时,maven解析有两个优先原则: 深度策略。A->B->C(1.0) A->C(2.0)。此时导入C(2.0) FCFS策略。A->B->C(1.0) A->B->C(2.0)。此时导入C(1.0) 排除依赖1234567891011<dependency> <groupId>com.alibaba.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>3.2.7</version> <exclusions> <exclusion> <groupId>apache-lang</groupId> <artifactId>commons-lang</artifactId> </exclusion> </exclusions></dependency> 引入了rocketmq,但是不想引入rocketmq里面的apache-lang就可以用exclusions元素进行排除。 排除的时候只需要定位groupId和artifactId就可以确定这个依赖。 setting.XMLlocalRepository本地仓库路径默认值为:$ {user.home} /.m2 / repository1<localRepository>D:/Program Files/Apache/maven-repository</localRepository> interactiveModeMaven是否应该尝试与用户进行交互以进行输入。默认为:true usePluginRegistryMaven是否单独使用plugin-registry.xml文件来管理插件版本默认值为:false offline设置maven是否应该在全离线模式下运行。默认值为:false proxiesproxies表示maven的代理123456789101112<proxies> <proxy> <id>optional</id> <active>true</active> <protocol>http</protocol> <username>proxyuser</username> <password>proxypass</password> <host>proxy.host.net</host> <port>80</port> <nonProxyHosts>local.net|some.host.com</nonProxyHosts> </proxy></proxies> proxies下可以设置多个proxy,使用ID进行唯一标识区分。 id: 每个代理的唯一标识. active: 代理的激活状态,默认值为:true。设置多个代理时,使用第一个active为true的代理。 protocol: 代理使用的协议。默认值为:http username: 代理需要认证时的用户名。 password: 代理需要认证时的密码。 host: 主机名 port: 端口号,默认值为:8080 nonProxyHosts: 表示指定哪些主机名不需要代理,可以用”|” 分隔多个主机名,也支持通配符”*“; server用于连接远程仓库时的安全认证123456789101112<servers> <server> <id>nexus-releases</id> <username>deployment</username> <password>deployment</password> <privateKey>${usr.home}/.ssh/id_dsa</privateKey> <passphrase>some_passphrase</passphrase> <filePermissions>664</filePermissions> <directoryPermissions>775</directoryPermissions> <configuration></configuration> </server></serves> id: id是最关键的,这个id必须与需要认证的repository元素的id完全一致才行,换句话说,正式这个id将认证信息和仓库配置联系在了一起。 username: 用户名 password: 密码 privateKey: 鉴权时使用的私钥位置 passphrase: 鉴权时使用的私钥密码。 filePermissions: 文件被创建时的权限。如果在部署的时候会创建一个仓库文件或者目录,这时候就可以使用权限(permission)。 directoryPermissions: 目录被创建时的权限 configuration: 传输层额外的配置项 mirrors仓库的镜像12345678<mirrors> <mirror> <id>nexus</id> <name>all repository mirror</name> <url>http://172.16.21.3:8081/nexus/content/groups/public/</url> <mirrorOf>*</mirrorOf> </mirror></mirrors> id,name,url与仓库的配置相同 id: 仓库的唯一标识。 name: 仓库名 url: 仓库的地址.http://maven.net.cn/content/groups/public/ 是 中央仓库 http://repo1.maven.org/maven2/ 在中国的镜像。 mirrorOf:*表示任何对中央仓库的请求都会被转到镜像仓库中。profile个性配置文件,可以通过不同的方式激活。激活条件全部满足时激活该配置文件 1234567891011121314151617181920<profiles> <profile> <id>test</id> <activation> ··· </activation> <repositories> <repository> ··· </repository> </repositories> <pluginRepositories> <pluginRepository> ··· </pluginRepository> </pluginRepositories> </profile> </profiles> id: 配置文件的唯一标识 activation: 激活方式 repositories: 依赖仓库 pluginRepositories: 插件仓库 activation123456789101112131415161718<activation> <activeByDefault>false</activeByDefault> <jdk>1.6</jdk> <os> <name>Windows 7</name> <family>Windows</family> <arch>x86</arch> <version>5.1.2600</version> </os> <property> <name>mavenVersion</name> <value>2.0.3</value> </property> <file> <exists>${basedir}/file2.properties</exists> <missing>${basedir}/file1.properties</missing> </file></activation> activeByDefault: 当设置为true时,没有其他profile激活时,自动激活。 jdk: 当JDK版本满足时激活,可以用开闭区间表示一个JDK满足的范围。 os: 当操作系统满足时激活 name: 操作系统 family: 操作系统类型 arch: 操作系统位数 version: 操作系统版本 property: 键值对的形式,当只存在name时。hello属性存在,即可激活。当name,value都存在时,hello属性存在,并且hello属性的value为world时可以激活。 name: hello value: world file: 表示当文件存在或不存在的时候激活 exists: 该路径上的文件存在时激活 missing: 该路径上的文件不存在时激活 repository123456789101112131415161718<repositories> <repository> <id>codehausSnapshots</id> <name>Codehaus Snapshots</name> <releases> <enabled>false</enabled> <updatePolicy>always</updatePolicy> <checksumPolicy>warn</checksumPolicy> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>never</updatePolicy> <checksumPolicy>fail</checksumPolicy> </snapshots> <url>http://snapshots.maven.codehaus.org/maven2</url> <layout>default</layout> </repository></repositories> repositories中可以有多个repository,使用ID进行唯一标识区分。 id: 每个中央仓库的唯一标识。Maven自带的中央仓库使用的id为central,如果其他仓库声明也用该id,就会覆盖中央仓库的配置。 name: 仓库名 url: 中央仓库的地址。 releases: 表示开启仓库的发布版本下载支持。 enabled: 启用状态。默认值为:true。 updatePolicy: 更新时间,可选值有(always、daily、interval:minutes和never) always: 始终 daily: 每天 interval:minutes: 指定时间间隔(单位为分钟) never: 从不 checksumPolicy: 当Maven在部署项目到仓库的时候会连同校验文件一起提交,checksumPolicy表示当这个校验文件缺失或不正确的时候该如何处理,可选项有ignore、fail和warn。 ignore: 忽略 fail: 失败 warn: 警告 snapshots: 表示关闭仓库的快照版本下载支持。参数与releases类似 pluginRepository插件仓库与依赖仓库配置类似12345678910111213<pluginRepositories> <pluginRepository> <id>central</id> <name>Central Repository</name> <url>http://central</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository></pluginRepositories> activeProfiles手动激活的构建配置文件列表,按照应用顺序指定。1234<activeProfiles> <activeProfile>alwaysActiveProfile</activeProfile> <activeProfile>anotherAlwaysActiveProfile</activeProfile></activeProfiles> 上述配置表示激活所有。 pluginGroups插件组123<pluginGroups> <pluginGroup>org.mortbay.jetty</pluginGroup></pluginGroups> 添加了上面的插件后就可以使用 1mvn jetty:run]]></content>
<categories>
<category>框架</category>
</categories>
<tags>
<tag>maven</tag>
</tags>
</entry>
<entry>
<title><![CDATA[memchahed介绍]]></title>
<url>%2F2018%2F10%2F11%2F%E6%A1%86%E6%9E%B6-memchahed%E4%BB%8B%E7%BB%8D%2F</url>
<content type="text"><![CDATA[abstract 我们先抛出几个问题。 什么是缓存?为什么要有缓存?有哪些缓存技术以及他们的优缺点?]]></content>
<categories>
<category>框架</category>
</categories>
</entry>
<entry>
<title><![CDATA[javaScript]]></title>
<url>%2F2018%2F08%2F29%2F%E5%89%8D%E7%AB%AF-javaScript%2F</url>
<content type="text"><![CDATA[abstract 字符串操作substring截取字符串(“有始无终”) var s = "abcd"; s.substring(1,3); -> "bc" $.trim()$.trim() 函数用于去除字符串两端的空白字符。 var s = " 123 123 456 " $.trim(s); -> "123 123 456" AJAX一、$.ajax()该方法是 jQuery 底层 AJAX 实现。 1、async类型:Boolean 默认值: true。 默认设置下,所有请求均为异步请求。如果需要发送同步请求,请将此选项设置为 false。注意,同步请求将锁住浏览器,用户其它操作必须等待请求完成才可以执行。两条并列AJAX要区分先后顺序的话应采取同步 2、cache类型:Boolean 默认值: true。 dataType 为 script 和 jsonp 时默认为 false。设置为 false 将不缓存此页面。 3、contentType类型:String 默认值: “application/x-www-form-urlencoded”。 请求发送信息至服务器时内容编码类型。默认值适合大多数情况。如果你明确地传递了一个content-type给 $.ajax() 那么它必定会以设定类型发送给服务器(即使没有数据要发送)。 4、success类型:Function 请求成功后的回调函数。 参数:由服务器返回,并根据 dataType 参数进行处理后的数据;描述状态的字符串。 这是一个 Ajax 事件。 5、error类型:Function 默认值: 自动判断 (xml 或 html)。请求失败时调用此函数。 有以下三个参数:XMLHttpRequest 对象、错误信息、(可选)捕获的异常对象。 如果发生了错误,错误信息(第二个参数)除了得到 null 之外,还可能是 “timeout”, “error”, “notmodified” 和 “parsererror”。 这是一个 Ajax 事件。 6、type类型:String 默认值: (“GET”)。 请求方式 (“POST” 或 “GET”), 默认为 “GET”。注意:其它 HTTP 请求方法,如 PUT 和 DELETE 也可以使用,但仅部分浏览器支持。 7、url类型:String 默认值: 当前页地址。 发送请求的地址。 8、dataType类型:String 预期接收服务器返回的数据类型。如果不指定,jQuery 将自动根据 HTTP 包 MIME 信息来智能判断. “xml”: 返回 XML 文档,可用 jQuery 处理。 “html”: 返回纯文本 HTML 信息;包含的 script 标签会在插入 dom 时执行。 “script”: 返回纯文本 JavaScript 代码。不会自动缓存结果。除非设置了 “cache” 参数。注意:在远程请求时(不在同一个域下),所有 POST 请求都将转为 GET 请求。(因为将使用 DOM 的 script标签来加载) “json”: 返回 JSON 数据 。 “jsonp”: JSONP 格式。使用 JSONP 形式调用函数时,如 “myurl?callback=?” jQuery 将自动替换 ? 为正确的函数名,以执行回调函数。 “text”: 返回纯文本字符串 9、data类型:String 发送到服务器的数据。将自动转换为请求字符串格式。GET 请求中将附加在 URL 后。查看 processData 选项说明以禁止此自动转换。必须为 Key/Value 格式。如果为数组,jQuery 将自动为不同值对应同一个名称。如 {foo:[“bar1”, “bar2”]} 转换为 ‘&foo=bar1&foo=bar2’。 $.ajax({ type: "POST", async: true, cache: true, url: "ajax.php", dataType: "json", data: {"username": "bbdog","password": 123456}, contentType: "", success: function(msg) { console.log(msg) }, error: function() { console.log("error") } }) 二、$.post()语法jQuery.post(url,data,success(data, textStatus, jqXHR),dataType) url必需。规定把请求发送到哪个 URL。 data可选。映射或字符串值。规定连同请求发送到服务器的数据。 success(data, textStatus, jqXHR)可选。请求成功时执行的回调函数。 dataType可选。规定预期的服务器响应的数据类型。 默认执行智能判断(xml、json、script 或 html)。 三、$.get()$(selector).get(url,data,success(response,status,xhr),dataType) 参数于post()方法类似 选择器.val().html().text() 操作DOMremove()和empty()删除元素/内容 如需删除元素和内容,一般可使用以下两个 jQuery 方法: remove() - 删除被选元素(及其子元素) empty() - 从被选元素中删除子元素 append()和appendTo$(selector).append(content) $(selector).append(function(index,html)) $(content).appendTo(selector) Tips:append() 和 appendTo() 方法执行的任务相同。不同之处在于:内容和选择器的位置,以及 append() 能够使用函数来附加内容。 .val().html().text() text()//返回文本内容 $(selector).text(); //设置文本内容 $(selector).text(content); //使用函数设置内容 $(selector).text(function(index,oldcontent)) $(selector).attr({attribute:value, attribute:value ...}) class//添加样式 $(selector).addClass(class) //同时添加多个中间用空格隔开 //移除样式 $(selector).removeClass(class) 属性attr //返回属性值 $(selector).attr(attribute) //设置属性值 $(selector).attr(attribute,value) //移除属性值 $(selector).removeAttr(attribute) 可以操作标签的属性和样式的属性;css //返回属性值 $("p").css("background-color"); //设置属性值 $("p").css("background-color","yellow"); 隐藏与显示查看显示与隐藏 $('div:visible'); // 所有可见的div $('div:hidden'); // 所有隐藏的div 1.hide()和show() //隐藏 $(selector).hide(speed,callback); //显示 $(selector).show(speed,callback); 可选的 speed 参数规定隐藏/显示的速度,可以取以下值:"slow"、"fast" 或毫秒。 可选的 callback 参数是隐藏或显示完成后所执行的函数名称。 2.toggle() //显示被隐藏的元素,并隐藏已显示的元素: $(selector).toggle(speed,callback); 3.css(‘display’,*) $("#id").css('display','none');//隐藏 $("#id").css('display','block');//显示 或者 $("#id")[0].style.display='none'; 4.css(‘visibility’,*) $("#id").css('visibility','hidden');//元素隐藏 $("#id").css('visibility','visible');//元素显示 display:none与visible:hidden的区别 visible保留'物理空间',display不保留'物理空间' 数据类型转换typeof()typeof()可以用来检测给定变量的数据类型,它将返回一个字符串,表示表达式的类型,而表达式的类型只有六种可能: number string boolean object function undefined 转为数字类型转为Int var iNum1 = parseInt(“1234blue”); -> 1234 var iNum2 = parseInt(“oxA”); -> 10 var iNum3 = parseInt(“22.5″); -> 22 var iNum4 = parseInt(“blue”); -> NaN 基模式转为Int var iNum1 = parseInt(“AF”,16); -> 175 var iNum2 = parseInt(“10″,2); -> 2 var iNum3 = parseInt(“10″,8); -> 8 var iNum4 = parseInt(“10″,10); -> 10 如果以0开头最好使用十进制基模式 var iNum1 = parseInt(“010″); -> 8 转为Float var fNum1 = parseFloat(“1234blue”); -> 1234.0 var fNum2 = parseFloat(“0xA”); -> NaN var fNum3 = parseFloat(“.22.5″); -> 0.22 var fNum5 = parseFloat(“0908″); -> NaN var fNum6 = parseFloat(“blue”); -> NaN 转为字符串类型使用toString()方法,常用源数据有Boolean、number、String 1.Boolean var b = true; b.toString(); -> "true" 2.number[默认模式] var num1 = 10.00; num1.toString(); -> "10" 3.number[基(进制基数)模式] var iNum = 10; alert(iNum.toString(2)); -> “1010″ alert(iNum.toString(8)); -> “12″ alert(iNum.toString(16)); -> “A” replace()String.replace(regexp/substr,replacement); var s = "abcd" s = s.replace("a","z"); -> "zbcd" s = s.replace(/c/,"z"); -> "zbzd" s = s.replace(/z/g,"a"); -> "abad" s = s.replace() 将json对象序列化为json字符串contentType: ‘application/json’JSON.stringify(JSONObject) 服务器端可以用JSON.parse将json串还原成JSONObject 数字计算“四舍六入五成双”的toFixed 当有效位后一位,≤4 时舍去,≥6时进1;当等于5时,先判断5后是否还有数,有则进;无数时再判断5数的奇偶性,前为奇数则进1,否则舍去。不同浏览器执行不同ES标准,计算时可能有所不同(尤其IE浏览器) toFixed()返回数字形式的字符串,可参与运算可以用toFixed补全小数点数。 取整Math.round()可先*100 再/100 用于四舍五入。 和传统四舍五入最接近,当有效位后一位,≤4 时舍去,≥6时进1,当等于5时,向正无穷方向取整 待认领window.confirm .dialog .parents $(this) .siblings .not .eq .each .prev .live .is .before .match 数据为空的几种方式null “” undefined正则]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>javaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[IntelliJ IDEA]]></title>
<url>%2F2018%2F08%2F02%2F%E5%B7%A5%E5%85%B7-IntelliJ-IDEA%2F</url>
<content type="text"><![CDATA[abstract]]></content>
<categories>
<category>工具</category>
</categories>
</entry>
<entry>
<title><![CDATA[FreeMarker]]></title>
<url>%2F2018%2F08%2F02%2F%E5%89%8D%E7%AB%AF-FreeMarker%2F</url>
<content type="text"><![CDATA[abstract]]></content>
<categories>
<category>前端</category>
</categories>
</entry>
<entry>
<title><![CDATA[SSM]]></title>
<url>%2F2018%2F08%2F02%2F%E6%A1%86%E6%9E%B6-SSM%2F</url>
<content type="text"><![CDATA[abstract 主标签次级标签CRUD标签### 通用参数id 唯一标识parameterType 出入参数类型 flushCachestatementTypetimeout resultTyperesultMapuseCache useGeneratedKeyskeyProperty其他参数:]]></content>
<categories>
<category>框架</category>
</categories>
</entry>
<entry>
<title><![CDATA[SQL]]></title>
<url>%2F2018%2F08%2F02%2F%E6%95%B0%E6%8D%AE%E5%BA%93-SQL%2F</url>
<content type="text"><![CDATA[abstract]]></content>
<categories>
<category>数据库</category>
</categories>
</entry>
<entry>
<title><![CDATA[HTML]]></title>
<url>%2F2018%2F08%2F02%2F%E5%89%8D%E7%AB%AF-HTML%2F</url>
<content type="text"><![CDATA[abstract]]></content>
<categories>
<category>前端</category>
</categories>
</entry>
<entry>
<title><![CDATA[关于如何学习方法的总结]]></title>
<url>%2F2018%2F08%2F02%2F%E6%8A%80%E5%B7%A7-%E5%85%B3%E4%BA%8E%E5%A6%82%E4%BD%95%E6%8A%80%E6%9C%AF%E5%AD%A6%E4%B9%A0%E6%96%B9%E6%B3%95%E7%9A%84%E6%80%BB%E7%BB%93%2F</url>
<content type="text"><![CDATA[记一些学习技术的方法,用高效的方法去学习新的东西,花点时间去学习 学习的技巧收益会比“努力学习”更高,就像提升加速度比提升速度走的更远。 给一个时间限度首先需要先初步了解要学习的技术,对需要掌握的点有个初步认识。再根据自己可支配的学习时间估算出一个比较合理的截止时间。 动手敲敲怎么防止不学完就忘好记性不如烂笔头,可以搭建一个自己的博客,将自己的收获写在里面。不在乎有多少人去看,重在在于这个整理的过程。费曼学习法中指出,当你能熟练的把一个东西向别人讲述的时候,你也就学会了这个东西。 不放过细小的部分,只要是写出来的东西都要对他负责,不用整理的很细致,把涉及的源码都剖析一遍。重在整个文章能围绕一条中心线走下去。看起来很有层次性。 当你把写博客成为一种习惯,时间长了看看之前写的博客对自己也是一种收获。]]></content>
<categories>
<category>技巧</category>
</categories>
<tags>
<tag>学习方法</tag>
<tag>高效</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hello World]]></title>
<url>%2F2017%2F12%2F25%2Fhello-world%2F</url>
<content type="text"><![CDATA[Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. 勿忘启蒙之师 Quick StartCreate a new post1$ hexo new "My New Post" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment]]></content>
</entry>
</search>