Java基础

一、Java语言基础

Java语言的特点**

简单易学;

面向对象(封装,继承,多态);

平台无关性( Java 虚拟机实现平台无关性);

可靠性;

安全性;

支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);

支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);

编译与解释并存;

Java和C++的区别

JDK与JRE、JVM

JDK: 顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。

JRE: 普通用户而只需要安装 JRE(Java Runtime Environment)来运行 Java 程序。而程序开发者必须安装JDK来编译、调试程序。

JVM: 当我们运行一个程序时,JVM 负责将字节码转换为特定机器代码,JVM 提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是 java 程序可以一次编写多处执行的原因。

区别与联系:

  • JDK 用于开发,JRE 用于运行java程序 ;
  • JDK 和 JRE 中都包含 JVM ;
  • JVM 是 java 编程语言的核心并且具有平台独立性。

标识符的合法性

答:由字母、数字、_和$组成,长度不限。其中字母可以是大写或小写的英文字母,数字为0到9。标识符第一个字符不能是数字。标识符区分大小写,标识符不能包含空格。

Java基本数据类型及字节数

取值范围
整型int4*8
short2*8-32768~32767
long8*8
byte1*8-128~127
布尔型boolean1*8flase,true
字符型char1*8
浮点型flaot4*8float类型的数值有一个后缀F(例如:3.14F)
double8*8没有后缀F的浮点数值(如3.14)默认为double类型

隐式类型转换、显示类型转换

当将占位数少的类型赋值给占位数多的类型时,Java自动使用隐式类型转换。当把在级别高的变量的值赋给级别低的变量时,必须使用显示类型转换运算。

和&&的区别

答:&&和||是短路的与和或,当左边条件成立时,不在判断右边的条件。而&和|是将两边条件均运算完毕后再判断结果。

switch case 支持的数据类型

  • 基本数据类型:byte, short, char, int
  • 包装数据类型:Byte, Short, Character, Integer
  • 枚举类型:Enum
  • 字符串类型:String(Jdk 7+ 开始支持)

switch不支持double这样的浮点型

char 型变量中能不能存贮一个中文汉字,为什么

char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char类型占2个字节(16比特),所以放一个中文是没问题的。

补充:使用Unicode意味着字符在JVM内部和外部有不同的表现形式,在JVM内部都是Unicode,当这个字符被从JVM内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以Java中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如InputStreamReader和OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务。

基本数据类型判断相等时,为什么不用“==”,==比较的是什么?

存储精度问题,一般使用阈值,a-b 小于这个阈值,一般就认为相等。

对于基础类型,比较的是他们的值,对于对象类型,比较的是对象的属性

一个十进制的数在内存中是怎么存的?

以二进制补码形式存储,最高位是符号位,正数的补码是它的原码,负数的补码是它的反码加1,在求反码时符号位不变,符号位为1,其他位取反

为啥有时会出现4.0-3.6=0.40000001这种现象?

原因简单来说是这样:2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。

equal()和hashcode()

  • 两者关系

    • 如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同;
    • 如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false)
  • 为什么重写equals还要重写hashcode

    为了提高程序的效率才实现了hashcode方法,先进行hashcode的比较,如果不同,那没就不必在进行equals的比较了,这样就大大减少了equals比较的次数

  • 若对一个类不重写,它的equals()方法是如何比较的?

​ 如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;

值传递与参数(引用)传递

  • 对于基本数据类型,引用指实际对象,在方法中对形参赋值不会改变实参的值

  • 对于string等对象类型,引用的是实际对象引用的副本,在方法中对形参赋值不会改变实参的值,但是调用自身改变属性(如append()方法)则会改变实参

    赋值只是将副本引用指向新的对象,实际引用的对象没变

    而调用对象自身的属性方法,副本引用与实际引用指向同一个对象,副本引用改变对象属性后会作用到实际引用对象

编译执行与解释执行

a) 编译方式:Compilation:针对当前的机器处理器芯片,将源程序全部翻译成机器指令,称做目标程序,再将目标程序交给计算机执行.

b) 解释方式:Interpretation:这种方式不产生整个的目标程序,而是根据当前的机器处理器芯片,边翻译边执行,翻译一句执行一句.

Java程序执行流程

a) 首先编写java源文件(扩展名为.java的文本文档)。

b) 用javac命令把源文件编译成字节码文件(.class文件)

c) 用java命令执行字节码文件。

java有哪些API

java.lang,java.util,java.util.concurrent,java.util.concurrent.atomic

http://tool.oschina.net/apidocs/apidoc?api=jdk-zh

JDK 1.8 引入的5个新特性

1)接口的默认方法,java 8 允许我们给接口添加一个非抽象方法,只需使用 default 关键字。

2)lambda 表达式,在 java8 之前,若想将行为传入函数,仅有的选择是匿名类,而定义行为最重要的那行代码,却混在中间不够突出。lambda 表达式取代了匿名类,编码更清晰。

3)函数式接口:指仅仅只有一个抽象方法的接口,每一个该类型的 lambda表达式都会被匹配到这个抽象方法。每一个 lambda 表达式都对应一个类型,通常是接口类型,我们可以把 lambda 表达式当作任意只包含一个抽象方法的接口类型,为了确保接口一定达到这个要求(即有一个抽象方法),你只需要给你的接口加上@FunctioalInterface 注释(编译器若发现标注了这个注释的接口有多于一个抽象方法,则报错)。

4)lambda 作用域,在 lambda 表达式中访问外层作用域和老版本的匿名对象中的方法很相似,你可以直接访问标记了 final 的外层局部变量或实例的字段以及静态变量。lambda 表达式对外层局部变量只可读不可写,对类实例变量可读也可写。

5)dateAPI:java8 在 java.time 包中包含一组全新日期 API。

6)annotation 注释,java8 支持可重复注解,相同的注解可以在同一地方使用多次

  • 说说Lamda表达式的优缺点。
    • 优点:代码简洁,开发迅速 ,方便函数式编程 ,并行计算 java引入lambda,改善了集合操作(引入Stream API)
    • 缺点:代码可读性变差 ,性能方面,在非并行计算中,很多计算未必有传统的for性能要高不容易进行调试

I/O流、BIO/NIO、annotation

  • IO分类情况

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zjfcKa3g-1571881499756)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571471987594.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9KwyCZgw-1571881499757)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571472012213.png)]

  • 用Java代码列出一个目录下所有的文件?

    如果只要求列出当前文件夹下的文件,代码如下所示:

    import java.io.File;
    
    class Test12 {
    
        public static void main(String[] args) {
            File f = new File("/Users/Hao/Downloads");
            for(File temp : f.listFiles()) {
                if(temp.isFile()) {
                    System.out.println(temp.getName());
                }
            }
        }
    }
    

    如果需要对文件夹继续展开,代码如下所示:

    import java.io.File;
    
    class Test12 {
    
        public static void main(String[] args) {
            showDirectory(new File("/Users/Hao/Downloads"));
        }
    
        public static void showDirectory(File f) {
            _walkDirectory(f, 0);
        }
    
        private static void _walkDirectory(File f, int level) {
            if(f.isDirectory()) {
                for(File temp : f.listFiles()) {
                    _walkDirectory(temp, level + 1);
                }
            }
            else {
                for(int i = 0; i < level - 1; i++) {
                    System.out.print("\t");
                }
                System.out.println(f.getName());
            }
        }
    }
    

    在Java 7中可以使用NIO.2的API来做同样的事情,代码如下所示:

    class ShowFileTest {
    
        public static void main(String[] args) throws IOException {
            Path initPath = Paths.get("/Users/Hao/Downloads");
            Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {
    
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
                        throws IOException {
                    System.out.println(file.getFileName().toString());
                    return FileVisitResult.CONTINUE;
                }
    
            });
        }
    }
    
  • NIO NIO详解

NIO主要应用于文件IO与网络IO

可简单认为:IO是面向流的处理,NIO是面向块(缓冲区)的处理

    • 面向流的I/O 系统一次一个字节地处理数据,是阻塞的
    • 一个面向块(缓冲区)的I/O系统以块的形式处理数据,是非阻塞的

NIO主要有三个核心部分组成

  • buffer缓冲区
  • Channel管道
  • Selector选择器

异常(Error和Exception)

https://www.cnblogs.com/hysum/p/7112011.html

  • java中异常的体系是怎么样的呢?

1.Java中的所有不正常类都继承于Throwable类。Throwable主要包括两个大类,一个是Error类,另一个是Exception类;

img

2.其中Error类中包括虚拟机错误和线程死锁,一旦Error出现了,程序就彻底的挂了,被称为程序终结者;

img

3.Exception类,也就是通常所说的“异常”。主要指编码、环境、用户操作输入出现问题,Exception主要包括两大类,非检查异常(RuntimeException)和检查异常(其他的一些异常)

img

4.RuntimeException异常主要包括以下四种异常(其实还有很多其他异常,这里不一一列出):空指针异常、数组下标越界异常、类型转换异常、算术异常。RuntimeException异常会由java虚拟机自动抛出并自动捕获**(就算我们没写异常捕获语句运行时也会抛出错误!!)**,此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。

img

5.检查异常,引起该异常的原因多种多样,比如说文件不存在、或者是连接错误等等。跟它的“兄弟”RuntimeException运行异常不同,该异常我们必须手动在代码里添加捕获语句来处理该异常,这也是我们学习java异常语句中主要处理的异常对象。

img

  • try-catch-finally语句

    (1)try块:负责捕获异常,一旦try中发现异常,程序的控制权将被移交给catch块中的异常处理程序。

    【try语句块不可以独立存在,必须与 catch 或者 finally 块同存】

    (2)catch块:如何处理?比如发出警告:提示、检查配置、网络连接,记录错误等。执行完catch块之后程序跳出catch块,继续执行后面的代码。

    编写catch块的注意事项:多个catch块处理的异常类,要按照先catch子类后catch父类的处理方式,因为会【就近处理】异常(由上自下)。

    (3)finally:最终执行的代码,用于关闭和释放资源。

  • throw与throws的比较

    throw --将产生的异常抛出,是抛出异常的一个动作

    throws-- 声明异常

​ 1、throws出现在方法函数头;而throw出现在函数体。
​ 2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
​ 3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

  • 运行时异常与受检异常
    • 运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生
    • 受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发
    • Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常

java 序列化

  • 什么是Java对象的持久化?

    POJO(PlainOrdinaryJavaObject,简单的Java对象)创建后保存在JVM堆中,当对象回收后边无法访问对象的属性,因此需提前保存

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nP4EagzC-1571881499759)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571472821087.png)]

    Serialization是Java内置的一个 API,通过这个步骤,将 对象状态信息转化为字节流的方式,用于存储或者作为信息进行传输;而后再根据需要,通过反序列化的方式,将字节流转换成初始的对象

  • 什么是Serializable?

    SerializableJava.io包中的一个内置接口,是一个空的接口声明,**仅用作标识实现该接口的类可以被序列化。**不实现 Serializable这个接口,就会报出 NotSerializationException这个异常。

  • Serializable序列化的作用

    1. 将对象的状态保存在存储媒体中以便可以在以后重写创建出完全相同的副本;
    2. 按值将对象从一个从一个应用程序域发向另一个应用程序域。

    Serializable接口的作用就是可以把对象存到字节流,然后可以恢复。所以你想如果你的对象没有序列化,怎么才能进行网络传输呢?要网络传输就得转为字节流,所以在分布式应用中,你就得实现序列化。如果你不需要分布式应用,那就没必要实现实现序列化

  • 在使用前应该注意的几个问题

    1. 静态变量在序列化过程中并不会被保存:序列化保存的是对象状态,而静态变量是属于类变量,因此并不会被保存。
    2. 一个类能不能序列化,就看它是否实现 Serializable这个接口。并且如果子类的父类实现了该接口,那么这个子类也可以实现序列化。
    3. serialVersionUID:这是一个与可序列化的类相关联的版本号,在反序列化的过程中,这个序列号用来验证是不是该类进行的序列化过程。匹配成功,则进行反序列化,若 serialVersionUID匹配不成功,那么就会抛出 InvalidClassException这个异常。虽然可以不人为设定这个数值,但该接口的 API强烈建议开发者自行对这个版本号进行显式声明,并且规定这个值必须是 static类型和 final类型。通常一般我们也会将其设为 priavte私有变量的表述方式,例如: priavtestaticfinallongserialVersionUID=1L
    4. Transient关键字控制的变量不会序列化到目标文件中,当反序列化完成后,这个 Transient修饰的变量将被赋予Java规定的初始值。例如在下图中,字符串 gender被赋予了默认值 null
  • 如何持久化一个对象:序列化与反序列化?

    • 对象的序列化过程

      在这个步骤中需要 ObjectOutputStream将对象的基本数据类型写入流中。ObjectOutputStream很独特,它只能将支持 Serializable接口的对象写入字节流。默认的对象序列化机制写入字节流的内容包括:对象的类、类签名、非静态字段的值。

      //将对象写入字节流
      ObjectOutputStream objectOutputStream;try{    
        // 将一个OutputStream对象作为构造函数的参数创建对象,这里是文件对象            objectOutputStream = new ObjectOutputStream(new   FileOutputStream("tempFile"));    // 将小明这个对象写入到这个文本中    objectOutputStream.writeObject(user);    //关闭流    objectOutputStream.close();
        }catch (IOException e)
        {    e.printStackTrace();
        }
      
    • 反序列化

    ObjectInputStream方法来读入需要复原的字节流,然后调用 readObject的方法转换成对象。在这个过程中,会对序列号 serialVersionUID进行校验工作,以确保类型匹配正确。

    File file = new File("tempFile");ObjectInputStream objectInputStream;try{    // 将存储小明对象的文件流作为参数来创建一个对象    objectInputStream = new ObjectInputStream(new FileInputStream(file));    // 从这个字节流中读取对象,并强制转换为User类对象    User myUser = (User) objectInputStream.readObject();    System.out.println(myUser);}catch (IOException e){    e.printStackTrace();}catch (ClassNotFoundException e){    e.printStackTrace();}
    
  • Java 的序列化算:

    1)当前类的描述

    2)当前类属性的描述

    3)父类描述

    4)父类属性

    5)父类属性值描述

    6)子类属性值描述

  • Json与FastJson库

    Json是一种轻量级的数据交换格式,结构清晰,简明易读

    阿里巴巴提供了一个非常好用的工具FastJson,它是阿里巴巴开源的Json解析库,并且支持将JavaBean序列化为Json字符串,也可以从Json字符串反序列为JavaBean。

​ 使用FastJson对JavaBean进行序列化与反序列化简两个过程都只需要一行代码便可以完成相关操作。例如FastJson对小明进行序列化,见如下代码:

User user = new User();
user.setName("明");
user.setGender("male");
user.setAge(25);
user.setWork("Programmer");
user.setWords("hello");
String jsonData = JSONObject.toJSONString(user);
System.out.println(jsonData);

通过JSONObject调用 toJSONString的方法,传入需要序列化的对象,只需要一行代码,就可以得到对象序列化之后的字符串。通过打印就可以看出这个对象里面到底有些啥。

img

java时间与日期

  • 打印某一时刻(昨天,未来某天,日历)

  • java.util.Date

    java.util.Date对象,如何格式化成如下格式 YYYY-MM-dd hh:mm:ss

    SimpleDateFormat formate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    formate.format(new Date()));
    

java采用什么字符集。该字符集有多少字符

答:Java 使用 unicode 字符集,所以常量共有 65535 个

广义表即我们通常所说的列表(lists) 深度和广度的求法

  • 广义表长度的求法:长度的求法为最大括号中的逗号数加1。
  • 广义表深度的求法:深度的求法为每个元素的括号匹配数加1的最大值。

String

  • string不可变的好处
    • 缓存hash值,作为hashmap的Key.
    • string pool值保存一份string字面量
    • 安全性,网络连接参数用string可保证不被改变
    • 线程安全,被final修饰,无法再被改变
  • String, StringBuffer and StringBuilder
    • String 不可变,StringBuffer 和 StringBuilder 可变
    • String线程安全,StringBuffer 是线程安全的,内部使用 synchronized 进行同步,StringBuilder 不是线程安全的
  • String pool
    • 在 Java 7 之前,String Pool 被放在运行时常量池中, Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
    • 第一次调用intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
  • string 比较
  • 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
  • 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
	      String s1 = new String("计算机");
	      String s2 = s1.intern();
	      String s3 = "计算机";
	      System.out.println(s2);//计算机
	      System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象,
	      System.out.println(s3 == s2);//true,因为两个都是常量池中的String对象

3 String 字符串拼接

		  String str1 = "str";
		  String str2 = "ing";
		  String str3 = "str" + "ing";//常量池中的对象
		  String str4 = str1 + str2; //在堆上创建的新的对象	  
		  String str5 = "string";//常量池中的对象
		  System.out.println(str3 == str4);//false
		  System.out.println(str3 == str5);//true
		  System.out.println(str4 == str5);//false

尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。

String与基本数据类型的互转

  • string转化为int型,double型

​ Integer.parseInt(“1”);

Double.parseDouble(“25.45”);

  • int型,double型转string

    (1)num + “”

    (2)String.valueOf(num)

    (3)Integer.toString(num)

  • string 函数

    • StringTokenizer:按照指定的分隔符,将字符串解析成若干语言符号

    • Character.isDigit(‘a’) :判断一个字符是不是数字

    • Character.isUpperCase(‘U’):判断一个字符是不是大写

    • GB2312编码的字符串转换为ISO-8859-1编码的字符串:

      String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1");

数组

数组的声明与初始化

dataType[] arrayRefVar;
dataType arrayRefVar[];  // 效果相同,但不是首选方法
创建并初始化一维数组
arrayRefVar = new dataType[arraySize];     
设置初始值
dataType[] arrayRefVar = {value0, value1, ..., valuek};
Arrays.fill(arrayRefvar,defaultVal)
创建并初始化二维数组
type[][] typeName = new type[typeLength1][typeLength2];
String类型二维数组
String s[][] = new String[2][];
s[0] = new String[2];
s[1] = new String[3];

char[] data = str.toCharArray();// 将字符串转为数组
Arrays.toString(intArr)  //int转换为string类型 
数组扩容 
Array.copyOf(E[] e,newLength);

二、Java语言基础

https://www.nowcoder.com/questionTerminal/4437fe0059884f848af282c0f6c54724?toCommentId=2495996

img

数组(Array)和列表(ArrayList)有什么区别?

Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。

Array大小是固定的,ArrayList的大小是动态变化的。

ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。 对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

  • ArrayList,LinkedArrayList,Vector
    1. ArrayList 和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢
    2. Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器。
    3. LinkedList使用双向链表实现存储,

Arrays类

Set

Map

HashMap与Hashtable的区别

父类线程安全允许null值扩容方式哈希
HashMapAbstractMapkey和value都允许默认大小是16,而且一定是2的指数,2*old自己实现的hash函数
HashtableDictionarykey和value都不允许默认大小是11,old*2+1key.hashCode()
  • ​ HashMap结构是数组+链表(节点超过八个自动转成红黑树)

  • ​ Hashtable的每一个方法都加上了synchronized,因此是线程安全的

  • 一般不使用Hashtable,使用**Collections.**synchronizedMap(Map map);将非线程安全的map转换成线程安全的map。或者使用java.util.concurrent.ConcurrentHashMap

  • 多线程并发操作hashmap的问题

  • hash值如何计算

    HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash

  • loadFactor

    loadFactor加载因子是控制数组存放数据的疏密程度,loadFactor越趋近于1,那么 数组中存放的数据(entry)也就越多,也就越密

ConcurrentHashMap

ConcurrentHashMap1.8之前是使用segment分段锁实现,1.8之后直接采用一个大数组使用cas和synchronized(针对首节点进行加锁)

  • JDK 1.7: Segment+HashEntry组成

img

当执行put操作时,会进行第一次key的hash来定位Segment的位置,如果该Segment还没有初始化,即通过CAS操作进行赋值,然后进行第二次hash操作,找到相应的HashEntry的位置,这里会利用继承过来的锁的特性,在将数据插入指定的HashEntry位置时(链表的尾端),会通过继承ReentrantLock的tryLock()方法尝试去获取锁,如果获取成功就直接插入相应的位置,如果已经有线程获取该Segment的锁,那当前线程会以自旋的方式去继续的调用tryLock()方法去获取锁,超过指定次数就挂起,等待唤醒。

  • 并发操作计算size

    • 第一种方案他会使用不加锁的模式去尝试多次计算ConcurrentHashMap的size,最多三次,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的;
    • 第二种方案是如果第一种方案不符合,他就会给每个Segment加上锁,然后计算ConcurrentHashMap的size返回。
  • JDK 1.8 Node数组+链表(红黑树)

    img

  • put 操作

    这个put的过程很清晰,对当前的table进行无条件自循环直到put成功,可以分成以下六步流程来概述。

    1. 如果没有初始化就先调用initTable()方法来进行初始化过程
    2. 如果没有hash冲突就直接CAS插入
    3. 如果还在进行扩容操作就先进行扩容
    4. 如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入,
    5. 最后一个如果该链表的数量大于阈值8,就要先转换成黑红树的结构,break再一次进入循环
    6. 如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容
  • 两个版本的区别

    • JDK1.8的实现降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)
    • JDK1.8版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使用synchronized来进行同步,所以不需要分段锁的概念
    • JDK1.8使用红黑树来优化链表

TreeMap

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10skdOR3-1571881499760)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1568171464459.png)]

  • TreeMap实现了NavigableMap接口,而NavigableMap接口继承着SortedMap接口,致使我们的TreeMap是有序的
  • TreeMap底层是红黑树,它方法的时间复杂度都不会太高:log(n)~
  • TreeMap是利用红黑树构造的有序map
  • 非同步
  • 使用Comparator或者Comparable来比较key是否相等与排序的问题
  • key一定是不能为null
  • 值得说明的是:如果使用的是compareTo(T o)方法来比较,key一定是不能为null,并且得实现了Comparable接口的。
  • 即使是传入了Comparator对象,不用compareTo(T o)方法来比较,key也是不能为null的
  • TreeMap非同步的,想要同步可以使用Collections来进行封装

TreeMap有序是通过Comparator来进行比较的,如果comparator为null,那么就使用自然顺序

  • LinkedHashMap底层是双向链表,可以实现LRU

hash解决散列冲突的方法

  • 拉链法” :将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

  • 开放定址法:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中

    Hi=(H(key)+di)% m i=1,2,…,n

    其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有以下三种:

    • 线性探测再散列:di=1,2,3,…,m-1
    • 二次探测再散列:di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )
    • 伪随机探测再散列:di=伪随机数序列。

Set

  • HashSet集合

    • A:底层数据结构是哈希表(是一个元素为链表的数组)
  • TreeSet集合

    • A:底层数据结构是红黑树(是一个自平衡的二叉树)
    • B:保证元素的排序方式
  • LinkedHashSet集合

    • A::底层数据结构由哈希表和链表组成。
  • HashSet

    HashSet 是一个不允许存储重复元素的集合

    它是将存放的对象当做了 HashMap 的健,value 都是相同的 PRESENT 。由于 HashMapkey 是不能重复的,所以每当有重复的值写入到 HashSet 时,value 会被覆盖,但 key 不会受到影响,这样就保证了 HashSet 中只能存放不重复的元素。

Collection 与Collections

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rs5IpB2v-1571881499761)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1568170761186.png)]

  1. Collection与Collections的区别

Collection是Java集合框架中的基本接口;

Collections是Java集合框架提供的一个工具类,其中包含了大量用于操作或返回集合的静态方法。

在Java 6中Arrays.sort()和Collections.sort()使用的是MergeSort,而在Java 7中,内部实现换成了TimSort,其对对象间比较的实现要求更加严格

  1. Collections.sort排序内部原理
  2. Collection.sort的两种排序方式**
  • Collection.sort(list list) 按自然顺序排序,元素必须有可比较性,
  • 实现 comparable 接口,Collection.sort(list list,comparator)

Iterator和ListIterator

链接:https://www.nowcoder.com/questionTerminal/9dbbd35ff35e4e008fdd792c2b539

我们在使用List,Set的时候,为了实现对其数据的遍历,我们经常使用到了Iterator(迭代器)。
使用迭代器,你不需要干涉其遍历的过程,只需要每次取出一个你想要的数据进行处理就可以了。但是在使用的时候也是有不同的。
List和Set都有iterator()来取得其迭代器。对List来说,你也可以通过listIterator()取得其迭代器,两种迭代器在有些时候是不能通用的,Iterator和ListIterator主要区别在以下方面:

  1. ListIterator有add()方法,可以向List中添加对象,而Iterator不能
  2. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
  3. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
  4. 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。 因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。
    其实,数组对象也可以用迭代器来实现。 org.apache.commons.collections.iterators.ArrayIterator就可以实现此功能。
    一般情况下,我们使用Iterator就可以了,如果你需要进行记录的前后反复检索的话,你就可以使用ListIterator来扩展你的功能,(有点象JDBC中的滚动结果集)。
    ListIterator是一个双向迭代器。ListIterator没有当前元素,它的当前游标是位于调用next()和previsous()返回的元素之间。不过下面举的例子有点问题:下面的例子是n+1个元素。如果有n个元素,那么游标索引就是0…n共n+1个。
    注意:romove和set方法不是针对当前游标的操作,而是针对最后一次的next()或者previous()调用

快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?**

一:快速失败(fail—fast)

​ 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的结构进行了修改(增加、删除),则会抛出Concurrent Modification Exception。

​ 原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果结构发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

​ 注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。

​ 场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。

​ 二:安全失败(fail—safe)

​ 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

​ 原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。

​ 缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

​ 场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

三、Java面向对象

面向对象的特征有哪些方面?

继承,多态,封装,抽象

面向过程和面向对象的区别

  • 面向过程

优点: 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

缺点: 没有面向对象易维护、易复用、易扩展

  • 面向对象

优点: 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护

缺点: 性能比面向过程低

java中四种修饰符的限制范围。

public 公有的,任何类都可以访问。

protected 受保护的,同一个包的类可以访问,不同包的子类可以访问

default 同一个包的可以访问

private 私有的,在同一个类中可以访问。

Java 中的重载和重写的区别

Overload 特点:

​ 1.发生在同一个类中

​ 2.方法名相同,参数列表(个数,类型)不同

​ 3.遵循编译期绑定,根据参数的类型来访问. 修饰词 返回值 并未做要求,所以说修饰词和返回值可同可不同!

Overwite特点:

​ 1.发生在父子类中

  1. 方法名和参数(统称签名)必须相同

  2. 遵循运行期绑定,根据对象的类型来绑定

  3. 注意: 子类重写父类的方法,有两同,两小,一大的原则:

    1)两同:方法名称相同,参数相同

    2)两小:子类方法抛出的异常,小于或等于父类异常

    ​ 子类方法返回值为引用类型时,小于等于父类

    ​ 父类方法返回值为viod或基本类型时必须相同

​ 3)一大: 子类方法的访问权限必须大于或等于父类

重载的方法能否根据返回类型进行区分?

overload可以改变返回值的类型

抽象类和接口有什么异同?

从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

1.接口的方法默认是public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法
2.接口中的实例变量默认是final类型的,而抽象类中则不一定
3.一个类可以实现多个接口,但最多只能实现一个抽象类
4.一个类实现接口的话要实现接口的所有方法,而抽象类不一定
5.接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象

6.一个抽象类可以有构造方法,接口没有构造方法

静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同?

接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类

静态方法,静态内部类和内部类

  1. Static Nested Class是被声明为静态的内部类,它可以不依赖于外部类实例被实例化,而通常的内部类需要在外部类实例化后才能实例化
  2. 静态方法中不可访问非静态变量,因为静态方法不依赖类被实例,但非静态变量以来静态变量。
  3. 一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝,实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它
  4. 一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。
  5. 内部类可以继承其他类或实现其他接口

对象的clone

有两种方式:
  1). 实现Cloneable接口并重写Object类中的clone()方法;
  2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆

泛型

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。

  • 什么是泛型擦除?

Java语言的泛型采用的是擦除法实现的伪泛型,泛型信息(类型变量、参数化类型)编译之后通通被除掉了。使用擦除法的好处就是实现简单、非常容易Backport,运行期也能够节省一些类型所占的内存空间。而擦除法的坏处就是,通过这种机制实现的泛型远不如真泛型灵活和强大。

泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换成它们非泛型上界。

如果要调用实际类型的方法,属性,则必须利用externds给出该类型的上界。

public class Manipulator2<T extends HasF> {
    private T obj;
    public Manipulator2(T x) { obj = x; }
    public void manipulate() { obj.f(); }
}

泛型类型参数将擦除到它的第一个边界(可以有多个边界)。编译时,Java编译器会将T擦除成HasF,就好像在类的声明中用HasF替换了T一样。

https://www.jianshu.com/p/2bfbe041e6b7

  1. 怎么理解java的object 是所有类的父类。说说为什么要把那些方法放在object中

    1. 万物皆对象,有一个对象可以管理。

    2. 第二方便垃圾回收机制,这样所有的对象都会有垃圾回收的方法,而不用每一个对象都进行不同的定义。

    3. 三集合类方便,不用为了实现集合重复的实现类别,因为所有的对象都是object的子类,所以对于集合来说只要put和add只要是object类型就可以满足,不用因为不同的集合去实现不同的方法。

  2. private修饰的方法可以通过反射访问,那么private的意义是什么

    • 对用户调用类方法或属性的一种约束
    • 从外部调用对象时,可以清晰看到类的结构,并且只关注可以调用的部分方法或属性
  3. Object类的hashCode和equals方法的作用是什么,子类重写时需要注意什么?

  4. object中定义了哪些方法?

    clone(), equals(), hashCode(), toString(), notify(), notifyAll(),
    wait(), finalize(), getClass()

  5. 对象间的clone

  6. 面向接口编程优点:1)接口经过合理设计之后,有助于程序设计的规范化,可以并行
    开发,提高工作效率;2)实现了程序的可插拔性,降低耦合度。

  7. 有一个类有一个成员变量和一个方法,当创建100个该类对象时,内存里有多少个成员变量和多少个方

  8. 接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)?

  9. 一个类声明实现一个接口,那么这个类需要做什么工作。

    实现接口里的所有方法,并且这些方法的访问权限必须的public。

  10. 什么是抽象类。什么是抽象方法。有什么特点。

    a) 用关键字abstract修饰类称为抽象类,abstract类不能用new运算创建对象,必须产生其子类,由子类创建对象。

    b) 用关键字abstract修饰方法称为抽象方法,abstract方法,只允许声明,而不允许实现。

  11. 子类能继承父类的哪些变量和方法。

    如果子类和父类在同一包中,那么子类可以继承父类中的不是private的成员变量作为自己的成员变量,并且也继承了父类中不是private的方法作为自己的方法。

    如果子类和父类不在同一个包中,那么,子类继承了父类的protected、public 成员变量做为子类的成员变量,并且继承了父类的protected,public 方法为子类的方法。

  12. 什么是类的成员变量,局部变量,实例成员变量,类成员变量

    a) 变量定义部分所定义的变量被称为类的成员变量。

    b) 在方法体中定义的变量和方法的参数被称为局部变量。

    c) 成员变量又分为实例成员变量和类成员变量(static修饰)。

    个类的所有对象共享一个类变量

  13. 成员变量的作用范围。局部变量的作用范围。

​ a) 成员变量在整个类内都有效

b) 局部变量只在定义它的类内有效。

  1. 什么是类方法,什么是实例方法,类方法有什么特点?

    类方法用static修饰。无static修饰的方法为实例方法。

    类方法只能调用类变量和类方法。

  2. 构造方法

    构造方法是一种特殊方法,它的名字必须与它所在的类的名字完全相同,并且不返回任何数据类型。

    如果类中没有构造方法,系统会提供一个默认的构造方法,默认的构造方法是无参的。

    在创建对象的时候,java虚拟机会调用类的构造方法来创建对象。一般对象的初始化工作可以放在构造方法里。

    构造方法不可以被继承。

  3. Java泛型和类型擦除

    泛型即参数化类型,在创建集合时,指定集合元素的类型,此集合只能传入该类型的参数。类型擦除:java编译器生成的字节码不包含泛型信息,所以在编译时擦除:1.泛型用最顶级父类替换;2.移除。

  4. 为什么可以直接用类名来访问类成员变量和类方法。

    当类被加载到虚拟机的时候,类成员变量就被分配内存,类方法被分配入口地址,所以不用创建对象,可以直接通过类名调用。

  5. 类方法有什么特点。

    类方法只能调用类变量和类方法。

  6. final关键字可以用来修饰什么。分别起什么作用?

    a) final可以修饰类,这样的类不能被继承。

    b) final可以修饰方法,这样的方法不能被重写。

    c) final可以修饰变量,这样的变量的值不能被修改,是常量。

  7. extends、super关键字的作用。

    a) 使用super调用父类的构造方法。

    b) 使用super操作被隐藏的成员变量和方法。

    extends上限通配符,用来限制类型的上限。super下限通配符,用来限制类型的下限

  8. 向上转型。

    假设A 类是B 类的父类,当我们用子类创建一个对象,并把这个对象的引用放到父类的对象中时,我们称这个父类对象是子类对象的上转型对象。

    a) 上转对象不能操作子类新增的成员变量,失掉了这部分属性,不能使用子类新增的方法,失掉了一些功能

    b) 上转型对象可以操作子类继承或重写的成员变量,也可以使用子类继承的或重写的方法。

  9. Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?

    匿名的内部类是没有名字的内部类。不能extends(继承) 其它类,但一个内部类可以作为一个接口,由另一个内部类实现。

  10. 内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?

  11. 一个".java"源文件中是否可以包含多个类(不是内部类)?有什么限制?

  12. 怎么实现一个不可变类:

    • 将类声明为final,所以它不能被继承

    • 将所有的成员声明为私有的,这样就不允许直接访问这些成员

    • 对变量不要提供setter方法

    • 将所有可变的成员声明为final,这样只能对它们赋值一次

    • 通过构造器初始化所有成员,进行深拷贝(deep copy)

    • 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝

  13. 内部类一共有 4 种。1)成员内部类:外部类的成员。2)局部内部类。3)静态内部类:
    类似于静态成员。4)匿名内部类:实现一个接口 or 继承一个抽象类。
    外部类不能任意访问内部类的成员,要做到这一点,外部类必须创建其内部类的对象(静态
    内部类除外)。内部类可以访问 all 外部类的成员,因为内部类就像是一个类的成员。

  14. 抽象类和接口应用场景——1)标记接口:什么抽象方法都没有。2)需要实现特定的多
    项功能,而这些功能之间完全没有联系。3)定义了一个接口,但又不想强迫每个实现类都
    必须实现所有抽象方法,所以可以用抽象类来实现一部分方法体,比如 adapter。抽象类是
    简化接口的实现,它不仅提供了公共方法的实现,让我们可以快速开发,又允许你的类完全
    可以实现所有的方法,不会出现紧耦合的情况。

  15. 面向对象的"六原则一法则"

反射

谈谈如何通过反射创建对象?

img

  • 1.通过class类对象调用newInstance()方法,例如:String.class.newInstance()
  • 2.通过class类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:String.class.getConstrucctor(String.class).newInstance(“Hello”);

如何通过反射获取和设置对象私有字段的值?

答:可以通过类对象的getDeclaredField()方法字段(Field)对象,然后再通过字段对象的setAccessible(true)将其设置为可以访问

http://www.oh100.com/kaoshi/java/413532.html

如何通过反射调用对象的方法?

public static void main(String[] args) throws Exception {
        String str = "hello";
        Method m = str.getClass().getMethod("toUpperCase");
        System.out.println(m.invoke(str));  // HELLO
    }

代理

jdk 动态代理 vscglib 动态代理,他们底层分别怎么实现的

设计模式

  • 单例模式(手撕各种单例模式)
  • 工厂模式(Spring的BeanFactory,ApplicationContext)vs.抽象工厂模式
  • 构造器模式(应用场景:解决构造复杂对象的麻烦。复杂:需要输入的参数的组合较多)
  • 代理模式(Spring AOP(动态代理模式:2种实现方式,即jdk & 字节码方式))
  • 装饰器模式(应用场景:若想对一个类从不同角度进行功能扩展,例如java.io中,InputStream是一个抽象类,标准类库中提供了FileInputStream\ByteArrayInputStream等各种不同的子类,分别从不同角度对InputStream进行了功能扩展。这些不同的实现类其构造函数的输入均为InputStream(的实现类),然后对InputStream添加不同层次的逻辑,从而实现不同的功能,这就是装饰。)
  • 观察者模式(各种事件监听器)
  • 策略模式(比较器Comparator)
  • 迭代器模式(ArrayList等集合框架中的迭代器)
  • 生产者消费者模式(消息队列)要能手撕

写一个生产者消费者模式

写一个你认为最好的单例模式

注解的原理

使用java动态代理,有什么条件,为什么有这个条件?

五、Spring相关

IOC应用及原理

IOC(Inversion of Control),即“控制反转”,IOC,另外一种说法叫DI(Dependency Injection)

作用: 取消每个类管理与自己有交互的类的引用和依赖,代码将会变的异常难以维护和极度的高耦合,通过IOC将这些相互依赖对象的创建、装配、协调工作交给Spring容器去处理,每个对象只需要关注其自身的业务逻辑关系就可以

优点: IOC 或 依赖注入把应用的代码量降到最低,降低耦合,它使应用容易测试,

自己实现IOC要怎么做,哪些步骤?

有哪些不同类型的IOC(依赖注入)方式?

  • **构造器依赖注入:**构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
  • **Setter方法注入:**Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
  • 接口注入;
  • 注解方法注入;

AOP应用及原理

  • AOP:面向切面编程,往往被定义为促使软件系统实现关注点的分离的技术

每一个组件各负责一块特定功能,将其他日志,事务等融入自身的业务组件中,这些系统服务横切关注点,因为它们会跨越系统的多个组件。

一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。

  • AOP相关的术语:

通知: 通知定义了切面是什么以及何时使用的概念。Spring 切面可以应用5种类型的通知:

前置通知(Before):在目标方法被调用之前调用通知功能。
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
返回通知(After-returning):在目标方法成功执行之后调用通知。
异常通知(After-throwing):在目标方法抛出异常后调用通知。
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

  • 连接点、切点、增强、引入、织入

连接点:是在应用执行过程中能够插入切面的一个点。

切点: 切点定义了切面在何处要织入的一个或者多个连接点。
切面:是通知和切点的结合。通知和切点共同定义了切面的全部内容。
引入:引入允许我们向现有类添加新方法或属性。
织入:是把切面应用到目标对象,并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期中有多个点可以进行织入:
编译期: 在目标类编译时,切面被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标加载到JVM时被织入。这种方式需要特殊的类加载器(class loader)它可以在目标类被引入应用之前增强该目标类的字节码。
运行期: 切面在应用运行到某个时刻时被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面的。

Spring MVC的工作原理

  • MVC 的原理图如下:

  • SpringMVC 简单介绍

SpringMVC 框架是以请求为驱动,围绕 Servlet 设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是 DispatcherServlet,它是一个 Servlet,顶层是实现的Servlet接口。

如下图所示:
SpringMVC运行原理

(1)客户端(浏览器)发送请求,直接请求到 DispatcherServlet。

(2)DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。

(3)解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。

(4)HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑。

(5)处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。

(6)ViewResolver 会根据逻辑 View 查找实际的 View。

(7)DispaterServlet 把返回的 Model 传给 View(视图渲染)。

(8)把 View 返回给请求者(浏览器)

Spring涉及的设计模式

https://www.cnblogs.com/baizhanshi/p/6187537.html

Struts拦截器和Spring AOP区别

ApplicationContext通常的实现是什么?

  • **FileSystemXmlApplicationContext :**此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
  • **ClassPathXmlApplicationContext:**此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。
  • **WebXmlApplicationContext:**此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。

Spring中BeanFactory和ApplicationContext的区别

Application contexts提供一种方法处理文本消息,一个通常的做法是加载文件资源(比如镜像),它们可以向注册为监听器的bean发布事件。另外,在容器或容器内的对象上执行的那些不得不由bean工厂以程序化方式处理的操作,可以在Application contexts中以声明的方式处理。Application contexts实现了MessageSource接口,该接口的实现以可插拔的方式提供获取本地化消息的方法。

Spring Bean的作用域

Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中 的形式定义。

配置元数据的方式:XML,注解,Java

bean的作用域

  • singleton : bean在每个Spring ioc 容器中只有一个实例,默认为单件。
  • prototype:一个bean的定义可以有多个实例。
  • request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

Spring框架中的单例bean不是线程安全的

Spring Bean的生命周期

  • Spring容器 从XML 文件中读取bean的定义,并实例化bean。
  • Spring根据bean的定义填充所有的属性。
  • 如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。
  • 如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。
  • 如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。
  • 如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
  • 如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。
  • 如果bean实现了 DisposableBean,它将调用destroy()方法。

Spring中自动装配的方式

Spring 容器能够自动装配相互合作的bean,这意味着容器不需要和配置,能通过Bean工厂自动处理bean之间的协作。

五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入。

  • no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
  • **byName:**通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
  • **byType::**通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
  • constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
  • **autodetect:**首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。

spring框架的优点

  • **轻量:**Spring 是轻量的,基本的版本大约2MB。
  • **控制反转:**Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
  • **面向切面的编程(AOP):**Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
  • **容器:**Spring 包含并管理应用中对象的生命周期和配置。
  • MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
  • **事务管理:**Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。
  • **异常处理:**Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。

Spring支持的事务管理类型

  • 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
  • **声明式事务管理:**这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。

Spring框架的事务管理有哪些优点?

  • 它为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。
  • 它为编程式事务管理提供了一套简单的API而不是一些复杂的事务API如
  • 它支持声明式事务管理。
  • 它和Spring各种数据访问抽象层很好得集成。

Spring 代理

Spring 实现 aop:JDK 动态代理,cglib 动态代理。
JDK 动态代理:其代理对象必须是某个接口的实现,他是通过在运行期间创建一个接口
的实现类来完成对目标对象的代理。核心类有:InnovactionHandler,Proxy。
Cgib 动态代理:实现原理类似于 jdk 动态代理,只是他在运行期间生成的代理对象是针
对目标类扩展的子类。MethodInterceptor。

Spring怎样解决循环依赖的问题

https://blog.csdn.net/qq_36381855/article/details/79752689

Spring vs Spring Boot

Spring Cloud

参考资料:https://zhuanlan.zhihu.com/p/54608361

其他框架

❤1、Hibernate
1、阐述实体对象的三种状态以及转换关系。 

2、Hibernate中SessionFactory是线程安全的吗?Session是线程安全的吗(两个线程能够共享同一个Session吗)? 

3、Hibernate中Session的load和get方法的区别是什么? 

4、如何理解Hibernate的延迟加载机制?在实际应用中,延迟加载与Session关闭的矛盾是如何处理的? 

4、简述Hibernate常见优化策略。 

5、锁机制有什么用?简述Hibernate的悲观锁和乐观锁机制。 

6、Hibernate如何实现分页查询? 

7、谈一谈Hibernate的一级缓存、二级缓存和查询缓存。 
❤2、Struts
1、说说STRUTS的应用 
❤3、Mybatis
1、解释一下MyBatis中命名空间(namespace)的作用。 

2、MyBatis中的动态SQL是什么意思? 

mybatis动态标签,写一个具体例子

mybatis底层原理你了解吗

❤4、MVC
1、Spring MVC注解的优点 

2、springmvc和spring-boot区别? 

3、SpringMVC的运行机制,运行机制的每一部分的相关知识? 

4、谈谈Spring MVC的工作原理是怎样的? 
❤5、各框架对比与项目优化
1、Mybatis和Hibernate区别? 

2、介绍一下你了解的Java领域的Web Service框架。 
❤6、 服务器

Nginx

nginx反向代理的底层原理是怎么实现的

nginx线上优化有做过吗

服务器之间怎么通信

springmvc与Struts的区别

springmvc是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,参数的传递是直接注入到方法中的,是该方法独有的。

struts2是类级别的拦截, 一个类对应一个request上下文, struts是在接受参数的时候,可以用属性来接受参数, 这就说明参数是让多个方法共享的,这也就无法用注解或其他方式标识其所属方法了

一致性算法:1)平衡性:哈希的结果能够尽可能分布到所有缓存中去。2)单调性:如
果已经有一些内容通过 hash 分派到了相应的缓冲中,又有新的缓冲区加入到系统中,那么
hash 的结果应该能保证原有已分配的内容可以被映射到新的缓冲区中,或原来的缓冲区中。
3)分散性:在分布式环境中,终端有可能看不到所有缓冲区,而只能看到其中一部分,当
终端希望通过哈希过程将内容映射到缓冲区上时,由于不同终端所见的缓冲范围有可能不
同,可能导致相同的内容被不同的终端映射到不同的缓冲区上,这种情况应该避免。4)负
载:既然不同的终端可能将相同的内容映射到不同缓冲区中,那么对于一个特定的缓冲区而
言,也可能被不同的用户映射为不同的内容。
在分布式集群中,对机器的添加和删除或机器故障后自动脱离集群,这些操作是分布式
集群管理最基本的功能,若采用常用的 hash(object)%N 算法,那么在有机器添加或删除以后,
很多原有的数据就无法找到,所以出现一致性哈希算法——1)环形 hash 空间:按照常用的
hash 算法来将对应的 key 哈希到一个具有 2 32 个桶的空间,即(0-2 32 -1)的数字空间中,现
在我们将这些数字头尾相连,想象成一个闭合的环形。2)把数据通过一定的 hash 算法映射
到环上。3)将机器通过一定的 hash 算法映射到环上。4)节点按顺时针转动,遇到的第一
个机器,就把数据放在该机器上。

Nginx:可以根据客户端的 ip 进行负载均衡,在 upstream 里设置 ip_path,负载均衡五
种配置:1)轮询(默认),每个请求按时间顺序逐一分配到不同的后端服务器,如果后端
服务器 down 掉,能自动剔除;2)指定权重,指定轮询几率。权重越大,轮询几率越大,
用于后端服务器性能不均的情况。3)ip 绑定 ip_path,每个请求按访问 ip 的哈希结果分配,
这样每个客户固定访问一个服务器,可以解决 session 问题。4)fair(第三方)按后端服务
器的响应时间来分配请求,响应时间短的优先分配。5)url_hash 按访问的 url 结果来分配请
求,使每个 url 定位到同一个后端服务器。后端服务器为缓存时比较有效。具体的配置自己
网上百度。

六、Web编程

❤1、web编程基础

1、启动项目时如何实现不在链接里输入项目名就能启动? 

2、1分钟之内只能处理1000个请求,你怎么实现,手撕代码? 

3、什么时候用assert 

4、JAVA应用服务器有那些? 

5、JSP的内置对象及方法。 

6、JSP和Servlet有哪些相同点和不同点,他们之间的联系是什么?(JSP) 

7、说一说四种会话跟踪技术 

8、讲讲Request对象的主要方法 

9、说说weblogic中一个Domain的缺省目录结构?比如要将一个简单的helloWorld.jsp放入何目录下,然后在浏览器上就可打入主机? 

10、jsp有哪些动作?作用分别是什么? 

11、请谈谈JSP有哪些内置对象?作用分别是什么? 

12、说一下表达式语言(EL)的隐式对象及其作用 

13、JSP中的静态包含和动态包含有什么区别? 

14、过滤器有哪些作用和用法? 

15、请谈谈你对Javaweb开发中的监听器的理解? 

16、说说web.xml文件中可以配置哪些内容? 

广播和多播(组播)仅应用于UDP,网上视频会议、网上视频点播特别适合采用多播方式。

❤2、web编程进阶

1、forward与redirect区别,说一下你知道的状态码,redirect的状态码是多少? 

2、servlet生命周期,是否单例,为什么是单例。 

3、说出Servlet的生命周期,并说出Servlet和CGI的区别。 

4、Servlet执行时一般实现哪几个方法? 

5、阐述一下阐述Servlet和CGI的区别? 

6、说说Servlet接口中有哪些方法? 

7、Servlet 3中的异步处理指的是什么? 

8、如何在基于Java的Web项目中实现文件上传和下载? 

9、服务器收到用户提交的表单数据,到底是调用Servlet的doGet()还是doPost()方法? 

10、Servlet中如何获取用户提交的查询参数或表单数据? 

11、Servlet中如何获取用户配置的初始化参数以及服务器上下文参数? 

12、讲一下redis的主从复制怎么做的? 

13、redis为什么读写速率快性能好? 

14、redis为什么是单线程? 

15、缓存的优点? 

16、aof,rdb,优点,区别? 

17、redis的List能用做什么场景? 

18、说说MVC的各个部分都有那些技术来实现?如何实现? 

19、什么是DAO模式? 

20、请问Java Web开发的Model 1和Model 2分别指的是什么? 

21、你的项目中使用过哪些JSTL标签? 

22、使用标签库有什么好处?如何自定义JSP标签?(JSP标签) 

❤3、web编程原理

1、get和post区别? 

2、请谈谈转发和重定向的区别? 

3、说说你对get和post请求,并且说说它们之间的区别? 

4、cookie 和session 的区别? 

5、forward 和redirect的区别 

6、BS与CS的联系与区别。 

7、如何设置请求的编码以及响应内容的类型? 

8、什么是Web Service(Web服务)? 

9、谈谈Session的save()、update()、merge()、lock()、saveOrUpdate()和persist()方法分别是做什么的?有什么区别? 

10、大型网站在架构上应当考虑哪些问题? 

11、请对J2EE中常用的名词进行解释(或简单描述) 

七、数据库

❤1、SQL基础

​ 1、写SQL:找出每个城市的最新一条记录。

2、一个学生表,一个课程成绩表,怎么找出学生课程的最高分数 

3、有一组合索引(A,B,C),会出现哪几种查询方式?tag:sql语句 

❤2、JDBC基础

数据库两种引擎Myisam,InnoDB

Myisam:不支持事务行级锁和外键约束。所以当执行 insert 和 update 时,执行写操作
时,要锁定整个表,所以效率低。但是它保存了表的行数,执行 select count() from table
时,不需要全表扫描,而是直接读取保存的值。所以若读操作远远多于写操作,并且不需要
事务,myisam 是首选。
Innodb:支持事务、行级锁和外键,mysql 运行时,Innodb 会在内存中建立缓冲池,用
于缓冲数据和索引。不保存表的行数,执行 select count(
) from table 时要全表扫描。写不锁
定全表,高效并发时效率高。

全表扫描慢的话怎么解决

  1. 并行查询、建立索引

B-tree 索引可以用于使用< <= = >= >或者 between 运算符的列比较。如果 Like 的参数是
一个没有以通配符起始的常量字符串也可以使用这种索引。Hash 索引:1)只能够用于使用
=或<=>运算符的相比较,不能用于有范围的运算符,因为键经过 hash 以后的值没有意义 2)
优化器不能使用 hash 索引来加速 order by 操作。这种类型的索引不能够用于按照顺序查找
下一个条目 3)mysql 无法使用 hash 索引来估计两个值之间有多少行 4)查找某行记录必须
全键匹配,而且 B-tree 索引,任何该键的左前缀都可以用以查找记录 5)若将一张 myisam
或 innodb 表换为一个 hash 索引的内存表,一些查询可能受影响。

​ 1、数据库水平切分,垂直切分

2、数据库索引介绍一下。介绍一下什么时候用Innodb什么时候用MyISAM。 

3、数据库两种引擎Myisam,InnoDB 

​ 数据库索引什么时候会失效:

4、索引了解嘛,底层怎么实现的,什么时候会失效 

5、问了数据库的隔离级别 

6、数据库乐观锁和悲观锁 

7、数据库的三范式? 

8、讲一下数据库ACID的特性? 

9、mysql主从复制? 

10、leftjoin和rightjoin的区别? 

11、数据库优化方法 

12、谈一下你对继承映射的理解。 

13、说出数据连接池的工作机制是什么? 

14、事务的ACID是指什么? 

15、JDBC中如何进行事务处理? 

​ 1、JDBC的反射,反射都是什么?

2、Jdo是什么? 

3、Statement和PreparedStatement有什么区别?哪个性能更好? 

4、使用JDBC操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能?

怎么判断一个 mysql 中 select 语句是否使用了索引,可以在 select 语句前加上 explain,
比如 explain select * from tablename;返回的一列中,若列名为 key 的那列为 null,则没有使
用索引,若不为 null,则返回实际使用的索引名。让 select 强制使用索引的语法:select * from
tablename from index(index_name);

事务中锁分类

事务特性

视图

事务隔离级别就是对事物并发控制的等级。串行化:所有事务一个接一个的串行执行。
可重复读:所有被 select 获取的数据都不能被修改,这样就可以避免一个事务前后读取数据
不一致的情况。读已提交:被读取的数据可以被其他事务修改。读未提交:允许其他事务看
到没提交的数据。脏读:事务没提交,提前读取,就是当一个事务正在访问数据,并且对数
据进行了修改,而这种修改还没提交到数据库,这时,另外一个事务也访问这个数据,然后
使用了这个数据。

数据库优化

一百台服务器同时部署一个应用,如何处理

如何处理数据库高并发

数据库的crud语句的书写

数据库优化策略、如何优化

海量数据优化

为什么即用MySQL,又用Redis,又用MongoDB

mysql给离散度低的字段建立索引会出现什么问题,具体说下原因

数据一致性怎么保证

https://blog.csdn.net/hxpjava1/article/details/79409459

如何防止数据库管理员盗用数据

用户注册完有短信提醒和邮件提醒两个功能,邮件那边出现问题,怎么解决

一致性哈希

https://www.cnblogs.com/study-everyday/p/8629100.html

❤3、Redis基础

redis缓存在项目中的应用
  • 缓存像广告,商品类目这些不需要经常修改的数据,每次去数据库查询浪费资源和增加数据库压力
  • 利用redis进行会话缓存(session cache)
  • 排行榜/计数器
  • 全页缓存(FPC)
redis内存不够怎么办
  • 增加内存:redis设置配置文件的maxmemory参数,可以控制其最大可用内存大小,maxmemory-policy

  • 内存淘汰策略

    • volatile-lru 使用LRU算法删除一个键(只对设置了生存时间的键)
    • allkeys-lru 使用LRU算法删除一个键
    • volatile-random 随机删除一个键(只对设置了生存时间的键)
    • allkeys-random 随机删除一个键
    • volatile-ttl 删除生存时间最近的一个键
    • noeviction 不删除键,只返回错误

    LRU算法,least RecentlyUsed,最近最少使用算法。也就是说默认删除最近最少使用的键。

    但是一定要注意一点!redis中并不会准确的删除所有键中最近最少使用的键,而是随机抽取3个键,删除这三个键中最近最少使用的键。

  • redis集群:客户端分片、代理分片、RedisCluster

    • 客户端分片

    通过业务代码自己实现路由

    优势:可以自己控制分片算法、性能比代理的好

    劣势:维护成本高、扩容/缩容等运维操作都需要自己研发

    • 代理分片

    代理程序接收到来自业务程序的数据请求,根据路由规则,将这些请求分发给正确的Redis实例并返回给业务程序。使用类似Twemproxy、Codis等中间件实现。

    优势:运维方便、程序不用关心如何链接Redis实例

    劣势:会带来性能消耗(大概20%)、无法平滑扩容/缩容,需要执行脚本迁移数据,不方便(Codis在Twemproxy基础上优化并实现了预分片来达到Auto Rebalance)。

    • Redis Cluster

    优势:官方集群解决方案、无中心节点,和客户端直连,性能较好

    劣势:方案太重、无法平滑扩容/缩容,需要执行相应的脚本,不方便、太新,没有相应成熟的解决案例

redis持久化方案
  • RDB 和 AOF

redis 与memcached的区别

1、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。

2 、Redis支持数据的备份,即master-slave模式的数据备份。

3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。

Exception vs Error

finally vs final vs finalize

Object类及其方法实现(尤其是equals() and hashCode())

  • 反射与动态代理
  • 序列化与反序列化(底层实现)

怎么保证 redis 和 db 中的数据一致

实现一个类,要求要放在 hashset 里

问若客户端和服务器之间,1s 会发生 5000 到 6000次短链接,会发生什么问题

redis和memcached的区别。 redis支持哪些数据结构。 redis是单线程的么,所有的工作都是单线程么。 redis如何存储一个String的。 redis的部署方式,主从,集群。 redis的哨兵模式,一个key值如何在redis集群中找到存储在哪里。 redis持久化策略。

基于数据库,基于缓存、基于zk三种,三种方案的优缺点

缓存和zk了解多少

常用缓存:redis、memcached

memcached容易被DDOS攻击,Zookeeper介绍

分布式 一致性哈希、RPC原理和设计(通信协议、序列化方式、超时机制等)、负载均衡、分布式缓存架构设计、分布式消息、分布式事务、paxos(这个可能只有在技术专业型很强的职位上会去问)

八、场景题

❤1、场景题汇总

1、情景题:如果一个外卖配送单子要发布,现在有200个骑手都想要接这一单,如何保证只有一个骑手接到单子?

2、场景题:美团首页每天会从10000个商家里面推荐50个商家置顶,每个商家有一个权值,你如何来推荐?第二天怎么更新推荐的商家?

可以借鉴下stackoverflow,视频网站等等的推荐算法。

3、场景题:微信抢红包问题

悲观锁,乐观锁,存储过程放在mysql数据库中。

4、场景题:1000个任务,分给10个人做,你怎么分配,先在纸上写个最简单的版本,然后优化。

全局队列,把1000任务放在一个队列里面,然后每个人都是取,完成任务。

分为10个队列,每个人分别到自己对应的队列中去取务。

5、场景题:保证发送消息的有序性,消息处理的有序性。

6、如何把一个文件快速下发到100w个服务器

7、给每个组分配不同的IP段,怎么设计一种结构使的快速得知IP是哪个组的?

8、10亿个数,找出最大的10个。

建议一个大小为10的小根堆。

9、有几台机器存储着几亿淘宝搜索日志,你只有一台2g的电脑,怎么选出搜索热度最高的十个搜索关键词?

10、分布式集群中如何保证线程安全?

11、给个淘宝场景,怎么设计一消息队列?

12、10万个数,输出从小到大?

先划分成多个小文件,送进内存排序,然后再采用多路归并排序。

13、有十万个单词,找出重复次数最高十个?

14、4 个瓶盖换 1 瓶酒,要和 150 瓶酒,他自己最少多少瓶?

15、给你一个 5L 和 3L 桶,水无限多,怎么到出 4L

16、 两个人交替拿100个球,每次最少拿1个,最多拿5个,你第一次拿多少个可是最终你能拿到第100个球

​ 先拿4个,然后每轮拿球保证与对方总数为6,经过16轮可使自己拿到第100个球。

分布式架构

  • 服务调度,涉及服务发现、配置管理、弹性伸缩、故障恢复等
  • 资源调度,涉及对底层资源的调度使用,如计算资源、网络资源和存储资源等。
  • 流量调度,涉及路由、负载均衡、流控、熔断等。
  • 数据调度,涉及数据复本、数据一致性、分布式事务、分库、分表等
  • 容错处理,涉及隔离、幂等、重试、业务补偿、异步、降级等。
  • 自动化运维,涉及持续集成、持续部署、全栈监控、调用链跟踪等。

Zookeeper如何保证一致性

Rt抖动如何判断

分布式锁如何实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值