package com.jiucaiyuan.net.question;
import java.util.*;
/**
* 实现一个文件系统
* 支持文件和目录的增加、删除、重命名、移动
*
* @Author jiucaiyuan 2024/08/08 14:15
* @mail services@jiucaiyuan.net
*
*/
public class FileSystem {
public enum FileType{
FILE,DIRECTORY
}
/**
* 文件(文件,目录)
*/
public static class FileNode {
/**
* 文件名
*/
public String name;
/**
* 文件类型
*/
public FileType type;
/**
* 父目录
*/
public FileNode parent;
/**
* 当前为目录时所有子文件和目录
* 不支持重名
* 文件名或目录名
* 使用map结构,可以快速查看某个指定名称的文件或目录
*/
public Map<String,FileNode> childrenMap;
public FileNode(String fileName,FileType fileType) {
name = fileName;
type = fileType;
parent = null;
if (fileType == FileType.DIRECTORY){
childrenMap = new HashMap<>();
}
}
}
public static class File {
/**
* 文件系统的根目录
*/
private static FileNode root;
public File() {
root = new FileNode("/",FileType.DIRECTORY);
}
/**
* 树形输出指定目录下所有文件
* 暂时格式比较粗糙
*
* @param path
*/
public void tree(String path){
FileNode current = search(path,null);
if (current == null ){
return;
}
print(current,0);
}
/**
* 向文件中增加一个文件或目录
* @param path 目录,格式: "/"、"/User/src"
* @param fileName
* @param fileType
*/
public FileNode create(String path,String fileName,FileType fileType) {
if (fileName == null || fileType == null) {
return null;
}
FileNode exist = search(path,fileName);
if (exist != null){
throw new RuntimeException("文件已存在"+fileName);
}
FileNode parentFile = search(path,null);
if (parentFile == null ){
throw new RuntimeException("父目录不存在,不能创建该文件");
}
FileNode newFile = new FileNode(fileName,fileType);
newFile.parent = parentFile;
if (parentFile != null){
parentFile.childrenMap.put(newFile.name,newFile);
}
return newFile;
}
/**
* 删除某个文件或文件夹
*
* @param path 删除指定目录下文件或文件夹,如果为空,不允许
* @param fileName 待删除文件名或目录名
*/
public boolean delete(String path,String fileName) {
FileNode needDeleteFile = search(path,fileName);
if (needDeleteFile == null){
return false;
}
if (needDeleteFile.parent == null){
// 删除根目录下所有文件
root.childrenMap = new HashMap<>();
}else{
// 删除该文件或目录
needDeleteFile.parent.childrenMap.remove(fileName);
}
return true;
}
/**
* 搜索指定文件
*
* 第一版本:查找指定目录下的指定文件或目录(不含子目录下的同名文件或目录)
* 后期版本再实现指定目录下所有指定文件或目录
*
* @param path 搜索指定目录下文件或文件夹,如果为空,搜根目录下
* @param fileName 待搜索的文件,为空时,表示搜索目录,返回path对应的目录文件
* @return
*/
public FileNode search(String path,String fileName) {
if (path == null) {
path = "/";
}
String[] paths = convertPath(path);
if (fileName == null){
if (paths.length == 1){
return root;
}
fileName = paths[paths.length -1];
paths = Arrays.copyOfRange(paths, 0, paths.length - 1);
}
FileNode node = root;
if (node.name != paths[0]){
// 如果根目录不对,直接返回未找到
return null;
}else {
// 如果根目录一致,继续向下查找
FileNode result = search(node,paths,0,fileName);
return result;
}
}
/**
* 移动指定文件到目标目录
*
* @param fileName 待移动的文件或目录
* @param fromPath 从哪里移动
* @param toPath 移动到哪里
* @return
*/
public boolean move(String fileName,String fromPath,String toPath) {
if (fileName == null){
return true;
}
FileNode fromPathFile = search(fromPath,null);
if (fromPathFile == null){
throw new RuntimeException("未找到指定目录 "+fromPath);
}
FileNode file = search(fromPath,fileName);
if (file == null){
throw new RuntimeException("未找到指定目录下 "+fromPath+" 指定文件 "+fileName);
}
FileNode toDirectory = search(toPath,null);
if (toDirectory == null){
throw new RuntimeException("未找到指定目标目录 "+fileName);
}
toDirectory.childrenMap.put(file.name,file);
file.parent = toDirectory;
fromPathFile.childrenMap.remove(fileName);
return true;
}
/**
* 文件或目录改名
*
* @param path 所在目录
* @param oldName 原名称
* @param newName 新名称
* @return
*/
public void rename(String path, String oldName,String newName) {
if (oldName == null){
return;
}
FileNode oldFile = search(path,oldName);
oldFile.parent.childrenMap.remove(oldFile.name);
oldFile.name = newName;
oldFile.parent.childrenMap.put(newName,oldFile);
}
private FileNode search(FileNode startIndexFileNode,String[] paths,int startIndex,String fileName){
// 如果节点为空或目录为空,返回没找到
if (startIndexFileNode == null || paths == null){
return null;
}
// 如果当前已经是末级目录,则只在当前目录下找文件
if (startIndex == paths.length -1 ){
if (startIndexFileNode.type == FileType.FILE){
return null;
}
for (String key : startIndexFileNode.childrenMap.keySet()){
FileNode childFileNode = startIndexFileNode.childrenMap.get(key);
if ( childFileNode.name.equals(fileName)) {
return childFileNode;
}
}
return null;
}else
// 如果当前非末级目录,需要找下一级目录
if (startIndexFileNode.type == FileType.DIRECTORY && paths[startIndex].equals(startIndexFileNode.name)){
for (String key : startIndexFileNode.childrenMap.keySet()){
FileNode childFileNode = startIndexFileNode.childrenMap.get(key);
if (childFileNode.type == FileType.DIRECTORY ){
// 在下一级目录中继续找
FileNode dest = search(childFileNode,paths,startIndex +1,fileName);
if (dest != null) {
return dest;
}
}
}
}
return null;
}
private String[] convertPath(String path){
if (path == null) {
return new String[]{};
}
String old = path;
if (path.startsWith("/")){
path = path.substring(1);
}
if (path.endsWith("/")){
path = path.substring(0,path.length());
}
String[] paths = path.split("/");
List<String> pathList = new ArrayList<>();
pathList.add("/");
for (int i=0;i<paths.length;i++){
if (paths[i].equals("")){
continue;
}
pathList.add( paths[i]);
}
String[] result = pathList.toArray(new String[pathList.size()]);
return result;
}
private void print(FileNode node,int level){
if (node == null) {
System.out.println("");
}else{
if (level > 0) {
System.out.println(formatPrefix(level) + node.name);
}else {
System.out.println(".");
}
}
if (node.type == FileType.DIRECTORY ){
for (String key : node.childrenMap.keySet()){
FileNode value = node.childrenMap.get(key);
print(value,level + 1);
}
}
}
private String formatPrefix(int count){
if( count == 0){
}
StringBuffer sb = new StringBuffer();
for (int i = 0;i<count;i++){
if (i == count -1 ){
sb = sb.append("├─");
}else{
sb = sb.append(" ");
}
}
return sb.toString();
}
}
public static void main(String[] args){
FileSystem.File file = new FileSystem.File();
// 创建目录和文件
file.create(null,"readme.txt",FileType.FILE);
file.create("/","A",FileType.DIRECTORY);
file.create("/A","AB",FileType.DIRECTORY);
file.create("/A/AB","AC",FileType.DIRECTORY);
file.create("/A/AB/AC","AD",FileType.DIRECTORY);
file.create("/A/AB/AC/AD","AD.txt",FileType.DIRECTORY);
file.create("/A","AE",FileType.DIRECTORY);
file.create("/A","AF",FileType.DIRECTORY);
file.create("/A","AG",FileType.DIRECTORY);
file.create("/A","AG.txt",FileType.DIRECTORY);
file.create("/","B",FileType.DIRECTORY);
file.create("/B/","BB",FileType.DIRECTORY);
System.out.println("打印根目录");
file.tree("/");
// 删除目录
file.delete("/A/AB/AC","AD");
System.out.println("删除后打印根目录");
file.tree("/");
// 移动文件到指定目录下
file.move("readme.txt","/","/A/AB/AC");
System.out.println("移动后打印根目录");
file.tree("/");
// 移动目录到指定目录下
file.move("AB","/A","/B/BB");
System.out.println("移动后打印根目录");
file.tree("/");
// 查询指定目录下的文件
FileNode destFile = file.search("/B/BB/AC","readme.txt");
System.out.println("查找目录 /B/BB/AC 下的 readme.txt 文件,结果 file = " + destFile);
// 查询指定目录下的文件
FileNode destFile2 = file.search("/B/BB/AB/AC","readme.txt");
System.out.println("查找目录 /B/BB/AC 下的 readme.txt 文件,结果 file = " + destFile2);
}
}