您正在查看: 2015年11月

编程札记(一)

Overview

之前跟Young一起做上一个项目时,遇到和解决了很多小的但是又很常见的编程问题,做完项目想总结一下的时候又发现这是一系列问题,没有太多明确的界限,又好像存在于编程的每一个细节中,我觉得应该统称为编程的基础。
一直说要写,可总觉得无从下笔。现在下决心尝试一下,想到哪里就写到哪里吧~

1. 计算机的结构

现在的计算机依然采用的是冯·诺依曼提出的计算机体系结构。计算机由控制器、运算器、存储器、输入设备、输出设备五部分组成。其中控制器和运算器一起组成了CPU,存储器可以分为内存和硬盘,输入设备就是鼠标键盘麦克风等,输出设备主要是显示器一类的设备。

1.1 内存和硬盘的区别:

写这个的时候我内心是挣扎的,因为可能一眼看起来显得很low,但是这确实是我在接触计算机的过程中遇到的一个困惑,在这里记下来,说不上能解决谁的困惑呢。

简单来说,内存和硬盘都是用来存储东西的,只是由于他们的材质不同,导致他们存储东西的失效和存取效率不同,因此在计算机中的用途也就不同。

内存的主要材质是RAM(Ramdom Access Memory,即随机存取存储器),特点是存储空间小,高速存取,读写时间相等,且与地址无关(即可以随机存取),易挥发性。但是存储信息具有瞬时性,断电之后信息就会完全丢失,这就是为什么台式机忽然断电,你正在编辑的东西如果没保存就全没了,除非你已经保存到了硬盘上。

与RAM容易混淆的是ROM(Read Only Memory,即只读存储器),从它的名字就可以知道,ROM与RAM最大的区别在于ROM是只读的,但是断电之后信息不丢失,因此通常被用来做计算机启动用的BIOS芯片,存取速度相对于RAM来说比较低。
这里说的ROM的只读,并不是说完全不能写,BIOS里的信息不就是写进去的嘛,而且我们经常也会听到刷BOIS,其实就是把BOIS的信息更改,也就是更新ROM中的信息。只是相对于RAM,ROM不适合频繁写而已。

硬盘的特点是存储空间大(现在家用硬盘都是1T+),按址存取,存取速度慢,永久保存数据。在出现固态硬盘前,机械硬盘就是硬盘的代名词,跟RAM可以说是对立的存在。
机械硬盘的工作原理跟CD,唱片机等一样,就是一个盘在转,有一个接触盘的磁头读取接触到的区域的信息。只是机械硬盘是一个立体的结构,里面有很多层类似于盘的面,信息的读取就是靠磁头的移动和磁盘的转动实现。知道了机械硬盘的结构,也就能理解它的特点了,因为磁头要动,盘面要转动,所以机械硬盘的动静很大,发热多,不耐摔(一不小心就把磁头摔坏了)。 速度也很大程度上取决于盘面的旋转速度,盘面旋转速度越快,读取信息自然也就越快,买机械硬盘时,硬盘转速可以说是最重要的指标,5400转/秒(没有更低的了),7200转/秒(主流),12000转/秒(企业级硬盘)。

固态硬盘的存在可以说是内存和机械硬盘的折衷,存储空间没有机械硬盘大,但是可以实现随机读取,性能上相对于机械硬盘有了质的飞越,由于固态硬盘没有了磁盘和磁头这些东西,所以机械硬盘那一系列缺点固态硬盘都没有,一定要说缺点的话,那就是存储空间还不够大,贵。不过从它的缺点来看,它应该是未来的趋势,毕竟有些缺点代表过去,而价格贵空间不够大这些问题,未来多半都能解决,从CPU,硬盘的发展我们已经无数次的看到了这一点。

一句话解释就是,开着计算机,操作系统和其他程序运行时需要的东西都要load到内存中,想要永久保存的东西,都需要从内存中写到硬盘上。

既然所有的程序都是CPU计算,内存只是帮CPU存着结果,那一个程序运行时,CPU和内存以及硬盘又是怎么协作的呢。

1.2 CPU、内存和硬盘

在任何一本计算机组成原理教材中,输入和输出设备都是很简略的,因为真的没有太多需要说的,如果一定要说,我更想解释机械键盘,高清显示屏这些东西对程序员的重要性~

一个程序在运行时(不考虑输入和输出),实际上就是CPU、内存和硬盘的协作过程。 计算机要执行一个程序,需要先把这个程序,程序运行需要的数据等从硬盘导入到内存中,运行结束之后,产生的数据还需要重新写入硬盘永久保存。因此,内存可以看做是CPU和硬盘之间的混缓冲池,CPU是不会直接和硬盘交换数据的,因为硬盘读取的速度太慢了。

但是CPU和内存之间的交换依然存在问题,因为内存这些年的发展也就是变大变大变大,在读写速度上没有什么改进,毕竟材质摆在那里。而CPU的发展日新月异,不只主频越来越高,CPU核数也越来越多,计算能力越来越强,单位时间可以计算的数据量也就越来越大。CPU实际在做运算时,是需要把数据从内存load到CPU中的寄存器中的,随着CPU性能的提升,单位时间在寄存器和内存之间传输的数据量也越来越大,一边算一边取肯定不行,因为CPU就要闲着等数据了。所以CPU在寄存器和内存之间也设置了一个缓冲,那就是CPU缓存。这个缓存是在CPU中的,用来预取内存中的数据,供CPU使用,这样CPU在使用时就不用从内存中获取数据了,而是先从CPU缓存中获取数据。

CPU缓存对CPU性能的影响是至关重要的,CPU读取数据的方式变成了:先去缓存中找,没有命中,再去内存中取,如果缓存中有,就可以直接取到放到寄存器中。如果CPU缓存频繁的不命中,就变成了每次CPU读取数据,都多做了一次无用功,因此CPU缓存的命中率十分重要。不过现在这部分已经相当成熟了,CPU的缓存命中率可以达到95%以上,甚至更高(99%)。

CPU缓存的设计很直观,最早的时候只有一级缓存,CPU在缓存中找不到想要的数据就得去内存了。而且为了保证缓存命中时的速度,一级缓存通常都很小(不然弄个足够大的,程序需要的数据都预取过来,肯定命中啊,但是命中之后取数据的速率会降低),因此命中率不是很高,再后来就有了2级缓存,比一级缓存大,但是存取速度比一级缓存慢一点点,但是肯定比内存存取快得多,这样CPU在一级缓存中找不到的时候,会再去二级缓存找,还找不到的话才会去内存中找。二级可能还不够用,后来CPU又有了三级缓存。当然,也不是越多越好,通常三级缓存就足够了,既保证了命中率,又不至于影响存取速度。
下面是一个主流的Intel CPU的部分配置:

Intel 酷睿i7 4790K:
CPU主频    4GHz
核心数量    四核心 
线程数    八线程
制作工艺    22纳米
三级缓存    8MB 

可以看到,三级缓存也不过8M,跟内存的大小相比,简直可以忽略不计。

跟存取有关的另一个问题就是程序的局部性原理

程序的局部性原理是指程序总是趋向于使用最近使用过的数据和指令,也就是说程序执行时所访问的存储器地址分布不是随机的,而是相对地簇集,这种簇集包括指令和数据两部分。
程序局部性包括程序的时间局部性和程序的空间局部性。
    1. 程序的时间局部性: 是指程序即将用到的信息可能就是目前正在使用的信息。
    2. 程序的空间局部性: 是指程序即将用到的信息可能与目前正在使用的信息在空间上相邻或者临近。

这种局部性很大程度上是由循环语句造成的,在编程中,循环的比例极其高,而循环使得程序具有很好的局部性:程序指令反复执行,数据存储很有规律性。良好的编程规范会极大地提升缓存的使用效率,而缓存能够拥有高命中率的基础也正是程序的局部性原理。

这也是GOTO语句有害性的主要依据,GOTO语句使得程序的执行变得很难提前预测,增加了缓存预测的难度,因此GOTO语句在很多语言中都成了过去。

这里不再展开讲编程对CPU缓存以及内存的影响,有兴趣的话可以自己搜索~

1.3 内存的使用

不同操作系统中对于内存的使用是完全不同的:

  • Windows是按需使用,你打开QQ,操作系统就会把QQ程序和需要的数据等载入内存,你关了QQ,Windows就会把QQ使用的内存释放掉,所以Windows的内存使用量就是实际的使用量,如果你的内存使用比很高,说明你的内存应该升级了。
  • Mac采用不同的内存使用策略,那就是你打开一个程序之后,就算你已经关了这个程序,这个程序实际上依然在内存中,这样当你下次再打开的时候,这个程序响应时间会非常短,就好像你本来就开着一样。实际上它确实一直就开着,常驻内存,只是你不知道而已。
    当然,当开的程序足够多时,内存总是会被占满,这时候Mac系统会用一个内存压缩技术把已经不用的程序压缩,给新程序腾出空间。
    所以,买Mac的话内存配置高一点总是好的,不会浪费。

实际上,无论什么系统,计算机配置多大内存,内存总有占满的时候,那么当内存不够用的时候,操作系统就需要将一部分程序的内存腾出来给新的程序用,这部分内存储存的信息会被写到硬盘上暂存,这块硬盘上的区域有一个专有的名字,叫做交换区(Swap Area)

在linux下,使用free -h命令就可以看到下面的信息:

             total       used       free     shared    buffers     cached
Mem:          992M       909M        82M        14M       133M       216M
-/+ buffers/cache:       560M       432M
Swap:           0B         0B         0B

其中的Swap就是交换区的大小,一般来说Swap used是0,说明内存充足,如果used很大,说明内存不够用,频繁有程序被换出内存。

如果内存不够用了,需要将别的程序占用的内存放到交换区,那么操作系统应该怎么操作呢?
我们举一个极端点的例子,假设程序可用内存为500M,程序A需要使用300M,程序B需要使用250M,那么如果A在运行时,B也运行,系统应该怎么办。

直观的想法就是把A换出去,把B放进来。这样做会有两个问题:一是不管是A在运行,还是B在内存中运行,实际上都是有很多空闲内存未使用的;二是现在的系统都是分式运行的,每个程序都分配时间片,如果A和B同时运行,那么程序会A和B交替运行,这样A和B在内存中换来换去,效率极低,显然是不合理的。

解决的方法就是把内存划分成小份,每个程序拥有很多份内存,就算需要换出去,也只需要换出一部分就可以了,这个思路被称为分页,操作系统会将内存划分成一样大小的小份,每一份被称为一个页面,分页对用户程序是透明的,是一种物理结构的内存划分。这种内存管理方式也叫分页存储管理。但是分页系统也会有别的问题,假设页面大小是4KB,一个程序占用内存10K,那么就需要分配给这个程序3个页面,第3个页面就有2K的空间是没有使用的,也就是有2K的空间是被浪费的,这些被浪费的空间被称为内存碎片,因为碎片是在页面里面的,所以分页产生的内存碎片也叫内碎片

可以看到,页面既不能太大,也不能太小。太小的话一个程序分到的页面会特别多,不方便管理,页表也会很大;太大的话,最后一个页面利用率低,可能产生很多内存碎片,浪费内存空间。

有了页表管理之后,就实现了对内存的细粒度管理,给程序分配内存空间时也不需要连续的内存空间了(因为页表会索引分配的页)。另外,当内存满了之后,将内存交换出去的单位也是页面,使得交换内存的粒度也很小。

当然,还有段式内存管理,现代操作系统大多采用的也都是段页结合,这一就不再详细说明,可以参考 http://blog.csdn.net/wangrunmin/article/details/7967293

1.4 32位系统 or 64位系统

32位和64位的系统都需要配合CPU使用,如果你的CPU是32位的,那么装64位作用也不大。但是现在的CPU一般都支持64位系统。
两者的区别主要有两点,一是寻址能力,一是计算能力。

寻址能力:

  • 32位系统可以寻址的空间是$2^{32}$,即4G的空间,如果你使用了32位的系统,那么最多只可以用4G的内存,更多的内存装上也是无法识别的。
  • 64位系统可以寻址的空间是$2^{64}$,即16TB,当然这只是理论上而已。以Windows 7为例,家庭普通版能支持8GB内存,家庭高级版能支持16GB内存,而64位的Windows7专业版、企业版和旗舰版最高可支持192GB内存。

计算能力:
64位平台上的运行性能要远超过32位平台。原因在与CPU通用寄存器的数据位宽,64位平台是64位,而32位平台是32位,运算时理论上性能会相应提升1倍。实际上,在64位Windows7下运行32位的应用软件并不会让你感觉到性能的飞跃,只有64位的应用软件才能最大化发挥64位平台的优势。但显而易见,目前64位的应用程序在种类的数量上都要远低于32位平台。

PhosphoPrediction项目总结-前端

Overview

PhosphoPrediction这个项目是我们两个开发的第一个项目,整体进度比较快,但是因为是第一次,很多小细节花了不少的时间,当然,每一次细节的修改都有新知识的补充。
这里我主要记录下前端的一些知识点以备后面项目使用。

1. 长字符串自动换行的问题

在结果页面中,我们不只在页面上显示了预测结果,还将原始序列,Native Disorder等信息显示在了页面,由于这些序列特别长,所以在显示的时候就会出现一些问题,比如会显示为一个不换行的序列,导致页面出现了横向滚动条,非常不美观。
我们希望这个长序列可以在超出容器大小的时候自动换行,而不是把容器撑大。
解决方法:
给盛放长字符串内容的容器(可以是<div>或者<td>等)添加下面的样式:

word-wrap : break-word;
word-break: break-all;

这两个属性就是为了处理长字符串自动换行而存在的,break-word表示一个长句子换行时,在两个单词之间出现换行,而不会把一个单词断开换行,比如有一个句子I am a student from China!,换行的时候会变成

I am a
student from
China!

或者

I am a
student
from
China!

但是,不会出现下面的换行

I am
a stud
ent from
China!

也就是说,word-wrap : break-word;会保持单词的完整性,让每个单词都在同一行,然后产生换行。通常情形下,这样做是好的选择,美观又不影响阅读。但是,如果一个单词特别长呢,比如一个100个字母的单词出现了,而这个容器宽度只能容纳50个字母,那应该怎么断行?依然不会断行,这个长单词就会把容器撑大,让它的宽度变成100个字母的长度。
很不幸,我们处理的序列就是这样的情况,序列长度通常有几百个字母甚至更多,而且中间没有空格。
这时候我们需要word-break: break-all;,从字面意思就可以看出,强制一切换行,超出容器宽度,就自动换行。例子就不举了,看一下服务器的结果页面就可以看到效果。

2.关于margin和padding

这两个属性对于页面布局的细节调整非常的重要,比如说页面的页边距,容器内部内容跟容器的边距等等。
用一幅图就可以直观地表示:

margin-padding.jpg

margin就是一个容器的外边距,是这个容器离包含它的容器的距离;而padding就是容器的内边距,就是容器的内容距这个容器的距离。border就是这个容器的边,图上画的比较大就是为了突出一点显示而已。

3.<div>居中

通常,为了美观,我们不会将页面的内容以100%的宽度铺在浏览器中,而是在左右各留出一段空白,在中间显示内容。
我们可以在<body>标签中,使用一个<div>,为这个div添加下面的样式:

width: 80%;
height: 100%; 
margin-left:auto;
margin-right: auto;

可以看出,我们将这个<div>宽度设置成上层容器(这里就是<body>)的80%,这样<body><div>中间就有20%的空白。 我们将<div>的外边距设置成auto,浏览器会自动设置,让<div><body>中居中,也就是在左右两边各留10%的空白。
注意,这里的margin一定不要使用具体的数值,原因是在一个屏幕上可能留100px的左边空白就可以居中,而在更大的屏幕上100px可能不能让内容在屏幕的正中央。

PhosphoPrediction项目总结

Overview

PhosphoPrediction项目是Chris和我做的一个新项目,主要是为本地客户端程序添加一个相同功能的web server。由于出差新疆,只能晚上回酒店自己加班写代码,在Chris的帮助下,前前后后忙了大约两周总算有了个不错的小成果,心中颇感欣慰。这段时间,Chris不仅给了我技术上的指导,更给我排解了心中的许多烦恼,在此感谢我最好的朋友Chris(我知道你不喜欢我当面夸奖感谢你,哈哈)。

1.Java web服务器实现的功能

1.1 前端接收序列传入后台

很多类似的项目一次只能接收一条序列,本项目可以同时接收多条序列,包含名字的FASTA格式的序列和不包含名字的raw序列。在webserver界面可以选择model,默认为ATM;同时可以选择threshold,默认全部显示。

1.2 后台处理序列,并展示在jsp页面

后台共接收3个参数,即seqStringmodelthreshold,并生成getterssetters

private String seqString;
private String model;
private double threshold;

public String getSeqString() {
    return seqString;
}
public void setSeqString(String seqString) {
    this.seqString = seqString;
}

public String getModel() {
    return model;
}
public void setModel(String model) {
    this.model = model;
}

    public double getThreshold() {
    return threshold;
}
public void setThreshold(double threshold) {
    this.threshold = threshold;
}

我们在前端得到的threshold,仅仅是double类型的value,我们处理成String类型并展示在result界面。我们可以根据选择的阈值threshold,展示我们所需要的结果。为此,我们将处理result的模块剥离出来,放在formatResults.java类中。该类提供两个方法,是重载关系,唯一的区别是传入的参数threshold

//不包含threshold的方法,得到全部的sites对应的results
public ArrayList<Result> getFormatResults(String originSeqString, String model) {
    RemoteWork robot = new RemoteWork();
    ArrayList<Result> results = robot.formatInput(originSeqString);
    ArrayList<Result> allResults = new ArrayList<>();
    String seq_name="";
    String seq="";
    int input_count=1;
    String input_replace="";
    Result res;
    for(int i=0; i<results.size(); i++){
        res=results.get(i);
        if(res.getName().substring(0, 5).equals("input")) {
            input_replace = "-" + input_count;
            res.setName(res.getName().replaceAll("-input", input_replace)+" *");
            input_count++;
        }
        seq_name = res.getName();
        seq = res.getSeq();
        
        res.setDiso(robot.runDiso(seq));
        
        res.setSs(robot.runSable_ss(seq));
        
        res.setSa(robot.runSable_sa(seq));
        
        robot.getFunc(seq_name);
        
        res.setSites(robot.Predict(seq, model, seq_name));
        allResults.add(res);
    }
    return allResults;
}

//包含threshold的方法,得到筛选的sites对应的results
public ArrayList<Result> getFormatResults(String originSeqString, String originModel, double originThreshold) {
    ArrayList<Result> allResults = getFormatResults(originSeqString, originModel);
    
    ArrayList<Result> formatResults = new ArrayList<>();
    PredictionSite site=new PredictionSite();
    
    for(Result result:allResults) {
        ArrayList<PredictionSite> selectedPredictionSites = new ArrayList<>();
        for(int j=0; j<result.getSites().size(); j++) {
            site=result.getSites().get(j);
            double d=Double.valueOf(site.getProb());
            if(originThreshold==0) {
                selectedPredictionSites.add(site);
            }
            else if(originThreshold==0.3){
                if(!(d<0.3)){
                    selectedPredictionSites.add(site);
                }
            }
            else if(originThreshold==0.5){
                if(!(d<0.5)){
                    selectedPredictionSites.add(site);
                }
            }
            else if(originThreshold==0.8){
                if(!(d<0.8)){
                    selectedPredictionSites.add(site);
                }
            }
        }
        result.setSites(selectedPredictionSites);
        formatResults.add(result);
    }
    
    return formatResults;
}

action中得到formatResults后,在jsp页面中循环,这里用到了jstl表达式,需要引入包jstl.jar,可以点击这里下载jsp页面的头部加上这么一句:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>


<c:forEach items="${formatResults }" var="res">
    <b> ${res.getName() } </b>
    <c:forEach items="${res.getSites() }" var="site">
        <c:out value="${site.getRank() }"/>
        <c:out value="${site.getPos() }"/>
        <c:out value="${site.getLeft().substring(0, 4) }"/></span><span class="emphasisLetter"> ${site.getLeft().substring(4) } </span><span class="constant"><c:out value="${site.getRight() }"/></span>     
        <c:out value="${site.getProb() }"/>
    </c:forEach>

然后显示出Original SequenceNative DisorderSecondary StructureSolvent Accessibility

    <c:out value="${res.getSeq() }"/>
    <c:out value="${res.getDiso() }"/>
    <c:out value="${res.getSs() }"/>
    <c:out value="${res.getSa() }"/>
</c:forEach>

这里面JSTL的详细用法,可参考JSTL标签库学习总结

1.3 jsp页面同时添加导出结果按钮

添加导出为exceltxt两个功能的按钮。不在服务器端生成文件,而在客户端在线实时生成,节约服务器资源。

//结果写入excel文件
public InputStream getExcelFile() {  
    HttpSession session = ServletActionContext.getRequest().getSession();
    formatResults=(ArrayList<Result>)session.getAttribute("formatResults"); 
    
    HSSFWorkbook workbook = new HSSFWorkbook();
    HSSFSheet sheet = workbook.createSheet("sheet1");
    Result result;
    PredictionSite site;
    int k=0;
    for(int i=0; i<formatResults.size(); i++){
        
        result=formatResults.get(i);
        HSSFRow row = sheet.createRow(k++);
        HSSFCell cell = row.createCell(0);
        cell.setCellValue(result.getName());
        row = sheet.createRow(k++);
        cell = row.createCell(0);  
        cell.setCellValue("Rank");  
        cell = row.createCell(1);  
        cell.setCellValue("Position");  
        cell = row.createCell(2);  
        cell.setCellValue("Surrounding Sequence");  
        cell = row.createCell(3);  
        cell.setCellValue("Probability Score"); 
        for( int j=0;j<result.getSites().size();j++ ){
            site=result.getSites().get(j);
            
            row = sheet.createRow(k++);
            cell = row.createCell(0);
            cell.setCellValue(site.getRank());  
            cell = row.createCell(1);  
            cell.setCellValue(site.getPos());  
            cell = row.createCell(2);  
            cell.setCellValue(site.getLeft()+site.getRight());  
            cell = row.createCell(3);  
            cell.setCellValue(site.getProb());
            
        }
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue("Original Sequence:"); 
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue(result.getSeq()); 
        
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue("Native Disorder:"); 
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue(result.getDiso()); 
        
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue("Secondary Structure:"); 
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue(result.getSs()); 
        
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue("Solvent Accessibility:"); 
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue(result.getSa()); 
        //换行,打印空行
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue("\n");
    }

    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    try {  
        workbook.write(baos);  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
    byte[] ba = baos.toByteArray();  
    ByteArrayInputStream bais = new ByteArrayInputStream(ba);
    return bais;  
    }
public String downloadExcel() throws Exception{
    return SUCCESS;
}

输出excel文件的模块,主要参考了在线实时生成Excel文件流供下载。但是这篇博客写的太繁琐,于是将它简化了一大部分。这其中需要用到一个包:poi-3.13-20150929.jar,最新版本的poi可以在这里下载。更加详细的POI操作excel的方法,请参考这里
而导出txt文件的方法则要简单许多。

public InputStream getTxtFile(){
    HttpSession session = ServletActionContext.getRequest().getSession();
    formatResults=(ArrayList<Result>)session.getAttribute("formatResults");
    Result result;
    PredictionSite site;

    StringBuffer sb=new StringBuffer();
    String newLine,tab,str0,str1,str2,str3,str4,str5,str6,str7,str8,
                str9,str10,str11,str12,str13,str14,str15,str16;
    newLine="\n";
    tab="\t";
    for(int i=0; i<formatResults.size(); i++){

        result=formatResults.get(i);
        str0=result.getName();
        sb.append(str0).append(newLine);
        str1="Rank";  
        str2="Position";  
        str3="Surrounding Sequence";  
        str4="Probability Score"; 
        sb.append(str1).append(tab).append(str2).append(tab).append(str3).append(tab).append(str4).append(tab).append(newLine);
        for( int j=0;j<result.getSites().size();j++ ){
            site=result.getSites().get(j);
            str5=site.getRank();  
            str6=site.getPos();  
            str7=(site.getLeft()+site.getRight());  
            str8=site.getProb();
            sb.append(str5).append(tab).append(str6).append(tab).append(str7).append(tab).append(str8).append(tab).append(newLine);
            
        }
        str9="Original Sequence:"; 
        str10=result.getSeq(); 
        str11="Native Disorder:"; 
        str12=result.getDiso(); 
        str13="Secondary Structure:"; 
        str14=result.getSs(); 
        str15="Solvent Accessibility:"; 
        str16=result.getSa(); 
        sb.append(str9).append(newLine).append(str10).append(newLine).append(str11).append(newLine).append(str12).append(newLine)
        .append(str13).append(newLine).append(str14).append(newLine).append(str15).append(newLine).append(str16).append(newLine).append(newLine);
    }
    
    ByteArrayInputStream in = new ByteArrayInputStream(sb.toString().getBytes());  
    return in; 
}
public String downloadTxt() throws Exception{
    return SUCCESS;
}

2.需要注意的地方

这次的项目,过程中出现过几次低级错误。Chris说:“越是奇葩的bug,往往隐藏着低级的错误。”这句话在这个项目中屡屡应验。记录一下,以避免再犯。

2.1 struts.xml文件配置

本项目共产生3actionwebserver界面点击提交,excel文件导出,txt文件导出。配置如下

    <action name="result" class="jphospho.JPhospho" method="execute">
        <result>
            /serverResult.jsp
        </result>
    </action>
    
    <action name="excel" class="jphospho.JPhospho" method="downloadExcel">
        <result type="stream">
            <param name="contentType">application/vnd.ms-excel</param>  
            <param name="contentDisposition">attachment;fileName="Detail.xls"</param>  
            <param name="inputName">excelFile</param>  
            <param name="bufferSize">1024</param>  
        </result>
    </action>
    
    <action name="txt" class="jphospho.JPhospho" method="downloadTxt">  
        <result type="stream">  
            <param name="contentType">application/octet-stream</param>  
            <param name="inputName">txtFile</param>  
            <param name="contentDisposition">attachment;fileName="Detail.txt"</param>  
            <param name="bufferSize">4096</param>  
        </result>  
    </action>

需要注意的是,第一个actionresult type不能是chain,如果是chain,则会出现不跳转而直接下载文件的bug。那么怎么在action之间进行传值呢?详见下一条。

2.2 action之间传值

配置struts.xml是行不通的,我们采用了HttpSession传值方法。在第一个action中的return SUCCESS之前,加上这么两句代码

    HttpSession session = ServletActionContext.getRequest().getSession();
    session.setAttribute("formatResults", formatResults); 

这样,formatResults就保存在了session中,然后可以在另外两个action中取值了。

    HttpSession session = ServletActionContext.getRequest().getSession();
    formatResults=(ArrayList<Result>)session.getAttribute("formatResults"); 

2.3 处理输入的序列

输入序列如果不包含名字,是多条裸序列,那么原来的项目会当做是一条序列处理,这显然是不符合要求的。我们为每一个序列之前添加一个>ii1开始计数。在这里,处理换行符的时候,有一点要特别注意。接收的字符串中的换行符,一定是\r\n,而输出的文件中,换行需要System.getProperty("line.separator"),否则就会出现问题。

public String getFormatSeqString(String originSeqString) {
    String formatSeqString;
    boolean b = String.valueOf(originSeqString.trim().charAt(0)).equals(">");
    if(b) {
        formatSeqString = originSeqString;
        return formatSeqString;
    } 
    
    String[] strs = originSeqString.trim().split("\r\n");
    ArrayList<String> list = new ArrayList<>();
    int k=1;
    for(int i=0; i<strs.length; i++){
        if(!strs[i].equals("") && !strs[i].equals("\r\n")){
            list.add(">"+(k++)+"\r\n"+strs[i]);
        }
    }
    formatSeqString=String.join("\r\n",list);
    return formatSeqString;
}

2.4 初始化问题

在生成文件方法里,StringBuffer必须得用new初始化,否则会出现空指针错误。

StringBuffer sb=new StringBuffer();

关于StringStringBufferStringBuilder的区别可以参考这里StringBuffer是可扩展且线程安全的,因此,我们用它最好。
另外,多重循环的初始化一定要注意初始化的位置。