Java基础/集合互问

JAVA基础

讲讲final关键字的作用

数据:
声明数据为常量,可以是运行时常量,也可以是在运行时被初始化后不能被改变的常量。

  • 对于基本类型,final使数值不变;
  • 对于引用类型,final使引用不变,也就不能引用其他对象,但是被引用的对象本身是可以修改的。

方法
声明方法不能被子类重写。
private方法隐式的被指定为final,如果在子类种定义的方法和基类中的一个private方法签名相同,此时子类的方法不是重写基类的方法,而是在子类中定义了一个新的方法。


声明不允许被继承。


讲讲static关键字!

静态变量
又称为类变量,也就是说这个变量是属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量的内存中只存在一份。

静态方法
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象的方法。方法中不能有this和super关键字。

静态语句块在类初始化时运行一次。
非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。静态内部类不能访问外部类的非静态的变量和方法

public class OuterClass {
   class InnerClass {
   }
   static class StaticInnerClass {
   }
   public static void main(String[] args) {
       // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
       OuterClass outerClass = new OuterClass();
       InnerClass innerClass = outerClass.new InnerClass();
       StaticInnerClass staticInnerClass = new StaticInnerClass();
   }
}

静态变量和静态代码块的初始化顺序取决于他们在代码中的顺序


讲讲深克隆和浅克隆的区别

浅拷贝: 拷贝对象和原始对象的引用同一个对象
深拷贝: 拷贝对象和原始对象的引用类型引用不同的对象

在这里插入图片描述


讲讲受检异常和非受检异常。你知道那些搜检异常和非受检异常?

  • 受检异常 : 需要try … catch… 语句捕获并进行处理,并且可以从异常中恢复(io,sql)
  • 非受检异常: 是程序运行时错误,例如除0,调用不存在的方法… 会引发Arithmetic Exception, 此时程序奔溃并且无法恢复。

集合

哪些集合类是线程安全的?

  • Vector: 就比ArrayList多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率 (页面相应速度)是优先考虑的。
  • Stack: 堆栈类,先进后出
  • Hashtable: 就比HashMap多了个线程安全
  • Enumeration: 枚举,相当于迭代器

知道集合类的快速失败机制吗?

现象:
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了增加,删除,修改,则会抛出ConcurrentModificationException。

原理:
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量。集合在被遍历期间如果发生内容的变化,就会改变modCount的值。每当迭代器使用hashNext/next遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常。

场景:
java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)

注意 这里的抛出异常条件是检测到modCount != exceptedmodCount这个条件。如果集合发生变化时修改modCount的值刚好等于exceptedmodCount的值,则异常不会抛出。因此,不能依赖这个异常是否抛出而进行并发操作的编程。这个异常只建议用于检测并发修改的bug。。

解决方法:

在遍历过程中,所有涉及到改变modCount值的地方全部加上synchronized。
使用CopyOnWriteArrayList来替换ArrayList

安全失败
现象:
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有的集合内容,在拷贝的集合上进行遍历

原理:
由于迭代器是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所做的修改并不能被迭代器检测到,所以不会出发ConcurrentModificationException

缺点:
基于拷贝内容的优点是避免了ConcurrentModificationException,但同样的,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。这也是他的缺点,同时,由于是需要拷贝的,所以比较吃内存。

场景:
java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
比如CopyOnWriteArrayList

在这里插入图片描述
摘自:快速失败&&安全失败机制


如果你想确保一个集合不能被修改你会怎么做?

可以使用Collections.unmodifiableCollection(Colletion c)方法来创建一个只读集合,这个改变集合的任何操作都会抛出Java. lang. UnsupportedOperationException 异常。

List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());

说一下HashSet的实现原理?

HashSet 是基于 HashMap实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此HashSet的实现比较简单,相关HashSet的操作,基本上都是调用底层的HashMap的相关方法来完成,HashSet不允许重复的值。

private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;

public HashSet() {
    map = new HashMap<>();
}
public boolean add(E e) {
    // 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
	return map.put(e, PRESENT)==null;
}

BlockingQueue是什么?(实现类有哪些是用于线程池的队列)

Java.util.Concurrent.BlockingQueue是一个检索队列,在进行一个元素的检索或移除一个元素的时候,他会等待队列变为空;当在添加一个元素时,它会等待对立的可用空间。BlockingQueue接口是Java集合的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都是在BlockingQueue的实现类中被处理了。

Java 提供了集中BlockQueue的实现,比如ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue,SynchronizedQueue等。


为什么HashMap中的String,Integer这样的包装类适合作为K?

String,Integer等包装类的特性能够保证Hash值的不可更改和计算准确性,能够有效的减少碰撞的几率。

  1. 都是final类型,即不可变,保证key的不可更改性,不会存在获取hash值不同的情况。
  2. 内部重写了equals(),hashCode() 等方法,遵守了HashMap内部的规范(putVal()方法),不容易出现Hash计算错误的情况

String重写的equals方法

public boolean equals(Object anObject) {
       if (this == anObject) {
           return true;
       }
       if (anObject instanceof String) {
           String anotherString = (String)anObject;
           int n = value.length;
           if (n == anotherString.value.length) {
               char v1[] = value;
               char v2[] = anotherString.value;
               int i = 0;
               while (n-- != 0) {
                   if (v1[i] != v2[i])
                       return false;
                   i++;
               }
               return true;
           }
       }
       return false;
   }

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;
    }

HashMap和ConcurrentHashMap的区别

  1. ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
  2. HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

ConcurrentHashMap底层具体实现知道吗?实现原理是什么?

JDK1.7
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。

在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下:
在这里插入图片描述

一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

  1. 该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色;
  2. Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。

JDK1.8
在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
结构如下:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值