极率大的源码底层原理,杂食面试题(含答案)

版权声明:如笔记。一点一滴,伴我成长。可转载 https://blog.csdn.net/Butterfly_resting/article/details/89668402

本文的面试题如下:
HashSet底层原理:(面试过)
HashMap底层原理:(面试过)
Hashtable底层原理:
ConcurrentHashMap 的工作原理
IO,NIO,AIO底层原理
抽象类和接口类的区别?
常考的设计模式模式:单例模式,观察者模式,代理模式,工厂模式,装饰器模式
Servlet生命周期
阐述下快速排序
forward 和redirect的区别
Java类初始化顺序
Java有几种文件拷贝方式?哪一种最高效?
equals 与 == ,hashcode()的区别
如何解决跨域
常见的异常类有哪些?
TreeSet,TreeMap,HashSet的区别


HashSet底层原理:(面试过)

http://zhangshixi.iteye.com/blog/673143
https://blog.csdn.net/HD243608836/article/details/80214413
HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素。HashSet的存储方式是把HashMap中的Key作为Set的对应存储项。该行的value就是一个Object类型的常量
(HashMap储存键值对 HashSet仅仅存储对象)
为什么无重复?
HashSet的存储方式是把HashMap中的Key作为Set的对应存储项。因为HashMap的key是不能有重复的
2. HashSet的实现
对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成, (实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。)
HashSet的源代码

对于HashSet中保存的对象,请注意正确重写其equals和hashCode方法,以保证放入的对象的唯一性。
一定要覆盖自定义类的 equals 和 hashCode 方法,hashCode 方法是找到当前对象在 Node 数组的位置,而 equals 是比较当前对象与对应坐标链表中的对象是否相同。
插入

当有新值加入时,底层的HashMap会判断Key值是否存在(HashMap细节请移步深入理解HashMap),如果不存在,则插入新值,同时这个插入的细节会依照HashMap插入细节;如果存在就不插入。

HashMap底层原理:

1. HashMap概述
HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
2. HashMap的数据结构
HashMap实际上是一个“数组+链表+红黑树”的数据结构
3. HashMap的存取实现
(1.8之前的)
当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
1.8
put():

  1. 根据key计算得到key.hash = (h = k.hashCode()) ^ (h >>> 16);
  2. 根据key.hash计算得到桶数组的索引index = key.hash & (table.length - 1),这样就找到该key的存放位置了:
    ① 如果该位置没有数据,用该数据新生成一个节点保存新数据,返回null;
    ② 如果该位置有数据是一个红黑树,那么执行相应的插入 / 更新操作
    ③ 如果该位置有数据是一个链表,分两种情况一是该链表没有这个节点,另一个是该链表上有这个节点,注意这里判断的依据是key.hash是否一样: 如果该链表没有这个节点,那么采用尾插法新增节点保存新数据,返回null; 如果该链表已经有这个节点了,那么找到該节点并更新新数据,返回老数据。
    get():
    计算需获取数据的hash值(计算过程跟put一样),计算存放在数组table中的位置(计算过程跟put一样),然后依次在数组,红黑树,链表中查找(通过equals()判断),最后再判断获取的数据是否为空,若为空返回null否则返回该数据

树化与还原

  • 哈希表的最小树形化容量
  • 当哈希表中的容量大于这个值时(64),表中的桶才能进行树形化
  • 否则桶内元素太多时会扩容,而不是树形化
  • 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
  • 一个桶的树化阈值
  • 当桶中元素个数超过这个值时(8),需要使用红黑树节点替换链表节点
  • 这个值必须为 8,要不然频繁转换效率也不高
  • 一个树的链表还原阈值
  • 当扩容时,桶中元素个数小于这个值(6),就会把树形的桶元素 还原(切分)为链表结构
  • 这个值应该比上面那个小,至少为 6,避免频繁转换
    条件1. 如果当前桶数组为null或者桶数组的长度 < MIN_TREEIFY_CAPACITY(64),则进行扩容处理(见代码片段2:resize());
    条件2. 当不满足条件1的时候则将桶中链表内的元素转换成红黑树!!!稍后再详细讨论红黑树。

扩容机制的实现

  1. 扩容(resize)就是重新计算容量。当向HashMap对象里不停的添加元素,而HashMap对象内部的桶数组无法装载更多的元素时,HashMap对象就需要扩大桶数组的长度,以便能装入更多的元素。
  2. capacity 就是数组的长度/大小,loadFactor 是这个数组填满程度的最大比比例。
  3. size表示当前HashMap中已经储存的Node<key,value>的数量,包括桶数组和链表 / 红黑树中的的Node<key,value>。
  4. threshold表示扩容的临界值,如果size大于这个值,则必需调用resize()方法进行扩容。
  5. 在jdk1.7及以前,threshold = capacity * loadFactor,其中 capacity 为桶数组的长度。 这里需要说明一点,默认负载因子0.75是是对空间和时间(纵向横向)效率的一个平衡选择,建议大家不要修改。 jdk1.8对threshold值进行了改进,通过一系列位移操作算法最后得到一个power of two size的值
    什么时候扩容

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

扩容必须满足两个条件
1、 存放新值的时候 当前已有元素的个数 (size) 必须大于等于阈值

2、 存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值换算出来的数组下标位置已经存在值)
//如果计算的哈希位置有值(及hash冲突),且key值一样,则覆盖原值value,并返回原值value

if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
        V oldValue = e.value;
        e.value = value;
        e.recordAccess(this);
        return oldValue;

      }

resize()方法: 该函数有2种使用情况1.初始化哈希表 2.当前数组容量过小,需扩容
过程:
HashMap是先插入数据再进行扩容的,但是如果是刚刚初始化容器的时候是先扩容再插入数据。
插入键值对时发现容量不足,调用resize()方法方法,
1.首先进行异常情况的判断,如是否需要初始化,二是若当前容量》最大值则不扩容,
2.然后根据新容量(是就容量的2倍、)新建数组,将旧数组上的数据(键值对)转移到新的数组中,这里包括:(遍历旧数组的每个元素,重新计算每个数据在数组中的存放位置。(原位置或者原位置+旧容量),将旧数组上的每个数据逐个转移到新数组中,这里采用的是尾插法。)
3.新数组table引用到HashMap的table属性上
4.最后重新设置扩容阙值,

ConcurrentHashMap 的工作原理

概念:
ConcurrentHashMap的目标是实现支持高并发、高吞吐量的线程安全的HashMap。
1.8之前:
数据结构:
ConcurrentHashMap是由Segment数组结构和 多个HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap相似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中: ConcurrentHashMap中默认是把segments初始化为长度为16的数组
在这里插入图片描述
1.8后
变化
ConcurrentHashMap的JDK8与JDK7版本的并发实现相比,最大的区别在于JDK8的锁粒度更细,理想情况下talbe数组元素的大小就是其支持并发的最大个数
实现
改进一:取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。
数据结构
改进二:将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。
概念
JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本。

树化和还原
与HashMap一样 。

一些成员
Node是ConcurrentHashMap存储结构的基本单元,继承于HashMap中的Entry,用于存储数据。 ,就是一个链表,但是只允许对数据进行查找,不允许进行修改
通过TreeNode作为存储结构代替Node来转换成黑红树。TreeBin
TreeBin就是封装TreeNode的容器,它提供转换黑红树的一些条件和锁的控制
// 读写锁状态
static final int WRITER = 1; // 获取写锁的状态
static final int WAITER = 2; // 等待写锁的状态
static final int READER = 4; // 增加数据时读锁的状态

构造器
public ConcurrentHashMap() {
} 初始化其实是一个空实现, 初始化操作并不是在构造函数实现的,而是在put操作中实现。 还提供了其他的构造函数,有指定容量大小或者指定负载因子,跟HashMap一样。

存取实现
put(): 对当前的table进行无条件自循环直到put成功
新:
版本1:

  1. 如果没有初始化就先调用initTable()方法来进行初始化过程
  2. 如果没有hash冲突就直接CAS插入
  3. 如果还在进行扩容操作就先进行扩容(ForwardingNode的hash值判断)
  4. 如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入,
  5. 最后一个如果该链表的数量大于阈值8,就要先转换成黑红树的结构,break再一次进入循环(树化)
  6. 如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容。
    版本2:
    1判空;ConcurrentHashMap的key、value都不允许为null
    2计算hash。利用方法计算hash值。
    3遍历table,进行节点插入操作,过程如下:
    如果table为空,则表示ConcurrentHashMap还没有初始化,则进行初始化操作:initTable()
    根据hash值获取节点的位置i,若该位置为空,则直接插入,这个过程是不需要加锁的。计算f位置:i=(n - 1) & hash
    如果检测到fh = f.hash == -1,则f是ForwardingNode节点,表示有其他线程正在进行扩容操作,则帮助线程一起进行扩容操作
    如果f.hash >= 0 表示是链表结构,则遍历链表,如果存在当前key节点则替换value,否则插入到链表尾部。如果f是TreeBin类型节点,则按照红黑树的方法更新或者增加节点
    若链表长度 > TREEIFY_THRESHOLD(默认是8),则将链表转换为红黑树结构

4调用addCount方法,ConcurrentHashMap的size + 1

如果一个或多个线程正在对ConcurrentHashMap进行扩容操作,当前线程也要进入扩容的操作中。这个扩容的操作之所以能被检测到,是因为transfer方法中在空结点上插入forward节点,如果检测到需要插入的位置被forward节点占有,就帮助进行扩容;
如果检测到要插入的节点是非空且不是forward节点,就对这个节点加锁,这样就保证了线程安全。尽管这个有一些影响效率,但是还是会比hashTable的synchronized要好得多。

get()

  1. 计算hash值,定位到该table索引位置,如果是首节点符合就返回
  2. 如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,查找该节点,匹配就返回
  3. 以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回null
    概括版
    (1)对于get读操作,如果当前节点有数据,还没迁移完成,此时不影响读,能够正常进行。
    如果当前链表已经迁移完成,那么头节点会被设置成fwd节点,此时get线程会帮助扩容。
    (2)对于put/remove写操作,如果当前链表已经迁移完成,那么头节点会被设置成fwd节点,此时写线程会帮助扩容,如果扩容没有完成,当前链表的头节点会被锁住,所以写线程会被阻塞,直到扩容完成。

扩容机制https://www.e-learn.cn/content/java/1154828
总体:

  1. 计算每个线程可以处理的桶区间。默认 16.
  2. 初始化临时变量 nextTable,扩容 2 倍。
  3. 死循环,计算下标。完成总体判断。
  4. 如果桶内有数据,同步转移数据。通常会像链表拆成 2 份。

先来看一下单线程是如何完成的
它的大体思想就是遍历、复制的过程。首先根据运算得到需要遍历的次数i,然后利用tabAt方法获得i位置的元素:
如果这个位置为空,就在原table中的i位置放入forwardNode节点,这个也是触发并发扩容的关键点;
如果这个位置是Node节点(fh>=0),如果它是一个链表的头节点,就构造一个反序链表,把他们分别放在nextTable的i和i+n的位置上
如果这个位置是TreeBin节点(fh<0),也做一个反序处理,并且判断是否需要untreefi,把处理的结果分别放在nextTable的i和i+n的位置上
遍历过所有的节点以后就完成了复制工作,这时让nextTable作为新的table,并且更新sizeCtl为新容量的0.75倍 ,完成扩容。
多线程是如何完成的:
如果遍历到的节点是forward节点,就向后继续遍历,再加上给节点上锁的机制,就完成了多线程的控制。多线程遍历节点,处理了一个节点,就把对应点的值set为forward,另一个线程看到forward,就向后遍历。这样交叉就完成了复制工作。

Hashtable底层原理:

概念
HashTable类继承自Dictionary类, 实现了Map接口。 大部分的操作都是通过synchronized锁保护的,是线程安全的, key、value都不可以为null, 每次put方法不允许null值,如果发现是null,则直接抛出异常。
官方文档也说了:如果在非线程安全的情况下使用,建议使用HashMap替换,如果在线程安全的情况下使用,建议使用ConcurrentHashMap替换。

数据结构
数组+链表。

存取实现
put():
限制了value不能为null。
由于直接使用key.hashcode(),而没有向hashmap一样先判断key是否为null,所以key为null时,调用key.hashcode()会出错,所以hashtable中key也不能为null。
Hashtable是在链表的头部添加元素的。
int index = (hash & 0x7FFFFFFF) %tab.length;获取index的方式与HashMap不同

扩容机制
Hashtable默认capacity是11,默认负载因子是0.75.。当前表中的Entry数量,如果超过了阈值,就会扩容,即调用rehash方法,重新计算每个键值对的hashCode;
判断新的容量是否超过了上限,没超过就新建一个新数组,大小为原数组的2倍+1,将旧数的键值对重新hash添加到新数组中。

IO,NIO,AIO底层原理

区分同步或异步:同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,其他任务不需要等待当前调用返回
区分阻塞与非阻塞:在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如 ServerSocket 新连接建立完毕,或数据读取、写入操作完成;而非阻塞则是不管 IO 操作是否结束,直接返回,相应操作在后台继续处理。
IO概念
传统的 java.io 包,它基于流模型实现。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。缺点则是 IO 效率和扩展性存在局限性

NIO概念
在 Java 1.4 中引入了 NIO 框架(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序。
NIO 指新IO,核心是 同步非阻塞,解决传统IO的阻塞问题。操作对象是Buffer。 其实NIO的核心是IO线程池,(一定要记住这个关键点)。 NIO中的IO多路复用调用系统级别的select和poll模型,由系统进行监控IO状态,避免用户线程通过反复尝试的方式查询状态。
Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
NIO 的主要组成部分
Buffer,高效的数据容器,除了布尔类型,所有原始数据类型都有相应的 Buffer 实现。
Channel:类似在 Linux 之类操作系统上看到的文件描述符,用来支持批量式 IO 操作的一种抽象。
Selector,是 NIO 实现多路复用的基础,它提供了一种高效的机制,可以检测到注册在 Selector 上的多个 Channel 中,是否有 Channel 处于就绪状态,进而实现了单线程对多 Channel 的高效管理Selector 同样是基于底层操作系统机制
Chartset,提供 Unicode 字符串定义
NIO的工作原理
1.由一个专门的线程来处理所有的IO事件,并负责分发。
2.事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
3.线程通讯:线程之间通过wait,notify等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。

3.通信模型是怎么实现的呢?
java NIO采用了双向通道(channel)进行数据传输,而不是单向的流(stream),在通道上可以注册我们感兴趣的事件。
四种事件
服务端接收客户端连接事件SelectionKey.OP_ACCEPT(16)
客户端连接服务端事件SelectionKey.OP_CONNECT(8)
读事件SelectionKey.OP_READ(1)
写事件SelectionKey.OP_WRITE(4)
服务端和客户端各自维护一个管理通道的对象,我们称之为selector,该对象能检测一个或多个通道 (channel) 上的事件。我们以服务端为例,如果服务端的selector上注册了读事件,某时刻客户端给服务端发送了一些数据,阻塞I/O这时会调用read()方法阻塞地读取数据,而NIO的服务端会在selector中添加一个读事件。服务端的处理线程会轮询地访问selector,如果访问selector时发现有感兴趣的事件到达,则处理这些事件,如果没有感兴趣的事件到达,则处理线程会一直阻塞直到感兴趣的事件到达为止。
在这里插入图片描述
AIO概念
在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。

抽象类和接口类的区别?

1.抽象方法必须为 public 或者 protected,缺省情况下默认为 public。而接口的所有方法访问权限自动被声明为 public abstract,接口中可以定义“成员变量”,会自动变为 public static final 修饰的静态常量
2.抽象方法必须由子类来实现,接口可以通过类命名直接访问:ImplementClass.name
3.如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法,如果子类没有实现父类的抽象方法,则必须将子类也定义为抽象类。 而 实现接口的非抽象类必须实现接口中所有方法,抽象类可以不用全部实现
4.抽象类可以有构造器,而接口没有
5.一个子类只存在一个父类,而实现多接口接口

常考的设计模式模式:单例模式,观察者模式,代理模式,工厂模式,装饰器模式

单例模式:
一个类只有一个实例
观察者模式
一对多的依赖关系,让多个观察者同时监听某一个主题帮助。当该对象发生变化时,会通知所有观察者,使他们的状态自动发生变化。

代理模式
就是为其他对象提供一种代理以控制对这个对象的访问。
静态代理在使用时,需要定义接口或者父类,被代理对象(目标对象)与代理对象(Proxy)一起实现相同的接口或者是继承相同父类。

代理模式(通过代理静默地解决一些业务无关的问题,比如远程、安全、事务、日志、资源关闭……让应用开发者可以只关心他的业务)
静态代理:事先写好代理类,可以手工编写,也可以用工具生成。缺点是每个业务类都要对应一个代理类,非常不灵活。
动态代理:运行时自动生成代理对象。缺点是生成代理代理对象和调用代理方法都要额外花费时间。
JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。新版本也开始结合ASM机制。
cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
Java 发射机制的常见应用:动态代理(AOP、RPC)、提供第三方开发者扩展能力(Servlet容器,JDBC连接)、第三方组件创建对象(DI)
(动态)代理模式主要涉及三个要素:
其一:抽象类接口
其二:被代理类(具体实现抽象接口的类)
其三:动态代理类:实际调用被代理类的方法和属性的类

工厂模式
工厂模式是用来创建对象的一种最常用的设计模式,我们不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂
装饰器模式
允许向一个现有的对象添加新的功能,同时又不改变其结构. 不修改原来代码的情况下,动态地给一个对象添加一些额外的职责和功能

Servlet生命周期

Web 容器加载 Servlet 并将其实例化后, Servlet 生命周期开始,容器运行其 init 方法进行
Servlet 的初始化,请求到达时运行其 service 方法, service 方法自动派遣,运行请求的
doXXX 方法(doGet、 doPost),当服务器决定将实例销毁的时候调用其 Destroy 方法。
Servlet的生命周期包含了下面4个阶段:
①实例化阶段:服务器对Servlet进行实例化,调用Servlet的构造方法
②初始化阶段:服务器调用Servlet的init方法进行初始化(只在第一次请求时调用)。
③请求处理阶段:服务器调用Servlet的service方法,然后根据请求方式调用相应的doXXX方法。
④服务终止阶段:服务器调用Servlet的destroy方法销毁Servlet实例
1.
Servlet 生命周期:Servlet 加载—>实例化—>服务—>销毁。
2.
init():在Servlet的生命周期中,仅执行一次init()方法。它是在服务器装入Servlet时执行的,负责初始化Servlet 对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行 init()。
3.
service():它是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,该对象的 Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应” (ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。默认的服务功能是调用与HTTP请求的方法 相应的do功能。
4.
destroy(): 仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资 源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。

https://www.cnblogs.com/lgk8023/p/6427977.html
https://my.oschina.net/u/2274056/blog/371357

阐述下快速排序

在数组中任意取一位置,将其他的数据与它进行比较,小于的放在左边,大于或者等于的放在右边,

Java类初始化顺序

基类静态代码块,基类静态成员字段(并列优先级,按照代码中出现的先后顺序执行,且只有第一次加载时执行)——>派生类静态代码块,派生类静态成员字段(并列优先级,按照代码中出现的先后顺序执行,且只有第一次加载时执行)——>基类普通代码块,基类普通成员字段(并列优点级,按代码中出现先后顺序执行)——>基类构造函数——>派生类普通代码块,派生类普通成员字段(并列优点级,按代码中出现先后顺序执行)——>派生类构造函数

Java有几种文件拷贝方式?哪一种最高效?

1.利用 java.io 类库,直接为源文件构建一个 FileInputStream 读取,然后再为目标文件构建一个 FileOutputStream,完成写入工作
2.利用 java.nio 类库提供的 transferTo 或 transferFrom 方法实现。在 Linux 和 Unix 上,则会使用到零拷贝技术,数据传输并不需要用户态参与,省去了上下文切换的开销和不必要的内存拷贝,进而可能提高应用拷贝性能。
3.Java 标准类库本身已经提供了几种 Files.copy 的实现。
总体上来说,NIO transferTo/From 的方式可能更快,因为它更能利用现代操作系统底层机制,避免不必要拷贝和上下文切换。

equals 与 == ,hashcode()的区别

== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

两个对象的 hashCode() 相同,equals() 不一定 true。(因为在散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。)
1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。
2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。

如何解决跨域

http://blog.720ui.com/2016/web_cross_domain/#跨域问题普遍么

跨域问题,是由于JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。换句话说,只有JavaScript存在跨域问题。
http://blog.720ui.comhttp://docs.720ui.com ,是不同的二级域名,存在跨域问题。
http://blog.720ui.comhttps://blog.720ui.com , 是不同的协议,存在跨域问题
http://blog.720ui.comhttp://blog.720ui.com:4000 , 是不同的端口号,存在跨域问题。
http://blog.720ui.com/java/http://blog.720ui.com/about/, 虽然文件夹不同,但是是相同域名下,所以不存在跨域问题。

如何去解决跨域问题呢
1)CORS 全称为 Cross Origin Resource Sharing(跨域资源共享) 浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,但用户不会有感觉。因此,实现CORS通信的关键是服务端。服务端只需添加相关响应头信息,即可实现客户端发出 AJAX 跨域请求。 值得注意的是,浏览器必须先以 OPTIONS 请求方式发送一个预请求,从而获知服务器端对跨源请求所支持 HTTP 方法。 不幸的是,CORS不支持IE8、IE9
2)搭建中间转发层
跨域问题的核心是什么?不同源访问。是啊,如果我们转换成同源请求,就不存在这个问题啦。 通过将服务端的请求进行转发
3)Nginx反向代理(常用)
首先,产品需要搭建一个中转nginx服务器,用于转发请求。当然,我们都是基于Nginx作为反向代理,所以当然是水到渠成。
那么,Nginx的思路,就是通过Nginx解析URL地址的时候进行判断,将请求转发的具体的服务器上。

常见的异常类有哪些?

NullPointerException 空指针异常
ClassNotFoundException 指定类不存在
NumberFormatException 字符串转换为数字异常
IndexOutOfBoundsException 数组下标越界异常
ClassCastException 数据类型转换异常
FileNotFoundException 文件未找到异常
NoSuchMethodException 方法不存在异常
IOException IO 异常
SocketException Socket 异常

TreeSet,TreeMap,HashSet的区别

TreeSet底层就是一个TreeMap,但是Value值为一个虚拟值,实现了Set接口。

TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树。TreeSet 里绝大部分方法都是直接调用 TreeMap 的方法来实现的。 内部维持了一个简化版的 TreeMap, TreeSet 内部需要对存储的元素进行排序 , 需要将该类继承comparable并重写compareTo方法(TreeMap 对key进行升序排列)
treeset和hashset的区别

1)HashSet和TreeSet的区别就是:后者可以排序 、因为TreeSet底层是二叉树、通过传入比较器,可以自定义排序。但需要自定义比较器。

猜你喜欢

转载自blog.csdn.net/Butterfly_resting/article/details/89668402