Java开发岗面试互联网大厂冲刺,每日10题Day3
Posted Java架构师-大仙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java开发岗面试互联网大厂冲刺,每日10题Day3相关的知识,希望对你有一定的参考价值。
今天大仙抽出一些时间整理了一下以前面试被问到的问题,也给出了答案也是希望仙友们能找到一份自己喜欢的工作,在文末也整理了一份资料给大家~~~
一.HashMap 和 Hashtable 有什么区别?
操作步骤/方法
1.历史原因不同。Hashtable是基于陈旧的Dictionary类的,HashMap是Java1.2引进的Map接口的一个实现。
2.也许最重要的不同是Hashtable的方法是同步的,而HashMap的方法不是。这就意味着,虽然你可以不用采取任何特殊的行为就可以在一个多线程的应用程序中用一个Hashtable,但你必须同样地为一个HashMap提供外同步。一个方便的方法就是利用Collections类的静态的synchronizedMap()方法,它创建一个线程安全的Map对象,并把它作为一个封装的对象来返回。这个对象的方法可以让你同步访问潜在的HashMap。这么做的结果就是当你不需要同步时,你不能切断Hashtable中的同步(比如在一个单线程的应用程序中),而且同步增加了很多处理费用。
3.只有HashMap可以让你将空值作为一个表的条目的key或value。HashMap中只有一条记录可以是一个空的key,但任意数量的条目可以是空的value。这就是说,如果在表中没有发现搜索键,或者如果发现了搜索键,但它是一个空的值,那么get()将返回null。如果有必要,用containKey()方法来区别这两种情况。
二.如何决定使用 HashMap 还是 TreeMap?
如何决定使用 HashMap 还是 TreeMap?
如果你需要得到一个有序的结果时就应该使用TreeMap(因为HashMap中元素的排列顺序是不固定的)。除此之外,由于HashMap有更好的性能,所以大多不需要排序的时候我们会使用HashMap。
拓展
1、HashMap 和 TreeMap 的实现
HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。
- HashMap(): 构建一个空的哈希映像
- HashMap(Map m): 构建一个哈希映像,并且添加映像m的所有映射
- HashMap(int initialCapacity): 构建一个拥有特定容量的空的哈希映像
- HashMap(int initialCapacity, float loadFactor): 构建一个拥有特定容量和加载因子的空的哈希映像
TreeMap:基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。
- TreeMap():构建一个空的映像树
- TreeMap(Map m): 构建一个映像树,并且添加映像m中所有元素
- TreeMap(Comparator c): 构建一个映像树,并且使用特定的比较器对关键字进行排序
- TreeMap(SortedMap s): 构建一个映像树,添加映像树s中所有映射,并且使用与有序映像s相同的比较器排序
2、HashMap 和 TreeMap 都是非线程安全
HashMap继承AbstractMap抽象类,TreeMap继承自SortedMap接口。
- AbstractMap抽象类:覆盖了equals()和hashCode()方法以确保两个相等映射返回相同的哈希码。如果两个映射大小相等、包含同样的键且每个键在这两个映射中对应的值都相同,则这两个映射相等。映射的哈希码是映射元素哈希码的总和,其中每个元素是Map.Entry接口的一个实现。因此,不论映射内部顺序如何,两个相等映射会报告相同的哈希码。
- SortedMap接口:它用来保持键的有序顺序。SortedMap接口为映像的视图(子集),包括两个端点提供了访问方法。除了排序是作用于映射的键以外,处理SortedMap和处理SortedSet一样。添加到SortedMap实现类的元素必须实现Comparable接口,否则您必须给它的构造函数提供一个Comparator接口的实现。TreeMap类是它的唯一一个实现。
3、TreeMap中默认是按照升序进行排序的,如何让他降序
通过自定义的比较器来实现。定义一个比较器类,实现Comparator接口,重写compare方法,有两个参数,这两个参数通过调用compareTo进行比较,而compareTo默认规则是:
- 如果参数字符串等于此字符串,则返回 0 值;
- 如果此字符串小于字符串参数,则返回一个小于 0 的值;
- 如果此字符串大于字符串参数,则返回一个大于 0 的值。
自定义比较器时,在返回时多添加了个负号,就将比较的结果以相反的形式返回,代码如下:
之后,通过MyComparator类初始化一个比较器实例,将其作为参数传进TreeMap的构造方法中:
这样,我们就可以使用自定义的比较器实现降序了
三.说一下 HashMap 的实现原理?
HashMap概述:HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap的数据结构:在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根绝hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。
需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
四.说一下 HashSet 的实现原理?
底层是基于HashMap实现的
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
/**
* 默认是构造器没有参数,并使用默认初始容量为16和加载因子0.75
*/
public HashSet() {
map = new HashMap<>();
}
/**
* 构造一个包含指定collection中的元素的新set。
*
* 实际底层使用默认的加载因子0.75和足以包含指定collection中所有元素的初始容量来创建一个HashMap。
* @param c 其中的元素将存放在此set中的collection。
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
/**
* 以指定的initialCapacity构造一个空的HashSet。
*
* 实际底层以相应的参数及加载因子loadFactor为0.75构造一个空的HashMap。
* @param initialCapacity 初始容量。
*/
public HashSet(int initialCapacity, float loadFactor)
{
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/**
* 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。此构造函数为包访问权限,不对外公开,
* 实际只是是对LinkedHashSet的支持。
*
* 实际底层会以指定的参数构造一个空LinkedHashMap实例来实现。
* @param initialCapacity 初始容量。
* @param loadFactor 加载因子。
* @param dummy 标记。
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}...
}
默认情况下采用的是 initial capacity为16,load factor 为 0.75
add方法:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
如果此 set 中尚未包含指定元素,则添加指定元素。更确切地讲,如果此 set 没有包含满足(enull ? e2null : e.equals(e2)) 的元素 e2,则向此 set 添加指定的元素 e。如果此 set 已包含该元素,则该调用不更改 set 并返回 false。但底层实际将将该元素作为 key 放入 HashMap。思考一下为什么?
由于 HashMap 的 put() 方法添加 key-value 对时,当新放入 HashMap 的 Entry 中 key 与集合中原有 Entry 的 key 相同(hashCode()返回值相等,通过 equals 比较也返回 true),新添加的 Entry 的 value 会将覆盖原来 Entry 的 value(HashSet 中的 value 都是PRESENT),但 key 不会有任何改变,因此如果向 HashSet 中添加一个已经存在的元素时,新添加的集合元素将不会被放入 HashMap中,原来的元素也不会有任何改变,这也就满足了 Set 中元素不重复的特性。
该方法如果添加的是在 HashSet 中不存在的,则返回 true;如果添加的元素已经存在,返回 false。其原因在于我们之前提到的关于 HashMap 的 put 方法。该方法在添加 key 不重复的键值对的时候,会返回 null。
/**
* 如果此set包含指定元素,则返回true。
* 更确切地讲,当且仅当此set包含一个满足(o==null ? e==null : o.equals(e))的e元素时,返回true。
*
* 底层实际调用HashMap的containsKey判断是否包含指定key。
* @param o 在此set中的存在已得到测试的元素。
* @return 如果此set包含指定元素,则返回true。
*/
public boolean contains(Object o) {
return map.containsKey(o);
}
/**
* 如果指定元素存在于此set中,则将其移除。更确切地讲,如果此set包含一个满足(o==null ? e==null : o.equals(e))的元素e,
* 则将其移除。如果此set已包含该元素,则返回true
*
* 底层实际调用HashMap的remove方法删除指定Entry。
* @param o 如果存在于此set中则需要将其移除的对象。
* @return 如果set包含指定元素,则返回true。
*/
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
五.ArrayList 和 LinkedList 的区别是什么?
工作中,大家是不是经常分不清楚,什么时候用ArrayList,什么时候用LinkedList?
所以我们应该弄清楚ArrayList和LinkedList之间的区别,然后才知道工作中该如何选型。
1、ArrayList和LinkedList查询之间的区别
首先,从名字就可以看出,ArrayList和LinkedList的区别,ArrayList是基于数组的,LinkedList是基于链表的。
从这一点,我们可以推理出来,ArrayList适合查询,LinkedList适合插入,但是这只是一个广泛的结论,我们应该了解的更细致一点。
比如,对于ArrayList,它真正的优点是按下标查询元素,相比于LinkedList,LinkedList也可以按下标查询元素,但是LinkedList需要对底层链表进行遍历,才能找到指定下标的元素,而ArrayList不用,所以这是ArrayList的优点。
但是,如果我们讨论的是获取第一个元素,或最后一个元素,ArrayList和LinkedList在性能上是没有区别的,因为LinkedList中有两个属性分别记录了当前链表中的头结点和尾结点,并不需要遍历链表。
以上,是对于ArrayList和LinkedList在查询方面的区别。
2、ArrayList和LinkedList插入之间的区别
我们再来研究一下在插入方面的区别:
ArrayList可以插入到指定下标位置,或者数组末尾,这种插入普通情况下是很快的,但是如果某次插入操作触发了扩容,那么本次插入就增加了额外的扩容成本。
对于LinkedList,如果是插在链表的头部或者是尾部都是很快的,因为LinkedList中有单独的属性记录的链表的头结点和尾结点,不过,如果是插在指定下标位置,那么就需要遍历链表找到指定位置,从而降低了效率。
但是,使用LinkedList是不用担心扩容问题的,链表是不需要扩容的。
综上:
1.默认情况下,比如调用ArrayList和LinkedList的add(e)方法,都是插入在最后,如果这种操作比较多,那么就用LinkedList,因为不涉及到扩容。
2.如果调用ArrayList和LinkedList的add(index, e)方法比较多,就要具体考虑了,因为ArrayList可能会扩容,LinkedList需要遍历链表,这两种到底哪种更快,是没有结论的,得具体情况具体分析。
3.还有,如果是插入场景比较少,但经常需要查询的话,查询分两种,第一种就是普通遍历,也就是经常需要对List中的元素进行遍历,那么这两种是区别不大的,遍历链表和遍历数组的区别,第二种就是经常需要按指定下标获取List中的元素,如果这种情况如果比较多,那么就用ArrayList
以上,是对ArrayList和LinkedList在查询和插入之间的分析和对比。
六.如何实现数组和 List 之间的转换?
1.List转换成为数组。(这里的List是实体是ArrayList)
调用ArrayList的toArray方法。
toArray
public T[] toArray(T[] a)返回一个按照正确的顺序包含此列表中所有元素的数组;返回数组的运行时类型就是指定数组的运行时类型。如果列表能放入指定的数组,则返回放入此列表元素的数组。否则,将根据指定数组的运行时类型和此列表的大小分配一个新的数组。
如果指定的数组能容纳列表并有剩余空间(即数组的元素比列表的多),那么会将数组中紧跟在集合末尾的元素设置为 null。这对确定列表的长度很有用,但只 在调用方知道列表中不包含任何 null 元素时才有用。
指定者:
接口 Collection 中的 toArray
指定者:
接口 List 中的 toArray
覆盖:
类 AbstractCollection 中的 toArray
参数:
a - 要存储列表元素的数组,如果它足够大的话;否则,它是一个为存储列表元素而分配的、具有相同运行时类型的新数组。
返回:
包含列表元素的数组。
抛出:
ArrayStoreException - 如果 a 的运行时类型不是此列表中每个元素的运行时类型的超类型。
具体用法:
List list = new ArrayList();
list.add("1");
list.add("2");
final int size = list.size();
String[] arr = (String[])list.toArray(new String[size]);
还可以使用这个方法
String[] userid = {"aa","bb","cc"};
List<String> userList = new ArrayList<String>();
Collections.addAll(userList, userid);
2.数组转换成为List
调用Arrays的asList方法.
asList
public static List asList(T… a)返回一个受指定数组支持的固定大小的列表。(对返回列表的更改会“直写”到数组。)此方法同 Collection.toArray 一起,充当了基于数组的 API 与基于 collection 的 API 之间的桥梁。返回的列表是可序列化的,并且实现了 RandomAccess。
此方法还提供了一个创建固定长度的列表的便捷方法,该列表被初始化为包含多个元素:
List stooges = Arrays.asList("Larry", "Moe", "Curly");
参数:
a - 支持列表的数组。
返回:
指定数组的列表视图。
另请参见:
Collection.toArray()
具体用法:
String[] arr = new String[] {"1", "2"};
List list = Arrays.asList(arr);
七.ArrayList 和 Vector 的区别是什么?
八.Array 和 ArrayList 有何区别?
2016-04-11 回答
ArrayList 的容量是该列表可包含的元素数。随着向 ArrayList 中添加元素,容量通过重新分配按需自动增加
ArrayList myAL = new ArrayList();
myAL.Add("Hello");
myAL.Add("World");
myAL.Add("!");
// Displays the properties and values of the ArrayList.
Console.WriteLine( "myAL" );
一个元素就是 Array 中的一个值。Array 的长度是它可包含的元素总数。Array 的秩是 Array 中的维
数。Array 中维度的下限是 Array 中该维度的起始索引,多维 Array 的各个维度可以有不同的界限。
int[] myIntArray = new int[5] { 1, 2, 3, 4, 5 };
Object[] myObjArray = new Object[5] { 26, 27, 28, 29, 30 };
// Prints the initial values of both arrays.
Console.WriteLine( "Initially," );
Console.Write( "integer array:" );
九.在 Queue 中 poll()和 remove()有什么区别?
相同点:都是返回第一个元素,并在队列中删除返回的对象。
不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。代码示例:Queue queue = new LinkedList();queue. offer(“string”); // addSystem. out. println(queue. poll());System. out. println(queue. remove());System. out. println(queue. size());
十.哪些集合类是线程安全的?
在集合框架中,有些类是线程安全的,这些都是jdk1.1中的出现的。在jdk1.2之后,就出现许许多多非线程安全的类。 下面是这些线程安全的同步的类:
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
- statck:堆栈类,先进后出
- hashtable:就比hashmap多了个线程安全
- enumeration:枚举,相当于迭代器
- 除了这些之外,其他的都是非线程安全的类和接口。
- 线程安全的类其方法是同步的,每次只能一个访问。是重量级对象,效率较低。
其他:
hashtable跟hashmap的区别
hashtable是线程安全的,即hashtable的方法都提供了同步机制;hashmap不是线程安全的,即不提供同步机制 ;hashtable不允许插入空值,hashmap允许!
戳此链接领取Java学习资料/面试资料
以上是关于Java开发岗面试互联网大厂冲刺,每日10题Day3的主要内容,如果未能解决你的问题,请参考以下文章
Java岗大厂面试百日冲刺Day43— Shrio1 (日积月累,每日三题)
Java岗大厂面试百日冲刺 - 日积月累,每日三题Day22—— 并发编程2
Java岗大厂面试百日冲刺 - 日积月累,每日三题Day21—— Linux命令