本篇文章的学习资源来自Java学习视频教程:Java核心技术(进阶)_华东师范大学_中国大学MOOC(慕课)
本篇文章的学习笔记即是对Java核心技术课程的总结,也是对自己学习的总结
文章目录
Java核心技术(进阶)
Java核心技术(进阶)内容:应用
- 庞大的第三方库
- 和其他系统/编程语言交互
- 文件、数据库、网络、C、JavaScript、Python
Java核心技术进阶主要内容:
- Java构建工具:Maven
- Java单元测试:Junit
- 高级文件处理
- 多线程和并发
- 网络和邮件
- 数据库
- Java和其他语言交互
- Java和java、C、JS、Python等
第一章:Maven
当前主要的Java构建工具
- Maven,Gradle,Ivy,Builder,Ant等
Maven基本概念:
- apache组织提供的一个顶级项目,由Java开发
- 作用:
- 管理项目构建生命周期
- 管理项目中jar(核心)。自动帮程序员甄别和下载第三方库(jar)
- 管理项目基础信息(文档管理、测试报告)
1、项目构建生命周期:
[清理]–>[编译]–>[测试]–>[报告]–>[打包]–>[安装]–>[发布]
[clean]–>[compile]–>[test]–>[report]–>[package]–>[install]–>[publish]
2、项目中jar包管理
Maven工作原理:
[本地仓库]–>[局域网私服仓库]–>[中央仓库]
3、Maven项目信息管理
- api文档
- 测试报告
Maven安装与配置
安装 解压即可
Maven插件结构:
bin
boot 插件
conf 配置文件
lib
IDEA自带Maven位于D:\DevTools\ideaIU-2020.2.3.win\plugins\maven\lib\maven3目录下,
查看版本号:在maven3/bin目录下,调出cmd,输入mvn -v
所以Maven原本是不需要配置的。只是为了在任何路径下都可以使用Maven命令,所以需要配置
配置
- JAVA_HOME
- MAVEN-HOME:Maven安装地址
- [path] %MAVEN-HOME%\bin
Maven本地仓库配置
本地仓库默认位置:
C:\Users\Administrator\.m2\repository
重新设置Maven本地仓库地址
Maven安装路径\conf\setting.xml中定位标签
#53 /path/to/local/repository
重新设置本地仓库位置
D:\DevTools\ideaIU-2020.2.3.win\plugins\maven\repository
阿里云镜像设置
在mirror位置(大约160行左右)添加阿里云镜像
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
Maven工程结构
-
Maven可以管理的工程,必须按照【约定结构】来创建
-
结构
-
src:(Java代码)
-
main文件夹:主要开发 业务程序
-
java(创建Java文件)
Java文件创建package,如 src\main\java\com\bjpowernode\model\Student.java
-
resources(存放程序资源文件(配置文件,properties))
-
-
test:主要进行测试 单元测试程序
-
Java(创建测试类)
Java文件创建测试类,如 src\main\java\com\bjpowernode\model\TestMain.java
-
resources(存放测试程序资源文件(测试时使用的配置文件))
-
-
-
target文件夹:(编译后class文件,在创建项目时,不需要创建。Maven命令在执行时自动创建target)
目录如图:
-
pom.xml:(核心配置文件:主要用来向Maven讨要jar包)(Project Object Model)
-
eg:记事本开发Maven工程
Student.java
package com.bjpowernode.model;
public class Student{
public void sayHello(){
System.out.print("Hello Maven");
}
}
TestMain.java
package com.bjpowernode.test;
import com.bjpowernode.model.Student;
import org.junit.Test;
public class TestMain{
@Test
public void myTest (){
Student stu=n ew Student();
stu.sayHello();
}
}
pom.xml
包含了项目信息,依赖信息、构建信息
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> //固定不变的
//这三行是定位package的,简称gav
<groupId>com.bjpowernode</groupId> //域名
<artifactId>Maven_Project</artifactId> //项目名,不能有中文
<version>6.0</version> //版本号
//<package>war</package> //这一行通常为默认jar或war,需要时手动添加
//添加依赖
<dependencies>
//所需的依赖可以到百度Maven Repository,搜索相关包名,获取gav
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
常见Maven命令:(与项目构建生命周期相关)
1、mvn clean 删除当前工程中target
2、mvn compile 将当前工程中main文件下的所有java编译为class,输送到target文件中
3、mvn test 运行test文件下所有测试文件
4、mvn package 首先将test文件下所有的java测试类的方法调用执行进行测试,并生成[测试报告]。如果测试没有问题,将main文件下所有class文件打成(jar/war),然后输送到target
5、mvn install 与package命令执行基本一致,将[jar\war]推送到Maven的本地仓库
Maven坐标(gav)
为了方便Maven对jar的定位,在Maven世界中,每一个jar都要由一个独立坐标,相当于ip
artifact:构建
这个独立坐标由三部分组成
- 公司域名反顺序 组织
- 项目名称 产品名称
- 项目版本号 版本
Maven开发流程
- 新建Maven项目
- 在中央仓库查找第三方jar的依赖文本
- 拷贝依赖文本至项目的pom.xml
- 执行Maven,编译/构建整个项目
第二章:单元测试和JUnit
1、单元测试
软件测试分类
- 单元测试VS集成测试
- 白盒测试VS黑盒测试
- 自动测试VS手动测试
- 回归测试:修改旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误
- 压力测试
- 。。。
2、JUnit :Java单元测试框架
目前JUnit包含JUnit3,JUnit4,JUnit5,主流使用JUnit4
每一个测试方法的头部加@Test(注解),这样JUnit会自动执行这些测试方法
eg:Triangle
package com.ouc.test;
import static org.junit.Assert.*;//导入Assert类的所有静态方法,自JDK1.5引入
import com.ouc.model.Triangle;
import org.junit.Assert;
import org.junit.Test;
public class TestTriangle {
@Test
public void test(){
assertEquals(false,new Triangle().JudgeEdges(1,2,3));//静态assert方法
//Assert.assertEquals(false,new Triangle().JudgeEdges(1,2,3));//动态assert方法
}
}
JUnit单元测试与Maven测试的区别:
JUnit单元测试一次只能测试一个JUnit test,Maven将test文件夹下测试所有test类
第三章:高级文本处理
第一节:Java字符编码
编码方案
- UTF-8 兼容ASCII,变长(1-4个字节存储字符),经济,方便传输
- UTF-16 用变长(2-4个字节)来存储所有字符
- UTF-32 用32bits(4个字节)存储所有字符
ANSI编码
- Windows上非Unicode的默认编码
- 在简体中文Windows操作系统中,ANSI编码代表GBK编码
- 在繁体中文Windows操作系统中,ANSI编码代表Big5
- 记事本默认采用ANSI保存
- ANSI编码文件不能兼容使用
Java的字符编码
- 源文件编码:采用UTF-8编码
- 程序内部采用UTF-16编码存储所有字符(不是程序员控制)
- 和外界(文本文件)的输入输出尽量采用UTF-8编码
- 不能使用一种编码写入,换另一种编码读取
第二节:Java国际化编程
Java是第一个设计成支持国际化的编程语言
- java.util.ResourceBundle 用于加载一个语言_国家语言包
- java.util.Locale 定义一个语言_国家
Locale(zh_CN,en_US,…)
- 语言,zh,en等
- 国家/地区,CN,US等
- 其他变量(几乎不用)
Locale方法
- getAvailableLocales()返回所有的可用的Locale
- getDefault()返回默认的Locale
语言文件
- 一个Properties文件
- 包含K-V对,每行一个K-V,例如age=20
- 命名规则
- 包名+语言+国家地区.properties,(语言和国家地区可选)
- message.properties
- message_zh.properties
- message_zh_CN.properties
- 存储文件必须是ASCII文件,如果是ASCII以外的文字,必须使用Unicode表示\uxxxx,也可以采用native2ascii.exe(%JAVA_HOME\bin%目录下)进行转码
ResourceBundle类
- ResourceBundle
- 根据Locale要求,加载语言文件(Properties)
- 存储所有的K-V对
- getString(String key)返回所对应的value
- ResourceBundle根据key找value的查找路径
- 包名_当前Locale语言__当前Locale国家地区
- 包名_当前Locale语言
- 包名
eg:
创建txt文件,内容:hello=你好,世界
调用jdk下的native2ascii.exe转码,cmd命令行下输入
native2ascii.exe a.txt message_zh_CN.properties
将message_zh_CN.properties放在main\resources下,
public static void main(String[] args) {
//取得系统默认的国家/语言环境
Locale myLocale = Locale.getDefault();
//myLocale=new Locale("en","US");//强制转化en_US
System.out.println("myLocale = " + myLocale);//zh_CN //en_US
//根据指定语言_国家环境加载资源文件
ResourceBundle bundle = ResourceBundle.getBundle("message", myLocale);
System.out.println("bundle.getString(\"hello\") = " + bundle.getString("hello"));
}
第三节:Java高级字符串处理
正则表达式(REGEX)(Regular Express)
一、概念
作用
- **检索:**通过正则表达式,从字符串中获取我们想要的部分
- **匹配:**判断给定的字符串是否符合正则表达式的过滤逻辑
正则表达式就是由普通字符以及特殊字符(称为元字符)组成的文字模式。该模式描述在查找文字主体时待匹配的一个或多个字符串。正则表达式用来描述字符串的特征。
正则表达式是非常复杂的,理解正则表达式能做什么(字符串匹配、字符串提取、字符串替换),掌握常用的正则表达式用法。
3个重要的正则表达式命令
-
在正则表达式中有三种类型的括号,[]、{}、()。中括号[]内是需要匹配的字符,大括号{}内是指定匹配字符的数量,小括号()则是用来改变优先级或分组的
-
插入符号"^"表示正则式的开始
-
美元符号"$"表示正则式的结束
eg:Regex obj = new Regex("[a-z]{10}]");//搜索长度为10的a-z的英文字母
二、元字符
. 除\n以外的任意的单个字符 注:有且只有一个
[] 字符组:[]表示在字符组中罗列出来的字符,任意取单个。 eg:a[a-zA-Z王]b -表示范围,-在第一位时只表示一个字符,不表示元字符
以下是通配符:
| 表示“或”的意思 正则表达式尽量减少“|”的使用,“|”越多,越消耗性能
* 表示限定前面的表达式出现0次或者多次
+ 表示一次或多次,至少一次
? 表示出现0次或1次
{} 指定匹配字符的数量 eg:[0-9]{8} [a-z]{4,}
[0-9] \d \D取余
[a-zA-Z0-9_] \w \W取余
\s 表示所有不可见字符
注:要想完全匹配,必须要加^和$
Java的正则表达式
java.util.regex包
- Pattern 正则表达式的编译表示
- compile 编译一个正则表达式为Pattern对象
- matcher 用Pattern对象匹配一个字符串,返回匹配结果
- Matcher
- Index Methods(位置方法) //start(),start(int group),end(),end(int group)
- Search Methods(查找方法) //lookingAt(),find(),find(int start),matches()
- lookingAt()//部分匹配
- matches()//完全匹配
- Replacement Methods(替换方法) //replaceAll(String replacement)
eg
private static final String REGEX = "\\bdog\\b";//\b表示边界
private static final String INPUT = "dog dog dog doggie dogg";
public static void main(String[] args) {
//检查字符串里面有多少个dog
Pattern p = Pattern.compile(REGEX);
Matcher m = p.matcher(INPUT);
int count = 0;
while (m.find()) {
count++;
System.out.println("count = " + count);
System.out.println("m = " + m.start());
System.out.println("m.end() = " + m.end());
}
}
private static final String REGEX = "Foo";
private static final String INPUT = "Foooooooo";
private static Pattern pattern;
private static Matcher matcher;
public static void main(String[] args) {
//Initialize
pattern = Pattern.compile(REGEX);
matcher = pattern.matcher(INPUT);
System.out.println("REGEX = " + REGEX);
System.out.println("INPUT = " + INPUT);
System.out.println("matcher.lookingAt() = " + matcher.lookingAt());//部分匹配
System.out.println("matcher.matches() = " + matcher.matches());//完全匹配
}
private static String REGEX = "a*b";//*表示限定前面的a可以有0或多个
private static String INPUT = "aabfooaabfooabfoobcdd";
private static String REPLACE = "-";
public static void main(String[] args) {
Pattern pattern = Pattern.compile(REGEX);
Matcher matcher = pattern.matcher(INPUT);
StringBuffer stringBuffer = new StringBuffer();
//全部替换
while (matcher.find()) {
matcher.appendReplacement(stringBuffer, REPLACE);
}
//将最后的尾巴字符串附加上
matcher.appendTail(stringBuffer);
System.out.println("stringBuffer.toString() = " + stringBuffer.toString());
}
private static String REGEX="dog";
private static String INPUT="The dog says meow.All dogs say meow.";
private static String REPLACE="cat";
public static void main(String[] args) {
Pattern pattern=Pattern.compile(REGEX);
Matcher matcher=pattern.matcher(INPUT);
INPUT=matcher.replaceAll(REPLACE);//将所有的dog换成cat
System.out.println("INPUT = " + INPUT);
}
其他字符串操作
- 集合和字符串互转
public static void main(String[] args) {
List<String> names = new LinkedList<>();
names.add("xiaohong");
names.add("xiaoming");
names.add("daming");
//从ArrayList变到字符串
String str1=String.join(",",names);
System.out.println("str1 = " + str1);
//JDK自带的String.join方法只能拼接字符串元素
//使用Apache Commons Lang的StringUtils.join方法可以拼接更多类型的元素
String str2= StringUtils.join(names,",");
System.out.println("str2 = " + str2);
//从字符串转到ArrayList
List<String> names2= Arrays.asList(str1.split(","));
for (String name :
names2) {
System.out.println("name = " + name);
}
//StringUtils可以支持更多数据类型
ArrayList<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(5);
ids.add(3);
String str3=StringUtils.join(ids,",");
System.out.println("str3 = " + str3);
}
第四章 高级文件处理
1、xml简介
XML:可扩展标记语言
纯文本表示,跨系统/平台/语言
区分大小写
1、文档声明
2、元素=标签
3、属性
4、注释
5、CDATA区、特殊字符<!CDATA[不想解析的内容]>
文档声明 文档中必须要有一个根标签
<?xml version="1.0" encoding="UTF-8"?>
元素=标签
注:xml必须要有根节点,且只能有一个节点
<student></student>
属性
<student name='xiaoming'></student>
注释
<!--注释-->
CDATA区、特殊字符
< <
> >
" "
' '
XML Schema(XSD,XML Schema Definition)
- 定义xml文档的结构,DTD(Document Type Definition)的继任者
- 支持数据类型,可扩展,功能更完善、强大
XSL
- 扩展样式表语言(eXtensible Stylesheet Language)
- XSL作用于XML,等同于CSS作用于HTML
2、xml解析
xml解析方法
- 树结构
- DOM:Document Object Model文档对象模型,擅长(小规模)读/写
- 流结构
- SAX:Simple API for XML 流机制解释器(推模式),擅长读
- Stax:The Streaming API for XML 流机制解释器(拉模式),擅长读
DOM
- DocumentBuilder解析类,parse方法
- Node节点主接口,getChildNodes返回一个NodeList
- NodeList节点列表,每个元素是一个Node
- 注意:标签和标签之间的空格也会被上级父节点视为子元素
- Document文档根节点
- Element标签节点元素(每一个标签都是标签节点)
- Text节点(包含在XML元素内的,都算Text节点)
- Attr节点(每个属性节点)
eg:
DOMReader:
//自上而下进行访问
public static void RecursiveTraverse() {
try {
//采用DOM解析XML文件
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db=dbf.newDocumentBuilder();
Document document= db.parse("users.xml");
//获取所有的一级子节点
NodeList usersList=document.getChildNodes();
System.out.println("usersList.getLength() = " + usersList.getLength()); //1
for (int i = 0; i < usersList.getLength(); i++) {
Node users=usersList.item(i); //1 users
NodeList userList=users.getChildNodes();//获取二级子节点user的列表
System.out.println("userList.getLength() = " + userList.getLength());//9
for (int j = 0; j < userList.getLength(); j++) {
Node user=userList.item(j);
if (user.getNodeType()==Node.ELEMENT_NODE){
NodeList metaList=user.getChildNodes();
System.out.println("metaList.getLength() = " + metaList.getLength());//7
for (int k = 0; k < metaList.getLength(); k++) {
//到最后一级文本
Node meta=metaList.item(k);
if (meta.getNodeType()==Node.ELEMENT_NODE){
System.out.println("metaList.item(k).getTextContent() = " + metaList.item(k).getTextContent());
}
}
}
}
}
System.out.println();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
}
//根据名字进行搜索
public static void TraverseBySearch() {
try {
//采用DOM解析XML文件
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse("users.xml");
Element rootElement = document.getDocumentElement();
NodeList nodeList = rootElement.getElementsByTagName("name");
if (nodeList != null) {
for (int i = 0; i < nodeList.getLength(); i++) {
Element element = (Element) nodeList.item(i);
System.out.println("element.getTextContent() = " + element.getTextContent());
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
DOMWriter
public static void main(String[] args) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
//新创建一个Document节点
Document document = builder.newDocument();
if (document != null) {
Element docx = document.createElement("document");
Element element = document.createElement("element");
element.setAttribute("type", "para");
element.setAttribute("alignment", "left");//element增加2个属性
Element object = document.createElement("object");
object.setAttribute("type", "text");
Element text = document.createElement("text");
text.appendChild(document.createTextNode("abcdefg"));//给text节点赋值
Element bold = document.createElement("bold");
bold.appendChild(document.createTextNode("true"));//给bold节点赋值
object.appendChild(text); //把text节点挂在object下
object.appendChild(bold); //把bold节点挂在object下
element.appendChild(object);
docx.appendChild(element);
document.appendChild(docx); //把docx挂在document下
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(document);
//定义目标文件
File file = new File("dom_result.xml");
StreamResult result = new StreamResult(file);
//将xml文件写入到文件中
transformer.transform(source,result);
System.out.println("Write xml file successfully");
}
} catch (ParserConfigurationException | TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerException e) {
e.printStackTrace();
}
}
SAX(Simple API for XML)
- 采用事件/流模型来解析XML文档,更快速、更轻量
- 有选择的解析和访问,不像DOM加载整个文档,内存要求更低
- SAX对XML文档的解析为一次性读取,不创建/不存储文档对象,很难同时访问文档中的多处数据
- 推模型。当它每发现一个节点就引发一个事件,而我们需要编写这些事件的处理程序。关键类:DefaultHandler
- SAX的5个回调方法
- startDocument 文档开始解析
- endDocument 文档结束解析
- startElement 开始访问元素
- endElement 结束访问元素
- characters 访问元素正文
public class SAXReader {
public static void main(String[] args) throws SAXException, IOException {
XMLReader parser = XMLReaderFactory.createXMLReader();
BoolHandler boolHandler = new BoolHandler();
parser.setContentHandler(boolHandler);
parser.parse("books.xml");
System.out.println("boolHandler = " + boolHandler.getNameList());
}
}
class BoolHandler extends DefaultHandler {
private List<String> nameList;
private boolean title = false;
public List<String> getNameList() {
return nameList;
}
//实现DefaultHandler的5个回调方法
//xml文档加载时
public void startDocument() throws SAXException {
System.out.println("Start parsing document...");
nameList = new ArrayList<String>();
}
//文档解析结束
public void endDocument() throws SAXException {
System.out.println("End");
}
//访问某一个元素
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if (qName.equals("title"))
title = true;
}
//结束访问元素
public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
//End of processing current element
if (title) {
title = false;
}
}
//访问元素正文
public void characters(char[] ch, int start, int length) {
if (title) {
String bookTitle = new String(ch, start, length);
System.out.println("bookTitle = " + bookTitle);
nameList.add(bookTitle);
}
}
}
Stax(Streaming API for XML)
- 流模型中的拉模型
- 在遍历文档时,会把感兴趣的部分从读取器中拉出,不需要引发事件,允许我们选择性地处理节点。这大大提高了灵活性,以及整体效率。
- 两套处理API
- 基于指针的API,XMLStreamReader
- 基于迭代器的API,XMLEventReader
eg:
//流模式
public static void readByStream() {
String xmlFile = "books.xml";
XMLInputFactory factory = XMLInputFactory.newFactory();
XMLStreamReader streamReader = null;
try {
streamReader = factory.createXMLStreamReader(new FileReader(xmlFile));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (XMLStreamException e) {
e.printStackTrace();
}
//基于指针遍历
try {
while (streamReader.hasNext()) {
int event = streamReader.next();
//如果是元素的开始
if (event == XMLStreamConstants.START_ELEMENT) {
//列出所有书籍名称
if ("title".equalsIgnoreCase(streamReader.getLocalName())) {
System.out.println("title: " + streamReader.getElementText());
}
}
}
streamReader.close();
} catch (XMLStreamException e) {
e.printStackTrace();
}
}
//事件模式
public static void readByEvent() {
String xmlFile = "books.xml";
XMLInputFactory factory = XMLInputFactory.newFactory();
boolean titleFlag = false;
try {
//创建基于迭代器的事件读取器对象
XMLEventReader eventReader = factory.createXMLEventReader(new FileReader(xmlFile));
//遍历Event迭代器
while (eventReader.hasNext()) {
XMLEvent event = eventReader.nextEvent();
//如果事件对象是元素的开始
if (event.isStartElement()) {
//转换成开始元素事件对象
StartElement start = event.asStartElement();
//打印元素标签的本地名称
String name = start.getName().getLocalPart();
if (name.equals("title")) {
titleFlag = true;
System.out.println("title:");
}
//取得所有属性
Iterator attrs = start.getAttributes();
while (attrs.hasNext()) {
//打印所有属性信息
Attribute attr = (Attribute) attrs.next();
//System.out.println("attr.getValue() = " + attr.getValue());
}
//如果是正文
if (event.isCharacters()) {
String s = event.asCharacters().getData();
if (null != s && s.trim().length() > 0 && titleFlag) {
System.out.println("s.trim() = " + s.trim());
}
}
//如果事件对象是元素的结束
if (event.isEndElement()) {
EndElement end = event.asEndElement();
String titleName = end.getName().getLocalPart();
if (titleName.equals("title")) {
titleFlag = false;
}
}
}
eventReader.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (XMLStreamException e) {
e.printStackTrace();
}
}
3、JSON简介及解析
JSON
-
JavaScript Object Notation,js对象表示法
-
一种轻量级的数据交换格式
-
JSONObject,名称-值对。如"firstName":“John”
- json对象:{“name”:“John”,“email”:“a@b.com”}
- 数据在键值对中
- 数据由逗号分隔
- 花括号保存对象
-
JSONArray,json数组
- 方括号保存数组
- [{K:V,K:V,K:V,…},{}]
- 方括号保存数组
java的JSON处理
- org.json:JSON官方推荐的解析类
- 简单易用,通用性强,但无法完成复杂功能
- GSON:Google出品。基于反射,可以实现JSON对象、JSON字符串和Java对象互转
- Jackson:号称最快的JSON处理器
JSON主要用途
- JSON生成
- JSON解析
- JSON校验
- 和Java Bean对象进行互解析
- 具有一个无参的构造函数
- 可以包括多个属性,所有属性都是private
- 每个属性都有相应的Getter/Settrt方法
- Java Bean用于封装数据,又可称为POJO(Plain Old Java Object)
eg:
Person.java Java Bean类
public class Person {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<Integer> getScores() {
return scores;
}
public void setScores(List<Integer> scores) {
this.scores = scores;
}
private String name;
private int age;
private List<Integer> scores;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
books.json
{
"books": [
{
"category": "COOKING",
"title": "Everyday Italian",
"author": "Giada De Laurentiis",
"year": "2005",
"price": 30.00
},
{
"category": "CHILDREN",
"title": "Harry Potter",
"author": "J K. Rowling",
"year": "2005",
"price": 29.99
},
{
"category": "WEB",
"title": "Learning XML",
"author": "Erik T. Ray",
"year": "2003",
"price": 39.95
}
]
}
org.json:
//写JSON文件
public static void testJsonObject() {
//构造对象
Person p = new Person();
p.setName("Tom");
p.setAge(20);
p.setScores(Arrays.asList(60,70,80));
//构造JSONObject对象
JSONObject obj = new JSONObject();
//string
obj.put("name", p.getName());
//int
obj.put("age", p.getAge());
//array
obj.put("scores", p.getScores());
//null
//object.put("null", null);
System.out.println(obj);
System.out.println("name: " + obj.getString("name"));
System.out.println("age: " + obj.getInt("age"));
System.out.println("scores: " + obj.getJSONArray("scores"));
}
//读JSON文件
public static void testJsonFile() {
File file = new File("books.json");
try (FileReader reader = new FileReader(file)) {
//读取文件内容到JsonObject对象中
int fileLen = (int) file.length();
char[] chars = new char[fileLen];
reader.read(chars);
String s = String.valueOf(chars);
JSONObject jsonObject = new JSONObject(s);
//开始解析JSONObject对象
JSONArray books = jsonObject.getJSONArray("books");
List<ArrayList> bookList = new ArrayList<>();
for (Object book : books) {
//获取单个JSONObject对象
JSONObject bookObject = (JSONObject) book;
ArrayList book1=new ArrayList();
book1.add(bookObject.getString("author"));
book1.add(bookObject.getString("year"));
book1.add(bookObject.getString("title"));
book1.add(bookObject.getInt("price"));
book1.add(bookObject.getString("category"));
bookList.add(book1);
}
for (ArrayList book : bookList) {
System.out.println(book.get(0)+","+book.get(2));
}
} catch (Exception e) {
e.printStackTrace();
}
}
GSON:
public static void testJsonObject() {
//构造对象
Person p = new Person();
p.setName("Tom");
p.setAge(20);
p.setScores(Arrays.asList(60,70,80));
//从Java对象到JSON字符串
Gson gson = new Gson();
String s = gson.toJson(p);
System.out.println(s); //{"name":"Tom","age":20,"scores":[60,70,80]}
//从JSON字符串到Java对象
Person p2 = gson.fromJson(s, Person.class);
System.out.println(p2.getName()); //Tom
System.out.println(p2.getAge()); //20
System.out.println(p2.getScores());//[60, 70, 80]
//调用GSON的JsonObject
JsonObject json = gson.toJsonTree(p).getAsJsonObject(); //将整个json解析为一颗树
System.out.println(json.get("name")); //"Tom"
System.out.println(json.get("age")); //20
System.out.println(json.get("scores"));//[60,70,80]
}
public static void testJsonFile() {
Gson gson = new Gson();
File file = new File("books2.json");
try (FileReader reader = new FileReader(file)) {
//从JSON字符串到Java对象
List<Book> books = gson.fromJson(reader, new TypeToken<List<Book>>(){}.getType());
} catch (Exception e) {
e.printStackTrace();
}
}
json和XML比较
- 都是数据交换格式,可读性强,可扩展性高
- 大部分的情况下,JSON更具优势(编码简单,转换方便),而且JSON字符长度一般小于XML,传输效率更高
- XML更加注重标签和顺序,JSON会丢失信息
4、图形图像及解析
图形图像基础概念
- 图形:Graph
- 矢量图,根据几何特性来画的,比如点、直线、弧线等
- 图像:Image
- 由像素点组成
- 格式:jpg,png,bmp,gif,svg,wmf,tiff等
Java图形图像关键类
- 图形:Graph
- java.awt包
- Java 2D库:Graphics2D,Line2D,Rectangle2D,Ellipse2D,Arc2D(弧形)
- Color,Stroke(线条)
- 图像
- java.imageio包
- ImageIO,BufferedImage,ImageReader,ImageWriter
java图像关键类描述
- Java原生支持jpg,png,bmg,gif,wbmp
- javax.imageio.ImageIO
- 自动封装多种ImageReader和ImageWriter,读写图像文件
- read读取图片,write写图片
- java.awt.image.BufferedImage,图像在内存中的表示类
- getHeight 获取高度
- getWidth 获取宽度
eg:
readAndWriteImage:
public static void readAndWrite() throws Exception {
BufferedImage image = ImageIO.read(new File("c:/temp/ecnu.jpg"));
System.out.println("Height: " + image.getHeight()); // 高度像素
System.out.println("Width: " + image.getWidth()); // 宽度像素
ImageIO.write(image, "png", new File("c:/temp/ecnu.png"));
}
在已知图像格式,选定了该格式的reader,加载速度更快
// 指定用jpg Reader来加载,速度会加快
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpg");
ImageReader reader = (ImageReader) readers.next();
System.out.println(reader.getClass().getName());
ImageInputStream iis = ImageIO.createImageInputStream(new File("c:/temp/ecnu.jpg"));
reader.setInput(iis, true);
System.out.println("Height:" + reader.getHeight(0));
System.out.println("Width:" + reader.getWidth(0));
裁剪图片
/**
* cropImage 将原始图片文件切割一个矩形,并输出到目标图片文件
* @param fromPath 原始图片
* @param toPath 目标图片
* @param x 坐标起点x
* @param y 坐标起点y
* @param width 矩形宽度
* @param height 矩形高度
* @param readImageFormat 原始文件格式
* @param writeImageFormat 目标文件格式
* @throws Exception
*/
public static void cropImage(String fromPath, String toPath, int x, int y, int width, int height, String readImageFormat,
String writeImageFormat) throws Exception {
FileInputStream fis = null;
ImageInputStream iis = null;
try {
// 读取原始图片文件
fis = new FileInputStream(fromPath);
Iterator<ImageReader> it = ImageIO.getImageReadersByFormatName(readImageFormat);
ImageReader reader = it.next();
iis = ImageIO.createImageInputStream(fis);
reader.setInput(iis, true);
// 定义一个矩形 并放入切割参数中
ImageReadParam param = reader.getDefaultReadParam();
Rectangle rect = new Rectangle(x, y, width, height);
param.setSourceRegion(rect);
//从源文件读取一个矩形大小的图像
BufferedImage bi = reader.read(0, param);
//写入到目标文件
ImageIO.write(bi, writeImageFormat, new File(toPath));
} finally {
fis.close();
iis.close();
}
}
拼接图片
/**
* 横向拼接两张图片,并写入到目标文件
* 拼接的本质,就是申请一个大的新空间,然后将原始的图片像素点拷贝到新空间,最后保存
* @param firstPath 第一张图片的路径
* @param secondPath 第二张图片的路径
* @param imageFormat 拼接生成图片的格式
* @param toPath 目标图片的路径
*/
public static void combineImagesHorizontally(String firstPath, String secondPath,String imageFormat, String toPath){
try {
//读取第一张图片
File first = new File(firstPath);
BufferedImage imageOne = ImageIO.read(first);
int width1 = imageOne.getWidth();//图片宽度
int height1 = imageOne.getHeight();//图片高度
//从第一张图片中读取RGB
int[] firstRGB = new int[width1*height1];
firstRGB = imageOne.getRGB(0,0,width1,height1,firstRGB,0,width1);
//对第二张图片做同样的处理
File second = new File(secondPath);
BufferedImage imageTwo = ImageIO.read(second);
int width2 = imageTwo.getWidth();
int height2 = imageTwo.getHeight();
int[] secondRGB = new int[width2*height2];
secondRGB = imageTwo.getRGB(0,0,width2,height2,secondRGB,0,width2);
//生成新图片
int height3 = (height1>height2)?height1:height2; //挑选高度大的,作为目标文件的高度
int width3 = width1 + width2; //宽度,两张图片相加
BufferedImage imageNew = new BufferedImage(width3,height3,BufferedImage.TYPE_INT_RGB);
//设置左半部分的RGB 从(0,0) 开始
imageNew.setRGB(0,0,width1,height1,firstRGB,0,width1);
//设置右半部分的RGB 从(width1, 0) 开始
imageNew.setRGB(width1,0,width2,height2,secondRGB,0,width2);
//保存图片
ImageIO.write(imageNew, imageFormat, new File(toPath));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 纵向拼接图片(两张)
* 拼接的本质,就是申请一个大的新空间,然后将原始的图片像素点拷贝到新空间,最后保存
* @param firstPath 读取的第一张图片
* @param secondPath 读取的第二张图片
* @param imageFormat 图片写入格式
* @param toPath 图片写入路径
*/
public static void combineImagesVertically(String firstPath, String secondPath,String imageFormat, String toPath){
try {
//读取第一张图片
File first = new File(firstPath);
BufferedImage imageOne = ImageIO.read(first);
int width1 = imageOne.getWidth();//图片宽度
int height1 = imageOne.getHeight();//图片高度
//从图片中读取RGB
int[] firstRGB = new int[width1*height1];
firstRGB = imageOne.getRGB(0,0,width1,height1,firstRGB,0,width1);
//对第二张图片做相同的处理
File second = new File(secondPath);
BufferedImage imageTwo = ImageIO.read(second);
int width2 = imageTwo.getWidth();
int height2 = imageTwo.getHeight();
int[] secondRGB = new int[width2*height2];
secondRGB = imageTwo.getRGB(0,0,width2,height2,secondRGB,0,width2);
//生成新图片
int width3 = (width1>width2)?width1:width2; //挑选宽度大的,作为目标文件的宽度
int height3 = height1+height2; //高度,两张图片相加
BufferedImage imageNew = new BufferedImage(width3,height3,BufferedImage.TYPE_INT_RGB);
//设置上半部分的RGB 从(0,0) 开始
imageNew.setRGB(0,0,width1,height1,firstRGB,0,width1);
//设置下半部分的RGB 从(0, height1) 开始
imageNew.setRGB(0,height1,width2,height2,secondRGB,0,width2);
//保存图片
ImageIO.write(imageNew, imageFormat, new File(toPath));
} catch (Exception e) {
e.printStackTrace();
}
}
验证码的生成
验证码,一个图片文件
public class ValidateCodeTest {
//没有1 I L 0 o
static char[] codeSequence = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9' };
static int charNum = codeSequence.length;
public static void main(String[] a) throws IOException
{
generateCode("f:/code.jpg");
}
public static void generateCode(String filePath) throws IOException {
// 首先定义验证码图片框
int width = 80; // 验证码图片的宽度
int height = 32; // 验证码图片的高度
BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//定义图片上的图形和干扰线
Graphics2D gd = buffImg.createGraphics();
gd.setColor(Color.LIGHT_GRAY); // 将图像填充为浅灰色
gd.fillRect(0, 0, width, height);
gd.setColor(Color.BLACK); // 画边框。
gd.drawRect(0, 0, width - 1, height - 1);
// 随机产生16条灰色干扰线,使图像中的认证码不易识别
gd.setColor(Color.gray);
// 创建一个随机数生成器类 用于随机产生干扰线
Random random = new Random();
for (int i = 0; i < 16; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
gd.drawLine(x, y, x + xl, y + yl);
}
//计算字的位置坐标
int codeCount = 4; // 字符个数
int fontHeight; // 字体高度
int codeX; // 第一个字符的x坐标,因为后面的字符坐标依次递增,所以它们的x轴值是codeX的倍数
int codeY; // 验证字符的y坐标,因为并排所以值一样
// width-4 除去左右多余的位置,使验证码更加集中显示,减得越多越集中。
// codeCount+1 //等比分配显示的宽度,包括左右两边的空格
codeX = (width - 4) / (codeCount + 1); //第一个字母的起始位置
fontHeight = height - 10; // height - 10 高度中间区域显示验证码
codeY = height - 7;
// 创建字体,字体的大小应该根据图片的高度来定。
Font font = new Font("Fixedsys", Font.PLAIN, fontHeight);
gd.setFont(font);
// 随机产生codeCount数字的验证码。
for (int i = 0; i < codeCount; i++) {
// 每次随机拿一个字母,赋予随机的颜色
String strRand = String.valueOf(codeSequence[random.nextInt(charNum)]);
int red = random.nextInt(255);
int green = random.nextInt(255);
int blue = random.nextInt(255);
gd.setColor(new Color(red,green,blue));
//把字放到图片上!!!
gd.drawString(strRand, (i + 1) * codeX, codeY);
}
ImageIO.write(buffImg, "jpg", new File(filePath));
}
}
统计图生成
- 柱状图/饼图/折线图
- 基于jFreeChart可以快速生成实现统计图生成
- 设定数据集
- 调用ChartFactory生成图形
添加依赖
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.0.19</version>
</dependency>
eg:
public class JFreeChartTest {
public static void main(String[] args) {
writeBar("F:/bar.jpg"); // 柱状图
writePie("F:/pie.jpg"); // 饼图
writeLine("F:/line.jpg");// 折线图
}
public static StandardChartTheme getChineseTheme()
{
StandardChartTheme chineseTheme = new StandardChartTheme("CN");
chineseTheme.setExtraLargeFont(new Font("隶书", Font.BOLD, 20));
chineseTheme.setRegularFont(new Font("宋书", Font.PLAIN, 15));
chineseTheme.setLargeFont(new Font("宋书", Font.PLAIN, 15));
return chineseTheme;
}
public static void writeBar(String fileName) {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
dataset.addValue(11, "", "第一季度");
dataset.addValue(41, "", "第二季度");
dataset.addValue(51, "", "第三季度");
dataset.addValue(4, "", "第四季度");
// PlotOrientation.HORIZONTAL横向 PlotOrientation.VERTICAL 竖向
// 引入中文主题样式
ChartFactory.setChartTheme(getChineseTheme());
JFreeChart chart = ChartFactory.createBarChart3D("柱状图", "2018年", "产品总量", dataset, PlotOrientation.VERTICAL,
false, false, false);
try {
ChartUtilities.saveChartAsJPEG(new File(fileName), chart, 600, 300);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void writePie(String fileName) {
DefaultPieDataset pds = new DefaultPieDataset();
pds.setValue("C人数", 100);
pds.setValue("C++人数", 200);
pds.setValue("Java人数", 300);
try {
ChartFactory.setChartTheme(getChineseTheme());
JFreeChart chart = ChartFactory.createPieChart("饼图", pds);
ChartUtilities.saveChartAsJPEG(new File(fileName), chart, 600, 300);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void writeLine(String fileName) {
DefaultCategoryDataset lines = new DefaultCategoryDataset();
//第一条线
lines.addValue(100, "Java核心技术", "1月");
lines.addValue(200, "Java核心技术", "2月");
lines.addValue(400, "Java核心技术", "3月");
lines.addValue(500, "Java核心技术", "4月");
//第二条线
lines.addValue(100, "Java核心技术(进阶)", "1月");
lines.addValue(400, "Java核心技术(进阶)", "2月");
lines.addValue(900, "Java核心技术(进阶)", "3月");
try {
ChartFactory.setChartTheme(getChineseTheme());
JFreeChart chart = ChartFactory.createLineChart("折线图", "时间", "人数", lines);
ChartUtilities.saveChartAsJPEG(new File(fileName), chart, 600, 300);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5、条形码和二维码及解析
BarCode和QRCode
Java本身没有处理二维码的库,依赖第三方库
- ZXing(Zebra Crossing)
- Google出品,支持1D和2D的Barcode
6、Docx简介及解析
- Word2003(包括)之前都是doc,文档格式不公开
- Word2007(包括)之后都是docx,遵循XML路线,文档格式公开
- docx为主要研究对象
- 文字样式、表格、图片、公式
Docx功能和处理
- 常见功能
- docx解析
- docx生成(完全生成、模板加部分生成:套打)
- 处理的第三方库
- Jacob,COM4J(Windows平台)
- POI,docx4j,OpenOffice/Libre Office SDK
POI
- Apache POI出品,必属精品,poi.apache.org
- 可处理docx,xlsx,pptx,visio等office套件
- 纯Java工具包,无需第三方依赖
- 主要类
- XWPFDocument 整个文档对象
- XWPFParagraph 段落
- XWPFRun 一个片段(字体样式相同的一段)
- XWPFPicture 图片
- XWPFTable 表格
7、表格文件简介及解析
表格文件
- xls/xlsx文件
- CSV文件(Comma-Separated Values文件):以逗号分隔的文本文件。广义的CSV文件,可以由空格/Tab键/分号/…/完成字段分隔
第三方包
- POI
- 主要类
- XSSFWorkbook 整个文档对象
- XSSFSheet 单个sheet对象
- XSSFRow 一行对象
- XSSFCell 一个单元格对象
- 主要类
- COM4J(Windows平台)
- Apache Commons CSV
- CSVFormat 文档格式
- CSVParser 解析文档
- CSVRecord 一行记录
- CSVPrinter 写入文档
8、PDF简介及解析
PDF处理和第三方包
- 常见功能处理
- 解析PDF
- 生成PDF(转化)
- 第三方包
- Apache PDFBox(免费)
- iText(收费)
- XDocReport(将docx转化为PDF)
Apache PDFBox
- 纯Java类库
- 主要功能:创建、提取文本、分隔/合并/删除,…
- 主要类:
- PDDocument pdf文档对象
- PDFTextStripper pdf文本对象
- PDFMergerUtility 合并工具
第五章:Java多线程和并发编程
1、多进程和多线程简介
多进程
- 多进程优点:
- 可以同时运行多个任务
- 程序因IO堵塞时,可以释放CPU,让CPU为其他程序服务
- CPU不再提高频率,而是提高核数
- 多核和并行程序才是提高程序性能的唯一办法
- 当系统有多个CPU时,可以为多个程序同时服务
- 多进程的缺点:
- 太笨重,不好管理
- 太笨重,不好切换
多线程
- 一个程序可以包括多个子任务,可串/并行
- 每个子任务可以称为一个线程
- 如果一个子任务阻塞,程序可以将CPU调度另外一个子任务进行工作,这样CPU还是保留在本程序中,而不是被调度到别的程序(进程)去。这样,提高本程序所获得的CPU时间和利用率
多进程VS多线程
- 线程共享数据
- 线程通讯更高效
- 线程更轻量级,更容易切换
- 多个线程更容易管理
2、Java多线程实现
Java多线程创建
- java.lang.Thread
- 线程继承Thread类,实现run方法
- java.lang.Runnable接口
- 线程实现Runnable接口,实现run方法
注:Runnable方式:
- 可以通过实现Runnable接口来创建线程;
- 实现Runnable的对象必须包装在Thread类里面,才可以启动
class Thread1 extends Thread{
public void run(){
System.out.println("hello");
}
}
class Thread2 implements Runnable{
public void run(){
System.out.println("hello");
}
}
public static void main(String[] a)
{
new Thread1().start();
new Thread(new Thread2()).start();
}
Java多线程启动
启动
- start方法,会自动以新进程调用run方法
- 直接调用run方法,将变成串行执行
- 同一个线程,多次start会报错,只执行第一次start方法
- 多个线程启动,其启动的先后顺序是随机的
- 线程无需关闭,只要其run方法执行结束后,自动关闭
- main函数(线程)可能早于新线程结束,整个程序并不会终止
- 整个程序终止是等所有的线程都终止(包括main函数线程)
Java多线程实现对比
Thread VS Runnable
- thread占据父类的名额,不如Runnable方便
- thread类实现了Runnable
- Runnable启动时需要thread类的支持
- Runnable更容易实现多线程中资源共享
结论:建议实现Runnable接口来完成多线程
3、Java多线程信息共享
多线程信息共享
- 粗粒度:子线程与子线程之间、和main线程之间缺乏交流
- 细粒度:线程之间有信息交流通讯
- 通过共享变量达到信息共享
- JDK原生库暂不支持发送消息(类似MPI并行库直接发送消息)
通过共享变量在多个线程中共享消息
- static变量
- 同一个Runnable类的成员变量
多线程信息共享问题
- 工作缓存副本
- 关键步骤缺乏加锁限制
- 当对同一个变量进行修改时,需要进行加锁限制
eg:
i++,并非原子性操作
- 读取主存i(正本)到工作缓存(副本)中
- 每个CPU执行(副本)i+1操作
- CPU将结果写入到缓存(副本)中
- 数据从工作缓存(副本)刷到主存(正本)中
变量副本问题的解决方法
- 采用volatile关键字修饰变量
- 即一旦变量在一个工作缓存中修改了,其他线程也马上就能够看到
- 保证不同线程工作副本对共享变量操作时的可见性
public class ThreadDemo2 {
public static void main(String args[]) throws Exception {
TestThread2 t = new TestThread2();
t.start();
Thread.sleep(2000);
t.flag = false;
System.out.println("main thread is exiting");
}
}
class TestThread2 extends Thread {
//boolean flag = true; //子线程不会停止
volatile boolean flag = true; //用volatile修饰的变量可以及时在各线程里面通知
public void run() {
int i = 0;
while (flag) {
i++;
}
System.out.println("test thread3 is exiting");
}
}
关键步骤加锁限制
- 互斥:某一个线程运行一个代码段(关键区),其他线程不能同时运行这个代码段
- 同步:多个线程的运行,必须按照某一种规定的先后顺序来运行
- 互斥是同步的一种特例
- 互斥的关键字是synchronized
- synchronized代码块/函数,只能一个线程进入
- synchronized加大性能负担,但是使用简便
eg:
public class ThreadDemo3 {
public static void main(String[] args) {
TestThread3 t = new TestThread3();
new Thread(t, "Thread-0").start();
new Thread(t, "Thread-1").start();
new Thread(t, "Thread-2").start();
new Thread(t, "Thread-3").start();
}
}
class TestThread3 implements Runnable {
private volatile int tickets = 100; // 多个 线程在共享的
public void run() {
while (true) {
sale();
try {
Thread.sleep(100);
} catch (Exception e) {
System.out.println(e.getMessage());
}
if (tickets <= 0) {
break;
}
}
}
public synchronized void sale() { // 同步函数
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--);
}
}
}
4、Java多线程生命周期管理
- 粗粒度:线程之间缺乏同步
- 细粒度:线程之间有同步协作
线程状态
- NEW刚创建(new)
- RUNNABLE就绪态(start)
- RUNNING运行中(run)
- BLOCK阻塞(sleep)
- TERMINATED结束
- Thread的部分API已经废弃
- 暂停和恢复suspend/resume
- 消亡stop/destroy
- 线程阻塞和唤醒
- sleep,时间一到,自己会醒来
- wait/notify/notifyAll,等待,需要别人来唤醒
- join,等待另外一个线程结束
- interrupt,向另外一个线程发送中断信号,该线程收到信号,会触发InterruptrdException(可解除阻塞),并进行下一步处理
多线程管理一些概念
-
多线程死锁
- 每个线程互相持有别人需要的锁
- 预防死锁,对资源进行等级排序
-
守护(后台)线程
-
普通线程的结束,是run方法运行结束
-
守护线程的结束,是run方法运行结束,或main函数结束
t.setDaemon(true);//设置守护线程
-
守护线程永远不要访问资源,如文件或数据库等
-
-
线程查看工具 jvisualvm
5、Java并发框架Executor
并行困难(任务分配和执行过程高度耦合)
- 如何控制粒度,切割任务
- 如何分配任务给线程,监督线程执行过程
并行模式
- 主从模式(Master-Slave)
- Worker模式(Worker-Worker)(P2P)
java并发编程
- Thread/Runnable/Thread组管理
- Executor框架
- Fork-Join框架
线程组ThreadGroup
- 线程的集合
- 树形结构,大线程组可以包括小线程组
- 可以通过enumerate方法遍历组内的线程,执行操作
- 能够有效管理多个线程,但是管理效率低
- 任务分配和执行过程高度耦合
- 重复创建线程、关闭线程操作,无法重用线程
Executor并发框架
- 从jdk5开始提供Executor FrameWork(java.util.concurrent.*)
- 分离任务的创建和执行者的创建
- 线程重复利用(new线程代价很大)
- 共享线程池
- 预设好的多个Thread,可弹性增加
- 多次执行很多很小的任务
- 任务创建和执行过程解耦
- 程序员无需关心线程池执行任务过程
- 主要类:ExecutorService,ThreadPoolExecutor,Future
- Executors.newCachedThreadPool/newFixedThreadPool创建线程池
- ExecutorService 线程池服务
- Callable 具体的逻辑对象(线程类)
- Future 返回结果
eg:计算1-1000的值
SumTest
public class SumTest {
public static void main(String[] args) {
// 执行线程池
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newFixedThreadPool(4);
List<Future<Integer>> resultList=new ArrayList<>();
//统计1-1000总和,分成10个任务计算,提交任务
for (int i=0; i<10; i++){
SumTask calculator=new SumTask(i*100+1, (i+1)*100);
Future<Integer> result=executor.submit(calculator);
resultList.add(result);
}
// 每隔50毫秒,轮询等待10个任务结束
do {
System.out.printf("Main: 已经完成多少个任务: %d\n",executor.getCompletedTaskCount());
for (int i=0; i<resultList.size(); i++) {
Future<Integer> result=resultList.get(i);
System.out.printf("Main: Task %d: %s\n",i,result.isDone());
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (executor.getCompletedTaskCount()<resultList.size());
// 所有任务都已经结束了,综合计算结果
int total = 0;
for (int i=0; i<resultList.size(); i++) {
Future<Integer> result=resultList.get(i);
Integer sum=null;
try {
sum=result.get();
total = total + sum;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
System.out.printf("1-1000的总和:" + total);
// 关闭线程池
executor.shutdown();
}
}
SumTest
public class SumTask implements Callable<Integer> {
//定义每个线程计算的区间
private int startNumber;
private int endNumber;
public SumTask(int startNumber, int endNumber){
this.startNumber=startNumber;
this.endNumber=endNumber;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=startNumber; i<=endNumber; i++)
{
sum = sum + i;
}
Thread.sleep(new Random().nextInt(1000));
System.out.printf("%s: %d\n",Thread.currentThread().getName(),sum);
return sum;
}
}
6、Java并发框架Fork-join
Fork-Join
- 分治编程:分解、治理、合并
- 适用于整体任务量不好确定的场合(最小任务可确定)(二分法)
- 关键类
- ForkJoinPool任务池
- RecursiveAction
- RecursiveTask
eg:
SumTest
//分任务求和
public class SumTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建执行线程池
ForkJoinPool pool = new ForkJoinPool();
//ForkJoinPool pool = new ForkJoinPool(4);
//创建任务
SumTask task = new SumTask(1, 10000000);
//提交任务
ForkJoinTask<Long> result = pool.submit(task);
//等待结果
do {
System.out.printf("Main: Thread Count: %d\n",pool.getActiveThreadCount());
System.out.printf("Main: Paralelism: %d\n",pool.getParallelism());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (!task.isDone());
//输出结果
System.out.println(result.get().toString());
}
}
SumTask
//分任务求和
public class SumTask extends RecursiveTask<Long> {
private int start;
private int end;
public SumTask(int start, int end) {
this.start = start;
this.end = end;
}
public static final int threadhold = 5;
@Override
protected Long compute() {
Long sum = 0L;
// 如果任务足够小, 就直接执行
boolean canCompute = (end - start) <= threadhold;
if (canCompute) {
for (int i = start; i <= end; i++) {
sum = sum + i;
}
} else {
// 任务大于阈值, 分裂为2个任务
int middle = (start + end) / 2;
SumTask subTask1 = new SumTask(start, middle);
SumTask subTask2 = new SumTask(middle + 1, end);
invokeAll(subTask1, subTask2);
Long sum1 = subTask1.join();
Long sum2 = subTask2.join();
// 结果合并
sum = sum1 + sum2;
}
return sum;
}
}
7、Java并发数据结构
- 常用的数据结构是线程不安全的
- ArrayList,HashMap,HashSet非同步的
- 多个线程同时读写,可能会抛出异常或数据错误
- 传统的Vector,Hashtable等同步,但集合性能过差
- 并发数据结构:数据添加和删除
- 阻塞式集合:当集合为空或者满时,等待
- 非阻塞式集合:当集合为空或者满时,不等待,返回null或异常
List
- Vector:同步安全,写多读少
- ArrayList:不安全
- Collections.synchronizedList(List list):基于synchronized,效率差
- CopyOnWriteArrayList:读多写少,基于复制机制,非阻塞
Set
- HashSet:不安全
- Collections.synchronizedSet(Set set):基于synchronized,效率差
- CopyOnWriteArraySet(基于CopyOnWriteArrayList实现):读多写少,非阻塞
Map
- Hashtable:同步安全,写多读少
- HashMap:不安全
- Collections.synchronizedMap(Map map)基于synchronized,效率差
- ConcurrentHashMap:读多写少,非阻塞
Queue&Deque(队列,jdk1.5提出)
- ConcurrentLinkedQueue 非阻塞
- ArrayBlockingQueue/LinkedBlockingQueue阻塞
8、Java并发协作控制
粗粒度
- Thread/Executor/Fork-Join
- 线程之间缺少协作
- synchronized同步
- 限定只有一个线程才能进入关键区
- 简单粗暴,性能损失有点大
Lock
- Lock也可以实现同步的效果(synchronized的升级版)
- 实现更复杂的临界区结构
- tryLock方法可以预判锁是否空闲
- 允许分离读写的操作,多个读,一个写
- readLock,读锁,可以多个线程共享
- writeLock,写锁,排他的,只能一个线程拥有
- 性能更好
- ReentrantLock类,可重入的互斥锁
- ReentrantReadWriteLock类,可重入的读写锁
- lock和unlock函数
public class LockExample {
private static final ReentrantLock queueLock = new ReentrantLock(); //可重入锁
private static final ReentrantReadWriteLock orderLock = new ReentrantReadWriteLock(); //可重入读写锁
/**
* 有家奶茶店,点单有时需要排队
* 假设想买奶茶的人如果看到需要排队,就决定不买
* 又假设奶茶店有老板和多名员工,记单方式比较原始,只有一个订单本
* 老板负责写新订单,员工不断地查看订单本得到信息来制作奶茶,在老板写新订单时员工不能看订单本
* 多个员工可同时看订单本,在员工看时老板不能写新订单
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
//buyMilkTea();
handleOrder(); //需手动关闭
}
public void tryToBuyMilkTea() throws InterruptedException {
boolean flag = true;
while(flag)
{
if (queueLock.tryLock()) {
//queueLock.lock();
long thinkingTime = (long) (Math.random() * 500);
Thread.sleep(thinkingTime);
System.out.println(Thread.currentThread().getName() + ": 来一杯珍珠奶茶,不要珍珠");
flag = false;
queueLock.unlock();
} else {
//System.out.println(Thread.currentThread().getName() + ":" + queueLock.getQueueLength() + "人在排队");
System.out.println(Thread.currentThread().getName() + ": 再等等");
}
if(flag)
{
Thread.sleep(1000);
}
}
}
public void addOrder() throws InterruptedException {
orderLock.writeLock().lock();
long writingTime = (long) (Math.random() * 1000);
Thread.sleep(writingTime);
System.out.println("老板新加一笔订单");
orderLock.writeLock().unlock();
}
public void viewOrder() throws InterruptedException {
orderLock.readLock().lock();
long readingTime = (long) (Math.random() * 500);
Thread.sleep(readingTime);
System.out.println(Thread.currentThread().getName() + ": 查看订单本");
orderLock.readLock().unlock();
}
public static void buyMilkTea() throws InterruptedException {
LockExample lockExample = new LockExample();
int STUDENTS_CNT = 10;
Thread[] students = new Thread[STUDENTS_CNT];
for (int i = 0; i < STUDENTS_CNT; i++) {
students[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
long walkingTime = (long) (Math.random() * 1000);
Thread.sleep(walkingTime);
lockExample.tryToBuyMilkTea();
} catch(InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
);
students[i].start();
}
for (int i = 0; i < STUDENTS_CNT; i++)
students[i].join();
}
public static void handleOrder() throws InterruptedException {
LockExample lockExample = new LockExample();
Thread boss = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
lockExample.addOrder();
long waitingTime = (long) (Math.random() * 1000);
Thread.sleep(waitingTime);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
});
boss.start();
int workerCnt = 3;
Thread[] workers = new Thread[workerCnt];
for (int i = 0; i < workerCnt; i++)
{
workers[i] = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
lockExample.viewOrder();
long workingTime = (long) (Math.random() * 5000);
Thread.sleep(workingTime);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
});
workers[i].start();
}
}
}
Semaphore
- 信号量:本质上是一个计数器
- 计数器大于0,可以使用,等于0不能使用
- 可以设置多个并发量,例如限制10个访问
- Semaphore
- acquire获取(信号量-1)
- release释放(信号量+1)
- 比Lock更进一步,可以控制多个同时访问关键区
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private final Semaphore placeSemaphore = new Semaphore(5);
public boolean parking() throws InterruptedException {
if (placeSemaphore.tryAcquire()) {
System.out.println(Thread.currentThread().getName() + ": 停车成功");
return true;
} else {
System.out.println(Thread.currentThread().getName() + ": 没有空位");
return false;
}
}
public void leaving() throws InterruptedException {
placeSemaphore.release();
System.out.println(Thread.currentThread().getName() + ": 开走");
}
/**
* 现有一地下车库,共有车位5个,由10辆车需要停放,每次停放时,去申请信号量
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
int tryToParkCnt = 10;
SemaphoreExample semaphoreExample = new SemaphoreExample();
Thread[] parkers = new Thread[tryToParkCnt];
for (int i = 0; i < tryToParkCnt; i++) {
parkers[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
long randomTime = (long) (Math.random() * 1000);
Thread.sleep(randomTime);
if (semaphoreExample.parking()) {
long parkingTime = (long) (Math.random() * 1200);
Thread.sleep(parkingTime);
semaphoreExample.leaving();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
parkers[i].start();
}
for (int i = 0; i < tryToParkCnt; i++) {
parkers[i].join();
}
}
}
Latch
- 等待锁,是一个同步辅助类
- 用来同步执行任务的一个或多个线程
- 不是用来保护临界区或者共享资源
- CountDownLatch
- countDown()计数减法1
- await() 等待latch变成0
Barrier
- 集合点,也是一个同步辅助类
- 运行多个线程在某一个点上进行同步
- CyclicBarrier
- 构造函数是需要同步的线程数量
- await等待其他线程,到达数量后,就放行
Phaser
- 允许执行并发多阶段任务,同步辅助类
- 在每一个阶段结束的位置对线程进行同步,当所有的线程都到达这步,再进行下一步
- Phaser
- arrive()
- arriveAndAwaitAdvance()
Exchanger
- 允许在并发线程中互相交换消息
- 允许在2个线程中定义同步点,当两个线程都到达同步点,它们交换数据结构
- Exchanger
- exchange(),线程双方互相交互数据
- 交换数据是双向的
9、Java定时任务执行
Thread/Executor/Fork-Join多线程
- 立刻执行
- 框架调度
定时执行
- 固定某一个时间点运行
- 以某一个周期
简单定时器机制
- 设置计划任务,也就是在指定的时间开始执行某一个任务
- TimerTask封装任务
- Timer类 定时器
public class TimerTest {
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
Timer timer = new Timer();
System.out.println("当前时间:"+new Date().toLocaleString());
//当前时间1秒后,每2秒执行一次
timer.schedule(task, 1000, 2000);
Thread.sleep(10000);
task.cancel(); //取消当前的任务
System.out.println("================================");
Calendar now = Calendar.getInstance();
now.set(Calendar.SECOND,now.get(Calendar.SECOND)+3);
Date runDate = now.getTime();
MyTask2 task2 = new MyTask2();
timer.scheduleAtFixedRate(task2,runDate,3000); //固定速率
Thread.sleep(20000);
timer.cancel(); //取消定时器
}
}
class MyTask extends TimerTask {
public void run() {
System.out.println("运行了!时间为:" + new Date());
}
}
class MyTask2 extends TimerTask {
public void run() {
System.out.println("运行了!时间为:" + new Date());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
注:一个timer对象可以执行多个计划任务,但是这些任务是串行执行的。如果有一个任务执行很慢,将会影响后续的任务准点运行。
Executor+定时器机制
- ScheduledExecutorService
- 定时任务
- 周期任务
package schedule;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorTest {
public static void main(String[] a) throws Exception
{
//executeAtFixTime();
//executeFixedRate(); //3s
executeFixedDelay(); //4s
}
public static void executeAtFixTime() throws Exception {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.schedule(
new MyTask(),
1,
TimeUnit.SECONDS);
Thread.sleep(10000);//休眠10秒
executor.shutdown();
}
/**
* 周期任务 固定速率 是以上一个任务开始的时间计时,period时间过去后,检测上一个任务是否执行完毕,
* 如果上一个任务执行完毕,则当前任务立即执行,如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行。
* @throws Exception
*/
public static void executeFixedRate() throws Exception {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(
new MyTask(),
1,
3000,
TimeUnit.MILLISECONDS);
Thread.sleep(10000);
executor.shutdown();
}
/**
* 周期任务 固定延时 是以上一个任务结束时开始计时,period时间过去后,立即执行。
* @throws Exception
*/
public static void executeFixedDelay() throws Exception {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleWithFixedDelay(
new MyTask(),
1,
3000,
TimeUnit.MILLISECONDS);
Thread.sleep(10000);
executor.shutdown();
}
}
class MyTask implements Runnable {
public void run() {
System.out.println("时间为:" + new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println("时间为:" + new Date());
}
}
第六章 Java网络编程
1、网络基础知识
ip
补充:ipv4逐渐枯竭,在NAT网络地址转换和端口加持下坚持下来,即网关和端口
port:端口,0~65535
- 0~1023,OS已经占用了,80是Web,23是telnet
- 1024~65535,一般程序可使用(谨防冲突)
两台机器通讯就是在IP+Port上进行的
查询ip:Windows:ipconfig Linux/Mac:ifconfig
查询端口:Windows/Linux/Max:netstat -an
网络架构模型
通讯协议:TCP和UDP
- TCP(Transmission Control Protocol)
- 传输控制协议,面向连接的协议
- 两台机器的可靠无差错的数据传输
- 双向字节流传递
- UDP(User Datagram Protocol)
- 用户数据报协议,面向无连接协议
- 不保证可靠的数据传输
- 速度快,也可以在较差网络下使用
2、Java UDP编程
- 计算机通讯:数据从一个IP的port出发(发送方),运送到另外一个IP的port(接收方)
- UDP:无连接无状态的通讯协议
- 发送方发送消息,如果接收方刚好在目的地,则可以接收。如果不在,那这个消息就丢失了
- 发送发也无法得知是否发送成功
- UDP的好处就是简单、节省、经济
Java UDP
- DatagramSocket:通讯的数据管道
- send和receive方法
- (可选,多网卡)绑定一个IP和Port
- DatagramPacket
- 集装箱:封装数据
- 地址标签:目的地IP+Port
eg:
UdpRecv
import java.net.*;
public class UdpRecv
{
public static void main(String[] args) throws Exception
{
DatagramSocket ds=new DatagramSocket(3000);//定义了在本机的3000端口
byte [] buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf,1024);//集装箱,用来封装数据
System.out.println("UdpRecv: 我在等待信息");
ds.receive(dp);//接收方准备就绪。若没有收到消息,程序会阻塞在这里
System.out.println("UdpRecv: 我接收到信息");
String strRecv=new String(dp.getData(),0,dp.getLength()) + " from " + dp.getAddress().getHostAddress()+":"+dp.getPort();
System.out.println(strRecv);
Thread.sleep(1000);
System.out.println("UdpRecv: 我要发送信息");
String str="hello world 222";
DatagramPacket dp2=new DatagramPacket(str.getBytes(),str.length(),
InetAddress.getByName("127.0.0.1"),dp.getPort());
ds.send(dp2);
System.out.println("UdpRecv: 我发送信息结束");
ds.close();
}
}
UdpSend
import java.net.*;
public class UdpSend
{
public static void main(String [] args) throws Exception
{
DatagramSocket ds=new DatagramSocket();
String str="hello world";
DatagramPacket dp=new DatagramPacket(str.getBytes(),str.length(),InetAddress.getByName("127.0.0.1"),3000);
System.out.println("UdpSend: 我要发送信息");
ds.send(dp);
System.out.println("UdpSend: 我发送信息结束");
Thread.sleep(1000);
byte [] buf=new byte[1024];
DatagramPacket dp2=new DatagramPacket(buf,1024);
System.out.println("UdpSend: 我在等待信息");
ds.receive(dp2);
System.out.println("UdpSend: 我接收到信息");
String str2=new String(dp2.getData(),0,dp2.getLength()) + " from " + dp2.getAddress().getHostAddress()+":"+dp2.getPort();
System.out.println(str2);
ds.close();
}
}
注意:最好先有receive,再send。不然可能会丢失某个信息
3、Java TCP编程
**TCP协议:**有连接、保证可靠的无误差通讯
- 服务器:创建一个ServerSocket,等待连接
- 客户机:创建一个Socket,连接到服务器
- 服务器:ServerSocket接收到连接,创建一个Socket和客户的Socket建立专线连接,后续服务器和客户机的对话(这一对Socket)会在一个单独的线程(服务器端)上运行
- 服务器的ServerSocket继续等待连接,返回1
注:这里所说的服务器不是指硬件服务器,而是软件服务器。软件服务器有两个要求:
- 它能够实现一定的功能
- 它必须在一个公开地址上对外提供服务
Java TCP编程
- ServerSocker:服务器码头
- 需要绑定port
- IP地址默认本机。如果有多块网卡,需要绑定一个IP地址
- Socket:运输通道
- 客户端需要绑定服务器的地址和port
- 客户端往Socket输入流写入数据,送到服务端
- 客户端从Socket输出流取服务器端过来的数据
- 服务器反之亦然
一些规则:
- 服务端等待响应时,处于阻塞状态
- 服务端可以同时响应多个客户端
- 服务端每接受一个客户端,就启动一个独立的线程与之对应
- 客户端或者服务端都可以选择关闭这对Socket的通道
eg:
TcpClient:
import java.net.*;
import java.io.*;
public class TcpClient {
public static void main(String[] args) {
try {
Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8001); //需要服务端先开启
//同一个通道,服务端的输出流就是客户端的输入流;服务端的输入流就是客户端的输出流
InputStream ips = s.getInputStream(); //开启通道的输入流
BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
OutputStream ops = s.getOutputStream(); //开启通道的输出流
DataOutputStream dos = new DataOutputStream(ops);
BufferedReader brKey = new BufferedReader(new InputStreamReader(System.in));
while (true)
{
String strWord = brKey.readLine();
if (strWord.equalsIgnoreCase("quit"))
{
break;
}
else
{
System.out.println("I want to send: " + strWord);
dos.writeBytes(strWord + System.getProperty("line.separator"));
System.out.println("Server said: " + brNet.readLine());
}
}
dos.close();
brNet.close();
brKey.close();
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
TcpServer:(只能实现一个client的一次对话)
import java.net.*;
import java.io.*;
public class TcpServer
{
public static void main(String [] args)
{
try
{
ServerSocket ss=new ServerSocket(8001); //驻守在8001端口
Socket s=ss.accept(); //阻塞,等到有客户端连接上来
System.out.println("welcome to the java world");
InputStream ips=s.getInputStream(); //有人连上来,打开输入流
OutputStream ops=s.getOutputStream(); //打开输出流
//同一个通道,服务端的输出流就是客户端的输入流;服务端的输入流就是客户端的输出流
ops.write("Hello, Client!".getBytes()); //输出一句话给客户端
BufferedReader br = new BufferedReader(new InputStreamReader(ips));
//从客户端读取一句话
System.out.println("Client said: " + br.readLine());
ips.close();
ops.close();
s.close();
ss.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
TcpServer:(可以实现多个client的多次对话)
问题:
- 不是聊天室功能,不同client之间不能对话,需要服务器将client的话发送给其他client
- 每多一个client,需要创建一个线程。应该考虑executor并发框架
TcpServer2
import java.net.*;
public class TcpServer2
{
public static void main(String [] args)
{
try
{
ServerSocket ss=new ServerSocket(8001);
while(true)
{
Socket s=ss.accept();
System.out.println("来了一个client");
new Thread(new Worker(s)).start();
}
//ss.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
Worker
import java.net.*;
import java.io.*;
class Worker implements Runnable {
Socket s;
public Worker(Socket s) {
this.s = s;
}
public void run() {
try {
System.out.println("服务人员已经启动");
InputStream ips = s.getInputStream();
OutputStream ops = s.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(ips));
DataOutputStream dos = new DataOutputStream(ops);
while (true) {
String strWord = br.readLine();
System.out.println("client said:" + strWord +":" + strWord.length());
if (strWord.equalsIgnoreCase("quit"))
break;
String strEcho = strWord + " 666";
// dos.writeBytes(strWord +"---->"+ strEcho +"\r\n");
System.out.println("server said:" + strWord + "---->" + strEcho);
dos.writeBytes(strWord + "---->" + strEcho + System.getProperty("line.separator"));
}
br.close();
// 关闭包装类,会自动关闭包装类中所包装的底层类。所以不用调用ips.close()
dos.close();
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
4、Java HTTP编程
HTTP
- 资源文件采用HTML编写,以URL形式对外提供
- 访问方式:
- GET:从服务器获取资源到客户端
- POST:从客户端向服务器发送数据
- PUT:上传文件
- DELETE:删除文件
- HEAD:报文头部
- OPTIONS:询问支持的方法
- TRACE:追踪路径
- CONNECT:用隧道协议连接代理
Java HTTP编程(java.net包)
- 支持模拟成浏览器的方式去访问网页
- URL,Uniform Resource Locator,统一资源定位符,代表一个资源
- URLConnection
- 获取资源的连接器
- 根据URL的openConnection()方法获得URLConnection
- connection方法,建立和资源的联系通道
- getInputStream方法,获取资源的内容
eg:
URLConnectionGetTest
public class URLConnectionGetTest
{
public static void main(String[] args)
{
try
{
String urlName = "http://www.baidu.com";
URL url = new URL(urlName);
URLConnection connection = url.openConnection();
connection.connect();
// 打印http的头部信息
Map<String, List<String>> headers = connection.getHeaderFields();
for (Map.Entry<String, List<String>> entry : headers.entrySet())
{
String key = entry.getKey();
for (String value : entry.getValue())
System.out.println(key + ": " + value);
}
// 输出将要收到的内容属性信息
System.out.println("----------");
System.out.println("getContentType: " + connection.getContentType());
System.out.println("getContentLength: " + connection.getContentLength());
System.out.println("getContentEncoding: " + connection.getContentEncoding());
System.out.println("getDate: " + connection.getDate());
System.out.println("getExpiration: " + connection.getExpiration());
System.out.println("getLastModifed: " + connection.getLastModified());
System.out.println("----------");
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
// 输出收到的内容
String line = "";
while((line=br.readLine()) != null)
{
System.out.println(line);
}
br.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
5、Java HTTP编程(HttpClient)
HttpClient
- JDK HTTP Client (JDK自带,从9开始)
- Apache HttpComponents的HttpClient
JDK HTTPClient
- JDK9新增,jdk10更新,jdk11正式发布
- java.net.http包
- 取代URLConnection
- 支持HTTP/1.1和HTTP/2
- 实现大部分HTTP方法
- 主要类
- HttpClient
- HttpRequest
- HttpResponse
Apache HttpComponents的HttpClient
- 是一个集成的Java HTTP工具包
进一步拓展
- 爬虫
- 自动刷票机器人
- 各类Web监控
- Web测试
- ……
6、Java NIO编程
- 传统的TCP/UDP通讯:Blocking I/O
- NIO
- Non-Blocking I/O,又名New I/O
- 提供非阻塞通讯等方式
- 避免同步I/O通讯效率过低
- 一个线程可以管理多个连接
- 减少线程多的压力
NIO主要类
- Buffer缓冲区
- Channel通道
- Selector多路选择器
Buffer缓冲区,一个可以读写的内存区域
- ByteBuffer,CharBuffer,DoubleBuffer,IntBuffer,LongBuffer,ShortBuffer(StringBuffer不是Buffer缓冲区)
- 四个主要属性
- capacity容量,position读写位置,limit界限,mark标记,用于重复一个读/写操作
Channel通道
- 全双工的,支持读/写(而Stream流是单向的)
- 支持异步读写
- 和Buffer配合,提高效率
- ServerSocketChannel服务器TCP Socket接入通道,接收客户端
- SocketChannel TCP Socket通道,可支持阻塞/非阻塞通讯
- DatagramChannel UDP通道
- FileChannel文件通道
Selector多路选择器(轮询开关)
- 每隔一段时间,不断轮询注册在其上的Channel
- 如果有一个Channel有接入、读、写操作,就会被轮询出来
- 根据SelectionKey可以获取相应的Channel,进行后续IO操作
- 避免过多的线程
- SelectionKey四种类型
- OP_CONNECT
- OP_ACCEPT
- OP_READ
- OP_WRITE
7、Java AIO编程
AIO
- Asynchronous I/O,异步I/O
- 异步I/O,采用回调方法进行处理读写操作,基于事件驱动
- 主要类
- AsynchronousServerSocketChannel服务器接受请求通道
- bind 绑定在某一个端口 accept 接受客户端请求
- AsynchronousSocketChannel Socket通讯通道
- read 读数据 write 写数据
- CompletionHandler 异步处理类
- completed 操作完成后异步调用方法 failed操作失败后异步调用方法
- AsynchronousServerSocketChannel服务器接受请求通道
三种I/O比较:
8、Netty编程
**Netty:**集成的第三方网络通讯库
-
一个非阻塞的客户端-服务器网络通讯框架
-
基于异步事件驱动模型
-
简化Java的TCP和UDP编程
-
支持HTTP/2,SSL等多种协议
-
支持多种数据格式,如JSON等
-
关键技术
- 通道 Channel
- ServerSocketChannel/NioServerSocketChannel/…
- SocketChannel/NioSocketChannel
- 事件 EventLoop
- 为每个通道定义一个EventLoop,处理所有的I/O事件
- EventLoop注册事件
- EventLoop将事件派发给ChannelHandler
- EventLoop安排进一步操作
- 事件
- 事件按照数据流向进行分类
- 入站事件:连接激活/数据读取/……
- 出站事件:打开到远程连接/写数据/……
- 事件处理ChannelHandler
- Channel通道发生数据或状态改变
- EventLoop会将事件分类,并调回ChannelHandler的回调函数
- 程序员需要实现ChannelHandler内的回调函数
- ChannelInboundHandler/ChannelOutboundHandler
- 通道 Channel
-
《Netty实战》,Norman Maurer著,何品 译,人民邮电出版社
-
《Netty权威指南》,李林锋,电子工业出版社
9、邮件基础知识
主要协议(发送端口25,接收端口110)
- 发送,SMTP,Simple Mail Transfer Protocol
- 接收,Pop3,Post Office Protocol3
- 接收,IMAP,Internet Message Access Protocol,IMAP4
- 摘要浏览
- 选择下载附件
- 多文件夹
- 网络硬盘
10、Java Mail编程
第七章 Java数据库编程
1、数据库和SQL
2、JDBC基本操作
Java连接数据库操作步骤
- 构建连接
- 注册驱动,寻找材质,class.forName();
- 建立连接
- 执行操作
- Statement(执行者)
- ResultSet(结果集)
- 释放连接
- connection.close();
Statement
- Statement执行者类
- 使用executeQuery()执行select语句,返回结果放在ResultSet
- 使用executeUpdate()执行insert/update/delete,返回修改的行数
- 一个Statement对象一次只能执行一个命令
- ResultSet结果对象
- next()判断是否还有下一条记录
- getInt/getString/getDouble/……
- 可以按索引位置,可以按照列名
eg:
public class MySQLTest {
//连接信息
private static String url = "jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8";
private static String userName = "root";
private static String password = "root";
public static void main(String[] args) {
//创建mysql连接
try {
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("注册驱动成功");
} catch (ClassNotFoundException e) {
System.out.println("注册驱动失败");
e.printStackTrace();
return;
}
databaseUpdate("insert into student(id,name,age,sex) values('004','zhangsan',18,'male')");
databaseUpdate("update student set age = 50 where id = '004'");
databaseUpdate("delete from student where id = 003");
databaseSearch();
}
private static void databaseSearch() {
//创建连接
Connection conn = null;
try {
conn = DriverManager.getConnection(url, userName, password);
//构建数据库执行者
Statement statement = conn.createStatement();
System.out.println("创建Statement成功");
//执行SQL语句并返回结果到resultSet
ResultSet resultSet = statement.executeQuery("select id,name,age,sex from student");
System.out.println("查询成功");
//开始遍历ResultSet数据
while (resultSet.next()) {
System.out.println(resultSet.getString(1) + " " + resultSet.getString(2) + " "
+ resultSet.getByte(3) + " " + resultSet.getString(4));
}
resultSet.close();
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (!(conn == null)) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
private static void databaseUpdate(String sql) {
//创建连接
Connection conn = null;
try {
conn = DriverManager.getConnection(url, userName, password);
//构建数据库执行者
Statement statement = conn.createStatement();
System.out.println("创建Statement成功");
//执行SQL语句并返回结果到resultSet
int result = statement.executeUpdate(sql);
System.out.println("result = " + result);
System.out.println("执行成功");
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (!(conn == null)) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
注意:
-
ResultSet不能做多个笛卡尔积连接
-
例如:
-
while(rs1.next()){ while(rs2.next()){ //这个循环次数是 rs1条数*rs2条数 } }
-
-
resultset最好不要超过百条,否则极其影响性能
-
resultset也不是一口气加载所有的select结果数据
3、JDBC高级操作
JDBC高级操作
- 事务管理
- PreparedStatement
- ResultSetMetaData
事务
- 数据库事务,Database Transaction
- 作为单个逻辑工作单元执行的一系列操作,要么完全执行,要么完全不执行
- 事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)
- 事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理
JDBC事务
- 关闭自动提交,实现多语句同一事务
- connection.setAutoCommit(false);
- connection.commit();提交事务
- connection.rollback();回滚事务
- 保存点机制
- connnection.setSavepoint()
- connection.rollback(Savepoint)
eg:
// 构建Java和数据库之间的桥梁:URL,用户名,密码
conn = DriverManager.getConnection(url, "root", "123456");
conn.setAutoCommit(false);
insertBook(conn, "insert into t_book values(101, 'aaaa', 10)");
insertBook(conn, "insert into t_book values(102, 'bbbb', 10)");
insertBook(conn, "insert into t_book values(103, 'cccc', 10)");
Savepoint phase1 = conn.setSavepoint(); //设置一个保存点
insertBook(conn, "insert into t_book values(104, 'cccc', 10)");
insertBook(conn, "insert into t_book values(105, 'cccc', 10)");
conn.rollback(phase1); //回滚到phase1保存点,即上面2行无效
conn.commit();
PreparedStatement
- 更为安全地执行SQL,防止SQL注入
- 和Statement区别是使用“?”代替字符串拼接
- 使用setXXX(int,Object)的函数来实现对于?的替换
- 注:不需要考虑字符串的两侧单引号
- 提供addBatch批量更新功能
eg:
String sql = "insert into t_book(bookid,bookname,price) values(?,?,?)";
//构建数据库执行者
PreparedStatement pstmt = conn.prepareStatement(sql);
//执行SQL语句
int bookid = 10;
String bookName = "Effective Java',50);delete from t_book;insert into t_book values(101, 'faked book";
int price = 50;
pstmt.setInt(1, bookid);
pstmt.setString(2, bookName);
pstmt.setInt(3, price);
int result = pstmt.executeUpdate();
pstmt.close();
批量插入数据
String sql = "insert into t_book(bookid,bookname,price) values(?,?,?)";
//构建数据库执行者
PreparedStatement pstmt = conn.prepareStatement(sql);
//执行SQL语句
String bookName = "aaaaaaaaaaaaaaaa";
int price = 50;
for(int i=200;i<210;i++)
{
pstmt.setInt(1, i);
pstmt.setString(2, bookName);
pstmt.setInt(3, price);
pstmt.addBatch();
}
pstmt.executeBatch();
pstmt.close();
注:如果有大量的SQL语句,它们结构相同,仅仅差别在具体数值上,那么可以通过addBatch方法进行批量操作。这样会提高性能,减少数据库负担。
- PreparedStatement使用预编译速度相对Statement快很多
ResultSetMetaData
- ResultSet可以用来承载所有的select语句返回的结果集
- ResultSetMetaData来获取ResultSet返回的属性
- getColumnCount(),返回结果的列数
- getColumnClassName(i),返回第i列的数据的Java类名
- getColumnTypeName(i),返回第i列的数据库类型名称
- getColumnName(i),返回第i列的列名
- getColumnType(i),返回第i列的SQL类型
eg:
//获取结果集的元数据
ResultSetMetaData meta = rs.getMetaData();
int cols = meta.getColumnCount();
for(int i=1;i<=cols;i++)
{
System.out.println(meta.getColumnName(i) + "," + meta.getColumnTypeName(i));
}
4、数据库连接池
享元模式
- connection是Java和数据库两个平行系统的桥梁
- 桥梁构建不易,成本很高,单次使用成本昂贵
- 运用共享技术来实现数据库连接池(享元模式)
- 降低系统中数据库连接connection对象的数量
- 降低数据库服务器的连接响应速度
- 提高Connection获取的响应速度
常用的数据库连接池
- DBCP(Apache,性能较差)
- C3P0
- Druid(阿里)
eg:
//从c3p0获取
//conn = C3p0Factory1.getConnection();
//conn = C3p0Factory2.getConnection();
//从Druid获取
//conn = DruidFactory1.getConnection();
conn = DruidFactory2.getConnection();
C3p0Factory1
public class C3p0Factory1 {
private static ComboPooledDataSource dataSource = null;
public static void init() throws Exception {
dataSource = new ComboPooledDataSource();
dataSource.setDriverClass( "com.mysql.jdbc.Driver" );
dataSource.setJdbcUrl( "jdbc:mysql://localhost:3306/test" );
dataSource.setUser("root");
dataSource.setPassword("123456");
// the settings below are optional -- c3p0 can work with defaults
dataSource.setMinPoolSize(5); //设置最小值
dataSource.setAcquireIncrement(5); //设置增量
dataSource.setMaxPoolSize(20); //设置最大值
// The DataSource dataSource is now a fully configured and usable pooled DataSource
}
public static Connection getConnection() throws Exception {
if(null == dataSource)
{
init();
}
return dataSource.getConnection();
}
}
C3p0Factory2
public class C3p0Factory2 {
private static ComboPooledDataSource dataSource = null;
public static void init() throws Exception {
dataSource = new ComboPooledDataSource();
//dataSource 自动加载c3p0-config.xml文件
// The DataSource dataSource is now a fully configured and usable pooled DataSource
}
public static Connection getConnection() throws Exception {
if(null == dataSource)
{
init();
}
return dataSource.getConnection();
}
}
c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config> <!-- 默认配置 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
</c3p0-config>
DruidFactory1
public class DruidFactory1 {
private static DruidDataSource dataSource = null;
public static void init() throws Exception {
dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test");
dataSource.setInitialSize(5);
dataSource.setMinIdle(1); //设置idle时间
dataSource.setMaxActive(10); //设置最大连接数
// 启用监控统计功能 dataSource.setFilters("stat");//
}
public static Connection getConnection() throws Exception {
if(null == dataSource)
{
init();
}
return dataSource.getConnection();
}
}
DruidFactory2
public static void init() throws Exception {
Properties properties = new Properties();
InputStream in = DruidFactory2.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(in);
dataSource = (DruidDataSource)DruidDataSourceFactory.createDataSource(properties);
in.close();
}
druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test
username=root
password=123456
filters=stat
initialSize=2
maxActive=300
maxWait=60000
timeBetweenEvictionRunsMillis=60000
minEvictableIdleTimeMillis=300000
validationQuery=SELECT 1
testWhileIdle=true
testOnBorrow=false
testOnReturn=false
poolPreparedStatements=false
maxPoolPreparedStatementPerConnectionSize=200
第八章 Java混合编程
1、Java调用Java程序(RMI)
多虚拟机JVM的程序运行
- 启动多个main程序,这些程序可以部署在多个机器/虚拟机上
- 多个进程可通过网络互相传递消息进行协作
- 进程通过RMI可调用另一个机器的Java的函数
RMI:Remote Method Invocation 远程方法调用
- 两个位于不同JVM虚拟机的Java程序互相请求访问
- 4、5、6、7这四步由RMI自己完成,1、2、3、8由程序员完成
RMI的参数和返回值
- (自动化)传递远程对象(实现Remote接口)
- 当一个对远程对象的引用从一个JVM传递到另一个JVM,该远程对象的发送者和接收者将持有同一个实体对象的引用。这个引用并非是一个内存位置,而是由网络地址和该远程对象的唯一标识符构成的。
- 两个JVM拥有同一个对象
- (自动化)传递可序列化对象(实现Serializable接口)
- JVM中的一个对象经过序列化后的字节,通过网络,其副本传递到另一个JVM中,并重新还原为一个Java对象
- 每个JVM拥有自己的对象
eg:
RMI优点
- 跨平台分布式对象调用
- 完全对象支持
- 安全策略
RMI缺点
- 双方必须是Java语言实现
- 不如消息传递协作方便
2、Java调用C程序(JNI)
JNI
- JNI,Java Naitve Interface
- 采用JNI,将丧失跨平台性(dll文件只能运行在Windows上,so文件只能运行在Linux上)
- Java和本地C代码进行互操作
- Java调用C程序完成一些需要快速计算的功能(常见,重点)
- C调用Java程序(基于反射的方法)
eg:
class HelloNative{
public static native void greeting();
}
- 在Java类中声明一个本地方法
- 调用javac.exe编译,得到HelloNative.class
- 调用javah.exe得到包含该方法(Java_HelloNative_greeting)的头文件HelloNative.h
- 实现.c文件(对应HelloNative.h)
- 将.c和.h文件,整合为共享库(DLL)文件
- 在Java类中,加载相应的共享库文件
3、Java调用JavaScript程序(Nashorn)
4、Java调用Python程序(Jyphon)
5、Java调用Web Service
6、Java调用命令行
Runtime
- Java提供Runtime类
- exec以一个独立进程执行命令command,并返回Process句柄
- 当独立进程启动后,需要处理该进程的输出流/错误流
- 调用Process.getInputStream可以获取进程的输出流
- 调用Process.getErrorStream可以获取进程的错误输出流
- 调用Process.waitFor等待目标进程的终止(当前进程阻塞)
Java进阶总结
需要熟知的内容
- Maven项目构建和编译
- JUnit单元测试
- ResourceBundle多国语,Pattern和Matcher正则表达式
- 多种文件处理(掌握方法,编程时候再查询API)
- 多线程和并发(Thread,Executors,Fork-Join)
- 网络编程(TCP/UDP,NIO原理,邮件编程)
- 数据库编程
- 混合编程(掌握原理,编程时候再查询API)