Java面试八股文——Java基础部分

目录

 Java 基础

1. JDK 和 JRE 有什么区别?

2. == 和 equals 的区别是什么?

3. 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?

4. final 在 Java 中有什么作用?

5. Java 中的 Math. round(-1. 5) 等于多少?

补充:Math提供了三个与取整有关的方法:ceil、floor、round

6. String 属于基础的数据类型吗?

7. Java 中操作字符串都有哪些类?它们之间有什么区别?

8. String str="i"与 String str=new String("i")一样吗?

9. 如何将字符串反转?

10. String 类的常用方法都有那些?

分类记忆:

11. 抽象类必须要有抽象方法吗?

12. 普通类和抽象类有哪些区别?

13. 抽象类能使用 final 修饰吗?

14. 接口和抽象类有什么区别?

15. Java 中 IO 流分为几种?

16. BIO、NIO、AIO 有什么区别?

17. Files的常用方法都有哪些?

18、java 中都有哪些引用类型?

19、在 Java 中,为什么不允许从静态方法中访问非静态变量?

20、说说Java Bean的命名规范

21、Java Bean 属性命名规范问题分析

22、在 Java 中,什么时候用重载,什么时候用重写?

23、举例说明什么情况下会更倾向于使用抽象类而不是接口?

24、实例化对象有哪几种方式

25、byte类型127+1等于多少

容器

26. Java 容器都有哪些?

27. Collection 和 Collections 有什么区别?

28. List、Set、Map 之间的区别是什么?

补充:更加具体的List和Set的区别

29. HashMap 和 Hashtable 有什么区别?

30. 如何决定使用 HashMap 还是 TreeMap?

31. 说一下 HashMap 的实现原理?

32. 说一下 HashSet 的实现原理?

33、HasmMap和HashSet的区别

34、hashcode是什么?有什么作用?

35. ArrayList 和 LinkedList 的区别是什么?

36. 如何实现数组和 List 之间的转换?

37. ArrayList 和 Vector 的区别是什么?

38. Array 和 ArrayList 有何区别?

39. 在 Queue 中 poll()和 remove()有什么区别?

补充一些区别:

40. 哪些集合类是线程安全的?

41. 迭代器 Iterator 是什么?

42. Iterator 怎么使用?有什么特点?

43. Iterator 和 ListIterator 有什么区别?

44. 怎么确保一个集合不能被修改?

其他:

45、set有哪些实现类?

46、队列和栈是什么?有什么区别?

47、Java8开始ConcurrentHashMap,为什么舍弃分段锁?


 Java 基础

1. JDK 和 JRE 有什么区别?

    JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。
    JRE:Java Runtime Environment 的简称,Java 运行环境,为 Java 的运行提供了所需环境。

具体来说 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了很多 Java 程序调试和分析的工具。简单来说:如果你需要运行 Java 程序,只需安装 JRE 就可以了,如果你需要编写 Java 程序,需要安装 JDK。

JDK中包含JRE,JDK中有一个名为jre的目录,里面包含两个文件夹bin和lib,bin就是JVM,lib就是JVM工作所需要的类库。

2. == 和 equals 的区别是什么?

对于基本类型和引用类型 == 的作用效果是不同的,如下所示:

    基本类型:比较的是值是否相同;
    引用类型:比较的是引用是否相同;

  1. 对于基本类型,==比较的是值;
  2. 对于引用类型,==比较的是地址;

而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

  1. equals不能用于基本类型的比较;
  2. 如果没有重写equals,equals就相当于==;
  3. 如果重写了equals方法,equals比较的是对象的内容;

3. 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?

不对,两个对象的 hashCode() 相同,equals() 不一定 true。

代码示例:

    String str1 = "通话";
    String str2 = "重地";
    System. out. println(String. format("str1:%d | str2:%d",  str1. hashCode(),str2. hashCode()));
    System. out. println(str1. equals(str2));

执行的结果:

    str1:1179395 | str2:1179395
     
    false

代码解读:很显然“通话”和“重地”的 hashCode() 相同,然而 equals() 则为 false,因为在散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。

4. final 在 Java 中有什么作用?

    final 修饰的类叫最终类,该类不能被继承。
    final 修饰的方法不能被重写。
    final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

(1)用来修饰一个引用

  1.  如果引用为基本数据类型,则该引用为常量,该值无法修改;
  2.  如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。
  3.  如果引用时类的成员变量,则必须当场赋值,否则编译会报错。

5. Java 中的 Math. round(-1. 5) 等于多少?

等于 -1,因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负 0.5 是直接舍弃。

补充:Math提供了三个与取整有关的方法:ceil、floor、round

(1)ceil:向上取整;

Math.ceil(11.3) = 12;

Math.ceil(-11.3) = 11;

(2)floor:向下取整;

Math.floor(11.3) = 11;

Math.floor(-11.3) = -12;

(3)round:四舍五入;

加0.5然后向下取整。

Math.round(11.3) = 11;

Math.round(11.8) = 12;

Math.round(-11.3) = -11;

Math.round(-11.8) = -12;

6. String 属于基础的数据类型吗?

String 不属于基础类型,基础类型有 8 种:byte、boolean、char、short、int、float、long、double,而 String 属于对象。

7. Java 中操作字符串都有哪些类?它们之间有什么区别?

操作字符串的类有:String、StringBuffer、StringBuilder。

String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

8. String str="i"与 String str=new String("i")一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String("i") 则会被分到堆内存中。

String str="i"会将起分配到常量池中,常量池中没有重复的元素,如果常量池中存中i,就将i的地址赋给变量,如果没有就创建一个再赋给变量。

String str=new String(“i”)会将对象分配到堆中,即使内存一样,还是会重新创建一个新的对象。

9. 如何将字符串反转?

使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。

示例代码:

    // StringBuffer reverse
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer. append("abcdefg");
    System. out. println(stringBuffer. reverse()); // gfedcba
    // StringBuilder reverse
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder. append("abcdefg");
    System. out. println(stringBuilder. reverse()); // gfedcba

10. String 类的常用方法都有那些?

    indexOf():返回指定字符的索引。
    charAt():返回指定索引处的字符。
    replace():字符串替换。
    trim():去除字符串两端空白。
    split():分割字符串,返回一个分割后的字符串数组。
    getBytes():返回字符串的 byte 类型数组。
    length():返回字符串长度。
    toLowerCase():将字符串转成小写字母。
    toUpperCase():将字符串转成大写字符。
    substring():截取字符串。
    equals():字符串比较。

分类记忆:

(1)常见String类的获取功能

length:获取字符串长度;
charAt(int index):获取指定索引位置的字符;
indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引;
substring(int start):从指定位置开始截取字符串,默认到末尾;
substring(int start,int end):从指定位置开始到指定位置结束截取字符串;

(2)常见String类的判断功能

equals(Object obj): 比较字符串的内容是否相同,区分大小写;
contains(String str): 判断字符串中是否包含传递进来的字符串;
startsWith(String str): 判断字符串是否以传递进来的字符串开头;
endsWith(String str): 判断字符串是否以传递进来的字符串结尾;
isEmpty(): 判断字符串的内容是否为空串"";

(3)常见String类的转换功能

byte[] getBytes(): 把字符串转换为字节数组;
char[] toCharArray(): 把字符串转换为字符数组;
String valueOf(char[] chs): 把字符数组转成字符串。valueOf可以将任意类型转为字符串;
toLowerCase(): 把字符串转成小写;
toUpperCase(): 把字符串转成大写;
concat(String str): 把字符串拼接;

(4)常见String类的其他常用功能

replace(char old,char new) 将指定字符进行互换
replace(String old,String new) 将指定字符串进行互换
trim() 去除两端空格
int compareTo(String str) 会对照ASCII 码表 从第一个字母进行减法运算 返回的就是这个减法的结果,如果前面几个字母一样会根据两个字符串的长度进行减法运算返回的就是这个减法的结果,如果连个字符串一摸一样 返回的就是0。

11. 抽象类必须要有抽象方法吗?

不需要,抽象类不一定非要有抽象方法。

示例代码:

    abstract classCat{
        publicstaticvoidsayHi(){
            System. out. println("hi~");
        }
    }

上面代码,抽象类并没有抽象方法但完全可以正常运行。


12. 普通类和抽象类有哪些区别?

    普通类不能包含抽象方法,抽象类可以包含抽象方法,只需申明,无须实现。抽象类的子类必须实现抽象类中的所有抽象方法,否则子类仍然是抽象类;
    抽象类不能直接实例化,普通类可以直接实例化。
    抽象方法不能声明为静态、不能被static、final修饰。

13. 抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类,如下图所示,编辑器也会提示错误信息:

14. 接口和抽象类有什么区别?

    实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
    构造函数:抽象类可以有构造函数;接口不能有。

    方法:抽象类中可以包含抽象方法和非抽象方法,接口只能有抽象方法
    实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
    访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。

补充:共同点

接口和抽象类都可以实例化

(1)接口

①java8之前,接口中的方法都是抽象方法,省略了public abstract。②java8之后;接口中可以定义静态方法,静态方法必须有方法体,普通方法没有方法体,需要被实现;

(2)抽象类
如果一个类继承了抽象类,①如果实现了所有的抽象方法,子类可以不是抽象类;②如果没有实现所有的抽象方法,子类仍然是抽象类。

15. Java 中 IO 流分为几种?

按功能来分:输入流(input)、输出流(output)。

按类型来分:字节流和字符流。

字节流:inputStream、outputStream;

字符流:reader、writer;

字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。

16. BIO、NIO、AIO 有什么区别?

    BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。

(一个连接一个线程。

JDK1.4之前,建立网络连接的时候采用BIO模式,先在启动服务端socket,然后启动客户端socket,对服务端通信,客户端发送请求后,先判断服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝请求,如果有的话会等待请求结束后才继续执行。)


    NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。

(一个请求一个线程。

NIO主要是想解决BIO的大并发问题,BIO是每一个请求分配一个线程,当请求过多时,每个线程占用一定的内存空间,服务器瘫痪了。

JDK1.4开始支持NIO,适用于连接数目多且连接比较短的架构,比如聊天服务器,并发局限于应用中。)


    AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。

(一个有效请求一个线程。

JDK1.7开始支持AIO,适用于连接数目多且连接比较长的结构,比如相册服务器,充分调用OS参与并发操作。)

17. Files的常用方法都有哪些?

    Files. exists():检测文件路径是否存在。
    Files. createFile():创建文件。
    Files. createDirectory():创建文件夹。
    Files. delete():删除一个文件或目录。
    Files. copy():复制文件。
    Files. move():移动文件。
    Files. size():查看文件个数。
    Files. read():读取文件。
    Files. write():写入文件。

18、java 中都有哪些引用类型?

(1)强引用

Java中默认声明的就是强引用,比如:

    Object obj = new Object();
    obj = null;

只要强引用存在,垃圾回收器将永远不会回收被引用的对象。如果想被回收,可以将对象置为null;

(2)软引用(SoftReference)

在内存足够的时候,软引用不会被回收,只有在内存不足时,系统才会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会跑出内存溢出异常。

byte[] buff = new byte[1024 * 1024];
SoftReference<byte[]> sr = new SoftReference<>(buff);

(3)弱引用(WeakReference)

进行垃圾回收时,弱引用就会被回收。

(4)虚引用(PhantomReference)

(5)引用队列(ReferenceQueue)

引用队列可以与软引用、弱引用、虚引用一起配合使用。

当垃圾回收器准备回收一个对象时,如果发现它还有引用,就会在回收对象之前,把这个引用加入到引用队列中。

程序可以通过判断引用队列中是否加入了引用,来判断被引用的对象是否将要被垃圾回收,这样可以在对象被回收之前采取一些必要的措施。


19、在 Java 中,为什么不允许从静态方法中访问非静态变量?

    静态变量属于类本身,在类加载的时候就会分配内存,可以通过类名直接访问;
    非静态变量属于类的对象,只有在类的对象产生时,才会分配内存,通过类的实例去访问;
    静态方法也属于类本身,但是此时没有类的实例,内存中没有非静态变量,所以无法调用。

20、说说Java Bean的命名规范

    JavaBean 类必须是一个公共类,并将其访问属性设置为 public
    JavaBean 类必须有一个空的构造函数:类中必须有一个不带参数的公用构造器,此构造器也应该通过调用各个特性的设置方法来设置特性的缺省值。
    一个javaBean类不应有公共实例变量,类变量都为private
    持有值应该通过一组存取方法(getXxx 和 setXxx)来访问:对于每个特性,应该有一个带匹配公用 getter 和 setter 方法的专用实例变量。

属性为布尔类型,可以使用 isXxx() 方法代替 getXxx() 方法。

通常属性名是要和 包名、类名、方法名、字段名、常量名作出区别的:

首先:必须用英文,不要用汉语拼音

(1)包(package)

用于将完成不同功能的类分门别类,放在不同的目录(包)下,包的命名规则:将公司域名反转作为包名。比如www.sohu.com 对于包名:每个字母都需要小写。比如:com.sohu.test;该包下的Test类的全名是:com.sohu.Test.Java 。

如果定义类的时候没有使用package,那么java就认为我们所定义的类位于默认包里面(default package)。

(2)类

首字母大写,如果一个类由多个单词构成,那么每个单词的首字母都大写,而且中间不使用任何的连接符。尽量使用英文。如ConnectionFactory

(3)方法

首单词全部小写,如果一个方法由多个单词构成,那么从第二个单词开始首字母大写,不使用连接符。addPerson

(4)字段

与方法相同。如ageOfPerson

(5)常量

所有单词的字母都是大写,如果有多个单词,那么使用下划线链接即可。

如:public static final int AGE_OF_PERSON = 20; //通常加上static

21、Java Bean 属性命名规范问题分析

    public class User {
        private String busName;
        private String pCount;
        private Boolean isRunning;
        //正确的命名方式,驼峰式的
        public String getBusName() {
            return busName;
        }
        public void setBusName(String busName) {
            this.busName = busName;
        }
        //这是什么?
        public String getpCount() {
            return pCount;
        }
        public void setpCount(String pCount) {
            this.pCount = pCount;
        }
        //这个也是不允许的
        public Boolean getIsRunning() {
            return isRunning;
        }
        public void setIsRunning(Boolean isRunning) {
            this.isRunning = isRunning;
        }
    }

1. javabean属性命名尽量使用常规的驼峰式命名规则
2. 属性名第一个单词尽量避免使用一个字母:如eBook, eMail。
3. boolean属性名避免使用 “is” 开头的名称
4. 随着jdk, eclipse, spring 等软件版本的不断提高, 底版本的出现的问题可能在高版本中解决了, 低版本原来正常的代码可能在高版本环境下不再支持。

22、在 Java 中,什么时候用重载,什么时候用重写?

(1)重载是多态的集中体现,在类中,要以统一的方式处理不同类型数据的时候,可以用重载。

(2)重写的使用是建立在继承关系上的,子类在继承父类的基础上,增加新的功能,可以用重写。

(3)简单总结:

重载是多样性,重写是增强剂;
目的是提高程序的多样性和健壮性,以适配不同场景使用时,使用重载进行扩展;
目的是在不修改原方法及源代码的基础上对方法进行扩展或增强时,使用重写;

生活例子:

    你想吃一碗面,我给你提供了拉面,炒面,刀削面,担担面供你选择,这是重载;
    你想吃一碗面,我不但给你端来了面,还给你加了青菜,加了鸡蛋,这个是重写;

设计模式:

    cglib实现动态代理,核心原理用的就是方法的重写;

详细解答:

    Java的重载(overload) 最重要的应用场景就是构造器的重载,构造器重载后,提供多种形参形式的构造器,可以应对不同的业务需求,加强程序的健壮性和可扩展性,比如我们最近学习的Spring源码中的ClassPathXmlApplicationContext,它的构造函数使用重载一共提供了10个构造函数,这样就为业务的选择提供了多选择性。在应用到方法中时,主要是为了增强方法的健壮性和可扩展性,比如我们在开发中常用的各种工具类,比如我目前工作中的短信工具类SMSUtil, 发短信的方法就会使用重载,针对不同业务场景下的不同形参,提供短信发送方法,这样提高了工具类的扩展性和健壮性。
    总结:重载必须要修改方法(构造器)的形参列表,可以修改方法的返回值类型,也可以修改方法的异常信息即访问权限;使用范围是在同一个类中,目的是对方法(构造器)进行功能扩展,以应对多业务场景的不同使用需求。提高程序的健壮性和扩展性。
     java的重写(override) 只要用于子类对父类方法的扩展或修改,但是在我们开发中,为了避免程序混乱,重写一般都是为了方法的扩展,比如在cglib方式实现的动态代理中,代理类就是继承了目标类,对目标类的方法进行重写,同时在方法前后进行切面织入。

总结:方法重写时,参数列表,返回值得类型是一定不能修改的,异常可以减少或者删除,但是不能抛出新的异常或者更广的异常,方法的访问权限可以降低限制,但是不能做更严格的限制。

(4)在里氏替换原则中,子类对父类的方法尽量不要重写和重载。(我们可以采用final的手段强制来遵循)

23、举例说明什么情况下会更倾向于使用抽象类而不是接口?

接口和抽象类都遵循”面向接口而不是实现编码”设计原则,它可以增加代码的灵活性,可以适应不断变化的需求。下面有几个点可以帮助你回答这个问题:在 Java 中,你只能继承一个类,但可以实现多个接口。所以一旦你继承了一个类,你就失去了继承其他类的机会了。

接口通常被用来表示附属描述或行为如: Runnable 、 Clonable 、 Serializable 等等,因此当你使用抽象类来表示行为时,你的类就不能同时是 Runnable 和 Clonable( 注:这里的意思是指如果把 Runnable 等实现为抽象类的情况 ) ,因为在 Java 中你不能继承两个类,但当你使用接口时,你的类就可以同时拥有多个不同的行为。

在一些对时间要求比较高的应用中,倾向于使用抽象类,它会比接口稍快一点。如果希望把一系列行为都规范在类继承层次内,并且可以更好地在同一个地方进行编码,那么抽象类是一个更好的选择。有时,接口和抽象类可以一起使用,接口中定义函数,而在抽象类中定义默认的实现。

24、实例化对象有哪几种方式

    new
    clone()
    通过反射机制创建

    //用 Class.forName方法获取类,在调用类的newinstance()方法
    Class<?> cls = Class.forName("com.dao.User");
    User u = (User)cls.newInstance();

    序列化反序列化

//将一个对象实例化后,进行序列化,再反序列化,也可以获得一个对象(远程通信的场景下使用)
    ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("D:/data.txt"));
    //序列化对象
    out.writeObject(user1);
    out.close();
    //反序列化对象
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/data.txt"));
    User user2 = (User) in.readObject();
    System.out.println("反序列化user:" + user2);
    in.close();

25、byte类型127+1等于多少

byte的范围是-128~127。

字节长度为8位,最左边的是符号位,而127的二进制为01111111,所以执行+1操作时,01111111变为10000000。

大家知道,计算机中存储负数,存的是补码的兴衰。左边第一位为符号位。

那么负数的补码转换成十进制如下:

一个数如果为正,则它的原码、反码、补码相同;一个正数的补码,将其转化为十进制,可以直接转换。

已知一个负数的补码,将其转换为十进制数,步骤如下:

    先对各位取反;
    将其转换为十进制数;
    加上负号,再减去1;

例如10000000,最高位是1,是负数,①对各位取反得01111111,转换为十进制就是127,加上负号得-127,再减去1得-128;

容器

26. Java 容器都有哪些?

Java 容器分为 Collection 和 Map 两大类,其下又有很多子类,如下所示:

    Collection
    List
        ArrayList
        LinkedList
        Vector
        Stack
    Set
        HashSet
        LinkedHashSet
        TreeSet
    Map
    HashMap
        LinkedHashMap
    TreeMap
    ConcurrentHashMap
    Hashtable

27. Collection 和 Collections 有什么区别?

    Collection 是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如 List、Set 等。Collection派生了两个子接口list和set,分别定义了两种不同的存储方式。
    Collections 是一个包装类,包含了很多静态方法(对集合的搜索、排序、线程安全化等),不能被实例化,就像一个工具类,比如提供的排序方法:Collections. sort(list),服务于Collection框架。

28. List、Set、Map 之间的区别是什么?

List、Set、Map 的区别主要体现在两个方面:元素是否有序、是否允许元素重复。

三者之间的区别,如下表:

补充:更加具体的List和Set的区别

(1)List简介

实际上有两种List:一种是基本的ArrayList,其优点在于随机访问元素,另一种是LinkedList,它并不是为快速随机访问设计的,而是快速的插入或删除。
ArrayList:由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。
LinkedList :对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。
还具有下列方 法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。

(2)Set简介

Set具有与Collection完全一样的接口,因此没有任何额外的功能。实际上Set就是Collection,只是行为不同。这是继承与多态思想的典型应用:表现不同的行为。Set不保存重复的元素(至于如何判断元素相同则较为负责)

Set : 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。
TreeSet: 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。

(3)list与Set区别

① List,Set都是继承自Collection接口

② List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。)

③ Set和List对比:

Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

29. HashMap 和 Hashtable 有什么区别?

    存储:HashMap 运行 key 和 value 为 null,而 Hashtable 不允许。
    线程安全:Hashtable 是线程安全的,而 HashMap 是非线程安全的。
    推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。

30. 如何决定使用 HashMap 还是 TreeMap?

对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择。

31. 说一下 HashMap 的实现原理?

HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。


(1)简介

HashMap基于map接口,元素以键值对方式存储,允许有null值,HashMap是线程不安全的。

(2)基本属性

初始化大小,默认16,2倍扩容;
负载因子0.75;
初始化的默认数组;
size
threshold。判断是否需要调整hashmap容量

(3)HashMap的存储结构

JDK1.7中采用数组+链表的存储形式。

HashMap采取Entry数组来存储key-value,每一个键值对组成了一个Entry实体,Entry类时机上是一个单向的链表结构,它具有next指针,指向下一个Entry实体,以此来解决Hash冲突的问题。

HashMap实现一个内部类Entry,重要的属性有hash、key、value、next。

JDK1.8中采用数据+链表+红黑树的存储形式。当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。

32. 说一下 HashSet 的实现原理?

HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。

HashSet实际上是一个HashMap实例,数据存储结构都是数组+链表。

HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value都是一个统一的对象PRESENT。

private static final Object PRESENT = new Object();

HashSet中add方法调用的是底层HashMap中的put方法,put方法要判断插入值是否存在,而HashSet的add方法,首先判断元素是否存在,如果存在则插入,如果不存在则不插入,这样就保证了HashSet中不存在重复值。

 通过对象的hashCode和equals方法保证对象的唯一性。

33、HasmMap和HashSet的区别

(1)先了解一下HashCode

Java中的集合有两类,一类是List,一类是Set。

List:元素有序,可以重复;

Set:元素无序,不可重复;

要想保证元素的不重复,拿什么来判断呢?这就是Object.equals方法了。如果元素有很多,增加一个元素,就要判断n次吗?

显然不现实,于是,Java采用了哈希表的原理。哈希算法也称为散列算法,是将数据依特定算法直接指定到一根地址上,初学者可以简单的理解为,HashCode方法返回的就是对象存储的物理位置(实际上并不是)。

这样一来,当集合添加新的元素时,先调用这个元素的hashcode()方法,就一下子能定位到他应该放置的物理位置上。如果这个位置上没有元素,他就可以直接存储在这个位置上,不用再进行任何比较了。如果这个位置上有元素,就调用它的equals方法与新元素进行比较,想同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际上调用equals方法的次数就大大降低了,几乎只需要一两次。

简而言之,在集合查找时,hashcode能大大降低对象比较次数,提高查找效率。

Java对象的equals方法和hashCode方法时这样规定的:

相等的对象就必须具有相等的hashcode。

    如果两个对象的hashcode相同,他们并不一定相同。
    如果两个对象的hashcode相同,他们并不一定相同。

如果两个Java对象A和B,A和B不相等,但是A和B的哈希码相等,将A和B都存入HashMap时会发生哈希冲突,也就是A和B存放在HashMap内部数组的位置索引相同,这时HashMap会在该位置建立一个链接表,将A和B串起来放在该位置,显然,该情况不违反HashMap的使用规则,是允许的。当然,哈希冲突越少越好,尽量采用好的哈希算法避免哈希冲突。

equals()相等的两个对象,hashcode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。

(2)HashMap和HashSet的区别

 

34、hashcode是什么?有什么作用?

Java中Object有一个方法:

public native int hashcode();

(1)hashcode()方法的作用

hashcode()方法主要配合基于散列的集合一起使用,比如HashSet、HashMap、HashTable。

当集合需要添加新的对象时,先调用这个对象的hashcode()方法,得到对应的hashcode值,实际上hashmap中会有一个table保存已经存进去的对象的hashcode值,如果table中没有改hashcode值,则直接存入,如果有,就调用equals方法与新元素进行比较,相同就不存了,不同就存入。

(2)equals和hashcode的关系

如果equals为true,hashcode一定相等;

如果equals为false,hashcode不一定不相等;

如果hashcode值相等,equals不一定相等;

如果hashcode值不等,equals一定不等;

(3)重写equals方法时,一定要重写hashcode方法

(4)百度百科

    hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。

    hashCode 的常规协定是:
    在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
    如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
    以下情况不 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
    实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

    当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

(5)小白解释

    1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
    例如内存中有这样的位置
    0  1  2  3  4  5  6  7  
    而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
    但如果用hashcode那就会使效率提高很多。
    我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。

    2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
    也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
    那么。重写了equals(),为什么还要重写hashCode()呢?
    想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊。

35. ArrayList 和 LinkedList 的区别是什么?

    数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
    随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
    增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。

综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。

36. 如何实现数组和 List 之间的转换?

    数组转 List:使用 Arrays. asList(array) 进行转换。
    List 转数组:使用 List 自带的 toArray() 方法。

代码示例:

    // list to array
    List<String> list = new ArrayList<String>();
    list. add("王磊");
    list. add("的博客");
    list. toArray();
    // array to list
    String[] array = new String[]{"王磊","的博客"};
    Arrays. asList(array);

37. ArrayList 和 Vector 的区别是什么?

    线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
    性能:ArrayList 在性能方面要优于 Vector。
    扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。

38. Array 和 ArrayList 有何区别?

    Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
    Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
    Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。

39. 在 Queue 中 poll()和 remove()有什么区别?

    相同点:都是返回第一个元素,并在队列中删除返回的对象。
    不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。

代码示例:

    Queue<String> queue = new LinkedList<String>();
    queue. offer("string"); // add
    System. out. println(queue. poll());
    System. out. println(queue. remove());
    System. out. println(queue. size());

补充一些区别:

(1)offer()和add()区别:

增加新项时,如果队列满了,add会抛出异常,offer返回false。

(2)poll()和remove()区别:

poll()和remove()都是从队列中删除第一个元素,remove抛出异常,poll返回null。

(3)peek()和element()区别:

peek()和element()用于查询队列头部元素,为空时element抛出异常,peek返回null。
 

40. 哪些集合类是线程安全的?

Vector、Hashtable、Stack 都是线程安全的,而像 HashMap 则是非线程安全的,不过在 JDK 1.5 之后随着 Java. util. concurrent 并发包的出现,它们也有了自己对应的线程安全类,比如 HashMap 对应的线程安全类就是 ConcurrentHashMap。

Vector:就比Arraylist多了个同步化机制(线程安全)。
Stack:栈,也是线程安全的,继承于Vector。
Hashtable:就比Hashmap多了个线程安全。
ConcurrentHashMap:是一种高效但是线程安全的集合。

41. 迭代器 Iterator 是什么?

Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。

为了方便的处理集合中的元素,Java中出现了一个对象,该对象提供了一些方法专门处理集合中的元素.例如删除和获取集合中的元素.该对象就叫做迭代器(Iterator)。

42. Iterator 怎么使用?有什么特点?

Iterator 接口源码中的方法:

    java.lang.Iterable 接口被 java.util.Collection 接口继承,java.util.Collection 接口的 iterator() 方法返回一个 Iterator 对象
    next() 方法获得集合中的下一个元素
    hasNext() 检查集合中是否还有元素
    remove() 方法将迭代器新返回的元素删除

Iterator 使用代码如下:

    List<String> list = new ArrayList<>();
    Iterator<String> it = list. iterator();
    while(it. hasNext()){
      String obj = it. next();
      System. out. println(obj);
    }

Iterator 的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。

43. Iterator 和 ListIterator 有什么区别?

    Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
    Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
    ListIterator 从 Iterator 接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

(1)ListIterator 继承 Iterator

(2)ListIterator 比 Iterator多方法

    add(E e)  将指定的元素插入列表,插入位置为迭代器当前位置之前
    set(E e)  迭代器返回的最后一个元素替换参数e
    hasPrevious()  迭代器当前位置,反向遍历集合是否含有元素
    previous()  迭代器当前位置,反向遍历集合,下一个元素
    previousIndex()  迭代器当前位置,反向遍历集合,返回下一个元素的下标
    nextIndex()  迭代器当前位置,返回下一个元素的下标

(3)使用范围不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子类

    ListIterator 有 add 方法,可以向 List 中添加对象;Iterator 不能
    ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向遍历;Iterator不可以
    ListIterator 有 nextIndex() 和previousIndex() 方法,可定位当前索引的位置;Iterator不可以
    ListIterator 有 set()方法,可以实现对 List 的修改;Iterator 仅能遍历,不能修改。
 

44. 怎么确保一个集合不能被修改?

可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。

示例代码如下:

    List<String> list = new ArrayList<>();
    list. add("x");
    Collection<String> clist = Collections. unmodifiableCollection(list);
    clist. add("y"); // 运行时此行报错
    System. out. println(list. size());

其他:

我们很容易想到用final关键字进行修饰,我们都知道

final关键字可以修饰类,方法,成员变量,final修饰的类不能被继承,final修饰的方法不能被重写,final修饰的成员变量必须初始化值,如果这个成员变量是基本数据类型,表示这个变量的值是不可改变的,如果说这个成员变量是引用类型,则表示这个引用的地址值是不能改变的,但是这个引用所指向的对象里面的内容还是可以改变的。

那么,我们怎么确保一个集合不能被修改?首先我们要清楚,集合(map,set,list…)都是引用类型,所以我们如果用final修饰的话,集合里面的内容还是可以修改的。

我们可以做一个实验:

可以看到:我们用final关键字定义了一个map集合,这时候我们往集合里面传值,第一个键值对1,1;我们再修改后,可以把键为1的值改为100,说明我们是可以修改map集合的值的。

那我们应该怎么做才能确保集合不被修改呢?
我们可以采用Collections包下的unmodifiableMap方法,通过这个方法返回的map,是不可以修改的。他会报 java.lang.UnsupportedOperationException错。

同理:Collections包也提供了对list和set集合的方法。

Collections.unmodifiableList(List)
Collections.unmodifiableSet(Set)

45、set有哪些实现类?

(1)HashSet

HashSet是set接口的实现类,set下面最主要的实现类就是HashSet(也就是用的最多的),此外还有LinkedHashSet和TreeSet。
HashSet是无序的、不可重复的。通过对象的hashCode和equals方法保证对象的唯一性。
HashSet内部的存储结构是哈希表,是线程不安全的。

(2)TreeSet

TreeSet对元素进行排序的方式:

元素自身具备比较功能,需要实现Comparable接口,并覆盖compareTo方法。
元素自身不具备比较功能,需要实现Comparator接口,并覆盖compare方法。

(3)LinkedHashSet

LinkedHashSet是一种有序的Set集合,即其元素的存入和输出的顺序是相同的。

46、队列和栈是什么?有什么区别?

(1)队列先进先出,栈先进后出。

(2)遍历数据速度不同。

栈只能从头部取数据 也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性;

队列则不同,他基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多。


47、Java8开始ConcurrentHashMap,为什么舍弃分段锁?

ConcurrentHashMap的原理是引用了内部的 Segment ( ReentrantLock )  分段锁,保证在操作不同段 map 的时候, 可以并发执行, 操作同段 map 的时候,进行锁的竞争和等待。从而达到线程安全, 且效率大于 synchronized。

但是在 Java 8 之后, JDK 却弃用了这个策略,重新使用了 synchronized+CAS。

弃用原因

通过  JDK 的源码和官方文档看来, 他们认为的弃用分段锁的原因由以下几点:

加入多个分段锁浪费内存空间。
生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
为了提高 GC 的效率
新的同步方案

既然弃用了分段锁, 那么一定由新的线程安全方案, 我们来看看源码是怎么解决线程安全的呢?(源码保留了segment 代码, 但并没有使用)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值