【深度思考】JDK8中日期類型該如何使用?

在JDK8之前,處理日期時間,我們主要使用3個類,DateSimpleDateFormatCalendar

這3個類在使用時都或多或少的存在一些問題,比如SimpleDateFormat不是線程安全的,

比如DateCalendar獲取到的月份是0到11,而不是現實生活中的1到12,關於這一點,《阿里巴巴Java開發手冊》中也有提及,因為很容易犯錯:

不過,JDK8推出了全新的日期時間處理類解決了這些問題,比如InstantLocalDateLocalTimeLocalDateTimeDateTimeFormatter,在《阿里巴巴Java開發手冊》中也推薦使用Instant

LocalDateTimeDateTimeFormatter

但我發現好多項目中其實並沒有使用這些類,使用的還是之前的DateSimpleDateFormatCalendar,所以本篇博客就講解下JDK8新推出的日期時間類,主要是下面幾個:

  1. Instant
  2. LocalDate
  3. LocalTime
  4. LocalDateTime
  5. DateTimeFormatter

1. Instant

1.1 獲取當前時間

既然Instant可以代替Date類,那它肯定可以獲取當前時間:

Instant instant = Instant.now();
System.out.println(instant);

輸出結果:

2020-06-10T08:22:13.759Z

細心的你會發現,這個時間比北京時間少了8個小時,如果要輸出北京時間,可以加上默認時區:

System.out.println(instant.atZone(ZoneId.systemDefault()));

輸出結果:

2020-06-10T16:22:13.759+08:00[Asia/Shanghai]

1.2 獲取時間戳

Instant instant = Instant.now();

// 當前時間戳:單位為秒
System.out.println(instant.getEpochSecond());
// 當前時間戳:單位為毫秒
System.out.println(instant.toEpochMilli());

輸出結果:

1591777752

1591777752613

當然,也可以通過System.currentTimeMillis()獲取當前毫秒數。

1.3 將long轉換為Instant

1)根據秒數時間戳轉換:

Instant instant = Instant.now();
System.out.println(instant);

long epochSecond = instant.getEpochSecond();
System.out.println(Instant.ofEpochSecond(epochSecond));
System.out.println(Instant.ofEpochSecond(epochSecond, instant.getNano()));

輸出結果:

2020-06-10T08:40:54.046Z

2020-06-10T08:40:54Z

2020-06-10T08:40:54.046Z

2)根據毫秒數時間戳轉換:

Instant instant = Instant.now();
System.out.println(instant);

long epochMilli = instant.toEpochMilli();
System.out.println(Instant.ofEpochMilli(epochMilli));

輸出結果:

2020-06-10T08:43:25.607Z

2020-06-10T08:43:25.607Z

1.4 將String轉換為Instant

String text = "2020-06-10T08:46:55.967Z";
Instant parseInstant = Instant.parse(text);
System.out.println("秒時間戳:" + parseInstant.getEpochSecond());
System.out.println("豪秒時間戳:" + parseInstant.toEpochMilli());
System.out.println("納秒:" + parseInstant.getNano());

輸出結果:

秒時間戳:1591778815

豪秒時間戳:1591778815967

納秒:967000000

如果字符串格式不對,比如修改成2020-06-10T08:46:55.967,就會拋出java.time.format.DateTimeParseException異常,如下圖所示:

2. LocalDate

2.1 獲取當前日期

使用LocalDate獲取當前日期非常簡單,如下所示:

LocalDate today = LocalDate.now();
System.out.println("today: " + today);

輸出結果:

today: 2020-06-10

不用任何格式化,輸出結果就非常友好,如果使用Date,輸出這樣的格式,還得配合SimpleDateFormat指定yyyy-MM-dd進行格式化,一不小心還會出個bug,比如去年年底很火的1個bug,我當時還是截了圖的:

這2個好友是2019/12/31關注我的,但我2020年1月2號查看時,卻显示成了2020/12/31,為啥呢?格式化日期時格式寫錯了,應該是yyyy/MM/dd,卻寫成了YYYY/MM/dd,剛好那周跨年,就显示成下一年,也就是2020年了,當時好幾個博主寫過文章解析原因,我這裏就不做過多解釋了。

划重點:都說到這了,給大家安利下我新註冊的公眾號「申城異鄉人」,歡迎大家關注,更多原創文章等着你哦,哈哈。

2.2 獲取年月日

LocalDate today = LocalDate.now();

int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();

System.out.println("year: " + year);
System.out.println("month: " + month);
System.out.println("day: " + day);

輸出結果:

year: 2020

month: 6

day: 10

獲取月份終於返回1到12了,不像java.util.Calendar獲取月份返回的是0到11,獲取完還得加1。

2.3 指定日期

LocalDate specifiedDate = LocalDate.of(2020, 6, 1);
System.out.println("specifiedDate: " + specifiedDate);

輸出結果:

specifiedDate: 2020-06-01

如果確定月份,推薦使用另一個重載方法,使用枚舉指定月份:

LocalDate specifiedDate = LocalDate.of(2020, Month.JUNE, 1);

2.4 比較日期是否相等

LocalDate localDate1 = LocalDate.now();
LocalDate localDate2 = LocalDate.of(2020, 6, 10);
if (localDate1.equals(localDate2)) {
    System.out.println("localDate1 equals localDate2");
}

輸出結果:

localDate1 equals localDate2

2.5 獲取日期是本周/本月/本年的第幾天

LocalDate today = LocalDate.now();

System.out.println("Today:" + today);
System.out.println("Today is:" + today.getDayOfWeek());
System.out.println("今天是本周的第" + today.getDayOfWeek().getValue() + "天");
System.out.println("今天是本月的第" + today.getDayOfMonth() + "天");
System.out.println("今天是本年的第" + today.getDayOfYear() + "天");

輸出結果:

Today:2020-06-11

Today is:THURSDAY

今天是本周的第4天

今天是本月的第11天

今天是本年的第163天

2.6 判斷是否為閏年

LocalDate today = LocalDate.now();

System.out.println(today.getYear() + " is leap year:" + today.isLeapYear());

輸出結果:

2020 is leap year:true

3. LocalTime

3.1 獲取時分秒

如果使用java.util.Date,那代碼是下面這樣的:

Date date = new Date();

int hour = date.getHours();
int minute = date.getMinutes();
int second = date.getSeconds();

System.out.println("hour: " + hour);
System.out.println("minute: " + minute);
System.out.println("second: " + second);

輸出結果:

注意事項:這幾個方法已經過期了,因此強烈不建議在項目中使用:

如果使用java.util.Calendar,那代碼是下面這樣的:

Calendar calendar = Calendar.getInstance();

// 12小時制
int hourOf12 = calendar.get(Calendar.HOUR);
// 24小時制
int hourOf24 = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
int milliSecond = calendar.get(Calendar.MILLISECOND);

System.out.println("hourOf12: " + hourOf12);
System.out.println("hourOf24: " + hourOf24);
System.out.println("minute: " + minute);
System.out.println("second: " + second);
System.out.println("milliSecond: " + milliSecond);

輸出結果:

注意事項:獲取小時時,有2個選項,1個返回12小時制的小時數,1個返回24小時制的小時數,因為現在是晚上8點,所以calendar.get(Calendar.HOUR)返回8,而calendar.get(Calendar.HOUR_OF_DAY)返回20。

如果使用java.time.LocalTime,那代碼是下面這樣的:

LocalTime localTime = LocalTime.now();
System.out.println("localTime:" + localTime);

int hour = localTime.getHour();
int minute = localTime.getMinute();
int second = localTime.getSecond();

System.out.println("hour: " + hour);
System.out.println("minute: " + minute);
System.out.println("second: " + second);

輸出結果:

可以看出,LocalTime只有時間沒有日期。

4. LocalDateTime

4.1 獲取當前時間

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("localDateTime:" + localDateTime);

輸出結果:

localDateTime: 2020-06-11T11:03:21.376

4.2 獲取年月日時分秒

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("localDateTime: " + localDateTime);

System.out.println("year: " + localDateTime.getYear());
System.out.println("month: " + localDateTime.getMonthValue());
System.out.println("day: " + localDateTime.getDayOfMonth());
System.out.println("hour: " + localDateTime.getHour());
System.out.println("minute: " + localDateTime.getMinute());
System.out.println("second: " + localDateTime.getSecond());

輸出結果:

4.3 增加天數/小時

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("localDateTime: " + localDateTime);

LocalDateTime tomorrow = localDateTime.plusDays(1);
System.out.println("tomorrow: " + tomorrow);

LocalDateTime nextHour = localDateTime.plusHours(1);
System.out.println("nextHour: " + nextHour);

輸出結果:

localDateTime: 2020-06-11T11:13:44.979

tomorrow: 2020-06-12T11:13:44.979

nextHour: 2020-06-11T12:13:44.979

LocalDateTime還提供了添加年、周、分鐘、秒這些方法,這裏就不一一列舉了:

4.4 減少天數/小時

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("localDateTime: " + localDateTime);

LocalDateTime yesterday = localDateTime.minusDays(1);
System.out.println("yesterday: " + yesterday);

LocalDateTime lastHour = localDateTime.minusHours(1);
System.out.println("lastHour: " + lastHour);

輸出結果:

localDateTime: 2020-06-11T11:20:38.896

yesterday: 2020-06-10T11:20:38.896

lastHour: 2020-06-11T10:20:38.896

類似的,LocalDateTime還提供了減少年、周、分鐘、秒這些方法,這裏就不一一列舉了:

4.5 獲取時間是本周/本年的第幾天

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("localDateTime: " + localDateTime);

System.out.println("DayOfWeek: " + localDateTime.getDayOfWeek().getValue());
System.out.println("DayOfYear: " + localDateTime.getDayOfYear());

輸出結果:

localDateTime: 2020-06-11T11:32:31.731

DayOfWeek: 4

DayOfYear: 163

5. DateTimeFormatter

JDK8中推出了java.time.format.DateTimeFormatter來處理日期格式化問題,《阿里巴巴Java開發手冊》中也是建議使用DateTimeFormatter代替SimpleDateFormat

5.1 格式化LocalDate

LocalDate localDate = LocalDate.now();

System.out.println("ISO_DATE: " + localDate.format(DateTimeFormatter.ISO_DATE));
System.out.println("BASIC_ISO_DATE: " + localDate.format(DateTimeFormatter.BASIC_ISO_DATE));
System.out.println("ISO_WEEK_DATE: " + localDate.format(DateTimeFormatter.ISO_WEEK_DATE));
System.out.println("ISO_ORDINAL_DATE: " + localDate.format(DateTimeFormatter.ISO_ORDINAL_DATE));

輸出結果:

如果提供的格式無法滿足你的需求,你還可以像以前一樣自定義格式:

LocalDate localDate = LocalDate.now();

System.out.println("yyyy/MM/dd: " + localDate.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")));

輸出結果:

yyyy/MM/dd: 2020/06/11

5.2 格式化LocalTime

LocalTime localTime = LocalTime.now();
System.out.println(localTime);
System.out.println("ISO_TIME: " + localTime.format(DateTimeFormatter.ISO_TIME));
System.out.println("HH:mm:ss: " + localTime.format(DateTimeFormatter.ofPattern("HH:mm:ss")));

輸出結果:

14:28:35.230

ISO_TIME: 14:28:35.23

HH:mm:ss: 14:28:35

5.3 格式化LocalDateTime

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
System.out.println("ISO_DATE_TIME: " + localDateTime.format(DateTimeFormatter.ISO_DATE_TIME));
System.out.println("ISO_DATE: " + localDateTime.format(DateTimeFormatter.ISO_DATE));

輸出結果:

2020-06-11T14:33:18.303

ISO_DATE_TIME: 2020-06-11T14:33:18.303

ISO_DATE: 2020-06-11

6. 類型相互轉換

6.1 Instant轉Date

JDK8中,Date新增了from()方法,將Instant轉換為Date,代碼如下所示:

Instant instant = Instant.now();
System.out.println(instant);

Date dateFromInstant = Date.from(instant);
System.out.println(dateFromInstant);

輸出結果:

2020-06-11T06:39:34.979Z

Thu Jun 11 14:39:34 CST 2020

6.2 Date轉Instant

JDK8中,Date新增了toInstant方法,將Date轉換為Instant,代碼如下所示:

Date date = new Date();
Instant dateToInstant = date.toInstant();
System.out.println(date);
System.out.println(dateToInstant);

輸出結果:

Thu Jun 11 14:46:12 CST 2020

2020-06-11T06:46:12.112Z

6.3 Date轉LocalDateTime

Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime localDateTimeOfInstant = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
System.out.println(date);
System.out.println(localDateTimeOfInstant);

輸出結果:

Thu Jun 11 14:51:07 CST 2020

2020-06-11T14:51:07.904

6.4 Date轉LocalDate

Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime localDateTimeOfInstant = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
LocalDate localDate = localDateTimeOfInstant.toLocalDate();
System.out.println(date);
System.out.println(localDate);

輸出結果:

Thu Jun 11 14:59:38 CST 2020

2020-06-11

可以看出,Date是先轉換為Instant,再轉換為LocalDateTime,然後通過LocalDateTime獲取LocalDate

6.5 Date轉LocalTime

Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime localDateTimeOfInstant = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
LocalTime toLocalTime = localDateTimeOfInstant.toLocalTime();
System.out.println(date);
System.out.println(toLocalTime);

輸出結果:

Thu Jun 11 15:06:14 CST 2020

15:06:14.531

可以看出,Date是先轉換為Instant,再轉換為LocalDateTime,然後通過LocalDateTime獲取LocalTime

6.6 LocalDateTime轉Date

LocalDateTime localDateTime = LocalDateTime.now();

Instant toInstant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Date dateFromInstant = Date.from(toInstant);
System.out.println(localDateTime);
System.out.println(dateFromInstant);

輸出結果:

2020-06-11T15:12:11.600

Thu Jun 11 15:12:11 CST 2020

6.7 LocalDate轉Date

LocalDate today = LocalDate.now();

LocalDateTime localDateTime = localDate.atStartOfDay();
Instant toInstant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Date dateFromLocalDate = Date.from(toInstant);
System.out.println(dateFromLocalDate);

輸出結果:

Thu Jun 11 00:00:00 CST 2020

6.8 LocalTime轉Date

LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();

LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
Instant instantFromLocalTime = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Date dateFromLocalTime = Date.from(instantFromLocalTime);

System.out.println(dateFromLocalTime);

輸出結果:

Thu Jun 11 15:24:18 CST 2020

7. 總結

JDK8推出了全新的日期時間類,如InstantLocaleDateLocalTimeLocalDateTimeDateTimeFormatter,設計比之前更合理,也是線程安全的。

《阿里巴巴Java開發規範》中也推薦使用Instant代替DateLocalDateTime 代替 CalendarDateTimeFormatter 代替 SimpleDateFormat

因此,如果條件允許,建議在項目中使用,沒有使用的,可以考慮升級下。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

車主們說這款有着跑車外觀的車 油耗出奇地低

只是高速時的風噪略大、儲物空間比較少,應該在四個門板多做一些儲物格。關於空間,我的身高176cm,把駕駛位置調好后,我在後排還有一拳多的腿部空間。而我的車型是2。0L的平均百公里油耗是7。8L。車主:Bestss購買車型:三廂 1。

10多萬的合資A級車選擇非常多,要論最個性、最具動感外觀的車型,馬自達3昂克賽拉絕對是最具實力的車型之一。下面我們就來看看這款車的車主們都對它有哪些評價。

長安馬自達-馬自達3 Axela昂克賽拉

指導價:11.49-15.99萬

車主:人稱啊明

購買車型:三廂 1.5L自動豪華型

裸車價格:12.89萬

每一個男人都有一個跑車夢,昂克賽拉的外觀很像跑車,還有聰明的變速箱、讓人滿意的低油耗、起步快等優點,所以我最終選擇了這款車。

它的操控真的不錯,給人的感覺是穩、實、准,轉向手感不錯,採用了四輪獨立懸架,支撐性好,在同級車型中性價比很高。

目前我的車行駛了快8000公里了,平均百公里油耗只有7.1L!創馳藍天技術真不是蓋的。

車主:佛山小偉

購買車型:三廂 2.0L自動旗艦型

裸車價格:14.99萬

我對“魂動”的設計外觀和駕駛體驗最滿意,它指向精準、換擋果斷、動力也充沛。只是高速時的風噪略大、儲物空間比較少,應該在四個門板多做一些儲物格。

關於空間,我的身高176cm,把駕駛位置調好后,我在後排還有一拳多的腿部空間。而我的車型是2.0L的平均百公里油耗是7.8L。

車主:Bestss

購買車型:三廂 1.5L手動豪華型

裸車價格:12.29萬

外觀和內飾就不用我多說了,大多數買昂克賽拉的人都是奔着漂亮的外觀去的!它的1.5L缸內直噴發動機動力夠用,2000轉以後的動力有比較大的爆發。

行駛起來胎噪有些大,可能是輪胎側重抓地力的原因,還有就是車漆有些薄。

目前我的車行駛了15000公里了,我對它比較滿意,目前的平均油耗是7.2L,油耗不算高!

編者點評:

昂克賽拉是一款性格鮮明的車型,它堅持採用四輪獨立懸挂、偏向性能的輪胎、AT變速箱等,讓它的操控性出色,如果你喜歡駕駛感受好的A級車,昂克賽拉絕對是一個不錯的選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

為什麼朋友說他夢想的車是艾母雞?看完本文後終於懂了

說到奔馳許多人最初的印象都是寬、大、貴外加後排坐着一個人肥,牙黃,地中海的老闆似乎奔馳一向跟年輕、運動、操控這些詞扯不上什麼關係所以坊間也流傳這麼一個說法——甚至有一些“自信狂人”遇上大奔時會抑制不住腎上腺

說到奔馳

許多人最初的印象都是

寬、大、貴

外加後排坐着一個

人肥,牙黃,地中海的老闆

似乎奔馳一向跟

年輕、運動、操控這些詞

扯不上什麼關係

所以坊間也流傳這麼一個說法——

甚至有一些“自信狂人”

遇上大奔時

會抑制不住腎上腺本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

我很驚訝!十幾萬的國產SUV竟有人幹得過眾泰?

對於我們汽車媒體來說,這是個很方面的功能。榮威RX5配備了車身穩定系統、定速巡航、一鍵啟動、電動駐車、前撞預警系統、陡坡緩降、電動後備廂、車道保持、道路限速識別、四驅系統鎖止等豐富配置。動力系統方面,RX5將搭載1。

國內緊湊型SUV的熱度只增不減,2016年,榮威帶來了他們潛心打造,並得到馬雲的阿里技術支持的榮威RX5,主打互聯網概念。

榮威RX5外觀走大氣沉穩范,中規中矩但又精緻的造型給人不錯的印象。前臉“展翼格柵”將兩側前大燈融貫一體,前大燈為矩陣式LED大燈,其由24顆LED光源組成,燈組內部還集成了“如意形”LED日間行車燈。矩陣式全LED大燈更顯科技感。

一進車內最吸睛的當屬中控10.4英寸大屏。由於大屏幕的使用,一些傳統按鍵被取消,整體設計簡潔時尚,僅在屏幕下方保留了五個實體按鍵。同時RX5在內飾用料上也多處使用了軟質皮革包裹,凸顯質感。

RX5配備了榮威和阿里聯合開發的Yun OS,功能較為強大,可以通過大數據為用戶提供個性化服務。還可以支持綁定航拍機和運動相機,旅途中拍攝的畫面可以呈現在中控的大屏幕上。對於我們汽車媒體來說,這是個很方面的功能。

榮威RX5配備了車身穩定系統、定速巡航、一鍵啟動、電動駐車、前撞預警系統、陡坡緩降、電動後備廂、車道保持、道路限速識別、四驅系統鎖止等豐富配置。

動力系統方面,RX5將搭載1.5T和2.0T兩款渦輪增壓發動機,其中1.5T發動機最大功率為169馬,峰值扭矩250N·m,與之匹配的是手動/TST 7速雙離合變速箱;2.0T發動機最大功率為220馬力,峰值扭矩350N·m,與之匹配的是TST 6速濕式雙離合變速箱。

整車開起來動力表現不錯,車輛的轉向是比較精準的,底盤偏向柔軟和舒適,但是又保留了一些路感。很符合家用SUV得定位。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

機器學習——手把手教你用Python實現回歸樹模型

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天這篇是機器學習專題的第24篇文章,我們來聊聊回歸樹模型。

所謂的回歸樹模型其實就是用樹形模型來解決回歸問題,樹模型當中最經典的自然還是決策樹模型,它也是幾乎所有樹模型的基礎。雖然基本結構都是使用決策樹,但是根據預測方法的不同也可以分為兩種。第一種,樹上的恭弘=叶 恭弘子節點就對應一個預測值和分類樹對應,這一種方法稱為回歸樹。第二種,樹上的恭弘=叶 恭弘子節點對應一個線性模型,最後的結果由線性模型給出。這一種方法稱為模型樹。

今天我們先來看看其中的回歸樹。

回歸樹模型

回歸樹模型的核心算法,也就是構建決策樹的算法,就是我們上篇文章所講的CART算法。如果有生疏或者是遺漏的同學,可以通過下方傳送門回顧一下:

機器學習——十大數據挖掘之一的決策樹CART算法

CART算法的核心精髓就是我們每次選擇特徵對數據進行拆分的時候,永遠對數據集進行二分。無論是離散特徵還是連續性特徵,一視同仁。CART還有一個特點是使用GINI指數而不是信息增益或者是信息增益比來選擇拆分的特徵,但是在回歸問題當中用不到這個。因為回歸問題的損失函數是均方差,而不是交叉熵,很難用熵來衡量連續值的準確度。

在分類樹當中,我們一個恭弘=叶 恭弘子節點代表一個類別的預測值,這個類別的值是落到這個恭弘=叶 恭弘子節點當中訓練樣本的類別的眾數,也就是出現頻率最高的類別。在回歸樹當中,恭弘=叶 恭弘子節點對應的自然就是一個連續值。這個連續值是落到這個節點的訓練樣本的均值,它的誤差就是這些樣本的均方差。

另外,之前我們在選擇特徵的劃分閾值的時候,對閾值的選擇進行了優化,只選擇了那些會引起預測類別變化的閾值。但是在回歸問題當中,由於預測值是一個浮點數,所以這個優化也不存在了。整體上來說,其實回歸樹的實現難度比分類樹是更低的。

實戰

我們首先來加載數據,我們這次使用的是scikit-learn庫當中經典的波士頓房價預測的數據。關於房價預測,kaggle當中也有一個類似的比賽,叫做:house-prices-advanced-regression-techniques。不過給出的特徵更多,並且存在缺失等情況,需要我們進行大量的特徵工程。感興趣的同學可以自行研究一下。

首先,我們來獲取數據,由於sklearn庫當中已經有數據了,我們可以直接調用api獲取,非常簡單:

import numpy as np
import pandas as pd
from sklearn.datasets import load_boston
boston = load_boston()

X, y = boston.data, boston.target

我們輸出前幾條數據查看一下:

這個數據質量很高,sklearn庫已經替我們做完了數據篩選與特徵工程,直接拿來用就可以了。為了方便我們傳遞數據,我們將X和y合併在一起。由於y是一維的數組形式是不能和二維的X合併的,所以我們需要先對y進行reshape之後再進行合併。

y = y.reshape(-1, 1)
X = np.hstack((X, y))

hstack函數可以將兩個np的array橫向拼接,與之對應的是vstack,是將兩個array縱向拼接,這個也是常規操作。合併之後,y作為新的一列添加在了X的後面。數據搞定了,接下來就要輪到實現模型了。

在實現決策樹的主體部分之前,我們先來實現兩個輔助函數。第一個輔助函數是計算一批樣本的方差和,第二個輔助函數是獲取樣本的均值,也就是子節點的預測值。

def node_mean(X):
    return np.mean(X[:, -1])


def node_variance(X):
    return np.var(X[:, -1]) * X.shape[0]

這個搞定了之後,我們繼續實現根據閾值拆分數據的函數。這個也可以復用之前的代碼:

from collections import defaultdict
def split_dataset(X, idx, thred):
    split_data = defaultdict(list)
    for x in X:
        split_data[x[idx] < thred].append(x)
    return list(split_data.values()), list(split_data.keys())

接下來是兩個很重要的函數,分別是get_thresholds和split_variance。顧名思義,第一個函數用來獲取閾值,前面說了由於我們做的是回歸模型,所以理論上來說特徵的每一個取值都可以作為切分的依據。但是也不排除可能會存在多條數據的特徵值相同的情況,所以我們對它進行去重。第二個函數是根據閾值對數據進行拆分,返回拆分之後的方差和。

def get_thresholds(X, i):
    return set(X[:, i].tolist())

# 每次迭代方差優化的底線
MINIMUM_IMPROVE = 2.0
# 每個恭弘=叶 恭弘子節點最少樣本數
MINIMUM_SAMPLES = 10

def split_variance(dataset, idx, threshold):
    left, right = [], []
    n = dataset.shape[0]
    for data in dataset:
        if data[idx] < threshold:
            left.append(data)
        else:
            right.append(data)
    left, right = np.array(left), np.array(right)
    # 預剪枝
    # 如果拆分結果有一邊過少,則返回None,防止過擬合
    if len(left) < MINIMUM_SAMPLES or len(right) < MINIMUM_SAMPLES:
        return None
    # 拆分之後的方差和等於左子樹的方差和加上右子樹的方差和
    # 因為是方差和而不是均方差,所以可以累加
    return node_variance(left) + node_variance(right)

這裏我們用到了MINIMUM_SAMPLES這個參數,它是用來預剪枝用的。由於我們是回歸模型,如果不對決策樹的生長加以限制,那麼很有可能得到的決策樹的恭弘=叶 恭弘子節點和訓練樣本的數量一樣多。這顯然就陷入了過擬合了,對於模型的效果是有害無益的。所以我們要限制每個節點的樣本數量,這個是一個參數,我們可以根據需要自行調整。

接下來,就是特徵和閾值篩選的函數了。我們需要開發一個函數來遍歷所有可以拆分的特徵和閾值,對數據進行拆分,從所有特徵當中找到最佳的拆分可能。

def choose_feature_to_split(dataset):
    n = len(dataset[0])-1
    m = len(dataset)
    # 記錄最佳方差,特徵和閾值
    var_ = node_variance(dataset)
    bestVar = float('inf')
    feature = -1
    thred = None
    for i in range(n):
        threds = get_thresholds(dataset, i)
        for t in threds:
            # 遍歷所有的閾值,計算每個閾值的variance
            v = split_variance(dataset, i, t)
            # 如果v等於None,說明拆分過擬合了,跳過
            if v is None:
                continue
            if v  < bestVar:
                bestVar, feature, thred = v, i, t
    # 如果最好的拆分效果達不到要求,那麼就不拆分,控制子樹的數量
    if var_ - bestVar < MINIMUM_IMPROVE:
        return None, None
    return feature, thred

和上面一樣,這個函數當中也用到了一個預剪枝的參數MINIMUM_IMPROVE,它衡量的是每一次生成子樹帶來的收益。當某一次生成子樹帶來的收益小於某個值的時候,說明收益很小,並不划算,所以我們就放棄這次子樹的生成。這也是預剪枝的一種。

這些都搞定了之後,就可以來建樹了。建樹的過程和之前類似,只是我們這一次的數據當中沒有特徵的name,所以我們去掉特徵名稱的相關邏輯。

def create_decision_tree(dataset):
    dataset = np.array(dataset)
    
    # 如果當前數量小於10,那麼就不再繼續劃分了
    if dataset.shape[0] < MINIMUM_SAMPLES:
        return node_mean(dataset)
    
    # 記錄最佳拆分的特徵和閾值
    fidx, th = choose_feature_to_split(dataset)
    if fidx is None:
        return th
    
    node = {}
    node['feature'] = fidx
    node['threshold'] = th
    
    # 遞歸建樹
    split_data, vals = split_dataset(dataset, fidx, th)
    for data, val in zip(split_data, vals):
        node[val] = create_decision_tree(data)
    return node

我們來完整測試一下建樹,首先我們需要對原始數據進行拆分。將原始數據拆分成訓練數據和測試數據,由於我們的場景比較簡單,就不設置驗證數據了。

拆分數據不用我們自己實現,sklearn當中提供了相應的工具,我們直接調用即可:

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=23)

我們一般用到的參數就兩個,一個是test_size,它可以是一個整數也可以是一個浮點數。如果是整數,代表的是測試集的樣本數量。如果是一個0-1.0的浮點數,則代表測試集的佔比。random_state是生成隨機數的時候用到的隨機種子。

我們輸出一下生成的樹,由於數據量比較大,可以看到一顆龐大的樹結構。建樹的部分實現了之後,最後剩下的就是預測的部分了。

預測部分的代碼和之前分類樹相差不大,整體的邏輯完全一樣,只是去掉了feature_names的相關邏輯。

def classify(node, data):
    key = node['feature']
    pred = None
    thred = node['threshold']

    if isinstance(node[data[key] < thred], dict):
        pred = classify(node[data[key] < thred], data)
    else:
        pred = node[data[key] < thred]
            
    # 放置pred為空,挑選一個恭弘=叶 恭弘子節點作為替補
    if pred is None:
        for key in node:
            if not isinstance(node[key], dict):
                pred = node[key]
                break
    return pred

由於這個函數一次只能接受一條數據,如果我們想要批量預測的話還不行,所以最好的話再實現一個批量預測的predict函數比較好。

def predict(node, X):
    y_pred = []
    for x in X:
        y = classify(node, x)
        y_pred.append(y)
    return np.array(y_pred)

后剪枝

后剪枝的英文原文是post-prune,但是翻譯成事後剪枝也有點奇怪。anyway,我們就用后剪枝這個詞好了。

在回歸樹當中,我們利用的思想非常樸素,在建樹的時候建立一棵盡量複雜龐大的樹。然後在通過測試集對這棵樹進行修剪,修剪的邏輯也非常簡單,我們判斷一棵子樹存在分叉和沒有分叉單獨成為恭弘=叶 恭弘子節點時的誤差,如果修剪之後誤差更小,那麼我們就減去這棵子樹。

整個剪枝的過程和建樹的過程一樣,從上到下,遞歸執行。

整個邏輯很好理解,我們直接來看代碼:

def is_dict(node):
    return isinstance(node, dict)


def prune(node, testData):
    testData = np.array(testData)
    if testData.shape[0] == 0:
        return node
 
    # 拆分數據
    split_data, _ = split_dataset(testData, node['feature'], node['threshold'])
    # 對左右子樹遞歸修剪
    if is_dict(node[0]):
        node[0] = prune(node[0], split_data[0])
    if is_dict(node[1]) and len(split_data) > 1:
        node[1] = prune(node[1], split_data[1])

    # 如果左右都是恭弘=叶 恭弘子節點,那麼判斷當前子樹是否需要修剪
    if len(split_data) > 1 and not is_dict(node[0]) and not is_dict(node[1]):
        # 計算修剪前的方差和
        baseError = np.sum(np.power(np.array(split_data[0])[:, -1] - node[0], 2)) + np.sum(np.power(np.array(split_data[1])[:, -1] - node[1], 2))
        # 計算修剪后的方差和
        meanVal = (node[0] + node[1]) / 2
        mergeError = np.sum(np.power(meanVal - testData[:, -1], 2))
        if mergeError < baseError:
            return meanVal
        else:
            return node
    return node

最後,我們對修剪之後的效果做一下驗證:

從圖中可以看到,修剪之前我們在測試數據上的均方差是19.65,而修剪之後降低到了19.48。從數值上來看是有效果的,只是由於我們的訓練數據比較少,同時進行了預剪枝,影響了后剪枝的效果。但是對於實際的機器學習工程來說,一個方法只要是有明確效果的,在代價可以承受的範圍內,它就是有價值的,千萬不能覺得提升不明顯,而隨便否定一個方法。

這裏計算均方差的時候用到了sklearn當中的一個庫函數mean_square_error,從名字當中我們也可以看得出來它的用途,它可以對兩個Numpy的array計算均方差

總結

關於回歸樹模型的相關內容到這裏就結束了,我們不僅親手實現了模型,而且還在真實的數據集上做了實驗。如果你是親手實現的模型的代碼,相信你一定會有很多收穫。

雖然從實際運用來說我們幾乎不會使用樹模型來做回歸任務,但是回歸樹模型本身是非常有意義的。因為在它的基礎上我們發展出了很多效果更好的模型,比如大名鼎鼎的GBDT。因此理解回歸樹對於我們後續進階的學習是非常重要的。在深度學習普及之前,其實大多數高效果的模型都是以樹模型為基礎的,比如隨機森林、GBDT、Adaboost等等。可以說樹模型撐起了機器學習的半個時代,這麼說相信大家應該都能理解它的重要性了吧。

今天的文章就到這裏,如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

本文使用 mdnice 排版

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

沒有國產主機,怎麼開發:交叉編譯和QEMU虛擬機

1. 背景

近期國產化的趨勢越來越濃,包括國產操作系統、國產CPU等。時隔十多年,QQ for Linux也更新了。做為軟件開發人員,“有幸”也需要適配國產化。至於國產化的意義等就不在此討論。

本文提到的國產主機主要是指使用國產CPU和操作系統的計算機,比如:操作系統是銀河麒麟,CPU是飛騰FT2000。如果需要做適配開發,起碼需要一台對應的主機吧。據說在國產化早期,有錢都難買到機器,需要特殊渠道申請購買。不過,現在購買還是比較方便的。

通過客戶提供的正規正統的廠家詢價,着實嚇一跳,一台居然要一萬多!!而同等性能配置的windows-x86普通台式主機,才兩三千塊左右,相差有點大呀。本着能省就省的原則,上萬能的某寶看能不能淘一個。真得感謝馬爸爸和深圳華強北,5千多塊,突然感覺肉沒那麼痛了。

其實完全可以理解,國產的批量肯定很小很小,價格必然是高的。對於不專門開發“國產軟件”的公司來說,買一台使用率比較低的機器不太值得。後面將介紹在沒有國產主機情況下,進行軟件開發的兩種替代方法:交叉編譯和QEMU虛擬機。

2. 銀河麒麟是什麼

銀河麒麟操作系統有服務器版本和桌面版本,本文使用的是桌面版本。具體細節看官方的介紹即可,就不做搬運工了。官方說的自主研發、安全可控都不是我們所關心的,我們只需要關心它的內核是什麼,會不會如網上所說根本就是個Ubutun,改個皮膚而已?!。

先用VMware安裝個虛擬機試試吧,網上找了一個只有X86架構的鏡像包Kylin-4.0.2-desktop-sp2_Community-20171127-x86_64.iso,安裝過程略過,使用命令“uname -a”查一下。

Linux wrgz-Lenovo 4.11.0-14-generic #20~16.04.1kord0k1-Ubuntu SMP Wed Oct 18 00:56:13 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

看到Ubuntu就放心了,就當它是個Ubuntu Linux就行了。

3. 飛騰FT2000又是什麼

通俗點講它就是個CPU,再看看飛騰的官網上的描述。FT-2000系列芯片是基於飛騰片上并行系統(PSoC)體繫結構設計的通用微處理器,兼容ARMv8指令集,兼容支持ARM64和ARM32兩種執行模式。哦嚯,划個重點,簡單點看它就是一個ARMv8的64位CPU。

划個不考試的重點:對於應用軟件開發者,簡單理解為是在ARMv8架構上的Ubuntu Linux上進行開發軟件;對於普通辦公者,則理解為是仿Windows的Linux系統。

4. 交叉編譯

本文提到的軟件開發,是使用C/C++開發無界面的應用軟件,實際上開發和測試都有是可以在Ubuntu上進行。但發布軟件則需要真機編譯或者交叉編譯才能運行。

很幸運,在上飛騰官網時,發現了飛騰FT2000的技術文檔FT-2000+64Sv1.1.pdf,裏面有介紹到交叉編譯環境。

  • 安裝Ubuntu16.04(可安裝在虛擬機上或 X86電腦裸機上)
  • 安裝成功后,虛擬機 apt 源修改 修改/etc/apt/source.list 內容為如下:
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial main restricted
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates main restricted
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-backports main restricted universe > multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security main restricted
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security universe
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security multiverse
  • 運行 apt-get update,再運行apt-get install gcc-aarch64-linux-gnu安裝
  • 使用命令aarch64-linux-gnu-gcc –v可以看到gcc版本號為gcc version 5.4.0 20160609

有了交叉編譯器,編譯是很輕鬆的事。經後續測試,交叉編譯出來的程序,可以在國產真機上運行。

5. QEMU虛擬機是什麼

我們經常使用的虛擬機軟件是VMware,擺着這麼好的不用,為什麼選擇QEMU呢。這得從他們的區別說起。

VMware重點於在一個硬件平台下運行多個操作系統,虛擬硬件平台與宿主硬件架構一致,也就是說虛擬機程序中的指令一般就是宿主CPU指令集,可以直接執行,因此一般速度上也就比較快。

QEMU的特點是可以虛擬不同的硬件平台架構,比如在X86機器上虛擬出ARM架構的機器。許多基於ARM指令集的Android手機模擬器是基於Qemu的,很適合無真機情況下進行Android開發。當然執行ARM指令,需要轉換成X86指令才能在宿主機器上運行,這樣速度一般會慢點。

由於本文提到的國產主機就是ARM架構的,VMware並不適用,而QEMU則符合要求。還有一個原因是QEMU支持Windows,只需要一個安裝包,安裝過程簡單,太香了。

6. QEMU安裝銀河麒麟操作系統

無獨有偶,鯤鵬處理器也是ARMv8指令集,在華為官網看到詳細的安裝過程,安裝細節可參考https://www.huaweicloud.com/kunpeng/software/qemu.html。

下面只針對一些重點關注點做些說明。

  • 需要下載一個Arm64架構的麒麟桌面操作系統鏡像包,名字類似Kylin-4.0.2-desktop-sp3-xxxxxxx-arm64.iso。之所以重點提這點,是因為這種鏡像包在網上很難找。有想到用Arm64架構的Ubuntu鏡像包代替,才發現原來官方並沒有提供ARM桌面版的鏡像包(有ARM服務器版)。
  • 原來華為提供的安裝參數有些問題,包括網絡、鼠標、鍵盤參數。這些參數配置不對,會直接影響使用。

QEMU有一個不太人性化的特點,就是沒有提供類似VMware的界面操作,只能通過命令操作,參數還特別多,網上的資料不多,官方文檔都有是英文的。下面給出三個重要的QEMU命令:創建、安裝、啟動。

創建
這個步驟就是創建一個預分配一個大文件,做為虛擬機的磁盤,我比較任性地分配了40G。

c:\qemu\qemu-img.exe create D:\qemu\vm\kylin\hdd01.img 40G

安裝

c:\qemu\qemu-system-aarch64.exe -m 4096 -cpu cortex-a72 -smp 2,cores=2,threads=1,sockets=1 -M virt -bios D:\qemu\bios\QEMU_EFI.fd -net nic,model=pcnet -device nec-usb-xhci -device usb-kbd -device usb-mouse -device VGA -drive if=none,file=D:\software\kylin\Kylin-4.0.2-desktop-sp3-19122616.Z1-arm64.iso,id=cdrom,media=cdrom -device virtio-scsi-device -device scsi-cd,drive=cdrom -drive if=none,file=D:\qemu\vm\kylin\hdd01.img,id=hd0 -device virtio-blk-device,drive=hd0

啟動

c:\qemu\qemu-system-aarch64.exe -m 4096 -cpu cortex-a72 -smp 2,cores=2,threads=1,sockets=1 -M virt -bios D:\qemu\bios\QEMU_EFI.fd -net nic -net tap,ifname=tap0 -device nec-usb-xhci -device usb-kbd -device usb-mouse -device VGA -device virtio-scsi-device -drive if=none,file=D:\qemu\vm\kylin\hdd01.img,id=hd0 -device virtio-blk-device,drive=hd0

安裝和啟動的命令參數差不多,統一說明它們的含義:

參數 說明
qemu-system-aarch64.exe 二進制文件,提供模擬aarch64架構的虛擬機進程
-m 2048 分配2048MB內存
-M virt 模擬成什麼服務器,我們一般選擇virt就可以了,他會自動選擇最高版本的virt
-cpu cortex-a72 模擬成什麼CPU,其中cortex-a53\a57\a72都是ARMv8指令集的
-smp 2,cores=2,threads=1,sockets=1 2個vCPU,這2個vCPU由qemu模擬出的一個插槽(socket)中的2個核心,每個核心支持一個超線程構成
-bios xxx 指定bios bin所在的路徑
-device xxx 添加一個設備,參數可重複
-drive 添加一個驅動器,參數可重複
-net 添加網絡設備

QEMU虛擬機怎麼連網
在Windows上使用qemu虛擬機,使虛擬機能連網,配置方法如下:

  • 在Windows主機上安裝TAP網卡驅動:可下載openvpn客戶端軟件,只安裝其中的TAP驅動;在網絡連接中,會看到一個新的虛擬網卡,屬性類似於TAP-Windows Adapter V9,將其名稱修改為tap0
  • 將虛擬網卡和Windows上真實網卡橋接:選中這兩塊網卡,右鍵,橋接。此時,Windows主機將不能連接互聯網,需要在網橋上配置IP地址和域名等信息,才能使Windows主機連接互聯網。
  • QEMU參數配置:在虛擬機啟動命令行添加以下參數–net nic -net tap,ifname=tap0;tap0為的虛擬網卡名。

7. 總結

國產操作系統的使用體驗已經好了很多,輕度辦公室還是可行的,但想替換Windows,太難了。
QEMU可以虛擬不同的硬件平台架構,是個不錯的虛擬機軟件,而且開源,但在使用體驗方面還是差了一些。

歡迎關注我的公眾號【林哥哥的編程札記】,謝謝!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

寫了個全局變量的bug,被同事們打臉!!!

話說棧長前陣子寫了一個功能,測試 0 bug 就上線了,上線后也運行好好的,好多天都沒有人反饋bug,超爽。。

不出問題還好,出問題就是大問題。。

最近有個客戶反饋某些數據混亂問題,看代碼死活看不出什麼問題,很詭異,再仔細看代碼,原來是一個全局變量的問題,導致在併發情況下出現了線程不安全的問題,事後被同事們打臉!!!

慎用全局變量,我在公司一直在強調,沒想到這麼低級的問題居然發生在自己身上,說起來真的慚愧啊。。

最開始使用的是 Spring 注入對象的方式:

@Autowired
private Object object;

因為 Spring 默認是單例,所以這樣寫是沒有問題的,後來隨着業務的發展,需要多個不同的業務實例,我改成了這種方式:

@Setter
private Object object;

這個 @Setter 是 Lombok 的註解,用來生成 setters 方法,現在想起來,真是低級啊,同時操作的情況下,這個對象肯定會出現覆蓋的情況,從而導致上面說的問題。

寫了一個這麼低級bug,我也不怕不好意思發出來,大家都謹記一下吧。

另外,我再總結幾個慎用全局變量的場景:

1、SimpleDateFormat

SimpleDateFormat 禁止定義成 static 變量或者全局共享變量,因為它是線程不安全的,都被寫進阿里巴巴的《Java開發手冊》里了:

為什麼說 SimpleDateFormat 不是線程安全的呢?

來看下它的 format 方法源碼:

可以看到 calendar 變量居然也是全局變量,多線程情況下就會存在設置臟變量的情況。

所以,如果要用 SimpleDateFormat,就在每次用的時候都創建一個 SimpleDateFormat 對象,做到線程間隔離。

2、資源連接

資源連接包括數據庫連接、FTP連接、Redis連接等,這種也要慎用全局變量,一量使用全局變量,就會遇到以下問題:

1)關閉連接的時候,就可能把別人正在操作的連接給關了,導致其他線程的業務中斷;

2)因為是全局變量,創建的時候可能會創建多個實例,在關閉連接的時候,就可能只關閉了一個對象的連接,造成其他連接沒有被關閉,最後導致連接耗光系統不可用;

3、数字運算

這也是個很經典的問題了,如果要用多線程對一個数字進行累加等其他運算處理,千萬不要用全局基礎類型的變量,如下所示:

private long count;

多線程情況下,某個線程獲取到的值可能已經被其他線程修改了,最後得到的值就不準確了。

當然,上面的示例可以通過加鎖的方式來解決,也可以使用全局的原子類(java.util.concurrent.atomic.Atom*)進行處理,比如:

private AtomicInteger count = new AtomicInteger();

注意,這種原子類使用全局變量就沒有線程安全的問題,它使用了 CAS 算法保證了數據一致性。

不過,阿里推薦使用LongAdder,因為性能更好:

java.util.concurrent.atomic.LongAdder

4、全局session

來看下面的例子:

@Autowired
protected HttpSession session;

全局注入一個 Session 對象,在 Spring 中,這樣全局注入使用上面是默認沒問題的,包括 request, response 對象,都可以通過全局注入來獲取。

這樣會存在線程安全性嗎?

不會!

使用這種方式,當 Bean 初始化時,Spring 並沒有注入真實對象,而是注入了一個代理對象,真正使用的時候通過該代理對象獲取真正的對象。

並且,在注入此類對象時,Spring使用了線程局部變量(ThreadLocal),這就保證了 request/response/session 對象的線程安全性了。

具體就不展開了,詳細的介紹及測試大家可以點擊這個鏈接查看這篇文章。

既然是線程安全,但也得小心,如果我在方法中主動使 session 對象失效並重建了:

session.invalidate();
session = request.getSession();

這樣,session對象就變成了真實對象了,不再是代理對象,就變成了文章最開始的時候我說的那種多線程安全問題了,如果線上出現 session 會話混亂,用戶 A 就可能看到用戶 B 的數據,你想想可不可怕?

所以,即使可以這樣使用,也得千萬小心謹慎,最好是在方法級別使用這些對象。

總結

今天,棧長總結了一下我是怎麼寫出這個全局變量的低級 bug,也總結了下慎用全局變量的 4 種情況,相信大家多多少都遇到過類似的問題,希望能幫助大家少踩坑。

全局變量雖好,但我們也得謹慎使用啊,一定要考慮是否引起多線程安全問題,不然會引起重大問題。

你還遇到過哪些全局變量的問題,歡迎留言分享哦!

推薦去我的博客閱讀更多:

1.Java JVM、集合、多線程、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、後端、架構、阿里巴巴等大廠最新面試題

覺得不錯,別忘了點贊+轉發哦!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

豪雨引發土石流洪災 印度尼泊爾過去1週至少41死

摘錄自2020年8月31日中央社報導

尼泊爾和印度官員今(31日)表示,過去一週豪雨導致洪水及土石流,兩國共有至少41人喪命。南亞年度雨季進入最後階段,在各國已奪走數百人命。

尼泊爾內政部官員表示,西部偏遠地區昨天暴雨導致土石流,埋沒5戶住家,並造成10人死亡,死者包括4名孩童。路透社報導,多山的尼泊爾今年至少已269人死於土石流與洪水,另有76人失蹤。

印度國家緊急應變中心也表示,西部古茶拉底省(Gujarat)過去2天內已有14人因與大雨和洪水相關事故喪命。在東部的奧里薩省(Odisha),過去1週洪水也奪走至少17條人命,造成數以千計民眾流離失所,影響逾50萬人。

氣候變遷
國際新聞
印度
尼泊爾
豪雨
土石流
洪災

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

日貨輪觸礁漏油毀生態 模里西斯擬求償9.9億

摘錄自2020年9月1日自由時報報導

日本大型貨輪「若潮號」(WAKASHIO)7月在模里西斯近海觸礁,超過千噸燃油外洩造成當地生態浩劫,模里西斯政府計畫求償13.4億模里西斯盧比(約新台幣9.95億元)。另外,協助清理漏油的拖船昨晚(31日)撞上駁船,造成2死2失蹤。

綜合外媒報導,日本商船三井集團營運的巴拿馬籍大型散貨輪「若潮號」7月25日觸礁時,載有約3800噸燃油,據悉,有1000多噸燃油外洩海上,被污染的沿海地區已禁止捕魚,模里西斯政府也在8月7日宣布進入環境緊急狀態,擔心瀕物種恐受到影響。

根據模里西斯政府文件顯示的擬議計畫,當局將向日方求償13.4億模里西斯盧比,這筆錢將用來支持受到漏油影響的當地漁民社區,其中12億盧比用於建造100艘漁船、970萬盧比用於為475名漁民和60名船長提供培訓。

污染治理
國際新聞
模里西斯
燃油外洩
漏油事件
觸礁

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

深圳強制垃圾分類 罰款最高216萬

摘錄自2020年9月2日中央社報導

鄰近香港的中國大陸深圳市昨(1日)起強制實施垃圾分類,違反的話最高罰款人民幣50萬元(新台幣216萬元)。

深圳新聞網今(2日)報導,深圳昨天起實施「深圳市生活垃圾分類管理條例」,羅湖區銀龍花園物業負責人收到首張整改通知書,原因是社區沒有設置分類投放點,且大件垃圾未及時清運。

報導表示,深圳對相關處罰分為個人和單位兩類,其中對運輸類垃圾的收運單位處5萬元以上50萬元以下罰款;個人的生活垃圾處50元罰款,情節嚴重的罰200元。

2019年起,大陸一些城市開始推動垃圾分類,以改善環境,提升城市生態水平。

污染治理
國際新聞
中國
垃圾分類

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益