一、5 个 Android 开发不得不透彻理解的字符串问题

1、String 是 java 中的根本数据类型吗?是可变的吗?是线程安全的吗?

String 不是根本数据类型,java 中的根本数据类型是:byte、short、int、long、char、float、double、boolean。

String 是不可变类,一旦创立了 String 目标,咱们就无法改动它的值,因此,它是线程安全的,可以安全地用于多线程环境中。

2、为什么要规划成不可变的呢?

String 规划为不可变的原因有如下 3 点:

  • 1、安全:由于 String 广泛用于 java 类中的参数,所以安全是非常重要的考虑点,包括线程安全,打开文件,存储数据密码等等。
  • 2、效率:String 的不变性保证哈希码始终如一,所以在用于 HashMap 等数据结构的时分就不需求从头计算哈希码,提高效率。
  • 3、空间:由于不同的字符串变量可以引证池中的相同字符串,所以可以在java 运行时节省大量的 java 堆空间,假如字符串是可变的话,任何一个变量的值改动,就会反射到其他变量,那字符串池也就没有任何意义了。

3、假如 String 是不可变的,那咱们平常赋值的是改了什么呢?

平常运用双引号办法赋值的时分其实是回来的字符串引证,并不是改动了这个字符串目标。

4、String 有哪些创立办法?它们在 JVM 的存储办法是相同吗?

String 常见的创立办法有两种:

第一种:String s1 = “Java

s1 会先去字符串常量池中找字符串 “Java”,假如有相同的字符则直接回来字符串引证,假如没有此字符串则会先在常量池中创立此字符串,然后再返字符串引证

第二种:String s2 = new String(“Java”)

s2 是直接在堆上创立一个变量目标,但不存储到字符串池,调用 intern 办法才会把此字符串保存到常量池中

5、简单讲讲 String,StringBuffer,StringBuilder 的区别?

String 是不可变类,每逢咱们对 String 进行操作的时分,总是会创立新的字符串。操作 String 很耗资源,所以 Java 供给了两个东西类 StringBuffer 和 StringBuilder 来操作 String。

StringBuffer 和 StringBuilder 是可变类,StringBuffer 是线程安全的,StringBuilder 不是线程安全的。所以在多线程对同一个字符串操作的时分,咱们应该挑选用 StringBuffer,由于不需求处理多线程的状况,StringBuilder 的效率比 StringBuffer 要高。

二、怎么中止一个线程?

停止线程有 3 种办法:

1、运用 stop 办法强行停止线程

可是这种办法是不安全的,主要有两点原因:

  • 1)、thread.stop() 调用之后,创立子线程的线程就会抛出 ThreadDeathError 的过错,而且会开释子线程持有的一切锁
  • 2)、一个线程被 stop 中止并开释锁,其写入的内存数据写到一半没有被铲除,但此时另一个线程得到锁发现此内存区块的数据是反常的

2、运用 interrupt() 办法中止线程

这种办法的缺陷在于线程不一定会停止。

Thread::Interrupted() 比较 Thread::IsInterrupted() 办法在native 底层只是多调用了一行 SetInterruptedLocked(false) 来清空当时的中止状态,其内部运用了 wait_mutex_ 互斥加锁来保证线程安全。

3、运用 volatile boolean 变量退出标志

这种办法可以使线程正常退出,也便是当 run 办法完成后线程停止。

由于 interrupt 是体系办法,而且运用了 JNI 调用、内部完成采用了加锁,且它的触发办法是采取了抛反常的办法,所以建议需求支持体系办法时采用中止,其它状况用 volatile boolean 标志位即可。

4、那怎么停止线程池呢?

停止线程池有两种办法:

ExecutorService 线程池供给了 shutdown 和 shutdownNow 这样的生命周期办法来关闭线程池本身以及它所具有的线程。

  • 1、shutdown 关闭线程池:线程池不会立即退出,不再承受新的使命,但可以继续履行池子中现已添加到等候行列的使命。
  • 2、shutdownNow 关闭线程池并中止使命:线程池不会立即退出,不再承受新的使命,也不再处理等候行列中的使命,并企图运用 Thread.interrupt() 去打断正在履行的使命。可是咱们都知道,假如线程中没有 sleep、wait、Condition、定时锁等应用,interrupt() 是无法中止当时线程的,所以,ShutdownNow() 并不代表线程池就一定能立即退出,它或许必须要等候一切正在履行的使命都履行完成了才能退出。

三、类加载机制

1、类加载的主要流程是怎样的?

虚拟机把描绘类的数据从 Class 文件加载到内存,并对数据进行加载、链接(验证、预备、解析)和初始化,终究构成可以被虚拟机直接运用的 Java 目标,这便是虚拟机的类加载机制,详细的流程如下:

1)、加载阶段

便是经过一个类的全限定名来获取其定义的二进制字节省,将这个字节省通化为办法区的运行时数据结构,然后在 Java 堆中生成一个代表这个类的 Class 目标,作为对办法区这些数据的拜访进口

加载阶段是开发人员可控性最强的阶段,由于开发人员可以自定义类加载器,关于数组而言,状况有所不同,数组类本身不经过类加载器创立,它是由 Java 虚拟机直接创立的。

2)、验证

它是链接阶段的第一步,这一阶段的意图是为了保证 Class 文件的字节省中所包括的信息契合当时虚拟机的要求,而且不会危害虚拟机本身的安全。它包括:

  • 1、文件格局校验:验证字节省是否契合 Class 文件格局的标准,例如是否以 0xCAFEBABE 开头、主次版本号是否在当时虚拟机的处理范围内、常量池中的常量是否有不被支持的类型。
  • 2、元数据校验:对字节码描绘的信息进行语义剖析,以保证其描绘的信息契合 Java 语言标准的要求,例如这个类除了 Object 外是否有父类。
  • 3、字节码校验:经过数据流和控制流剖析,确认程序语义是合法的、契合逻辑的。

3)、预备阶段

它是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所运用的内存都将在办法区中进行分配。

需求注意的是,这时分进行内存分配的只是包括类变量,不包括实例变量,实例变量将会在目标实例化时随着目标一同分配在 Java 堆上

其次,这里所说的变量初始值是该数据类型的零值,例如 0、null、false 等,而不是在 Java 代码中被显现赋予的值。

4)、解析阶段

它是虚拟机将常量池内的符号引证替换为直接引证的进程。

符号引证以一组符号来描绘所引证的目标,而直接引证则是直接指向目标的内存地址指针

5)、初始化阶段

它是履行类结构器办法的进程。

类结构器办法是由编译器主动搜集类中的一切类变量的赋值动作和静态句子块中的句子合并产生的,而编译器搜集的次序是由句子在源文件中出现的次序所决议的。

虚拟机遇保证一个类的结构器办法在多线程环境中被正确的加锁同步,假如多个线程同时去初始化一个类,那么只会有一个线程去履行这个类的类结构器办法,其他线程都需求堵塞等候,而这也是静态内部类能完成单例的主要原因之一。

2、类加载的进程能举个详细的实例阐明一下吗?

以 JsonChao jsonChao = new Person()为例进行阐明:

  • 1)、由于 new 用到了 JsonChao.class,所以会先找到 JsonChao.class 文件,并加载到内存中;
  • 2)、履行该类中的 static 代码块,假如有的话,给 JsonChao.class 类进行初始化;
  • 3)、在堆内存中开辟空间,以分配内存地址;
  • 4)、在堆内存中树立目标的特有特点,并进行默许初始化;
  • 5)、对特点进行显现初始化;
  • 6)、对目标进行结构代码块初始化;
  • 7)、对目标进行与之对应的结构函数初始化;
  • 8)、将内存地址赋给栈内存中的 jsonChao 变量。

3、说说你对 Java 和 Android 类加载器的了解?

Java 类加载器包括 4 种:

  • BootstrapClassLoader:启动类加载器,运用 C++ 完成。
  • ExtClassLoader:扩展类加载器,运用 Java 完成。
  • AppClassLoader:应用程序类加载器,加载当时应用类途径的一切类。
  • UserDefinedClassLoader:用户自定义的类加载器。

Android 类加载器包括 3 种:

  • BootClassLoader:给体系预加载运用的。
  • PathClassLoader:给体系、应用程序加载 class 文件用的。
  • DexClassLoader:加载 apk、dex、zip 文件用的。

4、PathDexClassLoader 和 DexClassLoader 有哪些区别?

在 8.0(API 26)之前,它们二者的唯一区别是第二个参数 optimizedDirectory,它是生成的 odex(优化的 dex)寄存的途径

PathClassloader 直接为空,而 DexClassLoader 是运用用户传进来的途径,所以 DexClassLoader 可以加载未安装的 apk/jar/dex,而 PathDexClassLoader 只能加载体系中现已安装过的apk

而在 8.0(API 26)及之后,二者就彻底一样了。

DexClassLoader 的参数意义如下:

  • dexPath:dex 文件以及包括 dex 的 apk 文件或 jar 文件的途径,多个途径用文件分隔符分隔,默许文件分隔符为:。
  • optimizedDirectory:Android 体系寄存 ODEX 文件的途径。PathClassLoader 中默许运用 “/data/dalvik-cache”,而 DexClassLoader 则需求咱们指定 ODEX 优化文件的寄存途径。
  • librarySearchPath:所运用到的 C/C++ 库的寄存途径。
  • parent:这个参数的主要作用是为了保存 java 中 ClassLoader 的托付机制。

5、那 BootClassLoader 和 PathClassLoader 的异同有哪些?

BootClassLoader 是 PathClassLoader 的 parent,这里要注意 parent 与父类的区别

但它们的创立机遇是一样的,在 Zygote 进程启动调用 zygoteInit 的时分就创立了 BootClassLoader 和 PathClassLoader

6、Class 加载的源码有看过吗?

Class 的加载办法有两种:Class.forName 和 ClassLoader.loadClass

Class 文件终究被加载到内存傍边,生成一个 Class 目标都是经过 class_linker.cp 傍边的 DefineClass 完成的,而不同的是 Class.forName 之后还会对生成的 Class 目标进行初始化操作

好了,今天的分享就到这里。

假如还想检查更多的高频问答分享,请扫描下方二维码检查:

三个值得深入思考的 Android 问答分享(第 1 期)

参阅链接:

1、深入理解Java虚拟机(第3版)

2、Java编程思想 (第4版)

3、深入理解Android:Java虚拟机ART