# Java進階技巧- 陣列、函式、字串
## 1.陣列
### 1.基本介紹及記憶體位置
1. ★陣列是連續的記憶體,陣列只有參考位置(最頂層1維、2維取最高的一個)在stack裡面,其他都在heap中,基本資料型態的話就會直接在stack裡面,不會進到heap裡。
2. 陣列存取資料,都會是相同性質的資料,如果是不同系列的資料,其實就用不同變數宣告即可。
3. 基本資料型態存的stack記憶體位置是值,(heap)不會存資料,
參考資料型態的stack記憶體是存位置,heap才會存記憶體位置或值(看幾維陣列,最後一層才是值(如果是參考資料型態的陣列,最後一層還是位置),其他都是位置)。
多維陣列,也就是一直以一維陣列拆解,最後一層在看是值或是地址。
4. 基本資料型態陣列,陣列內值預設值為零; 參考資料型態陣列,陣列內值預設值為null
5. 如果直接是參考資料型態陣列的變數名稱,印出會得到記憶體的位置
6. int a[] = new int[3];
int b[] = a; //如果是 int b[] = a[] 或 a[0] 是錯的
因為int b[ ] = a整個陣列,也就是說b[ ] 所有值跟位置都跟a[ ] 一樣,因為記憶體位置指向相同。
a[0] = 4;
System.out.println(b[0]);
b[0] = 4
#### 手抄筆記-陣列記憶體圖示:
記憶體圖片,
1.陣列、參考資料型態、多重陣列
2.程式碼記憶體
3.程式碼記憶體全部
![](https://i.imgur.com/dmIweDM.jpg)
![](https://i.imgur.com/ozsjhDe.jpg)
![](https://i.imgur.com/yIlunnC.jpg)
![](https://i.imgur.com/iVepFrl.jpg)
### 2.多維陣列資訊
1. 兩層迴圈寫法(如果二維陣列長度不同時(第二個[2][3],三如果長度是不同時)
```java
int[][] test = {{1,2,3},{4,5}};
for (int i = 0; i < test.length; i++){
for (int n = 0; n < test[i].length; n++){
System.out.println(test[i][n]);
}
}
```
多維陣列直接賦值方法及示意圖
![](https://i.imgur.com/KwszopN.png)
### 3.陣列交換&共用
1. 交換時少一層,二維陣列交換,暫存只需要定義一維陣列;一維陣列交換只需要定義int 即可。
2. 要將兩個陣列具有相同數值有兩個方法:
1. 共用陣列( arr1 = arr2;):將arr1的參考位置指向arr2,電腦中的記憶體空間不變(heap),但stack 還是有陣列arr1的記憶體,只是兩個都指向同個位置。
2. 陣列的methon:"clone()" (ex: arr1 = arr2.clone() ):創建與原陣列等大的空間,並將原陣列中的資料複製過去。複製是直接將arr2複製,然後arr1指向新複製的陣列,也就是記憶體空間也會改變。
- 示意圖
![](https://i.imgur.com/upjDZT8.jpg)
![](https://i.imgur.com/04vTANX.jpg)
3. 陣列如果被取代,假如原始的記憶體沒有被任何一個讀取(即沒有連結時), 原始的記憶體就會被GC(垃圾回收) 清理掉,不會浪費記憶體空間
4. 陣列交換方式:
```java
public static void main(String[] args) {
int arr[][] = new int[4][2]; // 座號 成績
for (int i = 0; i < 4; i++) {
arr[i][0] = i + 1;
arr[i][1] = (int) (Math.random() * 101);
}
for (int i = 0; i < 4; i += 2) {
//利用值的方式
for (int n = 0; n < 2; n++) {
int tmp = arr[i][n];
arr[i][n] = arr[i + 1][n];
arr[i + 1][n] = tmp;
}
//利用位置的方式
// int[] tmp = arr[i];
// arr[i] = arr[i+1];
// arr[i+1] = tmp;
}
for (int i = 0; i < 4; i++) {
System.out.println("座號:" + arr[i][0] + "成績:" + arr[i][1]);
}
}
}
```
### 4.陣列自動放大(可無限輸入資料)
將陣列長度擴大,內建函式有兩種方式: (可看第一周投影片 5-4 記憶體管理)
1. System.arraycopy(來源陣列,起始索引值,目的陣列,起始索引值,複製長度);
第一種方法,要先創立新陣列當作目的陣列,先一行宣告,並且在此時就要給予長度。
2. Arrays.copyOf(要被複製的陣列,新陣列的長度);
第二種方法,不需要先新創立,會自動放過去。
以下為手動製作方法:
```java
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int arr[] = new int[2];
int count = 0;
do {
int tmp = sc.nextInt();
if (tmp == -1) {
break;
}
if (arr.length == count) {
int tmpArr[] = new int[arr.length * 2];
for (int i = 0; i < arr.length; i++) {
tmpArr[i] = arr[i];
}
arr = tmpArr;
}
arr[count++] = tmp;
} while (true);
for (int i = 0; i < count; i++) {
System.out.print(arr[i] + " ");
}
}
}
```
### 5.陣列排序(0820課堂)
1.氣泡排序法
最慢的方法:從第一位開始,一直跟後面比,比較大就交換位置(因此不一定每次都是第一個被移到最後面,而是當次最大的數值會被移到最後一個),總共會執行n-1次,並執行 n-1 - i次(因為後面的數值不用再比較,一定是最大)。
```java
package test;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
int arr[] = {7, 4, 6, 2, 1, 5};
printArr(arr);
bubbleShot(arr);
printArr(arr);
}
//氣泡排序法,可以獨立出來一個function
public static void bubbleShot(int[] arr){
//第一層,控制次數,需要做n-1次,因為第一位不用比較
for (int i = 0; i < arr.length - 1; i++) {
//第二層,控制每一次要比對的次數,因為每次都是跟後面(n+1)比,因此要-1不然到最後一位時
//沒有資料可以比,又因為每次結束後,最後一個數字一定是最大值,因此要減掉已執行過的次數,
//因為就算比也不會比較大。
for (int n = 0; n < arr.length - 1 - i; n++) {
if (arr[n] > arr[n + 1]) {
swap(arr, n, n + 1); // 一樣可以用其他function
}
}
}
}
public static void swap(int[] arr, int i1, int i2) {
int tmp = arr[i1];
arr[i1] = arr[i2];
arr[i2] = tmp;
}
public static void printArr(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
}
```
### 6.陣列搜尋(0820課堂)
1. 線性循序搜尋法:就是平常我們使用的,從第一個比到最後一個
2. 二分(元)搜尋法,最多的次數就是(工程領域的log是以2為底,也是是2的幾次方) logN + 1 次。(Math.log (double)),但其實不用用到,解法如下,可參考(0820課堂實作)
```java
正確做法:
用兩個變數left與right分別紀錄搜尋的最左與最右索引
中間數索引 middle=(left+right)/2
如果數字應落在左半邊,新的right值:
right = middle - 1
如果數字應落在右半邊,新的left值:
left = middle + 1
left應當等於或比right小,否則就代表鍵值不存在 <- 此時代表找不到。
如果數值跟middle相等,也就代表找到值了。
//老師版本的二元搜尋法,不用用log,直接用左右兩邊的值去確認位置
public static int binarySearch(int num[], int count, int search){
int left = 0;
int right = count -1;
int middle;
while(left <= right){ // <= 在left = right 時才會進迴圈
middle = (left + right) / 2;
if (search == num[middle]){
return middle;
}
if (search > num[middle]){
left = middle + 1; // 放棄左半邊,會加1的原因,是因為middle已經確認過不是了,所以要掠過它
}else{
right = middle - 1; // 放棄右半邊,會減一的原因同上
}
}
return -1;
}
// java 內建的二元搜尋法:
// 所屬類別:java.util.Arrays
// 語法:public static int binarySearch(int[] a,int key)
// 功能:二分搜尋。
// 引數:key為尋找目標。a[ ]為已排序的工作陣列。
// 回傳值:若key位於a陣列中,則回傳索引值。若不位於a陣列中,則回傳負值。
我的作法: 直接用<= 不另做 +1 -1 的判斷
public static int twoSearch(int num[], int count, int search) {
int min = 0;
int max = count;
for (int i = 0; i <= (int) (Math.log(count)) + 1; i++) { //用log找最多幾次,因為log有時會有小數點,所以用<= 就算沒用掉也會直接跳出
int index = (min + max) / 2;
if (search == num[index]) {
return index;
}
if (search < num[index]) {
max = index;
continue;
}
min = index;
}
return -1;
}
```
## 2.函式 ( Function) (範例:0820課堂)
### 1.基本資訊及架構
1. 寫在main函式外,但要包含在主類別之中。
2. 每個java 一定在:main funtion 主程式開始及結束,其他皆只是函式或方法。
main funtion =
class Main{
public static void main (String[] args) {
→此為主程式,方法在這個之上或之下都可以,要在class Main 裡
3. public 是存取修飾詞,static 修飾詞 (靜態函式的意思),(暫時再寫的時候都先加上
public static 都先加。)
4. void 沒有回傳值,常用在不需要回傳資料,EX: 可以直接輸出時,不需要回傳資料
5. void 的位置,如果是要有回傳值,就將void 改成要的資料型態(ex: int,double,String) 基本資料跟參考資料型態都可。
6. main 為名稱,像變數名稱一樣都可以隨意叫,但盡量遵守命名原則,小寫開頭,轉折處大寫EX: dateArr。
7. String[] args 則是放入要從外面帶入的參數(如果沒有要代入的話,就留小括號即可,裡面不用放,就算是void還是可以代入外面的參數,只是無法用return傳回數值而已),要有形態,可以有兩個以上的參數,用逗點分隔,可以不同型態,但都一定要有型態跟參數(詳細範例可以看後面);後面的大括號內放的是function的行為(←平常在寫得)。
Tips.小括號裡面其實就跟平常在宣告變數一樣,只是差在都用逗點區隔即可
EX: int a ,外面的 小括號內是 1 ,也就是在進入方法時,初始化變數宣告 int a = 1的意思。
8. 定義function的的時候,最好只做到那個方法要用到的功能就好,不要超過上限,盡量讓if的判斷都在外面,增加彈性。
9. java函式範例圖:
![](https://i.imgur.com/7hMHp8I.jpg)
### 2.補充資訊
1. 定義function的的時候,最好只做到那個方法要用到的功能就好,不要超過上限,盡量讓if的判斷都在外面,增加彈性。
2. 有回傳值時重點要注意function傳回值得型態,是否跟主程式的吻合,EX:主程式是宣告int變數承接回傳的值,但function回傳的是double,就會發生錯誤,
3. 只要不是void的,都有回傳值,就要寫return,但void有時候可以沒回傳但影響到主程式的值。
4. return後面也可以不帶參數,不帶參數就是返回空,其實主要目的就是用於想中斷函數執行,返回調用函數處。
5. return和break的主要區別在於:return是跳出method,回到喊它的那個method,break是跳出所在的迴圈
6. function 如果要提前結束只能用return ,但要有回傳值才可以,如果是void一樣可以用return結束函式,只是不會有回傳值,等於直接關掉function回到上一層。
7. Java的函式隸屬於某一個類別,除非傳入的參數不同,否則在同一個類別當中不允許宣告兩個相同名稱的函式。
8. 同個calss function可以互相使用,但是在建立變數時自然會占用到記憶體,因此在效能與彈性及流暢度中,自己要抓平衡
9. ★java 只有: Call by Value 傳值呼叫 是資料的值傳入方法裡面 (記憶體的圖在下方);
Call by Reference 傳參呼叫: 是資料的位置傳入方法裡面; ( C++有,專門語法)
Call by Address 傳址呼叫 ←只有台灣在講 ( 其實就是將陣列指到同個位置,有些人誤講成傳址呼叫,但java是把這個位置當成值傳回 ←在別的語法也就是傳參呼叫)
10. Function 本身可以直接直接在主程式中寫,不需搭配任何變數,就算不是void也行:
```java
public static void main(String[] args) {
test();
test();
}
public static void test(){
System.out.print("!");
}
public static void main(String[] args) {
test(1); //此處,test(1); 回傳了1 但是沒有任何變數去承接他
int b = test(1);
}
public static int test(int a){
System.out.print(a);
return 1;
}
```
### 3.funtion的範例及function執行順序:
funtion的開始,會先宣告參數 因此會先執行 System.out.println(random(1,6)); 因此會先執行小括號內的random(1,6)
```java
int r = random(1, 6);
System.out.println(r);
/*可以改寫為:
System.out.println(random(1, 6));
執行順序,function在被呼叫時,第一件事情就是宣告變數 (將參數帶入變數中 EX: int a = 3) ,
此時random(1, 6) 也就是參數位置;
function會先宣告一個變數(ex;a
也就是 a = random(1, 6); 因此會先執行
也就是說:function 的參數執行順序,由左至右,月內層function越先執行;
*/
}
// random (裡面就跟平常在宣告變數一樣,只是差在都用逗點區隔即可 )
public static int random(int min, int max){
int r = (int)(Math.random() * ( max - min + 1) + min);
return r;
// 可以改寫成:return = (int)(Math.random() * (max - min + 1) + min);
}
```
- Function 範例1:因為有時候會是以記憶體位置傳回,因此雖然是void宣告,仍有可能影響到主程式的數值(含記憶體圖片)
```java
public static void main(String[] args) {
int[] a = new ine[1];
a[0] = 5;
test(a);
System.out.println(a[0]);
}
// 主程式的a 代表的是一維陣列,所以test(a) 也就是將這個一維陣列傳入function test小括號內
// 因為一維陣列是參考資料型態,也就是說傳的值是記憶體的位置,而非數值,因此會變成test裡面的
// a[0] 指向的記憶體位置跟主程式的一維陣列a 相同,所以修改時,就會改變主程式的a陣列值。
public static void test(int[] a){
a[0] = 1;
}
//以下為傳值回去,就影響不到a[0]的數字,因為是傳 "5" 這個數值回a 而void並沒有return,因此
// a = 1 只是在 test 的 a = 1,與主程式無關。
public static void main(String[] args) {
int[] a = new int[1];
a[0] = 5;
test(a[0]);
System.out.println(a[0]);
}
public static void test(int a){
a = 1;
}
```
![](https://i.imgur.com/FkDLBT7.jpg)
## 3.進階函式 - 函式多載(overload)
1. ★面試必考,常與覆寫( override ) 做比較;函式多載(overload):
函式定義可允許同名但不同署名的形式,這就是所謂多載(overload)的機制。
只要參數的數量,或型態不一樣(包含順序),就可以用同個方法名稱命名,不會重複。
EX:有一個String陣列跟一個Int陣列,要同時做arrDouble,不需要將arrDouble取名為arrDoubleInt、 arrDoubleStr 只需要創建兩個方法名為arrDouble後面的參數及前面傳回的資料型態改變即可。
署名:是名稱包含到小括號(函式名稱、參數資料型態、參數的個數與順序)
(回傳值型態、函示名稱、參數宣告)。
命名是function名稱而已
- 示意圖
![](https://i.imgur.com/fAoy2CW.png)
![](https://i.imgur.com/FCS0QRL.png)
## 4.遞迴函式(recursive)
1. 通常用在處理複雜資料結構的運作時。
2. 遞迴函式關鍵兩要素: base case 、 副函式會呼叫自己
3. 透過函式本身再次呼叫自己,直到 base case(最基本可解決的情況,類似迴圈的停止點)時才回傳值,如果沒設base case的話,就像while迴圈沒有break 會無限循環,只是函式的無限呼叫會導致記憶體過載。
4. 遞迴函式在設計程式上,耗費記憶的資源量大,因為每一次遞迴,就等於會再呼叫自己一次,假如遞迴20次,就等於同時存在20個,直到碰到basecase時才會開始回傳並關閉function
5. 迴圈 VS 遞迴
1. 理論上任何重複結構都可透過迴圈或遞迴的方式表現。
2. 兩種寫法都有無限重複的可能
3. 程式設計師需要程式撰寫的複雜度與執行效能間做考量
4. 進行、結束方式:
迴圈:透過設定重複的條件 ex: for (次數)、while、do while (true 或條件) 決定是否進行,或是提前使用break跳出。
遞迴:透過重複呼叫自己 ,除非碰到設定的base case值才跳出
5. 優缺點分析:
迴圈:因為要考慮重複的規律性,因此寫法會變得複雜,但是卻比較省記憶體。
遞迴:好處是寫起程式比較簡單,但相對地也比較耗記憶體。
6. 練習時的建議:每次練習皆需要用迴圈的方法重寫過一次,因為:
使用遞迴寫法有助提高程式的易讀性,但從效能的觀點來看,並非是演繹流程的最好寫法。
同樣問題嘗試用迴圈再撰寫一遍,以便未來當有效能考量的必要時,能採最有效的方式來實作。
### 1.範例及記憶體示意圖
![](https://i.imgur.com/5Bn71lt.jpg)
![](https://i.imgur.com/aDaWBMe.jpg)
費式數列示意圖 (
![](https://i.imgur.com/GEd4aRP.png)
![](https://i.imgur.com/Mnquu84.png)
### 2.範例code
```java
// public static int f(int x) {
// int[] num = new int[2];
// num[0] = 0;
// num[1] = 1;
// while (x >= num.length) {
// int tmp[] = new int[num.length * 2];
// for (int i = 0; i < num.length; i++) {
// tmp[i] = num[i];
// }
// num = tmp;
// }
// for (int i = 2; i <= x; i++) {
// num[i] = num[i-1] + num[i-2];
// }
// return num[x];
// }
//如果是需要求x很大的話 建議用陣列,
// public static int f(int x) {
// int[] num = new int[x + 1];
// for (int i = 0; i <= x; i++) {
// if (i <= 1) {
// num[i] = i;
// break;
// }
// num[i] = num[i - 1] + num[i - 2];
// }
// return num[x];
// }
//如果是需要求x較小的話 建議用值計算,較省記憶體,
public static int f(int n) {
if (n == 0 || n == 1) {
return n;
}
int a1 = 0;
int a2 = 1;
for (int i = 2; i <= n; i++) {
int tmp = a2;
a2 = a1 + a2;
a1 = tmp;
}
return a2;
}
// public static int f(int x) {
// if (x == 0 || x == 1) {
// return x;
// }
// return f(x - 1) + f(x - 2);
// }
```
```java
package test;
import java.util.Scanner;
import java.lang.Math;
public class Test {
public static void main(String[] args) {
System.out.println(question1(3));
System.out.println(question2(3));
System.out.println(question3(3));
System.out.println(question4(3));
System.out.println(question5(3));
System.out.println(question6(3, 3));
System.out.println(question8(16, 12));
}
// 1. 1+2+...+n OK
// 2. 1-2+3-4+....+n
// 3. 1+4+9+16+....+n*n
// 4. n! ( 階層函式)
// 5. fib (費式數列)
// 6. base^exp (次方)
// 7. base^exp ( O(logn) ) - 沒寫,看不懂題目
// 8. gcd (最大公因數)
public static int question1(int n) {
if (n == 1) {
return 1;
}
return question1(n - 1) + n;
}
public static int question2(int n) {
if (n <= 0) {
return n;
}
if (n % 2 == 1) {
return question2(n - 1) + n;
} else {
return question2(n - 1) - n;
}
}
public static int question3(int n) {
if (n == 1) {
return 1;
}
return question3(n - 1) + n * n;
}
public static int question4(int n) {
if (n == 1) {
return 1;
}
return question4(n - 1) * n;
}
public static int question5(int n) {
if (n == 0 || n == 1) {
return n;
}
return question5(n - 1) + question5(n - 2);
}
public static int question6(int n, int times) {
if (times == 0) {
return 1;
}
if (times == 1) {
return n;
}
return question6(n, (times - 1)) * n;
}
public static int question8(int a, int b) {
if (a % b != 0) {
return question8(b, a % b);
} else {
return b;
}
}
}
```
## 5.字串
### 1.基本介紹
1. 字串的本質是字元(char)型態的陣列。
2. 在 Java 中將字串視為 String 類別的一個實例,使用 String 類別來建構,也就是將其視為物件存在於程式之中,可以,這讓字串處理在 Java 中變得簡單。(不需要import)
3. 字串的輸入是雙引號" " , 讀取是用nextLine()。
4. 可以使用" + " 運算子來做子元串接(會自動重新定義(Override)字串)。
5. 字串建構:除了直接 = “ “ 外,也可以用字元陣列建構
EX: char[] name = {‘1’, ‘5’, ‘9’}; // 創建一個字元陣列
String str = new String(name); // 創建一個字串,並將name字元陣列的值存入字串中,因此字元陣列name就算後面修正也不影響,因為不是將str的位置指向name。
String[] nameStr = {“jachy”, “joy”}; //創建一個String陣列 並初始化賦值 String nameStr[0] = jachy [1] = joy;
6. 格式化用法,為何function可以這樣無限制加入,是因為宣告參數時,如果是用參數資料型態...參數名稱,就可以無限加入,這是一種Object的方法。
7. 編譯時期的String 跟執行時間的String 會是在不同的記憶體空間;所以如果在編譯時期會在同個heap中,如果是在執行時期,會=new兩個heap記憶體空間,因此兩個不會相等(heap 裡面會叫做字串池)。
8. 字串所有的字,在編譯時期產生的,只要是重複的就會是同個位置,不同的話會在創一格新的,字串如果相加,如果跟後面的數字一樣,會自動跑到那個位置。
9. 字串池會在程式結束時才會釋放掉,而nextLine() 創出來的則在沒有人指向他時,會被GC掉。
10. 字串內容是不可變的,當字串已在記憶體中被建立出來,該記憶體中的字串內容就不允許變更,會在heap裡面,直到沒人指向他,程式結束或被GC。
### 2.字串使用方法(詳細可見7-2 字串說明、)
1. 將字串轉換為數值型態,可轉換成對應的數值型態:
(數值型態全名ex: Byte Integer Float) .parse (跟前面型態一樣,除了整數改成Int)(字串)
2. 如果指定的字串無法剖析為指定的資料型態數值,則會發生 NumberFormatException 例外(亦即如果字串長度過大,ex: 回傳值是214877858,如果前面型態是用byte short去轉換,就會發生錯誤,如果字串更大,就會用long存取。)
#### 3.常用方法 說明:
1. int length() 取得字串的字元長度
2. equals() 判斷原字串中的字元是否相等於指定字串中的字元:使用 A.equals(B) 也就是像整數 if ( A == B)的意思
3. toLowerCase() 轉換字串中的英文字元為小寫
4. toUpperCase() 轉換字串中的英文字元為大寫
5. length、concat ; split() ;substring() ;contains; replace; compareTo; eaquls; try很常用到
6. contains(String)
7. 範例&示意圖
```java
public static void main(String[] args) {
String date = "1999/9/9";
String[] strs = date.split("/");
//正則表示式 , 因為split方法內用for迴圈跑,所以String陣列會自己跑0,1,2
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
}
![](https://i.imgur.com/QeyCzG6.jpg)
### 3.取得字串中的字元方法
1. char charAt(int index) 傳回指定索引處的**字元**
1. int indexOf(int ch) 傳回指定**字元**第一個找到的索引位置
1. int indexOf(String **str**) 傳回指定**字串**第一個找到的索引位置
1. int lastIndexOf(int ch) 傳回指定**字元**最後一個找到的索引位置
1. String substring(int beginIndex) 取出指定索引處至字串尾端的子字串
1. String substring(int beginIndex, int endIndex) 取出指定索引範圍子字串
1. char[] toCharArray() 將字串轉換為字元陣列
範例圖
![](https://i.imgur.com/AbjL9nA.jpg)
### 4.endsWith() 方法可以判斷字串是不是以指定的文字作為結束,您可以使用這個方法來過濾檔案名稱。
- 範例(使用 endsWith() 來過濾出副檔名為 jpg 的檔案)
![](https://i.imgur.com/1d5RLQm.png)
### 5.[方法大全](http://tw.gitbook.net/java/java_strings.html) (java 極客書)
### 6.[7-3 String類別-已加註筆記](https://drive.google.com/file/d/1dYvTA3fXo_MFR3dknrcBOF9p1gafuUTy/view?usp=sharing)
### 7.字串記憶體位置圖
![](https://i.imgur.com/5XHT6V7.jpg)
###### tags: `CMoney7th-戰鬥營學習筆記`