一、几种常见的日期和时间类介绍

介绍时间工具类不可避免必须要去触碰几个常见的日期和时间类,所以就简单介绍一下。

1、jdk1.8之前的日期时间类

a、Date类

我们可以通过new的方式生成一个Date对象,构造函数有参的和无参的,无参的是获取当前的系统的时间,Date这个类有不少过期的方法,而且Date是线程不安全的,所以当你需要考虑线程安全的情况时,Date其实使用起来有一定的局限。Date类中有fastTime成员变量,所以对于一个Date来说,存有时间戳的,这就给各种日期和时间对象的的转换提供了可能。

b、Calendar类

这是一个抽象类,其有多个子类补充了很多其他的功能。Calendar翻译过来就是日历,所以我们可以获取日期哪一年、哪一个月和哪一天,以及计算和获取某个月的第一天等等。Calendar也是线程不安全的,其中的set方法其实可以修改Calendar中的成员变量。

c、SimpleDateFormat

在java8之前我们一般习惯使用SimpleDateFormat这个类来进行时间转换成字符串的操作。但是这个类是线程不安全的,这就意味着我们在转换时候存在着转换风险,当然我们有解决的方法,一个是使用本地变量ThreadLocal存放SimpleDateFormat,如果使用Map来存放,其实在生成的时候还是有线程安全的问题,这里我们使用悲观锁来做一下限制,当然也可以使用线程安全的Map(Hashtable、synchronizedMap、ConcurrentHashMap)。

下面使用synchronized加锁:

/** 锁对象 */
private static final Object lockObj = new Object(); /** 存放不同的日期模板格式的sdf的Map */
private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>(); /**
* 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
*
* @param pattern
* @return
*/
private static SimpleDateFormat getSdf(final String pattern) {
ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern); // 生成的时候我们需要去考虑线程问题,Map并没有做线程处理,我们可以
if (tl == null) {
synchronized (lockObj) {
tl = sdfMap.get(pattern);
if (tl == null) {
// 只有Map中还没有这个pattern的sdf才会生成新的sdf并放入map
System.out.println("put new sdf of pattern " + pattern + " to map"); // 这里是关键,使用ThreadLocal<SimpleDateFormat>替代原来直接new SimpleDateFormat
tl = new ThreadLocal<SimpleDateFormat>() { @Override
protected SimpleDateFormat initialValue() {
System.out.println("thread: " + Thread.currentThread() + " init pattern: " + pattern);
return new SimpleDateFormat(pattern);
}
};
sdfMap.put(pattern, tl);
}
}
} return tl.get();
} /**
* ThreadLocal的原理是,获取一个静态变量的副本,这个其实是牺牲空间换取时间的案例
*
* @param date
* @param pattern
* @return
*/
public static String format(Date date, String pattern) {
return getSdf(pattern).format(date);
} public static Date parse(String dateStr, String pattern) throws ParseException {
return getSdf(pattern).parse(dateStr);
}

当然我们也可以使用线程安全的map:

private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new Hashtable<String, ThreadLocal<SimpleDateFormat>>();

d、sql包中其实也有几个时间的类  java.sql.Date/Time/Timestamp

首先这几个类继承自util包中的Date类,相当于将java.util.Date分开表示了。Date表示年月日等信息。Time表示时分秒等信息。Timestamp多维护了纳秒,可以表示纳秒。平时用的不是很多。也是线程不安全的类。

2、java8以后的时间日期类

在java8以后新增加了date-time包

a、Instant

这个类在java8之前和之后的时间日期类中都提供了转换的方法,这样就能很明确的通过Instant这个中间变量实现,java8之前和之后的时间日期类的相互转换。但是我们需要注意的是,Instant主要维护的是秒和纳秒字段,可以表示纳秒范围,如果不符合转换条件,就会抛出异常。

以Date类为例,看一下源码:

public static Date from(Instant instant) {
try {
return new Date(instant.toEpochMilli());
} catch (ArithmeticException ex) {
throw new IllegalArgumentException(ex);
}
} /**
* Converts this {@code Date} object to an {@code Instant}.
* <p>
* The conversion creates an {@code Instant} that represents the same
* point on the time-line as this {@code Date}.
*
* @return an instant representing the same point on the time-line as
* this {@code Date} object
* @since 1.8
*/
public Instant toInstant() {
return Instant.ofEpochMilli(getTime());
}

b、Clock

有获取当前时间的方法,也可以获取当前Instant,Clock是有时区或者说时区偏移量的。Clock是一个抽象类,其内部有几个子类继承自Clock。

几个抽象方法:

public abstract ZoneId getZone();
public abstract Clock withZone(ZoneId zone);
public long millis() {
return instant().toEpochMilli();
}
public abstract Instant instant();

c、ZoneId/ZoneOffset/ZoneRules

ZoneId和ZoneOffset都是用来代表时区的偏移量的,一般ZoneOffset表示固定偏移量,ZoneOffset 表示与UTC时区偏移的固定区域(即UTC时间为标准),不跟踪由夏令时导致的区域偏移的更改;ZoneId 表示可变区偏移,表示区域偏移及其用于更改区域偏移的规则夏令时。这里举一个简单的例子,美国东部时间,我们可以使用zoneId来表示,应为美国使用的是冬令时和夏令时的时候时间是有区别的,和中国的时差会有一个小时的差别。而ZoneRules 跟踪区域偏移如何变化,时区的真正规则定义在ZoneRules中,定义了什么时候多少偏移量。

常用的几个:

//美东时间
public static final String TIMEZONE_EST_NAME = "US/Eastern";
public static final ZoneId TIMEZONE_EST = ZoneId.of(TIMEZONE_EST_NAME);
//北京时间
public static final String TIMEZONE_GMT8_NAME = "GMT+8";
public static final ZoneId TIMEZONE_GMT8 = ZoneId.of(TIMEZONE_GMT8_NAME);
public static final ZoneOffset BEIJING_ZONE_OFFSET =ZoneOffset.of("+08:00");
public static final ZoneOffset STATISTIC_ZONE_OFFSET =ZoneOffset.of("+03:00");
private static final ZoneId NEW_YORK_ZONE_ID = ZoneId.of("America/New_York");
private static final ZoneId SHANGHAI_ZONE_ID = ZoneId.of("Asia/Shanghai");

d、LocalDateTime/LocalTime/LocalDate/ZoneDateTime

LocalDateTime/LocalTime/LocalDate都没有时区的概念,其中LocalDate主要是对日期的操作,LocalTime主要是对时间的操作,LocalDateTime则是日期和时间都会涉及:

jshell> LocalDate.now()
$46 ==> 2018-07-07 jshell> LocalDate.of(2018, 3, 30)
$47 ==> 2018-03-30 jshell> LocalTime.now()
$48 ==> 00:32:06.883656 jshell> LocalTime.of(12,43,12,33333);
$49 ==> 12:43:12.000033333 jshell> LocalDateTime.now()
$50 ==> 2018-07-07T00:32:30.335562400 jshell> LocalDateTime.of(2018, 12, 30, 12,33)
$51 ==> 2018-12-30T12:33 jshell> LocalDateTime.of(LocalDate.now(), LocalTime.now())
$52 ==> 2018-07-07T00:40:38.198318200

而ZoneDateTime会带有时区和偏移量的,可以看看他的成员变量:

 /**
* The local date-time.
*/
private final LocalDateTime dateTime;
/**
* The offset from UTC/Greenwich.
*/
private final ZoneOffset offset;
/**
* The time-zone.
*/
private final ZoneId zone;

可以看出这是集合了时间时区和偏移量的新的时间类。

二、常用的TimeUtils或者DateUtils的编写

import java.text.ParseException;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Hashtable;
import java.util.Map; /**
* Created by hehuaichun on 2018/10/22.
*/
public class TimeUtils {
/**
* 考虑港股和美股 采用GMT-1时区来确定报表日 即T日的报表包含北京时间T日9时至T+1日9时的数据
*/
public static final ZoneId TIMEZONE_GMT_1 = ZoneId.of("GMT-1");
public static final String TIMEZONE_EST_NAME = "US/Eastern";
public static final ZoneId TIMEZONE_EST = ZoneId.of(TIMEZONE_EST_NAME);
public static final String TIMEZONE_GMT8_NAME = "GMT+8";
public static final ZoneId TIMEZONE_GMT8 = ZoneId.of(TIMEZONE_GMT8_NAME); /**
* 常用时间转换格式
*/
public static final String TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DATE_NO_GAP_FORMAT = "yyyyMMdd";
public static final String DATE_GAP_FORMAT = "yyyy-MM-dd";
public static final String TIME_HH_MM_FORMAT = "HHmm";
public static final Map<String, DateTimeFormatter> DATE_TIME_FORMAT_MAP = new Hashtable<String, DateTimeFormatter>() {
{
put(TIME_FORMAT, DateTimeFormatter.ofPattern(TIME_FORMAT));
put(DATE_NO_GAP_FORMAT, DateTimeFormatter.ofPattern(DATE_NO_GAP_FORMAT));
put(DATE_GAP_FORMAT, DateTimeFormatter.ofPattern(DATE_GAP_FORMAT));
put(TIME_HH_MM_FORMAT, DateTimeFormatter.ofPattern(TIME_HH_MM_FORMAT));
}
}; /**
* 根据format的格式获取相应的DateTimeFormatter对象
*
* @param format 时间转换格式字符串
* @return
*/
public static DateTimeFormatter getDateTimeFormatter(String format) {
if (DATE_TIME_FORMAT_MAP.containsKey(format)) {
return DATE_TIME_FORMAT_MAP.get(format);
} else {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
DATE_TIME_FORMAT_MAP.put(format, formatter);
return formatter;
}
} /**
* 获取当前日期的开始时间
*
* @param zoneId 时间偏移量
* @return
*/
public static LocalDateTime todayStart(ZoneId zoneId) {
return startOfDay(0, zoneId);
} /**
* 获取当前的ZoneDateTime
*
* @param zoneId 时区偏移量
* @return
*/
public static ZonedDateTime now(ZoneId zoneId) {
return ZonedDateTime.now(zoneId);
} /**
* 获取当前日期的开始时间ZonedDateTime
*
* @param date 日期
* @param zoneId 时区偏移量
* @return
*/
public static ZonedDateTime localDateToZoneDateTime(LocalDate date, ZoneId zoneId) {
return date.atStartOfDay(zoneId);
} /**
* 获取当前日期的开始时间
*
* @param dateTime
* @return
*/
public static LocalDateTime startOfDay(ZonedDateTime dateTime) {
return dateTime.truncatedTo(ChronoUnit.DAYS).toLocalDateTime();
} /**
* 获取今天后的指定天数的开始时间
*
* @param plusDays 当前多少天后
* @param zoneId 时区偏移量
* @return
*/
public static LocalDateTime startOfDay(int plusDays, ZoneId zoneId) {
return startOfDay(now(zoneId).plusDays(plusDays));
} /**
* 获取指定日期的后几个工作日的时间LocalDate
*
* @param date 指定日期
* @param days 工作日数
* @return
*/
public static LocalDate plusWeekdays(LocalDate date, int days) {
if (days == 0) {
return date;
}
if (Math.abs(days) > 50) {
throw new IllegalArgumentException("days must be less than 50");
}
int i = 0;
int delta = days > 0 ? 1 : -1;
while (i < Math.abs(days)) {
date = date.plusDays(delta);
DayOfWeek dayOfWeek = date.getDayOfWeek();
if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY) {
i += 1;
}
}
return date;
} /**
* 获取指定日期的后几个工作日的时间ZoneDateTime
*
* @param date
* @param days
* @return
*/
public static ZonedDateTime plusWeekdays(ZonedDateTime date, int days) {
return plusWeekdays(date.toLocalDate(), days).atStartOfDay(date.getZone());
} /**
* 获取当前月份的第一天的时间ZoneDateTime
*
* @param zoneId
* @return
*/
public static ZonedDateTime firstDayOfMonth(ZoneId zoneId) {
return now(zoneId).withDayOfMonth(1);
} /**
* 将Date转成指定时区的Date
*
* @param date
* @return
*/
public static Date dateToDate(Date date, ZoneId zoneId) {
LocalDateTime dt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
return toDate(ZonedDateTime.of(dt, zoneId));
} /**
* 将LocalDate转成Date
*
* @param date
* @return
*/
public static Date toDate(LocalDate date) {
return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
} /**
* ZonedDateTime 转换成Date
*
* @param dateTime
* @return
*/
public static Date toDate(ZonedDateTime dateTime) {
return Date.from(dateTime.toInstant());
} /**
* String 转换成 Date
*
* @param date
* @param format
* @return
* @throws ParseException
*/
public static Date stringToDate(String date, String format, ZoneId zoneId) throws ParseException {
DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
Instant instant = Instant.from(formatter.parse(date));
return Date.from(instant);
} /**
* 将Date转成相应的时区的localDate
*
* @param date
* @param zoneId
* @return
*/
public static LocalDate toLocalDate(Date date, ZoneId zoneId) {
return date.toInstant().atZone(zoneId).toLocalDate();
} /**
* 将Instant转成指定时区偏移量的localDate
*
* @param instant
* @param zoneId
* @return
*/
public static LocalDate toLocalDate(Instant instant, ZoneId zoneId) {
return instant.atZone(zoneId).toLocalDate();
} /**
* 将Instant转换成指定时区偏移量的localDateTime
* @param instant
* @param zoneId
* @return
*/
public static LocalDateTime toLocalDateTime(Instant instant, ZoneId zoneId){
return instant.atZone(zoneId).toLocalDateTime();
} /**
* 将Instant转成系统默认时区偏移量的LocalDateTime
* @param instant
* @return
*/
public static LocalDateTime toLocalDateTime(Instant instant){
return toLocalDateTime(instant, ZoneId.systemDefault());
} /**
* 将ZoneDateTime 转成 指定时区偏移量的LocalDateTime
* @param zonedDateTime 时间
* @param zoneId 指定时区偏移量
* @return
*/
public static LocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime, ZoneId zoneId){
return zonedDateTime.toInstant().atZone(zoneId).toLocalDateTime();
} /**
*将ZoneDateTime 转成 LocalDateTime
* @param zonedDateTime
* @return
*/
public static LocalDateTime toLocalDateTime(ZonedDateTime zonedDateTime){
return zonedDateTime.toLocalDateTime();
} /**
* String 转成 ZoneDateTime
* 需要类似 yyyy-MM-dd HH:mm:ss 需要日期和时间信息完整信息
*
* @param date
* @param format
* @param zoneId
* @return
*/
public static ZonedDateTime stringToZoneDateTime(String date, String format, ZoneId zoneId) {
DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
return ZonedDateTime.parse(date, formatter);
} /**
* 将时间戳long转成ZonedDateTime
*
* @param timeStamp
* @param zoneId
* @return
*/
public static ZonedDateTime longToZoneDateTime(long timeStamp, ZoneId zoneId) {
return ZonedDateTime.from(Instant.ofEpochMilli(timeStamp).atZone(zoneId));
} /**
* 两个时区的zoneDateTime相互转换
*
* @param zonedDateTime 需要转换的如期
* @param zoneId 转换成的ZoneDateTime的时区偏移量
* @return
*/
public static ZonedDateTime zonedDateTimeToZoneDateTime(ZonedDateTime zonedDateTime, ZoneId zoneId) {
return ZonedDateTime.ofInstant(zonedDateTime.toInstant(), zoneId);
} /**
* Date 转成 指定时区偏移量的ZoneDateTime
* @param date
* @param zoneId
* @return
*/
public static ZonedDateTime toZonedDateTime(Date date, ZoneId zoneId){
return date.toInstant().atZone(zoneId);
} /**
* LocaldateTime 转成 指定时区偏移量的ZonedDateTime
* @param localDateTime 本地时间
* @param zoneId 转成ZonedDateTime的时区偏移量
* @return
*/
public static ZonedDateTime toZonedDateTime(LocalDateTime localDateTime, ZoneId zoneId){
return localDateTime.atZone(zoneId);
} /**
* Date装换成String
*
* @param date 时间
* @param format 转化格式
* @return
*/
public static String dateToString(Date date, String format, ZoneId zoneId) {
DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
return formatter.format(date.toInstant());
} /**
* ZoneDateTime 转换成 String
*
* @param dateTime
* @param zoneId localDateTime所属时区
* @return
*/
public static String zoneDateTimeToString(ZonedDateTime dateTime, String format, ZoneId zoneId) {
DateTimeFormatter formatter = getDateTimeFormatter(format).withZone(zoneId);
return dateTime.format(formatter);
} /**
* LocalDateTime 转成 String
*
* @param localDateTime
* @param format
* @return
*/
public static String localDateTimeToString(LocalDateTime localDateTime, String format){
DateTimeFormatter formatter = getDateTimeFormatter(format);
return localDateTime.format(formatter);
} /**
* 将ZonedDateTime转成时间戳long
*
* @parm zonedDateTime
* @return
*/
public static long zoneDateTimeToLong(ZonedDateTime zonedDateTime) {
return zonedDateTime.toInstant().toEpochMilli();
} /**
* 将LocalDateTime转成时间戳long
* @param localDateTime
* @param zoneId
* @return
*/
public static long toLong(LocalDateTime localDateTime, ZoneId zoneId){
return zoneDateTimeToLong(localDateTime.atZone(zoneId));
} }

三、编写的总结

1、首先我们尽量使用线程安全的时间类,也就是说尽量使用java8以后的几种时间类。从源码可以看出,java8之后的几个时间类是用final修饰的,线程安全。

2、我们要利用好静态变量,也就是不仅在使用的全局变量的时候需要线程安全的问题,还需要考虑时间空间的代价。其实SimpleDateTimeFormat和DateTImeFormatter的生成会消耗不少资源。

3、Date类中含有时间戳,Instant有两个成员变量seconds和nanos变量,这样Instant就作为一个中间量,作用很大,不仅提供了java8之前的时间类和java8之后时间类的转换,还提供了其他很多有用的方法。

4、ZonedDateTime含有时区偏移变量,其他的如Date、LocalDateTime、LocalDate、LocalTime、Instant都不含有时区偏移变量,但是Date想要转换成java8之后的时间类或者使用DateTimeFormatter转换时,需要提供ZoneId或者ZoneOffset,类似这种 date.toInstant().atZone(zoneId)。整体思路实现Date -> Instant -> ZonedDateTime -> 其他时间类。

5、ZonedDateTime是有日期和时间的,我们在DateTimeFormatter的时候需要注意, ZonedDateTime转成字符串非常的灵活,但是字符串转成ZonedDateTime的时候需要提供类似yyyy-MM-dd HH:mm:ss这种格式。

java的TimeUtils或者DateUtils的编写心得的更多相关文章

  1. JAVA第二次作业展示与学习心得

    JAVA第二次作业展示与学习心得 在这一次作业中,我学习了复选框,密码框两种新的组件,并通过一个邮箱登录界面将两种组件运用了起来.具体的使用方法和其他得组件并没有什么大的不同. 另外我通过查阅资料使用 ...

  2. 搭建java开发环境、使用eclipse编写第一个java程序

    搭建java开发环境.使用eclipse编写第一个java程序 一.Java 开发环境的搭建 1.首先安装java SDK(简称JDK). 点击可执行文件 jdk-6u24-windows-i586. ...

  3. 35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n); (2)编写一个类:ClassA来实现接口InterfaceA,实现int method(int n)接口方 法时,要求计算1到n的和; (3)编写另一个类:ClassB来实现接口InterfaceA,实现int method(int n)接口 方法时,要求计算n的阶乘(n

      35.按要求编写Java程序: (1)编写一个接口:InterfaceA,只含有一个方法int method(int n): (2)编写一个类:ClassA来实现接口InterfaceA,实现in ...

  4. 用Java为Hyperledger Fabric(超级账本)编写区块链智能合约链代码

    编写第一个 Java 链代码程序 在上一节中,您已经熟悉了如何构建.运行.部署和调用链代码,但尚未编写任何 Java 代码. 在本节中,将会使用 Eclipse IDE.一个用于 Eclipse 的 ...

  5. JAVA WEB快速入门之从编写一个基于SpringBoot+Mybatis快速创建的REST API项目了解SpringBoot、SpringMVC REST API、Mybatis等相关知识

    JAVA WEB快速入门系列之前的相关文章如下:(文章全部本人[梦在旅途原创],文中内容可能部份图片.代码参照网上资源) 第一篇:JAVA WEB快速入门之环境搭建 第二篇:JAVA WEB快速入门之 ...

  6. 文章推荐一个Java程序员跟大家谈谈从业心得

    一个Java程序员跟大家谈谈从业心得 2017-10-21 java那些事 java那些事 java那些事 微信号 csh624366188 功能介绍 分享java开发中常用的技术,分享软件开发中各种 ...

  7. Java初学者最近三次作业的心得体会

    作为一个初学者,简单的谈一下自己的作业心得体会.如果你是完全没有接触过Java的学习,本篇博文可能会有些收获,如果你已经学习Java有一段时间了,那么可以放弃这篇文章了,因为这篇文章讲解的是基本的东西 ...

  8. java web学习总结(二十三) -------------------编写自己的JDBC框架

    一.元数据介绍 元数据指的是"数据库"."表"."列"的定义信息. 1.1.DataBaseMetaData元数据 Connection.g ...

  9. 使用JAVA客户端对HDFS进行代码编写(五)

    在linux中,在JAVA中编程,耗时的不是代码的编写而是环境的搭建,版本的选择...日了苍天,昨天eclipse突然抽风在linux运行不起来,耗了几个小时,试了各种办法...现在windows环境 ...

随机推荐

  1. PyQt的QString 和 QStringList

    在Qt的C++实现中的QString 和 QStringList 在Python的实现中等效替换为 "str1" 和 ["str1","str2&qu ...

  2. jQuery制作简洁的多级联动Select下拉框

    今天我们要来分享一款很实用的jQuery插件,它是一个基于jQuery多级联动的省市地区Select下拉框,并且值得一提的是,这款联动下拉框是经过自定义美化过的,外观比浏览器自带的要漂亮许多.另外,这 ...

  3. Boost库初见

    Boost库是一个功能强大.构造精巧.跨平台.开源并且完全免费的C++库,有C++"准"标准库的美称! Boost有着与其它程序库(如MFC等)无法比拟的优点. Boost库采用了 ...

  4. ionic跳转(二)

    1)网上说要想在js里跳转用,$state.go()方法,但找了大半天都没找到在ionic使用$state的方法 2)要想用js跳转,直接用原生js跳转也是可以的 location.href='#ho ...

  5. maven web框架搭建

    前面我们描述了如何使用maven搭建一个最基本的Java Project 框架.在实际生产应用中,一般不会仅仅建立一个最基本的Java Project项目,目前 Java Web 开发已经成为Java ...

  6. java环境变量、集成开发环境与使用两个类

    1.集成开发环境(IDE,Integrated Development Environment )是用于提供程序开发环境的应用程序,一般包括代码编辑器.编译器.调试器和图形用户界面等工具.集成了代码编 ...

  7. Servlet------>mvc模式原理图

    常用开发模式: 客户在客户端 访问,发送请求到servlet servlet调用service接口 service实现类调用dao接口 dao接口通过jdbc技术操作数据库,并存储到javabean, ...

  8. .net core 启动域名及端口配置

    前两天转载一篇.net core 启动分析,由于发布时候一直纠结在默认5000端口上,所以好好研究了一下. 1.IIS集成 如果通过IIS当宿主的话,那这些都不是事情,强大的IIS可以帮助我们对站点的 ...

  9. Spring Data 查询方法的规则定义(五)

    有句话这样说  欲练神功  挥刀自宫  请亲们先回到第一个  从Spring data 介绍 开始看  搭好环境 跟着步伐一块走 Spring Data 的方法必须严格按照它的规范进行编写,如果写错了 ...

  10. python 中几个层次的中文编码.md

    转自:[http://swj.me/] 介绍 一直不太喜欢使用命令行,所以去年年底的技术创新中,使用TkInter来开发小工具.结果花费了大量的时间来学习TkInter ui的使用. 最近想整理该工具 ...