Java基础

2017-10-24  本文已影响10人  wayneinyz

1. ArrayList、LinkedList、Vector的区别

答:


集合关系
相同点:

ArraList、LinkedList、Vector 都实现了 List 接口。

不同点:

(1) ArrayList 底层是用数组实现的,可以认为 ArrayList 是一个可改变大小的数组。
当越来越多的元素添加到 ArrayList 的时候,它的大小会动态增长。
由于 ArrayList 本质上是一个数组,所以它的元素可以直接通过 get 和 set 方法来访问。
(2) LinkedList 底层是用双向链表实现的。所以,在添加和删除操作上,性能比 ArrayList 好;在查询和修改操作上,性能比 ArrayList 差。
(3) Vector 和 ArrayList 几乎是一样的,区别在于 Vector 是线程安全的。因此,性能上较 ArrayList 要差。另一个区别是扩容策略不一样:当越来越多的元素添加进来需要更大空间的时候,Vector 的大小增长为原来的两倍,而 ArrayList 的大小增长原来的50%

总结:

2. Sring、StringBuffer、StringBuilder的区别

答:

String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)

String 类型和 StringBuffer 类型的主要性能区别在于:

在大部分情况下 StringBuffer > String
StringBuffer

java.lang.StringBuffer 是线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过调用某些方法可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer 的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。
每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。
append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。

在大部分情况下 StringBuilder > StringBuffer
StringBuilder

java.lang.StringBuilder 一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

总结:

3. HashMap与Hashtable

相同点
HashMap
HashMap 存数据的过程是:

HashMap 内部维护了一个存储数据的 Entry 数组,HashMap 采用链表解决冲突,每一个 Entry 本质上是一个单向链表。当准备添加一个 key-value
对时,首先通过 hash(key) 方法计算 hash 值,然后通过
indexFor(hash,length) 求该 key-value 对的存储位置,计算方法是先用
hash&0x7FFFFFFF 后,再对 length 取模,这就保证每一个 key-value 对都能存入 HashMap 中,当计算出的位置相同时,由于存入位置是一个链表,则把这个 key-value 对插入链表头。

Hashtable
HashMap与Hashtable的区别
(1) 继承的父类不同

Hashtable 继承自 Dictionary 类,而 HashMap 继承自 AbstractMap 类。但二者都实现了 Map 接口。

(2) 线程安全性不同

Hashtable 中的方法是 synchronized 的,而 HashMap 中的方法在默认情况下是非 synchronized 的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用 HashMap 时就必须要自己增加同步处理。

(3) 是否提供contains方法
(4) key和value是否允许null值
(5) 两个遍历方式的内部实现上不同

Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable 还使用了 Enumeration 的方式 。

(6) hash值不同

哈希值的使用不同,Hashtable 直接使用对象的 hashCode。而 HashMap
重新计算 hash 值。
hashCode 是 jdk 根据对象的地址或者字符串或者数字算出来的 int 类型的数值。
Hashtable 计算 hash 值,直接用 key 的 hashCode(),而 HashMap 重新计算了 key 的 hash 值,Hashtable 在求 hash 值对应的位置索引时,用取模运算,而 HashMap 在求位置索引时,则用与运算,且这里一般先用hash&0x7FFFFFFF 后,再对 length 取模,&0x7FFFFFFF 的目的是为了将负的 hash 值转化为正值,因为 hash 值有可能为负数,而
&0x7FFFFFFF 后,只有符号外改变,而后面的位都不变。

(7) 内部实现使用的数组初始化和扩容方式不同

4. Java的四种引用

(1) 强引用(StrongReference)

强引用在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用:

Object object = new Object();
String str = "hello";

只要某个对象有强引用与之关联,JVM 必定不会回收这个对象,即使在内存不足的情况下,JVM 宁愿抛出 OutOfMemory 错误也不会回收这种对象。
如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

(2) 软引用(SoftReference)

软引用是用来描述一些有用但并不是必需的对象,在 Java 中用
java.lang.ref.SoftReference 类来表示。对于软引用关联着的对象,只有在内存不足的时候 JVM 才会回收该对象。因此,这一点可以很好地用来解决 OOM 的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被 JVM 回收,这个软引用就会被加入到与之关联的引用队列中。

(3) 弱引用(WeakReference)

弱引用也是用来描述非必需对象的,当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在 java 中,用
java.lang.ref.WeakReference 类来表示。

(4) 虚引用(PhantomReference)

虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在
java 中用 java.lang.ref.PhantomReference 类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收

进一步理解软引用和弱引用

对于强引用,我们平时在编写代码时经常会用到。而对于其他三种类型的引用,使用得最多的就是软引用和弱引用,这2种既有相似之处又有区别。
它们都是用来描述非必需对象的,但是被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收
针对上面的特性,软引用适合用来进行缓存,当内存不够时能让JVM回收内存,弱引用能用来在回调函数中防止内存泄露。因为回调函数往往是匿名内部类,隐式保存有对外部类的引用,所以如果回调函数是在另一个线程里面被回调,而这时如果需要回收外部类,那么就会内存泄露,因为匿名内部类保存有对外部类的强引用。

5. 关键字synchronized、volatile、transient

(1) synchronized关键字

synchronized 关键字用于多线程访问程序中的共享资源时实现顺序同步访问资源。可以修饰方法或者代码块。而且关键字 synchronized 取得的锁都是对象锁

注意:
什么叫对象锁呢,就是一个对象产生一把锁,如果多个线程调用一个对象的多个方法,这些方法都被 synchronized 修饰,那么这些线程共同竞争一把锁,最后表现的就是同步顺序执行各个被 synchronized 修饰的方法。

(1.1) 同步方法

使用 synchronized 修饰的方法是同步方法,多个线程调用同一个对象的同步方法时会顺序同步执行。

(1.1.1) synchronized锁重入

当一个线程执行 synchronized 关键字修饰的方法的时候,其他线程是不可以访问该对象中 synchronized 关键字修饰的方法的,因为唯一的一把锁已经被当前线程使用了。但是如果当前线程在 synchronized 方法/块的内部调用本类或者其父类的其他 synchronized 方法/块,是永远可以得到锁的,这就叫做锁重入

(1.1.2) 出现异常,同步锁自动释放

当线程执行 synchronized 修饰的代码出现异常时,其所持有的锁会自动释放,从而其他线程可以再次争夺锁的使用权而非一直等待造成死锁。

(1.1.3) 同步不具有继承性

如果父类被 synchronized 关键字修饰,那么线程执行父类代码会同步,但是同步并不会继承给其子类,调用子类的方法仍然是异步的。

(1.2) 同步代码块

一般来说,一个方法处理的内容很多,如果 synchronized 修饰以后,其他同步方法就必须等待其执行完毕才可以继续执行,如果该方法需要较长时间处理,这就明显会降低效率,失去了多线程的意义,所以我们可以考虑将同步的范围缩小,即从同步一个方法缩小为同步一段代码块,这就是同步代码块产生的原因。
同步代码块的语法是:

synchronized (this) {} 
synchronized (object) {}

synchronized(this )我称之为 this 同步代码块,针对的是当前对象;synchronized(object) 我称之为非 this 同步代码块,针对的是 object 对象。

注意:
不论是同步方法还是同步代码块,实质上都是争夺锁的问题,而锁一定是对象级的,即一个对象只会产生一个锁,所以只要有一个线程在执行
synchronized 修饰的东西(不论是方法还是代码块),那么其他线程都无法访问被 synchronized 修饰的方法或代码块。
但是注意使用非this同步代码块的时候,里面的 object 不要用 String 类型的,因为大家都知道 JVM 具有 String 常量池缓存的功能,所以使用 String 类型可能产生问题。

(1.3) 静态同步synchronized方法和synchronized(class)代码块

关键字 synchronized 还可以应用在 static 静态方法上,或者
synchronized(class) 代码块。如果这样写,产生的是类级别的锁,也就是给 *.java 这个类加锁,而非给某个对象加锁。这就意味着线程执行同一个类的不同对象的静态同步 synchronized 方法和synchronized(class) 代码块时,都会同步执行。

(2) volatile关键字

Java语言提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile 类型的变量时总会返回最新写入的值

在访问 volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此 volatile 变量是一种比 sychronized 关键字更轻量级的同步机制

当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到 CPU 缓存中。如果计算机有多个 CPU,每个线程可能在不同的 CPU 上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。

当一个变量定义为 volatile 之后,将具备两种特性:
volatile 性能:

volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

总结:

总体来说,volatile 是并发编程中的一种优化,在某些场景下可以代替
synchronized。但是,volatile 的不能完全取代 synchronized 的位置,只有在一些特殊的场景下,才能适用 volatile。总的来说,必须同时满足下面两个条件才能保证在并发环境的线程安全:

(3) transient关键字

transient 关键字为我们提供了便利,你只需要实现 Serializable 接口,将不需要序列化的属性前添加关键字 transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

小结:

6. foreach与for循环效率对比

(1) 对于数组而言,for 和 foreach 遍历效率相差不大。
(2) 对于集合而言
(3) Effective Java 中建议,一般情况下使用 foreach 进行循环,因为其在简洁性和预防 Bug 方面有着传统 for 循环无法比拟的优势,并且没有性能损失。但是除了以下三种情况:
建议:

7. Java是按值传递还是按引用传递?

(1) 什么是按值传递

指的是在方法调用时,传递给参数的是值的拷贝。
按值传递的重要特点:传递的是值的拷贝,也就是说传递后就互不相关了。

(2) 什么是按引用传递

指的是在方法调用时,传递给参数的是引用的地址,也就是变量所对应的内存空间的地址。
按引用传递的重要特点:传递的是引用的地址,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。

总结:

一般情况下,在数据做为参数传递的时候,基本数据类型是值传递,引用数据类型是引用传递(地址传递)。

特例:
String对象做为参数传递时,走的依然是引用传递,只不过String这个类比较特殊。 String对象一旦创建,内容不可更改。每一次内容的更改都是重现创建出来的新对象

参考 JAVA中值传递和引用传递的三种情况

上一篇下一篇

猜你喜欢

热点阅读