✅作者简介:热爱国学的Java后端开发者,修心和技术同步精进。
🍎个人主页:Java Fans的博客
🍊个人信条:不迁怒,不贰过。小知识,大智慧。
💞当前专栏:Java面试题总结
✨特色专栏:国学周更-心性养成之路
🥭本文内容:Java Web高级面试题(二)
更多内容点击👇
Java Web高级面试题(一)
本文目录
- Q53. Java 序列化中如果有些字段不想进行序列化,怎么办?
- Q54. 说说List,Set,Map三者的区别?
- Q55. Hashtable与Hashmap的区别
- Q56. arraylist linkedlist vector 区别
- Q57. Java 集合部分都有哪些接口,主要体现了哪些设计模式?
- Q58. Java集合框架是什么?说出一些集合框架的优点?
- Q59. 集合框架中的泛型有什么优点?
- Q60. Java集合框架的基础接口有哪些?
- Q61. 为何Collection不从Cloneable和Serializable接口继承?
- Q62. 为何Map接口不继承Collection接口?
- Q63. 什么是迭代器(Iterator)?
- Q64. Iterator和ListIterator的区别是什么?
- Q65. Java中的HashMap的工作原理是什么?
- Q66. HashMap和Hashtable有什么区别?
- Q67. 如何决定选用HashMap还是TreeMap?
- Q68. ArrayList和Vector有何异同点?
- Q69. Array和ArrayList有何区别?什么时候更适合用Array?
- Q70. 面向对象的特征有哪些方面?
- Q71. 访问修饰符 public,private,protected,以及不写(默认)时的区别?
- Q72. 构造器(constructor)是否可被重写(override)?
- Q73. List、Set、Map 是否继承自 Collection 接口?
- Q74. 阐述 ArrayList、Vector、LinkedList 的存储性能和特性。
- Q75. Collection 和 Collections 的区别?
- Q76. List、Map、Set 三个接口存取元素时,各有什么特点?
- Q77. TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?
- Q78. Java 中如何实现序列化,有什么意义?
- Q79. 简述一下面向对象的“六原则一法则”。
- Q80. a.hashCode() 有什么用?与 a.equals(b) 有什么关系?
- Q81. poll() 方法和 remove() 方法的区别?
- Q82. Java 中 LinkedHashMap 和 PriorityQueue 的区别是什么?
- Q83. ArrayList 与 LinkedList 的不区别?
- Q84. Java 中的 TreeMap 是采用什么树实现的?
- Q85. Hashtable与HashMap有什么不同之处?
- Q86. java 中的 HashSet,内部是如何工作的?
- Q87. 有没有可能两个不相等的对象有有相同的 hashcode?
- Q88. 两个相同的对象会有不同的的 hash code 吗?
- Q89. Java 中,Comparator 与 Comparable 有什么不同?
- Q90. 说出几点 Java 中使用 Collections 的最佳实践
- Q91. 什么是 java 序列化,如何实现 java 序列化?
- Q92. 并发集合和普通集合如何区别?
- Q93. List 的三个子类的特点
- Q94. List 和Map、Set 的区别
- Q95. Collection 框架中实现比较要实现什么接口
- Q96. ArrayList 和 Vector 的区别
- Q97. List 和 Map 区别?
- Q98. List, Set, Map 是否继承自 Collection 接口?
- Q99. 说出 ArrayList,Vector, LinkedList 的存储性能和特性
- Q100. 为什么集合类没有实现Cloneable和Serializable接口?
- Q101. 什么是迭代器 (Iterator)
- Q102. Iterator和ListIterator 的区别是什么?
- Q103. Java中的HashMap的工作原理是什么?
- Q104. hashCode()和equals() 方法的重要性体现在什么地方?
- Q105. 什么是Java优先级队列(Priority Queue)?
- Q106.什么是xml,使用 xml 的优缺点,xml 的解析器有哪几种,分别有什么区别?
- Q107. session 共享怎么做的(分布式如何实现 session 共享)?
Q53. Java 序列化中如果有些字段不想进行序列化,怎么办?
对于不想进行序列化的变量,使用 transient 关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
Q54. 说说List,Set,Map三者的区别?
List(对付顺序的好帮手): List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象;
Set(注重独一无二的性质): 不允许重复的集合。不会有多个元素引用相同的对象。
Map(用Key来搜索的专家): 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。
Q55. Hashtable与Hashmap的区别
继承不同,hashtable继承自dictionary,hashmap继承自abstractmap。
Hashtable: 生成一个新的,空的hashtable,使用默认的capacity容量为11,factor增长因子为0.75, 其实就是因为这个put方法是synchronized的,所以可以保证其线程安全。
Hashmap:允许null key/value,线程不安全
另外,ConcurrentHashMap所谓线程安全是如果没有哈希冲突使用compareAndSwapObject方式新增节点,如果哈希冲突的时候锁住哈希冲突的节点,这样新增的节点是线程安全的,而 ConcurrentHashMap又不像hashtable那样整个put方法被锁定,所以性能比hashtable要好,因为这样不影响其他节点的插入和读取。(concurrenthashmap也不允许空键值,抛空指针异常,CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。)
Q56. arraylist linkedlist vector 区别
- 对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
- 在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
- LinkedList不支持高效的随机元素访问。
- ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
Q57. Java 集合部分都有哪些接口,主要体现了哪些设计模式?
Java 集合部分主要有Collection、List、Set、Map、Comparator、Iterator 等,主要体现的设计模式是策略模式和迭代模式
策略模式主要体现在每个接口有不同的实现,可以完成互换,如List 接口下有ArrayList 和LinkedList,在不同的场景下可以互换。
迭代模式主要体现在Iterator 的实现,为不同的数据存储方式(数组、链表、散列表等)提供了统一的访问方式。
Comparator 体现的设计模式是策略模式,即不改变对象自身,而使用一个策略对象去改变它的行为。
注:策略模式的优缺点是什么:
优点:(1)将具体算法逻辑与客户类分离,(2)避免了大量的if else 判断
缺点:(1)每个算法一个类,产生了太多的类,(2)客户端要知道所有的策略类,以便决定使用哪一个。
Q58. Java集合框架是什么?说出一些集合框架的优点?
每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector、Stack、HashTable和Array。随着集合的广泛使用,Java1.2提出了囊括所有集合接口、实现和算法的集合框架。在保证线程安全的情况下使用泛型和并发集合类,Java已经经历了很久。它还包括在Java并发包中,阻塞接口以及它们的实现。集合框架的部分优点如下:
(1)使用核心集合类降低开发成本,而非实现我们自己的集合类。
(2)随着使用经过严格测试的集合框架类,代码质量会得到提高。
(3)通过使用JDK附带的集合类,可以降低代码维护成本。
(4)复用性和可操作性。
Q59. 集合框架中的泛型有什么优点?
Java1.5引入了泛型,所有的集合接口和实现都大量地使用它。泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。泛型也使得代码整洁,我们不需要使用显式转换和instanceOf操作符。它也给运行时带来好处,因为不会产生类型检查的字节码指令。
Q60. Java集合框架的基础接口有哪些?
Collection为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java平台不提供这个接口任何直接的实现。
Set是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌。
List是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List更像长度动态变换的数组。
Map是一个将key映射到value的对象.一个Map不能包含重复的key:每个key最多只能映射一个value。
一些其它的接口有Queue、Dequeue、SortedSet、SortedMap和ListIterator。
Q61. 为何Collection不从Cloneable和Serializable接口继承?
Collection接口指定一组对象,对象即为它的元素。如何维护这些元素由Collection的具体实现决定。例如,一些如List的Collection实现允许重复的元素,而其它的如Set就不允许。很多Collection实现有一个公有的clone方法。然而,把它放到集合的所有实现中也是没有意义的。这是因为Collection是一个抽象表现。重要的是实现。
当与具体实现打交道的时候,克隆或序列化的语义和含义才发挥作用。所以,具体实现应该决定如何对它进行克隆或序列化,或它是否可以被克隆或序列化。
在所有的实现中授权克隆和序列化,最终导致更少的灵活性和更多的限制。特定的实现应该决定它是否可以被克隆和序列化。
Q62. 为何Map接口不继承Collection接口?
Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map继承Collection毫无意义,反之亦然。
Map包含key-value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范。
Q63. 什么是迭代器(Iterator)?
Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object Obj)删除,可以通过迭代器的remove()方法删除。
Q64. Iterator和ListIterator的区别是什么?
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
Q65. Java中的HashMap的工作原理是什么?
在Java中最常用的两种结构是数组和模拟指针(引用),几乎所有的数据结构都可以利用这两种来组合实现,HashMap也是如此。实际上HashMap是一个“链表散列”,如下是它数据结构:最左侧是一个数组,数组中的每一个元素都是一个链表,链表的每一个元素都是entry。
HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。
Q66. HashMap和Hashtable有什么区别?
1、HashMap是非线程安全的,HashTable是线程安全的。
2、HashMap的键和值都允许有null值存在,而HashTable则不行。
3、因为线程安全的问题,HashMap效率比HashTable的要高。
4、Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
一般现在不建议用HashTable, ①是HashTable是遗留类,内部实现很多没优化和冗余。②即使在多线程环境下,现在也有同步的ConcurrentHashMap替代,没有必要因为是多线程而用HashTable。
Q67. 如何决定选用HashMap还是TreeMap?
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
Q68. ArrayList和Vector有何异同点?
ArrayList和Vector在很多时候都很类似。
(1)两者都是基于索引的,内部由一个数组支持。
(2)两者维护插入的顺序,我们可以根据插入顺序来获取元素。
(3)ArrayList和Vector的迭代器实现都是fail-fast的。
(4)ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。
以下是ArrayList和Vector的不同点。
(1)Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。
(2)ArrayList比Vector快,它因为有同步,不会过载。
(3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。
Q69. Array和ArrayList有何区别?什么时候更适合用Array?
Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
Array是指定大小的,而ArrayList大小是固定的。
Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。尽管ArrayList明显是更好的选择,但也有些时候Array比较好用。
(1)如果列表的大小已经指定,大部分情况下是存储和遍历它们。
(2)对于遍历基本数据类型,尽管Collections使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢。
(3)如果你要使用多维数组,使用[][]比List
Q70. 面向对象的特征有哪些方面?
面向对象的特征主要有以下几个方面:
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段(如果不能理解请阅读阎宏博士的《Java 与模式》或《设计模式精解》中关于桥梁模式的部分)。
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别,明显全自动洗衣机封装更好因此操作起来更简单;我们现在使用的智能手机也是封装得足够好的,因为几个按键就搞定了所有的事情)。
多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。 态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当 A 系统访问 B 系统提供的服务时,B系统有多种提供服务的方式,但一切对 A 系统来说都是透明的(就像电动剃须刀是 A 系统,它的供电系统是 B 系统,B 系统可以使用电池供电或者用交流电,甚至还有可能是太阳能,A 系统只会通过 B 类对象调用供电的方法,但并不知道供电系统的底层实现是什么,究竟通过何种方式获得了动力)。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1). 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2). 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
Q71. 访问修饰符 public,private,protected,以及不写(默认)时的区别?
修饰符 | 当前类 | 同 包 | 子 类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
类的成员不写访问修饰时默认为 default。默 认对于同一个包中的其他类相当于公开( public),对于不是同一个包中的其他类相当于私有( private)。 受保护(protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。Java 中,外部类的修饰符只能是 public 或默认,类的成员(包括内部类)的修饰符可以是以上四种。
Q72. 构造器(constructor)是否可被重写(override)?
构造器不能被继承,因此不能被重写,但可以被重载。
Q73. List、Set、Map 是否继承自 Collection 接口?
List、Set 是,Map 不是 。Map 是键值对映射容器,与 List 和 Set 有明显的区别,而 Set 存储的零散的元素且不允许有重复元素(数学中的集合也是如此), List 是线性结构的容器,适用于按数值索引访问元素的情形。
Q74. 阐述 ArrayList、Vector、LinkedList 的存储性能和特性。
ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据 快 而 插入数据慢, Vector 中的方法由于添加了 synchronized 修饰,因此 Vector 是 线程安全的容器,但性能上较 ArrayList 差,因此已经是 Java 中的遗留容 器 。 LinkedList 使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。Vector 属于遗留容器( Java 早期的版本中提供的容器,除此之外, Hashtable、 Dictionary、 BitSet、 Stack、 Properties 都是遗留容器),已经不推荐使用,但是由于 ArrayList 和 LinkedListed 都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类 Collections 中的 synchronizedList 方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。
Q75. Collection 和 Collections 的区别?
Collection 是一个接口,它是 Set、 List 等容器的父接口;
Collections 是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。
Q76. List、Map、Set 三个接口存取元素时,各有什么特点?
List 以特定索引来存取元素,可以有重复元素 。
Set 不能存放重复元素(用对象的equals()方法来区分元素是否重复) 。
Map 保存键值对( key-value pair)映射,映射关系可以是一对一或多对一。 Set 和 Map 容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理论存取时间复杂度为 O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的键( key)构成排序树从而达到排序和去重的效果。
Q77. TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?
TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。
TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进行排序。
Collections 工具类的 sort 方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是 Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用( Java 中对函数式编程的支持)。
Q78. Java 中如何实现序列化,有什么意义?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。
要实现序列化,需要让一个类实现 Serializable 接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过 writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过 readObject 方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆。
Q79. 简述一下面向对象的“六原则一法则”。
六原则:
- 单一职责原则:一个类只做它该做的事情。
- 开闭原则:软件实体应当对扩展开放,对修改关闭。
- 依赖倒转原则:面向接口编程。
- 里氏替换原则:任何时候都可以用子类型替换掉父类型。
- 接口隔离原则:接口要小而专,绝不能大而全。
- 合成聚合复用原则:优先使用聚合或合成关系复用代码。
一法则:
- 迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。
Q80. a.hashCode() 有什么用?与 a.equals(b) 有什么关系?
hashCode() 方法是相应对象整型的 hash 值 。 它常用于基于 hash 的集合类,如 Hashtable、 HashMap、LinkedHashMap 等等。 它与 equals() 方法关系特别紧密。根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具有相同的 hash code。
Q81. poll() 方法和 remove() 方法的区别?
poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。
Q82. Java 中 LinkedHashMap 和 PriorityQueue 的区别是什么?
PriorityQueue 保证最高或者最低优先级的 的 元 素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序 。 当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。
Q83. ArrayList 与 LinkedList 的不区别?
最明显的区别是 ArrrayList 底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构书链表,不支持随机访问 。 使用下标访问一个元素, ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
Q84. Java 中的 TreeMap 是采用什么树实现的?
Java中的TreeMap是使用红黑树实现的。
Q85. Hashtable与HashMap有什么不同之处?
a) Hashtable 是 JDK 1 遗留下来的类,而 HashMap 是后来增加的 。
b) Hashtable 是同步的,比较慢,但 HashMap 没有同步策略,所以会更快 。
c) Hashtable 不允许有个空的 key,但是 HashMap 允许出现一个 null key。
Q86. java 中的 HashSet,内部是如何工作的?
HashSet 的内部采用 HashMap 来实现 。由于 Map 需要 key 和 value,所以所有 key 的都有一个默认 value。 类似于 HashMap, HashSet 不允许重复的key,只允许有一个 null key,意思就是 HashSet 中只允许存储一个 null 对象 。
Q87. 有没有可能两个不相等的对象有有相同的 hashcode?
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突 。相等 hashcode 值的规定只是说如果两个对象相等,必须有相同的 hashcode 值,但是没有关于不相等对象的任何规定。
Q88. 两个相同的对象会有不同的的 hash code 吗?
不能。hashcode是计算对象的散列值;对象相同的时候,计算对象散列值的方法也一定是相同的。
Q89. Java 中,Comparator 与 Comparable 有什么不同?
Comparable 接口用于定义对象的自然顺序,而 comparator 通常用于定义用户定制的顺序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。
Q90. 说出几点 Java 中使用 Collections 的最佳实践
a)使用正确的集合类,例如,如果不需要同步列表,使用 ArrayList 而不是Vector。
b)优先使用并发集合,而不是对集合进行同步 。 并发集合提供更好的可扩展性 。
c)使用接口代表和访问集合,如使用 List 存储 ArrayList,使用 Map 存储 HashMap 等等 。
d)使用迭代器来循环集合 。
e)使用集合的时候使用泛型 。
Q91. 什么是 java 序列化,如何实现 java 序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
序 列 化 的 实 现 : 将 需 要 被 序 列 化 的 类 实 现 Serializable 接 口 , 该 接 口 没 有 需 要 实 现 的 方 法 , implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流。
Q92. 并发集合和普通集合如何区别?
并发集合常见的有 ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque 等。并发集合位 于 java.util.concurrent 包 下 , 是 jdk1.5 之 后 才 有 的 , 主 要 作 者 是 Doug Lea (http://baike.baidu.com/view/3141057.htm)完成的。
在 java 中有普通集合、同步(线程安全)的集合、并发集合。普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性。线程安全集合仅仅是给集合添加了 synchronized 同步锁,严重牺牲了性能,而且对并发的效率就更低了,并发集合则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率。
Q93. List 的三个子类的特点
ArrayList 底层结构是数组,底层查询快,增删慢。
LinkedList 底层结构是链表型的,增删快,查询慢。
voctor 底层结构是数组 线程安全的,增删慢,查询慢。
Q94. List 和Map、Set 的区别
-
结构特点
List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合;
List 中存储的数据是有顺序,并且允许重复;
Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的,
Set 中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 决定,位置是固定的(Set 集合根据 hashcode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的); -
实现类
List 接口有三个实现类(LinkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢;ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不便于插入删除;Vector:基于数组实现,线程安全的,效率低)。
Map 接口有三个实现类(HashMap:基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和 null 键;HashTable:线程安全,低效,不支持 null 值和 null 键;LinkedHashMap:是 HashMap 的一个子类,保存了记录的插入顺序;SortMap 接口:TreeMap,能够把它保存的记录根据键排序,默认是键值的升序排序)。
Set 接口有两个实现类(HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重写 equals()和 hashCode()方法;LinkedHashSet:继承与 HashSet,同时又基于 LinkedHashMap 来进行实现,底层使用的是 LinkedHashMp)。 -
区别
List 集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过list.get(i)方法来获取集合中的元素;Map 中的每一个元素包含一个键和一个值,成对出现,键对象不可以重复,值对象可以重复;
Set 集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如 TreeSet 类,可以按照默认顺序,也可以通过实现 Java.util.Comparator接口来自定义排序方式。
Q95. Collection 框架中实现比较要实现什么接口
comparable/comparator
Q96. ArrayList 和 Vector 的区别
这两个类都实现了List 接口(List 接口继承了Collection 接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,,并且其中的数据是允许重复的,这是 HashSet 之类的集合的最大不同处,HashSet 之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素(本来题目问的与hashset 没有任何关系,但为了说清楚ArrayList 与Vector 的功能,我们使用对比方式,更有利于说明问题)。
接着才说ArrayList 与 Vector 的区别,这主要包括两个方面:.
(1) 同步性:
Vector 是线程安全的,也就是说是它的方法之间是线程同步的,而 ArrayList 是线程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
(2) 数据增长:
ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList 与Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector 默认增长为原来两倍,而 ArrayList 的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的 1.5 倍)。ArrayList 与Vector 都可以设置初始的空间大小,Vector 还可以设置增长的空间大小,而 ArrayList 没有提供设置增长空间的方法。
总结:即 Vector 增长原来的一倍,ArrayList 增加原来的 0.5 倍。
Q97. List 和 Map 区别?
一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List 中存储的数据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。
Q98. List, Set, Map 是否继承自 Collection 接口?
List,Set 是,Map 不是
Q99. 说出 ArrayList,Vector, LinkedList 的存储性能和特性
ArrayList 和Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector 由于使用了 synchronized 方法(线程安全),通常性能上较ArrayList 差,而LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
LinkedList 也是线程不安全的,LinkedList 提供了一些方法,使得 LinkedList 可以被当作堆栈和队列来使用。
Q100. 为什么集合类没有实现Cloneable和Serializable接口?
集合类接口指定了一组叫做元素的对象。 集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。
Q101. 什么是迭代器 (Iterator)
Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合的元素。
克隆 (cloning) 或者是序列化 (serialization) 的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。
Q102. Iterator和ListIterator 的区别是什么?
Iterator 可用来遍历 Set 和 List 集合,但是 ListIterator 只能用来遍历 List 。
Iterator 对集合只能是前向遍历, ListIterator 既可以前向也可以后向。
ListIterator 实现了 Iterator 接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
Q103. Java中的HashMap的工作原理是什么?
Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap 需要一个hash函数,它使用hashCode()和equals()方法从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。
如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing) 。
Q104. hashCode()和equals() 方法的重要性体现在什么地方?
Java中的HashMap使用hashCode() 和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap 的精确性和正确性是至关重要的。
Q105. 什么是Java优先级队列(Priority Queue)?
PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(naturalorder) 排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。
PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue 不是线程安全的,入队和出队的时间复杂度是O(log(n))。
Q106.什么是xml,使用 xml 的优缺点,xml 的解析器有哪几种,分别有什么区别?
xml是一种可扩展性标记语言,支持自定义标签(使用前必须预定义)使用DTD和XML Schema 标准化XML 结构。
优点:用于配置文件,格式统一,符合标准;用于在互不兼容的系统间交互数据,共享数据方便;
缺点:xml 文件格式复杂,数据传输占流量,服务端和客户端解析 xml 文件占用大量资源且不易维护。
Xml 常用解析器有 2 种,分别是:DOM 和 SAX;主要区别在于它们解析xml 文档的方式不同。使用 DOM 解析,xml 文档以 DOM树形结构加载入内存,而 SAX 采用的是事件模型。
Q107. session 共享怎么做的(分布式如何实现 session 共享)?
问题描述:一个用户在登录成功以后会把用户信息存储在 session 当中,这时 session 所在服务器为server1,那么用户在 session 失效之前如果再次使用 app,那么可能会被路由到 server2,这时问题来了,server 没有该用户的session,所以需要用户重新登录,这时的用户体验会非常不好,所以我们想如何实现多台 server 之间共享 session, 让用户状态得以保存。
1)服务器实现的 session 复制或 session 共享,这类型的共享 session 是和服务器紧密相关的,比如 webSphere 或 JBOSS 在搭建集群时候可以配置实现session 复制或session 共享,但是这种方式有一个致命的缺点,就是不好扩展和移植,比如我们更换服务器,那么就要修改服务器配置。
2)利用成熟的技术做session 复制,比如12306 使用的gemfire,比如常见的内存数据库如redis 或memorycache,这类方案虽然比较普适,但是严重依赖于第三方,这样当第三方服务器出现问题的时候,那么将是应用的灾难。
3)将 session 维护在客户端,很容易想到就是利用 cookie,但是客户端存在风险,数据不安全,而且可以存放的数据量比较小,所以将session 维护在客户端还要对 session 中的信息加密。
我们实现的方案可以说是第二种方案和第三种方案的合体,可以利用 gemfire 实现 session 复制共享,还可以将session 维护在redis 中实现 session 共享,同时可以将 session 维护在客户端的 cookie 中,但是前提是数据要加密。这三种方式可以迅速切换,而不影响应用正常执行。我们在实践中,首选 gemfire 或者 redis 作为 session 共享的载体,一旦session 不稳定出现问题的时候,可以紧急切换 cookie 维护 session 作为备用,不影响应用提供服务。
这里主要讲解 redis 和 cookie 方案,gemfire 比较复杂大家可以自行查看 gemfire 工作原理。利用 redis 做session 共享,首先需要与业务逻辑代码解耦,不然 session 共享将没有意义,其次支持动态切换到客户端 cookie 模式。redis 的方案是,重写服务器中的 HttpSession 和 HttpServletRequest,首先实现 HttpSession 接口,重写 session的所有方法,将 session 以 hash 值的方式存在redis 中,一个session 的 key 就是 sessionID,setAtrribute 重写之后就是更新 redis 中的数据,getAttribute 重写之后就是获取 redis 中的数据,等等需要将 HttpSession 的接口一一实现。
实现了 HttpSesson,那么我们先将该 session 类叫做 MySession(当然实践中不是这么命名的),当 MySession出现之后问题才开始,怎么能在不影响业务逻辑代码的情况下,还能让原本的request.getSession()获取到的是MySession,而不是服务器原生的session。 这里,我决定重写服务器的HttpServletRequet,这里先称为MyRequest,但是这可不是单纯的重写,我需要在原生的 request 基础上重写,于是我决定在 filter 中,实现 request 的偷梁换柱,我的思路是这样的,MyRequest 的构建器,必须以 request 作为参数,于是我在 filter中将服务器原生的 request(也有可能是框架封装过的request) , 当做参数 new 出来一个 MyRequest ,并且 MyRequest 也实现了HttpServletRequest 接口,其实就是对原生 request 的一个增强,这里主要重写了几个 request 的方法,但是最重要的是重写了 request.getSession(),写到这里大家应该都明白为什么重写这个方法了吧,当然是为了获取 MySession,于是这样就在filter 中,偷偷的将原生的request 换成MyRequest 了,然后再将替换过的request传入chan.doFilter(), 这样 filter 时候的代码都使用的是 MyRequest 了,同时对业务代码是透明的,业务代码获取 session 的方法仍然是request.getSession(),但其实获取到的已经是 MySession 了,这样对 session 的操作已经变成了对redis 的操作。这样实现的好处有两个,第一开发人员不需要对 session 共享做任何关注,session 共享对用户是透明的;第二,filter 是可配置的,通过filter 的方式可以将 session 共享做成一项可插拔的功能,没有任何侵入性。
这个时候已经实现了一套可插拔的session 共享的框架了,但是我们想到如果 redis 服务出了问题,这时我们该怎么办呢,于是我们延续 redis 的想法,想到可以将 session 维护在客户端内(加密的 cookie),当然实现方法还是一样的,我们重写 HttpSession 接口,实现其所有方法,比如 setAttribute 就是写入 cookie,getAttribute 就是读取cookie,我们可以将重写的 session 称作 MySession2,这时怎么让开发人员透明的获取到MySession2 呢,实现方法还是在filter 内偷梁换柱,在 MyRequest 加一个判断,读取 sessionType 配置,如果 sessionType 是 redis 的,那么 getSession 的时候获取到的是MySession,如果 sessionType 是 coolie 的,那么 getSession 的时候获取到的是MySession2,以此类推,用同样的方法就可以获取到 MySession 3,4,5,6 等等。
这样两种方式都有了,那么我们怎实现两种 session 共享方式的快速切换呢,刚刚我提到一个 sessionType,这是用来决定获取到session 的类型的,只要变换sessionType 就能实现两种session共享方式的切换,但是sessionType 必须对所有的服务器都是一致的,如果不一致那将会出现比较严重的问题,我们目前是将 sessionType 维护在环境变量里,如果要切换 sessionType 就要重启每一台服务器,完成 session 共享的转换,但是当服务器太多的时候将是一种灾难。而且重启服务意味着服务的中断,所以这样的方式只适合服务器规模比较小,而且用户量比较少的情况,当服务器太多的时候,务必需要一种协调技术,能够让服务器能够及时获取切换的通知。基于这样的原因,我们选用zookeeper 作为配置平台,每一台服务器都会订阅 zookeeper 上的配置,当我们切换 sessionType 之后,所有服务器都会订阅到修改之后的配置,那么切换就会立即生效,当然可能会有短暂的时间延迟,但这是可以接受的。
码文不易,本篇文章就介绍到这里,如果想要学习更多Java系列知识,点击关注博主,博主带你零基础学习Java知识。与此同时,对于日常生活有困扰的朋友,欢迎阅读我的第四栏目:《国学周更—心性养成之路》,学习技术的同时,我们也注重了心性的养成。