(一)Java 面试问题 — Java 基础知识+集合

(一)Java 面试问题 — Java 基础知识+集合


一、Java基础知识

1、面向对象的基本特征

面向对象的基本特征:封装、继承、多态、抽象
封装:就是把对象的属性和行为(数据)结合为一个独立的整体,并尽可能的隐藏对象内部的实现细节,就是把不想告诉或者不该告诉别人的东西隐藏起来,把可以告诉别人的公开,别人只能用我提供的功能实现需求,而不知道是如何实现的。增加安全性。
继承:子类继承父类的数据属性和行为,并根据自己的需求拓展出新的行为,提高了代码的复用性。
多态:指允许不同的对象对统一消息做出相应处理,即同一消息可以根据发送对象不同而采用多种不同的行为方式(发消息就是函数调用),封装和继承都是为多态而准备的,在执行期间判断引用对象的实际类型,根据其实际的类型调用其相应的方法
抽象:表示对问题领域进行分析、设计中得出的抽象的概念,是对一系列看上去不同,但本质上相同具体概念的抽象。在Java中抽象用abstract关键字来修饰,用abstract修饰类时,此类就不能被实例化,从这里可以看出,抽象类(接口)就是为了继承而存在的。


2、Java的基本数据类型

数据类型字节数位数
byte18
short216
int432
long864
float432
double864
boolean18
char216

3、JDK、JVM、JRE的区别

JDK(Java Development Kit)是整个 Java 的核心,是 java 开发工具包,包括了 Java 运
行环境 JRE、Java 工具和 Java 基础类库。
JRE(Java Runtime Environment)是运行 JAVA 程序所必须的环境的集合,包含 java
虚拟机和 java 程序的一些核心类库。
JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,是整个 java 实现跨平台的最核
心的部分,能够运行以 Java 语言写作的软件程序。


4、重载和重写的区别

重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回
值和访问修饰符可以不同,发生在编译时。
重写: 发生在父子类中,方法名.参数列表必须相同,返回值范围小于等于父类,抛出的异
常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private
则子类就不能重写该方法。


5、Java 中==和 equals 的区别

== 的作用:
基本类型:比较的就是值是否相同
引用类型:比较的就是地址值是否相同
equals 的作用:
引用类型:默认情况下,比较的是地址值。
特:String、Integer、Date 这些类库中 equals 被重写,比较的是内容而不是地址!
面试题:请解释字符串比较之中 “ == ” 和 equals() 的区别?
答:==:比较的是两个字符串内存地址(堆内存)的数值是否相等,属于数值比较;equals():
比较的是两个字符串的内容,属于内容比较。


6、String、StringBuffer、StringBuilder 三者之间的区别

String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
String 中的 String 类中使用 final 关键字修饰字符数组来保存字符串,private final char
value[] ,String 对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符
串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
小结:
(1)如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据用 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据用 StringBuilder。


7、接口和抽象类的区别是什么?

实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
构造函数:抽象类可以有构造函数;接口不能有。
main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符


8、 string 常用的方法有哪些?

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


9、什么是单例模式?有几种?

单例模式:某个类的实例在多线程环境下只会被创建一次出来。
单例模式有饿汉式单例模式、懒汉式单例模式和双检锁单例模式三种。
饿汉式:线程安全,一开始就初始化。
以静态内部类来实现线程安全,内部类的加载方式是classLoader,jvm在初始化类时本身就会保证只有一个线程会初始化类。

puclic class Singleton{
	private static Singleton instance = new Singleton();
	private Singleton(){}
	public static Singleton getInstance(){
		return instance;
	}
}

懒汉式:非线程安全、延迟初始化

public class Singleton{
	private static Singleton instance;
	private Singleton(){}
	public static Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		}
		return instance;
	}
}

双检锁:线程安全,延迟初始化
volatile+双重检验锁实现线程安全,创建对象的过程为:1、分配内存空间;2、创建对象;3、将内存空间的地址赋值给对象引用。但是jvm在创建对象时可能会将这段代码重排序,导致对象引用为空,使用volatile关键字可以禁止jvm重排序。

public class Singleton{
	private volatile static Singleton singleton;
	private Singleton(){}
	public static Singleton getSingleton(){
		if(singleton == null){
			synchronized(Singleton.class){
				if(singleton == null){
					singleton = new Singleton();
				}
			}
		}
		return singleton;
	}
}

10、反射

在 Java 中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和
方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息 以及动
态调用对象方法的功能成为 Java 语言的反射机制。
获取 Class 对象的 3 种方法 :
调用某个对象的 getClass()方法
Person p=new Person();
Class clazz=p.getClass();
调用某个类的 class 属性来获取该类对应的 Class 对象
Class clazz=Person.class;
使用 Class 类中的 forName()静态方法(最安全/性能最好)
Class clazz=Class.forName(“类的全路径”); (最常用)


11、 jdk1.8 的新特性

1、Lambda 表达式:
Lambda 允许把函数作为一个方法的参数。

new Thread( ()->System.out.println("abc")).start();

2、方法引用
方法引用允许直接引用已有 Java 类或对象的方法或构造方法。

ArrayList<String> list = new ArrayList<>();
	list.add("aaa");
	list.add("bbb");
	list.add("ccc");
	list.forEach(System.out::println);

上例中我们将 System.out::println 方法作为静态方法来引用。
3、函数式接口
有且仅有一个抽象方法的接口叫做函数式接口,函数式接口可以被隐式转换为 Lambda 表
达式。通常函数式接口上会添加@FunctionalInterface 注解。
4、接口允许定义默认方法和静态方法
从 JDK8 开始,允许接口中存在一个或多个默认非抽象方法和静态方法。
5、Stream API
新添加的 Stream API(java.util.stream)把真正的函数式编程风格引入到 Java 中。这种
风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,
比如筛选,排序,聚合等。

List<String> list = Arrays.asList("a","b","c","d","e","f","g","","a","b");
	list.stream()//获取集合的流对象
		.filter(string -> !string.isEmpty())//对数据进行过了操作,过滤掉空字符串
		.distinct()//去重
		.forEach(a -> System.out.println(a));

6、日期/时间类改进
之前的 JDK 自带的日期处理类非常不方便,我们处理的时候经常是使用的第三方工具包,
比如 commons-lang 包等。不过 JDK8 出现之后这个改观了很多,比如日期时间的创建、
比较、调整、格式化、时间间隔等。
这些类都在 java.time 包下,LocalDate/LocalTime/LocalDateTime。
7、Optional 类
Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent()方法会返回 true,
调用 get()方法会返回该对象。

String string  = "abd";
Optional<String> optional = Optional.of(string);
boolean present = optional.isPresent();
String value = optional.get();
System.out.println(present+"/"+value);

8、Java8 Base64 实现
Java 8 内置了 Base64 编码的编码器和解码器。


12、 Java 的异常

Throwable
Error
Exception
运行时异常RuntimeException
虚拟机错误VirtuallyMachineRrror
内存溢出错误OutOfMemoryError
线程死锁TheadDeath
SQL异常SQLException
IO异常IOException
空指针异常NullPointerException
数组下标越界异常ArrayIndexOutofBoundsException
算术异常ArithmeticException
类转换异常ClassCastException

Throwable 是所有 Java 程序中错误处理的父类,有两种资类:Error 和 Exception。
Error:表示由 JVM 所侦测到的无法预期的错误,由于这是属于 JVM 层次的严重错误,导
致 JVM 无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显
示错误信息。
Exception:表示可恢复的例外,这是可捕捉到的。
1.运行时异常:都是 RuntimeException 类及其子类异常,如 NullPointerException(空指
针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序
中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从
逻辑角度尽可能避免这类异常的发生。运行时异常的特点是 Java 编译器不会检查它,也就
是说,当程序中可能出现这类异常,即使没有用 try-catch 语句捕获它,也没有用 throws
子句声明抛出它,也会编译通过。
2.非运行时异常(编译异常):是 RuntimeException 以外的异常,类型上都属于 Exception
类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。
如 IOException、SQLException 等以及用户自定义的 Exception 异常,一般情况下不自定
义检查异常。

常见的 RunTime 异常几种如下:
NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常


13、 BIO、NIO、AIO 有什么区别?

BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用
方便,并发处理能力低。
NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通
道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO
的操作基于事件和回调机制。


14、 Threadloal 的原理

ThreadLocal:为共享变量在每个线程中创建一个副本,每个线程都可以访问自己内部的副 本变量。通过 threadlocal 保证线程的安全性。
其实在 ThreadLocal 类中有一个静态内部类 ThreadLocalMap(其类似于 Map),用键值对
的形式存储每一个线程的变量副本,ThreadLocalMap 中元素的 key 为当前 ThreadLocal
对象,而 value 对应线程的变量副本。
ThreadLocal 本身并不存储值,它只是作为一个 key 保存到 ThreadLocalMap 中,但是
这里要注意的是它作为一个 key 用的是弱引用,因为没有强引用链,弱引用在 GC 的时候可
能会被回收。这样就会在 ThreadLocalMap 中存在一些 key 为 null 的键值对(Entry)。因
为 key 变成 null 了,我们是没法访问这些 Entry 的,但是这些 Entry 本身是不会被清除的。
如果没有手动删除对应 key 就会导致这块内存即不会回收也无法访问,也就是内存泄漏。
使用完 ThreadLocal 之后,记得调用 remove 方法。 在不使用线程池的前提下,即使不调
用 remove 方法,线程的"变量副本"也会被 gc 回收,即不会造成内存泄漏的情况。


15、 同步锁、死锁、乐观锁、悲观锁

同步锁:
当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要
保证线程同步互斥,就是指并发执行的多个线程,在同一时间内只允许一个线程访问共享数
据。Java 中可以使用 synchronized 关键字来取得一个对象的同步锁。
死锁:
何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
乐观锁:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是
在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和
CAS 算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类
似于 write_conditio 机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。
悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时
候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使
用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了
很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java 中
synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。


16、说一下 synchronized 底层实现原理?

1)syn 锁住的是对象,对象里边的组成如下:
Java 的对象布局:必须是要求对象的大小必须是 8 的整数倍
(1)首先是有一个 object header
(2)填充数据 :当对象大小不足 8 的倍数的时候,他会把当前对象填充成 8 的倍数,假如他现在本身就是 8 的 byte 的倍数,此时填充数据就不用了
(3)成员变量 :就是咱们成员变量
2)锁升级
(1) 偏向锁
线程要尝试去加锁,他会去判断当前这个 mark-word 里边是否包含线程 id,如果没有线程id 的话,他会去利用 cas 把自己的线程 id 写入到 mark-word 里边去第二次这个线程再次过来的时候,他会去判断当前这个 mark-word 里边是否包含线程 id,如果有了的话,他就会把他自己的线程 id 和对象头里边的线程 id 进行对比,如果发现是一样的此时就标识获得到了锁如果你在没加锁的情况打印对象头:他默认就是 无锁可偏向,如果你没加锁的情况计算了hashCode 码,无锁不可偏向,如果此时你加锁无法成为偏向锁,直接膨胀成一把轻量级锁
(2)轻量级锁
a 升级成轻量级锁三个条件
你现在已经是无锁不可偏向,此时加锁那么他就直接是一把轻量级锁
没有关闭延迟偏向锁打开,他会自动成为轻量级锁
如果程序出现交替执行,他也会成为一把轻量级锁
b 原理
首先方法压栈,此时这个方法栈帧就压栈,栈帧种就创建两个和锁有关系的空间displace hrd ,owner
他会将锁里边的 mark-word 里边信息拷贝到 hrd 种
他会用栈帧 owner 指针去指向我们的对象头
对象头中轻量级指针会指向当前创建出来的这个栈帧
锁会把当前的状态修改成 00
如果说完成了以上 4 件事情,那么此时才表示加锁成功,这把锁就是属于当前线程的
3) 重量级锁

(1)Java 如果发现要创建一把重量级锁,我们 Java 就会为我们创建一个 C++的ObjectMonitor,会让对象头中 monitor 指向我们这个 ObjectMonitor 对象如果你进入到锁内部时,这个 ObjectMonitor 他会 发起汇编指定 monitorenter,当你出 syn 代码代码块的时候,他会发出 monitorexit 指令如果你在执行 syn 过程中出现了异常,其实上他还是会执行 monitorexit 这样一个指令
(2)对象头里边 monitor 指针会指向 ObjectMonitor 对象,当多个线程来加锁的时候,他们就会执行 monitorenter 指令,进入到 ObjectMonitor 进入到 entrylist 中等待抢锁,他们会利用 cas 来进行抢锁,如果抢锁成功,ObjectMonitor,他会把他内部的 owner 的指针去指向咱们抢锁成功 线程,然后会让计数器+1,如果此时是抢锁的线程是持有锁的线程,那么此时 count 就会再+1 ,释放锁的时候,把 count 进行–,直到count== 0 的时候就把 owner 置为 null,如果你对线程调用 wait 方法,此时这些被 wait的线程他就会进入到 waitSet 中,只有当你去调用 notifyall 方法的时候他才会从新开始这样一套流程
(3)如果是同步方法,那么他执行的 指令 acc_synchronized ,但是这是隐式调用


17、synchronized 和 volatile 的区别是什么?

volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从
主存中读取; synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线
程被阻塞住。
volatile 仅能使用在变量级别;synchronized 则可以使用在变量、方法、和类级别的。
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证
变量的修改可见性和原子性。
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。


18、synchronized 和 Lock 有什么区别?

首先 synchronized 是 java 内置关键字,在 jvm 层面,Lock 是个 java 类;
synchronized 无法判断是否获取锁的状态,Lock 可以判断是否获取到锁;
synchronized 会自动释放锁(a 线程执行完同步代码会释放锁;b 线程执行过程中发生
异常会释放锁),Lock 需在 finally 中手工释放锁(unlock()方法释放锁),否则容易造成线
程死锁;
用 synchronized 关键字的两个线程 1 和线程 2,如果当前线程 1 获得锁,线程 2 线程
等待。如果线程 1 阻塞,线程 2 则会一直等待下去,而 Lock 锁就不一定会等待下去,如果
尝试获取不到锁,线程可以不用一直等待就结束了;
synchronized 的锁可重入、不可中断、非公平,而 Lock 锁可重入、可判断、可公平
(两者皆可);
Lock 锁适合大量同步的代码的同步问题,synchronized 锁适合代码少量的同步问题。


19、手写冒泡排序

代码如下(示例):

public class sort{
    public static void sort(){
        Scanner input = new Scanner(System.in);
        int sort[] = new int[10];
        int temp;
        System.out.println("请输入10个排序的数据:");
        for(int i = 0 ; i < 10 ; i ++){
            sort[i] = input.nextInt();
        }
        for(int i = 0 ; i < sort.length - 1 ; i++){
            for(int j = 0 ; j < sort.length-i-1 ; j++){
                if(sort[j] < sort[j+1]){
                    temp = sort[j];
                    sort[j] = sort[j+1];
                    sort[j+1] = temp;
                }
            }
        }
        System.out.println("排序后的顺序为:");
        for(int i = 0 ; i<sort.length;i++){
            System.out.println(sort[i]+"----");
        }
    }
    public static void main(String[] msg){
        sort();
    }
}

二、集合

1、 常见的数据结构

常用的数据结构有:数组,栈,队列,链表,树,散列,堆,图等

数据结构
数组array
栈stack
链表LinkedList
图Graph
散列表Hash
队列Queue
树Tree
堆Heap

数组是最常用的数据结构,数组的特点是长度固定,数组的大小固定后就无法扩容了 ,数组只能存储一种类型的数据 ,添加,删除的操作慢,因为要移动其他的元素。
是一种基于先进后出(FILO)的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
队列是一种基于先进先出(FIFO)的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊线性表,它按照先进先出的原则存储数据,先进入的数据,在读取数据时先被读取出来。
链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结节(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等。
是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家谱、单位的组织架构等等。有二叉树、平衡树、红黑树、B 树、B+树。
散列表,也叫哈希表,是根据关键码和值 (key 和 value) 直接进行访问的数据结构,通过 key 和 value 来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。
是计算机学科中一类特殊的数据结构的统称,堆通常可以被看作是一棵完全二叉树的数组对象。
:图是由一组顶点和一组能够将两个顶点相连的边组成的


2、集合和数组的区别

区别:数组长度固定 集合长度可变
数组中存储的是同一种数据类型的元素,可以存储基本数据类型,也可以存储引用数据
类型;
集合存储的都是对象,而且对象的数据类型可以不一致。在开发当中一般当对象较多的
时候,使用集合来存储对象。


3、List 和 Map、Set 的区别

List 和 Set 是存储单列数据的集合,Map 是存储键值对这样的双列数据的集合;
List 中存储的数据是有顺序的,并且值允许重复;
Map 中存储的数据是无序的,它的键是不允许重复的,但是值是允许重复的;
Set 中存储的数据是无顺序的,并且不允许重复,但元素在集合中的位置是由元素的hashcode 决定,即位置是固定的(Set 集合是根据 hashcode 来进行数据存储的,所以位置是固定的,但是这个位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的)。


4、 List 和 Map、Set 的实现类

映射关系
排序
无序
保持顺序
ListIterator
查改
有序可重复
增删
无序不重复
无序
保持顺序
需要排序
Iterator
Map
List
Collection
set
TreeMap
HashMap
LinkedHashMap
ArrayList
LikedList
HashSet
LinkedHashSet
TreeSet

(1)Connection 接口
List 有序,可重复
ArrayList
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
Vector
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低, 已给舍弃了
LinkedList
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高

Set 无序,唯一
HashSet
底层数据结构是哈希表。(无序,唯一)
如何来保证元素唯一性?
依赖两个方法:hashCode()和 equals()
LinkedHashSet
底层数据结构是链表和哈希表。(FIFO 插入有序,唯一)
a.由链表保证元素有序
b.由哈希表保证元素唯一
TreeSet
底层数据结构是红黑树。(唯一,有序)
a. 如何保证元素排序的呢?
自然排序
比较器排序
b.如何保证元素唯一性的呢?
根据比较的返回值是否是 0 来决定
(2)Map 接口有四个实现类:
HashMap
基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和 null 键, 线程
不安全。
HashTable
线程安全,低效,不支持 null 值和 null 键;
LinkedHashMap
线程不安全,是 HashMap 的一个子类,保存了记录的插入顺序;
TreeMap
能够把它保存的记录根据键排序,默认是键值的升序排序,线程不安全。


5、HashMap 的底层原理

HashMap 在 JDK1.8 之前的实现方式 数组+链表,
但是在 JDK1.8 后对 HashMap 进行了底层优化,改为了由 数组+链表或者数值+红黑树
实现,主要的目的是提高查找效率

JDK版本实现方式字节数>8字节数<6
1.8以前数组+单向链表数组+单向链表数组+单向链表
1.8以后数组+单向链表+红黑树数组+红黑树数组+单向链表

(1)Jdk8 数组+链表或者数组+红黑树实现,当链表中的元素超过了 8 个以后, 会将链表转换为红黑树,当红黑树节点 小于 等于 6 时又会退化为链表。
(2)当 new HashMap():底层没有创建数组,首次调用 put()方法示时,底层创建长度为16 的数组,jdk8 底层的数组是:Node[],而非 Entry[],用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用 rehash 方法将数组容量增加到原来的两倍,专业术语叫做扩容,在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能。默认的负载因子大小为 0.75,数组大小为 16。也就是说,默认情况下,那么当 HashMap中元素个数超过 160.75=12 的时候,就把数组的大小扩展为 216=32,即扩大一倍。
(3)在我们 Java 中任何对象都有 hashcode,hash 算法就是通过 hashcode 与自己进行向右位移 16 的异或运算。这样做是为了计算出来的 hash 值足够随机,足够分散,还有产生的数组下标足够随机,
map.put(k,v)实现原理
1)首先将 k,v 封装到 Node 对象当中(节点)。
2)先调用 k 的 hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
3)下标位置上如果没有任何元素,就把 Node 添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着 k 和链表上每个节点的 k 进行 equal。如果所有的 equals方法返回都是 false,那么这个新的节点将被添加到链表的末尾。如其中有一个 equals返回了 true,那么这个节点的 value 将会被覆盖。
map.get(k)实现原理
1)、先调用 k 的 hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
2)、在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回 null。如果这个位置上有单向链表,那么它就会拿着参数 K 和单向链表上的每一个节点的 K 进行 equals,如果所有 equals 方法都返回 false,则 get 方法返回 null。如果其中一个节点的 K 和参数 K 进行 equals 返回 true,那么此时该节点的 value 就是我们要找的 value 了,get 方法最终返回这个要找的 value。
(4)Hash 冲突
不同的对象算出来的数组下标是相同的这样就会产生 hash 冲突,当单线链表达到一定长度后效率会非常低。
(5)在链表长度大于 8 的时候,将链表就会变成红黑树,提高查询的效率。


6、Hashmap 和 hashtable ConcurrentHashMap 区别

区别对比一(HashMap 和 HashTable 区别):
(1)HashMap 是非线程安全的,HashTable 是线程安全的。
(2)HashMap 的键和值都允许有 null 值存在,而 HashTable 则不行。
(3)因为线程安全的问题,HashMap 效率比 HashTable 的要高。
(4)Hashtable 是同步的,而 HashMap 不是。因此,HashMap 更适合于单线程环境,而 Hashtable 适合于多线程环境。一般现在不建议用 HashTable, ①是 HashTable 是遗留类,内部实现很多没优化和冗余。②即使在多线程环境下,现在也有同步的ConcurrentHashMap 替代,没有必要因为是多线程而用 HashTable。
区别对比二(HashTable 和 ConcurrentHashMap 区别):
HashTable 使用的是 Synchronized 关键字修饰,ConcurrentHashMap 是 JDK1.7使用了锁分段技术来保证线程安全的。JDK1.8ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍


7、ArrayList的扩容机制

  • ArrayList() 会使用长度为0的数组
  • ArrayList(int initialCapacity) 会使用指定容量的数组
  • public· ArrayList(Conllection<? extends E> c) 会使用c的大小作为数组容量
  • add(Object o) 首次扩容为10,再次扩容为 上次容量>>1+上次容量
    【0,10,15,22,33,49,73,109,163,244,366,549,823,1234,1851,2776,4164,6246,9369,14053,21079】
  • addAll(Conllection c) 没有元素时,扩容为Math.max(10,实际元素个数),有元素为Math.max(上次容量>>1+上次容量,实际原宿个数)

8、iterator的fail-fast和fail-safe

  • fail-fast: 一旦发现遍历他人修改,则立刻抛出异常,ArraryList典型代表,遍历同时不能修改,尽快失败
    遍历时检测最开始的数组长度和遍历时数组长度,不一致时抛出异常ConcurrentModificationException–并发修改异常
  • fail-safe: 发现遍历的时候他人修改,应当能有对应策略,例如牺牲掉一致性让整个遍历运行完成
    ConpyOnWriteArrayList典型代表 遍历同时不能修改,写时复制,读写分离
  • Vector和ArrayList一样是fail-fast的 会抛出并发修改修改异常

ArrayLsit和LikedList的比较

  • ArrayList
    ① 基于数组,需要连续内存
    ② 随机访问快(需根据下标访问)
    ③ 尾部插入,删除性能可以,其他部分插入,删除会移动数据,因此性能会低
    ④ 可以利用cpu缓存,局部性原理
  • LinkedList
    ① 基于双向链表,不需要连续内存
    ② 随机访问慢(需要沿链表遍历)
    ③ 头尾插入删除性能高
    ④ 占用内存多
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值