• [技术干货] 【Java Web学习 | 第五篇】CSS(4) -盒子模型
    CSS盒子模型在网页布局的世界里,CSS盒子模型是一切布局的基础。无论是简单的文本段落还是复杂的页面组件,都可以被看作是一个个矩形的"盒子"。掌握盒子模型的工作原理,是成为前端开发者的必备技能。本文将从基础概念出发,通过实例代码详细解析盒子模型的核心要素及实战技巧。1. 什么是CSS盒子模型?CSS盒子模型是一种将HTML元素视为矩形盒子的布局模型,每个盒子由四个部分组成:内容区(content):元素实际内容所在的区域内边距(padding):内容区与边框之间的空间边框(border):围绕内边距和内容区的线条外边距(margin):盒子与其他盒子之间的空间这四个部分共同决定了元素在页面中的大小和位置,是页面布局的三大核心之一(另外两个是浮动和定位)。2. 边框(border):盒子的"外衣"边框是盒子最外层的可见边界,由三个属性共同定义:宽度、样式和颜色。边框的基本属性#div1 {  width: 300px;  height: 200px;  border-width: 10px;      /* 边框宽度 */  border-style: solid;    /* 边框样式 */  border-color: pink;     /* 边框颜色 */}运行项目并下载源码css也可以使用简写形式,顺序可以任意调整:#div1 {  border: 10px solid pink;  /* 宽度 样式 颜色 */}运行项目并下载源码css边框样式是必须指定的属性,常用值包括:solid:实线边框dashed:虚线边框dotted:点线边框单边边框设置我们可以单独设置盒子四个方向的边框,利用CSS的层叠性可以简化代码:#div2 {  width: 200px;  height: 200px;  border: 5px solid blue;  /* 先设置四边为蓝色 */  border-top: 5px solid red; /* 再单独设置上边框为红色 */}运行项目并下载源码css边框对盒子大小的影响重要提示:边框会增加盒子的实际大小。例如,一个宽度为100px的盒子,如果设置了5px的边框,其实际宽度将变为110px(100 + 5×2)。解决方案:测量盒子时不包含边框如果测量包含边框,需从width/height中减去边框宽度的两倍表格细线边框表格边框默认会有重叠问题,使用border-collapse: collapse可以合并相邻边框:table, td, th {  border: 1px solid pink;  border-collapse: collapse; /* 合并相邻边框 */}运行项目并下载源码css3. 内边距(padding):内容与边框的缓冲带内边距用于控制内容区与边框之间的距离,让内容不会紧贴边框。内边距的基本用法内边距有四个方向的属性:padding-top:上内边距padding-right:右内边距padding-bottom:下内边距padding-left:左内边距也可以使用简写形式,规则如下:.div3 {  /* 1个值:上下左右都为5px */  /* padding: 5px; */    /* 2个值:上下5px,左右10px */  /* padding: 5px 10px; */    /* 3个值:上5px,左右10px,下20px */  /* padding: 5px 10px 20px; */    /* 4个值:上5px,右10px,下20px,左30px(顺时针方向) */  padding: 5px 10px 20px 30px;}运行项目并下载源码css内边距对盒子大小的影响与边框类似,指定了宽高的盒子设置内边距会使盒子变大。例如,一个200×200px的盒子,设置10px内边距后,实际大小会变为220×220px。解决方案:如果需要保持盒子总大小不变,应从width/height中减去内边距的总和。内边距的实用技巧当导航栏中每个菜单项的字数不同时,使用内边距代替固定宽度可以实现更灵活的布局:#nav {  height: 41px;  line-height: 41px; /* 垂直居中 */  background-color: #fcfcfc;}#nav a {  display: inline-block;  padding: 0 20px; /* 左右内边距撑开盒子 */  text-decoration: none;}运行项目并下载源码css这种方式能保证每个菜单项的内容与边缘距离一致,且能自适应内容长度。内边距不影响盒子大小的特殊情况如果盒子没有指定width/height属性,设置内边距不会撑开盒子大小:.div5 {  width: 300px;  height: 100px;  background-color: pink;}.div5 p {  padding: 50px; /* p元素没有指定宽高,不会超出父元素 */}运行项目并下载源码css4. 外边距(margin):盒子之间的距离外边距控制盒子与其他盒子之间的距离,其语法与内边距类似。外边距的基本用法外边距同样有四个方向的属性:margin-top:上外边距margin-right:右外边距margin-bottom:下外边距margin-left:左外边距简写规则与padding完全一致,这里不再赘述。外边距的典型应用:水平居中外边距最常用的技巧之一是 实现块级元素的水平居中,需要满足两个条件:盒子必须指定宽度(width)左右外边距都设置为auto.header {  width: 500px;  height: 200px;  background-color: skyblue;  margin: 100px auto; /* 上下100px,左右自动居中 */}运行项目并下载源码css对于行内元素或行内块元素,水平居中需要给其父元素设置text-align: center。外边距合并问题使用margin定义垂直方向的外边距时,可能会出现外边距合并的现象 --> 当父元素没有设置 “阻隔”(如边框、内边距、overflow 等)时,父元素的margin-top会与子元素的margin-top合并,表现为:父元素会 “吸收” 子元素的上外边距,两者的外边距合并为一个(取较大值)。视觉上,子元素的上外边距 “转移” 到了父元素身上,导致父元素跟着子元素一起向下移动(看起来像 “塌陷”)。例如:#father {  width: 400px;  height: 400px;  background-color: purple;  margin-top: 50px;}#son {  width: 200px;  height: 200px;  background-color: pink;  margin-top: 100px; /* 会导致父元素一起下移 */}运行项目并下载源码css显示效果【本来子元素按照margin-top:100px 应该从父元素的上顶部分开,但实际并没有,而是父元素跟者子元素一起下来了(塌陷)】:解决方法:为父元素定义上边框(可设为透明)border: 1px solid transparent;为父元素定义上内边距 padding: 1px;为父元素添加overflow: hidden(推荐,不改变盒子大小)#father {  /* 其他样式不变 */  overflow: hidden; /* 解决外边距塌陷 */}运行项目并下载源码css显示效果:清除默认内外边距网页中很多元素(如ul、p等)会自带默认的内外边距,且不同浏览器表现不一致。因此,布局前通常会先清除这些默认样式:* {  padding: 0; /* 清除内边距 */  margin: 0;  /* 清除外边距 */}运行项目并下载源码css注意:行内元素为了兼容性,建议只设置左右方向的内外边距,上下方向可能不起作用(转换为块级或行内块元素后可正常使用)。综合代码演示<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>盒子模型</title>    <!-- 页面布局三大核心:盒子模型、浮动、定位 -->    <!--    谓盒子模型:就是把HTML页面中的布局元素看做是一个矩形的盒子,也就是一个盛装内容的容器            CSS盒子模型本质上是一个盒子,封装周围的HTML元素,包括:边框、外边距、内边距和实际内容    -->    <style>        /* 注意,这句话应为我们使用CSS时的第一句话        * {            padding: 0; 清除内边距             margin: 0;  清除外边距        }         */        /*  <!-- 111.border -->        <!-- border可以设置元素的边框,边框由三部分组成:边框宽度(粗细)、边框样式、边框颜色            border: border-width | border-style | border-color;                 边框属性简写(复合写法):border-width border-style border-color;(没有顺序要求)        -->         */        #div1 {            width: 300px;            height: 200px;            /* 1.border-width 边框的粗细,一般情况下都用 px */            border-width: 10px;            /* 2.border-style 边框的样式 solid(实现边框) dashed(虚线边框) dotted(点线边框) */            border-style: solid;            /* 3.border-color */            border-color: pink;            /* 4.边框的简写 */            /* border: 10px solid pink; */        }        /* 5.边框分开写法: border-top/left/right/bottom: 5px solid red */        /* 注意:11.边框会额外增加盒子的实际大小,比如 width:100px的盒子,若有border: 5px solid red ,则盒子的实际宽度为100+2*5                解决方案:1. 测量盒子的时候不要量边框                         2. 如果测量的时候包含了边框,则需要width/height减去边框宽度*2(若两边都有边框)        */        #div2 {            width: 200px;            height: 200px;            /* 给定一个200*200的盒子,设置上边框为红色,其余边为蓝色 */            /* border-top: 5px solid red;            border-bottom: 5px solid blue;            border-left: 5px solid blue;            border-right: 5px solid blue; */            /* 这种写法更好,合理运用层叠性(就近原则) */            border: 5px solid blue;            border-top: 5px solid red;        }        /* 6.表格细线边框 (边框与边框之间会有像素融合问题,5px+5px=10px)*/        /* 解决:border-collapse: collapse(合并) 相邻边框合并在一起*/        table {            width: 500px;            height: 250px;            text-align: center;        }        table,        td,        th {            border: 1px solid pink;            /* 合并相邻的边框 */            border-collapse: collapse;        }        /* 7.内边距 padding 盒子里面的内容默认适合盒子的边缘紧挨着的,这时就需要padding来进行调节            11.padding 属性用于设置内边距,即边框和内容之间的距离                1.padding-left 左内边距                2.padding-right 右内边距                3.padding-top 上内边距                4.padding-bottom 下内边距            22.padding复合写法(简写)                1.padding: 5px;  1个值,代表上下左右都有5像素内边距                2.padding: 5px 10px; 2个值,代表上下内边是5像素,左右内边距是10像素                3.padding: 5px 10px 20px; 3个值,代表上内边距是5像素,左右内边距是10像素,下内边距是20像素                4.padding: 5px 10px 20px 30px; 4个值,上是5像素,右10像素,下20像素,左30像素 顺时针(上右下左)            33.padding会影响盒子的实际大小,也就是说,如果盒子已经有了宽度和高度,此时再指定内边框,会撑大盒子                解决方案:如果要保证盒子跟效果图大小一致,则让width/height减去多出来的内边距大小即可(注意两边的内边距都要考虑进去)        */        .div3 {            width: 200px;            height: 200px;            background-color: pink;            /* 在浏览器右键点击检查,把鼠标移动到对应的位置,内边距部分会变为跟背景不一样的颜色 */            /* padding-left: 5px;            padding-top: 5px;            padding-right: 5px;            padding-bottom: 5px; */            /* 内边距简写 */            /* padding: 5px; */            /* padding: 5px 10px; */            /* padding: 5px 10px 20px; */            padding: 5px 10px 20px 30px;        }        /* 7.1 内边距的应用:当网页中每个导航栏(盒子)里面的字数不一样多时,我们就不要统一给每一个盒子设定宽度了              直接用padding撑开盒子,就可以实现即使内容长度不一样,内容与盒子之间的边距也一样        */        #nav {            height: 41px;            border-top: 3px solid #ff8500;            border-bottom: 1px solid #edeef0;            background-color: #fcfcfc;            /* 运用了继承 a继承来自div的行高 */            line-height: 41px;        }        #nav a {            /* height: 41px; 由于a属于行内元素,这样设置无效,此时必须要转换 行内元素->行内块元素(不能装换成块级元素,因为其无法一行显示多个) */            display: inline-block;            height: 41px;            padding: 0 20px;            font-size: 15px;            color: #4c4c4c;            text-decoration: none;        }        /* 伪类选择器 */        #nav a:hover {            background-color: #eee;            color: #ff8500;        }        /* 7.2 padding不会影响盒子大小的情况            如果盒子本身没有指定width/height属性,则此时padding就不会撑开盒子大小(对应没有设置的属性)            总结:孩子有边框,孩子变大了,但是父亲不受影响,也就是父盒子里面的子盒子会受内边框影响,但是父盒子不会发生变化        */        .div5 {            width: 300px;            height: 100px;            background-color: pink;        }        .div5 p {            /* p的width不会变成300+30*2=360px,在自身width没有指定的情况下,不会超出父亲的范围 */            /* width: 100%; */                        padding:30px;        }        /* 8. 外边距(margin) 控制盒子和盒子之间的距离            属性:  1.margin-left 左外边距                    2.margin-right 右外边距                    3.margin-top 上外边距                    4.margin-bottom 下外边距            注意:margin简写方式代表的意义和padding完全一致        */        .div6 {            height: 200px;            width: 100px;            background-color: pink;        }        /* #one {            margin-bottom: 20px;        } */        #two {            margin-top: 20px;            margin-left: 20px;            margin-bottom: 50px;        }        /* 8.1 外边距的典型应用            外边距可以让块级盒子水平居中(盒子默认左侧对齐),但是必须满足两个条件:                1.盒子必须指定了宽度(width),否则它就跟浏览器/父盒子一样宽了                2.盒子左右的外边距都设置为 auto            以下三种写法都可以:                1.margin-left: auto; margin-right: auto;                2.margin: auto; (上下左右都auto了)                3.(常用)margin: 0px auto; (上下外边距为0px 左右auto)            注意:以上方法是让块级元素居中,行内元素或者行内块元素水平居中给其 父元素 添加 text-align: center; 即可        */        .header {            width: 500px;            height: 200px;            background-color: skyblue;            /* 简写法:上下外边距为100px,左右外边距auto实现块级元素自动水平居中 */            margin: 100px auto;            /* text-align: center 使行内元素和行内块元素(img)都会居中对齐 */            text-align: center;        }        #span1 {            margin: 0 auto;            /* margin: 0 auto;  不起效果,其只对块级元素起作用                给其父元素添加 text-align: center才对            */        }        /* 8.2  使用margin定义块元素的垂直外边距时,可能会出现外边距的合并        嵌套块元素垂直外边距的塌陷(父盒子跟者子盒子一起塌下来了):            对于两个嵌套关系(父子关系)的块级元素,父元素有上边距同时子元素也有上边距,此时父元素会塌陷较大的外边距值                解决方案:                    1.可以为父元素定义上边框                    2.可以为父元素定义上内边框                    3.可以为父元素添加 overflow: hidden         */        #father {            width: 400px;            height: 400px;            background-color: purple;            margin-top: 50px;            /* 父元素塌陷解决 */            /* 1.  transparent(透明)*/            /* border: 1px solid transparent; */            /* 2. */            /* padding: 1px; */            /* 3. 最好,不会改变盒子的大小,上面两种都有影响*/            overflow: hidden;            /* 4.还有其他方法,比如 浮动、固定、绝对定位 这些的盒子不会有塌陷问题 */        }        #son {            width: 200px;            height: 200px;            background-color: pink;            /* 本来子元素按照margin-top:100px 应该从父元素的上顶部分开,但实际并没有,而是父元素跟者子元素一起下来了(塌陷) */            margin-top: 100px;        }        /* 9. 内外边距的清除  */        /*  网页很多元素都会自带默认的内外边距,而且不同浏览器默认的也不一致。            因此我们在布局前,要先清除网页元素的内外边距                 *{                    padding: 0; 清除内边距                    margin: 0; 清除外边距                }           !注意:                 行内元素为了照顾兼容性,尽量只设置左右内外边距,不要设置上下内外边距(即使设置也不起效果)。但是转换为块级元素和行内元素就可以        */        /* ! 这句话也是我们CSS的第一行代码  !*/        * {            padding: 0;            margin: 0;        }        #span2 {            background-color: pink;            margin: 20px;        }    </style></head><body>    <!-- 测试边框各属性 -->    <div id="div1"></div>    <br />    <!-- 测试边框分开写法 -->    <div id="div2"></div>    <!-- 测试表格细线边框 -->    <table align="center" border="1" cellpadding="0" cellspacing="0">        <thead> <!--注意不要把<thead>和<th>搞混 前一个是表结构标签,使表格层次看着更清晰,而后者是类似加粗版的<td>-->            <tr>                <th>排名</th>                <th>关键词</th>                <th>趋势</th>                <th>进入搜索</th>                <th>最近七日</th>                <th>相关链接</th>            </tr>        </thead>        <tbody>            <tr>                <td>1</td>                <td>鬼吹灯</td>                <td><img src="picture/down.jpeg" /></td>                <td>345</td>                <td>123</td>                <td><a href="#">贴吧</a> <a href="#">图片</a> <a href="#">百科</a></td>            </tr>            <tr>                <td>2</td>                <td>盗墓笔记</td>                <td><img src="picture/down.jpeg" /></td>                <td>124</td>                <td>123421</td>                <td><a href="#" target="_blank">贴吧</a> <a href="#">图片</a> <a href="#">百科</a></td>            </tr>            <tr>                <td>3</td>                <td>西游记</td>                <td><img src="picture/up.jpeg" /></td>                <td>212</td>                <td>3213</td>                <td><a href="#" target="_blank">贴吧</a> <a href="#">图片</a> <a href="#">百科</a></td>            </tr>            <tr>                <td>4</td>                <td>甄嬛传</td>                <td><img src="picture/up.jpeg" /></td>                <td>2343</td>                <td>243343</td>                <td><a href="#" target="_blank">贴吧</a> <a href="#">图片</a> <a href="#">百科</a></td>            </tr>        </tbody>    </table>    <!-- 盒子模型内边距测试 -->    <div class="div3">这是盒子模型的内边距测试 天王盖地虎,宝塔镇河妖妖怪阿斯蒂芬哈喽</div>    <!-- 内边距的应用 -->    <div id="nav">        <a href="">新浪微博</a>        <a href="">手机新浪网</a>        <a href="">移动客户端</a>        <a href="">微博</a>        <a href="">交友网</a>    </div>    <!-- 测试padding不会影响盒子大小的情况 -->    <div class="div5">        <p>        </p>    </div>    换行    <br>    <!-- 测试盒子模型之外边距 -->    <div class="div6" id="one"></div>    <div class="div6" id="two"></div>    <!-- 外边距的应用 -->    <div class="header">        <span id="span1">里面的文字,为行内元素</span>        <img src="picture/up.jpeg" />    </div>    <br>    <!-- 外边距的合并 -->    <div id="father">        <div id="son"></div>    </div>    <br />    <!-- 内外边距的清除 -->    <!-- 网页很多元素都会自带默认的内外边距,而且不同浏览器默认的也不一致。         因此我们在布局前,要先清除网页元素的内外边距     -->    12345    <ul>        <li>            你好        </li>    </ul>    <span id="span2">行内元素尽量只设置左右的内外边距</span></body></html>运行项目并下载源码html显示效果:CSS美化三剑客:圆角边框、盒子阴影与文字阴影在网页设计中,细节决定品质。圆角边框、盒子阴影和文字阴影这三个CSS属性,虽然看似简单,却能瞬间提升页面的精致度和立体感。1. 圆角边框(border-radius):告别生硬直角默认情况下,HTML元素的边框都是直角的,显得生硬刻板。border-radius属性通过设置"圆角半径",能让元素边缘呈现平滑的弧形效果,是现代UI设计的基础。核心语法:圆角的本质圆角效果的原理是在元素的每个角落绘制一个圆形(或椭圆),圆形与边框的交集形成弧形边缘。border-radius的值就是这个圆形的半径,值越大,圆角越明显。/* 基础语法 */selector {  border-radius: length; /* 可以是px、%等单位 */}运行项目并下载源码css实战案例:从矩形到圆形圆形效果当元素是正方形时,将border-radius设置为宽度的50%,即可得到完美圆形:.yuanxing {  width: 300px;  height: 300px; /* 宽高相等的正方形 */  background-color: pink;  border-radius: 50%; /* 半径=宽高的一半,形成圆形 */}运行项目并下载源码css圆角矩形对于长方形,将border-radius设置为高度的一半,可得到两侧半圆的胶囊形:.juxing {  width: 300px;  height: 100px; /* 高度是宽度的1/3 */  background-color: pink;  border-radius: 50px; /* 50px = 100px高度的一半 */}运行项目并下载源码css自定义不规则圆角border-radius支持为四个角分别设置不同半径,顺序为左上角→右上角→右下角→左下角(顺时针):.radius1 {  width: 200px;  height: 200px;  background-color: pink;  /* 单独设置左上角圆角 */  border-top-left-radius: 30px;  /* 也可简写:border-radius: 10px 20px 30px 40px; */}运行项目并下载源码css实用技巧单位选择:px适合固定尺寸的圆角,%适合响应式设计(随元素大小自动调整)兼容处理:现代浏览器均支持,但老旧浏览器(如IE8及以下)不支持,需谨慎使用常见场景:按钮、头像、卡片组件、输入框等需要柔和边缘的元素2. 盒子阴影(box-shadow):给元素添加立体感现实世界中,物体总会因为光线产生阴影,box-shadow属性正是通过模拟这种光影效果,让平面的元素产生立体感。语法解析selector {  box-shadow: h-shadow v-shadow blur spread color inset;}运行项目并下载源码css各参数含义:h-shadow:必需,水平阴影位置(正值向右,负值向左)v-shadow:必需,垂直阴影位置(正值向下,负值向上)blur:可选,模糊距离(值越大,阴影越模糊)spread:可选,阴影尺寸(正值扩大阴影,负值缩小)color:可选,阴影颜色(常用rgba设置半透明)inset:可选,将外阴影改为内阴影(默认是外阴影,不可写outset)实战案例:交互增强效果基础阴影效果为元素添加轻微阴影,增强层次感:.shadow1 {  width: 200px;  height: 200px;  background-color: pink;  /* 水平11px、垂直17px、模糊14px、半透明黑色阴影 */  box-shadow: 11px 17px 14px -4px rgba(0, 0, 0, 0.3);}运行项目并下载源码csshover交互阴影鼠标悬浮时显示阴影,提升交互体验:.shadow1 {  width: 200px;  height: 200px;  background-color: pink;  transition: box-shadow 0.3s; /* 平滑过渡 */}.shadow1:hover {  /* 鼠标经过时显示阴影 */  box-shadow: 11px 17px 14px -4px rgba(0, 0, 0, 0.3);}运行项目并下载源码css注意事项阴影不占用空间:不会影响其他元素的布局,也不会撑开父容器多重阴影:用逗号分隔可添加多个阴影,实现复杂效果性能考量:过多或过大的阴影可能影响页面渲染性能,需适度使用3. 文字阴影(text-shadow):让文字更有质感文字作为页面的核心内容,适当的阴影能增强可读和视觉冲击力。text-shadow的用法与box-shadow类似,但作用对象是文字。语法解析selector {  text-shadow: h-shadow v-shadow blur color;}运行项目并下载源码css参数与盒子阴影基本一致,只是没有spread(阴影尺寸)和inset(内阴影)属性。实战案例:突出标题文字#character {  font-size: 50px;  color: orange;  font-weight: 700;  /* 水平5px、垂直5px、模糊6px、半透明黑色阴影 */  text-shadow: 5px 5px 6px rgba(0, 0, 0, 0.3);}运行项目并下载源码css这段代码会让文字产生轻微的立体感,同时与背景形成更好的区分度。创意用法发光效果:使用与文字同色的阴影,设置较大的模糊值.glow-text {  color: white;  text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #ff00de;}运行项目并下载源码css立体字效果:叠加多个方向的阴影,模拟光照层次感4. 综合实战:打造精致卡片组件将三个属性结合使用,能快速提升组件质感。例如一个产品卡片:.product-card {  width: 300px;  padding: 20px;  background: orange;  border-radius: 10px; /* 圆角边框 */  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); /* 轻微外阴影 */}.product-card h3 {   font-size: 20px;   text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); /* 文字阴影 */}运行项目并下载源码css这样的卡片会显得精致且有层次,远胜于生硬的直角和扁平效果。综合代码演示<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>圆角边框+盒子阴影+文字阴影</title>    <style>        /* 111.圆角边框  */        /* border-radius: length; 设置元素的外边框圆角 radius(圆的半径),圆与边框的交集形成的圆角效果             参数值可以为 数值 或 百分比 的形式                1.如果盒子是个正方形,想要设置为一个圆,把数值修改为高度或宽度的一半即可,或者直接写为50%                2.如果盒子是个矩形,设置为高度的一半就可以                3.该属性是一个简写属性,可以跟四个值,分别代表左上角、右上角、右下角、左下角                4.每个角分开写:border-top-left-radius、border-top-right-radius(top必须在前面)、                               border-bottom-right-radius、border-bottom-lef-radius(bottom也要在前面)        */        /* 圆形  radiu设置为盒子宽度的一半即可*/        .yuanxing {            width: 300px;            height: 300px;            background-color: pink;            /* border-radius: 150px; */            /* 50% 就是宽度和高度的一半 */            border-radius: 50%;        }        /* 圆角矩形 radius设置为盒子高度的一半*/        .juxing {            width: 300px;            height: 100px;            background-color: pink;            border-radius: 50px;        }        /* 每个角设置不同的radius */        .radius1 {            width: 200px;            height: 200px;            background-color: pink;            /* border-radius: 10px 20px 30px 40px; */            /* border-radius: 10px 40px; */            border-top-left-radius: 30px;        }        /* 222.盒子阴影  */        /* 语法:box-shadow: h-shadow v-shadow blur spread color inset                h-shadow 必需。水平阴影的位置,允许负值(数值越大,影子越往右边偏移)                v-shadow 必需。垂直阴影的位置,允许负值(数值越大,影子越往下偏移)                blur 可选。模糊距离(影子是虚的还是实的,数值越大,影子越模糊)                spread 可选。阴影的尺寸                color 可选。阴影的颜色                inset 可选。将外部阴影(outset)改为内部阴影            注意;                1.默认是外阴影(outset),但是不可以书写这个单词,否则将导致阴影无效                2.盒子阴影不占空间,不会影响其他盒子的排列位置        */        .shadow1 {            width: 200px;            height: 200px;            background-color: pink;            margin: 100px auto;            /* rgba半透明化 */            /* box-shadow: 11px 17px 14px -4px rgba(0, 0, 0, 0.3); */        }        .shadow1:hover {            /* 原先盒子没有影子,当鼠标经过盒子时就添加阴影效果 */            box-shadow: 11px 17px 14px -4px rgba(0, 0, 0, 0.3);        }        /* 文字阴影 */        /* 语法: text-shadow: h-shadow v-shadow blur color;                h-shadow 必需。水平阴影的位置,允许负值                v-shadow 必需。竖直阴影的位置,允许负值                blur 可选。模糊的距离)                color 可选。模糊的颜(数值越大阴影越虚)        */        #character {            font-size: 50px;            color: orange;            font-weight: 700;            text-shadow: 5px 5px 6px rgba(0, 0, 0, 0.3);        }          .glow-text {            color: aqua;            font-size: 100px;            text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px aqua;          }          .product-card {            width: 300px;            padding: 20px;            background: orange;            border-radius: 10px; /* 圆角边框 */            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); /* 轻微外阴影 */          }        .product-card h3 {            font-size: 20px;            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); /* 文字阴影 */        }    </style></head><body>    <!-- 圆形的做法  -->    圆形    <div class="yuanxing"></div>    <p>分隔</p>    <!-- 圆角矩形 -->    圆角矩形    <div class="juxing"></div>    <p>分隔</p>    <div class="radius1"></div>    <p>分隔</p>    <!-- 盒子阴影测试 -->    <div class="shadow1"></div>    <!-- 文字阴影测试 -->    <div id="character">你是阴影,我是火影</div>    <div class="glow-text">glow测试</div>    <div class="product-card">        <h3>大夏境内,诸神禁行</h3>    </div></body></html>运行项目并下载源码html————————————————原文链接:https://blog.csdn.net/2401_86760859/article/details/154315541
  • [技术干货] 【Java Web学习 | 第八篇】JavaScript(2) 基础知识2
    JavaScript 运算符与流程控制全解析在 JavaScript 中,运算符和流程控制是实现逻辑处理的基础。本文在前文基础上补充for循环内容,全面讲解比较运算符、逻辑运算符、条件判断语句(if、switch)及循环语句(while、for),帮助你掌握 JavaScript 的逻辑构建能力。一、运算符:自增、比较与逻辑1. 自增运算符(++)自增运算符分为前缀(++i)和后缀(i++),核心区别在于返回值时机:后缀自增(i++):先返回当前值,再自增前缀自增(++i):先自增,再返回新值let i = 1;console.log(i++ + ++i + i); // 结果为7// 解析:// 1. i++ 先返回1,i变为2// 2. ++i 先自增为3,返回3// 3. 此时i=3,总和:1+3+3=7一键获取完整项目代码javascript2. 比较运算符用于判断值的关系,返回布尔值,需重点区分==与===:运算符    特点    示例    结果==    只比较值(隐式转换类型)    2 == "2"    true(字符串转数字)===    比较值和类型(无转换)    2 === "2"    false(类型不同)!=    只比较值不相等    2 != "2"    false(值相等)!==    比较值或类型不相等    2 !== "2"    true(类型不同)特殊规则:字符串按字符编码比较(如"a" < "b"为true)NaN与任何值比较都返回false(包括自身)console.log(2 == "2"); // true(隐式转换)console.log(2 === "2"); // false(类型不同)console.log("aabgg" < "bzzzz"); // true(首字符"a"<"b")一键获取完整项目代码javascript3. 逻辑运算符组合多个条件判断,返回布尔值:运算符    描述    示例    结果&&    逻辑与(两边都真才真)    3<5 && 3<9    true两条竖线(打不出来)    逻辑或(至少一边真则真)    3<5 两条竖线 3>100    true!    逻辑非(取反)    !(2>5)    true真值/假值规则:假值:0、""、null、undefined、NaN真值:除假值外的所有值(如非空字符串、非0数字)console.log(!9); // false(9是真值)console.log(!""); // true(空字符串是假值)一键获取完整项目代码javascript二、条件判断语句1. if 语句根据条件执行代码块,条件会隐式转换为布尔值:// 语法if (条件) {  // 条件为真时执行} else {  // 条件为假时执行}// 示例:判断空格字符串是否为真if (" ") { // 空格字符串是真值(非空)  console.log(true); // 输出:true} else {  console.log(false);}一键获取完整项目代码javascript2. 三目运算符if-else的简写形式:条件 ? 表达式1 : 表达式2// 判断2与"2"是否绝对相等document.write(2 === "2" ? "相等" : "不等"); // 输出:不等一键获取完整项目代码javascript3. switch 语句用于多条件等值判断(使用===比较),需配合break防止穿透:let num5 = 3;switch (num5) {  case 1:    console.log("选了1");    break;  case 2:    console.log("选了2");    break;  case 3:    console.log("选了3"); // 匹配成功,输出此句    break;  default:    console.log("无匹配项");}// 输出:选了3一键获取完整项目代码javascript三、循环语句1. while 循环根据条件重复执行代码块,continue可跳过本次循环:let n = 5;while (n--) { // n从5递减到0  if (n === 3) continue; // 跳过n=3的循环  document.write(`执行第${n}次<br/>`);}// 输出:// 执行第4次// 执行第2次// 执行第1次// 执行第0次一键获取完整项目代码javascript2. for 循环更灵活的循环方式,适合已知循环次数的场景,语法:for (初始化; 条件; 更新) {  // 循环体}一键获取完整项目代码javascript示例:基本用法// 输出1-5for (let i = 1; i <= 5; i++) {  console.log(i); // 依次输出1、2、3、4、5}一键获取完整项目代码javascript总结本文全面讲解了 JavaScript 核心运算符和流程控制:自增运算符的前缀/后缀差异影响返回值比较运算符中===比==更严格(检查类型)逻辑运算符依赖真值/假值转换条件判断:if适合区间判断,switch适合等值判断循环:while适合未知次数,for适合已知次数,灵活使用continue和break————————————————原文链接:https://blog.csdn.net/2401_86760859/article/details/154581317
  • [技术干货] Java IO 流进阶:Buffer 与 Channel 核心概念解析及与传统 IO 的本质区别
     在 Java IO 编程中,传统的字节流与字符流大家都不陌生,但当面对高并发、大文件处理等场景时,NIO(New IO)中的 Buffer 与 Channel 逐渐成为性能优化的关键。本文将深入剖析 Buffer 与 Channel 的核心概念,通过对比传统 IO 流,带你理解它们为何能显著提升 IO 效率,并配合直观的图示帮你建立清晰的认知。一、传统 IO 流的局限性:为什么需要 Buffer/Channel?        在了解 Buffer 与 Channel 之前,我们先回顾传统 IO 流的工作方式。传统 IO 流分为字节流(InputStream/OutputStream) 和字符流(Reader/Writer),其核心特点可概括为:单向传输:流是单向的,输入流只能读,输出流只能写,如FileInputStream只能从文件读数据,FileOutputStream只能向文件写数据。阻塞操作:读写操作是阻塞的,当调用read()或write()时,线程会一直等待数据传输完成,期间无法做其他事情。直接操作数据:数据通过流直接传输,没有中间缓冲层,每次读写都可能触发底层系统调用(如磁盘 IO 或网络 IO),而系统调用的开销是很大的。我们用一张图直观展示传统 IO 流的工作模式: 传统 IO 的瓶颈:在高并发场景下,频繁的系统调用和线程阻塞会导致资源浪费(如线程上下文切换),而单向传输也限制了数据操作的灵活性。为解决这些问题,JDK 1.4 引入了 NIO,其中 Buffer(缓冲区)和 Channel(通道)是核心组件。二、Buffer:数据的 "临时仓库"        Buffer 是 NIO 中用于存储数据的容器,本质是一块内存区域,可以理解为 "数据的临时仓库"。所有数据的读写都必须通过 Buffer 完成,这与传统 IO 直接操作流的方式截然不同。2.1 Buffer 的核心属性Buffer 有三个核心属性,决定了其读写状态,这是理解 Buffer 的关键:capacity(容量):Buffer 的最大容量(初始化后不可变),即最多能存储多少数据(如 1024 字节)。position(位置):当前操作的位置(类似指针)。写数据时:position 从 0 开始,每写入一个数据,position+1,最大为 capacity-1。读数据时:position 从 0 开始,每读取一个数据,position+1,最大为 limit-1。limit(限制):当前可操作的数据边界。写模式下:limit = capacity(最多写到容量上限)。读模式下:limit = 写模式结束时的 position(最多读到实际写入的数据量)。此外,还有一个可选属性mark(标记),用于记录某个位置,方便后续通过reset()回到该位置。2.2 Buffer 的工作流程(以读文件为例)写模式:从 Channel 读取数据到 Buffer,此时 position 从 0 开始递增,直到数据写完(position = 实际写入量)。切换读模式:调用flip()方法,将 limit 设为当前 position,position 重置为 0,准备读取数据。读模式:从 Buffer 读取数据到程序,position 从 0 开始递增,直到 limit(即实际写入量)。清空 / 重用:调用clear()(清空缓冲区,position=0,limit=capacity)或compact()(保留未读完的数据,将其移到缓冲区开头),准备下次写入。用图示展示 Buffer 的状态变化: 2.3 常见 Buffer 类型Java 为不同数据类型提供了对应的 Buffer 实现(除 boolean 外):ByteBuffer(最常用,处理字节数据)CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer        其中ByteBuffer支持直接内存(堆外内存)分配,通过allocateDirect(int capacity)创建,减少了 JVM 堆内存与 native 内存之间的复制,适合大文件或频繁 IO 场景。三、Channel:双向的数据通道Channel(通道)是 NIO 中数据传输的 "通道",类似于传统 IO 中的流,但有本质区别:双向性:Channel 是双向的,既可以读也可以写(通过isReadable()/isWritable()判断),而流是单向的。基于 Buffer 操作:Channel 必须配合 Buffer 使用,数据的读写都通过 Buffer 完成(read(Buffer)和write(Buffer))。支持非阻塞:部分 Channel(如SocketChannel、ServerSocketChannel)支持非阻塞模式,配合 Selector 可实现高效的多路复用。可异步关闭:Channel 可以被异步关闭,且关闭后相关操作会立即终止。3.1 常见 Channel 类型FileChannel:用于文件读写,只能在阻塞模式下工作。SocketChannel:用于 TCP 客户端网络通信,支持非阻塞。ServerSocketChannel:用于 TCP 服务器端监听连接,支持非阻塞。DatagramChannel:用于 UDP 协议的数据传输,支持非阻塞。3.2 Channel 与 Buffer 的协作流程以文件读写为例,Channel 与 Buffer 的交互流程如下:打开 Channel(如FileChannel)。创建 Buffer(如ByteBuffer)。读操作:Channel 将数据写入 Buffer(channel.read(buffer))。切换 Buffer 为读模式(buffer.flip())。从 Buffer 读取数据到程序(buffer.get())。写操作:程序将数据写入 Buffer(buffer.put())。切换 Buffer 为写模式(buffer.flip()或buffer.compact())。Channel 从 Buffer 读取数据并写入目标(channel.write(buffer))。关闭 Channel 和清理 Buffer。用图示展示这一过程: 四、Buffer/Channel 与传统 IO 流的核心区别为了更清晰地对比,我们用表格总结两者的关键差异:特性    传统 IO 流    Buffer/Channel (NIO)数据传输方式    直接通过流传输,无缓冲层    必须通过 Buffer 间接传输方向性    单向(输入流只读,输出流只写)    双向(Channel 可同时读写)阻塞性    阻塞 IO(操作时线程等待)    支持非阻塞 IO(配合 Selector)效率    频繁系统调用,效率低    批量操作减少系统调用,效率高适用场景    简单 IO、低并发场景    高并发、大文件、网络 IO 场景操作粒度    字节 / 字符级(单次操作一个数据)    缓冲区级(单次操作一批数据)核心差异本质:传统 IO 是 "流导向",NIO 是 "缓冲区导向"。缓冲区导向通过批量处理数据减少了用户态与内核态的切换(系统调用),而非阻塞特性则避免了线程在 IO 等待时的资源浪费,这也是 NIO 在高并发场景下性能更优的根本原因。五、简单示例:传统 IO 与 NIO 读写文件对比5.1 传统 IO 文件复制// 传统IO流实现文件复制try (InputStream in = new FileInputStream("source.txt");     OutputStream out = new FileOutputStream("target.txt")) {    byte[] buffer = new byte[1024];    int len;    while ((len = in.read(buffer)) != -1) { // 每次读1024字节到临时数组        out.write(buffer, 0, len); // 直接写入输出流    }} catch (IOException e) {    e.printStackTrace();}一键获取完整项目代码java5.2 NIO(Buffer/Channel)文件复制// NIO(Buffer+Channel)实现文件复制try (FileChannel inChannel = new FileInputStream("source.txt").getChannel();     FileChannel outChannel = new FileOutputStream("target.txt").getChannel()) {    ByteBuffer buffer = ByteBuffer.allocateDirect(1024); // 直接内存缓冲区    while (inChannel.read(buffer) != -1) { // 从通道读数据到缓冲区        buffer.flip(); // 切换为读模式        outChannel.write(buffer); // 从缓冲区写数据到通道        buffer.clear(); // 清空缓冲区,准备下次读取    }} catch (IOException e) {    e.printStackTrace();}一键获取完整项目代码java对比分析:虽然两者都用到了 "缓冲区"(传统 IO 的 byte 数组也是一种缓冲),但 NIO 的 Buffer 是与 Channel 深度结合的抽象,提供了更精细的状态管理(position/limit),且FileChannel支持transferTo()/transferFrom()方法直接在通道间传输数据(零拷贝),效率远高于传统 IO。六、总结        Buffer 与 Channel 是 Java NIO 的核心组件,它们通过 "缓冲区导向" 和 "双向通道" 的设计,解决了传统 IO 流在高并发场景下的效率问题:Buffer:作为数据的临时仓库,通过 position/limit/capacity 管理数据读写状态,支持批量操作,减少系统调用。Channel:作为双向数据通道,必须配合 Buffer 使用,支持非阻塞模式,提升了 IO 操作的灵活性和效率。        在实际开发中,简单场景(如小文件读写)用传统 IO 更简洁,而高并发、大文件或网络编程场景(如 Netty 框架)则应优先考虑 NIO 的 Buffer 与 Channel,以获得更好的性能。        希望本文能帮助你理清 Buffer/Channel 与传统 IO 的区别,为后续深入学习 NIO(如 Selector、非阻塞模式)打下基础。如果有疑问,欢迎在评论区交流!————————————————原文链接:https://blog.csdn.net/qq_40303030/article/details/152888895
  • [技术干货] JavaSE重点总结后篇
    一、面向对象1、深拷贝和其那拷贝的区别        在 Java 中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两种拷贝对象的方式,它们在拷贝对象的方式上有很大不同。          浅拷贝会创建一个新对象,但这个新对象的属性(字段)和原对象的属性完全相同。如果属性是基本数据类型,拷贝的是基本数据类型的值;如果属性是引用类型,拷贝的是引用地址,因此新旧对象共享同一个引用对象。        浅拷贝的实现方式为:实现 Cloneable 接口并重写 clone() 方法。class Person implements Cloneable {    String name;    int age;    Address address;     public Person(String name, int age, Address address) {        this.name = name;        this.age = age;        this.address = address;    }     @Override    protected Object clone() throws CloneNotSupportedException {        return super.clone();    }} class Address {    String city;     public Address(String city) {        this.city = city;    }} public class Main {    public static void main(String[] args) throws CloneNotSupportedException {        Address address = new Address("张家口");        Person person1 = new Person("寻星探路", 18, address);        Person person2 = (Person) person1.clone();         System.out.println(person1.address == person2.address); // true    }}一键获取完整项目代码java        深拷贝也会创建一个新对象,但会递归地复制所有的引用对象,确保新对象和原对象完全独立。新对象与原对象的任何更改都不会相互影响。深拷贝的实现方式有:手动复制所有的引用对象,或者使用序列化与反序列化。①、手动拷贝class Person {    String name;    int age;    Address address;     public Person(String name, int age, Address address) {        this.name = name;        this.age = age;        this.address = address;    }     public Person(Person person) {        this.name = person.name;        this.age = person.age;        this.address = new Address(person.address.city);    }} class Address {    String city;     public Address(String city) {        this.city = city;    }} public class Main {    public static void main(String[] args) {        Address address = new Address("张家口");        Person person1 = new Person("寻星探路", 18, address);        Person person2 = new Person(person1);         System.out.println(person1.address == person2.address); // false    }}一键获取完整项目代码java②、序列化与反序列化import java.io.*; class Person implements Serializable {    String name;    int age;    Address address;     public Person(String name, int age, Address address) {        this.name = name;        this.age = age;        this.address = address;    }     public Person deepClone() throws IOException, ClassNotFoundException {        ByteArrayOutputStream bos = new ByteArrayOutputStream();        ObjectOutputStream oos = new ObjectOutputStream(bos);        oos.writeObject(this);         ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());        ObjectInputStream ois = new ObjectInputStream(bis);        return (Person) ois.readObject();    }} class Address implements Serializable {    String city;     public Address(String city) {        this.city = city;    }} public class Main {    public static void main(String[] args) throws IOException, ClassNotFoundException {        Address address = new Address("张家口");        Person person1 = new Person("寻星探路", 18, address);        Person person2 = person1.deepClone();         System.out.println(person1.address == person2.address); // false    }}一键获取完整项目代码java2、Java创建对象有哪几种方式?Java 有四种创建对象的方式:①、new 关键字创建,这是最常见和直接的方式,通过调用类的构造方法来创建对象。Person person = new Person();一键获取完整项目代码java②、反射机制创建,反射机制允许在运行时创建对象,并且可以访问类的私有成员,在框架和工具类中比较常见。Class clazz = Class.forName("Person");Person person = (Person) clazz.newInstance();一键获取完整项目代码java③、clone 拷贝创建,通过 clone 方法创建对象,需要实现 Cloneable 接口并重写 clone 方法。Person person = new Person();Person person2 = (Person) person.clone();一键获取完整项目代码java④、序列化机制创建,通过序列化将对象转换为字节流,再通过反序列化从字节流中恢复对象。需要实现 Serializable 接口。Person person = new Person();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));oos.writeObject(person);ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"));Person person2 = (Person) ois.readObject();一键获取完整项目代码java二、String1、String 和StringBuilder、StringBuffer 的区别?        String 和StringBuilder、StringBuffer在 Java 中都是用于处理字符串的,它们之间的区别是,String 是不可变的,平常开发用得最多,当遇到大量字符串连接时,就用 StringBuilder,它不会生成很多新的对象,StringBuffer 和 StringBuilder 类似,但每个方法上都加了 synchronized 关键字,所以是线程安全的。 String的特点String类的对象是不可变的。也就是说,一旦一个String对象被创建,它所包含的字符串内容是不可改变的。每次对String对象进行修改操作(如拼接、替换等)实际上都会生成一个新的String对象,而不是修改原有对象。这可能会导致内存和性能开销,尤其是在大量字符串操作的情况下。StringBuilder的特点StringBuilder提供了一系列的方法来进行字符串的增删改查操作,这些操作都是直接在原有字符串对象的底层数组上进行的,而不是生成新的 String 对象。StringBuilder不是线程安全的。这意味着在没有外部同步的情况下,它不适用于多线程环境。相比于String,在进行频繁的字符串修改操作时,StringBuilder能提供更好的性能。 Java 中的字符串连+操作其实就是通过StringBuilder实现的。StringBuffer的特点   StringBuffer和StringBuilder类似,但StringBuffer是线程安全的,方法前面都加了synchronized关键字。使用场景String:适用于字符串内容不会改变的场景,比如说作为 HashMap 的 key。StringBuilder:适用于单线程环境下需要频繁修改字符串内容的场景,比如在循环中拼接或修改字符串,是 String 的完美替代品。StringBuffer:现在已经不怎么用了,因为一般不会在多线程场景下去频繁的修改字符串内容2、String 是不可变类吗?        String 是不可变的,这意味着一旦一个 String 对象被创建,其存储的文本内容就不能被改变。这是因为:①、不可变性使得 String 对象在使用中更加安全。因为字符串经常用作参数传递给其他 Java 方法,例如网络连接、打开文件等。        如果 String 是可变的,这些方法调用的参数值就可能在不知不觉中被改变,从而导致网络连接被篡改、文件被莫名其妙地修改等问题。②、不可变的对象因为状态不会改变,所以更容易进行缓存和重用。字符串常量池的出现正是基于这个原因。        当代码中出现相同的字符串字面量时,JVM 会确保所有的引用都指向常量池中的同一个对象,从而节约内存。③、因为 String 的内容不会改变,所以它的哈希值也就固定不变。这使得 String 对象特别适合作为 HashMap 或 HashSet 等集合的键,因为计算哈希值只需要进行一次,提高了哈希表操作的效率。如何保证String不可变?        第一,String 类内部使用一个私有的字符数组来存储字符串数据。这个字符数组在创建字符串时被初始化,之后不允许被改变。private final char value[];一键获取完整项目代码java        第二,String 类没有提供任何可以修改其内容的公共方法,像 concat 这些看似修改字符串的操作,实际上都是返回一个新创建的字符串对象,而原始字符串对象保持不变。public String concat(String str) {    if (str.isEmpty()) {        return this;    }    int len = value.length;    int otherLen = str.length();    char buf[] = Arrays.copyOf(value, len + otherLen);    str.getChars(buf, len);    return new String(buf, true);}一键获取完整项目代码java        第三,String 类本身被声明为 final,这意味着它不能被继承。这防止了子类可能通过添加修改方法来改变字符串内容的可能性。public final class String一键获取完整项目代码java三、异常处理1、Java中的异常体系?        Java 中的异常处理机制用于处理程序运行过程中可能发生的各种异常情况,通常通过 try-catch-finally 语句和 throw 关键字来实现。   Throwable 是 Java 语言中所有错误和异常的基类。它有两个主要的子类:Error 和 Exception,这两个类分别代表了 Java 异常处理体系中的两个分支。        Error 类代表那些严重的错误,这类错误通常是程序无法处理的。比如,OutOfMemoryError 表示内存不足,StackOverflowError 表示栈溢出。这些错误通常与 JVM 的运行状态有关,一旦发生,应用程序通常无法恢复。        Exception 类代表程序可以处理的异常。它分为两大类:编译时异常(Checked Exception)和运行时异常(Runtime Exception)。①、编译时异常(Checked Exception):这类异常在编译时必须被显式处理(捕获或声明抛出)。        如果方法可能抛出某种编译时异常,但没有捕获它(try-catch)或没有在方法声明中用 throws 子句声明它,那么编译将不会通过。例如:IOException、SQLException 等。②、运行时异常(Runtime Exception):这类异常在运行时抛出,它们都是 RuntimeException 的子类。对于运行时异常,Java 编译器不要求必须处理它们(即不需要捕获也不需要声明抛出)。        运行时异常通常是由程序逻辑错误导致的,如 NullPointerException、IndexOutOfBoundsException 等。2、异常的处理方式①、遇到异常时可以不处理,直接通过throw 和 throws 抛出异常,交给上层调用者处理。throws 关键字用于声明可能会抛出的异常,而 throw 关键字用于抛出异常。public void test() throws Exception {    throw new Exception("抛出异常");}一键获取完整项目代码java②、使用 try-catch 捕获异常,处理异常。try {    //包含可能会出现异常的代码以及声明异常的方法}catch(Exception e) {    //捕获异常并进行处理}finally {    //可选,必执行的代码}一键获取完整项目代码javacatch和finally的异常可以同时抛出吗?        如果 catch 块抛出一个异常,而 finally 块中也抛出异常,那么最终抛出的将是 finally 块中的异常。catch 块中的异常会被丢弃,而 finally 块中的异常会覆盖并向上传递。public class Example {    public static void main(String[] args) {        try {            throw new Exception("Exception in try");        } catch (Exception e) {            throw new RuntimeException("Exception in catch");        } finally {            throw new IllegalArgumentException("Exception in finally");        }    }}一键获取完整项目代码javatry 块首先抛出一个 Exception。控制流进入 catch 块,catch 块中又抛出了一个 RuntimeException。但是在 finally 块中,抛出了一个 IllegalArgumentException,最终程序抛出的异常是 finally 块中的 IllegalArgumentException。        虽然 catch 和 finally 中的异常不能同时抛出,但可以手动捕获 finally 块中的异常,并将 catch 块中的异常保留下来,避免被覆盖。常见的做法是使用一个变量临时存储 catch 中的异常,然后在 finally 中处理该异常:public class Example {    public static void main(String[] args) {        Exception catchException = null;        try {            throw new Exception("Exception in try");        } catch (Exception e) {            catchException = e;            throw new RuntimeException("Exception in catch");        } finally {            try {                throw new IllegalArgumentException("Exception in finally");            } catch (IllegalArgumentException e) {                if (catchException != null) {                    System.out.println("Catch exception: " + catchException.getMessage());                }                System.out.println("Finally exception: " + e.getMessage());            }        }    }}一键获取完整项目代码java 四、I/O1、Java中IO流分为几种?        Jaa IO 流的划分可以根据多个维度进行,包括数据流的方向(输入或输出)、处理的数据单位(字节或字符)、流的功能以及流是否支持随机访问等。按照数据流方向进行划分?输入流(Input Stream):从源(如文件、网络等)读取数据到程序。输出流(Output Stream):将数据从程序写出到目的地(如文件、网络、控制台等)。按处理数据单位如何划分?字节流(Byte Streams):以字节为单位读写数据,主要用于处理二进制数据,如音频、图像文件等。字符流(Character Streams):以字符为单位读写数据,主要用于处理文本数据。按功能如何划分?节点流(Node Streams):直接与数据源或目的地相连,如 FileInputStream、FileOutputStream。处理流(Processing Streams):对一个已存在的流进行包装,如缓冲流 BufferedInputStream、BufferedOutputStream。管道流(Piped Streams):用于线程之间的数据传输,如 PipedInputStream、PipedOutputStream。IO流用到了什么设计模式?        用到了——装饰器模式,装饰器模式的核心思想是在不改变原有对象结构的前提下,动态地给对象添加新的功能。         具体到 Java IO 中,InputStream 和 OutputStream 这些抽象类定义了基本的读写操作,然后通过各种装饰器类来增强功能。比如 BufferedInputStream 给基础的输入流增加了缓冲功能,DataInputStream 增加了读取基本数据类型的能力,它们都是对基础流的装饰和增强。InputStream input = new BufferedInputStream(    new DataInputStream(        new FileInputStream("data.txt")    ));一键获取完整项目代码java        这里 FileInputStream 提供基本的文件读取能力,DataInputStream 装饰它增加了数据类型转换功能,BufferedInputStream 再装饰它增加了缓冲功能。每一层装饰都在原有功能基础上增加新特性,而且可以灵活组合。        我对装饰器模式的理解是它很好地体现了“组合优于继承”的设计原则。优势在于运行时动态组合功能,而且遵循开闭原则,可以在不修改现有代码的情况下增加新功能。Java 缓冲区溢出,如何预防?        Java 缓冲区溢出主要是由于向缓冲区写入的数据超过其能够存储的数据量。可以采用这些措施来避免:①、合理设置缓冲区大小:在创建缓冲区时,应根据实际需求合理设置缓冲区的大小,避免创建过大或过小的缓冲区。②、控制写入数据量:在向缓冲区写入数据时,应该控制写入的数据量,确保不会超过缓冲区的容量。Java 的 ByteBuffer 类提供了remaining()方法,可以获取缓冲区中剩余的可写入数据量。import java.nio.ByteBuffer; public class ByteBufferExample {     public static void main(String[] args) {        // 模拟接收到的数据        byte[] receivedData = {1, 2, 3, 4, 5};        int bufferSize = 1024;  // 设置一个合理的缓冲区大小         // 创建ByteBuffer        ByteBuffer buffer = ByteBuffer.allocate(bufferSize);         // 写入数据之前检查容量是否足够        if (buffer.remaining() >= receivedData.length) {            buffer.put(receivedData);        } else {            System.out.println("Not enough space in buffer to write data.");        }         // 准备读取数据:将limit设置为当前位置,position设回0        buffer.flip();         // 读取数据        while (buffer.hasRemaining()) {            byte data = buffer.get();            System.out.println("Read data: " + data);        }         // 清空缓冲区以便再次使用        buffer.clear();    }}一键获取完整项目代码java2、有了字节流为什么还要有字符流?        其实字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还比较耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。        所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。文本存储是字节流还是字符流,视频文件呢?        在计算机中,文本和视频都是按照字节存储的,只是如果是文本文件的话,我们可以通过字符流的形式去读取,这样更方面的我们进行直接处理。        比如说我们需要在一个大文本文件中查找某个字符串,可以直接通过字符流来读取判断。        处理视频文件时,通常使用字节流(如 Java 中的FileInputStream、FileOutputStream)来读取或写入数据,并且会尽量使用缓冲流(如BufferedInputStream、BufferedOutputStream)来提高读写效率。3、BIO、NIO、AIO 之间的区别?BIO:采用阻塞式 I/O 模型,线程在执行 I/O 操作时被阻塞,无法处理其他任务,适用于连接数较少的场景。NIO:采用非阻塞 I/O 模型,线程在等待 I/O 时可执行其他任务,通过 Selector 监控多个 Channel 上的事件,适用于连接数多但连接时间短的场景。AIO:使用异步 I/O 模型,线程发起 I/O 请求后立即返回,当 I/O 操作完成时通过回调函数通知线程,适用于连接数多且连接时间长的场景。五、序列化1、什么是序列化?什么是反序列化?        序列化(Serialization)是指将对象转换为字节流的过程,以便能够将该对象保存到文件、数据库,或者进行网络传输。        反序列化(Deserialization)就是将字节流转换回对象的过程,以便构建原始对象。 解释一下序列化的过程和作用序列化过程通常涉及到以下几个步骤:第一步,实现 Serializable 接口。public class Person implements Serializable {    private String name;    private int age;     // 省略构造方法、getters和setters}一键获取完整项目代码java第二步,使用 ObjectOutputStream 来将对象写入到输出流中。ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));一键获取完整项目代码java第三步,调用 ObjectOutputStream 的 writeObject 方法,将对象序列化并写入到输出流中。Person person = new Person("寻星探路", 18);out.writeObject(person);一键获取完整项目代码java2、有几种序列化方式?Java 对象序列化 :Java 原生序列化方法即通过 Java 原生流(InputStream 和 OutputStream 之间的转化)的方式进行转化,一般是对象输出流 ObjectOutputStream和对象输入流ObjectInputStream。Json 序列化:这个可能是我们最常用的序列化方式,Json 序列化的选择很多,一般会使用 jackson 包,通过 ObjectMapper 类来进行一些操作,比如将对象转化为 byte 数组或者将 json 串转化为对象。ProtoBuff 序列化:ProtocolBuffer 是一种轻便高效的结构化数据存储格式,ProtoBuff 序列化对象可以很大程度上将其压缩,可以大大减少数据传输大小,提高系统性能。六、网络编程1、了解过Socket网络套接字吗?        Socket 是网络通信的基础,表示两台设备之间通信的一个端点。Socket 通常用于建立 TCP 或 UDP 连接,实现进程间的网络通信。  一个简单的 TCP 客户端:class TcpClient {    public static void main(String[] args) throws IOException {        Socket socket = new Socket("127.0.0.1", 8080); // 连接服务器        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);         out.println("Hello, Server!"); // 发送消息        System.out.println("Server response: " + in.readLine()); // 接收服务器响应         socket.close();    }}一键获取完整项目代码javaTCP 服务端:class TcpServer {    public static void main(String[] args) throws IOException {        ServerSocket serverSocket = new ServerSocket(8080); // 创建服务器端Socket        System.out.println("Server started, waiting for connection...");        Socket socket = serverSocket.accept(); // 等待客户端连接        System.out.println("Client connected: " + socket.getInetAddress());         BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);         String message;        while ((message = in.readLine()) != null) {            System.out.println("Received: " + message);            out.println("Echo: " + message); // 回送消息        }         socket.close();        serverSocket.close();    }}一键获取完整项目代码javaRPC框架了解吗?        RPC是一种协议,允许程序调用位于远程服务器上的方法,就像调用本地方法一样。RPC 通常基于 Socket 通信实现。RPC,Remote Procedure Call,远程过程调用        RPC 框架支持高效的序列化(如 Protocol Buffers)和通信协议(如 HTTP/2),屏蔽了底层网络通信的细节,开发者只需关注业务逻辑即可。  常见的 RPC 框架包括:gRPC:基于 HTTP/2 和 Protocol Buffers。Dubbo:阿里开源的分布式 RPC 框架,适合微服务场景。Spring Cloud OpenFeign:基于 REST 的轻量级 RPC 框架。Thrift:Apache 的跨语言 RPC 框架,支持多语言代码生成。七、泛型1、Java泛型了吗?        泛型主要用于提高代码的类型安全,它允许在定义类、接口和方法时使用类型参数,这样可以在编译时检查类型一致性,避免不必要的类型转换和类型错误。        没有泛型的时候,像 List 这样的集合类存储的是 Object 类型,导致从集合中读取数据时,必须进行强制类型转换,否则会引发 ClassCastException。List list = new ArrayList();list.add("hello");String str = (String) list.get(0);  // 必须强制类型转换一键获取完整项目代码java泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。  1.泛型类://此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型//在实例化泛型类时,必须指定T的具体类型public class Generic<T>{     private T key;     public Generic(T key) {        this.key = key;    }     public T getKey(){        return key;    }}一键获取完整项目代码java如何实例化泛型类:Generic<Integer> genericInteger = new Generic<Integer>(123456);一键获取完整项目代码java2.泛型接口 :public interface Generator<T> {    public T method();}一键获取完整项目代码java实现泛型接口,指定类型:class GeneratorImpl<T> implements Generator<String>{    @Override    public String method() {        return "hello";    }}一键获取完整项目代码java3.泛型方法 :   public static < E > void printArray( E[] inputArray )   {         for ( E element : inputArray ){            System.out.printf( "%s ", element );         }         System.out.println();    }一键获取完整项目代码java使用:// 创建不同类型数组: Integer, Double 和 CharacterInteger[] intArray = { 1, 2, 3 };String[] stringArray = { "Hello", "World" };printArray( intArray  );printArray( stringArray  );一键获取完整项目代码java泛型常用的通配符有哪些?常用的通配符为: T,E,K,V,?? 表示不确定的 java 类型T (type) 表示具体的一个 java 类型K V (key value) 分别代表 java 键值中的 Key ValueE (element) 代表 Element什么是泛型擦除?所谓的泛型擦除,官方名叫“类型擦除”。Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的类型信息都会被擦掉。也就是说,在运行的时候是没有泛型的。例如这段代码,往一群猫里放条狗:LinkedList<Cat> cats = new LinkedList<Cat>();LinkedList list = cats;  // 注意我在这里把范型去掉了,但是list和cats是同一个链表!list.add(new Dog());  // 完全没问题!一键获取完整项目代码java        因为 Java 的范型只存在于源码里,编译的时候给你静态地检查一下范型类型是否正确,而到了运行时就不检查了。上面这段代码在 JRE(Java运行环境)看来和下面这段没区别:LinkedList cats = new LinkedList();  // 注意:没有范型!LinkedList list = cats;list.add(new Dog());一键获取完整项目代码java为什么要类型擦除呢?        主要是为了向下兼容,因为 JDK5 之前是没有泛型的,为了让 JVM 保持向下兼容,就出了类型擦除这个策略。八、反射1、什么是反射?原理?应用?        反射允许 Java 在运行时检查和操作类的方法和字段。通过反射,可以动态地获取类的字段、方法、构造方法等信息,并在运行时调用方法或访问字段。比如创建一个对象是通过 new 关键字来实现的:Person person = new Person();一键获取完整项目代码java        Person 类的信息在编译时就确定了,那假如在编译期无法确定类的信息,但又想在运行时获取类的信息、创建类的实例、调用类的方法,这时候就要用到反射。        反射功能主要通过 java.lang.Class 类及 java.lang.reflect 包中的类如 Method, Field, Constructor 等来实现。  比如说我们可以动态加载类并创建对象:String className = "java.util.Date";Class<?> cls = Class.forName(className);Object obj = cls.newInstance();System.out.println(obj.getClass().getName());一键获取完整项目代码java比如说我们可以这样来访问字段和方法:// 加载并实例化类Class<?> cls = Class.forName("java.util.Date");Object obj = cls.newInstance(); // 获取并调用方法Method method = cls.getMethod("getTime");Object result = method.invoke(obj);System.out.println("Time: " + result); // 访问字段Field field = cls.getDeclaredField("fastTime");field.setAccessible(true); // 对于私有字段需要这样做System.out.println("fastTime: " + field.getLong(obj));一键获取完整项目代码java反射有哪些应用场景?①、Spring 框架就大量使用了反射来动态加载和管理 Bean。Class<?> clazz = Class.forName("com.example.MyClass");Object instance = clazz.newInstance();一键获取完整项目代码java②、Java 的动态代理机制就使用了反射来创建代理类。代理类可以在运行时动态处理方法调用,这在实现 AOP 和拦截器时非常有用。InvocationHandler handler = new MyInvocationHandler();MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance(    MyInterface.class.getClassLoader(),    new Class<?>[] { MyInterface.class },    handler);一键获取完整项目代码java③、JUnit 和 TestNG 等测试框架使用反射机制来发现和执行测试方法。反射允许框架扫描类,查找带有特定注解(如 @Test)的方法,并在运行时调用它们。Method testMethod = testClass.getMethod("testSomething");testMethod.invoke(testInstance);一键获取完整项目代码java④、最常见的是写通用的工具类,比如对象拷贝工具。比如说 BeanUtils、MapStruct 等等,能够自动拷贝两个对象之间的同名属性,就是通过反射来实现的。 反射的原理是什么?        每个类在加载到 JVM 后,都会在方法区生成一个对应的 Class 对象,这个对象包含了类的所有元信息,比如字段、方法、构造器、注解等。        通过这个 Class 对象,我们就能在运行时动态地创建对象、调用方法、访问字段。反射的优缺点是什么?        反射的优点还是很明显的。首先是能够在运行时动态操作类和对象。其次是能够编写通用的代码,一套代码可以处理不同类型的对象。还有就是能够突破访问限制,访问 private 字段和方法,这在反编译场景下很有用。        但反射的缺点也不少。最明显的是性能问题,反射操作比直接调用要慢很多,因为需要在运行时解析类信息、进行类型检查、权限验证等。        其次是反射能够绕过访问控制,访问和修改 private 成员,这会破坏类的封装。九、Lambda表达式1、Lambda表达式了解多少?        Lambda 表达式主要用于提供一种简洁的方式来表示匿名方法,使 Java 具备了函数式编程的特性。比如说我们可以使用 Lambda 表达式来简化线程的创建:new Thread(() -> System.out.println("Hello World")).start();一键获取完整项目代码java这比以前的匿名内部类要简洁很多。        所谓的函数式编程,就是把函数作为参数传递给方法,或者作为方法的结果返回。比如说我们可以配合 Stream 流进行数据过滤:List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);List<Integer> evenNumbers = numbers.stream()    .filter(n -> n % 2 == 0)    .collect(Collectors.toList());一键获取完整项目代码java        其中 n -> n % 2 == 0 就是一个 Lambda 表达式。表示传入一个参数 n,返回 n % 2 == 0 的结果。————————————————原文链接:https://blog.csdn.net/2402_83476639/article/details/155102993
  • [技术干货] Java外功精要(3)——Spring配置文件和mybatis
    1.配置文件1.1 概述计算机配置文件:用于存储系统、应用程序的设置信息,通常以文本或结构化数据格式(如JSON、XML、INI等)保存。其核心功能包括但不限于:参数定制:允许用户或管理员调整软件或硬件的运行参数环境适配:根据不同设备或场景加载特定配置(如开发/生产环境)持久化存储:确保重启后设置仍生效SpringBoot配置文件:SpringBoot支持多种类型的配置文件,常见的格式包括properties、yaml和yml,主要用于集中管理应用程序的各种配置参数,简化部署和开发过程中的环境切换YAML和YML本质上是相同的文件格式,只是文件扩展名的不同,两者在功能和使用上没有区别1.2 propertiesproperties配置文件是最早期的配置⽂件格式,也是创建SpringBoot项⽬默认的配置⽂件采用常见的键值对格式(key=value)支持#开头的注释#应用程序名称spring.application.name=configuration#应用程序端口号server.port=8080#数据库连接信息spring.datasource.url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=root一键获取完整项目代码properties1.3 yml采用键值对格式(key: value),冒号后必须有空格数据序列化格式,通过缩进表示层级关系支持#开头的注释spring:  application:    #应用程序名称    name: configuration  #数据库连接信息  datasource:    url: jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=false    username: root    password: root#应用程序端口号server:  port: 8080一键获取完整项目代码yaml1.4 优缺点对比properties优点:语法简单直观,采用key=value形式,适合初学者快速上手与Java生态兼容性极强缺点:缺乏层次结构,复杂配置时容易冗余。上述配置数据库连接信息时spring.datasource前缀冗余不支持数据类型定义,所有值均为字符串,需手动转换yml优点:层次化结构清晰,通过缩进表示层级,适合复杂配置场景支持数据类型(如布尔值、数字),减少手动类型转换缺点:格式错误易导致解析失败(容易忽略冒号后空格)部分旧版工具链兼容性较差,需额外依赖解析库注:SpringBoot同时支持两种格式,混合使用时若key重复,properties优先级高于yml1.5 @Value注解作用:是Spring框架提供了一个@Value注解(org.springframework.beans.factory.annotation.Value),用于将外部配置文件中的值注入到Spring管理的Bean中示例:(properties和yml的读取方式相同)package org.example.configuration.config;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;@Configurationpublic class Config {    @Value("${spring.application.name}")    private String applicationName;    @Value("${server.port}")    private Integer port;    @Value("${spring.datasource.url}")    private String url;    @Value("${spring.datasource.username}")    private String username;    @Value("${spring.datasource.password}")    private String password;    public void print() {        System.out.println("applicationName=" + applicationName);        System.out.println("port=" + port);        System.out.println("url=" + url);        System.out.println("username=" + username);        System.out.println("password=" + password);    }}package org.example.configuration;import org.example.configuration.config.Config;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;@SpringBootApplicationpublic class ConfigurationApplication {    public static void main(String[] args) {        ApplicationContext context = SpringApplication.run(ConfigurationApplication.class, args);        Config config = context.getBean(Config.class);        config.print();    }}一键获取完整项目代码java运行结果:applicationName=configurationport=8080url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falseusername=rootpassword=root2.mybatis2.1 概述MyBatis是一款优秀的持久层框架,支持自定义 SQL、存储过程、高级映射以及多种配置方式。它消除了几乎所有的JDBC代码和参数的手动设置以及结果集的检索支持存储过程:指的是数据库管理系统(DBMS)允许用户创建、存储和执行存储过程的能力。存储过程是一组预编译的SQL语句,存储在数据库中,可以被应用程序调用执行支持高级映射:指通过配置或注解实现复杂SQL查询结果与Java对象之间的灵活转换。其核心目标是简化数据库关联操作,提升开发效率支持多种配置方式:mybatis支持注解和xml两种配置方式2.2 前置操作引入依赖:Spring Web,Mybatis Framework,MySQL Driver,Lombok在application.properties/yml中添加数据库连接信息:#应用程序名称spring.application.name=configuration#应用程序端口号server.port=8080#数据库连接信息spring.datasource.url=jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=root#自动驼峰转换mybatis.configuration.map-underscore-to-camel-case=true一键获取完整项目代码propertiesspring:  application:    #应用程序名称    name: configuration  #数据库连接信息  datasource:    url: jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=utf8&useSSL=false    username: root    password: root#应用程序端口号server:  port: 8080mybatis:  configuration:     map-underscore-to-camel-case: true #自动驼峰转换一键获取完整项目代码yamlSQL命名规范:采用下划线分隔单词(如order_detail)Java命名规范:大驼峰/小驼峰2.3 注解2.3.1 配置1.创建一个接口,并使用 @Mapper注解 修饰import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface BlogMapper {    //其他代码}一键获取完整项目代码java@Mapper注解:允许开发者直接在接口方法上通过注解配置SQL语句,无需编写XML映射文件。适用于简单SQL场景,能显著减少配置量2.初始化数据create table blog (id int primary key auto_increment,name varchar(128),age int);insert into blog values (null,'刘备',30),(null,'关羽',28),(null,'张飞',25);一键获取完整项目代码sql3.创建对应实体类import lombok.Data;@Datapublic class PersonInfo {    private Integer id;    private String name;    private Integer age;    public PersonInfo(Integer id, String name, Integer age) {        this.id = id;        this.name = name;        this.age = age;    }    public PersonInfo() {    }}一键获取完整项目代码sql2.3.2 CRUDimport com.example.spring_mybatis.model.PersonInfo;import org.apache.ibatis.annotations.*;@Mapperpublic interface BlogMapper {    @Select("select * from blog")    List<PersonInfo> getPersonInfoAll();    @Insert("insert into blog values (#{id},#{name},#{age})")    Integer addPerson(PersonInfo person);    @Update("update blog set name = #{name},age = #{age} where id = #{id}")    Integer updatePerson(PersonInfo personInfo);    @Delete("delete from blog where id = #{id}")    Integer deletePerson(Integer id);}一键获取完整项目代码sql按住alt+insert,可在test目录下生成以上方法的测试方法import com.example.spring_mybatis.model.PersonInfo;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }    @Test    void getPersonInfoAll() {        List<PersonInfo> personInfoAll = blogMapper.getPersonInfoAll();        log.info("查询成功,personInfoAll:{}",personInfoAll.toString());        //查询成功,personInfoAll:[PersonInfo(id=1, name=刘备, age=30),        //PersonInfo(id=2, name=关羽, age=28),        //PersonInfo(id=3, name=张飞, age=25)]    }    @Test    void addPerson() {        Integer ret = blogMapper.addPerson(new PersonInfo(null, "赵云", 25));        log.info("添加成功,影响行数:{}",ret.toString());//添加成功,影响行数:1    }    @Test    void updatePerson() {        Integer ret = blogMapper.updatePerson(new PersonInfo(1, "刘备", 35));        log.info("更新成功,影响行数:{}",ret.toString());//更新成功,影响行数:1    }    @Test    void deletePerson() {        Integer ret = blogMapper.deletePerson(4);        log.info("删除成功,影响行数:{}",ret.toString());//删除成功,影响行数:1    }}一键获取完整项目代码sql2.3.3 @Param作用:用于在Mapper接口方法中为形式参数指定名称。当方法有多个参数时,通过该注解明确SQL中引用的参数名,避免依赖参数顺序@Mapperpublic interface BlogMapper {    //@Param    @Update("update blog set name = #{name},age = #{age} where id = #{id}")    Integer updatePersonInfo(@Param("id") Integer userId,@Param("name") String userName,@Param("age") Integer userAge);}一键获取完整项目代码java@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }    @Test    void updatePersonInfo() {        Integer ret = blogMapper.updatePersonInfo(1, "刘玄德", 30);        log.info("更新成功,影响行数:{}",ret.toString());//更新成功,影响行数:1    }}一键获取完整项目代码java2.4 xml2.4.1 配置1.创建一个接口,并使用 @Mapper注解 修饰import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface BlogXMLMapper {    //其他代码}一键获取完整项目代码java2.配置mybatis的xml文件路径mybatis:  mapper-locations: classpath:mybatis/**Mapper.xml #配置mybatis的xml文件路径  #标识位于resources/mybatis路径下任何以Mapper结尾的xml文件为mybatis的配置文件一键获取完整项目代码xml3.在resources/mybatis路径下创建以Mapper结尾的xml文件,并添加如下代码<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!--namespace:用于指定该XML文件对应的Java接口或类的全限定名--><mapper namespace="com.example.spring_mybatis.mapper_blog.BlogXMLMapper"></mapper>一键获取完整项目代码xml2.4.2 示例在接口中声明方法import com.example.spring_mybatis.model_blog.PersonInfo;import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapperpublic interface BlogXMLMapper {    List<PersonInfo> getPersonInfoAll();}一键获取完整项目代码java在对应xml文件中实现接口方法id:是MyBatis映射文件中SQL语句的唯一标识符。需与Mapper接口中的方法名一致,保证映射正确resultType:指定SQL查询结果映射的Java对象类型,需为全限定类名<mapper namespace="com.example.spring_mybatis.mapper_blog.BlogXMLMapper">    <select id="getPersonInfoAll" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select * from blog    </select></mapper>一键获取完整项目代码xml按住alt+insert,可在test目录下生成以上方法的测试方法import com.example.spring_mybatis.model_blog.PersonInfo;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest@Slf4jclass BlogXMLMapperTest {    private final BlogXMLMapper blogXMLMapper;    @Autowired    public BlogXMLMapperTest(BlogXMLMapper blogXMLMapper) {        this.blogXMLMapper = blogXMLMapper;    }    @Test    void getPersonInfoAll() {        List<PersonInfo> personInfoAll = blogXMLMapper.getPersonInfoAll();        log.info("查询成功,personInfoAll:{}",personInfoAll.toString());    }}一键获取完整项目代码java运行结果:2.5 动态SQL动态SQL:指在程序运行时根据条件或参数动态生成的SQL语句。与静态SQL相比,动态SQL更具灵活性,适用于需要根据不同条件构建查询的场景。例如,在某些web/app进行账号注册时会出现非必填选项mybatis的注解和xml两种方式都能实现动态SQL,但xml较为方便,所以下文使用xml来实现动态SQL2.5.1 trim标签作用:用于自定义字符串截取规则。包含四个属性:prefix:最终结果添加前缀suffix:最终结果添加后缀prefixOverrides:去除首部指定内容suffixOverrides:去除尾部指定内容2.5.2 if标签作用:用于条件判断,通常在where或set语句中使用。当test表达式的值为true时,包含标签内的SQL片段    <insert id="addPersonInfo">        insert into blog        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                id,            </if>            <if test="name != null">                name,            </if>            <if test="age != null">                age,            </if>        </trim>        values        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                #{id},            </if>            <if test="name != null">                #{name},            </if>            <if test="age != null">                #{age},            </if>        </trim>    </insert>一键获取完整项目代码xml2.5.3 where标签作用:替代SQL中的where关键字。当if条件成立时才会加入SQL片段,并自动去除第一个子句的and/or    <select id="getPersonInfoByNameAndAge">        select * from blog        <where>            <if test="name != null">                and name = #{name}            </if>            <if test="age != null">                and age = #{age}            </if>        </where>    </select>一键获取完整项目代码xml2.5.4 set标签作用:用于update语句。当if条件成立时才会加入SQL片段,并自动去除最后一个子句的逗号、    <update id="updatePersonInfo">        update blog        <set>            <if test="name != null">                name = #{name},            </if>            <if test="age != null">                age = #{age},            </if>        </set>        <where>            and id = #{id}        </where>    </update>一键获取完整项目代码xml2.5.5 foreach标签作用:用于集合遍历。主要属性:collection:集合参数名item:当前元素变量名open/close:包围符号separator:分隔符    @Test    void getPersonInfoById() {        ArrayList<Integer> ids = new ArrayList<>();        ids.add(1);        ids.add(2);        ids.add(3);        List<PersonInfo> personInfoById = blogXMLMapper.getPersonInfoById(ids);        System.out.println(personInfoById);    }一键获取完整项目代码java    <select id="getPersonInfoById" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select * from blog        where id in        <foreach collection="ids" item="id" open="(" close=")" separator=",">            #{id}        </foreach>    </select>一键获取完整项目代码xml2.5.6 include标签作用:用于引用SQL片段,通过refid指定要引用的片段id。需配合sql标签使用,实现代码复用    <sql id="collection">        id,name,age    </sql>    <select id="getPersonInfoAll" resultType="com.example.spring_mybatis.model_blog.PersonInfo">        select        <include refid="collection">        </include>        from blog    </select>一键获取完整项目代码xml2.6 主键返回主键返回:指在数据库插入操作后,自动获取刚插入记录的主键值。在mybatis中使用注解和xml都能获取到返回的主键1.注解实现@Mapperpublic interface BlogMapper {    @Options(useGeneratedKeys = true,keyProperty = "id")    @Insert("insert into blog values (#{id},#{name},#{age})")    Integer addPerson(PersonInfo person);}@SpringBootTest@Slf4jclass BlogMapperTest {    private final BlogMapper blogMapper;    @Autowired    public BlogMapperTest(BlogMapper blogMapper) {        this.blogMapper = blogMapper;    }        @Test    void addPerson() {        PersonInfo personInfo = new PersonInfo(null, "黄忠", 60);        Integer ret = blogMapper.addPerson(personInfo);        log.info("添加成功,影响行数:{},返回的主键值:{}",ret.toString(),personInfo.getId());//添加成功,影响行数:1,返回的主键值:6    }}一键获取完整项目代码java 2.通过xml实现@SpringBootTest@Slf4jclass BlogXMLMapperTest {    private final BlogXMLMapper blogXMLMapper;    @Autowired    public BlogXMLMapperTest(BlogXMLMapper blogXMLMapper) {        this.blogXMLMapper = blogXMLMapper;    }        @Test    void addPersonInfo() {        PersonInfo personInfo = new PersonInfo();        personInfo.setAge(40);        personInfo.setName("曹操");        Integer ret = blogXMLMapper.addPersonInfo(personInfo);        log.info("添加成功,影响行数:{},返回的主键值:{}",ret.toString(),personInfo.getId());//添加成功,影响行数:1,返回的主键值:7    }}一键获取完整项目代码java    <insert id="addPersonInfo" useGeneratedKeys="true" keyProperty="id">        insert into blog        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                id,            </if>            <if test="name != null">                name,            </if>            <if test="age != null">                age,            </if>        </trim>        values        <trim prefix="(" suffix=")" suffixOverrides=",">            <if test="id != null">                #{id},            </if>            <if test="name != null">                #{name},            </if>            <if test="age != null">                #{age},            </if>        </trim>    </insert>一键获取完整项目代码xml2.7 预编译/即时SQL预编译SQL(Prepared Statements):SQL语句在程序运行前被预先编译并存储在数据库中。执行时只需传递参数,无需重新编译SQL语句安全性高:通过参数化查询避免SQL注入攻击。参数化查询是一种将SQL语句与用户输入数据分离的数据库操作方式,查询语句中使用占位符(如?、@param等)代替直接拼接用户输入,执行时通过预编译机制将参数动态绑定到占位符位置性能优化:编译一次,多次执行,减少数据库开销即时SQL(Dynamic SQL):在程序运行时动态生成并立即编译执行,每次执行都可能涉及完整的SQL解析和编译过程灵活性高:可根据运行时条件动态拼接SQL语句潜在风险:直接拼接用户输入可能导致SQL注入性能开销:每次执行需重新编译#占位符会使用预编译机制,将参数值安全地绑定到SQL语句中,防止SQL注入攻击。MyBatis会将#替换为?,然后通过JDBC的预编译功能设置参数值$占位符直接进行字符串替换,将参数值拼接到SQL语句中,不会进行预编译或转义处理SQL注入攻击:当恶意输入" 'or 1 = '1 "时1.预编译SQL@Select("select * from blog where name= #{name}")List<UserInfo> queryByName(String name)一键获取完整项目代码java预编译SQL会根据参数的类型判断是否需要加引号,上述name参数是String类型,需要加引号,这就是参数化查询的作用。最终SQL:select * from blog where name = " 'or 1='1 "; # 整个 'or 1='1 会作为name的值一键获取完整项目代码sql2.即时SQL@Select("select * from blog where name= '${name}'")List<UserInfo> queryByName(String name)一键获取完整项目代码java即时SQL不会判断参数类型从而是否添加引号,所以需要手动加上单引号。最终SQL:select * from blog where name = ''or 1 = '1'; # 因为1=1是恒等式,所以该表的数据会被全部查询出来,这就是SQL注入————————————————原文链接:https://blog.csdn.net/2401_89167985/article/details/152804543
  • [技术干货] 用 Notion + 自动化打造你的个人知识管理系统
    你是不是也这样:看到好文章先收藏,结果再也没打开过;笔记散落在微信、网页、本地文档里,想找时怎么也找不到;学了一堆东西,却没法串联起来形成自己的知识体系?其实,问题不在你不够努力,而在缺乏一个趁手的系统。一、Notion的优点Notion 不是唯一选择,但它有几个独特优势:自由度高:数据库、看板、日历、列表随意组合双向链接:轻松建立知识点之间的关联(类似 Roam Research)全平台同步:手机、电脑、网页端实时更新免费够用:个人使用基本功能完全免费API 开放:可接入自动化工具(如 Zapier、Make)更重要的是——它足够简单,能让你坚持用下去。二、三库一体一个有效的 PKM 系统不需要复杂,关键在于输入 → 整理 → 输出的闭环。我们用三个核心数据库实现:1. 收件箱(Inbox)—— 临时存放所有碎片信息用途:快速保存灵感、链接、摘录,不求完美,只求不丢字段建议:内容(文本/URL)来源(如“知乎”、“播客”、“会议记录”)状态(待处理 / 已归档)创建时间 技巧:在手机桌面放 Notion 快捷方式,看到好内容 5 秒内存入收件箱。2. 知识库(Knowledge Base)—— 结构化沉淀核心内容用途:经过整理的、可长期复用的知识单元(类似维基)字段建议:标题标签(多选:技术/心理学/产品…)相关主题(关联到其他知识条目)原始来源(链接回 Inbox 条目)最后复习时间 关键:每条知识尽量独立、原子化。例如不要写“机器学习笔记”,而是拆成“梯度下降原理”、“过拟合解决方法”等小条目。3. 项目/行动库(Projects & Actions)—— 驱动实践与输出用途:把知识转化为具体行动(写作、做项目、分享)字段建议:任务名称关联知识(多对多链接到 Knowledge Base)截止日期状态(规划中 / 进行中 / 已完成)三、从一条微博到知识卡片假设你在微博看到一段关于“费曼学习法”的精彩解释:步骤 1:快速存入收件箱打开 Notion 手机 App点击“+”新建页面,粘贴原文 + 微博链接标记来源为“微博”,状态为“待处理”步骤 2:每日整理(10 分钟即可)打开收件箱,筛选“待处理”阅读内容,用自己的话重写核心观点(避免直接收藏)在知识库中新建页面《费曼学习法》,填写:核心步骤:1. 选择概念 2. 教给别人 3. 发现漏洞 4. 简化类比关联已有知识:《认知负荷理论》《主动回忆》添加标签:#学习方法 #教育步骤 3:驱动输出在项目库中创建任务:“写一篇费曼学习法实践心得”关联《费曼学习法》知识条目设置一周后提醒这样,一条碎片信息就完成了从“看到”到“内化”再到“输出”的完整旅程。四、减少手动操作1. 用浏览器插件一键保存网页安装Notion Web Clipper ,点击即可将当前网页保存到指定数据库(如收件箱),自动带标题、URL、截图。2. 邮件自动转 Notion(适合订阅内容)通过 Notion API + Zapier:规则:收到发件人邮件动作:自动创建 Inbox 条目,内容为邮件正文3. 微信内容自动同步(进阶)使用工具如 WeChat Notion Sync(开源项目),将微信“文件传输助手”或特定群聊消息自动转发到 Notion(需自建服务)。 知识管理不是为了囤积信息,而是构建一个能自我生长的认知外脑。Notion 只是工具,真正的价值在于你如何用它建立输入、思考、输出的正向循环。 
  • [技术干货] 如何选择日志和监控系统的集成方案?
    选择日志与监控系统的集成方案,是构建高效可观测性体系的关键一步。一个良好的集成不仅能提升故障排查效率,还能降低运维成本、避免数据孤岛。以下是系统化的选型思路和实践建议,适用于从初创团队到大型企业的不同场景。一、明确核心需求在选型前,先回答以下问题:问题决策影响业务规模:日均日志量多少?服务数量?决定是否需要分布式架构、冷热分层SLA 要求:能否容忍分钟级延迟?是否需实时告警?影响采集与查询引擎选型团队技能:是否有专职 SRE?熟悉哪些技术栈?决定自建 or 托管合规要求:数据是否必须私有化部署?排除公有云托管方案预算限制:愿意为可观测性投入多少成本?开源 vs 商业产品的权衡关键原则:不要为“未来可能的需求”过度设计,优先解决当前最痛的点(如“无法快速定位线上错误”)。二、集成架构的三种主流模式1. 统一平台模式(All-in-One)代表方案:Datadog、New Relic、阿里云 ARMS、腾讯云可观测平台特点:日志、指标、链路追踪一体化采集与展示自动关联 trace_id、service 等上下文内置智能告警、仪表盘、SLO 管理适用场景:团队规模小,希望快速上线愿意接受按量付费(通常按主机数或数据量计费)不想维护底层基础设施 优势:开箱即用,体验流畅 劣势:长期成本高,数据迁移困难2. 开源组合模式(Best-of-Breed)典型栈:采集:OpenTelemetry SDK + Fluent Bit / Vector传输:OpenTelemetry Collector存储与查询:Logs → Grafana Loki / ElasticsearchMetrics → Prometheus + Thanos / VictoriaMetricsTraces → Jaeger / Grafana Tempo可视化:Grafana(统一面板)适用场景:需要数据自主可控有运维能力,追求成本优化希望避免厂商锁定 优势:灵活、可扩展、长期成本低 劣势:需自行保障高可用、扩缩容、备份3. 混合模式(Hybrid)策略:核心系统自建(保障安全+成本),边缘或临时项目使用托管服务示例:生产环境:Loki + Prometheus + Tempo(私有部署)测试环境 / 新业务:Datadog(快速验证)优势:兼顾控制力与敏捷性三、关键技术考量点1. 上下文关联能力确保日志、指标、链路能通过共同字段(如 trace_id, service.name, pod)联动查询。推荐做法:应用使用 OpenTelemetry SDK,自动注入 trace_id 到日志日志格式为 JSON,包含标准字段(遵循 OTel Semantic Conventions)2. 采集性能与资源开销避免在应用进程内做 heavy parsing(如正则提取)使用轻量级 agent(如 Fluent Bit < Fluentd,Vector 性能更优)启用批处理和压缩(gzip)减少网络流量3. 查询语言与体验Prometheus 的 PromQL 适合指标分析Loki 的 LogQL 支持标签过滤 + 日志内容搜索Elasticsearch 的 Lucene 语法强大但学习成本高 尽量统一查询入口(如 Grafana),降低用户认知负担4. 告警闭环告警应基于指标(如错误率 > 1%),而非原始日志告警信息需包含上下文链接(如“点击查看相关 Trace”)支持自动静默、升级、通知渠道(企业微信/钉钉/Slack) 实施路线图Phase 1:基础覆盖应用接入 OTel SDK部署统一日志采集(Fluent Bit → Loki)配置核心服务指标监控(Prometheus)Phase 2:关联与告警实现日志 ↔ Trace 联动设置关键业务告警(如 API 错误率、延迟 P99)Phase 3:优化与治理引入采样、冷热分层建立日志规范(禁止随意打 INFO)接入 SLO 和用户体验监控(RUM) 日志与监控的集成,不是“选一个工具”,而是设计一套可持续演进的数据流水线。无论选择开源还是商业方案,核心目标始终是:让工程师在最短时间内,获得最多有效信息。建议从一个小而完整的闭环开始(如“一个服务的日志+指标+告警”),验证流程后再横向扩展。记住:最好的可观测性系统,是那个被团队真正用起来的系统,而不是功能最全的那个。
  • [行业前沿] 可观测性系统的设计与实践
     在复杂的分布式系统中,传统的监控手段——如简单的指标采集和日志查看——已难以满足快速定位问题、理解系统行为和保障用户体验的需求。可观测性(Observability)作为新一代系统洞察方法论,正逐步取代传统监控,成为云原生时代运维与开发的核心能力。本文将深入探讨可观测性的三大支柱(日志、指标、链路追踪)如何协同工作,分析主流技术栈的选型逻辑,并分享构建高效、低成本、可扩展可观测性系统的工程实践。什么是可观测性?可观测性源于控制理论,指通过系统外部输出(如日志、指标)推断其内部状态的能力。在软件工程中,它强调无需修改代码即可理解系统运行时行为,尤其适用于黑盒或灰盒系统。与传统监控的区别在于:监控:基于已知问题设置告警(“已知的未知”)可观测性:支持探索未知问题(“未知的未知”)例如,当用户投诉“页面加载慢”,可观测性系统应能帮助工程师快速下钻:是某个数据库查询变慢?CDN 回源异常?还是第三方 API 延迟升高?可观测性的三大支柱1. 日志(Logs):记录离散事件日志是最原始的系统输出,通常为带时间戳的文本或结构化 JSON。用途:调试、审计、错误追踪挑战:数据量大、非结构化、检索成本高2. 指标(Metrics):聚合的数值序列指标是对系统状态的量化描述,如 CPU 使用率、HTTP 请求数、错误率。特点:高压缩比、低存储成本、适合告警局限:丢失上下文,无法还原具体请求示例:http_requests_total{method="POST", status="500", path="/api/order"}3. 链路追踪(Traces):请求的全链路视图追踪记录单个请求在多个服务间的流转路径,由一系列 Span 组成。核心价值:可视化调用拓扑、定位性能瓶颈、识别异常服务标准协议:OpenTelemetry(OTLP)已成为事实标准,取代早期的 Zipkin、Jaeger 协议。技术栈选型:开源 vs 托管开源自建方案日志:Fluent Bit → Kafka → Loki / Elasticsearch指标:Prometheus + Thanos(长期存储)追踪:OpenTelemetry Collector → Jaeger / Tempo可视化:Grafana(统一面板)优势:成本可控、数据自主、灵活定制代价:运维复杂、需处理扩缩容、高可用保障云托管服务AWS:CloudWatch Logs + X-Ray + Managed PrometheusGCP:Cloud Logging + Cloud Trace + Cloud Monitoring第三方:Datadog、New Relic、Splunk Observability优势:开箱即用、自动扩缩、集成 AI 异常检测代价:按量计费成本高,存在厂商锁定风险选型建议:初创团队/成本敏感 → 开源组合(Loki + Prometheus + Tempo + Grafana)中大型企业/追求效率 → 托管服务 + OpenTelemetry 标准接入统一数据采集:OpenTelemetry 的核心作用OpenTelemetry(OTel)是 CNCF 主导的可观测性标准,提供:语言 SDK:自动注入 trace_id、采集指标(支持 Java、Go、Python 等)Collector:接收、处理、转发 Logs/Metrics/Traces语义约定:统一字段命名(如 http.method, db.statement)典型架构:App (OTel SDK) → OTel Collector → Backend (Loki/Prometheus/Jaeger)通过 OTel,应用只需一次埋点,即可将数据分发至多个后端,避免 vendor lock-in。成本优化策略可观测性数据增长迅猛,需主动控制成本:采样(Sampling)对高频 Trace 按比例采样(如 10%)错误请求 100% 采集,确保关键路径不丢失日志分级生产环境关闭 DEBUG 日志WARN/ERROR 日志强制保留冷热分层存储热数据(7天):高性能存储(Elasticsearch)冷数据(30天+):对象存储(S3)+ 查询引擎(Athena)指标预聚合在 Collector 层对高基数指标进行降维(如合并相似 label)构建一个轻量级可观测性平台假设你正在搭建一个微服务系统,预算有限但需基本可观测能力:采集层应用集成 OTel SDK,自动注入 trace_id 到日志部署 Fluent Bit DaemonSet 采集容器日志传输层使用 OTel Collector 接收数据,做简单过滤和批处理存储与查询Logs → Grafana Loki(低成本,标签索引)Metrics → Prometheus + VictoriaMetrics(高压缩比)Traces → Grafana Tempo(与 Loki 共享对象存储)可视化Grafana 统一面板:日志搜索、指标图表、Trace 下钻联动该方案总资源占用可控制在 2–4 核 CPU + 8GB 内存,适合中小规模集群。常见误区与避坑指南误区1:“日志越多越好”→ 后果:存储爆炸、检索变慢。应聚焦关键路径日志。误区2:“有了 Prometheus 就够了”→ 后果:无法定位具体错误请求。需结合 Trace 和 Logs。误区3:“可观测性只是运维的事”→ 正确做法:开发需在代码中合理埋点(如记录业务关键事件)。误区4:忽略上下文关联→ 必须确保 Logs、Metrics、Traces 共享相同标识(如 trace_id、service.name)。 可观测性不是一堆工具的堆砌,而是一种以问题为导向的系统思维。优秀的可观测性系统,能让工程师在 5 分钟内回答:“用户为什么失败?”、“系统哪里变慢了?”、“这次发布是否引入了风险?”。随着 OpenTelemetry 的普及和云原生生态的成熟,构建高效、统一、低成本的可观测性平台已不再是巨头专属。无论团队规模大小,只要坚持结构化数据、统一上下文、分层存储和成本意识,就能让系统真正“可见、可查、可信赖”。毕竟,在复杂的世界里,看得见,才安心。
  • [行业前沿] 倒排索引在AI场景中的应用有哪些?
     1. 检索增强生成(RAG, Retrieval-Augmented Generation)RAG 是当前大语言模型(LLM)落地的关键架构之一,其核心思想是:在生成答案前,先从外部知识库中检索相关上下文,再将上下文与用户问题一同输入 LLM,从而提升回答的准确性、时效性和可解释性。倒排索引的作用:对知识库文档(如 FAQ、产品手册、维基页面)进行分词并构建倒排索引用户提问时,提取关键词(或经 query expansion 后),通过倒排索引快速召回包含这些关键词的候选文档作为第一阶段粗排(Candidate Generation),高效过滤出 Top-K 相关文档(如 100 篇),供后续精排或向量重排使用优势:检索速度快,适合处理海量文本可解释性强(能明确看到命中了哪些关键词)对拼写错误、同义词可通过 Analyzer(如 IK 分词器 + 同义词库)优化2. 混合检索(Hybrid Search):关键词 + 语义的双重保障纯向量检索虽能理解语义相似性,但对精确术语、实体名、代码片段、ID 等字面匹配需求表现不佳。而倒排索引恰好擅长此类任务。混合策略:两路召回融合:一路用倒排索引做关键词匹配,一路用向量索引做语义匹配,再加权合并结果(如 Reciprocal Rank Fusion)级联过滤:先用倒排索引召回包含关键实体(如“iPhone 15”、“北京”)的文档,再在子集中做向量精排元数据过滤:利用倒排索引对文档的标签、类别、时间等结构化字段进行高效过滤应用场景:电商搜索:“红色连衣裙 2024新款” → 倒排索引确保“红色”“连衣裙”“2024”被精确命中,向量模型理解“新款”语义技术文档搜索:“如何修复 Kubernetes Pod CrashLoopBackOff” → 倒排索引精准捕获错误码,向量模型理解“修复”意图3. 多模态检索中的元数据索引在图像、视频、音频等多模态 AI 系统中,原始内容被编码为向量,但其关联的文本元数据(如标题、标签、OCR 识别结果、ASR 转录文本)仍需高效检索。倒排索引的角色:对图像的 OCR 文本、视频字幕、音频转写内容构建倒排索引用户输入文本查询时,可同时触发:向量路径:将 query 编码为向量,搜索视觉/听觉特征向量文本路径:通过倒排索引搜索元数据中的关键词二者结果融合,提升召回率案例:视频平台搜索“包含‘特斯拉发布会’字幕的视频” → 倒排索引快速定位字幕含该短语的视频片段医疗影像系统:通过报告中的“肺结节”“CT”等关键词召回相关病例,再结合影像向量比对4. 负样本挖掘与训练数据构建在训练对比学习(Contrastive Learning)或双塔模型时,需要大量难负样本(Hard Negative) 来提升模型判别能力。倒排索引辅助负采样:给定一个正样本 query,用倒排索引召回一批“看似相关但实际无关”的文档(如包含部分关键词但主题偏离)这些文档作为高质量负样本,比随机负样本更能提升模型性能5. 实时更新与高吞吐写入场景相比向量索引(如 FAISS、HNSW)通常需要批量重建或复杂增量更新,倒排索引天然支持高频写入和实时可见(如 Elasticsearch 的近实时 refresh)。AI 应用价值:用户行为日志、实时新闻、社交媒体内容等动态数据,可立即被倒排索引收录并参与检索在 RAG 系统中,新上传的文档几秒内即可被查询到,满足企业对“知识即时生效”的需求6. 可解释性与调试支持AI 系统常被视为“黑盒”,而倒排索引提供了透明的检索依据:可展示“因命中关键词‘退款政策’而推荐该文档”便于人工审核、规则干预(如屏蔽某些关键词结果)在合规场景(如金融、医疗)中满足审计要求 
  • [技术干货] 深入剖析现代数据库索引结构:B+树、LSM 树与倒排索引
     在数据驱动的时代,数据库作为信息系统的核心组件,其性能直接决定了应用的响应速度与用户体验。而索引(Index),作为加速数据检索的关键技术,堪称数据库的“高速公路”。不同的数据访问模式催生了多样化的索引结构——从关系型数据库广泛采用的 B+ 树,到 NoSQL 系统偏爱的 LSM 树(Log-Structured Merge-Tree),再到搜索引擎依赖的 倒排索引(Inverted Index)。理解这些索引的设计哲学、适用场景与实现细节,是构建高性能数据系统的基础。一、B+ 树:OLTP 场景的黄金标准1. 结构特点B+ 树是一种多路平衡查找树,专为磁盘 I/O 优化设计:所有数据存储在叶子节点,非叶子节点仅保存索引键和指针叶子节点通过链表串联,支持高效的范围查询节点大小通常设为一页(如 4KB 或 16KB),减少磁盘随机读取次数例如,在 MySQL InnoDB 中,主键索引即为聚簇 B+ 树,行数据直接存储在叶子节点;二级索引则为非聚簇 B+ 树,叶子节点存储主键值。2. 优势与适用场景点查高效:时间复杂度 O(logₙN),n 为分支因子(通常数百)范围扫描快:叶子节点链表顺序遍历,避免回溯写入稳定:每次更新仅修改少量页面,支持 WAL(Write-Ahead Logging)保证事务原子性3. 写放大与碎片问题频繁的随机插入可能导致页分裂,产生空间碎片;删除操作则留下“空洞”,需定期 OPTIMIZE TABLE 回收空间。二、LSM 树:为写密集型负载而生当写入吞吐成为瓶颈(如日志、时序数据、消息队列),B+ 树的随机写开销显得力不从心。LSM 树通过将随机写转化为顺序写,极大提升写入性能。1. 核心思想:分层合并LSM 树将数据分为多层(Level),典型结构如下:MemTable:内存中的有序结构(如 SkipList),接收新写入Immutable MemTable:写满后冻结,异步刷入磁盘SSTable(Sorted String Table):磁盘上的只读有序文件Compaction:后台合并小 SSTable 为大文件,清除过期/删除数据写入流程:数据先写入 WAL(保证持久性)插入 MemTable(内存操作,极快)MemTable 满后转为 Immutable,刷盘生成 Level 0 SSTable后台线程定期执行 Compaction,合并多层 SSTable2. 优势与代价写入吞吐极高:顺序写磁盘,接近硬件极限空间放大:Compaction 过程中临时占用额外空间读放大:需查询多层 SSTable 和 MemTable,可能触发多次 I/O写放大:同一数据在 Compaction 中被反复重写3. 优化策略布隆过滤器(Bloom Filter):快速判断 key 是否存在于某 SSTable,减少无效 I/O分层 Compaction(Leveled) vs 大小分级(Size-Tiered):权衡读写放大与空间效率三、倒排索引:全文搜索的基石在搜索引擎或需要文本匹配的场景中,传统索引无法高效支持“关键词 → 文档”的反向查找。倒排索引应运而生。1. 结构组成倒排索引由两部分构成:词典(Dictionary):所有唯一词条的有序列表,通常用 B+ 树或 FST(Finite State Transducer)存储倒排列表(Posting List):每个词条对应的文档 ID 列表,常附带位置、频率等元数据例如,对两篇文档建立索引:Doc1: "The quick brown fox"Doc2: "Jumped over the lazy dog"倒排列表:"the" → [Doc1(pos=0), Doc2(pos=2)]"fox" → [Doc1(pos=3)]...2. 高效压缩与跳表优化Delta 编码:文档 ID 通常递增,存储相邻差值(如 [100, 105, 110] → [100, 5, 5])跳表(Skip List):在长 Posting List 中设置跳跃指针,加速“AND/OR”查询的交并集计算3. 实时更新挑战传统倒排索引为静态构建。为支持实时写入,现代引擎(如 Elasticsearch)采用:分段(Segment)机制:新数据写入新 Segment,后台合并近实时(NRT)搜索:通过 refresh 操作使新数据可查(默认 1 秒)四、三大索引对比与选型指南特性B+ 树LSM 树倒排索引核心优势点查 & 范围查询快写入吞吐高文本关键词检索高效写入性能中等(随机写)极高(顺序写)中等(需分段合并)读取性能稳定低延迟可能较高(多层查找)依赖压缩与缓存事务支持强(MVCC + WAL)弱(通常最终一致)无典型系统MySQL, PostgreSQLCassandra, RocksDBElasticsearch, Lucene适用场景OLTP、高一致性业务日志、时序、IoT搜索、文本分析五、混合索引:现代数据库的融合趋势单一索引难以满足复杂需求,新一代数据库开始融合多种结构:TiDB:底层使用 LSM 树(RocksDB),上层构建分布式 B+ 树索引MongoDB:默认 B 树索引,同时支持全文检索(倒排)和地理空间索引ClickHouse:主键使用稀疏索引(类似 B+ 树),配合 MergeTree 引擎优化分析查询此外,向量索引(如 HNSW、IVF)正随着 AI 应用兴起,用于相似性搜索,进一步拓展索引的边界。六、结语索引是数据库性能的“命门”,其选择绝非技术炫技,而是对业务访问模式的深刻理解。B+ 树在事务型系统中屹立不倒,LSM 树在写密集场景大放异彩,倒排索引则撑起了整个搜索生态。作为工程师,我们不仅要知其然,更要知其所以然——理解每种索引背后的权衡(Trade-off):读写放大、空间效率、一致性模型。唯有如此,才能在面对海量数据、高并发、低延迟的挑战时,做出最合理的架构决策。未来,随着硬件演进(如持久内存、NVMe)与 AI 负载增长,索引技术将继续演化,但其核心使命始终不变:让数据触手可及。
  • [交流吐槽] FileReader/Writer 修改后缀名的避坑指南
    全网同名「悬着的心终于死了」,全网同名认证~  题主要批量修改网上下来的一些图片的后缀名,因为之前学艺不精,搞出来很多问题,这里记录一下(>_<)。之前学习操作文件的时候很草率,总结了一套文件基本操作流程:现在回来才发现这个套路并不适合所有的文件类型先上错误代码:import java.io.*;public class Main { public static void main(String[] args) throws Exception { String imgPath="D:\\文件夹/00015.webp"; File file=new File(imgPath); FileReader fileReader=new FileReader(file); FileWriter fileWriter=new FileWriter(file); String fatherPath=file.getParent(); String name=file.getName(); System.out.println(file.renameTo(new File(fatherPath,toJpg(name)))); try { fileReader.close(); fileWriter.close(); }catch (Exception e){ e.printStackTrace(); } } public static String toJpg(String oName){ int path=oName.lastIndexOf("."); return oName.substring(0,path)+".jpg"; }}操作的时候,各种错误:这里我总结为2个问题:1.FileWriter错误使用就像前面所说,我是直接套“模板”写,FileReader和FileWriter虽然没用到,但没报错就没删(这次长记性了>m<)经过排查,发现因为我多写这一句FileWriter才导致图片损坏这里是搜集到的结果如果 WebP 文件已经损坏,可以考虑使用专门的工具进行修复。例如,FabConvert 提供了一个免费的 WEBP 修复工具,可以在任何具有现代网络浏览器的系统上运行,并且没有使用限制未正确关闭流:在使用 FileWriter 写入数据后,如果没有正确关闭流,可能会导致数据未完全写入,从而损坏文件 。异常处理不当:如果在写入过程中发生异常,而异常没有被正确捕获和处理,可能会导致文件处于不一致的状态 。写入中途断电或系统崩溃:在写入过程中,如果遇到断电或系统崩溃等意外情况,可能会导致文件写入未完成,从而损坏文件。文件系统限制:某些文件系统可能有写入限制,例如最大文件大小或特定格式要求,不遵守这些限制可能会导致文件损坏。缓存问题:有时候浏览器可能会缓存旧版本的图片文件,导致新的 WebP 图片无法加载,这可能是文件损坏的一个表现 。文件损坏:检查 WebP 图片文件是否损坏或完整。有时候图片文件可能会在上传或保存过程中出现问题 。为了避免文件损坏,应该采取以下措施:确保在使用 FileWriter 后正确关闭它。使用异常处理来捕获并处理写入过程中可能发生的错误。避免在没有适当同步机制的情况下进行并发写入。使用事务或日志记录来确保写入操作的原子性和一致性。检查磁盘空间,并确保应用程序有足够的权限来写入文件。这里我并没有用FileWriter写入数据,所以应该是文件系统限制的原因,希望有懂的大佬可以在评论区解答一下(ˊ˘ˋ*)♡。2.FileReader错误使用这里通过反复测试发现,在没有FileReader这一句是file.renameTo()是可以执行的,原因是在使用FileReader时会资源锁定。以下为总结:在许多编程语言中,当你使用 FileReader 或类似的文件读取类与一个 File 对象关联后,你通常不能再直接操作这个 File 对象来读取文件。这主要是因为以下几个原因:资源锁定:一旦 FileReader 打开了一个文件,操作系统可能会锁定这个文件,防止其他进程或线程同时读取或写入,以避免数据损坏或冲突。状态管理:FileReader 可能内部维护了文件的状态信息,如当前读取位置。如果尝试用同一个 File 对象再次创建 FileReader,可能会遇到状态不一致的问题。设计模式:编程语言的设计可能鼓励使用流式操作,即一次只通过一个流(如 FileReader)来处理文件,而不是同时打开多个流。资源释放:如果 FileReader 没有被正确关闭,它可能会持续占用文件资源,导致其他操作无法进行。API限制:某些编程语言或库可能在API设计上限制了对同一个文件对象的多次使用,以简化资源管理和错误处理。如果需要在同一个程序中多次读取同一个文件,通常的做法是:在每次读取操作之后,确保关闭 FileReader 对象。如果需要再次读取,可以重新打开文件,创建一个新的 FileReader 对象。例如,在Java中,你可以这样做:File file = new File("path/to/your/file.txt");// 第一次读取FileReader reader1 = new FileReader(file);// ... 执行读取操作 ...reader1.close();// 第二次读取FileReader reader2 = new FileReader(file);// ... 执行读取操作 ...reader2.close();综上,修改了这两个问题后,修改文件后缀名就成功了import java.io.*;public class Main { public static void main(String[] args) throws Exception { String imgPath="D:\\文件夹/00015.webp"; File file=new File(imgPath); //FileReader fileReader=new FileReader(file); //FileWriter fileWriter=new FileWriter(file); String fatherPath=file.getParent(); String name=file.getName(); System.out.println(file.renameTo(new File(fatherPath,toJpg(name)))); } public static String toJpg(String oName){ int path=oName.lastIndexOf("."); return oName.substring(0,path)+".jpg"; }最后叨叨一句,自己准备的模板一定要完全弄懂口牙_(:зゝ∠)_
  • [问题求助] 跑JAVA代码时候发现JFrame这个图形化的类没办法使用
    如图,跑JAVA代码时候发现JFrame这个图形化的类没办法使用
  • [行业前沿] 如何优化 JFR(Java Flight Recorder)分析的性能?
    JFR(Java Flight Recorder)是 JDK 内置的高性能诊断工具,以极低开销记录 JVM 和应用运行时的关键事件。然而,不当配置可能导致录制开销升高、文件过大或分析困难。尤其在高并发、长时间运行的生产环境(如鲲鹏 ARM64 服务器)中,合理优化 JFR 的使用策略至关重要。本文将从 录制配置、资源控制、事件筛选、分析效率 四个维度,系统讲解如何优化 JFR 的性能表现。一、核心原则:平衡“数据完整性”与“运行开销”JFR 的默认配置(profile.jfc)已针对通用场景做了权衡,但实际业务需根据目标调整:目标推荐策略长期监控(7×24)仅启用关键事件(GC、线程、CPU 采样),降低采样频率故障复现启用详细事件(方法、异常、I/O),短时间高保真录制性能压测对比使用统一配置,确保数据可比性二、优化录制阶段的性能1. 选择合适的预设模板JDK 提供两个内置模板:default.jfc:基础事件(低开销,适合长期运行)profile.jfc:包含方法采样、锁竞争等(中等开销,适合性能分析)建议:# 长期监控用 default-XX:StartFlightRecording=settings=default,duration=1h,filename=/data/jfr/low.jfr# 深度分析用 profile-XX:StartFlightRecording=settings=profile,duration=5m,filename=/data/jfr/high.jfr 毕昇 JDK 还提供 kunpeng-optimized.jfc(如有),可进一步适配 ARM64。2. 自定义 .jfc 配置文件(推荐)复制并修改模板,关闭非必要事件:<!-- custom-low-overhead.jfc --><configuration ...> <event name="jdk.MethodSampling"> <setting name="enabled">false</setting> <!-- 关闭方法采样 --> </event> <event name="jdk.JavaMonitorEnter"> <setting name="enabled">true</setting> <setting name="threshold">10ms</setting> <!-- 仅记录 >10ms 的锁等待 --> </event> <event name="jdk.GCPhasePause"> <setting name="enabled">true</setting> </event></configuration>启动时指定:-XX:StartFlightRecording=settings=/path/to/custom-low-overhead.jfc,...3. 控制录制时长与文件大小避免无限制录制导致磁盘爆满:# 方式1:固定时长duration=10m# 方式2:循环录制(保留最近数据)maxsize=500MB,maxage=1h# 方式3:条件触发(JDK 17+ 支持)-XX:FlightRecorderOptions:repository=/tmp/jfr-cache生产建议:单文件 ≤ 1GB总录制时长 ≤ 30 分钟(除非明确需要长期趋势)4. 调整采样频率(降低 CPU 开销)关键参数:jdk.ThreadCPULoad:线程 CPU 采样间隔(默认 1s)jdk.ExecutionSample:方法栈采样间隔(默认 10ms)在自定义 .jfc 中调整:<event name="jdk.ExecutionSample"> <setting name="period">100ms</setting> <!-- 从 10ms 放宽到 100ms --></event> 注意:采样间隔越长,热点方法识别精度越低。三、减少 I/O 与内存开销1. 使用高速存储路径将 .jfr 文件写入 SSD 或内存盘:filename=/dev/shm/app.jfr # 写入 tmpfs(内存文件系统) 优势:避免磁盘 I/O 成为瓶颈 风险:重启丢失,需及时备份2. 启用压缩(JDK 17+)-XX:FlightRecorderOptions=compress=true可减少 30%~50% 文件体积。3. 避免多进程同时写同一目录每个 Java 进程应使用独立子目录,防止文件锁竞争。四、优化分析阶段的效率1. 使用命令行快速筛查(避免 GUI 开销)# 查看 GC 暂停总时间jfr print --events GCPhasePause app.jfr | grep "duration"# 统计最耗时的 10 个方法jfr summary app.jfr --category "Code" | head -n 102. 在分析机而非生产机运行 JMC将 .jfr 文件拷贝至开发机或专用分析服务器;避免在生产环境启动图形界面工具。3. 使用脚本自动化分析结合 jfr 命令 + Shell/Python 脚本,实现:自动提取关键指标生成性能报告触发告警(如 GC 暂停 > 100ms)示例脚本片段:MAX_PAUSE=$(jfr print --events GCPhasePause app.jfr | awk '/duration/ {print $2}' | sort -nr | head -1)if [ "$MAX_PAUSE" -gt 100000000 ]; then # 100ms in nanoseconds echo "ALERT: Max GC pause exceeds 100ms!"fi五、鲲鹏 ARM64 环境下的特别建议优先使用毕昇 JDK其 JFR 实现针对鲲鹏处理器的缓存、NUMA 架构优化,事件采集效率更高。关注 ARM64 特有事件如:jdk.CPULoad:ARM64 大小核调度可能影响 CPU 利用率jdk.NativeLibrary:验证 native 库是否为 ARM64 编译对比 x86 基线在相同负载下,分别录制 x86 与鲲鹏的 JFR 数据,使用 JMC 的 Compare 功能 定位架构差异点。 
  • [技术干货] 鲲鹏服务器上的 Java 性能调优利器
    什么是 JFR?Java Flight Recorder(JFR)是 Oracle 自 JDK 7 引入、并在 JDK 11+ 开源的高性能事件记录框架。它以极低的性能开销(通常 <1%)持续收集 JVM 和应用层的关键事件,包括:GC 活动(暂停时间、堆变化)线程状态(阻塞、锁竞争)方法采样(CPU 热点)I/O 操作异常抛出类加载JIT 编译行为优势:内置于 JDK,无需额外依赖支持长时间录制(小时级)事件粒度细、上下文完整官方工具 JMC(Java Mission Control)提供可视化分析启用 JFR 并录制数据方法 1:启动时开启录制(推荐用于生产)java -XX:+FlightRecorder \ -XX:StartFlightRecording=duration=300s,filename=/tmp/app.jfr,name=MyAppProfile \ -jar your-app.jar参数说明:duration=300s:录制 5 分钟后自动停止filename:输出文件路径name:录制会话名称方法 2:运行时动态开启(需 JDK 14+ 或使用 jcmd)# 查看 Java 进程 PIDjps# 启动 60 秒录制jcmd <PID> JFR.start duration=60s filename=/tmp/runtime.jfr# 手动停止(可选)jcmd <PID> JFR.stop name=1注意:鲲鹏服务器上建议将 .jfr 文件保存至高速 SSD,避免 I/O 影响录制性能。 分析 JFR 数据工具 1:Java Mission Control(JMC)下载 JMC(支持 ARM64):华为毕昇 JDK 自带 JMC或从 Adoptium 下载 ARM64 版打开 .jfr 文件:jmc /tmp/app.jfr关键分析视图:Memory → Garbage Collections:GC 频率与暂停时间Threads → Thread Dump:线程阻塞与锁竞争Code → Hot Methods:CPU 消耗最高的方法I/O → File Read/Write:磁盘 I/O 瓶颈工具 2:命令行快速查看(无需 GUI)# 使用 jfr 工具(JDK 19+ 内置)jfr print --events GCPhasePause /tmp/app.jfr# 统计方法采样jfr summary /tmp/app.jfr 在鲲鹏服务器上,JFR 是 Java 应用性能调优不可或缺的工具。结合毕昇 JDK 的 ARM64 优化,可以:精准识别 GC、锁、I/O 等瓶颈;对比 x86 与 ARM64 的性能差异;验证 JVM 参数调优效果;构建性能基线,支撑容量规划。
  • [问题求助] 鸿蒙版CodeArts多久能支持svn和maven
    鸿蒙版CodeArts IDE,进行Java开发spring boot微服务,支持svn和maven配置吗?如果不支持,有没有其他的解决办法,如果有的话,有详细步骤吗?第一次用CodeArts IDE,还不知道怎么用呢
总条数:685 到第
上滑加载中