第5章数组、字符串与正则表达式 本章主要内容:  数组的定义与数组元素的访问;  字符串及应用;  正则表达式及应用。 在程序设计中,数组和字符串是常用的数据结构。无论是面向过程的程序设计,还是面向对象的程序设计,数组和字符串都起着重要的作用。正则表达式是对字符串进行查找、匹配、分割和替换等操作的处理工具。 5.1数组 所谓数组就是若干个相同数据类型的元素按一定顺序排列的集合。在Java语言中数组元素可以由基本数据类型的变量组成,也可以由对象组成。数组中的所有元素都具有相同的数据类型,用一个统一的数组名和一个下标来唯一地确定数组中的元素。从构成形式上数组可以分为一维数组和多维数组。 为了充分地理解数组的概念,首先介绍一下Java语言有关内存分配的知识。Java语言把内存分为两种: 栈内存和堆内存。 在方法中定义的一些基本类型的变量和对象的引用变量(即引用类型的变量)都在方法的栈内存中分配,当在一段代码块中定义一个变量时,Java就在栈内存中为这个变量分配内存空间,当超出变量的作用域后,Java会自动释放掉为该变量所分配的内存空间。 堆内存用来存放由new运算符创建的数组或对象,即它们在堆中分配的内存,并由Java虚拟机的垃圾回收器自动管理。在堆中创建了一个数组或对象后,同时还在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,也称为引用数据类型的变量。引用变量实际上保存的是数组或对象在堆内存中的首地址(也称为对象的句柄),以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。引用变量就相当于是为数组或对象起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组或对象本身在堆内存中分配,即使程序运行到使用new运算符创建数组或对象的语句所在的代码块之外,数组或对象本身所占据的内存也不会被释放,数组或对象在没有引用变量指向它时,会变为垃圾,不能再被使用,但仍然占据内存空间不放,在随后一个不确定的时间被垃圾回收器收走(释放掉),这也是Java比较占内存的原因。 Java有一个特殊的引用型常量null,如果将一个引用变量赋值为null,则表示该引用变量不指向(引用)任何对象。 有了栈内存与堆内存的知识后,对下面要介绍的数组和后续章节中将要介绍的对象会有更深的了解。 数组主要有如下几个特点。  数组是相同数据类型元素的集合;  数组中的各元素是有先后顺序的,它们在内存中按照这个先后顺序连续存放在一起;  数组元素用整个数组的名字和它自己在数组中的顺序位置来表示。例如,a[0]表示名字为a的数组中的第一个元素,a[1]代表数组a的第二个元素,以此类推。 5.1.1一维数组 一维数组是最简单的数组,其逻辑结构是线性表。要使用一维数组,需要经过定义、初始化和应用等过程。 1. 一维数组的定义 要使用Java语言的数组,一般需经过三个步骤: 一是声明数组; 二是分配空间; 三是创建数组元素并赋值。前两个步骤的语法如下: 数据类型[ ] 数组名; //声明一维数组 数组名=new 数据类型[个数]; //分配内存给数组 在数组的声明格式里,“数据类型”是声明数组元素的数据类型,其可以是Java语言中任意的数据类型,包括基本类型和引用类型。“数组名”是用来统一这些相同类型数据的名称,其命名规则和变量的命名规则相同。其中“[]”指明该变量是一个数组类型变量,Java语言是将“[]”放到数组名的前面,但也可以像C/C++的定义方式将“[]”放在数组名的后面来定义数组,如“数据类型 数组名[];”。与C/C++不同,Java语言在数组的声明中并不为数组元素分配内存,因此“[]”中不用给出数组中元素的个数(即数组的长度),但必须在为它分配内存空间后才可使用。 数组声明之后,接下来便是要分配数组所需的内存,这时必须用运算符new,其中“个数”是告诉编译器,所声明的数组要存放多少个元素,所以new运算符是通知编译器根据括号里的个数,在内存中分配一块空间供该数组使用。数组创建后就不能再修改它的大小。利用new运算符为数组元素分配内存空间的方式称为动态内存分配方式。 下面举例来说明数组的定义,如: int[ ] x;//声明名称为x的int型数组 x=new int[10]; //x数组中包含有10个元素,并为这10元素分配内存空间 在声明数组时,也可以将两个语句合并成一行,格式如下: 数据类型[ ] 数组名= new 数据类型[个数]; 利用这种格式在声明数组的同时,也分配一块内存供数组使用。如上面的例子可以写成如下形式: int[ ] x = new int[10]; 等号左边的int[] x相当于定义了一个特殊的变量x,x的数据类型是一个对int型数组对象的引用,x就是一个数组的引用变量,其引用的数组元素个数不定。等号右边的new int[10]就是在堆内存中创建一个具有10个int型变量的数组对象。“int[] x = new int [10];”就是将右边的数组对象赋值给左边的数组引用变量。若利用两行的格式来声明数组,其意义也是相同的。如: int[ ] x;//定义了一个数组x,这条语句执行完成后的内存状态如图5.1所示 x=new int[10];//数组初始化,这条语句执行完后的内存状态如图5.2所示 图5.1只声明了数组,而没有对其分配内存空间 图5.2声明数组并分配相应的内存空间,引用变量指向数组对象 执行第2条语句“x=new int [10];”后,在堆内存里创建了一个数组对象,为这个数组对象分配了10个整数单元,并将数组对象赋给了数组引用变量x。引用变量就相当于C语言中的指针变量,而数组对象就是指针变量指向的那个内存块。所以说在Java内部还是有指针的,只是把指针的概念对用户隐藏起来了,而用户所使用的是引用变量。 用户也可以改变x的值,让它指向另外一个数组对象,或者不指向任何数组对象。要想让x不指向任何数组对象,只需要将常量null赋给x即可。如“x=null;”,这条语句执行完后的内存状态如图5.3所示。 执行完“x=null;”语句后,原来通过new int [10]产生的数组对象不再被任何引用变量所引用,变成了“孤儿”,也就成了垃圾,直到垃圾回收器来将它释放掉。 说明: 数组用new运算符分配内存空间的同时,数组的每个元素都会自动赋一个默认值: 整数为0,实数为0.0,字符为“\0”,boolean型为false,引用型为null。这是因为数组实际是一种引用型的变量,而其每个元素是引用型变量的成员变量。 图5.3引用变量与引用对象断开 Java语言提供的java.util.Arrays类用于支持对数组的操作,如表5.1所示。 表5.1数组类Arrays的常用方法 常 用 方 法功 能 说 明 public static int binarySearch(X[] a,X key)X是任意数据类型。返回key在升序数组a中首次出现的下标,若a中不包含key,则返回负值 public static void sort(X[] a)X是任意数据类型。对数组a升序排序后仍存放在a中 public static void sort(X[] a,int fromIndex,int toIndex)对任意类型的数组a中从fromIndex到toIndex-1的元素进行升序排序,其结果仍存放在a数组中 public static X[] copyOf(X[] original, int newLength)截取任意类型数组original中长度为newLength的数组元素复制到调用数组 public static boolean equals(X[] a, X[] a2)判断同类型的两个数组a和a2中对应元素值是否相等。若相等则返回true,否则返回false 3.6节中介绍过局部变量类型推断功能,所以也可以使用var作为数组的类型声明数组。使用变量类型推断,必须以var作为类型名声明变量且必须初始化,如语句: var myArray=new int[10]; 该语句中数组myArray的类型被推断为int型。但需说明一点,在利用var声明数组时不能在var声明的左侧使用方括号,因此下面的声明语句是错误的。 var[] myArray=new int[10]; 2. 一维数组元素的访问 当定义了一个数组,并用运算符new为它分配了内存空间以后,就可以引用数组中的各元素了。要想使用数组中的元素,可以利用数组名和下标来实现。数组元素的引用方式为: 数组名[下标] 其中,“下标”可以是整型数或整型表达式,如a[3+i](i为整数)。Java语言数组的下标是从0开始的。例如: int[ ] x = new int[10]; 其中,x[0]代表数组中第1个元素,x[1]代表第2个元素,x[9]为第10个元素,也就是最后一个元素。另外,与C/C++不同,Java语言对数组元素要进行越界检查以保证安全性。同时,对于每个数组都有一个属性length指明它的长度,如x.length指出数组x所包含的元素个数。 【例5.1】声明一个一维数组,其长度为5,利用循环对数组元素进行赋值,然后再利用另一个循环逆序输出数组元素的内容。程序代码如下: 1//FileName: App5_1.java一维数组 2public class App5_1{ 3public static void main(String[] args){ 4int i; 5int[] a;//声明一个数组a 6a=new int[5];//分配内存空间供整型数组a使用,其元素个数为5 7for(i=0;i<5;i++)//对数组元素进行赋值 8a[i]=i; 9for(i=a.length-1;i>=0;i--) //逆序输出数组的内容 10System.out.print("a["+i+"]="+a[i]+ ",\t"); 11System.out.println("\n数组a的长度是: "+a.length);//输出数组的长度 12} 13} 程序运行结果: a[4]=4,a[3]=3,a[2]=2,a[1]=1,a[0]=0 数组a的长度是: 5 该程序的第5行声明了一个整型数组a,第6行为其分配包含5个元素的空间; 第7、8两行是利用for循环为数组元素赋值; 第9、10两行是利用for循环将数组a的各元素反序输出; 第11行是利用数组的长度属性length输出元素个数。 3. 一维数组的初始化及应用 对数组元素的赋值,既可以使用单独方式进行(如例5.1),也可以在定义数组的同时就为数组元素分配空间并赋值,这种赋值方法称为数组的初始化。其格式如下: 数据类型[] 数组名={初值0,初值1,…,初值n-1}; 数组初始化语句中不能使用new操作符,且必须将声明、创建和初始化数据都放在一条语句中,否则会产生语法错误。在花括号内的初值会依次赋值给数组的第1,2,…,n个元素。此外,在声明数组的时候,并不需要将数组元素的个数给出,编译器会根据所给的初值个数来设置数组的长度。如: int[] a={1,2,3,4,5}; 在上面的语句中,声明了一个整型数组a,虽然没有特别指明数组的长度,但是由于花括号里的初值有5个,编译器会分别依次指定各元素存放,a[0]为1,a[1]为2,…,a[4]为5。 注意: 在Java程序中声明数组时,无论用何种方式定义数组,都不能指定其长度。如以“int[5] a;”方式声明数组将是非法的,该语句在编译时将出错。 说明: 虽然可以用var声明数组类型,但不能将var用于数组初始化,所以语句“var a={1,2,3,4,5};”是错误的。 【例5.2】设数组中有n个互不相同的数,不用排序求出其中的最大值和次最大值。 1//FileName: App5_2.java比较数组元素值的大小 2public class App5_2{ 3public static void main(String[] args){ 4int i,max,sec; 5int[] a={8,50,20,7,81,55,76,93};//声明数组a,并赋初值 6if(a[0]>a[1]){ 7max=a[0]; //max存放最大值 8sec=a[1];//sec存放次最大值 9} 10else{ 11max=a[1]; 12sec=a[0]; 13} 14System.out.print("数组的各元素为: "+a[0]+ ""+a[1]); 15for(i=2;imax){//判断最大值 18sec=max; //原最大值降为次最大值 19 max=a[i]; //a[i]为新的最大值 20} 21else if(a[i]>sec)//即a[i]不是新的最大值,但若a[i]大于次最大值 22sec=a[i];//a[i]为新的次最大值 23} 24System.out.print("\n其中的最大值是: "+max);//输出最大值 25System.out.println(" 次最大值是: "+sec);//输出次最大值 26} 27} 程序运行结果: 数组的各元素为: 85020781557693 其中的最大值是: 93次最大值是: 81 该程序的第5行定义并初始化了数组a,第6~13行利用ifelse语句将数组前两个元素中大的数保存在变量max中,将小的数存放在变量sec中; 第15~23行利用for循环与if语句的结合,从数组的第三个元素开始到最后,对数组中的元素进行输出并检测,若检测到新的最大数,则将其保存到变量max中,次最大数保存到变量sec中; 最后第24、25两行将数组中的最大数和次最大数输出。 【例5.3】设有N个人围坐一圈并按顺时针方向从1到N编号,从第S个人开始进行1到M报数,报数到第M的人出圈,再从他的下一个人重新开始从1到M报数,如此进行下去,每次报数到M的人就出圈,直到所有人都出圈为止。给出这N个人的出圈顺序。 1//FileName: App5_3.java "约瑟夫环"问题 2public class App5_3{ 3public static void main(String[] args){ 4final int N=13,S=3,M=5; //设有13个人,从第3个人开始进行1到5报数 5int i=S-1,j,k=N,g=1; 6var a=new int[N];//数组a被推断为int型 7for(int h=1;h<=N;h++) 8a[h-1]=h;//将第h人的编号存入下标为h-1的数组元素中 9System.out.println("\n出圈的顺序为: "); 10do{ 11i=i+(M-1);//计算出圈人的下标i 12while(i>=k) //当数组下标i大于或等于圈中的人数k时 13i=i-k;//将数组的下标i减去圈中的人数k 14System.out.print(""+a[i]);//输出出圈人的编号 15for(j=i;j=k时,即是i已经超出剩下的人数,因此第13行是要重新计算数组的下标。第14行是输出出圈人的编号。第15、16行的循环是当下标为i的人出圈后,把后续人的编号前移。由于有一个人出圈,圈中的人数少1,所以第17行将用于表示圈中剩余人数的变量k减1。第18行的变量g用于控制dowhile循环次数的变量,所以每次加1,每次循环找出一个出圈的人,所以循环共执行N次。 下面再给出该题的另一算法。 1//FileName: App5_3.java 2public class App5_3{//例题5.3的另一解法 3public static void main(String[] args){ 4final int N=13, S=3, M=5; //N为总人数,从第S个人开始报数,报数到M的人出圈 5int[] p=new int[N]; //数组p用于标识已出圈的人 6int[] q=new int[N]; //数组q存放出队顺序 7int i,j,k,n=0; 8k=S-2; //k从1开始数出圈人的下标 9for(i=1;i<=N;i++){ 10for(j=1; j<=M; j++){ //从1到M报数,计算出圈人的下标k 11if(k==N-1) //当出圈人的下标达到末尾时 12k=0;//出圈人的下标从0开始 13else 14k++; //否则下标加1 15if(p[k]==1) //若p[k]=1,说明下标为k的人已出圈 16j--;//由于让过已出圈的人,因此j要减1,以保证每次数过M个人 17} 18p[k]=1;//将下标为k的数组元素置1,表示其出圈 19q[n++]=k+1;//将下标为k的人的编号k+1存入数组元素q[n]中 20}//将上行改为System.out.print((k+1)+" ");后可去掉下面三行输出语句 21System.out.println("出队顺序为: "); 22for(i=0; i0时,字符串将被分割 limit-1次; 当 limit<0时,字符串将被尽可能多地分割; 当 limt=0时,字符串被尽可能多地分割,但是尾部的空字符串会被抛弃 public String[] split(String regex)功能同上,相当于limit=0的情况 public String toLowerCase()将字符串中的所有字符都转换为小写字符 public String toUpperCase()将字符串中的所有字符都转换为大写字符 在3.8.2节中强调过不能用关系运算符>、>=、<或<=比较两个字符串,对于两个字符串的比较需用s1.compareTo(s2)方法。该方法返回的实际值是依据s1和s2从左到右第一个不同字符之间的距离得出的。例如,s1为"abc",s2为"abf",则s1.compareTo(s2)返回-3。首先比较的是s1的s2中第一个位置的两个字符,因为它们都是a,相等,所以再比较第二个位置的两个字符,由于它们同为b,也相等,因此继续比较第三个位置的两个字符,这时一个是c另一个是f,由于c比f小3,因此返回值为-3。 【例5.6】判断回文字符串。 回文是一种“从前往后读”和“从后往前读”都相同的字符串,例如"rotor"就是一个回文字符串。在本例中使用了两种算法来判断回文字符串。程序中比较两个字符时,使用关系运算符“==”,而比较两个字符串时,则需使用equals()方法。程序代码如下: 1//FileName: App5_6.java字符串应用:判断回文字符串 2public class App5_6{ 3public static void main(String[] args){ 4var str="rotor"; //由初值的类型可推断出str为String类型 5int i=0,n; 6boolean yn=true; 7if(args.length>0) 8str=args[0]; 9System.out.println("str="+str); 10n=str.length(); 11char sChar,eChar; 12while(yn && (i?@[\]^_`{|}~ \p{Graph}可视字符[\p{Alnum}\p{Punct}] \p{Print}可打印字符[\p{Graph}\x20] \p{Blank}空格或制表符[\t] \p{Cntrl}控制字符[\x00\x1F\x7F] \p{XDigit}十六进制数字[09afAF] \p{Space}空字符[\t\n\x0B\f\r] 说明: 在正则表达式中使用元字符时要在其前面加上转义字符“\”,如元字符“\d”在正则表达式中要写成“\\d”,又如若要想使用普通意义的点字符“.”,必须写成“\.”。 例如,元字符\d代表0~9中的任意一个数字,所以与正则表达式\\dabc匹配的字符串有“0abc” “1abc” “2abc” …“9abc”。 对于元字符\b是匹配单词的边界,如“er\b”可以匹配单词never中的er,但不能匹配verb中的er。而对于元字符\B则是匹配非单词的边界,如“er\B”可以匹配“verb”中的er,但不能匹配never中的er。 表5.4中的^和$两个符号是正则表达式的边界定位符。例如,^once表示该模式只匹配以once开头的字符串,如该模式与字符串"once upon a time"匹配,但不与"There once was a man from Guangzhou"匹配。$符号用来匹配那些以给定模式结尾的字符串,例如,bucket$,这个模式与"Who kept all of this cash in a bucket"匹配,但与"buckets"不匹配。 在正则表达式中可以使用方括号将若干个字符括起来表示一个元字符,该元字符代表方括号中的任何一个字符。表5.5给出了常用的方括号表达式。 表5.5正则表达式常用的方括号表达式 方括号表达式功 能 说 明 [xyz]代表x、y、z中的任何一个字符 [^xyz]代表除x、y、z以外的任何一个字符 [a-d]代表a~d中的任何一个字符 [a-zA-Z]代表a~z或A~Z中的任何一个字符 续表 方括号表达式功 能 说 明 [a-d[m-q]]代表a~d或m~q中的任何一个字符(并集),等于[admq] [a-z&&[def]]代表a~z并且是d、e、f中的任何一个字符(交集),等于[def] [a-f&&[^de]]代表a~f而用不是d或e的任一字符(差集),等于[acf]。即a、b、c和f 说明: 当符号^在方括号表达式中使用时,表示不接受该方括号表达式中的字符集合。 除了方括号表达式外,在正则表达式中还可以使用限定符,表5.6给出了正则表达式中常用的限定符。 表5.6正则表达式中常用的限定符 限定符功 能 说 明 X??号前面的X最多只出现1次,?等价于{0,1} X**号前面的X出现0次或多次,*等价于{0,} X++号前面的X出现1次或多次,+等价于{1,} X{n}X刚好出现n次 X{n,}X至少出现n次 X{n,m}X出现n~m次 X|Y在|左右两边的子表达式中任选一项,即或选X或选Y 例如,正则表达式“runoo*b”,可以匹配 “runob” “runoob” “runoooooob”等字符串,因为*号代表它前面的字符(本例中为o)可以不出现,也可以出现一次或者多次。 注意: 不能将定位符与限定符一起使用。由于在紧靠换行或者单词边界的前面或后面不能有一个以上位置,因此不允许诸如^*之类的表达式。若要匹配一行文本开始处的文本,应在正则表达式的开始处使用^字符。不要将^的这种用法与方括号表达式内的用法混淆。若要匹配一行文本结束处的文本,应在正则表达式的结束处使用$字符。 下面分析两个正则表达式。 (1) 正则表达^[-]?[09]+\.?[09]+$ 解析为以一个可选的负号([-]?)开头(^)、跟着1个或多个的数字([09]+)和一个可有可无的小数点(\.?),再跟1个或多个数字([09]+),并且后面没有其他任何字符($)。由于元字符+与{1,}等价,都代表1个或多个前面的内容,所以上面的正则表达式可以简化为^\?[09]{1,}\.?[09]{1,}$。 (2) 正则表达式^Chapter|Section[19][09]{0,1}$表示要么匹配行首的单词Chapter,要么匹配行尾的单词Section及跟在其后的任何数字。如果输入字符串是Chapter22,那么上面的表达式只匹配单词Chapter。如果输入字符串是Section22,那么该表达式匹配Section22。 除了方括号表达式外,还可以使用圆括号将正则表达式分组,此时圆括号可以将括起来的若干个字符合成一个字符,例如: a(bc)* //代表a后面跟0个或多个"bc" a(bc){1,5} //代表a后面跟1~5个"bc" 下面给出几个常用的正则表达式。 IP地址: ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?) 邮箱地址: \w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* 日期: \d{4}[年|\-|\.]\d{\1-\12}[月|\-|\.]\d{\1-\31}日? 汉字: [\u4e00-\u9fa5]{0,} 5.3.2正则表达式的应用 正则表达式既可以直接用于字符串进行模式匹配,也可以利用Pattern和Matcher两个类使用正则表达式。下面分别举例说明。 【例5.7】利用正则表达来分割字符串,并输出被分割出的每个子字符串。 1//FileName: App5_7.java 2import java.util.Scanner; //导入Scanner类 3public class App5_7{ 4public static void main(String[] args){ 5String str="他说: 我很好。我说: 那就好Hello Good morning"; 6String regex="[\\s\\p{Punct}]+";//定义正则表达式 7String[] words=str.split(regex);//分割出匹配的子字符串 8for(int i=0;i0,那么模式将被应用最多limit-1次; 如果limit<0,那么该模式将被应用尽可能多的次数; 如果limit=0,那么模式将被应用尽可能多的次数,并且末尾的空字符串将被丢弃 public String[] split(CharSequence input)功能同上,相当于limit=0的情况 public static String quote(String s)返回匹配字符串s的字符串形式的正则表达式。参数中的元字符或转义字符并没有特殊意义 说明: matcher()与split()方法中的参数input是将要用于匹配模式的字符序列,又称为输入序列,它是CharSequence接口类型,该接口定义了一组只读字符。String以及其他类实现了该接口,因此可以向matcher()和split()方法传递字符串。 正则表达式必须先编译成类型为Pattern的模式对象,模式对象是通过该类调用自己的compile()方法创建的。一旦创建了Pattern对象,就可以使用该对象调用matcher()方法创建Matcher对象。然后就可以利用Matcher对象调用相应的方法来执行各种模式匹配操作。表5.8给出Matcher类的常用方法。 表5.8Matcher类的常用方法 常 用 方 法功 能 说 明 public boolean matches()判断字符序列是否与模式完全匹配 public boolean find()判断输入序列的子序列是否与模式相匹配。可以重复调用这个方法以查找所有匹配的子序列。对方法的每次调用,都是从上一次离开的位置开始 续表 常 用 方 法功 能 说 明 public boolean find(int start)判断输入序列从start位置开始是否有匹配的子序列 public boolean lookingAt()对前面的字符串进行匹配,只有匹配到的字符串在最前面才会返回true public String group()返回匹配到的子字符串 public int start()返回当前匹配到的字符串在原目标字符串中的位置 public int end()返回当前匹配的字符串末尾字符之后下一个字符的索引位置 public String replaceAll(String replacement)用字符序列replacement替换与模式相匹配的序列,将更新后的输入序列作为字符串返回 public String replaceFirst(String replacement)用字符串replacement替换与模式匹配序列中的第一个子序列后返回 【例5.8】把用户给定的字符串按正则表达式进行匹配并输出,然后再对用户输入的Email地址是否合法进行验证。 1//FileName: App5_8.java 2import java.util.Scanner;//导入Scanner类 3import java.util.regex.Pattern; //导入Pattern类 4import java.util.regex.Matcher;//导入Matcher类 5public class App5_8{ 6public static void main(String[] args){ 7Pattern p=Pattern.compile("ab\\.*c"); //编译正则表达式 8Matcher m=p.matcher("ab..cxyzab...cxxx");//创建给定输入序列的匹配器 9while(m.find())//判断输入序列中是否有匹配的子字符串 10System.out.println(m.group()+": "+m.start()+","+m.end()); 11//下面定义邮箱格式正则表达式 12String regex="^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"; 13String yn="y"; 14Scanner scan=new Scanner(System.in);//创建从键盘上读数据的对象scan 15do{ 16System.out.print("请输入邮箱地址: "); 17String input=scan.next(); 18Pattern pa=Pattern.compile(regex);//编译正则表达式 19Matcher ma=pa.matcher(input); //创建给定输入序列的匹配器 20if(ma.matches())//判断匹配是否成功 21System.out.println("邮箱地址正确!"); 22else 23System.out.println("邮箱地址格式错!"); 24System.out.print("是否继续输入: "); 25yn=scan.next(); 26}while(yn.equalsIgnoreCase("y")); 27} 28} 程序运行结果: ab..c: 0,5 ab...c: 8,14 请输入邮箱地址: 139@qq.com 邮箱地址正确! 是否继续输入: n 该程序的第7行是用Pattern类直接调用compile()方法将正则表达式"ab\\.*c"编译成模式对象p,第8行则是用模式对象调用matcher()方法生成匹配器对象m,第9、10行在while循环中分别用group()、start()和end()方法将匹配的子串和起止位置输出。第12行定义了正则表达式用于匹配Email邮箱地址。第18行是将正则表达式编译成模式对象pa,第19行则是利用该对象pa调用Pattern类的matcher()方法创建匹配对象ma进行模式匹配。第20行的if语句是判断匹配是否成功。 本章小结 1. 数组是由若干个相同类型的变量按一定顺序排列所组成的数据结构,它们用一个共同的名字来表示。数组的元素可以是基本类型或引用类型。根据存放元素的复杂程度,数组分为一维数组、二维数组及多维数组。 2. 要使用Java语言的数组,必须经过两个步骤: (1)声明数组; (2)分配内存给数组。 3. 在Java语言中要取得数组的长度,即数组元素的个数,可以利用数组的.length属性来完成。 4. 如果想直接在声明时就给数组赋初值,则只要在数组的声明格式后面加上元素的初值即可。 5. Java语言允许二维数组中每行的元素个数不相同。 6. 在二维数组中,若要想获得整个数组的行数,或者是某行元素的个数时,也可以利用.length属性来取得。 7. 字符串可以分为两大类: 一类是创建之后不会再做修改和变动的字符串变量; 另一类是创建之后允许再做修改的字符串变量。 8. 字符串常量与字符常量不同,字符常量是用单引号(')括起来的单个字符,而字符串常量是用双引号(")括起来的字符序列。 9. 正则表达式本质上就是一个字符串,这个字符串是按照一定的语法和规范被构造出来作为限定条件的“规则字符串”,这个“规则字符串”描述了一种字符串匹配的模式。 习题5 5.1从键盘输入n个数,输出这些数中大于其平均值的数。 5.2从键盘输入n个数,求这n个数中的最大数与最小数并输出。 5.3求一个3阶方阵的对角线上各元素之和。 5.4找出4×5矩阵中值最小和最大的元素,并分别输出其值及所在的行号和列号。 5.5产生0~10的8个随机整数,并利用冒泡排序法将其升序排序后输出(冒泡排序算法: 每次进行相邻两数的比较,若次序不对,则交换两数的次序)。 5.6有15红球和15个绿球排成一圈,从第1个球开始数,当数到第13个球时就取出此球,然后再从下一个球开始数,当再数到第13个球时又取出此球,如此循环进行,直到仅剩15个球为止,问怎样排法才能使每次取出的球都是红球? 5.7编写Java应用程序,比较命令行中给出的两个字符串是否相等,并输出比较的结果。 5.8从键盘上输入一个字符串和子串的开始位置与长度,截取该字符串的子串并输出。 5.9从键盘上输入一个字符串和一个字符,从该字符串中删除给定的字符。 5.10编程统计用户从键盘输入的字符串中所包含的字母、数字和其他字符的个数。 5.11将用户从键盘输入的每行数据都显示输出,直到输入“exit”字符串,程序运行结束。 5.12利用正则表达式判断输入的字符串是否合法,要求字符串由7个字符组成,并且第1位必须是大写字母,第2~4位是小写字母,后3位必须是数字。 5.13利用正则表达式判断输入的IP地址是否合法,IP地址以x.x.x.x的形式表示,其中每个x都是一个0~255的十进制数。