面试题答案
一键面试可能出现问题的场景
在 os.Open
调用成功,但 io.Copy
发生错误时,文件虽然打开了,但 content.String()
返回空字符串,而 defer file.Close()
会在函数结束时关闭文件,这在大多数情况下没问题。然而,如果在 io.Copy
出错后,函数执行过程中发生了 panic
,defer
语句虽然会执行,但在程序异常退出时,可能会存在资源释放不及时的情况,尤其在高并发场景下,可能导致文件描述符耗尽等问题。
改进方案一:使用 context
- 代码修改:
func readFileContent(ctx context.Context, filePath string) string {
file, err := os.Open(filePath)
if err != nil {
return ""
}
defer file.Close()
var content bytes.Buffer
ctx, cancel := context.WithCancel(ctx)
go func() {
defer cancel()
_, err = io.Copy(&content, file)
if err != nil {
cancel()
}
}()
select {
case <-ctx.Done():
if ctx.Err() != nil {
return ""
}
}
return content.String()
}
- 优点:
- 可以更优雅地处理在操作过程中的取消操作,无论是正常的错误还是
panic
导致的异常情况,都能通过context
及时取消相关操作,减少资源占用时间。 - 在高并发场景下,能够有效管理资源,避免资源长时间占用。
- 可以更优雅地处理在操作过程中的取消操作,无论是正常的错误还是
- 缺点:
- 代码复杂度增加,需要引入
context
相关概念和操作,对于简单场景有点“杀鸡用牛刀”。 - 增加了额外的 goroutine 开销,虽然在大多数情况下开销较小,但在对性能要求极高的场景下可能需要考虑。
- 代码复杂度增加,需要引入
改进方案二:手动处理错误并提前关闭文件
- 代码修改:
func readFileContent(filePath string) string {
file, err := os.Open(filePath)
if err != nil {
return ""
}
var content bytes.Buffer
_, err = io.Copy(&content, file)
if err != nil {
file.Close()
return ""
}
err = file.Close()
if err != nil {
return ""
}
return content.String()
}
- 优点:
- 代码相对简单,直接在错误发生时手动关闭文件,避免了
defer
在panic
情况下可能出现的资源释放问题。 - 不需要引入新的概念或额外的 goroutine,对性能影响小。
- 代码相对简单,直接在错误发生时手动关闭文件,避免了
- 缺点:
- 代码重复度增加,每个可能出错的地方都需要手动关闭文件,违背了 DRY(Don't Repeat Yourself)原则。
- 可能因为疏忽忘记在某些错误处理分支关闭文件,相比
defer
的自动机制,可靠性略低。