java基础面试题(一)

作者: 我犟不过你 | 来源:发表于2020-09-28 15:48 被阅读0次

1、java中的几种数据类型,各自占用多少字节?

image.png

2、String类能被继承吗?为什么?

可以肯定的是,不能被继承,因为String类有final修饰符,而final修饰的类是不能被继承的。

引申,为什么要被final修饰?

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

上面的源码看到,String类是final修饰的,并且它的属性value[]也是由final修饰的。但是value[]即使是final其数组内容也是可以变得,这里其实是private和final同时保证的。

为了实现字符串池

只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现,因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。

字符串池:String类是我们使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,这就是我们今天要讨论的核心,即字符串池(String Pool)。字符串池由String类私有的维护。

在Java中有两种创建字符串对象的方式
1)采用字面值的方式赋值。
2)采用new关键字新建一个字符串对象。

两种方式的关系

为了线程安全

因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

为了实现String可以创建HashCode不可变性

因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
另外,如果HashMap的key是可变的,结果可想而知。

3、两个对象的hashCode()相同,则equals也一定为true,对吗?

很明显是不对的,我们通过HashMap举个例子。

HashMap的结果是数组加链表(这里指jdk1.7以前,1.8加上红黑树),每次执行put时,会在数组上判断当前key的hashcode是否存在,如果存在,就存储在当前数组节点的链表上,这就是解决哈希碰撞拉链法,当然不能直接放在链表上,还需要通过equals进行比较,存在相等的话则重复,不相等则put成功。

4、String属于基础类型吗

在问题二中明确介绍了常量池的用法,会误认为是常量,请参考问题2。

5、java中操作字符串都有哪些类,他们之间有什么区别?

String
StringBuilder
StringBuffer

1)通过继承关系来看
String继承关系 StringBuilder继承关系 StringBuffer继承关系

通过继承关系发现String和StringBuffer、StringBuilder时候没有任何相似关系的。而StringBuilder和StringBuffer继承关系相同。

2)通过创建的字符串来看
String StringBuffer StringBuilder

从上面的图看出,三种类型都是通过基本类型char[]存放数据,不同点在于String的长度是4,而另外两个是20,这是因为在源码中,StringBuilder和StringBuffer给的默认长度是字符串的length()加上16

public StringBuffer(String str) {
        super(str.length() + 16);
        append(str); 
}

public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
}

这两种类型其实默认大小即是16,并且支持指定大小的构造
StringBuffer相比StirngBuilder多了一个toStringCache()。这个我猜想是应为StringBuffer是线程安全的,为了提升速度吧。

3)通过toString方法比较,这点我也有疑惑

String的toString(),直接返回当前对象。

public String toString() {
        return this;
}

StringBuffer的toString()。

public synchronized String toString() {
        //判断缓存的char[]是否为空
        if (toStringCache == null) {
            //空的话从value中范围copy过来赋值
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        //new一个String对象返回,传了一个boolean的share属性,但是这个属性后面并没有被使用。
        return new String(toStringCache, true);
 }

StringBuilder的toStirngBuilder()。

public String toString() {
        // new String对象,给value、起始位置,和字符串长度
        return new String(value, 0, count);
}

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) {
                this.value = "".value;
                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);
}

经上述比较,String返回自身,StringBuilder和StirngBuffer返回的是自身value的范围copy的字符串,同样都返回new的String对象,区别在于StringBuffer多了一个toStringCache数组,暂时没有具体的解释用在哪里

4)通过内部的append方法

首先我们从源码看到,String的类的是final不可变的,而StringBuffer和StringBuilder是可以进行叠加的。
另外我们发现StringBuffer 的方法都是Synchrosized修饰的,即线程安全的,并且将toStringCache设置为null。

@Override
public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
}

@Override
public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
}

注意:与HashMap相同,都存在扩容的机制,我们应该尽量避免,手动指定合适的大小。

6、java中的IO流分几种

按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分为节点流和处理流。

节点流:可以从或向一个特定的地方(节点)读写数据。
如:FileReader、FileWriter、FileInputStream、FileOutputStream等文件进行处理的节点流。
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
如:BufferedImputStrean、BufferedOutputStream、BufferedReader、BufferedWrite。

Java I0流的40多个类都是从如下4个抽象类基类中派生出来的:
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

java IO流

相关文章

网友评论

    本文标题:java基础面试题(一)

    本文链接:https://www.haomeiwen.com/subject/ncqduktx.html