[c++] Wie lese ich effizient große Datenmengen in mein Objekt


little-smile

Grünschnabel
Hallo Tutorials-Member,

Ich bin neu, bitte verzeiht mit etwaige Verstöße gegen Posting-Regeln. Nun zu meiner Frage:

Ich will große Dateien in ein Objekt einlesen. Genauer handelt es sich bei den Dateien um Dateien in FastaFormat (http://de.wikipedia.org/wiki/FASTA-Format). Dieses Format dient dazu Sequenzinformationen textbasiert zu halten. Eine solche Datei sieht wie folgt aus:

>ID Description
Sequence
>ID Description
Sequence

Alle drei Infos sind variabel, sprich nicht von fester Länge. Darüber hinaus kann die Sequence sich über mehrere Zeilen erstrecken.
Diese Infos will ich in meinem Objekt Sequences speichern + zusätzliche. Eine solche Datei umfasst 60 000 bis zu 100 000 Sequenzen.

Mein Ansatz bis dato war es, den kompletten Dateiinhalt in einen STL string Objekt einzulesen. Dieses dann sequenzweise zu durchlaufen und eine Instanz von meinem Objekt anzulegen. Dies funktioniert, aber ist einfach zu langsam für die große Datenmenge.

Zusätzliche Info, ich speichere den Pointer auf die jeweilige Instanz in einer STL-list, um die Sequenzen anschließend nach Größe zu sortieren.

Hat vielleicht einer von euch eine Idee, wie ich das Einlesen beschleunigen kann.

Hier noch ein paar Code-Schnipsel:

Sequences. hpp

Code:
class Sequences
{
	public:
		/*Constructor & Destructor*/
		Sequences();
		Sequences(const string SeqName, const string SeqInfo, const string SeqSeq, int SeqLength = 0 );
		
                virtual ~Sequences();
		
		/*further member functions*/
		void setSequenceID(unsigned int id);
		void setSequenceName(string& name);
		void setSequenceInfo(string& info);
		void setSequenceSequence(string& seq);
	        void setSequenceLength(unsigned int length);
                ......

	private:	
 		unsigned int SeqID;
		string SeqName;
		string SeqInfo;
		string SeqSequence;
                unsigned int SeqLength;
};


meine Reading function:

Code:
void readFromFASTA (const string& fastafile, list<Sequences*>& list_of_seq, ostream& supervision)
{ 
	size_t size = getFileSize( fastafile); /*eigene Funktion*/
  		
  	/*allocate memory*/
  	content = new char[size];
	
	/*open file, read the complete input into content, close file*/
	loadFromFile( name, content, size );
	
	/*local variables*/
	string identifiers;
	string description;
	string seq;
	
	string buf = content;
		
	size_t space;
	size_t tab;
	size_t end_of_line;
	size_t seq_sep; 	
	size_t separator;

	int num = 0;
	
	/*pass the buffer & extract the Sequences*/
	if (buf[0] != '>')
	{	
		supervision << "Error in sequence:  Your Fasta file starts with "<< buffer[0] << " instead of '>' as it should be."<< endl;
    	throw HTSClustException("File doesn't confirm the FastA format");	
	}
	
	/*pass through the buffer, create a Sequence object for every sequence found in the buffer*/
	supervision << "\nStarting data storage process" << endl;
	
        while (buf.size() != 0)
	{	
		if (num%1000 == 0)
		{
			supervision <<num <<" Sequences read!"  endl;
		}	
		
		string header_buf;
		
		/*extract the first line, which is the header line*/
		end_of_line = buf.find_first_of('\n');
		header_buf = buf.substr( 0, end_of_line );
			
		space = header_buf.find_first_of(' ');	/*points into header_buf at first space, NULL if not found*/
  		tab = header_buf.find_first_of('\t');	/*points into header_buf at first tab, NULL if not found*/
	
		
		/*only the identifier is found in the header*/
		if( (space == string::npos) && (tab == string::npos) )
		{
			identifiers = header_buf;
		}//~if
		else
		{
			if (space == string::npos) space = tab;
  			if (tab == string::npos) tab = space;
  			separator = (space < tab) ? space :  tab;
  		
  			/*Split the header into identifier and description*/
  			identifiers = header_buf.substr(1, separator);
  			description = header_buf.substr(separator, end_of_line); 			
		}//~else
	
		/*remove space at begin and end of identifiers*/
		size_t i;
		
		for ( i=0 ;i < identifiers.size() ; i++ )
		{
			if(identifiers[i] == ' ' )
			{
			 	identifiers.erase(i,i+1);
			}
		}//~for
		
		
		/*remove space at begin and end of description*/

		while(description[0] == ' ' )
		{
			 	description.erase(0,1);
		}

		while(description[description.size()] == ' ' )
		{
			 	description.erase(description.size()-1,description.size());
		}

		/*Read the sequence until new '>' is found.*/
		buf = buf.substr(end_of_line);
  		seq_sep = buf.find_first_of('>');
		seq = buf.substr( 0, seq_sep );
		
		/*remove \n in the sequence*/
		string::iterator it;
		size_t length = 0;
	
		
		for ( it=seq.begin(); it < seq.end() ; ++it )
		{
			if(*it == '\n' )
			{
			 	seq.erase(it,it+1);
			}
		}//~for
		
		length = seq.size();
	

		list_of_seq.push_back(new Sequences(identifiers, description, seq, length));
	
	/*remove data for actual sequence*/
		if(seq_sep != string::npos)
		{
			buf = buf.substr(seq_sep);
		}
		else
		{
			buf = "";
		}		
		++num;
	}//~while  	
	
	supervision << "\nStoring process is finished!" << endl;
}//~readFromFASTA



Vielen Dank für euren Support im Voraus.
little-smile
 
Hallo little-smile,

willkommen auf tutorials.de :)

Da das FASTA-Format offensichtlich zeilenbasiert aufgebaut ist, würde es sich hier anbieten, die Datei zeilenweise einzulesen. Dazu gibt es in C++ die Funktion getline. Beispiel:
C++:
#include <fstream>
#include <string>
#include <list>

using namespace std;

int main(void) {
  ifstream file("fasta.txt");
  string line;
  string identifiers;
  string description;  
  string seq;
  list<Sequences*> list_of_seq;

  while (getline(file, line)) {
    if (line[0] == ';') continue; // Kommentare überspringen
    if (line[0] == '>') {
      if (!seq.empty()) {
        // Vorangegangene Sequenz ist fertig
        list_of_seq.push_back(new Sequences(identifiers, description, seq, seq.size()));
      }
      // Kopfzeile verarbeiten
      // TODO: Hier description und identifiers füllen
      seq.erase();
    } else {
      // Sequenzzeile verarbeiten
      seq.append(line);
    }
  }

  if (!seq.empty()) {
    // Letzte Sequenz nicht vergessen
    list_of_seq.push_back(new Sequences(identifiers, description, seq, seq.size()));
  }

  file.close();

  return 0;
}
Dann wäre es außerdem noch sinnvoll, die Strings im Konstruktor von Sequences als Referenz zu übergeben, um unnötige Kopiervorgänge zu vermeiden.

Grüße, Matthias
 
Zuletzt bearbeitet von einem Moderator:

little-smile

Grünschnabel
Danke Matthias für deine Antwort!

Ich dachte halt, die Dateiinhalt mit einem Rutsch einzulesen wäre effizienter (weniger Lese-Operationen). Das war unbegründet, da ja ein Objekt von ifstream angelegt wird oder?!
Okay, ich teste mal, ob dein Ansatz "schneller" ist.

Mit den Referenzen, das ist ein guter Tipp. C++ ist recht neu für mich und dadurch achte ich nicht immer auf solche Details.

Nochmals danke, ich setz mich gleich (nach dem Mittagessen) mal ran und setze deinen Ansatz um! Lass dich dann wissen, ob es was gebracht hat.

little-smile
 

little-smile

Grünschnabel
Danke Matthias,

Das mit den Referenzen war echt ein super Tipp! Läuft nun sehr schnell, schneller als erwartet!

VIelen Dank nochmals, little-smile
 

devDevil

Erfahrenes Mitglied
Hm also die Streams sind gebuffert ... ehm der Inhalt wird komplett in einen Buffer gelesen und die Streamoperatoren lesen im Prinzip einfach nur daraus ...
C++:
std::ifstream file_stream("input.txt");
std::clog << file_stream.rdbuf();
würde dir d.h. auch den Dateiinhalt ausgeben ;)