首页 文章 精选 留言 我的

精选列表

搜索[增删改查],共8409篇文章
优秀的个人博客,低调大师

当面付ALI39448和ALI39441排方案

说明: 该贴说明的是当面付花呗分期报错:当前交易不支持花呗分期,请使用其它方式付款(ALI39448) PS:还有一种报错也有:当前交易不支持花呗分期,请使用其它方式付款(ALI39441)报错截图如下: 报错自查:1.是否签约了花呗分期产品2.是否传入了对应的花呗分期参数,参考【花呗分期商家接入说明】3.传入的是否是实物交易,虚拟交易是不行的。参考该贴【支付渠道以及商品类型说明】:[url]https://openclub.alipay.com/read.php?tid=4541[/url]4.更换参数中的值total_amount为400以下金额,如果可以测试接口通过而不报错但400及以上到大几千都报这个错误,这就是花呗分期被限额了,建议咨询【商户客服】 :[url]https://ope

优秀的个人博客,低调大师

Omdia:预计中国生成式 AI 软件市场五年超 4 倍

Omdia发布报告称,2024年中国的生成式人工智能(GenAI)软件收入已达到18亿美元。 鉴于该技术仍处于起步阶段,预计未来五年的市场规模将增长超4倍,到2029年达到98亿美元。这一收入还不包括其他关键GenAI技术,如人工智能训练和推理芯片和服务器、人工智能数据中心建设和场地购置成本,以及GenAI开发平台收入。 报告指出,GenAI 厂商无法单靠消费者市场来生存。像 OpenAI 这样的 GenAI 市场领导者都无法单凭面向消费者的对话式人工智能来维持营收。中国 GenAI 解决方案供应商正将企业应用作为更具商业可行性的前进道路。其中,面向企业市场的智能体成为新赛道。 阿里巴巴、百度、华为和腾讯等主要厂商正在为企业提供智能体开发平台、现成模板和针对特定应用的智能体,同时大幅降低大模型推理的价格。 进入到 2025 年,Omdia 预计这些厂商将进一步开发智能体相关的能力,包括多模态大模型、多智能体协作和协调框架、支援其他数据类型的检索增强生成(RAG),以及支援边缘侧和端侧应用的更小、更高效的行业模型和小模型。其中的佼佼者正是DeepSeek。企业能利用他们的强化学习和蒸馏技术将大模型部署在在资源受限的的运行环境中。 同时,中国企业也需要在严重的资源限制和严格的法规环境下部署 GenAI,因此他们对 GenAI 投资回报率(ROI)的审查也将越来越严格。 眼看GenAI六小虎的发展,预计 2025 年新大模型的开发门槛将大大提高。并非所有厂商都能从万卡过渡到十万卡、从单个大规模数据中心到多个数据中心互联。因此,今年有可能将出现频密的市场整合和玩家退圈。 Omdia 预计数个大模型厂商将转型为 GenAI 平台或 GenAI 应用厂商,并将重心放在海外市场。

优秀的个人博客,低调大师

Filecoin正式启动NFT分布式存储服务,恐更多安全威胁

近日,分布式存储协议Filecoin宣布推出了一项全新的免费服务:NFT.Storage,该项服务是专为不可分割、不可替代、独一无二的,不可替代的代币(NFT)推出的分布式存储链下合约,允许用户离线保存NFT元数据和内容。 “ NFT是人类文化遗产的一部分,设计长期可访问的NFT数据至关重要。”Filecoin官方Protocol Labs实验室的工程师Mikeal Rogers说:“内容寻址和分布式存储网络确保数字艺术品、篮球卡和虚拟不动产长期安全可用。” 为什么存储NFT很重要? NFT是密码学上唯一的令牌,可以用来表示数字内容,例如艺术品和音乐。由于它们不可互换,稀缺并且可以作为一种或小批量铸造,因此,NFT在收藏家群体中如雨后春笋般涌现,其中不乏一些以高得离谱的价格成交,市场非常火爆。 但是有一个问题!尽管NFT名义上可以代表艺术品,音乐或视频,但就此而言,区块链令牌实际上并不包含文件本身,甚至不包含其作者的名字。取而代之的是,NFT本质上是元数据的集合,指向它们所代表的文件链接,这些链接需要由第三方(例如拍卖平台)托管。 正因为如此,如果它们所代表的文件在互联网上不再可用,数百万美元的非金融交易就有可能失效。例如,如果托管文件的第三方公司破产了,且没有任何可证明的东西将NFT连接到实际的媒体文件,NFT甚至可能失去其作为真实性和所有权的数字证书的身份。 保持文件可访问性 为了解决这个问题,Filecoin官方Protocol Labs实验室和云存储服务提供商Pinata引入了一种去中心化协议NFT.Storage,该协议允许用户通过内容寻址和去中心化存储来“备份”NFT和相应的媒体文件。 NFT.Storage完全基于Filecoin主网和星际文件系统(IPFS)技术推出,IPFS技术可以让用户自由创建文件内容的哈希值(内容标识符或CID),以便于查找相应的文件,而这些文件被托管在世界各地的IPFS节点上,此方法可以确保只要有一台主机能提供访问服务,便始终可以找到这些文件,这里当然也包括NFT所附带的文件。 云存储服务提供商Pinata通过官网强调,NFT.Storage服务可以为客户的NFT提供“安全且可验证的文件”,用户在通过Pinata服务器上传内容时,需在铸造NFT代币NFS之前绑定文件内容的IPFS CID。 Filecoin官方在公告中表示,这种称为内容寻址的方法,可以有效防止诸如防止丢失或被“rug-pulls"的问题,因为所有CID是从内容本身生成的。NFT.Storage使用Filecoin进行长期分散的数据存储代理存储和检索交易,以长期保存NFT数据。Filecoin提供了一个使用加密证明的持久层,以确保NFT数据的持久性和持久性。 NFT.Storage将使用IPFS为所有新的NFT创建数字指纹,无论其存储方式或存储位置如何,该数字指纹都能通用地引用该内容,使用这些数字指纹来引用NFT资产和元数据可以。 安全的不确定性 不过,这里还有一些注意事项。尽管Filecoin声明NFT将在其服务上“保证长期安全可用”,但NFT.Storage并不能完全免除与其他第三方托管服务提供商相同的不确定性。因为Filecoin在NFT.Storage的声明中加入了免责条款:“Protocol Labs Inc.将持续为NFT用户提供免费存储服务,数据将无限期地永久保存,直到Protocol Labs决定结束NFT.storage项目为止,Protocol Labs Inc.保留自行决定终止NFT.storage的权利,Protocol Labs Inc.承诺在可能需要终止服务前90天内通知NFT爱好者,给用户足够的时间来安排通过其他方式存储其数据。“ 事实上,NFT.Storage服务的推出将能提升IPFS各节点存储数据的价值,毕竟NFT代表的是海量的高价值物品或文件,数字资产价值提升,必然更会引起恶意攻击者的注意,可以预见,针对IPFS的攻击将会更加密集与高级。因此,尽管NFT.Storage能为NFT创作者和所有者提供额外的安全保护层,但它依然可能存在一些不可避免的安全风险。

优秀的个人博客,低调大师

光子芯片研究重大突破:电脑性能暴

“每隔24个月芯片晶体管数量增加一倍”,在过去四十年,Intel创始人之一戈登·摩尔提出的摩尔定律一直推送电子行业的发展。 但在过去几年,业内“摩尔定律”失效的声音不断,在9月26日的NVIDIA GTC大会上,黄仁勋在在开场后的前5分钟,就直截了当的表示“摩尔定律已经终结”——“设计人员无法再创造出以实现更高指令级并行性的CPU架构”“晶体管数每年增长50%,但CPU的性能每年仅增长10%”。 在未来,人类可能无法再靠过去晶体管堆叠的方式来提升计算机的性能,光子芯片被认为是未来的主要发展方向之一。 据新华社报道,英国牛津大学日前宣布,其研究人员在光子芯片研发方面取得突破,能够模拟人脑神经突触的结构形成可高速传递信息的“光子突触”,这种技术今后可能有助大幅提高电脑的速度。 牛津大学教授哈里什·巴斯卡兰表示,过去数十年,业内一直在致力于研发出能像人脑一样运转的电脑,大量神经元通过突触互相连接,同时处理和存储大量信息,十分高效,耗能也很低。 据了解,英国牛津大学的研究人员使用特殊的相变材料与集成光路开发出一种光子芯片,可形成与人脑相似的“光子突触”,其运行速度比人脑神经突触快1000倍。 值得一提的是,由于光信号的传输速度非常快,而且不会像电流传输一样发热,光子芯片前景被十分看好。此次研究今后可能会有助于大幅提升电脑运行速度。 本文转自d1net(转载)

优秀的个人博客,低调大师

还在用ES日志吗,快看看石墨文档 Clickhouse 日志架构玩法

1 背景 石墨文档全部应用部署在Kubernetes上,每时每刻都会有大量的日志输出,我们之前主要使用SLS和ES作为日志存储。但是我们在使用这些组件的时候,发现了一些问题。 成本问题: SLS个人觉得是一个非常优秀的产品,速度快,交互方便,但是SLS索引成本比较贵 我们想减少SLS索引成本的时候,发现云厂商并不支持分析单个索引的成本,导致我们无法知道是哪些索引构建的不够合理 ES使用的存储非常多,并且耗费大量的内存 通用问题: 如果业务是混合云架构,或者业务形态有SAAS和私有化两种方式,那么SLS并不能通用 日志和链路,需要用两套云产品,不是很方便 精确度问题:SLS存储的精度只能到秒,但我们实际日志精度到毫秒,如果日志里面有traceid,SLS中无法通过根据traceid信息,将日志根据毫秒时间做排序,不利于排查错误 我们经过一番调研后,发现使用Clickhouse能够很好的解决以上问题,并且Clickhouse省存储空间,非常省钱,所以我们选择了Clickhouse方案存储日志。但当我们深入研究后,Clickhouse作为日志存储有许多落地的细节,但业界并没有很好阐述相关Clickhouse采集日志的整套流程,以及没有一款优秀的Clickhouse日志查询工具帮助分析日志,为此我们写了一套Clickhouse日志系统贡献给开源社区,并将Clickhouse的日志采集架构的经验做了总结。先上个Clickhouse日志查询界面,让大家感受下石墨最懂前端的后端程序员。 2 架构原理图 我们将日志系统分为四个部分:日志采集、日志传输、日志存储、日志管理。 日志采集:LogCollector采用Daemonset方式部署,将宿主机日志目录挂载到LogCollector的容器内,LogCollector通过挂载的目录能够采集到应用日志、系统日志、K8S审计日志等 日志传输:通过不同Logstore映射到Kafka中不同的Topic,将不同数据结构的日志做了分离 日志存储:使用Clickhouse中的两种引擎数据表和物化视图 日志管理:开源的Mogo系统,能够查询日志,设置日志索引,设置LogCollector配置,设置Clickhouse表,设置报警等 以下我们按照这四大部分,阐述其中的架构原理 3 日志采集 3.1 采集方式 Kubernetes容器内日志收集的方式通常有以下三种方案 DaemonSet方式采集:在每个 node 节点上部署LogCollector,并将宿主机的目录挂载为容器的日志目录,LogCollector读取日志内容,采集到日志中心。 网络方式采集:通过应用的日志 SDK,直接将日志内容采集到日志中心 。 SideCar方式采集:在每个 pod 内部署LogCollector,LogCollector只读取这个 pod 内的日志内容,采集到日志中心。 以下是三种采集方式的优缺点: DaemonSet方式 网络方式 SideCar方式 采集日志类型 标准输出+文件 应用日志 部署运维 一般,维护DaemonSet 低,维护配置文件 日志分类存储 可通过容器/路径等映射 业务独立配置 支持集群规模 取决于配置数 无限制 适用场景 日志分类明确、功能较单一 性能要求极高的场景 资源消耗 中 低 我们主要采用DaemonSet方式和网络方式采集日志。DaemonSet方式用于ingress、应用日志的采集,网络方式用于大数据日志的采集。以下我们主要介绍下DeamonSet方式的采集方式。 ​ 3.2 日志输出 从上面的介绍中可以看到,我们的DaemonSet会有两种方式采集日志类型,一种是标准输出,一种是文件。 引用元乙的描述:虽然使用 Stdout 打印日志是 Docker 官方推荐的方式,但大家需要注意:这个推荐是基于容器只作为简单应用的场景,实际的业务场景中我们还是建议大家尽可能使用文件的方式,主要的原因有以下几点: Stdout 性能问题,从应用输出 stdout 到服务端,中间会经过好几个流程(例如普遍使用的JSON LogDriver):应用 stdout -> DockerEngine -> LogDriver -> 序列化成 JSON -> 保存到文件 -> Agent 采集文件 -> 解析 JSON -> 上传服务端。整个流程相比文件的额外开销要多很多,在压测时,每秒 10 万行日志输出就会额外占用 DockerEngine 1 个 CPU 核; Stdout 不支持分类,即所有的输出都混在一个流中,无法像文件一样分类输出,通常一个应用中有 AccessLog、ErrorLog、InterfaceLog(调用外部接口的日志)、TraceLog 等,而这些日志的格式、用途不一,如果混在同一个流中将很难采集和分析; Stdout 只支持容器的主程序输出,如果是 daemon/fork 方式运行的程序将无法使用 stdout; 文件的 Dump 方式支持各种策略,例如同步/异步写入、缓存大小、文件轮转策略、压缩策略、清除策略等,相对更加灵活。 从这个描述中,我们可以看出在docker中输出文件在采集到日志中心是一个更好的实践。所有日志采集工具都支持采集文件日志方式,但是我们在配置日志采集规则的时候,发现开源的一些日志采集工具,例如fluentbit、filebeat在DaemonSet部署下采集文件日志是不支持追加例如pod、namespace、container_name、container_id等label信息,并且也无法通过这些label做些定制化的日志采集。 agent类型 采集方式 daemonset部署 sidecar部署 ilogtail 文件日志 能够追加label信息,能够根据label过滤采集 能够追加label信息,能够根据label过滤采集 fluentbit 文件日志 无法追加label信息,无法根据label过滤采集 能够追加abel信息,能够根据label过滤采集 filebeat 文件日志 无法追加label信息,无法根据label过滤采集 能够追加label信息,能够根据label过滤采集 ilogtail 标准输出 能够追加label信息,能够根据label过滤采集 能够追加label信息,能够根据label过滤采集 fluentbit 标准输出 能够追加label信息,能够根据label过滤采集 能够追加abel信息,能够根据label过滤采集 filebeat 标准输出 能够追加label信息,能够根据label过滤采集 能够追加label信息,能够根据label过滤采集 基于无法追加label信息的原因,我们暂时放弃了DeamonSet部署下文件日志采集方式,采用的是基于DeamonSet部署下标准输出的采集方式。 ​ 3.3 日志目录 以下列举了日志目录的基本情况 目录 描述 类型 /var/log/containers 存放的是软链接,软链到/var/log/pods里的标准输出日志 ​标准输出 /var/log/pods 存放标准输出日志 ​标准输出 /var/log/kubernetes/ master存放Kubernetes 审计输出日志 标准输出 /var/lib/docker/overlay2 存放应用日志文件信息 文件日志 /var/run 获取docker.sock,用于docker通信 文件日志 /var/lib/docker/containers 用于存储容器信息 两种都需要 因为我们采集日志是使用的标准输出模式,所以根据上表我们的LogCollector只需要挂载/var/log,/var/lib/docker/containers两个目录。 3.3.1 标准输出日志目录 应用的标准输出日志存储在/var/log/containers目录下,​文件名是按照K8S日志规范生成的。这里以nginx-ingress的日志作为一个示例。我们通过ls /var/log/containers/ | grep nginx-ingress指令,可以看到nginx-ingress的文件名。 nginx-ingress-controller-mt2wx_kube-system_nginx-ingress-controller-be3741043eca1621ec4415fd87546b1beb29480ac74ab1cdd9f52003cf4abf0a.log ​ 我们参照K8S日志的规范:/var/log/containers/%{DATA:pod_name}_%{DATA:namespace}_%{GREEDYDATA:container_name}-%{DATA:container_id}.log。可以将nginx-ingress日志解析为: pod_name:nginx-ingress-controller-mt2w namespace:kube-system container_name:nginx-ingress-controller container_id:be3741043eca1621ec4415fd87546b1beb29480ac74ab1cdd9f52003cf4abf0a 通过以上的日志解析信息,我们的LogCollector 就可以很方便的追加pod、namespace、container_name、container_id的信息。 ​ 3.3.2 容器信息目录 应用的容器信息存储在/var/lib/docker/containers目录下,目录下的每一个文件夹为容器ID,我们可以通过cat config.v2.json获取应用的docker基本信息。 3.4 LogCollector采集日志 3.4.1 配置 我们LogCollector采用的是fluent-bit,该工具是cncf旗下的,能够更好的与云原生相结合。通过Mogo系统可以选择Kubernetes集群,很方便的设置fluent-bit configmap的配置规则。 3.4.2 数据结构 ​fluent-bit的默认采集数据结构 @timestamp字段:string or float,用于记录采集日志的时间 log字段:string,用于记录日志的完整内容 Clickhouse如果使用@timestamp的时候,因为里面有@特殊字符,会处理的有问题。所以我们在处理fluent-bit的采集数据结构,会做一些映射关系,并且规定双下划线为Mogo系统日志索引,避免和业务日志的索引冲突。 _time_字段:string or float,用于记录采集日志的时间 _log_字段:string,用于记录日志的完整内容 例如你的日志记录的是{"id":1},那么实际fluent-bit采集的日志会是{"_time_":"2022-01-15...","_log_":"{\"id\":1}" 该日志结构会直接写入到kafka中,Mogo系统会根据这两个字段_time_、_log_设置clickhouse中的数据表。 3.4.3 采集 如果我们要采集ingress日志,我们需要在input配置里,设置ingress的日志目录,fluent-bit会把ingress日志采集到内存里。 然后我们在filter配置里,将log改写为_log_ 然后我们在ouput配置里,将追加的日志采集时间设置为_time_,设置好日志写入的kafka borkers和kafka topics,那么fluent-bit里内存的日志就会写入到kafka中 日志写入到Kafka中_log_需要为json,如果你的应用写入的日志不是json,那么你就需要根据fluent-bit的parser文档,调整你的日志写入的数据结构:https://docs.fluentbit.io/manual/pipeline/filters/parser 4 日志传输 Kafka主要用于日志传输。上文说到我们使用fluent-bit采集日志的默认数据结构,在下图kafka工具中我们可以看到日志采集的内容。 在日志采集过程中,会由于不用业务日志字段不一致,解析方式是不一样的。所以我们在日志传输阶段,需要将不同数据结构的日志,创建不同的Clickhouse表,映射到Kafka不同的Topic。这里以ingress为例,那么我们在Clickhouse中需要创建一个ingress_stdout_stream的Kafka引擎表,然后映射到Kafka的ingress-stdout Topic里。 5 日志存储 我们会使用三种表,用于存储一种业务类型的日志。 Kafka引擎表:将数据从Kafka采集到Clickhouse的ingress_stdout_stream数据表中 createtablelogger.ingress_stdout_stream ( _source_String, _pod_name_String, _namespace_String, _node_name_String, _container_name_String, _cluster_String, _log_agent_String, _node_ip_String, _time_Float64, _log_String ) engine=KafkaSETTINGSkafka_broker_list='kafka:9092',kafka_topic_list='ingress-stdout',kafka_group_name='logger_ingress_stdout',kafka_format='JSONEachRow',kafka_num_consumers=1; 物化视图:将数据从ingress_stdout_stream数据表读取出来,_log_根据Mogo配置的索引,提取字段在写入到ingress_stdout结果表里 CREATEMATERIALIZEDVIEWlogger.ingress_stdout_viewTOlogger.ingress_stdoutAS SELECT toDateTime(toInt64(_time_))AS_time_second_, fromUnixTimestamp64Nano(toInt64(_time_*1000000000),'Asia/Shanghai')AS_time_nanosecond_, _pod_name_, _namespace_, _node_name_, _container_name_, _cluster_, _log_agent_, _node_ip_, _source_, _log_AS_raw_log_,JSONExtractInt(_log_,'status')ASstatus,JSONExtractString(_log_,'url')ASurl FROMlogger.ingress_stdout_streamwhere1=1; 结果表:存储最终的数据 createtablelogger.ingress_stdout ( _time_second_DateTime, _time_nanosecond_DateTime64(9,'Asia/Shanghai'), _source_String, _cluster_String, _log_agent_String, _namespace_String, _node_name_String, _node_ip_String, _container_name_String, _pod_name_String, _raw_log_String, statusNullable(Int64), urlNullable(String), ) engine=MergeTreePARTITIONBYtoYYYYMMDD(_time_second_) ORDERBY_time_second_ TTLtoDateTime(_time_second_)+INTERVAL7DAY SETTINGSindex_granularity=8192; 6 总结流程 日志会通过fluent-bit的规则采集到kafka,在这里我们会将日志采集到两个字段里 _time_字段用于存储fluent-bit采集的时间 _log_字段用于存放原始日志 通过mogo,在clickhouse里设置了三个表 app_stdout_stream: 将数据从Kafka采集到Clickhouse的Kafka引擎表 app_stdout_view: 视图表用于存放mogo设置的索引规则 app_stdout:根据app_stdout_view索引解析规则,消费app_stdout_stream里的数据,存放于app_stdout结果表中 最后mogo的UI界面,根据app_stdout的数据,查询日志信息 7 Mogo界面展示 查询日志界面 设置日志采集配置界面 以上文档描述是针对石墨Kubernetes的日志采集,想了解物理机采集日志方案的,可以在下文中找到《Mogo使用文档》的链接,运行docker-compose体验Mogo 全部流程,查询Clickhouse日志。限于篇幅有限,Mogo的日志报警功能,下次在讲解。 ​ 8 资料 github地址: https://github.com/shimohq/mogo Mogo文档:https://mogo.shimo.im Mogo使用文档:https://mogo.shimo.im/doc/AV62KU4AABMRQ fluent-bit文档:https://docs.fluentbit.io/ K8S日志 6 个 K8S 日志系统建设中的典型问题,你遇到过几个:https://developer.aliyun.com/article/718735 一文看懂 K8S 日志系统设计和实践:https://developer.aliyun.com/article/727594 9 个技巧,解决 K8S 中的日志输出问题:https://developer.aliyun.com/article/747821 直击痛点,详解 K8S 日志采集最佳实践:https://developer.aliyun.com/article/749468?spm=a2c6h.14164896.0.0.24031164UoPfIX Clickhouse Clickhouse官方文档:https://clickhouse.com/ Clickhouse作为Kubernetes日志管理解决方案中的存储:http://dockone.io/article/9356 Uber 如何使用 ClickHouse 建立快速可靠且与模式无关的日志分析平台?:https://www.infoq.cn/article/l4thjgnr7hxpkgpmw6dz 干货 | 携程ClickHouse日志分析实践:https://mp.weixin.qq.com/s/IjOWAPOJXANRQqRAMWXmaw 为什么我们要从ES迁移到ClickHouse:https://mp.weixin.qq.com/s/l4RgNQPxvdNIqx52LEgBnQ ClickHouse 在日志存储与分析方面作为 ElasticSearch 和 MySQL 的替代方案:https://mp.weixin.qq.com/s/nJXorcgi0QfXPCKr_HdUZg 快手、携程等公司转战到 ClickHouse,ES 难道不行了?:https://mp.weixin.qq.com/s/hP0ocT-cBCeIl9n1wL_HBg 日志分析下ES/ClickHouse/Loki比较与思考:https://mp.weixin.qq.com/s/n2I94X6tz2jOABzl1djxYg

优秀的个人博客,低调大师

【Android面试漏补缺】之Handler详解,带你全面理解Handler消息机制

在安卓面试中,关于 Handler 的问题是必备的,但是这些关于 Handler 的知识点你都知道吗? 一、题目层次 Handler 的基本原理 子线程中怎么使用 Handler MessageQueue 获取消息是怎么等待 为什么不用 wait 而用 epoll 呢? 线程和 Handler Looper MessageQueue 的关系 多个线程给 MessageQueue 发消息,如何保证线程安全 Handler 消息延迟是怎么处理的 View.post 和 Handler.post 的区别 Handler 导致的内存泄漏 非 UI 线程真的不能操作 View 吗 二、题目详解 代码分析基于 Android SDK 28 大家可以先看上面的问题思考一下,如果都清楚的话,下面的文章也没必要看了~ 1. Handler 的基本原理 关于 Handler 的原理,相比不用多说了,大家都应该知道,一张图就可以说明(图片来自网络)。 2. 子线程中怎么使用 Handler 除了上面 Handler 的基本原理,子线程中如何使用 Handler 也是一个常见的问题。 子线程中使用 Handler 需要先执行两个操作:Looper.prepare 和 Looper.loop。 为什么需要这样做呢?Looper.prepare 和 Looper.loop 都做了什么事情呢? 我们知道如果在子线程中直接创建一个 Handler 的话,会报如下的错误: "Can't create handler inside thread xxx that has not called Looper.prepare() 我们可以看一下 Handler 的构造函数,里面会对 Looper 进行判断,如果通过 ThreadLocal 获取的 Looper 为空,则报上面的错误。 public Handler(Callback callback, boolean async) { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } } public static @Nullable Looper myLooper() { return sThreadLocal.get(); } 那么 Looper.prepare 里做了什么事情呢? private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } 可以看到,Looper.prepare 就是创建了 Looper 并设置给 ThreadLocal,这里的一个细节是每个 Thread 只能有一个 Looper,否则也会抛出异常。 而 Looper.loop 就是开始读取 MessageQueue 中的消息,进行执行了。 这里一般会引申一个问题,就是主线程中为什么不用手动调用这两个方法呢?相信大家也都明白,就是 ActivityThread.main 中已经进行了调用。 通过这个问题,又可以引申到 ActivityThread 相关的知识,这里就不细说了。 3. MessageQueue 如何等待消息 上面说到 Looper.loop 其实就是开始读取 MessageQueue 中的消息了,那 MessageQueue 中没有消息的时候,Looper 在做什么呢?我们知道是在等待消息,那是怎么等待的呢? 通过 Looper.loop 方法,我们知道是 MessageQueue.next() 来获取消息的,如果没有消息,那就会阻塞在这里,MessageQueue.next 是怎么等待的呢? public static void loop() { final MessageQueue queue = me.mQueue; for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } } } Message next() { for (;;) { nativePollOnce(ptr, nextPollTimeoutMillis); // ... } } 在 MessageQueue.next 里调用了 native 方法 nativePollOnce。 // android_os_MessageQueue.cpp static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->pollOnce(env, obj, timeoutMillis); } void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) { // ... mLooper->pollOnce(timeoutMillis); // ... } // Looper.cpp int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { // ... result = pollInner(timeoutMillis); // ... } int Looper::pollInner(int timeoutMillis) { // ... int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); } 从上面代码中我们可以看到,在 native 侧,最终是使用了 epoll_wait 来进行等待的。 这里的 epoll_wait 是 Linux 中 epoll 机制中的一环,关于 epoll 机制这里就不进行过多介绍了,大家有兴趣可以参考https://segmentfault.com/a/1190000003063859 那其实说到这里,又有一个问题,为什么不用 java 中的 wait / notify 而是要用 native 的 epoll 机制呢? 4. 为什么不用 wait 而用 epoll 呢? 说起来 java 中的 wait / notify 也能实现阻塞等待消息的功能,在 Android 2.2 及以前,也确实是这样做的。 可以参考这个 commithttps://www.androidos.net.cn/android/2.1_r2.1p2/xref/frameworks/base/core/java/android/os/MessageQueue.java 那为什么后面要改成使用 epoll 呢?通过看 commit 记录,是需要处理 native 侧的事件,所以只使用 java 的 wait / notify 就不够用了。 具体的改动就是这个 commithttps://android.googlesource.com/platform/frameworks/base/+/fa9e7c05c7be6891a6cf85a11dc635a6e6853078%5E%21/#F0 Sketch of Native input for MessageQueue / Looper / ViewRoot MessageQueue now uses a socket for internal signalling, and is prepared to also handle any number of event input pipes, once the plumbing is set up with ViewRoot / Looper to tell it about them as appropriate. Change-Id: If9eda174a6c26887dc51b12b14b390e724e73ab3 不过这里最开始使用的还是 select,后面才改成 epoll。 具体可见这个 commithttps://android.googlesource.com/platform/frameworks/base/+/46b9ac0ae2162309774a7478cd9d4e578747bfc2%5E%21/#F16 至于 select 和 epoll 的区别,这里也不细说了,大家可以在上面的参考文章中一起看看。 5. 线程和 Handler Looper MessageQueue 的关系 这里的关系是一个线程对应一个 Looper 对应一个 MessageQueue 对应多个 Handler。 6. 多个线程给 MessageQueue 发消息,如何保证线程安全 既然一个线程对应一个 MessageQueue,那多个线程给 MessageQueue 发消息时是如何保证线程安全的呢? 说来简单,就是加了个锁而已。 // MessageQueue.java boolean enqueueMessage(Message msg, long when) { synchronized (this) { // ... } } 7. Handler 消息延迟是怎么处理的 Handler 引申的另一个问题就是延迟消息在 Handler 中是怎么处理的?定时器还是其他方法? 这里我们先从事件发起开始看起: // Handler.java public final boolean postDelayed(Runnable r, long delayMillis) { return sendMessageDelayed(getPostMessage(r), delayMillis); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { // 传入的 time 是 uptimeMillis + delayMillis return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { // ... return enqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { // 调用 MessageQueue.enqueueMessage return queue.enqueueMessage(msg, uptimeMillis); } 从上面的代码逻辑来看,Handler post 消息以后,一直调用到 MessageQueue.enqueueMessage 里,其中最重要的一步操作就是传入的时间是 uptimeMillis + delayMillis。 boolean enqueueMessage(Message msg, long when) { synchronized (this) { // ... msg.when = when; Message p = mMessages; // 下一条消息 // 根据 when 进行顺序排序,将消息插入到其中 if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { // 找到 合适的节点 Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } } // 插入操作 msg.next = p; // invariant: p == prev.next prev.next = msg; } // 唤醒队列进行取消息 if (needWake) { nativeWake(mPtr); } } return true; } 通过上面代码我们看到,post 一个延迟消息时,在 MessageQueue 中会根据 when 的时长进行一个顺序排序。 接着我们再看看怎么使用 when 的。 Message next() { // ... for (;;) { // 通过 epoll_wait 等待消息,等待 nextPollTimeoutMillis 时长 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // 当前时间 final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // 获得一个有效的消息 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // 说明需要延迟执行,通过; nativePollOnce 的 timeout 来进行延迟 // 获取需要等待执行的时间 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 立即执行的消息,直接返回 // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { // 当前没有消息要执行,则执行 IdleHandler 中的内容 pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // 如果没有 IdleHandler 需要执行,则去等待 消息的执行 mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // 执行 idle handlers 内容 for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // 如果执行了 idle handlers 的内容,现在消息可能已经到了执行时间,所以这个时候就不等待了,再去检查一下消息是否可以执行, nextPollTimeoutMillis 需要置为 0 nextPollTimeoutMillis = 0; } } 通过上面的代码分析,我们知道了执行 Handler.postDelayd 时候,会执行下面几个步骤: 将我们传入的延迟时间转化成距离开机时间的毫秒数 MessageQueue 中根据上一步转化的时间进行顺序排序 在 MessageQueue.next 获取消息时,对比当前时间(now)和第一步转化的时间(when),如果 now < when,则通过 epoll_wait 的 timeout 进行等待 如果该消息需要等待,会进行 idel handlers 的执行,执行完以后会再去检查此消息是否可以执行 8. View.post 和 Handler.post 的区别 我们最常用的 Handler 功能就是 Handler.post,除此之外,还有 View.post 也经常会用到,那么这两个有什么区别呢? 我们先看下 View.post 的代码。 // View.java public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. getRunQueue().post(action); return true; } 通过代码来看,如果 AttachInfo 不为空,则通过 handler 去执行,如果 handler 为空,则通过 RunQueue 去执行。 那我们先看看这里的 AttachInfo 是什么。 这个就需要追溯到 ViewRootImpl 的流程里了,我们先看下面这段代码。 // ViewRootImpl.java final ViewRootHandler mHandler = new ViewRootHandler(); public ViewRootImpl(Context context, Display display) { // ... mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); } private void performTraversals() { final View host = mView; // ... if (mFirst) { host.dispatchAttachedToWindow(mAttachInfo, 0); mFirst = false; } // ... } 代码写了一些关键部分,在 ViewRootImpl 构造函数里,创建了 mAttachInfo,然后在 performTraversals 里,如果 mFirst 为 true,则调用 host.dispatchAttachedToWindow,这里的 host 就是 DecorView,如果有读者朋友对这里不太清楚,可以看看前面【面试官带你学安卓-从View的绘制流程】说起这篇文章复习一下。 这里还有一个知识点就是 mAttachInfo 中的 mHandler 其实是 ViewRootImpl 内部的 ViewRootHandler。 然后就调用到了 DecorView.dispatchAttachedToWindow,其实就是 ViewGroup 的 dispatchAttachedToWindow,一般 ViewGroup 中相关的方法,都是去依次调用 child 的对应方法,这个也不例外,依次调用子 View 的 dispatchAttachedToWindow,把 AttachInfo 传进去,在 子 View 中给 mAttachInfo 赋值。 // ViewGroup void dispatchAttachedToWindow(AttachInfo info, int visibility) { mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW; super.dispatchAttachedToWindow(info, visibility); mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW; final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility())); } final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); for (int i = 0; i < transientCount; ++i) { View view = mTransientViews.get(i); view.dispatchAttachedToWindow(info, combineVisibility(visibility, view.getVisibility())); } } // View void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; // ... } 看到这里,大家可能忘记我们开始刚刚要做什么了。 我们是在看 View.post 的流程,再回顾一下 View.post 的代码: // View.java public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } getRunQueue().post(action); return true; } 现在我们知道 attachInfo 是什么了,是 ViewRootImpl 首次触发 performTraversals 传进来的,也就是触发 performTraversals 之后,View.post 都是通过 ViewRootImpl 内部的 Handler 进行处理的。 如果在 performTraversals 之前或者 mAttachInfo 置为空以后进行执行,则通过 RunQueue 进行处理。 那我们再看看 getRunQueue().post(action); 做了些什么事情。 这里的 RunQueue 其实是 HandlerActionQueue。 HandlerActionQueue 的代码看一下。 public class HandlerActionQueue { public void post(Runnable action) { postDelayed(action, 0); } public void postDelayed(Runnable action, long delayMillis) { final HandlerAction handlerAction = new HandlerAction(action, delayMillis); synchronized (this) { if (mActions == null) { mActions = new HandlerAction[4]; } mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); mCount++; } } public void executeActions(Handler handler) { synchronized (this) { final HandlerAction[] actions = mActions; for (int i = 0, count = mCount; i < count; i++) { final HandlerAction handlerAction = actions[i]; handler.postDelayed(handlerAction.action, handlerAction.delay); } mActions = null; mCount = 0; } } } 通过上面的代码我们可以看到,执行 getRunQueue().post(action); 其实是将代码添加到 mActions 进行保存,然后在 executeActions 的时候进行执行。 executeActions 执行的时机只有一个,就是在 dispatchAttachedToWindow(AttachInfo info, int visibility) 里面调用的。 void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; if (mRunQueue != null) { mRunQueue.executeActions(info.mHandler); mRunQueue = null; } } 看到这里我们就知道了,View.post 和 Handler.post 的区别就是: 如果在 performTraversals 前调用 View.post,则会将消息进行保存,之后在 dispatchAttachedToWindow 的时候通过 ViewRootImpl 中的 Handler 进行调用。 如果在 performTraversals 以后调用 View.post,则直接通过 ViewRootImpl 中的 Handler 进行调用。 这里我们又可以回答一个问题了,就是为什么 View.post 里可以拿到 View 的宽高信息呢? 因为 View.post 的 Runnable 执行的时候,已经执行过 performTraversals 了,也就是 View 的 measure layout draw 方法都执行过了,自然可以获取到 View 的宽高信息了。 9. Handler 导致的内存泄漏 这个问题就是老生常谈了,可以由此再引申出内存泄漏的知识点,比如:如何排查内存泄漏,如何避免内存泄漏等等。 10. 非 UI 线程真的不能操作 View 吗 我们使用 Handler 最多的一个场景就是在非主线程通过 Handler 去操作 主线程的 View。 那么非 UI 线程真的不能操作 View 吗? 我们在执行 UI 操作的时候,都会调用到 ViewRootImpl 里,以 requestLayout 为例,在 requestLayout 里会通过 checkThread 进行线程的检查。 // ViewRootImpl.java public ViewRootImpl(Context context, Display display) { mThread = Thread.currentThread(); } public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } } void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } 我们看这里的检查,其实并不是检查主线程,是检查 mThread != Thread.currentThread,而 mThread 指的是 ViewRootImpl 创建的线程。 所以非 UI 线程确实不能操作 View,但是检查的是创建的线程是否是当前线程,因为 ViewRootImpl 创建是在主线程创建的,所以在非主线程操作 UI 过不了这里的检查。 三、总结 一个小小的 Handler,其实可以引申出很多问题,这里这是列举了一些大家可能忽略的问题,更多的问题就等待大家去探索了~ 这里来总结一下: 1. Handler 的基本原理 一张图解释(图片来自网络) 2. 子线程中怎么使用 Handler Looper.prepare 创建 Looper 并添加到 ThreadLocal 中 Looper.loop 启动 Looper 的循环 3. MessageQueue 获取消息是怎么等待 通过 epoll 机制进行等待和唤醒。 4. 为什么不用 wait 而用 epoll 呢? 在 Android 2.2 及之前,使用 Java wait / notify 进行等待,在 2.3 以后,使用 epoll 机制,为了可以同时处理 native 侧的消息。 5. 线程和 Handler Looper MessageQueue 的关系 一个线程对应一个 Looper 对应一个 MessageQueue 对应多个 Handler。 6. 多个线程给 MessageQueue 发消息,如何保证线程安全 通过对 MessageQueue 加锁来保证线程安全。 7. Handler 消息延迟是怎么处理的 将传入的延迟时间转化成距离开机时间的毫秒数 MessageQueue 中根据上一步转化的时间进行顺序排序 在 MessageQueue.next 获取消息时,对比当前时间(now)和第一步转化的时间(when),如果 now < when,则通过 epoll_wait 的 timeout 进行等待 如果该消息需要等待,会进行 idel handlers 的执行,执行完以后会再去检查此消息是否可以执行 8. View.post 和 Handler.post 的区别 View.post 最终也是通过 Handler.post 来执行消息的,执行过程如下: 如果在 performTraversals 前调用 View.post,则会将消息进行保存,之后在 dispatchAttachedToWindow 的时候通过 ViewRootImpl 中的 Handler 进行调用。 如果在 performTraversals 以后调用 View.post,则直接通过 ViewRootImpl 中的 Handler 进行调用。 9. Handler 导致的内存泄漏 略过不讲~ 10. 非 UI 线程真的不能操作 View 吗 不能操作,原因是 ViewRootImpl 会检查创建 ViewRootImpl 的线程和当前操作的线程是否一致。而 ViewRootImpl 是在主线程创建的,所以非主线程不能操作 View。 最后 职业生涯从来不是百米赛跑,而是马拉松,不断投资自己,获得可以迁移的技能,独立思考的能力,到中后期越是软性的技能越能给你加成,愿诸位工程师能够远离焦虑,活出多彩的人生。 最近断断续续整理了一些面试题,目的是想了解一下大厂招聘的技术热点,不断提升学习,有兴趣的朋友【点击我】免费获取哦。 篇幅有限,仅展示部分内容

优秀的个人博客,低调大师

漏补缺:2020年搞定SpringCloud面试(含答案和思维导图)

前言 Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。 Spring Cloud有以下特点:约定优于配置;适用于各种环境。开发、部署PC Server或各种云环境(例如阿里云、AWS等)均可;隐藏了组件的复杂性,并提供声明式、无xml的配置方式;开箱即用,快速启动;轻量级的组件。Spring Cloud整合的组件大多比较轻量。例如Eureka、Zuul等,都是各自领域轻量级的实现;组件丰富,功能齐全。Spring Cloud 为微服务架构提供了非常完整的支持。例如、配置管理、服务发现、断路器、微服务网关等;选型中立、丰富。例如,Spring Cloud支持使用Eureka、Zookeeper或Consul实现服务发现;灵活。Spring Cloud的组成部分是解耦的,开发人员可以按需灵活挑选技术选型。 关于Spring Cloud微服务架构的知识总结了个思维导图分享给大家 Spring Cloud面试题 1、什么是 Spring Cloud?2、使用 Spring Cloud 有什么优势?3、服务注册和发现是什么意思?Spring Cloud 如何实现?4、Spring Cloud 和dubbo区别?5、SpringBoot和SpringCloud的区别?6、负载平衡的意义什么?7、什么是 Hystrix?它如何实现容错?8、什么是 Hystrix 断路器?我们需要它吗?9、什么是 Netflix Feign?它的优点是什么?10、什么是 Spring Cloud Bus?我们需要它吗?11、Spring Cloud断路器的作用12、什么是SpringCloudConfig?13、Spring Cloud Gateway? 1、什么是 Spring Cloud? Spring cloud 流应用程序启动器是基于 Spring Boot 的 Spring 集成应用程序,提供与外部系统的集成。Spring cloud Task,一个生命周期短暂的微服务框架,用于快速构建执行有限数据处理的应用程序。 2、使用 Spring Cloud 有什么优势? 使用 Spring Boot 开发分布式微服务时,我们面临以下问题(1)与分布式系统相关的复杂性-这种开销包括网络问题,延迟开销,带宽问题,安全问题。(2)服务发现-服务发现工具管理群集中的流程和服务如何查找和互相交谈。它涉及一个服务目录,在该目录中注册服务,然后能够查找并连接到该目录中的服务。(3)冗余-分布式系统中的冗余问题。(4)负载平衡 --负载平衡改善跨多个计算资源的工作负荷,诸如计算机,计算机集群,网络链路,中央处理单元,或磁盘驱动器的分布。(5)性能-问题 由于各种运营开销导致的性能问题。(6)部署复杂性-Devops 技能的要求。 3、服务注册和发现是什么意思?Spring Cloud 如何实现? 当我们开始一个项目时,我们通常在属性文件中进行所有的配置。随着越来越多的服务开发和部署,添加和修改这些属性变得更加复杂。有些服务可能会下降,而某些位置可能会发生变化。手动更改属性可能会产生问题。 Eureka 服务注册和发现可以在这种情况下提供帮助。由于所有服务都在 Eureka 服务器上注册并通过调用 Eureka 服务器完成查找,因此无需处理服务地点的任何更改和处理。 4、Spring Cloud 和dubbo区别? (1)服务调用方式 dubbo是RPC springcloud Rest Api(2)注册中心,dubbo 是zookeeper springcloud是eureka,也可以是zookeeper(3)服务网关,dubbo本身没有实现,只能通过其他第三方技术整合,springcloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,springcloud支持断路器,与git完美集成配置文件支持版本控制,事物总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素。 5、SpringBoot和SpringCloud的区别? SpringBoot专注于快速方便的开发单个个体微服务。SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务SpringBoot可以离开SpringCloud独立使用开发项目, 但是SpringCloud离不开SpringBoot ,属于依赖的关系.SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。 6、负载平衡的意义什么? 在计算中,负载平衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算资源的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间并避免任何单一资源的过载。使用多个组件进行负载平衡而不是单个组件可能会通过冗余来提高可靠性和可用性。负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务器进程。 7、什么是 Hystrix?它如何实现容错? Hystrix 是一个延迟和容错库,旨在隔离远程系统,服务和第三方库的访问点,当出现故障是不可避免的故障时,停止级联故障并在复杂的分布式系统中实现弹性。通常对于使用微服务架构开发的系统,涉及到许多微服务。这些微服务彼此协作。思考以下微服务 假设如果上图中的微服务 9 失败了,那么使用传统方法我们将传播一个异常。但这仍然会导致整个系统崩溃。随着微服务数量的增加,这个问题变得更加复杂。微服务的数量可以高达 1000.这是 hystrix 出现的地方 我们将使用 Hystrix 在这种情况下的 Fallback 方法功能。我们有两个服务 employee-consumer 使用由 employee-consumer 公开的服务。简化图如下所示 现在假设由于某种原因,employee-producer 公开的服务会抛出异常。我们在这种情况下使用 Hystrix 定义了一个回退方法。这种后备方法应该具有与公开服务相同的返回类型。如果暴露服务中出现异常,则回退方法将返回一些值。 8、什么是 Hystrix 断路器?我们需要它吗? 由于某些原因,employee-consumer 公开服务会引发异常。在这种情况下使用Hystrix 我们定义了一个回退方法。如果在公开服务中发生异常,则回退方法返回一些默认值。 如果 firstPage method() 中的异常继续发生,则 Hystrix 电路将中断,并且员工使用者将一起跳过 firtsPage 方法,并直接调用回退方法。 断路器的目的是给第一页方法或第一页方法可能调用的其他方法留出时间,并导致异常恢复。可能发生的情况是,在负载较小的情况下,导致异常的问题有更好的恢复机会 。 9、什么是 Netflix Feign?它的优点是什么? Feign 是受到 Retrofit,JAXRS-2.0 和 WebSocket 启发的 java 客户端联编程序。Feign 的第一个目标是将约束分母的复杂性统一到 http apis,而不考虑其稳定性。在 employee-consumer 的例子中,我们使用了 employee-producer 使用 REST模板公开的 REST 服务。但是我们必须编写大量代码才能执行以下步骤(1)使用功能区进行负载平衡。(2)获取服务实例,然后获取基本 URL。(3)利用 REST 模板来使用服务。 前面的代码如下 @Controller public class ConsumerControllerClient { @Autowired private LoadBalancerClient loadBalancer; public void getEmployee() throws RestClientException, IOException { ServiceInstance serviceInstance=loadBalancer.choose("employee-producer"); System.out.println(serviceInstance.getUri()); String baseUrl=serviceInstance.getUri().toString(); baseUrl=baseUrl+"/employee"; RestTemplate restTemplate = new RestTemplate(); ResponseEntity<String> response=null; try{ response=restTemplate.exchange(baseUrl, HttpMethod.GET, getHeaders(),String.class); } catch (Exception ex) { System.out.println(ex); } System.out.println(response.getBody()); } 之前的代码,有像 NullPointer 这样的例外的机会,并不是最优的。我们将看到如何使用 Netflix Feign 使呼叫变得更加轻松和清洁。如果 Netflix Ribbon 依赖关系也在类路径中,那么 Feign 默认也会负责负载平衡。 10、什么是 Spring Cloud Bus?我们需要它吗? 考虑以下情况:我们有多个应用程序使用 Spring Cloud Config 读取属性,而Spring Cloud Config 从 GIT 读取这些属性。下面的例子中多个员工生产者模块从 Employee Config Module 获取 Eureka 注册的财产。 如果假设 GIT 中的 Eureka 注册属性更改为指向另一台 Eureka 服务器,会发生什么情况。在这种情况下,我们将不得不重新启动服务以获取更新的属性。还有另一种使用执行器端点/刷新的方式。但是我们将不得不为每个模块单独调用这个 url。例如,如果 Employee Producer1 部署在端口 8080 上,则调用 http:// localhost:8080 / refresh。同样对于 Employee Producer2 http://localhost:8081 / refresh 等等。这又很麻烦。这就是 Spring Cloud Bus 发挥作用的地方。 Spring Cloud Bus 提供了跨多个实例刷新配置的功能。因此,在上面的示例中,如果我们刷新 Employee Producer1,则会自动刷新所有其他必需的模块。如果我们有多个微服务启动并运行,这特别有用。这是通过将所有微服务连接到单个消息代理来实现的。无论何时刷新实例,此事件都会订阅到侦听此代理的所有微服务,并且它们也会刷新。可以通过使用端点/总线/刷新来实现对任何单个实例的刷新。 11.springcloud断路器的作用 当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应 当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)断路器有完全打开状态:一段时间内 达到一定的次数无法调用 并且多次监测没有恢复的迹象 断路器完全打开 那么下次请求就不会请求到该服务半开:短时间内 有恢复迹象 断路器会将部分请求发给该服务,正常调用时 断路器关闭关闭:当服务一直处于正常状态 能正常调用 12、什么是SpringCloudConfig? 在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。使用:(1)添加pom依赖(2)配置文件添加相关配置(3)启动类添加注解@EnableConfigServer 13、Spring Cloud Gateway? Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,取代Zuul网关。网关作为流量的,在微服务系统中有着非常作用,网关常见的功能有路由转发、权限校验、限流控制等作用。使用了一个RouteLocatorBuilder的bean去创建路由,除了创建路由RouteLocatorBuilder可以让你添加各种predicates和filters,predicates断言的意思,顾名思义就是根据具体的请求的规则,由具体的route去处理,filters是各种过滤器,用来对请求做各种判断和修改。欢迎大家关注我的公种浩【程序员追风】,2019年多家公司java面试题整理了1000多道400多页pdf文档,文章都会在里面更新,整理的资料也会放在里面。 最后 欢迎大家一起交流,喜欢文章记得关注我点个赞哟,感谢支持!

优秀的个人博客,低调大师

2019年Java面试题基础系列228道(2),漏补缺!

2019年Java面试题基础系列228道 上一篇更新1~20题的答案解析https://yq.aliyun.com/articles/738108?spm=a2c4e.11155435.0.0.56793312VUpLTJ 本次更新Java 面试题(一)的21~50题答案 21、描述一下 JVM 加载 class 文件的原理机制? JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。 由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应的 Class 对象。加载完成后,Class 对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。 类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader 的子类)。从 Java 2(JDK 1.2)开始,从 Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制中,JVM 自带的 Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。下面是关于几个类加载器的说明: (1) Bootstrap:一般用本地代码实现,负责加载 JVM 基础核心类库(rt.jar); (2) Extension:从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是 Bootstrap; (3) System:又叫应用类加载器,其父类是 Extension。它是应用最广泛的类加载器。它从环境变量 classpath 或者系统属性 java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。 22、char 型变量中能不能存贮一个中文汉字,为什么? char 类型可以存储一个中文汉字,因为 Java 中使用的编码是 Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个 char 类型占 2 个字节(16 比特),所以放一个中文是没问题的。 补充:使用 Unicode 意味着字符在 JVM 内部和外部有不同的表现形式,在 JVM内部都是 Unicode,当这个字符被从 JVM 内部转移到外部时(例如存入文件系统中),需要进行编码转换。所以 Java 中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如 InputStreamReader 和 OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务;对于 C 程序员来说,要完成这样的编码转换恐怕要依赖于 union(联合体/共用体)共享内存的特征来实现了。 23、抽象类(abstract class)和接口(interface)有什么异同? 抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是 private、默认、protected、public 的,而接口中的成员全都是 public 的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。 24、静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同? Static Nested Class 是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化,其语法看起来挺诡异的,如下所示。 /** * 扑克类(一副扑克) * @author 骆昊 * */ public class Poker { private static String[] suites = {"黑桃", "红桃", "草花", "方块"}; private static int[] faces = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; private Card[] cards; /** * 构造器 * */ public Poker() { cards = new Card[52]; for (int i = 0; i < suites.length; i++) { for (int j = 0; j < faces.length; j++) { cards[i * 13 + j] = new Card(suites[i], faces[j]); } } } /** * 洗牌 (随机乱序) * */ public void shuffle() { for (int i = 0, len = cards.length; i < len; i++) { int index = (int) (Math.random() * len); Card temp = cards[index]; cards[index] = cards[i]; cards[i] = temp; } } /** * 发牌 * @param index 发牌的位置 * */ public Card deal(int index) { return cards[index]; } /** * 卡片类(一张扑克) * [内部类] * @author 骆昊 * */ public class Card { private String suite; // 花色 private int face; // 点数 public Card(String suite, int face) { this.suite = suite; this.face = face; } @Override public String toString() { String faceStr = ""; switch(face) { case 1: faceStr = "A"; break; case 11: faceStr = "J"; break; case 12: faceStr = "Q"; break; case 13: faceStr = "K"; break; default: faceStr = String.valueOf(face); } return suite + faceStr; } } } 测试代码: class PokerTest { public static void main(String[] args) { Poker poker = new Poker(); poker.shuffle(); // 洗牌 Poker.Card c1 = poker.deal(0); // 发第一张牌 // 对于非静态内部类 Card // 只有通过其外部类 Poker 对象才能创建 Card 对象 Poker.Card c2 = poker.new Card("红心", 1); // 自己创建一张牌 System.out.println(c1); // 洗牌后的第一张 System.out.println(c2); // 打印: 红心 A } } 面试题 - 下面的代码哪些地方会产生编译错误? class Outer { class Inner { } public static void foo() { new Inner(); } public void bar() { new Inner(); } public static void main(String[] args) { new Inner(); } } 注意:Java 中非静态内部类对象的创建要依赖其外部类对象,上面的面试题中 foo和 main 方法都是静态方法,静态方法中没有 this,也就是说没有所谓的外部类对象,因此无法创建内部类对象,如果要在静态方法中创建内部类对象,可以这样做:new Outer().new Inner(); 25、Java 中会存在内存泄漏吗,请简单描述。 理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被 GC 回收,因此也会导致内存泄露的发生。例如Hibernate 的 Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。下面例子中的代码也会导致内存泄露。 import java.util.Arrays; import java.util.EmptyStackException; public class MyStack<T> { private T[] elements; private int size = 0; private static final int INIT_CAPACITY = 16; public MyStack() { elements = (T[]) new Object[INIT_CAPACITY]; } public void push(T elem) { ensureCapacity(); elements[size++] = elem; } public T pop() { if(size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if(elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } } 上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下似乎没有什么明显的问题,它甚至可以通过你编写的各种单元测试。然而其中的 pop 方法却存在内存泄露的问题,当我们用 pop 方法弹出栈中的对象时,该对象不会被当作垃圾回收,即使使用栈的程序不再引用这些对象,因为栈内部维护着对这些对象的过期引 用(obsolete reference)。在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是无意识的对象保持。如果一个对象引用被无意识的保留起来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其他对象,即使这样的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之外,从而对性能造成重大影响,极端情况下会引发 Disk Paging(物理内存与硬盘的虚拟内存交换数据),甚至造成 OutOfMemoryError。 26、抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被 synchronized修饰? 都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如 C 代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized 和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。 27、阐述静态变量和实例变量的区别。 静态变量是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。补充:在 Java 开发中,上下文类和工具类中通常会有大量的静态成员。 28、是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用? 不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在调用静态方法时可能对象并没有被初始化。 29、如何实现对象克隆? 有两种方式: 1). 实现 Cloneable 接口并重写 Object 类中的 clone()方法; 2). 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下。 import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class MyUtil { private MyUtil() { throw new AssertionError(); } @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T obj) throws Exception { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bout); oos.writeObject(obj); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bin); return (T) ois.readObject(); // 说明:调用 ByteArrayInputStream 或 ByteArrayOutputStream 对象的 close 方法没有任何意义 // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这 一点不同于对外部资源(如文件流)的释放 } } 下面是测试代码: import java.io.Serializable; /** * 人类 * @author 骆昊 * */ class Person implements Serializable { private static final long serialVersionUID = -9102017020286042305L; private String name; // 姓名 private int age; // 年龄 private Car car; // 座驾 public Person(String name, int age, Car car) { this.name = name; this.age = age; this.car = car; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Car getCar() { return car; } public void setCar(Car car) { this.car = car; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", car=" + car + "]"; } } /** * 小汽车类 * @author 骆昊 * */ class Car implements Serializable { private static final long serialVersionUID = -5713945027627603702L; private String brand; // 品牌 private int maxSpeed; // 最高时速 public Car(String brand, int maxSpeed) { this.brand = brand; this.maxSpeed = maxSpeed; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public int getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(int maxSpeed) { this.maxSpeed = maxSpeed; } @Override public String toString() { return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]"; } } class CloneTest { public static void main(String[] args) { try { Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300)); Person p2 = MyUtil.clone(p1); // 深度克隆 p2.getCar().setBrand("BYD"); // 修改克隆的 Person 对象 p2 关联的汽车对象的品牌属性 // 原来的 Person 对象 p1 关联的汽车不会受到任何影响 // 因为在克隆 Person 对象时其关联的汽车对象也被克隆了 System.out.println(p1); } catch (Exception e) { e.printStackTrace(); } } } 注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用 Object 类的 clone 方法克隆对象。让问题在编译的时候暴露出来总是好过把问题留到运行时。 30、GC 是什么?为什么要有 GC? GC 是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。Java 程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但 JVM 可以屏蔽掉显示的垃圾回收调用。垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在 Java 诞生初期,垃圾回收是 Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今 Java 的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得 iOS 的系统比 Android 系统有更好的用户体验,其中一个深层次的原因就在于 Android 系统中垃圾回收的不可预知性。 补充:垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量垃圾回收等方式。标准的 Java 进程既有栈又有堆。栈保存了原始型局部变量,堆保存了要创建的对象。Java 平台对堆内存回收和再利用的基本算法被称为标记和清除,但是 Java 对其进行了改进,采用“分代式垃圾收集”。这种方法会跟 Java对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象移动到不同区域: (1)伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。 (2)幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。 (3)终身颐养园(Tenured):这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。与垃圾回收相关的 JVM 参数: -Xms / -Xmx — 堆的初始大小 / 堆的最大大小 -Xmn — 堆中年轻代的大小 -XX:-DisableExplicitGC — 让 System.gc()不产生任何作用 -XX:+PrintGCDetails — 打印 GC 的细节 -XX:+PrintGCDateStamps — 打印 GC 操作的时间戳 -XX:NewSize / XX:MaxNewSize — 设置新生代大小/新生代最大大小 -XX:NewRatio — 可以设置老生代和新生代的比例 -XX:PrintTenuringDistribution — 设置每次新生代 GC 后输出幸存者 乐园中对象年龄的分布 -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:设置老 年代阀值的初始值和最大值 -XX:TargetSurvivorRatio:设置幸存区的目标使用率 31、String s = new String(“xyz”);创建了几个字符串对象? 两个对象,一个是静态区的”xyz”,一个是用 new 创建在堆上的对象。 32、接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concreteclass)? 接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类。 33、一个”.java”源文件中是否可以包含多个类(不是内部类)?有什么限制? 可以,但一个源文件中最多只能有一个公开类(public class)而且文件名必须和公开类的类名完全保持一致。 34、Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口? 可以继承其他类或实现其他接口,在 Swing 编程和 Android 开发中常用此方式来实现事件监听和回调。 35、内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制? 一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。 36、Java 中的 final 关键字有哪些用法? (1)修饰类:表示该类不能被继承; (2)修饰方法:表示方法不能被重写; (3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。 37、指出下面程序的运行结果 class A { static { System.out.print("1"); } public A() { System.out.print("2"); } } class B extends A{ static { System.out.print("a"); } public B() { System.out.print("b"); } } public class Hello { public static void main(String[] args) { A ab = new B(); ab = new B(); } } 执行结果:1a2b2b。创建对象时构造器的调用顺序是:先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器。提示:如果不能给出此题的正确答案,说明之前第 21 题 Java 类加载机制还没有完全理解,赶紧再看看吧。 38、数据类型之间的转换: (1) 如何将字符串转换为基本数据类型? (2) 如何将基本数据类型转换为字符串?答: (1)调用基本数据类型对应的包装类中的方法 parseXXX(String)或valueOf(String)即可返回相应基本类型; (2)一种方法是将基本数据类型与空字符串(”“)连接(+)即可获得其所对应的字符串;另一种方法是调用 String 类中的 valueOf()方法返回相应字符串 39、如何实现字符串的反转及替换? 方法很多,可以自己写实现也可以使用 String 或 StringBuffer/StringBuilder 中的方法。有一道很常见的面试题是用递归实现字符串反转,代码如下所示: public static String reverse(String originStr) { if(originStr == null || originStr.length() <= 1) return originStr; return reverse(originStr.substring(1)) + originStr.charAt(0); } 40、怎样将 GB2312 编码的字符串转换为 ISO-8859-1 编码的字符串? 代码如下所示: String s1 = "你好"; String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1"); 41、日期和时间: (1)如何取得年月日、小时分钟秒? (2) 如何取得从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的毫秒数? (3) 如何取得某月的最后一天? (4)如何格式化日期? 答: 问题 1:创建 java.util.Calendar 实例,调用其 get()方法传入不同的参数即可获得参数所对应的值。Java 8 中可以使用 java.time.LocalDateTimel 来获取,代码如下所示。 public class DateTimeTest { public static void main(String[] args) { Calendar cal = Calendar.getInstance(); System.out.println(cal.get(Calendar.YEAR)); System.out.println(cal.get(Calendar.MONTH)); // 0 - 11 System.out.println(cal.get(Calendar.DATE)); System.out.println(cal.get(Calendar.HOUR_OF_DAY)); System.out.println(cal.get(Calendar.MINUTE)); System.out.println(cal.get(Calendar.SECOND)); // Java 8 LocalDateTime dt = LocalDateTime.now(); System.out.println(dt.getYear()); System.out.println(dt.getMonthValue()); // 1 - 12 System.out.println(dt.getDayOfMonth()); System.out.println(dt.getHour()); System.out.println(dt.getMinute()); System.out.println(dt.getSecond()); } } 问题 2:以下方法均可获得该毫秒数。 Calendar.getInstance().getTimeInMillis(); System.currentTimeMillis(); Clock.systemDefaultZone().millis(); // Java 8 问题 3:代码如下所示。 Calendar time = Calendar.getInstance(); time.getActualMaximum(Calendar.DAY_OF_MONTH 问题 4:利用 java.text.DataFormat 的子类(如 SimpleDateFormat 类)中的format(Date)方法可将日期格式化。Java 8 中可以用java.time.format.DateTimeFormatter 来格式化时间日期,代码如下所示。 import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Date; class DateFormatTest { public static void main(String[] args) { SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd"); Date date1 = new Date(); System.out.println(oldFormatter.format(date1)); // Java 8 DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); LocalDate date2 = LocalDate.now(); System.out.println(date2.format(newFormatter)); } } 补充:Java 的时间日期 API 一直以来都是被诟病的东西,为了解决这一问题,Java8 中引入了新的时间日期 API,其中包括 LocalDate、LocalTime、LocalDateTime、Clock、Instant 等类,这些的类的设计都使用了不变模式,因此是线程安全的设计。 42、打印昨天的当前时刻。 import java.util.Calendar; class YesterdayCurrent { public static void main(String[] args){ Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE, -1); System.out.println(cal.getTime()); } } 在 Java 8 中,可以用下面的代码实现相同的功能。 import java.time.LocalDateTime; class YesterdayCurrent { public static void main(String[] args) { LocalDateTime today = LocalDateTime.now(); LocalDateTime yesterday = today.minusDays(1); System.out.println(yesterday); } } 43、比较一下 Java 和 JavaSciprt。 JavaScript 与 Java 是两个公司开发的不同的两个产品。Java 是原 SunMicrosystems 公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而 JavaScript 是 Netscape 公司的产品,为了扩展 Netscape 浏览器的功能而开发的一种可以嵌入 Web 页面中运行的基于对象和事件驱动的解释性语言。JavaScript 的前身是 LiveScript;而 Java 的前身是 Oak 语言。下面对两种语言间的异同作如下比较: (1)基于对象和面向对象:Java 是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript 是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。 (2)解释和编译:Java 的源代码在执行之前,必须经过编译。JavaScript 是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了 JIT(即时编译)技术来提升 JavaScript 的运行效率) (3)强类型变量和类型弱变量:Java 采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript 中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript 的解释器在运行时检查推断其数据类型。 (4)代码格式不一样。 补充:上面列出的四点是网上流传的所谓的标准答案。其实 Java 和 JavaScript最重要的区别是一个是静态语言,一个是动态语言。目前的编程语言的发展趋势是函数式语言和动态语言。在 Java 中类(class)是一等公民,而 JavaScript 中函数(function)是一等公民,因此 JavaScript 支持函数式编程,可以使用 Lambda函数和闭包(closure),当然 Java 8 也开始支持函数式编程,提供了对 Lambda表达式以及函数式接口的支持。对于这类问题,在面试的时候最好还是用自己的语言回答会更加靠谱,不要背网上所谓的标准答案。欢迎大家关注我的公种浩【程序员追风】,2019年多家公司java面试题整理了1000多道400多页pdf文档,文章都会在里面更新,整理的资料也会放在里面。 44、什么时候用断言(assert)? 断言在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。一般来说,断言用于保证程序最基本、关键的正确性。断言检查通常在开发和测试时开启。为了保证程序的执行效率,在软件发布后断言检查通常是关闭的。断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为 true;如果表达式的值为 false,那么系统会报告一个 AssertionError。断言的使用如下面的代码所示: assert(a > 0); // throws an AssertionError if a <= 0 断言可以有两种形式: assert Expression1; assert Expression1 : Expression2 ; Expression1 应该总是产生一个布尔值。 Expression2 可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的字符串消息。 要在运行时启用断言,可以在启动 JVM 时使用-enableassertions 或者-ea 标记。要在运行时选择禁用断言,可以在启动 JVM 时使用-da 或者-disableassertions标记。要在系统类中启用或禁用断言,可使用-esa 或-dsa 标记。还可以在包的基础上启用或者禁用断言。注意:断言不应该以任何方式改变程序的状态。简单的说,如果希望在不满足某些条件时阻止代码的执行,就可以考虑用断言来阻止它。 45、Error 和 Exception 有什么区别? Error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况; Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。 46、try{}里有一个 return 语句,那么紧跟在这个 try 后的finally{}里的代码会不会被执行,什么时候被执行,在 return前还是后? 会执行,在方法返回调用者前执行。 注意:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误,Eclipse 中可以在如图所示的地方进行设置,强烈建议将此项设置为编译错误。 47、Java 语言如何进行异常处理,关键字:throws、throw、try、catch、finally 分别如何使用? Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在 Java 中,每个异常都是一个对象,它是 Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。一般情况下是用 try 来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理;try 用来指定一块预防所有异常的程序;catch 子句紧跟在 try 块后面,用来指定你想要捕获的异常的类型;throw 语句用来明确地抛出一个异常;throws 用来声明一个方法可能抛出的各种异常(当然声明异常时允许无病呻吟);finally 为确保一段代码不管发生什么异常状况都要被执行;try 语句可以嵌套,每当遇到一个 try 语句,异常的结构就会被放入异常栈中,直到所有的 try 语句都完成。如果下一级的try 语句没有对某种异常进行处理,异常栈就会执行出栈操作,直到遇到有处理这种异常的 try 语句或者最终将异常抛给 JVM。 48、运行时异常与受检异常有何异同? 异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java 编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,在 Effective Java 中对异常的使用给出了以下指导原则: (1)不要将异常处理用于正常的控制流(设计良好的 API 不应该强迫它的调用者为了正常的控制流而使用异常) (2)对可以恢复的情况使用受检异常,对编程错误使用运行时异常 (3)避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生) (4)优先使用标准的异常 (5)每个方法抛出的异常都要有文档 (6)保持异常的原子性 (7)不要在 catch 中忽略掉捕获到的异常 49、列出一些你常见的运行时异常? (1)ArithmeticException(算术异常) (2) ClassCastException (类转换异常) (3) IllegalArgumentException (非法参数异常) (4) IndexOutOfBoundsException (下标越界异常) (5) NullPointerException (空指针异常) (6) SecurityException (安全异常) 50、阐述 final、finally、finalize 的区别。 (1) final:修饰符(关键字)有三种用法:如果一个类被声明为 final,意味着它不能再派生出新的子类,即不能被继承,因此它和 abstract 是反义词。将变量声明为 final,可以保证它们在使用中不被改变,被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为 final 的方法也同样只能使用,不能在子类中被重写。 (2)finally:通常放在 try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要 JVM 不关闭都能执行,可以将释放外部资源的代码写在 finally 块中. (3)finalize:Object 类中定义的方法,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写 finalize()方法可以整理系统资源或者执行其他清理工作。 最后 欢迎大家一起交流,喜欢文章记得点个赞哟,感谢支持!

优秀的个人博客,低调大师

WiFi探针个人隐私?第三方App才是元凶

【大咖・来了 第7期】10月24日晚8点观看《智能导购对话机器人实践》 在上周五播出的3.15晚会上,央视报道了使用WiFi探针功能可以窃取用户隐私的事件,引发了不少人的恐慌。其实,WiFi探针技术本无偷窃个人隐私的功能,手机里乱装的第三方app才是出卖用户数据的元凶。 WiFi探针不能获取个人信息 许多网友们现在还是搞不清WiFi探针是什么。其实,WiFi探针是依附于无线AP等WiFi发射设备中的一种功能。比如,我们在商场、餐馆、咖啡厅等公共场合连接WiFi网络时,WiFi探针就能够探知我们手中设备的MAC地址。 每台设备的MAC地址是固定且不变的,通过在后台的大数据数据库进行比对,从用户的MAC地址可以顺藤摸瓜显示用户的手机号、最近消费记录、年龄、兴趣爱好、常用app等,形成一个用户画像。商家通过用户画像,对不同用户推送不同的广告促销信息,从而达到所谓的精准营销目的。 第三方app泄露你的隐私 通过上文的介绍,我们可以看到,WiFi探针只能够获取用户连接WiFi设备的MAC地址,并不能直接收集用户的隐私数据,那么我们的隐私是怎么泄露的? 答案是第三方app。尤其是安卓手机在安装app时,会显示获取用户手机的各种权限和各种数据。比如,获取用户的实时位置、电话权限、通信录权限等。而且大多数app在注册时需要用户使用手机号注册,这样一来用户的手机号就很容易和手机的MAC地址匹配绑定上。 这些app平日里潜伏在用户的手机中,默默的记录着你的各项使用行为和使用习惯,再上传到后台数据库。通过数据的非法流通,一些明其名曰做大数据的公司,其实就是干着收集和贩卖用户数据的勾当。 当商家需要精准营销,增加营销手段时,店内的WiFi探针先探知用户手机的MAC地址,再通过MAC在大数据平台中找寻到用户的手机号码,进一步了解到用户的用户画像,是不是很惊悚? 防范有招 为了防范通过MAC地址找到我们的设备和个人信息,用户在公共场合建议关闭手机的WiFi功能,避免被探知到MAC地址。也不要连接陌生的和来路不明的WiF,保护个人隐私。 此外,从安卓8.0的原生系统开始,安卓系统已经默认开启“MAC地址随机”的功能,在安卓9.0的开发者选项中也可以找到,建议用户打开这项功能。这样一来,每一次探针扫描到用户设备的MAC地址都不一样,可以迷惑对方。安卓手机千万小心不要安装不知名应用市场的app,乱下app会让你的隐私加速泄露。 苹果手机用户也不用害怕,iOS8以后苹果手机就已经支持随机MAC地址功能了。而且在app方面,苹果的app store已经对app权限严加管理和审核,不会出现默默收集用户隐私的情况。 结束语 在互联网时代,每个人的隐私或多或少都被泄露,通过大数据技术把我们变成了透明人。尤其是安卓手机的app权限滥用问题,中消协发布的数据显示85.2%的人因个人隐私泄露被骚扰过。除了消费者自身要加强警惕和自我保护意识外,相关监管法律应尽快加强。

优秀的个人博客,低调大师

HTAP数据库 PostgreSQL 场景与性能测试之 1 - (OLTP) 点

标签 PostgreSQL , HTAP , OLTP , OLAP , 场景与性能测试 背景 PostgreSQL是一个历史悠久的数据库,历史可以追溯到1973年,最早由2014计算机图灵奖得主,关系数据库的鼻祖Michael_Stonebraker操刀设计,PostgreSQL具备与Oracle类似的功能、性能、架构以及稳定性。 PostgreSQL社区的贡献者众多,来自全球各个行业,历经数年,PostgreSQL 每年发布一个大版本,以持久的生命力和稳定性著称。 2017年10月,PostgreSQL 推出10 版本,携带诸多惊天特性,目标是胜任OLAP和OLTP的HTAP混合场景的需求: 《最受开发者欢迎的HTAP数据库PostgreSQL 10特性》 1、多核并行增强 2、fdw 聚合下推 3、逻辑订阅 4、分区 5、金融级多副本 6、jso

优秀的个人博客,低调大师

清华与快手联手推出新型 SVG 扩散模型,训练效率暴 6200%

清华大学与快手可灵团队合作推出了一款名为 SVG(无 VAE 潜在扩散模型)的新型生成模型。此次创新不仅在训练效率上实现了6200% 的惊人提升,而且在生成速度上更是达到了3500% 的飞跃。 VAE 在图像生成领域的衰退,主要源于其存在的 “语义纠缠” 问题。也就是说,当我们尝试仅仅改变图像中某一特征(如猫的颜色)时,其他特征(如体型、表情)往往也会受到影响,导致生成的图像不够精准。为了解决这个问题,清华与快手的 SVG 模型采取了不同的策略,主动构建了一个融合语义与细节的特征空间。 在 SVG 模型的设计中,团队首先使用 DINOv3预训练模型作为语义提取器,该模型经过大规模的自监督学习,能够有效识别和分离不同类别的特征,解决了传统 VAE 模型中的语义混乱。此外,为了补充细节,团队还特别设计了一个轻量级的残差编码器,确保细节信息不会与语义特征相冲突。关键的分布对齐机制则进一步增强了这两种特征的融合,保证了生成图像的高质量。 实验结果表明,SVG 模型在生成质量和多任务通用性方面,全面超越了传统的 VAE 方案。在 ImageNet 数据集上,SVG 模型在仅训练80个周期时,FID 值(衡量生成图像与真实图像相似度的指标)达到6.57,远超同规模的 VAE 模型;而在推理效率上,SVG 模型也显示出卓越的性能,在较少的采样步骤下即可生成清晰图像。此外,SVG 模型的特征空间还可直接用于图像分类、语义分割等多种视觉任务,无需额外微调,大大提高了应用的灵活性。

优秀的个人博客,低调大师

如何让服务在流量暴的情况下保持稳定输出

服务自适应降载保护设计 设计目的 保证系统不被过量请求拖垮 在保证系统稳定的前提下,尽可能提供更高的吞吐量 设计考虑因素 如何衡量系统负载 是否处于虚机或容器内,需要读取cgroup相关负载 用1000m表示100%CPU,推荐使用800m表示系统高负载 尽可能小的Overhead,不显著增加RT 不考虑服务本身所依赖的DB或者缓存系统问题,这类问题通过熔断机制来解决 机制设计 计算CPU负载时使用滑动平均来降低CPU负载抖动带来的不稳定,关于滑动平均见参考资料 滑动平均就是取之前连续N次值的近似平均,N取值可以通过超参beta来决定 当CPU负载大于指定值时触发降载保护机制 时间窗口机制,用滑动窗口机制来记录之前时间窗口内的QPS和RT(response time) 滑动窗口使用5秒钟50个桶的方式,每个桶保存100ms时间内的请求,循环利用,最新的覆盖最老的 计算maxQPS和minRT时需要过滤掉最新的时间没有用完的桶,防止此桶内只有极少数请求,并且RT处于低概率的极小值,所以计算maxQPS和minRT时按照上面的50个桶的参数只会算49个 满足以下所有条件则拒绝该请求 当前CPU负载超过预设阈值,或者上次拒绝时间到现在不超过1秒(冷却期)。冷却期是为了不能让负载刚下来就马上增加压力导致立马又上去的来回抖动 averageFlying > max(1, QPS*minRT/1e3) averageFlying = MovingAverage(flying) 在算MovingAverage(flying)的时候,超参beta默认取值为0.9,表示计算前十次的平均flying值 取flying值的时候,有三种做法: 请求增加后更新一次averageFlying,见图中橙色曲线 请求结束后更新一次averageFlying,见图中绿色曲线 请求增加后更新一次averageFlying,请求结束后更新一次averageFlying 我们使用的是第二种,这样可以更好的防止抖动,如图: QPS = maxPass * bucketsPerSecond maxPass表示每个有效桶里的成功的requests bucketsPerSecond表示每秒有多少个桶 1e3表示1000毫秒,minRT单位也是毫秒,QPS*minRT/1e3得到的就是平均每个时间点有多少并发请求 降载的使用 已经在rest和zrpc框架里增加了可选激活配置 CpuThreshold,如果把值设置为大于0的值,则激活该服务的自动降载机制 如果请求被drop,那么错误日志里会有dropreq关键字 参考资料 滑动平均 Sentinel自适应限流 项目地址 https://github.com/tal-tech/go-zero 好未来技术

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Apache Tomcat

Apache Tomcat

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。

JDK

JDK

JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。