NIO的使用

一)、什么叫NIO?

定义:是一套新的Java I/O标准, 在java1.4中被纳入JDK中。

二)、NIO的实现方法

NIO是基于块的, 以块为基本单位处理数据。

标准的I/O是基于流实现的,以字节为单位处理数据。

三)、NIO的特性

1).为所有的原始类型特供Buffer支持

    ByteBuffer

    CharBuffer

    DoubleBuffer

    FloatBuffer

    IntBuffer

    LongBuffer

    ShortBuffer

2).字符集编码解码解决方案,使用java.nio.Charset

  1. .增加通道(Channel)对象,做为新的原始的I/O抽象

4).支持锁和内存映射文件的文件访问接口

5).提供了基于Selector的异步网络I/O

四)、NIO的两个重要组件

Buffer: 缓冲, 是一块连续的内存块,是NIO中读写数据的中转地。

Channel: 通道, 表示缓冲数据的源头或目的地。

Buffer和Channel的关系:

Channel作为数据的源头:从Channel中写数据到Buffer

Channel    --------->     Buffer

Channel作为数据的目的地:从Buffer中写出数据到Channel

     Channel   <---------      Buffer

五)、NIO的Buffer类族和Channel

Buffer: 是一个抽象类,JDK为每一种Java原生类型都创建了一个Buffer.

注: 除了ByteBuffer外,其它每一种Buffer都具有完全一样的操作。

原因:ByteBuffer多用于绝大多数数标准I/O操作的接口。

Channel: 是一个双向通道,既可读也可写。

注:应用程序中不能直接对Channel进行读写操作,在读取Channel时,需要先将数据读入到相对应的Buffer中,然后在Buffer中进行读取。

使用Buffer读取文件:

public class Nio_Buffer_Channel {
public static void main(String[] args) throws IOException {
//获取一个输入流对象
FileInputStream fin = new FileInputStream("d:/a.txt");
//获取输入流对象的通道,作为数据的源头
FileChannel fileChannel = fin.getChannel();
//创建一个Buffer对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
//从通道中读取数据到Buffer中
fileChannel.read(buffer);
//关闭通道
fileChannel.close();
buffer.flip();
}
}

使用Buffer完成文件的复制:

public class Nio_Buffer_Copy {
public static void main(String[] args) throws IOException {
//输出流对象
FileOutputStream fout = new FileOutputStream("d:/c.txt");
//输入流对象
FileInputStream fin = new FileInputStream("d:/a.txt");
//输出流的通道,数据的目的地
FileChannel writeChannel = fout.getChannel();
//输入流的通道,数据的源头
FileChannel readChannel = fin.getChannel();
//Buffer对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(true){
buffer.clear();
//返回读取数据的大小
int len = readChannel.read(buffer);
if(len == -1){
break;
}
buffer.flip();
writeChannel.write(buffer);
}
}
}

结果:

0
11
0
11
0

六)、深入学习Buffer

主要属性:

//标志位
private int mark = -1;
//写模式:当前缓冲区的位置,从Position的下一个位置写数据
//读模式:当前缓冲区的读位置,将从此位置后,读取数据
private int position = 0;
//写模式: 缓冲区的实际上限,总是小于等于容量,通常等于容量
//读模式: 代表可读取的总容量,和上次写入的数据量相等
private int limit;
//写模式: 缓冲区的总容量上限
//读模式: 缓冲区的总容量上限
private int capacity;

对Buffer进行claer()和flip()操作时lmint和position的变化

public class Filp_Clear {
public static void main(String[] args) {
//创建具有15个字节的Buffer对象
ByteBuffer buffer = ByteBuffer.allocate(15);
System.out.println("存入元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
//向Buffer中存入数据
for(int i = 0 ; i < 10 ; i++){
buffer.put((byte)i);
}
//存入元素后position和limit的变化
System.out.println("存入元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
buffer.flip();
//flip后position和limit的变化
System.out.println("flip后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
for(int i = 0 ; i < 5 ; i++){
System.out.print(buffer.get()+" ");
}
System.out.println();
//读取Buffer元素后position和limit的变化
System.out.println("读取Buffer元素后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
buffer.rewind();
System.out.println("rewind==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
buffer.flip();
//第二次flip后position和limit的变化
System.out.println("第二次flip后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity());
buffer.clear();
//clear后position和limit的变化
System.out.println("clear后position和limit的变化==>"+"position:"+buffer.position()+" limit:"+buffer.limit()+" capacity:" +buffer.capacity()); }
}

运行结果:

存入元素后position和limit的变化==>position:0 limit:15 capacity:15
存入元素后position和limit的变化==>position:10 limit:15 capacity:15
flip后position和limit的变化==>position:0 limit:10 capacity:15
0 1 2 3 4
读取Buffer元素后position和limit的变化==>position:5 limit:10 capacity:15
rewind==>position:0 limit:10 capacity:15
第二次flip后position和limit的变化==>position:0 limit:0 capacity:15
clear后position和limit的变化==>position:0 limit:15 capacity:15

结果分析:

1).当第一次创建Buffer对象时

    position = 0, capacity = limit = Buffer数组容量大小

2).往Buffer添加数据

   position = 数组所占数据的大小,capacity = limit = Buffer数组容量大小

3).buffer.flip()操作

    position = 0, limit = 数组中所占元素的大小,即原position值, capacity =  Buffer数组容量大小

4).buffer.get()获取元素

   position = 获取元素的个数,limit = 数组中所占元素的大小,capacity =  Buffer数组容量大小

5).再次buffer.flip()

    position = 获取元素的个数,limit = position值,  capacity =  Buffer数组容量大小

    注: 当执行flip操作,limit值总是等于上一次的position值

6).buffer.clear

  position = 0, capacity = limit = Buffer数组容量大小

总结:

   i.  put():  position = 数组元素个数 , limit 和 capacity 不变

   ii.  flip(): position = 0,   limit = 原position值,  capacity 不变

   iii. rewind(): position = 0, limit 和 capacity 不变

   iiii.  claer(): 回到初始状态,position = 0, capacity = limit = Buffer数组容量大小。

七)、Buffer的相关操作

1)、Buffer的创建:

1.从堆中分配

//从堆中分配
ByteBuffer buffer = ByteBuffer.allocation(1024);
//ByteBuffer类
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
} //HeapByteBuffer类 extend ByteBuffer
HeapByteBuffer(int cap, int lim) { // package-private
super(-1, 0, lim, cap, new byte[cap], 0);
/*
hb = new byte[cap];
offset = 0;
*/
}
//super(-1, 0, lim, cap, new byte[cap], 0)
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}

2.从既有数组中创建

byte array[] = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(arry);
public static ByteBuffer wrap(byte[] array) {
return wrap(array, 0, array.length);
}
public static ByteBuffer wrap(byte[] array,
int offset, int length)
{
try {
return new HeapByteBuffer(array, offset, length);
} catch (IllegalArgumentException x) {
throw new IndexOutOfBoundsException();
}
}
HeapByteBuffer(byte[] buf, int off, int len) { // package-private super(-1, off, off + len, buf.length, buf, 0);
/*
hb = buf;
offset = 0;
*/
} ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}

2)、重置和清空缓存区

1.rewind(): 将position置为0,并清除标志位(mark)mark = -1

作用:提取Buffer的有效数据。

2.clear(): 将position重置为0,同时,将limit设置为capacity大小,清除标志位 (mark), msrk = -1

 作用:为重写Buffer做准备。

3.flip(): 将limit设置到position所在位置,将position重置为0,并清除标志位mark = -1

  作用:读写转换时使用。

注:这里的重置是指重置Buffer的各标志位,并不是清空Buffer的内容。

3)、读/写缓冲区

//返回当前position的数据,position后移一位
public byte get();
//读取Buffer的数据到dst,并恰当的移动position
public ByteBuffer get(byte[] dst);
//读取给定index上的数据,不改变position的位置
public byte get(int index);
//在当前位置写入给定数据,position后移一位
public ByteBuffer put(byte b);
//将当前数据写入index位置,position位置不变
public ByteBuffer put(int index, byte b);
//将给定的数据src写入到Buffer中,并恰当的移动position
public final ByteBuffer put(byte[] src);

4)、标志缓冲区

标志(mark)缓冲区:类似于书签,可以随时记录当前位置,在任意时刻,可以回到这个位置。

操作mark的方法:

public final Buffer mark()
public final Buffer reset()

1.设置mark为当前position值

buffer.mark();
public final Buffer mark() {
//将mark值置为当前的position值
mark = position;
return this;
}

2.获取mark值,将当前的position置为mark值

buffer.reset();
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}

测试:

public class MarkTest {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0 ; i < 10 ; i++){
buffer.put(((byte)i)); }
//读写转换
buffer.flip();
for(int i = 0 ; i < buffer.limit() ; i++){
System.out.print(buffer.get());
if(i == 4){
//mark = 5,当 i = 4 时,get后,position后移的一位,所以mark = 5
buffer.mark();
System.out.print("mark = "+buffer.mark());
}
}
System.out.println();
System.out.println("原position值 = "+buffer.position());
//重置position为mark值
buffer.reset();
System.out.println("reset后position值 = "+buffer.position());
while(buffer.hasRemaining()){
System.out.print(buffer.get());
}
}
}

结果:

01234mark = java.nio.HeapByteBuffer[pos=5 lim=10 cap=15]56789
原position值 = 10
reset后position值 = 5
56789

5)、复制缓冲区

复制缓冲区:以原缓冲区为基础,生成一个完全一样的新缓冲区。

新缓冲区的特点:

1.新生成的缓冲区和原缓冲区共享相同的内存数据。

2.缓冲区的任意一方的数据改动都是相互可见的。

3.新生成的缓冲区和原缓冲区独立维护各自的position、limit、mark。

作用:增加了程序的灵活性,为多方同时处理数据提供了可能。

生成复制缓冲区的方法:

public ByteBuffer duplicate()

测试:

public class Duplicate_Buffer {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0 ; i < 10 ; i++){
buffer.put((byte)i);
}
//复制当前缓冲区
ByteBuffer copyBuffer = buffer.duplicate();
System.out.println("复制后的缓冲区");
System.out.println(buffer);
System.out.println(copyBuffer);
//重置copyBuffer
copyBuffer.flip();
//重置copyBuffer后缓冲区变化
System.out.println("重置copyBuffer后的缓冲区");
System.out.println(buffer);
System.out.println(copyBuffer);
//向copyBuffer缓冲区中存入数据
copyBuffer.put((byte)100);
//buffer在相同的位置数据也发生了变化
System.out.println("buffer.get(0) = "+buffer.get(0));
System.out.println("copyBuffer.get(0) = "+ copyBuffer.get(0)); }
}

结果:

复制后的缓冲区
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
重置copyBuffer后的缓冲区
java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBuffer[pos=0 lim=10 cap=15]
buffer.get(0) = 100
copyBuffer.get(0) = 100

结论:在对副本copyBuffer进行put操作后,原Buffer相同所在位置的数据也同样发

         生了变化。

6)、缓冲区分片

定义:在现有的缓冲区中,创建新的子缓冲区。

特点:子缓冲区和父缓冲区共享数据。

创建子缓冲区的方法:slice()

ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0; i < 10; i++){
buffer.put((byte)i);
}
buffer.position(2);
buffer.limit(6);
//生成2-6的缓冲区
ByteBuffer subBuffer = buffer.slice(); public class Slice_Buffer {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0; i < 10; i++){
buffer.put((byte)i);
}
buffer.position(2);
buffer.limit(6);
//生成2-6的缓冲区
ByteBuffer subBuffer = buffer.slice();
System.out.println(subBuffer);
System.out.println(subBuffer.get(0));
System.out.println(subBuffer.get(3)); }
}

结果:

java.nio.HeapByteBuffer[pos=0 lim=4 cap=4]
2
5

子缓冲区发生了变化,父缓冲区也随即发生变化:

public class Slice_Buffer {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0; i < 10; i++){
buffer.put((byte)i);
}
buffer.position(2);
buffer.limit(6);
//生成2-6的缓冲区
ByteBuffer subBuffer = buffer.slice();
System.out.println(subBuffer);
System.out.println(subBuffer.get(0));
System.out.println(subBuffer.get(3));
for(int i = 0; i < subBuffer.capacity(); i++){
byte bb = subBuffer.get(i);
bb*= 10;
subBuffer.put(bb);
}
//重置父缓冲区,若不重置,输出20、30、40、50
buffer.position(0);
buffer.limit(buffer.capacity());
while(buffer.hasRemaining()){
System.out.print(buffer.get()+" ");
}
System.out.println();
}
}

7)、只读缓冲区

特点:只读,只读缓冲区和原始缓冲区共享内存块,原始缓冲区的修改,只读缓冲区也是可见的。

创建只读缓冲区的方法:

asRreadOnlyBuffer()

public class AsReadOnlyBuffer_Test {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(15);
for(int i = 0; i < 10; i++){
buffer.put((byte)i);
}
//创建只读缓冲区
ByteBuffer readBuffer = buffer.asReadOnlyBuffer();
System.out.println(buffer);
System.out.println(readBuffer);
readBuffer.flip();
while(readBuffer.hasRemaining()){
System.out.print(readBuffer.get()+" ");
}
System.out.println();
//检测对原始缓冲区进行修改,只读缓冲区可见
buffer.put(2,(byte)20);
readBuffer.flip();
while(readBuffer.hasRemaining()){
System.out.print(readBuffer.get()+" ");
}
}
}

结果:

java.nio.HeapByteBuffer[pos=10 lim=15 cap=15]
java.nio.HeapByteBufferR[pos=10 lim=15 cap=15]
0 1 2 3 4 5 6 7 8 9
0 1 20 3 4 5 6 7 8 9

8)、文件映射到内存

使用文件映射到内存读取文件中的数据使用RandomAccessFile对象读取

RamdomAcessFile: 随机访问存取对象。

特点: 专门处理文件的类 ,支持"随机访问"方式。

随机:指可以跳转到文件的任意位置处读写数据 。

由FileChannel.map()将文件映射到内存:

//将文件的前1024个字节映射到内存
MappedByteBuffer mbb = channel.map(FileChannel.MapMode.Read_WRITE, 0, 1024) public class MappedFile {
public static void main(String[] args) throws IOException {
//随机访问文件类
RandomAccessFile fin = new RandomAccessFile("d:/a.txt","rw");
FileChannel channel = fin.getChannel();
MappedByteBuffer buffer =
//将文件的前1024个字节映射到内存
channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
while(buffer.hasRemaining()){
System.out.print((char)buffer.get());
}
}
}

结果:

hhhhhhhhhhh

结论:使用文件映射内存的方式,将文本文件通过FileChannel映射到内存中,返回一个Buffer对象,从内存中读取文件的内容,通过修改Buffer,将实际数据写到对应的磁盘中。

9)、处理结构化数据

处理结构化数据的方法:

1.散射:将数据读入到一组Buffer中,即将数据读到Buffer[]中。

由scatteringByteBuffer接口提供操作方法:

public long read(Buffer[] dsts) throws IOException;
public long read(Buffer[] dsts, int offset, int length) throws IOException;

注:通过散射读取数据,通道依次填充每个缓冲区。

2.聚集:将数据写入到一组Buffer中,即将数据写入到Buffer[]中。

 由GatheringByteChannel接口提供操作方法:

public long write(Buffer[] src) throws IOException;
public long write(Buffer[] src, int offset, int length) throws IOException;

散射/聚集的使用:

对于一个固定格式的文件的读写,在已知文件具体结构情况下,可以构建若干个符合文件结构的Buffer,Buffer的大小恰好符合各段结构的大小。

Jdk提供了各种通道,使用散射和聚集读写结构化数据:

例:DatagramChannel、 FileChannel、SocketChannel

注:这3个通道都实现了ScatteringByteChannel和GatheringByteChannel.

通过聚集创建文本文件,格式为 书名作者:

public class GatheringBuffer {
public static void main(String[] args) throws IOException {
//通过聚集创建文本文件,格式为 书名作者
ByteBuffer bookBuf = ByteBuffer.wrap("java性能优化技巧".getBytes("Utf-8"));
ByteBuffer autBuf = ByteBuffer.wrap("葛一鸣".getBytes("utf-8"));
int booklen = bookBuf.capacity();
int autlen = autBuf.capacity();
ByteBuffer buffer[] = new ByteBuffer[]{bookBuf ,autBuf};
File file = new File("d:/gather.txt");
//文件不存在时,创建一个新文件
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fou = new FileOutputStream(file);
FileChannel channel = fou.getChannel();
//聚集写文件
channel.write(buffer);
channel.close(); }
}

通过散射读取固定格式的文本文件:

前提:知道文件对应的格式长度,精确的构造Buffer

public class GatheringBuffer {
public static void main(String[] args) throws IOException {
//通过聚集创建文本文件,格式为 书名作者
ByteBuffer bookBuf = ByteBuffer.wrap("java性能优化技巧".getBytes("utf-8"));
ByteBuffer autBuf = ByteBuffer.wrap("葛一鸣".getBytes("utf-8"));
int booklen = bookBuf.capacity();
int autlen = autBuf.capacity();
ByteBuffer[] buffer = new ByteBuffer[]{bookBuf ,autBuf};
File file = new File("d:/gather.txt");
//文件不存在时,创建一个新文件
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fou = new FileOutputStream(file);
FileChannel channel = fou.getChannel();
//聚集写文件
channel.write(buffer);
channel.close();
//通过散射读取文件,格式:书名作者
//构造精确的Buffer
ByteBuffer scatter_bookBuffer = ByteBuffer.allocate(booklen);
ByteBuffer scatter_autBuffer = ByteBuffer.allocate(autlen);
ByteBuffer[] scatter_Buffer = new ByteBuffer[]{scatter_bookBuffer, scatter_autBuffer};
FileInputStream fin = new FileInputStream("d:/gather.txt");
FileChannel fileChannel = fin.getChannel();
fileChannel.read(scatter_Buffer);
String bookName = new String(scatter_Buffer[0].array(),"utf-8");
String authorName = new String(scatter_Buffer[1].array(),"utf-8");
System.out.println(bookName+authorName);
}
}

结果:

java性能优化技巧葛一鸣

八)、MappedByteBuffer性能评估

性能比较:

传统的基于流的I/O操作 < NIO的ByteBuffer < NIO的MappedByteBuffer(将文件映射到内存)。

九)、直接内存访问

DirectBuffer: Buffer提供的直接访问系统物理内存的类。

普通的ByteBuffer: 在堆上分配内存,其最大内存,受最大堆限制。

DirectBuffer: 直接分配在物理内存中,并不占用堆空间。

创建DirectBuffer对象:

ByteBuffer.allocateDirect()

DirectBuffer和ByteBuffer的区别:

1.DirectBuffer的内存访问速度比ByteBuffer的快

2.创建和销毁DirectBuffer的花费远比ByteBuffer的高

DirectBuffer的使用:

在需要频繁的创建和销毁Buffer时,不宜使用DirectBuffer, 但如果能将DirectBuffer进行复用,那么,在读写的情况下,可以大幅度的改善系统的性能。

NIO流的学习以及Buffer的相关操作的更多相关文章

  1. linux系统命令学习系列8-文件相关操作touch,rm,mv,cat,head,tail命令

    上节内容: 系统和目录管理 Pwd命令 .和..目录 相对路径和绝对路径 作业:进入opt路径,分别使用相对路径方法和绝对路径方法进入到其实任意一个子目录 cd /opt 相对路径 cd rh 绝对路 ...

  2. linux学习笔记一----------文件相关操作

    一.目录结构 二.文件管理操作命令(有关文件夹操作,使用Tab键自动补全文件名(如果多个默认第一个)) 1.ls 查看目录信息:ls -l 查看目录详细信息(等价于ll 某些系统不支持) 2.pwd ...

  3. python基础学习一 字符串的相关操作

    python的字符串 在python中,字符串是以unicode编码的,所以python的字符串支持多语言 对于单个字符的编码,python提供了ord()函数获取字符的整数表示,chr()函数是把编 ...

  4. NIO流—理解Buffer、Channel概念和NIO的读写操作

    NIO流与IO流的区别 面向流与面向块 IO流是每次处理一个或多个字节,效率很慢(字符流处理的也是字节,只是对字节进行编码和解码处理). NIO流是以数据块为单位来处理,缓冲区就是用于读写的数据块.缓 ...

  5. nio的简单学习

    参考链接: http://www.iteye.com/magazines/132-Java-NIO https://www.cnblogs.com/xiaoxi/p/6576588.html http ...

  6. NIO、AIO学习历程

    今天我们以一个常见的面试题作为开始:"谈谈你对IO与NIO的理解".要回答这个问题,我们首先我要了解几个概念: NIO 同步+非阻塞 IO(BIO) 同步+阻塞 AIO 异步+非阻 ...

  7. 关于JAVA IO流的学习

    初学Java,一直搞不懂java里面的io关系,在网上找了很多大多都是给个结构图草草描述也看的不是很懂.而且没有结合到java7 的最新技术,所以自己来整理一下,有错的话请指正,也希望大家提出宝贵意见 ...

  8. (原)关于MEPG-2中的TS流数据格式学习

    关于MEPG-2中的TS流数据格式学习 Author:lihaiping1603 原创:http://www.cnblogs.com/lihaiping/p/8572997.html 本文主要记录了, ...

  9. Scala学习(三)----数组相关操作

    数组相关操作 摘要: 本篇主要学习如何在Scala中操作数组.Java和C++程序员通常会选用数组或近似的结构(比如数组列表或向量)来收集一组元素.在Scala中,我们的选择更多,不过现在我们先假定不 ...

随机推荐

  1. codeforce -14A A. Letter

    A. Letter time limit per test 1 second memory limit per test 64 megabytes input standard input outpu ...

  2. 函数进阶(三) day14

    目录 昨日内容 迭代器 可迭代对象 迭代器对象 for循环原理 三元表达式 列表推导式 字典生成式 zip 生成器表达式 生成器 yield 递归 今日内容 匿名函数 内置方法 掌握 了解 异常处理 ...

  3. unity 动画 音频播放

    采用Unity进行音频动画的播放时最常用的技术,在此进行一下简单讲解与应用. (一)动画播放(本文采用animation进行验证,关于animation和animator区别可问度娘,在此不做赘述) ...

  4. django-表单之新增字段和设置css属性(四)

    要注意是模板元素 和 表单元素的对应.

  5. 设计模式C++描述----03.工厂(Factory)模式

    工厂模式属于创建型模式,大致可以分为三类,简单工厂模式.工厂方法模式.抽象工厂模式. 一. 简单工厂模式 简单工厂模式,它的主要特点是需要在工厂类中做判断,从而创造相应的产品.当增加新的产品时,就需要 ...

  6. AQL基本语法

    目录: 基本的CRUD 插入 检索 更新 删除 匹配文件 排序和限制 限制 排序 组合 图操作 地理位置查询 一.数据预览 本次使用的数据共有43条,每条数据包含姓氏.年龄.活动状态和特征等六个字段 ...

  7. Google Tensorflow 迁移学习 Inception-v3

    附上代码加数据地址 https://github.com/Liuyubao/transfer-learning ,欢迎参考. 一.Inception-V3模型 1.1 详细了解模型可参考以下论文: [ ...

  8. beanFactory 设计模式 Bean 生命周期的胡言乱语,哈哈

    写在前面的话 适用读者:有一定经验的,本文不适合初学者,因为可能不能理解我在说什么 文章思路:不会一开始就像别的博客文章那样,Bean 的生命周期,源码解读(给你贴一大堆的源码).个人觉得应该由问题驱 ...

  9. [UWP]使用SpringAnimation创建有趣的动画

    1. 什么是自然动画 最近用弹簧动画(SpringAnimation)做了两个番茄钟,关于弹簧动画官方文档已经介绍得够详细了,这篇文章就摘录一些官方文档核心内容. 在传统UI中,关键帧动画(KeyFr ...

  10. 爬虫学习--常用的正则表达式 Day3

    在做爬虫经常遇到需要用正则校验数据时候,往往是在网上去找很久,结果找来的还是不很符合要求.所以我最近把开发中常用的一些正则表达式整理了一下,给自己留个底,也给朋友们做个参考. 一.校验数字的表达式 1 ...