- 浏览: 260197 次
- 性别:
- 来自: 济南
文章分类
最新评论
-
MR3CHEN:
gaojiehigh 写道正在找这样的方法,我不过发现了一个问 ...
Java删除文件夹以及文件夹下的子目录与文件 -
gaojiehigh:
正在找这样的方法,我不过发现了一个问题,嘿嘿
[img][/i ...
Java删除文件夹以及文件夹下的子目录与文件 -
mimang2007110:
这个方法很实用,刚才适用了一下,挺好的,多谢
Java删除文件夹以及文件夹下的子目录与文件 -
sblig:
int icount = toKenizer.countT ...
Java拆分字符串返回数组 -
haiyangyiba:
文件夹中有中文文件不行,
Java解压缩ZIP文件同时包含Jar包解决ZIP包中含有中文名称信息的文件
1.3.3 MultipartStream类
MultipartStream类用来对上传的请求输入流进行解析,它是整个Apache上传组件中最复杂的类。
1.设计思想
MultipartStream类中定义了一个byte[]类型的boundary成员变量,这个成员变量用于保存图1.3中的各个数据分区之间的分隔界线,每个分区
分别代表一个表单字段的信息。图1.3中的每个分区又可以分为描述头部分和主体部分,MultipartStream类中定义了一个readHeaders()方法来
读取描述头部分的内容,MultipartStream类中定义了一个readBodyData(OutputStream output)方法来读取主体部分的内容,并将这些内容写
入到一个作为参数传入进来的输出流对象中。readBodyData方法接收的参数output对象在应用中的实际类型是DeferredFileOutputStream,这
个对象又是保存在DefaultFileItem类对象中的一个成员变量,这样,readBodyData方法就可以将一个分区的主体部分的数据写入到
DefaultFileItem类对象中。
因为图1.3中的实体内容内部的字段分隔界线是在content-type头中指定的字段分隔界线前面增加了两个减号(-)字符而形成的,而每个字段
分隔界线与它前面内容之间还进行了换行,这个换行并不属于表单字段元素的内容。所以,MultipartStream类中的成员变量boundary中存储的
字节数组并不是直接从content-type头的boundary参数中获得的字符序列,而是在boundary参数中指定的字符序列前面增加了四个字节,依次
是‘\n’、‘\r’、‘-’和‘-’。MultipartStream类中定义了一个readBoundary()方法来读取和识别各个字段之间分隔界线,有一点特殊的
是,图1.3中的第一个分隔界线前面没有回车换行符,它是无法与成员变量boundary中的数据相匹配的,所以无法调用readBoundary()方法进行
读取,而是需要进行特殊处理,其后的每个分隔界线都与boundary中的数据相匹配,可以直接调用readBoundary()方法进行读取。在本章的后
面部分,如果没有特别说明,所说的分隔界线都是指成员变量boundary中的数据内容。
RFC 1867格式规范规定了描述头和主体部分必须用一个空行进行分隔,如图1.3所示,也就是描述头和主体部分使用“\n”、“\r”、“\n”、
“\r”这四个连续的字节内容进行分隔。MultipartStream类的设计者为了简化编程,在readHeaders()方法中将“\n”、“\r”、“\n”、
“\r”这四个连续的字节内容连同描述头一起进行读取。readHeaders()方法在读取数据的过程中,当它发现第一个‘\n’、‘\r’、‘\n’、
‘\r’ 连续的字节序列时就会返回,即使主体部分正好也包含了“\n”、“\r”、“\n”、“\r”这四个连续的字节内容,但是,它们只会被
随后调用的readBodyData方法作为主体内容读取,永远不会被readHeaders()方法读取到,所以,它们不会与作为描述头和主体部分的分隔字符
序列发生冲突。
由于readHeaders()方法读取了一个分区中的主体部分前面的所有内容(包括它前面的换行),而它与下一个分区之间的分隔界线前面的换行又
包含在了成员变量boundary中,这个换行将被readBoundary()方法读取,所以,夹在readheaders()方法读取的内容和readBoundary()方法读取
的内容之间的数据全部都属于表单字段元素的内容了,因此,读取分区中的主体部分的readBodyData(OutputStream output)方法不需要进行特
别的处理,它直接将读取的数据写入到DefaultFileItem类对象中封装的DeferredFileOutputStream属性对象中即可。
2. 构造方法
MultipartStream类中的一个主要的构造方法的语法定义如下:
public (InputStream input, byte[] boundary, int bufSize)
其中,参数input是指从HttpServetRequest请求对象中获得的字节输入流对象,参数boundary是从请求消息头中获得的未经处理的分隔界线,
bufSize指定图1.10中的buffer缓冲区字节数组的长度,默认值是4096个字节。这个构造方法的源代码如下:
public MultipartStream(InputStream input, byte[] boundary, int bufSize)
{
// 初始化成员变量
this.input = input;
this.bufSize = bufSize;
this.buffer = new byte[bufSize];
this.boundary = new byte[boundary.length + 4];
this.boundaryLength = boundary.length + 4;
//buffer缓冲区中保留给下次读取的最大字节个数
this.keepRegion = boundary.length + 3;
this.boundary[0] = 0x0D; //‘\n’的16进制形式
this.boundary[1] = 0x0A; //‘\r’的16进制形式
this.boundary[2] = 0x2D; //‘-’的16进制形式
this.boundary[3] = 0x2D;
//在成员变量boundary中生成最终的分隔界线
System.arraycopy (boundary, 0, this.boundary, 4, boundary.length);
head = 0; // 成员变量,表示正在处理的这个字节在buffer中的位置指针
tail = 0; // 成员变量,表示实际读入到buffer中的字节个数
}
3. readByte方法
MultipartStream类中的readByte()方法从字节数组缓冲区buffer中读一个字节,当buffer缓冲区中没有更多的数据可读时,该方法会自动从输
入流中读取一批新的字节数据来重新填充buffer缓冲区。readByte()方法的源代码如下:
public byte readByte () throws IOException
{
// 判断是否已经读完了buffer缓冲区中的所有数据
if (head == tail)
{
head = 0;
//读入新的数据内容来填充buffer缓冲区
tail = input.read(buffer, head, bufSize);
if (tail == -1)
{
throw new IOException("No more data is available ");
}
}
return buffer[head++];// 返回当前字节,head++
}
其中,head变量是MultipartStream类中定义的一个int类型的成员变量,它用于表示正在读取的字节在buffer数组缓冲区中的位置;tail变量
也是MultipartStream类中定义的一个int类型的成员变量,它用于表示当前buffer数组缓冲区装入的实际字节内容的长度。在MultipartStream
类中主要是通过控制成员变量head的值来控制对buffer缓冲区中的数据的读取和直接跳过某段数据,通过比较head与tail变量的值了解是否需
要向buffer缓冲区中装入新的数据内容。当每次向buffer缓冲区中装入新的数据内容后,都应该调整成员变量head和tail的值。
4. arrayequals静态方法
MultipartStream类中定义了一个的arrayequals静态方法,用于比较两个字节数组中的前面一部分内容是否相等,相等返回true,否则返回
false。arrayequals方法的源代码如下,参数count指定了对字节数组中的前面几个字节内容进行比较:
public static boolean arrayequals(byte[] a, byte[] b,int count)
{
for (int i = 0; i < count; i++)
{
if (a[i] != b[i])
{
return false;
}
}
return true;
}
5. findByte方法
MultipartStream类中的findByte()方法从字节数组缓冲区buffer中的某个位置开始搜索一个特定的字节数据,如果找到了,则返回该字节在
buffer缓冲区中的位置,不再继续搜索,如果没有找到,则返回-1。findByte方法的源代码如下,参数pos制定了不搜索的起始位置值,value
是要搜索的字节数据:
protected int findByte(byte value,int pos)
{
for (int i = pos; i < tail; i++)
{
if (buffer[i] == value)
{
return i; // 找到该值,findByte方法返回
}
}
return - 1;
}
如果程序需要在buffer缓冲区中多次搜索某个特定的字节数据,那就可以循环调用findByte方法,只是在每次调用findByte方法时,必须不断
地改变参数pos的值,让pos的值等于上次调用findByte的返回值,直到findByte方法返回-1时为止,如图1.13所示。
图1.13
6. findSeparator方法
MultipartStream类中的findSeparator方法用于从字节数组缓冲区buffer中查找成员变量boundary中定义的分隔界线,并返回分隔界线的第一
个字节在buffer缓冲区中的位置,如果在buffer缓冲区中没有找到分隔界线,则返回-1。
findSeparator方法内部首先调用findByte方法在buffer缓冲区中搜索分隔界线boundary的第一个字节内容,如果没有找到,则说明buffer缓冲
区中没有包含分隔界线;如果findByte方法在buffer缓冲区中找到了分隔界线boundary的第一个字节内容,findSeparator方法内部接着确定该
字节及随后的字节序列是否确实是分隔界线。findSeparator方法内部循环调用findByte方法,直到找到分隔界线或者findByte方法已经查找到
了buffer缓冲区中的最后boundaryLength -1个字节。findSeparator方法内部为什么调用findByte方法查找到buffer缓冲区中的最后
boundaryLength-1个字节时就停止查找呢?这是为了解决如图1.10所示的buffer缓冲区中装入了分隔界线的部分内容的特殊情况,所以在
findSeparator()方法中不要搜索buffer缓冲区中的最后的boundaryLength -1个字节,而是把buffer缓冲区中的最后这boundaryLength -1个字
节作为保留区,在下次读取buffer缓冲区时将这些保留的字节数据重新填充到buffer缓冲区的开始部分。findSeparator方法的源代码如下:
protected int findSeparator()
{
int first;
int match = 0;
int maxpos = tail - boundaryLength;//在buffer中搜索的最大位置
for (first = head;(first <= maxpos) && (match != boundaryLength);
first++)
{
//在buffer缓冲区中寻找boundary的第一个字节
first = findByte(boundary[0], first);
/*buffer中找不到boundary[0]或者boundary[0]位于保留区中,
则可以判断buffer中不存在分隔界线*/
if (first == -1 || (first > maxpos))
{
return -1;
}
//确定随后的字节序列是否确实是分隔界线的其他字节内容
for (match = 1; match < boundaryLength; match++)
{
if (buffer[first + match] != boundary[match])
{
break;
}
}
}
// 当前buffer中找到boundary,返回第一个字节所在位置值
if (match == boundaryLength)
{
return first - 1;
}
return -1; // 当前buffer中没找到boundary,返回-1
}
图1.14中描述了findSeparator方法内部定义的各个变量的示意图。
图1.14
findSeparator方法内部的代码主要包括如下三个步骤:
(1)循环调用findByte(boundary[0], first)找到buffer缓冲区中的与boundary[0]相同的字节的位置,并将位置记录在first变量中。
(2)比较buffer缓冲区中的first后的boundaryLength-1个字节序列是否与boundary中的其他字节序列相同。如果不同,说明这个first变量指
向的字节不是分隔界线的开始字节,跳出内循环,将first变量加1后继续外循环调用findByte方法;如果相同,说明在当前缓冲区buffer中找
到了分隔界线,内循环正常结束,此时match变量的值为boundaryLength,接着执行外循环将first变量加1,然后执行外循环的条件判断,由于
match != boundaryLength条件不成立,外循环也随之结束。
(3)判断match是否等于boundaryLength,如果等于则说明找到了分隔界线,此时返回成员变量boundary的第一个字节在缓冲区buffer中位置
,由于第(2)中将first加1了,所以这里的返回值应该是first-1;如果不等,说明当前缓冲区huffer中没有分隔界线,返回-1。
7. readHeaders方法
MultipartStream类中的readHeaders方法用于读取一个分区的描述头部分,并根据DiskFileUpload类的setHeaderEncoding方法设定的字符集编
码将描述头部分转换成一个字符串返回。
在调用readHeaders方法之前时,程序已经调用了findSeparator方法找到了分隔界线和读取了分隔界线前面的内容,此时MultipartStream类中
的成员变量head指向了buffer缓冲区中的分隔界线boundary的第一个字节,程序接着应调用readBoundary方法跳过分隔界线及其随后的回车换
行两个字节,以保证在调用readHeaders方法时,成员变量head已经指向了分区的描述头的第一个字节。在readHeaders方法内部,直接循环调
用readByte方法读取字节数据,并把读到的数据存储在一个字节数组输出流中,直到读取到了连续的两次回车换行字符,就认为已经读取完了
描述头的全部内容,此时成员变量head将指向分区中的主体内容的第一个字节。readHeaders()方法的源代码如下:
public String readHeaders()throws MalformedStreamException
{
int i = 0;
//从下面的代码看来,这里定义成一个byte即可,不用定义成byte数组
byte b[] = new byte[1];
//用于临时保存描述头信息的字节数组输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//对描述头部分的数据内容过大进行限制处理
int sizeMax = HEADER_PART_SIZE_MAX;
int size = 0;
while (i < 4)
{
try
{
b[0] = readByte(); }
catch (IOException e)
{
throw new MalformedStreamException("Stream ended unexpectedly " );
}
size++;
//静态常量HEADER_SEPARATOR的值为:{0x0D, 0x0A, 0x0D, 0x0A}
if (b[0] == HEADER_SEPARATOR[i])
{
i++;
}
else
{
i = 0;
}
if (size <= sizeMax)
{
baos.write(b[0]); // 将当前字节存入缓冲流
}
}
String headers = null; // 找到HEADER_SEPARATOR后,获取描述头
if (headerEncoding != null)
{
try
{
headers = baos.toString(headerEncoding);
}
catch (UnsupportedEncodingException e)
{
headers = baos.toString();
}
}
else
{
headers = baos.toString();
}
return headers;
}
readHeaders方法循环调用readByte()方法逐个读取buffer缓冲区中的字节,并将读取的字节与HEADER_SEPARATOR ={‘\n’,‘\r’,‘\n’
,‘\r’}的第一个字节进行比较,如果这个字节等于HEADER_SEPARATOR的首字节‘\n’,则循环控制因子i加1,这样,下次调用readByte()方
法读取的字节将与HEADER_SEPARATOR中的第二字节比较,如果相等,则依照这种方式比较后面的字节内容,如果连续读取到了
HEADER_SEPARATOR字节序列,则循环语句结束。readHeaders方法将读取到的每个正常字节写入到了一个字节数组输出流中,其中也包括作为描
述头与主体内容之间的分隔序列HEADER_SEPARATOR中的字节数据。由于readByte()方法会自动移动head变量的值和自动向缓冲区buffer中载入
数据,所以,readHeaders方法执行完以后,成员变量head指向分区主体部分的首字节。readHeaders方法最后将把存入字节数组输出流中的字
节数据按指定字符集编码转换成字符串并返回,该字符串就是描述头字符串。
8. readBodyData方法
MultipartStream类中的readBodyData方法用于把主体部分的数据写入到一个输出流对象中,并返回写入到输出流中的字节总数。当调用
readBodyData方法前,成员变量head已经指向了分区的主体部分的首字节,readBodyData方法调用完成后,成员变量head指向分区分隔界线的
首字节。readBodyData方法中需要调用findSeparator方法找出下一个分区分隔界线的首字节位置,才能知道这次读取的分区主体内容的结束位
置。从分区主体部分的首字节开始,直到在findSeparator方法找到的下一个分区分隔界线前的所有数据都是这个分区的主体部分的数据,
readBodyData方法需要把这些数据都写到输出流output对象中。如果findSeparator方法在buffer缓冲区中没有找到分区分隔界线,
readBodyData方法还必须向buffer缓冲区中装入新的数据内容后继续调用findSeparator方法进行处理。在向buffer缓冲区中装入新的数据内容
时,必须先将上次保留在buffer缓冲区中的内容转移进新buffer缓冲区的开始处。readBodyData方法的源代码如下,传递给readBodyData方法
的参数实际上是一个DeferredFileOutputStream类对象:
public int readBodyData(OutputStream output)
throws MalformedStreamException,IOException
{
// 用于控制循环的变量
boolean done = false;
int pad;
int pos;
int bytesRead;
// 写入到输出流中的字节个数
int total = 0;
while (!done)
{
pos = findSeparator();// 搜索分隔界线
if (pos != -1) //缓冲区buffer中包含有分隔界线
{
output.write(buffer, head, pos - head);
total += pos - head;
head = pos;//head变量跳过主体数据,指向分隔界线的首字节
done = true;// 跳出循环
}
else //缓冲区buffer中没有包含分隔界线
{
/*根据缓冲区中未被readHeaders方法读取的数据内容是否大于图1.4中的
保留区的大小,来决定保留到下一次buffer缓冲区中的字节个数
*/
if (tail - head > keepRegion)
{
pad = keepRegion;
}
else
{
pad = tail - head;
}
output.write(buffer, head, tail - head - pad);
total += tail - head - pad;//统计写入到输出流中的字节个数
/*将上一次buffer缓冲区中的未处理的数据转移到
下一次buffer缓冲区的开始位置
*/
System.arraycopy(buffer, tail - pad, buffer, 0, pad);
head = 0; //让head变量指向缓冲区的开始位置
//向buffer缓冲区中载入新的数据
bytesRead = input.read(buffer, pad, bufSize - pad);
if (bytesRead != -1)
{
//设置buffer缓冲区中的有效字节的个数
tail = pad + bytesRead;
}
else
{
/*还没有找到分隔界线,输入流就结束了,输入流中的数据格式
显然不正确,保存缓冲区buffer中还未处理的数据后抛出异常
*/
output.write(buffer, 0, pad);
output.flush();
total += pad;
throw new MalformedStreamException
("Stream ended unexpectedly ");
}
}
}
output.flush();
return total;
}
9. discardBodyData方法
MultipartStream类中的discardBodyData方法用来跳过主体数据,它与readBodyData方法非常相似,不同之处在于readBodyData方法把数据写
入到一个输出流中,而discardBodyData方法是把数据丢弃掉。discardBodyData方法返回被丢掉的字节个数,方法调用完成后成员变量head指
向下一个分区分隔界线的首字节。MultipartStream类中定义discardBodyData这个方法,是为了忽略主体内容部分的第一个分隔界线前面的内
容,按照MIME规范,消息头和消息体之间的分隔界线前面可以有一些作为注释信息的内容,discardBodyData就是为了抛弃这些注释信息而提供
的。discardBodyData方法的源代码如下:
public int discardBodyData() throws MalformedStreamException,IOException
{
boolean done = false;
int pad;
int pos;
int bytesRead;
int total = 0;
while (!done)
{
pos = findSeparator();
if (pos != -1)
{
total += pos - head;
head = pos;
done = true;
}
else
{
if (tail - head > keepRegion)
{
pad = keepRegion;
}
else
{
pad = tail - head;
}
total += tail - head - pad;
System.arraycopy(buffer, tail - pad, buffer, 0, pad);
head = 0;
bytesRead = input.read(buffer, pad, bufSize - pad);
if (bytesRead != -1)
{
tail = pad + bytesRead;
}
else
{
total += pad;
throw new MalformedStreamException
("Stream ended unexpectedly ");
}
}
}
return total;
}
10. readBoundary方法
对于图1.3中的每一个分区的解析处理,程序首先要调用readHeaders方法读取描述头,接着要调用readBodyData(OutputStream output)读取主
体数据,这样就完成了一个分区的解析。readBodyData方法内部调用findSeparator方法找到了分隔界线,然后读取分隔界线前面的内容,此时
MultipartStream类中的成员变量head指向了buffer缓冲区中的分隔界线boundary的第一个字节。findSeparator方法只负责寻找分隔界线
boundary在缓冲区buffer中的位置,不负责从buffer缓冲区中读走分隔界线的字节数据。在调用readBodyData方法之后,程序接着应该让成员
变量head跳过分隔界线,让它指向下一个分区的描述头的第一个字节,才能调用readHeaders方法去读取下一个分区的描述头。
MultipartStream类中定义了一个readBoundary方法,用于让成员变量head跳过分隔界线,让它指向下一个分区的描述头的第一个字节。对于图
1.3中的最后的分隔界线,它比其他的分隔界线后面多了两个“-”字符,而其他分隔界线与下一个分区的内容之间还有一个回车换行,所以,
readBoundary方法内部跳过分隔界线后,还需要再读取两个字节的数据,才能让成员变量head指向下一个分区的描述头的第一个字节。
readBoundary方法内部读取分隔界线后面的两个字节数据后,根据它们是回车换行、还是两个“-”字符,来判断这个分隔界线是下一个分区的
开始标记,还是整个请求消息的实体内容的结束标记。如果readBoundary方法发现分隔界线是下一个分区的开始标记,那么它返回true,否则
返回false。readBoundary()方法的源代码如下:
public boolean readBoundary()throws MalformedStreamException
{
byte[] marker = new byte[2];
boolean nextChunk = false;
head += boundaryLength; // 跳过分隔界线符
try
{
marker[0] = readByte();
marker[1] = readByte();
// 静态常量STREAM_TERMINATOR ={‘-’、‘-’}
if (arrayequals(marker, STREAM_TERMINATOR, 2))
{
nextChunk = false;
}
// 静态常量FIELD_SEPARATOR ={‘/n’、‘/r’}
else if (arrayequals(marker, FIELD_SEPARATOR, 2))
{
nextChunk = true;
}
else
{
/*如果读到的既不是回车换行,又不是两个减号,
说明输入流有问题,则抛
MultipartStream类用来对上传的请求输入流进行解析,它是整个Apache上传组件中最复杂的类。
1.设计思想
MultipartStream类中定义了一个byte[]类型的boundary成员变量,这个成员变量用于保存图1.3中的各个数据分区之间的分隔界线,每个分区
分别代表一个表单字段的信息。图1.3中的每个分区又可以分为描述头部分和主体部分,MultipartStream类中定义了一个readHeaders()方法来
读取描述头部分的内容,MultipartStream类中定义了一个readBodyData(OutputStream output)方法来读取主体部分的内容,并将这些内容写
入到一个作为参数传入进来的输出流对象中。readBodyData方法接收的参数output对象在应用中的实际类型是DeferredFileOutputStream,这
个对象又是保存在DefaultFileItem类对象中的一个成员变量,这样,readBodyData方法就可以将一个分区的主体部分的数据写入到
DefaultFileItem类对象中。
因为图1.3中的实体内容内部的字段分隔界线是在content-type头中指定的字段分隔界线前面增加了两个减号(-)字符而形成的,而每个字段
分隔界线与它前面内容之间还进行了换行,这个换行并不属于表单字段元素的内容。所以,MultipartStream类中的成员变量boundary中存储的
字节数组并不是直接从content-type头的boundary参数中获得的字符序列,而是在boundary参数中指定的字符序列前面增加了四个字节,依次
是‘\n’、‘\r’、‘-’和‘-’。MultipartStream类中定义了一个readBoundary()方法来读取和识别各个字段之间分隔界线,有一点特殊的
是,图1.3中的第一个分隔界线前面没有回车换行符,它是无法与成员变量boundary中的数据相匹配的,所以无法调用readBoundary()方法进行
读取,而是需要进行特殊处理,其后的每个分隔界线都与boundary中的数据相匹配,可以直接调用readBoundary()方法进行读取。在本章的后
面部分,如果没有特别说明,所说的分隔界线都是指成员变量boundary中的数据内容。
RFC 1867格式规范规定了描述头和主体部分必须用一个空行进行分隔,如图1.3所示,也就是描述头和主体部分使用“\n”、“\r”、“\n”、
“\r”这四个连续的字节内容进行分隔。MultipartStream类的设计者为了简化编程,在readHeaders()方法中将“\n”、“\r”、“\n”、
“\r”这四个连续的字节内容连同描述头一起进行读取。readHeaders()方法在读取数据的过程中,当它发现第一个‘\n’、‘\r’、‘\n’、
‘\r’ 连续的字节序列时就会返回,即使主体部分正好也包含了“\n”、“\r”、“\n”、“\r”这四个连续的字节内容,但是,它们只会被
随后调用的readBodyData方法作为主体内容读取,永远不会被readHeaders()方法读取到,所以,它们不会与作为描述头和主体部分的分隔字符
序列发生冲突。
由于readHeaders()方法读取了一个分区中的主体部分前面的所有内容(包括它前面的换行),而它与下一个分区之间的分隔界线前面的换行又
包含在了成员变量boundary中,这个换行将被readBoundary()方法读取,所以,夹在readheaders()方法读取的内容和readBoundary()方法读取
的内容之间的数据全部都属于表单字段元素的内容了,因此,读取分区中的主体部分的readBodyData(OutputStream output)方法不需要进行特
别的处理,它直接将读取的数据写入到DefaultFileItem类对象中封装的DeferredFileOutputStream属性对象中即可。
2. 构造方法
MultipartStream类中的一个主要的构造方法的语法定义如下:
public (InputStream input, byte[] boundary, int bufSize)
其中,参数input是指从HttpServetRequest请求对象中获得的字节输入流对象,参数boundary是从请求消息头中获得的未经处理的分隔界线,
bufSize指定图1.10中的buffer缓冲区字节数组的长度,默认值是4096个字节。这个构造方法的源代码如下:
public MultipartStream(InputStream input, byte[] boundary, int bufSize)
{
// 初始化成员变量
this.input = input;
this.bufSize = bufSize;
this.buffer = new byte[bufSize];
this.boundary = new byte[boundary.length + 4];
this.boundaryLength = boundary.length + 4;
//buffer缓冲区中保留给下次读取的最大字节个数
this.keepRegion = boundary.length + 3;
this.boundary[0] = 0x0D; //‘\n’的16进制形式
this.boundary[1] = 0x0A; //‘\r’的16进制形式
this.boundary[2] = 0x2D; //‘-’的16进制形式
this.boundary[3] = 0x2D;
//在成员变量boundary中生成最终的分隔界线
System.arraycopy (boundary, 0, this.boundary, 4, boundary.length);
head = 0; // 成员变量,表示正在处理的这个字节在buffer中的位置指针
tail = 0; // 成员变量,表示实际读入到buffer中的字节个数
}
3. readByte方法
MultipartStream类中的readByte()方法从字节数组缓冲区buffer中读一个字节,当buffer缓冲区中没有更多的数据可读时,该方法会自动从输
入流中读取一批新的字节数据来重新填充buffer缓冲区。readByte()方法的源代码如下:
public byte readByte () throws IOException
{
// 判断是否已经读完了buffer缓冲区中的所有数据
if (head == tail)
{
head = 0;
//读入新的数据内容来填充buffer缓冲区
tail = input.read(buffer, head, bufSize);
if (tail == -1)
{
throw new IOException("No more data is available ");
}
}
return buffer[head++];// 返回当前字节,head++
}
其中,head变量是MultipartStream类中定义的一个int类型的成员变量,它用于表示正在读取的字节在buffer数组缓冲区中的位置;tail变量
也是MultipartStream类中定义的一个int类型的成员变量,它用于表示当前buffer数组缓冲区装入的实际字节内容的长度。在MultipartStream
类中主要是通过控制成员变量head的值来控制对buffer缓冲区中的数据的读取和直接跳过某段数据,通过比较head与tail变量的值了解是否需
要向buffer缓冲区中装入新的数据内容。当每次向buffer缓冲区中装入新的数据内容后,都应该调整成员变量head和tail的值。
4. arrayequals静态方法
MultipartStream类中定义了一个的arrayequals静态方法,用于比较两个字节数组中的前面一部分内容是否相等,相等返回true,否则返回
false。arrayequals方法的源代码如下,参数count指定了对字节数组中的前面几个字节内容进行比较:
public static boolean arrayequals(byte[] a, byte[] b,int count)
{
for (int i = 0; i < count; i++)
{
if (a[i] != b[i])
{
return false;
}
}
return true;
}
5. findByte方法
MultipartStream类中的findByte()方法从字节数组缓冲区buffer中的某个位置开始搜索一个特定的字节数据,如果找到了,则返回该字节在
buffer缓冲区中的位置,不再继续搜索,如果没有找到,则返回-1。findByte方法的源代码如下,参数pos制定了不搜索的起始位置值,value
是要搜索的字节数据:
protected int findByte(byte value,int pos)
{
for (int i = pos; i < tail; i++)
{
if (buffer[i] == value)
{
return i; // 找到该值,findByte方法返回
}
}
return - 1;
}
如果程序需要在buffer缓冲区中多次搜索某个特定的字节数据,那就可以循环调用findByte方法,只是在每次调用findByte方法时,必须不断
地改变参数pos的值,让pos的值等于上次调用findByte的返回值,直到findByte方法返回-1时为止,如图1.13所示。
图1.13
6. findSeparator方法
MultipartStream类中的findSeparator方法用于从字节数组缓冲区buffer中查找成员变量boundary中定义的分隔界线,并返回分隔界线的第一
个字节在buffer缓冲区中的位置,如果在buffer缓冲区中没有找到分隔界线,则返回-1。
findSeparator方法内部首先调用findByte方法在buffer缓冲区中搜索分隔界线boundary的第一个字节内容,如果没有找到,则说明buffer缓冲
区中没有包含分隔界线;如果findByte方法在buffer缓冲区中找到了分隔界线boundary的第一个字节内容,findSeparator方法内部接着确定该
字节及随后的字节序列是否确实是分隔界线。findSeparator方法内部循环调用findByte方法,直到找到分隔界线或者findByte方法已经查找到
了buffer缓冲区中的最后boundaryLength -1个字节。findSeparator方法内部为什么调用findByte方法查找到buffer缓冲区中的最后
boundaryLength-1个字节时就停止查找呢?这是为了解决如图1.10所示的buffer缓冲区中装入了分隔界线的部分内容的特殊情况,所以在
findSeparator()方法中不要搜索buffer缓冲区中的最后的boundaryLength -1个字节,而是把buffer缓冲区中的最后这boundaryLength -1个字
节作为保留区,在下次读取buffer缓冲区时将这些保留的字节数据重新填充到buffer缓冲区的开始部分。findSeparator方法的源代码如下:
protected int findSeparator()
{
int first;
int match = 0;
int maxpos = tail - boundaryLength;//在buffer中搜索的最大位置
for (first = head;(first <= maxpos) && (match != boundaryLength);
first++)
{
//在buffer缓冲区中寻找boundary的第一个字节
first = findByte(boundary[0], first);
/*buffer中找不到boundary[0]或者boundary[0]位于保留区中,
则可以判断buffer中不存在分隔界线*/
if (first == -1 || (first > maxpos))
{
return -1;
}
//确定随后的字节序列是否确实是分隔界线的其他字节内容
for (match = 1; match < boundaryLength; match++)
{
if (buffer[first + match] != boundary[match])
{
break;
}
}
}
// 当前buffer中找到boundary,返回第一个字节所在位置值
if (match == boundaryLength)
{
return first - 1;
}
return -1; // 当前buffer中没找到boundary,返回-1
}
图1.14中描述了findSeparator方法内部定义的各个变量的示意图。
图1.14
findSeparator方法内部的代码主要包括如下三个步骤:
(1)循环调用findByte(boundary[0], first)找到buffer缓冲区中的与boundary[0]相同的字节的位置,并将位置记录在first变量中。
(2)比较buffer缓冲区中的first后的boundaryLength-1个字节序列是否与boundary中的其他字节序列相同。如果不同,说明这个first变量指
向的字节不是分隔界线的开始字节,跳出内循环,将first变量加1后继续外循环调用findByte方法;如果相同,说明在当前缓冲区buffer中找
到了分隔界线,内循环正常结束,此时match变量的值为boundaryLength,接着执行外循环将first变量加1,然后执行外循环的条件判断,由于
match != boundaryLength条件不成立,外循环也随之结束。
(3)判断match是否等于boundaryLength,如果等于则说明找到了分隔界线,此时返回成员变量boundary的第一个字节在缓冲区buffer中位置
,由于第(2)中将first加1了,所以这里的返回值应该是first-1;如果不等,说明当前缓冲区huffer中没有分隔界线,返回-1。
7. readHeaders方法
MultipartStream类中的readHeaders方法用于读取一个分区的描述头部分,并根据DiskFileUpload类的setHeaderEncoding方法设定的字符集编
码将描述头部分转换成一个字符串返回。
在调用readHeaders方法之前时,程序已经调用了findSeparator方法找到了分隔界线和读取了分隔界线前面的内容,此时MultipartStream类中
的成员变量head指向了buffer缓冲区中的分隔界线boundary的第一个字节,程序接着应调用readBoundary方法跳过分隔界线及其随后的回车换
行两个字节,以保证在调用readHeaders方法时,成员变量head已经指向了分区的描述头的第一个字节。在readHeaders方法内部,直接循环调
用readByte方法读取字节数据,并把读到的数据存储在一个字节数组输出流中,直到读取到了连续的两次回车换行字符,就认为已经读取完了
描述头的全部内容,此时成员变量head将指向分区中的主体内容的第一个字节。readHeaders()方法的源代码如下:
public String readHeaders()throws MalformedStreamException
{
int i = 0;
//从下面的代码看来,这里定义成一个byte即可,不用定义成byte数组
byte b[] = new byte[1];
//用于临时保存描述头信息的字节数组输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//对描述头部分的数据内容过大进行限制处理
int sizeMax = HEADER_PART_SIZE_MAX;
int size = 0;
while (i < 4)
{
try
{
b[0] = readByte(); }
catch (IOException e)
{
throw new MalformedStreamException("Stream ended unexpectedly " );
}
size++;
//静态常量HEADER_SEPARATOR的值为:{0x0D, 0x0A, 0x0D, 0x0A}
if (b[0] == HEADER_SEPARATOR[i])
{
i++;
}
else
{
i = 0;
}
if (size <= sizeMax)
{
baos.write(b[0]); // 将当前字节存入缓冲流
}
}
String headers = null; // 找到HEADER_SEPARATOR后,获取描述头
if (headerEncoding != null)
{
try
{
headers = baos.toString(headerEncoding);
}
catch (UnsupportedEncodingException e)
{
headers = baos.toString();
}
}
else
{
headers = baos.toString();
}
return headers;
}
readHeaders方法循环调用readByte()方法逐个读取buffer缓冲区中的字节,并将读取的字节与HEADER_SEPARATOR ={‘\n’,‘\r’,‘\n’
,‘\r’}的第一个字节进行比较,如果这个字节等于HEADER_SEPARATOR的首字节‘\n’,则循环控制因子i加1,这样,下次调用readByte()方
法读取的字节将与HEADER_SEPARATOR中的第二字节比较,如果相等,则依照这种方式比较后面的字节内容,如果连续读取到了
HEADER_SEPARATOR字节序列,则循环语句结束。readHeaders方法将读取到的每个正常字节写入到了一个字节数组输出流中,其中也包括作为描
述头与主体内容之间的分隔序列HEADER_SEPARATOR中的字节数据。由于readByte()方法会自动移动head变量的值和自动向缓冲区buffer中载入
数据,所以,readHeaders方法执行完以后,成员变量head指向分区主体部分的首字节。readHeaders方法最后将把存入字节数组输出流中的字
节数据按指定字符集编码转换成字符串并返回,该字符串就是描述头字符串。
8. readBodyData方法
MultipartStream类中的readBodyData方法用于把主体部分的数据写入到一个输出流对象中,并返回写入到输出流中的字节总数。当调用
readBodyData方法前,成员变量head已经指向了分区的主体部分的首字节,readBodyData方法调用完成后,成员变量head指向分区分隔界线的
首字节。readBodyData方法中需要调用findSeparator方法找出下一个分区分隔界线的首字节位置,才能知道这次读取的分区主体内容的结束位
置。从分区主体部分的首字节开始,直到在findSeparator方法找到的下一个分区分隔界线前的所有数据都是这个分区的主体部分的数据,
readBodyData方法需要把这些数据都写到输出流output对象中。如果findSeparator方法在buffer缓冲区中没有找到分区分隔界线,
readBodyData方法还必须向buffer缓冲区中装入新的数据内容后继续调用findSeparator方法进行处理。在向buffer缓冲区中装入新的数据内容
时,必须先将上次保留在buffer缓冲区中的内容转移进新buffer缓冲区的开始处。readBodyData方法的源代码如下,传递给readBodyData方法
的参数实际上是一个DeferredFileOutputStream类对象:
public int readBodyData(OutputStream output)
throws MalformedStreamException,IOException
{
// 用于控制循环的变量
boolean done = false;
int pad;
int pos;
int bytesRead;
// 写入到输出流中的字节个数
int total = 0;
while (!done)
{
pos = findSeparator();// 搜索分隔界线
if (pos != -1) //缓冲区buffer中包含有分隔界线
{
output.write(buffer, head, pos - head);
total += pos - head;
head = pos;//head变量跳过主体数据,指向分隔界线的首字节
done = true;// 跳出循环
}
else //缓冲区buffer中没有包含分隔界线
{
/*根据缓冲区中未被readHeaders方法读取的数据内容是否大于图1.4中的
保留区的大小,来决定保留到下一次buffer缓冲区中的字节个数
*/
if (tail - head > keepRegion)
{
pad = keepRegion;
}
else
{
pad = tail - head;
}
output.write(buffer, head, tail - head - pad);
total += tail - head - pad;//统计写入到输出流中的字节个数
/*将上一次buffer缓冲区中的未处理的数据转移到
下一次buffer缓冲区的开始位置
*/
System.arraycopy(buffer, tail - pad, buffer, 0, pad);
head = 0; //让head变量指向缓冲区的开始位置
//向buffer缓冲区中载入新的数据
bytesRead = input.read(buffer, pad, bufSize - pad);
if (bytesRead != -1)
{
//设置buffer缓冲区中的有效字节的个数
tail = pad + bytesRead;
}
else
{
/*还没有找到分隔界线,输入流就结束了,输入流中的数据格式
显然不正确,保存缓冲区buffer中还未处理的数据后抛出异常
*/
output.write(buffer, 0, pad);
output.flush();
total += pad;
throw new MalformedStreamException
("Stream ended unexpectedly ");
}
}
}
output.flush();
return total;
}
9. discardBodyData方法
MultipartStream类中的discardBodyData方法用来跳过主体数据,它与readBodyData方法非常相似,不同之处在于readBodyData方法把数据写
入到一个输出流中,而discardBodyData方法是把数据丢弃掉。discardBodyData方法返回被丢掉的字节个数,方法调用完成后成员变量head指
向下一个分区分隔界线的首字节。MultipartStream类中定义discardBodyData这个方法,是为了忽略主体内容部分的第一个分隔界线前面的内
容,按照MIME规范,消息头和消息体之间的分隔界线前面可以有一些作为注释信息的内容,discardBodyData就是为了抛弃这些注释信息而提供
的。discardBodyData方法的源代码如下:
public int discardBodyData() throws MalformedStreamException,IOException
{
boolean done = false;
int pad;
int pos;
int bytesRead;
int total = 0;
while (!done)
{
pos = findSeparator();
if (pos != -1)
{
total += pos - head;
head = pos;
done = true;
}
else
{
if (tail - head > keepRegion)
{
pad = keepRegion;
}
else
{
pad = tail - head;
}
total += tail - head - pad;
System.arraycopy(buffer, tail - pad, buffer, 0, pad);
head = 0;
bytesRead = input.read(buffer, pad, bufSize - pad);
if (bytesRead != -1)
{
tail = pad + bytesRead;
}
else
{
total += pad;
throw new MalformedStreamException
("Stream ended unexpectedly ");
}
}
}
return total;
}
10. readBoundary方法
对于图1.3中的每一个分区的解析处理,程序首先要调用readHeaders方法读取描述头,接着要调用readBodyData(OutputStream output)读取主
体数据,这样就完成了一个分区的解析。readBodyData方法内部调用findSeparator方法找到了分隔界线,然后读取分隔界线前面的内容,此时
MultipartStream类中的成员变量head指向了buffer缓冲区中的分隔界线boundary的第一个字节。findSeparator方法只负责寻找分隔界线
boundary在缓冲区buffer中的位置,不负责从buffer缓冲区中读走分隔界线的字节数据。在调用readBodyData方法之后,程序接着应该让成员
变量head跳过分隔界线,让它指向下一个分区的描述头的第一个字节,才能调用readHeaders方法去读取下一个分区的描述头。
MultipartStream类中定义了一个readBoundary方法,用于让成员变量head跳过分隔界线,让它指向下一个分区的描述头的第一个字节。对于图
1.3中的最后的分隔界线,它比其他的分隔界线后面多了两个“-”字符,而其他分隔界线与下一个分区的内容之间还有一个回车换行,所以,
readBoundary方法内部跳过分隔界线后,还需要再读取两个字节的数据,才能让成员变量head指向下一个分区的描述头的第一个字节。
readBoundary方法内部读取分隔界线后面的两个字节数据后,根据它们是回车换行、还是两个“-”字符,来判断这个分隔界线是下一个分区的
开始标记,还是整个请求消息的实体内容的结束标记。如果readBoundary方法发现分隔界线是下一个分区的开始标记,那么它返回true,否则
返回false。readBoundary()方法的源代码如下:
public boolean readBoundary()throws MalformedStreamException
{
byte[] marker = new byte[2];
boolean nextChunk = false;
head += boundaryLength; // 跳过分隔界线符
try
{
marker[0] = readByte();
marker[1] = readByte();
// 静态常量STREAM_TERMINATOR ={‘-’、‘-’}
if (arrayequals(marker, STREAM_TERMINATOR, 2))
{
nextChunk = false;
}
// 静态常量FIELD_SEPARATOR ={‘/n’、‘/r’}
else if (arrayequals(marker, FIELD_SEPARATOR, 2))
{
nextChunk = true;
}
else
{
/*如果读到的既不是回车换行,又不是两个减号,
说明输入流有问题,则抛
发表评论
-
java输出文件
2010-12-01 16:51 1358try { File file = new Fil ... -
Java字符串将不是字符数字的字符转换为十六进制字符
2010-07-12 10:28 1326char data[] = "asdfasdf中国1 ... -
Java解压ZIP文件同时解决压缩文件中含有中文名文件乱码的问题
2010-04-22 13:12 5347在使用Java对ZIP压缩文件进行解压的方式中有两种,一种是使 ... -
Java解析XML格式字符串返回Document类型对象
2008-05-23 14:46 9014package com.yc.util; import ja ... -
Java进行WebService通讯
2008-05-19 15:10 2172package com.yc.ycportal.cqkf.se ... -
Java实现MD5算法加密
2008-05-16 09:17 1230/** * MD5 算法的Java Bean */ pac ... -
Java手工打Jar包
2008-05-10 11:28 1881用法:jar {ctxu}[vfm0Mi] [jar-文件] ... -
Java解压缩ZIP文件同时包含Jar包解决ZIP包中含有中文名称信息的文件
2008-05-07 13:42 3319我们知道压缩文件中有第一个文件夹为原始文件夹:例如我们对一个目 ... -
Java上传文件实例
2008-05-05 10:08 4007package com.yc.eap.util; impor ... -
Servlet中文API文档
2008-04-26 15:47 2238基本类和接口 一、javax.servlet.Servlet ... -
ServletConfig和ServletConfig参数访问
2008-04-26 14:20 1356HttpServletRequest,HttpServletR ... -
Java上传文件同时含有表单域的实现
2008-04-25 17:08 2108package com.yc.eap.util; impor ... -
Jsp通过文件流实现文件下载
2008-04-23 10:44 2881<%@ page contentType="a ... -
Commons-fileupload工具的API与开发实例解析(二)
2008-04-23 10:24 21591.2.5 文件上传编程实例 下面参考图1.2中看到的示例代码 ... -
Commons-fileupload工具的API与开发实例解析(一)
2008-04-23 10:22 3493文件上传组件的应用与编写 在许多Web站点应用中都需要为 ... -
Java解析XML文件
2008-04-23 10:15 1785package com.yc.ycportal.ge.util ... -
Java压缩与解压缩文件
2008-04-23 10:05 1717package com.yc.ycportal.ge.util ... -
Java只获取系统时间的年份
2008-04-23 10:04 5574public static String getYear(){ ... -
Java连接Oracle数据库调用存储过程获取数据集
2008-04-23 10:03 2367package com.yc.ycportal.ge.util ... -
Java实现Ftp文件下载
2008-04-23 10:02 4592从http://www.enterprisedt.com/下载 ...
相关推荐
Apache Commons IO则是Apache Commons项目中的另一个关键组件,它提供了大量与I/O(输入/输出)相关的实用工具类。`commons-io-2.5.jar` 包含了各种I/O操作的通用功能,如文件读写、流操作、文件比较、文件过滤等。...
而`commons-io-2.4`是Apache Commons IO库的2.4版本,它是处理输入/输出操作的工具集,与FileUpload配合处理文件上传时,常被用到。 文件上传在Web应用中的实现并不直接内置在Servlet API中,因此,`commons-...
总之,Apache Commons FileUpload是Java Web开发中处理文件上传不可或缺的工具,它提供了高效、灵活且安全的文件上传解决方案。通过理解和熟练运用这个库,开发者可以轻松地在自己的应用中实现复杂的文件上传功能。
Apache Commons FileUpload与Apache Commons IO是Java开发中处理文件上传的两个重要库。它们为开发者提供了强大而灵活的工具,使得在Web应用中处理文件上传变得简单易行。 `commons-fileupload-1.2.1.jar`是Apache ...
总之,`commons-fileupload-1.2.2.jar`和`commons-io-2.4.jar`是Java Web开发中处理文件上传和下载不可或缺的工具。尽管它们的版本可能不是最新的,但依然能提供可靠的功能,并且在很多现有的系统中广泛使用。理解并...
在Java Web开发中,文件上传是一项常见的功能,Apache Commons FileUpload库是实现这一功能的强大工具。本示例将深入解析如何结合Maven构建一个基于Servlet的文件上传演示项目。Maven是一个项目管理工具,它可以帮助...
在"commons-fileupload-1.2.1.jar"这个版本中,它提供了一个简洁的API,使得开发者可以轻松地解析请求并获取上传的文件。 具体来说,FileUpload库允许你创建一个`DiskFileItemFactory`实例来配置临时存储参数,然后...
`commons-file`标签可能是指与`commons-fileupload`相关的所有操作,包括文件读写、文件处理等。而`图片上传`则特指使用该库上传图片文件的场景。 至于文件列表中提到的"Roger",这可能是实际的项目文件或代码示例...
"commons-fileupload-1.3.2.jar" 提供了处理这种类型请求的API,它能够解析请求体,将文件和文本字段分开,然后将文件数据存储到临时文件或内存中。这个库支持大文件上传,可以设置内存阈值,超过该阈值时,文件会被...
使用Commons-Fileupload,你需要配置Servlet容器来解析多部分请求,并创建`DiskFileItemFactory`来处理临时文件存储,接着创建`FileUpload`实例进行文件解析。 在选择使用哪个组件时,应考虑项目需求和团队的熟悉...
Apache Commons FileUpload是一个Java库,专门用于处理HTTP...总的来说,Apache Commons FileUpload是Java Web开发中不可或缺的工具,它极大地简化了文件上传的处理,使得开发者能够更专注于业务逻辑,而非底层细节。
总的来说,Apache Commons FileUpload和Commons IO是Java Web开发中处理文件上传不可或缺的工具,它们简化了这个过程,提高了代码的可读性和可维护性。通过合理配置和使用这两个库,开发者可以构建安全、高效、用户...
总的来说,`commons-fileupload.jar`和`commons-io-1.4.jar`是Java Web开发中实现文件上传功能必不可少的工具,它们为开发者提供了强大且灵活的接口,简化了文件上传的实现过程。了解并熟练掌握这两个库的使用,有助...
6. **API使用**:使用FileUpload库通常涉及创建一个`ServletFileUpload`实例,然后使用`parseRequest()`方法解析请求,得到`FileItem`对象。每个`FileItem`代表表单的一个字段或上传的文件,可以从中获取名称、内容...
`commons-io-1.3.2.jar`是Apache Commons IO库,它是`commons-fileupload`依赖的库,提供了各种与I/O操作相关的工具类。例如,`FileUtils`类提供了文件和目录的便捷操作,如复制、移动、删除等。在处理文件上传时,...
Apache Commons FileUpload与Apache Commons IO是Java开发中用于处理文件上传功能的重要库。这两个库的组合在处理HTTP请求中的文件上传部分尤其有用,特别是在Web应用中。下面将详细阐述这两个库的功能、使用方法...
在Java Web开发中,文件上传是一项常见的功能,Apache Commons FileUpload库为开发者提供了方便、灵活的文件上传解决方案。本教程将深入讲解如何使用`commons-fileupload`库处理中文乱码问题以及实现多文件上传。 ...
Apache Commons FileUpload是Java中处理HTTP请求中文件上传的一个强大工具,尤其在Web开发中扮演着重要角色。这个组件是Apache软件基金会的Commons项目的一部分,旨在简化从HTTP请求中提取上传文件的任务。在本文中...
库的核心类包括`FileItem`,用于表示上传的单一文件或表单字段,以及`FileUpload`,用于解析请求并创建`FileItem`实例。此外,它还支持设置内存阈值和临时文件存储路径,以优化大文件上传的处理。 `commons-io-...