一维数组

单个的数组变量可以引用一个大的数据集合。

  在程序执行过程中,经常需要存储大量的数据,例如,假设需要读取某科100位学员的成绩,计算它们的平均成绩,然后找出有多少个学员成绩大于平均值。首先,程序需要读入这些数并且计算它们的平均值,然后将每个数与平均值进行比较判断它是否大于平均值。为了完成这个任务,必须将全部的数据存储到变量中。必须声明100个变量,并且重复书写100次几乎完全相同的代码。这样编写程序的方式是不太现实的,那么该如何解决这个问题呢?
  这就需要高效有条理的方法——数组:数组是编程语言中最常见的一种数据结构,可以用它来存储一个元素个数固定且元素类型相同的有序集,每个数组元素存放一个数据,通常可通过数组元素的索引来访问数组元素,包括为数组元素赋值和取出数组元素的值。在现在这个例子中,可以将所有的100位学员的成绩存储在一个数组中,并且通过一个数组变量访问它。

一旦数组被创建,它的大小是固定的。使用一个数组引用变量,通过下标来访问数组中的元素

  数组是用来存储数据的集合,但是,java的数组要求所有的数组元素具有相同的类型。因此,在一个数组中,数组元素的类型是唯一的,即一个数组里只能存储一种数据类型的数据,而不能存储多种数据类型的数据。无须声明单个变量,例如:score0, score1,...score99,只要声明一个数组变量scores,并且用scores[0], scores[1],...scores[99]来表示单个变量(成绩)。

1、声明数组

  为了在程序中使用数组,必须声明一个引用数组的变量,并指明数组的元素类型

    语法:elementType[] arrayName;(数据类型[] 数组引用变量)

  elementType可以是任意数据类型,但是数组中所有的元素都必须具有相同的数据类型。

  注意:因为java是面向对象的语言,而类与类之间可以支持继承关系,这样可能产生一个数组里可以存放多种数据类型的假象(参考后面合唱团例子)。例如有一个水果数组,要求每个数组元素都是水果,实际上数组元素既可以是苹果,也可以是是banana(苹果、香蕉都继承了水果,都是一种特性的水果),但这个数组的数组元素的类型还是唯一的,只能是水果类型。

  从上面语法中看出,数组也是一种数据类型,它本身是一种引用数据类型。例如int是一个基本数据类型,但int[]就是一种引用数据类型。

   声明数组时不能指定数组的长度

2、数组初始化

  不同于基本数据类型变量的声明,声明一个数组变量时并不在内存中给数组分配任何空间。数组是一种引用类型的变量,因此使用它定义一个变量时,仅仅表示定义了一个引用变量(也就是定义了一个指针),这个引用变量还没有指向任何有效的内存,因此定义数组时不能指定数组的长度。而且由于定义数组只是定义了一个引用变量,并未指向任何有效的内存空间,所以还没有内存空间来存储数组元素,因此这个数组也不能使用,只有对数组进行初始化后才能使用(如果变量不包含对数组的引用,那么这个变量的值为null。除非数组已经被创建,否则不能给它分配任何元素)。

  初始化数组有两种方式

              1)静态初始化:初始化数组和给数组赋值同时完成,在开辟数组的空间时依据已给定的元素个数来开辟相应的空间数。    

      a.静态初始化的第一种方式:

        elementType[] arrayName = new elementType[]{value0,value1,value2,...,valuek};            

      b.静态初始化的第二种方式:Java有一个简捷的标记,称作数组初始化语法,它使用下面的语法将声明数组、创建数组和初始化数组结合到一个语句中:

        elementType[] arrayName={value0,value1,value2,...,valuek};(元素类型[] 数组引用变量={值1,值2,...,值k};)

      说明数组在定义时就完成了空间的开辟和赋值;注意:使用该语法时,必须声明、创建和初始化数组都放在一条语句中。将他们分开会产生语法错误。因此,下面的语句是错误的:。

        int a[];
        a = {10,20,30};//这不行

              2)动态初始化:只告知数组需要几个存储空间,但是并未决定要存哪些值,由系统为每个元素指定初始值。语法格式如下:

      arrayName = new type[length];

//数组的声明和初始化同时完成,使用动态初始化语法
int[] prices = new int[5];
//数组的声明和初始化同时完成,初始化数组时元素的类型是定义数组时元素的子类
Object[] books = new String[5];   

              注意:a.做动态初始化时必须指明数组的元素个数;

                         b.数组一旦初始化,其长度是不可变的。

//创建数组及初始化代码
double[] myList = new double[10];
myList[0] = 5.6;
myList[1] = 4.5;
myList[2] = 3.3;
myList[3] = 13.2;
myList[4] = 4.0;
myList[5] = 34.33;
myList[6] = 34.0;
myList[7] = 45.45;
myList[8] = 99.993;
myList[9] = 11123;

  下图展示了这个数组

3、数组大小和默认值

  当数组分配空间时,必须指定该数组能够存储的元素个数,从而确定数组大小。创建数组之后就不能在修改它的大小。可以使用arrayName.length得到数组的大小。例如:prices.length为5.

  当创建数组后,它的元素被赋予默认值,数值型基本数据类型默认值为0,char型的默认值为'\u0000‘,boolean型的默认值为false,引用数据类型默认值为null.

4、访问数组元素

  使用“数组名[下标]”的方式,数组下标从0开始,到arrayName.length - 1结束。

  数组中的每个元素都可以使用下面的语法表示,称为下标变量(indexed variable)

  arrayName[index];(数组引用变量[下标];)

  例如:prices[4]表示数组prices的最后一个元素。

  创建数组后,下标变量与正常变量的使用方法相同。例如:下面代码将prices[0]和prices[1]的值相加赋给prices[2].

prices[2] = prices[0] + prices[1];

  下面的循环是将0付给prices[0],1赋给prices[1]....4赋给prices[4];

for(int i = 0; i < prices.length; i++){
  prices[i] = i;  
}

5、处理数组

  处理数组元素时,经常会用到for循环,理由有以下两点:

  1).数组中的所有元素都是同一类型的,可以使用循环以同样的方式反复处理这些元素。
  2).由于数组的大小是已知的,所以很自然地使用for循环。
  假如创建如下数组
  double[] list = new double[10];

  下面是一些处理数组的例子:
  1)使用输入值初始化数组:循环使用用户输入的数值初始化数组。

/**
 * 使用输入值初始化数组:循环使用用户输入的数值初始化数组。
 */
public static void inputInitArray() {
  //声明创建了大小为10的double型数组
  double[] list = new double[10];
  Scanner input = new Scanner(System.in);
  System.out.println("请输入" + list.length + "个值:");
  for(int i = 0; i < list.length; i++)
    list[i] = input.nextDouble();
  input.close();
  printArray(list);
  }    

  2)使用随机数初始化数组

/**
 * 使用随机数初始化数组
 */
public static void randomInitArray() {
  double[] list = new double[10];
  //Math.random()---->[0,1)
  for(int i = 0; i < list.length; i++)
    list[i] = Math.random() * 100;
    printArray(list);
}

  3)显示数组,打印数组中的每一个元素

/**
* 打印数组中的每个元素
* @param arr double型数组
*/
public static void printArray(double[] arr) {
  //第一种方式:使用for循环
  System.out.print("[");
  for(int i = 0; i < arr.length; i++)
    System.out.print(arr[i] + (i == arr.length - 1 ? "]\r\n" : ", "));
    //第二种形式:使用Arrays工具类完成打印
    System.out.println("=========================");
    System.out.println(Arrays.toString(arr));
}

  4)对所有元素进行求和,使用名为total的变量存储和。total的值初始化为0.

	/**
	 * 对double数组中所有元素进行求和
	 * @param arr double数组
	 * @return 和
	 */
	public static double sum(double[] arr) {
		double total  = 0;
		for(int i = 0; i < arr.length; i++)
			total += arr[i];
		return total;
	}

  5)找出最大值
  使用名为max的变量存储最大值。将max的值初始化为list[0],为了找出数组list中的最大元素,将每个元素与max比较,如果该元素大于max,则更新max

	/**
	 * 获取double数组中最大元素
	 * @param arr double数组
	 * @return double数组中最大元素
	 */
	public static double getMax(double[] arr) {
		double max = arr[0];
		for(int i = 1; i < arr.length; i++) {
			if(max < arr[i]) max = arr[i];
		}
		return max;
	}

  6) 找出最大元素的最小下标值

	/**
	 * 找出最大元素的最小下标值(如果数组中含有多个最大元素,那么找出最大元素第一次出现的位置)
	 * @param arr double数组
	 * @return double数组中最大元素的最小索引
	 */
	public static int getMaxElementIndex(double[] arr) {
		double max = arr[0];
		int index = 0;
		for(int i = 1; i < arr.length; i++) {
			if(max < arr[i]) {
				max = arr[i];
				index = i;
			}
		}
		return index;
	}

  7)随机打乱,在很多应用中,需要对数组中的元素进行任意的重新排序(如洗牌),这称作打乱(shuffling)。为了完成这种功能,针对每个元素list[i],随意产生一个下标j,然后将list[i]和list[j]互换 .

            

/**
* 随机打乱:对数组中的元素进行任意的重新排序,
* 针对每个元素arr[i],随意产生一个下标j,然后将arr[i]和arr[j]进行互换
* @param arr 要打乱的数组
*/
public static void shuffing(double[] arr) {
  for(int i = 0; i < arr.length; i++) {
    int j  =(int) (Math.random() * (i + 1));
    double temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
  }
  System.out.println(Arrays.toString(arr));
}

  8)移动元素:向左或者向右移动元素,这里的例子就是将元素向左移动一个位置:

/**
 * 移动元素:向左或者向右移动元素,这里的例子就是将数组中每个元素依次向左移动一个位置
 * 将第一个元素值放在最后一个元素
 * @param arr 
 */
 public static void moveElement(double[] arr) {
  double temp = arr[0];
  for(int i = 1; i < arr.length; i++) {
    arr[i - 1] = arr[i];
  }
  arr[arr.length - 1] = temp;
}

  9)简化编码:对于某些任务来说,数组可以极大简化编码,比如前面的生肖年。

String[] years = {"猴","鸡","狗","猪","鼠","牛","虎","兔","龙","蛇","马","羊"};
System.out.println(year + "年的生肖为:" + years[year % 12]);

6、foreach循环
  java支持一个简便的for循环,称为foreach循环,即不使用下标变量就可以顺序地遍历整个数组:

for(double e : list){
    System.out.println(e);
}

  可以读作:对list中每个元素e进行以下操作。注意,变量e必须声明为与list中元素相同的数据类型。

  通常,foreach的语法为:

  for(elementType element : arrayName){

    //Process the element

  }
  但是 ,当需要使用其它顺序遍历数组或改变数组中的元素时,还是必须使用下标变量

  警告:越界访问数组时经常会出现的程序设计错误,会抛出一个运行错误ArrayIndexOutOfBoundsException。为了避免错误的发生,在使用时应该确保所使用的下标不超过数组.length - 1.
数组的索引下标是从0开始的(下标为0表示第一个元素)

7、示例:分析数字
  回到开篇中读取100个数,计算它们的平均值,然后找出有多少个数大于平均值。为了更加灵活地处理任意数目的输入,我们让用户给出输入的个数,而不是将其固定为100.

import java.util.Scanner;

public class AnalyzeNumber {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
      
        System.out.println("请输入要存储的个数");
        int n = input.nextInt();
        
        double[] numbers = new double[n];
        double sum = 0;
        
        System.out.println("请输入您想存储的数字");
        for(int i = 0;i < numbers.length;i++){
            numbers[i] = input.nextDouble();
            sum += numbers[i];
        }
        
        double avg = sum / n;
        System.out.println("平均值为:"+avg);
        
        for (int i = 0; i < n; i++) {
            if(numbers[i] > avg){
                System.out.println(numbers[i]);
            }
        }
        input.close();
    }
}

8:数组的复制(*)

  要将一个数组中的内容复制到另外一个数组中,你需要将数组的每一个元素复制到另外一个数组中。

  在程序中经常需要复制一个数组或数组的一部分。在这种情况下,你可能会尝试使用赋值语句(=),如下所示:

    list2 = list1;

  该语句并不能将list1引用的数组内容复制给list'2,而只是将list1的引用值复制给了list2.在这条语句后,list1和list2都指向同一个数组,如下图所示。list2原先引用的数组不能再引用,它就变成了垃圾,会被java虚拟机自动收回(垃圾回收)。

  在Java中,可以使用复制语句复制基本数据类型的变量,但不能复制数组。将一个数组变量赋值给另一个数组变量,实际上是将一个数组的引用赋值给另一个变量,使两个变量都指向相同的内存地址。

  复制数组有三种方法:

  • 使用循环语句逐个地复制数组的元素。
int[] sourceArray = {2,5,8,10,200,-20};
int[] targetArray = new int[sourceArray.length];
for(int i = 0; i < sourceArray.length; i++)
    targetArray[i] = sourceArray[i];
  • 使用System类中的静态方法arraycopy。
System.arraycopy(sourceArray,srcPos,targetArray,tarPos,length);
其中,参数srcPos和tarPos分别表示在源数组sourceArray和目标数组targetArray中的起始位置,从sourceArray复制到targetArray中的元素个数由参数length指定。例如:
System.arraycopy(sourceArray,0,targetArray,0,sourceArray.length);
  • 使用clone方法复制数组。

示例:一副牌
  从一副52张的牌中随机挑出4张牌。所有的牌可以用一个名为deck的数组表示,这个数组从0到51的初始值来填充:

int[] deck = new int[52];
//初始化牌
for(int i = 0; i < deck.length; i++)
    deck[i] = i;

  牌号从0到12、13到25、26到38以及39到51分别表示13张黑桃、13张红桃,13张方块、13张梅花,cardNumber / 13决定牌的花色,而cardNumber % 13 决定是具体花色中的哪张牌。在洗牌(打乱数组deck)后,从deck中选出前4张牌

                         

                                                   52张牌存储在一个名为deck的数组中

                      

                                                               CardNumber标识一张牌的花色和等级数字

/**
 * 从一副52张的牌中随机挑出4张牌
 * @author Adan
 *
 */
public class DeckDemo {
    public static void main(String[] args) {
        //声明一个52个元素空间大小的int数组来表示52张牌
        int[] deck = new int[52];
        for(int i = 0; i < deck.length; i++) deck[i] = i;
        //洗牌(随机打乱)
        for(int i = 0; i < deck.length; i++) {
            int index = (int)(Math.random() * deck.length);
            int temp = deck[i];
            deck[i] = deck[index];
            deck[index] = temp;
        }
        //定义一个代表扑克牌花色的数组
        String[] suits = {"♠","♥","♣","♦"};
        //同理,定义牌号的数组
        String[] ranks = {"A","2","3","4","5","6","7","8","9","10","J","Q","K"};
        for(int i = 0; i < 3;i++) {
            String suit = suits[deck[i] / 13];//取得花色
            String rank = ranks[deck[i] % 13];
            System.out.println(suit + " " + rank);
        }
    }
}

将数组传递给方法

   当一个数组传递给方法时,数组的引用被传递给方法

public static double getMax(double[] arr) {
    double max = arr[0];
    for(int i = 1; i < arr.length; i++) {
        if(max < arr[i]) max = arr[i];
    }
    return max;
 }

  Java使用按值传递(pass-by-value)的方式将实参传递给方法。传递基本数据类型变量的值与传递数组值有很大的不同。

  • 对于基本数据类型参数,传递的是实参的值。
  • 对于数组类型参数,参数值是数组的引用,给方法传递的这个引用。从语义上来讲,最好的描述就是参数传递的是共享信息(pass-by-sharing),既方法中的数组和传递的数组是一样的。所以,如果改变方法中的数组,将会看到方法外的数组也变化了。 

从方法中返回数组

/**
     * 从方法返回一个数组(反转后的数组)
     * @param list 源数组
     * @return 反转后的数组
     */
    public static int[] reverse(int[] list) {
        int[] result = new int[list.length];
        for(int i = 0,j = result.length - 1; i < list.length; i++,j--) 
            result[j] = list[i];
        return result;
    }

示例:统计每个字母出现的次数

   随机生成100个小写字母并将其放入到一个字符数组中,对该数组中每个字母出现的次数进行统计。

public class StatisticsLetterCount {
	/**
	 * 创建一个大小为100的字符数组,用于存放随机生成的小写字母
	 * @return 字符数组
	 */
	public static char[] createLetters() {
		char[] letters = new char[100];
		for(int i = 0; i < letters.length; i++) {
			letters[i] = generateRandomLowerLetter();
		}
		return letters;
	}

	public static char generateRandomLowerLetter() {
		return (char)('a' + Math.random() * ('z' - 'a'));
	}
	
	public static void showLetterCount(char[] letters) {
		int[] times = new int[26];//每个元素初始值为0,times[0]代表a出现的次数,times[25】代表z出现的次数
		for(int i = 0; i < letters.length; i++) 
			times[letters[i] - 'a']++;
		for(int i = 0; i < times.length; i++) 
			System.out.print((char)('a' + i) + ":" + times[i] + ((i + 1) % 10 == 0 ? "\r\n" : "\t"));
	}
	
	public static void main(String[] args) {
		showLetterCount(createLetters());
	}
}

可变长参数列表

   具有相同类型的可变长参数可以传递给方法,可变参数的本质为数组

public static int sum(int ... nums) {
	if(nums == null) return 0;
	int sum = 0;
	for(int num : nums) sum+= num;
	return sum;
}

  注意:一个方法只能有一个可变参数且只能出现在最后位置。

数组的查找

  • 线性查找
     /**
       * 线性查找关键字元素key在数组中的索引
	 * 从数组的第一个元素依次的比较,如果该元素==关键字元素,返回该元素对应的索引。
	 * @param key 关键字
	 * @param list 数组
	 * @return 如果查找到该元素,就返回该元素对应的索引,没有找到返回-1
	 */
	public static int linearSearch(int key,int[] list) {
		for(int i = 0; i < list.length; i++) {
			if(list[i] == key) return i;
		}
		return -1;
	}
  • 二分查找
     /**
	 * 折半查找
	 * 		前提:数组是一个有序数组
	 * 每次折半取中间元素
	 * 1.如果关键字小于中间元素,只需要在数组的前一半进行查找
	 * 2.如果关键字大于中间元素,只需要在数组的后一半进行查找
	 * 3.相等,就返回mid
	 */
	public static int binarySearch(int key, int[] list) {
		int low = 0;
		int high = list.length - 1;
		while(high >= low) {
			System.out.println("-----");
			int mid = (low + high) >>> 1;
			if(key < list[mid])
				high = mid - 1;
			else if(key > list[mid])
				low = mid + 1;
			else
				return mid;
		}
		return -low - 1;
	}

数组的排序

  • 选择排序
package edu.uestc.avatar;

import java.util.Arrays;

public class ArraySort {
    /**
     * 使用选择排序将数组中的元素进行升序排列
     *     首先找到数组中最小的数,然后将它和第一个元素进行交换
     *     在剩下的数中找到最小数,然后将它和第二个元素进行交换
     *     依次类推,直到数列中只剩下一个元素
     * @param array 要排序的整数数组
     */
    public static void selectionSort(int[] array) {
        for(int i = 0; i < array.length - 1; i++) {
            int currentMin = array[i];
            int currentMinIndex = i;
            for(int j = i + 1; j < array.length; j++) {
                if(currentMin > array[j]) {
                    currentMin = array[j];
                    currentMinIndex = j;
                }
            }
            if(currentMinIndex != i) {
                array[currentMinIndex] = array[i];
                array[i] = currentMin;
            }
        }
    }
    
    public static void main(String[] args) {
        int[] list = {40, 60, 75, 72, 61, 73, 18, 35, 95, 70};
        selectionSort(list);
        System.out.println(Arrays.toString(list));
    }
}
  • 冒泡排序

Arrays类(java.util.Arrays)

  1.Arrays类包含一些实用的方法用于常见的数组操作,比如排序和查找

  2.Arrays类包含各种各样的静态方法,用于实现数组的排序和查找、数组的比较和填充数组元素,以及返回数组的字符串表示。这些方法都有对所有基本类型的重载方法。

  3.可以使用sort或parallelSort方法对整个数组或部分数组进行排序。

  4.可以采用二分查找法(binarySearch方法)在数组中查找关键字。数组必须提前按升序排列好。如果数组中不存在关键字,方法返回 -(插入点下标+1)。

  5.可以采用equals方法检测两个数组是否相等。如果他们的内容相同,那么这两个数组相等。

  6.可以使用fill方法填充整个数组或部分数组。

  7.可以是同toString方法来返回一个字符串,该字符串中代表了数组中的所有元素。这是一个显示数组中所有元素的快捷和简便的方法。

命令行参数

 

课后作业(练习)

1.有一个数组:8,4,2,1,23,344,12

  1)循环输出数组的值——使用三种方式。

  2)求数组中所有数值的和。

2.猜数游戏:从键盘中任意输入一个数据,判断数组中是否包含此数,要求每一个功能独立成方法。

3.找出两个数组中的差异元素并存入一个新的数组并排序,假设每个数组内部都没有重复元素。

4.取1-100的10个随机数存入数组中,要求数据不能重复

5. 设计一个方法,将数组中的数据打乱顺序

6.n(自定义输入)个人围成一圈,并依次编号1-n,从编号为1的人开始按顺时针方向每隔一人选出一个,剩下的人重新围成一圈,如此循环,直到只剩下1,2人,如果你想成为这两个幸运儿,问:最开始你应该站在什么位置.

7.给定一个整数数组,例如{6,4,7,2,5,8}和一个数字,例如10,请设计一个函数找出两个元素(或同一个元素加自身),并且使这两个数的和为给定数字,并打印出来(提示:先进行数组排序)

8.给定一个含有n个元素的整型数组a例如{1,1,1,2,4,3,3} ,如果某些元素出现的次数为奇数次,则将其输出:例如1,2,4

9.冒泡排序(下沉排序)

10.打印两个有序数组中的共同元素

 

 
posted @ 2020-03-17 21:46  Tiger-Adan  阅读(2302)  评论(2编辑  收藏  举报