Java-NIO(二):缓冲区(Buffer)的数据存取

  • 缓冲区(Buffer):

  一个用于特定基本数据类行的容器。有java.nio包定义的,所有缓冲区都是抽象类Buffer的子类。

  Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道读入到缓冲区,从缓冲区写入通道中的。

  Buffer就像一个数组,可以保存多个相同类型的数据。根据类型不同(boolean除外),有以下Buffer常用子类:

  1. ByteBuffer
  2. CharBuffer
  3. ShortBuffer
  4. IntBuffer
  5. LongBuffer
  6. FloatBuffer
  7. DoubleBuffer

上述Buffer类他们都采用相似的方法进行管理数据,只是各自管理的数据类型不同而已,都是通过以下方法获取一个Buffer对象:

static XxxBuffer allocate(int capacity)

创建一个容量为capacity的XxxBuffer对象。

  • Buffer中的重要概念:

1)容量(capacity):表示Buffer最大数据容量,缓冲区容量不能为负,并且建立后不能修改。

2)限制(limit):第一个不应该读取或者写入的数据的索引,即位于limit后的数据不可以读写。缓冲区的限制不能为负,并且不能大于其容量(capacity)。

3)位置(position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制(limit)。

4)标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark()方法指定Buffer中一个特定的position,之后可以通过调用reset()方法恢复到这个position。

java.nio.Buffer.java
  1 /*
  2  * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
  3  * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  4  *
  5  *
  6  *
  7  *
  8  *
  9  *
 10  *
 11  *
 12  *
 13  *
 14  *
 15  *
 16  *
 17  *
 18  *
 19  *
 20  *
 21  *
 22  *
 23  *
 24  */
 25 
 26 package java.nio;
 27 
 28 import java.util.Spliterator;
 29 
 30 /**
 31  * A container for data of a specific primitive type.
 32  *
 33  * <p> A buffer is a linear, finite sequence of elements of a specific
 34  * primitive type.  Aside from its content, the essential properties of a
 35  * buffer are its capacity, limit, and position: </p>
 36  *
 37  * <blockquote>
 38  *
 39  *   <p> A buffer's <i>capacity</i> is the number of elements it contains.  The
 40  *   capacity of a buffer is never negative and never changes.  </p>
 41  *
 42  *   <p> A buffer's <i>limit</i> is the index of the first element that should
 43  *   not be read or written.  A buffer's limit is never negative and is never
 44  *   greater than its capacity.  </p>
 45  *
 46  *   <p> A buffer's <i>position</i> is the index of the next element to be
 47  *   read or written.  A buffer's position is never negative and is never
 48  *   greater than its limit.  </p>
 49  *
 50  * </blockquote>
 51  *
 52  * <p> There is one subclass of this class for each non-boolean primitive type.
 53  *
 54  *
 55  * <h2> Transferring data </h2>
 56  *
 57  * <p> Each subclass of this class defines two categories of <i>get</i> and
 58  * <i>put</i> operations: </p>
 59  *
 60  * <blockquote>
 61  *
 62  *   <p> <i>Relative</i> operations read or write one or more elements starting
 63  *   at the current position and then increment the position by the number of
 64  *   elements transferred.  If the requested transfer exceeds the limit then a
 65  *   relative <i>get</i> operation throws a {@link BufferUnderflowException}
 66  *   and a relative <i>put</i> operation throws a {@link
 67  *   BufferOverflowException}; in either case, no data is transferred.  </p>
 68  *
 69  *   <p> <i>Absolute</i> operations take an explicit element index and do not
 70  *   affect the position.  Absolute <i>get</i> and <i>put</i> operations throw
 71  *   an {@link IndexOutOfBoundsException} if the index argument exceeds the
 72  *   limit.  </p>
 73  *
 74  * </blockquote>
 75  *
 76  * <p> Data may also, of course, be transferred in to or out of a buffer by the
 77  * I/O operations of an appropriate channel, which are always relative to the
 78  * current position.
 79  *
 80  *
 81  * <h2> Marking and resetting </h2>
 82  *
 83  * <p> A buffer's <i>mark</i> is the index to which its position will be reset
 84  * when the {@link #reset reset} method is invoked.  The mark is not always
 85  * defined, but when it is defined it is never negative and is never greater
 86  * than the position.  If the mark is defined then it is discarded when the
 87  * position or the limit is adjusted to a value smaller than the mark.  If the
 88  * mark is not defined then invoking the {@link #reset reset} method causes an
 89  * {@link InvalidMarkException} to be thrown.
 90  *
 91  *
 92  * <h2> Invariants </h2>
 93  *
 94  * <p> The following invariant holds for the mark, position, limit, and
 95  * capacity values:
 96  *
 97  * <blockquote>
 98  *     <tt>0</tt> <tt>&lt;=</tt>
 99  *     <i>mark</i> <tt>&lt;=</tt>
100  *     <i>position</i> <tt>&lt;=</tt>
101  *     <i>limit</i> <tt>&lt;=</tt>
102  *     <i>capacity</i>
103  * </blockquote>
104  *
105  * <p> A newly-created buffer always has a position of zero and a mark that is
106  * undefined.  The initial limit may be zero, or it may be some other value
107  * that depends upon the type of the buffer and the manner in which it is
108  * constructed.  Each element of a newly-allocated buffer is initialized
109  * to zero.
110  *
111  *
112  * <h2> Clearing, flipping, and rewinding </h2>
113  *
114  * <p> In addition to methods for accessing the position, limit, and capacity
115  * values and for marking and resetting, this class also defines the following
116  * operations upon buffers:
117  *
118  * <ul>
119  *
120  *   <li><p> {@link #clear} makes a buffer ready for a new sequence of
121  *   channel-read or relative <i>put</i> operations: It sets the limit to the
122  *   capacity and the position to zero.  </p></li>
123  *
124  *   <li><p> {@link #flip} makes a buffer ready for a new sequence of
125  *   channel-write or relative <i>get</i> operations: It sets the limit to the
126  *   current position and then sets the position to zero.  </p></li>
127  *
128  *   <li><p> {@link #rewind} makes a buffer ready for re-reading the data that
129  *   it already contains: It leaves the limit unchanged and sets the position
130  *   to zero.  </p></li>
131  *
132  * </ul>
133  *
134  *
135  * <h2> Read-only buffers </h2>
136  *
137  * <p> Every buffer is readable, but not every buffer is writable.  The
138  * mutation methods of each buffer class are specified as <i>optional
139  * operations</i> that will throw a {@link ReadOnlyBufferException} when
140  * invoked upon a read-only buffer.  A read-only buffer does not allow its
141  * content to be changed, but its mark, position, and limit values are mutable.
142  * Whether or not a buffer is read-only may be determined by invoking its
143  * {@link #isReadOnly isReadOnly} method.
144  *
145  *
146  * <h2> Thread safety </h2>
147  *
148  * <p> Buffers are not safe for use by multiple concurrent threads.  If a
149  * buffer is to be used by more than one thread then access to the buffer
150  * should be controlled by appropriate synchronization.
151  *
152  *
153  * <h2> Invocation chaining </h2>
154  *
155  * <p> Methods in this class that do not otherwise have a value to return are
156  * specified to return the buffer upon which they are invoked.  This allows
157  * method invocations to be chained; for example, the sequence of statements
158  *
159  * <blockquote><pre>
160  * b.flip();
161  * b.position(23);
162  * b.limit(42);</pre></blockquote>
163  *
164  * can be replaced by the single, more compact statement
165  *
166  * <blockquote><pre>
167  * b.flip().position(23).limit(42);</pre></blockquote>
168  *
169  *
170  * @author Mark Reinhold
171  * @author JSR-51 Expert Group
172  * @since 1.4
173  */
174 
175 public abstract class Buffer {
176 
177     /**
178      * The characteristics of Spliterators that traverse and split elements
179      * maintained in Buffers.
180      */
181     static final int SPLITERATOR_CHARACTERISTICS =
182         Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;
183 
184     // Invariants: mark <= position <= limit <= capacity
185     private int mark = -1;
186     private int position = 0;
187     private int limit;
188     private int capacity;
189 
190     // Used only by direct buffers
191     // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
192     long address;
193 
194     // Creates a new buffer with the given mark, position, limit, and capacity,
195     // after checking invariants.
196     //
197     Buffer(int mark, int pos, int lim, int cap) {       // package-private
198         if (cap < 0)
199             throw new IllegalArgumentException("Negative capacity: " + cap);
200         this.capacity = cap;
201         limit(lim);
202         position(pos);
203         if (mark >= 0) {
204             if (mark > pos)
205                 throw new IllegalArgumentException("mark > position: ("
206                                                    + mark + " > " + pos + ")");
207             this.mark = mark;
208         }
209     }
210 
211     /**
212      * Returns this buffer's capacity.
213      *
214      * @return  The capacity of this buffer
215      */
216     public final int capacity() {
217         return capacity;
218     }
219 
220     /**
221      * Returns this buffer's position.
222      *
223      * @return  The position of this buffer
224      */
225     public final int position() {
226         return position;
227     }
228 
229     /**
230      * Sets this buffer's position.  If the mark is defined and larger than the
231      * new position then it is discarded.
232      *
233      * @param  newPosition
234      *         The new position value; must be non-negative
235      *         and no larger than the current limit
236      *
237      * @return  This buffer
238      *
239      * @throws  IllegalArgumentException
240      *          If the preconditions on <tt>newPosition</tt> do not hold
241      */
242     public final Buffer position(int newPosition) {
243         if ((newPosition > limit) || (newPosition < 0))
244             throw new IllegalArgumentException();
245         position = newPosition;
246         if (mark > position) mark = -1;
247         return this;
248     }
249 
250     /**
251      * Returns this buffer's limit.
252      *
253      * @return  The limit of this buffer
254      */
255     public final int limit() {
256         return limit;
257     }
258 
259     /**
260      * Sets this buffer's limit.  If the position is larger than the new limit
261      * then it is set to the new limit.  If the mark is defined and larger than
262      * the new limit then it is discarded.
263      *
264      * @param  newLimit
265      *         The new limit value; must be non-negative
266      *         and no larger than this buffer's capacity
267      *
268      * @return  This buffer
269      *
270      * @throws  IllegalArgumentException
271      *          If the preconditions on <tt>newLimit</tt> do not hold
272      */
273     public final Buffer limit(int newLimit) {
274         if ((newLimit > capacity) || (newLimit < 0))
275             throw new IllegalArgumentException();
276         limit = newLimit;
277         if (position > limit) position = limit;
278         if (mark > limit) mark = -1;
279         return this;
280     }
281 
282     /**
283      * Sets this buffer's mark at its position.
284      *
285      * @return  This buffer
286      */
287     public final Buffer mark() {
288         mark = position;
289         return this;
290     }
291 
292     /**
293      * Resets this buffer's position to the previously-marked position.
294      *
295      * <p> Invoking this method neither changes nor discards the mark's
296      * value. </p>
297      *
298      * @return  This buffer
299      *
300      * @throws  InvalidMarkException
301      *          If the mark has not been set
302      */
303     public final Buffer reset() {
304         int m = mark;
305         if (m < 0)
306             throw new InvalidMarkException();
307         position = m;
308         return this;
309     }
310 
311     /**
312      * Clears this buffer.  The position is set to zero, the limit is set to
313      * the capacity, and the mark is discarded.
314      *
315      * <p> Invoke this method before using a sequence of channel-read or
316      * <i>put</i> operations to fill this buffer.  For example:
317      *
318      * <blockquote><pre>
319      * buf.clear();     // Prepare buffer for reading
320      * in.read(buf);    // Read data</pre></blockquote>
321      *
322      * <p> This method does not actually erase the data in the buffer, but it
323      * is named as if it did because it will most often be used in situations
324      * in which that might as well be the case. </p>
325      *
326      * @return  This buffer
327      */
328     public final Buffer clear() {
329         position = 0;
330         limit = capacity;
331         mark = -1;
332         return this;
333     }
334 
335     /**
336      * Flips this buffer.  The limit is set to the current position and then
337      * the position is set to zero.  If the mark is defined then it is
338      * discarded.
339      *
340      * <p> After a sequence of channel-read or <i>put</i> operations, invoke
341      * this method to prepare for a sequence of channel-write or relative
342      * <i>get</i> operations.  For example:
343      *
344      * <blockquote><pre>
345      * buf.put(magic);    // Prepend header
346      * in.read(buf);      // Read data into rest of buffer
347      * buf.flip();        // Flip buffer
348      * out.write(buf);    // Write header + data to channel</pre></blockquote>
349      *
350      * <p> This method is often used in conjunction with the {@link
351      * java.nio.ByteBuffer#compact compact} method when transferring data from
352      * one place to another.  </p>
353      *
354      * @return  This buffer
355      */
356     public final Buffer flip() {
357         limit = position;
358         position = 0;
359         mark = -1;
360         return this;
361     }
362 
363     /**
364      * Rewinds this buffer.  The position is set to zero and the mark is
365      * discarded.
366      *
367      * <p> Invoke this method before a sequence of channel-write or <i>get</i>
368      * operations, assuming that the limit has already been set
369      * appropriately.  For example:
370      *
371      * <blockquote><pre>
372      * out.write(buf);    // Write remaining data
373      * buf.rewind();      // Rewind buffer
374      * buf.get(array);    // Copy data into array</pre></blockquote>
375      *
376      * @return  This buffer
377      */
378     public final Buffer rewind() {
379         position = 0;
380         mark = -1;
381         return this;
382     }
383 
384     /**
385      * Returns the number of elements between the current position and the
386      * limit.
387      *
388      * @return  The number of elements remaining in this buffer
389      */
390     public final int remaining() {
391         return limit - position;
392     }
393 
394     /**
395      * Tells whether there are any elements between the current position and
396      * the limit.
397      *
398      * @return  <tt>true</tt> if, and only if, there is at least one element
399      *          remaining in this buffer
400      */
401     public final boolean hasRemaining() {
402         return position < limit;
403     }
404 
405     /**
406      * Tells whether or not this buffer is read-only.
407      *
408      * @return  <tt>true</tt> if, and only if, this buffer is read-only
409      */
410     public abstract boolean isReadOnly();
411 
412     /**
413      * Tells whether or not this buffer is backed by an accessible
414      * array.
415      *
416      * <p> If this method returns <tt>true</tt> then the {@link #array() array}
417      * and {@link #arrayOffset() arrayOffset} methods may safely be invoked.
418      * </p>
419      *
420      * @return  <tt>true</tt> if, and only if, this buffer
421      *          is backed by an array and is not read-only
422      *
423      * @since 1.6
424      */
425     public abstract boolean hasArray();
426 
427     /**
428      * Returns the array that backs this
429      * buffer&nbsp;&nbsp;<i>(optional operation)</i>.
430      *
431      * <p> This method is intended to allow array-backed buffers to be
432      * passed to native code more efficiently. Concrete subclasses
433      * provide more strongly-typed return values for this method.
434      *
435      * <p> Modifications to this buffer's content will cause the returned
436      * array's content to be modified, and vice versa.
437      *
438      * <p> Invoke the {@link #hasArray hasArray} method before invoking this
439      * method in order to ensure that this buffer has an accessible backing
440      * array.  </p>
441      *
442      * @return  The array that backs this buffer
443      *
444      * @throws  ReadOnlyBufferException
445      *          If this buffer is backed by an array but is read-only
446      *
447      * @throws  UnsupportedOperationException
448      *          If this buffer is not backed by an accessible array
449      *
450      * @since 1.6
451      */
452     public abstract Object array();
453 
454     /**
455      * Returns the offset within this buffer's backing array of the first
456      * element of the buffer&nbsp;&nbsp;<i>(optional operation)</i>.
457      *
458      * <p> If this buffer is backed by an array then buffer position <i>p</i>
459      * corresponds to array index <i>p</i>&nbsp;+&nbsp;<tt>arrayOffset()</tt>.
460      *
461      * <p> Invoke the {@link #hasArray hasArray} method before invoking this
462      * method in order to ensure that this buffer has an accessible backing
463      * array.  </p>
464      *
465      * @return  The offset within this buffer's array
466      *          of the first element of the buffer
467      *
468      * @throws  ReadOnlyBufferException
469      *          If this buffer is backed by an array but is read-only
470      *
471      * @throws  UnsupportedOperationException
472      *          If this buffer is not backed by an accessible array
473      *
474      * @since 1.6
475      */
476     public abstract int arrayOffset();
477 
478     /**
479      * Tells whether or not this buffer is
480      * <a href="ByteBuffer.html#direct"><i>direct</i></a>.
481      *
482      * @return  <tt>true</tt> if, and only if, this buffer is direct
483      *
484      * @since 1.6
485      */
486     public abstract boolean isDirect();
487 
488 
489     // -- Package-private methods for bounds checking, etc. --
490 
491     /**
492      * Checks the current position against the limit, throwing a {@link
493      * BufferUnderflowException} if it is not smaller than the limit, and then
494      * increments the position.
495      *
496      * @return  The current position value, before it is incremented
497      */
498     final int nextGetIndex() {                          // package-private
499         if (position >= limit)
500             throw new BufferUnderflowException();
501         return position++;
502     }
503 
504     final int nextGetIndex(int nb) {                    // package-private
505         if (limit - position < nb)
506             throw new BufferUnderflowException();
507         int p = position;
508         position += nb;
509         return p;
510     }
511 
512     /**
513      * Checks the current position against the limit, throwing a {@link
514      * BufferOverflowException} if it is not smaller than the limit, and then
515      * increments the position.
516      *
517      * @return  The current position value, before it is incremented
518      */
519     final int nextPutIndex() {                          // package-private
520         if (position >= limit)
521             throw new BufferOverflowException();
522         return position++;
523     }
524 
525     final int nextPutIndex(int nb) {                    // package-private
526         if (limit - position < nb)
527             throw new BufferOverflowException();
528         int p = position;
529         position += nb;
530         return p;
531     }
532 
533     /**
534      * Checks the given index against the limit, throwing an {@link
535      * IndexOutOfBoundsException} if it is not smaller than the limit
536      * or is smaller than zero.
537      */
538     final int checkIndex(int i) {                       // package-private
539         if ((i < 0) || (i >= limit))
540             throw new IndexOutOfBoundsException();
541         return i;
542     }
543 
544     final int checkIndex(int i, int nb) {               // package-private
545         if ((i < 0) || (nb > limit - i))
546             throw new IndexOutOfBoundsException();
547         return i;
548     }
549 
550     final int markValue() {                             // package-private
551         return mark;
552     }
553 
554     final void truncate() {                             // package-private
555         mark = -1;
556         position = 0;
557         limit = 0;
558         capacity = 0;
559     }
560 
561     final void discardMark() {                          // package-private
562         mark = -1;
563     }
564 
565     static void checkBounds(int off, int len, int size) { // package-private
566         if ((off | len | (off + len) | (size - (off + len))) < 0)
567             throw new IndexOutOfBoundsException();
568     }
569 
570 }
View Code

 注意:0<=mark<=position<=capacity

测试代码:

 1 package com.dx.nios;
 2 
 3 import java.nio.ByteBuffer;
 4 
 5 import org.junit.Test;
 6 
 7 public class BufferTest {
 8 
 9     @Test
10     public void TestBuffer() {
11         ByteBuffer byteBuffer = ByteBuffer.allocate(10);
12         
13         System.out.println("------------allocate------------------");
14         System.out.println(byteBuffer.position());
15         System.out.println(byteBuffer.limit());
16         System.out.println(byteBuffer.capacity());
17         
18         
19         byteBuffer.put("abcde".getBytes());
20                 
21         System.out.println("------------put------------------");
22         System.out.println(byteBuffer.position());
23         System.out.println(byteBuffer.limit());
24         System.out.println(byteBuffer.capacity());
25         
26         byteBuffer.flip();
27         
28         System.out.println("------------flip------------------");
29         System.out.println(byteBuffer.position());
30         System.out.println(byteBuffer.limit());
31         System.out.println(byteBuffer.capacity());        
32         
33     }
34 }

输出结果:

------------allocate------------------
0
10
10
------------put------------------
5
10
10
------------flip------------------
0
5
10

分析:

  •  Buffer常用函数测试:

 1 package com.dx.nios;
 2 
 3 import java.nio.ByteBuffer;
 4 
 5 import org.junit.Test;
 6 
 7 public class BufferTest {
 8 
 9     @Test
10     public void TestBuffer() {
11         // 1.使用allocate()申请10个字节的缓冲区
12         ByteBuffer byteBuffer = ByteBuffer.allocate(10);
13         System.out.println("------------allocate------------------");
14         System.out.println(byteBuffer.position());
15         System.out.println(byteBuffer.limit());
16         System.out.println(byteBuffer.capacity());
17 
18         // 2.使用put()存放5个字节到缓冲区
19         byteBuffer.put("abcde".getBytes());
20         System.out.println("------------put------------------");
21         System.out.println(byteBuffer.position());
22         System.out.println(byteBuffer.limit());
23         System.out.println(byteBuffer.capacity());
24 
25         // 3.切换到读取数据模式
26         byteBuffer.flip();
27         System.out.println("------------flip------------------");
28         System.out.println(byteBuffer.position());
29         System.out.println(byteBuffer.limit());
30         System.out.println(byteBuffer.capacity());
31 
32         // 4.从缓冲区中读取数据
33         System.out.println("------------get------------------");
34         byte[] bytes = new byte[byteBuffer.limit()];
35         byteBuffer.get(bytes);
36         System.out.println(new String(bytes, 0, bytes.length));
37         System.out.println(byteBuffer.position());
38         System.out.println(byteBuffer.limit());
39         System.out.println(byteBuffer.capacity());
40 
41         // 5.设置为可重复读取
42         System.out.println("------------rewind------------------");
43         byteBuffer.rewind();
44         System.out.println(byteBuffer.position());
45         System.out.println(byteBuffer.limit());
46         System.out.println(byteBuffer.capacity());
47         byte[] bytes2 = new byte[byteBuffer.limit()];
48         byteBuffer.get(bytes2);
49         System.out.println(new String(bytes2, 0, bytes2.length));
50         System.out.println(byteBuffer.position());
51         System.out.println(byteBuffer.limit());
52         System.out.println(byteBuffer.capacity());
53 
54         // 6。clear清空缓存区,但是内容没有被清掉,还存在。只不过这些数据状态为被遗忘状态。
55         System.out.println("------------clear------------------");
56         byteBuffer.clear();
57         System.out.println(byteBuffer.position());
58         System.out.println(byteBuffer.limit());
59         System.out.println(byteBuffer.capacity());
60         byte[] bytes3 = new byte[10];
61         byteBuffer.get(bytes3);
62         System.out.println(new String(bytes3, 0, bytes3.length));
63     }
64 }

输出:

 1 ------------allocate------------------
 2 0
 3 10
 4 10
 5 ------------put------------------
 6 5
 7 10
 8 10
 9 ------------flip------------------
10 0
11 5
12 10
13 ------------get------------------
14 abcde
15 5
16 5
17 10
18 ------------rewind------------------
19 0
20 5
21 10
22 abcde
23 5
24 5
25 10
26 ------------clear------------------
27 0
28 10
29 10
30 abcde
  • mark与reset的用法:

 1 @Test
 2     public void testMark() {
 3         ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
 4         byteBuffer.put("abcde".getBytes());
 5         byteBuffer.flip();
 6 
 7         byte[] bytes = new byte[byteBuffer.limit()];
 8         byteBuffer.get(bytes, 0, 2);
 9         System.out.println(new String(bytes, 0, bytes.length));
10 
11         System.out.println(byteBuffer.position());
12         System.out.println(byteBuffer.limit());
13         System.out.println(byteBuffer.capacity());
14         
15         byteBuffer.mark();
16         System.out.println("---------mark----------");
17         
18         byteBuffer.get(bytes, 0, 2);
19         System.out.println(new String(bytes, 0, bytes.length));
20 
21         System.out.println(byteBuffer.position());
22         System.out.println(byteBuffer.limit());
23         System.out.println(byteBuffer.capacity());
24         
25         byteBuffer.reset();    
26         System.out.println("---------reset----------");
27         
28         System.out.println(byteBuffer.position());
29         System.out.println(byteBuffer.limit());
30         System.out.println(byteBuffer.capacity());
31     }

打印信息:

ab
2
5
1024
---------mark----------
cd
4
5
1024
---------reset----------
2
5
1024

 
原文地址:https://www.cnblogs.com/yy3b2007com/p/7261025.html