基于Hadoop分布式存储的网盘系统实现(简易粗糙版)

这篇具有很好参考价值的文章主要介绍了基于Hadoop分布式存储的网盘系统实现(简易粗糙版)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

大家好,我是邵奈一,一个不务正业的程序猿、正儿八经的斜杠青年。
1、世人称我为:被代码耽误的诗人、没天赋的书法家、五音不全的歌手、专业跑龙套演员、不合格的运动员…
2、这几年,我整理了很多IT技术相关的教程给大家,爱生活、爱分享。
3、如果您觉得文章有用,请收藏,转发,评论,并关注我,谢谢!
博客导航跳转(请收藏):邵奈一的技术博客导航
| 公众号 | 微信 | CSDN | 掘金 | 51CTO | 简书 | 微博 |


0x00 教程内容

  1. 准备工作
  2. 编写代码
  3. 运行效果
  4. 实现说明

说明:本实现非常地粗糙,仅供参考。通过本教程,可以看到运行结果,并且实现跟 HDFS 交互的最基本的功能。

0x01 基于Hadoop分布式存储的网盘系统实现

1. 准备工作

(1)需要自行安装好 Hadoop
(2)启动好 Hadoop

2. 编写代码

注意:这里必须要引入Hadoop的依赖,可以参考教程:Java API实现HDFS的相关操作 新建一个Maven项目,并引入依赖。

(1)编写页面代码: MainFram

package com.bigdata.mapreduce.swing;
import com.bigdata.mapreduce.utils.HDFSUtil;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;

public class MainFram extends JFrame{

	private JTree tree=null;
	private JButton btnUpload = new JButton("上传");
	private JButton btnDownload = new JButton("下载");
	private JButton btnCreateDir = new JButton("创建文件夹");
	private JButton btndeleteDirOrFiles = new JButton("删除文件或者文件夹");
	private JButton btnRenameDirOrFiles = new JButton("重命名文件或者文件夹");
	private JButton btnMoveDir = new JButton("移动文件/文件夹");
	private JButton btnListDir = new JButton("列出文件");
	private DefaultMutableTreeNode root=new DefaultMutableTreeNode("我的网盘");
	private JPanel jp_center=new JPanel();

	// 初始化函数
	private void initTree() {
    	tree=new JTree(root);
    	tree.addTreeSelectionListener(new TreeSelectionListener() {
			@Override
			public void valueChanged(TreeSelectionEvent e) {
				tree_ValueChanged(e);
			}   		
    	});
    }

	private void tree_ValueChanged(TreeSelectionEvent e) {
//    	JOptionPane.showMessageDialog(this,
//    	tree.getSelectionPath().getLastPathComponent().toString());
		btnListDir_Clicked();
    }

    public MainFram() {

    	JPanel jp=(JPanel)this.getContentPane();

    	initTree();
    	JScrollPane jsp_tree=new JScrollPane(tree);

    	// 创建Panel
        JPanel jp_top=new JPanel();

        // 添加三个按钮
    	jp_top.add(btnUpload);
    	jp_top.add(btnDownload);
    	jp_top.add(btnCreateDir);
    	jp_top.add(btndeleteDirOrFiles);
    	jp_top.add(btnRenameDirOrFiles);
    	jp_top.add(btnMoveDir);
//    	jp_top.add(btnListDir);

    	JSplitPane splitPane_right=new JSplitPane(JSplitPane.VERTICAL_SPLIT,jp_top,jp_center);

    	splitPane_right.setDividerLocation(100);
    	splitPane_right.setDividerSize(1);
    	
    	JSplitPane splitPane=new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,jsp_tree,splitPane_right);
    	splitPane.setDividerLocation(300);
    	splitPane.setDividerSize(2);

    	jp.add(splitPane);

    	btnUpload.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				btnUpload_Clicked();
			}
    	});

    	btnDownload.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				btnDownload_Clicked();
			}
    	});

		btnCreateDir.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				btnCreateDir_Clicked();
			}
		});
		btnListDir.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				btnListDir_Clicked();
			}
		});
		btndeleteDirOrFiles.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				btndeleteDirOrFiles_Clicked();
			}
		});
		btnRenameDirOrFiles.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				btnRenameDirOrFiles_Clicked();
			}
		});
		btnMoveDir.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				btnMoveDir_Clicked();
			}
		});

    	this.setTitle("我的云盘");
		this.setSize(1200, 800);
		this.setVisible(true);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    private void btnDownload_Clicked() {
		System.out.println("download");
		String message = "请输入需要下载的文件,HDFS上的文件:\n";
		try {
			List<String> hdfsDir = HDFSUtil.listRemoteDirAndFiles("/");
			for (int k = 0; k < hdfsDir.size(); k++) {
				message += hdfsDir.get(k) + "\n";
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		String srcPath = JOptionPane.showInputDialog(null, message);
		System.out.println(srcPath);

		JFileChooser jf = new JFileChooser();
		jf.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		jf.setDialogTitle("请选择要上传的文件夹...");
		jf.showDialog(null, null);
		String destPath = jf.getSelectedFile().getAbsolutePath() + "/";
//		String[] name = jf.getSelectedFile().getAbsolutePath().split("/");
//		String destPath = "/user";

		if (destPath.isEmpty()) {
			System.out.println("请选择本地路径!");
		} else {
			try {
				HDFSUtil.downloadFromHDFS(srcPath, destPath);
			} catch (Exception e1) {
				e1.printStackTrace();
			}
		}
		System.out.println("下载成功!");
    }
    private void btnUpload_Clicked() {
		System.out.println("upload");
		JFileChooser jf = new JFileChooser();
		jf.setFileSelectionMode(JFileChooser.FILES_ONLY);
		jf.setDialogTitle("请选择要上传的文件...");
		jf.showDialog(null, null);
		String srcPath = jf.getSelectedFile().getAbsolutePath() + "/";
//		String destPath = "/";
		if (srcPath.isEmpty()) {
			System.out.println("本地文件路径不能为空!");
		} else {
			String destPath = JOptionPane.showInputDialog(null, "请输入需要上传到HDFS的路径:");
			if (!destPath.isEmpty()) {
				try {
					HDFSUtil.uploadToHDFS(srcPath, destPath);
				} catch (Exception e1) {
					e1.printStackTrace();
				}
			} else {
				System.out.println("HDFS路径不能为空!");
			}

		}
		System.out.println("上传成功!");
    }

	private void btnMoveDir_Clicked() {
		String message1 = "请输入需要移动的文件或者文件夹名:\n";
		String message2 = "请输入想要移动后的文件夹名:\n";
		try {
			List<String> hdfsDir = HDFSUtil.listRemoteDirAndFiles("/");
			for (int k = 0; k < hdfsDir.size(); k++) {
				message1 += hdfsDir.get(k) + "\n";
				message2 += hdfsDir.get(k) + "\n";
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		String oldName = JOptionPane.showInputDialog(null, message1);
		String newName = JOptionPane.showInputDialog(null, message2);

		System.out.println(oldName);
		if (oldName.isEmpty() || newName.isEmpty()) {
			System.out.println("参数错误,请重试!");
		} else {
			try {
				HDFSUtil.moveDirFromHDFS("/" + oldName, "/" + newName);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		System.out.println("移动文件/文件夹成功!");
	}

	private void btnRenameDirOrFiles_Clicked() {
		String message1 = "请输入需要重命名的文件或者文件夹名:\n";
		String message2 = "请输入想要重命后的文件或者文件夹名:\n";
		try {
			List<String> hdfsDir = HDFSUtil.listRemoteDirAndFiles("/");
			for (int k = 0; k < hdfsDir.size(); k++) {
				message1 += hdfsDir.get(k) + "\n";
				message2 += hdfsDir.get(k) + "\n";
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		String oldName = JOptionPane.showInputDialog(null, message1);
		String newName = JOptionPane.showInputDialog(null, message2);

		System.out.println(oldName);
		if (oldName.isEmpty() || newName.isEmpty()) {
			System.out.println("参数错误,请重试!");
		} else {
			try {
				HDFSUtil.renameFromHDFS("/" + oldName, "/" + newName);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		System.out.println("重命名文件/文件夹成功!");
	}

	private void btndeleteDirOrFiles_Clicked() {
		String message = "请输入需要删除的文件或者文件夹名:\n";
		try {
			List<String> hdfsDir = HDFSUtil.listRemoteDirAndFiles("/");
			for (int k = 0; k < hdfsDir.size(); k++) {
				message += hdfsDir.get(k) + "\n";
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		String deleteName = JOptionPane.showInputDialog(null, message);
		System.out.println(deleteName);
		if (deleteName.isEmpty()) {
			System.out.println("请输入文件或者文件夹名!");
		} else {
			try {
				HDFSUtil.deleteFromHDFS("/" + deleteName);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		System.out.println("删除文件/文件夹成功!");
	}

	private void btnListDir_Clicked() {
		int flag = 0;
		System.out.println("listDir");
		try {
			List<String> hdfsDir = HDFSUtil.listRemoteDirAndFiles("/");
//			List<String> hdfsDir = HDFSUtil.listRemoteDir("/");
			List<String> oneDirList = new ArrayList<>();
			DefaultMutableTreeNode oneDir = new DefaultMutableTreeNode();
			DefaultMutableTreeNode twoDir = new DefaultMutableTreeNode();
			for (int i = 0; i < hdfsDir.size(); i++) {
				System.out.println(hdfsDir.get(i));
				String[] arr = hdfsDir.get(i).split("/");
				int length = arr.length;
				// 目前只支持而级目录
				if (length == 2) { // length为2表示只有一个文件或者空文件夹,如:/user
					oneDir = new DefaultMutableTreeNode(arr[1]);
					root.add(oneDir);
				} else if (length == 3) { // length为3表示有两个文件或者两个空文件夹,如:/user/hello.txt
					// 如果一级目录里已经有这个目录,则不添加这个目录
					for (int j = 0; j < oneDirList.size(); j++) {
						if (oneDirList.get(j).equals(arr[1])) {
							flag = 1;
						}
					}
					if (flag == 0) {
						oneDir = new DefaultMutableTreeNode(arr[1]);
						twoDir = new DefaultMutableTreeNode(arr[2]);
						oneDirList.add(arr[1]);
						oneDir.add(twoDir);
						root.add(oneDir);
					} else if (flag == 1) {
						twoDir = new DefaultMutableTreeNode(arr[2]);
						oneDir.add(twoDir);
					}
				}
			}
			System.out.println("列出文件成功!");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void btnCreateDir_Clicked() {
		System.out.println("createDir");
		String remoteDirName = JOptionPane.showInputDialog(null, "请输入需要创建的文件夹名:");
		System.out.println(remoteDirName);
		if (remoteDirName.isEmpty()) {
			System.out.println("请输入文件名!");
		} else {
			try {
				HDFSUtil.createDirFromHDFS("/" + remoteDirName);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		System.out.println("新建文件夹成功!");
	}

	public static void main(String[] args) {
		JFrame.setDefaultLookAndFeelDecorated(true);
		new MainFram();
    }

}

(2)编写HDFS代码: HDFSUtil

package com.bigdata.mapreduce.utils;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.* ;
public class HDFSUtil {
	private static String hdfsURL="hdfs://localhost:9000";
	private static Configuration conf;
	static {
		conf = new Configuration();
		conf.set("fs.defaultFS", hdfsURL);
	}

	// 从HDFS上下载文件
	public static void downloadFromHDFS(String remoteFile,String localFile) throws Exception {
			FileSystem fs = FileSystem.get(conf);
			Path remotePath = new Path(remoteFile);
			Path localPath = new Path(localFile);
			fs.copyToLocalFile(remotePath, localPath);
			fs.close();
	}

	// 上传文件到HDFS
	public static void uploadToHDFS(String localfile,String remotefile) throws Exception {
		FileSystem fs = FileSystem.get(conf);
		Path remotePath = new Path(remotefile);
		Path localPath = new Path(localfile);
		fs.copyFromLocalFile(localPath, remotePath);
		fs.close();
	}

	// 创建HDFS文件夹
	public static void createDirFromHDFS(String remoteDir) throws Exception {
		FileSystem fs = FileSystem.get(conf);
		Path remotePath = new Path(remoteDir);
		//调用mkdirs函数创建目录
		fs.mkdirs(remotePath);
		fs.close();
	}

	// 删除文件和文件夹
	public static void deleteFromHDFS(String remoteDir) throws Exception {
		FileSystem fs = FileSystem.get(conf);
		Path remotePath = new Path(remoteDir);
		//调用mkdirs函数创建目录,true表示循环递归删除
		fs.delete(remotePath, true);
		fs.close();
	}

	// 重命名文件
	public static void renameFromHDFS(String oldName, String newName) throws Exception {
		FileSystem fs = FileSystem.get(conf);
		Path hdfsOldName = new Path(oldName);
		Path hdfsNewName = new Path(newName);
		fs.rename(hdfsOldName, hdfsNewName);
		fs.close();
	}

	// 查看文件夹
	public static void ListHDFSDir(String remoteDir) throws Exception {
		FileSystem fs = FileSystem.get(conf);
		Path dirPath = new Path(remoteDir);
		/*递归获取目录下的所有文件*/
		RemoteIterator<LocatedFileStatus> remoteIterator = fs.listFiles(dirPath, true);
		/*输出每个文件的信息*/
		while (remoteIterator.hasNext()) {
			FileStatus s = remoteIterator.next();
			System.out.println("路径: " + s.getPath().toString());
			System.out.println("权限: " + s.getPermission().toString());
			System.out.println("大小: " + s.getLen());
			/*返回的是时间戳,转化为时间日期格式*/
			Long timeStamp = s.getModificationTime();
			SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss");
			String date = format.format(timeStamp);
			System.out.println("时间: " + date);
			System.out.println();
		}
		fs.close();
	}

	// 列出文件
	public static List<String> listRemoteDirAndFiles(String remoteDir) throws Exception {
		List<String> remoteDirList = new ArrayList<>();
		FileSystem fs = FileSystem.get(conf);
		Path dirPath = new Path(remoteDir);
		/*递归获取目录下的所有文件*/
		RemoteIterator<LocatedFileStatus> remoteIterator = fs.listFiles(dirPath, true);
		/*输出每个文件的信息*/
		while (remoteIterator.hasNext()) {
			FileStatus s = remoteIterator.next();
			String myPath = s.getPath().toString().substring(21);
			remoteDirList.add(myPath);
		}
		fs.close();
		return remoteDirList;
	}

	// 列出文件夹
	public static List<String> listRemoteDir(String remoteDir) throws Exception {
		List<String> remoteDirList = new ArrayList<>();
		FileSystem fs = FileSystem.get(conf);
		Path dirPath = new Path(remoteDir);
		FileStatus[] fileStatus = fs.listStatus(dirPath);
		for (FileStatus file : fileStatus) {
			if (file.isDirectory()) {
				listRemoteDir(file.getPath().toString());
				remoteDirList.add(file.getPath().toString().substring(21));
			} else {
				remoteDirList.add(file.getPath().toString().substring(21));
			}
		}
		fs.close();
		return remoteDirList;
	}

	// 移动文件夹
	public static void moveDirFromHDFS(String oldPath, String newPath) throws Exception {
		FileSystem fs = FileSystem.get(conf);
		Path hdfsOldName = new Path(oldPath);
		Path hdfsNewName = new Path(newPath);
		fs.rename(hdfsOldName, hdfsNewName);
		fs.close();
	}

	// 测试
//	public static void main(String[] args) {
//		try {
			// 1、列出文件
//			List<String> list = HDFSUtil.listRemoteDirAndFiles("/");
//			for (int i = 0; i < list.size(); i++) {
//				System.out.println(list.get(i));
//			}

			// 2、重名文件
//			HDFSUtil.renameFromHDFS("/start-dfs.cmd", "/start-dfs.sh");

			// 3、创建文件夹
//			HDFSUtil.createDirFromHDFS("/hive");

			// 4、移动文件夹(把/路径下的hive文件夹,放到/user路径)
//			HDFSUtil.moveDirFromHDFS("/hive", "/user");

			// 5、删除文件或者文件夹
//			HDFSUtil.deleteFromHDFS("/user");
//			HDFSUtil.deleteFromHDFS("/user/hive/start-dfs.sh");


//		} catch (Exception e) {
//			e.printStackTrace();
//		}
//	}
}
3. 运行效果

很多的功能都已经实现,虽然不是特别地实用。

直接运行MainFram类就可以看到运行效果:
基于Hadoop分布式存储的网盘系统实现(简易粗糙版)

4. 实现说明

(1)如果需要修改HDFS的链接,可以修改代码的值:

private static String hdfsURL="hdfs://localhost:9000";

(2)目前目录树其实是还不规范的,只是站在零基础同学角度的一个实现方案,需要自行完善。

0xFF 总结

  1. 通过本教程,可以扩展大家的思维,其实我们学了HDFS的API操作,并不是一无是处的,我们其实是可以自己写一个简单的小工具来使用的。
  2. 关注本博主,学习更多有趣的知识。

邵奈一 原创不易,如转载请标明出处,教育是一生的事业。文章来源地址https://www.toymoban.com/news/detail-497163.html


到了这里,关于基于Hadoop分布式存储的网盘系统实现(简易粗糙版)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Hadoop是一个开源的分布式处理系统,主要用于处理和存储大量数据

    Hadoop是一个开源的分布式处理系统,主要用于处理和存储大量数据。它是由Apache软件基金会开发的,现在已经成为大数据领域中广泛使用的技术之一。 Hadoop架构 Hadoop的架构包括以下几个主要组件: Hadoop Distributed File System (HDFS) : HDFS是Hadoop的核心组件之一,它是一个分布式文

    2024年02月04日
    浏览(58)
  • 大数据--分布式存储 Hadoop

    Hadoop指Apache这款开源框架,它的核心组件有: HDFS(分布式文件系统):解决海量数据存储 MAPREDUCE(分布式运算编程框架):解决海量数据计算 YARN(作业调度和集群资源管理的框架):解决资源任务调度 目前主流的hadoop框架已经迭代更新到hadoop3.x的版本了,本篇的介绍也是

    2024年01月17日
    浏览(52)
  • 基于区块链的分布式存储系统开发论文研究

    论文引用:[1]蔡维德,郁莲,王荣,刘娜,邓恩艳.基于区块链的应用系统开发方法研究[J].软件学报,2017,28(06):1474-1487. 1. 区块链介绍   区块链是由多独立节点参与的分布式数据系统,也可以理解为分布式账簿(distributed ledger technologt,简称DLT),由这些节点共同维护,它的特点是

    2024年02月12日
    浏览(61)
  • 基于hadoop下的使用map reduce分布式系统的高考高频词汇统计

    hadoop 课程设计报告 一、设计目的与要求 1 、设计目的 通过hadoop课程设计可以加深、巩固对本门专业课程理论知识的掌握。通过eclipse和hadoop来编写课设报告等方面的实践训练,筑牢编程基础,培养良好的逻辑思维能力,提高综合运用能力。同时也锻炼学生自我管理和自我发展

    2024年02月07日
    浏览(53)
  • 基于Windows系统的Hadoop伪分布式模式部署-从零开始(我的学习记录)

    目录 前言 一.JDK的下载安装配置 1.JDK 下载 2.JDK 安装 3.JDK 环境变量配置 4.验证JDK安装是否成功 5.重点? 二.Hadoop部署以及工具集winutils 1.下载Hadoop解压/下载winutils以及\\\"安装\\\"         下载Hadoop和winutils         \\\"安装\\\"winutils 2.配置Hadoop环境变量/配置Hadoop文件 Hadoop配置环境变量

    2024年04月13日
    浏览(62)
  • 基于hadoop下的使用map reduce分布式系统的高考高频词汇统计(内有源码下载)

    hadoop 课程设计报告 一、设计目的与要求 1 、设计目的 通过hadoop课程设计可以加深、巩固对本门专业课程理论知识的掌握。通过eclipse和hadoop来编写课设报告等方面的实践训练,筑牢编程基础,培养良好的逻辑思维能力,提高综合运用能力。同时也锻炼学生自我管理和自我发展

    2024年02月11日
    浏览(35)
  • Hadoop完全分布式安装基于Docker

    (都在root用户下) 在Dockfile文件中添加以下内容 基于centos镜像,生成带有spenssh-server、openssh-clients的镜像,用户为root,密码为a123456,镜像维护者(作者)为hadoop 建好Dockerfile文件后,生成镜像,在终端输入: 1、在主机下载ssh 2、把hadoop和jdk传到/root 3、解压hadoop和jdk 4、生成带

    2024年04月29日
    浏览(51)
  • Hadoop分布式文件系统(三)

    目录 一、Hadoop 1、MapReduce 1.1、理解MapReduce思想 1.2、分布式计算概念 1.3、MapReduce介绍 1.4、MapReduce特点 1.5、MapReduce局限性 1.6、MapReduce实例进程 1.7、MapReduce阶段组成 1.8、MapReduce数据类型 1.9、MapReduce官方示例 1.9.1、示例说明--圆周率PI评估 1.9.2、官方示例--WordCount单词统计 1.10、

    2024年01月16日
    浏览(47)
  • 基于Linux的Hadoop伪分布式安装

    1.1 创建新用户(需注意权限问题:切换为root用户) 1.2 添加新用户hadoop,并设置相关信息(一直回车默认就可以) 1.3 退出当前用户登录hadoop用户(或直接在Ubuntu中切换用户即可) 1.4 以管理员身份(root用户)执行指令visudo,来修改配置 visudo打开的是 /etc/sudoers 文件,修改该

    2024年02月03日
    浏览(40)
  • 二、Hadoop分布式系统基础架构

            分布式体系中,会存在众多服务器,会造成混乱等情况。那如何让众多服务器一起工作,高效且不出现问题呢? 在大数据体系中,分布式的调度主要有2类架构模式: ~去中心化模式 ~中心化模式         没有明确的中心,众多服务器之间基于特定的规则进行同步

    2024年02月05日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包