JAVA开发讲义(二)-Java程序设计之数据之谜四
程序之美
今天,又一不小心,听到同事说:“看,空指针”、“数组越界了,所以异常了!”,数组,一个不起眼的名词,在程序开发中,起着关乎全局的作用,可以毫不夸张的说,我们写的代码,其中有40%左右,都和 数组相关,数据结构中,数组也占着非常重要的权重和位置,不管是排序算法也好,背包算法也好,你随眼扫一下代码块,都和数组脱离不了关系,所以也可以这样说,数组处理能力是考验一个程序员是否优秀的一个重要的指标,也是程序员们的基本功,在程序员改Bug的过程中,有60%的Bug可能都是数组使用不当引起的,我们在初学代码时,数组也是我们认为难以理解和接受的。
既然“数组如此重要,我们这一篇就来着重介绍数组,同时也会介绍大数值相关的知识,并且配有相应的实例代码,帮助小伙伴们理解与消化,我相信,经过这一篇的讲解,不管时在校的,还是就业的,或者是正在不断面试的朋友们,你们都能有所收获,有所提升。
大数值
如果基本的数据结构不能满足你的需求,java.math包中提供了两个很有用的类:BigInteger和BigDecimal。这两个类可以处理任意长度数字序列的值,BigInteger类可以处理任意精度的整型运算,BigDecimal类可以处理任意精度的浮点数运算。将任意整型数值转换为大数值如下:
BigInteger a = BigInteger.valueOf(100);
大数值的加减乘除,都需要使用自身所带的方法来完成,不能使用算术运算符。如下:
BigInteger a = BigInteger.valueOf(100);
BigInteger b = BigInteger.valueOf(300);
BigInteger c = a.add(b);// c = a + b;
BigInteger d = c.multiply(b.add(BigInteger.valueOf(2)));//d = c * (b +2)
下面我们看下彩票概率程序的改进,使其可以采用大数值进行运算。
import java.math.*;
import java.util.*;
/**
* This program uses big numbers to compute the odds of winning the grand prize in a lottery.
* @version 1.20 2004-02-10
* @author Cay Horstmann
*/
public class BigIntegerTest
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
System.out.print("How many numbers do you need to draw? ");
int k = in.nextInt();
System.out.print("What is the highest number you can draw? ");
int n = in.nextInt();
/*
* compute binomial coefficient n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k)
*/
BigInteger lotteryOdds = BigInteger.valueOf(1);
for (int i = 1; i <= k; i++)
lotteryOdds = lotteryOdds.multiply(BigInteger.valueOf(n - i + 1)).divide(
BigInteger.valueOf(i));
System.out.println("Your odds are 1 in " + lotteryOdds + ". Good luck!");
}
}
按照算数运算符核心算法为:
lotteryOdds = lotteryOdds *(n - i + 1) / i;
使用大数据就是:
lotteryOdds = lotteryOdds.multiply(BigInteger.valueOf(n - i + 1)).divide(
BigInteger.valueOf(i));
下面我们罗列下大数据常用的几个函数:
BigInteger类:
1、BigInteger add(BigInteger other);
2、BigInteger subtract(BigInteger other);
3、BigInteger multiply(BigInteger other);
4、BigInteger divide(BigInteger other);
以上为加减乘除运算
5、BigInteger mod(BigInteger other);
该函数返回本大数与另一个大数other的余数。
6、int compareTo(BigInteger other);
如果本大数与另一个大数other相等,返回0,如果本大数小于另一个大数other,返回负数,否则返回正数。
7、static BigInteger valueOf(long x);
返回值等于x的大整数,通俗一点说就是long类型转换为大数。
BigDecimal类:
1、BigDecimal add(BigDecimal other);
2、BigDecimal subtract(BigDecimal other);
3、BigDecimal multiply(BigDecimal other);
4、BigDecimal divide(BigDecimal other RoundingMode mode);
为大数的加减乘除等,这里说下RoundingMode舍入方式,即为四舍五入方式。
5、int compareTo(BigDecimal other);
如果本大数与另一个大数other相等,返回0,如果本大数小于另一个大数other,返回负数,否则返回正数。
6、static BigDecimal valueOf(long x);
返回值等于x的大实数,通俗一点说就是long类型转换为大数。
7、static BigDecimal valueOf(long x, int scale);
返回值为x/(10的scale次方)的大实数。
数组
画重点了,数组,数组,数组,数组是一种数据结构,用来存储同一类型值的集合。通过数组的下标来进行数组访问。如下我们定义一个数组:
int[] a;
a = new int[100];//开辟100个整型空间,这样就创建了一个可以存储100个整型的数组。
由上面可知,我们可以通过new int[n]创建一个长度为n的数组。
如何访问数组呢
a[0] 访问第0个空间
a[1] 访问第1个空间
a[2] 访问第2个空间
如果我们加入for循环
for(int i = 0; i < 100; i ++){
System.out.println(a[i]);
}
我们就可以将数组a中的100个元素遍历输出;这时我们输出出来的就都是0,为什么呢?因为我们没有给他们进行赋值操作,他们都是默认值0,故而输出全是0;
那么怎么进行赋值呢?
a[0] = 0;
a[1] = 1;
如果我们加入for循环
for(int i = 0; i < 100; i ++){
a[i] = i;
}
我们就完成了对数组a的赋值操作,下面我们看下整体代码:
import java.math.BigInteger;
import java.util.Date;
import java.util.Scanner;
public class Test2 {
public static void main(String[] args) {
int[] a = new int[100];
for(int i = 0; i< 100; i++) {
a[i] = i;
}
for(int i = 0; i< 100; i++) {
System.out.println(a[i]);
}
}
}
for each 循环
java有一种很强的循环能力,可以依次处理数组中的每个元素,不用过多的牵扯下标值。增强的for循环的语句格式为:
for(variable : collection) statement
实例代码为:
int[] a = new int[100];
for(int b: a){
System.out.println(b);
}
这样就可以打印数组中的每一个元素,每个元素占一行。
java语言的设计者认为应该使用诸如foreach、in这样的关键字,但由于其最初不是包含在java语言中的,而是后期添加进去的,需要废弃已经包含同名方法或变量的就代码。
传统的for循环可以达到同样的效果:
for(int i = 0; i< 100; i++) {
System.out.println(a[i]);
}
相比而言,for each循环语句会遍历数组中的每一个元素值,而不使用下标,它显得更加简洁、更不容易出错。当然我们也可以使用数组函数完成此功能,如:Arrays类的toString方法,具体如下:
System.out.println(Arrays.toString(a));
一样可以实现输出打印的效果。
数组的初始化以及匿名数组
数组初始化
int[] primes = {1, 4, 5, 6, 7, 8, 9};//这种初始化不需要new
当然我们也可以初始化一个匿名数组。
new int[]{3, 5, 6, 8, 9};
另外还可以这样:
int[] primes;
primes = new int[]{3, 5, 6, 8, 9};
这里我们一样需要着重讲下空指针。如下:
int[] a;
for(int b: a){//a保空指针
System.out.println(b);
}
数组长度为0和空指针是不同的,我们可以new一个长度为0的元素,但是他是有指向,有地址的,而空指针则没有,这里要特别注意:
int[] f = new int[0];
int[] b;
System.out.println(b);//报空指针
System.out.println(f);
数组拷贝
数组拷贝即为一个数组的元素拷贝给另外一个元素。这里我们可以通过两种方式来实现这个操作。
一种为:
int[] a = new int[]{1, 3, 5, 7, 9};
int[] b = new int[5];
for(int i = 0; i < 5; i++){
b[i] = a[i];
}
另外一种我们要借助与Arrays类的CopyOf函数,具体如下:
b = Arrays.copyOf(a, a.length);
对于int型的数组,如果b开辟的空间比a大,那么多余的元素将被赋值为0;如果元素是boolean类型,则将被赋值为false,相反,如果b小于a的空间,那么只拷贝前面的元素。
JAVA数组和C++在堆栈上有着很大的区别,基本上分配在堆上的数组指针是一样的。如:
int[] a = new int[100];//java
不同于:
int a[100];//c++
却相同于:
int* a = new int[100];//c++
命令行参数
在我们写java程序时,我们可能会留意到JAVA的main函数带有一个String[] args参数,这就是main函数接收的命令行参数,也是一个字符串数组。如下:
public class Message{
public static void main(String[] args){
if(args.length == 0 || args[0].equals("-h"))
System.out.print("Hello");
else if(args[0].equals("-g")){
System.out.print("Goodbye,");
for(int i = 0; i < args.length; i ++){
System.out.print(" " + args[i]);
}
System.out.print("!";
}
}
}
可以通过如下形式运行:
gava Message -g cruel word
args[0]:"-g"
args[1]:“cruel”
args[2]:“word”
输出结果为:
Goodbye,-g cruel word!
数组排序
java中的在数组排序可以手动排序,比如使用排序算法:冒泡算法,选择算法,插入算法等进行排序,也可借助与Arrays类sort方法进行排序。个人推荐使用Arrays.sort方法,因为这个方法使用了优化的快速排序算法。
下面我们看下其排序结果。
int[] a = new int[]{8, 6, 6, 7, 8, 3, 5, 0, 12, 5};
Arrays.sort(a);
for(int r : a){
System.out.printf("%d ", r);
}
}
结果为:0 3 5 5 6 6 7 8 8 12
下面我们讲下Arrays类中的相关函数。
1、static String toString(type[] a) 5.0
返回包含a中数据元素的字符串,这些数据元素被放在括号内,并用逗号分隔;
参数a可以是:int,long, short, char, byte, boolean, float或double的数组。
2、static type copyOf(type[] a, int length) 6
拷贝字符串,返回一个a数组的拷贝。
3、static type copyOfRange(type[] a, int start, int end) 6
返回与a类型相同的一个数组,其长度为length或者end-start,素组元素为a的值
参数a可以是:int,long, short, char, byte, boolean, float或double的数组
参数start:起始下标(包含这个值)
参数end:终止下标(不包含这个值)。如果end > a.length,那么结果为0或false。
参数length:拷贝数组的长度。如果length值大于a.length,结果为0或者false;否则,数组中只有前面的length个数据元素的拷贝值。
4、static void sort(type[] a)
采用优化快速排序算法对数组进行排序。
参数a可以是:int,long, short, char, byte, boolean, float或double的数组
5、static int binarySearch(type[] a, type v)
6、static int binarySearch(type[] a, int start, int end, type v) 6
采用二分搜索算法查找值v。如果查找成功,则返回相应的下标值;否则,返回一个负数值r。-r-1是为了保持a有序v应插入的位置。
参数a可以是:int,long, short, char, byte, boolean, float或double的数组
start: 起始下标(包含这个值)
end:终止下标(不包含这个值)
v:同a的数据元素类型相同的值。
7、static void fill(type[] a, type v)
将数组的所有数据元素值设置为v。
参数a可以是:int,long, short, char, byte, boolean, float或double的数组
v:与a的数据元素类型相同的值。
8、static boolean equals(type[] a, type[] b)
如果两个数组大小相同,并且下标相同的元素都对应相等,返回true。
参数a、b 类型为:int,long, short, char, byte, boolean, float或double的数组
多维数组
多维数组是相对于一维数组来说的,这也让我想到了我们初高中时学习的坐标系,一维成线,二维成面,三维成体,当然这个口诀也适用与多维数组,对于一维数组,我们可以理解成他就是一个顺序表,而二维数据,我们可以理解成他是一个面,巨型的面,三维数组我们可以理解为他是一个长方体,当三维相等是就是正方体了,当然理解可以这个样理解,在计算机里的存储和布局就不是这样的了,我们只是方便理解罢了。
一维数组:int[] a = new int[]{1,2,4,5};
二维数组:int[][] b = new int[][]{{1,2,4,5},{1,2,4,5},{1,2,4,5}};
三维数组:int[][][] c = new int[][][]{{{1,2,4,5},{1,2,4,5},{1,2,4,5}},{{1,2,4,5},{1,2,4,5},{1,2,4,5}},{{1,2,4,5},{1,2,4,5},{1,2,4,5}}}
当然也包含四维,五维数组等。感兴趣的朋友可以深入理解下。
一但数组被初始化,就可以利用两个或多个方括号访问每个元素。比如二维b[i][j],三维c[i][j][k]等。
多维数数组的遍历
二维数组:
for(double[] row: a)
for(double value:row)
do something with value
想要快速打印一个二维数组的数据元素。可以如下操作:
System.out.println(Arrays.deepToString(b))
完整实例:
public class CompoundInterest
{
public static void main(String[]args)
{
final int STARTRATE=10;
final int NRATES=6;
final int NYEARS=10;
double[]interestRate=new double[NRATES];
for(int j=0;j<interestRate.length;j++)
interestRate[j]=(STARTRATE+j)/100.00;
double[][] balances=new double[NYEARS][NRATES];
for(int j=0;j<balances[0].length;j++)
balances[0][j]=10000;
for(int i=1;i<balance.length;i++)
{
for(int j=0;j<balances[i].length;j++)
{
double oldBalance=balance[i-1][j];
double interest=oldBalance*interestRate[j];
balances[i][j]=oldBalance+interest;
}
}
for(int j=0;j<interestRate.length;j++)
System.out.printf("%9.0f%%",100*interestRate[j]);
System.out.println();
for(double[]row:balances)
{
for(double b:row)
System.out.printf("%10.2f",b);
System.out.println();
}
}
}
不规则数组
JAVA实际上没有多维数组,只有一维数组,多维数组被理解为"数组的数组",例如在前面的实例中,balances数组实际上是一个包含10个元素的数组,而每个元素又是一个由6个浮点数组成的数组,参见下图。
表达式balances[i]引用第i个子数组,也就是二维表的第i行。它本身也是一个数组,balances[i][j]引用这个数组的第j项。
由于可以单独的存取数组的某一行,所以可以让两行交换。
double[] temp = balances[i];
balances[i] = balances[i + 1];
balances[i + 1] = temp;
可以构造一个“不规则”数组,即数组的每一行有不同的长度。如下实例:
public class YanghuiThree {
public static void main(String[] args){
int[][] arr=new int[6][6];
//生成竖线和对角线
for(int i=0;i<6;i++){
arr[i][i]=1;
arr[i][0]=1;
}
//根据头上的元素和头上左边的元素生成该元素
for(int i=2;i<6;i++){
for(int j=1;j<=i-1;j++){
arr[i][j]=arr[i-1][j]+arr[i-1][j-1];
}
}
//打印输出结果
for(int i=0;i<6;i++){
for(int j=0;j<=i;j++){
System.out.print(arr[i][j]+" ");
}
System.out.println();
}
}
}
其结果为:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
杨辉三角地打印就是不规则数组使用的典型案例。上述我们是通过开辟int[6][6]规则数组完成的,那么如何通过不规则数组完成呢,见如下代码:
/**
* This program demonstrates a triangular array.
* @version 1.20 2004-02-10
* @author Cay Horstmann
*/
public class LotteryArray
{
public static void main(String[] args)
{
final int NMAX = 10;
// allocate triangular array
int[][] odds = new int[NMAX + 1][];
for (int n = 0; n <= NMAX; n++)
odds[n] = new int[n + 1];
// fill triangular array
for (int n = 0; n < odds.length; n++)
for (int k = 0; k < odds[n].length; k++)
{
/*
* compute binomial coefficient n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k)
*/
int lotteryOdds = 1;
for (int i = 1; i <= k; i++)
lotteryOdds = lotteryOdds * (n - i + 1) / i;
odds[n][k] = lotteryOdds;
}
// print triangular array
for (int[] row : odds)
{
for (int odd : row)
System.out.printf("%4d", odd);
System.out.println();
}
}
}
好了,到此为止,java基本程序设计结构我们就全部讲完了,从下篇开始我们就开始java的精髓“对象和类”了,不知小伙伴们是否有收获,如果有什么疑问可以给我留言,或者私信我,我都会在工作之余给以回复,但愿能够在大家前进的旅途中起到增砖添瓦的作用,很感谢朋友们能在百忙之中阅读这篇文章。能够帮到大家是我最大的幸福。