Strings, IO and working with files

Handle Unicode text correctly, build strings efficiently, and read and write files with the standard library and CSV.jl.

Strings and Unicode

s = "Zürich"
length(s)                # 6 characters
sizeof(s)                # 7 bytes: ü is two bytes in UTF-8
ncodeunits(s)

s[1]                     # 'Z'
# s[2]                   # ERROR: StringIndexError, byte 2 is inside a character
collect(s)               # characters as a vector
eachindex(s)             # the valid byte indices
nextind(s, 1)            # the next valid index after a position

# iteration is by character, indexing is by byte: iterate, do not index
for c in "café"
    print(c, " ")
end
FunctionDoes
split(s, ",")Split into a vector of substrings
join(v, ",")Concatenate with a separator
replace(s, "a" => "b")Substitution, once or all
occursin("x", s)Substring test
startswith / endswithPrefix and suffix tests
strip(s)Trim whitespace, with an optional character set
uppercase / lowercaseUnicode-aware case conversion
lpad / rpadPad to a width

Build strings with interpolation or join, never by concatenating in a loop. A String is immutable, so each concatenation allocates a new one and turns a linear task into a quadratic one.

Files and buffers

write("out.txt", "hello\n")            # create or overwrite
content = read("out.txt", String)

open("append.txt", "a") do io
    write(io, "more text\n")
end

for line in eachline("out.txt")        # streaming, does not load the file
    println(strip(line))
end

buf = IOBuffer()
print(buf, "value = ", 42)
String(take!(buf))                     # consume the buffer

using Printf
@printf("%-10s %8.2f\n", "total", 1234.5)
@sprintf("%05d", 42)
  • eachline streams and keeps memory flat; read(path, String) loads everything, which is wrong for a large log.
  • Always prefer the do form of open: it closes the handle even when the body throws.
  • take!(buf) empties the buffer as it returns the contents, so call it once and keep the result.

CSV and downloads

using CSV, DataFrames, Downloads

df = CSV.read("data.csv", DataFrame)
CSV.write("out.csv", df)

# types, missing values and delimiters are explicit
df2 = CSV.read("export.psv", DataFrame;
               delim = '|', missingstring = "NA", types = Dict(:amount => Float64))

url = "https://example.com/data.csv"
Downloads.download(url, "download.csv")
⚠️
If a column has any missing, its element type becomes a union such as Union{Missing, Float64}, which is slower and propagates through arithmetic. Decide early whether missing rows are dropped, filled or kept, and use disallowmissing only after proving the column is complete.

FAQ

Why is s[2] an error for a string containing an accented character?
String indexing is by byte, and the second byte of a two-byte character is not a valid index. Iterate with for c in s, or use collect(s) when you need indexable characters.
How do I print with a specific number of decimal places?
Use Printf.@printf or @sprintf for C-style format strings. round(x; digits = 2) changes a numeric value; @sprintf formats it without changing it.

DataFrames, CSV and tabular data in Julia Modules and organising a Julia project

Last refreshed 2026-09-18.