Hadoop:文件操作之Java接口(FileSystem类)

目录

通过java.net.URL读取数据

通过FlieSystem API读写数据

读取数据

写入数据

新建目录

新建文件并写入

追加写入

一致模型

修改配置信息

通过FileStatus查询文件系统

查看文件系统信息

查看块信息

查看datanode信息


通过java.net.URL读取数据

让java程序能够识别hadoop的hdfs URL需要先通过FsUrlStreamHandlerFactory实例调用java.net.URL对象的setURLStreamHandlerFactory()方法,每个java虚拟机只能调用一次此方法,所以一般在静态方法中调用;也就是说程序的其他部件已经声明了一个URLStreamHandlerFactory实例就无法使用这种方法读取数据。所以一般很少使用这种方法读取数据

以下方法类似于Linux中的cat命令显示hadoop文件系统中的文件内容:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.hadoop.fs.FsUrlStreamHandlerFactory;
import org.apache.hadoop.io.IOUtils;

public class CatByURL {
	static{
		URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
	}
	public static void main(String[] args) {
		InputStream in = null;
		try {
			//通过URL对象获取流
			in = new URL("hdfs://master1:9000/d1/file").openStream();
			// static void copyBytes(InputStream in, OutputStream out, int buffSize, boolean close)
			// 最后一个参数指复制结束后是否关闭数据流,因为System.out不必关闭输入流
			IOUtils.copyBytes(in, System.out, 10, false);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			//关闭数据流
			IOUtils.closeStream(in);
		}
	}
}

 

通过FlieSystem API读写数据

Hadoop文件系统通过Hadoop Path对象来代表文件,可以将路径视为一个Hadoop文件系统URI。FileSystem是一个通用的文件系统API

获取FileSystem实例的静态工厂方法之一:

public static FileSystem get(Configuration conf) throws IOException

读取数据

第一步需要检索HDFS,Configuration对象封装了客户端或服务器的配置。通过设置配置文件读取类路径来实现;此方法返回的是默认文件系统(core-site.xml,core-default.xml)

同样在获取了FileSystem实例后,调用open()方法可以获取文件的输入流

public FSDataInputStream open(Path f) throws IOException    //默认缓冲区为4kb
public abstract FSDataInputStream open(Path f, int bufferSize) throws IOException

我们可以重写上面的CatByURL

	@Test
	public void catbyFS() throws IOException{
		/* 创建配置对象conf,加载配置文件信息:
		 * 比如hadoop-common-2.7.3.jar里的core-default和src下的core-site.xml*/
		Configuration conf = new Configuration();
		/* 通过FileSystem提供的静态方法get(Configuration conf)
		 * 获取FileSystem的具体实例,DistributedFileSystem
                 * 同时加载了剩下的六个配置文件  */
		FileSystem fs = FileSystem.get(conf);
		/*使用Path来描述HDFS系统上的一个文件*/
		Path path = new Path("hdfs://master1:9000/d1/file");
		/*通过fs.open(Path path)方法 来进行一些权限,路径等校验,如果校验通过,就返回一个输入流*/
		FSDataInputStream input = fs.open(path);
		IOUtils.copyBytes(input, System.out, 10, false);
		IOUtils.closeStream(input);
	}

FSDataInputStream对象

FSDataInputStream并不是标准的java.io对象,支持随机访问

public class FSDataInputStream extends DataInputStream
    implements Seekable, PositionedReadable//...
    {
}

FSDataInputStream是继承了DataInputStream的一个特殊类,并且实现了Seekable接口,支持在文件中找到指定位置,并提供了一个查询当前文职相对于文件起始位置偏移量的查询方法:

public interface Seekable {
  /* 从文件开始查找给定的偏移量。下一个read()将来自该位置。不能查找文件末尾后面的内容*/
  void seek(long pos) throws IOException;
  
  /* 返回从文件开始的当前偏移量*/
  long getPos() throws IOException;

  @Override
  int read(ByteBuffer buf)

  //...
}

seek()与InputStream的skip()方法不同,seek()可以移到文件中任意一个绝对位置,skip()只能相对于当前位置定位到另一个新位置

@Test 
	public void testSeek() throws Exception{
		Configuration conf = new Configuration();
		FileSystem fs = FileSystem.get(conf);
		Path path = new Path("hdfs://master1:9000/d1/file");
		FSDataInputStream in = fs.open(path);
		Long pos = in.getPos();
		System.out.println("起始位置:"+pos);
		IOUtils.copyBytes(in, System.out, 10, false);
		pos = in.getPos();
		System.out.println("结束位置(文件字节数)"+pos);
		//将指针移动到文件的第10个字节之后,重新往后读取文件
		in.seek(10);
		pos = in.getPos();
		System.out.println(pos);
		//一次只能读一个块的信息
		byte[] bs = new byte[10];
		in.read(bs);
		System.out.println(new String(bs));
		IOUtils.closeStream(in);
	}

运行结果

起始位置:0
asdasd
asdadsa
结束位置(文件字节数):15
10
adsa

与IOUtils.copyBytes()不同的是,通过bytes数组读取数据只能读取到一个块的数据,比如块大小为10b,文件大小为15b,则文件分为2个块存储在datanode上,即使通过15b的byte数组读取数据,也只能获取到第一个块内的10b数据;

另外seek()方法是一个相对高开小的操作,需要慎重使用

 

写入数据

新建目录

通过FileSystem的mkdir(Path f)方法,并且可以创建多级目录,该方法是返回boolean类型;相当于shell接口的hadoop fs -mkdir -p

	//@Test
	public void testMkdir() throws Exception{
		Configuration conf = new Configuration();
		FileSystem fs = FileSystem.get(conf);
		Path path = new Path("hdfs://master1:9000/d2");
		fs.mkdirs(path);
	}

新建文件并写入

默认加载客户端上的默认配置信息,或者可以在src目录下创建hdfs-site.xml设置自定义属性,,create方法写入文件时会自动创建父目录,不需要显示创建一个目录

相当于shell 接口的hadoop fs -put localfile dst

    @Test
    public void testWrite() throws Exception{
		Configuration conf = new Configuration();
		FileSystem fs = FileSystem.get(conf);
		Path path = new Path("hdfs://master1:9000/d2/f3");
		FSDataOutputStream out = fs.create(path);
		out.write("asdasd".getBytes());
		out.close();
	}

追加写入

与新建文件不同的是,FileSystem的实例需要调用append(Path f)获得FSDataOutputStream类对象,即可对已经存在的文件进行写入

注意:当默认副本数大于节点数时,可以在hdfs-site.xml中设置属性:dfs.client.block.write.replace-datanode-on-failure.policy为NEVER

dfs.client.block.write.replace-datanode-on-failure.policy属性仅在dfs.client.block.write.replace-datanode-on-failure为true时生效,属性值:

  •  ALWAYS:总是在删除现有的datanode时添加一个新的datanode
  • NEVER:永远不要添加新的datanode
  • DEFAULT:设r为复制数,设n为现有datanode的个数;当r≥3或floor(r/2)≥n或r>n,块=hflushed/appended

FSDataOutputStream对象

FileSystem实例的create()方法返回FSDataOutputStream对象,它也有一个查询文件当前位置的方法getPos()

public class FSDataOutputStream extends DataOutputStream implements Syncable{
 
    public void write(int b) throws IOException {}
    
    public void write(byte b[], int off, int len) throws IOException {}
      
    public long getPos() throws IOException {}
    
    public void close() throws IOException {}

    //...    
}

与FSDataInputStream不同的是,FSDataOutputStream类不允许在文件中定位,因为HDFS只允许对一个一打开的文件进行顺序写入,或在现有文件的末尾追加数据

一致模型

文件系统的一致模型描述了文件读写的数据可见性

通过新建一个文件之后,即FileSystem实例调用create方法后,文件在文件系统的命名空间中立即可见;但是,写入文件的内容并不保证能立即可见,即使数据块已经刷新并存储(调用flush()方法),文件长度显示仍为0;只有当写的数据超过一个块后,之前的数据块是可见的,当前正在写入的块是不可见的

FSDataOutputStream提供了hdfs()方法,可以强行将客户端内缓存刷新到datanode中的内存中,若成功执行hdfs能保存文件中到目前为止写入的数据均到达所有datanode的写入管道,并且可见;但hflush不能保证datanode已经将数据写到磁盘上,如果数据中心断电,数据会丢失

public void hflush() throws IOException

hsync()方法能保证数据持久化到磁盘上

public void hsync() throws IOException

 

修改配置信息

在以下这两行代码中,客户端获取了hadoop的相关配置信息

		Configuration conf = new Configuration();
		FileSystem fs = FileSystem.get(conf);

第一行代码读取了core-default和core-site.xml;

第二行读取了 hdfs-default.xml,hdfs-site.xml,mapred-default.xml,mapred-site.xml,yarn-default.xml,yarn-site.xml;

而在对每一个配置文件的读取上存在着前后顺序,客户端会先读取集群上(即namenode)的配置文件,然后读取客户端(src目录)下的配置文件(若存在);所以我们可以在src下新建配置文件,这样就不需要去集群中修改;但是有一些属性只能在集群中修改,在客户端修改会无效,比如像hdfs-site.xml中的dfs.namenode.fs-limits.min-block-size(最小块大小)这种名字中含有"namenode"的属性一般都需要在集群中修改

另外也可以通过调用Configuration实例的set(String name, String value)方法设置属性值,但注意需要在FileSystem.get(conf)之前设置,比如:

		Configuration conf = new Configuration();
		conf.set("dfs.blocksize", "1500000");
		FileSystem fs = FileSystem.get(conf);

dfs.blocksize为hdfs-site.xml中的属性,而构造Configuration的实例的过程仅读取了core有关属性;其实这是一个设置属性的过程,而不是覆盖原来属性值,当运行到FileSystem.get(conf)时,读取属性若客户端中已经有了相关属性就不会去覆盖了,这也就是为什么set访问写在get后无法修改属性值的原因

通过FileStatus查询文件系统

查看文件系统信息

FileStatus类封装了文件系统中的文件和目录的元数据,包括文件长度,块大小,复本,修改时间,所有者以及权限信息

FileSystem的getFileStatus()方法可以获取文件或目录的FileStatus对象,相当于shell接口的hadoop fs -ls

	//@Test
	public void testStatus() throws Exception{
		Configuration conf = new Configuration();
		FileSystem fs = FileSystem.get(conf);
		Path path = new Path("hdfs://master1:9000/d1/file");
		FileStatus status = fs.getFileStatus(path);
		System.out.println(status.toString());
	}

运行结果

FileStatus{path=hdfs://master1:9000/d1/file; isDirectory=false; length=15; replication=3; blocksize=134217728; modification_time=1555219406730; access_time=1555510861507; owner=jinge; group=supergroup; permission=rw-r--r--; isSymlink=false}

获取单一属性方法:

Hadoop:文件操作之Java接口(FileSystem类)_第1张图片

查看块信息

FileSystem下有获取所有块位置信息的方法:

public BlockLocation[] getFileBlockLocations(FileStatus file, long start, long len)
	@Test
	public void testLocation() throws IOException {
		Configuration conf = new Configuration();
		FileSystem fs = FileSystem.get(conf);
		Path path = new Path("hdfs://master1:9000/d1/f1");
		FileStatus stat = fs.getFileStatus(path);
		//stat.getLen()为文件系统中块个数,f1仅有1块
		BlockLocation[] bls = fs.getFileBlockLocations(stat, 0, stat.getLen());
		for(BlockLocation b:bls) {
			//获取块所在主机列表
			String[] hosts = b.getHosts();
			for(String host:hosts) {
				System.out.println("host:"+host+" ");
			}
			//获取该块的名称(ip:port)列表
			String[] names = b.getNames();
			for(String name:names) {
				System.out.println("name:"+name+" ");
			}
			//获取块所在网络拓扑列表
			String[] topos = b.getTopologyPaths();
			for(String topo:topos) {
				System.out.println("topo:"+topo+" ");
			}
			//...
		}
	}

查看datanode信息

DistributedFileSystem类下有获取所有datanode节点的方法:

    public DatanodeInfo[] getDataNodeStats() throws IOException
	@Test
	public void testDatanodeInfo() throws IOException {
		Configuration conf = new Configuration();
		DistributedFileSystem fs = (DistributedFileSystem) FileSystem.get(conf);
		DatanodeInfo[] infos = fs.getDataNodeStats();
		for(DatanodeInfo info:infos) {
			System.out.println("BlockPoolUsed:"+info.getBlockPoolUsed());
			System.out.println("CacheCapacity:"+info.getCacheCapacity());
			System.out.println("DatanodeReport:"+info.getDatanodeReport());
			System.out.println("DatanodeUuid:"+info.getDatanodeUuid());
			System.out.println("HostName:"+info.getHostName());
			System.out.println("InfoPort:"+info.getInfoPort());
			System.out.println("Name:"+info.getName());
			//...
		}
	}

 

 

你可能感兴趣的:(Hadoop)