干货深度讲解APP结构,看完后你学会了?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了干货深度讲解APP结构,看完后你学会了?相关的知识,希望对你有一定的参考价值。

参考技术A > 本文节选自霍格沃兹测试学院内部教材

我们要学习App结构,那么就是一定先了解APK包的结构。

什么是APK  

APK 是 android Package 的缩写,其实就是 Android 的安装包。通过将 APK 文件直接传到 Android 模拟器或

Android 手机中执行即可安装。

APK 文件其实是 zip 格式,但后缀名被修改为 apk,通过 Android Studio 可以看到 APK 内部的文件。

APK结构  

知道什么是 APK 之后,接下来再来看看 APK 里面的结构是怎么样的。

下面拿雪球 APK 来举例。

  * lib/ 目录

存放的是一些 so 文件。so 文件是二进制文件,用来兼容各种类型的 CPU。

Android 开发中,在打包发布应用时会选择应用适配的 CPU 架构平台,在引用第三方库时也遇到根据不同 CPU 架构引入相应的 so 包。Android

主要包括这几种 CPU 架构:armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips,大多数情况下只需要支持

armabi 与 x86 的架构即可。

不同的 CPU 架构决定了 app 可以运行在哪些设备上。比如我们的手机设备一般来说用的是 arm 架构,而我们的模拟器一般来说是 x86 架构。如果

app 只支持 arm 架构的话,那么就只能安装在真机上,模拟器上安装就会不成功。如果是两种架构都支持的话,那么就是真机和模拟器都可以安装了。

  * res/ 目录

res 目录是工程资源目录,存放的是各种资源文件,包括界面布局,图片,字符串等。

  * assets/ 目录

assets 目录用来存放配置文件。

  * classes(n).dex 文件

DEX 编译 Java 的 Class 文件,生成 classes.dex 文件。

  * resources.arsc 文件

resources.arsc 文件是编译后的二进制资源文件。

  * AndroidManifest.xml 文件

AndroidManifest.xml 文件是 Android

的清单文件,是每个应用都必须定义和包含的。它描述了应用的名字、版本、权限、引用的库文件等等信息。

移动端App分类  

app 可以分为下面这些类型

  1. Native App:原生 app 手机应用程序,使用原生的语言开发的手机应用。比如系统自带的计算器、闹钟就是原生 app。

  2. Hybrid App:混合型 app 手机应用程序,混合使用原生的程序和 html5 页面开发的手机应用。现在大部分的 app 都是这种混合型的,比如微信,支付宝等等。

  3. Web App:基于 Web 的 app 手机应用程序,完全使用 HTML5 页面加前端 js 框架开发的手机应用。比如在浏览器中打开美团,这个页面就是网页 Web App。

Native App  

对于 Native APP 来说,一个页面上有下面这些类型的对象。

 **Activity**

Activity 是 Android 四大组件之一,用于展示一个与用户交互的界面。Activity 是存放 View

对象的容器,也是界面的载体,可以用来展示一个界面。

 **Window**

Window 是 Android 中的窗口,表示顶级窗口,也就是主窗口。它提供标准的用户界面策略,如背景、标题、区域、默认按键处理等。

 **View**

View 就是一个个视图的对象。视图,是用户接口组件的基本构建块,它在屏幕中占用一个矩形区域,它是所有 UI 控件的基类,如一个按钮或文本框。View

负责图形界面渲染及事件处理。

 **ViewGroup**

ViewGroup 是 Android 中的视图组。包含多个 View,也可以包含 ViewGroup。

查看界面元素  

对于 Android 来说,要查看 app 界面的元素需要用到工具帮忙,常用的工具有 uiautomatorviewer。这是 Android SDK

自带的工具,使用起来非常简单。它可以获取到整个界面的布局,我们通过它就可以很容易的查看界面当中的元素和元素的属性了。

元素属性  

左侧就是我们同步过来的界面,可以在界面上直接选择元素,然后右面就会展示界面布局和对应元素的属性。

布局  

了解 app 的界面布局,可以让我们做自动化测试的时候更容易去定位页面上的元素。

这里的布局就是指界面元素排布的方式。界面上的布局方式都有以下这些方式:

  * 线性布局(LinearLayout):所有子视图在单个方向(垂直或水平)保持对齐

  * 相对布局(RelativeLayout):每个视图的位置可以指定为相对于同级元素的位置。例如在另一个视图的左侧或下方,或相对于父级区域的位置,例如在底部、左侧或中心对齐

  * 帧布局(FrameLayout):坐标原点是屏幕的左上角,位置固定,只需为控件指定大小即可,用来显示一个单一的视图

  * 绝对布局(AbsoluteLayout):能够指定其子视图的确切位置

  * 表格布局(TableLayout):通过画表表格的方式来实现布局,整个页面就相当于一张大的表格,控件就放在每个 Cell 中

常见属性  

  * index:元素索引

  * text:显示文本

  * resource-id:元素id

  * class:类名

  * package:包名

  * content-desc:描述文案

  * checkable:是否可以选择

  * checked:是否已经选择

  * clickable:是否可以点击

  * enabled:是否可用

  * focusable:是否可以聚焦

  * fucused:是否已经聚焦

  * scrollable:是否可以滚动

  * long-clickable:是否可以长选择

  * password:是否为密码输入框

  * selected:是否已选择

  * bounds:元素位置坐标

在这些属性当中,对于测试有意义的有 text、resource-id class 和 content-

desc,其余的暂时不用太关注。这几个属性在后面做自动化的时候,可以用来定位元素。

App结构讲解就先讲到这里啦,下期我们分享adb常用命令。大家还想看什么内容的文章也可以留言告诉我们哦!

 ** _ 

来霍格沃兹测试开发学社,学习更多软件测试与测试开发的进阶技术,知识点涵盖web自动化测试 app自动化测试、接口自动化测试、测试框架、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移、测试右移、精准测试、测试平台开发、测试管理等内容,课程技术涵盖bash、pytest、junit、selenium、appium、postman、requests、httprunner、jmeter、jenkins、docker、k8s、elk、sonarqube、jacoco、jvm-sandbox等相关技术,全面提升测试开发工程师的技术实力

QQ交流群:484590337

公众号 TestingStudio

视频资料领取:https://qrcode.testing-studio.com/f?from=jianshu&url=https://ceshiren.com/t/topic/15844

点击查看更多信息

深度分析:面试腾讯,阿里面试官都喜欢问的String源码,看完你学会了吗?

前言

最近花了两天时间,整理了一下String的源码。这个整理并不全面但是也涵盖了大部分Spring源码中的方法。后续如果有时间还会将剩余的未整理的方法更新到这篇文章中。方便以后的复习和面试使用。如果文章中有地方有问题还请指出。

简述

字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了String 类来创建和操作字符串。字符串缓冲区支持可变字符串。因为String对象是不可变的,因此可以共享它们。

String类代表字符串,Java程序中的所有字符串字面值如"abc"都是这个类的实例对象。String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了。如果需要对字符串做很多修改,那么应该选择使用StringBuilder或者StringBuffer。

最简单的创建字符串的方式:String qc = "qiu chan"编译器会使用该值创建一个 对象。我们也可以使用关键字New创建String对象。
String类型的常量池比较特殊。它的主要使用方法有两种:
直接使用双引号声明出来的String对象会直接存储在常量池中。
如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。

继承/实现关系

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    // 省略
}    

String是final修饰的不能够被继承和修改。

源码

String的底层使用的是char数组用于存储。

private final char value[];

缓存字符串的哈希码默认值为0

private int hash;

无参数构造函数

public String() {
        this.value = "".value;
}

解析:初始化一个新创建的String对象,使其代表一个空字符序列。 注意,由于String是不可变的,所以不需要使用这个构造函数。

参数为字符串的构造函数

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

解析:初始化一个新创建的String对象,使其代表与参数相同的字符序列。换句话说,新创建的字符串是参数字符串的副本。除非需要参数字符串的显式拷贝,否则不需要使用这个构造函数,因为String是不可变的。

参数为char数组的构造函数

public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
}

解析:分配一个新的String,使其代表当前字符数组参数中包含的字符序列。使用Arrays.copyOf方法进行字符数组的内容被复制。字符数组的后续修改不会影响新创建的字符串。

参数为char数组并且带有偏移量的构造方法

// value[]:作为字符源的数组,offset:偏移量、下标从0开始并且包括offset,count:从数组中取到的元素的个数。
public String(char value[], int offset, int count) {
  // 如果偏移量小于0抛出IndexOutOfBoundsException异常
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
  // 判断要取的元素的个数是否小于等于0
    if (count <= 0) {
    // 要取的元素的个数小于0,抛出IndexOutOfBoundsException异常
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
    // 在要取的元素的个数等于0的情况下,判断偏移量是否小于等于数组的长度
        if (offset <= value.length) {
      // 偏移量小于等于数组的长度,返回一个空字符串数组的形式
            this.value = "".value;
            return;
        }
    }
    // 如果偏移量的值大于数组的长度减去取元素的个数抛出IndexOutOfBoundsException异常
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
  // 复制元素
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}

解析:分配一个新的Sting,来源于给定的char数组中的字符。offset参数是子数组中第一个字符的索引,count参数指定子数组的长度。子数组被被复制以后,对字符数组的修改不会影响新创建的字符串。

参数为StringBuffer的构造方法

public String(StringBuffer buffer) {
  // 这里对StringBuffer进行了加锁,然后再进行拷贝操作。这里对其进行加锁正是为了保证在多线程环境下只能有一个线程去操作StringBuffer对象。
    synchronized(buffer) {
        this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
    }
}

解析:分配一个新的字符串,该字符串包含当前字符串缓冲区参数中包含的字符序列。Arrays.copyOf方法进行字符串缓冲区中内容的复制。这里对StringBuffer进行了加锁,然后再进行拷贝操作。这里对其进行加锁正是为了保证在多线程环境下只能有一个线程去操作StringBuffer对象。

参数为StringBuilder的构造方法

public String(StringBuilder builder) {
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

解析:参数是StringBuilder,这个是线程不安全的,但是性能相对于StringBuffer有很大的提升,源码的注释中说通过toString方法从字符串构建器中获取字符串可能会运行得更快,通常是首选。

length方法

public boolean isEmpty() {
    // 底层的char数组的长度是否为0进行判断
        return value.length == 0;
}

//举例
@Test
public void test_string_isEmpty(){
    System.out.println(" ".isEmpty());// false
  System.out.println("".isEmpty());// true
}

解析:返回此字符串的长度。查看源码发现,这个value是一个char数组,本质获取的是字符串对应的char数组的长度。

isEmpty方法

public boolean isEmpty() {
    // 底层的char数组的长度是否为0进行判断
        return value.length == 0;
}

//举例
@Test
public void test_string_isEmpty(){
    System.out.println(" ".isEmpty());// false
  System.out.println("".isEmpty());// true
}

解析:判断给定的字符串是否为空,底层实现是根据char数组的长度是否为0进行判断。

charAt方法

public char charAt(int index) {
  // 给定的索引小于0或者给定的索引大于这个字符串对应的char数组的长度抛出角标越界异常
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
  // 获取当前的指定位置的char字符
    return value[index];
}

解析:根据给定的索引获取当前的指定位置的char字符。如果给定的索引否小于0,或者给定的索引是大于这个字符串对应的char数组的长度抛出角标越界异常。index是从0开始到length-1结束。序列的第一个char值在索引0处,下一个在索引1处,依此类推,与数组索引一样。

getChars方法

// srcBegin:要复制的字符串中第一个字符的索引【包含】。srcEnd:要复制的字符串中最后一个字符之后的索引【不包含】。dst[]:目标数组。dstBegin:目标数组中的起始偏移量。
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
  // 校验起始索引小于0抛出角标越界异常
    if (srcBegin < 0) {
        throw new StringIndexOutOfBoundsException(srcBegin);
    }
  // 校验结束索引大于原始字符串的长度抛出角标越界异常
    if (srcEnd > value.length) {
        throw new StringIndexOutOfBoundsException(srcEnd);
    }
  // 校验结束索引大于起始索引抛出角标越界异常
    if (srcBegin > srcEnd) {
        throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    }
  // 数组的拷贝
    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

// 案例
@Test
public void test_string_codePointAt(){
  // 原始字符串
    String h = "ahelloworld";
  // 目标char数组
    char[] data = new char[4];
  // 执行拷贝
    h.getChars(2, 6, data, 0);
    System.out.println(data);
}

解析:将字符串中的字符复制到目标字符数组中。索引包含srcBegin,不包含srcEnd。

equals方法

// anObject:与此String进行比较的对象。
public boolean equals(Object anObject) {
  // 引用相同直接返回true
    if (this == anObject) {
        return true;
    }
    // 判断给定的对象是否是String类型的
    if (anObject instanceof String) {
    // 给定的对象是字符串类型的转换为字符串类型
        String anotherString = (String)anObject;
    // 获取当前字符串的长度
        int n = value.length;
    // 判断给定字符串的长度是否等于当前字符串的长度
        if (n == anotherString.value.length) {
      // v1[]代表当前字符串对应的char数组
            char v1[] = value;
      // v2[]代表给定的字符串对应的char数组
            char v2[] = anotherString.value;
      // 遍历原始char数组,并且与给定的字符串对应的数组进行比较
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
          // 任意一个位置上不相等返回false
                    return false;
                i++;
            }
      // 都相等返回true
            return true;
        }
    }
  // 不是String类型,或者长度不一致返回false
    return false;
}

解析:这个方法重写了Object中的equals方法。方法中的将此字符串与指定对象进行比较。接下来附赠一个手写的String字符串equals方法。

手写equals方法

private boolean mineEquals(String srcObject, Object anObject){
  // 比较引用是否相同
    if (srcObject == anObject){
        return true;
    }
  // 引用不相同比较内容
    if (anObject instanceof String){
        String ans = (String) anObject;
        char[] srcChar = srcObject.toCharArray();
        char[] anChar = ans.toCharArray();
        int n = srcChar.length;
        if (n == anChar.length){
            int i = 0;
            while (n-- != 0){
                if (srcChar[i] != anChar[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

// 测试我们自己写的equals方法
    @Test
    public void test_string_mine(){
        String s = new String("aaa");
    // 走的是引用的比较
        System.out.println(s.equals(s));// true 
        boolean b = mineEquals(s, s);
        System.out.println(b);// true
    }

equalsIgnoreCase方法

public boolean equalsIgnoreCase(String anotherString) {
  // 引用相同返回true。引用不相同进行长度、各个位置上的char是否相同
    return (this == anotherString) ? true
            : (anotherString != null)
            && (anotherString.value.length == value.length)
            && regionMatches(true, 0, anotherString, 0, value.length);
}

解析:将此字符串与另一个字符串进行比较,而忽略大小写注意事项。regionMatches方法的源码很有趣的,源码里面有一个while循环,先进行未忽略大小的判断,然后进行忽略大小的判断,在忽略大小的判断中,先进行的是大写的转换进行比较,但是可能会失败【这种字体Georgian alphabet】。所以在大写转换以后的比较失败,进行一次小写的转换比较。

startsWith方法

// 判断是否以指定的前缀开头
public boolean startsWith(String prefix) {
  // 0代表从开头进行寻找
  return startsWith(prefix, 0);
}

endsWith方法

// 判断是否以指定的前缀结尾
public boolean endsWith(String suffix) {
  // 从【value.length - suffix.value.length】开始寻找,这个方法调用的还是startsWith方法
  return startsWith(suffix, value.length - suffix.value.length);
}

startsWith和endsWith最终的实现方法

// prefix: 测试此字符串是否以指定的前缀开头。toffset: 从哪里开始寻找这个字符串。
public boolean startsWith(String prefix, int toffset) {
  // 原始的字符串对应的char[]
    char ta[] = value;
  // 开始寻找的位置
    int to = toffset;
  // 获取指定的字符串对应的char[]
    char pa[] = prefix.value;
    int po = 0;
  // 获取指定的字符串对应的char[]长度
    int pc = prefix.value.length;
    // 开始寻找的位置小于0,或者起始位置大于要查找的长度【value.length - pc】返回false。
    if ((toffset < 0) || (toffset > value.length - pc)) {
        return false;
    }
  // 比较给定的字符串的char[]里的每个元素是否跟原始的字符串对应的char数组的元素相同
    while (--pc >= 0) {
        if (ta[to++] != pa[po++]) {
      // 有一个char不相同返回false
            return false;
        }
    }
  // 相同返回true
    return true;
}

substring方法

// 返回一个字符串,该字符串是该字符串的子字符串。beginIndex开始截取的索引【包含】。
public String substring(int beginIndex) {
  // 校验指定的索引,小于0抛出角标越界
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
  // 子字符串的长度
    int subLen = value.length - beginIndex;
  // 子字符串的长度小于0抛出角标越界
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
  // 开始位置为0,返回当前字符串,不为0,创建一个新的子字符串对象并返回
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

解析:返回一个字符串,该字符串是该字符串的子字符串。子字符串以指定索引处的字符开头【包含】,并且扩展到该字符串的末尾。

substring方法

// 返回一个字符串,该字符串是该字符串的子字符串。beginIndex开始截取的索引【包含】。
public String substring(int beginIndex) {
  // 校验指定的索引,小于0抛出角标越界
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
  // 子字符串的长度
    int subLen = value.length - beginIndex;
  // 子字符串的长度小于0抛出角标越界
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
  // 开始位置为0,返回当前字符串,不为0,创建一个新的子字符串对象并返回
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

解析:返回一个字符串,该字符串是该字符串的子字符串。子字符串从指定的beginIndex开始【包含】,并且扩展到索引endIndex-1处的字符【不包含】。

concat方法

public String concat(String str) {
  // 获取给定的字符串的长度
    int otherLen = str.length();
  // 长度为0,直接返回当前的字符串
    if (otherLen == 0) {
        return this;
    }
  // 获取当前字符串的长度
    int len = value.length;
  // 构建一个新的长度为len + otherLen的字符数组,并且将原始的数据放到这个数组
    char buf[] = Arrays.copyOf(value, len + otherLen);
  // 这个底层调用是System.arraycopy这个方法的处理是使用c语言写的
    str.getChars(buf, len);
    return new String(buf, true);
}

将指定的字符串连接到该字符串的末尾。字符串拼接。

format方法

// 使用指定的格式字符串和参数返回格式化的字符串。
public static String format(String format, Object... args) {
    return new Formatter().format(format, args).toString();
}

// 案例,这里是使用%s替换后面的如"-a-"
@Test
public void test_start(){
    System.out.println(String.format("ha %s hh %s a %s h", "-a-", "-b-", "-c-"));
}

trim方法

public String trim() {
  // 指定字符串的长度
    int len = value.length;
  // 定义一个开始位置的索引0
    int st = 0;
  // 定义一个char[] val,用于避免使用getfiled操作码,这个可以写段代码反编译一下看看
    char[] val = value;
  // 对于字符串的开头进行去除空格,并记录这个索引
    while ((st < len) && (val[st] <= ‘ ‘)) {
        st++;
    }
  // 对于字符串的尾部进行去除空格,也记录这个索引,这个索引就是去除尾部空格后的索引
    while ((st < len) && (val[len - 1] <= ‘ ‘)) {
        len--;
    }
  // 根据上面记录的长度判断是否要截取字符串
    return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}

返回一个字符串,其值就是这个字符串,并去掉任何首部和尾部的空白。

join方法

// 返回一个新的String,该字符串由给定的分隔符和要连接的元素组成。delimiter:分隔每个元素的分隔符。elements:连接在一起的元素。
public static String join(CharSequence delimiter, CharSequence... elements) {
  // delimiter和elements为空抛出空指针异常,null会被拦截,""不会被拦截
    Objects.requireNonNull(delimiter);
    Objects.requireNonNull(elements);
    // 
    StringJoiner joiner = new StringJoiner(delimiter);
  // 遍历给定的要拼接的元素,拼接的元素允许为null
    for (CharSequence cs: elements) {
    // 执行拼接方法
        joiner.add(cs);
    }
    return joiner.toString();
}

// 拼接方法
public StringJoiner add(CharSequence newElement) {
  // prepareBuilder()方法首次调用会创建StringBuilder对象,后面再调用会执行拼接分隔符
    prepareBuilder().append(newElement);
    return this;
}

// 未进行拼接创建StringBuilder对象,已经拼接以后value != null执行拼接分隔符
private StringBuilder prepareBuilder() {
  // 判断拼接的value是否为空
    if (value != null) {
    // 不为空执行拼接分隔符
        value.append(delimiter);
    } else {
    // 最开始使用拼接的时候,调用这个方法创建一个空的StringBuilder对象,只调一次
        value = new StringBuilder().append(prefix);
    }
    return value;
}

// 上面是调用的这个拼接元素方法
@Override
public StringBuilder append(CharSequence s) {
  // 这里啥都没处理,调用的是父类的append方法,设计模式为建造者模式
    super.append(s);
    return this;
}

// 上面的prepareBuilder方法是拼接分隔符,这个方法是将分隔符和给定的元素拼接的方法
@Override
public AbstractStringBuilder append(CharSequence s) {
  // 以下3个判断根据类型和是否为空进行区别拼接
    if (s == null)
        return appendNull();
    if (s instanceof String)
        return this.append((String)s);
    if (s instanceof AbstractStringBuilder)
        return this.append((AbstractStringBuilder)s);
  // 拼接
    return this.append(s, 0, s.length());
}

将给定的字符串以给定的分割符分割并返回分隔后的字符串。

replace方法

// target:要被替换的目标字符串。 replacement:替换的字符串
public String replace(CharSequence target, CharSequence replacement) {
    return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
            this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}

解析:用指定的字符串替换这个字符串中与之匹配的每个子字符串。替换从字符串的开头到结尾,例如,在字符串 "aaa "中用 "b "替换 "aa "将导致 "ba "而不是 “ab”。

replaceAll方法

// regex:这个支持正则表达式,也可以是要被替换的目标字符串。
public String replaceAll(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

问题:replace和replaceAll方法的区别是啥?
replaceAll支持正则表达式。

针对char的replace方法

// oldChar:要被替换的字符,newChar:替换的字符
public String replace(char oldChar, char newChar) {
  // oldChar不等于newChar
    if (oldChar != newChar) {
    // 当前字符串的长度
        int len = value.length;
    // 这个用于下面的while循环里的条件比较,val[i]中的i是从0开始的
        int i = -1;
    // 定义一个char[] val,用于避免使用getfiled操作码,这个可以写段代码反编译一下看看
        char[] val = value; /* avoid getfield opcode */
    // 这个用于记录这个i的值,并且判断是否有要替换的,这个循环有利于性能的提升
        while (++i < len) {
      // val[i]中的i是从0开始的
            if (val[i] == oldChar) {
        // 有要替换的直接跳出循环
                break;
            }
        }
    // 上面的while循环中如果有要替换的i肯定小于len,如果没有下面这个判断就不会执行
        if (i < len) {
      // 能进到这个循环肯定是有要替换的,创建一个长度为len的char数组
            char buf[] = new char[len];
      // 上面的i是记录第一个可以替换的char的索引,下面这个循环是将这个i索引前的不需要被替换的填充到buf[]数组中
            for (int j = 0; j < i; j++) {
        // 填充buf[]数组
                buf[j] = val[j];
            }
      // 从可以替换的索引i开始将剩余的字符一个一个填充到 buf[]中
            while (i < len) {
        // 获取要被替换的字符
                char c = val[i];
        // 判断这个字符是否真的需要替换,c == oldChar成立就替换,否则不替换
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
      // 返回替换后的字符串
            return new String(buf, true);
        }
    }
  // oldChar等于newChar直接返回当前字符串
    return this;
}

案例

@Test
public void test_matches(){
    String a = "adddfdefe";
    System.out.println(a.replace(‘d‘, ‘b‘));// abbbfbefe
}

仿写replace方法参数针对char

仿写

// 和源码给的唯一不同的是参数传递,其他的都和源码一样,自己写一遍可以加深记忆和借鉴编程思
public String replace(String source, char oldChar, char newChar) {
  char[] value = source.toCharArray();
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */
        while (++i < len) {
            if (val[i] == oldChar) {
                break;
            }
        }
        if (i < len) {
            char buf[] = new char[len];
            for (int j = 0; j < i; j++) {
                buf[j] = val[j];
            }
            while (i < len) {
                char c = val[i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            return new String(buf);
        }
    }
    return new String(value);
}

intern方法

public native String intern();

这是一个native方法。调用String#intern方法时,如果池中已经包含一个由equals方法确定的等于此String对象的字符串,则返回来自池的字符串。否则,将此String对象添加到池中,并返回这个String的引用。

以上是关于干货深度讲解APP结构,看完后你学会了?的主要内容,如果未能解决你的问题,请参考以下文章

深度分析:mybatis的底层实现原理,看完你学会了吗?

深度分析:mybatis的底层实现原理,看完你学会了吗?

新鲜出炉!JAVA线程池精华篇深度讲解,看完你还怕面试被问到吗?

Python逆向JavaScript,深度解析Q群成员数据的采集与邮件的来源,阅读完后你就明白了!

通俗讲解深度学习和神经网络!

用自己的云服务器来学会OpenCV炫酷使用深度学习需要热爱博客之星我来了