目录
1.2ArrayList和LinkedList的区别是什么?(java集合)
1.15 使用多线程,模拟实现红灯,绿灯,和黄灯的功能红灯--打出5个数字,接着黄灯亮--打印3个数字,接着绿灯亮--打印5个数字(多线程)
1.16java中的wait()和sleep()方法有什么不同?
1.23多线程中start()和run()方法的区别是什么?
5.5Spring中BeanFactory和FactoryBean的区别?
1.Java基础
1.1 面向对象的特征有哪些?(面向对象)
封装(可复用性):
属性封装:属性私有化;设计相关的公共getter和setter来达成对属性的间接访问。
逻辑封装:隐藏具体实现,提供对外方法接口供用户调用。
继承(可复用性):
从已有类中派生出新的类,新的类能够吸收已有的类的数据属性和行为,并能扩展新的能力。
多态(灵活性):两个类存在继承关系,存在方法的重写,在调用时,有父类引用指向子类对象。
1.2ArrayList和LinkedList的区别是什么?(java集合)
ArrayList和LinkedList两者都实现了List接口,但它们的底层不同。
(1) .ArrayList的底层是用数组实现的,而LinkedList的底层是链表。
(2).因为ArrayList的底层是数组,而数组是基于索引的数据结构。因此ArrayList查找数据是很快的,时间复杂度为O(1),但删除和插入的开销很大。
(3)因为LinkedList的底层是链表,所以,LinkedList在插入数据和删除数据时的开销优于ArrayList,但在查询搜索数据时的开销比ArrayList大。
(4)LinkedList需要更多的内存(链表的节点在内存中是不连续的)。
(5)读少,插入删除多推荐用LinkedList;读多,插入删除少,推荐用ArrayList。(不是绝对的)
1.3高并发中的集合有哪些问题?(多线程与数据结构)
(1)第一代线程安全集合类
(2)第二代线程非安全集合类
底层使用synchronized代码块锁。
(3)第三代线程安全集合类
java.util.concurrent.*
ConcurrentHashMap:
CopyOnWriteArrayList:
CopyOnWriteArraySet:
底层大都采用Lock锁,保证线程安全的同时,性能也很高。
1.4 什么是线程安全与不安全?(多线程)
当多线程时,才存在线程安全问题。在堆内存中的数据可以被任何线程访问到,在没有限制的情况下,存在被意外修改的风险,即堆内存的空间在没有保护机制的情况下,对于多线程来说,是不安全的。
解决方法: 1.私有化(放到栈内存中,例如局部变量,缺点:缩小了使用范围)---隔离
2.不共享(ThreadLocal类,每个线程各自拷贝一份)--隔离
3.只能看不能改(final)--标记
4.先入为主(线程的同步【互斥锁(悲观锁)】线程很多时)--标记
5.失败重试(CAS乐观锁)-->线程较少时
1.5java虚拟机的内存结构(JVM)
如下图:蓝色的为共享,橙色的是私有
1.方法区:存类的信息
2.堆:存对象的信息
3.虚拟机会创建一个名字叫main的主线程(即程序中的main方法),主线程的内存由JVM Stacks分配(JVM Stacks也负责分配线程本身的内存)
4.自定义方法内的局部变量和方法参数等利用的是栈的内存(JVM Stacks)
5.本地方法(例如:HashCode()等)引用本地方法栈
6.程序计数器:通俗地来说,就是用来记录当前的线程执行到了第几行代码。
1.6二分查找?(数据结构)
前提:已有有序数组A
1.定义左边界left,右边界right,确定搜索范围,循环执行二分查找(2,3)
2.获取中间索引middle=Floor((left+right)/2).
3.中间索引的值与待搜索的值T进行比较:
case 1: A[middle]==T,返回中间索引
case 2:A[middle]>T,说明待查找的值在middle左边,需要将右边界设置为(middle-1),重新查找。
case 3:A[middle]<T,说明待查找的值在middle右边,需要将左边界设置为(middle+1),重新查找。
4.若L>R时,表示没有找到,应当结束循环。
代码实现:
public class Solution {
public static void main(String[] args) {
int[] array={1,5,8,11,19,22,31,35,40,45,48,49,50};
int target=48;
int indx=binarySearch(array,target);
System.out.println(indx);
}
public static int binarySearch(int[] a,int target){
int left=0,right=a.length-1,middle;
while (left<=right){
middle=(left+right)/2;
if(a[middle]==target){
return middle;
} else if (a[middle]>target) {
right=middle-1;
}else {
left=middle+1;
}
}
return -1;
}
}
问题:以上,当left+right数值很大,溢出整数范围时应当如何解决?
解决方法:将中间索引赋值改为:middle=left+(right-lft)/2或(right+left)>>>1
例一:对于有序列表1,5,8,11,19,22,31,35,40,45,48,49,50当二分查找48时,查找成功需要比较几次?
思路:1.长度为奇数二分取中间的值;偶数二分取中间靠左的值。
例二:在拥有128个元素的数组中二分查找一个数,需要比较的次数最多不超过多少次?
思路一:128一直除2,直到1,中间除了几次,答案就是几;即2的几次方等于128,答案就是几
思路二:将问题转发为l2g128的对数运算。如果结果是整数,则为最终结果;如果结果为小数,则舍去小数部分,整数加一作为最终结果。
1.7单例模式方法一--饿汉式?(设计模式)
要点:构造私有;提供静态的成员单例类型属性(私有),并要求在声明时实例化;提供一个公共的静态方法getInstance()用于访问(获得)实例。代码如下:
public class SingletonOne {
private static final SingletonOne INSTANCE=new SingletonOne();
private SingletonOne(){
}
public static SingletonOne getInstance(){
return INSTANCE;
}
}
饿汉式单例的特点:单例类实例提前创建。
注意事项:单例模式的单例不是安全的。可以通过以下途径破坏
1.如果单例对象实现了Serializable接口,单例可能会被破坏;这种情况就是利用反序列化可能破坏单例。预防方法:重写public Object readResolve()方法,代码如下:
public class SingletonOne implements Serializable {
private static final SingletonOne INSTANCE=new SingletonOne();
private SingletonOne(){
if(INSTANCE!=null){
throw new RuntimeException("单例对象不能重复创建");
}
......
}
public static SingletonOne getInstance(){
return INSTANCE;
}
public Object readResolve(){
return INSTANCE;
}
}
2.可以通过反射直接调用私有的构造方法破坏单例。预防方法:在构造方法例加入判断来判断是否单例,代码如下:
public class SingletonOne {
private static final SingletonOne INSTANCE=new SingletonOne();
private SingletonOne(){
if(INSTANCE!=null){
throw new RuntimeException("单例对象不能重复创建");
}
//
......
}
public static SingletonOne getInstance(){
return INSTANCE;
}
}
3.Unsafe对象破坏单例,无法预防。
1.8单例模式方法二--懒汉式?(设计模式)
要点:构造私有;提供静态的成员单例类型属性(私有);提供一个公共的静态方法getInstance()用于访问(获得)实例。代码如下:
懒汉式单例模式代码如下:
public class SingletonTwo implements Serializable {
private static SingletonTwo INSTANCE=null;
private SingletonTwo(){
}
public static SingletonTwo getInstance(){
if(INSTANCE==null){
INSTANCE=new SingletonTwo();
}
return INSTANCE;
}
public Object readResolve(){
return INSTANCE;
}
}
懒汉式单例需要考虑线程安全问题。需要在方法前面加synchronized关键字,如下:
public class SingletonTwo implements Serializable {
private static SingletonTwo INSTANCE=null;
private SingletonTwo(){
}
public static synchronized SingletonTwo getInstance(){
if(INSTANCE==null){
INSTANCE=new SingletonTwo();
}
return INSTANCE;
}
public Object readResolve(){
return INSTANCE;
}
}
但以上方法性能较差。实际是首次创建单例对象时需要提供线程安全得保护,再此调用时并不需要加synchronized关键字进行保护,解决方法是使用DCL(双端检索)代码如下:
public class SingletonTwo implements Serializable {
private static volatile SingletonTwo INSTANCE=null;
private SingletonTwo(){
}
public static synchronized SingletonTwo getInstance(){
if(INSTANCE==null){
synchronized (SingletonTwo.class){
if (INSTANCE==null){
INSTANCE=new SingletonTwo();
}
}
}
return INSTANCE;
}
public Object readResolve(){
return INSTANCE;
}
}
注意 :volatile关键字。
1.9并行与并发的区别?(操作系统、多线程)
case1:单核cpu:
线程实际上是串行(利用时间片,在操作系统中,有一个部分叫任务管理器,它的作用是将cpu时间片【windows下的时间片最小为15ms】分给不同的程序使用,只是由于切换得很快,我们感觉是同时进行得)执行的。
即:微观串行,宏观并行。
所以,并发:线程(切换)轮流使用CPU;并发是在同一时间应对多件事情的能力。
case2: 多核cpu:
每个核都可以进行调度运行线程,这时候线程可以是并行的。
并行:是同一时间动手做多件事情的能力。
现在计算机以多核cpu居多,在多核cpu的情况下:
并发是在同一时间应对多件事情得能力,多个线程轮流使用一个或多个cpu
并行是同一时间动手做多件事情的能力,例如:4核cpu同时执行4个线程
1.10 程序计数器是什么?(JVM)
参考 1.5
程序计数器:线程是私有的(每一个线程都有一个程序计数器),内部保存的字节码的行号(可以理解为代码的行号,但有区别)。用于记录正在执行的直接骂指令的地址。
应用场景:线程切换
1.11 详细介绍java堆?(JVM)
java中的堆是一个线程共享的区域。主要用来保存对象实例、数组等,当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出OutOfMemoryError(内存溢出)异常。
所以,因为是线程共享的,所以存在线程安全问题。
在java8中,堆=年轻代+老年代
年轻代=Eden区+2个大小严格相同的Survivor(幸存者区)区
老年代用于保存生命周期长的对象,一般是一些老的对象。
1.12排序算法(数据结构)
1.12.1冒泡排序
代码实现如下:
public class BubbleSort {
public static void main(String[] args) {
int[] a={5,9,7,4,1,3,2,8};
for (int i=0;i<bubble(a).length;i++){
System.out.println(bubble(a)[i]);
}
}
public static int[] bubble(int[] a){
for ( int j=0;j<a.length;j++){
for (int i=0;i<a.length-1;i++){
if(a[i]>a[i+1]){
swap(a,i,i+1);
}
}
}
return a;
}
public static void swap(int[] a,int i,int j){
int temp=a[j];
a[j]=a[i];
a[i]=temp;
}
}
优化一后的代码如下,减少不必要得冒泡次数:
public class BubbleSort {
public static void main(String[] args) {
int[] a={5,9,7,4,1,3,2,8};
for (int i=0;i<bubble(a).length;i++){
System.out.println(bubble(a)[i]);
}
}
public static int[] bubble(int[] a){
for ( int j=0;j<a.length;j++){
boolean swapped=false;
for (int i=0;i<a.length-1;i++){
if(a[i]>a[i+1]){
swap(a,i,i+1);
swapped=true;
}
}
if (!swapped){
break;
}
}
return a;
}
public static void swap(int[] a,int i,int j){
int temp=a[j];
a[j]=a[i];
a[i]=temp;
}
}
1.13如何防止死锁?(多线程)
死锁简单来说就是两个或两个以上的线程,在执行过程中去争夺同一个共享资源,从而导致相互等待的现象。如果没有外部干预的情况下,线程就会一直处于阻塞状态,从而无法继续执行;这种一直处于等待阻塞状态的线程,就会成为死锁线程。
如何避免死锁?
只能外部干预,例如:重启程序,杀掉线程,因此,只能在写代码的时候尽量规避死锁的产生。
避免死锁,就是要破坏死锁产生的条件。但互斥条件是无法破坏的,但其他的条件可以通过人为干
预进行破坏。例如:“请求和保持条件”----可以在首次执行时一次性申请所有资源。
“不可抢占条件”------在线程申请其他资源申请不到的情况下,可以主动释放线程占有的资源。
“循环等待条件”-----按序申请资源来防止死锁的产生。
1.14什么是守护线程?(多线程)
Java中的线程可以分为两种,一种是用户线程,一种是守护线程。
一般情况下,如果不做特别的说明配置(使用.setDaemon(true/false)可以将线程设置为守护/用户线程),默认都是用户线程。
用户线程(User Thread)是系统的工作线程,它会完成这个程序需要完成的业务操作,例如:main线程
守护线程(Daemon Thread),也可称为服务线程,当程序中没有可服务的线程时会自动离开(假设当系统只剩下守护线程的时候,Java虚拟机会自动退出)。因此,守护线程的优先级比较低,用于为其他的线程等提供服务。
守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。
1.15 使用多线程,模拟实现红灯,绿灯,和黄灯的功能红灯--打出5个数字,接着黄灯亮--打印3个数字,接着绿灯亮--打印5个数字(多线程)
1.定义绿灯的任务
public class Green implements Runnable{
private Light light;
public Green(Light light) {
this.light = light;
}
@Override
public void run() {
while (true){
try {
this.light.green();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
2.定义红灯任务
public class Red implements Runnable{
private Light light;
public Red(Light light) {
this.light = light;
}
@Override
public void run() {
while (true){
try {
this.light.red();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
3.定义黄灯任务
public class Yellow implements Runnable{
private Light light;
public Yellow(Light light) {
this.light = light;
}
@Override
public void run() {
while (true){
try {
this.light.yellow();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
4.定义实体类和相关执行顺序等细节
public class Light {
private final Integer RED_STATE = 1;
private final Integer YELLOW_STATE = 3;
private final Integer GREEN_STATE = 2;
//AtomicInteger :JUC支持线程并发安全
private final AtomicInteger atomicInteger = new AtomicInteger(RED_STATE);
//默认使用的是非公平锁
//new ReentrantLock(true):采用公平锁
private final Lock lock = new ReentrantLock();
//为三种颜色的线程准备三个执行条件
private final Condition RED_CONDITION = lock.newCondition();
private final Condition YELLOW_CONDITION = lock.newCondition();
private final Condition GREEN_CONDITION = lock.newCondition();
//红灯亮前提:状态为1
/**
* synchronized:java语言级别支持的线程同步方式
* 可重入锁是Java API级别的线程同步方式
*/
public void red() throws InterruptedException {
//同步方法使用lock对具有线程安全的数据上锁
try {
//上锁后,不管程序是否发生异常,都必须要解锁,否则会出现死锁,可重入锁可以锁多次,锁了几次就需要解锁几次
lock.lock();//利用死循环空转,不会释放线程,直到得到CPU使用权为止
//if (lock.tryLock()) {//尝试得到CPU使用权,不会空转
while (this.atomicInteger.get() != RED_STATE) {
RED_CONDITION.await();
}
for (int i = 1; i <=5; i++) {
System.out.println("红灯亮" + i + "次");
}
Thread.sleep(1000);
atomicInteger.set(YELLOW_STATE);
//只有黄灯线程唤醒
YELLOW_CONDITION.signalAll();
// }
} finally {
//上锁后,不管程序是否发生异常,都必须要解锁,否则会出现死锁,可重入锁可以锁多次,锁了几次就需要解锁几次
lock.unlock();
}
}
public void yellow() throws InterruptedException {
try {
lock.lock();
// if (lock.tryLock()){
while (atomicInteger.get() != YELLOW_STATE) {
YELLOW_CONDITION.await();
}
for (int i = 1; i <=3; i++) {
System.out.println("黄灯亮" + i + "次");
}
Thread.sleep(1000);
atomicInteger.set(GREEN_STATE);
GREEN_CONDITION.signalAll();
// }
} finally {
lock.unlock();
}
}
public void green() throws InterruptedException {
try {
lock.lock();
// if (lock.tryLock()){
while (this.atomicInteger.get() != GREEN_STATE) {
GREEN_CONDITION.await();
}
for (int i = 1; i <=5; i++) {
System.out.println("绿灯亮" + i + "次");
}
Thread.sleep(1000);
atomicInteger.set(RED_STATE);
RED_CONDITION.signalAll();
// }
} finally {
lock.unlock();
}
}
测试类:
/*
*2、使用多线程,模拟实现红灯,绿灯,和黄灯的功能
红灯--打出5个数字,接着黄灯亮--打印3个数字,接着绿灯亮--打印5个数字
以此循环
使用可重入锁
*
* */
public class Test{
public static void main(String[] args) {
Light light=new Light();
new Thread(new Red(light),"RED").start();
new Thread(new Green(light),"GREEN").start();
new Thread(new Yellow(light),"YELLOW").start();
}
}
1.16java中的wait()和sleep()方法有什么不同?
共同点:
wait()、wait(long)和sleep(long)都能让当前线程暂时放弃CPU的使用权,进入阻塞状态。
不同点:
1.方法归属不同--sleep() 来自 Thread,wait() 来自 Object。
sleep是Thread的静态方法。
wait()和wait(long)都是Object的成员方法,每个对象都有。
2.醒来时机不同--sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。
执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来
wait(long)和wait()还可以被notify唤醒,wait()如果不唤醒会一直等下去
它们都可以被打断唤醒
3.锁的特性不同--sleep() 不释放锁;wait() 释放锁。
wait方法的调用必须先获取wait对象的锁,而sleep无此限制
wait方法执行后会释放对象锁,允许其它线程获得该对象锁
sleep方法如果在synchronized代码块中执行,并不会释放对象锁
1.17Java中 Integer和int的区别?
1.Integer是int的包装类,int是java中一种基本的数据类型
2.Integer实际是对象的引用,当new一个Integer时,实际生成一个指针指向对象,而int则直接存储数值
3.Integer的默认值为null,而int的默认值为0.
1.18值传递与引用传递问题
写出如下代码的执行结果:
public class TestStringBuffer {
public static void main(String args[]) {
StringBuffer a = new StringBuffer("A");
StringBuffer b = new StringBuffer("B");
mb_operate(a, b);
System.out.println(a + "." + b);
}
static void mb_operate(StringBuffer x, StringBuffer y) {
x.append(y);
y = x;
}
}
答:AB.B
1.19java中线程的状态有哪些?
在Java中,线程的运行状态被定义为6个枚举值,分别是:
1新建状态(NEW):线程已经建好,还没调用start()方法。
2.就绪状态(RUNNABLE):线程可能正在运行,也可能在就绪队列等待系统分配CPU资源。
3.阻塞状态(BLOCKED):线程处于等待(Monitor Lock)锁的状态。
4.等待状态(WAITTING):线程处于条件等待状态,当触发后,会被唤醒。
5.计时等待状态(TIMED_WAIT):与等待状态时一样的,只是比等待状态多了一个超时触发机制。
6.终止状态(TERMINATED):表示线程执行结束。线程的生命周期走到尽头,线程的资源释放。
1.20二叉树展开为链表(算法)
题目如下:114. 二叉树展开为链表 - 力扣(LeetCode)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public void flatten(TreeNode root) {
//先序遍历:根左右
if(root==null) return;
TreeNode right=root.right;
root.right=root.left;
root.left=null;
flatten(root.right);
flatten(right);
TreeNode p=root;
while(p.right!=null){
p=p.right;
}
p.right=right;
}
}
1.21 jsp八大隐含对象
名 | 类型 | 代表 | 作用 |
pageContext | PageContext | 当前页面上下文 | 获取页面中的其他的隐含对象,同时它还是一个域对象 |
exception | Throwable | 异常信息 | 获取页面中的异常 |
request | HttpServletRequest | 请求 | 可以获取用户发送的请求信息,他也是一个域对象 |
response | HttpServletResponse | 响应 | 向浏览器发送响应信息 |
application | ServcletContext | 代表整个WEB应用 | Java Web中最大的域对象 |
session | HttpSession | 当前会话 | 可以作为域的对象来共享数据 |
out | JspWriter | 输出流 | 可以向页面输出内容 |
config | ServlrtConfig | 当前JSP的配置信息 | 可以获取到servlet标签中初始化参数 |
page | Object在servlet方法中有如下代码Object page=this | 代表当前JSP的对象 |
1.22 编写程序实现1到100万中间素数个数的计算
分析:需要利用多线程对任务进行拆分,不能一个for循环写到底,后者会导致程序运行时间过长。
任务定义和任务拆分代码如下:
import java.util.concurrent.RecursiveTask;
public class IsPrimeCountTask extends RecursiveTask<Integer> {
/**
* 要统计的内容
* */
private int num;
/**
* 统计行开始的索引
* */
private int begin;
/**
* 设置每个操作行的数量
* **/
private int groupNumber= Runtime.getRuntime().availableProcessors();
private static final int THRESHOLE=10000000;
private int end;
public IsPrimeCountTask( int begin, int end) {
this.begin = begin;
this.end = end;
}
// 判断一个数是否为素数
public static boolean isPrime(int num) {
if (num <= 1) {
return false;
}
if (num <= 3) {
return true;
}
if (num % 2 == 0 || num % 3 == 0) {
return false;
}
// 只需要试除到平方根
for (int i = 5; i * i <= num; i += 6) {
if (num % i == 0 || num % (i + 2) == 0) {
return false;
}
}
return true;
}
@Override
protected Integer compute() {
int sum=0;
if((end-begin)+1<=THRESHOLE){
for (int i=begin;i<end;i++){
// System.out.println("begin=="+begin+" , end=="+end+", 计算i="+i);
if(isPrime(i)){
sum++;
}
}
}
else {//根据groupNumber进行分组,每一组创建一个wordCountTask
int middle=(begin+end)/2;
IsPrimeCountTask task1=new IsPrimeCountTask(begin,middle);
IsPrimeCountTask task2=new IsPrimeCountTask(begin+1,end);
task1.fork();
task2.fork();
sum=task1.join()+task2.join();
}
return sum;
}
测试类:
public class ForkJoinTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool pool = new ForkJoinPool();
IsPrimeCountTask isPrimeCountTask=new IsPrimeCountTask(1,1000000);
ForkJoinTask forkJoinTask = pool.submit(isPrimeCountTask);
System.out.println(isPrimeCountTask.get());
}
}
运行结果:
1.23多线程中start()和run()方法的区别是什么?
1.
start()用来启动线程,改变的是线程的状态,有就绪进入执行。
run()方法中包含要执行的代码,在执行状态状态时运行里面的代码(run() 方法用于执行线程的运行时代码)。
2.
run() 可以重复调用。
start() 只能调用一次。
1.24创建线程有哪几种方式?
创建线程有三种方式:
- 继承 Thread 重写 run 方法;
- 实现 Runnable 接口;(推荐)
- 实现 Callable 接口。(与实现Runnable接口的方式相比有返回值)
1.25H2O的生成(线程的同步)
题目如下:
class H2O {
Semaphore H=new Semaphore(2);
Semaphore O=new Semaphore(0);
public H2O() {
}
public void hydrogen(Runnable releaseHydrogen) throws InterruptedException {
H.acquire(1);
// releaseHydrogen.run() outputs "H". Do not change or remove this line.
releaseHydrogen.run();
O.release(1);
}
public void oxygen(Runnable releaseOxygen) throws InterruptedException {
O.acquire(2);
// releaseOxygen.run() outputs "O". Do not change or remove this line.
releaseOxygen.run();
H.release(2);
}
}
2.计算机网络
2.1 OSI七层模型
2.2.什么是网络四元组?
四元组:在TCP协议中,如何去确定一个客户端连接的组成要素。包括:源IP地址,目标IP地址,源端口号,目标端口号。
当一个客户端和服务端建立一个TCP连接是,通过四元组来确定唯一的一个TCP连接。
2.3.TCP的三次握手和四次挥手?
TCP协议是传输层的协议;传输层负责简历端到端的连接。
2.3.1TCP的三次握手
上图解释:主机A用户输入一个字符“C”后,产生一个TCP的段。段的序列号是42,ACK确认号是79。这两个数是建立连接的时候随机选择的。主机B回传这个字符,返回的时候的序列号是79,ACK是43,由于刚才来的那个段例只装了一个字符(刚好占位一个字节),所以,43=42+1,是这么来的。与此同时,我们确认了43之前的字节都已经接收成功。ACK消息就到主机A了。主机A再发一个确认,不带任何数据,这时序列号变成了43。ACK变成了80。利用这个确认刚才收到的消息。
(1) 服务端通过socket,bind和listen准备好接受外来的连接,此时服务端状态为Listen (2)客户端通过调用connect来发起主动连接,导致客户端TCP发送一个SYN(同步)字节,告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号,客户端状态为SYN_SENT。 (3)服务器确认(ACK)客户的SYN,并自己也发送一个SYN,它包含服务器将在同一连接中发送数据的初始序列号。 (4)客户端确认服务的ACK和SYN,向服务器发送ACK,客户端状态ESTABLISHED (5)服务器接收ACK,服务器状态ESABLISHED。
3.操作系统与linux
3.1线程与进程的区别?
当一个程序被运行,从磁盘程序的代码到内存中,这时就开启了一个进程。
一个进程内会有1到多个线程。
区别:
进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务
不同的进程使用不同的内存空间,在当前进程下的所有的线程可以共享内存空间。
线程更轻量,线程的切换成本要比进程的切换成本低
4.数据库相关
4.1数据库事务的隔离级别?
数据库事务隔离级别用于解决数据库并发事务问题。
1.读未提交的数据
2.读已提交的数据
3.可重复读数据
4.串行化(一般不用)
数据库事务的四大隔离级别如下图所示:
其中,mysql默认的事务隔离级别为Reaptable Read;Oricle默认的事务隔离级别为Read committed,从上到下,数据的安全性增加,但性能减弱。
# 查看事务的隔离级别
SELECT @@TRANSATION_ISAOLATION;
#设置事务的隔离级别,SESSION:当前会话。GLOBAL:所有会话。
SET [ SESSION | GLOBAL ] TRANSACTION ISAOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE};
4.2数据库的三大范式
第一范式(1NF):
确保数据库表字段的原子性(不可再分,数据表中存放的数据准确可靠),同时表必须要有一个主键(一个字段只维护一个信息)。
缺点:满足第一范式设计的数据表字段(数量会增加)。
第二范式(2NF):
(首先必须要满足第一范式)非主键列必须完全依赖于主键,而不能只依赖于主键的一部分。(一张数据表的所有列只用于描述一类实体信息)
缺点:满足第二范式设计的数据表,表的数量会增加。
第三范式(3NF):
(首先必须满足第二范式)必须有主键,有外键;使用主外约束维持表间关系。消除对主属性的传递依赖。
缺点:列变多,数据有冗余。
4.3.数据库事务的特性?
ACID保证是原子性(Atomicity)、一致性(Consisttency)、隔离性(Isolation)和耐用性(Durability)的缩写,是对数据库事务方面的一组要求。一般来说,系统对ACID保证越严格,则在性能上做出的让步就会越大。开发人员用ACID分类来交流不同方案所做的妥协,比如在聊NoSQL系统时。
1.原子性:无论成败,事务必须整体执行。
原子性事务不能被部分执行:或者整个操作都执行了,或者数据库保持原样。比如说要删除某个用户的所使用记录,如果作为一个事务的话,或者全删掉,或者一条都不删。最终不能是有些删掉了,有些没删掉,还保持原来的状态。甚至在系统出错或断电后,任然要保持原子性。原子性在这里的意思是不可再分。
2.一致性:始终确保约束条件。
成功完成的事务必须符合系统中定义的所有数据的完整性约束。例如:主键必须唯一、数据必须要符合某种特定的模式,或外键必须要指向所存在的实体。产生不一致状态的事务一般也会失败,然而小问题是可以自动解决的,比如:将数据转换为正确的形态。不要吧一致性的C与CAP定理中的C搞混了,那个C是指在读取分布式存储的数据时,确保呈现的是一个视图。
3.隔离性:并发事务不会相互干扰。
不管是并发还是线性执行,隔离性事务的执行结果应该都是一样的。系统的隔离水平会直接影响它执行并发操作的能力。全局锁是一种比较低幼的隔离方式,由于在事务期间会把整个数据库锁住,所以只能串行处理事务。这是很强的隔离性保证,但效率也极低:那些跟事务完全无关联的数据集根本不应该被锁住(比如说:一个用户添加评论时不应该导致另一个用户无法更新自己的个人资料)。在现实情况中,数据库系统会提供更加精细的和有选择性的锁方式(比如:锁表、锁记录或锁数据域),以实现各种程度的隔离水平。更复杂的系统甚至可能会采用隔离水平最低的锁方式,乐观地并行执行所有事务,直到检测到冲突时才会逐步细化锁模式。
4.耐用性:事务是永久性的。
事务的耐用性是对持久化生效的保证,在重启、断电、系统错误甚至硬件实践的情况下,持久化的效果依然不受影响。比如SQLite内存模式下的事务就没有耐用性,进程退出后所有数据都没了。而在SQLite把数据写到硬盘中时,事物的耐用性就很好,因为机器重启后数据还在。
5.框架相关
5.1Spring中常见的注解有哪些?
5.2Spring MVC中常用注解有哪些?
5.3Spring Boot中常用注解有哪些?
5.4 Spring MVC的执行流程?
5.5Spring中BeanFactory和FactoryBean的区别?
BeanFactory:以Factory结尾,说明BeanFactory是一个工厂类。负责生产和管理Bean的一个工厂接口,提供一个Spring Ioc容器规范;eanFactory是Spring容器的核心接口,它是用来管理和维护Bean实例的容器;BeanFactory提供了依赖注入、生命周期管理和AOP等功能,它是Spring IoC(控制反转)的基础;BeanFactory的实现类包括XmlBeanFactory、DefaultListableBeanFactory等,其中XmlBeanFactory已经被废弃,推荐使用ApplicationContext作为替代。
FactoryBean:FactoryBean是一个接口,它允许用户自定义Bean的创建过程,通常用于创建复杂的Bean对象;FactoryBean的实现类可以覆盖getObject()方法,该方法负责实际创建和返回Bean实例。(一种Bean创建的一种方式,对Bean的一种扩展。对于复杂的Bean对象初始化创建使用其可封装对象的创建细节,FactoryBean通常用于创建某些特殊类型的Bean,例如数据库连接池、RMI代理、代理工厂等。)
总结:BeanFactory是Spring的核心容器,用于管理和维护Bean对象的生命周期,提供了IoC和AOP的支持。FactoryBean是一个接口,允许用户自定义Bean的创建过程(通过工厂模式来生产Bean),通常用于创建特殊类型的Bean。FactoryBean的实现类在Spring中也是以Bean的形式存在,但它们用于封装自定义的创建逻辑。
5.6什么是AOP(Aspect-Oriented Programming)?
(AOP)面向切面编程:将公共逻辑(事务管理、日志、缓存等)封装为切面,和业务代码进行分离,可以减少重复代码和降低模块之间的耦合度。
5.7@Autowired和@Resource的区别?
@Autowired是Spring的注解,默认按照类型匹配(byType)
@Resource是J2ee的注解,默认按照byName模式自动注入,@Resource有两个重要的属性:name和type,name属性指定bean的名字,type属性指定bean的类型,如果使用name属性,则按照byName模式的自动注入策略;如果使用type属性,则按照byType模式的自动注入策略。如果既不指定name也不指定type,spring容器将通过反射技术默认按byName模式注入