Resive world

Come world to Record life


  • Home

  • Tags

  • Categories

  • Archives

  • Sitemap

  • Search

解决 ubuntu 服务器中文乱码

Posted on 2018-03-15 | In Linux

之前买了阿里云的服务器,后来在上面编辑中文字符的时候发现乱码,在网上找了下解决方案,发现比较乱,有的也不太好用,特此整理了一下可用的一个方案。

Read more »

Java中日期处理的一些坑

Posted on 2018-03-11 | In Java

前言

记录下最近在用java处理日期格式的时候遇到的一些坑,虽然是挺简单的一些点,但是如果不了解清楚在使用的时候还是会走很多弯路的。

相关类

Java8 之后,涉及日期处理的类基本都分到了 java.time 包下,非常清楚,功能也做了强化。而在这之前,如果我们要处理日期,就只能组合的使用 java.util 以及 java.text 这两个包,感觉十分凌乱。当然,以后我们就不用再纠结这些了,直接用 java.time 包就行了。这个包下有众多类,不过一般在做日期转换的时候主要关注下面这几个:

1
2
3
4
5
6
7
8
9
LocalDate
LocalTime
LocalDateTime
ZonedDateTime
DateTimeFormatter
TemporalAccessor
TemporalQueries
TemporalQueries
ChronoField

前四个类是用来保存日期的,DataTimeFormatter 使用来将日期进行格式化和解析的,剩下的是用来从格式化的时间数据中提取信息的,分工明确。看起来挺简单的,但是在用的时候还是有一些坑。。。

一些坑

日期本地化

很经典的一个例子就是给一个格式化的日期串,比如 Sun Feb 13 15:00:10 +0000 2011 我们该怎么把他提取到 LocalDateTime 里。
看上去没难度,查一下 DateTimeFormatter 的文档,找到每个部分对应的 pattern 即可:

1
2
3
4
5
6
public void test() {
String dateString = "Sun Feb 13 15:00:10 2011";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy");
LocalDateTime dateTime = LocalDateTime.parse(dateString, formatter);
System.out.println(dateTime);
}

然而,这段代码却极大可能会报错:

1
2
3
4
Exception in thread "main" java.time.format.DateTimeParseException: Text 'Sun Feb 13 15:00:10 2011' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
......

说是无法解析,研究了半天才发现 DateTimeFormatter 这个类默认进行了本地化的设置,如果默认环境是中文,那么他只能解析用中文表示的字符串,类似 星期日 二月 13 15:00:10 2011。。。
解决的方法也很简单,强行传入一个本地化参数即可:

1
2
3
4
5
6
public void test() {
String dateString = "Sun Feb 13 15:00:10 2011";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy", Locale.US);
LocalDateTime dateTime = LocalDateTime.parse(dateString, formatter);
System.out.println(dateTime);
}

带时区的数据

如果我们希望打印带时区信息的格式串,一定要用 ZonedDateTime 而不能用 LocalDateTime ,比如

1
2
3
4
5
6
public void test() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss xx yyyy", Locale.US);
String zoned = formatter.format(ZonedDateTime.now());//正常
String local = formatter.format(LocalDateTime.now());//报错

}

正常的会打印出 Sun Mar 11 14:22:43 +0800 2018,而错误的就会报下面的错:

1
2
3
4
5
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds
at java.time.LocalDate.get0(LocalDate.java:680)
at java.time.LocalDate.getLong(LocalDate.java:659)
at java.time.LocalDateTime.getLong(LocalDateTime.java:720)
......

正确使用格式串

关于格式串的含义可以参考 java doc,这里有很多意义相近的表示方法,用的时候要用心区分。
比如文档里有这两行:

1
2
3
4
Symbol  Meaning         Presentation      Examples
------ ------- ------------ -------
Y week-based-year year 1996; 96
y year-of-era year 2004; 04

所以我们指的年到底是哪个年呢?这个要好好区分,其实我们一般用的年是指 ‘year-of-era’ ,如果用了另外一个就会发生解析错误。

参考资料

java8 doc DateTimeFormatter

面试分享:2018阿里巴巴前端面试总结(题目+答案)

Posted on 2018-02-28 | In Interview

脑子混了记得不多了,记得多少就记录多少吧。。。。

Read more »

crontab 踩坑之绝对路径

Posted on 2018-02-13 | In Linux

由于放假后网络原因不方便使用电脑,需要创建一个 crontab 定时任务,用来在每天固定时间执行一个 Shell 脚本

Read more »

Slf4j框架理解与分析

Posted on 2018-02-04 | In Java

前言

这两天在看设计模式相关的书,正好看到了门面模式,感觉不太能领悟它的精髓,就想找一些例子来看,突然发现这个slf4j框架不就是一个门面(facade /fəˈsɑ:d/)么,干脆就直接拿来看一看了,正好也把java的日志系统也了解了解。

日志系统

不谈那些比较复杂的分布式日志系统,这里主要讨论的是单机常用的那些日志框架。现在在Java生态中存在的日志框架也是挺多的了,初来乍到看起来有点懵,其实所谓的日志框架主要分两种:

  • 一种是日志框架门面(facade),用来统一一个共同的接口,方便我们用相同的代码支持不同的实现方法。主要包括commons-logging和slf4j两套规范。
  • 另一种就是日志框架的实现了,主要包括log4j、log4j2、logback、slf4j-simple和java.util.logging包。

这两类东西当然要一起用,显然属于apache的东西一般放在一起用(log4j log4j2 commons-logging),属于QOS的东西也一般放在一起用(logback slf4j)。不过话也不是绝对的,既然是门面嘛,肯定要支持的全一点了,如果门面跟实现的api不同的话呢,那就用适配器模式写一个adapter层做适配就好了。比如slf4j支持jul跟log4j就是通过适配器来做的。

这种通过门面来统一日志框架的好处是显而易见的,那就是我们在写代码的时候只需要知道门面的api就行了(这通常是比较简单而且一致的),不需要知道不同框架的实现细节。我们完全可以在运行时再指定我们使用哪一套日志框架,这样就做到了业务逻辑与日志系统的解耦。如果是从依赖关系的角度来讲就是,比如对与slf4j,我们只需要在编译时依赖slf4j-api.jar(编译依赖),这个jar里几乎只是定义了接口跟一些通用的工具;然后我们可以在运行时再去指定各种不同的实现,比如slf4j-simple.jar(运行依赖)。

这种使用方式与我们使用sql绑定驱动挺像的,其实他们都是采用的类似的思想。

但是这就带来一个问题,这种运行时进行服务发现的功能是怎么实现的呢?这其实至少有两种解决办法,一种是通过配置文件,将需要加载的类写到配置文件里,然后再通过ClassLoader去加载这个类,做到运行时的加载(比如commons-logging的配置);还有一种就更加方便了,不需要额外的配置文件,就能做到类的动态加载—SPI。

SPI

概述

SPI 就是(Service Provider Interface),字面意思就是服务提供接口,这套规范其实我在之前的Lombok原理分析中提到过,只是当时还不知道这个就是这么一个玩意。。。他的用法其实很简单,就是在服务调用者跟服务提供者之间商定了一个协议:

  1. 服务发现者需要定义一个接口。
  2. 服务提供者要实现之前的接口,然后在 classpath 里的 META-INF/services 文件夹下新建一个文件,文件名是之前的接口的全类名,文件内容是实现类的全类名。
  3. 服务发现者保证会通过 ServiceLoader 在类路径内的所有jar包中搜索指定接口的实现类,进行实例化。

显然,一般来讲服务发现者一般就不能直接通过构造函数来构造这个接口的实现类,而是通过静态工厂方式封装实例化的过程。

例子

举一个简单的例子,首先定义一个没有实现的接口Ispi.java:

1
2
3
4
5
package com.mythsman.test;

public interface Ispi {
void say();
}

我们的目的是最终能够在场景类中通过这样的方法来调用Test.java:

1
2
3
4
5
6
7
8
package com.mythsman.test;

public class Test {
public static void main(String[] args) {
Ispi ispi = SpiFactory.getSpi();
ispi.say();
}
}

那么首先我们需要写一个实现类SpiImpl.java:

1
2
3
4
5
6
7
8
9
package com.mythsman.test;

public class SpiImpl implements Ispi {
@Override
public void say() {
System.out.println("Hey , I'm an implement");
}
}

然后创建resources/META-INF/services文件夹(resources文件夹已写入类路径),在文件夹下写入创建一个文件名为com.mythsman.test.Ispi,内容为com.mythsman.test.SpiImpl,用以注册这个服务。

当然,上面这两步既可以在同一个项目中,也可以在另外一个项目中,只需要打包后将jar包放入类路径即可。

接着来写工厂SpiFactory.java:

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
package com.mythsman.test;

import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;

public class SpiFactory {

public static Ispi getSpi() {
ServiceLoader<Ispi> loader = ServiceLoader.load(Ispi.class);
List<Ispi> ispiList = new ArrayList<>();
for (Ispi ispi : loader) {
ispiList.add(ispi);
}
if (ispiList.size() > 2) {
System.err.println("Mutiple implements found.");
return new SubstituteSpi();
} else if (ispiList.size() < 1) {
System.err.println("No implements found.");
return new SubstituteSpi();
} else {
return ispiList.get(0);
}
}
}

这里的ServiceLoader会去类路径中查找所有支持了Ispi接口的实现类,并返回一个迭代器,这个迭代器会实例化所有的实现类。当然我们可能会发现多个实现类或者没有发现实现类,这时为了保证代码的健壮我们通常会写一个默认的实现SubstituteSpi.java:

1
2
3
4
5
6
7
8
package com.mythsman.test;

public class SubstituteSpi implements Ispi {
@Override
public void say() {
System.out.println("Well I'm a backburner implement");
}
}

如果能成功发现一个,那么我们就可以返回这一个实例了。

这种方法应该是一种比较清楚的服务动态发现的方法了。

实践

实际上我们的很多库都采用了SPI的规范只是我们可能用到的时候对原理不太了解,其实下面这些常见的框架里都用到了SPI:

JDBC

我们在入门的时候都学过用jdbc包,用的时候我们都被要求写一段类似下面的代码:

1
Class.forName("com.mysql.cj.jdbc.Driver");

其实这段代码没有任何实际意义,只是显式的加载了一个类,告诉我们记得添加这个jar包,实际上只要将这个jar包放在了类路径里面,这段话其实就没有必要了。
我们去查 mysql-connector-java 这个包就会发现,他用的就是spi的方法,将自己的 com.mysql.cj.jdbc.Driver 这个类注册给了 java.sql.Driver 这个接口。加载的时候用的其实也是 ServiceLoader 。

Lombok

lombok的原理也是类似,他用自己写的 AnnotationProcessor 去实现 javax.annotation.processing.Processor ,从而做到在编译期进行注解处理。

Slf4j

这个我们下面细讲。

反正我当前遇到的jar包,只要它的scope是runtime,基本上都是通过这种方法来搞得。

SLF4J

成员

slf4j通过上述的方法构建了自己的生态圈,在slf4j-api-xxx.jar的统一管理下容纳了多种实现:

  • slf4j-log4j12-xxx.jar
  • slf4j-jdk14-xxx.jar
  • slf4j-nop-xxx.jar
  • slf4j-simple-xxx.jar
  • slf4j-jcl-xxx.jar
  • logback-classic-xxx.jar
    其中slf4j-nop比较无聊,其实就是什么都没有实现,所有的log都不会处理;slf4j-simple比较小巧,基本能满足简单的使用,配置也很少;logback-classic是slf4j相同作者的作品;剩下其他的则都是相当于一个适配层,将slf4j与其他实现进行适配。

接口

那么如果我们想写一个支持slf4j规范的框架应该怎么写呢,其实很简单,只需要实现 org.slf4j.spi.SLF4JServiceProvider 这个接口即可。比如slf4j-jdk14就是这么写的:

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
package org.slf4j.jul;

import org.slf4j.ILoggerFactory;
import org.slf4j.IMarkerFactory;
import org.slf4j.helpers.BasicMDCAdapter;
import org.slf4j.helpers.BasicMarkerFactory;
import org.slf4j.spi.MDCAdapter;
import org.slf4j.spi.SLF4JServiceProvider;

public class JULServiceProvider implements SLF4JServiceProvider {

public static String REQUESTED_API_VERSION = "1.8.99"; // !final

private ILoggerFactory loggerFactory;
private IMarkerFactory markerFactory;
private MDCAdapter mdcAdapter;

public ILoggerFactory getLoggerFactory() {
return loggerFactory;
}

public IMarkerFactory getMarkerFactory() {
return markerFactory;
}

public MDCAdapter getMDCAdapter() {
return mdcAdapter;
}

public String getRequesteApiVersion() {
return REQUESTED_API_VERSION;
}

public void initialize() {
loggerFactory = new JDK14LoggerFactory();
markerFactory = new BasicMarkerFactory();
mdcAdapter = new BasicMDCAdapter();
}
}

对于简单的日志系统,我们其实只需要实现一下ILoggerFactory接口就行了,IMarkerFactory 跟 MDCAdapter用默认的就可以了。

使用

用起来很方便,用LoggerFactory创建logger即可:

1
2
3
4
5
6
7
8
9
10
11
package com.mythsman.test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Test {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Test.class);
logger.info("this is an info");
}
}

getLogger传入的其实就是一个名字而已,方便我们定位而且这些log默认会写到System.err里。

配置

那么如果我们想对日志进行配置该怎么弄呢,比如设置日志级别,日志输出文件?遗憾的是,日志门面并没有对这个进行统一,而是将这个功能交给了不同的日志实现自己去做(毕竟这东西也不太好统一)。比如logback和log4j,我们就要写长长的xml;比如slf4j-simple,我们就要去看他源码,找到他定义的日志文件和配置项(查看SimpleLoggerConfiguration类就很清楚了)。

MDC

记得slf4j的接口里有一个MDC,这个东西是做什么的呢?其实从他的名字就可以猜到(Mapped Diagnostic Context),其实就是一个类似Map的上下文。他解决了我们可能会希望进行一些即时数据的保存与计算:

1
2
3
4
5
6
7
8
9
10
package com.mythsman.test;

import org.slf4j.MDC;

public class Test {
public static void main(String[] args) {
MDC.put("key", "value");
System.out.println(MDC.get("key"));
}
}

其实说白了也就是一个ThreadLocal的ConcurrentHashMap。。。。

参考文章

Java常用日志框架介绍
Slf4j user manual
Java 规范 SPI
slf4j log4j logback关系详解和相关用法

小米降噪耳机(3.5mm)评测

Posted on 2018-02-03 | In 分享镜

之前在同学的安利之下,我剁手了一副小米降噪耳机 Type-C 版(评测可以看 这里)。但是 Type-C 版的音质并不好,而且听歌时手机不能充电。于是,我就手痒,又买了小米降噪耳机 3.5mm 版(完了,没钱了)

Read more »

SED 命令简明教程

Posted on 2018-01-31 | In Linux

awk于 1977 年出生,今年 36 岁本命年,sed比awk大 2-3 岁,awk就像林妹妹,sed 就是宝玉哥哥了。所以 林妹妹跳了个Topless,他的哥哥sed坐不住了,也一定要出来抖一抖。

sed全名叫stream editor,流编辑器,用程序的方式来编辑文本,相当的hacker啊。sed基本上就是玩正则模式匹配,所以,玩sed的人,正则表达式一般都比较强。

Read more »

测评:海淀、西城区高三期末语文试卷对比

Posted on 2018-01-30 | In 自言语

2018 年是北京市高考改革前的倒数第二年。在新课标改革前夕,高考的变革在试卷上如何体现成为大家关心的重点。通过高三期末试题的分析,可以看到今年高考的一个走向和趋势。

Read more »

在Hexo博客 NexT主题中部署Wildfire评论系统

Posted on 2018-01-30 | In Linux

前一段时间,发现了一个评论系统很好用,果断把这个评论系统换到自己的博客里了。

所以本文主要讲在 Hexo 的 NexT 主题中如何使用 Wildfire ,至于其他的博客以及其他的主题中如何使用的问题,我就不多说了。有需求的朋友可以去项目主页提问,或者在这里提问也可以。如果我懂得话一定会回答的。

Read more »

使用 JavaScript 实现简单的拖拽

Posted on 2018-01-29 | In javascript

步骤

使用 JavaScript 实现拖拽的步骤:

  • 让元素捕获事件(mousedown, mousemove & mouseup)
  • 单击并不释放,触发 mousedown,标记开始拖拽,并获取元素和鼠标的位置
  • 拖动鼠标,触发 mousemove,不断的获取鼠标的位置,并通过计算重新确定元素的位置
  • 释放师表,触发 mouseup,结束拖拽,确定元素位置并更新
Read more »
1…222324…58

574 posts
69 categories
286 tags
© 2024 Companyd
Powered by Hexo
|
Theme — NexT.Muse v5.1.4