3.2 “恺撒密码”实例
【案例说明】
恺撒密码是罗马时期恺撒创造的,用于加密通过信使传递的作战命令。它是将字母表中的字母移动一定位置来实现加密。例如,如果向右移动两位,则字母'A'将变为'C',字母'B'将变为'D',……,字母'X'变成'Z',字母'Y'则变为'A'。假如有个明文字符串"Hello"用这种方法加密,它将变为密文"Jgnnq";如果要解密该字符串,则只要将字母向相反方向移动同样的位数即可,如密文"Jgnnq"每个字母左移两位将变为"Hello"。这里,移动的位数“2”是加密和解密所用的密钥。本案例要求用Java语言编程来实现加密和解密这两个过程,程序运行界面如图3.3所示。
图3.3 加密解密问题
【案例目的】
(1)熟悉并掌握字符串常量的使用。
(2)理解并掌握字符串变量的定义和使用方法。
(3)熟练掌握从字符数组生成字符串的方法。
(4)熟练掌握针对字符串类的常用方法。
(5)理解并掌握字符串的转换方法。
【技术要点】
(1)首先,将要加密的内容和解密的内容看作一个字符串,由于恺撒密码器的移位是针对字符的,因此,需要将待处理字符串中的每个字符取出,然后对每个字符分别加以移位,这就需要使用String类的相应方法。
(2)Java语言提供了两个用于处理字符串的类:
● String类用于处理不可变的字符串。
● StringBuffer类用于处理可改变的字符串。
如果能够确信程序中使用的字符串的值在运行过程中不会改变,应尽量使用String类对象;如果使用的字符串值在程序运行过程少会改变,就要使用StringBuffer类对象,这样可以提高程序的运行性能,在处理字符串的编程中要考虑它们各自的特点。
【相关知识及注意事项】
3.2.1 字符串常量
字符串常量是用双引号引起来的字符序列,如"鲜花"、"Hello world"等。
字符串常量属于不可改变的字符串,因此在Java语言中,字符串常量是匿名的String对象,可以调用String的各个方法来处理这些字符串。
注意:Java语言中相同的字符串常量属于同一个对象,占用同一块内存空间,这和C中的处理方式不同。熟悉C的读者知道,若在C中定义两个字符串:chars[]="Hello world",t[]="Hello world";那么s和t将占用不同的内存空间,而Jave语言与此不同。
3.2.2 字符串变量
1.字符串的声明和创建
字符串可以用String声明和创建,String不是基本数据类型,而是一个类。
字符串的声明的格式如下:
String 字符串变量名;
例如,本案例中对要进行处理的加密或解密字符串的声明代码如下:
String s; //s即要进行处理的加密或解密字符串
2.字符串的创建
(1)用字符串常量来初始化一个字符串
例如:
String str="abc";
(2)用字符数组来创建字符串
例如:
char data[]={'a','b','c'};
String str=new String(data); //利用String构造函数来产生字符串
(3)创建一个空字符串
例如:
String s=new String();
(4)利用初始字符串创建字符串对象
String(String str),根据给定的字符串str创建字符串对象
例如,利用字符串常量创建字符串:
s=new String("I am a student.");
或简写成:
s="I am a student.";
字符串声明和创建也可一起完成:
Sting s=new String("I am a student.");
以下代码用字符串对象创建新字符串:
String newStr=new String(str);
以上代码使字符串newStr与字符串str的内容相同,但是,它们是两个字符串对象。
以上方法均可创建字符串,但采用第一种方法较为方便。表3.3列出了String类的构造函数。
表3.3 String类的构造函数
3.2.3 从字符数组生成字符串
Java语言提供了用字符数组来创建字符串的String类的构造方法,能够实现从字符数组生成字符串。
(1)String(char []chars)
根据字符数组chars创建一个字符串。
例如,代码:
char chars[]={'g','i','r','l'};
String s=new String(chars);
所生成的字符串内容是"girl"。
(2)String(char[]chars,int offset,int length)
取出字符数组,从数组的第offset位置开始,创建长度为length的字符串。
例如,以下代码输出3456。
3.2.4 字符串的操作
String类也提供了相当多的方法来处理字符串,如字符串的比较、连接、字符串的转换大小写等。表3.4列出了一些常用方法。
表3.4 String类的常用方法
注意:String字符串的操作不是对原字符串进行的,而是对新生成的一个原字符串的复制进行的,其操作结果不影响原字符串。相反,StringBuffer字符串的操作是对原字符串本身进行的,操作之后,原字符串的值发生了变化,变成操作后的新串。
表3.5列出了StringBuffer类的一些常用方法。
表3.5 StringBuffer类的常用方法
注意:如果操作后的字符串超出已分配的缓冲区,则系统会自动为它分配额外的空间。
下面对String类中常用的一些操作进行讲解。
1.字符串连接
字符串有一个连接运算符+,得到连接两个字符串的结果;一个连接方法concat(String s),实现复制参数s字符串的内容,连接在字符串对象之后,得到一新的字符串。
(1)运算符“+”
当两边都是字符串实例的引用时,运算的结果是将生成一个新的字符串实例,内容为两字符串实例内容的合并的结果。例如:
"String"+"abc" 结果是"String abc"
当两侧不一致时,先将不为字符串数据的数据转化成字符串数据,再进行字符串间的“+”操作。例如:
"String"+1 结果为"String 1"
(2)字符串连接方法
String concat(String str)
该方法将指定字符串str连到此字符串的结尾。
如果参数字符串的长度为0,则返回此String对象。否则,创建一个新的String对象,用来表示由此String对象表示的字符序列和由参数字符串表示的字符序列串联而成的字符序列。例如:
"cares".concat("s")returns"caress"
"to".concat("get").concat("her")returns"together"
2.获取字符串的长度
length()方法可以获取一个字符串长度。
public int length()
该方法返回此字符串的长度,长度等于字符串中16位Unicode字符数。
例如,以下代码输出4。
字符串常量也可以使用length()方法获得长度。例如:"我是好学生".length()的值是5。
3.字符串比较
(1)boolean equals(String str)和boolean equalsIgnoreCase(String s)
这两个方法都用来比较两个字符串的值是否相等,不同之处在于:后者是忽略大小写的。例如:
System.out.println("Java".equals("JAVA")); //输出的值应为false
System.out.println("Java".equalsIgnoreCase("JAVA")); //输出的值应为true
注意:运算符“==”用于比较两个引用变量是否引用同一个实例,而equals()和equalsIgnoreCase()则用于比较两个字符串中对应的每个字符值是否相同。例如:
(2)int compareTo(String str)
该方法用于比较两个字符串的大小,若调用方法的字符串比参数串大,则返回正整数:反之,则返回负整数;若两串相等,则返回0。
注意:①若两个串对应位置上的字符均相同,但长度不同,则方法的返回值为两者长度之差。例如:
System.out.println("Java".compareTo("JavaApplet")); //输出结果为-6
②若两个串有不同的字符,则从左边起第一个不同字符的Unicode码值之差即为两个字符串比较大小的结果。例如:
System.out.println("java".compareTo("Java")); //输出结果为32
(3)boolean endsWidth(String str)和boolean startsWidth(String str)
判断当前字符串是否以str字符串为后缀或是否以str字符串为前缀。例如,知道每一地区的电话号码都是以一些特定数字串开始,如果想要区分不同地区的电话号码,则可使用如下语句:
4.字符串检索
(1)charAt(int index)
该方法用于检索此字符串指定索引处的char值,索引范围为从0到length()-1,第一个char值在索引0处。
(2)indexOf(int ch)和indexOf(int ch,int fromIndex)
indexOf(int ch)方法检索在该对象表示的字符序列中第一次出现该字符的位置索引,如果未出现该字符,则返回-1。
indexOf(int ch,int fromIndex)方法在此对象表示的字符序列中第一次出现的大于或等于fromIndex的字符的位置索引,如果未出现该字符,则返回-1。
(3)lastIndexOf(int ch)和lastIndexOf(int ch,int fromIndex)
lastIndexOf(int ch)方法检索在该对象表示的字符序列中最后一次出现该字符的位置索引,如果未出现该字符,则返回-1。
lastIndexOf(int ch,int fromIndex)方法检索在此对象表示的字符序列中最后一次出现的大于或等于fromIndex的字符的位置索引,如果在该点之前未出现该字符,则返回-1。
(4)int indexOf(String s)和int indexOf(String s,int startpoint)
实现字符串检索。前一个方法是从指定字符串的头开始检索参数字符串s,返回字符串s首次出现的位置。后一个方法则在指定字符串中从某个位置开始检索参数字符串s,返回字符串s首次出现的位置。如果没有检索到。则返回-1。
例如以下代码,结果见注释。
5.字符串截取
截取字符串的子串得到一新的字符串,可以使用String类中的如下方法:
String substring(int startpoint);
String substring(int startpoint,int end);
其中startpoint是字符串的开始下标,end是截止下标。子串是从开始下标开始,至截止下标的前一个下标为止范围内的子串。例如,以下程序代码,结果如注释所示。
6.替换字符串的某字符得到一个新字符串
使用String类中的方法
String replace(char oldChar,char newChar)
用参数newChar指定的字符替换s中由oldChar指定的所有字符,产生一个新的字符串。例如,代码:
String s1="1234567788";
String s2=s1.replace("7","A");
创建两个String对象,s1为"1234567788",s2为"123456AA88"。
7.去掉前后空格得到一个新字符串
方法trim()可以去掉字符串的前后空格。例如代码:
String s="Hello world!";
System.out.println(s.trim()); //输出结果为"Hello world!"
字符串"Hello world!"去掉空格后得到一个新的字符串"Hello world!"。
注意:方法trim()不能改变字符串自己,而是产生一个新的字符串。
3.2.5 字符串的转换
1.字符串大小写的转换
(1)toUpperCase()
该方法将字符串内的所有小写字符改为大写。
(2)toLowerCase()
该方法将字符串内的所有大写字符改为小写。
例如代码:
System.out.println("hello".toUpperCase()); //输出结果为"HELLO"
System.out.println("YOU".toLowerCase()); //输出结果为"you"
2.字符串与基本数据类型的转换
Java中的各种原始数据类型与String类之间可以通过提供的各种方法相互转换。
(1)从基本类型到字符串的转换
String类提供的valueOf()系列的静态方法可用于从原始数据类型对象转换成字符串。valueOf()系列的静态方法见表3.6。
表3.6 String类中valueOf系列静态方法
例如,下列代码实现了整型数据到字符串的转换:
其他基本数据类型到字符串的转换与此类似。
(2)字符串到基本数据类型的转换
将字符串的值转换为其他数据类型的数据的实现方法有两种:
①先转换成相应的包装类实例,再调用对应的方法转换成其他类型。
基本数据类型对应的包装类大都提供了valueOf(String s)方法,一般格式如下:
public static 类型名 valueOf(String s)
该方法可以将字符串转换为指定包装类实例。
在此基础上,再调用包转类提供的可生成对象基本值的typeValue方法即可。例如:
Boolean b=Boolean.valueOf("true").booleanValue(); //将字符串转换成布尔数据
注意:Character没有成员方法valueOf方法,实现字符串的值到字符型基本数据类型的转换可使用String类的charAt(int n)方法。例如:
String str="abc";
char a=str.charAt(0); //返回字符'a'
②静态parseXXX方法。基本数据类型对应的包装类还提供了将字符串转换为基本值的parseType方法,使用该方法可以直接将字符串转换为指定的基本数据类型。
例如:
上述代码将字符串"1"转换成为了不同基本数据类型的数据。
3.字符串与数组的转换
(1)字符数组到字符串的转换
字符数组到字符串的转换可以通过下面的方法:
①String(char[]chars)
该方法为String类的构造方法,利用字符数组来创建字符串。
②static String valueOf(char[] data)
该方法为String类提供的valueOf()系列的静态方法,可以返回data数组参数的字符串表示形式。
例如,以下实现转换的代码:
(2)字符串到字符数组的转换
字符串到到字符数组的转换可以利用以下方法:
public char[] toCharArray()
该方法将此字符串转换为一个新的字符数组,返回一个新分配的字符数组,它的长度是此字符串的长度,而且内容被初始化为包含此字符串表示的字符序列。例如:
4.String与StringBuffer的转换
String类和StringBuffer类之间既有联系又有区别,为了在程序中更好的应用两者的优点,有时会对两者进行转换。
(1)String到Stringbuffer的转换
String到Stringbuffer的转换可以利用StringBuffer类的构造方法实现:
public StringBuffer(String str)
该方法可以构造一个字符串缓冲区,并将其内容初始化为指定的字符串内容。该字符串的初始容量为16加上字符串参数的长度。
例如:
(2)Stringbuffer到String的转换
Stringbuffer到String的转换有两种方法实现:
①public String(StringBuffer buffer)
该方法分配一个新的字符串,它包含当前包含在字符串缓冲区参数中的字符序列。此字符串缓冲区的内容已被复制,后续对它的修改不会影响新创建的字符串。
②public String toString()
该方法返回对象本身(它已经是一个字符串!)。
例如:设b为StringBuffer类型的变量,则
以上代码中,String s=new String(b);与String s=b.toString();有相同的效果。
【代码及分析】
程序解释及常见问题如下:
(1)字符串与数组一样,也是从0号字符开始的,所以在取字符串中的字符的循环“for(int i=0;i<s.length();i++){…}”中,循环变量i的初始值为0,终止值为字符串长度减1的大小。length()方法可以返回字符串的长度。
(2)代码“s.charAt(i)”的功能是返回字符串s中序列号为i的字符,序列号从0开始。
(3)代码“c+=key%26;”作用是将字符c往后移动key%26位,但是不能超出小写字母或大写字母的范围,如'z'再向后移动2位会超出范围,对于超出范围的数据要通过代码“if(c<'a')c+=26;if(c>'z')c-=26;”进行纠正,这样就能保证加密或解密后仍然是字母。对于大写字母的处理也一样。
【应用扩展】
(1)除String类外,还可以利用字符串缓冲类StringBuffer来处理字符串。这个类处理的是长度和内容可变的字符串。使用StringBuffer类,可以方便地在缓冲字符串里加入字符和子串,或替换缓冲字符串中的字符和子串。
(2)对英文句子的加密或解谜
对英文句子的加密或解谜时,只对单词进行加密或解谜处理,空格并不做处理。此时需要使用StringTokenizer类对英语句子处理成单个单词再加密或解谜,空格不进行加密。
java.util包中的类StringTokenizer用于语言符号(单词)的分析。例如,如果把空格作为分隔符,字符串“We are learning Java programming”有5个单词。要分析出字符串中的单词,要用StringTokenizer类的构造方法,由给定的字符串构造一个StringTokenizer对象,StringTokenizer对象称作字符串分析器。然后利用一个单词方法nextToken(),结合循环,从字符串分析器中逐一取出单词。用hasMoreTokens()方法控制循环,只要字符串中还有语言符号,该方法就返回true,否则返回false。调用countTokens()方法能得到字符串分析器中一共有多少个单词。
StringTokenizer类有两个常用构造方法:
StringTokenizer(String s),为字符串s构造一个分析器。使用默认的分隔符集合,即空格符(若干个空格被看作一个空格)、换行符、回车符、Tab符、进纸符。
StringTokenizer(String s,String delim),为字符串s构造一个分析器,以字符串参数delim中的字符作为分隔符。
下面是一个使用tokenizer的实例,代码如下:
输出以下字符串:
分离出的单词可运用本案例代码进行加密、解谜操作。