& 和 &&区别
作为运算符:& 将二进制的每一位进行与运算
作为逻辑运算符:两者都是与,&& 如果左边为假则终止右边运算,即短路运算。& 则需要把两边的比较执行完
int和Integer的区别
int是Java的基本数据类型,而Integer是int的包装类
int直接存储整数值,而Integer是一个对象,包含了一些额外的方法和功能
int的默认值是0,而Integer的默认值是null
扩展:那么我们在判断这两个类型是否相等的时候有什么要注意的呢?
- int 和 int 类型的比较可以直接用 == 判断
- int 和 Integer 类型的比较时也可以用 == 、 Integer会自动拆箱为 int
- Integer 和 Integer 类型的比较中。如果数值范围在[-128,127]之间可以用 == 。其他范围只能用 equals 方法、原因是:JVM会维护这个范围内的缓存,比如第一个Integer是127,会存放在缓存中;在创建第二个Integer时会直接返回缓存的127,所以两者是相等的
接口和抽象类的区别
1.语法层面
- 抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract (隐式声明)方法(JDK8默认方法);
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final(隐式声明)类型的(必须在声明时赋值);
- 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2.设计层面
- 抽象类:是对整个类进行抽象,包括属性,行为(方法),那么一定是抽象类的种类(拥有同一种属性或行为的类)
- 接口:是对行为(行为)的抽象
- 抽象类是一种模板设计。接口是一种规范。
3.怎么选择
- 如果拥有一些方法,并想让他们中的一些有默认的具体实现,请选择抽象类
- 如果想实现多重继承,那么请使用接口,由于java不支持多继承,子类不能继承多个类,但一个类可以实现多个接口,因此可以使用接口来解决。
- 如果基本功能在不断变化,那么就使用抽象类,如果使用接口,那么每次变更都需要相应的去改变实现该接口的所有类。
JDK8中为什么会提供默认方法?
目的:用来减少抽象类和接口的差异,可以在接口中提供默认的实现方法并实现该接口的类不用强制去实现这个方法。JDK8中接口的静态方法只能通过接口名直接去调用,接口中的默认方法因为不是abstract的,所以可重写,也可以不重写。
谈谈你对树的理解
1.树的由来
数组:检索效率高
链表:添加删除效率高
树:综合检索和操作的效率
2.树的分类
根据不同的分类方式,树可以分为不同的类型:
- 根据树分支的数量限制:可以分为二叉树和多叉树。二叉树最多只有两个子节点,而多叉树一个节点可以有多于两个的子节点。
- 根据树节点的有序性:可以分为查找树和无序树。查找树的基本特征为任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。无序树则没有特定的键值大小关系。
- 根据具体用途和特征:例如红黑树、AVL树、平衡二叉树、平衡二叉搜索树等。红黑树是一种自平衡二叉查找树,AVL树也是一种自平衡二叉查找树,它要求任何节点的两个子树的高度差最大为1。平衡二叉树和平衡二叉搜索树则是为了平衡树的左右子树的高度差。
- 根据树的完整性和是否包含空值:可以分为完全二叉树、满二叉树、完全二叉搜索树、满二叉搜索树等。完全二叉树和满二叉树是包含所有节点的二叉树,而完全二叉搜索树和满二叉搜索树则是所有节点都按照一定顺序排列的二叉搜索树。
3.常见树的特点
- 普通二叉查找树:倾斜的问题
- AVL自平衡:左旋右旋开销问题
- 红黑树:黑节点平衡、深度问题、二叉。作为内存中数据存储
- B树和B+树:多路查找、深度比较少、一般用于大规模数据存储、索引
4.TreeMap和HashMap的区别
HashMap的综合效率为什么比TreeMap高
什么我们工作中常用HashMap而不用TreeMap?
TreeMap:本质就是红黑树的实现
HashMap:以jdk8为例。就是通过 数组+链表+红黑树+算法实现的
通过上面的实现也可以看到HashMap综合的读写效率要比TreeMap高了
HashMap
为什么引入红黑树
因为链表长度增加后检索的效率急剧降低,复杂度是 O(n)
解决hash冲突。为什么不直接用红黑树?
因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。当元素小于 8 个的时候,此时做查询操作,链表结构已经能保证查询性能。当元素大于 8 个的时候, 红黑树搜索时间复杂度是O(logn),而链表是 O(n),此时需要红黑树来加快查询速度,但是新增节点的效率变慢了。
因此,如果一开始就用红黑树结构,元素太少,新增效率又比较慢,无疑这是浪费性能的。
为什么链表改为红黑树的阈值是 8
首先和hashcode碰撞次数的泊松分布有关,主要是为了寻找一种时间和空间的平衡。在负载因子0.75(HashMap默认)的情况下,单个hash槽内元素个数为8的概率小于百万分之一,将7作为一个分水岭,等于7时不做转换,大于等于8才转红黑树,小于等于6才转链表。链表中元素个数为8时的概率已经非常小,再多的就更少了,所以原作者在选择链表元素个数时选择了8,是根据概率统计而选择的
默认加载因子为什么是0.75
这个是从时间和空间的角度综合得出的。
- 如果是1.0 当数组的值全部填充了才会发生扩容,此时Hash冲突是避免不了的。链表的操作或者红黑树的操作会牺牲时间来保证空间的利用率
- 如果是0.5 当数组中一半的数据利用了之后就会开始扩容。这时填充的数据少。hash冲突也会减少,底层的链表和红黑树的高度也会降低。查询效率增加。但是这时还有太多的空间没有利用。空间资源浪费了。
- 所以0.75是综合考虑得出的
为什么要右移16位
其实是为了减少碰撞,进一步降低hash冲突的几率。int类型的数值是4个字节的,右移16位异或可以同时保留高16位于低16位的特征
当数组的长度很短时,只有低位数的hashcode值能参与运算。而让高16位参与运算可以更好的均匀散列,减少碰撞,进一步降低hash冲突的几率。并且使得高16位和低16位的信息都被保留了。
在HashMap的put方法里面,是通过Key的hash值与数组的长度取模计算得到数组的位置。
而在绝大部分的情况下,n的值一般都会小于2^16次方,也就是65536。
所以也就意味着i的值 , 始终是使用hash值的低16位与(n-1)进行取模运算,这个是由与运算符&的特性决定的。
这样就会造成key的散列度不高,导致大量的key集中存储在固定的几个数组位置,很显然会影响到数据查找性能。
为什么Hash值要与length-1相与
把 hash 值对数组长度取模运算,模运算的消耗很大,没有位运算快。
当 length 总是 2 的n次方时,h& (length-1) 运算等价于对length取模,也就是 h%length,但是 & 比 % 具有更高的效率。
介绍下put方法的流程
- 首先根据 key 的值计算 hash 值,找到该元素在数组中存储的下标;
- 如果数组是空的,则调用 resize 进行初始化;
- 如果没有哈希冲突直接放在对应的数组下标里;
- 如果冲突了,且 key 已经存在,就覆盖掉 value;
- 如果冲突后,发现该节点是红黑树,就将这个节点挂在树上;
- 如果冲突后是链表,判断该链表是否大于 8 ,如果大于 8 并且数组容量小于 64,就进行扩容;如果链表节点大于 8 并且数组的容量大于 64,则将这个结构转换为红黑树;否则,链表插入键值对,若 key 存在,就覆盖掉 value。
String
为什么 String 在 java 中是不可变的?
- 如果不是不可变的:这种情况根本不可能,因为在字符串池的情况下,一个字符串对象/文字,例如 “Test” 已被许多参考变量引用,因此如果其中任何一个更改了值,其他参数将自动受到影响
- 字符串被广泛作为参数使用:例如,为了打开网络连接,你可以将主机名和端口号作为字符串传递,你可以将数据库URL 作为字符串传递, 以打开数据库连接,你可以通过将文件名作为参数传递给 File I/O 类来打开 Java 中的任何文件。如果String不是不可变的,这将导致严重的安全威胁,我的意思是有人可以访问他有权授权的任何文件,然后可以故意或意外地更改文件名并获得对该文件的访问权限。由于不变性,你无需担心这种威胁。这个原因也说明了,为什么String 在 Java 中是最终的,通过使 **java.lang **.String final,Java设计者确保没有人覆盖 String 类的任何行为。
- 线程间共享:由于 String 是不可变的,它可以安全地共享许多线程,这对于多线程编程非常重要. 并且避免了 Java 中的同步问题,不变性也使得String 实例在 Java 中是线程安全的,这意味着你不需要从外部同步 String 操作。
- String 缓存其哈希码:Java 中的不可变 String 缓存其哈希码,并且不会在每次调用 String 的 hashCode 方法时重新计算,这使得它在 Java 中的 HashMap 中使用的 HashMap 键非常快。简而言之,因为 String 是不可变的,所以没有人可以在创建后更改其内容,这保证了 String 的 hashCode 在多次调用时是相同的。
- 类加载机制使用:如果 String 是可变的,加载“java.io.Writer” 的请求可能已被更改为加载 “mil.vogoon.DiskErasingWriter”. 安全性和字符串池是使字符串不可变的主要原因
为什么 char 数组比 String 更适合存储密码
- 由于字符串在 Java 中是不可变的,如果你将密码存储为纯文本,它将在内存中可用,直到垃圾收集器清除它. 并且为了可重用性,会存在 String 在字符串池中, 它很可能会保留在内存中持续很长时间,从而构成安全威胁。由于任何有权访问内存转储的人都可以以明文形式找到密码,这是另一个原因,你应该始终使用加密密码而不是纯文本。由于字符串是不可变的,所以不能更改字符串的内容,因为任何更改都会产生新的字符串,而如果你使用char[],你就可以将所有元素设置为空白或零。因此,在字符数组中存储密码可以明显降低窃取密码的安全风险。
- 使用 String 时,总是存在在日志文件或控制台中打印纯文本的风险,但如果使用 Array,则不会打印数组的内容而是打印其内存位置。
序列化
什么是 Java 序列化
序列化是把对象改成可以存到磁盘或通过网络发送到其他运行中的Java 虚拟机的二进制格式的过程, 并可以通过反序列化恢复对象状态. Java 序列化API给开发人员提供了一个标准机制, 通过java.io.Serializable 和 java.io.Externalizable 接口, ObjectInputStream及ObjectOutputStream 处理对象序列化. Java 程序员可自由选择基于类结构的标准序列化或是他们自定义的二进制格式,通常认为后者才是最佳实践, 因为序列化的二进制文件格式成为类输出 API的一部分, 可能破坏 Java 中私有和包可见的属性的封装.
如何序列化
让 Java中的类可以序列化很简单. 你的 Java 类只需要实现 java.io.Serializable 接口, JVM 就会把 Object对象按默认格式序列化. 让一个类是可序列化的需要有意为之. 类可序列会可能为是一个长期代价, 可能会因此而限制你修改或改变其实现.当你通过实现添加接口来更改类的结构时, 添加或删除任何字段可能会破坏默认序列化, 这可以通过自定义二进制格式使不兼容的可能性最小化,但仍需要大量的努力来确保向后兼容性。序列化如何限制你更改类的能力的一个示例是 SerialVersionUID。如果不显式声明SerialVersionUID, 则 JVM 会根据类结构生成其结构, 该结构依赖于类实现接口和可能更改的其他几个因素。假设你新版本的类文件实现的另一个接口, JVM 将生成一个不同的 SerialVersionUID 的,当你尝试加载旧版本的程序序列化的旧对象时, 你将获得无效类异常 InvalidClassException。
Java 中的可序列化接口和可外部接口之间的区别是什么?
Externalizable给我们提供 writeExternal() 和 readExternal() 方法, 这让我们灵活地控制 Java 序列化机制, 而不是依赖于Java 的默认序列化。 正确实现 Externalizable 接口可以显著提高应用程序的性能。
public interface Serializable {
}
public interface Externalizable extends java.io.Serializable {
/**
* The object implements the writeExternal method to save its contents
* by calling the methods of DataOutput for its primitive values or
* calling the writeObject method of ObjectOutput for objects, strings,
* and arrays.
*
* @serialData Overriding methods should use this tag to describe
* the data layout of this Externalizable object.
* List the sequence of element types and, if possible,
* relate the element to a public/protected field and/or
* method of this Externalizable class.
*
* @param out the stream to write the object to
* @exception IOException Includes any I/O exceptions that may occur
*/
void writeExternal(ObjectOutput out) throws IOException;
/**
* The object implements the readExternal method to restore its
* contents by calling the methods of DataInput for primitive
* types and readObject for objects, strings and arrays. The
* readExternal method must read the values in the same sequence
* and with the same types as were written by writeExternal.
*
* @param in the stream to read data from in order to restore the object
* @exception IOException if I/O errors occur
* @exception ClassNotFoundException If the class for an object being
* restored cannot be found.
*/
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
serialVersionUID的作用
SerialVersionUID是一个用来标识Serializable类版本的唯一标识符。它的作用是在序列化和反序列化过程中用来验证类的版本一致性,确保反序列化过程中的类的版本和序列化时的版本是一致的,以避免出现类版本不一致导致的问题。如果在反序列化时发现类的版本不一致,就会抛出InvalidClassException异常。因此,SerialVersionUID是用来确保类的版本一致性的重要机制。
序列化时,某些变量不希望被序列化怎么办
如果你不希望任何字段是对象的状态的一部分, 然后声明它静态或瞬态(trasient)根据你的需要, 这样就不会是在 Java 序列化过程中被包含在内。
IO相关
同步 异步 阻塞 非阻塞
同步和异步指的是:当前线程是否需要等待方法调用执行完毕。
阻塞和非阻塞指的是:当前接口数据还未准备就绪时,线程是否被阻塞挂起
同步&异步其实是处于框架这种高层次维度来看待的,而阻塞&非阻塞往往针对底层的系统调用方面来抉择,也就是说两者是从不同维度来考虑的。
这四个概念两两组合,会形成4个新的概念,如下:
同步阻塞:客户端发送请求给服务端,此时服务端处理任务时间很久,则客户端则被服务端堵塞了,所以客户端会一直等待服务端的响应,此时客户端不能做其他任何事,服务端也不会接受其他客户端的请求。这种通信机制比较简单粗暴,但是效率不高。
同步非阻塞:客户端发送请求给服务端,此时服务端处理任务时间很久,这个时候虽然客户端会一直等待响应,但是服务端可以处理其他的请求,过一会回来处理原先的。这种方式很高效,一个服务端可以处理很多请求,不会在因为任务没有处理完而堵着,所以这是非阻塞的。
异步阻塞:客户端发送请求给服务端,此时服务端处理任务时间很久,但是客户端不会等待服务器响应,它可以做其他的任务,等服务器处理完毕后再把结果响应给客户端,客户端得到回调后再处理服务端的响应。这种方式可以避免客户端一直处于等待的状态,优化了用户体验,其实就是类似于网页里发起的ajax异步请求。
异步非阻塞:客户端发送请求给服务端,此时服务端处理任务时间很久,这个时候的任务虽然处理时间会很久,但是客户端可以做其他的任务,因为他是异步的,可以在回调函数里处理响应;同时服务端是非阻塞的,所以服务端可以去处理其他的任务,如此,这个模式就显得非常的高效了。
什么是BIO
BIO : 同步并阻塞 ,服务器实现一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,没处理完之前此线程不能做其他操作(如果是单线程的情况下,我传输的文件很大呢?),当然可以通过线程池机制改善。
BIO方式 适用于连接数目比较小且固定的架构 ,这种方式对服务器资源要求比较高,并发局限于应用中JDK1.4以前的唯一选择,但程序直观简单易理解。
什么是NIO
NIO : 同步非阻塞 ,服务器实现一个连接一个线程,即客户端发送的连接请求都会注册到多路复用器上,多复用器轮询到连接有I/O请求时才启动一个线程进行处理。
NIO方式 适用于连接数目多且连接比较短(轻操作)的架构 ,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4之后开始支持。
什么是AIO?
AIO : 异步非阻塞 ,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由操作系统先完成了再通知服务器应用去启动线程进行处理,AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用操作系统参与并发操作,编程比较复杂,JDK1.7之后开始支持。
AIO属于NIO包中的类实现,其实 IO主要分为BIO和NIO ,AIO只是附加品,解决IO不能异步的实现在以前很少有Linux系统支持AIO,Windows的IOCP就是该AIO模型。但是现在的服务器一般都是支持AIO操作
字节流和字符流的介绍
- 字节流继承inputStream和OutputStream
- 字符流继承自InputSteamReader和OutputStreamWriter
字符流和字节流的使用非常相似,但是实际上字节流的操作不会经过缓冲区(内存)而是直接操作文本本身的,而字符流的操作会先经过缓冲区(内存)然后通过缓冲区再操作文件
在选择流类型时,需要考虑到处理的数据类型。如果处理的是文本数据,应选择字符流;如果处理的是二进制数据或非文本数据,应选择字节流。
JavaWEB
什么是网络编程
网络编程的本质是多台计算机之间的数据交换。数据传递本身没有多大的难度,不就是把一个设备中的数据发送给其他设备,然后接受另外一个设备反馈的数据。现在的网络编程基本上都是基于请求/响应方式的,也就是一个设备发送请求数据给另外一个,然后接收另一个设备的反馈。
在网络编程中,发起连接程序,也就是发送第一次请求的程序,被称作客户端(Client),等待其他程序连接的程序被称作服务器(Server)。客户端程序可以在需要的时候启动,而服务器为了能够时刻相应连接,则需要一直启动。例如以打电话为例,首先拨号的人类似于客户端,接听电话的人必须保持电话畅通类似于服务器。连接一旦建立以后,就客户端和服务器端就可以进行数据传递了,而且两者的身份是等价的。在一些程序中,程序既有客户端功能也有服务器端功能,最常见的软件就是QQ、微信这类软件了。
网络编程中的两个主要问题是如何解决的
- 一个是如何准确的定位网络上一台或多台主机
- 另一个就是找到主机后如何可靠高效的进行数据传输,
在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠(TCP)的或非可靠(UDP)的数据传输机制,这是网络编程的主要对象,一般不需要关心!P层是如何处理数据的。
目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在雲要服务时向服务器提 出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也 能及时得到服务。
网络协议是什么
在计算机网络要做到井井有条的交换数据,就必须遵守一些事先约定好的规则,比如交换数据的格式、是否需要发送一个应答信息。这些规则被称为网络协议。
介绍下OSI七层和TCP/IP四层的关系
为了更好地促进互联网的研究和发展,国际标准化组织ISO在1985 年指定了网络互联模型。OSI 参考模型(Open System Interconnect Reference Model),具有 7 层结构
应用层:各种应用程序协议,比如HTTP、HTTPS、FTP、SOCKS安全套接字协议、DNS域名系统、GDP网关发现协议等等。
表示层:加密解密、转换翻译、压缩解压缩,比如LPP轻量级表示协议。
会话层:不同机器上的用户建立和管理会话,比如SSL安全套接字层协议、TLS传输层安全协议、RPC远程过程调用协议等等。
传输层:接受上一层的数据,在必要的时候对数据进行分割,并将这些数据交给网络层,保证这些数据段有效到达对端,比如TCP传输控制协议、UDP数据报协议。
网络层:控制子网的运行:逻辑编址、分组传输、路由选择,比如IP、IPV6、SLIP等等。
数据链路层:物理寻址,同时将原始比特流转变为逻辑传输路线,比如XTP压缩传输协议、PPTP点对点隧道协议等等。
物理层:机械、电子、定时接口通信信道上的原始比特流传输,比如IEEE802.2等等。
而且在消息通信的过程中具体的执行流程为:
网络传输的数据其实会通过这七层协议来进行数据的封装和拆解
TCP原理
三次握手:
1.第一次握手:客户端将标志位syn重置为1,随机产生seq=a,并将数据包发送给服务端
2.第二次握手:服务端收到syn=1知道客户端请求连接,服务端将syn和ACK都重置为1,ack=a+1,随机产一个值seq=b,并将数据包发送给客户端,服务端进入syn_RCVD状态。
3.第三次握手:客户端收到确认后,检查ack是否为a+1,ACK是否为1,若正确将ACK重置为1,将ack改为b+1,然后将数据包发送给服务端服务端检查ack与ACK,若都正确,就建立连接,进入ESTABLISHEN.
四次挥手:
1.开始双方都处于连接状态
2.客户端进程发出FIN报文,并停止发送数据,在报文中FIN结束标志为1,seq为a连接状态下发送给服务器的最后一个字节的序号+1,报文发送结束后,客户端进入FIN-WIT1状态。
3.服务端收到报文,向客户端发送确认报文,ACK=1,seq为b服务端给客户端发送的最后字节的序号+1,ack=a+1,发送后客户端进入close-wait状态,不再发送数据,但服务端发送数据客户端一九可以收到(城为半关闭状态)。
4.客户端收到服务器的确认报文后,客户端进入fin-wait2状态进行等待服务器发送第三次的挥手报文。
5.服务端向fin报文FIN=1ACK=1,seq=c(服务器向客户端发送最后一个字节序号+1),ack=b+1,发送结束后服务器进入last-ack状态等待最后的确认。
6.客户端收到是释放报文后,向服务器发送确认报文进入time-wait状态,后进入close
7.服务端收到确认报文进入close状态。