1、java 中操作字符串都有哪些类?它们之间有什么区别?
String、StringBuffer、StringBuilder
String对象是final修饰,不可集成,不可修改,任何对String的修改,都会导致产生一个新String对象。
String: 默认使用final修饰,即声明后,则不可改变String类的方法都是返回new String。即对String对象的任何改变都不影响到原对象,对字符串的修改操作都会生成新的对象。
StringBuffer : 对字符串的操作的方法都加了synchronized,保证线程安全。
StringBuilder : 不保证线程安全,在方法体内需要进行字符串的修改操作,可以new StringBuilder对象,调用StringBuilder对象的append、replace、delete等方法修改字符串。
2、String str=“i” 与 String str=new String(“i”) 一样吗?
-
字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能
-
JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
- 为字符串开辟一个字符串常量池,类似于缓存区
- 创建字符串常量时,首先搜索字符串常量池,是否存在该字符串
- 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
实现的基础
实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享
运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以在常量池中的这些字符串不会被垃圾收集器回收
String s1 = "张三";
String s2 = "张三";
String s3 = new String("张三");
String s4 = new String("张三");
System.out.println(s1 == s2);
System.out.println(s3 == s4);
String s1 = “张三” 的方式,Java 虚拟机会将其分配到常量池中,而常量池中没有重复的元素,比如当执行“张三”时,java虚拟机会先在常量池中检索是否已经有“张三”,如果有那么就将“张三”的地址赋给变量,如果没有就创建一个,然后在赋给变量。
而 String s3 = new String(“张三”) 则会被分到堆内存中,即使内容一样还是会创建新的对象。
// 代码:堆栈方法区存储字符串
String str1 = “abc”;
String str2 = “abc”;
String str3 = “abc”;
String str4 = new String(“abc”);
String str5 = new String(“abc”);
3、描述函数参数传递中,传引用和传值的区别,什么情况下发生传值?什么情况下发生传引用?
普通类型一般存储在栈内存,所有都为值传递,而对象一般存储在堆存储,在内存只是存储了堆内存地址的引用,所有对象一般都为引用传递
传值,函数内部修改值,不会影响函数外部的变量,传引用,会影响引用指向的堆内存存储的对象。
1、简单数据类型都是传值。
2、对象类型(引用类型)默认是传引用地址。
int aaa(int b) {
}
aaa(5);
int bbb(Student s1) {
s1.name="new name";
s1.age = 16;
}
Student sss = new Student();
bbb(sss);
4、String s = new String(“xyz”)创建了几个对象?String s=“a”+“b”+“c”+"d"创建了多少个对象?是否可以继承String类?
问题1:两个,
一个是字符串字面量"xyz"所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,另一个是通过new String(“xyz”)在堆存储创建并初始化的对象、这个对象指向了常量池中的“xyz”实例
问题2:一个
因为经过编译器优化,实际上
String s5 = "a" + "b" + "c" + "d";
经过编译后,会变成
String s5 = "abcd";
那么,会产生一个驻留常量池的字符串常量对象,及一个指向的这个对象的引用。
问题3:不可以,因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
5、String和StringBuffer、StringBuilder的区别是什么?
从可变性上来区分
-
String类中使用字符数组保存字符串,
private final char value[]
,所以string对象是不可变的。 -
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,
char[]value
,但是没有使用final关键字修饰,所以这两种对象都是可变的。
从线程安全性来区分
-
String中的对象是不可变的,也就可以理解为常量,线程安全。
-
AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
-
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
-
StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
从性能上来区分
- 每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。
- StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。
- 相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
-
如果要操作少量的数据用String
-
单线程操作字符串缓冲区下操作大量数据 StringBuilder
-
多线程操作字符串缓冲区下操作大量数据 StringBuffer
6、自动装箱与拆箱都是什么含义
装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;
自动装箱表示对于byte(字节)、short(短整形)、int、long(长整形)、float(单浮点型)、double(双浮点型)、boolean(布尔类型)、char(字符类型),可以自动将其转换成对应的Byte、Short、Integer、Long、Float、Double、Boolean、Char对象。
自动拆箱则是自动装箱的反过程。
Integer a = 10; //自动装箱
Integer a = new Integer(10);//手动装箱
Integer i = null; // 包装类型,可以为null
if(i == 10){
System.out.println("aaaa");
}
7、讲讲类的实例化顺序,有一个类,拥有静态数据,构造函数,成员变量,其父类也有静态数据,构造函数,成员变量,当 new 此变量的时候, 他们的执行顺序是什么。
此题考察的是类加载器实例化时进行的操作步骤(加载–>连接->初始化)。其执行顺序如下:
先父后子,先静态后非静态
8、try{}里有一个return语句,那么紧跟在这个try后的finally{}里的code会不会被执行,什么时候被执行,在return前还是后?
-
try中的return语句调用的函数先于finally中调用的函数执行,也就是说return语句先执行,finally语句后执行。
-
return并不是让函数马上返回,而是return语句执行后,将把返回结果放置进函数栈中,此时函数并不是马上返回,它要执行finally语句后才真正开始返回。
-
注意,return语句执行完毕后,如果没有finally语句,则会马上返回。
9、描述一下Java 面向对象编程三大特性:封装、继承、多态
封装
封装把一个对象的属性私有化,(属性的私有化可以确保,属性的安全性)同时提供一些可以被外界访问的属性的方法(getter,setter),如果不想被外界方法,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
继承:java中只能支持单基础,但是可以实现多个接口
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类(不太懂)。通过使用继承我们能够非常方便地复用以前的代码。
关于继承如下3点请记住:
- 子类拥有父类非private的属性和方法。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。(方法重写,以后介绍)。
多态
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(多个类各自实现接口方法)。
10、GC是什么?为什么要有GC?
垃圾收集GC(Garbage Collection)是Java语言的核心技术之一,GC可以自动回收内存垃圾,节约系统资源,避免内存溢出。
11、描述一下垃圾回收的优点和原理。有哪些回收机制?有哪些垃圾回收器?
垃圾回收机制,自动进行GC,将开发人员从容易犯错的内存资源管理中解放出来。
标记垃圾的算法:Java中标记垃圾的算法主要有两种, 引用计数法和可达性分析算法。
原理:当某一个对象,没有任何引用指向它的时候,那么它就满足垃圾回收的条件,在适当的时候,JVM虚拟机进行GC将其回收,释放空间,以供后续再利用。
两种常见的回收机制:
定时回收
每隔30分钟进行一次回收,这种机制的弊端是如果垃圾产生的比较快,有可能30分钟之内垃圾已经把内存占用光了,导致性能变慢
当垃圾占到某个百分比的时候,进行回收
比如,当垃圾占到70%的时候,进行回收。 这种机制的弊端是,如果垃圾产生的频率很快,那么JVM就必须高频率的进行垃圾回收。 而在垃圾回收的过程中, JVM会停顿下来,只做垃圾回收,而影响业务功能的正常运行。
一般说来 JVM会采用两种机制结合的方式进行垃圾回收。
垃圾收集器一般分为以下几种
- Serial收集器(复制算法)
新生代单线程收集器,标记和清理都是单线程,优点是简单高效。是client级别默认的GC方式,可以通过-XX:+UseSerialGC
来强制指定。 - Serial Old收集器(标记-整理算法)
老年代单线程收集器,Serial收集器的老年代版本。 - ParNew收集器(停止-复制算法)
新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。 - Parallel Scavenge收集器(停止-复制算法)
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。是server级别默认采用的GC方式,可用-XX:+UseParallelGC
来强制指定,用-XX:ParallelGCThreads=4
来指定线程数。 - Parallel Old收集器(停止-复制算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。 - CMS(Concurrent Mark Sweep)收集器(标记-清理算法)
高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择。
12、垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
当对象永久地失去引用后,系统标记此对象为垃圾,会在合适的时候回收它所占的内存。
另一种更详细一点GC时间的说法就是:
- 当应用程序分配新的对象,GC的代的预算大小已经达到阈值,比如GC的第0代已满
- 代码主动显式调用System.gc() 将通知虚拟机可以回收垃圾。
- 其他特殊情况,比如,windows报告内存不足、CLR卸载AppDomain、CLR关闭,甚至某些极端情况下系统参数设置改变也可能导致GC回收。