面试题答案
一键面试框架设计思路
- 区分文件类型:在项目初始化时,根据文件大小判断使用传统IO还是NIO。可以设定一个阈值(例如10MB),小于该阈值的文件视为小文件,大于该阈值的文件视为大文件。
- 小文件处理:
- 使用传统IO:传统IO在处理小文件时相对简单直观,开销较小。使用
FileInputStream
和FileOutputStream
进行文件的读写操作。 - 示例代码:
- 使用传统IO:传统IO在处理小文件时相对简单直观,开销较小。使用
fun readSmallFile(filePath: String): String {
val file = File(filePath)
return file.readText()
}
fun writeSmallFile(filePath: String, content: String) {
val file = File(filePath)
file.writeText(content)
}
- 大文件处理:
- 使用NIO:NIO的
ByteBuffer
和FileChannel
提供了更高效的大文件处理能力,支持内存映射等特性。 - 示例代码:
- 使用NIO:NIO的
fun readLargeFile(filePath: String): String {
val file = File(filePath)
val channel = FileInputStream(file).channel
val buffer = ByteBuffer.allocate(8192)
val output = StringBuilder()
while (channel.read(buffer) != -1) {
buffer.flip()
output.append(Charsets.UTF_8.decode(buffer).toString())
buffer.clear()
}
channel.close()
return output.toString()
}
fun writeLargeFile(filePath: String, content: String) {
val file = File(filePath)
val channel = FileOutputStream(file).channel
val buffer = Charsets.UTF_8.encode(content)
while (buffer.hasRemaining()) {
channel.write(buffer)
}
channel.close()
}
- 统一接口封装:为了方便调用,将小文件和大文件的读写操作封装在统一的接口中。
interface FileOperator {
fun readFile(filePath: String): String
fun writeFile(filePath: String, content: String)
}
class DefaultFileOperator(private val threshold: Long = 10 * 1024 * 1024) : FileOperator {
override fun readFile(filePath: String): String {
val file = File(filePath)
return if (file.length() <= threshold) {
readSmallFile(filePath)
} else {
readLargeFile(filePath)
}
}
override fun writeFile(filePath: String, content: String) {
val file = File(filePath)
if (file.length() <= threshold) {
writeSmallFile(filePath, content)
} else {
writeLargeFile(filePath, content)
}
}
}
性能调优关键点
- 缓冲区大小:对于NIO操作,缓冲区大小设置很关键。过小的缓冲区会导致频繁的读写操作,过大的缓冲区会浪费内存。一般可以根据系统内存和文件读写模式进行调整,8192字节是一个常见的推荐值。
- 内存映射:对于大文件读取,可以使用
FileChannel.map
方法将文件映射到内存,这样可以减少I/O操作次数,提高读取性能。示例代码如下:
fun readLargeFileByMap(filePath: String): String {
val file = File(filePath)
val channel = FileInputStream(file).channel
val mappedBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, file.length())
val result = Charsets.UTF_8.decode(mappedBuffer).toString()
channel.close()
return result
}
- 异步操作:对于频繁的小文件读写,可以考虑使用异步操作来提高整体性能。例如,使用Kotlin的协程配合
Dispatchers.IO
来实现异步读写。
import kotlinx.coroutines.*
fun readSmallFileAsync(filePath: String): Deferred<String> = GlobalScope.async(Dispatchers.IO) {
val file = File(filePath)
file.readText()
}
fun writeSmallFileAsync(filePath: String, content: String): Deferred<Unit> = GlobalScope.async(Dispatchers.IO) {
val file = File(filePath)
file.writeText(content)
}
- 文件缓存:对于经常读取的小文件,可以考虑使用缓存机制,避免重复读取磁盘。例如,使用
LruCache
来实现简单的文件内容缓存。
import java.util.concurrent.ConcurrentHashMap
class FileCache(private val maxSize: Int) {
private val cache = ConcurrentHashMap<String, String>()
private var currentSize = 0
fun get(filePath: String): String? {
return cache[filePath]
}
fun put(filePath: String, content: String) {
if (currentSize >= maxSize) {
val leastRecentlyUsed = cache.keys.first()
cache.remove(leastRecentlyUsed)
currentSize--
}
cache[filePath] = content
currentSize++
}
}
在readSmallFile
方法中可以先检查缓存,有则直接返回,无则读取文件并放入缓存。
val fileCache = FileCache(100)
fun readSmallFile(filePath: String): String {
val cached = fileCache.get(filePath)
if (cached != null) {
return cached
}
val file = File(filePath)
val content = file.readText()
fileCache.put(filePath, content)
return content
}