Java基础—数组

一、数组的定义

1.数组的概念
数组是同一种类型数据的集合,是一个容器,用于存储多个数据。

2.数组的优点
数组中的每个元素都有下表值,下标从0开始。下标值方便程序员对数组中的数据进行操作。

3.数组定义的格式
1)元素类型[]数组名=new 元素类型[元素个数或数组长度]; 这种定义方法称之为动态定义法。

示例:int[] arr = new int[5];

2) 元素类型[]数组名= new 元素类型[]{元素,元素,……}; 这种定义方法称之为静态定义法。

示例:int[] arr = new int[] {3,5,1,7};
          int[] arr = {3,5,1,7};

二、引用数据类型内存结构

1.Java内存结构
由于Java对不同类型的数据有不同的处理方式,所以Java对内存进行了不同的区域划分。这提高了运算效率,优化了内存管理方式。

1)栈内存
栈内存用于储存局部变量。当数据使用完毕之后,其所占空间会自动释放。

2)堆内存
a. 堆内存用于储存对象和数组,所有通过new建立的实例都在堆内存中存放;
b. 每一个对象实例都有一个内存地址;
c. 实例中的变量都有默认的初始化值;
d. 实例不再被使用时(没有任何引用指向该实例),该实例会在不确定的时间被垃圾回收器回收,释放内存

2.数组的内存结构示意图
数组属于引用数据类型,其内存结构示意图如下:

这里写图片描述

当执行int[] x = new int[3]时,
1)在栈内存开辟一个内存空间,用于存储变量x
2)在堆内存开辟内存空间,用于存储new出来的数组对象实例;
3)将数组对象的内存首地址(0x0079)赋给x
4)至此,x就是数组对象的引用,指向数组对象

3.两个引用指向同一数组对象实例

int[] x = new int[3];
int[] y = x;
y[1] = 89;
x = null;

该代码段中,将数组的内存地址引用赋给了x,也赋给了y。那么xy就指向了同一数组对象。xy的内存情况如下图:

这里写图片描述

因此,当x = null执行之后,内存中并没有产生垃圾,因为变量y也指向该数组,该数组仍处于被引用状态。同时,y[1] = 89执行之后,x[1]的值也将变为89,而不是0

4.基本数据类型内存结构对比
引用数据类型中,两个变量指向同一对象实例,其中一个变量对对象的值进行修改,会影响另一个变量,这是引用数据类型的规律。下面对比一下基本数据类型做同样的赋值操作之后的内存示意图。

int a = 5;
int b = a;
b = 8;

运行结束后,a的值仍旧为5。内存图如下:

这里写图片描述

三、数组操作过程中常见的异常

1.ArrayIndexOutOfBoundsException数组下标越界异常

int[] arr = new int[3];
System.out.println(arr[3]);

arr[3]超出下标范围,出现异常。

2.NullPointerException空指针异常

int[] arr = new int[3];
arr = null;
System.out.println(arr[1]);

arr被赋值为空,已经无法指向数组对象,再使用arr操作数组,出现空指针异常。

四、排序算法

1.选择排序
1)从下标为0的数开始,逐一和后面的数进行比较,遇到小的数,则交换位置

示例代码:

package test;

/**
 * 选择排序
 * @author jeremy
 * 从下标0开始,将每一个数与后一个数进行比较,如果后一个数大于前一个数,则交换位置
 */

public class SelectSort {
    public static void main(String[] args) {
        int[] arr = new int[]{3, 11, 7, 5, 18, 34, -6};

        //排序前
        printArray(arr);

        //排序
        selectSort(arr);

        //排序后
        printArray(arr);
    }

    public static void selectSort(int[] arr) {
        for(int x = 0; x < arr.length - 1; x ++ ) {
            for(int y = x + 1; y < arr.length; y ++) {
                if(arr[x] > arr[y]) {
                    int tmp = arr[x];
                    arr[x] = arr[y];
                    arr[y] = tmp;
                }
            }
        }
    }

    public static void printArray(int[] arr) {

        System.out.print("[");
        for(int x = 0; x < arr.length; x ++) {

            //判断如果是不是最后一个元素,在元素后面加逗号,如果是,直接打印并换行
            if(x != arr.length - 1) {
                System.out.print(arr[x] + ", ");
            } else {
                System.out.println(arr[x] + "]");
            }
        }
    }
}

2)从下标为0的数开始,逐一和后面的数进行比较,如果遇到比自身小的数,让小的数继续和后面的数进行比较,直至发现最小的数,再和比较开始时的第一个数交换位置。效率稍高于第一种选择排序

示例代码:

package test;

/**
 *  从下标为0的数开始,逐一和后面的数进行比较,如果遇到比自身小的数,让小的数继续和后面的数进行比较
 *  直至发现最小的数,再和比较开始时的第一个数交换位置。
 * @author jeremy
 *
 */

public class SelectSort2 {
    public static void main(String[] args) {
    int[] arr = new int[]{3, 11, 7, 5, 18, 34, -6};

        //排序前
        printArray(arr);

        //排序
        selectSort(arr);

        //排序后
        printArray(arr);
    }

    public static void selectSort(int[] arr) {

        //声明最小值的下标,和第三方变量
        int minPos, tmp;

        for(int x = 0; x < arr.length - 1; x ++ ) {
            minPos = x; //假设最小值下标等于第一个数的下标,即为0
            for(int y = x + 1; y < arr.length; y ++) {
                if(arr[y] < arr[minPos]) {
                    minPos = y; //如果后一个数小于minPos下标所在的那个数,则交换下标值,即保证minPos为最小值
                }
            }

            //如果minPos不是最初的那个x的值,即minPos所在位置的数字已经发生变化,则将minPos下标所在位置的值和初始值做交换,得到数组中最小值
            if(minPos != x) {
                tmp = arr[minPos];
                arr[minPos] = arr[x];
                arr[x] = tmp;
            }
        }
    }

    public static void printArray(int[] arr) {

        System.out.print("[");
        for(int x = 0; x < arr.length; x ++) {

            //判断如果是不是最后一个元素,在元素后面加逗号,如果是,直接打印并换行
            if(x != arr.length - 1) {
                System.out.print(arr[x] + ", ");
            } else {
                System.out.println(arr[x] + "]");
            }
        }
    }
}

2.冒泡排序
每一次循环,确定一个最大的数,并让最大的数下沉到最后,让较小的数逐次上升。

示例代码:

package test;

/**
 * 冒泡排序:相邻两个数进行比较,遇到较小的数则交换位置。
 * 每一次比较完成,则确定一个最大的数。
 * @author jeremy
 *
 */

public class BubbleSort {
    public static void main(String[] args) {

        int[] arr = new int[]{3, 11, 7, 5, 18, 34, -6};

        printArray(arr);

        bubbleSort(arr);

        printArray(arr);
    }

    public static void bubbleSort(int[] arr) {

        int tmp;

        for(int x = 0; x < arr.length; x ++) {

            //arr.length - x保证每次比较之后,最后一个元素不参与比较
            //arr.length - x -1保证下标y + 1不会越界
            for(int y = 0; y < arr.length - x - 1; y ++) {
                if(arr[y] > arr[y + 1]) {
                    tmp = arr[y];
                    arr[y] = arr[y + 1];
                    arr[y + 1] = tmp;
                }
            }
        }
    }

    public static void printArray(int[] arr) {

        System.out.print("[");
        for(int x = 0; x < arr.length; x ++) {

            //判断如果是不是最后一个元素,在元素后面加逗号,如果是,直接打印并换行
            if(x != arr.length - 1) {
                System.out.print(arr[x] + ", ");
            } else {
                System.out.println(arr[x] + "]");
            }
        }
    }
}

3.真实开发,请使用Arrays.sort()方法…

五、数组查找

1.折半查找

示例一:

package test;

/**
 * 值班查找的前提是数组是有序数组
 * 折半查找1:当中间值等于目标值的时候,结束查找,并返回查找结果
 * @author jeremy
 *
 */

public class HalfSearch1 {
    public static void main(String[] args) {
        int[] arr = new int[]{-6, 2, 5, 8, 19, 21, 33, 66, 120, 211};

        System.out.println("index = " + halfSearch(arr, 2));
    }

    public static int halfSearch(int[] arr, int key) {
        int min = 0;
        int max = arr.length - 1;
        int mid = (arr.length - 1) / 2;

        while(arr[mid] != key) {
            if(arr[mid] < key) {
                min = mid + 1; //目标数大于中间数,较小值的下标等于中间数下标+1
            } else {
                max = mid - 1; //目标数小于中间数,较大值的下标等于中间数下标-1
            }
            //如果较小值的下标大于了较大值的下标,说明目标数不存在,则返回-1
            if(min > max) {
                return -1;
            }
            mid = (min + max) / 2;
        }
        return mid;
    }

}

示例二:

package test;

/**
 * 折半查找2:当较小值下标小于等于较大值下标时,继续查找,直到找到目标数;如果不存在,返回-1
 * @author jeremy
 *
 */

public class HalfSearch2 {
    public static void main(String[] args) {
        int[] arr = new int[]{-6, 2, 5, 8, 19, 21, 33, 66, 120, 211};

        System.out.println("index = " + halfSearch(arr, -6));
    }

    public static int halfSearch(int[] arr, int key) {
        int min = 0;
        int max = arr.length - 1;
        int mid;

        while(min <= max) {
            mid = (min + max) >> 1;
            if(arr[mid] < key) {
                min = mid + 1;
            //这里一定要写上else if,因为while条件中没有用数组的具体值做比较
            //因此如果找到了相等的值,而没有判断条件来做出返回动作,程序会出错,即查找第二个数字的时候
            //返回的还是下标0
            } else if(arr[mid] > key) {
                max = mid - 1;
            } else {
                return mid;
            }
        }
        return -1;
    }
}

2.折半查找拓展
在有序数组中插入一个数,使数组仍保持有序。

示例代码:

package test;

/**
 * 在一个有序数组中插入一个数字,使得插入数字后的数组仍然保持有序
 * @author jeremy
 *
 */

public class InsertNumber {
    public static void main(String[] args) {
        int[] arr = new int[]{-6, 2, 5, 8, 19, 21, 33, 66, 120, 211};

        System.out.println("Insert Position is: " + insertPos(arr, 34));
    }

    public static int insertPos(int[] arr, int key) {
        int min = 0;
        int max = arr.length - 1;
        int mid = (min + max) / 2;

        while(min <= max) {
            if(arr[mid] < key) {
                min = mid + 1;
            } else if(arr[mid] > key) {
                max = mid - 1;
            } else {
                return mid;
            }
            mid = (max + min) / 2;
        }
        return min; //如果找不到目标数字,直接返回目标数组前一个数的下标,就是目标数字的插入位置
    }

    public static void printArray(int[] arr) {

        System.out.print("[");
        for(int x = 0; x < arr.length; x ++) {

            //判断如果是不是最后一个元素,在元素后面加逗号,如果是,直接打印并换行
            if(x != arr.length - 1) {
                System.out.print(arr[x] + ", ");
            } else {
                System.out.println(arr[x] + "]");
            }
        }
    }
}

六、进制转换

1.十进制转二进制

示例代码:

package test;

/**
 * 十进制转换为二进制数
 * @author jeremy
 *
 */

public class ToBinary {
    public static void main(String[] args) {
        toBin(6);
    }

    public static void toBin(int num) {
        StringBuffer sb = new StringBuffer();

        while(num > 0) {
            sb.append(num % 2); //模2,将余数存储到StringBuffer当中
            num /= 2; //原数字除以2,准备继续做模运算
        }
        System.out.println(sb.reverse()); //用sb对象的reverse()方法,反向打印出结果
    }
}

2.十进制转十六进制

示例代码:

package test;

/**
 * 十进制转十六进制
 * @author jeremy
 *
 */

public class ToHex {
    public static void main(String[] args) {
        toHex(60);
    }

    public static void toHex(int num) {

        int tmp;

        StringBuffer sb = new StringBuffer();

        for(int x = 0; x < 8; x ++) {

            tmp = num & 15;

            if(tmp > 9) {
                sb.append((char)(tmp - 10 + 'A')); //如果&15之后的数大于9,则转换成字母
            } else {
                sb.append(tmp);
            }

            num = num >>> 4; // & 操作结束,原数字右移4为,准备下一次 & 操作
        }

        System.out.println(sb.reverse()); //反向输出结果

    }
}

3.查表法十进制转十六进制

示例代码:

package test;

/**
 * 查表法十进制转十六进制
 * @author jeremy
 *
 */

public class ToHexTable {

    //声明十六进制表
    private static char[] ch = new char[]{'0', '1', '2', '3'
                            , '4', '5', '6', '7'
                            , '8', '9', 'A', 'B'
                            , 'C', 'D', 'E', 'F'};

    private static  char[] result = new char[8];

    public static void main(String[] args) {
        toHex(60);
    }

    public static void toHex(int num) {

        int tmp;
        int pos = result.length; //声明一个指针,用于字符数组从最后一个开始储存十六进制数

        while(num != 0) { //判断num是否大于0,如果等于0,则不需要再进行右移操作,有效位已经全部转换为十六进制

            tmp = num & 15; //& 15 取出有效位的十六进制数

            result[--pos] = ch[tmp]; //从数组的最后以为开始储存查表得出的十六进制数

            num = num >>> 4; //右移4位
        }

        //从有效位开始打印
        for(int x = pos; x < result.length; x ++) {
            System.out.print(result[x]);
        }

    }
}

4.抽取进制转换共性代码

示例代码:

package test;

/**
 * 将查表法中的代码抽取,提供方法计算十进制转其他进制
 * @author jeremy
 *
 */

public class ToAllNumberFormat {
    public static void main(String[] args) {
        toBin(6);
        toBin(-6);
        toOct(60);
        toOct(-60);
        toHex(60);
        toHex(-60);
        toBin(0);
    }

    //十进制转二进制
    public static void toBin(int num) {
        formatTrans(num, 1, 1);
    }

    //十进制转八进制
    public static void toOct(int num) {
        formatTrans(num, 7, 3);
    }

    //十进制转十六进制
    public static void toHex(int num ) {
        formatTrans(num, 15, 4);
    }

    public static void  formatTrans(int num, int base, int offset) {

        //判断如果要转换的十进制数为0,直接输出0,并结束方法
        if(num == 0) {
            System.out.println(0);
            return;
        }

        //声明十六进制表,该表包含八进制表和二进制表
        char[] ch = new char[]{'0', '1', '2', '3'
            , '4', '5', '6', '7'
            , '8', '9', 'A', 'B'
            , 'C', 'D', 'E', 'F'};

        //声明32位数组,该数组包含能储存八进制和十六进制数
        char[] result = new char[32];

        //声明指针,用于存储数据
        int pos = result.length;

        while(num != 0) {
            result[--pos] = ch[num & base];
            num = num >>> offset;
        }

        for(int x = pos; x < result.length; x ++) {
            System.out.print(result[x]);
        }
        System.out.println();
    }
}

七、二维数组

1.二维数组的定义
1)int[][]arr=newint[2][4];
1> 定义了名称为arr的二维数组;
2> 二维数组中有2个一维数组;
3> 每一个一维数组中有4个元素;
4> 一维数组的名称分别为arr[0],arr[1]
5> 给第一个一维数组1下标位赋值为78写法是:arr[0][1]=78;

2)int[][]arr=newint[3][];
1> 二维数组中有3个一维数组;
2> 每个一维数组被默认初始化为空值null
3> 可以对这个三个一维数组分别进行初始化:

arr[0] = new int[3];
arr[1] = new int[1];
arr[2] = new int[2];

3)int[][]arr={{3,5,2},{2,6,7},{9,2,1,6}};
1> 定义一个名称为arr 的二维数组;
2> 二维数组中的有三个一维数组;
3> 每一个一维数组中具体元素也都已初始化;
4> 第一个一维数组arr[0]={3,5,2};
5> 第二个一维数组arr[1]={2,6,7};
6> 第三个一维数组arr[2]={9,2,1,6};
7> 第三个一维数组的长度表示方式:arr[2].length;

2.二维数组声明和求和

package test;

/**
 * 声明一个二维数组,遍历求和
 * @author jeremy
 *
 */

public class TwoDArray {
    public static void main(String[] args) {
        int[][] arr = new int[][]{{2, 3, 5, 1}, {5, 77, 6, 10}, {4, 13, 17, 7}, {8, 9, 0, 11}};

        int sum = 0;

        //遍历二维数组,并求和
        for(int x = 0; x < arr.length; x ++) {
            for(int y = 0; y < arr[x].length; y ++) {
                sum += arr[x][y];
            }
        }
        System.out.println("sum = " + sum);
    }
}

3.小细节
int[] x, y[];
这样的声明方式,x是一维数组,y是二维数组。

八、知识拓展

数组的复制。使用java.lang.System类的静态方法:

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

可以用于数组src从第srcPos项元素开始的length个元素拷贝到目标数组destdestPos项开始的length个位置。如果源数据数目超过目标数组边界将抛出IndexOutOfBoundsException异常。

示例代码:

package test;

/**
 * 测试System类的arraycopy方法
 * @author jeremy
 *
 */

class TestArrayCopy {
    public static void main(String[] args) {
    String[] s = {"Microsoft", "IBM", "SUN", "Oracle", "Apple"};
    String sBak[] = new String[6];

    //public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
    //src:原数组;srcPos:从原数组第srcPos个元素开始拷贝;dest:目标数组;destPos:从目标数组中的destPos位开始拷贝;
    //length;拷贝多少个原数组中的元素
    System.arraycopy(s, 0, sBak, 0, s.length);

    for(int i = 0; i < sBak.length; i ++) {
        System.out.print(sBak[i] + " ");
    }

    System.out.println();

    int[][] array = {{1, 2}, {1, 2, 3}, {3, 4}};
    int[][] arrayBak = new int[3][];

    System.arraycopy(array, 0, arrayBak, 0, array.length);

    arrayBak[2][1] = 100;

    for(int i = 0; i < array.length; i ++) {
        for(int j = 0; j < array[i].length; j ++) {
            System.out.print(array[i][j] + " ");
        }
        System.out.println();
    }

    }
}