剑指offer打卡Day19:替换空格(从源码看String)
题目描述
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy
解析:
-
初见题目
就这就这?
不得不说看到题目的时候我是楞的,剑指怎么会出这么简单的算法题?
特地翻了下剑指offer的书,才发现词题主要考察的是C语言的指针。
用Java的话凭借其强大的类库可以轻松搞定的
- 解法一:
- 调用
String
中的replace()
方法即可
- 调用
- 解法二:
- 调用
String
中的charAt()
遍历其每个字符也可实现
- 调用
- 解完之后总觉得索然无味,好像在此题中没学到啥东西。但这题都用到了
String
中的方法,可以看看String的源码
- 解法一:
解答:
@Test
public void ttest1(){
StringBuffer test1 = new StringBuffer("We Are Happy");
System.out.println(replaceSpace_withReplace(test1));
System.out.println("=================");
System.out.println(replaceSpace_withCharAt(test1));
}
/**
* 解法1:
* 调用`String`中的`replace()`方法即可
* @param str
* @return
*/
public String replaceSpace_withReplace(StringBuffer str) {
return str.toString().replace(" ","%20");
}
/**
* 解法2:
* 调用`String`中的`charAt()`遍历其每个字符也可实现
* @param str
* @return
*/
public String replaceSpace_withCharAt(StringBuffer str) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i <str.length(); i++) {
char c = str.charAt(i);
if (c == ' '){
sb.append("%20");
}else {
sb.append(c);
}
}
return sb.toString();
}
String源码分析:
不得不说,其实看源码其实就是靠考察英语能力😏
参考博客:【Java源码分析】Java8的String源码分析
-
String定义:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
-
从类的声明中可以看到:
-
String为final类,不能被继承
-
那 StringBuffer 和 StringBuilder 它们呢?
//StringBuffer public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence //StringBuilder public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence
-
原来是直接实现CharSequence接口
CharSequence 接口,表示是一个有序字符的序列,因为String的本质是一个char类型数组,理同StringBuffer 和StringBuilder
-
-
String类实现了java.io.Serializable接口,可以实现序列化
-
String类实现了
Comparable<String>
,可以用于比较大小(按顺序比较单个字符的ASCII码)
-
-
-
字段属性
//用来存字符串,字符串的本质,是一个final的char型数组 private final char value[]; //缓存字符串的哈希 private int hash; // Default to 0 //实现序列化的标识 private static final long serialVersionUID = -6849794470754667710L;
-
构造函数:
好家伙,不看不知道,一看才发现有十几个构造方法,结合api及其他技术博客,重点看下这几个。
/** 1.这是一个经常会使用的String的无参构造函数。 * 默认将""空字符串的value赋值给实例对象的value,也是空字符 */ public String() { this.value = "".value; } /** 2.这是一个有参构造函数,参数为一个String对象 * 将形参的value和hash赋值给实例对象作为初始化 */ public String(String original) { this.value = original.value; this.hash = original.hash; } /** 3.这是一个有参构造函数,参数为一个char字符数组 * 虽然我不知道为什么要Arrays.copyOf去拷贝,而不直接this.value = value; * 意义就是通过字符数组去构建一个新的String对象 */ public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } /** 4.这是一个有参构造函数,参数为char字符数组,offset(起始位置,偏移量),count(个数) * 作用就是在char数组的基础上,从offset位置开始计数count个,构成一个新的String的字符串 * 意义就类似于截取count个长度的字符集合构成一个新的String对象 * 另外:此类构造方法还有用入参为 public String(byte value[], int offset, int count) 的重写,不再赘述 */ public String(char value[], int offset, int count) { if (offset < 0) { //如果起始位置小于0,抛异常 throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { if (count < 0) { //如果个数小于0,抛异常 throw new StringIndexOutOfBoundsException(count); } if (offset <= value.length) { //在count = 0的前提下,如果offset<=len,则返回"" this.value = "".value; return; } } // Note: offset or count might be near -1>>>1. //如果起始位置>字符数组长度 - 个数,则无法截取到count个字符,抛异常 if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } //重点,从offset开始,截取到offset+count位置(不包括offset+count位置) this.value = Arrays.copyOfRange(value, offset, offset+count); } /** 5.这是一个有参构造函数,参数为int字符数组,offset(起始位置,偏移量),count(个数) * 作用跟04构造函数差不多,但是传入的不是char字符数组,而是int数组。 * 而int数组的元素则是字符对应的ASCII整数值 * 例子:new String(new int[]{97,98,99},0,3); output: abc */ public String(int[] codePoints, int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count <= 0) { if (count < 0) { throw new StringIndexOutOfBoundsException(count); } if (offset <= codePoints.length) { this.value = "".value; return; } } // Note: offset or count might be near -1>>>1. if (offset > codePoints.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } //以上都是为了处理offset和count的正确性,如果有错,则抛异常 final int end = offset + count; // Pass 1: Compute precise size of char[] int n = count; for (int i = offset; i < end; i++) { int c = codePoints[i]; if (Character.isBmpCodePoint(c)) continue; else if (Character.isValidCodePoint(c)) n++; else throw new IllegalArgumentException(Integer.toString(c)); } // Pass 2: Allocate and fill in char[] final char[] v = new char[n]; for (int i = offset, j = 0; i < end; i++, j++) { //从offset开始,到offset + count int c = codePoints[i]; if (Character.isBmpCodePoint(c)) v[j] = (char)c; //将Int类型显式缩窄转换为char类型 else Character.toSurrogates(c, v, j++); } this.value = v; //最后将得到的v赋值给String对象的value,完成初始化 } /** 6.这是一个有参构造函数,参数为byte数组,offset(起始位置,偏移量),长度,和字符编码格式 * 就是传入一个byte数组,从offset开始截取length个长度,其字符编码格式为charsetName,如UTF-8 * 例子:new String(bytes, 2, 3, "UTF-8"); */ public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException { if (charsetName == null) throw new NullPointerException("charsetName"); checkBounds(bytes, offset, length); this.value = StringCoding.decode(charsetName, bytes, offset, length); } /**同上 */ public String(byte bytes[], int offset, int length, Charset charset) { if (charset == null) throw new NullPointerException("charset"); checkBounds(bytes, offset, length); this.value = StringCoding.decode(charset, bytes, offset, length); } /** 7.这是一个有参构造函数,参数为byte数组和字符集编码 * 用charsetName的方式构建byte数组成一个String对象 */ public String(byte bytes[], String charsetName) throws UnsupportedEncodingException { this(bytes, 0, bytes.length, charsetName); } /** 同上 */ public String(byte bytes[], Charset charset) { this(bytes, 0, bytes.length, charset); } /** 8.有参构造函数,参数为StringBuffer类型 * 就是将StringBuffer构建成一个新的String,比较特别的就是这个方法有synchronized锁 * 同一时间只允许一个线程对这个buffer构建成String对象 */ public String(StringBuffer buffer) { synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); //使用拷贝的方式 } } /** 9.有参构造函数,参数为StringBuilder * 同8差不多,只不过是StringBuilder的版本,差别就是没有实现线程安全 */ public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); } /** 10这个构造函数比较特殊,有用的参数只有char数组value,是一个不对外公开的构造函数,没有访问修饰符 * 加入这个share的只是为了区分于String(char[] value)方法,用于重载,功能类似于03,我也在03表示过疑惑。 * 为什么提供这个方法呢,因为性能好,不需要拷贝。为什么不对外提供呢?因为对外提供会打破value为不变数组的限制。 * 如果对外提供这个方法让String与外部的value产生关联,如果修改外不的value,会影响String的value。所以不能对外提供 */ String(char[] value, boolean share) { // assert share : "unshared not supported"; this.value = value; }
-
太长不看版总结:
-
可以构造空字符串对象,既
""
-
可以根据
String
,StringBuilder
,StringBuffer
构造字符串对象 -
可以根据
char
数组,其子数组构造字符串对象 -
可以根据
int
数组,其子数组构造字符串对象 -
可以根据某个字符集编码对
byte
数组,其子数组解码并构造字符串对象
-
-
-
replace类函数
//替换,将字符串中的oldChar字符全部替换成newChar public String replace(char oldChar, char newChar) { if (oldChar != newChar) { //如果旧字符不等于新字符的情况下 int len = value.length; //获得字符串长度 int i = -1; //flag char[] val = value; /* avoid getfield opcode */ while (++i < len) { //循环len次 if (val[i] == oldChar) { //找到第一个旧字符,打断循环 break; } } if (i < len) { //如果第一个旧字符的位置小于len char buf[] = new char[len]; 新new一个字符数组,len个长度 for (int j = 0; j < i; j++) { buf[j] = val[j]; 把旧字符的前面的字符都复制到新字符数组上 } while (i < len) { //从i位置开始遍历 char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; //发生旧字符就替换,不想关的则直接复制 i++; } return new String(buf, true); //通过新字符数组buf重构一个新String对象 } } return this; //如果old = new ,直接返回自己 } //替换第一个旧字符 String replaceFirst(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceFirst(replacement); } //当不是正规表达式时,与replace效果一样,都是全体换。如果字符串的正则表达式,则规矩表达式全体替换 public String replaceAll(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceAll(replacement); } //可以用旧字符串去替换新字符串 public String replace(CharSequence target, CharSequence replacement) { return Pattern.compile(target.toString(), Pattern.LITERAL).matcher( this).replaceAll(Matcher.quoteReplacement(replacement.toString())); }
-
由此可见String的替换有四种方式:
- 将所有 newChar 换成oldChar
- 将首个 newChar 换成oldChar
- 用正则替换
- 用new subString去替换 old subString
具体例子:
String s = "my.test.txt"; System.out.println(s.replace(".", "#")); //my#test#txt System.out.println(s.replaceAll(".", "#")); //########### System.out.println(s.replaceFirst(".", "#")); //#y.test.txt System.out.println(s) //my.test.txt
-
参考资料:
-
分析非常全面,推荐一看
-
机器翻译的有时不是很好,推荐还是看英文版https://docs.oracle.com/javase/8/docs/api/
-
截图的注解还是很详细的
-
看到有就稍作记录哈哈