Java基础(三):Java异常机制以及底层实现原理

news2024/11/25 20:29:22

🌷一、异常

☘️1.1 什么是异常

Java异常是程序发生错误的一种处理机制,异常的顶级类是ThrowableThrowable字面意思就是可抛出的,该类是所有的错误和异常的超类,只有Throwable类或者Throwable子类的实例对象才可以被Java虚拟机抛出,或者被Java语句抛出。

🍇Throwable有两个子类ErrorException

  • Error:表示错误,比如内存溢出。

  • Exception:表示异常,比如IO异常。

🍈Exception又分为运行时异常和非运行时异常

  • 运行时异常:也叫做非检查异常,在代码中抛出运行时异常时,Java编译器在编译时不会报错,只有在运行的时候才可能会抛出该异常,RuntimeException类和该类的子类都是运行时异常。

  • 非运行时异常:也叫做检查异常或者编译时异常,Java编译器在编译的时候如果发现程序抛出了检查异常,那么编译器就会报错,需要我们在代码中处理,要么捕获,要么继续抛出。没有继承RuntimeException的异常都是非运行时异常。

Error类及其子类,都是运行时异常

🌱1.2 代码示例

1.2.1 运行时异常

运行时异常,比如ArrayIndexOutOfBoundsExceptionNullPointerExceptionRuntimeException,当程序抛出运行时异常时,即使我们没有处理,编译器也不会报错,这种抛出不一定是显示的抛出,更多的是在运行时出现异常之后JVM自动的抛出。

public void runException () {
    throw new RuntimeException();
}

1.2.2 非运行时异常

非运行时异常,比如RuntimeExceptionNoSuchMethodException,当程序抛出了非运行时异常时,我们必须处理,否则编译器编译时就会报错。非运行时异常更多是提供给开发者用于对可能出现的异常情况进行处理。

public void nonRunException () {
    try {
        throw new FileNotFoundException();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}

🌲1.3 异常处理方法

Java中异常处理方法有两种:继续抛出、捕获处理。

1.3.1 继续抛出

抛出异常有一个关键字:throwsthrows可以抛出多个异常,多个异常类用逗号分隔即可。

当方法中抛出异常之后,如果我们不对异常处理,可以在方法上使用throws抛出异常,这样就不需要方法处理异常,但是抛出异常的方法,需要调用者来处理异常。

1.3.2 捕获处理

异常的另外一种处理办法就是捕获处理,把异常在方法内部就处理掉,这样调用者调用方法时,不需要再处理异常。

捕获异常有几个关键字trycatchfinally

  • try:用于包裹异常的代码

  • catch:用于捕获异常之后的处理,可以有多个catch来捕获不同的异常,也可以用一个catch,不同的异常用符号|来分割

  • finally:无论如何都会执行的代码块

捕获异常有三种格式:

  • 第一种,用try捕获异常,然后在catch中处理。
try {
} catch () {
}
// 也可以有多个catch
try {
} catch () {
} catch () {
}
// 也可以在catch中直接捕获多个异常
try {
} catch (Exception1 | Exception2) { 
}
  • 第二种,用try捕获异常,在catch中处理异常,并且在finally中执行需要执行的代码
try {
} catch () {
} finally {
}
  • 第三种,捕获异常之后不处理,但是要在finally中执行需要执行的代码
try {
} finally {
}

🥬1.4 try-with-resources

try-with-reourcesJDK1.7出来的新语法,这个语法有两个目的:主要是自动关闭资源,为了减少对finally的依赖,同时使得代码变得简洁一些、同时还可以传递被抑制的异常。

1.4.1 自动关闭资源

我们先说第一个,我们先看一下如果用传统的try-catch是怎么使用的。

如果我们用try-catch-finally来关闭异常,可以发现关闭资源的代码比业务代码还要多,看起来也不是很简洁。

public static void main(String[] args) {
  FileInputStream fileInputStream = null;
  try {
    fileInputStream = new FileInputStream("");
  } catch (FileNotFoundException e) {
    e.printStackTrace();
  } finally {
    try {
      if (fileInputStream != null) {
        fileInputStream.close();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

现在我们用try-with-resource来改写这个,把需要关闭资源的放到try的括号里面,这样发生了异常会自动关闭。

public static void main(String[] args)  {
       
  try(FileInputStream fileInputStream = new FileInputStream("")) {
    int result = fileInputStream.read();
  } catch (IOException e) {
    e.printStackTrace();
  }
}

那么try-with-resource是怎么实现自动关闭,我们反编译一下字节码,可以看到本质上还是用的try-catch来实现的,只不过是在try里面调用了close

public static void main(String[] args) {
try {
    FileInputStream fileInputStream = new FileInputStream("file.txt");
    Object var2 = null;
    if (fileInputStream != null) {
        if (var2 != null) {
            try {
                fileInputStream.close();
            } catch (Throwable var4) {
                ((Throwable)var2).addSuppressed(var4);
            }
        } else {
            fileInputStream.close();
        }
    }
} catch (IOException var5) {
    IOException e = var5;
    e.printStackTrace();
}

1.4.2 传递抑制的异常

如果tryfinally都发生了异常,并且抛出那么finally中的异常就会覆盖try中的异常。

我们在一个方法里面先抛出一个FileNotFoundException异常,然后再finally里面关闭这个流的时候又会出现NullPointerException异常,在main方法进行捕获之后可以发现最后只打印了NullPointerException异常,而FileNotFoundException并没有打印出来。

public static void main(String[] args) {
    try {
        c();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static void c () throws IOException {
    FileInputStream fileInputStream = null;
    try {
        fileInputStream = new FileInputStream("");
    } catch (FileNotFoundException e) {
        throw e;
    } finally {
        try {
            fileInputStream.close();
        } catch (IOException e) {
            throw e;
        }
    }
}

打印出来的异常信息

java.lang.NullPointerException
	at com.lee.study.exception.Demo.c(Demo.java:26)
	at com.lee.study.exception.Demo.main(Demo.java:12)

我们再用try-with-resources改写一下,这里我们需要构造一个自定义的类,实现在关闭的时候也抛出异常。

public static void main(String[] args) {
        try (MyAutoCloseable myAutoCloseable = new MyAutoCloseable();) {
            myAutoCloseable.read();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}
// 实现了AutoCloseable接口可以自动的调用close方法
class MyAutoCloseable implements AutoCloseable {

public void read() throws Exception {
    throw new Exception("read");
}

@Override
public void close() throws Exception {
    throw new Exception("close");
}

打印出来的异常信息

java.lang.Exception: read
	at com.lee.study.exception.MyAutoCloseable.read(Demo.java:22)
	at com.lee.study.exception.Demo.main(Demo.java:12)
	Suppressed: java.lang.Exception: close
		at com.lee.study.exception.MyAutoCloseable.close(Demo.java:27)
		at com.lee.study.exception.Demo.main(Demo.java:13)

我们看下反编译代码,可以看到在finally中关闭的时候调用了一个方法addSuppressed,这个方法就是用来把这些抑制的异常都记录下来。

public static void main(String[] args) {
        try {
            MyAutoCloseable myAutoCloseable = new MyAutoCloseable();
            Throwable var2 = null;

            try {
                myAutoCloseable.read();
            } catch (Throwable var12) {
                var2 = var12;
                throw var12;
            } finally {
                if (myAutoCloseable != null) {
                    if (var2 != null) {
                        try {
                            myAutoCloseable.close();
                        } catch (Throwable var11) {
                            var2.addSuppressed(var11);
                        }
                    } else {
                        myAutoCloseable.close();
                    }
                }

            }
        } catch (Exception var14) {
            Exception e = var14;
            e.printStackTrace();
        }

    }

🌳1.5 自定义异常

除了Java中提供的异常之外,我们也可以自定义一些异常,自定义异常可以自定义运行时异常和非运行时异常两种方式。

1.5.1 自定义运行时异常

自定义运行时异常之需要继承RuntimeException这个方法即可,这里我们加了一个构造方法,这样可以创建异常对象时加一些描述异常信息。

public class DemoRuntimeException extends RuntimeException{
    public DemoRuntimeException (String message) {
        super(message);
    }
}

1.5.2 自定义非运行时异常

自定义非运行时异常只需要继承Exception,这里也是加了一个构造方法,描述异常信息。

public class DemoNonRuntimeException extends Exception{

    public DemoNonRuntimeException (String message) {
        super(message);
    }
}

🌸二、源码分析

🌴2.1 变量

// 用来保存栈信息的轨迹
private transient Object backtrace;

// 记录描述异常信息
private String detailMessage;

// 描述这个异常是哪个Throwable导致的,默认是当前Throwable
private Throwable cause = this;

// 记录异常抛出位置的栈信息,每个StackTraceElement代表一个栈
private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
private StackTraceElement[] stackTrace = UNASSIGNED_STACK;

//用来保存被屏蔽的异常对象,如果在finally中抛出了异常,就会覆盖try中的异常,这个是JDK 1.7引入的新特性
private static final List<Throwable> SUPPRESSED_SENTINEL =
  Collections.unmodifiableList(new ArrayList<Throwable>(0));
private List<Throwable> suppressedExceptions = SUPPRESSED_SENTINEL;

🌵2.2 构造方法

/**
 * 无参构造方法,只调用了fillInStackTrace方法
 */
public Throwable() {
  fillInStackTrace();
}

/**
 * @param message 描述异常的信息,打印异常信息会一并打印
 */
public Throwable(String message) {
  fillInStackTrace();
  detailMessage = message;
}

/**
 * @param message 描述异常的信息,打印异常信息会一并打印
 * @param cause 当前异常是哪个Throwable导致的
 */
public Throwable(String message, Throwable cause) {
  fillInStackTrace();
  detailMessage = message;
  this.cause = cause;
}

/**
 * @param cause 当前异常是哪个Throwable导致的
 */
public Throwable(Throwable cause) {
  fillInStackTrace();
  detailMessage = (cause==null ? null : cause.toString());
  this.cause = cause;
}

/**
 * @param message 描述异常的信息,打印异常信息会一并打印
 * @param cause 当前异常是哪个Throwable导致的
 * @param enableSuppression 是否开启记录被屏蔽的对象
 * @param writableStackTrace 是否开启记录异常栈信息
 */
protected Throwable(String message, Throwable cause,
                    boolean enableSuppression,
                    boolean writableStackTrace) {
  if (writableStackTrace) {
    fillInStackTrace();
  } else {
    stackTrace = null;
  }
  detailMessage = message;
  this.cause = cause;
  if (!enableSuppression)
    suppressedExceptions = null;
}

🌾2.3 fillInStackTrace

2.3.1 方法描述

在构造方法中都会先调用一下这个fillInStackTrace方法,这个方法主要是获取栈信息,并且对保存栈信息数组进行清空。

首先会判断当前保存栈信息数组stackTrace和栈信息轨迹backtrace是否为空,如果不为空的话调用nativefillInStackTrace获取栈信息,然后把保存栈信息数组stackTrace清空。

当创建Throwable传入的参数writableStackTracefalsestackTrace就会为空,这样就不会开启异常栈信息的记录。如果你不需要记录栈信息,在自定义异常时可以重新这个方法,直接返回this

public synchronized Throwable fillInStackTrace() {
  if (stackTrace != null || backtrace != null) {
    fillInStackTrace(0);
    stackTrace = UNASSIGNED_STACK;
  }
  return this;
}

private native Throwable fillInStackTrace(int dummy);

2.3.2 代码示例

先看一个例子,在main方法中调用a,并且捕获异常,打印异常信息,在a调用b,并捕获异常,打印异常信息。

public class Demo {

    public static void main(String[] args) {
        try {
            a ();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void a () throws Exception {
        try {
            c();
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    public static void c () throws Exception {
        throw new Exception("报错了");
    }
}

把异常信息打印出来,发现两次打印的是一样的,也就是捕获到异常又抛出,异常的栈轨迹是一样的。

java.lang.Exception: 报错了
	at com.lee.study.exception.Demo.c(Demo.java:25)
	at com.lee.study.exception.Demo.a(Demo.java:17)
	at com.lee.study.exception.Demo.main(Demo.java:9)
java.lang.Exception: 报错了
	at com.lee.study.exception.Demo.c(Demo.java:25)
	at com.lee.study.exception.Demo.a(Demo.java:17)
	at com.lee.study.exception.Demo.main(Demo.java:9)

我们将代码修改一下,在catch里面调用一下fillInStackTrace方法

public class Demo {

    public static void main(String[] args) {
        try {
            a ();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void a () throws Exception {
        try {
            c();
        } catch (Exception e) {
            e.printStackTrace();
            // 调用一下fillInStackTrace
            e.fillInStackTrace();
            throw e;
        }
    }

    public static void c () throws Exception {
        throw new Exception("报错了");
    }
}

再看一下打印的栈轨迹信息,第二次打印的时候已经没有调用b的轨迹了。其实调用这个方法,相当于是把异常栈的数组给清空了。

java.lang.Exception: 报错了
	at com.lee.study.exception.Demo.c(Demo.java:27)
	at com.lee.study.exception.Demo.a(Demo.java:17)
	at com.lee.study.exception.Demo.main(Demo.java:9)
java.lang.Exception: 报错了
	at com.lee.study.exception.Demo.a(Demo.java:21)
	at com.lee.study.exception.Demo.main(Demo.java:9)

2.3.3 爬栈

有时候我们不需要记录异常栈轨迹信息,有两种方法实现,第一个是在创建ThrowablewritableStackTrace设置为false,第二个是我们可以重写fillInStackTrace方法,直接返回this

定义一个异常,并且调用打印异常信息

public class DemoNonRuntimeException extends Exception{

    public DemoNonRuntimeException (String message) {
        super(message);
    }
}
public class Demo {

    public static void main(String[] args) {
        try {
            c ();
        } catch (DemoNonRuntimeException e) {
            e.printStackTrace();
        }
    }

    public static void c () throws DemoNonRuntimeException {
        throw new DemoNonRuntimeException("报错了");
    }
}

打印的异常信息

com.lee.study.exception.DemoNonRuntimeException: 报错了
	at com.lee.study.exception.Demo.c(Demo.java:17)
	at com.lee.study.exception.Demo.main(Demo.java:9)

如果我们重写fillInStackTrace方法

public class DemoNonRuntimeException extends Exception{

    public DemoNonRuntimeException (String message) {
        super(message);
    }

    public Throwable fillInStackTrace () {
        return this;
    }
}

public class Demo {

    public static void main(String[] args) {
        try {
            c ();
        } catch (DemoNonRuntimeException e) {
            e.printStackTrace();
        }
    }

    public static void c () throws DemoNonRuntimeException {
        throw new DemoNonRuntimeException("报错了");
    }
}

再打印异常信息,就会发现已经没有栈轨迹信息了。

com.lee.study.exception.DemoNonRuntimeException: 报错了

🌿2.4 printStackTrace

这个方法是用来打印异常栈信息的方法,我们对这个方法应该是最熟悉的。

private void printStackTrace(PrintStreamOrWriter s) {
  Set<Throwable> dejaVu =
    Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
  dejaVu.add(this);

  synchronized (s.lock()) {
    s.println(this);
    StackTraceElement[] trace = getOurStackTrace();
    for (StackTraceElement traceElement : trace)
      s.println("\tat " + traceElement);

    for (Throwable se : getSuppressed())
      se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);

    Throwable ourCause = getCause();
    if (ourCause != null)
      ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
  }
}

🍀2.5 getMessage

用于获取异常描述信息

public String getMessage() {
  return detailMessage;
}

🌹三、JVM处理异常原理

这里我们从字节码的角度来分析一下异常的实现原理,主要是try-catch的实现原理,因为throws时不处理异常的,只是继续抛出。

这里我们以三个例子来说明异常处理的原理:

🥇try-catch

🥈try-catch-finally

🥉try-catch-finally,并在finallyreturn

🍁3.1 分析前准备

3.1.1 安装字节码分析工具

因为需要字节码分析异常处理的原理,首先我们需要在idea中安装字节码工具:jclasslib插件。

安装好之后,我们找到需要分析的编译后的class文件,然后选择导航栏的View-Show Bytecode With Jclasslib查看字节码文件。

在这里插入图片描述

3.1.2 异常表

Java中,如果一个方法有try-catch处理,在被编译成字节码都会生成一个异常表,异常表是一个表格,每一个都代表一个异常处理。

在这里插入图片描述

🥇起始PC:代表这个异常处理器监控的字节码开始位置。

🥈结束PC:代表这个异常处理器监控的字节码结束位置(不包括最后一行)。

🥉跳转PC:代表监控范围内如果发生了异常,跳转到该行字节码去执行。

🍂3.2 try-catch

3.2.1 示例代码

public class Demo {

    public static void main(String[] args) throws Exception {
        invoke(0, 1);
    }

    public static void invoke (int a, int b) throws Exception {
        try {
            if (b == 0) {
                throw new ArithmeticException();
            }
            int c = a / b;
        } catch (ArithmeticException e) {
            throw new Exception();
        }
    }
}

3.2.2 字节码

 0 iload_1
 1 ifne 12 (+11)
 4 new #3 <java/lang/ArithmeticException>
 7 dup
 8 invokespecial #4 <java/lang/ArithmeticException.<init>>
11 athrow
12 iload_0
13 iload_1
14 idiv
15 istore_2
16 goto 28 (+12)
19 astore_2
20 new #5 <java/lang/Exception>
23 dup
24 invokespecial #6 <java/lang/Exception.<init>>
27 athrow
28 return

3.2.3 异常表

在这里插入图片描述

3.2.4 分析

  1. 在字节码11行athrow抛出了异常ArithmeticException

  2. JVM会去异常表中找,是否有ArithmeticException的异常处理器。

  3. 在异常表中有ArithmeticException的处理器,跳转到19行继续执行。

  4. 在27行又抛出了一个异常Exception

  5. JVM会继续在异常表中找,是否有Exception的异常处理器。

  6. 这里没有Exception的处理器,会返回到方法的调用者继续执行上述流程,如果还是没找到就结束线程。

🍃3.3 try-catch-finally

3.3.1 示例代码

public class Demo {

    public static void main(String[] args) throws Exception {
        invoke(0, 1);
    }

    public static void invoke (int a, int b) throws Exception {
        try {
            if (b == 0) {
                throw new ArithmeticException();
            }
            int c = a / b;
        } catch (ArithmeticException e) {
            throw new Exception();
        } finally {
            System.out.println("执行方法");
        }
    }
}

3.3.2 字节码

 0 iload_1
 1 ifne 12 (+11)
 4 new #3 <java/lang/ArithmeticException>
 7 dup
 8 invokespecial #4 <java/lang/ArithmeticException.<init>>
11 athrow
12 iload_0
13 iload_1
14 idiv
15 istore_2
16 getstatic #5 <java/lang/System.out>
19 ldc #6 <执行方法>
21 invokevirtual #7 <java/io/PrintStream.println>
24 goto 47 (+23)
27 astore_2
28 new #8 <java/lang/Exception>
31 dup
32 invokespecial #9 <java/lang/Exception.<init>>
35 athrow
36 astore_3
37 getstatic #5 <java/lang/System.out>
40 ldc #6 <执行方法>
42 invokevirtual #7 <java/io/PrintStream.println>
45 aload_3
46 athrow
47 return

3.3.3 异常表

在这里插入图片描述

3.3.4 分析

  1. 增加了finally之后,在异常表中多了两个异常处理器,并且都是any的,表示任何异常都可以捕获。

  2. 这两个异常处理器最终都跳转到36行执行

3.3.5 为什么finally一定会执行

分两种情况如果代码执行中发生了异常和没有发生异常

  • 发生了异常

    无论时在try还是在catch中,第二个和第三个异常处理器都会捕获到,都会跳转到36行继续执行也就是finally中的内容

  • 没有发生异常

    在编译的时候会把finally的字节码拷贝到try里面代码块的后面,这样也会执行,所以为什么字节码里面有两处finally的内容

🍄3.4 try-finally-return

3.4.1 示例代码

public class Demo {

    public static void main(String[] args) {
        invoke();
    }

    public static int invoke () {
        int a = 1;
        try {
            a = 2;
            return a;
        } finally {
            a = 3;
            return a;
        }
    }
}

3.4.2 字节码

从字节码分析中分析,当finally中有return之后,会将try里面的覆盖掉,在6-9就是return的内容,而try里面的return已经不见了。

 0 iconst_1 // 将1放入操作数栈
 1 istore_0 // 将1存储到局部变量表第一个位置
 2 iconst_2 // 将2放入操作数栈
 3 istore_0 // 将2存储到局部变量表第一个位置
 4 iload_0  // 加载局部变量表第一个位置元素放入操作数栈
 5 istore_1 // 将2存储到局部变量表第二个位置
 6 iconst_3 // 将3放入操作数栈
 7 istore_0 // 将3存储到局部变量表第一个位置
 8 iload_0 // 加载局部变量表第一个位置元素放入操作数栈
 9 ireturn // 返回
10 astore_2  
11 iconst_3 // 将3放入操作数栈
12 istore_0 // 将3存储到局部变量表第一个位置
13 iload_0 // 加载局部变量表第一个位置元素放入操作数栈
14 ireturn // 返回

🪴3.5 finally赋值

3.5.1 示例代码

public class Demo {

    public static void main(String[] args) {
        invoke();
    }

    public static int invoke () {
        int a = 1;
        try {
            a = 2;
            return a;
        } finally {
           a = 3;
        }
    }
}

3.5.2 字节码

 0 iconst_1 // 将1放入操作数栈
 1 istore_0 // 将1存储到局部变量表第一个位置
 2 iconst_2 // 将2放入操作数栈
 3 istore_0 // 将2存储到局部变量表第一个位置
 4 iload_0 // 从局部变量表中加载第一个位置元素放入操作数栈
 5 istore_1 // 将2存储到局部变量表第二个位置
 6 iconst_3 // 将3放入操作数栈
 7 istore_0 // 将3存储到局部变量表第一个位置
 8 iload_1 // 从局部变量表中夹在第二哥位置元素放入操作数栈,也就是2
 9 ireturn //返回
10 astore_2
11 iconst_3
12 istore_0
13 aload_2
14 athrow

3.5.3 分析

这里在finally中对a进行赋值,但是在try中返回,最后返回的值是2还是3?

这里返回的是2,虽然finally里面的内容一定会执行,但是从字节码中可以得知,在返回的时候是将局部变量表第二个位置的元素返回的,而3是存储在局部变量表第一个位置。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1648863.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

超详细——集成学习——Adaboost实现多分类——附代码

资料参考 1.【集成学习】boosting与bagging_哔哩哔哩_bilibili 集成学习——boosting与bagging 强学习器&#xff1a;效果好&#xff0c;模型复杂 弱学习器&#xff1a;效果不是很好&#xff0c;模型简单 优点 集成学习通过将多个学习器进行结合&#xff0c;常可获得比单一…

Xinstall广告效果监测,助力广告主优化投放策略

在移动互联网时代&#xff0c;APP推广已成为企业营销的重要手段。然而&#xff0c;如何衡量推广效果&#xff0c;了解用户来源&#xff0c;优化投放策略&#xff0c;一直是广告主和开发者面临的难题。这时&#xff0c;Xinstall作为国内专业的App全渠道统计服务商&#xff0c;以…

TCP四次挥手分析

TCP四次挥手分析 概念过程分析为什么连接的时候是三次握手&#xff0c;关闭的时候却是四次握手&#xff1f;为什么要等待2MSL&#xff1f; 概念 四次挥手即终止TCP连接&#xff0c;就是指断开一个TCP连接时&#xff0c;需要客户端和服务端总共发送4个包以确认连接的断开。 在…

有关string的部分接口

1.迭代器与反向迭代器(iterator-) 迭代器是可以用来访问string里面的内容的&#xff0c;这里来记录一下使用的方法。 里面用到了一个叫做begin函数和一个end函数&#xff0c;这两个都是针对string使用的函数。 s1.begin()函数是指向string内容的第一个元素 而s1.end()指向的则…

「新媒体营销必备」短链接生成,让你的内容更易传播!

在信息大爆炸的今天&#xff0c;无论是企业还是个人都需要有一个快速有效的方式让信息传播。而短链接生成的出现&#xff0c;为我们带来了极大的便利。 C1N短网址&#xff08;c1n.cn&#xff09;是一家致力于为用户提供快速、安全的短链接服务的公司。作为专注于短链接的品牌&…

Windows Server 2019虚拟机安装

目录 第一步、准备工作 第二步、部署虚拟机 第三步、 Windows Server 2019系统启动配置 第一步、准备工作 下载Windows Server 2019系统镜像 官网下载地址&#xff1a;Windows Server 2019 | Microsoft Evaluation Center VMware Workstation 17下载地址&#xff1a; 链…

阿里云国际服(alibabacloud)介绍、注册、购买教程?

一、什么是阿里云国际版&#xff1f; 阿里云分为国内版和国际版。国内版仅面向中国大陆客户&#xff0c;国际版面向全球客户。 二、国际版与国内版有何异同&#xff1f; 1&#xff09;异&#xff1a;除了目标客户不同&#xff0c;运营主体不同&#xff0c;所需遵守的法律与政…

暗区突围pc端下载教程 暗区突围pc端怎么下载

暗区突围pc端下载教程 暗区突围pc端怎么下载 《暗区突围》是一款刺激的第一人称射击游戏。目前pc版本要上线了&#xff0c;即将在5月正式上线。在这款游戏里&#xff0c;我们会在随机的时间、地点&#xff0c;拿着不一定的装备&#xff0c;跟其他玩家拼个高低&#xff0c;还需…

(十六)Servlet教程——Servlet文件下载

Servlet文件下载 文件下载是将服务器上的资源下载到本地&#xff0c;可以通过两种方式来下载服务器上的资源。第一种是使用超链接来下载&#xff0c;第二种是通过代码来下载。 超链接下载 在HTML或者JSP页面中使用超链接时&#xff0c;可以实现页面之间的跳转&#xff0c;但是…

开发环境虚拟环境学习记录

1、VS Code搭建python环境 下载好Visual Studio Code后&#xff0c;首先需要进入Visual Studio Code并安装支持python开发的插件&#xff1a; 2、虚拟环境 2.1、初识虚拟环境 概述&#xff1a;①、在使用Python语言的时候我们使用pip来安装第三方包&#xff0c;但是由于pip的…

Leetcode—138. 随机链表的复制【中等】

2024每日刷题&#xff08;129&#xff09; Leetcode—138. 随机链表的复制 实现代码 /* // Definition for a Node. class Node { public:int val;Node* next;Node* random;Node(int _val) {val _val;next NULL;random NULL;} }; */class Solution { public:Node* copyRan…

Linux动态库与静态库解析

文章目录 一、引言二、C/C源文件的编译过程三、静态库1、静态库的定义和原理2、静态库的优缺点3、静态库的创建和使用a、创建静态库b、使用静态库 四、动态库1、动态库的定义和原理2、动态库的优缺点3、动态库的创建和使用示例a、创建动态库b、使用动态库 五、动静态库的比较 一…

【Python小技巧】matplotlib不显示图像竟是numpy惹的祸

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、问题&#xff1a;df.plot() 显示不出图像二、尝试各种解决办法1. 增加matplotlib.use&#xff0c;设定GUI2. 升级matplotlib版本 三、numpy是个重要的库1. …

详解MySQL常用的数据类型

前言 MySQL是一个流行的关系型数据库管理系统&#xff0c;它支持多种数据类型&#xff0c;以满足不同数据处理和存储的需求。理解并正确使用这些数据类型对于提高数据库性能、确保数据完整性和准确性至关重要。本文将详细介绍MySQL中的数据类型&#xff0c;包括数值类型、字符…

算法分析 KMP算法中next值的计算、0/1背包问题

5.6.1 KMP算法中next值的计算 设模式的长度为m。用蛮力法求解 KMP算法中的 next值时&#xff0c;next[0]可直接给出&#xff0c;计算next[j](1<j<m-1)则需要在 T[0] …T[j-1]中分别取长度为j-1、..、2、1的真前缀和真后缀并比较是否相等&#xff0c;最坏情况下的时间代价…

2024------MySQL数据库基础知识点总结

-- 最好的选择不是最明智的&#xff0c;而是最勇敢的&#xff0c;最能体现我们真实意愿的选择。 MySQL数据库基础知识点总结 一、概念 数据库&#xff1a;DataBase&#xff0c;简称DB。按照一定格式存储数据的一些文件的组合顾名思义: 存储数据的仓库&#xff0c;实际上就是一…

Java初识继承

继承 文章目录 继承为什么需要继承继承中变量的访问特点继承中方法的访问特点继承的优缺点 概念:在Java中&#xff0c;继承是面向对象编程的一个基本特性。它允许我们定义一个新类&#xff0c;它从另一个已经存在的类继承其属性和方法。被继承的类称为父类或超类&#xff0c;新…

Linux进程——Linux进程与进程优先级

前言&#xff1a;在上一篇了解完一部分常见的进程状态后&#xff0c;我们先来把剩下的进程状态了解一下&#xff0c;再来进入进程优先级的学习&#xff01; 如果对前面Linux进程不太熟悉可以先阅读&#xff1a; Linux进程 本篇主要内容&#xff1a; 僵尸进程和孤儿进程 Linux进…

63-HDMI转VGA电路设计

视频链接 HDMI转VGA电路设计01_哔哩哔哩_bilibili HDMI转VGA电路设计 HDMI电路设计&#xff08;参考第13课&#xff09; VGA电路设计&#xff08;参考第15课&#xff09; DP转VGA电路设计&#xff08;参考第75课&#xff09; 1、HDMI转VGA转换器 2、HDMI转VGA简介 1、解…

融知财经:期货风险有多大,期货风险进行控制的方法

期货价格变化远大于股票市场&#xff0c;其风险也大于股票市场&#xff0c;当然其预期收益机会相对较高&#xff0c;因此期货更适合激进型投资者。在期货投资市场中&#xff0c;除了市场外的常见风险外&#xff0c;转型期投资者的投机心理和杠杆效应会增加期货的交易风险&#…