Java基础面试题-可能读了个假书?

2022-05-08 09:36:35 浏览数 (1)

文章目录

  • Java基础
    • 字符型常量和字符串常量
    • 重载和重写的区别
    • 成员变量与局部变量
    • ==和equals()
    • hashcode()和equals()
    • transient关键字
    • BIO、NIO和AIO
  • Java集合
    • ArrayList和LinkedList
    • HashMap和HashTable
    • HashMap底层实现
    • comparable和comparator
    • 小结

Java基础

字符型常量和字符串常量

  1. 字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干字符。
  2. 字符常量相当于一个整型值,可以参加表达式运算,字符串常量代表一个地址值。
  3. 字符常量只占2字节,字符串常量占若干字节。

重载和重写的区别

重载:是同样的一个方法能够根据输入数据的不同,做出不同处理。重载是同一个类中多个同名方法根据不同的传参执行不同的逻辑处理。 重写:是当子类继承自父类的相同方法,输入数据一样,但是要做出的和父类不一样的响应时,就要重写父类方法。重写是子类堆父类的重新改造,外部样子不能改变,但是可以改变内部逻辑。

以最常见的构造器为例,构造器不能被重写(override),但是可以被重载(overload),也就是经常看见一个类中有很多构造器的情况。

成员变量与局部变量

  1. 语法形式上,成员变量是属于类的,而局部变量是方法中定义的变量或是方法的参数。成员变量可以被staticprivatepublic等修饰符修饰,而局部变量是不能被访问修饰符锁修饰的,但是他们都可以被final关键字修饰。
  2. 存储方式上,成员变量如果是使用static关键字修饰的,那么这个成员变量就是属于类的,反之是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引用数据类型,那存放的是指向堆内存对象的引用或是指向常量池中的地址。
  3. 生存时间上,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用的自动创建和消失。
  4. 成员变量如果没有赋初始值的话,会自动以类型的默认值而复制;而局部变量则不会自动赋值。

==和equals()

==:判断两个对象的内存地址是不是相等。

equals():判断两个对象是不是同一个对象。

  • 当类没有覆盖equals()方法时,则通过equals()比较的是该类的两个对象,这种情况等价于==。
  • 当类有覆盖equals()方法时,一般我们会通过比较两个对象的内容来判断是否相等。
代码语言:javascript复制
public class Car {
    public static void main(String[] args) {
        String a="一键三连";//常量池中
        String b="一键三连";
        String c=new String("一键三连");//引用
        String d=new String("一键三连");
        System.out.println(a==b);//true,指向常量池同一个地址
        System.out.println(c==d);//false,两个不同的引用地址
        System.out.println(a.equals(b));//true,判断内容
        System.out.println(c.equals(d));//true
    }
}

注意:

  1. String中的equals()方法是被重写过的,因为object中的equals()方法比较的是对象的内存地址,而String中的equals()方法是比较对象的值。
  2. 当创建String类型的对象时,虚拟机会再常量池中查找是否有已存在的值相同对象,若有则把它赋给当前引用,没有的话则重新创建一个。

hashcode()和equals()

你知道为什么重写equals()方法必须重写hashcode()方法吗?

先介绍下hashcodehashcode()的作用是获取一个int整数即哈希码,也称为散列码。哈希码是确定对象在哈希表中的索引位置,Java中的所有类都包含该函数。

哈希码在HashSet中应用:当把对象加入HashSet时,HashSet会先计算对象的hashcode来判断对象加入的位置,同时也会与该位置其他加入对象的hashcode作比较,若没有相符的hashcode则会假设对象没有重复出现,否则会调用equals()方法来检查哈希码相同的对象内容是否相同,若内容也相同,HashSet就不会让其成功加入;否则的话就会重写散列到其他位置。通过hashcode大大减少了equals的调用次数,提高执行效率。这也回答了开头的问题。

hashcode()equals()相关规定:

  • 若两个对象相等,则hashcode一定是相同的,调用equals()也都是返回true
  • 两个对象有相同的hashcode,但它们不一定相等
  • 因此equals()覆盖的地方,hashcode()方法也必须覆盖。
  • hashcode()默认是对堆上的对象产生独特值,如果没有重写hashcode(),则该class的两个对象无论如何都不会相等。

transient关键字

对于不想进行序列化的变量,使用transient关键字修饰。当对象被反序列化时,被transient关键字修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰方法和类。

BIO、NIO和AIO

  • BIO(Blocking I/O,同步阻塞I/O模式),数据的读取写入必须阻塞在一个线程内等待其完成。让每个连接专注于自己的I/O并且编程模式简单,不用过多考虑系统加载、限流等问题,但是连接数非常大时就无能为力了。
  • NIO(Non-blocking/New I/O,同步非阻塞I/O模式),支持面向缓冲的,基于通道的I/O操作方法。阻塞模式使用就像传统中的支持一样,较为简单,但是性能和可靠性不好,而非阻塞模式正好与之相反,对于高负载、高并发的应用程序,应使用NIO模式开发。
  • AIO(Asynchronous I/O,异步非阻塞模式),异步I/O是基于回调机制实现的,也就是应用操作之后会直接返回,而不是阻塞在那里,当后台处理完成后,操作系统会通知相应的线程进行后续的操作。

Java集合

ArrayList和LinkedList

  1. 线程安全 ArrayListLinkedList都是不同步的,也就是线程不安全,vector是同步的,线程安全。
  2. 底层数据结构 ArrayList底层使用的是Object数组,LinkedList底层使用的是双向链表。
  3. 插入和删除元素 ArrayList采用数组存储,所以插入和删除受元素位置影响;LinkedList采用链表存储,所以插入和删除元素时受到元素位置影响。
  4. 快速随机访问 快速随机访问是通过元素的序号快速获取元素对象,通过底层数据就够就知道了,ArrayList支持,LinkedList不支持。
  5. 内存空间占用 ArrayList的空间主要浪费在了list列表的结尾会预留一定的容量空间,而LinkedList主要花费在每一个元素都要比ArrayList更多的空间,因为存前驱后继节点及数据等。

HashMap和HashTable

HashMap的长度为什么是2的幂次方? 为了能让HashMap减少碰撞高效存储,要尽量把数据分配均匀。首先哈希值的取值范围很大,有将近40亿的空间,内存时放不下的,换言之,哈希值并不能直接使用,需要对数组的长度进行取模,得到的余数就是对应的数组下标。 而设计成2的幂次就是因为如果取余操作中除数是2的幂次的话,就等价于与其除数减一的与操作,也就是说

hash%length=(length-1)&hash

,而采用二进制位运算可以大大提高效率,这也就是为什么HashMap的长度是2的幂次方。

HashMap底层实现

JDK1.8之前: JDK1.8之前HashMap底层是数组和链表结合在一起使用也就是链表散列,HashMap通过key的hashcode经过扰动函数处理后得到hash值,然后通过

(n-1)&hash

判断当前元素存放的位置,如果当前位置存在元素的话,就判断该元素与要存入的元素的hash值以及是否相同,若相同则直接覆盖,否则通过拉链法来解决冲突。所谓扰动函数指的就是HashMap的hash方法。使用hash方法之后可以减少碰撞。

JDK1.8之后: 优化了哈希冲突的解决方法,当链表长度大于阈值(默认为8)时,会将链表转换为红黑树,以减少搜索时间。

comparable和comparator


  • comparable接口出自java.lang包,它有一个compateTo(Object obj)方法用来排序。
  • comparator接口出自java.util包,他有一个compare(Object obj1,Object obj2)方法用来排序。
代码语言:javascript复制
    public static void main(String[] args) {
        ArrayList<Integer> l = new ArrayList();
        l.add(2);
        l.add(1);
        l.add(3);
        System.out.println("===原始===");
        System.out.println(l);
        System.out.println("===自然排序===");
        Collections.sort(l);
        System.out.println(l);
        System.out.println("===定制排序===");
        Collections.sort(l, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        System.out.println(l);
    }
/*运行结果如下:
===原始===
[2, 1, 3]
===自然排序===
[1, 2, 3]
===定制排序===
[3, 2, 1]
*/
代码语言:javascript复制
public class Car implements Comparable<Car>{
    private String name;
    private double price;
    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }
    public String getName() {
        return name;
    }
    public double getPrice() {
        return price;
    }
    @Override
    public String toString() {
        return "Car{"  
                "name='"   name   '''  
                ", price="   price  
                '}';
    }
    @Override
    public int compareTo(Car o) {
        if(this.price==o.getPrice()) //价格相同 名字升序
            return this.name.compareTo(o.getName());
        else if(this.price>o.getPrice())return -1;
        else return 1; //价格降序
    }
    public static void main(String[] args) {
        ArrayList<Car> l =new ArrayList();
        l.add(new Car("C",10));
        l.add(new Car("A",10));
        l.add(new Car("C",15));
        Collections.sort(l);
        System.out.println(l);
    }
}
/*运行结果如下:
[Car{name='C', price=15.0}, Car{name='A', price=10.0}, Car{name='C', price=10.0}]
*/

小结

Collection

  1. List ①ArrayList:Object数组 ②Vector:Object数组 ③LinkedList:双向链表
  2. Set ①HashSet:无序唯一,底层采用HashMap保存元素 ②TreeSet:有序唯一,红黑树(即自平衡的排序二叉树)

Map

  1. HashMap:jdk1.8前时数组 链表,jdk1.8后大于阈值(默认8)则转链表为红黑树
  2. LinkedHashMap:继承自HashMap,增加了双向链表
  3. HashTable:数组 链表
  4. TreeMap:红黑树

0 人点赞