Java知识点汇总

记录一下java学习过程的重要知识点
1.在java中如果被除数或者除数有一个为浮点类型,0或者0.0是可以用作除数的,结果得正负无穷;取余操作亦是如此。
2.java在7.0之后switch语句case后面支持String类型。
3.java嵌套循环中内部循环使用break或者continue来控制,外部循环结束或者跳过可以使用标签,示例代码:
 
out: for (int i = 0; i < 10; i++) {
   System.out.print("i = " + i + "\t");
   for (int j = 0; j < 5; j++) {
    System.out.print("j =" + j + "\t");
    if (j == 3) {
     break out;
    }
   }
   System.out.print("\n");
  }
4.java5之后提供了foreach循环,此循环不需要知道数组或者集合的长度,应用更方便。注意:使用foreach循环不能改变元素的值,因为遍历过程中是创建的临时变量。
5.数组引用变量只是一个引用,引用变量是访问真实对象的根本方式,如果希望在程序中访问数组对象本身,只能通过这个数组的引用变量来访问它。
6.实际的数组对象被存储在堆内存中;如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈内存中。
7.当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存中,随着方法的执行结束,这个方法的内存栈也将自然销毁。 因此,所有在方法中定义的局部变量都是放在栈内存中的;在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随着方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(在方法的参数传递时很常见),则这个对象依然不会被销毁。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收器才会在合适的时候回收它。
8.static修饰符的真正作用就是用于区分成员变量、方法、内部类、初始化块这四种成员到底属于类本身还是属于实例。在类中定义的成员,static相当于一个标志,有static修饰的成员属于类本身,没有static修饰的成员属于该类的实例。
9.长度可变的形参只能处于形参列表的最后,一个方法中最多只能包含一个长度可变的形参。长度可变的形参既可以传入多个参数,也可以传入一个数组。
10.如果同一个类中定义了test(String... books)方法,同时还定义了一个test(String)方法,则test(String... books)方法的books不可能通过直接传入一个字符串参数来调用,如果只传入一个参数,系统会执行重载的test(String)方法。如果需要调用test(String... books)方法,又只想传入一个字符串参数,则可采用传入字符串数组的形式。
11.如果一个Java源文件里定义的所有类都没有使用public修饰,则这个源文件的文件名可以是一切合法的文件名;但如果一个Java源文件里定义了一个public修饰的类,则这个源文件的文件名必须与public修饰的类的类名相同。
12.当程序创建一个子类对象时,系统不仅会为该类中定义的实例分配内存,也会为它从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量。
13.构造器相关:
#a.子类构造器执行体的第一行使用super显式调用父类构造器,系统将根据super调用里传入的实参列表调用父类对应的构造器;
#b.子类构造器执行体的第一行代码使用this显式调用本类中重载的构造器,系统将根据this调用里传入的实参列表调用本类中的另一个构造器。执行本类中另一个构造器时即会调用父类构造器;
#c.子类构造器执行体中既没有super调用,也没有this调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。
14.不管上面哪种情况,当调用子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行;不仅如此,执行父类构造器时,系统会再次上溯执行其父类构造器......依次类推,创建任何java对象,最先执行的总是java.lang.Object类的构造器。
15.考虑到进行强制类型转换时可能出现异常,因此进行类型转换之前应先通过instanceof运算符来判断是否可以成功转换。
16.当创建java对象时,系统总是先调用该类里定义的初始化块。初始化块虽然也是Java类的一种成员,但它没有名字,也就没有标识,因此无法通过类、对象来调用初始化块。初始化块只在创建java对象时隐式执行,而且在执行构造器之前。
17.实际上初始化代码块是一个假象,使用javac命令编译java类后,该java类中的初始化块会消失,初始化块中的代码会被“还原”到每个构造器中,且位于构造器所有代码的前面。
18.与构造器类似,创建一个Java对象时,不仅会执行该类的普通初始化块和构造器,而且系统还会一直上溯到java.lang.Object类,先执行java.lang.Object类的初始化块,开始执行 java.lang.Object的构造器,依次向下执行其父类的初始化块,开始执行其父类的构造器.....最后才执行该类的初始化块和构造器,返回该类的对象。
19.初始化块定义时使用static修饰符,这个初始化块就变成了静态初始化块,也被称为类初始化块(普通初始化块负责对对象执行初始化,类初始化块负责对类进行初始化)。静态初始化块是类相关的,系统将在类初始化阶段执行静态初始化块,而不是在对象创建时执行。因此静态初始化块总是比普通初始化块先执行。
20.与普通初始化块类似的是,系统在类初始化阶段执行静态初始化块时,不仅会执行本类的静态初始化块,而且还会一直上溯到java.lang.Object类(如果他包含静态初始化块),先执行java.lang.Object类的静态初始化块(如果有),然后执行其父类的静态初始化块......最后才执行该类的静态初始化块。
21.静态初始化块和声明静态成员变量时所指定的初始值都是该类的初始化代码,它们的执行顺序与源程序中的排列顺序相同。
22.java提供了final关键字来修饰变量、方法和类,系统不允许为final变量重新赋值,子类不允许覆盖父类的final方法,final类不能派生子类。通过使用final关键字,允许java实现不可变类,不可变类会让系统更安全。
23.JDK1.5以后支持所谓的自动装箱即可以直接把一个基本类型值赋给一个包装类实例,
Integer int1 = 2;
 Integer int2 = 2;
 System.out.println(int1 == int2);
 Integer int3 = 128;
 Integer int4 = 128;
 System.out.println(int3 == int4);
结果是true,false。原因在于观察Integer源码可知内部会管理一个长度为256的Integer数组,里面存了-128-127所有整数,所以把一个-128-127之间的数自动装箱成一个Integer实例时,实际是指向cache数组中的一个元素,所以它们完全相等,而每次把不在-128-127之间的整数自动装箱,就会不相等。
24.对于直接做+运算的两个字符串(字面量)常量,并不会放入String常量池中,而是直接把运算后的结果放入常量池中;对于先声明的字符串字面量常量,会放入常量池,但是若使用字面量的引用进行运算就不会把运算后的结果放入常量池中了;总结一下就是JVM会对String常量的运算进行优化,未声明的,只放结果;已经声明的,只放声明
25.final修饰的成员变量必须由程序员显式的指定初始值,final修饰的类变量必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定;final修饰的实例变量必须在非静态初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方的其中之一指定。
26.与普通成员变量不同,final成员变量(包括实例变量和类变量)必须由程序员显式初始化,系统不会对final成员变量进行隐式初始化。
27.当时用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但是这个对象完全可以发生改变。
28.对于final变量,只要满足:使用final修饰符修饰;在定义该final变量时指定了初始值;该初始值可以在编译时就被确定下来这三个条件,这个final变量就不再是一个变量,相当于一个直接量。
29.当定义final变量时就为该变量指定了初始值,而且该初始值在编译时就能确定下来,那么这个final变量本质就是一个“宏变量”,编译器会把程序中所有用到这个变量的地方直接替换成该变量的值。
30.抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体;抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类实例。即使抽象类里不包含抽象方法,这个抽象类也不能创建实例;抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用;含有抽象方法的类(包括直接定义了一个抽象方法;或继承了一个抽象父类,但没有完全实现父类包含的抽象方法;或实现了一个接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义成抽象类。
31.接口里定义的内部类、内部接口、内部枚举默认都采用public static两个修饰符,不管定义时是否指定这两个修饰符,系统都会自动使用public static对它们进行修饰。
32.java8允许在接口中定义默认方法,默认方法必须使用default修饰,该方法不能使用static修饰,无论程序是否指定,默认方法总是使用public修饰如果开发者没有指定public,系统会自动为默认方法添加public修饰符。由于默认方法并没有static修饰,因此不能直接使用接口来调用默认方法,需要使用接口的实现类的实例来调用默认方法。
33.java8允许在接口中定义类方法,类方法必须使用static修饰,该方法不能使用default修饰,无论程序是否指定,类方法总是使用public修饰--如果开发者没有指定public,系统会自动为类方法添加public修饰符。类方法可以直接使用接口来调用。
34.内部类比外部类可以多使用三个修饰符:private、protected、static--外部类不可以使用这三个修饰符;非静态内部类不能拥有静态成员。
35.大部分时候内部类都被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与成员变量、方法、构造器和初始化块相似的类成员;局部内部类和匿名内部类则不是类成员。
36.如果外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可通过使用this、外部类类名.this作为限定来区分。
37.java8之前,java要求被局部内部类、匿名内部类访问的局部变量必须使用final修饰,java8之后,如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了final修饰。
38.Lambda表达式:形参列表。形参列表允许省略参数类型。如果形参列表只有一个参数,甚至连形参列表的圆括号也可以省略;箭头(->)。必须通过英文中画线号和大于符号组成;代码块。如果代码块只包含一条语句,Lambda表达式允许省略代码块的花括号,那么这条语句就不要用花括号表示语句结束。Lambda代码块只有一条return语句,甚至可以省略return关键字。Lambda表达式需要返回值,而它的代码块中仅有一条省略了return的语句,Lambda表达式会自动返回这条语句的值。
39.Lambda表达式的类型,也被称为“目标类型”,Lambda表达式的目标类型必须是“函数式接口”。函数式接口代表只含有一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但只能声明一个抽象方法。
40.Lambda表达式的目标类型必须是明确的函数式接口;Lambda表达式只能为函数式接口创建对象。Lambda表达式只能实现一个方法,因此它只能为只有一个抽象方法的接口(函数式接口)创建对象。
41.为了保证Lambda表达式的目标类型是一个明确的函数式接口,一般有如下三种常见形式:将Lambda表达式赋值给函数式接口类型的变量;将Lambda表达式作为函数式接口类型的参数传给某个方法;使用函数式接口对Lambda表达式进行强制类型转换。
42.如果Lambda表达式的代码块只有一条代码,还可以在代码块中使用方法引用和构造器引用。
43.引用类方法:示例(类名::类方法),说明(函数式接口中被实现方法的全部参数传给该类方法作为参数),对应的Lambda表达式:(a,b,c...)->类名.类方法(a,b,c...);引用特定对象的实例方法:示例(特定对象::实例方法),说明(函数式接口中被实现方法的全部参数传给该方法作为参数),对应的Lambda表达式:(a,b,c...)->特定对象.实例方法(a,b,c...);引用某类对象的实例方法:示例(类名::实例方法),说明(函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数),对应的Lambda表达式:(a,b,c...)->a.实例方法(b,c...);引用构造器:示例(类名::new),说明(函数式接口中被实现方法的全部参数传给该构造器作为参数),对应的Lambda表达式:(a,b....)->new 类名(a,b,...);
例子:1.引用类方法
@FunctionalInterface
 public interface Converter {
  Integer converter(String from);
 }
 Converter converter=from->Integer.valueOf(from);  改写之后:Converter converter=Integer::valueOf;
 Integer val1=converter.converter("99");
 System.out.println(val1);
2.引用特定对象的实例方法
Converter converter2 = from -> "fkit.org".indexOf(from);  改写之后:Converter converter2="fkit.org"::indexOf;
3.引用某类对象的实例方法
@FunctionalInterface
 public interface MyTest {
  String test(String a, int b, int c);
 }
 MyTest myTest = (a, b, c) -> a.substring(b, c);  改写之后:MyTest myTest = String::substring;
4.引用构造器
 @FunctionalInterface
 public interface YourTest {
  JFrame win(String title);
 }
 YourTest yourTest = a -> new JFrame(a);  改写之后:YourTest yourTest  =  JFrame::new;
44.Lambda表达式和匿名内部类的相同点:Lambda表达式与匿名内部类一样,都可以直接访问“effectively final”的局部变量,以及外部类的成员变量(包括实例变量和类变量);Lambda表达式创建的对象与匿名内部类生成的对象一样,都可以直接调用从接口中继承的默认方法。
45.Lambda表达式和匿名内部类的不同点:匿名内部类可以为任意接口创建实例--不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法即可,但Lambda表达式只能为函数式接口创建实例;;匿名内部类可以为抽象类甚至普通类创建实例,但Lambda表达式只能为函数式接口创建实例;匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法;但Lambda表达式的代码块不允许调用接口中定义的默认方法。
46.枚举类和普通类的区别:枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是默认继承Object类,因此枚举类不能显式继承其他父类。其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable接口;使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类;枚举类的构造器只能使用private访问控制符,如果省略了构造器的访问控制符,则默认使用private修饰,如果强制指定访问控制符,则只能指定private修饰符;枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例,列出这些实例时,系统会自动添加public static final修饰,无需程序员显式添加。
47.枚举类默认提供了一个values()方法,该方法可以很方便的遍历所有的枚举值。
48.java垃圾回收机制的特性:垃圾回收机制只负责回收堆内存中的对象,不会回收任何物力资源(例如数据库连接、网络IO等资源);程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行。当对象永久的失去引用后,系统就会在合适的时候回收它所占的内存;在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收。
49.大部分时候,程序强制系统垃圾回收后总会有一些效果。强制系统垃圾回收有如下两种方式:调用System类的gc()静态方法:System.gc();调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()。
50.对象的引用:强引用(StrongReference,程序创建一个对象,并把这个对象赋给一个引用变量,程序通过该引用变量来操作实际的对象);软引用(SoftReference,需要通过SoftReference类来实现,当一个对象只有软引用时,它有可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存空间足够时,他不会被系统回收,程序也可使用该对象,当系统内存不足时,系统可能会回收它。软引用通常用于堆内存敏感的程序中);弱引用(WeakReference,通过WeakReference类实现,弱引用和软引用类似,但是引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。当然,并不是说当一个对象又有弱引用时,它就会立即被回收--正如那些失去引用的对象一样,必须等到系统垃圾回收机制运行时才会被回收);虚引用(PhantomReference:通过PhantomReference类实现,虚引用完全类似于没有引用。虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在。如果一个对象只有一个虚引用时,那么它也没有引用效果大致相同。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列(ReferenceQueue)联合使用)。
51.使用这些引用类可以避免在程序执行期间将对象留在内存中。如果以软引用、弱引用或者虚引用的方式引用对象,垃圾回收器就能够随意地释放对象。如果希望尽可能减小程序在其生命周期中所占用的内存大小时,这些引用类就很有用处。
52.由于垃圾回收的不确定性,当程序从软引用、弱引用中取出被引用对象时,可能这个被引用对象已经被释放了。如果程序需要使用这个对象就需要重新穿件,具体操作有两种大致如下:
obj = wr.get();
     if(obj == null){
         wr = new WearReference(recreateIt());
         obj = wr.get();
     }
     //使用obj...
     obj = null;
     正是由于垃圾回收的不确定性,假如在if块内系统执行了垃圾回收,后面obj就有可能拿到null,所以该方法存在弊端
      obj = wr.get();
     if(obj == null){
         obj = recreateIt();
         wr = new WearReference(obj);
     }
     //使用obj...
     obj = null;
53.java程序在不同操作系统上运行时,可能需要取得平台相关的属性,或者调用平台命令来完成特定功能。java提供了System和Runtime类来与程序的运行平台进行交互。
54.System类提供了一个identityHashCode(Object x)方法,该方法返回指定对象的精确hashCode值,也就是根据该对象的地址计算得到的hashCode值。当某个对象的hashCode方法被重写后,该类实例的hashCode()方法就不能唯一的标识该对象;但是通过identityHashCode()方法返回的hashCode值,依然是根据该对象的地址计算得到的hashCode值。所以如果两个对象的identityHashCode值相同,则两个对象绝对是同一个对象。
55.Runtime可以直接单独启动一个进程来运行操作系统的命令:启动系统记事本
Runtime runtime=Runtime.getRuntime();
     try {
 runtime.exec("notepad.exe");
     } catch (IOException e) {
     // TODO Auto-generated catch block
 e.printStackTrace();
     }
56.Object类的clone()方法是一种“浅克隆”,只克隆该对象的所有成员变量值,不会对引用类型的成员变量值所引用的对象进行克隆。如果需要对对象进行“深克隆”,则需要开发者自己进行“递归”克隆,保证所有引用类型的成员变量所引用的对象都被复制了。
57.Random类专门用于生成一个伪随机数,它有两个构造器:一个构造器使用默认的种子(以当前时间作为种子),另一个构造器需要程序员显式传入一个long型整数的种子。ThreadLocalRandom类是java7新增的一个类,它是Random的增强版。在并发访问的环境下,使用ThreadLocalRandom来代替Random可以减少多线程资源竞争,最终保证系统具有更好的线程安全性。
58.Random使用一个48位的种子,如果这个类的两个实例是用同一个种子创建,对他们以同样的顺序调用方法,则它们会产生相同的数字序列。当使用默认的种子构造Random对象时,他们属于同一个种子。
59.由于java的double类型进行基本运算时会造成精度丢失,所以java提供了BigDecimal类。创建BigDecimal对象时,不要直接使用double浮点数作为构造器参数来调用BigDecimal构造器,否则同样会发生精度丢失。如果使用BigDecimal(String val)构造器的结果是可预知的,因此通常建议优先使用基于string的构造器,如果必须使用double浮点数作为BigDecimal构造器的参数时,不要直接将double浮点数作为参数,应该通过BigDecimal.valueOf(double value)静态方法来创建BigDecimal对象。
60.关于Calendar类有个好用的方法add()和roll()功能类似都是接收两个参数,一个field代表操作的是年月日时分秒之一,第二个参数是要添加(正数)减少(负数)的值,两个方法的区别add如果field添加超过限制会改变前一位,roll不会。
61.Calendar类的set方法有延时性,每次执行set之后Calendar对象并没有立即更改只是在内部标记了一个set了某字段的属性,当再次调用get()/getTime()/getTimeInMillis()/add()或roll()时才会改变。这样可以避免不必要的计算。
62.当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传递给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。
63.Iterator必须依附于Collection对象,若有一个Iterator对象,则必须有一个与之关联的Collection对象。Iterator提供了两个方法来迭代访问Collection集合里的元素,并可通过remove()方法来删除集合中上一次next()方法返回的集合元素。
64.当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove()方法删除上一次next()方法返回的集合元素才可以,否则将发生java.util.ConcurrentModificationException异常。
65.当使用foreach循环迭代访问集合元素时,该集合也不能改变否则将引发ConcurrentModificationException异常。
66.java8位Collection集合新增了一个removeIf(Predicate filter)方法,可以批量删除filter条件的所有元素,Predicate也是函数式接口,因此可使用Lambda表达式作为参数。使用Predicate的test()方法判断该对象是否满足Predicate指定的条件。
67.HashSet中不能存入相同的元素,判断元素是否相等的标准是,对象的hashCode()方法的返回值。
67.当向hashSet中添加可变对象时,必须十分小心,如果修改HashSet集合中的对象,有可能导致该对象与集合中的其他对象相等(HashCode值相等),从而导致HashSet无法准确访问该对象。
68.HashSet中添加的元素都是无序的,HashSet的子类LinkedHashSet存入数据是有序的,但相应的性能会略低于HashSet。
69.TreeSet是SortedSet接口的实现类,可以确保元素处于排序状态,因为TreeSet中的元素是有顺序的,所以增加了访问第一个、前一个、后一个、最后一个元素的方法,并提供了三个从TreeSet中截取子TreeSet的方法。
70.Treeset根据元素的实际值大小来进行排序,不是根据插入顺序。HashSet根据hash算法确定元素的存储位置,TreeSet采用红黑树的数据结构来存储集合元素。Treeset支持自然排序和定制排序。
71.自然排序:TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素升序排列。
72.Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法。例如obj1.compareTo(obj2),如果该方法返回0则表明两个对象相等;如果该方法返回一个正整数,则表明obj1大于obj2;如果该方法返回一个负整数,则表明obj1小于obj2。
73.向TreeSet中添加元素时,只有第一个元素无需实现Comparable接口,后面的所有元素必须实现,即便如此从TreeSet中取出元素时,仍然会引发ClassCastException,所以最好所有元素都要实现Comparable接口。
74.一旦改变了TreeSet集合里可变元素的实例变量,当再企图删除该对象时,TreeSet也会删除失败(甚至集合中原有的、实例变量没有被修改但与修改后元素相等的元素也无法删除)。所以为了程序更加健壮,不要修改放入HashSet和TreeSet集合中元素的关键实例变量。
75.定制排序:再创建TreeSet时,提供一个Comparator对象与该TreeSet集合关联。
76.EnumSet是一个专为枚举类设计的集合类,所有元素必须是指定枚举类型的枚举值,集合元素也是有序的以枚举值再Enum类内的定义顺序来确定集合元素的顺序。
77.EnumSet在内部以位向量的形式存储。EnumSet没有提供任何构造方法,要通过提供的类方法构建实例。
78.List代表一个元素有序,可重复的集合,集合中每个元素都有其对应的顺序索引。
79.List集合是根据对象的equals()方法的返回值来判断对象是否相等的。
80.ArrayList和Vector都是List的实现类,都是基于数组实现的List类,内部封装了一个动态的、允许再分配的Object[]数组,它们内部使用initialCapacity参数来设置该数组的长度,每次添加元素超出该数组长度时,它们的initialCapacity会自动增长,如果向ArrayList或Vector集合中添加大量元素时,可使用ensureCapacity(int minCapacity)(将ArrayList或Vector集合的数组长度增加大于或等于minCapacity值)方法一次性的增加initialCapacity。可以减少重分配的次数,提高性能。
81.如果开始就知道ArrayList或Vector集合需要保存多少个元素,可以在创建的时候指定initialCapacity。如果创建空的ArrayList或Vector集合,默认长度是10。
82.ArrayList和vector用法相同,Vector是一个非常古老的借口从JDK1存在,方法名大多很长不方便使用,但是是线程安全的,ArrayList使用方便但是多线程使用要注意很多问题(线程不安全)。
83.Vector的子类Stack是栈式存储结构,但是由于古老,推荐使用ArrayDeque(List和Deque的实现类)。
84.固定长度的List:Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加、删除。通过Arrays工具类的asList(Object... a)获得实例。
85.Queue集合:Queue用于模拟队列这种数据结构,队列先进先出,头部保存在队列中存放时间最长的元素,尾部保存在队列中存放时间最短的元素。Queue接口有一个PriorityQueue实现类。除此之外,Queue还有一个Deque接口,Deque代表一个双端队列,双端队列可以同时从两端来添加删除元素,因此Deque的实现类既可以当队列使用也可以当成栈使用。Java为Deque提供了ArrayDeque和LinkedList两个实现类。
86.PriorityQueue实现类:一个对列实现类,但是不是绝对标准的对列实现,因为它保存队列的顺序不是按照加入队列的顺序,而是按队列元素的大小进行重新排序的。
87.Deque是Queue接口的子接口,代表双端队列,ArrayDeque是Deque的实现类,也是基于数组实现的,内部数组默认长度16.
88.LinkedList是List和Deque的实现类,与ArrayDeque和ArrayList实现机制不同,它内部以链表形式保存元素,因此随机访问集合元素时性能较差,但是在插入、删除元素时性能出色。而以数组形式实现的集合随机访问元素的性能较好。
89.Collections类中提供了多个synchronizedXxx()方法,该方法可将指定集合包装成线程同步的集合,解决多线程并发访问集合的线程安全问题。
90.数组和泛型有所不同,假设Foo是Bar的一个字类型(子类或者子接口),那么Foo[]依然是Bar[]的子类型;但G\<Foo\>不是G\<Bar\>的子类型。
91.当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉。
92.查看表结构desc 表名;查看建表约束show create 表名;删除约束alter table 表名 drop index street;添加约束alter table [table_name] add constraint [constraint_name] [unique| primary key|foreign key] ([column_name]);
93.主键约束相当于非空约束和唯一约束,即主键约束的列既不允许出现重复值,也不允许出现null值;如果对多列组合建立主键约束,则多列里包含的每一列都不能为空,但只要求这些列组合不能重复。主键列的值可用于唯一的标识表中的一条记录。
94.每一个表中最多允许有一个主键,但这个主键约束可由多个数据列组合而成,主键是表中能唯一确定一行记录的字段或字段组合。
95.对于mysql而言,如果在算数表达式中使用null,将会导致整个算术表达式的返回值为null,如果在字符串连接运算中出现null,将会导致连接之后的结果也为null。
96.sql中判断两个值是否相等的比较运算符是单等号,判断不相等的运算符是<>;sql中的赋值运算符不是等号,而是冒号等号(:=)。
97.使用distinct去除重复行,它的作用是去除后面字段组合的重复值,而不管对应记录在数据库是否重复。
98.mysql使用模糊查询时,_代表一个字符,%代表多个字符,如果真正需要用到这些字符用\转义。
99.cross join(交叉连接即笛卡尔积),natural join(自然连接,基于有相同的列名的表作连接,如果没有相同列效果和交叉连接相同),using字句连接(和自然连接相比可以指定连接参考的列,如果指定的列在某张表不存在会报错),on字句连接(on后面跟连接条件),左右全外连接(left、right、full join,连接条件也用on,左外连接把左表不符合条件的行列出,右外连接把右表不符合条件的行列出)。
100.Windows的路径分隔符使用(\),而java程序中的反斜线表示转义字符,所以如果需要在windows的路径下包括反斜线,则应该使用两条反斜线或者直接使用(/)也可以,java程序支持将斜线当成平台无关的路径分隔符。
101.InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流。OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流。
102.读写文件时一定要注意字符流对应字符流,字节流对应字节流。如果进行输入/输出的内容是文本内容,则应该考虑使用字符流;如果进行输入输出的内容是二进制内容,则应该考虑使用字节流。
103.通过在实例变量前面使用transient关键字修饰,可以指定Java序列化时无需理会该实例变量。使用transient修饰的实例变量将被完全隔离在序列化机制之外,这样导致在反序列化恢复Java对象时无法取得该实例变量的值。
104.文件锁控制文件的全部或部分字节的访问,Java提供了FileLock支持文件锁功能。在FileChannel中提供了lock()/tryLock()方法可以获得文件锁FileLock对象,从而锁定文件。两个方法区别:lock()试图锁定某个文件时,如果无法得到文件锁,阻塞线程;而tryLock()是尝试锁定文件,它将直接返回不会阻塞,返回文件锁或者null。
105.如果FileChannel只想锁定文件的部分内容可以使用lock(long position,long size,boolean shared)对文件从position开始,长度为size的内容加锁,阻塞式;tryLock(long position,long size,boolean shared),非阻塞式加锁。当shared为true时,表明该锁是一个共享锁,它将允许多个进程来读取该文件,但阻止其他进程获得对应文件的排它锁。当shared为false,表明该锁是一个排它锁,它将锁住对该文件的读写,可以通过FileLock的isShared来判断它获得的锁是否为共享锁。
106.处理完文件后通过FileLock的release()方法释放文件锁。
107.并发性和并行性是两个概念,并行指在同一时刻有多条指令在多个处理器上同时执行;并发指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
108.多线程优势:进程之间不能共享内存,但是线程之间共享内存非常容易。系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高。Java语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编程。
109.使用继承Thread类的方法来创建线程类,多个线程之间无法共享线程类的实例变量。
110.Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体,而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
111.采用Runnable接口的方式创建的多个线程可以共享线程类的实例变量。这是因为在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程可以共享同一个target,所以线程可以共享一个线程类(实际上应该是线程的target类)的实例变量。
112.线程的生命周期:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。
113.new出一个线程类之后该线程处于新建状态,调用start方法之后处于就绪状态,如果手动调用了线程的run方法,该线程类将被当做普通Java对象调用(单线程的),只能对处于新建状态的线程调用start()方法,否则将引发IllegalTheadStateException异常。
114.如果希望调用子线程的start()方法后子线程立即开始执行,程序可以使用Thread.sleep(1)来让当前运行的线程(主线程)睡眠1毫秒-1毫秒就够了,因为在这1毫秒内CPU不回空闲,它会去执行另一个处于就绪状态的线程,这样就可以让子线程立即开始执行。
115.调用线程的isAlive()方法可以测试某个线程是否死亡,当线程处于就绪、运行、阻塞三种状态时,该方法返回true;当线程处于新建、死亡两种状态时,返回false。
116.Thread提供了一个让一个线程等待另一个线程完成的方法-join()方法。当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。
117.调用setDaemon(true)(必须在start方法之前调用)方法可以指定线程设置成后台线程,如果所有前台线程都死亡,后台线程会自动死亡,前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。
118.通过调用Thread类的静态sleep()方法实现当前执行的线程暂停一段时间,并进入阻塞状态。
119.yield()方法也是Thread类的一个静态方法,可以让正在执行的线程暂停,但不会阻塞该线程,它只是将该线程转入就绪状态。该方法只是让当前线程暂停,让系统的线程调度器重新调度一次,完全有可能还调度到自己。事实上只有优先级比当前线程高或者和当前线程相等且处于就绪状态的线程才有可能执行。
120.任何时刻只有一个线程可以获得对同步监视器的锁定,当同步代码执行完成后,该线程会释放对该同步监视器的锁定。
121.synchronized关键字可以修饰方法,可以修饰代码块,不能修饰构造器,成员变量等。
122.线程会在如下几种情况释放对同步监视器的锁定:a、当前进程的同步方法、同步代码块执行结束;b、当前线程在同步方法、同步代码块中遇到break、return终止了执行;c、当前线程在同步代码块、同步方法出现未处理的error或Exception导致异常结束;d、当前线程在执行同步代码块或者同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。
123.如下情况线程不会释放同步监视器:a、线程执行同步方法或者同步代码块,调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器;b、线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器。
124.Object类提供的wait(),notify()和notifyAll()三个方法必须由同步监视器对象来调用,对于有synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这三个方法。对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这个方法。
125.wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。wait()方法有三种形式,无参(一直等待,直到其他线程通知),带毫秒参数的wait(),带毫秒微妙参数表示一段时间后释放对该同步监视器的锁定。
126.notify():唤醒在此同步监视器上等待的单个县城。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定后(使用wait()方法),才可以执行被唤醒的线程。
127.notifyAll():唤醒再次同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后才可以执行被唤醒的线程。
128.Condition实例被绑定在一个Lock对象上,通过Lock对象的newCondition()方法获取Condition实例,Condition实例提供了如下三个方法:
129.await()类似于隐式同步监视器上的wait()方法。signal()类似于notify()。signalAll()类似notifyAll()方法。
130.BlockingQueue:可以作为线程同步工具,当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则线程被阻塞;当消费线程试图从BlockingQueue中取出元素时,如果该队列已空,则改程序被阻塞。
131.BlockingQueue继承了Queue接口,可以使用Queue接口中的方法,归纳如下:a.在队列尾部插入元素。包括add(E e)、offer(E e)和put(E e)方法,当该队列已满,这三个方法分别会抛出异常、返回false、素色队列;b.在队列头部删除并返回删除的元素。包括remove()、poll()和take()方法。当该队列已空时,这三个方法分别会抛出异常、返回false、阻塞队列;c.在队列头部取出但不删除元素。包括element()和peek()方法,当队列已空时,这两个方法分别抛出异常、返回false。
132.线程池:通过Executors工厂类来生产线程池。
  a.newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池;
  b.newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中;
  c.newSingleThreadExecutor():创建一个只有单线程的线程池,它相当于调用newFixedThreadPool()方法时传入参数为1;
  d.newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池内。
  e.newSingleThreadScheduleExecuter():创建只有一个线程的线程池,它可以在指定延迟之后执行线程任务;
  f.ExecutorSevice newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争;
  g.ExecutorService newWorkStealingPool():该方法是前一个方法的简化版本。如果当前机器有4个CPU,则目标并行级别被设置成4,也就是相当于为前一个方法传入4作为参数。
前三个方法返回ExecutorService对象,代表一个线程池,它可以执行Runnable对象或Callable对象所代表的线程;中间两个方法返回ScheduleExecutorService线程池,它是ExecutorService的子类,可以在指定延迟后执行线程任务;最后两个方法生成work stealing池,相当于后台线程池,如果所有的前台线程都死亡了,work stealing池中的线程会自动死亡。
133.线程池使用步骤:a.调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。b.创建Runable实现类或Callable实现类的实例,作为线程执行任务。c.调用ExecutorService独享的submit()方法来提交Runnable实例或Callable实例。d.当不想提交任何任务时,  调用ExecutorService对象的shutdown()方法来关闭线程池。
134.ForkJoinPool支持将一个任务拆分成多个小任务并行计算,再把多个小任务的结果合并成总的计算结果。ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池,两个构造器:ForkJoinPool(int parallelism):创建一个包含parallelism个并行线程的ForkJoinPool,ForkJoinPool()创建一个以Runtime.availableProcessors()方法的返回值作为parallelism参数来创建ForkJoinPool。
135.创建ForkJoinPool之后就可以调用submit(ForkJoinTask task)或invoke(ForkJoinTask task)方法来执行指定的任务。ForkJoinTask代表一个可以并行、合并的任务。ForkJoinTask是一个抽象类,他还有两个抽象子类:RecursiveAction和RecursiveTask。其中RecursiveTask代表有返回值的任务,而RecursiveAction代表没有返回值的任务。
136.ThreadLocal代表一个线程局部变量,通过把数据放在ThreadLocal中就可以让每个线程创建一个该变量的副本,从而避免并发访问的线程安全问题。
137.同步机制是为了同步多个线程对相同资源的并发访问,是多个线程之间进行通信的有效方式;ThreadLocal是为了隔离多个线程的数据共享,从根本上避免多个线程之间对共享资源的竞争,也就不需要对多个线程进行同步了。
138.由于ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的,如果程序中有多个线程可能访问这些集合,可以使用Collections提供的类方法把这些集合包装成线程安全的集合,synchronizedCollection\synchronizedList\synchronizedMap\ synchronizedSet\SynchronizedSortedMap\synchronizedSortedSet等
139.除了上面的方法,还有一些线程安全的集合类大致分为两类:
   a.以Concurrent开头的集合类ConcurrentHashMap,ConcurrentSkipListMap,ConcurrentSkipListSet,ConcurrentLinkedQueue和ConcurrentLinkedDeque;
   b.以CopyOnWrite开头的集合类,如CopyOnWriteArrayList,CopyOnWriteArraySet。
140.当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化。
141.类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
142.类的连接:当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JAR中。类连接又可分为如下三个阶段:
   a.验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致;
   b.准备:类准备阶段则负责为类的类变量分配内存,并设置默认初始值;
   c.解析:将类的二进制数据中的符号引用替换成直接引用。
143.类的初始化:虚拟机负责对类进行初始化,主要就是对类变量进行初始化。在java中对类变量指定初始值有两种方式:
   a.声明类变量是指定初始值;
   b.使用静态初始化块为类变量指定初始值。
144.类初始化的时机:
   a.创建类的实例(使用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例);
   b.调用某个类的类方法;
   c.访问某个类或接口的类变量,或为该类变量赋值;
   d.使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。例如代码:Class.forName("Person"),如果系统还未初始化Person类,则这行代码会导致该Person类被初始化,并返回Person类对应的java.lang.Class對象;
   e.初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化;
   f.直接使用java.exe命令来运行某个主类。当运行某个主类时,程序会先初始化该主类。
145.对于final型的类变量,如果该类变量的值在编译时就可以确定下来,那么这个类变量相当于“宏变量”。java编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此即使程序使用该静态类变量,也不会导致该类的初始化。
146.当使用ClassLoader类的loadClass()方法来加载某个类时,该方法只是加载该类,并不会执行该类的初始化。使用Class的forName()静态方法才会导致强制初始化该类。
147.类加载器负责加载所有的类,系统为所有被加载入内存的类生成一个java.lang.Class实例。一旦一个类被载入JVM中,同一个类就不会被再次载入。在java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为唯一标识。
148.Jvm启动时,会形成由三个类加载器组成的初始类加载器层次结构:Bootstrap ClassLoader(根类加载器),Extension ClassLoader(扩展类加载器),System ClassLoader(系统类加载器)。
149.Bootstrap ClassLoader负责加载Java核心类(Java/jdk1.8.0/jre/lib/rt.jar),不是java.lang.ClassLoader的子类,而是由JVM自身实现。Extension Classloader负责加载FRE的扩展目录(jre/lib/ext或者由java.ext.dirs系统属性指定的目录)中JAR包的类。System ClassLoader负责在JVM启动时来加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,用户自定义的类加载器都以类加载器作为父加载器。
150.类加载机制有三种:全盘负责(当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入);父类委托(先让parent(父)类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类);缓存机制(保证所有加载过得Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区中)。
151.类加载器加载Class大致要经过八个步骤:
  1.检查此Class是否载入过(即在缓存区中是否有此Class),如果有直接进入最后一步,否则执行第二步;
  2.如果父类加载器不存在(如果没有父类加载器,则要么parent一定是根类加载器,要么本身就是根类加载器),则跳第四步,如果父类加载器存在,则执行第三步
  3.请求使用父类加载器去载入目标类,如果成功载入则跳8,否则跳5;
  4.请求使用根类加载器来载入目标类,如果成功载入跳8,否则跳7;
  5.当前类加载器尝试寻找Class文件(从与此ClassLoader相关的类路径中寻找),如果找到则执行6,找不到执行7;
  6.从文件中载入Class,成功后跳8;
  7.抛出ClassNotFoundException异常;
  8.返回对应的java.lang.Class对象。
152.JVM中除根类加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。ClassLoader有如下两个关键方法:
  loadClass(String name,boolean resolve):该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象。
  findClass(String name):根据指定名称来查找类。
     如果需要实现自定义的ClassLoader,则可以通过重写以上两个方法来实现,通常推荐重写findClass()方法,而不是重写loadClass()方法。loadClass()方法的执行步骤如下:
     1.用findLoadedClass(String)来检查是否已经加载类,如果已经加载则直接返回。
     2.在父类加载器上调用loadClass()方法。如果父类加载器为null,则使用根类加载器来加载。
     3.调用findClass(String)方法查找类。
      从以上步骤可以看出,如果重写findClass()方法可以避免覆盖默认类加载器的父类委托、缓冲机制两种策略;如果重写loadClass()方法,则实现逻辑更为复杂。
153.在ClassLoader里还有一个核心方法:Class defineClass(String name,byte[] b,int off,int len),该方法负责将指定类的字节码文件(即Class文件,如Hello.class)读入字节数组byte[] b内,并把它转换为Class对象,该字节码文件可以来源于文件、网络等。
154.在java程序中获得Class对象通常有如下三种方式:
  1.使用Class类的forName(String clazzName)静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须添加完整包名);
  2.调用某个类的class属性来获取该类对应的Class对象。例如,Person.class将会返回Person类对应的Class对象。
  3.调用某个对象的getClass()方法。该方法是java.lang.Object类中的一个方法,所以所有的Java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象。
155.通过反射来生成对象有两种方式:
  1.使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器,而执行newInstance()方法时实际上是利用默认构造器来创建该类的实例;
  2.先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。
156.通过反射调用方法:当获得某个类对应的Class对象后,可以通过该Class对象的getMethods()或者getMethod()方法获取全部或指定方法,这两个方法返回值为Method数组或者Method对象。每个Method对象对应一个方法,可以调用Method里包含的invoke()方法来调用该方法。
157.如果程序需要调用某个对象的private方法,可以先调用Method对象的setAccessible(boolean flag)方法,将Method对象的accessible设置为指定的布尔值。true指示该Method在使用时应该取消java语言的访问权限检查;值为false,则指示该Method在使用时要实施java语言的访问权限检查。
158.setAccessible(boolean flag)方法并不属于Method,而是属于它的父类AccessibleObject,因此Method、Constructor、Field都可调用该方法,从而实现反射来调用private方法、private构造器和private成员变量。
159.通过反射来访问成员变量:通过Class对象的getFields()(getDeclaredFields()获取各种访问修饰符的变量)或者getField()(getDeclaredField()获取各种访问修饰符的变量)方法可以获取该类所包含的全部成员变量或者指定成员变量。
  getXxx(Object obj):获取obj对象的该成员变量的值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用类型,则取消get后面的Xxx。
  setXxx(Object obj,Xxx val):将obj对象的该成员变量设置成val值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用类型,则取消set后面的Xxx。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值