JAVA基础知识之NIO——Buffer.Channel,Charset,Channel文件锁

NIO机制

NIO即NEW IO的意思,是JDK1.4提供的针对旧IO体系进行改进之后的IO,新增了许多新类,放在java.nio包下,并对java.io下许多类进行了修改,以便使用与nio.

在java.io中,无论上层通过什么方式访问数据,在底层都是通过字节的方式来读取,

即使是BufferedReader也一样,虽然是先集中将OS中的数据读取到buffer中,再由上层应用从buffer中读取,或者是上层先将数据写入buffe中,再批量写入OS中去,

buffer机制只是减少了IO的切换次数,但是访问数据的方式并没有变化,在OS底层依然是一个字节一个字节地操作。

而在NIO中则出现了全新的访问数据的方式,NIO引入了Buffer和Channel的概念,可以通过Channel将OS中一块或者全部数据直接映射成Buffer,应用程序则从Buffer中读取数据

NIO这种类似虚拟内存映射的机制不仅仅是减少了与OS的IO切换次数,而且真正减少了直接读取OS的次数,因此NIO在性能上比传统IO要高得多。

也就是说传统的IO读取的是数据流,而NIO读取的是数据块。

Buffer

Buffer的作用就是用来装载数据,然后输出数据,其内部类似于一个数组,但是又不是完全相同。

针对不同的功能/数据类型,有不同的Buffer,ByteBuffer, CharBuffer, ShortBuffer, IntBuffer等等。ByteBuffer和CharBuffer是最常用的Buffer.

Buffer是一个抽象类,要得到一个Buffer对象,需要用具体的Buffer子类的allocate(int i)方法。

Buffer的内部结构类似一个数组,因此也提供了一些类似数组的属性,

capacity表示Buffer的容量;

limit表示界限,用来表示读取数据时的最大的下标。

position则类似一个游标,随着读和写而变化。

mark是一个标志位,相当于一个绝对坐标,position可以直接定位到mark位置。

Buffer的工作过程:

  • 写Buffer

初始状态的Buffer的position位置为0,limit等于capacity, 当put进数据到Buffer的时候,position会随之后移,

当所有数据装载完毕之后,调用Buffer的flip()方法,会将limit设置为position位置,position则置0,

这就做好了读的准备,此时position就充当数组下标的角色,而limit就是数组的上限,最多只能读到这里。

  • 读Buffer

当Buffer输出数据结束后,此时的position应该已经读到了limit位置,调用Buffer的clear()方法,会将position再次置0,同时会将limit置为capacity,

这又恢复了Buffer写数据前的状态,相当于重置了Buffer,为下一次写进Buffer做好了准备。但注意的是,clear()这个过程并没有清空Buffer中的数据。

 

 下面演示Buffer的使用,

 1 package nio;
 2 
 3 import java.nio.CharBuffer;
 4 
 5 public class BufferTest {
 6     public static void main(String[] args) {
 7         //创建Buffer
 8         CharBuffer buff = CharBuffer.allocate(8);
 9         System.out.println("capacity: "+buff.capacity());
10         System.out.println("limit: "+buff.limit());
11         System.out.println("position: "+buff.position());
12         //放入元素
13         buff.put('a');
14         buff.put('b');
15         buff.put('c');
16         System.out.println("放入3个元素后,position: "+buff.position());
17         buff.flip();
18         System.out.println("执行flip()后,limit: "+buff.limit()+", position: "+buff.position());
19         //取出第一个元素
20         System.out.println("第一个元素(position=0): "+buff.get());
21         System.out.println("取出第一个元素后,position:" +buff.position());
22         buff.clear();
23         System.out.println("执行clear()后,limit: "+buff.limit()+" ,position: "+buff.position());
24         System.out.println("执行clear()后,buffer并没有被清除。第三个元素为: "+buff.get(2));
25         System.out.println("执行绝对读取后, position: "+buff.position());
26     }
27 }

 注意从Buffer中读数据的时候,有两种方式,一种是读相对读取,即从当前position位置读取,读完之后,position会随之后移。另一种是绝对位置读取,直接读取具体位置的数据,position不会改变。

下面是执行结果,

 1 capacity: 8
 2 limit: 8
 3 position: 0
 4 放入3个元素后,position: 3
 5 执行flip()后,limit: 3, position: 0
 6 第一个元素(position=0): a
 7 取出第一个元素后,position:1
 8 执行clear()后,limit: 8 ,position: 0
 9 执行clear()后,buffer并没有被清除。第三个元素为: c
10 执行绝对读取后, position: 0

从上面程序看出,比起普通的数组,Buffer更智能一点,读取数据的时候不需要关心Buffer中有多少数据,读取到limit位置即可,不会出现越界问题。

(PS:上面是书上说的,我本人并没有看出单纯使用Buffer能比单纯使用数组强大到哪去,倒是麻烦了不少,Buffer只有在结合Channel使用的时候才有用吧) 

Channel

NIO中真正有革命性改动的应该是引入了Channel机制,Channel可以将文件的部分或全部映射成Buffer进行读取,直接减少了读取文件的次数。

不过Channel的映射文件机制需要借助Buffer,Channel需要将文件映射成Buffer,应用程序再从Buffer中读取数据,就相当于从文件中读数据,或者应用程序将数据写入Buffer,就相当于写入文件,

这不过这个过程相对底层OS来说,是一块一块进行的,而不是一个字节一个字节进行的。

Channle也不是直接创建,而是由传统io中的流对象的getChannel()返回的。

不同功能的流对象返回不同的changnel(),例如FileInputStream(),FileOutputstream()返回的是FileChannel, 而PipeInputStream返回的是PipeChannel.

下面演示Channel的用法,

 1 package nio;
 2 
 3 import java.io.File;
 4 import java.io.FileInputStream;
 5 import java.io.FileNotFoundException;
 6 import java.io.FileOutputStream;
 7 import java.io.IOException;
 8 import java.io.RandomAccessFile;
 9 import java.nio.ByteBuffer;
10 import java.nio.CharBuffer;
11 import java.nio.MappedByteBuffer;
12 import java.nio.channels.FileChannel;
13 import java.nio.charset.Charset;
14 import java.nio.charset.CharsetDecoder;
15 
16 public class FileChannelTest {
17     public static void fileChannel() {
18         File f = new File("tmp.txt");
19         try ( 
20                 //FileInputStream获取的channel只能读
21                 FileChannel inChannel = new FileInputStream(f).getChannel();
22                 //FileOutputStream获取的channel只能写
23                 FileChannel outChannel = new FileOutputStream("a.txt").getChannel()
24                 ) {
25             //将整个文件映射成buffer
26             MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
27             Charset charset = Charset.forName("GBK");
28             //输出buffer里的全部数据,就直接写入了a.txt
29             outChannel.write(buffer);
30             //复位buffer的limit和position
31             buffer.clear();
32             //解码器
33             CharsetDecoder decoder = charset.newDecoder();
34             //用解码器将ByteBuffer解码成CharBuffer
35             CharBuffer charBuffer = decoder.decode(buffer);
36             //charBuffer的toString()方法可以获取对应的字符串
37             System.out.println(charBuffer);
38         } catch (IOException e) {
39             e.printStackTrace();
40         }
41     }
42     
43     public static void randomFileChannel() throws FileNotFoundException, IOException {
44         File f = new File("a.txt");
45         try (
46                 RandomAccessFile raf = new RandomAccessFile(f, "rw");
47                 //random Channel的读写模式取决于RandomAccessFile的打开模式 
48                 FileChannel randomChannel = raf.getChannel()
49                 ) {
50             ByteBuffer buffer = randomChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
51             //把channel的指针移动到最后,程序就可以让ByteBuffer的数据追加到文件后面
52             randomChannel.position(f.length());
53             //写入buffer,就直接写入了文件
54             randomChannel.write(buffer);
55             buffer.clear();
56             Charset charset = Charset.forName("GBK");
57             //解码器
58             CharsetDecoder decoder = charset.newDecoder();
59             //用解码器将ByteBuffer解码成CharBuffer
60             CharBuffer charBuffer = decoder.decode(buffer);
61             //charBuffer的toString()方法可以获取对应的字符串
62             System.out.println(charBuffer);
63         }
64     }
65     public static void main(String[] args) throws FileNotFoundException, IOException {
66         //fileChannel();
67         randomFileChannel();
68     }
69 }

在RandomAccessFile中也包含了一个getChannel()方法返回FileChannel,但是它的读写属性则是取决于RandomAccessFile的打开方式,如上面的randomFileChannel()所示。

 

字符集和Charset简介

字符集

所谓字符集,就是对每个字符进行编号,所有编号的集合就是一种字符集,例如对汉字‘刚’的十进制编码是65,但是计算机存储的是二进制,65的二进制是01000001,这样就实现了字符到二进制的映射关系,这个过程也叫编码。

目前存在很多种字符集,java默认使用Unicode字符集,使用两个字节来表示一个字符。但很多操作系统使用的其他字符集,如果直接与java读写数据,就会乱码。

因此java.nio中,提供了一个Charset类,实现字节与字符之间的互相转换。

通过Charset我们可以查看平台支持什么字符集,

1     public static void charsetTest() {
2         SortedMap<String,Charset> map = Charset.availableCharsets();
3         for (String key : map.keySet()) {
4             System.out.println(key+"                        : "+map.get(key));
5         }    
6     }

我的电脑上会输出以下字符集,

《JAVA基础知识之NIO——Buffer.Channel,Charset,Channel文件锁》
《JAVA基础知识之NIO——Buffer.Channel,Charset,Channel文件锁》

  1 Big5                        : Big5
  2 Big5-HKSCS                        : Big5-HKSCS
  3 CESU-8                        : CESU-8
  4 EUC-JP                        : EUC-JP
  5 EUC-KR                        : EUC-KR
  6 GB18030                        : GB18030
  7 GB2312                        : GB2312
  8 GBK                        : GBK
  9 hp-roman8                        : hp-roman8
 10 IBM-Thai                        : IBM-Thai
 11 IBM00858                        : IBM00858
 12 IBM00924                        : IBM00924
 13 IBM01140                        : IBM01140
 14 IBM01141                        : IBM01141
 15 IBM01142                        : IBM01142
 16 IBM01143                        : IBM01143
 17 IBM01144                        : IBM01144
 18 IBM01145                        : IBM01145
 19 IBM01146                        : IBM01146
 20 IBM01147                        : IBM01147
 21 IBM01148                        : IBM01148
 22 IBM01149                        : IBM01149
 23 IBM037                        : IBM037
 24 IBM1026                        : IBM1026
 25 IBM1047                        : IBM1047
 26 IBM1047_LF                        : IBM1047_LF
 27 IBM1141_LF                        : IBM1141_LF
 28 IBM1153                        : IBM1153
 29 IBM273                        : IBM273
 30 IBM277                        : IBM277
 31 IBM278                        : IBM278
 32 IBM280                        : IBM280
 33 IBM284                        : IBM284
 34 IBM285                        : IBM285
 35 IBM290                        : IBM290
 36 IBM297                        : IBM297
 37 IBM420                        : IBM420
 38 IBM424                        : IBM424
 39 IBM437                        : IBM437
 40 IBM500                        : IBM500
 41 IBM775                        : IBM775
 42 IBM850                        : IBM850
 43 IBM852                        : IBM852
 44 IBM855                        : IBM855
 45 IBM857                        : IBM857
 46 IBM860                        : IBM860
 47 IBM861                        : IBM861
 48 IBM862                        : IBM862
 49 IBM863                        : IBM863
 50 IBM864                        : IBM864
 51 IBM865                        : IBM865
 52 IBM866                        : IBM866
 53 IBM868                        : IBM868
 54 IBM869                        : IBM869
 55 IBM870                        : IBM870
 56 IBM871                        : IBM871
 57 IBM918                        : IBM918
 58 IBM924_LF                        : IBM924_LF
 59 ISO-2022-CN                        : ISO-2022-CN
 60 ISO-2022-JP                        : ISO-2022-JP
 61 ISO-2022-JP-2                        : ISO-2022-JP-2
 62 ISO-2022-KR                        : ISO-2022-KR
 63 ISO-8859-1                        : ISO-8859-1
 64 ISO-8859-10                        : ISO-8859-10
 65 ISO-8859-13                        : ISO-8859-13
 66 ISO-8859-14                        : ISO-8859-14
 67 ISO-8859-15                        : ISO-8859-15
 68 ISO-8859-16                        : ISO-8859-16
 69 ISO-8859-2                        : ISO-8859-2
 70 ISO-8859-3                        : ISO-8859-3
 71 ISO-8859-4                        : ISO-8859-4
 72 ISO-8859-5                        : ISO-8859-5
 73 ISO-8859-6                        : ISO-8859-6
 74 ISO-8859-7                        : ISO-8859-7
 75 ISO-8859-8                        : ISO-8859-8
 76 ISO-8859-9                        : ISO-8859-9
 77 JIS_X0201                        : JIS_X0201
 78 JIS_X0212-1990                        : JIS_X0212-1990
 79 KOI8-R                        : KOI8-R
 80 KOI8-U                        : KOI8-U
 81 PTCP154                        : PTCP154
 82 RK1048                        : RK1048
 83 Shift_JIS                        : Shift_JIS
 84 TIS-620                        : TIS-620
 85 US-ASCII                        : US-ASCII
 86 UTF-16                        : UTF-16
 87 UTF-16BE                        : UTF-16BE
 88 UTF-16LE                        : UTF-16LE
 89 UTF-32                        : UTF-32
 90 UTF-32BE                        : UTF-32BE
 91 UTF-32LE                        : UTF-32LE
 92 UTF-8                        : UTF-8
 93 windows-1250                        : windows-1250
 94 windows-1251                        : windows-1251
 95 windows-1252                        : windows-1252
 96 windows-1253                        : windows-1253
 97 windows-1254                        : windows-1254
 98 windows-1255                        : windows-1255
 99 windows-1256                        : windows-1256
100 windows-1257                        : windows-1257
101 windows-1258                        : windows-1258
102 windows-31j                        : windows-31j
103 x-Big5-HKSCS-2001                        : x-Big5-HKSCS-2001
104 x-Big5-Solaris                        : x-Big5-Solaris
105 x-compound-text                        : x-compound-text
106 x-EUC-TW                        : x-EUC-TW
107 x-EUC_CN                        : x-EUC_CN
108 x-EUC_JP_LINUX                        : x-EUC_JP_LINUX
109 x-eucJP-Open                        : x-eucJP-Open
110 x-IBM-udcJP                        : x-IBM-udcJP
111 x-IBM1006                        : x-IBM1006
112 x-IBM1025                        : x-IBM1025
113 x-IBM1027                        : x-IBM1027
114 x-IBM1041                        : x-IBM1041
115 x-IBM1043                        : x-IBM1043
116 x-IBM1046                        : x-IBM1046
117 x-IBM1046S                        : x-IBM1046S
118 x-IBM1088                        : x-IBM1088
119 x-IBM1097                        : x-IBM1097
120 x-IBM1098                        : x-IBM1098
121 x-IBM1112                        : x-IBM1112
122 x-IBM1114                        : x-IBM1114
123 x-IBM1115                        : x-IBM1115
124 x-IBM1122                        : x-IBM1122
125 x-IBM1123                        : x-IBM1123
126 x-IBM1124                        : x-IBM1124
127 x-IBM1130                        : x-IBM1130
128 x-IBM1164                        : x-IBM1164
129 x-IBM1165                        : x-IBM1165
130 x-IBM1166                        : x-IBM1166
131 x-IBM1351                        : x-IBM1351
132 x-IBM1362                        : x-IBM1362
133 x-IBM1363                        : x-IBM1363
134 x-IBM1363C                        : x-IBM1363C
135 x-IBM1364                        : x-IBM1364
136 x-IBM1370                        : x-IBM1370
137 x-IBM1371                        : x-IBM1371
138 x-IBM1377                        : x-IBM1377
139 x-IBM1379                        : x-IBM1379
140 x-IBM1380                        : x-IBM1380
141 x-IBM1381                        : x-IBM1381
142 x-IBM1382                        : x-IBM1382
143 x-IBM1383                        : x-IBM1383
144 x-IBM1385                        : x-IBM1385
145 x-IBM1386                        : x-IBM1386
146 x-IBM1388                        : x-IBM1388
147 x-IBM1390                        : x-IBM1390
148 x-IBM1390A                        : x-IBM1390A
149 x-IBM1399                        : x-IBM1399
150 x-IBM1399A                        : x-IBM1399A
151 x-IBM16684                        : x-IBM16684
152 x-IBM16684A                        : x-IBM16684A
153 x-IBM29626                        : x-IBM29626
154 x-IBM29626C                        : x-IBM29626C
155 x-IBM300                        : x-IBM300
156 x-IBM300A                        : x-IBM300A
157 x-IBM301                        : x-IBM301
158 x-IBM33722                        : x-IBM33722
159 x-IBM33722A                        : x-IBM33722A
160 x-IBM33722C                        : x-IBM33722C
161 x-IBM420S                        : x-IBM420S
162 x-IBM4933                        : x-IBM4933
163 x-IBM720                        : x-IBM720
164 x-IBM737                        : x-IBM737
165 x-IBM808                        : x-IBM808
166 x-IBM833                        : x-IBM833
167 x-IBM834                        : x-IBM834
168 x-IBM835                        : x-IBM835
169 x-IBM836                        : x-IBM836
170 x-IBM837                        : x-IBM837
171 x-IBM856                        : x-IBM856
172 x-IBM859                        : x-IBM859
173 x-IBM864S                        : x-IBM864S
174 x-IBM867                        : x-IBM867
175 x-IBM874                        : x-IBM874
176 x-IBM875                        : x-IBM875
177 x-IBM897                        : x-IBM897
178 x-IBM921                        : x-IBM921
179 x-IBM922                        : x-IBM922
180 x-IBM927                        : x-IBM927
181 x-IBM930                        : x-IBM930
182 x-IBM930A                        : x-IBM930A
183 x-IBM933                        : x-IBM933
184 x-IBM935                        : x-IBM935
185 x-IBM937                        : x-IBM937
186 x-IBM939                        : x-IBM939
187 x-IBM939A                        : x-IBM939A
188 x-IBM942                        : x-IBM942
189 x-IBM942C                        : x-IBM942C
190 x-IBM943                        : x-IBM943
191 x-IBM943C                        : x-IBM943C
192 x-IBM947                        : x-IBM947
193 x-IBM948                        : x-IBM948
194 x-IBM949                        : x-IBM949
195 x-IBM949C                        : x-IBM949C
196 x-IBM950                        : x-IBM950
197 x-IBM951                        : x-IBM951
198 x-IBM954                        : x-IBM954
199 x-IBM954C                        : x-IBM954C
200 x-IBM964                        : x-IBM964
201 x-IBM970                        : x-IBM970
202 x-IBM971                        : x-IBM971
203 x-ISCII91                        : x-ISCII91
204 x-ISO-2022-CN-CNS                        : x-ISO-2022-CN-CNS
205 x-ISO-2022-CN-GB                        : x-ISO-2022-CN-GB
206 x-iso-8859-11                        : x-iso-8859-11
207 x-ISO-8859-6S                        : x-ISO-8859-6S
208 x-JIS0208                        : x-JIS0208
209 x-JISAutoDetect                        : x-JISAutoDetect
210 x-Johab                        : x-Johab
211 x-KOI8_RU                        : x-KOI8_RU
212 x-KSC5601                        : x-KSC5601
213 x-MacArabic                        : x-MacArabic
214 x-MacCentralEurope                        : x-MacCentralEurope
215 x-MacCroatian                        : x-MacCroatian
216 x-MacCyrillic                        : x-MacCyrillic
217 x-MacDingbat                        : x-MacDingbat
218 x-MacGreek                        : x-MacGreek
219 x-MacHebrew                        : x-MacHebrew
220 x-MacIceland                        : x-MacIceland
221 x-MacRoman                        : x-MacRoman
222 x-MacRomania                        : x-MacRomania
223 x-MacSymbol                        : x-MacSymbol
224 x-MacThai                        : x-MacThai
225 x-MacTurkish                        : x-MacTurkish
226 x-MacUkraine                        : x-MacUkraine
227 x-MS932_0213                        : x-MS932_0213
228 x-MS950-HKSCS                        : x-MS950-HKSCS
229 x-MS950-HKSCS-XP                        : x-MS950-HKSCS-XP
230 x-mswin-936                        : x-mswin-936
231 x-mswin-936A                        : x-mswin-936A
232 x-PCK                        : x-PCK
233 x-SJIS_0213                        : x-SJIS_0213
234 x-UTF-16LE-BOM                        : x-UTF-16LE-BOM
235 X-UTF-32BE-BOM                        : X-UTF-32BE-BOM
236 X-UTF-32LE-BOM                        : X-UTF-32LE-BOM
237 x-UTF_8J                        : x-UTF_8J
238 x-windows-1256S                        : x-windows-1256S
239 x-windows-50220                        : x-windows-50220
240 x-windows-50221                        : x-windows-50221
241 x-windows-874                        : x-windows-874
242 x-windows-949                        : x-windows-949
243 x-windows-950                        : x-windows-950
244 x-windows-iso2022jp                        : x-windows-iso2022jp

View Code

通过Charset的forName()方法,可以指定当前以什么字符集进行编码或者解码,

Charset中的 CharsetEncoder可以将Unicode字符集以forName()指定的字符集进行编码,并转换成ByteBuffer(字节序列).

Charset中的 CharsetDecoder可以将其他字符集的ByteBuffer以Unicode字符集进行解码,并转换成CharBuffer(字符序列).

以上两行待验证。

下面演示Charset用法,

 1 package nio;
 2 
 3 import java.nio.ByteBuffer;
 4 import java.nio.CharBuffer;
 5 import java.nio.charset.CharacterCodingException;
 6 import java.nio.charset.Charset;
 7 import java.nio.charset.CharsetDecoder;
 8 import java.nio.charset.CharsetEncoder;
 9 import java.util.SortedMap;
10 
11 public class CharsetTest {
12     public static void charsetTest() {
13         SortedMap<String,Charset> map = Charset.availableCharsets();
14         for (String key : map.keySet()) {
15             System.out.println(key+"                        : "+map.get(key));
16         }    
17     }
18     
19     public static void charsetTransForm() throws CharacterCodingException {
20         //简体中文编码
21         Charset cn = Charset.forName("GBK");
22         CharsetEncoder cnEncoder = cn.newEncoder();
23         CharsetDecoder cnDecoder = cn.newDecoder();
24         CharBuffer cbuff = CharBuffer.allocate(8);
25         cbuff.put("A");
26         cbuff.put("孙");
27         cbuff.put("悟");
28         cbuff.put("空");
29         //调用flip(), lim设置为pos位置,pos恢复起始位置, 读就绪
30         cbuff.flip();
31         //字符转字节
32         ByteBuffer bbuff = cnEncoder.encode(cbuff);
33         for (int i = 0; i < bbuff.limit(); i++) {
34             //直接输出字节
35             System.out.print(bbuff.get(i)+" ");
36         }
37         System.out.println("\n");
38         //字节转字符
39         System.out.println(cnDecoder.decode(bbuff));
40     }
41     public static void main(String[] args) throws CharacterCodingException {
42         charsetTest();
43         //charsetTransForm();
44     }
45 }

上面的charsetTransForm()方法输出如下,

1 65 -53 -17 -50 -14 -65 -43 
2 
3 A孙悟空

在String类里,也提供了一个getBytes(String charset)的方法,可以返回byte[],可以将指定字符集的字符串转换成字节码。

文件锁

Channel中提供了lock(阻塞)和trylock(非阻塞)来在多进程并发时锁定文件,这两个方法返回一个FileLock对象。  FileLock对象的release()方法可以释放文件锁。

下面是一个简单例子,

 1 package nio;
 2 
 3 import java.io.FileNotFoundException;
 4 import java.io.FileOutputStream;
 5 import java.io.IOException;
 6 import java.nio.channels.FileChannel;
 7 import java.nio.channels.FileLock;
 8 
 9 public class FileLockTest {
10     public static void main(String[] args) throws FileNotFoundException, IOException, InterruptedException {
11         try (
12                 FileChannel channel = new FileOutputStream("a.txt").getChannel()) {
13             //使用非阻塞方式加锁
14             FileLock lock = channel.lock();
15             Thread.sleep(10000);
16             lock.release();
17         }
18     }
19 }

Channel提供的文件锁并不常用,部分原因原因如下,

  • 并发控制性能不高,还是推荐用数据库
  • 某些平台上文件锁无效
  • 如果两个java程序同时使用一个Java虚拟机运行,则不能同时对一个文件加锁。
  • 某些平台上,关闭FlieLock时会释放锁,因此同一个文件被锁定应该避免打开多个FileLock

 

    原文作者:fysola
    原文地址: http://www.cnblogs.com/fysola/p/6134849.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞