剑指offer打卡Day19:替换空格(从源码看String)

剑指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
      

参考资料:

  1. 【Java源码分析】Java8的String源码分析

    分析非常全面,推荐一看

  2. Java8 中文api

    机器翻译的有时不是很好,推荐还是看英文版https://docs.oracle.com/javase/8/docs/api/

  3. java源码解析之string(一)

    截图的注解还是很详细的

  4. markdown表情包

    看到有就稍作记录哈哈

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值