我在创建一个我在GoLang中递归编写的程序的迭代版本时遇到了麻烦。目标是获取目录路径并返回包含该目录中的文件信息的JSON树,并保留目录结构。以下是我到目前为止的情况:
我创建了一个File结构,它将包含目录树中每个条目的信息:
type File struct {
ModifiedTime time.Time `json:"ModifiedTime"`
IsLink bool `json:"IsLink"`
IsDir bool `json:"IsDir"`
LinksTo string `json:"LinksTo"`
Size int64 `json:"Size"`
Name string `json:"Name"`
Path string `json:"Path"`
Children []File `json:"Children"`
}
在我的迭代程序中,我创建了一个堆栈来模拟递归调用。
func iterateJSON(path string) {
var stack []File
var child File
var file File
rootOSFile, _ := os.Stat(path)
rootFile := toFile(rootOSFile, path) //start with root file
stack = append(stack, rootFile) //append root to stack
for len(stack) > 0 { //until stack is empty,
file = stack[len(stack)-1] //pop entry from stack
stack = stack[:len(stack)-1]
children, _ := ioutil.ReadDir(file.Path) //get the children of entry
for i := 0; i < len(children); i++ { //for each child
child = (toFile(children[i], path+"/"+children[i].Name())) //turn it into a File object
file.Children = append(file.Children, child) //append it to the children of the current file popped
stack = append(stack, child) //append the child to the stack, so the same process can be run again
}
}
rootFile.Children
output, _ := json.MarshalIndent(rootFile, "", " ")
fmt.Println(string(output))
}
func toFile(file os.FileInfo, path string) File {
var isLink bool
var linksTo string
if file.Mode()&os.ModeSymlink == os.ModeSymlink {
isLink = true
linksTo, _ = filepath.EvalSymlinks(path + "/" + file.Name())
} else {
isLink = false
linksTo = ""
}
JSONFile := File{ModifiedTime: file.ModTime(),
IsDir: file.IsDir(),
IsLink: isLink,
LinksTo: linksTo,
Size: file.Size(),
Name: file.Name(),
Path: path,
Children: []File{}}
return JSONFile
}
理论上,当我们在堆栈中移动时,子文件应该附加到根文件。但是,唯一返回的是根文件(不附加任何子项)。知道为什么会这样吗?
答案 0 :(得分:3)
主要问题是结构体不是像切片或贴图这样的描述符值,也就是说,如果将一个struct值赋给变量,它将被复制。如果将struct值分配给切片或数组的元素,则将复制切片。他们不会联系!
因此,当您将rootFile
添加到stack
时,然后弹出stack
中的元素(等于rootFile
)并修改弹出的元素元素,您将不会观察到局部变量rootFile
中的更改。
解决方案很简单:使用指向结构的指针。
您的代码中也有错误:
child = (toFile(children[i], path+"/"+children[i].Name())) //turn it into a File object
应该是:
child = (toFile(children[i], file.Path+"/"+children[i].Name())) // ...
我宁愿使用path.Join()
或filepath.Join()
来加入路径元素:
child = toFile(children[i], filepath.Join(file.Path, children[i].Name()))
如果初始路径以斜杠或反斜杠结尾,并且您明确地将其与另一个斜杠连接,则您的代码甚至可能无效。 Join()
会照顾这些,所以你不必这样做。
不要在函数开头声明所有局部变量,只有在需要它们时,才能在最内部的块中声明它们。这将确保您不会意外地分配给错误的变量,并且您将知道它不会在最里面的块之外进行修改(因为它不在范围内) - 这有助于更容易地理解您的代码。您也可以使用short variable declaration。
使用for ... range
构造,更清洁。例如:
for _, chld := range children {
child := toFile(chld, filepath.Join(file.Path, chld.Name()))
file.Children = append(file.Children, child)
stack = append(stack, child)
}
还可以使用zero values,例如,如果文件不是链接,则无需设置IsLink
和LinksTo
字段,因为零值为{{ 1}}和false
这就是你最终会得到的结果。
尽管在这里可能并不重要,但总是处理错误,打印或记录它们至少这样你就不会浪费时间去弄清楚如果不符合预期会出现什么问题(你最终会在你的代码中搜索bug,几小时后你最终添加了打印错误,看到错误不在你的代码中,而是在其他地方)。
""