记录一个java文件传输不完整问题

场景/现象描述

生产进行文件同步时,遇到一个文件传输相关的问题,代码逻辑大致如下:

  1. 从数据中筛选数据
  2. 将数据写入到指定文件放到本地服务器中
  3. 将本地服务器中的文件,使用ftp的方式传入到目标服务器中(其他系统)

逻辑很简单对吧,问题是这样:
发现目标服务器中出现较多的文件大小为40960B,且文件最后一行数据被截断了,并不完整。
我方本地服务器中的文件大小是41000+ 到42000+不等
即,文件传输并不完整。

排查过程

  1. 最开始时,认为是传输时有问题,文件上传是自己封装在jar包里的方法,传入文件本地路径、目标服务器地址+路径、用户名/密码就可以传输,内部逻辑有分片、断点续传等。排查与调试后发现jar是正确的,各种场景都可以正确上传。
  2. 后来怀疑是文件在传输时没写完,传输结束才写完。又因为系统架构是双机的,文件目录挂载了NAS,且步骤2与步骤3是用feign调用的(即走负载均衡),所以怀疑是NAS同步是否有延迟?就在代码逻辑的步骤2与步骤3前后都打印了一下文件MD5,发现文件MD5也是一致的,说明文件前后并没有发生变化。
  3. 再后来,发现走了弯路,不是说文件大小不对么,那么在步骤2/3前后都增加以下文件大小打印:
File file = new File();
logger.info("文件大小:"+file.length);
  1. 结果出乎预料,发现文件在步骤2结束时,feign调用步骤3进行传输前,大小就是40960B。
  2. 排查代码后发现,步骤2中写文件使用的是FileWriter 处理的文件,一眼看过去好像是没什么问题。
FileWriter fw = null;
try{
	//步骤2--写文件
	
	//步骤3--调用feign上传文件
	
}catch(Exception e){
	//异常处理
}finally{
	IoUtil.close(fw);
}
  1. 但仔细看就会发现,步骤2与步骤3都在一个try里面包裹,FileWriter 流关闭是在finally里面,且步骤2与步骤3中,并没有对FileWriter 进行flush处理
  2. FileWriter 流关闭时,会自动调用一次flush方法,所以本地文件的大小是正确的,但执行步骤2与步骤3时,文件的大小就是40960B,因为剩下的一千多B还在缓冲区中,并没有被推出到文件中,因为没有调用flush方法。

解决方法

在步骤2与步骤3中间,增加一个flush调用,将缓冲区中最后一个部分的流推出到文件中。

fw.flush();

思考

造成问题的原因:
FileWriter 的缓冲区默认应该是8192(8K),FileWriter写文件时,每达到一次缓冲区大小,自动将缓冲区的内容推到文件中,例如:当文件总大小是42625B时,FileWriter 会自动将前5次推到文件中(40960B),第6次时,因为不满8192B,所以还在缓冲区中,直到调用flush时才推出(close方法会先flush一下再关闭流)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
JAVA WEB框架,java网站一个模块只用写一个文件 以前的servlet在现在的开发中已经不怎么常见,因为操作起来比较原始和麻烦。有些人就是不安于现状去改造它。 做得好的有Struts,Hybernate,Spring那么这些框架都是很成功的,但是它们在使用的时候都少不了要配置这配置那搞得初学者晕头转向的。那么黄迎斌在些推出最近自己研发一套框架,欢迎大家使用。 此框架将数据库操作简化为0,基本不用管数据库,后台模块只用写一个文件! 框架基础: jdk1.6以上版本, 数据库mysql5.5以上或者access2003以上 netbeans IDE7.0以上。 tomcat7.0以上 java ee 6.0 说明: 这个案例是黄迎斌封装servlet的成功案例,下面就其配置做详细说明。 使用规范: 1.数据表的名字必需和实体POJO类的名字一致。POJO类可以通过另一工具DBReverse自动生成。例如:数据库有user表那么必需有一个POJO类名为User.jsva(大小写忽略) 2.所有表单里面的参数名必需和数据库里面的字段名一致。例如添加一个用户的时候,user表中有name和id两个字段,那么在表单中必需有两个<input>name分别为name和id(大小写忽略) 3.每一个控制层的跳转必需要带上参数mode,mode可为: OTHER//其它,SHOWONE//显示单例,SHOWLIST//显示列表, ADD //添加记录,UPDATE//更新记录, EDIT//编辑记录,DELETE//删除记录(大小写忽略) 包介绍: baseservlet 封装servlet的包不用改,直接引用。 |___DataGet.java封装了request.getParameter的方法。直接给一个实例,它自动根据传来的参数把值set到相应的成员表变中去。 |___GetAdm.java封装了权限获得的方法。有一个常量MANAGEERPOWER数值为管理员权限。做项目时自行设置。(修改“=”后的值就可以) |___HyberbinServlet.java封装了Servlet,以后写的sevlet直接继承这个类就可以。里面有一个抽象类execute,在继承的类中必需实现这个方法。 database 封装了数据库的所有操作,每次配置数据库的时候只需要修改DatabaseINI.java中的相关配置就可以,其它的直接引用。 |___DatabaseAccess.java 封装了底层数据库的操作,可以用来更新、修改、删除、查询操作。注意:这个类只允许DatabaseINI.java调用其它任何类调用都是不规范的。 |___DatabaseINI.java 封装了获得DatabaseAccess的方法,每次项目只需修改dbUrl,dbType,user,pass参数就可以。要获得DatabaseAccess对象只需调用getDatabase()方法就可以。 |___GetSql.java 自动生成sql语句。在本框架中基本上不直接使用。 |___Hyberbin.java 进一步封装了数据库的操作,用户不直接对数据库操作,只要给出实体POJO类,数据可以自动查询、修改、删除、插入 servlet 用户自己的包,完成相应模块的功能。 |___Szdw.java POJO类,对应数据库中szdw表(名字一致),里面所有成员变量都对应数据库表中一个字段。 |___SzdwServlet.java 用户自己写的servlet,在public class 上面一行有如下说明:@WebServlet(name = "SzdwServlet", urlPatterns = {"/Szdw.jsp"})告诉tomcat这个servlet的名字和路径。 框架流程详解: 用户请求:(必需有mode告诉servlet请求类型)-》servlet(获得请求类型转化为event)->调用execute方法(解析event)->调用相应的方法-》发送数据到用户浏览器。 例如本案例中是师资队伍中的相关模块。 要显示整个师资队伍,那么流程如下: 1.用户请求:http://localhost:8080/HybServlet/Szdw.jsp?mode=showlist (注意:必带mode) 2.servlet获得请求类型转化为event=SHOWLIST=2; 3.执行execute方法,解析到SHOWLIST要完成以下动作showlist();send = "szdw.jsp";break;,showlist()中完成收集数据存储在request对象中。 4.send到szdw.jsp即用户看到的师资队伍。 上面的流程servlet除了mode没有需要得到上文有关数据,所以在execute中不需要调用load(formbean)方法。 下面看一下另一种情况,添加一个老师到师资队伍: 1.用户请求:action=Szdw.jsp?mode=add 填写相关的表单,例如xm(姓名),xb(性别)……(表单的name和数据库中字段保持一致) 2.servlet获得请求类型转化为event=ADD=3; 3.执行execute方法,解析到ADD要完成以下动作load(szdw);add();showlist();send = "szdw.jsp";break;,注意load()方法自动将相关参数set到formbean中去不需要用户再调用request.getParameter()。 4.send到szdw.jsp即用户看到的师资队伍。 还有一种情况就是用户所需要接收的参数不在数据库表中,那么调用load()是得不到效果的。那么这时候自己可以按以前的方法用request.getParameter()去获得 Hyberbin工具详解: Hyberbin.java进一步封装了数据库的操作,用户不直接对数据库操作,给数据库的操作带来了极大的简便。其使用方法如下。 1.例如当前要向数据库表szdw中添加数据: 那么在构造Hyberbin的时候需要给出需要插入的szdw数据放在szdw的POJO类中去,还要给出szdw表的主键是id. 现有 Szdw szdw;里面有所有关于要插入这个表的教师的相关信息 构造Hyberbin:Hyberbin hyberbin=new Hyberbin(szdw,"id"); 插入:boolean b = hyberbin.addByNoKey();//因为主键是自动生成不需要加所以是addByNoKey 返回的布尔值告诉用户是否成功。 2.例如要修改师资队伍中id=5的教师信息: 同样在构造Hyberbin的时候需要给出需要修改的数据放在szdw的POJO类中去,还要给出szdw表的主键是id. 现有 Szdw szdw;里面有所有关于要插入这个表的教师的相关信息,包括这个老师的id是等于5的。 构造Hyberbin:Hyberbin hyberbin=new Hyberbin(szdw,"id"); 修改: boolean b = hyberbin.updata(); 返回的布尔值告诉用户是否成功。 3.显示一个id=5的教师的信息 同样在构造Hyberbin的时候需要给出需要查询的数据将放在szdw的POJO类中,szdw可以没有任何信息. 构造Hyberbin:Hyberbin hyberbin=new Hyberbin(new Szdw(),"id"); 查询: szdw=hyberbin.showOne(5+"");//默认id是字符串,如果是数字就加一个""空引号就行 szdw里面就存有所查询到的结果。 4.删除id=5的教师信息 同样在构造Hyberbin的时候需要给出需要删除的数据将放在szdw的POJO类中,szdw可以没有任何信息. 构造Hyberbin:Hyberbin hyberbin=new Hyberbin(new Szdw(),"id"); 修改: boolean b = hyberbin.dell(5+""); 5.要显示所有教师信息 同样在构造Hyberbin的时候需要给出需要查询的数据在哪个表(szdw)中,szdw可以没有任何信息. 构造Hyberbin:Hyberbin hyberbin=new Hyberbin(new Szdw(),"id"); 查询:LinkedList<Object> list = hyberbin.showAll(); 查询结果放在一个链表中了 用的时候用强制类型转换 把Object转换为Szdw就行了。 以在的操作是不是有局限性?删除非要按ID删除?查询的结果不能筛选? 作者已经考虑到这个,下面介绍使用附件。 public void setHql(String Hql) { this.Hql = Hql; } 只要你调用了 setHql(String Hql)的方法,那么它会按照你的hql语句执行查询或者删除修改等操作。 public void setOrder(String order) { this.order = order; } 只要你调用 setOrder(String order) 的方法,查询的结果将按照你给的排序方法排序。 ————————————————————————————————呵呵,一切就这么简单,just do it! hyberbin 2011.10.25 14:12
1. 概述 该资源主要是为Java Web系统的毕业设计所准备。该资源集中为学生提供了完整的毕业设计辅助材料,从论文、设计文档到源代码等内容,都是为了使学生能够更好地完成毕业设计。 2. 包含内容 论文:这部分包含了对整个Java Web系统的概述,设计的目标、需求分析、系统分析与设计方法、相关的技术研究和背景知识等。学生可以通过这些论文了解到项目的整体框架和设计思路。 设计文档:详细记录了系统的设计过程,包括但不限于需求文档、系统架构设计、数据库设计、界面设计、功能模块设计等。学生可以根据这些文档进行系统的具体实现和调试。 源代码:提供了整个Java Web系统的完整代码,从前端的HTML、CSS、JavaScript到后端的Java代码和数据库脚本等。学生可以直接使用这些代码作为参考或者在此基础上进行修改和优化。 辅助信息:可能会包括与项目相关的其他资料,如技术文档、API手册、第三方库的使用指南等。帮助学生在实现过程中解决技术难题。 3. 使用方法 学生可以首先通过阅读论文来了解项目的背景和目标,然后参照设计文档进行代码的实现。在遇到技术难题时,可以查阅辅助信息中的资料。源代码部分可以作为参考,学生可以基于其进行扩展或修改。 4. 注意事项 学生在使用这些资源时应确保遵循学校和课程的相关规定,如防止抄袭、确保原创等。 在参考源代码进行编程时,需要确保理解其工作原理和逻辑,避免仅仅复制和粘贴。 希望这种资源描述能够满足您的需求!如果还有其他的疑问或者需要进一步的描述,请告诉我。
假设我们有一个包含大模型文件文件夹,该文件夹名为"model",其中包含一个名为"model.bin"的文件。这个文件夹的完整路径为"C:\data\model"。 下面是一个示例代码,用于通过HTTP协议实现Java断点续传,其中包含大模型文件文件夹路径为上述路径: ```java import java.io.*; import java.net.*; public class ResumeDownloadExample { public static void main(String[] args) throws Exception { String fileURL = "http://localhost:8080/download/model.bin"; String saveDir = "C:\\data\\model"; URL url = new URL(fileURL); HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); // 设置断点续传起始位置 File file = new File(saveDir + File.separator + "model.bin"); long startPos = 0; if (file.exists()) { startPos = file.length(); httpConn.setRequestProperty("Range", "bytes=" + startPos + "-"); } int responseCode = httpConn.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_PARTIAL) { String disposition = httpConn.getHeaderField("Content-Disposition"); String contentType = httpConn.getContentType(); int contentLength = httpConn.getContentLength(); System.out.println("Content-Type = " + contentType); System.out.println("Content-Disposition = " + disposition); System.out.println("Content-Length = " + contentLength); // 下载文件 InputStream inputStream = httpConn.getInputStream(); RandomAccessFile outputStream = new RandomAccessFile(file, "rw"); outputStream.seek(startPos); byte[] buffer = new byte[4096]; int bytesRead = -1; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.close(); inputStream.close(); System.out.println("文件下载完成"); } else { System.out.println("服务器返回错误:" + responseCode); } httpConn.disconnect(); } } ``` 在这个示例中,我们通过HTTP协议从服务器上下载名为"model.bin"的文件。如果文件夹中已经存在该文件,程序会设置HTTP请求头中的"Range"字段,以便从上次中断的位置继续下载。在下载过程中,程序会将文件分成多个部分进行传输,并在传输过程中记录每个部分的传输状态,以便在传输中断后能够恢复传输。最终,程序会将所有部分的数据合并成完整文件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值