1. 遍历ArrayList时如何正确移除一个元素
错误写法示例一:
public static void remove(ArrayList<String> list) {
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if (s.equals("bb")) {
list.remove(s);
}
}
}
错误写法示例二:
public static void remove(ArrayList<String> list) {
for (String s : list) {
if (s.equals("bb")) {
list.remove(s);
}
}
}
要分析产生上述错误现象的原因唯有翻一翻jdk的ArrayList源码,先看下ArrayList中的remove方法(注意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是怎么实现的:
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
按一般执行路径会走到else路径下最终调用faseRemove方法:
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
// Let gc do its work
}
可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。针对错误写法一,在遍历第二个元素字符串bb时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也是字符串bb)至当前位置,导致下一次循环遍历时后一个字符串bb并没有遍历到,所以无法删除。 针对这种情况可以倒序删除的方式来避免:
public static void remove(ArrayList<String> list) {
for (int i = list.size() - 1; i >= 0; i--) {
String s = list.get(i);
if (s.equals("bb")) {
list.remove(s);
}
}
}
因为数组倒序遍历时即使发生元素删除也不影响后序元素遍历。
而错误二产生的原因却是foreach写法是对实际的Iterable、hasNext、next方法的简写,问题同样处在上文的fastRemove方法中,可以看到第一行把modCount变量的值加一,但在ArrayList返回的迭代器(该代码在其父类AbstractList中):
public Iterator<E> iterator() {
return new Itr();
}
这里返回的是AbstractList类内部的迭代器实现private class Itr implements Iterator,看这个类的next方法:
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
第一行checkForComodification方法:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法把修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或foreach的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。
public static void remove(ArrayList<String> list) {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("bb")) {
it.remove();
}
}
}
2. 说一说ArrayList 的扩容机制吧
ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。
public boolean add(E e) {
//扩容
ensureCapacityInternal(size + 1);
// Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果最小需要空间比elementData的内存空间要大,则需要扩容
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 获取elementData数组的内存空间长度
int oldCapacity = elementData.length;
// 扩容至原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//校验容量是否够
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//若预设值大于默认的最大值,检查是否溢出
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用Arrays.copyOf方法将elementData数组指向新的内存空间
//并将elementData的数据复制到新的内存空间
elementData = Arrays.copyOf(elementData, newCapacity);
}
3. HashMap是怎么解决哈希冲突的
Hashmap解决hash冲突,使用的是链地址法,即数组+链表的形式来解决。put执行首先判断table[i]位置,如果为空就直接插入,不为空判断和当前值是否相等,相等就覆盖,如果不相等的话,判断是否是红黑树节点,如果不是,就从table[i]位置开始遍历链表,相等覆盖,不相等插入。
4. String 类的常用方法都有那些?
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空白。
- split():分割字符串,返回一个分割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串长度。
- toLowerCase():将字符串转成小写字母。
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比较。
5. 简述Java中的集合
- Collection下:List系(有序、元素允许重复)和Set系(无序、元素不重复) “ set根据equals和hashcode判断,一个对象要存储在Set中,必须重写equals和hashCode方法 ”
- Map下:HashMap线程不同步;TreeMap线程同步
- Collection系列和Map系列:Map是对Collection的补充,两个没什么关系
“ 后面的问题,大家可以先自己独立思考一下。 另外我把所有Java相关的面试题和答案都整理出来了,需要的私信,即可免费获取哦!
6. EnumSet是什么?
7. 创建String对象的不同方式有哪些?
8. 为何Map接口不继承Collection接口?
9. HashMap和Hashtable的区别
10. ArrayList和HashMap默认大小?
11. LinkedList的是单向链表还是双向?
12. 当一个集合被作为参数传递给一个函数时,如何才可以确保函数不能修改它?
13. 在Java中,HashMap是如何工作的?
14. TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?
15. HashSet和TreeSet有什么区别?
16. String是不可变的有什么好处?
17. Java集合类框架的最佳实践有哪些?
18. HashSet是如何保证不重复的
19. HashMap的实现原理
20. Iterater和ListIterator之间有什么区别?
21. 队列和栈是什么,列出它们的区别?
22. HashMap线程安全吗?
23. 写一段代码在遍历 ArrayList 时移除一个元素
24. 有没有有顺序的Map实现类,如果有,他们是怎么保证有序的
25. ArrayList,Vector,LinkedList的存储性能和特性
26. 什么是字符串池?
27. 哪些集合类提供对元素的随机访问?
28. hashCode()和equals()方法有何重要性?
29. 我们能否使用任何类作为Map的key?
30. # 总结
31. 集合框架中的泛型有什么优点?
32. ArrayList和Vector有何异同点?
33. HashMap,HashTable,ConcurrentHash的共同点和区别
34. Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()?
35. Java中的同步集合与并发集合有什么区别
36. String str="i"与 String str=new String("i")一样吗?
37. 下面这条语句一共创建了多少个对象:String s="a"+"b"+"c"+"d"
38. 如果想用Object作为hashMap的Key?;
39. Java 中操作字符串都有哪些类?它们之间有什么区别?
40. 谈谈线程池阻塞队列吧~
41. Java集合框架是什么?说出一些集合框架的优点?
42. 与Java集合框架相关的有哪些最好的实践?
43. TreeMap底层?
44. ArrayList和LinkedList有何区别?
45. 我们如何从给定集合那里创建一个synchronized的集合?
46. Java中怎么打印数组?
47. 当一个集合被作为参数传递给一个函数时,如何才可以确保函数不能修改它?
48. 如何决定选用HashMap还是TreeMap?
49. HashMap总结
50. 说出ArrayList,LinkedList的存储性能和特性
51. Collections类是什么?
52. Comparator和Comparable的区别?
53. Iterator和ListIterator的区别是什么?
54. List、Map、Set三个接口,存取元素时,各有什么特点?
55. Comparable和Comparator接口有何区别?
56. Map接口提供了哪些不同的集合视图?
57. 哪些集合类是线程安全的?哪些不安全?
58. 通过迭代器fail-fast属性,你明白了什么?
59. HashMap和HashTable有何不同?
60. 为何Iterator接口没有具体的实现?
61. 如何实现集合排序?
62. 如何比较两个字符串?
63. 讲讲红黑树的特点?
64. 遍历一个List有哪些不同的方式?
65. 集合框架里实现的通用算法有哪些?
66. UnsupportedOperationException是什么?
67. 为何没有像Iterator.add()这样的方法,向集合中添加元素?
68. 如何实现数组和 List之间的转换?
69. Collection与Collections的区别是什么?
70. LinkedHashMap的应用,底层,原理
71. ArrayList 和 HashMap 的默认大小是多数?
72. HashMap自动扩容
73. HashMap在JDK1.7和JDK1.8中有哪些不同?
74. 什么是String,它是什么数据类型?
75. Set里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用==还是equals()?它们有何区别?
76. ArrayList和Vector的区别
77. 在迭代一个集合的时候,如何避免ConcurrentModificationException?
78. 如何判断两个String是否相等?
79. LinkedHashMap和PriorityQueue的区别
80. String s = new String("xyz");创建了几个StringObject?是否可以继承String类?
81. HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
82. Collections.sort和Arrays.sort的实现原理
83. 为何迭代器没有一个方法可以直接获取下一个元素,而不需要移动游标?
84. 如何打印数组内容
85. 大写的O是什么?举几个例子?
86. 为什么HashMap中String、Integer这样的包装类适合作为key?
87. 如何将String转换为byte array,反过来呢?
88. BlockingQueue是什么?
89. 阻塞队列的实现,ArrayBlockingQueue的底层实现?
90. 如何分割一个String?
91. Array和ArrayList有何区别?什么时候更适合用Array?
92. 如何让一个字符串变成小写或大写形式?
93. 说一下HashSet的实现原理?
94. 迭代器 Iterator 是什么?怎么用,有什么特点?
95. fail-fast与fail-safe有什么区别?
96. ArrayList和LinkedList区别?
97. String、StringBuffer和StringBuilder区别(类似上一题)
98. 怎么确保一个集合不能被修改?
99. Iterator是什么?
100. ArrayList和LinkedList的区别?
101. ArrayList集合加入1万条数据,应该怎么提高效率
102. 为什么要引入SpringBuffer、StringBuilder两种字符串处理类?
103. HashMap的扩容操作是怎么实现的?
104. 什么是Java优先级队列(Priority Queue)?
105. ArrayList和Array有什么区别?
106. ConcurrentHashMap和Hashtable的区别?
107. Comparable和Comparator接口是什么?
108. HashMap 的长度为什么是2的幂次方,以及其他常量定义的含义~
109. ConcurrenHashMap 原理?1.8 中为什么要用红黑树?
110. Java集合框架的基础接口有哪些?
111. Enumeration和Iterator接口的区别?
112. 我们如何对一组对象进行排序?
113. WeakHashMap与HashMap的区别是什么?
114. 如何将String转换为char,反过来呢?
115. 并发集合类是什么?
116. poll()方法和remove()方法区别?
117. Java中的集合及其继承关系
118. 为何Collection不从Cloneable和Serializable接口继承?
119. HashMap的put方法的具体流程?
120. TreeMap的实现原理
121. 如何对Object的list排序
122. Array 和 ArrayList 有何区别?
需要上述最新面试资料的小伙伴们,关注后私信“需要”,即可免费获取哦!