使用FileSystem类进行文件读写及查看文件信息.docx
《使用FileSystem类进行文件读写及查看文件信息.docx》由会员分享,可在线阅读,更多相关《使用FileSystem类进行文件读写及查看文件信息.docx(16页珍藏版)》请在冰豆网上搜索。
使用FileSystem类进行文件读写及查看文件信息
在这一节我们要深入了解Hadoop的FileSystem类——这是与与hadoop的文件系统交互的重要接口。
虽然我们只是着重于HDFS的实现,但我们在编码时一般也要注意代码在FileSystem不同子类文件系统之间的可移植性。
这是非常有用的,比如说你可以非常方便的直接用同样的代码在你的本地文件系统上进行测试。
使用hadoopURL读数据
从hadoop文件系统中读取文件的最简单的方法之一便是使用.URL对象来打开一个欲从中读取数据的流(stream)。
通常情况下的编程风格如下:
1InputStreamin=null;
2try{
3in=newURL("hdfs:
//host/path").openStream();
4//processin
5}finally{
6IOUtils.closeStream(in);
7}
想要使java识别出hdfs开头的URL标示还需要一点其他的工作要做:
通过URL的setURLStreamHandlerFactory()方法为java设置一个FSUrlStreamHandlerFactory。
这个方法在每个JVM中只能调用一次,所以它通常会被放在一个staticblock中执行(如下所示),但如果你的某部分程序——例如一个你无法修改源代码的第三方组件——已经调用了这个方法,那你就不能通过URL来这样读取数据了(下一节我们会介绍另一种方法)。
1publicclassURLCat{
2static{
3 URL.setURLStreamHandlerFactory(newFsUrlStreamHandlerFactory());
4}
5
6publicstaticvoidmain(String[]args)throwsException{
7 InputStreamin=null;
8 try{
9 in=newURL(args[0]).openStream();
10 IOUtils.copyBytes(in,System.out,4096,false);
11 }finally{
12 //TODO:
handleexception
13 IOUtils.closeStream(in);
14 }
15}
16}
上例中我们使用了Hadoop中的IOUtils类的两个静态方法:
1)IOUtils.copyBytes(),其中in表示拷贝源,System.out表示拷贝目的地(也就是要拷贝到标准输出中去),4096表示用来拷贝的buffer大小,false表明拷贝完成后我们并不关闭拷贝源可拷贝目的地(因为System.out并不需要关闭,in可以在finally语句中被关闭)。
2)IOUtils.closeStream(),用来关闭一个流。
下面是我们的测试例子:
%hadoopURLCathdfs:
//localhost/user/tom/quangle.txt
OnthetopoftheCrumpettyTree
TheQuangleWanglesat,
Buthisfaceyoucouldnotsee,
OnaccountofhisBeaverHat.
使用FileSystem读取数据
就像上节所说的,有时候我们无法通过设置URLStreamHandlerFactory方法的方式来通过URL读取数据,这时FIleSystemAPI就派上用场了。
Hadoop文件系统中的文件是用Hadoop的Path对象来表示的(而不是java中的java.io.File对象,因为它的语义太接近于本地文件系统了)。
你可以把一个Path对象看做Hadoop文件系统中的某一个URL,如上例中的“hdfs:
//localhost/user/tom/quangle.txt”。
Filesystem是一个通用的文件系统API,所以使用它的第一步就是先抽取出它的一个实例出来——在这个例子中是HDFS。
下面列出了几个Filesystem的用于抽取Filesystem实例的几个静态方法:
publicstaticFileSystemget(Configurationconf)throwsIOException
publicstaticFileSystemget(URIuri,Configurationconf)throwsIOException
publicstaticFileSystemget(URIuri,Configurationconf,Stringuser)throwsIOException
一个Configuration对象封装了客户端或服务器端的配置信息,这些配置信息是通过从conf/core-size.xml之类的配置文件中读取出来的名值对来设置的。
下面我们一一说明上面的三个方法:
1)第一个方法返回一个默认的文件系统(在conf/core-site.xml中通过fs.default.name来指定的,如果在conf/core-site.xml中没有设置则返回本地文件系统)。
2)第二个方法通过uri来指定要返回的文件系统(例如,如果uri是上个测试例子中的hdfs:
//localhost/user/tom/quangle.txt,也即以hdfs标识开头,那么就返回一个hdfs文件系统,如果uri中没有相应的标识则返回本地文件系统)。
3)第三个方法返回文件系统的机理同
(2)是相同的,但它同时又限定了该文件系统的用户,这在安全方面是很重要的。
有时候你可能想要使用一个本地文件系统,你可以使用另一个很方便的方法:
publicstaticLocalFileSystemgetLocal(Configurationconf)throwsIOException
得到一个文件系统的实例后,我们可以调用该实例的open()方法来打开某个给定文件的输入流(第一个方法使用一个默认的4KB的输入缓冲):
publicFSDataInputStreamopen(Pathf)throwsIOException
publicabstractFSDataInputStreamopen(Pathf,intbufferSize)throwsIOException
把上面介绍的组合起来我们就得到了下面的代码:
1publicclassFileSystemCat{
2publicstaticvoidmain(String[]args)throwsException{
3Stringuri=args[0];
4Configurationconfiguration=newConfiguration();
5FileSystemfs=FileSystem.get(URI.create(uri),configuration);
6InputStreamin=null;
7try{
8in=fs.open(newPath(uri));
9IOUtils.copyBytes(in,System.out,4096,false);
10}finally{
11IOUtils.closeStream(in);
12}
13}
14}
FSDataInputStream
与URL的openStream()方法返回InputStream不同,FileSystem的open()方法返回的是一个FSDataInputStream对象(继承关系:
java.io.InputStream-->java.io.FilterInputStream-->java.io.DataInputStream-->org.apache.hadoop.fs.FSDataInputStream)。
由于FSDataInputStream实现了Closeable, DataInput, PositionedReadable, Seekable等接口,你可以从流中的任意一个位置读取数据。
Seekable接口的seek()和getPos()方法允许我们跳转到流中的某个位置并得到其位置:
publicinterfaceSeekable{
voidseek(longpos)throwsIOException;
longgetPos()throwsIOException;
}
如果调用seek()时指定了一个超过文件长度的位移值,会抛出IOException异常。
与java.io.Inputstream的skip()方法指明一个相对位移值不同,seek()方法使用的是绝对位移值。
如下所示的代码通过seek()方法读取了两次输入文件:
1publicclassFileSystemDoubleCat{
2publicstaticvoidmain(String[]args)throwsException{
3Stringuri=args[0];
4Configurationconf=newConfiguration();
5FileSystemfs=FileSystem.get(URI.create(uri),conf);
6FSDataInputStreamin=null;
7try{
8in=fs.open(newPath(uri));
9IOUtils.copyBytes(in,System.out,4096,false);
10in.seek(0);//gobacktothestartofthefile
11IOUtils.copyBytes(in,System.out,4096,false);
12}finally{
13IOUtils.closeStream(in);
14}
15}
16}
运行结果如下:
%hadoopFileSystemDoubleCathdfs:
//localhost/user/tom/quangle.txt
OnthetopoftheCrumpettyTree
TheQuangleWanglesat,
Buthisfaceyoucouldnotsee,
OnaccountofhisBeaverHat.
OnthetopoftheCrumpettyTree
TheQuangleWanglesat,
Buthisfaceyoucouldnotsee,
OnaccountofhisBeaverHat.
FSDataInputStream也实现了 PositionedReadable接口,这允许你从流中的某个给定位置读取给定长度的内容:
publicinterfacePositionedReadable{
publicintread(longposition,byte[]buffer,intoffset,intlength)
throwsIOException;
publicvoidreadFully(longposition,byte[]buffer,intoffset,intlength)
throwsIOException;
publicvoidreadFully(longposition,byte[]buffer)throwsIOException;
}
说明:
read()方法从文件的给定position出读取length个字节到buffer的offset处。
返回值是读取到的实际字节数,调用者应该检查这个返回值,因为它可能比length小(可能读到了文件末尾,或发生了中断等等)。
调用所有的这些方法并不会改变文件的偏移值,所以这些方法是线程安全的。
也由此提供了一种当访问某文件的内容时访问该文件的另一部分数据——例如元数据——的很方便的方法。
最后需要注意的是调用seek()方法的代价比较高,应尽量避免使用。
你的程序应该基于流式访问来构建,而不是执行一大堆seek。
写数据
FileSystem类有很多方法用来创建一个文件,最简单的就是以欲创建文件的Path对象为参数的create(Pathf)方法,该方法返回一个用来写入数据的输出流:
publicFSDataOutputStreamcreate(Pathf)throwsIOException
该方法还有几个重载的方法,通过这些重载的方法你可以指定是否覆盖该文件名已存在的文件,这个文件的备份数,用来写数据的buffersize,该文件的block大小和文件权限等。
create()方法会创建指定的文件名中包含的任何不存在的父目录,这样虽然很方便,但不推荐使用(因为如果某个父目录中存在其他数据,会被覆盖掉从而导致文件丢失)。
如果你想要当父目录不存在时该创建操作失败,你可以在调用create()方法之前调用exists()方法检查指明的父目录是否存在,如果存在则报错以让create()失败
create()方法还有一个重载方法可以让你传递一个回调的借口——progressable,这样你的程序就会知道你的数据被写入了多少,也即写入的进度(progress):
packageorg.apache.hadoop.util;
publicinterfaceProgressable{
publicvoidprogress();
}
除了创建一个新文件以写入数据以外,我们还可以使用append()方法向一个已存在文件添加数据:
publicFSDataOutputStreamappend(Pathf)throwsIOException
有了这个函数,应用程序就可以向那些不能限制大小的文件(如logfile,你事先并不知道待记录日志会有多少)写数据了。
append操作在Hadoop的fileSystem中是可选的,例如HDFS实现了它,但S3就没有。
下面这个例子展示了如何从本地文件系统拷贝一个文件到HDFS,我们在每64KB大小的数据写入之后调用一次progress()函数,这个函数每被调用一次打印一个句点:
1publicclassFileCopyWithProgress{
2publicstaticvoidmain(String[]args)throwsException{
3StringlocalSrc=args[0];
4Stringdst=args[1];
5InputStreamin=newBufferedInputStream(newFileInputStream(localSrc));
6Configurationconf=newConfiguration();
7FileSystemfs=FileSystem.get(URI.create(dst),conf);
8OutputStreamout=fs.create(newPath(dst),newProgressable(){
9publicvoidprogress(){
10System.out.print(".");
11}
12});
13IOUtils.copyBytes(in,out,4096,true);
14}
15}
下面是该例子的示范用法:
%hadoopFileCopyWithProgressinput/docs/1400-8.txthdfs:
//localhost/user/tom/
1400-8.txt
...............
注:
现在除了HDFS以外的其他Hadoop支持的文件系统都不支持progress()方法,但我们应该知道进度信息(pregress)在MapReduce程序中是非常重要的。
FSDataOutputStream
FileSystem中的create()方法返回一个FSDataOutputStream,像FSDataInputStream一样,它也有一个用于查询位移的方法(但并没有类似于FSDataInputStream中seek()的方法,因为Hadoop不允许向流中的任意位置写数据,我们只能在一个文件的末尾处添加数据):
packageorg.apache.hadoop.fs;
publicclassFSDataOutputStreamextendsDataOutputStreamimplementsSyncable{
publiclonggetPos()throwsIOException{
//implementationelided
}
//implementationelided
}
查询某个文件系统
文件元数据:
FileStatus
任何文件系统的典型功能就是能够遍历它的目录结构从而获取有关目录和文件的信息。
Hadoop中的FileStatus类为文件和目录包装了其元数据(包括文件长度,block大小,冗余度,修改时间,文件所有者和权限等信息),其getFileStatus()方法提供了获取某个给定文件或目录的FileStatus对象的途径,如下所示:
1publicclassShowFileStatusTest{
2privateMiniDFSClustercluster;//useanin-processHDFSclusterfortesting(这个类在最新的Hadoop1.0.4中已经被废弃了)
3
4privateFileSystemfs;
5
6@Before
7publicvoidsetUp()throwsIOException{
8Configurationconf=newConfiguration();
9if(System.getProperty("test.build.data")==null){
10System.setProperty("test.build.data","/tmp");
11}
12cluster=newMiniDFSCluster(conf,1,true,null);
13fs=cluster.getFileSystem();
14OutputStreamout=fs.create(newPath("/dir/file"));
15out.write("content".getBytes("UTF-8"));
16out.close();
17}
18
19@After
20publicvoidtearDown()throwsIOException{
21if(fs!
=null){
22fs.close();
23}
24if(cluster!
=null){
25cluster.shutdown();
26}
27}
28
29@Test(expected=FileNotFoundException.class)
30publicvoidthrowsFileNotFoundForNonExistentFile()throwsIOException{
31fs.getFileStatus(newPath("no-such-file"));
32}
33
34@Test
35publicvoidfileStatusForFile()throwsIOException{
36Pathfile=newPath("/dir/file");
37FileStatusstat=fs.getFileStatus(file);
38assertThat(stat.getPath().toUri().getPath(),is("/dir/file"));
39assertThat(stat.isDir(),is(false));
40assertThat(stat.getLen(),is(7L));
41assertThat(stat.getModificationTime(),
42is(lessThanOrEqualTo(System.currentTimeMillis())));
43assertThat(stat.getReplication(),is((short)1));
44assertThat(stat.getBlockSize(),is(64*1024*1024L));
45assertThat(stat.getOwner(),is("tom"));
46assertThat(stat.getGroup(),is("supergroup"));
47assertThat(stat.getPermission().toString(),is("rw-r--r--"));
48}
49
50@Test
51publicvoidfileStatusForDirectory()throwsIOException{
52Pathdir=newPath("/dir");
53FileStatusstat=fs.getFileStatus(dir);
54assertThat(stat.getPath().toUri().getPath(),is("/dir"));
55assertThat(stat.isDir(),is(true));
56assertThat(stat.getLen(),is(0L));
57assertThat(stat.getModificationTime(),
58is(lessThanOrEqualTo(System.currentTimeMillis())));
59assertThat(stat.getReplication(),is((short)0));
60assertThat(stat.getBlockSize(),is(0L));
61assertThat(stat.getOwner(),is("tom