面向对象设计实践(基于java)
caiyi 2021/10/1
source:https://www.icourse163.org/spoc/course/ECNU-1464731161
期末划考试范围:junit单元测试,第三章文本处理,第四章文件处理,第五章多线程并行(5.4 5.7 5.8很少考 5.9定时任务要考),第六章网络编程(6.6 6.7 6.8不考),第七章数据库编程(7.3不考),第八章(8.1-8.4考的少 只考8.5 8.6)
第一章 Maven
Maven概念
自动下载和管理jar包,配置build path,存在本地进行管理,编译、测试、运行、和打包发布java项目的构建工具
Maven编译工作流程
Maven目录结构
Maven项目构建流程
首先创建maven项目
选择 maven project
勾选 create a simple project
输入 group id (组织名)、artifact id(作品名/项目名)
然后在mvn中央仓库搜索工具包的名字
https://mvnrepository.com/
选择合适的版本,复制依赖文本
将依赖文本添加到项目pom.xml中
Maven编译和运行
右键项目 → Run As → Maven Build
在Goals输入 clean package → Apply → Run
编译成功
编译过程中他会自动把jar包下载下来
运行程序
总结,构建工具的功能
-
自动帮程序员甄别和下载第三方库(jar)
-
完成整个项目编译(调用javac.exe)
-
完成整个项目单元测试流程(调用JUnit工具)
-
完成项目打包(jar/war等格式,调用jar.exe)
一些常用的依赖文本
opencc4j 简体转繁体
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>opencc4j</artifactId>
<version>1.7.1</version>
</dependency>
Junit 测试
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
commons math 数学
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
</dependency>
pinyin4j 汉字转拼音
<dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
<version>2.5.1</version>
</dependency>
Maven报错解决办法
在pom.xml中添加以下配置语句
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
第二章 单元测试和Junit
软件测试
在规定的条件下对程序进行操作, 以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程
软件测试分类
单元 vs 集成测试
白盒 vs 黑盒测试
自动 vs 手动测试
回归测试、压力测试
JUnit:一个java语言的单元测试框架,大部分java IDE都集成了JUnit作为单元测试工具
JUnit 用法
用上一章的方法新建一个maven项目
⭐️记得在 pom.xml 里导入 JUnit 的依赖文本,否则会报错
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>
观察项目目录,我们在src/main/java 里面放业务代码,src/test/java 里面放测试代码
在 src/main/java 新建 LeapYear.java,给定一个年份,判断是不是闰年
public class LeapYear {
public boolean isLeapYear(int year) {
if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0){
return true;
}
else{
return false;
}
}
}
在 src/test/java 新建 LeapYearTest.java
每一个测试方法的头部加@Test注解,这样JUnit会自动执行这些测试方法
import是导入一个类或者几个类,import static是导入某一个类的所有静态方法,以便我们在程序调用的时候不需要再写 Assert.assertEquals()
import static org.junit.Assert.*;
import org.junit.Test;
public class LeapYearTest {
@Test
public void test() {
assertEquals(true,new LeapYear().isLeapYear(2020));
assertEquals(false,new LeapYear().isLeapYear(2019));
}
}
junit单元测试,右键LeapYearTest.java → Run As → JUnit Test
结果正确!
修改一下代码,运行结果错误!和我们期待的值不一样
我们也可以通过Maven Test来运行JUnit程序
运行错误
修改第八行的 false 为 true,运行正确
二者区别
JUnit一次只能执行一个test类,Maven可以批量执行
第三章 高级文本处理
3.1 Java字符编码
ASCII码
用一个字节(1 Byte = 8 bits)来存储 a-z A-Z 0-9和一些常用的符号,回车13,零48,A65,a97
Unicode编码
编码方案有UTF-8,兼容ASCII,变长1-4个字节存储字符,经济方便传输
Java的字符编码
源文件编码:采用UTF-8编码
程序内部采用UTF-16编码存储所有字符(不是程序员控制)
和外界(文本文件)的输入输出尽量采用UTF-8编码
通过几个程序了解一下Java字符编码
获取默认字符集,本机默认是GBK
输出所有支持的字符集
用UTF-8格式写入文件
写入成功
接着用UTF-8编码读取文件
读取成功!
再试试用 GBK 编码读取文件呢,可以发现它乱码了
3.2 Java国际化编程
多语言版本的软件,一套语言多个语言包,根据语言设定,可以切换显示文本
主要用到两个类
java.util.ResourceBundle 用于加载一个语言_国家语言包
java.util.Locale 定义一个语言_国家
Locale类
属性:语言(zh, en),国家地区(CN, US)
方法:getAvailableLocales(), getDefault()返回默认的
ResourceBundle类
→ 根据 Locale 的值加载对应的properties文件
→ 存储语言集合中所有的K-V对
→ getString(String key) 返回所对应的value
properties语言文件
我们所有的文本值都放在 properties文件里面,存放的都是KV对,存储文件必须是ASCII编码或Unicode编码
命名规则:包名 _ 语言 _ 国家地区.properties、
可以采用native2ascii.exe进行转码
国际化转化演示
所有的文本值都放在 properties文件里面
其他国际化
日期时间国际化 DateTimeFormatter和Locale的结合
数字金额国际化 NumberFormat和Locale的结合
解决引入java.util 爆红的方法
右键项目 → Build Path → Configure Build Path
Java Build Path → Libraries选项卡 → 右侧Add Library按钮 → JRE System Library
3.3 高级字符串处理
3.3.1 正则表达式
用事先定义好的一些特定字符、及这些特定字符的组合,组成一 个“规则字符串”,用于测试字符串内的模式、识别和替换文本、提取文本
匹配模板:定界符、原子、特殊功能字符、模式修正符
Java.util.regex包
-
Pattern 正则表达式的编译表示
compile 编译一个正则表达式为Pattern对象
matcher 用Pattern对象匹配一个字符串,返回匹配结果
-
Matcher
Index Methods(位置方法) // start(), start(int group), end(), end(int group)
Study Methods(查找方法) // lookingAt(), find(), find(int start), matches()
Replacement Methods(替换方法) //replaceAll(String replacement)
例子
example1:一个pattern就代表一个正则表达式
example2:lookingAt()和matches()的区别
example3:替换掉满足匹配模式的字符串为另一个替换字符文额 e
example4:把所有dog替换为cat,dogs替换为cats
example5:用replaceAll()的方法修改example3
example6:邮箱的正则表达式判断
public static void regularExpression() {
String REGEX_EMAIL = "^\\w+((-\\w+)|(\\.\\w+))*@[A-Za-z0-9]+(([.\\-])[A-Za-z0-9]+)*\\.[A-Za-z0-9]+$";//email
Pattern pattern = Pattern.compile(REGEX_EMAIL);
String[] emails = new String[]{"123^@qq.com", "name_321@163.com", "+whatever*72@gmail.com"};
for(String email : emails) {
Matcher matcher = pattern.matcher(email);
if (matcher.matches()) {
System.out.println(email + " is valid email.");
} else {
System.out.println(email + " is not valid email.");
}
}
}
3.3.2 其他字符串处理
集合和字符串互转
引入依赖文件
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
import依赖包
import org.apache.commons.lang3.StringUtils;
public class String2List {
public static void main(String[] args) {
List<String> names = new LinkedList<String>();
names.add("Xiaohong");
names.add("Xiaoming");
names.add("Daming");
names.add("Xiaohei");
//从ArrayList变到字符串
String str1 = String.join(",", names); //String.join, JDK 8 引入
System.out.println(str1);
String str2 = StringUtils.join(names, ","); //Apache Commons Lang
System.out.println(str2);
//从字符串变回ArrayList
List<String> names2 = Arrays.asList(str2.split(","));
for(String name:names2)
{
System.out.println(name);
}
//StringUtils 可以支持更多数据类型
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(3);
ids.add(5);
String str3 = StringUtils.join(ids, ",");
System.out.println(str3);
}
}
OUTPUT
Xiaohong,Xiaoming,Daming,Xiaohei
Xiaohong,Xiaoming,Daming,Xiaohei
Xiaohong
Xiaoming
Daming
Xiaohei
1,3,5
字符串转义
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.6</version>
</dependency>
import org.apache.commons.text.StringEscapeUtils;
public class EscapeString {
public static void main(String[] args) {
String str = "He didn't say, \"Stop!\"";
//转义
String escapedStr = StringEscapeUtils.escapeJava(str);
System.out.println("escape" + ":" + escapedStr);
//从转义字符串转回来
String str2 = StringEscapeUtils.unescapeJava(escapedStr);
System.out.println("unescape" + ":" + str2);
}
}
OUTPUT
escape:He didn't say, \"Stop!\"
unescape:He didn't say, "Stop!"
变量名字格式化(驼峰命名)
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0.1-jre</version>
</dependency>
import com.google.common.base.CaseFormat;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
public class GuavaUtil {
public static void main(String[] args) throws Exception {
List<Integer> list = new ArrayList<Integer>();
list.add(123);
list.add(456);
System.out.println(list);
//直接初始化List数组
List<Integer> integers = Lists.newArrayList(123, 456);
System.out.println(integers);
//拆分字符串,忽略空字符串
Iterable<String> split = Splitter.on(',')
.trimResults()
.omitEmptyStrings()
.split("123,321,, abc");
for (String s : split) {
System.out.println(s);
}
//对比常规方法拆分字符串,忽略空字符串
String a = "123,321,, abc";
String[] as = a.split(",");
for(int i=0;i<as.length;i++)
{
if(null == as[i] || as[i].length()<=0)
{
continue;
}
else
{
System.out.println(as[i].trim());
}
}
//驼峰命名
String s1 = "CONSTANT_NAME";
String s2 = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, s1);
System.out.println(s2); //constantName;
}
}
从字符串到输入流
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
public class String2InputStream {
public static void main(String[] args) {
//构造字符串列表
List<String> names = new LinkedList<String>();
names.add("Xiaohong");
names.add("Xiaoming");
names.add("Daming");
names.add("Xiaohei");
//合并为一个字符串,以逗号相连
String nameStr = String.join(",",names);
//将字符串作为默认的输入流
InputStream in = IOUtils.toInputStream(nameStr, Charsets.toCharset("UTF-8"));
//重置系统的输入流
System.setIn(in);
//模拟键盘输入 这也是OJ平台测试用例输入的原理
//此处也可以换成一个文件输入流
Scanner sc=new Scanner(System.in);
sc.useDelimiter(",");
while(sc.hasNext())
{
System.out.println(sc.next());
}
}
}
第四章 高级文件处理
4.1 XML DOM解析
可扩展标记语言:意义加数据
DOM/SAX/Stax是JDK自带的解析功能
xml解析方法
树结构:DOM 文档对象模型
流结构:SAX 流机制解析器(推)、Stax 流机制解析器(拉)
DOM方法
其处理方式是将 XML 整个作为类似树结构的方式读入内存中
解析大数据量的 XML 文件,会遇到内存泄露及程序崩溃的风险。
-
DocumentBuilder 解析类,parse方法
-
Node 节点主接口,getChildNodes返回一个NodeList
-
NodeList 节点列表,每个元素是一个Node
-
Document 文档根节点
-
Element 标签节点元素 (每一个标签都是标签节点)
-
Text节点 (包含在XML元素内的,都算Text节点)
-
Attr节点(每个属性节点)
读取dom方法一,自上而下解析:
用到的函数
getNodeName()
getTextContent()
getChildNodes()
Node.ELEMENT_NODE
usersList.item(i)
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(); //长度为1
//一级节点开始遍历
for (int i = 0; i < usersList.getLength(); i++)
{
Node users = usersList.item(i); //1 users
NodeList userList = users.getChildNodes(); //获取二级子节点user的列表,长度为9
//二级节点开始遍历
for (int j = 0; j < userList.getLength(); j++) //9
{
Node user = userList.item(j);
if (user.getNodeType() == Node.ELEMENT_NODE)
{
NodeList metaList = user.getChildNodes(); //长度为7
//三级节点开始遍历
for (int k = 0; k < metaList.getLength(); k++) //7
{
//到最后一级文本
Node meta = metaList.item(k);
if (meta.getNodeType() == Node.ELEMENT_NODE)
{
System.out.println(metaList.item(k).getNodeName()
+ ":" + metaList.item(k).getTextContent());
}
}
System.out.println();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
读取dom方法二,搜索:
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.getNodeName() + " = " + element.getTextContent());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
基于dom技术写xml文件:
public class DomWriter {
public static void main(String[] args) {
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();
//新创建一个Document节点
Document document = dbBuilder.newDocument();
if (document != null)
{
Element docx = document.createElement("document"); //都是采用Document创建元素
Element element = document.createElement("element");
element.setAttribute("type", "paragraph");
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); //把object节点挂在element下
docx.appendChild(element); //把element节点挂在docx下
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 (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
<document>
<element alignment="left" type="paragraph">
<object type="text">
<text>abcdefg</text>
<bold>true</bold>
</object>
</element>
</document>
4.2 SAX DOM解析
Simple API for XML
采用事件/流模型来解析 XML 文档,更快速、更轻量。
有选择的解析和访问,不像 DOM 加载整个文档,内存要求较低。
SAX 对 XML 文档的解析为一次性读取,不创建/不存储文档对象,很难同时访问文档中的多处数据。
推模型:当它每发现一个节点就引发一个事件,而我们需要编写这些事件的处理程序,defaulthandler
public class SAXReader {
public static void main(String[] args) throws SAXException, IOException {
XMLReader parser = XMLReaderFactory.createXMLReader();
BookHandler bookHandler = new BookHandler();
parser.setContentHandler(bookHandler);
parser.parse("books.xml");
System.out.println(bookHandler.getNameList());
}
}
//定义响应程序,必须继承DefaultHandler
class BookHandler extends DefaultHandler {
private List<String> nameList;
private boolean title = false;
public List<String> getNameList() {
return nameList;
}
// 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");
}
// 访问某一个元素,qname指每个尖括号里面的名字
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if (qName.equals("title")) { //要解析其他元素只需要改这里,比如把“title”改为“year”
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("Book title: " + bookTitle);
nameList.add(bookTitle);
}
}
}
4.3 Stax DOM解析
Streaming API for XML
流模型中的拉模型
在遍历文档时,会把感兴趣的部分从读取器中拉出,不需要引发事件,允许我们选择性地处理节点
两套处理API:
基于指针的API, XMLStreamReader
基于迭代器的API,XMLEventReader
方法一,流模式
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());
}
if ("year".equalsIgnoreCase(streamReader.getLocalName())) {
System.out.println("year:" + streamReader.getElementText());
}
}
}
streamReader.close();
} catch (XMLStreamException e) {
e.printStackTrace();
}
}
方法二,事件模式
有点没看懂
public static void readByEvent() {
String xmlFile = "books.xml";
XMLInputFactory factory = XMLInputFactory.newInstance();
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();
//System.out.print(start.getName().getLocalPart());
if(name.equals("title"))
{
titleFlag = true;
System.out.print("title:");
}
// 取得所有属性
Iterator attrs = start.getAttributes();
while (attrs.hasNext()) {
// 打印所有属性信息
Attribute attr = (Attribute) attrs.next();
//System.out.print(":" + attr.getName().getLocalPart() + "=" + attr.getValue());
}
//System.out.println();
}
//如果是正文
if(event.isCharacters())
{
String s = event.asCharacters().getData();
if(null != s && s.trim().length()>0 && titleFlag)
{
System.out.println(s.trim());
}
}
//如果事件对象是元素的结束
if(event.isEndElement())
{
EndElement end = event.asEndElement();
String name = end.getName().getLocalPart();
if(name.equals("title"))
{
titleFlag = false;
}
}
}
eventReader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (XMLStreamException e) {
e.printStackTrace();
}
}
4.4 JSON解析
是一种轻量级的数据交换格式,类似XML,更小、更快、更易解析
最早用于Javascript中,容易解析,最后推广到全语言
尽管使用Javascript语法,但是独立于编程语言
三个JSON处理的包:
-
org.json:JSON官方推荐的解析类
-
GSON:Google出品
-
Jackson:号称最快的JSON处理器
Java Bean对象:
-
具有一个无参的构造函数
-
可以包括多个属性,所有属性都是private
-
每个属性都有相应的Getter/Setter方法
OrgJson
- 从JSON里put和get数据
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文件里面读取数据并放到person对象里面
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<Book> bookList = new ArrayList<>();
for (Object book : books) {
//获取单个JSONObject对象
JSONObject bookObject = (JSONObject) book;
Book book1 = new Book();
book1.setAuthor(bookObject.getString("author"));
book1.setYear(bookObject.getString("year"));
book1.setTitle(bookObject.getString("title"));
book1.setPrice(bookObject.getInt("price"));
book1.setCategory(bookObject.getString("category"));
bookList.add(book1);
}
for(Book book:bookList)
{
System.out.println(book.getAuthor() + ", " + book.getTitle());
}
} catch (Exception e) {
e.printStackTrace();
}
}
//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
}
]
}
GSON
- 可以通过gson函数很方便的在java对象和json里面直接转换,不需要像org.json一样还要put来get去
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]
}
- 从json文件中解析数据输入到java对象
第五章 java多线程和并发线程
串行程序,程序只能在单核上运行,无法利用多个cpu
并行程序,程序可以利用多个计算核运行
5.1 多线程的生命周期
java多线程的创建
有两种方法
线程继承(extends)java.lang.Thread类,实现run方法
public class Thread1 extends Thread{
public void run()
{
System.out.println("hello");
}
public static void main(String[] a)
{
new Thread1().start();
}
}
线程实现(implement)java.lang.Runnable类,实现run方法
runnable是java四个主要接口之一:Clonable/Comparable/Serializable/Runnable
public class Thread2 implements Runnable{
public void run()
{
System.out.println("hello");
}
public static void main(String[] a)
{
new Thread(new Thread2()).start();
}
}
java多线程的启动
两种创建方式不同,启动方式也是不同的
多个线程启动,其启动的先后顺序是随机的
1.通过继承Thread类创建的线程
start方法,会自动以新线程调用run方法,请注意要是直接调用run,会变成串行执行
一个线程对象不能多次start,会报错,多个线程对象都start后,哪一个线程先执行完全由操作系统/JVM来主导
public class ThreadDemo1
{
public static void main(String args[]) throws Exception
{
new TestThread1().start();
while(true)
{
System.out.println("main thread is running");
Thread.sleep(1000);
}
}
}
class TestThread1 extends Thread
{
public void run()
{
while(true)
{
System.out.println(" TestThread1 is running");
try {
Thread.sleep(1000); //1000毫秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
2.通过实现Runnable类创建的线程
注意Runnable对象必须放在一个Thread类中才能运行,不能直接对一个Runnable对象执行start方法
public class ThreadDemo3
{
public static void main(String args[])
{
TestThread3 tt= new TestThread3();//创建TestThread类的一个实例
//new TestThread3().start();会报错
Thread t= new Thread(tt);//创建一个Thread类的实例
t.start();//使线程进入Runnable状态
while(true)
{
System.out.println("main thread is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class TestThread3 implements Runnable //extends Thread
{
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName() +
" is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
java多线程的结束
线程无需关闭,只要run执行完之后就自动结束了
main函数线程可能早于新线程结束,整个程序的终止是等所有线程都中止
5.2 多线程信息共享
粗粒度:子线程与子线程之间和main线程之间缺乏交流
细粒度:线程之间有信息交流通讯(通过共享变量达到信息共享,有两种方法:static变量/同一个Runnable类的成员变量)
static变量实例
public class ThreadDemo0
{
public static void main(String [] args)
{
new TestThread0().start();
new TestThread0().start();
new TestThread0().start();
new TestThread0().start();
}
}
class TestThread0 extends Thread
{
//private int tickets=100; //每个线程卖100张,没有共享
private static int tickets=100; //static变量是共享的,所有的线程共享
public void run()
{
while(true)
{
if(tickets>0)
{
System.out.println(Thread.currentThread().getName() +
" is selling ticket " + tickets);
tickets = tickets - 1;
}
else
{
break;
}
}
}
}
同一个Runnable类的成员变量实例
注意TestThread1只被创建了一次,就是t,而new Thread只是把同一个t包装成不同的线程对象启动
public class ThreadDemo1
{
public static void main(String [] args)
{
TestThread1 t=new TestThread1();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class TestThread1 implements Runnable
{
private int tickets=100;
public void run()
{
while(true)
{
if(tickets>0)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() +" is selling ticket " + tickets);
}
else
{
break;
}
}
}
}
多线程信息共享问题:
1.工作缓存副本
2.关键步骤缺乏加锁限制
缓存副本解决方法: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关键字修饰的代码块或函数,只能一个线程进入
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 = 10; // 多个 线程在共享的
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--);
}
}
}
//OUTPUT
Thread-0 is saling ticket 10
Thread-3 is saling ticket 9
Thread-2 is saling ticket 8
Thread-1 is saling ticket 7
Thread-1 is saling ticket 6
Thread-2 is saling ticket 5
Thread-3 is saling ticket 4
Thread-0 is saling ticket 3
Thread-3 is saling ticket 2
Thread-2 is saling ticket 1
5.3 多线程管理
线程的阻塞和唤醒
sleep,时间一到,自己会醒来
–wait/notify/notifyAll,等待,需要别人来唤醒
– join,等待另外一个线程结束
– interrupt,向另外一个线程发送中断信号,该线程收到信号,会
触发InterruptedException(可解除阻塞),并进行下一步处