1hutool源码分析:DateUtil(时间工具类)-当前时间和当前时间戳_hutool 日期转10位时间戳

	return toString(DatePattern.NORM_DATETIME_FORMAT);
}

DateTime重写了toString() 方法,格式化了时间,返回"yyyy-MM-dd HH:mm:ss" 格式字符串


![image-20210706210932679](https://img-blog.csdnimg.cn/img_convert/bbf6c74379abe2cc303ada4b9579fe2e.png)


从\*\*DateTime(long timeMillis, TimeZone timeZone)**中源码中,可看出,在**Date(long date)\*\*基础上,多加了timeZone的赋值。


![image-20210706213821993](https://img-blog.csdnimg.cn/img_convert/dbf59f8db7fd48575e1a734f67985a89.png)


#### 传统写法一


获取当前时间



	//获取当前时间
	Date date = new Date();
	System.out.println(date.toString());
	//格式化时间
	SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
	System.out.println("当前时间为: " + ft.format(date));

#### 对比


hutool的写法对比传统写法会更加简洁一些,而且定义了DateTime对象,这个是hutool的DateUtil(时间工具类)的基础。


### 源码分析二



/\*\*

* 当前时间
*/
public DateTime() {
this(TimeZone.getDefault());
}


\*\*TimeZone.getDefault()\*\*是JDK自带的方法,所属包:java.util;方法返回此主机(程序运行的主机)的默认时区


#### 使用默认时区是否有风险?


我们来看下默认时区是怎么取值的


![image-202107085554049](https://img-blog.csdnimg.cn/img_convert/46ad93a043944770f22032c11fd7aa56.png)


1、java.util.TimeZone类中getDefault方法的源代码显示,它最终是会调用sun.util.calendar.ZoneInfo类的getTimeZone 方法。这个方法为需要的时间区域返回一个作为ID的String参数。


2、时间ID zoneID的获取方式:先从JVM中的user.timezone变量中读取,如果读不到,再读取系统的默认时区


**zoneID = getSystemTimeZoneID(javaHome);**



/**
* Gets the platform defined TimeZone ID.
**/
private static native String getSystemTimeZoneID(String javaHome);


看到这个native ,说明已经挖到核心了,到了这一步,还是不清楚是怎么获取系统的默认时区的,那怎么办,JDK代码只能跟到这里。


转战OpenJDK,源码下载方式:https://gitee.com/mirrors/openjdk


3、如果再读不到,就用默认的 **GMT\_ID = "GMT"**


##### 避免风险最佳实践


JVM中的user.timezone变量中设置时区


#### 什么是native


native是一个计算机函数,一个Native Method就是一个Java调用非Java代码的接口。方法的实现由非Java语言实现,比如C或C++。


##### native的源码怎么看呢


以\*\*private static native String getSystemTimeZoneID(String javaHome)\*\*为例



getSystemTimeZoneID方法所在的package java.util.TimeZone;


如图所示,找到TimeZone.c下的getSystemTimeZoneID方法


![image-20210706233905997](https://img-blog.csdnimg.cn/img_convert/dd40696814c634cbf4d9c8d76fe9e509.png)


![image-20210706234052425](https://img-blog.csdnimg.cn/img_convert/192a5875393e28b15a078779115fc94c.png)



/*
* Gets the platform defined TimeZone ID
*/
JNIEXPORT jstring JNICALL
Java_java_util_TimeZone_getSystemTimeZoneID(JNIEnv *env, jclass ign,
jstring java_home, jstring country)
{
const char *cname;
const char *java_home_dir;
char *javaTZ;

if (java_home == NULL)
    return NULL;

java_home_dir = JNU\_GetStringPlatformChars(env, java_home, 0);
if (java_home_dir == NULL)
    return NULL;

if (country != NULL) {
    cname = JNU\_GetStringPlatformChars(env, country, 0);
    /\* ignore error cases for cname \*/
} else {
    cname = NULL;
}

/\*

* Invoke platform dependent mapping function
*/
javaTZ = findJavaTZ_md(java_home_dir, cname);

free((void \*)java_home_dir);
if (cname != NULL) {
    free((void \*)cname);
}

if (javaTZ != NULL) {
    jstring jstrJavaTZ = JNU\_NewStringPlatform(env, javaTZ);
    free((void \*)javaTZ);
    return jstrJavaTZ;
}
return NULL;

}


重点:调用不同平台相关的映射函数



/*
* Invoke platform dependent mapping function
*/
javaTZ = findJavaTZ_md(java_home_dir, cname);


去查找findJavaTZ\_md方法时,发现存在分别在solaris和windows两个目录下。


![image-20210706234905448](https://img-blog.csdnimg.cn/img_convert/d01d161204e65ec25cd82071373b2d9c.png)


查了下这两个目录的差别:



因为OpenJDK里,Java标准库和部分工具的源码repo(jdk目录)里,BSD和Linux的平台相关源码都是在solaris目录里的。
原本Sun JDK的源码里平台相关的目录就是从solaris和windows这两个目录开始的,后来Unix系的平台相关代码全都放在solaris目录下了,共用大部分代码。

作者:RednaxelaFX
链接:https://www.zhihu.com/question/58982441/answer/170264788
来源:知乎


**简单的理解就是:**


**window系统下,使用windows目录下编译的JDK代码**


**unix系的平台下,使用solaris目录下编译的JDK代码**


#### 了解不同系统下findJavaTZ\_md方法执行


##### windows系统



/*
* Detects the platform time zone which maps to a Java time zone ID.
*/
char *findJavaTZ_md(const char *java_home_dir, const char *country)
{
char winZoneName[MAX_ZONE_CHAR];
char winMapID[MAX_MAPID_LENGTH];
char *std_timezone = NULL;
int result;

winMapID[0] = 0;
result = getWinTimeZone(winZoneName, winMapID);

if (result != VALUE_UNKNOWN) {

必看视频!获取2024年最新Java开发全套学习资料

if (result == VALUE_GMTOFFSET) {
        std_timezone = \_strdup(winZoneName);
    } else {
        std_timezone = matchJavaTZ(java_home_dir, result,
                                   winZoneName, winMapID, country);
    }
}

return std_timezone;

}


注释写得很清楚,获取“Time Zones”注册表中的当前时区



/*
* Gets the current time zone entry in the “Time Zones” registry.
*/
static int getWinTimeZone(char *winZoneName, char *winMapID)
{

}


时区的设置方式:


![image-202107086550950](https://img-blog.csdnimg.cn/img_convert/ef131da3908ea708d3982aa52b2a0241.png)


那时区上的选择值是从哪取到的,上面有说了,是在注册表中取值


打开注册表 :Regedit–>



计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\


##### unix系的平台


findJavaTz\_md()方法的注释上写得很清楚了:将平台时区ID映射为Java时区ID



/*
* findJavaTZ_md() maps platform time zone ID to Java time zone ID
* using <java_home>/lib/tzmappings. If the TZ value is not found, it
* trys some libc implementation dependent mappings. If it still
* can’t map to a Java time zone ID, it falls back to the GMT+/-hh:mm
* form. `country’, which can be null, is not used for UNIX platforms.
*/
/*ARGSUSED1*/
char *
findJavaTZ_md(const char *java_home_dir, const char *country)
{
char *tz;
char *javatz = NULL;
char *freetz = NULL;

tz = getenv("TZ");

#ifdef __linux__
if (tz == NULL) {
#else
#ifdef __solaris__
if (tz == NULL || *tz == ‘\0’) {
#endif
#endif
tz = getPlatformTimeZoneID();
freetz = tz;
}

/\*

* Remove any preceding ‘:’
*/
if (tz != NULL && *tz == ‘:’) {
tz++;
}

#ifdef __solaris__
if (strcmp(tz, “localtime”) == 0) {
tz = getSolarisDefaultZoneID();
freetz = tz;
}
#endif

if (tz != NULL) {

#ifdef __linux__
/*
* Ignore “posix/” prefix.
*/
if (strncmp(tz, “posix/”, 6) == 0) {
tz += 6;
}
#endif
javatz = strdup(tz);
if (freetz != NULL) {
free((void *) freetz);
}
}
return javatz;
}


步骤:


1、使用< Java home>/lib/tzmappings,。如果没有找到"TZ"变量,就进行第2步


2、 tz = getPlatformTimeZoneID(); 执行Linux特定的映射,如果找到,返回一个时区ID,否则返回null


【Linux】Centos7修改系统时区timezone方式:



timedatectl


![image-202107086455780](https://img-blog.csdnimg.cn/img_convert/b3b0b289c75764ec03f9773b953237a7.png)


修改时区



timedatectl set-timezone Asia/Shanghai


![image-2021070864438866](https://img-blog.csdnimg.cn/img_convert/96fe7a99c5ce515897eefb796a0d888a.png)


3、对比/etc/localtime与"/usr/share/zoneinfo目录下的文件,如果一致,就返回时区ID,没有则到第4步


4、返回到GMT


## 方法名称:DateUtil.dateSecond()


### 方法描述


当前时间,转换为{@link DateTime}对象,忽略毫秒部分


### 源码分析一



/**
* 当前时间,转换为{@link DateTime}对象,忽略毫秒部分
*
* @return 当前时间
* @since 4.6.2
*/
public static DateTime dateSecond() {
return beginOfSecond(date());
}

public static DateTime beginOfSecond(Date date) {
	return new DateTime(beginOfSecond(calendar(date)));
}

public static Calendar beginOfSecond(Calendar calendar) {
	return truncate(calendar, DateField.SECOND);
}

到这里时,实际上要经过三步方法的调用


1. calendar(date) 转换为Calendar对象
2. beginOfSecond(Calendar calendar) 获取秒级别的开始时间,即忽略毫秒部分
3. DateTime(Calendar calendar) Calendar对象转为DateTime对象



//CalendarUtil 类
/**
* 获取秒级别的开始时间,即忽略毫秒部分
*
* @param calendar 日期 {@link Calendar}
* @return {@link Calendar}
* @since 4.6.2
*/
public static Calendar beginOfSecond(Calendar calendar) {
return truncate(calendar, DateField.SECOND);
}


/\*\*

* 修改日期为某个时间字段起始时间
*
* @param calendar {@link Calendar}
* @param dateField 时间字段
* @return 原{@link Calendar}
*/
public static Calendar truncate(Calendar calendar, DateField dateField) {
return DateModifier.modify(calendar, dateField.getValue(), DateModifier.ModifyType.TRUNCATE);
}


//DateModifier类
/\*\*

* 修改日期
*
* @param calendar {@link Calendar}
* @param dateField 日期字段,即保留到哪个日期字段
* @param modifyType 修改类型,包括舍去、四舍五入、进一等
* @return 修改后的{@link Calendar}
*/
public static Calendar modify(Calendar calendar, int dateField, ModifyType modifyType) {
// AM_PM上下午特殊处理
if (Calendar.AM_PM == dateField) {
boolean isAM = DateUtil.isAM(calendar);
switch (modifyType) {
case TRUNCATE:
calendar.set(Calendar.HOUR_OF_DAY, isAM ? 0 : 12);
break;
case CEILING:
calendar.set(Calendar.HOUR_OF_DAY, isAM ? 11 : 23);
break;
case ROUND:
int min = isAM ? 0 : 12;
int max = isAM ? 11 : 23;
int href = (max - min) / 2 + 1;
int value = calendar.get(Calendar.HOUR_OF_DAY);
calendar.set(Calendar.HOUR_OF_DAY, (value < href) ? min : max);
break;
}
// 处理下一级别字段
return modify(calendar, dateField + 1, modifyType);
}

	// 循环处理各级字段,精确到毫秒字段
	for (int i = dateField + 1; i <= Calendar.MILLISECOND; i++) {
		if (ArrayUtil.contains(IGNORE_FIELDS, i)) {
			// 忽略无关字段(WEEK\_OF\_MONTH)始终不做修改
			continue;
		}

		// 在计算本周的起始和结束日时,月相关的字段忽略。
		if (Calendar.WEEK_OF_MONTH == dateField || Calendar.WEEK_OF_YEAR == dateField) {
			if (Calendar.DAY_OF_MONTH == i) {
				continue;
			}
		} else {
			// 其它情况忽略周相关字段计算
			if (Calendar.DAY_OF_WEEK == i) {
				continue;
			}
		}

		modifyField(calendar, i, modifyType);
	}
	return calendar;
}

![image-202107087711643](https://img-blog.csdnimg.cn/img_convert/55c20949ba60e700d1066660aa9c013d.png)


循环处理各级字段:


1、// 忽略无关字段(WEEK\_OF\_MONTH)始终不做修改



/** 忽略的计算的字段 */
private static final int[] IGNORE_FIELDS = new int[] { //
Calendar.HOUR_OF_DAY, // 与HOUR同名
Calendar.AM_PM, // 此字段单独处理,不参与计算起始和结束
Calendar.DAY_OF_WEEK_IN_MONTH, // 不参与计算
Calendar.DAY_OF_YEAR, // DAY_OF_MONTH体现
Calendar.WEEK_OF_MONTH, // 特殊处理
Calendar.WEEK_OF_YEAR // WEEK_OF_MONTH体现
};


2、// 在计算本周的起始和结束日时,月相关的字段忽略。


3、modifyField(calendar, i, modifyType); i=14(含义Calendar.MILLISECOND)


![image-2021070874122944](https://img-blog.csdnimg.cn/img_convert/a60d21e7c6338a29175f37ef8b5e137f.png)


4、calendar.set(field, DateUtil.getBeginValue(calendar, field));


![image-2021070874456421](https://img-blog.csdnimg.cn/img_convert/de5e83e775b52ed0aa38146fb240fda2.png)



/**
 * 获取指定日期字段的最小值,例如分钟的最小值是0
 *
 * @param calendar  {@link Calendar}
 * @param dateField {@link DateField}
 * @return 字段最小值
 * @see Calendar#getActualMinimum(int)
 * @since 4.5.7
 */
public static int getBeginValue(Calendar calendar, int dateField) {
	if (Calendar.DAY_OF_WEEK == dateField) {
		return calendar.getFirstDayOfWeek();
	}
	return calendar.getActualMinimum(dateField);
}

所以就得到我们想要的,忽略毫秒



calendar.set(field, DateUtil.getBeginValue(calendar, field));
–>calendar.set(field,0);//field=14(含义Calendar.MILLISECOND)


![image-2021070874736271](https://img-blog.csdnimg.cn/img_convert/2d56fee0c7fb8cc347e69f1dfde88f02.png)


## 方法名称:DateUtil.now()


### 方法描述


当前时间,格式 yyyy-MM-dd HH:mm:ss


### 源码分析一



/**
* 当前时间,格式 yyyy-MM-dd HH:mm:ss
*
* @return 当前时间的标准形式字符串
*/
public static String now() {
return formatDateTime(new DateTime());
}


	/\*\*

* 格式化日期时间

* 格式 yyyy-MM-dd HH:mm:ss
*
* @param date 被格式化的日期
* @return 格式化后的日期
*/
public static String formatDateTime(Date date) {
if (null == date) {
return null;
}
return DatePattern.NORM_DATETIME_FORMAT.format(date);
}


![image-202107089719007](https://img-blog.csdnimg.cn/img_convert/5f17a1045e4a0be388405fc080a2436d.png)



//FastDateFormat 是一个线程安全的实现。
public class FastDateFormat extends Format implements DateParser, DatePrinter {

}


凭什么说FastDateFormat是一个线程安全的实现,让我们来深挖一下



/\*\*

* 标准日期时间格式,精确到秒 {@link FastDateFormat}:yyyy-MM-dd HH:mm:ss
*/
public static final FastDateFormat NORM_DATETIME_FORMAT = FastDateFormat.getInstance(NORM_DATETIME_PATTERN);


public static FastDateFormat getInstance(final String pattern) {
	return CACHE.getInstance(pattern, null, null);
}

看到CACHE就来精神了,说明这里用了缓存



public F getInstance(final String pattern, TimeZone timeZone, Locale locale) {

F format = cInstanceCache.get(key);

}

总结:绘上一张Kakfa架构思维大纲脑图(xmind)

image

其实关于Kafka,能问的问题实在是太多了,扒了几天,最终筛选出44问:基础篇17问、进阶篇15问、高级篇12问,个个直戳痛点,不知道如果你不着急看答案,又能答出几个呢?

若是对Kafka的知识还回忆不起来,不妨先看我手绘的知识总结脑图(xmind不能上传,文章里用的是图片版)进行整体架构的梳理

梳理了知识,刷完了面试,如若你还想进一步的深入学习解读kafka以及源码,那么接下来的这份《手写“kafka”》将会是个不错的选择。

  • Kafka入门

  • 为什么选择Kafka

  • Kafka的安装、管理和配置

  • Kafka的集群

  • 第一个Kafka程序

  • Kafka的生产者

  • Kafka的消费者

  • 深入理解Kafka

  • 可靠的数据传递

  • Spring和Kafka的整合

  • SpringBoot和Kafka的整合

  • Kafka实战之削峰填谷

  • 数据管道和流式处理(了解即可)

image

image

Locale locale) {

F format = cInstanceCache.get(key);

}

总结:绘上一张Kakfa架构思维大纲脑图(xmind)

[外链图片转存中…(img-Sj7ruWkc-1720153299085)]

其实关于Kafka,能问的问题实在是太多了,扒了几天,最终筛选出44问:基础篇17问、进阶篇15问、高级篇12问,个个直戳痛点,不知道如果你不着急看答案,又能答出几个呢?

若是对Kafka的知识还回忆不起来,不妨先看我手绘的知识总结脑图(xmind不能上传,文章里用的是图片版)进行整体架构的梳理

梳理了知识,刷完了面试,如若你还想进一步的深入学习解读kafka以及源码,那么接下来的这份《手写“kafka”》将会是个不错的选择。

  • Kafka入门

  • 为什么选择Kafka

  • Kafka的安装、管理和配置

  • Kafka的集群

  • 第一个Kafka程序

  • Kafka的生产者

  • Kafka的消费者

  • 深入理解Kafka

  • 可靠的数据传递

  • Spring和Kafka的整合

  • SpringBoot和Kafka的整合

  • Kafka实战之削峰填谷

  • 数据管道和流式处理(了解即可)

[外链图片转存中…(img-2BdvGb6C-1720153299086)]

[外链图片转存中…(img-dVhFPYOz-1720153299086)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值