如何将一个10G的文件逆序写入一个新的文件

最近的同事的组内分享的内容就是标题的内容,这是一道大数据处理的算法的经典面试题,以前也经常碰到,用到的是mmap的知识,了解过,但是没有自己实际去写过。趁这个机会,就再一次回顾下,用swift来实现一把。

参考文献

特权级, 用户态,内核态,用户空间,内核空间,线程上下文,中断上下文, MMU等概念请参考下文

linux一些概念

mmap分析

分析

由于指定数据的内容比较大,所以我们不能直接通过read,write的方式将大文件直接读入内容,因为这样内存会不够用,所以我们需要借助linux中的mmap的知识,进行内存映射

mmap工作原理

mmap的工作原理,当你发起这个调用的时候,它只是在你的虚拟空间中分配了一段空间,连真实的物理地址都不会分配的,当你访问这段空间,CPU陷入OS内核执行异常处理,然后异常处理会在这个时间分配物理内存,并用文件的内容填充这片内存,然后才返回你进程的上下文,这时你的程序才会感知到这片内存里有数据

mmap函数

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);

具体参数含义

start :  指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。

length:  代表将文件中多大的部分映射到内存。

prot  :  映射区域的保护方式。可以为以下几种方式的组合:
                PROT_EXEC 映射区域可被执行
                PROT_READ 映射区域可被读取
                PROT_WRITE 映射区域可被写入
                PROT_NONE 映射区域不能存取
flags :  影响映射区域的各种特性。在调用mmap()时必须要指定

MAP_SHARED 或MAP_PRIVATE。
                MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。

                MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。

                MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。

                MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。

                MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。

                MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。

fd    :  要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,
      然后对该文件进行映射,可以同样达到匿名内存映射的效果。

offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是PAGE_SIZE的整数倍。
复制代码

返回值: 若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

错误代码: EBADF 参数fd 不是有效的文件描述词 EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。 EINVAL 参数start、length 或offset有一个不合法。 EAGAIN 文件被锁住,或是有太多内存被锁住。 ENOMEM 内存不足。

用户层的调用很简单,其具体功能就是直接将物理内存直接映射到用户虚拟内存,使用户空间可以直接对物理空间操作。但是对于内核层而言,其具体实现比较复杂。

munmap函数

通过mmap映射出来的内存,通过munmap来解除映射关系

int munmap( void * addr, size_t len )

在进程地址空间中解除一个映射关系,当映射关系解除后,对原来映射地址的访问将导致段错误发生。

void * addr :调用mmap()时返回的地址
size_t len :映射区的大小
复制代码

msync函数

int msync ( void * addr , size_t len, int flags)

一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以调用msync()实现磁盘上文件与共享内存区的内容一致。

void * addr :调用mmap()时返回的地址
size_t len :映射区的大小
int flags :MS_ASYN: 异步写,MS_SYN : 同步写,MS_INVALIDAT : 无效的cache 数据。
复制代码

具体代码如下

 func handleFile() {
        
        let filePath = Bundle.main.path(forResource: "data", ofType: "txt")
        let filePathOut = Bundle.main.path(forResource: "dataout", ofType: ".txt")
        
        let fhIn = FileHandle.init(forReadingAtPath: filePath!)
        let fhOut = FileHandle.init(forWritingAtPath: filePathOut!)
        
        // 清空输出文件的内容
        fhOut?.truncateFile(atOffset: 0)
        
        // 获取文件大小
        let fileSize = fhIn?.seekToEndOfFile()
        
        // 由于要处理的文件较大,所以不能一次将文件全部映射进内存,采用一个合适的映射内存大小。
        // 通过count来执行映射次数
        let count = fileSize! / MEM_SIZE
        // 剩余部分内存
        let leftSize = fileSize! % MEM_SIZE
        
        
        // 两部分分开处理,剩余部分写入
        // 文件最后的,写入文件的最开始位置
        let leftPart = mmap(UnsafeMutableRawPointer.init(bitPattern: 0), Int(leftSize), PROT_READ, MAP_SHARED, fhIn!.fileDescriptor, off_t(MEM_SIZE * count))
        if leftPart == MAP_FAILED {
            print("剩余部分映射失败)")
            return
        }
        let leftBuf = malloc(Int(leftSize))
        memcpy(leftBuf, leftPart, Int(leftSize))
        var data = Data.init(bytes: leftBuf!, count: Int(leftSize))
        data.reverse()
        fhOut?.write(data)
        fhOut?.synchronizeFile()
        
        munmap(leftPart, Int(leftSize))
        free(leftBuf)
        
        print("剩余部分写入成功")
        
        
        // 多线程处理大小的内存,加快处理速度
        let queue = OperationQueue.init()
        queue.maxConcurrentOperationCount = 5 // 设置最大并发数,线程太多,因为线程切换,速度反而也降低

        // 使用信号量,防止资源写入的时候,多线程seek文件的问题
        let semaphore = DispatchSemaphore.init(value: 1)
        
        // 从第0到count段数据,分别写入文件的相应位置
        for i in 0..<count {
            
            queue.addOperation {
                let part = mmap(UnsafeMutableRawPointer.init(bitPattern: 0), Int(MEM_SIZE), PROT_READ, MAP_SHARED, fhIn!.fileDescriptor, off_t(i * MEM_SIZE))
                
                if part == MAP_FAILED {
                    print("映射失败 i = \(i)")
                    return
                }
                let buf = malloc(Int(MEM_SIZE))
                memcpy(buf, part, Int(MEM_SIZE))
                var data = Data.init(bytes: buf!, count: Int(MEM_SIZE))
                data.reverse()
                free(buf)
                
                semaphore.wait() // 抢占信号资源
                fhOut?.seek(toFileOffset: leftSize + MEM_SIZE * (count - i - 1))
                fhOut?.write(data)
                fhOut?.synchronizeFile()
                semaphore.signal() // 释放信号资源

                munmap(part, Int(MEM_SIZE))
                print("操作成功 i= \(i)")
            }
        }
        
        //等队列中所有操作结束,才能执行后面的close句柄的操作
        queue.waitUntilAllOperationsAreFinished()
        fhIn?.closeFile()
        fhOut?.closeFile()
    }
复制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值