TChain可以完成具有相同结构(指存储的信息相同)的树的串联读取,也可以完成具有相同事例数(如果事例数不相同可能会导致数据错位)的不同结构的树的并联读取。
一、TChain的基本介绍
1、TChain::Add()的串联读取
在存储大量数据时,考虑到计算机系统的限制(如内存等)可能会考虑将一份条件相同的数据拆分后进行存储。这样就会形成多份内部格式和数据结构相同的.root文件,其在后续处理时则需要一并处理。依经验之谈,可以用TFile指针加循环完成读取。但是当各个文件命名不够规范的时候,就会造成更严重的麻烦。对此,ROOT提供了TChain类,同时搭配使用通配符和循环脚本可以完成该目的。
在此之前,需要您去看一下《到底什么是Tree》这篇文章,接下来要以此为基础继续讲下去。假设我们拥有文件DAT000001.root(含有n个事例)和DAT000002.root(含有m个事例),其具有结构相同的Tree名为“Particle”,则可以使用如下代码:
TChain ch("Particle");
ch.Add("DAT000001.root");
ch.Add("DAT000002.root");
//Poniter form
TChain* ch = new TChain("Particle");
ch->Add("DAT000001.root");
ch->Add("DAT000002.root");
TChain::Add()方法对于Tree的操作如下图,其导致整个Tree结构的延长,类似一种串联我也就这么叫了,比较好理解,形成的链表将具有n+m个事例:
2、TChain::AddFriend()的并联读取
如前文所述,TChain::AddFriend()方法的形式相信读者也能够猜到。如果在数据存储时,将多个事例的不同数据参量分开存储,就用到接下来的方法。
假设我们拥有文件DAT000001.root(含有n个事例)和DAT000002.root(含有m个事例),其具有结构相同的Tree名为“Particle”。同时,我们拥有文件DAT000011.root(含有n个事例)和DAT000012.root(含有m个事例),其具有结构相同的Tree名为“Fitting”。我们的目的是将结构相同的两者分别串联后再并联,则可以使用如下代码(还未测试,应该没问题):
TChain ch1("Particle");
ch.Add("DAT000001.root");
ch.Add("DAT000002.root");
TChain ch2("Fitting");
ch.Add("DAT000011.root");
ch.Add("DAT000012.root");
ch1.AddFriend("Fitting");
//Poniter form
TChain* ch1 = new TChain("Particle");
ch1->Add("DAT000001.root");
ch1->Add("DAT000002.root");
TChain* ch2 = new TChain("Fitting");
ch2->Add("DAT000011.root");
ch2->Add("DAT000012.root");
ch1->AddFriend("Fitting");
TChain::AddFriend()方法对于Tree的操作如下图(需和上文的图一并对比),其导致整个Tree结构的延长(当串联文件只有一件时则不延长)和拓宽,其主要功能是后者,这类似一种并联,形成的链表将被拓宽:
如果仅想要拓宽树的宽度,则没有必要使用TChain类,选择使用TTree::AddFriend()方法即可,在这里我引用一段官网的代码,读者可以注意一下官方教程的代码风格。
void treeWithFriend() {
std::unique_ptr<TFile> myFile( TFile::Open("file.root") );
auto tree = myFile->Get<TTree>("TreeName");
std::unique_ptr<TFile> myFriendFile( TFile::Open("friend.root") );
auto friendTree = myFriendFile->Get<TTree>("FriendTreeName");
tree->AddFriend(friendTree);
int variable;
tree->SetBranchAddress("branchName", &variable);
int variableFriend;
tree->SetBranchAddress("FriendTreeName.friendBranchName", &variableFriend);
// Iteration over `tree` automatically advances its friend trees.
for (int iEntry = 0; tree->LoadTree(iEntry) >= 0; ++iEntry) {
// Load the data for the given tree entry
tree->GetEntry(iEntry);
printf("%d %d\n", variable, variableFriend);
}
二、TChain读取文件
1、TChain的读取模板
简单总结如何用TChain读取.root中Tree的数据:
1、定义a个TChain指针(a由做多少次拓宽决定,如果仅是延长Tree则a=1)
2、将相同结构的.root文件利用Add()串联进入同一个Chain
3、将不同结构的.root文件利用AddFriend()并联至一个Chain(如果没有,跳过本步骤)
4、使用TChain的GetEntries()获取事例总数n
5、使用SetBranchAddress将Branch地址与自设变量相连
6、使用循环令GetEntry()或GetEvent()从0到n-1进行遍历获取数据
代码模板如下,注意一些字符串变量的细节:
{
Int_t variable,variableFriend;//just a example
TChain* ch1 = new TChain("TreeName");
ch1->Add("filename1.root");
ch1->Add("filename2.root");
TChain* ch2 = new TChain("FriendTreeName");
ch2->Add("friendname1.root");
ch2->Add("friendname2.root");
ch1->AddFriend("FriendTreeName");//If there is no FriendChain,ignore this part above
Long64_t entries = ch1->GetEntries();
ch1->SetBranchAddress("branchName", &variable);
ch1->SetBranchAddress("FriendTreeName.friendBranchName", &variableFriend);//same as above
for(Int_t i=0;i<entries;++i)
{
ch1->GetEntry(i);
//handle
}
}
2、如何正确使用通配符
在以上的例子中,我们仅用了较少的例子,如果存在数百份.root文件,自然不可能一个个地添加到Chain中,ROOT对此提供了通配符来解决这个问题。TChian允许传入一个符合通配符规则的字符串。
TChain* ch = new TChain("TreeName");
ch->Add("/home/username/data/*.root");
该代码将/home/username/data路径下所有以.root为结尾的文件添加进入Chain。
如果您想要配合bash使用该语法,请注意通配符的规则。我们将如上代码编入文件ChainTest.C并使用g++编译的可执行文件(如果您不知道如何使用,请查看《生成ROOT可执行文件》),同时希望使其传入的第一个参数为目标路径,那么您应该:
#include <iostream>
#include <string>
#include <cstring>
#include "TH2F.h"
#include "TROOT.h"
#include "TFile.h"
#include "TTree.h"
#include "TChain.h"
using namespace std;
Int_t main(Int_t argc , Char_t** argv)
{
TChain *ch = new TChain("TreeName");
string dirname = argv[1];
string filename = dirname + "*.root";//pay attention to this part
ch->Add(filename.c_str());
//data reading part above
return 0;
}
您应该在shell中这样输入:
./ChainTest /home/username/data/
而不是:
#include <iostream>
#include <string>
#include <cstring>
#include "TH2F.h"
#include "TROOT.h"
#include "TFile.h"
#include "TTree.h"
#include "TChain.h"
using namespace std;
Int_t main(Int_t argc , Char_t** argv)
{
TChain *ch = new TChain("TreeName");
ch->Add(argv[1]);
//data reading part above
return 0;
}
shell中:
./ChainTest /home/username/data/*.root
注意,上述提到的第二种做法是错误的 。在shell中直接输入通配符将会使该参量被传入程序前就会被先行替换为若干符合条件的字符串。假如该文件夹有DAT000001.root和DAT000002.root,那么上述语句在传入程序前就会变成:
./ChainTest /home/username/data/DAT000001.root /home/username/data/DAT000002.root
该做法会导致TChain只能接收到第一个文件名,这并不是我们想要的结果。
三、关于有类列表的root
以上提及的TChain都用于具有简单的用户定义的结构的Tree的读取,如果目标Tree是以类为结构存入的,那么将会导致读取的错误,对于这种情况,在以后会介绍一个MakeClass()的方法,来解决这一种读取困难。
【资料】
1、ROOT官网——ROOT: analyzing petabytes of data, scientifically. - ROOT
2、ROOT教程——Trees - ROOT
3、ROOT官方指导书——《ROOTUsersGuide》
如有错误请指正。