Java 编程问题:III.使用日期和时间
原文:Java Coding Problems
协议:CC BY-NC-SA 4.0
贡献者:飞龙
本文来自【ApacheCN Java 译文集】,自豪地采用谷歌翻译。
本章包括 20 个涉及日期和时间的问题。这些问题通过Date
、Calendar
、LocalDate
、LocalTime
、LocalDateTime
、ZoneDateTime
、OffsetDateTime
、OffsetTime
、Instant
等涵盖了广泛的主题(转换、格式化、加减、定义时段/持续时间、计算等)。到本章结束时,您将在确定日期和时间方面没有问题,同时符合您的应用的需要。本章介绍的基本问题将非常有助于了解日期-时间 API 的整体情况,并将像拼图中需要拼凑起来的部分一样解决涉及日期和时间的复杂挑战。
问题
使用以下问题来测试您的日期和时间编程能力。我强烈建议您在使用解决方案和下载示例程序之前,先尝试一下每个问题:
-
将字符串转换为日期和时间:编写一个程序,演示字符串和日期/时间之间的转换。
-
格式化日期和时间:**解释日期和时间的格式模式。
-
获取当前日期/时间(不含日期/时间):编写程序,提取当前日期(不含时间或日期)。
-
从
LocalDate
和LocalTime
到LocalDateTime
:编写一个程序,从LocalDate
对象和LocalTime
构建一个LocalDateTime
。它将日期和时间组合在一个LocalDateTime
对象中。 -
通过
Instant
类获取机器时间:解释并举例说明Instant
API。 -
定义使用基于日期的值的时间段(
Period
)和使用基于时间的值的时间段(Duration
):解释并举例说明Period
和Duration
API 的用法。 -
获取日期和时间单位:编写一个程序,从表示日期时间的对象中提取日期和时间单位(例如,从日期中提取年、月、分钟等)。
-
对日期时间的加减:编写一个程序,对日期时间对象加减一定的时间(如年、日、分等)(如对日期加 1 小时,对
LocalDateTime
减 2 天等)。 -
获取 UTC 和 GMT 的所有时区:编写一个程序,显示 UTC 和 GMT 的所有可用时区。
-
获取所有可用时区的本地日期时间:编写一个程序,显示所有可用时区的本地时间。68. 显示航班日期时间信息:编写程序,显示 15 小时 30 分钟的航班时刻信息。更确切地说,是从澳大利亚珀斯飞往欧洲布加勒斯特的航班。
-
将 Unix 时间戳转换为日期时间:编写将 Unix 时间戳转换为
java.util.Date
和java.time.LocalDateTime
的程序。 -
查找月份的第一天/最后一天:编写一个程序,通过 JDK8,
TemporalAdjusters
查找月份的第一天/最后一天。 -
定义/提取区域偏移:编写一个程序,展示定义和提取区域偏移的不同技术。
-
Date
与Temporal
之间的转换:编写Date
与Instant
、LocalDate
、LocalDateTime
等之间的转换程序。 -
迭代一系列日期:编写一个程序,逐日(以一天的步长)迭代一系列给定日期。
-
计算年龄:编写一个计算一个人年龄的程序。
-
一天的开始和结束:编写一个程序,返回一天的开始和结束时间。
-
两个日期之间的差异:编写一个程序,计算两个日期之间的时间量(以天为单位)。
-
实现象棋时钟:编写实现象棋时钟的程序。
以下各节介绍上述问题的解决方案。记住,通常没有一个正确的方法来解决一个特定的问题。另外,请记住,这里显示的解释仅包括解决问题所需的最有趣和最重要的细节。下载示例解决方案以查看更多详细信息,并在这个页面中试用程序。
58 将字符串转换为日期和时间
将String
转换或解析为日期和时间可以通过一组parse()
方法来完成。从日期和时间到String
的转换可以通过toString()
或format()
方法完成。
JDK8 之前
在 JDK8 之前,这个问题的典型解决方案依赖于抽象的DateFormat
类的主扩展,名为SimpleDateFormat
(这不是线程安全类)。在本书附带的代码中,有几个示例说明了如何使用此类。
从 JDK8 开始
从 JDK8 开始,SimpleDateFormat
可以替换为一个新类—DateTimeFormatter
。这是一个不可变(因此是线程安全的)类,用于打印和解析日期时间对象。这个类支持从预定义的格式化程序(表示为常量,如 ISO 本地时间2011-12-03
,是ISO_LOCAL_DATE
)到用户定义的格式化程序(依赖于一组用于编写自定义格式模式的符号)。
此外,除了Date
类之外,JDK8 还提供了几个新类,它们专门用于处理日期和时间。其中一些类显示在下面的列表中(这些类也被称为临时类,因为它们实现了Temporal
接口):
-
LocalDate
(ISO-8601 日历系统中没有时区的日期) -
LocalTime
(ISO-8601 日历系统中无时区的时间) -
LocalDateTime
(ISO-8601 日历系统中无时区的日期时间) -
ZonedDateTime
(ISO-8601 日历系统中带时区的日期时间),依此类推 -
OffsetDateTime
(在 ISO-8601 日历系统中,有 UTC/GMT 偏移的日期时间) -
OffsetTime
(在 ISO-8601 日历系统中与 UTC/GMT 有偏移的时间)
为了通过预定义的格式化程序将String
转换为LocalDate
,它应该遵循DateTimeFormatter.ISO_LOCAL_DATE
模式,例如2020-06-01
。LocalDate
提供了一种parse()
方法,可以如下使用:
// 06 is the month, 01 is the day
LocalDate localDate = LocalDate.parse("2020-06-01");
类似地,在LocalTime
的情况下,字符串应该遵循DateTimeFormatter.ISO_LOCAL_TIME
模式;例如,10:15:30
,如下面的代码片段所示:
LocalTime localTime = LocalTime.parse("12:23:44");
在LocalDateTime
的情况下,字符串应该遵循DateTimeFormatter.ISO_LOCAL_DATE_TIME
模式,例如2020-06-01T11:20:15
,如下代码片段所示:
LocalDateTime localDateTime
= LocalDateTime.parse("2020-06-01T11:20:15");
在ZonedDateTime
的情况下,字符串必须遵循DateTimeFormatter.ISO_ZONED_DATE_TIME
模式,例如2020-06-01T10:15:30+09:00[Asia/Tokyo]
,如下代码片段所示:
ZonedDateTime zonedDateTime
= ZonedDateTime.parse("2020-06-01T10:15:30+09:00[Asia/Tokyo]");
在OffsetDateTime
的情况下,字符串必须遵循DateTimeFormatter.ISO_OFFSET_DATE_TIME
模式,例如2007-12-03T10:15:30+01:00
,如下代码片段所示:
OffsetDateTime offsetDateTime
= OffsetDateTime.parse("2007-12-03T10:15:30+01:00");
最后,在OffsetTime
的情况下,字符串必须遵循DateTimeFormatter.ISO_OFFSET_TIME
模式,例如10:15:30+01:00
,如下代码片段所示:
OffsetTime offsetTime = OffsetTime.parse("10:15:30+01:00");
如果字符串不符合任何预定义的格式化程序,则是时候通过自定义格式模式使用用户定义的格式化程序了;例如,字符串01.06.2020
表示需要用户定义格式化程序的日期,如下所示:
DateTimeFormatter dateFormatter
= DateTimeFormatter.ofPattern("dd.MM.yyyy");
LocalDate localDateFormatted
= LocalDate.parse("01.06.2020", dateFormatter);
但是,像12|23|44
这样的字符串需要如下用户定义的格式化程序:
DateTimeFormatter timeFormatter
= DateTimeFormatter.ofPattern("HH|mm|ss");
LocalTime localTimeFormatted
= LocalTime.parse("12|23|44", timeFormatter);
像01.06.2020, 11:20:15
这样的字符串需要一个用户定义的格式化程序,如下所示:
DateTimeFormatter dateTimeFormatter
= DateTimeFormatter.ofPattern("dd.MM.yyyy, HH:mm:ss");
LocalDateTime localDateTimeFormatted
= LocalDateTime.parse("01.06.2020, 11:20:15", dateTimeFormatter);
像01.06.2020, 11:20:15+09:00 [Asia/Tokyo]
这样的字符串需要一个用户定义的格式化程序,如下所示:
DateTimeFormatter zonedDateTimeFormatter
= DateTimeFormatter.ofPattern("dd.MM.yyyy, HH:mm:ssXXXXX '['VV']'");
ZonedDateTime zonedDateTimeFormatted
= ZonedDateTime.parse("01.06.2020, 11:20:15+09:00 [Asia/Tokyo]",
zonedDateTimeFormatter);
像2007.12.03, 10:15:30, +01:00
这样的字符串需要一个用户定义的格式化程序,如下所示:
DateTimeFormatter offsetDateTimeFormatter
= DateTimeFormatter.ofPattern("yyyy.MM.dd, HH:mm:ss, XXXXX");
OffsetDateTime offsetDateTimeFormatted
= OffsetDateTime.parse("2007.12.03, 10:15:30, +01:00",
offsetDateTimeFormatter);
最后,像10 15 30 +01:00
这样的字符串需要一个用户定义的格式化程序,如下所示:
DateTimeFormatter offsetTimeFormatter
= DateTimeFormatter.ofPattern("HH mm ss XXXXX");
OffsetTime offsetTimeFormatted
= OffsetTime.parse("10 15 30 +01:00", offsetTimeFormatter);
前面示例中的每个ofPattern()
方法也支持Locale
。
从LocalDate
、LocalDateTime
或ZonedDateTime
到String
的转换至少可以通过两种方式完成:
- 依赖于
LocalDate
、LocalDateTime
或ZonedDateTime.toString()
方法(自动或显式)。请注意,依赖于toString()
将始终通过相应的预定义格式化程序打印日期:
// 2020-06-01 results in ISO_LOCAL_DATE, 2020-06-01
String localDateAsString = localDate.toString();
// 01.06.2020 results in ISO_LOCAL_DATE, 2020-06-01
String localDateAsString = localDateFormatted.toString();
// 2020-06-01T11:20:15 results
// in ISO_LOCAL_DATE_TIME, 2020-06-01T11:20:15
String localDateTimeAsString = localDateTime.toString();
// 01.06.2020, 11:20:15 results in
// ISO_LOCAL_DATE_TIME, 2020-06-01T11:20:15
String localDateTimeAsString
= localDateTimeFormatted.toString();
// 2020-06-01T10:15:30+09:00[Asia/Tokyo]
// results in ISO_ZONED_DATE_TIME,
// 2020-06-01T11:20:15+09:00[Asia/Tokyo]
String zonedDateTimeAsString = zonedDateTime.toString();
// 01.06.2020, 11:20:15+09:00 [Asia/Tokyo]
// results in ISO_ZONED_DATE_TIME,
// 2020-06-01T11:20:15+09:00[Asia/Tokyo]
String zonedDateTimeAsString
= zonedDateTimeFormatted.toString();
- 依靠
DateTimeFormatter.format()
方法。请注意,依赖于DateTimeFormatter.format()
将始终使用指定的格式化程序打印日期/时间(默认情况下,时区将为null
),如下所示:
// 01.06.2020
String localDateAsFormattedString
= dateFormatter.format(localDateFormatted);
// 01.06.2020, 11:20:15
String localDateTimeAsFormattedString
= dateTimeFormatter.format(localDateTimeFormatted);
// 01.06.2020, 11:20:15+09:00 [Asia/Tokyo]
String zonedDateTimeAsFormattedString
= zonedDateTimeFormatted.format(zonedDateTimeFormatter);
在讨论中添加一个明确的时区可以如下所示:
DateTimeFormatter zonedDateTimeFormatter
= DateTimeFormatter.ofPattern("dd.MM.yyyy, HH:mm:ssXXXXX '['VV']'")
.withZone(ZoneId.of("Europe/Paris"));
ZonedDateTime zonedDateTimeFormatted
= ZonedDateTime.parse("01.06.2020, 11:20:15+09:00 [Asia/Tokyo]",
zonedDateTimeFormatter);
这次,字符串表示欧洲/巴黎时区中的日期/时间:
// 01.06.2020, 04:20:15+02:00 [Europe/Paris]
String zonedDateTimeAsFormattedString
= zonedDateTimeFormatted.format(zonedDateTimeFormatter);
59 格式化日期和时间
前面的问题包含一些通过SimpleDateFormat.format()
和DateTimeFormatter.format()
格式化日期和时间的风格。为了定义格式模式,开发人员必须了解格式模式语法。换句话说,开发人员必须知道 Java 日期时间 API 使用的一组符号,以便识别有效的格式模式。
大多数符号与SimpleDateFormat
(JDK8 之前)和DateTimeFormatter
(从 JDK8 开始)通用。下表列出了 JDK 文档中提供的最常见符号的完整列表:
字母 | 含义 | 演示 | 示例 |
---|---|---|---|
y |
年 | 年 | 1994; 94 |
M |
月 | 数字/文本 | 7; 07; Jul; July; J |
W |
每月的一周 | 数字 | 4 |
E |
星期几 | 文本 | Tue; Tuesday; T |
d |
日期 | 数字 | 15 |
H |
小时 | 数字 | 22 |
m |
分钟 | 数字 | 34 |
s |
秒 | 数字 | 55 |
S |
秒的分数 | 数字 | 345 |
z |
时区名称 | 时区名称 | Pacific Standard Time; PST |
Z |
时区偏移 | 时区偏移 | -0800 |
V |
时区 ID(JDK8) | 时区 ID | America/Los_Angeles; Z; -08:30 |
下表提供了一些格式模式示例:
模式 | 示例 |
---|---|
yyyy-MM-dd |
2019-02-24 |
MM-dd-yyyy |
02-24-2019 |
MMM-dd-yyyy |
Feb-24-2019 |
dd-MM-yy |
24-02-19 |
dd.MM.yyyy |
24.02.2019 |
yyyy-MM-dd HH:mm:ss |
2019-02-24 11:26:26 |
yyyy-MM-dd HH:mm:ssSSS |
2019-02-24 11:36:32743 |
yyyy-MM-dd HH:mm:ssZ |
2019-02-24 11:40:35+0200 |
yyyy-MM-dd HH:mm:ss z |
2019-02-24 11:45:03 EET |
E MMM yyyy HH:mm:ss.SSSZ |
Sun Feb 2019 11:46:32.393+0200 |
yyyy-MM-dd HH:MM:ss VV (JDK8) |
2019-02-24 11:45:41 Europe/Athens |
在 JDK8 之前,可以通过SimpleDateFormat
应用格式模式:
// yyyy-MM-dd
Date date = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
String stringDate = formatter.format(date);
从 JDK8 开始,可以通过DateTimeFormatter
应用格式模式:
- 对于
LocalDate
(ISO-8601 日历系统中没有时区的日期):
// yyyy-MM-dd
LocalDate localDate = LocalDate.now();
DateTimeFormatter formatterLocalDate
= DateTimeFormatter.ofPattern("yyyy-MM-dd");
String stringLD = formatterLocalDate.format(localDate);
// or shortly
String stringLD = LocalDate.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
- 对于
LocalTime
(ISO-8601 日历系统中没有时区的时间):
// HH:mm:ss
LocalTime localTime = LocalTime.now();
DateTimeFormatter formatterLocalTime
= DateTimeFormatter.ofPattern("HH:mm:ss");
String stringLT
= formatterLocalTime.format(localTime);
// or shortly
String stringLT = LocalTime.now()
.format(DateTimeFormatter.ofPattern("HH:mm:ss"));
- 对于
LocalDateTime
(ISO-8601 日历系统中没有时区的日期时间):
// yyyy-MM-dd HH:mm:ss
LocalDateTime localDateTime = LocalDateTime.now();
DateTimeFormatter formatterLocalDateTime
= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String stringLDT
= formatterLocalDateTime.format(localDateTime);
// or shortly
String stringLDT = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
- 对于
ZonedDateTime
(ISO-8601 日历系统中带时区的日期时间):
// E MMM yyyy HH:mm:ss.SSSZ
ZonedDateTime zonedDateTime = ZonedDateTime.now();
DateTimeFormatter formatterZonedDateTime
= DateTimeFormatter.ofPattern("E MMM yyyy HH:mm:ss.SSSZ");
String stringZDT
= formatterZonedDateTime.format(zonedDateTime);
// or shortly
String stringZDT = ZonedDateTime.now()
.format(DateTimeFormatter
.ofPattern("E MMM yyyy HH:mm:ss.SSSZ"));
- 对于
OffsetDateTime
(在 ISO-8601 日历系统中,与 UTC/GMT 有偏移的日期时间):
// E MMM yyyy HH:mm:ss.SSSZ
OffsetDateTime offsetDateTime = OffsetDateTime.now();
DateTimeFormatter formatterOffsetDateTime
= DateTimeFormatter.ofPattern("E MMM yyyy HH:mm:ss.SSSZ");
String odt1 = formatterOffsetDateTime.format(offsetDateTime);
// or shortly
String odt2 = OffsetDateTime.now()
.format(DateTimeFormatter
.ofPattern("E MMM yyyy HH:mm:ss.SSSZ"));
- 对于
OffsetTime
(在 ISO-8601 日历系统中与 UTC/GMT 有偏移的时间):
// HH:mm:ss,Z
OffsetTime offsetTime = OffsetTime.now();
DateTimeFormatter formatterOffsetTime
= DateTimeFormatter.ofPattern("HH:mm:ss,Z");
String ot1 = formatterOffsetTime.format(offsetTime);
// or shortly
String ot2 = OffsetTime.now()
.format(DateTimeFormatter.ofPattern("HH:mm:ss,Z"));
60 获取没有时间/日期的当前日期/时间
在 JDK8 之前,解决方案必须集中在java.util.Date
类上。绑定到本书的代码包含此解决方案。
从 JDK8 开始,日期和时间可以通过专用类LocalDate
和LocalTime
从java.time
包中获得:
// 2019-02-24
LocalDate onlyDate = LocalDate.now();
// 12:53:28.812637300
LocalTime onlyTime = LocalTime.now();
61 LocalDate
和LocalTime
中的LocalDateTime
LocalDateTime
类公开了一系列of()
方法,这些方法可用于获取LocalDateTime
的不同类型的实例。例如,从年、月、日、时、分、秒或纳秒获得的LocalDateTime
类如下所示:
LocalDateTime ldt = LocalDateTime.of(2020, 4, 1, 12, 33, 21, 675);
因此,前面的代码将日期和时间组合为of()
方法的参数。为了将日期和时间组合为对象,解决方案可以利用以下of()
方法:
public static LocalDateTime of(LocalDate date, LocalTime time)
这导致LocalDate
和LocalTime
,如下所示:
LocalDate localDate = LocalDate.now(); // 2019-Feb-24
LocalTime localTime = LocalTime.now(); // 02:08:10 PM
它们可以组合在一个对象LocalDateTime
中,如下所示:
LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
格式化LocalDateTime
显示日期和时间如下:
// 2019-Feb-24 02:08:10 PM
String localDateTimeAsString = localDateTime
.format(DateTimeFormatter.ofPattern("yyyy-MMM-dd hh:mm:ss a"));
62 通过Instant
类的机器时间
JDK8 附带了一个新类,名为java.time.Instant
。主要地,Instant
类表示时间线上的一个瞬时点,从 1970 年 1 月 1 日(纪元)的第一秒开始,在 UTC 时区,分辨率为纳秒。
Java8Instant
类在概念上类似于java.util.Date
。两者都代表 UTC 时间线上的一个时刻。当Instant
的分辨率高达纳秒时,java.util.Date
的分辨率为毫秒。
这个类对于生成机器时间的时间戳非常方便。为了获得这样的时间戳,只需调用如下的now()
方法:
// 2019-02-24T15:05:21.781049600Z
Instant timestamp = Instant.now();
使用以下代码段可以获得类似的输出:
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
或者,使用以下代码段:
Clock clock = Clock.systemUTC();
调用Instant.toString()
产生一个输出,该输出遵循 ISO-8601 标准来表示日期和时间。
将字符串转换为Instant
遵循 ISO-8601 标准表示日期和时间的字符串可以通过Instant.parse()
方法轻松转换为Instant
,如下例所示:
// 2019-02-24T14:31:33.197021300Z
Instant timestampFromString =
Instant.parse("2019-02-24T14:31:33.197021300Z");
向Instant
添加/减去时间
对于添加时间,Instant
有一套方法。例如,向当前时间戳添加 2 小时可以如下完成:
Instant twoHourLater = Instant.now().plus(2, ChronoUnit.HOURS);
在减去时间方面,例如 10 分钟,请使用以下代码段:
Instant tenMinutesEarlier = Instant.now()
.minus(10, ChronoUnit.MINUTES);
除plus()
方法外,Instant
还包含plusNanos()
、plusMillis()
、plusSeconds()
。此外,除了minus()
方法外,Instant
还包含minusNanos()
、minusMillis()
、minusSeconds()
。
比较Instant
对象
比较两个Instant
对象可以通过Instant.isAfter()
和Instant.isBefore()
方法来完成。例如,让我们看看以下两个Instant
对象:
Instant timestamp1 = Instant.now();
Instant timestamp2 = timestamp1.plusSeconds(10);
检查timestamp1
是否在timestamp2
之后:
boolean isAfter = timestamp1.isAfter(timestamp2); // false
检查timestamp1
是否在timestamp2
之前:
boolean isBefore = timestamp1.isBefore(timestamp2); // true
两个Instant
对象之间的时差可以通过Instant.until()
方法计算:
// 10 seconds
long difference = timestamp1.until(timestamp2, ChronoUnit.SECONDS);
在Instant
和LocalDateTime
、ZonedDateTime
和OffsetDateTime
之间转换
这些常见的转换可以在以下示例中完成:
- 在
Instant
和LocalDateTime
之间转换-因为LocalDateTime
不知道时区,所以使用零偏移 UTC+0:
// 2019-02-24T15:27:13.990103700
LocalDateTime ldt = LocalDateTime.ofInstant(
Instant.now(), ZoneOffset.UTC);
// 2019-02-24T17:27:14.013105Z
Instant instantLDT = LocalDateTime.now().toInstant(ZoneOffset.UTC);
- 在
Instant
和ZonedDateTime
之间转换—将Instant
UTC+0 转换为巴黎ZonedDateTime
UTC+1:
// 2019-02-24T16:34:36.138393100+01:00[Europe/Paris]
ZonedDateTime zdt = Instant.now().atZone(ZoneId.of("Europe/Paris"));
// 2019-02-24T16:34:36.150393800Z
Instant instantZDT = LocalDateTime.now()
.atZone(ZoneId.of("Europe/Paris")).toInstant();
- 在
Instant
和OffsetDateTime
之间转换-指定 2 小时的偏移量:
// 2019-02-24T17:34:36.151393900+02:00
OffsetDateTime odt = Instant.now().atOffset(ZoneOffset.of("+02:00"));
// 2019-02-24T15:34:36.153394Z
Instant instantODT = LocalDateTime.now()
.atOffset(ZoneOffset.of("+02:00")).toInstant();
63 使用基于日期的值定义时段,使用基于时间的值定义持续时间
JDK8 附带了两个新类,分别命名为java.time.Period
和java.time.Duration
。让我们在下一节中详细了解它们。
使用基于日期的值的时间段
Period
类意味着使用基于日期的值(年、月、周和天)来表示时间量。这段时间可以用不同的方法获得。例如,120 天的周期可以如下获得:
Period fromDays = Period.ofDays(120); // P120D
在ofDays()
方法旁边,Period
类还有ofMonths()
、ofWeeks()
和ofYears()
。
或者,通过of()
方法可以得到 2000 年 11 个月 24 天的期限,如下所示:
Period periodFromUnits = Period.of(2000, 11, 24); // P2000Y11M24D
Period
也可以从LocalDate
中得到:
LocalDate localDate = LocalDate.now();
Period periodFromLocalDate = Period.of(localDate.getYear(),
localDate.getMonthValue(), localDate.getDayOfMonth());
最后,可以从遵循 ISO-8601 周期格式PnYnMnD
和PnW
的String
对象获得Period
。例如,P2019Y2M25D
字符串表示 2019 年、2 个月和 25 天:
Period periodFromString = Period.parse("P2019Y2M25D");
调用Period.toString()
将返回时间段,同时也遵循 ISO-8601 时间段格式,PnYnMnD
和PnW
(例如P120D
、P2000Y11M24D
)。
但是,当Period
被用来表示两个日期之间的一段时间(例如LocalDate
时,Period
的真实力量就显现出来了。2018 年 3 月 12 日至 2019 年 7 月 20 日期间可表示为:
LocalDate startLocalDate = LocalDate.of(2018, 3, 12);
LocalDate endLocalDate = LocalDate.of(2019, 7, 20);
Period periodBetween = Period.between(startLocalDate, endLocalDate);
年、月、日的时间量可以通过Period.getYears()
、Period.getMonths()
、Period.getDays()
获得。例如,以下辅助方法使用这些方法将时间量输出为字符串:
public static String periodToYMD(Period period) {
StringBuilder sb = new StringBuilder();
sb.append(period.getYears())
.append("y:")
.append(period.getMonths())
.append("m:")
.append(period.getDays())
.append("d");
return sb.toString();
}
我们将此方法称为periodBetween
(差值为 1 年 4 个月 8 天):
periodToYMD(periodBetween); // 1y:4m:8d
当确定某个日期是否早于另一个日期时,Period
类也很有用。有一个标志方法,名为isNegative()
。有一个A
周期和一个B
周期,如果B
在A
之前,应用Period.between(A, B)
的结果可以是负的,如果A
在B
之前,应用isNegative()
的结果可以是正的,如果B
在A
之前,false
在A
之前,则isNegative()
返回true B
,如我们的例子所示(基本上,如果年、月或日为负数,此方法返回false
):
// returns false, since 12 March 2018 is earlier than 20 July 2019
periodBetween.isNegative();
最后,Period
可以通过加上或减去一段时间来修改。方法有plusYears()
、plusMonths()
、plusDays()
、minusYears()
、minusMonths()
、minusDays()
等。例如,在periodBetween
上加 1 年可以如下操作:
Period periodBetweenPlus1Year = periodBetween.plusYears(1L);
添加两个Period
类可以通过Period.plus()
方法完成,如下所示:
Period p1 = Period.ofDays(5);
Period p2 = Period.ofDays(20);
Period p1p2 = p1.plus(p2); // P25D
使用基于时间的值的持续时间
Duration
类意味着使用基于时间的值(小时、分钟、秒或纳秒)来表示时间量。这种持续时间可以通过不同的方式获得。例如,可以如下获得 10 小时的持续时间:
Duration fromHours = Duration.ofHours(10); // PT10H
在ofHours()
方法旁边,Duration
类还有ofDays()
、ofMillis()
、ofMinutes()
、ofSeconds()
和ofNanos()
。
或者,可以通过of()
方法获得 3 分钟的持续时间,如下所示:
Duration fromMinutes = Duration.of(3, ChronoUnit.MINUTES); // PT3M
Duration
也可以从LocalDateTime
中得到:
LocalDateTime localDateTime
= LocalDateTime.of(2018, 3, 12, 4, 14, 20, 670);
// PT14M
Duration fromLocalDateTime
= Duration.ofMinutes(localDateTime.getMinute());
也可从LocalTime
中获得:
LocalTime localTime = LocalTime.of(4, 14, 20, 670);
// PT0.00000067S
Duration fromLocalTime = Duration.ofNanos(localTime.getNano());
最后,可以从遵循 ISO-8601 持续时间格式PnDTnHnMn.nS
的String
对象获得Duration
,其中天被认为正好是 24 小时。例如,P2DT3H4M
字符串有 2 天 3 小时 4 分钟:
Duration durationFromString = Duration.parse("P2DT3H4M");
调用Duration.toString()
将返回符合 ISO-8601 持续时间格式的持续时间PnDTnHnMn.nS
(例如,PT10H
、PT3M
或PT51H4M
)。
但是,与Period
的情况一样,当Duration
用于表示两次之间的时间段(例如,Instant
时,揭示了它的真实功率。从 2015 年 11 月 3 日 12:11:30 到 2016 年 12 月 6 日 15:17:10 之间的持续时间可以表示为两个Instant
类之间的差异,如下所示:
Instant startInstant = Instant.parse("2015-11-03T12:11:30.00Z");
Instant endInstant = Instant.parse("2016-12-06T15:17:10.00Z");
// PT10059H5M40S
Duration durationBetweenInstant
= Duration.between(startInstant, endInstant);
以秒为单位,可通过Duration.getSeconds()
方法获得该差值:
durationBetweenInstant.getSeconds(); // 36212740 seconds
或者,从 2018 年 3 月 12 日 04:14:20.000000670 到 2019 年 7 月 20 日 06:10:10.000000720 之间的持续时间可以表示为两个LocalDateTime
对象之间的差异,如下所示:
LocalDateTime startLocalDateTime
= LocalDateTime.of(2018, 3, 12, 4, 14, 20, 670);
LocalDateTime endLocalDateTime
= LocalDateTime.of(2019, 7, 20, 6, 10, 10, 720);
// PT11881H55M50.00000005S, or 42774950 seconds
Duration durationBetweenLDT
= Duration.between(startLocalDateTime, endLocalDateTime);
最后,04:14:20.000000670 和 06:10:10.000000720 之间的持续时间可以表示为两个LocalTime
对象之间的差异,如下所示:
LocalTime startLocalTime = LocalTime.of(4, 14, 20, 670);
LocalTime endLocalTime = LocalTime.of(6, 10, 10, 720);
// PT1H55M50.00000005S, or 6950 seconds
Duration durationBetweenLT
= Duration.between(startLocalTime, endLocalTime);
在前面的例子中,Duration
通过Duration.getSeconds()
方法以秒表示,这是Duration
类中的秒数。然而,Duration
类包含一组方法,这些方法专用于通过toDays()
以天为单位、通过toHours()
以小时为单位、通过toMinutes()
以分钟为单位、通过toMillis()
以毫秒为单位、通过toNanos()
以纳秒为单位来表达Duration
。
从一个时间单位转换到另一个时间单位可能会产生残余。例如,从秒转换为分钟可能导致秒的剩余(例如,65 秒是 1 分钟,5 秒是剩余)。残差可以通过以下一组方法获得:天残差通过toDaysPart()
,小时残差通过toHoursPart()
,分钟残差通过toMinutesPart()
等等。
假设差异应该显示为天:小时:分:秒:纳秒(例如,9d:2h:15m:20s:230n
)。将toFoo()
和toFooPart()
方法的力结合在一个辅助方法中将产生以下代码:
public static String durationToDHMSN(Duration duration) {
StringBuilder sb = new StringBuilder();
sb.append(duration.toDays())
.append("d:")
.append(duration.toHoursPart())
.append("h:")
.append(duration.toMinutesPart())
.append("m:")
.append(duration.toSecondsPart())
.append("s:")
.append(duration.toNanosPart())
.append("n");
return sb.toString();
}
让我们调用这个方法durationBetweenLDT
(差别是 495 天 1 小时 55 分 50 秒 50 纳秒):
// 495d:1h:55m:50s:50n
durationToDHMSN(durationBetweenLDT);
与Period
类相同,Duration
类有一个名为isNegative()
的标志方法。当确定某个特定时间是否早于另一个时间时,此方法很有用。有持续时间A
和持续时间B
,如果B
在A
之前,应用Duration.between(A, B)
的结果可以是负的,如果A
在B
之前,应用Duration.between(A, B)
的结果可以是正的,进一步逻辑,isNegative()
如果B
在A
之前,则返回true
,如果A
在B
之前,则返回false
,如以下情况:
durationBetweenLT.isNegative(); // false
最后,Duration
可以通过增加或减少持续时间来修改。有plusDays()
、plusHours()
、plusMinutes()
、plusMillis()
、plusNanos()
、minusDays()
、minusHours()
、minusMinutes()
、minusMillis()
和minusNanos()
等方法来执行此操作。例如,向durationBetweenLT
添加 5 小时可以如下所示:
Duration durationBetweenPlus5Hours = durationBetweenLT.plusHours(5);
添加两个Duration
类可以通过Duration.plus()
方法完成,如下所示:
Duration d1 = Duration.ofMinutes(20);
Duration d2 = Duration.ofHours(2);
Duration d1d2 = d1.plus(d2);
System.out.println(d1 + "+" + d2 + "=" + d1d2); // PT2H20M
64 获取日期和时间单位
对于Date
对象,解决方案可能依赖于Calendar
实例。绑定到本书的代码包含此解决方案。
对于 JDK8 类,Java 提供了专用的getFoo()
方法和get(TemporalField field)
方法。例如,假设下面的LocalDateTime
对象:
LocalDateTime ldt = LocalDateTime.now();
依靠getFoo()
方法,我们得到如下代码:
int year = ldt.getYear();
int month = ldt.getMonthValue();
int day = ldt.getDayOfMonth();
int hour = ldt.getHour();
int minute = ldt.getMinute();
int second = ldt.getSecond();
int nano = ldt.getNano();
或者,依赖于get(TemporalField field)
结果如下:
int yearLDT = ldt.get(ChronoField.YEAR);
int monthLDT = ldt.get(ChronoField.MONTH_OF_YEAR);
int dayLDT = ldt.get(ChronoField.DAY_OF_MONTH);
int hourLDT = ldt.get(ChronoField.HOUR_OF_DAY);
int minuteLDT = ldt.get(ChronoField.MINUTE_OF_HOUR);
int secondLDT = ldt.get(ChronoField.SECOND_OF_MINUTE);
int nanoLDT = ldt.get(ChronoField.NANO_OF_SECOND);
请注意,月份是从 1 开始计算的,即 1 月。
例如,2019-02-25T12:58:13.109389100
的LocalDateTime
对象可以被切割成日期时间单位,结果如下:
Year: 2019 Month: 2 Day: 25 Hour: 12 Minute: 58 Second: 13 Nano: 109389100
通过一点直觉和文档,很容易将此示例改编为LocalDate
、LocalTime
、ZonedDateTime
和其他示例。
65 日期时间的加减
这个问题的解决方案依赖于专用于处理日期和时间的 Java API。让我们在下一节中看看它们。
使用Date
对于Date
对象,解决方案可能依赖于Calendar
实例。绑定到本书的代码包含此解决方案。
使用LocalDateTime
跳转到 JDK8,重点是LocalDate
、LocalTime
、LocalDateTime
、Instant
等等。新的 Java 日期时间 API 提供了专门用于加减时间量的方法。LocalDate
、LocalTime
、LocalDateTime
、ZonedDateTime
、OffsetDateTime
、Instant
、Period
、Duration
以及许多其他方法,如plusFoo()
和minusFoo()
,其中Foo
可以用单位替换时间(例如,plusYears()
、plusMinutes()
、minusHours()
、minusSeconds()
等等)。
假设如下LocalDateTime
:
// 2019-02-25T14:55:06.651155500
LocalDateTime ldt = LocalDateTime.now();
加 10 分钟和调用LocalDateTime.plusMinutes(long minutes)
一样简单,减 10 分钟和调用LocalDateTime.minusMinutes(long minutes)
一样简单:
LocalDateTime ldtAfterAddingMinutes = ldt.plusMinutes(10);
LocalDateTime ldtAfterSubtractingMinutes = ldt.minusMinutes(10);
输出将显示以下日期:
After adding 10 minutes: 2019-02-25T15:05:06.651155500
After subtracting 10 minutes: 2019-02-25T14:45:06.651155500
除了每个时间单位专用的方法外,这些类还支持plus/minus(TemporalAmount amountToAdd)
和plus/minus(long amountToAdd, TemporalUnit unit)
。
现在,让我们关注Instant
类。除了plus/minusSeconds()
、plus/minusMillis()
、plus/minusNanos()
之外,Instant
类还提供了plus/minus(TemporalAmount amountToAdd)
方法。
为了举例说明这个方法,我们假设如下Instant
:
// 2019-02-25T12:55:06.654155700Z
Instant timestamp = Instant.now();
现在,让我们加减 5 个小时:
Instant timestampAfterAddingHours
= timestamp.plus(5, ChronoUnit.HOURS);
Instant timestampAfterSubtractingHours
= timestamp.minus(5, ChronoUnit.HOURS);
输出将显示以下Instant
:
After adding 5 hours: 2019-02-25T17:55:06.654155700Z
After subtracting 5 hours: 2019-02-25T07:55:06.654155700Z
66 使用 UTC 和 GMT 获取所有时区
UTC 和 GMT 被认为是处理日期和时间的标准参考。今天,UTC 是首选的方法,但是 UTC 和 GMT 在大多数情况下应该返回相同的结果。
为了获得 UTC 和 GMT 的所有时区,解决方案应该关注 JDK8 前后的实现。所以,让我们从 JDK8 之前有用的解决方案开始。
JDK8 之前
解决方案需要提取可用的时区 ID(非洲/巴马科、欧洲/贝尔格莱德等)。此外,每个时区 ID 都应该用来创建一个TimeZone
对象。最后,解决方案需要提取特定于每个时区的偏移量,并考虑到夏令时。绑定到本书的代码包含此解决方案。
从 JDK8 开始
新的 Java 日期时间 API 为解决这个问题提供了新的工具。
在第一步,可用的时区 id 可以通过ZoneId
类获得,如下所示:
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
在第二步,每个时区 ID 都应该用来创建一个ZoneId
实例。这可以通过ZoneId.of(String zoneId)
方法实现:
ZoneId zoneid = ZoneId.of(current_zone_Id);
在第三步,每个ZoneId
可用于获得特定于所识别区域的时间。这意味着需要一个“实验室老鼠”参考日期时间。此参考日期时间(无时区,LocalDateTime.now()
)通过LocalDateTime.atZone()
与给定时区(ZoneId
)组合,以获得ZoneDateTime
(可识别时区的日期时间):
LocalDateTime now = LocalDateTime.now();
ZonedDateTime zdt = now.atZone(ZoneId.of(zone_id_instance));
atZone()
方法尽可能地匹配日期时间,同时考虑时区规则,例如夏令时。
在第四步,代码可以利用ZonedDateTime
来提取 UTC 偏移量(例如,对于欧洲/布加勒斯特,UTC 偏移量为+02:00
):
String utcOffset = zdt.getOffset().getId().replace("Z", "+00:00");
getId()
方法返回规范化区域偏移 ID,+00:00
偏移作为Z
字符返回;因此代码需要快速将Z
替换为+00:00
,以便与其他偏移对齐,这些偏移遵循+hh:mm
或+hh:mm:ss
格式。
现在,让我们将这些步骤合并到一个辅助方法中:
public static List<String> fetchTimeZones(OffsetType type) {
List<String> timezones = new ArrayList<>();
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
LocalDateTime now = LocalDateTime.now();
zoneIds.forEach((zoneId) -> {
timezones.add("(" + type + now.atZone(ZoneId.of(zoneId))
.getOffset().getId().replace("Z", "+00:00") + ") " + zoneId);
});
return timezones;
}
假设此方法存在于DateTimes
类中,则获得以下代码:
List<String> timezones
= DateTimes.fetchTimeZones(DateTimes.OffsetType.GMT);
Collections.sort(timezones); // optional sort
timezones.forEach(System.out::println);
此外,还显示了一个输出快照,如下所示:
(GMT+00:00) Africa/Abidjan
(GMT+00:00) Africa/Accra
(GMT+00:00) Africa/Bamako
...
(GMT+11:00) Australia/Tasmania
(GMT+11:00) Australia/Victoria
...
67 获取所有可用时区中的本地日期时间
可通过以下步骤获得此问题的解决方案:
- 获取本地日期和时间。
- 获取可用时区。
- 在 JDK8 之前,使用
SimpleDateFormat
和setTimeZone()
方法。 - 从 JDK8 开始,使用
ZonedDateTime
。
JDK8 之前
在 JDK8 之前,获取当前本地日期时间的快速解决方案是调用Date
空构造器。此外,还可以使用Date
在所有可用的时区中显示,这些时区可以通过TimeZone
类获得。绑定到本书的代码包含此解决方案。
从 JDK8 开始
从 JDK8 开始,获取默认时区中当前本地日期时间的一个方便解决方案是调用ZonedDateTime.now()
方法:
ZonedDateTime zlt = ZonedDateTime.now();
所以,这是默认时区中的当前日期。此外,该日期应显示在通过ZoneId
类获得的所有可用时区中:
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
最后,代码可以循环zoneIds
,对于每个区域 ID,可以调用ZonedDateTime.withZoneSameInstant(ZoneId zone)
方法。此方法返回具有不同时区的此日期时间的副本,并保留以下瞬间:
public static List<String> localTimeToAllTimeZones() {
List<String> result = new ArrayList<>();
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("yyyy-MMM-dd'T'HH:mm:ss a Z");
ZonedDateTime zlt = ZonedDateTime.now();
zoneIds.forEach((zoneId) -> {
result.add(zlt.format(formatter) + " in " + zoneId + " is "
+ zlt.withZoneSameInstant(ZoneId.of(zoneId))
.format(formatter));
});
return result;
}
此方法的输出快照可以如下所示:
2019-Feb-26T14:26:30 PM +0200 in Africa/Nairobi
is 2019-Feb-26T15:26:30 PM +0300
2019-Feb-26T14:26:30 PM +0200 in America/Marigot
is 2019-Feb-26T08:26:30 AM -0400
...
2019-Feb-26T14:26:30 PM +0200 in Pacific/Samoa
is 2019-Feb-26T01:26:30 AM -1100
68 显示航班的日期时间信息
本节提供的解决方案将显示有关从澳大利亚珀斯到欧洲布加勒斯特的 15 小时 30 分钟航班的以下信息:
- UTC 出发和到达日期时间
- 离开珀斯的日期时间和到达布加勒斯特的日期时间
- 离开和到达布加勒斯特的日期时间
假设从珀斯出发的参考日期时间为 2019 年 2 月 26 日 16:00(或下午 4:00):
LocalDateTime ldt = LocalDateTime.of(
2019, Month.FEBRUARY, 26, 16, 00);
首先,让我们将这个日期时间与澳大利亚/珀斯(+08:00)的时区结合起来。这将产生一个特定于澳大利亚/珀斯的ZonedDateTime
对象(这是出发时珀斯的时钟日期和时间):
// 04:00 PM, Feb 26, 2019 +0800 Australia/Perth
ZonedDateTime auPerthDepart
= ldt.atZone(ZoneId.of("Australia/Perth"));
此外,让我们在ZonedDateTime
中加上 15 小时 30 分钟。结果ZonedDateTime
表示珀斯的日期时间(这是抵达布加勒斯特时珀斯的时钟日期和时间):
// 07:30 AM, Feb 27, 2019 +0800 Australia/Perth
ZonedDateTime auPerthArrive
= auPerthDepart.plusHours(15).plusMinutes(30);
现在,让我们计算一下布加勒斯特的日期时间和珀斯的出发日期时间。基本上,以下代码表示从布加勒斯特时区的珀斯时区出发的日期和时间:
// 10:00 AM, Feb 26, 2019 +0200 Europe/Bucharest
ZonedDateTime euBucharestDepart
= auPerthDepart.withZoneSameInstant(ZoneId.of("Europe/Bucharest"));
最后,让我们计算一下到达布加勒斯特的日期和时间。以下代码表示布加勒斯特时区珀斯时区的到达日期时间:
// 01:30 AM, Feb 27, 2019 +0200 Europe/Bucharest
ZonedDateTime euBucharestArrive
= auPerthArrive.withZoneSameInstant(ZoneId.of("Europe/Bucharest"));
如下图所示,从珀斯出发的 UTC 时间是上午 8:00,而到达布加勒斯特的 UTC 时间是晚上 11:30:
这些时间可以很容易地提取为OffsetDateTime
,如下所示:
// 08:00 AM, Feb 26, 2019
OffsetDateTime utcAtDepart = auPerthDepart.withZoneSameInstant(
ZoneId.of("UTC")).toOffsetDateTime();
// 11:30 PM, Feb 26, 2019
OffsetDateTime utcAtArrive = auPerthArrive.withZoneSameInstant(
ZoneId.of("UTC")).toOffsetDateTime();
69 将 Unix 时间戳转换为日期时间
对于这个解决方案,假设下面的 Unix 时间戳是 1573768800。此时间戳等效于以下内容:
11/14/2019 @ 10:00pm (UTC)
- ISO-8601 中的
2019-11-14T22:00:00+00:00
-
Thu, 14 Nov 2019 22:00:00 +0000
,RFC 822、1036、1123、2822 -
Thursday, 14-Nov-19 22:00:00 UTC
,RFC 2822 -
2019-11-14T22:00:00+00:00
在 RFC 3339 中
为了将 Unix 时间戳转换为日期时间,必须知道 Unix 时间戳的分辨率以秒为单位,而java.util.Date
需要毫秒。因此,从 Unix 时间戳获取Date
对象的解决方案需要将 Unix 时间戳乘以 1000,从秒转换为毫秒,如下两个示例所示:
long unixTimestamp = 1573768800;
// Fri Nov 15 00:00:00 EET 2019 - in the default time zone
Date date = new Date(unixTimestamp * 1000L);
// Fri Nov 15 00:00:00 EET 2019 - in the default time zone
Date date = new Date(TimeUnit.MILLISECONDS
.convert(unixTimestamp, TimeUnit.SECONDS));
从 JDK8 开始,Date
类使用from(Instant instant)
方法。此外,Instant
类附带了ofEpochSecond(long epochSecond)
方法,该方法使用1970-01-01T00:00:00Z
的纪元的给定秒数返回Instant
的实例:
// 2019-11-14T22:00:00Z in UTC
Instant instant = Instant.ofEpochSecond(unixTimestamp);
// Fri Nov 15 00:00:00 EET 2019 - in the default time zone
Date date = Date.from(instant);
上一示例中获得的瞬间可用于创建LocalDateTime
或ZonedDateTime
,如下所示:
// 2019-11-15T06:00
LocalDateTime date = LocalDateTime
.ofInstant(instant, ZoneId.of("Australia/Perth"));
// 2019-Nov-15 00:00:00 +0200 Europe/Bucharest
ZonedDateTime date = ZonedDateTime
.ofInstant(instant, ZoneId.of("Europe/Bucharest"));
70 查找每月的第一天/最后一天
这个问题的正确解决将依赖于 JDK8、Temporal
和TemporalAdjuster
接口。
Temporal
接口位于日期和时间的表示后面。换句话说,表示日期和/或时间的类实现了这个接口。例如,以下类只是实现此接口的几个类:
-
LocalDate
(ISO-8601 日历系统中没有时区的日期) -
LocalTime
(ISO-8601 日历系统中无时区的时间) -
LocalDateTime
(ISO-8601 日历系统中无时区的日期时间) -
ZonedDateTime
(ISO-8601 日历系统中带时区的日期时间),依此类推 -
OffsetDateTime
(在 ISO-8601 日历系统中,从 UTC/格林威治时间偏移的日期时间) -
HijrahDate
(希吉拉历法系统中的日期)
TemporalAdjuster
类是一个函数式接口,它定义了可用于调整Temporal
对象的策略。除了可以定义自定义策略外,TemporalAdjuster
类还提供了几个预定义的策略,如下所示(文档包含了整个列表,非常令人印象深刻):
-
firstDayOfMonth()
(返回当月第一天) -
lastDayOfMonth()
(返回当月最后一天) -
firstDayOfNextMonth()
(次月 1 日返回) -
firstDayOfNextYear()
(次年第一天返回)
注意,前面列表中的前两个调整器正是这个问题所需要的。
考虑一个修正-LocalDate
:
LocalDate date = LocalDate.of(2019, Month.FEBRUARY, 27);
让我们看看二月的第一天/最后一天是什么时候:
// 2019-02-01
LocalDate firstDayOfFeb
= date.with(TemporalAdjusters.firstDayOfMonth());
// 2019-02-28
LocalDate lastDayOfFeb
= date.with(TemporalAdjusters.lastDayOfMonth());
看起来依赖预定义的策略非常简单。但是,假设问题要求您查找 2019 年 2 月 27 日之后的 21 天,也就是 2019 年 3 月 20 日。对于这个问题,没有预定义的策略,因此需要自定义策略。此问题的解决方案可以依赖 Lambda 表达式,如以下辅助方法中所示:
public static LocalDate getDayAfterDays(
LocalDate startDate, int days) {
Period period = Period.ofDays(days);
TemporalAdjuster ta = p -> p.plus(period);
LocalDate endDate = startDate.with(ta);
return endDate;
}
如果此方法存在于名为DateTimes
的类中,则以下调用将返回预期结果:
// 2019-03-20
LocalDate datePlus21Days = DateTimes.getDayAfterDays(date, 21);
遵循相同的技术,但依赖于static
工厂方法ofDateAdjuster()
,下面的代码片段定义了一个静态调整器,返回下一个星期六的日期:
static TemporalAdjuster NEXT_SATURDAY
= TemporalAdjusters.ofDateAdjuster(today -> {
DayOfWeek dayOfWeek = today.getDayOfWeek();
if (dayOfWeek == DayOfWeek.SATURDAY) {
return today;
}
if (dayOfWeek == DayOfWeek.SUNDAY) {
return today.plusDays(6);
}
return today.plusDays(6 - dayOfWeek.getValue());
});
我们将此方法称为 2019 年 2 月 27 日(下一个星期六是 2019 年 3 月 2 日):
// 2019-03-02
LocalDate nextSaturday = date.with(NEXT_SATURDAY);
最后,这个函数式接口定义了一个名为adjustInto()
的abstract
方法。在自定义实现中,可以通过向该方法传递一个Temporal
对象来覆盖该方法,如下所示:
public class NextSaturdayAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
DayOfWeek dayOfWeek = DayOfWeek
.of(temporal.get(ChronoField.DAY_OF_WEEK));
if (dayOfWeek == DayOfWeek.SATURDAY) {
return temporal;
}
if (dayOfWeek == DayOfWeek.SUNDAY) {
return temporal.plus(6, ChronoUnit.DAYS);
}
return temporal.plus(6 - dayOfWeek.getValue(), ChronoUnit.DAYS);
}
}
下面是用法示例:
NextSaturdayAdjuster nsa = new NextSaturdayAdjuster();
// 2019-03-02
LocalDate nextSaturday = date.with(nsa);
71 定义/提取区域偏移
通过区域偏移,我们了解需要从 GMT/UTC 时间中添加/减去的时间量,以便获得全球特定区域(例如,澳大利亚珀斯)的日期时间。通常,区域偏移以固定的小时和分钟数打印:+02:00
、-08:30
、+0400
、UTC+01:00
,依此类推。
因此,简而言之,时区偏移量是指时区与 GMT/UTC 之间的时间差。
JDK8 之前
在 JDK8 之前,可以通过java.util.TimeZone
定义一个时区,有了这个时区,代码就可以通过TimeZone.getRawOffset()
方法得到时区偏移量(原始部分来源于这个方法不考虑夏令时)。绑定到本书的代码包含此解决方案。
从 JDK8 开始
从 JDK8 开始,有两个类负责处理时区表示。首先是java.time.ZoneId
,表示欧洲雅典等时区;其次是java.time.ZoneOffset
(扩展ZoneId
),表示指定时区的固定时间(偏移量),以 GMT/UTC 表示。
新的 Java 日期时间 API 默认处理夏令时;因此,使用夏令时的夏-冬周期区域将有两个ZoneOffset
类。
UTC 区域偏移量可以很容易地获得,如下所示(这是+00:00
,在 Java 中用Z
字符表示):
// Z
ZoneOffset zoneOffsetUTC = ZoneOffset.UTC;
系统默认时区也可以通过ZoneOffset
类获取:
// Europe/Athens
ZoneId defaultZoneId = ZoneOffset.systemDefault();
为了使用夏令时进行分区偏移,代码需要将日期时间与其关联。例如,关联一个LocalDateTime
类(也可以使用Instant
),如下所示:
// by default it deals with the Daylight Saving Times
LocalDateTime ldt = LocalDateTime.of(2019, 6, 15, 0, 0);
ZoneId zoneId = ZoneId.of("Europe/Bucharest");
// +03:00
ZoneOffset zoneOffset = zoneId.getRules().getOffset(ldt);
区域偏移量也可以从字符串中获得。例如,以下代码获得+02:00
的分区偏移:
ZoneOffset zoneOffsetFromString = ZoneOffset.of("+02:00");
这是一种非常方便的方法,可以将区域偏移快速添加到支持区域偏移的Temporal
对象。例如,使用它将区域偏移添加到OffsetTime
和OffsetDateTime
(用于在数据库中存储日期或通过电线发送的方便方法):
OffsetTime offsetTime = OffsetTime.now(zoneOffsetFromString);
OffsetDateTime offsetDateTime
= OffsetDateTime.now(zoneOffsetFromString);
我们问题的另一个解决方法是依赖于从小时、分钟和秒来定义ZoneOffset
。ZoneOffset
的一个助手方法专门用于:
// +08:30 (this was obtained from 8 hours and 30 minutes)
ZoneOffset zoneOffsetFromHoursMinutes
= ZoneOffset.ofHoursMinutes(8, 30);
在ZoneOffset.ofHoursMinutes()
旁边有ZoneOffset.ofHours()
、ofHoursMinutesSeconds()
和ofTotalSeconds()
。
最后,每个支持区域偏移的Temporal
对象都提供了一个方便的getOffset()
方法。例如,下面的代码从前面的offsetDateTime
对象获取区域偏移:
// +02:00
ZoneOffset zoneOffsetFromOdt = offsetDateTime.getOffset();
72 在日期和时间之间转换
这里给出的解决方案将涵盖以下Temporal
类—Instant
、LocalDate
、LocalDateTime
、ZonedDateTime
、OffsetDateTime
、LocalTime
和OffsetTime
。
Date
-Instant
为了从Date
转换到Instant
,可采用Date.toInstant()
方法求解。可通过Date.from(Instant instant)
方法实现反转:
-
Date
到Instant
可以这样完成:
Date date = new Date();
// e.g., 2019-02-27T12:02:49.369Z, UTC
Instant instantFromDate = date.toInstant();
-
Instant
到Date
可以这样完成:
Instant instant = Instant.now();
// Wed Feb 27 14:02:49 EET 2019, default system time zone
Date dateFromInstant = Date.from(instant);
请记住,Date
不是时区感知的,但它显示在系统默认时区中(例如,通过toString()
)。Instant
是 UTC 时区。
让我们快速地将这些代码片段包装在两个工具方法中,它们在一个工具类DateCo
上一篇: ZonedDateTime
下一篇: 快速设置时区
推荐阅读
-
Java 8新特性探究(十三)JavaFX 8新特性以及开发2048游戏-JavaFX历史## 跟java在服务器端和web端成绩相比,桌面一直是java的软肋,于是Sun公司在2008年推出JavaFX,弥补桌面软件的缺陷,请看下图JavaFX一路走过来的改进 从上图看出,一开始推出时候,开发者需使用一种名为JavaFX Script的静态的、声明式的编程语言来开发JavaFX应用程序。因为JavaFX Script将会被编译为Java bytecode,程序员可以使用Java代码代替。 JavaFX 2.0之后的版本摒弃了JavaFX Script语言,而作为一个Java API来使用。因此使用JavaFX平台实现的应用程序将直接通过标准Java代码来实现。 JavaFX 2.0 包含非常丰富的 UI 控件、图形和多媒体特性用于简化可视化应用的开发,WebView可直接在应用中嵌入网页;另外 2.0 版本允许使用 FXML 进行 UI 定义,这是一个脚本化基于 XML 的标识语言。 从JDK 7u6开始,JavaFx就与JDK捆绑在一起了,JavaFX团队称,下一个版本将是8.0,目前所有的工作都已经围绕8.0库进行。这是因为JavaFX将捆绑在Java 8中,因此该团队决定跳过几个版本号,迎头赶上Java 8。 ##JavaFx8的新特性 ## ###全新现代主题:Modena 新的Modena主题来替换原来的Caspian主题。不过在Application的start方法中,可以通过setUserAgentStylesheet(STYLESHEET_CASPIAN)来继续使用Caspian主题。 参考http://fxexperience.com/2013/03/modena-theme-update/ ###JavaFX 3D 在JavaFX8中提供了3D图像处理API,包括Shape3D (Box, Cylinder, MeshView, Sphere子类),SubScene, Material, PickResult, LightBase (AmbientLight 和PointLight子类),SceneAntialiasing等。Camera类也得到了更新。从JavaDoc中可以找到更多信息。 ###富文本 强化了富文本的支持 ###TreeTableView ###日期控件DatePicker 增加日期控件 ###用于 CSS 结构的公共 API
-
HHKB BT 蓝牙键盘:装逼必备利器?" 使用HHKB BT 蓝牙键盘的心得体会 在互联网的世界里,越来越多的人都追求个性化和独特性,无论是穿着打扮,还是使用的电子产品,都能看到人们的创意和想法。最近我在网上看到了一款备受推崇的机械键盘——HHKB BT 蓝牙键盘。 HHKB BT 蓝牙键盘是一款由日本知名键盘制造商 Filco 生产的产品,被誉为程序员的信仰。其最大的特点就是采用了静电容轴,按键更加灵敏且耐用。此外,HHKB BT 蓝牙键盘还具有非常紧凑的设计,仅60个键位,非常适合长时间工作或编程的人士使用。 然而,当我收到 HHKB BT 蓝牙键盘并开始使用时,我发现了一些问题。首先,数字键整体向右移动了一位,这对于习惯使用数字键的人来说是一种困扰。其次,HHKB BT 蓝牙键盘的价格较高,并且只能通过代购购买,售后服务也不完善。 尽管存在这些问题,我还是对 HHKB BT 蓝牙键盘产生了浓厚的兴趣。我尝试将数字键重新排列,使其更适合我的使用习惯,同时我也开始学习如何更有效地使用 HHKB BT 蓝牙键盘进行编程。 总的来说,HHKB BT 蓝牙键盘是一款非常专业且高效的机械键盘,如果你是一位需要长时间使用键盘的开发者或者程序员,那么它绝对值得你考虑。当然,高昂的价格和售后服务的问题也需要你权衡一下是否值得投资。
-
【2022新手指南】Java编程进阶之路 - 六、技术架构篇 ### MySQL索引底层解析与优化实战 - 你会讲解MySQL索引的数据结构吗?性能调优技巧知多少? - Redis深度揭秘:你知道多少?从基础到哨兵、主从复制全梳理 - Redis持久化及哨兵模式详解,还有集群搭建和Leader选举黑箱打开 - Zookeeper是个啥?特性和应用场景大公开 - ZooKeeper集群搭建攻略及 Leader选举、读写一致性、共享锁实现细节 - 探究ZooKeeper中的Leader选举机制及其在分布式环境中的作用 - Zab协议深入剖析:原理、功能与在Zookeeper中的核心地位 - RabbitMQ全方位解读:工作模式、消费限流、可靠投递与配置策略 - 设计者视角:RabbitMQ过期时间、死信队列与延时队列实践指南 - RocketMQ特性和应用场景揭示:理解其精髓与差异化优势 - Kafka详细介绍:特性及广泛应用于实时数据处理的场景解析 - ElasticSearch实力揭秘:特性概述与作为搜索引擎的广泛应用 - MongoDB认知升级:非关系型数据库的优势阐述,安装与使用实战教学 - BIO/NIO/AIO网络模型对比:掌握它们的区别与在网络编程中的实际应用 - Netty带你飞:理解其超快速度背后的秘密,包括线程模型分析 - 网络通信黑科技:Netty编解码原理与常用编解码器的应用,Protostuff实战演示 - 解密Netty粘包与拆包现象,怎样有效应对这一常见问题 - 自定义Netty心跳检测机制,轻松调整检测间隔时间的艺术 - Dubbo轻骑兵介绍:核心特性概览,服务降级实战与其实现益处 - Dubbo三大神器解读:本地存根与本地伪装的实战运用与优势呈现 ----------------------- 七、结语与回顾
-
SSM三大框架基础面试题-一、Spring篇 什么是Spring框架? Spring是一种轻量级框架,提高开发人员的开发效率以及系统的可维护性。 我们一般说的Spring框架就是Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和DI的基础,AOP组件用来实现面向切面编程。 Spring的6个特征: 核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL。 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。 数据访问:事务,DAO支持,JDBC,ORM,编组XML。 Web支持:Spring MVC和Spring WebFlux Web框架。 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。 语言:Kotlin,Groovy,动态语言。 列举一些重要的Spring模块? Spring Core:核心,可以说Spring其他所有的功能都依赖于该类库。主要提供IOC和DI功能。 Spring Aspects:该模块为与AspectJ的集成提供支持。 Spring AOP:提供面向切面的编程实现。 Spring JDBC:Java数据库连接。 Spring JMS:Java消息服务。 Spring ORM:用于支持Hibernate等ORM工具。 Spring Web:为创建Web应用程序提供支持。 Spring Test:提供了对JUnit和TestNG测试的支持。 谈谈自己对于Spring IOC和AOP的理解 IOC(Inversion Of Controll,控制反转)是一种设计思想: 在程序中手动创建对象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,并非Spring特有。IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。 将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个Service类可能由几百甚至上千个类作为它的底层,假如我们需要实例化这个Service,可能要每次都搞清楚这个Service所有底层类的构造函数,这可能会把人逼疯。如果利用IOC的话,你只需要配置好,然后在需要的地方引用就行了,大大增加了项目的可维护性且降低了开发难度。 Spring中的bean的作用域有哪些? 1.singleton:该bean实例为单例 2.prototype:每次请求都会创建一个新的bean实例(多例)。 3.request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。 4.session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。 5.global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。 Spring中的单例bean的线程安全问题了解吗? 概念用于理解:大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。 有两种常见的解决方案(用于回答的点): 1.在bean对象中尽量避免定义可变的成员变量(不太现实)。 2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal(线程本地化对象)中(推荐的一种方式)。 ThreadLocal解决多线程变量共享问题(参考博客):https://segmentfault.com/a/1190000009236777 Spring中Bean的生命周期: 1.Bean容器找到配置文件中Spring Bean的定义。 2.Bean容器利用Java Reflection API创建一个Bean的实例。 3.如果涉及到一些属性值,利用set方法设置一些属性值。 4.如果Bean实现了BeanNameAware接口,调用setBeanName方法,传入Bean的名字。 5.如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader方法,传入ClassLoader对象的实例。 6.如果Bean实现了BeanFactoryAware接口,调用setBeanClassFacotory方法,传入ClassLoader对象的实例。 7.与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。 8.如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执postProcessBeforeInitialization方法。 9.如果Bean实现了InitializingBean接口,执行afeterPropertiesSet方法。 10.如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。 11.如果有和加载这个Bean的Spring容器相关的BeanPostProcess对象,执行postProcessAfterInitialization方法。 12.当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy方法。 13.当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。 Spring框架中用到了哪些设计模式? 1.工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。 2.代理设计模式:Spring AOP功能的实现。 3.单例设计模式:Spring中的bean默认都是单例的。 4.模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。 5.包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 6.观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。 7.适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。 还有很多。。。。。。。 @Component和@Bean的区别是什么 1.作用对象不同。@Component注解作用于类,而@Bean注解作用于方法。 2.@Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径)。@Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。 3.@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。 @Configuration public class AppConfig { @Bean public TransferService transferService { return new TransferServiceImpl; } } <beans> <bean id="transferService" class="com.kk.TransferServiceImpl"/> </beans> @Bean public OneService getService(status) { case (status) { when 1: return new serviceImpl1; when 2: return new serviceImpl2; when 3: return new serviceImpl3; } } 将一个类声明为Spring的bean的注解有哪些? 声明bean的注解: @Component 组件,没有明确的角色 @Service 在业务逻辑层使用(service层) @Repository 在数据访问层使用(dao层) @Controller 在展现层使用,控制器的声明 注入bean的注解: @Autowired:由Spring提供 @Inject:由JSR-330提供 @Resource:由JSR-250提供 *扩:JSR 是 java 规范标准 Spring事务管理的方式有几种? 1.编程式事务:在代码中硬编码(不推荐使用)。 2.声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。 Spring事务中的隔离级别有哪几种? 在TransactionDefinition接口中定义了五个表示隔离级别的常量:ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,Mysql默认采用的REPEATABLE_READ隔离级别;Oracle默认采用的READ_COMMITTED隔离级别。ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 Spring事务中有哪几种事务传播行为? 在TransactionDefinition接口中定义了八个表示事务传播行为的常量。 支持当前事务的情况:PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。 不支持当前事务的情况:PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。 其他情况:PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。 二、SpringMVC篇 什么是Spring MVC ?简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。 Spring MVC的工作原理了解嘛? image.png Springmvc的优点: (1)可以支持各种视图技术,而不仅仅局限于JSP; (2)与Spring框架集成(如IoC容器、AOP等); (3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。 (4) 支持各种请求资源的映射策略。 Spring MVC的主要组件? (1)前端控制器 DispatcherServlet(不需要程序员开发) 作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。 (2)处理器映射器HandlerMapping(不需要程序员开发) 作用:根据请求的URL来查找Handler (3)处理器适配器HandlerAdapter 注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。 (4)处理器Handler(需要程序员开发) (5)视图解析器 ViewResolver(不需要程序员开发) 作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view) (6)视图View(需要程序员开发jsp) View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等) springMVC和struts2的区别有哪些? (1)springmvc的入口是一个servlet即前端控制器(DispatchServlet),而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。 (2)springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。 (3)Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。 SpringMVC怎么样设定重定向和转发的? (1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4" (2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com" SpringMvc怎么和AJAX相互调用的? 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 : (1)加入Jackson.jar (2)在配置文件中配置json的映射 (3)在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。 如何解决POST请求中文乱码问题,GET的又如何处理呢? (1)解决post请求乱码问题: 在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8; <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> (2)get请求中文参数出现乱码解决方法有两个: ①修改tomcat配置文件添加编码与工程编码一致,如下: <ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/> ②另外一种方法对参数进行重新编码: String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8") ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。 Spring MVC的异常处理 ? 统一异常处理: Spring MVC处理异常有3种方式: (1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver; (2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器; (3)使用@ExceptionHandler注解实现异常处理; 统一异常处理的博客:https://blog.csdn.net/ctwy291314/article/details/81983103 SpringMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决? 是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写成员变量。(此题目类似于上面Spring 中 第5题 有两种解决方案) SpringMVC常用的注解有哪些? @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。 @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。 @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。 SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代? 一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。 如果在拦截请求中,我想拦截get方式提交的方法,怎么配置? 可以在@RequestMapping注解里面加上method=RequestMethod.GET。 怎样在方法里面得到Request,或者Session? 直接在方法的形参中声明request,SpringMVC就自动把request对象传入。 如果想在拦截的方法里面得到从前台传入的参数,怎么得到? 直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。 如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象? 直接在方法中声明这个对象,SpringMVC就自动会把属性赋值到这个对象里面。 SpringMVC中函数的返回值是什么? 返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的。 SpringMVC用什么对象从后台向前台传递数据的? 通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以拿到数据。 怎么样把ModelMap里面的数据放入Session里面? 可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。 SpringMvc里面拦截器是怎么写的: 有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可: <!-- 配置SpringMvc的拦截器 --> <mvc:interceptors> <!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 --> <bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean> <!-- 只针对部分请求拦截 --> <mvc:interceptor> <mvc:mapping path="/modelMap.do" /> <bean class="com.zwp.action.MyHandlerInterceptorAdapter" /> </mvc:interceptor> </mvc:interceptors> 注解原理: 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池 三、Mybatis篇 什么是MyBatis? MyBatis是一个可以自定义SQL、存储过程和高级映射的持久层框架。 讲下MyBatis的缓存 MyBatis的缓存分为一级缓存和二级缓存,一级缓存放在session里面,默认就有, 二级缓存放在它的命名空间里,默认是不打开的,使用二级缓存属性类需要实现Serializable序列化接口, 可在它的映射文件中配置<cache/> Mybatis是如何进行分页的?分页插件的原理是什么? 1)Mybatis使用RowBounds对象进行分页,也可以直接编写sql实现分页,也可以使用Mybatis的分页插件。 2)分页插件的原理:实现Mybatis提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql。 举例:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10 简述Mybatis的插件运行原理,以及如何编写一个插件? 1)Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、 Executor这4种接口的插件,Mybatis通过动态代理, 为需要拦截的接口生成代理对象以实现接口方法拦截功能, 每当执行这4种接口对象的方法时,就会进入拦截方法, 具体就是InvocationHandler的invoke方法,当然, 只会拦截那些你指定需要拦截的方法。 2)实现Mybatis的Interceptor接口并复写intercept方法, 然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可, 记住,别忘了在配置文件中配置你编写的插件。 Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不? 1)Mybatis动态sql可以让我们在Xml映射文件内, 以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能。 2)Mybatis提供了9种动态sql标签:trim|where|set|foreach|if|choose|when|otherwise|bind。 3)其执行原理为,使用OGNL从sql参数对象中计算表达式的值, 根据表达式的值动态拼接sql,以此来完成动态sql的功能。 #{}和${}的区别是什么? 1)#{}是预编译处理,${}是字符串替换。 2)Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值(有效的防止SQL注入); 3)Mybatis在处理${}时,就是把${}替换成变量的值。 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里? Hibernate属于全自动ORM映射工具, 使用Hibernate查询关联对象或者关联集合对象时, 可以根据对象关系模型直接获取,所以它是全自动的。 而Mybatis在查询关联对象或关联集合对象时, 需要手动编写sql来完成,所以,称之为半自动ORM映射工具。 Mybatis是否支持延迟加载?如果支持,它的实现原理是什么? 1)Mybatis仅支持association关联对象和collection关联集合对象的延迟加载, association指的就是一对一,collection指的就是一对多查询。 在Mybatis配置文件中, 可以配置是否启用延迟加载lazyLoadingEnabled=true|false。 2)它的原理是,使用CGLIB创建目标对象的代理对象, 当调用目标方法时,进入拦截器方法, 比如调用a.getB.getName, 拦截器invoke方法发现a.getB是null值, 那么就会单独发送事先保存好的查询关联B对象的sql, 把B查询上来,然后调用a.setB(b), 于是a的对象b属性就有值了, 接着完成a.getB.getName方法的调用。 这就是延迟加载的基本原理。 MyBatis与Hibernate有哪些不同? 1)Mybatis和hibernate不同,它不完全是一个ORM框架, 因为MyBatis需要程序员自己编写Sql语句, 不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句, 并将java对象和sql语句映射生成最终执行的sql, 最后将sql执行的结果再映射生成java对象。 2)Mybatis学习门槛低,简单易学,程序员直接编写原生态sql, 可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发, 例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁, 一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性, 如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。 3)Hibernate对象/关系映射能力强,数据库无关性好, 对于关系模型要求高的软件(例如需求固定的定制化软件) 如果用hibernate开发可以节省很多代码,提高效率。 但是Hibernate的缺点是学习门槛高,要精通门槛更高, 而且怎么设计O/R映射,在性能和对象模型之间如何权衡, 以及怎样用好Hibernate需要具有很强的经验和能力才行。 总之,按照用户的需求在有限的资源环境下只要能做出维护性、 扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。 MyBatis的好处是什么? 1)MyBatis把sql语句从Java源程序中独立出来,放在单独的XML文件中编写, 给程序的维护带来了很大便利。 2)MyBatis封装了底层JDBC API的调用细节,并能自动将结果集转换成Java Bean对象, 大大简化了Java数据库编程的重复工作。 3)因为MyBatis需要程序员自己去编写sql语句, 程序员可以结合数据库自身的特点灵活控制sql语句, 因此能够实现比Hibernate等全自动orm框架更高的查询效率,能够完成复杂查询。 简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系? Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。 在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象, 其每个子元素会被解析为ParameterMapping对象。 <resultMap>标签会被解析为ResultMap对象, 其每个子元素会被解析为ResultMapping对象。 每一个<select>、<insert>、<update>、<delete> 标签均会被解析为MappedStatement对象, 标签内的sql会被解析为BoundSql对象。 什么是MyBatis的接口绑定,有什么好处? 接口映射就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置. 接口绑定有几种实现方式,分别是怎么实现的? 接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加 上@Select@Update等注解里面包含Sql语句来绑定, 另外一种就是通过xml里面写SQL来绑定,在这种情况下, 要指定xml映射文件里面的namespace必须为接口的全路径名. 什么情况下用注解绑定,什么情况下用xml绑定? 当Sql语句比较简单时候,用注解绑定;当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多 MyBatis实现一对一有几种方式?具体怎么操作的? 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成; 嵌套查询是先查一个表,根据这个表里面的结果的外键id, 去再另外一个表里面查询数据,也是通过association配置, 但另外一个表的查询通过select属性配置。 Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别? 能,Mybatis不仅可以执行一对一、一对多的关联查询, 还可以执行多对一,多对多的关联查询,多对一查询, 其实就是一对一查询,只需要把selectOne修改为selectList即可; 多对多查询,其实就是一对多查询,只需要把selectOne修改为selectList即可。 关联对象查询,有两种实现方式,一种是单独发送一个sql去查询关联对象, 赋给主对象,然后返回主对象。另一种是使用嵌套查询,嵌套查询的含义为使用join查询, 一部分列是A对象的属性值,另外一部分列是关联对象B的属性值, 好处是只发一个sql查询,就可以把主对象和其关联对象查出来。 MyBatis里面的动态Sql是怎么设定的?用什么语法? MyBatis里面的动态Sql一般是通过if节点来实现,通过OGNL语法来实现, 但是如果要写的完整,必须配合where,trim节点,where节点是判断包含节点有 内容就插入where,否则不插入,trim节点是用来判断如果动态语句是以and 或or 开始,那么会自动把这个and或者or取掉。 Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式? 第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。 第二种是使用sql列的别名功能,将列别名书写为对象属性名, 比如T_NAME AS NAME,对象属性名一般是name,小写, 但是列名不区分大小写,Mybatis会忽略列名大小写,
-
异步编程RxJava-介绍-前言 前段时间写了一篇对协程的一些理解,里面提到了不管是协程还是callback,本质上其实提供的是一种异步无阻塞的编程模式;并且介绍了java中对异步无阻赛这种编程模式的支持,主要提到了Future和CompletableFuture;之后有同学在下面留言提到了RxJava,刚好最近在看微服务设计这本书,里面提到了响应式扩展(Reactive extensions,Rx),而RxJava是Rx在JVM上的实现,所有打算对RxJava进一步了解。 RxJava简介 RxJava的官网地址:https://github.com/ReactiveX/RxJava, 其中对RxJava进行了一句话描述:RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM. 大意就是:一个在Java VM上使用可观测的序列来组成异步的、基于事件的程序的库。 更详细的说明在Netflix技术博客的一篇文章中描述了RxJava的主要特点: 1.易于并发从而更好的利用服务器的能力。 2.易于有条件的异步执行。 3.一种更好的方式来避免回调地狱。 4.一种响应式方法。 与CompletableFuture对比 之前提到CompletableFuture真正的实现了异步的编程模式,一个比较常见的使用场景: CompletableFuture<Integer> future = CompletableFuture.supplyAsync(耗时函数); Future<Integer> f = future.whenComplete((v, e) -> { System.out.println(v); System.out.println(e); }); System.out.println("other..."); 下面用一个简单的例子来看一下RxJava是如何实现异步的编程模式: Observable<Long> observable = Observable.just(1, 2) .subscribeOn(Schedulers.io).map(new Func1<Integer, Long> { @Override public Long call(Integer t) { try { Thread.sleep(1000); //耗时的操作 } catch (InterruptedException e) { e.printStackTrace; } return (long) (t * 2); } }); observable.subscribe(new Subscriber<Long> { @Override public void onCompleted { System.out.println("onCompleted"); } @Override public void onError(Throwable e) { System.out.println("error" + e); } @Override public void onNext(Long result) { System.out.println("result = " + result); } }); System.out.println("other..."); Func1中以异步的方式执行了一个耗时的操作,Subscriber(观察者)被订阅到Observable(被观察者)中,当耗时操作执行完会回调Subscriber中的onNext方法。 其中的异步方式是在subscribeOn(Schedulers.io)中指定的,Schedulers.io可以理解为每次执行耗时操作都启动一个新的线程。 结构上其实和CompletableFuture很像,都是异步的执行一个耗时的操作,然后在有结果的时候主动告诉我结果。那我们还需要RxJava干嘛,不知道你有没有注意,上面的例子中其实提供2条数据流[1,2],并且处理完任何一个都会主动告诉我,当然这只是它其中的一项功能,RxJava还有很多好用的功能,在下面的内容会进行介绍。 异步观察者模式 上面这段代码有没有发现特别像设计模式中的:观察者模式;首先提供一个被观察者Observable,然后把观察者Subscriber添加到了被观察者列表中; RxJava中一共提供了四种角色:Observable、Observer、Subscriber、Subjects Observables和Subjects是两个被观察者,Observers和Subscribers是观察者; 当然我们也可以查看一下源码,看一下jdk中的Observer和RxJava的Observer jdk中的Observer: public interface Observer { void update(Observable o, Object arg); } RxJava的Observer: public interface Observer<T> { void onCompleted; void onError(Throwable e); void onNext(T t); } 同时可以发现Subscriber是implements Observer的: public abstract class Subscriber<T> implements Observer<T>, Subscription 可以发现RxJava中在Observer中引入了2个新的方法:onCompleted和onError onCompleted:即通知观察者Observable没有更多的数据,事件队列完结 onError:在事件处理过程中出异常时,onError会被触发,同时队列自动终止,不允许再有事件发出。 正是因为RxJava提供了同步和异步两种方式进行事件的处理,个人觉得异步的方式更能体现RxJava的价值,所以这里给他命名为异步观察者模式。 好了,下面正式介绍RxJava的那些灵活的操作符,这里仅仅是简单的介绍和简单的实例,具体用在什么场景下,会在以后的文章中介绍 Maven引入
-
Java 使用定时任务 - 前言:Java 开发过程中经常会遇到使用定时任务的情况,如在某个活动结束时自动生成获奖者名单、导出 excel 等。常见的有以下四种方式:Timer、ScheduledExecutorService、SpringTask、Quartz。 实现 Java 定时任务的四种方法 (1) JDK 自带定时器实现 (2) Spring Task @Scheduled 注解任务调度 (3) Quartz 定时器实现 (4) Elastic-job 分布式任务调度框架 JDK 自带 .NET Framework 2.0JDK 自带 Timer 和 JDK1.5 + 新 ScheduledExecutorService; Spring3.0自带的任务调度工具:它可以看做是一个轻量级的Quartz,而且使用起来比Quartz简单得多,一般可以直接用@Scheduled+corn表达式来注解实现; Quartz:简单但功能强大的 JAVA 作业调度框架; Elastic-job分布式作业调度框架:是当当网架构师基于Zookepper、Quartz开发并开源的一个Java分布式定时任务,解决了Quartz不支持分布式的缺点。 JDK自带的java.util. JDK 自带的 java.util.Import 是 JDK 的一部分。 java.util.import import java.util. import java.util. public class Test { /** * 第一个方法:设置在指定时间执行指定任务,只执行一次 * schedule(TimerTask task, Date time) */ public static void timer1 { Timer timer = new Timer; timer.schedule(new Timer) timer.schedule(new 定时任务) public void run { System.out.println(new Date + "\t "+"--specify the task to be run---"); } }, new Date(System.currentTimeMillis + 2000)); } } } /** * 第二个方法:设置指定任务在延迟后执行,只执行一次 * schedule(TimerTask task, long delay) * 延迟单位毫秒 */ public static void timer2{ Timer timer = new Timer; timer.schedule(new Timer) timer.schedule(new 定时任务) public void run { system.out.println(new Date + "\t "+"--specify the task to be run---"); } }, 2000); } /** * 第三个方法:设置指定的任务在指定的延迟时间后周期性执行,周期时间为 period * schedule(TimerTask task, long delay, long period) * scheduleAtFixedRate(TimerTask task, long delay, long period) * 延迟,周期以毫秒为单位 */ public static void timer3 { Timer timer = new Timer; timer.schedule(new Timer) timer.schedule(new 定时任务) public void run { system.out.println(new Date + "\t "+"--specify the task to be run---"); } }, 1000, 1000); } /** * 第四种方法:设置指定任务 task 在指定时间 firstTime 开始重复循环执行,循环时间为周期 * schedule(TimerTask task, Date firstTime, long period) * scheduleAtFixedRate(TimerTask task, Date firstTime, long period) * 以毫秒为单位的周期 */ public static void timer4 { Calendar calendar = Calendar.getInstance; calendar.set(Calendar.HOTIME) */ calendar.set(Calendar.HOUR_OF_DAY, 12); // 控制时间 calendar.set(Calendar.MINUTE, 0); // 控制分钟数 calendar.set(Calendar.SECOND, 0); // 控制秒数 Date time = calendar.getTime; // 推导出执行任务的时间,本例中为今天 12:00:00。 Timer timer = new Timer; timer.schedule(new Timer) timer.schedule(new 定时任务) public void run { System.out.println(new Date +"\t "+"--- 指定要执行的任务 ---"); } }, time, 1000); } /** * schedule 方法和 scheduleAtFixedRate 方法的区别: * (1) schedule 方法:如果第一次执行时间延迟,则根据上次实际执行完成时间点计算后续执行时间,即:下一次执行时间点 = 上次程序执行完成时间点 + 间隔时间 * (2) scheduleAtFixedRate 方法:如果第一次执行时间延迟,则根据上次开始时间点计算后续执行时间,即:下次执行时间点=上次程序执行时间点+间隔时间,*且前一个任务的执行时间延迟,则根据上次实际执行完成时间点计算后续执行时间,即:下次执行时间点=上次程序执行完成时间点+间隔时间。 *而上一个任务的执行时间大于间隔时间,就会与当前任务重叠,TimerTask 在执行时需要考虑线程同步的问题 */ } 计时器的缺陷:
-
包婷婷 (201550484)作业一 统计软件简介与数据操作-SPSS(Statistical Product and Service Solutions),"统计产品与服务解决方案"软件。最初软件全称为"(SolutionsStatistical Package for the Social Sciences),但是随着SPSS产品服务领域的扩大和服务深度的增加,SPSS公司已于2000年正式将英文全称更改为"统计产品与服务解决方案",标志着SPSS的战略方向正在做出重大调整。为IBM公司推出的一系列用于统计学分析运算、数据挖掘、预测分析和决策支持任务的软件产品及相关服务的总称SPSS,有Windows和Mac OS X等版本。 1984年SPSS总部首先推出了世界上第一个统计分析软件微机版本SPSS/PC+,开创了SPSS微机系列产品的开发方向,极大地扩充了它的应用范围,并使其能很快地应用于自然科学、技术科学、社会科学的各个领域。世界上许多有影响的报刊杂志纷纷就SPSS的自动统计绘图、数据的深入分析、使用方便、功能齐全等方面给予了高度的评价。 R统计软件介绍 R是一套完整的数据处理、计算和制图软件系统。其功能包括:数据存储和处理系统;数组运算工具(其向量、矩阵运算方面功能尤其强大);完整连贯的统计分析工具;优秀的统计制图功能;简便而强大的编程语言:可操纵数据的输入和输出,可实现分支、循环,用户可自定义功能。 与其说R是一种统计软件,还不如说R是一种数学计算的环境,因为R并不是仅仅提供若干统计程序、使用者只需指定数据库和若干参数便可进行一个统计分析。R的思想是:它可以提供一些集成的统计工具,但更大量的是它提供各种数学计算、统计计算的函数,从而使使用者能灵活机动的进行数据分析,甚至创造出符合需要的新的统计计算方法。 该语言的语法表面上类似 C,但在语义上是函数设计语言(functional programming language)的变种并且和Lisp 以及 APL有很强的兼容性。特别的是,它允许在"语言上计算"(computing on the language)。这使得它可以把表达式作为函数的输入参数,而这种做法对统计模拟和绘图非常有用。 R是一个免费的*软件,它有UNIX、LINUX、MacOS和WINDOWS版本,都是可以免费下载和使用的。在R主页那儿可以下载到R的安装程序、各种外挂程序和文档。在R的安装程序中只包含了8个基础模块,其他外在模块可以通过CRAN获得。 二、R语言 R是用于统计分析、绘图的语言和操作环境。R是属于GNU系统的一个*、免费、源代码开放的软件,它是一个用于统计计算和统计制图的优秀工具。 R作为一种统计分析软件,是集统计分析与图形显示于一体的。它可以运行于UNIX,Windows和Macintosh的操作系统上,而且嵌入了一个非常方便实用的帮助系统,相比于其他统计分析软件,R还有以下特点: 1.R是*软件。这意味着它是完全免费,开放源代码的。可以在它的网站及其镜像中下载任何有关的安装程序、源代码、程序包及其源代码、文档资料。标准的安装文件身自身就带有许多模块和内嵌统计函数,安装好后可以直接实现许多常用的统计功能。[2] 2.R是一种可编程的语言。作为一个开放的统计编程环境,语法通俗易懂,很容易学会和掌握语言的语法。而且学会之后,我们可以编制自己的函数来扩展现有的语言。这也就是为什么它的更新速度比一般统计软件,如,SPSS,SAS等快得多。大多数最新的统计方法和技术都可以在R中直接得到。[2] 3. 所有R的函数和数据集是保存在程序包里面的。只有当一个包被载入时,它的内容才可以被访问。一些常用、基本的程序包已经被收入了标准安装文件中,随着新的统计分析方法的出现,标准安装文件中所包含的程序包也随着版本的更新而不断变化。在另外版安装文件中,已经包含的程序包有:base一R的基础模块、mle一极大似然估计模块、ts一时间序列分析模块、mva一多元统计分析模块、survival一生存分析模块等等.[2] 4.R具有很强的互动性。除了图形输出是在另外的窗口处,它的输入输出窗口都是在同一个窗口进行的,输入语法中如果出现错误会马上在窗口口中得到提示,对以前输入过的命令有记忆功能,可以随时再现、编辑修改以满足用户的需要。输出的图形可以直接保存为JPG,BMP,PNG等图片格式,还可以直接保存为PDF文件。另外,和其他编程语言和数据库之间有很好的接口。[2] 5.如果加入R的帮助邮件列表一,每天都可能会收到几十份关于R的邮件资讯。可以和全球一流的统计计算方面的专家讨论各种问题,可以说是全世界最大、最前沿的统计学家思维的聚集地.[2] R是基于S语言的一个GNU项目,所以也可以当作S语言的一种实现,通常用S语言编写的代码都可以不作修改的在R环境下运行。 R的语法是来自Scheme。R的使用与S-PLUS有很多类似之处,这两种语言有一定的兼容性。S-PLUS的使用手册,只要稍加修改就可作为R的使用手册。所以有人说:R,是S-PLUS的一个“克隆”。 但是请不要忘了:R是免费的(R is free)。R语言源代码托管在github,具体地址可以看参考资料。[3] 。 R语言的下载可以通过CRAN的镜像来查找。 R语言有域名为.cn的下载地址,有六个,其中两个由Datagurn,由 中国科学技术大学提供的。R语言Windows版,其中由两个下载地点是Datagurn和 USTC提供的。 三、stata Stata 是一套提供其使用者数据分析、数据管理以及绘制专业图表的完整及整合性统计软件。它提供许许多多功能,包含线性混合模型、均衡重复反复及多项式普罗比模式。用Stata绘制的统计图形相当精美。 新版本的STATA采用最具亲和力的窗口接口,使用者自行建立程序时,软件能提供具有直接命令式的语法。Stata提供完整的使用手册,包含统计样本建立、解释、模型与语法、文献等超过一万余页的出版品。 除此之外,Stata软件可以透过网络实时更新每天的最新功能,更可以得知世界各地的使用者对于STATA公司提出的问题与解决之道。使用者也可以透过Stata. Journal获得许许多多的相关讯息以及书籍介绍等。另外一个获取庞大资源的管道就是Statalist,它是一个独立的listserver,每月交替提供使用者超过1000个讯息以及50个程序。 四、PYTHON
-
活动时间:2018年8月20日11:30-12:30 活动地点上海展览中心一楼友谊厅 活动嘉宾刘宪权 2017年7月8日国务院发布的《新一代人工智能发展规划》预测了人工智能发展的不确定性可能带来的新挑战:"人工智能是一项影响广泛的颠覆性技术,可能带来改变就业结构、冲击法律和社会伦理、侵犯个人隐私、挑战国际关系准则等问题,将对*管理、经济 对*管理、经济安全和社会稳定乃至全球治理产生深远影响。在大力发展人工智能的同时,我们必须高度重视可能带来的安全风险挑战。" 诚然,人工智能技术在给人类生活带来极大便利的同时,也给人类社会带来了极大的犯罪风险。人工智能技术作为一种犯罪工具,无疑为犯罪插上了科技的 "翅膀",使社会危害程度呈几何倍数增长,甚至可能引发更多新型犯罪。在此背景下,本书探讨了人工智能技术给刑法带来的风险与挑战,同时,本书也对这些风险与挑战做出了回应。 人类既然有能力开发人工智能,也应该有能力控制人工智能。为了避免出现科幻电影中人工智能不受人类控制的情况,我们必须重视人工智能的安全与风险问题。防控人工智能风险,关键在于控制人工智能产品的开发和使用。当前,人工智能产品的开发和使用存在多重刑事风险,从源头上防控这些风险或许是更为合适的选择。因此,刑法需要在其他法律法规对人工智能产品的研发和使用作出具体规定的基础上,将严重危害社会的行为纳入规制范围。--摘自《人工智能当代刑法面临的挑战 责任编辑:刘宪权 出版日期:2018年7月 定价:68.00 元 刘宪权
-
☕ 【Java 技术指南】"Java8 编程主题",让您真正会使用新版 Java 日期和时间 API 编程指南
-
Java 编程问题:III.使用日期和时间