万字博文教你搞懂java源码的日期和时间相关用法_could not be parsed at index 0

	}

![image-20210714224261](https://img-blog.csdnimg.cn/img_convert/09d537bb7cd894f18088ebae04499be1.png)


日期和时间格式化参数说明



yyyy:年
MM:月
dd:日
hh:1~12小时制(1-12)
HH:24小时制(0-23)
mm:分
ss:秒
S:毫秒
E:星期几
D:一年中的第几天
F:一月中的第几个星期(会把这个月总共过的天数除以7)
w:一年中的第几个星期
W:一月中的第几星期(会根据实际情况来算)
a:上下午标识
k:和HH差不多,表示一天24小时制(1-24)。
K:和hh差不多,表示一天12小时制(0-11)。
z:表示时区


#### SimpleDateFormat线程不安全原因及解决方案



##### SimpleDateFormat线程为什么是线程不安全的呢?


来看看SimpleDateFormat的源码,先看format方法:



// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);

}


问题就出在成员变量**calendar**,如果在使用SimpleDateFormat时,用static定义,那SimpleDateFormat变成了共享变量。那SimpleDateFormat中的**calendar**就可以被多个线程访问到。


SimpleDateFormat的parse方法也是线程不安全的:



public Date parse(String text, ParsePosition pos)
{

Date parsedDate;
try {
parsedDate = calb.establish(calendar).getTime();
// If the year value is ambiguous,
// then the two-digit year == the default start year
if (ambiguousYear[0]) {
if (parsedDate.before(defaultCenturyStart)) {
parsedDate = calb.addYear(100).establish(calendar).getTime();
}
}
}
// An IllegalArgumentException will be thrown by Calendar.getTime()
// if any fields are out of range, e.g., MONTH == 17.
catch (IllegalArgumentException e) {
pos.errorIndex = start;
pos.index = oldStart;
return null;
}

    return parsedDate;  

}


由源码可知,最后是调用\*\*parsedDate = calb.establish(calendar).getTime();\*\*获取返回值。方法的参数是calendar,calendar可以被多个线程访问到,存在线程不安全问题。


我们再来看看\*\*calb.establish(calendar)\*\*的源码


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


**calb.establish(calendar)**方法先后调用了**cal.clear()**和**cal.set()**,先清理值,再设值。但是这两个操作并不是原子性的,也没有线程安全机制来保证,导致多线程并发时,可能会引起cal的值出现问题了。


##### 验证SimpleDateFormat线程不安全



public class SimpleDateFormatDemoTest {

private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public static void main(String[] args) {
		//1、创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(5);
    //2、为线程池分配任务
    ThreadPoolTest threadPoolTest = new ThreadPoolTest();
    for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
    }
    //3、关闭线程池
    pool.shutdown();
}


static class  ThreadPoolTest implements Runnable{

    @Override
    public void run() {
			String dateString = simpleDateFormat.format(new Date());
			try {
				Date parseDate = simpleDateFormat.parse(dateString);
				String dateString2 = simpleDateFormat.format(parseDate);
				System.out.println(Thread.currentThread().getName()+" 线程是否安全: "+dateString.equals(dateString2));
			} catch (Exception e) {
				System.out.println(Thread.currentThread().getName()+" 格式化失败 ");
			}
    }
}

}


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


出现了两次false,说明线程是不安全的。而且还抛异常,这个就严重了。


##### 解决方案


这个是阿里巴巴 java开发手册中的规定:


![img](https://img-blog.csdnimg.cn/img_convert/0b0505050e3dd8d511c77456538e5bb8.png)


1、不要定义为static变量,使用局部变量


2、加锁:synchronized锁和Lock锁


3、使用ThreadLocal方式


4、使用DateTimeFormatter代替SimpleDateFormat(DateTimeFormatter是线程安全的,java 8+支持)


5、使用FastDateFormat 替换SimpleDateFormat(FastDateFormat 是线程安全的,Apache Commons Lang包支持,不受限于java版本)


###### 解决方案1:不要定义为static变量,使用局部变量


就是要使用SimpleDateFormat对象进行format或parse时,再定义为局部变量。就能保证线程安全。



public class SimpleDateFormatDemoTest1 {

public static void main(String[] args) {
		//1、创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(5);
    //2、为线程池分配任务
    ThreadPoolTest threadPoolTest = new ThreadPoolTest();
    for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
    }
    //3、关闭线程池
    pool.shutdown();
}


static class  ThreadPoolTest implements Runnable{

	@Override
	public void run() {
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String dateString = simpleDateFormat.format(new Date());
		try {
			Date parseDate = simpleDateFormat.parse(dateString);
			String dateString2 = simpleDateFormat.format(parseDate);
			System.out.println(Thread.currentThread().getName()+" 线程是否安全: "+dateString.equals(dateString2));
		} catch (Exception e) {
			System.out.println(Thread.currentThread().getName()+" 格式化失败 ");
		}
	}
}

}


![image-20210805936439](https://img-blog.csdnimg.cn/img_convert/90df0d7831e093ce9e260d4fd72115ed.png)


由图可知,已经保证了线程安全,**但这种方案不建议在高并发场景下使用**,因为会创建大量的SimpleDateFormat对象,影响性能。


###### 解决方案2:加锁:synchronized锁和Lock锁


**加synchronized锁**:  
 SimpleDateFormat对象还是定义为全局变量,然后需要调用SimpleDateFormat进行格式化时间时,再用synchronized保证线程安全。



public class SimpleDateFormatDemoTest2 {

private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public static void main(String[] args) {
		//1、创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(5);
    //2、为线程池分配任务
    ThreadPoolTest threadPoolTest = new ThreadPoolTest();
    for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
    }
    //3、关闭线程池
    pool.shutdown();
}

static class  ThreadPoolTest implements Runnable{

	@Override
	public void run() {
		try {
			synchronized (simpleDateFormat){
				String dateString = simpleDateFormat.format(new Date());
				Date parseDate = simpleDateFormat.parse(dateString);
				String dateString2 = simpleDateFormat.format(parseDate);
				System.out.println(Thread.currentThread().getName()+" 线程是否安全: "+dateString.equals(dateString2));
			}
		} catch (Exception e) {
			System.out.println(Thread.currentThread().getName()+" 格式化失败 ");
		}
	}
}

}


![image-2021080591591](https://img-blog.csdnimg.cn/img_convert/50010803918801ec697b6097fe320e73.png)  
 如图所示,线程是安全的。定义了全局变量SimpleDateFormat,减少了创建大量SimpleDateFormat对象的损耗。但是使用synchronized锁,  
 同一时刻只有一个线程能执行锁住的代码块,在高并发的情况下会影响性能。**但这种方案不建议在高并发场景下使用**  
 **加Lock锁**:  
 加Lock锁和synchronized锁原理是一样的,都是使用锁机制保证线程的安全。



public class SimpleDateFormatDemoTest3 {

private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
		//1、创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(5);
    //2、为线程池分配任务
    ThreadPoolTest threadPoolTest = new ThreadPoolTest();
    for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
    }
    //3、关闭线程池
    pool.shutdown();
}

static class  ThreadPoolTest implements Runnable{

	@Override
	public void run() {
		try {
			lock.lock();
				String dateString = simpleDateFormat.format(new Date());
				Date parseDate = simpleDateFormat.parse(dateString);
				String dateString2 = simpleDateFormat.format(parseDate);
				System.out.println(Thread.currentThread().getName()+" 线程是否安全: "+dateString.equals(dateString2));
		} catch (Exception e) {
			System.out.println(Thread.currentThread().getName()+" 格式化失败 ");
		}finally {
			lock.unlock();
		}
	}
}

}


![image-20210805940496](https://img-blog.csdnimg.cn/img_convert/a005b9c52f251c0a8ff7ba308ebeb372.png)  
 由结果可知,加Lock锁也能保证线程安全。要注意的是,最后一定要释放锁,代码里在**finally里增加了lock.unlock();**,保证释放锁。  
 在高并发的情况下会影响性能。**这种方案不建议在高并发场景下使用**


###### 解决方案3:使用ThreadLocal方式


使用ThreadLocal保证每一个线程有SimpleDateFormat对象副本。这样就能保证线程的安全。



public class SimpleDateFormatDemoTest4 {

private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
	@Override
	protected DateFormat initialValue() {
		return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	}
};
public static void main(String[] args) {
		//1、创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(5);
    //2、为线程池分配任务
    ThreadPoolTest threadPoolTest = new ThreadPoolTest();
    for (int i = 0; i < 10; i++) {
        pool.submit(threadPoolTest);
    }
    //3、关闭线程池
    pool.shutdown();
}

static class  ThreadPoolTest implements Runnable{

	@Override
	public void run() {
		try {
				String dateString = threadLocal.get().format(new Date());
				Date parseDate = threadLocal.get().parse(dateString);
				String dateString2 = threadLocal.get().format(parseDate);
				System.out.println(Thread.currentThread().getName()+" 线程是否安全: "+dateString.equals(dateString2));
		} catch (Exception e) {
			System.out.println(Thread.currentThread().getName()+" 格式化失败 ");
		}finally {
			//避免内存泄漏,使用完threadLocal后要调用remove方法清除数据
			threadLocal.remove();
		}
	}
}

}


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


使用ThreadLocal能保证线程安全,且效率也是挺高的。**适合高并发场景使用**。


###### 解决方案4:使用DateTimeFormatter代替SimpleDateFormat


使用DateTimeFormatter代替SimpleDateFormat(DateTimeFormatter是线程安全的,java 8+支持)  
 [DateTimeFormatter介绍](#DateTimeFormatter)



public class DateTimeFormatterDemoTest5 {
private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”);

public static void main(String[] args) {
	//1、创建线程池
	ExecutorService pool = Executors.newFixedThreadPool(5);
	//2、为线程池分配任务
	ThreadPoolTest threadPoolTest = new ThreadPoolTest();
	for (int i = 0; i < 10; i++) {
		pool.submit(threadPoolTest);
	}
	//3、关闭线程池
	pool.shutdown();
}


static class  ThreadPoolTest implements Runnable{

	@Override
	public void run() {
		try {
			String dateString = dateTimeFormatter.format(LocalDateTime.now());
			TemporalAccessor temporalAccessor = dateTimeFormatter.parse(dateString);
			String dateString2 = dateTimeFormatter.format(temporalAccessor);
			System.out.println(Thread.currentThread().getName()+" 线程是否安全: "+dateString.equals(dateString2));
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(Thread.currentThread().getName()+" 格式化失败 ");
		}
	}
}

}


![image-2021080591443373](https://img-blog.csdnimg.cn/img_convert/8dbddf627d283357f1765d17ba4a52fd.png)  
 使用DateTimeFormatter能保证线程安全,且效率也是挺高的。**适合高并发场景使用**。


###### 解决方案5:使用FastDateFormat 替换SimpleDateFormat


使用FastDateFormat 替换SimpleDateFormat(FastDateFormat 是线程安全的,Apache Commons Lang包支持,不受限于java版本)



public class FastDateFormatDemo6 {
private static FastDateFormat fastDateFormat = FastDateFormat.getInstance(“yyyy-MM-dd HH:mm:ss”);

public static void main(String[] args) {
	//1、创建线程池
	ExecutorService pool = Executors.newFixedThreadPool(5);
	//2、为线程池分配任务
	ThreadPoolTest threadPoolTest = new ThreadPoolTest();
	for (int i = 0; i < 10; i++) {
		pool.submit(threadPoolTest);
	}
	//3、关闭线程池
	pool.shutdown();
}


static class  ThreadPoolTest implements Runnable{

	@Override
	public void run() {
		try {
			String dateString = fastDateFormat.format(new Date());
			Date parseDate =  fastDateFormat.parse(dateString);
			String dateString2 = fastDateFormat.format(parseDate);
			System.out.println(Thread.currentThread().getName()+" 线程是否安全: "+dateString.equals(dateString2));
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(Thread.currentThread().getName()+" 格式化失败 ");
		}
	}
}

}


使用FastDateFormat能保证线程安全,且效率也是挺高的。**适合高并发场景使用**。


###### FastDateFormat源码分析



Apache Commons Lang 3.5



//FastDateFormat
@Override
public String format(final Date date) {
return printer.format(date);
}

@Override
public String format(final Date date) {
	final Calendar c = Calendar.getInstance(timeZone, locale);
	c.setTime(date);
	return applyRulesToString(c);
}

源码中 Calender 是在 format 方法里创建的,肯定不会出现 setTime 的线程安全问题。这样线程安全疑惑解决了。那还有性能问题要考虑?


我们来看下FastDateFormat是怎么获取的



FastDateFormat.getInstance();
FastDateFormat.getInstance(CHINESE_DATE_TIME_PATTERN);


看下对应的源码



/**
* 获得 FastDateFormat实例,使用默认格式和地区
*
* @return FastDateFormat
*/
public static FastDateFormat getInstance() {
return CACHE.getInstance();
}

/**
* 获得 FastDateFormat 实例,使用默认地区

* 支持缓存
*
* @param pattern 使用{@link java.text.SimpleDateFormat} 相同的日期格式
* @return FastDateFormat
* @throws IllegalArgumentException 日期格式问题
*/
public static FastDateFormat getInstance(final String pattern) {
return CACHE.getInstance(pattern, null, null);
}


这里有用到一个CACHE,看来用了缓存,往下看



private static final FormatCache CACHE = new FormatCache(){
@Override
protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
return new FastDateFormat(pattern, timeZone, locale);
}
};

//
abstract class FormatCache {

private final ConcurrentMap<Tuple, F> cInstanceCache = new ConcurrentHashMap<>(7);

private static final ConcurrentMap<Tuple, String> C_DATE_TIME_INSTANCE_CACHE = new ConcurrentHashMap<>(7);
...

}


![image-20210728914309](https://img-blog.csdnimg.cn/img_convert/726cfac67f45b965a81cf383137696ab.png)


在getInstance 方法中加了ConcurrentMap 做缓存,提高了性能。且我们知道ConcurrentMap 也是线程安全的。


###### 实践



/**
* 年月格式 {@link FastDateFormat}:yyyy-MM
*/
public static final FastDateFormat NORM_MONTH_FORMAT = FastDateFormat.getInstance(NORM_MONTH_PATTERN);


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



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


![image-20210728205104833](https://img-blog.csdnimg.cn/img_convert/0dcc8e2f0db4ff15a3c3349710a9a1e8.png)


![image-2021072895259113](https://img-blog.csdnimg.cn/img_convert/36412da4194421f93245070987d70b2f.png)


如图可证,是使用了ConcurrentMap 做缓存。且key值是格式,时区和locale(语境)三者都相同为相同的key。


### 问题


1、tostring()输出时,总以系统的默认时区格式输出,不友好。


2、时区不能转换


3、日期和时间的计算不简便,例如计算加减,比较两个日期差几天等。


4、格式化日期和时间的SimpleDateFormat对象是线程不安全的


5、Date对象本身也是线程不安全的



public class Date
implements java.io.Serializable, Cloneable, Comparable
{

}


## 二:Calendar


### 支持版本及以上


JDK1.1


### 介绍


#### Calendar类说明


Calendar类提供了获取或设置各种日历字段的各种方法,比Date类多了一个可以计算日期和时间的功能。


#### Calendar常用的用法



	// 获取当前时间:
	Calendar c = Calendar.getInstance();
	int y = c.get(Calendar.YEAR);
	int m = 1 + c.get(Calendar.MONTH);
	int d = c.get(Calendar.DAY_OF_MONTH);
	int w = c.get(Calendar.DAY_OF_WEEK);
	int hh = c.get(Calendar.HOUR_OF_DAY);
	int mm = c.get(Calendar.MINUTE);
	int ss = c.get(Calendar.SECOND);
	int ms = c.get(Calendar.MILLISECOND);
	System.out.println("返回的星期:"+w);
	System.out.println(y + "-" + m + "-" + d + " "  + " " + hh + ":" + mm + ":" + ss + "." + ms);

![image-202107141981024](https://img-blog.csdnimg.cn/img_convert/9bb2f5d9bfdb5429740864709bf9744d.png)


如上图所示,月份计算时,要+1;返回的星期是从周日开始计算,周日为1,1~7表示星期;


#### Calendar的跨年问题和解决方案


##### 问题


背景:在使用Calendar 的api getWeekYear()读取年份,在跨年那周的时候,程序获取的年份可能不是我们想要的,例如在2019年30号时,要返回2019,结果是返回2020,是不是有毒



// 获取当前时间:
Calendar c = Calendar.getInstance();
c.clear();
String str = “2019-12-30”;
try {
c.setTime(new SimpleDateFormat(“yyyy-MM-dd”).parse(str));
int y = c.getWeekYear();
System.out.println(y);
} catch (ParseException e) {
e.printStackTrace();
}


![image-202107148203567](https://img-blog.csdnimg.cn/img_convert/9ffa5367a01e018f765146e16de13303.png)


##### 分析原因


老规矩,从源码入手



Calendar类
-------------------------
//@since 1.7
public int getWeekYear() {
throw new UnsupportedOperationException();
}


这个源码有点奇怪,getWeekYear()方法是java 7引入的。它的实现怎么是抛出异常,但是执行时,又有结果返回。


断点跟进,通过**Calendar.getInstance()**获取的Calendar实例是**GregorianCalendar**


![image-202107148065](https://img-blog.csdnimg.cn/img_convert/79a9f056837047953dc23d36048026cc.png)



GregorianCalendar
--------------------------------------
public int getWeekYear() {
int year = get(YEAR); // implicitly calls complete()
if (internalGetEra() == BCE) {
year = 1 - year;
}

    // Fast path for the Gregorian calendar years that are never
    // affected by the Julian-Gregorian transition
    if (year > gregorianCutoverYear + 1) {
        int weekOfYear = internalGet(WEEK_OF_YEAR);
        if (internalGet(MONTH) == JANUARY) {
            if (weekOfYear >= 52) {
                --year;
            }
        } else {
            if (weekOfYear == 1) {
                ++year;
            }
        }
        return year;
    }
    ...
    }

方法内获取的年份刚开始是正常的


![image-20210714707548](https://img-blog.csdnimg.cn/img_convert/48203e94cf0f6094b6d1b9c92cef736b.png)


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


**在JDK中会把前一年末尾的几天判定为下一年的第一周,因此上面程序的结果是1**


##### 解决方案


使用Calendar类 get(Calendar.YEAR)获取年份


### 问题


1、读取月份时,要+1


2、返回的星期是从周日开始计算,周日为1,1~7表示星期


3、Calendar的跨年问题,获取年份要用c.get(Calendar.YEAR),不要用c.getWeekYear();


4、获取指定时间是一年中的第几周时,调用cl.get(Calendar.WEEK\_OF\_YEAR),要注意跨年问题,跨年的那一周,获取的值为1。离跨年最近的那周为52。


## 三:LocalDateTime


### 支持版本及以上


jdk8


### 介绍


#### LocalDateTime类说明


表示当前日期时间,相当于:yyyy-MM-ddTHH:mm:ss


#### LocalDateTime常用的用法


##### 获取当前日期和时间



	LocalDate d = LocalDate.now(); // 当前日期
	LocalTime t = LocalTime.now(); // 当前时间
	LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
	System.out.println(d); // 严格按照ISO 8601格式打印
	System.out.println(t); // 严格按照ISO 8601格式打印
	System.out.println(dt); // 严格按照ISO 8601格式打印

![image-20210714857780](https://img-blog.csdnimg.cn/img_convert/88a2ca1f2ab378e9fb78c35b6c19578c.png)


由运行结果可行,本地日期时间通过now()获取到的总是以当前默认时区返回的


##### 获取指定日期和时间



	LocalDate d2 = LocalDate.of(2021, 07, 14); // 2021-07-14, 注意07=07月
	LocalTime t2 = LocalTime.of(13, 14, 20); // 13:14:20
	LocalDateTime dt2 = LocalDateTime.of(2021, 07, 14, 13, 14, 20);
	LocalDateTime dt3 = LocalDateTime.of(d2, t2);
	System.out.println("指定日期时间:"+dt2);
	System.out.println("指定日期时间:"+dt3);

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


##### 日期时间的加减法及修改



	LocalDateTime currentTime = LocalDateTime.now(); // 当前日期和时间
	System.out.println("------------------时间的加减法及修改-----------------------");
	//3.LocalDateTime的加减法包含了LocalDate和LocalTime的所有加减,上面说过,这里就只做简单介绍
	System.out.println("3.当前时间:" + currentTime);
	System.out.println("3.当前时间加5年:" + currentTime.plusYears(5));
	System.out.println("3.当前时间加2个月:" + currentTime.plusMonths(2));
	System.out.println("3.当前时间减2天:" + currentTime.minusDays(2));
	System.out.println("3.当前时间减5个小时:" + currentTime.minusHours(5));
	System.out.println("3.当前时间加5分钟:" + currentTime.plusMinutes(5));
	System.out.println("3.当前时间加20秒:" + currentTime.plusSeconds(20));
	//还可以灵活运用比如:向后加一年,向前减一天,向后加2个小时,向前减5分钟,可以进行连写
	System.out.println("3.同时修改(向后加一年,向前减一天,向后加2个小时,向前减5分钟):" + currentTime.plusYears(1).minusDays(1).plusHours(2).minusMinutes(5));
	System.out.println("3.修改年为2025年:" + currentTime.withYear(2025));
	System.out.println("3.修改月为12月:" + currentTime.withMonth(12));
	System.out.println("3.修改日为27日:" + currentTime.withDayOfMonth(27));
	System.out.println("3.修改小时为12:" + currentTime.withHour(12));
	System.out.println("3.修改分钟为12:" + currentTime.withMinute(12));
	System.out.println("3.修改秒为12:" + currentTime.withSecond(12));

![image-20210714941902](https://img-blog.csdnimg.cn/img_convert/391472f879266d60a5d313b8a0796da9.png)


### LocalDateTime和Date相互转化


##### Date转LocalDateTime



	System.out.println("------------------方法一:分步写-----------------------");
	//实例化一个时间对象
	Date date = new Date();
	//返回表示时间轴上同一点的瞬间作为日期对象
	Instant instant = date.toInstant();
	//获取系统默认时区
	ZoneId zoneId = ZoneId.systemDefault();
	//根据时区获取带时区的日期和时间
	ZonedDateTime zonedDateTime = instant.atZone(zoneId);
	//转化为LocalDateTime
	LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
	System.out.println("方法一:原Date = " + date);
	System.out.println("方法一:转化后的LocalDateTime = " + localDateTime);

	System.out.println("------------------方法二:一步到位(推荐使用)-----------------------");
	//实例化一个时间对象
	Date todayDate = new Date();
	//Instant.ofEpochMilli(long l)使用1970-01-01T00:00:00Z的纪元中的毫秒来获取Instant的实例
	LocalDateTime ldt = Instant.ofEpochMilli(todayDate.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime();
	System.out.println("方法二:原Date = " + todayDate);
	System.out.println("方法二:转化后的LocalDateTime = " + ldt);

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


##### LocalDateTime转Date



	System.out.println("------------------方法一:分步写-----------------------");
	//获取LocalDateTime对象,当前时间
	LocalDateTime localDateTime = LocalDateTime.now();
	//获取系统默认时区
	ZoneId zoneId = ZoneId.systemDefault();
	//根据时区获取带时区的日期和时间
	ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);
	//返回表示时间轴上同一点的瞬间作为日期对象
	Instant instant = zonedDateTime.toInstant();
	//转化为Date
	Date date = Date.from(instant);
	System.out.println("方法一:原LocalDateTime = " + localDateTime);
	System.out.println("方法一:转化后的Date = " + date);

	System.out.println("------------------方法二:一步到位(推荐使用)-----------------------");
	//实例化一个LocalDateTime对象
	LocalDateTime now = LocalDateTime.now();
	//转化为date
	Date dateResult = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
	System.out.println("方法二:原LocalDateTime = " + now);
	System.out.println("方法二:转化后的Date = " + dateResult);

![image-20210714211035080](https://img-blog.csdnimg.cn/img_convert/878a923c61fa76d95853b0c45e3f1d0a.png)


### 线程安全


网上大家都在说JAVA 8提供的LocalDateTime是线程安全的,但是它是如何实现的呢


今天让我们来挖一挖



public final class LocalDateTime
implements Temporal, TemporalAdjuster, ChronoLocalDateTime, Serializable {

}


由上面的源码可知,LocalDateTime是不可变类。我们都知道一个Java并发编程规则:不可变对象永远是线程安全的。


对比下Date的源码 ,Date是可变类,所以是线程不安全的。



public class Date
implements java.io.Serializable, Cloneable, Comparable
{

}


## 四:ZonedDateTime


### 支持版本及以上


jdk8



还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!


王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。


对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!


【完整版领取方式在文末!!】


***93道网络安全面试题***


![](https://img-blog.csdnimg.cn/img_convert/6679c89ccd849f9504c48bb02882ef8d.png)








![](https://img-blog.csdnimg.cn/img_convert/07ce1a919614bde78921fb2f8ddf0c2f.png)





![](https://img-blog.csdnimg.cn/img_convert/44238619c3ba2d672b5b8dc4a529b01d.png)





内容实在太多,不一一截图了


### 黑客学习资源推荐


最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!


对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。


😝朋友们如果有需要的话,可以联系领取~

#### 1️⃣零基础入门


##### ① 学习路线


对于从来没有接触过网络安全的同学,我们帮你准备了详细的**学习成长路线图**。可以说是**最科学最系统的学习路线**,大家跟着这个大的方向学习准没问题。


![image](https://img-blog.csdnimg.cn/img_convert/acb3c4714e29498573a58a3c79c775da.gif#pic_center)


##### ② 路线对应学习视频


同时每个成长路线对应的板块都有配套的视频提供:


![image-20231025112050764](https://img-blog.csdnimg.cn/874ad4fd3dbe4f6bb3bff17885655014.png#pic_center)


#### 2️⃣视频配套工具&国内外网安书籍、文档


##### ① 工具


![](https://img-blog.csdnimg.cn/img_convert/d3f08d9a26927e48b1332a38401b3369.png#pic_center)


##### ② 视频


![image1](https://img-blog.csdnimg.cn/img_convert/f18acc028dc224b7ace77f2e260ba222.png#pic_center)


##### ③ 书籍


![image2](https://img-blog.csdnimg.cn/img_convert/769b7e13b39771b3a6e4397753dab12e.png#pic_center)

资源较为敏感,未展示全面,需要的最下面获取

![在这里插入图片描述](https://img-blog.csdnimg.cn/e4f9ac066e8c485f8407a99619f9c5b5.png#pic_center)![在这里插入图片描述](https://img-blog.csdnimg.cn/111f5462e7df433b981dc2430bb9ad39.png#pic_center)


##### ② 简历模板


![在这里插入图片描述](https://img-blog.csdnimg.cn/504b8be96bfa4dfb8befc2af49aabfa2.png#pic_center)

 **因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆**




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值