------- android培训、java培训、期待与您交流! ----------
1. 练习一
需求:模拟trim方法,去除字符串两端的空格。
分析:
这里我们引用标准类库String类的trim方法API文档描述:
如果此String对象表示一个空字符序列,或者此String对象表示的字符序列的第一个和最后一个字符的代码都大于'\u0020'(空格字符),则返回对此String对象的引用。
否则,若字符串中没有代码大于'\u0020'的字符,则创建并返回一个表示空字符串的新String对象。
否则,假定k为字符串中代码大于'\u0020'的第一个字符的索引,m为字符串中代码大于'\u0020'的最后一个字符的索引。创建一个新的String对象,它表示此字符串中从索引 k 处的字符开始,到索引m处的字符结束的子字符串,即this.substring(k, m+1)的结果。
实现方式:
返回值类型为String,传入参数为需要被操作的字符串对象——String str。
首先,定义一个length局部整型变量,初始化值为str.length(),因为下面的代码需要反复使用字符串的长度,这样做可以减少调用length()方法的次数,提高代码效率。
其次,通过isEmpty()方法判断该字符串是否为空,如果满足返回原字符串。
第三,通过两次charAt方法,判断字符串第一个和最后一个字符是否是空格,如果均不是则返回原字符串。
第四,定义表示字符串角标值的局部整型变量index。在while循环内,通过charAt方法从头开始判断该字符串是否全部由空格组成,如果是则返回一个空字符串。
最后,如果以上判断都不满足,那么定义两个整型变量start和end,表示前后第一个非空格字符角标值。先后通过两个while循环,分别从头尾开始判断某个角标位上字符是否是空格,直到出现非空格字符,并通过start和end记录第一个非空格字符角标值,最终通过substring方法,返回用start和end截取的子串。
代码:
代码1
class StringTest
{
public static void main(String[] args)
{
String str1 = "";
System.out.println("空字符串:"+myTrim(str1));
String str2 = "Hello World!";
System.out.println("没有空格:("+myTrim(str2)+")");
String str3 = " ";
System.out.println("全为空格:(" + myTrim(str3) + ")");
String str4 = " Hello World! ";
System.out.println("前后空格:("+ myTrim(str4) + ")");
}
public static String myTrim(String str)
{
//获取字符串长度
int length = str.length();
//判断字符串是否为空
if(str.isEmpty())
return str;
//判断字符串前后是否有空格
if(str.charAt(0) != ' ' && str.charAt(length-1) != ' ')
return str;
//判断字符串是否是全部由空格组成
int index = 0;
while(str.charAt(index) == ' ')
{
//当判断到最后一个角标位仍为空格,返回一个空字符串
if(index == length-1)
return "";
index++;
}
//记录前后两个角标位数值
int start = 0, end = str.length()-1;
//从头角标位开始判断是否是空格,同时判断两角标值是否相同
while(str.charAt(start) == ' ')
start++;
//从尾角标位开始判断是否是空格,同时判断两角标值是否相同
while(str.charAt(end) == ' ')
end--;
return str.substring(start,end+1);
}
}运行结果为:
空字符串:
没有空格:(Hello World!)
全为空格: ()
前后空格:(Hello World!)
另外,有兴趣的朋友可以查看String类trim方法的源代码。
2. 练习二
需求一:将字符串反转。
分析:
首先,将字符串转换为字符数组。
其次,将字符数组反转。
最后,将反转后的字符数组转换为字符串。
实现方式:
实现方式比较简单,不再赘述。
代码:
代码2:
class StringTest2
{
public static void main(String[] args)
{
String oldStr = "123456789";
String newStr = reverseString(oldStr);
System.out.println(oldStr);
System.out.println(newStr);
}
public static String reverseString(String str)
{
char[] arr = str.toCharArray();
//将前后对应角标位上的字符进行交换,将该动作单独封装为一个方法
reverse(arr);
return new String(arr);
}
private static void reverse(char[] arr)
{
for(int start = 0, end = arr.length-1; start <= end; start++, end--)
//将交换动作再单独封装为一个方法
swap(arr,start, end);
}
private static void swap(char[] arr, int x, int y)
{
char temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
}运行结果为:
123456789
987654321
针对上述例子,给大家一个建议:上例中我们将一个“大”的功能细分为多个小方法,而不是“挤”在一个函数中,这样做不仅提高了代码的阅读性、后期的维护性,而且还提高了放法的复用性,其他方法也都可以使用这些小方法,而不必重复定义。
需求二:对某个字符串的指定部分字符进行反转。
分析:
首先,获取原字符串的字符数组形式。
其次,将上述字符数组指定角标位之间的字符进行反转。
最后,将反转后的字符数组转换为字符串返回。
实现方式:
只需在代码2的基础上进行修改即可。
首先,定义一个reverseString(Stringstr)方法的重载方法,参数列表为String str, int start, int end,其中start和end表示需要反转的部分字符串头尾角标值。这样就表示调用多参数reverseString方法,用于反转部分字符串;调用单参数reverseString方法,反转整个字符串。
其次,将代码2中的reverse方法参数列表改为char[]arr, int x, int y,x和y分别表示,需要反转的部分字符串(亦可理解为字符数组)的头尾角标值。相对应的for循环中start和end变量的初始化值不再是0和arr.length-1,而是x和y-1。这里之所以定义为y-1,是为了贯彻前述包含头,不包含尾的思想,那么相应的调用该方法时,传入的尾部角标值应比实际值大1。
这样一来,如果想要反转整个字符串,只需调用reverseString的重载方法,并传入头角标值0和字符串长度即可,无需重复定义方法体。
其他部分不做修改。
代码:
代码3:
class StringTest3
{
public static void main(String[] args)
{
String oldStr = "123456789";
//全部反转
String newStr = reverseString(oldStr);
//部分反转
String newStr2 = reverseString(oldStr, 2, 4);
System.out.println(oldStr);
System.out.println(newStr);
System.out.println(newStr2);
}
//该方法则专门用于反转整个字符窜
public static String reverseString(String str)
{
//只需调用reverseString重载方法,并传入头角标值和字符串长度即可
reverseString(arr,0, str.length());
}
//定义reverseString重载方法,用于反转部分字符串
public static String reverseString(String str, int start, int end)
{
char[] arr = str.toCharArray();
//传入需要反转部分的字符串头尾角标值
reverse(arr,start, end);
return new String(arr);
}
private static void reverse(char[] arr, int x, int y)
{
//start和end初值为需要反转部分的字符串头尾角标值
for(int start = x, end = y-1; start<=end; start++, end--)
swap(arr,start, end);
}
private static void swap(char[] arr, int x, int y)
{
char temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
}运行结果为:
123456789
987654321
124356789
3. 练习三
需求:
获取一个字符串在另一个字符串中出现的次数。
分析一:
首先要判断目标字符串中是否包含指定子串,如果不包含直接返回0。
其次,定义一个计数器。
第三,循环获取子串在目标字符串中的位置,获取到一个,计数器就记一次数。直到获取到的子串位置角标为-1时停止循环,并返回计数器数值。
实现方式一:
定义三个局部整型变量count、index和subLength,分别表示子串个数,起始搜索角标值以及子串长度。子串长度通过length()方法获取,可以避免后续代码频繁调用该方法。开启while循环,结束条件是从index角标位开始搜索到的子串所在角标是否为-1。如果不为-1,count自增,并将子串的长度相加至index值,表示下次从子串后面的位置开始搜索。最后返回子串个数——count。
代码:
代码5:
class StringTest5
{
public static void main(String[] args)
{
String target = "Hello World! Hello Java!";
String sub = "Hello";
System.out.println(getSubCount(target,sub));
}
public staticintgetSubCount(String target, String sub)
{
int count = 0, index = 0, subLength = sub.length();
//判断从指定位置开始搜索获取的子串角标值是否为-1
while((index = target.indexOf(sub, index)) != -1)
{
count++;
//表示下次从子串后面的位置开始搜索
index += subLength;
}
return count;
}
}运行结果为:
2
分析二:
通过split方法,按照子串将原字符串分割成字符串数组,再对字符数组的长度进行一定的处理,就可以作为子串的个数返回。
实现方式二:
split方法的特点是,如果在原字符串头部就包含一个子串,那么就会分割出一个空字符串,这就是需要对字符数组的长度进行处理的原因。这里我们以“ABC”和“22”字符串的组合为例进行简单分析。其中“22”为分割条件子串。组合方式有以下四种,
"ABC22ABC22ABC":子串个数2,字符数组长度3
"22ABC22ABC22ABC":子串个数3,字符数组长度4
"ABC22ABC22ABC22":子串个数3,字符数组长度3
"22ABC22ABC22ABC22":子串个数4,字符数组长度4
那么通过上述分析得知,只有在原字符串末尾不包含子串的情况下,子串个数比分割获得的字符数组长度少1,因此只需要通过lastIndexOf方法判断最后一个子串是否在原字符串的尾部即可。
代码:
代码6:
class StringTest6
{
public static void main(String[] args)
{
String str1 = "ABC22ABC22ABC";
String str2 = "ABC22ABC22ABC22";
String str3 = "22ABC22ABC22ABC";
String str4 = "22ABC22ABC22ABC22";
String sub = "22";
System.out.println(getSubCount(str1,sub));
System.out.println(getSubCount(str2,sub));
System.out.println(getSubCount(str3,sub));
System.out.println(getSubCount(str4,sub));
}
public static int getSubCount(String source, String sub)
{
/*
如果最后一个子串在原字符串的末尾,那么其起始角标值就是
source.length()-sub.length(),大家可以自行验证。
如果满足,直接返回字符数组长度即可,
如果不满足,返回值应是字符数组长度减1
*/
return source.lastIndexOf(sub)!= source.length()-sub.length() ? source.split(sub).length-1 : source.split(sub).length;
}
}运行结果为:
2
3
3
4
补充:
还有一种实现方法与方法一类似,不同之处在于while循环的判断条件内通过indexOf(Stringstr)方法获取角标值,为了避免重复获取角标值,在循环内通过substring方法传入index值截取原字符串,不断去掉已经获取到的子串以及子串前的字符,这样也可以获取子串个数,但是会在内存中产生大量字符串对象,不利于内存优化。
4. 练习四
需求:
获取两个字符串中最长相同子串。
分析:
假设有a、b一短一长两个字符串(如果长度相同,任取一个字符串为短串)。首先判断b中是否包含a,如果包含,那么a就是最长相同子串,否则从a中截取长度减1的子串,有两种截取方式——0角标到倒数第二个角标,以及1角标到末尾角标。再分别判断这两个子串是否包含于b中,以此类推,不断缩短a串的长度,这样可以保证取到某个长度下的所有子串,并逐一判断是否包含在b中。
实现方式:
首先,要判断两个字符串的长度,递减截取短串会比较高效。
其次,由于从短串截取的子串长度会不断变小,因此需要定义一个for循环,循环内的变量x不断递增,其中x表示短串长度的递减量。
第三,在外层for循环内再嵌套一个for循环,不断地从原短串中,通过两个不断自增的变量——start和end——截取子串。start表示头角标值,初始值为0;end表示尾角标值,初始化值由短串长度减去外层for循环中的递增变量算得。由于头尾角标值不断自增,这样就可以截取到某个长度下的所有子串,并逐一判断这些子串是否包含在长串中。
代码:
代码7:
class StringTest7
{
public static void main(String[] args)
{
String str1 = "abcdefJavafedcba";
String str2 = "123Java321";
System.out.println(getMaxSub(str1,str2));
}
public static String getMaxSub(String s, String l)
{
/*
先判断两字符串的长短,为便于代码阅读
要求s表示短字符串,l表示长字符串
如果不满足上述要求,就交换两个字符串
*/
if(s.length() > l.length())
{
String temp = l;
l = s;
s = temp;
}
int slength = s.length();
String temp = null;
//第一层循环决定短串递减量,每次递减一个长度
for(int x = 0; x <slength; x++)
{
//第二层循环按照第一层循环的递减量,按顺序截取子串
for(int start=0, end=slength-x ;end<=slength ; start++, end++)
{
//temp表示截取的子串
temp = s.substring(start, end);
//为方便观察代码的运作规律,打印每次截取的子串
System.out.println(temp);
//如果长串中包含截取得到的子串,表示找到最长相同子串,返回该子传
if(l.contains(temp))//这里也可以通过indexOf判断
return temp;
}
}
//如果最终未能找到最长相同子串,返回空字符串
return "";
}
}运行结果为:
123Java321
123Java32
23Java321
123Java3
23Java32
3Java321
123Java
23Java3
3Java32
Java321
123Jav
23Java
3Java3
Java32
ava321
123Ja
23Jav
3Java
Java3
ava32
va321
123J
23Ja
3Jav
Java
Java
1万+

被折叠的 条评论
为什么被折叠?



