Java 输入与输出之 NIO【非阻塞式IO】【NIO核心原理】探索之【一】

news2024/11/15 8:43:38

Java标准的输入/输出(Input/Output,简称I/O)是Java程序与外部世界进行交互的重要机制,它允许程序读取和写入数据到各种类型的源,如文件、网络套接字、管道、内存缓冲区等。Java I/O API主要位于java.io包中,提供了丰富的类和接口来处理不同类型的输入输出操作。
Java 的 I/O 类库位于 java.io 包中,JDK 1.0 最初的Java IO只支持字节流(InputStream、OutputStream)和字符流(Reader、Writer)两种,属于阻塞式IO(BIO)模型。
Java标准的输入/输出(I/O)体系一些实现类的层次关系:
在这里插入图片描述

以下是一个简单的 Java I/O 示例,它展示了如何使用 FileInputStream 和 FileOutputStream 读取和写入文件:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
 
public class IOExample {
    public static void main(String[] args) {
        try {
            // 创建输入流以读取文件
            FileInputStream fis = new FileInputStream("input.txt");
            // 创建输出流以写入文件
            FileOutputStream fos = new FileOutputStream("output.txt");
 
            int content;
            // 读取并写入文件直到文件末尾
            while ((content = fis.read()) != -1) {
                fos.write(content);
            }
 
            // 关闭流
            fis.close();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

一、Java I/O发展史
JDK 1.0 最初的Java IO只支持字节流(InputStream、OutputStream)和字符流(Reader、Writer)两种,属于阻塞式IO(BIO)模型。
JDK 1.4 引入了一套全新的IO处理机制,引入了缓存区(Buffer)、通道(Channel)等概念,与之前的标准IO(BIO)相比,NIO具有更高的可扩展性和灵活性,特别是在网络编程和高并发场景下,表现得更为出色。这是非阻塞式IO(NIO)模式。提供了更强大的文件处理功能和更高效的IO操作,如内存映射文件等的功能。Java NIO(New I/O)是一种高性能的I/O处理机制,它提供了对标准Java I/O API的替代方案,以支持更高效的文件和网络数据传输。
在JDK 1.7 版本中对NIO进行了完善,推出了NIO.2,也称为AIO(异步IO),在处理大量并发请求时具有优势,特别是在网络编程和高并发场景下,表现得更为出色。
在这里插入图片描述
IO和NIO的区别
Java IO和NIO的主要区别在于两者的处理方式不同。Java IO是面向流(Stream)的,它将输入输出数据直接传输到目标设备或文件中,以流的形式进行读写;而NIO则是面向缓冲区(Buffer)的,它将会使用缓存去管理数据,使得读写操作更加快速和灵活。
特别是在网络编程和高并发场景下,Java NIO表现得更为出色。Java IO在进行网络通信时,每个客户端连接都需要创建一个线程来进行处理,这样会导致系统资源的浪费。Java NIO则只需要一个线程就可以完成对多个客户端连接的处理,大大减少系统资源的占用。
在这里插入图片描述
二、NIO核心原理
主要包括:缓冲区(Buffer)、通道(Channel)和选择器(Selector)、字符集(Charset);首先获取用于连接IO设备的通道channel以及用于容纳数据的缓冲区,利用选择器Selector监控多个Channel的IO状况(多路复用),然后操作缓冲区,对数据进行处理。 NIO基于Channel和Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道,即一个单独的线程现在可以管理多个输入和输出通道。

  1. 缓冲区Buffer
    缓冲区是Java NIO中一个非常重要的概念,所有数据都是通过缓冲区对象进行传输的。缓冲区是一段连续的内存块,用于保存读写的数据。缓冲区对象包含了一些状态变量,例如容量(capacity)、限制(limit)、位置(position)等,用于控制数据的读写。
    缓冲区可以在内存中创建,并可以通过通道(Channel)进行读写操作,也可以作为参数传递给其他方法。缓冲区在java NIO中负责数据的存取,底层缓冲区其实就是数组,用于存储不同数据类型的数据,根据不同的数据类型(Boolean除外),提供了相应类型的缓冲区:ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、CharBuffer。

缓冲区的四个核心属性:
capacity:容量,表示缓冲区的最大容量,声明后不能改变
position:表示缓冲区中正在操作数据的位置。当前位置,下一个要被读取或写入的位置;
limit:界限,缓冲区中可以操作数据的大小,表示可以读写的元素数量;
mark:标志,可以让缓冲区记住一个position或limit的值。可以通过调用reset()恢复到mark的位置。
缓冲区的读写操作都会修改position和limit属性,例如在从缓冲区中读取数据时,position属性会自动向后移动,而limit属性则不会更改,因此读取操作只能读取到limit位置之前的数据。

四者的关系:0<mark<=position<=limit<=capacity
缓冲区的三个核心操作方法:
put():存数据到缓存区,写数据模式。
flip():切换到读数据模式(position和limit改变,capacity不变)
get():从缓冲区中读取数据。

通过:static ByteBuffe allocate(int capacity)创建指定大小的缓冲区,在JVM内存中创建,在每次调用基础操作系统的一个本机IO之前或者之后,虚拟机都会将缓冲区的内容复制到中间缓冲区(或者从中间缓冲区复制内容),缓冲区的内容驻留在JVM内,因此销毁容易,但是占用JJVM内存开销,处理过程中有复杂的操作。

  1. 通道Channel
    通道(Channel)是Java NIO的核心概念,是网络或文件IO操作的抽象,它表示一个数据通讯的连接,这个连接可以连接到 I/O 设备(例如:磁盘文件,Socket)或者一个支持 I/O 访问的应用程序。在java NIO中Channel本身不负责存储数据,通道可以和缓冲区一起使用,让数据直接在缓冲区之间进行传输。

在这里插入图片描述

通道类似于标准IO中的输入输出流,但通道更加灵活和高效。与输入输出流不同的是,通道可以使用Selector选择器实现非阻塞IO操作,并且可以同时进行读写操作。

通道的主要实现类:
FileChannel:用于文件读写操作;
DatagramChannel:用于UDP协议的网络通信;
SocketChannel:用于TCP协议的网络通信;
ServerSocketChannel:用于监听TCP连接请求。

通道的获取方式
java针对支持通道的类提供了getChannel()方法。
支持通道的类如下:
(一)提供本地文件IO的Channel类有:
FileInputStream
FileOutputStream
RandomAccessFile
(二)提供网络套接字IO的Channel类:
DatagramSocket
Socket
ServerSocket
(三)获取通道的其他方式:
在JDK7.0中的AIO针对各个通道提供静态方法open()可打开并返回指定通道;
在JDK7.0中的AIO的Files类可使用Files类的静态方法newByteChannel()获取字节通道。

在使用NIO进行网络编程时,我们常常使用SocketChannel和ServerSocketChannel来实现客户端与服务器之间的通信。使用FileChannel可以完成对本地文件的读写操作,使用DatagramChannel可以发送和接收UDP协议的数据包。

我们来看一个从文件读数据的例程,其中方法fileReadBIO()是用BIO的输入流方式读文件信息;方法fileReadNIO()使用NIO的FileChannel和字节缓冲区ByteBuffer来读文件信息。

package nio;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileReadTest {
    public static void fileReadBIO(){
        InputStream in = null;
        System.out.println("BIO模式读文件测试***");
        try{
        	String path = "D:/temp/TestBIO.txt";
            in = new BufferedInputStream(new FileInputStream(path));
 
            byte [] buf = new byte[1024];
            int bytesRead = in.read(buf);
            while(bytesRead != -1)
            {
                for(int i=0;i<bytesRead;i++)
                    System.out.print((char)buf[i]);
                bytesRead = in.read(buf);
            }
        }catch (IOException e)
        {
            e.printStackTrace();
        }finally{
            try{
                if(in != null){
                    in.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
    
    public static void fileReadNIO(){
    	System.out.println("NIO模式读文件测试***");
        RandomAccessFile aFile = null;
        try{
            aFile = new RandomAccessFile("D:/temp/TestNIO.txt","rw");
            FileChannel fileChannel = aFile.getChannel();
            ByteBuffer buf = ByteBuffer.allocate(1024);
 
            int bytesRead = fileChannel.read(buf);
            System.out.println(bytesRead);
            while(bytesRead != -1)
            {
                buf.flip();
                while(buf.hasRemaining())
                {
                    System.out.print((char)buf.get());
                }
 
                buf.compact();
                bytesRead = fileChannel.read(buf);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally{
            try{
                if(aFile != null) aFile.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
    	fileReadBIO();
    	System.out.println();
    	fileReadNIO();
	}
    
}

  1. 选择器Selector和选择键SelectionKey
    选择器(Selector)和选择键(SelectionKey)是Java NIO提供的另外两个核心组件。选择器用于检测通道的状态,并且可以根据通道状态进行非阻塞选择操作。而选择键则是一种将通道和选择器进行关联的机制。

使用选择器可以实现单线程管理多个通道的方式,以此实现高并发IO操作。在选择器的模型中,每个通道都会注册到一个选择器上,并且每个通道都有一个其唯一的选择键对象来代表这个通道。选择键对象包含几个标志位,表示通道的当前状态等信息。

选择器可以监听多个通道的事件,例如连接就绪、读取数据就绪、写入数据就绪等等。当有一个或多个通道的事件就绪时,选择器就会自动返回这些通道的选择键,我们可以通过选择键获取到对应的通道,然后进行相应的操作。

选择器是Java NIO中的一个重要组件,它可以用于同时监控多个通道的读写事件,并在有事件发生时立即做出响应。选择器可以实现单线程监听多个通道的效果,从而提高系统吞吐量和运行效率。

  1. 字符集Charset(编码解码)
    编码(按指定Charset编码方案编码)
    字符串转成字节数组
    解码(按指定Charset编码方案解码)
    字节数组转成字符串

请看一个编码和解码的演示例程:

package nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Map;
import java.util.Set;

/**
 * 字符集(Charset)
 * 编码:字符串-->字节数组
 * 解码:字节数组-->字符串
 */

public class CharsetDemo {
	static String infStr = "绿水青山就是金山银山!";
    public static void charSetEncoderAndDecoder() throws CharacterCodingException {
        Charset charset=Charset.forName("UTF-8");
        //1.获取编码器
        CharsetEncoder charsetEncoder=charset.newEncoder();
        //2.获取解码器
        CharsetDecoder charsetDecoder=charset.newDecoder();

        //3.获取需要解码编码的数据
        CharBuffer charBuffer=CharBuffer.allocate(1024);
        charBuffer.put(infStr);
        charBuffer.flip();

        //4.编码
        ByteBuffer byteBuffer=charsetEncoder.encode(charBuffer);
        System.out.println("编码后:");
        for (int i=0;i<byteBuffer.limit();i++) {
            System.out.println(byteBuffer.get());
        }
        //5.解码
        byteBuffer.flip();
        CharBuffer charBuffer1=charsetDecoder.decode(byteBuffer);
        System.out.println("\n解码后:");
        System.out.println(charBuffer1.toString());
        System.out.println("\n使用不正确的编码格式解码,解码结果:");
        Charset charset1=Charset.forName("GBK");
        byteBuffer.flip();
        CharBuffer charBuffer2 =charset1.decode(byteBuffer);
        System.out.println(charBuffer2.toString());
    }
    /***查询系统可用的字符编码***/
    public static void getAvailableCharsets() {
        //6.获取Charset所支持的字符编码
        System.out.println("\n系统可用的字符编码:");
         Map<String ,Charset> map= Charset.availableCharsets();
         Set<Map.Entry<String,Charset>>  set=map.entrySet();
        for (Map.Entry<String,Charset> entry: set
             ) {
            System.out.println(entry.getKey()+"="+entry.getValue().toString());
        }
    }
    
    public static void main(String[] args) throws IOException {
    	/****字符集编码和解码演示****/
    	charSetEncoderAndDecoder();
    	/***查询系统可用的字符编码***/
    	getAvailableCharsets();
	}
}

下面我们对通道的主要实现类来进行一下介绍:

  1. FileChannel:用于文件读写操作;

文件通道FileChannel是用于读取,写入,文件的通道。FileChannel只能被InputStream、OutputStream、RandomAccessFile所创建。
使用fileChannel.transferTo()可极大提高文件的复制效率,为进行大容量文件的读和写,直接把读通道和写通道建立了连接,还能有效避免因文件过大而导致内存溢出。

FileChannel的常用方法:
int read(ByteBuffer dst) 从Channel当中读取数据至ByteBuffer
long read(ByteBuffer[] dsts)将channel当中的数据“分散”至ByteBuffer[]
int write(Bytesuffer src)将ByteBuffer当中的数据写入到Channel
long write(ByteBuffer[] srcs)将Bytesuffer[]当中的数据“聚集”到Channel
long position()返回此通道的文件位置
FileChannel position(long p)设置此通道的文件位置
long size()返回此通道的文件的当前大小
FileChannel truncate(long s)将此通道的文件截取为给定大小
void force(boolean metaData)强制将所有对此通道的文件更新写入到存储设备中

这里我们提供一个文件读、写和拷贝复制的例程:

package nio;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelTest {
    public static void readFile(){ //读文件例程
        try {
            //1.定义一个文件字节输入流与源文件接通
            FileInputStream fos = new FileInputStream(new File("D:/temp/test01.txt"));
            //2.获取文件字节输入流的文件通道
            FileChannel channel = fos.getChannel();
            //3.定义一个缓存区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            int bytesRead = channel.read(buf); //4.读取数据到缓存区
            String str = null;
            while(bytesRead != -1) {
                //5、切换
                buf.flip();
                //6.读取缓存区中的数据并输出即可
                str = new String(buf.array(), 0, buf.remaining());
                System.out.println("读取内容..." + str);
                bytesRead = channel.read(buf); //4.读取数据到缓存区
            }
            channel.close();    
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void writeFile(){ //写文件例程
        try {
            //1.字节输出流通向目标文件
            FileOutputStream fos = new FileOutputStream(new File("D:/temp/test01.txt"));
            //2.得到字节输出流对应的通道Channel
            FileChannel channel = fos.getChannel();
            //3.分配缓存区
            ByteBuffer bf = ByteBuffer.allocate(1024);
            bf.put("最近公司有个需求,就是上传产品详情图。" .getBytes());
            //4.把缓存区切换为写模式
            bf.flip();
            //5.输出数据到文件
            channel.write(bf);
            channel.close();
            System.out.println("完成数据写入....");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void copyFile(){ //复制文件例程
        try {
            long starTime = System.currentTimeMillis();
            //1、创建输入文件流
            FileInputStream fis = new FileInputStream(new File("D:/temp/test01.txt"));
            //2、得到输入channel
            FileChannel fisChannel = fis.getChannel();
            //3、创建输出文件流
            FileOutputStream fos = new FileOutputStream(new File("D:/temp/test02.txt"));
            //4、得到输出channel
            FileChannel fosChannel = fos.getChannel();
            //5、使用输入channel将文件转到fosChannel
            fisChannel.transferTo(0, fisChannel.size(), fosChannel);
            fis.close();
            fos.close();
            fisChannel.close();
            fosChannel.close();
            long endTime = System.currentTimeMillis();
            System.out.println("耗时=" + (endTime - starTime) + "ms");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

	public static void main(String[] args) throws IOException {
		writeFile(); 
		System.out.println("从文件读信息:");
		readFile();
	}
}

例程说明:
方法readFile()演示如何从文件读入数据信息;
方法writeFile()演示如何把数据信息写入磁盘文件中。
方法copyFile()则是一个文件复制演示程序。
由于例程中没有显式指定数据信息的字符集编码方案,如果读入一个其他文本编辑器编辑的文本文件,显示出来也可能是乱码的。
因此,本例程中最后测试时,先调用writeFile(); 然后再调用readFile();,这样可确保读写测试都使用默认的字符集编码方案。由于写入的文本信息太少,第一次无法测试到循环读信息的效果。可在第一次测试后,再用文本编辑器随意增加足够文本信息,再进行测试,才可测试到循环读信息。

缓冲区Buffer的使用说明:
Buffer顾名思义:缓冲区,实际上是一个容器,一个连续数组。通道Channel提供从文件、网络读取数据的渠道,但是读写的数据都必须在缓冲区Buffer进行缓存。
可以把Buffer简单地理解为一组基本数据类型的元素列表,它通过几个变量来保存这个数据的当前位置状态:capacity, position, limit, mark:

在这里插入图片描述
我们以“写文件例程”方法writeFile()为例来进行说明。我们来分析这几行源代码:

            //3.分配缓存区
            ByteBuffer bf = ByteBuffer.allocate(1024);
            bf.put("最近公司有个需求,就是上传产品详情图。" .getBytes());
            //4.把缓存区切换为写模式
            bf.flip();
            //5.输出数据到文件
            channel.write(bf);
            channel.close();

//3.分配缓存区
ByteBuffer bf = ByteBuffer.allocate(1024);
当执行完上面这行代码后,程序分配了缓冲区Buffer,此时Buffer的初始状态如下图,position的位置为0,capacity和limit默认都是数组长度。
在这里插入图片描述
bf.put(“最近公司有个需求,就是上传产品详情图。” .getBytes());
执行完上面这行代码,缓冲区Buffer写入了数据后,position的位置移到写入数据的后面,缓冲区的状态如下:
在这里插入图片描述
//4.把缓冲区切换为写模式
bf.flip();
执行完上面这行代码flip()后,缓冲区的position的位置移到0,limit则移到了原来position的位置。
在这里插入图片描述
在下一次再往Buffer写数据之前我们再调用clear()方法,缓冲区的索引位置又回到了初始位置。
调用clear()方法:position将被设回0,limit设置成capacity,换句话说,Buffer被清空了,其实Buffer中的数据并未被清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”。
如果Buffer中仍有未读的数据,且后续还需要这些数据,那么可使用compact()方法,其功能有点像磁盘碎片整理的作用。compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。
通过调用Buffer.mark()方法,可以标记Buffer中的一个特定的position,之后可以通过调用Buffer.reset()方法恢复到这个position。
Buffer.rewind()方法将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素。

关于NIO网络通讯编程的应用请参见博客:
Java 输入与输出之 NIO【非阻塞式IO】【NIO网络编程】探索之【二】

参考文献&博客:

  1. 参考文献之一
    攻破JAVA NIO技术壁垒
  2. 参考文献之二
    Java NIO全面详解(看这篇就够了)
  3. 参考文献之三
    Java NIO详解
  4. 参考文献之四
    Java FileChannel文件的读写实例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2078589.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SqlHelper 使用EF-Core框架 连接池处理并发

定义数据库 数据库名称&#xff1a;T_dicomPatientMsg 注意5大约束条件&#xff1a; 1.主键约束&#xff1a;primary key IDKEY设置为主键&#xff0c;主键设置自增长 2.唯一性约束&#xff1a;unique 3.默认约束&#xff1a;default 所有值都要设置默认值&#xff0c…

Unraid 手动安装docker

目录 常用镜像链接一.安装示例1[firefox浏览器]:1.离线下载docker镜像2.将xxx.tar镜像数据加载到 Docker 中3.手动添加docker 二.安装示例2[等我有东西需要安装再回来补教程吧]:三.获取UDI和GID 常用镜像链接 特别版 emby 文件管理器 filebrowser内外穿透 zerotierNAS媒体库管…

Python和Pycharm安装

有需要的私聊我吧&#xff01;&#xff01;&#xff01;

伺服电机最佳速度范围是多少?

伺服电机的最佳速度范围取决于多种因素&#xff0c;包括电机的规格、负载类型、控制要求和应用环境等。一般来说&#xff0c;伺服电机的最佳速度范围是其额定转速的70%到100%之间。这一范围内&#xff0c;电机能够提供最佳的效率、精度和响应速度。 关键因素影响伺服电机速度范…

从B端工程师到AI绘画工程师:我的转行之路与实战指南

一、背景&#xff1a;B端工程师的迷茫与探索 大家好&#xff0c;我是一名有着五年B端服务经验的软件工程师。在长期的B端工作中&#xff0c;我逐渐感到自己的技术栈和视野受限&#xff0c;对未来的职业发展产生了迷茫。在一次偶然的机会中&#xff0c;我接触到了AI绘画这一领域…

nacos 安装

1. 环境准备 使用此快速开始方法进行Nacos安装及部署&#xff0c;需要安装Docker和Docker Compose。 如何下载不下来&#xff0c;可换镜像加速地址 vi /etc/docker/daemon.json {"registry-mirrors": ["https://docker.registry.cyou"] }可用镜像加速地…

合宙Air700EAQ硬件设计手册——应用接口2

Air700EAQ是一款基于移芯EC716E平台设计的LTE Cat 1无线通信模组。 支持亚洲FDD-LTE的4G远距离无线传 输技术。 以极小封装&#xff0c;极高性价比&#xff0c;满足IoT行业的数传应用需求。 例如共享应用场景&#xff0c;定位器场景&#xff0c;DTU数 传场景等。 在上文我们…

E82EV752K4C变频器可议价

E82EV752K4C变频器可议价 E82EV752K4C变频器可议价 E82EV752K4C变频器可议价 E82EV752K4C变频器参数表 E82EV752K4C变频器引脚图 E82EV752K4C变频器线路图 E82EV752K4C变频器节能主要表现在风机、水泵的应用上。风机、泵类负载采用变频调速后&#xff0c;节电率为20%&…

基于Java+SpringBoot+Vue的知识管理系统

基于JavaSpringBootVue的知识管理系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 哈喽…

AI驱动的Web3革命:如何通过智能技术实现去中心化

在当今的数字世界中&#xff0c;人工智能&#xff08;AI&#xff09;和Web3分别代表了智能技术和去中心化网络的两大潮流。它们的结合不仅展示了科技的无限潜力&#xff0c;还预示着我们即将迎来一个全新的互联网时代。本文将探讨AI如何驱动Web3革命&#xff0c;并实现更加智能…

RFID光触发标签在零售行业的深度应用

零售行业作为现代经济的重要组成部分&#xff0c;面临着诸多挑战和竞争压力&#xff0c;消费者需求的多样化、快速变化的市场趋势以及日益复杂的供应链管理&#xff0c;都对零售商提出了更高的要求&#xff0c;在这样的背景下&#xff0c;寻求创新的技术解决方案以提高运营效率…

CRMEB 多店版移动端商家管理功能-工作台

一、功能说明 工作台页面可查看当日总销售额&#xff0c;当日订单数、支付人数以及当日浏览量。快捷进如待处理事项页面&#xff0c;包含商家管理所有功能页面入口。 二、操作流程 1、查看销售额 商家可查看当日总销售额。当日订单数&#xff0c;支付人数以及当日浏览量。点…

TikTok达人的社群经济新生态

在数字化时代&#xff0c;TikTok达人带货不仅催生了新的社群经济生态&#xff0c;还实现了品牌、达人与消费者之间的共创、共享与共赢。本文Nox聚星将和大家探讨TikTok达人带货如何催生新的社群经济生态&#xff0c;并分析其对品牌成长和消费者满意度的推动作用。 1. 共创&…

PHP多门店民宿酒店预订系统小程序源码

&#x1f3e8;✨「多门店酒店民宿预订系统」——一键解锁全球住宿新体验&#xff01;&#x1f30d;&#x1f3e0; &#x1f31f; 开篇种草&#xff1a;旅行新伙伴&#xff0c;预订无忧&#xff01; 嘿小伙伴们&#xff0c;是不是每次计划旅行都被繁琐的酒店民宿预订搞得头大&…

东方晶源即将亮相IDAS 2024设计自动化产业峰会!

第二届设计自动化产业峰会IDAS 2024&#xff08;Intelligent Design Automation Summit 2024&#xff09;将于2024年9月23日-24日在上海张江科学会堂隆重举行。 东方晶源微电子科技&#xff08;北京&#xff09;股份有限公司将亮相峰会&#xff01;期待与您相聚&#xff0c;与全…

Coze智能体:最长用的5类插件工具集

Coze智能体&#xff1a;最长用的5类插件工具集 前言搜索类 1.必应搜索2. 必应图片搜索3. 头条搜索4. 获取头条新闻5. 抖音视频搜索6. 百度搜索7. 微信搜索8. 知乎热榜搜索工具类 1.中文文本转语音2. 英文文本转语音3. 语音转文字&#xff1a;4. 代码执行器文档类 1. 链接读取2.…

市场风向标美元承压,日元与商品货币走强

美元承压&#xff0c;空头趋势或加速 随着美联储政策前景的逐步明朗&#xff0c;资产管理公司正积极调整策略&#xff0c;预计将在鲍威尔会议后进一步增加美元净空头头寸。COT报告显示&#xff0c;美元净多头已降至六个月低位&#xff0c;显示市场对美元信心减弱。美元指数逼…

每日OJ_牛客_客似云来(简单斐波那契)

目录 牛客_客似云来&#xff08;简单斐波那契&#xff09; 解析代码 牛客_客似云来&#xff08;简单斐波那契&#xff09; 客似云来__牛客网 解析代码 老样子&#xff0c;先准备好斐波那契的数组&#xff0c;然后遍历那一段数组&#xff0c;求出他们的和即可。而第80项斐波那…

使用cephadm工具在ubuntu2004系统中安装ceph 16.2.7指定版本单点集群

文章目录 前言一、环境版本二、准备基础环境1.时间同步、防火墙服务、时区校正2.安装docker服务3.安装lvm服务 三、准备部署工作1.获取cephadm文件2.下载所需镜像 四、进行部署1.开始部署2.建立cephfs 总结 前言 接到开发同事的需求&#xff0c;在物理机上准备一个和甲方一样版…