JAVA笔记1

泛型

泛型,一定是为了规定某个属性、或者方法的入参、出参的类型!

JAVA中的泛型声明,一定是写在<>里面的,所以是谁的泛型,看<>写在哪。

这个是类的声明:

static class Genericdemo<T,k>{
public T x; public T Y;
        public T getValue() {
            return value;
        }
        public Genericdemo(T value, K key) {
            super();
            this.value = value;
            this.key = key;
        }

}

这个是方法的声明:

public <T> T tell(T t){ 
return t;
}
泛型配合反射

泛型里面要填反射得到的类的类型,不能用List这种。。。。因为<>里面放的东西不会作为java代码去进行执行。

所以只能用泛型背身的规则,在类上面设定一个泛型T,然后在方法里面用List。

反序列化

使用jackson进行反序列化需要指定类型时:

当想要获取复杂泛型的类类型时,不能用ArrayList<HashMap>.class

需要借助工具类TypeReference,写成 new TypeReference<ArrayList<HashMap>>(){}

这个写法也是有讲究的。TypeReference本身是一个抽象类,不能实例化。new TypeReference<ArrayList<HashMap>>(){}的写法是构造一个匿名内部类。

互为主备

ES里面:互为主备的意思是,每台服务器即放一部分主节点,又放一部分副节点,不是一台完全放主,一台完全放副。

MyBatis

mybatis解析jdbcType=xxx,后面的xxx连大小写都不能错。否则会报错。

附上对照类型:

 1 JDBC Type           Java Type  
 2 CHAR                String  
 3 VARCHAR             String  
 4 LONGVARCHAR         String  
 5 NUMERIC             java.math.BigDecimal  
 6 DECIMAL             java.math.BigDecimal  
 7 BIT                 boolean  
 8 BOOLEAN             boolean  
 9 TINYINT             byte  
10 SMALLINT            short  
11 INTEGER             INTEGER  
12 BIGINT              long  
13 REAL                float  
14 FLOAT               double  
15 DOUBLE              double  
16 BINARY              byte[]  
17 VARBINARY           byte[]  
18 LONGVARBINARY       byte[]  
19 DATE                java.sql.Date  
20 TIME                java.sql.Time  
21 TIMESTAMP           java.sql.Timestamp  
22 CLOB                Clob  
23 BLOB                Blob  
24 ARRAY               Array  
25 DISTINCT            mapping of underlying type  
26 STRUCT              Struct  
27 REF                 Ref  
28 DATALINK            java.net.URL[color=red][/color] 

classpath

<importresource="classpath\*:/META-INF/spring/capacity.mybaties.service.xml"/>

这个classpath真的就是系统的classpath,包含当前目录和放第三方jar的地方。

所以才有那么多用classpath*这种写法的,遍历jar包里面的文件。

Spring注入
@Autowired
Private UserMapper userMapper;

直接这么配一个mapper是不会有注入的。为什么呢?
一定要在这个类上配@component这样的注解,因为你首先要把这个bean配给spring去管理,其次你才是能够往这个bean里面去注入属性呀。
此外,使用了这个注入的类本身也需要用@service这样的注入才行,因为你只有交给spring管理,spring才能往你的字段里面去注入这个属性。
最后,使用了这个注入的类的这个类,还不能new出来,因为你new出来的就不是spring管理的,spring没法往你的字段里面去注入。所以你只能通过去spring的context里面取出来才行。

如果这个属性注入不是通过注解注入,是通过xml配置,还需要在代码里有对应的有参构造方法或者set方法。因为spring的机理就是通过反射调用这些方法去注入的。(不知道为什么通过注解的时候可以不需要,可能是因为注解之后使用的是反射去拿的这些数据?)

当我们使用autowired的时候,我们知道其类也要交给spring去管理。

但是我们经常会遇到一个坑,那就是,当我们以前是new去创建那个对象,后来我们给那个类加了注入,也把那个类交给了spring管理,但是这个时候呢,发现以前的代码不可用了。

为什么呢?就是因为以前的代码是new出来的对象。不是取的spring管理的对象。

导出excel
  • 需要提前判断数据量,防止数据量太大导致OOM;
  • 数据量大的时候导出需要做异步,不然等待时间会很长;
  • 生成excel一般是一个一个单元格的创建,但是比较费时,大数据可以考虑先创建模板,然后往里面写数据;
  • 数据量实在太大的时候,可以导出为csv的文件,而不是excel。
Excel显示数字

Excel显示数字时,如果数字大于12位,它会自动转化为科学计数法;如果数字大于15位,它不仅用于科学技术费表示,还会只保留高15位,其他位都变0。

这是个坑,所以在导出时可以考虑在数字后面加上一个看不见的字符,比如制表符“\t”,这样就会被当做是字符串进行处理。使用制表符还有一个好处,就是在excel里面看不出来,只有在TXT里面用十六进制打开才能看出来里面有一个字符。

CSV文件中的逗号和双引号

CSV是默认用逗号作为分隔符的,如果你的文件本身就含有逗号呢,那就会出现错乱。

这个时候需要用双引号把有逗号的地方括起来,像ab,c—>“ab,c”

但是如果原先的字段里面又有双引号呢,这个时候就需要把原先的双引号加倍,像a"b,c"“d—>“a,”“b,c””"“d”

分布式和集群
  • 分布式是一个拆成多个;
  • 集群是一个复制成多个;
fileOutputStream内存使用

我们在使用fileOutputStream向文件里写数据时:

首先内存里一般有我们的数据源,这里占用一次内存(除非我们是从一个input流里读数据);

其次我们的这个outputstream本身就会在内存里保留一定量的数据,满足其输出机制的情况下再往硬盘里写(比如在flush()的时候就会写一次);

所以,总共在内存里面可能会存有两份数据。

windows的内存申请

第一种原因(主要影响):

系统的内存有8G,实际使用了5G,但是此时其实还有2G被各种程序占用了,但是没有被使用。

所以其实只剩下1G可以使用了,如果你一申请使用的内存超过1G, 就会报OOM。

这个可以通过看资源监视器的“physical memory” 和“commit ”来观察。

第二种原因(次要影响):

因为内存不够了,要新申请一段内存来用,这段内存申请不下来,就直接报OOM了。

比如你现在用了1G内存,你嫌不够,要再申请1G。此时系统还有500M内存,但是你申请不了1G,你就报OOM了,而不是一点一点用到500M都没有了才报。

浮点数格式化

如果要让浮点数显示固定格式,一定不要在格式化后再进行计算,否则之前的格式化就相当于失败了,还是会有可能显示199.00000000000003这种数字。

字符流与字节流

字符流和字节流的本质区别就是名字上的区别:

字符流和字节流都是可以通过write写数据的,但是他们写的是不一样内容。

字符流的write接受的是String。

字节流的write接受的是byte[]。

所以你想用字节流写文字,还是得自己先手动把文字String转换成byte[],字符流也就是起了这个作用罢了。

如果想指定字符的编码字符集,必须使用字节码转字符码。

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filename, true),"UTF-8");

之后你可以包装成BufferedWriter也没有问题。

NGINX

通过nginx进行转发的时候,原先网页穿过来的https协议会在过了nginx这个关卡后会变成http.

TOMCAT

TOMCAT的conf目录下,有两个重要的文件,一个是web.xml,还有一个是context.xml。

Web.xml配置了要被所有项目使用的配置,而context.xml配置了各个项目自己的web.xml所放置的目录,一般是“WEB-INF/web.xml”,也就是各个项目的web.xml都放在自己项目的WEB-INF目录下面。

转发和重定向

servlet的forward和redirect的本质区别在于:

  • forward是请求转发,是转发到同一个项目里。
  • redirect是请求重定向,会要求浏览器再重新发一次请求,不要求在统一项目里。

假设初始请求是http:10.10.0.0:8080/myproject/1folder/2index.html

我们知道,根据tomcat的要求,/myproject是项目名(只有一个默认的ROOT项目时可以省略这一层),/1folder/2index.html是sevletPath(暂时不考虑sevletInfo,好像现在因为都是REST所以被淘汰了)。

假设我们要转到/1folder/2folder/3index.html。就是从第二层文件夹转到第三层文件夹。

无论是转发还是重定向,都可以使用相对路径以及绝对路径。

相对路径:写法是“xx/xx”,最前面没有“/”。

转发和重定向的相对路径写法都是一样的,因为都是相对于本次请求的关系。

  • 转发:request.getRequestDispatcher("2folder/3index.html").forward(request,response)
  • 重定向:response.redirect("2folder/3index.html")

绝对路径:写法是“/xx/xx”,最前面有“/”。

转发和重定向的绝对路径是有区别的。

因为转发是在项目里面转发,你不能转发到别的项目里面去,那你得绝对路径不可能跨出项目,所以最前面的“/”后面肯定是项目内的根路径。

而重定向可以浏览器重新请求,定向到别的项目里面,自然,它的绝对路径最前面的“/”后面就应该是从项目名开始。

  • 转发:request.getRequestDispatcher("/1folder/2folder/3index.html").forward(request,response)
  • 重定向:response.redirect("/myproject/1folder/2folder/3index.html")
获取测试资源

相对路径:

  • 使用this.getClass().getResourceAsStream("a.txt"),获取的文件是跟本class文件同文件夹下的文件(项目名/target/test-classes/com/abc/def/a.txt)
  • 使用new File("a.txt"),获取的是项目目录下的文件,也就是说文件和src、target同目录(项目名/a.txt)

绝对路径:

  • 使用this.getClass().getResourceAsStream("/a.txt"),获取的文件是直接在测试文件夹根目录下的文件(项目名/target/test-classes/a.txt)
  • 使用new File("/a.txt"),获取的是磁盘根目录下的文件(D:/a.txt)
换行符

我们知道windows(\r\n)和unix(\n)的换行符不一样。

但是\n和\r其实这本质上就是两个符号而已,决定他们是什么含义完全由协议决定。

比如HTTP传输协议(RFC2068)中,就强制要求,请求头和请求体之间的换行必须是CRLF,不能只有一个LF。而在请求体内部,当请求类型时TXT时,允许通过单个的LF或者CR来进行换行。

WEB服务和WEB服务器

Web服务和Web服务器不是一个东西。

  • Web服务器是用来解析HTTP请求的,把请求按照协议的规定进行解析的。
  • Web服务是运行在web服务器上的服务程序,需要解析什么样的协议可以自己定制。

所以Web服务可以选择SOAP也可以选择REST。

web服务选择SOAP的话,SOAP是基于XML格式的协议进行通信,需要把XML格式写在HTTP里面的正文里面。

WEB服务,也就是SOAP,就是通过XML格式传递信息,然后放在HTTP的请求里面,通过HTTP进行发送的方式。

从LIST取数据

获取list里面的数据,在JAVA里面,ArrayList和LinkedList一样的。都是用xxx.get(i)来获取。

但是,其内部实现是完全不一样的,因为ArrayList是数组,所以会直接定位到i位置。而LinkedList是链表,每一次get(i)都是从头开始遍历整个链表去到目的地,所以时间特别长。

所以要遍历一个List里面的所有数据,如果是linkedlist,绝对绝对不能写成for(int i ,i<x, i++){ list.get(i)}的形式,因为这样会特别耗时。

增强for循坏(写作for(xx: list){})完美的解决了这个问题,其不仅也是顺序的,也不会有效率问题。

因为增强for循坏是调用的ArrayList或者LinkedList本身内部迭代器的next()方法。ArrayList内部是每次都数组位置+1;LinkedList是每次索引+1,而不会从头开始。

异常处理

对于异常的处理,最佳的实践是每一层都把异常catch住,然后每一层都往上抛,最后在最表层把堆栈信息打印出来。记住不需要每一次层都去打印堆栈。因为每一层打印的话大家的堆栈有效信息其实是一样的。最佳的方式是在最表层进行打印。

最佳实践的代码示例:

Public void layer2() throws Exception{
try{
layer3();
}catch(Exceptione){
e.printStackTrace();//此行可选,只在最表层打印
Throw new Exception("layer2备注",e);
}
}

下面是在最表层打印 的效果:

java.lang.Exception: layer2备注
	at huawei.testmain.layer2(testmain.java:27)
	at huawei.testmain.layer1(testmain.java:33)
	at huawei.testmain.main(testmain.java:42)
Caused by: java.lang.Exception: layer3备注
	at huawei.testmain.layer3(testmain.java:16)
	at huawei.testmain.layer2(testmain.java:24)
	... 2 more
Caused by: java.lang.Exception: layer4备注
	at huawei.testmain.layer4(testmain.java:6)
	at huawei.testmain.layer3(testmain.java:13)
	... 3 more

中间层打印的效果:

java.lang.Exception: layer3备注
	at huawei.testmain.layer3(testmain.java:16)
	at huawei.testmain.layer2(testmain.java:24)
	at huawei.testmain.layer1(testmain.java:33)
	at huawei.testmain.main(testmain.java:42)
Caused by: java.lang.Exception: layer4备注
	at huawei.testmain.layer4(testmain.java:6)
	at huawei.testmain.layer3(testmain.java:13)
	... 3 more

最底层打印的效果:

java.lang.Exception: layer4备注
	at huawei.testmain.layer4(testmain.java:6)
	at huawei.testmain.layer3(testmain.java:13)
	at huawei.testmain.layer2(testmain.java:24)
	at huawei.testmain.layer1(testmain.java:33)
	at huawei.testmain.main(testmain.java:42)

无论在那一层打印,都能显示从头到尾的调用信息(前提是底层有把异常往表层throw,如果没有的话表层在打印堆栈的时候会没有那个caused by后面的信息)。

但是使用不断往上抛异常然后在表层打印的方式,可以把底层抛异常时添加的备注信息也打出来。而在底层打印无法打出表层的备注信息

如果你不加备注的话,try catch住然后再往外抛和不抓住直接抛是差不多的,就看你的finally有没有需要写的代码。

new对象

对象要在开始使用的地方再new出来,不要老早就new出来,然后一路传过去。

外部接口交互

代码里如果有用到外部接口,一定要把所有的可能问题都想到,然后用异常处理机制处理好,不然以后出问题了,虽然可能不在你,但是你去定位的话特别特别麻烦。

boolean对象命名

任何bolean变量都不要命名成isXXXX,这样会有序列化问题。

因为框架在序列化的时候,会把这种变量的set方法也写成isXXXX(),然后变量名就会被认为是XXXX,然后导致在解析的时候可能会弄错导致属性获取不到,进而抛出异常。

当然,也有的框架能力比较强,支持处理这种问题,但是我们还是不要去冒这个险了。

ObjectMapper异常

ObjectMapper有个坑,就是序列化的时候如果它处理不了就会给你抛异常(其实也是合理的,但是你不想它提示)。

比如从字符串反序列化到对象的时候,如果字符串里有内容无法找到对应的字段,就会抛出异常。这种异常可以在配置ObjectMapper的时候给配置掉。

new ObjectMapper().configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES,false).writeValueAsString(executeParameter);
JAVA自带的queue和stack

需要栈或者堆的数据结构时,不推荐用stack或者queue,推荐使用linkedlist(这也是stack和queue的底层实现),而且他们都能够转型成为Array(通过原生的toArray方法)、LinkedList(用构造方法)、ArrayList(用构造方法)等数据类型。

不管stack还是queue,他们转换成array这些数据类型的时候,顺序都是按先放进去的是小号、后放进去的是大号码,不会因为stack的FILO和queue的FIFO而改变。

java中的queue接口是实现了collection的接口,所以对queue的操作,不推荐用collection的add和remove来增加和删除元素,因为如果队列满了会报错。推荐用offer和poll来操作,因为他们出错时不会报错而是通过返回值来判断。

LinkedBlockingQueue是用链表实现的队列。ArrayBlockingQueue是用数组实现的queue。

不管用哪种数据类型,如果要实现满了自动删除元素的功能,都需要自己去判断是不是满了,比如有的可以通过size判断,有的像offer可以通过增加数据的返回值判断。

数组的equals

数组在使用equals的方法的时候有个坑,因为equals本身是对象,我们想当然的以为它可以用equals方法进行比较。

但是,数组这个坑本身并没有重写equals方法,所以它的equals和==是一样的。。。。。

如果要进行数组比较的话需要使用Arrays这个公共类的Arrays.equals(a,b)方法。

SpringMVC这种拦截器在拦截请求

springMVC这种拦截器在拦截请求的时候,在方法一个级别的path配置的时候,尽量不要使用有重叠区域的path配置,比如一个方法的配置是/v1,另一个方法的配置是/v1/execute,这样的话,当/v1/execute的请求到来的时候,可能匹配任意一个方法,会出现bug。

--------------------------这个想法已经被推翻了,同时在方法里使用/v1和/v1/execute是没有问题的。

真正导致产品错误的原因是因为不同的方法使用了方法的重载。。。。。。。。path竟然无法支持相同名字不同参数的方法。。。。会出现wrong number of arguments的错误。。。。坑啊。。。。

REST

REST是一种思想,它可以是一个单独的客户端,可以支持各种语言。

就算只是在java中,已jar包呈现的情况下,它也可以是单独的http服务器,也可以是配合tomcat一起使用的一个三方jar。

我们常用的是和tomcat配合使用,在tomcat里面配一个filter去拦截所有的请求,然后自己再把请求分发到各个方法上面,在方法上面可以配@GET,配@PATH,配@xxx等等来决定哪个方法接受哪些请求。

REST的返回,用的也是Response,但是这个Response和HttpServletResponse完全是两种类型的response。REST的response的一般使用方法是return Response.status().entity().build();

在使用REST返回时,有一个坑,就是jsr-311的标准和其实现像restlet等如果版本不匹配的时候,使用像Response.ok()等方法时可能会出现像AbstractMethodError 这样的错误。Response.status()以前可能支持enum的入参,可能现在只能是int。反正要注意各种可能的不兼容错误。

MYSQL特性
  • mysql是单进程多线程。oracle是多进程。所以mysql对单机的cpu的使用率是比较低的,有的人会在一台机器上布置好几个mysql。
  • 不建议一个mysql在多个表分别使用不同的引擎。
  • tar包安装mysql的方式更方便,升级的时候直接把文件替换就好。但是性能更差,因为本地编译安装可以连接到最新的库文件。
  • General Log会有20%以上的性能消耗,所以一般都是关的。
  • 高数据量的时候不推荐online DDL,因为可能最后一直追都追不上最新的数据。
读配置文件

JAVA读配置文件有几个坑。

  • 读配置文件,也要注意,如果你用的不是默认的GBK字符集,你要想想你的配置文件有没有在某个地方显式地指定字符集。
  • 读配置文件时,本地的目录机构和打包发布成为war包的目录结构是不一样的,我们一般是需要区别对待的,对于线上的文件,需要使用this.class.getResourceAsInputStream
  • 读配置文件的内容时,其本质就相当于字符串里面的内容一样,所以要特别注意\这个符号,如果你需要用D盘的路径,你应该写成uee.upload.dir.tmp=D:\\
String的一些处理方式

我们在用String时,由于String是immutable的,如果我们需要按位操作字符串,可以使用toCharArray把String转换成字符数组处理。

同时还需要注意的是new String(“xx”)和"xx"的区别。

TOMCAT启动报错
validateJarFile(/home/cmp/cmpportal/tomcat/webapps/cmp-portal.cars/com.huawei.cmp.cat.frontend.server-6.1.73.B000-SNAPSHOT/WEB-INF/lib/javax.el-3.0.1-b09.jar) - jar not loaded. See Servlet Spec 3.0, section 10.7.2. Offending class: javax/el/Expression.class

这个错误的原因是Servlet Spec里面规定了Servlet容器(像TOMCAT)里面的一个特性就是,不能覆盖Servlet容器里面已经有的java SE平台类(放在TOMCAT自己的lib里面的那些jar包一般都是一些标准API, 尤其是像java.* 和javax.* 这种的)。

 As described in the Java EE license agreement, servlet containers that are not part of a Java EE product should not allow the application to override Java SE platform classes, such as those in the java.* and javax.* namespaces, that Java SE does not allow to be modified. The container 102 Java Servlet Specification • November 2009 should not allow applications to override or access the container’s implementation classes. 

然后我的jar包是javax.el,这个包里有javax的标准,也有实现。然后TOMCAT里面有一个el.api的包。然后当加载我们的javax.el包时,javax/el/Expression.class这个类会和el.api包里的同名类冲突,然后整个包都加载不了了。

JAVA为什么导入API的实现jar包,不用配置就能关联他们

因为使用了JAVA Service Provider功能。

需要在META-INF/services下面配置一个配置文件说明provider都有谁,还必须用UTF-8编码,这个配置文件的名字需要是API包中的类名,比如META-INF\services\javax.validation.spi.ValidationProvider;

然后实现类必须有一个无参构造方法,方便被实例化。

这样所有调用API的方法都会去调用它的实现。

JAVA的流和匿名内部类中,不能引用外部final变量
// 这样是不允许的
int a = 1;
int b = 2;
list.foreach(item -> b = item+(a++));

流里面的那个 -> 本质上就是一个匿名内部类。

为什么匿名内部类中只能引用final的变量呢,因为匿名内部类本身就是一个单独的class类,它的母类对象甚至是方法可能会不在,而这个匿名内部类还在,此时,如果它依然去引用一个消失了的原始类型,肯定是不行的。所以匿名内部类在使用外部数据的时候,内部是拷贝了一份在本地,为了跟外部数据一致(表面上你不知道是拷贝的),你就需要是final的(java8甚至可以不加final关键字,只要你保证跟外部数据一致就行)。如果你想要更改这个数据,自己重新new一个局部变量就行。

但是流有一个问题,你不能new一个局部变量,所以此时你需要在外部处理好,或者使用引用变量,比如上面这个题目,就可以改写为

// 方案一,在外部改变好再放进流
int a = 1;
int b = 2;
List list2 = new ArrayList(); 
for (int item:list){
    list2.add(item+a++);
}
list2.foreach(item -> b = item);

// 方案二,使用引用变量
final AtomicInteger a = new AtomicInteger(1);
final AtomicInteger b = new AtomicInteger(2);
list.foreach(item -> b.(a.getAndAdd(1)));
JAVA替换jar包

替换jar包不是把后缀名改了就行的,有的情况,生成classpath时不会去读非jar文件。单是有的时候会,kafka在读自己lib下的jar包时,其他后缀名的也会去读。。。这是个坑。

此外,换了jar包别忘了改权限。

JACKSON

jackson从字符串反序列化为对象,然后再从对象序列化为字符串后,很难保持一样,就算是在数据一模一样的情况下,对象里面的属性的位置在重新转化为字符串时先后顺序还是可能会发生变化。

循环里的各种BUG

for循坏的判断条件不要用大小会变的变量,更不要在循坏体里面改变这个变量。

// 这个代码里面的children.size()的大小随着childeren.pollFirst()一直在变,所以总的循坏次数没有达到
for (int i = 1; i <= children.size(); i++) {
	children.pollFirst();
}
使用特殊字符的各种坑

使用“|”这个字符时,有很多坑,要时刻注意方法接受的参数是当作正则还是普通字符串。因为在不同的地方它可能有不通的含义:

  • 当使用split方法分割“|”周围的字符串时,split方法接受的是正则,而“|”在正则里有特殊含义,所以你要用转义符,最后你需要写成xxxx.split("\\|")
  • 当使用contains方法时,contains方法接受的是普通的字符串,所以此时“|”只会被当做普通的字符串处理,如果使用"\\|"的话,contains方法则会去匹配"\|"两个字符(因为java把\当做特殊字符,所以\\会解析成\),所以最后你需要写成xxx.contains("|")
TreeSet的坑

TreeSet有个坑,它不仅不能add相同的对象,连compare方法比较等价的对象,也不能添加!!!!

RPC的核心

RPC的核心不是在于他用了什么样的通信方式,他可以是自建TCP/IP,也可以是基于HTTP。关键的问题在于它的上层架构要保证我在方法的调用时,能够直接调用远程的方法,使用起来就跟调用本地的方法一样,而不是跟REST一样需要去一个一个的发请求解析请求。

在引用外部接口少的时候,使用REST还是可以的,当使用到的外部接口多的时候,就需要使用RPC了。

归一化

java.text.Normalizer.normalize()可以去除全角半角的区别

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值