跳到主要内容

浅谈https非对称加密

· 阅读需 4 分钟
季冠臣
后端研发工程师

背景在互联网上,许多敏感信息被传输,例如账号密码、信用卡信息等等,如果这些信息被黑客窃取,就会导致严重的安全问题。为了保障网络安全,人们开发了一种名为HTTPS的安全协议

HTTP协议的缺陷

HTTP(Hyper Text Transfer Protocol)是一种客户端和服务器之间通信的协议,它是Web应用程序的基础。HTTP协议的通信过程中,数据是明文传输的,这使得黑客可以通过中间人攻击来窃取数据,例如通过网络钓鱼或假冒WiFi热点等方式。此外,黑客还可以通过重放攻击或篡改攻击来破坏数据传输的完整性和机密性。

HTTPS的优势

为了保证数据的安全性和完整性,人们开发了HTTPS协议。HTTPS(Hyper Text Transfer Protocol Secure)是一种安全协议,它使用SSL / TLS协议来保护数据传输。与HTTP不同,HTTPS的数据传输是经过加密的,黑客无法窃取数据。此外,HTTPS协议还可以防止重放攻击和篡改攻击,从而保证了数据传输的完整性和安全性。

HTTPS的实现技术

非对称加密

HTTPS使用非对称加密算法来保护数据传输的机密性。非对称加密算法使用一对公钥和私钥来加密和解密数据。公钥可以公开发布,任何人都可以使用它来加密数据,但只有持有相应私钥的人才能解密数据。因此,即使黑客拦截了数据,也无法解密。

数字证书

数字证书用于验证通信双方的身份,并确保数据传输的安全性。数字证书中包含了以下信息:

  • 证书颁发机构(CA)的名称和地址
  • 证书持有人的名称和地址
  • 证书的有效期限
  • 证书的公钥

证书颁发机构(CA)是一家受信任的第三方机构,用于签发和验证数字证书。CA会对证书持有人的身份进行严格的验证,并为其签署数字证书。由于CA是受信任的第三方机构,因此数字证书可以被广泛地接受和使用。

HTTPS的连接过程

以下是HTTPS连接的步骤:

  1. 客户端连接服务器。
  2. 客户端发送SSL / TLS协议版本和加密算法。
  3. 服务器发送SSL / TLS协议版本和加密算法。
    1. 客户端验证数字证书,验证服务器身份。
    2. 如果数字证书有效并可信,则客户端生成一个随机值并使用服务器的公钥加密它。
    3. 服务器使用其私钥解密客户端发送的随机值。
    4. 客户端和服务器都使用该随机值生成对称密钥。
    5. 客户端向服务器发送已使用对称密钥加密的“完成”通知。
    6. 服务器使用对称密钥解密“完成”通知,然后使用对称密钥对数据进行加密和解密。

HTTPS的连接过程

客户端:

  1. 连接服务器。
  2. 发送SSL / TLS协议版本和加密算法。
  3. 接收SSL / TLS协议版本和加密算法。
  4. 验证数字证书,验证服务器身份。
  5. 如果数字证书有效并可信,则生成一个随机值并使用服务器的公钥加密它。
  6. 使用生成的随机值生成对称密钥。
  7. 向服务器发送已使用对称密钥加密的“完成”通知。
  8. 对称加密和解密数据。

服务器:

  1. 接收客户端连接。
  2. 接收SSL / TLS协议版本和加密算法。
  3. 发送SSL / TLS协议版本和加密算法。
  4. 发送数字证书。
  5. 如果数字证书有效并可信,则使用私钥解密客户端发送的随机值。
  6. 使用生成的随机值生成对称密钥。
  7. 接收已使用对称密钥加密的“完成”通知。
  8. 对称加密和解密数据。

总结

HTTPS是一种安全协议,它使用SSL / TLS协议来保护数据传输。HTTPS使用非对称加密算法和数字证书来保证数据传输的机密性和安全性。HTTPS连接的步骤包括客户端连接服务器、发送SSL / TLS协议版本和加密算法、验证数字证书、生成随机值、生成对称密钥、发送“完成”通知和对称加密和解密数据。HTTPS可以保护数据的安全性和完整性,从而有效地防止黑客攻击。

浏览量:加载中...

浅谈equals方法

· 阅读需 5 分钟
季冠臣
后端研发工程师

equals是Java中非常常见的方法,equals不只是字符串区别于==的一个比较的方法,继承Object的类都可以重写equals方法,因为类很多,有时候会记得很乱,所以这里总结一下和它相关的一些知识点。

话不多说,上源码,以下基于JDK1.8文档总结

1 、Object类中

Indicates whether some other object is "equal to" this one. 指示其他对象是否“等于”此对象。

public boolean equals(Object obj) {
return (this == obj);
}

重写equals方法

子类重写equals方法时,要遵循如下规则:

  • 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
  • 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
  • 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
  • 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
  • 对于任何非空引用值 x,x.equals(null) 都应返回 false。

​ 当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

2、String类中

Compares this string to the specified object. 将此字符串与指定对象进行比较。

public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

3、Arrays类中

Returns true if the two specified arrays of longs are equal to one another. 如果两个数组以相同的顺序包含相同的元素,那么它们就是相等的。另外,如果两个数组引用都为空,则认为两个数组引用相等。

public static boolean equals(long[] a, long[] a2) {
if (a==a2)
return true;
if (a==null || a2==null)
return false;

int length = a.length;
if (a2.length != length)
return false;

for (int i=0; i<length; i++)
if (a[i] != a2[i])
return false;

return true;
}

4、Collection类

Compares the specified object with this collection for equality. 将指定的对象与此集合进行比较以获得相等性。

boolean equals(Object o);

5、Set类(HashSet、LinkedHashSet、TreeSet...)

Compares the specified object with this set for equality. 比较指定对象与此集合是否相等。

public boolean equals(Object o) {
if (o == this)
return true;

if (!(o instanceof Set))
return false;
Collection<?> c = (Collection<?>) o;
if (c.size() != size())
return false;
try {
return containsAll(c);
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
}
public int hashCode() {
int h = 0;
Iterator<E> i = iterator();
while (i.hasNext()) {
E obj = i.next();
if (obj != null)
h += obj.hashCode();
}
return h;
}

6、List类(ArrayList、LinkedList、Vector、Stack...)

Compares the specified object with this list for equality.比较指定对象与此列表是否相等。

public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof List))
return false;

ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
while (e1.hasNext() && e2.hasNext()) {
E o1 = e1.next();
Object o2 = e2.next();
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
//Returns the hash code value for this list.
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
//Vector
public synchronized boolean equals(Object o) {
return super.equals(o);
}
public synchronized int hashCode() {
return super.hashCode();
}
//Stack
class Stack<E> extends Vector<E>{}

7、Map类(Hashtable、HashMap、LinkedHashMap、TreeMap...)

Compares the specified object with this map for equality. 比较指定对象与此映射是否相等。

public boolean equals(Object o) {
if (o == this)
return true;

if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;

try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}

return true;
}
public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}

8、Number类以Integer为例

image-20221019090440643

public boolean equals(Object obj) {
return (this == obj);
}
// Integer
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;

/**
* Constructs a newly allocated {@code Integer} object that
* represents the specified {@code int} value.
*
* @param value the value to be represented by the
* {@code Integer} object.
*/
public Integer(int value) {
this.value = value;
}

9、File类中

Tests this abstract pathname for equality with the given object. 测试此抽象路径名是否与给定对象相等。

public boolean equals(Object obj) {
if ((obj != null) && (obj instanceof File)) {
return compareTo((File)obj) == 0;
}
return false;
}
// Compares two abstract pathnames lexicographically.
public int compareTo(File pathname) {
return fs.compare(this, pathname);
}
/**
* The FileSystem object representing the platform's local file system.
*/
private static final FileSystem fs = DefaultFileSystem.getFileSystem();
/**
* Compare two abstract pathnames lexicographically.
*/
public abstract int compare(File f1, File f2);
浏览量:加载中...

悲观锁乐观锁小结

· 阅读需 2 分钟
季冠臣
后端研发工程师

一点点关于乐观和悲观锁的想法 简单记录一下..

悲观锁

就是很悲观的看待问题。

主观认为每次去拿数据都认为会被别人修改,所以每次拿数据的时候都会上锁,这样别人想拿到这个数据就会block直到它拿到锁。如果发生并发冲突,会屏蔽一切可能违反数据完整性的操作 悲观锁之所以悲观。就在于此。所以对商品加上锁,就可以安心的做判断和更新。因为这个时候不会有别人更新这条商品库存。Java synchronized就是悲观锁的一种实现,每次线程要修改数据时都会先获得锁 保证同一时刻只有一个线程 能操作数据 其他线程会被block(阻塞)

乐观锁

就是非常乐观的看待问题 无需加锁。

但是会在表中增加版本字段,更新时where语句中增加版本的判断 算是CAS (compare and swap)比较并替换的操作 在商品库存场景中number起到了版本控制 相当于 version 的作用 总的来说就是 假设不会发生冲突 只在提交操作时检查是否违反了数据的完整性 乐观锁适用于读多写少的应用场合。这样可以提高吞吐量

也有的会加时间戳。

实现分布式锁 : Redis 和 Zookeeper

浏览量:加载中...

傻瓜式安装mysql8.0-Linux

· 阅读需 2 分钟
季冠臣
后端研发工程师

安装mysql8.0以CentOS为例,傻瓜式安装?haha平时傻瓜式安装习惯了,今天来尝试一些指定版本的手动安装,真香啊

一、 查看并卸载mariadb

centos7中会自带的mariadb 与 mysql冲突 所以我们要把它卸载

 rpm -qa | grep mariadb
rpm -e --nodeps mariadb-libs

image-20221209213550740

二、下载mysql安装包

下载安装包 https://downloads.mysql.com/archives/community/

三、上传到opt中的mysql 目录,然后解压

 tar -xvf mysql-8.0.30-1.el7.x86_64.rpm-bundle.tar

image-20221209213752847

四、安装依赖关系顺序安装执行 (有先后顺序、倒序卸载)

 rpm -ivh mysql-community-common-8.0.30-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-plugins-8.0.30-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-8.0.30-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-8.0.30-1.el7.x86_64.rpm
rpm -ivh mysql-community-icu-data-files-8.0.30-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-8.0.30-1.el7.x86_64.rpm

image-20221209214139803

五、对mysql进行初始化

 mysqld --initialize --console

六、修改mysql安装目录的所有用户和所属组

 chown -R mysql:mysql /var/lib/mysql/

七、开启mysql 服务

 systemctl start mysqld

八、查看mysql的临时密码

 cat /var/log/mysqld.log | grep localhost

九、登录mysql

image-20221209214718110

到这基本就ok了,但是我们总不能用这个临时密码登录吧,所以要修改我们mysql密码

十、修改密码

alter user 'root'@'localhost' identified by '123456';

十一、修改root用户的登录IP限制

 use mysql;
update user set host ='%' where user='root';

image-20221209215546129

十二、重启mysql服务

systemctl restart mysqld

🆗;现在我们用本地可视化工具Navicat尝试连接

image-20221209215749697

十三、如果你想卸载,请逆序依次执行,要注意一定把/var/lib/mysql目录删掉。

image-20221209140020730

over~

浏览量:加载中...

Linux常用命令

· 阅读需 6 分钟
季冠臣
后端研发工程师

在这里总结了一些常用的Linux命令~

一、基础命令:

1、 cd命令:切换目录

img

2、 ls命令:列出目录内容

img

3、 pwd命令:查询所在的目录

image-20221207211508849

4、cat命令:查看小文件内容

image-20221207211649448

5、more命令:查看大文件内容

image-20221207212018383

6、head命令:查看前n行

image-20221207212139775

7、tail命令:查看文件后面n行

image-20221207212447511

8、touch命令:创建一个空文件

image-20221207212656485

9、mkdir命令:创建目录

image-20221207213048534

10:rm命令:删除文件或目录

image-20221207213128621

11、cp命令:拷贝文件

image-20221207213347413

12、mv命令:移动或更名现有的文件或目录

image-20221207213519218

13、echo命令:标准输出命令

image-20221207213826413

14、man命令和help命令:查看帮助文档和查看内部命令帮助

image-20221207214926181

15、clear命令:清屏

16、grep命令:查找文件里符合条件的字符串。-i 忽略大小写

image-20221207215302936

17、find命令:查询文件 顺序查找 效率低

image-20221207215517304

18、locate命令:根据索引查找 配合updatedb使用

image-20221207215617990

19、cal:查看日历

image-20221207215745650

20、tar命令:压缩文件、解压缩文件

image-20221207220536963

21、poweroff:关机命令

二、网络配置命令

对比 windows图形化这样的↓

image-20221209170451421

image-20221209170508186

1、ifconfig/ ip addr命令: 查看网络配置信息

image-20221209170846498

2、 为什么修改ip为固定ip呢?

如使用Linux作为服务器使用,应采用固定IP地址,而不是自动分配IP,避免变化。

3、怎么修改呢?

vim /etc/sysconfig/network-scripts/ifcfg-ens33

#静态分配IP,而不再使用DHCP动态分配
BOOTPROTO="static"
#IP和子网掩码
IPADDR=192.168.74.128
NETMASK=255.255.255.0
#网关和DNS服务器
GATEWAY=192.168.74.2
DNS1=8.8.8.8
#IP地址的前24为代表网络地址,后面是主机地址
PREFIX=24


解释:
DEVICE=eth0 #接口名(设备,网卡)
BOOTPROTO=none # IP的配置方法[none|static|bootp|dhcp](引导时不使用协议|静态分配IP|BOOTP协议|DHCP 协议)
BROADCAST=192.168.1.255 #广播地址
HWADDR=00:0C:2x:6x:0x:xx #MAC地址
IPADDR=192.168.1.23 #IP地址
NETMASK=255.255.255.0 # 网络掩码
NETWORK=192.168.1.0 #网络地址
ONBOOT=yes #系统启动的时候网络接口是否有效(yes/no)
TYPE=Ethernet #网络类型(通常是Ethemet)

三、进程命令

对比 win👇

image-20221209171644329

1、ps命令: proess

image-20221209172412180

image-20221209172352111

-aux参数: ps –aux|grep xxx 结合管道符使用

System V展示风格:

USER:用户名称
PID:进程号
%CPU:进程占用CPU的百分比
%MEM:进程占用物理内存的百分比
VSZ:进程占用的虚拟内存大小(单位:KB)
RSS:进程占用的物理内存大小(单位:KB)
TT:终端名称,缩写
STAT:进程状态,其中S-睡眠,s-该进程是会话的先导进程,N-进程拥有比普通优先级更低的优先级,R-正在运行,D-短期等待,Z-僵死进程,T-被跟踪或者被停止等等
STARTED:进程的启动时间
TIME:CPU时间,即进程使用CPU的总时间
COMMAND:启动进程所用的命令和参数,如果过长会被截断显示

image-20221209173408216

image-20221209173026044

-a : 显示当前总段的所有进行信息 -u : 以用户的格式显示进程信息 -x : 显示后台进程运行的参数

-ef参数:以全格式显示当前所有的进程 -e 显示所有进程。-f 全格式。 ps -ef|grep xxx

BSD风格:

UID:用户ID
PID:进程ID
PPID:父进程ID
C:CPU用于计算执行优先级的因子。数值越大,表明进程是CPU密集型运算,执行优先级会降低;数值越小,表明进程是I/O密集型运算,执行优先级会提高
STIME:进程启动的时间
TTY:完整的终端名称
TIME:CPU时间
CMD:启动进程所用的命令和参数

image-20221209173335705

image-20221209173302440

2、kill命令:

kill pid杀死指定pid对应的进程.

kill pid 告诉进程,你需要被关闭,请自行停止运行并退出

kill -9 pid:强行杀死进程 告诉进程,你被终结了,请立刻退出

四、服务类命令(centos)

对比---win👇

image-20221209173715127

1、service

  • 管理方式:

service 服务名 start

​ service 服务名 stop

​ service 服务名 restart

​ service 服务名 reload

​ service 服务名 status

  • 查看方法:/etc/init.d/服务名
  • 通过 chkconfig 命令设置自启动

​ 查看服务chkconfig --list|grep xxx

​ chkconfig --level 5 服务名 on

  • 运行级别:

image-20221209174235215

查看默认级别: vi /etc/inittab

Linux系统有7种运行级别(runlevel):常用的是级别3和5

运行级别0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动

运行级别1:单用户工作状态,root权限,用于系统维护,禁止远程登陆

运行级别2:多用户状态(没有NFS),不支持网络

运行级别3:完全的多用户状态(有NFS),登陆后进入控制台命令行模式

运行级别4:系统未使用,保留

运行级别5:X11控制台,登陆后进入图形GUI模式

运行级别6:系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动

2、systemctl:

  • 有方便统一的管理方式(常用的方法)

​ systemctl start 服务名(xxxx.service)

​ systemctl restart 服务名(xxxx.service)

​ systemctl stop 服务名(xxxx.service)

​ systemctl reload 服务名(xxxx.service)

​ systemctl status 服务名(xxxx.service)

  • l查看服务的方法 /usr/lib/systemd/system

  • 查看服务的命令

​ systemctl list-unit-files

​ systemctl --type service

  • 通过systemctl命令设置自启动

​ 自启动systemctl enable service_name

​ 不自启动systemctl disable service_name

  • 运行级别

img

  • 查看默认级别: vim /etc/inittab
Centos7运行级别简化为:

multi-user.target等价于原运行级别3(多用户有网,无图形界面)

graphical.target等价于原运行级别5(多用户有网,有图形界面)

3、netstat

查看系统的网络情况

-an 按一定顺序排列输出

-p 显示哪个进程在调用

netstat –anp|grep 8080 查看占用8080端口的进程

如果出现端口冲突,可以先使用该命令找到占用端口的进程,再使用kill杀死进程

image-20221209174534275

浏览量:加载中...

JDK8新特性

· 阅读需 2 分钟
季冠臣
后端研发工程师

主要介绍了JDK8的一些新特性,也算不上新特性,出来好多年了....

JDK8新特性

1、interface中可以有static⽅法,但必须有⽅法实现体,该⽅方法只属于该接⼝,接口名直接调⽤用该方法 2、接⼝中新增default关键字修饰的方法,default方法只能定义在接⼝中,可以在子类或子接⼝中被重写。default定义的⽅方法必须有⽅方法体 3、父接口的default方法如果在子接口或子类被重写,那么⼦接口实现对象、子类对象,调⽤用该方法,以重写为准 4、本类、接⼝如果没有重写父类(即接⼝)的default方法,则在调⽤default⽅法时,使⽤用⽗类定义的default⽅方法逻辑

public interface IPay{  
// static修饰符定义静态⽅法
static void staticMethod() {
System.out.println("接⼝中的静态⽅法");
}

// default修饰符定义默认方法 ,默认方法不是抽象方法,可以不重写也可以重写
default void defaultMethod() {
System.out.println("接⼝中的默认⽅法");
}
}
// static⽅法必须通过接⼝类调⽤用
IPay.staticMethod();

//default⽅法必须通过实现类的对象调⽤用
new IPay().defaultMethod();
浏览量:加载中...

异常处理中finally必执行

· 阅读需 1 分钟
季冠臣
后端研发工程师

课程代码演示 面试题,返回结果最终是finally为准(尽量不要在finally里面使用return,会忽略try catch里面的return,容易造成未知的bug)

try{
// 可能发生异常的代码
}catch(ExceptionName1 e1){
//出异常的时候处理
}catch(ExceptionName2 e2){
//出异常的时候处理
}
try{
// 可能发生异常的代码
}catch(ExceptionName1 e1){
//出异常的时候处理
}finally{
//肯定执行的代码
}
try{
// 可能发生异常的代码
}finally{
//肯定执行的代码
}
public static int divide(int num1, int num2){
try {
int result = num1/num2;
return result;
}catch (Exception e){
System.out.println("出异常");
}finally {
System.out.println("finally执⾏了");
return -2;
}
// return -1;
}

不管是否存在异常输出都是-2

浏览量:加载中...

Java流程控制语句

· 阅读需 27 分钟
季冠臣
后端研发工程师

程序设计需要有流程控制语句来完成用户的要求,根据用户的输入决定程序要进入什么流程,即“做什么”以及“怎么做”等。从结构化程序设计角度出发,程序有 3 种结构:顺序结构、选择结构和循环结构。

一、表达式和语句

1、表达式:

1、变量或常量 + 运算符 构成的计算表达式 2、new 表达式,结果是一个数组或类的对象。 3、方法调用表达式,结果是方法返回值或void(无返回值)。

2、语句:

程序的功能是由语句来完成的,语句分为单语句和复合语句。

单语句: 1、空语句,什么功能都没有。它就是单独的一个分号;(需避免) 2、表达式语句,就是表达式后面加分号;

不是所有表达式加分号都能称为一个独立的语句的,只有以下三种表达式加上分号才能构成一个独立的语句:

  • 计算表达式中的赋值表达式、自增自减表达式

  • new表达式

  • 方法调用表达式

    //空语句
    ;

    //表达式语句
    i++; //自增表达式 + ;
    System.out.println("hello"); //方法调用表达式 + ;

    复合语句分为:

    (1)分支语句:if...else,switch...case

    (2)循环语句:for,while,do...while

    (3)跳转语句:break,continue,return,throw

    (4)try语句:try...catch...finally

    (5)同步语句:synchronized


二、顺序结构

顺序结构就是程序从上到下逐行地执行。表达式语句都是顺序执行的。并且上一行对某个变量的修改对下一行会产生影响。

    public void test1() {
int x = 1;
int y = 2;
System.out.println("x = " + x);
System.out.println("y = " + y);
//对x、y的值进行修改
x++;
y = 2 * x + y;
x = x * 10;
System.out.println("x = " + x);
System.out.println("y = " + y);
}

image-20220922140246151


三、输入输出语句

3.1、输出语句

1、两种常见的输出语句

换行输出语句:输出内容后进行换行,格式如下:

System.out.println(输出内容);//输出内容之后,紧接着换行

不换行输出语句:输出内容后不换行,格式如下

System.out.print(输出内容);////输出内容之后不换行

public void test2() {
String name = "季冠臣";
int age = 18;

//对比如下两组代码:
System.out.println(name);
System.out.println(age);

System.out.print(name);
System.out.print(age);
System.out.println(); //()里面为空,效果等同于换行,输出一个换行符
//等价于 System.out.print("\n"); 或 System.out.print('\n');
//System.out.print();//错误,()里面不能为空 核心类库PrintStream类中没有提供print()这样的方法

//对比如下两组代码:
System.out.print("姓名:" + name +",");//""中的内容会原样显示
System.out.println("年龄:" + age);//""中的内容会原样显示

System.out.print("name = " + name + ",");
System.out.println("age = " + age);
}

image-20220922164329556

注意事项:

​ 换行输出语句,括号内可以什么都不写,只做换行处理

​ 不换行输出语句,括号内什么都不写的话,编译报错

​ 如果()中有多项内容,那么必须使用 + 连接起来

​ 如果某些内容想要原样输出,就用""引起来,而要输出变量中的内容,则不要把变量名用""引起来

2、格式化输出(选讲)

  • %d:十进制整数

  • %f:浮点数

  • %c:单个字符

  • %b:boolean值

  • %s:字符串

  • ....

    public void test3() {
    byte b = 127;
    int age = 18;
    long bigNum = 123456789L;
    float weight = 123.4567F;
    double money = 589756122.22552;
    char gender = '男';
    boolean marry = true;
    String name = "张三";
    System.out.printf("byte整数:%d,年龄:%d," +
    "大整数:%d,身高:%f,身高:%.1f,钱:%f," +
    "钱:%.2f,性别:%c,婚否:%b,姓名:%s",
    b,age,bigNum,weight,weight,money,money,
    gender,marry,name);
    }

    image-20220303160140762

3、关于几个转义字符的输出效果说明

public void test4() {
System.out.println("hello\tjava");
System.out.println("hello\rjava");
System.out.println("hello\njava");
}
public void test5() {
System.out.println("hello\tworld\tjava.");
System.out.println("chailinyan\tis\tbeautiful.");
System.out.println("姓名\t基本工资\t年龄");
System.out.println("张三\t10000.0\t23");
}

3.2、输入语句

键盘输入代码的四个步骤:

1、申请资源,创建Scanner类型的对象 2、提示输入xx 3、接收输入内容 4、全部输入完成之后,释放资源,归还资源

1、各种类型的数据输入

示例代码:

//如果在.java源文件上面没有这句import语句,
//那么在代码中每次使用Scanner就要用java.util.Scanner的全名称,比较麻烦

/*
键盘输入代码的四个步骤:
1、申请资源,创建Scanner类型的对象
2、提示输入xx
3、接收输入内容
4、全部输入完成之后,释放资源,归还资源

如果你在键盘输入过程中,遇到java.util.InputMismatchException异常,
说明你输入的数据,其类型与接收数据的变量的类型不匹配。
*/
public void test6() {
//1、准备Scanner类型的对象
//Scanner是一个引用数据类型,它的全名称是java.util.Scanner
//input就是一个引用数据类型的变量了,赋给它的值是一个对象(对象的概念我们后面学习,暂时先这么叫)
//new Scanner(System.in)是一个new表达式,该表达式的结果是一个对象
//引用数据类型 变量 = 对象;
//这个等式的意思可以理解为用一个引用数据类型的变量代表一个对象,所以这个变量的名称又称为对象名
//我们也把input变量叫做input对象
Scanner input = new Scanner(System.in);//System.in默认代表键盘输入
//这里变量名是input,下面就用input

//2、提示输入xx
System.out.print("请输入一个整数:");

//3、接收输入内容
int num = input.nextInt();
System.out.println("num = " + num);

//列出其他常用数据类型的输入
/*
long bigNum = input.nextLong();
double d = input.nextDouble();
boolean b = input.nextBoolean();
String s = input.next();
char c = input.next().charAt(0);//先按照字符串接收,然后再取字符串的第一个字符(下标为0)
*/

//释放资源
input.close();
}

image-20220922171118305

2、next()与nextLine()

/*
next()方法:
遇到空格等空白符,就认为输入结束
nextLine()方法:
遇到回车换行,才认为输入结束
*/
public void test7() {
//申请资源
Scanner input = new Scanner(System.in);

System.out.print("请输入姓名:");
//String name = input.next();//张 三 只能接收张,后面的空格和三无法接收,被下面的输入接收
String name = input.nextLine();
System.out.println("name = " + name);

System.out.print("请输入年龄:");
int age = input.nextInt(); //23回车换行 这里只接收23,回车换行被下面的输入接收
input.nextLine();//读取23后面的回车换行,但是这个不需要接收,只有下面一个输入是nextLine()情况下才需要这样,如果下面的输入是next()或者是nextInt(),nextDouble()等就不需要这么干
System.out.println("age = " + age);

System.out.print("请输入电话号码:");
String tel = input.nextLine();
System.out.println("tel = " + tel);

//释放资源
input.close();
}

image-20220922171943776

四、分支语句

4.1、单分支条件判断

if语句第一种格式: if

if(条件表达式){
语句体;

执行流程

  • 首先判断条件表达式看其结果是true还是false
  • 如果是true就执行语句体
  • 如果是false就不执行语句体

if

案例:从键盘第一个小的整数赋值给small,第二个大的整数赋值给big,如果输入的第一个整数大于第二个整数,就交换。输出显示small和big变量的值。

    public void test8() {
Scanner input = new Scanner(System.in);

System.out.print("请输入第一个整数:");
int small = input.nextInt();

System.out.print("请输入第二个整数:");
int big = input.nextInt();

if (small > big) {
int temp = small;
small = big;
big = temp;
}
System.out.println("small=" + small + ",big=" + big);

input.close();
}

4.2、双分支条件判断

if语句第二种格式: if...else

if(关系表达式) { 
语句体1;
}else {
语句体2;
}

执行流程

  • 首先判断关系表达式看其结果是true还是false

  • 如果是true就执行语句体1

  • 如果是false就执行语句体2

ifelse

案例:从键盘输入一个整数,判定是偶数还是奇数

    public void test9() {
// 判断给定的数据是奇数还是偶数
Scanner input = new Scanner(System.in);

System.out.print("请输入整数:");
int a = input.nextInt();

if(a % 2 == 0) {
System.out.println(a + "是偶数");
} else{
System.out.println(a + "是奇数");
}
input.close();
}

4.3、多分支条件判断

if (判断条件1) {
执行语句1;
} else if (判断条件2) {
执行语句2;
}
...
}else if (判断条件n) {
执行语句n;
} else {
执行语句n+1;
}

执行流程

  • 首先判断关系表达式1看其结果是true还是false
  • 如果是true就执行语句体1,然后结束当前多分支
  • 如果是false就继续判断关系表达式2看其结果是true还是false
  • 如果是true就执行语句体2,然后结束当前多分支
  • 如果是false就继续判断关系表达式…看其结果是true还是false
  • 如果没有任何关系表达式为true,就执行语句体n+1,然后结束当前多分支。

ifelseif

案例:通过指定考试成绩,判断学生等级,成绩范围[0,100]

  • 90-100 优秀
  • 80-89 好
  • 70-79 良
  • 60-69 及格
  • 60以下 不及格
    public void test10() {
Scanner input = new Scanner(System.in);
System.out.print("请输入成绩[0,100]:");
int score = input.nextInt();

if(score<0 || score>100){
System.out.println("你的成绩是错误的");
}else if(score>=90 && score<=100){
System.out.println("你的成绩属于优秀");
}else if(score>=80 && score<90){
System.out.println("你的成绩属于好");
}else if(score>=70 && score<80){
System.out.println("你的成绩属于良");
}else if(score>=60 && score<70){
System.out.println("你的成绩属于及格");
}else {
System.out.println("你的成绩属于不及格");
}
input.close();
}

4.4、if...else嵌套

在if的语句块中,或者是在else语句块中, 又包含了另外一个条件判断(可以是单分支、双分支、多分支)

执行的特点:

  • 如果是嵌套在if语句块中的 只有当外部的if条件满足,才会去判断内部的条件
  • 如果是嵌套在else语句块中的 只有当外部的if条件不满足,进入else后,才会去判断内部的条件

案例:从键盘输入一个年份值和月份值,输出该月的总天数

要求:年份为正数,月份1-12。

例如:输入2022年5月,总天数是31天。

​ 输入2022年2月,总天数是28天。

​ 输入2020年2月,总天数是29天。

   public void test11() {
//从键盘输入一个年份和月份
Scanner input = new Scanner(System.in);

System.out.print("年份:");
int year = input.nextInt();
// input.nextLine();

System.out.print("月份:");
int month = input.nextInt();
// input.nextLine();

if(year>0){
if(month>=1 && month<=12){
//合法的情况
int days;
if(month==2){
if(year%4==0 && year%100!=0 || year%400==0){
days = 29;
}else{
days = 28;
}
}else if(month==4 || month==6 || month==9 || month==11){
days = 30;
}else{
days = 31;
}
System.out.println(year+"年" + month + "月有" + days +"天");
}else{
System.out.println("月份输入不合法");
}
}else{
System.out.println("年份输入不合法");
}

input.close();
}

4.5、switch...case多分支结构

语法格式:

switch(表达式){
case 常量值1:
语句块1;
【break;】
case 常量值2:
语句块2;
【break;】
。。。
【default:
语句块n+1;
【break;】

}

执行过程:

(1)入口

①当switch(表达式)的值与case后面的某个常量值匹配,就从这个case进入;

②当switch(表达式)的值与case后面的所有常量值都不匹配,寻找default分支进入;不管default在哪里

(2)一旦从“入口”进入switch,就会顺序往下执行,直到遇到“出口”,即可能发生贯穿

(3)出口

①自然出口:遇到了switch的结束}

②中断出口:遇到了break等

注意:

(1)switch(表达式)的值的类型,只能是:4种基本数据类型(byte,short,int,char),两种引用数据类型(JDK1.5之后枚举、JDK1.7之后String)

(2)case后面必须是常量值,而且不能重复

1、如何避免case穿透

案例:从键盘输入星期的整数值,输出星期的英文单词

  public void test12() {
//定义指定的星期
Scanner input = new Scanner(System.in);
System.out.print("请输入星期值:");
int weekday = input.nextInt();

//switch语句实现选择
switch(weekday) {
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
case 4:
System.out.println("Thursday");
break;
case 5:
System.out.println("Friday");
break;
case 6:
System.out.println("Saturday");
break;
case 7:
System.out.println("Sunday");
break;
default:
System.out.println("你输入的星期值有误!");
break;
}

input.close();
}

2、利用case的穿透性

在switch语句中,如果case的后面不写break,将出现穿透现象,也就是一旦匹配成功,不会在判断下一个case的值,直接向后运行,直到遇到break或者整个switch语句结束,switch语句执行终止。

练习:根据指定的月份输出对应季节

/*
* 需求:指定一个月份,输出该月份对应的季节。
* 一年有四季
* 3,4,5 春季
* 6,7,8 夏季
* 9,10,11 秋季
* 12,1,2 冬季
*/

public void test13() {
Scanner input = new Scanner(System.in);
System.out.print("请输入月份:");
int month = input.nextInt();

switch(month) {
case 1:
case 2:
case 12:
System.out.println("冬季");
break;
case 3:
case 4:
case 5:
System.out.println("春季");
break;
case 6:
case 7:
case 8:
System.out.println("夏季");
break;
case 9:
case 10:
case 11:
System.out.println("秋季");
break;
default:
System.out.println("你输入的月份有误");
break;
}

input.close();
}

3、Java12之后switch新特性(选讲)

Switch 表达式也是作为预览语言功能的第一个语言改动被引入Java12 中,开始支持如下写法:

		switch(month) {
case 3,4,5 -> System.out.println("春季");
case 6,7,8 -> System.out.println("夏季");
case 9,10,11 -> System.out.println("秋季");
case 12,1,2 -> System.out.println("冬季");
default->System.out.println("月份输入有误!");
};

4、if语句与switch语句比较

  • if语句的条件是一个布尔类型值,if条件表达式为true则进入分支,可以用于范围的判断,也可以用于等值的判断,使用范围更广。
  • switch语句的条件是一个常量值(byte,short,int,char,枚举,String),只能判断某个变量或表达式的结果是否等于某个常量值,使用场景较狭窄。
  • 当条件是判断某个变量或表达式是否等于某个固定的常量值时,使用if和switch都可以,习惯上使用switch更多。当条件是区间范围的判断时,只能使用if语句。
  • 另外,使用switch可以利用穿透性,同时执行多个分支,而if...else没有穿透性。
案例1:使用if、switch都可以

使用if实现根据指定的月份输出对应季节

/*
* 需求:定义一个月份,输出该月份对应的季节。
* 一年有四季
* 3,4,5 春季
* 6,7,8 夏季
* 9,10,11 秋季
* 12,1,2 冬季
*
* 分析:
* A:指定一个月份
* B:判断该月份是几月,根据月份输出对应的季节
* if
* switch
*/

public void test14() {
Scanner input = new Scanner(System.in);
System.out.print("请输入月份:");
int month = input.nextInt();

if ((month == 1) || (month == 2) || (month == 12)) {
System.out.println("冬季");
} else if ((month == 3) || (month == 4) || (month == 5)) {
System.out.println("春季");
} else if ((month == 6) || (month == 7) || (month == 8)) {
System.out.println("夏季");
} else if ((month == 9) || (month == 10) || (month == 11)) {
System.out.println("秋季");
} else {
System.out.println("你输入的月份有误");
}

input.close();
}
案例2:使用switch更好

用year、month、day分别存储今天的年、月、日值,然后输出今天是这一年的第几天。

注:判断年份是否是闰年的两个标准,满足其一即可

​ 1)可以被4整除,但不可被100整除

​ 2)可以被400整除

例如:1900,2200等能被4整除,但同时能被100整除,但不能被400整除,不是闰年

    public void test15() {
int year = 2021;
int month = 12;
int day = 18;
//判断这一天是当年的第几天==>从1月1日开始,累加到xx月xx日这一天
//(1)[1,month-1]个月满月天数
//(2)单独考虑2月份是否是29天(依据是看year是否是闰年)
//(3)第month个月的day天

//声明一个变量days,用来存储总天数
int days = 0;

//累加[1,month-1]个月满月天数
switch (month) {
case 12:
//累加的1-11月
days += 30;//这个30是代表11月份的满月天数
//这里没有break,继续往下走
case 11:
//累加的1-10月
days += 31;//这个31是代表10月的满月天数
//这里没有break,继续往下走
case 10:
days += 30;//9月
case 9:
days += 31;//8月
case 8:
days += 31;//7月
case 7:
days += 30;//6月
case 6:
days += 31;//5月
case 5:
days += 30;//4月
case 4:
days += 31;//3月
case 3:
days += 28;//2月
//在这里考虑是否可能是29天
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
days++;//多加1天
}
case 2:
days += 31;//1月
case 1:
days += day;//第month月的day天
}

//输出结果
System.out.println(year + "年" + month + "月" + day + "日是这一年的第" + days + "天");
}
}
案例3:只能使用if

从键盘输入一个整数,判断是正数、负数、还是零。

    public void test16() {
Scanner input = new Scanner(System.in);

System.out.print("请输入整数:");
int num = input.nextInt();

if (num > 0) {
System.out.println(num + "是正整数");
} else if (num < 0) {
System.out.println(num + "是负整数");
} else {
System.out.println(num + "是零");
}

input.close();
}

image-20220922174928300


五、循环语句

循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循环体语句,当反复执行这个循环体时,需要通过修改循环变量使得循环判断条件为false,从而结束循环,否则循环将一直执行下去,形成死循环。

5.1、for循环

for循环语句格式:

for(初始化语句①; 循环条件语句②; 迭代语句④){
循环体语句③
}

注意:

(1)for(;;)中的两个;是不能多也不能少

(2)循环条件必须是boolean类型

执行流程:

  • 第一步:执行初始化语句①,完成循环变量的初始化;
  • 第二步:执行循环条件语句②,看循环条件语句的值是true,还是false;
    • 如果是true,执行第三步;
    • 如果是false,循环语句中止,循环不再执行。
  • 第三步:执行循环体语句③
  • 第四步:执行迭代语句④,针对循环变量重新赋值
  • 第五步:根据循环变量的新值,重新从第二步开始再执行一遍

1、使用for循环重复执行某些语句

案例:输出1-5的数字

public class Test01For {
public static void main(String[] args) {
for (int i = 1; i <=5; i++) {
System.out.println(i);
}
/*
执行步骤:
*/
}
}

思考:

(1)使用循环和不使用循环的区别

(2)如果要实现输出从5到1呢

(3)如果要实现输出从1-100呢,或者1-100之间3的倍数或以3结尾的数字呢

2、变量作用域

案例:求1-100的累加和

public class Test02ForVariableScope {
public static void main(String[] args) {
//考虑变量的作用域
int sum = 0;
for (int i = 1; i <= 100 ; i++) {
// int sum = 0;
sum += i;
}
// System.out.println("i = " + i);
System.out.println("sum = " + sum);
}
}

3、死循环

for(;;){
循环体语句块;//如果循环体中没有跳出循环体的语句,那么就是死循环
}

注意:

(1)如果两个;之间写true的话,就表示循环条件成立

(2)如果两个;之间的循环条件省略的话,就默认为循环条件成立

(3)如果循环变量的值不修改,那么循环条件就会永远成立

案例:实现爱你到永远

public class Test03EndlessFor {
public static void main(String[] args) {
for (; ;){
System.out.println("我爱你!");
}
// System.out.println("end");//永远无法到达的语句,编译报错
}
}
public class Test03EndlessFor {
public static void main(String[] args) {
for (; true;){ //条件永远成立
System.out.println("我爱你!");
}
}
}
public class Test03EndlessFor {
public static void main(String[] args) {
for (int i=1; i<=10; ){ //循环变量没有修改,条件永远成立,死循环
System.out.println("我爱你!");
}
}
}

思考一下如下代码执行效果:

public class Test03EndlessFor {
public static void main(String[] args) {
for (int i=1; i>=10; ){ //?? 一次都不执行
System.out.println("我爱你!");
}
}
}

5.2、关键字break

使用场景:终止switch或者当前循环

  • 在选择结构switch语句中

  • 在循环语句中

  • 离开使用场景的存在是没有意义的

案例:从键盘输入一个大于1的自然数,判断它是否是素数 提示:素数是指大于1的自然数中,除了1和它本身以外不能再有其他因数的自然数,即某个素数n,在[2,n-1]范围内没有其他自然数可以把n整除

import java.util.Scanner;

public class Test04Break {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);

System.out.print("请输入一个整数:");
int num = input.nextInt();

boolean flag = true;//假设num是素数
//找num不是素数的证据
for(int i=2; i<num; i++){//i<=Math.sqrt(num);
if(num % i ==0){//num被某个i整除了,num就不是素数
flag = false;
break;//找到其中一个可以把num整除的数,就可以结束了,因为num已经可以判定不是素数了
}
}

//只有把[2,num-1]之间的所有数都检查过了,才能下定结论,num是素数
if(num >1 && flag){
System.out.println(num + "是素数");
}else{
System.out.println(num + "不是素数");
}
}
}

5.3、while循环

1、while循环语句基本格式:

while (循环条件语句①) {
循环体语句②;
}

注意:

while(循环条件)中循环条件必须是boolean类型

执行流程:

  • 第一步:执行循环条件语句①,看循环条件语句的值是true,还是false;
    • 如果是true,执行第二步;
    • 如果是false,循环语句中止,循环不再执行。
  • 第二步:执行循环体语句②;
  • 第三步:循环体语句执行完后,重新从第一步开始再执行一遍

1、循环条件成立就执行循环体语句

案例:从键盘输入整数,输入0结束,统计输入的正数、负数的个数。

import java.util.Scanner;

public class Test05While {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);

int positive = 0;
int negative = 0;
int num = 1; //初始化为特殊值,使得第一次循环条件成立
while(num != 0){
System.out.print("请输入整数(0表示结束):");
num = input.nextInt();

if(num > 0){
positive++;
}else if(num < 0){
negative++;
}
}
System.out.println("正数个数:" + positive);
System.out.println("负数个数:" + negative);

input.close();
}
}

2、死循环

while(true){
循环体语句;//如果此时循环体中没有跳出循环的语句,就是死循环
}

注意:

(1)while(true):常量true表示循环条件永远成立

(2)while(循环条件),如果循环条件中的循环变量值不修改,那么循环条件就会永远成立

(3)while()中的循环条件不能空着

import java.util.Scanner;

public class Test05While {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);

int positive = 0;
int negative = 0;

while(true){
System.out.print("请输入整数(0表示结束):");
int num = input.nextInt();

if(num > 0){
positive++;
}else if(num < 0){
negative++;
}else{
break;
}
}
System.out.println("正数个数:" + positive);
System.out.println("负数个数:" + negative);

input.close();
}
}

思考下面代码的执行效果,为什么?

  • 输入0
  • 输入1
import java.util.Scanner;

public class Test05While {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);

int positive = 0;
int negative = 0;

System.out.print("请输入整数(0表示结束):");
int num = input.nextInt();

while(num != 0){
if(num > 0){
positive++;
}else if(num < 0){
negative++;
}
}
System.out.println("正数个数:" + positive);
System.out.println("负数个数:" + negative);

input.close();
}
}

5.4、do...while循环

do...while循环语句标准格式:

do {
循环体语句①;
} while (循环条件语句②);

注意:

(1)while(循环条件)中循环条件必须是boolean类型

(2)dowhile();最后有一个分号

(3)do...while结构的循环体语句是至少会执行一次,这个和for和while是不一样的

执行流程:

  • 第一步:执行循环体语句①;
  • 第二步:执行循环条件语句②,看循环条件语句的值是true,还是false;
    • 如果是true,执行第三步;
    • 如果是false,循环语句终止,循环不再执行。
  • 第三步:循环条件语句执行完后,重新从第一步开始再执行一遍

1、do...while循环至少执行一次循环体

案例:随机生成一个100以内的整数,猜这个随机数是多少?

从键盘输入数,如果大了提示,大了,如果小了,提示小了,如果对了,就不再猜了,并统计一共猜了多少次

提示:随机数 Math.random()

double num = Math.random();// [0,1)的小数

import java.util.Scanner;

public class Test07DoWhile {
public static void main(String[] args) {
//随机生成一个100以内的整数
/*
Math.random() ==> [0,1)的小数
Math.random()* 100 ==> [0,100)的小数
(int)(Math.random()* 100) ==> [0,100)的整数
*/
int num = (int)(Math.random()* 100);
//System.out.println(num);

//声明一个变量,用来存储猜的次数
int count = 0;

Scanner input = new Scanner(System.in);
int guess;//提升作用域
do{
System.out.print("请输入100以内的整数:");
guess = input.nextInt();

//输入一次,就表示猜了一次
count++;

if(guess > num){
System.out.println("大了");
}else if(guess < num){
System.out.println("小了");
}
}while(num != guess);

System.out.println("一共猜了:" + count+"次");

input.close();
}
}

2、死循环

do{
循环体语句;//如果此时循环体中没有跳出循环的语句,就是死循环
}while(true);

注意:

(1)while(true):常量true表示循环条件永远成立

(2)while(循环条件),如果循环条件中的循环变量值不修改,那么循环条件就会永远成立

(3)while()中的循环条件不能空着

import java.util.Scanner;

public class Test08EndlessDoWhile {
public static void main(String[] args) {
//随机生成一个100以内的整数
/*
Math.random() ==> [0,1)的小数
Math.random()* 100 ==> [0,100)的小数
(int)(Math.random()* 100) ==> [0,100)的整数
*/
int num = (int)(Math.random()* 100);
//System.out.println(num);

//声明一个变量,用来存储猜的次数
int count = 0;
Scanner input = new Scanner(System.in);
do{
System.out.print("请输入100以内的整数:");
int guess = input.nextInt();

//输入一次,就表示猜了一次
count++;

if(guess > num){
System.out.println("猜大了");
}else if(guess < num){
System.out.println("猜小了");
}else{
System.out.println("猜对了,一共猜了" + count+"次");
break;
}
}while(true);

input.close();
}
}

5.5、循环语句的区别

  • 从循环次数角度分析

    • do...while循环至少执行一次循环体语句
    • for和while循环先循环条件语句是否成立,然后决定是否执行循环体,至少执行零次循环体语句
  • 如何选择

    • 遍历有明显的循环次数(范围)的需求,选择for循环
    • 遍历没有明显的循环次数(范围)的需求,循环while循环
    • 如果循环体语句块至少执行一次,可以考虑使用do...while循环
    • 本质上:三种循环之间完全可以互相转换,都能实现循环的功能
  • 三种循环结构都具有四要素:

    • (1)循环变量的初始化表达式
    • (2)循环条件
    • (3)循环变量的修改的迭代表达式
    • (4)循环体语句块

5.6、循环嵌套

所谓嵌套循环,是指一个循环的循环体是另一个循环。比如for循环里面还有一个for循环,就是嵌套循环。当然可以是三种循环任意互相嵌套。

例如:两个for嵌套循环格式

for(初始化语句①; 循环条件语句②; 迭代语句⑦) {
for(初始化语句③; 循环条件语句④; 迭代语句⑥) {
循环体语句⑤;
}
}

**执行特点:**外循环执行一次,内循环执行一轮。

案例1:打印5行直角三角形

*
**
***
****
*****
	public static void main(String[] args){
for (int i = 0; i < 5; i++) {
for (int j = 0; j <= i; j++) {
System.out.print("*");
}
System.out.println();
}
}

案例2:break结束当层循环

案例:找出1-100之间所有的素数(质数)

提示:素数是指大于1的自然数中,除了1和它本身以外不能再有其他因数的自然数,即某个素数n,在[2,n-1]范围内没有其他自然数可以把n整除

public class Test09LoopNesting {
public static void main(String[] args){
//找出1-100之间所有的素数(质数)
for(int i=2; i<=100; i++){
//里面的代码会运行100遍
//每一遍i的值是不同的,i=2,3,4,5...100
//每一遍都要判断i是否是素数,如果是,就打印i
/*
如何判断i是否是素数
(1)假设i是素数
boolean flag = true;//true代表素数
(2)找i不是素数的证据
如果在[2,i-1]之间只要有一个数能够把i整除了,说明i就不是素数
修改flag = false;
(3)判断这个flag
*/
//(1)假设i是素数
boolean flag = true;//true代表素数
//(2)找i不是素数的证据
for(int j=2; j<i; j++){
if(i%j==0){
flag = false;//找到一个就可以了
break;
}
}
//(3)判断这个flag
if(flag){
System.out.println(i);
}
}
}
}

5.7、关键字continue

使用场景:提前结束本次循环,继续下一次的循环

1、跳过本次循环

分析如下代码运行结果:

public class Test10Continue {
public static void main(String[] args) {
for(int i=1; i<=5; i++){
for(int j=1; j<=5; j++){
if(i==j){
continue;
// break;
}
System.out.print(j);
}
System.out.println();
}
}
}

2、使用continue提高效率

public class Test10Continue {
public static void main(String[] args) {
//找出1-100之间所有的素数(质数)
for(int i=2; i<=100; i++){
if(i!=2 && i%2==0 || i!=5 && i%5==0){//偶数一定不是素数,
continue;
}

//里面的代码会运行100遍
//每一遍i的值是不同的,i=2,3,4,5...100
//每一遍都要判断i是否是素数,如果是,就打印i
/*
如何判断i是否是素数
(1)假设i是素数
boolean flag = true;//true代表素数
(2)找i不是素数的证据
如果在[3,i-1]之间只要有一个数能够把i整除了,说明i就不是素数
修改flag = false;
这里从3开始找,是因为我们前面排除了偶数
(3)判断这个flag
*/
//(1)假设i是素数
boolean flag = true;//true代表素数
//(2)找i不是素数的证据
for(int j=3; j<i; j++){ // j<=Math.sqrt(i);
if(i%j==0){
flag = false;//找到一个就可以了
break;
}
}
//(3)判断这个flag
if(flag){
System.out.println(i);
}
}
}
}

浏览量:加载中...

DQL数据查询语言

· 阅读需 8 分钟
季冠臣
后端研发工程师

总结了数据查询相关的主要方法,在工作中经常用到下面先引入我们的数据:

/*创建部门表*/
CREATE TABLE dept(
  deptnu     INT PRIMARY KEY comment '部门编号',
  dname       VARCHAR(50) comment '部门名称',
  addr       VARCHAR(50) comment '部门地址'
);
某个公司的员工表
CREATE TABLE employee(
  empno       INT PRIMARY KEY comment '雇员编号',
  ename       VARCHAR(50) comment '雇员姓名',
  job         VARCHAR(50) comment '雇员职位',
  mgr         INT comment '雇员上级编号',
  hiredate   DATE comment '雇佣日期',
  sal         DECIMAL(7,2) comment '薪资',
  deptnu     INT comment '部门编号'
)ENGINE=MyISAM DEFAULT CHARSET=utf8;
/*创建工资等级表*/
CREATE TABLE salgrade(
5.1 mysql查询子句之一where条件查询
简介:详解where条件下的各种查询
简单查询
精确条件查询
模糊条件查询
  grade       INT PRIMARY KEY comment '等级',
  lowsal     INT comment '最低薪资',
  higsal     INT comment '最高薪资'
);
/*插入dept表数据*/
INSERT INTO dept VALUES (10, '研发部', '北京');
INSERT INTO dept VALUES (20, '工程部', '上海');
INSERT INTO dept VALUES (30, '销售部', '广州');
INSERT INTO dept VALUES (40, '财务部', '深圳');
/*插入emp表数据*/
INSERT INTO employee VALUES (1009, '唐僧', '董事长', NULL, '2010-11-17', 50000, 10);
INSERT INTO employee VALUES (1004, '猪八戒', '经理', 1009, '2001-04-02', 29750, 20);
INSERT INTO employee VALUES (1006, '猴子', '经理', 1009, '2011-05-01', 28500, 30);
INSERT INTO employee VALUES (1007, '张飞', '经理', 1009, '2011-09-01', 24500,10);
INSERT INTO employee VALUES (1008, '诸葛亮', '分析师', 1004, '2017-04-19', 30000, 20);
INSERT INTO employee VALUES (1013, '林俊杰', '分析师', 1004, '2011-12-03', 30000, 20);
INSERT INTO employee VALUES (1002, '牛魔王', '销售员', 1006, '2018-02-20', 16000, 30);
INSERT INTO employee VALUES (1003, '程咬金', '销售员', 1006, '2017-02-22', 12500, 30);
INSERT INTO employee VALUES (1005, '后裔', '销售员', 1006, '2011-09-28', 12500, 30);
INSERT INTO employee VALUES (1010, '韩信', '销售员', 1006, '2018-09-08', 15000,30);
INSERT INTO employee VALUES (1012, '安琪拉', '文员', 1006, '2011-12-03', 9500, 30);
INSERT INTO employee VALUES (1014, '甄姬', '文员', 1007, '2019-01-23', 7500, 10);
INSERT INTO employee VALUES (1011, '妲己', '文员', 1008, '2018-05-23', 11000, 20);
INSERT INTO employee VALUES (1001, '小乔', '文员', 1013, '2018-12-17', 8000, 20);
/*插入salgrade表数据*/
INSERT INTO salgrade VALUES (1, 7000, 12000);
INSERT INTO salgrade VALUES (2, 12010, 14000);
INSERT INTO salgrade VALUES (3, 14010, 20000);
INSERT INTO salgrade VALUES (4, 20010, 30000);
INSERT INTO salgrade VALUES (5, 30010, 99990);

一、where条件查询

1、简单查询
select * from employee;
select empno,ename,job as ename_job from employee;
2、精确条件查询
select * from employee where ename='后裔';
select * from employee where sal != 50000;
select * from employee where sal <> 50000;
select * from employee where sal > 10000;
3、模糊条件查询
show variables like '%aracter%'; 
select * from employee where ename like '林%';
4、范围查询
select * from employee where sal between 10000 and 30000; 
select * from employee where hiredate between '2011-01-01' and '2017-12-1';
5、离散查询
select * from employee where ename in ('猴子','林俊杰','小红','小胡');  
6、消除重复值
select distinct(job) from employee;
7、统计查询(聚合函数)
count(code)或者count(*)
select count(*) from employee;
select count(ename) from employee;
       
sum() 计算总和
select sum(sal) from employee;
       
max() 计算最大值
select * from employee where sal= (select max(sal) from employee);
       
avg()   计算平均值
select avg(sal) from employee;
       
min()   计算最低值
select * from employee where sal= (select min(sal) from employee);
       
concat函数: 起到连接作用
select concat(ename,' 是 ',job) as aaaa from employee;

二、group by分组查询(分组)

1、作用:把行 按 字段 分组

2、语法:group by 列1,列2....列n

3、适用场合:常用于统计场合,一般和聚合函数连用

eg:
select deptnu,count(*) from employee group by deptnu;

select deptnu,job,count(*)from employee group by deptnu,job;

select job,count(*) from employee group by job;

三、having条件查询(筛选)

1、作用:对查询的结果进行筛选操作

2、语法:having 条件 或者 having 聚合函数 条件

3、适用场合:一般跟在group by之后

eg:
select job,count(*) from employee group by job having job ='文员';

select deptnu,job,count(*) from employee group by deptnu,job having count(*)>=2;

select deptnu,job,count(*) as 总数 from employee group by deptnu,job having 总数>=2

四、order by排序查询(排序)

1、作用:对查询的结果进行排序操作

2、语法:order by 字段1,字段2 .....

3、适用场合:一般用在查询结果的排序

eg:
select * from employee order by sal;

select * from employee order by hiredate;

# 倒序
select deptnu,job,count(*) as 总数 from employee group by deptnu,job having 总数>=2 order by deptnu desc;

# 正序
select deptnu,job,count(*) as 总数 from employee group by deptnu,job having 总数>=2 order by deptnu asc;

select deptnu,job,count(*) as 总数 from employee group by deptnu,job having 总数>=2 order by deptnu;

顺序:where ---- group by ----- having ------ order by

五、limit限制查询(限制)

1、作用:对查询结果起到限制条数的作用

2、语法:limit n,m n:代表起始条数值,不写默认为0;m代表:取出的条数

3、适用场合:数据量过多时,可以起到限制作用

eg:
  select * from XD.employee limit 4,5;

六、exist型子查询

用法: 1、exists型子查询后面是一个受限的select查询语句 2、exists子查询,如果exists后的内层查询能查出数据,则返回 TRUE 表示存在;为空则返回 FLASE则不存在。

分为俩种:exists跟 not exists

select 1 from employee where 1=1;
select * from 表名 a where exists (select 1 from 表名2 where 条件);

eg:查询出公司有员工的部门的详细信息

select * from dept a where exists (select 1 from employee b where a.deptnu=b.deptnu);

select * from dept a where not exists (select 1 from employee b where a.deptnu=b.deptnu);

七、左连接查询与右连接查询

用法和应用场景: 1、左连接称之为左外连接 右连接称之为右外连接 这俩个连接都是属于外连接 2、左连接关键字:left join 表名 on 条件 / left outer 表名 join on 条件 右连接关键字:right join 表名 on 条件/right outer 表名 join on 条件 3、左连接说明: left join 是left outer join的简写,左(外)连接,左表(a_table)的记录将会全部表示出来, 而右表(b_table)只会显示符合搜索条件的记录。右表记录不足的地方均为NULL。 4、右连接说明:right join是right outer join的简写,与左(外)连接相反,右(外)连接,左表(a_table)只会显示符合搜索条件的记录,而右表(b_table)的记录将会全部表示出来。左表记录不足的地方均为NULL。

eg:列出部门名称和这些部门的员工信息,同时列出那些没有的员工的部门
dept,employee

select a.dname,b.* from dept a left join employee b on a.deptnu=b.deptnu;

select b.dname,a.* from employee a right join dept b on b.deptnu=a.deptnu;

八、内连接查询与联合查询

语法和应用场景: 1、内连接:获取两个表中字段匹配关系的记录 2、主要语法:INNER JOIN 表名 ON 条件;

eg:想查出员工张飞的所在部门的地址

select a.addr from dept a inner join employee b on a.deptnu=b.deptnu and b.ename='张飞';

select a.addr from dept a,employee b where a.deptnu=b.deptnu and b.ename='张飞';

3、联合查询:就是把多个查询语句的查询结果结合在一起 主要语法1:... UNION ... (去除重复) 主要语法2:... UNION ALL ...(不去重复) 4、union查询的注意事项:

(1)两个select语句的查询结果的“字段数”必须一致;
(2)通常,也应该让两个查询语句的字段类型具有一致性;
(3)也可以联合更多的查询结果;
(4)用到order by排序时,需要加上limit(加上最大条数就行),需要对子句用括号括起来

eg:对销售员的工资从低到高排序,而文员的工资从高到低排序
(select * from employee a where a.job = '销售员' order by a.sal limit 999999 ) union (select * from employee b where b.job = '文员' order by b.sal desc limit 999999);
浏览量:加载中...

DDL数据定义语言

· 阅读需 5 分钟
季冠臣
后端研发工程师

主要介绍了对库、表的基本操作和mysql基本数据类型

1、mysql库的创建、查看以及使用/切换
  • 创建数据库space1

    create database space1;
  • 查看当前在哪个数据库

    select database;
  • 进入数据库

    use 库名;
  • 判断是否存在,如果不存在则创建数据库space2

    create database if not exists space2;
  • 创建数据库并指定字符集为GBK

    create database space3 default character set gbk; 
  • 查看某个库是什么字符集

    show create database space1; 
  • 查看当前mysql使用的字符集

    show variables like 'character%';
2、mysql表的数据类型
<1>整数型
类型   大小   范围(有符号) 范围(无符号unsigned)   用途
TINYINT 1字节   (-128,127)     (0,255)           小整数值
SMALLINT 2字节   (-32768,32767) (0,65535)          大整数值
MEDIUMINT 3字节  (-8388608,8388607) (0,16777215)   大整数值
INT 4字节 (-2147483648,2147483647) (0,4294967295) 大整数值
BIGINT   8字节    ()           (0,2的64次方减1)   极大整数值

<2>浮点型
FLOAT(m,d) 4字节   单精度浮点型 备注:m代表总个数,d代表小数位个数
DOUBLE(m,d) 8 字节  双精度浮点型 备注:m代表总个数,d代表小数位个数

<3>定点型
DECIMAL(m,d)   依赖于M和D的值   备注:m代表总个数,d代表小数位个数

<4>字符串类型
类型         大小             用途
CHAR         0-255字节         定长字符串
VARCHAR       0-65535字节       变长字符串
TINYTEXT     0-255字节         短文本字符串
TEXT         0-65535字节       长文本数据
MEDIUMTEXT   0-16777215字节   中等长度文本数据
LONGTEXT     0-4294967295字节 极大文本数据

char的优缺点:存取速度比varchar更快,但是比varchar更占用空间
varchar的优缺点:比char省空间。但是存取速度没有char快

<5>时间型
数据类型   字节数     格式                 备注
date       3   yyyy-MM-dd           存储日期值
time       3     HH:mm:ss           存储时分秒
year       1      yyyy              存储年
datetime   8   yyyy-MM-dd HH:mm:ss    存储日期+时间
timestamp   4  yyyy-MM-dd HH:mm:ss     存储日期+时间,可作时间戳

create table test_time (
date_value date,
time_value time,
year_value year,
datetime_value datetime,
timestamp_value timestamp
) engine=innodb charset=utf8;

insert into test_time values(now(),now(),now(),now(),now());
3、mysql表的创建
  • 语法:(注意空格位

    CREATE TABLE 表名 (    
    字段名1 字段类型1 约束条件1 说明1,
    字段名2 字段类型2 约束条件2 说明2,
    字段名3 字段类型3 约束条件3 说明3
    );
                   
    create table 新表名 as select * from 旧表名 where 1=2;(注意:建议这种创建表的方式用于日常测试,因为可能索引什么的会复制不过来)

    create table 新表名 like 旧表名;
  • 约束条件

    comment         ----说明解释
    not null       ----不为空
    default         ----默认值
    unsigned       ----无符号(即正数)
    auto_increment ----自增
    zerofill       ----自动填充
    unique key     ----唯一值
  • 创建sql:

    CREATE TABLE student (

    id tinyint(5) zerofill auto_increment not null comment '学生学号',

    name varchar(20) default null comment '学生姓名',

    age tinyint default null comment '学生年龄',

    class varchar(20) default null comment '学生班级',

    sex char(5) not null comment '学生性别',

    unique key (id)

    )engine=innodb charset=utf8;;
                     


    CREATE TABLE student (

    id tinyint(5) auto_increment default null comment '学生学号',

    name varchar(20) default null comment '学生姓名',

    age tinyint default null comment '学生年龄',

    class varchar(20) default null comment '学生班级',

    sex char(5) not null comment '学生性别',

    unique key (id)
                     
    )engine=innodb charset=utf8;;
4、mysql表的查看
  • 查看数据库中的所有表:show tables;
  • 查看表结构:desc 表名;
  • 查看创建表的sql语句:show create table 表名;
  • \G :有结束sql语句的作用,还有把显示的数据纵向旋转90度
  • \g :有结束sql语句的作用
  • 复制A表结构创建e表: create table e as select *from A;或者写成create table e like A;(不包含表数据)
5、mysql表的维护和删除
  • 修改表名

     rename table 旧表名 to 新表名;

    rename table student to user;
  • 添加列

    给表添加一列:alter table 表名 add 列名 类型;

    alter table user add addr varchar(50);

    alter table add 列名 类型 comment '说明';

    alter table user add famliy varchar(50) comment '学生父母';

    给表最前面添加一列:alter table 表名 add 列名 类型 first;

    alter table user add job varchar(10) first;

    给表某个字段后添加一列:alter table 表名 add 列名 类型 after 字段名;

    alter table user add servnumber int(11) after id;

    注意:没有给表某个字段前添加一列的说法。
  • 修改列类型

    alter table 表名 modify 列名 新类型;

    alter table user modify servnumber varchar(20);
  • 修改列名

    alter table 表名 change 旧列名 新列名 类型;

    alter table user change servnumber telephone varchar(20);
  • 删除列

    alter table 表名 drop 列名;

    alter table user drop famliy;
  • 修改字符集

    alter table 表名 character set 字符集;

    alter table user character set GBK;
  • 表的删除

    drop table 表名;
    drop table user;

    看表是否存在,若存在则删除表:drop table if exists 表名;
    drop table if exists teacher;
浏览量:加载中...