面向对象设计实践(基于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包下载下来
运行程序
总结,构建工具的功能
一些常用的依赖文本
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项目
:star:记得在 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]+$" ; 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" ); String str1 = String.join("," , names); System.out.println(str1); String str2 = StringUtils.join(names, "," ); System.out.println(str2); List<String> names2 = Arrays.asList(str2.split("," )); for (String name:names2) { System.out.println(name); } 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,XiaoheiXiaohong ,Xiaoming,Daming,XiaoheiXiaohong 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<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); } }
从字符串到输入流 <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); 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 { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document document = db.parse("users.xml" ); NodeList usersList = document.getChildNodes(); for (int i = 0 ; i < usersList.getLength(); i++) { Node users = usersList.item(i); NodeList userList = users.getChildNodes(); for (int j = 0 ; j < userList.getLength(); j++) { Node user = userList.item(j); if (user.getNodeType() == Node.ELEMENT_NODE) { NodeList metaList = user.getChildNodes(); 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).getNodeName() + ":" + metaList.item(k).getTextContent()); } } System.out.println(); } } } } catch (Exception e) { e.printStackTrace(); } }
读取dom方法二,搜索:
public static void traverseBySearch () { try { 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 = dbBuilder.newDocument(); if (document != null ) { Element docx = document.createElement("document" ); Element element = document.createElement("element" ); element.setAttribute("type" , "paragraph" ); element.setAttribute("alignment" , "left" ); Element object = document.createElement("object" ); object.setAttribute("type" , "text" ); Element text = document.createElement("text" ); text.appendChild(document.createTextNode("abcdefg" )); Element bold = document.createElement("bold" ); bold.appendChild(document.createTextNode("true" )); object.appendChild(text); object.appendChild(bold); element.appendChild(object); docx.appendChild(element); document.appendChild(docx); 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); 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()); } }class BookHandler extends DefaultHandler { private List<String> nameList; private boolean title = false ; public List<String> getNameList () { return nameList; } 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 { 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)); 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.print("title:" ); } Iterator attrs = start.getAttributes(); while (attrs.hasNext()) { Attribute attr = (Attribute) attrs.next(); } } 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 obj = new JSONObject (); obj.put("name" , p.getName()); obj.put("age" , p.getAge()); obj.put("scores" , p.getScores()); 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)) { int fileLen = (int ) file.length(); char [] chars = new char [fileLen]; reader.read(chars); String s = String.valueOf(chars); JSONObject jsonObject = new JSONObject (s); JSONArray books = jsonObject.getJSONArray("books" ); List<Book> bookList = new ArrayList <>(); for (Object book : books) { 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" : [ { "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 )); Gson gson = new Gson (); String s = gson.toJson(p); System.out.println(s); Person p2 = gson.fromJson(s, Person.class); System.out.println(p2.getName()); System.out.println(p2.getAge()); System.out.println(p2.getScores()); JsonObject json = gson.toJsonTree(p).getAsJsonObject(); System.out.println(json.get("name" )); System.out.println(json.get("age" )); System.out.println(json.get("scores" )); }
从json文件中加载数据,变成一个对象数组,操作非常简单
public static void testJsonFile () { Gson gson = new Gson (); File file = new File ("books2.json" ); try (FileReader reader = new FileReader (file)) { List<Book> books = gson.fromJson(reader, new TypeToken <List<Book>>(){}.getType()); for (Book book : books) { System.out.println(book.getAuthor() + ", " + book.getTitle()); } } catch (Exception e) { e.printStackTrace(); } }
Jackson
java对象转换为json字符串
static void testJsonObject () throws IOException { ObjectMapper om = new ObjectMapper (); Person p = new Person (); p.setName("Tom" ); p.setAge(20 ); p.setScores(Arrays.asList(60 ,70 ,80 )); String jsonStr = om.writeValueAsString(p); System.out.println(jsonStr); Person p2 = om.readValue(jsonStr, Person.class); System.out.println(p2.getName()); System.out.println(p2.getAge()); System.out.println(p2.getScores()); JsonNode node = om.readTree(jsonStr); System.out.println(node.get("name" ).asText()); System.out.println(node.get("age" ).asText()); System.out.println(node.get("scores" )); }
从json文件里加载数据变成对象数组,非常简单
static void testJsonFile () throws IOException { ObjectMapper om = new ObjectMapper (); File json2 = new File ("books2.json" ); List<Book> books = om.readValue(json2, new TypeReference <List<Book>>(){}); for (Book book : books) { System.out.println(book.getAuthor()); System.out.println(book.getTitle()); } }
4.5 图形图像解析 图形:Graph 矢量图,根据几何特性来画的,比如点、直线、弧线等
图像:Image 由像素点组成
图像格式:jpg, png, bmp, svg, wmf, gif, tiff 等
图像颜色:RGB(Red, Green, Blue)
javax.imageio包:
public static void main (String[] args) throws Exception { readAndWrite(); readComparison(); cropImage("c:/temp/ecnu.jpg" , "c:/temp/shida.jpg" , 750 , 250 , 700 , 300 , "jpg" , "jpg" ); combineImagesHorizontally("c:/temp/ecnu.jpg" ,"c:/temp/ecnu.jpg" ,"jpg" , "c:/temp/ecnu2.jpg" ); combineImagesVertically("c:/temp/ecnu.jpg" ,"c:/temp/ecnu.jpg" ,"jpg" , "c:/temp/ecnu3.jpg" ); }
读和写图片 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" )); }
切割矩形 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(); } }
效果如下:
横向拼接 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(); 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); imageNew.setRGB(0 ,0 ,width1,height1,firstRGB,0 ,width1); imageNew.setRGB(width1,0 ,width2,height2,secondRGB,0 ,width2); ImageIO.write(imageNew, imageFormat, new File (toPath)); } catch (Exception e) { e.printStackTrace(); } }
效果如下
纵向拼接 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(); 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); imageNew.setRGB(0 ,0 ,width1,height1,firstRGB,0 ,width1); imageNew.setRGB(0 ,height1,width2,height2,secondRGB,0 ,width2); ImageIO.write(imageNew, imageFormat, new File (toPath)); } catch (Exception e) { e.printStackTrace(); } }
应用 验证码的生成 题目要求
public class ValidateCodeTest { 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("c:/temp/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 ); 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; int codeY; codeX = (width - 4 ) / (codeCount + 1 ); fontHeight = height - 10 ; codeY = height - 7 ; Font font = new Font ("Fixedsys" , Font.PLAIN, fontHeight); gd.setFont(font); 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)); } }
应用 统计图生成 包括柱状图、饼图、折线图
public static void main (String[] args) { writeBar("c:/temp/bar.jpg" ); writePie("c:/temp/pie.jpg" ); writeLine("c:/temp/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 , "" , "第四季度" ); 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(); } }
4.6 条形码和二维码解析 两个处理二维码的第三方库
只要我们把内容提供给zebra crossing,他就会自动生成好二维码,也可以解析
下面是生成和解析百度网址二维码的例子,我们需要改动的只有file name和content内容
public class QRCodeTest { private static int WIDTH = 300 ; private static int HEIGHT = 300 ; private static String FORMAT = "png" ; public static void generateQRCode (File file, String content) { Map<EncodeHintType, Object> hints = new HashMap <>(); hints.put(EncodeHintType.CHARACTER_SET, "utf-8" ); hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); hints.put(EncodeHintType.MARGIN, 2 ); try { BitMatrix bitMatrix = new MultiFormatWriter ().encode(content, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, hints); Path path = file.toPath(); MatrixToImageWriter.writeToPath(bitMatrix, FORMAT, path); } catch (Exception e) { e.printStackTrace(); } } public static void readQrCode (File file) { MultiFormatReader reader = new MultiFormatReader (); try { BufferedImage image = ImageIO.read(file); BinaryBitmap binaryBitmap = new BinaryBitmap (new HybridBinarizer (new BufferedImageLuminanceSource (image))); Map<DecodeHintType, Object> hints = new HashMap <>(); hints.put(DecodeHintType.CHARACTER_SET, "utf-8" ); Result result = reader.decode(binaryBitmap, hints); System.out.println("解析结果:" + result.toString()); System.out.println("二维码格式:" + result.getBarcodeFormat()); System.out.println("二维码文本内容:" + result.getText()); } catch (Exception e) { e.printStackTrace(); } } public static void main (String[] args) { generateQRCode(new File ("2dcode.png" ), "https://www.baidu.com" ); readQrCode(new File ("2dcode.png" )); } }
第二个第三方库barcode4j
它只能生成二维码,不能解析二维码
另外,要想使用这两个包,必须在pom.xml里面引入配置文件
<dependency > <groupId > com.google.zxing</groupId > <artifactId > core</artifactId > <version > 3.3.3</version > </dependency > <dependency > <groupId > com.google.zxing</groupId > <artifactId > javase</artifactId > <version > 3.3.3</version > </dependency > <dependency > <groupId > net.sf.barcode4j</groupId > <artifactId > barcode4j</artifactId > <version > 2.1</version > </dependency > <dependency > <groupId > org.apache.avalon.framework</groupId > <artifactId > avalon-framework-api</artifactId > <version > 4.3.1</version > </dependency >
4.7 DOCX文件解析 word2003以前文档格式是doc,文档格式不公开
word2007以后文档格式是docx,遵循xml路线,文档格式公开
常见功能:docx解析,docx生成
java没办法处理docx,用到的第三方库,我们主要用到POI
POI是一个纯java的工具包,不需要引入第三方的依赖
这一节的例子:
读取docx文件的text
读取docx文件里的图片
写入图片
读取docx文件里的表格
将数据写入docx文件的表格里
文档模板替换功能,套打功能
就是把文件里占位符换成真实的数据
4.8 表格文件的处理 xls和xlxs文件:sheet,行,列,单元格
csv文件
用到的第三方包:POI
写xlsx文件 public static void writeXLSXFile () throws IOException { String excelFileName = "Test.xlsx" ; String sheetName = "Sheet1" ; XSSFWorkbook wb = new XSSFWorkbook (); XSSFSheet sheet = wb.createSheet(sheetName); for (int r = 0 ; r < 5 ; r++) { XSSFRow row = sheet.createRow(r); for (int c = 0 ; c < 5 ; c++) { XSSFCell cell = row.createCell(c); cell.setCellValue("Cell " + r + " " + c); } } FileOutputStream fileOut = new FileOutputStream (excelFileName); wb.write(fileOut); fileOut.flush(); fileOut.close(); }
读xlsx文件 public static void readXLSXFile () throws IOException { InputStream ExcelFileToRead = new FileInputStream ("Test.xlsx" ); XSSFWorkbook wb = new XSSFWorkbook (ExcelFileToRead); XSSFSheet sheet = wb.getSheetAt(0 ); XSSFRow row; XSSFCell cell; Iterator rows = sheet.rowIterator(); while (rows.hasNext()) { row = (XSSFRow) rows.next(); Iterator cells = row.cellIterator(); while (cells.hasNext()) { cell = (XSSFCell) cells.next(); if (cell.getCellType() == XSSFCell.CELL_TYPE_STRING) { System.out.print(cell.getStringCellValue() + " " ); } else if (cell.getCellType() == XSSFCell.CELL_TYPE_NUMERIC) { System.out.print(cell.getNumericCellValue() + " " ); } else { } } System.out.println(); } }
读csv文件 以逗号分隔的文本文件,可以用excel打开变成表格
根据索引值获取csv文件数据
public static void readCSVWithIndex () throws Exception { Reader in = new FileReader ("c:/temp/score.csv" ); Iterable<CSVRecord> records = CSVFormat.EXCEL.parse(in); for (CSVRecord record : records) { System.out.println(record.get(0 )); } }
根据列名来获取列,在读入的时候给每个列取个名字
public static void readCSVWithName () throws Exception { Reader in = new FileReader ("c:/temp/score.csv" ); Iterable<CSVRecord> records = CSVFormat.RFC4180.withHeader("Name" , "Subject" , "Score" ).parse(in); for (CSVRecord record : records) { System.out.println(record.get("Subject" )); } }
写csv文件 public static void writeCSV () throws Exception { try (CSVPrinter printer = new CSVPrinter (new FileWriter ("person.csv" ), CSVFormat.EXCEL)) { printer.printRecord("id" , "userName" , "firstName" , "lastName" , "birthday" ); printer.printRecord(1 , "john73" , "John" , "Doe" , LocalDate.of(1973 , 9 , 15 )); printer.println(); printer.printRecord(2 , "mary" , "Mary" , "Meyer" , LocalDate.of(1985 , 3 , 29 )); } catch (IOException ex) { ex.printStackTrace(); } }
4.9 PDF文件的处理 portable document 便携式文档格式
抽取pdf的文本 public static void main (String[] args) { File pdfFile = new File ("simple.pdf" ); PDDocument document = null ; try { document=PDDocument.load(pdfFile); AccessPermission ap = document.getCurrentAccessPermission(); if (!ap.canExtractContent()) { throw new IOException ("你没有权限抽取文本" ); } int pages = document.getNumberOfPages(); PDFTextStripper stripper=new PDFTextStripper (); stripper.setSortByPosition(true ); stripper.setStartPage(1 ); stripper.setEndPage(pages); String content = stripper.getText(document); System.out.println(content); } catch (Exception e) { System.out.println(e); } }
写pdf public static void main (String[] args) { createHelloPDF(); }public static void createHelloPDF () { PDDocument doc = null ; PDPage page = null ; try { doc = new PDDocument (); page = new PDPage (); doc.addPage(page); PDFont font = PDType1Font.HELVETICA_BOLD; PDPageContentStream content = new PDPageContentStream (doc, page); content.beginText(); content.setFont(font, 12 ); content.moveTextPositionByAmount(100 , 700 ); content.showText("hello world" ); content.endText(); content.close(); doc.save("test.pdf" ); doc.close(); } catch (Exception e) { System.out.println(e); } }
合并两个pdf private static final Log LOG = LogFactory.getLog(NewMergePdfs.class);public static void main (String[] a) throws Exception { merge(); }public static void merge () throws Exception { FileOutputStream fos = new FileOutputStream (new File ("merge.pdf" )); ByteArrayOutputStream mergedPDFOutputStream = null ; File file1 = new File ("sample1.pdf" ); File file2 = new File ("sample2.pdf" ); List<InputStream> sources = new ArrayList <InputStream>(); try { sources.add(new FileInputStream (file1)); sources.add(new FileInputStream (file2)); mergedPDFOutputStream = new ByteArrayOutputStream (); PDFMergerUtility pdfMerger = new PDFMergerUtility (); pdfMerger.addSources(sources); pdfMerger.setDestinationStream(mergedPDFOutputStream); PDDocumentInformation pdfDocumentInfo = new PDDocumentInformation (); pdfMerger.setDestinationDocumentInformation(pdfDocumentInfo); pdfMerger.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly()); fos.write(mergedPDFOutputStream.toByteArray()); fos.close(); } catch (Exception e) { throw new IOException ("PDF merge problem" , e); } finally { for (InputStream source : sources) { IOUtils.closeQuietly(source); } IOUtils.closeQuietly(mergedPDFOutputStream); IOUtils.closeQuietly(fos); } }
删除一页pdf public static void main (String[] args) throws Exception { File file = new File ("merge.pdf" ); PDDocument document = PDDocument.load(file); int noOfPages = document.getNumberOfPages(); System.out.println("total pages: " + noOfPages); document.removePage(1 ); System.out.println("page removed" ); document.save("merge2.pdf" ); document.close(); }
XdocReport类 主要用途是将docx文档转换为pdf/html….
public class XDocReportTest { public static void main (String[] args) throws Exception { XWPFDocument doc = new XWPFDocument (new FileInputStream ("template.docx" )); PdfOptions options = PdfOptions.create(); options.fontProvider(new IFontProvider () { public Font getFont (String familyName, String encoding, float size, int style, Color color) { try { BaseFont bfChinese = BaseFont.createFont( "C:\\Program Files (x86)\\Microsoft Office\\root\\VFS\\Fonts\\private\\STSONG.TTF" , BaseFont.IDENTITY_H, BaseFont.EMBEDDED); Font fontChinese = new Font (bfChinese, size, style, color); if (familyName != null ) fontChinese.setFamily(familyName); return fontChinese; } catch (Throwable e) { e.printStackTrace(); return ITextFontRegistry.getRegistry().getFont(familyName, encoding, size, style, color); } } }); PdfConverter.getInstance().convert(doc, new FileOutputStream ("template.pdf" ), options); } }
第五章 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 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }
2.通过实现Runnable类创建的线程
注意Runnable对象必须放在一个Thread类中才能运行,不能直接对一个Runnable对象执行start方法
public class ThreadDemo3 { public static void main (String args[]) { TestThread3 tt= new TestThread3 (); Thread t= new Thread (tt); t.start(); while (true ) { System.out.println("main thread is running" ); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }class TestThread3 implements Runnable { 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 static int tickets=100 ; 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 { volatile boolean flag = true ; 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--); } } }
5.3 多线程管理
线程的阻塞和唤醒
sleep ,时间一到,自己会醒来 –wait /notify/notifyAll,等待,需要别人来唤醒 – join ,等待另外一个线程结束 – interrupt,向另外一个线程发送中断信号,该线程收到信号,会 触发InterruptedException(可解除阻塞),并进行下一步处
5.5 java并发框架 executor java并行模式
线程组的管理,就是有很多的线程类的时候,怎么把他放在一起进行管理
线程组如何管理线程
但线程组并没有解决高耦合的问题,所以我们引入了框架
下面看代码
main函数 里就是两件事,创建服务器,创建100个任务
现在可以不知道这个任务具体指什么,但我们先创建出来
public class Main { public static void main (String[] args) throws InterruptedException { Server server=new Server (); for (int i=0 ; i<100 ; i++){ Task task=new Task ("Task " +i); Thread.sleep(10 ); server.submitTask(task); } server.endServer(); } }
服务器端
submittask是我们外界创建任务时调用的,向线程池提交任务
其中executor.execute(task); //执行
public class Server { private ThreadPoolExecutor executor; public Server () { executor=(ThreadPoolExecutor)Executors.newCachedThreadPool(); } public void submitTask (Task task) { System.out.printf("Server: A new task has arrived\n" ); executor.execute(task); System.out.printf("Server: Pool Size: %d\n" ,executor.getPoolSize()); System.out.printf("Server: Active Count: %d\n" ,executor.getActiveCount()); System.out.printf("Server: Completed Tasks: %d\n" ,executor.getCompletedTaskCount()); } public void endServer () { executor.shutdown(); } }
任务端
主要的任务代码写在这里
package executor.example1;import java.util.Date;import java.util.concurrent.TimeUnit;public class Task implements Runnable { private String name; public Task (String name) { this .name=name; } public void run () { try { Long duration=(long )(Math.random()*1000 ); System.out.printf("%s: Task %s: Doing a task during %d seconds\n" ,Thread.currentThread().getName(),name,duration); Thread.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("%s: Task %s: Finished on: %s\n" ,Thread.currentThread().getName(),name,new Date ()); } }
再看一个复杂的例子
0-1000的数求和
分成10个任务,线程池固定只有4个线程
public class SumTest { public static void main (String[] args) { ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newFixedThreadPool(4 ); List<Future<Integer>> resultList=new ArrayList <>(); 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); } 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(); } }
任务类
继承了callable的方法,不同的是把run()写为call(),并且call()是有返回值的
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; } }
5.6 Fork-join 这是一种分治编程
下面例子用fork-join来求一个数组和
sumtask实现了RecursiveTask,一个可递归的任务,必须实现compute()方法
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 { 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; } }
主程序
public class SumTest { public static void main (String[] args) throws ExecutionException, InterruptedException { ForkJoinPool pool = new ForkJoinPool (); 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()); } }
5.9 定时任务执行 固定某个时间点执行,或者周期性执行、
简单定时机制 executor+定时器 executeAtFixTime() 在指定时间点启动
executeFixedRate() 每三秒钟执行一次
executeFixedDelay() 跟上个函数差不多,不同的是从上个任务结束时间开始算三秒
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 { executeFixedDelay(); } public static void executeAtFixTime () throws Exception { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1 ); executor.schedule( new MyTask (), 1 , TimeUnit.SECONDS); Thread.sleep(20000 ); executor.shutdown(); } public static void executeFixedRate () throws Exception { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1 ); executor.scheduleAtFixedRate( new MyTask (), 1 , 3000 , TimeUnit.MILLISECONDS); Thread.sleep(20000 ); executor.shutdown(); } public static void executeFixedDelay () throws Exception { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1 ); executor.scheduleWithFixedDelay( new MyTask (), 1 , 3000 , TimeUnit.MILLISECONDS); Thread.sleep(20000 ); executor.shutdown(); } }class MyTask implements Runnable { public void run () { System.out.println("时间为:" + new Date ()); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } }
Quartz 有trigger这个概念,可以控制执行次数
第六章 网络编程 6.1 网络基础 每个计算设备上都有若干个网卡,每个网卡上有(全球唯一)单独的硬件地址,MAC地址
IP地址:每个网卡/机器都有一个或多个IP地址
Windows平台ipconfig, Linux/Mac平台ifconfig:查询ip地址
在Windows/Linux/Mac上都可以通过netstat -an:查询端口
公网(万维网/互联网)和内网(局域网):网络是分层的,最外层是公网/互联网,底下的每层都是内网
ip地址可以在每个层次的网重用,tracert 看当前机器 和目标机器的访问中继
6.2 java UDP协议 计算机通讯:数据从一个IP的port出发(发送方),运输到另外一个IP的port(接收方)
无连接无状态的通讯协议:发送方发送消息,如果接收方刚好在目的地,则可以接受。如果不在,那这个消息就丢失了,发送方也无法得知是否发送成功
UDP的好处就是简单,节省,经济
UDPsender 发送信息给指定端口
先定义管道DatagramSocket,再定义集装箱DatagramPacket
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(); } }
UDPrecv 同样是先定义管道DatagramSocket,再定义集装箱DatagramPacket
从12行开始等待信息,接受到之后再发送一条信息出去
import java.net.*;public class UdpRecv { public static void main (String[] args) throws Exception { DatagramSocket ds=new DatagramSocket (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(); } }
在eclipse运行结果
在命令行运行结果
6.3 java TCP协议 ①服务器:创建一个ServerSocket,等待连接
②客户机:创建一个Socket,连接到服务器
③服务器:ServerSocket接收到连接,创建一个Socket和客户的 Socket建立专线连接,后续服务器和客户机的对话(这一对Socket) 会在一个单独的线程(服务器端)上运行
④服务器的ServerSocket继续等待连接,返回①
用到的两个类
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 服务端 可以接纳多个客户端,每一个客户端来了之后就会发送一个hello client
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(); } } catch (Exception e) { e.printStackTrace(); } } }
其中worker是一个实现了runnable接口的并发类
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" ; System.out.println("server said:" + strWord + "---->" + strEcho); dos.writeBytes(strWord + "---->" + strEcho + System.getProperty("line.separator" )); } br.close(); dos.close(); s.close(); } catch (Exception e) { e.printStackTrace(); } } }
命令行运行结果
6.4 HTTP 网页是特殊的网络服务(HTTP, Hypertext Transfer Protocol)
步骤一 在浏览器输入URL地址
步骤二 浏览器将连接到远程服务器上(IP+80Port)
步骤三 请求下载一个HTML文件下来,放到本地临时文件夹中
步骤四 在浏览器显示出来
Java HTTP编程 (java.net包) 支持模拟成浏览器的方式去访问网页
URLConnectionGet 以get的方式访问百度,希望拿下百度首页的内容
java能拿到的都是html源代码,不能渲染成漂亮的网页
import java.io.*;import java.net.*;import java.util.*;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(); 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(); } } }
URLConnectionPost 去一个联邦快递查询右边的网站
在代码中将信息填写进网站的表单,然后post,就完成了对这个网页的提交访问
import java.io.*;import java.net.*;import java.nio.file.*;import java.util.*;public class URLConnectionPostTest { public static void main (String[] args) throws IOException { String urlString = "https://tools.usps.com/go/ZipLookupAction.action" ; Object userAgent = "HTTPie/0.9.2" ; Object redirects = "1" ; CookieHandler.setDefault(new CookieManager (null , CookiePolicy.ACCEPT_ALL)); Map<String, String> params = new HashMap <String, String>(); params.put("tAddress" , "1 Market Street" ); params.put("tCity" , "San Francisco" ); params.put("sState" , "CA" ); String result = doPost(new URL (urlString), params, userAgent == null ? null : userAgent.toString(), redirects == null ? -1 : Integer.parseInt(redirects.toString())); System.out.println(result); } public static String doPost (URL url, Map<String, String> nameValuePairs, String userAgent, int redirects) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); if (userAgent != null ) connection.setRequestProperty("User-Agent" , userAgent); if (redirects >= 0 ) connection.setInstanceFollowRedirects(false ); connection.setDoOutput(true ); try (PrintWriter out = new PrintWriter (connection.getOutputStream())) { boolean first = true ; for (Map.Entry<String, String> pair : nameValuePairs.entrySet()) { if (first) { first = false ; } else { out.print('&' ); } String name = pair.getKey(); String value = pair.getValue(); out.print(name); out.print('=' ); out.print(URLEncoder.encode(value, "UTF-8" )); } } String encoding = connection.getContentEncoding(); if (encoding == null ) { encoding = "UTF-8" ; } if (redirects > 0 ) { int responseCode = connection.getResponseCode(); System.out.println("responseCode: " + responseCode); if (responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_SEE_OTHER) { String location = connection.getHeaderField("Location" ); if (location != null ) { URL base = connection.getURL(); connection.disconnect(); return doPost(new URL (base, location), nameValuePairs, userAgent, redirects - 1 ); } } } else if (redirects == 0 ) { throw new IOException ("Too many redirects" ); } StringBuilder response = new StringBuilder (); try (Scanner in = new Scanner (connection.getInputStream(), encoding)) { while (in.hasNextLine()) { response.append(in.nextLine()); response.append("\n" ); } } catch (IOException e) { InputStream err = connection.getErrorStream(); if (err == null ) throw e; try (Scanner in = new Scanner (err)) { response.append(in.nextLine()); response.append("\n" ); } } return response.toString(); } }
6.5 HttpClient 两个包
java.net.http包,jdk自带的
JDKHttpClientGet public class JDKHttpClientGetTest { public static void main (String[] args) throws IOException, InterruptedException { doGet(); } public static void doGet () { try { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder(URI.create("http://www.baidu.com" )).build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); } catch (Exception e) { e.printStackTrace(); } } }
JDKHttpClientPost public class JDKHttpClientPostTest { public static void main (String[] args) throws IOException, InterruptedException { doPost(); } public static void doPost () { try { HttpClient client = HttpClient.newBuilder().build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://tools.usps.com/go/ZipLookupAction.action" )) .header("User-Agent" , "HTTPie/0.9.2" ) .header("Content-Type" ,"application/x-www-form-urlencoded;charset=utf-8" ) .POST(HttpRequest.BodyPublishers.ofString("tAddress=" + URLEncoder.encode("1 Market Street" , "UTF-8" ) + "&tCity=" + URLEncoder.encode("San Francisco" , "UTF-8" ) + "&sState=CA" )) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); System.out.println(response.headers()); System.out.println(response.body().toString()); } catch (Exception e) { e.printStackTrace(); } } }
6.9 邮件
邮件客户端:Foxmail、OutLook(Express, Microsoft Outlook)、Thunderbird (linux平台)
邮件服务端:Microsoft Exchange Server、IBM Lotus Notes、SendMail, Qmail, James
主要协议(发送端口25, 接收端口110)
发送, SMTP, Simple Mail Transfer Protocol
接收, Pop3, Post Office Protocol 3, (POP)
接收, IMAP, Internet Message Access Protocol, IMAP4
在正式开发之前,需要去邮箱里面确认
添加依赖
<dependencies > <dependency > <groupId > com.sun.mail</groupId > <artifactId > javax.mail</artifactId > <version > 1.6.2</version > </dependency > </dependencies >
MailClientRecv package tools;import javax.mail.*;import javax.mail.internet.*;import javax.activation.*;import java.util.*;public class MailClientRecv { private Session session; private Store store; private String username = "13896796126@163.com" ; private String password = " " ; private String popServer = "pop.163.com" ; public void init () throws Exception { Properties props = new Properties (); props.put("mail.store.protocol" , "pop3" ); props.put("mail.imap.class" , "com.sun.mail.imap.IMAPStore" ); props.put("mail.pop3.class" , "com.sun.mail.pop3.POP3Store" ); session = Session.getInstance(props,null ); session.setDebug(false ); store = session.getStore("pop3" ); store.connect(popServer,username,password); } public void receiveMessage () throws Exception { String folderName = "inbox" ; Folder folder=store.getFolder(folderName); if (folder==null ) { throw new Exception (folderName+"邮件夹不存在" ); } folder.open(Folder.READ_ONLY); System.out.println("您的收件箱有" +folder.getMessageCount()+"封邮件." ); System.out.println("您的收件箱有" +folder.getUnreadMessageCount()+"封未读的邮件." ); Message[] messages=folder.getMessages(); for (int i=1 ;i<=3 ;i++) { System.out.println("------第" +i+"封邮件-------" ); Message message = messages[i]; System.out.println((message.getFrom())[0 ]); System.out.println(message.getSubject()); System.out.println(); } folder.close(false ); } public void close () throws Exception { store.close(); } public static void main (String[] args) throws Exception { MailClientRecv client=new MailClientRecv (); client.init(); client.receiveMessage(); client.close(); } }
运行结果
MailClientSend package tools;import javax.mail.*;import java.util.*;import messages.*;public class MailClientSend { private Session session; private Transport transport; private String username = "zhangcaiyii@163.com" ; private String password = "ZCY929weilaikeqi" ; private String smtpServer = "smtp.163.com" ; public void init () throws Exception { Properties props = new Properties (); props.put("mail.transport.protocol" , "smtp" ); props.put("mail.smtp.class" , "com.sun.mail.smtp.SMTPTransport" ); props.put("mail.smtp.host" , smtpServer); props.put("mail.smtp.port" , "25" ); props.put("mail.smtp.auth" , "true" ); session = Session.getInstance(props,new Authenticator (){ public PasswordAuthentication getPasswordAuthentication () { return new PasswordAuthentication (username, password); } }); session.setDebug(true ); transport = session.getTransport(); } public void sendMessage () throws Exception{ Message msg = AttachmentMessage.generate(); transport.connect(); transport.sendMessage(msg, msg.getAllRecipients()); System.out.println("邮件已经成功发送" ); } public void close () throws Exception { transport.close(); } public static void main (String[] args) throws Exception { MailClientSend client=new MailClientSend (); client.init(); client.sendMessage(); client.close(); } }
发送文本右键 TextMessage
public class TextMessage { public static MimeMessage generate () throws Exception { String from = "lychen@sei.ecnu.edu.cn " ; String to = "chenliangyu1980@126.com" ; String subject = "test" ; String body = "您好,这是来自一封chenliangyu的测试邮件" ; Session session = Session.getDefaultInstance(new Properties ()); MimeMessage message = new MimeMessage (session); message.setFrom(new InternetAddress (from)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); message.setSentDate(new Date ()); message.setSubject(subject); message.setText(body); message.saveChanges(); return message; } }
发送html邮件 HTMLMessage
public class HtmlMessage { public static MimeMessage generate () throws Exception { String from = "lychen@sei.ecnu.edu.cn " ; String to = "chenliangyu1980@126.com" ; String subject = "HTML邮件" ; String body = "<a href=http://www.ecnu.edu.cn>" + "<h4>欢迎大家访问我们的网站</h4></a></br>" + "<img src=\"https://news.ecnu.edu.cn/_upload/article/images/2e/e2/6b554d034c9192101208c732195e/16a6ec66-6729-4469-a5f4-0435e0f2e66a.jpg\">" ; Session session = Session.getDefaultInstance(new Properties ()); MimeMessage message = new MimeMessage (session); message.setFrom(new InternetAddress (from)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); message.setSentDate(new Date ()); message.setSubject(subject); message.setContent(body, "text/html;charset=gb2312" ); message.saveChanges(); return message; } }
第七章 数据库编程 7.2 JDBC基本操作 java和数据库是两套平行的系统,要想连接需要借助jdbc(主流)
jdbc sql连接操作
Select 首先添加mysql依赖
<dependencies > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.25</version > </dependency > </dependencies >
注意一个大坑,配置文件里mysql-connector-jar和本地mysql的版本一定要一致!否则会报错
该代码可以直接用
要改的地方只有数据库名,用户名密码,select语句,还有输出是get的数据类型
import java.sql.*;public class SelectTest { public static void main (String[] args) { try { Class.forName("com.mysql.jdbc.Driver" ); System.out.println("注册驱动成功!" ); }catch (ClassNotFoundException e1){ System.out.println("注册驱动失败!" ); e1.printStackTrace(); return ; } String url="jdbc:mysql://localhost:3306/blog?useSSL=false" ; Connection conn = null ; try { conn = DriverManager.getConnection(url, "root" , " " ); Statement stmt = conn.createStatement(); System.out.println("创建Statement成功!" ); ResultSet rs = stmt.executeQuery("select id, name, age from t_person order by id" ); while (rs.next()) { System.out.println(rs.getInt(1 ) + "," + rs.getString(2 ) + "," + rs.getInt("age" )); } rs.close(); stmt.close(); } catch (SQLException e){ e.printStackTrace(); } finally { try { if (null != conn) { conn.close(); } } catch (SQLException e){ e.printStackTrace(); } } } }
打开navicat,连接到服务器,并创建表 t_person
运行结果
Update 该代码跟select唯一不同的就是,执行语句一个是stmt.executeQuery(sql),一个是stmt.executeUpdate(sql)
也可以直接用,用的时候改改几个sql语句,数据库名字,用户名密码就好
import java.sql.*;public class UpdateTest { public static void main (String[] args) { executeUpdate("update t_person set age = 40 where id = 316" ); executeUpdate("insert into t_person(id, name, age) values(320, 'xuexiaonan', 22)" ); executeUpdate("delete from t_person where id = 318" ); } public static void executeUpdate (String sql) { try { Class.forName("com.mysql.cj.jdbc.Driver" ); System.out.println("注册驱动成功!" ); }catch (ClassNotFoundException e1){ System.out.println("注册驱动失败!" ); e1.printStackTrace(); } String url="jdbc:mysql://localhost:3306/blog?useSSL=false" ; Connection conn = null ; try { conn = DriverManager.getConnection(url, "root" , "zcy123456" ); Statement stmt = conn.createStatement(); System.out.println("创建Statement成功!" ); int result = stmt.executeUpdate(sql); stmt.close(); } catch (SQLException e){ e.printStackTrace(); } finally { try { if (null != conn) { conn.close(); } } catch (SQLException e){ e.printStackTrace(); } } } }
运行结果
7.3 JDBC高级特性 addbsh preparedStatement 使用问号来代替字符串拼接
7.4 数据库连接池
数据库连接池
主程序
import java.sql.*;public class SelectTest { public static void main (String[] args) { Connection conn = null ; try { conn = DruidFactory2.getConnection(); Statement stmt = conn.createStatement(); System.out.println("创建Statement成功!" ); ResultSet rs = stmt.executeQuery("select id, name, age from t_person order by id" ); while (rs.next()) { System.out.println(rs.getInt(1 ) + "," + rs.getString(2 ) + "," + rs.getInt("age" )); } rs.close(); stmt.close(); } catch (Exception e){ e.printStackTrace(); } finally { try { if (null != conn) { conn.close(); } } catch (SQLException e){ e.printStackTrace(); } } } }
c3p0 引入依赖
<dependency > <groupId > com.mchange</groupId > <artifactId > c3p0</artifactId > <version > 0.9.5.2</version > </dependency >
c3p0是管理数据库连接的,它不能代替数据库连接