Lesson07_数组
数组的定义
-
概述
数组可以看成是多个相同类型数据组合,对这些数据的统一管理,数组变量属引用类型。简单的说,数组就是同一种类型数据的集合。其实数组就是一个容器。数组中的每个数据被称作元素。在数组中可以存放任意类型的元素,但同一个数组里存放的元素类型必须一致。它可以自动给数组中的元素从0开始编号,方便操作这些元素。
-
Java当中具有持有数据功能的容器中,数组是最基本的,也是运算速度最快的。
-
定义:数组是相同类型数据的有序集合。数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。其中,每一个数据称作一个元素,每个元素可以通过一个索引(下标)来访问它们。
-
简单的来说,数组是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,并通过编号的方式 对这些数据进行统一管理。
-
基本特点:
- 长度是确定的。数组一旦被创建,它的大小就是不可以改变的。
- 其元素必须是相同类型,不允许出现混合类型。
- 数组类型可以是任何数据类型,包括基本类型和引用类型。
- 每个数组都有一个名为length的只读属性,表示数组的长度。
- 访问数组元素通过[]和下标
- 新生成的数组,如果是引用类型数据,里面元素默认值为null,如果是基本数据类型,都是各自的默认值(数值型、char为0,boolean类型为false)
-
几个常见概念
- 数组名:即数组的名称,与变量名想对应,命名的时候必须要遵循Java中的命名规则。
- 下标:下标(或索引):即数组的编号,需要注意的是数组的下标(或索引)是从0开始编号的。
- 元素:数组中每一个数据即为数组的元素,在数组中每一个元素的数据类型是一致的。
- 数组的长度:数组中元素的个数就是数组的长度,使用“数组名.length”来表示数组的长度。
-
注意:
- 数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型;
- 数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中存储的。
- 创建数组对象会在内存中开辟一整块连续的空间,而数组名中引用的是这块连续空间的首地址
-
数组的分类
- 按照维度:一维数组、二维数组、三维数组、…
- 按照元素类型:基本数据类型元素的数组、引用数据类型元素的数组(即对象数组)。
数组的创建
-
声明
注意:java语言中声明数组时不能指定长度(元素的个数),如int [5] arr或者int arr [5]是非法的。
// 数组的声明(不开辟内存空间) int[] arr ; boolean[] booArr; String[] strArr; //[]可以放在后面,推荐使用第一种 type arr_name [];
-
赋值
arr[0] = 1;// 给数组arr的第一个元素赋值为1(下标从0开始)
-
定义
int[] arr = new int[10];// 创建一个长度为10的int数组,默认全是0;若是布尔数组,默认全是false int[] arr = {1,2,3,4,5};// 创建的时候就赋值 int [] arr = new int [] {1,3,5};//后面不能指定长度,长度是由实际元素个数决定的。
-
初始化
-
我们前面讲到变量的初始化,如果说需要使用的变量,必须先声明、初始化之后才可以使用。数组可以看作是一个特殊的变量。那么同样的数组也要通过声明、初始化之后才可以使用的。
-
数组的初始化方式总共有三种:静态初始化、动态初始化、默认初始化。下面针对这三种方式分别讲解。
-
静态初始化
除了用new关键字来产生数组以外,还可以直接在定义数组的同时就为数组元素分配空间并赋值。
int[] a = { 1, 2, 3 };// 静态初始化基本类型数组; Man[] mans = { new Man(1, 1), new Man(2, 2) };// 静态初始化引用类型数组;
-
动态初始化
数组定义与为数组元素分配空间并赋值的操作分开进行。
int[] a1 = new int[2];//动态初始化数组,先分配空间; a1[0]=1;//给数组元素赋值; a1[1]=2;//给数组元素赋值;
-
默认初始化
数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。
int a2[] = new int[2]; // 默认值:0,0 boolean[] b = new boolean[2]; // 默认值:false,false String[] s = new String[2]; // 默认值:null, null
-
-
注意:
- 不管是何种初始化方式初始化数组,数组的长度是固定的;
- 如果要初始化一个空数组,则必须使用动态初始化方式为其赋予长度
- 定义并用运算符new为之分配空间后,才可以引用数组中的每个元素;
- 数组一旦初始化,其长度是不可变的
-
-
内存分布(图)
- 数组在内存中是连续的
-
实例
-
创建基本类型一维数组(数组(堆内存)存放值,如图1)
public class Test { public static void main(String args[]) { int[] s = null; // 声明数组; s = new int[10]; // 给数组分配空间; for (int i = 0; i < 10; i++) { s[i] = 2 * i + 1;//给数组元素赋值; System.out.println(s[i]); } } }
-
创建引用类型一维数组(数组中(堆内存)存放对象引用,如图2)
class Man{ private int age; private int id; public Man(int id,int age) { super(); this.age = age; this.id = id; } } public class AppMain { public static void main(String[] args) { Man[] mans; //声明引用类型数组; mans = new Man[10]; //给引用类型数组分配空间; Man m1 = new Man(1,11); Man m2 = new Man(2,22); mans[0]=m1;//给引用类型数组元素赋值; mans[1]=m2;//给引用类型数组元素赋值; } }
-
-
注意:
- 声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM才分配空间,这时才与长度有关。
- 声明一个数组的时候并没有数组真正被创建。
- 构造一个数组,必须指定长度。
名词解释
- 声明
- 基本数据类型:int a;声明时开辟空间
- 引用数据类型:int[] a;在栈中开辟空间,指向null
- 初始化
- 基本数据类型:int a = 3;第一次赋值
- 引用数据类型:String str = “abc”;第一次引用,即声明时引用
- 定义
- 基本数据类型:int a = 3;
- 引用数据类型:User user = new User();当然我们也可以在基本定义方式上增加修饰符:private、public等,也可以在()传入构造函数的参数
- 变量定义方式包含了4个动作:变量的声明、引用、创建、初始化。
- 创建
- 一般用在引用数据类型中,new关键字即为创建。
- 赋值、引用
- 变量名 = 值;int a = 0;int b;b=3;为赋值
- 变量名 = 对象;User user = users;User user;user=users;为引用
- 赋值与引用概念基本相同,赋值是针对基本数据类型,引用是针对引用数据类型。
数组的遍历
-
普通for循环遍历数组
数组元素下标的合法区间:[0, length-1]。我们可以通过下标来遍历数组中的元素,遍历时可以读取元素的值或者修改元素的值。
public class Test {
public static void main(String[] args) {
int[] a = new int[4];
//初始化数组元素的值
for(int i=0;i<a.length;i++){
a[i] = 100*i;
}
//读取元素的值
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
}
}
-
增强for循环遍历数组
-
增强for循环for-each是JDK1.5新增加的功能,专门用于读取数组或集合中所有的元素,即对数组进行遍历。
-
语法
for(type type_name:arr_name) { System.out.println(type_name); }
-
实例
public class Test { public static void main(String[] args) { String[] ss = { "aa", "bbb", "ccc", "ddd" }; for (String temp : ss) { System.out.println(temp); } } }
-
注意
-
for-each增强for循环在遍历数组过程中不能修改数组中某元素的值。
-
for-each仅适用于遍历,不涉及有关索引(下标)的操作
public void test1(){ String[] ss = { "aa", "bbb", "ccc", "ddd" }; int i = 0; for (String s : ss) { ss[i]=s+i; i++; System.out.println(s); } System.out.println("**************"); for (String s : ss) { System.out.println(s); } } } //结果如下,虽然可以强行利用for-each改变数组元素,但是对于当前for-each的遍历没有影响 aa bbb ccc ddd ************** aa0 bbb1 ccc2 ddd3
-
-
数组的内存结构
内存是计算机临时存储数据的区域,我们会将内存在逻辑上分配成不同区域方便对数据进行分类高效管理。
-
寄存器:最快的存储区域直接与CPU打交道,是程序员无法控制的计算区域。
-
堆栈:又叫栈,仅次于寄存器。用于存储局部变量。
-
堆:通用内存池,用于存放所有引用数据类型对象。每个对象均有地址,且有默认初始化值。
-
方法区:又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量;其中包含的都是在程序中永远的唯一的元素
- 常量存储区域:用于存放永远不会被改变的值。
- 静态域:存放程序运行时一直存在的数据。
-
Java中栈(stack)和堆(heap)是Java在内存(ram)中存放数据的地方。
-
栈(stack):线程私有的内存区域,栈中只保存基本数据类型的对象和自定义对象的引用(不是对象),对象都存放在共享heap中;每个栈中的数据(基本数据类型和对象引用)都是私有的,其他栈不能访问;栈内存中放哪些东西?
- 基本类型的变量,例如int a=3中的a;
- 对象的引用变量,例如Thread t=new Thread();中的t。
当在代码块中定义一个变量时,Java就在栈中为这个变量分配内存空间;当超过变量的作用域后,Java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。
-
堆(heap):它是一个可动态申请的内存空间(其记录空闲内存空间的链表由操作系统维护),用来存放由new创建的对象和数组。jvm只有一个heap区,被所有线程共享,不存放基本类型和对象引用,只存放对象本身。堆内存中存放哪些东西?
在堆中存放的内存,由Java虚拟机垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量持有的内容等于数组或者对象在堆内存中的首地址。在栈中的这个特殊的变量,就成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。
-
方法区:与Java堆一样,是各个线程共享的内存区域。方法区存储什么?
- 它存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
- 所有定义的方法的信息都保存在该区域,此区域属于共享区间。静态变量+常量+类信息+运行时常量池存在方法区中,实例变量存在堆内存中。
- 注意:
- JVM只有一个方法区,被所有线程共享;
- 存着所有的class文件信息和static变量。
-
注意
- 数组和对象,通过new建立的实例都存放在堆内存中。
- 每一个实体都有内存地址值,存储在栈中
- 实体中的变量都有默认初始化值
- 实体不在被使用,会在不确定的时间内被垃圾回收器回收
- 声明创建数组时内存
- 定义两个数组,先定义一个数组赋值,输出。然后定义第二个数组的时候把第一个数组的地址赋值给第二个数组。然后给第二个数组赋值,再次输出两个数组的名及元素。
-
数组的拷贝
System类里也包含了一个static void arraycopy(object src,int srcpos,object dest, int destpos,int length)方法,该方法可以将src数组里的元素值赋给dest数组的元素,其中srcpos指定从src数组的第几个元素开始赋值,destops是讲src的元素从dest哪个下标开始复制,length参数指定将src数组的多少个元素赋给dest数组的元素。
public class Test {
public static void main(String args[]) {
String[] s = {"阿里","尚学堂","京东","搜狐","网易"};
String[] sBak = new String[6];
System.arraycopy(s,0,sBak,0,s.length);
for (int i = 0; i < sBak.length; i++) {
System.out.print(sBak[i]+ "\t");
}
}
}
数组的查找
-
线性查找(查找最大值或最小值)
-
二分查找
二分法检索(binary search)又称折半检索,二分法检索的基本思想是设数组中的元素从小到大有序地存放在数组(array)中,首先将给定值key与数组中间位置上元素的关键码(key)比较,如果相等,则检索成功;
否则,若key小,则在数组前半部分中继续进行二分法检索;
若key大,则在数组后半部分中继续进行二分法检索。
这样,经过一次比较就缩小一半的检索区间,如此进行下去,直到检索成功或检索失败。
二分法检索是一种效率较高的检索方法。比如,我们要在数组[7, 8, 9, 10, 12, 20, 30, 40, 50, 80, 100]中查询到10元素,过程如下:
import java.util.Arrays; public class Test { public static void main(String[] args) { int[] arr = { 30,20,50,10,80,9,7,12,100,40,8}; int searchWord = 20; // 所要查找的数 Arrays.sort(arr); //二分法查找之前,一定要对数组元素排序 System.out.println(Arrays.toString(arr)); System.out.println(searchWord+"元素的索引:"+binarySearch(arr,searchWord)); } public static int binarySearch(int[] array, int value){ int low = 0; int high = array.length - 1; while(low <= high){ int middle = (low + high) / 2; if(value == array[middle]){ return middle; //返回查询到的索引位置 } if(value > array[middle]){ low = middle + 1; } if(value < array[middle]){ high = middle - 1; } } return -1; //上面循环完毕,说明未找到,返回-1 } }
数组的排序
-
冒泡
冒泡排序是最常用的排序算法,在笔试中也非常常见,能手写出冒泡排序算法可以说是基本的素养。
算法重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来,这样越大的元素会经由交换慢慢“浮”到数列的顶端。
public static void bubbleSort(int [] arr){ for (int i = 0; i < arr.length-1; i++) { //控制每一轮的比较,从第一个开始到最后一个 //没一轮都会将最大值放在最后一位,第n轮后面排好的数有n所以是arr.length-i //第一轮时j+1是最后一位,所以j+1<arr.length也就是j<arr.length-1; for (int j = 0; j < arr.length-i-1; j++) { //相邻两个元素比较,如果前面大于后面,则位置交换 if (arr[j]>arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } System.out.println(Arrays.toString(arr)); }
冒泡排序算法的运作如下:
1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3. 针对所有的元素重复以上的步骤,除了最后一个。
4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 ```java import java.util.Arrays; public class Test { public static void main(String[] args) { int[] values = { 3, 1, 6, 2, 9, 0, 7, 4, 5, 8 }; bubbleSort(values); System.out.println(Arrays.toString(values)); } public static void bubbleSort(int[] values) { int temp; for (int i = 0; i < values.length; i++) { for (int j = 0; j < values.length - 1 - i; j++) { if (values[j] > values[j + 1]) { temp = values[j]; values[j] = values[j + 1]; values[j + 1] = temp; } } } } } ``` **基于冒泡排序的以下特点:** 1. 整个数列分成两部分:前面是无序数列,后面是有序数列。 2. 初始状态下,整个数列都是无序的,有序数列是空。 3. 每一趟循环可以让无序数列中最大数排到最后,(也就是说有序数列的元素个数增加1),也就是不用再去顾及有序序列。 4. 每一趟循环都从数列的第一个元素开始进行比较,依次比较相邻的两个元素,比较到无序数列的末尾即可(而不是数列的末尾);如果前一个大于后一个,交换。 5. 判断每一趟是否发生了数组元素的交换,如果没有发生,则说明此时数组已经有序,无需再进行后续趟数的比较了。此时可以中止比较。 优化 ```java import java.util.Arrays; public class Test1 { public static void main(String[] args) { int[] values = { 3, 1, 6, 2, 9, 0, 7, 4, 5, 8 }; bubbleSort(values); System.out.println(Arrays.toString(values)); } public static void bubbleSort(int[] values) { int temp; int i; // 外层循环:n个元素排序,则至多需要n-1趟循环 for (i = 0; i < values.length - 1; i++) { // 定义一个布尔类型的变量,标记数组是否已达到有序状态 boolean flag = true; /*内层循环:每一趟循环都从数列的前两个元素开始进行比较,比较到无序数组的最后*/ for (int j = 0; j < values.length - 1 - i; j++) { // 如果前一个元素大于后一个元素,则交换两元素的值; if (values[j] > values[j + 1]) { temp = values[j]; values[j] = values[j + 1]; values[j + 1] = temp; //本趟发生了交换,表明该数组在本趟处于无序状态,需要继续比较; flag = false; } } //根据标记量的值判断数组是否有序,如果有序,则退出;无序,则继续循环。 if (flag) { break; } } } } ```
-
选择
public static void selectSort(int [] arr){ //设置最小元素的下标 int min; //从第一个元素开始,最后一个不用排 for (int i = 0; i < arr.length-1; i++) { //默认当前元素就是最小值 min = i; //从下一个元素开始依次往后找到最小值,记录下标 for (int j = i+1; j < arr.length; j++) { if (arr[min]>arr[j]) { min=j; } } //如果记录的下标不是当前元素,则将当前元素替换为最小值 if (min != i) { int temp = arr[i]; arr[i] = arr[min]; arr[min] = temp; } } System.out.println(Arrays.toString(arr)); }
-
插入
public static void insertSort(int [] arr){ //默认第一个元素就是有序,从第二个开始,依次插入到有序元素中 for (int i = 1; i < arr.length; i++) { //记录当前需要插入的元素 int temp = arr[i]; //记录有序元素的最后位置 int j = i-1; //开始插入,有序元素下标最小为0,插入时有序元素依次后移,直到 //当前需要插入的元素不大于有序元素则就是需要插入的位置。 while (j>=0 && arr[j] > temp) { //将有序元素中大于需要插入元素的所以元素依次后移 arr[j+1] = arr[j]; j--; } //由于最后一次移动以后j又自减,所以需要插入的位置是j+1的位置。 arr[j+1] = temp; } System.out.println(Arrays.toString(arr)); }
Arrays工具类
JDK提供的java.util.Arrays类,包含了常用的数组操作,方便我们日常开发。Arrays类包含了:排序、查找、填充、打印内容等常见的操作。
-
数组的打印
import java.util.Arrays; public class Test { public static void main(String args[]) { int[] a = { 1, 2 }; System.out.println(a); // 打印数组引用的值; System.out.println(Arrays.toString(a)); // 打印数组元素的值; } }
注意:此处的Arrays.toString()方法是Arrays类的静态方法,不是前面讲的Object的toString()方法。对于Arrays.toString()而言,当数组中有数组时,不会打印出数组中的内容,只会以地址的形式打印出来,当里面只有数据时,会打印输出。
-
数组的排序
-
基本数据类型
import java.util.Arrays; public class Test { public static void main(String args[]) { int[] a = {1,2,323,23,543,12,59}; System.out.println(Arrays.toString(a)); Arrays.sort(a); //此方法是Arrays提供的,将对数组进行升序排序,没有提供降序排序的方法 System.out.println(Arrays.toString(a)); } }
-
引用数据类型
import java.util.Arrays; public class Test { public static void main(String[] args) { Man[] msMans = { new Man(3, "a"), new Man(60, "b"), new Man(2, "c") }; Arrays.sort(msMans); System.out.println(Arrays.toString(msMans)); } } class Man implements Comparable { int age; int id; String name; public Man(int age, String name) { super(); this.age = age; this.name = name; } public String toString() { return this.name; } public int compareTo(Object o) { Man man = (Man) o; if (this.age < man.age) { return -1; } if (this.age > man.age) { return 1; } return 0; } }
-
-
数组的填充
数组中的元素定义完成之后,可以通过Arrays类中的静态方法fill()来对数组进行替换或者赋值。fill()方法可以对数组全部进行替换或者赋值,也可以对指定下标(索引)的元素进行替换或者赋值。
对全部元素进行替换或者赋值:fill(数组的名称,值)。这里需要注意的是,这个值的数据类型一定要与数组的数据类型保持一致。
对指定下标范围的元素进行替换或赋值:格式:fill(数组的名称,下标1,下标2,值)。下标1:需要替换或者赋值的起始下标,值不能小于0;下标2:需要替换或者赋值的终止下标,值不能大于数组的长度;值:值的数据类型一定要与数组的数据类型保持一致。
import java.util.Arrays; public class Test { public static void main(String[] args) { int[] a= {1,2,323,23,543,12,59}; System.out.println(Arrays.toString(a)); Arrays.fill(a, 2, 4, 100); //将2到4索引的元素替换为100; System.out.println(Arrays.toString(a)); } }
-
二分查找法
- 对这个数组查找
- 格式: binarySearch(数组名称,搜索的值);
- 注意:在这个方法,如果这个搜索的值在数组中,则会返回搜索值所在的下标,否则返回小于0的数。在查询之前必须对数组进行排序,否则结果是不确定的。如果数组中有多个指定值的元素,则无法保证找到的哪一个。因此这个查询方法针对的数组是有序、无重复的。
- 在指定范围进行查找
- 格式: binarySearch(数组名称,起始下标,终止下标,搜索的值);
- 注意:在这个方法,起始下标不能小于0,终止下标不能大于数组的长度。
import java.util.Arrays; public class Test { public static void main(String[] args) { int[] a = {1,2,323,23,543,12,59}; System.out.println(Arrays.toString(a)); Arrays.sort(a); //使用二分法查找,必须先对数组进行排序; System.out.println(Arrays.toString(a)); //返回排序后新的索引位置,若未找到返回负数。 System.out.println("该元素的索引:"+Arrays.binarySearch(a, 12)); } }
- 对这个数组查找
-
数组的拷贝
在Arrays类中,有两个方法可以对数组进行复制,分别是copyOf()和copyOfRange()。
- copyOf()方法
- 格式:copyOf(数组名称,长度);
- 注意:
- 这儿的数组指的是需要进行复制的数组(原数组);长度是指复制后新数组的长度,如果新数组的长度大于原数组的长度则用数组数据类型对应的默认值填充;这种情况,我们称之为给数组扩容(字面意思,为数组扩大容量)。
- 如果新数组的长度小于原数组的长度,新数组则会从原数组的第一个元素开始截取直到满足新数组长度为止,可以简单的理解为缩容。
- 例9:使用Arrays.copyOf()给数组int[]arr={8,2,4,6,5,9}进行扩容和缩容复制。
- copyOfRange()方法
- 格式:copyOfRange(数组名称,起始下标,终止下标);
- 注意:
- 起始下标:原数组的起始下标,值不能小于0;
- 终止下标:原数组的终止下标,如果大于原数组的长度则用数组数据类型的默认值填充;
- 例10:使用Arrays.copyOfRange()给数组int[]arr={8,2,4,6,5,9}进行指定范围的复制。
int[] brr = Arrays.copyOf(arr,10); // arr -> brr , brr的长度是10 System.out.println(Arrays.toString(brr)); // 数组拷贝,某一个范围 int[] crr = Arrays.copyOfRange(arr,3,5); // 拷贝arr中的[3,5)区间的元素 System.out.println(Arrays.toString(crr));
- copyOf()方法
-
综合示例
/**
* Arrays工具类
*/
import java.util.Arrays;
public class ArraysDemo{
public static void main(String[] args){
int[] arr = {1,2,3,4,5,6,7,8,9};
// 二分查找
int index = Arrays.binarySearch(arr,3);// 数字3在数组arr中的下标
System.out.println("下标为:"+index);
// 数组拷贝
int[] brr = Arrays.copyOf(arr,10); // arr -> brr , brr的长度是10
System.out.println(Arrays.toString(brr));
// 数组拷贝,某一个范围
int[] crr = Arrays.copyOfRange(arr,3,5); // 拷贝arr中的[3,5)区间的元素
System.out.println(Arrays.toString(crr));
// 填充数组
int[] drr = new int[10];
Arrays.fill(drr,6); // 将数组drr中的每个元素都赋值为6
System.out.println(Arrays.toString(drr));
// 数组排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
二维数组
多维数组可以看成以数组为元素的数组。可以有二维、三维、甚至更多维数组,但是实际开发中用的非常少。最多到二维数组(学习容器后,我们一般使用容器,二维数组用的都很少)。
一维数组中的每一个元素都是一维数组
-
内存图
-
声明
public class Test { public static void main(String[] args) { // Java中多维数组的声明和初始化应按从低维到高维的顺序进行 int[][] a = new int[3][]; a[0] = new int[2]; a[1] = new int[4]; a[2] = new int[3]; // int a1[][]=new int[][4];//非法 } }
-
静态初始化
public class Test { public static void main(String[] args) { int[][] a = { { 1, 2, 3 }, { 3, 4 }, { 3, 5, 6, 7 } }; System.out.println(a[2][3]); } }
-
动态初始化
import java.util.Arrays; public class Test { public static void main(String[] args) { int[][] a = new int[3][]; // a[0] = {1,2,5}; //错误,没有声明类型就初始化 a[0] = new int[] { 1, 2 }; a[1] = new int[] { 2, 2 }; a[2] = new int[] { 2, 2, 3, 4 }; System.out.println(a[2][3]); System.out.println(Arrays.toString(a[0])); System.out.println(Arrays.toString(a[1])); System.out.println(Arrays.toString(a[2])); } }
-
获取长度
//获取的二维数组第一维数组的长度。 System.out.println(a.length); //获取第二维第一个数组长度。 System.out.println(a[0].length);
-
综合示例
// 定义数组1
int[][] arr = {{1,2},{3,4,5},{6,7,8,9}};
int a = arr[2][1];// a == 7
// 定义数组2
int[][] arr = new int[3][]; // arr含有3个元素
arr[0] = new int[2]; // 每个元素都是个int[]
arr[1] = new int[3];
arr[2] = new int[4];
arr[0][0] = 1;
arr[0][1] = 2;
arr[1][0] = 3;
arr[1][1] = 4;
arr[1][2] = 5;
arr[2][0] = 6;
arr[2][1] = 7;
arr[2][2] = 8;
arr[2][3] = 9;
// 定义数组3
int[][] arr = new int[8][8];// 8行8列的二维int数组
杨辉三角
/**
* // 杨辉三角
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
...
输出杨辉三角的前8行
* 介绍:第一列和最后一列是1,其它元素是上一个和上一个左边的那个元素的和
*/
import java.util.Arrays;
public class YangHuiDemo{
public static void main(String[] args){
int[][] arr = new int[8][];
for(int i = 0; i < arr.length; i ++){
arr[i] = new int[i+1];
}
for(int i = 0; i < arr.length; i ++){
for(int j = 0; j < arr[i].length; j ++){
// 第一列和最后一列的元素是1
if(j == 0 || j == i){
arr[i][j] = 1;
}else{
arr[i][j] = arr[i-1][j] + arr[i-1][j-1];
}
}
}
// 遍历二维数组
for(int i = 0; i < arr.length; i ++){
for(int j = 0; j < arr[i].length; j ++){
System.out.print(arr[i][j] + " ");
}
System.out.println();// 为了好看
}
}
}
常见错误(异常)
在使用数组时,因为不当的操作,我们可能会通过编译,但是在运行期间遇到一些程序报错类似这样编译时不报错,运行期报错的错误叫运行时错误。
数组最常见的两个运行时错误:空指针异常和数组索引越界异常。
-
数组下标越界(ArrayIndexOutOfBoundsException)
在数组操作中如果出现这种异常,一定是数组下标越界。为什么会造成这种异常,比如说,我声明一个数组,数组的长度为5,但在请求的是请求的是下标为6时就会出现这种异常。
public class ArrayExceptionDemo{ public static void main(String[]args){ int[]arr={1,2,3,4,5};//数组的长度为5 System.out.println(arr[6]); } }
-
空指针异常(NullPointerException)
在数组arr引用没有指向实体,却在操作实体中的元素时
public class ArrayExceptionDemo{ public static void main(String[]args){ int[]arr=null; System.out.println(arr[0]); } }
空指针异常是Java中最常见的异常之一,它不仅在数组的使用中会出现,还会在其他地方也容易出现。
Random工具类
- 概念:
- Random这个类是为了解决我们在程序开发过程中,需要使用到随机数而产生的。Random类中实现的随机算法是伪随机,也就是有规则的随机。
- 在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。相同种子数的Random对象,相同次数生成的随机数字是完全相同的。也就是说,两个种子数相同的Random对象,第一次生成的随机数字完全相同,第二次生成的随机数字也完全相同(也就是说同一个代码运行的第一次跟第二次的结果是一样的)。这点在生成多个随机数字时需要特别注意。
- 注意:
- Random类位于java.util包中,主要用于生成伪随机数。
- Random类将种子数作为随机算法的起源数字,计算生成伪随机数,其与生成的随机数字的区间无关。
- 创建Random实例时,若没有指定种子数,则会以当前时间作为种子数,来计算生成伪随机数。
- 拥有相同种子的Random实例,在相同次数下,生成的伪随机数完全相同。
- 用法:
- random.nextBoolean():用于从该随机数生成器的序列中得到下一个伪均匀分布的boolean
- random.nextBytes():用于生成随机字节并将其放入用户提供的byte数组中。
- random.nextDouble():用于从该随机数生成器的序列中得到下一个伪均匀分布在0.0到1.0之间的double值,[0.0, 1.0)。
- random.nextFloat():用于从该随机数生成器的序列中得到下一个伪均匀分布在0.0到1.0之间的float值,[0.0, 1.0)。
- random.nextInt():
- random.nextInt():用于从该随机数生成器的序列中得到下一个伪均匀分布的int值。
- random.nextInt(int bound):用于从该随机数生成器的序列中得到下一个0到结束值之间伪均匀分布的int值,[0, 结束值)。
- random.nextGaussian():用于从该随机数生成器的序列中得到下一个伪均匀分布的double值;生成的double值符合均值为0,方差为1的正态分布(高斯分布)。
- 使用步骤(参考Scanner类):
- 第一步:导包–>import java.util.Random;
- 第二步:创建对象(实例化对象):Random random=new Random();
- 第三步:调用方法:
- nextInt(int maxValue) 产生[0,maxValue)范围的随机数
- nextDouble() 产生[0,1)范围的随机数
- 实例:Random random = new Random ();
- int myNumber = random.nextInt(100);//结果为0-99的一个数
- double myNumber2 = random.nextDouble();//结果为0-1之间的一个小数
练习题及参考答案
-
求阶乘
@author 姚飞 5! = 5 * 4 * 3 *2 * 1 4! = 4 * 3 * 2 * 1 3! = 3 * 2 * 1 2! = 2 * 1 1! = 1 5! + 4! = 5 * 4! + 4! = (5+1) * 4! 5! + 4! + 3! = (5+1) * 4! + 3! = (5+1)*4 * 3! + 3! = ((5+1)*4+1) * 3! 5! + 4! + 3! + 2! = ((5+1)*4+1) * 3! + 2! = ((5+1)*4+1) * 3 * 2! + 2! = (((5+1)*4+1)*3+1) * 2! 5! + 4! + 3! + 2! + 1!= ((5+1)*4+1)*3+1)*2!+1! = ((5+1)*4+1)*3+1)*2 *1! + 1! = ((5+1)*4+1)*3+1)*2+1 // 求7!+6!+5!+4!+3!+2!+1!的值 int res = 1; for(int i = 7; i >= 2; i --){ res = res*i+1; } System.out.println(res);
-
数组的遍历
int[] arr = {1,2,3,4,5}; // 数组的遍历 for(int i = 0; i < arr.length; i ++){ // 最后一个元素的下标是 arr.length-1 System.out.println(arr[i]); }
-
在一维数组中存储5个数,先输出下标为奇数的元素,再输出下标为偶数的元素
int[] arr = {1,2,3,4,5}; // 数组的遍历 for(int i = 0; i < arr.length; i ++){ if(i%2 == 1){// 下标为奇数 System.out.println(arr[i]); } } for(int i = 0; i < arr.length; i ++){ if(i%2 == 0){// 下标为偶数 System.out.println(arr[i]); } }
-
定义一个数组,在数组中保存1到10,然后将所有数组元素相乘并输出结果。
/** * 3.定义一个数组,在数组中保存1到10, * 然后将所有数组元素相乘并输出结果。 * 思路:依次取出数组中的每一个元素,相乘 * [] 是访问数组元素的唯一方式 */ public class Ex2{ public static void main(String[] args){ int[] arr = {1,2,3,4,5,6,7,8,9,10}; int res = 1; // 求乘积,初始值不能是0 for(int i = 0; i < arr.length; i ++){ res *= arr[i]; } System.out.println("res = "+ res); } }
-
将一个数组中的元素全部【复制】到另一个数组中。
int[] arr = new int[20]; Random random = new Random(); // 通过随机数生成器给数组arr赋值 for(int i = 0; i < arr.length; i ++){ arr[i] = random.nextInt(); } int[] brr = new int[arr.length]; // 拷贝:依次取出arr中的每一个元素,赋值给brr中对应位置的元素 for(int i = 0; i < arr.length; i ++){ brr[i] = arr[i]; } // 检验结果 for(int i = 0; i < arr.length; i ++){ System.out.println(arr[i] + " : "+ brr[i]); }
-
【线性查找】键盘输入一个数字,判断数组中是否包含该数字。如果有,则输出其下标;否则,提示找不到。
/** * 线性查找指定元素 * 核心思路就是【遍历】 */ import java.util.Scanner; public class FindDemo{ public static void main(String[] args){ int[] arr = {1,9,2,8,3,7,4,6,5}; Scanner input = new Scanner(System.in); System.out.println("请输入你要查找的数字"); int target = input.nextInt(); // 开始查找(遍历) boolean isFind = false;// 标记是否找到 for(int i = 0; i < arr.length; i ++){ if(arr[i] == target){ System.out.println("找到,下标为:"+i); isFind = true;// 表示已经找到 break;// 如果考虑存在多个目标元素,则不写break } } /* 程序执行到这里,有两种情况: ① 找到后,循环结束,break跳转到这里 ② 遍历完毕,没有找到,循环结束,自然而然走到这里 */ if(!isFind){// 找到了就不打印;未找到则打印 System.out.println("未找到!"); } } }
-
【二分查找】有个已排序的整形数组,1,2,3,4,5,6,7,8,9,10,11,12,33,55,88, 输入一个数,查找其在数组中的下标
import java.util.Scanner; public class BinarySearchDemo{ public static void main(String[] args){ int[] arr = {1,2,3,4,5,6,7,8,9,10,11,12,33,55,88}; // 该数组是升序排序 Scanner input = new Scanner(System.in); System.out.println("请输入你要查找的数字"); int target = input.nextInt(); // 二分查找 int start = 0; // 第一个元素的位置 int end = arr.length-1;// 最后一个元素的位置 int middle = (start + end) / 2; while(start <= end){ if(target == arr[middle]){ System.out.println("找到,下标:"+middle); break; }else{ if(target > arr[middle]){ // 在右半部分,向右缩小查找范围 start = middle + 1; // [ -------> | 有效范围 ] }else{ // 在左半部分,向左缩小查找范围 end = middle - 1; // [ 有效范围 | <------- ] } middle = (start + end) / 2; } } // 有两种可能性 if(!(start <= end)){ System.out.println("未找到!"); } } }
-
冒泡排序
/** * 冒泡排序(假设任务是升序排序) * 思路:相邻元素比较,大的元素往后走 * {2,7,5,3,1} * {2,7,5,3,1} * {2,5,7,3,1} * {2,5,3,7,1} * {2,5,3,1,7} */ import java.util.Arrays;// 操作数组的工具类 public class BubbleSortDemo{ public static void main(String[] args){ int[] arr = {1,9,2,8,3,7,4,6,5}; // 外层循环控制轮数(每一轮都能得到一个最大值放在最后;9个元素只需要比较8轮,所以-1) for(int m = 0; m < arr.length-1; m ++){ // 把最大值放在最后一个位置(随着轮数m的增加,需要比较的元素个数不断减少,所以-m) for(int i = 0; i+1 < arr.length-m; i ++){ if(arr[i] > arr[i+1]){// 相邻元素比较 // 交换 int t = arr[i]; arr[i] = arr[i+1]; arr[i+1] = t; } } } // 打印数组,检验排序结果 System.out.println(Arrays.toString(arr)); } }
-
选择排序
/** * 选择排序(假设任务是升序排序) * 思路:依次选择位置,让合适的数据填过来 * 选择第一个位置,则找到最小值放在此处; * 选择第二个位置,则找到第二小的元素放在此处。 */ import java.util.Arrays;// 操作数组的工具类 public class SelectSortDemo{ public static void main(String[] args){ int[] arr = {1,9,2,8,3,7,4,6,5}; // i依次表示每轮的起点(最后一个元素不用再继续排序了,所以-1) for(int i = 0; i < arr.length - 1; i ++){ int min = i;// min表示本轮中最小值嫌疑人的下标,先假设本轮起点就是最小值 // 寻找有效范围内最小元素的下标(注:有效范围指的是从下标i到最后一个位置) for(int j = i + 1; j < arr.length; j ++){ if(arr[j] < arr[min]){ min = j;// 发现了更小的元素,记仇,标记它 } } // 循环结束之后,min下标对应的元素一定是该轮中最小的元素 if(min != i){ // 最开始的假设是错误的 int t = arr[i]; arr[i] = arr[min]; arr[min] = t; } } // 打印数组,检验排序结果 System.out.println(Arrays.toString(arr)); } }
-
插入排序
/** * 插入排序 * 理解模型:摸牌 * 把整个数组分为有序、无序两部分。 * 从无序部分摸出一张牌,从右向左依次与手里牌比较大小 * 如果摸的牌比手里牌小,则前移,直到找到合适的位置插入 */ public class InsertSortDemo{ public static void main(String[] args){ int[] arr = {1,9,2,8,3,7,4,6,5}; // i表示无序部分的第一张牌,也就是摸的牌的位置 for(int i = 1; i < arr.length; i ++){ int num = arr[i]; // 牌的值 int index = i; // 下标,不断前移,寻找合适的位置插入 // 如果手里牌比前一张牌小,并且没有到达开头,则继续向前比较 while(index > 0 && num < arr[index-1]){ arr[index] = arr[index-1]; index --; } // 内层循环结束,说明找到了要插入的位置 arr[index] = num; } // 打印排序后的结果 for(int i = 0; i < arr.length; i ++){ System.out.print(arr[i]+ " "); } } }
-
求出斐波那契数列(Fibonacci)的前20项存入数组中,并将数组中的结果遍历输出。
(1 1 2 3 5 8 13 21 34 55 89 144 233)
int[] arr = new int[20]; arr[0] = 1; arr[1] = 1; // 从第三个元素开始,每个元素是其前两个数的和 for(int i = 2; i < arr.length; i ++){ arr[i] = arr[i-1] + arr[i-2]; } System.out.println(Arrays.toString(arr));
-
将一个数组中的元素首尾交换顺序后输出。{1,0,0,8,6}->{6,8,0,0,1}
/** * 将一个数组中的元素逆序后输出。{1,0,0,8,6}->{6,8,0,0,1} * 思路: * {1,0,0,8,6} * i--> <--j * i向后移动,j向前移动 * 交换i和j所在位置的元素 * 当i == j时结束 */ import java.util.Arrays; public class Ex2{ public static void main(String[] args){ int[] arr = {1,0,0,8,6}; int i = 0; int j = arr.length -1; for(;i < j; i ++, j --){ int t = arr[i]; arr[i] = arr[j]; arr[j] = t; } System.out.println(Arrays.toString(arr)); } }
总结
-
数组是相同类型数据的有序集合。
-
数组的四个基本特点:
– 其长度是确定的
– 其元素必须是相同类型
– 可以存储基本数据类型和引用数据类型
– 数组变量属于引用类型
- 一维数组的声明方式
– type[] arr_name; (推荐使用这种方式)
– type arr_name[]。
-
数组的初始化:静态初始化、动态初始化和默认初始化。
-
数组的长度:数组名.length,下标的合法区间[0,数组名.length-1]。
-
数组拷贝:System类中的static void arraycopy(object src,int srcpos,object dest, int destpos,int length)方法。
-
数组操作的常用类java.util.Arrays类
– 打印数组:Arrays.toString(数组名);
– 数组排序:Arrays.sort(数组名);
– 二分查找:Arrays.binarySearch(数组名,查找的元素)。
- 二维数组的声明
– type[][]arr_name=new type[length][];
– type arr_name[][]=new type[length][length]。
作业
一、 选择题
- 在Java中,以下程序段能正确为数组赋值的是( )。(选择二项)
A.
`int` `a[]={``1``,``2``,``3``,``4``};`
B.
`int` `b[``4``]={``1``,``2``,``3``,``4``};`
C.
`int` `c[];``c=``new` `int``[] {``1``,``2``,``3``,``4``};`
D.
`int` `d[];d=``new` `int``[]{``1``,``2``,``3``,``4``};`
- 已知表达式int [] m={0,1,2,3,4,5,6};下面( )表达式的值与数组最大下标数相等。(选择一项)
A.
`m.length()`
B.
`m.length-``1`
C.
`m.length()+``1`
D.
`m.length+``1`
- 在Java中,以下定义数组的语句正确的是( )。(选择二项)
A.
`int` `t[``10``]=``new` `int``[ ];`
B.
`char` `[ ]a=``new` `char``[``5``]; ``char` `[]a={‘a’,’b’};`
C.
`String [ ] s=``new` `String [``10``];`
D.
`double``[ ] d [ ]=``new` `double` `[``4``][ ]; ``double``[][] d;``double` `d[][];`
- 分析下面的Java源程序,编译后的运行结果是( )。(选择一项)
`import` `java.util.*;``public` `class` `Test {`` ``public` `static` `void` `main(String[ ] args) {`` ``int` `[ ] numbers=``new` `int``[ ]{``1``,``2``,``3``};`` ``System.out.println(Arrays.binarySearch(numbers, ``2``));`` ``}``}`
A.输出:0
B.输出:1
C.输出:2
D.输出:3
- 以下选项中能够正确创建一个数组的是( )。(选择二项)
A.
`float` `[]f[] = ``new` `float``[``6``][``6``];`
B.
`float` `f[][] = ``new` `float``[][];`
C.
`float` `[``6``][]f = ``new` `float``[``6``][``6``];`
D.
`float` `[][]f = ``new` `float``[``6``][];`
二、 简答题
-
数组的特点。
-
数组的优缺点
-
冒泡排序的算法。
-
数组的三种初始化方式是什么?
三、 编码题
-
使用三种方式遍历数组int[]arr={1,4,7,2,5,8,3,6,9}
-
数组查找操作:定义一个长度为10 的一维字符串数组,在每一个元素存放一个单词;然后运行时从命令行输入一个单词,程序判断数组是否包含有这个单词,包含这个单词就打印出“Yes”,不包含就打印出“No”。
-
获取数组最大值和最小值操作:利用Java的Random类的方法,编写函数得到0到n之间的随机数,n是参数。并找出产生50个这样的随机数中最大的、最小的数,并统计其中>=60的有多少个。
-
数组逆序操作:定义长度为10的数组,将数组元素对调,并输出对调前后的结果。
思路:把0索引和arr.length-1的元素交换,把1索引和arr.length-2的元素交换……
只要交换到arr.length/2的时候即可。
-
使用Arrays.sort()对数组int[]arr={8,2,4,6,5,9}进行排序。
-
声明一个数组,并使用Arrays.fill()方法在指定范围内为数组赋值。
-
从键盘输入11个数存入一维数组中,排序之后遍历输出。
-
定义数组int arr[]={2,5,8,23,54,21,45};使用Arrays自带的方法查询在数组是否存在21这个数。如果有返回他在数组中的下标,如果没有,则提示用户数组中没有这个数。
-
随机生成一个1-100的数,然后你来猜,当你猜小了时,提示你猜小了,当你猜大时,提示你猜大了,当你猜中时,提示恭喜您猜中了,最多能猜5次,当猜了5次还没有猜中,提示你也猜错5次请下次再来。
-
使用Arrays.copyOfRange()给数组int[]arr={8,2,4,6,5,9}进行指定范围的复制。
-
使用Arrays.copyOf()给数组int[]arr={8,2,4,6,5,9}进行扩容和缩容复制。
预习
方法