Java 问题参考手册
Java中一个字符占多少个字节,扩展再问int, long, double占多少字节
String,StringBuilder,StringBuffer的区别?**
答: String * 字符串常量 * 创建后不可改变 * 对字符串的修改都需要回收再创建 * 通过:private final char value[] 实现,线程安全 * StringBuilder * 是变量,可改变 * 对变量的操作是直接操作创建的对象 * 父类:AbstractStringBuilder
StringBuffer
- 是变量,可改变
- 对变量的操作是直接操作创建的对象
- 父类:AbstractStringBuilder
- 对比
- 两个字符串变量拼接,String需要不断进行创建、操作;而StringBuilder和StringBuffer不需要。
- StringBuilder 不是线程安全的,StringBuffer是线程安全的,String Buffer的线程安全是通过对大部分的方法增加synchronized关键字实现的。
-
使用建议
-
String:少量字符串操作;
- StringBuilder:单线程情况下,在字符缓冲区进行大量的字符操作;
- StringBuffer:多线程情况下,在字符缓冲区进行大量的字符操作;
接口与抽象类的区别
答:
接口
- 用interface 关键字修饰
- 接口中方法默认问public abstract类型,必须是抽象类型
- 成员默认都是:public static final类型的,必须被显示初始化,只能是这种类型
- 接口没有构造方法,不能实例化
- 一个接口不能实现另一个接口,但可以继承多个其它接口
- 接口必须通过类来实现它的抽象方法
- 实现接口的类必须实现接口中得所有方法
- 类可以实现多个接口,实现接口的关键字为:implements
- 是一种行为规范,是对行为的抽象
抽象类
- 用abstract 关键字修饰
- 类中方法只能用public或者protected 修饰
- 类种的变量可以是各种类型
- 可以有自己的实现
- 不能直接创建对象,但是可以有构造方法,帮助子类实例化
- 子类必须实现父类的抽象方法
- 类只能继承一个直接的父类,继承类的关键字为:extends
- 是一种模板设计模式
- 是为了继承而存在的,是对一种事物的抽象
什么是java序列化,如何实现java序列化?(写一个实例)
String s = new String("abc");创建了几个 String Object
java中有哪些异常类,分别怎么使用
答:
- 主要分为两类:Error,Exception
- 都继承自:java.lang.Throwable
- 对于Error,程序无法处理了
例如:OutOfMemoryError、OutOfMemoryError等等, 这些异常发生时, java虚拟机一般会终止线程。
-
对于异常,分为:CheckedException和RuntimeException;
-
所有RuntimeException类及其子类的实例被称为Runtime异常,不属于该范畴的异常则被称为CheckedException.
-
CheckedException
-
如果没有处理checked异常,程序在编译时发生错误无法编译,这体现了Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。对Checked异常处理方法有两种:
1、当前方法知道如何处理该异常,则用try...catch块来处理。
2、当前方法不知道如何处理,则在定义该方法时声明抛出该异常。
比较熟悉的checked类有:
java.lang.ClassNotFoundException
java.lang.NoSuchMetodException
java.io.IOException
- RuntimeException
Runtime如除数是0和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们 比较常用的异常类
Java.lang.ArithmeticException
Java.lang.ArrayStoreExcetpion
Java.lang.ClassCastException
Java.lang.IndexOutOfBoundsException
Java.lang.NullPointerException
*异常处理
-
非检查异常表示无法让程序恢复运行的异常,导致这种异常的原因通常是由于执行了错误的操作。一旦出现错误,建议让程序终止。
-
受检查异常表示程序可以处理的异常。如果抛出异常的方法本身不处理或者不能处理它,那么方法的调用者就必须去处理该异常,否则 调用会出错,连编译也无法通过。
-
对于运行异常,建议不要用 try...catch...捕获处理,应该在程序开发调试的过程中尽量的避免,当然有一些必须要处理的,自己知道 了那个部分会出现异常,而这种异常你要把它处理的你想要的结果,例如:空值处理
Collection和Collections的区别是什么
常用的集合类有哪些?比如List如何排序?**
答: 集合的根有两个:Map Collection Vector
- 继承 AbstractList
- 实现 List
- 实现 RandomAccess
- 实现 Cloneable
- 实现 java.io.Serializable
- 线程安全
TreeSet
- 继承 AbstractSet
- 实现 NavigableSet
- 实现 Cloneable
- 实现 java.io.Serializable
ArrayList
- 继承 AbstractList
- 实现 List
- 实现 RandomAccess
- 实现 Cloneable
- 实现 java.io.Serializable
- 非线程安全
LinkedList
- 继承 AbstractSeqentialList
- 实现 List
- 实现 Deque
- 实现 Cloneable
- 实现 java.io.Serializable
- 非线程安全
ConcurrentLinkedQueue
- 继承 AbstractQueue
- 实现 Queue
- 实现 java.io.Serizlizable
- 线程安全
ConcurrentHashMap
- 继承 AbstractMap
- 实现 ConcurrentMap
- 实现 Serializable
- LinkedHashMap
- 继承 HashMap
- 实现 Map
- 非线程安全
HashMap
- 继承 AbstraceMap
- 实现 Map
- 实现 Cloneable
- 实现 Serializable
Hashtable
- 继承 Dictionary
- 实现 Map
- 实现 Cloneable
- 实现 java.io.Serializable
TreeMap
- 继承 AbstractMap
- 实现 NavigableMap
- 实现 Cloneable
- 实现 java.io.Serializable
List一般使用:Collections进行排序
排序时需要实现接口Comparator的compare方法,此方法中定义排序规则。
实现此方法内容有两种方式:
1. 在sort方法中直接实现,new一个Comparator
HashMap实现原理,如何保证HashMap的线程安全
答:
- HashMap本质上是:数组+链表组成的数据结构。
- 主干是一个Entry数组,Entry也是HashMap的基本组成单元。(在1.8中,有所不同,Node是主干,Node实现了Entry)Entry的默认大小是:16,
- 每个Entry 又是一个链表结构。
- 通过这种数组 + 链表方式,实现对对象的存储。
- HashMap的几个特点:
- 默认数组:16
- 默认加载因子:0.75
- 当存放对象个数大于:12时,自动进行扩容,重建HashMap
- 库容的容量为:2的次幂
- 实现HashMap线程安全有两种方式:
- 对HashMap增加Synchronized来实现线程安全。
- 用ConcurrentHashMap 替换HashMap,因为ConcurrentHashMap通过分段锁机制实现线程安全,并且比通过对HashMap增加Synchronized更加高效。
Hashmap 什么时候进行扩容
List,Set,Map三个接口,存取元素时,各有什么特点
Heap 和Stack 的区别
HashSet和HashTree的区别
HashSet的底层实现是什么
LinkedHashMap的底层实现是什么
为什么集合类没有实现Cloneable和Serializable接口
HashMap,Hashtable,ConcurrentHashMap的共同点与区别
*HashTable
底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
初始size为11,扩容:newsize = olesize*2+1
计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length
-
HashMap
底层数组+链表实现,可以存储null键和null值,线程不安全 初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂 扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入 插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容) 当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀 计算index方法:index = hash & (tab.length – 1)
-
HashMap的初始值还要考虑加载因子:
哈希冲突:若干Key的哈希值按数组大小取模后,如果落在同一个数组下标上,将组成一条Entry链,对Key的查找需要遍历Entry链上的每个元素执行equals()比较。 加载因子:为了降低哈希冲突的概率,默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定100/0.75=134的数组大小。 空间换时间:如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。
-
HashMap和Hashtable都是用hash算法来决定其元素的存储,因此HashMap和Hashtable的hash表包含如下属性:
容量(capacity):hash表中桶的数量 初始化容量(initial capacity):创建hash表时桶的数量,HashMap允许在构造器中指定初始化容量 尺寸(size):当前hash表中记录的数量 负载因子(load factor):负载因子等于“size/capacity”。负载因子为0,表示空的hash表,0.5表示半满的散列表,依此类推。轻负载的散列表具有冲突少、适宜插入与查询的特点(但是使用Iterator迭代元素时比较慢)
除此之外,hash表里还有一个“负载极限”,“负载极限”是一个0~1的数值,“负载极限”决定了hash表的最大填满程度。当hash表中的负载因子达到指定的“负载极限”时,hash表会自动成倍地增加容量(桶的数量),并将原有的对象重新分配,放入新的桶内,这称为rehashing。
HashMap和Hashtable的构造器允许指定一个负载极限,HashMap和Hashtable默认的“负载极限”为0.75,这表明当该hash表的3/4已经被填满时,hash表会发生rehashing。
“负载极限”的默认值(0.75)是时间和空间成本上的一种折中:
较高的“负载极限”可以降低hash表所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的操作(HashMap的get()与put()方法都要用到查询)
较低的“负载极限”会提高查询数据的性能,但会增加hash表所占用的内存开销
程序员可以根据实际情况来调整“负载极限”值。 ConcurrentHashMap
底层采用分段的数组+链表实现,线程安全
通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
Hashtable和HashMap都实现了Map接口,但是Hashtable的实现是基于Dictionary抽象类的。Java5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
HashMap基于哈希思想,实现对数据的读写。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来存储值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞时,对象将会储存在链表的下一个节点中。HashMap在每个链表节点中储存键值对对象。当两个不同的键对象的hashcode相同时,它们会储存在同一个bucket位置的链表中,可通过键对象的equals()方法来找到键值对。如果链表大小超过阈值(TREEIFY_THRESHOLD,8),链表就会被改造为树形结构。
在HashMap中,null可以作为键,这样的键只有一个,但可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示HashMap中没有该key,也可以表示该key所对应的value为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个key,应该用containsKey()方法来判断。而在Hashtable中,无论是key还是value都不能为null。
Hashtable是线程安全的,它的方法是同步的,可以直接用在多线程环境中。而HashMap则不是线程安全的,在多线程环境中,需要手动实现同步机制。
Hashtable与HashMap另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。
先看一下简单的类图:
从类图中可以看出来在存储结构中ConcurrentHashMap比HashMap多出了一个类Segment,而Segment是一个可重入锁。
ConcurrentHashMap是使用了锁分段技术来保证线程安全的。
锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap提供了与Hashtable和SynchronizedMap不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。
ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。
ArrayList和LinkedList内部的实现大致是怎样的?他们之间的区别和优缺点? **
答:
- ArrayList
- 基于动态数组的数据结构
- 访问元素及更新数据速度快
- LinkedList
- 基于链表的数据结构
- 因为要移动指针,所以访问和更新速度较ArrayList慢
- 删除和新增速度快
Java集合类框架的最佳实践有哪些
什么是迭代器Iterator
Iterator和ListIterator的区别是什么
final/finally/finalize的区别
12. synchronized 关键字的实现原理?**
答: //TODO: 每个对象天生都是一个监视器, monitorenter monitorexit
什么是IO?**
答:
- input output的缩写
- 是对数据的流入和流出的一种抽象
- 输入和输出都是针对流来说的
- 输入和输出可操作的最小单位是一个字节称作字节流
- 输入好输出可操作的最小单位是一个字符称作字符流
- 字节操作流以:stream结尾
- 字符操作流以:reader和writer结尾
- 处理纯文本数据时使用字符流,处理非纯文本时使用字节流。
- 纯文本数据:读用Reader,写用Writer
- 非纯文本数据:读用InputStream,写用OutputStream
什么是NIO?**
答:
- Non-blocking Input Output
- NIO是一种同步非阻塞的IO模型。
- 同步是指线程不断地轮询IO事件是否就绪
- 非阻塞就是指线程在等待IO的时候,可以同时做其它的任务
- 同步的核心是selector:selector代替了线程本身轮询IO事件,避免了阻塞同时减少了不必要的线程消耗
- 非阻塞的核心是通道和缓冲区:当IO事件就绪时,可以通过写到缓冲区,保证IO的成功,而无需线程阻塞式地等待
- 标准的IO是基于字节流和字符流进行操作的,而NIO是基于通道和缓冲区进行操作的,数据总是从通道读取到缓冲区,或者从缓冲区写入 到通道。
- NIO主要包含三个核心组件:channel,buffer,selector
channel叫做通道。是IO源与目标节点打开的链接,和传统的IO很相似,但是channel是双向的,主要有如下几种通道: * FileChannel:从文件读或者向文件写数据 * SocketChannel:以TCP来向网络两段读写数据 * ServerSocketChannel:监听客户端发起的连接,并为每个连接创建一个新的SocketChannel来进行数据读写 * DatagramChannel:以UDP协议来向网络连接的两端读写数据
使用channel的例子
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
buffer 缓冲区,是一个容器,是一个连接数组。channel提供从文件、网络读取数据的渠道,但是读取或者写入的数据都必须经过buffer。
常用Buffer的子类有: * ByteBuffer * IntBuffer * CharBuffer * LongBuffer * DoubleBuffer * FloatBuffer * ShortBuffer
这些buffer覆盖了能通过IO发送的基本数据类型:byte,char,int,float,double,long,short。 使用buffer读写数据的几个步骤: * 写入数据到buffer * 调用flip()方法 * 从 Buffer中读取数据 * 调用clear()方法或者compact()方法 * flip方法是进行模式切换(写模式切换到读模式) * clear方法,清空整个缓冲区,compact清除已经读过的数据
buffer的三个属性
- capacity
buffer的容量,只能存放:byte,long,char等类型,满了,需要清空
- position
写入数据时,表示当前的位置,初始值为:0,最大值为:capacity-1 读入数据时,表示某个特定位置 写模式切换为读模式,position重置为:0
- limit
写模式下,buffer的limit表示最多能往buffer里写多少数据。 写模式下:limit等于buffer的capacity 读模式下,limit表示最多能多多少数据 写模式切换为读模式时,limit设置为写模式下的position值
使用buffer的例子
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//申请一个48字节的buffer,也就是:capacity为48
ByteBuffer buf = ByteBuffer.allocate(48);
/** 向buffer中写数据 */
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
/** 从写模式切换到读模式,position会被设置为:0,limit设置为position设置为0之前的值 */
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // read 1 byte at a time
}
/** buffer 数据读完,清空buffer */
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
selector
选择器 用来轮询每个注册的channel,一旦发现channel有注册的事件发生,便获取事件然后进行处理。用一个单线程就可以管理多个通道,也就是管理多个连接,连接在真正有读写事件发生时,才会调用函数来进行读写,大大减少了系统开销,并且为每个连接都创建一个线程,不用去维护多个线程,避免了多线程之间的上下文切换导致的开销。
与Selector有关的一个关键类是:SelectionKey,一个SelectionKey表示一个到达的事件,这2个类构成了服务端处理业务的关键逻辑。
什么是BIO?**
答:
- block input output
- BIO是一种阻塞IO
什么是AIO?**
select/epoll的区别?**
答:
多路复用的原理?**
答: I/O多路复用技术通过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降底了系统的维护工作量,节省了系统资源.
C10K问题?
答: 最初的服务器都是基于进程/线程模型的,新到来一个TCP连接,就需要分配1个进程(或者线程)。而进程又是操作系统最昂贵的资源,一台机器无法创建很多进程。如果是C10K就要创建1万个进程,那么操作系统是无法承受的。如果是采用分布式系统,维持1亿用户在线需要10万台服务器,成本巨大,也只有Facebook,Google,雅虎才有财力购买如此多的服务器。这就是C10K问题的本质。
解决这一问题,主要思路有两个:一个是对于每个连接处理分配一个独立的进程/线程;另一个思路是用同一进程/线程来同时处理若干连接
1. 简单描述tls?**
3. 攻击网站主要有哪些手法? **
答:
sql注入,旁注,XSS跨站,COOKIE欺骗,DDOS,0day漏洞,社会工程学。
3. PreparedStatement与Statement的区别?
答:
- PreparedStatement继承自Statement,都是接口
- PreparedStatement可以使用占位符,是预编译的,批处理比Statement效率高
- PreparedStatement:表示预编译的 SQL 语句的对象
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象
- 在默认情况下,同一时间每个 Statement 对象只能打开一个 ResultSet 对象
** 12. 线程同步的方法?
答:
- synchronized关键字修饰 同步方法,同步代码块
如果aba和aab相等,给定两个字符串判断是否相等?
答: 先排序,在比较两个字符串是否相等; 遍历两个字符串,碰到相等的字符,则删除,最后判断两个字符串的长度