从0开始回顾Java---系列四

简介: String1、String 是 Java 基本数据类型吗?可以被继承吗?为什么?String是Java基本数据类型吗?不是。Java 中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type)。String是一个比较特殊的引用数据类型。String 类可以继承吗?不行。String 类使用 final 修饰,是所谓的不可变类,无法被继承。String 类为什么要被设计成不可继承?1. 字符串是一种非常重要且常用的数据类型

String


1、String 是 Java 基本数据类型吗?可以被继承吗?为什么?

String是Java基本数据类型吗?

不是。Java 中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type)。

String是一个比较特殊的引用数据类型。

String 类可以继承吗?

不行。String 类使用 final 修饰,是所谓的不可变类,无法被继承

String 类为什么要被设计成不可继承?

  1. 字符串是一种非常重要且常用的数据类型,它在 Java 程序中被广泛使用。为了保证字符串类型的安全性和可靠性,Java 的设计者决定不允许用户扩展它。
  2. 同时,由于字符串是一种常量类型,一旦创建就不能更改。所以,即使你能够继承 String 类并扩展它的功能,也没有太多的意义。

2、String,StringBuffer,StringBuilder 的区别是什么?

可变和不可变:

  • String是final修饰符修饰的字符数组,所以是不可变的,如果操作的是少量的数据,则可以使用String;  
  • StringBuilder和StringBuffer是可变的字符串数组;  

是否多线程安全:

  • String中的对象是不可变的,也就可以理解为常量,显然线程安全
  • StringBuilder是非线程安全的因为Stringbuilder继承了父类abstractStringBuilder的append方法,该方法中有一个count+=len的操作不是原子操作,所以在多线程中采用StringBuilder会丢失数据的准确性并且会抛ArrayIndexOutOfBoundsException的异常。
  • StringBuffer是线程安全的,因为他的append方法被synchronized关键字修饰了,所以它能够保证线程同步和数据的准确性 。

性能:

  • 每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象,效率低。
  • 因为StringBuffer是被synchronized修饰的,所以在单线程的情况下StringBuilder的执行效率是要比StringBuffer高的,所以一般在单线程下执行大量的数据使用StringBuilder,多线程的情况下则使用StringBuffer。  

3、String str1 = new String("abc")和String str2 = "abc" 的区别?

两个语句都会去字符串常量池中检查是否已经存在 “abc”,如果有则直接使用,如果没有则会在常量池中创建 “abc” 对象。

堆与常量池中的String

但是不同的是,String str1 = new String("abc") 还会通过 new String() 在堆里创建一个 "abc" 字符串对象实例。所以后者可以理解为被前者包含。

String s = new String("abc")创建了几个对象?

很明显,一个或两个。如果字符串常量池已经有“abc”,则是一个;否则,两个。

当字符创常量池没有 “abc”,此时会创建如下两个对象:

  • 一个是字符串字面量 "abc" 所对应的、字符串常量池中的实例
  • 另一个是通过 new String() 创建并初始化的,内容与"abc"相同的实例,在堆中。

4、String有哪些特性?

  • 不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性;
  • 常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用;
  • final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。

5、在使用 HashMap 的时候,用 String 做 key 有什么好处?


HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快


6、String为什么要设计成不可变的?


  1. 便于实现字符串池
  • 在堆中开辟一块存储空间String pool,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。
  • 如果字符串是可变的,某一个字符串变量改变了其值,那么其指向的变量的值也会改变,String pool将不能够实现!
  1. 使多线程安全
  • 在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全
  1. 避免安全问题
  • 在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。
  1. 加快字符串处理速度
  • 由于String是不可变的,保证了hashcode的唯一性于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。

7、两个字符串相加的底层是如何实现的?

  • 如果拼接的都是字符串常量,则在编译时编译器会将其直接优化为一个完整的字符串,和你直接写一个完整的字符串是一样的。
  • 如果拼接的字符串中包含变量,则在编译时编译器采用StringBuilder对其进行优化,即自动创建StringBuilder实例并调用其append()方法,将这些字符串拼接在一起。

8、intern方法有什么作用?

JDK源码里已经对这个方法进行了说明:

* <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>

意义:

  • 如果当前字符串内容存在于字符串常量池(即equals()方法为true,也就是内容一样),直接返回字符串常量池中的字符串
  • 否则,将此String对象添加到池中,并返回String对象的引用


9、字符串拼接的方式有哪些?


  1. 直接用 + ,底层用 StringBuilder 实现。只适用小数量,如果在循环中使用 + 拼接,相当于不断创建新的 StringBuilder 对象再转换成 String 对象,效率极差。
  2. 使用 String 的 concat 方法,该方法中使用 Arrays.copyOf 创建一个新的字符数组 buf 并将当前字符串 value 数组的值拷贝到 buf 中,buf 长度 = 当前字符串长度 + 拼接字符串长度。之后调用 getChars 方法使用 System.arraycopy 将拼接字符串的值也拷贝到 buf 数组,最后用 buf 作为构造参数 new 一个新的 String 对象返回。效率稍高于直接使用 +
  3. 使用 StringBuilderStringBuffer,两者的 append 方法都继承自 AbstractStringBuilder,该方法首先使用 Arrays.copyOf 确定新的字符数组容量,再调用 getChars 方法使用 System.arraycopy 将新的值追加到数组中。StringBuilder 是 JDK5 引入的,效率高但线程不安全。StringBuffer 使用 synchronized 保证线程安全。


10、String.hashCode()源码了解多少?

String.hashCode()源码:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

从源码中我们可以看出:

  1. String有一个私有变量hash来缓存哈希值,即当该串第一次调用hashCode()方法时,hash默认值为0,继续执行,当字符串长度大于0时计算出一个哈希值赋给hash,之后再调用hashCode()方法时不会重新计算,直接返回hash;
  2. 计算时,使用的是该字符串截成的一个字符数组,用每个字符的ASCII值进行计算,根据注释可以看出哈希计算公式是:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],n是字符数组的长度,s是字符数组;
  3. 算法中还有一个乘数31,为什么使用31呢?
  1. hash函数必须选用质数,这是被科学家论证过的hash函数减少冲突的理论;
  2. 如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为使用偶数相当于位移运算(低位补0);
  3. 31 * i 可以用 (i << 5) -  i 来计算,而移位操作的效率高于乘法,所以这是基于性能角度的考虑;
  4. 31是个不大不小的质数,兼顾了性能和冲突率,太小hash冲突概率大,太大过于分散占用存储空间大,所以选择一个不大不小的质数很有必要。


11、String类的equals()源码了解多少?

String类的equals()源码:

public boolean equals(Object anObject) {
    // 检查两个字符串是否指向同一个对象。如果是,则直接返回 true
    if (this == anObject) {
        return true;
    }
    // 检查给定的对象是否是 String 类型的。如果不是,则返回 false
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = count;
        // 比较两个字符串的长度是否相等。如果不相等,则返回 false
        if (n == anotherString.count) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = offset;
            int j = anotherString.offset;
            // 逐个比较两个字符串的每个字符是否相等。如果有任意一个字符不相等,则返回 false;
            while (n-- != 0) {
                if (v1[i++] != v2[j++])
                    return false;
            }
            // 否则,返回 true
            return true;
        }
    }
    return false;
}

12、String源码中有哪些地方被final修饰?

java.lang.String 类的源码中,有以下几处地方被 final 修饰:

  1. private final char[] value:表示字符串的值的数组。这个字段被 final 修饰,意味着一旦初始化完成就不能再更改。
  2. private final int offset:表示字符串的值数组的偏移量。这个字段被 final 修饰,意味着一旦初始化完成就不能再更改。
  3. private final int count:表示字符串的值数组的长度。这个字段被 final 修饰,意味着一旦初始化完成就不能再更改。
  4. private final int hash:表示字符串的哈希值。这个字段被 final 修饰,意味着一旦初始化完成就不能再更改
  5. public final class String:这个类被 final 修饰,意味着其不可以被继承。
  6. 此外,在 java.lang.String 类中还有若干个方法被 final 修饰,这些方法不能被子类覆盖。例如,final int length() 方法就是一个返回字符串长度的方法,它被 final 修饰,意味着不能被子类覆盖。

java.lang.String 类的源码如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    // 字符串的底层实现是一个 private 修饰的字符数组
    private final char value[];
    // 构造函数,用于创建一个新的字符串对象
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    // 构造函数,用于创建一个新的字符串对象
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }
    // 构造函数,用于创建一个新的字符串对象
    public String(String original) {
        this.value = original.value;
    }
    // 返回字符串对象的长度
    public int length() {
        return value.length;
    }
    // 返回指定位置的字符
    public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }
    // 返回一个新的字符串对象,它是原始字符串的子串
    public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        if (beginIndex > endIndex) {
            throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this :
            new String(value, beginIndex, endIndex - beginIndex);
    }
    // 返回一个新的字符串对象,它是原始字符串的副本
    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        char buf[] = Arrays.copyOf(value, value.length + otherLen);
        str.getChars(buf, value.length);
        return new String(buf, true);
    }
    // 返回一个新的字符串对象,它是原始字符串的大写形式
    public String toUpperCase(Locale locale) {
        if (locale == null) {
            throw new NullPointerException();
        }
        int len = value.length;
        for (int i = 0; i < len; ) {
            int c = Character.codePointAt(value, i);
            int srcCount = Character.charCount(c);
            if (c == Character.toUpperCase(c)) {
                i += srcCount;
                continue;
            }
            char[] tem = Character.toChars(Character.toUpperCase(c));
            char[] buf = new char[len + tem.length - srcCount];
            System.arraycopy(value, 0, buf, 0, i);
            int j = i;
            for (char c2 : tem) {
                buf[j++] = c2;
            }
            System.arraycopy(value, i + src


相关文章
|
9月前
|
缓存 Java 调度
从0开始回顾Java---系列五
Integer 1、Integer a= 127,Integer b = 127;Integer c= 128,Integer d = 128;相等吗? 答案是a和b相等,c和d不相等。 ● 对于基本数据类型==比较的值 ● 对于引用数据类型==比较的是地址 Integer a= 127这种赋值,是用到了Integer自动装箱的机制。自动装箱的时候会去缓存池里取Integer对象,没有取到才会创建新的对象。 如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用缓存池中的Integer对象,超过范围 a1==b1的结果是false。 publi
|
9月前
|
安全 Java 程序员
从0开始回顾Java---系列一
Java概述 1、什么是Java? Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。 Java语言作为静态面向对象编程语言的优秀代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。 2、Java 语言的优点? 1. 平台无关性,摆脱硬件束缚,"一次编写,到处运行",保证这一点的是 Java 的虚拟机机制。 2. 相对安全的内存管理和访问机制,避免大部分内存泄漏和指针越界。 3. 面向对象(封装,继承,多态) 4. 支持多线程。C++ 语言没有内置的多线程
|
9月前
|
存储 安全 Java
从0开始回顾Java---系列三
面向对象 1、谈一谈你对面向对象的理解 ? 面向过程: 一件事该怎么做,注重实现过程,以过程为中心; 面向对象: 实现对象是谁,只关心怎样使用,不关心具体实现(只关心实现对象是谁,有封装、继承、多态三大特性); 总结: ● 面向对象是一种编程思想,早期的面向过程的思想就是一件事该怎么做,而面向对象就是一件事该由谁来做,它怎么做的我不管,我只需要调用就行。而这些是由面向对象的三大特性来实现的,三大特性就是封装、继承、多态。 2、面向对象的三大特性? 封装:封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法。 多态: 所谓多态就是指程序中定义的引用变量所指
|
9月前
|
JSON Java 编译器
从0开始回顾Java---系列六
IO 1、Java中IO流分为几种? 流按照不同的特点,有很多种划分方式: • 按照流的流向分,可以分为输入流和输出流; • 按照操作单元划分,可以划分为字节流和字符流; • 按照流的角色划分为节点流和处理流。 Java Io流共涉及40多个类,看上去杂乱,其实都存在一定的关联, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。 • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。 • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。 IO流用到了什么设计模式? 装饰器模式 2、既
|
9月前
|
存储 安全 算法
从0开始回顾Java---系列九
TreeMap 1、TreeMap 有什么特点? 1. TreeMap是基于红黑树的一种提供顺序访问的Map,增删改查的平均和最差时间复杂度均为 O(logn) ,最大特点是 Key 有序。 2. Key 必须实现 Comparable 接口或提供的 Comparator 比较器,所以 Key 不允许为 null。 3. TreeMap是一个线程不安全,有序的键值对集合,因为TreeMap实现了SotredMap接口。 4. TreeMap实现了Cloneable接口,可被克隆,实现了Serializable接口,可序列化; 2、讲讲 TreeMap 怎么实现有序的? TreeMap
|
9月前
|
存储 安全 Java
从0开始回顾Java---系列七
引言 1、常见集合有哪些? Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出了三个子接口:List、Set、Queue,因此Java集合大致也可分成List、Set、Queue、Map四种接口体系。 ● List代表了有序可重复集合,可直接根据元素的索引来访问; ● Set代表无序不可重复集合,只能根据元素本身来访问; ● Queue是队列集合。 ● Map代表的是存储key-value对的集合,可根据元素的key来访问value。 2、线程安全的集合有哪些?线程不安全的呢? 线程安全的: ● Hashtable:比HashMap多了个线
|
9月前
|
存储 安全 算法
从0开始回顾Java---系列八
HashMap 1、HashMap 有什么特点? HashMap 基于哈希表的 Map 接口实现,是以 key-value 存储形式存在,主要用来存放键值对。 特点: ● HashMap 的实现不是同步的,这意味着它不是线程安全的 ● key 是唯一不重复的,底层的哈希表结构,依赖 hashCode 方法和 equals 方法保证键的唯一 ● key、value 都可以为null,但是 key 位置只能是一个null ● HashMap 中的映射不是有序的,即存取是无序的 ● key 要存储的是自定义对象,需要重写 hashCode 和 equals 方法,防止出现地址不同内
零基础学java---方法(2)
零基础学java---方法(2)
82 0
零基础学java---方法(1)
零基础学java---方法(1)
131 2
|
存储 Java 索引
零基础学java---数组
零基础学java---数组
97 0
零基础学java---数组

热门文章

最新文章