龙空技术网

Java 面试基础题

互联网杂谈A 49

前言:

当前看官们对“java基础笔试代码题”都比较讲究,同学们都想要分析一些“java基础笔试代码题”的相关资讯。那么小编也在网上搜集了一些关于“java基础笔试代码题””的相关文章,希望各位老铁们能喜欢,小伙伴们一起来了解一下吧!

静态变量和实例变量的区别在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。“==” & equals()

一、对象类型不同

1、equals():是超类Object中的方法。

2、==:是操作符。

二、比较的对象不同

1、equals():用来检测两个对象是否相等,即两个对象的内容是否相等。

2、==:用于比较引用和比较基本数据类型时具有不同的功能,具体如下:

(1)基础数据类型:比较的是他们的值是否相等,比如两个int类型的变量,比较的是变量的值是否一样。

(2)引用数据类型:比较的是引用的地址是否相同,比如说新建了两个User对象,比较的是两个User的地址是否一样。

三、运行速度不同

1、equals():没有==运行速度快。

2、==:运行速度比equals()快,因为==只是比较引用。

浅拷贝 & 深拷贝浅拷贝对基本数据类型进行值传递对引用数据类型进行引用地址的拷贝深拷贝对基本数据类型进行值传递对引用数据类型,创建一个新的对象,并复制其内容

实现深拷贝有两种方式,「序列化对象方式」和「二次调用 clone 方式」

序列化(serialization)方式 先对对象进行序列化,再进行反序列化,得到一个新的深拷贝的对象二次调用 clone 方式 先调用对象的 clone() 方法 对对象的引用类型的属性值,继续调用 clone() 方法进行拷贝

创建对象的4种方法

使用 new 关键字反射机制实现 Cloneable 接口,使用 clone 方法创建对象序列化和反序列化

以上 4 种方式,都可以创建一个 Java 对象,实现机制上有如下区别

方式 1 和方式 2 中,都明确地显式地调用了对象的构造函数。方式 3 中,是对已经的对象,在内存上拷贝了一个影印,并不会调用对象的构造函数。方式 4 中,对对象进行序列化,转化为了一个文件流,再通过反序列化生成一个对象,也不会调用构造函数。

clone和new的效率对比

JVM 对使用 new 方法创建对象的方式进行了优化,默认情况下,new 的效率更高。new 方式创建对象时,会调用类的构造函数。若构造函数中有耗时操作,则会影响 new 方法创建对象的效率。clone 方式创建对象,并不会调用类的构造函数。HashMap , 扩容机制

什么时候扩容?当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值(即当前数组的长度乘以加载因子的值的时候),就要自动扩容了。

扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。

final Node<K,V>[] resize() { 2     Node<K,V>[] oldTab = table; 3     int oldCap = (oldTab == null) ? 0 : oldTab.length; 4     int oldThr = threshold; 5     int newCap, newThr = 0; 6     if (oldCap > 0) { 7         // 超过最大值就不再扩充了,就只好随你碰撞去吧 8         if (oldCap >= MAXIMUM_CAPACITY) { 9             threshold = Integer.MAX_VALUE;10             return oldTab;11         }12         // 没超过最大值,就扩充为原来的2倍13         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&14                  oldCap >= DEFAULT_INITIAL_CAPACITY)15             newThr = oldThr << 1; // double threshold16     }17     else if (oldThr > 0) // initial capacity was placed in threshold18         newCap = oldThr;19     else {               // zero initial threshold signifies using defaults20         newCap = DEFAULT_INITIAL_CAPACITY;21         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);22     }23     // 计算新的resize上限24     if (newThr == 0) {25 26         float ft = (float)newCap * loadFactor;27         newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?28                   (int)ft : Integer.MAX_VALUE);29     }30     threshold = newThr;31     @SuppressWarnings({"rawtypes","unchecked"})32         Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];33     table = newTab;34     if (oldTab != null) {35         // 把每个bucket都移动到新的buckets中36         for (int j = 0; j < oldCap; ++j) {37             Node<K,V> e;38             if ((e = oldTab[j]) != null) {39                 oldTab[j] = null;40                 if (e.next == null)41                     newTab[e.hash & (newCap - 1)] = e;42                 else if (e instanceof TreeNode)43                     ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);44                 else { // 链表优化重hash的代码块45                     Node<K,V> loHead = null, loTail = null;46                     Node<K,V> hiHead = null, hiTail = null;47                     Node<K,V> next;48                     do {49                         next = e.next;50                         // 原索引51                         if ((e.hash & oldCap) == 0) {52                             if (loTail == null)53                                 loHead = e;54                             else55                                 loTail.next = e;56                             loTail = e;57                         }58                         // 原索引+oldCap59                         else {60                             if (hiTail == null)61                                 hiHead = e;62                             else63                                 hiTail.next = e;64                             hiTail = e;65                         }66                     } while ((e = next) != null);67                     // 原索引放到bucket里68                     if (loTail != null) {69                         loTail.next = null;70                         newTab[j] = loHead;71                     }72                     // 原索引+oldCap放到bucket里73                     if (hiTail != null) {74                         hiTail.next = null;75                         newTab[j + oldCap] = hiHead;76                     }77                 }78             }79         }80     }81     return newTab;82 }
多个线程如果共享多个资源,需要怎么保证安全关键字synchronized解析

在Java中,最简单粗暴的同步手段就是synchronized关键字,其同步的三种用法:

① 同步实例方法,锁是当前实例对象

② 同步类方法,锁是当前类对象

③ 同步代码块,锁是括号里面的对象

同步方法:方法级同步没有通过字节码指令来控制,它实现在方法调用和返回操作之中。当方法调用时,调用指令会检查方法ACC_SYNCHRONIZED访问标志是否被设置,若设置了则执行线程需要持有管程(Monitor)才能运行方法,当方法完成(无论是否出现异常)时释放管程。

同步代码块:synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令,每条monitorenter指令都必须执行其对应的monitorexit指令,为了保证方法异常完成时这两条指令依然能正确执行,编译器会自动产生一个异常处理器,其目的就是用来执行monitorexit指令(图中14-18、24-30为异常流程)。

ReentrantLock意思为可重入锁,指的是一个线程能够对一个临界资源重复加锁。为了帮助大家更好地理解ReentrantLock的特性,我们先将ReentrantLock跟常用的Synchronized进行比较,其特性如下:

volatile,CAS/ABA

volatile变量自身具有下列特性:

① 可见性/一致性:对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile 变量最后的写入。

② 原子性:对任意单个 volatile 变量的读/写具有原子性,但类似于 volatile++这种复合操作不具有原子性。

happens-before 规则中有这么一条:

volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

happens-before的这个规则会保证volatile写-读具有如下的内存语义:

volatile写的内存语义:

当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。volatile读的内存语义:

当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

CAS — CompareAndSet

public abstract boolean compareAndSet(T obj, int expect, int update);

以原子的方式更新这个更新器所管理的对象(obj)的成员变量,并且将这个成员变量更新为给定的更新后的值(update)如果当前值等于期望值(expect)时。

CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。

循环时间长开销很大。只能保证一个共享变量的原子操作。ABA问题。

什么是ABA问题?ABA问题怎么解决?

CAS 的使用流程通常如下:1)首先从地址 V 读取值 A;2)根据 A 计算目标值 B;3)通过 CAS 以原子的方式将地址 V 中的值从 A 修改为 B。

但是在第1步中读取的值是A,并且在第3步修改成功了,我们就能说它的值在第1步和第3步之间没有被其他线程改变过了吗?

如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

标签: #java基础笔试代码题