JAVA基础总结

1.String、StringBuilder、StringBuffer的区别是什么,String为什么是不可变的?

不可变性

String类的底层源码中,通过final关键字修饰字符串数组,private final char value[],所以是不可变的。StringBuffer和StringBuilder类是集成自 AbstractStringBuilder 类, AbstractStringBuilder 类中的变量没有用final关键字修饰,所以是可变的。

线程安全性

String是不可变的,是常量,所以是线程安全的。StringBuilder和StringBuffer都继承自AbstractStringBuilder 类,StringBuffer对方法加了同步锁,所以是线程安全的,而StringBuilder没有加同步锁,所以是线程不安全的。

性能

对String类型进行改变是都会new一个新的string对象,并指向新的对象;

StringBuffer直接通过改变自身变量的值,StringBuilder相对能提升性能,但有线程不安全的情况。

2.== 与 equals

==用来判断两个对象是否相等,基础数据类型比较的是值是否相等。

没有重写对象的equals方法时,也是用来比较两个对象是否相等,重写equals方法可以比较内容相等,返回true

3.try/catch/finally中finally是否一定执行

在以下4种特殊情况下,finally块不会被执行:

1. 在finally语句块中发生了异常。

2. 在前面的代码中用了System.exit()退出程序。

3. 程序所在的线程死亡。

4. 关闭CPU。

4.接口和抽象类的区别?

1.抽象类中可以实现方法,抽象接口不能实现方法(jdk1.8以后可以实现方法);

2.接口中的变量默认是final类型,而抽象类不是;

3.类只能继承一个抽象类,而接口可以实现多个;

4.类继承接口需要实现所有方法,抽象类不需要;

5.接口不能用new实例化,可以声明。必须引用一个实现该接口的对象抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

5.Arraylist 与 LinkedList 异同

安全性

ArrayList与LinkedList都是线程不安全的。

数据结构实现

ArryayList是基于Object数组,而LinkedList是基于双向循环链表(JDK1.6之前为循环链表,之后取消了循环)

插入删除的影响

ArrayList采用数组存储,插入删除受元素位置的影响;

LinkedList采用链表存储,插入删除不收元素位置影响;

访问元素

LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。

实现了RandomAccess接口的list,优先选择普通for循环 ,其次foreach,未实现RandomAccess接口的list, 优先选择iterator遍历(foreach遍历底层也是通过iterator实现的),大size的数据,千万不要使用普通for循环

6.ArrayList 与 Vector 区别

Vector中所有方法都是同步的,在多线程操作中,同步操作会耗费时间,而ArrayList是不需要同步的,在不需要考虑线程安全时可以使用。

7.HashMap的底层实现

待补充完善

8.HashSet 和 HashMap 区别

HashMap实现Map接口,用来存储键值对,调用put方法添加元素,使用Key计算Hashcode,而HashSet实现Set接口,用来存储对象,调用add方法添加对象,使用对象计算hashcode,根据equals方法判断对象相等,执行效率的话,HashMap优与HashSet。

9.ConcurrentHashMap 和 Hashtable 的区别

ConcurrentHashMap 和 HashTable 的区别主要体现在实现线程安全的方式上不同。ConcurrentHashMap使用的是分段锁,HashTable使用的是同一把锁,因而在多线程并发中效率很低。

10.多线程实现方式

1.继承Thread类,2.实现Runnable接口,3.基于线程池实现

11.线程有哪些基本状态

线程基本状态

12.乐观锁和悲观锁

悲观锁,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程),乐观锁,总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。

使用场景

乐观锁适用于写比较少的情况,多写的情况使用悲观锁。

乐观锁的实现方式

乐观锁一般会使用版本号机制或CAS算法实现。

乐观锁的缺点

1.ABA问题;2.循环时间长,开销大;3.只能保证一个共享变量的原子操作

13.CAS算法

compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数

需要读写的内存值 V

进行比较的值 A

拟写入的新值 B

当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试

14.可重入锁和非可重入锁的区别?

 广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。

15.谈谈 synchronized和ReenTrantLock 的区别

1.两者都为可重入锁;2.synchronized是依赖于jvm,ReenTrantLock是依赖于API;3.ReenTrantLock 比 synchronized 增加了一些高级功能,主要有三点:1等待可中断;2可实现公平锁;3可实现选择性通知(锁可以绑定多个条件)

16.synchronized 关键字和 volatile 关键字的区别

volatile关键字是线程同步的轻量级实现,但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使synchronized 关键字的场景还是更多一些。

多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞

volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。

volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。

17.为什么使用线程池?

线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。为了降低资源消耗、提高响应速度、提高线程的可管理性

18.实现Runnable接口和Callable接口的区别

两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。

19.如何创建线程池

1.通过构造方法创建,通过 ThreadPoolExecutor创建

2.通过Executor 框架的工具类Executors来实现,可创建三种类型的ThreadPoolExecutor,FixedThreadPool(固定数量的线程池)、SingleThreadExecutor(单线程)、CachedThreadPool(可扩展的线程池)

20.AQS 原理

待补充

21. Java 内存区域

JAVA内存区域在JDK1.8之前分为:堆、方法区、直接内存、程序计数器、虚拟机栈、本地方法栈

JDK1.8之后将方法区删掉,引用直接内存。

线程私有的:程序计数器、虚拟机栈、本地方法栈

线程共享的:堆、方法区、直接内存 (非运行时数据区的一部分)

程序技术器的作用有两个:1.字节码通过改变程序计数器的位置来依次读取指令;2.用于记录当前程序运行的位置,以便线程切换时知道程序运行到哪一步。

JAVA虚拟机栈用于存放局部变量,局部变量表主要存放了基础数据类型和引用类型,虚拟机栈会抛出两种异常StackOverFlowError和OutOfMemoryError

本地方法栈虚拟机使用到的 Native 方法服务

堆,线程共享区域,用于存放对象实例,几乎所有的对象和数组都在这里分配内存。

方法区,线程共享区,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

22.JAVA对象的创建

类加载检查-->分配内存-->初始化零值-->设置对象头-->执行int方法

类加载检查:发起new指令创建对象时,先检查当前类是否已经被加载过,如果没有,先进行类加载过程。

分配内存:类加载检查完成后,虚拟机在堆中为类分配相应的内存。分配方式有两种:指针碰撞和空闲列表。选在以上方式中的哪一种,由JAVA堆是否规整来决定,而Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩")。内存分配并发问题通过cas+失败重试解决

初始化零值:虚拟机将分配到的内存空间初始化为零值(不包括对象头)。为了保证对象实例可以不赋初始值被引用。

设置对象头:虚拟机要对对象进行必要的设置,将类的信息存放在对象头。如元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。

执行int方法:对象实例创建完毕后,执行int方法为对象按照开发者初始化值

23.JAVA对象内存布局

在 Hotspot 虚拟机中,分为 3 块区域:对象头实例数据对齐填充

24.判断对象是否死亡


是否死亡

25.如何判断一个类是无用的类

满足三个条件:1.该类中的所有引用已经被回收;2.加载该类的classloader已经被回收;3.该类没有在任何一个地方被引用,无法在任何地方通过反射访问该类的方法。

满足这个三个条件也不一定会被回收

26.垃圾收集算法

1.标记-清除:将可回收对象进行标记,在标记完成后统一回收对象,会有两种问题:效率问题和空间问题(标记清除后会产生大量不连续的碎片)

2.复制算法:将内存分半大小相同两块,将存活对象复制到另一块内存。然后把剩余的空间全部清理掉。

3.标记-整理:将可回收对象标记,将存活对象向一端移动,最后将端点边界外的内存全部清理

4.分代收集:新生代对象每次都会有大量对象死去,所以可以选择复制算法,只需要复制很少的内存就可以清除掉;老年代对象,没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集

27.垃圾收集器


垃圾收集器

1.Serial收集器:

是单线程收集器,在收集时会暂停所有正在工作的线程,直到完成收集工作,与其他单线程收集器相比简单高效;

2.ParNew收集器:

其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。

3.Parallel Scavenge 收集器:与ParNew收集器

4.Serial Old 收集器:

Serial 收集器的老年代版本,它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案

5.CMS 收集器:

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。

主要四个阶段:初始标记、并发标记、重新标记、并发清除

优点:并发收集、低停顿

缺点:对 CPU 资源敏感;无法处理浮动垃圾;它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。

6.G1收集器:

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.

优点:

并行与并发:可利用系统中的多核来缩短系统停顿时间,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。

分代收集:但是还是保留了分代收集的概念。

空间整合:G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。

可预测的停顿:可预测收集时间

28.计算机网络分层

TCP/IP四层:应用层、网络层、传输层、网络接口层

TCP/IP五层:应用层、网络层、传输层、数据链路层、物理层

OSI7层:应用层、会话层、表示层、网络层、传输层、数据链路层、物理层

29. TCP、UDP 协议的区别

UDP传输时不需要建立连接,远程主机在收到报文后不需要给出任何确认。不提供可靠交付,在即时通讯中有用到。

TCP提供面向连接的服务,传输数据前需要先建立连接,数据传输完毕要释放连接,TCP可提供可靠的传输服务(TCP可靠性体现在传输数据前会先进行三次握手来建立连接,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),同时也会带来很多资源开销。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。

30.在浏览器中输入url地址 ->> 显示主页的过程

DNS解析-->建立连接-->发送HTTP请求-->服务器处理HTTP请求返回-->浏览器解析返回数据-->关闭连接

31.各种协议与HTTP协议之间的关系


用到的协议

32.TCP 三次握手和四次挥手

客户端–发送带有 SYN 标志的数据包–一次握手–服务端 

服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端 

客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端

为什么进行三次握手呢?

三次握手为了建立可靠的通信,为了确认客户端和服务端都发送接收正常。

第一次握手确认了server接收正常,第二次握手确认了server端接收正常、client端发送、接收正常,第三次握手确认了server端发送正常、接收正常,client端发送正常、接收正常

为什么四次挥手?

客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送

服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号

服务器-关闭与客户端的连接,发送一个FIN给客户端

客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1

33.MySQL 常见的两种存储引擎:MyISAM与InnoDB的理解

1.count运算上,Mylsam缓存有数据行数,执行效率上比InnoDB快;

2.事务控制和崩溃后安全恢复:MyISAM 强调的是性能,执行速度比InnoDB快,不支持事务控制,InnoDB支持事务的控制、回滚、崩溃后的安全恢复;

3.MyISAM不支持外键,而InnoDB支持。

MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。在数据库主从分离下,一般选用MyISAM作为主库存储引擎,需要事务支持,并发量写入很大时选择InnoDB,大量数据读取时选用MyISAM

34.数据库索引思维导图


思维导图

35.MySql存储结构

MySQL的基本存储结构是页(记录都存在页里边):各个数据页可以组成一个双向链表,每个数据页中的记录又可以组成一个单向链表。查找数据的过程简单来说,1. 定位到记录所在的页:需要遍历双向链表,找到所在的页;2. 从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了。

为什么使用索引以后会提高查询效率?索引相当于把无序的数据变成相对有序。

36.当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,常见的优化措施

1.限定数据范围,查询必须附带查询条件;

2.读写分离,主库负责写入数据,从库负责读数据;

3.垂直分区,简单来说就是讲一张表按照相关性进行拆分,将一张表拆成多个表;

4.水平分区,保持数据表结构不变,将数据分为多个表存储,水平拆分支持大数据量的存储。在实际使用中药进行分库分表,水平拆分支持非常大的数据量。

在实际使用中,尽量不对数据库进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度。

数据库分片的两种常见方案:

1.客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。

2.中间件代理:在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。

36.事务的特性

原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;

隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;

持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

37.并发事务带来的问题

脏读:当一个事务正在访问数据库对数据进行了修改,还没有提交,另一个事务访问的数据是还没有提交的数据,事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的

丢失修改:事务读取一条数据时修改,另一个事务也读取到数据并进行修改,事务一的修改将丢失。

不可重复读:在一个事务内多次读取数据,在这个事务还没有结束的时候,另一个事务修改了同一条数据并提交,可能会两次读取数据不一致的情况

幻读:幻读与不可重复读类似。在一个事务读取多条数据的时候,另一个事务插入了几条数据,再随后的查询中多出了几条数据。不可重复读的重点是修改,幻读的重点在于新增或者删除。

38.事务隔离级别

READ-UNCOMMITTED(读取未提交)、READ-COMMITTED(读取已提交)、REPEATABLE-READ(可重复读)、SERIALIZABLE(可串行化)

39.为什么要用 redis /为什么要用缓存

主要是用于高性能和高并发。

高性能:用户访问服务器直接读数据库中的数据是从硬盘中读取的,比较慢,将该用户访问的数据存到缓存中就可以直接从缓存中获取了,相当于直接从内存中获取,如果数据库中的数据改变改变相应的缓存数据即可。

高并发:直接操作缓存能够承受的请求是远远大于直接操作数据库的,可以把数据库中的部分数据放到缓存中,用户的请求只需要经过缓存就可以了。

40.为什么要用 redis 而不用 map/guava 做缓存?

缓存分为本地缓存和分布式缓存,Map和guava是通过本地缓存,随着jvm的销毁而结束,在多实例的情况下,每个实例保存一套缓存。不具有一致性。使用redis做分布式缓存,多个实例可共享一个缓存具有一致性。redis缓存缺点需要保持高可用性,架构也比较复杂

41.redis 和 memcached 的区别

1.redis支持更丰富的数据类型,redis支持String、list、set、zset、hash等,memcached 仅支持String类型。

2.redis的数据支持持久化,可存储到本地硬盘,memcached将数据存储在内存中;

3.redis支持集群部署,memcached不支持

4.memcached是多线程,非阻塞IO复用模型,redis是单线程多路IO复用模型

42.redis中的过期时间

redis中过期时间可以通过expire time设置,时间到期后会通过两种方式删除,定期删除和惰性删除。

redis 内存淘汰机制

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的).

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错

43.redis 持久化机制

Redis的一种持久化方式叫快照(snapshotting,RDB),Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。可以将快照复制到其他服务器从而创建具有相同数据的服务器副本。

另一种方式是只追加文件(append-only file,AOF),与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。

44.redis事务

Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。

45.缓存雪崩和缓存穿透问题解决方案(扩展)

缓存雪崩:缓存同一时间大面积失效,会导致请求全部落到数据库上,造成数据库无法短时间处理大量请求而崩掉。

事前:尽量保证redis的高可用,发现宕机立即补上,选择合适的内存淘汰策略

事中:本地ehcache缓存+hystrix限流&降级避免mysql崩掉

事后:依靠redis的持久化机制尽可能恢复数据

缓存穿透:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

46.如何解决 Redis 的并发竞争 Key 问题

使用分布式锁

redis实现:加锁时通过jedis.set(String key, String value, String nxxx, String expx, int time)方法为数据设置一个唯一key,和不存在时新增,存在时什么都不做,并设置一个过期时间。

解锁时通过首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)

使用缓存实现分布式锁的缺点

通过超时时间来控制锁的失效时间并不是十分的靠谱。

zookeeper实现:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。

你可能感兴趣的:(JAVA基础总结)