Android的错误? :String.substring(5).replace(“”,“”)//空字符串错误、空字符串、Android、String

2023-09-04 06:07:03 作者:卟疼噯佬娘伱僦綄蛋ㄋ

下面是我的code:

 字符串str =just_a_string;
的System.out.println(]+ STR +[);
的System.out.println(]+ str.replace(,)+[);
的System.out.println(]+ str.substring(5)+[);
的System.out.println(]+ str.substring(5).replace(,)+[);
的System.out.println(]+ str.substring(3,8)+[);
的System.out.println(]+ str.substring(3,8).replace(,)+[);
的System.out.println(]+sdajndan.substring(5).replace(,)+[);
 

和这里是输出

  05-09 19:09:20.570:我/的System.out(23801):] just_a_string [
05-09 19:09:20.570:我/的System.out(23801):] just_a_string [
05-09 19:09:20.570:我/的System.out(23801):]通过对a_string [
05-09 19:09:20.570:我/的System.out(23801):] A_S [**
05-09 19:09:20.570:我/的System.out(23801):] t_a_s [
05-09 19:09:20.570:我/的System.out(23801):] T_ [**
05-09 19:09:20.570:我/的System.out(23801):] [**
 
android 字符串替换 Android开发之旅 android架构

显然,行标有**是意外。

这个问题发生在我的Andr​​oid手机A(LG P920的Optimus 3D,安卓2.3.3)。虽然我测试在我的Andr​​oid手机B(LG E720擎天柱别致,安卓2.2),它停止。我猜它运行到一个无限循环。

我已经测试了这两款手机,与Java 1.5 1.6 。这两个结果分别为相同的行为。

我还测试了我同一个Eclipse用的的Java 的项目,为 1.5 1.6 1.7 。他们所有的输出是正确的,符合市场预期。

我不知道这可能是实施与string.replace(,)对字符串的的支持数组。

能否请你帮我来测试你的设备?

任何人都可以请提供我的与string.replace(CharSequence的,CharSequence中)法的Andr​​oid源$ C ​​$ C? (像什么的 docjar )

非常感谢!

我已经修改了codeA位,所以它也可以显示在Android设备上。 (这是一样的code反正)。

在测试了我的两个手机A和手机B.行为研究仍然是相同的,如上所述。

 包com.example.testprojectnew;

进口android.app.Activity;
进口android.os.Bundle;
进口android.widget.TextView;

公共类MainActivity延伸活动{

    字符串output_text =;

    @覆盖
    保护无效的onCreate(包savedInstanceState){
        super.onCreate(savedInstanceState);
        的setContentView(R.layout.activity_main);

        字符串str =just_a_string;
        过程(1+ STR +[);
        过程(2]+ str.replace(,)+[);
        过程(3]+ str.substring(5)+[);
        过程(4]+ str.substring(5).replace(,)+[);
        进程(5]+ str.substring(3,8)+[);
        进程(6]+ str.substring(3,8).replace(,)+[);
        进程(7]+sdajndan.substring(5).replace(,)+[);

        output_text = output_text.concat(\ñ\ nLines(1和2),(3和4),(5和6),应该是相同的。);

        ((TextView中)findViewById(R.id.a_string))的setText(output_text)。
    }
    私人无效过程(字符串str){
        的System.out.println(STR);
        output_text = output_text.concat(STR).concat(\ N);
    }
}
 

解决方案

宾果!我发现这个错误!

感谢@izht提供的链接来源$ C ​​$ C 。我已经找到关于这个问题的错误。

这只是是具有字符串的支持数组时发生不同的(长)值比实际字符串。特别是,当在 String.offset (专用变量)是大于零。

这里的修复:

 公共字符串替换(CharSequence的目标,为CharSequence替换){
    如果(目标== NULL){
        抛出新的NullPointerException异常(目标==空);
    }
    如果(更换== NULL){
        抛出新的NullPointerException异常(替代==空);
    }

    串targetString = target.toString();
    INT matchStart =的indexOf(targetString,0);
    如果(matchStart == -1){
        //如果有什么取代,返回原始的字符串不变。
        回到这一点;
    }

    串replacementString = replacement.toString();

    //空目标在开始和结束,每个人物之间的匹配。
    INT targetLength = targetString.length();
    如果(targetLength == 0){
        INT resultLength =(数+ 2)* replacementString.length();
        StringBuilder的结果=新的StringBuilder(resultLength);
        result.append(replacementString);
//的for(int i =抵消; I<计数;我++){//原来,错误
        的for(int i =抵消; I<(计数+偏移量); ++ I){//修复
            result.append(值[I]);
            result.append(replacementString);
        }
        返回result.toString();
    }

    StringBuilder的结果=新的StringBuilder(计数);
    INT searchStart = 0;
    做 {
        //在比赛前的字符复制...
        result.append(数值,偏移+ searchStart,matchStart  -  searchStart);
        //插入替换...
        result.append(replacementString);
        //并跳过比赛...
        sea​​rchStart = matchStart + targetLength;
    }而((matchStart =的indexOf(targetString,searchStart))!= -1);
    //复制任何尾随字符...
    result.append(数值,偏移+ searchStart,计数 -  searchStart);
    返回result.toString();
}
 

我不知道为什么Android有改变(和改变错误地)的更换()以这种方式。最初的Java实现没有这个问题。

由这路,什么是呢?我能做些什么呢? (比其他更换()格外小心,或者扔掉我的Andr​​oid手机: - /)

顺便说一句即时通讯相当肯定我的LG E720擎天柱别致(安卓2.2),使用的是不同的源$ C ​​$ C比一个。它使停止(犯罪嫌疑人无限循环)在与string.replace()与空的目标字符串。最近我发现它抛出这个错误信息:

  05-10 16:41:13.155:E / AndroidRuntime(9384):致命异常:主要
05-10 16:41:13.155:E / AndroidRuntime(9384):java.lang.OutOfMemoryError
05-10 16:41:13.155:E / AndroidRuntime(9384):在java.lang.AbstractStringBuilder.enlargeBuffer(AbstractStringBuilder.java:97)
05-10 16:41:13.155:E / AndroidRuntime(9384):在java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java:157)
05-10 16:41:13.155:E / AndroidRuntime(9384):在java.lang.StringBuilder.append(StringBuilder.java:217)
05-10 16:41:13.155:E / AndroidRuntime(9384):在java.lang.String.replace(String.java:1497)
05-10 16:41:13.155:E / AndroidRuntime(9384):在com.example.testprojectnew.MainActivity.onCreate(MainActivity.java:22)
05-10 16:41:13.155:E / AndroidRuntime(9384):在android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
05-10 16:41:13.155:E / AndroidRuntime(9384):在android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
05-10 16:41:13.155:E / AndroidRuntime(9384):在android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
05-10 16:41:13.155:E / AndroidRuntime(9384):在android.app.ActivityThread.access $ 2300(ActivityThread.java:125)
05-10 16:41:13.155:E / AndroidRuntime(9384):在android.app.ActivityThread $ H.handleMessage(ActivityThread.java:2033)
05-10 16:41:13.155:E / AndroidRuntime(9384):在android.os.Handler.dispatchMessage(Handler.java:99)
05-10 16:41:13.155:E / AndroidRuntime(9384):在android.os.Looper.loop(Looper.java:123)
05-10 16:41:13.155:E / AndroidRuntime(9384):在android.app.ActivityThread.main(ActivityThread.java:4627)
05-10 16:41:13.155:E / AndroidRuntime(9384):在java.lang.reflect.Method.invokeNative(本机方法)
05-10 16:41:13.155:E / AndroidRuntime(9384):在java.lang.reflect.Method.invoke(Method.java:521)
05-10 16:41:13.155:E / AndroidRuntime(9384):在com.android.internal.os.ZygoteInit $ MethodAndArgsCaller.run(ZygoteInit.java:878)
05-10 16:41:13.155:E / AndroidRuntime(9384):在com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)
05-10 16:41:13.155:E / AndroidRuntime(9384):在dalvik.system.NativeStart.main(本机方法)
 

在第二个想法,如果for循环啄的是的错误。这应该是一个编译时的问题。为什么会在不同的手机采取不同(不同的Andr​​oid版本)?

完整解决方法

从谷歌的更新 ,他们已经修复了它,并将纠正在未来的版本。

与此同时,我写了一个修补的方法,基于的其code :

(这是必要的,因为(1)我们仍然必须等待正确的版本,(2),我们需要采取didnt认为该固定更新设备服务)的

  / **补丁为与string.replace(CharSequence的目标,CharSequence的更换),
 *因为原来是车的时候CharSequence的目标是空的,即。
 *修补通过谷歌Android:https://android-review.googlesource.com/58393
 * /
公共静态字符串replacePatched(最终串串,最终CharSequence的目标,最终的CharSequence替换){
    如果(目标== NULL){
        抛出新的NullPointerException异常(目标==空);
    }
    如果(更换== NULL){
        抛出新的NullPointerException异常(替代==空);
    }

    最后弦乐targetString = target.toString();
    INT matchStart = string.indexOf(targetString,0);
    如果(matchStart == -1){
        //如果有什么取代,返回原始的字符串不变。
        返回新的String(字符串);
    }

    最后的char []值= string.toCharArray(); //需要的补丁
    最终诠释计数= value.length; //需要的补丁

    最后弦乐replacementString = replacement.toString();

    //空目标在开始和结束,每个人物之间的匹配。
    如果(targetString.length()== 0){
        //结果包含了原来的计数字,的副本
        每个人的角色,并最终才//替换字符串
        //在最后替换字符串的副本。
        最终StringBuilder的结果=新的StringBuilder(数+(计数+ 1)* replacementString.length());
        result.append(replacementString);
        的for(int i = 0; I<计数;我++){
            result.append(值[I]);
            result.append(replacementString);
        }
        返回新的字符串(结果); // StringBuilder.toString()不给确切长度
    }

    最终StringBuilder的结果=新的StringBuilder(计数);
    INT searchStart = 0;
    做 {
        //在比赛前的字符复制...
        result.append(值,searchStart,matchStart  -  searchStart);
        //插入替换...
        result.append(replacementString);
        //并跳过比赛...
        sea​​rchStart = matchStart + targetString.length();
    }而((matchStart = string.indexOf(targetString,searchStart))!= -1);
    //复制任何尾随字符...
    result.append(值,searchStart,计数 -  searchStart);
    返回新的字符串(结果); // StringBuilder.toString()不给确切长度
}
 

本详细的版本:

  / **补丁为与string.replace(CharSequence的目标,CharSequence的更换),
 *因为原来是车的时候CharSequence的目标是空的,即。
 *修补通过谷歌Android:https://android-review.googlesource.com/58393
 * /
公共静态字符串replacePatched(最终串串,最终CharSequence的目标,最终的CharSequence替换){
    如果(目标== NULL){
        抛出新的NullPointerException异常(目标==空);
    }
    如果(更换== NULL){
        抛出新的NullPointerException异常(替代==空);
    }

//串targetString = target.toString(); // 原版的
    最后弦乐targetString = target.toString();
// INT matchStart =的indexOf(targetString,0); // 原版的
    INT matchStart = string.indexOf(targetString,0);
    如果(matchStart == -1){
        //如果有什么取代,返回原始的字符串不变。
//返回这一点; // 原版的
        返回新的String(字符串);
    }

    最后的char []值= string.toCharArray(); //需要的补丁
    最终诠释计数= value.length; //需要的补丁

//串replacementString = replacement.toString(); // 原版的
    最后弦乐replacementString = replacement.toString();

    //空目标在开始和结束,每个人物之间的匹配。
// INT targetLength = targetString.length(); // 原版的
//如果(targetLength == 0){//原
    如果(targetString.length()== 0){
// INT resultLength =(数+ 2)* replacementString.length(); // 原版的
// //结果包含了原来的计数字,的副本
每个人的角色,并最终才// //替换字符串
// //在最后替换字符串的副本。
// INT resultLength =计数+1(计数+ 1)* replacementString.length(); //修补通过谷歌Android
// StringBuilder的结果=新的StringBuilder(resultLength); // 原版的
        最终StringBuilder的结果=新的StringBuilder(数+(计数+ 1)* replacementString.length());
        result.append(replacementString);
//的for(int i =抵消; I<计数;我++){//原
// INT端=偏移+计数; //修补通过谷歌Android
//为(INT I =抵消;!I =结束; ++ I){//修补通过谷歌Android
        的for(int i = 0; I<计数;我++){
            result.append(值[I]);
            result.append(replacementString);
        }
//返回result.toString(); // 原版的
        返回新的字符串(结果); // StringBuilder.toString()不给确切长度
    }

// StringBuilder的结果=新的StringBuilder(计数); // 原版的
    最终StringBuilder的结果=新的StringBuilder(计数);
    INT searchStart = 0;
    做 {
        //在比赛前的字符复制...
// result.append(数值,偏移+ searchStart,matchStart  -  searchStart); // 原版的
        result.append(值,searchStart,matchStart  -  searchStart);
        //插入替换...
        result.append(replacementString);
        //并跳过比赛...
// searchStart = matchStart + targetLength; // 原版的
        sea​​rchStart = matchStart + targetString.length();
//}而((matchStart =的indexOf(targetString,searchStart))!= -1); // 原版的
    }而((matchStart = string.indexOf(targetString,searchStart))!= -1);
    //复制任何尾随字符...
// result.append(数值,偏移+ searchStart,计数 -  searchStart); // 原版的
    result.append(值,searchStart,计数 -  searchStart);
//返回result.toString(); // 原版的
    返回新的字符串(结果); // StringBuilder.toString()不给确切长度
}
 

Here is my code:

String str = "just_a_string";
System.out.println("]" + str + "[");
System.out.println("]" + str.replace("", "") + "[");
System.out.println("]" + str.substring(5) + "[");
System.out.println("]" + str.substring(5).replace("", "") + "[");
System.out.println("]" + str.substring(3, 8) + "[");
System.out.println("]" + str.substring(3, 8).replace("", "") + "[");
System.out.println("]" + "sdajndan".substring(5).replace("", "") + "[");

and here is the output

05-09 19:09:20.570: I/System.out(23801): ]just_a_string[
05-09 19:09:20.570: I/System.out(23801): ]just_a_string[
05-09 19:09:20.570: I/System.out(23801): ]a_string[
05-09 19:09:20.570: I/System.out(23801): ]a_s[      **
05-09 19:09:20.570: I/System.out(23801): ]t_a_s[
05-09 19:09:20.570: I/System.out(23801): ]t_[       **
05-09 19:09:20.570: I/System.out(23801): ][         **

Obviously, the lines marked with ** are unexpected.

This issue happens to my Android phone A (LG P920 Optimus 3D, Android 2.3.3). While i test on my Android phone B (LG E720 Optimus Chic, Android 2.2), it halts. i guess it runs into an infinite loop.

i have tested on both phones, with Java 1.5 and 1.6. Both result in the same behaviour respectively.

i have also tested on my same Eclipse with a Java project, for 1.5, 1.6, and 1.7. All of their outputs are correct, as expected.

i wonder this might be a device specific issue of implementing String.replace("", "") against the String's backing array.

Could you please help me to test in your devices?

Could anyone please provide me the Android source code of the String.replace(CharSequence, CharSequence) method? (like what in docjar)

Thanks a lot!

i have modified the code a bit, so it can also display on the Android device. (It is just the same code anyway).

Tested on both my phone A and phone B. Behaviours are still the same, as mentioned above.

package com.example.testprojectnew;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

    String output_text = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String str = "just_a_string";
        process("1]" + str + "[");
        process("2]" + str.replace("", "") + "[");
        process("3]" + str.substring(5) + "[");
        process("4]" + str.substring(5).replace("", "") + "[");
        process("5]" + str.substring(3, 8) + "[");
        process("6]" + str.substring(3, 8).replace("", "") + "[");
        process("7]" + "sdajndan".substring(5).replace("", "") + "[");

        output_text = output_text.concat("\n\nLines (1 & 2), (3 & 4), (5 & 6), should be the same.");

        ((TextView) findViewById(R.id.a_string)).setText(output_text);
    }
    private void process(String str) {
        System.out.println(str);
        output_text = output_text.concat(str).concat("\n");
    }
}

解决方案

Bingo! i have found the bug!

Thanks @izht for providing the link of the source code. i have located the bug regarding this problem.

This only happens when the String's backing array is having a different (longer) value than the actual String. In particular, when the String.offset (private variable) is larger than zero.

Here's the fix:

public String replace(CharSequence target, CharSequence replacement) {
    if (target == null) {
        throw new NullPointerException("target == null");
    }
    if (replacement == null) {
        throw new NullPointerException("replacement == null");
    }

    String targetString = target.toString();
    int matchStart = indexOf(targetString, 0);
    if (matchStart == -1) {
        // If there's nothing to replace, return the original string untouched.
        return this;
    }

    String replacementString = replacement.toString();

    // The empty target matches at the start and end and between each character.
    int targetLength = targetString.length();
    if (targetLength == 0) {
        int resultLength = (count + 2) * replacementString.length();
        StringBuilder result = new StringBuilder(resultLength);
        result.append(replacementString);
//        for (int i = offset; i < count; ++i) {             // original, bug
        for (int i = offset; i < (count + offset); ++i) {    // fix
            result.append(value[i]);
            result.append(replacementString);
        }
        return result.toString();
    }

    StringBuilder result = new StringBuilder(count);
    int searchStart = 0;
    do {
        // Copy characters before the match...
        result.append(value, offset + searchStart, matchStart - searchStart);
        // Insert the replacement...
        result.append(replacementString);
        // And skip over the match...
        searchStart = matchStart + targetLength;
    } while ((matchStart = indexOf(targetString, searchStart)) != -1);
    // Copy any trailing chars...
    result.append(value, offset + searchStart, count - searchStart);
    return result.toString();
}

i am not sure why Android has to alter (and altered wrongly) the replace() in this way. The original Java implementation doesn't have this issue.

By-the-way, what's now? What can i do with it? (other than using replace() with extra care, or throw away my Android phones :-/)

Btw i m quite sure my LG E720 Optimus Chic (Android 2.2) is using a different source code than that one. It keeps halting (suspect infinite looping) upon String.replace() with an empty target string. Lately i found it throws this error message:

05-10 16:41:13.155: E/AndroidRuntime(9384): FATAL EXCEPTION: main
05-10 16:41:13.155: E/AndroidRuntime(9384): java.lang.OutOfMemoryError
05-10 16:41:13.155: E/AndroidRuntime(9384):   at java.lang.AbstractStringBuilder.enlargeBuffer(AbstractStringBuilder.java:97)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java:157)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at java.lang.StringBuilder.append(StringBuilder.java:217)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at java.lang.String.replace(String.java:1497)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at com.example.testprojectnew.MainActivity.onCreate(MainActivity.java:22)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at android.app.ActivityThread.access$2300(ActivityThread.java:125)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at android.os.Handler.dispatchMessage(Handler.java:99)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at android.os.Looper.loop(Looper.java:123)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at android.app.ActivityThread.main(ActivityThread.java:4627)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at java.lang.reflect.Method.invokeNative(Native Method)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at java.lang.reflect.Method.invoke(Method.java:521)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:878)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)
05-10 16:41:13.155: E/AndroidRuntime(9384):   at dalvik.system.NativeStart.main(Native Method)

At a second thought, if that for-loop thingy is the bug. It should be a compile time issue. Why would it act differently in different phones (different versions of Android)?

Complete Workaround

Got an update from Google, that they have patched it, and will correct it in the future release.

Meanwhile, i have written a patched method, based on their code:

(This is necessary because (1) we still have to wait for the correct release, (2) we need to take care of devices that didnt make that fixed update)

/** Patch for the String.replace(CharSequence target, CharSequence replacement),
 *  because the original is buggy when CharSequence target is empty, i.e. "".
 *  Patched by Google Android: https://android-review.googlesource.com/58393
 */
public static String replacePatched(final String string, final CharSequence target, final CharSequence replacement) {
    if (target == null) {
        throw new NullPointerException("target == null");
    }
    if (replacement == null) {
        throw new NullPointerException("replacement == null");
    }

    final String targetString = target.toString();
    int matchStart = string.indexOf(targetString, 0);
    if (matchStart == -1) {
        // If there's nothing to replace, return the original string untouched.
        return new String(string);
    }

    final char[] value = string.toCharArray();                              // required in patch
    final int count = value.length;                                         // required in patch

    final String replacementString = replacement.toString();

    // The empty target matches at the start and end and between each character.
    if (targetString.length() == 0) {
        // The result contains the original 'count' characters, a copy of the
        // replacement string before every one of those characters, and a final
        // copy of the replacement string at the end.
        final StringBuilder result = new StringBuilder(count + (count + 1) * replacementString.length());
        result.append(replacementString);
        for (int i = 0; i < count; ++i) {
            result.append(value[i]);
            result.append(replacementString);
        }
        return new String(result);      // StringBuilder.toString() does not give exact length
    }

    final StringBuilder result = new StringBuilder(count);
    int searchStart = 0;
    do {
        // Copy characters before the match...
        result.append(value, searchStart, matchStart - searchStart);
        // Insert the replacement...
        result.append(replacementString);
        // And skip over the match...
        searchStart = matchStart + targetString.length();
    } while ((matchStart = string.indexOf(targetString, searchStart)) != -1);
    // Copy any trailing chars...
    result.append(value, searchStart, count - searchStart);
    return new String(result);          // StringBuilder.toString() does not give exact length
}

The verbose version:

/** Patch for the String.replace(CharSequence target, CharSequence replacement),
 *  because the original is buggy when CharSequence target is empty, i.e. "".
 *  Patched by Google Android: https://android-review.googlesource.com/58393
 */
public static String replacePatched(final String string, final CharSequence target, final CharSequence replacement) {
    if (target == null) {
        throw new NullPointerException("target == null");
    }
    if (replacement == null) {
        throw new NullPointerException("replacement == null");
    }

//    String targetString = target.toString();                                    // original
    final String targetString = target.toString();
//    int matchStart = indexOf(targetString, 0);                                  // original
    int matchStart = string.indexOf(targetString, 0);
    if (matchStart == -1) {
        // If there's nothing to replace, return the original string untouched.
//        return this;                                                            // original
        return new String(string);
    }

    final char[] value = string.toCharArray();                              // required in patch
    final int count = value.length;                                         // required in patch

//    String replacementString = replacement.toString();                          // original
    final String replacementString = replacement.toString();

    // The empty target matches at the start and end and between each character.
//    int targetLength = targetString.length();                                   // original
//    if (targetLength == 0) {                                                    // original
    if (targetString.length() == 0) {
//        int resultLength = (count + 2) * replacementString.length();            // original
//        // The result contains the original 'count' characters, a copy of the
//        // replacement string before every one of those characters, and a final
//        // copy of the replacement string at the end.
//        int resultLength = count + (count + 1) * replacementString.length();    // patched by Google Android
//        StringBuilder result = new StringBuilder(resultLength);                 // original
        final StringBuilder result = new StringBuilder(count + (count + 1) * replacementString.length());
        result.append(replacementString);
//        for (int i = offset; i < count; ++i) {                                  // original
//        int end = offset + count;                                               // patched by Google Android
//        for (int i = offset; i != end; ++i) {                                   // patched by Google Android
        for (int i = 0; i < count; ++i) {
            result.append(value[i]);
            result.append(replacementString);
        }
//        return result.toString();                                               // original
        return new String(result);      // StringBuilder.toString() does not give exact length
    }

//    StringBuilder result = new StringBuilder(count);                            // original
    final StringBuilder result = new StringBuilder(count);
    int searchStart = 0;
    do {
        // Copy characters before the match...
//        result.append(value, offset + searchStart, matchStart - searchStart);   // original
        result.append(value, searchStart, matchStart - searchStart);
        // Insert the replacement...
        result.append(replacementString);
        // And skip over the match...
//        searchStart = matchStart + targetLength;                                // original
        searchStart = matchStart + targetString.length();
//    } while ((matchStart = indexOf(targetString, searchStart)) != -1);          // original
    } while ((matchStart = string.indexOf(targetString, searchStart)) != -1);
    // Copy any trailing chars...
//    result.append(value, offset + searchStart, count - searchStart);            // original
    result.append(value, searchStart, count - searchStart);
//    return result.toString();                                                   // original
    return new String(result);          // StringBuilder.toString() does not give exact length
}