面试的java八股文*基础(2021.6.21之前)

自我介绍
介绍项目
在项目中我负责的模块

基础
非科班的还有问:你看过什么java的书籍

java并发的艺术
java成神之路
java的编程思想
effective Java

java的八大基本数据类型和默认值

整形  int0long0byte(0)  short  (0L)
字符  char(false)
浮点型  double(0.0d)  float(0.0f)
布尔型 boolean (0)

在这里插入图片描述

i++与++i

1、首先,单独拿出来说++i和i++,意思都是一样的,就是i=i+12、如果当做运算符来说,就是a=i++或者a=++i这样的形式。情况就不一样了。
	先说a=i++,这个运算的意思是先把i的值赋予a,然后在执行i=i+1;
	而a=++i,这个的意思是先执行i=i+1,然后在把i的值赋予a;


	举个例子来说,
	如果一开始i=4。那么执行a=i++这条语句之后,a=4,i=5;
	那么执行a=++i这条语句之后,i=5,a=5;
	同理,i----i的用法也是一样的。

JDK 和 JRE 有什么区别?

jdk是java的开发工具包,jre是java运行环境  jdk->jre->jvm

== 和 equals 的区别是什么?

"=="是判断两个变量或实例是不是指向同一个内存空间。
"equals"是判断两个变量或实例所指向的内存空间的值是不是相同。

java 中 IO 流分为几种?

字节流
字符流
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

重写与重载

重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。

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

java的面向对象:封装,继承,多态

封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,
如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个
类没有提供给外界访问的方法,那么这个类也没有什么意义了。
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加
新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过
使用继承我们能够非常方便地复用以前的代码。
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发
出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变
量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中
实现的方法,必须在由程序运行期间才能决定。

string stringBuffer 和stringBuilder的区别
在这里插入图片描述

成员变量与局部变量

1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的
变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所
修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员
变量和局部变量都能被 final 所修饰;
2. 从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存
在于堆内存,局部变量存在于栈内存
3. 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对
象的创建而存在,而局部变量随着方法的调用而自动消失。
4. 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情
况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不
会自动赋值。

构造方法的特征

1. 名字与类名相同;
2. 没有返回值,但不能用 void 声明构造函数;
3. 生成类的对象时自动执行,无需调用。

hashCode与equals

面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals
时必须重写 hashCode 方法?”
1. 如果两个对象相等,则 hashcode 一定也是相同的
2. 两个对象相等,对两个对象分别调用 equals 方法都返回 true
3. 两个对象有相同的 hashcode 值,它们也不一定是相等的
4. 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写
hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个
对象指向相同的数据)

线程与程序与进程

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的
过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空
间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工
作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就
是说程序是静态的代码。
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态
的。

final 关键字主要用在三个地方:变量、方法、类。

1. 对于一个 final 变量,如果是基本数据类型的变量,则其数值一旦在初始
化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不
能再让其指向另一个对象。
2. 当用 final 修饰一个类时,表明这个类不能被继承。final 类中的所有成员
方法都会被隐式地指定为 final 方法。
3. 使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承
类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将
final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用
带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行
这些优化了)。类中所有的 private 方法都隐式地指定为 final

Java 中的异常处理
在这里插入图片描述

BIO、NIO、AIO 有什么区别?

Bio是同步阻塞io
Nio是同步非阻塞io
Aio是异步非阻塞io

Object 类相关方法

getClass
获取当前运行时对象的 Class 对象。
hashCode
返回对象的 hash 码。
clone
拷贝当前对象, 必须实现 Cloneable 接口。浅拷贝对基本类型进行值拷贝,对引用类型拷贝引用;深拷贝对基本类型进行值拷贝,对引用类型对象不但拷贝对象的引用还拷贝对象的相关属性和方法。两者不同在于深拷贝创建了一个新的对象。
equals
通过内存地址比较两个对象是否相等,String 类重写了这个方法使用值来比较是否相等。
toString
返回类名@哈希码的 16 进制。
notify
唤醒当前对象监视器的任一个线程。
notifyAll
唤醒当前对象监视器上的所有线程。
wait
1、暂停线程的执行;2、三个不同参数方法(等待多少毫秒;额外等待多少毫秒;一直等待)3、与 Thread.sleep(long time) 相比,sleep 使当前线程休眠一段时间,并没有释放该对象的锁,wait 释放了锁。
finalize
对象被垃圾回收器回收时执行的方法。

序列化

Java 对象实现序列化要实现 Serializable 接口。

反序列化并不会调用构造方法。反序列的对象是由 JVM 自己生成的对象,不通过构造方法生成。
序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
如果想让某个变量不被序列化,使用 transient 修饰。
单例类序列化,需要重写 readResolve() 方法。

反射

在运行时动态的获取类的完整信息
增加程序的灵活性
JDK 动态代理使用了反射

Java 集合框架

List(线性结构)

ArrayList
Object[] 数组实现,默认大小为 10 ,支持随机访问,连续内存空间,插入末尾时间复杂度 o(1),插入第 i 个位置时间复杂度 o(n - i)。扩容,大小变为 1.5 倍,Arrays.copyOf(底层 System.ArrayCopy),复制到新数组,指针指向新数组。
Vector
类似 ArrayList,线程安全,扩容默认增长为原来的 2 倍,还可以指定增长空间长度。
LinkedList
基于链表实现,1.7 为双向链表,1.6 为双向循环链表,取消循环更能分清头尾。

在这里插入图片描述

Map(K,V 对)

HashMap
底层数据结构,JDK 1.8 是数组 + 链表 + 红黑树,JDK 1.7 无红黑树。链表长度大于 8 时,转化为红黑树,优化查询效率。
初始容量为 16,通过 tableSizeFor 保证容量为 2 的幂次方。寻址方式,高位异或,(n-1)&h 取模,优化速度。
扩容机制,当元素数量大于容量 x 负载因子 0.75 时,容量扩大为原来的 2 倍,新建一个数组,然后转移到新数组。
基于 Map 实现。
线程不安全。
HashMap (1.7) 多线程循环链表问题
在多线程环境下,进行扩容时,1.7 下的 HashMap 会形成循环链表。
怎么形成循环链表:
假设有一 HashMap 容量为 2 , 在数组下标 1 位置以 A -> B 链表形式存储。有一线程对该 map 做 put 操作,由于触发扩容条件,需要进行扩容。这时另一个线程也 put 操作,同样需要扩容,并完成了扩容操作,由于复制到新数组是头部插入,所以 1 位置变为 B -> A 。这时第一个线程继续做扩容操作,首先复制 A ,然后复制 B ,再判断 B.next 是否为空时,由于第二个线程做了扩容操作,导致 B.next = A,所以在将 A 放到 B 前,A.next 又等于 B ,导致循环链表出现。

在这里插入图片描述

HashTable
线程安全,方法基本全用 Synchronized 修饰。
初始容量为 11 ,扩容为 2n + 1 。
继承 Dictionary 类。
ConcurrentHashMap
线程安全的 HashMap。
1.7 采用分段锁的形式加锁;1.8 使用 Synchronized 和 CAS 实现同步,若数组的 Node 为空,则通过 CAS 的方式设置值,不为空则加在链表的第一个节点。获取第一个元素是否为空使用 Unsafe 类提供的 getObjectVolatile 保证可见性。
对于读操作,数组由 volatile 修饰,同时数组的元素为 Node,Node 的 K 使用 final 修饰,V 使用 volatile 修饰,下一个节点也用 volatile 修饰,保证多线程的可见性。
LinkedHashMap
LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。
TreeMap
有序的 Map,红黑树结构,可以自定义比较器来进行排序。
Collections.synchronizedMap 如何实现 Map 线程安全?
基于 Synchronized ,实际上就是锁住了当前传入的 Map 对象。

Set(唯一值)

HashSet
基于 HashMap 实现,使用了 HashMap 的 K 作为元素存储,V 为 new Object() ,在 add() 方法中如果两个元素的 Hash 值相同,则通过 equals 方法比较是否相等。
LinkedHashSet
LinkedHashSet 继承于 HashSet,并且其内部是通过 LinkedHashMap 来实现的。
TreeSet
红黑树实现有序唯一。

Java 多线程

synchronized
修饰代码块
底层实现,通过 monitorenter & monitorexit 标志代码块为同步代码块。
修饰方法
底层实现,通过 ACC_SYNCHRONIZED 标志方法是同步方法。
修饰类 class 对象时,实际锁在类的实例上面。
单例模式

偏向锁,自旋锁,轻量级锁,重量级锁
通过 synchronized 加锁,第一个线程获取的锁为偏向锁,这时有其他线程参与锁竞争,升级为轻量级锁,其他线程通过循环的方式尝试获得锁,称自旋锁。若果自旋的次数达到一定的阈值,则升级为重量级锁。
需要注意的是,在第二个线程获取锁时,会先判断第一个线程是否仍然存活,如果不存活,不会升级为轻量级锁。

Lock
ReentrantLock
基于 AQS (AbstractQueuedSynchronizer)实现,主要有 state (资源) + FIFO (线程等待队列) 组成。
公平锁与非公平锁:区别在于在获取锁时,公平锁会判断当前队列是否有正在等待的线程,如果有则进行排队。
使用 lock()unLock() 方法来加锁解锁。
ReentrantReadWriteLock
同样基于 AQS 实现,内部采用内部类的形式实现了读锁(共享锁)和写锁 (排它锁)。
非公平锁吞吐量高
在获取锁的阶段来分析,当某一线程要获取锁时,非公平锁可以直接尝试获取锁,而不是判断当前队列中是否有线程在等待。一定情况下可以避免线程频繁的上下文切换,这样,活跃的线程有可能获得锁,而在队列中的锁还要进行唤醒才能继续尝试获取锁,而且线程的执行顺序一般来说不影响程序的运行。
volatile

Java 内存模型
在这里插入图片描述
在多线程环境下,保证变量的可见性。使用了 volatile 修饰变量后,在变量修改后会立即同步到主存中,每次用这个变量前会从主存刷新。
禁止 JVM 指令重排序。
单例模式双重校验锁变量为什么使用 volatile 修饰?
禁止 JVM 指令重排序,new Object()分为三个步骤:申请内存空间,将内存空间引用赋值给变量,变量初始化。如果不禁止重排序,有可能得到一个未经初始化的变量。

线程的五种状态
New
Runnable
Blocked
Waiting(无限期等待)
Timed Waiting(有期限等待)
Terminated
wait()sleep()
 yield()
  join()
  
线程使用方式
继承 Tread 类
实现 Runnable 接口
实现 Callable 接口:带有返回值

Runnable 和 Callable 比较

方法签名不同, void Runnable.run() , V Callable.call() throws Exception
是否允许有返回值, Callable 允许有返回值
是否允许抛出异常, Callable 允许抛出异常。
提交任务方式, Callable 使用 Future<T> submit(Callable<T> task) 返回 Future 对象,调用其 get() 方法可以获得返回值, Runnable 使用 void execute(Runnable command)
hapens-before
如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
ThreadLocal
场景
主要用途是为了保持线程自身对象和避免参数传递,主要适用场景是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
原理
为每个线程创建变量副本,不同线程之间不可见,保证线程安全。使用 ThreadLocalMap 存储变量副本,以 ThreadLocal 为 K,这样一个线程可以拥有多个 ThreadLocal 对象。
实际
使用多数据源时,需要根据数据源的名字切换数据源,假设一个线程设置了一个数据源,这个时候就有可能有另一个线程去修改数据源,可以使用 ThreadLocal 维护这个数据源名字,使每个线程持有数据源名字的副本,避免线程安全问题。

线程池

分类
FixThreadPool 固定数量的线程池,适用于对线程管理,高负载的系统
SingleThreadPool 只有一个线程的线程池,适用于保证任务顺序执行
CacheThreadPool 创建一个不限制线程数量的线程池,适用于执行短期异步任务的小程序,低负载系统
ScheduledThreadPool 定时任务使用的线程池,适用于定时任务
线程池的几个重要参数
int corePoolSize, 核心线程数
int maximumPoolSize, 最大线程数
long keepAliveTime, TimeUnit unit, 超过 corePoolSize 的线程的存活时长,超过这个时间,多余的线程会被回收。
BlockingQueue<Runnable> workQueue, 任务的排队队列
ThreadFactory threadFactory, 新线程的产生方式
RejectedExecutionHandler handler) 拒绝策略
线程池线程工作过程
corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略

核心线程在线程池中一直存活,当有任务需要执行时,直接使用核心线程执行任务。当任务数量大于核心线程数时,加入等待队列。当任务队列数量达到队列最大长度时,继续创建线程,最多达到最大线程数。当设置回收时间时,核心线程以外的空闲线程会被回收。如果达到了最大线程数还不能够满足任务执行需求,则根据拒绝策略做拒绝处理。
线程池拒绝策略(默认抛出异常)
|:---|:---|
| AbortPolicy | 抛出 RejectedExecutionException |
| DiscardPolicy | 什么也不做,直接忽略 |
| DiscardOldestPolicy | 丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置 |
| CallerRunsPolicy | 直接由提交任务者执行这个任务 |
如何根据 CPU 核心数设计线程池线程数量
IO 密集型 2nCPU
计算密集型 nCPU+1
其中 n 为 CPU 核心数量,可通过 Runtime.getRuntime().availableProcessors() 获得核心数:。
为什么加 1:即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费。

网络编程

HTTP协议

  • 一次http请求的过程
- `用户输入url,浏览器本地解析url,如果在host文件中存有对应ip则访问对应ip,否则将域名交给DNS服务器,DNS服务器返回对应IP地址,应用层向ip地址发送http请求,然后是传输层TCP的三次握手确认连接,第一次是客户端向服务器发送syn,
- 第二次是服务器发送syn和ack到客户端,
- 第三次是客户端发送syn与ack确认,此时TCP握手成功,然后到网络层,通过ARP协议,使用ip解析出MAC地址,然后通过MAC地址在数据链路层传输数据,服务器接收到数据包后,由web服务器处理该请求,查找客户端请求的资源,并返回响应报文。
断开连接需要TCP4次挥手,任意一方可开始,机器1发送fin=1到机器2表示数据传输完毕,断开请求,机器2返回ack=1确认收到关闭请求,等待机器2的信息传送完毕后,机器2发送fin=1到机器1,此时机器2会有定时器等待机器1返回ack=1,如果没有返回,会重发fin=1,机器1返回ack=1确认关闭请求。`
  • http与https的区别
https需要去CA申请证书
http运行在TCP之上,所有传输内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输内容都是加密的
http默认端口80,HTTPS默认端口443
  • GET和POST区别
GET方法是从服务器获取资源
POST方法是向指定URI提交数据,数据放在body中
GET请求的URL有长度限制,而POST请求数据会放在消息体中,没有长度限制
GET请求会被浏览器主动cache,而post不会
GET请求在发送过程中产生一个TCP数据包,POST请求会产生两个数据包。对于GET请求,浏览器会将header和data一起发送,服务器返回响应,而POST请求,浏览器是先发送header,服务器响应100 continue,浏览器再发送data。

TCP协议
1.TCP三次握手与四次挥手,本文第一条
2.如何可以绕过三次握手?
在这里插入图片描述
4.TCP半连接队列和全连接队列
5.半连接队列:

数据结构
算法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小小刘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值