Java后端各种小知识点

 

1、ArrayList和LinkedList的比较

ArrayList实现了List接口,它是以数组的方式来实现的,数组的特性是可以使用索引的方式来快速定位对象的位置,因此对于快速的随机取得对象的需求,使用ArrayList实现执行效率上会比较好. 

 

LinkedList是采用链表的方式来实现List接口的,它本身有自己特定的方法,如: addFirst(),addLast(),getFirst(),removeFirst()等. 由于是采用链表实现的,因此在进行insert和remove动作时在效率上要比ArrayList要好得多!适合用来实现Stack(堆栈)与Queue(队列),前者先进后出,后者是先进先出.

 

在删除可插入对象的动作时,为什么ArrayList的效率会比较低呢?

解析: 因为ArrayList是使用数组实现的,若要从数组中删除或插入某一个对象,需要移动后段的数组元素,从而会重新调整索引顺序,调整索引顺序会消耗一定的时间,所以速度上就会比LinkedList要慢许多. 相反,LinkedList是使用链表实现的,若要从链表中删除或插入某一个对象,只需要改变前后对象的引用即可!

 

2、String 、String Buffer、String Builder的区别

这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。

(1)首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String

  String最慢的原因:

  String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。

Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。

String str="abc"+"de"和String str="abcde"是等效的,如果分行写的话就会出现垃圾回收,降低速度。

(2)在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的,因为它加了一些锁。

(3)总结一下

 

  String:适用于少量的字符串操作的情况

  StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

  StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

3、Singleton和prototype的区别

默 认情况下,从bean工厂所取得的实例为Singleton(bean的singleton属性) 

Singleton: Spring容器只存在一个共享的bean实例,默认的配置。 那么Spring IoC容器中只会存在一个共享的bean实,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。换言之,当把一个bean定义设置为singlton作用域时,Spring IoC容器只会创建该bean定义的唯一实例。这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都将返回被缓存的对象实例。

Prototype: 每次对bean的请求都会创建一个新的bean实例(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法--只有getbean才会在容器中生成一个bean)

二者选择的原则:有状态的bean都使用Prototype作用域,而对无状态的bean则应该使用 singleton作用域。

spring Bean的作用域:

scope=singleton(默认,单例,生成一个实例) 不是线程安全,性能高

scope=prototype(多线程, 生成多个实例)

4、有状态对象和无状态对象

有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。其实就是有数据成员的对象。

无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象。不能保存数据,是不变类,是线程安全的。具体来说就是只有方法没有数据成员的对象,或者有数据成员但是数据成员是可读的对象。

/**
 * 有状态bean,有state,user等属性,并且user有存偖功能,是可变的。
 */
public class StatefulBean {

    public int state;
    // 由于多线程环境下,user是引用对象,是非线程安全的
    public User user;

/**
 * 无状态bean,不能存偖数据。因为没有任何属性,所以是不可变的。只有一系统的方法操作。
 */
public class StatelessBeanService {

    // 虽然有billDao属性,但billDao是没有状态信息的,是Stateless Bean.
    BillDao billDao;

5、列出集中JDK并发容器

Collection.synchronizedMap();

Collection.synchronizedList();

LinkedBlockingDeque:双向链表实现的双端阻塞队列。

ConcurrentLinkedDeque:线程安全的无界双端队列。底层采用双向链表。支持FIFO和FILO

CopyOnWriteArrayList:提供高效地读取操作,使用在读多写少、数据量较小的场景。CopyOnWriteArrayList读取操作不用加锁,且是安全的;写操作时,先copy一份原有数据数组,再对复制数据进行写入操作,最后将复制数据替换原有数据,从而保证写操作不影响读操作,修改容器的代价是昂贵的,因此建议批量增加addAll、批量删除removeAll。

ConcurrentLinkedQueue:使用单链表作为数据结构,它采用无锁操作,可以认为是高并发环境下性能最好的队列。

ConcurrentSkipListMap:跳表是条有序的单链表,它的每个节点都有多个指向后继节点的引用。它有多个层次,上层都是下层的子集,从而能跳过不必要的节点,提升搜索速度。它通过空间来换取时间ConcurrentSkipListMap的实现就是实现了一个无锁版的跳表,主要是利用无锁的链表的实现来管理跳表底层,同样利用CAS来完成替换。

ConcurrentHashMap:实现了HashTable的所有功能,线程安全,但却在检索元素时不需要锁定,因此效率更高。采用分段锁实现并发。

6、ConcurrentHashMap和Hashtable的区别

它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。

7、同步锁

一个线程在访问一个对象的同步方法时,另一个线程可以同时访问这个对象的非同步方法。

一个线程在访问一个对象的同步方法时,另一个线程不能同时访问这个对象的另一个同步方法。

一个线程在访问一个对象的非静态同步方法时,另一个线程可以同时访问这个对象的另一个静态同步方法。

8、ArrayList的动态扩容

 在JKD1.6中实现是,如果通过无参构造的话,初始数组容量为10,每次通过copeOf的方式扩容后容量为原来的1.5倍以上就是动态扩容的原理。

9、ArrayList和Vector的区别

在程序中使用这两种容器没太大区别,ArrayList是线程异步,不安全;Vector是线程同步,安全,就这点区别。

10、取模和取余运算的区别

11、Hashcode与equals方法

    也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。

put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。

 

有人会说,可以直接根据hashcode值判断两个对象是否相等吗?肯定是不可以的,因为不同的对象可能会生成相同的hashcode值。虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过equals方法。

  也就是说对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;

  如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;

  如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;

  如果两个对象的hashcode值相等,则equals方法得到的结果未知。

12、位运算

位运算符的基本运算,假设整数变量A的值为60和变量B的值为13:

操作符描述例子
如果相对应位都是1,则结果为1,否则为0(A&B),得到12,即0000 1100
|如果相对应位都是0,则结果为0,否则为1(A | B)得到61,即 0011 1101
^如果相对应位值相同,则结果为0,否则为1(A ^ B)得到49,即 0011 0001
按位取反运算符翻转操作数的每一位,即0变成1,1变成0。(〜A)得到-61,即1100 0011
<< 按位左移运算符。左操作数按位左移右操作数指定的位数。A << 2得到240,即 1111 0000
>> 按位右移运算符。左操作数按位右移右操作数指定的位数。A >> 2得到15即 1111
>>> 按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。A>>>2得到15即0000 1111

 


右移>> :该数对应的二进制码整体右移,左边的用原有标志位补充,右边超出的部分舍弃。

无符号右移>>> :不管正负标志位为0还是1,将该数的二进制码整体右移,左边部分总是以0填充,右边部分舍弃。

举例对比:

-5用二进制表示1111 1011,红色为该数标志位

-5>>2: 1111 1011-------------->1111 1110。

11为标志位

-5>>>2:  1111 1011-------------->0011 1110。

00为补充的0

题外话:位运算是一种基于二进制的运算,涉及到知识包含原码、反码、补码,在此做一个小小的说明,

对于原码。就是当前数字的二进制表现形式,如-1的原码是1000 0001。

对于反码,正数的反码就是本身。负数的反码是二进制保留符号位。剩余位取反,比如-1的反码是1111 1110;

对于补码,正数的反码、补码、原码都是一样的,负数的补码是在其反码的基础上+1,比如-1的补码是1111 1111。

 

在JDK的原码中。有很多初始值都是通过位运算计算的,位运算有很多特性,能够在线性增长的数据中起到作用。且对于一些运算,位运算是最直接、最简便的方法。

13、ConcurrentHashmap数据结构

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap中扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap中包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构。一个Segment里面包含一个HashEntry数组,每个HashEntry是一个链表的元素。每个Segment拥有一个锁,当对HashEntry数组的数据进行修改时,必须先获得对应的Segment锁,如图所示:

14、一个Thread调用多次start方法会怎样

 

Java 的线程是不允许启动两次的,第二次调用必然会抛出 IllegalThreadStateException,这是一种运行时异常,多次调用 start 被认为是编程错误。

 

15、截取字符串,一个空格或者多个空格

str.split("\\s+")

16、集合分片

使用guava类库,将集合分成size为20的多个子集合。

import com.google.common.collect.Lists;
public class ListsPartitionTest {
    public static void main(String[] args) {
        List<String> ls = Arrays.asList("1,2,3,4,5,6,7,8,9,1,2,4,5,6,7,7,6,6,6,6,6,66".split(","));
        System.out.println(Lists.partition(ls, 20));
    }
}

17 sleep不释放对象锁,wait放弃对象锁

18   HashMap的线程不安全性体现在哪里?

假设两个线程同时进行resize,A->B 第一线程在处理过程中比较慢,第二个线程已经完成了倒序编程了B-A 那么就出现了循环,B->A->B.这样就出现了就会出现CPU使用率飙升

在这个过程中可以发现,之所以出现死循环,主要还是在于对于链表对倒序处理,在Java 8中,已经不在使用倒序列表,死循环问题得到了极大改善

19 BIO、NIO、AIO 有什么区别?

  • BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
  • NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
  • AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

ImageIO类专门用于图片的一些操作。

20、HashMap红黑树出现的条件

红黑树出现在hashmap数组长度大于64,链表长度大于8,才会把链表替换成红黑树

21、Map实现类的有序无序问题

(1)Map的实现类有HashMap,LinkedHashMap,TreeMap。 (2) HashMap是有无序的,LinkedHashMap和TreeMap都是有序的(LinkedHashMap记录了添加数据的顺序;TreeMap默认是自然升序)。 (3) LinkedHashMap底层存储结构是哈希表+链表,链表记录了添加数据的顺序。 (4). TreeMap底层存储结构是二叉树,二叉树的中序遍历保证了数据的有序性。 (5). LinkedHashMap有序性能比较高,因为底层数据存储结构采用的哈希表。

22、如何决定该用抽象类还是接口?

在代码设计、编程开发的时候,什么时候该用抽象类?什么时候该用接口? 实际上,判断的标准很简单。如果我们要表示一种 is-a 的关系,并且是为了解决代码复用的问题,我们就用抽象类;如果我们要表示一种 has-a 关系,并且是为了解决抽象而非代码复用的问题,那我们就可以使用接口。 从类的继承层次上来看,抽象类是一种自下而上的设计思路,先有子类的代码重复,然后再抽象成上层的父类(也就是抽象类)。而接口正好相反,它是一种自上而下的设计思路。我们在编程的时候,一般都是先设计接口,再去考虑具体的实现。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值