需求:有个同学拿了一堆化学上面催化剂的一些数据让我写个程序处理一下。同学给我的是一个txt文件,里面有很多行数据,每378行分为一组,组与组之间有一行空白行。有的行有5列,有的行只有3列,要求是将从第二组开始的数据按照行号拼接到第一组数据后面,第二组接到第一组后面,第三组接到第二组后面……以此类推。
这个需求听上去其实也不复杂,无非就是对文件的操作,具体的拼接过程完全可以在内存中实现(只要有足够大的内存),C语言忘得都差不多了,借此机会复习一下吧。
先看一下原数据格式:
1 -0.101783D+02 -0.243955D+01 -0.216450D-04 -0.414976D-03 -0.414376D-14
2 -0.243955D+01 -0.998679D+00 0.222268D-03 0.393219D-01 -0.263128D-14
3 -0.216450D-04 0.222268D-03 -0.117000D+00 -0.812384D-03 -0.426587D-15
4 -0.414976D-03 0.393219D-01 -0.812384D-03 -0.183967D+00 0.764145D-15
5 -0.414376D-14 -0.263128D-14 -0.426587D-15 0.764145D-15 -0.402880D-03
1 -0.200450D+01 -0.166424D-05 0.176481D-03 -0.485456D-14 -0.935424D+00
2 -0.107777D+01 0.309533D-03 0.492703D-01 0.101422D-16 -0.604235D+00
3 0.394986D-03 -0.364537D+00 -0.624885D-03 -0.434795D-15 0.522164D-03
4 0.610953D-01 -0.667912D-03 -0.456884D+00 -0.162973D-15 -0.507692D-01
5 -0.153209D-15 0.584723D-15 0.193193D-14 -0.254258D+00 -0.904307D-14
1 -0.936221D+00 -0.933496D+00 -0.240847D-04 -0.281712D-14 0.299125D-15
2 -0.655863D+00 -0.510790D+00 -0.107102D-02 0.103522D-14 -0.577630D-15
3 -0.289317D-04 0.126813D-03 -0.907516D-01 0.165825D-15 0.843699D-15
4 0.144731D+00 0.137608D-01 -0.292953D-04 0.381367D-15 -0.200552D-15
5 -0.329280D-14 -0.865301D-14 0.111258D-14 0.119046D-03 0.274277D-01
1 -0.166760D-08 -0.650029D-09 -0.164477D-08
2 -0.482698D-08 -0.166674D-08 -0.497130D-08
3 -0.270613D-10 0.154384D-09 -0.137192D-09
4 -0.344229D-09 -0.262246D-09 -0.810262D-09
5 0.620346D-15 0.223117D-15 0.668207D-16
原始数据比较多,这里就拿一部分举例,数据保存在in.txt文件,输出结果保存在out.txt文件中。
下面上代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
// 列数,该数值要足够大保证能存下拼接后的字符串
#define MAX_COL 1000
// 行数
#define ROW 5
// 读取一行数据,在没有遇到回车的情况下最多读取的字符数
#define LENGTH 1024
/* 说明:将需要读取的文件和程序放在同一个文件夹下
文件名需要和main函数中的一致
输出文件会自动生成,也在同一个目录下
*/
/* 函数功能: 去掉一行前面的行号和空格
如果第一个非空格字符不是负号则添加一个空格方便对齐
*/
int trim(char* buffer)
{
int i=0;
int count=0;
for(i=0;count<=2;i++)
{
if(count==0&&buffer[i]!=' ')
{
count=1;
}
else if(count==1&&buffer[i]==' ')
count=2;
else if(count==2&&buffer[i]!=' ')
return (buffer[i]!='-')?i-1:i;
}
return 0;
}
/* 函数功能:将缓存中的一行数据追加到数组中对应行
函数返回追加后数组中这一行字符串末尾的下标,方便下次查找
参数值:data是一个动态二维数组,用于缓存全部数据
buffer是缓存的一行数据
row行号
len[]保存的是追加前data数组中第row行字符串结尾处的下标,追加后会更新对应的值
*/
int append(char* data,char* buffer,int row,int len[])
{
// 根据行号,找到数组中这一行字符串的末尾的下标
int i=len[row];
int j;
int begin=0;
// 判断是第几组数据
// 如果不是第一组则去掉行号和前面多余的空格
// 如果第一个非空格字符不是负号则添加一个空格方便对齐
if(i!=0)
{
begin=trim(buffer);
}
// 去掉buffer中的换行符
if(buffer[strlen(buffer)-1]=='\n')
{
buffer[strlen(buffer)-1]='\0';
}
// 将最后的'\0'换成空格
if(data[row*MAX_COL+i]=='\0')
{
data[row*MAX_COL+i]=' ';
i++;
}
// 循环将buffer中的字符复制到data中
for(j=begin;buffer[j]!='\0';i++,j++)
{
data[row*MAX_COL+i]=buffer[j];
}
// data中字符串结尾添加'\0'
data[row*MAX_COL+i]='\0';
return i;
}
/* 函数功能:读取文件中一行数据
*/
char* read(char* buffer,FILE* fp)
{
return fgets(buffer,MAX_COL,fp);
}
/* 函数功能:判断某一行是不是空白行
如果是返回0,否则返回1
*/
int isEmptyLine(char* buffer)
{
int i=0;
for(i=0;buffer[i]!='\0';i++)
{
// 只要碰到非空格或回车,就返回1
if(buffer[i]!=' '&&buffer[i]!='\n')
return 1;
}
// 为空白行,返回0
return 0;
}
main()
{
FILE* fp;
FILE* out;
char* data;
char* buffer;
// 保存data数组每一行字符串的结尾下标
int len[ROW];
// 行号和组号,每5行为一组
int row_num=0;
int i;
// 打开源文件
fp=fopen("in.txt","r");
// 输出文件
out=fopen("out.txt","a");
// 二维数组保存数据
// 这里申请的是动态二维数组
data=(char*)calloc(ROW*MAX_COL,sizeof(char));
// 临时保存读取到的一行数据
buffer=(char*)malloc(LENGTH*sizeof(char));
// 初始化len数组
for(i=0;i<ROW;i++)
{
len[i]=0;
}
// 检查文件有没有打开成功
if(fp==NULL)
{
printf("cannot open");
return-1;
}
// 检查动态内存有没有申请成功
if(data==NULL)
{
printf("cannot calloc memory for data");
return -1;
}
if(buffer==NULL)
{
printf("cannot calloc memory for buffer");
return -1;
}
// feof(char* cp)函数是用来判断是否到文件尾的
// 如果是文件尾,返回非0值
while(feof(fp)==0)
{
// 读取一行数据,存到缓存
read(buffer,fp);
// 如果是空白行则跳过
if(isEmptyLine(buffer)==0)
{
continue;
}
// 将读取到的一行数据追加到data数组对应行
len[row_num]=append(data,buffer,row_num,len);
row_num++;
// 判断一组有没有读完
// 如果读完一组,则将行号重置,组号加1
if(row_num==ROW)
{
row_num=0;
}
}
// 将数组中保存的字符串输出到文件
for(i=0;i<ROW;i++)
{
fputs(&data[i*MAX_COL],out);
fputs("\n",out);
}
// 释放动态内存
free(buffer);
buffer=NULL;
free(data);
data=NULL;
// 关闭文件
fclose(fp);
fclose(out);
fp=NULL;
out=NULL;
return 0;
}
其实这里面文件操作并不复杂,复杂在对字符串的处理,这也是C语言不方便的地方,需要小心一些隐藏的字符,如‘\0','\n',' '等。下面再用Java实现一下,对比C的代码要简单很多:
package mp1;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Data
{
private static final int LINE_SUM=5;
public static void main(String[] args) throws IOException
{
// 输出文件
File outFile=new File("out.txt");
FileWriter fWriter=new FileWriter(outFile, false);
// 读取文件
FileReader fReader=new FileReader("in.txt");
BufferedReader br=new BufferedReader(fReader);
StringBuilder[] data=new StringBuilder[LINE_SUM];
String buffer=null;
int seq=0;
int group=1;
while((buffer=br.readLine())!=null)
{
// 跳过空白行
if(!buffer.trim().equals(""))
{
// 如果是第一组,直接存入数组
if(group==1)
{
data[seq]=new StringBuilder("");
data[seq].append(buffer);
}
// 其他组
else
{
// 获取序号,判断应该存入哪一行
String trimedBuffer=buffer.trim();
// System.out.println(trimedBuffer);
// 去掉序号
buffer=trimedBuffer.substring(trimedBuffer.indexOf(" ")).trim();
// 判断第一串数字有没有负号
// 如果没有负号则在开头添加一个空格,方便对齐
if(!(buffer.charAt(0)=='-'))
{
buffer=" "+buffer;
}
// 追加到对应的行
data[seq].append(" "+buffer);
}
// 序号加1
seq++;
// 一组读取完毕,序号重置,组号加1
if(seq==LINE_SUM)
{
seq=0;
group++;
}
}
}
// 将数组中的数据写入文件
for(int i=0;i<data.length;i++)
{
fWriter.write(data[i].toString()+"\n");
}
System.out.println("group="+(group-1));
fWriter.close();
fReader.close();
}
}