第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()方法内,根据传入的正则表达式生成相应的模板。模板又根据传入的字符串生成匹配模式对字符进行匹配处理,并将匹配结果返回。如果符合匹配模式,则验证通过。