SQL语句查询


微信公众号:菜鸟永恒



第1章 SQL语句查询


1.1 排序


通过order by语句,可以将查询出的结果进行排序。放置在select语句的最后。
格式:
SELECT FROM 表名 ORDER BY 排序字段ASC|DESC;
ASC 升序 (默认)
DESC 降序


1.查询所有商品信息,使用价格排序(降序)


SELECT FROM product ORDER BY price DESC;


2.在价格排序(降序)的基础上,以分类排序(降序)


SELECT FROM product ORDER BY price DESC,category_id DESC;


3.显示商品的价格(去重复),并排序(降序)


SELECT DISTINCT price FROM product ORDER BY price DESC;


1.2 聚合


之前我们做的查询都是横向查询,它们都是根据条件一行一行的进行判断,而使用聚合函数查询是纵向查询,他是对查询后的结果的列进行计算,然后返回一个单一的值;另外聚合函数会忽略空值。
今天我们学习如下五个聚合函数:
 count:统计指定列不为NULL的记录行数;
 sum:计算指定列的数值和,如果指定列类型不是数值类型,那么计算结果为0;
 max:计算指定列的最大值,如果指定列是字符串类型,那么使用字符串排序运算;
 min:计算指定列的最小值,如果指定列是字符串类型,那么使用字符串排序运算;
 avg:计算指定列的平均值,如果指定列类型不是数值类型,那么计算结果为0;


1 查询商品的总条数


SELECT COUNT() FROM product;


2 查询价格大于200商品的总条数


SELECT COUNT() FROM product WHERE price > 200;


3 查询分类为’c001’的所有商品价格的总和


SELECT SUM(price) FROM product WHERE category_id = ‘c001’;


4 查询分类为’c002’所有商品的平均价格


SELECT AVG(price) FROM product WHERE category_id = ‘c002’;


5 查询商品的最大价格和最小价格


SELECT MAX(price),MIN(price) FROM product;


1.3 分组


分组查询是指使用group by字句对查询信息进行分组。
 格式:
SELECT 字段1,字段2… FROM 表名GROUP BY分组字段 HAVING 分组条件;
分组操作中的having子语句,是用于在分组后对数据进行过滤的,作用类似于where条件。


 having与where的区别:
 having是在分组后对数据进行过滤.
where是在分组前对数据进行过滤
 having后面可以使用统计函数过滤数据
where后面不可以使用统计函数。


0 统计所有商品的个数


1 统计各个分类商品的个数


SELECT category_id ,COUNT() FROM product GROUP BY category_id ;


2 统计各个分类商品的个数,且只显示个数大于1的信息


SELECT category_id ,COUNT() FROM product GROUP BY category_id HAVING COUNT() > 1;


1.4 分页查询


分页查询在项目开发中常见,由于数据量很大,显示屏长度有限,因此对数据需要采取分页显示方式。例如数据共有30条,每页显示5条,第一页显示1-5条,第二页显示6-10条。
 格式:
SELECT 字段1,字段2… FROM 表明 LIMIT M,N
M: 整数,表示从第几条索引开始,计算方式 (当前页-1)*每页显示条数
N: 整数,表示查询多少条数据
SELECT 字段1,字段2… FROM 表明 LIMIT 0,5
SELECT 字段1,字段2… FROM 表明 LIMIT 5,5


第2章 SQL备份与恢复


2.1 SQL备份


选中数据库,右键”备份/导出”,指定导出路径,保存成.sql文件即可。


2.2 SQL恢复


数据库列表区域右键“从SQL转储文件导入数据库”,指定要执行的SQL文件,执行即可。


第3章 SQL约束


3.1 数据完整性


添加数据完整性=添加表约束
分类:实体完整性,域完整性,引用完整性
实体完整性: 对数据行的约束,比如:主键约束,唯一约束
域完整性: 对数据列的约束,比如:该列的数据类型, 默认约束,非空约束
引用完整性: 外键约束(多表的关系)


3.2 主键约束


PRIMARY KEY 约束唯一标识数据库表中的每条记录,每条记录中被主键约束 约束的字段不能相同。
主键必须是唯一的值。
主键列不能是 NULL 值。
每个表都应该有且只能有一个主键。


3.2.1 添加主键约束


 方式一:创建表时,在字段描述处,声明指定字段为主键:
CREATE TABLE Persons
(
Id_P int PRIMARY KEY,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)
 方式二:创建表时,在constraint约束区域,声明指定字段为主键:
 格式:[constraint 名称] primary key (字段列表)
 关键字constraint可以省略,如果需要为主键命名,constraint不能省略,主键名称一般没用。
 字段列表需要使用小括号括住,如果有多字段需要使用逗号分隔。声明两个以上字段为主键,我们称为联合主键。
CREATE TABLE Persons
(
FirstName varchar(255),
LastName varchar(255),
Address varchar(255),
City varchar(255),
CONSTRAINT pk_PersonID PRIMARY KEY (FirstName,LastName)
)

CREATE TABLE Persons
(
FirstName varchar(255),
LastName varchar(255),
Address varchar(255),
City varchar(255),
PRIMARY KEY (FirstName)
)


 方式三:创建表之后,通过修改表结构,声明指定字段为主键:
ALTER TABLE Persons ADD [CONSTRAINT 名称] PRIMARY KEY (字段列表)


CREATE TABLE Persons
(
FirstName varchar(255),
LastName varchar(255),
Address varchar(255),
City varchar(255)
)
ALTER TABLE Persons ADD PRIMARY KEY (FirstName)


3.2.2 删除主键约束


如需撤销 PRIMARY KEY 约束,请使用下面的 SQL:
ALTER TABLE Persons DROP PRIMARY KEY


3.3 自动增长列


我们通常希望在每次插入新记录时,数据库自动生成字段的值。
我们可以在表中使用 auto_increment(自动增长列)关键字,自动增长列类型必须是整形,自动增长列必须为键(一般是主键)。
 下列 SQL 语句把 “Persons” 表中的 “P_Id” 列定义为 auto-increment 主键
CREATE TABLE Persons
(
P_Id int PRIMARY KEY AUTO_INCREMENT,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)
 向persons添加数据时,可以不为P_Id字段设置值,也可以设置成null,数据库将自动维护主键值:
INSERT INTO Persons (FirstName,LastName) VALUES (‘Bill’,’Gates’)
INSERT INTO Persons (P_Id,FirstName,LastName) VALUES (NULL,’Bill’,’Gates’)
 面试:delete和truncate的区别
 Delete删除表中的数据,但不重置auto-increment记录数。
 Truncate删除表中的数据,auto-increment记录数将重置。Truncate其实先删除表然后再创建表。
 扩展:默认地,AUTO_INCREMENT 的开始值是 1,如果希望修改起始值,请使用下列 SQL 语法:
ALTER TABLE Persons AUTO_INCREMENT=100


3.4 非空约束NOT NULL


NOT NULL 约束强制列不接受 NULL 值。
NOT NULL 约束强制字段始终包含值。这意味着,如果不向字段添加值,就无法插入新记录或者更新记录。
 方式一:创建表,下面的 SQL 语句强制 “Id_P” 列和 “LastName” 列不接受 NULL 值:
CREATE TABLE Persons
(
Id_P int NOT NULL,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)


 方式二:修改表结构
添加非空约束 ALTER TABLE student MODIFY LastName varchar(255) NOT NULL


删除非空约束 ALTER TABLE student MODIFY LastName varchar(255

3.5 唯一约束


UNIQUE 约束唯一标识数据库表中的每条记录。
UNIQUE 和 PRIMARY KEY 约束均为列或列集合提供了唯一性的保证。
PRIMARY KEY 拥有自动定义的 UNIQUE 约束。
请注意,每个表可以有多个 UNIQUE 约束,但是每个表只能有一个 PRIMARY KEY 约束。


3.5.1 添加唯一约束


与主键添加方式相同,共有3种,
 方式1:创建表时,在字段描述处,声明唯一:
CREATE TABLE Persons
(
Id_P int UNIQUE,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)
 方式2:创建表时,在约束区域,声明唯一:
CREATE TABLE Persons
(
Id_P int,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
Address varchar(255),
City varchar(255),
CONSTRAINT 名称UNIQUE (Id_P)
)


 方式3:创建表后,修改表结构,声明字段唯一:
ALTER TABLE Persons ADD [CONSTRAINT 名称] UNIQUE (Id_P)


3.5.2 删除唯一约束


 如需撤销 UNIQUE 约束,请使用下面的 SQL:
ALTER TABLE Persons DROP INDEX 约束名称
 如果添加唯一约束时,没有设置约束名称,默认是当前字段的字段名。


唯一约束与主键约束的区别:
主键:唯一、不能为空、一个表只能有一个主键,非业务数据
唯一:唯一、可以有空值,但只能有一个空值。一个表可以有多个唯一约束。


3.6 默认约束


在添加数据中,如果该字段不指定值,采用默认值处理
 方式一: 创建表,字段处声明
CREATE TABLE Persons
(
Id_P int,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
Address varchar(255) DEFAULT ‘北京’,
City varchar(255)
)
 方式二: 修改表结构
ALTER TABLE Persons MODIFY Address VARCHAR(255) DEFAULT ‘北京’
删除默认约束ALTER TABLE Persons MODIFY Address VARCHAR(255)

数据库篇多表操作


微信公众号:菜鸟永恒



第1章 多表操作


实际开发中,一个项目通常需要很多张表才能完成。例如:一个商城项目就需要分类表(category)、商品表(products)、订单表(orders)等多张表。且这些表的数据之间存在一定的关系,接下来我们将在单表的基础上,一起学习多表方面的知识。



1.1 表与表之间的关系
有3类表关系:一对多(多对一)、多对多、一对一(了解)
 一对多关系:
 常见实例:学生和考试成绩(画图),客户和订单,分类和商品,部门和员工.
 一对多建表原则:在从表(多方)创建一个字段,字段作为外键指向主表(一方)的主键.


 多对多关系:
 常见实例:学生和教师,商品和订单(画图),学生和课程、用户和角色
 多对多关系建表原则:需要创建第三张表,中间表中至少两个字段,这两个字段分别作为外键指向各自一方的主键.

 一对一关系:(了解)
 在实际的开发中应用不多,比如QQ号码,和QQ用户信息
 因为一对一可以创建成一张表.
 两种建表原则:
 外键唯一:主表的主键和从表的外键(唯一),形成主外键关系,外键唯一unique。
 外键是主键:主表的主键和从表的主键,形成主外键关系。


1.2 外键约束


现在我们有两张表“分类表”和“商品表”,为了表明商品属于哪个分类,通常情况下,我们将在商品表上添加一列,用于存放分类cid的信息,此列称为:外键


此时“分类表category”称为:主表,“cid”我们称为主键。“商品表products”称为:从表,category_id称为外键。我们通过主表的主键和从表的外键来描述主外键关系,呈现就是一对多关系。
外键特点:

 从表外键的值是对主表主键的引用。
 从表外键类型,必须与主表主键类型一致。


 声明外键约束
语法:alter table 从表 add [constraint] [外键名称] foreign key (从表外键字段名) references 主表 (主表的主键);
[外键名称] 用于删除外键约束的,一般建议“_fk”结尾
alter table 从表 drop foreign key 外键名称
 使用外键目的:
 保证数据完整性


1.3 一对多操作
1.3.1 分析


 category分类表,为一方,也就是主表,必须提供主键cid
 products商品表,为多方,也就是从表,必须提供外键category_id


1.3.2 实现:分类和商品


创建分类表


create table category(
cid int(32) PRIMARY KEY ,
cname varchar(100) #分类名称
);


商品表


CREATE TABLE products (
pid int PRIMARY KEY ,
pname VARCHAR(40) ,
price DOUBLE ,
category_id int
);


添加约束


alter table products add constraint product_fk foreign key (category_id) references category (cid);


1.3.3 操作


1 向分类表中添加数据


INSERT INTO category (cid ,cname) VALUES(1,’服装’);


2 向商品表添加普通数据,没有外键数据,默认为null


INSERT INTO products (pid,pname) VALUES(1,’商品名称’);


3 向商品表添加普通数据,含有外键信息(数据存放在)


INSERT INTO products (pid ,pname ,category_id) VALUES(2,’商品名称2’, 1);


4 向商品表添加普通数据,含有外键信息(数据不存在) – 不能异常


INSERT INTO products (pid ,pname ,category_id) VALUES(3,’商品名称2’,9);


5 删除指定分类(分类被商品使用) – 执行异常


DELETE FROM category WHERE cid = 1;


1.4 多对多
1.4.1 分析


 商品和订单多对多关系,将拆分成两个一对多。
 products商品表,为其中一个一对多的主表,需要提供主键pid
 orders 订单表,为另一个一对多的主表,需要提供主键oid
 orderitem中间表,为另外添加的第三张表,需要提供两个外键oid和pid


1.4.2 实现:订单和商品


商品表[已存在]


订单表


create table orders(
oid int PRIMARY KEY ,
totalprice double #总计
);


订单项表


create table orderitem(
oid int,– 订单id
pid int(50)– 商品id
);


—- 订单表和订单项表的主外键关系


alter table orderitem add constraint orderitem_orders_fk foreign key (oid) references orders(oid);


—- 商品表和订单项表的主外键关系


alter table orderitem add constraint orderitem_product_fk foreign key (pid) references products(pid);


1.4.3 操作


1 向商品表中添加数据


INSERT INTO products (pid,pname) VALUES(3,’商品名称’);


2 向订单表中添加数据


INSERT INTO orders (oid ,totalprice) VALUES(1,998);
INSERT INTO orders (oid ,totalprice) VALUES(2,100);


3向中间表添加数据(数据存在)


INSERT INTO orderitem(pid,oid) VALUES(1, 1);
INSERT INTO orderitem(pid,oid) VALUES(1, 2);
INSERT INTO orderitem(pid,oid) VALUES(2,2);


4删除中间表的数据


DELETE FROM orderitem WHERE pid=2 AND oid = 2;


5向中间表添加数据(数据不存在) – 执行异常


INSERT INTO orderitem(pid,oid) VALUES(2, 3);


6删除商品表的数据 – 执行异常


DELETE FROM products WHERE pid = 1;

数据库篇一


微信公众号:菜鸟永恒



第1章 数据库介绍


1.1 数据库概述
 什么是数据库(DB:DataBase)
数据库就是存储数据的仓库,其本质是一个文件系统,数据按照特定的格式将数据存储起来,用户可以对数据库中的数据进行增加,修改,删除及查询操作。
 什么是数据库管理系统
数据库管理系统(DataBase Management System,DBMS):指一种操作和管理数据库的 大型软件,用于建立、使用和维护数据库,对数据库进行统一管理和控制,以保证数据库的安全性和完整性。用户通过数据库管理系统访问数据库中表内的数据。
 数据库与数据库管理系统的关系


1.2 数据库表


数据库中以表为组织单位存储数据。表中有行和列,我们叫做字段和记录


1.3 表数据


表类似我们的Java类,每个字段都有对应的数据类型。


那么用我们熟悉的java程序来与关系型数据对比,就会发现以下对应关系。
类———-表
类中属性———-表中字段
对象———-记录


根据表字段所规定的数据类型,我们可以向其中填入一条条的数据,而表中的每条数据类似类的实例对象。表中的一行一行的信息我们称之为记录。
 表记录与java类对象的对应关系


1.4 常见数据库


 常见的数据库管理系统
MYSQL :开源免费的数据库,小型的数据库.已经被Oracle收购了.MySQL6.x版本也开始收费。
Oracle :收费的大型数据库,Oracle公司的产品。Oracle收购SUN公司,收购MYSQL。
DB2 :IBM公司的数据库产品,收费的。常应用在银行系统中.
SQLServer:MicroSoft 公司收费的中型的数据库。C#、.net等语言常使用。
SyBase :已经淡出历史舞台。提供了一个非常专业数据建模的工具PowerDesigner。
SQLite : 嵌入式的小型数据库,应用在手机端。
常用数据库:MYSQL Oracle
这里使用MySQL数据库。MySQL中可以有多个数据库,数据库是真正存储数据的地方。


第2章 MySql数据库


2.1 MySql安装
 安装
参考MySQL安装图解.doc


安装后,MySQL会以windows服务的方式为我们提供数据存储功能。开启和关闭服务的操作:右键点击我的电脑→管理→服务→可以找到MySQL服务开启或停止。

也可以在DOS窗口,通过命令完成MySQL服务的启动和停止(必须以管理运行cmd命令窗口)


2.2 登录MySQL数据库


MySQL是一个需要账户名密码登录的数据库,登陆后使用,它提供了一个默认的root账号,使用安装时设置的密码即可登录。
格式1:cmd> mysql –u用户名–p密码
例如:mysql -uroot –proot


格式2:cmd> mysql –host=ip地址 –user=用户名 –password=密码
例如:mysql –host=127.0.0.1 –user=root –password=root
Mysql –h127.0.0.1 –uroot -proot


2.3 SQLyog(MySQL图形化开发工具)


 安装:
提供的SQLyog软件为免安装版,可直接使用
 使用:
输入用户名、密码,点击连接按钮,进行访问MySQL数据库进行操作


在Query窗口中,输入SQL代码,选中要执行的SQL代码,按F8键运行,或按执行按钮运行。


第3章 SQL语句


3.1 SQL概述


3.1.1 SQL语句介绍


数据库是不认识JAVA语言的,但是我们同样要与数据库交互,这时需要使用到数据库认识的语言SQL语句,它是数据库的代码。
结构化查询语言(Structured Query Language)简称SQL,是关系型数据库管理系统都需要遵循的规范。不同的数据库生产厂商都支持SQL语句,但都有特有内容。
普通话:各数据库厂商都遵循的ISO标准。
方言:数据库特有的关键字,语法。


3.1.2 SQL语句分类


 SQL分类:
 数据定义语言:简称DDL(Data Definition Language),用来定义数据库中的对象:数据库,表,列等。关键字:create,alter,drop, show等
 数据操作语言:简称DML(Data Manipulation Language),用来对数据库中表的记录进行更新。关键字:insert,delete,update等
 数据控制语言:简称DCL(Data Control Language),用来定义数据库的访问权限和安全级别,及创建用户。
 数据查询语言:简称DQL(Data Query Language),用来查询数据库中表的记录。关键字:select,from,where等


3.1.3 SQL通用语法


 SQL语句可以单行或多行书写,以分号结尾
 可使用空格和缩进来增强语句的可读性
 MySQL数据库的SQL语句不区分大小写,关键字建议使用大写
 例如:SELECT FROM user。
 同样可以使用/**/的方式完成注释 在Sqlyog中也可以是 #或者- -注释一行
 MySQL中的我们常使用的数据类型如下


详细的数据类型如下(不建议详细阅读!)


3.2 DDL之数据库操作:database


3.2.1 创建数据库(增)
格式:
create database 数据库名;
create database 数据库名 character set 字符集;
例如:


创建数据库数据库中数据的编码采用的是安装数据库时指定的默认编码 utf8


CREATE DATABASE webdb_1;


创建数据库并指定数据库中数据的编码


CREATE DATABASE webdb_2 CHARACTER SET utf8;


3.2.2 查看数据库(查)
查看数据库MySQL服务器中的所有的数据库:
show databases;


查看某个数据库的定义的信息:
show create database 数据库名;
例如:
Show create database webdb_1;


3.2.3 删除数据库(删)
drop database 数据库名称;
例如:
drop database webdb_2;


3.2.4 修改正在使用的数据库(切换数据库)
 查看正在使用的数据库:
select database();


 切换数据库:
use 数据库名;
例如:
use webdb_1;


3.3 DDL之表操作:table


3.3.1 创建表


 格式:
create table 表名(
字段名 类型(长度) [约束],
字段名 类型(长度) [约束],

);
类型:
varchar(n) 字符串
int 整形
double 浮点
date 时间
timestamp 时间戳
约束(后期讲解,现在先知道):
primary key 主键,被主键修饰字段中的数据,不能重复、不能为null。
例如:


创建分类表


CREATE TABLE category (
cid INT primary key, #分类ID
cname VARCHAR(100) #分类名称
);


3.3.2 查看表


 查看数据库中的所有表:
格式:show tables;
 查看表结构:
格式:desc 表名;
例如:desc sort;


3.3.3 删除表


 格式:drop table 表名;
例如:drop table category;


3.3.4 修改表(只改名字)
 rename table 表名 to 新表名;
作用:修改表名
例如:


5, 为分类表category改名成 category2


RENAME TABLE category TO category2;


3.4 DDL之表结构操作:列


3.4.1 对表的结构进行操作(主要是操作表中的列):


 alter table 表名 add 列名 类型(长度) [约束];
作用:修改表–添加列.
例如:


1,为分类表添加一个新的字段为分类描述 varchar(20)


ALTER TABLE category ADD desc VARCHAR(20);


 alter table 表名 drop 列名;
作用:修改表–删除列.
例如:


4, 删除分类表中snamename这列


ALTER TABLE category DROP description;


 alter table 表名 change 旧列名 新列名 类型(长度) 约束;
作用:修改表–修改列名.
例如:


3, 为分类表的分类名称字段进行更换更换为 snamesname varchar(30)


ALTER TABLE category CHANGE descdescription VARCHAR(30);


 alter table 表名 modify 列名 类型(长度) 约束;
作用:修改表–修改列的类型长度及约束.
例如:


2, 为分类表的描述字段进行修改,类型varchar(50) 添加约束 not null


ALTER TABLE category MODIFY desc VARCHAR(50) NOT NULL;


 alter table 表名 character set 字符集;(一般不修改)
作用:修改表的字符集
例如:


6, 为分类表 category 的编码表进行修改,修改成 gbk


ALTER TABLE category CHARACTER SET gbk;


 查看表结构中的列(查看表时已经学过):
格式:desc 表名;
例如:desc sort;


3.5 DML数据操作语言


介绍两个约束
1.primary key 主键,值唯一,并且不能为空
2.auto_increment 自动增长,数据可以由MySql自己维护


3.5.1 插入表记录:insert(增)


 语法:
– 向表中插入某些字段
insert into 表 (字段1,字段2,字段3..) values (值1,值2,值3..);
–向表中插入所有字段,字段的顺序为创建表时的顺序
insert into 表 values (值1,值2,值3..);
 注意:
 值与字段必须对应,个数相同,类型相同
 值的数据大小必须在字段的长度范围内
 除了数值类型外,其它的字段类型的值必须使用引号引起。(建议单引号)
 如果要插入空值,可以不写字段,或者插入 null。


 例如:


INSERT INTO category(cid,cname) VALUES(‘c001’,‘电器’);
INSERT INTO category(cid,cname) VALUES(‘c002’,‘服饰’);
INSERT INTO category(cid,cname) VALUES(‘c003’,‘化妆品’);
INSERT INTO category(cid,cname) VALUES(‘c004’,‘书籍’);

INSERT INTO category(cid) VALUES(‘c002’);
INSERT INTO category(cname) VALUES(‘耗材’);

3.5.2 更新表记录:update(改)


用来修改指定条件的数据,将满足条件的记录指定列修改为指定值
 语法:
–更新所有记录的指定字段
update 表名 set 字段名=值,字段名=值,…;
–更新符号条件记录的指定字段
update 表名 set 字段名=值,字段名=值,… where 条件;
 注意:
 列名的类型与修改的值要一致.
 修改值得时候不能超过最大长度.
 除了数值类型外,其它的字段类型的值必须使用引号引起


3.5.3 删除记录:delete(删)


 语法:
delete from 表名 [where 条件];
或者
truncate table 表名;


 面试题:
删除表中所有记录使用delete from 表名; 还是用truncate table 表名;ctrl+z
删除方式:delete 一条一条删除,不清空auto_increment记录数。
truncate 直接将表删除,重新建表,auto_increment将置为零,从新开始。


3.6 DOS操作数据乱码解决


我们在dos命令行操作中文时,会报错
insert into category(cid,cname) values(‘c010’,’中文’);
ERROR 1366 (HY000): Incorrect string value: ‘\xB7\xFE\xD7\xB0’ for column ‘cname’ at row 1
解决方案1:在cmd命令窗口中输入命令,此操作当前窗口有效,为临时方案。
set names gbk;
解决方案2:安装目录下修改my.ini文件,重启服务所有地方生效。[不建议]


错误原因:因为mysql的客户端设置编码是utf8,而系统的cmd窗口编码是gbk
1)查看MySQL内部设置的编码
show variables like ‘character%’; 查看所有mysql的编码


2)修改client、connection、results的编码一致(GBK编码)
client connetion result 和客户端保持一致,都为GBK
 将客户端编码修改为gbk.


方式1:单独设置


set character_set_client=gbk;
set character_set_connection=gbk;
set character_set_results=gbk;


方式2:快捷设置


set names gbk;


3.7 DQL数据查询语言


3.7.1 准备工作


#创建商品表:
create table product(
    pid int primary key,
    pname varchar(20),
    price double,
    category_id varchar(32)
);
INSERT INTO product(pid,pname,price,category_id) VALUES(1,‘联想’,5000,‘c001’);
INSERT INTO product(pid,pname,price,category_id) VALUES(2,‘海尔’,3000,‘c001’);
INSERT INTO product(pid,pname,price,category_id) VALUES(3,‘雷神’,5000,‘c001’);

INSERT INTO product(pid,pname,price,category_id) VALUES(4,‘JACK JONES’,800,‘c002’);
INSERT INTO product(pid,pname,price,category_id) VALUES(5,‘真维斯’,200,‘c002’);
INSERT INTO product(pid,pname,price,category_id) VALUES(6,‘花花公子’,440,‘c002’);
INSERT INTO product(pid,pname,price,category_id) VALUES(7,‘劲霸’,2000,‘c002’);

INSERT INTO product(pid,pname,price,category_id) VALUES(8,‘香奈儿’,800,‘c003’);
INSERT INTO product(pid,pname,price,category_id) VALUES(9,‘相宜本草’,200,‘c003’);
INSERT INTO product(pid,pname,price,category_id) VALUES(10,‘面霸’,5,‘c003’);

INSERT INTO product(pid,pname,price,category_id) VALUES(11,‘好想你枣’,56,‘c004’);
INSERT INTO product(pid,pname,price,category_id) VALUES(12,‘香飘飘奶茶’,1,‘c005’);

INSERT INTO product(pid,pname,price,category_id) VALUES(13,‘果9’,1,NULL);

3.7.2 语法:
select [distinct] | 列名1,列名2 from 表 [where 条件]


3.7.3 简单查询


1.查询所有的商品. select from product;


2.查询商品名和商品价格. select pname,price from product;


3.去掉重复值. select distinct price from product;


4.查询结果是表达式(运算查询):将所有商品的价格+10元进行显示.


select pname,price+10 from product;


5.别名查询.使用的关键字是as(as可以省略的).


5.1表别名: select from product as p;
5.2列别名:select pname as pn from product;


3.7.4 条件查询


#查询商品名称为“花花公子”的商品所有信息:
SELECT  FROM product WHERE pname = ‘花花公子’

#查询价格为800商品
SELECT 
 FROM product WHERE price = 800

#查询价格不是800的所有商品
SELECT  FROM product WHERE price != 800
SELECT 
 FROM product WHERE price <> 800
SELECT  FROM product WHERE NOT(price = 800)

#查询商品价格大于60元的所有商品信息
SELECT 
 FROM product WHERE price > 60;


#查询商品价格在200到1000之间所有商品
SELECT  FROM product WHERE price >= 200 AND price <=1000;
SELECT 
 FROM product WHERE price BETWEEN 200 AND 1000;

#查询商品价格是200或800的所有商品
SELECT  FROM product WHERE price = 200 OR price = 800;
SELECT 
 FROM product WHERE price IN (200,800);

#查询含有’霸’字的所有商品
SELECT  FROM product WHERE pname LIKE ‘%霸%’;

#查询以’香’开头的所有商品
SELECT 
 FROM product WHERE pname LIKE ‘香%’;

#查询第二个字为’想’的所有商品
SELECT  FROM product WHERE pname LIKE ‘_想%’;


#查询没有分类的商品
SELECT 
 FROM product WHERE category_id IS NULL

#查询有分类的商品
SELECT * FROM product WHERE category_id IS NOT NULL
#查询所有价格大于2000的电脑商品(catetory_id是c001)
或者价格大于2000的服装商品(catetory_id是c002)**

XML


微信公众号:菜鸟永恒



学习目标
 能够说出XML的作用
 能够编写XML文档声明
 能够编写符合语法的XML
 能够通过DTD约束编写XML文档
 能够通过Schema约束编写XML文档
 能够通过Dom4j解析XML文档
第1章 xml基本使用
1.1 XML概述
1.1.1 什么是XML
XML全称为Extensible Markup Language,意思是可扩展的标记语言。
W3C在1998年2月发布1.0版本,2004年2月又发布1.1版本,但因为1.1版本不能向下兼容1.0版本,所以1.1没有人用。同时,在2004年2月W3C又发布了1.0版本的第三版。我们要学习的还是1.0版本!!!



1.1.2 XML的作用
 存放数据


<?xml version=”1.0” encoding=”UTF-8”?>
<persons>
    <person id=“p001”>
        <name>张三</name>
    </person>
    <person id=“p002”>
        <name>李四</name>
    </person>
</persons>

类似于java代码


class Person{
    String id;
String name;
}

public void test(){
    HashSet<Person> persons = new HashSet<Person>();
    persons.addnew Person(“p001”,“张三”) );
    persons.addnew Person(“p002”,“李四”) );
}

 配置文件


<?xml version=”1.0” encoding=”UTF-8”?>
<beans>
    <bean className=“com.itheima_00_Bean.User”>
        <property name=“username” value=“jack”></property>
    </bean>
</beans>

类似于java代码


class Bean{
    private String username;
    private String pws;
    //补全set\get方法
}

import com.itheima_00_Bean.User;
public static void main(){
    Class clzzz = Class.forName(“com.itheima_00_Bean.User”);
    Object obj = clazz.newInstance();
    Method method = clazz.getMethod(“setUsername”,String.class);
    method.invoke(obj,“jack”);
}

1.2 XML的语法


1.2.1 文档声明
 XML文档声明格式:直接在Eclipse中创建XML文件直接写以下声明


<?xml version=”1.0” encoding=”UTF-8”?>


  1. 文档声明必须为结束,中间没有空格;

  2. 文档声明必须从文档的0行0列位置开始;

  3. 文档声明只有2个属性,格式 属性名= “属性值”,属性值必须使用””
    a) versioin:指定XML文档版本。必须属性,因为我们不会选择1.1,只会选择1.0;
    b) encoding:指定当前文档的编码。可选属性,默认值是utf-8;
    4.Eclipse创建的XML文件可自动生成文档声明


1.2.2 注释
XML的注释,以“”结束。注释内容会被XML解析器忽略!


1.2.3 元素(标签/标记tag)
 元素 element


<bean>内容</bean>


  1. 元素是XML文档中最重要的组成部分,

  2. 普通元素的结构开始标签、元素体、结束标签组成。例如:大家好

  3. 元素体:元素体可以是元素,也可以是文本,例如:你好

  4. 空元素:空元素只有开始标签,而没有结束标签,但元素必须自己闭合,例如:

  5. 元素命名:
    a) 区分大小写
    b) 不能使用空格,不能使用冒号:
    c) 不建议以XML、xml、Xml开头

  6. 格式化良好的XML文档,必须只有一个根元素。


1.2.4 属性
 属性 attribute


<bean id=”” className=””>


  1. 属性是元素的一部分,它必须出现在元素的开始标签中

  2. 属性的定义格式:属性名= ”属性值”,其中属性值必须使用单引或双引

  3. 一个元素可以有0~N个属性,但一个元素中不能出现同名属性

  4. 属性名不能使用空格、冒号等特殊字符,且必须以字母开头


1.2.5 转义字符 (xml文件浏览器是支持的,检测语法等)
 转义字符
因为很多符号已经被XML文档结构所使用,所以在元素体或属性值中想使用这些符号就必须使用转义字符,例如:“<”、“>”、“’”、“””、“&”。


1.2.6 CDATA区


 CDATA区


<![CDATA[任意内容]]>

需求:在一个标签中显示一下内容
String s = “abc”;
int count = 0;
for(int i = 0;i < s.length() ; i++){
char c = s.charAt(i);
if(c >= ‘a’ && c <= ‘z’){
count++;
}
}
System.out.println(“count = “ + count);
当大量的转义字符出现在xml文档中时,会使xml文档的可读性大幅度降低。这时如果使用CDATA段就会好一些。
在CDATA段中出现的“<”、“>”、“””、“’”、“&”,都无需使用转义字符。这可以提高xml文档的可读性。
在CDATA段中不能包含“]]>”,即CDATA段的结束定界符。


1.3 XML约束


在XML技术里,可以编写一个文档来约束一个XML文档的书写规范,类似于现实生活中的法律,这称之为XML的约束。
常见的xml约束:DTD、Schema


1.3.1 DTD约束


1.3.1.1 什么是DTD


DTD(Document Type Definition),文档类型定义,用来约束XML文档。规定XML文档中元素的名称,子元素的名称及顺序,元素的属性等。


1.3.1.2 DTD重点要求


开发中,我们很少自己编写DTD约束文档,通常情况我们都是通过框架提供的DTD约束文档,编写对应的XML文档。常见框架使用DTD约束有:struts2、hibernate等。
通过提供的DTD“bean.dtd”编写XML


<?xml version=”1.0” encoding=”UTF-8”?>
<!–
    传智播客DTD教学实例文档。
    模拟spring规范,如果开发人员需要在xml使用当前DTD约束,必须包括DOCTYPE。
    格式如下:
    <!DOCTYPE beans SYSTEM ”bean.dtd”>
–>

<!ELEMENT beans (bean,import) >
<!ELEMENT bean (property)>
<!ELEMENT property (#PCDATA)>

<!ELEMENT import (#PCDATA)>

<!ATTLIST bean id CDATA #REQUIRED
               className CDATA #REQUIRED
>


<!ATTLIST property name CDATA #REQUIRED
                   value CDATA #REQUIRED
>


<!ATTLIST import resource CDATA #REQUIRED>

1.3.1.3 案例实现
 步骤1:创建bean-dtd.xml文档,并将“bean.dtd”拷贝相同目录下。


 步骤2:从DTD文档开始处,拷贝需要的“文档声明”


 步骤3:完成xml内容编写


<?xml version=”1.0” encoding=”UTF-8”?>
<!DOCTYPE beans SYSTEM ”bean.dtd”>
<beans>
    <bean id=“” className=“”></bean>

    <bean id=“” className=“”>
        <property name=“” value=“”></property>
        <property name=“” value=“”></property>
    </bean>

    <import resource=“”></import>
    <import resource=“”></import>

</beans>

1.3.1.4 DTD语法(了解)


1.3.1.4.1 文档声明(如果引用DTD文件)



  1. 内部DTD,在XML文档内部嵌入DTD,只对当前XML有效。


<?xml version=”1.0” encoding=”utf-8” ?>
<!DOCTYPE beans [
    … //具体的语法
]>

</beans>
<beans>


  1. 外部DTD—本地DTD,DTD文档在本地系统上,公司内部自己项目使用。


<?xml version=”1.0” encoding=”utf-8” ?>
<!DOCTYPE beans SYSTEM ”bean.dtd”>
<beans>
</beans>


  1. 外部DTD—公共DTD,DTD文档在网络上,一般都有框架提供。


<?xml version=”1.0” encoding=”UTF-8”?>
<!DOCTYPE beans PUBLIC ”-//SPRING//DTD BEAN 2.0//EN”
http://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>

</beans>

1.3.1.4.2 元素声明


定义元素语法:
元素名:自定义
元素描述包括:符号和数据类型
常见符号:? + () | ,
常见类型:#PCDATA 表示内容是文本,不能是子标签



 实例

1.3.1.4.3 属性声明


属性的语法:(attribute)

元素名:属性必须是给元素添加,所有必须先确定元素名
属性名:自定义
属性类型:ID、CDATA、枚举…
ID : ID类型的属性用来标识元素的唯一性
CDATA:文本类型
枚举:(e1 | e2 | …) 多选一
约束:
#REQUIRED:说明属性是必须的;required
#IMPLIED:说明属性是可选的;implied
 实例


1.3.2 Schema约束


1.3.2.1 什么是Schema


Schema是新的XML文档约束;
Schema要比DTD强大很多,是DTD 替代者;
Schema本身也是XML文档,但Schema文档的扩展名为xsd,而不是xml。
Schema 功能更强大,数据类型更完善
Schema 支持名称空间(类似java的包)


1.3.2.2 Schemal约束文档和XML关系


当W3C提出Schema约束规范时,就提供“官方约束文档”。我们通过官方文档,必须“自定义schema 约束文档”,开发中“自定义文档”由框架编写者提供。我们提供“自定义文档”限定,编写出自己的xml文档。


1.3.2.3 Schema重点要求


与DTD一样,要求可以通过schema约束文档编写xml文档。常见框架使用schema的有:Spring等
通过提供“bean-schema.xsd”编写xml文档


<?xml version=”1.0” encoding=”UTF-8”?>
<!– 
    Schema教学实例文档。
    模拟spring规范,如果开发人员需要在xml使用当前Schema约束,必须包括指定命名空间。
    格式如下:
    <beans xmlns=”http://www.itcast.cn/bean"
       xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=”h ttp://www.itcast.cn/bean bean-schema.xsd"
    >
–>

<schema xmlns=http://www.w3.org/2001/XMLSchema"
        targetNamespace=http://www.itcast.cn/bean"
        xmlns:xsd=http://www.w3.org/2001/XMLSchema"
        xmlns:tns=http://www.itcast.cn/bean"
        elementFormDefault=“qualified”>

    <!– 声明根标签 –>
    <element name=“beans”>
        <complexType>
            <choice minOccurs=“0” maxOccurs=“unbounded”>
                <element name=“bean”>
                    <complexType>
                        <sequence minOccurs=“0” maxOccurs=“unbounded”>
                            <element name=“property”>
                                <complexType>
                                    <attribute name=“name” use=“required”></attribute>
                                    <attribute name=“value” use=“required”></attribute>
                                </complexType>
                            </element>
                        </sequence>
                        <attribute name=“id” use=“required”></attribute>
                        <attribute name=“className” use=“required”></attribute>
                    </complexType>
                </element>
                <element name=“import”>
                    <complexType>
                        <attribute name=“resource” use=“required”></attribute>
                    </complexType>
                </element>
            </choice>
        </complexType>
    </element>
</schema>

 案例文档中同一个“ 命名空间”分别使用“默认命名空间”和“显示命名空间”进行引入,所以文档中和<xsd:schema style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>作用一样。
</xsd:schema>


1.3.2.4 案例实现



  1. 步骤1:创建bean.xml,并将“bean-schema.xsd”拷贝到同级目录


  2. 步骤2:从xsd文档中拷贝需要的“命名空间”



  3. 完成xml内容编写



<?xml version=”1.0” encoding=”UTF-8”?>
<beans xmlns=http://www.itcast.cn/bean"
       xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=http://www.itcast.cn/bean bean-schema.xsd"
>

    <bean id=“” className=“”></bean>
    <bean id=“” className=“”>
        <property name=“” value=“”/>
        <property name=“” value=“”/>
    </bean>

    <import resource=“”/>
    <import resource=“”/>
</beans>

1.3.2.5 命名空间(语法)
1.3.2.5.1 什么是命名空间
如果一个XML文档中使用多个Schema文件,而这些Schema文件中定义了相同名称的元素时就会出现名字冲突。这就像一个Java文件中使用了import java.util.和import java.sql.时,在使用Date类时,那么就不明确Date是哪个包下的Date了。
总之名称空间就是用来处理元素和属性的名称冲突问题,与Java中的包是同一用途。如果每个元素和属性都有自己的名称空间,那么就不会出现名字冲突问题,就像是每个类都有自己所在的包一样,那么类名就不会出现冲突。


1.3.2.5.2 声明命名空间


默认命名空间:,使用<标签>
显式命名空间:,使用<别名:标签>


 实例:bean.xml


1.4 dom4j解析


1.4.1 XML解析概述


当将数据存储在XML后,我们就希望通过程序获得XML的内容。如果我们使用Java基础所学习的IO知识是可以完成的,不过你需要非常繁琐的操作才可以完成,且开发中会遇到不同问题(只读、读写)。人们为不同问题提供不同的解析方式,并提交对应的解析器,方便开发人员操作XML。


1.4.2 解析方式和解析器


 开发中比较常见的解析方式有三种,如下:



  1. DOM:要求解析器把整个XML文档装载到内存,并解析成一个Document对象。
    a) 优点:元素与元素之间保留结构关系,故可以进行增删改查操作。
    b) 缺点:XML文档过大,可能出现内存溢出显现。

  2. SAX:是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。并以事件驱动的方式进行具体解析,每执行一行,都将触发对应的事件。(了解)
    a) 优点:处理速度快,可以处理大文件
    b) 缺点:只能读,逐行后将释放资源。

  3. PULL:Android内置的XML解析方式,类似SAX。(了解)


 解析器:就是根据不同的解析方式提供的具体实现。有的解析器操作过于繁琐,为了方便开发人员,有提供易于操作的解析开发包。


 常见的解析开发包:
 JAXP:sun公司提供支持DOM和SAX开发包
 JDom:dom4j兄弟
 jsoup:一种处理HTML特定解析开发包
 dom4j:比较常用的解析开发包,hibernate底层采用。


1.4.3 DOM解析原理及结构模型


XML DOM 将整个XML文档加载到内存,并获得一个Document对象(实际上是一个DOM树),通过Document对象就可以对DOM进行操作


DOM中的核心概念就是节点(Element),在XML文档中的元素、属性、文本等,在DOM中都叫做节点!


1.4.4 API使用


如果需要使用dom4j,必须导入jar包。


dom4j 必须使用核心类SaxReader加载xml文档获得Document,通过Document对象获得文档的根元素,然后就可以操作了。
常用API如下:



  1. SaxReader对象
    a) read(…) 加载执行xml文档

  2. Document对象
    a) getRootElement() 获得根元素

  3. Element对象
    a) elements(…) 获得指定名称的所有子元素。可以不指定名称
    b) element(…) 获得指定名称第一个子元素。可以不指定名称
    c) getName() 获得当前元素的元素名
    d) attributeValue(…) 获得指定属性名的属性值
    e) elementText(…) 获得指定名称子元素的文本值
    f) getText() 获得当前元素的文本内容


public static void main(String[] args) throws Exception {
        SAXReader sax = new SAXReader();
        Document document = sax.read(“beans.xml”);

        Element elemRoot = document.getRootElement();

        List<Element>list = elemRoot.elements();
        for(Element element : list){
            String id =element.attributeValue(“id”);
            String className = element.attributeValue(“className”);
            System.out.println(id+“”+className);

            List<Element>listElem = element.elements();
            for(Element elem : listElem){
                String name = elem.attributeValue(“name”);
                String value = elem.attributeValue(“value”);
                System.out.println(name+“”+value);
            }
        }
    }


beans.xml
<?xml version=“1.0” encoding=“UTF-8”?>
<beans>
<bean id=“001” className=“cn.itcast.demo.User”>
<property name=“username” value=“jack”></property>
<property name=“password” value=“123”></property>
</bean>

<bean id=“002” className=“cn.itcast.demo.Admin”>
<property name=“username” value=“admin”></property>
<property name=“password” value=“123321”></property>
</bean>
</beans>

网络编程


微信公众号:菜鸟永恒



第14天 网络编程
今日内容介绍
 网络通信协议
 UDP通信
 TCP通信
今日学习目标
 能够辨别UDP和TCP协议特点
 能够说出UDP协议下两个常用类名称
 能够说出TCP协议下两个常用类名称
 能够编写UDP协议下字符串数据传输程序
 能够编写TCP协议下字符串数据传输程序
*
 能够理解TCP协议下文件上传案例


第1章 网络通信协议
这里的网络是指计算机网络,首先我们应该知道什么是计算机网络?


通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶


的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
网络通信协议有很多种,目前应用最广泛的是TCP/IP协议(Transmission Control Protocal/Internet Protocal传输控制协议/英特网互联协议),它是一个包括TCP协议和IP协议,UDP(User Datagram Protocal)协议和其它一些协议的协议组,在学习具体协议之前首先了解一下TCP/IP协议组的层次结构。
在进行数据传输时,要求发送的数据与收到的数据完全一样,这时,就需要在原有的数据上添加很多信息,以保证数据在传输过程中数据格式完全一致。TCP/IP协议的层次结构比较简单,共分为四层,如图所示。

图1-1 TCP/IP网络模型
上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能,接下来针对这四层进行详细地讲解。
链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
运输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。
1.1 IP地址和端口号
要想使网络中的计算机能够进行通信,必须为每台计算机指定一个标识号,通过这个标识号来指定接受数据的计算机或者发送数据的计算机。
在TCP/IP协议中,这个标识号就是IP地址,它可以唯一标识一台计算机,目前,IP地址广泛使用的版本是IPv4,它是由4个字节大小的二进制数来表示,如:00001010000100000010100100000001。由于二进制形式表示的IP地址非常不便记忆和处理,因此通常会将IP地址写成十进制的形式,每个字节用一个十进制数字(0-255)表示,数字间用符号“.”分开,如 “192.168.1.100”。
随着计算机网络规模的不断扩大,对IP地址的需求也越来越多,IPV4这种用4个字节表示的IP地址面临枯竭,因此IPv6 便应运而生了,IPv6使用16个字节表示IP地址,它所拥有的地址容量约是IPv4的8×1028倍,达到2128个(算上全零的),这样就解决了网络地址资源数量不够的问题。


通过IP地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号。在计算机中,不同的应用程序是通过端口号区分的。端口号是用两个字节(16位的二进制数)表示的,它的取值范围是0~65535,其中,0~1023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。
常见的应用以及端口号:`
Mysql:3306 Web服务器Tomcat :8080
Oracle:1521


接下来通过一个图例来描述IP地址和端口号的作用,如下图所示。


从上图中可以清楚地看到,位于网络中一台计算机可以通过IP地址去访问另一台计算机,并通过端口号访问目标计算机中的某个应用程序。


1.2 InetAddress
了解了IP地址的作用,我们看学习下JDK中提供了一个InetAdderss类,该类用于封装一个IP地址,并提供了一系列与IP地址相关的方法,下表中列出了InetAddress类的一些常用方法。


上图中,列举了InetAddress的四个常用方法。其中,前两个方法用于获得该类的实例对象,第一个方法用于获得表示指定主机的InetAddress对象,第二个方法用于获得表示本地的InetAddress对象。通过InetAddress对象便可获取指定主机名,IP地址等,接下来通过一个案例来演示InetAddress的常用方法,如下所示。


public class Example01 {
    public static void main(String[] args) throws Exception {
        InetAddress local = InetAddress.getLocalHost();

        System.out.println(“本机的IP地址:” + local.getHostAddress());
        System.out.println(“itcast的IP地址:” + remote.getHostAddress());
        System.out.println(“itcast的主机名为:” + remote.getHostName());
InetAddress remote = InetAddress.getByName(“119.75.216.20”);
         InetAddress remote = InetAddress.getByName(“127.0.0.1”);//本地回送地址

    }
}

第2章 UDP与TCP协议


在介绍TCP/IP结构时,提到传输层的两个重要的高级协议,分别是UDP和TCP,其中UDP是User Datagram Protocol的简称,称为用户数据报协议,TCP是Transmission Control Protocol的简称,称为传输控制协议。


2.1 UDP协议
UDP协议的特点是面向无连接的通信协议,不保证数据的完整性,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据,例如:我们的供屏软件。
这种协议既然有那么多缺点为什么还用它?
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。


2.2 TCP协议


TCP协议的特点是面向连接的通信协议,保证数据的安全和完整性即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。第一次握手,客户端向服务器端发出连接请求,等待服务器确认,第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求,第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。


由于TCP协议的面向连接特性,它可以保证传输数据的安全性,所以是一个被广泛采用的协议,例如在下载文件时,如果数据接收不完整,将会导致文件数据丢失而不能被打开,因此,下载文件时必须采用TCP协议。


第3章 UDP通信


在UPD通信中有2个常用的类,一个是数据包类DatagramPacket,
一个是数据包发送接收器类DatagramSocket
3.1 DatagramPacket
前面介绍了UDP是一种面向无连接的协议,因此,在通信时发送端和接收端不用建立连接。UDP通信的过程就像是货运公司在两个码头间发送货物一样。在码头发送和接收货物时都需要使用集装箱来装载货物,UDP通信也是一样,发送和接收的数据也需要使用“集装箱”进行打包,为此JDK中提供了一个DatagramPacket类,该类的实例对象就相当于一个集装箱,用于封装UDP通信中发送或者接收的数据。
想要创建一个DatagramPacket对象,首先需要了解一下它的构造方法。在创建发送端和接收端的DatagramPacket对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组来存放接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端IP地址和端口号。
接下来根据API文档的内容,对DatagramPacket的构造方法进行逐一详细地讲解。


使用该构造方法在创建DatagramPacket对象时,指定了封装数据的字节数组和数据的大小,没有指定IP地址和端口号。很明显,这样的对象只能用于接收端,不能用于发送端。因为发送端一定要明确指出数据的目的地(ip地址和端口号),而接收端不需要明确知道数据的来源,只需要接收到数据即可。


使用该构造方法在创建DatagramPacket对象时,不仅指定了封装数据的字节数组和数据的大小,还指定了数据包的目标IP地址(addr)和端口号(port)。该对象通常用于发送端,因为在发送数据时必须指定接收端的IP地址和端口号,就好像发送货物的集装箱上面必须标明接收人的地址一样。
上面我们讲解了DatagramPacket的构造方法,接下来对DatagramPacket类中的常用方法进行详细地讲解,如下表所示。

3.2 DatagramSocket


DatagramPacket数据包的作用就如同是“集装箱”,可以将发送端或者接收端的数据封装起来。然而运输货物只有“集装箱”是不够的,还需要有码头。在程序中需要实现通信只有DatagramPacket数据包也同样不行,为此JDK中提供的一个DatagramSocket类。DatagramSocket类的作用就类似于码头,使用这个类的实例对象就可以发送和接收DatagramPacket数据包,发送数据的过程如下图所示。


在创建发送端和接收端的DatagramSocket对象时,使用的构造方法也有所不同,下面对DatagramSocket类中常用的构造方法进行讲解。

该构造方法用于创建发送端的DatagramSocket对象,在创建DatagramSocket对象时,并没有指定端口号,此时,系统会分配一个没有被其它网络程序所使用的端口号。

该构造方法既可用于创建接收端的DatagramSocket对象,又可以创建发送端的DatagramSocket对象,在创建接收端的DatagramSocket对象时,必须要指定一个端口号,这样就可以监听指定的端口。
上面我们讲解了DatagramSocket的构造方法,接下来对DatagramSocket类中的常用方法进行详细地讲解。

3.3 UDP网络程序


讲解了DatagramPacket和DatagramSocket的作用,接下来通过一个案例来学习一下它们在程序中的具体用法。
下图为UDP发送端与接收端交互图解


要实现UDP通信需要创建一个发送端程序和一个接收端程序,很明显,在通信时只有接收端程序先运行,才能避免因发送端发送的数据无法接收,而造成数据丢失。因此,首先需要来完成接收端程序的编写。
 UDP完成数据的发送


/
 发送端
  1,创建DatagramSocket对象
 
 2,创建DatagramPacket对象,并封装数据
  3,发送数据
 
 4,释放流资源
 /

public class UDPSend {
    public static void main(String[] args) throws IOException {
        //1,创建DatagramSocket对象
        DatagramSocket sendSocket = new DatagramSocket();
        //2,创建DatagramPacket对象,并封装数据
        //public DatagramPacket(byte[] buf, int length, InetAddress address,  int port)
        //构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
        byte[] buffer = “hello,UDP”.getBytes();
        DatagramPacket dp = new DatagramPacket(buffer, buffer.length, InetAddress.getByName(“192.168.75.58”), 12306);
        //3,发送数据
        //public void send(DatagramPacket p) 从此套接字发送数据报包
        sendSocket.send(dp);
        //4,释放流资源
        sendSocket.close();
    }
}

 UDP完成数据的接收


/
  UDP接收端
 
 
  1,创建DatagramSocket对象
 
 2,创建DatagramPacket对象
  3,接收数据存储到DatagramPacket对象中
 
 4,获取DatagramPacket对象的内容
  5,释放流资源
 
/

public class UDPReceive {
    public static void main(String[] args) throws IOException {
        //1,创建DatagramSocket对象,并指定端口号
        DatagramSocket receiveSocket = new DatagramSocket(12306);
        //2,创建DatagramPacket对象, 创建一个空的仓库
        byte[] buffer = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buffer, 1024);
        //3,接收数据存储到DatagramPacket对象中
        receiveSocket.receive(dp);
        //4,获取DatagramPacket对象的内容
        //谁发来的数据  getAddress()
        InetAddress ipAddress = dp.getAddress();
        String ip = ipAddress.getHostAddress();//获取到了IP地址
        //发来了什么数据  getData()
        byte[] data = dp.getData();
        //发来了多少数据 getLenth()
        int length = dp.getLength();
        //显示收到的数据
        String dataStr = new String(data,0,length);
        System.out.println(“IP地址:”+ip+ “数据是”+ dataStr);
        //5,释放流资源
        receiveSocket.close();
    }
}

第4章 TCP通信


TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象。
区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。
而TCP通信是严格区分客户端与服务器端(什么是客户端和服务器?)的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接。
在JDK中提供了两个类用于实现TCP程序,一个是Socket类,用于表示客户端,一个是ServerSocket类,用于表示服务器端。
通信时,首先创建代表服务器端的ServerSocket对象,该对象相当于开启一个服务,并等待客户端的连接,然后创建代表客户端的Socket对象向服务器端发出连接请求,服务器端响应请求,两者建立连接开始通信。
4.1 ServerSocket
通过前面的学习知道,在开发TCP程序时,首先需要创建服务器端程序。JDK的java.net包中提供了一个ServerSocket类,该类的实例对象可以实现一个服务器段的程序。通过查阅API文档可知,ServerSocket类提供了多种构造方法,接下来就对ServerSocket的构造方法进行逐一地讲解。


使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上(参数port就是端口号)。
接下来学习一下ServerSocket的常用方法,如表所示。

ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的accept()方法,接收来自客户端的请求。当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求,accept()方法才会返回一个Scoket对象用于和客户端实现通信,程序才能继续向下执行。


4.2 Socket


讲解了ServerSocket对象可以实现服务端程序,但只实现服务器端程序还不能完成通信,此时还需要一个客户端程序与之交互,为此JDK提供了一个Socket类,用于实现TCP客户端程序。
通过查阅API文档可知Socket类同样提供了多种构造方法,接下来就对Socket的常用构造方法进行详细讲解。


使用该构造方法在创建Socket对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数host接收的是一个字符串类型的IP地址。

该方法在使用上与第二个构造方法类似,参数address用于接收一个InetAddress类型的对象,该对象用于封装一个IP地址。
在以上Socket的构造方法中,最常用的是第一个构造方法。
接下来学习一下Socket的常用方法,如表所示。

在Socket类的常用方法中,getInputStream()和getOutStream()方法分别用于获取输入流和输出流。当客户端和服务端建立连接后,数据是以IO流的形式进行交互的,从而实现通信。
接下来通过一张图来描述服务器端和客户端的数据传输,如下图所示。


4.3 简单的TCP网络程序


了解了ServerSocket、Socket类的基本用法,为了让大家更好地掌握这两个类的使用,接下来通过一个TCP通信的案例来进一步学习。如下图所示。


要实现TCP通信需要创建一个服务器端程序和一个客户端程序,为了保证数据传输的安全性,首先需要实现服务器端程序。


/
 
 TCP 服务器端
  
 
 1,创建服务器ServerSocket对象(指定服务器端口号)
  2,开启服务器了,等待客户端的连接,当客户端连接后,可以获取到连接服务器的客户端Socket对象
 
 3,给客户端反馈信息
  4,关闭流资源
 
/

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1,创建服务器ServerSocket对象(指定服务器端口号)
        ServerSocket ss = new ServerSocket(8888);
        //2,开启服务器了,等待客户端的连接,当客户端连接后,可以获取到连接服务器的客户端Socket对象
        Socket s = ss.accept();
        //3,给客户端反馈信息
        /
         
 a,获取客户端的输出流
          b,在服务端端,通过客户端的输出流写数据给客户端
         
/

        //a,获取客户端的输出流
        OutputStream out = s.getOutputStream();
        //b,在服务端端,通过客户端的输出流写数据给客户端
        out.write(“你已经连接上了服务器”.getBytes());
        //4,关闭流资源
        out.close();
        s.close();
        //ss.close();  服务器流 通常都是不关闭的
    }
}

完成了服务器端程序的编写,接下来编写客户端程序。


/
 
 TCP 客户端
  
 
 1,创建客户端Socket对象,(指定要连接的服务器地址与端口号)
  2,获取服务器端的反馈回来的信息
 
 3,关闭流资源
 /

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1,创建客户端Socket对象,(指定要连接的服务器地址与端口号)
        Socket s = new Socket(“192.168.74.58”8888);
        //2,获取服务器端的反馈回来的信息
        InputStream in = s.getInputStream();
        //获取获取流中的数据
        byte[] buffer = new byte[1024];
        //把流中的数据存储到数组中,并记录读取字节的个数
        int length = in.read(buffer);
        //显示数据
        System.out.println( new String(buffer, 0 , length) );
        //3,关闭流资源
        in.close();
        s.close();
    }
}

4.4 文件上传案例
目前大多数服务器都会提供文件上传的功能,由于文件上传需要数据的安全性和完整性,很明显需要使用TCP协议来实现。接下来通过一个案例来实现图片上传的功能。如下图所示。原图:文件上传.bmp


    首先编写服务器端程序,用来接收图片。
/
  文件上传  服务器端
 

 /

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1,创建服务器,等待客户端连接
        ServerSocket serverSocket = new ServerSocket(8888);
        Socket clientSocket = serverSocket.accept();
        //显示哪个客户端Socket连接上了服务器
        InetAddress ipObject = clientSocket.getInetAddress();//得到IP地址对象
        String ip = ipObject.getHostAddress(); //得到IP地址字符串
        System.out.println(“小样,抓到你了,连接我!!” + “IP:” + ip);

        //7,获取Socket的输入流
        InputStream in = clientSocket.getInputStream();
        //8,创建目的地的字节输出流   D:\upload\192.168.74.58(1).jpg
        BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream(“D:\upload\192.168.74.58(1).jpg”));
        //9,把Socket输入流中的数据,写入目的地的字节输出流中
        byte[] buffer = new byte[1024];
        int len = -1;
        while((len = in.read(buffer)) != -1){
            //写入目的地的字节输出流中
            fileOut.write(buffer, 0, len);
        }

        //—————–反馈信息———————
        //10,获取Socket的输出流, 作用:写反馈信息给客户端
        OutputStream out = clientSocket.getOutputStream();
        //11,写反馈信息给客户端
        out.write(“图片上传成功”.getBytes());

        out.close();
        fileOut.close();
        in.close();
        clientSocket.close();
        //serverSocket.close();
    }
}

 编写客户端,完成上传图片


/
  文件上传 客户端
 
 
  public void shutdownOutput()  禁用此Socket的输出流,间接的相当于告知了服务器数据写入完毕
 
/

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //2,创建客户端Socket,连接服务器
        Socket socket = new Socket(“192.168.74.58”8888);
        //3,获取Socket流中的输出流,功能:用来把数据写到服务器
        OutputStream out = socket.getOutputStream();
        //4,创建字节输入流,功能:用来读取数据源(图片)的字节
        BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream(“D:\NoDir\test.jpg”));
        //5,把图片数据写到Socket的输出流中(把数据传给服务器)
        byte[] buffer = new byte[1024];
        int len = -1;
        while ((len = fileIn.read(buffer)) != -1){
            //把数据写到Socket的输出流中
            out.write(buffer, 0, len);
        }
        //6,客户端发送数据完毕,结束Socket输出流的写入操作,告知服务器端
        socket.shutdownOutput();

        //—————–反馈信息———————
        //12,获取Socket的输入流  作用: 读反馈信息
        InputStream in = socket.getInputStream();
        //13,读反馈信息
        byte[] info = new byte[1024];
        //把反馈信息存储到info数组中,并记录字节个数
        int length = in.read(info);
        //显示反馈结果
        System.out.println( new String(info, 0, length) );

        //关闭流
        in.close();
        fileIn.close();
        out.close();
        socket.close();
    }
}

4.5 文件上传案例多线程版本


实现服务器端可以同时接收多个客户端上传的文件。
 我们要修改服务器端代码


/
 
 文件上传多线程版本, 服务器端
 */

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1,创建服务器,等待客户端连接
        ServerSocket serverSocket = new ServerSocket(6666);

        //实现多个客户端连接服务器的操作
        while(true){
            final Socket clientSocket = serverSocket.accept();
            //启动线程,完成与当前客户端的数据交互过程
            new Thread(){
                public void run({
                    try{
                        //显示哪个客户端Socket连接上了服务器
                        InetAddress ipObject = clientSocket.getInetAddress();//得到IP地址对象
                        String ip = ipObject.getHostAddress(); //得到IP地址字符串
                        System.out.println(“小样,抓到你了,连接我!!” + “IP:” + ip);

                        //7,获取Socket的输入流
                        InputStream in = clientSocket.getInputStream();
                        //8,创建目的地的字节输出流   D:\upload\192.168.74.58(1).jpg
                        BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream(“D:\upload\“+ip+“(“+System.currentTimeMillis()+“).jpg”));
                        //9,把Socket输入流中的数据,写入目的地的字节输出流中
                        byte[] buffer = new byte[1024];
                        int len = -1;
                        while((len = in.read(buffer)) != -1){
                            //写入目的地的字节输出流中
                            fileOut.write(buffer, 0, len);
                        }

                        //—————–反馈信息———————
                        //10,获取Socket的输出流, 作用:写反馈信息给客户端
                        OutputStream out = clientSocket.getOutputStream();
                        //11,写反馈信息给客户端
                        out.write(“图片上传成功”.getBytes());

                        out.close();
                        fileOut.close();
                        in.close();
                        clientSocket.close();
                    } catch(IOException e){
                        e.printStackTrace();
                    }
                };
            }.start();
        }

        //serverSocket.close();
    }
}

第5章 总结


5.1 知识点总结


 IP地址:用来唯一表示我们自己的电脑的,是一个网络标示
 端口号: 用来区别当前电脑中的应用程序的
 UDP: 传送速度快,但是容易丢数据,如视频聊天,语音聊天
 TCP: 传送稳定,不会丢失数据,如文件的上传、下载
 UDP程序交互的流程


 发送端


1,创建DatagramSocket对象
2,创建DatagramPacket对象,并封装数据
3,发送数据
4,释放流资源


 接收端


1,创建DatagramSocket对象
2,创建DatagramPacket对象
3,接收数据存储到DatagramPacket对象中
4,获取DatagramPacket对象的内容
5,释放流资源


 TCP程序交互的流程


 客户端


1,创建客户端的Socket对象
2,获取Socket的输出流对象
3,写数据给服务器
4,获取Socket的输入流对象
5,使用输入流,读反馈信息
6,关闭流资源


 服务器端


1,创建服务器端ServerSocket对象,指定服务器端端口号
2,开启服务器,等待着客户端Socket对象的连接,如有客户端连接,返回客户端的Socket对象
3,通过客户端的Socket对象,获取客户端的输入流,为了实现获取客户端发来的数据
4,通过客户端的输入流,获取流中的数据
5,通过客户端的Socket对象,获取客户端的输出流,为了实现给客户端反馈信息
6,通过客户端的输出流,写数据到流中
7,关闭流资源

转换流,打印流,序列化


微信公众号:菜鸟永恒



第11天 IO流
今日内容介绍
 转换流
 序列化流
 打印流
 Properties
今日学习目标
 能够阐述编码表的意义
 能够使用转换流读取指定编码的文本文件
 能够使用转换流写入指定编码的文本文件
 能够使用Properties的load方法加载文件中配置信息
 能够使用Properties的store方法保存配置信息到文件
 能够说出打印流的特点
 能够使用序列化流写出对象到文件
 能够使用反序列化流读取文件到程序中
 能够导入commons-io工具包
 能够配置classpath添加jar包到项目中
 能够使用FileUtils常用方法操作文件


第1章 字符流
经过前面的学习,我们基本掌握的文件的读写操作,在操作过程中字节流可以操作所有数据,可是当我们操作的文件中有中文字符,并且需要对中文字符做出处理时怎么办呢?
1.1 字节流读取字符的问题
通过以下程序读取带有中文件的文件。


public class CharStreamDemo {
    public static void main(String[] args) throws IOException {
        //给文件中写中文
        writeCNText();
        //读取文件中的中文
        readCNText();
    }   
    //读取中文
    public static void readCNText() throws IOException {
        FileInputStream fis = new FileInputStream(“c:\cn.txt”);
        int ch = 0;
        while((ch = fis.read())!=-1){
            System.out.println(ch);
        }

    }
    //写中文
    public static void writeCNText() throws IOException {
        FileOutputStream fos = new FileOutputStream(“c:\cn.txt”);
        fos.write(“a欢迎你”.getBytes());
        fos.close();
    }
}

上面程序在读取含有中文的文件时,我们并没有看到具体的中文,而是看到一些数字,这是什么原因呢?既然看不到中文,那么我们如何对其中的中文做处理呢?要解决这个问题,我们必须研究下字符的编码过程。


1.2 字符编码表
我们知道计算机底层数据存储的都是二进制数据(二进制了解吗?最高位表示什么?),而我们生活中的各种各样的数据,如何才能和计算机中存储的二进制数据对应起来呢?
这时老美他们就把每一个字符和一个整数对应起来,就形成了一张编码表,老美他们的编码表就是ASCII表。其中就是各种英文字符对应的编码。
编码表:其实就是生活中字符和计算机二进制的对应关系表。
1、ascii: 一个字节中的7位就可以表示。对应的码值都是正数。00000000-0xxxxxxx(其他码表都要兼容它) 0000 0000
2、iso-8859-1:拉丁码表 latin,用了一个字节用的8位。1-xxxxxxx 负数。
3、GB2312:简体中文码表。包含6000-7000中文和符号。用两个字节表示。中文的两个字节都是开头为1 ,即两个字节都是负数。
GBK:目前最常用的中文码表,2万的中文和符号。用两个字节表示,其中的一部分文字,第一个字节开头是1,第二字节开头是0
GB18030:最新的中文码表,目前还没有正式使用。やめて
4、unicode:国际标准码表:无论是什么文字,都用两个字节存储。
 Java中的char类型用的就是这个码表。char c = ‘a’;占两个字节。
 Java中的字符串是按照系统默认码表来解析的。简体中文版 字符串默认的码表是GBK。
5、UTF-8:基于unicode,一个字节就可以存储的数据,不用两个字节存储,而且这个码表更加的标准化,在每一个字节头加入了编码信息(后期到api中查找)。
能识别中文的码表:GBK、UTF-8;正因为识别中文码表不唯一,涉及到了编码解码问题。
对于我们开发而言;常见的编码 GBK UTF-8 ISO-8859-1
1.GBK:中文2字节
2.UTF-8:中文3字节


文字—>(数字) :编码: 就是看能看懂内容,转换成看不懂的内容。a 0110 0001
(数字)—>文字 : 解码: 就是把看不懂的内容,转换成看懂的内容。0110 0001 –a


1.3 方便程序员的IO流
在IO开发过程中,我们传输最频繁的数据为字符,


我爱你—> 10100101010100110010101001—>妹子


而以字节方式传输字符需要每次将字符串转换成字节再处理,而且也丧失了程序员对数据内容的判断(因为程序员只认识字符,不认识字节)。所以,为了让程序员方便对字符进行操作,Java提供了专门以字符作为操作单位的类——字符流,其底层仍然为字节流。
显然,字符流只能操作字符,无法操作其他数据,如声音、视频等。
在基础班我们已经学习过字符流了,这里就进行一个简单的回顾
1.4 字符流回顾
 IO流的分类
|- 字节流
|- 字节输入流 InputStream 抽象类
|- FileInputStream 操作文件的字节输入流
|- 字节输出流 OuputStream抽象类
|- FileOutputStream 操作文件的字节输出流
|- 字符流
|- 字符输入流 Reader抽象类
|- FileReader 用来操作文件的字符输入流(简便的流)
|- 字符输出流 Writer抽象类
|- FileWriter 用来操作文件的字符输出流(简便的流)
Java中流的命名 规范 功能+类型


第2章 转换流


在学习字符流(FileReader、FileWriter)的时候,其中说如果需要指定编码和高效区大小时,可以在字节流的基础上,构造一个InputStreamReader或者OutputStreamWriter,这又是什么意思呢?
2.1 字符编码表(重复内容)
我们知道计算机底层数据存储的都是二进制数据,而我们生活中的各种各样的数据,如何才能和计算机中存储的二进制数据对应起来呢?
这时老美他们就把每一个字符和一个整数对应起来,就形成了一张编码表,老美他们的编码表就是ASCII表。其中就是各种英文字符对应的编码。
编码表:其实就是生活中字符和计算机二进制的对应关系表。
1、ascii: 一个字节中的7位就可以表示。对应的字节都是正数。0-xxxxxxx
2、iso-8859-1:拉丁码表 latin,用了一个字节用的8位。1-xxxxxxx 负数。
3、GB2312:简体中文码表。包含6000-7000中文和符号。用两个字节表示。两个字节都是开头为1 ,两个字节都是负数。
GBK:目前最常用的中文码表,2万的中文和符号。用两个字节表示,其中的一部分文字,第一个字节开头是1,第二字节开头是0
GB18030:最新的中文码表,目前还没有正式使用。
4、unicode:国际标准码表:无论是什么文字,都用两个字节存储。
 Java中的char类型用的就是这个码表。char c = ‘a’;占两个字节。
 Java中的字符串是按照系统默认码表来解析的。简体中文版 字符串默认的码表是GBK。
5、UTF-8:基于unicode,一个字节就可以存储数据,不要用两个字节存储,而且这个码表更加的标准化,在每一个字节头加入了编码信息(后期到api中查找)。
能识别中文的码表:GBK、UTF-8;正因为识别中文码表不唯一,涉及到了编码解码问题。
对于我们开发而言;常见的编码 GBK UTF-8 ISO-8859-1
文字—>(数字) :编码: 就是看能看懂内容,转换成看不懂的内容。
(数字)—>文字 : 解码: 就是把看不懂的内容,转换成看懂的内容。


2.2 OutputStreamWriter类(转换流,快递运输的思想)
查阅OutputStreamWriter的API介绍,OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的字符编码表,将要写入流中的字符编码成字节。它的作用的就是,将字符串按照指定的编码表转成字节,在使用字节流将这些字节写出去。


 代码演示:


public static void writeCN() throws Exception {
        //创建与文件关联的字节输出流对象
        FileOutputStream fos = new FileOutputStream(“c:\cn8.txt”);
        //创建可以把字符转成字节的转换流对象,并指定编码
        OutputStreamWriter osw = new OutputStreamWriter(fos,“utf-8”);
        //调用转换流,把文字写出去,其实是写到转换流的高效区中
        osw.write(“你好”);//写入高效区。
        osw.close();
    }

OutputStreamWriter流对象,它到底如何把字符转成字节输出的呢?
其实在OutputStreamWriter流中维护自己的高效区,当我们调用OutputStreamWriter对象的write方法时,会拿着字符到指定的码表中进行查询,把查到的字符编码值转成字节数存放到OutputStreamWriter高效区中。然后再调用刷新功能,或者关闭流,或者高效区存满后会把高效区中的字节数据使用字节流写到指定的文件中。


2.3 InputStreamReader类
查阅InputStreamReader的API介绍,InputStreamReader 是字节流通向字符流的桥梁:它使用指定的字符编码表读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。


 代码演示


public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
        //演示字节转字符流的转换流
        readCN();
    }
    public static void readCN() throws IOException{
        //创建读取文件的字节流对象
        InputStream in = new FileInputStream(“c:\cn8.txt”);
        //创建转换流对象 
        //InputStreamReader isr = new InputStreamReader(in);这样创建对象,会用本地默认码表读取,将会发生错误解码的错误
        InputStreamReader isr = new InputStreamReader(in,“utf-8”);
        //使用转换流去读字节流中的字节
        int ch = 0;
        while((ch = isr.read())!=-1){
            System.out.println((char)ch);
        }
        //关闭流
        isr.close();
    }
}

注意:在读取指定的编码的文件时,一定要指定编码格式,否则就会发生解码错误,而发生乱码现象。


2.4 转换流和子类区别
发现有如下继承关系:
Writer 字符输出流
|- OutputStreamWriter 转换流(字符流—>字节流)(属于字符输出流, 可以指定字符编码表,用来写入数据到文件)
|–FileWriter 操作文件中字符输出流,采用默认的字符编码表
fw.write(“你好”)
Reader 字符输入流
|- InputStreamReader: 转换流(字节流字符流)(属于字符输入流,可以指定字符编码表,用来从文件中读数据)
|–FileReader操作文件中字符输入流,采用默认的字符编码表


父类和子类的功能有什么区别呢?
OutputStreamWriter和InputStreamReader是字符和字节的桥梁:也可以称之为字符转换流。字符转换流原理:字节流+编码表。
FileWriter和FileReader:作为子类,仅作为操作字符文件的便捷类存在。当操作的字符文件,使用的是默认编码表时可以不用父类,而直接用子类就完成操作了,简化了代码。


InputStreamReader isr = new InputStreamReader(new FileInputStream(“a.txt”));//默认GBK字符集。
InputStreamReader isr = new InputStreamReader(new FileInputStream(“a.txt”),”GBK”);//指定GBK字符集。
FileReader fr = new FileReader(“a.txt”);


OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“a.txt”));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“a.txt”),”GBK”);


FileWriter fw = new FileWriter(“a.txt”)


这三句代码的功能是一样的,其中第三句最为便捷。
注意:一旦要指定其他编码时,绝对不能用子类,必须使用字符转换流。什么时候用子类呢?
条件:
1、操作的是文件。2、使用默认编码。
总结:
字节—>解码—>字符 : 看不懂的—>看的懂的。 需要读。输入流。 InputStreamReader
字符—>编码—>字节 : 看的懂的—>看不懂的。 需要写。输出流。 OutputStreamWriter


第3章 序列化流与反序列化流
用于从流中读取对象的操作流 ObjectInputStream 称为 反序列化流
用于向流中写入对象的操作流 ObjectOutputStream 称为 序列化流
 特点:用于操作对象。可以将对象写入到文件中,也可以从文件中读取对象。


3.1 对象序列化流ObjectOutputStream
ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。
注意:只能将支持 java.io.Serializable 接口的对象写入流中


 代码演示:


public class ObjectStreamDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        /
         
 将一个对象存储到持久化(硬盘)的设备上。
         /

        writeObj();//对象的序列化。
    }
    public static void writeObj() throws IOException {
        //1,明确存储对象的文件。
        FileOutputStream fos = new FileOutputStream(“tempfile\obj.object”);
        //2,给操作文件对象加入写入对象功能。
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        //3,调用了写入对象的方法。
        oos.writeObject(new Person(“wangcai”,20));
        //关闭资源。
        oos.close();
    }
}

 Person类


public class Person implements Serializable {
    private String name;
    private int age;
    public Person() {
        super();
    }
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return “Person [name=” + name + “, age=” + age + “]”;
    }
}

3.2 对象反序列化流ObjectInputStream
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。支持 java.io.Serializable接口的对象才能从流读取。


 代码演示


public class ObjectStreamDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        readObj();//对象的反序列化。
    }
    public static void readObj() throws IOException, ClassNotFoundException {

        //1,定义流对象关联存储了对象文件。
        FileInputStream fis = new FileInputStream(“tempfile\obj.object”);

        //2,建立用于读取对象的功能对象。
        ObjectInputStream ois = new ObjectInputStream(fis);

        Person obj = (Person)ois.readObject();

        System.out.println(obj.toString());

    }
}

3.3 序列化接口(主要是介绍两个异常)
当一个对象要能被序列化,这个对象所属的类必须实现Serializable接口。否则会发生异常NotSerializableException异常。
同时当反序列化对象时,如果对象所属的class文件在序列化之后进行的修改,那么进行反序列化也会发生异常InvalidClassException。发生这个异常的原因如下:
 该类的序列版本号与从流中读取的类描述符的版本号不匹配
 该类包含未知数据类型
 该类没有可访问的无参数构造方法
Serializable标记接口。该接口给需要序列化的类,提供了一个序列版本号。serialVersionUID. 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。


 代码修改如下,修改后再次写入对象,读取对象测试


public class Person implements Serializable {
    //给类显示声明一个序列版本号。
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    public Person() {
        super();

    }
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return “Person [name=” + name + “, age=” + age + “]”;
    }
}

3.4 瞬态关键字transient
当一个类的对象需要被序列化时,某些属性不需要被序列化,这时不需要序列化的属性可以使用关键字transient修饰。只要被transient修饰了,序列化时这个属性就不会被序列化了。
同时静态修饰也不会被序列化,因为序列化是把对象数据进行持久化存储,而静态的属于类加载时的数据,不会被序列化。
 代码修改如下,修改后再次写入对象,读取对象测试


public class Person implements Serializable {
    /
      给类显示声明一个序列版本号。
     
/

    private static final long serialVersionUID = 1L;
    private static String name;
    private transient/瞬态/ int age;

    public Person() {
        super();

    }

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return “Person [name=” + name + “, age=” + age + “]”;
    }
}

第4章 打印流
4.1 打印流的概述
打印流添加输出数据的功能,使它们能够方便地打印各种数据值表示形式.
打印流根据流的分类:
 字节打印流 PrintStream
 字符打印流 PrintWriter
 方法:
void print(String str): 输出任意类型的数据,
void println(String str): 输出任意类型的数据,自动写入换行操作
 代码演示:


 / 
 
 需求:把指定的数据,写入到printFile.txt文件中
  
 
 分析:
      1,创建流
 
     2,写数据
      3,关闭流
 
/

public class PrintWriterDemo {
    public static void main(String[] args) throws IOException {
        //创建流
        //PrintWriter out = new PrintWriter(new FileWriter(“printFile.txt”));
        PrintWriter out = new PrintWriter(“printFile.txt”);
        //2,写数据
        for (int i=0; i<5; i++) {
            out.println(“helloWorld”);
        }
        //3,关闭流
        out.close();
    }
}

第5章 commons-IO


5.1 导入classpath
加入classpath的第三方jar包内的class文件才能在项目中使用
使用第三方类库的步骤:
1.创建lib文件夹
2.将commons-io.jar拷贝到lib文件夹
3.右键点击commons-io.jar,Build Path→Add to Build Path


5.2 FileUtils


提供文件操作(移动文件,读取文件,检查文件是否存在等等)的方法。
 常用方法:


readFileToString(File file):读取文件内容,并返回一个String;
writeStringToFile(File file,String content):将内容content写入到file中;
copyFile(File srcFile, File destFile): 文件复制
copyDirectoryToDirectory(File srcDir,File destDir);文件夹复制


 代码演示:


/
 
 普通方式,完成文件的复制
 /

public class CommonsIODemo01 {
    public static void main(String[] args) throws IOException {
        //method1(“D:\test.avi”, ”D:\copy.avi”);

        //通过Commons-IO完成了文件复制的功能
        FileUtils.copyFile(new File(“D:\test.avi”), new File(“D:\copy.avi”));
    }

    //文件的复制
    private static void method1(String src, String dest) throws IOException {
        //1,指定数据源 
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(src));
        //2,指定目的地
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dest));
        //3,读
        byte[] buffer = new byte[1024];
        int len = -1;
        while ( (len = in.read(buffer)) != -1) {
            //4,写
            out.write(buffer, 0, len);
        }
        //5,关闭流
        in.close();
        out.close();
    }
}

/
  使用commons-io完成文件、文件夹的复制
 
/

public class CommonsIODemo02 {
    public static void main(String[] args) throws IOException {
        //通过Commons-IO完成了文件复制的功能
        FileUtils.copyFile(new File(“D:\test.avi”), new File(“D:\copy.avi”));

        //通过Commons-IO完成了文件夹复制的功能
        //D:\基础班 复制到 C:\abc文件夹下
        FileUtils.copyDirectoryToDirectory(new File(“D:\基础班”), new File(“C:\abc”));
    }
}

IO【字节流、高效流】


微信公众号:菜鸟永恒
第11天 IO流
今日内容介绍
 字节流
 缓冲流
今日学习目标
 能够使用字节输出流写出数据到文件
 能够使用字节输入流读取数据到程序
 能够理解读取数据read(byte[])方法的原理
 能够使用字节流完成文件的复制
 能够使用字节缓冲流读取数据到程序
 能够使用字节缓冲流写出数据到文件
 能够完成单级文件夹复制
第1章 IO
1.1 IO概述
回想之前写过的程序,数据都是在内存中,一旦程序运行结束,这些数据都没有了,等下次再想使用这些数据,可是已经没有了。那怎么办呢?能不能把运算完的数据都保存下来,下次程序启动的时候,再把这些数据读出来继续使用呢?其实要把数据持久化存储,就需要把内存中的数据存储到内存以外的其他持久化设备(硬盘、光盘、U盘等 ROM)上。
当需要把内存中的数据存储到持久化设备上这个动作称为输出(写)Output操作。
当把持久设备上的数据读取到内存中的这个动作称为输入(读)Input操作。
因此我们把这种输入和输出动作称为IO操作。


1.2 IO流流向分类
按照流向分:输入流与输出流,每个IO流对象均要绑定一个IO资源
分类关系如下:
字节输入流 InputStream 抽象类
FileInputStream 操作文件的字节输入流
字节输出流 OutputStream 抽象类
FileOutputStream 操作文件的字节输出流
按照传输方式:分为字节流和字符流
字符流 按照字符的方式读写
字节流 按照字节的方式读写



1.3 一切均为字节
在数据传输过程中,一切数据(文本、图像、声音等)最终存储的均为一个个字节,即二进制数字。所以数据传输过程中使用二进制数据可以完成任意数据的传递。
为什么打开一个文本文件 我们看到的不是0101001这些数据?
我们向一个文件中存储一定的数据(一些数字),如果使用文本方式打开,则会以文本的方式解释数据。如果以视频的方式打开,则会以视频的方式解释数据。音频、可行执行文件等亦是如此。所以,在文件传输过程中,我们要时刻明确,传输的始终为二进制数据。


第2章 字节流
既然一切数据都是字节,那么字节流如何操作呢,那么接下来我们进行字节流的学习.
2.1 字节输出流OutputStream
OutputStream此抽象类,是表示输出字节流的所有类的超类。操作的数据都是字节,定义了输出字节流的基本共性功能方法。
输出流中定义都是写write方法,如下图:


2.1.1 FileOutputStream类
OutputStream有很多子类,其中子类FileOutputStream可用来写入数据到文件。
FileOutputStream类,即文件输出流,是用于将数据写入 File的输出流。

 构造方法


2.1.2 FileOutputStream类写入数据到文件中(三个方法)
 将数据写到文件中,
 步骤
 1.创建输出流对象
 2.调用输出流对象的写数据方法
 3.释放资源
 代码演示:


public class FileOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        //需求:将数据写入到文件中。
        //创建存储数据的文件。
        File file = new File(“c:\file.txt”);
        //创建一个用于操作文件的字节输出流对象。一创建就必须明确数据存储目的地。
        //输出流目的是文件,会自动创建。如果文件存在,则覆盖。
        FileOutputStream fos = new FileOutputStream(file);
        //调用父类中的write方法。
        byte[] data = “abcde”.getBytes();
        fos.write(data);
        //关闭流资源。
        fos.close();
    }
}

2.1.3 给文件中续写和换行
我们直接new FileOutputStream(file)这样创建对象,写入数据,会覆盖原有的文件,那么我们想在原有的文件中续写内容怎么办呢?
继续查阅FileOutputStream的API。发现在FileOutputStream的构造函数中,可以接受一个boolean类型的值,如果值true,就会在文件末位继续添加。
 构造方法


 给文件中续写数据和换行,代码演示:


public class FileOutputStreamDemo2 {
    public static void main(String[] args) throws Exception {
        File file = new File(“c:\file.txt”);
        FileOutputStream fos = new FileOutputStream(file, true);
        String str = “\r\n”+“itcast”;
        fos.write(str.getBytes());
        fos.close();
    }
}

2.2 字节输入流InputStream
通过前面的学习,我们可以把内存中的数据写出到文件中,那如何想把内存中的数据读到内存中,我们通过InputStream可以实现。InputStream此抽象类,是表示字节输入流的所有类的超类。,定义了字节输入流的基本共性功能方法。


 int read():读取一个字节并返回,没有字节返回-1.
 int read(byte[]): 读取一定量的字节数,并存储到字节数组中,返回读取到的字节数。
2.2.1 FileInputStream类
InputStream有很多子类,其中子类FileInputStream可用来读取文件内容。
FileInputStream 从文件系统中的某个文件中获得输入字节。

 构造方法

2.2.2 FileInputStream类读取数据read方法(只读纯英文,有空介绍一下读中文问题)
在读取文件中的数据时,调用read方法,实现从文件中读取数据

 从文件中读取数据,代码演示:


public class FileInputStreamDemo {
    public static void main(String[] args) throws IOException {
        File file = new File(“c:\file.txt”);
        //创建一个字节输入流对象,必须明确数据源,其实就是创建字节读取流和数据源相关联。
        FileInputStream fis = new FileInputStream(file);
        //读取数据。使用 read();一次读一个字节。
        int ch = 0;
        while((ch=fis.read())!=-1){
            System.out.println(“ch=”+(char)ch);
        }
        // 关闭资源。
        fis.close();
    }
}

2.2.3 读取数据read(byte[])方法
在读取文件中的数据时,调用read方法,每次只能读取一个,太麻烦了,于是我们可以定义数组作为临时的存储容器,这时可以调用重载的read方法,一次可以读取多个字符。


public class FileInputStreamDemo2 {
    public static void main(String[] args) throws IOException {
        /
         
 演示第二个读取方法, read(byte[]);
         /

        File file = new File(“c:\file.txt”);
        // 创建一个字节输入流对象,必须明确数据源,其实就是创建字节读取流和数据源相关联。
        FileInputStream fis = new FileInputStream(file);        
        //创建一个字节数组。
        byte[] buf = new byte[1024];//长度可以定义成1024的整数倍。      
        int len = 0;
        while((len=fis.read(buf))!=-1){
            System.out.println(new String(buf,0,len));
        }
        fis.close();
    }
}

2.3 字节流练习
既然会了文件的读和写操作了,那么我们就要在这个基础上进行更为复杂的操作。使用读写操作完成文件的复制。
2.3.1 复制文件
原理;读取一个已有的数据,并将这些读到的数据写入到另一个文件中。


public class CopyFileTest {
    public static void main(String[] args) throws IOException {
        //1,明确源和目的。
        File srcFile = new File(“c:\YesDir\test.JPG”);
        File destFile = new File(“copyTest.JPG”);

        //2,明确字节流 输入流和源相关联,输出流和目的关联。
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);

        //3, 使用输入流的读取方法读取字节,并将字节写入到目的中。
        int ch = 0;
        while((ch=fis.read())!=-1){
            fos.write(ch);
        }
        //4,关闭资源。
        fos.close();
        fis.close();
    }
}

上述代码输入流和输出流之间是通过ch这个变量进行数据交换的。
上述复制文件有个问题,每次都从源文件读取一个,然后在写到指定文件,接着再读取一个字符,然后再写一个,一直这样下去。效率极低。
2.3.2 临时数组方式复制文件
上述代码复制文件效率太低了,并且频繁的从文件读数据,和写数据,能不能一次多把文件中多个数据都读进内容中,然后在一次写出去,这样的速度一定会比前面代码速度快。


public class CopyFileByBufferTest {
    public static void main(String[] args) throws IOException {
        File srcFile = new File(“c:\YesDir\test.JPG”);
        File destFile = new File(“copyTest.JPG”);
        // 明确字节流 输入流和源相关联,输出流和目的关联。
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);
        //定义一个缓冲区。
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = fis.read(buf)) != -1) {
            fos.write(buf, 0, len);// 将数组中的指定长度的数据写入到输出流中。
        }
        // 关闭资源。
        fos.close();
        fis.close();
    }
}

第3章 缓冲流
在我们学习字节流与字符流的时候,大家都进行过读取文件中数据的操作,读取数据量大的文件时,读取的速度会很慢,很影响我们程序的效率,那么,我想提高速度,怎么办?
Java中提高了一套缓冲流,它的存在,可提高IO流的读写速度
缓冲流,根据流的分类分类字节缓冲流与字符缓冲流。
在基础班我们已经学习过字符缓冲流了,那么今天学习一下字节缓冲流。
3.1 字节缓冲流
字节缓冲流根据流的方向,共有2个
 写入数据到流中,字节缓冲输出流 BufferedOutputStream
 读取流中的数据,字节缓冲输入流 BufferedInputStream


它们的内部都包含了一个缓冲区,通过缓冲区读写,就可以提高了IO流的读写速度
3.1.1 字节缓冲输出流BufferedOutputStream
通过字节缓冲流,进行文件的读写操作 写数据到文件的操作
 构造方法
public BufferedOutputStream(OutputStream out)创建一个新的缓冲输出流,以将数据写入指定的底层输出流。


public class BufferedOutputStreamDemo01 {
    public static void main(String[] args) throws IOException {

        //写数据到文件的方法
        write();
    }

    /
      写数据到文件的方法
     
 1,创建流
      2,写数据
     
 3,关闭流
     /

    private static void write() throws IOException {
        //创建基本的字节输出流
        FileOutputStream fileOut = new FileOutputStream(“abc.txt”);
        //使用高效的流,把基本的流进行封装,实现速度的提升
        BufferedOutputStream out = new BufferedOutputStream(fileOut);
        //2,写数据
        out.write(“hello”.getBytes());
        //3,关闭流
        out.close();
    }
}

3.1.2 字节缓冲输入流 BufferedInputStream
刚刚我们学习了输出流实现了向文件中写数据的操作,那么,现在我们完成读取文件中数据的操作
 构造方法
public BufferedInputStream(InputStream in)


    /
      从文件中读取数据
     
 1,创建缓冲流对象
      2,读数据,打印
     
 3,关闭
     /

    private static void read() throws IOException {
        //1,创建缓冲流对象
        FileInputStream fileIn = new FileInputStream(“abc.txt”);
        //把基本的流包装成高效的流
        BufferedInputStream in = new BufferedInputStream(fileIn);
        //2,读数据
        int ch = -1;
        while ( (ch = in.read()) != -1 ) {
            //打印
            System.out.print((char)ch);
        }
        //3,关闭
        in.close();
    }

3.1.3 复制单级文件夹



  数据源:e:\demo
 
 目的地:e:\test
  
 
 分析:
          A:封装目录
 
         B:获取该目录下的所有文本的File数组
          C:遍历该File数组,得到每一个File对象
 
         D:把该File进行复制
 */

public class CopyFolderDemo {
    public static void main(String[] args) throws IOException {
        // 封装目录
        File srcFolder = new File(“e:\demo”);
        // 封装目的地
        File destFolder = new File(“e:\test”);
        // 如果目的地文件夹不存在,就创建
        if (!destFolder.exists()) {
            destFolder.mkdir();
        }

        // 获取该目录下的所有文本的File数组
        File[] fileArray = srcFolder.listFiles();

        // 遍历该File数组,得到每一个File对象
        for (File file : fileArray) {
            // System.out.println(file);
            // 数据源:e:\demo\e.mp3
            // 目的地:e:\test\e.mp3
            String name = file.getName(); // e.mp3
            File newFile = new File(destFolder, name); // e:\test\e.mp3

            copyFile(file, newFile);
        }
    }

    private static void copyFile(File file, File newFile) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
                file));
        BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream(newFile));

        byte[] bys = new byte[1024];
        int len = 0;
        while ((len = bis.read(bys)) != -1) {
            bos.write(bys, 0, len);
        }

        bos.close();
        bis.close();
    }
}

IO【File、递归】


微信公众号:菜鸟永恒 欢迎关注



第10天 File,递归
今日内容介绍
 File
 递归
今日学习目标
 能够说出File对象的创建方式
 能够说出File类获取名称的方法名称
 能够说出File类获取绝对路径的方法名称
 能够说出File类获取文件大小的方法名称
 能够说出File类判断是否是文件的方法名称
 能够说出File类判断是否是文件夹的方法名称
 能够辨别相对路径和绝对路径
 能够遍历文件夹
 能够解释递归的含义
 能够使用递归的方式计算5的阶乘
 能够说出使用递归会内存溢出隐患的原因


第1章 File
在我们操作系统中,数据都保存在文件中,而文件存放相应的文件夹中。那么Java中是如何描述这些的呢?
1.1 File类的出现
打开API,搜索File类。阅读其描述:File文件和目录路径名的抽象表示形式。即,Java中把文件或者目录(文件夹)都封装成File对象。也就是说如果我们要去操作硬盘上的文件,或者文件夹只要找到File这个类即可。那么我们就要研究研究File这个类中都有那些功能可以操作文件或者文件夹呢?
1.2 File类的构造方法


 通过构造方法创建File对象,我们进行演示:


public class FileDemo {
    public static void main(String[] args{
        //File构造函数演示
        String pathName = “e:\java_code\day22e\hello.java”;
        File f1 = new File(pathName);//将Test22文件封装成File对象。注意;有可以封装不存在文件或者文件夹,变成对象。
        System.out.println(f1);

        File f2 = new File(“e:\java_code\day22e”,“hello.java”);
        System.out.println(f2);

        //将parent封装成file对象。
        File dir = new File(“e:\java_code\day22e”);
        File f3 = new File(dir,“hello.java”);
        System.out.println(f3);
    }
}

1.3 File类的获取方法
创建完了File对象之后,那么File类中都有如下常用方法,可以获取文件相关信息


 方法演示如下:


public class FileMethodDemo {
    public static void main(String[] args{
        //创建文件对象
        File file = new File(“Test22.java”);
        //获取文件的绝对路径,即全路径
        String absPath = file.getAbsolutePath();
        //File中封装的路径是什么获取到的就是什么。
        String path = file.getPath();
        //获取文件名称
        String filename = file.getName();
        //获取文件大小
        long size = file.length();

        System.out.pri ntln(“absPath=”+absPath);
        System.out.println(“path=”+path);
        System.out.println(“filename=”+filename);
        System.out.println(“size=”+size);
    }
}

1.4 文件和文件夹的创建删除等
经常上面介绍,我们知道可以通过File获取到文件名称,文件路径(目录)等信息。
接下来演示使用File类创建、删除文件等操作。


 我们进行方法的演示


public class FileMethodDemo2 {
    public static void main(String[] args) throws IOException {
        // 对文件或者文件加进行操作。
        File file = new File(“e:\file.txt”);
        // 创建文件,如果文件不存在,创建 true 如果文件存在,则不创建 false。 如果路径错误,IOException。
        boolean b1 = file.createNewFile();
        System.out.println(“b1=” + b1);
        //———–删除文件操作——-注意:不去回收站。慎用——
         boolean b2 = file.delete();
         System.out.println(“b2=”+b2);

        //———–需要判断文件是否存在————
         boolean b3 = file.exists();
         System.out.println(“b3=”+b3);

        //———- ———-
        File dir = new File(“e:\abc”);
        //mkdir()创建单个目录。//dir.mkdirs();创建多级目录
        boolean b4 = dir.mkdir();
        System.out.println(“b4=”+b4);
        //删除目录时,如果目录中有内容,无法直接删除。
        boolean b5 = dir.delete();
        //只有将目录中的内容都删除后,保证该目录为空。这时这个目录才可以删除。
        System.out.println(“b5=” + b5);

        //———–判断文件,目录————
        File f = new File(“e:\javahaha”);// 要判断是否是文件还是目录,必须先判断存在。
        // f.mkdir();//f.createNewFile();
        System.out.println(f.isFile());
        System.out.println(f.isDirectory());
    }
}

1.5 listFiles()和list()方法介绍
文件都存放在目录(文件夹)中,那么如何获取一个目录中的所有文件或者目录中的文件夹呢?那么我们先想想,一个目录中可能有多个文件或者文件夹,那么如果File中有功能获取到一个目录中的所有文件和文件夹,那么功能得到的结果要么是数组,要么是集合。我们开始查阅API。


 方法演示如下:


public class FileMethodDemo3 {
    public static void main(String[] args{
        File dir = new File(“e:\java_code”);
        //获取的是目录下的当前的文件以及文件夹的名称。
        String[] names = dir.list();
        for(String name : names){
            System.out.println(name);
        }
        //获取目录下当前文件以及文件对象,只要拿到了文件对象,那么就可以获取其中想要的信息
        File[] files = dir.listFiles();
        for(File file : files){
            System.out.println(file);
        }
    }
}

注意:在获取指定目录下的文件或者文件夹时必须满足下面两个条件
1,指定的目录必须是存在的,
2,指定的必须是目录。
否则容易返回数组为null,再使用该null数组时会出现NullPointerException


第2章 递归
2.1 递归的概述
递归,指在当前方法内调用自己的这种现象
public void method(){
System.out.println(“递归的演示”);
//在当前方法内调用自己
method();
}
递归分为两种,直接递归和间接递归。
直接递归称为方法自身调用自己。间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
注意:1.递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
2.在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
构造方法禁止递归


 递归的代码演示,计算1-n之间的和,使用递归完成


public class DiGuiDemo {
    public static void main(String[] args{
        //计算1~num的和,使用递归完成
        int n = 5;
        int sum = getSum(n);
        System.out.println(sum);

    }
    public static int getSum(int n{
        if(n == 1){
            return 1;
        }
        return n + getSum(n-1);
    }
}

 代码执行流程图解


2.2 递归打印所有子目录中的文件路径
编写一个方法用来打印指定目录中的文件路径,并进行方法的调用
要求:若指定的目录有子目录,那么把子目录中的文件路径也打印出来
步骤:
1. 指定要打印的目录File对象
2. 调用getFileAll()方法
2.1 获取指定目录中的所有File对象
2.2 遍历得到每一个File对象
2.3 判断当前File 对象是否是目录
判断结果为true,说明为目录,通过递归,再次调用步骤2的getFileAll()方法
判断结果为false,说明是文件,打印文件的路径
 代码演示


public class FileDemo2 {
    public static void main(String[] args{
        File file = new File(“d:\test”);
        getFileAll(file);
    }
    //获取指定目录以及子目录中的所有的文件
    public static void getFileAll(File file{
        File[] files = file.listFiles();
        //遍历当前目录下的所有文件和文件夹
        for (File f : files) {
            //判断当前遍历到的是否为目录
            if(f.isDirectory()){
                //是目录,继续获取这个目录下的所有文件和文件夹
                getFileAll(f);
            }else{
                //不是目录,说明当前f就是文件,那么就打印出来
                System.out.println(f);
            }
        }
    }
}

集合【Map、可变参数、Collections】

第9天 集合
今日学习内容
 Map集合
今日学习目标
 能够说出Map集合特点
 使用Map集合添加方法保存数据
 使用”键找值”的方式遍历Map集合
 使用”键值对”的方式遍历Map集合
 能够使用HashMap存储自定义键值对的数据
 能够说出可变参数的作用
 能够使用集合工具类
 能够使用HashMap编写斗地主洗牌发牌案例
第1章 Map接口概述
1.1 Map集合的特点
我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同,如下图。
 Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
 Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
 Collection中的集合称为单列集合,Map中的集合称为双列集合。
 需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
 Map中常用的集合为HashMap集合、LinkedHashMap集合。


1.2 Map接口中常用集合概述
通过查看Map接口描述,看到Map有多个子类,这里我们主要讲解常用的HashMap集合、LinkedHashMap集合。
 HashMap<k,v style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
 LinkedHashMap<k,v style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
 注意:Map接口中的集合都有两个泛型变量<k,v style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量<k,v style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>的数据类型可以相同,也可以不同。
1.3 Map接口中的常用方法
</k,v></k,v></k,v></k,v>


 put方法:将指定的键与值对应起来,并添加到集合中
 方法返回值为键所对应的值
使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;
使用put方法时,若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。
 get方法:获取指定键(key)所对应的值(value)
 remove方法:根据指定的键(key)删除元素,返回被删除元素的值(value)。


Map接口的方法演示
public class MapDemo {
public static void main(String[] args) {
//创建Map对象
Map<string, string=”” style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> map = new HashMap<string,string style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>();
//给map中添加元素
map.put(“星期一”, “Monday”);
map.put(“星期日”, “Sunday”);
System.out.println(map); // {星期日=Sunday, 星期一=Monday}</string,string></string,>


    //当给Map中添加元素,会返回key对应的原来的value值,若key没有对应的值,返回null
    System.out.println(map.put(“星期一”“Mon”)); // Monday
    System.out.println(map); // {星期日=Sunday, 星期一=Mon}

    //根据指定的key获取对应的value
    String en = map.get(“星期日”);
    System.out.println(en); // Sunday

    //根据key删除元素,会返回key对应的value值
    String value = map.remove(“星期日”);
    System.out.println(value); // Sunday
    System.out.println(map); // {星期一=Mon}
}

}


1.4 Map集合遍历键找值方式
键找值方式:即通过元素中的键,获取键所对应的值
操作步骤与图解:
1.获取Map集合中所有的键,由于键是唯一的,所以返回一个Set集合存储所有的键


2.遍历键的Set集合,得到每一个键
3.根据键,获取键所对应的值

代码演示:
public class MapDemo {
public static void main(String[] args) {
//创建Map对象
Map<string, string=”” style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> map = new HashMap<string,string style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>();
//给map中添加元素
map.put(“邓超”, “孙俪”);
map.put(“李晨”, “范冰冰”);
map.put(“刘德华”, “柳岩”);
//获取Map中的所有key
Set keySet = map.keySet();
//遍历存放所有key的Set集合
Iterator it =keySet.iterator();
while(it.hasNext()){
//得到每一个key
String key = it.next();
//通过key获取对应的value
String value = map.get(key);
System.out.println(key+”=”+value);
}
}
}
1.5 Entry键值对对象(补充一下内部类和内部接口)
在Map类设计时,提供了一个嵌套接口:Entry。Entry将键值对的对应关系封装成了对象。即键值对对象,这样我们在遍历Map集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。
</string,string></string,>


 Entry是Map接口中提供的一个静态内部嵌套接口。



 getKey()方法:获取Entry对象中的键
 getValue()方法:获取Entry对象中的值

 entrySet()方法:用于返回Map集合中所有的键值对(Entry)对象,以Set集合形式返回。
1.6 Map集合遍历键值对方式
键值对方式:即通过集合中每个键值对(Entry)对象,获取键值对(Entry)对象中的键与值。
操作步骤与图解:
1.获取Map集合中,所有的键值对(Entry)对象,以Set集合形式返回。

2.遍历包含键值对(Entry)对象的Set集合,得到每一个键值对(Entry)对象
3.通过键值对(Entry)对象,获取Entry对象中的键与值。


public class MapDemo {
public static void main(String[] args) {
//创建Map对象
Map<string, string=”” style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> map = new HashMap<string,string style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>();
//给map中添加元素
map.put(“邓超”, “孙俪”);
map.put(“李晨”, “范冰冰”);
map.put(“刘德华”, “柳岩”);
//获取Map中的所有key与value的对应关系
Set<map.entry<string,string style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>> entrySet = map.entrySet();
//遍历Set集合
Iterator<map.entry<string,string style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>> it =entrySet.iterator();
while(it.hasNext()){
//得到每一对对应关系
Map.Entry<string,string style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> entry = it.next();
//通过每一对对应关系获取对应的key
String key = entry.getKey();
//通过每一对对应关系获取对应的value
String value = entry.getValue();
System.out.println(key+”=”+value);
}
}
}
注意:Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。
1.7 HashMap存储自定义类型键值
练习1:使用map存储:键为学号,值为一个学生的对象, 学生对象有属性(姓名,年龄)
练习2:使用map存储:键为学生(姓名,年龄)值为学生自己的家庭住址。那么,既然有对应关系,则将学生对象和家庭住址存储到map集合中。学生作 为键, 家庭住址作为值。
注意,学生姓名相同并且年龄相同视为同一名学生。
 学生类
public class Student {
private String name;
private int age;</string,string></map.entry<string,string></map.entry<string,string></string,string></string,>


//编写构造方法,文档中已省略
//编写get,set方法,文档中已省略
//编写toString方法,文档中已省略

}
 测试类
public class HashMapTest {
public static void main(String[] args) {
//1,创建hashmap集合对象。
Map<student,string style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> map = new HashMap<student,string style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>();</student,string></student,string>


    //2,添加元素。
    map.put(new Student(“lisi”,28), “上海”);
    map.put(new Student(“wangwu”,22), “北京”);
    map.put(new Student(“zhaoliu”,24), “成都”);
    map.put(new Student(“zhouqi”,25), “广州”);
    map.put(new Student(“wangwu”,22), “南京”);

    //3,取出元素。键找值方式
    Set<Student> keySet = map.keySet();
    for(Student key : keySet){
        String value = map.get(key);
        System.out.println(key.toString()+“…..”+value);
    }

    //取出元素。键值对方式
    Set<Map.Entry<Student, String>> entrySet = map.entrySet();
    for (Map.Entry<Student, String> entry : entrySet) {
        Student key = entry.getKey();
        String value = entry.getValue();
        System.out.println(key.toString()+“…..”+value);
    }
}

}
 当给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的hashCode和equals方法(如果忘记,请回顾HashSet存放自定义对象)。
 如果要保证map中存放的key和取出的顺序一致,可以使用LinkedHashMap集合来存放。
1.8 LinkedHashMap
我们知道HashMap保证成对元素唯一,并且查询速度很快,可是成对元素存放进去是没有顺序的,那么我们要保证有序,还要速度快怎么办呢?
在HashMap下面有一个子类LinkedHashMap,它是链表和哈希表组合的一个数据存储结构。


public class LinkedHashMapDmeo {
public static void main(String[] args) {


  LinkedHashMap<StringString> map = new LinkedHashMap<String,String>();

  map.put(“邓超”“孙俪”);
  map.put(“李晨”“范冰冰”);
  map.put(“刘德华”“柳岩”);

  Set<Entry<String,String>> entrySet = map.entrySet();

  for (Entry<StringString> entry : entrySet) {
    System.out.println(entry.getKey()+“  ”+entry.getValue());
   }

}
}
结果:
邓超 孙俪
李晨 范冰冰
刘德华 柳岩
1.9 Properties类介绍
Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
特点:
1、Hashtable的子类,map集合中的方法都可以用。
2、该集合没有泛型。键值都是字符串。
3、它是一个可以持久化的属性集。键值可以存储到集合中,也可以存储到持久化的设备(硬盘、U盘、光盘)上。键值的来源也可以是持久化的设备。
4、有和流技术相结合的方法。


 load(InputStream) 把指定流所对应的文件中的数据,读取出来,保存到Propertie集合中
 load(Reader)
 store(OutputStream,commonts)把集合中的数据,保存到指定的流所对应的文件中,参数commonts代表对描述信息
 Store(Writer,comments);
5.成员方法:



  • public Object setProperty(String key, String value)调用 Hashtable 的方法 put。

  • public String getProperty(String key)用指定的键在此属性列表中搜索属性

  • public Set stringPropertyNames()返回此属性列表中的键集,


代码演示:
/




    • Properties集合,它是唯一一个能与IO流交互的集合


    • 需求:向Properties集合中添加元素,并遍历


    • 方法:

  • public Object setProperty(String key, String value)调用 Hashtable 的方法 put。


  • public Set stringPropertyNames()返回此属性列表中的键集,


  • public String getProperty(String key)用指定的键在此属性列表中搜索属性
    /
    public class PropertiesDemo01 {
    public static void main(String[] args) {
    //创建集合对象
    Properties prop = new Properties();
    //添加元素到集合
    //prop.put(key, value);
    prop.setProperty(“周迅”, “张学友”);
    prop.setProperty(“李小璐”, “贾乃亮”);
    prop.setProperty(“杨幂”, “刘恺威”);


    //System.out.println(prop);//测试的使用
    //遍历集合
    Set<String> keys = prop.stringPropertyNames();
    for (String key : keys) {
        //通过键 找值
        //prop.get(key)
        String value = prop.getProperty(key);
        System.out.println(key+“==” +value);
    }

    }
    }
    1.10 读取文件中的数据,并保存到集合
    需求:从属性集文件prop.properties 中取出数据,保存到集合中
    分析:
    1,创建集合
    2,创建流对象
    3,把流所对应文件中的数据 读取到集合中
    load(InputStream) 把指定流所对应的文件中的数据,读取出来,保存到Propertie集合中
    load(Reader)
    4,关闭流
    5,显示集合中的数据
    代码演示:
    public class PropertiesDemo03 {
    public static void main(String[] args) throws IOException {
    //1,创建集合
    Properties prop = new Properties();
    //2,创建流对象
    FileReader in = new FileReader(“prop.properties”);
    //3,把流所对应文件中的数据 读取到集合中
    prop.load(in);
    //4,关闭流
    in.close();
    //5,显示集合中的数据
    System.out.println(prop);


    }
    }
    1.11 可变参数
    在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化成如下格式:
    修饰符 返回值类型 方法名(参数类型… 形参名){ }
    其实这个书写完全等价与
    修饰符 返回值类型 方法名(参数类型[] 形参名){ }
    只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。



jdk1.5以后。出现了简化操作。… 用在参数上,称之为可变参数。
同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。
代码演示:
public class ParamDemo {
public static void main(String[] args) {
int[] arr = {21,89,32};
int sum = add(arr);
System.out.println(sum);
sum = add(21,89,32);//可变参数调用形式
System.out.println(sum);


}

//JDK1.5之后写法
public static int add(int…arr){
    int sum = 0;
    for (int i = 0; i < arr.length; i++) {
        sum += arr[i];
    }
    return sum;
}

//原始写法
/
public static int add(int[] arr) {
    int sum = 0;
    for (int i = 0; i < arr.length; i++) {
        sum += arr[i];
    }
    return sum;
}
/


}
 上述add方法在同一个类中,只能存在一个。因为会发生调用的不确定性
注意:
1,一个方法中只能有一个可变参数.
2这个方法拥有多参数,参数中包含可变参数,可变参数一定要写在参数列表的末尾位置。
1.12 Collections集合工具类(可补充静态导入和Map嵌套)
Collections是集合工具类,用来对集合进行操作。部分方法如下:


 public static void sort(List list) // 集合元素排序
//排序前元素list集合元素 [33,11,77,55]
Collections.sort( list );
//排序后元素list集合元素 [11,33,55,77]


 public static void shuffle(List list) // 集合元素存储位置打乱
//list集合元素 [11,33,55,77]
Collections.shuffle( list );
//使用shuffle方法后,集合中的元素为[77,33,11,55],每次执行该方法,集合中存储的元素位置都会随机打乱
第2章 模拟斗地主洗牌发牌
2.1 案例介绍
按照斗地主的规则,完成洗牌发牌的动作。


具体规则:
1. 组装54张扑克牌



  1. 将54张牌顺序打乱

    1. 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。

    2. 查看三人各自手中的牌(按照牌的大小排序)、底牌
       手中扑克牌从大到小的摆放顺序:大王,小王,2,A,K,Q,J,10,9,8,7,6,5,4,3


2.2 案例需求分析
 准备牌:
完成数字与纸牌的映射关系:
使用双列Map(HashMap)集合,完成一个数字与字符串纸牌的对应关系(相当于一个字典)。
 洗牌:
通过数字完成洗牌发牌
 发牌:
将每个人以及底牌设计为ArrayList,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。
存放的过程中要求数字大小与斗地主规则的大小对应。
将代表不同纸牌的数字分配给不同的玩家与底牌。
 看牌:
通过Map集合找到对应字符展示。
通过查询纸牌与数字的对应关系,由数字转成纸牌字符串再进行展示。


2.3 实现代码步骤

首先,要修改java文件编码,由GBK修改为UTF-8,因为默认的字符编码GBK没有我们要的梅花、方片、黑桃、红桃(♠♥♦♣)等特殊字符。


import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

/
 
 斗地主洗牌发牌排序
 */

public class Poker {

    public static void main(String[] args{

        //准备花色
        ArrayList<String> color = new ArrayList<String>();
        color.add(“♠”);
        color.add(“♥”);
        color.add(“♦”);
        color.add(“♣”);

        //准备数字
        ArrayList<String> number = new ArrayList<String>();
Collections.addAll(number,“3”,“4”,“5”,“6”,“7”,“8”,“9”,“10”,“J”,“Q”,“K”,“A”,“2”);

        //定义一个map集合:用来将数字与每一张牌进行对应
        HashMap<Integer, String> map = new HashMap<Integer, String>();

        int index = 0;
        //加入大小王
        map.put(index++, “小☺”);
        map.put(index++, “大☻”);

        for (String thisNumber : number) {
            for (String thisColor : color) {
                map.put(index++, thisColor+thisNumber);
            }
        }

        //一副54张的牌 ArrayList里边为0-53的数的新牌
        ArrayList<Integer> cards = new ArrayList<Integer>();

        for (int i = 0; i <= 53; i++) {
            cards.add(i);
        }

        //洗牌
        Collections.shuffle(cards);

        //创建三个玩家和底牌
        ArrayList<Integer> iPlayer = new ArrayList<Integer>();
        ArrayList<Integer> iPlayer2 = new ArrayList<Integer>();
        ArrayList<Integer> iPlayer3 = new ArrayList<Integer>();
        ArrayList<Integer> itCards = new ArrayList<Integer>();

        //遍历这副洗好的牌,遍历过程中,将牌发到三个玩家和底牌中
        for (int i = 0; i < cards.size(); i++) {
            if(i>=51) {
                iCards.add(cards.get(i));
            } else {
                if(i%3==0) {
                    iPlayer.add(cards.get(i));
                }else if(i%3==1) {
                    iPlayer2.add(cards.get(i));
                }else {
                    iPlayer3.add(cards.get(i));
                }
            }
        }

        //对每个人手中的牌排序
        Collections.sort(iPlayer);
        Collections.sort(iPlayer2);
        Collections.sort(iPlayer3);

        //对应数字形式的每个人手中的牌,定义字符串形式的牌
        ArrayList<String> sPlayer = new ArrayList<String>();
        ArrayList<String> sPlayer2 = new ArrayList<String>();
        ArrayList<String> sPlayer3 = new ArrayList<String>();
        ArrayList<String> sCards = new ArrayList<String>();

        for (Integer key : iPlayer) {
            sPlayer.add(map.get(key));
        }
        for (Integer key : iPlayer2) {
            sPlayer2.add(map.get(key));
        }
        for (Integer key : iPlayer3) {
            sPlayer3.add(map.get(key));
        }
        for (Integer key : iCards) {
            sCards.add(map.get(key));
        }

        //看牌
        System.out.println(sPlayer);
        System.out.println(sPlayer2);
        System.out.println(sPlayer3);
        System.out.println(sCards);
    }
}

集合【LinkedList、HashSet、Collection集合体系】

新的起点是成功的开端,目标是行动的航标。一个人生活中没有任何目标,就如大海中漂泊的孤舟,不是稳步前进,而是随波逐流。

8 集合

今日学习内容

u List集合

u Set集合

今日学习目标

u 能够说出List集合特点:有序,索引和重复

u 使用List存储的数据结构:ArrayList是数组结构 LinkedList是链表结构

u 能够说出List常见的三个的特点

u 能够说出Set集合的特点:无索引,不重复

u 说出哈希表的特点

u 使用HashSet集合存储自定义元素

u 说出判断集合元素唯一的原理

第1章     数据结构

1.1      List集合存储数据的结构

List接口下有很多个集合,它们存储元素所采用的结构方式是不同的,这样就导致了这些集合有它们各自的特点,供给我们在不同的环境下进行使用。数据存储的常用结构有:堆栈、队列、数组、链表。我们分别来了解一下:

l  堆栈,采用该结构的集合,对元素的存取有如下的特点:

n  先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。

n  栈的入口、出口的都是栈的顶端位置

n  压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。

n  弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。

1.PNG


l  队列,采用该结构的集合,对元素的存取有如下的特点:

n  先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,安检。排成一列,每个人依次检查,只有前面的人全部检查完毕后,才能排到当前的人进行检查。

n  队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。

2.PNG


l  数组,采用该结构的集合,对元素的存取有如下的特点:

n  查找元素快:通过索引,可以快速访问指定位置的元素

n  增删元素慢:

u  指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图

u  指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。如下图

3.PNG


l  链表,采用该结构的集合,对元素的存取有如下的特点:

n  多个节点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。

n  查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素

n  增删元素快:

u  增加元素:操作如左图,只需要修改连接下个元素的地址即可。

u  删除元素:操作如右图,只需要修改连接下个元素的地址即可。

         4.PNG

        

1.2      LinkedList集合(有空可以先看一下List这个接口和方法,以及List实现类的各种数据结构特点)

LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。如下图

5.PNG


LinkedListList的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。

方法演示:

        LinkedList<String> link = new LinkedList<String>();

        //添加元素

        link.addFirst(“abc1”);

        link.addFirst(“abc2”);

        link.addFirst(“abc3”);

        //获取元素

        System.out.println(link.getFirst());

        System.out.println(link.getLast());

        //删除元素

        System.out.println(link.removeFirst());

        System.out.println(link.removeLast());

       

        while(!link.isEmpty()){ //判断集合是否为空

            System.out.println(link.pop()); //弹出集合中的栈顶元素

       }

第2章     Set接口(特点以及方法?)

查阅Set集合的API介绍,通过元素的equals方法,来判断是否为重复元素,它是个不包含重复元素的集合。Set集合取出元素的方式可以采用:迭代器、增强for

Set集合有多个子类,这里我们介绍其中的HashSetLinkedHashSet这两个集合。

2.1      HashSet集合介绍

查阅HashSet集合的API介绍:此类实现Set接口,由哈希表支持(实际上是一个 HashMap集合)。HashSet集合不能保证的迭代顺序与元素存储顺序相同。

HashSet集合,采用哈希表结构存储数据,保证元素唯一性的方式依赖于:hashCode()equals()方法。

2.2      HashSet集合存储数据的结构(哈希表)

什么是哈希 呢?

哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会根据这些对象的特有数据结合相应的算法,计算出这个对象在数组中的位置,然后把这个对象存放在数组中。而这样的数组就称为哈希数组,即就是哈希表。

当向哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object类中的hashCode方法。由于任何对象都是Object类的子类,所以任何对象有拥有这个方法。即就是在给哈希表中存放对象时,会调用对象的hashCode方法,算出对象在表中的存放位置,这里需要注意,如果两个对象hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中。

注意:String类重写了hashcode方法,按照自己的方式计算,”abc””acD”哈希值相同

总结:保证HashSet集合元素的唯一,其实就是根据对象的hashCodeequals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCodeequals方法建立属于当前对象的比较方式。

6.PNG

2.3      HashSet存储JavaAPI中的类型元素

HashSet中存储JavaAPI中提供的类型元素时,不需要重写元素的hashCodeequals方法,因为这两个方法,在JavaAPI的每个类中已经重写完毕,如String类、Integer类等。

l  创建HashSet集合,存储String对象。

publicclass HashSetDemo {

    publicstaticvoid main(String[] args) {

        //创建HashSet对象

        HashSet<String> hs = new HashSet<String>();

        //给集合中添加自定义对象

        hs.add(“zhangsan”);

        hs.add(“lisi”);

        hs.add(“wangwu”);

        hs.add(“zhangsan”);

        //取出集合中的每个元素

        Iterator<String> it = hs.iterator();

        while(it.hasNext()){

            String s = it.next();

            System.out.println(s);

        }

    }

}

输出结果如下,说明集合中不能存储重复元素:

wangwu

lisi

zhangsan

2.4      HashSet存储自定义类型元素

HashSet中存放自定义类型元素时,需要重写对象中的hashCodeequals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一

l  创建自定义对象Student

publicclass Student {

    private String name;

    privateintage;

    public Student(String name, int age) {

        super();

        this.name = name;

        this.age = age;

    }

    public String getName() {

        returnname;

    }

    publicvoid setName(String name) {

        this.name = name;

    }

    publicint getAge() {

        returnage;

    }

    publicvoid setAge(int age) {

        this.age = age;

    }

    @Override

    public String toString() {

        return“Student [name=” + name + “, age=” + age + “]”;

    }

    @Override

    publicint hashCode() {

        finalint prime = 31;

        int result = 1;

        result = prime result + age;

        result = prime result + ((name == null) ? 0 : name.hashCode());

        return result;

    }

    @Override

    publicboolean equals(Object obj) {

        if (this == obj)

            returntrue;

        if(!(obj instanceof Student)){

            System.out.println(类型错误);

            returnfalse;

        }

        Student other = (Student) obj;

        returnthis.age ==  other.age&&this.name.equals(other.name);

    }

}


l  创建HashSet集合,存储Student对象。

publicclass HashSetDemo {

    publicstaticvoid main(String[] args) {

        //创建HashSet对象

        HashSet hs = new HashSet();

        //给集合中添加自定义对象

        hs.add(new Student(“zhangsan”,21));

        hs.add(new Student(“lisi”,22));

        hs.add(new Student(“wangwu”,23));

        hs.add(new Student(“zhangsan”,21));

        //取出集合中的每个元素

        Iterator it = hs.iterator();

        while(it.hasNext()){

            Student s = (Student)it.next();

            System.out.println(s);

        }

    }

}

输出结果如下,说明集合中不能存储重复元素:

Student [name=lisi, age=22]

Student [name=zhangsan, age=21]

Student [name=wangwu, age=23]

第3章     判断集合元素唯一的原理

3.1      ArrayListcontains方法判断元素是否重复原理

7.PNG

ArrayListcontains方法调用时,会使用传入的元素的equals方法依次与集合中的旧元素所比较,从而根据返回的布尔值判断是否有重复元素。此时,当ArrayList存放自定义类型时,由于自定义类型在未重写equals方法前,判断是否重复的依据是地址值,所以如果想根据内容判断是否为重复元素,需要重写元素的equals方法。

3.2      HashSetadd/contains等方法判断元素是否重复原理

8.PNG

Set集合不能存放重复元素,其添加方法在添加时会判断是否有重复元素,有重复不添加,没重复则添加。

HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCodeequals方法的返回结果。规则如下:

先判断新元素与集合内已经有的旧元素的HashCode

l  如果不同,说明是不同元素,添加到集合。

l  如果相同,再判断equals比较结果。返回true则相同元素;返回false则不同元素,添加到集合。

所以,使用HashSet存储自定义类型,如果没有重写该类的hashCodeequals方法,则判断重复时,使用的是地址值,如果想通过内容比较元素是否相同,需要重写该元素类的hashcodeequals方法。

第4章     Collection总结

4.1      知识点总结

l  Collection:

    |- List 可以存储重复元素,有序的(元素存取顺序),有索引

            |- ArrayList

                |- LinkedList

        |- Set 不能存储重复元素,无序的(元素存取顺序,LinkedHashSet是有序的),无索引

        |- HashSet

                    |- LinkedHashSet

l  Collection方法:

n  boolean add(Object e) 把给定的对象添加到当前集合中

n  void clear() 清空集合中所有的元素

n  boolean remove(Object o) 把给定的对象在当前集合中删除

n  boolean contains(Object o) 判断当前集合中是否包含给定的对象

n  boolean isEmpty() 判断当前集合是否为空

n  Iterator iterator() 迭代器,用来遍历集合中的元素的

n  int size() 返回集合中元素的个数

n  Object[] toArray() 把集合中的元素,存储到数组中

l  Iterator :  迭代器

n  Object next()返回迭代的下一个元素

n  boolean hasNext()如果仍有元素可以迭代,则返回 true。

l  List与Set集合的区别?

List:

                                     它是一个有序的集合(元素存与取的顺序相同)

                                     它可以存储重复的元素                           

                            Set:

                                     它是一个无序的集合(元素存与取的顺序可能不同)

                            它不能存储重复的元素

l  List集合中的特有方法(下标)

n  void add(int index, Object element) 将指定的元素,添加到该集合中的指定位置上

n  Object get(int index)返回集合中指定位置的元素。

n  Object remove(int index) 移除列表中指定位置的元素, 返回的是被移除的元素

n  Object set(int index, Object element)用指定元素替换集合中指定位置的元素,返回值的更新前的元素

l  ArrayList:

         底层数据结构是数组,查询快,增删慢

         线程不安全,效率高

l  LinkedList:

         底层数据结构是链表,查询慢,增删快

         线程不安全,效率高

l  泛型: 用来约束数据的数据类型

n  泛型的格式:

                   <数据类型>

                   泛型可以使用在 类,接口,方法上

n  泛型的好处

                   A:提高了程序的安全性

                   B:将运行期遇到的问题转移到了编译期

                   C:省去了类型强转的麻烦

l  增强for:在循环的过程中不能修改集合的长度

         简化数组和Collection集合的遍历

                   格式:

                   for(元素数据类型 变量 : 数组或者Collection集合) {

                            使用变量即可,该变量就是元素

                   }

     好处:简化遍历(语法糖)

l  HashSet:

         元素唯一不能重复

         底层结构是 哈希表结构

         元素的存与取的顺序不能保证一致

l  LinkedHashSet:

         元素唯一不能重复

         底层结构是 链表结构+哈希表结构

+      元素的存与取的顺序一致

         如何保证元素的唯一的?

                   重写hashCode() equals()方法