Java 接口
Hadoop 有一个抽象的文件系统概念, HDFS 只是其中的一个实现。Java 抽象类org.apache.hadoop.fs.FileSystem 定义了Hadoop 中的一个文件系统接口:与Hadoop 的某一文件系统进行交互的API 。虽然我们主要关注的是HDFS的实例,即DistributedFileSystem,但总体来说,还是应该继承FileSystem抽象类,并编写代码,以保持其在不同文件系统中的可移植性。
从Hadoop URL 中读取数据
要从Hadoop文件系统中读取文件,最简单的方陆是使用java.net.URL 对象打开数据流,进而从中读取数据。具体格式如下:
InputStream in = null;
try {
in = new URL("hdfs://host/path").openStream();
// process in
} finally {
IOUtils.closeStream(in);
}
让Java 程序能够识别Hadoop的hdfs URL 方案还需要一些额外的工作。这里采用的方能是通过FsUrlStreamHandlerFactory 实例调用URL中的setURLStreamHandlerFactory 方法。由于Java 虚拟机只能调用一次上述方法,
因此通常在静态方法中调用上述方法。这个限制意味着如果程序的其他组件(不受你控制的第三方组件)已经声明了一个URLStreamHandlerFactory 实例,
你将无法再使用上述方法从Hadoop 中读取数据。下一节将讨论另一备选方法。
通过URLStreamHandler 实例以标准输出方式显示Hadoop 文件系统的文件
public class URLCat {
static {
URL.setURLStreamHandlerFactorγ(new FsUrlStreamHandler、Factory());
}
public static void main(String[] args) throws Exception {
InputStream in = null;
try {
in = new URL(args[0]).openStream();
IOUtils.copyBytes(in, System.out, 4096, false);
} finally {
IOutils.closeStream(in);
}
}
}
我们可以调用Hadoop 中简洁的IOUtils 类,并在finally 子句中关闭数据流,
同时也可以在输入流和输出流之间复制数据(本例中为System.out)。copyBytes
方法的最后两个参数,第一个用于设置复制的缓冲区大小,第二个用于设置复制结束后是否关闭数据流。这里我们选择自行关闭输入流,因而System.out 不关
闭输入流。
下面是一个运行示例:
% hadoop URLCat hdfs://localhost/user/tom/quangle.txt
On the top of the Crumpetty Tree
The Quangle Wangle sat,
But his face you could not see,
On account of his Beave Hat.
通过FileSystem API 读取数据
正如前一小节所解释的,有时无法在应用中设置URLStreamHandlerFactory实例。这种情况下,需要使用FileSystem API 来打开一个文件的输入流。
Hadoop 文件系统中通过HadoopPath 对象来代表文件(而非java.io.File 对象,因为它的语义与本地文件系统联系太紧密)。你可以将一条路径视为一个Hadoop 文件系统URI ,如hdfs://localhost/userltom/quangle. txt 。
FileSystem 是一个通用的文件系统API,所以第一步是检索我们需要使用的文件系统实例,这里是HDFS 。获取FileSystem 实例有两种静态工广方法:
public staticFileSystem get(Configuration conf) throws IOException
Public staticFileSystem get(URI uri, Configuration conf) throws IOException
Configuration 对象封装了客户端或服务器的配置,通过设置配置文件读取类路径来实现(如conf/core-site. xml) 。第一个方法返回的是默认文件系统(在conf/core-site.xml 中指定的,如果没有指定,则使用默认的本地文件系统)。第二个方法通过给定的URI 方案和权限来确定要使用的文件系统,如果给定URI 中没有指定方案,则返回默认文件系统。
有了FileSystem 实例之后,我们调用open()函数来获取文件的输入流:
Public FSDatalnputStream open(Path f) throws IOException
Public abstract FSDatalnputStream open(Path f , int bufferSize)throws IOException
第一个方法使用默认的缓冲区大小4 KB。
将上述方法结合起来,我们重写上一个例子:
直接使用FileSystem 以标准输出格式显示Hadoop 文件系统中的文件
public class FileSystemCat {
public static void main(String[] args)throws Exception {
String uri = args[0] ;
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(uri), conf);
InputStream in = null;
try {
in = fs.open(new Path(uri));
IOUtils.copyBytes(in , System.out, 4096 ,false);
} finally {
IOUtils.closeStream(in);
}
}
}
FSDatalnputStream
实际上, FileSystem 对象中的open() 方住返回的是FSDatalnputStream对象,而不是标准的java.io 类对象。这个类是继承了java.io.DatalnputStream 接口的一个特殊类,井支持随机访问,由此可以从流的任意位置读取数据。
package org.apache.hadoop.fs;
public class FSDatalnputStream extendsDatalnputStream
implements Seekable, PositionedReadable {
// implementation elided
}
Seekable 接口支持在文件中找到指定位置,并提供一个查询当前位置相对于文件起始位置偏移量(getPos ())的查询方法:
public interface Seekable {
void seek(long pos) throws IOException;
long getPos() throws IOException;
boolean seekToNewSource (long tagetPos) throwsIOException;
}
务必牢记, seek() 方也是一个相对高开销的操作,需要慎重使用。建议用流数据来构建应用的访问模式(如使用MapReduce) ,而非执行大量的seek() 方法。
写入数据
FileSystem 类有一系列创建文件的方法。最简单的方法是给准备创建的文件指定
一个Path 对象,然后返回一个用于写入数据的输出流:
public FSDataOutputStream create(Path f)throws IOException
上述方法有多个重载版本,允许我们指定是否需要强制覆盖已有的文件、文件备份
数量、写入文件时所用缓冲区大小、文件块大小以及文件权限。
还有一个重载方法progressable ,用于传递回调接口,如此一来,可以把数据写人数据节点的进度通知到你的应用:
package org.apache.hadoop.util;
public interface Progressable {
public void progress();
}
另一种新建文件的方法,是使用append()方法在一个已有文件末尾追加数据(还存
在一些其他重载版本) :
public FSDataOutputStream append(Path f)throws IOException
该追加操作允许一个writer 打开文件后在访问该文件的最后偏移量处追加数据。有了这个API ,某些应用可以创建无边界文件,例如,日志文件可以在机器重启后在已有文件后面继续追加数据。该追加操作是可选的,井非所有Hadoop 文件系统都实现了该操作。例如, HDFS 支持追加,但S3 文件系统就不支持。