教 学 活 动 首 页
基 本 内 容 |
第 4 章 JSP 中的文件操作 |
教学目的与要求:通过本章的学习让学生了解文件类,流的概念;理解字节流,字符流,回压字符流,数据流,对象流,RandomAccessFile 流的含义;掌握以上各种流的文件操作方法;并能够实现文件上传和文件下载。 |
教学内容: 4.1 File 类 4.2 使用字节流读写文件 4.3 使用字符流读写文件 4.4 回压字符流 4.5 数据流 4.6 对象流 4.7 RandomAccessFile 流 4.8 文件上传 4.9 文件下载 |
教学基本要求: 了解:文件类,流的概念 理解:字节流,字符流,回压字符流,数据流,对象流,RandomAccessFile 流 掌握:以上各种流的文件操作方法 应用:文件上传,文件下载 |
教学重点教学难点: 文件类的基本文件操作,字节流的操作方法,字符流的操作方法,回压字符流的操作方法,数据流的操作方法,对象流的操作方法,RandomAccessFile 流的操作方法,文件上传,文件下载 |
教学方法: 教学手段:多媒体教学和计算机程序演示 |
教学小结: (见教学进程) |
作业与思考:见课后习题 |
课后记载:
|
教 学 进 程
第4章 JSP中的文件操作 有时服务器需要将客户提交的信息保存到文件或根据客户的要求将服务器上的文件的内容显示到客户端。JSP通过Java的输入输出流来实现文件的读写操作。 4.1 File 类 File类的对象主要用来获取文件本身的一些信息,例如文件所在的目录、文件的长度、文件读写权限等,不涉及对文件的读写操作。 创建一个File对象的构造方法有3个:
l File(String filename); l File(String directoryPath,String filename); l File(File f, String filename);
其中,filename是文件名字,directoryPath是文件的路径,f是指定成一个目录的文件。 使用File(String filename)创建文件时,该文件被认为是与当前应用程序在同一目录中,由于JSP引擎是在bin下启动执行的,所以该文件被认为在下列目录中: D:\Tomcat\jakarta-tomcat-4.0\bin\ 。 4.1.1 获取文件的属性 经常使用File类的下列方法获取文件本身的一些信息: 1. public String getName():获取文件的名字。 2. public boolean canRead():判断文件是否是可读的。 3. public boolean canWrite():判断文件是否可被写入。 4. public boolean exits():判断文件是否存在。 5. public long length():获取文件的长度(单位是字节)。 6. public String getAbsolutePath():获取文件的绝对路径。 7. public String getParent():获取文件的父目录。 8. public boolean isFile():判断文件是否是一个正常文件,而不是目录。 9. public boolean isDirectroy():判断文件是否是一个目录。 10. public boolean isHidden():判断文件是否是隐藏文件。 11. public long lastModified():获取文件最后修改的时间(时间是从1970年午夜至文件最后修改时刻的毫秒数)。 在下面的例子1中,我们使用上述的一些方法,获取某些文件的信息。 例子1(效果如图4.1所示) Example4_1.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import="java.io.*"%> <HTML> <BODY bgcolor=cyan><Font Size=1> <%File f1=new File("D:\\Tomcat\\jakarta-tomcat-4.0\\webapps\\root","Example3_1.jsp"); File f2=new File("jasper.sh"); %> <P> 文件Example3_1.jsp是可读的吗? <%=f1.canRead()%> <BR> <P>文件Example3_1.jsp的长度: <%=f1.length()%>字节 <BR> <P> jasper.sh是目录吗? <%=f2.isDirectory()%> <BR> <P>Example3_1.jsp的父目录是: <%=f1.getParent()%> <BR> <P>jasper.sh的绝对路径是: <%=f2.getAbsolutePath()%> </Font> </BODY> </HTML>
(1)创建目录 File对象调用方法:public boolean mkdir()创建一个目录,如果创建成功返回true,否则返回false(如果该目录已经存在将返回false)。 在下面的例子2中,我们在Root下创建一个名字是Students的目录 例子2(效果如图4.2所示) Example4_2.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import="java.io.*"%> <HTML> <BODY><Font Size=2> <% File dir=new File("D:/Tomcat/jakarta-tomcat-4.0/webapps/root","Students"); %> <P> 在root下创建一个新的目录:Student,<BR>成功创建了吗? <%=dir.mkdir()%> <P> Student是目录吗? <%=dir.isDirectory()%> </Font> </BODY> </HTML> (2)列出目录中的文件 如果File对象是一个目录,那么该对象可以调用下述方法列出该目录下的文件和子目录: l public String[] list():用字符串形式返回目录下的全部文件, l public File [] listFiles():用File对象形式返回目录下的全部文件。 在下面的例子3中,输出了Root下的全部文件中的5个和全部子目录。 例子3(效果如图4.3所示) Example4_3.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import="java.io.*"%> <HTML> <BODY><Font Size=2> <% File dir=new File("D:/Tomcat/jakarta-tomcat-4.0/webapps/root"); File file[]=dir.listFiles(); %> <P> 列出root下的5个长度大于1000字节的文件和全部目录: <BR>目录有: <% for(int i=0;i<file.length;i++) {if(file[i].isDirectory()) out.print("<BR>"+file[i].toString()); } %> <P> 5个长度大于1000 字节的文件名字: <% for(int i=0,number=0;(i<file.length)&&(number<=5);i++) {if(file[i].length()>=1000) {out.print("<BR>"+file[i].toString()); number++; } } %> </Font> </BODY> </HTML>
我们有时需要列出目录下指定类型的文件,比如.jsp、.txt等扩展名的文件。可以使用File类的下述两个方法,列出指定类型的文件, l public String[] list(FilenameFilter obj); 该方法用字符串形式返回目录下的指定类型的所有文件。 l public File [] listFiles(FilenameFilter obj); 该方法用File对象返回目录下的指定类型所有文件。 FilenameFile是一个接口,该接口有一个方法:
public boolean accept(File dir,String name);
当向list方法传递一个实现该接口的对象时,list方法在列出文件时,将让该文件调用accept方法检查该文件是否符合accept方法指定的目录和文件名字要求。 在下面的例子4中,列出Root目录下的部分JSP文件的名字。 例子4 Example4_4.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <%! class FileJSP implements FilenameFilter { String str=null; FileJSP(String s) {str="."+s; } public boolean accept(File dir,String name) { return name.endsWith(str); } } %> <P>下面列出了服务器上的一些jsp文件 <% File dir=new File("d:/Tomcat/Jakarta-tomcat-4.0/webapps/root/"); FileJSP file_jsp=new FileJSP("jsp"); String file_name[]=dir.list(file_jsp); for(int i=0;i<5;i++) {out.print("<BR>"+file_name[i]); } %> 4.1.3 删除文件和目录 File对象调用方法public boolean delete()可以删除当前对象代表的文件或目录,如果File对象表示的是一个目录,则该目录必须是一个空目录,删除成功返回true。 下面的例子5删除Root目录下的A.java文件和Students目录。 例子5(效果如图4.4所示) Example4_5.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <HTML> <BODY> <%File f=new File("d:/Tomcat/Jakarta-tomcat-4.0/webapps/root/","A.java"); File dir=new File("d:/Tomcat/Jakarta-tomcat-4.0/webapps/root","Students"); boolean b1=f.delete(); boolean b2=dir.delete(); %> <P>文件A.java成功删除了吗? <%=b1%> <P>目录Students成功删除了吗? <%=b2%> </BODY> </HTML> 4.2 使用字节流读写文件 Java的I/O流提供一条通道程序,可以使用这条通道把源中的数据送给目的地。把输入流的指向称做源,程序从指向源的输入流中读取源中的数据。而输出流的指向是数据要去的一个目的地,程序通过向输出流中写入数据把信息传递到目的地,如下图4.5、4.6所示。 java.io包提供大量的流类。所有字节输入流类都是InputStream(输入流)抽象类的子类,而所有字节输出流都是OutputStream(输出流)抽象类的子类。
l int read(): 输入流调用该方法从源中读取单个字节的数据,该方法返回字节值(0~255之间的一个整数),如果未读出字节就返回-1。 l int read(byte b[]): 输入流调用该方法从源中试图读取b.length个字节到b中,返回实际读取的字节数目。如果到达文件的末尾,则返回-1。 l int read(byte b[], int off, int len): 输入流调用该方法从源中试图读取len个字节到b中,并返回实际读取的字节数目。如果到达文件的末尾,则返回-1,参数off指定从字节数组的某个位置开始存放读取的数据。 l void close(): 输入流调用该方法关闭输入流。 l long skip(long numBytes): 输入流调用该方法跳过numBytes个字节,并返回实际跳过的字节数目。
OutputStream类的常用方法: l void write(int n): 输出流调用该方法向输出流写入单个字节。 l void write(byte b[]): 输出流调用该方法向输出流写入一个字节数组。 l void write(byte b[],int off,int len): 从给定字节数组中起始于偏移量off处取len个字节写到输出流。 l void close(): 关闭输出流。
4.2.1 FileInputStream 和FileOutputStream类 FileInputStream该类是从InputStream中派生出来的简单输入类。该类的所有方法都是从InputStream类继承来的。为了创建FileInputStream类的对象,用户可以调用它的构造方法。下面显示了两个构造方法:
l FileInputStream(String name); l FileInputStream(File file);
第一个构造方法使用给定的文件名name创建一个FileInputStream对象,第二个构造方法使用File对象创建FileInputStream对象。参数name和file指定的文件称做输入流的源,输入流通过调用read方法读出源中的数据。 FileInpuStream文件输入流打开一个到达文件的输入流(源就是这个文件,输入流指向这个文件)例如,为了读取一个名为myfile.dat的文件,建立一个文件输入流对象,如下所示:
FileInputStream istream = new FileInputStream("myfile.dat");
文件输入流构造方法的另一种格式是允许使用文件对象来指定要打开哪个文件,如下所示:
FileInputStream(File file);
例如,下面这段代码使用文件输入流构造方法来建立一个文件输入流,如下所示:
File f = new File("myfile.dat"); FileInputStream istream = new FileInputStream(f);
当您使用文件输入流构造方法建立通往文件的输入流时,可能会出现错误(也被称为异常)。例如,您试图要打开的文件可能不存在。当出现I/O错误,Java生成一个出错信号,它使用一个IOException(IO异常)对象来表示这个出错信号。程序必须使用一个catch(捕获)块检测并处理这个异常。例如,为了把一个文件输入流对象与一个文件关联起来,使用类似于下面所示的代码:
try {FileInputStream ins = new FileInputStream("myfile.dat"); //读取输入流 } catch (IOException e ) { //文件I/O错误 System.out.println("File read error: " +e ); } 与FileInputStream类相对应的类是FileOutputStream类。FileOutputStream提供了基本的文件写入能力。除了从FileOutputStream类继承来的方法以外,FileOutputStream类还有两个常用的构造方法,这两个构造方法如下所示:
l FileOutputStream(String name); l FileOutputStream(File file);
第一个构造方法使用给定的文件名name创建一个FileOutputStream对象。第二个构造方法使用File对象创建FileOutputStream对象。参数name和file指定的文件称做输出流的目的地,通过向输出流中写入数据把信息传递到目的地。创建输出流对象也能发生IOException异常,必须在try、catch块语句中创建输出流对象。 注:使用FileInputStream的构造方法:FileInputStream(String name)创建一个输入流时,以及使用FileOutputStream的构造方法:FileOutputStream(String name)创建一个输出流时要保证文件和当前应用程序在同一目录下。由于JSP引擎是在bin下启动执行的,所以文件必须在bin目录中。 4.2.2 BufferedInputStream和BufferedOutputStream类 为了提高读写的效率,FileInputStream流经常和BufferedInputStream流配合使用,FileOutputStream流经常和BufferedOutputStream流配合使用。 BufferedInputStream的一个常用的构造方法是:
BufferedInputStream(InputStream in);
该构造方法创建缓存输入流,该输入流的指向是一个输入流。当我们要读取一个文件,比如A.txt时,可以先建立一个指向该文件的文件输入流:
FileInputStream in=new FileInputStream(“A.txt”);
然后再创建一个指向文件输入流in的输入缓存流(就好象把两个输入水管接在一起):
BufferedInputStream buffer=new BufferedInputStream(in);
这时,我们就可以让buffer调用read方法读取文件的内容,buffer在读取文件的过程中,会进行缓存处理,增加读取的效率。 同样,当要向一个文件,比如B.txt,写入字节时,可以先建立一个指向该文件的文件输出流:
FileOutputStream out=new FileOutputStream(“B.txt”);
然后再创建一个指向输出流out的输出缓存流:
BufferedOutputStream buffer=new BufferedOutputStream(out);
这时,buffer调用write方法向文件写入内容时会进行缓存处理,增加写入的效率。需要注意的是,写入完毕后,须调用flush方法将缓存中的数据存入文件。 在下面的例子6中,服务器将若干内容写入一个文件,然后读取这个文件,并将文件的内容显示给客户。
Example4_6.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <HTML> <BODY bgcolor=cyan><FONT size=1> <%File dir=new File("d:/Tomcat/Jakarta-tomcat-4.0/webapps/root","Students"); dir.mkdir(); File f=new File(dir,"hello.txt"); try{ FileOutputStream outfile=new FileOutputStream(f); BufferedOutputStream bufferout=new BufferedOutputStream(outfile); byte b[]="你们好,很高兴认识你们呀!<BR>nice to meet you".getBytes(); bufferout.write(b); bufferout.flush(); bufferout.close(); outfile.close(); FileInputStream in=new FileInputStream(f); BufferedInputStream bufferin=new BufferedInputStream(in); byte c[]=new byte[90]; int n=0; while((n=bufferin.read(c))!=-1) { String temp=new String(c,0,n); out.print(temp); } bufferin.close(); in.close(); } catch(IOException e) { } %> </BODY> </HTML> 4.3 使用字符流读写文件 上面我们学习了使用字节流读写文件,字节流不能直接操作Unicode字符,所以Java提供了字符流,由于汉字在文件中占用2个字节,如果使用字节流,读取不当会出现乱码现象,采用字符流就可以避免这个现象,在Unicode字符中,一个汉字被看作一个字符。 所有字符输入流类都是Reader(输入流)抽象类的子类,而所有字符输出流都是Writer(输出流) 抽象类的子类。
Reader类中常用方法: l int read(): 输入流调用该方法从源中读取一个字符,该方法返回一个整数(0~65535之间的一个整数,Unicode字符值),如果未读出字符就返回-1。 l int read(char b[]): 输入流调用该方法从源中读取b.length个字符到字符数组b中,返回实际读取的字符数目。如果到达文件的末尾,则返回-1。 l int read(char b[], int off, int len): 输入流调用该方法从源中读取len个字符并存放到字符数组b中,返回实际读取的字符数目。如果到达文件的末尾,则返回-1。其中,off参数指定read方法从符数组b中的什么地方存放数据。 l void close(): 输入流调用该方法关闭输入流。 l long skip(long numBytes): 输入流调用该方法跳过numBytes个字符,并返回实际跳过的字符数目。
Writer类中 常用方法: l void write(int n): 向输入流写入一个字符。 l void write(byte b[]): 向输入流写入一个字符数组。 l void write(byte b[],int off,int length): 从给定字符数组中起始于偏移量off处取len个字符写到输出流。 l void close(): 关闭输出流。 4.3.1 FileReader 和FileWriter类 FileReader该类是从Reader中派生出来的简单输入类。该类的所有方法都是从Reader类继承来的。为了创建FileReader类的对象,用户可以调用它的构造方法。下面显示了两个构造方法:
l FileReader(String name); l FileReader (File file) ;
第一个构造方法使用给定的文件名name创建一个FileReader对象,第二个构造方法使用File对象创建FileReader对象。参数name和file指定的文件称做输入流的源,输入流通过调用read方法读出源中的数据。 与FileReader类相对应的类是FileWriter类。FileWriter提供了基本的文件写入能力。除了从FileWriter类继承来的方法以外,FileWriter类还有两个常用的构造方法,这两个构造方法如下所示:
l FileWriter(String name); l FileWriter (File file);
第一个构造方法使用给定的文件名name创建一个FileWriter对象。第二个构造方法使用File对象创建FileWriter对象。参数name和file指定的文件称做输出流的目的地,通过向输出流中写入数据把信息传递到目的地。创建输入、输出流对象能发生IOException异常,必须在try、catch块语句中创建输入、输出流对象。 4.3.2 BufferedReader和BufferedWriter类 为了提高读写的效率,FileReader流经常和BufferedReader流配合使用;FileWriter流经常和BufferedWriter流配合使用。BufferedReader流还可以使用方法String readLine()读取一行;BufferedWriter流还可以使用方法void write(String s,int off,int length)将字符串s的一部分写入文件,使用newLine()向文件写入一个行分隔符。 在下面的例子7中,服务器将若干内容写入一个文件,然后读取这个文件,并将文件的内容显示给客户。 例子7(效果如图4.8所示) Example4_7.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <HTML> <BODY> <% File dir=new File("d:/Tomcat/Jakarta-tomcat-4.0/webapps/root","Students"); dir.mkdir(); File f=new File(dir,"apple.txt"); try{FileWriter outfile=new FileWriter(f); BufferedWriter bufferout=new BufferedWriter(outfile); bufferout.write("你好:"); bufferout.newLine(); bufferout.write("好久不见了,近来在忙什么呢?"); bufferout.newLine(); bufferout.write("欢迎来玩"); bufferout.newLine(); bufferout.write("2002年8月8日"); bufferout.flush(); bufferout.close(); outfile.close(); FileReader in=new FileReader(f); BufferedReader bufferin=new BufferedReader(in); String str=null; while((str=bufferin.readLine())!=null) {out.print("<BR>"+str); } bufferin.close(); in.close(); } catch(IOException e) { } %> </BODY> </HTML> 下面的例子8是一个网络英语单词测试,客户首先访问Example4_8.jsp页面,该页面通过输入流技术将一个文本文件读入,这个文本的每一行是一个英语单词选择练习,如下所示:
English.txt: #Take an umbrella with you in case___#it will rain#it rains#it raining#it rained#B# #He is no longer the honest man___he was#who#whom#which#that#D# #During the recession, thousands of workers were____#laid on#laid down#laid out#laid off#B# #The building casts a large__on the ground#shadow#shade#shanpoo#shawl#D# #The driver came close to ___killed for speeding#having been#have been#be#being#A#
然后把文本的每一行都存入客户的session对象中,并用行号做关键字,需要时可通过这个关键字在session对象中查找这一行,同时,将一个分数属性也存入客户的session对象中。 客户点击“开始练习”按钮进入练习页面Exercise.jsp,该页面从客户的session对象中首先取出第一行,然后通过StringTokenizer类将该行的语言符号取出。将第一个语言符号作为试题,第2到第4个语言符号作为选择,第5个语言符号是该试题的答案,把该题的答案存入客户的session对象中,将来根据题号在session对象中检索这个答案。当客户点击该页面中的“提交答案”按钮后再从session对象取出下一行,依次类推。 例子8(效果如图4.9所示) Example4_8.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <%@ page import ="java.util.*" %> <% int i=0; String str=null; Integer score=new Integer(0); Integer number=new Integer(0); session.setAttribute("score",score); session.setAttribute("序号",number); try{ //englishi.txt存放在f:/2000目录下。 File f=new File("f:/2000","English.txt"); FileReader in=new FileReader(f); BufferedReader buffer=new BufferedReader(in); while((str=buffer.readLine())!=null) {i++; session.setAttribute(""+i,str); } } catch(IOException e) { } %> <HTML> <BODY> <P><BR>点击按钮进入练习页面: <FORM action="Exercise.jsp" method="post" name=form> <Input type=submit value="开始练习"> </FORM> </BODY> </HTML>
Exercise.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <%@ page import ="java.util.*" %> <HTML> <BODY> <% String option[]=new String[7]; int 题号=0; if(!(session.isNew())) { Integer number=(Integer)session.getAttribute("序号");//获取题号。 if(number==null) {number=new Integer(0); } number=new Integer(number.intValue()+1);//将题号加1。 session.setAttribute("序号",number); //更新序号 int i=0; String str=(String)session.getAttribute(""+number);//获取行号是number的文本。 if(str==null) {str="#练习结束#练习结束#练习结束#练习结束#练习结束#再见#"; } StringTokenizer tokenizer=new StringTokenizer(str,"#");//分析该行文本。 while(tokenizer.hasMoreTokens()) {option[i]=tokenizer.nextToken();i++; } 题号=number.intValue(); session.setAttribute("答案"+题号,option[5]); //将该题答案存入session。 out.print("<BR>"+"试题"+number+"<BR>"+option[0]); out.print("<BR>请选择您的答案:"); out.print("<FORM action=Exercise.jsp method=post name=form>");
out.print("<BR>"+"<Input type=radio name=R value=A>"); out.print("A. "+option[1]); out.print("<BR>"+"<Input type=radio name=R value=B>"); out.print("B. "+option[2]); out.print("<BR>"+"<Input type=radio name=R value=C>"); out.print("C. "+option[3]); out.print("<BR>"+"<Input type= radio name=R value=D>"); out.print("D. "+option[4]); out.print("<BR>"+"<Input type=submit name=submit value=提交答案>"); out.print("</FORM>"); } %> <% String answer=request.getParameter("R");//获取客户提交的答案。 //获取题目的标准答案,需要注意的是:客户提交答案后,该页面就将题号增加1 // 因此,要给客户的上一题进行评判必须将题号减1。 String 答案=(String)session.getAttribute("答案"+(题号-1)); if(answer==null) {answer="您没有给出选择呢"; } if(answer.equals(答案)) { Integer score=(Integer)session.getAttribute("score"); score=new Integer(score.intValue()+1); session.setAttribute("score",score); } out.print("<BR>"+"您现在的得分是:"+session.getAttribute("score")); out.print("<BR>"+"你的上一题的选择是:"+answer); out.print("<BR>"+"上一题的正确答案是:"+答案); %> </BODY> </HTML> 4.4 回压字符流 称PushbackReader类创建的对象为回压字符流。回压流可以使用unread(char ch)将一个字符回压到该流中,被回压的字符是该回压流紧接着再调用read()方法时最先读出的字符。回压流可以用来监视读出的信息,当读出一个不需要的信息时,可以不处理该信息,而将需要的信息回压,然后再读出回压的信息。该类的构造方法是:
PushbackReader(Reader in);
当我们使用前面讲的字节输入流或字符输入流把JSP文件或超文本文件发送给客户时,客户的浏览器将解释运行超文本标记,客户无法看见原始的超文本文件和JSP文件。我们可以使用回压流技术,读取原始的网页文件,当读取到“<”符号时,将“<”回压、读取到“>”时,将“>”回压。 下面的例子9将JSP源文件显示给客户。 例子9(效果如图4.10所示) Example4_9.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <HTML> <BODY bgcolor=cyan><FONT size=1> <%File f=new File("d:/Tomcat/Jakarta-tomcat-4.0/webapps/root","Example2_4.jsp"); try{ FileReader in=new FileReader(f) ; PushbackReader push=new PushbackReader(in); int c; char b[]=new char[1]; while ( (c=push.read(b,0,1))!=-1)//读取1个字符放入字符数组b。 { String s=new String(b); if(s.equals("<")) //回压的条件 { push.unread('&'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. out.print(new String(b)); push.unread('L'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. out.print(new String(b)); push.unread('T'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. out.print(new String(b)); } else if(s.equals(">")) //回压的条件 { push.unread('&'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. out.print(new String(b)); push.unread('G'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. out.print(new String(b)); push.unread('T'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. out.print(new String(b)); } else if(s.equals("\n")) { out.print("<BR>"); } else {out.print(new String(b)); } } push.close(); } catch(IOException e){} %> </BODY> </HTML> 4.5 数据流 DataInputStream类和DataOutputStream类创建的对象被称为数据输入流和数据输出流。这两个流是很有用的两个流,它们允许程序按着机器无关的风格读取Java原始数据。也就是说,当我们读取一个数值时,不必再关心这个数值应当是多少个字节。 DataInputStream类和DataOutputStream的构造方法: DataInputStream(InputStream in):将创建的数据输入流指向一个由参数in指定的输入流,以便从后者读取数据(按着机器无关的风格读取)。 DataOutputStream(OutnputStream out):将创建的数据输出流指向一个由参数out指定的输出流,然后通过这个数据输出流把Java数据类型的数据写到输出流out。
DataInputStream类及DataOutputSteam的部分方法 l close() 关闭流 l readBoolean() 读取一个布尔值 l readByte() 读取一个字节 l readChar() 读取一个字符 l readDouble() 读取一个双精度浮点值 l readFloat() 读取一个单精度浮点值 l readInt() 从文件中读取一个int值 l readlong() 读取一个长型值 l readShort() 读取一个短型值 l ReadUnsignedByte() 读取一个无符号字节 l ReadUnsignedShort() 读取一个无符号短型值 l readUTF() 读取一个UTF字符串 l skipBytes(int n) 跳过给定数量的字节 l writeBoolean(boolean v) 把一个布尔值作为单字节值写入 l writeBytes(String s) 写入一个字符串 l writeChars(String s) 写入字符串 l writeDouble(double v) 写入一个双精度浮点值 l writeFloat(float v) 写入一个单精度浮点值 l writeInt(int v) 一个int值 l writeLong(long v) 一个长型值 l writeShort(int v) 一个短型值 l writeUTF(String s) 写入一个UTF字符串
下面的例子10使用数据流实现录入成绩单和显示成绩单。在Example4_10.jsp录入成绩单,连接到showresult.jsp页面查询成绩单。 例子10(效果如图4.11、4.12所示) Example4_10.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <HTML> <BODY> <P> 在下面的表格输入成绩: <FORM action="Example4_10.jsp" method=post name=form> <Table align="CENTER" Border> <TR> <TH width=50> 姓名</TH> <TH width=50> 数学</TH> <TH width=50>英语</TH> </TR> <% int i=0; while(i<=6) {out.print("<TR>"); out.print("<TD>"); out.print("<INPUT type=text name=name value=姓名>"); out.print("</TD>"); out.print("<TD>"); out.print("<INPUT type=text name=math value=0>"); out.print("</TD>"); out.print("<TD>"); out.print("<INPUT type=text name=english value=0>"); out.print("</TD>"); out.print("</TR>"); i++; } %> <TR> <TD> <INPUT type=submit name="g" value="写入成绩" > </TD> <TD> Math</TD> <TD> English</TD> </TR> </Table> </FORM> <% String name[]=request.getParameterValues("name"); String math[]=request.getParameterValues("math"); String english[]=request.getParameterValues("english"); try{ File f=new File("f:/2000","student.txt"); FileOutputStream o=new FileOutputStream(f); DataOutputStream DataOut=new DataOutputStream(o); for(int k=0;k<name.length;k++) {DataOut.writeUTF(name[k]); DataOut.writeUTF(math[k]); DataOut.writeUTF(english[k]); } DataOut.close(); o.close(); } catch(IOException e) { } catch(NullPointerException ee) { } %> <P><BR>查看成绩单: <A href=showresult.jsp><BR> 连接到成绩单页面> </BODY> </HTML>
showresult.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <HTML> <BODY> <P>成绩单: <% try{ File f=new File("f:/2000","student.txt"); FileInputStream in=new FileInputStream(f); DataInputStream DataIn=new DataInputStream(in); String name="ok"; String math="0",english="0"; out.print("<Table Border>"); out.print("<TR>"); out.print("<TH width=50> 姓名</TH>"); out.print("<TH width=50> 数学</TH>"); out.print("<TH width=50>英语</TH>"); out.print("</TR>"); while((name=DataIn.readUTF())!=null) { byte bb[]=name.getBytes("ISO-8859-1"); name=new String(bb); math=DataIn.readUTF(); english=DataIn.readUTF(); out.print("<TR>"); out.print("<TD width=200>"); out.print(name); out.print("</TD>"); out.print("<TD width=100>"); out.print(math); out.print("</TD>"); out.print("<TD width=100>"); out.print(english); out.print("</TD>"); out.print("</TR>"); } out.print("</Table>"); DataIn.close(); in.close(); } catch(IOException ee) { } %> </BODY> </HTML>
ObjectInputStream类和ObjectOutputStream类分别是DataInputStream类和DataOutputStream类的子类。ObjectInputStream类和ObjectOutputStream类创建的对象被称为对象输入流和对象输出流。对象输出流使用writeObject(Object obj)方法将一个对象obj写入到一个文件,对象输入流使用readObject()读取一个对象到程序中。 ObjectInputStream类和ObjectOutputStream类的构造方法分别是:
l ObjectInputStream(InputStream in); l ObjectOutputStream(OutputStream out);
ObjectOutputStream的指向应当是一个输出流对象,因此当准备将一个对象写入到文件时,首先用FileOutputStream创建一个文件输出流,如下列代码所示:
FileOutputStream file_out=new FileOutputStream("tom.txt"); ObjectOutputStream object_out=new ObjectOutputStream(file_out);
同样ObjectInputStream的指向应当是一个输入流对象,因此当准备从文件中读入一个对象到程序中时,首先用FileInputStream创建一个文件输入流,如下列代码所示:
FileInputStream file_in=new FileInputStream("tom.txt"); ObjectInputStream object_in=new ObjectInputStream(file_in);
在下面的例子11中我们使用对象流技术实现网上货单录入与查询,客户在Example4_11.jsp页面输入数据提交给input.jsp页面,input.jsp页面首先读取文件goods_name.txt中的散列表对象,如果文件不存在,该客户就是第一个录入货物的客户,就将录入的数据存入散列表,并将散列表写入新创建的文件goods_name.txt。如果该文件已经存在,客户就从文件goods_name.txt读出散列表,查找客户要存放的货物的货号是否已经存在,如果货号已经存在,客户新录入的数据就替换了旧的库存,然后将散列表写入到文件;如果货号不存在,客户就可将新的货号的库存信息存入到散列表,并将散列表写入到文件。 例子11中使用了散列表这种数据结构。使用java.util包中的Hashtable类来创建散列表对象,该类的常用方法如下: l public Hashtable(): 创建具有默认容量和装载因子为0.75的散列表。 l public Hashtable(int itialCapacity): 创建具有指定容量和装载因子为0.75的散列表。 l public Hashtable(int initialCapacity,float loadFactor): 创建具有默认容量和指定装载因子散列表。 l public void clear(): 清空散列表。 l public boolean contains(Object o): 判断散列表是否含有元素o。 l public Object get(Object key): 获取散列表中具有关键字key的数据项。 l public boolean isEmpty(): 判断散列表是否为空。 l public Object put(Object key,Object value): 向散列表添加数据项value并把关键字key关联到数据项value。 l public Object remove(Object key): 删除关键字是key的数据项。 l public int size(): 获取散列表中关键字的数目。 使用上述的get方法可以从散列表中检索某个数据。我们还可以借助Enumeration对象实现遍历散列表,一个散列表可以使用elements()方法获取一个Enumeration对象,后者使用nextElement()方法遍历散列表。
Example4_11.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <%@ page import ="java.util.*" %> <HTML> <BODY> <P> 输入货物有关信息: <FORM action="input.jsp" method=post > <P>货号: <INPUT type=text name="N"> <P>数量: <INPUT type=text name="M"> <BR> <INPUT type=submit value="提交"> </FORM> <FORM action="showgoods.jsp" method=post > <P>查看库存情况 <BR> <INPUT type=submit value="去查看库存"> </FORM> </BODY> </HTML>
input.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <%@ page import ="java.util.*" %> <HTML> <BODY> <%! //声明创建一个散列表对象,被所有的客户共享: Hashtable hashtable=new Hashtable(); //一个向散列表填加信息的同步方法: synchronized void putGoodsToHashtable(String key,String list) {hashtable.put(key,list); } //从散列表移去信息的同步方法: synchronized void removeGoodsToHashtable(String key) {hashtable.remove(key); } %> <%--获取客户提交的书号名字和数量--%> <% String name=request.getParameter("N"); if(name==null) {name="have no any goods number"; } byte c[]=name.getBytes("ISO-8859-1"); name=new String(c); String mount=request.getParameter("M"); if(mount==null) {mount="have no any recoder"; } byte d[]=mount.getBytes("ISO-8859-1"); mount=new String(d); %> <%--从文件中读散列表,如果文件不存在,你就是第一个录入的人,负责写散列表到文件--%> <%File f=new File("F:/2000","goods_name.txt"); if(f.exists()) { try{FileInputStream in=new FileInputStream(f); ObjectInputStream object_in=new ObjectInputStream(in); hashtable=(Hashtable)object_in.readObject(); object_in.close(); in.close(); String temp=(String)hashtable.get(name); if(temp==null) {temp=""; } if((temp.length())!=0) { out.print("<BR>"+"该货号已经存在,您已经修改了数量"); String s="货号:"+name+" ,数量:"+mount; removeGoodsToHashtable(name); //首先移去旧的信息。 putGoodsToHashtable(name,s); //填加新的信息。 //将新的散列表写入到文件: try{FileOutputStream o=new FileOutputStream(f); ObjectOutputStream object_out=new ObjectOutputStream(o); object_out.writeObject(hashtable); object_out.close(); o.close(); } catch(Exception eee) { } } else {String s="货号:"+name+" ,数量:"+mount; putGoodsToHashtable(name,s); //向散列表填加新的货物信息。 //再将新的散列表写入到文件: try{FileOutputStream o=new FileOutputStream(f); ObjectOutputStream object_out=new ObjectOutputStream(o); object_out.writeObject(hashtable); object_out.close(); o.close(); } catch(Exception eee) { } out.print("<BR>"+"您已经将货物存入文件"); out.print("<BR>"+"货物的货号:"+name); } } catch(IOException e) {} } else { //如果文件不存在,您就是第1个录入信息的人。 String s="货号:"+name+"数量:"+mount; putGoodsToHashtable(name,s);//放信息到散列表。 //负责创建文件,并将散列表写入到文件: try{ FileOutputStream o=new FileOutputStream(f); ObjectOutputStream object_out=new ObjectOutputStream(o); object_out.writeObject(hashtable); object_out.close(); o.close(); out.print("<BR>"+"您是第一个录入货物的人"); out.print("<BR>"+"货物的货号:"+name); } catch(Exception eee) { } } %> <FORM action="showgoods.jsp" method=post > <P>查看库存情况 <BR> <INPUT type=submit value="去查看库存"> </FORM> <A href="Example4_11.jsp"><BR>返回录入页面 </BODY> </HTML>
showgoods.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <%@ page import ="java.util.*" %> <HTML> <BODY> <P> 已有货物的有关信息: <% try{File f=new File("F:/2000","goods_name.txt"); FileInputStream in=new FileInputStream(f); ObjectInputStream object_in=new ObjectInputStream(in); Hashtable hashtable=(Hashtable)object_in.readObject(); object_in.close(); in.close(); Enumeration enum=hashtable.elements(); while(enum.hasMoreElements()) //遍历当前散列表。 { String goods=(String)enum.nextElement(); out.print("<BR>"+"货号及库存:"+goods); } hashtable.clear(); } catch(ClassNotFoundException event) { out.println("无法读出"); } catch(IOException event) {out.println("无法读出"); } %> <A href="Example4_11.jsp"><BR>返回录入页面 </BODY> </HTML> 4.7 RandomAccessFile类 前面我们学习了用来处理文件的几个文件输入流、文件输出流,而且通过一些例子,已经了解了那些流的功能。 RandomAccessFile类创建的流与前面的输入、输出流不同,RandomAccessFile类既不是输入流类InputStream类的子类,也不是输出流类OutputStram类的子类流。习惯上,仍然称RandomAccessFile类创建的对象为一个流,RandomAccessFile流的指向既可以作为源也可以作为目的地,换句话说,当我们想对一个文件进行读写操作时,我们可以创建一个指向该文件的RandomAccessFile流即可,这样我们既可以从这个流中读取这个文件的数据,也通过这个流写入数据给这个文件。 RandomAccessFile类的两个构造方法: l RandomAccessFile(String name,String mode):参数name用来确定一个文件名,给出创建的流的源(也是流目的地),参数mode取“r”(只读)或“rw”(可读写),决定创建的流对文件的访问权利。 l RandomAccessFile(File file,String mode): 参数file是一个File对象,给出创建的流的源(也是流目的地),参数mode取“r”(只读)或“rw”(可读写),决定创建的流对文件的访问权利。创建对象时应捕获FileNotFoundException异常,当流进行读写操作时,应捕获IOException异常。 RandomAccessFile类中有一个方法seek(long a),用来移动RandomAccessFile流指向的文件的指针,其中参数 a 确定文件指针距离文件开头的字节位置。另外流还可以调用getFilePointer()方法获取当前文件的指针的位置(RandomAccessFile类的一些方法见下表),RandomAccessFile流对文件的读写比顺序读写的文件输入输出流更为灵活。 RandomAccessFile类的常用方法 l close() 关闭文件 l getFilePointer() 获取文件指针的位置 l length() 获取文件的长度 l read() 从文件中读取一个字节的数据 l readBoolean() 从文件中读取一个布尔值,0代表false;其他值代表true l readByte() 从文件中读取一个字节 l readChar() 从文件中读取一个字符(2个字节) l readDouble() 从文件中读取一个双精度浮点值(8个字节) l readFloat() 从文件中读取一个单精度浮点值(4个字节) l readFully(byte b[]) 读b.length字节放入数组b,完全填满该数组 l readInt() 从文件中读取一个int值(4个字节) l readLine() 从文件中读取一个文本行 l readlong() 从文件中读取一个长型值(8个字节) l readShort() 从文件中读取一个短型值(2个字节) l readUTF() 从文件中读取一个UTF字符串 l seek() 定位文件指针在文件中的位置 l setLength(long newlength) 设置文件的长度 l skipBytes(int n) 在文件中跳过给定数量的字节 l write(byte b[]) 写b.length个字节到文件 l writeBoolean(boolean v) 把一个布尔值作为单字节值写入文件 l writeByte(int v) 向文件写入一个字节 l writeBytes(String s) 向文件写入一个字符串 l writeChar(char c) 向文件写入一个字符 l writeChars(String s) 向文件写入一个作为字符数据的字符串 l writeDouble(double v) 向文件写入一个双精度浮点值 l writeFloat(float v) 向文件写入一个单精度浮点值 l writeInt(int v) 向文件写入一个int值 l writeLong(long v) 向文件写入一个长型int值 l writeShort(int v) 向文件写入一个短型int值 l writeUTF(String s) 写入一个UTF字符串
下面的例子实现网上小说创作,每个客户都可以参与一部小说的写作,也就是说一个客户需接着前一个客户的写作内容继续写作。在服务器的某个目录下有4部小说,小说的内容完全由客户来决定。客户首先在Example4_12.jsp页面选择一部小说的名字,然后连接到continueWrite.jsp页面,该页面显示小说的已有内容,客户可以在该页面输入续写的内容,再提交给continue.jsp页面,该页面负责将续写的内容存入文件,并通知客户续写是否成功,如果其他用户正在保存续写的内容到该小说,就通知该客户等待。
例子12(效果如图4.15、4.16、4.17所示) Example4_12.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <HTML> <BODY bgcolor=cyan><Font size=1> <% String str=response.encodeURL("continueWrite.jsp"); %> <P>选择您想续写小说的名字:<BR> <FORM action="<%=str%>" method=post name=form> <BR><INPUT type="radio" name="R" value="spring.doc" >美丽的故事 <BR><INPUT type="radio" name="R" value="summer.doc" >火热的夏天 <BR><INPUT type="radio" name="R" value="autumn.doc" >秋天的收获 <BR><INPUT type="radio" name="R" value="winter.doc" >冬天的大雪 <BR> <INPUT type=submit name ="g" value="提交"> </FORM> </BODY> </HTML> continueWrite.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <HTML> <BODY bgcolor=cyan><Font size=1> <P>小说已有内容: <Font size=1 Color=Navy> <%String str=response.encodeURL("continue.jsp"); %> <%--获取客户提交的小说的名字--%> <%String name=(String)request.getParameter("R"); if(name==null) {name=""; } byte c[]=name.getBytes("ISO-8859-1"); name=new String(c); session.setAttribute("name",name); File storyFileDir=new File("F:/2000","story"); storyFileDir.mkdir(); File f=new File(storyFileDir,name); //列出小说的内容: try{ RandomAccessFile file= new RandomAccessFile(f,"r"); String temp=null; while((temp=file.readUTF())!=null) { byte d[]=temp.getBytes("ISO-8859-1"); temp=new String(d); out.print("<BR>"+temp); } file.close(); } catch(IOException e){} %> </Font> <P>请输入续写的新内容: <Form action="<%=str%>" method=post name=form> <TEXTAREA name="messages" ROWs="12" COLS=80 WRAP="physical"> </TEXTAREA> <BR> <INPUT type="submit" value="提交信息" name="submit"> </FORM>
continue.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <%@ page import ="java.util.*" %> <HTML> <BODY> <%! //声明一个需同步处理的文件: File f=null; String use="yes"; %> <%--获取客户提交的小说的名字--%> <% String name=(String)session.getAttribute("name"); byte c[]=name.getBytes("ISO-8859-1"); name=new String(c); //获取客户续写的内容: String content=(String)request.getParameter("messages"); if(content==null) {content=" "; } %> <%File storyFileDir=new File("F:/2000","story"); storyFileDir.mkdir(); f=new File(storyFileDir,name); //把对文件的操作放入一个同步块中,并通知 //其它用户该文件正在被操作中: if(use.startsWith("yes")) { synchronized(f) { use="beisusing"; try{ RandomAccessFile file=new RandomAccessFile(f,"rw"); file.seek(file.length()); //定位到文件的末尾。 file.writeUTF(content); file.close(); use="yes"; out.print("<BR>"+"内容已经写入"); } catch(IOException e){} } } //如果该小说正在被续写,就通知客户等待: else {out.print("该小说正在被续写,请等待"); } %> </BODY> </HTML> 4.8 文件上传 客户通过一个JSP页面,上传文件给服务器时,该JSP页面必须含有File类型的表单,并且表单必须将ENCTYPE的属性值设成“multipart/form-data”,File类型表单如下所示:
<Form action= “接受上传文件的页面” method= “post” ENCTYPE=“ multipart/form-data” <Input Type= “File” name= “picture” > </Form>
JSP引擎可以让内置对象request调用方法getInputStream()获得一个输入流,通过这个输入流读入客户上传的全部信息,包括文件的内容以及表单域的信息。 下面的例子13中,客户通过Example4_13.jsp页面上传如下的文本文件A.txt。 A.txt: 你好,我们正在学习文件的上传,request调用getInpuStream()可以获得一个输入流,通过这个输入流可以读取客户上传的全部信息,包括表单的头信息以及上传文件的内容。以后将讨论如何去掉表单的信息,获取文件的内容。 在accept.jsp页面,内置对象request调用方法getInputStream()获得一个输入流in、用FileOutputStream创建一个输出流o。输入流in读取客户上传的信息,输出流o将读取的信息写入文件B.txt,该文件B.txt被存放于服务器的F:/2000中。B.txt的内容如图4.20所示。
例子13(效果如图4.18、4.19、4.20所示) Example4_13.jsp: <%@ page contentType="text/html;charset=GB2312" %> <HTML> <BODY> <P>选择要上传的文件:<BR> <FORM action="accept.jsp" method="post" ENCTYPE="multipart/form-data"> <INPUT type=FILE name="boy" size="38"> <BR> <INPUT type="submit" name ="g" value="提交"> </BODY> </HTML>
accept.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <HTML> <BODY> <%try{ InputStream in=request.getInputStream(); File f=new File("F:/2000","B.txt"); FileOutputStream o=new FileOutputStream(f); byte b[]=new byte[1000]; int n; while((n=in.read(b))!=-1) {o.write(b,0,n); } o.close(); in.close(); } catch(IOException ee){} out.print("文件已上传"); %> </BODY> </HTML> 通过上面的讨论我们知道,按着HTTP协议,文件表单提交的信息中,前4行和后面的5行是表单本身的信息,中间部分才是客户提交的文件的内容。在下面的例子中我们通过输入输出流技术获取文件的内容,即去掉表单的信息。 首先,我们将客户提交的全部信息保存为一个临时文件,该文件的名字是客户的session对象的Id,不同客户的这个Id是不同的。然后读取该文件的第2行,这一行中含有客户上传的文件的名字,获取这个名字。再获取第4行结束的位置,以及倒数第6行的结束位置,因为这两个位置之间的内容是上传文件的内容。然后将这部分内容存入文件,该文件的名字和客户上传的文件的名字保持一致。最后删除临时文件。 在下面的例子14中,客户上传一个图象文件,还可以连接到showImage.jsp页面查看这个上传图象的效果。我们可以允许客户将文件上传到服务器的任何一个目录,为了让客户能查看上传图象的效果,下面的例子14将上传文件保存到一个web服务目录D:/tomcat/jakarta-tomcat-4.0/webapps/examples中,假设服务器的IP是:192.168.0.100。
例子14(效果如图4.21、4.22、4.23所示) Example4_14.jsp: <%@ page contentType="text/html;charset=GB2312" %> <HTML> <BODY> <% String str=response.encodeURL("acceptFile.jsp"); %> <P>选择要上传的文件:<BR> <FORM action="<%=str %>" method="post" ENCTYPE="multipart/form-data"> <INPUT type=FILE name="boy" size="45"> <BR> <INPUT type="submit" name ="g" value="提交"> </FORM> </BODY> </HTML> acceptFile.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <HTML> <BODY> <%try{ //用客户的session的id建立一个临时文件: String tempFileName=(String)session.getId(); //建立临时文件f1: File f1=new File("D:/Tomcat/jakarta-tomcat-4.0/webapps/examples/",tempFileName); FileOutputStream o=new FileOutputStream(f1); //将客户上传的全部信息存入f1: InputStream in=request.getInputStream(); byte b[]=new byte[10000]; int n; while( (n=in.read(b))!=-1) {o.write(b,0,n); } o.close();in.close(); //读取临时文件f1,从中获取上传文件的名字,和上传的文件的内容: RandomAccessFile random=new RandomAccessFile(f1,"r"); //读出f1的第2行,析取出上传文件的名字: int second=1; String secondLine=null; while(second<=2) {secondLine=random.readLine(); second++; } //获取第2行中目录符号'\'最后出现的位置 int position=secondLine.lastIndexOf('\\'); //客户上传的文件的名字是: String fileName=secondLine.substring(position+1,secondLine.length()-1); random.seek(0); //再定位到文件f1的开头。 //获取第4行回车符号的位置: long forthEndPosition=0; int forth=1; while((n=random.readByte())!=-1&&(forth<=4)) { if(n=='\n') { forthEndPosition=random.getFilePointer(); forth++; } } //根据客户上传文件的名字,将该文件存入磁盘: File f2=new File("D:/Tomcat/jakarta-tomcat-4.0/webapps/examples/",fileName); session.setAttribute("Name",fileName);//供showImage.jsp页面使用。 RandomAccessFile random2=new RandomAccessFile(f2,"rw"); //确定出文件f1中包含客户上传的文件的内容的最后位置,即倒数第6行。 random.seek(random.length()); long endPosition=random.getFilePointer(); long mark=endPosition; int j=1; while((mark>=0)&&(j<=6)) { mark--; random.seek(mark); n=random.readByte(); if(n=='\n') { endPosition=random.getFilePointer(); j++; } } //将random流指向文件f1的第4行结束的位置: random.seek(forthEndPosition); long startPoint=random.getFilePointer(); //从f1读出客户上传的文件存入f2(读取从第4行结束位置和倒数第6行之间的内容)。 while(startPoint<endPosition-1) { n=random.readByte(); random2.write(n); startPoint=random.getFilePointer(); } random2.close();random.close(); f1.delete(); //删除临时文件 } catch(IOException ee){} out.print("文件已上传"); %> <P> 查看上传的图象效果 <%String str=response.encodeURL("showImage.jsp"); %> <FORM action="<%=str%>"> <Input type=submit value="查看"> </FOrm > </BODY> </HTML> showImage.jsp: <HTML> <BODY> <% String name=(String)session.getAttribute("Name"); if(name==null) {name=""; } out.print("<image src=http://192.168.1.100:8080/examples/"+name); %> </BODY> </HTML> 4.9 文件下载 JSP内置对象response调用方法getOutputStream()可以获取一个指向客户的输出流,服务器将文件写入这个流,客户就可以下载这个文件了。 当JSP页面提供下载功能时,应当使用response对象向客户发送HTTP头信息,说明文件的MIME类型,这样客户的浏览器就会调用相应的外部程序打开下载的文件。例如,Ms-Word文件的MIME类型是application/msword,pdf文件的MIME类型是application/pdf。点击点击资源管理器→工具→文件夹选项→文件类型可以查看文件的相应的MIME类型。
例子15(效果如图4.24、4.25所示) Example4_15.jsp: <%@ page contentType="text/html;charset=GB2312" %> <HTML> <BODY> <P>点击超链接下载Zip文档book.Zip <BR> <A href="loadFile.jsp">下载book.zip </Body> </HTML> loadFile.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import="java.io.*" %> <HTML> <BODY> <% //获得响应客户的输出流: OutputStream o=response.getOutputStream(); //输出文件用的字节数组,每次发送500个字节到输出流: byte b[]=new byte[500]; //下载的文件: File fileLoad=new File("f:/2000","book.zip"); //客户使用保存文件的对话框: response.setHeader("Content-disposition","attachment;filename="+"book.zip"); //通知客户文件的MIME类型: response.setContentType("application/x-tar"); //通知客户文件的长度: long fileLength=fileLoad.length(); String length=String.valueOf(fileLength); response.setHeader("Content_Length",length); //读取文件book.zip,并发送给客户下载: FileInputStream in=new FileInputStream(fileLoad); int n=0; while((n=in.read(b))!=-1) { o.write(b,0,n); } %> </BODY> </HTML> 注:如果在fileLoad.jsp中取消下列语句:
response.setHeader("Content-disposition","attachment;filename="+"book.zip");
那么客户的浏览器将调用相应的外部程序,在当前位置直接打开下载的文件。 4.10 分页读取文件 当读取一个较大的文件时,比如想让客户阅读一部小说,我们希望分页地读取该文件。可以借助session对象实现分页读取文件。当客户向JSP页面发出请求时,JSP页面建立一个指向该文件的输入流,通过文件流每次读取文件的若干行。 我们已经知道HTTP协议是一种无状态协议。一个客户向服务器发出请求(request)然后服务器返回响应(respons),连接就被关闭了。在服务器端不保留连接的有关信息,因此当下一次连接时,服务器已没有以前的连接信息了,无法判断这一次连接和以前的连接是否属于同一客户。也就是说,如果我们请求每次读取10行,那么第一次请求会读取文件的前10行,当我们第2次请求时,JSP页面会重新将输入流指向文件,这样,我们第2次读取的内容和第一次读取的完全相同,仍是文件的前10行。因此,必须使用会话来记录有关连接的信息。当客户第一次请求该页面时,创建指向文件的输入流连接,然后将这个输入流保存到客户的会话中,当客户再次请求这个页面时,直接使用客户会话中的输入流继续读取的文件的后续10行就可以了。 另外,为了能读取JSP文件,我们还需对读出的文本进行回压流处理。 下面例子16实现了分页读取文件。 例子16(效果如图4.26所示) readFileByLine.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <%! //对字符串进行回压流处理的方法: public String getString(String content) { try{ StringReader in=new StringReader(content) ;//指向字符串的字符流。 PushbackReader push=new PushbackReader(in); //回压流 StringBuffer stringbuffer=new StringBuffer(); //缓冲字符串对象。 int c; char b[]=new char[1]; while ( (c=push.read(b,0,1))!=-1)//读取1个字符放入字符数组b。 { String s=new String(b); if(s.equals("<")) //回压的条件 { push.unread('&'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); push.unread('L'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); push.unread('T'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); } else if(s.equals(">")) //回压的条件 { push.unread('&'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); push.unread('G'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); push.unread('T'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); } else if(s.equals("\n")) { stringbuffer.append("<BR>"); } else { stringbuffer.append(s); } } push.close(); in.close(); return new String(stringbuffer); //返回处理后的字符串。 } catch(IOException e) {return content=new String("不能读取内容"); } } %> <% String s=request.getParameter("g"); //获取客户提交的信息(是否重新读取文件) if(s==null) {s=""; } byte b[]=s.getBytes("ISO-8859-1"); s=new String(b); File f=null; FileReader in=null; BufferedReader buffer=null; if(session.isNew()) //当第一次请求该页面时,建立和文件的输入流连接。 { f=new File("f:/javabook","JSP教程.txt"); in=new FileReader(f); buffer=new BufferedReader(in); session.setAttribute("file",f); session.setAttribute("in",in); session.setAttribute("buffer",buffer); } if(s.equals("重新读取文件")) //当请求重新读取文件时,建立和文件的输入流连接。 { f=new File("f:/javabook","JSP教程.txt"); in=new FileReader(f); buffer=new BufferedReader(in); session.setAttribute("file",f); session.setAttribute("in",in); session.setAttribute("buffer",buffer); } //将上述对象保存到用户的session 对象中: try{ String str=null; int i=1; f=(File)session.getAttribute("file"); in=(FileReader)session.getAttribute("in"); buffer=(BufferedReader)session.getAttribute("buffer"); while( ((str=buffer.readLine())!=null)&&(i<=5)) { //为了能显示原始的HTML文件或JSP文件需使用回压流技术。 str=getString(str); out.print("<BR>"+str); i++; } } catch(IOException e) { out.print("文件不存在,或读取出现问题"); } %> <%String code=response.encodeURL("readFileByLine.jsp"); %> <HTML> <BODY> <P><BR>点击按钮读取下5行: <FORM action="<%=code%>" method="post" name=form> <Input type=submit value="读取文件的下5行"> </FORM> <P><BR>点击按钮读重新读取文件: <FORM action="" method="post" name=form> <Input type=submit name="g" value="重新读取文件"> </FORM> </BODY> </HTML> 4.11 标准化考试 大部分网络上的标准化考试试题都是使用数据库技术实现的,使用数据库易编写代码,但降低了效率,因为打开一个数据库连接的时间要远远慢于打开一个文件。这一节,我们结合Java的流技术实现一个标准化网络考试。 用一个文件输入流每次读取一道试题。 试题文件的书写格式是: 第一行必须是全部试题的答案(用来判定考试者的分数)。 每道题目之间用一行:*****分割(至少含有两个**)。 试题可以是一套标准的英语测试题,包括单词测试,阅读理解等(也可以在文件的最后给出整套试题的全部答案)。所用试题English.txt如图4.27所示.。下述的ttt.jsp存放在Root目录中,English.txt存放在F:/2000中。
标准化考试(效果如图4.28所示) ttt.jsp: <%@ page contentType="text/html;charset=GB2312" %> <%@ page import ="java.io.*" %> <%@ page import ="java.util.*" %> <%! String answer=null;//存放答案用的字符串。 //对字符串进行回压流处理的方法: public String getString(String content) { try{ StringReader in=new StringReader(content) ;//指向字符串的字符流。 PushbackReader push=new PushbackReader(in); //回压流 StringBuffer stringbuffer=new StringBuffer(); //缓冲字符串对象。 int c; char b[]=new char[1]; while ( (c=push.read(b,0,1))!=-1)//读取1个字符放入字符数组b。 { String s=new String(b); if(s.equals("<")) //回压的条件 { push.unread('&'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); push.unread('L'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); push.unread('T'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); } else if(s.equals(">")) //回压的条件 { push.unread('&'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); push.unread('G'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); push.unread('T'); push.read(b,0,1); //push读出被回压的字符字节,放入数组b. stringbuffer.append(new String(b)); } else if(s.equals("\n")) { stringbuffer.append("<BR>"); } else { stringbuffer.append(s); } } push.close(); in.close(); return new String(stringbuffer); //返回处理后的字符串。 } catch(IOException e) {return content=new String("不能读取内容"); } } %> <% String s=request.getParameter("g"); //获取客户提交的信息(是否重新读取文件) if(s==null) {s=""; } byte b[]=s.getBytes("ISO-8859-1"); s=new String(b); File f=null; FileReader in=null; BufferedReader buffer=null; Integer number=new Integer(0); //初始题号。 Integer score=new Integer(0);//初始分数。 if(session.isNew()) //当第一次请求该页面时,建立和文件的输入流连接。 { f=new File("f:/2000","English.txt"); in=new FileReader(f); buffer=new BufferedReader(in); //读入文件的第1行:答案 answer=buffer.readLine().trim();; //将上述f、in、buffer对象保存到用户的session 对象中: session.setAttribute("file",f); session.setAttribute("in",in); session.setAttribute("buffer",buffer); //再将初始题号保存到session对象中: session.setAttribute("number",number); //再将用户的初始得分保存到session对象中: session.setAttribute("score",score); } if(s.equals("重新练习")) //当请求重新读取文件时,建立和文件的输入流连接。 { f=new File("f:/2000","English.txt"); in=new FileReader(f); buffer=new BufferedReader(in); //读入文件的第1行:答案 answer=buffer.readLine().trim(); //将上述f、in、buffer对象保存到用户的session 对象中: session.setAttribute("file",f); session.setAttribute("in",in); session.setAttribute("buffer",buffer); //再将初始题号保存到session对象中: session.setAttribute("number",number); //再将用户的初始得分保存到session对象中: session.setAttribute("score",score); } //读取试题: try{ String str=null; f=(File)session.getAttribute("file"); in=(FileReader)session.getAttribute("in"); buffer=(BufferedReader)session.getAttribute("buffer"); while((str=buffer.readLine())!=null) { //为了能显示原始的HTML文件或JSP文件需使用回压流技术。 str=getString(str); if(str.startsWith("**")) //每个试题的结束标志。 {break; } out.print("<BR>"+str); } } catch(IOException e) { out.print(""+e); } %> <%String code=response.encodeURL("ttt.jsp"); %> <HTML> <BODY><Font size=1> <P><BR>请选择答案: <FORM action="<%=code%>" method="post" name=form> <Input type=radio name="r" value="A">A <Input type=radio name="r" value="B">B <Input type=radio name="r" value="C">C <Input type=radio name="r" value="D">D <Input type=submit name="submit" value="提交答案"> </FORM> <% //当用户提交表单时,获取提交的答案: //判断用户是否提交了答案表单: String select=request.getParameter("submit"); //获取客户提交的答案选择表单 if(select==null) {select=""; } byte c[]=select.getBytes("ISO-8859-1"); select=new String(c); if(select.equals("提交答案")) { String userAnswer=request.getParameter("r"); if(userAnswer==null) { userAnswer="#"; } //将提交的答案与正确答案进行比较: //首先获取题号: Integer num=(Integer)session.getAttribute("number"); int tihao=num.intValue(); //获取相应题号的标准答案: char correctAnswer='\0'; try{ correctAnswer=answer.charAt(tihao); } catch(StringIndexOutOfBoundsException ee) { tihao=0; } //然后再将题号重新存入session对象: tihao=tihao+1; Integer newNumber=new Integer(tihao); session.setAttribute("number",newNumber); //将用户提交的答案与标准答案比较 char user=userAnswer.charAt(0); if(user==correctAnswer) { //给用户增加分值: Integer newScore=(Integer)session.getAttribute("score"); int fenshu=newScore.intValue(); fenshu=fenshu+1; newScore=new Integer(fenshu); session.setAttribute("score",newScore); out.print("您答对了,您现在的得分是:"); out.print(""+fenshu); } else { out.print("您没有答对,继续努力!"); out.print("您现在的得分是:"+session.getAttribute("score")); } } %> <P><BR>点击按钮重新练习: <FORM action="" method="post" name=form> <Input type=submit name="g" value="重新练习"> </FORM> </FONT> </BODY> </HTML> |