跳到主要內容

系統化學習多線程(一)

大綱



-------------------------學前必讀----------------------------------


學習不能快速成功,但一定可以快速入門
整體課程思路:
1.實踐為主,理論化偏少
2.課程筆記有完整的案例和代碼,(為了學習效率)再開始之前我會簡單粗暴的介紹知識點案例思路,
有基礎的同學聽了之後可以直接結合筆記寫代碼,
如果沒聽懂再向下看視頻,我會手把手編寫代碼和演示測試結果;
3.重要提示,學編程和學游泳一樣,多實踐學習效率才高,理解才透徹;
4.編碼功底差的建議每個案例代碼寫三遍,至於為什麼...<<賣油翁>>...老祖宗的智慧


-------------------------------------------------------------------------


 1.線程


1.1.什麼是線程


線程(英語:thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程并行執行不同的任務。在Unix System V及SunOS中也被稱為輕量進程(lightweight processes),但輕量進程更多指內核線程(kernel thread),而把用戶線程(user thread)稱為線程。(來自百度百科)


一個進程可以有很多線程,每條線程并行執行不同的任務。


1.2.多線程hello word


需求:模擬在計算上一邊聽歌一邊打遊戲


三種實現方案如下:

TestDemo
 1 package com.wfd360.thread;
2
3 import com.wfd360.thread.demo01.GameRunnable;
4 import com.wfd360.thread.demo01.MusicRunnable;
5 import com.wfd360.thread.demo02.GameThread;
6 import com.wfd360.thread.demo02.MusicThread;
7 import org.junit.Test;
8
9 /**
10 * @author 姿勢帝-博客園
11 * @address https://www.cnblogs.com/newAndHui/
12 * @WeChat 851298348
13 * @create 05/03 5:27
14 * @description 需求分析:
15 * 1.模擬一邊打遊戲一邊聽音樂,在控制台打印輸出模擬
16 * 2.把兩個業務封裝成獨立的線程,實現接口Runnable或繼承Thread,通過看源碼你會發現Thread類實現了接口Runnable,使用本質上這兩種方法時一樣的。
17 * 3.Thread類提供兩個方法,線程主題方法run,啟動線程方法start
18 */
19 public class TestDemo {
20 /**
21 * 方式1:實現接口Runnable
22 */
23 @Test
24 public void testRunnable() throws InterruptedException {
25 System.out.println("-------test start-------");
26 // 實例對象
27 MusicRunnable music = new MusicRunnable();
28 GameRunnable game = new GameRunnable();
29 // 創建線程
30 Thread musicThread = new Thread(music);
31 Thread gameThread = new Thread(game);
32 // 啟動線程
33 musicThread.start();
34 gameThread.start();
35 System.out.println("--------等待其他線程執行--------------");
36 Thread.sleep(5 * 1000);
37 System.out.println("-------test end-------");
38 }
39
40 /**
41 * 方式2:繼承Thread
42 */
43 @Test
44 public void testThread() throws InterruptedException {
45 System.out.println("-------test start-------");
46 // 創建線程
47 MusicThread musicThread = new MusicThread();
48 GameThread gameThread = new GameThread();
49 // 啟動線程
50 musicThread.start();
51 gameThread.start();
52 System.out.println("--------等待其他線程執行--------------");
53 Thread.sleep(5 * 1000);
54 System.out.println("-------test end-------");
55 }
56
57 /**
58 * 方式3:簡寫,這種寫法一般我們在做模擬測試的使用,在正式代碼中建議不使用,可讀性較差
59 */
60 @Test
61 public void testThreadSimple() throws InterruptedException {
62 System.out.println("-------test start-------");
63 // 創建線程
64 Thread musicThread = new Thread(() -> {
65 for (int i = 0; i < 100; i++) {
66 System.out.println("=======聽音樂中============" + i);
67 }
68 });
69 Thread gameThread = new Thread(() -> {
70 for (int i = 0; i < 100; i++) {
71 System.out.println("=======打遊戲中============" + i);
72 }
73 });
74 // 啟動線程
75 musicThread.start();
76 gameThread.start();
77 System.out.println("--------等待其他線程執行--------------");
78 Thread.sleep(5 * 1000);
79 System.out.println("-------test end-------");
80 }
81 }



 實現接口Runnable


 1 package com.wfd360.thread.demo01;
2
3 /**
4 * @author 姿勢帝-博客園
5 * @address https://www.cnblogs.com/newAndHui/
6 * @WeChat 851298348
7 * @create 05/03 5:31
8 * @description
9 */
10 public class GameRunnable implements Runnable {
11 @Override
12 public void run() {
13 for (int i = 0; i < 100; i++) {
14 System.out.println("=======打遊戲中============" + i);
15 }
16 }
17 }

GameRunnable
 1 package com.wfd360.thread.demo01;
2
3 /**
4 * @author 姿勢帝-博客園
5 * @address https://www.cnblogs.com/newAndHui/
6 * @WeChat 851298348
7 * @create 05/03 5:29
8 * @description
9 */
10 public class MusicRunnable implements Runnable {
11 @Override
12 public void run() {
13 for (int i = 0; i < 100; i++) {
14 System.out.println("=======聽音樂中============"+i);
15 }
16 }
17 }

MusicRunnable

 繼承Thread


 1 package com.wfd360.thread.demo02;
2
3 /**
4 * @author 姿勢帝-博客園
5 * @address https://www.cnblogs.com/newAndHui/
6 * @WeChat 851298348
7 * @create 05/03 6:00
8 * @description
9 */
10 public class GameThread extends Thread {
11 @Override
12 public void run() {
13 for (int i = 0; i < 100; i++) {
14 System.out.println("-------遊戲中----------"+i);
15 }
16 }
17 }

GameThread GameThread
 1 package com.wfd360.thread.demo02;
2
3 /**
4 * @author 姿勢帝-博客園
5 * @address https://www.cnblogs.com/newAndHui/
6 * @WeChat 851298348
7 * @create 05/03 6:00
8 * @description
9 */
10 public class MusicThread extends Thread {
11 @Override
12 public void run() {
13 for (int i = 0; i < 100; i++) {
14 System.out.println("-------音樂中----------" + i);
15 }
16 }
17 }



 總結


啟動線程兩種方式:


    1.通過繼承Thread類


    2.實現Runnable接口


 使用哪種方式更好?


區別: 


一個類如果繼承了其他類,就無法在繼承Thread類,在Java中,一個類只能繼承一個類,而一個類如果實現了一個接口,還可以實現其他接口,接口是可以多實現的,所以說


Runable的擴展性更強,但是繼承的方式更簡單,個人建議一般情況下使用Thread;


實現接口Runnable或繼承Thread,通過看源碼你會發現Thread類實現了接口Runnable,使用本質上這兩種方法是一樣的

啟動線程流程:


    創建啟動線程的方式一:繼承Thread類


       1.將業務方法封裝成線程對象,自定義類t extends Thread類; 


       2.覆寫run方法: 覆寫第一步中的run方法;


       3.創建自定義對象t


       4.啟動線程 t.start();


   創建啟動線程方式二:實現Runnable接口


      1.將業務方法封裝成線程對象,自定義類t implements Runnable接口;


      2.實現第一步中的run方法


      3.創建自定義對象t


      4.啟動線程 new Thread(t).start();


1.3.對主線程與創建線程執行順序的理解


問題:
直接寫一個簡單的HelloWorld 程序,有沒有線程?
==>有一個主線程,在垃圾回收的時候,有gc 線程。


 1 package com.wfd360.thread;
2
3 import org.junit.Test;
4
5 /**
6 * @author 姿勢帝-博客園
7 * @address https://www.cnblogs.com/newAndHui/
8 * @WeChat 851298348
9 * @create 05/04 11:09
10 * @description <p>
11 * 問題:
12 * 直接寫一個簡單的HelloWorld 程序,有沒有線程?
13 * ==>有一個主線程,在垃圾回收的時候,有gc 線程。
14 * 結論:一旦線程啟動起來之後就是獨立的,和創建環境沒有關係;
15 * 啟動線程不能直接調用run方法,必須調用start方法;
16 * </p>
17 */
18 public class TestDemo02 {
19 /**
20 * 如果把創建線程放在循環語句的 下 面,會交替出現嗎
21 * ==>否,因為主線程執行完成后才會啟動hello線程
22 *
23 * @throws Exception
24 */
25 @Test
26 public void test1() throws Exception {
27 System.out.println("---test start-------");
28 // 執行主線程
29 for (int i = 0; i < 100; i++) {
30 System.out.println("-----test1--------" + i);
31 }
32 // 啟動hello線程
33 new HelloThread().start();
34 System.out.println("=======等待執行完成===========");
35 Thread.sleep(5 * 1000);
36 System.out.println("---test end-------");
37 }
38
39 /**
40 * 如果把創建線程放在循環語句的 上 面,會交替出現嗎
41 * ==>可能會,可能不會,可能出現for循環完之後,線程還沒有啟動完;
42 *
43 * @throws Exception
44 */
45 @Test
46 public void test2() throws Exception {
47 System.out.println("---test start-------");
48 // 啟動hello線程
49 new HelloThread().start();
50 // 執行主線程
51 for (int i = 0; i < 100; i++) {
52 System.out.println("-----test1--------" + i);
53 }
54 System.out.println("=======等待執行完成===========");
55 Thread.sleep(5 * 1000);
56 System.out.println("---test end-------");
57 }
58
59 /**
60 * 採用內部類的方式定義一個hello線程對象
61 */
62 class HelloThread extends Thread {
63 @Override
64 public void run() {
65 for (int i = 0; i < 100; i++) {
66 System.out.println("-----HelloThread--------" + i);
67 }
68 }
69 }
70 }

TestDemo02

結論:一旦線程啟動起來之後就是獨立的,和創建環境沒有關係;
啟動線程不能直接調用run方法,必須調用start方法;


 1.4.對sleep方法的理解


package com.wfd360.thread;

/**
*
@author 姿勢帝-博客園
* @address
https://www.cnblogs.com/newAndHui/
* @WeChat 851298348
* @create 05/04 11:34
* @description <p>
* Thread類的方法:
* static void sleep(long millis) 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行);
* </p>
*/
public class TestSleep {
/**
* 做一個簡易倒計時,10秒鐘,控制台每一秒輸出一個数字,如10,9,8,7.....0
*/
public static void main(String[] args) throws Exception {
System.out.println(
"---test start-------");
for (int i = 10; i >= 0; i--) {
Thread.sleep(
1 * 1000);
System.out.println(i);
}
System.out.println(
"---test end-------");
}
}

1.5.線程名稱的設置與獲取


繼承方式


簡單需求:使用多線程模擬多窗口售票


 1 package com.wfd360.thread.demo03Ticket;
2
3 /**
4 * @author 姿勢帝-博客園
5 * @address https://www.cnblogs.com/newAndHui/
6 * @WeChat 851298348
7 * @create 05/04 11:55
8 * @description <p>
9 * 模擬多線程售票
10 * </p>
11 */
12 public class TicketThread extends Thread {
13 // 假定票總是100張
14 private static Integer num = 100;
15
16 @Override
17 public void run() {
18 // 只要有票就一直售票
19 while (num > 0) {
20 System.out.println("正在出售第" + num + "張票");
21 --num;
22 }
23 System.out.println("===售票結束===");
24 }
25 }

TicketThread

test


/**
* 測試模擬三個窗口售票
*
@throws InterruptedException
*/
@Test
public void testTicketThread() throws InterruptedException {
System.out.println(
"---test start-------");
// 模擬多3個窗口售票
TicketThread ticketThread1 = new TicketThread();
TicketThread ticketThread2
= new TicketThread();
TicketThread ticketThread3
= new TicketThread();
// 啟動線程售票
ticketThread1.start();
ticketThread2.start();
ticketThread3.start();
System.out.println(
"======等待售票============");
Thread.sleep(
5 * 1000);
System.out.println(
"---test end-------");
}

結果:



1.在售票過程中不能區分售出的票是那個窗口售出的,解決通過線程名稱判斷


2.有重複售出的票(後面的線程同步解決)


解決第一個問題,設置獲取線程名稱,通過Thread對象裏面自帶的getName,setName方法



 具體代碼


設置線程名稱



 獲取線程名稱



 上面講了繼承的方式獲取線程名稱,那麼實現接口Runnable的方式怎麼獲取設置勒


繼承Thread的方式,可以通過getName的方式獲取當前線程的名稱?
那使用Runnable的方式,能通過getName獲取嘛?


getName方法是Thread類的,但是TicketThread現在並沒有繼承Thread類,而是實現了Runnable接口.


問題:如果實現Runnable接口,怎麼獲取線程名稱?


 思考:TicketThread類裏面的代碼要執行,它肯定存在於某個線程中, 就比如寫個helloword打印語句,是不是也處於一個主線程中,那這裏怎麼獲取線程名稱?
通過動態獲取,當程序正在執行的時候,獲取當前正在執行的線程名稱。怎麼獲取?
在Thread類裏面有個靜態的方法currentThread() 方法,返回當前正在執行的線程引用;


Thread.currentThread().getName


那怎麼設置線程名稱?


Thread類裏面有個name字段,相當於Thread類把它包裝了一下:


通過源碼可以發現,構造方法裏面還有可以傳一個名字:



具體實現代碼如下



 


 總結:


繼承方式設置\獲取線程名稱通過 Thread對象裏面的 setName,getName方法;


實現接口方式設置名稱通過 new Thread('線程實例對象', "線程名稱"),獲取線程名稱通過:Thread.currentThread().getName


1.6.Thread的join方法


void join() 方法 :等待該線程終止
void join(long millis) 方法 :等待該線程終止的時間最長為millis毫秒


需求: 當主線程運行到20的時候(i =20)的時候,讓JoinThread線程加進來直到執行完成,在執行主線程.


 1 package com.wfd360.thread;
2
3 import org.junit.Test;
4
5 /**
6 * @author 姿勢帝-博客園
7 * @address https://www.cnblogs.com/newAndHui/
8 * @WeChat 851298348
9 * @create 05/04 6:31
10 * @description
11 */
12 public class Test05Join {
13 /**
14 * 需求:
15 * 當主線程for循環到i=20時,等JoinThread線程執行完成后,在執行for循環的線程
16 * @throws InterruptedException
17 */
18 @Test
19 public void testJoinThread() throws InterruptedException {
20 System.out.println("---test start-------");
21 // 開啟線程
22 JoinThread thread = new JoinThread();
23 thread.start();
24 // 循環打印線程
25 for (int i = 0; i < 100; i++) {
26 System.out.println("======testJoinThread=========="+i);
27 Thread.sleep(1);
28 if (i==20){
29 // 等線程JoinThread執行完成
30 thread.join();
31 }
32 }
33 System.out.println("=============等待線程執行完成===================");
34 Thread.sleep(10*1000);
35 System.out.println("---test end-------");
36 }
37
38 class JoinThread extends Thread {
39 @Override
40 public void run() {
41 for (int i = 0; i < 100; i++) {
42 System.out.println("=====JoinThread=======" + i);
43 // 模擬處理很多業務耗時1毫秒
44 try {
45 Thread.sleep(1);
46 } catch (InterruptedException e) {
47 e.printStackTrace();
48 }
49 }
50 }
51 }
52 }

Test05Join

1.7.線程優先級


直接上代碼


 1 package com.wfd360.thread;
2
3 /**
4 * @author 姿勢帝-博客園
5 * @address https://www.cnblogs.com/newAndHui/
6 * @WeChat 851298348
7 * @create 05/05 8:17
8 * @description <p>
9 * 1.==>線程優先級的理解:
10 * 線程的優先級和生活中類似,高優先級線程的執行優先於低優先級線程;
11 * 並不是絕對的,可能優先級高的線程優先 比 優先級低的線程先執行,只能說,高優先級的線程優先執行的幾率更多;
12 * (比如兩個線程,一個優先級高,一個優先級低,如果一共運行一個小時,優先級高的線程執行遠遠大於優先級低的但是並不是說優先級高的先執行完,
13 * 在執行優先級低的)
14 * 2.==>重新設置線程優先級
15 * int getPriority() 返回線程的優先級。
16 * void setPriority(int newPriority) 更改線程的優先級。Java線程的優先級從1到10級別,值越大優先級越高.
17 * 3.==>線程的默認優先級受創建線程的環境影響,默認值5,自定義線程的默認優先級和創建它的環境的線程優先級一致
18 * </p>
19 */
20 public class Test06Priority {
21 /**
22 * 測試獲取線程優先級,設置線程優先級,驗證線程優先級受創建環境影響
23 * @param args
24 */
25 public static void main(String[] args) {
26 Thread threadMain = Thread.currentThread();
27 // 獲取默認優先級数字
28 System.out.println("main線程默認優先級:" +threadMain.getPriority());// 5
29 // 重新設置默認優先級数字
30 threadMain.setPriority(8);
31 // 再次重新獲取優先級数字
32 System.out.println("main線程修改后的優先級:" +threadMain.getPriority());// 8
33 // 創建一個線程查看優先級
34 Thread thread = new Thread();
35 System.out.println("thread線程的優先級:" +thread.getPriority());// 8 受創建環境影響
36 }
37 }

1.8.後台線程,即守護線程


直接看代碼


 1 package com.wfd360.thread;
2
3 import com.wfd360.thread.demo04Daemon.DaemonThreaad;
4
5 /**
6 * @author 姿勢帝-博客園
7 * @address https://www.cnblogs.com/newAndHui/
8 * @WeChat 851298348
9 * @create 05/05 9:12
10 * @description <p>
11 * 後台線程,即守護線程
12 * 後台線程:指為其他線程提供服務的線程,也稱為守護線程。JVM的垃圾回收線程就是一個後台線程。
13 * 需求:嘗試把線程標記為後台線程或者標記為(前台)線程;
14 * Thread類提供的方法:
15 * 方法1: void setDaemon(boolean on) 將該線程標記為守護線程或用戶線程,true為後台線程,false為用戶線程(前台線下)
16 * 怎樣測試該線程是否是守護線程?
17 * 方法2:isDaemon() 測試該線程是否為守護線程. true為後台線程,false為用戶線程(前台線下)
18 * <p>
19 * 結論1:活動的線程(已經在執行的線程t.start())不能設置後台線程,即主線程不能設置為後台線程。
20 * 結論2: 自定義線程的默認狀態和環境有關,後台線程中創建的線程默認是後台線程,前台線程中創建的線程為前台線程.
21 * 結論3: 前台線程執行完后,會直接關閉後台線程,即自定義的後台線程不一定能執行完成
22 * </p>
23 */
24 public class Test07Daemon {
25 /**
26 * 測試1
27 * 查看主線程的狀態,嘗試更改
28 * 結論:活動的線程不能設置為後台線程
29 *
30 * @param args
31 */
32 public static void main1(String[] args) {
33 Thread threadMain = Thread.currentThread();
34 System.out.println("是後台線程么:" + threadMain.isDaemon());// false
35 threadMain.setDaemon(true); // 報錯,活動的線程不能設置為後台線程
36 System.out.println("修改后是後台線程么:" + threadMain.isDaemon());
37 }
38
39 /**
40 * 測試2
41 * 查看主線程中 創建線程的狀態,嘗試更改;
42 *
43 * @param args
44 */
45 public static void main2(String[] args) {
46 Thread thread = new Thread();
47 // false
48 System.out.println("是後台線程么:" + thread.isDaemon());
49 // 修改為後台線程
50 thread.setDaemon(true);
51 System.out.println("修改后是後台線程么:" + thread.isDaemon());
52 }
53
54 /**
55 * 測試3
56 * 查看主線程中 創建線程的狀態,嘗試更改,讓線程處於活動狀態在修改->報錯;
57 *
58 * @param args
59 */
60 public static void main3(String[] args) {
61 DaemonThread thread = new DaemonThread();
62 // 讓線程處於活躍狀態
63 thread.start();
64 // false
65 System.out.println("是後台線程么:" + thread.isDaemon());
66 // 修改為後台線程,報錯,當前已經是活躍狀態(thread.start())不能修改為後台線程
67 thread.setDaemon(true);
68 System.out.println("修改后是後台線程么:" + thread.isDaemon());
69 }
70
71 /**
72 * 測試4
73 * 前台線程執行完后,會直接關閉後台線程,即如果後台線程不一定能執行完成
74 * 可以通過修改等待執行時間來觀察DaemonThread線程的數組輸出變化
75 *
76 * @param args
77 */
78 public static void main(String[] args) throws InterruptedException {
79 DaemonThread thread = new DaemonThread();
80 // 修改為後台線程
81 thread.setDaemon(true);
82 // 讓線程處於活躍狀態
83 thread.start();
84 System.out.println("========等待後台線程執行============");
85 Thread.sleep(5 * 1000);
86 }
87 }

 1 package com.wfd360.thread.demo04Daemon;
2
3 /**
4 * @author 姿勢帝-博客園
5 * @address https://www.cnblogs.com/newAndHui/
6 * @WeChat 851298348
7 * @create 05/05 9:27
8 * @description
9 */
10 public class DaemonThread extends Thread {
11 @Override
12 public void run() {
13 for (int i = 0; i < 10; i++) {
14 System.out.println("===="+i);
15 try {
16 Thread.sleep(1000);
17 } catch (InterruptedException e) {
18 e.printStackTrace();
19 }
20 }
21 }
22 }

DaemonThread

線程基礎相關的方法定義就先到這裏,下一篇我們將進入線程同步.


https://www.cnblogs.com/newAndHui/p/12831089.html


系統化的在線學習:點擊進入學習

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

【其他文章推薦】



網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!



※台北網頁設計公司全省服務真心推薦



※想知道最厲害的網頁設計公司"嚨底家"!



※推薦評價好的iphone維修中心



網頁設計最專業,超強功能平台可客製化



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



Orignal From: 系統化學習多線程(一)

留言

這個網誌中的熱門文章

有了四步解題法模板,再也不害怕動態規劃!(看不懂算我輸)

導言 動態規劃問題一直是算法面試當中的重點和難點,並且動態規劃這種通過空間換取時間的算法思想在實際的工作中也會被頻繁用到,這篇文章的目的主要是解釋清楚 什麼是動態規劃 ,還有就是面對一道動態規劃問題,一般的 思考步驟 以及其中的注意事項等等,最後通過幾道題目將理論和實踐結合。 什麼是動態規劃 如果你還沒有聽說過動態規劃,或者僅僅只有耳聞,或許你可以看看 Quora 上面的這個 回答 。 How to explain dynamic 用一句話解釋動態規劃就是 " 記住你之前做過的事 ",如果更準確些,其實是 " 記住你之前得到的答案 "。 我舉個大家工作中經常遇到的例子。 在軟件開發中,大家經常會遇到一些系統配置的問題,配置不對,系統就會報錯,這個時候一般都會去 Google 或者是查閱相關的文檔,花了一定的時間將配置修改好。 過了一段時間,去到另一個系統,遇到類似的問題,這個時候已經記不清之前修改過的配置文件長什麼樣,這個時候有兩種方案,一種方案還是去 Google 或者查閱文檔,另一種方案是借鑒之前修改過的配置,第一種做法其實是萬金油,因為你遇到的任何問題其實都可以去 Google,去查閱相關文件找答案,但是這會花費一定的時間,相比之下,第二種方案肯定會更加地節約時間,但是這個方案是有條件的,條件如下: 之前的問題和當前的問題有着關聯性,換句話說,之前問題得到的答案可以幫助解決當前問題 需要記錄之前問題的答案 當然在這個例子中,可以看到的是,上面這兩個條件均滿足,大可去到之前配置過的文件中,將配置拷貝過來,然後做些細微的調整即可解決當前問題,節約了大量的時間。 不知道你是否從這些描述中發現,對於一個動態規劃問題,我們只需要從兩個方面考慮,那就是 找出問題之間的聯繫 ,以及 記錄答案 ,這裏的難點其實是找出問題之間的聯繫,記錄答案只是順帶的事情,利用一些簡單的數據結構就可以做到。 概念 上面的解釋如果大家可以理解的話,接    動態規劃 算法是通過拆分問題,定義問題狀態和狀態之間的關係,使得問題能夠以遞推(或者說分治)的方式去解決。它的幾個重要概念如下所述。    階段: 對於一個完整的問題過程,適當的切分為若干個相互聯繫的子問題,每次在求解一個子問題...

計算機本地文件快要滅絕了

   編者按: 文件是数字世界的基石,是我們基本的工作單位。但是,隨着互聯網的雲化、平台化、服務化,文件日益變得可有可無。這樣一種改變究竟好不好呢?喜歡懷舊的 Simon Pitt 開始回顧各種文件的好處,哪怕這讓他顯得不合時宜。原文發表在 medium 上,標題是:Computer Files Are Going Extinct   我喜歡文件。我喜歡對文件重命名、移動、排序,改變它們在文件夾中的显示方式,去備份文件,將之上傳到互聯網,恢復它們,對其進行複製,甚至還可以對文件進行碎片整理。作為信息存儲方式的一種隱喻,在我看來文件是很出色的。我喜歡把文件當作一個工作單位。如果我要寫篇文章,文章會放在文件裏面。如果我要生成圖像,圖像會保存進文件裏面。    謳歌 files.doc   文件是擬物化的。這是個很花哨的詞,只是用來表示文件是反映現實物品的一個数字概念。比方說,Word 文檔就像一張紙,躺在你的辦公桌上(desktop)。JPEG 就像一幅畫,等等。它們每個都有一個小圖標,圖標的樣子看起來像它們所代表的現實物品。一堆紙,一個畫框,一個馬尼拉文件夾。真的挺很迷人的。   我喜歡文件的一點是,不管裏面有什麼,跟文件的交互方式總是一致的。我上面提到的那些東西——複製、排序、碎片整理——我可以對任何文件進行那些處理。文件可能是圖像、遊戲的一部分、也可能是我最喜歡的餐具清單。碎片整理程序不在乎它是什麼。它不會去判斷內容。   自從我開始在 Windows 95 裏面創建文件以來,我就一直都很喜歡文件。但是我注意到我們已經開始慢慢地遠離把文件當作基本工作單位的做法。 Windows95。我的計算機    services.mp3 的興起   十幾歲的時候,我開始痴迷於收集和管理数字音樂:我收藏 MP3 文件。一大堆的 128 kbps MP3 文件。如果你足夠幸運,有自己的 CD 刻錄機的話,就可以將它們刻錄到 CD 上,然後在朋友之間傳遞。一張 CD 可以容納 700 MB。這相當於將近 500 張軟盤!   我會仔細端詳我的收藏,然後煞費苦心地給它們添加上 IDv1 和 IDv2 音樂標籤。隨着時間的流逝,大家開始開發可以在雲端自動獲取曲目列表的工具,這樣你就可以檢查和驗證 MP3 的質量。有時候我甚至會去聽那些該死的東西,儘管...

純電動 Mini Cooper SE 將成為中國國產車,年產 16 萬輛

BMW 集團與中國長城汽車合資,將於江蘇建立新廠,專門投入生產 MINI Cooper SE 和部分長城品牌電動車,預計於 2022 年完工並投入生產,每年將可生產 16 萬輛電動車。 靈動可愛的 Mini Cooper,在許多車迷心中都有著特殊的地位,今年 7 月發表了首款純電動版本的 Mini Cooper SE 之後,獲得熱烈迴響,預訂數量已接近 8 萬台,顯示大家對於純電 Mini 的熱愛,因為油電版的 Mini Cooper Countryman 的全球總銷售量也才 3 萬出頭。 Mini Cooper SE 之前公布了官方定價,最低從 27,900 歐元起算,美國售價約 29,900 美元。相比現有的三門款,只貴了一成左右。然而,三年後,中國消費者將有機會買到最便宜的電動 Mini。 電動 Mini Cooper SE 最低價是 27,900 歐元,扣掉全額補助最低可以到 24,400 歐元。 BMW 集團與中國長城汽車集團於 2018 年宣布,將組建合資公司光束汽車,投入在中國的電動車生產計畫,而現在他們正式宣布啟動計畫,於江蘇張家港打造一個新工廠,全部投入電動車的製造,包括了 Mini Cooper SE 和其他長城汽車旗下的電動車。 目前的電動 Mini 只在英國牛津工廠製造,不難想像當產能轉移到中國後,Mini Cooper SE 的價格將有機會進一步調降,來競爭全球最大的電動車市場。這座屬於合資公司光束汽車的新工廠,採用一個新的產銷模式,由 BMW 和長城共同合作開發、設計、製造新產品,但是銷售通路完全沿用原本的品牌渠道。 換句話說,2020 年到 2022 年銷售的電動 Mini,將會是英國製造,而 2022 年後就會有中國製造版本開賣,考量到 Mini 在中國每年約有 30 萬輛的銷售額,同時油電版的 Coutryman 銷量更佔了全球將近五分之一,無怪乎 BMW 會想在最接近主要市場的地方蓋工廠囉。 外型完美復刻油車版 最後,簡單介紹一下 Mini Cooper SE 這台車。Mini 在電動化的路上,盡力保持著跟經典造型一致的設計,畢竟大家愛的就是它的設計。電動版的 Mini 車頭、車身跟車屁股都多了一個黃色的插頭標誌,車頭的氣壩則變成封閉式設計,除此之外,幾乎看不出來差別,連馬達...