英文:
Create iterative JSON directory tree - Golang
问题
我正在尝试将我以递归方式用Go语言编写的程序创建成迭代版本,但遇到了困难。目标是接收一个目录路径,并返回一个包含该目录中文件信息并保留目录结构的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) //从根文件开始
stack = append(stack, rootFile) //将根文件添加到栈中
for len(stack) > 0 { //直到栈为空
file = stack[len(stack)-1] //从栈中弹出一个条目
stack = stack[:len(stack)-1]
children, _ := ioutil.ReadDir(file.Path) //获取该条目的子项
for i := 0; i < len(children); i++ { //对于每个子项
child = (toFile(children[i], path+"/"+children[i].Name())) //将其转换为File对象
file.Children = append(file.Children, child) //将其添加到当前弹出的文件的子项中
stack = append(stack, child) //将子项添加到栈中,以便可以再次运行相同的过程
}
}
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
}
理论上,当我们通过栈移动时,子文件应该被追加到根文件中。然而,返回的只有根文件(没有任何子文件被追加)。你有任何想法为什么会发生这种情况吗?
英文:
I'm having trouble creating an iterative version of a program I wrote recursively in GoLang. The goal is to take a directory path and return a JSON tree that contains file information from that directory and preserves the directory structure. Here is what I have so far:
I've created a File struct that will contain the information of each entry in the directory tree:
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"`
}
In my iterative program, I create a stack to simulate the recursive calls.
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
}
Theoretically, the child files should be appended to the root file as we move through the stack. However, the only thing that is returned is the root file (without any children appended). Any idea as to why this is happening?
答案1
得分: 6
主要问题是结构体不像切片或映射那样是描述符值,也就是说,如果将结构体值赋给变量,它将被复制。如果将结构体值赋给切片或数组的元素,切片将被复制。它们不会链接起来!
因此,当你将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()
会处理这些问题,因此你不必担心。
不要在函数的开头声明所有局部变量,只有在需要它们的时候才声明,并且在最内部需要它们的块中声明。这将确保你不会意外地赋值给错误的变量,并且你会知道它在最内部的块之外不会被修改(因为在它之外它不在作用域内)- 这有助于更容易理解你的代码。你还可以使用短变量声明。
利用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)
}
还要利用零值,例如如果一个文件不是链接,你不需要设置IsLink
和LinksTo
字段,因为零值是false
和""
,这正是你最终得到的值。
虽然在这里可能不重要,但始终处理错误,至少打印或记录错误,这样如果某些东西不符合你的预期,你就不会浪费时间查找问题所在(你将不断在代码中搜索错误,并且几个小时后,你终于添加了打印错误的代码,然后发现错误不在你的代码中,而是在其他地方)。
使用指针和上述提到的提示的工作变体
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) {
rootOSFile, _ := os.Stat(path)
rootFile := toFile(rootOSFile, path) //start with root file
stack := []*File{rootFile}
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 _, chld := range children { //for each child
child := toFile(chld, filepath.Join(file.Path, chld.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
}
}
output, _ := json.MarshalIndent(rootFile, "", " ")
fmt.Println(string(output))
}
func toFile(file os.FileInfo, path string) *File {
JSONFile := File{ModifiedTime: file.ModTime(),
IsDir: file.IsDir(),
Size: file.Size(),
Name: file.Name(),
Path: path,
Children: []*File{},
}
if file.Mode()&os.ModeSymlink == os.ModeSymlink {
JSONFile.IsLink = true
JSONFile.LinksTo, _ = filepath.EvalSymlinks(filepath.Join(path, file.Name()))
} // Else case is the zero values of the fields
return &JSONFile
}
英文:
The main problem is that structs are not descriptor values like slices or maps, that is if you assign a struct value to a variable, it will be copied. If you assign a struct value to an element of a slice or array, the slice will be copied. They will not be linked!
So when you add your rootFile
to stack
, and then you pop an element from the stack
(which will be equal to rootFile
) and you modify the popped element, you will not observe the changes in your local variable rootFile
.
Solution is simple: use pointers to structs.
You also have a mistake in your code:
child = (toFile(children[i], path+"/"+children[i].Name())) //turn it into a File object
It should be:
child = (toFile(children[i], file.Path+"/"+children[i].Name())) // ...
Tips to improve your code:
I would rather use path.Join()
or filepath.Join()
to join path elements:
child = toFile(children[i], filepath.Join(file.Path, children[i].Name()))
Your code might not even work if the initial path ends with a slash or backslash and you explicitly concatenate it with another slash. Join()
will take care of these so you don't have to.
Don't declare all local variables ahead in the beginning of your function, only when you need them, and in the most inner block you need them. This will ensure you don't accidentally assign to the wrong variable, and you will know it is not modified outside of the innermost block (because outside of it it is not in scope) - this helps understanding your code much easier. You may also use short variable declaration.
Make use of the for ... range
construct, much cleaner. For example:
for _, chld := range children {
child := toFile(chld, filepath.Join(file.Path, chld.Name()))
file.Children = append(file.Children, child)
stack = append(stack, child)
}
Also make use of zero values, for example if a file is not a link, you don't need to set the IsLink
and LinksTo
fields as the zero values are false
and ""
which is what you would end up with.
And although it may not be important here, but always handle errors, print or log them as a minimum so you won't end up wasting time figuring out what is wrong if something is not what you expect (you will end up searching bugs in your code, and hours later you finally add print errors and see the bug wasn't in your code but somewhere else).
Working variant using pointers and tips mentioned above
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) {
rootOSFile, _ := os.Stat(path)
rootFile := toFile(rootOSFile, path) //start with root file
stack := []*File{rootFile}
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 _, chld := range children { //for each child
child := toFile(chld, filepath.Join(file.Path, chld.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
}
}
output, _ := json.MarshalIndent(rootFile, "", " ")
fmt.Println(string(output))
}
func toFile(file os.FileInfo, path string) *File {
JSONFile := File{ModifiedTime: file.ModTime(),
IsDir: file.IsDir(),
Size: file.Size(),
Name: file.Name(),
Path: path,
Children: []*File{},
}
if file.Mode()&os.ModeSymlink == os.ModeSymlink {
JSONFile.IsLink = true
JSONFile.LinksTo, _ = filepath.EvalSymlinks(filepath.Join(path, file.Name()))
} // Else case is the zero values of the fields
return &JSONFile
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论