Java常用类库简单介绍



Java常用类库简单介绍,相关实例源码,常用类的方法使用。Java类库的结构,指的是Java API(Application Programming Interface,应用程序接口),是java系统给的实现的标准类的集合。合理和充分利用类库提供的类和接口,可以完成字符串处理、绘图、网络应用、数学计算等多方面的工作,还可提高编程效率,使程序简练、易懂。

Java类库中的类和接口大多封装在特定的包里,每个包具有自己的功能。表7.1列出了Java中一些常用的包及其简要的功能。其中,包名后面带“. *”的表示其中包括一些相关的包。有关类的介绍和使用方法,Java中提供了极其完善的技术文档。我们只需了解技术文档的格式就能方便地查阅文档。

表7.1Java提供的部分常用包

包名 主要功能
java.applet 提供了创建applet需要的所有类
java.awt.* 提供了创建用户界面以及绘制和管理图形、图像的类
java.beans.* 提供了开发Java Beans需要的所有类
java.io 提供了通过数据流、对象序列以及文件系统实现的系统输入、输出
java.lang.* Java编程语言的基本类库
java.math.* 提供了简明的整数算术以及十进制算术的基本函数
java.rmi 提供了与远程方法调用相关的所有类
java.net 提供了用于实现网络通讯应用的所有类
java.security.* 提供了设计网络安全方案需要的一些类
java.sql 提供了访问和处理来自于Java标准数据源数据的类
java.test 包括以一种独立于自然语言的方式处理文本、日期、数字和消息的类和接口
java.util.* 包括集合类、时间处理模式、日期时间工具等各类常用工具包
javax.accessibility 定义了用户界面组件之间相互访问的一种机制
javax.naming.* 为命名服务提供了一系列类和接口
javax.swing.* 提供了一系列轻量级的用户界面组件,是目前Java用户界面常用的包

 

注:在使用Java时,除了java.lang外,其他的包都需要import语句引入之后才能使用。

7.2 java.lang包中的常用类

java.lang是Java语言最广泛使用的包。它所包括的类是其他包的基础,由系统自动引入,程序中不必用import语句就可以使用其中的任何一个类。java.lang中所包含的类和接口对所有实际的Java程序都是必要的。下面我们将分别介绍几个常用的类。

7.2.1 String类和StringBuffer类

许多语言中,字符串是语言固有的基本数据类型。但在Java语言中字符串通过String类和StringBuffer类来处理。

1.String类

Java语言中的字符串属于String类。虽然有其它方法表示字符串(如字符数组),但Java使用String类作为字符串的标准格式。Java编译器把字符串转换成String对象。String对象一旦被创建了,就不能被改变。如果需要进行大量的字符串操作,应该使用StringBuffer类或者字符数组,最终结果可以被转换成String格式。

(1)创建字符串

创建字符串的方法有多种方式,通常我们用String类的构造器来建立字符串。表6.2列出了String类的构造器及其简要说明。

表7.2 String类构造器概要

构造器 说明
String( ) 初始化一个新的String对象,使其包含一个空字符串
String(char[ ] value ) 分配一个新的String对象,使它代表字符数组参数包含的字符序列
String(char[ ] valu, int offset,int count) 分配一个新的String对象,使它包含来自字符数组参数中子数组的字符
String(Stringvalue ) 初始化一个新的String对象,使其包含和参数字符串相同的字符序列
String(StringBufferbuffer ) 初始化一个新的String对象,它包含字符串缓冲区参数中的字符序列

 

【例7.1】使用多种方法创建一个字符串并输出字符串内容。

public class StrOutput {

public static void main(Sring[] args) {

//将字符串常量作为String对象对待,实际上是将一个String对象赋值给另一个

String s1 = “Hello,java!”;

//声明一个字符串,然后为其赋值

String s2;

s2 = “Hello,java!”;

//使用String类的构造器中的一个。创建一个空字符串,然后赋值给它

String s3 = new String( );

S3 = “Hello,java!”;

//将字符串直接传递给String类构造器来创建新的字符串

String s4 = new String(“Hello,java!”);

//使用String类的构造器中的一个。

//通过创建字符数组传递给String类构造器来创建新的字符串

Char c1[ ] = { ‘ H’, ‘ i’, ‘ , ‘ , ‘j’, ‘a’, ‘v’, ‘a’};

String s5 = newString(c1 );

//将字符数组子集传递给String类构造器来创建新的字符串

String s6 = new String(c1,0,2 );

System.out.println(s1);

System.out.println(s2);

System.out.println(s3);

System.out.println(s4);

System.out.println(s5);

System.out.println(s6);

}

}

运行结果:

C:\>java StrOutput

Hello,java!

Hello,java!

Hello,java!

Hello,java!

Hi,java

Hi

(2)字符串的操作

Java语言提供了多种处理字符串的方法。表6.3列出了String类常用的方法。

 

表7.3 String类的常用方法

方法 说明
char charAt(int index) 获取给定的Index处的字符
int compareTo(String anotherString) 按照字典的方式比较两个字符串
int compareToIgnoreCase(String str) 按照字典的方式比较两个字符串,忽略大小写
String concat(String str ) 将给定的字符串连接到这个字符串的末尾
static String copyValueOf(char[ ] data) 创建一个和给定字符数组相同的String对象
static String copyValueOf(char[ ]data ,

int offset,int count)

使用偏移量,创建一个和给定字符数组相同的String对象
boolean equals(Object anObject) 将这个String对象和另一个对象String进行比较
boolean equalsIgnoreCase(Sting anotherString) 将这个String对象和另一个对象String进行比较,忽略大小写
void getChars(getChars(int strbegin,

int strend,char[ ] data,int offset)

将这个字符串的字符拷贝到目的数组
int indexOf(int char) 产生这个字符串中出现给定字符的第一个位置的索引
int indexOf(int ch,int fromIndex) 从给定的索引处开始,产生这个字符串中出现给定字符的第一个位置的索引
int indexOf(String str) 产生这个字符串中出现给定子字符的第一个位置的索引
int indexOf(String str,int fromIndex) 从给定的索引处开始,产生这个字符串中出现给定子字符的第一个位置的索引
int length( ) 产生这个字符串的长度
boolean regionMatches(boolean ignoreCase,int toffset,String other,int ooffset,int len) 检查两个字符串区域是否相等,允许忽略大小写
String replace(char oldChar,char newChar) 通过将这个字符串中的odChar字符转换为newChar字符来创建一个新字符串
boolean starsWith(String prefix) 检查这个字符串是否以给定的前缀开头
boolean starsWith(String prefix,int toffset) 从给定的索引处开头,检查这个字符串是否以给定的前缀开头
String substring(int strbegin) 产生一个新字符串,它是这个字符串的子字符串
String substring(int strbegin,int strend) 产生一个新字符串,它是这个字符串的子字符串,允许指定结尾处的索引
char[ ] toCharArray( ) 将这个字符串转换为新的字符数组
String toLowerCase( ) 将这个String对象中的所有字符变为小写
String toString( ) 返回这个对象(它已经是一个字符串)
String toUpperCase( ) 将这个String对象中的所有字符变为大写
String trim( ) 去掉字符串开头和结尾的空格
static String valueOf(int i) 将int参数转化为字符串返回。该方法有很多重载方法,用来将基本数据类型转化为字符串。如:static String valueOf(float f),static String valueOf(long l)等

 

下面结合常用的方法,介绍几种典型的字符串操作。

①字符串的比较

Java语言提供了四种字符串的比较方法,这些方法有些类似于操作符。例如,可以使用equals、equalsIgnoreCase、regionMatches和compareTo方法来实现对字符串的比较。调用形式如下:

s1.equals(s2) —– 如果s1等于s2,返回true,否则为false。

s1. equalsIgnoreCase (s2) —– 如果s1等于s2,返回true,否则为false,忽略大小写。

s1. regionMatches(boolean ignoreCase,int toffset,s2,int ooffset,int len ) —– 如果s1和 s2的子串相等,返回true,否则为false。其中,ignoreCase为忽略大小写设置,true为忽略大小写,false为不忽略大小写,toffset确定s1的起始偏移量,ooffset确定s2的起始偏移量,len确定子串的长度。

s1. compareTo (s2) —–如果s1<s2,则返回小于0的值;如果s1=s2,则返回0;如果s1>s2,则返回大于0的值

【例7.2】比较字符串。

public class StrCompare {

public static void main(Sring[] args) {

String s1=”aaaa”;

String s2=”aaaa”;

String s3=”AAAA”;

String s4=”bcd”;

if (s1.equals(s2)) {

System.out.println(“s1==s2″);

}

else {

System.out.println(“s1!=s2″);

}

if (s1.equalsIgnoreCase(s3)) {

System.out.println(” s1= =s3 when ignoring case”);

}

else {

System.out.println(” s1!=s3 when ignoring case”);

}

if (s1.regionMatches(true,0,s3,1,3)) {

System.out.println(” s1= =s3 when ignoring case”);

}

else {

System.out.println(” s1!=s3 when ignoring case”);

}

if (s1.regionMatches(false,0,s3,1,3)) {

System.out.println(” s1= =s3 when not ignoring case”);

}

else {

System.out.println(“s1!=s3 when not ignoring case”);

}

if (s1.compareTo(s4)<0) {

System.out.println(” s1<s4″);

}

else if (s1.compareTo(s4)= =0){

System.out.println(“s1= =s4″);

}

else{

System.out.println(“s1>s4″);

}

}

}

运行结果:

C:\>java StrCompare

s1= =s2

s1= =s3 when ignoring case

s1= =s3 when ignoring case

s1!= =s3 when not ignoring case

s1<s4

②求字符串长度

使用String类的length方法,调用形式如下:

s1.length( ) —– 返回s1的长度,其类型为int。

【例7.3】求指定字符串的长度。

public class StrLength

{public static void main(Sring[] args)

{String s1=”Hello,Java!”;

int i=s1.length();

System.out.println(“字符串s1长度为”+i);

}

}

运行结果:

C:\>java StrLength

字符串s1长度为11

③连接字符串

可以使用两种方法将字符串连接起来:+操作符、用String类的concat方法。Concat方法的调用形式如下:

s1.concat(s2) —– 将两个字符串连接起来。

s1.concat(“字符串常量”) —– 将字符串和字符串常量连接起来。

【例7.4】使用+和concat方法创建同一个字符串。

public class StrConcat

{public static void main(Sring[] args)

{String s1=”Hello”;

String s2=s1+”,”;

String s3=s2.concat(” Java”);!

String s4=new String(” ! “);

String s5=s4.concat(s4);

System.out.println(” 连接而成的字符串是”+s5);

}

}

运行结果:

C:\>java StrConcat

连接而成的字符串是Hello,Java!

④拷贝字符串

可以有四种方法将一个字符串复制到另一个字符数组或String对象中:copyValueOf、getChars、toCharArray、substring。调用形式:

s1.copyValueOf(data) —– 将数组data中的内容全部拷贝到字符串中。

s1.copyValueOf(data,int offset,int count) —– 将数组data中以offset起始,长度为count的内容拷贝到字符串中。

s1.getChars(int strbegin,int strend, data,int offset) —– 将s1的全部或部分内容拷贝到数组data中。其中,strbegin为字符的起始,strend 为字符的终止,offset为字符数组的起始。

data=s1.toCharArray( ) —– 将s1中的全部内容拷贝到一个字符数组data中。

s2=s1.substring(int strbegin) —– 将s1中以stregin起始的内容拷贝到s2中。

s2=s1.substring(int strbegin,int strend) —– 将s1中以stregin起始,以strend结束之间的内容拷贝到s2中。

【例7.5】拷贝字符串。

public class StrCopy

{public static void main(Sring[] args)

{

String s1=new String( );

char data[ ]={ ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’};

s1=s1.copyValueOf(data);

System.out.println(” s1=”+s1);

s1=s1.copyValueOf(data,2,3);

System.out.println(” s1=”+s1);

s1.getChars(1,2, data,0)

System.out.println(” data=”+data);

data=s1. toCharArray( );

System.out.println(” data=”+data);

String s2=new String( );

String s3=new String( );

s2=s1.substring(0);

System.out.println(” s2=”+s2);

s3= s1.substring(1,2);

System.out.println(” s3=”+s3);

}

}

运行结果:

C:\>java StrCopy

s1=abcdef

s2=cde

data=decdef

data=cde

s2=cde

s3=de

⑤在字符串中查找字符和子串

在字符串中查找字符和子串,确定它们的位置,有几种常用的方法:charAt、indexOf、lastIndexOf。调用形式如下:

s1.chatAt(int index)——–返回s1中index所对应的字符。其中,index是下标号。

s1. indexOf (int char)——–返回s1中字符char在字符串中第一次出现的位置。

s1. lastIndexOf (int char)——–返回s1中字符char在字符串中最后一次出现的位置。

s1. indexOf (s2)——–返回s2在s1中第一次出现的位置。

s1. lastIndexOf (s2)——–返回s2在s1中最后一次出现的位置。

【例7.6】查找字符和子串。

public class StrSearch

{public static void main(Sring[] args)

{

String s1=”Javav”;

char c=s1.charAt(2);

System.out.println(“c=”,+c);

int i=s1.indexOf(‘a’);

System.out.println(“fistchar=”,+i);

int j=s1.lastIndexOf(‘a’);

System.out.println(“lastchar=”,+j);

i= s1.indexOf(“av”);

System.out.println(“fiststring=”,+i);

j=s1.lastIndexOf(“av”);

System.out.println(“laststring=”,+j);

}

}

运行结果:

C:\>java StrSearch

c=v

firstchar=1


lastchar=3

firststring=1

laststring=3

⑥修改字符串

修改字符串的常用方法有:replace、toLowerCase、toUpperCase、trim。调用形式如下:

s1.replace(oldchar,newchar)——–用新字符newchar替代旧字符oldchar,若指定字符不存在,则不替代。

s1.toLowerCase( )——–将s1中的所有大写字母转换为小写字母。

s1.toUpperCase( )——– 将s1中的所有小写字母转换为大写字母。

s1.trim( )——–删除s1中的首、尾空格。

【例7.7】修改字符串。

public class StrModify

{public static void main(Sring[] args)

{

String s1=”Java”;

s1=s1.replae(‘a’, ‘b’);

System.out.println(“s1=”,+s1);

String s2=s1.toLowerCase( );

String s3=s1. toUpperCase ( );

System.out.println(“s2=”,+s2);

System.out.println(“s3=”,+s3);

s2= s1.trim( );

System.out.println(“s2=”,+s2);

}

}

运行结果:

C:\>java StrModify

s1= Jbvb

s2= jbvb

s3= JBVB

s2= jbvb

2.StringBuffer类

缓冲字符串类StringBuffer与String类相似,它具有String类的很多功能,甚至更丰富。它们主要的区别是StringBuffer对象可以方便地在缓冲区内被修改,如增加、替换字符或子串。与Vector对象一样,StringBuffer对象可以根据需要自动增长存储空间,故特别适合于处理可变字符串。当完成了缓冲字符串数据操作后,可以通过调用其方法StringBuffer.toString( )或String构造器把它们有效地转换回标准字符串格式。

(1)创建StringBuffer对象

可以使用StringBuffer类的构造器来创建StringBuffer对象。表6.4 是StringBuffer的构造器及其简要说明。

表6.4StringBuffer类构造器概要

构造器 说明
StringBuffer( ) 构造一个空的缓冲字符串,其中没有字符,初始长度为16个字符的空间
StringBuffer(int length) 构造一个长度为length的空缓冲字符串
StringBuffer(String str) 构造一个缓冲字符串,其内容初始化为给定的字符串str,再加上16个字符的空间

 

【例7.8】用多种方法创建StringBuffer对象。

public class StrBufferSet

{public static void main(Sring[] args)

{

StringBuffers1=new StringBuffer( );

s1.append(“Hello,Java!”);

System.out.println(“s1=” +s1);

StringBuffers2=new StringBuffer(10 );

S2.insert(0, “Hello,Java!”);

System.out.println(“s2=”+s2);

StringBuffers3=new StringBuffer(“Hello,Java!”);

System.out.println(“s3=”+s3);

}

}

运行结果:

C:\>java StrBufferSet

s1=Hello,Java!

s2=Hello,Java!

s3=Hello,Java!

(2)StringBuffer类的常用方法

StringBuffer类是可变字符串,因此它的操作主要集中在对字符串的改变上。

①为StringBuffer的对象插入和追加字符串

可以在StringBuffer对象的字符串之中插入字符串,或在其之后追加字符串,经过扩充之后形成一个新的字符串,方法有:append和insert,调用形式如下:

s1.append(s2)——–将字符串s2加到s1之后。

s1.insert(int offset,s2)——–在s1从起始处offset开始插入字符串s2。

append和insert都有多个重载方法,这里不一一赘述。关于append和insert方法的使用见例6.8 。

②获取和设置StringBuffer对象的长度和容量

获取和设置StringBuffer对象的长度和容量的方法有:length、capacity、setlength,调用形式如下:

s1.length( )——–返回s1中字符个数。

s1. capacity ( )——–返回s1的容量,即内存空间数量。通常会大于length( )

s1. setlength (int newLength )——–改变s1中字符的个数,如果newLength大于原个数,则新添的字符都为空(”");相反,字符串中的最后几个字符将被删除。

【例7.9】显示确定字符串的长度和容量,并改变字符串的长度。

public class StrLen

{public static void main(Sring[] args)

{

StringBuffers1=new StringBuffer(“Hello,Java!”);

System.out.println(“The length is”+s1.length( ));

System.out.println(“The allocated length is”+s1.capacity( ));

s1.setlength(100);

System.out.println(“The new length is”+s1.length( ));

}

}

运行结果:

C:\>java StrLen

The length is11

The allocated length is22

The new length is100

③读取和改变StringBuffer对象中的字符

读取StringBuffer对象中的字符的方法有:charAt和getChar,这与String对象方法一样。在StringBuffer对象中,设置字符及子串的方法有:setCharAt、replace;删除字符及子串的方法有:delete、deleteCharAt。调用形式如下:

s1.setCharAt(int index,char ch)——–用ch替代s1中index位置上的字符。

s1.replace(int start,int end,s2)——–s1中从start(含)开始到end(不含)结束之间的字符串以s2代替。

s1.delete(int start,int end)——–删除s1中从start(含)开始到end(不含)结束之间的字符串。

s1.deleteCharAt(int index)——删除s1中index位置上的字符。

【例7.10】改变字符串的内容。

public class StrChange

{public static void main(Sring[] args)

{

StringBuffers1=new StringBuffer(“Hallo,Java!”);

s1.setCharAt(1, ‘e’);

System.out.println(s1);

s1.replace(1,5, “i”);

System.out.println(s1);

s1.delete(0,3);

System.out.println(s1);

s1.deleteCharAt(4);

System.out.println(s1);

}

}

运行结果:

C:\>java StrChange

Hello,Java!

Hi,Java!

Java!

Java

7.2.2 System类

System类是一个特殊类,它是一个公共最终类,不能被继承,也不能被实例化,即不能创建System类的对象。

System类功能强大,与Runtime一起可以访问许多有用的系统功能。System类保存静态方法和变量的集合。标准的输入、输出和Java运行时的错误输出存储在变量in,out和err中。由System类定义的方法丰富并且实用。System类中所有的变量和方法都是静态的,使用时以System作为前缀,即形如“System.变量名”和“System.方法名”。

1.标准的输入输出

System类包含三个使用频繁的公共数据流,分别是:标准输入(in)、标准输出(out)、标准错误输出(err)。

① public static final InputStream in——–标准输入。

这个属性是InputStream类的一个对象,它是未经包装的原始Input Stream,读取System.in之前应该先加以包装。可以通过read()方法读取字节数据。

② public static final PrintStream out——–标准输出。

③ public static final PrintStream err———标准输出。

out和err都已经被包装成PrintStream对象,所以可以直接使用System.out和System.err。可以通过方法print()、println()或write()方法很方便地完成各种数据类型的输出。out与err使用上的不同是: System.out用于输出普通信息,out的输出一般需要缓存;System.err一般情况下用来打印错误信息,不需要缓存,快速显示紧急信息。

关于InputStream类和PrintStream类将在java.io包中介绍。

2.System类的常用方法

System类有一些有用的方法,这些方法用于处理运行环境。下面简单介绍几个方法及其功能。

(1)获取当前时间

使用currentTineMillis( )可以记录程序执行的时间,这是一个特别有意义的用法。currentTineMillis( )方法返回自从1970年1月1日午夜起到现在的时间,时间单位是毫秒。如果要记录程序中一段有问题程序的运行时间,可以在这段程序开始之前调用currentTineMillis( )方法存储当前时间,在这段程序结束处再次调用currentTineMillis( )方法。执行该段程序所花费的时间为其结束时刻的时间值减去其开始时刻的时间值。下面的程序段可以用来估计一下执行某个循环所占用的时间:

long startTime=System.currenTimerMillis( );//记录循环开始时间

int sum=0;

for(int i=0;i<100000;i++){

sum+=i;

}

long endTime=System.currentTimeMillis( );// 记录循环结束时间

System.out.Println(“time: “+(endTime-startTime)+ “milliseconds. “);

注意:虽然使用cuttentTimeMillis()方法可以计算出当前的日期和时间,但是获取当前日期和时间最好使用java.util中的Date类。

(2)快速复制数组

使用arraycopy()方法可以将一个任意类型的数组快速地从一个地方复制到另一个地方。这比使用循环编写的程序要快得多。调用形式为:

System.arraycopy(a1,int sourceStart,a2,int targetStart,int size)——将数组a1从下标sourceStart开始,长度为size的元素依次复制到数组a2的以targetStart为起始的单元中。

【例7.11】用arraycopy()方法复制两个数组。

class CopyArray

{static byte array1[ ]={97,98,99,100,101};

static byte array2[ ]={102,102,102,102,102};

public static void main(Sring[] args)

{

System.out.println(” array1=”+new String(array1));

System.out.println(” array2=”+new String(array2));

System.arraycopy(array1,0,array2,0,array1.length);

System.out.println(” array1=”+new String(array1));

System.out.println(” array2=”+new String(array2));

System.arraycopy(array1,0,array1,1,array1.length-1);

System.arraycopy(array2,1,array2,0,array2.length-1);

System.out.println(” array1=”+new String(array1));

System.out.println(” array2=”+new String(array2));

}

}

运行结果:

C:\>java CopyArray

array1=abcde

array2=fffff

array1=abcde

array2= abcde

array1=aabcd

array2=bcdee

(3)退出虚拟机

在用户的程序还未执行完之前,强制关闭Java虚拟机的方法是exit():

Public static void exit(int exitCode)

关闭虚拟机的同时把状态信息exitCode传递给操作系统,exitCoded非零时,表示非正常退出。

(4)强制垃圾收集

垃圾收集器一般情况下运行于后台,并自动地收集已不使用了的内存。使用gc()方法可强制垃圾收集器启动:

public static void gc()

3.环境属性

可以通过调用System.getProperty()方法来获得不同环境属性的值。例如下面的程序显示当前用户目录的路径:

class PlayUserDir{

public static void main(String[ ] args){

System.out.println(System.getProperty(” user.dir”));

}

}

可以通过setProperty( )方法设置系统属性的值:

public static String setProperty(String key,String value);

其中,key为键名,value为键值。

6.2.3 Math类

Math类提供了用于几何学、三角学以及几种一般用途方法的浮点函数,来执行很多数学运算。

1.Math类定义的两个双精度常量

doubleE——–常量e(2.7182818284590452354)

doublePI——–常量pi(3.14159265358979323846)

2.Math类定义的常用方法

Math类定义的方法是静态的,可以通过类名直接调用。下面简要介绍几类常用的方法。

①三角函数

public static double sin(double a)——三角函数正弦。

public static double cos(double a)——三角函数余弦。

public static double tan(double a)——三角函数正切。

public static double asin(double a)——三角函数反正弦。

public static double acos(double a)——三角函数反余弦。

public static double atan(double a)——三角函数反正切。

② 指数函数

public static double exp(double a)——返回ea的值。

public static double log(double a)—— 返回lna的值。

public static double pow (double y,double x)—— 返回以y为底数,以x为指数的幂值。

public static double sqrt(double a)—— 返回a的平方根。

③ 舍入函数

public static intceil(double a)——- 返回大于或等于a的最小整数。

public static intfloor(double a)——- 返回小于或等于a的最大整数。

以下三个方法都有其它数据类型的重载方法:

public static intabs(int a)——- 返回a的绝对值。

public static intmax(int a,int b)——- 返回a和b的最大值。

public static intmin(int a,int b)——- 返回a和b的最小值。

④其它数学方法

public static doublerandom( )—— 返回一个伪随机数,其值介于0和1之间。

public static doubletoRadians(doubleangle )—— 将角度转换为弧度。

public static doubletoDegrees (doubleangle)—— 将弧度转换为角度。

 

http://jack-chen10.blog.163.com/blog/static/677512820099231741929/