Post

JAVA程序设计

JAVA语言概述

  • JAVA SE(标准版)
    (1) java.lang:提供和Java语言及Java运行环境紧密相关的基础类和接口。
    (2) java.io:提供输入输出类,这些类一般是面向流的,但是其中也包含了一个用于随机访问文件的类。
    (3) java.math:提供用以进行高精度运算的数学类以及高精度的素数生成器。
    (4) java.net:提供和网络相关的输入输出类,为基本的应用层协议提供封装类。
    (5) java.text:提供与文本处理相关的算法支持类。
    (6) java.util:主要提供与复杂数据结构算法处理相关的算法支持类。
    (1) java.applet:用以支持运行在浏览器上的JavaApplet小应用程序的开发。
    (2) java.beans:用以支持JavaBeans架构定义的beans可重用小组件的开发。
    (3) java.awt:也称之为抽象窗口工具包,提供了支持和本地操作系统紧密相关的重组件开发的基础类,GUI事件系统的核心以及窗口系统的基础编程接口。
    (4) java.sql:用以支持基于JDBC API的数据库应用开发。
    (5) javax.swing:在Java.awt基础上,提供和平台无关的图形界面应用程序的开发。
  • JAVA EE(企业版)
    这个平台为开发和运行企业级应用软件提供了API和运行环境。Java EE可用于开发网络和WEB应用服务,以及其他的大规模、多层次、可伸缩、高可靠性和高度安全的网络应用程序。
  • JAVA ME(微型版)
    Java ME是专用于嵌入式系统的Java平台,它的目标设备包括工控平台、移动电话、掌上电脑和机顶盒。
  • JAVA CARD
    Java CARD被广泛应用于SIM卡和ATM卡上。
    1. Java是简单的
      Java可被看作是C++ 的一种很大程度上的改进和简化的版本。在Java中,指针被取消掉了,多重继承也被替换成了另一种更简单的语言结构——接口。另外Java还拥有自动的内存垃圾回收机制。
    2. Java是面向对象的。
    3. Java是分布式的。
    4. Java是解释型的。
      Java字节码和机器硬件架构无关,可以运行在任何安装了Java解释器的计算机中。(不需要像C++一样搬到另一种硬件架构上时需要重新编译)
    5. Java是健壮的。
      (1)Java的编译器能够报告很多其他编程语言生成的程序在编译完成后第一次运行时才能够发现的问题,因此可以帮助程序员在程序开发过程的早期就发现潜在的问题。
      (2)Java取消了在C/C++中存在的容易引起错误的编程元素,如指针的取消就保证了不会出现内存泄露的问题。
      (3)Java支持运行时的异常捕捉。它能确保程序在运行时即使出现了错误,也还能够继续执行并顺利地完成任务。
    6. Java是安全的。
      它的安全性基于一个前提:没有任何东西是可以被信任的。因此Java采用了多种安全机制来确保运行着Java虚拟机的宿主系统不会受到恶意软件的损害。
    7. Java是架构中立的。 当今世界上主流的操作系统平台(Windows、MAC、Linux等)都能运行Java虚拟机。
    8. Java是可移植的。
      Java应用程序不需经过重新编译,就可以运行在任何可以运行Java虚拟机的平台上。很多编程语言,它们的语言特性是平台相关的,如C语言中,整型和浮点型的长度就是由CPU的字长决定的。但是在Java语言中没有针对特定平台的特性,例如Java整型和浮点型的长度在所有的平台上都是一样的。这就确保了Java的可移植性。
      Java虚拟机本身也具备可移植性,可以很容易地移植到新的硬件和操作系统上。实际上,Java编译器本身就是用Java写的。
    9. Java是高性能的。
      采用了JIT技术的Java虚拟机可以在某段字节码开始运行之前,就抢先把它编译成对应的本地机器代码,在这段字节码开始运行的时候,Java虚拟机用对应的本地机器代码替换掉它,从而大大提升了Java字节码的运行速度。
    10. Java是多线程的
      在很多编程语言中,使用多线程必须调用特定的操作系统接口,而在Java中使用多线程却非常简单,因为Java提供了简单易用的多线程类库。
    11. Java是动态的
      一个Java应用程序无需重新编译就可以在运行的时候读入一个新的类,因此用户不需要重新安装一个新的应用程序版本。在需要的时候,新的功能可以无缝地、透明地植入到Java应用程序中。

      JAVA开发环境

      (1)  bin目录:用以开发、执行、调试和打包Java程序的实用程序。
      (2)  db目录:由Oracle打包的开放源代码数据库Apache Derby,这个数据库可以被嵌入Java应用程序中。
      (3)  demo目录:Java SE平台的示例源代码,包括了使用Swing和其他Java基类以及Java平台调试器体系结构的示例。
      (4)  include目录:包括了支持使用Java本地接口、JVM工具接口以及Java平台的其他功能进行本地代码编程的C语言头文件。
      (5)  jre目录:这是包含在JDK内部的Java运行环境,也即JRE,其中包括了Java虚拟机、类库以及其他支持执行Java程序的文件。
      (6)  lib目录:附加库目录,里面包括了Java开发过程中所需的其他类库以及相关的支持文件。
      (7)  sample目录:包括了某些Java API的编程示例源代码。
      (8)  src.zip文件:Java核心API类库中所有类的Java源文件(也即Java.、Javax. 等基础包中包含的类的源文件)。

      数据类型

      分为两大类:基本数据类型和复合数据类型

  • 标识符:以字母,_$开头
  • 基本数据类型byte,int,short,long,float,double
    优先级: $ byte \to short \to char \to int \to long \to float \to double $
  • 复合数据类型类class,接口interface,数组array
  • 引用类型(Number的子类)Byte,Integer,Short,Long,Float,Double

    移位运算符

    特别注意:
    >>>不带符号右移,高位补零 >>右移,正数高位补零z,负数高位补一

    类和对象

    构造函数

    1. 默认无参构造
    2. 与类同名
    3. 无返回值
    4. 可有多个
    5. this()可以在类内部函数中调用

      final与static的区别

      static可以修饰类的代码块,final不可以。
      static不可以修饰方法内的局部变量,final可以。

      static

      static修饰表示静态或全局,被修饰的属性和方法属于类,可以用类名.静态属性 / 方法名访问
      static修饰的代码块表示静态代码块,当Java虚拟机(JVM)加载类时,就会执行该代码块,只会被执行一次
      static修饰的属性,也就是类变量,是在类加载时被创建并进行初始化,只会被创建一次
      static修饰的变量可以重新赋值
      static方法中不能用this和super关键字
      static方法必须被实现,而不能是抽象的abstract
      static方法只能被static方法覆盖

      final

      final修饰表示常量、一旦创建不可改变
      final标记的成员变量必须在声明的同时赋值,或在该类的构造方法中赋值,不可以重新赋值
      final方法不能被子类重写
      final类不能被继承,没有子类,final类中的方法默认是final的
      final不能用于修饰构造方法
      private类型的方法默认是final类型的

      访问权限

      public:任何地方
      private:只能在本类中
      protected:在同一包中或者子类(可以不同包)
      default(无修饰符):只能在同一包中,不同包的子类无法访问

      嵌套类和内部类

      静态嵌套类

      静态嵌套类不能直接引用外部类中定义的变量或方法
      静态嵌套类可以像其他独立类(顶级类)一样与其外部类的实例成员进行交互,而且实际上,静态嵌套类在行为上就是一个独立的类,只不过是为了打包方便而嵌套在另一个独立的类中(顶级类)。
      在静态类内部如果有变量与外部类重名,需使用外部类名字.this.变量名

      非静态嵌套类(内部类)

      跟方法和变量一样,内部类与它外部类的实例相关联,并可以直接访问外部类的方法和属性。此外,由于内部类与外部类的实例关联,所以内部类本身并不能定义任何静态成员
      要实例化内部类,必须先实例化外部类,例:

      1
      2
      3
      4
      
      // 创建外部类
      OuterClass outerObject = new OuterClass();
      // 创建内部类
      OuterClass.InnerClass innerObject = outerObject.new InnerClass();
      

      问答题

    6. 为什么this关键字不能出现在类方法中?
      “this”关键字是一个特殊的引用,它指向当前实例对象。然而,在类方法(也称为静态方法)中,”this”关键字是没有定义的。原因是静态方法是类级别的,而不是实例级别的。它们不依赖于任何特定对象实例,因此也没有当前对象实例来引用。在静态方法中,你可以直接通过类名调用其他静态方法或静态变量,而不需要创建类的实例。
    7. 类成员变量和实例成员变量
      实例成员变量:
      生命周期:随对象创建和消亡
      数据存储位置:存储在堆内存
      调用方式:被对象调用
      初始化时机:实例化对象时初始化
      类成员变量(static):
      生命周期:随类加载和消失
      数据存储位置:方法区(共享数据区)
      调用方式:被对象调用或者被类名调用
      初始化时机:类首次加载时初始化,优先于实例成员

      继承与接口

      子类(被创建为实例对象时)可以调用父类方法间接访问父类私有变量

      重载与重写的区别

      重载

      (1):方法名必须相同

(2):方法的参数列表一定不一样。

(3):访问修饰符和返回值类型可以相同也可以不同。

重写

(1):方法名必须相同,返回值类型必须相同

(2):参数列表必须相同

(3):访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。

(4):子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。

(5):构造方法不能被重写

super关键字

可以调用super.来获取被覆盖的变量或方法

上转型对象

1
Person p=new Worker()

特点:

  1. 不能访问子类实体的成员变量和成员方法
  2. 可以访问子类实体中继承了父类的成员变量和成员方法,以及子类对象重写的父类方法
  3. 同名元素访问的是父类的而不是子类的
  4. 可以强制转换为子类

    抽象类

    抽象方法: (1) 没有具体实现,用abstract修饰
    (2) 包含抽象方法的一定是抽象类
    (3) 抽象类不能被实例化
    (4) 抽象类主要作为父类,子类要么重写抽象方法,要么也为抽象类

    接口

    接口内部默认全部方法是public和abstract的
    全部成员变量为static和final的

    接口回调

    与上转型类似,使用接口创建的对象,可以用=将实现了该接口的对象实例应用,从而能调用该实现的方法

    字符串使用

    String类

    String的函数

    .length()返回长度
    .charAt(index)返回下标为index的字符
    .replace(String)替换为参数字符串
    .substring(beginIndex,endIndex)截取字符串
    .substring(beginIndex)beginIndex后面的字符串
    new String(char[])char数组创建String对象 .getChars(int begin,int end,char[],int charBegin)将String(begin~end)接下来放入字符数组charBegin开始的位置
    .concat(String)拼接两个字符串,也可以用+

    字符串比较

    .equals(String)返回true or false
    .equalsIgnoreCase(String)忽略大小写的比较
    toLowerCase()全部转小写
    toUpperCase()全部转大写
    compareTo()字典序比较,返回正整数,0,负整数

    常量字符串的引用

    1
    2
    3
    4
    5
    6
    
    String s1="Hello world";
    String s2="Hello world";
    String s3="Hello "+"world";
    String s41="Hello ";
    String s42="world";
    String s4=s41+s42; 
    

    s1==s2==s3在生成时有同一内容
    但不等于s4,因为s4是在运行时连接而不是编译时

    字符串的查询

    contains(String)是否包含某个字符串
    startsWith(String)判断参数字符串是否为该字符串的前缀
    endsWith(String)判断参数字符串是否为该字符串的后缀
    .indexOf(String)获取参数字符串的起始位置,找不到返回-1

    字符串转为数值,数值转字符串

    1.字符串转数值:

    1
    2
    3
    4
    5
    6
    7
    
    //返回基本数据类型
    float f=Float.parseFloat("");
    int i=Integer.parseInteger("");
    ....
    //以此类推
    //返回对象
    float f=Float.valueOf("3.14");//返回浮点数对象Float,内容为3.14
    

    2.数值转字符串:

    1
    2
    3
    4
    5
    6
    7
    
    int i=1024;
    //方法一
    String s=""+i;
    //方法二
    String s=String.valueOf(i);
    //方法三
    String s=Integer.toString(i);
    

    格式化字符串

    System.out.printf("%f",f)
    如果需要获取,则:
    String s=String.format("%f",f)

    StringBuilder类

    优点:修改高效,如果对字符串频繁修改,则使用StringBuilder
    缺点:多线程不安全,如果需要多个线程之间数据同步,建议使用StringBuffer

    长度和容量

    .length()
    .capacity()返回字符容量,一般大于字符串长度

    构造方法

    StringBuilder()
    StringBuilder(CharSequence cs)创建一个字符串内容和cs相同,并增加16个容量
    StringBuilder(int initCapacity)初始容量为initCapacity的空字符串
    StringBuilder(String s)和第二个构造相同
    .setLength(int)设置长度,大于则添加空字符,小于则裁剪
    .ensureCapacity(int)保障容量至少为参数值
    使用append(),insert(),setLength()时容量自动增加

    常用方法

  5. 末尾添加
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    append(boolean b)
    append(char c)
    append(char []str)
    append(char []str,int offset,int len)//char数组的第一个字符下标为offset长度为len
    append(double d)
    append(float f)
    append(int i)
    append(long l)
    append(Object obj)
    append(String s)
    
  6. 删除
    1
    2
    
    delete(int start,int end)//删除区间
    deleteCharAt(int index)//删除index的字符
    
  7. 插入
    1
    
    insert(int offset,Object obj)
    
  8. 翻转
    1
    
    reverse()
    
  9. toString()

    StringBuffer类

    方法与StringBuilder类几乎相同
    优点: 线程安全 如果字符串缓冲区被单个线程使用,则用StringBuilder(效率高)
    如果需要多线程同步,则用StringBuffer

    泛型与集合

    泛型

    1
    2
    3
    4
    
    List list=new ArrayList();//无泛型,获取数据时需要强制转换
    List<String> list=new ArrayList<String>();//泛型,无需强制转换,就是String
    定义 
    public class name<T1,T2,..,Tn>{}
    

    集合类

    Collection接口有两大接口:ListSet
    List接口包括三大类:ArrayList,Vector,LinkedList
    Set接口包括三大类:HashSet,LinkedHashSet,TreeSet

Map接口提供键到值的映射
Map接口有三大类:TreeMap,HashMap,HashTable

ArrayList:(线程不安全)

特点: 动态数组,可变大小。
优点: 高效的随机访问和快速尾部插入。
缺点: 中间插入和删除相对较慢。

LinkedList:

特点: 双向链表,元素之间通过指针连接。
优点: 插入和删除元素高效,迭代器性能好。
缺点: 随机访问相对较慢。

HashSet:

特点: 无序集合,基于HashMap实现。
优点: 高效的查找和插入操作。
缺点: 不保证顺序。

HashMap:(对标c++的unordered_map)

特点: 基于哈希表实现的键值对存储结构。 优点: 高效的查找、插入和删除操作。 缺点: 无序,不保证顺序。

TreeMap:(对标c++的map)

特点: 基于红黑树实现的有序键值对存储结构。 优点: 有序,支持按照键的顺序遍历。 缺点: 插入和删除相对较慢。

JAVA异常处理

1
2
3
4
5
6
7
try{

}catch(Exception e){

}finally{

}

一旦捕捉到异常,就会跳过所有catch
所有异常类型都是内置类Throwable的子类,因此,Throwable在异常类层次结构的顶层。Thorwable类有两个子类Error和Exception,分别表示错误和异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常,这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。

  1. Error类
    Error类是错误类,表示仅靠程序本身无法修复的严重错误,如内存溢出错误(OutOfMemoryError)、线程死亡错误(ThreadDeath)等。这类异常发生时,Error类对象由Java虚拟机生成并抛出给系统。
  2. Exception类 可以自定义异常(继承Exception类),重写toString()函数,来打印自己想要的信息

    File类与输入输出流

    File类

    构造方法

    File(String parent, String child)
    File(File parent, String child)
    File(URI uri)
    File(String pathname)

    方法

    (1) 文件名的处理:
    String getName():得到一个文件的名称(不包括路径)。
    String getParent():得到一个文件的上一级目录名。
    String getPath():得到一个文件的路径名。
    String getAbsolutePath():得到一个文件的绝对路径名。
    String renameTo(File newName):将当前文件名更名为给定文件的完整路径。
    (2) 文件属性测试:
    boolean isAbsolute():测试此抽象路径名是否为绝对路径名。
    boolean canRead():测试当前文件是否可读。
    boolean canWrite():测试当前文件是否可写。
    boolean exists():测试当前File对象所指示的文件是否存在。
    boolean isFile():测试当前文件是否是文件(不是目录)。
    boolean isDirectory();测试当前文件是否是目录。
    boolean isHidden():测试当前文件是否是一个隐藏文件。 (3) 普通文件信息和工具:
    long lastModified():得到文件最近一次修改的时间。
    long length():得到文件的长度,以字节为单位。
    boolean delete():删除当前文件。
    (4) 目录操作:
    boolean mkdir():根据当前对象生成一个由该对象指定的路径。
    String[] list():返回一个字符串数组,列出当前目录中的文件和目录。

    输入输出流

    InputStream类

    (1)  public abstract int read():返回读取的一个字节;如果到达流的末尾,则返回 -1。
    (2)  public int read(byte[] b):从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中。
    (3)  public int read(byte[] b, int off, int len):将输入流中最多 len 个数据字节读入 byte 数组。
    (4)  public long skip(long n):跳过和丢弃此输入流中数据的n个字节。
    (5)  public int available():可以不受限制地从此输入流读取(或跳过)估计的字节数;如果到达输入流末尾,则返回0。
    (6)  public void mark(int readlimit):在此输入流中标记当前的位置,readlimit 参数告知此输入流在标记位置失效之前允许读取的字节数。
    (7)  public void reset():将此流重新定位到最后一次对此输入流调用mark方法时的位置。
    (8)  public boolean markSupported():如果此输入流实例会支持mark和reset方法,则返回true;否则返回false。
    (9)  public void close():关闭此输入流并释放与该流关联的所有系统资源。

    OutputStream类

    (1)  public abstract void write(int b):将指定的字节写入此输出流,要写入的字节是参数b的八个低位。
    (2)  public void write(byte[] b):将b.length个字节从指定的byte数组写入此输出流。
    (3)  public void write(byte[] b, int off, int len):将指定的byte数组中从偏移量off 开始的len个字节写入此输出流。
    (4)  public void flush():刷新此输出流并强制写出所有缓冲的输出字节。
    (5)  public void close():关闭此输出流并释放与此流有关的所有系统资源。

    文件字节流(FileInputStream,FileOutputStream)

    管道流

    必须两个共用

(1) 在构造方法中进行连接:
PipedInputStream(PipedOutputStream pos);
PipedOutputStream(PipedInputStream pis);
(2) 通过各自的connect()方法连接:
在类PipedInputStream中,connect(PipedOutputStream pos);
在类PipedOutputStream中,connect(PipedInputStream pis);

数据流类

DataInputStream类的构造方法描述为:
public DataInputStream(InputStream in)
DataOutputStream类的构造方法描述为:
public DataOutputStream(OutputStream out)

字符流类

字节流不能操作Unicode字符,由于Java采用16位的Unicode字符,即一个字符占16位,所以要使用字符流,以提供直接的字符输入输出支持。在Unicode字符中,一个汉字被看成一个字符,占用2个字节,若使用字节流,读取不当会出现乱码,若采用字符流,则不会出现类似情形。

Reader类

(1)  public int read(CharBuffer target):试图将字符读入指定的字符缓冲区。
(2)  public int read():读取单个字符。
(3)  public int read(char[] cbuf):将字符读入数组。
(4)  public abstract int read(char[] cbuf,int off,int len):将字符读入数组的某一部分。
(5)  public long skip(long n):跳过n个字符。
(6)  public boolean ready():判断是否准备读取此流。
(7)  public boolean markSupported():判断此流是否支持mark()操作。
(8)  public void mark(int readAheadLimit):标记流中的当前位置。
(9)  public void reset():重置该流。
(10)  public abstract void close():关闭该流并释放与之关联的所有资源

Writer类

(1)  public void write(int c):写入单个字符。
(2)  public void write(char[] cbuf):写入字符数组。
(3)  public abstract void write(char[] cbuf,int off,int len):写入字符数组的某一部分。
(4)  public void write(String str):写入字符串。
(5)  public void write(String str,int off,int len):写入字符串的某一部分。
(6)  public Writer append(CharSequence csq):将指定字符序列添加到此writer。
(7)  public Writer append(CharSequence csq,int start,int end):将指定字符序列的子序列添加到此writer。
(8)  public Writer append(char c):将指定字符添加到此writer。
(9)  public abstract void flush():刷新该流的缓冲。
(10)  public abstract void close():关闭此流,但要先刷新它。

文件字符流(FileReader,FileWriter)

缓冲流

BufferReader指向FileReader
PrintWriter指向FileWriter

线程

进程

进程(process)从加载到内存、运行、运行完毕退出内存,是一个完整的产生、发展到消亡的生命周期,在这个过程中,每个进程都享有CPU分配给它的专用内存数据区。在以时间片方式分享CPU的操作系统中,CPU控制权在进程间切换时,需要先保存当前进程数据区,然后恢复另一进程的专用数据区。用户看到的单一应用程序实际上可能是一组相互协作的进程。

线程

线程(thread)是比进程小的执行单位,由进程创建,是进程里的一条执行路径,事实上每个进程都至少包含有一个线程。多线程是指进程中同时拥有多条执行路径,多线程运行情况如图10.2所示。同属于一个进程的多个线程共享进程的资源,包括了内存数据区和打开的文件,这为线程间的有效通信提供了方便,但也带来了潜在问题。由于线程本身不拥有资源,所以线程的切换比进程的切换开销小。

创建线程

  1. 继承Thread类
  2. 实现Runnable接口(更灵活)

    主要方法

  3.  Thread(String threadName)
    Thread的构造函数,用于构造一个名为threadName的线程。
  4.  Thread()
    Thread的构造函数,用于构造一个名为Thread-加上一个数字的线程,例如Thread-0、Thread-1、Thread-2…等。
  5.  start()
    线程对象通过调用start方法启动线程,使它从新建状态进入到就绪状态排队等候CPU时间片,一旦获得CPU资源,就执行线程内的run()方法。注意,如果试图使用start方法再次启动一个已经就绪的线程,那么就会产生一个  
  6.  run()
    用来定义线程对象被调用后所执行的操作,是线程的使命所在,无论是通过继承Thread类还是通过实现Runnable接口来创建线程,都必须重写run()方法,否则所建立的将是不作为的线程对象,因而run()方法也可以理解为线程体。run()方法执行完毕,线程对象就进入死亡状态。
  7.  setName()
    用于设置线程的名称。
  8.  getName()
    用于返回线程的名称。
  9.  toString()
    返回一个包含了线程名称、线程优先级别和线程所属线程组字符串。
  10.  currentThread()
    Thread类的类方法,返回一个当前正在运行状态中的线程对象的引用。
  11.  isAlive()
    测试当前线程是否处于活动的状态。新建的线程在调用start()方法前,isAlive()返回false;在调用了start()方法后,直到run()方法结束之前,isAlive()返回true;run()方法运行结束,线程进入死亡状态,isAlive()返回false。
  12.  sleep(int millsecond)
    sleep()是线程类的一个静态(static)方法,使线程休眠一段由参数millsecond指定长度的时间,单位为毫秒。当线程休眠时会放弃CPU的使用权,所以往往在高优先级别的线程中调用sleep方法,主动让出CPU,使得低优先级别的线程得以运行。如果一个线程在休眠期间被打断,将引发一个InterruptedException异常,所以sleep方法的调用应该放在try-catch结构块中。
  13.  interrupt()
    线程对象可以通过调用interrupt方法中断本线程的休眠,从而让该线程进入就绪状态。
  14.  wait()
    使线程进入等待状态。
  15.  notify()与notifyAll()
    notify()方法唤醒正在等待的线程,notifyAll()方法唤醒所有正在等待的线程。
  16.  join()
    等待目标线程死亡之后,当前线程才能继续运行。

    synchronized关键字

  17. synchronized修饰方法
  18. synchroized(A){S;} 对象A设为临界资源,S为临界区

    线程的协作

    使用wait(),notify(),notifyAll()
    只能直接或间接用于临界区

    线程挂起

    sleep(),wait(),join()
    在线程A运行过程需要插入B线程,则在A线程体中加入B.join()

    线程调度的优先级

    setPriority(number)默认为5,取值为0-10

This post is licensed under CC BY 4.0 by the author.