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. 02jmeter-函数助手使用

    示例:__Random函数 1.打开函数助手,并按提示写入value 2.引用.复制出${__Random(1,99,gp)}放到需要引用的地方 3.请求成功后可通过debug sampler查看变量 ...

  2. 百万年薪python之路 -- 面向对象之三大特性

    1.面向对象之三大特性 1.1封装 封装:就是把一堆代码和数据,放在一个空间,并且可以使用 对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封 ...

  3. Activity 学习(二) 搭建第一个Activity流程框架

    本次示例使用的IDER测试完成 测试背景 : xx饿了去饭店吃饭  需要先和服务员点餐  点完餐后服务员将菜品传递给厨师制作  制作完成后吃饱 一 :创建流程图 创建上一篇测试成功出现的BpmnFil ...

  4. JVM学习记录2--垃圾回收算法

    首先要明确,垃圾回收管理jvm的堆内存,方法区是堆内存的一部分,所以也是. 而本地方法栈,虚拟机栈,程序计数器随着线程开始而产生,线程的结束而消亡,是不需要垃圾回收的. 1. 判断对象是否可以被回收 ...

  5. java秀发入门到优雅秃头路线导航【教学视频+博客+书籍整理】

    目录 一.Java基础 二.关于JavaWeb基础 三.关于数据库 四.关于ssm框架 五.关于数据结构与算法 六.关于开发工具idea 七.关于项目管理工具Mawen.Git.SVN.Gradle. ...

  6. Java匹马行天下之一顿操作猛如虎,框架作用知多少?

    流行框架: 框架就是开发人员定义好的一套模板,程序员只需要往模板中添加响应的代码即可,填完代码,项目就完成了.所以框架存在的意义以及我们学习框架的目的就是想办法能够让程序员快速的完成整个项目的开发.理 ...

  7. Jsp的四大域对象

    Jsp     Jsp的四大域对象   作用范围 特殊之处   pageContext 当前jsp页面,当转发就失效 可以获取其他域对象中的值   request 一次请求,转发公用request,重 ...

  8. 关于php注释那些事

    代码注释的作用  --- 为自己,也为别人. 永远不要过于相信自己的理解力!当你思路通畅,思如泉涌,进入编程境界,你可以很流畅的实现某个功能,但这种一泻千里的流畅可能只停留在了当时的状态.当你几个月, ...

  9. TypeError: expected string or bytes-like object

    在写Python代码的时候,遇到了"TypeError: a bytes-like object is required, not 'str'"错误,此处实验机器的Python环境 ...

  10. 学习笔记11全局处理程序global.asax

    *全局处理程序Clobal.asax只能叫这个名字,不能修改文件名,如果网站没有的话,可以自己添加. *Application[]类似于session,是全局的,Application["k ...