Метод содержимое файла с расширением class
Подобно паре Directory/DirectoryInfo для работы с файлами предназначена пара классов File и FileInfo . С их помощью мы можем создавать, удалять, перемещать файлы, получать их свойства и многое другое.
FileInfo
Некоторые полезные методы и свойства класса FileInfo :
CopyTo(path) : копирует файл в новое место по указанному пути path
Create() : создает файл
Delete() : удаляет файл
MoveTo(destFileName) : перемещает файл в новое место
Свойство Directory : получает родительский каталог в виде объекта DirectoryInfo
Свойство DirectoryName : получает полный путь к родительскому каталогу
Свойство Exists : указывает, существует ли файл
Свойство Length : получает размер файла
Свойство Extension : получает расширение файла
Свойство Name : получает имя файла
Свойство FullName : получает полное имя файла
Для создания объекта FileInfo применяется конструктор, который получает в качестве параметра путь к файлу:
Класс File реализует похожую функциональность с помощью статических методов:
Copy() : копирует файл в новое место
Create() : создает файл
Delete() : удаляет файл
Move : перемещает файл в новое место
Exists(file) : определяет, существует ли файл
Пути к файлам
Для работы с файлами можно применять как абсолютные, так и относительные пути:
Получение информации о файле
Удаление файла
Перемещение файла
Если файл по новому пути уже существует, то с помощью дополнительного параметра можно указать, надо ли перезаписать файл (при значении true файл перезаписывается)
Копирование файла
Метод CopyTo класса FileInfo принимает два параметра: путь, по которому файл будет копироваться, и булевое значение, которое указывает, надо ли при копировании перезаписывать файл (если true , как в случае выше, файл при копировании перезаписывается). Если же в качестве последнего параметра передать значение false , то если такой файл уже существует, приложение выдаст ошибку.
Метод Copy класса File принимает три параметра: путь к исходному файлу, путь, по которому файл будет копироваться, и булевое значение, указывающее, будет ли файл перезаписываться.
Чтение и запись файлов
В дополнение к вышерассмотренным методам класс File также предоставляет ряд методов для чтения-записи текстовых и бинарных файлов:
AppendAllLines(String, IEnumerable) / AppendAllLinesAsync(String, IEnumerable, CancellationToken)
добавляют в файл набор строк. Если файл не существует, то он создается
AppendAllText(String, String) / AppendAllTextAsync(String, String, CancellationToken)
добавляют в файл строку. Если файл не существует, то он создается
byte[] ReadAllBytes (string path) / Task
считывают содержимое бинарного файла в массив байтов
string[] ReadAllLines (string path) / Task
считывают содержимое текстового файла в массив строк
string ReadAllText (string path) / Task ReadAllTextAsync (string path, CancellationToken cancellationToken)
считывают содержимое текстового файла в строку
IEnumerable ReadLines (string path)
считывают содержимое текстового файла в коллекцию строк
void WriteAllBytes (string path, byte[] bytes) / Task WriteAllBytesAsync (string path, byte[] bytes, CancellationToken cancellationToken)
записывают массив байт в бинарный файл. Если файл не существует, он создается. Если существует, то перезаписывается
void WriteAllLines (string path, string[] contents) / Task WriteAllLinesAsync (string path, IEnumerable contents, CancellationToken cancellationToken)
записывают массив строк в текстовый файл. Если файл не существует, он создается. Если существует, то перезаписывается
WriteAllText (string path, string? contents) / Task WriteAllTextAsync (string path, string? contents, CancellationToken cancellationToken)
записывают строку в текстовый файл. Если файл не существует, он создается. Если существует, то перезаписывается
Как видно, эти методы покрывают практически все основные сценарии - чтение и запись текстовых и бинарных файлов. Причем в зависимости от задачи можно применять как синхронные методы, так и их асинхронные аналоги.
Например, запишем и считаем обратно в строку текстовый файл:
Стоит отметить, что при добавлении текста я добавил в строку последовательность "\n", которая выполняет перевод на следующую строку. Благодаря этому добавляемый текст располагается в файле на новой строке.
Если мы хотим, что в файле изначально шло добавление на новую строку, то для записи стоит использовать метод WriteAllLines/ WriteAllLinesAsync , а для добавления - AppendAllLines / AppendAllLinesAsync
Аналогично при чтении файла если мы хотим каждую строку файла считать отдельно, то вместо ReadAllText / ReadAllTextAsync применяется ReadAllLines / ReadAllTextAsync .
Кодировка
В качестве дополнительного параметра методы чтения-записи текстовых файлов позволяют установить кодировку в виде объекта System.Text.Encoding :
Для установки кодировки при записи и чтении здесь применяется встроенное значение Encoding.Unicode . Также можно указать название кодировки, единственное следует удостовериться, что текущая операционная система поддерживает выбранную кодировку:
Продолжаем разговор о том, как Java Virtual Machine работает внутри. В предыдущей статье (оригинал на анг.) мы рассмотрели подсистему загрузки классов. В этой статье мы поговорим о структуре class-файлов.
Как мы уже знаем, весь исходный код, написанный на языке программирования Java, сначала компилируется в байт-код с помощью компилятора javac , входящего в состав Java Development Kit. Байт-код сохраняется в бинарный файл в специальный class-файл. Затем эти class-файлы динамически (при необходимости) загружаются в память загрузчиком классов (ClassLoader).
Рисунок — компиляция исходного кода Java
Каждый файл с расширением .java компилируется как минимум в один файл .class . Для каждого класса, интерфейса и модуля, определенных в исходном коде, создается по одному .class файлу. Это также относится к интерфейсам и вложенным классам.
Примечание — для простоты файлы с расширением .class будем называть “class-файлами”.
Давайте напишем простую программу.
Запуск javac для этого файла приведет к появлению следующих файлов.
Как видите, для каждого класса и интерфейса создается отдельный class-файл.
Что внутри class-файла?
Class-файл содержит следующую информацию.
Магическое число, сигнатура. Первые четыре байта каждого class-файла всегда 0xCAFEBABE . Эти четыре байта идентифицируют class-файл Java.
Версия файла. Следующие четыре байта содержат мажорную и минорную версию файла. Вместе эти номера определяют версию формата class-файла. Если class-файл имеет основной мажорную версию M и минорную m, то мы обозначаем эту версию как M.m.
У каждой JVM есть ограничения по поддерживаемым версиям class-файлов. Например, Java 11 поддерживает major версию с 45 до 55, Java 12 — с 45 по 56.
Пул констант. Таблица структур, представляющих строковые константы, имена классов, интерфейсов, полей, методов и другие константы, которые есть в структуре ClassFile и ее подструктурах. Каждый элемент пула констант начинается с однобайтового тега, определяющего тип константы. В зависимости от типа константы следующие байты могут быть непосредственным значением константы или ссылкой на другой элемент в пуле.
Флаги доступа. Список флагов, которые указывают класс это или интерфейс, public или private, финальный класс или нет. Различные флаги, такие как ACC_PUBLIC , ACC_FINAL , ACC_INTERFACE , ACC_ENUM и т. д. описаны спецификации Java Virtual Machine Specification.
This class. Ссылка на запись в пуле констант.
Super class. Ссылка на запись в пуле констант.
Интерфейсы. Количество интерфейсов, реализованных классом.
Количество полей. Количество полей в классе или интерфейсе.
Поля. После количества полей следует таблица структур переменной длины. По одной для каждого поля с описанием типа поля и названия (со ссылкой на пул констант).
Количество методов. Количество методов в классе или интерфейсе. Это число включает только методы, которые явно определены в классе, без методов, унаследованных от суперклассов.
Методы. Далее находятся сами методы. Для каждого метода содержится следующая информация: дескриптор метода (тип возвращаемого значения и список аргументов), количество слов, необходимых для локальных переменных метода, максимальное количество слов стека, необходимых для стека операндов метода, таблицу исключений, перехватываемых методом, байт-коды метода и таблица номеров строк.
Количество атрибутов. Количество атрибутов в этом классе, интерфейсе или модуле.
Атрибуты. После количества атрибутов следуют таблицы или структуры переменной длины, описывающие каждый атрибут. Например, всегда есть атрибут “SourceFile”. Он содержит имя исходного файла, из которого был скомпилирован class-файл.
Хотя class-файл напрямую не человекочитаемый, в JDK есть инструмент под названием javap, который выводит его содержимое в удобном формате.
Давайте напишем простую программу на Java, указанную ниже.
Давайте скомпилируем эту программу с помощью javac , которая создаст файл HelloWorld.class , и используем javap для просмотра файла HelloWorld.class . Запустив javap с параметром -v (verbose) для HelloWorld.class получим следующий результат:
Здесь вы можете увидеть, что класс публичный ( public ) и у него в пуле констант 37 записей. Есть один атрибут (SourceFile внизу), класс реализует два интерфейса (Serializable, Cloneable), у него нет полей и есть два метода.
Возможно, вы заметили, что в исходном коде есть только один статический метод main, но class-файл говорит, что есть два метода. Вспомните конструктор по умолчанию — это конструктор без аргументов, добавленный компилятором javac , байт-код которого также виден в выводе. Конструкторы рассматриваются как методы.
Больше почитать про javap вы можете здесь.
Совет: вы также можете использовать javap для того, чтобы увидеть, чем лямбды отличаются от анонимных внутренних классов.
Это исключительно удобно в случаях, когда вы хотите добавить функционал к таким типам, которых не контролируете. Фактически, любому рано или поздно приходилось писать расширение для BCL, просто чтобы сделать некоторые вещи более доступными.
Но, наряду со сравнительно очевидными случаями использования, есть и очень интересные паттерны, завязанные непосредственно на использовании методов расширений и демонстрирующие, как их можно задействовать не самым традиционным образом.
Добавление методов к перечислениям
В некоторых случаях может быть полезно запрограммировать логику в перечисление. Например, если значение перечисления может существовать в нескольких разных представлениях, и вы хотели бы легко преобразовывать одно в другое.
Например, представьте себе следующий тип в обычном приложении, позволяющем сохранять файлы в различных форматах:
Данное перечисление определяет список форматов, поддерживаемых в приложении, и может использоваться в разных частях приложения для инициирования логики ветвления в зависимости от конкретного значения.
Поскольку каждый формат файла может быть представлен в виде файлового расширения, было бы хорошо, если бы в каждом FileFormat содержался метод для получения этой информации. Как раз при помощи метода расширения это и можно сделать, примерно так:
Что, в свою очередь, позволяет нам поступить так:
Рефакторинг классов моделей
Бывает, что вы не хотите добавлять метод непосредственно к классу, например, если работаете с анемичной моделью.
Анемичные модели обычно представлены набором публичных неизменяемых свойств, только для получения. Поэтому при добавлении методов к классу модели может создаться впечатление, что нарушается чистота кода, либо можно заподозрить, что методы обращаются к какому-либо приватному состоянию. Методы расширения такой проблемы не вызывают, поскольку не имеют доступа к приватным членам модели и по природе своей не являются частью модели.
Итак, рассмотрим следующий пример с двумя моделями: одна представляет закрытый список титров, а другая – отдельную строку титров:
В текущем состоянии, если потребуется получить строку субтитров, отображенную в конкретный момент времени, мы запустим LINQ такого рода:
Здесь в самом деле напрашивается какой-либо вспомогательный метод, который можно было бы реализовать либо как метод члена, либо как метод расширения. Я предпочитаю второй вариант.
В данном случае метод расширения позволяет добиться того же, что и обычный, но дает ряд неочевидных бонусов:
- Понятно, что этот метод работает только с публичными членами класса и не изменяет его приватного состояния каким-нибудь таинственным образом.
- Очевидно, что этот метод просто позволяет срезать угол и предусмотрен здесь только для удобства.
- Этот метод относится к совершенно отдельному классу (или даже сборке), назначение которых – отделять данные от логики.
Как сделать интерфейсы разностороннее
При проектировании интерфейса всегда хочется, чтобы контракт оставался минимальным, поскольку так его будет легче реализовать. Это очень помогает, когда интерфейс предоставляет функционал в самом обобщенном виде, так что ваши коллеги (или вы сами) можете надстраивать над ним обработку более специфичных случаев.
Если вам это кажется нонсенсом, рассмотрим типичный интерфейс, сохраняющий модель в файл:
Все работает нормально, но через пару недель может подоспеть новое требование: классы, реализующие IExportService , должны не только экспортировать в файл, но и уметь писать в файл.
Таким образом, чтобы выполнить это требование, мы добавляем к контракту новый метод:
Это изменение только что сломало все имеющиеся реализации IExportService , поскольку теперь все их нужно обновить, чтобы они поддерживали и запись в память.
Но, чтобы не делать всего этого, мы могли с самого начала спроектировать интерфейс немного иначе:
В таком виде интерфейс вынуждает прописывать место назначения в максимально обобщенном виде, то есть, это Stream . Теперь при работе мы более не ограничены файлами и можем также нацеливаться на различные другие варианты вывода.
Единственный недостаток такого подхода заключается в том, что самые базовые операции становятся не столь простыми, как мы привыкли: теперь приходится задавать конкретный экземпляр Stream , обертывать его в инструкцию using и передавать как параметр.
К счастью, этот недостаток полностью обнуляется при использовании методов расширений:
Выполнив рефакторинг исходного интерфейса, мы сделали его гораздо более разносторонним и, благодаря использованию методов расширения, ничуть не пожертвовали удобством использования.
Таким образом, я считаю методы расширения бесценным инструментом, который позволяет сохранить простое простым, а сложное превратить в возможное.
Извиняюсь за столь глупый вопрос и не желание рыться в гугле.
Смотрите. Создаю два файла -
Сам класс в .h, а его описание в .cpp. А потом к основному main.cpp подключаю просто файл .h. Я правильно понимаю?
Но вот только как Class.cpp увидит класс в Class.h? Я получается должен еще в самом Class.cpp подключить Class.h. Так ли это? Получается какой-то костыль. И вообще кто придумал так делать? Это же ужас как не удобно. Чтобы создать новую функцию нужно лезть в один файл, потом описывать ее в другом файле. При этом если функция имеет много аругментов. Почему нельзя все сразу в .h файле описать и потом его подключить к main.cpp?
Такс, давай отойдем немного назад, ведь у тебя явно ощущаются пробелы в знаниях.
Чтобы тебя правильно понимали, сперва нужно понять общую терминологию, а далее - этой терминологией нужно корректно пользоваться.
У нас есть два строгих термина: Declaration (объявление) и Definition (определение).
Замечу одну важную вещь - для определения характерно ODR (правило однократного определения), но пока на это можно не отвлекаться, потом надо будет.
А для чего ты это делаешь? Какую цель ты преследуешь, когда сперва объявляешь свой тип в отдельном файле, а потом говоришь препроцессору включить этот файл в тело исходного кода?
Один раз я уже отвечал на подобный вопрос, повторю только место, где начинается важная сейчас информация: "С точки зрения компилятора есть только один формат файла - формат исходного кода, который ему и надо обработать".
Компилятору известно только понятие Translation Unit. Он ничего не понимает во всех этих .h/.inl/.inc/.c/.cpp/.
Для правильной сборки кода ты должен рассказать компилятору о том, как из всей понятной только тебе бурды собрать вожделенный для него модуль трансляции, в котором будет весь исчерпывающий код для завершения компиляции.
Тебе незачем лепить файлы только ради объявлений и определений. Все равно для компилятора это будет собрано в один файл. А такие системы сборки, как FastBuild или IncrediBuild и вовсе весть проект вгоняют в один файл и только потом кормят этим файлом компилятор.
И это ошибочный вывод. Вот теперь давай разбираться в ODR.
One Definition Rule гарантирует нам детерминизм любого используемого в коде типа, любой функции, любой переменной в рамках одной области видимости (но не в разных).
Это правило позволяет сказать, что свойства программы однозначно определены до процесса трансляции и не изменяются после него.
Любой мелкий проект ты всегда можешь написать в одном файле на 2 килостроки. Это не страшно.
Когда у тебя более-менее большой проект, один файл у тебя уже не получится.
Появляется необходимость заниматься архитектурой кода: выводить сущности, связывать их, получать зависимости. Помимо архитектуры кода, появляется необходимость наладить грамотное разделение кода на файлы. А файлы нужно грамотно компоновать вместе. Появляется необходимость в макроархитектуре проекта.
Тут на помощь и приходят устоявшиеся правила разделения кода по типам файлов.
В таких условиях очень важно соблюдать ODR и крайне важно понимать смысл таких ключевых слов, как static и inline.
А помимо ODR тут еще встает вопрос избыточной компиляции кода - когда ненужные объявления все равно включаются в исходный код и впустую тратят процессорное время на свою трансляцию. В этом случае становится важно не включать лишние объявления или ненужные определения в заголовочные файлы.
И именно поэтому, если определение сущности можно выделить в отдельный модуль трансляции, то это лучше сделать. Если тип явным образом не используется в других модулях проекта, его лучше загнать в файл исходного кода. Если функция не предназначена для встраивания, ее лучше загнать в файл исходного кода. Если есть частная специализация шаблона, которая используется только в одном файле исходного кода, то лучше там ее и определить.
В этом случае процесс сборки твоего большого проекта будет легким и быстрым.
Это обычный исполняемый файл программы или библиотеки, предназначенной для выполнения на виртуальной машине Java. Очевидно, вы открыли его редактором, который отображает содержимое нетекстовых файлов в шестнадцатиричном представлении.
Шестнадцатиричная последовательность 0xCAFEBABE - сигнатура заголовка .class-файла согласно стандарта.
То что вы обнаружили, называется байткодом. Это набор инструкций, который является промежуточным между исходным кодом и машинным кодом. В дальнейшем он либо интерпретируется, либо компилируется jit компилятором в машинный.
Как известно, java кроссплатформенный язык. Из за этого исходный код компилировать в машинный сразу под все платформы крайне затруднительно. Поэтому, придумали байткод. Он не привязан к конкретной архитектуре, но при этом разбирает исходный код на мелкие инструкции, позволяющие производить некоторые оптимизации.
Формат данного файла описан в спеке.
Для дизассемблирования байткода можно воспользоваться стандартной утилитой javap
0xcafebabe точнее CA FE BA BE - это стандартная сигнатура .class файла Java - об этом уже все сказали.
Имеется немало легенд почему выбрана такая сигнатура cafebabe - кафешная красотка, по словам Джеймса Гослинга (для тех кто в танке - создателя языка Java):
"We used to go to lunch at a place called St Michael's Alley. According to local legend, in the deep dark past, the Grateful Dead used to perform there before they made it big. It was a pretty funky place that was definitely a Grateful Dead Kinda Place. When Jerry died, they even put up a little Buddhist-esque shrine. When we used to go there, we referred to the place as Cafe Dead. Somewhere along the line it was noticed that this was a HEX number. I was re-vamping some file format code and needed a couple of magic numbers: one for the persistent object file, and one for classes. I used CAFEDEAD for the object file format, and in grepping for 4 character hex words that fit after "CAFE" (it seemed to be a good theme) I hit on BABE and decided to use it. At that time, it didn't seem terribly important or destined to go anywhere but the trash-can of history. So CAFEBABE became the class file format, and CAFEDEAD was the persistent object format. But the persistent object facility went away, and along with it went the use of CAFEDEAD - it was eventually replaced by RMI.
В русском толковании звучит примерно следующим образом (переведен только смысл, опущены несущественные детали):
Мы частенько захаживали в один ресторанчик (во времена когда мы создавали язык Java). Согласно местной легенде в стародавние времена в этом ресторанчике выступала местная рок-группа "Благодарный Мертвец" (Grateful Dead), которая потом стала очень известной. Мы промежь себя так и называли ресторанчик "У мертвеца" (Cafe Dead). Однажды кто-то заметил, что CAFEDEAD составлено из HEX символов CA FE DE AD - я потом применил это как сигнатуру для формата объектных файлов Java. Слово CAFE - вообще было "в тему" (на американском сленге Java означает второсортный кофе), для .class файлов я выбрал вместо DEAD - BABE (красотка), тогда никто не думал, что это так важно. Так и появилась сигнатура CAFEBABE, а CAFEDEAD в итоге умер и заменен протоколом RMI.
Где-то слышал, что BABE был также выбран далеко не случайно, дескать кто-то из команды флиртовал с девицей, которая обслуживала их в этом ресторанчике.
Читайте также: