Основы: одна статья для понимания JAVA.IO, кодировки символов, URL и Spring.Resource.

Java
Основы: одна статья для понимания JAVA.IO, кодировки символов, URL и Spring.Resource.

1 байтовый поток JAVA.IO

inputstream.png
inputstream.png
  • LineNumberInputStream и StringBufferInputStream официально не рекомендуется больше использовать, вместо них рекомендуется использовать LineNumberReader и StringReader.
  • ByteArrayInputStream и ByteArrayOutputStream
    • Массивы байтов обрабатывают потоки, создают в памяти буфер для использования в качестве потока и считывают данные из буфера быстрее, чем с носителя (например, с диска).
//用ByteArrayOutputStream暂时缓存来自其他渠道的数据
ByteArrayOutputStream data = new ByteArrayOutputStream(1024); //1024字节大小的缓存区
data.write(System.in.read()); // 暂存用户输入数据

//将data转为ByteArrayInputStream
ByteArrayInputStream in = new ByteArrayInputStream(data.toByteArray());
  • FileInputStream и FileOutputStream
    • Получите доступ к файлу, используйте файл как InputStream и реализуйте операции чтения и записи в файле.
  • ObjectInputStream и ObjectOutputStream
    • Поток объектов, конструктор должен передавать поток для чтения и записи объектов JAVA; его можно использовать для сериализации, а объект должен реализовать интерфейс Serializable.
//java对象的写入
FileOutputStream fileStream = new FileOutputStream("example.txt");
ObjectOutputStream out = new ObjectOutputStream(fileStream);
Example example = new Example();
out.writeObject(example);

//java对象的读取
FileInputStream fileStream = new FileInputStream("example.txt");
ObjectInputStream in = new ObjectInputStream(fileStream);
Example = (Example) in.readObject();
  • PipedInputStream и PipedOutputStream
    • Конвейерный поток, подходящий для передачи данных между двумя потоками, один поток отправляет данные через выходной поток конвейера, а другой поток считывает данные через входной поток конвейера для реализации обмена данными между двумя потоками.
// 创建一个发送者对象
Sender sender = new Sender(); // 创建一个接收者对象
Receiver receiver = new Receiver(); // 获取输出管道流
// 获取输入输出管道流
PipedOutputStream outputStream = sender.getOutputStream(); 
PipedInputStream inputStream = receiver.getInputStream();
// 链接两个管道,这一步很重要,把输入流和输出流联通起来  
outputStream.connect(inputStream);
sender.start();// 启动发送者线程
receiver.start();// 启动接收者线程
  • SequenceInputStream
    • Объединяет несколько потоков ввода в один поток ввода, позволяя приложениям последовательно объединять несколько потоков ввода.
InputStream in1 = new FileInputStream("example1.txt");
InputStream in2 = new FileInputStream("example2.txt");
SequenceInputStream sequenceInputStream = new SequenceInputStream(in1, in2);
//数据读取
int data = sequenceInputStream.read();
  • FilterInputStream и FilterOutputStream используют шаблон декоратора для добавления дополнительных функций к потоку.Параметры построения подкласса требуют InputStream/OutputStream
ByteArrayOutputStream out = new ByteArrayOutputStream(2014);
//数据写入,使用DataOutputStream装饰一个InputStream
//使用InputStream具有对基本数据的处理能力
DataOutputStream dataOut = new DataOutputStream(out);
dataOut.writeDouble(1.0);
//数据读取
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
DataInputStream dataIn = new DataInputStream(in);
Double data = dataIn.readDouble();
  • DataInputStream и DataOutputStream (подклассы потока Filter)
    • Добавляет возможность обработки различных базовых типов данных для других потоков, таких как byte, int, String.
  • BufferedInputStream и BufferedOutputStream (подклассы потока Filter)
    • Добавить буферизацию для других потоков
  • PushBackInputStream (подкласс FilterInputStream)
    • Оттолкните входной поток, вы можете перемотать некоторые данные, прочитанные в буфер входного потока.
  • PrintStream (подкласс FilterOutputStream)
    • Поток печати, аналогичный System.out.print

2 символьный поток JAVA.IO

21.png
21.png
  • Из направленного графа потока байтов и потока символов они соответствуют друг другу, например CharArrayReader и ByteArrayInputStream.
  • Преобразование потока байтов и потока символов: InputStreamReader может преобразовывать InputStream в Reader, OutputStreamReader может преобразовывать OutputStream в Writer.
//InputStream转为Reader
InputStream inputStream = new ByteArrayInputStream("程序".getBytes());
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
//OutputStream转为Writer
OutputStream out = new FileOutputStream("example.txt");
OutputStreamWriter writer = new OutputStreamWriter(out);
//以字符为单位读写
writer.write(reader.read(new char[2]));
  • Отличие: единицей чтения потока байтов являются байты, а единицей чтения потока символов — символы; символ состоит из байтов, например кодировка с переменной длиной слова UTF-8 представлена ​​1~4 байтами.

3 искаженных персонажа и поток символов

  • Символы представлены разными кодировками, и их длина в байтах (длина слова) различна. Например, формат кодировки utf-8 слова «Cheng» состоит из [-25][-88][-117]. Кодировка ISO_8859_1 представляет собой один байт [63].
  • Обычно работа ресурсов в работе ориентирована на потоки байтов, однако при преобразовании ресурсов данных в байты по разным байт-кодам их содержимое различается, что легко приводит к искажению символов.
  • Два искаженных сценария
    • Кодировка символов, используемая кодированием и декодированием, несовместима: ресурс закодирован в UTF-8, но открывается с использованием декодирования GBK в коде.
    • Использование потока байтов для чтения количества байтов не соответствует указанной длине слова символа: символ состоит из байтов, например, формат utf-8 «cheng» составляет три байта; если он читается каждые два байта в InputStream Возьмите поток, а затем преобразуйте его в String (кодировка java по умолчанию — utf-8), тогда будут искаженные символы (наполовину китайские, угадайте что)
ByteArrayInputStream in = new ByteArrayInputStream("程序大法好".getBytes());
byte[] buf = new byte[2]; //读取流的两个字节
in.read(buf); //读取数据
System.out.println(new String(buf)); //乱码
---result---- 
�  //乱码
  • Искаженная сцена 1, если вы знаете кодировку символов ресурса, вы можете использовать соответствующую кодировку символов для декодирования и решения проблемы.
  • искаженная сцена 2,Все байты могут быть прочитаны за один раз, а затем закодированы за один раз. Но для больших файловых потоков это нереально, отсюда и появление символьных потоков
  • Поток байтов преобразуется в поток символов с помощью InputStreamReader и OutputStreamReader, в которых может быть указана кодировка символов, а затем обрабатывается в единицах символов, что может решить искаженные символы.
InputStreamReader reader = 
      new InputStreamReader(inputStream, StandardCharsets.UTF_8);

4 Концептуальное различие между набором символов и кодировкой символов

  • Связь между набором символов и кодировкой символов, набор символов — это спецификация, а кодировка символов — это конкретная реализация спецификации.;Набор символов определяет уникальное соответствие между символами и значениями двоичного кода, но не определяет конкретный метод хранения;
  • unicode, ASCII, GB2312, GBK — все это наборы символов;Среди них ASCII, GB2312 и GBK являются как наборами символов, так и кодировками; будьте осторожны, чтобы не перепутать разницу между ними.; А конкретная реализация юникода это UTF-8, UTF-16, UTF-32
  • Самый ранний код ASCII использовал один байт (8 бит) для указания отношения отображения между символами и двоичным кодом.Стандартный код ASCII определяет 128 символов, что достаточно для английского мира. Но как сопоставить другие текстовые символы, такие как китайский и японский? поэтому появились другие более крупные наборы символов
  • unicode (унифицированный набор символов), в первые дни он использовал 2 байта для представления 1 символа, а весь набор символов может содержать 65536 символов. Однако этого все еще недостаточно, поэтому он расширен до 4 байтов для представления символа, а текущий поддерживаемый диапазон составляет U+010000~U+10FFFF.
  • Утверждение, что unicode состоит из двух байтов, неверно;UTF-8 имеет переменную длину слова и должен храниться в 1~4 байтах; UTF-16 обычно состоит из двух байтов (диапазон U+0000~U+FFFF), если два байта не могут быть сохранены, тогда используйте 4 байта; UTF- 32 - фиксированные четыре байта
  • Символы, представленные Unicode, будут начинаться с «U+», за которыми следуют шестнадцатеричные числа, например, кодировка «word» — U+5B57.
  • Кодировка UTF-8 и набор символов Unicode
Сфера Unicode(Binary) Кодировка UTF-8 (двоичная) Длина байта кодировки UTF-8
U+0000~U+007F 00000000 00000000 00000000 0XXXXXXX 0XXXXXX 1
U+0080~U+07FF 00000000 00000000 00000YYY YYXXXXXX 110YYYYY 10XXXXXX 2
U+0800~U+FFFF 00000000 00000000 ZZZZYYYY YYXXXXXX 1110ZZZZ 10YYYYYY 10XXXXXX 3
U+010000~U+10FFFF 00000000 000AAAZZ ZZZZYYYY YYXXXXXX 11110AAA 10ZZZZZZ 10YYYYYY 10XXXXXX 4
  • Программа разделена на внутренний код и внешний код.Кодировка java по умолчанию - UTF-8, что фактически относится к внешнему коду;Внутренний код стремится использовать код фиксированной длины, в основе которого лежит принцип выравнивания памяти, что удобно для обработки. Иностранные коды, как правило, используют коды переменной длины, которые кодируют общие символы как короткие коды, а редкие символы — как длинные коды, экономя место для хранения и полосу пропускания передачи.
  • Строки JDK8 используют char[] для хранения символов, char имеет размер два байта и используется кодировка UTF-16 (внутренний код).Китайские символы, указанные в Unicode, находятся в U+0000~U+FFFF, поэтому использование char (кодировка UTF-16) для хранения китайского языка не будет искажено.
  • После JDK9,Строки хранятся с использованием массивов byte[], потому что некоторые символы нельзя хранить в char, например символы эмодзи., использование байтов для хранения строк проще расширять
  • JDK9, если все содержимое строки состоит из символов ISO-8859-1/Latin-1 (1 символ 1 байт), используйте кодировку ISO-8859-1/Latin-1 для хранения строки, в противном случае используйте хранилище кодировки UTF-16. массив (2 или 4 байта)
System.out.println(Charset.defaultCharset()); //输出java默认编码
for (byte item : "程序".getBytes(StandardCharsets.UTF_16)) {
    System.out.print("[" + item + "]");
}
System.out.println("");
for (byte item : "程序".getBytes(StandardCharsets.UTF_8)) {
    System.out.print("[" + item + "]");
}
----result----
UTF-8       //java默认编码UTF-8
[-2][-1][122][11][94][-113] //UTF_16:6个字节?
[-25][-88][-117][-27][-70][-113] //UTF_8:6个字节 正常
  • Кодировка UTF-16 "программы" на самом деле выводит 6 байт, что на два байта больше.Какова ситуация? Попробуйте еще один вывод символов
for (byte item : "程".getBytes(StandardCharsets.UTF_16)) {
    System.out.print("[" + item + "]");
}
---result--
[-2][-1][122][11]
  • Видно, что байты в кодировке UTF-16 — это еще два [-2][-1] байта, а шестнадцатеричное — 0xFEFF. в то время как он используется для определения порядка кодированияBig endianвсе ещеLittle endian. Возьмем, к примеру, символ '中', его шестнадцатеричный код Unicode — 4E2D. При сохранении 4E находится впереди, а 2D — сзади, что соответствует старшему порядку байтов, 2D — впереди, а 4E — сзади, что означает Little. порядок байтов FEFF означает, что хранилище использует Big endian, а FFFE означает использование Little endian.
  • Почему в UTF-8 нет проблем с порядком байтов?Личное мнение, т.к. UTF-8 переменная длина, то по 0, 110, 1110, 11110 заголовка первого байта определяется, нужны ли следующие байты для формирования символов, и легко читается и обрабатывается с помощью Big endian, с которым, в свою очередь, непросто справиться, поэтому он вынужден использовать Big endian
  • На самом деле я чувствую, что UTF-16 может принудительно использовать обратный порядок байтов, но это историческая проблема. . .

5 Краткое введение в концепцию URI

  • Теперь, когда есть java.io для управления потоком ресурсов, но для сетевых ресурсов, как его открыть и как найти? Ответ URI-URL
  • Полное имя URIUniform Resource IdentifierЕдиный идентификатор ресурса
  • С точки зрения непрофессионала, это строка, похожая на идентификационный номер, но она используется для идентификации ресурсов (таких как адреса электронной почты, имена хостов, файлы и т. д.).
  • URI имеют определенные правила:[scheme]:[scheme-specific-part][#fragment]
    • Дальнейшее подразделение может быть выражено как[scheme]:[//authority][/path][?query][#fragment], где конкретными частями шаблона являются полномочия, путь, запрос, а полномочия можно рассматривать как доменное имя, напримерwww.baidu.com
    • Завершающим подразделением является[scheme]:[//host:port][/path][?query][#fragment], это то же самое, что адресная ссылка, которую вы видите каждый день
  • Форма части, специфичной для схемы, зависит от схемы., а общий шаблон URI выглядит следующим образом
    • фтп: FTP-сервер
    • файл: файл на локальном диске
    • http: использовать протокол передачи гипертекста
    • mailto: адрес электронной почты
    • telnet: подключение к службам на основе Telnet
    • В Java также широко используются некоторые нестандартные режимы настройки, такие как rmi, jar, jndi, doc, jdbc и т. д.
  • В java URI абстрагируется как класс java.net.URI Ниже перечислены несколько распространенных методов построения.
//根据str生成URI
public URI(String str) throws URISyntaxException
public URI(String scheme, String authority,
       String path, String query, String fragment)throws URISyntaxException
public static URI create(String str) //调用 URI(String str)   
  • Общие методы работы JAVA.URI
public String getScheme()    //获取模式
public String getSchemeSpecificPart()//获取模式特定部分
public String getFragment()  //获取片段标识符
//以上三个方法是通用的
public String getAuthority() //授权机构,如www.baidu.com
public String getHost()      //获取主机部分,如127.0.0.1
public int getPort()         //如8080
public String getPath()      //定位路径
public String getQuery()     //查询条件

6 Понятие URL и отличие от URL

  • Полное имя URL-адресаUniform Resource Location, Унифицированный указатель ресурсов
  • URL — это подмножество URI, которое помимо идентификации ресурсов также предоставляет путь для поиска ресурсов;В библиотеке классов Java класс URI не содержит никаких методов для доступа к ресурсам, его единственная функция — разрешение, а класс URL может открывать поток для доступа к ресурсу.
  • URN (унифицированные имена ресурсов), которые принадлежат одному и тому же подмножеству URI,Идентифицирует только имя ресурса, не указывая, как найти ресурс;как:mailto:clswcl@gmail.comЭто своего рода URN Я знаю, что это почтовый ящик, но я не знаю, как его найти и найти.
  • Проще говоря, URN говорит вам, что есть место под названием Гуанчжоу, но не говорит, как туда добраться. Вы можете сесть на поезд или самолет; URL-адрес скажет вам отправиться в Гуанчжоу на самолете, а другой URL-адрес говорит ехать поездом
  • Общие правила синтаксиса для URL-адресов
协议://主机名:端口/路径?查询#片段
[protocol]:[//host:port][/path][?query][#fragment]
  • Метод построения URL, метод получения
//基于URL模式构造URL实例
public URL(String spec) throws MalformedURLException
//其中file相当于path、query和fragment三个部分组成
public URL(String protocol, String host, int port, String file) throws MalformedURLException

//根据类加载器获取URL
URL systemResource = ClassLoader.getSystemResource(String name)
Enumeration<URL> systemResources = ClassLoader.getSystemResources(String name)
URL resource = Main.class.getResource(String name)
Enumeration<URL> resources = Main.class.getClassLoader().getResources(String name)
  • Функция операции для получения данных ресурса через URL
public final InputStream openStream() throws java.io.IOException
public URLConnection openConnection() throws java.io.IOException
public final Object getContent() throws java.io.IOException

7 методов получения ресурсов Spring.Resource и Spring

  • Когда дело доходит до ресурсов, мы должны упомянуть, как Spring получает ресурсы.Существуют два наиболее часто используемых метода.
    • Получить ресурсы через подклассы интерфейса Resource
    • Получить ресурсы через подклассы интерфейса ResourceLoader
  • Функции манипулирования списком ресурсов Spring.Resource
//判断资源是否存在
boolean exists(); //
//返回当前资源对应的URL,不能解析则会抛出异常;如ByteArrayResource就不能解析为一个URL
URL getURL() throws IOException;
//返回当前资源对应的URI
URI getURI() throws IOException;
//返回当前资源对应的File
File getFile() throws IOException;
//返回对应的ReadableByteChannel
default ReadableByteChannel readableChannel() throws IOException
  • Познакомить с использованием подклассов, связанных с ресурсами.
  • 1 FileSystemResource: Получить ресурсы через файловую систему
Resource resource = new FileSystemResource("D:/example.txt");
File file= new File("example.txt");
Resource resource2 = new FileSystemResource(file);
  • 2 ByteArrayResource: получить ресурс, представленный массивом байтов.
    • Основанный на реализации ByteArrayInputStream и массива байтов, сценарий приложения аналогичен ByteArrayInputStream, кэшируя ресурсы byte[]
  • 3 ClassPathResource: получить ресурсы по пути к классам
//ClassPathResource.java 的三个属性
private final String path;
//使用Class或ClassLoader加载资源
private ClassLoader classLoader;
private Class<?> clazz;

---使用方式----
Resource resource = new ClassPathResource("test.txt");
  • 4 InputStreamResource: получить объект InputStream и получить ресурсы, инкапсулированные входным потоком.
  • 5 ServletContextResourse: загрузить ресурсы пути в среду ServletContext (относительно корневого каталога веб-приложения) и получить ресурсы
  • 6 UrlResource: доступ к ресурсам http и FTP через URL-адреса и т. д.

8 ResourceLoader для получения ресурсов

resource.png
resource.png
  • ResourceLoader предназначен для защиты конкретной реализации Resource и унификации способа получения ресурсов. Вы можете загрузить ClassPathResource из ResourceLoader, FileSystemResource и т. д.
public interface ResourceLoader {
  // 默认从类路径加载的资源 前缀: "classpath:",获取ClassPathResource
   String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
  Resource getResource(String location);
  • Интерфейс ResourceLoader по умолчанию загружает ресурсы по пути к классам.
public interface ResourcePatternResolver extends ResourceLoader {
  // 默认加载所有路径(包括jar包)下面的文件,"classpath*:", 获取ClassPathResource
  String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
  • ResourcePatternResolver загрузит все файлы по указанному пути по умолчанию, чтобы получить ClassPathResource;classpath: будет искать только путь к классам и classpath*: будет сканировать все пакеты JAR и файлы, которые появляются в пути к классам.
//Ant风格表达式  com/smart/**/*.xml 
ResourcePatternResoler resolver = new PathMatchingResourcePatternResolver();
Resource resources[] = resolver.getResources("com/smart/**/*.xml");

// ApplicationContext ctx 
//FileSystemResource资源
Resource template = ctx.getResource("file:///res.txt");
//UrlResource资源
Resource template = ctx.getResource("https://my.cn/res.txt");
  • LocationPattern метода getResource ResourceLoader может установить префикс шаблона ресурса для получения ресурсов, отличных от ClassPathResource.locationPattern поддерживает стиль Ant.
префикс Пример описывать
classpath: classpath:config.xml Загрузить из пути к классам
file: file:///res.txt Загрузить FileSystemResource из файловой системы
http: http://my.cn/res.txt Загрузить URL-ресурс

9 JAVA.Properties, которые нужно понять

  • Properties — это класс обработки конфигурации, который поставляется с java; два способа загрузки ресурсов свойствами
public class Properties extends Hashtable<Object,Object>{
    .... //可根据Reader或者InputStream加载properties文件内容
    public synchronized void load(Reader reader) throws IOException
    public synchronized void load(InputStream inStream) throws IOException
  • Код примера конфигурации чтения свойств
//res.properties
username = root
password = password
-------代码示例-------------
InputStream input = ClassLoader.getSystemResourceAsStream("res.properties");
Properties prop = new Properties();
prop.load(inputStream); //根据inputStream载入资源
String username = prop.getProperty("username");

10 Чтение ресурсов конфигурации yml

  • Обычные java-проекты могут импортировать jackson-dataformat-yaml, если им нужно читать yml, а конфигурация springboot по умолчанию поддерживает чтение yml
<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-yaml</artifactId>
  <version>2.9.5</version>
</dependency>
  • Чтение ресурсов конфигурации yml на основе jackson-dataformat-yaml
//res.yml 配置
name: chen
params:
  url:  http://www.my.com
  
----------代码示例---------------
InputStream input = ClassLoader.getSystemResourceAsStream("res.yml");
Yaml yml = new Yaml();
Map map = new Yaml().loadAs(input, LinkedHashMap.class);; //根据inputStream载入资源
String name = MapUtils.getString(map,"name"); // chen
//url:  http://www.my.com
String url = MapUtils.getString((Map)map.get("params"),"url")

11 Аккуратное закрытие ресурсов, синтаксис try-with-resource и lombok@Cleanup

  • Открытие ресурсов требует соответствующего закрытия, но мы часто забываем закрыть ресурсы или чувствуем себя загроможденными, закрывая ресурсы в нескольких кодах.Есть ли краткий метод закрытия?
  • Для автоматического закрытия классов ресурсов необходимо реализовать интерфейс AutoCloseable и использовать с сахаром синтаксиса try-with-resource.
public class YSOAPConnection implements AutoCloseable {
    private SOAPConnection connection;
    public static YSOAPConnection open(SOAPConnectionFactory soapConnectionFactory) throws SOAPException  {
        YSOAPConnection ySoapConnection = new YSOAPConnection();
        SOAPConnection connection = soapConnectionFactory.createConnection();
        ySoapConnection.setConnection(connection);
        return ySoapConnection;
    }
    public SOAPMessage call(SOAPMessage request, Object to) throws SOAPException {
        return connection.call(request, to); 
    }
    @Override
    public void close() throws SOAPException {
        if (connection != null) {  connection.close(); }
    }
}
//自动关闭的资源类使用示例
try (YSOAPConnection soapConnection=YSOAPConnection.open(soapConnectionFactory)){
    SOAPMessage soapResponse = soapConnection.call(request, endpoint);
    ...//数据操作
} catch (Exception e) {
    log.error(e.getMessage(), e);
    ...
}
  • аннотация lombok @Cleanup будет вызываться, когда закончится жизненный цикл объектаpublic void close();Объект должен реализовать интерфейс AutoCloseable
import lombok.Cleanup;
@Cleanup  // @Cleanup的使用
YSOAPConnection soapConnection=YSOAPConnection.open(soapConnectionFactory)

12 Если ресурс не закрыт, каков худший результат?

  • Собственный класс ресурсов JDK не закрыт и никогда не будет существовать. JVM автоматически закроет его с финализацией, например FileInputStream
//FileInputStream.java - JDK8
//jdk8的FileInputStream重写了finalize,保证对象回收前开启的资源被关闭
protected void finalize () throws IOException {
    if (guard != null) {
        guard.warnIfOpen();
    }
    if ((fd != null) && (fd != FileDescriptor.in)) {
        close();
    }
}
  • После JDK9 механизм finalize заменяется механизмом Cleaner; объекты, автоматически возвращаемые механизмом Cleaner, также должны реализовывать интерфейс AutoCloseable; Cleaner реализуется на основе PhantomReference; учащиеся, интересующиеся деталями реализации, могут обратиться к соответствующим документы.
  • Однако если используется механизм закрытия ресурсов, предоставляемый JDK, закрытие ресурсов будет отложено на долгое время по сравнению с закрытием вручную. Согласно тесту,Используйте try-with-resources, чтобы закрыть ресурс и позволить сборщику мусора собрать его за 12 наносекунд. При использовании механизма финализатора время увеличивается до 550 наносекунд.
  • Если ресурс не будет закрыт вовремя, он займет ресурс и повлияет на выполнение других потоков; например, файловый ресурс linux, максимальное количество файлов, которые может открыть процесс linux по умолчанию, составляет 1024 (некоторые 2048). , это значение настраивается); если поток содержит более дюжины файловых ресурсов, необходимо подождать 550 наносекунд, чтобы использовать механизм финализатора для освобождения ресурсов, а другие потоки в том же процессе будут ждать, пока цветы не исчезнут.

Добро пожаловать на ошибку в тексте

Обратите внимание на публичный номер и общайтесь вместе

Справочная статья