2018-06-05 第三十天

本文详细介绍了Java中集合类如ArrayList、LinkedList、HashMap等的区别与使用场景,以及各种IO流如FileInputStream、FileOutputStream的功能与操作方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Vector 和 ArrayList

相同点:

1:都是List 的实现的子类。

2:底层都是使用数组实现。

不同点:

1:ArrayList 是一个线程非安全的,效率更高。Vector  是一个线程安全的,效率相对较低。所以在线程安全的情况下优先考虑ArrayList 。

2:ArrayList 相比 Vector 出现的更晚。

3:扩容的规则不同:Vector  %100 扩容。。ArrayList 50% 扩容。

4:Vector  可以使用枚举器  Enumeration 遍历。


二、ArrayList 和 LinkedList

相同点:

1:都属于List 实现的子类。

2:都可以通过序号访问元素。

不同点:

1:ArrayList 底层使用 数组实现。LinkedList 使用双向链表实现。

2:ArrayList 遍历的效率很高。LinkedList 遍历的效率相对较低。

3:ArrayList 根据内容查找,删除元素,插入元素,效率相对较低,因为要移动大量的元素。


三、HashMap 和 HashTable

--WeekHashMap:如果一个对象的引用只被包含在了 WeekHashMap 中,垃圾回收的时候可以被回收掉。

--IdentityHashMap:key 的唯一性 通过 == 比较。实现唯一性,而不是通过key 的equals 来比较内容保证唯一性。


import java.util.Enumeration;

import java.util.Hashtable;

public class TestHashTable {

public static void main(String[] args) {

//Hashtable 是早期的一个 映射关系的容器类。

//早期继承了类 Dictionary 字典类。后来Map 接口出现之后,又实现了Map 接口。

//HashMap 的key 和 value 都可以是null.

//Hashtable 的key 和 value 都不能是null。

//Hashtable 是一个线程安全的容器,HashMap 是一个非安全的,效率更高。

Hashtable table = new Hashtable<>();

table.put("cn", "China");

table.put("uk", "United Kingdom");

table.put("uk", "England");

table.put("us", "America");

// table.put("jp", null);

//早期的Hashtable的遍历方式

//得到所有的key 的枚举器 通过key 可以得到value

Enumeration keys = table.keys();

while (keys.hasMoreElements()) {

String key = keys.nextElement();

String value = table.get(key);

System.out.println(key +" --> "+ value);

}

//得到所有的value ,不能获得对应的key

Enumeration elements = table.elements();

while (elements.hasMoreElements()) {

String value = elements.nextElement();

System.out.println(" --> "+ value);

}

System.out.println(table);

}

}


四、Collection 和 Collections

不同点:一个是容器类的顶层接口,一个是用于操作容器的工具类。

java.util.Arrays:一个用于操作数组的工具类。

java.util.Collections:一个用于操作容器的工具类。

里面封装了很多操作容器对象的各种的方法。


static Enumeration  enumeration(Collection c)

返回一个指定collection 上的枚举。


五、数组-容器

1:数组的长度是不能更改的。

所有的容器的容量都是可以根据需求增长,扩容的。

2:数组进行元素的删除和插入操作的时候,效率比较低。需要移动大量的元素。

HashMap 进行元素的删除和插入操作,效率比较高。LinkedList 效率也很高,就是需要先定位元素的位置(效率低)。

3:数组元素的类型只能是一种。

容器中只要是Object类的实例即可放到容器中,作为元素。

4:数组通过内容查找元素的效率是比较低的。

HashMap 通过内容查找元素效率比较高。

5:数组的元素是连续分配的,那么必须在堆内存中找到连续的指定内存空间才能容纳数组的所有的数据。对内存要求稍微多一些。

链表、二叉树、散列表(一部分要求连续),都没有要求元素必须是连续分配的。

6:数组没有提供任何的封装,所有对元素的操作,都是通过自定义方法来实现的。对数组元素的操作比较麻烦。

所有的容器都被封装成了类,提供了各种对元素操作的方法。


六、IO流

I:input  输入:将外部的数据读取到程序的内存中。

O:output 输出:将程序内存中的数据写出到其他的地方。


File 类:不能对文件读写。

需要通过IO 流来实现对文件的读写。


概念:流就是信息的通道。通过该通道可以实现对文件的读写操作。stream


流的分类:

1:按照数据的流向来分:

1:输入流  可以将外部的数据读取内存中的流   InputStream、Reader

2:输出流  将内存中的数据写出到目的地的流   OutputStream、Writer


2:按照流处理的最小的数据单元来分:

1:字节流  流每次处理的数据的最小单元是 1 byte。 InputStream   OutputStream

2:字符流  流每次处理的数据的最小单元是 1个 char Reader   Writer


字节流,什么类型的数据都可以处理。音频,视频,图片,文本。

字符流,只能处理文本数据。别的类型不能处理。处理字符功能更强。


3:按照处理的数据的源头不同来分:

1:节点流 :直接和数据源相连的流。 FileInputStream FileOutputStream

2:处理流、包装流:以其他的流为数据源的流。 BufferedInputStream  BufferedOutputStream


InputStream:是一个抽象类,是所有的字节输入流的父类。

OutputStream:是一个抽象类,是所有的字节输出流的父类。


Reader:是一个抽象类,是所有的字符输入流的父类

Writer:是一个抽象类,使所有的字符输出流的父类。



七、FileInputStreamFileOutputStream

和IO 相关的类 都在  java.io.*;


FileInputStream: 是InputStream 的子类。

文件字节输入节点流。可以用来将文件中的内容,读取到内存中来。

FileOutputStream: OutputStream 的子类。

文件字节输出节点流。


作用:对文件进行写入数据的。


import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;


public class FileInputOutStreamTest {


public static void main(String[] args) {

testFileInputStream2();

}


//将指定的文件的内容全部读取到内容中,并打印到控制台。

static void testFileInputStream(){

//创建文件对象

File file = new File("res/my_letter.txt");

try {

//创建流对象,在程序内存和 数据源之间建立了信息的通道。

FileInputStream fis = new FileInputStream(file);

//流对象提供了那些功能来读取数据源的信息。

//从数据源读取一个字节的数据,并返回  返回类型是 int。

//read 方法每次被调用,都会导致 流中记录读取位置的游标后移一个字节。

//读取下一个字节的数据。 如果流中不能在读取到数据(读取到了文件or 流的末尾返回-1)

int value = fis.read();

while(value != -1){//没有到达数据的末尾

System.out.print((char)value);

value = fis.read();

}


catch (Exception e) {

e.printStackTrace();

}

}


//将指定的文件的内容全部读取到内容中,并打印到控制台。

static void testFileInputStream1(){

//创建文件对象

File file = new File("res/my_letter.txt");

try {

//创建流对象,在程序内存和 数据源之间建立了信息的通道。

FileInputStream fis = new FileInputStream(file);

//流对象提供了那些功能来读取数据源的信息。

byte[] buf = new byte[10];

//每次通过流从数据源最多可以读取buf.length个字节的数据。 

//被读取的字节数据保存到 数组中。返回的是本次读取到的有效的字节数。

//如果读取到了文件的末尾,返回 -1;

int count = fis.read(buf);

while(count != -1){

//是用字节数组生成字符串对象

String str = new String(buf,0,count);

System.out.print(str);

count = fis.read(buf);

}


//读取数据放到指定数组的区间中。

// fis.read(b, off, len)


catch (Exception e) {

e.printStackTrace();

}

}

//测试其他的方法

static void testFileInputStream2(){

//创建文件对象

File file = new File("res/my_letter.txt");

FileInputStream fis = null;

try {

//创建流对象,在程序内存和 数据源之间建立了信息的通道。

fis = new FileInputStream(file);

//TODO

// int count = 0;

// while((count = fis.read(buf))!= -1){

// String str = new String(buf,0,count);

// System.out.print(str);

// }

//跳过指定的字节数

fis.skip(10);

//得到文件的长度,创建足够大的数据

long len = file.length();

byte[] buf = new byte[(int)len];

fis.read(buf);

String str = new String(buf);

System.out.println(str);

// 得到的文件中可以读取到的有效的字节数据个数

fis.available();


catch (Exception e) {

e.printStackTrace();

}finally {

//要确保程序可以正常的关闭流对象。如果不关闭,那么垃圾回收器都不能正常回收掉。

if(fis != null){

try {

fis.close();

catch (IOException e) {

e.printStackTrace();

}

}

}

}


//对指定的文件写入指定的内容

static void testFileOutputStream(){

//

FileOutputStream fos = null;

try {

//创建流对象,当下面的语句执行的时候,会先将目的地的数据清空,然后再写入。

fos = new FileOutputStream("./res/2.txt",true);//true ,尾部添加,默认为false。

String str = "我女儿喜欢看海底小纵队,她是里面的皮医生!我呢,是呱唧!\n";

//将字符串转换为字节数组

byte[] buf = str.getBytes();

//通过流对象去写出指定内容

//将buf 中的全部的字节数据写出到目的地

fos.write(buf);

//fos.write(int) 将参数 int 的对应的字节数据写出去,每次写出一个字节。把参数的后八位写出去。

catch (Exception e) {

e.printStackTrace();

}finally {

if(fos != null){

try {

fos.close();

catch (IOException e) {

e.printStackTrace();

}

}

}

}

//使用文件输入输出流来实现文件的复制。。如果文件不存在,那么使用FileOutputStream 往外写,那么会创建一个文件。

static void copyFile()throws Exception{

// 使用 文件输入流,读取字节数据,然后将读取到的字节数据,再尾部追加到指定的文件中。

FileInputStream fis = new FileInputStream("c:/dear.jpg");

FileOutputStream fos = new FileOutputStream("c:/dear_copy.jpg",true);

byte[] buf = new byte[1000];

int count = fis.read(buf);

while(count != -1){

//读取多少数据,写出去多少数据

fos.write(buf,0,count);

count = fis.read(buf);

}

fis.close();

fos.close();

}

}

 

八、read 和 write

问:为什么read 读取的是一个字节,返回的却是 int?

答:为了避免读取到的字节数据是八个二进制的1 的数据(-1)。如果读取到了一个 字节数据  是 -1 ,那么和 流的末尾的条件冲突。

即使读取到了8个 二进制的1,也需要转换为int 类型。


使用int 的低八位保存读取到的一个字节数据。

11111111111111111111111111111111

000000000000000000000000 1111 1111 === 255


使用按位与&0xff 来实现需求---使用int 的低八位保存读取到的 一个字节数据。

 

wirte(int value) 只将 value 的低八位写出去。使用(byte)value强转直接将value 的高24位全部砍掉了。

 

九、字符集

1:美国标准信息交换码:ASCII。

单字节的字符集,一共128个字符,只用到了一个字节数据的低7位。最高位是0.  每一个字符都对应着一个唯一的整数。

A<--->65  0<---->48  a<--->97


2: gb2312:是中国早期的一个字符集。兼容了ASCII。用一个字节的数据表示 ASCII 部分的字符。

中文简体,需要两个字节的数据才能表示一个中文字符。


3:gbk:兼容了gb2312. 增加了各个少数民族的符号,繁体中文。

中文简体,需要两个字节的数据才能表示一个中文字符。


4:utf-8:兼容了ASCII。三个字节代表一个中文。


5:Unicode:四个字节的。

 

十、FileReaderFileWriter以及flush

FileReader: 是 Reader 的子类。是用来读取字符的。

FileReader可以每次实现从底层的字节数据中读取一个或者多个字节数据,来保证这些字节数据是一个或者几个完整的字符。要依赖于某个字符集(平台默认的GBK)。从底层读取的是字节数据(整数)去默认的字符集中 找该整数对应的字符。


所有的字符流工作的时候都要依赖于某个字符集。

 

import java.io.FileReader;

import java.io.FileWriter;

import java.io.IOException;

import java.util.Arrays;


public class TestFileReaderWriter {


public static void main(String[] args) {

// testFileReader();

copyTextFile();

}

//从指定的文件中,读取全部的字符

static void testFileReader(){

FileReader fr = null;

try {

//创建字符输入流对象

fr = new FileReader("./res/1.txt");

int value = fr.read();

while(value != -1){

System.out.print((char)value);

value = fr.read();

}

catch (Exception e) {

e.printStackTrace();

}finally {

if(fr != null){

try {

fr.close();

catch (IOException e) {

e.printStackTrace();

}

}

}

}

//使用文件字符输入输出流实现文本的复制

static void copyTextFile(){

FileReader fr = null;

FileWriter fw = null;

try {

fr = new FileReader("./res/my_letter.txt");

fw = new FileWriter("./res/my_letter_todear.txt");

// int value = 0;

// while((value = fr.read())!= -1){

// //将value 写出去

// fw.write(value);

// }

char[] buf = new char[10];

int count = 0;

while((count = fr.read(buf)) != -1){

System.out.println(Arrays.toString(buf));

//这个write 方法,并没有直接将字符数据写出到 目的文件中,

//写到了一个缓冲区中。为了减少直接对磁盘的IO 提高效率

//解决方案:1 把流正常关闭,关闭流的时候,所有的相关的缓冲区中的数据都将会被刷新到目的地。

//2:将缓冲区的数据,刷新出去。

fw.write(buf, 0, count);

}

//将缓冲区的数据,刷新出去到目的地。

fw.flush();

catch (Exception e) {

e.printStackTrace();

}finally {

if(fr != null){

try {

fr.close();

catch (IOException e) {

e.printStackTrace();

}

}

if(fw != null){

try {

fw.close();

catch (IOException e) {

e.printStackTrace();

}

}

}

}

}


十一BufferedInputStreamBufferedOutputStream

带缓冲区的字节输入输出流。为了提高读写的效率。


import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;


public class BufferedInputOutputStreamTest {


public static void main(String[] args) {

long time = System.currentTimeMillis();

copyFile();

long cost = System.currentTimeMillis()-time;

System.out.println("buffered cost = "+cost);


time = System.currentTimeMillis();

try {

copyFile1();

catch (Exception e) {

e.printStackTrace();

}

cost = System.currentTimeMillis()-time;

System.out.println("cost = "+cost);

}


//带缓冲区实现文件复制

static void copyFile(){

BufferedInputStream bis = null;

BufferedOutputStream bos = null;

try {

//这是一个包装流,以其他的流为数据源读取数据。为了提高效率的

bis = new BufferedInputStream(new FileInputStream("c:/dear.jpg"));

bos = new BufferedOutputStream(new FileOutputStream("c:/nan.jpg"));


byte[] buf = new byte[100];

int count = bis.read(buf);

while(count != -1){

bos.write(buf, 0, count);

count = bis.read(buf);

}


catch (Exception e) {

e.printStackTrace();

}finally {

if(bis != null){

try {

bis.close();

catch (IOException e) {

e.printStackTrace();

}

}

if(bos != null){

try {

bos.close();

catch (IOException e) {

e.printStackTrace();

}

}

}

}


//使用文件输入输出流来实现文件的复制。。如果文件不存在,那么使用FileOutputStream 往外写,那么会创建一个文件。

static void copyFile1()throws Exception{

// 使用 文件输入流,读取字节数据,然后将读取到的字节数据,再尾部追加到指定的文件中。

FileInputStream fis = new FileInputStream("c:/dear.jpg");

FileOutputStream fos = new FileOutputStream("c:/dear_copy.jpg",true);


byte[] buf = new byte[1000];


int count = fis.read(buf);

while(count != -1){

//读取多少数据,写出去多少数据

fos.write(buf,0,count);

count = fis.read(buf);

}


fis.close();

fos.close();

}

}




内容概要:论文提出了一种基于空间调制的能量高效分子通信方案(SM-MC),将传输符号分为空间符号和浓度符号。空间符号通过激活单个发射纳米机器人的索引来传输信息,浓度符号则采用传统的浓度移位键控(CSK)调制。相比现有的MIMO分子通信方案,SM-MC避免了链路间干扰,降低了检测复杂度并提高了性能。论文分析了SM-MC及其特SSK-MC的符号错误率(SER),并通过仿真验证了其性能优于传统的MIMO-MC和SISO-MC方案。此外,论文还探讨了分子通信领域的挑战、优势及相关研究工作,强调了空间维度作为新的信息自由度的重要性,并提出了未来的研究方向和技术挑战。 适合人群:具备一定通信理论基础,特别是对纳米通信和分子通信感兴趣的科研人员、研究生和工程师。 使用场景及目标:①理解分子通信中空间调制的工作原理及其优势;②掌握SM-MC系统的具体实现细节,包括发射、接收、检测算法及性能分析;③对比不同分子通信方案(如MIMO-MC、SISO-MC、SSK-MC)的性能差异;④探索分子通信在纳米网络中的应用前景。 其他说明:论文不仅提供了详细的理论分析和仿真验证,还给出了具体的代码实现,帮助读者更好地理解和复现实验结果。此外,论文还讨论了分子通信领域的标准化进展,以及未来可能的研究方向,如混合调制方案、自适应调制技术和纳米机器协作协议等。
package com.temptopn.api; import org.apache.hadoop.io.*; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; import java.util.*; public class TopNReducer extends Reducer<TempKey, IntWritable, Text, Text> { private Text outputKey = new Text(); private Text outputValue = new Text(); @Override protected void reduce(TempKey key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { TreeMap<Integer, Integer> dailyTemps = new TreeMap<>(Collections.reverseOrder()); // 收集每的最高温度 for (IntWritable val : values) { int day = key.getDay(); dailyTemps.merge(day, val.get(), Math::max); } // 取温度最高的两 Iterator<Map.Entry<Integer, Integer>> it = dailyTemps.entrySet().iterator(); int count = 0; while (it.hasNext() && count < 2) { Map.Entry<Integer, Integer> entry = it.next(); String date = String.format("%04d-%02d-%02d", key.getYear(), key.getMonth(), entry.getKey()); outputKey.set(date); outputValue.set(String.format("站点:%s 温度:%d", key.getStationId(), entry.getValue())); context.write(outputKey, outputValue); count++; } } }package com.temptopn.api; import org.apache.hadoop.io.*; import org.apache.hadoop.mapreduce.Mapper; import java.io.IOException; public class TopNMapper extends Mapper<LongWritable, Text, TempKey, IntWritable> { private TempKey tempKey = new TempKey(); private IntWritable temp = new IntWritable(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] parts = value.toString().split("\\s+"); // 要求:至少11列,且温度字段(第8列)有效 if (parts.length >= 11 && !parts[8].equals("-9999")) { try { // 构造复合键(假设站点ID固定为"599970") tempKey.setStationId("599970"); // 硬编码或从配置读取 tempKey.setYear(Integer.parseInt(parts[0])); tempKey.setMonth(Integer.parseInt(parts[1])); tempKey.setDay(Integer.parseInt(parts[2])); // 解析温度(第8列) int temperature = Integer.parseInt(parts[8]); tempKey.setTemp(temperature); temp.set(temperature); context.write(tempKey, temp); } catch (Exception e) { context.getCounter("MAPPER", "PARSE_ERRORS").increment(1); } } else { context.getCounter("MAPPER", "SKIPPED_RECORDS").increment(1); } } }package com.temptopn.api; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class TopNClient { public static void main(String[] args) throws Exception { if (args.length != 2) { System.err.println("Usage: TopNClient <input path> <output path>"); System.exit(1); } Configuration conf = new Configuration(); // 启用递归读取输入目录 conf.set("mapreduce.input.fileinputformat.input.dir.recursive", "true"); Job job = Job.getInstance(conf, "Station599970_Top2_Temperature"); job.setJarByClass(TopNClient.class); // 设置输入路径(自动递归子目录) FileInputFormat.addInputPath(job, new Path(args[0])); // 自动删除已存在的输出目录 Path outputPath = new Path(args[1]); FileSystem fs = outputPath.getFileSystem(conf); if (fs.exists(outputPath)) { fs.delete(outputPath, true); System.out.println("已删除已存在的输出目录: " + args[1]); } FileOutputFormat.setOutputPath(job, outputPath); // Mapper设置 job.setMapperClass(TopNMapper.class); job.setMapOutputKeyClass(TempKey.class); job.setMapOutputValueClass(IntWritable.class); // Shuffle配置 job.setPartitionerClass(TempPartitioner.class); job.setSortComparatorClass(TempSortComparator.class); job.setGroupingComparatorClass(TempGoupingComparator.class); // Reducer设置 job.setReducerClass(TopNReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); System.exit(job.waitForCompletion(true) ? 0 : 1); } }package com.temptopn.api; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; public class TempSortComparator extends WritableComparator { protected TempSortComparator() { super(TempKey.class, true); } @Override public int compare(WritableComparable a, WritableComparable b) { TempKey k1 = (TempKey) a; TempKey k2 = (TempKey) b; int c1 = Integer.compare(k1.getYear(), k2.getYear()); if (c1 == 0) { int c2 = Integer.compare(k1.getMonth(), k2.getMonth()); if (c2 == 0) { return -Integer.compare(k1.getTemp(), k2.getTemp()); // 温度降序 } return c2; } return c1; } }package com.temptopn.api; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.mapreduce.Partitioner; public class TempPartitioner extends Partitioner<TempKey, IntWritable> { @Override public int getPartition(TempKey key, IntWritable value, int numPartitions) { return (key.getYear() * 12 + key.getMonth()) % numPartitions; } }package com.temptopn.api; import org.apache.hadoop.io.WritableComparable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; public class TempKey implements WritableComparable<TempKey> { private int year; private int month; private int day; private int temp; private String stationId; // Getters and Setters public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } public int getTemp() { return temp; } public void setTemp(int temp) { this.temp = temp; } public String getStationId() { return stationId; } public void setStationId(String stationId) { this.stationId = stationId; } @Override public void write(DataOutput out) throws IOException { out.writeInt(year); out.writeInt(month); out.writeInt(day); out.writeInt(temp); out.writeUTF(stationId); } @Override public void readFields(DataInput in) throws IOException { year = in.readInt(); month = in.readInt(); day = in.readInt(); temp = in.readInt(); stationId = in.readUTF(); } @Override public int compareTo(TempKey that) { int c1 = Integer.compare(this.year, that.getYear()); if (c1 == 0) { int c2 = Integer.compare(this.month, that.getMonth()); return (c2 == 0) ? Integer.compare(this.day, that.getDay()) : c2; } return c1; } }package com.temptopn.api; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; public class TempGoupingComparator extends WritableComparator { protected TempGoupingComparator() { super(TempKey.class, true); } @Override public int compare(WritableComparable a, WritableComparable b) { TempKey k1 = (TempKey) a; TempKey k2 = (TempKey) b; int c1 = Integer.compare(k1.getYear(), k2.getYear()); return (c1 == 0) ? Integer.compare(k1.getMonth(), k2.getMonth()) : c1; } }package com.temptopn.api; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart; import javafx.stage.Stage; import java.time.LocalDate; import java.time.Month; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; public class TemperatureVisualization extends Application { // 温度数据记录类 static class TemperatureRecord { LocalDate date; int temperature; public TemperatureRecord(LocalDate date, int temperature) { this.date = date; this.temperature = temperature; } } @Override public void start(Stage primaryStage) { // 准备数据 List<TemperatureRecord> records = prepareData(); // 创建坐标轴 NumberAxis xAxis = new NumberAxis(); xAxis.setLabel("月份"); xAxis.setAutoRanging(false); xAxis.setLowerBound(1); xAxis.setUpperBound(12); xAxis.setTickUnit(1); NumberAxis yAxis = new NumberAxis(); yAxis.setLabel("温度 (×0.1℃)"); // 创建折线图 LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis); lineChart.setTitle("气象站599970 2018温度变化趋势"); // 添加数据系列 XYChart.Series<Number, Number> dailySeries = new XYChart.Series<>(); dailySeries.setName("每日最高温度"); XYChart.Series<Number, Number> monthlyMaxSeries = new XYChart.Series<>(); monthlyMaxSeries.setName("月最高温度趋势"); // 计算每月最高温度 double[] monthlyMax = new double[12]; int[] monthlyCount = new int[12]; for (TemperatureRecord record : records) { int month = record.date.getMonthValue(); double temp = record.temperature; // 添加每日温度点 dailySeries.getData().add(new XYChart.Data<>( month + (record.date.getDayOfMonth() / 31.0), temp )); // 计算每月最高温度 if (temp > monthlyMax[month-1]) { monthlyMax[month-1] = temp; } monthlyCount[month-1]++; } // 添加月最高温度趋势线 for (int i = 0; i < 12; i++) { if (monthlyCount[i] > 0) { monthlyMaxSeries.getData().add(new XYChart.Data<>(i + 1, monthlyMax[i])); } } lineChart.getData().addAll(dailySeries, monthlyMaxSeries); // 设置场景并显示 Scene scene = new Scene(lineChart, 800, 600); primaryStage.setScene(scene); primaryStage.show(); } private List<TemperatureRecord> prepareData() { String data = "2018-01-31\t站点:599970 温度:180\n" + "2018-01-30\t站点:599970 温度:170\n" + "2018-02-28\t站点:599970 温度:200\n" + "2018-02-27\t站点:599970 温度:260\n" + "2018-03-31\t站点:599970 温度:180\n" + "2018-03-30\t站点:599970 温度:180\n" + "2018-04-30\t站点:599970 温度:190\n" + "2018-04-29\t站点:599970 温度:200\n" + "2018-05-31\t站点:599970 温度:280\n" + "2018-05-30\t站点:599970 温度:210\n" + "2018-06-30\t站点:599970 温度:120\n" + "2018-06-29\t站点:599970 温度:160\n" + "2018-07-31\t站点:599970 温度:130\n" + "2018-07-30\t站点:599970 温度:150\n" + "2018-08-31\t站点:599970 温度:200\n" + "2018-08-30\t站点:599970 温度:200\n" + "2018-09-30\t站点:599970 温度:180\n" + "2018-09-29\t站点:599970 温度:210\n" + "2018-10-31\t站点:599970 温度:220\n" + "2018-10-30\t站点:599970 温度:180\n" + "2018-11-30\t站点:599970 温度:290\n" + "2018-11-29\t站点:599970 温度:130\n" + "2018-12-31\t站点:599970 温度:300\n" + "2018-12-30\t站点:599970 温度:180"; List<TemperatureRecord> records = new ArrayList<>(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); for (String line : data.split("\n")) { String[] parts = line.split("\t"); LocalDate date = LocalDate.parse(parts[0], formatter); int temp = Integer.parseInt(parts[1].split("温度:")[1]); records.add(new TemperatureRecord(date, temp)); } return records; } public static void main(String[] args) { launch(args); } } 转为hive代码
07-09
<think>我们有一个Hadoop MapReduce程序用于统计每个份中气温最高的Top2记录。现在需要将其转换为HiveQL实现。 原MapReduce程序逻辑: - Map阶段:读取输入数据,每行数据格式为“份,月份,日,气温”,输出键值对(份,气温) - Reduce阶段:对每个份的所有气温进行排序,然后取最高的两个气温值输出。 在Hive中,我们可以通过窗口函数(Window Function)来实现类似的TopN查询。 步骤: 1. 创建Hive表来存储原始数据。 2. 使用窗口函数对每个份的气温进行降序排序,并为每条记录分配一个行号(rank)。 3. 选择每个份中行号小于等于2的记录。 注意:原始数据可能有多个相同份的记录,且气温可能有重复,因此我们使用ROW_NUMBER()函数,如果相同气温视为不同记录(即允许并列的情况,但这里我们按顺序取前两个,如果要求并列则使用RANK或DENSE_RANK,但题目要求Top2,通常理解为两个记录,所以这里使用ROW_NUMBER)。 假设原始数据表结构为: weather_data (year STRING, month STRING, day STRING, temperature DOUBLE) 我们使用ROW_NUMBER()函数,按份分区,按气温降序排序。 HiveQL查询代码: ```sql SELECT year, temperature, month, day FROM ( SELECT year, temperature, month, day, ROW_NUMBER() OVER (PARTITION BY year ORDER BY temperature DESC) as rank FROM weather_data ) ranked WHERE rank <= 2; ``` 但是,原MapReduce程序只输出份和气温,而这里我们还可以输出月份和日(如果需要的话)。如果不需要,可以只选择份和气温。 如果原始数据中气温字段可能是字符串,可能需要转换为数值类型。这里假设已经是DOUBLE。 另外,注意处理数据质量问题,比如缺失值。在查询中可以提前过滤掉无效的气温记录。 如,在子查询中先过滤掉temperature为NULL的记录: ```sql SELECT year, temperature, month, day FROM ( SELECT year, temperature, month, day, ROW_NUMBER() OVER (PARTITION BY year ORDER BY temperature DESC) as rank FROM weather_data WHERE temperature IS NOT NULL ) ranked WHERE rank <= 2; ``` 这,我们就得到了每个份气温最高的前两条记录。 注意:如果同一个份有两条气温相同的记录,使用ROW_NUMBER()会赋予不同的行号(随机顺序),这可能两条记录不会同时出现(比如一个排第一,一个排第二,但只取前两个,那么两条可能都被取到,也可能只取到一条?)。实际上,ROW_NUMBER()在遇到相同值时,排序是随机的(除非指定了额外的排序列)。因此,为了确保相同气温的记录都能被正确排序,我们可以增加一个排序列(比如月份和日,或者时间戳)来保证顺序。但原MapReduce程序只按气温排序,没有指定相同气温时的顺序,所以结果可能不一致。 如果要求相同气温的记录都保留(即并列),那么我们可以使用RANK函数,但是RANK函数可能会使同一个份返回多于2条记录(比如前两个记录的气温相同,则它们都是第1名,然后第三名就是第3,我们只取前两名,那么第三名就不会被取到,但是如果有三个气温都是第一,那么RANK=1的记录有三个,我们取前两个RANK<=2,那么这三个都会被取到,这就不是Top2了,而是所有并列第一的记录)。所以这里需要明确需求。 根据常见的TopN需求(取最高的N条记录,不处理并列),我们使用ROW_NUMBER,并指定相同气温时的额外排序规则(比如按日期降序,这取最近的记录)或者不指定(由Hive决定,但结果可能不稳定)。为了稳定,我们最好增加一个额外的排序列。 假设我们希望在气温相同时,按月份和日降序(最近的日期排在前面),那么可以这: ```sql SELECT year, temperature, month, day FROM ( SELECT year, temperature, month, day, ROW_NUMBER() OVER ( PARTITION BY year ORDER BY temperature DESC, month DESC, day DESC ) as rank FROM weather_data WHERE temperature IS NOT NULL ) ranked WHERE rank <= 2; ``` 这,每个份取气温最高的两条记录,如果气温相同,则取日期最近的。 如果原MapReduce程序没有指定相同气温时的顺序,那么HiveQL的转换可以只按气温排序,不增加额外排序,但这每次执行结果可能不一致(因为Hive在相同气温时顺序不确定)。为了结果稳定,建议增加额外的排序列。 因此,最终HiveQL查询如上。 创建表的语句(如果还没有表的话): ```sql CREATE TABLE weather_data ( year STRING, month STRING, day STRING, temperature DOUBLE ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' STORED AS TEXTFILE; ``` 然后加载数据。 这,我们就完成了将MapReduce程序转换为HiveQL查询的任务。 相关问题: 1. 如果要求并列排名(比如两个记录都是第一名,则都要)并且取前两名,那么应该怎么修改? 2. HiveQL中窗口函数ROW_NUMBER, RANK, DENSE_RANK有什么区别? 3. 在Hive中如何优化这个TopN查询的性能?</think>### 将Hadoop MapReduce程序转换为HiveQL实现(气温最高Top2统计) #### 步骤分解 1. **原始MapReduce逻辑分析** 典型TopN温度统计的MapReduce流程: - **Map阶段**:提取份和温度字段 ```java // 伪代码 map(key, line): year = line.split(",")[0] temp = line.split(",")[3] emit(year, temp) ``` - **Reduce阶段**:对每个份的温度集合排序并取Top2 ```java // 伪代码 reduce(year, temps): sorted_list = sort(temps) // 降序排序 top2 = sorted_list[0:2] // 取最高两个值 emit(year, top2) ``` 2. **HiveQL转换实现** 使用窗口函数`ROW_NUMBER()`高效实现相同逻辑: ```sql -- 创建原始数据表(假设数据格式:份,月份,日,温度) CREATE TABLE weather_data ( year STRING, month STRING, day STRING, temperature FLOAT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ','; -- Top2温度查询 SELECT year, temperature, month, day FROM ( SELECT year, temperature, month, day, ROW_NUMBER() OVER ( PARTITION BY year ORDER BY temperature DESC ) AS rank FROM weather_data ) ranked WHERE rank <= 2; ``` #### 关键优化说明 1. **窗口函数优势**: - `PARTITION BY year`:按份分组,对应Reduce阶段的key分组 - `ORDER BY temperature DESC`:温度降序排序 - `ROW_NUMBER()`:为每组生成序号,直接过滤Top2 2. **数据倾斜处理**: ```sql -- 若某些份数据量过大,启用Map端聚合 SET hive.map.aggr = true; SET hive.groupby.skewindata = true; ``` 3. **空值处理**: ```sql WHERE temperature IS NOT NULL -- 排除无效记录 ``` #### 性能对比 | 指标 | MapReduce | HiveQL | |---------------|-------------------------------|--------------------------------| | 代码量 | 50+行Java代码 | 10行SQL | | 执行效率 | 需启动JVM,多阶段shuffle | 引擎优化,向量化计算 | | 维护成本 | 需编译部署 | 直接脚本执行 | | 扩展性 | 修改需重新编码 | 调整SQL参数即可 | > **最佳实践**:对于TB级数据,建议对`year`字段分桶提升性能: > ```sql > CREATE TABLE weather_data_bucketed > CLUSTERED BY (year) INTO 32 BUCKETS; > ``` [^1]: Dean J., Ghemawat S. MapReduce: Simplified Data Processing on Large Clusters. OSDI 2004.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值