1. 程式人生 > >JavaSE基礎學習筆記及案例(一)IO流與多執行緒(上)

JavaSE基礎學習筆記及案例(一)IO流與多執行緒(上)

IO流

1. IO流知識點
IO流(字元輸入流FileReader) 位元組輸入流 FileInputStream

IO流(字元輸出流FileWriter) 位元組輸出流 FileOutputStream

字元緩衝區輸入流( BufferedReader)
位元組緩衝區輸入流BufferedInputStream

字元緩衝區輸出流( BufferedWriter)
位元組緩衝區輸出流BufferedOutputStream

2. 實戰案例
案例2.1 將鍵盤錄入拷貝到當前專案下的text.txt中,鍵盤錄入資料遇到quit時就退出

public class demo12 {
     /**
      * @param args
      * 將鍵盤錄入拷貝到當前專案下的text.txt中,鍵盤錄入資料遇到quit時就退出
      *
      * 1.建立鍵盤錄入物件
      * 2.建立輸出流物件,關聯text.txt檔案
      * 3.定義無限迴圈
      * 4.遇到quit退出
      * 5.沒有quit就將內容寫出
      * 6.關流
      * @throws IOException
      */
     public static void main(String[] args) throws IOException{
          Scanner sc = new Scanner(System.in);//建立鍵盤錄入物件
          //2.建立輸出流物件,關聯text.txt
          FileOutputStream fos = new FileOutputStream("text.txt");
          //3.定義無限迴圈
          while(true){
              String nl = sc.nextLine();//將鍵盤錄入物件寫在line中
              //4.遇到quit退出
              if("quit".equals(nl)){
                   break;
              }else{//5.沒有quit就將內容寫出
                   fos.write(nl.getBytes());//字串必須轉換成位元組陣列
                   fos.write("\r\n".getBytes());
              }
              fos.close();
          }
     }
}

結果:將111111.txt寫入到了text檔案中。。。。。。。。。。。。。。。
***案例2.2***模擬收費軟體試用次數

public class test {
     /**
      * @param args
      * 模擬試用版軟體,10次機會
      *
      * 1.建立帶緩衝的輸入流物件,使用readLine方法,保證資料的原樣性
      * 2.將讀到的字串轉換成int數;
      * 3.對int數進行判斷,如果大於0,將其--寫回去,如果不大於0,提示請購買正版
      * 4.在if判斷將要--的結果列印,並將結果通過輸出流寫到檔案上
      * @throws IOException
      */
     public static void main(String[] args) throws IOException{
          //1.建立帶緩衝的輸入流物件,使用readLine方法,保證資料的原樣性
          BufferedReader br = new BufferedReader(new FileReader("config.txt"));
          //2將讀到的字串轉換成int型別
          String line = br.readLine();
          //將數字字串轉換成數字
          int times = Integer.parseInt(line);
          //3.對int數進行判斷,如果大於0,將其--寫回去,如果不大於0,提示請購買正版
          if(times>0){
              System.out.println("您還有"+times--+"次機會");
              FileWriter fw = new FileWriter("config.txt");
              fw.write(times+"");
              fw.close();
          }else{
              System.out.println("已結束,請購買正版");
          }
          //關閉流
          br.close();
     }
}

案例3 IO流獲取文字字元出現次數

/**
 * @author 
 *
 *1.建立帶緩衝的輸入流物件
 *2.建立雙列集合物件
 *3.將讀到的字元儲存在雙列集合中,儲存的時候做判斷,如果不包含這個鍵
 *   就將鍵和1儲存,如果包含這個鍵,就將鍵和值加1儲存
 *4.關閉輸入流
 *5.建立輸出流
 *6.遍歷集合,將集合中的內容寫到times.txt中,
 *7.關閉輸出流
 */
public class demo1_Wrap {
     public static void main(String[] args) throws IOException{
          //1.建立待緩衝的輸入流物件
          BufferedReader br = new BufferedReader(new FileReader("aaa.txt"));
          //2.建立雙列集合
          TreeMap<Character, Integer> tm = new TreeMap<>();
          //3.判斷
          int ch;
          while((ch=br.read())!=-1){
              //向下強轉
              char c = (char)ch;
              /*if(!tm.containsKey(c)){//如果集合不包含,就把字元加入,計數1次
                   tm.put(c, 1);
              }else{//如果集合已經包含該字元,則次數+1(次數通過鍵獲取值>>>tm.get(c)+1)
                   tm.put(c, tm.get(c)+1);
              }*/
              //三元運算子表示
              tm.put(c, !tm.containsKey(c)? 1 :tm.get(c)+1);
              
          }
          //4.關流
          br.close();
          //5.建立輸出流物件
          BufferedWriter bw = new BufferedWriter(new FileWriter("times.txt"));
          //6.遍歷集合,將集合內容寫到times.txt中
          for (Character key : tm.keySet()) {
              bw.write(key+"="+tm.get(key));//寫出鍵和值
              bw.newLine();//換行
          }
          //7.關閉輸出流
          bw.close();
     }
}

案例4 拷貝檔案

public class demo11 {
     public static void main(String[] args) throws IOException{
          //獲取檔案
          File file = getFile();
      //BufferedInputStream和BufferOutputStream拷貝
          BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));//讀取資料,建立輸入流物件
          BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file.getName()));//建立輸出流物件
          int b;
          while((b=bis.read())!=-1){
              bos.write(b);
          }
          bis.close();
          bos.close();
     }
     /**
      * @return
      * 定義一個方法獲取鍵盤錄入的檔案路徑(僅獲取檔案)
      */
     public static File getFile(){
          Scanner sc = new Scanner(System.in);
          while(true){
              String nc = sc.nextLine();//接收鍵盤錄入路徑
              File file = new File(nc);//封裝成File物件,並對其進行判斷
              if(!file.exists()){
                   System.out.println("檔案不存在");
              }else if(file.isDirectory()){
                   System.out.println("不是檔案");
              }else{
                   return file;
              }
          }
     }
}

案例5 從鍵盤輸入一個資料夾路徑,列印該資料夾下的所有.java檔名

/*案例:從鍵盤輸入一個資料夾路徑,列印該資料夾下的所有.java檔名
 *
 *從鍵盤接受一個資料夾路徑
 *1.如果資料夾不存在,提示
 *2.如果輸入的不是資料夾,也提示
 *3.如果是資料夾路徑,則直接返回
 *列印該資料夾下的以java結尾的檔案
 *4.獲取該資料夾路徑下所有的檔案和資料夾,儲存在File陣列中
 *5.遍歷陣列,對每一個檔案或者資料夾做判斷
 *6.如果是檔案,且字尾是.java的>>列印
 *7.如果是資料夾就遞迴呼叫
 */
public class demo4_File {
	public static void main(String[] args){
		File dir = getDir();//獲取資料夾路徑
		getJava(dir);
	}
	/**
	 * @return
	 * 獲取資料夾路徑
	 */
	public static File getDir(){
		Scanner sc = new Scanner(System.in);
		System.out.println("請輸入路徑:");
		while(true){
			String line = sc.nextLine();//將路徑儲存
			//封裝路徑
			File dir = new File(line);
			if(!dir.exists()){
				System.out.println("您錄入的文降價不存在");
			}else if(dir.isFile()){
				System.out.println("您錄入的是檔案路徑,請重新錄入");
			}else{
				return dir;//返回資料夾路徑
			}
		}
	}
	/**
	 * 獲取資料夾下的所有java檔案
	 */
	public static void getJava(File dir){
		File[] files = dir.listFiles();//獲取資料夾下的所有檔案和資料夾,儲存在files中
		//遍歷陣列,對每一個檔案或者資料夾列印
		for (File file : files) {
			if(file.isFile()&&file.getName().endsWith(".java")){
				System.out.println(file);
			}else{
				//遞迴呼叫
				if(file.isDirectory()){
					getJava(file);//如果file是一個資料夾,那麼就把這個資料夾在進行呼叫
				}
			}
		}
	}
}

遞迴呼叫練習

案例一 運用遞迴計算資料夾大小

/**
 * @author ZHENG
 *	計算資料夾大小
 */
public class test2 {
	public static void main(String[] args){
		File dir = getDir();
		long l = getDirLength(dir);
		System.out.println(l);
	}
	/**
	 * 獲取資料夾
	 * @return
	 */
	public static File getDir(){
		Scanner sc = new Scanner(System.in);
		System.out.println(">>請輸入資料夾路徑:");
		while(true){
			String nl = sc.nextLine();
			//接受的資料封裝成Flie物件
			File dir = new File(nl);
			if(!dir.exists()){
				System.out.println("資料夾不存在");
			}else if(dir.isFile()){
				System.out.println("不是資料夾");
			}else{
				return dir;//返回資料夾
			}
		}
	
	}
	/**
	 * @param dir
	 * @return
	 * 計算資料夾大小
	 */
	public static long getDirLength(File dir){
		int len = 0;
		//獲取該資料夾下的所有檔案,並進行遍歷
		File[] files = dir.listFiles();
		for (File file : files) {
			if(file.isFile()){
				len += file.length();
			}else{
				//否則遞迴呼叫自身,直到找到檔案為止
				len+=getDirLength(file);
			}
		}
		return len;
	}
}

案例2刪除空的資料夾

public class test3 {
	public static void main(String[] args){
		File dir = test2.getDir();//獲取資料夾
		deleteFile(dir);
	}
	
	public static void deleteFile(File dir){
		File[] files = dir.listFiles();
		for (File file : files) {
			if(file.isFile()){
				file.delete();
			}else{//如果是資料夾遞迴呼叫
				deleteFile(file);
			}
		}
		dir.delete();//刪除空資料夾
	}
}

案例3將資料夾中的所有檔案按照層級列印

public class test4{
	public static void main(String[] args){
		//依據test2裡的getDir方法獲取資料夾路徑
		File dir = test2.getDir();
		printLev(dir,0);
	}
	private static void printLev(File dir,int lev){
		//將資料夾中的所有檔案按照層級列印
		File[] files = dir.listFiles();
		for(File file:files){
			//無論是檔案還是資料夾都需要直接列印
			system.out.println(file);
			//如果是資料夾,遞迴呼叫
			if(file.isDirectory()){
				printLev(file,lev+1);
			}
		}
	}
}

多執行緒

1.多執行緒之多執行緒的兩種實現方式
1.1繼承Thread方式開啟執行緒

public class demo1_Thread {

	public static void main(String[] args) {
		MyThread mt = new MyThread();
		mt.start();//開啟執行緒
		for (int i = 0; i < 1000; i++) {
			System.out.println("主方法");
		}
		
	}

}
class MyThread extends Thread{

	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println("aaaa");
		}
	}
	
}

1.2實現Runnable介面方式開啟執行緒

public class demo2_Thread {

	public static void main(String[] args) {
		MyThread mt = new MyThread();
		new Thread(mt).start();//Runnable介面實現開啟執行緒
		for (int i = 0; i < 1000; i++) {
			System.out.println("主方法");
		}
	}
}
class MyThread implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println("aaaaaaaa");
		}
	}
}

2.多執行緒(兩種實現方式的區別)
檢視原始碼的區別:
1.繼承Thread:由於子類重寫了Thread類的run()方法,當呼叫start()時,直接找子類的run()方法;
2.實現Runable:建構函式中傳入了Runable的引用,成員變數記住了它,start()呼叫run()方法時內部判斷成員變數Runable的引用是否為空,不為空編譯時看的是Runable的run()方法,執行時執行的是子類的Run()方法。
繼承Thread
好處:可直接使用Thread類中的方法,程式碼簡單;
弊端:若有父類,則不能使用這種方法;
實現Runable介面
好處:介面可以多實現
弊端:不能直接使用Thread中的方法,先獲取執行緒物件後,才可以得到Thread方法,程式碼複雜

3.多執行緒之匿名內部類實現執行緒的兩種方式
好處:不需要繼承,也不需要實現介面,程式碼簡潔
匿名內部類之實現Runnable介面方式開啟執行緒方式:需要把Runnable子類物件當做引數傳給Thread的子類物件方可開啟執行緒。

/**
 1. @author ZHENG
 2. 匿名內部類實現多執行緒
 */
public class demo3_Thread {
	public static void main(String[] args){
		
		//第一種匿名內部類方式:繼承Thread類
		new Thread(){//1.繼承Thread類
			//2.重寫Run方法
			public void run(){
				for (int i = 0; i < 1000; i++) {
					System.out.println("aaaaaaa");
				}
			}
		}.start();//3.開啟執行緒
		
		
		//第二種匿名內部類方式:實現Runnable介面
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 1000; i++) {
					System.out.println("bbbbbb");
				}
			}
		}).start();//Runnable方式開啟執行緒
	}
}

4.多執行緒之獲取執行緒名字與設定執行緒的名字
------獲取名字:getName()
------設定名字: setName()

/**
 * @author ZHENG
 * 
 * 獲取與設定執行緒名字的三種方式
 *
 */
public class demo4 {
	public static void main(String[] args){
		/* 方式1 */
		new Thread("執行緒名字"){
			public void run(){
				for (int i = 0; i < 1000; i++) {
					System.out.println(getName()+"aaaaaa");
				}
			}
		}.start();
		/* 方式2 */
		new Thread(){
			public void run(){
				this.setName("設定執行緒名字");
				for (int i = 0; i < 1000; i++) {
					System.out.println(getName()+"bbbbb");
				}
			}
		}.start();
		
		/* 方式3 父類引用指向子類物件*/
		Thread t1 = new Thread("執行緒名字"){
			public void run(){
				for (int i = 0; i < 1000; i++) {
					System.out.println(getName()+"aaaaaa");
				}
			}
		};
		t1.setName("新的名字1");
		t1.start();
		Thread t2 = new Thread(){
			public void run(){
				this.setName("設定執行緒名字");
				for (int i = 0; i < 1000; i++) {
					System.out.println(getName()+"bbbbb");
				}
			}
		};
		t2.setName("新的名字2");
		t2.start();
	}
}

5.多執行緒之獲取當前執行緒物件
-----Thread.currentThread();
------Thread.currentThread().getName();//獲取當前執行緒的名字

以及如何設定與獲取主執行緒的名字

public class demo6 {
	public static void main(String[] args){
		new Thread(){
			public void run(){
				for (int i = 0; i < 1000; i++) {
					System.out.println(getName()+"aaaaaaaa");
				}
			}
	}.start();
	new Thread(new Runnable() {
		
		@Override
		public void run() {
			for (int i = 0; i < 1000; i++) {
				System.out.println(Thread.currentThread().getName()+"bbbbbb");
			}
		}
	}).start();
	//獲取主執行緒的名字
	Thread.currentThread().setName("主執行緒");
	System.out.println(Thread.currentThread().getName());

}
}

6.多執行緒之執行緒休眠
----Thread.sleep(1000)//休眠1s
運用休眠實現倒計時功能

public class demo7 {
	public static void main(String[] args){
		for (int i=5; i>=0; i--) {
			if(i==0){
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("已通知");
				break;
			}else{
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("倒計時"+i+"秒");
			}
			
		}
	}
}

7.多執行緒之守護執行緒
----setDaemon():設定一個守護執行緒,該執行緒不會單獨執行,當非守護執行緒結束後,該執行緒自動停止。
守護執行緒=======“車馬相”等;
非守護執行緒=====“帥”

public class demo8_Daemon {
	public static void main(String[] args){
		//將t2設定為守護執行緒:當t1(帥)掛掉,t2(車馬相等)自動掛掉,但是由於緩衝的原因
		//t2不會立刻停止,但執行次數一定小於50次
		Thread t1 = new Thread(){
			public void run(){
				for (int i = 0; i < 2; i++) {//執行2次
					System.out.println(getName()+"...aaaa");
				}
			}
		};
		
		Thread t2 = new Thread(){//執行50次
			public void run(){
				for (int i = 0; i < 50; i++) {
					System.out.println(getName()+"...bbb");
				}
			}
		};
		t2.setDaemon(true);//將t2設定為守護執行緒
		t1.start();
		t2.start();
	}
}

8.多執行緒之加入執行緒
-------join():當前執行緒暫停,等待其他執行緒結束後再執行;
-------join(1000)等待1s後繼續交叉執行

public class demo9_join {
	public static void main(String[] args){
		final Thread t1 = new Thread(){
			public void run(){
				for (int i = 0; i < 10; i++) {
					System.out.println(getName()+"aaaaa");
				}
			}
		};
		Thread t2 = new Thread(){
			public void run(){
				for (int i = 0; i < 10; i++) {
					
						try {
							t1.join(1);//插隊指定的時間,過了時間,兩條執行緒交替執行,1ms
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					
					System.out.println(getName()+"bbbbbbb");
				}
			}
		};
		t1.start();
		t2.start();
	}
}

9.多執行緒之設定執行緒的優先順序
---------setPriority();
t1.setPriority(Thread.MIN_PRIORITY);//將t1設定成最小的執行緒優先順序
t2.setPriority(Thread.MAX_PRIORITY);//將t2設定成最大的執行緒優先順序,即保證t2執行緒首先執行完

/**
 * @author ZHENG
 *	設定執行緒優先順序
 */
public class demo10_Priority {
	public static void main(String[] args){
		Thread t1 = new Thread(){
			public void run(){
				for (int i = 0; i < 100; i++) {
					System.out.println(getName()+"aaaaaaaa");
				}
			}
		};
		Thread t2 = new Thread(){
			public void run(){
				for (int i = 0; i < 100; i++) {
					System.out.println(getName()+"bbbbbbbb");
				}
			}
		};
		/*t1.setPriority(1);
		t2.setPriority(10);*/
		t1.setPriority(Thread.MIN_PRIORITY);//將t1設定成最小的執行緒優先順序
		t2.setPriority(Thread.MAX_PRIORITY);//將t2設定成最大的執行緒優先順序
		t1.start();
		t2.start();
	}
}

10.多執行緒之同步程式碼塊——同步方法
-----synchronized關鍵字加上一個鎖物件來定義一段程式碼,叫做同步程式碼塊
多個同步程式碼塊如果使用相同的鎖物件,即是同步的;
鎖物件可以使用任意物件,隨便建立一個物件也可以,但是不能使用匿名物件
例子:
class Demo{
}
class Print{
Demo d = new Demo();
public void print1(){
synchronized(d){
}
}
}
1.非靜態的同步方法鎖物件是什麼?--------this
即synchronized(this){

}
2.靜態方法的鎖物件是-------------Print.class物件
即synchronized(Print.class){

}

public class demo10 {
	public static void main(String[] args){
		final Print p = new Print();
		new Thread(){
			public void run(){
				while(true){
					p.print1();
				}
			}
		}.start();
		new Thread(){
			public void run(){
				while(true){
					p.print2();
				}
			}
		}.start();
	}
}
class Print{
	public static synchronized void print1(){
			System.out.print("1");
			System.out.print("2");
			System.out.print("3");
			System.out.print("4");
			System.out.println();
	}
	public static void print2(){
		synchronized (Print.class) {
			System.out.print("a");
			System.out.print("b");
			System.out.print("c");
			System.out.print("d");
			System.out.println();
		}
	}
}

11.多執行緒之執行緒安全問題----多執行緒實現4個窗口出售100張火車票

/**
 * @author ZHENG
 *	多執行緒實現4個窗口出售100張火車票
 */
public class demo12 {
	public static void main(String[] args){
		Tickets t1 = new Tickets();
		t1.setName("1號視窗");
		t1.start();
		Tickets t2 = new Tickets();
		t2.setName("2號視窗");
		t2.start();
		Tickets t3 = new Tickets();
		t3.setName("3號視窗");
		t3.start();
		Tickets t4 = new Tickets();
		t4.setName("4號視窗");
		t4.start();
	}
}
class Tickets extends Thread{
	//定義初始化100張火車票(定義成靜態的代表4個執行緒共享100張票,即4個視窗一共出售100張票)
	private static int tickets = 100;
	public void run(){
		while(true){
			synchronized (Tickets.class) {
				if(tickets==0){
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(getName()+"票已售完");
					break;
				}else{
					try {
						Thread.sleep(10);//如果有大量資料如10w資料如果不睡眠則無法全部加載出來
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("【"+getName()+"】:"+"已售出"+tickets--+"號票");
				}
			}
		}
	}
}

12.多執行緒之死鎖
避免死鎖:不要巢狀同步程式碼塊
13多執行緒之執行緒安全問題
通過檢視原始碼可以得出:執行緒安全的都是有synchronized修飾的類

  • 以Hashtable為例通過ctrl+o找到put方法可以看出Hashtable由synchronized修飾
  1. Vector:執行緒安全的; ArrayList:執行緒不安全
  2. StringBuffer:執行緒安全的;StringBuilder執行緒不安全的
  3. Hashtable:執行緒安全的;HashMap執行緒不安全的