- 前言
随意玩玩,顺便了解下io流。
- 正文
Java中存在File类,构造时候传入路径,即可锁定文件,如果不存在可以自己创建。
读写文件的时候使用FileInputStream和FileOutputStream两个类,他们都是基于字节读取的。
下面是一个demo
package FileDemo; import java.io.File; public class FileDemo1 { public static void main(String[] args) { File f = new File("/Users/MacBook/Downloads/mv.zip"); System.out.println(f.exists()); System.out.println(f.getAbsolutePath()); System.out.println(f.length()); } }
exist()判断文件是否存在,返回true或者false;
getAbsolutePath()返回文件的绝对路径;
length()返回的是long类型的文件长度,我的是64位系统,通过Long.Max_Value查看long的上限大概是2的63次方,它的长度计算起来大概是很大的GB数,所以大小完全不用担心。
文件读写的demo
package FileDemo; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /** * 文件读写demo * @author ctk * */ public class FileRWDemo { public static void main(String[] args) { File f= new File("src/FileDemo/hello.txt");//虚拟路径从项目下开始 File nf = new File("src/FileDemo/temp.txt"); long fsize = f.length(); FileInputStream fins = null; FileOutputStream fous = null ; System.out.println(f.getAbsolutePath()); System.out.println(fsize); try { if(!nf.exists()) nf.createNewFile(); fins = new FileInputStream(f); fous = new FileOutputStream(nf); long index = 0; int readSize = 20; while(index != fsize){ if((fsize - index) < 20) readSize = (int)(fsize - index); byte[] b = new byte[readSize]; fins.read(b); System.out.println(new String(b)); index = index + readSize; } // fous.write(b); // fous.write(b); } catch (Exception e) { e.printStackTrace(); } finally { try { if(fins != null) fins.close(); if(fous != null) fous.close(); } catch (IOException e) { e.printStackTrace(); } } } }
我的读写模型是从文件读出了20个byte然后将20个字节写入新文件中,类似于滑动窗口的思路只是不做校验。文件指针使用int来记录,int的范围保证不会溢出的必要条件就是每次读取的byte数<Integer.Max_Value就可以了,使用index来记录主要是记录读取的进度,也可以考虑做成断点续传。
一个断点续传的方法就是申请上传的时候把服务器的文件指针置于客户端index位置,“在通信时候双方协调成想要的效果”就是设计进程间通信的一个思路。而为了使得上传文件可用,必须加入校验index之前的文件是否与客户端的index文件一致,简称一致性校验。一致性校验的方法可以使逐个byte比较;如果为了减少通信成本,则可以使用md5这种类型的计算结果比较。
综合这些细节,文件上传这个过程大概是:用户发起上传请求->服务器响应已准备好->用户发送文件名与大小->服务器检查在用户可操作空间内是否存在文件,没有则创建,完成工作后返回接受正文数据的ack->用户开始读取文件数据并写入上传通道中->...不断重复直到文件上传结束。断点续传可以是一个附加功能:如果上传的时候实现这个过程,则传给服务器文件指针并且做index前的md5校验和发送校验码,如果index属于合法范围(0,len),并且服务器存在该文件和md5的校验正确,则服务器可以从index位置开始写入数据。
上传与下载其实是一个功能,明其一即可。
然后回到文件读写,经过这次研究,我确信这些读写通道都十分的“懒”,如果你不flush或者不readfully,他们就会偷懒,但是只会在特定的情景有影响,比如在写的过程中使用size和byte的混合传输的时候。“懒”就是存在数据缓冲区,读的时候把任务订在哪儿了,但是这句代码执行完的时候却没有把任务为完成。
下面是文件的一个demo
package FileDemo; import java.io.File; import java.io.IOException; /** * 获取目录下的所有文件 * @author ctk * */ public class FileDemo1 { public static void main(String[] args) throws IOException { File f = new File("/Users/MacBook/Downloads"); File[] files = f.listFiles(); for(int i=0;i<files.length;i++) { System.out.println("filesName:"+files[i].getName()); System.out.println("isDir:"+files[i].isDirectory()); } } }
listFiles这个方法返回此文件(目录)下所有的子文件,问我好用么?肯定好用撒,做linux在线命令行服务器的时候就用到了。
此外,文件还有getParent,获取上级菜单,太方便了。
对此功能的联想?做个在线存储空间可以吧?百度云盘之类的,如果是文件点击还可以做下载。为此必须确保在设计的时候多个连接为多个线程,并且线程独享一个“用户当前路径”,这个设计和linux的terminal的思路是一样的。你可以设计用户可访问空间,让它不能操作系统文件就行,一个在线存储服务器的模型大概就浮现在脑中了。
下面是一个socket读写demo的片段
if(f.exists()){ FileInputStream fins = new FileInputStream(f); long fsize = f.length(); int sendSize = 50000; long index = 0; double advance = 0;//进度 ous.write((f.getName()+"\r\n").getBytes());//发送文件名 LogFile.appendRecord("开始上传文件:"+f.getName()); ous.flush(); byte[] b = new byte[sendSize]; while(index != fsize){ if(fsize - index < 50000){ sendSize = (int)(fsize - index); } System.out.println("发送长度:"+sendSize); dous.writeInt(sendSize);//发送长度 dous.flush(); msg = ins.readLine(); System.out.println("收到信息:"+msg); fins.read(b,0,sendSize); System.out.println("发送数据"); dous.write(b,0,sendSize);//发送数据 dous.flush(); msg = ins.readLine(); System.out.println("收到信息:"+msg); index = index + sendSize; advance = (index*1.0)/(fsize); System.out.println("进度:"+advance*100); } dous.writeInt(-1);//发送结束 fins.close(); LogFile.appendRecord("上传完成:"+f.getName()); }
//创建路径 File dir = new File("src"+ServerSetup.separator+"file"+socket.getInetAddress().toString()); if(!dir.exists()) dir.mkdir(); String fileName = ins.readLine(); File f = new File(dir.getPath()+ServerSetup.separator+fileName); FileOutputStream fos = null; if(!f.exists()) f.createNewFile(); fos = new FileOutputStream(f); DataInputStream dins = new DataInputStream(socket.getInputStream()); //上传接收 int size = 0; int maxSize = 100000; byte[] temp = new byte[maxSize]; while(true){ size = dins.readInt(); System.out.println("收到size:"+size); ous.write(("长度-"+StatusCode.Ack.getName()).getBytes()); ous.flush(); if(size <= 0 || size >maxSize) { ous.write(-1); ous.flush(); break; } dins.readFully(temp,0,size); fos.write(temp,0,size); fos.flush(); ous.write(("数据写入-"+StatusCode.Ack.getName()).getBytes()); ous.flush(); } fos.close(); if(size == 0){ LogFile.appendRecord("文件不存在,客户端已取消连接"); }else if(size == -1) LogFile.appendRecord("接受到文件:"+fileName); else LogFile.appendRecord("一次发送文件过长:"+size);
最开始我使用的是read方法,它的“懒惰”使得这个程序在一次传输大byte的时候经常出现size出错,因为size+byte可能会在byte没读完的时候开始执行readInt方法,就读到了byte的范围内不是正确的size了,导致进程间不能很好的交流。所以为了避免“偷懒”我使用readFully方法,它可以一次性读满后执行下一句。
另外文件读取流中,有一个参数可以设置末尾添加或者从头写入,意思就是初始化这个对象的时候指针实在0或者是在length位置。每一个读写流在生命开始的时候它就会有一个位置,你可以skip,在你执行程序的读的时候它会移动,到末尾了可以reset,但是读到哪里它就记录到哪里。
下面是一个linux服务器的demo
package package3.server; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** * 文件命令服务器 * @author ctk */ public class CommandServer { public static void main(String[] args) { ServerSocket server = null; try { server = new ServerSocket(9999); System.out.println("server setup successful!"); } catch (IOException e) { e.printStackTrace(); } Socket client = null; BufferedReader ins = null; DataOutputStream dous = null; try { client = server.accept(); ins = new BufferedReader(new InputStreamReader(client.getInputStream(),"UTF-8")); dous = new DataOutputStream(client.getOutputStream()); while(true){ String cmd = ins.readLine(); System.out.println("server recv cmd:"+cmd); if(cmd.equals("hello")) { dous.write("hello\r\n".getBytes()); dous.flush(); }else if(cmd.equals("ls")){ File f = new File("/Users/MacBook/Downloads"); File[] files = f.listFiles(); dous.writeInt(files.length); dous.flush(); for(int i=0;i<files.length;i++){ dous.write((files[i].getName()+"\r\n").getBytes()); } }else if(cmd.equals("bye")){ dous.write("bye\r\n".getBytes()); dous.flush(); break; }else if(cmd != null && cmd.equals("")){ continue; }else dous.write("unknow cmd \r\n".getBytes()); } } catch (IOException e) { e.printStackTrace(); } finally { try{ dous.close(); ins.close(); client.close(); }catch (IOException e) { e.printStackTrace(); } } } }
当输入ls的时候,将“当前目录”的所有子文件输出回去,不过这个demo只会固定输出一个目录,因为没做当前目录切换。
package package3.client; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.Socket; import java.net.UnknownHostException; /** * 文件命令客户端 * @author ctk */ public class CommandClient { public static void main(String[] args) { Socket client = null; try { client = new Socket("localhost",9999); System.out.println("connect server successful!"); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } DataInputStream dins = null; DataOutputStream dous = null; BufferedReader cmdReader =new BufferedReader(new InputStreamReader(System.in)); try { dins = new DataInputStream(client.getInputStream()); dous = new DataOutputStream(client.getOutputStream()); BufferedReader bins = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); while(true){ String cmd = cmdReader.readLine(); if(cmd == null) break; else if(cmd.equals("")) continue; if(cmd.equals("ls")){ dous.write((cmd+"\r\n").getBytes()); int len = dins.readInt(); for(int i = 0;i<len;i++){ System.out.println(bins.readLine()); } }else { dous.write((cmd+"\r\n").getBytes()); String msg = bins.readLine(); System.out.println("client recv :"+msg); } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { dins.close(); dous.close(); client.close(); } catch (IOException e) { e.printStackTrace(); } } } }
我的客户端是支持命令行输入的,不知道变成linux的插件有没有可能,好像Java这个方面会比较差,可能考虑使用Python或者C来做试试看。
命令行服务器我的设想是匹配命令解析字符串并执行相应的功能,如果要像linux这样高效的话可能不是匹配字符串这个思路,或者它使用高效的字符串匹配算法,如KMP。
- 后记
闲暇时候需做些有趣的事情。