Android App开发实战项目之电子书架的实现(附源码 简单易懂 可直接使用)

news2024/10/5 17:25:46

需要图片集和源码请点赞关注收藏后评论区留言~~~

一、需求描述

在手机上浏览电子书的浏览体验跟阅读纸质书差不多,翻页过程仍旧呈现纸张翻转的视觉特效,让读者看起来赏心悦目。总结一下,手机阅读无非是要具有两大功能点:其一为书架管理,主要是书籍的增删改查,其二为浏览操作,主要是翻页过程的处理

二、功能分析

电子书有几个问题,一方面是电子书格式多样,另一方面是Android没有现成的控件可以统一显示这些电子书,格式各异的电子书要在手机屏幕的方寸之间展示,十分困难。

对于前一个问题。可将电子书统一成少数几种公共格式以便降低编码难度,对于后一个问题,可将电子书的每个页面都转成图片文件,然后利用图像视图浏览电子书。

接下来的实战项目中,暂且只支持两种格式的电子书,分别是PDF,一种与平台无关的电子文件和DJVU。

接下来分析电子书阅读器中用到的一些技术

资产管理器AssetManager 初始的五本演示电子书

数据库框架Room 每本电子书的图书名称 作者 页数统一保存到数据库中

PDF文件渲染器 把PDF文件解析为一组图片

贝塞尔曲线 在浏览电子书的翻页过程中 利用它实现翻页特效

JNI接口

图片文件处理

输入对话框

 下面简单介绍一下主要代码模块之间关系

EbookReaderActivity 电子书阅读器的书籍列表页面

PdfRenderActiviity  PDF电子书的阅读页面

PdfSlideActivity PDF电子书的平滑翻页

PdfCurveActivity 贝塞尔曲线翻页

PdfOpenglActivity 卷曲翻页

DvjuRenderActivity DJVU电子书的阅读页面

ImageFragment 每页电子书图片的展示碎片

三、效果展示

效果展示如下 既有普通的平滑翻页 也有贝塞尔曲线和卷曲翻页 看起来更加逼真和赏心悦目

 

 四、代码

Ebook类

package com.example.ebook;

import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import com.example.ebook.adapter.BookListAdapter;
import com.example.ebook.dao.BookDao;
import com.example.ebook.entity.BookInfo;
import com.example.ebook.util.AssetsUtil;
import com.example.ebook.widget.InputDialog;

import java.util.ArrayList;
import java.util.List;

public class EbookReaderActivity extends AppCompatActivity implements
        AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {
    private static final String TAG = "EbookReaderActivity";
    private ListView lv_ebook; // 声明一个用于展示书籍列表的列表视图对象
    private String[] mFileNameArray = {"tangshi.pdf", "android.pdf", "zhugeliang.djvu", "dufu.djvu", "luyou.djvu"};
    private List<BookInfo> mBookList = new ArrayList<>(); // 书籍信息列表
    private BookListAdapter mAdapter; // 声明一个书籍列表的适配器对象
    private BookDao bookDao; // 声明一个书籍的持久化对象

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ebook_reader);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 保持屏幕常亮
        initView(); // 初始化视图
        new Thread(() -> copyPdfFile()).start(); // 启动演示文件的复制线程
    }

    // 初始化视图
    private void initView() {
        Toolbar tl_head = findViewById(R.id.tl_head);
        tl_head.setTitle("电子书架");
        setSupportActionBar(tl_head); // 替换系统自带的ActionBar
        // 设置工具栏左侧导航图标的点击监听器
        tl_head.setNavigationOnClickListener(view -> finish());
        lv_ebook = findViewById(R.id.lv_ebook);
    }

    // 把assets目录下的演示文件复制到存储卡
    private void copyPdfFile() {
        // 从App实例中获取唯一的书籍持久化对象
        bookDao = MainApplication.getInstance().getBookDB().bookDao();
        mBookList = bookDao.queryAllBook(); // 获取所有书籍记录
        if (mBookList!=null && mBookList.size()>0) {
            runOnUiThread(() -> initBookList()); // 初始化书籍列表
            return;
        }
        List<BookInfo> bookList = new ArrayList<>();
        for (String file_name : mFileNameArray) {
            String dir = String.format("%s/%s/",
                    getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(),
                    file_name.substring(file_name.lastIndexOf(".")+1)
            );
            String fileName = file_name.substring(file_name.lastIndexOf("/") + 1);
            // 把资产目录下的电子书复制到存储卡
            AssetsUtil.Assets2Sd(this, fileName, dir + fileName);
            bookList.add(new BookInfo(file_name));
        }
        bookDao.insertBookList(bookList); // 把演示用的电子书信息添加到数据库
        runOnUiThread(() -> initBookList()); // 初始化书籍列表
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        initBookList(); // 初始化书籍列表
    }

    // 初始化书籍列表
    private void initBookList() {
        mBookList = bookDao.queryAllBook(); // 获取所有书籍记录
        // 下面把书籍列表通过ListView展现出来
        mAdapter = new BookListAdapter(this, mBookList);
        lv_ebook.setAdapter(mAdapter);
        lv_ebook.setOnItemClickListener(this);
        lv_ebook.setOnItemLongClickListener(this);
    }

    // 在点击书籍记录时触发
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        String file_name = mBookList.get(position).getFileName();
        String title = mBookList.get(position).getTitle();
        Log.d(TAG, "file_name="+file_name+", title="+title);
        if (file_name.endsWith(".pdf")) { // PDF格式
            // 跳转到PDF阅读界面
            startReader(file_name, title, PdfRenderActivity.class);
        } else if (file_name.endsWith(".djvu")) { // DJVU格式
            // 跳转到第三方Vudroid提供的阅读界面
            startReader(file_name, title, DjvuRenderActivity.class);
        } else {
            Toast.makeText(this, "暂不支持该格式的电子书", Toast.LENGTH_SHORT).show();
        }
    }

    // 在长按书籍记录时触发
    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
        // 以下创建并弹出标题填写对话框
        InputDialog dialog = new InputDialog(this, mBookList.get(position).getFileName(),
                position, "请输入书籍名称", (idt, content, seq) -> {
            BookInfo book = mBookList.get(seq);
            book.setTitle(content);
            bookDao.updateBook(book); // 更新数据库中该书籍记录的标题
        });
        dialog.show();
        return true;
    }

    // 启动指定的电子书阅读界面
    private void startReader(String file_name, String title, Class<?> cls) {
        Intent intent = new Intent(this, cls);
        intent.putExtra("file_name", file_name);
        intent.putExtra("title", title);
        startActivity(intent);
    }

}

PDF类

package com.example.ebook;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.ViewGroup;

import com.example.ebook.util.AssetsUtil;
import com.example.ebook.util.Utils;
import com.example.ebook.widget.CurveView;

import java.util.ArrayList;
import java.util.List;

public class PdfCurveActivity extends AppCompatActivity {
    private final static String TAG = "PdfCurveActivity";
    private CurveView cv_book; // 声明一个卷曲视图对象
    private List<String> mPathList = new ArrayList<>(); // 图片路径列表
    private String mFileName = "tangshi.pdf"; // 演示文件的名称

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pdf_curve);
        initView(); // 初始化视图
        // 加载pdf会花一点点时间,这里先让整个界面出来,再慢慢渲染pdf
        new Handler(Looper.myLooper()).post(() -> renderPDF());
    }

    // 初始化视图
    private void initView() {
        String title = "";
        // 从前个页面传来的数据中获取书籍的标题和文件名称
        if (getIntent().getExtras()!=null && !getIntent().getExtras().isEmpty()) {
            title = getIntent().getStringExtra("title");
            mFileName = getIntent().getStringExtra("file_name");
        }
        Toolbar tl_head = findViewById(R.id.tl_head);
        tl_head.setTitle(!TextUtils.isEmpty(title) ? title : mFileName);

        // 设置工具栏左侧导航图标的点击监听器
        tl_head.setNavigationOnClickListener(view -> finish());
        cv_book = findViewById(R.id.cv_book);
        findViewById(R.id.btn_resume).setOnClickListener(v -> cv_book.reset());
    }

    // 开始渲染PDF文件
    private void renderPDF() {
        // 把资产文件转换为图片路径列表
        mPathList = AssetsUtil.getPathListFromPdf(this, mFileName);
        Log.d(TAG, "mPathList.size="+mPathList.size());
        Bitmap first = BitmapFactory.decodeFile(mPathList.get(0));
        int height = (int)(1.0*first.getHeight()/first.getWidth() * Utils.getScreenWidth(this));
        Log.d(TAG, "height="+height);
        ViewGroup.LayoutParams params = cv_book.getLayoutParams();
        params.height = height; // 根据书页图片的尺寸调整卷曲视图的高度
        cv_book.setLayoutParams(params); // 设置卷曲视图的布局参数
        cv_book.setFilePath(mPathList); // 设置卷曲视图的文件路径
    }

}

卷曲翻页类

package com.example.ebook;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.pdf.PdfRenderer;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import com.example.ebook.util.AssetsUtil;
import com.example.ebook.util.BitmapUtil;
import com.example.ebook.util.Utils;

import java.io.File;
import java.util.ArrayList;

import fi.harism.curl.CurlPage;
import fi.harism.curl.CurlView;

public class PdfOpenglActivity extends AppCompatActivity {
    private final static String TAG = "PdfOpenglActivity";
    private CurlView cv_content; // 声明一个卷曲视图对象
    private ArrayList<String> mImgList = new ArrayList<>(); // 图片路径列表
    private String mFileName = "tangshi.pdf"; // 演示文件的名称

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pdf_opengl);
        initView(); // 初始化视图
        // 加载pdf会花一点点时间,这里先让整个界面出来,再慢慢渲染pdf
        new Handler(Looper.myLooper()).post(() -> renderPDF());
    }

    // 初始化视图
    private void initView() {
        String title = "";
        // 从前个页面传来的数据中获取书籍的标题和文件名称
        if (getIntent().getExtras()!=null && !getIntent().getExtras().isEmpty()) {
            title = getIntent().getStringExtra("title");
            mFileName = getIntent().getStringExtra("file_name");
        }
        Toolbar tl_head = findViewById(R.id.tl_head);
        tl_head.setTitle(!TextUtils.isEmpty(title) ? title : mFileName);

        // 设置工具栏左侧导航图标的点击监听器
        tl_head.setNavigationOnClickListener(view -> finish());
        cv_content = findViewById(R.id.cv_content);
    }

    // 开始渲染PDF文件
    private void renderPDF() {
        String dir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/pdf/";
        String filePath = dir + mFileName;
        // 无法直接从asset目录读取PDF文件,只能先把PDF文件复制到存储卡,再从存储卡读取PDF
        AssetsUtil.Assets2Sd(this, mFileName, filePath);
        try {
            // 打开存储卡里指定路径的PDF文件
            ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
                    new File(filePath), ParcelFileDescriptor.MODE_READ_ONLY);
            // 创建一个PDF渲染器
            PdfRenderer pdfRenderer = new PdfRenderer(pfd);
            Log.d(TAG, "page count=" + pdfRenderer.getPageCount());
            // 依次处理PDF文件的每个页面
            for (int i = 0; i < pdfRenderer.getPageCount(); i++) {
                // 生成该页图片的保存路径
                String imgPath = String.format("%s/%03d.jpg", dir, i);
                mImgList.add(imgPath);
                // 打开序号为i的页面
                PdfRenderer.Page page = pdfRenderer.openPage(i);
                // 创建该页面的临时位图
                Bitmap bitmap = Bitmap.createBitmap(page.getWidth(), page.getHeight(),
                        Bitmap.Config.ARGB_8888);
                bitmap.eraseColor(Color.WHITE); // 将临时位图洗白
                // 渲染该PDF页面并写入到临时位图
                page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
                BitmapUtil.saveImage(imgPath, bitmap); // 把位图对象保存为图片文件
                page.close(); // 关闭该PDF页面
            }
            pdfRenderer.close(); // 处理完毕,关闭PDF渲染器
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        // 从指定路径的图片文件中获取位图数据
        Bitmap bitmap = BitmapFactory.decodeFile(mImgList.get(0));
        int iv_height = (int)(1.0*bitmap.getHeight()/bitmap.getWidth() * Utils.getScreenWidth(this));
        showImage(iv_height); // 在卷曲视图上显示位图图像
        bitmap.recycle(); // 回收位图对象
    }

    // 在卷曲视图上显示位图图像
    private void showImage(int height) {
        LayoutParams params = cv_content.getLayoutParams();
        params.height = height;
        // 设置卷曲视图的布局参数
        cv_content.setLayoutParams(params);
        // 设置卷曲视图的书页提供器
        cv_content.setPageProvider(new PageProvider(mImgList));
        // 设置卷曲视图的尺寸变更观察器
        cv_content.setSizeChangedObserver(new SizeChangedObserver());
        // 设置卷曲视图默认显示第一页
        cv_content.setCurrentIndex(0);
        // 设置卷曲视图的背景颜色
        cv_content.setBackgroundColor(Color.LTGRAY);
    }

    // 定义一个加载图片页面的提供器
    private class PageProvider implements CurlView.PageProvider {
        private ArrayList<String> mPathArray = new ArrayList<>();

        public PageProvider(ArrayList<String> pathArray) {
            mPathArray = pathArray;
        }

        @Override
        public int getPageCount() {
            return mPathArray.size();
        }

        // 在页面更新时触发
        public void updatePage(CurlPage page, int width, int height, int index) {
            // 加载指定页面的位图
            Bitmap front = BitmapFactory.decodeFile(mPathArray.get(index));
            // 设置书页的纹理
            page.setTexture(front, CurlPage.SIDE_BOTH);
        }
    }

    // 定义一个监听卷曲视图发生尺寸变更的观察器
    private class SizeChangedObserver implements CurlView.SizeChangedObserver {
        @Override
        public void onSizeChanged(int w, int h) {
            // 设置卷曲视图的观看模式
            cv_content.setViewMode(CurlView.SHOW_ONE_PAGE);
            // 设置卷曲视图的四周边缘
            cv_content.setMargins(0f, 0f, 0f, 0f);
        }
    }

}

XML文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/book_bg3"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/tl_head"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/blue_light"
        app:navigationIcon="@drawable/icon_back" />

    <ListView
        android:id="@+id/lv_ebook"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

创作不易 觉得有帮助请点赞关注收藏~~~

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

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

相关文章

【GlobalMapper精品教程】025:影像数据集的建立与巧妙使用

GlobalMapper影像数据集类似于金字塔,作用是提高大量影像的加载与显示速度,还可批量进行一系列设置。本文的配套数据为data025.rar。 文章目录 1. 建立影像数据集2. 影像数据集的使用1. 建立影像数据集 (1)点击【文件】→【创建新地图目录】。 (2)选择影像数据集存放路径…

Doker学习笔记1(狂神)

虚拟机技术缺点&#xff1a; 1.资源占用十分多 2.冗余步骤多 3.启动很慢&#xff01; 容器化技术 我们去安装docker&#xff1a; 我们先保证我们的虚拟机是可以使用的。 环境查看&#xff1a; 系统内核是3.10以上的 系统版本&#xff1a; 我们用的是centOS7虚拟机。 然后…

微服务学习笔记(二)

文章目录Spring Cloud Eureka1.Spring Cloud Eureka 简介2.Spring Cloud Eureka 和 Zookeeper 的区别2.1 什么是 CAP 原则&#xff08;面试&#xff09;2.2 分布式特征3.Spring Cloud 其他注册中心3.1 Consul3.2 Nacos4.Spring Cloud Eureka 快速入门4.1 搭建 Eureka-server4.1…

【ELM回归预测】探路者优化极限学习机回归预测【含Matlab源码 2231期】

⛄一、探路者算法简介 提出的一种新兴的智能优化算法&#xff0c;该算法的思想起源于群体动物的狩猎行为&#xff0c;种群中的个体分为探路者和跟随者两种角色。算法的寻优过程模拟了种群寻找食物的探索过程&#xff0c;利用探路者、跟随者两种角色不同的位置更新方式以及角色…

蓝牙血压计PCBA硬件解决方案

蓝牙血压计是利用现代电子技术与血压间接测量原理进行血压测量的医疗设备。家庭医疗保健已成为现代人的医疗保健时尚。过去人们测量血压必须到医院才行&#xff0c;而今只要拥有了蓝牙血压计&#xff0c;坐在家里便可随时监测血压的变化&#xff0c;如发现血压异常便可及时去医…

odoo14 | odoo中domain的复杂写法

本片文章主要讲述domain中复杂业务需求的逻辑构思&#xff0c;关于doamin的使用位置会在另一篇文章中体现。 二叉树与波兰式 在讲述domain的使用前先讲解一下科班生必学的《数据结构与算法》中二叉树遍历与波兰式的内容&#xff0c;如果你会二叉树先序遍历与波兰式转换请直接…

从零开始的深度学习之旅(2)

目录深层神经网络1. 异或门问题1.1 异或代码实现2.神经网络的层2.1 去除激活函数的异或门2.2 使用sigmoid函数的异或门3.从0实现深度神经网络的正向传播深层神经网络 1. 异或门问题 在第一篇的博客中,我们使用代码实现了与门 import torch X torch.tensor([[1,0,0],[1,1,0]…

通过实战总结的 使用GoFrame小技巧

文章目录gf gen dao设置参数可传可不传model作为结构体类型模型关联添加数据主程序如下&#xff1a;gomeGoods.MainImgs的定义&#xff1a;批量插入数据配置插件自动生成service总结一起学习有朋友问我能不能搞一个GoFrame技巧篇&#xff0c;让新手少踩坑的那种。今天他来了&am…

数据库-范式例题

目录 1、请简述满足1NF、2NF和3NF的基本条件。并完成下题:某信息一览表如下:其是否满足3NF,若不满足将其化为符合3NF的关系。 解:     1NF: 属性都是不可分割的数据项 2NF: 不存在部分函数依赖,存在传递函数依赖。 2NF: 不存在

算法复杂度分析中的渐近分析(基于输入大小)

为什么要进行性能分析&#xff1f; 有许多重要的事情需要注意&#xff0c;例如用户友好性、模块化、安全性、可维护性等。为什么要担心性能&#xff1f;答案很简单&#xff0c;只有当我们有性能时&#xff0c;我们才能拥有上述所有东西。因此&#xff0c;性能就像货币&#xf…

磷酸化多肽Asp-Arg-Val-Tyr(PO3H2)-Ile-His-Pro-Phe、129785-85-9

在血管平滑肌细胞中有多种作用&#xff0c;包括正常动脉的收缩&#xff0c;培养的细胞或病变血管的肥大或增生等。 编号: 200676 中文名称: 八肽DRV-pTyr-IHPF CAS号: 129785-85-9 单字母: H2N-DRV-pTyr-IHPF-OH 三字母: H2N-Asp-Arg-Val-Tyr(PO3H2)-Ile-His-Pro-Phe-COOH 氨基…

MySQL——进阶

第1章 存储引擎 1.1 MySQL体系结构 1.2 存储引擎介绍 定义&#xff1a;存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的&#xff0c;而不是基于库的&#xff0c;所以存储引擎也可被称为表类型。我们可以在创建表的时候&#xff0c;来指定…

msfconsole之制作windows木马并成功获取shell

msfconsole之制作windows木马并成功获取shell 一、工具简介     msfconsole 简称 msf 是一款常用的安全测试工具&#xff0c;包含了常见的漏洞利用模块和生成各种木马&#xff0c;其提供了一个一体化的集中控制台&#xff0c;通过msfconsole&#xff0c;你可以访问和使用所…

软考高级系统架构师_计算机组成与结构---备考笔记004

奇校验和海明码考的比较多,可以看到: 奇校验:比如有个11001 这个要进行校验,那么,需要在后面添加什么?注意,要添加0,因为奇校验,是奇数个1,这里已经有3个1了,所以后面要加0,变成110010 这样 然后我们再来看,如

01-SpringMVC项目构建

Overview 梳理了创建基于SpringMVC的项目创建流程和注意点&#xff0c;防止遗忘 1. 创建一个空项目 2. 添加新模块 3. 在pom文件中指定打包方式并刷新 4. 将模块目录结构改造成web项目 这里的web配置是你在pom文件中指定war的打包方式后就会出现的&#xff0c;当然没出现也可…

迅为3A5000_7A2000开发板龙芯全国产处理器LoongArch架构核心方案

1.全国产设计方案 从里到外 100% 全国产 从CPU自主指令系统到开发板每一个元器件&#xff0c;做到100%全国产化。 2.产品开发更快捷 PCIE 32路 相比同类嵌入式板卡仅2到4路的PCIE&#xff0c; 这款核心板可以支持多达32路的PCIE 3.0接口 3.工业化标准设计 遵循COM E…

数据库-mysql架构与sql执行原理(上)(一)

目录 一、一条查询的sql他是怎么样去执行的呢&#xff1f; 二、建立链接 同步 异步 三、连接方式 长链接 短链接 四、通信协议 Unix socket TCP/IP 共享内存 五、通信方式 单工 半双工 全双工 六、缓存 七、解析 八、预处理器 九、优化器 十、查询执行引擎…

ASEMI肖特基二极管MBR15200FAC参数,MBR15200FAC图片

编辑-Z ASEMI肖特基二极管MBR15200FAC参数&#xff1a; 型号&#xff1a;MBR15200FAC 最大重复峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;200V 最大RMS电桥输入电压&#xff08;VRMS&#xff09;&#xff1a;140V 最大直流阻断电压&#xff08;VDC&#xff09…

智慧零售解决方案-最新全套文件

智慧零售解决方案-最新全套文件一、建设背景二、建设思路三、建设方案四、获取 - 智慧零售全套最新解决方案合集一、建设背景 传统零售业发展趋势分析&#xff1a; &#xff08;1&#xff09;市场竞争日益激烈 零售业的发展一直是一个竞争性强的产业&#xff0c;这个市场的发…

数据结构之堆

堆&#xff1a; 堆是一颗完全二叉树&#xff0c;分为大根堆和小根堆两种。大根堆&#xff1a;根节点大于等于左右节点&#xff1b;小根堆&#xff1a;根节点小于等于左右节点。所以大根堆的根节点是最大值&#xff0c;小根堆的根节点是最小值。 c中priority_queue可以用来声明…