存储过程和函数的区别?
存储过程 是一段代码(过程),存储在数据库中的SQL组成。一个存储过程通常用于实现一段业务逻辑;
函数通常是数据库已定义的办法。
存储过程和函数执行不是由程序调用,也不是手动启动。而是由事件触发、激活从而实现执行。
一个函数通常专一与某个性能,视为其余程序服务的,须要在其余语句中调用函数才能够,而存储过程不能被其余调用,是本人执行通过call执行。
存储过程和函数都是属于某个数据库。
函数有一个返回值,而存储过程是通过参数返回的,能够有多个或者没有。
函数个别状况下是用来计算并返回一个计算结果,而存储过程个别是用来实现特定的数据操作。
oracle中存储过程和函数都能够返回值,然而函数必须要返回值并且个别返回一个值,而存储过程则没有这个限度。
创立存储过程?
MySQL创立调用:
create procedure 存储过程名称(in|out|inout 参数名称 参数类型,……)begin过程体;end
1、查问
查问所有存储过程状态
show procedure status;
查看对应数据库下所有存储过程状态
show procedure status where db="数据库名";
查看名称蕴含Student的存储过程状态
show procedure status where name like "%Student%";
查问存储过程具体代码
show create procedure 过程名;
2、批改
alter procedure 过程名([过程参数[,…]])过程体;
3、删除
drop procedure 过程名;
注:不能在一个存储过程中删除另一个存储过程,只能调用另一个存储过程。
调用存储过程
mysql存储过程用call和过程名以及一个括号,括号外面依据须要,退出参数,参数包含输出参数、输入参数、输入输出参数调用。
call 存储过程名([过程参数[,...]])
Oracle创立调用:
create or replace procedure sample_proc as --申明 msg varchar2(50);begin --执行 msg:='Hello world';--为参数赋值 dbms_output.put_line('你好的英文为:'||msg);--输入参数exception --存贮过程异样 ;end; --有三种执行语法--执行语法1call sample_proc();--执行后果sample_proc ) 胜利。你好的英文为:Hello world --执行语法2exec sample_proc;--执行后果匿名块已实现你好的英文为:Hello world --执行语法3set serveroutput onbegin sample_proc;end;--执行后果匿名块已实现你好的英文为:Hello world
函数的创立调用?
MySQL创立:
DELIMITER $$ --定义结束符。MySQL默认的结束符是分号,然而函数体中可能用到分号。为 了防止抵触,须要另外定义结束符。DROP FUNCTION IF EXISTS function_name$$ --如果函数genPerson曾经存在了,就删除掉。CREATE FUNCTION function_name(name varchar(20)) RETURNS varchar(50) --创立函数genPerson,函数的参数是name,返回值是varchar(50)。BEGIN --函数体放在BEGIN 与 END之间。 DECLARE str VARCHAR(50) DEFAULT ''; --DECLARE申明变量,str类型是varchar(50),默认值是空。 SET @tableName=name; SET str=CONCAT('create table ', @tableName,'(id int, name varchar(20));'); --CONCAT连贯多个字符串。 return str; --RETURN 返回拼接后的字符串str。END $$DELIMITER ;
Oracle创立:
CREATE [OR REPLACE] FUNCTION function_name [ (parameter [,parameter]) ] RETURN return_datatypeIS | AS [declaration_section]BEGIN executable_section[EXCEPTION exception_section]END [function_name];执行:--调用select function_name('参数');--删除DROP FUNCTION function_name;
定时器的创立调用?
MySQL创立:
CREATE EVENT IF NOT EXISTS 打算名 -- 打算频率和开启打算工夫或者是打算执行的工夫 -- 前一个能够实现继续的打算调度,后一个到指定工夫进行调度,执行完完结,没有持续性 ON SCHEDULE [EVERY 10 SECOND STARTS TIMESTAMP 开启工夫] [AT 开启工夫] -- 当打算执行实现时,是否删除 ON COMPLETION [NOT] PRESERVE do call 存储过程 ---查看数据库是否开启调度-- 查看是否开启调度show variables like '%event_scheduler%';-- value为OFF,未开启;-- 开启SET GLOBAL event_scheduler = 1;--敞开和开启指定定时器ALTER EVENT 定时器名 ON COMPLETION PRESERVE [ENABLE][DISABLE];-- 开启ENABLE,敞开DISABLE --删除定时器drop EVENT 定时器名;
Oracle创立:
DECLARE job_test number; -- DECLARE 用来定义unlockTest_timer 的定时器编号BEGIN SYS.DBMS_JOB.SUBMIT( job => unlockTest_timer, --job 指的是定时器编号,在DECLARE 中曾经申明 what => 'pro_test;', --what 指的是要执行的存储过程,也就是SQL语句 NEXT_DATE => sysdate, --next_date 指的是下次执行工夫 INTERVAL => 'sysdate+1/(24*60)' --interval 指的是每次执行工夫的间隔时间 这里是一分钟执行一次 );Commit;End;--定时器创立好后,会主动执行。--查看在执行的定时器,job-定时器编号SELECT job, next_date, next_sec, failures, broken FROM user_jobs; begin
触发器创立和调用
触发器组成:
1、触发事件---DML或DDL语句。
2、触发工夫---是在触发事件产生之前(before) 还是之后(after) 触发
3、触发操作---应用PL/SQL块进行相应的数据库操作
4、触发对象---表、视图、模式、数据库
5、触发频率---触发器内定义的动作被执行的次数,包含语句级和行级。
MySQL
触发器是依照BEFORE触发器、行操作、AFTER触发器的程序执行的,其中任何一步产生谬误都不会继续执行剩下的操作。如果是对事务表进行的操作,那么会整个作为一个事务被回滚,然而如果是对非事务表进行的操作,那么曾经更新的记录将无奈回滚,
CREATE [DEFINER = { user | CURRENT_USER }] TRIGGER trigger_name --trigger_name:触发器的名称,不能与曾经存在的触发器反复; trigger_time trigger_event --trigger_time:{ BEFORE | AFTER },示意在事件之前或之后触发;trigger_event::{ INSERT |UPDATE | DELETE },触发该触发器的具体事件; ON tbl_name FOR EACH ROW trigger_body --tbl_name:该触发器作用在tbl_name上; SHOW TRIGGERS trigger_name;--命令查看触发器DROP TRIGGER trigger_name;--删除
Oracle
触发器:相似于AOP(面向切面编程)中的拦截器;不能传递参数,输入参数,也不能显示调用,只有满足触发器条件时会由Oracle主动调用。
限度
1、触发器不承受参数
2、一个表上最多可有12个触发器,但同一时间、同一事件、同一类型的触发器只能有一个。并各触发器之间不能有矛盾。
3、一个表上的触发器越多,该表上的DM操作的性能影响就越大
4、触发器代码的大小不能超过32K。如须要大量的代码创立触发器,则首先创立过程,而后在触发器中应用CALL语句调用过程
5、触发器代码只能蕴含SELECT、INSERT、UPDATE和DELETE语句,
6、不能蕴含DDL语句(CREATE、ALTER和DROP) 和事务管制语句(COMMIT、ROLLBACK和SAVEPOINT)
语句触发器
1、语句触发器是指当执行DML语句时被隐含执行的触发器
2、如果在表上针对某种DML操作创立了语句触发器,则当执行DML操作时会主动地执行触发器的相应代码
3、为了审计DML操作,或者确保DML操作平安执行时,能够应用语句触发器
触发器用处很多,例如用户清理购物车后将会触发待收货的数据库
--创立触发器create or replace trigger tri_testbefore--触发之前update or delete--更新或删除on empfor each row--对行进行操作 begin dbms_output.put_line(:old.sal);--old示意数据库旧值 insert into demo(id) values (:new.sal);--new新值 end;update emp set sal=888 where empno=7788;commit;--代码解释:先执行创立触发器代码后,再执行最初的更新语句。当更新恩平、表后将会输入数据库中原本寄存的值,并且触发增加语句在demo表中插入一条语句。--查问select * from 表 where object_type='TRIGGER';--删除drop trigger ...;drop trigger ..,;
定时工作的创立和调用
MySQL:
自 MySQL5.1.6起,减少了一个十分有特色的性能–事件调度器(Event Scheduler),能够用做定时执行某些特定工作,来取代原先只能由操作系统的打算工作来执行的工作。事件调度器有时也可称为长期触发器(temporal triggers),因为事件调度器是基于特定工夫周期触发来执行某些工作,而触发器(Triggers)是基于某个表所产生的事件触发的,区别也就在这里。
--在应用这个性能之前必须确保 event_scheduler 已开启,可执行 :mysq> SET GLOBAL event_scheduler = 1;# 或mysql> SET GLOBAL event_scheduler = ON;也能够在配置文件中增加设置 : event_scheduler=1也能够间接在启动命令加上 : --event_scheduler=1--查看以后是否已开启事件调度器 :mysql> SHOW VARIABLES LIKE 'event_scheduler';# 或mysql> SELECT @@event_scheduler;# 或mysql> SHOW PROCESSLIST;--创立事件(CREATE EVENT)CREATE EVENT [IFNOT EXISTS] event_name ON SCHEDULE schedule [ON COMPLETION [NOT] PRESERVE] --设置这个事件是执行一次还是长久执行,默认为 NOT PRESERVE [ENABLE | DISABLE] --可设置该事件创立后状态是否开启或敞开,默认为ENABLE [COMMENT 'comment'] --能够给该事件加上正文 DO sql_statement;--批改事件(ALTER EVENT)ALTER EVENT event_name [ON SCHEDULE schedule] [RENAME TO new_event_name] [ON COMPLETION [NOT] PRESERVE] [COMMENT 'comment'] [ENABLE | DISABLE] [DO sql_statement]--长期敞开事件mysql> ALTER EVENT e_test DISABLE;# 开启事件mysql> ALTER EVENT e_test ENABLE;--删除事件(DROP EVENT)DROP EVENT [IF EXISTS] event_name
Oracle:
--创立jobbegin sys.dbms_job.submit(job => 1, --代表的是号码,第几个定时工作 what => 'sys_mailing_list_job;', --这个是调用的你想应用的存储过程切记要打;不然会报错 next_date => to_date('20-08-2018 14:05:00', 'dd-mm-yyyy hh24:mi:ss'), --这个是下次调用的工夫 interval => 'trunc(sysdate,''hh'')+(60+5)/(24*60)'); commit; --这个是间隔时间 。我这个代表的是每个小时的过5 比方 1:05,2:05,3:05...24小时的end;--删除job: dbms_job.remove(jobno); -- jobno就是你得工作号 --批改要执行的操作: job:dbms_job.what(jobno, what); --指定工作号以及存储过程--批改下次执行工夫:dbms_job.next_date(jobno, next_date); --指定工作号的工夫--批改间隔时间:dbms_job.interval(jobno, interval); --指定工作号的间隔时间--启动job: dbms_job.run(jobno); --指定工作号启动--进行job: dbms.broken(jobno, broken, nextdate); --broken为boolean值 N代表启动,Y代表没启动(STOP)
Linux常用命令
运行jar包:java -jar查看日志全文:cat + 日志名称.log实时查看日志:tail -f +日志名称.log含糊查问日志:grep -r -200 "查问内容" 日志名称.log查看过程:ps命令-a,查看所有-u,以用户(user)的格局显示-x, 显示后盾过程运行参数-ef,以全格局显示过程所有信息,包含父过程Pid,创建人,创立工夫,过程号。等等 ps -l 列出与本次登录无关的过程信息; ps -aux 查问内存中过程信息; ps -aux | grep ... 查问...过程的详细信息; top 查看内存中过程的动静信息; kill -9 pid 杀死过程。
并行和并发有什么区别?
- 并行(Parallel):指两个或者多个事件在同一时刻产生,即同时做不同事的能力。例如垃圾回收时,多条垃圾收集线程并行工作,但此时用户线程依然处于期待状态。
- 并发(Concurrent):指两个或多个事件在同一时间距离内产生,即交替做不同事的能力,多线程是并发的一种模式。例如垃圾回收时,用户线程与垃圾收集线程同时执行(但不肯定是并行的,可能会交替执行),用户程序在持续运行,而垃圾收集程序运行于另一个CPU上。
线程和过程的基本概念、线程的根本状态以及状态之间的关系?
- 一个线程是过程的一个程序执行流程。一个过程中的全副线程共享同一个堆空间。线程自身有一个供程序执行时的栈,一个过程中能够蕴含多个线程。
- 线程的根本状态:新建、就绪、运行状态、阻塞状态、死亡状态。
- 新建状态:利用NEW运算创立了线程对象,此时线程状态为新建状态,调用了新建状态线程的start()办法,将线程提交给操作系统,筹备执行,线程将进入到就绪状态。
- 就绪状态:由操作系统调度的一个线程,没有被零碎调配到处理器上执行,一旦处理器有闲暇,操作系统会将它放入处理器中执行,此时线程从就绪状态切换到运行时状态。
- 运行状态:线程正在运行的过程中,碰到调用Sleep()办法,或者期待IO实现,或期待其余同步办法实现时,线程将会从运行状态,进入到阻塞状态。
- 死亡状态:线程一旦脱离阻塞状态时,将从新回到就绪状态,从新向下执行,最终进入到死亡状态。一旦线程对象是死亡状态,就只能被GC回收,不能再被调用。
守护线程是什么?
- 守护线程又称为后盾线程,它独立于管制终端并且周期性地执行某种工作或期待解决某些产生的事件
- 失常创立的线程都是一般线程,或称为前台线程,守护线程与一般线程在应用上没有什么区别,然而他们有一个最次要的区别是在于过程的完结中。当一个过程中所有一般线程都完结时,那么过程就会完结。如果过程完结时还有守护线程在运行,那么这些守护线程就会被强制完结
- 在 Java 中垃圾回收线程就是非凡的守护线程
艰深来说:守护线程是个服务线程,精确地来说就是服务其余的线程。
创立线程有哪几种形式?
- 继承Thread类(真正意义上的线程类),是Runnable接口的实现。
- 实现Runnable接口,并重写外面的run办法。
- 应用Executor框架创立线程池。Executor框架是juc里提供的线程池的实现。
sleep() 和 wait() 有什么区别?
- 类的不同:sleep() 来自 Thread,wait() 来自 Object。
- 开释锁:sleep() 不开释锁;wait() 开释锁。
- 用法不同:sleep() 工夫到会主动复原;wait() 能够应用 notify()/notifyAll()间接唤醒。
线程的 run() 和 start() 有什么区别?
- start() 办法用于启动线程,run() 办法用于执行线程的运行时代码。
- run() 能够反复调用,而 start() 只能调用一次。
- 第二次调用start() 必然会抛出运行时异样
创立线程池有哪几种形式?
- newSingleThreadExecutor():它的特点在于工作线程数目被限度为 1,操作一个无界的工作队列,所以它保障了所有工作的都是被程序执行,最多会有一个工作处于活动状态,并且不容许使用者改变线程池实例,因而能够防止其扭转线程数目;
- newCachedThreadPool():它是一种用来解决大量短时间工作工作的线程池,具备几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创立新的工作线程;如果线程闲置的工夫超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会耗费什么资源。其外部应用 SynchronousQueue 作为工作队列;
- newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背地应用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是流动的。这意味着,如果工作数量超过了流动队列数目,将在工作队列中期待闲暇线程呈现;如果有工作线程退出,将会有新的工作线程被创立,以补足指定的数目 nThreads;
- newSingleThreadScheduledExecutor():创立单线程池,返回 ScheduledExecutorService,能够进行定时或周期性的工作调度;
- newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()相似,创立的是个 ScheduledExecutorService,能够进行定时或周期性的工作调度,区别在于繁多工作线程还是多个工作线程;
- newWorkStealingPool(int parallelism):这是一个常常被人疏忽的线程池,Java 8 才退出这个创立办法,其外部会构建ForkJoinPool,利用Work-Stealing算法,并行地解决工作,不保障解决程序;
- ThreadPoolExecutor():是最原始的线程池创立,下面1-3创立形式都是对ThreadPoolExecutor的封装。
在 Java 程序中怎么保障多线程的运行平安?
- 应用安全类,比方 Java. util. concurrent 下的类。
- 应用主动锁 synchronized。
- 应用手动锁 Lock。
9. 什么是死锁?怎么避免死锁?
当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的状况下,就会产生 AB 两个线程因为相互持有对方须要的锁,而产生的阻塞景象,咱们称为死锁。
避免死锁办法:
尽量应用 tryLock(long timeout, TimeUnit unit)的办法(ReentrantLock、ReentrantReadWriteLock),设置超时工夫,超时能够退出避免死锁。
尽量应用 Java. util. concurrent 并发类代替本人手写锁。
尽量升高锁的应用粒度,尽量不要几个性能用同一把锁。
尽量减少同步的代码块。
synchronized 和 volatile 的区别是什么?
- volatile 是变量修饰符;synchronized 是润饰类、办法、代码段。
- volatile 仅能实现变量的批改可见性,不能保障原子性;而 synchronized 则能够保障变量的批改可见性和原子性。
- volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
synchronized 和 Lock 有什么区别?
- synchronized 能够给类、办法、代码块加锁;而 lock 只能给代码块加锁。
- synchronized 不须要手动获取锁和开释锁,应用简略,产生异样会主动开释锁,不会造成死锁;而 lock 须要本人加锁和开释锁,如果使用不当没有 unLock()去开释锁就会造成死锁。
- 通过 Lock 能够晓得有没有胜利获取锁,而 synchronized 却无奈办到。
synchronized 和 ReentrantLock 区别是什么?
- ReentrantLock 应用起来比拟灵便,然而必须有开释锁的配合动作;
- ReentrantLock 必须手动获取与开释锁,而 synchronized 不须要手动开释和开启锁;
- ReentrantLock 只实用于代码块锁,而 synchronized 可用于润饰办法、代码块等。
为什么应用线程池?
因为创立和销毁线程都须要很大的开销,使用线程池就能够大大的缓解这些内存开销很大的问题;能够依据零碎的承受能力,调整线程池中工作线线程的数目,避免因为耗费过多的内存。
Activiti?
个别抉择为Activiti作为工作流模块的引擎。
Activiti领有更简洁强壮的接口;
Activiti领有更敌对的用户体验;
Activiti反对启动引擎后随时热部署;
Activiti领有更敌对易用的Eclipse编译插件和在线插件;
Activiti依赖更少的jar包。
rabbitmq 的音讯是怎么发送的?
首先客户端必须连贯到 RabbitMQ 服务器能力公布和生产音讯,客户端和 rabbit server 之间会创立一个 tcp 连贯,一旦 tcp 关上并通过了认证(认证就是你发送给 rabbit 服务器的用户名和明码),你的客户端和 RabbitMQ 就创立了一条 amqp 信道(channel),信道是创立在“实在” tcp 上的虚构连贯,amqp 命令都是通过信道发送进来的,每个信道都会有一个惟一的 id,不论是公布音讯,订阅队列都是通过这个信道实现的。
rabbitmq怎么保障音讯的稳定性?
提供了事务的性能。
通过将 channel 设置为 confirm(确认)模式。
rabbitmq怎么防止音讯失落?
音讯长久化;
ACK确认机制;
设置集群镜像模式;
音讯弥补机制。
rabbitmq 有哪些重要的角色?
RabbitMQ 中重要的角色有:生产者、消费者和代理:
- 生产者:音讯的创建者,负责创立和推送数据到音讯服务器;
- 消费者:音讯的接管方,用于解决数据和确认音讯;
- 代理:就是 RabbitMQ 自身,用于表演“快递”的角色,自身不生产音讯,只是表演“快递”的角色。
rabbitmq 有哪些重要的组件?
- ConnectionFactory(连贯管理器):应用程序与Rabbit之间建设连贯的管理器,程序代码中应用。
- Channel(信道):音讯推送应用的通道。
- Exchange(交换器):用于承受、调配音讯。
- Queue(队列):用于存储生产者的音讯。
- RoutingKey(路由键):用于把生成者的数据调配到交换器上。
- BindingKey(绑定键):用于把交换器的音讯绑定到队列上。
rabbitmq 中 vhost 的作用是什么?
vhost 能够了解为虚构 broker ,即 mini-RabbitMQ server。其外部均含有独立的 queue、exchange 和 binding 等,但最最重要的是,其领有独立的权限零碎,能够做到 vhost 范畴的用户管制。当然,从 RabbitMQ 的全局角度,vhost 能够作为不同权限隔离的伎俩(一个典型的例子就是不同的利用能够跑在不同的 vhost 中)。
要保障音讯长久化胜利的条件有哪些?
- 申明队列必须设置长久化 durable 设置为 true.
- 音讯推送投递模式必须设置长久化,deliveryMode 设置为 2(长久)。
- 音讯曾经达到长久化交换器。
- 音讯曾经达到长久化队列。
rabbitmq 长久化有什么毛病?
长久化的缺地就是升高了服务器的吞吐量,因为应用的是磁盘而非内存存储,从而升高了吞吐量。可尽量应用 ssd 硬盘来缓解吞吐量的问题。
rabbitmq 有几种播送类型?
- fanout: 所有bind到此exchange的queue都能够接管音讯(纯播送,绑定到RabbitMQ的接受者都能收到音讯);
- direct: 通过routingKey和exchange决定的那个惟一的queue能够接管音讯;
- topic:所有合乎routingKey(此时能够是一个表达式)的routingKey所bind的queue能够接管音讯;
rabbitmq 怎么实现提早音讯队列?
- 通过音讯过期后进入死信交换器,再由交换器转发到提早生产队列,实现提早性能;
- 应用 RabbitMQ-delayed-message-exchange 插件实现提早性能。
rabbitmq 集群有什么用?
集群次要有以下两个用处:
- 高可用:某个服务器呈现问题,整个 RabbitMQ 还能够持续应用;
- 高容量:集群能够承载更多的音讯量。
rabbitmq 节点的类型有哪些?
- 磁盘节点:音讯会存储到磁盘。
- 内存节点:音讯都存储在内存中,重启服务器音讯失落,性能高于磁盘类型。
rabbitmq 集群搭建须要留神哪些问题?
- 各节点之间应用“--link”连贯,此属性不能疏忽。
- 各节点应用的 erlang cookie 值必须雷同,此值相当于“秘钥”的性能,用于各节点的认证。
- 整个集群中必须蕴含一个磁盘节点。
rabbitmq 每个节点是其余节点的残缺拷贝吗?为什么?
不是,起因有以下两个:
- 存储空间的思考:如果每个节点都领有所有队列的齐全拷贝,这样新增节点岂但没有新增存储空间,反而减少了更多的冗余数据;
- 性能的思考:如果每条音讯都须要残缺拷贝到每一个集群节点,那新增节点并没有晋升解决音讯的能力,最多是放弃和单节点雷同的性能甚至是更糟。
rabbitmq 集群中惟一一个磁盘节点解体了会产生什么状况?
如果惟一磁盘的磁盘节点解体了,不能进行以下操作:
- 不能创立队列
- 不能创立交换器
- 不能创立绑定
- 不能增加用户
- 不能更改权限
- 不能增加和删除集群节点
惟一磁盘节点解体了,集群是能够放弃运行的,但你不能更改任何货色。
rabbitmq 对集群节点进行程序有要求吗?
RabbitMQ 对集群的进行的程序是有要求的,应该先敞开内存节点,最初再敞开磁盘节点。如果程序恰好相反的话,可能会造成音讯的失落。
kafka 能够脱离 zookeeper 独自应用吗?为什么?
kafka 不能脱离 zookeeper 独自应用,因为 kafka 应用 zookeeper 治理和协调 kafka 的节点服务器。
kafka 有几种数据保留的策略?
kafka 有两种数据保留策略:依照过期工夫保留和依照存储的音讯大小保留。
kafka 同时设置了 7 天和 10G 革除数据,到第五天的时候音讯达到了 10G,这个时候 kafka 将如何解决?
这个时候 kafka 会执行数据革除工作,工夫和大小不管那个满足条件,都会清空数据。
什么状况会导致 kafka 运行变慢?
- cpu 性能瓶颈
- 磁盘读写瓶颈
- 网络瓶颈
应用 kafka 集群须要留神什么?
- 集群的数量不是越多越好,最好不要超过 7 个,因为节点越多,音讯复制须要的工夫就越长,整个群组的吞吐量就越低。
- 集群数量最好是复数,因为超过一半故障集群就不能用了,设置为复数容错率更高。