如今我们来深入了解一下Hadoop的FileSystem类。

这个类是用来跟Hadoop的文件系统进行交互的。尽管我们这里主要是针对HDFS。可是我们还是应该让我们的代码仅仅使用抽象类FileSystem。这样我们的代码就能够跟不论什么一个Hadoop的文件系统交互了。在写測试代码时,我们能够用本地文件系统測试,部署时使用HDFS。仅仅需配置一下,不须要改动代码了。

在Hadoop 1.x以后的版本号中引入了一个新的文件系统接口叫FileContext,一个FileContext实例能够处理多种文件系统。并且接口更加清晰和统一。


用Hadoop URL来读取HDFS里的文件

在讨论Java API之前。先来看一个用Hadoop URL来读文件数据的方式。这样的方式从严格意义上讲不能算是Hadoop给Java的接口。应该说是用Java Net的方式来向HDFS发送一个网络请求然后读取返回流。
与通过URL去读取一个HTTP服务拿到一个流相似
  1. InputStream in = null;
  2. try {
  3. in = new URL("hdfs://host/path").openStream();
  4. //操作输入流in。能够读取到文件的内容
  5. } finally {
  6. IOUtils.closeStream(in);
  7. }
这个方式有个小问题。Java虚拟机默认是不认识hdfs这个协议的,要想让它知道,须要通过
URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
来设置一个UrlStreamHandlerFactory,以便使其认识hdfs协议。
可是这种方法的调用是对整个虚拟机生效的,所以,假设程序中有其他部分,尤其是第三方框架,设置了这个工厂。那就会出现故障。因此这就成为了使用这样的方式来訪问HDFS的限制。

完整代码例如以下:
  1. import java.io.InputStream;
  2. import java.net.URL;
  3. import org.apache.hadoop.fs.FsUrlStreamHandlerFactory;
  4. import org.apache.hadoop.io.IOUtils;
  5.  
  6. public class URLCat {
  7. static {
  8. URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
  9. }
  10. public static void main(String[] args) throws Exception {
  11. InputStream in = null;
  12. try {
  13. in = new URL(args[0]).openStream();
  14. IOUtils.copyBytes(in, System.out, 4096, false);
  15. } finally {
  16. IOUtils.closeStream(in);
  17. }
  18. }
  19. }
在这个程序里,先设置JVM的URLStreamHandlerFactory。然后通过url打开一个流,读取流,就得到了文件的内容,通过IOUtils.copyBytes()把读到的内容写出到标准输出流里。也就是控制台上,就实现了类似于Linux里的cat命令的功能。
最后把输入流关闭。

IOUtils是hadoop提供的一个工具类。copyBytes的后两个參数分别表示buffer大小和结束后是否关闭流,我们在后面手动关闭流。所以传false。

将程序打成jar包放到hadoop上去,运行:
$hadoop jar urlcat.jar hdfs://localhost/user/norris/weatherdata.txt
可将前一节放到hdfs上去的weatherdata.txt里的内容显示到控制台上。

运行hadoop jar的方法好像之前忘记写博客了,简单说一句,就是把程序打成jar包。能够指定一个可运行类,然后运行:
$hadoop jar xxx.jar
或者假设不打jar包。把class文件直接放上去。然后运行hadoop XxxClass也行,事实上说白了hadoop命令就是启动一个虚拟机,跟运行java XxxClass或者java -jar xxx.jar一样。仅仅只是用hadoop命令启动虚拟机。在启动前,hadoop命令自己主动把须要的类库增加到了CLASSPATH中。因此省去了自己去环境变量设置的麻烦。

值得注意的是,假设你把class放在了/home/norris/data/hadoop/bin/文件夹下,须要在$HADOOP_INSTALL/etc/hadoop/hadoop-env.sh最后一行加入
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:/home/norris/data/hadoop/bin/
把自己的class存放位置变成hadoop搜索class位置的一个选项。

用FileSystem(org.apache.hadoop.fs.FileSystem)类来读取HDFS里的文件

有个类叫Path(org.apache.hadoop.fs.Path),这个类的实例代表HDFS里的一个文件(或文件夹),能够把它理解成java.io.File,跟这个File类是一样的。仅仅只是File这个名字听起来就像是一个本地文件系统的类,所以为了差别。取名为Path。也能够把Path理解成一个HDFS上的URI,比方hdfs://localhost/user/norris/weatherdata.txt。
FileSystem类是一个抽象类。代表全部能够执行在Hadoop上的文件系统,我们这里尽管在讨论HDFS,但事实上FileSystem类能够代表如本地文件系统等的不论什么能够执行于Hadoop上的文件系统。我们敲代码也应该针对FileSystem来写,而不是org.apache.hadoop.hdfs.DistributedFileSystem,以便方便移植到其它文件系统上去。

第一步,先要拿到一个FileSystem实例。通过以下API能够获取到实例:
  1. public static FileSystem get(Configuration conf) throws IOException;
  2. public static FileSystem get(URI uri, Configuration conf) throws IOException;
  3. public static FileSystem get(final URI uri, final Configuration conf, final String user) throws IOException, InterruptedException;
当中,
Configuration(org.apache.hadoop.conf.Configuration)是指Hadoop的配置,假设用默认的构造函数构造出Configuration,就是指core-site.xml里的配置。前面《配置Hadoop》(http://blog.csdn.net/norriszhang/article/details/38659321)一节讲到了该配置。把fs配置成了hdfs。所以依据这个配置Hadoop就知道该取到一个DistributedFileSystem(org.apache.hadoop.hdfs.DistributedFileSystem)实例。
URI是指文件在HDFS里存放的路径。
String user这个參数,将在《安全》章节讨论。
即使在操作HDFS。你可能也同一时候希望訪问本地文件系统。能够通过以下API方便地获取:
  1. public static LocalFileSystem getLocal(Configuration conf) throws IOException;

通过调用FileSystem实例的open方法能够得到一个输入流:
  1. public FSDataInputStream open(Path f) throws IOException;
  2. public abstract FSDataInputStream open(Path f, int bufferSize) throws IOException;
当中的int bufferSize是缓冲区大小。假设不传这个參数,默认大小是4K。
以下是使用FileSystem读取HDFS中文件内容的完整程序:
  1. import java.net.URI;
  2. import org.apache.hadoop.conf.Configuration;
  3. import org.apache.hadoop.fs.FSDataInputStream;
  4. import org.apache.hadoop.fs.FileSystem;
  5. import org.apache.hadoop.fs.Path;
  6. import org.apache.hadoop.io.IOUtils;
  7. public class FileSystemCat {
  8. public static void main(String[] args) throws Exception {
  9. String uri = args[0];
  10. Configuration conf = new Configuration();
  11. FileSystem fs = FileSystem.get(URI.create(uri), conf);
  12. //System.out.println(fs.getClass().getName()); //这里能够看到得到的实例是DistributedFileSystem,由于core-site.xml里配的是hdfs
  13. FSDataInputStream in = null;
  14. try {
  15. in = fs.open(new Path(uri));
  16. IOUtils.copyBytes(in, System.out, 4096, false);
  17. } finally {
  18. IOUtils.closeStream(in);
  19. }
  20. }
  21. }
打成jar包放到hadoop上去执行:
$hadoop jar filesystemcat.jar hdfs://localhost/user/norris/weatherdata.txt
在控制台输出了weatherdata.txt文件里的内容。


大家看一下API,fs.open方法的返回值是FSDataInputStream。而不是java.io包里的流。
这个流继承自java.io.DataInputStream,同一时候实现Seekable(org.apache.hadoop.fs.Seekable)。也就是说能够调用seek()方法读取文件的任何位置数据。
比如上面程序加两行:
  1. in.seek(0);
  2. IOUtils.copyBytes(in, System.out, 4096, false);
就能够实现把文件内容打印两遍。

另外。FSDataInputStream类同一时候也实现了PositionedReadable(org.apache.hadoop.fs.PositionedReadable)接口,接口中定义的三个方法同意在任何位置读取文件内容:
  1. public int read(long position, byte[] buffer, int offset, int length) throws IOException;
  2. public void readFully(long position, byte[] buffer, int offset, int length) throws IOException;
  3. public void readFully(long position, byte[] buffer) throws IOException;
而且,这些方法是线程安全的,可是FSDataInputStream不是线程安全的,仅仅是说不同的FSDataInputStream实例在不同的线程里读文件内容是安全的。可是一个FSDataInputStream实例被不同的线程调用是不安全的。所以,应该为每一个线程创建各自的FSDataInputStream实例。
最后,调用seek()方法的开销是相当巨大的。应该尽量少调用,你的程序应该设计成尽量流式获取文件内容。


用FileSystem类来向HDFS里写文件

FileSystem类里提供了一大堆API用来在创建文件。当中最简单的一个是:
  1. public FSDataOutputStream create(Path f) throws IOException;
创建一个Path类代表的文件。并返回一个输出流。
这种方法有一大堆的重载方法。能够用来设置是否覆盖已有文件,该文件复制的份数,写入时的缓冲区大小,文件块大小(block),权限等。
假设该Path代表的文件的父文件夹(甚至爷爷文件夹)不存在。这些文件夹会被自己主动创建,这样确时在非常多情况下提供了方便。可是有时却与我们的需求不相符,假设你确实希望当父文件夹不存在时不要创建。应该自己推断其父文件夹是否存在。原来的API中有个createNonRecursive方法,假设父文件夹不存在会失败。但如今这一组方法已经被废弃。不建议使用了。
create方法的一个重要重载方法是:
  1. public FSDataOutputStream create(Path f, Progressable progress) throws IOException;
Progressable接口的progress方法能够用来当有数据写入时回调。
  1. public interface Progressable {
  2. public void progress();
  3. }
假设不想新建一个文件,而是希望向已有文件追加内容,能够调用:
  1. public FSDataOutputStream append(Path f) throws IOException;
该方法是可选实现方法,也就是说不是全部的Hadoop FileSystem都实现了该方法,比如。HDFS实现了。而S3文件系统就没有实现。而且。HDFS在1.x以后的版本号中的实现才是稳定的实现,之前的实现是有问题的。

以下这个程序把一个本地文件上传到HDFS上。而且在每次progress方法被调用时输出一个点(.)
  1. import java.io.BufferedInputStream;
  2. import java.io.FileInputStream;
  3. import java.io.InputStream;
  4. import java.io.OutputStream;
  5. import java.net.URI;
  6.  
  7. import org.apache.hadoop.conf.Configuration;
  8. import org.apache.hadoop.fs.FileSystem;
  9. import org.apache.hadoop.fs.Path;
  10. import org.apache.hadoop.io.IOUtils;
  11. import org.apache.hadoop.util.Progressable;
  12.  
  13. public class FileCopyWithProgress {
  14. public static void main(String[] args) throws Exception {
  15. String localSrc = args[0];
  16. String dst = args[1];
  17.  
  18. InputStream in = new BufferedInputStream(new FileInputStream(localSrc));
  19.  
  20. Configuration conf = new Configuration();
  21. FileSystem fs = FileSystem.get(URI.create(dst), conf);
  22. OutputStream out = fs.create(new Path(dst), new Progressable() {
  23. @Override
  24. public void progress() {
  25. System.out.print(".");
  26. // try {
  27. // Thread.sleep(1000);
  28. // } catch (Exception e) {
  29. // e.printStackTrace();
  30. // }
  31. }
  32. });
  33. IOUtils.copyBytes(in, out, 4096, true);
  34. System.out.println();
  35. System.out.println("end.");
  36. }
  37. }
程序中的progress什么条件下被调用呢?这个API并没有说明,也就是说你不能如果它在某时会被调用。实际的測试结果是大概写入64K时会被调用一次,但多次測试也不一致,尤其在文件小时,更加不可预知。
假设打开上面程序的Thread.sleep()的凝视,会发现,事实上在progress在被调用时,主程序是等待的。假设把主线程的ID和调用progress方法的线程ID打印出来发现。每次调用progress的线程都是同一个线程,但不是主线程。可是,主线程的"end."确实是在全部点(.)都输出后才输出的。
我复制的文件是4743680字节,即大概4.5M,打出73个点。平均64K一个点。

到眼下为止,仅仅有HDFS在写入文件时回调progress,其他文件系统都没有实现回调progress,在后面做MapReduce程序时会发现,这一回调很实用。

FileSystem里的create方法返回的FSDataOutputStream类有一个方法:
  1. public long getPos() throws IOException;
能够查询当前在往文件的哪个位置写,一个写入的偏移量。
可是与FSDataInputStream不同,FSDataOutputStream没有seek方法,由于HDFS仅仅同意顺序写,对于一个打开的文件。仅仅同意向尾部追加,不同意在任何位置写,因此也就没有必要提供seek方法了。


创建文件夹

FileSystem中的创建文件夹的方法:
public boolean mkdirs(Path f) throws IOException;
与java.io.File.mkdirs方法一样,创建文件夹,并同一时候创建缺失的父文件夹。
我们一般不须要创建文件夹,由于创建文件时,默认就把所需的文件夹都创建好了。

查询文件元信息:FileStatus(org.apache.hadoop.fs.FileStatus)

FileSystem类中的getFileStatus()方法返回一个FileStatus实例。该FileStatus实例中包括了该Path(文件或文件夹)的元信息:文件大小,block大小,复制的份数,最后改动时间。全部者。权限等。

程序简单,先上代码,再解释一下
  1. import static org.junit.Assert.*;
  2. import static org.hamcrest.CoreMatchers.*;
  3. import java.io.FileNotFoundException;
  4. import java.io.IOException;
  5. import java.io.OutputStream;
  6. import org.apache.hadoop.conf.Configuration;
  7. import org.apache.hadoop.fs.FileStatus;
  8. import org.apache.hadoop.fs.FileSystem;
  9. import org.apache.hadoop.fs.Path;
  10. import org.apache.hadoop.hdfs.MiniDFSCluster;
  11. import org.junit.After;
  12. import org.junit.Before;
  13. import org.junit.Test;
  14. public class ShowFileStatusTest {
  15. private static final String SYSPROP_KEY = "test.build.data";
  16. /** MiniDFSCluster类在hadoop-hdfs-2.4.1-tests.jar中,是一个专门用于測试的in-process HDFS集群 */
  17. private MiniDFSCluster cluster;
  18. private FileSystem fs;
  19. @Before
  20. public void setUp() throws IOException {
  21. Configuration conf = new Configuration();
  22. String sysprop = System.getProperty(SYSPROP_KEY);
  23. if (sysprop == null) {
  24. System.setProperty(SYSPROP_KEY, "/tmp");
  25. }
  26. cluster = new MiniDFSCluster(conf, 1, true, null);
  27. fs = cluster.getFileSystem();
  28. OutputStream out = fs.create(new Path("/dir/file"));
  29. out.write("content".getBytes("UTF-8"));
  30. out.close();
  31. }
  32. @After
  33. public void tearDown() throws IOException {
  34. if (fs != null) {
  35. fs.close();
  36. }
  37. if (cluster != null) {
  38. cluster.shutdown();
  39. }
  40. }
  41. @Test(expected = FileNotFoundException.class)
  42. public void throwsFileNotFoundForNonExistentFile() throws IOException {
  43. fs.getFileStatus(new Path("no-such-file"));
  44. }
  45. @Test
  46. public void fileStatusForFile() throws IOException {
  47. Path file = new Path("/dir/file");
  48. FileStatus stat = fs.getFileStatus(file);
  49.  
  50. assertThat(stat.getPath().toUri().getPath(), is("/dir/file"));
  51. assertThat(stat.isDirectory(), is(false));
  52. assertThat(stat.getLen(), is(7L));
  53. assertTrue(stat.getModificationTime() <= System.currentTimeMillis());
  54. assertThat(stat.getReplication(), is((short)1));
  55. assertThat(stat.getBlockSize(), is(64 * 1024 * 1024L));
  56. assertThat(stat.getOwner(), is("norris"));
  57. assertThat(stat.getGroup(), is("supergroup"));
  58. assertThat(stat.getPermission().toString(), is("rw-r--r--"));
  59. }
  60. @Test
  61. public void fileStatusForDirectory() throws IOException {
  62. Path dir = new Path("/dir");
  63. FileStatus stat = fs.getFileStatus(dir);
  64. assertThat(stat.getPath().toUri().getPath(), is("/dir"));
  65. assertThat(stat.isDirectory(), is(true));
  66. assertThat(stat.getLen(), is(0L));
  67. assertTrue(stat.getModificationTime() <= System.currentTimeMillis());
  68. assertThat(stat.getReplication(), is((short)0));
  69. assertThat(stat.getBlockSize(), is(0L));
  70. assertThat(stat.getOwner(), is("norris"));
  71. assertThat(stat.getGroup(), is("supergroup"));
  72. assertThat(stat.getPermission().toString(), is("rwxr-xr-x"));
  73. }
  74. }
程序编译须要引入hadoop-hdfs-2.4.1-tests.jar和hadoop-hdfs-2.4.1.jar,在Hadoop的安装包中能够找到。
MiniDFSCluster类在hadoop-hdfs-2.4.1-tests.jar中,是一个专门用于測试的内存HDFS集群。
其他代码看名称都能够理解,仅仅是这个JUnit的assertThat方法让我整了好半天,不知道is方法是哪个类里的静态方法。找了网上都直接这样写。好像写了就能用似的,可是我却编译只是,后来才知道,是org.hamcrest.CoreMatchers类里的静态方法。须要静态引入该类。另外有个lessThanOrEqualTo方法,死活没找到在哪个类里,仅仅好用assertTrue方法取代了。

程序执行须要用JUnit启动,由于这是一个test case。
把junit.jar和org.hamcrest.core_1.3.0.v201303031735.jar放到server上。改动hadoop-env.sh。把我们刚才改动的最后一行:
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:/home/norris/data/hadoop/bin/
再追加上这两个jar。即:
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:/home/norris/data/hadoop/bin/:/home/norris/data/hadoop/lib/junit.jar:/home/norris/data/hadoop/lib/org.hamcrest.core_1.3.0.v201303031735.jar

然后执行时不是执行我们写的类,而是执行junit,然后让junit去test我们写的类:
$hadoop org.junit.runner.JUnitCore ShowFileStatusTest
执行结果发现,进行了三个測试。两个成功了,一个失败了,失败在
assertThat(stat.getBlockSize(), is(64 * 1024 * 1024L));这行
期望值是:67108864。也就是64M。实际值是:134217728,也就是128M。也就是说HDFS的block size的默认值是128M,这个我还没搞明确是由于新版的Hadoop改动了block size的默认值还是由于我又一次在64位系统下编译了Hadoop,假设在hdfs-site.xml中设置:
<property>
    <name>dfs.block.size</name>
    <value>67108864</value>
</property>
把block size设成64M,就三个都成功了。

Hadoop HDFS (3) JAVA訪问HDFS的更多相关文章

  1. Hadoop HDFS (3) JAVA訪问HDFS之二 文件分布式读写策略

    先把上节未完毕的部分补全,再剖析一下HDFS读写文件的内部原理 列举文件 FileSystem(org.apache.hadoop.fs.FileSystem)的listStatus()方法能够列出一 ...

  2. Hadoop-2.6.0上的C的API訪问HDFS

    在通过Hadoop-2.6.0的C的API訪问HDFS的时候,编译和执行出现了不少问题,花费了几天的时间,上网查了好多的资料,最终还是把问题给攻克了 參考文献:http://m.blog.csdn.n ...

  3. HDFS简单介绍及用C语言訪问HDFS接口操作实践

    一.概述 近年来,大数据技术如火如荼,怎样存储海量数据也成了当今的热点和难点问题,而HDFS分布式文件系统作为Hadoop项目的分布式存储基础,也为HBASE提供数据持久化功能,它在大数据项目中有很广 ...

  4. JAVA訪问URL

    JAVA訪问URL: package Test; import java.io.BufferedReader; import java.io.IOException; import java.io.I ...

  5. Java 訪问权限控制:你真的了解 protected keyword吗?

    摘要: 在一个类的内部,其成员(包含成员变量和成员方法)是否能被其它类所訪问,取决于该成员的修饰词:而一个类是否能被其它类所訪问,取决于该类的修饰词.Java的类成员訪问权限修饰词有四类:privat ...

  6. Cassandra数据库Java訪问

    针对的时Cassandra 2.0 数据库 Java本地client訪问Cassandra,首先建立Javaproject,使用Maven进行管理. 引入依赖: <dependency> ...

  7. Hadoop学习(2)-java客户端操作hdfs及secondarynode作用

    首先要在windows下解压一个windows版本的hadoop 然后在配置他的环境变量,同时要把hadoop的share目录下的hadoop下的相关jar包拷贝到esclipe 然后Build Pa ...

  8. HDFS的java接口——简化HDFS文件系统操作

    今天闲来无事,于是把HDFS的基本操作用java写出简化程序出来给大家一些小小帮助! package com.quanttech; import org.apache.hadoop.conf.Conf ...

  9. 三国武将查询系统 //Java 訪问 数据库

    import java.awt.*; import javax.swing.*; import java.awt.event.ActionListener; import java.awt.event ...

随机推荐

  1. Perl数组: shift, unshift, push, pop

    pop pop函数会删除并返回数组的最后一个元素. .. ; $fred = pop(@array); # $fred变成9,@array 现在是(5,6,7,8) $barney = pop @ar ...

  2. OA系统权限管理设计方案学习

    学习之:http://www.cnblogs.com/kivenhou/archive/2009/10/19/1586106.html 此为模型图: 据此写了sql语句: drop table if ...

  3. 详解函数声明VS函数表达式

    函数声明 比方如下:1.我们以一个完整的语句以function开头,不加任何东西. 2.有一个函数名(add) 3.参数可带可不带(x,y) 4.有一个数体 满足以上要求的我们统称为函数声明! 附加小 ...

  4. rel=nofollow 是什么意思

    nofollow是什么意思? nofollow是html标签的一个属性值,Google推荐使用nofollow,告诉机器(爬虫)无需追踪目标页,是指禁止蜘蛛爬行和传递权重,但是如果你是通过sitema ...

  5. [前端笔记]第三篇:JavaScript

    JavaScript是一门编程语言,浏览器内置了JavaScript语言的解释器,所以在浏览器上按照JavaScript语言的规则编写相应代码之,浏览器可以解释并做出相应的处理. 一.代码存放位置 J ...

  6. 03:计算(a+b)/c的值

    总时间限制:  1000ms 内存限制:  65536kB 描述 给定3个整数a.b.c,计算表达式(a+b)/c的值,/是整除运算. 输入 输入仅一行,包括三个整数a.b.c, 数与数之间以一个空格 ...

  7. 将数据库字段从float修改为decimal

    decimal(6,2) 可以表示0000.00~9999.99 alter table test modify aaa decimal(6,2); 则表里所有大于10000的数会被设置为9999.9 ...

  8. ubuntu下配置protobuf

    http://blog.csdn.net/guoyilongedu/article/details/17093811 最近想研究protobuf ,尝试了很多次都没有成功,我用的是ubuntu,在虚拟 ...

  9. zip file 压缩文件

    有时候我们希望 upload 文件后自动压缩, 可以节省空间. 可以使用微软提供的压缩代码 Install-Package System.IO.Compression.ZipFile -Version ...

  10. unity 基础学习 transform

    unity  基础学习   transform 1.unity采用的是右手坐标系,X轴右手为+,Y轴向上为+,Z轴朝里为+; 但是我们从3D MAX中导入模型之后,发现轴向并没有遵从这个原理, 其实是 ...