HDFS-java客户端操作API

客户端操作环境搭建

1.环境准备工作:

在windows使用java客户端调用HDFS集群时,会初始化一个本地的文件系统对象,期间会用到hadoop编译包的一些文件。所以我们需要将Hadoop安装包解压到某个目录,并配置HADOOP_HOME 环境变量同时在path中添加%HADOOP_HOME%/bin

需要注意,要选择对应版本的windows编译包。如果用Linux包(即集群中安装的hadoop包)解压,则需要手动添加winutils.exe和hadoop.dll到bin目录。

winutils对应版本下载链接如下,我用的是Hadoop3.1.3,版本列表没有,使用的3.1.2代替也生效。

https://github.com/cdarlint/winutils

2.新建maven工程:

添加依赖:依赖的版本可以和集群的hadoop版本不一致。

 <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>-->
            <!--<groupId>org.apache.logging.log4j</groupId>-->
            <!--<artifactId>log4j-core</artifactId>-->
            <!--<version>2.8.2</version>-->
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs-client</artifactId>
            <version>3.2.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>3.2.0</version>
        </dependency>
    </dependencies>

添加一个日志配置文件log4j.properties到resources目录:

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

3.新建测试类:

package com.zd.hadoop;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.Test;

public class HadfsClient {
    @Test
    public void testHdfs(){
        Configuration conf= new Configuration();
        conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
        conf.set("fs.defaultFS", "hdfs://192.168.43.11:9000");
        try {
            FileSystem fs = FileSystem.get(conf);
            // 2 创建目录
            fs.mkdirs(new Path("/zx/6222"));
            // 3 关闭资源
            fs.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行后,在集群的hdfs便可以生成一个目录:

在这里插入图片描述

常见错误:
  1. 缺少wintuils.exe 和hadoop.dll
Exception in thread "main" java.lang.RuntimeException: java.io.FileNotFoundException: Could not locate Hadoop executable:
  1. 权限错误:
org.apache.hadoop.security.AccessControlException: Permission denied: user=Administrator, access=WRITE, inode="/zx":xiaomao:supergroup:drwxr-xr-x

这是因为调用用户使用了windows的administrator,要采用集群中的用户,所以通过下面方式调用:指明用户是 xiaomao

 FileSystem fs = FileSystem.get(new URI("hdfs://192.168.43.11:9000"), conf, "xiaomao");

3.hadoop环境配置错误

java.io.FileNotFoundException:HADOOP_HOME and hadoop.home.dir are unset

该错误是因为没有再windows本地配置hadoop,参加文章开头。

4.main方法错误

Exception in thread "main" org.apache.hadoop.fs.UnsupportedFileSystemException: No FileSystem for scheme "hdfs"

因为依赖的范围是provided,所以运行main方法,报错。

该错误还有一个可能的原因是,如果配置了maven-assembly-plugin插件,描述如下

Different JARs (hadoop-commons for LocalFileSystem, hadoop-hdfs for DistributedFileSystem) each contain a different file called org.apache.hadoop.fs.FileSystem in their META-INFO/services directory. This file lists the canonical classnames of the filesystem implementations they want to declare (This is called a Service Provider Interface implemented via java.util.ServiceLoader, see org.apache.hadoop.FileSystem line 2622).

When we use maven-assembly-plugin, it merges all our JARs into one, and all META-INFO/services/org.apache.hadoop.fs.FileSystem overwrite each-other. Only one of these files remains (the last one that was added). In this case, the FileSystem list from hadoop-commons overwrites the list from hadoop-hdfs, so DistributedFileSystem was no longer declared.

这是需要这样修复:

hadoopConfig.set("fs.hdfs.impl", 
        org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()
    );
    hadoopConfig.set("fs.file.impl",
        org.apache.hadoop.fs.LocalFileSystem.class.getName()

或者在core-site.xml 添加如下:

<property>
   <name>fs.file.impl</name>
   <value>org.apache.hadoop.fs.LocalFileSystem</value>
   <description>The FileSystem for file: uris.</description>
</property>

<property>
   <name>fs.hdfs.impl</name>
   <value>org.apache.hadoop.hdfs.DistributedFileSystem</value>
   <description>The FileSystem for hdfs: uris.</description>
</property>
客户端常用API

完整代码github

1.文件常见API操作
package com.zd.hadoop;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.apache.hadoop.fs.permission.FsPermission;
import org.junit.Test;

import java.net.URI;

/**
 * HDFS客户端文件操作API
 */
public class HDFSClientAPI {

    private String url = "hdfs://192.168.43.11:9000";
    private String user = "xiaomao";
    /**
     * 获取一个只有基本配置FileSystem对象
     * @return
     */
    public FileSystem createCommonFS() {
        try {
            Configuration conf= new Configuration();
            //如果对接的文件系统
            FileSystem fs = FileSystem.get(new URI(url), conf, user);
            return fs;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 创建目录
     * @throws Exception
     */
    @Test
    public void mkdirs() throws Exception{
        FileSystem fs = createCommonFS();
        // 2 创建目录
        fs.mkdirs(new Path("/zx/7117"));
        // 3 关闭资源
        fs.close();
    }


    /**
     * 删除文件或文件夹
     * @throws Exception
     */
    @Test
    public void delete() throws Exception{
        FileSystem fs = createCommonFS();
        //如果删除是目录recursive必须设置为true,表示递归删除。如果是文件,true和false都可以。
        fs.delete(new Path("/zx/7117"),true);
        fs.close();
    }


    /**
     * 上传文件
     * @throws Exception
     */
    @Test
    public void copyFromLocalFile() throws Exception{
        FileSystem fs = createCommonFS();
        // 如果参数为 /zx/file/a.txt 表示重命名为a.txt
        fs.copyFromLocalFile(new Path("f:/hdfs.txt"),new Path("/zx/file/"));
    }


    /**
     * 下载文件
     * @throws Exception
     */
    @Test
    public void copyToLocalFile() throws Exception{
        FileSystem fs = createCommonFS();
        // 表示将file目下的文件拷贝到当前f盘,如果指定文件名,则是拷贝指定文件到本地
        fs.copyToLocalFile(new Path("/zx/file/"),new Path("f:/"));
    }

    /**
     * 修改文件夹或文件名
     * @throws Exception
     */
    @Test
    public void rename() throws Exception{
        FileSystem fs = createCommonFS();
        fs.rename(new Path("/zx/file"),new Path("/zx/files"));
    }

    /**
     * 查看文件详情
     * @throws Exception
     */
    @Test
    public void listFiles() throws Exception{
        FileSystem fs = createCommonFS();
        RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"),true);
        while(listFiles.hasNext()){
            LocatedFileStatus fileStatus = listFiles.next();
            System.out.println(fileStatus.isFile()?"文件":"文件夹");//判断文件类型
            System.out.println(fileStatus.getPath().getName());// 文件名称
            System.out.println(fileStatus.getPermission());// 文件权限
            System.out.println(fileStatus.getLen());// 文件长度

            BlockLocation[] blockLocations = fileStatus.getBlockLocations();
            for(BlockLocation blockLocation:blockLocations){
                String[] hosts = blockLocation.getHosts();
                for(String host:hosts){
                    System.out.println(host);
                }
            }

        }

        fs.close();
    }

    /**
     * 判断是文件夹还是文件
     * @throws Exception
     */
    @Test
    public void isFile() throws Exception{
        FileSystem fs = createCommonFS();
        // 2 判断操作
        FileStatus[] listStatus = fs.listStatus(new Path("/"));

        for (FileStatus fileStatus : listStatus) {

            if (fileStatus.isFile()) {
                // 文件
                System.out.println("f:"+fileStatus.getPath().getName());
            }else{
                // 文件夹
                System.out.println("d:"+fileStatus.getPath().getName());
            }
        }

        // 3 关闭资源
        fs.close();
    }




    /**
     * 检测环境变量
     */
    @Test
    public void testEnv(){
        System.out.println(System.getenv("HADOOP_HOME"));
    }
}

2.流操作-分块下载
package com.zd.hadoop;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.junit.Test;

/**
 * HDFS IO流相关操作
 */
public class HDFSIO {


	/**
	 * 通过流 上传文件到HDFS
	 * @throws IOException
	 * @throws InterruptedException
	 * @throws URISyntaxException
	 */
	@Test
	public void putFileToHDFS() throws IOException, InterruptedException, URISyntaxException{
		
		// 1 获取对象
		Configuration conf = new Configuration();
		FileSystem fs = FileSystem.get(new URI("hdfs://hadoop101:9000"), conf , "xiaomao");
		
		// 2 获取输入流
		FileInputStream fis = new FileInputStream(new File("f:/测试文件.docx"));
		
		// 3 获取输出流
		FSDataOutputStream fos = fs.create(new Path("/zx/622/测试文件.docx"));
		
		// 4 流的对拷
		IOUtils.copyBytes(fis, fos, conf);
		
		// 5 关闭资源
		IOUtils.closeStream(fos);
		IOUtils.closeStream(fis);
		fs.close();
	}


	/**
	 * 通过流下载hdfs上的文件到本地
	 * @throws IOException
	 * @throws InterruptedException
	 * @throws URISyntaxException
	 */
	@Test
	public void getFileFromHDFS() throws IOException, InterruptedException, URISyntaxException{
		
		// 1 获取对象
		Configuration conf = new Configuration();
		FileSystem fs = FileSystem.get(new URI("hdfs://hadoop101:9000"), conf , "xiaomao");
		
		// 2 获取输入流
		FSDataInputStream fis = fs.open(new Path("/zx/622/测试文件.docx"));
		
		// 3 获取输出流
		FileOutputStream fos = new FileOutputStream(new File("f:/download.docx"));
		
		// 4 流的对拷
		IOUtils.copyBytes(fis, fos, conf);
		
		// 5 关闭资源
		IOUtils.closeStream(fos);
		IOUtils.closeStream(fis);
		fs.close();
	}
	
	//下载第一块
	@Test
	public void readFileSeek1() throws IOException, InterruptedException, URISyntaxException{
		
		// 1 获取对象
		Configuration conf = new Configuration();
		FileSystem fs = FileSystem.get(new URI("hdfs://hadoop101:9000"), conf , "xiaomao");
		
		// 2 获取输入流
		FSDataInputStream fis = fs.open(new Path("/zx/622/测试文件.docx"));
		
		// 3 获取输出流
		FileOutputStream fos = new FileOutputStream(new File("f:/测试文件.docx.part1"));
		
		// 4 流的对拷(只拷贝20m)
		byte[] buf = new byte[1024];
		for (int i = 0; i < 1024 * 20; i++) {
			fis.read(buf);
			fos.write(buf);
		}
		
		// 5 关闭资源
		IOUtils.closeStream(fos);
		IOUtils.closeStream(fis);
		fs.close();
	}
	
	// 下载第二块
	@SuppressWarnings("resource")
	@Test
	public void readFileSeek2() throws IOException, InterruptedException, URISyntaxException{
		
		// 1 获取对象
		Configuration conf = new Configuration();
		FileSystem fs = FileSystem.get(new URI("hdfs://hadoop101:9000"), conf , "xiaomao");
		
		// 2 获取输入流
		FSDataInputStream fis = fs.open(new Path("/zx/622/测试文件.docx"));
		
		// 3 设置指定读取的起点
		fis.seek(1024*1024*20);
		
		// 4 获取输出流
		FileOutputStream fos = new FileOutputStream(new File("f:/测试文件.docx.part2"));
		
		// 5 流的对拷
		IOUtils.copyBytes(fis, fos, conf);
		
		// 6 关闭资源
		IOUtils.closeStream(fos);
		IOUtils.closeStream(fis);
		fs.close();
	}
	

对分块下载的文件,在windows下,进入命令行窗口,用下面的命令合并

type 测试文件.docx.part2>>测试文件.docx.part1

修改后缀后,便可以正常打开。

附录:

scope依赖范围:

compile (编译范围)

compile是默认的范围;如果没有提供一个范围,那该依赖的范围就是编译范围。编译范围依赖在所有的classpath 中可用,同时它们也会被打包。

provided (已提供范围)

provided 依赖只有在当JDK 或者一个容器已提供该依赖之后才使用。例如, 如果你开发了一个web 应用,你可能在编译 classpath 中需要可用的Servlet API 来编译一个servlet,但是你不会想要在打包好的WAR 中包含这个Servlet API;这个Servlet API JAR 由你的应用服务器或者servlet 容器提供。已提供范围的依赖在编译classpath (不是运行时)可用。它们不是传递性的,也不会被打包。

runtime (运行时范围)

runtime 依赖在运行和测试系统的时候需要,但在编译的时候不需要。比如,你可能在编译的时候只需要JDBC API JAR,而只有在运行的时候才需要JDBC
驱动实现。

test (测试范围)

test范围依赖 在一般的编译和运行时都不需要,它们只有在测试编译和测试运行阶段可用。

system (系统范围)

system范围依赖与provided 类似,但是你必须显式的提供一个对于本地系统中JAR 文件的路径。这么做是为了允许基于本地对象编译,而这些对象是系统类库的一部分。这样的构件应该是一直可用的,Maven 也不会在仓库中去寻找它。如果你将一个依赖范围设置成系统范围,你必须同时提供一个 systemPath 元素。注意该范围是不推荐使用的(你应该一直尽量去从公共或定制的 Maven 仓库中引用依赖)。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值