JavaScript读二进制文件并用ajax传输二进制流

综合网上多个教程,加上自己实践得出的方法,目前能够兼容谷歌、IE11、IE10。

htmlbody里的内容,没什么特殊的。

1 <div id="dConfirm">
2     <p style="float: left;margin-left: 20px;margin-top: 20px">
3         <form action="javascript: uploadAndSubmit();" name="demoForm" id="demoForm" method="post" enctype="multipart/form-data">
4             <p>上传文件: <input type="file" name="file" id="str_file"/></p>
5             <p><input type="submit" value="上传" /></p>
6         </form>
7     </p>
8 </div>
View Code

读取二进制文件:

 1 function uploadAndSubmit()
 2     {
 3         filename=document.getElementById("str_file").value;
 4         var form = document.forms["demoForm"];
 5         if(filename!="")
 6         {
 7             try
 8             {
 9                 var obj = new ActiveXObject("ADODB.Stream");//这个必然是IE
10             }
11             catch(e)
12             {
13                 var file = form["file"].files[0];
14                 var reader = new FileReader();
15                 reader.readAsBinaryString(file);//这个读法是异步的
16 
17                 reader.onloadend=function()
18                 {
19                     // 这个事件在读取结束后,无论成功或者失败都会触发
20                     if (reader.error) {
21                         console.log(reader.error);
22                     } else {
23                         uploadAndSubmit2(reader.result);
24                     }
25                 }
26                 return;
27             }
28             var bf1=new BinaryFile(filename);//这个读法是同步的
29             uploadAndSubmit2(bf1.ReadAll());
30         }
31     }

这里要对浏览器类型做一下判断,如果不是IE则使用FileReader进行读取,如果是IE则使用activex控件读取。这里有一个坑,虽然IE11和IE10不支持FileReader对象的方法,但IE11和IE10的“typeof FileReader”并不是“undefined”,难以直接通过是否支持FileReader来区分浏览器。还要注意的是FileReader方法是异步读文件,activex是同步读文件,我一直没想明白这两条路线怎样封装在一个方法里,不知大家有没有好办法。

其中BinaryFile对象的构造方法摘自http://www.codeproject.com/Articles/17825/Reading-and-Writing-Binary-Files-Using-JScript?msg=3718403#xx3718403xx技术博客,在博客的回复中有一个改进方法据说效率更高,但因为没有看懂,所以选用了原始方法。

原始方法很长:

  1 //使用ADODB.Stream控件时要注意ISO-8859-1和Windows-1252字符集之间的转换
  2 function BinaryFile(name)
  3 {
  4     var adTypeBinary = 1
  5     var adTypeText   = 2
  6     var adSaveCreateOverWrite = 2
  7     // The trick - this is the 'old fassioned' not translation page
  8     // It lest javascript use strings to act like raw octets
  9     var codePage='437';
 10 
 11     this.path=name;
 12 
 13     var forward  = new Array();
 14     var backward = new Array();
 15 
 16     // Note - for better performance I should preconvert these hex
 17     // definitions to decimal - at some point :-) - AJT
 18     forward['80'] = '00C7';
 19     forward['81'] = '00FC';
 20     forward['82'] = '00E9';
 21     forward['83'] = '00E2';
 22     forward['84'] = '00E4';
 23     forward['85'] = '00E0';
 24     forward['86'] = '00E5';
 25     forward['87'] = '00E7';
 26     forward['88'] = '00EA';
 27     forward['89'] = '00EB';
 28     forward['8A'] = '00E8';
 29     forward['8B'] = '00EF';
 30     forward['8C'] = '00EE';
 31     forward['8D'] = '00EC';
 32     forward['8E'] = '00C4';
 33     forward['8F'] = '00C5';
 34     forward['90'] = '00C9';
 35     forward['91'] = '00E6';
 36     forward['92'] = '00C6';
 37     forward['93'] = '00F4';
 38     forward['94'] = '00F6';
 39     forward['95'] = '00F2';
 40     forward['96'] = '00FB';
 41     forward['97'] = '00F9';
 42     forward['98'] = '00FF';
 43     forward['99'] = '00D6';
 44     forward['9A'] = '00DC';
 45     forward['9B'] = '00A2';
 46     forward['9C'] = '00A3';
 47     forward['9D'] = '00A5';
 48     forward['9E'] = '20A7';
 49     forward['9F'] = '0192';
 50     forward['A0'] = '00E1';
 51     forward['A1'] = '00ED';
 52     forward['A2'] = '00F3';
 53     forward['A3'] = '00FA';
 54     forward['A4'] = '00F1';
 55     forward['A5'] = '00D1';
 56     forward['A6'] = '00AA';
 57     forward['A7'] = '00BA';
 58     forward['A8'] = '00BF';
 59     forward['A9'] = '2310';
 60     forward['AA'] = '00AC';
 61     forward['AB'] = '00BD';
 62     forward['AC'] = '00BC';
 63     forward['AD'] = '00A1';
 64     forward['AE'] = '00AB';
 65     forward['AF'] = '00BB';
 66     forward['B0'] = '2591';
 67     forward['B1'] = '2592';
 68     forward['B2'] = '2593';
 69     forward['B3'] = '2502';
 70     forward['B4'] = '2524';
 71     forward['B5'] = '2561';
 72     forward['B6'] = '2562';
 73     forward['B7'] = '2556';
 74     forward['B8'] = '2555';
 75     forward['B9'] = '2563';
 76     forward['BA'] = '2551';
 77     forward['BB'] = '2557';
 78     forward['BC'] = '255D';
 79     forward['BD'] = '255C';
 80     forward['BE'] = '255B';
 81     forward['BF'] = '2510';
 82     forward['C0'] = '2514';
 83     forward['C1'] = '2534';
 84     forward['C2'] = '252C';
 85     forward['C3'] = '251C';
 86     forward['C4'] = '2500';
 87     forward['C5'] = '253C';
 88     forward['C6'] = '255E';
 89     forward['C7'] = '255F';
 90     forward['C8'] = '255A';
 91     forward['C9'] = '2554';
 92     forward['CA'] = '2569';
 93     forward['CB'] = '2566';
 94     forward['CC'] = '2560';
 95     forward['CD'] = '2550';
 96     forward['CE'] = '256C';
 97     forward['CF'] = '2567';
 98     forward['D0'] = '2568';
 99     forward['D1'] = '2564';
100     forward['D2'] = '2565';
101     forward['D3'] = '2559';
102     forward['D4'] = '2558';
103     forward['D5'] = '2552';
104     forward['D6'] = '2553';
105     forward['D7'] = '256B';
106     forward['D8'] = '256A';
107     forward['D9'] = '2518';
108     forward['DA'] = '250C';
109     forward['DB'] = '2588';
110     forward['DC'] = '2584';
111     forward['DD'] = '258C';
112     forward['DE'] = '2590';
113     forward['DF'] = '2580';
114     forward['E0'] = '03B1';
115     forward['E1'] = '00DF';
116     forward['E2'] = '0393';
117     forward['E3'] = '03C0';
118     forward['E4'] = '03A3';
119     forward['E5'] = '03C3';
120     forward['E6'] = '00B5';
121     forward['E7'] = '03C4';
122     forward['E8'] = '03A6';
123     forward['E9'] = '0398';
124     forward['EA'] = '03A9';
125     forward['EB'] = '03B4';
126     forward['EC'] = '221E';
127     forward['ED'] = '03C6';
128     forward['EE'] = '03B5';
129     forward['EF'] = '2229';
130     forward['F0'] = '2261';
131     forward['F1'] = '00B1';
132     forward['F2'] = '2265';
133     forward['F3'] = '2264';
134     forward['F4'] = '2320';
135     forward['F5'] = '2321';
136     forward['F6'] = '00F7';
137     forward['F7'] = '2248';
138     forward['F8'] = '00B0';
139     forward['F9'] = '2219';
140     forward['FA'] = '00B7';
141     forward['FB'] = '221A';
142     forward['FC'] = '207F';
143     forward['FD'] = '00B2';
144     forward['FE'] = '25A0';
145     forward['FF'] = '00A0';
146     backward['C7']   = '80';
147     backward['FC']   = '81';
148     backward['E9']   = '82';
149     backward['E2']   = '83';
150     backward['E4']   = '84';
151     backward['E0']   = '85';
152     backward['E5']   = '86';
153     backward['E7']   = '87';
154     backward['EA']   = '88';
155     backward['EB']   = '89';
156     backward['E8']   = '8A';
157     backward['EF']   = '8B';
158     backward['EE']   = '8C';
159     backward['EC']   = '8D';
160     backward['C4']   = '8E';
161     backward['C5']   = '8F';
162     backward['C9']   = '90';
163     backward['E6']   = '91';
164     backward['C6']   = '92';
165     backward['F4']   = '93';
166     backward['F6']   = '94';
167     backward['F2']   = '95';
168     backward['FB']   = '96';
169     backward['F9']   = '97';
170     backward['FF']   = '98';
171     backward['D6']   = '99';
172     backward['DC']   = '9A';
173     backward['A2']   = '9B';
174     backward['A3']   = '9C';
175     backward['A5']   = '9D';
176     backward['20A7'] = '9E';
177     backward['192']  = '9F';
178     backward['E1']   = 'A0';
179     backward['ED']   = 'A1';
180     backward['F3']   = 'A2';
181     backward['FA']   = 'A3';
182     backward['F1']   = 'A4';
183     backward['D1']   = 'A5';
184     backward['AA']   = 'A6';
185     backward['BA']   = 'A7';
186     backward['BF']   = 'A8';
187     backward['2310'] = 'A9';
188     backward['AC']   = 'AA';
189     backward['BD']   = 'AB';
190     backward['BC']   = 'AC';
191     backward['A1']   = 'AD';
192     backward['AB']   = 'AE';
193     backward['BB']   = 'AF';
194     backward['2591'] = 'B0';
195     backward['2592'] = 'B1';
196     backward['2593'] = 'B2';
197     backward['2502'] = 'B3';
198     backward['2524'] = 'B4';
199     backward['2561'] = 'B5';
200     backward['2562'] = 'B6';
201     backward['2556'] = 'B7';
202     backward['2555'] = 'B8';
203     backward['2563'] = 'B9';
204     backward['2551'] = 'BA';
205     backward['2557'] = 'BB';
206     backward['255D'] = 'BC';
207     backward['255C'] = 'BD';
208     backward['255B'] = 'BE';
209     backward['2510'] = 'BF';
210     backward['2514'] = 'C0';
211     backward['2534'] = 'C1';
212     backward['252C'] = 'C2';
213     backward['251C'] = 'C3';
214     backward['2500'] = 'C4';
215     backward['253C'] = 'C5';
216     backward['255E'] = 'C6';
217     backward['255F'] = 'C7';
218     backward['255A'] = 'C8';
219     backward['2554'] = 'C9';
220     backward['2569'] = 'CA';
221     backward['2566'] = 'CB';
222     backward['2560'] = 'CC';
223     backward['2550'] = 'CD';
224     backward['256C'] = 'CE';
225     backward['2567'] = 'CF';
226     backward['2568'] = 'D0';
227     backward['2564'] = 'D1';
228     backward['2565'] = 'D2';
229     backward['2559'] = 'D3';
230     backward['2558'] = 'D4';
231     backward['2552'] = 'D5';
232     backward['2553'] = 'D6';
233     backward['256B'] = 'D7';
234     backward['256A'] = 'D8';
235     backward['2518'] = 'D9';
236     backward['250C'] = 'DA';
237     backward['2588'] = 'DB';
238     backward['2584'] = 'DC';
239     backward['258C'] = 'DD';
240     backward['2590'] = 'DE';
241     backward['2580'] = 'DF';
242     backward['3B1']  = 'E0';
243     backward['DF']   = 'E1';
244     backward['393']  = 'E2';
245     backward['3C0']  = 'E3';
246     backward['3A3']  = 'E4';
247     backward['3C3']  = 'E5';
248     backward['B5']   = 'E6';
249     backward['3C4']  = 'E7';
250     backward['3A6']  = 'E8';
251     backward['398']  = 'E9';
252     backward['3A9']  = 'EA';
253     backward['3B4']  = 'EB';
254     backward['221E'] = 'EC';
255     backward['3C6']  = 'ED';
256     backward['3B5']  = 'EE';
257     backward['2229'] = 'EF';
258     backward['2261'] = 'F0';
259     backward['B1']   = 'F1';
260     backward['2265'] = 'F2';
261     backward['2264'] = 'F3';
262     backward['2320'] = 'F4';
263     backward['2321'] = 'F5';
264     backward['F7']   = 'F6';
265     backward['2248'] = 'F7';
266     backward['B0']   = 'F8';
267     backward['2219'] = 'F9';
268     backward['B7']   = 'FA';
269     backward['221A'] = 'FB';
270     backward['207F'] = 'FC';
271     backward['B2']   = 'FD';
272     backward['25A0'] = 'FE';
273     backward['A0']   = 'FF';
274 
275     var hD="0123456789ABCDEF";
276     this.d2h = function(d)
277     {
278         var h = hD.substr(d&15,1);
279         while(d>15) {d>>=4;h=hD.substr(d&15,1)+h;}
280         return h;
281     }
282 
283     this.h2d = function(h)
284     {
285         return parseInt(h,16);
286     }
287 
288     this.WriteAll = function(what)
289     {
290         //Create Stream object
291         //var BinaryStream = WScript.CreateObject("ADODB.Stream");
292         var BinaryStream = new ActiveXObject("ADODB.Stream");
293         //Specify stream type - we cheat and get string but 'like' binary
294         BinaryStream.Type = adTypeText;
295         BinaryStream.CharSet = '437';
296         //Open the stream
297         BinaryStream.Open();
298         // Write to the stream
299         BinaryStream.WriteText(this.Forward437(what));
300         // Write the string to the disk
301         BinaryStream.SaveToFile(this.path, adSaveCreateOverWrite);
302 
303         // Clearn up
304         BinaryStream.Close();
305     }
306 
307     this.ReadAll  = function()
308     {
309         //Create Stream object - needs ADO 2.5 or heigher
310         //var BinaryStream = WScript.CreateObject("ADODB.Stream")
311         var BinaryStream = new ActiveXObject("ADODB.Stream");
312         //Specify stream type - we cheat and get string but 'like' binary
313         BinaryStream.Type = adTypeText;
314         BinaryStream.CharSet = codePage;
315         //Open the stream
316         BinaryStream.Open();
317         //Load the file data from disk To stream object
318         BinaryStream.LoadFromFile(this.path);
319         //Open the stream And get binary 'string' from the object
320         var what = BinaryStream.ReadText;
321         // Clean up
322         BinaryStream.Close();
323         return this.Backward437(what);
324     }
325 
326     /* Convert a octet number to a code page 437 char code */
327     this.Forward437 = function(inString)
328     {
329         var encArray = new Array();
330         var tmp='';
331         var i=0;
332         var c=0;
333         var l=inString.length;
334         var cc;
335         var h;
336         for(;i<l;++i)
337         {
338             c++;
339             if(c==128)
340             {
341                 encArray.push(tmp);
342                 tmp='';
343                 c=0;
344             }
345             cc=inString.charCodeAt(i);
346             if(cc<128)
347             {
348                 tmp+=String.fromCharCode(cc);
349             }
350             else
351             {
352                 h=this.d2h(cc);
353                 h=forward[''+h];
354                 tmp+=String.fromCharCode(this.h2d(h));
355             }
356         }
357         if(tmp!='')
358         {
359             encArray.push(tmp);
360         }
361 
362         // this loop progressive concatonates the
363         // array elements entil there is only one
364         var ar2=new Array();
365         for(;encArray.length>1;)
366         {
367             var l=encArray.length;
368             for(var c=0;c<l;c+=2)
369             {
370                 if(c+1==l)
371                 {
372                     ar2.push(encArray[c]);
373                 }
374                 else
375                 {
376                     ar2.push(''+encArray[c]+encArray[c+1]);
377                 }
378             }
379             encArray=ar2;
380             ar2=new Array();
381         }
382         return encArray[0];
383     }
384     /* Convert a code page 437 char code to a octet number*/
385     this.Backward437 = function(inString)
386     {
387         var encArray = new Array();
388         var tmp='';
389         var i=0;
390         var c=0;
391         var l=inString.length;
392         var cc;
393         var h;
394         for(;i<l;++i)
395         {
396             c++;
397             if(c==128)
398             {
399                 encArray.push(tmp);
400                 tmp='';
401                 c=0;
402             }
403             cc=inString.charCodeAt(i);
404             if(cc<128)
405             {
406                 tmp+=String.fromCharCode(cc);
407             }
408             else
409             {
410                 h=this.d2h(cc);
411                 h=backward[''+h];
412                 tmp+=String.fromCharCode(this.h2d(h));
413             }
414         }
415         if(tmp!='')
416         {
417             encArray.push(tmp);
418         }
419 
420         // this loop progressive concatonates the
421         // array elements entil there is only one
422         var ar2=new Array();
423         for(;encArray.length>1;)
424         {
425             var l=encArray.length;
426             for(var c=0;c<l;c+=2)
427             {
428                 if(c+1==l)
429                 {
430                     ar2.push(encArray[c]);
431                 }
432                 else
433                 {
434                     ar2.push(''+encArray[c]+encArray[c+1]);
435                 }
436             }
437             encArray=ar2;
438             ar2=new Array();
439         }
440         return encArray[0];
441     }
442 }
View Code

其中主体部分是:

 1 this.ReadAll  = function()
 2     {
 3         //Create Stream object - needs ADO 2.5 or heigher
 4         //var BinaryStream = WScript.CreateObject("ADODB.Stream")
 5         var BinaryStream = new ActiveXObject("ADODB.Stream");
 6         //Specify stream type - we cheat and get string but 'like' binary
 7         BinaryStream.Type = adTypeText;
 8         BinaryStream.CharSet = codePage;
 9         //Open the stream
10         BinaryStream.Open();
11         //Load the file data from disk To stream object
12         BinaryStream.LoadFromFile(this.path);
13         //Open the stream And get binary 'string' from the object
14         var what = BinaryStream.ReadText;
15         // Clean up
16         BinaryStream.Close();
17         return this.Backward437(what);
18     }

这里就是使用"ADODB.Stream"控件读取文件的方法,可以看到作者使用的读取类型是adTypeText(2),是在用文本读取方式读二进制文件!而按照文档改为adTypeBinary(1)类型后则读不到任何内容,不知道是为什么。

其余部分代码则是在做编码转换工作,大体意思是读文件时要使用“ISO-8859-1”字符集,用http发送文件时则要使用“Windows-1252”字符集,这两种字符集只有极少数字符有差别,所以在读到的数据中找到有区别的部分一一转换为另一种字符集表示。

ajax发送二进制流:

 1 function uploadAndSubmit2(BinaryContent)
 2     {
 3         Url = UrlHead + "Cook.ashx";
 4         xmlHttp=new XMLHttpRequest();
 5         xmlHttp.open("POST",Url + "?method=post&func=file_upload&fileName=" + encodeURIComponent(filename.split("\")[filename.split("\").length-1]));//IE处理汉字url
 6         xmlHttp.sendAsBinary(BinaryContent);
 7         xmlHttp.onreadystatechange = function ()
 8         {
 9             if (xmlHttp.readyState == 4) {
10                 if (xmlHttp.status == 200) {
11                     var str=xmlHttp.response;
12                     alert(str);
13                     xmlHttp.abort();
14                 }
15             }
16         }
17     }

为了进行二进制传输这里没有使用兼容旧版本IE的“window.ActiveXObject("Msxm12.XMLHTTP")”和“window.ActiveXObject("Microsoft.XMLHTTP")”,不知大家有没有支持这两种activex控件的二进制传输方法。

在一篇教程里第六行前面有一行:

xmlHttp.overrideMimeType('text/plain; charset=x-user-defined');//:x-user-defined告诉浏览器不要解析返回数据

加上这个一行后浏览器将不会对后台返回的数据的编码格式进行解析,具体来讲就是返回到前台的中文文本都显示为“ ”或“口”,我估计作者这样做是为了在前台接收后台传来的二进制数据。

事实上只有火狐的XMLHttpRequest支持sendAsBinary方法,为了在IE和谷歌下使用,需要给XMLHttpRequest增加一个原型方法:

1 //给XMLHttpRequest的原型添加二进制发送功能
2 XMLHttpRequest.prototype.sendAsBinary = function(datastr) {
3     function byteValue(x) {
4         return x.charCodeAt(0) & 0xff;
5     }
6     var ords = Array.prototype.map.call(datastr, byteValue);
7     var ui8a = new Uint8Array(ords);
8     this.send(ui8a.buffer);
9 }

这里的代码就不太懂了,其中第六行IE8不支持、第七行IE9不支持。

后台使用的是java serverlet,以下是最终调用的java类的代码:

 1 public String FileUpload(HttpServletRequest request) throws IOException
 2     {
 3         request.setCharacterEncoding("UTF-8");
 4         BufferedInputStream fileIn = new BufferedInputStream(request.getInputStream()); 
 5         String fn = request.getParameter("fileName");            
 6         byte[] buf = new byte[1024];    
 7         File file = new File("d:/" + fn); 
 8         BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream(file)); 
 9         try
10         {           
11             while (true) 
12             { 
13                  // 读取数据
14                  int bytesIn = fileIn.read(buf, 0, 1024);               
15                  System.out.println(bytesIn);              
16                  if (bytesIn == -1) 
17                  { 
18                      break; 
19                  } 
20                  else 
21                  { 
22                     fileOut.write(buf, 0, bytesIn); 
23                  } 
24             } 
25             fileOut.flush(); 
26             return("保存成功");
27         }
28         catch(Exception e)
29         {
30             return "保存失败,原因:"+e.toString();
31         }
32         finally
33         {            
34             fileOut.close(); 
35         }
36     }