Продвинутые операции с данными
Play ITЗагрузка интерактивного демо…
Перед чтением: Операторы — общие понятия оператора, операнда, приоритетов и типов операций; здесь те же идеи применительно к данным.
О разделе · Структуры данных (как хранить) → здесь (что делать с данными в памяти и при передаче).
Мы уже рассматривали базовые операции с данными — ввод/вывод, чтение и запись, кэширование, копирование, перемещение, вырезание, вставка, удаление, очистка, создание, изменение. Базовые операции работают комплексно, независимо от типа данных. Также мы изучили типы данных — булево значение, числа, строки, идентификаторы. Но что же можно делать с ними на практике?
Операции из этой статьи ежедневно встречаются в API, ETL-процессах, мобильных клиентах, обработке логов, интеграциях с внешними сервисами и базами данных. Чем лучше вы понимаете эти операции, тем легче диагностировать ошибки данных в продакшене.
| Тема в этой статье | Углубление |
|---|---|
| Биты, байты, слова | Представление информации - биты, байты, машинные слова |
| Адреса и указатели | Адресация данных в памяти |
| Маршалинг, JSON, protobuf | Маршалинг и анмаршалинг - сериализация объектов |
Операции с данными
Операции с данными - это действия, которые можно выполнять над информацией, и они могут быть атомарными (простыми) или составными (сложными). Собственно, как и сами данные - это может быть запись в поле как набор данных одного типа, так и целая структура в виде таблицы. Но давайте потихоньку - сначала с операциями.
Практическая модель обработки
Полезно держать в голове базовый конвейер:
- Получить данные (файл, сеть, очередь, форма, датчик).
- Проверить валидность и целостность.
- Преобразовать типы и структуру.
- Применить бизнес-правила.
- Сохранить или отправить дальше.
- Зафиксировать результат в логах и метриках.
Такой каркас делает обработку предсказуемой и удобной для отладки.
Категории операций с данными
Основные категории:
- CRUD-операции - создание (CREATE), чтение (READ), изменение (UPDATE), удаление (DELETE);
- Ввод/вывод (I/O) - чтение из файла, сети, датчика, запись на диск, в базу и т.д.;
- Копирование/перемещение - дублирование или транспортировка данных;
- Поиск и фильтрация - поиск по значению, условию, шаблону;
- Сортировка - упорядочивание данных;
- Агрегация - суммирование, подсчёт, усреднение и т.п.;
- Валидация - проверка корректности данных;
- Шифрование/дешифрование - защита данных;
- Сериализация/десериализация - преобразование объектов в поток байтов и обратно;
- Кэширование - временное хранение для ускорения доступа;
- Репликация/синхронизация - копирование между системами.
// Пример: заказ в интернет-магазине (смысл операций, не синтаксис языка)
АЛГОРИТМ ОбработатьЗаказ(заказ)
если не Валидировать(заказ) то
вернуть ошибка "некорректные данные"
конец
Создать(заказ в базе) // CREATE
прочитать := Прочитать(остаток_на_складе) // READ
если прочитать < заказ.количество то
Обновить(заказ, статус="отменён") // UPDATE
вернуть ошибка "нет на складе"
конец
Обновить(остаток, остаток - заказ.количество)
текст := УпаковатьВJSON(заказ) // сериализация / маршалинг
Отправить(складской_сервис, текст)
КОНЕЦ
В обыкновенной речи люди используют лишь термины "запись/чтение", "передача" и "обработка". То есть, заказчику, к примеру, не так важно, что будет с данными, он ставит задачу в стиле "система должна получить и обработать данные", подразумевая, что будет целый комплекс мероприятий по проверке, сериализации/десериализации, валидации, парсингу, преобразованиям, с дальнейшим распределением, конвертированием, записью и прочим хозяйством.
Давайте погрузимся - а что же можно делать с данными? Что за обработка?
Преобразование
Явное и неявное преобразование
Преобразование типов данных (Type Casting / Type Conversion) - это процесс смены типа данных одного значения на другой. К примеру, превращение строки "123" в число 123. Да, для системы это разные значения, и только динамически типизированные языки могут автоматически приводить один тип к другому.
ввод := "123" // строка с клавиатуры
если ввод — только цифры то
число := ПреобразоватьВЦелое(ввод) // явное преобразование
сумма := число + 10
иначе
вывести "введите число, не текст"
конец
Явное преобразование (explicit) подразумевает, что программист сам указывает преобразование:
int x = (int)3.14; /* 3 — дробная часть отбрасывается */
int x = (int) 3.14;
Неявное (implicit) подразумевает, что система делает это автоматически:
double d = 5; /* int → double */
# В Python 5 и 5.0 — разные объекты; float(5) — явное
x = 5.0
К примеру, есть int (целое) и float/double (дробное): 3 можно превратить в 3.0 и наоборот (с потерей дробной части при усечении к целому).
Другой пример — string и int: "123" → 123, но "abc" даст ошибку или NaN:
int("123") # 123
int("abc") # ValueError
Number("123") // 123
Number("abc") // NaN
Boolean в C/C++ в арифметике часто трактуется как 0/1; в Python True/False — отдельный тип.
Важно помнить о рисках: потеря точности (3.9 → int → 3), переполнение узкого типа, разный знак у % для отрицательных чисел в C и Python.
Парсинг
На практике это очень часто, к примеру, когда у нас, как у программистов, будут исходные данные в виде сплошного текста, и задача будет подготовить обработку этих данных, чтобы система распределяла их по структурам.
К примеру, из текста "Иван, 22 года, место рождения Москва", можно извлечь ценные данные - имя "Иван", возраст "22" и место рождения "Москва", и в коде придётся выполнять определенное чтение, преобразование и обработку.
Такая операция называется парсинг (parse, parsing) — автоматизированное извлечение и структурирование данных. Парсить можно текст, HTML, логи, ответы API. Цель — получить поля (имя, возраст, город), отбросить "шум" и привести типы.
Упрощённо: исходный текст → токены/части → структура (словарь, объект). Для жёсткого формата хватает split или regex; для JSON/SQL — полноценный парсер.
line = "Иван, 22 года, место рождения Москва"
# Хрупко, если формат изменится —
parts = line.split(", ")
# Надёжнее при известном шаблоне —
import re
m = re.match(r"^(.+), (\d+) года, место рождения (.+)$", line)
if m:
name, age, city = m.group(1), int(m.group(2)), m.group(3)
Готовые форматы (JSON, CSV, XML) парсят стандартными библиотеками — не изобретайте свой "мини-JSON" на split для внешних данных.
Операции с числами
Виды операций с числами
Числа, как мы понимаем, бывают целые (int, long), вещественные (float, double), комплексные (в математике), и большие числа (BigInteger, ОЧЕНЬ большие числа).
Соответственно, и операции с ними в первую очередь математические.
Виды операций с числами:
- Арифметические —
+,-,*,/,%(остаток),**(степень); - Сравнение —
==,!=,<,>,<=,>=; - Инкремент/декремент: ++, --;
- Побитовые (для целых) —
&,|,^,~,<<,>>; - Математические функции —
sqrt(),abs(),round(),floor(),ceil(),sin(),log(); - Генерация случайных чисел.
Если вы знаете математику хотя бы на базовом уровне, то это должно легко пониматься. Если же нет, то кое-что мы попробуем подтянуть в главе с мыслительной базой, так что всё ещё впереди.
+, сложение - бинарная операция, сопоставляющая двум числам a и b их суммуa+b.-, вычитание - операция, обратная сложению:a-b=a+(-b).*, умножение - бинарная операция, сопоставляющая числам a и b их произведениеa×b./, деление - операция, обратная умножению.%, остаток от деления, модуль - операция, возвращающая остаток при делении a на b.**, возведение в степень (Python; в C/Java —pow()/Math.pow), где a — основание, b — показатель.
:::note Нюансы по языкам
3 / 2в Python 3 даёт1.5, в C/Java при целых операндах —1.%при отрицательных числах: в Python знак у результата как у делителя, в C — как у делимого.++/--есть в C, C++, Java, JavaScript; в Python инкремента нет (x += 1). :::
Операции сравнения являются логическими, возвращающими всегда булево значение true либо false (истина либо ложь).
==(равно) это эквивалентность,a==bистинно, если они имеют одинаковое значение.!=(не равно), это отрицание равенства;<(меньше), строгое упорядочивание, левый оператор лежит левее на числовой прямой;>(больше), обратное строгое упорядочивание;<=(меньше или равно), нестрогое упорядочение;>=(больше или равно), аналогичное, но обратное.
Инкремент и декремент изменяют значение переменной на 1.
++(инкремент) всегда увеличивает значение переменной на 1;--(декремент) всегда уменьшает значение на 1.
Это нужно, к примеру, для счётчика, когда какую-то операцию мы провели, и нужно записать итерацию, что это первый раз (1), затем в следующий раз, что это второй (1+1=2, т.е. 1++), потом третий (2+1=3, т.е. 2++). Так и работают абсолютно все простейшие счетчики, используйте инкременты вместо +1/-1.

Итерацией называют каждый "проход" цикла. Если бы мы принудительно не меняли бы значение X на картинке выше, то получили бы бесконечный прирост или бесконечное уменьшение.

В данном случае в начале цикла у нас X. Допустим, мы передали его в цикл как "0", и цикл в первой своей итерации прибавит +1, и значение X станет 1. Поскольку операция будет повторяться, то во второй итерации значение X станет 2.
Побитовые операции (для целых чисел) работают на уровне двоичного представления чисел (битов). Подробнее про бит и байт — в Представление информации - биты, байты, машинные слова.
&(побитовое И, AND) - для каждого бита, если оба бита равны 1, иначе 0. То есть, когда нужно добавить еще одно условие или действие.|(побитовое ИЛИ, OR), если хотя бы один из битов равен 1.^(побитовое исключающее ИЛИ, XOR), 1 если биты различаются.~(побитовое НЕ, NOT), инвертирует каждый бит.<<(сдвиг влево), сдвиг всех битов числа a на n позиций влево.>>(сдвиг вправо), сдвиг вправо на n битов.
Эти операции используются в низкоуровневом программировании, криптографии, оптимизации.
Математика и генерация
Математические функции же определены на вещественных числах. Это квадратный корень, абсолютное значение, округление до ближайшего целого, округление вниз/вверх, синус, логарифм - это всё зависит от задач, и они, что логично, являются математическими вычислениями. Не всегда придётся писать простые приложения, порой нужна сложная математика, чтобы определять координаты местоположений, к примеру. Чтобы не вызвать инфаркт у гуманитариев (я таковым, кстати, являюсь), давайте остановимся с математикой.
Генерация случайных чисел - это процесс получения псевдослучайных или истинно случайных чисел. Псевдослучайные числа генерируются детерменированным алгоритмом, при правильном выборе параметров последовательность выглядит случайной. Это применяется для рандомизации, моделирования, криптографии, алгоритмов различного характера и конечно в играх. К примеру, в ролевых играх вроде Diablo формируется таблица с шансами и учитывается целая система, основанная на рандомизации.
По поводу генерации следует сказать забавную штуку. Система генерирует не полноценные случайности - программно всё-равно это статистически-математические функции, которые не являются чистыми случайностями философского уровня, поэтому математические случайности можно вычислить или расшифровать, и для реального шифрования иногда используют естественную "рандомизацию". Например, Cloudflare использует лава-лампы, маятники и распад урана для шифрования трафика, что дарует особый, уникальный алгоритм шифрования.
Операции с текстом (строками)
Основные операции с текстом
Текст - это последовательность символов, но в IT принято использовать термин "строка" (string). Здесь основные операции:
- Конкатенация:
"Hello" + "World"образует"HelloWorld"(объединение строк); - Обрезка (срез);
- Поиск подстроки;
- Замена;
- Разделение;
- Склейка;
- Преобразование регистра;
- Обрезка пробелов;
- Проверки;
- Форматирование.
Подстрока, подмножество и подпоследовательность
Подстрока — это последовательность символов, которая содержится внутри другой строки (называемой исходной строкой или суперстрокой) и состоит из одного или нескольких подряд идущих символов этой строки.
Для "Привет" подстроками будут:
- "п", "р", "и", "в", "е", "т" — подстроки длины 1
- "при", "рив", "иве", "вет" — подстроки длины 3
- "привет" — вся строка тоже является подстрокой
- "" — пустая строка (в некоторых определениях тоже считается подстрокой)
Подстрока сохраняет порядок символов и непрерывна.
Подпоследовательность — это последовательность символов, которые встречаются в строке в том же порядке, но не обязательно подряд.
В подпоследовательности порядок символов сохраняется, но символы могут быть пропущены между ними, и не обязательна непрерывность, в отличие от подстроки. К примеру, в строке "программирование", примерами последовательности будут:
"пгм" — символы 'п', 'г', 'м' идут в строке в таком порядке, хоть и не подряд; "рар" — 'р' → потом 'а' → потом снова 'р'; "пир" — 'п' → потом 'и' → потом 'р'.
А вот "омар" не будет последовательностью, потому что нужные символы идут в разном порядке - "программирование", после "о" идёт или "а" или "р", так что нет.
Подмножество символов — это просто набор символов, которые есть в строке, без учёта порядка и количества. Порядок не важен, повторы не учитываются, и не обязательны все вхождения символов.
Конкатенация
Конкатенация, или объединение строк, это бинарная операция, при которой две строки объединяются в одну строку, где символы первого операнда следуют непосредственно перед символами второго.
Обозначается как строка+строка в программировании.
Как в примере описано - "Hello" + "World" образует "HelloWorld", и как можно заметить, пробела между строками не будет. Поэтому, если хотите красивое "Привет, Тимур", можно выполнять операцию вроде "Привет" + ", " + "Тимур" - и обратите внимание на ", " - пробел тоже символ, который важно указывать.
x = "Привет"
y = "Тимур"
z = x + ", " + y
print(z) # Привет, Тимур
Обрезка, склейка и регистр
Срез — вырезать кусок строки по номерам символов (индексам). В большинстве языков нумерация с нуля: первый символ — под индексом 0.
| Операция | Python | JavaScript | Java |
|---|---|---|---|
| Срез | "Programming"[3:8] → "gramm" | "Programming".slice(3, 8) | "Programming".substring(3, 8) |
| Поиск | "banana".find("ana") → 1 | "banana".indexOf("ana") | "banana".indexOf("ana") |
| Замена | "hello world".replace("world", "everyone") | то же | то же |
| Регистр | "Hello".upper() | "Hello".toUpperCase() | "Hello".toUpperCase() |
Нормализация регистра — приведение букв к одному виду (все строчные или все заглавные) перед сравнением строк. Цифры и знаки препинания регистра не имеют.
Обрезка, разбиение и склейка в девяти языках
Когда читают строку из файла, поля формы или ответа API, обычно нужны три шага.
- Trim (в Python — strip) — убрать лишнее только по краям строки: пробелы, табуляцию (
\t), перевод строки (\n). Середину метод не трогает. - Split — разрезать одну строку на список (массив) частей по разделителю (запятая, пробел, слэш
/). - Join — собрать список строк обратно в одну, вставив между элементами нужный разделитель.
TypeScript использует те же методы, что и JavaScript — в таблицах ниже одна колонка на оба языка. Подробнее о строках в JavaScript — статья о строках; о TypeScript — раздел TypeScript.
Обрезка пробелов по краям
| Язык | С двух сторон | Только слева / справа | Примечание |
|---|---|---|---|
| Python | " x ".strip() | .lstrip(), .rstrip() | Второй аргумент — набор символов для удаления, например .strip("!") |
| JavaScript | " x ".trim() | .trimStart(), .trimEnd() | |
| Java | " x ".strip() | .stripLeading(), .stripTrailing() | Метод .strip() с Java 11; подробнее — Unicode и пробелы |
| C# | " x ".Trim() | .TrimStart(), .TrimEnd() | .Trim(' ', '\t') — перечисление символов для удаления |
| PHP | trim($s) | ltrim($s), rtrim($s) | Глобальные функции PHP |
| Kotlin | " x ".trim() | .trimStart(), .trimEnd() | raw?.trim() — безопасный вызов, если строка может быть null |
| Groovy | " x ".trim() | как в Java | Для многострочных литералов — .stripIndent(), .stripMargin() |
| C++ | своей функцией | своей функцией | Примеры trim и split — в справочнике C++ |
Разбиение и склейка
| Язык | Split | Join | Когда что вызывать |
|---|---|---|---|
| Python | "a,b,c".split(",") | ",".join(parts) | split() без аргумента режет по любым пробелам подряд |
| JavaScript | "a,b,c".split(",") | parts.join(",") | |
| Java | "a,b,c".split(",") | String.join(",", parts) | Аргумент split — шаблон регулярного выражения, см. RegEx |
| C# | "a,b,c".Split(',') | string.Join(",", parts) | Флаг StringSplitOptions.TrimEntries обрезает пробелы у каждого поля |
| PHP | explode(",", $s) | implode(",", $arr) | Для шаблона RegEx — preg_split, см. ниже |
| Kotlin | "a,b,c".split(",") | parts.joinToString(",") | .split(Regex("\\s+")) — разбить по одному или нескольким пробелам |
| Groovy | "a,b,c".split(",") | parts.join(",") | Методы GDK поверх Java-строк |
| C++ | stringstream + getline | цикл или std::accumulate | С C++23 — std::views::split, см. C++ |
Путь к файлу папка/подпапка/файл.txt часто режут по /. В Python удобен rsplit('/', 1) — разрез только по последнему слэшу, чтобы отделить имя файла от каталогов. Склейка частей обратно — через join.
Типичный сценарий — строка из CSV
CSV (Comma-Separated Values) — текст, где поля в строке разделены запятыми. Подробнее о табличных форматах — в структурах данных (раздел про CSV-файл).
Строка из файла или формы редко бывает идеальной. Типичный вид:
- пробелы в начале и в конце всей строки;
- символ перевода строки
\nв конце; - пробелы вокруг отдельных полей после запятой.
Порядок обработки
- Обрезать края всей строки (
strip/trim). - Разрезать по запятой (
split/explode). - Обрезать края каждого поля.
Исходная строка: " Анна, 25, программист \n".
Ожидаемый результат: ['Анна', '25', 'программист'].
raw = " Анна, 25, программист \n"
fields = [part.strip() for part in raw.strip().split(",")]
# ['Анна', '25', 'программист']
Та же логика в других языках:
| Язык | Код |
|---|---|
| JavaScript / TypeScript | raw.trim().split(",").map(s => s.trim()) |
| Java | Arrays.stream(line.strip().split(",")).map(String::strip).toList() |
| C# | line.Trim().Split(',').Select(s => s.Trim()).ToArray() |
| C# (короче) | line.Split(',', StringSplitOptions.TrimEntries) |
| PHP | array_map('trim', explode(',', trim($line))) |
| Kotlin | line.trim().split(",").map { it.trim() } |
| Groovy | line.trim().split(',').collect { it.trim() } |
| C++ | обрезка и split по запятой |
Собрать поля обратно с запятой и пробелом между ними:
", ".join(fields) # "Анна, 25, программист"
Для учебных файлов и простых форм такой разбор достаточен. Сложный CSV (кавычки, переносы внутри ячейки) разбирают стандартными библиотеками форматов JSON, CSV и XML.
Частые ошибки при разборе строк
Java — split и регулярные выражения
RegEx (регулярное выражение) — шаблон для поиска и разбиения текста. Метод String.split принимает именно такой шаблон. В RegEx точка . означает "любой символ", поэтому "a.b.c".split(".") даёт пустой массив — строка режется по каждому символу.
- Разрез по буквальной точке:
"a.b.c".split("\\."). - Общие правила шаблонов — в справочнике RegEx.
- Методы
Stringв Java — полезные методы String.
PHP — explode и preg_split
Две разные функции для двух задач.
explode(',', $s)— разделитель точная подстрока (удобно для простого CSV).preg_split('/\s+/', $s)— разделитель шаблон RegEx (удобно, когда между словами может быть любое количество пробелов).
Справочник функций — PHP, строки.
Пустые поля после split
Если между двумя запятыми ничего нет, многие языки сохраняют пустую строку в списке.
"a,,b".split(',')в Python →['a', '', 'b']- то же в JavaScript
Как убрать пустые элементы:
- Python —
[p for p in parts if p] - C# —
StringSplitOptions.RemoveEmptyEntries - JavaScript —
.filter(Boolean)
Unicode и пробелы
Unicode — стандарт кодирования символов. Помимо обычного пробела в нём есть другие пробельные символы (например неразрывный пробел).
- Java —
.strip()(Java 11+) обрабатывает широкий набор пробелов Unicode;.trim()ориентирован на более узкий набор. - Python —
.strip()убирает пробельные символы Unicode с краёв строки. - C# —
.Trim()учитывает Unicode whitespace.
C++ — обрезка и split своими функциями
У std::string есть find, substr, erase. Готовые trim и split добавляют вручную:
- обрезка —
find_if_notи копирование подстроки; - разбиение —
std::stringstreamиstd::getline; - с C++23 —
std::views::split.
Пошаговые примеры — разбиение и обрезка в C++. Тип std::string — в этом же справочнике.
Проверки (предикаты над строками) — логические функции, возвращающие true или false.
startWith(prefix)— начинается ли строка с префикса;endsWith(suffix)- проверяет, заканчивается ли строка суффиксом;contains(substring)- проверяет, содержит ли строка подстроку;isEmpty()- проверяет, является ли строка пустой (длина 0);isNumeric(),isAlpha(),isWhitespace()- проверка типа символов.
Эти операции - предикаты в теории языков, используются в валидации, парсинге, анализе. Например, isEmpty может использоваться для проверки на случай, если строка окажется пустой.
Форматирование
Форматирование строк - это процесс вставки значений переменных, чисел, дат и прочих значений в шаблон строки с контролем их внешнего вида.
Это может быть подстановка значений по позиции или имени (допустим, мы будем подставлять "Hello, {name}!", и в {name} подставляется значение этой переменной, которым может быть "Тимур", "Иван" или любое другое текстовое значение - в итоге текст будет как "Hello, Тимур!".
Форматирование бывает трёх типов:
- Позиционное —
printf("Name — %s, Age — %d", name, age); - Именованное:
"Hello, {name}".format(name="Bob"); - Интерполяция:
f"Result: {result}".
Операции с потоками данных
Потоки
Передачу структур между процессами и сервисами (JSON, protobuf, безопасность) разбираем в Маршалинг и анмаршалинг.
О потоках мы уже говорили, даже если покажется незнакомым — это последовательности байтов, которые могут быть прочитаны или записаны по частям, к примеру, байты, аудио, видео, бинарные данные. Давайте даже конкретно их рассмотрим:
- Байты (bytes) - сырые данные;
- Аудио (WAV, MP3, FLAC) - звуковые данные;
- Видео (MP4, AVI, MOV) - видеопотоки;
- Изображения (JPEG, PNG, GIF) - бинарные данные с пикселями;
- Документы (PDF, DOCX) - структурированные бинарные/текстовые данные.
Какие же операции могут быть с ними?
- Чтение/запись потока - побайтово или блоками;
- Буферизация - временная буферизация для эффективности;
- Кодирование/декодирование - например, base64, UTF-8;
- Стриминг - передача данных "на лету";
- Конвертация форматов - MP3 в WAV, PNG в JPEG;
- Обработка медиа - наложение эффектов, обрезка, компрессия;
- Хеширование - SHA-256, MD5 от бинарных данных;
- Сериализация - преобразование объекта в байты, и десериализация - наоборот.
Надёжность потоковой обработки
Для продакшн-систем важны не только операции, но и режим отказов:
- Повторяемость обработки (idempotency), чтобы повторный запуск не портил данные.
- Контроль порядка сообщений, если бизнес-логика зависит от последовательности.
- Лимиты памяти и размер буферов при чтении больших файлов.
- Таймауты и ретраи при работе с сетью.
- Контроль целостности (
checksum,hash) до и после передачи.
Чтение большого файла целиком в память часто приводит к всплескам потребления RAM. В большинстве случаев лучше использовать чтение блоками (streaming/chunking) и поэтапную обработку.
Чтение и запись потока
Чтение/запись потока - побайтово или блоками. Это операции последовательного доступа к данным в виде потока байтов (byte stream).
Поток является абстракцией последовательности данных, доступных по одному байту или блоку. Побайтовое чтение/запись это последовательное извлечение или запись одного байта за другим, а блочное чтение/запись работает с фиксированным или переменным количеством байт - это количество называется буфером. Определяется некий размер буфера - и читается поблочно.
Поток - это отображение данных, где каждый элемент - байт, а чтение - итерация по этому отображению. Файлы, сокеты - это всё потоки. Блочная передача, конечно, эффективнее.
Буферизация и кодирование
Буферизация - это временное хранение данных в буфере (памяти) для уменьшения количества обращений к медленным устройствам (диск и сеть). Это снижает количество вызовов, к примеру вместо 1000 вызовов write(1 byte) будет один вызов write(1000 bytes). Буферизация может быть полной (данные пишутся при заполнении буфера), построчной (после символа \n, для текстовых потоков). Без буферизации данные передаются немедленно.
Кодирование/декодирование - это преобразование данных из одного представления в другое для совместимости, передачи или интерпретации. Кодирование текста (UTF-8, UTF-16 и прочие) это использование переменных кодировок, где каждый символ определяется количеством байт. ASCII-символы (0-127) кодируются одним байтом, и текст переводится из одной кодировки в другую.
Кодирование бинарных данных (Base64) преобразует бинарные данные в строку из 64 ASCII-символов (A-Z, a-z, 0-9, +, /), используется когда бинарные данные нельзя передать как есть. В итоге алгоритм группирует по 3 байтам (24 бита), затем разбивает на 4 группы по 6 бит и индексирует в алфавите Base64.
Стриминг же является процессом постепенной передачи и обработки данных по мере их поступления, без необходимой загрузки всего объёма в память. Как итог - обработка происходит в режиме реального времени, и так работает, например, прослушивание музыки до её полной загрузки. Конвертация форматов - это преобразование данных из одного формата представления в другой с сохранением (или изменением) содержания. Думаю, каждый сталкивался с конвертацией, к примеру, когда нужно было перевести MP3 в WAV или PNG в JPEG. Процесс сначала декодирует исходный формат, затем обрабатывает и кодирует в целевой формат, что позволяет превратить один файл в совершенно другой.
Обработка медиа - это преобразование мультимедийных данных (аудио, видео, изображений) с изменений их содержания или качества. Обрезка удаляет части данных, наложение эффектов добавляет фильтры (размытие, контраст), наложение звука, субтитров, а компрессия уменьшает объём за счёт удаления избыточной или малозаметной информации. Компрессия, кстати бывает с потерями (MP3, JPEG), когда теряется информация, и без потерь (FLAC, PNG), когда имеется возможность полного восстановления. Словом, JPEG в BMP не будет соответствовать первоначальному BMP, который превращали в JPEG.
Хеширование — функция, преобразующая последовательность байт произвольной длины в фиксированный "отпечаток" (хеш). Применяется для проверки целостности файлов, подписей, блокчейна. Для паролей используют медленные алгоритмы с солью (bcrypt, Argon2), а не MD5/SHA-1 в чистом виде.
Сериализация преобразует объект в поток байтов или текст; десериализация восстанавливает объект. JSON/XML/YAML — человекочитаемые; Protocol Buffers, MessagePack — компактные бинарные. pickle в Python удобен локально, но небезопасен для данных из сети (см. Маршалинг и анмаршалинг - сериализация объектов).
Операции с объектами
Объект
Объекты - это структуры, содержащие данные и поведение, или просто структурированные данные.
- Объекты в объектно-ориентированном программировании - это классы, экземпляры, методы, о них мы поговорим позже;
- Структуры / записи - именованные поля;
- Словари / хэш-таблицы - ключ-значение;
- Массивы/списки/коллекции - упорядоченные данные;
- JSON, XML, YAML - текстовые представления объектов.
Объект — это сущность, содержащая состояние (данные, поля, атрибуты), поведение (методы, функции), идентичность (уникальность). Это структурированный контейнер данных по принципу "имя — значение". В каждое поле можно записывать данные и манипулировать ими.

Операции с объектами
С объектами могут быть следующие операции:
- Создание (new User);
- Доступ к полям (объект.поле);
- Изменение полей (объект.поле = 25);
- Добавление/удаление полей;
- Итерация;
- Клонирование/копирование (кстати, может быть поверхностное и глубокое);
- Сравнение - по значению или ссылке;
- Сериализация/Десериализация;
- Валидация схемы объекта.
Создание объекта - это процесс выделения памяти и инициализации объекта с заданным типом или структурой. В ООП это более сложный процесс, который мы изучим отдельно.
Доступ к полям подразумевает получение значения по имени поля (атрибута) объекта через точку - "объект.поле". Но, кстати, через точку - это называется "точечная нотация", когда используется специальный оператор ".", а есть ещё скобочная user["name"], позволяющая использовать динамические ключи.
Изменение полей (объект.поле = 25) подразумевает мутацию состояния объекта путём присвоения нового значения существующему полю. Это изменяет состояние объекта - в функциональных языках такие операции запрещены (например, Haskell), так как объекты там неизменны, что противоречит чистоте функций.
Добавление/удаление полей подразумевает динамическое расширение или сужение объекта. Это свойство динамических объектов, поддерживается не везде. К примеру, в Java или C++ поля класса зафиксированы и их нельзя менять, а в JavaScript и Python можно менять.
Итерация по объекту - это последовательный обход всех полей или элементов объекта. Итерация бывает нескольких типов:
- по ключам -
for (key in obj); - по значениям -
for (value of Object.values(obj)); - по парам -
for ([k, v] of Object.entries(obj));
Формально, итерация актуальнее для массивов (по индексам) и для словарей (по ключам). Если есть у нас массив [1, 2, 3], то цикл for будет обходить каждый элемент массива по очереди - сначала 1, потом 2, потом 3.
Клонирование/копирование имеют общую цель - создать объект с теми же данными, но новый объект должен быть независимым от оригинала.
Копирование может быть:
- Поверхностным — копируются поля верхнего уровня; если поле — ссылка на вложенный объект, копируется ссылка, а не содержимое;
- Глубоким — рекурсивно копируются все вложенные объекты.
import copy
original = {"tags": ["a", "b"]}
shallow = original.copy()
deep = copy.deepcopy(original)
original["tags"].append("c")
# shallow["tags"] → ["a", "b", "c"] — общий вложенный список
# deep["tags"] → ["a", "b"] — независимая копия
Сравнение объектов — два разных смысла:
| Подход | Смысл | Примеры |
|---|---|---|
| По ссылке (identity) | Один и тот же экземпляр в памяти | Python: a is b; Java: == для ссылок; JS: a === b для объектов |
| По значению (equality) | Равны поля, даже если объекты разные | Java: equals(); Python: == (часто через __eq__) |
Сериализация преобразует объект в последовательность байт или текст (JSON, XML); десериализация восстанавливает объект. Для обмена между сервисами см. маршалинг.
Валидация схемы объекта - это проверка, соответствует ли объект заданной структуре и ограничениям (схеме). Проверяется наличие обязательных полей, типы значений, диапазоны чисел, формат строк, вложенность.
Мини-кейс — приём JSON от внешнего API
Когда сервис получает внешний JSON, практический pipeline выглядит так:
- Сначала парсим JSON в структуру.
- Затем валидируем обязательные поля и типы.
- Нормализуем значения (даты, валюты, идентификаторы, регистр строк).
- Сохраняем в доменную модель.
- Логируем причины отклонения невалидных записей.
Этот подход помогает разделять "ошибки поставщика данных" и "ошибки вашего кода".
Операции с датами и временем
Дата и время
Операции с датами и временем. Нет, конечно подразумевается не перемотка или путешествие во времени, но всё же дата и время являются особым типом данных, который учитывает часовые пояса, форматы, високосные годы и прочее.
Основные операции:
- Получение текущей даты/времени;
- Создание даты;
- Форматирование (к примеру, в dd.MM.yyyy);
- Парсинг строки в дату;
- Вычисление разницы между датами;
- Добавление/вычитание;
- Сравнение;
- Работа с часовыми поясами (UTC, GMT+5, автоматический перевод);
- Определение дня недели, месяца, года.
Даты и время - особый тип данных, который сочетает математическую, астрономическую и социальную природу. Работа с ними требует особого подхода из-за календарных аномалий - високосные годы, переходы на летнее время, разные часовые пояса, исторические изменения календарей и прочее.
Дата и время - это момент на временной оси, отнесённый к определённой системе отсчёта (календарю, часовому поясу), и представленный в виде структуры:
DateTime=(Y,M,D,h,m,s,ms) + tz (часовой пояс)
Абсолютное время - это момент в UTC, а относительное время - это момент, привязанный к локальному часовому поясу. В информатике используется модель временной оси - непрерывная шкала, отсчитываемая от фиксированной точки (эпохи).
Работа с часовыми поясами в основном требует понимания основ.
Часовой пояс (time zone) — это политическая и географическая зона, в которой используется единое время.
UTC (Universal Time Coordinated) — основа всемирного времени, не имеет летнего времени.
GMT (Greenwich Mean Time) — исторически, близко к UTC, но не совсем то же.
TZ-база данных (Olson database) — стандартная база временных зон (например, Europe/Moscow, America/New_York).
Операции с датами и временем
Получение текущей даты/времени — момент по системным часам. Unix timestamp — секунды (или миллисекунды) с 1970-01-01 00:00:00 UTC. Важно различать "сейчас в UTC" и "сейчас в локали":
from datetime import datetime, timezone
utc_now = datetime.now(timezone.utc)
local_now = datetime.now().astimezone()
const utc = new Date().toISOString(); // 2025-04-05T14:30:00.000Z
const local = new Date().toString();
Точность и часовой пояс зависят от API — смотрите документацию библиотеки.
Создание даты подразумевает построение объекта даты/времени по заданным компонентам - год, месяц, день, час и т.д. При этом используется учёт часового пояса (если не указан - то применяется локальный или UTC), и проверка корректности (например, 33 февраля автоматически корректируется).
Форматирование даты (в dd.MM.yyyy и др) это преобразование объекта даты/времени в строку в заданном человекочитаемом формате. Примеры:
dd.MM.yyyy→"05.04.2025"yyyy-MM-dd HH:mm:ss→"2025-04-05 14:30:00"ISO 8601: 2025-04-05T14:30:00Z
Символы формата:
- d — день (1–31), dd — с ведущим нулём
- M — месяц (1–12), MM — с нулём
- yyyy — 4-значный год
- HH, mm, ss — часы, минуты, секунды
Форматирование зависит от локали - например, en-US vs ru-RU.
Собственно, поэтому язык, регион, дата и время идут вместе - потому что определяется регион, который и определяет основные правила форматирования.
Парсинг строки в дату — обратная операция к форматированию. Важны неоднозначность ("01.02.03"), некорректный ввод ("32.13.9999") и опасный автопарсинг (new Date("xyz") → Invalid Date). Указывайте формат явно:
from datetime import datetime
datetime.strptime("05.04.2025", "%d.%m.%Y")
new Date("2025-04-05T14:30:00Z"); // ISO 8601
Вычисление разницы между датами - это определение интервала времени между двумя моментами. Единицами измерения здесь бывают секунды/миллисекунды (для точных вычислений), а также дни, месяцы, годы. Сложности здесь могут быть разве что с месяцами разной длины (28-31 день), годами (365 или 366 дней), и переходами на летнее время (могут убрать или добавить час). Разница в миллисекундах — самая точная. Разница в месяцах/годах — условна и требует специальных библиотек.
Добавление или вычитание (смещение даты) это изменение даты на заданную продолжительность. +5 дней, -2 часа. Прибавление дней учитывает високосные годы, прибавление месяцев имеет свои ньюансы (31 января + 1 месяц это не 31 февраля), как и прибавление часов (может пересекать переход на летнее время, в итоге получим пропуск или двойной час).
Сравнение дат это логическая операция, определяющая порядок двух моментов времени. Операции здесь обычные ==, !=, <, >, <=, >=. Сравнение возможно только в одной временной шкале (лучше в UTC), а если сравнивать локальные даты с разными часовыми поясами, можно получить ошибку. Даты упорядочены линейно, как вещественные числа.
Конвертация переводит время из одного часового пояса в другой; DST может добавить или убрать час. Практика: хранить в UTC, показывать в локальном поясе (база IANA: Europe/Moscow). Иначе возможны "двойные" или "пропавшие" часы.
Определение дня недели, месяца, года — извлечение календарных полей. Високосный год — делится на 4, но не на 100, кроме случаев деления на 400. Для бизнес-логики ("рабочий ли день") используйте библиотеки (dateutil, java.time), а не ручной календарь.
Частые ошибки при работе с данными
- Смешение локального времени и UTC внутри одного процесса.
- Неявные преобразования типов в критичных расчётах.
- Сравнение денег в
float/doubleвместо целочисленных минимальных единиц илиdecimal. - Отсутствие валидации на границах системы.
- Доверие входным данным из сети без схемы и ограничений.
Продакшн-кейс
Платёжный сервис получает операции из трёх источников: веб-форма, мобильный клиент и очередь партнёра.
- Входящие данные приводятся к единой схеме.
- Денежные суммы нормализуются в минимальные единицы.
- Временные метки переводятся в UTC.
- События сохраняются в журнал и проходят валидацию на идемпотентность.
- Только после этого выполняются бизнес-операции списания и подтверждения.
Такой конвейер снижает число расхождений между каналами и упрощает расследование инцидентов.
Антипаттерны
- "Молчаливые" приведения типов без логирования ошибок преобразования.
- Хранение дат как строк с локальным форматом внутри БД.
- Загрузка больших файлов целиком в память при потоковом сценарии.
- Ручной парсинг JSON через
split, когда доступен стандартный парсер. - Сравнение денежных значений в
floatс допущением точного равенства.
Чек-лист качества обработки данных
- Данные валидируются на входе и перед записью в хранилище.
- Для времени зафиксирована единая стратегия: хранение в UTC, отображение в локали.
- Для денег и критичных расчётов выбраны точные типы.
- Ошибки парсинга и преобразования попадают в наблюдаемость (логи, метрики, алерты).
- Потоковая обработка ограничивает память и поддерживает повторяемый запуск.
Мини-упражнения
- Возьмите CSV и постройте pipeline — парсинг, валидация, нормализация, запись.
- Подготовьте пример с поверхностным и глубоким копированием вложенного объекта.
- Реализуйте конвертацию времени из локали в UTC и обратно для интерфейса пользователя.
- Сравните хеши файла до и после передачи через сеть.
Контрольные вопросы
- Где у вашего сервиса проходит граница доверия к данным?
- Какие преобразования выполняются всегда, а какие зависят от источника?
- Какие форматы обмена подходят для читаемости, а какие — для скорости и размера?
- Какие ошибки данных должны прерывать процесс, а какие можно отправлять в карантин?