vol
vol

Reputation: 1914

How to read a block of data from file (instead of a whole file) in Swift

Let's say I've got a file that is 8 bytes long containing only ASCII characters: brownfox.

Instead of loading the whole file and working on if, I wan't to load block of 2 bytes [UInt8] and do operation on 2-bytes sized blocks, so the operations goes as follow:

  1. load br from file (and not the whole file)
  2. do any actions on the data, for example reverse to rb
  3. save the output to another file
  4. repeat for: ow nf ox

Reasoning behind this: This way if I handle a file that is 1GB of text I don't have to actualy have 1GB of RAM free (or 2GB for input and output file).

This method for files handling is important to me for encryption and sending to cloud solutions.

I'm using this extension:

extension Data {
    /**
     Consumes the specified input stream, creating a new Data object
     with its content.
     - Parameter reading: The input stream to read data from.
     - Note: Closes the specified stream.
     */
    init(reading input: InputStream) {
        self.init()
        input.open()

        let bufferSize = 1024
        let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
        while input.hasBytesAvailable {
            let read = input.read(buffer, maxLength: bufferSize)
            self.append(buffer, count: read)
        }
        buffer.deallocate()

        input.close()
    }

    /**
     Consumes the specified input stream for up to `byteCount` bytes,
     creating a new Data object with its content.
     - Parameter reading: The input stream to read data from.
     - Parameter byteCount: The maximum number of bytes to read from `reading`.
     - Note: Does _not_ close the specified stream.
     */
    init(reading input: InputStream, for byteCount: Int) {
        self.init()
        input.open()

        let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: byteCount)
        let read = input.read(buffer, maxLength: byteCount)
        self.append(buffer, count: read)
        buffer.deallocate()
    }
}

But init(reading input: InputStream, for byteCount: Int) always goes from 1st byte. How can I read for example from 16th to 20th byte?

Documentation on InputStream.read(_:maxLength:)

From the current read index, take up to the number of bytes specified in the second parameter from the stream and place them in the client-supplied buffer (first parameter). The buffer must be of the size specified by the second parameter. Return the actual number of bytes placed in the buffer; if there is nothing left in the stream, return 0. Reset the index into the stream for the next read operation.

What can I do to not reset the index, and get the next operation from the place where the previous ended?

Upvotes: 7

Views: 5259

Answers (2)

vol
vol

Reputation: 1914

rmaddy's solution worked!

Here's a very rough snippet for anybody that will came here with the same problem. It's not an exact answer, but it shows everything that needs to be done :)

    func loadInBlocks(path: String) -> [Data] {
        var blocks = [Data]()
        let correctPath = //path to file
        let fileHandle = FileHandle(forReadingAtPath: correctPath)

        let dataFromFirstByteTo4thByte = fileHandle!.readData(ofLength: 4)
        blocks.append(dataFromFirstByteTo4thByte)
        fileHandle?.seek(toFileOffset: 4)
        let dataFrom5thByteTo8thByte = fileHandle!.readData(ofLength: 4)
        blocks.append(dataFrom5thByteTo8thByte)
        fileHandle?.closeFile()

        return blocks
    }

and the actual usage:

    func loadBlock(number: Int, withBlockSize size: Int, path: String) throws -> Data {
        let correctPath = path.replacingOccurrences(of: "file://", with: "").replacingOccurrences(of: "%20", with: " ")

        guard let fileHandle = FileHandle(forReadingAtPath: correctPath) else { throw NSError() }

        let bytesOffset = UInt64((number-1) * size)
        fileHandle.seek(toFileOffset: bytesOffset)
        let data = fileHandle.readData(ofLength: size)
        fileHandle.closeFile()
        return data
    }

Upvotes: 4

rmaddy
rmaddy

Reputation: 318954

Use FileHandle. You can open the file handle for reading. Then use seek(toFileOffset:) to set where you wish to read from. Then use readData(ofLength:) to get some Data. Be sure to close the file handle when done.

Upvotes: 8

Related Questions