String面试问题

参考

String str1 = new String("hello");
String str2 ="hello";

s3和s4 都是在堆中创建新的对象, string 底层都是通过char[] 来实现的,引入了字符串常量池进行了优化可以节省内存空间,创建str1时 会在堆中创建对象,同时在串池中创建了 hello

验证:
String str1 = new String(“hello”);
String str3= s1.intern();
String str2 =“hello”;
System.out.println(intern == s2);
在这里插入图片描述

为何String要被设计为不可变?
String这个类本身就是被设计为了不可变类(Immutable),但是为何当初设计jdk的作者会将string设计为了不可变类呢?
以下是我个人的理解:
1.将一个字符变量设置为不可变类的话,在多线程访问的场景下,多个线程同时访问一个字符串变量,当其中一个线程修改了字符串变量之后,只会读取到新的引用,而其他线程读取到的值并不会受到影响。相当于没有加锁也不会具有线程安全问题类。

2.看看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;
    }

这段代码进过分析之后就会发现,只要string内部的hash有经过计算,那么第二次则不会再做hashcode的计算,而是直接返回已有的hashcode。因此说string这种不变类特别适合做hashmap的key。

hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。

hashCode 的常规协定是:
在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
以下情况不 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

那么hashcode 有什么用呢

1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
例如内存中有这样的位置
0 1 2 3 4 5 6 7
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
但如果用hashcode那就会使效率提高很多。
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。

2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,

回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。

也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
那么。重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊

重写equals 和不重写结果是不一样的

package demo3;
public class HashTest  {
    private int i;

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }


    public boolean equals(Object object) {
        if (object == null) {
            return false;
        }
        if (object == this) {
            return true;
        }
        if (!(object instanceof HashTest)) {
            return false;
        }
        HashTest other = (HashTest) object;
        if (other.getI() == this.getI()) {
            return true;
        }
        return false;
    }


    public int hashCode() {
        return i % 10;
    }

    public final static void main(String[] args) {
        HashTest a = new HashTest();
        HashTest b = new HashTest();
        a.setI(1);
        b.setI(1);

        System.out.println(a.hashCode() == b.hashCode());
        System.out.println(a.equals(b));

    }
}

由于String里定义类final来修饰存储的字符数组,但是是否有什么方法来修改这一变量呢?

其实是可以的,final修饰符主要是在编译期间保证来变量的不可修复,但是在运行期间如果需要对对象实例的数据进行修改,可以通过反射的方式来操作。下边是一段代码案例:

 public static void test3() throws NoSuchFieldException, IllegalAccessException {
        String str = "test3";
        String str2 = "test3";
        Field field = String.class.getDeclaredField("value");
        field.setAccessible(true);
        char[] chars = (char[]) field.get(str);
        chars[1]='7';
        System.out.println(str+" "+str2);
    }

这段代码就通过反射的机制,直接将常量池里的字符串值直接做了修改,导致所有引用这个字符串常量的对象都受到了影响。

常量和变量

final String a = "hello";   //字面常量
final String b = "world";  //字面常量
String c = a+b;    //此处会在编译时直接替换成String c = "helloworld"
final String a = "hello"; //字面常量
String b = "world";    //变量
String c = a+b;    //此处会new StringBulider().append()
package string;

public class StringDemo {
    public static void main(String[] args) {
        String a="a";

        final String b = "b";

        String ab1 = "ab";
        String ab2 = a + b;
        String ab3 = a + "b";
        String ab4 = "a" + b;
        System.out.println(ab1 == ab2); //false
        System.out.println(ab1 == ab3); //false
        System.out.println(ab2 == ab3); //false
        System.out.println(ab1 == ab4); //true
        System.out.println(ab2 == ab4); //false
        System.out.println(ab3 == ab4); //false

		System.out.println(System.identityHashCode(ab1));
        System.out.println(System.identityHashCode(ab4));
    }

}

这里比较不解的就是 ** System.out.println(ab1 == ab4); //true** 这句了
因为: String ab1 = “ab”; 创建了对象并存在了常量池中,字符串"a " 也在常量池中 , final String b = “b”; 是因为final修饰的变量不能再指向其他对象,存储在常量池 中 ,所以 String ab4 = “a” + b; 时不会创建对象了,所以ab1 和ab4的地址就一样了 。

public class Demo1_23 {

    //  ["ab", "a", "b"]
    public static void main(String[] args) {

        String x = "ab";

        // "a"常量在常量串池中
        String s = new String("a") + new String("b");

        // 堆  new String("a")   new String("b") new String("ab")
        String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回

        System.out.println( s2 == x);
        System.out.println( s == x );
    }

}
public class Demo1_21 {

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b"; // ab
        String s4 = s1 + s2;   // new String("ab")
        String s5 = "ab";
        String s6 = s4.intern();

// 问
        System.out.println(s3 == s4); // false
        System.out.println(s3 == s5); // true
        System.out.println(s3 == s6); // true

        String x2 = new String("c") + new String("d"); // new String("cd")
        String x1 = "cd";
       //常量池中有cd了 没有放入成功, x2 返回的是常量池中的cd x2是堆中的false
        x2.intern();
        
        System.out.println(x1 == x2); //false
    }
}

StringBuffer

String str = "zs";
        for (int i = 0; i < 1000; i++) {
            str = str + i;
        }
        

由于String 是不可变的, 上述代码就会一直在堆中创建新的对象,是非常耗性能的,因此用到了StringBuffer的可变 对char[] 扩容

 StringBuffer sb = new StringBuffer("zs");
        for (int i = 0; i<1000;i++){
            sb.append(i);
        }

对象始终只有一个。
sb.append(i); 实际是对数组的一个扩容,然后又返回当前对象

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

少量的数据: 适用 String
单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

包装类

装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;

 //自动装箱使用到了常量池技术 只在byte范围内
        Integer i5 = 1; //
        Integer i6 = 1;
        System.out.println(i5 == i6); //true


        Integer i1 = 128; //(超过了byte最大范围 )
        Integer i2 = 128;
        System.out.println(i1 == i2); //false

        Integer i3 = new Integer(12);
        Integer i4 = new Integer(12);
        System.out.println(i3 == i4); //false


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
中常被问及,可以采用以下两种方式解决跨域问题: 1.使用注解@CrossOrigin 在Controller中的方法上使用@CrossOrigin注解即可,如下所示: ```java @CrossOrigin(origins = {"http://localhost:8080"}, maxAge = 3600) @RequestMapping(value = "/test", method = RequestMethod.GET) public String doTest() { return "success"; } ``` @CrossOrigin注解有两个参数,第一个参数为请求来源,可以是字符串数组,也可以是"*",表示接受所有来源的请求。第二个参数为缓存时间,单位为秒,指定了浏览器对该响应进行缓存的时间。 2.配置CorsFilter 在Application.java中添加CorsFilter,如下所示: ```java @Configuration public class CorsConfig { private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); // 允许任何域名使用 corsConfiguration.addAllowedHeader("*"); // 允许任何头 corsConfiguration.addAllowedMethod("*"); // 允许任何方法(post、get等) return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); // 对所有路径进行跨域处理 return new CorsFilter(source); } } ``` 这里使用的是CorsFilter,将跨域处理交由Spring Boot的Filter,实现更为细致的跨域控制。配置中我们允许任何来源,任何头,任何方法的请求进行跨域处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值