Pieces

至今一切社会的历史都是阶级斗争的历史。
  自由民和奴隶、贵族和平民、领主和农奴、行会师傅和帮工,一句话,压迫者和被压迫者,始终处于相互对立的地位,进行不断的、有时隐蔽有时公开的斗争,而每一次斗争的结局是整个社会受到革命改造或者斗争的各阶级同归于尽。
  在过去的各个历史时代,我们几乎到处都可以看到社会完全划分为各个不同的等级,看到社会地位分成的多种多样的层次。在古罗马,有贵族、骑士、平民、奴隶,在中世纪,有封建主、臣仆、行会师傅、帮工、农奴,而且几乎在每一个阶级内部又有一些特殊的阶层。
  从封建社会的灭亡中产生出来的现代资产阶级社会并没有消灭阶级对立。它只是用新的阶级、新的压迫条件、新的斗争形式代替了旧的。
  。。。
    由此可见,现代资产阶级本身是一个长期发展过程的产物,是生产方式和交换方式的一系列变革的产物。
  。。。
  资产阶级在它的不到一百年的阶级统治中所创造的生产力,比过去一切世代创造的全部生产力还要多,还要大。自然力的征服,机器的采用,化学在工业和农业中的应用,轮船的行驶,铁路的通行,电报的使用,整个整个大陆的开垦,河川的通航,仿佛用法术从地下呼唤出来的大量人口,——过去哪一个世纪料想到在社会劳动里蕴藏有这样的生产力呢?
  。。。
  资产阶级生存和统治的根本条件,是财富在私人手里的积累,是资本的形成和增殖;资本的条件是雇佣劳动。雇佣劳动完全是建立在工人的自相竞争之上的。资产阶级无意中造成而又无力抵抗的工业进步,使工人通过结社而达到的革命联合代替了他们由于竞争而造成的分散状态。于是,随着大工业的发展,资产阶级赖以生产和占有产品的基础本身也就从它的脚下被挖掉了。它首先生产的是它自身的掘墓人。资产阶级的灭亡和无产阶级的胜利是同样不可避免的。
  。。。
  社会主义的资产者愿意要现代社会的生存条件,但是不要由这些条件必然产生的斗争和危险。他们愿意要现存的社会,但是不要那些使这个社会革命化和瓦解的因素。他们愿意要资产阶级,但是不要无产阶级。在资产阶级看来,它所统治的世界自然是最美好的世界。资产阶级的社会主义把这种安慰人心的观念制成半套或整套的体系。它要求无产阶级实现它的体系,走进新的耶路撒冷,其实它不过是要求无产阶级停留在现今的社会里,但是要拋弃他们关于这个社会的可恶的观念。
  这种社会主义的另一种不够系统、但是比较实际的形式,力图使工人阶级厌弃一切革命运动,硬说能给工人阶级带来好处的并不是这样或那样的政治改革,而仅仅是物质生活条件即经济关系的改变。但是,这种社会主义所理解的物质生活条件的改变,绝对不是只有通过革命的途径才能实现的资产阶级生产关系的消灭,而是一些行政上的改良,这些改良是在这种生产关系的基础上实行的,因而丝毫不会改变资本和雇佣劳动的关系,至多只能减少资产阶级的统治费用和简化它的财政管理。
  资产阶级的社会主义只有在它变成纯粹的演说辞令的时候,才获得自己的适当的表现。
  自由贸易!为了工人阶级的利益;保护关税!为了工人阶级的利益;单身牢房!为了工人阶级的利益。——这才是资产阶级的社会主义唯一认真说出的最后的话。
  资产阶级的社会主义就是这样一个论断:资产者之为资产者,是为了工人阶级的利益。
  。。。   
    共产党人不屑于隐瞒自己的观点和意图。他们公开宣布:他们的目的只有用暴力推翻全部现存的社会制度才能达到。让统治阶级在共产主义革命面前发抖吧。无产者在这个革命中失去的只是锁链。他们获得的将是整个世界。

定位 Guava EventBus 重复消费

问题描述

我们在系统中使用 Guava 的 EventBus 进行代码解耦,一直运行良好,但是在某一次发版后,出现了消息重复消费的情况,示例代码如下:

1
2
3
4
5
6
7
8
9
AbstractListener.java //抽象父类
public abstract class AbstractListener<T> implements InitializingBean {
@Override
public void afterPropertiesSet() { //系统初始化时进行消费者注册
GuavaEventBusFactory.getDefault().eventBus().register(this);
}
protected abstract void onEvent(T t) throws Exception;
}

1
2
3
4
5
6
7
8
9
10
11
12
CallNotifyListener.java //具体业务类,对事件进行消费
@Component
@Lazy(false)
public class CallNotifyListener extends AbstractListener<CallBack> {
Logger logger = LoggerFactory.getLogger(CallNotifyListener.class);
@Subscribe
@Override
public void onEvent(CallBack event) throws Exception {
logger.info("CallNotifyListener onEvent :{},{}", event.getAppid(),event);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
ScheduledTasks.java //事件生产者
@Component
public class ScheduledTasks {
Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);
@Scheduled(fixedRate = 2000)
public void scheduleTaskWithFixedRate() {
logger.info("send msg - {}",new Date());
CallBack callBack = new CallBack(1,System.currentTimeMillis());
GuavaEventBusFactory.getDefault().eventBus().post(callBack);
}
}

预期执行情况:

1
2
3
4
5
2018-08-11 20:49:22.728 INFO 27619 --- [ main] c.a.eventbus.EventBusTestApplication : Started EventBusTestApplication in 1.476 seconds (JVM running for 1.848)
2018-08-11 20:49:24.724 INFO 27619 --- [pool-2-thread-1] com.acvrock.eventbus.ScheduledTasks : send msg - Sat Aug 11 20:49:24 CST 2018
2018-08-11 20:49:24.725 INFO 27619 --- [pool-1-thread-2] com.acvrock.eventbus.CallNotifyListener : CallNotifyListener onEvent :1533991764724,CallBack{chid=1, appid=1533991764724}
2018-08-11 20:49:26.726 INFO 27619 --- [pool-2-thread-1] com.acvrock.eventbus.ScheduledTasks : send msg - Sat Aug 11 20:49:26 CST 2018
2018-08-11 20:49:26.727 INFO 27619 --- [pool-1-thread-3] com.acvrock.eventbus.CallNotifyListener : CallNotifyListener onEvent :1533991766727,CallBack{chid=1, appid=1533991766727}

实际执行情况:

1
2
3
4
5
6
7
2018-08-11 20:51:20.279 INFO 27674 --- [ main] c.a.eventbus.EventBusTestApplication : Started EventBusTestApplication in 1.062 seconds (JVM running for 1.442)
2018-08-11 20:51:22.271 INFO 27674 --- [pool-2-thread-1] com.acvrock.eventbus.ScheduledTasks : send msg - Sat Aug 11 20:51:22 CST 2018
2018-08-11 20:51:22.272 INFO 27674 --- [pool-1-thread-3] com.acvrock.eventbus.CallNotifyListener : CallNotifyListener onEvent :1533991882271,CallBack{chid=1, appid=1533991882271}
2018-08-11 20:51:22.272 INFO 27674 --- [pool-1-thread-4] com.acvrock.eventbus.CallNotifyListener : CallNotifyListener onEvent :1533991882271,CallBack{chid=1, appid=1533991882271}
2018-08-11 20:51:24.275 INFO 27674 --- [pool-2-thread-1] com.acvrock.eventbus.ScheduledTasks : send msg - Sat Aug 11 20:51:24 CST 2018
2018-08-11 20:51:24.275 INFO 27674 --- [pool-1-thread-2] com.acvrock.eventbus.CallNotifyListener : CallNotifyListener onEvent :1533991884275,CallBack{chid=1, appid=1533991884275}
2018-08-11 20:51:24.275 INFO 27674 --- [pool-1-thread-5] com.acvrock.eventbus.CallNotifyListener : CallNotifyListener onEvent :1533991884275,CallBack{chid=1, appid=1533991884275}

可以看到消费者对事件消费了两次,但是通过检查代码发现并没有相关的改动,机器环境也没有变动。
经过一番定位后发现是编译机器 JDK 版本升级到 JDK 8,新版 javac 编译后字节码有变动。
导致相同代码和环境下出现了不同的行为。

定位过程:

对代码调用进行跟踪,先从 EventBus.post 方法看起

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
public void post(Object event) {
Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());
boolean dispatched = false;
for (Class<?> eventType : dispatchTypes) {
subscribersByTypeLock.readLock().lock();
try {
Set<EventSubscriber> wrappers = subscribersByType.get(eventType);
if (!wrappers.isEmpty()) {
dispatched = true;
for (EventSubscriber wrapper : wrappers) {
enqueueEvent(event, wrapper);
}
}
} finally {
subscribersByTypeLock.readLock().unlock();
}
}
if (!dispatched && !(event instanceof DeadEvent)) {
post(new DeadEvent(this, event));
}
dispatchQueuedEvents();
}

debug 时发现在 JDK 7 环境下,subscribersByType.get(eventType) 只能获取到一个 EventSubscriber 对象,即:onEvent(CallBack event)。
而在 JDK 8 环境下,subscribersByType.get(eventType) 的确能够获取到两个 EventSubscriber 对象,分别是:onEvent(CallBack event)和onEvent(Object o), 导致 enqueueEvent 了两次
subscribersByType 对象是一个类似 Map 结构,用于存储 EventSubscriber 对象,初始化数据的位置在 EventBus.register中:

1
2
3
4
5
6
7
8
9
10
public void register(Object object) {
Multimap<Class<?>, EventSubscriber> methodsInListener =
finder.findAllSubscribers(object);
subscribersByTypeLock.writeLock().lock();
try {
subscribersByType.putAll(methodsInListener);
} finally {
subscribersByTypeLock.writeLock().unlock();
}
}

这里 findAllSubscribers 的实现是在 AnnotatedSubscriberFinder 类中,逻辑比较简单,先调用 getAnnotatedMethods(clazz) 获取类中有 @Subscribe 注解的方法列表,然后实例化成 EventSubscriber 对象,存进 methodsInListener 并返回。
JDK 7 环境下 getAnnotatedMethods(clazz) 返回了 onEvent(CallBack event) 方法
JDK 8 环境下 getAnnotatedMethods(clazz) 返回了 onEvent(CallBack event) 方法和 onEvent(Object o) 方法,但是 onEvent(Object o) 不是我们定义的方法,而是 JDK 实现泛型时生成的桥方法,可以看成是在父类中定义的 onEvent(T t) 擦除泛型转换而来,并没有带 @Subscribe

1
2
3
4
5
6
7
8
9
10
11
public Multimap<Class<?>, EventSubscriber> findAllSubscribers(Object listener) {
Multimap<Class<?>, EventSubscriber> methodsInListener = HashMultimap.create();
Class<?> clazz = listener.getClass();
for (Method method : getAnnotatedMethods(clazz)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> eventType = parameterTypes[0];
EventSubscriber subscriber = makeSubscriber(listener, method);
methodsInListener.put(eventType, subscriber);
}
return methodsInListener;
}

getAnnotatedMethods(clazz) 方法中使用了 LoadingCache 缓存,最后获取注解的方法是:getAnnotatedMethodsInternal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static ImmutableList<Method> getAnnotatedMethodsInternal(Class<?> clazz) {
Set<? extends Class<?>> supers = TypeToken.of(clazz).getTypes().rawTypes();
Map<MethodIdentifier, Method> identifiers = Maps.newHashMap();
for (Class<?> superClazz : supers) {
for (Method superClazzMethod : superClazz.getMethods()) {
if (superClazzMethod.isAnnotationPresent(Subscribe.class)) {
Class<?>[] parameterTypes = superClazzMethod.getParameterTypes();
if (parameterTypes.length != 1) {
throw new IllegalArgumentException("Method " + superClazzMethod
+ " has @Subscribe annotation, but requires " + parameterTypes.length
+ " arguments. Event subscriber methods must require a single argument.");
}
MethodIdentifier ident = new MethodIdentifier(superClazzMethod);
if (!identifiers.containsKey(ident)) {
identifiers.put(ident, superClazzMethod);
}
}
}
}
return ImmutableList.copyOf(identifiers.values());
}

这里关键的一句判断是 superClazzMethod.isAnnotationPresent(Subscribe.class),isAnnotationPresent是 JDK reflect 包中的方法,经过调试,发现在 JDK 7 中,

1
2
onEvent(CallBack event) 方法返回 true,
onEvent(Object o) 方法返回 false,

而在 JDK 8 中,

1
2
onEvent(CallBack event) 方法返回 true,
onEvent(Object o) 方法返回 true。

导致返回了 onEvent(CallBack event) 和 onEvent(Object o),并初始化出两个 EventSubscriber。

原因

对比两个版本 javac 编译出来的字节码

JDK 7 版本

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
g6763$ javap -v CallNotifyListener.class
...
public class com.acvrock.eventbus.CallNotifyListener extends com.acvrock.eventbus.AbstractListener<com.acvrock.eventbus.CallBack>
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
...
#26 = Utf8 RuntimeVisibleAnnotations
#27 = Utf8 Lcom/google/common/eventbus/Subscribe;
#28 = Utf8 (Ljava/lang/Object;)V
...
{
...
public void onEvent(com.acvrock.eventbus.CallBack) throws java.lang.Exception;
...
Exceptions:
throws java.lang.Exception
RuntimeVisibleAnnotations:
0: #27()
public void onEvent(java.lang.Object) throws java.lang.Exception;
...
Exceptions:
throws java.lang.Exception
}
...

第一个方法 onEvent(com.acvrock.guavatest.CallBack) 有一个 #27 Subscribe 运行时注解 第二个方法 onEvent(java.lang.Object) 没有带 RuntimeVisibleAnnotations

JDK 8 版本

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
g6763$ javap -v CallNotifyListener.class
...
public class com.acvrock.eventbus.CallNotifyListener extends com.acvrock.eventbus.AbstractListener<com.acvrock.eventbus.CallBack>
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
...
#26 = Utf8 RuntimeVisibleAnnotations
#27 = Utf8 Lcom/google/common/eventbus/Subscribe;
#28 = Utf8 (Ljava/lang/Object;)V
...
{
...
public void onEvent(com.acvrock.eventbus.CallBack) throws java.lang.Exception;
...
Exceptions:
throws java.lang.Exception
RuntimeVisibleAnnotations:
0: #27()
public void onEvent(java.lang.Object) throws java.lang.Exception;
...
Exceptions:
throws java.lang.Exception
RuntimeVisibleAnnotations:
0: #27()
}
...

两个方法都有 运行时注解 #27(),也就是 Subscribe
在网上 JDK-8029563 : Method.getAnnotations() changed its return value in JDK 8. 这个描述和我们发现的问题是一致的
导致这一变动的是这个调整 JDK-6695379 : Copy method annotations and parameter annotations to synthetic bridge methods, javac 实现了在编译时,拷贝方法和参数上的注解到桥方法上,也就是 onEvent(java.lang.Object)上,在 JDK 7u80 和 JDK 8b94 后生效

guava 如何解决

在 guava 18 中就已经解决了这个问题,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static ImmutableList<Method> getAnnotatedMethodsInternal(Class<?> clazz) {
Set<? extends Class<?>> supers = TypeToken.of(clazz).getTypes().rawTypes();
Map<MethodIdentifier, Method> identifiers = Maps.newHashMap();
for (Class<?> superClazz : supers) {
for (Method superClazzMethod : superClazz.getMethods()) {
if (superClazzMethod.isAnnotationPresent(Subscribe.class)
&& !superClazzMethod.isBridge()) {
Class<?>[] parameterTypes = superClazzMethod.getParameterTypes();
if (parameterTypes.length != 1) {
throw new IllegalArgumentException("Method " + superClazzMethod
+ " has @Subscribe annotation, but requires " + parameterTypes.length
+ " arguments. Event subscriber methods must require a single argument.");
}
MethodIdentifier ident = new MethodIdentifier(superClazzMethod);
if (!identifiers.containsKey(ident)) {
identifiers.put(ident, superClazzMethod);
}
}
}
}
return ImmutableList.copyOf(identifiers.values());
}

判断了方法有 Subscribe 注解并且不是桥方法才认为是 subscribe 方法,对应的代码提交记录: Fix EventBus to not include bridge methods when registering subscribe
真是一个摸不着头脑的 bug

参考

关于桥方法可以参考 Effects of Type Erasure and Bridge Methods
示例代码Exercise-code/eventbusTest

静宏的 2017 -> 2018

一.2017 总结

2017 最大的变化是换了新公司,原公司因为一些原因暂停营业,加入 100 教育

成果

  • 工作
    高频的需求迭代,学习更强的时间管理能力,不轻易陷进代码实现细节,导致进度的拖延
    相比完善的基础服务和运维环境,助力解放开发过程中繁琐的环境搭建/配置/监控,大开眼界

  • 学习
    对 go 语言进行学习,初步掌握语法概念,需要找一个适当的项目进行练手
    看了一部分 mybatis 代码,抄了部分代码实现一个动态接口方法调用功能,感觉良好

二.2018 展望

  1. 把家里攒的书都看一遍
  2. 对 Spring stack 深入了解,特别是 spring 5
  3. Kotlin 看起来很诱惑,接触

记一次 CPU 忙碌导致的超时

通过业务日志发现客户端调用某一远程服务时不时连接超时,导致部分业务请求受影响,以下记录了定位的过程
首先收集 thrift 代理日志,统计该服务超时情况,结果如下:

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
**@ubuntu:/data/log/ServiceAgent_d$ zgrep 'time out' ServiceAgent_d.log.*.gz |awk '{print $3,$7,$12}'|grep od
11:11:20 od_getById ***.***.112.152_60008
14:11:25 od_listByParam ***.***.112.152_60008
19:11:25 od_getById ***.***.112.152_60008
19:11:27 od_getById ***.***.112.152_60008
00:12:02 od_ping ***.***.112.152_60008
05:10:30 od_ping ***.***.112.152_60008
05:11:37 od_ping ***.***.112.152_60008
21:11:37 od_getById ***.***.112.152_60008
21:11:49 od_listByParam ***.***.112.152_60008
10:10:11 od_listByParam ***.***.112.152_60008
11:11:31 od_listByParam ***.***.112.152_60008
14:10:24 od_listByParam ***.***.112.152_60008
14:11:34 od_getById ***.***.112.152_60008
11:11:39 od_listByParam ***.***.112.152_60008
13:11:39 od_listByParam ***.***.112.152_60008
20:11:26 od_listByParam ***.***.112.152_60008
21:11:33 od_listByPage ***.***.112.152_60008
21:11:38 od_listByParam ***.***.112.152_60008
10:11:43 od_listByParam ***.***.112.152_60008
11:11:26 od_getById ***.***.112.152_60008
14:11:36 od_getById ***.***.112.152_60008
17:11:42 od_getById ***.***.112.152_60008
11:10:10 od_listByParam ***.***.112.152_60008
15:10:13 od_listByParam ***.***.112.152_60008
19:11:26 od_listByParam ***.***.112.152_60008
14:10:30 od_listByPage ***.***.112.152_60008
16:10:18 od_listByPage ***.***.112.152_60008
20:10:30 od_listByParam ***.***.112.152_60008
**@ubuntu:/data/log/ServiceAgent_d$

左右端详,可以发现以下特点:具有周期性,而且集中在 152 机器上,都是每个小时的10分钟左右出现
接下来查看机器运行情况:

可看到 CPU 也是有规律的升高.
通过应用部署架构已知该主机上有定时任务 task 类型的应用,所以初步怀疑是某些 task 进程有问题

接下来进入该 152 机器,使用 top 命令观察 CPU,等待下一个周期,等到 11 分钟时,得到以下截图: 得知占用最大的程序为 gzip ,但是查不到 gzip 进行什么操作,无解
另辟蹊径,咨询运维得知:每小时的第 10 分钟,logrotate 会对日志进行压缩轮转,如下图:
所以定位的重点就变成:是否是某些日志设置不当,导致 logrotate 异常
检查 logrotate 运行状态:
grep 10-20 /var/lib/logrotate/status
检查 logrotate 操作的文件大小
grep 10-20 /var/lib/logrotate/status |awk -F '"' '{print $2}'|xargs ls -alh
倒数第二行,发现文件大小异常,检查日志文件所属程序,发现是遗留的 kafka 程序
解决方案: 关闭它
观察日志,没有发现超时,问题解决

记一次 SourceTree 错误

公司使用的 svn 服务器有限制,导致使用 SourceTree 一直获取不下来,因为无法在 SourceTree 中使用用户名密码进行认证,所以导致必须使用 git svn 先获取仓库到本地,然后再用 SourceTree 导入,但是导入后也同样会出现类似下面的错误:

1
2
3
4
5
6
7
8
9
10
11
git -c diff.mnemonicprefix=false -c core.quotepath=false -c credential.helper=sourcetree svn fetch
Authentication realm: <https://192.168.1.251:8443> VisualSVN Server
Password for 'jinke.jiang':
Authentication realm: <https://192.168.1.251:8443> VisualSVN Server
Username: Use of uninitialized value in concatenation (.) or string at /Applications/SourceTree.app/Contents/Resources/git_local/lib/perl5/site_perl/Git/SVN/Prompt.pm line 20.
Password for '':
Authentication realm: <https://192.168.1.251:8443> VisualSVN Server
Username: Use of uninitialized value in concatenation (.) or string at /Applications/SourceTree.app/Contents/Resources/git_local/lib/perl5/site_perl/Git/SVN/Prompt.pm line 20.
Password for '':
Can't create session: Unable to connect to a repository at URL 'https://192.168.1.251:8443/svn/jinguanjia_ios': No more credentials or we tried too many times.
Authentication failed at /Applications/SourceTree.app/Contents/Resources/git_local/lib/perl5/site_perl/Git/SVN.pm line 143.

原因是在使用 git svn 时输入的密码也没有进行保存,解决方法:

打开 ~/.subversion/servers ,把最后的

store-passwords = no
store-ssl-client-cert-pp = no
store-plaintext-passwords = no
store-ssl-client-cert-pp-plaintext = no

修改成:

store-passwords = yes
store-ssl-client-cert-pp = yes
store-plaintext-passwords = yes
store-ssl-client-cert-pp-plaintext = yes

然后再使用 git svn 再操作一遍以保存账号密码


参考链接:Can't make svn store password, even though the configuration is set to allow it

通过 U 盘驱动安装 Arch Linux

今天在一台老主机上安装了 Arch Linux,记录安装一下过程

格式化 U 盘

打开 Disk Utility 选择 U 盘点击 Erase,选择 MS-DOS(FAT),即格式为 FAT32 格式,点击确认,直到提示成功,点击 Unmount 按钮,取消挂载

查看 U 盘盘符

在 Terminal 下进行操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ diskutil list
/dev/disk0 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *251.0 GB disk0
1: EFI EFI 209.7 MB disk0s1
2: Apple_CoreStorage Macintosh HD 250.1 GB disk0s2
3: Apple_Boot Recovery HD 650.1 MB disk0s3
/dev/disk1 (internal, virtual):
#: TYPE NAME SIZE IDENTIFIER
0: Macintosh HD +249.8 GB disk1
Logical Volume on disk0s2
6EED22C6-6FD7-4444-AA83-A305369854DE
Unlocked Encrypted
/dev/disk2 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: FDisk_partition_scheme *8.0 GB disk2
1: DOS_FAT_32 U 8.0 GB disk2s1

根据容量判断哪个盘是 U 盘,这里为 /dev/disk2

将下载的 iso 文件写入 U 盘

1
2
3
4
5
$ sudo dd if=/Users/moon/Downloads/archlinux-2017.04.01-x86_64.iso of=/dev/rdisk2 bs=1m;sync
Password:
478+0 records in
478+0 records out
501219328 bytes transferred in 93.303333 secs (5371934 bytes/sec)

此处注意在disk2 前加 r,等待结束,此时 U 盘已经处理完毕,插入需要安装的电脑进行安装,下面可以参考两个文档进行安装,一个是 arch wiki,一个是百度经验的文档

开机选择 U 盘驱动,不同机器略有不同,我的机器是 F12 ,然后选中插入的 U 盘

下面使用百度的图,因为我的安装时忘记截图了

选择 Boot Arch Linux(x86-64) 或者 Boot Arch Linux(i686),我只有 Boot Arch Linux(x86-64)一个选项

进入命令行界面,先检测网络是否连接,安装时需要网络

使用 ping baidu.com 进行测试,退出需要按 Ctrl+c 中断

磁盘分区,格式化

输入 lsblk 查看硬件设备,根据容量判断哪个是硬盘,一般都是 sda

输入 fdisk /dev/sda 对硬盘进行分区, fdisk 的各种命令请输入 m 进行查看,一般是用 p 查看分区表情况,用 d 删除分区,n 新建分区,w 保存分区,保存后退出

回到 shell 界面后输入 mkfs.ext4 /dev/sda1 进行格式化,格式化为 ext4 文件格式

挂载硬盘

输入 mount /dev/sda1 /mnt 将分区挂载到 /mnt 上

修改源

按照 arch wiki 的步骤,现在就可以进行系统安装了,但是为了加速安装,我们需要把安装源修改成国内地址。
步骤:输入 vi /etc/pacman.d/mirrorslist 打开文件
在命令模式下输入 /163 回车查找 163 的源,它的位置在首屏的十几行的位置
这时候输入 yy 进行复制,然后移动光标到第一行
输入 p 进行粘贴,然后保存退出,修改源完毕
再安装时就会优先取 163 源里的数据

安装

输入 pacstrap /mnt base

安装完毕后,输入 arch-chroot /mnt,可能需要再输入 sh 接入 sh 环境

安装 grub

输入 pacman -S grub

配置 grub

输入 grub-install /dev/sda

配置 boot 文件

输入 grub-mkconfig -o /boot/grub/grub.cfg

至此,全部操作完毕,输入 exit 退出,输入 reboot 重启

如何摆脱对微博的依赖

为什么要戒掉微博

写代码时,如果注意力高度集中,这时候就容易出成果,但是如果走神的话很容易就磨洋工。
造成走神的原因有很多,比如外部的原因:写代码时接到一个推销电话、同事过来请教一个问题等等,也有我们自身的原因,如:编译代码时无聊刷刷微博、看看 Q 群、逛逛淘宝。
外部的干扰因素可以通过物理的方式来解决!比如带一个头戴式耳机,提高被骚扰的难度,但是自身的原因就控制不了啦~毕竟每天总有那么几个小时在 building,这时候如何克制住心理的魔鬼,不去看那些新鲜事呢?

网上也有类似的文章,一般的解决方法有这几种:

  1. 找件事来替代微博,形成一种新的行为习惯。例如原本你晚上都闲着上网的,现在你每晚去游泳1小时,不带手机,回来后不急着开电脑,陪家人看会儿电视……然后刷会儿微博,尽量不留言和人做无营养的交流互动,批判性的审视自己关注的人的到底有多少讯息是对自己有益的,转变对微博的看法……
  2. 转跳注意力:调整关注人群,选择一些能有所长进和思考的人关注,及时在阅读过程中专跳出去,比如你看到某名博上关于某社会事件的争论,在稍微看浏览完相关信息和大众观点后,跳出来,拿纸笔或者在电脑上记录下你的所想和议论,(我这种笨人随便写点什么都会花掉大半个晚上的时间),或者是看到某人推荐一本书,你就隔日买来进行阅读,总之就是把微博作为一个讯息点,扩展其内容到别的主体上,具体因人而异;
  3. 如果你很难脱离微博,也可以用延迟的办法,将一日的内容分为几次来刷,强迫自己,只有下班回家路上才刷一次,追溯一下早上的内容,也不要三五分钟就叮当一下。

但是还是避免不了每当 IDEA 编译时,不假思索地以迅雷不及掩耳之势点击 Chrome 图标,敲下 weibo.com 并回车...

这时候,我们需要一种强制性措施

打开终端,输入 vim /etc/hosts,打开 hosts 文件,在最下面添加一行
127.0.0.1 weibo.com
这时候再不假思索地点击 Chrome 图标,敲下 weibo.com 并回车时,就会进入一个空白页了

全文完

但是如果没有微博,我们自然会有新的消磨时间的工具,如 v2ex.com,reddit.com,因为总有那么几段时间是等待的

不妨将我们的注意力引导到更加积极的地方去,比如说 稀土掘金segmentfault
说干就干:
打开终端,先 ping 一下 e.xitu.io 的网站,得到 ip 为 207.226.141.170 ,然后 输入 vim /etc/hosts,打开 hosts 文件,在最下面添加一行
127.0.0.1 weibo.com
修改完毕在访问 weibo.com,但是这个时候却不能出现我们希望看到的 e.xitu.io 的首页,看起来对方服务器反向代理时处理了,所以只能换另外一种方式
--未完待续

静宏的 2016 -> 2017

一.2016 总结

一句话总结的话:"一个 Java 工程师平静的一年"
从这句话可以隐隐约约闻出不好的气息,一个开发工程师如果觉得稳定,那么他应该考虑开始准备简历了,我觉得一份优秀的工作,应该是业务不断地促使技术的迭代,所以开发工程师注定闲不下来,促成技术的进步,2016 年工作一直很平稳,业务量并不大,导致一直在重复地 CRUD,所以我把精力放在了各种技术的拓展上,从 Spring 到 Spring boot,从各种 NoSql 数据库、缓存到各种消息队列,从 Dubbo 到 Spring Cloud,现在我已经学会如何利用 Github 进行有效地学习、工作
先看一下 2014 到现在的计划:

  • (每到报名时间都懒癌发作- -!)
  • (基本完成,下一步目标是把学习时间段移到早上)
  • (基本完成,使用文本按日期+各种 checklist 记录)
  • (基本完成,但是一般都没有记录)
  • (懒癌发作- -!必须走出舒适区,读书学习就是我的舒适区,创造力硬伤)
  • (写代码的人如果很少接触命令行说明他在划水,只热衷于命令行说明他是一个顽固派,热爱命令行,热爱图形化设计才是好程序员)
  • (感谢 JetBrains 从 Android Studio 到 IntelliJ IDEA,让我越来越热爱编程)
  • (完成一点点,笔记很少)
  • (正在进行中)
  • (时间协调注定无法平衡,得到什么就会失去什么,唯有让自身变得优秀才谈得上是有用的社交)
  • (正在进行中)

成果

  • 工作
    工作两年,有着过于熟练的手感,逐渐成长成为一名 CRUD 小能手,狂接需求的老司机,这时候应该有人开始会藐视技术了吧。所有顽固的老程序员都会有藐视技术的习惯,自认为已经洞察编程套路,自以为天下武功,無堅不破,唯快不破!天下编程都是同样的套路,熟悉一种就可以融会贯通。
    我还在读书的时候也会有这种想法,认为编程语言只是有不同的语法大全,现在看来这种心态是可悲的,和整天讨论这门语言好,或者那门语言坏的人一样,悲其一叶障目,更悲其大愚若智的自的心态。
    这一切都来源于眼界的不同,所以 2016 年,在工作中不断地进行试验,Redis、应用服务器集群、消息队列、CDN+负载均衡等等技术,这些事情必须要亲自做一遍才知道其中的坑与特性。
    上半年开始回顾 Java 后端栈的各种框架的使用:Spring、Spring boot、MyBatis、Lucene、Shiro等等,还弄了几个运维脚本,如:shell 脚本定时监控日志报警log4j2 日志配置用 Cronolog 切割 Tomcat 的 catalina.out 日志,现在看来都可以用 ELK 来做,相见恨晚呀!
    下半年开始回顾 Java 基础:Java 基础数据类型、集合框架、IO等等,其中发现有两个现象
    1. 如果学习过程中如果时间跨度过长,学习效果会减掉一大半,必须一蹴而就才有效果
    2. 如果学习过程没有进行记录,那么忘记的速度就是 O(n^2)
  • 学习
    今年下载的代码装满了我的 2T 硬盘,各种风格都有,有趣、猎奇、严谨、搞笑...
    下图是今年 Github 的记录,后边的提交是强行提交的,即使不写代码也写思路,逼迫自己不断地学习

    读书方面除了线下一月一本的技术书,还有就是微信上的一些电子书,现在上下班路上的时间总共是两个小时,微信读书大部分时间都是这个时间段,唯一的缺点就是没有趁手可以总结的工具,导致一读而过,没有沉淀太多东西下来
  • 生活
    时间协调注定无法平衡,得到什么就会失去什么,所以今年生活非常无味,没有太多激情的瞬间,毕业后就没有放松地出去旅行过,但是并不遗憾,因为独处能够带来大段的时间,用来深入地学习和思考。
    1. 虽然讨厌做饭,因为很浪费时间,但是也学会了一些菜
    2. 每天饭后散步是思考的时间,感谢 ofo 共享单车,让我像秃鹰一样高效地探索公司周边的街头,这很有意义,让我对社会的感知摆脱来自电脑的依赖,通过阳光去看到生活,而不是通过微博、QQ 去和世界打交道
    3. 上半年的运动量还行,下半年就基本落下了,如果再不迅速扭转局面,二十几年来苗条的身体将面临着严重的威胁

不足

几年来未完成清单:

二.2017 展望

  1. 研究 Spring 源码,争取 2017 年 12 月份能够研究完:
  • 容器实现
  • 标签解析
  • bean 加载
  • AOP
  • JDBC
  • 事务

Keep Learning, Keep Thinking, Keep Practising, Keep Reflecting, Keep Examining, Keep Changing.

Java 中的基本数据类型

Java 中数值类型的表示,byte 占用 8 个 bit ,每个 bit 只有 0 和 1 两个值,byte 的值在 bit 中表示就是二进制,所以值为 4 的 byte ,在计算机中表示为二进制的原码:
00000100,如果是负数,则表示为负数正值的补码,如: -4 表示为:11111100,可以看出,如果首位为 1 时,表示负数,首位为 0 时,表示正数。所以 byte 的最大值和最小值分别是 127 、-127

  • 负数的二进制位表示过程如下:
    -4 取正值:4
    正值转换为原码 00000100
    原码取反为反码 11111011
    反码+1变成补码 11111100

字符类型:char(16)
布尔类型:boolean
数值类型:byte(8)\short(16)\int(32)\long(64)\float(32)\double(64)

  • 位运算 左移:<< 丢弃最高位,0 补最低位,如果没有溢出,就是 n 次方
    右移:>> 符号位不变,左边补上符号位
    无符号位右移:>>> 丢弃最低位,0 补最高位

按位与、按位或、按位取反、按位异或

  1. 网络通信、协议解析、高性能编程比较常见
  2. 位运算是针对整型的,进行位运算时,除了 long 类型外,其他类型会自动转为 int 型
  3. 如果移动的位数超过了 32 位(long是 64 位),那么编译器会对移动的位数取模,如对 int 型移动 33 位,实际上只移动了 33%32=1 位

Java 包装类型内存大小比 基本类型大得多
BigInteger\BigDecimal 表示大整数和大浮点数

  1. 可以精确地表示大整数和小数
  2. 不可变性,不适合于大量的数学运算
  3. 如果需要精确计算,使用 String 构造 BigDecimal ,避免使用 double ,因为 doublle 本身就不准确了
  4. equals 方法认为 0.1 和 0.1 是相等的,但是 0.10 和 0.1 是不等的。方法 compareTo 认为 0.1 和 0.1 相等, 0.10 和 0.1 是相等的,所以比较 BigDecimal 时,使用 compareTo()
  5. 有时候运算无法得到精确结果,产生无限循环小数,可以显式地控制舍入

Java 数组是特殊的对象
如何让对象数组缓存友好?

  • 程序启动是预先生成数组中引用的对象,增加缓存友好的概率
    For(...){ a[i] = new ObjectXX() }
  • unsafe 控制 Java 对象内存位置
  • 不使用对象

java.util.Arrays 提供了 Java 数组排序方法,基本类型使用 DualPivotQuickSort 算法
,Java 对象排序使用 TimSort 算法(JDK7),MergeSort(JDK6)

Java 数组高级技巧

  • 初始化数组: Arrays.setAll(array,generator);
  • 高效复制数组: int[] arr = Arrays.copyof(源数组,新数组长度);
  • 高效排序: Arrays.sort、并行排序 Arrays.parallelSort
  • 高效查询: Arrays.binarySearch
  • 输出内容为字符串: Arrays.toString(array)

Java 集合框架

  • Apache Common Collections
  • Google Guava
  • Java Collections Framework

Collection 可遍历的、可伸缩的、可查询的,而可排序不是集合的要求
Iterator 设计模式解决无限的数据-> 有限的内存这个矛盾

PriorityQueue 优先队列,使用二叉堆实现,二叉堆又是用数组表示
BlockingQueue 堵塞队列,用于生产者消费者模式
HashMap 内部为一个 Tree 实现
如果以插入+查找最小+删除最小来比较红黑树和最小堆时,红黑树占优
JDK 8 中,HashMap Hash 冲突时,使用红黑树代替链表

java.lang.ref.SoftReference 软引用
java.lang.ref.WeakReference 弱引用
java.lang.ref.PhantomReference 虚引用
WeakHashMap
SkipList 跳表 ConcurrentSkipListMap 空间换时间
树为了排序

实践

  • 定义时使用接口
  • 底层返回集合为空时,返回长度为 0 的集合或者数组,不要返回 null
  • 谨慎对待 hashCode 和 equals
  • 用 Arrays 和 Collections 处理:排序、查找、集合类型转换、集合计算操作
  • 如果元素长度固定,优先考虑 Array
  • 使用泛型
  • 遍历 List 时,考虑使用 ListIterator
  • 频繁查找某个元素时,考虑进行排序
  • 需要元素有序时,考虑使用 Linked 家族
  • 使用 WeakHashMap 维护短暂使用的数据
  • 多线程环境下优先使用 concurrent 家族
  • 多线程环境下频繁遍历少量修改,考虑 CopyOnWriteArrayList
  • 多线程环境下不要强一致性的遍历器,可考虑使用 ConcurrentHashahMap
  • 多线程环境下使用 TreeMap ,考虑 ConcurrentSkipListSet 和 ConcurrentSkipListMap

QA

1 分析Collection接口以及其子接口,很通俗的方式说说,究竟有哪些类型的Collection,各自解决什么样的问题 Collection 接口关系如图

三个子接口分别是: Queue、Set、List,还有一个抽象类 AbstractCollection, Collection 接口抽象了一组元素的集合,提供了通用的集合操作方法

  • 其中 Set 代表无序,不可重复的集合,就像是一个盘子里的小石头,每个都一样是石头,但是每一个形状又都不一样,Set 的 API 和 Collection 完全一样;
  • List 代表有序,可重复的集合,List中的每一个元素都有一个索引;第一个元素的索引值是0,往后的元素的索引值依次+1,对比 Collection,List 拓展了自己的方法,增加了 “添加、删除、获取、修改指定位置的元素”、“获取List中的子队列” 等方法;
  • Queue 代表一种队列集合,队列的头部保存在队列中存放时间最长的元素,尾部保存存放时间最短的元素。新元素插入到队列的尾部,取出元素会返回队列头部的元素,对比 Collection 接口,Queue 增加了"队尾元素插入","获取队头元素" 等方法
  • AbstractCollection 是一个抽象类,它实现了 Collection 中除 iterator() 和 size() 之外的函数。 AbstractCollection 的主要作用:它实现了 Collection 接口中的大部分函数。从而方便其它类实现Collection,比如ArrayList、LinkedList等,它们这些类想要实现Collection接口,通过继承AbstractCollection就已经实现了大部分的接口了。

2 TreeSet继承了什么Set,与HashSet的区别是?HashSet与HashTable是“一脉相承”的么? TreeSet 和 HashSet 类图如下: 可以看到,TreeSet 和 HashSet 都继承了 AbstractSet,都实现了 Serializable、Cloneable 接口,但是对于 HashSet 来说,TreeSet 实现了 NavigableSet、SortedSet 接口 SortedSet 具有排序功能,它支持对 Set 中的元素排序,提供了三大功能,分别是

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface SortedSet<E> extends Set<E> {
// Range-view 范围查看
SortedSet<E> subSet(E fromElement, E toElement);
SortedSet<E> headSet(E toElement);
SortedSet<E> tailSet(E fromElement);
// Endpoints 端点
E first();
E last();
// Comparator access 访问 Comparator
Comparator<? super E> comparator();
}

NavigableSet 直译成中文就是:可导航的 Set,是 SortedSet 的子接口,有 ConcurrentSkipListSet, TreeSet 两种实现
增加了返回小于(lower)、小于等于(floor)、大于等于(ceiling)和大于(higher)输入参数的一个元素的方法
弹出第一个(pollFirst),最后一个元素(pollLast)、
以及正向和逆向的迭代器、
返回更小的元素集合(headSet),更大的元素集合(tailSet),区间元素集合(subSet)

HashSet与HashTable 不是是“一脉相承”的,因为 HashSet 实现了 Set 接口,HashTable 实现了 Map 接口

3 Queue接口增加了哪些方法,这些方法的作用和区别是?
Queue 本质上是一个操作受限的集合,结构图如下:

1
2
3
4
5
offer 添加一个元素并返回插入结果,优于add,因为如果队列已满,则返回false,不抛出异常
remove 移除并返回队列头部的元素, 如果队列为空,则抛出一个异常
poll 移除并返问队列头部的元素,优于remove,因为如果队列为空,则返回null
element 返回队列头部的元素,如果队列为空,则抛出一个异常
peek 返回队列头部的元素,优于peek,因为如果队列为空,则返回null

4 LinkedList也是一种Queue么?是否是双向链表?
LinkedList 实现了 Queue 接口,所以可以作为队列,作为 FIFO 的队列时,下表的方法等价:

队列方法 等效方法
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()

LinkedList 是双向链表实现,从下图可以看出,它分别记录了头节点和尾节点,便于双向遍历

5 Java数组如何与Collection相互转换

  • Collection to Array:
  1. Bar[] result = foos.stream().map(Bar::new).toArray(Bar[]::new);
  2. Foo[] foos = x.toArray(new Foo[x.size()]);
  3. int i = 0;
    Bar[] bars = new Bar[fooCollection.size()]; for( Foo foo : fooCollection ) { // where fooCollection is Collection<Foo> bars[i++] = new Bar(foo); }
  • Array to Collection:
  1. XXX xxx = new XXX(Arrays.asList(array));
  2. Collections.addAll(list, array);
  3. XXX xxx = Arrays.stream(array).collect(Collectors.toXXX());

6 Map的一级子接口有哪些种类,分别用作什么目的?
下图为 Map 实现类和子接口
子接口有:
Bindings, 不明 ConcurrentMap<K,V>, 定义了几个基于 CAS(Compare and Set)操作 MessageContext, 不明
ObservableMap, 允许注册观察者跟踪 Map 值的更改
SortedMap<K,V> 可进行排序的 Map
XSNamedMap, 为内部使用的接口,不明
7 HashSet 与HashMap中放入的自定义对象必须要实现哪些方法,说明原因
如果要将自定义的对象放入到HashMap或HashSet中,需要@Override hashCode()和equals()方法。hashCode()方法决定了对象会被放到哪个bucket里,当多个对象的哈希值冲突时,equals()方法决定了这些对象是否是“同一个对象”。
8 TreeSet里的自定义对象必须要实现什么方法,说明原因
因为 TreeSet 具有排序功能,所以对象集合必须实现Comparable接口,并重写compareTo()方法,通常我们需要保持 compareTo 和 equals 同步,所以最好也实现 equalse 方法
9 LinkedHashMap使用什么来保存数据,其效率与HashMap相比如何?它又有什么独特特性
LinkedHashMap 继承了 HashMap ,所以底层使用了数组来保存数据,用 set 来保存 key 集合,但是它又新增了 head 和 tail 实现双向循环链表,下面是开销情况:
对比 HashMap Hash的无序性,LinkedHashMap 的元素可以按插入顺序或访问顺序排列

10 IdentityHashMap 里面如果按照下面的方法放入对象,分别是什么结果,请解释原因

1
2
3
4
5
6
7
8
9
10
11
Integer a=5;
Integer b=5;
map.put(a,"100");
map.put(b,"100";
System.out.println(map.size);
map.clear();
Integer a=Integer.MAX_VALUE-1;
Integer b=Integer.MAX_VALUE-1;
map.put(a,"100");
map.put(b,"100";
System.out.println(map.size);

结果如图:
原因是:是 IdentityHashMap 使用的是==比较key的值,调用 Integer.valueOf, 当值小于 127 时,返回的都是 IntegerCache 的值,所以 IdentityHashMap 认为它是同一个 key ,128 开始就返回一个新的 Integer, IdentityHashMap就认为不相等了

加分题, 给出JDK 1.8的java 集合框架全图谱(Class类图), 并标明1.7与1.8里出现的新的类,解释其目的
下图为集合框架(非并发)

  • Iterator。它是遍历集合的工具,我们经常使用Iterator迭代器来遍历集合。Collection的实现类都要实现iterator()函数,返回一个Iterator对象。

  • Collection是一个interface
    Collection有List、Set和Queue三大分支。

    • List<E>是一个队列,根据下标索引,第一个元素的下标是0,List的实现类有LinkedList, ArrayList, Vector, Stack。List是有序的队列,List中可以有重复的值。

    • Set<E>是一个集合,SET中的值是唯一的,我们经常会遇到List去重的问题,把List转为SET就可以快速实现 Set的实现类有HastSet和TreeSet。HashSet。其中TreeSet是有序的。

    • Queue 代表一种队列集合,队列的头部保存在队列中存放时间最长的元素,尾部保存存放时间最短的元素。新元素插入到队列的尾部,取出元素会返回队列头部的元素

  • Map<K,V>是一个interface,即key-value键值对。Map中的每一个元素包含“一个key”和“key对应的value”。

    • AbstractMap是个抽象类,它实现了Map接口中的大部分API。而HashMap,TreeMap,WeakHashMap都是继承于AbstractMap。
  • 抽象类AbstractCollection、AbstractList、AbstractSet、AbstractMap是抽象类,他们都实现了各自的大部分方法,我们直接继承Abstract类就可以省去重复编码相同的方法

  • List简介

  1. List 是一个接口,它继承于Collection的接口。它代表着有序的队列。
  2. AbstractList 是一个抽象类,它继承于AbstractCollection。AbstractList实现List接口中除size()、get(int location)之外的函数。
  3. AbstractSequentialList 是一个抽象类,它继承于AbstractList。AbstractSequentialList 实现了“链表中,根据index索引值操作链表的全部函数”。
  4. ArrayList, LinkedList, Vector, Stack, 是List的4个实现类,RandomAccessSubList是内部类。
  • Map体系
  1. Map 是映射接口,Map中存储的内容是键值对(key-value)。
  2. AbstractMap 是继承于Map的抽象类,它实现了Map中的大部分API。其它Map的实现类可以通过继承AbstractMap来减少重复编码。
  3. SortedMap 是继承于Map的接口。SortedMap中的内容是排序的键值对,排序的方法是通过比较器(Comparator)。
  4. NavigableMap 是继承于SortedMap的接口。相比于SortedMap,NavigableMap有一系列的导航方法;如"获取大于/等于某对象的键值对"、“获取小于/等于某对象的键值对”等等。
  5. TreeMap 继承于AbstractMap,且实现了NavigableMap接口;因此,TreeMap中的内容是“有序的键值对”, 它是通过红黑树实现的。它一般用于单线程中存储有序的映射。
  6. HashMap 继承于AbstractMap,没实现SortedMap或NavigableMap接口;因此,HashMap的内容是无序的键值对。
  7. LinkedHashMap 继承于HashMap,LinkedHashMap 的元素可以按插入顺序或访问顺序排列
  8. IdentityHashMap 是 1.4 版本新增的一种特殊的HashMap,还是用key的hashCode来决定entry的槽位,但是不用key的equals方法来决定是否相等了,而是默认使用地址(即使实现了equals,equals也不起作用)来决定是否相等
  9. EnumMap 是 1.5 版本新增的一种键为枚举类型的特殊的Map实现。所有的Key也必须是一种枚举类型,EnumMap是使用数组来实现的。
  10. Hashtable继承于Dictionary(Dictionary也是键值对的接口),实现Map接口;因此,Hashtable的内容也是“键值对,是无序的”。 Hashtable是线程安全的。
  11. WeakHashMap 继承于AbstractMap。它和HashMap的键类型不同,WeakHashMap的键是“弱键”, 当“弱键”被GC回收时,它对应的键值对也会被从WeakHashMap中删除。JVM提供的弱引用
  • Set简介
  1. Set 是继承于Collection的接口。它是一个不允许有重复元素的集AbstractSet 是一个抽象类,它继承于AbstractCollection,AbstractCollection实现了Set中的绝大部分函数,为Set的实现类提供了便利。
  2. HashSet 和 TreeSet 是Set的两个实现类。HashSet中的元素是无序的。TreeSet中的元素是有序的,不支持快速随机遍历,只能通过迭代器进行遍历。
  3. LinkedHashSet继承于 HashSet ,它以元素插入的顺序来维护集合的链接表,允许以插入的顺序在集合中迭代;
  4. EnumSet 是 enum 值的集合。在 Java 中,每个 enum 都映射至一个 int 类型:每个 enum 值映射的 int 都互不相同。这使得 BitSet 之类的集合结构成为可能,每个 bit 都映射到一个不同的 enum 值。此类还存在两种实现——包含单个 long 类型(可存储64个 enum 值,足够覆盖99.9%的用例)的 RegularEnumSet 与包含 long[] 类型的 JumboEnumSet
  • Queues(队列)/deques(双队列)
  1. AbstractQueue 是继承于Queue的抽象类,它实现了Queue中的大部分API。其它Queue的实现类可以通过继承AbstractQueue来减少重复编码
  2. PriorityQueue 继承 AbstractQueue ,是一个基于优先级堆的极大优先级队列。
  3. ArrayDeque 基于数组的双端队列

并发的集合有点乱...

Java 集合系列02之 Collection架构
Java HashSet和HashMap源码剖析
Difference between HashMap, LinkedHashMap and TreeMap
Java 性能调优指南之 Java 集合概览

Java IO 编码解码

ASCII 数字 0-9 对应的 byte 值为 48-57
Unicode 与 Java
char 是字符数据类型,是无符号的,占2字节,大小范围是 0-65535,是 16 位二进制的 Unicode 字符,Java 用 char 来表示一个字符
大部分中文使用三个字节的 UTF-8 方法编码

IO 传输的两个关键问题:

  1. Encode/Decode
  2. Big Endian/Little Endian

二进制方式读取文件

  • ByteArrayOutputStream: 可以捕获内存缓冲区的数据,转换成字节数组
  • ByteArrayInputStream: 可以将字节数组转化为输入流
  • 字符串的编码/解码问题
    charset
  • 多字节数据(short/int/long/double/float)的编码/解码问题
    ObjectOutputStream (默认 Big-Endian 顺序)
  • Object 对象编码/解码问题
    Java Serialize 机制(二进制)以及 java.beans 包里面的 XMLEncoder (文本编码)

Java Object 序列化机制

  • 当父类实现了序列化,子类也自动实现了序列化,不需要显式实现 Serializable 接口
  • 反过来一个子类实现了 Serializable 接口,希望父类对象序列化,需要父类也实现Serializable 接口,否则父类对象的成员变量不参与序列化
  • 当一个对象的实例变量引用其他对象,序列化该对象时,也会把引用对象也进行序列化
  • 某些对象不能序列化,如:安全问题不能远程传输的对象、无法重新还原的对象(socket、thread、Stream)

RandomAccessFile==>MappedByteBuffer
MappedByteBuffer:内存映射文件 MapMode 映射模式:

  • READ_ONLY 只读映射模式
  • READ_WRITE 读写映射模式
  • PRIVATE 通过 put 方法对 MappedByteBuffer 的修改不会修改到磁盘文件,只修改虚拟内存的修改

MappedByteBuffer 在父类 ByteBuffer 的基础上新增了几个方法

  1. fore 缓冲区在 READ_WRITE 模式下,此方法对缓冲区所做的内容更改强制写入文件
  2. load 将缓冲区的内容载入物理内存,并返回该缓冲区的引用
  3. isLoaded 判断缓冲区的内容是否在物理内存,如果在就返回 true,否则返回 false

Java IO 中应用到了装饰者模式

程序员修炼之道

Don't Live with Broken Windows
Be a Catalyst for Change
Remember the Big Picture
Make Quality a Requirements Issue
Invest Regularly in Your Knowledge Portfolio

  • 每年至少学习一种新语言
  • 每季度阅读一本技术书籍
  • 也要阅读非技术书籍
  • 上课
  • 参加本地用户组织
  • 实验不同的环境
  • 跟上潮流
  • 上网

Critically Analyze What You Read and Hear
WISDOM 离合诗

  • 你想让他们学到什么?
  • 他们对你讲的什么感兴趣?
  • 他们有多富有经验?
  • 他们想要什么细节?
  • 你先要让谁拥有这些信息?

It's Both What You Say and the Way You Say It

DRY - Don't Repeat Yourself
重复是如何发生的

  • 强加的重复
  • 无意的重复
  • 无耐性的重复
  • 开发者之间的重复

把低级的知识放在代码中,把注释保留给其他的高级说明

Make it Easy to Reuse
Eliminate Effects Between Unrelated Things
There Are No Final Decisions
Use Tracer Bullets to Find the Target

  • 用户能够及早看到能工作的东西
  • 开发者构建里一个它们能在其中工作的结构
  • 你有了一个集成平台
  • 你有了可用于演示的东西
  • 你见更能够感受到工作进度

语言的界限就是一个人的世界的界限
Program Close to the Problem domain
Estimate to Avoid Surprises

时长 报出估算的单位
1~15 天
3~8 周
8~30 周
30+ 周 在给出估算前努力思考一下

Iterate the Schedule with the Code
Keep Knowledge in Plain Text
Use the Power of Command Shells
Use a Single Editor Well
Always Use Source Code Control
Fix the Problem,Not the Blame
Don't Panic
"Select" Isn't Broken
Don't Assume it - Prove It
Learn a Text Manipulation Language
Write Code That Writes Code
You Can't Write Perfect Software
Design with Contracts
Crash Early
If it Can't Happen,Use Assertions to Ensure That it Won't
Use Exceptions for Exceptional Problems
Finish What You Start
Minimize Coupling Between Modules
Configure,Don't Integrate
Put Abstractions in Code,Details in Metadata
Analyze Workflow to Improve Concurrency
Design Using Services
Always Design for Concurrency
Separate Views from Models
Use Blackboards to Coordinate Workflow
Don't Program by Coincidence

怎样深思熟虑地编程

  • 总是意识到你在做什么
  • 不要盲目地编程
  • 按照计划行事
  • 依靠可靠的事物
  • 为你的假定建立文档
  • 不要只是测试你的代码,还要测试你的假定
  • 为你的工作划分优先级
  • 不要做历史的奴隶

Estimate the Order of Your Algorithms
Test Your Estimates
Refactor Early,Refactor Often
Design to Test
Test Your Software,or Your Users Will
Don't Use Wizard Code You Don't Understand
完美,不是在没有什么需要增加,而在没有什么需要去掉时达到的
Don't Gather Requirements - Dig for Them
Work with a User to Think Like a User
Abstractions Live Longer than Details
Use a Project Glossary
Don't Think Outside the Box - Find the Box
Listen to Nagging Doubts - Start When You're Ready
Some Things Are Better Done than Described
Don't Be a Slave to Format Methods
Expensive Tools Do Not Produce Better Designs
Organize Around Functionality,Not Job Functions
Test Early. Test Often. Test Automatically.
Coding Ain't Done Til All the Tests Run
Use Saboteurs to Test Your Testing
Find Bugs Once
Treat English as Just Another Programming Language
Build Documentation In,Don't Bolt It On
Gently Exceed Your Users' Expectaions
Sign Your Work

记一次 java.lang.NoSuchMethodError 错误

今天在使用 EnableScheduling 测试计划任务时,出现了以下错误:

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
WARNING: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scheduledTaskService' defined in file [/Users/moon/spring/target/classes/taskscheduler/ScheduledTaskService.class]: Initialization of bean failed; nested exception is java.lang.NoSuchMethodError: org.springframework.aop.support.AopUtils.selectInvocableMethod(Ljava/lang/reflect/Method;Ljava/lang/Class;)Ljava/lang/reflect/Method;
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scheduledTaskService' defined in file [/Users/moon/spring/target/classes/taskscheduler/ScheduledTaskService.class]: Initialization of bean failed; nested exception is java.lang.NoSuchMethodError: org.springframework.aop.support.AopUtils.selectInvocableMethod(Ljava/lang/reflect/Method;Ljava/lang/Class;)Ljava/lang/reflect/Method;
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:751)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:861)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
at taskscheduler.Main.main(Main.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.lang.NoSuchMethodError: org.springframework.aop.support.AopUtils.selectInvocableMethod(Ljava/lang/reflect/Method;Ljava/lang/Class;)Ljava/lang/reflect/Method;
at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.processScheduled(ScheduledAnnotationBeanPostProcessor.java:300)
at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.postProcessAfterInitialization(ScheduledAnnotationBeanPostProcessor.java:283)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1588)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
... 15 more

经过排查后发现是 spring-contextspring-aop 版本不兼容导致

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<!--spring aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.7.RELEASE</version>
</dependency>

更换 spring-contextspring-aop 版本即可解决

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<!--spring aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>

在 Linux 下配置 Redis 开机启动

在 Linux 下配置 Redis 开机启动,主要步骤如下:

  1. 设置 Redis redis.conf 文件,使之能够以后台模式运行
  2. 编写 shell 启动 Redis 脚本
  3. 配置 Linux 开机启动配置

实际操作:

  • 打开 redis.conf 文件,修改:#daemonize yesdaemonize yes
  • 编写 shell 脚本,输入vim /etc/init.d/redis
    下面有两段代码任选一段都可以使用,都是 Centos 可用的启动 Redis 脚本,其中的文件位置需要根据实际情况修改

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
# chkconfig: 2345 10 90
# description: Start and Stop redis
PATH=/usr/local/bin:/sbin:/usr/bin:/bin
REDISPORT=6379 #实际环境而定
EXEC=/usr/local/redis/src/redis-server #实际环境而定
REDIS_CLI=/usr/local/redis/src/redis-cli #实际环境而定
PIDFILE=/var/run/redis.pid
CONF="/usr/local/redis/redis.conf" #实际环境而定
case "$1" in
start)
if [ -f $PIDFILE ]
then
echo "$PIDFILE exists, process is already running or crashed."
else
echo "Starting Redis server..."
$EXEC $CONF
fi
if [ "$?"="0" ]
then
echo "Redis is running..."
fi
;;
stop)
if [ ! -f $PIDFILE ]
then
echo "$PIDFILE exists, process is not running."
else
PID=$(cat $PIDFILE)
echo "Stopping..."
$REDIS_CLI -p $REDISPORT SHUTDOWN
while [ -x $PIDFILE ]
do
echo "Waiting for Redis to shutdown..."
sleep 1
done
echo "Redis stopped"
fi
;;
restart|force-reload)
${0} stop
${0} start
;;
*)
echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2
exit 1
esac

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
#!/bin/sh
#chkconfig: 345 86 14
#description: Startup and shutdown script for Redis
PROGDIR=/usr/local/bin
PROGNAME=redis-server
DAEMON=$PROGDIR/$PROGNAME
CONFIG=/etc/redis.conf
PIDFILE=/var/run/redis.pid
DESC="redis daemon"
SCRIPTNAME=/etc/init.d/redis
start()
{
if test -x $DAEMON
then
echo -e "Starting $DESC: $PROGNAME"
if $DAEMON $CONFIG
then
echo -e "OK"
else
echo -e "failed"
fi
else
echo -e "Couldn't find Redis Server ($DAEMON)"
fi
}
stop()
{
if test -e $PIDFILE
then
echo -e "Stopping $DESC: $PROGNAME"
if kill `cat $PIDFILE`
then
echo -e "OK"
else
echo -e "failed"
fi
else
echo -e "No Redis Server ($DAEMON) running"
fi
}
restart()
{
echo -e "Restarting $DESC: $PROGNAME"
stop
start
}
list()
{
ps aux | grep $PROGNAME
}
case $1 in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
list)
list
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart|list}" >&2
exit 1
;;
esac
exit 0

  • 为 shell 启动 Redis 脚本添加执行权限:chmod +x /etc/init.d/redis
  • 测试脚本是否可用,可执行 service redis start 进行启动,启动后可连接 Redis 进行测试
  • 如果脚本可用,即可将其配置为开机启动项,执行chkconfig redis on即可,设置后可使用chkconfig --list查看是否有 redis 选项,到此,完成配置流程结束
  • 这时候,可能在开机时仍然无法自动启动 Redis ,这是因为开机执行启动脚本 /etc/init.d/redis ,是没有输入参数的,所以都会走到脚本中的 *) 这里,所以此时需要修改上面的脚本,如第二个脚本,修改

1
2
3
4
*)
echo "Usage: $SCRIPTNAME {start|stop|restart|list}" >&2
exit 1
;;

 *)
 start
 ;;

参考

Centos开机自启动redis
ddkangfu/redis开机启动脚本
init.d里chkconfig(linux启动脚本讲解+示例)

HotSpot VM 相关参数

HotSpot VM 相关参数

<!-- create time: 2016-06-10 13:46:04 Author: <TODO: 请写上你的名字>

This file is created by Marboohttp://marboo.io template file $MARBOO_HOME/.media/starts/default.md 本文件由 Marboohttp://marboo.io 模板文件 $MARBOO_HOME/.media/starts/default.md 创建 -->

##HotSpot VM 关于GC日志相关的命令行选项

1
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:<filename>

生成文件格式:

1
2
2.660: [GC [PSYoungGen: 142835K->10734K(142848K)] 144991K->26629K(317440K), 0.0203930 secs] [Times: user=0.05 sys=0.02, real=0.02 secs]
86.592: [Full GC [PSYoungGen: 3456K->0K(1329664K)] [ParOldGen: 153539K->90508K(212480K)] 156995K->90508K(1542144K) [PSPermGen: 67518K->67135K(220160K)], 0.4519800 secs] [Times: user=1.54 sys=0.03, real=0.45 secs]

  • 2.660 是从 JVM 启动直到垃圾收集发生所经历的时间
  • GC 代表是一次 Minor GC
  • Full GC 代表一次 Full GC
  • [PSYoungGen: 142835K->10734K(142848K)] 的 PSYoungGen 表示使用了多线程垃圾收集器 Parallel Scavenge,其它的收集器还有:多线程 ParNew 、单线程 ParNew、G1。 箭头(->)左边是回收前新生代占用空间,右边代表回收后占用的空间,Ps:Minor GC 回收后 Eden 空间为空,所以回收后的空间可以视为 Surivor 占用空间,括号里代表新生代的总大小
  • 144991K->26629K(317440K) 代表垃圾收集后 Java 堆堆使用情况(包括新生代和老年代),箭头(->)左边是回收前 Java 堆占用空间,右边代表回收后占用的空间,括号里代表 Java 堆的总大小 ,Ps:根据 Java 堆总大小和新生代总大小,可以推算出老年代占用空间
  • 0.0203930 secs 代表垃圾收集过程所消耗的时长
  • [Times: user=0.05 sys=0.02, real=0.02 secs] 提供了 CPU和时间消耗情况。user 是用户态的消耗时间,即运行于 JVM 中的时间。

如果想使用日历时间打印时间戳,可以使用 -XX:PrintGCDateStamps 替换 -XX:+PrintGCTimeStamps` 生成文件格式将会变成:

1
2016-06-10T15:29:34.899-0800: 19.409: [GC [PSYoungGen: 1291624K->16960K(1337344K)] 1396481K->131948K(1511936K), 0.0174330 secs] [Times: user=0.09 sys=0.02, real=0.02 secs]

设置内存空间

  • -Xmx 指定了新生代和老年代空间(Java 堆)大小的最大值
  • -Xms 指定了新生代和老年代空间(Java 堆)大小的初始值和最小值
  • 关注吞吐量及延迟的 Java 应用应该将 -Xmx 和 -Xms 设置为同一值,避免 Java 堆伸缩带来的 Full GC
  • -XX:NewSize=<n>[g|m|k] 新生代空间大小的初始值和最小值,必须和-XX:MaxNewSize配合使用
  • -XX:MaxNewSize=<n>[g|m|k] 新生代空间大小的最大值,必须和-XX:NewSize配合使用
  • -Xmn<n>[g|m|k] 一次性设置新生代空间的初始值、最小值、最大值,如果设置了该值,新生代大小会恒定,所以只有 -Xmx 和 -Xms 设置为同一值时才使用 -Xmn 选项
  • 老年代空间会根据 Java 堆空间和新生代空间隐性设定,最大为:-Xmx 减去 -XX:NewSize,最小为-Xmx 减去 -XX:MaxNewSize
  • -XX:PermSize=<n>[g|m|k] 永久代空间初始值和最小值
  • -XX:MaxPermSize=<n>[g|m|k] 永久代空间的最大值
  • 关注性能的 Java 应用应该将 -XX:PermSize 和 -XX:MaxPermSize 设置为同一值,避免永久堆大小调整带来的 Full GC
  • 如果不显式设置 Java 堆大小,JVM 会根据系统自动选择合适的值
  • JVM 内存调节的计算法则:
空间 命令行选项 占用倍数
Java 堆 -Xms 和 -Xmx 3~4 倍 Full GC 后的老年代空间占用量
永久代 -XX:PermSize 和 -XX:MaxPermSize 1.2~1.5 倍 Full GC 后的永久代空间占用量
新生代 -Xmn 1~1.5 倍 Full GC 后的老年代空间占用量
老年代 Java 堆大小减新生代大小 2~3 倍 Full GC 后老年代空间占用量
  • 调节新生代空间准则:
    • 老年代空间大小不应该小于活跃数据大小的 1.5 倍
    • 新生代空间至少应为 Java 堆 大小的 10%
    • 增大 Java 堆大小时,不能超过 JVM 可用的物理内存数

设置收集器

  • -XX:+UseParallelOldGC 指定使用 Throughput 收集器
  • -XX:+UseConcM 来源于 Java 性能优化权威指南 第七章

Java 中的无穷和非数

Java 中的正无穷(POSITIVE_INFINITY)负无穷(NEGATIVE_INFINITY)和非数(NaN)

1
2
3
1.0f / 0.0f = Infinity
-1.0f / 0.0f = -Infinity
0.0f / 0.0f = NaN

来源于 java.lang.Float 的源码

用 Cronolog 切割 Tomcat 的 catalina.out 日志

用 Cronolog 切割 Tomcat 的 catalina.out 日志

Cronolog 是一个小巧高效的日志文件处理工具,可以实现自动的按规则生成周期性的日志文件
安装 cronolog 命令如下:

1
2
3
4
5
6
wget ftp://de.aminet.net/macports/distfiles/cronolog-devel/cronolog-1.7.0-beta.tar.gz
tar zxvf cronolog-1.7.0-beta.tar.gz
cd cronolog-1.7.0
./configure
make install
which cronolog

也可使用 yum 进行安装
设置 catalina.out 位置,修改 catalina.sh:

1
2
3
4
5
6
7
8
# Add on extra jar files to CLASSPATH
if [ ! -z "$CLASSPATH" ] ; then
CLASSPATH="$CLASSPATH":
fi
CLASSPATH="$CLASSPATH""$CATALINA_HOME"/bin/bootstrap.jar
if [ -z "$CATALINA_OUT" ] ; then
CATALINA_OUT="$CATALINA_BASE"/logs/catalina.out
fi

为:

1
2
3
4
5
6
7
8
# Add on extra jar files to CLASSPATH
if [ ! -z "$CLASSPATH" ] ; then
CLASSPATH="$CLASSPATH":
fi
CLASSPATH="$CLASSPATH""$CATALINA_HOME"/bin/bootstrap.jar
if [ -z "$CATALINA_OUT" ] ; then
CATALINA_OUT=/mnt/logs/%Y-%m-%d.out
fi

配置 cronolog 切割日志,修改 catalina.sh

1
2
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&"

为:

1
2
org.apache.catalina.startup.Bootstrap "$@" start 2>&1 \
| /usr/sbin/cronolog "$CATALINA_OUT" >> /dev/null &

(有两处地方需要修改),此处的 /usr/sbin/cronolog 可以通过which cronolog查看,配置完毕启动 Tomcat 生效

log4j2 日志配置

log4j2 日志配置

目前用到 log4j2 的配置,如果想使用 Asynchronous Loggers 的话需要导入 disruptor-3.0.0 或者更高版本

<?xml version="1.0" encoding="GBK"?>
<!-- status="OFF",可以去掉,它的含义为是否记录 log4j2 本身的 event 信息,默认是 OFF -->
<configuration status="OFF">
<!-- 定义下面的引用名 -->
<Properties>
    <property name="log_pattern">%d{yyyy-MM-dd HH:mm:ss z} %-5level %class{36}%L%M - %msg%xEx%n</property>
    <property name="log-path">/logs/app</property><!--${sys:user.home} 是 home 目录、 ${web:rootDir} 是项目根目录 -->
    <property name="every_file_size">50M</property><!-- 日志切割的最小单位 -->
    <property name="output_log_level">debug</property><!-- 日志输出级别 -->
</Properties>

<!--先定义所有的 appender -->
<appenders>

    <!--这个输出控制台的配置 -->
    <Console name="Console" target="SYSTEM_OUT">
        <!--控制台只输出 level 及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
        <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" />
        <!--输出日志的格式 -->
        <PatternLayout pattern="${log_pattern}" />
    </Console>

    <!-- debug 级别日志文件 -->
    <!--每次大小超过 size,则这 size 大小的日志会自动进行压缩,作为存档 -->
    <RollingRandomAccessFile name="app_debug" fileName="${log-path}/debug/debug.log" filePattern="${log-path}/debug/debug-%d{yyyy-MM-dd}-%i.log.gz" immediateFlush="false">
        <Filters>
            <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL" />
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="NEUTRAL" />
        </Filters>
        <PatternLayout pattern="${log_pattern}" />
        <SizeBasedTriggeringPolicy size="${every_file_size}"/>
    </RollingRandomAccessFile>

    <!-- info 级别日志文件 -->
    <RollingRandomAccessFile name="app_info" fileName="${log-path}/info/info.log" filePattern="${log-path}/info/info-%d{yyyy-MM-dd}-%i.log.gz" immediateFlush="false">
        <Filters>
            <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL" />
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />
        </Filters>
        <PatternLayout pattern="${log_pattern}" />
        <SizeBasedTriggeringPolicy size="${every_file_size}"/>
    </RollingRandomAccessFile>

    <!-- error 级别日志文件 -->
    <RollingRandomAccessFile name="app_error" fileName="${log-path}/error/error.log" filePattern="${log-path}/error/error-%d{yyyy-MM-dd}-%i.log.gz" immediateFlush="false">
        <Filters>
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" />
        </Filters>
        <PatternLayout pattern="${log_pattern}" />
        <SizeBasedTriggeringPolicy size="${every_file_size}"/>
    </RollingRandomAccessFile>

</appenders>

<Loggers>
    <!--<AsyncLogger name="org.springframework" level="error" additivity="false" includeLocation="true">-->
        <!--<appender-ref ref="Console" />-->
        <!--<appender-ref ref="app_debug"/>-->
        <!--<appender-ref ref="app_info"/>-->
        <!--<appender-ref ref="app_error"/>-->
    <!--</AsyncLogger>-->

    <!--打印程序自身的日志-->
    <AsyncLogger name="com.abc" level="trace" additivity="false" includeLocation="true">
        <appender-ref ref="Console" />
        <appender-ref ref="app_debug"/>
        <appender-ref ref="app_info"/>
        <appender-ref ref="app_error"/>
    </AsyncLogger>
    <!--上面过滤完毕后,接下来的 log 就由 ROOT 进行管理-->
    <Root level="error">
        <appender-ref ref="Console" />
        <appender-ref ref="app_debug"/>
        <appender-ref ref="app_info"/>
        <appender-ref ref="app_error"/>
    </Root>
</Loggers>

</configuration>

shell脚本定时监控日志报警

shell脚本定时监控日志报警

最近遇到一个需求,需要监控服务器错误日志的情况,以便及时发现服务器异常,在网上找了一圈后发现了这一篇文章,shell脚本定时监控日志报警 ,经过修改后符合我们项目的要求,主要修改了获取日志文件列表的方式,使它支持多目录监控,使用时需要配置好 /etc/mail.rc 使之可发送邮件,再结合 Crontab 即可实现定时监控:
原程序:monitor.sh

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
#!/bin/bash
#记录上一次的行数
Last_num_d=/tmp/monitor/lastnum
#日志目录
Log_directory=/usr/local/nginx/logs
#ERROR log 临时存放目录
Error_log=/tmp/monitor/errorlog
#目录判断
d_judge(){
[ ! -d $1 ] && mkdir -p $1
}
d_judge $Last_num_d
d_judge $Error_log
for logfile in `ls $Log_directory |grep log |grep -v access` ; do
#for logfile in `ls $Log_directory` ; do
#先判断当前日志目录是否为空,为空则直接跳过
[ ! -s $Log_directory/$logfile ] && echo "`date` $logfile is empty" && continue
#判断记录上一次检查的行数的文件是否存在,不存在则给一个初始值
[ ! -f "$Last_num_d/$logfile" ] && echo 1 > $Last_num_d/$logfile
#将上一次值赋给变量
last_count=`cat $Last_num_d/$logfile`
#将当前的行数值赋给变量
current_count=`grep -Fc "" $Log_directory/$logfile`
#判断当前行数跟上一次行数是否相等,相等则退出当前循环
[ $last_count -eq $current_count ] && echo "`date` $logfile no change" && continue
#由于日志文件每天都会截断,因此会出现当前行数小于上一次行数的情况,此种情况出现则将上一次行数置1
[ $last_count -gt $current_count ] && last_count=1
#截取上一次检查到的行数至当前行数的日志并检索出有ERROR的日志,并重定向到相应的ERROR日志文件
sed -n "$last_count,$current_count p" $Log_directory/$logfile | grep -i ERROR >> $Error_log/$logfile && echo "`date` $logfile error " || echo "`date` $logfile changed but no error"
#判断ERROR日志是否存在且不为空,不为空则说明有错误日志,继而发送报警信息,报警完成后删除错误日志
[ -s $Error_log/$logfile ] && echo -e "$HOSTNAME \n `cat $Error_log/$logfile`" | mail -s "$logfile ERROR" xxxxxxx@qq.com && rm -rf $Error_log/$logfile
#结束本次操作之后把当前的行号作为下一次检索的last number
echo $current_count > $Last_num_d/$logfile
done

修改后:monitor_md.sh

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
#!/bin/bash
#记录上一次的行数
Last_num_d=/tmp/monitor/lastnum
#日志目录 可以是某目录如:Log_directory=/root/test,或者是多个目录,用空格隔开Log_directory=“/root/test /root/test1”
Log_directorys="/mnt/harddrive/logs/app1/error /mnt/harddrive/logs/app2/error /mnt/harddrive/logs/app3/error"
#ERROR log 临时存放目录
Error_log=/tmp/monitor/errorlog
#目录判断
d_judge(){
[ ! -d $1 ] && mkdir -p $1
}
d_judge $Last_num_d
d_judge $Error_log
for Log_directory in $Log_directorys ;do
# 所有日志log文件,除了带 access 的文件,此处的 awk 作用为查出全路径
for logfile in `ls -1 $Log_directory |awk '{print i$0}' i=$Log_directory'/' |grep log |grep -v access` ; do
#for logfile in `ls $Log_directory |grep log |grep -v access` ; do # 这是查出路径
#for logfile in `ls $Log_directory` ; do
# 文件名称不能带斜杠/,所以把文件名转换成不带斜杠的
logfilemarkfile=`echo $logfile |awk '{gsub("/","-") ; print $0}'`
#先判断当前日志目录是否为空,为空则直接跳过
[ ! -s $logfile ] && echo "`date` $logfile is empty" && continue
#判断记录上一次检查的行数的文件是否存在,不存在则给一个初始值
[ ! -f "$Last_num_d/$logfilemarkfile" ] && echo 1 > $Last_num_d/$logfilemarkfile
#将上一次值赋给变量
last_count=`cat $Last_num_d/$logfilemarkfile`
#将当前的行数值赋给变量
current_count=`grep -Fc "" $logfile`
#判断当前行数跟上一次行数是否相等,相等则退出当前循环
[ $last_count -eq $current_count ] && echo "`date` $logfile no change" && continue
#由于日志文件每天都会截断,因此会出现当前行数小于上一次行数的情况,此种情况出现则将上一次行数置1
[ $last_count -gt $current_count ] && last_count=1
#截取上一次检查到的行数至当前行数的日志并检索出有ERROR的日志,并重定向到相应的ERROR日志文件
sed -n "$last_count,$current_count p" $logfile | grep -i ERROR >> $Error_log/$logfilemarkfile && echo "`date` $logfile error " || echo "`date` $logfile changed but no error"
#判断ERROR日志是否存在且不为空,不为空则说明有错误日志,继而发送报警信息,报警完成后删除错误日志
[ -s $Error_log/$logfilemarkfile ] && echo -e "$HOSTNAME \n `cat $Error_log/$logfilemarkfile`" | mail -s "$logfile ERROR" xxxxxxx@qq.com && rm -rf $Error_log/$logfilemarkfile
#结束本次操作之后把当前的行号作为下一次检索的last number
echo $current_count > $Last_num_d/$logfilemarkfile
done
done

静宏的 2015 -> 2016

#2015 -> 2016

一.2015 总结

2015 年经历了挺多事:公司融资、公司倒闭、毕业、跳槽、再跳槽,工作也从一个纯安卓开发变成 Java 后台开发,自我感觉其实是不太好,至少没有达到最好的状态,主要的缺陷还是时间无法充分利用,学习效率较低。
2015 年初,所处的那家公司拿到了一点融资,然后在年中倒闭,在这家公司一年多的时间里,见证了一个项目的萌芽,发展,挣扎,消亡。真是百般滋味。按照我现在看到的东西肯定写不出什么总结经验,唯有领悟到一点:只有不断努力的做事情和学习,才能在不断变换的局势中应付自如
2015 年初还只是一个入门级安卓开发者,工作生活全部是安卓世界,但是工作需要,在比较短的时间里就强行转成了 Java 后台开发,初步涉及三大框架等等技术。这个情况让一个初级程序员很是迷茫,初学者在技术上的追求究竟是先广度再深度,还是先有深度再广度,这个问题我偏向于先从某方面的深度入手,再追求广度,所以在接下来的跳槽中,我的工作意向还是安卓开发,但是职场的风云变幻,总是出乎我的意料,在人生的第二家公司中,我以移动开发工程师的身份入职,但是期间做了大半个月的 Node.js,后来在我离职的时候,我已经是一名 Java 后台开发,半年时间又重新跳回 Java 开发,真有恍如隔世的感觉,所以在后来的一段时间,我恶补了 Java 开发的一些基础,看了各大框架和设计模式,把半路出家的坑补上,在 2015 年末的时候,自我评价应该能算是初级 Java 开发了,再反过来看安卓,感觉在设计和实现上, J2ee 和 Android 还有很多相通的地方,一些技术难点的理解反而比以前更清晰了

成果

  1. 在工作时带领过一段时间的技术团队,效果自我评价比较一般,但是也有收获,促使我看了几本团队管理和项目管理的书
  2. 在技术上 2015 年接触的面比较大,从 Android、iOS 到 Node.js、html5 再到 Java web,各种框架、工具都有所运用,体会到各种技术之间的异同
  3. 毕业了,拿了一些奖,再也没有骄傲的资本,正式成为一个普通开发者
  4. 买了十几本书,看了一部分,在一些陌生的技术领域还是看视频和博客比较多,人还是有惰性的,喜欢看现成的东西
  5. 买了 macbook pro, 算是 2014 年总结里实现最快的东西,果然金钱能解决的问题都不是问题

不足

  1. 2014 年总结里的不足之处改进地方不是很多,知识总结不足问题依旧存在、考证之类的都没实现、自己的 App 也没有落实
  2. 技术不过硬,面试时底气不足,做出的产品不精致,曾经被实力嘲讽了一下
  3. 跳槽太快,主要原因是自我定位不明确和看公司目光不行
  4. 运动量不够,2015 年前半年运动比较多,后半年应该各种事情,几乎没怎么运动
  5. 写作能力比较弱,讲故事姿势不对

二.2016 展望

2016 年计划依旧是技术优先

  • 报考通过 系统分析师/系统结构师/PMP
  • 把分散在各个角落的笔记收集起来,放在一个平台上,暂时存在 Github
  • 继续多读书,每个月必须有一本到手,读完有思维导图总结
  • 寻找自己感兴趣的项目,改进完善,折腾,这个估计会花费一些业余时间
  • 认识一些朋友,既要避开社交网络的淤泥,又要通过社交网络获取有用信息,还是比较有挑战性的
  • 健身,星期六早上

编程记录2

实现效果图如下的多选框: 效果图
为了自定义按钮的效果,设置了按钮的背景CheckBox代码如下: 效果图
styles.xml代码如下: 效果图
在4.4系统的效果还不错: 效果图
但是在4.0的系统下却变形了: 效果图
Google得:

在Android 4.2以下版本,如果使用paddingleft 处理checkbox 会遇到选项框和内容重叠的问题
解决方案有:

1
1、推荐方法:

1
2
3
4
5
6
7
8
checkBox.setButtonDrawable(getResources().getDrawable(R.drawable.eg_checkbox));
final float scale = this.getResources().getDisplayMetrics().density;
checkBox.setPadding(checkBox.getPaddingLeft() + (int)(10.0f * scale + 0.5f),
checkBox.getPaddingTop(),
checkBox.getPaddingRight(),
checkBox.getPaddingBottom());
```

或者

2、第二种方法:如果 R.drawable.eg_checkbox不是selector类型(如果是就用推荐方法),直接是图片可以用:

1
2
3
4
5
6
7
8
9
10
11
```java
checkBox1.setButtonDrawable(android.R.color.transparent);// 设置它的button的图片为不可见
checkBox1.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null,null);
final float scale = this.getResources().getDisplayMetrics().density;
int px = (int)(10.0f * scale + 0.5f);
checkBox1.setCompoundDrawablePadding(px);
```
来自:[ Android checkbox padding 兼容问题](http://blog.csdn.net/soldierguard/article/details/22885243)
还有来自[How to: Android – Spacing between CheckBox and text](http://sevennet.org/2014/12/21/how-to-android-spacing-between-checkbox-and-text/)的:

Answer: Android – Spacing between CheckBox and text

Given @DougW response, what I do to manage version is simpler, I add to my checkbox view: android:paddingLeft="@dimen/padding_checkbox" where the dimen is found in two values folders: values <resources> <dimen name="padding_checkbox">25dp</dimen> </resources> values-v17 (4.2 JellyBean) <resources> <dimen name="padding_checkbox">10dp</dimen> </resources> I have a custom check, use the dps to your best choice.

最后使用[How to: Android – Spacing between CheckBox and text](http://sevennet.org/2014/12/21/how-to-android-spacing-between-checkbox-and-text/)的方法: 
####在工程里建一个values-v17  
![解决方案](/img/20150123_checkbox_theme_fix2_code.png)  
在values-v17 建styles.xml,大于4.2的使用paddingLeft=12dip的配置: 
![解决方案](/img/20150123_checkbox_theme_fix3_code.png)  
在values的styles.xml中设置小于4.2的值  paddingLeft=25dip
![解决方案](/img/20150123_checkbox_theme_fix1_code.png) 
解决。

编程记录1

为FrameLayout设置大小的代码如下:

1
2
3
4
5
6
7
8
9
mAdvertFrame = (FrameLayout) findViewById(R.id.photos_change);
LayoutInflater mInflater = getLayoutInflater();
mAdvertFrame.setPadding(5, 5, 5, 0);
// 五分之二高度
int wheight = getWindowManager().getDefaultDisplay().getHeight();
wheight = (int) (wheight * 0.25);
FrameLayout.LayoutParams frame_params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, wheight);
mAdvertFrame.setLayoutParams(frame_params);

结果出现一下错误: 设置LayoutParams错误java.lang.ClassCastException: android.widget.FrameLayout$LayoutParams cannot be cast to android.widg查得原因为: ### LinearLayout为子控件分配空间的时候,获取FrameLayout的LayoutParams的必须为LinearLayout.LayoutParams,而非FrameLayout.LayoutParams。 修改代码如图: 修改后代码
解决。

My New Page

##根据开始使用hexo写博客学习hexo的使用,这些是测试文字,接下来我会使用 hexo clean
hexo g
hexo d


2016-05-02
修改 hexo g 为 hexo g && cp CNAME public/
因为目前绑定了域名,需要在首页添加 CNAME 文件
修改 hexo d 为 sudo hexo d

静宏的 2014 -> 2015

一. 2014 总结

2014 总体还是比较好的,虽然技术上没有到一个让老板放心的高度,对于一些开源项目也只是达到了会用的状态,但也算是入了门。

成果

  1. 2014年3月在学校开始学习Android,到了5月末就开始跟师兄写一个创业项目,因为没有经验,所以效果也是不尽人意,一些常见的常识性错误在所难免,一些复杂一点的界面需要研究实践很久,但也总算是上线了。
  2. 拿到了学院的奖学金。虽然只是很小的奖,也不代表什么,但也算是这一年的肯定。
  3. 业余时间读了几十本书,都是泛读,但有一些印象比较深刻,应该也会影响到我,比如说《小强升职记》、《人生需要整理》这些时间管理类的书。
  4. 开始学会规划时间。对微博、QQ这一类比较消耗注意力的应用的依赖减少了,不像之前读书的时候闲的发慌。效率也提高了不少,真真正正地写代码。
  5. 下半年考过了软件设计师,以考促学,学到了一些基础。
  6. 学会了上github,但是工作环境比较恶劣,上网都是问题,谷歌更是别说了。。

不足

  1. 对Android一些没有用到的部分技术比较弱,没有深入去理解其中的原理。
  2. 对于知识不善于总结记录,读过的书想总结却无从下手,所以感觉总是囫囵吞枣。对于遇到过的问题也不善于表达,所以我是属于那80%的内容消费者。
  3. 对学习新技术没有强有力的驱动,我很早就有github账号,但是一直都是围观者,今年学习了一下git、python和正则表达式,但是没有什么驱动感觉学了就放一边了,和以前学校的课程一样,所以我打算找一下挑战性的项目来练手。
  4. 到目前为止还没有真真正正的提交/分享过代码,实在惭愧,有太多因素,其中主要是技术、时间不足。
  5. 在目前的项目上犯了很多错误,不能很有经验地写代码,以至于目前的项目不像主流的APP那样流畅优秀,这也是我的技术比较弱自己摸索的后果。

二. 2015 展望

2015 还是有很多计划的。

  • 打算报考系统分析师/系统结构师。
  • 学习实践GTD、时间管理。
  • 养成记录习惯,实践印象笔记和tudo。
  • 多读书,尤其是技术方面的。
  • 模仿主流的App,亲手做一个出来。
  • 买macbook pro,毕竟电脑是程序员的工具,买一个好点可以节省生命。
  • 学习ios和html5,今年看到了一些Android和html5的混合应用,也想看看是什么原理。
  • 尝试分享记录一些东西,不做旁观者。
  • 尝试从Eclipse转到Android Studio,毕竟很多开源项目都是在Android Studio上面了。