diff --git a/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md b/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md index c660a745..2d6a2da5 100644 --- a/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md +++ b/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md @@ -1,229 +1,83 @@ + -- [一 Java中的值传递和引用传递(非常重要)](#一-java中的值传递和引用传递(非常重要)) - - [那么什么是值传递和应用传递呢?](#那么什么是值传递和应用传递呢?) - - [值传递和应用传递实例](#值传递和应用传递实例) - - [1. 值传递](#1-值传递) - - [2. 引用传递](#2-引用传递) - - [一些特殊的例子](#一些特殊的例子) - - [1. StringBuffer类型传递](#1-stringbuffer类型传递) - - [2. String类型传递](#2-string类型传递) - - [3. 一道类似的题目](#3-一道类似的题目) -- [二 ==与equals\(重要\)](#二-与equals重要) -- [三 hashCode与equals(重要)](#三-hashcode与equals(重要)) - - [hashCode()介绍](#hashcode()介绍) - - [为什么要有hashCode](#为什么要有hashcode) - - [hashCode()与equals()的相关规定](#hashcode()与equals()的相关规定) +- [List、Set、Map 三者的区别及总结](#list、set、map-三者的区别及总结) +- [Arraylist 与 LinkedList 区别](#arraylist-与-linkedlist-区别) +- [ArrayList 与 Vector 区别](#arraylist-与-vector-区别) +- [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别) +- [HashSet 和 HashMap 区别](#hashset-和-hashmap-区别) +- [HashMap 和 ConcurrentHashMap 的区别](#hashmap-和-concurrenthashmap-的区别) +- [HashSet如何检查重复](#hashset如何检查重复) + - [hashCode()与equals()的相关规定](#hashcode()与equals()的相关规定) + - [==与equals的区别](#与equals的区别) +- [comparable 和 comparator 的区别](#comparable-和-comparator-的区别) + - [Comparator 定制排序](#comparator-定制排序) + - [重写 compareTo 方法实现按年龄来排序](#重写-compareto-方法实现按年龄来排序) +- [如何实现数组与List的相互转换](#如何实现数组与list的相互转换) +- [如何求 ArrayList 集合的交集 并集 差集 去重复并集](#如何求-arraylist-集合的交集-并集-差集-去重复并集) +- [HashMap 的工作原理及代码实现](#hashmap-的工作原理及代码实现) +- [ConcurrentHashMap 的工作原理及代码实现](#concurrenthashmap-的工作原理及代码实现) +- [集合框架底层数据结构总结](#集合框架底层数据结构总结) + - [Collection](#collection) + - [1. List](#1-list) + - [2. Set](#2-set) + - [Map](#map) +- [集合的选用](#集合的选用) +- [集合的常用方法](#集合的常用方法) -## 一 Java中的值传递和引用传递(非常重要) +## List、Set、Map 三者的区别及总结 +- **List:对付顺序的好帮手** -**首先要明确的是:“对象传递(数组、类、接口)是引用传递,原始类型数据(整型、浮点型、字符型、布尔型)传递是值传递。”** + List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象 +- **Set: 注重独一无二的性质** + + 不允许重复的集合。不会有多个元素引用相同的对象。 -### 那么什么是值传递和应用传递呢? +- **Map: 用 Key 来搜索的专家** + + 使用键值对存储。Map 会维护与 Key 有关联的值。两个 Key 可以引用相同的对象,但 Key 不能重复,典型的 Key 是 String 类型,但也可以是任何对象。 + -**值传递**是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。) +## Arraylist 与 LinkedList 区别 +Arraylist 底层使用的是数组(存读数据效率高,插入删除特定位置效率低),LinkedList 底层使用的是双向循环链表数据结构(插入,删除特定位置效率特别高)。 -**引用传递**是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。) +学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用 LinkedList.一般程序只用 Arraylist 就够用了,因为一般数据量都不会蛮大,Arraylist 是使用最多的集合类。 -有时候面试官不是单纯问你“Java中是值传递还是引用传递”是什么啊,骚年?而是给出一个例子,然后让你写出答案,这种也常见在笔试题目中!所以,非常重要了,请看下面的例子: +## ArrayList 与 Vector 区别 +Vector 类的所有方法都是同步的。可以由两个线程安全地访问一个 Vector 对象、但是一个线程访问 Vector +,代码要在同步操作上耗费大量的时间。Arraylist不是同步的,所以在不需要同步时建议使用Arraylist。 -### 值传递和应用传递实例 +## HashMap 和 Hashtable 的区别 +1. HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。 -#### 1. 值传递 +2. 因为线程安全的问题,HashMap 要比 HashTable 效率高一点,HashTable 基本被淘汰。 +3. HashMap 允许有null值的存在,而在 HashTable 中 put 进的键值只要有一个null,直接抛出NullPointerException。 -```java -public static void main(String[] args) { - int num1 = 10; - int num2 = 20; +Hashtable 和 HashMap 有几个主要的不同:线程安全以及速度。仅在你需要线程安全的时候使用Hashtable,而如果你使用Java5或以上的话,请使用ConcurrentHashMap吧! - swap(num1, num2); +## HashSet 和 HashMap 区别 +![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536) - System.out.println("num1 = " + num1); - System.out.println("num2 = " + num2); -} +## HashMap 和 ConcurrentHashMap 的区别 +[HashMap 与 ConcurrentHashMap 的区别](https://blog.csdn.net/xuefeng0707/article/details/40834595) -public static void swap(int a, int b) { - int temp = a; - a = b; - b = temp; +1. ConcurrentHashMap 对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用 lock 锁进行保护,相对于 HashTable 的 synchronized 锁的粒度更精细了一些,并发性能更好,而 HashMap 没有锁机制,不是线程安全的。(JDK1.8之后 ConcurrentHashMap 启用了一种全新的方式实现,利用CAS算法。) +2. HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。 - System.out.println("a = " + a); - System.out.println("b = " + b); -} -``` +## HashSet如何检查重复 +当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他加入的对象的 hashcode 值作比较,如果没有相符的 hashcode ,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让加入操作成功。(摘自我的Java启蒙书《Head fist java》第二版) -**结果:** +### hashCode()与equals()的相关规定 +1. 如果两个对象相等,则 hashcode 一定也是相同的 +2. 两个对象相等,对两个 equals 方法返回true +3. 两个对象有相同的 hashcode 值,它们也不一定是相等的 +4. 综上,equals 方法被覆盖过,则hashCode方法也必须被覆盖 +5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。 -``` -a = 20 -b = 10 -num1 = 10 -num2 = 20 -``` - -**解析:** - -在swap方法中,a、b的值进行交换,并不会影响到num1、num2。因为,a、b中的值,只是从num1、num2的复制过来的。 -也就是说,a、b相当于num1、num2的副本,副本的内容无论怎么修改,都不会影响到原件本身。 - -#### 2. 引用传递 - -```java -public static void main(String[] args) { - int[] arr = {1,2,3,4,5}; - - change(arr); - - System.out.println(arr[0]); -} - -public static void change(int[] array) { -//将数组的第一个元素变为0 - array[0] = 0; -} -``` - -**结果:** - -``` -1 -0 -``` - -**解析:** - -无论是主函数,还是change方法,操作的都是同一个地址值对应的数组。 。因此,外部对引用对象的改变会反映到所有的对象上。 - -### 一些特殊的例子 - -#### 1. StringBuffer类型传递 - -```java - // 测试引用传递:StringBuffer - @org.junit.Test - public void method1() { - StringBuffer str = new StringBuffer("公众号:Java面试通关手册"); - System.out.println(str); - change1(str); - System.out.println(str); - } - - public static void change1(StringBuffer str) { - str = new StringBuffer("abc");//输出:“公众号:Java面试通关手册” - //str.append("欢迎大家关注");//输出:公众号:Java面试通关手册欢迎大家关注 - //str.insert(3, "(编程)");//输出:公众号(编程):Java面试通关手册 - - } -``` - -**结果:** - -``` -公众号:Java面试通关手册 -公众号:Java面试通关手册 -``` - -**解析:** - - -很多要这个时候要问了:StringBuffer创建的明明也是对象,那为什么输出结果依然是原来的值呢? - -因为在`change1`方法内部我们是新建了一个StringBuffer对象,所以`str`指向了另外一个地址,相应的操作也同样是指向另外的地址的。 - -把上面的代码解析成字节码分析就比较直观了,change1()方法解析成字节码如下: -``` - 0 new #2 - 3 dup - 4 ldc #8 - 6 invokespecial #4 > - 9 astore_0 -10 return -``` -我们知道,每个方法都是一个栈帧,每个栈帧都维护有一个局部变量表,分析第6步,发现调用了stringBuffer方法的初始化,也就是执行了```new StringBuffer("abc");```方法,接下来第9步是把栈顶的对象(也就是调用chang1方法传来的StringBuffer引用)加载进局部变量表弟一个slot中,接着方法就结束了,所以这个引用直接存到了change1的局部变量表中,而method1中输出的str还是自己的局部变量表中的引用,所以,change方法并不影响输出结果。 - -那么,如果将`change1`方法改成如下图所示,想必大家应该知道输出什么了,如果你还不知道,那可能就是我讲的有问题了,我反思(开个玩笑,上面程序中已经给出答案): - -``` - public static void change1(StringBuffer str) { - - str.append("欢迎大家关注"); - str.insert(3, "(编程)"); - - } -``` - - -#### 2. String类型传递 - -```java - // 测试引用传递:Sring - @org.junit.Test - public void method2() { - String str = new String("公众号:Java面试通关手册"); - System.out.println(str); - change2(str); - System.out.println(str); - } - - public static void change2(String str) { - // str="abc"; //输出:公众号:Java面试通关手册 - str = new String("abc"); //输出:公众号:Java面试通关手册 - } - -``` - -**结果:** - -``` -公众号:Java面试通关手册 -公众号:Java面试通关手册 -``` - -可以看到不论是执行`str="abc;"`还是`str = new String("abc");`str的输出的值都不变。 -按照我们上面讲“StringBuffer类型传递”的时候说的,`str="abc;"`应该会让str的输出的值都不变。为什么呢?因为String在创建之后是不可变的。 - -#### 3. 一道类似的题目 - -下面的程序输出是什么? -```java -public class Demo { - public static void main(String[] args) { - Person p = new Person("张三"); - - change(p); - - System.out.println(p.name); - } - - public static void change(Person p) { - Person person = new Person("李四"); - p = person; - } -} - -class Person { - String name; - - public Person(String name) { - this.name = name; - } -} -``` -很明显仍然会输出`张三`。因为`change`方法中重新创建了一个`Person`对象。 - -那么,如果把` change`方法改为下图所示,输出结果又是什么呢? - -```java - public static void change(Person p) { - p.name="李四"; - } -``` -答案我就不说了,我觉得大家如果认真看完上面的内容之后应该很很清楚了。 - -## 二 ==与equals(重要) +### ==与equals的区别 **==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。 @@ -259,45 +113,272 @@ public class test1 { - 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。 -## 三 hashCode与equals(重要) +## comparable 和 comparator 的区别 -面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” +- comparable 接口实际上是出自 java.lang 包 它有一个 compareTo(Object obj)方法用来排序 +- comparator 接口实际上是出自 java.util 包它有一个 compare(Object obj1, Object obj2)方法用来排序 -### hashCode()介绍 -hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。 +一般我们需要对一个集合使用自定义排序时,我们就要重写 compareTo 方法或 compare 方法,当我们需要对某一个集合实现两种排序方式,比如一个 song 对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写 compareTo 方法和使用自制的 Comparator 方法或者以两个 Comparator 来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的 Collections.sort(). -散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) +### Comparator 定制排序 +```java +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; -### 为什么要有hashCode +/** + * TODO Collections类方法测试之排序 + * @author Snailclimb + * @date 2017年11月20日 + * @version 1.8 + */ +public class CollectionsSort { + + public static void main(String[] args) { + + ArrayList arrayList = new ArrayList(); + arrayList.add(-1); + arrayList.add(3); + arrayList.add(3); + arrayList.add(-5); + arrayList.add(7); + arrayList.add(4); + arrayList.add(-9); + arrayList.add(-7); + System.out.println("原始数组:"); + System.out.println(arrayList); + // void reverse(List list):反转 + Collections.reverse(arrayList); + System.out.println("Collections.reverse(arrayList):"); + System.out.println(arrayList); +/* + * void rotate(List list, int distance),旋转。 + * 当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 + * list的前distance个元素整体移到后面。 + + Collections.rotate(arrayList, 4); + System.out.println("Collections.rotate(arrayList, 4):"); + System.out.println(arrayList);*/ + + // void sort(List list),按自然排序的升序排序 + Collections.sort(arrayList); + System.out.println("Collections.sort(arrayList):"); + System.out.println(arrayList); + + // void shuffle(List list),随机排序 + Collections.shuffle(arrayList); + System.out.println("Collections.shuffle(arrayList):"); + System.out.println(arrayList); + + // 定制排序的用法 + Collections.sort(arrayList, new Comparator() { + + @Override + public int compare(Integer o1, Integer o2) { + return o2.compareTo(o1); + } + }); + System.out.println("定制排序后:"); + System.out.println(arrayList); + } + +} + +``` +### 重写 compareTo 方法实现按年龄来排序 +```java +package map; + +import java.util.Set; +import java.util.TreeMap; + +public class TreeMap2 { + + public static void main(String[] args) { + // TODO Auto-generated method stub + TreeMap pdata = new TreeMap(); + pdata.put(new Person("张三", 30), "zhangsan"); + pdata.put(new Person("李四", 20), "lisi"); + pdata.put(new Person("王五", 10), "wangwu"); + pdata.put(new Person("小红", 5), "xiaohong"); + // 得到key的值的同时得到key所对应的值 + Set keys = pdata.keySet(); + for (Person key : keys) { + System.out.println(key.getAge() + "-" + key.getName()); + + } + } +} + +// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列 +// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他 +// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了 + +class Person implements Comparable { + private String name; + private int age; + + public Person(String name, int age) { + super(); + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + /** + * TODO重写compareTo方法实现按年龄来排序 + */ + @Override + public int compareTo(Person o) { + // TODO Auto-generated method stub + if (this.age > o.getAge()) { + return 1; + } else if (this.age < o.getAge()) { + return -1; + } + return age; + } +} +``` + + +## 如何实现数组与List的相互转换 +List转数组:toArray(arraylist.size()方法;数组转List:Arrays的asList(a)方法 +```java +List arrayList = new ArrayList(); + arrayList.add("s"); + arrayList.add("e"); + arrayList.add("n"); + /** + * ArrayList转数组 + */ + int size=arrayList.size(); + String[] a = arrayList.toArray(new String[size]); + //输出第二个元素 + System.out.println(a[1]);//结果:e + //输出整个数组 + System.out.println(Arrays.toString(a));//结果:[s, e, n] + /** + * 数组转list + */ + List list=Arrays.asList(a); + /** + * list转Arraylist + */ + List arrayList2 = new ArrayList(); + arrayList2.addAll(list); + System.out.println(list); +``` +## 如何求 ArrayList 集合的交集 并集 差集 去重复并集 +需要用到 List 接口中定义的几个方法: + +- addAll(Collection c) :按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾 +实例代码: +- retainAll(Collection c): 仅保留此列表中包含在指定集合中的元素。 +- removeAll(Collection c) :从此列表中删除指定集合中包含的所有元素。 +```java +package list; + +import java.util.ArrayList; +import java.util.List; + +/** + *TODO 两个集合之间求交集 并集 差集 去重复并集 + * @author Snailclimb + * @date 2017年11月21日 + * @version 1.8 + */ +public class MethodDemo { + + public static void main(String[] args) { + // TODO Auto-generated method stub + List list1 = new ArrayList(); + list1.add(1); + list1.add(2); + list1.add(3); + list1.add(4); + + List list2 = new ArrayList(); + list2.add(2); + list2.add(3); + list2.add(4); + list2.add(5); + // 并集 + // list1.addAll(list2); + // 交集 + //list1.retainAll(list2); + // 差集 + // list1.removeAll(list2); + // 无重复并集 + list2.removeAll(list1); + list1.addAll(list2); + for (Integer i : list1) { + System.out.println(i); + } + } + +} + +``` + +## HashMap 的工作原理及代码实现 + +[集合框架源码学习之HashMap(JDK1.8)](https://juejin.im/post/5ab0568b5188255580020e56) + +## ConcurrentHashMap 的工作原理及代码实现 + +[ConcurrentHashMap 实现原理及源码分析](http://www.cnblogs.com/chengxiao/p/6842045.html) -**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:** +## 集合框架底层数据结构总结 +### Collection + +#### 1. List + - Arraylist:数组(查询快,增删慢 线程不安全,效率高 ) + - Vector:数组(查询快,增删慢 线程安全,效率低 ) + - LinkedList:链表(查询慢,增删快 线程不安全,效率高 ) -当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。 +#### 2. Set + - HashSet(无序,唯一):哈希表或者叫散列集(hash table) + - LinkedHashSet:链表和哈希表组成 。 由链表保证元素的排序 , 由哈希表证元素的唯一性 + - TreeSet(有序,唯一):红黑树(自平衡的排序二叉树。) +### Map + - HashMap:基于哈希表的 Map 接口实现(哈希表对键进行散列,Map结构即映射表存放键值对) + - LinkedHashMap:HashMap 的基础上加上了链表数据结构 + - HashTable:哈希表 + - TreeMap:红黑树(自平衡的排序二叉树) + +## 集合的选用 +主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用 Map 接口下的集合,需要排序时选择 TreeMap ,不需要排序时就选择 HashMap 需要保证线程安全就选用 ConcurrentHashMap .当我们只需要存放元素值时,就选择实现 Collection 接口的集合,需要保证元素唯一时选择实现 Set 接口的集合比如 TreeSet 或 HashSet,不需要就选择实现 List 接口的比如 ArrayList 或 LinkedList,然后再根据实现这些接口的集合的特点来选用。 -### hashCode()与equals()的相关规定 - -1. 如果两个对象相等,则hashcode一定也是相同的 -2. 两个对象相等,对两个对象分别调用equals方法都返回true -3. 两个对象有相同的hashcode值,它们也不一定是相等的 -4. **因此,equals方法被覆盖过,则hashCode方法也必须被覆盖** -5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) - - - - -参考: - -[https://blog.csdn.net/zhzhao999/article/details/53449504](https://blog.csdn.net/zhzhao999/article/details/53449504) - -[https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html) - -[https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html) - -[https://www.cnblogs.com/Eason-S/p/5524837.html](https://www.cnblogs.com/Eason-S/p/5524837.html) +2018/3/11更新 +## 集合的常用方法 +今天下午无意看见一道某大厂的面试题,面试题的内容就是问你某一个集合常见的方法有哪些。虽然平时也经常见到这些集合,但是猛一下让我想某一个集合的常用的方法难免会有遗漏或者与其他集合搞混,所以建议大家还是照着API文档把常见的那几个集合的常用方法看一看。 +会持续更新。。。 +**参考书籍:** +《Head first java 》第二版 推荐阅读真心不错 (适合基础较差的) + 《Java核心技术卷1》推荐阅读真心不错 (适合基础较好的) + + 《算法》第四版 (适合想对数据结构的Java实现感兴趣的) +