• Java 面试题 (二) -------- Java 集合相关


    1、Java Bean 的命名规范

    JavaBean 类必须是一个公共类,并将其访问属性设置为 public

    JavaBean 类必须有一个空的构造函数:类中必须有一个不带参数的公用构造器,此构造器也应该通过调用各个特性的设置方法来设置特性的缺省值

    一个 JavaBean 类不应有公共实例变量,类变量都为 private 持有值应该通过一组存取方法 (getXxx 和 setXxx) 来访问:对于每个特性,应该有一个带匹配公用 getter 和 setter 方法的专用实例变量。

    属性为布尔类型,可以使用 isXxx() 方法代替 getXxx() 方法。

    通常属性名是要和 包名、类名、方法名、字段名、常量名作出区别的:

    首先:必须用英文,不要用汉语拼音

    ① 包 (package)

    用于将完成不同功能的类分门别类,放在不同的目录(包)下,包的命名规则:将公司域名反转作为包名。比如www.sohu.com 对于包名:每个字母都需要小写。比如:com.sohu.test,该包下的 Test 类的全名是:com.sohu.Test.Java

    如果定义类的时候没有使用 package,那么Java 就认为我们所定义的类位于默认包里面 (default package)。

    ② 类

    首字母大写,如果一个类由多个单词构成,那么每个单词的首字母都大写,而且中间不使用任何的连接符。尽量使用英文。如 ConnectionFactory

    ③ 方法

    首单词全部小写,如果一个方法由多个单词构成,那么从第二个单词开始首字母大写,不使用连接符。addPerson

    ④ 字段

    与方法相同。如 ageOfPerson

    ⑤ 常量

    所有单词的字母都是大写,如果有多个单词,那么使用下划线链接即可。

    如:

    public static final int AGE_OF_PERSON = 20; //通常加上static
    
    • 1

    2. 什么是 Java 的内存模型?

    在了解什么是 Java 内存模型之前,先了解一下为什么要提出 Java 内存模型。

    之前提到过并发编程有三大问题

    • CPU 缓存,在多核 CPU 的情况下,带来了可见性问题
    • 操作系统对当前执行线程的切换,带来了原子性问题
    • 编译器指令重排优化,带来了有序性问题

    为了解决并发编程的三大问题,提出了 JSR-133,新的 Java 内存模型,JDK 5 开始使用。

    简单总结下

    • Java 内存模型是 JVM 的一种规范

    • 定义了共享内存在多线程程序中读写操作行为的规范

    • 屏蔽了各种硬件和操作系统的访问差异,保证了 Java 程序在各种平台下对内存的访问效果一致

    • 解决并发问题采用的方式:限制处理器优化和使用内存屏障

    • 增强了三个同步原语 (synchronized、volatile、final) 的内存语义

    • 定义了 happens-before 规则

    3、在 Java 中,什么时候用重载,什么时候用重写?

    重载是多态的集中体现,在类中,要以统一的方式处理不同类型数据的时候,可以用重载。

    重写的使用是建立在继承关系上的,子类在继承父类的基础上,增加新的功能,可以用重写。

    简单总结:重载是多样性,重写是增强剂;

    目的是提高程序的多样性和健壮性,以适配不同场景使用时,使用重载进行扩展。

    目的是在不修改原方法及源代码的基础上对方法进行扩展或增强时,使用重写。

    在里氏替换原则中,子类对父类的方法尽量不要重写和重载。(我们可以采用 final 的手段强制来遵循)

    4、举例说明什么情况下会更倾向于使用抽象类而不是接口?

    接口和抽象类都遵循”面向接口而不是实现编码”设计原则,它可以增加代码的灵活性,可以适应不断变化的需求。下面有几个点可以帮助你回答这个问题:在 Java 中,你只能继承一个类,但可以实现多个接口。所以一旦你继承了一个类,你就失去了继承其他类的机会了。

    接口通常被用来表示附属描述或行为如: Runnable 、 Clonable 、 Serializable 等等,因此当你使用抽象类来表示行为时,你的类就不能同时是 Runnable 和 Clonable ( 注:这里的意思是指如果把 Runnable 等实现为抽象类的情况 ) ,因为在 Java 中你不能继承两个类,但当你使用接口时,你的类就可以同时拥有多个不同的行为。

    在一些对时间要求比较高的应用中,倾向于使用抽象类,它会比接口稍快一点。如果希望把一系列行为都规范在类继承层次内,并且可以更好地在同一个地方进行编码,那么抽象类是一个更好的选择。有时,接口和抽象类可以一起使用,接口中定义函数,而在抽象类中定义默认的实现。

    5、实例化对象有哪几种方式

    • new
    • clone()
    • 通过反射机制创建
    //用 Class.forName方法获取类,在调用类的newinstance()方法
    Class<?> cls = Class.forName("com.dao.User");
    User u = (User)cls.newInstance();
    
    • 1
    • 2
    • 3
    • 序列化反序列化
    //将一个对象实例化后,进行序列化,再反序列化,也可以获得一个对象(远程通信的场景下使用)
    ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("D:/data.txt"));
    //序列化对象
    out.writeObject(user1); 
    out.close();
    //反序列化对象
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/data.txt"));
    User user2 = (User) in.readObject();
    System.out.println("反序列化user:" + user2);
    in.close();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    6、Java 容器都有哪些?

    Collection:

    ① set:HashSet、TreeSet
    ② list:ArrayList、LinkedList、Vector

    Map:HashMap、HashTable、TreeMap

    7、Collection 和 Collections 有什么区别?

    Collection 是最基本的集合接口,Collection派生了两个子接口list和set,分别定义了两种不同的存储方式。

    Collections是一个包装类,它包含各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等)。

    此类不能实例化,就像一个工具类,服务于 Collection 框架。

    8、HashMap 和 Hashtable 有什么区别?

    HashMap是线程不安全的,HashTable是线程安全的;

    HashMap中允许键和值为null,HashTable不允许;

    HashMap的默认容器是16,为2倍扩容,HashTable默认是11,为2倍+1扩容;

    9、说一下 HashMap 的实现原理?

    ① 简介

    HashMap基于map接口,元素以键值对方式存储,允许有null值,HashMap是线程不安全的。

    ② 基本属性

    • 初始化大小,默认16,2倍扩容
    • 负载因子0.75
    • 初始化的默认数组
    • size
    • threshold。判断是否需要调整hashmap容量

    ③ HashMap的存储结构

    JDK1.7中采用数组+链表的存储形式。

    HashMap 采取 Entry 数组来存储 key-value,每一个键值对组成了一个 Entry 实体,Entry 类时机上是一个单向的链表结构,它具有 next 指针,指向下一个Entry实体,以此来解决 Hash 冲突的问题。

    HashMap实现一个内部类 Entry,重要的属性有hash、key、value、next。

    在这里插入图片描述
    JDK1.8中采用数据+链表+红黑树的存储形式。当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。

    在这里插入图片描述
    10、说一下 HashSet 的实现原理?

    HashSet 实际上是一个 HashMap 实例,数据存储结构都是数组+链表。

    HashSet 是基于 HashMap 实现的,HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 都是一个统一的对象PRESENT。

    private static final Object PRESENT = new Object();
    
    • 1

    HashSet 中 add 方法调用的是底层 HashMap 中的 put 方法,put 方法要判断插入值是否存在,而 HashSet 的 add 方法,首先判断元素是否存在,如果存在则插入,如果不存在则不插入,这样就保证了 HashSet 中不存在重复值。

    通过对象的 hashCode 和 equals 方法保证对象的唯一性。

    11、ArrayList 和 LinkedList 的区别是什么?

    ArrayList是动态数组的数据结构实现,查找和遍历的效率较高;

    LinkedList 是双向链表的数据结构,增加和删除的效率较高。

    12、哪些集合类是线程安全的

    Vector :就比Arraylist多了个同步化机制 (线程安全) 。
    Stack:栈,也是线程安全的,继承于Vector。
    Hashtable:就比Hashmap多了个线程安全。
    ConcurrentHashMap:是一种高效但是线程安全的集合。

    13、怎么确保一个集合不能被修改?

    我们很容易想到用 final 关键字进行修饰,如果说修饰的这个成员变量是引用类型,则表示这个引用的地址值是不能改变的,但是这个引用所指向的对象里面的内容还是可以改变的。

    我们可以采用 Collections 包下的 unmodifiableMap 方法,通过这个方法返回的map,是不可以修改的。他会报 java.lang.UnsupportedOperationException错。

    14、Java8 开始 ConcurrentHashMap , 为什么舍弃分段锁?

    ConcurrentHashMap 的原理是引用了内部的 Segment ( ReentrantLock ) 分段锁,保证在操作不同段 map 的时候, 可以并发执行, 操作同段 map 的时候,进行锁的竞争和等待。从而达到线程安全, 且效率大于 synchronized。

    但是在 Java 8 之后, JDK 却弃用了这个策略,重新使用了 synchronized+CAS。

    弃用原因:

    通过 JDK 的源码和官方文档看来, 他们认为的弃用分段锁的原因由以下几点:

    • 加入多个分段锁浪费内存空间。
    • 生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
    • 为了提高 GC 的效率
    • 新的同步方案

    15、ConcurrentHashMap (JDK1.8) 为什么要使用 synchronized 而不是如ReentranLock 这样的可重入锁?

    我想从下面几个角度讨论这个问题:

    ① 锁的粒度

    首先锁的粒度并没有变粗,甚至变得更细了。每当扩容一次,ConcurrentHashMap的并发度就扩大一倍。

    ② Hash冲突

    JDK1.7中,ConcurrentHashMap 从过二次 hash 的方式(Segment ->HashEntry)能够快速的找到查找的元素。在1.8中通过链表加红黑树的形式弥补了put、get时的性能差距。

    JDK1.8中,在ConcurrentHashmap进行扩容时,其他线程可以通过检测数组中的节点决定是否对这条链表(红黑树)进行扩容,减小了扩容的粒度,提高了扩容的效率。

    下面是我对面试中的那个问题的一下看法。

    ① 减少内存开销

    假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承AQS来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。

    ② 获得 JVM 的支持

    可重入锁毕竟是 API 这个级别的,后续的性能优化空间很小。synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得 synchronized 能够随着 JDK 版本的升级而不改动代码的前提下获得性能上的提升。

    16、ConcurrentHashMap 和 HashTable 有什么区别

    ConcurrentHashMap 融合了HashMap和 HashTable 的优势,HashMap是不同步的,但是单线程情况下效率高,HashTable是同步的同步情况下保证程序执行的正确性。

    ConcurrentHashMap 锁的方式是细粒度的。ConcurrentHashMap 将 hash 分为16个桶 (默认值) ,诸如 get、put、remove 等常用操作只锁住当前需要用到的桶。

    ConcurrentHashMap 的读取并发,因为读取的大多数时候都没有锁定,所以读取操作几乎是完全的并发操作,只是在求 size 时才需要锁定整个 hash。

  • 相关阅读:
    手撕指针第一页
    计算机网络基础
    Python笔记 · 私有方法、私有属性 & 单下划线、双下划线
    Mac如何安装Homebrew | 国内网6分钟搞定 | M1-M3同样适用
    sql基础语法
    labview入门到出家11(补充)——基于单片机和labview开发的虚拟示波器
    Dart 语言入门
    【读点论文】CMT: Convolutional Neural Networks Meet Vision Transformers
    每日一道算法题
    自定义线程池拒绝策略
  • 原文地址:https://blog.csdn.net/m0_51111980/article/details/128195541