第6章 字符串
字符串是编程中经常用到的数据类型,它是编程语言中表示文本的类型。Java提供了两种字符串操作:String类和StringBuffer类。在我们日常生活中对于邮箱输入的正确验证、相关证件的匹配以及实现对资料加密解密都是对字符串进行的操作。
实例36 获取字符串长度
本实例说明如何获得字符串的长度,包括多种获得方式,如获得字符串中的中文字符长度,获得字母、数字、空格的个数等。
技术要点
• String对象的字符串长度值是固定的,一旦字符串对象创建后就不再变化。length()方法返回String对象的char对象的个数,而不是Unicode字符个数。原因在于一个Unicode字符可能会用多个char表示。
• String对象中的charAt()方法可获得字符串的char对象,以便对字符串中的各个字母进行相应的操作。
实现步骤
(1)新建一个类名为TextLength.java。
(2)代码如下所示:
package com.zf.s6; //创建一个包 public class TextLength { //描述字符串长度的类 public static boolean isChineseChar(char c)throws Exception{ //判断是否是一个汉字 return String.valueOf(c).getBytes("GBK").length>1; //汉字的字节数大于1 } public static int getChineseCount(String s) throws Exception{ //获得汉字的长度 char c; int chineseCount=0; if(!"".equals("")){ //判断是否为空 s=new String(s.getBytes(),"GBK"); //进行统一编码 } for(int i=0;i<s.length();i++){ //for循环 c=s.charAt(i); //获得字符串中的每个字符 if(isChineseChar(c)){ //调用方法进行判断是否为汉字 chineseCount++; //等同于chineseCount=chineseCount+1 } } return chineseCount; //返回汉字个数 } public static String getStringInfo(String s){ //获得字母、数字、空格的个数 char ch; int character=0,blank=0,number=0; for(int i=0;i <s.length();i++) //for循环 { ch=s.charAt(i); if((ch>='a'&&ch <='z')||(ch>='A'&&ch <='Z'))//统计字母 character++; //等同于character=character+1 else if(ch==' ') //统计空格 blank++; //等同于blank=blank+1 else if(ch>='0'&& ch <='9') //统计数字 number++; //等同于number=number+1; }
return "字符串中共有"+character+"个字母,"+blank+"个空格,"+number+"个数字"; } public static void main(String []args) throws Exception { //Java程序的主入口方法 String s="hello world 世界你好!!123*"; System.out.println("字符串的总长度:"+s.length()); //显示字符串长度 System.out.println("字符串中汉字长度:" +getChineseCount(s)); //调用方法显示汉字长度 System.out.println(getStringInfo(s)); //调用方法显示其他类型的长度 } }
(3)运行结果如下所示:
字符串的总长度:22 字符串中汉字长度:4 字符串中共有10个字母,2个空格,3个数字
源程序解读
(1)本实例程序中自定义一个String对象。String类对象的值是不能修改的,也就是具有不变性。length()是对象的方法而不是属性名。
(2)在不同的编码格式下,英文字母和中文字母所占用的字节数是不同的。isChineseChar()方法表明一个中文字母占用2个字节数。
实例37 比较字符串
在编程中经常会遇到比较字符串的问题,Java提供“==”和equals()方法,还有String对象中的compareTo()方法。本实例将说明如何使用这些方法,以及该使用哪个方法进行程序操作。
技术要点
• equals()方法继承自Object类,可以被用户覆盖。用来比较对象的内容是否相等。equals()和hashCode()两个方法的使用是紧密配合的,要是自己改写了其中一个,就必须改写另外一个。因为两个相等的对象必须有相同的hashCode。
• ==是一个关系运算符,当被比较者是基本类型时,比较其值是否相等;当被比较者是引用类型时,比较其是否引用同一个对象或者说比较其是否指向同一个内存地址。
• compareTo()将当前实例与同一类型的另一个对象进行比较,并返回一个整数,该整数指示当前实例在排序顺序中的位置是位于另一个对象之前、之后还是与其位置相同。
实现步骤
(1)新建一个类为TextCompare.java。
(2)代码如下所示:
package com.zf.s6; //创建一个包 public class TextCompare { public static void main(String[] args) { //Java程序的主入口方法 String str1 = "Hello World!"; String str2 = "Hello World!"; String str3 = new String("Hello World!"); String str4 = new String("Hello World!"); //比较两个字符串的hashcode,默认是内存地址 System.out.println("str1与str2的哈希码是否相同:" +(str1.hashCode()==str2.hashCode())); System.out.println("str1与str2值是否相等:" +(str1.equals(str2))); System.out.println("str1与str2是否指向同一个内存地址:" +(str1==str2));
System.out.println("str1与str3的哈希码是否相同:" +(str1.hashCode()==str3.hashCode())); System.out.println("str1与str3值是否相等:" +str1.equals(str3)); System.out.println("str1与str3是否指向同一个内存地址:" +(str1==str3)); int isSame=str1.compareTo(str2); str1=str3; //将对象str3赋给对象str1 System.out.println("str1与str3的哈希码是否相同:" +(str1.hashCode()==str3.hashCode())); System.out.println("str1与str3是否指向同一个内存地址:" +(str1==str3)); System.out.println("str1与str3是否指向同一个内存地址:" +(str4==str3)); int isSame1=str4.compareTo(str3); if(isSame==0) //判断是否相等,0为相等 System.out.println("运用compareTo方法比较str1与str2相等"); if(isSame1==0) System.out.println("运用compareTo方法比较str4与str3相等");
}
}
(3)运行结果如下所示:
str1与str2的哈希码是否相同:true str1与str2值是否相等:true str1与str2是否指向同一个内存地址:true str1与str3的哈希码是否相同:true str1与str3值是否相等:true str1与str3是否指向同一个内存地址:false str1与str3的哈希码是否相同:true str1与str3是否指同一个内存地址:true str1与str3是否指同一个内存地址:false 运用compareTo方法比较str1与str2相等
源程序解读
(1)创建str1与str2对象用来比较其值与hashcode是否相等。
(2)创建str3用来比较其与str1的值和hashcode是否相等。
(3)创建str4对象用来比较与str3对象是否相等以及它们是否指向同一个内存地址。
(4)将str3赋值给str1,再来比较str1与str3的值和hashcode是否相等。
(5)compareTo()方法用来比较对象str1与str2以及str4与str3是否按顺序对应每一个字符相等。
实例38 Java字符串与文件的互转
Java中有时候需要读取一个文本类的文件,将其转换为字符串。或是将字符串写入文件中,然后做进一步处理。Java中没有现成的API方法,需要自己手写。本实例讲解字符串与文件的互转。
技术要点
• 将字符串写入指定文件中,首先需要确认文件以及目录是否存在,如果不存在必须新建文件。
• 将字符串写入文件中,需要先将字符串读到BufferedReader缓冲流中,将文件写入到BufferedWriter缓冲流中。然后根据循环将字符串写入BufferedWriter缓冲流中。写入完毕后必须关闭BufferReader缓冲流,以防其他线程访问该文件失败。
• 文本文件转换为字符串,需要指定字符串的编码方式。通常默认的编码方式是Unicode编码,若不指定,会显示中文乱码。
• 将文本文件转换为字符串,需要将指定文件写入到InputStreamReader中。文本文件的内容读取到StringWriter文本写入流中。读取完毕后必须关闭InputStreamReader,以防出现未关闭流异常。
实现步骤
(1)新建一个类名为StringFromOrToFile.java。
(2)代码如下所示:
package com.zf.s6; //创建一个包 import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.StringReader; import java.io.StringWriter;
public class StringFromOrToFile { public static int DEFAULT_BUFFER_SIZE=1000; //将字符串写入指定文件(当指定的父路径中文件夹不存在时,会最大限度去创建,以保证保存成功!) public static boolean stringToFile(String res, String filePath) { boolean flag = true; BufferedReader bufferedReader = null; BufferedWriter bufferedWriter = null; try { File distFile = new File(filePath); //创建文件 if (!distFile.getParentFile().exists()) //判断父路径的文件夹是否存在 distFile.getParentFile().mkdirs(); //可以在不存在的目录中创建文件夹 bufferedReader = new BufferedReader( new StringReader(res)); //将原字符串读入缓冲 bufferedWriter = new BufferedWriter( new FileWriter(distFile)); //将文件写入缓冲 char buf[] = new char[1024]; //字符缓冲区 int len; while ((len = bufferedReader.read(buf)) != -1) {//while循环 bufferedWriter.write(buf, 0, len); //将字符串写入文件 } bufferedWriter.flush(); //刷新写入流的缓冲 bufferedReader.close(); //关闭读出流 bufferedWriter.close(); //关闭写入流 } catch (IOException e) { //捕获异常 e.printStackTrace(); flag = false; return flag; } finally { //finally方法总被执行 if (bufferedReader != null) { //判断读出流是否为空 try { bufferedReader.close(); //确保读出流关闭 } catch (IOException e) { e.printStackTrace(); } } } return flag; //返回布尔类型 } //文本文件转换为指定编码的字符串 public static String fileToString(String filePath, String encoding) { InputStreamReader reader = null; StringWriter writer = new StringWriter(); try { if (encoding == null||"".equals(encoding.trim())) { //判断编码类型是否为空 reader = new InputStreamReader( new FileInputStream(new File(filePath)), //设置编码方式 encoding); } else { reader = new InputStreamReader( new FileInputStream(new File(filePath))); //文件进入输入流 } char[] buffer = new char[DEFAULT_BUFFER_SIZE]; int n = 0; while (-1 != (n = reader.read(buffer))) { //while循环 writer.write(buffer, 0, n); //将输入流写入输出流 } } catch (Exception e) { //捕获异常 e.printStackTrace(); return null; } finally { //finally总被执行 if (reader != null) try { reader.close(); //确保输入流关闭 } catch (IOException e) { e.printStackTrace(); } } if (writer != null) return writer.toString(); //返回转换结果 else return null; } public static void main(String[] args) { //Java程序的主入口方法 String res="字符串写入指定文件\r\n文本文件转换为指定编码的字符串"; String filePath="E:/text/1.txt"; //文件 String encoding="GB2312"; //编码格式设置 System.out.println("字符串写入指定文件是否成功:"+ stringToFile(res,filePath)); //调用方法将字符串写入文件 System.out.println("从"+filePath+"文件根据"+encoding+ //调用方法读取文件的内容 "编码格式读到的内容:\r\n"+fileToString(filePath,encoding));
}
}
(3)运行结果如下所示:
字符串写入指定文件是否成功:true 从E:/text/1.txt文件根据GB2312编码格式读到的内容: 字符串写入指定文件 文本文件转换为指定编码的字符串
源程序解读
(1)stringToFile()方法用来将字符串写入到指定文件中。当文件或目录不存在时,自动为其创建文件或目录。字符串写入到文件中,返回成功标记。
(2)fileToString方法用来将文本文件转换为指定编码的字符串。如果不知道编码,调用的时候设为null即可。不过最后设置编码的方式,以免出现乱码。静态变量DEFAULT_BUFFER_SIZE用来设置写入的字符串的长度不大于这个数值。
实例39 截取带汉字的字符串
在实际开发中,开发人员获取字符串的子串,或者截取指定位置之间的字符串,往往会用String对象中的indexOf()和substring()方法进行截取,但如果字符串中含有汉字,按照上述方法进行操作会导致截取到半个汉字。该实例解决了这一问题。
技术要点
• 构造一个调用类,并传入要操作的字符串和截取的字节数,默认从字符串下标0开始截取。
• 根据汉字和字母的字节数不同,将字符串中的元素进行划分。判断传入的字节数指定截取的位置是否是一个汉字,如果是,则不截取该汉字,显示该汉字前的字符串。
实现步骤
(1)新建一个类名为TextTruncate.java。
(2)代码如下所示:
package com.zf.s6; //创建一个包 class CopyStrByByte{ //调用类 private String str = ""; //字符串 private int copyNum = 0; //要复制的字节数 private String arrStr[]; //存放将字符串拆分成的字符数组 private int cutNum = 0; //已截取的字节数 private int cc = 0; //str中的中文字符数 public CopyStrByByte(String str,int copyNum){ //构造函数变量初始化 this.str = str; this.copyNum = copyNum; } public String CopyStr(){ //该方法获得指定的子串 arrStr = str.split(""); //将传入的字符串拆分为字符数组 str = ""; //清空,用于存放已截取的字符 for (int i = 0;i < arrStr.length;i++){ if(arrStr[i].getBytes().length == 1){ //非汉字 cutNum = cutNum + 1; //统计个数 str = str + arrStr[i]; //获得非汉字子串 }else if (arrStr[i].getBytes().length == 2) { //汉字 cc = cc + 1; cutNum = cutNum + 2; //汉字字节数为2进行统计 str = str + arrStr[i]; } if (cutNum >= copyNum) break; //已截取的字符数大于或等于要截取的字符数 } if (cutNum > copyNum) //已截取的字符数大于要截取的字符数 return str.substring(0, copyNum - cc); else return str; } } public class TextTruncate{ //描述字符串长度的类 public static void main(String args[]){ //Java程序的主入口方法 CopyStrByByte cp = new CopyStrByByte("我ABC汉DEF",6); //调用类并初始化 System.out.println(cp.CopyStr()); //调用方法获取指定子串 } }
(3)运行结果如下所示:
我ABC
源程序解读
(1)本实例程序创建CopyStrByByte类作为调用类。将CopyStrByByte类进行构造函数传参。注意:一个类中只能有一个类名前用public修饰,并且类名与用public修饰的类名相同。否则编译不通过。
(2)CopyStr()方法用来操作字符串。Split()方法运用不同的分隔符将字符串转化为字符串数组。程序中运用字母间隔作为分隔符,然后对数组中的每个元素的字节数进行判断,字节数为2,则元素为汉字。
(3)对不同字节数的元素进行字节统计,当字节数大于传入的字节数时跳出。
(4)在主程序入口Main方法中,创建一个CopyStrByByte的类实例cp,同时使用由汉字与字母组成的字符串和字节数,作为构造函数的参数,用以初始化数据。
实例40 替换字符串中的部分字符
有时需要将数百张文稿中的“文章”二字全部替换为“问题”,将每张文稿一一进行修改将是一件费时费力的事情,下面将提供一种方法来解决这类问题。
技术要点
• 判断要进行替换的字符串是否存在,并需要遍历找到要替换的字符串的位置。
• 在替换的字符串的位置上进行替换,获得替换后生成的新的字符串。
实现步骤
(1)新建一个类名为TextReplace.java。
(2)代码如下所示:
package com.zf.s6; //创建一个包 public class TextReplace { //描述字符串长度的类 //这个方法将字符串line中的子串oldString全部替换为newString public static final String replace(String line, String oldString,String newString){ if (!"".equals("")) { //判断字符串是否为空 return null; } int i = 0; if ((i = line.indexOf(oldString, i)) >= 0) { char[] line2 = line.toCharArray(); //字符串放入数组 char[] newString2 = newString.toCharArray(); //要替换的字符串 int oLength = oldString.length(); //被替换的字符串的长度 StringBuffer buf = new StringBuffer(line2.length); buf.append(line2, 0, i).append(newString2); i += oLength; int j = i; while ((i = line.indexOf(oldString, i)) > 0) { //while循环 buf.append(line2, j, i - j).append(newString2); i += oLength; j = i; } buf.append(line2, j, line2.length - j); return buf.toString(); //返回替换后的字符串 } return line; } public static void main(String args[]) { //Java程序的主入口方法 String s = "I OK OK best OK"; //原字符串 System.out.println("替换前的字符串:"+s); String s1 = replace(s, "OK", "hello"); //调用方法进行替换 System.out.println("替换后的字符串:"+s1); //输出替换后的字符串 } }
(3)运行结果如下所示:
替换前的字符串:I OK OK best OK 替换后的字符串:I hello hello best hello
源程序解读
(1)本程序定义一个replace()方法,对传入的字符串line进行部分替换。运用String对象的indexOf()方法,判断是否存在要替换的子串。若不存在子串,indexOf()方法返回-1。
(2)StringBuffer是字符串变量,它的对象是可以补充和修改的。StringBuffer对象的append()方法用来补充添加字符串。程序中运用while循环,当碰到替换前的字符串时,添加替换后的字符串,来达到替换的目的。
实例41 Java字符串之密码加密
黑客这个名词已不再陌生,为了维护网络信息的安全性,对输入的数据进行加密是至关重要的。MD5加密是目前国内网页设计中使用最多的口令加密方式,用于确保信息传输完整一致。本实例将详细讲解MD5的应用。
技术要点
• Message-Digest泛指字节串的Hash变换,就是把一个任意长度的字节串变换成一定长的大整数。注意我使用了“字节串”而不是“字符串”这个词,是因为这种变换只与字节的值有关,与字符集或编码方式无关。
• MD5将任意长度的“字节串”变换成一个128bit的大整数,并且它是一个不可逆的字符串变换算法,换句话说就是,即使看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串。从数学原理上说,是因为原始的字符串有无穷多个,这有点像不存在反函数的数学函数。
实现步骤
(1)新建一个类名为TextMD5.java。
(2)代码如下所示:
package com.zf.s6; //创建一个包 import java.security.MessageDigest; public class TextMD5 { //描述对密码进行加密和验证的类 private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5","6", //十六进制数字到字符的映射数组 "7", "8", "9", "a", "b", "c", "d", "e", "f" }; public static String createPassword(String inputString) { //把inputString加密 return encodeByMD5(inputString); } //验证输入的密码是否正确,password:真正的密码 public static boolean authenticatePassword(String password, String inputString) { if (password.equals(encodeByMD5(inputString))) { return true; } else { return false; } } private static String encodeByMD5(String originString) {//对字符串进行MD5加密 if (originString != null) { try { //创建具有指定算法名称的信息摘要 MessageDigest md = MessageDigest.getInstance("MD5"); //使用指定的字节数组对摘要进行最后更新,完成摘要计算 byte[] results = md.digest(originString.getBytes()); String resultString = byteArrayToHexString(results); //得到的字节数组变成字符串返回 return resultString.toUpperCase(); //返回加密后的字符串 } catch (Exception ex) { ex.printStackTrace(); } } return null; } private static String byteArrayToHexString(byte[] b) {//转换字节数组为十六进制字符串 StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) { resultSb.append(byteToHexString(b[i])); //调用方法将字节数组转为十六进制字符串 } return resultSb.toString(); //返回十六进制字符串 } //将一个字节转化成十六进制形式的字符串 private static String byteToHexString(byte b) { int n = b; if (n < 0) n = 256 + n; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } public static void main(String[] args) { //Java主程序入口 String password = TextMD5.createPassword("888888"); //调用方法对字符串进行加密 System.out.println("对888888用MD5加密后的字符串:\r\n" +password); //加密后的字符串 String inputString = "8888"; System.out.println("8888与密码匹配?" //验证加密后的字符串 + TextMD5.authenticatePassword(password, inputString)); inputString = "888888"; System.out.println("888888与密码匹配?" //验证加密后的字符串 + TextMD5.authenticatePassword(password, inputString)); } }
(3)运行结果如下所示:
对888888用MD5加密后的字符串: 21218CCA77804D2BA1922C33E0151105 8888与密码匹配?false 888888与密码匹配?true
源程序解读
(1)通过java.Security.MessageDigest的静态方法getInstance()创建带有指定算法名称的信息摘要,参数为算法名,传入MD5则表示使用MD5算法。
(2)MessageDigest的digest()实例方法使用指定的字节数组对摘要进行最后的更新,然后完成摘要计算,返回存放哈希值结果的字节数组,这个字节数组就是MD5的加密产品。
(3)将加密后的字节数组转换成十六进制的字符串,形成最终的密码。
(4)当输入字符串经过MD5加密后,得到的字符串与密码一样,认为密码验证通过。
实例42 正则表达式验证字符串
在程序开发中,难免会遇到需要匹配、验证、判断字符串的情况,而这些情况有时又比较复杂,学习及使用正则表达式,便成了解决这一复杂的主要手段。正则表达式是一种可以用于模式匹配和替换的规范,本实例将讲解运用正则表达式对邮箱和网址格式的验证。
技术要点
• 利用正则表达式对字符串进行验证,将给定的正则表达式编译到指定标志的模式中,生成匹配的模式,然后与指定的字符串进行匹配,如果匹配符合此模式,则验证成功。
• 验证邮箱的格式,需要了解邮箱中“@”、“.”元素的位置和作用。
• 验证网址格式,需要了解网址中的协议和格式,以防验证出错。
实现步骤
(1)新建一个类名为RegExpValidator.java。
(2)代码如下所示:
package com.zf.s6; //创建一个包 import java.util.regex.*; public final class RegExpValidator { //描述Java正则表达式验证的类 /** * @param 待验证的字符串 * @return 如果是符合邮箱格式的字符串,返回<b>true</b>,否则为<b>false</b> */ public static boolean isEmail(String str) { Stringregex = "[a-zA-Z_]{1,}[0-9]{0,}@(([a-zA-z0-9]-*){1,}\\.){1,3}[a-zA-z\\-]{1,}"; return match(regex, str); } /** * @param 待验证的字符串 * @return 如果是符合网址格式的字符串,返回<b>true</b>,否则为<b>false</b> */ public static boolean isHomepage(String str) { String regex = "http://(([a-zA-z0-9]|-){1,}\\.){1,}[a-zA-z0-9]{1,}-*"; return match(regex, str); } /** * @param regex正则表达式字符串 * @param str 要匹配的字符串 * @return 如果str符合regex的正则表达式格式,返回true, 否则返回 false; */ private static boolean match(String regex, String str) { Pattern pattern = Pattern.compile(regex); //将传入的正则表达式编译到模式中 Matcher matcher = pattern.matcher(str); //模式进行匹配字符串 return matcher.matches(); //返回匹配结果 } public static void main(String[] args) { //Java程序的主入口方法 String mail = "aasina@.com"; String homePage="http://www.sina.com.cn"; System.out.println(mail + "邮箱是否有效:" + isEmail(mail));//调用方法验证邮箱 System.out.println(homePage+"网址格式是否有效:"//调用方法验证网址格式 +isHomepage(homePage)); } }
(3)运行结果如下所示:
aasina@.com邮箱是否有效:false http://www.sina.com.cn网址格式是否有效:true
源程序解读
(1)在主程序入口main方法中,设置了需要验证的邮箱和网址,调用isE-mail()方法,验证邮箱字符串格式是否合理。调用isHomepage()方法,验证网址字符串是否合理。
(2)isE-mail()方法里编写匹配邮箱的正则表达式,并与传入的字符串作为参数传入调用的match()方法。
(3)isHomepage()方法里编写匹配网址的正则表达式,并与传入的字符串作为参数传入调用的match()方法。
(4)match()方法内,根据传入的正则表达式生成相应的模板。模板又根据传入的字符串生成匹配模式对字符进行匹配处理,并将匹配结果返回。如果符合匹配模式,则验证通过。