百木园-与人分享,
就是让自己快乐。

1.1 语法入门(更新到冒泡排序)

目录

  • 说在前面的话
    • 前言
      • 内容介绍
      • 更新内容
      • 其他说明
      • 本书由来
      • 帮助说明
      • 求职
      • 致谢与勘误
    • 自序
      • 第 3 版自序
      • 第 2 版自序
      • 第 1 版自序
      • 第 0 版自序
  • 第一章 JDK 下载及准备工作
    • 1.1 Java 介绍
      • 1.1.1 Java 简介
      • 1.1.2 Java 技术体系
      • 1.1.3 前后端的工作内容
      • 1.1.4 Java 语言特性
      • 1.1.5 Java 用途
    • 1.2 JDK 下载
      • 1.2.1 JDK 与 JRE 的区别
      • 1.2.2 JDK 下载与安装
      • 1.2.3 卸载
      • 1.2.4 禁止 JDK 检查更新
    • 1.3 编写代码前的准备工作
      • 1.3.1 为何要写笔记
      • 1.3.2 编写源代码使用的记事本
      • 1.3.3 练习双手打字
      • 1.3.4 Windows10 系统常用快捷键
      • 1.3.5 给初学者的一点建议
    • 1.4 Typora 软件使用
      • 1.4.1 下载与安装
      • 1.4.2 如何创建 Markdown 文件
      • 1.4.3 常用的 Markdown 语法
      • 1.4.4 常用的设置
      • 1.4.5 官网打不开的原因
      • 1.4.6 自动上传图片
    • 1.5 常用的 DOS 命令
      • 1.5.1 打开 DOS 窗口的几种方式
      • 1.5.2 进入目录内:使用绝对路径与相对路径演示
      • 1.5.3 常用的 DOS 命令
    • 1.6 Joplin 使用
      • 1.6.1 下载地址
      • 1.6.2 常用设置
    • 1.7 MarkText 使用
      • 1.7.1 编辑区域
      • 1.7.2 偏好设置
      • 1.7.3 图片上传
      • 1.7.4 File
      • 1.7.5 Edit
      • 1.7.6 Paragraph
      • 1.7.7 Format
      • 1.7.8 Window
      • 1.7.9 View
      • 1.7.a Help
    • 1.x 总结回顾
    • 1.y 脑海练习
  • 第二章 编写第一个程序
    • 2.1 编写源代码并运行
      • 2.1.1 创建 .java 文件编写源码
      • 2.1.2 配置环境变量
      • 2.1.3 查看命令使用帮助
      • 2.1.4 解释代码含义
      • 2.1.5 编译与运行
    • 2.2 代码书写规范
      • 2.2.1 代码书写细节
      • 2.2.2 代码规范
      • 2.2.3 标识符与关键字
    • 2.3 注释
      • 2.3.1 单行注释
      • 2.3.2 多行注释
      • 2.3.3 文档注释
      • 2.3.4 IDEA 工具自动快捷键添加普通注释
      • 2.3.5 使用 javac 命令编译时出现的\"错误: 编码 GBK 的不可映射字符\"
      • 2.3.6 查看与修改系统编码
      • 2.3.7 使用 API 文档
    • 2.x 总结回顾
    • 2.y 脑海练习
  • 第三章 变量
    • 3.1 变量
      • 3.1.1 字面量
      • 3.1.2 变量
    • 3.2 数据类型
      • 3.2.1 整数类型
      • 3.2.2 浮点类型
      • 3.2.3 字符类型
      • 3.2.4 布尔类型
    • 3.3 类型转换
      • 3.3.1 基本数据类型转换
      • 3.3.2 基本数据类型与 String 类型的转换
    • 3.x 总结回顾
    • 3.y 脑海练习
  • 第四章 运算符
    • 4.1 算术运算符
      • 4.1.1 四则运算:加减乘除
      • 4.1.2 求余数:%
      • 4.1.3 ++、--
    • 4.2 赋值运算符
      • 4.2.1 基本赋值运算符:=
      • 4.2.2 扩展赋值运算符:+=、-=、*=、/=、%=
    • 4.3 关系运算符
      • 4.3.1 >、<、<=、>=
      • 4.3.2 ==
      • 4.3.3 !=
    • 4.4 逻辑运算符
      • 4.4.1 &
      • 4.4.2 |
      • 4.4.3 !
      • 4.4.4 ^
    • 4.5 条件运算符
      • 4.5.1 三目运算符
    • 4.6 运算符优先级
    • 4.x 总结回顾
    • 4.y 实战演练
  • 第五章 流程控制语句
    • 5.1 输入与输出
      • 5.1.1 接收输入
      • 5.1.2 普通输出
      • 5.1.3 格式化输出
    • 5.2 分支控制语句
      • 5.2.1 if else
      • 5.2.2 switch case
    • 5.3 循环控制语句
      • 5.3.1 for
      • 5.3.2 while
    • 5.4 转向控制语句
      • 5.4.1 break
      • 5.4.2 continue
      • 5.4.3 return
    • 5.x 总结回顾
    • 5.y 掀起波澜
  • 第六章 数组
    • 6.1 一维数组
      • 6.1.1 数组介绍
      • 6.1.2 一维数组的声明
      • 6.1.3 一维数组的遍历
      • 6.1.4 数组元素默认值
      • 6.1.5 数组赋值机制
    • 6.2 多维数组
      • 6.2.1 二维数组声明
      • 6.2.2 遍历二维数组
      • 6.2.3 静态方法调用
    • 6.3 排序
      • 6.3.1 时间复杂度
      • 6.3.2 空间复杂度
      • 6.3.3 冒泡排序
      • 6.3.4 简单选择排序
  • 加载速度太慢,该分了

说在前面的话

前言

内容介绍

介绍 JavaSE 基础的基本语法知识,不会包含特别难以理解或更深层次的内容,通俗易懂。

前提

本书的所有例子使用 JDK8 在 Windows10 系统下完成,所以默认使用 exe;若你与我的不一致,快捷键、源码都有可能不同,但网上无偿资源十分的多,搜索了解不同之处即可。

受众人群

区别于教材,不会设问不答,而是十分详细,不会为读者留出思考和遐想的空间;目的只为节省时间,算是入门的自学读物吧。

主要让小白快速了解或回顾 JavaSE 的基础语法知识,随看即用,无阅读门槛。

如果要明明白白,至少要有初高中数学知识,否则遇到什么函数、多次项、对数、极限...有可能迷糊。

主要内容

本书前 k 章,涵盖了如 Java 基础语法、变量、运算符、流程控制语句、面向对象、常用类、集合、IO 等部分,基本囊括作为后端开发人员必学的知识点。

另外把那些不常用的知识自成一章,移入扩充内容

本书示范中,将可能使用到的软件、工具网址放入资源地址中。

章节编排

每章共有 3 级目录,取名尽量保持唯一性;第 2 级目录随后跟着内容导视,用来统领第 3 级目录的内容;若第 3 级目录中还有大量知识点,会提取到内容如下

对于讲源码的部分,先抛出结论,自成一节,在它的下一个目录,粘贴全部源码,逐行分析。

每章的结尾会插入总结回顾与习题;习题的平均难度分级:脑海练习、实战演练、掀起波澜、头脑风暴、大脑宕机、空空如也。

此外正文中可能会以课堂练习思考的形式发问。思考的答案紧跟题目后面,课堂练习答案在文章末尾,参考答案自成一章。

如何使用

因为人的大脑很特别,会自动屏蔽掉日常普通、枯燥乏味的事情,目的是保持内存充足。它只记住比较特别的,其它的都会慢慢遗忘。

什么是比较特别的?

  • 重复,一直重复;重复到大脑认为这不该忘记,有道言:熟能生巧、好记性不如烂笔头,还谈不上拼天赋包括努力。
  • 新奇的、有趣的、充满致命危险的、富有挑战性的、特殊的;与自己基因特性相关,偏向哪方面,大脑对哪感兴趣,不用你重复多次,自然会记住。

所以我加入了总结回顾,记录了这一章的重要内容,怕你看着看着就全忘了。放心,只有一点点,不会对你的大脑造成负担。看完后,记得休息一会。

更新内容

第 3 版的更新内容

新增 switch case 分支、补码、二进制转换、字符编码常识、值传递、String 字符串常量池和 intern 方法的解析、第三代日期类、UUID、正则表达式等一系列内容。

将双向链表移入 LinkedList 这节中。

取消了 b 标签的使用,因为无法在 CSDN 中无法做到与博客园一样的显示。

取消了 img 标签的使用,因为在 CSDN 中无法显示图片。

取消了 gif 动图的使用,因为打印 pdf 文件后就是静图;

取消了超链接使用,统一使用网址代替。

发现每章的内容过多,将每章继续拆分为几个小节。

移除了强类型语言、进制转换、字符编码、原码补码等内容。

第 2 版的更新内容

将所有目录重新整合,改名,保证标题的唯一性,否则博客园可能跳转错误。

将 字符编码 部分内容移到 常用类 一章中;将 Eclipse 移至扩充内容中;将下载与准备工作合二为一,修改 \\(\\xi\\) 符号的显示问题;用 ① 替换 1. 避免格式乱码。

由于章节过多,这篇只得拆分成 20 章,否则浏览器加载不过来。

其他说明

跳转问题

博客园有时会出毛病,在同一篇文章中,如果两个标题名相同,点击只会跳转到第一个同名标题;虽然已经开始在标题前面加 1.1.0 让标题名唯一,但是目前还未改完,请从点击如图展示的标题列表中跳转。(2022/03/11 18:00)

在这里插入图片描述

封面制作

封面是用 Win10 3D 画图制作的。

爱好

喜欢养鱼(草金鱼)、养龟(草龟);看奥特曼;看优秀的国漫,如伍六七、凡人修仙传;喜欢听梁博的歌;欢迎交流。

梦想:

本书由来

本人就不是正儿八经的程序员,你就当我童心未泯吧。疫情大家都被困在家中,只能慌着想另谋出路。

培训机构天天打广告,制图、CAD、编程、剪辑...,于是抱着试一试的心态,他们都说学 Java 好找工作,我怎么感觉教人赚钱的人最赚钱?

当然我没去培训机构,而是在网上一篇篇的看文章,因为学习路线图挺多,你在知乎上随便搜下,就有一大群打广告,引流到自己的微信公众号;让我不禁感慨那些熟手竟然都混的这么艰难。

我越看,越觉得自己太滞后了,太脱节了。Java 是上个世纪末出现的,我到现在才知道,仿佛我们这个地方的时间流速比发达城市慢了几千倍。我可能会想,如果要是早点知道就好了,也不至于大学期间抱着动力电池组、汽车检测与故障判断百无聊赖,只能在腾讯中迷失自我,寻求一点认同感。我现在才明白,并不是不知道自己到底喜欢什么,而是自己视野太窄,接触东西太少,别人也是如此,给不了贴合实际的建议,所以还是靠自己寻,但是又不能完全不听从别人建议,只希望别又浪费三年。

年龄越大越感觉似乎有一道屏障阻碍,不,不对,应该是高中时就有了这种局限性。

所以感觉自己就像困在茧中发育迟缓的儿童,或者像是一排纽扣,某个纽扣系错位,其它全都错位了。真担心,还没长起来,就 gg 了。我很明白,如果你也能感同深受,我祝你早日挣脱束缚,突破自己的局限性。

由此推出,可能我习以为常的概念,但其他人却一无所知,看到线头也认不出来,自然也不会揪。说实话,要不是学 Java 顺带接触了前端,我永远也不会知道浏览器页面背后隐藏的 HTML 标签,或许误解为 Word 或者其它更高深的东西。再想到大学时还是如高中般按部就班,呆呆地等老师讲,什么高等数学啊,没讲的就不知道要学,结果就讲到极限没讲了,直到毕业后才醒悟,可惜已经没有那么好的学习环境了,真傻。所以,我说非本科的,还是抽出时间干自己的事吧,只听老师讲没多大用处。

那些本科、985、211 出来的,我不知道他们学校是否与社会脱节,还是不需要培训直接就能胜任工作;又或者那些高材生早就摸索出了属于自己的道路,只待毕业后就能大展身手;真是年轻有为啊。当然如果不是,也不要妄自菲薄,至少你学习能力强,也聪明,打败了 50% 的同龄人,多接触我们这样的人,不要被外界评价降低了自己的自信心,哪怕现在积累散去,也可以东山再起,别放弃,要加油!

对我而言,也许受眼界或目光局限,看不远,但我并不认为这是遗憾,种树的最好时期除了十年前就是现在。

所以我的目标压根就不是教小白前沿知识,因为当一个新概念传到我们那,可能都不知道被多少人玩烂了;我只是相信还有一些人如同曾经的我一样,没来得及上道,视野受限,四处碰壁,我希望用自己的时间,能够节省他们的时间。

当然我可没有那么高尚,我很坏的,不受人待见,像是与世界格格不入披着人皮的异类,缺点也是一大堆,对此我没有什么意见,认了。我只是见不得有人跟我一样,踏同一个坑,犯同一个错,浪费生命,自责内疚,消耗最美的年华。尤其连我的初中同学都结婚生子了,能帮一个就帮一个吧。

这个学习时间太长了,还不容易上手,可能学不出什么名堂,做好心理准备吧。如果你很久以后才看到这篇文章,这本书介绍的内容、软件可能也会过时。(2021/07/15 19:29)

我以前听说过一个段子,让我改编一下吧:老师在课堂上将 100% 的知识讲出了 70%,同学们接收到了 40%,动笔自己试下,写出了 10 %,隔天只剩 1% 了,剩下全靠编。

于是我这知识算是从网上来,经过遗忘、组装、变异后再回到网上去吧。

帮助说明

你帮助了我,我自然很乐意回馈你。但是请客,我不知道你是真心的感到开心,还是出于礼貌,很拘谨,不好意思拒绝,说不出口,又或者只是人的本性如此,不受控制地开心,而不是你的本心;这样猜来猜去的,很累。再说了,本人没有去高级餐厅的经验,怕出洋相。

你如果有什么难处,力所能及下,我会帮助你的。(不借钱,被人骗过)

我听力不好,医生说是脑神经损伤,不可逆了(不可恢复):听力的衰弱、接收频率的丢失、无法被解析,说人话就是你叫我,我听不见;听见了声音,不认为是人在喊我;知道你在喊我,但不知道你在讲什么,需要重复几次才能听懂,所以不喜与人交流,不喜欢悄悄话,声音太小了。

我的语言沟通能力方面不是很好,每当亲戚叫我多接触人,出去找个好工作,不要好高骛远,实在不知道未来如何是好,常常被人当作矫情。

由于不怎么接触人,时常感觉自己不近人情,只在家的附近活动,逢年过节不送礼,又不嘘寒问暖走个表面形式,生病也不带礼物看看,没有那些乐呵呵的人受欢迎,我自认为挺讨人嫌,不像个正常人,融入集体,至少我周围看不到第 2 个我这样的,我经常自拟为披着人皮的生物,似人。

哦,你如果想招聘我,想好这一点,我不适合与客户打交道的。希望不会因此找不到工作而饿死吧,否则在全中国也是独一份笑话。

我很早之前就听说过:不要试图改变他人,因为他人无法改变,要从自己身上找原因。每次出现问题,不用别人说,自己就开始把矛头指向自己。现在发现改变自己也很难,之前一直对自己耿耿于怀,发现只会让事情变得越来越糟,因为与最了解自己的自己对抗,这是一件多么愚蠢的事,它总能找到内心最柔弱处,给你致命一击。

终于发现这样不行了,不原谅自己,自己就会拖自己下水:反正就这样了,无法挽回,还不如破罐子破摔;自己给自己过多压力,整个人非但不能前进,反而会散去精气神;一直被动接收别人的观点,只会让我深陷地狱,但我就是十分在意别人的看法,于是想着法子给自己洗脑:我凭什么要管别人的看法来折磨自己?我就这样坏、无耻啦,我就是有这么多不完美的地方,我不当人啦!咋滴?想着想着,于是学会了拥抱自己,接纳自己;哪怕自己有太多的缺陷与不足,拥有这个身体已经值得了,没有必要追求过多,生和它一起,逝也和它一起,它才是最了解我的人,其它都是浮云,没有必要与它不愉快。

于是担子自然就放下了一大半。它再差劲那也是我,不必撇清关系,并且它还是挺好用的,就是忘得快。

当然你如果说想同情我,那倒没有必要啊,我这不是聋,只是偶尔听不懂,大多数情况下还可以对话。谢谢你的关心,祝你天天开心,岁月安好。

image-20220319211226182

image-20220319211414076

image-20220319211515985

image-20220319211547033

image-20220319211629972

image-20220319211711191

image-20220319211802840

image-20220319211850676

image-20220319211921329

image-20220319211946650

数学讲的还是挺简单的,于是买了几本书,发现老师明明在视频上讲的通俗易懂,由浅入深,但是编写的大学教材是真的劝退人,几千页啊,战线拉得太长了。并且那段时间在学 Java,于是没再继续了,我只知道一点,不用的知识一定会忘记,所以现在干脆不学了,等用时再补。果然遗忘才是人类最大的法宝。

image-20220319212006864

求职

天要下雨,人要吃饭;不管再怎么想,到了年龄,就算不结婚生子,但还是得找工作。

不讲什么本人勤奋热爱好学、吃苦耐劳这套老掉牙的事了,这玩意就是你情我愿,一拍即合,不行就拉倒,谁也没有必要低声下气,彷佛占了什么便宜,双向选择嘛。

招聘网站一个个看(不太信任,听说骗子挺多,还是需要线下跑来跑去,还可能被第三方公司外包,扯皮都没法扯,精力都耗没了),什么一面二面,还要花时间背没有用的题目,互相试探,装的很牛的样子,然后等通知;或者一个个线下到处漫无目的地找;除以上途径之外,我还想在各大网站上发布信息,希望能够找到合适的工作。

若你想要了解我掌握的东西,那你看看我写的博客就行,也不会超出这个博客内容太多,忘了很正常,看看就捡起来了,我又不是电脑,没必要一直存储到脑海中,超时就丢掉。

要求:

  • 工作地址:离湖北越近越好。

  • 工作内容:与电脑相关就很好,敲字什么的无所谓,我不想丢失熟练度。

  • 工作形式:公司、团队搭伙、工厂、私活、兼职、在家...都可以。

  • 工作要求:

    1)连续工作半小时 ~ 1 小时,因为现在码字,眼睛不是很好,很干涩需要流泪才能缓解,需要休息 5 分钟远眺;我常常使用番茄钟,提醒自己该休息了,以免精力不足,影响明天。

    2)可以接受重复性劳动,时长不能超过 9 小时;禁止夜班,没得商量。为了身体着想,说不定工资还不够看病呢。

  • 公司气氛:不要勾心斗角、人人胡思乱想;不要官僚作风、气氛紧张。

  • 工资要求:实习期间工资 3000 以上。

  • 个人缺点:

    1)手笨,不会系绳子,只会打蝴蝶结与死结。

    2)习惯沉默寡言,不会烘托气氛;别觉得奇怪,各人秉性不同,不必强求。

    3)英语差,我尝试过好多次了,暂时办不到,以后有时间再试试。

  • 如果确定要为您办事了,我会在入职前将鱼放生,所以请不要耍我玩。

  • 补充一条,禁止传销、坑蒙拐骗其他人、等其它犯法行为。

你如果有意向,可以列出岗位的技术相关要求,若我有不熟悉的,我直接自学完了,再到你那来,给你干活去,这对我小菜一碟。

声明:天下没有不散的宴席,人都有分别的时候,好聚好散;请直接提出来,别耍阴谋,调来调去,或突然增大压力。

本人联系方式

不可以公开手机号,我怕销售天天打我电话,可以私聊我,在此期间应该一直在家附近寻找工作,我习惯被动,现在疫情这么严重,除非一锤定音,否则实在不想外地跑来跑去。

QQ:2675385031

微信号:cqhjava

邮箱:2675385031@qq.com

博客园:https://www.cnblogs.com/cqhh/

CSDN:https://blog.csdn.net/cqh123hh

(有人问,有工作就不错了,怎么还提这么高的要求?漫画附图:)

PS:这要求还算高?

image-20220320104756421

当然我很清楚,能够看到这篇文章的,要么是小白急于寻找信息,要么是好奇;我这啰里吧嗦的,这写了也是白写,所以没有寄托太多的希望;再说了,这毕竟不是专门的招聘网站。

工作情况

物业维修员:2019.10 ~ 2020.5

状态:待业中,最近被催着找工作,能敲电脑就行;只是怕又头脑一热,选错了,又浪费 3 年。

分支:送餐员、流水线、文员、Java 初级工程师、渔场、志愿员...

致谢与勘误

致谢

首先感谢父母给我一个好的身体,感谢母亲耐心的支持。

其次感谢遇到的挫折与不堪,及时纠正我前进的方向。好吧我还是讨厌它,为什么我总要给自己的生活添堵呢?

最后感谢在网上无私奉献知识的大家,我是在家自学的,不懂就上网搜,算是野生吧。你们对我带来了很大的帮助,可以说脑海中的知识体系都是网上博客园一篇篇的文章,一段段视频堆砌而成的,真的很感谢你们。生活在和平的中国,享受如此丰富的资源,挺幸运的,我会把文章知识来源链接放在参考文献中,但可能有遗漏,不能一一列出,但你们对我帮助也挺大,感谢。

这些在网上搜集的资料,如有侵权,烦请告知。

编者:cqh
2022 年 3 月 4 日,于湖北安陆

勘误

笔者也只能算是新手,学识尚浅、水平有限,有些抽象、底层的东西以目前的功力无法一眼看破,找不到合适的方法去验证结论,只能是凭第一感觉得到具体的规律,所以也可能是想当然,看法比较片面。你就当成强行解释,笑一笑吧,对身体好。

部分解释可能不详细或不清楚(词不达意),如果你有更好、更通俗易懂的语言,欢迎留言指出。

在阅读文章时,请你时刻持有一种质疑的态度,这样既能帮助自己思考,也能发现我文章的不足之处,如果文章中有什么错漏的地方,还请不吝啬指教与批评,十分感谢!

若有帮助,会将你列入鸣谢名单中。

书中源码地址

https://gitee.com/ccqqhh/java-se

自序

第 3 版自序

之前是想要找文献与资料,四处找不到;现在是资料漫天飞,四顾茫然;曾经的我反复横跳,浪费了很多时间与精力,干脆把曾经所做的学习笔记整合起来,方便大家。并且这期间,如果另有收获,会持续更新此书的内容。(学海无涯苦作舟)

本人专科毕业。我始终认为只有自己喜欢的才需要学,忽略了高中这不是专项发展,而是筛选人才;那么多的人,企业正好设置为本科门槛可以节省筛选时间;此外你通不过本科,企业难道不会怀疑你的学习能力有问题?既然是为了赚钱,那为什么不选更好的?人才多的去,谁稀罕你这一个。

我倒是不后悔,因为学历与钱对我而言不太重要,还比不上自身学会了写代码的那种喜悦;只是觉得大多数人经过 18 年的学习,转眼间淘汰了一半人,估计都流向工厂的流水线了,太浪费了。

一直笑着说大不了回家种田,但其实要是真的能够维持生活,没有人愿意背井离乡,出去打工的。我之前提到过滞后性,上一辈人因为这种滞后性,吃够了亏,于是只让我们专心学习,自己在外辛苦打工,承受所有的风险,只为了子代不再重蹈覆辙,以为我们考上大学了以后就能赚很多钱,可以享福了,却忽略了其它方面。

我们都以为学成了,考上了好大学,也许前途就顺了。但总还有一半人被淘汰了,比如我,父母还是抱有期望的,至少是个大学生啊!至少比我们强吧,结果期望越大,失望也就越大。

其实学校教的东西可用的很少,大多数就是纯粹应付考试。等上了大学后,被忽略的其它方面,弊处就开始显现;上一辈人突然觉得这么聪明的大学生,这点生活常识都不懂?人际交往、整理内务、洗衣做饭、修电脑啥都一团糟?你们在学校学了个啥?换我来吧!读书有啥用!废物!你们 90 后当家了,这个社会要完蛋!

我不太在意,我知道他的局限性,他所在的小地方,也就只能看到我,误以为我就是大学生的缩影。但是网上有才有能力的人多了海了,否则你们找工作怎么这么难找。当然工厂、销售...等岗位还是挺需要人的。

我认为有这些高智商的人材们,尤其是特别努力勤奋的,社会只会越来越好,但是对自己的命运不是特别乐观。

我希望以后不会固步自封,局限自己的认知,觉得自己经历过这么多的事情,可以一言堂,断定周围人的秉性。因为经验有时效性,且与个人息息相关,在别人身上不一定适用。传递的观念如果别人不听后吃亏掉坑,我就在旁边暗笑,那样太可怕了,那说明我已经变质了,经验此时反而束缚住了我前进的脚步。

警告自己的一句话:我知道人都会变,可是当你看到这曾经写下的这句话,如果觉得很幼稚可笑,算了,不想前进了,就说明你已经融化了。那么狠狠地抽自己,往死里抽,别让我瞧不起你,你欠我的。我现在拼死累活地,下决心,不是为了让现在的你心安理得的阻碍其他人的梦想,嘲讽其他人,也不是让你吃老本,坐吃山空,别以为这是理所当然,好好想想自己该做什么,还是在楼下打麻将与其他人夸天?

当初高中毕业,选专业,虽然实现不了小时候的理想,但是想着新能源汽车发展起来了,也可以保护环境啊,于是特别天真地就选了此专业。唉,实习时来的都是接插线、黑白班做饲料、卖挖掘机、开叉车...尤其看到要求男女不限,18 ~ 50,身体健康...沉默,我这时才明白自己早就已经 out 了,到这来只是将噩耗延迟而已,不禁怀疑我到底这 18 年的生涯到底学了什么?混成这个样子?竟然不知道在简历添些什么东西,热情开朗?踏实能干?会求微积分?也不明白自己有什么优点,自己的价值在哪?凭什么让企业招聘我。再看看其它要求高的,不觉得我能够达到,别人说不定看不上,真是贱啊。

好吧,乌龟壳虽然保护了我不受伤害,但是一旦有朝一日,从壳中脱离,没有练成应对危机的本能,只能扑街。

因为上半年考了一个电工证与驾驶证,友每天恨铁不成钢,即使之前有多不敢,导致错失了机会,所以这次一定不能放过!于是如同赶鸭子上架般慌着选择了工作,貌似能扯上一点关系的物业维修,正遭疫情,每天都是抄水电表,没积累可用的工作经验。解封辞职后开始学 Java,跟着网上的博客与视频一步步来,哎!觉得希望来了,这有什么难的。

那段时间,父亲和亲戚不再问读书成绩的事了,时常问我学了什么名堂,答:我学的这个集合很厉害,底层用多种数据结构存储数据,你看这个双向链...停停停,别跟我讲这个,我听不懂,这么有用,什么时候出去找事?可以赚多少钱?答:额...

得,赶进度吧,花了5个多月的时间,数据结构与算法、MySQL、JDBC、HTML、CSS、JavaScript、Tomcat、Http 协议、Servlet、JSP、AJAX、JQuery、Maven、MyBatis、SVN、Git、Spring、SpringMVC、Dubbo、Linux、SpringBoot、SpringCloud、Nginx、SpringSession、FastDFS、RabbitMQ、SpringSecurity、Shiro、Swagger、CRM 项目...

期间为了方便翻阅和复习,加页码、加目录、记时间、正字复习、画星星、康奈尔...方法效果甚微。

所以以我为反例,别用纸质笔记,很不方便的。

(之前有人问我,5 个月怎么这么慢?其实我之前太贪心了,还想学数学、英语、物理...)

也不是没有人劝我,江山易改本性难移,固定的思维就像茅坑里的石头难以改变。每次听到劝导,又开始神经发作,万一呢?你到底怕什么呢?谁会吃了你?这是否已经成了本能?我想不通,也说不清了...

我的理智告诉我,我只是害怕未知,本能拒绝去尝试,害怕再次失败,又被人侮辱与嘲笑,尤其是年龄大了,没有试错机会了,只能尽力维持现状,一旦没有了父母的支撑,把我投入到大海中,要么四肢扑腾,学会游泳,要么淹死。说到底,我还是有很好的父母,坚实的后盾,实在太幸福了,所以才令人感到如此悲哀。再结合到三和大神、26 岁成人被饿死之类的,我能理解亲戚他们的想法。算了,不说了,混成这个小孩模样,也是没谁了,希望能在饿死之前,主动寻求改变吧。没想到还真自证了自己初中时的想法,讽刺啊。

觉得学的也差不多了,开始看招聘,清一色的本科以上要求,这还玩个毛啊。再从网上看看题目,倒是不难,但想流利的说出,还是要背;语文、英语对我还是有点难,由于耳朵问题,只习惯看字幕,真不想把时间花在这无用的试探之上。说到底,我的信心一直被摧残,没被培养起来,一做事就放下手头的事跑过来看我,指指点点,说这不行那不行,不停地被否定,最后直接推开替换了我,一说话就被认为是借口,为什么别人行而我不行?对啊,我也纳闷这到底是为什么。所以畏手畏脚,一想到工作就躲避,哎呀都是我不行的,干不了,主动失去信心,下贱自己。那么别人看到这个样子,没有一点正能量,只会让现状越来越坏,犹如囚笼之兽。现在回想起来,这本来就不难,小事一桩!为什么非要挫折教育呢???究竟经历过什么事才会认为这是正确的做法?是不是我视野窄了,看不到背后的良苦用心?

得到一个规律:不犯错,必败事;如果小时候文文静静,逆来顺受,畏手畏脚,被框在自以为的规矩下,不敢尝试,看上去很听话很乖;但年纪大了,躲得了一时,躲不了一世,没有小错带来的经验,将来必定踩坑,成为平庸的人;这虽然不是必然发生的,但是概率很大,常在河边走总有一次会湿鞋嘛。小时犯错相比大时犯错,周围人包容度更高,所以建议小时多尝试。

受限于学历,所以我降低了要求,不得非要与代码搭边,随便啊,别进工厂黑白班、天天 12 小时,我还不想猝死。你们如果有什么好的渠道,智联、前程、脉脉...欢迎交流啊!

回顾,自己表面掌握了什么高大上的框架,但框架用起来很简单的,底层屏蔽了繁琐细节的具体实现,从配置多个 XML 文件到约定大于配置直接启动,增删改查就增几个注解,改下 SQL 语句,越学越简单,只是要背注解、方法名、操作步骤...才发现我其实也就是只会调下 API,太浅显了,一遇到稍微深层次的问题就扑街。忘了代码就在网上搜索复制粘贴,这不就是搬砖吗?我学到这之后就没学了,就是觉得太浅了,简直把我当做硬盘在用。这些方法名,记流程有个鬼用,时代一换,又得重新开始。

我觉得是基础没打好,也许是哪里走错,之前学的不够系统,又把 JavaSE 内容看了一遍,尚硅谷、动力节点、狂神说...,收获不大。

终于下定决心,这 Java 内容之多,之繁杂,总得有人要整理一下节省小白的时间,稍微扑腾一下吧;为了帮助和我一样的人,避免踏入同一个坑,开始本书的第 1 版制作:

在这里插入图片描述

在这里插入图片描述

当时正是过年,前前后后忙了几个月。又被打击了一顿,说我在家里游手好闲、不学无术,不务正业,只晓得找借口,是废物一个,在村里都不好意思说,不敢走亲戚,抬不起头,对外统一口径是我好高骛远,只晓得在家里玩;每当别人问起我的年龄时,是否还在上学,总是感到脸上一阵青一阵白十分羞愧,无力感萦绕在心头阴魂不散。此时负标终于超过负荷,被最后一根稻草压倒,期间强行振作,努力维护日常作息,又被说年轻人还是没有被锻炼到,还是要多给点压力锻炼下,否则太脆弱了,心理承受能力太差,连这点打击就把你压垮了,去做销售扭转下性格就好了,挺适合的。终于压不住内心的痛苦崩溃了,开始自暴自弃,为什么所有的评价都认为只有我这么废?算了吧,说的对啊,我的确没用,还学什么啊,都卖了吧,我实在太脆弱了不堪一击,整个人又坏又蠢,估计就连小学的我就瞧不起现在的自己,不搞了,自闭中;眼泪不能流,声音不能出,打碎牙齿往肚子里呜咽...男人!男人嘛,男人!你这么大个人,怎么还跟小儿一样,好意思吗?没希望了,一生已经毁了。

但是过了一段日子后,越看越觉得这个笔记不对劲,写的这么粗略,这是给自己看的吧?新手能够看得懂吗?强行驱动自己,做事必须有头有尾,还是续了一根弦。

当时又看了韩顺平老师的课程,他总结的学习方法很不错,先 demo 再写注意事项。然后由此编写了第 2 版:

初入门时 JDK 安的是 15,(应该是 8,做第一版教程时才应该把 8 删了,重新下了 15)当我重新准备下载时,发现页面找不到了,这才知道长期支持版本是什么东东。

好吧,为了回顾当初下载软件时遇见的问题,场景重现,方便截屏,狠心格式化了电脑,把之前的 Node.js、Linux、MySQL、Navicat、Maven仓库、Tomcat 等一切全部删了,直到现在都还没下载回来。之前放入 GitHub 仓库的代码,比如数据结构,被我认为代码写的太垃圾了,直接清空了。(现在就没有参照了,真是白给)

只剩下 FastDFS、RabbitMQ、SpringSession 的 demo 了。

好,没关系,大不了重新开始。

花了好几个月,废了很大心血,删了改,改了删,半夜突然醒来,打开电脑就只为修改一句话,信心满满,没有多少评论,想着是不是网络上资源太多,唾手可得,根本就不缺我一人的努力,此时突然觉得我都没有找到工作,所学知识之浅,路之狭窄已到头,还敢斗胆发文章,也不称称自己的斤两,这不是误人子弟吗,于是把之前的所有博客都删了,秉持了不尝试就不会失败的鸵鸟原则。

我现在想明白了,去追赶那些前言技术,学习语法,如何使用,只会流于表面,疲于奔命;干脆放弃掉,重新开始。我觉得就连 Java 语法,也变得不太重要,毕竟只是背别人定义的规则,还是表面;但是要写程序,没有办法,于是就有了语法入门篇。

根据纸质资料和粗略的第 1 版,以及网上的博客资料,拼拼凑凑得来了第 3 版,至此只完成到集合部分。并且我还是不满意,决定抽出时间,继续重置。

现在又要看最开始的老版笔记重新写一遍,做事有始有终,不能轻言放弃,爬也要爬到终点!没有什么是不可能的。还好现在没工作,否则抽不出时间写。由于删了之前的博客,有很多都是后来在最初版的基础上修修补补地,可能有很多疏漏、没来得及添加的、知识变异了的,恳请大家提出宝贵的批评和建议,以便改正。学习路线就按韩老师的来,先 demo 再声明课堂注意、出题目,按部就班的点还是挺不错的。

2022.3.11

第 2 版自序

接受现实,痛定思痛,干脆抽出时间,整理曾经写过的纸质笔记与博客,为尚未踏出校门的同学节省一点时间。

有的地方根本没必要学,直接复制粘贴看看 API 就行,没啥技术含量的。对于那些性格内向的人,心里有很多内容与墨水,但嘴里吐不出话的人,我特别能够理解你的苦衷,珍惜校招吧,避免毕业就是失业的尴尬局面。

有些人的确很聪明,在这里我不想扯勤奋与努力,以前也讲过,这只是表象,何况对于有些人也真的很难,考虑到通用性,内容稍微细一点,不要嫌我啰嗦。

我已经很知足了,阳光、空气、可以动的四肢、能计算 1 + 1 的大脑,没用什么遗憾了。对我而言,健康远比时间、金钱、技术重要。不对,这该死的技术,这抵挡不住的魅力啊!!!

真的好怀念魔兽,如果阿尔萨斯王子没走屠杀线一定会有美好的结局吧。

2021.7.4

第 1 版自序

最近学习了 MarkDown 语法与五笔打字,打算把纸质笔记整理成书,帮助大家。

经过一段时间的摸索中,得出了以下结论,希望对新手有帮助。

1、删除无效的软件(打造合适的环境)

比如一直让你刷刷刷,停不下来的。因为你每次刷,都在期待下一个视频对你有用,但很大的可能只是满足了你的多巴胺分泌。

上瘾后不要责怪自己,想办法清醒后把这些触手可及的东西,不管是卸载、还是关闭推送、扔在角落...尽一切可能不要让它来打扰你;如果你是因为没有事情,好无聊,看这些来打发时间,那就找一些其他事情吧,如散步、健身、看书什么都可以...

这种大量占用时间碎片,令人上瘾的软件,可以说是一种毒瘤,我想不通为什么要研发它,钱真的很重要,但也不至于这样吧。

我说过人就是个石头,现在看看销售营销等一些手段玩弄心理,越发坚定这个想法;我们能做的只不过是别让那些外力推动自己。

2、工作与学习之间的平衡

公司都是重复性劳动,没有实际上的经验提升,还要担心上级领导的试探,同事的打压,构建话术。下标后自学也只能学习 1、2 个小时,并且还不包括动手尝试的环节,能够学到手的知识十分浅显。等学完,天都换了。

并且公司本质上是私人组织的、有了好点子想变现、以盈利为目的的结构;你想想你要是老板,谁不行直接替换掉,反正人才多的是,为何还要等你慢慢成长,真以为自己是潜力股吗。

但是辞职也不可取,长时间在家,没有了生活来源;走在路上还要被婆婆们嬉笑。

此外亲戚们都不会理解,最亲的人也会骂你不中用,你给他也讲不明白,还会被觉得十分可怜。一开始还可以打哈哈,说自己已经毕业了,在学习编程;但之后呢?这条漫长的学习之路,不同于进厂教教就能直接上手。

我想提醒你,你是否也感觉大学的课完全没必要上,但我们也许还如同高中一样,按部就班,讲什么就听什么,那么到了毕业就等于失业,你应该抽空把自己的爱好发扬光大。如果等到上班再学,其他人会怎么想?

结论:请好好珍惜大学的时光,那是唯一适合自学的时候。否则你要承担额外的精神压力,扛不住人就废了。

3、适当约束自己的好奇心

不要看到什么都想学,什么都想买,一直在搜,知识太多,你学不过来的。你先在网上搜索,比如后端的整套知识点,按顺序来,一心一意,一套教程为主,有疑惑的再去别的地方比对。不要没事就搜索一大堆资料收藏,又不开始,资料一定要精简,那些看上去有用的知识但短期用不上的,直接取消收藏。等你到了这步,再找资源,别做松鼠。

关于难度高的,可以先跳过,记录下来,后面再补上,比如数据结构、JVM 底层、多线程与高并发、GUI 等等...别把初学时间浪费在这上面。

不要背代码,要掌握原理,方法可以到 API 文档上查。之前基本都是概念上的东西,理解就好了,但学到集合、IO、常用类如 String,只背方法名一定会消灭你学习的兴趣。

如果学习资料抛出一大堆让人心痒痒的新名词,操作不讲规则与原理,没有容易理解的例子,只是像背或粘贴似的敲打代码以及一大堆的命令控制流程,那么我劝你趁早跳过,用时再翻阅。

4、管理好精力

并不是一直在同一个地方枯坐,工作效率才会高。

要合理分配精力,因为精力是有限的。学习并不是生活的全部,如果你整天扑到学习上,只会感动自己。随着精力的消耗,自控力的减弱,一定会在某天突然崩掉,开始变得什么都没有兴趣,行动力拉到最低,即使强迫在学,也只是枯坐原地,享受着左耳进右耳出的枯燥感。

早起别熬夜,每天一定留至少 30 分钟的时间午睡。(为了第 2 天的精神良好)学习时推荐使用番茄钟,每过半小时就休息一小会,合理分配精力。(休息时不要看电子产品与动脑筋,要适当远眺)

5、学好英语

没什么说的,英语真的很通用很重要,为了阅读懂优质的国外资料。看着机翻的我太拉跨的,简直是哑巴英语。学不会也没关系,就算关了一扇窗,把开这扇窗的力量用到别的地方去,一定不会浪费的。

6、必学的知识点

从大多招聘岗位的要求取出交集:

IO、集合、JVM、多线程、Spring、SpringBoot、SpringMVC、MyBatis、Dubbo、SpringCloud、JavaScript、Vue、MySQL 与 Oracle 取其一、SQL 调优、Redis、Tomcat、Nginx、Docker、Zookeeper、RabbitMQ、Kafka、Linux、HTTP 与 TCP / IP 协议、Socker、Maven、Git、AJAX、ElasticSearch

剩下的知识,应聘意中岗位时再选择性补全。

7、选择书籍还是视频

视频时间看完所需时间更长,但可以一步步来,如果章节内容划分合理,每个视频时常合适,学习者更轻松完成每个目标,掌握自己的进度。适合初学者。

书籍花费时间较少,但经常有断节之处,初学者搞不明白缺少哪一个环节,看着晦涩难懂的文字,翻不了几页就草草放弃,适合耐得住性子的人。

8、不要过分追求完美(承认自己的不足之处)

想做就直接放手干,不要计划的那么周全,稍微一有变动,就放弃目标,想明天再做。是吧,别觉得只有从容不迫、不慌不忙、按部就班地才叫做事,而达不到自己的心理预期就不做了,大多数人都是计划赶不上变化,被搞得灰头土脸的。

另外人都是有惯性的,如果设置了太高的目标难度,只要有一天没坚持下来或不想做了,那么最终结果一定是放弃。

这不禁让我怀疑,以前对生命的定义。我本来以为生命是一种奇迹,我感到自豪,超脱于物质之上;比如从下坡推一个石头,它只会毫无疑问的滚落下去,而我们可以做出反应。

但经过太多的事后,我发现生命的本质与死物没有太大区别;生命就像是一台精密的操作仪器,如同电脑、机器人,只不过比它们先进点。

接收输入做出反应,将应对方式与最终结果存储;等下一次事件再发生时,可以不断优化脑海中存储的应对危机的方法,达成自我学习的本领。

9、学会分享

不要闭门造车,要发在各大网站上分享,Markdown 语法如果你都会了,直接发在博客网上。不要觉得自己技术菜。

第一,人人都有开始,你发出来了,别人才能纠正你的错误,避免成为井底之蛙。

第二,你又开始自耗了,不停地在脑海天人交战,一顿脑补:会不会万一发出来被人嘲笑怎么办?...于是藏着掖着,生怕别人知道,出丑,事情还未做,就想一系列的后果,别人有多可恶。我觉得世上还是好心人多,当然不要被零星几个恶评就忽略了大多数支持你的人。

第三,百分之 99.9 的人根本刷不到你的文章,或者一下就 × 过去了,即使刷到了,那还有 9.9 成人浏览刷的一下就关了,评论的那是少之又少,凤毛麟角。如果帮助不到他,谁还关心你是谁,最多充当局外客感到可笑。当你还在纠结别人的看法时,纠结要不要做时,别人根本记都不记得你。我们在网络时代那是十分渺小,沧海一粟,很快被淹没,就算有弄潮儿掀起了几朵浪花,也会很快平息,所以别再纠结丢不丢脸了。

10、关于努力与勤奋

有人经常说,要自律,要吃苦,要努力,才能成功,你看看这些成功人士都是这样...努力才能得到一切,才能成功。但我抛出一个结论:努力与吃苦,并不能成功,也不能改变命运。

他们忽略了一个东西,自律、吃苦等只是个表面行动展现出来的结果,并不是成功的原因。比如你打王者,技术很菜,但是你就想升段位,不用别人提醒,开了一把有一把,一直连跪,日夜颠倒,身体早就向你警告了,这种违反本能的行为真够自律的。不打一定不会上王者,打了无数盘由于技术太菜,也不一定能上王者。

如果你真心地想要完成某个目标,你不会觉得这是折磨的。再次声明,不是自律、努力才会成功,它们实际是想到达成目标,而自发控制自己身体机能活动的行为。

友情提醒:全身心很耗精力,记得番茄钟打断此状态(半小时),休息的时间眺望远方,不要看电视、玩智能设备,否则越来越累。

11、学完要多久?

如果你不复习,不怎么练,只看视频的话,最多半年。能够记住多少看命。

正常一年半(每天 8 小时以上不间断)

12、是不是很难?

相比越到后面越抽象,一环扣一环的数学,Java 其实还算好,小白也能调用方法完成工作,理解一下就行,屏蔽了底层如何实现的繁琐细节,看看 API 即可拿来用。

至于为什么一看就会,一敲就废?是不是我没有天赋?

说实话,这些东西不会就跳过,以后再补,只要学会调用方法,看看 API 就行。还没有达到拼天赋的地步,努力也无需拼,努力只是外表展现的结果,而不是因,若你感觉自己很努力了,很痛苦,就像我之前说的这样,你想想哪里出现了问题,是不是真的不感兴趣,居然会觉得要强逼自己,努力才会成功?想着人只有逼逼自己才行?

话说回来,不会不是真的完全不会,而是你看时觉得很正常,但一到做时脑海中缺少一些关键片段,甚至连思绪的头都没有,无法串联成珠,往往出错,其他人只不过是由于过往经历(熟能生巧)或者下自然地就补齐了这个漏洞。

没必要担心,如果你接触它的时间过长,会弥补的。不用特意寻找解决它的办法,多看看就行。(把同一个知识点学三遍差不多就熟了)

此外我觉得人略微笨些,是好事,意味着你能够帮助这世界绝大多数人,不会出现理所当然,而一些人却不能理解的事,知道哪里有槛,知道一些人哪里有误区。只要你走通了,其他人就能走通。

花自己的时间,让更多人节省了时间,值。努力活完短短的一生,将所得成果留给后代分享,人类真的是很了不起呢。

2021.2.11

第 0 版自序

今天是网上学习 Java 的第一天,自学,根据学习路线图一篇篇在网上搜来的知识。

本人才疏学浅,感悟还不够深,只能根据些许痕迹揣摩出背后道理。主要是臆想成分居多,如果有什么不对的地方,还请各位大师指点,大家一起共同进步。

另外希望把已建立的认知体系撕开一个口,推倒。包容,承认自己的失败,不要斗气就认这个死理,大不了掌握其它方法重头再来。

2020.6.13

第一章 JDK 下载及准备工作

内容导视:

  • Java 介绍
  • JDK 下载
  • 编写代码前的准备
  • md 软件简单使用
  • 常用的 DOS 命令

1.1 Java 介绍

内容导视:

  • Java 简介
  • Java 技术体系
  • 前后端的工作内容
  • Java 语言特性
  • Java 用途

看不懂就跳过,这里不是重头戏,说实话,挺无聊的,直接跳过也没问题。(我只教如何开车,不教车的来历、发动机缸数、如何运转;你觉得只是浮于表面,没办法啊,又不是科班)

1.1.1 Java 简介

Java 最早是由 SUN 公司(已被 Oracle 收购)的 James Gosling(詹姆斯·高斯林)在 1992 年开发的一种编程语言,最初被命名为 Oak,目标是针对小型家电设备的嵌入式应用,结果市场没啥反响。谁料到互联网的崛起,让 Oak 重新焕发了生机,于是 SUN 公司改造了 Oak,在 1995 年以 Java 的名称正式发布,原因是 Oak 已经被人注册了,因此 SUN 注册了 Java 这个商标。随着互联网的高速发展,Java 逐渐成为最重要的网络编程语言。

有兴趣查看以下文章,了解 Java 发展历史

  • 作者:JMCui,https://www.cnblogs.com/jmcui/p/11796303.html
  • 作者:苗子说全栈,https://baijiahao.baidu.com/s?id=1714823765201515049

此外 JDK1.5 与 JDK5 是一样的意思。因为比之前版本的变化大,以此作为区分,表示与众不同。就像美猴王自封齐天大圣,都是一个人(猴)。

此外还有 J2EE 、JavaEE,只是叫法不同,不必纠结。

正式发行名称 昵称
JDK 1.0.x Java 1.0
JDK 1.1.x Java 1.1
Java 2 Platform,Standard Edition,v1.2 Java 2
Java 2 Platform,Standard Edition,v1.3 Java 3
Java 2 Platform,Standard Edition,v1.4 Java 4
Java 2 Platform,Standard Edition,v5.0 Java 5
Java Platform,Standard Edition 6 Java 6
Java Platform,Standard Edition 7 Java 7
Java Platform,Standard Edition 8 Java 8

1.1.2 Java 技术体系

分成了三个技术体系

(以前被称为 J2SE、J2EE、J2ME)

JavaSE(Java Standard Edition):Java 标准版,包含核心的类库,主要开发桌面应用(如 Windows 下的应用程序)。允许您在桌面和服务器上开发和部署 Java 应用程序。JavaSE 和组件技术提供了当今应用程序所需要的丰富的用户界面、性能、多功能性、可移植性和安全性,并为 JavaEE 提供基础。

JavaEE(Java Enterprise Edition):Java 企业版,为开发企业环境下的应用程序提供的一套解决方案,包括 Web 网站后台开发等。该技术体系包含的技术:Servlet、JSP 等。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务端Java 应用。JavaEE 是在 JavaSE 的基础上构建的提供 Web 服务、组建模型、管理和通信 API。

JavaME(Java Platform Micro Edition):Java 微型版,JavaSE 的瘦身版。主要做嵌入式开发。 JavaME 是专门为资源受限的设备设计的,如为 M2M、工业控制、智能电网基础设施、环境传感器和跟踪等的无线模块、手机、PDA、电视机顶盒和打印机上运行的应用程序提供一个健壮且灵活的环境。自从安卓系统出来后, JavaME 就用的比较少了。

JavaSE 是整个 Java 平台的核心,做 Java 后端先学 JavaSE。

1.1.3 前后端的工作内容

前端

编写 html 代码搭建一个框架展现网页内容,如图片、文字、视频...,打开浏览器的任意网页,按下 F12 可以看到网页对应的 html 文件

用 CSS 技术美化页面,指定 html 标签的位置、样式等;

用 JavaScript 语言控制与用户的交互,比如网页弹窗、动态改变网页内容、验证登录信息、跳转页面等。

例:点击登录页面,输入用户名与密码,按下回车,会触发事件执行 JavaScript 代码,给用户响应(登录失败或成功)。

可以说前端相当于前台,是可以看的见的内容。

后端

接收从前端页面或其他后端服务传递过来的请求,处理业务逻辑,对数据库中的数据增删改查 CRUD。

给调用者一个响应,如把数据返回给调用者、操作是否失败、返回对应的 html 代码等,用户是看不见的。

例:接收到前端传过来的用户名与密码,查询数据库中是否存在,若不存在返回用户不存在。前端接收到后,改变页面信息给用户提示。

不限于使用哪种编程语言,只要能把正确的数据返回给调用者即可。

要保证访问量很大时(同一时间内能够处理尽可能多的请求)而服务器不宕机、响应时间短不至于用户长时间等待、多个用户访问时保证数据的一致性等。(高可用、高并发、分布式、自动化)

1.1.4 Java 语言特性

Java 是一门编程语言,特性如下:

1、面向对象

Java 以类为结构组织代码,对对象、继承、封装、多态、接口、包等均有很好的支持。为了简单起见,Java 只支持类之间的单继承,但是可以使用接口来实现多继承。使用 Java 语言开发程序,需要采用面向对象的思想设计程序和编写代码。

如果不想使用此特性,Java 也可以写出面向过程的代码。

2、平台无关性

一次编写,到处运行(Write Once,Run any Where)。因此采用 Java 语言编写的程序具有很好的可移植性,而保证这一点的正是 Java 虚拟机。在引入虚拟机之后,编译后的 class 文件可以在不同的平台上运行,不需要重新编译。

3、简单性

Java 语言的语法与 C 语言和 C++ 语言很相近,使得很多程序员学起来很容易。对 Java 来说,它舍弃了很多 C++ 中难以理解的特性,如操作符的重载和多继承等,而且 Java 语言不使用指针,加入了垃圾回收机制,解决了程序员需要管理内存的问题,使编程变得更加简单。

4、解释执行

Java 程序在 Java 平台运行时会被编译成字节码文件,然后可以在有 Java 环境的操作系统上运行。在运行文件时,Java 的解释器对这些字节码进行解释执行,执行过程中需要加入的类在连接阶段被载入到运行环境中。

解释型的转换工具称为解释器,程序在运行时使用解释器。每翻译一句,就执行一句,效率低,但跨平台性能好。

编译型的转换工具称为编译器,事先把源代码交给编译器,它一下子全部翻译,得到编译后的代码,可以直接被机器执行。

例如 JavaScript 语言有编译型的转换工具,如 chrome 的 V8 引擎,也有解释型的转换工具如 Netscape Navigator 的 JS 引擎。

5、多线程

Java 语言是多线程的,这也是 Java 语言的一大特性,它必须由 Thread 类和它的子类来创建。Java 支持多个线程同时执行,并提供多线程之间的同步机制。任何一个线程都有自己的 run() 方法,要执行的方法就写在 run() 方法体内。

6、分布式

Java 语言支持 Internet 应用的开发,在 Java 的基本应用编程接口中就有一个网络应用编程接口,它提供了网络应用编程的类库,包括 URL、URLConnection、Socket 等。Java 的 RIM 机制也是开发分布式应用的重要手段。

7、健壮性

Java 的强类型机制、异常处理、垃圾回收机制等都是 Java 健壮性的重要保证。对指针的丢弃是 Java 的一大进步。另外,Java 的异常机制也是健壮性的一大体现。

8、高性能

Java 的高性能主要是相对其他高级脚本语言来说的,随着 JIT(Just in Time)的发展,Java 的运行速度也越来越高。

9、安全性

Java 通常被用在网络环境中,为此,Java 提供了一个安全机制以防止恶意代码的攻击。除了 Java 语言具有许多的安全特性以外,Java 还对通过网络下载的类增加一个安全防范机制,分配不同的名字空间以防替代本地的同名类,并包含安全管理机制。

1.1.5 Java 用途

1、Android 应用

许多的 Android 应用都是由 Java 程序员开发者开发。虽然 Android 运用了不同的 JVM 以及不同的封装方式,但是代码还是用 Java 语言所编写。相当一部分的手机中都支持 Java 游戏,这就使很多非编程人员都认识了 Java。

2、在金融业应用的服务器程序

Java 在金融服务业的应用非常广泛,很多第三方交易系统、银行、金融机构都选择用 Java 开发,因为相对而言,Java 较安全 。大型跨国投资银行用 Java 来编写前台和后台的电子交易系统,结算和确认系统,数据处理项目以及其他项目。

3、网站

Java 在电子商务领域以及网站开发领域占据了一定的席位。开发人员可以运用许多不同的框架来创建 Web 项目,如 MyBatis、Spring 全家桶。

4、嵌入式领域

Java 在嵌入式领域发展空间很大。是指各种小型设备上的应用,包括机顶盒、车载的大屏影音娱乐设备、POS 机等。在这个平台上,只需 130KB 就能够使用 Java 技术。(在智能卡或者传感器上)

5、大数据技术

Hadoop 以及其他大数据处理技术很多都是用 Java,例如 Apache 的基于 Java 的 HBase 和 Accumulo 以及 ElasticSearchas。

6、框架

解决企业应用开发的复杂性,让开发更加方便。

7、软件

制作小游戏、开发工具 IDE 。

1.2 JDK 下载

内容导视:

  • JDK 与 JRE 的区别
  • JDK 下载与安装
  • 如何卸载
  • 如何禁止 JDK 检查更新

即使编写了 Java 源代码,系统也无法直接执行,需要 JDK 提供编译和运行 Java 程序的环境,将代码解释为系统可识别的机器码。

1.2.1 JDK 与 JRE 的区别

JRE(Java Runtime Enviroment):是 Java 的运行环境。面向 Java 程序的使用者,而不是开发者。

如果你仅下载并安装了 JRE,那么你的系统只能运行 Java 程序。JRE 是运行 Java 程序所必需环境的集合。它包括 JVM(虚拟机)、Java 平台核心类库(如 rt.jar)和支持文件。它不包含开发工具(编译器、调试器等)。

JVM(Java Virtual Machine):Java 虚拟机。是整个 Java 实现跨平台的最核心的部分,能够运行以 Java 语言编写的程序。其中的虚拟机屏蔽了底层运行平台的差别。

JDK(Java Development Kit):是 Java 开发工具包,它提供了 Java 的开发环境(提供了编译器 javac.exe 等工具,用于将 java 文件编译为 class 文件)和运行环境(提供了 JVM 和 rt.jar 即 Runtime 辅助包,用于解析 class 文件使其得到运行)。

如果你下载并安装了 JDK,那么你不仅可以开发 Java 程序,也同时拥有了运行 Java 程序的平台。JDK 是整个 Java 的核心,包括了 JRE 和 开发工具 jar 包,如 tools.jar。

核心类库:主要是开发经常使用的类库,避免重复造轮子。如 java.lang 下的 String、包装类等,已经被编译成了 .class 文件,需要时直接拿来用,不用自己重新写一份。

JDK > JRE > JVM

如果你只是想运行 Java 程序,就只安装 JRE 即可。

1.2.2 JDK 下载与安装

JDK 下载地址:https://www.oracle.com/java/technologies/downloads/

有 JDK7、8、11,通常使用的是 JDK8、11,因为它们是长期支持版本,一般的版本支持半年就不再显示。此时的你点开链接,说不定已经看不到 JDK7 了。

LTS 代表长期支持的版本。

Oracle 产品支持的三个策略:https://blog.csdn.net/iteye_21199/article/details/82305640

版本的有效时间:https://www.oracle.com/java/technologies/java-se-support-roadmap.html

JDK8 扩展支持到 2030 年,Oracle 将每三年指定一个版本作为长期支持版本。

查看自己电脑位数

x86 是 32 位,x64 是 64 位。

打开文件资源管理器,右键此电脑属性查看系统类型是 64 位操作系统,还是 32 位。

根据自己的系统、位数决定下哪个

比如我的电脑是 windows 系统 64 位,我使用的是 JDK8,按图选。

特别说明

需要自己创建账户,若不想可在网上搜索 JDK8 下载,或下载最新版本,此时是 JDK16,下载 zip 和 exe 格式的都行。我将网盘链接放在了资源地址这章中,需要自取。

安装

如果下载的是 exe 文件,直接双击,指定安装目录,一直下一步,就安装成功了;


(安装后我又卸了,使用了 zip,所以地址前后不一致)

是 zip 解压即可,点击解压后的文件夹,地址栏上显示的就是安装目录,要看到 bin 和其它目录,如图:

认为安装目录是 D:\\cqh_environment\\Java 就错了。

记住 JDK 的安装目录,配置环境变量要用

安装好后,看看目录:

bin 目录存放命令,如常用的 javac.exe、java.exe,用于编译,运行程序。

lib 存放着 jar 包,如 dt.jar 是运行环境类库,存放了 Swing 组件;tools.jar 是工具类库,用来编译与运行 java 文件。

src.zip 存放着 Java 源代码压缩文件。

rt.jar 在 jre\\lib 中。

解释

我们编写源代码使用的文件后缀为 .java,这种 .java 文件经过编译生成 .class 文件,多个.class 打包放在 .jar 包中。

1.2.3 卸载

呃,这只是教你如何卸载,并不是现在让你卸载...

打开控制面板/卸载程序,找到 Java。

或者打开 Windows 设置/应用/应用和功能,单击要删除的软件,会弹出卸载字样。

如果你不知道如何打开它们,请用百度搜索,如如何打开Windows设置;其实由很多问题都已经得到回答,没必要重复造轮子。学会使用搜索,对自学很有帮助。

卸载成功后,所有目录名带 Java、Sun、Oracle 的一律删掉,如:

删除安装 JDK 的目录、删除 C:\\Program Files (x86)\\Common Files 和 C:\\Program Files下的的 Java 和 Oracle 目录、C:\\Users\\用户名\\AppData\\LocalLow 下的 Oracle 和 Sun 目录、C:\\Users\\用户名\\AppData\\Roaming\\Sun。

1.2.4 禁止 JDK 检查更新

这是可选项,不是必须要做的,因为我觉得时不时弹出来像牛皮癣一样挺烦。

打开控制面板\\程序\\Java,取消勾选自动检查更新,不检查;

高级\\应用程序安装\\从不安装。

1.3 编写代码前的准备工作

如果你是什么都不懂的新手,请别慌着写代码,还有如下几件事要做:

内容导视:

  • 为什么要写笔记
  • 使用记事本编写源代码
  • 如何学会双手打字
  • Windows 系统常用的快捷键
  • 一点建议

1.3.1 为何要写笔记

为了对抗遗忘,可以快速回顾、促进理解、加深记忆,将知识有条不紊地归类,提炼反思应用。

不建议使用纸质的笔记,记录麻烦,不方便携带,有老化被撕毁的风险,摘抄代码时不能 ctrl+c 快速复制粘贴。

推荐使用 typora 软件、博客、等各大网站记录。

1.3.2 编写源代码使用的记事本

工欲善其事必先利其器,使用系统自带记事本编写程序,按下 tab 缩进一大节,逼得我每次使用 4 个空格,回退也难;关键字没有颜色,不易于识别与查看。

下面介绍几个更好的工具,安装后,右键文件选择打开方式即可。

EditPlus

建议网上搜下汉化版的,下面是官方的收费的。

下载页面:https://www.editplus.com/download.html

有 30 天的试用期,过后要购买许可证。

Notepad++

下载页面:https://notepad-plus.en.softonic.com/download

Download 就是下载的意思。

如何设置主题

设置\\语言格式设置\\选择主题,此外还可以根据不同语言设置样式。

image-20220314193213707

image-20220314192900107

Sublime Text

官网:https://www.sublimetext.com/

Visual Studio Code

官网:https://code.visualstudio.com/

Atom

官网:https://atom.io/

不能指定安装目录;这种是以项目的方式管理文件。

image-20220315100638692

也可以搜索一下其他的文本编辑器,看哪款适合你。

编写代码

打开刚刚下好的软件,输入源代码,最后 ctrl+s 保存,另存为 First.java。但是现在还不到写代码的时候,下一章好吗?

1.3.3 练习双手打字

一个指头敲字,一边看键盘,一边看输入的代码,很容易出差错。

既然选择了在电脑上打字,就要提高自己的指法速度,敲代码和写笔记才会快,两只手共同配合。

不用担心,我也这么走过来的,虽然一开始没有单只手指头快,但坚持下来,不看键盘,手势正确,打字速度会有明显的提升。

推荐使用金山打字通。

下载页面:http://www.51dzt.com/

从第一步,跟着图中手势位置慢慢打,差不多跟着敲一个星期,就基本实现盲打了。

页面上有金山打字通的下载,点击下载。

下载好后,双击执行 exe 文件,安装界面会跳出是否安装其它软件,根据自己需要确认是否勾选。

安装好后点击新手入门,练到不用看键盘,打字速度超过 30~60 字/分钟差不多够用了。(只需练习英文部分即可)

1.3.4 Windows10 系统常用快捷键

掌握常用的快捷键,能够极大地提升效率,节省时间。

如果是有触摸板的笔记本电脑。单击是鼠标左键,双击是鼠标右键。

请打开 windows 设置\\设备\\触摸板查看:(Windows + i 打开设置)

下面是我总结的常用几个快捷键。如 Ctrl + A,在电脑键盘上找到这两个键,同时按下去即可触发全选操作。

如果打不出中文,怀疑是按 Caps Lock 开启了大写或者连续按下 Shift 启动了粘滞键,再次按下此键即可恢复。

提前说明:

Ctrl + + 指同时按下 Ctrl、+ 这个键,别看见两个加号就懵了。

文本相关

先选中文字,Windows 键是 Alt 左边的

内容如下:

  • 常用的粘贴、选中、删除文字的快捷键
  • 翻页、浏览文字的快捷键
  • 切换输入法、中英转换
操作 快捷键
跳过单词 Ctrl + 左右箭头,可配合 Shift 使用选中文字
选中文字 Shift + 箭头、Shift + Home/End/Pgup/Pgdnd
复制 Ctrl + C
粘贴 Ctrl + V
全选 Ctrl + A
撤销 Ctrl + Z
查找 Ctrl + F
替换 Ctrl + H
删除文字 Del、Backspace
上翻 Pgup Pause
下翻 Pgdn Break
行首 Home
行尾 End
页首 Ctrl + Home
页尾 Ctrl + End
切换当前语言下的输入法 Shift + Ctrl
切换输入法 Windows + 空格
中英文切换 Shift、Ctrl + 空格
切换语言 Shift + Alt

热键相关

若桌面崩了,使用快捷键打开任务管理器,运行新任务,输入 explorer 后回车。

内容如下:

  • 创建文件、查看文件属性
  • 切换窗口,窗口最大化、最小化
  • 切换桌面,查看桌面
  • 截屏
  • 放大镜
  • 快捷方式打开应用、资源管理器

有的快捷键按下去时讲究先后顺序。

例:如 Alt + Tab ,先按住 Alt 键不要松,再按 Tab,有先后顺序(同时按也行),自己试试就知道我在讲什么了。

← 指的是左箭头。

说实话我向来很反感背知识,随用即取就行。但是那面试题人人都背,你不看看,万一被难倒了怎么办?算了不考虑这些,下面这些快捷键你只看看经常使用的即可。

操作 快捷键
改变桌面图标大小 按住 Ctrl + 鼠标滑轮滚动、触摸板两指缩放
将文件放入回收站 Ctrl + D 、 Del
永久删除文件 Shift + Del
创建文件夹 Alt + 2
查看当前文件属性 Alt + 1
所有窗口最小化 Windows + D 、 Windows + M 、三指下滑
所有窗口最小化又还原 双击 Windows + D 、三指下滑又上滑
当前窗口最小化 Windows + ↓
当前窗口还原、最大化 Windows + ↑
查找文件 Alt +空格
浏览器打开新的标签页 Ctrl + T
浏览器查看下载文件 Ctrl + J
浏览器当前页面静音 Ctrl + M
浏览器切换页面 Ctrl + 数字、 Ctrl + Tab +(Shift)、 Ctrl + Pgup 和 Pgdn
打开新的窗口 Ctrl + N
使当前窗口失去焦点,图层置于底下 Alt + Esc
打开任务管理器 Ctrl + Shift + Esc
锁屏 Windows + L
关闭文件 Ctrl + W
强制关闭文件 Alt + F4
关机 Alt + F4
打开文件资源管理器 Windows + E
将焦点定位到地址栏 Alt + D 、 F4
后退 Alt + ←、 Backspace
前进 Alt + →
返回上一级 Alt + ↑
在窗格、功能区域之间切换 F6
重命名 F2
切换至下一个,配合 F2 有奇效 Tab
有些快捷键再加上此键,实现逆向选择 如 Shift + Tab
即时切换应用 按住 Alt 不要松开单击 Tab 、三指左右滑动
来回切换引用 同时按下 Alt + Tab
切换应用 Alt + Ctrl + Tab 松开,单击 Tab 选择应用后回车
刷新 F5 、 Ctrl + R 、 Ctrl + F5
同一应用不同窗口切换 Ctrl + Tab
浏览器不同窗口切换 Ctrl + Pgup Pause 、 Ctrl + Pgdn Break
创建新桌面 Ctrl + Windows + D
关闭新桌面 Ctrl + Windows + F4
查看已有桌面 Windows + Tab (再按还原)、三指上滑(下滑还原)
切换桌面 Windows + Ctrl + 左右箭头
两个应用分屏 Windows + ←、 Windows + →
放大缩小屏幕 Windows 加 +/-
浏览器放大缩小 Ctrl 加 +/-
打开/关闭管理通知 Windows + A
打开 Windows 设置 Windows + i
锁定、切换用户、注销、打开任务管理器 Ctrl + Alt + Del

截屏

1)快捷键打开截全屏的工具

Windows + W

2)区域截屏

Windows + Shift + S

若点击了截图,照片会在 C:\\Users\\你的用户名\\AppData\\Local\\Packages\\Microsoft.ScreenSketch_8wekyb3d8bbwe\\TempState 下保存。

3)截全屏

Windows + PrtScSysRq

PrtScSysRq 键在 F12 右边,保存的图片在 C:\\Users\\自己的用户名\\Pictures\\Screenshots 下。

PrtScSysRq

打开画图工具,Ctrl + V 将截屏捕捉。

4)截屏,范围限制在当前工作页面(得到焦点的页面)

解释,比如你打开了 QQ,正在使用它聊天,按下了这个快捷键,只会截 QQ 界面。

Windows + Alt + PytScSysRq

5)录屏,范围限制在当前工作页面

Windows + Alt + R

捕获的图片和视频在 C:\\Users\\用户名\\Videos\\Captures 里。

6)游戏栏工具

Windows + G

Alt

打开软件,按一下 Alt ,会有字符提示,并且将焦点定位到菜单栏,此时可按左右键切换功能区,上下(不行就 Enter 回车)键打开功能区。

字符提示会显示出功能的快捷键,例:文件功能区会显示出 F ,代表只需要 Alt + F 就可以打开此功能,弹出的下拉列表一般也有字母提示,按下对应字母即可。(有时需同时按下 Alt )

总结: Alt + 划下划线的字母,可以快速打开菜单。

快速启动任务栏上的软件

你看看你的任务栏(屏幕的最下方),打开应用后可以右击任务栏上的应用图标,将其固定到任务栏,下次直接单击此图标就可以打开应用。或者 Windows键 + 数字,比如

浏览器在任务栏的第 1 个位置,只需 Windows + 1 就可以打开。或者 Windows + T,左右箭头选择后回车。

1.3.5 给初学者的一点建议

我希望你能后来居上,分享你的经验让我开开眼界。

下面是本人经过大量时间总结的经验,但你可以不看,因为没有什么规则可以量身定制。

1、约束好奇心,停止收藏资源

我直接说,你是学到死都学不完的。

资源无穷尽也。不要看到什么都想学,什么都想买,一直在搜,知识太多,你学不过来的。你先在网上搜索,比如后端的整套知识点,按顺序来,一心一意,一套教程为主,有疑惑的再去别的地方比对。不要没事就搜索一大堆资料收藏,又不开始,资料一定要精简,那些看上去有用的知识但短期用不上的,直接取消收藏(也包括我)。等你到了这步,再找资源,别做松鼠。

关于难度高的,可以先跳过,记录下来,后面再补上,比如数据结构、JVM 底层、多线程与高并发、GUI 等等...别把初学时间浪费在这上面,这些东西都是唬人放弃的。

不要背代码,要掌握原理,方法可以到 API 文档上查。之前基本都是概念上的东西,理解就好了,但学到集合、IO、常用类如 String,只背方法名一定会消灭你学习的兴趣。

这东西是一回生二回熟,第一遍不要想着要弄的多明白,简单过一下即可。下次复习的时候,无师自通,我敢说人就是最强的智能机器人。

至于看视频还是阅读书籍,我感悟如下:

视频时间看完所需时间更长,但可以一步步来,如果章节内容划分合理,每个视频时长合适,学习者更轻松完成每个目标,掌握自己的进度。适合初学者。

书籍花费时间较少,但经常有断节之处,初学者搞不明白缺少哪一个环节,看着晦涩难懂的文字,翻不了几页就草草放弃,适合耐得住性子的人。

2、管理好精力

人一天能够利用的时间极其有限,所以要学会合理分配精力。学习并不是生活的全部,要参与家务,如果你整天扑到学习上,只会感动自己。随着精力的消耗,自控力的减弱,一定会在某天突然崩掉,开始变得什么都没有兴趣,行动力拉到最低,即使强迫在学,也只是枯坐原地,享受着左耳进右耳出的枯燥感。

早起别熬夜,每天一定留至少 30 分钟的时间午睡,为了第 2 天的精神良好。学习时推荐使用番茄钟,每过半小时就休息一小会,合理分配精力。(休息时不要看电子产品与动脑筋,要适当远眺)

3、学会分享

不要闭门造车,要敢于发在各大网站上分享,Markdown 语法我会在下节中讲到,你学会了,注册个号,把笔记直接复制粘贴,发在博客网上;不要觉得自己技术菜。理由如下:

  1. 人人都有开始,你发出来了,别人才能纠正你的错误,避免成为井底之蛙。

  2. 你又开始自耗了,不停地在脑海天人交战,一顿脑补:会不会万一发出来被人嘲笑怎么办?...于是藏着掖着,生怕别人知道,出丑;事情还未做,就想一系列的后果,别人有多可恶。我觉得世上还是好心人多,当然不要被零星几个恶评就忽略了大多数支持你的人,这么大的林子总会有几个脑残。

  3. 百分之 99.9 的人根本刷不到你的文章,或者一下就 × 过去了,即使刷到了,那还有 9.9 成人浏览刷的一下就关了,评论的那是少之又少,凤毛麟角。

  4. 对于四处搜索资源的人,是没有精力留下痕迹的,没有用的直接毙掉,有用的看完理解后也直接毙掉;这么快节奏的生活,没人关心你是谁,最多充当局外客。当你还在纠结别人的看法时,纠结要不要做时,别人根本记都不记得你。我们在网络时代那是十分渺小,沧海一粟,很快被淹没,就算有弄潮儿掀起了几朵浪花,也会很快平息,所以别再纠结丢不丢脸了。

  5. 帮助别人就是帮助自己。虽然这个地球少了谁都能转动,但我愿意相信天生我材必有用,自己的不可替代性。

  6. 你已经落后别人太多了,得到的都不是最新的知识;不要求自己跑的更快,但是也要有行动的勇气吧,就算有 1000 个人从众笑你、反对你,我也不会笑你,只会支持你,为你的敢于行动喝彩。

  7. 或者你担心教会了徒弟饿死了师傅,认为自己凭什么白白让别人分享自己的辛苦所得,让那些白嫖怪轻而易举获取到所有。

    呃,我也不反对你,曾经的我认为那些动不动说要分享,文章标题、内容弄得很吸引人;结果在文章底部放了个二维码,说要关注才能获取验证码与资源,我一向嗤之以鼻,这不就是引流吗,能赚多少钱啊?但是现在觉得别人有权力这么做,没有人必须无偿满足你,既然付不了金钱,就只能搭进你的时间和人脉。

    对于那些开源的人,要不是他们的分享,让我窥见一斑,那我现在应该不在家,而是进厂做流水线去了,是不会发文章的,或许抱怨这个世界不公平。所以再次感谢开源的人,感谢你们的无私奉献,你们就是一道光,照亮被淘汰看不见希望的我,至于我的信念就从此转变为萤火虫也可与日月争辉。(不禁想起了一句话:为众人抱薪者,不可使其冻毙于风雪;为大众谋福利者,不可使其孤军奋战;为自由开路者,不可使其困顿于荆棘)很抱歉我现在捉襟见肘,无法回馈你们。

    说实话,现在当我使用手机、电脑、冰箱、自来水等一切产品时,都会惴惴不安,一是因为怕现在习惯了离不开了,但是不了解底层原理,要是他们以后技术垄断,坐地起价...;二是当我还在感叹新技术新发展新气象,日新月异,这些东西如此方便快捷,被制作被运送,傻瓜都能轻松使用。但是到底以牺牲了多少人的自由空暇为代价,令他们如同机械一般麻木,我们却理所当然,嘻嘻哈哈,嘲笑着都是因为不努力,教导下一辈不要学他们,要成为人上人...扪心自问,真的能够心安理得使用吗?平等或许只是伪命题。

4、承认自己的不完美

人无完人,总有顾及不到,犯错的时候。想做就直接放手干,不要计划地那么周全,稍微一有变动,就放弃目标,想明天再做。是吧,别觉得只有从容不迫、不慌不忙、按部就班地才叫做事,而达不到自己的心理预期就不做了,大多数人都是计划赶不上变化,被搞得灰头土脸的。

5、调整心态(找准目标)

如果你学这个只是为了找工作,那么你一定会很痛苦。为了以后不再工作而工作,为了以后享福而选择现在吃苦(为了不再吃苦而吃苦),为了将来而牺牲现在;这种矛盾的做法,并且大部分人实现不了财务自由,如果你只靠出卖自己的时间换来金钱的话。

你想得又得不到,或追赶不上前面的事物,你会处于十分焦虑的状态。你真想财务自由,那么就要垄断其他人的时间,给自己带来利益(用金钱换他们的时间,创造价值收入囊中),或者搭上顺风车。

但我不一样,我写这个,并不奢求得到回报,因为我写这个同时,我就很满足了。

但也不要担忧,如果企业家全都使用机器人或者雇很少的人,短时间的确能够让利润增加,因为减少了给员工那一项的支出;但是员工既是生产者也是消费者,如果他们没有时间买或者没有钱买,就算降低商品的价格,但为了有利润可赚,员工的工资也会降,那就更没有钱买,继续降低商品价格...如此反复,就倒闭了;所以他们是不会让穷人更穷的。

(我只是安慰你,这些玩意早被研究透了,说不定命运都被安排的明明白白)

6、相信自己

可你也与我有着同一样的感受,为什么这些大佬如此牛逼,而自己什么都看不懂,不能理解,沮丧睡不着觉。我觉得这不是你的问题,而是每个人的必经之路。希望你能够坚持下来,最后祝你学业有成,找个好饭碗。

7、选择城市

之前聊天时,问到了这个问题:去大城市还是小城市发展?然后又说小城市容不下灵魂、大城市容不下肉身。其实我都没有工作,村里人都知道我是游民,我给不了什么实质性的意见。

哈哈,没关系,任何一个选择都有得有失,你看看代价与风险是否自己能够承受:是接受安稳,被人看扁;还是避开喧嚣之地,舍命一拼,打下一片未来;就看你怎么选了。

8、没有绝对正确的理论

给出一个貌似符合常理的结论,有人举出反例,然后结论被推翻,再重新给出符合此反例的新的结论,不停周而复始...

如果这个结论目前还未被推翻,就暂时当作正确的吧,不需要怀疑一切,那样会很累的。

1.4 Typora 软件使用

简洁、功能强大、实时预览的 md 编辑器。

1.4.1 下载与安装

下载地址:https://www.typora.io

往下翻,点击 Windows(我的是 Windows 系统 64 位),点击 Donwload Beta(x64),就会自动下载;

双击下载好的 .exe,

指定安装位置,

下一步 next,

选中复选框代表创建桌面快捷方式。

1.4.2 如何创建 Markdown 文件

创建以 .md 结尾的文件,如 test.md,鼠标右键新建/文本文档,双击打开发现怎么还是记事本?

打开文件资源管理器,把文件扩展名和隐藏的项目打上对勾,就可以看到隐藏的后缀和文件夹如 AppData;

此时刚刚创建的文件显露真身,原来还是 txt 文件;右键重命名,把 .txt 去掉,再双击点进去。

1.4.3 常用的 Markdown 语法

此软件使用的是 Markdown 语法,一种纯文本格式的轻量级标记语言。通过简单的标记语法,它可以使普通文本内容具有一定的格式。

md 语法的出现不是为了替代 html,而是更方便书写。如果让我写那么多的 <>,不停思索如何嵌套,写一大堆长长的 css,我会疯掉的。

这款软件它可以你更加关注内容,而不是注意怎么摆弄格式。Word 是边写边在上面的边栏调字体大小、颜色、格式,很不方便。

内容如下:

  • 字体格式:如标题、斜体、加粗
  • 跳转链接:如超链接与图片
  • 内容排列:如表格、有序无序列表、各种图表、分割线

标题

一个 # + 空格,跟上标题内容即可。(Ctrl + 1 是一级标题,以此类推)

# 我是什么人
## 我是第2种人

一个 # 是一级标题,两个 # 是二级标题,依此类推,直到 6 级标题。(越来越小)

复选框

一个减号 + 空格 + [ ] + 空格

- [ ] 锻炼

粗、斜、斜加粗等字体

1、被两个星号包裹的字自动变粗(Ctrl + B);

**我是粗**

我是粗

2、被一个星号包裹的字是斜体(Ctrl + i);

*我是斜*

我是斜

3、被 3 个星号包裹的字是粗斜;

***粗斜体***

粗斜体

4、Tab 上面有一个键,按住 Shift,再按住它,可以打出 ~。

~~废弃~~

废弃

引用

大于号 + 空格 + 内容

> 摘抄自大文豪张三的一句话

摘抄自大文豪张三的一句话

键盘风格

被成对的 kbd 标签的内容。

<kbd>ctrl</kbd>

ctrl+c是复制

超链接和图片

这些地址就是网址。

![图片名](图片地址)
[超链接名](超链接地址)

百度(右击打开链接或按住 Ctrl,再单击此链接)

表格

Ctrl + T 快捷键创建表格,Ctrl + Enter 添加新行,Shift + Ctrl + Del 删除一行。

名称|性别|生日
--|--|--
赵三|男|1213.2.1
名称 性别 生日
赵三 1213.2.1

代码

单行使用`,多行三个`,指定代码语言:

`单行代码`
```java
多行代码
```
```javascript
//实际写法var foo = \'bar\';
```

单行代码

多行代码
//实际写法var foo = \'bar\';

列表

1、无序列表

单个星号 + 空格 + 内容

* 语文
* 数学
* 英语
  • 语文
  • 数学
  • 英语

2、有序列表

数字. + 空格 + 内容

1. 起床
2. 刷牙
3. 吃饭
  1. 起床
  2. 刷牙
  3. 吃饭

3、列表套列表

* 起床  
    * 睁开眼睛  
    * 我是沙福林
* 刷牙
  • 起床
    • 睁开眼睛
    • 我是沙福林
  • 刷牙

大纲

可以把标题列出来,即 # 后的内容。

[TOC]

分割线

三个减号+回车(Enter)


以下内容不需要了解,几乎不用。

甘特图

```mermaid
gantt
        dateFormat  YYYY-MM-DD
        title 这是标题,上面是日期格式年月日
        section 计划表
        准备行李(已完成)               :done,    des1, 2014-01-06,2014-01-08
        买机票(进行中)               :active,  des2, 2014-01-09, 3d
        拍照(计划)               :         des3, after des2, 5d
```

gantt
dateFormat YYYY-MM-DD
title 这是标题,上面是日期格式年月日
section 计划表
准备行李(已完成) :done, des1, 2014-01-06,2014-01-08
买机票(进行中) :active, des2, 2014-01-09, 3d
拍照(计划) : des3, after des2, 5d

UML图表

```mermaid
sequenceDiagram
张三->> 李四: 你好!李四, 最近怎么样?
李四-->>王五: 你最近怎么样,王五?
李四--x 张三: 我很好,谢谢!
李四-x 王五: 我很好,谢谢!

李四-->>张三: 打量着王五...
张三->>王五: 很好... 王五, 你怎么样?
王五->>张三: 不好
```

sequenceDiagram
张三->> 李四: 你好!李四, 最近怎么样?
李四-->>王五: 你最近怎么样,王五?
李四--x 张三: 我很好,谢谢!
李四-x 王五: 我很好,谢谢!

李四-->>张三: 打量着王五...
张三->>王五: 很好... 王五, 你怎么样?
王五->>张三: 不好

流程图

```mermaid
graph LR
A[长方形] -- 链接 --> B((圆))
A --> C(圆角长方形)
B --> D{菱形}
C --> D
```

graph LR
A[长方形] -- 链接 --> B((圆))
A --> C(圆角长方形)
B --> D{菱形}
C --> D

1.4.4 常用的设置

内容如下:

  • 常用的快捷键:如加粗、标题、表格等
  • 更改风格:如字体颜色、样式,显示代码行号
  • 将笔记导入导出
  • 支持数学符号

因为都是中文,只讲一点点,剩下的自己在菜单栏上研究吧。

快捷键

直接点击菜单栏,查看快捷方式,如点击视图、段落,上面显示了快捷方式更方便触发;或者按住 Alt + O,即可打开格式:如 Ctrl + B 是加粗的快捷键,Ctrl + \\ 是清除样式,Ctrl + T 是创建表格的快捷方式,Ctrl + / 是查看源代码,Windows + 句号是表情符号、视图放大、缩小...

段落\\YAML Front Matter(前言),因为我暂时用不上,看看其他人写的文章吧:

https://zhuanlan.zhihu.com/p/78087948?from_voters_page=true

https://zhuanlan.zhihu.com/p/370113792

https://blog.csdn.net/weixin_46037781/article/details/118759174

https://blog.csdn.net/qq_43444349/article/details/105282118

主题

Alt + T 选择自己想要的主题。

偏好设置

Ctrl + ,

如通用的自动保存选上,每次修改自动保存,不用按下 Ctrl + S。

高级模式选中调试模式,关闭文件后重新打开,右键检查元素(或 Shift + F12),会发现此软件就是一个浏览器,(浏览器按住 F12 键,会打开开发者模式,与这差不多)我们看到的笔记,实际是 md 语法被解析后生成的 HTML 标签与 CSS 相配合展现的页面;现在你再看看 md 语法到底有多方便了吧,这些标签要你自己写该多麻烦。

代码块显示行号

Markdown,将显示行号复选框勾选。

你看左边的1
你看左边的2    

更换主题

学了 CSS 后,再来。

之前创建 md 文件时,给隐藏的项目了打上对勾,代表显示隐藏的项目。其中 AppData 就是隐藏的项目,一般存放软件的配置文件。

C:\\Users\\你的用户名\\AppData\\Roaming\\Typora\\themes 下的几个 CSS 文件代表着不同的主题,如果不满意页面样式,可以修改对应主题 CSS(记得留个备份)或自定义 CSS,重新打开笔记后,可以在主题中看到你的 CSS,随便更换。

补丁:蓝色背景下的 gif 动图字体会模糊,必须在纯色红或白色背景下,gif 动图字体才会清晰,所以取消动图的使用。

将笔记转成其他类型的文件

PDF、HTML 等类型的都行,(without styles 是不带样式即没有 CSS 修饰,自己试试就知道区别了)

或者打开浏览器,使用打印功能(Ctrl + P),无渲染与解析,很差劲。

支持数学公式

按下 Ctrl + , 快捷键打开偏好设置/Markdown/Markdown 扩展语法/勾选内联公式,再用 $某字母$ 显示常用的数学符号。(当内联公式过多,可以考虑使用 $$某字母$$)

$$
\\in
$$

\\[\\in
\\]

常用的的数学符号写法:

  • 作者:DaneAI,https://blog.csdn.net/happyday_d/article/details/83715440
  • 作者:韩湘,https://blog.csdn.net/qq_38228254/article/details/78515800
  • 作者:白白旧维,https://www.cnblogs.com/1024th/p/11623258.html
  • 作者:樱花赞,https://blog.csdn.net/weixin_43444930/article/details/119791074

恢复未保存的文件

打开偏好设置,点击恢复未保存的草稿。

image-20220314195918762

1.4.5 官网打不开的原因

它升级到了 1.0 版本了!收费了。

不要惊讶,能够开源一定要有其它稳定的收入作为支撑;否则没钱,功能不会太强大,作者都快饿死了,当然没有功夫贡献自己的力量,进行频繁更新,跟上时代。

不能坚守本心的人会过的很苦:动摇,时刻怀疑自己的决定是否正确;后悔,要是当初如何...现在就...

Typora 代理商:https://typoraio.cn/

最后一个免费版,分享者:王炳明,https://wwe.lanzoui.com/i8PP3wzahrg

其实从来都没有说过免费,之前只是 Beta 测试版,类似于游戏中的公测吧,也不知道免费版什么时候就“关服”了。

作为一个即时渲染,所见即所得,按下 Ctrl + / 就可以看源码,简洁实用,早已习惯,离不开它了。其它的软件花里胡哨,什么功能都往里面加,试图想代替其它同类型的软件,结果搞得不伦不类,十分臃肿。

像其它 md 编辑器,大部分都是双栏丑丑的大屏幕,十分占空间,并且滚动内容时,左右视图不一致,还得分心同时看两边,真的是种折磨。应该让软件配合人,而不是让人记住软件的用法,主次关系要分清;真希望以后能够统一,有个规范,其它软件实现就可以了。

如果下载的是收费版,记得打开偏好设置\\通用,将 Typora 服务器使用国内服务器复选框勾选一下,怕连不上服务器,激活不了此软件。

1.4.6 自动上传图片

你如果使用久了,经常在网上发布文章,一定会遇见这个问题。

把写好的 md 文件,复制粘贴到博客园中。结果你发现图片加载不出来,一看地址,C:\\Users\\自己的用户名\\AppData\\Roaming\\Typora\\typora-user-images\\xxx。

这才知道原来图片被保存到了本地中,也难怪网上读取不到。那么你发现了,将图片直接拖拽到博客园的编辑页面,会自动生成网络上的 URL,可以在全网都能访问到。

但是时间长了,一张、一张拖拽也不是事啊。这时就需要图床替我们自动保存图片。下面介绍两种方式;

命令行上传图片

打开偏好设置\\图像,

image-20220314203029567

意思是当本地图片被你拖进 md 文件中,会自动上传图片。

点击下载或更新,下好了后会出现打开配置文件按钮,点击弹出 JSON 文件,将以下代码粘贴到这个文件中。

{
  \"picBed\": {
    \"uploader\": \"smms\",//代表当前的默认上传图床为 SM.MS
    \"smms\": {
      \"token\": \"LFJLSJljlfaoFJOLAF\"//这里面的token换成自己生成的token,一定要换
    }
  },
  \"picgoPlugins\": {}//为插件预留
}

这个 token 如何填?

打开 SM.MS 图床:https://sm.ms/register
自己注册账号后,再登陆:https://sm.ms/login

点击 User\\Dashboard\\API Token,进入:https://sm.ms/home/apitoken

点击 Generate Secret Token 按钮生成 token,将其填入 JSON 文件。

填完后,保存 JSON 文件。将图片拖拽 md 文件时,如有 Uploading 字样,就成功了。

在 Pictures 可以管理或删除这些文件,可惜根本没法根据 URL 找到图片,时间长了,定位删除图片还是不方便。

说实话我很担心,手贱全删了,导致 md 文件图片全部失效,还是放在博客园中保险些。

PicGo app 上传图片

下载地址:https://github.com/Molunerfinn/PicGo/tags

选择一个版本点击,下翻,在 Assets 中选择 exe 文件下载,安装后直接运行。

但无论怎么双击就是不会显示窗口,看看电脑右下角,点击蓝色图标:image-20220409200545365

图片上传区:图片上传 - SM.MS

图床设置\\SM.MS,填入 Token 后确定,设为默认图床。

Typora 软件,偏好\\图像,按图设置

image-20220409200916679

在 PicGo 的安装目录下有个 PicGo.exe,PicGo 路径就选择此 exe 的绝对路径。

格式\\图像\\上传所有本地图片

Typora 在 Windows 下自动上传图片

目前支持上传的方式有:自建服务器、腾讯云 COS、阿里云 OSS、七牛云 、Github、Gitee(码云)...

作者:thobian,https://www.zhihu.com/question/56641227/answer/810364545

阿里云

作者:夏2同学,https://zhuanlan.zhihu.com/p/138878534

其它图床

免费图床整理,https://withpinbox.com/explore/collection/332056

1.5 常用的 DOS 命令

内容导视:

  • 打开 DOS 窗口的几种方式

  • 进入目录内:使用绝对路径与相对路径演示

  • 常见的 DOS 命令

写出来的 Java 代码,需要在命令窗口中执行编译与运行命令,需要知道如何打开此黑窗口(Disk Operating System)。

1.5.1 打开 DOS 窗口的几种方式

  1. 屏幕最左下方,右击 Windows图标,点击 Windows PowerShell,管理员权限更高,可以修改系统关键文件。
  2. 点击 Windows 图标,往下翻在所有程序中找到 Windows 系统,点击命令提示符。(可以右键以管理员权限运行)
  3. Windows + R 输入 cmd 回车。(最常用
  4. 打开文件夹资源管理器,在地址栏输入 cmd 回车。(其次常用)
  5. 按住 Shift 别松,鼠标右击,在此处打开 PowerShell(S)。

作者:「已注销」,内容:将 Windows10 中的 WSL 添加至右键菜单,https://blog.csdn.net/gulang03/article/details/79177500

1.5.2 进入目录内:使用绝对路径与相对路径演示

下面说的目录和文件夹是一个意思。
输入命令后按下回车才能执行,回车键是 Enter。

由于不是可视化界面,不能像之前一样,点到哪里就跳到哪个文件夹下,需要使用 cd 命令,跳转到某路径下。

路径分为绝对路径相对路径

打开文件资源管理器(Windows + E),随便打开某盘下的文件夹,可以看到地址栏上的路径。假如以 D: 开头,D: 称为盘符,显示的路径称为绝对路径,D:\\ 称为 D 盘的根目录(最上一级,不能再上了)。

绝对路径:从盘符开始的路径,能够完整的描述文件位置的路径就是绝对路径。(唯一确定资源位置)
如路径 D:\\cqh_environment,指向 D 盘下的 cqh_environment 文件。

相对路径,是以当前路径作为出发点的路径,比如进入当前路径的 a 目录。

首先打开 DOS 窗口,切换盘符。

例:想要到 E 盘下,就输入 E: 后回车,输入 dir 回车查看当前路径下有那些子文件(没有就在该盘手动创建文件夹),使用 cd 文件夹名称 进入此文件夹。

使用 cd a 时,单看这个 a,鬼才知道这是哪个地方的文件夹,在哪里才能找到它,需要结合当前路径 E:\\ 才能知道,原来是要到 E 盘下去找 a,那么这个 a 就是相对路径。

可以这么理解,我说北京市某某地址的小区,你马上理解了要在哪去找,这是绝对路径;但我说我家旁边的一家店,你必须先知道我家在哪,再根据我家的位置确定这家店的位置,这就是相对路径。


..:上级目录

.:当前目录

现在回到上级目录,使用cd ..,现在演示一下绝对路径,cd 绝对路径,使用绝对路径时,必须是当前所在盘下的路径。

注意:路径必须存在,你需要把目录创建出来,才能使用 cd 命令进去。

如果使用相对路径,先切换到根目录,再执行 cd a\\b\\c\\d

怎么回到根目录?使用 cd E:\\ 吗?或者 cd ..\\..\\..\\..?

还有一种简单的方式 cd \\ 即可


一个 cd .. 是回到上级目录,
cd ..\\.. 是回到上上级目录。


若文件名太长记不住,可以打出首字母,再按下 Tab 键,也可上下键切换已经输入过的命令,或者你直接将文件拖入这个黑窗口,地址就出来了。

通过刚才的例子,发现跳转路径时,必须是当前盘下的路径,那么还不如使用相对路径,反正盘名写其它盘也没用。解决办法:加个参数 /d 即可。

例:现在在 C 盘下,我要直接到 E:\\a\\b\\c\\d,输入 cd /d E:\\a\\b\\c\\d 即可。

1.5.3 常用的 DOS 命令

内容如下:

  • 进入目录、返回上级目录
  • 打开常用的软件
  • 清空屏幕
  • 新建、删除文件
  • 查看系统变量
#切换盘符 C:
#查看当前目录下的文件 dir
#改变目录 cd /d C:\\a\\b\\c
#返回上一级 cd..
#直接回到根目录 cd \\
#进入子目录 cd 文件夹名称
#清空屏幕 cls
#退出 exit
#查看ip ipconfig
#查看ip详细 ipconfig/all

#打开软件
#打开计算器 calc
#打开绘图 mspaint
#打开记事本 notepad

#查看网络 ping www.baidu.com
#网络诊断 ping www.baidu.com -t
#终止 Ctrl + C

#新建文件夹 md 文件夹名
#新建文件 cd>文件名.后缀
#删除文件 del 文件名.后缀(可带*,*表示任意,小心别把所有东西删了)
#删除文件夹 rd 文件夹名
#打开文件 文件名.后缀

#打包
#把当前的路径下所有打包进a.jar
jar cvf a.jar .
#把当前的路径下所有打包进b.war
jar cvf b.war .
#可以使用绝对路径
jar cvf E:\\cqh\\b.war .

#查看系统变量的值,如第一个是C:\\Windows
echo %SystemRoot%
echo %ProgramFiles%
echo %Path%

#咳咳,你打开环境变量(网上搜,下节也有),左边是变量,右边是值

1.6 Joplin 使用

与其说是 Markdown 编辑器,倒不如说它是一个同步小云盘、整理目录的大师,不与 Typora 冲突。整个页面布局(左中上下):目录树、笔记列表、笔记标题、笔记正文。

由于可以设置中文,只讲一点点。

菜单栏的 Tools\\Options\\General,右边的 Language,下拉列表,选择中文(简体)。

Alt + ?打开对应的菜单栏:


F:文件
E:编辑
V:视图
G:跳转
B:笔记笔记本
N:笔记
T:工具
H:帮助


Ctrl + Q:退出。

1.6.1 下载地址

官网:https://joplinapp.org

github下载地址:https://github.com/laurent22/joplin/releases

往下翻,选择一个版本,点击 Assets 选择 exe 文件下载。

1.6.2 常用设置

导入 md 文件

文件\\导入,选择导入的是 Markdown 文件还是目录。(不要选带文章前言的,导出时会自动在文章前面加标题、创建和修改日期、时间,即 YAML Front matter)

把文件同步到本地

工具\\选项,同步,同步目标设为 File system,设置同步目录。(当然你也可以花钱存云)

给同步目标上的备份文件加密

工具\\选项,加密,启动加密;这样的话,如果把同步文件上传到云端,也不怕别人偷看。


如果删除了同步目标的文件,为了安全(故障保护默认开启),不会删除本地文件。

如果删除了本地文件,同步时间一到,同步目标的文件也会被删除。
可以趁着这时间差,从同步目标导入数据到本地。

同步\\显示高级选项,自己重新上传数据到同步目标或删除本地数据并从同步目标导入数据


如果两个终端数据不一样(同时修改一个文件,为不同内容),会把冲突的文件移到新创建的\"冲突\"笔记本中,等你处理。

使用外部 md 编辑器

使用自己的 Markdown 编辑器:工具\\选项\\通用选项,右边的文本编辑器命令,选一个能够打开 md 文件的 exe 命令,如 Typora.exe。(你还记得此软件的安装目录吗?)

在 Joplin 上任意选一个笔记,按下 Ctrl + E,自动转用外部编辑器编写文件。

自定义笔记本图标

笔记本右击\\编辑,图标\\Select emoji,选一个表情。

网页剪藏器

知识太多了,短时间内是看不完的,如果收藏,也有链接失效的风险,需要将文章内容复制下来。

工具:Chrome 浏览器。

由于插件市场打不开,点击:https://www.extfans.com/

搜索 Joplin Web Clipper,需要关注公众号,获取验证码后下载。下载后解压,里面有个 crx 文件,打开谷歌浏览器,地址栏输入:chrome://extensions/

把 crx 文件直接拖入此页面,添加扩展程序

image-20220409203712934

Joplin 软件,工具\\选项,网页剪辑器,启动网页剪辑器。

使用:

屏幕截图 2022-04-09 204022

Clip simplified page:简化后的页面

Clip complete page:完整页面

In notebook:保存在哪个笔记本

Title:文件名

同步到手机

真厉害啊!

把自己电脑当作服务器,同步到手机,教程:https://lightzhan.xyz/index.php/2020/11/15/joplin-webdav/

作者:lightzhan,密码:h3mu,WebDAV 小秘:https://lightzhan.lanzoui.com/b015wjsri

屏幕截图 2022-04-09 205026

打开 DOS 窗口,输入 ipconfig 命令,IPv4 地址对应的就是主机地址;用户名与密码自己设置。

无线局域网适配器 WLAN:

   连接特定的 DNS 后缀 . . . . . . . :
   本地链接 IPv6 地址. . . . . . . . : fe10::209
   IPv4 地址 . . . . . . . . . . . . : 192.168.1.1
   子网掩码  . . . . . . . . . . . . : 289.2.52.2
   默认网关. . . . . . . . . . . . . : fu81::22l

手机上自己搜索 Joplin app 吧,因为谷歌你们可能访问不了,我总不能把如何FQ写在这吧。

无标题

image-20220409210520668

WebDAV URL:http://主机地址:端口号

填入自己电脑上设置的用户名与密码,然后检查同步配置,如果显示“成功!同步配置看起来没问题。”就 OK。

如果成功后依然没有开始同步,一直转圈,退出软件重新进入。

其他人的使用心得

其他人的 Joplin 软件使用心得:
https://www.zhihu.com/question/436251626/answer/1909305492
https://segmentfault.com/a/1190000038918737

打开扩展语法,在工具\\选项\\Markdown,根据需要勾选,详细请看:https://lightzhan.xyz/index.php/2020/03/31/joplin-extension-usage/

如勾选启用typographer支持,让我试下:©(c)著作权所有人

1.7 MarkText 使用

是 Typora 的同类型产品,开源免费。

下载地址:https://marktext.app/

有时候可能打不开,https://github.com/marktext/marktext/tags

选择版本后,Assets 下点击 exe 文件进行下载。

由于作者计划 v1.0.0 以后支持其它语言(国际化),英语不好的,可以搜下汉化版。

如果 git 下载太慢:(慎用!!!)

作者:紫月java,https://www.cnblogs.com/ziyue7575/p/14157952.html

  • 安装用户脚本管理器:https://greasyfork.org/zh-CN/help/installing-user-scripts

  • 加速脚本:https://greasyfork.org/zh-CN/scripts/397419-fastgithub-镜像加速访问-克隆和下载

    屏幕截图 2022-04-09 221050

    将 js 文件拖入其中,刷新页面,效果如图:

image-20220410081922641

随便点击,开始下载吧。

1.7.1 编辑区域

查看源代码:Ctrl + E

整体布局

正上方是 md 文件的绝对路径;

左上角 A 是总字符个数;点击可以切换。

  • Words:单词个数(一个汉字算一个单词)
  • Characters:可见字符个数
  • Paragraphs:段落个数

右键标签栏

Close:关闭文件(未 Ctrl + S 保存会弹出提示)

Close others:关闭其它文件

Close saved tabs:关闭已保存的文件

Close all tabs:关闭所有文件

Rename:重命名文件

Copy path:复制文件的绝对路径

Show in folder:打开文件所在目录

段落快捷操作

当你每次移动光标时,当前段落对应的图标(最左边)就会显示,点击

image-20220410083416579

Duplicate:向下复制当前段落

Turn Into:变成?

  • Paragraph(Ctrl + 0):段落
  • Header 1(Ctrl + 1):h1 标题
  • Display Math(Alt + Ctrl + M):内联公式块
  • HTML Block(Alt + Ctrl + J):div 标签
  • Code Block(Alt + Ctrl + C):代码块
  • Quote Block(Alt + Ctrl + Q):引用
  • Order List(Alt + Ctrl + O):有序列表
  • Bullet List(Alt + Ctrl + U):无序列表
  • To-do List(Alt + Ctrl + X):任务复选框

New Paragraph:创建新的段落

Delete:删除当前段落

选中文字

从左至右依次是:粗(Ctrl + B)、斜(Ctrl + I)、下划线(Ctrl + U)、删除线(Ctrl + D)、高亮(Ctrl + H)、代码块(Ctrl + `)、内联公式(Ctrl + M)、超链接(Ctrl + L)、插入图片(Ctrl + Shift + I)、清除格式(Ctrl + Shift + R)

插入表情

Windows + 句号,或者 :a:,两个冒号跟一个字母

1.7.2 偏好设置

Ctrl + 逗号

General

自动保存

Auto Save:

  • Automatically save document changes:更改时自动保存
  • Delay following document edit before automatically saving:延迟 ?ms 后保存

更改菜单栏位置

Window:

Title bar style:

  • Custom:左上角
  • Native:正上方

Editor

字体样式

Text editor settings:文本编辑设置

  • Font size:字体大小
  • Line height:行距
  • Font family:字体样式
  • Maximum width of text editor:编辑器最大宽度,单位有:ch、px、%,例:输入 100px;输入错误会报红。

Markdown

支持上下标

Markdown extensions:扩展语法

启用 Enable Pandoc-style superscript and subscript

  • 5^3^:53
  • 5~3~:53

Spelling

Enable spell checker:开启拼写检查

Default language for spell checker:检查器的默认语言

Key Binding

更改操作的快捷键方式

image-20220410100837047

修改,重置,删除

每次修改后,需要翻到最下面保存

  • Save:保存
  • Restore default key bindings:恢复默认快捷键

1.7.3 图片上传

偏好设置/Image,

image-20220410103032577

Default action after an image is inserted from local folder or clipboard:插入图片后的操作(只支持 macOS 和 Windows 系统)

  • Keep original location:保存在原位置

  • Copy image to designated relative assets or global local folder:复制图片至指定位置(绝对或相对路径)

  • Upload image to cloud using selected uploader(must be configured below):上传至云端

Global or relative image folder:指定图片保存的绝对路径

Prefer relative assets folder:...相对路径,开启后,图片会放在与 md 文件的同级目录下

  • Relative image folder name:目录名称,即图片会放在此目录下

说一下历程吧。

之前不是使用 Typora 软件时下载了 PicGo app 嘛,但是提示检查不出来,说不存在;试着使用命令行方式吧(github 方式将来会废弃),下拉列表切换为 Command line script,提示要安装 PicGo-Core,于是先安装 Node.js,打开 DOS 窗口,输入 npm install picgo -g 命令,安装位置:C:\\Users\\自己的用户名\\AppData\\Roaming\\npm\\node_modules/

C:\\Users\\你的用户名\\.picgo 下创建了 config.json 文件,复制了如下:

{
  \"picBed\": {
    \"uploader\": \"smms\", // 代表当前的默认上传图床为 SM.MS,
    \"smms\": {
      \"token\": \"\" // 从 https://sm.ms/home/apitoken 获取的 token
    }
  },
  \"picgoPlugins\": {} // 为插件预留
}

PicGo-Core 指南:https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#自动生成

用法:https://github.com/PicGo/PicGo-Core

如上传文件:picgo upload 文件的绝对路径

于是学了一下 Shell 脚本的语法,提示说输入的唯一参数是图片路径,要求输出图片的 url 路径;于是上网搜,如何接收第一个参数、输出,分别是 $1、echo;尝试过程中发现路径必须以 / 分隔,而不是 \\

export LANG=\"en_US.UTF-8\"

str=$(picgo upload $1)
img=${str#*\"[PicGo SUCCESS]: \"}
echo $img

测试中发现运行不了,需要环境,接着下载了 git,打开 bash 窗口使用 ./xxx.sh 命令运行此文件。

正以为大功告成,可 MarkText 保存不了此 Shell 脚本的绝对路径。没有办法,下载了 v0.16.3 版本覆盖安装,偏好设置里 Image 下多了一项 Image Uploader

image-20220410110024054

选择,点击 Set as default,默认图片上传至 sm.ms。

然后下载新版本,再覆盖安装一次,打开

eBxUcITybHdiZ39png

下拉框改为 Picgo 即可。

拖入图片到窗口时,必须拖到当前段落(会显示一条绿线),才会成功。

image-20220410111435947

点击此图片,可以修改图片(查看是否为网络 URL)以及位置。

脚本:解释性语言,且是文本格式。

1.7.4 File

英语不好,又没有安装汉化版的...下面是菜单栏的各项,先从 File 开始:

New Tab(Ctrl + T):新建文件

New Window(Ctrl + N):生成新窗口

Open File(Ctrl + O):打开文件(可以直接将md 文件拖入此窗口)

Open Folder(Ctrl + Shift + O):打开目录(显示目录下的 md 文件、查找、显示文章中的标题)

Open Recent:打开最近文件

  • Clear Recently Used:清空最近文件(只是清除记录)

Save(Ctrl + S):保存

Save As(Ctrl + Shift + S):另存

Auto Save:退出文件前自动保存

Move To:将文件移动至...

Rename:重命名

Export:导出为

HTML

  • Page:设置页面标题(title 标签里的文本)

  • Style

    • Overwrite theme font settings:更改字体样式、大小、行距

    • Auto numbering headings:自动为 h1 以下级别的标题编号

    • Show front matter:显示前言

  • Theme:选择主题

  • Table of Contents:目录列表

PDF

  • Page

    • Page size:设置纸张大小

    • Landscape orientation:是否横向

    • Page margin:页边距(毫米)

      • Top/Bottom:上下边距

      • Left/Right:左右边距

    • Header & Footer:页眉页脚

      • Header type:页眉类型

        • Single cell:单(中间)

        • None:无

        • Three cells:三(左中右)

      • Customize style:定制风格

        • Header and footer font size:页眉页脚字体大小
  • Preferences(Ctrl + 逗号):偏好设置

  • Close Tab(Ctrl + W):关闭文件

  • Close Window(Ctrl + Shift + W):关闭窗口

  • Quit(Ctrl + Q):关闭所有窗口

导出时新增主题:https://github.com/marktext/marktext/blob/develop/docs/EXPORT_THEMES.md

1.7.5 Edit

Undo(Ctrl + Z):撤销

Redo(Ctrl + Shift + Z):反撤销

Cut(Ctrl + X):剪切

Copy(Ctrl + C):复制

Paste(Ctrl + V):粘贴

Copy as Markdown(Ctrl + Shift + C):以 Markdown 形式复制

Copy as HTML:以 HTML 标签形式复制

Paste as Plain Text(Ctrl + Shift + V):纯文本粘贴

Select All(Ctrl + A):选中所有

Duplicate(Alt + Ctrl + D):向下复制一行

Create Paragraph(Ctrl + Shift + N):创建新的段落

Delete Paragraph(Ctrl + Shift + D):删除当前段落

Find(Ctrl + F):查找

  • Case Sensitive:区分大小写
  • Select whole word:选择整个单词,如查找 a,不会出现包含 a 的单词如 puba,而是只有单独的 a。
  • Use query as RegEx:使用正则表达式
  • Find Next(F3):往下找
  • Find Previous(Shift + F3):往上找

Replace(Ctrl + R):替换所有、一个个替换

Find in Folder(Ctrl + Shift + F):在目录下的文件中查找(首先得 Open Folder 打开目录)

Line Ending:换行方式

  • Carriage return and line feed(CRLF):Windows 换行方式,即 \\r\\n

  • Line feed(LF):Linux换行方式,即 \\n

1.7.6 Paragraph

Heading 1、2、...、6 为 H1、H2、...、H6 标题

Promote Heading(Ctrl + Shift + =、Ctrl + 加号):提升标题等级

Demote Heading(Ctrl + -):降低标题等级

Table(Ctrl + Shift + T):创建表格

Code Fences(Ctrl + Shift + K):代码块

Quote Block(Ctrl + Shift + Q):引用

Math Block(Alt + Ctrl + N):内联公式块

HTML Block(Alt + Ctrl + H):HTML 标签

Ordered List(Ctrl + G):有序列表

Bullet List(Ctrl + H):无序列表

Task List(Alt + Ctrl + X):复选框

Loose List Item(Alt + Ctrl + L):增加列表之间的距离

Paragraph(Ctrl + Shift + 0):段落

Horizontal Rule(Ctrl + Shift + U):分隔线

Front Matter(Alt + Ctrl + Y):前言

1.7.7 Format

Bold(Ctrl + B):粗体

Italic(Ctrl + I):斜体

Underline(Ctrl + U):下划线

Superscript:上标

Subscript:下标

Highlight(Ctrl + Shift + H): 黄色高亮

Inline Code(Ctrl + `):单行代码块

Inline Math(Ctrl + Shift + M):单行内联公式

Strikethrough(Ctrl + D):删除线

Hyperlink(Ctrl + L):超链接

Image(Ctrl + Shift + I):图片链接

Clear Formatting(Ctrl + Shift + R):清除格式

1.7.8 Window

Minimize(Ctrl + M):窗口最小化

Always on Top:总是显示在最上层

Zoom In:放大

Zoom Out:缩小

Show in Full Screen(F11):全屏显示

1.7.9 View

Command Palette(Ctrl + Shift + P):查看操作的快捷方式

Source Code Mode(Ctrl + E):查看源代码

Typewriter Mode(Ctrl + Shift + G):打字机模式,保证光标在屏幕中间

Focus Mode(Ctrl + Shift + J):焦点模式,只关注当前输入行

Show Sidebar(Ctrl + J):显示侧边栏(目录树、查找、文章标题)

Show Tab Bar(Ctrl + Shift + B):显示标签栏(md 文件名)

Toggle Table of Contents(Ctrl + K):跳转至对应的 H 标题位置(点击左边侧边栏显示的标题)

Reload Images(F5):重新加载图片

1.7.a Help

Quick Start:快速开始

Markdown Reference:Markdown 语法

Changelog:更新日志

Donate via Open Collective:赞助

Feedback via Twitter:反馈(使用推特)

Report Issue or Request Feature:报告问题或请求功能

Website:网站

Watch on GitHub:查看 Github 项目

Follow us on GitHub:关注我们(GitHub)

Follow us on Twitter:关注我们(Twitter)

License:许可证

Check for updates:检查更新

About MarkText:关于 MarkText

1.x 总结回顾

(不要惊讶怎么这么少,我说过别把大脑当成硬盘,记一个 cd 命令就足够了,记得看完后休息一会,欲速则不达)

Java 介绍与下载

Java是最早由 SUN 公司的 James Gosling 开发的编程语言。

SUN 在 2009 年被 Oracle 收购;2014 年 3 月 19 日发布了 JDK8。

常用的 DOS 命令

cd 当前目录下的文件夹名 进入目录

1.y 脑海练习

1.1 JDK 和 JRE 的区别?

1.2 什么是编程?

1.3 Swing 组件是什么?

1.4 JAR 包是什么?

1.5 软件与程序指的是什么?

1.6 什么是编译型语言和解释型语言?

1.7 Java 语言特性?

1.8 能说下 JavaSE、JavaEE、JavaME 吗?

第二章 编写第一个程序

内容导视:

  • 编写源代码并运行
  • 代码书写规范
  • 注释

2.1 编写源代码并运行

内容导视:

  • 创建 .java 文件编写源码
  • 配置环境变量
  • 查看命令使用帮助
  • 解释代码含义
  • 编译与运行

2.1.1 创建 .java 文件编写源码

我们要开始写源代码啦!但只是在 DOS 窗口中输出一句话而已...

创建以 .java 结尾的文件,如 Hello.java。(以后统称为后缀或扩展名,打开文件资源管理器,查看,勾上文件扩展名)文件右击选择打开一种打开方式。(EditPlus 或其他文本编辑器,找不到选择其它应用,然后选中“始终使用此应用打开此类型的文件”复选框)这样你以后双击 .java 文件就可以直接使用此软件打开啦。

双击此文件,输入如下。还是直接复制、粘贴吧,记得保存。(Ctrl + S)

public class Hello {
	public static void main(String[] args) {
		System.out.println(\"hello world!\");
	}
}

打开 DOS 窗口,使用 cd 命令切换到此文件的所在目录

(如果你在桌面上创建的文件,路径是 C:\\Users\\自己的用户名\\Desktop,你可以自己打开文件资源管理器,点击左边的桌面图标,在地址栏上输入 cmd 后回车)

屏幕截图 2022-03-22 113317

在 DOS 窗口中输入 javac Hello.java,得到如下诡异的结果。

\'javac\' 不是内部或外部命令,也不是可运行的程序
或批处理文件。

你可能会疑惑?怎么没有找到这个命令,不是在 JDK 的安装路径\\bin 目录下有 javac.exe 命令吗?你说找不到?那为什么之前 DOS 窗口查看 IP 时,也就是C:\\Windows\\System32下的 ipconfig 命令就可以执行?

此电脑右键/属性/高级系统设置/环境变量/系统变量/Path 下看到了 %SystemRoot%\\system32,即 C:\\Windows\\System32

(如果桌面上没有此电脑图标)

  • 打开文件资源管理器,此电脑的图标在左边。
  • 或者 Windows + i打开设置,点击系统/关于,往下翻,相关设置/高级系统设置。
  • 右键/个性化/主题/桌面图标设置,勾选计算机图标后点击应用和确定。

猜想:难道系统只会在 Path 指定的路径下寻找命令吗?我随手就删了它(别试,否则手动还原),确定保存刚刚的修改操作,重新打开 DOS 窗口,再次执行 ipconfig,很明白了。

得出结论:执行的程序如果在当前目录下不存在,系统会在名为 Path 的环境变量指定的目录下查找

好的,大概明白了,但我的电脑上有多个用户,那么为了不影响其他用户,我只配置用户变量的 Path,优先级当然没有系统变量的高。(系统变量针对所有用户生效)

2.1.2 配置环境变量

上节知道了 Path 变量的作用,现在该把命令地址添加到 Path 中了。

双击 Path,添加 javac 命令所在路径。(如果你还记得 JDK 安装的位置,打开 bin 目录,复制地址栏上的地址)

如果这个 JDK 安装目录,以后会重复使用(不用怀疑,肯定了)。为了方便,新建变量 JAVA_HOME 保存 JDK 安装目录,供其它地方使用。

再把之前的 javac 命令所在路径替换成 %JAVA_HOME%\\bin

%JAVA_HOME% 代表变量 JAVA_HOME 对应的值。以后 JavaEE、开发工具需要获取 JDK 的安装目录,这样就不需要再手动设置了。

有的人可能版本较老,Path 变量值在一行文本框上显示。那么添加路径时,注意路径与路径之前使用;分隔,注意这个分号是英文状态下的半角符号,你按下 Shift,看看电脑右下角是否中英在切换。写成就错了。以后如果不说明,统一使用英文状态下的符号。

例:

检查自己是否配置好了:

打开 DOS 窗口,输入 java -version 和 javac 等命令
如果显示版本信息,证明 java.exe 是可以用的

再输入 echo %Path%
查看刚刚配置的 JDK的家\\bin 路径是否在其中

有的人可能有疑惑,明明自己没配环境变量,为什么 java.exe 可以使用?

答:使用 exe 文件安装 JDK 后,会自动在系统变量 Path 添加如下图路径:

C:\\Program Files (x86)\\Common Files\\Oracle\\Java\\javapath

所以没有手动配置环境变量时,java 命令可以用,但 javac 不行。

每次改动,都需要重新打开 DOS 窗口,配置才能生效。输入 javac Hello.java,如果你的语法正确,就会在当前路径下生成 class(字节码)文件,再输入 java Hello ,此时别带后缀。

有人就说了,每次运行 class 文件,都要使用 cd 命令进入此目录,再执行命令很麻烦。那么可以通过 classpath 变量指定字节码文件所在位置。

新建环境变量 classpath:

.是英文输入的句号,代表当前路径,如果不写就不会从当前路径寻找 class 文件。
由于我习惯把 class 文件放在桌面上,于是新增了桌面的路径,路径和路径直接使用;分隔,别使用中文的分号

这样不管在任何地方,输入 java Hello 就可以执行了。

总结

Path 是 Windows 查找 .exe 文件的路径;classpath 是 JVM 查找 .class 文件的路径;如果你以后学会了如何打 jar 包,假如它的绝对路径为 E:\\a\\c\\d\\Tool.jar,如果想任何位置敲 java -jar Tool.jar 让 jar 包执行,就可以把 E:\\a\\c\\d\\Tool.jar 加到 classpath 变量值里。

2.1.3 查看命令使用帮助

也许你总是会忘记,此命令应该携带什么参数...

想要执行的命令 -help,例举了可加的参数,如 -encoding。(对不起,我要给你埋坑了,如果你事先不知道此参数的作用...)

javadoc -helpjavac -help。(其实不加 -help 也行)

看,这不就出来了:

image-20220322120929241

2.1.4 解释代码含义

看不懂很正常,学完面向对象就懂了。突然遇到看不懂的不要怀疑自己智力有问题,只是还没到这步而已,大不了直接跳过。

public class Hello {
    // args 是变量名,可以根据标识符的命名规则取名
	public static void main(String[] args) {
		System.out.println(\"hello world!\");
	}
}

从头开始说吧。

  • 关键字 public 是访问修饰符,表明该类是一个公共类,可以控制其他对象对类成员的访问。
  • 关键字 class 用于声明一个类,其后所跟的 Hello 是类名。
  • Hello 后跟的 {} 称为类体,包含了方法、字段...
  • public static void main(String[] args) {}:这个是 main 方法,由于被 JVM 调用,也被称为主方法、入口方法。所有 java 代码都是最先从入口方法开始执行的。
    • main 后的 {} 称为方法体,包含了 java 语句。
    • 关键字 static 表示该方法是一个静态方法,无须创建类的实例即可调用。
    • 关键字 void 表示此方法没有返回值。
    • void 后面的是方法名 main。
    • main 后的括号包含一个形式参数,这个形参是 String 类型的数组,参数名是 args。
    • System.out.println(\"\"); 是 java 语句,代表打印(输出)括号中的内容到控制台(DOS 窗口)。
    • \"hello world!\" 是一个字符串。
    • ; 代表一条 java 语句的结束。

2.1.5 编译与运行

(字节码文件是 class 文件,由源代码中定义的 class 生成)

过程如下:

首先新建后缀为 java 的文件,编写出符合语法规则的代码。

编译:经过 javac 源文件名.java 命令编译生成字节码文件;

运行:使用 java 类名 命令在 JVM 虚拟机上运行此字节码文件,JVM 会调用此类的 main 方法。

(补充:因为一个源文件可以定义多个类,编译后生成多个 class 文件,所以 java 命令后面跟的不一定是源文件名)

编译

.java 文件是 java 的源文件,但是不能直接运行,必须先被编译成为 .class 文件才能够执行。别问,问就是电脑太笨,看不懂。

class 文件也称为字节码文件,上面的 javac 源文件名.java 就是编译的过程。

编译期将源码交给编译器,编译成可以被 JVM 识别的字节码,如果源代码不符合语法规则,就会报错(错误提示很智能,可以根据报错信息相应找到原因,自己试试比如删除一个大括号、引号等,看看 javac 命令给你报什么错)

运行

(当源文件修改后,必须重新编译才能生效)

编译后生成 class 字节码文件,使用 java class文件名 运行,别加 .class 后缀。

运行期类加载器(Class Loader)找字节码文件(如果没有配 classpath,默认从当前路径下找),找到了就加载字节码文件到 JVM 虚拟机,JVM 启动解释器对字节码文件进行解释,生成的二进制码读到内存中,由操作系统进行二进制码的执行。

字节码文件是一种和任何具体机器环境及操作系统环境无关的中间代码,编程人员和计算机都无法直接读懂字节码文件。它是一种二进制文件,是 Java 源文件由 Java 编译器编译后生成的目标代码文件。它必须由专用的 Java 解释器来解释执行,因此 Java 是一种在编译基础上进行解释运行的语言。

C 语言中文网下的解释,我觉得还行吧。我没玩过 C 语言,说什么就先听着,之后再忘掉就 OK 了。


1)将所有源代码一次性转换成二进制指令(也就是生成一个可执行程序,如 Windows 下的 .exe)
的转换工具称为编译器

比如 C 语言、C++ 的 GCC、Golang 的 GCC GO 等,源代码改动需要重新编译一次。

2)翻译一句,执行一句,不会生成可执行程序。
比如 Python 的 CPython 等,这种的转换工具称为解释器。

3)Java 和 C# 是一种比较特殊的存在。
如 java 有编译器 javac,但编译后的 class 文件计算机无法执行,还需要 java 解释器进行翻译。

它们的源代码需要先转换成一种中间文件(字节码文件),然后再将中间文件拿到虚拟机中执行。
Java 引领了这种风潮,它的初衷是在跨平台的同时兼顾执行效率;

C# 是后来的跟随者,但是 C# 一直止步于 Windows 平台,在其它平台鲜有作为。


Java 解释器负责将字节码文件翻译成具体硬件环境和操作系统平台下的机器代码,以便执行。因此 Java 程序不能直接运行在现有的操作系统平台上,它必须运行在被称为 Java 虚拟机的软件平台之上。

Java 虚拟机(JVM)是运行 Java 程序的软件环境,Java 解释器是 Java 虚拟机的一部分。在运行 Java 程序时,首先会启动 JVM,然后由它来负责解释执行 Java 的字节码程序,并且 Java 字节码程序只能运行于 JVM 之上。这样利用 JVM 就可以把 Java 字节码程序和具体的硬件平台以及操作系统环境分隔开来,只要在不同的计算机上安装了针对特定平台的 JVM,Java 程序就可以运行,而不用考虑当前具体的硬件平台及操作系统环境,也不用考虑字节码文件是在何种平台上生成的。

JVM 把这种不同软、硬件平台的具体差别隐藏起来,从而实现了真正的二进制代码级的跨平台移植。JVM 是 Java 平台架构的基础,Java 的跨平台特性正是通过在 JVM 中运行 Java 程序实现的。

Java 语言这种“一次编写,到处运行”的方式,有效地解决了目前大多数高级程序设计语言需要针对不同系统来编译产生不同机器代码的问题,即硬件环境和操作平台的异构问题,大大降低了程序开发、维护和管理的开销。

提示:Java 程序通过 JVM 可以实现跨平台特性,但 JVM 是不跨平台的。也就是说,不同操作系统之上的 JVM 是不同的,Windows 平台之上的 JVM 不能用在 Linux 平台,反之亦然。

2.2 代码书写规范

内容导视:

  • 代码细节
  • 代码规范
  • 标识符与关键字

2.2.1 代码书写细节

看不懂没关系,只挑能理解的。

1)字母严格区分大小写,如 class 不等于 Class。

2)在 java 中任何有效的代码必须写在“类体”中,就是 public class Hello 后的一对大括号 {} 中。

3)大括号必须要成对写,防止漏掉。

4)为了增加代码的可读性,大括号里的内容需要使用 tab 缩进,如 main 方法就比 class Hello 低一个层次。

class Hello {
    // 被 {} 包裹的部分选中,按下 tab
    public void some1() {
        // 被 {} 包裹的部分选中,按下 tab
        int i = 10;
        if (i > 10) {
            // 同理
            System.out.println(\"为何\");
        }
    }
}

5)main 后的 {} 包裹的内容称为方法体,由一行行的 java 语句构成,任何一条 java 语句必须以分号结尾;。若无特别说明,默认为英文状态下的符号。

6)方法体中代码遵循自上而下的顺序依次逐行执行,不可随意颠倒顺序。

System.out.println(i);// 执行到这句时,还没有 i 变量
int i = 10;

7)一个 java 源文件可以定义多个类。编译后,每一个类对应一个 class 文件,如以下编译后会生成 A.class、B.class、C.class 三个字节码文件

class A {
}

class B {
}

class C {
}

8)有 public 修饰的类可以没有,但如果有,被 public 修饰的类名必须与源文件名一致
例:源文件名为 Hello,则 public class 后的类名也应该为 Hello。

9)被 public 修饰的类如果有,则最多只能有一个。类似一个家只有一个主人吧。

10)运行时,只会调用对应类的入口方法里面的内容,入口方法有固定的书写格式:

public static void main(String[] args) {}

那我偏不信邪,改下,再运行。

E:\\cqh_demo\\01-JavaSE>java A
错误: 在类 A 中找不到 main 方法, 请将 main 方法定义为:
   public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application

好吧,再改回去了。

2.2.2 代码规范

规范与规则不一样,不是必须遵守。但是如果你不遵守,代码可读性会很差。反例如下,虽然可以通过编译,但你看起来感觉如何?

class
Hello{
public
    static
void
    main
    (String a[]){
System.
       out. println(\"你好\")
       ; 
    int i=2
        ;
    			if
                    (i>
                     1&&i
                     <29){
                    System.out.println(\"e\");
                }
}
	}

1)类和方法上的注释,以 javadoc 的方式,方便生成文档注释。

class Test {
    /**
     * 这个注释下节有讲,现在只是测试
     *
     */
    public void some() {}
}

2)非 javadoc 注释,用于给代码的维护者和读者看。(为什么这么写,如何修改,注意事项)

// 这就是注释,解释下面是将 10 赋给了 int 类型的变量 i;
int i = 10;

3)运算符如 <、= 的两边使用空格与字符隔开,举出正反例:

int a = 10;
int b=10;

if (a < 20 && b > 5) {}

if(a<20&&b>5){}

4)代码编写使用次行风格或行尾风格。你可以理解为这就是对整齐风格的一种追求吧。

// 行尾风格
public void add(int num) {
    if (num < 0) {
        this.age = 3;
    } else if (num < 6) {
        this.age = 33;
    } else {
    	this.age = 333;
    }
}
// 次行风格
public void add(int num)
{
    if (num < 0)
    {
        this.age = 3;
    } else if (num < 6)
    {
        this.age = 33;
    } else
    {
    	this.age = 333;
    }
}

2.2.3 标识符与关键字

声明:由于每次使用 import、带上完整类、main 方法比较繁琐。于是进行了简化,只提供关键部分,外面的类、方法等其它代码以后自己加吧,别直接复制代码,发现怎么跑不起来啊?

class Hello {
    public static void main(String[] args) {}
    
    public int add(int num1, int num2) {}
}

凡是程序员有权利命名的单词都是标识符

比如:类名、方法名、参数名、变量名、接口名、常量名。(这些是什么,以后就知道了,现在只是看看)

// add 是方法名有权利命名
public int add(int num1, int num2){
    // n 是变量名
    int n = num1;
}

你可以试试哪里可以修改,比如修改 public、int 等单词,是否会报错。你就明白,你可以动的地方有哪些。可以动而不报错的那些单词就是标识符,不可以动的就是关键字。


切换到英文输入,输出下划线、美元符号。

_:Shift 加 -
$:Shift + 4


命名规则

  • 标识符只能由数字、字母、_、$ 组成。(标识符中不能有空格)
  • 标识符不能以数字开头。
  • 关键字和保留字不能做标识符。
  • 标识符严格区分大小写,理论上没有长度限制。(如 Public 与 public 不一样)

命名规范

A)见名知意,如 day 代表天数。

1)别用拼音与英语混合,那样很难理解。

反例:tianKongBlue

2)杜绝完全不规范的英文缩写,避免望文不知义。

反例:AbstractClass “缩写”成 AbsClass;
condition “缩写”成 condi;
Function “缩写”成 Fu;
此类随意缩写严重降低了代码的可阅读性。

B)驼峰命名,多个单词连在一起时,单词首字母大写,增加识别和可读性,下面是阿里巴巴开发手册(黄山版)详细:

1)类名使用 UpperCamelCase 风格(首字母大写,后面每个单词首字母大写),以下情形例外:DO / PO / DTO / BO / VO / UID 等。

正例:ForceCode / UserDO / HtmlDTO / XmlService / TcpUdpDeal / TaPromotion
反例:forcecode / UserDo / HTMLDto / XMLService / TCPUDPDeal / TAPromotion

2)方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格(首字母小写,后面每个单词首字母大写)。

正例:localValue / getHttpMessage() / inputUserId

C)其它命名风格

1)常量名全部大写,单词间用_连接,力求语义表达完整,不要嫌名字长。

正例:MAX_STOCK_COUNT / CACHE_EXPIRED_TIME
反例:MAX_COUNT / EXPIRED_TIME

枚举成员实际上也是常量,名称需要全大写,单词间用_连接。

2)抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾;枚举类名带上 Enum 后缀。

3)接口和实现类的命名有两套规则:

  • 对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。

    正例:CacheServiceImpl 实现 CacheService 接口。 
    
  • 如果是形容能力的接口名称,取对应的形容词为接口名(通常是 –able 结尾的形容词)。

    正例:AbstractTranslator 实现 Translatable 接口。
    

4)在常量与变量命名时,表示类型的名词放在词尾,以提升辨识度。

正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT
反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD

5)如果模块、接口、类、方法使用了设计模式,在命名时要体现出具体模式。

正例: 
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;

说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计思想。

6)各层命名规约:

​ a)Service / DAO 层方法命名规约:

​ ① 获取单个对象的方法用 get 做前缀。

​ ② 获取多个对象的方法用 list 做前缀,复数结尾,如:listObjects

​ ③ 获取统计值的方法用 count 做前缀。

​ ④ 插入的方法用 save / insert 做前缀。

​ ⑤ 删除的方法用 remove / delete 做前缀。

​ ⑥ 修改的方法用 update 做前缀。

​ b)领域模型命名规约:

​ ① 数据对象:xxxDO,xxx 即为数据表名。

​ ② 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。

​ ③ 展示对象:xxxVO,xxx 一般为网页名称。

​ ④ POJO 是 DO / DTO / BO / VO 的统称,禁止命名成 xxxPOJO。

7)POJO 类中定义的布尔类型的变量,不要加 is 前缀,否则部分框架解析会引起序列化错误。

备注:POJO(Plain Ordinary Java Object):普通的 Java 对象。

反例:定义为基本数据类型 Boolean isDeleted 的属性,它的方法也是 isDeleted()。
框架在反向解析时,“误以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。

说明:本文 MySQL 规约中的建表约定第 1 条,表达是与否的变量采用 is_xxx 的命名方式,
所以需要在设置从 is_xxx 到 xxx 的映射关系。

8)包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

正例:应用工具类包名为 com.alibaba.ei.kunlun.aap.util;类名为 MessageUtils
(此规则参考 Spring 的框架结构)。

9)数组类型应与 [] 紧挨,以免看漏,认为是基本数据类型或 String 类型。

正例:定义整形数组 int[] arrayDemo。
反例:在 main 参数中,使用 String args[] 来定义。

10)避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可理解性降低。

// 反例
public class ConfusingName { 
    protected int stock; 
    protected String alibaba;
    // 非 setter/getter 的参数名称,不允许与本类成员变量同名
    public void access(String alibaba) {
        if (condition) {
            final int money = 666;
            // ...
        }for (int i = 0; i < 10; i++) {
            // 在同一方法体中,不允许与其它代码块中的 money 命名相同
            final int money = 15978;
            // ...
        } } }
class Son extends ConfusingName {
    // 不允许与父类的成员变量名称相同
    private int stock; 
}

说明:子类、父类成员变量名相同,即使是 public 也是能够通过编译,而局部变量在同一方法内的不同代码块中同名也是合法的,但是要避免使用。对于非 setter / getter 的参数名称也要避免与成员变量名称相同。

关键字

Java 关键字是对 Java 编译器有特殊含义的字符串,是编译器和程序员的一个约定,程序员利用关键字来告诉编译器其声明的变量类型、类、方法特性等信息。

关键字一律用小写字母标识,按其用途划分为如下几组。

1)用于数据类型的关键字有:

boolean、byte、char、 double、 false、float、int、long、new、short、true、void、instanceof。

2)用于语句的关键字有:

break、case、 catch、 continue、 default 、do、else、 for、 if、return、switch、try、 while、finally、 throw、this、 super。

3)用于修饰的关键字有:

abstract、final、native、private、protected、public、static、synchronized、transient、 volatile。

4)用于方法、类、接口、包和异常的关键字有:

class、 extends、 implements、interface、 package、import、throws。

5)保留字:

cat、 future、 generic、inner、 operator、 outer、rest、var、goto、byValue、cast、const 等都是Java尚未使用,但以后可能会作为关键字使用。

另外,Java 还有3个保留字:true、false、null。它们不是关键字,而是文字,包含 Java 定义的值。和关键字一样,它们也不可以作为标识符使用。

2.3 注释

内容导视:

  • 单行注释
  • 多行注释
  • 文档注释
  • IDEA 工具自动快捷键添加注释
  • 使用 javac 命令编译时出现的\"错误: 编码 GBK 的不可映射字符\"
  • 浅入编码
  • 查看系统编码
  • 使用 API 文档

注释是对代码的解释说明,方便理解代码的含义,提高代码的可读性。
注释不是编程语句,因此被编译器忽略。
如果不写注释,时间久了或代码过长本人也看不懂,所以编写注释是一个良好的习惯。

注释有三种,依次介绍。

2.3.1 单行注释

使用双斜杠 //,// 后的就是注释(仅一行),不会被编译器当成 java 语句。

public class A {
	public static void main(String[] args) {
		// 打印()中的话到控制台上,这个()中的字面量(数据)如果是字符串类型,需要用\"\"包裹起来
		System.out.println(\"Hello World A\");
	}
}

2.3.2 多行注释

对于很多内容,单行放不下,可以使用多行注释,在 /**/ 里内写下内容,星号别省略。

/*
	System 是类,
	通过类名.调用 out 这个静态变量,
	这个静态变量保存的是堆内存中的对象地址,被称为对象引用
	再通过对象引用,调用对象的 println 方法
*/
System.out.println(\"Hello World A\");

2.3.3 文档注释

下面看看就行,以后使用开发工具 IDE 自动生成,没必要手动敲。

文档注释可以被 javadoc 命令解析,生成以网页形式(html)显示的 API 文档。(Application Programming Interface:应用程序编程接口)。

当类又多又杂,一个个找类、看注释很麻烦,于是提取出来生成网页。

用来说明类、成员变量和方法的功能。不用在一个一个 java 文件中查看注释,直接打开 html 查看想要的方法。

javadoc 默认只提取 public、protected 修饰的部分,javadoc -help 查看可加的选项。

文档注释必须写在类、接口、方法、构造器、成员字段前面,写在其他位置无效。

文档注释中可以识别的标签如下:

标签 说明
@version 指定类的版本,用于类上
@author 标注类的作者
@since 从哪个版本起有了这个方法
@param 参数详细信息
@return 说明返回值
@throws 可能抛出的异常
@deprecated 表示不建议使用
@see 另请参阅

文档注释的标签区分大小写,别写错了,也可以看看源码上的注释是怎么写的。(别省略 * 号,注意观察下面的格式)

/**
 * @author 是在座的每一个人
 * @version 2.0.0
 */
public class Hello {

    /**
	 * 这个方法用来求两数之和
	 *
     * @param  num1	第一个参数
	 * @param  num2	第二个参数
     * @return 返回两个数的和
     * @throws 测试用而抛出的异常
     * @since  1.8.2
     */
    public int add(int num1, int num2) throws RuntimeException {
        return num1 + num2;
    }
} 

javadoc 命令只能提取文档注释。让我们试试吧。

javadoc Hello.java -encoding UTF-8 -version -author -private -charset UTF-8 -docencoding GBK

当前目录会生成 index.html 文件,双击此文件,交给浏览器解析(会自动打开浏览器),右键查看页面源代码,API 文档里的信息是读取 Hello.java 文件得来的。

使用此命令还可以追加其他 java 源文件的文档注释,例:javadoc -encoding UTF-8 Hello.java H.java

javadoc 命令中的参数说明


-encoding 是告知 java 源代码所用的字符编码;
-version、-author 是显示版本和作者;
-private 是显示所有类和成员;(包括显示私有,一般显示 protected 以上级别就行,可以不加此选项)

-charset 是告知浏览器此文件采用什么编码方式读取这个文件;
即 html 文件生成 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">

-docencoding 是指定生成的 html 文件的字符编码,不写此选项,默认 UTF-8。


由于文件被保存时的编码和读取时采用的编码不一致会出现乱码,所以别掉坑。咳咳,你知道自己的文件编码吗?在哪如何查看?编码是什么?你可以先了解下相关知识,或者你之前已经掉坑了,不知道满屏的 GBK 不可映射是啥意思,后面再讲好吗?

指定文档生成的位置

使用 -d 指定文件在 E:\\a\\b\\c 下生成,很抱歉让你的桌面生了一大堆 html 文件。

javadoc Hello.java -d E:\\a\\b\\c -version -author

代码一改,注释说明也得跟着改;为了避免无用功,代码中给标识符命名,见名知意,最好让所有人看到这段代码就知道它是干什么的,避免冗余无效注释。

下面不用看,我怕你晕:

import java.nio.channels.ReadableByteChannel;
import java.nio.charset.CharsetDecoder;

/**
 * 这是测试如何制作 API 文档的类
 *
 * <p> p 标签包围的文字代表一段,ul 和 li 标签是无序列表</p>
 * <ul>
 * <li>呵呵 {@code test} 呵呵</li>
 * <li>呵呵 <code>test</code> 呵呵</li>
 * <li>被上面 code 标签包裹的内容会更显眼,更细,一般用于标识符</li>
 * <li>see 标签另请参阅,格式:引用类的全类名#方法名(形参类型, 形参类型...)
 *     java.lang 包下的类可以省略包名。如果引用的类在 API 文档上,点击后会自动跳转
 *     这个 H 类我也打包到了此文档</li>
 * </ul>
 *
 * @author  cqh
 * @author  作者甲
 * @version 1.0.0
 * @see     Object#wait(long)
 * @see     Object#toString()
 * @see     StringBuffer
 * @see     H#hh()
 * @since   0.8.0
 */
public class Test {
    /**
     * 介绍属性的作用
     *
     * @see java.util.Scanner#makeReadable(ReadableByteChannel, CharsetDecoder)
     * @since 0.8.1
     */
    public String[] name;

    /**
     * 说明方法的作用
     *
     * @param name 这个name是 {@code String} 类型
     *       	   see参阅本类中其它方法省略类名。link如
     *             {@link #getAge(int, int)}也可以跳转,例
     *             {@link H#hh()},{@link H}
     * @return 返回一个人名
     * @throws ArrayIndexOutOfBoundsException 下标越界抛出此异常
     * @see #getAge(int, int)
     * @see H
     * @since 0.8.1
     */
    public String getName(String name) throws ArrayIndexOutOfBoundsException{
        return name;
    }

    /**
     * 另一种格式,把解释放到下一行 {@link java.lang.Object#notify()} 你好
     *
     * @param age
     *        这是人的年龄
     *
     * @param count
     *        总个数        
     *
     * @throws IndexOutOfBoundsException
     *         如果满足如下几种条件之一就抛出该异常
     *         <ul>
     *           <li> {@code age} 不是整数
     *           <li> {@code age} 没有填入
     *           <li> {@code age+(end-begin)} 超过了 
     *                {@code name.length}
     *         </ul>
     */
    public void getAge(int age, int count) throws IndexOutOfBoundsException{

    }
}

2.3.4 IDEA 工具自动快捷键添加普通注释

以后有了 IDEA 工具再试,现在不用看,直接跳过;放心 IDEA 工具中会更详细地说明。

在 IDEA 中每创建一个类时,自动在类上加注释

左上角 File/Settings/Editor/File and Code Templates,includes/File Header

里面粘贴

/**
 * 现在北京时间:${YEAR}/${MONTH}/${DAY} ${TIME}
 * 本类用于某某某
 *
 * @author  作者
 * @version 版本
 * @see     另请参阅
 * @since   从哪个版本有的
 */

点击 apply 和 OK。

image-20220322171502796

在方法上加注释

左上角 File/Settings/Editor/Live Templates

点击加号 Template Group 自己创建个组后,再选择你刚刚创建的组点击加号 Live Template。

解释:

Abbreviation:**

Description:解释快捷键的描述,自己填写

Options
Expand with Enter

Template text:
**
 *
 * @param   $param$
 * @return  
 * @throws
 */

当输入 Abbreviation 所写内容\"**\"时,按下 Enter 就把 ** 转成 Template text 中的内容

被 $$ 包围的变量需要点击 Edit vaiables 设置。

选择 Define 勾选 Java,就可以定义此模板只有在编写 java 文件才能使用。

点击 apply 和 OK。

然后我又定义了一个方法模板,缩写词:psvt;设置好后 OK 返回。

image-20220322172330365

在 java 文件输入 psvt 后按下 Tab 键,光标停在 $VAR$ 等我们输入;按下回车,光标到了 $END$ 处。

image-20220322172456942

image-20220322172627159

2.3.5 使用 javac 命令编译时出现的\"错误: 编码 GBK 的不可映射字符\"

内容如下:

  • 解决 GBK 乱码问题
  • 普及编码格式知识

本节片段提取:

友情提示,先复制文本,转换后直接粘贴。目的是避免转换编码后造成中文乱码,又得一个个改。

有 2 种方式解决:

1)文件/另存为副本,更改编码为 ANSI。(如果使用的是 notepad++,就在上方的工具栏的编码,转成 ANSI 编码)

image-20220322173042667

2)指定使用的编码格式。(编码格式在文本编辑器的右下方)-encoding UTF-8 是指定文件编码格式是 UTF-8。

屏幕截图 2022-03-22 173317

正文如下:

可能编译时由于代码(包括注释里面)中有中文,编译时说什么含有 GBK 的不可映射字符,不让通过。我相信新手的第一道关就卡在这,有人就干脆不写中文了。

为何报错

计算机只认得二进制数,也就是 0 和 1,存储和读取数据时都要使用二进制数表示。

0 和 1 可以有很多的组合,如 011、1001010 等等。可以用它们表示不同的数据,字符编码就是人为定义的一套转换表,规定一系列文字与二进制的映射关系。

(这里未区分字符集与字符编码的概念)

有多种字符编码,比如 ASCII 码,一般是 8 位二进制表示一个字符,如字符 \'a\' 对应 01100001,这个二进制码转为十进制是 96;

学过概率的知道,8 位数,每位是 0 或 1,最多可以表示 2 ^ 8 = 256 个字符,应对 26 个英文字母绰绰有余。这 8 位称为 1 个字节(byte),单个位称为 bit。

2 ^ 8 即 2 的 8 次方,只是为了方便表达。

ASCII 是美国人定义的,没有考虑其它国家。但我们国家用的不是英文,有很多字符是 ASCII 码没有的,1个字节也表示不了那么多的汉字,于是国人粗略规定了 GB2312,使用 2 个字节(16位)表示一个汉字。

如在 GB2312 编码中 \"中国\" 对应的二进制数 11010110 11010000 10111001 11111010,要是系统采用ASCII 码读取这段二进制,由于它们定义的字符的映射关系都不一样,ASCII 编码也不可能有中文对应的二进制码,所以解码不可能正确,解析不出来就会乱码。

这里二进制数每 8 位隔开,只是方便你们观看,实际使用时还是合在一起。

来,我们使用记事本试试看。此处文件编码为 ANSI,使用 ANSI 编码读取文件,没有乱码,显示 “天下”。

ANSI 代表系统默认编码方式,在中国是 GBK,放心 GBK 兼容 GB2312,使用 GBK 可以解析 GB2312。

举个例子,如果 ASCII 中所有字符对应的二进制码,在 GBK 中,对应关系也同样如此,就说明 GBK 兼容 ASCII 码。说人话就是 01100001 在 ASCII 中表示 \'a\',在 GBK 中也表示 \'a\'。那么使用 GBK 编码读取 ASCII 文件没有问题,反过来则不一定,因为 GBK 还多了 ASCII 中没有的汉字。

屏幕截图 2022-03-22 175428

让我们把文件编码转成 ISO-8859-1,也使用此编码读取文件。

image-20220322181031033

image-20220322181046936

现在再看看,这不就乱码了吗?

所以解码和编码都要使用同一套字符编码规则,notapad++ 可以使用不同的编码方式解析,你测试哪些编码是兼容的,不会乱码。

编码:字符 -> 二进制码

解码:二进制码 -> 字符

底层存储的实际是二进制码。

使用 javac 命令编译时,如果不指定 -encoding 选项,一般默认采用操作系统的字符编码方式,我们是 GBK。

在编写代码时,文本编辑器的右下角会显示当前文件的编码格式。编译时,若与系统当前编码不一致,不是同一套字符编码规则,解析不出来,就会报错;以后讲 char 类型时还会深入,现在讲的很浅显,看不明白很正常,不是你的原因。(我移到扩充内容中了)

两种方式解决

  • 告知此文件的编码方式。
    例:当前文件的编码格式是 UTF-8,文件是 Hello.java,编译时添加 -encoding 参数指定文件编码javac Hello.java -encoding UTF-8

  • 修改文件编码方式为 ANSI,文件另存时可以看到编码。(notepad++在工具栏/编码/转为...记得先全选复制,改编码后再粘贴,否则乱码没法撤回)

  • 所有文件使用统一的编码方式,如 UTF-8,新建环境变量 JAVA_TOOL_OPTIONS,值为 -Dfile.encoding=UTF-8,不建议,以后使用 IDE 工具统一使用 UTF-8 编码,不需要自己在外面配。

  • 我不写中文了,不建议,你是中国人,要不是英文通不过编译,我还真想全部使用中文符号。

2.3.6 查看与修改系统编码

通过 java 代码查看

执行以下代码查看系统默认编码

public class Hello {
    public static void main(String[] args) {
        String encoding = System.getProperty(\"file.encoding\");
		System.out.println(encoding);
    }
}

通过 DOS 窗口查看

打开 DOS 窗口(Windows + R,输入 cmd 回车),点击左上角图标/属性

看到了没有,我没有画圈哦。当前代码页是 936,对应简体中文编码 GBK。

代码页是字符集编码的别名,也称内码表,下面是代码页与编码的对应关系:

代码页       国家(地区)或语言 
437          美国 
708          阿拉伯文(ASMO 708)
720          阿拉伯文(DOS)
850          多语言(拉丁文 I) 
852          中欧(DOS) - 斯拉夫语(拉丁文 II) 
855          西里尔文(俄语) 
857          土耳其语 
860          葡萄牙语 
861          冰岛语 
862          希伯来文(DOS)
863          加拿大 - 法语 
865          日耳曼语 
866          俄语 - 西里尔文(DOS) 
869          现代希腊语
874          泰文(Windows)
932          日文(Shift-JIS)
936          中国 - 简体中文(GB2312)现在是 GBK 了,GBK 是在国家标准 GB2312 基础上扩容后兼容 GB2312 的标准。
949          韩文
950          繁体中文(Big5)
1200         Unicode        
1201         Unicode (Big-Endian)
1250         中欧(Windows)
1251         西里尔文(Windows)
1252         西欧(Windows)
1253         希腊文(Windows)
1254         土耳其文(Windows)
1255         希伯来文(Windows)
1256         阿拉伯文(Windows)
1257         波罗的海文(Windows)
1258         越南文(Windows)
20866        西里尔文(KOI8-R)
21866        西里尔文(KOI8-U)
28592        中欧(ISO)
28593        拉丁文 3 (ISO)
28594        波罗的海文(ISO)
28595        西里尔文(ISO)
28596        阿拉伯文(ISO)
28597        希腊文(ISO)
28598        希伯来文(ISO-Visual)
38598        希伯来文(ISO-Logical)
50000        用户定义的
50001        自动选择
50220        日文(JIS)
50221        日文(JIS-允许一个字节的片假名)
50222        日文(JIS-允许一个字节的片假名 - SO/SI)
50225        韩文(ISO)
50932        日文(自动选择)
50949        韩文(自动选择)
51932        日文(EUC)
51949        韩文(EUC)
52936        简体中文(HZ)
65000        Unicode (UTF-7)
65001        Unicode (UTF-8)

也可通过 DOS 命令查看当前编码:chcp

修改当前 DOS 窗口的编码:chcp 对应编码的代码页,如chcp 936,当控制台不支持中文时,可以试试。(重新打开 DOS 窗口时会失效,恢复原来默认编码)

修改系统编码

如果是 Windows10,打开设置(Windows + i),时间和语言/语言/管理语言设置/更改系统区域设置,

老版本的使用控制面板/时钟和区域/区域/管理/更改系统区域设置。

下面还有 beta 版(即测试版),使用 UTF-8 编码提供全球语言支持;但有些地方莫名其妙的乱码,不建议尝试,还是 GBK 靠谱。

作者:「已注销」,内容:修改 cmd 控制台默认代码页编码的几种方法【GBK、UTF-8】,https://blog.csdn.net/gulang03/article/details/81771343

2.3.7 使用 API 文档

解决了中文乱码后,来看看 rt.jar 包中的类、方法、字段上的文档注释生成的 API 文档。

JDK17 API文档:https://docs.oracle.com/en/java/javase/17/docs/api/index.html

JDK8 API文档:https://docs.oracle.com/javase/8/docs/api/

JDK8 API中文文档:https://www.matools.com/api/java8

就是记不住方法名怎么办,翻阅 API 文档查看方法的作用,你得首先记住哪个类好像有这个方法。

Java 语言提供了大量可供使用的基础类,Oracle 为这些类提供了对应的 API 文档,告诉开发者如何使用此类,以及方法。

通过包名 -> 类名 -> 方法这样的方式寻找。

以 JDK8 为例,哦,对了,将最上面的广告关掉。

如果不知道类在哪个包下,点击最上方的索引(INDEX)

如我要用 Math 类的求绝对值的方法,如果事先知道它在 java.lang 下(第一横线处下翻),找到后点击 java.lang,继续下翻(第二个横线处);找到后点击 Math,再看右边的页面,下翻;点击 abs 方法,就可以看到此方法的详细说明。

image-20220318155351799

打开 rt.jar(jre 的 lib 下) 也可以看到 Math.class。(使如果你没有软件可以打开 zip,win-rar 解压缩软件下载地址在资源地址中)

image-20220318154136523

image-20220318154212941

image-20220318154252881

你说这是字节码文件,肯本看不懂,要看源码上的文档注释。好吧,在 JDK 安装目录下,打开 src.zip\\java\\lang\\Math.java。

image-20220318154851832

image-20220318155512159

以这种方式,的确没有 API 文档访问方便不是吗?(养成在方法上写文档注释的好习惯,这样方便提取出来)

这是谷歌翻译:

返回 {@code float} 值的绝对值。
* 如果参数不是负数,则返回参数。
* 如果参数是否定的,则返回参数的否定。
* 特别案例:
* <ul><li>如果参数是正零或负零,则
* 结果为正零。
* <li>如果参数为无穷大,则结果为正无穷大。
* <li>如果参数为 NaN,则结果为 NaN。</ul>
* 换句话说,结果与表达式的值相同:
* <p>{@code Float.intBitsToFloat(0x7fffffff & Float.floatToIntBits(a))}
*
* @param a 要确定其绝对值的参数
* @return 参数的绝对值。

2.x 总结回顾

编译与运行

先编译成 .class 文件,后运行此类的 main 方法。

运行期类加载器(Class Loader)将 class 文件加载到 JVM 中,JVM 启动解释器对 class 文件解释,生成的机器码在内存中,由操作系统执行。

class 文件是与平台无关的中间代码。下载对应平台的 JVM,由它自带的解释器,将 class 文件翻译成当前操作系统可以执行的机器码,做到一次编写,可以在不同的平台上运行。但 JVM 不跨平台。

程序员可以命名的单词是标识符,如类名、方法名、变量名、接口名、常量名。

命名规则

  • 只能由数字、字母、_、$组成。
  • 不能以数字开头。
  • 不能以关键字作为标识符。

命名规范

  • 类名、接口名首字母大写,之后每个单词首字母大写。
  • 方法名、变量名首字母小写,之后每个单词首字母大写。

注释

合理使用注释,解释代码含义,方便他人阅读。

  • 类和方法上的注释,使用文档注释。
  • 类中、代码块中使用多行或单行注释,对难以理解的地方进行说明。
  • 不同逻辑的代码之间空行隔开。

编码

使用 UTF-8 格式编写源文件更通用。

2.y 脑海练习

2.1 为什么 Java 代码可以做到一次编译,到处运行?

2.2 一个源文件可以生成多个 class 文件吗?

2.3 Java 源文件定义的类名必须与文件名一致吗?

第三章 变量

内容导视:

  • 变量
  • 数据类型
  • 类型转换

3.1 变量

内容导视:

  • 字面量
  • 变量

3.1.1 字面量

值(数据)被称为字面量,一眼就能看到值的量。如 3 是整数型字面量,\'a\' 是字符型字面量;有些人把它叫做常量,无可非议。

字面量类型如下:

1)整数类型:如 1、2、3...这些整数。
2)浮点类型:如 22.4、3.2...这些小数。
3)布尔类型:只有两个值,true、false 分别代表真和假。
4)字符类型:\'a\'、\'b\'、\'c\'...用英文单引号括起来的单个字符
5)字符串类型:\"abc\"、\"b\"、\"北京欢迎你\"...用英文双引号括起来的是字符串。

3.1.2 变量

使用如下方式,也不是不可以,但是你不怕一个个复制粘贴不小心漏了吗?

System.out.println(327501510);
System.out.println(327501510 + 6);
System.out.println(\"我们的花园真漂亮\");
System.out.println(\"我们的花园真漂亮\");

为了使字面量得到重复利用,下面使用变量保存这些值。

内容如下:

  • 变量的声明
  • 变量含义
  • 变量按声明位置分类

变量声明

每个变量都需要先声明(定义)自己将要保存的数据的类型,后再给变量赋值。(放心,数据类型之后有讲,现在先看看,别管 int、String 是什么)

例:想要保存整数类型的值,需要先声明整数类型的变量,假如取名为 i:

int i;

再给变量赋一个整数值。

// 使用等号(赋值运算符)把等号右边的字面量赋给左边的变量
i = 327501510;

可以访问这个变量保存的值。

System.out.println(i);// 此时 i 为 327501510

也可以重新给 i 变量赋值,把之前保存的值覆盖。

i = 6;// 把 6 赋给 i
System.out.println(i);// 此时 i 为 6

两者结合,在声明的同时,赋值。

int i = 327501510;

那么就可以把开头的例子转变了:

int num1 = 327501510;
int num2 = 6;
String str = \"我们的花园真漂亮\";

System.out.println(num1);
System.out.println(num1 + num2);
System.out.println(str);
System.out.println(str);

num1、num2、str 是变量名,作为标识符,我们有权力命名,只要符合命名规则就行。

很明显通过以上例子看到变量有三个要素:

  • 变量的数据类型,如 int
  • 变量名,如 num1
  • 变量值,如 327501510

数据类型 变量名 = 字面量;

如果不考虑类型转换,那么变量的数据类型必须与字面量类型一致。

比如整数型的 int 类型不能保存浮点型的字面量,int i = 3.23; 是错误的写法。由于现在还没有讲数据类型,听着迷惑很正常。

变量含义

变量是内存中存储数据的最基本的单元,任何变量都有数据类型,不同的数据类型在内存中分配的空间大小不同。

int a = 3; 在内存里分配 4 个字节空间,空间存放着 3,a 就代表这个空间。

变量相当于内存中的一小块数据存储空间,通过变量名可以访问到这个区域。可以把 a 理解成你家的门牌号,通过门牌号定位你家。

int 类型会被分配 4 个字节大小的空间,1 个字节(byte)= 8 个比特位(bit)

1 TB = 1024 GB
1 GB = 1024 MB
1 MB = 1024 KB
1 KB = 1024 Byte
1 Byte = 8 Bit
1 Bit = 0 或 1

利用 0 和 1 的不同的组合代表各式各样的数据,等学到进制之间的转换、原码补码就明白了,但你也可以跳过,节省时间。

变量按声明位置分类

分为成员变量与局部变量。之间说过类名 Hello 后跟的 {} 是类体,方法名 main 后跟的 {} 是方法体。

public class Hello {
    // 方法体外、类体中声明的变量是成员变量
    int a = 2;
    
    public static void main(String[] args) {
        // 方法体中声明的变量是局部变量
        int b = 4;
        System.out.println(b);// 4
    }
}

值得注意的地方

1)局部变量必须赋值后才能访问,错误示范:

public static void main(String[] args) {
    int i;
    System.out.println(i);
}

你得先给 i 赋值,比如 int i = 10; 后再访问。

2)同一个域中,局部变量不能重复声明。

域:{} 包起来的范围。

// 错误示范
public static void main(String[] args) {
    int i = 3;
    int i = 6;
}

你得换个名字,以免冲突。比如把后面改成 int j = 6;

3)变量需要先声明,后访问,错误示范:

public static void main(String[] args) {
    System.out.println(i);
    int i = 3;
}

你应该把它俩的顺序换过来,毕竟 java 语句是自上而下逐行执行的。

3.2 数据类型

内容导视:

  • 整数类型
  • 浮点类型
  • 字符类型
  • 布尔类型
  • 基本数据类型转换
  • 基本数据类型与 String 类型的转换

数据类型 变量名 = 字面量;...变量的数据类型必须与字面量类型一致?

字面量讲了,变量名你可以随便取,接下来该说一说数据类型了。

Java 支持的数据类型有两种,基本数据类型引用数据类型

基本数据类型加上引用数据类型中的 String 类,正好与我们之前讲的字面量类型一一对应。

基本数据类型

1)整数类型

  • byte(字节):在内存中分配 1 个字节的空间。(8 位)
  • short(短整型):在内存中分配 2 个字节的空间。(16 位)
  • int(整型):在内存中分配 4 个字节的空间。(32 位)
  • long(长整型):在内存中分配 8 个字节的空间。(64 位)

2)浮点类型

  • float:在内存中分配 4 个字节的空间。
  • double:在内存中分配 8 个字节的空间。(double 精度比 float 更高)

3)布尔类型

  • boolean:在内存中分配 1 个字节(数组中)或 4 个字节的空间(单个定义时)。

4)字符类型

  • char:2 个字节。

引用数据类型

1)类(class)包括枚举

  • JDK 自带类库,如 java.lang.String、集合、包装类...
  • 用户自定义的类型,如我们之前写的 class Hello...
  • 第三方类库

2)接口(interface)包括注解

3)数组(array)

对于基本数据类型,如定义 int 类型的变量,就可以接收整数型的字面量:int i = 4;;定义 char 类型的变量,可以接收字符型的字面量:char c = \'中\';;定义 boolean 类型的变量可以接收 true 或 false:boolean b = true;...

对于引用类型的 String,定义 String 类型的变量,就可以接收字符串型的字面量:String str = \"我是什么样\";

正常情况下,占用字节空间越大,表示的数越多;如整数类型的 byte 只占一个字节,只能表示 -128 ~ 127 内的整数,所以才需要 int、long,用以存储更大的整数。当然存储的数很小时,没必要使用 long,太浪费空间。

引用类型可以赋值 null;等面向对象时讲,现在先看基本数据类型:

3.2.1 整数类型

整数类型用来存储整数类型的字面量。

以 1 个字节为例,1 个字节 8 位,每位是 0 或 1,那么就是有 28 种可能,即表示 256 个数。

下面是不同整数类型的取值范围:

类型 占用存储空间 取值范围
byte(字节) 1 个字节 [-128 ~ 127] 即 [-27 ~ 27 - 1]
short(短整型) 2 byte [-32768 ~ 32767] 即 [-215 ~ 215 - 1]
int(整型) 4 byte [-2147483648 ~ 2147483647] 即 [-231 ~ 231 - 1]
long(长整型) 8 byte [-263 ~ 263 - 1]

int i = 3;
byte b = 45;
b = 34;
System.out.println(i);

定义了 int 类型的变量,变量名为 i,保存的值为 3。

定义了 byte 类型的变量,变量名为 b,保存的值为 45。

把 34 赋给 b 变量,原来保存的 45 被修改了。

System.out.println(i);表示把 i 保存的值输出到控制台(目前是 DOS 窗口)上。

由于现在你们可能还不懂二进制,于是使用十进制表示,同时也是为了方便,避免写太多的 0、1。


有人可能会问超过了整数类型的取值范围会怎么样?

byte b = 128;
Hello.java:4: 错误: 不兼容的类型: 从 int 转换到 byte 可能会有损失
    byte b = 128;

这下就更疑惑了,什么叫 int 转成 byte 会有损失?难道这个 128 也就是整数型字面量默认被当作 int 类型处理吗?

让我们试一试:

long num1 = 2147483648;

推测:就算 long 类型可以保存这么大的数,但如果后面的 2147483648 真的被当作 int 类型处理的话,那肯定由于超过 int 的范围,会报错。

Hello.java:4: 错误: 过大的整数: 2147483648
    long num1 = 2147483648;

好的,大概明白了。得出结论:

  • 整数型字面量默认被当作 int 类型处理。
  • 为了方便,当被当作 int 类型处理的字面量的值没超过整数类型的范围时,可以直接赋值。如 byte b = 1;

有人就问了,超出 int 范围的值?那能不能让整数型字面量被当作 long 类型处理?

答:在声明整数型字面量时在其后加 l 或 L。

long num1 = 2147483648L;
long num2 = 2147483648l;

但是你也看见了,由于小写的 l 感觉就像 1,为了避免混淆,统一使用大写表示。

这里插一条概念:

值传递:把变量保存的值重新复制一份,传递给另一个变量;而另一个变量修改自己保存的值,不会影响原来变量保存的值。

值传递也称值拷贝

int i1 = 100;
// 把 i1 保存的值 100 复制一份,传给 i2,此时 i2 保存的值为 100
int i2 = i1;

// 修改 i2 保存的值,不会对 i1 有影响
i2 = 55;

System.out.println(\"i1 = \" + i1);// i1 = 100
System.out.println(\"i2 = \" + i2);// i2 = 55

好,有了这个概念,让我们试着互换两个变量保存的值。

大家想一想,如果现在有两个杯子 a、b,都装满了水,该如何互换?

无标题

是不是要准备第 3 个杯子 c,先把 a 倒进 c 中,然后把 b 倒进 a 中,最后把 c 倒进 b 中。

int a = 22;
int b = 433;

int c = a;// c = 22
a = b;// a = 433
b = c;// b = 22

可以不借助第三个变量 c 吗?

有聪明的小伙伴想到了:让 a 保存两值之和(a + b),让 b = 两值之和 - b = a;

int a = 22;
int b = 433;

a = a + b;// a = 22 + 433
b = a - b;// b = (22 + 433) - 433 = 22
a = a - b;// a = (22 + 433) - 22 = 433

3.2.2 浮点类型

浮点类型的变量可以接收一个小数,如 6.2、32.2。

由于使用指数的形式表示值,表示的数比相同字节下的整数类型更大,但是精度有限,结果可能有误差,无法精确表示。

现在看看浮点类型的取值范围,的确比整数类型的取值范围大多了:

类型 占用空间 范围
float 4 个字节 [1.4E-45 ~ 3.4028235E38] 即 [2-149 ~ 2128]
double 8 个字节 [4.9E-324 ~ 1.7976931348623157E308] 即 [2-1074 ~ 21024]

这段内容可以不看:


科学计数法

由于数太大了,为了更好表示数值不浪费空间,使用科学计数法表示。这个 E 大写小写都可以。

1.4E-45 = 1.4 * 10-45

3.4028235E38 = 3.4028235 * 1038

有人说为何使用科学计数法表示?以 2 的幂表示,如 2-149 是不是更节省空间?

7777777 保留 2 位有效数字,使用科学计数法表示:7.78 * 106(7.78E6);你此时可能还在算它是 2 的几次方吧?

Java 中,当小数超出 [-9999999,9999999] 范围时,会使用科学计数法表示。

System.out.println(-10000000.0);

控制台上会输出 -1.0E7

有聪明的伙伴可能注意到了两点,题干中的“小数”、字面量后的“.0”,注意啊这两个条件不能丢,否则会被当作 int 类型处理。那么下面这道题就可能做错:

思考控制台输出什么结果?

System.out.println(500e-2);

答:500e-2 = 500 * 10-2 = 500 / 102 = 5.0

别说结果是 5 啊。

下面呢?

System.out.println(-10000000);
System.out.println(500E-7);

第 1 个明显不是小数,还是原样输出。第 2 个你可能会觉得这既然超过上述所说的范围,那使用科学计数法表示,还是原样输出。

我留的坑啊,没有使用这种方式 500E-7 表示的,而是 5.0E-5

举个例子,a * 10n,那么 |a| 是 [1 ~ 10) 之间的数。

-78937935.2 ,一看超过了范围,用科学计数法表示:-7.89379352E77.89379352 大于等于 1,小于 10。你要是这样表示就错了:-78.9379352E6


小数类型的字面量默认被当作什么类型处理?

回过头来,小数类型的字面量又被当作什么类型处理?

做个实验:

float f = 3.14;
Hello.java:8: 错误: 不兼容的类型: 从double转换到float可能会有损失
   float f = 3.14;

看来默认被当作 double 类型处理。同样想要指定字面量被当作 float 处理,需要在字面量后加上 f 或 F。

float f = 3.14F;

同理,指定字面量被当作 double 类型处理。(其实 D、d 去掉也可以,在基本类型的转换中有讲)

double d1 = 5D;
double d2 = 3d;

有时候你会看见这种写法:double d = .12; 不要疑惑,这等同于 double d = 0.12 ,这个 0 可以省略不写,但一般不要这么做,否则其他人可能会疑惑。

精度

浮点数存放形式:浮点数 = 符号位 + 指数位 + 尾数位,尾数部分很可能会丢失,造成精度损失。(小数都是近似值)(有兴趣去扩充知识了解,这里不赘述)

System.out.println(0.11111111111111111111111111111F);
System.out.println(0.11111111111111111111111111111D);
0.11111111
0.1111111111111111

大概可以这么理解:

float 的精度是保留 8 位有效数字。

double 的精度是保留 16 位有效数字。通常使用 double 类型。

有效数字是一个数从左边第一个不为 0 的数字起,直到末尾止的数字称为有效数字,如 0.009210,有效数字 4 位:9、2、1、0。

保留两位有效数字:0.0092。

由于浮点数运算得到的结果可能有误差,所以如下就成了错误做法:

double d1 = 9.9 / 3;
double d2 = 3.3;

if (d1 == d2) {
  System.out.println(\"它们相等\");  
}

上面的意思是:如果 d1 等于 d2,就输出“它们相等”。

但试着运行,控制台什么都没有输出...

此时再试下访问 d1 的值:

System.out.println(d1);// 3.3000000000000003

看到没有?9.9 / 3 不等于 3.3,而是十分接近 3.3 的小数。

当对运算结果是小数的进行相等判断时,应该以两个数的差值的绝对值,在某个精度范围类判断。这个精度由自己决定,如人民币数值比较,人民币最低面额 1 分 = 0.01 元,只要两数差值小于 0.01,就认为它们相等。

现在该改一下了:

double d1 = 9.9 / 3;
double d2 = 3.3;

if (Math.abs(d1 - d2) < 1.0E-2) {
  System.out.println(\"它们相等\");  
}

还记得如何查 API 文档吗?当导入 java.lang 包下的类时,可以不用 import。哦,对了,你们还不懂,这节在面向对象的包机制中。

那我直接说含义,Math.abs(a):求 a 的绝对值。当 d1 - d2 的绝对值小于 0.01 时,我们姑且认为它们相等。

现在收尾。我还是说说怎么找吧:

打开 JDK API 文档,如果知道 Math 在哪个包下,比如 Math 在 java.lang 包下,点击 java.lang;

1.1 语法入门(更新到冒泡排序)

如果不知道,点击索引找 M 开头的类:

找到后点击 Math,看看 abs 方法的介绍。

3.2.3 字符类型

输出、打印是一个意思。

char 类型的变量可以保存单个字符,占用 2 个字节,取值范围:[0 ~ 65535] 即 [0 ~ 216 - 1]。

char c1 = \'中\';
char c2 = \'a\';

// 字符类型可以直接存放数字,当输出 c2 时,会输出 97 代表的字符,扩充内容中有讲
char c2 = 97;

你们可能会在其他地方遇到如 char c = \'\\n\';,这叫转义字符

一般使用 \\ 开头,代表着将一个字符转义,本质还是单个字符。


\\n:换行
\\t:制表符 tab
\\r:回车
\\u:把十六进制数转成对应的字符


1)\\n:

什么叫换行?

System.out.println(\"你好\\n我好\\n大家好\\n\");
char c = \'\\n\';
System.out.println(c);
System.out.println(2);

这里不得不提一下 ln:

System.out.print(2);
System.out.print(1);
System.out.println();// 代表换行
System.out.print(7);
21
7

ln 表示当前行结束输出,如果要继续输出,就要移到下行。

回过头来,\"你好\"换行,\"我好\"换行,\"大家好\"换行,ln 代表换行。
System.out.println(c);,c 是 \'\\n\',代表换行,ln 代表换行。
结果如下:

你好
我好
大家好



2

2)\\t:

System.out.println(\"狗\\t猫\\t鱼\");
狗      猫      鱼

可以看到狗与猫、鱼之间隔了一个制表符的距离。

3)\\r:

System.out.println(\"我是什么\\r人呢\");

回车:回到行首,输出文本。由此产生分歧,有人认为一个 \\n 足以表示回车加换行,两个字符太浪费了,而 Windows 系统中还保留原来概念,Enter 键(我们通常叫回车键),实际用两个字符表示:“\\r\\n”;对应的 ASCII 码用十进制表示分别为 13、10。

Windows 系统换行 Carriage return and line feed(CRLF):回车加换行

Linux 系统换行 Line feed(LF):换行

回到这里,如果回车但不换行,\\r 后的 “人呢” 还在同行首个位置上输出,把 “我是” 覆盖,最后结果:\"人呢什么\"。

4)\\u:

System.out.println(\"\\u5929\");

16 进制的 5929 对应的字符为天,有兴趣请在扩充内容中的字符编码了解。

这个 \\ 作用不止如此。

问想要使用 char 类型保存单个字符:英文单引号 \' 怎么做?

char c = \'\'\';这样?

Hello.java:4: 错误: 未结束的字符文字
                char c = \'\'\';

像这类具有特殊含义的字符,需要使用 \\ 转成普通的字符,使其不再被认为是代表字符开始的单引号。

char c = \'\\\'\';

同理,打印 \\、\" 等特殊符号,都需要在字符前加上 \\:

System.out.println(\"\\\"\");
System.out.println(\"\\\\\");

输出结果:

\"
\\

3.2.4 布尔类型

boolean 类型只有两个值:true 或 false,用于逻辑运算:2 < 3 为 true。

一般放在 if 、for 等语句的条件处,控制程序的流程,现在不用深究。

3.3 类型转换

内容导视:

  • 基本数据类型转换
  • 基本数据类型与 String 类型的转换

3.3.1 基本数据类型转换

缘起

double d = 3; 唉呀,字面量 3 不是被当作 int 类型处理吗?怎么就可以赋给 double 类型的 d?别急,看完以下实验就明白规则了。

boolean 除外,不同类型容量(取值范围)按从小到大排列:

byte -> short -> int -> long -> float -> double
char -> int -> long -> float -> double

基本数据类型的转换规则

前提:基本数据类型

当所赋值的字面量类型与变量的数据类型不一致时,会发生数据类型转换,从一种数据类型转成另一种数据类型。分为自动类型转换强制类型转换。除了 boolean 类型不能参与转换,其他基本数据类型可以互相转换。

小容量赋给大容量,会发生自动类型转型,如 int 类型自动转为 long 类型:

// int 类型自动转换为 long
long l = 3;

// long 类型自动转为 float
float d = 3L;
System.out.println(d);// 3.0

// char 自动转 int
int i = \'中\';
System.out.println(i);// 20013

\'中\'在 Unicode 字库的序号是 4E2D,使用 UTF-16 编码存储的二进制码为 0100111000101101,即 20013。

大容量赋给小容量,自动转换无法进行:

// 错误: 不兼容的类型: 从 double 转换到 int 可能会有损失
int i = 3.13;
// 错误: 不兼容的类型: 从 long 转换到 int 可能会有损失
i = 3L;
// 错误: 不兼容的类型: 从 int 转换到 byte 可能会有损失
byte b = 532;

这时就需要强制转换符(),但可能会有精度损失。

// 强制把 double 类型转成 int
int i = (int)3.13;
System.out.println(i);// i = 3

// 强制把 long 类型转成 int
i = (int)3L;
System.out.println(i);// i = 3

// 强制把 int 类型转成 byte
byte b = (byte)532;
System.out.println(b);// b = 20

补充细节

1)如果字面量的值没有超出 byte、short、char 的取值范围,可以直接赋值给它们。(除了被当作 long 类型处理的字面量)

byte b = 3;
char c = 33;

byte b1 = \'a\';// \'a\' 表示 97
// 超出 byte 的取值范围,从 char 转换到 byte 可能会有损失
byte b2 = \'中\';// \'中\' 表示 20013

需要注意,我指的是以字面量形式赋值,而不是值传递,自己试试报什么错?

int i1 = 3;
int i2 = 33;
char letter = \'a\';

byte b = i1;
char c = i2;

byte b1 = letter;

之前有人问你怎么知道 \'a\' 代表的整数,我又不想了解 ASCII 码,有什么简单的方法吗?这就是第 2 点,请看:

2)byte、short、char 类型的变量混算时,会自动升级到 int 类型。

怎么得出的这个结论?

byte b1 = 2;
byte b2 = 5;
byte b3 = b1 + b2;
Hello.java:6: 错误: 不兼容的类型: 从 int 转换到 byte 可能会有损失
byte b3 = b1 + b2;

是吧,两个 byte 类型的变量相加,竟然升级了。那我可以利用这一点,让 char 类型的变量升级成 int 变量,自然就知道该字符对应的整数了。

char c = \'中\';
short s = 0;

System.out.println(c + s);// 20013
System.out.println(\'1\' - 0);// 49

3)多种类型混算时,先把字面量转成容量大的那种数据类型,再进行计算。

如果你不知道这点,以后计算很容易吃亏。比如 10 / 4,你本来想得到 2.5,但是这参与运算的数,容量最大的也只是 int 类型,而 int 类型只能保存整数,所以 2.5 被削去了小数。

int i = 10 / 4;
System.out.println(i);// i = 2

这时就需要浮点类型的参与,改一下:

double i = 10.0 / 4;
System.out.println(i);// i = 2.5

思考如下语句可以通过编译吗?

byte b = 10;
b = b * 2;

b 保存的 10 对应 byte 类型,2 对应 int 类型,这就是混算;让 10 升级到 int 类型,与 2 相乘,结果还是 int 类型,再赋给 byte 类型的变量,报错:从 int 转换到 byte 可能会有损失

思考输出什么?

double d = 1 / 4 * 4.0;
System.out.println(d);

有人说这不就是 1 吗?有什么难的?当然也有其他人看出来了:1 / 4 是两个 int 类型的字面量参与运算,结果为 0;(你难道忘了 int 类型只能保存整数吗?)接着算,0 * 4.0 = 0.0;

所以结果为 0.0。(忘记带小数,哪怕只是 .0,结果就完全不同)

你的原意可能是这:double d = 1.0 / 4 * 4.0;

3.3.2 基本数据类型与 String 类型的转换

没想到吧,\"+\" 号除了能计算两数之和外,还能拼接字符串。哦,我好像之前已经用过了。

String str1 = \"我\";
String str2 = \"和\";
String str3 = \"你\";

String str4 = str1 + str2 + str3;
System.out.println(str4);// 我和你

思考输出什么?

System.out.println(4 + 3 * 2 + \"2\" + 5 * 5);

4 + 6 = 10;10 + \"2\" = \"102\";\"102\" + 25 = \"10225\";

没看晕吧?从左至右,乘号优先;字符串加谁,谁就被拼接在一起。那么就利用这个 \"+\",将基本数据类型转成字符串吧。

基本数据类型转成字符串

\"\" 代表空字符串。输出字符串时,是不会输出双引号的,这你应该早就知道了。

long l = 3L;
int i = 3;
char c = \'中\';
double d = 3.243;

String str1 = l + \"\";
String str2 = i + \"\";
String str3 = c + \"\";
String str4 = d + \"\";

System.out.println(str1);// 3
System.out.println(str2);// 3
System.out.println(str3);// 中
System.out.println(str4);// 3.243

字符串转成基本数据类型

别看,等到包装类时就懂了。(或者你会查 API 文档,它们在 java.lang 包下)

String str = \"123\";

byte num1 = Byte.parseByte(str);
short num2 = Short.parseShort(str);
int num3 = Integer.parseInt(str);
long num4 = Long.parseLong(str);
float num5 = Float.parseFloat(str);
double num6 = Double.parseDouble(str);

/*
    取下标为 0 的字符(得到 str 的第一个字符)
	下标是从 0 开始,以 1 递增,并不是从 1 开始哦
*/	
char num7 = str.charAt(0);

// 如果解析不是\"true\"的字符串,那么返回结果是 false
boolean num8 = Boolean.parseBoolean(\"true\");

System.out.println(num1);// 123
System.out.println(num2);// 123
System.out.println(num3);// 123
System.out.println(num4);// 123
System.out.println(num5);// 123.0
System.out.println(num6);// 123.0
System.out.println(num7);// 1
System.out.println(num8);// true

注意不要想着把字符串 \"abc\" 转成整数,编译虽然可以通过,但运行时会报 java.lang.NumberFormatException 异常(数字格式化异常),程序会在抛出异常的位置终止执行。(异常中有讲)

编译时只是检查语法,并不会解析 \"abc\" 是否能够转成整数。

3.x 总结回顾

变量

数据类型 变量名 = 值; 先声明后访问。

数据类型

分为基本数据类型(byte、short、int、long、float、double、char、boolean)与引用数据类型(类、接口、数组)。

不能直接以双等号判断两个浮点类型的值。应该让两数相减得到差值,如果差值在设定的精度范围内就认为它们相等。

3.y 脑海练习

3.1 下面语句能够通过编译吗?

1)

byte b = 2;
b = b * 2 + 10L;

2)

int i = 3;
float f = i + 2.22;

3)

int i = 21;
long l = 231;
double d = 3.23;

long l2 = i + l + d;

4)

int x = (int)3.14 * 3 + 6.2 * 10;

5)

short s1 = 34;
short s2 = s1 - 3;

6)

int i = 3;
char c = (char)i;

7)

byte b1 = 4 * 3;
byte b2 = b1 + (byte)4;

short s = b1 + b2;

8)

double d = 3;
float f = (float)d + .234F;

9)

long l = 3L;
double d = l + 3.24F;

3.2 下面输出结果?

1)

int x = (int)3.14 * 3 + 6 * 10;
System.out.println(x);

2)

int x = (int)(3.14 * 3 + 6.2 * 10);
System.out.println(x);

3.3 想在控制台上输出 \\,怎么写?

3.4 想在控制台上输出如下,怎么写?

第四章 运算符

内容导视:

  • 算术运算符
  • 赋值运算符
  • 关系运算符
  • 逻辑运算符
  • 条件运算符
  • 运算符优先级

4.1 算术运算符

内容导视:

  • 四则运算:+、-、*、/
  • 求余数:%
  • ++、--

4.1.1 四则运算:加减乘除

这个不用过多介绍,与初等数学一致,直接使用即可。

int a = 5;
int b = 2;

int add = a + b;
int sub = a - b;
int mult = a * b;
int divide = a / b;

7、3、10、2;注意 int 类型只能保存整数,会被削去小数部分。

此外 “+” 除了能够计算加法,还能拼接字符串,之前已经说过:

System.out.println(100 + 2 + \"字符串\" + 3 * 8);

“102字符串24”;从左至右,遇到字符串就拼接,拼接后还是字符串;记得乘法优先。

4.1.2 求余数:%

求余数也称取模。得到的余数一定小于除数。

如 10 % 3 = 1、11 % 3 = 2、12 % 3 = 0;

int i = 11 % 4;

11 除以 4,商为 2,余数为 3;所以 i = 3。

% 的本质

a % b = a - a / b * b

知道了上面的式子,自己试着算一下吧:

System.out.println(-10 % 3);
System.out.println(10 % -3);
System.out.println(-10 % -3);

计算结果如下:

-10 % 3 = -10 - (-10) / 3 * 3
        = -10 - (-3) * 3
        = -1
        
10 % -3 = 10 - 10 / (-3) * (-3)
        = 10 - (-3) * (-3)
        = 1
        
-10 % -3 = -10 - (-10) / (-3) * (-3)
         = -10 - 3 * (-3)
         = -1

4.1.3 ++、--

++ 是让变量保存的值,自加一。

int i = 10;
i++;
System.out.println(i);// i = 11
int i = 10;
++i;
System.out.println(i);// i = 11

++ 放在变量前后的区别

++ 放在变量前:

int i = 10;
int j = ++i;
System.out.println(j);// j = 11

等价于

int i = 10;
i = (int)(i + 1);// 先自加一,后赋值
int j = i;
System.out.println(j);// j = 11

++ 放在变量后:

int i = 10;
int j = i++;
System.out.println(j);// j = 10
int i = 10;
int j = i;// 先赋值,后自加一
i = (int)(i + 1);
System.out.println(j);// j = 10

结论:

当 ++ 出现在变量前,先自加一,再赋值;所以 i 自加一等于 11,再赋值给 j 为 11;

当 ++ 出现在变量后,会先赋值,再自加一;把 i 赋值给 j 即 10,再 i 自加 1。

反正 i 一定是 11;使用 ++ 或 -- 时,不会改变运算结果类型

例如:

byte b = 1;
b = b + 1;

会报错从 int 转成 byte 可能会有损失

换成如下就可以了,但是要注意别超了 byte 的取值范围,否则强转后会有精度损失。

byte b = 1;
b++;

-- 同理:

int i = 3;// i = 3
int j = i--;// j = 3、i = 2
int z = --j;// j = 2、z = 2

i = 2、j = 2、z = 2。

4.2 赋值运算符

内容导视:

  • 基本赋值运算符:=
  • 扩展赋值运算符:+=、-=、*=、/=、%=

4.2.1 基本赋值运算符:=

之前用过很多次了,通过 “=” 把字面量赋给变量、完成值传递。

int a = 10;// 10 被赋给了 int 类型的 a
int j = 10;
int i = j;// j 保存的 10 被赋给了 int 类型的 i

4.2.2 扩展赋值运算符:+=、-=、*=、/=、%=

只要学会了 +=,其它触类旁通。

int x = 5;
x += 2;// x = 7

相当于将自保存的值加 2:

int x = 5;
x = (int)(x + 2);// x = 7

使用扩展运算符,不会改变运算结果类型,与 ++ 一样。

看看下面的 i 的值为多少?

byte i = 8;
i *= 2;

i = (byte)(i * 2) = 16。

4.3 关系运算符

就是判断对错。判断结果为真: true,假:false。

使用的符号都是英文半角符号,字符之间没有空格。

!= != ! = 都错。

4.3.1 >、<、<=、>=

大于号、小于号...如同初等数学使用即可。

boolean result1 = 6 > 3;
boolean result2 = 79 < 6;
    
System.out.println(result1);// true
System.out.println(result2);// false

4.3.2 ==

双等号 “==” 是判断保存的值是否相等。

boolean b1 = \"你好\" == \"你好\";
boolean b2 = 5 == 6;

System.out.println(b1);// true
System.out.println(b2);// false
System.out.println(\"你好\" == \"你\");// false
System.out.println(5 == 5);// true

注意别把 “==” 与 “=” 混用了。我知道在日常生活中,使用 “=” 判断是否相等,但是在程序里 “=” 是用来赋值的,“==” 才是用来判断是否相等。

我举个例子,if 旁边的括号中的值只允许为 boolean 类型,当值为 true 时,才会执行 if {} 中的语句。

int i = 4;
if (i = 5) {
    System.out.println(\"Hello\");
}

假如你的原意是想 i 等于 5 时,就输出 “Hello”,但是由于你使用的是单等号,代表赋值,此时的 i = 5,所以原意成了

int i = 4;
if (5) {
    System.out.println(\"Hello\");
}

在 Java 中,正好 boolean 类型不能参与转换,所以编译时直接报错,你可以及时发现,但不是次次都是这么好运气。

如下:

boolean b1 = false;
if (b1 = true) {
    System.out.println(\"Hello\");// Hello
}
System.out.println(b1);// true

原意是当 b1 等于 true 时,输出 “Hello”,但是你的意图被扭曲了,这只是你不小心丢了一个等号而已。原意如下:

boolean b1 = false;
if (b1 == true) {
    System.out.println(\"Hello\");
}
System.out.println(b1);// false

说了这么多,只为说一句:判断两个值是否相等,请用 “==”。

4.3.3 !=

判断两个值是否不相等。

System.out.println(5 != 4);// true
System.out.println(\"你好\" != \"你好\");// false

5 的确不等于 4,判断为真,所以为 true。

之所以把 “==”、“!=” 单独拿出来,是因为它们与 “<”、“>=” 之类的符号不大一样,“!=” 与 “==” 不仅可以判断数值是否相等,还可以比较引用类型是否相等。

System.out.println(\"hel\" != \"hell\");// true
System.out.println(\"u\" == \"u\");// true
System.out.println(\"u\" < \"u\");// 报错,< 的两边只能为数值,如整数、小数

这里不得不提:\'a\' < \'b\' 为什么可以编译通过,因为这是 char 类型,可以转成 int 类型,比较大小。

4.4 逻辑运算符

就是与或非逻辑,逻辑运算符的两边要求都是布尔类型,且最终的结果也是布尔类型,下面是我高中时记的口诀:

与(&):一假为假

或(|):一真为真

非(!):真即假,假即真

异或(^):不同为真

上面的符号还是位运算符,由于不常用,略去用法。

4.4.1 &

a & b:当 a 和 b 同时为 true 时,结果才为真,否则为 false。

boolean b1 = 5 > 3;
boolean b2 = 8 < 9;
System.out.println(b1 & b2);// true

翻译成人话:5 大于 3 且 8 小于 9,命题为真。

需要注意的是,不同的运算符优先级不同,优先级高的先运算。之前通过乘法与加法的混合运算中就能看出来乘法优先。

思考如下输出结果:

boolean b1 = 1 < 0;
boolean b2 = 79 > 99;
boolean b3 = \"我\" != \"我\";

System.out.println(b1 & b2 == b3);

b1、b2、b3 都为 false;b1 & b2 为 false,false 等于 b3,所以结果为 true?

但你没有想到 “==” 的优先级高于 “&”,所以是 b2 == b3 先运算为 true,b1 & true,结果为 false。

上面的一句相当于:

System.out.println(b1 & (b2 == b3));// false

那该怎么办?扭曲了原意!可以加小括号提升优先级:

System.out.println((b1 & b2) == b3);// true

短路与:&&

&& 相比于逻辑与,效率更高:如果整个表达式结果已经确定,剩下式子不再执行与判断。

什么意思?

一假为假,当左边的值为 false,还需要去执行去判断右边吗?完全可以推出结果为 false。

int x = 3;
System.out.println((x < 2) & (++x > 3));
System.out.println(x);// 4
int x = 3;
System.out.println((x < 2) && (++x > 3));
System.out.println(x);// 3

x < 2 为 false,那么整个式子已经可以确定为 false,不需要再判断。通过上面的例子可以看出,短路与并没有去执行 ++x,所以 x 的结果不变,还是为 3.

4.4.2 |

a | b:当 a 和 b 同时为 false 时,结果才为 false,否则为 true。

System.out.println(8 < 1 | 9 > 3);// true

命题:8 小于 1 或者 9 大于 3,命题为真。

与 & 一样的是,| 也有对应的短路或。

||

如果整个表达式的结果已经确定,后面不再执行判断。

int x = 3;
System.out.println(8 < 9 | ++x == 8);
System.out.println(x);// 4
int x = 3;
System.out.println(8 < 9 || ++x == 8);
System.out.println(x);// 3

一般我们使用的是短路与和短路或,我几乎没有看见单个的,除非你想把所有的式子都执行一遍。

4.4.3 !

!a:对 a 取反;a 为 true,结果为 false;a 为 false,结果为 true。

System.out.println(!(3 < 5));// false

3 < 5 为 true,再取反为 false。

4.4.4 ^

a ^ b:当 a 不等于 b,结果为 true,否则为 false。

System.out.println(8 < 9 ^ 9 > 3);// false

8 < 9 为 true,9 > 3 为 true,它们相等,所以为 false。

4.5 条件运算符

算是条件语句的简化版吧,可以根据表达式的真假返回不同的值。

4.5.1 三目运算符

语法:布尔值 ? 值1 : 值2

当布尔值为 true,返回值1;为 false,返回值2。

boolean isBoy = true;
char zhangSan = isBoy ? \'男\' : \'女\';

System.out.println(zhangSan);// 男
double scope = 99.5;
String evaluate = scope < 60 ? \"不及格\" : \"及格\";
System.out.println(evaluate);// 及格

很明显 scope 小于 60 为 false,所以返回值2:“及格”。

4.6 运算符优先级

优先级高的先运算。

优先级从高到低

. ()
++ -- ~ !						单目运算符
* / %							算术运算符
+ -	
<< >> >>>						位移运算符
< > <= >= instanceof			比较运算符
== !=							逻辑运算符
&
^
|
&&
||
? :								三目运算符
= *= /= %=						赋值运算符
+= -= <<= >>=
>>>= &= ^= |=

例如:int a = 1 + 2 * 1; 先算乘法,接着加法,最后赋值。

4.x 总结回顾

++、+= 等赋值运算符不会改变运算结果类型。(使用了强制转换运算符)

判断两个值是否相等请用 “==”。

逻辑运算符中一般使用短路与、短路或。

4.y 实战演练

4.1 double i = 5 / 2; 中的 i 的值为?

4.2 输出什么?

1)

String s = \"张三\";
int i = 3;
System.out.println(s + i * 2 + \"b\");

2)

int x = 5;
int y = 5;
System.out.println(++x < 6);
System.out.println(y++ < 6);
System.out.println(y);

3)

int x = 10;
int a = x + (x++);
int b = x + (++x);
System.out.println(a);
System.out.println(b);
System.out.println(x);

4)

int i = 34;
int j = i--;
int z = --i;
System.out.println(i);
System.out.println(j);
System.out.println(z);

5)

int i = 2;
int j = 2 + i++;
System.out.println(i);
System.out.println(j);

6)

int i = 10;
i = ++i;
System.out.println(i);

7)

int i = 10;
i = i++;
System.out.println(i);

8)

byte b = 1;
byte b1 = b++;
byte b2 = --b;
System.out.println(b);
System.out.println(b1);
System.out.println(b2);

9)

boolean b = false;
System.out.println(b = true);
System.out.println(b == false);

10)

int x = 9;
int y = 12;

int z = x < y ? x + y : x - y;
System.out.println(z);

11)

int a = 10;
int b = 99;

int result = a > b ? a++ : b--;
System.out.println(result);
System.out.println(a);
System.out.println(b);

4.3 利用三目表达式求出三个数之间的最大数。

4.4 利用所学知识求出 33 天是多少个星期零几天?

4.5 3 / 9 * (242.2 - 100) 的结果是什么?

4.6 下面代码输出什么?

1)

boolean x = true;
boolean y = false;

byte num = 2;

if ((num++ == 2) && (y = true)) {num++;}
if ((x = false) || (++num == 5)) {num++;}

System.out.println(num);
System.out.println(x);
System.out.println(y);

2)

int i = 342;
int b = ++i + i;

System.out.println(b);

3)

int i = 342;
int b = i++ + ++i;

System.out.println(i);
System.out.println(b);

第五章 流程控制语句

内容导视:

  • 输入与输出
  • 分支控制语句
  • 循环控制语句
  • 转向控制语句

5.1 输入与输出

内容导视:

  • 接收输入
  • 普通输出
  • 格式化输出

5.1.1 接收输入

我们需要用到 java.util 包下的 Scanner 类,非 lang 包下的类需要导入,在首行加 import:

import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        // 创建一个扫描仪实例,扫描指定输入流产生的值
        Scanner scanner = new Scanner(System.in);
        
        System.out.print(\"等待你的输入,请按下任意键:\");
        
       	// 等待输入字符串,使用 String 类型的变量 name 接收
        String name = scanner.next();
        System.out.println(\"你输入的字符串为:\" + name);
        
        System.out.print(\"等待你输入整数:\");
        
        // 等待输入整数,使用 int 类型的变量 num 接收
        int num = scanner.nextInt();
        System.out.println(\"你输入的整数为:\" + num);
        
        /*
		scanner.nextDouble()是接收小数
        scanner.next().charAt(0)是接收字符
        */
    }
}

不想输入,请按 Ctrl + C 结束程序。

注意,让你输入整数时,你却输入了其它字符,会报 java.util.InputMismatchException 输入不匹配异常。

5.1.2 普通输出

输出与打印是一个意思,就是在控制台上显示一些文本信息。

System.out.println(\"向控制台输出一些内容\");

println 是 print line,即打印并换行:如果接着打印,内容会在下一行显示;如果想下次打印时,文本在同一行,去掉 ln。

5.1.3 格式化输出

就是把数据按指定格式输出,使用 printf 配合占位符 %,先把地方占着,再接收数据填充上去;

由于没有 ln,需要使用转义字符 \\n 手动换行,%d 是给整数占位置,%s 是给字符串占位置。占位后,需要传入值,从左至右,顺序不可颠倒,数据类型必须要对应上。

下面是常用的占位符:

占位符 解释
%d 给整数占位
%x 给整数占位,输出的整数以十六进制显示
%f 给小数占位
%e 给小数占位,输出的小数以科学计数法显示
%s 给字符串占位
String ln = \"\\n\";
System.out.println(\"班级人员详情:\");
String str = \"序号:%d,姓名:%s\" + ln;		

System.out.printf(str, 101, \"张三\");
System.out.printf(str, 102, \"猛男\");
System.out.printf(str, 103, \"武丑\");

System.out.println();

double num = 3.14256;
double num2 = 100123450.0;
int num3 = 15;
System.out.println(\"数字测试:\");
System.out.printf(\"这是小数:%f\" + ln, num);
System.out.printf(\"%f 保留 2 位小数:%.2f\" + ln, num, num);
System.out.printf(\"%f 的科学计数法显示:%e\" + ln, num2, num2);
System.out.printf(\"%d 对应的十六进制:%x\" + ln, num3, num3);

5.2 分支控制语句

分支控制语句也称选择语句。

内容导视:

  • if else
  • switch case

程序一般自上而下,逐行执行,但是有时也需要特殊操作;比如根据条件选择性地执行某段代码、循环执行某段代码...

5.2.1 if else

if else 语句最多只会执行一条分支,类似走路遇见岔路,只能选一条通过。先从最简单的 if 讲起。

单条分支

语法:

if (布尔类型的值) {
    java 语句...
}

当布尔类型的值为 true 时,才会执行 {} 中的 java 语句,如下:

int age1 = 11;
int age2 = 8;
if (age1 > age2) {
    System.out.println(\"我比你大\");// 此句话会输出
}

当 {} 中只有一条语句时,可以省略 {};但是最好不要那么做,以免别人误解。

if (age1 > age2) System.out.println(\"我比你大\");

if else

语法:

if (布尔类型的值) {
    分支一...
} else {
    分支二...
}

布尔类型的值为 true,执行第一条分支;为 false,执行第二条分支。

char sex = \'1\';

if (sex == \'0\') {
    System.out.println(\"输出女\");
} else {
    System.out.println(\"输出男\");// 此句会输出
}

多条分支

语法:

if (值1) {
    分支一...
} else if (值2) {
    分支二...
} else if (值3) {
    分支三...
} ...

从上至下,只执行第一个值为 true 的分支;else if 可以无限追加。

double scope = 100.0;

if (scope < 60) {
    System.out.println(\"你的成绩不及格\");
} else if (scope < 80) {
    System.out.println(\"你的成绩一般\");
} else if (scope < 90) {
    System.out.println(\"你的成绩良好\");
} else if (scope < 100) {
    System.out.println(\"你的成绩优秀\");
}

上面的分支都不会执行,因为从上至下,没有布尔表达式为 true 的。

int age = 20;
if (age < 18) {
    System.out.println(\"未成年人\");
} else if (age < 35) {
    System.out.println(\"青年人\"); // 此句将会输出
} else if (age < 50) {
    System.out.println(\"中年人\");
}

执行第二条分支。

if else if ... else

语法:

if (值1) {
    分支一...
} else if (值2) {
    分支二...
} else if (值3) {
    分支三...
} ...
} else {
    最后的分支
}    

当所有分支括号里的值都为 false 时,执行最后的分支。

int age = 888;
if (age < 18) {
    System.out.println(\"未成年人\");
} else if (age < 35) {
    System.out.println(\"青年人\"); // 此句将会输出
} else if (age < 50) {
    System.out.println(\"中年人\");
} else if (age < 200) {
    System.out.println(\"老年人\");
} else {
    System.out.println(\"妖怪吧!\");// 由于上面分支都不满足,执行此条分支
}

嵌套分支

if 语句中有 if 语句。

double scope = 120;

if (scope >= 0 && scope <= 100) {
    // 语句块 1
    if (scope < 60) {
   		System.out.println(\"你的成绩不及格\");
	} else if (scope < 80) {
	    System.out.println(\"你的成绩一般\");
	} else if (scope < 90) {
   		System.out.println(\"你的成绩良好\");
	} else {
    	System.out.println(\"你的成绩优秀\");
	}
    
} else {
    // 语句块 2
    System.out.println(\"这是人能考出的成绩?\");
}
/*
只有分数在 [0 ~ 100] 之间才会执行语句块 1,否则执行语句块 2
*/

嵌套最好不要超过三层,否则人容易迷惑,可读性太差。

5.2.2 switch case

语法:

switch (值) {
	case 字面量1:
		语句块1;
		break;
		
	case 字面量2:
    	语句块2;
    	break;
    	
    case 字面量3:
    	语句块3;
    	break;
    ...
    default:
    	语句块4;
}

从上至下,执行字面量等于 switch 括号中的值的 case 分支语句,遇见 break 结束 switch 语句。

default 如同 else 一样,可以不写;当没有一个匹配上,就执行此分支的语句。

char key = \'s\';

switch (key) {
    case \'w\' :
        System.out.println(\"上\");
        break;
    case \'s\' :
        System.out.println(\"下\");// 此条将会被输出
        break;   
    case \'a\' :
        System.out.println(\"左\");
        break;  
    case \'d\' :
        System.out.println(\"右\");
        break;  
    default:
        System.out.println(\"其它\");
}

注意

  1. case 后的字面量对应的数据类型必须与 switch 括号中的值的类型一致,或者可以自动转成此类型。
  2. case 后的字面量值不能重复。
  3. switch 括号中的值的数据类型只能是 int、String、Enum。(能够自动转换为这三种类型的也算)
  4. case 后的值不能是变量,只能是字面量与常量。

case 穿透现象

当执行某条 case 分支的语句时,如果没有 break 语句结尾,直接顺序执行之下的所有 case、default 分支的语句;因为只有执行了 break,才会退出 switch 语句。

看清楚了,我把 每条分支的 break 都略去了。

String weather = \"sunny\";

switch (weather) {
    case \"cloudy\" :
        System.out.println(\"阴天\");
    case \"sunny\" :
        System.out.println(\"晴天\");
    case \"rain\" :
        System.out.println(\"雨天\");
    default:
        System.out.println(\"其它天气\");
}

当输出 “晴天” 时,由于没有遇到 break 语句,继续执行之下语句,输出 “雨天”、“其它天气”。

打个比方吧:

在一个神奇的小区,房子一排排却单向连通,一旦进入其中一家,便可以由内部通道去往下一家。有的住户觉得很不安全,偷偷做了防范措施。

一个小偷捡到了一把锁,尝试着用它开启一户户门,从巷头走到巷尾。诶,有一扇门开了,进去拿了东西,但是主人安了警报铃,小偷马上 GG 了。另一个小偷也来了,也打开扇门,恰好主人家忽视了防护,小偷偷完这家后,直接顺着通道前往下一家,居然都没防范措施,直接全部偷完。
image-20220330215204967
case 合并

如果多个 case 分支执行的是相同的语句,可以将其合并:

char sex = \'男\';
switch (sex) {
    case \'0\' : case \'女\' :
        System.out.println(\"我有四个蛋\");
        break;
    case \'1\' : case \'男\' :
        System.out.println(\"咖喱?什么咖喱?\");
        System.out.println(\"快还给我!这是我的!\");
        break;
    default:
        System.out.println(\"只想守护你\");
}

当 sex 等于 \'0\' 或 \'女\' 时,执行第一条分支;sex 等于 \'1\' 或 \'男\' 执行第二条分支;都不满足执行默认分支。

5.3 循环控制语句

内容导视:

  • for
  • while

有些时候,我们需要重复执行某些事情...

5.3.1 for

语法:

for (初始化表达式; 布尔值(循环条件); 更新表达式) {
    循环体中的语句
}

执行顺序:

先执行初始化表达式,如果循环条件为 true,执行循环体中的语句;

执行完后,执行更新表达式,如果循环条件为 true,执行循环体中的语句...

直到循环条件为 false,退出 for 语句。

示例:

for (int i = 0; i < 2; i++) {
    System.out.println(\"两指夫人\" + i);
}

分析:

i --> 0;
此时 i 小于 2,为 true,执行循环体中的语句,输出:两指夫人0

执行更新表达式 i++
i --> 1;
此时 i 小于 2,为 true,执行循环体中的语句,输出:两指夫人1

执行更新表达式 i++
i --> 2;
此时 i 小于 2 为 false,退出循环

小技巧:

1)由于定义在 for () 中的变量的作用域仅在 for 循环体内:

for (int i = 0; i < 2; i++) {}
System.out.println(i);
Hello.java:8: 错误: 找不到符号
System.out.println(i);

为了能够在其它地方能够访问到此变量,可以将其提取出来。

int i = 0;
for (; i < 2; i++) {}
System.out.println(i);

2)初始化语句与更新表达式可以有多个,使用 , 隔开。

for (int i = 1, j = 2; i < 4 && j < 6; i++, j += 2) {
    System.out.println(\"i:\" + i 
		+ \",j:\" + j);
}

分析:

i --> 1,j --> 2
i < 4 && j < 6 为 true,执行循环体,输出:i:1,j:2

执行更新表达式:i++、j += 2
i --> 2,j --> 4
i < 4 && j < 6 为 true,执行循环体,输出:i:2,j:4

执行更新表达式:i++、j += 2
i --> 3,j --> 6
i < 4 && j < 6 为 false,结束 for 循环

5.3.2 while

语法:

while (布尔值(循环条件)) {
    循环体中的语句
}

如果布尔值为 true,执行循环体;执行结束后再判断布尔值...直到布尔值为 false,退出循环。

示例:

int i = 0;
while (i < 3) {
    System.out.println(i);
    i++;
}

输出 0、1、2

do while

语法:

do {
    循环体中的语句
} while (布尔值);

先执行循环体,执行结束后判断布尔值,如果为 true,执行循环体,再判断布尔值...直到布尔值为 false,退出循环。

与 while 不同的是,do while 是先执行再判断,所以一定会执行一次。

示例:

int i = 0;
do {
    System.out.println(i);
    i++;
} while (i < 3);

输出 0、1、2

嵌套循环

以 for 循环为例。之前先讲过嵌套,你就理解为套娃就行了,看到阿衰他妈给阿衰做了一笼包子,结果大脸妹打开包子一看,包子里有多个小包子,继续打开,更小的包子...无穷尽也,很影响食欲。嵌套最好不要超过三层,怕人发昏。

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        System.out.println(j);
	}
}

分析:

当 i = 0 时,执行循环体输出 0、1、2,当 j = 3 时,不满足条件,结束内层 for 循环
当 i = 1 时,执行循环体输出 0、1、2...
当 i = 2 时,执行循环体输出 0、1、2...

当 i = 3 时,不满足循环条件,退出外层 for 循环

外层的 for 一共循环执行 3 次;每次外层循环时,内层的 for 会循环执行 3 次;也就是内层的循环体中的语句一共执行 3 * 3 = 9 次。

5.4 转向控制语句

内容导视:

  • break
  • continue
  • return

终止或跳过循环...

5.4.1 break

break 代表终止语句块的执行(不再执行),一般用于 switch case、for、while 中。

举例:当 i = 2 时,终止循环。

int i = 0;
while (i < 10) {
    if (i == 2) {
        System.out.println(\"终止了循环\");
        break;
    }
    System.out.println(i);
    i++;
}

输出 0、1;当 i 等于 2 时,执行 if 语句,输出 “终止了循环”,接着执行 break 终止 for 循环。

当 break 用在嵌套循环中,默认终止最近的循环体。

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 2; j++) {
        if (j == 1) {
            break;
        }
        System.out.println(j);
    }
}

分析:

当 i = 0 时,输出 0
当 i = 1 时,输出 0
当 i = 2 时,输出 0
因为当 j = 1 时,满足了 if 条件,执行了 break,终止了里层的 for 循环。
(只是代表里层的循环执行结束,但外层循环还在继续执行)
当 i = 3 时,不满足条件,退出外层循环。

也可以自定义标签,终止标签对应的循环。

key1: for (int i = 0; i < 3; i++) {
    key2: for (int j = 0; j < 2; j++) {
        if (j == 1) {          
            break key1;
        }
        System.out.println(j);
    }
}

输出 0;当 j = 1 时,执行了 break,终止 key1 对应的 for 语句。

5.4.2 continue

跳过本次循环。(提前结束本次循环)

int i = 0;
while (i < 6){
    i++;
    if (i == 3){
        continue;
    }
    System.out.println(i);

}

当 i = 3 时,执行了 continue,跳过本次循环,没有执行输出语句,直接进入下一次循环。所以输出 1、2、4、5、6。

需要注意的是,此时我把 “i++” 放在了前面;因为 i++ 如果在 continue 后,当 i = 3 时,跳过本次循环,没有执行 i++,下次循环 i 还是 3,一直跳过,无限循环,永远执行不到 i++。

5.4.3 return

当方法没有返回值(void)时,使用 return; 代表结束当前方法。(在 main 方法中使用,代表结束程序)

public static void main(String[] args) {
    // for
    for (int i = 0; i < 6; i++) {
        System.out.println(i);
        if(i == 4) {
            return;
        }
	}
    // 其它语句
	System.out.println(\"代码块\");
}

输出 0、1、2、3、4 后,当 i = 4 时,执行 if 语句中的 return,结束 main 方法的执行。

如果把 return 换成 break,只结束了 for 循环,接着还会输出 “代码块”。

return 以后讲方法返回值时还会用到,现在只需了解它能够终止方法执行即可。

5.x 总结回顾

switch case 语句注意 break 不可省略,以免出现 case 穿透现象。

for 与 while 循环注意控制循环结束条件,防止布尔值一直为 true,避免无限循环。

5.y 掀起波澜

5.1 打印 [n,m] 之间的所有是 x 倍数的整数,统计个数及总和。

5.2 输出 1 + (n - 1) = n、2 + (n - 2) = n...50 + (n - 50) = n。

5.3 打印九九乘法口诀表。

5.4 打印一个 n 层的空心金字塔。例:当 n = 5 是,输出如下:

    *
   * *
  *   *
 *     *
*********

5.5 找出 3 位数的水仙花数,水仙花数是各位数的三次方之和等于此数,比如 153 = 13 + 53 + 33 = 153。

5.6 计算 1 - 1/2 + 1/3 - 1/4 +....- 1/100。

5.7 计算 1 + (1 + 2) + (1 + 2 + 3) +....+ (1 + 2 + 3 + 4 +...+ 100)。

第六章 数组

内容导视:

  • 一维数组
  • 多维数组
  • 排序
  • 查找

6.1 一维数组

内容导视:

  • 数组介绍
  • 一维数组的声明
  • 一维数组的遍历
  • 数组元素默认值
  • 数组赋值机制

6.1.1 数组介绍

数组是一种数组结构,当成一个容器吧,只能存放同一类型的元素。如 int 数组只能存放 int 类型的元素。(包括自动转换成 int 的元素)

数组一旦创建,长度不可变。如:

int[] arr = new int[5];

此数组实例的长度被确定为 5 了,不可改变。

数组中的元素的内存地址是连续的,数组拿首元素的内存地址作为整个数组的内存地址。

数组对象都有 length 属性,可以获取数组的长度。int length = arr.length;

每个元素都有下标,从 0 开始,以 1 递增,如最后一个元素的下标是 length - 1,首元素的下标为 0。

下标也被称为索引、index。

为什么数组长度不可变

int[] arr = new int[3];
arr = new int[6];
/*
这并不是改变了原数组的长度,而是重新在堆中创建了一个新的数组实例
将地址重新赋给引用,原来的数组实例由于没有引用指向被回收
*/

堆是一块内存空间,用来存放实例(我有时候也称对象)。引用是指保存了实例的内存地址的变量,可以通过引用操纵实例,如同遥控器控制电视一样。

语言设计者对需要执行的任务分配给不同的结构,数组具有固定长度,因为它旨在成为开发人员可以构建的低级别、简单的实现。

计算机内存有限,如果可以延长数组,但旁边的内存已经存了另一个对象怎么办?数组各个元素是彼此相邻存储,延长不了。

固定长度后,如果数组没有足够的空间存放元素,那么可以找到更大的空白空间创建一个新的更大的数组存放原有数组的数据,同时也可以腾出原数组的空间。

如果不确定数组长度多大,可以使用 List 集合存放数据。

集合底层已经实现好了,当数组快装满时,会在更大的空白处创建一个长度更大的数组,将原有数据复制到新数组,不需要我们手动实现。

为什么数组下标从 0 开始

  1. 历史原因

    之前 C 语言数组下标也是从 0 开始,没有必要出一种语言就改一次下标,增加额外的学习和理解成本。

  2. 减少 CPU 指令运算

    1)下标从 0 开始,计算第 i 个元素的地址,arr[i] = 首地址 + i * 单个元素所占字节。

    2)下标从 1 开始,计算第 i 个元素的地址,arr[i] = 首地址 + (i - 1) * 单个元素所占字节。

    每次寻找地址时,多了一次 i - 1 即减法的指令运算,更加消耗CPU 资源。(把大脑想象成 CPU,每次寻址时多计算一次二进制的减法)

使用数组存放元素的优点:根据下标查询元素效率极高

  • 每个元素的内存地址在空间上是连续的。
  • 数组中每个元素类型相同,占用空间大小一样。
  • 知道首元素的内存地址、每个元素的占用空间大小,通过下标可以算出元素的内存地址,直接通过内存地址定位元素。

缺点

  • 由于保证数组中每个元素的内存地址连续,随机增删元素时,会涉及到后面元素统一向后或向前位移的操作,效率较低。
  • 数组不能存储大数据量,因为很难在内存中找一块特别的大的连续的空间。

简化版本如下

数组是什么

  • 存储同一类型元素的容器,引用类型。

数组的优缺点

  • 根据下标查询元素效率极高
  • 随机增删元素效率低

为什么数组下标从 0 开始

前提:

  • 数组的每个元素的内存地址是连续的

  • 每个元素占用空间一样

知道首元素内存地址,可以算出第 i 个下标的元素的内存地址。设元素是 int 类型:(首地址即第一个元素的内存地址)

  • 如果下标从 1 开始,arr[i] 的地址 = 首地址 + (i - 1) * 4
  • 如果下标从 0 开始,arr[i] 的地址 = 首地址 + i * 4

很明显从 1 开始多算了一次减法,消耗的 CPU 更多。

为什么数组长度不可变

  • 数组是连续不断的

    • 如果数组可以延长,如果旁边恰好存了一个对象怎么办?
    • 如果数组可以缩减,那么突然空出来了一小块空间,别人如何利用?
  • 解决

    固定数组长度,如果数组满了:

    另寻一个更大的空间创建更大的数组,把原有数据填入新数组。旧数组无引用指向被当作垃圾回收,释放空间。

6.1.2 一维数组的声明

静态初始化

在声明时,同时确定了元素的值。

int[] array = {1, 2, 92, 64, 90};
/*
也可以这么写:
int[] array = new int[]{1, 2, 92, 64, 90};
有时候传参,或者先声明了 array,需要用到
*/

创建了一个 int 类型、长度为 5 的数组,引用名为 array,存放了 5 个 int 类型的元素,分别为 1、2、92、64、90。

上面图的元素,下标从左到右,依次为 0、1、2、3、4。

通过引用访问下标对应的元素:

// 访问第 1 个元素
int a1 = array[0];
// 访问第 5 个元素
int a5 = array[4];
// 获取数组的长度
int length = array.length;

System.out.println(a1);// 1
System.out.println(a5);// 90
System.out.println(length);// 5

动态初始化

int[] array2 = new int[3];
/*
可以先声明,后分配空间
int[] array2;
array2 = new int[3];
*/

创建了一个 int 类型、长度为 3 的数组,引用名为 array2,存放了 3 个 int 类型的元素,值都默认为 0。

System.out.println(array2[0]);// 0

赋值

// 把 53 赋给下标为 0 的元素
array2[0] = 53;
array2[1] = 2;
array2[2] = 44;

System.out.println(array2[0]);// 53

int[] arr1;// 此变量没有保存任何值,必须赋值(初始化)才能够访问
int[] arr2 = null;// 此变量保存了 null,代表空
int[] arr3 = {};// 创建了一个长度为 0 的数组(数组无元素),地址赋给了 arr3
int[] arr4 = new int[0];// 同上

6.1.3 一维数组的遍历

数组中的元素下标从 0 到 length - 1,可以使用 for 循环访问每个元素。

int[] arr = {6, 2, 9};

for (int i = 0; i < arr.length; i++) {
    int num = arr[i];
    System.out.println(num);
}

注意:当访问不存在的下标时,会报数组索引越界异常。

int i = arr[3];
Exception in thread \"main\" java.lang.ArrayIndexOutOfBoundsException: 3
        at Hello.main(Hello.java:5)

6.1.4 数组元素默认值

使用动态初始化时,不同类型的元素会有默认值。

数据类型 默认值
short、byte、int、long 0
float、double 0.0
boolean false
char \'\\u0000\'
引用类型包括 String、数组 null
long[] arr = new long[3];
byte b = arr[0];// 报错:从 long 转换到 byte 可能会有损失

特别说明一下:

char 的 \'\\u0000\' 并不是空格,而是空字符,代表什么都没有。

char[] arr = new char[3];
arr[1] = \' \';
System.out.println(arr[0] + 0);// 0
System.out.println(arr[1] + 0);// 32

\'\\u0000\' 对应整数 0,另一种写法 \'\\0\',是字符的八进制表示。打个比方,\'a\' 对应的 8、10、16 进制分别为 0141、97、0x0061,如下都是一个意思。

char c1 = 97;
char c2 = 0141;
char c3 = 0x0061;
char c4 = \'\\141\';
char c5 = \'a\';

6.1.5 数组赋值机制

int[] arr1 = {56, 6, 2};
// 将 arr1 保存的值拷贝一份,赋给 arr2
int[] arr2 = arr1;

arr1 保存的值为数组实例的内存地址,假设为 0x1111,然后拷贝一份地址(创建副本)给 arr2,本质还是值传递。

修改 arr2 保存的值,arr1 不会受到影响。

arr2 = null;
System.out.println(arr1);// [I@15db9742(等同于实例的内存地址)

本来应该就此打住,但是有人可能还接触过引用传递这个概念:

值传递(pass by value):赋值时将值拷贝一份赋给另一个变量,这样在另一个变量中修改自己保存的值,不会影响到最初的变量;

类似 Ctrl + C、Ctrl + V。

引用传递(pass by reference):赋值时将值的地址赋给另一个变量,另一个变量通过内存地址定位到此值,如果修改此值,会影响原来变量保存的值;

类似 Windows 系统的创建桌面快捷方式。

值传递 引用传递
会创建副本 不会创建副本
无法改变原变量保存的值 可以改变原变量保存的值

有人就问了:

int[] arr1 = {6, 62, 2};
int[] arr2 = arr1;

// 使用 arr2 改变了数组中第一个元素的值
arr2[0] = 99;
// 你看看,这不影响了 arr1 吗?
System.out.println(arr1[0]);// 99

不不不,不是这个意思,把 arr1、arr2 当成两个独立的遥控器吧,电视只有一个,arr2 换台,受影响的是电视,而不是 arr1。

这里修改的是数组中的元素,而不是 arr1 保存的值,arr1 保存的值还是这个实例的地址,没受影响。

结论:arr1 把自己保存的值拷贝一份赋给了arr2,只不过这个值恰好是地址,就当作是值传递吧。

数组实例 {6, 62, 2} 把地址赋给 arr1,才有点引用传递的味道。

由于不能直接通过实例访问到元素,必须借助引用;当没有引用时,再也不可能访问到此实例,等同于垃圾。

有人说,不对啊?必须借助引用访问元素这我知道,如 arr[0];可当没有引用时,我可以重新把这个实例的地址赋给另一个变量,又来了一个引用,这不就可以访问到了吗?

int[] arr1 = {5, 6, 2};// 保存实例的地址的变量称为引用
// 现在 arr1 不是 {5, 6, 2} 的引用了
arr1 = null;

// 重新将此实例的地址赋给 arr2
int[] arr2 = {5, 6, 2};
// 我访问到了!
System.out.println(arr2[1]);// 6

可是,你确定第 1 行与第 6 行的 {5, 6, 2} 是同一个实例吗?

要想确认是否是同一个实例,需要借助引用修改这个实例的值,再看看实例是否被修改了。如果被修改了,说明是同一个实例。

int[] arr1 = {5, 6, 2};
int[] arr2 = {5, 6, 2};

// 修改 arr1 对应的实例第一个元素为 9
arr1[0] = 9;

// 访问 arr2 对应的实例第一个元素,还是 5
System.out.println(arr2[0]);// 5

然而并没有被修改,说明不是同一个实例。所以第 1 行的 {5, 6, 2} 永远不可访问到了,这种访问不到的实例已经没有用处,需要及时被垃圾回收器清理。

也可通过直接输出引用、双等号比较,查看是否是同一个实例:

int[] arr1 = {5, 6, 2};
int[] arr2 = arr1;
int[] arr3 = {5, 6, 2};

// arr1 与 arr2 保存的值一样
System.out.println(arr1 == arr2);// true
// arr1 与 arr3 保存的值不一样,说明保存的不是同一个实例的内存地址
System.out.println(arr1 == arr3);// false

System.out.println(arr1);// [I@15db9742
System.out.println(arr2);// [I@15db9742
System.out.println(arr3);// [I@6d06d69c

于是我们通常说:基本数据类型使用双等号比较的是值,引用类型使用双等号比较的是内存地址。

那如果有人只认为保存的内容相等就行,不在乎是否为不同实例,在源代码最上面加入 import java.util.Arrays;

int[] arr1 = {5, 6, 2};
int[] arr2 = arr1;
int[] arr3 = {5, 6, 2};

// equals 比较内容
System.out.println(Arrays.equals(arr1, arr3));// true
System.out.println(arr1 == arr3);// false

6.2 多维数组

内容导视:

  • 二维数组声明
  • 遍历二维数组
  • 静态方法调用

6.2.1 二维数组声明

二维以上的数组用的很少,故略去。

声明二维数组

int[][] arr1;
int arr2[][];
int []arr3[];

一般使用第一种。

静态初始化

二维数组由多个一维数组组成(二维数组的每一个元素是一维数组),三维数组有多个二维数组组成...

int[] i1 = {5, 6, 7, 8};
int[] i2 = {63, 262, 2};
int[] i3 = {5, 2, 6};

int[][] arr1 = {i1, i2, i3};
int[][] arr2 = {
    {252, 26, 3},
    {1, 2, 3},
    {6, 4, 2}
};

动态初始化

语法:

数据类型[][] 数组名 = new 数据类型[二维数组的长度][一维数组的长度];

例:

int[][] arr = new int[2][3];

代表创建了一个 int 类型、长度为 2 的二维数组;(对于引用类型,只保存内存地址)

而二维数组中的每个元素都保存着 int 类型、长度为 3 的一维数组的内存地址。(但通常说二维数组里的每个元素是 int 类型、长度为 3 的一维数组)

访问

// 二维数组中的每一个元素都是一维数组,如 arr[0] 是 int[] 类型
int[][] arr = new int[2][3];
// arr[0] 代表二维数组中下标为 0 的元素,也就是一维数组,把一维数组的地址赋给 arr1
int[] arr1 = arr[0];

// 给 arr1 数组的下标为 0 的元素赋值 67
arr1[0] = 67;

// 获取 arr1 数组下标为 0 的元素
int num1 = arr1[0];
System.out.println(num1);

合并

int[][] arr = new int[2][3];
// 下标为 0 就是数组中的第一个元素
// 给 arr 下标为 0 的一维数组中的下标为 0 的元素赋值 67
arr[0][0] = 67;

// 访问 arr 下标为 0 的一维数组中的下标为 0 的元素
int num1 = arr[0][0];
System.out.println(num1);

例子:

int[][] arr = new int[2][3];
// 赋值
arr[0][0] = 52;
arr[0][1] = 5;
arr[0][2] = 62;// 现在下标为 0 的一维数组:{52, 5, 62}

arr[1][0] = 2;
arr[1][1] = 8;
arr[1][2] = 6;// 现在下标为 1 的一维数组:{2, 8, 6}

// 访问下标为 0 的数组的所有元素
int n1 = arr[0][0];
int n2 = arr[0][1];
int n3 = arr[0][2];

// 访问下标为 1 的数组的所有元素
int m1 = arr[1][0];
int m2 = arr[1][1];
int m3 = arr[1][2];

// 注意二维数组长度为 2,一维数组长度为 3,下标别越界。(下标别超过 length - 1)

当一维数组的长度不确定时,就不能使用上述方式动态初始化。

动态初始化 2

假如确定了二维数组的长度为 4,

int[][] arr = new int[4][]; 此时内存图如下:(因为一维数组是引用类型,不赋值默认为 null)

System.out.println(arr[0]);// null
// 使用 null 获取什么都会抛出 java.lang.NullPointerException 空指针异常
System.out.println(arr[0].length);

这类运行时异常,编译时检查不出来。

以后需要时再创建一维数组:

arr[0] = new int[1];
arr[1] = new int[2];
arr[2] = new int[3];

6.2.2 遍历二维数组

先得到每一个元素,也就是一维数组,再遍历一维数组:

int[][] total = new int[3][3];

for (int i = 0; i < total.length; i++) {
    int[] arr = total[i];
    for (int j = 0; j < arr.length; j++) {
        System.out.print(arr[j] + \",\");
    }
    System.out.println(\"一维数组遍历完毕\");
}

合并

int[][] total = new int[3][3];

for (int i = 0; i < total.length; i++) {
    for (int j = 0; j < total[i].length; j++) {
        System.out.print(total[i][j] + \"\\t\");
    }
    System.out.println();
}

6.2.3 静态方法调用

每次都是重复的代码,有点厌倦了,所以需要方法封装重复的代码了;只讲一点点。

class A {
    public static void f1(int[] arr) {}
    public static int sum(int a1, int a2) {return a1 + a2}
    
}

方法的第 3 个单词如 void、int...代表返回值,如果没有返回值就写 void,有返回值,就写返回值的类型,然后在方法结尾处写上 return 要返回的值;

f1、sum 是方法名,自己定义,符合标识符规则就行。

方法名后的是形式参数列表,可以定义任意个变量,变量之间使用英文逗号分隔,将来调用时传入实际参数,注意实际参数的个数和类型要与形式参数对应上。

如何使用方法?(调用方法)

在同一个类中,直接通过方法名(实参)调用。

class A {
    public static void main(String[] args) {
        // 使用 int 类型的变量接收返回值
        // int num1 = 4; int num2 = 5;
        int sum = sum(4, 5);
        System.out.println(sum);// 9
    }
    // 返回 num1 + num2
    public static int sum(int num1, int num2) {
        return num1 + num2;
    }
}

在同一个包下(同级目录),不同类中,使用类名.方法名(实参)调用:

class A {
    public static void main(String[] args) {
        System.out.println(B.f1());// 1
    }
}
class B {
    public static int f1() {return 1;}
}

不同包下的类需要导入,如使用其它包下的 Arrays 类,关于方法详细请看 API 文档;如果以后编译时说找不到符号,想一想自己导入了此类没有。

import java.util.Arrays;
class A {
    public static void main(String[] args) {
		int[] arr = {5, 26, 3};
        System.out.println(Arrays.toString(arr));// [5, 26, 3]
    }
}    

6.3 排序

内容导视:

  • 时间复杂度
  • 空间复杂度
  • 冒泡排序
  • 简单选择排序
  • 直接插入排序
  • 希尔排序
  • 堆排序
  • 归并排序
  • 快速排序
  • 基数排序

我们知道了如何定义数组来存放元素,那就试试对数组中的元素排序吧。

排序:一组元素按指定顺序排列的过程。生活中,排序无处不在,我会按照难度依次介绍(顺序:从小到大排),对于初学者,只需要了解冒泡排序与简单选择排序,其它不用看。

排序算法分为:

  • 插入排序
    • 直接插入排序
    • 希尔排序
  • 选择排序
    • 简单选择排序
    • 堆排序
  • 交换排序
    • 冒泡排序
    • 快速排序
  • 归并排序
  • 基数排序

在完成功能的情况下,我们需要考虑如何让程序运行时间更短,占用空间更小。(你也不希望软件卡半天没响应,又特别占用内存,对吧)

由此引申出来时间复杂度与空间复杂度,接下来一一介绍。

6.3.1 时间复杂度

描述了解决特定问题的步骤称为算法,如排序算法解决了元素无序的问题。只不过在计算机上,可以使用代码描述。

解决问题的方法有多种,不同算法的效率有高有低;最直观的方法就是编写不同的程序实现不同的算法,然后输入不同数据,进行编译,运行时对它们进行计时,用时最短的就是最好的。但这种事后测量的方法有很大的缺点与不确定性:

  • 实现不同程序需要耗费大量时间,而我们只需保留其中之一。
  • 数据的不同,可能对某个算法更加有利;如使用顺序与逆序查找,如果查找的数就在开头,顺序唰的一下找到了,就能说明顺序一定比逆序好吗?如何选择测试数据以及数据量的多少,才能能够保证结果的公平性,很难判断的。
  • 在不同的运行环境、硬件性能情况下得到的结果可能相差会很大;即使在同台机器上,也有可能测试时 CPU 负荷突然过高、运行内存忽高忽低、计算机电量不足...代码运行速度慢了下来,你究竟要测试几次,在不同的机器上,配置一个怎么样的的环境,才能得出令人信服的结果?

这时就需要我们自己在编写程序前,能够粗略估计代码的运行时间。(事前分析估算方法)

语句执行次数

也称语句频度、时间频度,记为 T(n),n 代表数据的个数。假设运行一行基础代码就算执行一次语句。代码花费的时间与语句执行次数成正比例,执行的语句越多,花费时间越多。

求 m1 方法内语句执行次数:

public static void m1(int[] arr) {
    System.out.println(\"你好\");// 执行 1 次
    System.out.println(\"我饿了\");// 执行 1 次
}

T(n) = 2;

此时数组中元素的个数 n 不会对语句执行次数产生影响,也就是说无论 arr.length 有多大,语句执行次数也就是 2。

求 for 内的语句执行次数:

public static void m2(int[] arr) {
    int n = arr.length;
    // 从 0 到 n - 1,一共循环 n 次,每次循环执行 2 个语句
    for (int i = 0; i < n; i++) {
        System.out.println(\"我不想排序\");// 一共执行 n 次
        System.out.println(\"我想睡觉\");// 一共执行 n 次
    }
}

T(n) = 2n;

求 for 内的语句执行次数:

public static void m3(int[] arr) {
    int n = arr.length;
    for (int i = 1; i < n; i *= 2) {
        System.out.println(\"没想到吧\");
        System.out.println(\"还有乘等\");
    }
}

for 循环执行了几次?

每次循环 i *= 2
第 1 次循环 i = 1 = 20;
第 2 次循环 i = 2 = 21;
第 3 次循环 i = 4 = 22;
...
第 x+1 次循环 i = 2x;

假设第 x+1 次循环时,i >= n,不满足条件,退出循环,得出不等式:
2x >= n
解出:x >= log2n

第 log2n + 1 次循环时,i 正好等于 n,退出了循环,这次不算,那么一共执行了 log2n 次循环,每次循环时执行 2 个语句。

T(n) = 2 * log2n;

若循环次数不是整数,向上取整,如 2.321928 记作 3。

对数:https://baike.baidu.com/item/对数/91326

对数公式:https://baike.baidu.com/item/对数公式/5557846

求方法中输出语句的执行次数:

public static void m4(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            System.out.println(\"i=\" + i
                + \",j=\" + j);
        }
    }
}

外层 for 一共循环 n 次,里层 for 一共循环 n 次,里面的输出语句被执行了 n * n 次。

T(n) = n2;

有了语句执行次数的函数,难道就能比较不同代码的运行时间吗?

比如 T(n) = 100n + 1 与 T(n) = n2 + 7,依然无法清晰看出谁的运行时间更短,此时需要看运行时间如何随 n 的增长而变化,选出增速最小的算法。

引出渐进时间复杂度(asymptotic time complexity)这个概念:

渐进时间复杂度

简称时间复杂度,定义如下:

代码执行语句次数记作 T(n);存在函数 f(n),当 n 趋近无穷大时,T(n) / f(n) 的比值为一个不等于 0 的常数,说明 f(n) 与 T(n) 的增长率相同,是同一量级;如 \\(\\lim\\limits_{x \\to \\infty} \\frac{2n + 10}{n}\\) = 2。

记作 T(n) = O( f(n) ),称 O( f(n) ) 为代码的时间复杂度;时间复杂度描述了代码执行时间随数据量增长的变化趋势。

如何推出 f(n)?

次数 n a:2n + 10 a`:n b:2n2 + 5 b`:n2
1 12 1 7 1
100 210 100 20005 10000
10000 20010 10000 200000005 100000000
1000000 2000010 1000000 2000000000005 1000000000000

当 n 越来越大时,常数、低次数项已经变得不太重要,如同 a 再怎么努力,也追不上 b,因此被可以省略。

  • 如果函数是常数,使用 1 代替
  • 若不是常数,只保留最高次数的那一项,并去除最高次数的系数

如 T(n) = 20,记作 T(n) = O(1);

T(n) = 2 * log2n + 98 = 2 * log2k * logkn + 98,只保留最高次数那项,且去掉系数,记作 T(n) = O(logkn);

设 k 为任意常数,由换底公式得:log2n = log2k * logkn

T(n) = 2n2 + 3n + 9,记作 T(n) = O(n2)。

image-20220405141508691

很明显随着 n 的增大,O(n2) 的时间复杂度(增长速度)远大于其它两个。

常见的时间复杂度

从小到大排(n 趋向无穷大时):

常数时间:O(1)

对数时间:O(logkn)

线性时间:O(n)

线性对数时间:O(n * logkn)

平方时间:O(n2)

立方时间:O(n3)

指数时间:O(2n)

阶乘时间:O(n!)

O(nn)

一般代码的时间复杂度为指数阶及以上,就不用考虑了,哪怕 n 只有 10000,结果也是天大的数字,除非你确定 n 十分的小,将来也不会增加。

计算 1 到 n 的数之和,来看看哪种算法时间效率高:

普通人想到的是一个个累加,用代码描述:

int n = 100;// 1
int sum = 0;// 1

for (int i = 1; i <= n; i++) {// n + 1
    sum += i;// n
}
System.out.println(sum);// 1

记录所有语句执行次数:2n + 4,时间复杂度:O(n);

前 n 次顺利通过 for 循环的判断条件,第 n + 1 次时判断失败,没有进入。

高斯想到的是首尾相加 * 个数 / 2:

int n = 100;// 1
int sum = (1 + n) * n / 2;// 1
System.out.println(sum);// 1

时间复杂度:O(1);

很明显高斯给出的算法效率更高;那么如果在某个程序中,需要解决求和问题,就可以选择此算法;而不是等到程序写好了,才掐着秒表,运行一下,一个个比哪个算法用的时间少。

我们可以看到,执行的代码就算有再多行,但如果与 n 的取值无关,通通记为 O(1),所以我只计算循环内某段语句的执行次数(受 n 影响),如 sum += i,其外的忽略不计,这样方便点。

算法优劣、数据的不同、数据量决定了程序的运行时间长短。

当数据量很少时,如 n = 2,计算机运行速度很快的,时间差异几乎是 0;使用事后计时的方法,是无法准确区分算法优劣的。

备注,高斯思路描述如下:

sum =     1 +    2  + ... + 100

sum = 100 +  99  + ... +     1

2sum = 101 + 101 + ... + 101 = 101 * 100

sum = 2sum / 2 = 101 * 50 = 5050

6.3.2 空间复杂度

代码耗费的存储空间,记作 S(n),同样也有 S(n) = O( f(n) ),O( f(n) ) 记为空间复杂度。

要求:记录一张 n * n 棋盘上的黑白棋子。

image-20220405165947443

代码 1:使用二维数组记录棋盘,空记为 0,黑记为 1,白记为 2;假设 n = 9:

int[][] arr = {
    {0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 2, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 1, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 1, 0, 0, 0, 0},
    {0, 2, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 1, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0},
};

数组所占空间:一个 int 类型是 4 个字节,S(n) = 4 * n2,空间复杂度为 O(n2);

想要获取第 row 行、第 column 列的信息:

public static void getState(int[][] arr, int row, int column) {
    // 注意下标从 0 开始,并不是 1,所以需要减去 1
    // 例:第 1 行第 1 列,即 arr[0][0]
    int data = arr[row - 1][column - 1];
    System.out.println(\"第 \" + row + \" 行、第 \" + column + \" 列的棋子为:\" + printChess(data));
}
public static String printChess(int data) {
    String state = \"?\";
    switch(data) {
        case 0 :
            state = \"空\";
            break;
        case 1 :
            state = \"黑\";
            break;
        case 2 :
            state = \"白\";
            break;
        default:
    }
	return state;
}

时间复杂度为 O(1)。

代码 2:创建二维数组保存数据

第一行记录数组一共 i 行 j 列,有 count 个棋子(目前是 9、9、5);剩下 count 行记录这些棋子的位置(下标从 0 开始)与保存的值;数组长度为 1 + count。

int[][] arr = {
    {9, 9, 5},// 棋盘的总行、列数、棋子的个数
    {1, 3, 2},// 第一个棋子的行数 - 1、列数 - 1、保存的值
    {2, 5, 1},
    {4, 4, 1},
    {5, 1, 2},
    {6, 3, 1}
};

count 的范围处于 [0, n2] 之间;

如果棋盘上没有一个棋子,最好情况;只记录棋盘几行几列、棋子的个数为 0,数组所占空间:S(n) = 4 * 3 = 12,空间复杂度为 O(1);
如果棋盘上放满了棋子,最差情况;需要记录 n2 个棋子的位置,数组所占空间:S(n) = 4 * 3 * (n2 + 1) = 12n2 + 12,空间复杂度为 O(n2)。

获取第 row 行、第 column 列的信息,比如获取第 3 行第 6 列的信息,如果棋子存在,那么二维数组中肯定记录了 2、5、棋子保存的值。

我们需要从头遍历,找到开头为 2、5 的一维数组;如果找到,说明棋子存在,获取这个一维数组第 3 个元素(保存的值);

如果没有找到,棋子不存在,记为空。

public static void getState(int[][] arr, int row, int column) {
    int i = row - 1;
    int j = column - 1;
    
    int data = 0;
    for(int m = 1; m < arr.length; m++) {
        if (arr[m][0] == i && arr[m][1] == j) {
            data = arr[m][2];
            break;
        }
    }
    System.out.println(\"第 \" + row + \" 行、第 \" + column + \" 列的棋子为:\" + printChess(data));
}

这种方式时间复杂度十分不稳定,最好情况 count = 0,不需要遍历,得到结果为空,时间复杂度为 O(1);最差情况 count = n2,且对应一维数组在最后一个,时间复杂度为 O(n2)。

当棋子较少时,使用代码 2 更节省空间;想要获取某行某列的信息,使用代码 1 时间效率更高。到底是用空间换时间,还是用时间换空间,凭自己取舍。

也可以组合:当用户下棋时,为了提升时间效率,用空间换时间,使用代码 1;退出棋盘时并不需要获取某行某列的信息,只需记录棋盘位置,使用代码 2 更节省空间。

6.3.3 冒泡排序

规定从小到大排序,那么小的要在前面,否则就需要交换。

Bubble Sort 算法思路:比较相邻元素,逆序就交换。

以 5,6,74,2,36,7 为例,每次比较中,我会用灰色标记大数。

第一轮:5,6,74,2,36,7

首先比较第 1 个与第 2 个元素的大小,5 < 6,不需要交换;
比较第 2 个与第 3 个,6 < 74,不需要交换;
比较第 3 个与第 4 个,74 > 2,需要交换,交换后:5,6,2,74,36,7
比较第 4 个与第 5 个,74 > 36,需要交换,交换后:5,6,2,36,74,7
比较第 5 个与第 6 个,74 > 7,需要交换,交换后:5,6,2,36,7,74

未命名文件

可以看到第一轮只是把最大的数归位了,继续找出第二大数吧。

第二轮:5,6,2,36,7,74

比较第 1 个与第 2 个,5 < 6,不需要交换;
比较第 2 个与第 3 个,6 > 2,需要交换,交换后:5,2,6,36,7,74
比较第 3 个与第 4 个,6 < 36,不需要交换;
比较第 4 个与第 5 个,36 > 7,需要交换,交换后:5,2,6,7,36,74

未命名文件 (1)

这一轮找出了第二大数 36。

有人这时可能会问,需不需要比较第 5 个与第 6 个?

要知道我们第一轮排序时,已经把最大的数放在了最后面,其它的数肯定不会大于最大数,所以没有必要进行比较了,剩下几轮同理,如下一轮只需比较到第 3 个与第 4 个,找出第三大数。

第三轮:5,2,6,7,36,74

比较第 1 个与第 2 个,5 > 2,需要交换,交换后:2,5,6,7,36,74
比较第 2 个与第 3 个,5 < 6,不需要交换;
比较第 3 个与第 4 个,6 < 7,不需要交换;

第三轮找出了第三大数 7。

未命名文件 (2)

第四轮:2,5,6,7,36,74

比较第 1 个与第 2 个,2 < 5,不需要交换;
比较第 2 个与第 3 个,5 < 6,不需要交换;

未命名文件 (3)

找出了第四大数 6。

第五轮:2,5,6,7,36,74

比较第 1 个与第 2 个,2 < 5,不需要交换;

未命名文件 (4)

找出了第五大数 5,接下来就只剩 2,没必要比了,到此结束。可以看出一个长度为 6 的数组,需要 5 轮才能排好序,轮数正是数组长度 - 1。

使用代码实现:

之前在整数类型中讲过两数如何交换值,这里借助第三个变量 temp。

注意下标从 0 开始,如第一轮,arr[0] 与 arr[1] 比较、arr[1] 与 arr[2] 比较、...、arr[4] 与 arr[5] 比较,一共 5 次,那么定义一个变量从 0 到 4 即可。

int[] arr = {5, 6, 74, 2, 36, 7};
int temp = 0;

// 第一轮
for (int j = 0; j < 5; j++) {
    // 如果逆序就交换
    if (arr[j] > arr[j + 1]) {// 比较 5 次
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
    }
}
// 第二轮
for (int j = 0; j < 4; j++) {
    if (arr[j] > arr[j + 1]) {// 比较 4 次
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
    }    
}
// 第三轮
for (int j = 0; j < 3; j++) {
    if (arr[j] > arr[j + 1]) {// 比较 3 次
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
    }    
}
// 第四轮
for (int j = 0; j < 2; j++) {
    if (arr[j] > arr[j + 1]) {// 比较 2 次
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
    }    
}
// 第五轮
for (int j = 0; j < 1; j++) {
    if (arr[j] > arr[j + 1]) {// 比较 1 次,即 arr[0] 与 arr[1]
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
    }    
}

// 遍历arr
for (int i = 0; i < arr.length; i++) {
    System.out.print(arr[i] + \",\");
}

可是你发现了,每轮对应的 for 循环都几乎一模一样,只是判断条件从 j < 5,j < 4 ...到 j < 1,可以考虑使用外层循环将其包裹,定义一个 i 从 5 到 1,让 j < i,而这个 5 正是数组的长度 - 1。

for (int i = arr.length - 1; i > 0; i--) {
    for (int j = 0; j < i; j++) {
        if (arr[j] > arr[j + 1]) {
            temp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = temp;
        }    
    } 
}

改进:

可以发现第三轮时就已经排好序了,再也没有交换元素,剩下几轮都是无用功。定义一个 boolean 变量,默认为 true。

如果交换了,说明还要继续循环,改为 true;如果没有交换,证明已经排好序了,退出循环。

boolean flag = true;
// flag 为 false 时,退出循环
for (int i = arr.length - 1; i > 0 && flag; i--) {
    flag = false;
    for (int j = 0; j < i; j++) {
        // 逆序就交换
        if (arr[j] > arr[j + 1]) {
            temp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = temp;
            // 还要继续循环
            flag = true;
        }    
    } 
}

改进后,设参与排序的数组长度为 n;

最坏情况:数组为逆序,每次比较都需要交换,比较次数为:\\(\\sum\\limits_{i=1}^{n-1}i\\) = 1 + 2 + ... + (n - 1) = n * (n - 1) / 2,时间复杂度为 O(n2);

最好情况:数组为顺序,当 i = n - 1 时,进入外循环,里循环 j 从 0 到 n - 2,一共 n - 1 次比较,没有进入 if 语句内,flag = false,下一次外循环直接退出。时间复杂度为 O(n)。

空间复杂度为 O(1)。

输入的数据如数组 arr,只取决于问题本身,与算法无关,不计入空间复杂度,只需计入算法实现所用的辅助空间。

包括输出:查看数组是否已经排序好了,都不计入。

6.3.4 简单选择排序

Simple Selection Sort 算法思路:找出小数,放在前面;第 i 轮找出第 i 小数,放在第 i 个位置。

小数:两数比较中,更小的数,我说的并不是 1.241 这种小数。

以 {5,2,6,252,1} 为例;小数使用灰色标记。

第一轮:5,2,6,252,1

比较第 1 个与第 2 个元素大小,5 > 2,小数为第 2 个;
比较第 2 个与第 3 个,2 < 6,小数为第 2 个;
比较第 2 个与第 4 个,2 < 252,小数为第 2 个;
比较第 2 个与第 5 个,2 > 1,小数为第 5 个;结束。

最小数在第 5 个位置上,应该放在第 1 个位置上。
交换第 5 个与第 1 个元素位置后:1,2,6,252,5

未命名文件 (5)

第一轮,找出了最小数并放在了合适的位置,那么接着找第二小数。

第二轮:1,2,6,252,5

比较第 2 个与第 3 个,2 < 6,小数为第 2 个;
比较第 2 个与第 4 个,2 < 252,小数为第 2 个;
比较第 2 个与第 5 个,2 < 5,小数为第 2 个;结束。

第二小数在第 2 个位置上,正好合适,不需要交换:1,2,6,252,5

未命名文件 (6)

不需要比较第 1 个与第 2 个,因为第 1 个是最小数,但我们找的是第二小数,下面同理。

第三轮:1,2,6,252,5

比较第 3 个与第 4 个,6 < 252,小数为第 3 个;
比较第 3 个与第 5 个,6 > 5,小数为第 5 个;结束。

第三小数在第 5 个位置上,应该放在第 3 个位置上。
交换第 5 个与第 3 个元素位置:1,2,5,252,6

未命名文件 (7)

第四轮:1,2,5,252,6

比较第 4 个与第 5 个,252 > 6,小数为第 5 个;结束。

第四小数在第 5 个位置上,应该放在第 4 个位置上。
交换第 5 个与第 4 个元素位置:1,2,5,6,252

未命名文件 (8)

5 个数,已经确定了 4 位,剩下一个数自动归位;轮数为数组长度 - 1。

使用代码实现:

我们可以看到每轮都是拿小数与其它数进行比较,所以需要定义变量如 min,记录小数的下标;设数组长度为 n。

如第 1 轮假设最小数下标也就是 min 为 0,然后让 arr[min] 与剩下的数,即与下标为 1、2、3 ... n - 1 的元素比较;期间,若发现更小数,将 min 替换为此数的下标,然后拿 arr[min] 继续与其它数比较。

第 2 轮设 min 为 1;

第 3 轮设 min 为 2;

...

第 n - 1 轮设 min 为 n - 2。

int[] arr = {5, 2, 6, 252, 1};
int n = arr.length;// n = 5
int temp = 0;

// 第一轮:找最小数,假设最小数下标为 0
int min = 0;
// 下标为 0 的数,与下标为 1、2、3、4 的元素进行比较
for (int j = 1; j < n; j++) {

    // 比较过程中,发现有更小的,将 min 替换为此数下标,保证 min 一直记录的是最小数的下标
    if (arr[j] < arr[min]) {// 比较 4 次
        min = j;
    }
}
// 如果 min 不等于 0,证明最小数的下标不是 0,需要将最小数与下标为 0 的元素交换位置
if (min != 0) {
    temp = arr[0];
    arr[0] = arr[min];
    arr[min] = temp;
}

// 第二轮:找出第二小数,假设第二小数下标为 1(因为下标为 0 的元素已经被确定为最小数,无需参与比较)
min = 1;
// 下标为 1 的数,与下标为 2、3、4 的元素进行比较
for (int j = 2; j < n; j++) {
    if (arr[j] < arr[min]) {// 比较 3 次
        min = j;
    }
}
if (min != 1) {
    temp = arr[1];
    arr[1] = arr[min];
    arr[min] = temp;
}

// 第三轮
min = 2;
for (int j = 3; j < n; j++) {
    if (arr[j] < arr[min]) {// 比较 2 次
        min = j;
    }
}
if (min != 2) {
    temp = arr[2];
    arr[2] = arr[min];
    arr[min] = temp;
}

// 第四轮
min = 3;
for (int j = 4; j < n; j++) {
    if (arr[j] < arr[min]) {// 比较 1 次
        min = j;
    }
}
if (min != 3) {
    temp = arr[3];
    arr[3] = arr[min];
    arr[min] = temp;
}
// 利用 Arrays 类的 toString 方法输出 arr 的内容
System.out.println(Arrays.toString(arr));

每轮对应的 for 循环几乎一致,观察每轮的变化之处:min = 0、1、2 ... n - 2,j = 1、2、3 ... n - 1。

考虑外层 for,定义 i 从 0 到 n - 2,让 min = i,j = i + 1。

for (int i = 0; i < n - 1; i++) {
    int min = i;
    for (int j = i + 1; j < n; j++) {
        if (arr[j] < arr[min]) {
            min = j;
        }
    }
    if (min != i) {
        temp = arr[i];
        arr[i] = arr[min];
        arr[min] = temp;
    }
}

简单选择排序,比较时没有交换元素,分开讨论:

比较次数是固定的,一共为 1 + 2 + ... + (n - 1) = n * (n - 1) / 2。

交换次数,最好情况不用交换,次数为 0;最坏情况,每次位置都不对,需要交换,次数为 n - 1。

综合,时间复杂度为 O(n2),空间复杂度为 O(1)。

加载速度太慢,该分了

统领链接:cnblogs 1.1 语法入门-content

作者:藏取有道

出处:https://www.cnblogs.com/cqhh/p/16131275.html

本文来自博客园,商业转载请联系原作者获得授权,非商业转载请在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。


来源:https://www.cnblogs.com/cqhh/p/16131275.html
本站部分图文来源于网络,如有侵权请联系删除。

未经允许不得转载:百木园 » 1.1 语法入门(更新到冒泡排序)

相关推荐

  • 暂无文章