设计理论网站,炫酷个人主页源码,昆明电子商务网站建设,做房产中介需要有内部网站吗一、回顾#x1f49b; 谈谈volatile关键字用法 volatile能够保证内存可见性#xff0c;会强制从主内存中读取数据#xff0c;此时如果其他线程修改被volatile修饰的变量#xff0c;可以第一时间读取到最新的值。 二、#x1f499; HashMap线程不安全没有锁,HashTable线程…一、回顾 谈谈volatile关键字用法 volatile能够保证内存可见性会强制从主内存中读取数据此时如果其他线程修改被volatile修饰的变量可以第一时间读取到最新的值。 二、 HashMap线程不安全没有锁,HashTable线程更加安全关键方法都提供了synchronizedCocurrrentHashMap是线程安全的hash表 HashMap是在方法中直接加上synchronized就相当于针对this当前对象加锁 HashTable——全局锁会安全但是缺点就是有巨大的锁开销会形成阻塞等待 HashTableString,Stringht......; hs.set(aaa,111) 任意的针对ht对象的操作都会涉及针对this的加锁此时如果多个线程想操作ht就一定会触发激烈的锁竞争最后都只能一个一个排着队依次执行——并发 所以会出现CocurrentHashMap 我们学过的哈希表的二次探测真实hash表基本不会出现而是采用链表的方式哈希桶如果修改操作是像下图这样针对两个不同的链表进行修改是否存在线程安全问题-当然不会 但是假如说是扩容就可能会有影响了但是扩容是很有重量级的操作把整个的哈希表都需要重新搬运一遍这样锁的开销也就微乎其微。 那么有人也会问-假如上面那种是不是说明这种我就完全不需要加锁了反正也是没有线程安全问题但是也不行因为下图这种假如说去插入两个在同一个链表的位置又会涉及到线程的安全问题——解决方法每个链表都安排一把锁-这样开销就会小很多这样第二个操作就会陷入阻塞等待因为第一个正在修改这样这个问题就解除了。 一个hash表上链表个数这么多两个线程正好在同时修改一个链表的操作本身就概率比较低整体锁的开销大大降低了这么修改也就不会有这么多的线程阻塞。 此时可能此时有人问该怎么给每个线程加锁呢——由于synchronized随意对象都可以加锁所以可以简单使用每个链表的头节点使用。 这也就是我们的改进 1.ConcurrentHashMap减小了锁的粒度每个链表有一把锁大部分情况下都没有涉及锁冲突。 2.广泛使用了CAS操作size这样的操作也不会存在锁冲突 3.写操作进行了加锁链表级读操作不加锁了就——如果有一个线程读一个线程写会有问题吗最多就是修改的一瞬间读到一个旧版本/新版本的数据不确定而已通过一些精密的操作保证不会读“半个数据”有新有旧 4.针对扩容操作进行优化渐进式扩容 HashTable一旦触发扩容就会一口气完成所有元素的搬运这个过程非常耗时间我们的大部分请求会很顺畅但是突然一个请求会卡很久——而且并不好确认哪里的问题因为这个触发概率很小很气。 所以这个改进——化整为零当需要扩容时创建一个更大的数据然后把旧的数据逐渐往新的数据哈桑搬运会出现一段时间——新旧数组共同存在的时间—— 1.新增数据就往新数组上插入 2.删除数据把旧的数组上的数据删除掉即可 3.查找元素新旧数据都要查 4.修改元素统一把这个元素放到新的数组上 与此同时每个操作都会触发一定到搬运每搬运一次就可以保证整体时间不是很大积少成多之后逐渐完成搬运了也就可以把之前的旧的数组销毁了 上面说的是HashTable和ConcurrentHashMap的区别 HashMap和ConcurrentHashMap的区别就是线程安全和不安全的区别。 treeMap和hashMap区别——哈希表和红黑树之间的区别 面试有可能问一手 ConcurrentHashMap的分段锁 Java8之前ConcurrentHashMap的分段锁区别 Java8之后就没有分段锁了 ConcurrentHashMap的分段锁确实可以提高效率但是不如一个链表一把锁效率更高而且分段锁的维护更加麻烦。 三、 文件IO操作 文件-存储数据的方式 操作系统通过“文件系统”这样的模块来管理硬盘。 文件不同的文件系统管理文件的方式都是一致的 通过“目录”构成了N叉树的文件结构 如D盘-tmp-cat.jpg通过这个路线就可以找到确定电脑的唯一一个文件这个路线就叫做路径。 以盘符开头的路径也叫做绝对路径绝对路径是从电脑这里出发找文件的过程。 以···或···开头的路径叫做绝对路径相对路径需要有一个基准目录/工作目录表示从这个基准目录出发怎么才能找到这个文件 如以D:为基准目录 .白哦是当前所在目录 ./tmp/cat.jpg 以D:/tmp为基准 ./cat.jpg ..表示当前目录的上一层目录 如果D:/tmp/111为基准是以tmp开始查找 ../cat.jpg D:/tmp/111/aaa为基准 ../../cat.jpg也是以tmp开始查找 文件系统存储的文件具体分为两个大类 1.文本文件 utf8就是一个大表实际上表上数据的集合叫做字符。 2.二进制文件 二进制数据 如何区分呢 一个最简单的方式判断就是文件是二进制还是文本直接使用记事本打开如果打开之后能看懂就是文本文件假如看不懂就是二进制文件记事本打开文件就会尝试把当前数据在码表中查询 word文档就是二进制文件功能太多了属于是“富文本”需要用二进制去组织后续文本的操作文本和二进制操作方式完全相同的 文件系统的操作—— 1.创建文件 2.删除文件 3.创建目录 通过一个类的使用——java.io.File(IO-Input和output站在cpu的角度来看待输入输出。 通过File对象描述一个具体的文件File对象可对应一个真实的文件也可对应一个不存在的文件。 File(String pathname)此处参数字符串表示一个路径可以是绝对路径也可以是相对路径 站在操作系统的角度来看目录也是文件操作系统中的文件是更为广义的概念具体里面有很多种不同的类型 1.普通文件通常见到的文件 2.目录文件通常见到的文件夹-(高级,文件夹太土鳖的 1.File filenew File(./test.txt); //路径随意填写可以不存在) 2.file.createNewFile()//创建文件(有可能抛异常 3.file.delete删除掉文件 file.deleteOnExit(),(这个是程序退出后再删除不是立刻删除有的时候可能会有这样一个功能临时文件程序运行的时候搞一个临时文件程序结束了临时文件会自动进行删除。 像是office等生产力软件都有生产临时文件功能这个临时文件就自动存储了你当前的编辑状态如果有人word长期不保存突然断电关机此时你在进行重启由于刚才是非正常关闭临时文件来不及删除是仍然存在的office启动就能知道上次是异常关闭就会提醒你是否要从之前的临时文件恢复未保存的结果。 创建一个目录 import java.io.File;
import java.io.IOException;public class Demo11 {public static void main(String[] args) throws IOException {File filenew File(./d.txt);//创建文件// file.createNewFile();//删除掉文件// file.deleteOnExit();//创建一层目录mk-make,dir-directory//mkdir()一次只能创建一级目录file.mkdir();// mddirs能创建多极目录file.mkdirs();}
} 文件命名也可以起到文件移动的效果 以上文件系统操作都是基于File类完成的。 文件流stream-主要原因操作系统流 文件内容的操作核心步骤四个 1.打开文件 fopen 2.关闭文件 fclose 3.读文件 fread 4.写文件 fwrite JavaIO流是庞大的体系涉及非常多的类不同的类有不同的特性使用方法基本类似。 字节流InputStream,OutputStream ,后续的一些操作字节的类都是衍生自这两个类以操作字节为单位二进制文件 字符流: Reader Write 操作字符为单位文本文件 reader.close:让一个进程打开一个文件是要从系统中一定的资源占据进程pcb文件描述符中的一个表项文件描述符是顺序表长度有限不可扩容如果不释放就会出现“文件资源泄露”这是很严重的问题一旦一直打开文件而不去关闭不用的文件文件描述符就会被占用满导致服务器宕机后续无法打开新的文件-年终奖消失大法 我们平时可以使用try catch finally {close}但是不够优雅 最好使用try with resources //这个就如同sychronized一样自动给你关闭文件但是这块写的不完全需要写使用资源的操作
try(Reader readernew FileReader(d:/test.txt)) read()一次读一个字符-char按照Integer来表示表示两个字符的范围-1表示已经读取完毕eof了 可能会有疑问——utf8格式一个字符三个字节为什么读出字符是两个字节呢 java的char类型是用unicode编码的一个字符两个字节使用完这个方法读取一个字符java标准库内部会帮我们自动转换unicode和utf8一个字符是不同的。 这个会把读到的内容填充到参数cbuf是数组中此处的参数相当于一个“输出型参数” char buf[]new char[1024]; reader.read(buf)//这种写法java中不太常见c)使用偏多通过read就会把一个本来空的数组填充上内容 read(char[]cbuf,int off,int len) 多个小文件都需要读取且需要拼接到一起就用这个方法比如三个文件大小都是100字节 read(cbuf,0,100)
read(cbuf,100,100)
read(cbuf,200,100) 我们如同下图那样先读取txt文件然后在去依次输出这个字符串读到文件末尾退出 import java.io.*;public class Demo12 {public static void main(String[] args) throws IOException {try(Reader readernew FileReader(/Users/lcl/untitled7/src/test.txt)){while(true){char buf[]new char[1024];int nreader.read(buf);if(n-1){System.out.println(读到文件末尾);break;}for(int i0;in;i){System.out.println(buf[i],);}
//String构造方法内部默认是utf8(但是你可以让他变成gbk
String snew String(0,n,gbk);String snew String();System.out.println(s);}}}
}read(byte[]b)-一次读若干字节填满数组的一部分 Scanner一视同仁只是把当前读到的字节数据进行转换不关心这个数据来自于标准输入还是来自文件或者网卡 以前学过的Scanner只是读文本文件的不适合读二进制文件在标准库中还提供了一些具体工具类辅助更方便的读写二进制文件。 import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;public class Demo13 {public static void main(String[] args) throws IOException {try(InputStream inputStreamnew FileInputStream(/Users/lcl/untitled7/src/test.txt)){Scanner scannernew Scanner(inputStream);
//第一段到空格之前的字符串读取test文件读取数据String s scanner.next();;System.out.println(s);String s1 scanner.next();;System.out.println(s1);
第二段空格之前相当于读取一个词String s2 scanner.next();;System.out.println(s2);}}
}输出使用方法和输入十分相似—— 关键的操作是writewrite之前要打开文件用完需要关闭文件输出流对象无论字节流还是字符流会打开文件之后清空文件内容正如我们之前那么写的i,变成了我喜欢你 但是我们假如想在他的后面去写而不去自动删除该怎么做呢可以追加写此时就不进行清空操作。OutputStream使用方式完全一样只不过write方法不能支持“字符串参数”。只能按照字节或者字节数组写入。 Scanner搭配InputStream可以简化代码效果可以不像我们之前那么一点一点读 PrintWritersout,点击里面的out她就是这个类使用一系列方法printf,println)搭配OutputStream 经典面试题写个代码递归目录 深度优先-DFS先中后序递归 广度优先-BFS层序
import java.io.File;
import java.util.Scanner;public class Demo15 {public static void main(String[] args) {Scanner scanner new Scanner(System.in);System.out.println(请输入搜索的根目录);File rootPath new File(scanner.next());System.out.println(请输入删除的关键词);String word scanner.next();if (!rootPath.isDirectory()) {System.out.println(路径不合法);return;}scanDir(rootPath, word);scanner.close();}public static void scanDir(File currentDir, String word) {//先列出当前目录包含哪些内容File[] files currentDir.listFiles();if (files null || files.length 0) {//空目录/非法目录return;}for (File f : files) {System.out.println(f.getAbsolutePath());if (f.isFile()) {//3看当前文件是普通文件看文件名字是否包含word来决定是否删除dealFile(f, word);} else {//4假如是当前文件是目录文件文件夹就再次递归直到找到文件。scanDir(f, word);}}}private static void dealFile(File f, String word) {//是根据文本的是名字删除,假如不存在就返回if (!f.getName().contains(word)) {return;}//打印删除文件的路径System.out.println(要删除的文件 f.getAbsolutePath());f.delete();}
} 2.进行普通文件的复制把一个文件复制成另一个文件 在这之前我们先要想一个问题读文件一次读1024好还是20480好 每次read都是访问硬盘此时把buffer接受的数组变大就能降低访问硬盘次数提高效率buffer大的前提空间需要充足 import java.io.*;
import java.util.Scanner;public class Demo16 {public static void main(String[] args) throws IOException {System.out.println(请输入复制的文件路径);Scanner scanner new Scanner(System.in);String src scanner.next();File srcFile new File(src);if (!srcFile.isFile()) {System.out.println(源文件不存在或者不是一个文件);return;}System.out.println(请输入复制目标文件路径);String dest scanner.next();File destFile new File(dest);//不要求目标文件本身存在但要保证目标文件所在的目录所在。//假设目标文件写作d:/tmp/cat2.jpg,就需保证d:tmp目录所在if (!destFile.getParentFile().isDirectory()) {System.out.println(您的路径非法);return;}
//输入流输出流按照字节流方式去打开这个文件try (InputStream inputStream new FileInputStream(srcFile);OutputStream outputStream new FileOutputStream(destFile)) {while(true){byte[] buffer new byte[1024];int n inputStream.read(buffer);System.out.println(n n);if (n -1) { //读完事了System.out.println(读到eof,结束); break;}
//从0开始写n这么长outputStream.write(buffer, 0, n);}}}}