IO write/read <strlen> <string>

With the suggestions below:

# write number as type T
function writenum(io, num, T)
    write(io, T(num))
end
# read n numbers of type T
function readnum(io, T, n)
    s = sizeof(T)
    f = zeros(UInt8, s*n)
    readbytes!(io, f, s*n)
    return reinterpret(T, f)
end
# read single number of type T
function readnum(io, T)
    s = sizeof(T)
    f = zeros(UInt8, s)
    readbytes!(io, f, s)
    return reinterpret(T, f)[1]
end
# write string
function writestr(io, str)
    write(io, codeunits(str))
end
# read string of length n
function readstr(io, n)
    return String(read(io, n))
end

# Test

io = open("io.bin", "w")
snd = "abc"
nsnd = length(snd)
writenum(io, nsnd, UInt8)
writestr(io, snd)
close(io)

io = open("io.bin", "r")
nrcv = readnum(io, UInt8)
rcv = readstr(io, nrcv)
close(io)

(nrcv == nsnd) && (snd == rcv)


This post has lots of information: Introducing Julia/Working with text files - Wikibooks, open books for an open world

What are you trying to do exactly?

1 Like

Thx for staying with me!
I consulted this doc, it does read/write a whole file, a line, using delimiters, but not the case I am looking for, a single string.
The whole is for a serializer and inter-process communication. There are of course already proven packages. I use it as a learning vehicle (getting around…) and hope to come up with something faster, using a small set of homogeneous object types.

to read a single string with known length:

String(read(io, N))
2 Likes

Not sure what you mean by more elegant. Is the do syntax from the first help example more elegant for your use case?

 open(f::Function, args...; kwargs....)

  Apply the function f to the result of open(args...; kwargs...) and close the resulting file descriptor upon completion.

  Examples
  ≡≡≡≡≡≡≡≡≡≡

  julia> open("myfile.txt", "w") do io
             write(io, "Hello world!")
         end;
  
  julia> open(f->read(f, String), "myfile.txt")
  "Hello world!"
  
  julia> rm("myfile.txt")
2 Likes

By elegant, I meant unified, concise or built-in for reading a number or a string.
The elegance of your example is in opening and closing a file.

Nice! Now if we could do the same with

UInt8(read(io, 1))

but such a method does not exist. How to write it?

To read a String as a series of bytes, you can use transcode:

julia> open("io.bin") do io
           str = read(io, String)
           transcode(UInt8, str)
       end
3-element Base.CodeUnits{UInt8, String}:
 0x61
 0x62
 0x63
1 Like

read(io, UInt8)

?

4 Likes
julia> open("./test", "w") do io1
           write(io1, "length7")
           write(io1, [1,2,3])
       end
24

julia> open("./test") do io2
           @show String(read(io2, 7))
           @show reinterpret(Int64, read(io2))
       end
String(read(io2, 7)) = "length7"
reinterpret(Int64, read(io2)) = [1, 2, 3]
1 Like

@jling, thank you.
Would like to take my question back as cannot reproduce the problem any longer.
Not sure what went wrong while testing code below.

Test code: binary io
x = [Ď€, sqrt(2), 1.0,  0.0]
nx = length(x)
Y = x*x'
nY, mY = size(Y)
str = "flag nx nY mY x Y"
slen = length(str)

# BINARY WRITE:
filename = "x_Y_binary_out.dat"
open(filename,"w") do io
    write(io, 'F')           #Char Flag
    write(io, str)
    write(io, nx, nY, mY, x, Y)
end

# BINARY READ:
io = open(filename,"r")
flagchar = Char(read(io, UInt8))
str2 = String(read(io, slen))
nx2 = read(io,Int64)
nY2 = read(io,Int64)
mY2 = read(io,Int64)
x2 = Vector{Float64}(undef,nx2)
read!(io,x2)
Y2 = Array{Float64}(undef,(nY2,mY2))
read!(io,Y2)
close(io)

Interestingly, with the do form I learned that it creates a scope, making it less usable to get the information “out”.

function round_trip(data)
    open("io.bin", "w") do io
        serialize(io, data)
    end

    io = open("io.bin", "r")
    data2 = deserialize(io)
    println("data2=$data2")

    open("io.bin", "r") do io
        data3 = deserialize(io)
        println("data3=$data3")
    end
    println("data3=$data3")
end

data = ("Ab", [pi, 2.0])
round_trip(data)
data2=("Ab", [3.141592653589793, 2.0])
data3=("Ab", [3.141592653589793, 2.0])
ERROR: LoadError: UndefVarError: data3 not defined

Indeed, it’s a new scope, but you can do

data3 = open("io.bin", "r") do io
    deserialize(io)
end

Additionally, do is a just a way of passing a function as the first argument to another function. So we can equivalently do:

data3 = open(io -> deserialize(io), "io.bin", "r")

And then simplify to

data3 = open(deserialize, "io.bin", "r")
4 Likes

For a novice like me, pure magic - thx a lot for these lines!
Can you recommend a book or other text to get familiar with such things?

The manual states

do
Create an anonymous function and pass it as the first argument to a function call. For example:

map(1:10) do x
    2x
end
is equivalent to map(x->2x, 1:10).

OK for the do now.
The second and third form are due to (specific I guess) Julia syntax for open():

You can call:

julia> open(read_and_capitalize, "hello.txt")
"HELLO AGAIN."

to open hello.txt, call read_and_capitalize on it, close hello.txt and return the capitalized contents.

RTFM.

Personally, I am not familiar with any of the julia books out there. I suggest reading the julia manual though. In particular, read the “Manual” section of the documentation (potentially excluding sections that seems irrelevant at the moment – e.g. asynchronous programming, if that isn’t important to you). Then also read the “Documentation” section. Everything up through Iteration Utilities. Beyond that, you might also need to look through sections of Standard Library documentation, as necessary. Going through all of this will expose you to a lot of aspects of julia you aren’t yet familiar with, and help explain ones you’re not yet clear about.

2 Likes

Thanks for the reading recommendations.

1 Like