Android类似微信聊天页面教程(Kotlin)四——数据本地化
前提条件
安装并配置好Android Studio
Android Studio Electric Eel | 2022.1.1 Patch 2
Build #AI-221.6008.13.2211.9619390, built on February 17, 2023
Runtime version: 11.0.15+0-b2043.56-9505619 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Windows 11 10.0
GC: G1 Young Generation, G1 Old Generation
Memory: 1280M
Cores: 6
Registry:
external.system.auto.import.disabled=true
ide.text.editor.with.preview.show.floating.toolbar=false
ide.balloon.shadow.size=0
Non-Bundled Plugins:
com.intuit.intellij.makefile (1.0.15)
com.github.setial (4.0.2)
com.alayouni.ansiHighlight (1.2.4)
GsonOrXmlFormat (2.0)
GLSL (1.19)
com.mistamek.drawablepreview.drawable-preview (1.1.5)
com.layernet.plugin.adbwifi (1.0.5)
com.likfe.ideaplugin.eventbus3 (2020.0.2)gradle-wrapper.properties
#Tue Apr 25 13:34:44 CST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
build.gradle(:Project)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {id 'com.android.application' version '7.3.1' apply falseid 'com.android.library' version '7.3.1' apply falseid 'org.jetbrains.kotlin.android' version '1.7.20' apply false
}
setting.gradle
pluginManagement {repositories {google()mavenCentral()gradlePluginPortal()maven { url 'https://jitpack.io' }}
}
dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()mavenCentral()gradlePluginPortal()maven { url 'https://jitpack.io' }}
}
rootProject.name = "logindemo"
include ':app'
build.gralde(:app)
plugins {id 'com.android.application'id 'org.jetbrains.kotlin.android'id 'kotlin-android'id 'kotlin-kapt'
}android {namespace 'com.example.fechat'compileSdk 33defaultConfig {applicationId "com.example.fechat"minSdk 26targetSdk 33versionCode 1versionName "1.0"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_11targetCompatibility JavaVersion.VERSION_11}kotlinOptions {jvmTarget = '1.8'}
}dependencies {implementation 'androidx.core:core-ktx:1.7.0'implementation 'androidx.appcompat:appcompat:1.6.1'implementation 'com.google.android.material:material:1.8.0'implementation 'androidx.constraintlayout:constraintlayout:2.1.4'// 沉浸式状态栏 https://github.com/gyf-dev/ImmersionBarimplementation 'com.gyf.immersionbar:immersionbar:3.0.0'implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' // fragment快速实现(可选)implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0' // kotlin扩展(可选)implementation 'com.google.code.gson:gson:2.8.9'implementation "androidx.room:room-runtime:2.4.2"implementation "androidx.room:room-ktx:2.4.2"kapt "androidx.room:room-compiler:2.4.2"implementation 'org.apache.commons:commons-csv:1.5'implementation 'com.permissionx.guolindev:permissionx:1.4.0'implementation 'com.blankj:utilcodex:1.30.0' // 无implementation 'com.github.bumptech.glide:glide:4.12.0'kapt 'com.github.bumptech.glide:compiler:4.12.0'
}
对Kotlin语言有基本了解
内容在前一篇博客中写了基础配置,如果本篇内容看不懂,可以先去上一篇。
数据本地化方案
采用room数据库保存首页用户聊天列表,为此引入room库
implementation "androidx.room:room-runtime:2.4.2"
implementation "androidx.room:room-ktx:2.4.2"
kapt "androidx.room:room-compiler:2.4.2"
采用csv文件来按行保存与用户聊天内容,这里csv不具有任何数据保护性,所以如果想要实现本地化并且数据加密,可以对保存在csv文件中的数据进行加密处理,读取时只需要解密即可还原,为此引入了kotlin的CSV读写库
implementation 'org.apache.commons:commons-csv:1.5'
其他优秀的开源库在这里也一起引入了,在这里感谢各位开源库作者
权限申请库
implementation 'com.permissionx.guolindev:permissionx:1.4.0'
通用工具类
implementation 'com.blankj:utilcodex:1.30.0' // 无
加载图片的glide库
implementation 'com.github.bumptech.glide:glide:4.12.0'
kapt 'com.github.bumptech.glide:compiler:4.12.0'
本地化实现过程中新增的代码过多,所以不便这里一一贴出来,感兴趣的同学请移步开源库
FeChat: 模仿微信
首页聊天页面中的数据刷新
package com.example.fechat.fragmentimport android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.fechat.R
import com.example.fechat.activity.MessageActivity
import com.example.fechat.base.BaseAdapter
import com.example.fechat.room.user.UserDBUtils
import com.example.fechat.room.user.UserEntity
import java.util.*class ChatFragment : Fragment() {private var baseAdapter: BaseAdapter? = nullprivate lateinit var recyclerView: RecyclerViewprivate var data: List<UserEntity>? = nulloverride fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {val view = inflater.inflate(R.layout.fragment_chat, container, false)recyclerView = view.findViewById(R.id.recyclerView)recyclerView.layoutManager = LinearLayoutManager(context)data =ArrayList(UserDBUtils.getAll(context)).filter { it.last_message.isNotEmpty() }data?.let {Collections.sort(it) { o1, o2 ->(o2.duration - o1.duration).toInt()}}baseAdapter = BaseAdapter(data!!)recyclerView.adapter = baseAdapterbaseAdapter?.setOnItemClickListener(object : BaseAdapter.OnItemClickListener {override fun onItemClick(view: View, position: Int) {val intent = Intent(context, MessageActivity::class.java)intent.putExtra("UserInfo", data!![position].toString())startActivity(intent)}})return view}fun resume() {data =ArrayList(UserDBUtils.getAll(context)).filter { it.last_message.isNotEmpty() }data?.let {Collections.sort(it) { o1, o2 ->(o2.duration - o1.duration).toInt()}}baseAdapter?.setNewData(data!!)}
}
其中UserDBUtils是数据库接口,读取保存的聊天记录(包含用户名和最新的聊天记录)
而Collections.sort是对读出来的聊天记录(多用户)按照最新聊天记录的时间进行的排序
聊天页面数据本地化
package com.example.fechat.activityimport android.annotation.SuppressLint
import android.os.Bundle
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.blankj.utilcode.util.FileUtils
import com.example.fechat.R
import com.example.fechat.adapter.ChatAdapter
import com.example.fechat.bean.MessageBean
import com.example.fechat.room.user.UserDBUtils
import com.example.fechat.room.user.UserEntity
import com.example.fechat.utils.CSVUtils
import com.google.gson.Gson
import com.gyf.immersionbar.ImmersionBarclass MessageActivity : AppCompatActivity() {private val beans = ArrayList<MessageBean>()private var adapter: ChatAdapter? = nullprivate lateinit var itemView: RecyclerViewprivate lateinit var userEntity: UserEntityprivate var messagePath = ""private val userName = "Admin"override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)ImmersionBar.with(this).statusBarDarkFont(true).statusBarColor(R.color.title).navigationBarColor(R.color.white).navigationBarDarkIcon(true).init()setContentView(R.layout.activity_message)val backTv = findViewById<TextView>(R.id.backTv)val inputText: EditText = findViewById(R.id.inputText)val sendText: TextView = findViewById(R.id.sendText)val userName: TextView = findViewById(R.id.userName)backTv.setOnClickListener {finish()}sendText.setOnClickListener {sendText(inputText.text.toString())inputText.setText("")}getBundle()initItemRecyclerView()userName.text = userEntity.userName}private fun getBundle() {val userInfo = intent.getStringExtra("UserInfo")userEntity = Gson().fromJson(userInfo, UserEntity::class.java)messagePath = CSVUtils.getPath(this, userEntity.userId)FileUtils.createOrExistsFile(messagePath)}override fun onResume() {super.onResume()beans.addAll(CSVUtils.readFromCSV(messagePath))}private fun initItemRecyclerView() {itemView = findViewById(R.id.itemView)val layoutManager = LinearLayoutManager(this)layoutManager.orientation = RecyclerView.VERTICALitemView.layoutManager = layoutManageradapter = ChatAdapter(beans, userEntity)itemView.adapter = adapter}@SuppressLint("NotifyDataSetChanged")private fun sendText(message: String) {insertMessage(message)adapter?.notifyDataSetChanged()}private fun insertMessage(message: String) {val messageBean = MessageBean(message, userName, false, System.currentTimeMillis(), true)beans.add(messageBean)CSVUtils.writeToCSV(messageBean, messagePath)val messageBeanResp =MessageBean(message, userEntity.userName, true, System.currentTimeMillis(), true)beans.add(messageBeanResp)CSVUtils.writeToCSV(messageBeanResp, messagePath)userEntity.duration = System.currentTimeMillis()userEntity.last_message = messageUserDBUtils.insertUser(this, userEntity)}
}
数据本地化包括对首页聊天记录列表的更新和单用户聊天记录保存到CSV文件中。
CSV工具类
package com.example.fechat.utilsimport android.content.Context
import com.example.fechat.bean.MessageBean
import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVParser
import org.apache.commons.csv.CSVPrinter
import java.io.*object CSVUtils {fun getPath(context: Context, userId: String): String {return "${context.getExternalFilesDir(null)}/message/${userId}.csv"}fun writeToCSV(bean: MessageBean, path: String) {val bufferWrite = BufferedWriter(OutputStreamWriter(FileOutputStream(path, true)))val csvPrinter = CSVPrinter(bufferWrite, CSVFormat.DEFAULT)val data = listOf(bean.message, bean.userName, bean.isResponse, bean.time, bean.isSuccess)csvPrinter.printRecord(data)csvPrinter.flush()csvPrinter.close()}fun readFromCSV(path: String): ArrayList<MessageBean> {val bufferedReader = BufferedReader(FileReader(File(path)))val csvParser = CSVParser(bufferedReader, CSVFormat.DEFAULT)val messageBeans = ArrayList<MessageBean>()csvParser.forEach { parse ->val messageBean = MessageBean(parse[0],parse[1],parse[2].toBoolean(),parse[3].toLong(),parse[4].toBoolean())messageBeans.add(messageBean)}return messageBeans}
}
新增的权限请求
package com.example.fechat.activityimport android.Manifest
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.widget.BaseAdapter
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import com.example.fechat.R
import com.example.fechat.fragment.ChatFragment
import com.example.fechat.fragment.ContactsFragment
import com.example.fechat.fragment.DiscoverFragment
import com.google.android.material.tabs.TabLayout
import com.gyf.immersionbar.ImmersionBar
import com.permissionx.guolindev.PermissionXclass MainActivity : AppCompatActivity() {private lateinit var viewPager: ViewPagerprivate lateinit var tabLayout: TabLayoutprivate lateinit var titleTv: TextViewprivate val fragments = ArrayList<Fragment>()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)ImmersionBar.with(this).statusBarDarkFont(true).statusBarColor(R.color.title).navigationBarColor(R.color.white).navigationBarDarkIcon(true).init()setContentView(R.layout.activity_main)initPermission()}private fun initPermission() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {if (!Environment.isExternalStorageManager()) {val intent = Intent()intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSIONval uri: Uri = Uri.fromParts("package", this.packageName, null)intent.data = uristartActivityForResult(intent, 0x99)} else {initView()}} else {PermissionX.init(this).permissions(Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE).request { allGranted, _, _ ->if (allGranted) {initView()}}}}private fun initView() {fragments.addAll(listOf(ChatFragment(),ContactsFragment(),DiscoverFragment()))titleTv = findViewById(R.id.titleTv)viewPager = findViewById(R.id.viewPager)tabLayout = findViewById(R.id.tabLayout)viewPager.adapter = ViewPagerAdapter(supportFragmentManager, fragments)tabLayout.setupWithViewPager(viewPager)tabLayout.getTabAt(0)?.text = "聊天"tabLayout.getTabAt(1)?.text = "联系人"tabLayout.getTabAt(2)?.text = "发现"tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {override fun onTabSelected(tab: TabLayout.Tab?) {titleTv.text = tab?.textif (tab?.position == 0) {(fragments[0] as ChatFragment).resume()}}override fun onTabUnselected(tab: TabLayout.Tab?) {}override fun onTabReselected(tab: TabLayout.Tab?) {}})}class ViewPagerAdapter(fragmentManager: androidx.fragment.app.FragmentManager,private val fragments: List<Fragment>) : FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {override fun getItem(position: Int): Fragment {return fragments[position]}override fun getCount(): Int {return fragments.size}}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (requestCode == 0x99) {initPermission()}}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /><applicationandroid:allowBackup="true"android:icon="@drawable/icon_logo"android:roundIcon="@drawable/icon_logo"android:label="@string/app_name"android:supportsRtl="true"android:name=".base.BaseApplication"android:theme="@style/Theme.FeChat.Font"tools:targetApi="31"><activityandroid:name=".activity.MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activity android:name=".activity.MessageActivity" /></application></manifest>
新增字体
字体是阿里开源的,仅供大家学习使用,商用可能产生版权纠纷
themes.xml
<style name="Theme.FeChat.Font" parent="Theme.FeChat"><item name="fontFamily">@font/alimama_dongfangdakai_regular</item>
</style>
字体引用
<applicationandroid:allowBackup="true"android:icon="@drawable/icon_logo"android:roundIcon="@drawable/icon_logo"android:label="@string/app_name"android:supportsRtl="true"android:name=".base.BaseApplication"android:theme="@style/Theme.FeChat.Font"tools:targetApi="31"><activityandroid:name=".activity.MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activity android:name=".activity.MessageActivity" />
</application>
相关文章:

Android类似微信聊天页面教程(Kotlin)四——数据本地化
前提条件 安装并配置好Android Studio Android Studio Electric Eel | 2022.1.1 Patch 2 Build #AI-221.6008.13.2211.9619390, built on February 17, 2023 Runtime version: 11.0.150-b2043.56-9505619 amd64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o. Windows 11 …...

C/C++基础知识
专栏:C/C 个人主页: C/C基础知识 前言C关键字(C98)命名空间命名空间的定义正常的命名空间的定义如何使用命名空间 命名空间可以嵌套同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中(一个工程中的.h文件和test.…...

Java 入门 - 语法基础
hello world public class Hello {public static void main(String[] args) {System.out.println("hello world");} } 复制代码 public: 是关键字;表示公开的class: 是关键字;用来定义类Hello: 是类名;大小写敏感;命名…...

Java线程池及拒绝策略详解
前文提到线程的使用以及线程间通信方式,通常情况下我们通过new Thread或者new Runnable创建线程,这种情况下,需要开发者手动管理线程的创建和回收,线程对象没有复用,大量的线程对象创建与销毁会引起频繁GC,…...

GitLABJenkins
GitLAB & Jenkins 目录 实践:基于Jenkins提交流水线(测试成功)-2023.4.25 目的:掌握通过触发器将GitLab和Jenkins集成,实现提交流水线。 1、触发Jenkins构建 安装Generic Webhook Trigger插件 重启后,进入一个Pipeline项目设…...
互联网摸鱼日报(2023-04-26)
互联网摸鱼日报(2023-04-26) InfoQ 热门话题 神州数码:抢抓云原生发展机遇,共建共治共享 OpenNJet 应用引擎开源生态 《产业数字人才研究与发展报告(2023)》 如何写出CPU友好的代码,百倍提升…...

石化企业数字化防爆融合通信解决方案
项目背景 石化工业是我国国民经济和社会发展的基础性、战略性产业,其发展和壮大受到了党和国家的高度重视。随着石化企业厂区规模的不断扩大以及技术的快速发展,现有石化企业专网通信系统建设相对滞后,缺乏结合人员管理、安全生产、安全通信…...
NTT学习笔记(快速数论变换)
一些概念 欧拉函数 ϕ ( n ) \phi(n) ϕ(n) 欧拉函数简介 阶 若 g g g和 n n n互质,则令 g x % n 1 g^x\%n1 gx%n1的最小正整数 x x x称为 g g g模 n n n的阶。 原根 对于互质的两个正整数 g g g和 n n n,如果 g g g模 n n n的阶为 ϕ ( n ) \phi…...

Android类似微信首页的页面开发教程(Kotlin)二
前提条件 安装并配置好Android Studio Android Studio Electric Eel | 2022.1.1 Patch 2 Build #AI-221.6008.13.2211.9619390, built on February 17, 2023 Runtime version: 11.0.150-b2043.56-9505619 amd64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o. Windows 11 …...
PAt A1015 Reversible Primes
1015 Reversible Primes 分数 20 作者 CHEN, Yue 单位 浙江大学 A reversible prime in any number system is a prime whose "reverse" in that number system is also a prime. For example in the decimal system 73 is a reversible prime because its rever…...
解决Lemuroid识别不到蓝牙键盘的问题
Android系统基于libretro的全能游戏模拟器,目前有RetroArch,Kodi,Lemuroid。 而且这三个都是开源免费的APP。 Lemuroid相对前面两个功能比较简陋。也不能自己下载核心。但代码也是最少的。 在使用Lemuroid的时候,发现它不能检测…...

SpringBoot 使用 Sa-Token 完成权限认证
一、设计思路 所谓权限认证,核心逻辑就是判断一个账号是否拥有指定权限: 有,就让你通过。没有?那么禁止访问! 深入到底层数据中,就是每个账号都会拥有一个权限码集合,框架来校验这个集合中是…...

Spring核心与设计思想、创建与使用
文章目录 一、Spring是什么二、为什么要学习框架三、IoC和DI(一)IoC1. 认识IoC2. Spring的核心功能 (二)DI 四、Spring项目的创建(一)使用 Maven 方式创建一个 Spring 项目 五、Spring项目的使用࿰…...
mysql 备份 还原
1:备份 执行命令方案1: /usr/local/mysql/bin/mysqldump -uX -pX -h 127.0.0.1 --set-gtid-purgedOFF --skip-extended-insert --add-drop-table --add-locks --create-options --disable-keys --lock-tables --quick --set-charset -e --max_allowed_packet16777216 --net_b…...

每日学术速递4.26
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.AutoNeRF: Training Implicit Scene Representations with Autonomous Agents 标题:AutoNeRF:使用自主代理训练隐式场景表示 作者:Pierre Marz…...
RabbitMQ使用StringRedisTemplate-防止重复消费
造成重复消费的原因: MQ向消费者推送message,消费者向MQ返回ack,告知所推送的消息消费成功。但是由于网络波动等原因,可能造成消费者向MQ返回的ack丢失。MQ长时间(一分钟)收不到ack,于是会向消…...
临沂大学张继群寄语
目录 寄语 1、不能有不良睹好 2、坚毅的个性和勤奋的品质 3、会存钱...
线程学习笔记
1:Thread 线程的生命周期控制 2:Runnable 可执行的任务和程序 3:Callable 执行程序后返回结果 4:Future 收集程序返回结果 5:Executor 线程池 6:ForkJoin 默认线程池 每个线程有工作队列 工作窃取 7:RunnableFuture FutureTask 实现 Runnable 和 Future 执…...

代码随想录算法训练营第四十二天|01背包问题,你该了解这些!、01背包问题,你该了解这些! 滚动数组 、416. 分割等和子集
文章目录 01背包问题,你该了解这些!01背包问题,你该了解这些! 滚动数组416. 分割等和子集 01背包问题,你该了解这些! 题目链接:代码随想录 二维数组解决0-1背包问题 解题思路: 1.dp…...
结构体指针、数组指针和结构体数组指针
结构体指针 首先让我们定义结构体: struct stu { char name[20]; long number; float score[4]; }; 再定义指向结构体类型变量的指针变量: struct stu *student; /*定义结构体类型指针*/ student malloc(sizeof(struct stu)); /*为指针变量分…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...